diff --git a/x/dex/contract/abci.go b/x/dex/contract/abci.go index 10dd6514c5..74b500f2be 100644 --- a/x/dex/contract/abci.go +++ b/x/dex/contract/abci.go @@ -74,6 +74,26 @@ func EndBlockerAtomic(ctx sdk.Context, keeper *keeper.Keeper, validContractsInfo return env.validContractsInfo, ctx, true } + // persistent contract rent charges for failed contracts and discard everything else + for _, failedContractAddress := range env.failedContractAddresses.ToOrderedSlice(datastructures.StringComparator) { + cachedContract, err := keeper.GetContract(cachedCtx, failedContractAddress) + if err != nil { + ctx.Logger().Error(fmt.Sprintf("error %s when getting updated contract %s to persist rent balance", err, failedContractAddress)) + continue + } + contract, err := keeper.GetContract(ctx, failedContractAddress) + if err != nil { + ctx.Logger().Error(fmt.Sprintf("error %s when getting contract %s to persist rent balance", err, failedContractAddress)) + continue + } + contract.RentBalance = cachedContract.RentBalance + err = keeper.SetContract(ctx, &contract) + if err != nil { + ctx.Logger().Error(fmt.Sprintf("error %s when persisting contract %s's rent balance", err, failedContractAddress)) + continue + } + } + // restore keeper in-memory state newGoContext := context.WithValue(ctx.Context(), dexutils.DexMemStateContextKey, memStateCopy) return filterNewValidContracts(ctx, env), ctx.WithContext(newGoContext), false diff --git a/x/dex/keeper/utils/wasm.go b/x/dex/keeper/utils/wasm.go index c945ac34fa..8ffb15f712 100644 --- a/x/dex/keeper/utils/wasm.go +++ b/x/dex/keeper/utils/wasm.go @@ -74,7 +74,7 @@ func sudo(sdkCtx sdk.Context, k *keeper.Keeper, contractAddress sdk.AccAddress, func sudoWithoutOutOfGasPanic(ctx sdk.Context, k *keeper.Keeper, contractAddress []byte, wasmMsg []byte, logName string) ([]byte, error) { defer func() { if err := recover(); err != nil { - // only propagate panic if the error is out of gas + // only propagate panic if the error is NOT out of gas if _, ok := err.(sdk.ErrorOutOfGas); !ok { panic(err) } else { diff --git a/x/dex/module_test.go b/x/dex/module_test.go index ab39ba65fd..914d1961dc 100644 --- a/x/dex/module_test.go +++ b/x/dex/module_test.go @@ -551,3 +551,67 @@ func TestEndBlockPanicHandling(t *testing.T) { _, found := dexkeeper.GetLongBookByPrice(ctx, contractAddr.String(), sdk.MustNewDecFromStr("1"), pair.PriceDenom, pair.AssetDenom) require.False(t, found) } + +func TestEndBlockRollbackWithRentCharge(t *testing.T) { + testApp := keepertest.TestApp() + ctx := testApp.BaseApp.NewContext(false, tmproto.Header{Time: time.Now()}) + ctx = ctx.WithContext(context.WithValue(ctx.Context(), dexutils.DexMemStateContextKey, dexcache.NewMemState(testApp.GetKey(types.StoreKey)))) + dexkeeper := testApp.DexKeeper + pair := TEST_PAIR() + // GOOD CONTRACT + testAccount, _ := sdk.AccAddressFromBech32("sei1yezq49upxhunjjhudql2fnj5dgvcwjj87pn2wx") + amounts := sdk.NewCoins(sdk.NewCoin("usei", sdk.NewInt(1000000)), sdk.NewCoin("uusdc", sdk.NewInt(1000000))) + bankkeeper := testApp.BankKeeper + bankkeeper.MintCoins(ctx, minttypes.ModuleName, amounts) + bankkeeper.SendCoinsFromModuleToAccount(ctx, minttypes.ModuleName, testAccount, amounts) + wasm, err := ioutil.ReadFile("./testdata/mars.wasm") + if err != nil { + panic(err) + } + wasmKeeper := testApp.WasmKeeper + contractKeeper := wasmkeeper.NewDefaultPermissionKeeper(&wasmKeeper) + var perm *wasmtypes.AccessConfig + codeId, err := contractKeeper.Create(ctx, testAccount, wasm, perm) + if err != nil { + panic(err) + } + contractAddr, _, err := contractKeeper.Instantiate(ctx, codeId, testAccount, testAccount, []byte(GOOD_CONTRACT_INSTANTIATE), "test", + sdk.NewCoins(sdk.NewCoin("usei", sdk.NewInt(100000)))) + if err != nil { + panic(err) + } + dexkeeper.SetContract(ctx, &types.ContractInfoV2{CodeId: 123, ContractAddr: contractAddr.String(), NeedHook: false, NeedOrderMatching: true, RentBalance: 1}) + dexkeeper.AddRegisteredPair(ctx, contractAddr.String(), pair) + // place one order to a nonexistent contract + dexutils.GetMemState(ctx.Context()).GetBlockOrders(ctx, utils.ContractAddress(contractAddr.String()), utils.GetPairString(&pair)).Add( + &types.Order{ + Id: 2, + Account: testAccount.String(), + ContractAddr: contractAddr.String(), + Price: sdk.MustNewDecFromStr("0.0001"), + Quantity: sdk.MustNewDecFromStr("0.0001"), + PriceDenom: pair.PriceDenom, + AssetDenom: pair.AssetDenom, + OrderType: types.OrderType_LIMIT, + PositionDirection: types.PositionDirection_LONG, + Data: "{\"position_effect\":\"Open\",\"leverage\":\"1\"}", + }, + ) + dexutils.GetMemState(ctx.Context()).GetDepositInfo(ctx, utils.ContractAddress(contractAddr.String())).Add( + &types.DepositInfoEntry{ + Creator: testAccount.String(), + Denom: "uusdc", + Amount: sdk.MustNewDecFromStr("10000"), + }, + ) + + ctx = ctx.WithBlockHeight(1) + testApp.EndBlocker(ctx, abci.RequestEndBlock{}) + // no state change should've been persisted for good contract because it should've run out of gas + matchResult, _ := dexkeeper.GetMatchResultState(ctx, contractAddr.String()) + require.Equal(t, 0, len(matchResult.Orders)) + // rent should still be charged even if the contract failed + contract, err := dexkeeper.GetContract(ctx, contractAddr.String()) + require.Nil(t, err) + require.Zero(t, contract.RentBalance) +}