From ac2d043412c5bb8ec1c28adc294006477c1b051b Mon Sep 17 00:00:00 2001 From: Eric Warehime Date: Mon, 14 Apr 2025 08:15:13 -0700 Subject: [PATCH 01/11] Remove sdk fork --- go.mod | 3 --- go.sum | 4 ++-- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/go.mod b/go.mod index 53af9851f..b2210dead 100644 --- a/go.mod +++ b/go.mod @@ -252,9 +252,6 @@ require ( replace ( // need this replace when importing cosmos/rosetta pkg cosmossdk.io/core => cosmossdk.io/core v0.11.0 - // need this replace to pick up the store changes (Copy func) in our cosmos-sdk branch - //todo: merge this sdk release - cosmossdk.io/store => github.com/cosmos/cosmos-sdk/store v1.1.2-0.20250319183239-53dea340efc7 // use cosmos fork of keyring github.com/99designs/keyring => github.com/cosmos/keyring v1.2.0 diff --git a/go.sum b/go.sum index e2e716e41..73135bb92 100644 --- a/go.sum +++ b/go.sum @@ -202,6 +202,8 @@ cosmossdk.io/log v1.5.0 h1:dVdzPJW9kMrnAYyMf1duqacoidB9uZIl+7c6z0mnq0g= cosmossdk.io/log v1.5.0/go.mod h1:Tr46PUJjiUthlwQ+hxYtUtPn4D/oCZXAkYevBeh5+FI= cosmossdk.io/math v1.5.0 h1:sbOASxee9Zxdjd6OkzogvBZ25/hP929vdcYcBJQbkLc= cosmossdk.io/math v1.5.0/go.mod h1:AAwwBmUhqtk2nlku174JwSll+/DepUXW3rWIXN5q+Nw= +cosmossdk.io/store v1.1.1 h1:NA3PioJtWDVU7cHHeyvdva5J/ggyLDkyH0hGHl2804Y= +cosmossdk.io/store v1.1.1/go.mod h1:8DwVTz83/2PSI366FERGbWSH7hL6sB7HbYp8bqksNwM= cosmossdk.io/tools/confix v0.1.2 h1:2hoM1oFCNisd0ltSAAZw2i4ponARPmlhuNu3yy0VwI4= cosmossdk.io/tools/confix v0.1.2/go.mod h1:7XfcbK9sC/KNgVGxgLM0BrFbVcR/+6Dg7MFfpx7duYo= cosmossdk.io/x/circuit v0.1.1 h1:KPJCnLChWrxD4jLwUiuQaf5mFD/1m7Omyo7oooefBVQ= @@ -393,8 +395,6 @@ github.com/cosmos/cosmos-proto v1.0.0-beta.5 h1:eNcayDLpip+zVLRLYafhzLvQlSmyab+R github.com/cosmos/cosmos-proto v1.0.0-beta.5/go.mod h1:hQGLpiIUloJBMdQMMWb/4wRApmI9hjHH05nefC0Ojec= github.com/cosmos/cosmos-sdk v0.50.13-0.20250319183239-53dea340efc7 h1:zoOAawQLQXLg+HuSOfmuwtTMC4Qovc23a60+xfoHKUw= github.com/cosmos/cosmos-sdk v0.50.13-0.20250319183239-53dea340efc7/go.mod h1:hrWEFMU1eoXqLJeE6VVESpJDQH67FS1nnMrQIjO2daw= -github.com/cosmos/cosmos-sdk/store v1.1.2-0.20250319183239-53dea340efc7 h1:7EBHeRE/Kmba2A9JlJ9/sOlHhmEHggyyWRD/uGccnC8= -github.com/cosmos/cosmos-sdk/store v1.1.2-0.20250319183239-53dea340efc7/go.mod h1:8DwVTz83/2PSI366FERGbWSH7hL6sB7HbYp8bqksNwM= github.com/cosmos/go-bip39 v1.0.0 h1:pcomnQdrdH22njcAatO0yWojsUnCO3y2tNoV1cb6hHY= github.com/cosmos/go-bip39 v1.0.0/go.mod h1:RNJv0H/pOIVgxw6KS7QeX2a0Uo0aKUlfhZ4xuwvCdJw= github.com/cosmos/go-ethereum v1.10.26-evmos-rc4.0.20250402013457-cf9d288f0147 h1:Hm9aFN6PBpc4YV4JZXJu4cLrOsVguDd9QwfnDmb5LGg= From ef68b495375a6edaef78f2c6e3650403442990e5 Mon Sep 17 00:00:00 2001 From: Eric Warehime Date: Tue, 15 Apr 2025 22:23:10 -0700 Subject: [PATCH 02/11] Use multistore instead of copy --- x/vm/statedb/statedb.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x/vm/statedb/statedb.go b/x/vm/statedb/statedb.go index ae7aaaff8..bee709cbe 100644 --- a/x/vm/statedb/statedb.go +++ b/x/vm/statedb/statedb.go @@ -111,7 +111,7 @@ func (s *StateDB) MultiStoreSnapshot() storetypes.CacheMultiStore { // the cacheCtx multi store is already a CacheMultiStore // so we need to pass a copy of the current state of it cms := s.cacheCtx.MultiStore().(storetypes.CacheMultiStore) - snapshot := cms.Copy() + snapshot := cms.CacheMultiStore() return snapshot } From e808a491f761bf727ee1b2c4935dfbcde3ca548a Mon Sep 17 00:00:00 2001 From: Eric Warehime Date: Wed, 7 May 2025 18:45:38 -0700 Subject: [PATCH 03/11] Add tests --- precompiles/distribution/distribution_test.go | 194 ++++++++++ precompiles/staking/setup_test.go | 18 + precompiles/staking/staking_test.go | 349 ++++++++++++++++++ precompiles/testutil/tracking_multi_store.go | 103 ++++++ x/vm/keeper/statedb_test.go | 97 +++++ x/vm/statedb/integration_test.go | 333 +++++++++++++++++ 6 files changed, 1094 insertions(+) create mode 100644 precompiles/testutil/tracking_multi_store.go diff --git a/precompiles/distribution/distribution_test.go b/precompiles/distribution/distribution_test.go index 53f9d34a2..dd4fc8fbb 100644 --- a/precompiles/distribution/distribution_test.go +++ b/precompiles/distribution/distribution_test.go @@ -10,6 +10,7 @@ import ( chainutil "github.com/cosmos/evm/evmd/testutil" "github.com/cosmos/evm/precompiles/distribution" + "github.com/cosmos/evm/precompiles/testutil" "github.com/cosmos/evm/testutil/constants" evmtypes "github.com/cosmos/evm/x/vm/types" @@ -276,3 +277,196 @@ func (s *PrecompileTestSuite) TestRun() { }) } } + +func (s *PrecompileTestSuite) TestCMS() { + var ( + ctx sdk.Context + err error + ) + testcases := []struct { + name string + malleate func() (common.Address, []byte) + expPass bool + errContains string + }{ + { + name: "pass - set withdraw address transaction", + malleate: func() (common.Address, []byte) { + valAddr, err := sdk.ValAddressFromBech32(s.network.GetValidators()[0].OperatorAddress) + s.Require().NoError(err) + val, _ := s.network.App.StakingKeeper.GetValidator(ctx, valAddr) + coins := sdk.NewCoins(sdk.NewCoin(constants.ExampleAttoDenom, math.NewInt(1e18))) + s.Require().NoError(s.network.App.DistrKeeper.AllocateTokensToValidator(ctx, val, sdk.NewDecCoinsFromCoins(coins...))) + + input, err := s.precompile.Pack( + distribution.SetWithdrawAddressMethod, + s.keyring.GetAddr(0), + s.keyring.GetAddr(0).String(), + ) + s.Require().NoError(err, "failed to pack input") + return s.keyring.GetAddr(0), input + }, + expPass: true, + }, + { + name: "pass - withdraw validator commissions transaction", + malleate: func() (common.Address, []byte) { + hexAddr := common.Bytes2Hex(s.keyring.GetAddr(0).Bytes()) + valAddr, err := sdk.ValAddressFromHex(hexAddr) + s.Require().NoError(err) + caller := common.BytesToAddress(valAddr) + + commAmt := math.LegacyNewDecWithPrec(1000000000000000000, 1) + valCommission := sdk.DecCoins{sdk.NewDecCoinFromDec(constants.ExampleAttoDenom, commAmt)} + // set outstanding rewards + s.Require().NoError(s.network.App.DistrKeeper.SetValidatorOutstandingRewards(ctx, valAddr, types.ValidatorOutstandingRewards{Rewards: valCommission})) + // set commission + s.Require().NoError(s.network.App.DistrKeeper.SetValidatorAccumulatedCommission(ctx, valAddr, types.ValidatorAccumulatedCommission{Commission: valCommission})) + + // set distribution module account balance which pays out the rewards + coins := sdk.NewCoins(sdk.NewCoin(s.bondDenom, commAmt.RoundInt())) + err = s.mintCoinsForDistrMod(ctx, coins) + s.Require().NoError(err, "failed to fund distr module account") + + input, err := s.precompile.Pack( + distribution.WithdrawValidatorCommissionMethod, + valAddr.String(), + ) + s.Require().NoError(err, "failed to pack input") + return caller, input + }, + expPass: true, + }, + { + name: "pass - withdraw delegator rewards transaction", + malleate: func() (common.Address, []byte) { + val := s.network.GetValidators()[0] + ctx, err = s.prepareStakingRewards( + ctx, + stakingRewards{ + Delegator: s.keyring.GetAccAddr(0), + Validator: val, + RewardAmt: testRewardsAmt, + }, + ) + s.Require().NoError(err, "failed to prepare staking rewards") + + input, err := s.precompile.Pack( + distribution.WithdrawDelegatorRewardsMethod, + s.keyring.GetAddr(0), + val.OperatorAddress, + ) + s.Require().NoError(err, "failed to pack input") + + return s.keyring.GetAddr(0), input + }, + expPass: true, + }, + { + name: "pass - claim rewards transaction", + malleate: func() (common.Address, []byte) { + ctx, err = s.prepareStakingRewards( + ctx, + stakingRewards{ + Delegator: s.keyring.GetAccAddr(0), + Validator: s.network.GetValidators()[0], + RewardAmt: testRewardsAmt, + }, + ) + s.Require().NoError(err, "failed to prepare staking rewards") + + input, err := s.precompile.Pack( + distribution.ClaimRewardsMethod, + s.keyring.GetAddr(0), + uint32(2), + ) + s.Require().NoError(err, "failed to pack input") + + return s.keyring.GetAddr(0), input + }, + expPass: true, + }, + { + name: "pass - fund community pool transaction", + malleate: func() (common.Address, []byte) { + input, err := s.precompile.Pack( + distribution.FundCommunityPoolMethod, + s.keyring.GetAddr(0), + big.NewInt(1e18), + ) + s.Require().NoError(err, "failed to pack input") + + return s.keyring.GetAddr(0), input + }, + expPass: true, + }, + { + name: "pass - fund community pool transaction", + malleate: func() (common.Address, []byte) { + input, err := s.precompile.Pack( + distribution.FundCommunityPoolMethod, + s.keyring.GetAddr(0), + big.NewInt(1e18), + ) + s.Require().NoError(err, "failed to pack input") + + return s.keyring.GetAddr(0), input + }, + expPass: true, + }, + } + + for _, tc := range testcases { + s.Run(tc.name, func() { + // setup basic test suite + s.SetupTest() + ctx = s.network.GetContext() + cms := &testutil.TrackingMultiStore{ + Store: s.network.App.BaseApp.CommitMultiStore().CacheMultiStore(), + Writes: 0, + HistoricalStores: nil, + } + ctx = ctx.WithMultiStore(cms) + baseFee := s.network.App.EVMKeeper.GetBaseFee(ctx) + + // malleate testcase + caller, input := tc.malleate() + contract := vm.NewPrecompile(vm.AccountRef(caller), s.precompile, big.NewInt(0), uint64(1e6)) + + contractAddr := contract.Address() + // Build and sign Ethereum transaction + txArgs := evmtypes.EvmTxArgs{ + Input: input, + ChainID: evmtypes.GetEthChainConfig().ChainID, + Nonce: 0, + To: &contractAddr, + Amount: nil, + GasLimit: 1000000, + GasPrice: chainutil.ExampleMinGasPrices.BigInt(), + GasFeeCap: baseFee, + GasTipCap: big.NewInt(1), + Accesses: &gethtypes.AccessList{}, + } + msgEthereumTx, err := s.factory.GenerateMsgEthereumTx(s.keyring.GetPrivKey(0), txArgs) + s.Require().NoError(err, "failed to generate Ethereum message") + + signedMsg, err := s.factory.SignMsgEthereumTx(s.keyring.GetPrivKey(0), msgEthereumTx) + s.Require().NoError(err, "failed to sign Ethereum message") + + resp, err := s.network.App.EVMKeeper.EthereumTx(ctx, &signedMsg) + + // Check results + if tc.expPass { + s.Require().NoError(err, "expected no error when running the precompile") + s.Require().NotNil(resp.Ret, "expected returned bytes not to be nil") + testutil.ValidateWrites(s.T(), cms, 2) + } else { + s.Require().Error(err, "expected error to be returned when running the precompile") + s.Require().Nil(resp.Ret, "expected returned bytes to be nil") + s.Require().ErrorContains(err, tc.errContains) + // Writes once because of gas usage + testutil.ValidateWrites(s.T(), cms, 1) + } + }) + } +} diff --git a/precompiles/staking/setup_test.go b/precompiles/staking/setup_test.go index 6cc087fe8..9d99a2e8a 100644 --- a/precompiles/staking/setup_test.go +++ b/precompiles/staking/setup_test.go @@ -1,6 +1,11 @@ package staking_test import ( + sdkmath "cosmossdk.io/math" + sdk "github.com/cosmos/cosmos-sdk/types" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + testconstants "github.com/cosmos/evm/testutil/constants" "testing" "github.com/stretchr/testify/suite" @@ -30,8 +35,21 @@ func TestPrecompileUnitTestSuite(t *testing.T) { func (s *PrecompileTestSuite) SetupTest() { keyring := testkeyring.New(2) + customGenesis := network.CustomGenesisState{} + // mint some coin to fee collector + coins := sdk.NewCoins(sdk.NewCoin(testconstants.ExampleAttoDenom, sdkmath.NewInt(1000000000000000))) + balances := []banktypes.Balance{ + { + Address: authtypes.NewModuleAddress(authtypes.FeeCollectorName).String(), + Coins: coins, + }, + } + bankGenesis := banktypes.DefaultGenesisState() + bankGenesis.Balances = balances + customGenesis[banktypes.ModuleName] = bankGenesis nw := network.NewUnitTestNetwork( network.WithPreFundedAccounts(keyring.GetAllAccAddrs()...), + network.WithCustomGenesis(customGenesis), ) grpcHandler := grpc.NewIntegrationHandler(nw) txFactory := factory.New(nw, grpcHandler) diff --git a/precompiles/staking/staking_test.go b/precompiles/staking/staking_test.go index 0e835fff9..c5fc4a0ed 100644 --- a/precompiles/staking/staking_test.go +++ b/precompiles/staking/staking_test.go @@ -12,6 +12,7 @@ import ( chainutil "github.com/cosmos/evm/evmd/testutil" "github.com/cosmos/evm/precompiles/authorization" "github.com/cosmos/evm/precompiles/staking" + "github.com/cosmos/evm/precompiles/testutil" testconstants "github.com/cosmos/evm/testutil/constants" testkeyring "github.com/cosmos/evm/testutil/integration/os/keyring" "github.com/cosmos/evm/x/vm/statedb" @@ -501,3 +502,351 @@ func (s *PrecompileTestSuite) TestRun() { }) } } + +// TestCMS tests the cache multistore writes. +func (s *PrecompileTestSuite) TestCMS() { + var ctx sdk.Context + testcases := []struct { + name string + malleate func(delegator, grantee testkeyring.Key) []byte + gas uint64 + expPass bool + expKeeperPass bool + errContains string + }{ + { + "fail - contract gas limit is < gas cost to run a query / tx", + func(delegator, grantee testkeyring.Key) []byte { + // TODO: why is this required? + err := s.CreateAuthorization(ctx, delegator.AccAddr, grantee.AccAddr, staking.DelegateAuthz, nil) + s.Require().NoError(err) + + input, err := s.precompile.Pack( + staking.DelegateMethod, + delegator.Addr, + s.network.GetValidators()[0].GetOperator(), + big.NewInt(1000), + ) + s.Require().NoError(err, "failed to pack input") + return input + }, + 8000, + false, + false, + "gas too low", + }, + { + "pass - delegate transaction", + func(delegator, grantee testkeyring.Key) []byte { + err := s.CreateAuthorization(ctx, delegator.AccAddr, grantee.AccAddr, staking.DelegateAuthz, nil) + s.Require().NoError(err) + + input, err := s.precompile.Pack( + staking.DelegateMethod, + delegator.Addr, + s.network.GetValidators()[0].GetOperator(), + big.NewInt(1000), + ) + s.Require().NoError(err, "failed to pack input") + return input + }, + 1000000, + true, + true, + "", + }, + { + "pass - undelegate transaction", + func(delegator, grantee testkeyring.Key) []byte { + err := s.CreateAuthorization(ctx, delegator.AccAddr, grantee.AccAddr, staking.UndelegateAuthz, nil) + s.Require().NoError(err) + + input, err := s.precompile.Pack( + staking.UndelegateMethod, + delegator.Addr, + s.network.GetValidators()[0].GetOperator(), + big.NewInt(1), + ) + s.Require().NoError(err, "failed to pack input") + return input + }, + 1000000, + true, + true, + "", + }, + { + "pass - redelegate transaction", + func(delegator, grantee testkeyring.Key) []byte { + err := s.CreateAuthorization(ctx, delegator.AccAddr, grantee.AccAddr, staking.RedelegateAuthz, nil) + s.Require().NoError(err) + + input, err := s.precompile.Pack( + staking.RedelegateMethod, + delegator.Addr, + s.network.GetValidators()[0].GetOperator(), + s.network.GetValidators()[1].GetOperator(), + big.NewInt(1), + ) + s.Require().NoError(err, "failed to pack input") + return input + }, + 1000000, + true, + true, + "failed to redelegate tokens", + }, + { + "pass - cancel unbonding delegation transaction", + func(delegator, grantee testkeyring.Key) []byte { + valAddr, err := sdk.ValAddressFromBech32(s.network.GetValidators()[0].GetOperator()) + s.Require().NoError(err) + // add unbonding delegation to staking keeper + ubd := stakingtypes.NewUnbondingDelegation( + delegator.AccAddr, + valAddr, + ctx.BlockHeight(), + time.Now().Add(time.Hour), + math.NewInt(1000), + 0, + s.network.App.StakingKeeper.ValidatorAddressCodec(), + s.network.App.AccountKeeper.AddressCodec(), + ) + err = s.network.App.StakingKeeper.SetUnbondingDelegation(ctx, ubd) + s.Require().NoError(err, "failed to set unbonding delegation") + + err = s.CreateAuthorization(ctx, delegator.AccAddr, grantee.AccAddr, staking.CancelUnbondingDelegationAuthz, nil) + s.Require().NoError(err) + + // Needs to be called after setting unbonding delegation + // In order to mimic the coins being added to the unboding pool + coin := sdk.NewCoin(testconstants.ExampleAttoDenom, math.NewInt(1000)) + err = s.network.App.BankKeeper.SendCoinsFromModuleToModule(ctx, stakingtypes.BondedPoolName, stakingtypes.NotBondedPoolName, sdk.Coins{coin}) + s.Require().NoError(err, "failed to send coins from module to module") + + input, err := s.precompile.Pack( + staking.CancelUnbondingDelegationMethod, + delegator.Addr, + s.network.GetValidators()[0].GetOperator(), + big.NewInt(1000), + big.NewInt(ctx.BlockHeight()), + ) + s.Require().NoError(err, "failed to pack input") + return input + }, + 1000000, + true, + true, + "", + }, + { + "pass - delegation query", + func(delegator, _ testkeyring.Key) []byte { + input, err := s.precompile.Pack( + staking.DelegationMethod, + delegator.Addr, + s.network.GetValidators()[0].GetOperator(), + ) + s.Require().NoError(err, "failed to pack input") + return input + }, + 1000000, + true, + true, + "", + }, + { + "pass - validator query", + func(_, _ testkeyring.Key) []byte { + valAddr, err := sdk.ValAddressFromBech32(s.network.GetValidators()[0].OperatorAddress) + s.Require().NoError(err) + + input, err := s.precompile.Pack( + staking.ValidatorMethod, + common.BytesToAddress(valAddr.Bytes()), + ) + s.Require().NoError(err, "failed to pack input") + return input + }, + 1000000, + true, + true, + "", + }, + { + "pass - redelgation query", + func(delegator, _ testkeyring.Key) []byte { + valAddr1, err := sdk.ValAddressFromBech32(s.network.GetValidators()[0].GetOperator()) + s.Require().NoError(err) + valAddr2, err := sdk.ValAddressFromBech32(s.network.GetValidators()[1].GetOperator()) + s.Require().NoError(err) + // add redelegation to staking keeper + redelegation := stakingtypes.NewRedelegation( + delegator.AccAddr, + valAddr1, + valAddr2, + ctx.BlockHeight(), + time.Now().Add(time.Hour), + math.NewInt(1000), + math.LegacyNewDec(1), + 0, + s.network.App.StakingKeeper.ValidatorAddressCodec(), + s.network.App.AccountKeeper.AddressCodec(), + ) + + err = s.network.App.StakingKeeper.SetRedelegation(ctx, redelegation) + s.Require().NoError(err, "failed to set redelegation") + + input, err := s.precompile.Pack( + staking.RedelegationMethod, + delegator.Addr, + s.network.GetValidators()[0].GetOperator(), + s.network.GetValidators()[1].GetOperator(), + ) + s.Require().NoError(err, "failed to pack input") + return input + }, + 1000000, + true, + true, + "", + }, + { + "pass - delegation query - read only", + func(delegator, _ testkeyring.Key) []byte { + input, err := s.precompile.Pack( + staking.DelegationMethod, + delegator.Addr, + s.network.GetValidators()[0].GetOperator(), + ) + s.Require().NoError(err, "failed to pack input") + return input + }, + 1000000, + true, + true, + "", + }, + { + "pass - unbonding delegation query", + func(delegator, _ testkeyring.Key) []byte { + valAddr, err := sdk.ValAddressFromBech32(s.network.GetValidators()[0].GetOperator()) + s.Require().NoError(err) + // add unbonding delegation to staking keeper + ubd := stakingtypes.NewUnbondingDelegation( + delegator.AccAddr, + valAddr, + ctx.BlockHeight(), + time.Now().Add(time.Hour), + math.NewInt(1000), + 0, + s.network.App.StakingKeeper.ValidatorAddressCodec(), + s.network.App.AccountKeeper.AddressCodec(), + ) + err = s.network.App.StakingKeeper.SetUnbondingDelegation(ctx, ubd) + s.Require().NoError(err, "failed to set unbonding delegation") + + // Needs to be called after setting unbonding delegation + // In order to mimic the coins being added to the unboding pool + coin := sdk.NewCoin(testconstants.ExampleAttoDenom, math.NewInt(1000)) + err = s.network.App.BankKeeper.SendCoinsFromModuleToModule(ctx, stakingtypes.BondedPoolName, stakingtypes.NotBondedPoolName, sdk.Coins{coin}) + s.Require().NoError(err, "failed to send coins from module to module") + + input, err := s.precompile.Pack( + staking.UnbondingDelegationMethod, + delegator.Addr, + s.network.GetValidators()[0].GetOperator(), + ) + s.Require().NoError(err, "failed to pack input") + return input + }, + 1000000, + true, + true, + "", + }, + { + "fail - invalid method", + func(_, _ testkeyring.Key) []byte { + return []byte("invalid") + }, + 100000, // use gas > 0 to avoid doing gas estimation + false, + true, + "no method with id", + }, + } + + for _, tc := range testcases { + s.Run(tc.name, func() { + // setup basic test suite + s.SetupTest() + ctx = s.network.GetContext().WithBlockTime(time.Now()) + + cms := &testutil.TrackingMultiStore{ + Store: s.network.App.BaseApp.CommitMultiStore().CacheMultiStore(), + Writes: 0, + HistoricalStores: nil, + } + ctx = ctx.WithMultiStore(cms) + baseFee := s.network.App.EVMKeeper.GetBaseFee(ctx) + + delegator := s.keyring.GetKey(0) + grantee := s.keyring.GetKey(1) + + contract := vm.NewPrecompile(vm.AccountRef(delegator.Addr), s.precompile, big.NewInt(0), tc.gas) + contractAddr := contract.Address() + + // malleate testcase + input := tc.malleate(delegator, grantee) + + // Build and sign Ethereum transaction + txArgs := evmtypes.EvmTxArgs{ + Input: input, + ChainID: evmtypes.GetEthChainConfig().ChainID, + Nonce: 0, + To: &contractAddr, + Amount: nil, + GasLimit: tc.gas, + GasPrice: chainutil.ExampleMinGasPrices.BigInt(), + GasFeeCap: baseFee, + GasTipCap: big.NewInt(1), + Accesses: ðtypes.AccessList{}, + } + + msgEthereumTx, err := s.factory.GenerateMsgEthereumTx(s.keyring.GetPrivKey(0), txArgs) + s.Require().NoError(err, "failed to generate Ethereum message") + signedMsg, err := s.factory.SignMsgEthereumTx(s.keyring.GetPrivKey(0), msgEthereumTx) + s.Require().NoError(err, "failed to sign Ethereum message") + + resp, err := s.network.App.EVMKeeper.EthereumTx(ctx, &signedMsg) + + // Check results + if tc.expPass { + s.Require().NoError(err, "expected no error when running the precompile") + s.Require().NotNil(resp.Ret, "expected returned bytes not to be nil") + testutil.ValidateWrites(s.T(), cms, 2) + } else { + if tc.expKeeperPass { + s.Require().Contains(resp.VmError, tc.errContains, + "expected error to be returned when running the precompile") + s.Require().Nil(resp.Ret, "expected returned bytes to be nil") + consumed := ctx.GasMeter().GasConsumed() + // LessThanOrEqual because the gas is consumed before the error is returned + s.Require().LessOrEqual(tc.gas, consumed, "expected gas consumed to be equal to gas limit") + // Writes once because of gas usage + testutil.ValidateWrites(s.T(), cms, 1) + } else { + s.Require().Error(err, "expected error to be returned when running the precompile") + s.Require().Nil(resp, "expected returned response to be nil") + s.Require().ErrorContains(err, tc.errContains) + testutil.ValidateWrites(s.T(), cms, 0) + } + consumed := ctx.GasMeter().GasConsumed() + // LessThanOrEqual because the gas is consumed before the error is returned + s.Require().LessOrEqual(tc.gas, consumed, "expected gas consumed to be equal to gas limit") + + } + }) + } +} diff --git a/precompiles/testutil/tracking_multi_store.go b/precompiles/testutil/tracking_multi_store.go new file mode 100644 index 000000000..2ccbea51d --- /dev/null +++ b/precompiles/testutil/tracking_multi_store.go @@ -0,0 +1,103 @@ +package testutil + +import ( + "github.com/stretchr/testify/require" + "io" + "testing" + "time" + + storetypes "cosmossdk.io/store/types" +) + +// TrackingMultiStore implements the CacheMultiStore interface, but tracks calls to the Write interface as +// well ass the branches created via CacheMultiStore() +type TrackingMultiStore struct { + Store storetypes.CacheMultiStore + Writes int + WriteTS *time.Time + HistoricalStores []*TrackingMultiStore +} + +func (t *TrackingMultiStore) GetStoreType() storetypes.StoreType { + return t.Store.GetStoreType() +} + +func (t *TrackingMultiStore) CacheWrap() storetypes.CacheWrap { + return t.Store.CacheWrap() +} + +func (t *TrackingMultiStore) CacheWrapWithTrace(w io.Writer, tc storetypes.TraceContext) storetypes.CacheWrap { + return t.Store.CacheWrapWithTrace(w, tc) +} + +func (t *TrackingMultiStore) CacheMultiStoreWithVersion(version int64) (storetypes.CacheMultiStore, error) { + return t.CacheMultiStoreWithVersion(version) +} + +func (t *TrackingMultiStore) GetStore(key storetypes.StoreKey) storetypes.Store { + return t.Store.GetStore(key) +} + +func (t *TrackingMultiStore) GetKVStore(key storetypes.StoreKey) storetypes.KVStore { + return t.Store.GetKVStore(key) +} + +func (t *TrackingMultiStore) TracingEnabled() bool { + return t.Store.TracingEnabled() +} + +func (t *TrackingMultiStore) SetTracer(w io.Writer) storetypes.MultiStore { + return t.Store.SetTracer(w) +} + +func (t *TrackingMultiStore) SetTracingContext(context storetypes.TraceContext) storetypes.MultiStore { + return t.Store.SetTracingContext(context) +} + +func (t *TrackingMultiStore) LatestVersion() int64 { + return t.Store.LatestVersion() +} + +func (t *TrackingMultiStore) Copy() storetypes.CacheMultiStore { + return t.Store.Copy() +} + +func (t *TrackingMultiStore) Write() { + t.Writes++ + now := time.Now() + t.WriteTS = &now + t.Store.Write() +} + +func (t *TrackingMultiStore) CacheMultiStore() storetypes.CacheMultiStore { + cms := t.Store.CacheMultiStore() + tms := &TrackingMultiStore{Store: cms} + t.HistoricalStores = append(t.HistoricalStores, tms) + return tms +} + +// ValidateWrites tests the number of writes to a tree of tracking multi stores, +// and that all the writes in a branching cache multistore/cascade upwards +func ValidateWrites(t *testing.T, ms *TrackingMultiStore, expWrites int) { + toTestCMS := []*TrackingMultiStore{ms} + writes := 0 + var writeTS *time.Time + for len(toTestCMS) > 0 { + currCMS := toTestCMS[0] + toTestCMS = toTestCMS[1:] + writes += currCMS.Writes + if currCMS.WriteTS != nil { + if writeTS != nil { + // assert that branches with higher depth were written first + require.True(t, currCMS.WriteTS.Before(*writeTS)) + } + writeTS = currCMS.WriteTS + } + if len(currCMS.HistoricalStores) > 0 { + for _, s := range currCMS.HistoricalStores { + toTestCMS = append(toTestCMS, s) + } + } + } + require.Equal(t, expWrites, writes) +} diff --git a/x/vm/keeper/statedb_test.go b/x/vm/keeper/statedb_test.go index f2467cbf2..b9a619c6e 100644 --- a/x/vm/keeper/statedb_test.go +++ b/x/vm/keeper/statedb_test.go @@ -2,6 +2,8 @@ package keeper_test import ( "bytes" + "cosmossdk.io/store/metrics" + pruningtypes "cosmossdk.io/store/pruning/types" "fmt" "math/big" "testing" @@ -1083,3 +1085,98 @@ func (suite *KeeperTestSuite) TestDeleteAccount() { }) } } + +func TestCacheMultiStore_SharedEphemeralState_GrandChild(t *testing.T) { + /* + This test shows that: + - The parent store, a first-level cache (childCMS), and a second-level cache (grandChildCMS) + can all share the same in-memory (ephemeral) state, even after the parent commits. + - Each "Write()" merges the lower-level cache's changes up one level: + grandChildCMS → childCMS → parentStore. + - Consequently, any direct parent writes can be observed immediately by both child and grandchild. + - Similarly, the child sees the grandchild’s changes only after the grandchild calls Write(), + and the parent sees them only after the child calls Write(). + */ + + // 1) Create the parent MultiStore (in memory) and mount a KVStore. + db := dbm.NewMemDB() + parentMS := NewStore(db, log.NewNopLogger(), metrics.NewNoOpMetrics()) + parentMS.SetPruning(pruningtypes.NewPruningOptions(pruningtypes.PruningNothing)) + + key1 := types.NewKVStoreKey("store1") + parentMS.MountStoreWithDB(key1, types.StoreTypeIAVL, nil) + + // Load the latest version (should be empty or zero-height in a fresh DB). + require.NoError(t, parentMS.LoadLatestVersion()) + + // Create a KVStore reference from the parent. + parentStore := parentMS.GetKVStore(key1) + + // Put some data in the parent before creating the child and grandchild caches. + parentStore.Set([]byte("initKey"), []byte("initVal")) + + // Create the 1st-level child CacheMultiStore before committing the parent. + childCMS := parentMS.CacheMultiStore() + childKV := childCMS.GetKVStore(key1) + + // Check that the child sees the parent's ephemeral data (initKey). + require.Equal(t, []byte("initVal"), childKV.Get([]byte("initKey")), + "child should see parent's in-memory data prior to commit") + + // Create a 2nd-level grandchild cache from the child. + grandChildCMS := childCMS.CacheMultiStore() + grandChildKV := grandChildCMS.GetKVStore(key1) + + // The grandchild also sees the initial parent data immediately. + require.Equal(t, []byte("initVal"), grandChildKV.Get([]byte("initKey")), + "grandchild should see parent's in-memory data prior to commit") + + // 2) Now commit the parent. In many Cosmos SDK setups, this might reset in-memory state, + // but here it remains shared. The child/grandchild references stay valid. + cid := parentMS.Commit() + require.Equal(t, int64(1), cid.Version, "parent is now at version 1") + + // The parent sets new data after commit, but the child and grandchild still see it + // due to shared ephemeral references in your environment. + parentStore.Set([]byte("postCommitKey"), []byte("postCommitVal")) + require.Equal(t, []byte("postCommitVal"), childKV.Get([]byte("postCommitKey")), + "child sees parent's new data even after parent commit") + require.Equal(t, []byte("postCommitVal"), grandChildKV.Get([]byte("postCommitKey")), + "grandchild sees parent's new data even after parent commit") + + // 3) Further direct writes to the parent store appear in both child and grandchild. + parentStore.Set([]byte("parentOnly"), []byte("parentVal")) + require.Equal(t, []byte("parentVal"), childKV.Get([]byte("parentOnly")), + "child should see parent's new data") + require.Equal(t, []byte("parentVal"), grandChildKV.Get([]byte("parentOnly")), + "grandchild also sees parent's new data") + + // 4) Demonstrate how the child and grandchild caches exchange changes. + // - The child sets "childKey" (immediately visible to the grandchild). + // - The grandchild sets "grandChildKey", which won't be visible to the child until + // grandChildCMS.Write() merges it up. + childKV.Set([]byte("childKey"), []byte("childVal")) + require.Equal(t, []byte("childVal"), grandChildKV.Get([]byte("childKey")), + "grandchild sees child's data immediately in this environment") + + grandChildKV.Set([]byte("grandChildKey"), []byte("grandChildVal")) + require.Nil(t, childKV.Get([]byte("grandChildKey")), + "child doesn't see grandchild data until grandChildCMS.Write()") + + // Once the grandchild calls Write(), the child's ephemeral state is updated. + grandChildCMS.Write() + require.Equal(t, []byte("grandChildVal"), childKV.Get([]byte("grandChildKey")), + "child sees grandchild data after grandchildCMS.Write()") + require.Nil(t, parentStore.Get([]byte("grandChildKey")), + "parent doesn't see it until childCMS.Write() merges it up another level") + + // Now the child calls Write(), so the parent store sees the grandchild's changes as well. + childCMS.Write() + require.Equal(t, []byte("grandChildVal"), parentStore.Get([]byte("grandChildKey")), + "parent sees merged data after childCMS.Write()") + + // 5) Finally, commit the parent again if desired. + cid2 := parentMS.Commit() + require.Equal(t, int64(2), cid2.Version, "parent at version 2") + // This final commit persists everything if the store type supports it (e.g., IAVL). +} diff --git a/x/vm/statedb/integration_test.go b/x/vm/statedb/integration_test.go index ed60bee6f..e88ac675a 100644 --- a/x/vm/statedb/integration_test.go +++ b/x/vm/statedb/integration_test.go @@ -1,6 +1,12 @@ package statedb_test import ( + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/evm/server/config" + "github.com/cosmos/evm/x/vm/statedb" + ethtypes "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/core/vm" + "io" "math/big" "testing" @@ -21,6 +27,7 @@ import ( evmtypes "github.com/cosmos/evm/x/vm/types" "cosmossdk.io/math" + storetypes "cosmossdk.io/store/types" ) func TestNestedEVMExtensionCall(t *testing.T) { @@ -35,6 +42,66 @@ type testCase struct { expContractERC20Balance *big.Int } +type TrackingMultiStore struct { + store storetypes.CacheMultiStore + writes int + historicalStores []*TrackingMultiStore +} + +func (t *TrackingMultiStore) GetStoreType() storetypes.StoreType { + return t.store.GetStoreType() +} + +func (t *TrackingMultiStore) CacheWrap() storetypes.CacheWrap { + return t.store.CacheWrap() +} + +func (t *TrackingMultiStore) CacheWrapWithTrace(w io.Writer, tc storetypes.TraceContext) storetypes.CacheWrap { + return t.store.CacheWrapWithTrace(w, tc) +} + +func (t *TrackingMultiStore) CacheMultiStoreWithVersion(version int64) (storetypes.CacheMultiStore, error) { + return t.CacheMultiStoreWithVersion(version) +} + +func (t *TrackingMultiStore) GetStore(key storetypes.StoreKey) storetypes.Store { + return t.store.GetStore(key) +} + +func (t *TrackingMultiStore) GetKVStore(key storetypes.StoreKey) storetypes.KVStore { + return t.store.GetKVStore(key) +} + +func (t *TrackingMultiStore) TracingEnabled() bool { + return t.store.TracingEnabled() +} + +func (t *TrackingMultiStore) SetTracer(w io.Writer) storetypes.MultiStore { + return t.store.SetTracer(w) +} + +func (t *TrackingMultiStore) SetTracingContext(context storetypes.TraceContext) storetypes.MultiStore { + return t.store.SetTracingContext(context) +} + +func (t *TrackingMultiStore) LatestVersion() int64 { + return t.store.LatestVersion() +} + +func (t *TrackingMultiStore) Copy() storetypes.CacheMultiStore { + return t.store.Copy() +} + +func (t *TrackingMultiStore) Write() { + t.writes++ + t.store.Write() +} + +func (t *TrackingMultiStore) CacheMultiStore() storetypes.CacheMultiStore { + cms := t.store.CacheMultiStore() + return &TrackingMultiStore{store: cms} +} + // This test is a demonstration of the flash loan exploit that was reported. // This happens when interacting with EVM extensions in smart contract methods, // where a resulting state change has the same value as the original state value. @@ -357,3 +424,269 @@ var _ = Describe("testing the flash loan exploit", Ordered, func() { }), ) }) + +var _ = Describe("testing statedb CMS branching", Ordered, func() { + var ( + keyring testkeyring.Keyring + // NOTE: we need to use the unit test network here because we need to retrieve the StateDB + network *testnetwork.UnitTestNetwork + handler grpc.Handler + factory testfactory.TxFactory + evm *vm.EVM + + deployer testkeyring.Key + + erc20Addr common.Address + flashLoanAddr common.Address + flashLoanContract evmtypes.CompiledContract + + validatorToDelegateTo string + + stakingPrecompile *stakingprecompile.Precompile + ) + + mintAmount := big.NewInt(2e18) + delegateAmount := big.NewInt(1e18) + + BeforeAll(func() { + keyring = testkeyring.New(2) + network = testnetwork.NewUnitTestNetwork( + testnetwork.WithPreFundedAccounts(keyring.GetAllAccAddrs()...), + ) + handler = grpc.NewIntegrationHandler(network) + factory = testfactory.New(network, handler) + + deployer = keyring.GetKey(0) + + var err error + stakingPrecompile, err = stakingprecompile.NewPrecompile(*network.App.StakingKeeper, network.App.AuthzKeeper) + Expect(err).ToNot(HaveOccurred(), "failed to create staking precompile") + + // Load the flash loan contract from the compiled JSON data. + flashLoanContract, err = testcontracts.LoadFlashLoanContract() + Expect(err).ToNot(HaveOccurred(), "failed to load flash loan contract") + + }) + + BeforeEach(func() { + valsRes, err := handler.GetBondedValidators() + Expect(err).ToNot(HaveOccurred(), "failed to get bonded validators") + + validatorToDelegateTo = valsRes.Validators[0].OperatorAddress + + // Deploy an ERC-20 token contract. + erc20Addr, err = factory.DeployContract( + deployer.Priv, + evmtypes.EvmTxArgs{}, + testfactory.ContractDeploymentData{ + Contract: contracts.ERC20MinterBurnerDecimalsContract, + ConstructorArgs: []interface{}{"TestToken", "TT", uint8(18)}, + }, + ) + Expect(err).ToNot(HaveOccurred(), "failed to deploy ERC-20 contract") + + Expect(network.NextBlock()).ToNot(HaveOccurred(), "failed to commit block") + + // Mint some tokens to the deployer. + _, err = factory.ExecuteContractCall( + deployer.Priv, + evmtypes.EvmTxArgs{To: &erc20Addr}, + testfactory.CallArgs{ + ContractABI: contracts.ERC20MinterBurnerDecimalsContract.ABI, + MethodName: "mint", + Args: []interface{}{ + deployer.Addr, mintAmount, + }, + }, + ) + Expect(err).ToNot(HaveOccurred(), "failed to mint tokens") + + Expect(network.NextBlock()).ToNot(HaveOccurred(), "failed to commit block") + + // Check the balance of the deployer on the ERC20 contract. + res, err := factory.ExecuteContractCall( + deployer.Priv, + evmtypes.EvmTxArgs{To: &erc20Addr}, + testfactory.CallArgs{ + ContractABI: contracts.ERC20MinterBurnerDecimalsContract.ABI, + MethodName: "balanceOf", + Args: []interface{}{ + deployer.Addr, + }, + }, + ) + Expect(err).ToNot(HaveOccurred(), "failed to get balance") + + Expect(network.NextBlock()).ToNot(HaveOccurred(), "failed to commit block") + + ethRes, err := evmtypes.DecodeTxResponse(res.Data) + Expect(err).ToNot(HaveOccurred(), "failed to decode balance of tx response") + + unpacked, err := contracts.ERC20MinterBurnerDecimalsContract.ABI.Unpack( + "balanceOf", + ethRes.Ret, + ) + Expect(err).ToNot(HaveOccurred(), "failed to unpack balance") + + balance, ok := unpacked[0].(*big.Int) + Expect(ok).To(BeTrue(), "failed to convert balance to big.Int") + Expect(balance.String()).To(Equal(mintAmount.String()), "balance is not correct") + + // Deploy the flash loan contract. + flashLoanAddr, err = factory.DeployContract( + deployer.Priv, + evmtypes.EvmTxArgs{}, + testfactory.ContractDeploymentData{ + Contract: flashLoanContract, + }, + ) + Expect(err).ToNot(HaveOccurred(), "failed to deploy flash loan contract") + + Expect(network.NextBlock()).ToNot(HaveOccurred(), "failed to commit block") + + // Approve the flash loan contract to spend tokens. This is required because + // the contract will get funds from the caller to perform actions. + _, err = factory.ExecuteContractCall( + deployer.Priv, + evmtypes.EvmTxArgs{To: &erc20Addr}, + testfactory.CallArgs{ + ContractABI: contracts.ERC20MinterBurnerDecimalsContract.ABI, + MethodName: "approve", + Args: []interface{}{ + flashLoanAddr, mintAmount, + }, + }, + ) + Expect(err).ToNot(HaveOccurred(), "failed to approve flash loan contract") + + Expect(network.NextBlock()).ToNot(HaveOccurred(), "failed to commit block") + + // Check the allowance. + res, err = factory.ExecuteContractCall( + deployer.Priv, + evmtypes.EvmTxArgs{To: &erc20Addr}, + testfactory.CallArgs{ + ContractABI: contracts.ERC20MinterBurnerDecimalsContract.ABI, + MethodName: "allowance", + Args: []interface{}{ + deployer.Addr, flashLoanAddr, + }, + }, + ) + Expect(err).ToNot(HaveOccurred(), "failed to get allowance") + + Expect(network.NextBlock()).ToNot(HaveOccurred(), "failed to commit block") + + ethRes, err = evmtypes.DecodeTxResponse(res.Data) + Expect(err).ToNot(HaveOccurred(), "failed to decode allowance tx response") + + unpacked, err = contracts.ERC20MinterBurnerDecimalsContract.ABI.Unpack( + "allowance", + ethRes.Ret, + ) + Expect(err).ToNot(HaveOccurred(), "failed to unpack allowance") + + var allowance *big.Int + allowance, ok = unpacked[0].(*big.Int) + Expect(ok).To(BeTrue(), "failed to convert allowance to big.Int") + Expect(allowance.String()).To(Equal(mintAmount.String()), "allowance is not correct") + + // Approve the flash loan contract to delegate tokens on behalf of user. + precompileAddr := stakingPrecompile.Address() + + _, err = factory.ExecuteContractCall( + deployer.Priv, + evmtypes.EvmTxArgs{To: &precompileAddr}, + testfactory.CallArgs{ + ContractABI: stakingPrecompile.ABI, + MethodName: "approve", + Args: []interface{}{ + flashLoanAddr, delegateAmount, []string{stakingprecompile.DelegateMsg}, + }, + }, + ) + Expect(err).ToNot(HaveOccurred(), "failed to approve flash loan contract") + + Expect(network.NextBlock()).ToNot(HaveOccurred(), "failed to commit block") + + // Check the allowance. + res, err = factory.ExecuteContractCall( + deployer.Priv, + evmtypes.EvmTxArgs{To: &precompileAddr}, + testfactory.CallArgs{ + ContractABI: stakingPrecompile.ABI, + MethodName: "allowance", + Args: []interface{}{ + deployer.Addr, flashLoanAddr, stakingprecompile.DelegateMsg, + }, + }, + ) + Expect(err).ToNot(HaveOccurred(), "failed to get allowance") + + Expect(network.NextBlock()).ToNot(HaveOccurred(), "failed to commit block") + + ethRes, err = evmtypes.DecodeTxResponse(res.Data) + Expect(err).ToNot(HaveOccurred(), "failed to decode allowance tx response") + + err = stakingPrecompile.UnpackIntoInterface(&allowance, "allowance", ethRes.Ret) + Expect(err).ToNot(HaveOccurred(), "failed to unpack allowance") + }) + + DescribeTable("call the flashLoan contract", func(tc testCase) { + cms := &TrackingMultiStore{ + store: network.App.BaseApp.CommitMultiStore().CacheMultiStore(), + writes: 0, + historicalStores: nil, + } + ctx := network.App.BaseApp.NewContext(false) + fromAddr := common.BytesToAddress(deployer.Priv.PubKey().Address().Bytes()) + sender := vm.AccountRef(fromAddr) + nonce, err := network.App.AccountKeeper.GetSequence(ctx, fromAddr.Bytes()) + Expect(err).ToNot(HaveOccurred()) + + txCfg := statedb.NewEmptyTxConfig(common.BytesToHash(ctx.HeaderHash())) + stateDB := statedb.New(ctx.WithMultiStore(cms), network.App.EVMKeeper, txCfg) + evmCfg, _ := network.App.EVMKeeper.EVMConfig(ctx, sdk.ConsAddress(ctx.BlockHeader().ProposerAddress)) + + /* + args := evmtypes.EvmTxArgs{ + To: &flashLoanAddr, + GasPrice: big.NewInt(900_000_000), + GasLimit: 400_000, + } + */ + callArgs := []interface{}{ + erc20Addr, + validatorToDelegateTo, + delegateAmount, + } + data, err := flashLoanContract.ABI.Pack(tc.method, callArgs...) + Expect(err).ToNot(HaveOccurred()) + msg := ethtypes.NewMessage( + fromAddr, + &flashLoanAddr, + nonce, + big.NewInt(0), // amount + config.DefaultGasCap, // gasLimit + big.NewInt(0), // gasFeeCap + big.NewInt(0), // gasTipCap + big.NewInt(0), // gasPrice + data, + ethtypes.AccessList{}, // AccessList + false, // isFake + ) + + evm = network.App.EVMKeeper.NewEVM(ctx, msg, evmCfg, nil, stateDB) + _, _, vmErr := evm.Call(sender, flashLoanAddr, msg.Data(), msg.Gas(), msg.Value()) + Expect(vmErr).ToNot(HaveOccurred()) + + Expect(network.NextBlock()).ToNot(HaveOccurred(), "failed to commit block") + }, + Entry("flashLoanWithRevert method -- track Writes", testCase{ + method: "flashLoan", + expDelegation: true, + expSenderERC20Balance: mintAmount, + expContractERC20Balance: big.NewInt(0), + }), + ) +}) From c541c2cffb60c586e31a1cb80dabe6691f6e70e5 Mon Sep 17 00:00:00 2001 From: Eric Warehime Date: Wed, 7 May 2025 18:48:32 -0700 Subject: [PATCH 04/11] Remove extra files --- x/vm/keeper/statedb_test.go | 97 --------- x/vm/statedb/integration_test.go | 333 ------------------------------- 2 files changed, 430 deletions(-) diff --git a/x/vm/keeper/statedb_test.go b/x/vm/keeper/statedb_test.go index b9a619c6e..f2467cbf2 100644 --- a/x/vm/keeper/statedb_test.go +++ b/x/vm/keeper/statedb_test.go @@ -2,8 +2,6 @@ package keeper_test import ( "bytes" - "cosmossdk.io/store/metrics" - pruningtypes "cosmossdk.io/store/pruning/types" "fmt" "math/big" "testing" @@ -1085,98 +1083,3 @@ func (suite *KeeperTestSuite) TestDeleteAccount() { }) } } - -func TestCacheMultiStore_SharedEphemeralState_GrandChild(t *testing.T) { - /* - This test shows that: - - The parent store, a first-level cache (childCMS), and a second-level cache (grandChildCMS) - can all share the same in-memory (ephemeral) state, even after the parent commits. - - Each "Write()" merges the lower-level cache's changes up one level: - grandChildCMS → childCMS → parentStore. - - Consequently, any direct parent writes can be observed immediately by both child and grandchild. - - Similarly, the child sees the grandchild’s changes only after the grandchild calls Write(), - and the parent sees them only after the child calls Write(). - */ - - // 1) Create the parent MultiStore (in memory) and mount a KVStore. - db := dbm.NewMemDB() - parentMS := NewStore(db, log.NewNopLogger(), metrics.NewNoOpMetrics()) - parentMS.SetPruning(pruningtypes.NewPruningOptions(pruningtypes.PruningNothing)) - - key1 := types.NewKVStoreKey("store1") - parentMS.MountStoreWithDB(key1, types.StoreTypeIAVL, nil) - - // Load the latest version (should be empty or zero-height in a fresh DB). - require.NoError(t, parentMS.LoadLatestVersion()) - - // Create a KVStore reference from the parent. - parentStore := parentMS.GetKVStore(key1) - - // Put some data in the parent before creating the child and grandchild caches. - parentStore.Set([]byte("initKey"), []byte("initVal")) - - // Create the 1st-level child CacheMultiStore before committing the parent. - childCMS := parentMS.CacheMultiStore() - childKV := childCMS.GetKVStore(key1) - - // Check that the child sees the parent's ephemeral data (initKey). - require.Equal(t, []byte("initVal"), childKV.Get([]byte("initKey")), - "child should see parent's in-memory data prior to commit") - - // Create a 2nd-level grandchild cache from the child. - grandChildCMS := childCMS.CacheMultiStore() - grandChildKV := grandChildCMS.GetKVStore(key1) - - // The grandchild also sees the initial parent data immediately. - require.Equal(t, []byte("initVal"), grandChildKV.Get([]byte("initKey")), - "grandchild should see parent's in-memory data prior to commit") - - // 2) Now commit the parent. In many Cosmos SDK setups, this might reset in-memory state, - // but here it remains shared. The child/grandchild references stay valid. - cid := parentMS.Commit() - require.Equal(t, int64(1), cid.Version, "parent is now at version 1") - - // The parent sets new data after commit, but the child and grandchild still see it - // due to shared ephemeral references in your environment. - parentStore.Set([]byte("postCommitKey"), []byte("postCommitVal")) - require.Equal(t, []byte("postCommitVal"), childKV.Get([]byte("postCommitKey")), - "child sees parent's new data even after parent commit") - require.Equal(t, []byte("postCommitVal"), grandChildKV.Get([]byte("postCommitKey")), - "grandchild sees parent's new data even after parent commit") - - // 3) Further direct writes to the parent store appear in both child and grandchild. - parentStore.Set([]byte("parentOnly"), []byte("parentVal")) - require.Equal(t, []byte("parentVal"), childKV.Get([]byte("parentOnly")), - "child should see parent's new data") - require.Equal(t, []byte("parentVal"), grandChildKV.Get([]byte("parentOnly")), - "grandchild also sees parent's new data") - - // 4) Demonstrate how the child and grandchild caches exchange changes. - // - The child sets "childKey" (immediately visible to the grandchild). - // - The grandchild sets "grandChildKey", which won't be visible to the child until - // grandChildCMS.Write() merges it up. - childKV.Set([]byte("childKey"), []byte("childVal")) - require.Equal(t, []byte("childVal"), grandChildKV.Get([]byte("childKey")), - "grandchild sees child's data immediately in this environment") - - grandChildKV.Set([]byte("grandChildKey"), []byte("grandChildVal")) - require.Nil(t, childKV.Get([]byte("grandChildKey")), - "child doesn't see grandchild data until grandChildCMS.Write()") - - // Once the grandchild calls Write(), the child's ephemeral state is updated. - grandChildCMS.Write() - require.Equal(t, []byte("grandChildVal"), childKV.Get([]byte("grandChildKey")), - "child sees grandchild data after grandchildCMS.Write()") - require.Nil(t, parentStore.Get([]byte("grandChildKey")), - "parent doesn't see it until childCMS.Write() merges it up another level") - - // Now the child calls Write(), so the parent store sees the grandchild's changes as well. - childCMS.Write() - require.Equal(t, []byte("grandChildVal"), parentStore.Get([]byte("grandChildKey")), - "parent sees merged data after childCMS.Write()") - - // 5) Finally, commit the parent again if desired. - cid2 := parentMS.Commit() - require.Equal(t, int64(2), cid2.Version, "parent at version 2") - // This final commit persists everything if the store type supports it (e.g., IAVL). -} diff --git a/x/vm/statedb/integration_test.go b/x/vm/statedb/integration_test.go index e88ac675a..ed60bee6f 100644 --- a/x/vm/statedb/integration_test.go +++ b/x/vm/statedb/integration_test.go @@ -1,12 +1,6 @@ package statedb_test import ( - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/evm/server/config" - "github.com/cosmos/evm/x/vm/statedb" - ethtypes "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/core/vm" - "io" "math/big" "testing" @@ -27,7 +21,6 @@ import ( evmtypes "github.com/cosmos/evm/x/vm/types" "cosmossdk.io/math" - storetypes "cosmossdk.io/store/types" ) func TestNestedEVMExtensionCall(t *testing.T) { @@ -42,66 +35,6 @@ type testCase struct { expContractERC20Balance *big.Int } -type TrackingMultiStore struct { - store storetypes.CacheMultiStore - writes int - historicalStores []*TrackingMultiStore -} - -func (t *TrackingMultiStore) GetStoreType() storetypes.StoreType { - return t.store.GetStoreType() -} - -func (t *TrackingMultiStore) CacheWrap() storetypes.CacheWrap { - return t.store.CacheWrap() -} - -func (t *TrackingMultiStore) CacheWrapWithTrace(w io.Writer, tc storetypes.TraceContext) storetypes.CacheWrap { - return t.store.CacheWrapWithTrace(w, tc) -} - -func (t *TrackingMultiStore) CacheMultiStoreWithVersion(version int64) (storetypes.CacheMultiStore, error) { - return t.CacheMultiStoreWithVersion(version) -} - -func (t *TrackingMultiStore) GetStore(key storetypes.StoreKey) storetypes.Store { - return t.store.GetStore(key) -} - -func (t *TrackingMultiStore) GetKVStore(key storetypes.StoreKey) storetypes.KVStore { - return t.store.GetKVStore(key) -} - -func (t *TrackingMultiStore) TracingEnabled() bool { - return t.store.TracingEnabled() -} - -func (t *TrackingMultiStore) SetTracer(w io.Writer) storetypes.MultiStore { - return t.store.SetTracer(w) -} - -func (t *TrackingMultiStore) SetTracingContext(context storetypes.TraceContext) storetypes.MultiStore { - return t.store.SetTracingContext(context) -} - -func (t *TrackingMultiStore) LatestVersion() int64 { - return t.store.LatestVersion() -} - -func (t *TrackingMultiStore) Copy() storetypes.CacheMultiStore { - return t.store.Copy() -} - -func (t *TrackingMultiStore) Write() { - t.writes++ - t.store.Write() -} - -func (t *TrackingMultiStore) CacheMultiStore() storetypes.CacheMultiStore { - cms := t.store.CacheMultiStore() - return &TrackingMultiStore{store: cms} -} - // This test is a demonstration of the flash loan exploit that was reported. // This happens when interacting with EVM extensions in smart contract methods, // where a resulting state change has the same value as the original state value. @@ -424,269 +357,3 @@ var _ = Describe("testing the flash loan exploit", Ordered, func() { }), ) }) - -var _ = Describe("testing statedb CMS branching", Ordered, func() { - var ( - keyring testkeyring.Keyring - // NOTE: we need to use the unit test network here because we need to retrieve the StateDB - network *testnetwork.UnitTestNetwork - handler grpc.Handler - factory testfactory.TxFactory - evm *vm.EVM - - deployer testkeyring.Key - - erc20Addr common.Address - flashLoanAddr common.Address - flashLoanContract evmtypes.CompiledContract - - validatorToDelegateTo string - - stakingPrecompile *stakingprecompile.Precompile - ) - - mintAmount := big.NewInt(2e18) - delegateAmount := big.NewInt(1e18) - - BeforeAll(func() { - keyring = testkeyring.New(2) - network = testnetwork.NewUnitTestNetwork( - testnetwork.WithPreFundedAccounts(keyring.GetAllAccAddrs()...), - ) - handler = grpc.NewIntegrationHandler(network) - factory = testfactory.New(network, handler) - - deployer = keyring.GetKey(0) - - var err error - stakingPrecompile, err = stakingprecompile.NewPrecompile(*network.App.StakingKeeper, network.App.AuthzKeeper) - Expect(err).ToNot(HaveOccurred(), "failed to create staking precompile") - - // Load the flash loan contract from the compiled JSON data. - flashLoanContract, err = testcontracts.LoadFlashLoanContract() - Expect(err).ToNot(HaveOccurred(), "failed to load flash loan contract") - - }) - - BeforeEach(func() { - valsRes, err := handler.GetBondedValidators() - Expect(err).ToNot(HaveOccurred(), "failed to get bonded validators") - - validatorToDelegateTo = valsRes.Validators[0].OperatorAddress - - // Deploy an ERC-20 token contract. - erc20Addr, err = factory.DeployContract( - deployer.Priv, - evmtypes.EvmTxArgs{}, - testfactory.ContractDeploymentData{ - Contract: contracts.ERC20MinterBurnerDecimalsContract, - ConstructorArgs: []interface{}{"TestToken", "TT", uint8(18)}, - }, - ) - Expect(err).ToNot(HaveOccurred(), "failed to deploy ERC-20 contract") - - Expect(network.NextBlock()).ToNot(HaveOccurred(), "failed to commit block") - - // Mint some tokens to the deployer. - _, err = factory.ExecuteContractCall( - deployer.Priv, - evmtypes.EvmTxArgs{To: &erc20Addr}, - testfactory.CallArgs{ - ContractABI: contracts.ERC20MinterBurnerDecimalsContract.ABI, - MethodName: "mint", - Args: []interface{}{ - deployer.Addr, mintAmount, - }, - }, - ) - Expect(err).ToNot(HaveOccurred(), "failed to mint tokens") - - Expect(network.NextBlock()).ToNot(HaveOccurred(), "failed to commit block") - - // Check the balance of the deployer on the ERC20 contract. - res, err := factory.ExecuteContractCall( - deployer.Priv, - evmtypes.EvmTxArgs{To: &erc20Addr}, - testfactory.CallArgs{ - ContractABI: contracts.ERC20MinterBurnerDecimalsContract.ABI, - MethodName: "balanceOf", - Args: []interface{}{ - deployer.Addr, - }, - }, - ) - Expect(err).ToNot(HaveOccurred(), "failed to get balance") - - Expect(network.NextBlock()).ToNot(HaveOccurred(), "failed to commit block") - - ethRes, err := evmtypes.DecodeTxResponse(res.Data) - Expect(err).ToNot(HaveOccurred(), "failed to decode balance of tx response") - - unpacked, err := contracts.ERC20MinterBurnerDecimalsContract.ABI.Unpack( - "balanceOf", - ethRes.Ret, - ) - Expect(err).ToNot(HaveOccurred(), "failed to unpack balance") - - balance, ok := unpacked[0].(*big.Int) - Expect(ok).To(BeTrue(), "failed to convert balance to big.Int") - Expect(balance.String()).To(Equal(mintAmount.String()), "balance is not correct") - - // Deploy the flash loan contract. - flashLoanAddr, err = factory.DeployContract( - deployer.Priv, - evmtypes.EvmTxArgs{}, - testfactory.ContractDeploymentData{ - Contract: flashLoanContract, - }, - ) - Expect(err).ToNot(HaveOccurred(), "failed to deploy flash loan contract") - - Expect(network.NextBlock()).ToNot(HaveOccurred(), "failed to commit block") - - // Approve the flash loan contract to spend tokens. This is required because - // the contract will get funds from the caller to perform actions. - _, err = factory.ExecuteContractCall( - deployer.Priv, - evmtypes.EvmTxArgs{To: &erc20Addr}, - testfactory.CallArgs{ - ContractABI: contracts.ERC20MinterBurnerDecimalsContract.ABI, - MethodName: "approve", - Args: []interface{}{ - flashLoanAddr, mintAmount, - }, - }, - ) - Expect(err).ToNot(HaveOccurred(), "failed to approve flash loan contract") - - Expect(network.NextBlock()).ToNot(HaveOccurred(), "failed to commit block") - - // Check the allowance. - res, err = factory.ExecuteContractCall( - deployer.Priv, - evmtypes.EvmTxArgs{To: &erc20Addr}, - testfactory.CallArgs{ - ContractABI: contracts.ERC20MinterBurnerDecimalsContract.ABI, - MethodName: "allowance", - Args: []interface{}{ - deployer.Addr, flashLoanAddr, - }, - }, - ) - Expect(err).ToNot(HaveOccurred(), "failed to get allowance") - - Expect(network.NextBlock()).ToNot(HaveOccurred(), "failed to commit block") - - ethRes, err = evmtypes.DecodeTxResponse(res.Data) - Expect(err).ToNot(HaveOccurred(), "failed to decode allowance tx response") - - unpacked, err = contracts.ERC20MinterBurnerDecimalsContract.ABI.Unpack( - "allowance", - ethRes.Ret, - ) - Expect(err).ToNot(HaveOccurred(), "failed to unpack allowance") - - var allowance *big.Int - allowance, ok = unpacked[0].(*big.Int) - Expect(ok).To(BeTrue(), "failed to convert allowance to big.Int") - Expect(allowance.String()).To(Equal(mintAmount.String()), "allowance is not correct") - - // Approve the flash loan contract to delegate tokens on behalf of user. - precompileAddr := stakingPrecompile.Address() - - _, err = factory.ExecuteContractCall( - deployer.Priv, - evmtypes.EvmTxArgs{To: &precompileAddr}, - testfactory.CallArgs{ - ContractABI: stakingPrecompile.ABI, - MethodName: "approve", - Args: []interface{}{ - flashLoanAddr, delegateAmount, []string{stakingprecompile.DelegateMsg}, - }, - }, - ) - Expect(err).ToNot(HaveOccurred(), "failed to approve flash loan contract") - - Expect(network.NextBlock()).ToNot(HaveOccurred(), "failed to commit block") - - // Check the allowance. - res, err = factory.ExecuteContractCall( - deployer.Priv, - evmtypes.EvmTxArgs{To: &precompileAddr}, - testfactory.CallArgs{ - ContractABI: stakingPrecompile.ABI, - MethodName: "allowance", - Args: []interface{}{ - deployer.Addr, flashLoanAddr, stakingprecompile.DelegateMsg, - }, - }, - ) - Expect(err).ToNot(HaveOccurred(), "failed to get allowance") - - Expect(network.NextBlock()).ToNot(HaveOccurred(), "failed to commit block") - - ethRes, err = evmtypes.DecodeTxResponse(res.Data) - Expect(err).ToNot(HaveOccurred(), "failed to decode allowance tx response") - - err = stakingPrecompile.UnpackIntoInterface(&allowance, "allowance", ethRes.Ret) - Expect(err).ToNot(HaveOccurred(), "failed to unpack allowance") - }) - - DescribeTable("call the flashLoan contract", func(tc testCase) { - cms := &TrackingMultiStore{ - store: network.App.BaseApp.CommitMultiStore().CacheMultiStore(), - writes: 0, - historicalStores: nil, - } - ctx := network.App.BaseApp.NewContext(false) - fromAddr := common.BytesToAddress(deployer.Priv.PubKey().Address().Bytes()) - sender := vm.AccountRef(fromAddr) - nonce, err := network.App.AccountKeeper.GetSequence(ctx, fromAddr.Bytes()) - Expect(err).ToNot(HaveOccurred()) - - txCfg := statedb.NewEmptyTxConfig(common.BytesToHash(ctx.HeaderHash())) - stateDB := statedb.New(ctx.WithMultiStore(cms), network.App.EVMKeeper, txCfg) - evmCfg, _ := network.App.EVMKeeper.EVMConfig(ctx, sdk.ConsAddress(ctx.BlockHeader().ProposerAddress)) - - /* - args := evmtypes.EvmTxArgs{ - To: &flashLoanAddr, - GasPrice: big.NewInt(900_000_000), - GasLimit: 400_000, - } - */ - callArgs := []interface{}{ - erc20Addr, - validatorToDelegateTo, - delegateAmount, - } - data, err := flashLoanContract.ABI.Pack(tc.method, callArgs...) - Expect(err).ToNot(HaveOccurred()) - msg := ethtypes.NewMessage( - fromAddr, - &flashLoanAddr, - nonce, - big.NewInt(0), // amount - config.DefaultGasCap, // gasLimit - big.NewInt(0), // gasFeeCap - big.NewInt(0), // gasTipCap - big.NewInt(0), // gasPrice - data, - ethtypes.AccessList{}, // AccessList - false, // isFake - ) - - evm = network.App.EVMKeeper.NewEVM(ctx, msg, evmCfg, nil, stateDB) - _, _, vmErr := evm.Call(sender, flashLoanAddr, msg.Data(), msg.Gas(), msg.Value()) - Expect(vmErr).ToNot(HaveOccurred()) - - Expect(network.NextBlock()).ToNot(HaveOccurred(), "failed to commit block") - }, - Entry("flashLoanWithRevert method -- track Writes", testCase{ - method: "flashLoan", - expDelegation: true, - expSenderERC20Balance: mintAmount, - expContractERC20Balance: big.NewInt(0), - }), - ) -}) From 8e2638de7297559c000000205a4bdc42b1ab8a2b Mon Sep 17 00:00:00 2001 From: Eric Warehime Date: Wed, 7 May 2025 18:49:03 -0700 Subject: [PATCH 05/11] Make format --- precompiles/staking/setup_test.go | 3 ++- precompiles/testutil/tracking_multi_store.go | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/precompiles/staking/setup_test.go b/precompiles/staking/setup_test.go index 9d99a2e8a..503d9355f 100644 --- a/precompiles/staking/setup_test.go +++ b/precompiles/staking/setup_test.go @@ -1,12 +1,13 @@ package staking_test import ( + "testing" + sdkmath "cosmossdk.io/math" sdk "github.com/cosmos/cosmos-sdk/types" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" testconstants "github.com/cosmos/evm/testutil/constants" - "testing" "github.com/stretchr/testify/suite" diff --git a/precompiles/testutil/tracking_multi_store.go b/precompiles/testutil/tracking_multi_store.go index 2ccbea51d..10db54664 100644 --- a/precompiles/testutil/tracking_multi_store.go +++ b/precompiles/testutil/tracking_multi_store.go @@ -1,11 +1,12 @@ package testutil import ( - "github.com/stretchr/testify/require" "io" "testing" "time" + "github.com/stretchr/testify/require" + storetypes "cosmossdk.io/store/types" ) From 6f2cb35f087e02d3eda77b3d7a1f320a462d2f09 Mon Sep 17 00:00:00 2001 From: Eric Warehime Date: Thu, 8 May 2025 08:04:30 -0700 Subject: [PATCH 06/11] Another format --- precompiles/testutil/tracking_multi_store.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/precompiles/testutil/tracking_multi_store.go b/precompiles/testutil/tracking_multi_store.go index 10db54664..ff872f85f 100644 --- a/precompiles/testutil/tracking_multi_store.go +++ b/precompiles/testutil/tracking_multi_store.go @@ -59,10 +59,6 @@ func (t *TrackingMultiStore) LatestVersion() int64 { return t.Store.LatestVersion() } -func (t *TrackingMultiStore) Copy() storetypes.CacheMultiStore { - return t.Store.Copy() -} - func (t *TrackingMultiStore) Write() { t.Writes++ now := time.Now() From f33e13bb77d09f04197de1a30f0f235cd33aed68 Mon Sep 17 00:00:00 2001 From: Eric Warehime Date: Thu, 8 May 2025 08:07:06 -0700 Subject: [PATCH 07/11] Linting --- precompiles/testutil/tracking_multi_store.go | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/precompiles/testutil/tracking_multi_store.go b/precompiles/testutil/tracking_multi_store.go index ff872f85f..6a0fda01c 100644 --- a/precompiles/testutil/tracking_multi_store.go +++ b/precompiles/testutil/tracking_multi_store.go @@ -32,7 +32,7 @@ func (t *TrackingMultiStore) CacheWrapWithTrace(w io.Writer, tc storetypes.Trace } func (t *TrackingMultiStore) CacheMultiStoreWithVersion(version int64) (storetypes.CacheMultiStore, error) { - return t.CacheMultiStoreWithVersion(version) + return t.Store.CacheMultiStoreWithVersion(version) } func (t *TrackingMultiStore) GetStore(key storetypes.StoreKey) storetypes.Store { @@ -76,6 +76,7 @@ func (t *TrackingMultiStore) CacheMultiStore() storetypes.CacheMultiStore { // ValidateWrites tests the number of writes to a tree of tracking multi stores, // and that all the writes in a branching cache multistore/cascade upwards func ValidateWrites(t *testing.T, ms *TrackingMultiStore, expWrites int) { + t.Helper() toTestCMS := []*TrackingMultiStore{ms} writes := 0 var writeTS *time.Time @@ -91,9 +92,7 @@ func ValidateWrites(t *testing.T, ms *TrackingMultiStore, expWrites int) { writeTS = currCMS.WriteTS } if len(currCMS.HistoricalStores) > 0 { - for _, s := range currCMS.HistoricalStores { - toTestCMS = append(toTestCMS, s) - } + toTestCMS = append(toTestCMS, currCMS.HistoricalStores...) } } require.Equal(t, expWrites, writes) From 941251565669bad8ef2b01e1d8ffb152cdbf4f1a Mon Sep 17 00:00:00 2001 From: Eric Warehime Date: Thu, 8 May 2025 08:49:17 -0700 Subject: [PATCH 08/11] Lint again --- precompiles/staking/setup_test.go | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/precompiles/staking/setup_test.go b/precompiles/staking/setup_test.go index 503d9355f..f50cf3bd4 100644 --- a/precompiles/staking/setup_test.go +++ b/precompiles/staking/setup_test.go @@ -3,19 +3,20 @@ package staking_test import ( "testing" - sdkmath "cosmossdk.io/math" - sdk "github.com/cosmos/cosmos-sdk/types" - authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" - banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" - testconstants "github.com/cosmos/evm/testutil/constants" - "github.com/stretchr/testify/suite" "github.com/cosmos/evm/precompiles/staking" + testconstants "github.com/cosmos/evm/testutil/constants" "github.com/cosmos/evm/testutil/integration/os/factory" "github.com/cosmos/evm/testutil/integration/os/grpc" testkeyring "github.com/cosmos/evm/testutil/integration/os/keyring" "github.com/cosmos/evm/testutil/integration/os/network" + + sdkmath "cosmossdk.io/math" + + sdk "github.com/cosmos/cosmos-sdk/types" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" ) type PrecompileTestSuite struct { From b7fee233ae0fcf16302672d7cdd2567bb12d14df Mon Sep 17 00:00:00 2001 From: Eric Warehime Date: Thu, 8 May 2025 16:04:44 -0700 Subject: [PATCH 09/11] Fix tests, add comments --- precompiles/staking/setup_test.go | 14 ++++++++++---- precompiles/staking/staking_test.go | 1 + precompiles/testutil/tracking_multi_store.go | 14 ++++++++++---- 3 files changed, 21 insertions(+), 8 deletions(-) diff --git a/precompiles/staking/setup_test.go b/precompiles/staking/setup_test.go index f50cf3bd4..883c83632 100644 --- a/precompiles/staking/setup_test.go +++ b/precompiles/staking/setup_test.go @@ -27,8 +27,9 @@ type PrecompileTestSuite struct { grpcHandler grpc.Handler keyring testkeyring.Keyring - bondDenom string - precompile *staking.Precompile + bondDenom string + precompile *staking.Precompile + customGenesis bool } func TestPrecompileUnitTestSuite(t *testing.T) { @@ -49,9 +50,14 @@ func (s *PrecompileTestSuite) SetupTest() { bankGenesis := banktypes.DefaultGenesisState() bankGenesis.Balances = balances customGenesis[banktypes.ModuleName] = bankGenesis - nw := network.NewUnitTestNetwork( + cfgOpts := []network.ConfigOption{ network.WithPreFundedAccounts(keyring.GetAllAccAddrs()...), - network.WithCustomGenesis(customGenesis), + } + if s.customGenesis { + cfgOpts = append(cfgOpts, network.WithCustomGenesis(customGenesis)) + } + nw := network.NewUnitTestNetwork( + cfgOpts..., ) grpcHandler := grpc.NewIntegrationHandler(nw) txFactory := factory.New(nw, grpcHandler) diff --git a/precompiles/staking/staking_test.go b/precompiles/staking/staking_test.go index c5fc4a0ed..e29539db3 100644 --- a/precompiles/staking/staking_test.go +++ b/precompiles/staking/staking_test.go @@ -505,6 +505,7 @@ func (s *PrecompileTestSuite) TestRun() { // TestCMS tests the cache multistore writes. func (s *PrecompileTestSuite) TestCMS() { + s.customGenesis = true var ctx sdk.Context testcases := []struct { name string diff --git a/precompiles/testutil/tracking_multi_store.go b/precompiles/testutil/tracking_multi_store.go index 6a0fda01c..5fc0c9a8b 100644 --- a/precompiles/testutil/tracking_multi_store.go +++ b/precompiles/testutil/tracking_multi_store.go @@ -11,11 +11,17 @@ import ( ) // TrackingMultiStore implements the CacheMultiStore interface, but tracks calls to the Write interface as -// well ass the branches created via CacheMultiStore() +// well as the branches created via CacheMultiStore() type TrackingMultiStore struct { - Store storetypes.CacheMultiStore - Writes int - WriteTS *time.Time + // Store is the underlying CacheMultiStore being wrapped and tracked. + Store storetypes.CacheMultiStore + // Writes is the number of times Write() has been called on this store. + Writes int + // WriteTS is the timestamp of the last Write() call, used to determine write order. + WriteTS *time.Time + // HistoricalStores holds any CacheMultiStores created from this store via CacheMultiStore(). + // Each represents a new *branch* of the same logical root, not a hierarchical child. + // Branches are tracked in order of creation, but have no implied depth or parent-child relationship. HistoricalStores []*TrackingMultiStore } From 927aeeb6db8f7ddb94ead618359bd27edc23eaec Mon Sep 17 00:00:00 2001 From: Eric Warehime Date: Fri, 9 May 2025 13:10:08 -0700 Subject: [PATCH 10/11] Fix staking CMS tests --- precompiles/staking/staking_test.go | 39 ++++++++++------------------- 1 file changed, 13 insertions(+), 26 deletions(-) diff --git a/precompiles/staking/staking_test.go b/precompiles/staking/staking_test.go index dfb193c82..f91c46098 100644 --- a/precompiles/staking/staking_test.go +++ b/precompiles/staking/staking_test.go @@ -476,7 +476,7 @@ func (s *PrecompileTestSuite) TestCMS() { var ctx sdk.Context testcases := []struct { name string - malleate func(delegator, grantee testkeyring.Key) []byte + malleate func(delegator testkeyring.Key) []byte gas uint64 expPass bool expKeeperPass bool @@ -484,10 +484,7 @@ func (s *PrecompileTestSuite) TestCMS() { }{ { "fail - contract gas limit is < gas cost to run a query / tx", - func(delegator, grantee testkeyring.Key) []byte { - // TODO: why is this required? - err := s.CreateAuthorization(ctx, delegator.AccAddr, grantee.AccAddr, staking.DelegateAuthz, nil) - s.Require().NoError(err) + func(delegator testkeyring.Key) []byte { input, err := s.precompile.Pack( staking.DelegateMethod, @@ -505,9 +502,7 @@ func (s *PrecompileTestSuite) TestCMS() { }, { "pass - delegate transaction", - func(delegator, grantee testkeyring.Key) []byte { - err := s.CreateAuthorization(ctx, delegator.AccAddr, grantee.AccAddr, staking.DelegateAuthz, nil) - s.Require().NoError(err) + func(delegator testkeyring.Key) []byte { input, err := s.precompile.Pack( staking.DelegateMethod, @@ -525,9 +520,7 @@ func (s *PrecompileTestSuite) TestCMS() { }, { "pass - undelegate transaction", - func(delegator, grantee testkeyring.Key) []byte { - err := s.CreateAuthorization(ctx, delegator.AccAddr, grantee.AccAddr, staking.UndelegateAuthz, nil) - s.Require().NoError(err) + func(delegator testkeyring.Key) []byte { input, err := s.precompile.Pack( staking.UndelegateMethod, @@ -545,9 +538,7 @@ func (s *PrecompileTestSuite) TestCMS() { }, { "pass - redelegate transaction", - func(delegator, grantee testkeyring.Key) []byte { - err := s.CreateAuthorization(ctx, delegator.AccAddr, grantee.AccAddr, staking.RedelegateAuthz, nil) - s.Require().NoError(err) + func(delegator testkeyring.Key) []byte { input, err := s.precompile.Pack( staking.RedelegateMethod, @@ -566,7 +557,7 @@ func (s *PrecompileTestSuite) TestCMS() { }, { "pass - cancel unbonding delegation transaction", - func(delegator, grantee testkeyring.Key) []byte { + func(delegator testkeyring.Key) []byte { valAddr, err := sdk.ValAddressFromBech32(s.network.GetValidators()[0].GetOperator()) s.Require().NoError(err) // add unbonding delegation to staking keeper @@ -583,9 +574,6 @@ func (s *PrecompileTestSuite) TestCMS() { err = s.network.App.StakingKeeper.SetUnbondingDelegation(ctx, ubd) s.Require().NoError(err, "failed to set unbonding delegation") - err = s.CreateAuthorization(ctx, delegator.AccAddr, grantee.AccAddr, staking.CancelUnbondingDelegationAuthz, nil) - s.Require().NoError(err) - // Needs to be called after setting unbonding delegation // In order to mimic the coins being added to the unboding pool coin := sdk.NewCoin(testconstants.ExampleAttoDenom, math.NewInt(1000)) @@ -609,7 +597,7 @@ func (s *PrecompileTestSuite) TestCMS() { }, { "pass - delegation query", - func(delegator, _ testkeyring.Key) []byte { + func(delegator testkeyring.Key) []byte { input, err := s.precompile.Pack( staking.DelegationMethod, delegator.Addr, @@ -625,7 +613,7 @@ func (s *PrecompileTestSuite) TestCMS() { }, { "pass - validator query", - func(_, _ testkeyring.Key) []byte { + func(_ testkeyring.Key) []byte { valAddr, err := sdk.ValAddressFromBech32(s.network.GetValidators()[0].OperatorAddress) s.Require().NoError(err) @@ -643,7 +631,7 @@ func (s *PrecompileTestSuite) TestCMS() { }, { "pass - redelgation query", - func(delegator, _ testkeyring.Key) []byte { + func(delegator testkeyring.Key) []byte { valAddr1, err := sdk.ValAddressFromBech32(s.network.GetValidators()[0].GetOperator()) s.Require().NoError(err) valAddr2, err := sdk.ValAddressFromBech32(s.network.GetValidators()[1].GetOperator()) @@ -681,7 +669,7 @@ func (s *PrecompileTestSuite) TestCMS() { }, { "pass - delegation query - read only", - func(delegator, _ testkeyring.Key) []byte { + func(delegator testkeyring.Key) []byte { input, err := s.precompile.Pack( staking.DelegationMethod, delegator.Addr, @@ -697,7 +685,7 @@ func (s *PrecompileTestSuite) TestCMS() { }, { "pass - unbonding delegation query", - func(delegator, _ testkeyring.Key) []byte { + func(delegator testkeyring.Key) []byte { valAddr, err := sdk.ValAddressFromBech32(s.network.GetValidators()[0].GetOperator()) s.Require().NoError(err) // add unbonding delegation to staking keeper @@ -735,7 +723,7 @@ func (s *PrecompileTestSuite) TestCMS() { }, { "fail - invalid method", - func(_, _ testkeyring.Key) []byte { + func(_ testkeyring.Key) []byte { return []byte("invalid") }, 100000, // use gas > 0 to avoid doing gas estimation @@ -760,13 +748,12 @@ func (s *PrecompileTestSuite) TestCMS() { baseFee := s.network.App.EVMKeeper.GetBaseFee(ctx) delegator := s.keyring.GetKey(0) - grantee := s.keyring.GetKey(1) contract := vm.NewPrecompile(vm.AccountRef(delegator.Addr), s.precompile, big.NewInt(0), tc.gas) contractAddr := contract.Address() // malleate testcase - input := tc.malleate(delegator, grantee) + input := tc.malleate(delegator) // Build and sign Ethereum transaction txArgs := evmtypes.EvmTxArgs{ From 96306250c56a6176ec4535b1ac57bca14be392a2 Mon Sep 17 00:00:00 2001 From: Eric Warehime Date: Fri, 9 May 2025 13:25:47 -0700 Subject: [PATCH 11/11] Lint fix --- precompiles/staking/staking_test.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/precompiles/staking/staking_test.go b/precompiles/staking/staking_test.go index f91c46098..e63c2c9f9 100644 --- a/precompiles/staking/staking_test.go +++ b/precompiles/staking/staking_test.go @@ -485,7 +485,6 @@ func (s *PrecompileTestSuite) TestCMS() { { "fail - contract gas limit is < gas cost to run a query / tx", func(delegator testkeyring.Key) []byte { - input, err := s.precompile.Pack( staking.DelegateMethod, delegator.Addr, @@ -503,7 +502,6 @@ func (s *PrecompileTestSuite) TestCMS() { { "pass - delegate transaction", func(delegator testkeyring.Key) []byte { - input, err := s.precompile.Pack( staking.DelegateMethod, delegator.Addr, @@ -521,7 +519,6 @@ func (s *PrecompileTestSuite) TestCMS() { { "pass - undelegate transaction", func(delegator testkeyring.Key) []byte { - input, err := s.precompile.Pack( staking.UndelegateMethod, delegator.Addr, @@ -539,7 +536,6 @@ func (s *PrecompileTestSuite) TestCMS() { { "pass - redelegate transaction", func(delegator testkeyring.Key) []byte { - input, err := s.precompile.Pack( staking.RedelegateMethod, delegator.Addr,