From 193d3a8145d87b144d49a57c1e5eb71bc664c51c Mon Sep 17 00:00:00 2001 From: lumtis Date: Wed, 30 Oct 2024 19:31:19 +0100 Subject: [PATCH 1/2] refactor: max supply check --- testutil/keeper/mocks/fungible/bank.go | 18 +++++ x/fungible/keeper/deposits_test.go | 4 + x/fungible/keeper/zeta.go | 27 +++++++ x/fungible/keeper/zeta_test.go | 78 +++++++++++++++++++ .../keeper/zevm_message_passing_test.go | 6 ++ x/fungible/types/errors.go | 1 + x/fungible/types/expected_keepers.go | 1 + 7 files changed, 135 insertions(+) diff --git a/testutil/keeper/mocks/fungible/bank.go b/testutil/keeper/mocks/fungible/bank.go index db14226310..1c46b35688 100644 --- a/testutil/keeper/mocks/fungible/bank.go +++ b/testutil/keeper/mocks/fungible/bank.go @@ -13,6 +13,24 @@ type FungibleBankKeeper struct { mock.Mock } +// GetSupply provides a mock function with given fields: ctx, denom +func (_m *FungibleBankKeeper) GetSupply(ctx types.Context, denom string) types.Coin { + ret := _m.Called(ctx, denom) + + if len(ret) == 0 { + panic("no return value specified for GetSupply") + } + + var r0 types.Coin + if rf, ok := ret.Get(0).(func(types.Context, string) types.Coin); ok { + r0 = rf(ctx, denom) + } else { + r0 = ret.Get(0).(types.Coin) + } + + return r0 +} + // MintCoins provides a mock function with given fields: ctx, moduleName, amt func (_m *FungibleBankKeeper) MintCoins(ctx types.Context, moduleName string, amt types.Coins) error { ret := _m.Called(ctx, moduleName, amt) diff --git a/x/fungible/keeper/deposits_test.go b/x/fungible/keeper/deposits_test.go index 3958ae191e..836ce0b61a 100644 --- a/x/fungible/keeper/deposits_test.go +++ b/x/fungible/keeper/deposits_test.go @@ -437,6 +437,10 @@ func TestKeeper_DepositCoinZeta(t *testing.T) { b := sdkk.BankKeeper.GetBalance(ctx, zetaToAddress, config.BaseDenom) require.Equal(t, int64(0), b.Amount.Int64()) errorMint := errors.New("", 1, "error minting coins") + + bankMock.On("GetSupply", ctx, mock.Anything, mock.Anything). + Return(sdk.NewCoin(config.BaseDenom, sdk.NewInt(0))). + Once() bankMock.On("MintCoins", ctx, types.ModuleName, mock.Anything).Return(errorMint).Once() err := k.DepositCoinZeta(ctx, to, amount) require.ErrorIs(t, err, errorMint) diff --git a/x/fungible/keeper/zeta.go b/x/fungible/keeper/zeta.go index bc15a06e44..b1f2329633 100644 --- a/x/fungible/keeper/zeta.go +++ b/x/fungible/keeper/zeta.go @@ -1,6 +1,7 @@ package keeper import ( + "errors" "math/big" sdk "github.com/cosmos/cosmos-sdk/types" @@ -9,9 +10,24 @@ import ( "github.com/zeta-chain/node/x/fungible/types" ) +// ZETAMaxSupplyStr is the maximum mintable ZETA in the fungible module +// 1.85 billion ZETA +const ZETAMaxSupplyStr = "1850000000000000000000000000" + // MintZetaToEVMAccount mints ZETA (gas token) to the given address // NOTE: this method should be used with a temporary context, and it should not be committed if the method returns an error func (k *Keeper) MintZetaToEVMAccount(ctx sdk.Context, to sdk.AccAddress, amount *big.Int) error { + zetaMaxSupply, ok := sdk.NewIntFromString(ZETAMaxSupplyStr) + if !ok { + return errors.New("failed to parse ZETA max supply") + } + + // Check if the max supply is reached + supply := k.bankKeeper.GetSupply(ctx, config.BaseDenom) + if supply.Amount.Add(sdk.NewIntFromBigInt(amount)).GT(zetaMaxSupply) { + return types.ErrMaxSupplyReached + } + coins := sdk.NewCoins(sdk.NewCoin(config.BaseDenom, sdk.NewIntFromBigInt(amount))) // Mint coins if err := k.bankKeeper.MintCoins(ctx, types.ModuleName, coins); err != nil { @@ -23,6 +39,17 @@ func (k *Keeper) MintZetaToEVMAccount(ctx sdk.Context, to sdk.AccAddress, amount } func (k *Keeper) MintZetaToFungibleModule(ctx sdk.Context, amount *big.Int) error { + zetaMaxSupply, ok := sdk.NewIntFromString(ZETAMaxSupplyStr) + if !ok { + return errors.New("failed to parse ZETA max supply") + } + + // Check if the max supply is reached + supply := k.bankKeeper.GetSupply(ctx, config.BaseDenom) + if supply.Amount.Add(sdk.NewIntFromBigInt(amount)).GT(zetaMaxSupply) { + return types.ErrMaxSupplyReached + } + coins := sdk.NewCoins(sdk.NewCoin(config.BaseDenom, sdk.NewIntFromBigInt(amount))) // Mint coins return k.bankKeeper.MintCoins(ctx, types.ModuleName, coins) diff --git a/x/fungible/keeper/zeta_test.go b/x/fungible/keeper/zeta_test.go index 62e41700c1..51e5fe279c 100644 --- a/x/fungible/keeper/zeta_test.go +++ b/x/fungible/keeper/zeta_test.go @@ -2,6 +2,7 @@ package keeper_test import ( "errors" + "github.com/stretchr/testify/mock" "math/big" "testing" @@ -11,6 +12,7 @@ import ( "github.com/zeta-chain/node/cmd/zetacored/config" testkeeper "github.com/zeta-chain/node/testutil/keeper" "github.com/zeta-chain/node/testutil/sample" + "github.com/zeta-chain/node/x/fungible/keeper" "github.com/zeta-chain/node/x/fungible/types" ) @@ -29,6 +31,46 @@ func TestKeeper_MintZetaToEVMAccount(t *testing.T) { require.True(t, bal.Amount.Equal(sdk.NewInt(42))) }) + t.Run("mint the token to reach max supply", func(t *testing.T) { + k, ctx, sdkk, _ := testkeeper.FungibleKeeper(t) + k.GetAuthKeeper().GetModuleAccount(ctx, types.ModuleName) + + acc := sample.Bech32AccAddress() + bal := sdkk.BankKeeper.GetBalance(ctx, acc, config.BaseDenom) + require.True(t, bal.IsZero()) + + zetaMaxSupply, ok := sdk.NewIntFromString(keeper.ZETAMaxSupplyStr) + require.True(t, ok) + + supply := sdkk.BankKeeper.GetSupply(ctx, config.BaseDenom).Amount + + newAmount := zetaMaxSupply.Sub(supply) + + err := k.MintZetaToEVMAccount(ctx, acc, newAmount.BigInt()) + require.NoError(t, err) + bal = sdkk.BankKeeper.GetBalance(ctx, acc, config.BaseDenom) + require.True(t, bal.Amount.Equal(newAmount)) + }) + + t.Run("can't mint more than max supply", func(t *testing.T) { + k, ctx, sdkk, _ := testkeeper.FungibleKeeper(t) + k.GetAuthKeeper().GetModuleAccount(ctx, types.ModuleName) + + acc := sample.Bech32AccAddress() + bal := sdkk.BankKeeper.GetBalance(ctx, acc, config.BaseDenom) + require.True(t, bal.IsZero()) + + zetaMaxSupply, ok := sdk.NewIntFromString(keeper.ZETAMaxSupplyStr) + require.True(t, ok) + + supply := sdkk.BankKeeper.GetSupply(ctx, config.BaseDenom).Amount + + newAmount := zetaMaxSupply.Sub(supply).Add(sdk.NewInt(1)) + + err := k.MintZetaToEVMAccount(ctx, acc, newAmount.BigInt()) + require.ErrorIs(t, err, types.ErrMaxSupplyReached) + }) + coins42 := sdk.NewCoins(sdk.NewCoin(config.BaseDenom, sdk.NewInt(42))) t.Run("should fail if minting fail", func(t *testing.T) { @@ -36,6 +78,9 @@ func TestKeeper_MintZetaToEVMAccount(t *testing.T) { mockBankKeeper := testkeeper.GetFungibleBankMock(t, k) + mockBankKeeper.On("GetSupply", ctx, mock.Anything, mock.Anything). + Return(sdk.NewCoin(config.BaseDenom, sdk.NewInt(0))). + Once() mockBankKeeper.On( "MintCoins", ctx, @@ -55,6 +100,9 @@ func TestKeeper_MintZetaToEVMAccount(t *testing.T) { mockBankKeeper := testkeeper.GetFungibleBankMock(t, k) + mockBankKeeper.On("GetSupply", ctx, mock.Anything, mock.Anything). + Return(sdk.NewCoin(config.BaseDenom, sdk.NewInt(0))). + Once() mockBankKeeper.On( "MintCoins", ctx, @@ -76,3 +124,33 @@ func TestKeeper_MintZetaToEVMAccount(t *testing.T) { mockBankKeeper.AssertExpectations(t) }) } + +func TestKeeper_MintZetaToFungibleModule(t *testing.T) { + t.Run("should mint the token in the specified balance", func(t *testing.T) { + k, ctx, sdkk, _ := testkeeper.FungibleKeeper(t) + acc := k.GetAuthKeeper().GetModuleAccount(ctx, types.ModuleName).GetAddress() + + bal := sdkk.BankKeeper.GetBalance(ctx, acc, config.BaseDenom) + require.True(t, bal.IsZero()) + + err := k.MintZetaToEVMAccount(ctx, acc, big.NewInt(42)) + require.NoError(t, err) + bal = sdkk.BankKeeper.GetBalance(ctx, acc, config.BaseDenom) + require.True(t, bal.Amount.Equal(sdk.NewInt(42))) + }) + + t.Run("can't mint more than max supply", func(t *testing.T) { + k, ctx, sdkk, _ := testkeeper.FungibleKeeper(t) + k.GetAuthKeeper().GetModuleAccount(ctx, types.ModuleName) + + zetaMaxSupply, ok := sdk.NewIntFromString(keeper.ZETAMaxSupplyStr) + require.True(t, ok) + + supply := sdkk.BankKeeper.GetSupply(ctx, config.BaseDenom).Amount + + newAmount := zetaMaxSupply.Sub(supply).Add(sdk.NewInt(1)) + + err := k.MintZetaToFungibleModule(ctx, newAmount.BigInt()) + require.ErrorIs(t, err, types.ErrMaxSupplyReached) + }) +} diff --git a/x/fungible/keeper/zevm_message_passing_test.go b/x/fungible/keeper/zevm_message_passing_test.go index f68fec1f62..f8e1e300d2 100644 --- a/x/fungible/keeper/zevm_message_passing_test.go +++ b/x/fungible/keeper/zevm_message_passing_test.go @@ -146,6 +146,9 @@ func TestKeeper_ZEVMDepositAndCallContract(t *testing.T) { }) require.NoError(t, err) errorMint := errors.New("", 10, "error minting coins") + bankMock.On("GetSupply", ctx, mock.Anything, mock.Anything). + Return(sdk.NewCoin(config.BaseDenom, sdk.NewInt(0))). + Once() bankMock.On("MintCoins", ctx, types.ModuleName, mock.Anything).Return(errorMint).Once() _, err = k.ZETADepositAndCallContract( @@ -296,6 +299,9 @@ func TestKeeper_ZEVMRevertAndCallContract(t *testing.T) { }) require.NoError(t, err) errorMint := errors.New("", 101, "error minting coins") + bankMock.On("GetSupply", ctx, mock.Anything, mock.Anything). + Return(sdk.NewCoin(config.BaseDenom, sdk.NewInt(0))). + Once() bankMock.On("MintCoins", ctx, types.ModuleName, mock.Anything).Return(errorMint).Once() _, err = k.ZETARevertAndCallContract( diff --git a/x/fungible/types/errors.go b/x/fungible/types/errors.go index cf333c9545..cb152f7ffe 100644 --- a/x/fungible/types/errors.go +++ b/x/fungible/types/errors.go @@ -34,4 +34,5 @@ var ( ErrZRC20NilABI = cosmoserrors.Register(ModuleName, 1132, "ZRC20 ABI is nil") ErrZeroAddress = cosmoserrors.Register(ModuleName, 1133, "address cannot be zero") ErrInvalidAmount = cosmoserrors.Register(ModuleName, 1134, "invalid amount") + ErrMaxSupplyReached = cosmoserrors.Register(ModuleName, 1135, "max supply reached") ) diff --git a/x/fungible/types/expected_keepers.go b/x/fungible/types/expected_keepers.go index c8dc02f013..8af00293ed 100644 --- a/x/fungible/types/expected_keepers.go +++ b/x/fungible/types/expected_keepers.go @@ -33,6 +33,7 @@ type BankKeeper interface { amt sdk.Coins, ) error MintCoins(ctx sdk.Context, moduleName string, amt sdk.Coins) error + GetSupply(ctx sdk.Context, denom string) sdk.Coin } type ObserverKeeper interface { From 7ab29ad20835c19d0e1fea0c35517c93846c9eb4 Mon Sep 17 00:00:00 2001 From: lumtis Date: Thu, 31 Oct 2024 11:00:32 +0100 Subject: [PATCH 2/2] create internal func --- x/fungible/keeper/zeta.go | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/x/fungible/keeper/zeta.go b/x/fungible/keeper/zeta.go index b1f2329633..cd4acfcaa7 100644 --- a/x/fungible/keeper/zeta.go +++ b/x/fungible/keeper/zeta.go @@ -1,7 +1,7 @@ package keeper import ( - "errors" + "fmt" "math/big" sdk "github.com/cosmos/cosmos-sdk/types" @@ -17,15 +17,8 @@ const ZETAMaxSupplyStr = "1850000000000000000000000000" // MintZetaToEVMAccount mints ZETA (gas token) to the given address // NOTE: this method should be used with a temporary context, and it should not be committed if the method returns an error func (k *Keeper) MintZetaToEVMAccount(ctx sdk.Context, to sdk.AccAddress, amount *big.Int) error { - zetaMaxSupply, ok := sdk.NewIntFromString(ZETAMaxSupplyStr) - if !ok { - return errors.New("failed to parse ZETA max supply") - } - - // Check if the max supply is reached - supply := k.bankKeeper.GetSupply(ctx, config.BaseDenom) - if supply.Amount.Add(sdk.NewIntFromBigInt(amount)).GT(zetaMaxSupply) { - return types.ErrMaxSupplyReached + if err := k.validateZetaSupply(ctx, amount); err != nil { + return err } coins := sdk.NewCoins(sdk.NewCoin(config.BaseDenom, sdk.NewIntFromBigInt(amount))) @@ -39,18 +32,25 @@ func (k *Keeper) MintZetaToEVMAccount(ctx sdk.Context, to sdk.AccAddress, amount } func (k *Keeper) MintZetaToFungibleModule(ctx sdk.Context, amount *big.Int) error { + if err := k.validateZetaSupply(ctx, amount); err != nil { + return err + } + + coins := sdk.NewCoins(sdk.NewCoin(config.BaseDenom, sdk.NewIntFromBigInt(amount))) + // Mint coins + return k.bankKeeper.MintCoins(ctx, types.ModuleName, coins) +} + +// validateZetaSupply checks if the minted ZETA amount exceeds the maximum supply +func (k *Keeper) validateZetaSupply(ctx sdk.Context, amount *big.Int) error { zetaMaxSupply, ok := sdk.NewIntFromString(ZETAMaxSupplyStr) if !ok { - return errors.New("failed to parse ZETA max supply") + return fmt.Errorf("failed to parse ZETA max supply: %s", ZETAMaxSupplyStr) } - // Check if the max supply is reached supply := k.bankKeeper.GetSupply(ctx, config.BaseDenom) if supply.Amount.Add(sdk.NewIntFromBigInt(amount)).GT(zetaMaxSupply) { return types.ErrMaxSupplyReached } - - coins := sdk.NewCoins(sdk.NewCoin(config.BaseDenom, sdk.NewIntFromBigInt(amount))) - // Mint coins - return k.bankKeeper.MintCoins(ctx, types.ModuleName, coins) + return nil }