@@ -1369,6 +1369,132 @@ func TestCloseBidUnknownOrder(t *testing.T) {
13691369 require .Error (t , err )
13701370}
13711371
1372+ // Regression: CreateLease must mark every other open bid on the order lost, close each
1373+ // loser's bid escrow, and never stop the loser loop early. Mainnet had BID_OPEN + open
1374+ // bid escrow when deployment-only onAccountClosed hooks ran against ScopeBid accounts.
1375+ func TestCreateLease_AllOtherBidsOnOrderMarkedLost (t * testing.T ) {
1376+ for _ , tc := range []struct {
1377+ name string
1378+ numLosers int
1379+ }{
1380+ {name : "one_loser" , numLosers : 1 },
1381+ {name : "two_losers" , numLosers : 2 },
1382+ {name : "three_losers" , numLosers : 3 },
1383+ } {
1384+ t .Run (tc .name , func (t * testing.T ) {
1385+ defaultDeposit , err := dtypes .DefaultParams ().MinDepositFor ("uact" )
1386+ require .NoError (t , err )
1387+
1388+ suite := setupTestSuite (t )
1389+ ctx := suite .Context ()
1390+
1391+ deployment := testutil .Deployment (t )
1392+ group := testutil .DeploymentGroup (t , deployment .ID , 0 )
1393+ group .GroupSpec .Resources = testutil .Resources (t , testutil .WithDenom ("uact" ))
1394+
1395+ owner := sdk .MustAccAddressFromBech32 (deployment .ID .Owner )
1396+
1397+ dmsg := & dtypes.MsgCreateDeployment {
1398+ ID : deployment .ID ,
1399+ Groups : dtypes.GroupSpecs {group .GroupSpec },
1400+ Deposit : deposit.Deposit {
1401+ Amount : defaultDeposit ,
1402+ Sources : deposit.Sources {deposit .SourceBalance },
1403+ },
1404+ }
1405+
1406+ suite .PrepareMocks (func (ts * state.TestSuite ) {
1407+ ts .BankKeeper ().
1408+ On ("SendCoinsFromAccountToModule" , mock .Anything , owner , emodule .ModuleName , sdk .NewCoins (dmsg .Deposit .Amount )).
1409+ Return (nil ).Once ()
1410+ })
1411+
1412+ res , err := suite .dhandler (ctx , dmsg )
1413+ require .NoError (t , err )
1414+ require .NotNil (t , res )
1415+
1416+ order , found := suite .MarketKeeper ().GetOrder (ctx , mv1.OrderID {
1417+ Owner : deployment .ID .Owner ,
1418+ DSeq : deployment .ID .DSeq ,
1419+ GSeq : 1 ,
1420+ OSeq : 1 ,
1421+ })
1422+ require .True (t , found )
1423+
1424+ attr := group .GroupSpec .Requirements .Attributes
1425+ totalBids := tc .numLosers + 1
1426+ addrs := make ([]sdk.AccAddress , totalBids )
1427+ bmsgs := make ([]* mvbeta.MsgCreateBid , totalBids )
1428+ for i := 0 ; i < totalBids ; i ++ {
1429+ prov := suite .createProvider (attr )
1430+ addr , err := sdk .AccAddressFromBech32 (prov .Owner )
1431+ require .NoError (t , err )
1432+ addrs [i ] = addr
1433+ bmsgs [i ] = & mvbeta.MsgCreateBid {
1434+ ID : mv1 .MakeBidID (order .ID , addr ),
1435+ Price : sdk .NewDecCoin (sdkutil .DenomUact , sdkmath .NewInt (int64 (i + 1 ))),
1436+ Deposit : deposit.Deposit {
1437+ Amount : mvbeta .DefaultBidMinDepositACT ,
1438+ Sources : deposit.Sources {deposit .SourceBalance },
1439+ },
1440+ }
1441+ }
1442+
1443+ suite .PrepareMocks (func (ts * state.TestSuite ) {
1444+ for i := range bmsgs {
1445+ ts .MockBMEForDeposit (addrs [i ], bmsgs [i ].Deposit .Amount )
1446+ }
1447+ })
1448+
1449+ for _ , bm := range bmsgs {
1450+ res , err := suite .handler (ctx , bm )
1451+ require .NoError (t , err )
1452+ require .NotNil (t , res )
1453+ }
1454+
1455+ winnerID := mv1 .MakeBidID (order .ID , addrs [0 ])
1456+ loserIDs := make ([]mv1.BidID , tc .numLosers )
1457+ for i := 0 ; i < tc .numLosers ; i ++ {
1458+ loserIDs [i ] = mv1 .MakeBidID (order .ID , addrs [i + 1 ])
1459+ }
1460+
1461+ // Loser bid AccountClose settles deposit back to provider; bank hooks must be allowed.
1462+ suite .PrepareMocks (func (ts * state.TestSuite ) {
1463+ ts .BankKeeper ().
1464+ On ("SendCoinsFromModuleToAccount" , mock .Anything , mock .Anything , mock .Anything , mock .Anything ).
1465+ Return (nil ).Maybe ().
1466+ On ("SendCoinsFromModuleToModule" , mock .Anything , mock .Anything , mock .Anything , mock .Anything ).
1467+ Return (nil ).Maybe ()
1468+ })
1469+
1470+ lmsg := & mvbeta.MsgCreateLease {BidID : winnerID }
1471+ leaseRes , err := suite .handler (ctx , lmsg )
1472+ require .NoError (t , err )
1473+ require .NotNil (t , leaseRes )
1474+
1475+ gotW , ok := suite .MarketKeeper ().GetBid (ctx , winnerID )
1476+ require .True (t , ok )
1477+ require .Equal (t , mvbeta .BidActive , gotW .State )
1478+
1479+ winEscrow , err := suite .EscrowKeeper ().GetAccount (ctx , winnerID .ToEscrowAccountID ())
1480+ require .NoError (t , err )
1481+ require .Equal (t , etypes .StateOpen , winEscrow .State .State ,
1482+ "winning bid escrow stays open for the lease" )
1483+
1484+ for _ , lid := range loserIDs {
1485+ got , ok := suite .MarketKeeper ().GetBid (ctx , lid )
1486+ require .True (t , ok , "bid %v" , lid )
1487+ require .Equal (t , mvbeta .BidLost , got .State , "bid %v" , lid )
1488+
1489+ eacc , err := suite .EscrowKeeper ().GetAccount (ctx , lid .ToEscrowAccountID ())
1490+ require .NoError (t , err )
1491+ require .Equal (t , etypes .StateClosed , eacc .State .State ,
1492+ "loser bid escrow must close (no deployment hook on ScopeBid)" )
1493+ }
1494+ })
1495+ }
1496+ }
1497+
13721498func (st * testSuite ) createLease () (mv1.LeaseID , mvbeta.Bid , mvbeta.Order ) {
13731499 st .t .Helper ()
13741500 bid , order := st .createBid ()
0 commit comments