feat: Use x/precisebank for x/evm keeper (#1960)

Replace x/evmutil EvmBankKeeper usage for x/evm
This commit is contained in:
drklee3 2024-07-10 14:20:12 -07:00 committed by GitHub
parent 9de9de671e
commit d2d661276e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 11 additions and 1174 deletions

View File

@ -550,7 +550,6 @@ func NewApp(
app.accountKeeper, app.accountKeeper,
) )
// TODO: Pass this to evmkeeper.NewKeeper() instead of evmutilKeeper
app.precisebankKeeper = precisebankkeeper.NewKeeper( app.precisebankKeeper = precisebankkeeper.NewKeeper(
app.appCodec, app.appCodec,
keys[precisebanktypes.StoreKey], keys[precisebanktypes.StoreKey],
@ -558,11 +557,13 @@ func NewApp(
app.accountKeeper, app.accountKeeper,
) )
evmBankKeeper := evmutilkeeper.NewEvmBankKeeper(app.evmutilKeeper, app.bankKeeper, app.accountKeeper)
app.evmKeeper = evmkeeper.NewKeeper( app.evmKeeper = evmkeeper.NewKeeper(
appCodec, keys[evmtypes.StoreKey], tkeys[evmtypes.TransientKey], appCodec, keys[evmtypes.StoreKey], tkeys[evmtypes.TransientKey],
govAuthAddr, 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 nil, // precompiled contracts
geth.NewEVM, geth.NewEVM,
options.EVMTrace, options.EVMTrace,

View File

@ -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
}

View File

@ -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))
}

View File

@ -11,8 +11,6 @@ import (
// RegisterInvariants registers the swap module invariants // RegisterInvariants registers the swap module invariants
func RegisterInvariants(ir sdk.InvariantRegistry, bankK types.BankKeeper, k Keeper) { 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)) 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. // 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)) // 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 // AllInvariants runs all invariants of the swap module
func AllInvariants(bankK types.BankKeeper, k Keeper) sdk.Invariant { func AllInvariants(bankK types.BankKeeper, k Keeper) sdk.Invariant {
return func(ctx sdk.Context) (string, bool) { 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 { if res, stop := BackedCoinsInvariant(bankK, k)(ctx); stop {
return res, 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. return CosmosCoinsFullyBackedInvariant(bankK, k)(ctx)
//
// 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
} }
} }

View File

@ -43,7 +43,7 @@ func (suite *invariantTestSuite) SetupValidState() {
for i := 0; i < 4; i++ { for i := 0; i < 4; i++ {
suite.Keeper.SetAccount(suite.Ctx, *types.NewAccount( suite.Keeper.SetAccount(suite.Ctx, *types.NewAccount(
suite.Addrs[i], suite.Addrs[i],
keeper.ConversionMultiplier.QuoRaw(2), sdkmath.NewInt(10000000000),
)) ))
} }
suite.FundModuleAccountWithKava( suite.FundModuleAccountWithKava(
@ -131,45 +131,6 @@ func (suite *invariantTestSuite) runInvariant(route string, invariant func(bankK
return dMessage, dBroken 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 // 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. // 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. // this test verifies that coins cannot be directly sent to the module account.

View File

@ -50,7 +50,6 @@ type Suite struct {
BankKeeper bankkeeper.Keeper BankKeeper bankkeeper.Keeper
AccountKeeper authkeeper.AccountKeeper AccountKeeper authkeeper.AccountKeeper
Keeper keeper.Keeper Keeper keeper.Keeper
EvmBankKeeper keeper.EvmBankKeeper
Addrs []sdk.AccAddress Addrs []sdk.AccAddress
EvmModuleAddr sdk.AccAddress EvmModuleAddr sdk.AccAddress
QueryClient types.QueryClient QueryClient types.QueryClient
@ -68,7 +67,6 @@ func (suite *Suite) SetupTest() {
suite.BankKeeper = tApp.GetBankKeeper() suite.BankKeeper = tApp.GetBankKeeper()
suite.AccountKeeper = tApp.GetAccountKeeper() suite.AccountKeeper = tApp.GetAccountKeeper()
suite.Keeper = tApp.GetEvmutilKeeper() suite.Keeper = tApp.GetEvmutilKeeper()
suite.EvmBankKeeper = keeper.NewEvmBankKeeper(tApp.GetEvmutilKeeper(), suite.BankKeeper, suite.AccountKeeper)
suite.EvmModuleAddr = suite.AccountKeeper.GetModuleAddress(evmtypes.ModuleName) suite.EvmModuleAddr = suite.AccountKeeper.GetModuleAddress(evmtypes.ModuleName)
// test evm user keys that have no minting permissions // test evm user keys that have no minting permissions

View File

@ -19,7 +19,11 @@ type AccountKeeper interface {
// BankKeeper defines the expected bank keeper interface // BankKeeper defines the expected bank keeper interface
type BankKeeper 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 GetSupply(ctx sdk.Context, denom string) sdk.Coin
SpendableCoins(ctx sdk.Context, addr sdk.AccAddress) sdk.Coins SpendableCoins(ctx sdk.Context, addr sdk.AccAddress) sdk.Coins