diff --git a/go.mod b/go.mod index a1e005a31..c02d5b130 100644 --- a/go.mod +++ b/go.mod @@ -254,9 +254,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 50deff574..9a22fb7d4 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/evidence v0.1.1 h1:Ks+BLTa3uftFpElLTDp9L76t2b58htjVbSZ86aoK/E4= @@ -392,8 +394,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= 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 5c75abc1c..b0f940639 100644 --- a/precompiles/staking/setup_test.go +++ b/precompiles/staking/setup_test.go @@ -6,10 +6,17 @@ import ( "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 { @@ -20,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) { @@ -30,8 +38,26 @@ func TestPrecompileUnitTestSuite(t *testing.T) { func (s *PrecompileTestSuite) SetupTest() { keyring := testkeyring.New(2) - nw := network.NewUnitTestNetwork( + 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 + cfgOpts := []network.ConfigOption{ network.WithPreFundedAccounts(keyring.GetAllAccAddrs()...), + } + 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 0a2f788d5..e63c2c9f9 100644 --- a/precompiles/staking/staking_test.go +++ b/precompiles/staking/staking_test.go @@ -11,6 +11,7 @@ import ( chainutil "github.com/cosmos/evm/evmd/testutil" "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" @@ -468,3 +469,335 @@ 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 + malleate func(delegator 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 testkeyring.Key) []byte { + 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 testkeyring.Key) []byte { + 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 testkeyring.Key) []byte { + 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 testkeyring.Key) []byte { + 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 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.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) + + contract := vm.NewPrecompile(vm.AccountRef(delegator.Addr), s.precompile, big.NewInt(0), tc.gas) + contractAddr := contract.Address() + + // malleate testcase + input := tc.malleate(delegator) + + // 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..5fc0c9a8b --- /dev/null +++ b/precompiles/testutil/tracking_multi_store.go @@ -0,0 +1,105 @@ +package testutil + +import ( + "io" + "testing" + "time" + + "github.com/stretchr/testify/require" + + storetypes "cosmossdk.io/store/types" +) + +// TrackingMultiStore implements the CacheMultiStore interface, but tracks calls to the Write interface as +// well as the branches created via CacheMultiStore() +type TrackingMultiStore struct { + // 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 +} + +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.Store.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) 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) { + t.Helper() + 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 { + toTestCMS = append(toTestCMS, currCMS.HistoricalStores...) + } + } + require.Equal(t, expWrites, writes) +} 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 }