From d2d661276ef848fd08ba072e4deb0ca26606fa85 Mon Sep 17 00:00:00 2001 From: drklee3 Date: Wed, 10 Jul 2024 14:20:12 -0700 Subject: [PATCH] feat: Use x/precisebank for x/evm keeper (#1960) Replace x/evmutil EvmBankKeeper usage for x/evm --- app/app.go | 7 +- x/evmutil/keeper/bank_keeper.go | 281 ---------- x/evmutil/keeper/bank_keeper_test.go | 798 --------------------------- x/evmutil/keeper/invariants.go | 50 +- x/evmutil/keeper/invariants_test.go | 41 +- x/evmutil/testutil/suite.go | 2 - x/evmutil/types/expected_keepers.go | 6 +- 7 files changed, 11 insertions(+), 1174 deletions(-) delete mode 100644 x/evmutil/keeper/bank_keeper.go delete mode 100644 x/evmutil/keeper/bank_keeper_test.go diff --git a/app/app.go b/app/app.go index bcc8fb61..7cffd8a7 100644 --- a/app/app.go +++ b/app/app.go @@ -550,7 +550,6 @@ func NewApp( app.accountKeeper, ) - // TODO: Pass this to evmkeeper.NewKeeper() instead of evmutilKeeper app.precisebankKeeper = precisebankkeeper.NewKeeper( app.appCodec, keys[precisebanktypes.StoreKey], @@ -558,11 +557,13 @@ func NewApp( app.accountKeeper, ) - evmBankKeeper := evmutilkeeper.NewEvmBankKeeper(app.evmutilKeeper, app.bankKeeper, app.accountKeeper) app.evmKeeper = evmkeeper.NewKeeper( appCodec, keys[evmtypes.StoreKey], tkeys[evmtypes.TransientKey], govAuthAddr, - app.accountKeeper, evmBankKeeper, app.stakingKeeper, app.feeMarketKeeper, + app.accountKeeper, + app.precisebankKeeper, // x/precisebank in place of x/bank + app.stakingKeeper, + app.feeMarketKeeper, nil, // precompiled contracts geth.NewEVM, options.EVMTrace, diff --git a/x/evmutil/keeper/bank_keeper.go b/x/evmutil/keeper/bank_keeper.go deleted file mode 100644 index 865e6bde..00000000 --- a/x/evmutil/keeper/bank_keeper.go +++ /dev/null @@ -1,281 +0,0 @@ -package keeper - -import ( - "fmt" - - errorsmod "cosmossdk.io/errors" - sdkmath "cosmossdk.io/math" - sdk "github.com/cosmos/cosmos-sdk/types" - sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" - evmtypes "github.com/evmos/ethermint/x/evm/types" - - "github.com/kava-labs/kava/x/evmutil/types" -) - -const ( - // EvmDenom is the gas denom used by the evm - EvmDenom = "akava" - - // CosmosDenom is the gas denom used by the kava app - CosmosDenom = "ukava" -) - -// ConversionMultiplier is the conversion multiplier between akava and ukava -var ConversionMultiplier = sdkmath.NewInt(1_000_000_000_000) - -var _ evmtypes.BankKeeper = EvmBankKeeper{} - -// EvmBankKeeper is a BankKeeper wrapper for the x/evm module to allow the use -// of the 18 decimal akava coin on the evm. -// x/evm consumes gas and send coins by minting and burning akava coins in its module -// account and then sending the funds to the target account. -// This keeper uses both the ukava coin and a separate akava balance to manage the -// extra precision needed by the evm. -type EvmBankKeeper struct { - akavaKeeper Keeper - bk types.BankKeeper - ak types.AccountKeeper -} - -func NewEvmBankKeeper(akavaKeeper Keeper, bk types.BankKeeper, ak types.AccountKeeper) EvmBankKeeper { - return EvmBankKeeper{ - akavaKeeper: akavaKeeper, - bk: bk, - ak: ak, - } -} - -// GetBalance returns the total **spendable** balance of akava for a given account by address. -func (k EvmBankKeeper) GetBalance(ctx sdk.Context, addr sdk.AccAddress, denom string) sdk.Coin { - if denom != EvmDenom { - panic(fmt.Errorf("only evm denom %s is supported by EvmBankKeeper", EvmDenom)) - } - - spendableCoins := k.bk.SpendableCoins(ctx, addr) - ukava := spendableCoins.AmountOf(CosmosDenom) - akava := k.akavaKeeper.GetBalance(ctx, addr) - total := ukava.Mul(ConversionMultiplier).Add(akava) - return sdk.NewCoin(EvmDenom, total) -} - -// SendCoins transfers akava coins from a AccAddress to an AccAddress. -func (k EvmBankKeeper) SendCoins(ctx sdk.Context, senderAddr sdk.AccAddress, recipientAddr sdk.AccAddress, amt sdk.Coins) error { - // SendCoins method is not used by the evm module, but is required by the - // evmtypes.BankKeeper interface. This must be updated if the evm module - // is updated to use SendCoins. - panic("not implemented") -} - -// SendCoinsFromModuleToAccount transfers akava coins from a ModuleAccount to an AccAddress. -// It will panic if the module account does not exist. An error is returned if the recipient -// address is black-listed or if sending the tokens fails. -func (k EvmBankKeeper) SendCoinsFromModuleToAccount(ctx sdk.Context, senderModule string, recipientAddr sdk.AccAddress, amt sdk.Coins) error { - ukava, akava, err := SplitAkavaCoins(amt) - if err != nil { - return err - } - - if ukava.Amount.IsPositive() { - if err := k.bk.SendCoinsFromModuleToAccount(ctx, senderModule, recipientAddr, sdk.NewCoins(ukava)); err != nil { - return err - } - } - - senderAddr := k.GetModuleAddress(senderModule) - if err := k.ConvertOneUkavaToAkavaIfNeeded(ctx, senderAddr, akava); err != nil { - return err - } - - if err := k.akavaKeeper.SendBalance(ctx, senderAddr, recipientAddr, akava); err != nil { - return err - } - - return k.ConvertAkavaToUkava(ctx, recipientAddr) -} - -// SendCoinsFromAccountToModule transfers akava coins from an AccAddress to a ModuleAccount. -// It will panic if the module account does not exist. -func (k EvmBankKeeper) SendCoinsFromAccountToModule(ctx sdk.Context, senderAddr sdk.AccAddress, recipientModule string, amt sdk.Coins) error { - ukava, akavaNeeded, err := SplitAkavaCoins(amt) - if err != nil { - return err - } - - if ukava.IsPositive() { - if err := k.bk.SendCoinsFromAccountToModule(ctx, senderAddr, recipientModule, sdk.NewCoins(ukava)); err != nil { - return err - } - } - - if err := k.ConvertOneUkavaToAkavaIfNeeded(ctx, senderAddr, akavaNeeded); err != nil { - return err - } - - recipientAddr := k.GetModuleAddress(recipientModule) - if err := k.akavaKeeper.SendBalance(ctx, senderAddr, recipientAddr, akavaNeeded); err != nil { - return err - } - - return k.ConvertAkavaToUkava(ctx, recipientAddr) -} - -// MintCoins mints akava coins by minting the equivalent ukava coins and any remaining akava coins. -// It will panic if the module account does not exist or is unauthorized. -func (k EvmBankKeeper) MintCoins(ctx sdk.Context, moduleName string, amt sdk.Coins) error { - ukava, akava, err := SplitAkavaCoins(amt) - if err != nil { - return err - } - - if ukava.IsPositive() { - if err := k.bk.MintCoins(ctx, moduleName, sdk.NewCoins(ukava)); err != nil { - return err - } - } - - recipientAddr := k.GetModuleAddress(moduleName) - if err := k.akavaKeeper.AddBalance(ctx, recipientAddr, akava); err != nil { - return err - } - - return k.ConvertAkavaToUkava(ctx, recipientAddr) -} - -// BurnCoins burns akava coins by burning the equivalent ukava coins and any remaining akava coins. -// It will panic if the module account does not exist or is unauthorized. -func (k EvmBankKeeper) BurnCoins(ctx sdk.Context, moduleName string, amt sdk.Coins) error { - ukava, akava, err := SplitAkavaCoins(amt) - if err != nil { - return err - } - - if ukava.IsPositive() { - if err := k.bk.BurnCoins(ctx, moduleName, sdk.NewCoins(ukava)); err != nil { - return err - } - } - - moduleAddr := k.GetModuleAddress(moduleName) - if err := k.ConvertOneUkavaToAkavaIfNeeded(ctx, moduleAddr, akava); err != nil { - return err - } - - return k.akavaKeeper.RemoveBalance(ctx, moduleAddr, akava) -} - -// IsSendEnabledCoins checks the coins provided and returns an ErrSendDisabled -// if any of the coins are not configured for sending. Returns nil if sending is -// enabled for all provided coins. -func (k EvmBankKeeper) IsSendEnabledCoins(ctx sdk.Context, coins ...sdk.Coin) error { - // IsSendEnabledCoins method is not used by the evm module, but is required by the - // evmtypes.BankKeeper interface. This must be updated if the evm module - // is updated to use IsSendEnabledCoins. - panic("not implemented") -} - -// ConvertOneUkavaToAkavaIfNeeded converts 1 ukava to akava for an address if -// its akava balance is smaller than the akavaNeeded amount. -func (k EvmBankKeeper) ConvertOneUkavaToAkavaIfNeeded(ctx sdk.Context, addr sdk.AccAddress, akavaNeeded sdkmath.Int) error { - akavaBal := k.akavaKeeper.GetBalance(ctx, addr) - if akavaBal.GTE(akavaNeeded) { - return nil - } - - ukavaToStore := sdk.NewCoins(sdk.NewCoin(CosmosDenom, sdk.OneInt())) - if err := k.bk.SendCoinsFromAccountToModule(ctx, addr, types.ModuleName, ukavaToStore); err != nil { - return err - } - - // add 1ukava equivalent of akava to addr - akavaToReceive := ConversionMultiplier - if err := k.akavaKeeper.AddBalance(ctx, addr, akavaToReceive); err != nil { - return err - } - - return nil -} - -// ConvertAkavaToUkava converts all available akava to ukava for a given AccAddress. -func (k EvmBankKeeper) ConvertAkavaToUkava(ctx sdk.Context, addr sdk.AccAddress) error { - totalAkava := k.akavaKeeper.GetBalance(ctx, addr) - ukava, _, err := SplitAkavaCoins(sdk.NewCoins(sdk.NewCoin(EvmDenom, totalAkava))) - if err != nil { - return err - } - - // do nothing if account does not have enough akava for a single ukava - ukavaToReceive := ukava.Amount - if !ukavaToReceive.IsPositive() { - return nil - } - - // remove akava used for converting to ukava - akavaToBurn := ukavaToReceive.Mul(ConversionMultiplier) - finalBal := totalAkava.Sub(akavaToBurn) - if err := k.akavaKeeper.SetBalance(ctx, addr, finalBal); err != nil { - return err - } - - fromAddr := k.GetModuleAddress(types.ModuleName) - if err := k.bk.SendCoins(ctx, fromAddr, addr, sdk.NewCoins(ukava)); err != nil { - return err - } - - return nil -} - -func (k EvmBankKeeper) GetModuleAddress(moduleName string) sdk.AccAddress { - addr := k.ak.GetModuleAddress(moduleName) - if addr == nil { - panic(errorsmod.Wrapf(sdkerrors.ErrUnknownAddress, "module account %s does not exist", moduleName)) - } - return addr -} - -// SplitAkavaCoins splits akava coins to the equivalent ukava coins and any remaining akava balance. -// An error will be returned if the coins are not valid or if the coins are not the akava denom. -func SplitAkavaCoins(coins sdk.Coins) (sdk.Coin, sdkmath.Int, error) { - akava := sdk.ZeroInt() - ukava := sdk.NewCoin(CosmosDenom, sdk.ZeroInt()) - - if len(coins) == 0 { - return ukava, akava, nil - } - - if err := ValidateEvmCoins(coins); err != nil { - return ukava, akava, err - } - - // note: we should always have len(coins) == 1 here since coins cannot have dup denoms after we validate. - coin := coins[0] - remainingBalance := coin.Amount.Mod(ConversionMultiplier) - if remainingBalance.IsPositive() { - akava = remainingBalance - } - ukavaAmount := coin.Amount.Quo(ConversionMultiplier) - if ukavaAmount.IsPositive() { - ukava = sdk.NewCoin(CosmosDenom, ukavaAmount) - } - - return ukava, akava, nil -} - -// ValidateEvmCoins validates the coins from evm is valid and is the EvmDenom (akava). -func ValidateEvmCoins(coins sdk.Coins) error { - if len(coins) == 0 { - return nil - } - - // validate that coins are non-negative, sorted, and no dup denoms - if err := coins.Validate(); err != nil { - return errorsmod.Wrap(sdkerrors.ErrInvalidCoins, coins.String()) - } - - // validate that coin denom is akava - if len(coins) != 1 || coins[0].Denom != EvmDenom { - errMsg := fmt.Sprintf("invalid evm coin denom, only %s is supported", EvmDenom) - return errorsmod.Wrap(sdkerrors.ErrInvalidCoins, errMsg) - } - - return nil -} diff --git a/x/evmutil/keeper/bank_keeper_test.go b/x/evmutil/keeper/bank_keeper_test.go deleted file mode 100644 index 96b3b747..00000000 --- a/x/evmutil/keeper/bank_keeper_test.go +++ /dev/null @@ -1,798 +0,0 @@ -package keeper_test - -import ( - "testing" - "time" - - sdkmath "cosmossdk.io/math" - tmtime "github.com/cometbft/cometbft/types/time" - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/stretchr/testify/suite" - - authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" - vesting "github.com/cosmos/cosmos-sdk/x/auth/vesting/types" - evmtypes "github.com/evmos/ethermint/x/evm/types" - - "github.com/kava-labs/kava/x/evmutil/keeper" - "github.com/kava-labs/kava/x/evmutil/testutil" - "github.com/kava-labs/kava/x/evmutil/types" -) - -type evmBankKeeperTestSuite struct { - testutil.Suite -} - -func (suite *evmBankKeeperTestSuite) SetupTest() { - suite.Suite.SetupTest() -} - -func (suite *evmBankKeeperTestSuite) TestGetBalance_ReturnsSpendable() { - startingCoins := sdk.NewCoins(sdk.NewInt64Coin("ukava", 10)) - startingAkava := sdkmath.NewInt(100) - - now := tmtime.Now() - endTime := now.Add(24 * time.Hour) - bacc := authtypes.NewBaseAccountWithAddress(suite.Addrs[0]) - vacc := vesting.NewContinuousVestingAccount(bacc, startingCoins, now.Unix(), endTime.Unix()) - suite.AccountKeeper.SetAccount(suite.Ctx, vacc) - - err := suite.App.FundAccount(suite.Ctx, suite.Addrs[0], startingCoins) - suite.Require().NoError(err) - err = suite.Keeper.SetBalance(suite.Ctx, suite.Addrs[0], startingAkava) - suite.Require().NoError(err) - - coin := suite.EvmBankKeeper.GetBalance(suite.Ctx, suite.Addrs[0], "akava") - suite.Require().Equal(startingAkava, coin.Amount) - - ctx := suite.Ctx.WithBlockTime(now.Add(12 * time.Hour)) - coin = suite.EvmBankKeeper.GetBalance(ctx, suite.Addrs[0], "akava") - suite.Require().Equal(sdkmath.NewIntFromUint64(5_000_000_000_100), coin.Amount) -} - -func (suite *evmBankKeeperTestSuite) TestGetBalance_NotEvmDenom() { - suite.Require().Panics(func() { - suite.EvmBankKeeper.GetBalance(suite.Ctx, suite.Addrs[0], "ukava") - }) - suite.Require().Panics(func() { - suite.EvmBankKeeper.GetBalance(suite.Ctx, suite.Addrs[0], "busd") - }) -} - -func (suite *evmBankKeeperTestSuite) TestGetBalance() { - tests := []struct { - name string - startingAmount sdk.Coins - expAmount sdkmath.Int - }{ - { - "ukava with akava", - sdk.NewCoins( - sdk.NewInt64Coin("akava", 100), - sdk.NewInt64Coin("ukava", 10), - ), - sdkmath.NewInt(10_000_000_000_100), - }, - { - "just akava", - sdk.NewCoins( - sdk.NewInt64Coin("akava", 100), - sdk.NewInt64Coin("busd", 100), - ), - sdkmath.NewInt(100), - }, - { - "just ukava", - sdk.NewCoins( - sdk.NewInt64Coin("ukava", 10), - sdk.NewInt64Coin("busd", 100), - ), - sdkmath.NewInt(10_000_000_000_000), - }, - { - "no ukava or akava", - sdk.NewCoins(), - sdk.ZeroInt(), - }, - { - "with avaka that is more than 1 ukava", - sdk.NewCoins( - sdk.NewInt64Coin("akava", 20_000_000_000_220), - sdk.NewInt64Coin("ukava", 11), - ), - sdkmath.NewInt(31_000_000_000_220), - }, - } - - for _, tt := range tests { - suite.Run(tt.name, func() { - suite.SetupTest() - - suite.FundAccountWithKava(suite.Addrs[0], tt.startingAmount) - coin := suite.EvmBankKeeper.GetBalance(suite.Ctx, suite.Addrs[0], "akava") - suite.Require().Equal(tt.expAmount, coin.Amount) - }) - } -} - -func (suite *evmBankKeeperTestSuite) TestSendCoinsFromModuleToAccount() { - startingModuleCoins := sdk.NewCoins( - sdk.NewInt64Coin("akava", 200), - sdk.NewInt64Coin("ukava", 100), - ) - tests := []struct { - name string - sendCoins sdk.Coins - startingAccBal sdk.Coins - expAccBal sdk.Coins - hasErr bool - }{ - { - "send more than 1 ukava", - sdk.NewCoins(sdk.NewInt64Coin("akava", 12_000_000_000_010)), - sdk.Coins{}, - sdk.NewCoins( - sdk.NewInt64Coin("akava", 10), - sdk.NewInt64Coin("ukava", 12), - ), - false, - }, - { - "send less than 1 ukava", - sdk.NewCoins(sdk.NewInt64Coin("akava", 122)), - sdk.Coins{}, - sdk.NewCoins( - sdk.NewInt64Coin("akava", 122), - sdk.NewInt64Coin("ukava", 0), - ), - false, - }, - { - "send an exact amount of ukava", - sdk.NewCoins(sdk.NewInt64Coin("akava", 98_000_000_000_000)), - sdk.Coins{}, - sdk.NewCoins( - sdk.NewInt64Coin("akava", 0o0), - sdk.NewInt64Coin("ukava", 98), - ), - false, - }, - { - "send no akava", - sdk.NewCoins(sdk.NewInt64Coin("akava", 0)), - sdk.Coins{}, - sdk.NewCoins( - sdk.NewInt64Coin("akava", 0), - sdk.NewInt64Coin("ukava", 0), - ), - false, - }, - { - "errors if sending other coins", - sdk.NewCoins(sdk.NewInt64Coin("akava", 500), sdk.NewInt64Coin("busd", 1000)), - sdk.Coins{}, - sdk.Coins{}, - true, - }, - { - "errors if not enough total akava to cover", - sdk.NewCoins(sdk.NewInt64Coin("akava", 100_000_000_001_000)), - sdk.Coins{}, - sdk.Coins{}, - true, - }, - { - "errors if not enough ukava to cover", - sdk.NewCoins(sdk.NewInt64Coin("akava", 200_000_000_000_000)), - sdk.Coins{}, - sdk.Coins{}, - true, - }, - { - "converts receiver's akava to ukava if there's enough akava after the transfer", - sdk.NewCoins(sdk.NewInt64Coin("akava", 99_000_000_000_200)), - sdk.NewCoins( - sdk.NewInt64Coin("akava", 999_999_999_900), - sdk.NewInt64Coin("ukava", 1), - ), - sdk.NewCoins( - sdk.NewInt64Coin("akava", 100), - sdk.NewInt64Coin("ukava", 101), - ), - false, - }, - { - "converts all of receiver's akava to ukava even if somehow receiver has more than 1ukava of akava", - sdk.NewCoins(sdk.NewInt64Coin("akava", 12_000_000_000_100)), - sdk.NewCoins( - sdk.NewInt64Coin("akava", 5_999_999_999_990), - sdk.NewInt64Coin("ukava", 1), - ), - sdk.NewCoins( - sdk.NewInt64Coin("akava", 90), - sdk.NewInt64Coin("ukava", 19), - ), - false, - }, - { - "swap 1 ukava for akava if module account doesn't have enough akava", - sdk.NewCoins(sdk.NewInt64Coin("akava", 99_000_000_001_000)), - sdk.NewCoins( - sdk.NewInt64Coin("akava", 200), - sdk.NewInt64Coin("ukava", 1), - ), - sdk.NewCoins( - sdk.NewInt64Coin("akava", 1200), - sdk.NewInt64Coin("ukava", 100), - ), - false, - }, - } - - for _, tt := range tests { - suite.Run(tt.name, func() { - suite.SetupTest() - - suite.FundAccountWithKava(suite.Addrs[0], tt.startingAccBal) - suite.FundModuleAccountWithKava(evmtypes.ModuleName, startingModuleCoins) - - // fund our module with some ukava to account for converting extra akava back to ukava - suite.FundModuleAccountWithKava(types.ModuleName, sdk.NewCoins(sdk.NewInt64Coin("ukava", 10))) - - err := suite.EvmBankKeeper.SendCoinsFromModuleToAccount(suite.Ctx, evmtypes.ModuleName, suite.Addrs[0], tt.sendCoins) - if tt.hasErr { - suite.Require().Error(err) - return - } else { - suite.Require().NoError(err) - } - - // check ukava - ukavaSender := suite.BankKeeper.GetBalance(suite.Ctx, suite.Addrs[0], "ukava") - suite.Require().Equal(tt.expAccBal.AmountOf("ukava").Int64(), ukavaSender.Amount.Int64()) - - // check akava - actualAkava := suite.Keeper.GetBalance(suite.Ctx, suite.Addrs[0]) - suite.Require().Equal(tt.expAccBal.AmountOf("akava").Int64(), actualAkava.Int64()) - }) - } -} - -func (suite *evmBankKeeperTestSuite) TestSendCoinsFromAccountToModule() { - startingAccCoins := sdk.NewCoins( - sdk.NewInt64Coin("akava", 200), - sdk.NewInt64Coin("ukava", 100), - ) - startingModuleCoins := sdk.NewCoins( - sdk.NewInt64Coin("akava", 100_000_000_000), - ) - tests := []struct { - name string - sendCoins sdk.Coins - expSenderCoins sdk.Coins - expModuleCoins sdk.Coins - hasErr bool - }{ - { - "send more than 1 ukava", - sdk.NewCoins(sdk.NewInt64Coin("akava", 12_000_000_000_010)), - sdk.NewCoins(sdk.NewInt64Coin("akava", 190), sdk.NewInt64Coin("ukava", 88)), - sdk.NewCoins(sdk.NewInt64Coin("akava", 100_000_000_010), sdk.NewInt64Coin("ukava", 12)), - false, - }, - { - "send less than 1 ukava", - sdk.NewCoins(sdk.NewInt64Coin("akava", 122)), - sdk.NewCoins(sdk.NewInt64Coin("akava", 78), sdk.NewInt64Coin("ukava", 100)), - sdk.NewCoins(sdk.NewInt64Coin("akava", 100_000_000_122), sdk.NewInt64Coin("ukava", 0)), - false, - }, - { - "send an exact amount of ukava", - sdk.NewCoins(sdk.NewInt64Coin("akava", 98_000_000_000_000)), - sdk.NewCoins(sdk.NewInt64Coin("akava", 200), sdk.NewInt64Coin("ukava", 2)), - sdk.NewCoins(sdk.NewInt64Coin("akava", 100_000_000_000), sdk.NewInt64Coin("ukava", 98)), - false, - }, - { - "send no akava", - sdk.NewCoins(sdk.NewInt64Coin("akava", 0)), - sdk.NewCoins(sdk.NewInt64Coin("akava", 200), sdk.NewInt64Coin("ukava", 100)), - sdk.NewCoins(sdk.NewInt64Coin("akava", 100_000_000_000), sdk.NewInt64Coin("ukava", 0)), - false, - }, - { - "errors if sending other coins", - sdk.NewCoins(sdk.NewInt64Coin("akava", 500), sdk.NewInt64Coin("busd", 1000)), - sdk.Coins{}, - sdk.Coins{}, - true, - }, - { - "errors if have dup coins", - sdk.Coins{ - sdk.NewInt64Coin("akava", 12_000_000_000_000), - sdk.NewInt64Coin("akava", 2_000_000_000_000), - }, - sdk.Coins{}, - sdk.Coins{}, - true, - }, - { - "errors if not enough total akava to cover", - sdk.NewCoins(sdk.NewInt64Coin("akava", 100_000_000_001_000)), - sdk.Coins{}, - sdk.Coins{}, - true, - }, - { - "errors if not enough ukava to cover", - sdk.NewCoins(sdk.NewInt64Coin("akava", 200_000_000_000_000)), - sdk.Coins{}, - sdk.Coins{}, - true, - }, - { - "converts 1 ukava to akava if not enough akava to cover", - sdk.NewCoins(sdk.NewInt64Coin("akava", 99_001_000_000_000)), - sdk.NewCoins(sdk.NewInt64Coin("akava", 999_000_000_200), sdk.NewInt64Coin("ukava", 0)), - sdk.NewCoins(sdk.NewInt64Coin("akava", 101_000_000_000), sdk.NewInt64Coin("ukava", 99)), - false, - }, - { - "converts receiver's akava to ukava if there's enough akava after the transfer", - sdk.NewCoins(sdk.NewInt64Coin("akava", 5_900_000_000_200)), - sdk.NewCoins(sdk.NewInt64Coin("akava", 100_000_000_000), sdk.NewInt64Coin("ukava", 94)), - sdk.NewCoins(sdk.NewInt64Coin("akava", 200), sdk.NewInt64Coin("ukava", 6)), - false, - }, - } - - for _, tt := range tests { - suite.Run(tt.name, func() { - suite.SetupTest() - suite.FundAccountWithKava(suite.Addrs[0], startingAccCoins) - suite.FundModuleAccountWithKava(evmtypes.ModuleName, startingModuleCoins) - - err := suite.EvmBankKeeper.SendCoinsFromAccountToModule(suite.Ctx, suite.Addrs[0], evmtypes.ModuleName, tt.sendCoins) - if tt.hasErr { - suite.Require().Error(err) - return - } else { - suite.Require().NoError(err) - } - - // check sender balance - ukavaSender := suite.BankKeeper.GetBalance(suite.Ctx, suite.Addrs[0], "ukava") - suite.Require().Equal(tt.expSenderCoins.AmountOf("ukava").Int64(), ukavaSender.Amount.Int64()) - actualAkava := suite.Keeper.GetBalance(suite.Ctx, suite.Addrs[0]) - suite.Require().Equal(tt.expSenderCoins.AmountOf("akava").Int64(), actualAkava.Int64()) - - // check module balance - moduleAddr := suite.AccountKeeper.GetModuleAddress(evmtypes.ModuleName) - ukavaSender = suite.BankKeeper.GetBalance(suite.Ctx, moduleAddr, "ukava") - suite.Require().Equal(tt.expModuleCoins.AmountOf("ukava").Int64(), ukavaSender.Amount.Int64()) - actualAkava = suite.Keeper.GetBalance(suite.Ctx, moduleAddr) - suite.Require().Equal(tt.expModuleCoins.AmountOf("akava").Int64(), actualAkava.Int64()) - }) - } -} - -func (suite *evmBankKeeperTestSuite) TestBurnCoins() { - startingUkava := sdkmath.NewInt(100) - tests := []struct { - name string - burnCoins sdk.Coins - expUkava sdkmath.Int - expAkava sdkmath.Int - hasErr bool - akavaStart sdkmath.Int - }{ - { - "burn more than 1 ukava", - sdk.NewCoins(sdk.NewInt64Coin("akava", 12_021_000_000_002)), - sdkmath.NewInt(88), - sdkmath.NewInt(100_000_000_000), - false, - sdkmath.NewInt(121_000_000_002), - }, - { - "burn less than 1 ukava", - sdk.NewCoins(sdk.NewInt64Coin("akava", 122)), - sdkmath.NewInt(100), - sdkmath.NewInt(878), - false, - sdkmath.NewInt(1000), - }, - { - "burn an exact amount of ukava", - sdk.NewCoins(sdk.NewInt64Coin("akava", 98_000_000_000_000)), - sdkmath.NewInt(2), - sdkmath.NewInt(10), - false, - sdkmath.NewInt(10), - }, - { - "burn no akava", - sdk.NewCoins(sdk.NewInt64Coin("akava", 0)), - startingUkava, - sdk.ZeroInt(), - false, - sdk.ZeroInt(), - }, - { - "errors if burning other coins", - sdk.NewCoins(sdk.NewInt64Coin("akava", 500), sdk.NewInt64Coin("busd", 1000)), - startingUkava, - sdkmath.NewInt(100), - true, - sdkmath.NewInt(100), - }, - { - "errors if have dup coins", - sdk.Coins{ - sdk.NewInt64Coin("akava", 12_000_000_000_000), - sdk.NewInt64Coin("akava", 2_000_000_000_000), - }, - startingUkava, - sdk.ZeroInt(), - true, - sdk.ZeroInt(), - }, - { - "errors if burn amount is negative", - sdk.Coins{sdk.Coin{Denom: "akava", Amount: sdkmath.NewInt(-100)}}, - startingUkava, - sdkmath.NewInt(50), - true, - sdkmath.NewInt(50), - }, - { - "errors if not enough akava to cover burn", - sdk.NewCoins(sdk.NewInt64Coin("akava", 100_999_000_000_000)), - sdkmath.NewInt(0), - sdkmath.NewInt(99_000_000_000), - true, - sdkmath.NewInt(99_000_000_000), - }, - { - "errors if not enough ukava to cover burn", - sdk.NewCoins(sdk.NewInt64Coin("akava", 200_000_000_000_000)), - sdkmath.NewInt(100), - sdk.ZeroInt(), - true, - sdk.ZeroInt(), - }, - { - "converts 1 ukava to akava if not enough akava to cover", - sdk.NewCoins(sdk.NewInt64Coin("akava", 12_021_000_000_002)), - sdkmath.NewInt(87), - sdkmath.NewInt(980_000_000_000), - false, - sdkmath.NewInt(1_000_000_002), - }, - } - - for _, tt := range tests { - suite.Run(tt.name, func() { - suite.SetupTest() - startingCoins := sdk.NewCoins( - sdk.NewCoin("ukava", startingUkava), - sdk.NewCoin("akava", tt.akavaStart), - ) - suite.FundModuleAccountWithKava(evmtypes.ModuleName, startingCoins) - - err := suite.EvmBankKeeper.BurnCoins(suite.Ctx, evmtypes.ModuleName, tt.burnCoins) - if tt.hasErr { - suite.Require().Error(err) - return - } else { - suite.Require().NoError(err) - } - - // check ukava - ukavaActual := suite.BankKeeper.GetBalance(suite.Ctx, suite.EvmModuleAddr, "ukava") - suite.Require().Equal(tt.expUkava, ukavaActual.Amount) - - // check akava - akavaActual := suite.Keeper.GetBalance(suite.Ctx, suite.EvmModuleAddr) - suite.Require().Equal(tt.expAkava, akavaActual) - }) - } -} - -func (suite *evmBankKeeperTestSuite) TestMintCoins() { - tests := []struct { - name string - mintCoins sdk.Coins - ukava sdkmath.Int - akava sdkmath.Int - hasErr bool - akavaStart sdkmath.Int - }{ - { - "mint more than 1 ukava", - sdk.NewCoins(sdk.NewInt64Coin("akava", 12_021_000_000_002)), - sdkmath.NewInt(12), - sdkmath.NewInt(21_000_000_002), - false, - sdk.ZeroInt(), - }, - { - "mint less than 1 ukava", - sdk.NewCoins(sdk.NewInt64Coin("akava", 901_000_000_001)), - sdk.ZeroInt(), - sdkmath.NewInt(901_000_000_001), - false, - sdk.ZeroInt(), - }, - { - "mint an exact amount of ukava", - sdk.NewCoins(sdk.NewInt64Coin("akava", 123_000_000_000_000_000)), - sdkmath.NewInt(123_000), - sdk.ZeroInt(), - false, - sdk.ZeroInt(), - }, - { - "mint no akava", - sdk.NewCoins(sdk.NewInt64Coin("akava", 0)), - sdk.ZeroInt(), - sdk.ZeroInt(), - false, - sdk.ZeroInt(), - }, - { - "errors if minting other coins", - sdk.NewCoins(sdk.NewInt64Coin("akava", 500), sdk.NewInt64Coin("busd", 1000)), - sdk.ZeroInt(), - sdkmath.NewInt(100), - true, - sdkmath.NewInt(100), - }, - { - "errors if have dup coins", - sdk.Coins{ - sdk.NewInt64Coin("akava", 12_000_000_000_000), - sdk.NewInt64Coin("akava", 2_000_000_000_000), - }, - sdk.ZeroInt(), - sdk.ZeroInt(), - true, - sdk.ZeroInt(), - }, - { - "errors if mint amount is negative", - sdk.Coins{sdk.Coin{Denom: "akava", Amount: sdkmath.NewInt(-100)}}, - sdk.ZeroInt(), - sdkmath.NewInt(50), - true, - sdkmath.NewInt(50), - }, - { - "adds to existing akava balance", - sdk.NewCoins(sdk.NewInt64Coin("akava", 12_021_000_000_002)), - sdkmath.NewInt(12), - sdkmath.NewInt(21_000_000_102), - false, - sdkmath.NewInt(100), - }, - { - "convert akava balance to ukava if it exceeds 1 ukava", - sdk.NewCoins(sdk.NewInt64Coin("akava", 10_999_000_000_000)), - sdkmath.NewInt(12), - sdkmath.NewInt(1_200_000_001), - false, - sdkmath.NewInt(1_002_200_000_001), - }, - } - - for _, tt := range tests { - suite.Run(tt.name, func() { - suite.SetupTest() - suite.FundModuleAccountWithKava(types.ModuleName, sdk.NewCoins(sdk.NewInt64Coin("ukava", 10))) - suite.FundModuleAccountWithKava(evmtypes.ModuleName, sdk.NewCoins(sdk.NewCoin("akava", tt.akavaStart))) - - err := suite.EvmBankKeeper.MintCoins(suite.Ctx, evmtypes.ModuleName, tt.mintCoins) - if tt.hasErr { - suite.Require().Error(err) - return - } else { - suite.Require().NoError(err) - } - - // check ukava - ukavaActual := suite.BankKeeper.GetBalance(suite.Ctx, suite.EvmModuleAddr, "ukava") - suite.Require().Equal(tt.ukava, ukavaActual.Amount) - - // check akava - akavaActual := suite.Keeper.GetBalance(suite.Ctx, suite.EvmModuleAddr) - suite.Require().Equal(tt.akava, akavaActual) - }) - } -} - -func (suite *evmBankKeeperTestSuite) TestValidateEvmCoins() { - tests := []struct { - name string - coins sdk.Coins - shouldErr bool - }{ - { - "valid coins", - sdk.NewCoins(sdk.NewInt64Coin("akava", 500)), - false, - }, - { - "dup coins", - sdk.Coins{sdk.NewInt64Coin("akava", 500), sdk.NewInt64Coin("akava", 500)}, - true, - }, - { - "not evm coins", - sdk.NewCoins(sdk.NewInt64Coin("ukava", 500)), - true, - }, - { - "negative coins", - sdk.Coins{sdk.Coin{Denom: "akava", Amount: sdkmath.NewInt(-500)}}, - true, - }, - } - for _, tt := range tests { - suite.Run(tt.name, func() { - err := keeper.ValidateEvmCoins(tt.coins) - if tt.shouldErr { - suite.Require().Error(err) - } else { - suite.Require().NoError(err) - } - }) - } -} - -func (suite *evmBankKeeperTestSuite) TestConvertOneUkavaToAkavaIfNeeded() { - akavaNeeded := sdkmath.NewInt(200) - tests := []struct { - name string - startingCoins sdk.Coins - expectedCoins sdk.Coins - success bool - }{ - { - "not enough ukava for conversion", - sdk.NewCoins(sdk.NewInt64Coin("akava", 100)), - sdk.NewCoins(sdk.NewInt64Coin("akava", 100)), - false, - }, - { - "converts 1 ukava to akava", - sdk.NewCoins(sdk.NewInt64Coin("ukava", 10), sdk.NewInt64Coin("akava", 100)), - sdk.NewCoins(sdk.NewInt64Coin("ukava", 9), sdk.NewInt64Coin("akava", 1_000_000_000_100)), - true, - }, - { - "conversion not needed", - sdk.NewCoins(sdk.NewInt64Coin("ukava", 10), sdk.NewInt64Coin("akava", 200)), - sdk.NewCoins(sdk.NewInt64Coin("ukava", 10), sdk.NewInt64Coin("akava", 200)), - true, - }, - } - for _, tt := range tests { - suite.Run(tt.name, func() { - suite.SetupTest() - - suite.FundAccountWithKava(suite.Addrs[0], tt.startingCoins) - err := suite.EvmBankKeeper.ConvertOneUkavaToAkavaIfNeeded(suite.Ctx, suite.Addrs[0], akavaNeeded) - moduleKava := suite.BankKeeper.GetBalance(suite.Ctx, suite.AccountKeeper.GetModuleAddress(types.ModuleName), "ukava") - if tt.success { - suite.Require().NoError(err) - if tt.startingCoins.AmountOf("akava").LT(akavaNeeded) { - suite.Require().Equal(sdk.OneInt(), moduleKava.Amount) - } - } else { - suite.Require().Error(err) - suite.Require().Equal(sdk.ZeroInt(), moduleKava.Amount) - } - - akava := suite.Keeper.GetBalance(suite.Ctx, suite.Addrs[0]) - suite.Require().Equal(tt.expectedCoins.AmountOf("akava"), akava) - ukava := suite.BankKeeper.GetBalance(suite.Ctx, suite.Addrs[0], "ukava") - suite.Require().Equal(tt.expectedCoins.AmountOf("ukava"), ukava.Amount) - }) - } -} - -func (suite *evmBankKeeperTestSuite) TestConvertAkavaToUkava() { - tests := []struct { - name string - startingCoins sdk.Coins - expectedCoins sdk.Coins - }{ - { - "not enough ukava", - sdk.NewCoins(sdk.NewInt64Coin("akava", 100)), - sdk.NewCoins(sdk.NewInt64Coin("akava", 100), sdk.NewInt64Coin("ukava", 0)), - }, - { - "converts akava for 1 ukava", - sdk.NewCoins(sdk.NewInt64Coin("ukava", 10), sdk.NewInt64Coin("akava", 1_000_000_000_003)), - sdk.NewCoins(sdk.NewInt64Coin("ukava", 11), sdk.NewInt64Coin("akava", 3)), - }, - { - "converts more than 1 ukava of akava", - sdk.NewCoins(sdk.NewInt64Coin("ukava", 10), sdk.NewInt64Coin("akava", 8_000_000_000_123)), - sdk.NewCoins(sdk.NewInt64Coin("ukava", 18), sdk.NewInt64Coin("akava", 123)), - }, - } - for _, tt := range tests { - suite.Run(tt.name, func() { - suite.SetupTest() - - err := suite.App.FundModuleAccount(suite.Ctx, types.ModuleName, sdk.NewCoins(sdk.NewInt64Coin("ukava", 10))) - suite.Require().NoError(err) - suite.FundAccountWithKava(suite.Addrs[0], tt.startingCoins) - err = suite.EvmBankKeeper.ConvertAkavaToUkava(suite.Ctx, suite.Addrs[0]) - suite.Require().NoError(err) - akava := suite.Keeper.GetBalance(suite.Ctx, suite.Addrs[0]) - suite.Require().Equal(tt.expectedCoins.AmountOf("akava"), akava) - ukava := suite.BankKeeper.GetBalance(suite.Ctx, suite.Addrs[0], "ukava") - suite.Require().Equal(tt.expectedCoins.AmountOf("ukava"), ukava.Amount) - }) - } -} - -func (suite *evmBankKeeperTestSuite) TestSplitAkavaCoins() { - tests := []struct { - name string - coins sdk.Coins - expectedCoins sdk.Coins - shouldErr bool - }{ - { - "invalid coins", - sdk.NewCoins(sdk.NewInt64Coin("ukava", 500)), - nil, - true, - }, - { - "empty coins", - sdk.NewCoins(), - sdk.NewCoins(), - false, - }, - { - "ukava & akava coins", - sdk.NewCoins(sdk.NewInt64Coin("akava", 8_000_000_000_123)), - sdk.NewCoins(sdk.NewInt64Coin("ukava", 8), sdk.NewInt64Coin("akava", 123)), - false, - }, - { - "only akava", - sdk.NewCoins(sdk.NewInt64Coin("akava", 10_123)), - sdk.NewCoins(sdk.NewInt64Coin("akava", 10_123)), - false, - }, - { - "only ukava", - sdk.NewCoins(sdk.NewInt64Coin("akava", 5_000_000_000_000)), - sdk.NewCoins(sdk.NewInt64Coin("ukava", 5)), - false, - }, - } - for _, tt := range tests { - suite.Run(tt.name, func() { - ukava, akava, err := keeper.SplitAkavaCoins(tt.coins) - if tt.shouldErr { - suite.Require().Error(err) - } else { - suite.Require().NoError(err) - suite.Require().Equal(tt.expectedCoins.AmountOf("ukava"), ukava.Amount) - suite.Require().Equal(tt.expectedCoins.AmountOf("akava"), akava) - } - }) - } -} - -func TestEvmBankKeeperTestSuite(t *testing.T) { - suite.Run(t, new(evmBankKeeperTestSuite)) -} diff --git a/x/evmutil/keeper/invariants.go b/x/evmutil/keeper/invariants.go index b8c880b1..25dc534e 100644 --- a/x/evmutil/keeper/invariants.go +++ b/x/evmutil/keeper/invariants.go @@ -11,8 +11,6 @@ import ( // RegisterInvariants registers the swap module invariants func RegisterInvariants(ir sdk.InvariantRegistry, bankK types.BankKeeper, k Keeper) { - ir.RegisterRoute(types.ModuleName, "fully-backed", FullyBackedInvariant(bankK, k)) - ir.RegisterRoute(types.ModuleName, "small-balances", SmallBalancesInvariant(bankK, k)) ir.RegisterRoute(types.ModuleName, "cosmos-coins-fully-backed", CosmosCoinsFullyBackedInvariant(bankK, k)) // Disable this invariant due to some issues with it requiring some staking params to be set in genesis. // ir.RegisterRoute(types.ModuleName, "backed-conversion-coins", BackedCoinsInvariant(bankK, k)) @@ -21,57 +19,11 @@ func RegisterInvariants(ir sdk.InvariantRegistry, bankK types.BankKeeper, k Keep // AllInvariants runs all invariants of the swap module func AllInvariants(bankK types.BankKeeper, k Keeper) sdk.Invariant { return func(ctx sdk.Context) (string, bool) { - if res, stop := FullyBackedInvariant(bankK, k)(ctx); stop { - return res, stop - } if res, stop := BackedCoinsInvariant(bankK, k)(ctx); stop { return res, stop } - if res, stop := CosmosCoinsFullyBackedInvariant(bankK, k)(ctx); stop { - return res, stop - } - return SmallBalancesInvariant(bankK, k)(ctx) - } -} -// FullyBackedInvariant ensures all minor balances are backed by the coins in the module account. -// -// The module balance can be greater than the sum of all minor balances. This can happen in rare cases -// where the evm module burns tokens. -func FullyBackedInvariant(bankK types.BankKeeper, k Keeper) sdk.Invariant { - broken := false - message := sdk.FormatInvariant(types.ModuleName, "fully backed broken", "sum of minor balances greater than module account") - - return func(ctx sdk.Context) (string, bool) { - totalMinorBalances := sdk.ZeroInt() - k.IterateAllAccounts(ctx, func(acc types.Account) bool { - totalMinorBalances = totalMinorBalances.Add(acc.Balance) - return false - }) - - bankAddr := authtypes.NewModuleAddress(types.ModuleName) - bankBalance := bankK.GetBalance(ctx, bankAddr, CosmosDenom).Amount.Mul(ConversionMultiplier) - - broken = totalMinorBalances.GT(bankBalance) - - return message, broken - } -} - -// SmallBalancesInvariant ensures all minor balances are less than the overflow amount, beyond this they should be converted to the major denom. -func SmallBalancesInvariant(_ types.BankKeeper, k Keeper) sdk.Invariant { - broken := false - message := sdk.FormatInvariant(types.ModuleName, "small balances broken", "minor balances not all less than overflow") - - return func(ctx sdk.Context) (string, bool) { - k.IterateAllAccounts(ctx, func(account types.Account) bool { - if account.Balance.GTE(ConversionMultiplier) { - broken = true - return true - } - return false - }) - return message, broken + return CosmosCoinsFullyBackedInvariant(bankK, k)(ctx) } } diff --git a/x/evmutil/keeper/invariants_test.go b/x/evmutil/keeper/invariants_test.go index 0dcaa1ed..73e07e68 100644 --- a/x/evmutil/keeper/invariants_test.go +++ b/x/evmutil/keeper/invariants_test.go @@ -43,7 +43,7 @@ func (suite *invariantTestSuite) SetupValidState() { for i := 0; i < 4; i++ { suite.Keeper.SetAccount(suite.Ctx, *types.NewAccount( suite.Addrs[i], - keeper.ConversionMultiplier.QuoRaw(2), + sdkmath.NewInt(10000000000), )) } suite.FundModuleAccountWithKava( @@ -131,45 +131,6 @@ func (suite *invariantTestSuite) runInvariant(route string, invariant func(bankK return dMessage, dBroken } -func (suite *invariantTestSuite) TestFullyBackedInvariant() { - // default state is valid - _, broken := suite.runInvariant("fully-backed", keeper.FullyBackedInvariant) - suite.Equal(false, broken) - - suite.SetupValidState() - _, broken = suite.runInvariant("fully-backed", keeper.FullyBackedInvariant) - suite.Equal(false, broken) - - // break invariant by increasing total minor balances above module balance - suite.Keeper.AddBalance(suite.Ctx, suite.Addrs[0], sdk.OneInt()) - - message, broken := suite.runInvariant("fully-backed", keeper.FullyBackedInvariant) - suite.Equal("evmutil: fully backed broken invariant\nsum of minor balances greater than module account\n", message) - suite.Equal(true, broken) -} - -func (suite *invariantTestSuite) TestSmallBalances() { - // default state is valid - _, broken := suite.runInvariant("small-balances", keeper.SmallBalancesInvariant) - suite.Equal(false, broken) - - suite.SetupValidState() - _, broken = suite.runInvariant("small-balances", keeper.SmallBalancesInvariant) - suite.Equal(false, broken) - - // increase minor balance at least above conversion multiplier - suite.Keeper.AddBalance(suite.Ctx, suite.Addrs[0], keeper.ConversionMultiplier) - // add same number of ukava to avoid breaking other invariants - amt := sdk.NewCoins(sdk.NewInt64Coin(keeper.CosmosDenom, 1)) - suite.Require().NoError( - suite.App.FundModuleAccount(suite.Ctx, types.ModuleName, amt), - ) - - message, broken := suite.runInvariant("small-balances", keeper.SmallBalancesInvariant) - suite.Equal("evmutil: small balances broken invariant\nminor balances not all less than overflow\n", message) - suite.Equal(true, broken) -} - // the cosmos-coins-fully-backed invariant depends on 1-to-1 mapping of module balance to erc20s // if coins can be sent directly to the module account, this assumption is broken. // this test verifies that coins cannot be directly sent to the module account. diff --git a/x/evmutil/testutil/suite.go b/x/evmutil/testutil/suite.go index c09ec153..f9e24f73 100644 --- a/x/evmutil/testutil/suite.go +++ b/x/evmutil/testutil/suite.go @@ -50,7 +50,6 @@ type Suite struct { BankKeeper bankkeeper.Keeper AccountKeeper authkeeper.AccountKeeper Keeper keeper.Keeper - EvmBankKeeper keeper.EvmBankKeeper Addrs []sdk.AccAddress EvmModuleAddr sdk.AccAddress QueryClient types.QueryClient @@ -68,7 +67,6 @@ func (suite *Suite) SetupTest() { suite.BankKeeper = tApp.GetBankKeeper() suite.AccountKeeper = tApp.GetAccountKeeper() suite.Keeper = tApp.GetEvmutilKeeper() - suite.EvmBankKeeper = keeper.NewEvmBankKeeper(tApp.GetEvmutilKeeper(), suite.BankKeeper, suite.AccountKeeper) suite.EvmModuleAddr = suite.AccountKeeper.GetModuleAddress(evmtypes.ModuleName) // test evm user keys that have no minting permissions diff --git a/x/evmutil/types/expected_keepers.go b/x/evmutil/types/expected_keepers.go index 31b4e4aa..1992f5d6 100644 --- a/x/evmutil/types/expected_keepers.go +++ b/x/evmutil/types/expected_keepers.go @@ -19,7 +19,11 @@ type AccountKeeper interface { // BankKeeper defines the expected bank keeper interface type BankKeeper interface { - evmtypes.BankKeeper + GetBalance(ctx sdk.Context, addr sdk.AccAddress, denom string) sdk.Coin + SendCoinsFromAccountToModule(ctx sdk.Context, senderAddr sdk.AccAddress, recipientModule string, amt sdk.Coins) error + SendCoinsFromModuleToAccount(ctx sdk.Context, senderModule string, recipientAddr sdk.AccAddress, amt sdk.Coins) error + MintCoins(ctx sdk.Context, moduleName string, amt sdk.Coins) error + BurnCoins(ctx sdk.Context, moduleName string, amt sdk.Coins) error GetSupply(ctx sdk.Context, denom string) sdk.Coin SpendableCoins(ctx sdk.Context, addr sdk.AccAddress) sdk.Coins