diff --git a/contracts/solidity/precompiles/testutil/contracts/StakingReverter.sol b/contracts/solidity/precompiles/testutil/contracts/StakingReverter.sol index 52659691d..fcf21798e 100644 --- a/contracts/solidity/precompiles/testutil/contracts/StakingReverter.sol +++ b/contracts/solidity/precompiles/testutil/contracts/StakingReverter.sol @@ -31,6 +31,20 @@ contract StakingReverter { } } + function callPrecompileBeforeAndAfterRevert(uint numTimes, string calldata validatorAddress) external { + STAKING_CONTRACT.delegate(address(this), validatorAddress, 10); + + for (uint i = 0; i < numTimes; i++) { + try + StakingReverter(address(this)).performDelegation( + validatorAddress + ) + {} catch {} + } + + STAKING_CONTRACT.delegate(address(this), validatorAddress, 10); + } + function performDelegation(string calldata validatorAddress) external { STAKING_CONTRACT.delegate(address(this), validatorAddress, 10); revert(); diff --git a/evmd/app.go b/evmd/app.go index 10e517f2f..afd0cc8cb 100644 --- a/evmd/app.go +++ b/evmd/app.go @@ -30,6 +30,7 @@ import ( feemarketkeeper "github.com/cosmos/evm/x/feemarket/keeper" feemarkettypes "github.com/cosmos/evm/x/feemarket/types" ibccallbackskeeper "github.com/cosmos/evm/x/ibc/callbacks/keeper" + // NOTE: override ICS20 keeper to support IBC transfers of ERC20 tokens "github.com/cosmos/evm/x/ibc/transfer" transferkeeper "github.com/cosmos/evm/x/ibc/transfer/keeper" @@ -481,7 +482,7 @@ func NewExampleApp( // NOTE: it's required to set up the EVM keeper before the ERC-20 keeper, because it is used in its instantiation. app.EVMKeeper = evmkeeper.NewKeeper( // TODO: check why this is not adjusted to use the runtime module methods like SDK native keepers - appCodec, keys[evmtypes.StoreKey], tkeys[evmtypes.TransientKey], + appCodec, keys[evmtypes.StoreKey], tkeys[evmtypes.TransientKey], keys, authtypes.NewModuleAddress(govtypes.ModuleName), app.AccountKeeper, app.PreciseBankKeeper, diff --git a/evmd/go.mod b/evmd/go.mod index 005edd2e1..38c264517 100644 --- a/evmd/go.mod +++ b/evmd/go.mod @@ -13,6 +13,7 @@ require ( cosmossdk.io/tools/confix v0.1.2 cosmossdk.io/x/evidence v0.2.0 cosmossdk.io/x/feegrant v0.2.0 + cosmossdk.io/x/tx v0.14.0 cosmossdk.io/x/upgrade v0.2.0 github.com/cometbft/cometbft v0.38.17 github.com/cosmos/cosmos-db v1.1.3 @@ -43,7 +44,6 @@ require ( cosmossdk.io/collections v1.2.1 // indirect cosmossdk.io/depinject v1.2.1 // indirect cosmossdk.io/schema v1.1.0 // indirect - cosmossdk.io/x/tx v0.14.0 // indirect filippo.io/edwards25519 v1.1.0 // indirect github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 // indirect github.com/99designs/keyring v1.2.2 // indirect diff --git a/precompiles/testutil/contracts/StakingReverter.json b/precompiles/testutil/contracts/StakingReverter.json index c1b2711ae..fb20f85f3 100644 --- a/precompiles/testutil/contracts/StakingReverter.json +++ b/precompiles/testutil/contracts/StakingReverter.json @@ -8,6 +8,24 @@ "stateMutability": "payable", "type": "constructor" }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "numTimes", + "type": "uint256" + }, + { + "internalType": "string", + "name": "validatorAddress", + "type": "string" + } + ], + "name": "callPrecompileBeforeAndAfterRevert", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, { "inputs": [ { @@ -175,8 +193,8 @@ "type": "function" } ], - "bytecode": "0x608060405260008055610ff9806100176000396000f3fe608060405234801561001057600080fd5b50600436106100575760003560e01c80634e5a8fe51461005c57806352fce7b114610078578063668f452b14610094578063cbc367d4146100c5578063f66013d7146100f5575b600080fd5b6100766004803603810190610071919061056c565b610111565b005b610092600480360381019061008d919061056c565b6101b3565b005b6100ae60048036038101906100a991906105cc565b61025b565b6040516100bc929190610704565b60405180910390f35b6100df60048036038101906100da9190610792565b6102f5565b6040516100ec919061097d565b60405180910390f35b61010f600480360381019061010a91906105cc565b6103a5565b005b600080815480929190610123906109ce565b919050555060005b838110156101ad573073ffffffffffffffffffffffffffffffffffffffff1663f66013d784846040518363ffffffff1660e01b815260040161016e929190610a63565b600060405180830381600087803b15801561018857600080fd5b505af1925050508015610199575060015b5080806101a5906109ce565b91505061012b565b50505050565b6000808154809291906101c5906109ce565b919050555060005b83811015610255573073ffffffffffffffffffffffffffffffffffffffff1663f66013d784846040518363ffffffff1660e01b8152600401610210929190610a63565b600060405180830381600087803b15801561022a57600080fd5b505af115801561023e573d6000803e3d6000fd5b50505050808061024d906109ce565b9150506101cd565b50505050565b600061026561042f565b61080073ffffffffffffffffffffffffffffffffffffffff1663241774e63086866040518463ffffffff1660e01b81526004016102a493929190610a96565b600060405180830381865afa1580156102c1573d6000803e3d6000fd5b505050506040513d6000823e3d601f19601f820116820180604052508101906102ea9190610c74565b915091509250929050565b6102fd610449565b60005b8381101561039e5761080073ffffffffffffffffffffffffffffffffffffffff1663223b3b7a846040518263ffffffff1660e01b81526004016103439190610cd0565b600060405180830381865afa158015610360573d6000803e3d6000fd5b505050506040513d6000823e3d601f19601f820116820180604052508101906103899190610ec8565b91508080610396906109ce565b915050610300565b5092915050565b61080073ffffffffffffffffffffffffffffffffffffffff166353266bbb308484600a6040518563ffffffff1660e01b81526004016103e79493929190610f56565b6020604051808303816000875af1158015610406573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061042a9190610f96565b600080fd5b604051806040016040528060608152602001600081525090565b6040518061016001604052806060815260200160608152602001600015158152602001600060038111156104805761047f6107ed565b5b8152602001600081526020016000815260200160608152602001600060070b8152602001600060070b815260200160008152602001600081525090565b6000604051905090565b600080fd5b600080fd5b6000819050919050565b6104e4816104d1565b81146104ef57600080fd5b50565b600081359050610501816104db565b92915050565b600080fd5b600080fd5b600080fd5b60008083601f84011261052c5761052b610507565b5b8235905067ffffffffffffffff8111156105495761054861050c565b5b60208301915083600182028301111561056557610564610511565b5b9250929050565b600080600060408486031215610585576105846104c7565b5b6000610593868287016104f2565b935050602084013567ffffffffffffffff8111156105b4576105b36104cc565b5b6105c086828701610516565b92509250509250925092565b600080602083850312156105e3576105e26104c7565b5b600083013567ffffffffffffffff811115610601576106006104cc565b5b61060d85828601610516565b92509250509250929050565b610622816104d1565b82525050565b600081519050919050565b600082825260208201905092915050565b60005b83811015610662578082015181840152602081019050610647565b60008484015250505050565b6000601f19601f8301169050919050565b600061068a82610628565b6106948185610633565b93506106a4818560208601610644565b6106ad8161066e565b840191505092915050565b6106c1816104d1565b82525050565b600060408301600083015184820360008601526106e4828261067f565b91505060208301516106f960208601826106b8565b508091505092915050565b60006040820190506107196000830185610619565b818103602083015261072b81846106c7565b90509392505050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b600061075f82610734565b9050919050565b61076f81610754565b811461077a57600080fd5b50565b60008135905061078c81610766565b92915050565b600080604083850312156107a9576107a86104c7565b5b60006107b7858286016104f2565b92505060206107c88582860161077d565b9150509250929050565b60008115159050919050565b6107e7816107d2565b82525050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b6004811061082d5761082c6107ed565b5b50565b600081905061083e8261081c565b919050565b600061084e82610830565b9050919050565b61085e81610843565b82525050565b60008160070b9050919050565b61087a81610864565b82525050565b600061016083016000830151848203600086015261089e828261067f565b915050602083015184820360208601526108b8828261067f565b91505060408301516108cd60408601826107de565b5060608301516108e06060860182610855565b5060808301516108f360808601826106b8565b5060a083015161090660a08601826106b8565b5060c083015184820360c086015261091e828261067f565b91505060e083015161093360e0860182610871565b50610100830151610948610100860182610871565b5061012083015161095d6101208601826106b8565b506101408301516109726101408601826106b8565b508091505092915050565b600060208201905081810360008301526109978184610880565b905092915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b60006109d9826104d1565b91507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8203610a0b57610a0a61099f565b5b600182019050919050565b600082825260208201905092915050565b82818337600083830152505050565b6000610a428385610a16565b9350610a4f838584610a27565b610a588361066e565b840190509392505050565b60006020820190508181036000830152610a7e818486610a36565b90509392505050565b610a9081610754565b82525050565b6000604082019050610aab6000830186610a87565b8181036020830152610abe818486610a36565b9050949350505050565b600081519050610ad7816104db565b92915050565b600080fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b610b1a8261066e565b810181811067ffffffffffffffff82111715610b3957610b38610ae2565b5b80604052505050565b6000610b4c6104bd565b9050610b588282610b11565b919050565b600080fd5b600080fd5b600067ffffffffffffffff821115610b8257610b81610ae2565b5b610b8b8261066e565b9050602081019050919050565b6000610bab610ba684610b67565b610b42565b905082815260208101848484011115610bc757610bc6610b62565b5b610bd2848285610644565b509392505050565b600082601f830112610bef57610bee610507565b5b8151610bff848260208601610b98565b91505092915050565b600060408284031215610c1e57610c1d610add565b5b610c286040610b42565b9050600082015167ffffffffffffffff811115610c4857610c47610b5d565b5b610c5484828501610bda565b6000830152506020610c6884828501610ac8565b60208301525092915050565b60008060408385031215610c8b57610c8a6104c7565b5b6000610c9985828601610ac8565b925050602083015167ffffffffffffffff811115610cba57610cb96104cc565b5b610cc685828601610c08565b9150509250929050565b6000602082019050610ce56000830184610a87565b92915050565b610cf4816107d2565b8114610cff57600080fd5b50565b600081519050610d1181610ceb565b92915050565b60048110610d2457600080fd5b50565b600081519050610d3681610d17565b92915050565b610d4581610864565b8114610d5057600080fd5b50565b600081519050610d6281610d3c565b92915050565b60006101608284031215610d7f57610d7e610add565b5b610d8a610160610b42565b9050600082015167ffffffffffffffff811115610daa57610da9610b5d565b5b610db684828501610bda565b600083015250602082015167ffffffffffffffff811115610dda57610dd9610b5d565b5b610de684828501610bda565b6020830152506040610dfa84828501610d02565b6040830152506060610e0e84828501610d27565b6060830152506080610e2284828501610ac8565b60808301525060a0610e3684828501610ac8565b60a08301525060c082015167ffffffffffffffff811115610e5a57610e59610b5d565b5b610e6684828501610bda565b60c08301525060e0610e7a84828501610d53565b60e083015250610100610e8f84828501610d53565b61010083015250610120610ea584828501610ac8565b61012083015250610140610ebb84828501610ac8565b6101408301525092915050565b600060208284031215610ede57610edd6104c7565b5b600082015167ffffffffffffffff811115610efc57610efb6104cc565b5b610f0884828501610d68565b91505092915050565b6000819050919050565b6000819050919050565b6000610f40610f3b610f3684610f11565b610f1b565b6104d1565b9050919050565b610f5081610f25565b82525050565b6000606082019050610f6b6000830187610a87565b8181036020830152610f7e818587610a36565b9050610f8d6040830184610f47565b95945050505050565b600060208284031215610fac57610fab6104c7565b5b6000610fba84828501610d02565b9150509291505056fea26469706673582212204c80bbe74182c86170368d1d98faa10eb2ae168be3f5db007cee902a6be79fee64736f6c63430008140033", - "deployedBytecode": "0x608060405234801561001057600080fd5b50600436106100575760003560e01c80634e5a8fe51461005c57806352fce7b114610078578063668f452b14610094578063cbc367d4146100c5578063f66013d7146100f5575b600080fd5b6100766004803603810190610071919061056c565b610111565b005b610092600480360381019061008d919061056c565b6101b3565b005b6100ae60048036038101906100a991906105cc565b61025b565b6040516100bc929190610704565b60405180910390f35b6100df60048036038101906100da9190610792565b6102f5565b6040516100ec919061097d565b60405180910390f35b61010f600480360381019061010a91906105cc565b6103a5565b005b600080815480929190610123906109ce565b919050555060005b838110156101ad573073ffffffffffffffffffffffffffffffffffffffff1663f66013d784846040518363ffffffff1660e01b815260040161016e929190610a63565b600060405180830381600087803b15801561018857600080fd5b505af1925050508015610199575060015b5080806101a5906109ce565b91505061012b565b50505050565b6000808154809291906101c5906109ce565b919050555060005b83811015610255573073ffffffffffffffffffffffffffffffffffffffff1663f66013d784846040518363ffffffff1660e01b8152600401610210929190610a63565b600060405180830381600087803b15801561022a57600080fd5b505af115801561023e573d6000803e3d6000fd5b50505050808061024d906109ce565b9150506101cd565b50505050565b600061026561042f565b61080073ffffffffffffffffffffffffffffffffffffffff1663241774e63086866040518463ffffffff1660e01b81526004016102a493929190610a96565b600060405180830381865afa1580156102c1573d6000803e3d6000fd5b505050506040513d6000823e3d601f19601f820116820180604052508101906102ea9190610c74565b915091509250929050565b6102fd610449565b60005b8381101561039e5761080073ffffffffffffffffffffffffffffffffffffffff1663223b3b7a846040518263ffffffff1660e01b81526004016103439190610cd0565b600060405180830381865afa158015610360573d6000803e3d6000fd5b505050506040513d6000823e3d601f19601f820116820180604052508101906103899190610ec8565b91508080610396906109ce565b915050610300565b5092915050565b61080073ffffffffffffffffffffffffffffffffffffffff166353266bbb308484600a6040518563ffffffff1660e01b81526004016103e79493929190610f56565b6020604051808303816000875af1158015610406573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061042a9190610f96565b600080fd5b604051806040016040528060608152602001600081525090565b6040518061016001604052806060815260200160608152602001600015158152602001600060038111156104805761047f6107ed565b5b8152602001600081526020016000815260200160608152602001600060070b8152602001600060070b815260200160008152602001600081525090565b6000604051905090565b600080fd5b600080fd5b6000819050919050565b6104e4816104d1565b81146104ef57600080fd5b50565b600081359050610501816104db565b92915050565b600080fd5b600080fd5b600080fd5b60008083601f84011261052c5761052b610507565b5b8235905067ffffffffffffffff8111156105495761054861050c565b5b60208301915083600182028301111561056557610564610511565b5b9250929050565b600080600060408486031215610585576105846104c7565b5b6000610593868287016104f2565b935050602084013567ffffffffffffffff8111156105b4576105b36104cc565b5b6105c086828701610516565b92509250509250925092565b600080602083850312156105e3576105e26104c7565b5b600083013567ffffffffffffffff811115610601576106006104cc565b5b61060d85828601610516565b92509250509250929050565b610622816104d1565b82525050565b600081519050919050565b600082825260208201905092915050565b60005b83811015610662578082015181840152602081019050610647565b60008484015250505050565b6000601f19601f8301169050919050565b600061068a82610628565b6106948185610633565b93506106a4818560208601610644565b6106ad8161066e565b840191505092915050565b6106c1816104d1565b82525050565b600060408301600083015184820360008601526106e4828261067f565b91505060208301516106f960208601826106b8565b508091505092915050565b60006040820190506107196000830185610619565b818103602083015261072b81846106c7565b90509392505050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b600061075f82610734565b9050919050565b61076f81610754565b811461077a57600080fd5b50565b60008135905061078c81610766565b92915050565b600080604083850312156107a9576107a86104c7565b5b60006107b7858286016104f2565b92505060206107c88582860161077d565b9150509250929050565b60008115159050919050565b6107e7816107d2565b82525050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b6004811061082d5761082c6107ed565b5b50565b600081905061083e8261081c565b919050565b600061084e82610830565b9050919050565b61085e81610843565b82525050565b60008160070b9050919050565b61087a81610864565b82525050565b600061016083016000830151848203600086015261089e828261067f565b915050602083015184820360208601526108b8828261067f565b91505060408301516108cd60408601826107de565b5060608301516108e06060860182610855565b5060808301516108f360808601826106b8565b5060a083015161090660a08601826106b8565b5060c083015184820360c086015261091e828261067f565b91505060e083015161093360e0860182610871565b50610100830151610948610100860182610871565b5061012083015161095d6101208601826106b8565b506101408301516109726101408601826106b8565b508091505092915050565b600060208201905081810360008301526109978184610880565b905092915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b60006109d9826104d1565b91507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8203610a0b57610a0a61099f565b5b600182019050919050565b600082825260208201905092915050565b82818337600083830152505050565b6000610a428385610a16565b9350610a4f838584610a27565b610a588361066e565b840190509392505050565b60006020820190508181036000830152610a7e818486610a36565b90509392505050565b610a9081610754565b82525050565b6000604082019050610aab6000830186610a87565b8181036020830152610abe818486610a36565b9050949350505050565b600081519050610ad7816104db565b92915050565b600080fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b610b1a8261066e565b810181811067ffffffffffffffff82111715610b3957610b38610ae2565b5b80604052505050565b6000610b4c6104bd565b9050610b588282610b11565b919050565b600080fd5b600080fd5b600067ffffffffffffffff821115610b8257610b81610ae2565b5b610b8b8261066e565b9050602081019050919050565b6000610bab610ba684610b67565b610b42565b905082815260208101848484011115610bc757610bc6610b62565b5b610bd2848285610644565b509392505050565b600082601f830112610bef57610bee610507565b5b8151610bff848260208601610b98565b91505092915050565b600060408284031215610c1e57610c1d610add565b5b610c286040610b42565b9050600082015167ffffffffffffffff811115610c4857610c47610b5d565b5b610c5484828501610bda565b6000830152506020610c6884828501610ac8565b60208301525092915050565b60008060408385031215610c8b57610c8a6104c7565b5b6000610c9985828601610ac8565b925050602083015167ffffffffffffffff811115610cba57610cb96104cc565b5b610cc685828601610c08565b9150509250929050565b6000602082019050610ce56000830184610a87565b92915050565b610cf4816107d2565b8114610cff57600080fd5b50565b600081519050610d1181610ceb565b92915050565b60048110610d2457600080fd5b50565b600081519050610d3681610d17565b92915050565b610d4581610864565b8114610d5057600080fd5b50565b600081519050610d6281610d3c565b92915050565b60006101608284031215610d7f57610d7e610add565b5b610d8a610160610b42565b9050600082015167ffffffffffffffff811115610daa57610da9610b5d565b5b610db684828501610bda565b600083015250602082015167ffffffffffffffff811115610dda57610dd9610b5d565b5b610de684828501610bda565b6020830152506040610dfa84828501610d02565b6040830152506060610e0e84828501610d27565b6060830152506080610e2284828501610ac8565b60808301525060a0610e3684828501610ac8565b60a08301525060c082015167ffffffffffffffff811115610e5a57610e59610b5d565b5b610e6684828501610bda565b60c08301525060e0610e7a84828501610d53565b60e083015250610100610e8f84828501610d53565b61010083015250610120610ea584828501610ac8565b61012083015250610140610ebb84828501610ac8565b6101408301525092915050565b600060208284031215610ede57610edd6104c7565b5b600082015167ffffffffffffffff811115610efc57610efb6104cc565b5b610f0884828501610d68565b91505092915050565b6000819050919050565b6000819050919050565b6000610f40610f3b610f3684610f11565b610f1b565b6104d1565b9050919050565b610f5081610f25565b82525050565b6000606082019050610f6b6000830187610a87565b8181036020830152610f7e818587610a36565b9050610f8d6040830184610f47565b95945050505050565b600060208284031215610fac57610fab6104c7565b5b6000610fba84828501610d02565b9150509291505056fea26469706673582212204c80bbe74182c86170368d1d98faa10eb2ae168be3f5db007cee902a6be79fee64736f6c63430008140033", + "bytecode": "0x6080604052600080556111b7806100176000396000f3fe608060405234801561001057600080fd5b50600436106100625760003560e01c80634e5a8fe51461006757806352fce7b114610083578063668f452b1461009f578063922a4b67146100d0578063cbc367d4146100ec578063f66013d71461011c575b600080fd5b610081600480360381019061007c919061072a565b610138565b005b61009d6004803603810190610098919061072a565b6101da565b005b6100b960048036038101906100b4919061078a565b610282565b6040516100c79291906108c2565b60405180910390f35b6100ea60048036038101906100e5919061072a565b61031c565b005b61010660048036038101906101019190610950565b6104b3565b6040516101139190610b3b565b60405180910390f35b6101366004803603810190610131919061078a565b610563565b005b60008081548092919061014a90610b8c565b919050555060005b838110156101d4573073ffffffffffffffffffffffffffffffffffffffff1663f66013d784846040518363ffffffff1660e01b8152600401610195929190610c21565b600060405180830381600087803b1580156101af57600080fd5b505af19250505080156101c0575060015b5080806101cc90610b8c565b915050610152565b50505050565b6000808154809291906101ec90610b8c565b919050555060005b8381101561027c573073ffffffffffffffffffffffffffffffffffffffff1663f66013d784846040518363ffffffff1660e01b8152600401610237929190610c21565b600060405180830381600087803b15801561025157600080fd5b505af1158015610265573d6000803e3d6000fd5b50505050808061027490610b8c565b9150506101f4565b50505050565b600061028c6105ed565b61080073ffffffffffffffffffffffffffffffffffffffff1663241774e63086866040518463ffffffff1660e01b81526004016102cb93929190610c54565b600060405180830381865afa1580156102e8573d6000803e3d6000fd5b505050506040513d6000823e3d601f19601f820116820180604052508101906103119190610e32565b915091509250929050565b61080073ffffffffffffffffffffffffffffffffffffffff166353266bbb308484600a6040518563ffffffff1660e01b815260040161035e9493929190610ed3565b6020604051808303816000875af115801561037d573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906103a19190610f3f565b5060005b83811015610427573073ffffffffffffffffffffffffffffffffffffffff1663f66013d784846040518363ffffffff1660e01b81526004016103e8929190610c21565b600060405180830381600087803b15801561040257600080fd5b505af1925050508015610413575060015b50808061041f90610b8c565b9150506103a5565b5061080073ffffffffffffffffffffffffffffffffffffffff166353266bbb308484600a6040518563ffffffff1660e01b815260040161046a9493929190610ed3565b6020604051808303816000875af1158015610489573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906104ad9190610f3f565b50505050565b6104bb610607565b60005b8381101561055c5761080073ffffffffffffffffffffffffffffffffffffffff1663223b3b7a846040518263ffffffff1660e01b81526004016105019190610f6c565b600060405180830381865afa15801561051e573d6000803e3d6000fd5b505050506040513d6000823e3d601f19601f820116820180604052508101906105479190611138565b9150808061055490610b8c565b9150506104be565b5092915050565b61080073ffffffffffffffffffffffffffffffffffffffff166353266bbb308484600a6040518563ffffffff1660e01b81526004016105a59493929190610ed3565b6020604051808303816000875af11580156105c4573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906105e89190610f3f565b600080fd5b604051806040016040528060608152602001600081525090565b60405180610160016040528060608152602001606081526020016000151581526020016000600381111561063e5761063d6109ab565b5b8152602001600081526020016000815260200160608152602001600060070b8152602001600060070b815260200160008152602001600081525090565b6000604051905090565b600080fd5b600080fd5b6000819050919050565b6106a28161068f565b81146106ad57600080fd5b50565b6000813590506106bf81610699565b92915050565b600080fd5b600080fd5b600080fd5b60008083601f8401126106ea576106e96106c5565b5b8235905067ffffffffffffffff811115610707576107066106ca565b5b602083019150836001820283011115610723576107226106cf565b5b9250929050565b60008060006040848603121561074357610742610685565b5b6000610751868287016106b0565b935050602084013567ffffffffffffffff8111156107725761077161068a565b5b61077e868287016106d4565b92509250509250925092565b600080602083850312156107a1576107a0610685565b5b600083013567ffffffffffffffff8111156107bf576107be61068a565b5b6107cb858286016106d4565b92509250509250929050565b6107e08161068f565b82525050565b600081519050919050565b600082825260208201905092915050565b60005b83811015610820578082015181840152602081019050610805565b60008484015250505050565b6000601f19601f8301169050919050565b6000610848826107e6565b61085281856107f1565b9350610862818560208601610802565b61086b8161082c565b840191505092915050565b61087f8161068f565b82525050565b600060408301600083015184820360008601526108a2828261083d565b91505060208301516108b76020860182610876565b508091505092915050565b60006040820190506108d760008301856107d7565b81810360208301526108e98184610885565b90509392505050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b600061091d826108f2565b9050919050565b61092d81610912565b811461093857600080fd5b50565b60008135905061094a81610924565b92915050565b6000806040838503121561096757610966610685565b5b6000610975858286016106b0565b92505060206109868582860161093b565b9150509250929050565b60008115159050919050565b6109a581610990565b82525050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b600481106109eb576109ea6109ab565b5b50565b60008190506109fc826109da565b919050565b6000610a0c826109ee565b9050919050565b610a1c81610a01565b82525050565b60008160070b9050919050565b610a3881610a22565b82525050565b6000610160830160008301518482036000860152610a5c828261083d565b91505060208301518482036020860152610a76828261083d565b9150506040830151610a8b604086018261099c565b506060830151610a9e6060860182610a13565b506080830151610ab16080860182610876565b5060a0830151610ac460a0860182610876565b5060c083015184820360c0860152610adc828261083d565b91505060e0830151610af160e0860182610a2f565b50610100830151610b06610100860182610a2f565b50610120830151610b1b610120860182610876565b50610140830151610b30610140860182610876565b508091505092915050565b60006020820190508181036000830152610b558184610a3e565b905092915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b6000610b978261068f565b91507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8203610bc957610bc8610b5d565b5b600182019050919050565b600082825260208201905092915050565b82818337600083830152505050565b6000610c008385610bd4565b9350610c0d838584610be5565b610c168361082c565b840190509392505050565b60006020820190508181036000830152610c3c818486610bf4565b90509392505050565b610c4e81610912565b82525050565b6000604082019050610c696000830186610c45565b8181036020830152610c7c818486610bf4565b9050949350505050565b600081519050610c9581610699565b92915050565b600080fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b610cd88261082c565b810181811067ffffffffffffffff82111715610cf757610cf6610ca0565b5b80604052505050565b6000610d0a61067b565b9050610d168282610ccf565b919050565b600080fd5b600080fd5b600067ffffffffffffffff821115610d4057610d3f610ca0565b5b610d498261082c565b9050602081019050919050565b6000610d69610d6484610d25565b610d00565b905082815260208101848484011115610d8557610d84610d20565b5b610d90848285610802565b509392505050565b600082601f830112610dad57610dac6106c5565b5b8151610dbd848260208601610d56565b91505092915050565b600060408284031215610ddc57610ddb610c9b565b5b610de66040610d00565b9050600082015167ffffffffffffffff811115610e0657610e05610d1b565b5b610e1284828501610d98565b6000830152506020610e2684828501610c86565b60208301525092915050565b60008060408385031215610e4957610e48610685565b5b6000610e5785828601610c86565b925050602083015167ffffffffffffffff811115610e7857610e7761068a565b5b610e8485828601610dc6565b9150509250929050565b6000819050919050565b6000819050919050565b6000610ebd610eb8610eb384610e8e565b610e98565b61068f565b9050919050565b610ecd81610ea2565b82525050565b6000606082019050610ee86000830187610c45565b8181036020830152610efb818587610bf4565b9050610f0a6040830184610ec4565b95945050505050565b610f1c81610990565b8114610f2757600080fd5b50565b600081519050610f3981610f13565b92915050565b600060208284031215610f5557610f54610685565b5b6000610f6384828501610f2a565b91505092915050565b6000602082019050610f816000830184610c45565b92915050565b60048110610f9457600080fd5b50565b600081519050610fa681610f87565b92915050565b610fb581610a22565b8114610fc057600080fd5b50565b600081519050610fd281610fac565b92915050565b60006101608284031215610fef57610fee610c9b565b5b610ffa610160610d00565b9050600082015167ffffffffffffffff81111561101a57611019610d1b565b5b61102684828501610d98565b600083015250602082015167ffffffffffffffff81111561104a57611049610d1b565b5b61105684828501610d98565b602083015250604061106a84828501610f2a565b604083015250606061107e84828501610f97565b606083015250608061109284828501610c86565b60808301525060a06110a684828501610c86565b60a08301525060c082015167ffffffffffffffff8111156110ca576110c9610d1b565b5b6110d684828501610d98565b60c08301525060e06110ea84828501610fc3565b60e0830152506101006110ff84828501610fc3565b6101008301525061012061111584828501610c86565b6101208301525061014061112b84828501610c86565b6101408301525092915050565b60006020828403121561114e5761114d610685565b5b600082015167ffffffffffffffff81111561116c5761116b61068a565b5b61117884828501610fd8565b9150509291505056fea2646970667358221220b11abac490c07bdcbb16b8258b290743cd7265572df2a5293e55d93b6e3a2a3864736f6c63430008140033", + "deployedBytecode": "0x608060405234801561001057600080fd5b50600436106100625760003560e01c80634e5a8fe51461006757806352fce7b114610083578063668f452b1461009f578063922a4b67146100d0578063cbc367d4146100ec578063f66013d71461011c575b600080fd5b610081600480360381019061007c919061072a565b610138565b005b61009d6004803603810190610098919061072a565b6101da565b005b6100b960048036038101906100b4919061078a565b610282565b6040516100c79291906108c2565b60405180910390f35b6100ea60048036038101906100e5919061072a565b61031c565b005b61010660048036038101906101019190610950565b6104b3565b6040516101139190610b3b565b60405180910390f35b6101366004803603810190610131919061078a565b610563565b005b60008081548092919061014a90610b8c565b919050555060005b838110156101d4573073ffffffffffffffffffffffffffffffffffffffff1663f66013d784846040518363ffffffff1660e01b8152600401610195929190610c21565b600060405180830381600087803b1580156101af57600080fd5b505af19250505080156101c0575060015b5080806101cc90610b8c565b915050610152565b50505050565b6000808154809291906101ec90610b8c565b919050555060005b8381101561027c573073ffffffffffffffffffffffffffffffffffffffff1663f66013d784846040518363ffffffff1660e01b8152600401610237929190610c21565b600060405180830381600087803b15801561025157600080fd5b505af1158015610265573d6000803e3d6000fd5b50505050808061027490610b8c565b9150506101f4565b50505050565b600061028c6105ed565b61080073ffffffffffffffffffffffffffffffffffffffff1663241774e63086866040518463ffffffff1660e01b81526004016102cb93929190610c54565b600060405180830381865afa1580156102e8573d6000803e3d6000fd5b505050506040513d6000823e3d601f19601f820116820180604052508101906103119190610e32565b915091509250929050565b61080073ffffffffffffffffffffffffffffffffffffffff166353266bbb308484600a6040518563ffffffff1660e01b815260040161035e9493929190610ed3565b6020604051808303816000875af115801561037d573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906103a19190610f3f565b5060005b83811015610427573073ffffffffffffffffffffffffffffffffffffffff1663f66013d784846040518363ffffffff1660e01b81526004016103e8929190610c21565b600060405180830381600087803b15801561040257600080fd5b505af1925050508015610413575060015b50808061041f90610b8c565b9150506103a5565b5061080073ffffffffffffffffffffffffffffffffffffffff166353266bbb308484600a6040518563ffffffff1660e01b815260040161046a9493929190610ed3565b6020604051808303816000875af1158015610489573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906104ad9190610f3f565b50505050565b6104bb610607565b60005b8381101561055c5761080073ffffffffffffffffffffffffffffffffffffffff1663223b3b7a846040518263ffffffff1660e01b81526004016105019190610f6c565b600060405180830381865afa15801561051e573d6000803e3d6000fd5b505050506040513d6000823e3d601f19601f820116820180604052508101906105479190611138565b9150808061055490610b8c565b9150506104be565b5092915050565b61080073ffffffffffffffffffffffffffffffffffffffff166353266bbb308484600a6040518563ffffffff1660e01b81526004016105a59493929190610ed3565b6020604051808303816000875af11580156105c4573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906105e89190610f3f565b600080fd5b604051806040016040528060608152602001600081525090565b60405180610160016040528060608152602001606081526020016000151581526020016000600381111561063e5761063d6109ab565b5b8152602001600081526020016000815260200160608152602001600060070b8152602001600060070b815260200160008152602001600081525090565b6000604051905090565b600080fd5b600080fd5b6000819050919050565b6106a28161068f565b81146106ad57600080fd5b50565b6000813590506106bf81610699565b92915050565b600080fd5b600080fd5b600080fd5b60008083601f8401126106ea576106e96106c5565b5b8235905067ffffffffffffffff811115610707576107066106ca565b5b602083019150836001820283011115610723576107226106cf565b5b9250929050565b60008060006040848603121561074357610742610685565b5b6000610751868287016106b0565b935050602084013567ffffffffffffffff8111156107725761077161068a565b5b61077e868287016106d4565b92509250509250925092565b600080602083850312156107a1576107a0610685565b5b600083013567ffffffffffffffff8111156107bf576107be61068a565b5b6107cb858286016106d4565b92509250509250929050565b6107e08161068f565b82525050565b600081519050919050565b600082825260208201905092915050565b60005b83811015610820578082015181840152602081019050610805565b60008484015250505050565b6000601f19601f8301169050919050565b6000610848826107e6565b61085281856107f1565b9350610862818560208601610802565b61086b8161082c565b840191505092915050565b61087f8161068f565b82525050565b600060408301600083015184820360008601526108a2828261083d565b91505060208301516108b76020860182610876565b508091505092915050565b60006040820190506108d760008301856107d7565b81810360208301526108e98184610885565b90509392505050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b600061091d826108f2565b9050919050565b61092d81610912565b811461093857600080fd5b50565b60008135905061094a81610924565b92915050565b6000806040838503121561096757610966610685565b5b6000610975858286016106b0565b92505060206109868582860161093b565b9150509250929050565b60008115159050919050565b6109a581610990565b82525050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b600481106109eb576109ea6109ab565b5b50565b60008190506109fc826109da565b919050565b6000610a0c826109ee565b9050919050565b610a1c81610a01565b82525050565b60008160070b9050919050565b610a3881610a22565b82525050565b6000610160830160008301518482036000860152610a5c828261083d565b91505060208301518482036020860152610a76828261083d565b9150506040830151610a8b604086018261099c565b506060830151610a9e6060860182610a13565b506080830151610ab16080860182610876565b5060a0830151610ac460a0860182610876565b5060c083015184820360c0860152610adc828261083d565b91505060e0830151610af160e0860182610a2f565b50610100830151610b06610100860182610a2f565b50610120830151610b1b610120860182610876565b50610140830151610b30610140860182610876565b508091505092915050565b60006020820190508181036000830152610b558184610a3e565b905092915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b6000610b978261068f565b91507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8203610bc957610bc8610b5d565b5b600182019050919050565b600082825260208201905092915050565b82818337600083830152505050565b6000610c008385610bd4565b9350610c0d838584610be5565b610c168361082c565b840190509392505050565b60006020820190508181036000830152610c3c818486610bf4565b90509392505050565b610c4e81610912565b82525050565b6000604082019050610c696000830186610c45565b8181036020830152610c7c818486610bf4565b9050949350505050565b600081519050610c9581610699565b92915050565b600080fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b610cd88261082c565b810181811067ffffffffffffffff82111715610cf757610cf6610ca0565b5b80604052505050565b6000610d0a61067b565b9050610d168282610ccf565b919050565b600080fd5b600080fd5b600067ffffffffffffffff821115610d4057610d3f610ca0565b5b610d498261082c565b9050602081019050919050565b6000610d69610d6484610d25565b610d00565b905082815260208101848484011115610d8557610d84610d20565b5b610d90848285610802565b509392505050565b600082601f830112610dad57610dac6106c5565b5b8151610dbd848260208601610d56565b91505092915050565b600060408284031215610ddc57610ddb610c9b565b5b610de66040610d00565b9050600082015167ffffffffffffffff811115610e0657610e05610d1b565b5b610e1284828501610d98565b6000830152506020610e2684828501610c86565b60208301525092915050565b60008060408385031215610e4957610e48610685565b5b6000610e5785828601610c86565b925050602083015167ffffffffffffffff811115610e7857610e7761068a565b5b610e8485828601610dc6565b9150509250929050565b6000819050919050565b6000819050919050565b6000610ebd610eb8610eb384610e8e565b610e98565b61068f565b9050919050565b610ecd81610ea2565b82525050565b6000606082019050610ee86000830187610c45565b8181036020830152610efb818587610bf4565b9050610f0a6040830184610ec4565b95945050505050565b610f1c81610990565b8114610f2757600080fd5b50565b600081519050610f3981610f13565b92915050565b600060208284031215610f5557610f54610685565b5b6000610f6384828501610f2a565b91505092915050565b6000602082019050610f816000830184610c45565b92915050565b60048110610f9457600080fd5b50565b600081519050610fa681610f87565b92915050565b610fb581610a22565b8114610fc057600080fd5b50565b600081519050610fd281610fac565b92915050565b60006101608284031215610fef57610fee610c9b565b5b610ffa610160610d00565b9050600082015167ffffffffffffffff81111561101a57611019610d1b565b5b61102684828501610d98565b600083015250602082015167ffffffffffffffff81111561104a57611049610d1b565b5b61105684828501610d98565b602083015250604061106a84828501610f2a565b604083015250606061107e84828501610f97565b606083015250608061109284828501610c86565b60808301525060a06110a684828501610c86565b60a08301525060c082015167ffffffffffffffff8111156110ca576110c9610d1b565b5b6110d684828501610d98565b60c08301525060e06110ea84828501610fc3565b60e0830152506101006110ff84828501610fc3565b6101008301525061012061111584828501610c86565b6101208301525061014061112b84828501610c86565b6101408301525092915050565b60006020828403121561114e5761114d610685565b5b600082015167ffffffffffffffff81111561116c5761116b61068a565b5b61117884828501610fd8565b9150509291505056fea2646970667358221220b11abac490c07bdcbb16b8258b290743cd7265572df2a5293e55d93b6e3a2a3864736f6c63430008140033", "linkReferences": {}, "deployedLinkReferences": {} } diff --git a/precompiles/testutil/contracts/StakingReverter.sol b/precompiles/testutil/contracts/StakingReverter.sol index 52659691d..469267cb4 100644 --- a/precompiles/testutil/contracts/StakingReverter.sol +++ b/precompiles/testutil/contracts/StakingReverter.sol @@ -31,6 +31,24 @@ contract StakingReverter { } } + /// @dev callPrecompileBeforeAndAfterRevert tests whether precompile calls that occur + /// before and after an intentionally ignored revert correctly modify the state. + /// This method assumes that the StakingReverter.sol contract holds a native balance. + /// Therefore, in order to call this method, the contract must be funded with a balance in advance. + function callPrecompileBeforeAndAfterRevert(uint numTimes, string calldata validatorAddress) external { + STAKING_CONTRACT.delegate(address(this), validatorAddress, 10); + + for (uint i = 0; i < numTimes; i++) { + try + StakingReverter(address(this)).performDelegation( + validatorAddress + ) + {} catch {} + } + + STAKING_CONTRACT.delegate(address(this), validatorAddress, 10); + } + function performDelegation(string calldata validatorAddress) external { STAKING_CONTRACT.delegate(address(this), validatorAddress, 10); revert(); diff --git a/tests/integration/precompiles/distribution/test_distribution.go b/tests/integration/precompiles/distribution/test_distribution.go index e59ddabfe..c00db04da 100644 --- a/tests/integration/precompiles/distribution/test_distribution.go +++ b/tests/integration/precompiles/distribution/test_distribution.go @@ -497,13 +497,18 @@ func (s *PrecompileTestSuite) TestCMS() { 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) + // NOTES: After stack-based snapshot mechanism is added for precompile call, + // CacheMultiStore.Write() is always called once when tx succeeds. + // It is because CacheMultiStore() is not called when creating snapshot for MultiStore, + // Count of Write() is not accumulated. + testutil.ValidateWrites(s.T(), cms, 1) } 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) + // NOTES: After stack-based snapshot mechanism is added for precompile call, + // CacheMultiStore.Write() is not called when tx fails. + testutil.ValidateWrites(s.T(), cms, 0) } }) } diff --git a/tests/integration/precompiles/distribution/test_integration.go b/tests/integration/precompiles/distribution/test_integration.go index 732840875..d930cd590 100644 --- a/tests/integration/precompiles/distribution/test_integration.go +++ b/tests/integration/precompiles/distribution/test_integration.go @@ -2274,7 +2274,7 @@ func TestPrecompileIntegrationTestSuite(t *testing.T, create network.CreateEvmAp // set gas such that the internal keeper function called by the precompile fails out mid-execution txArgs.GasLimit = 80_000 - _, _, err = s.factory.CallContractAndCheckLogs( + _, txRes, err := s.factory.CallContractAndCheckLogs( s.keyring.GetPrivKey(0), txArgs, callArgs, @@ -2286,7 +2286,8 @@ func TestPrecompileIntegrationTestSuite(t *testing.T, create network.CreateEvmAp balRes, err := s.grpcHandler.GetBalanceFromBank(s.keyring.GetAccAddr(0), s.bondDenom) Expect(err).To(BeNil()) finalBalance := balRes.Balance - expectedGasCost := math.NewInt(79_416_000_000_000) + + expectedGasCost := math.NewIntFromUint64(txRes.GasUsed).Mul(math.NewIntFromBigInt(txArgs.GasPrice)) Expect(finalBalance.Amount.Equal(initialBalance.Amount.Sub(expectedGasCost))).To(BeTrue(), "expected final balance must be initial balance minus any gas spent") res, err = s.grpcHandler.GetDelegationTotalRewards(s.keyring.GetAccAddr(0).String()) diff --git a/tests/integration/precompiles/staking/test_integration.go b/tests/integration/precompiles/staking/test_integration.go index 22ba50e04..c94c9d61f 100644 --- a/tests/integration/precompiles/staking/test_integration.go +++ b/tests/integration/precompiles/staking/test_integration.go @@ -1834,6 +1834,57 @@ func TestPrecompileIntegrationTestSuite(t *testing.T, create network.CreateEvmAp Expect(err).NotTo(BeNil()) Expect(err.Error()).To(ContainSubstring("not found"), "expected NO delegation created") }) + + It("should delegate before and after intentionaly ignored delegation revert - successful tx", func() { + delegationAmount := math.NewInt(10) + expectedDelegationAmount := delegationAmount.Add(delegationAmount) + + callArgs := testutiltypes.CallArgs{ + ContractABI: stakingReverterContract.ABI, + MethodName: "callPrecompileBeforeAndAfterRevert", + Args: []interface{}{ + big.NewInt(5), s.network.GetValidators()[0].OperatorAddress, + }, + } + + delegateCheck := passCheck.WithExpEvents(staking.EventTypeDelegate, staking.EventTypeDelegate) + + // The transaction should succeed with delegations occurring both before and after the intended revert. + // The revert itself is not propagated because it occurs within the scope of a try-catch statement, + // but is not caught by the catch block. + res, _, err := s.factory.CallContractAndCheckLogs( + s.keyring.GetPrivKey(0), + evmtypes.EvmTxArgs{ + To: &stkReverterAddr, + GasPrice: gasPrice.BigInt(), + }, + callArgs, + delegateCheck, + ) + Expect(err).To(BeNil(), "error while calling the smart contract: %v", err) + Expect(s.network.NextBlock()).To(BeNil()) + + fees := gasPrice.MulRaw(res.GasUsed) + + // delegation should have been created + qRes, err := s.grpcHandler.GetDelegation(sdk.AccAddress(stkReverterAddr.Bytes()).String(), s.network.GetValidators()[0].OperatorAddress) + Expect(err).To(BeNil()) + Expect(qRes.DelegationResponse.Delegation.GetDelegatorAddr()).To(Equal(sdk.AccAddress(stkReverterAddr.Bytes()).String()), "expected delegator address is equal to contract address") + Expect(qRes.DelegationResponse.Delegation.GetShares().BigInt()).To(Equal(expectedDelegationAmount.BigInt()), "expected different delegation shares") + + // contract balance should be deducted by delegation amount + balRes, err := s.grpcHandler.GetBalanceFromBank(stkReverterAddr.Bytes(), s.bondDenom) + Expect(err).To(BeNil()) + contractFinalBalance := balRes.Balance + Expect(contractFinalBalance.Amount).To(Equal(contractInitialBalance.Amount.Sub(expectedDelegationAmount))) + + // fees deducted on tx sender. + // delegation amount is deducted on contract balance that is previously funded. + balRes, err = s.grpcHandler.GetBalanceFromBank(s.keyring.GetAccAddr(0), s.bondDenom) + Expect(err).To(BeNil()) + txSenderFinalBal := balRes.Balance + Expect(txSenderFinalBal.Amount).To(Equal(txSenderInitialBal.Amount.Sub(fees)), "expected tx sender balance to be deducted by fees") + }) }) Context("Table-driven tests for Delegate method", func() { diff --git a/tests/integration/precompiles/staking/test_staking.go b/tests/integration/precompiles/staking/test_staking.go index ce665c4e2..09d638647 100644 --- a/tests/integration/precompiles/staking/test_staking.go +++ b/tests/integration/precompiles/staking/test_staking.go @@ -777,7 +777,11 @@ func (s *PrecompileTestSuite) TestCMS() { 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) + // NOTES: After stack-based snapshot mechanism is added for precompile call, + // CacheMultiStore.Write() is always called once when tx succeeds. + // It is because CacheMultiStore() is not called when creating snapshot for MultiStore, + // Count of Write() is not accumulated. + testutil.ValidateWrites(s.T(), cms, 1) } else { if tc.expKeeperPass { s.Require().Contains(resp.VmError, tc.errContains, @@ -786,8 +790,9 @@ func (s *PrecompileTestSuite) TestCMS() { 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) + // NOTES: After stack-based snapshot mechanism is added for precompile call, + // CacheMultiStore.Write() is not called when tx fails. + testutil.ValidateWrites(s.T(), cms, 0) } else { s.Require().Error(err, "expected error to be returned when running the precompile") s.Require().Nil(resp, "expected returned response to be nil") diff --git a/x/vm/keeper/keeper.go b/x/vm/keeper/keeper.go index 40019f21e..69c781ee9 100644 --- a/x/vm/keeper/keeper.go +++ b/x/vm/keeper/keeper.go @@ -40,6 +40,9 @@ type Keeper struct { // key to access the transient store, which is reset on every block during Commit transientKey storetypes.StoreKey + // KVStore Keys for modules wired to app + storeKeys map[string]*storetypes.KVStoreKey + // the address capable of executing a MsgUpdateParams message. Typically, this should be the x/gov module account. authority sdk.AccAddress @@ -73,6 +76,7 @@ type Keeper struct { func NewKeeper( cdc codec.BinaryCodec, storeKey, transientKey storetypes.StoreKey, + keys map[string]*storetypes.KVStoreKey, authority sdk.AccAddress, ak types.AccountKeeper, bankKeeper types.BankKeeper, @@ -106,6 +110,7 @@ func NewKeeper( transientKey: transientKey, tracer: tracer, erc20Keeper: erc20Keeper, + storeKeys: keys, } } @@ -351,3 +356,8 @@ func (k Keeper) AddTransientGasUsed(ctx sdk.Context, gasUsed uint64) (uint64, er k.SetTransientGasUsed(ctx, result) return result, nil } + +// KVStoreKeys returns KVStore keys injected to keeper +func (k Keeper) KVStoreKeys() map[string]*storetypes.KVStoreKey { + return k.storeKeys +} diff --git a/x/vm/keeper/keeper_test.go b/x/vm/keeper/keeper_test.go index 25d06f513..40bd87c7a 100644 --- a/x/vm/keeper/keeper_test.go +++ b/x/vm/keeper/keeper_test.go @@ -9,16 +9,33 @@ import ( cmtproto "github.com/cometbft/cometbft/proto/tendermint/types" cmttime "github.com/cometbft/cometbft/types/time" + erc20types "github.com/cosmos/evm/x/erc20/types" + feemarkettypes "github.com/cosmos/evm/x/feemarket/types" + precisebanktypes "github.com/cosmos/evm/x/precisebank/types" vmkeeper "github.com/cosmos/evm/x/vm/keeper" vmtypes "github.com/cosmos/evm/x/vm/types" "github.com/cosmos/evm/x/vm/types/mocks" + ibctransfertypes "github.com/cosmos/ibc-go/v10/modules/apps/transfer/types" + ibcexported "github.com/cosmos/ibc-go/v10/modules/core/exported" storetypes "cosmossdk.io/store/types" + evidencetypes "cosmossdk.io/x/evidence/types" + "cosmossdk.io/x/feegrant" + upgradetypes "cosmossdk.io/x/upgrade/types" "github.com/cosmos/cosmos-sdk/testutil" sdk "github.com/cosmos/cosmos-sdk/types" moduletestutil "github.com/cosmos/cosmos-sdk/types/module/testutil" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + authzkeeper "github.com/cosmos/cosmos-sdk/x/authz/keeper" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + consensusparamtypes "github.com/cosmos/cosmos-sdk/x/consensus/types" + distrtypes "github.com/cosmos/cosmos-sdk/x/distribution/types" + govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" + minttypes "github.com/cosmos/cosmos-sdk/x/mint/types" + paramstypes "github.com/cosmos/cosmos-sdk/x/params/types" + slashingtypes "github.com/cosmos/cosmos-sdk/x/slashing/types" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" ) type KeeperTestSuite struct { @@ -38,6 +55,16 @@ func TestKeeperTestSuite(t *testing.T) { } func (suite *KeeperTestSuite) SetupTest() { + keys := storetypes.NewKVStoreKeys( + authtypes.StoreKey, banktypes.StoreKey, stakingtypes.StoreKey, + minttypes.StoreKey, distrtypes.StoreKey, slashingtypes.StoreKey, + govtypes.StoreKey, paramstypes.StoreKey, consensusparamtypes.StoreKey, + upgradetypes.StoreKey, feegrant.StoreKey, evidencetypes.StoreKey, authzkeeper.StoreKey, + // ibc keys + ibcexported.StoreKey, ibctransfertypes.StoreKey, + // Cosmos EVM store keys + vmtypes.StoreKey, feemarkettypes.StoreKey, erc20types.StoreKey, precisebanktypes.StoreKey, + ) key := storetypes.NewKVStoreKey(vmtypes.StoreKey) transientKey := storetypes.NewTransientStoreKey(vmtypes.TransientKey) testCtx := testutil.DefaultContextWithDB(suite.T(), key, storetypes.NewTransientStoreKey("transient_test")) @@ -59,6 +86,7 @@ func (suite *KeeperTestSuite) SetupTest() { encCfg.Codec, key, transientKey, + keys, authority, suite.accKeeper, suite.bankKeeper, diff --git a/x/vm/statedb/interfaces.go b/x/vm/statedb/interfaces.go index f281fafc7..221268315 100644 --- a/x/vm/statedb/interfaces.go +++ b/x/vm/statedb/interfaces.go @@ -4,6 +4,8 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/vm" + storetypes "cosmossdk.io/store/types" + sdk "github.com/cosmos/cosmos-sdk/types" ) @@ -33,4 +35,8 @@ type Keeper interface { DeleteCode(ctx sdk.Context, codeHash []byte) SetCode(ctx sdk.Context, codeHash []byte, code []byte) DeleteAccount(ctx sdk.Context, addr common.Address) error + + // Getter for injected KVStore keys + // It is used for StateDB.snapshotter creation + KVStoreKeys() map[string]*storetypes.KVStoreKey } diff --git a/x/vm/statedb/journal.go b/x/vm/statedb/journal.go index 97aba07bb..3ae0817dd 100644 --- a/x/vm/statedb/journal.go +++ b/x/vm/statedb/journal.go @@ -23,8 +23,6 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/holiman/uint256" - storetypes "cosmossdk.io/store/types" - sdk "github.com/cosmos/cosmos-sdk/types" ) @@ -146,8 +144,8 @@ type ( slot *common.Hash } precompileCallChange struct { - multiStore storetypes.CacheMultiStore - events sdk.Events + snapshot int + events sdk.Events } createContractChange struct { account *common.Address @@ -182,7 +180,7 @@ func (ch createContractChange) Dirtied() *common.Address { func (pc precompileCallChange) Revert(s *StateDB) { // rollback multi store from cache ctx to the previous // state stored in the snapshot - s.RevertMultiStore(pc.multiStore, pc.events) + s.RevertMultiStore(pc.snapshot, pc.events) } func (pc precompileCallChange) Dirtied() *common.Address { diff --git a/x/vm/statedb/mock_test.go b/x/vm/statedb/mock_test.go index 130d771e6..6f83bf639 100644 --- a/x/vm/statedb/mock_test.go +++ b/x/vm/statedb/mock_test.go @@ -11,6 +11,8 @@ import ( "github.com/cosmos/evm/x/vm/statedb" "github.com/cosmos/evm/x/vm/types" + storetypes "cosmossdk.io/store/types" + sdk "github.com/cosmos/cosmos-sdk/types" ) @@ -115,3 +117,7 @@ func (k MockKeeper) Clone() *MockKeeper { codes := maps.Clone(k.codes) return &MockKeeper{accounts, codes} } + +func (k MockKeeper) KVStoreKeys() map[string]*storetypes.KVStoreKey { + return make(map[string]*storetypes.KVStoreKey) +} diff --git a/x/vm/statedb/state_object.go b/x/vm/statedb/state_object.go index dbb5cb155..a85bcb4df 100644 --- a/x/vm/statedb/state_object.go +++ b/x/vm/statedb/state_object.go @@ -9,8 +9,6 @@ import ( "github.com/cosmos/evm/x/vm/types" - storetypes "cosmossdk.io/store/types" - sdk "github.com/cosmos/cosmos-sdk/types" ) @@ -141,10 +139,10 @@ func (s *stateObject) SetBalance(amount *uint256.Int) uint256.Int { // AddPrecompileFn appends to the journal an entry // with a snapshot of the multi-store and events // previous to the precompile call -func (s *stateObject) AddPrecompileFn(cms storetypes.CacheMultiStore, events sdk.Events) { +func (s *stateObject) AddPrecompileFn(snapshot int, events sdk.Events) { s.db.journal.append(precompileCallChange{ - multiStore: cms, - events: events, + snapshot: snapshot, + events: events, }) } diff --git a/x/vm/statedb/statedb.go b/x/vm/statedb/statedb.go index 9eacbb482..a3e1cb53f 100644 --- a/x/vm/statedb/statedb.go +++ b/x/vm/statedb/statedb.go @@ -18,6 +18,8 @@ import ( "github.com/ethereum/go-ethereum/trie/utils" "github.com/holiman/uint256" + "github.com/cosmos/evm/x/vm/store/snapshotmulti" + vmstoretypes "github.com/cosmos/evm/x/vm/store/types" "github.com/cosmos/evm/x/vm/types" errorsmod "cosmossdk.io/errors" @@ -49,6 +51,9 @@ type StateDB struct { cacheCtx sdk.Context // writeCache function contains all the changes related to precompile calls. writeCache func() + // snapshotter is used for snapshot creation and revert + // this snapshot is used for precompile call + snapshotter vmstoretypes.Snapshotter // Transient storage transientStorage transientStorage @@ -163,29 +168,17 @@ func (s *StateDB) GetCacheContext() (sdk.Context, error) { return s.cacheCtx, nil } -// MultiStoreSnapshot returns a copy of the stateDB CacheMultiStore. -func (s *StateDB) MultiStoreSnapshot() storetypes.CacheMultiStore { - if s.writeCache == nil { - err := s.cache() - if err != nil { - return s.ctx.MultiStore().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.CacheMultiStore() - - return snapshot +// MultiStoreSnapshot snapshots stateDB CacheMultiStore +// and returns snapshot index +func (s *StateDB) MultiStoreSnapshot() int { + return s.snapshotter.Snapshot() } -func (s *StateDB) RevertMultiStore(cms storetypes.CacheMultiStore, events sdk.Events) { - s.cacheCtx = s.cacheCtx.WithMultiStore(cms) +func (s *StateDB) RevertMultiStore(snapshot int, events sdk.Events) { + s.snapshotter.RevertToSnapshot(snapshot) s.writeCache = func() { - // rollback the events to the ones - // on the snapshot s.ctx.EventManager().EmitEvents(events) - cms.Write() + s.cacheCtx.MultiStore().(storetypes.CacheMultiStore).Write() } } @@ -194,7 +187,21 @@ func (s *StateDB) cache() error { if s.ctx.MultiStore() == nil { return errors.New("ctx has no multi store") } - s.cacheCtx, s.writeCache = s.ctx.CacheContext() + s.cacheCtx, _ = s.ctx.CacheContext() + + // Get KVStores for modules wired to app + cms := s.cacheCtx.MultiStore().(storetypes.CacheMultiStore) + storeKeys := s.keeper.KVStoreKeys() + + // Create and set snapshot store to stateDB + snapshotStore := snapshotmulti.NewStore(cms, storeKeys) + s.snapshotter = snapshotStore + s.cacheCtx = s.cacheCtx.WithMultiStore(snapshotStore) + s.writeCache = func() { + s.ctx.EventManager().EmitEvents(s.cacheCtx.EventManager().Events()) + s.cacheCtx.MultiStore().(storetypes.CacheMultiStore).Write() + } + return nil } @@ -409,12 +416,12 @@ func (s *StateDB) setStateObject(object *stateObject) { // AddPrecompileFn adds a precompileCall journal entry // with a snapshot of the multi-store and events previous // to the precompile call. -func (s *StateDB) AddPrecompileFn(addr common.Address, cms storetypes.CacheMultiStore, events sdk.Events) error { +func (s *StateDB) AddPrecompileFn(addr common.Address, snapshot int, events sdk.Events) error { stateObject := s.getOrNewStateObject(addr) if stateObject == nil { return fmt.Errorf("could not add precompile call to address %s. State object not found", addr) } - stateObject.AddPrecompileFn(cms, events) + stateObject.AddPrecompileFn(snapshot, events) s.precompileCallsCounter++ if s.precompileCallsCounter > types.MaxPrecompileCalls { return fmt.Errorf("max calls to precompiles (%d) reached", types.MaxPrecompileCalls) diff --git a/x/vm/store/snapshotkv/store.go b/x/vm/store/snapshotkv/store.go new file mode 100644 index 000000000..6f9e002f6 --- /dev/null +++ b/x/vm/store/snapshotkv/store.go @@ -0,0 +1,69 @@ +package snapshotkv + +import ( + "fmt" + + "github.com/cosmos/evm/x/vm/store/types" + + "cosmossdk.io/store/cachekv" + storetypes "cosmossdk.io/store/types" +) + +// Store manages a stack of nested cache store to +// support the evm `StateDB`'s `Snapshot` and `RevertToSnapshot` methods. +type Store struct { + // Store of the initial state before transaction execution + initialStore storetypes.CacheKVStore + + // Stack of cached store + cacheStores []storetypes.CacheKVStore +} + +var _ types.SnapshotKVStore = (*Store)(nil) + +// NewStore creates a new Store object +func NewStore(store storetypes.CacheKVStore) *Store { + return &Store{ + initialStore: store, + cacheStores: nil, + } +} + +// CurrentStore returns the top of cached store stack. +// If the stack is empty, returns the initial store. +func (cs *Store) CurrentStore() storetypes.CacheKVStore { + l := len(cs.cacheStores) + if l == 0 { + return cs.initialStore + } + return cs.cacheStores[l-1] +} + +// Commit commits all the cached stores from top to bottom in order +// and clears the cache stack by setting an empty slice of cache store. +func (cs *Store) Commit() { + // commit in order from top to bottom + for i := len(cs.cacheStores) - 1; i >= 0; i-- { + cs.cacheStores[i].Write() + } + cs.initialStore.Write() + cs.cacheStores = nil +} + +// Snapshot pushes a new cached store to the stack, +// and returns the index of it. +func (cs *Store) Snapshot() int { + cs.cacheStores = append(cs.cacheStores, cachekv.NewStore(cs.CurrentStore())) + return len(cs.cacheStores) - 1 +} + +// RevertToSnapshot pops all the cached stores +// whose index is greator than or equal to target. +// The target should be snapshot index returned by `Snapshot`. +// This function panics if the index is out of bounds. +func (cs *Store) RevertToSnapshot(target int) { + if target < 0 || target >= len(cs.cacheStores) { + panic(fmt.Errorf("snapshot index %d out of bound [%d..%d)", target, 0, len(cs.cacheStores))) + } + cs.cacheStores = cs.cacheStores[:target] +} diff --git a/x/vm/store/snapshotkv/store_test.go b/x/vm/store/snapshotkv/store_test.go new file mode 100644 index 000000000..0939611d4 --- /dev/null +++ b/x/vm/store/snapshotkv/store_test.go @@ -0,0 +1,122 @@ +package snapshotkv_test + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/require" + + dbm "github.com/cosmos/cosmos-db" + "github.com/cosmos/evm/x/vm/store/snapshotkv" + + "cosmossdk.io/store/cachekv" + "cosmossdk.io/store/dbadapter" +) + +func newSnapshotKV() *snapshotkv.Store { + base := cachekv.NewStore(dbadapter.Store{DB: dbm.NewMemDB()}) + return snapshotkv.NewStore(base) +} + +func TestSnapshotIndexing(t *testing.T) { + store := newSnapshotKV() + + idx0 := store.Snapshot() + require.Equal(t, 0, idx0) + + idx1 := store.Snapshot() + require.Equal(t, 1, idx1) + + idx2 := store.Snapshot() + require.Equal(t, 2, idx2) +} + +func TestSnapshotRevertAndCommit(t *testing.T) { + store := newSnapshotKV() + + // set in base store + base := store.CurrentStore() + base.Set([]byte("a"), []byte("1")) + + idx0 := store.Snapshot() + store.CurrentStore().Set([]byte("b"), []byte("2")) + + idx1 := store.Snapshot() + store.CurrentStore().Set([]byte("c"), []byte("3")) + + // revert latest snapshot (idx1) + store.RevertToSnapshot(idx1) + require.Nil(t, store.CurrentStore().Get([]byte("c"))) + require.Equal(t, []byte("2"), store.CurrentStore().Get([]byte("b"))) + + // revert the first snapshot + store.RevertToSnapshot(idx0) + require.Nil(t, store.CurrentStore().Get([]byte("b"))) + require.Equal(t, []byte("1"), store.CurrentStore().Get([]byte("a"))) + + // take new snapshot and commit + store.Snapshot() + store.CurrentStore().Set([]byte("d"), []byte("4")) + store.Commit() + + require.Equal(t, []byte("4"), base.Get([]byte("d"))) + + // commit clears the snapshot stack + idx := store.Snapshot() + require.Equal(t, 0, idx) +} + +func TestSnapshotKVRevertOverwriteSameKey(t *testing.T) { + // Initialize a fresh SnapshotKVStore + store := newSnapshotKV() + base := store.CurrentStore() + + // Initial write under key "a" + store.CurrentStore().Set([]byte("a"), []byte("1")) + + // Overwrite "a" with "2" + idx0 := store.Snapshot() + store.CurrentStore().Set([]byte("a"), []byte("2")) + + // Overwrite "a" with "3" + idx1 := store.Snapshot() + store.CurrentStore().Set([]byte("a"), []byte("3")) + + // Revert to idx1: expect value "2" + store.RevertToSnapshot(idx1) + require.Equal(t, []byte("2"), store.CurrentStore().Get([]byte("a"))) + + // Revert to idx0: expect value "1" + store.RevertToSnapshot(idx0) + require.Equal(t, []byte("1"), store.CurrentStore().Get([]byte("a"))) + + // Take a new snapshot, overwrite "a" with "4", then commit + idx2 := store.Snapshot() + store.CurrentStore().Set([]byte("a"), []byte("4")) + store.Commit() + + // After commit, the base store should have "4" + require.Equal(t, []byte("4"), base.Get([]byte("a"))) + + // Commit clears the snapshot stack, so reverting to idx2 should panic + expectedErr := fmt.Sprintf("snapshot index %d out of bound [%d..%d)", idx2, 0, 0) + require.PanicsWithErrorf( + t, + expectedErr, + func() { store.RevertToSnapshot(idx2) }, + "RevertToSnapshot should panic when idx out of bounds", + ) +} + +func TestSnapshotInvalidIndex(t *testing.T) { + store := newSnapshotKV() + store.Snapshot() + + require.PanicsWithError(t, "snapshot index 1 out of bound [0..1)", func() { + store.RevertToSnapshot(1) + }) + + require.PanicsWithError(t, "snapshot index -1 out of bound [0..1)", func() { + store.RevertToSnapshot(-1) + }) +} diff --git a/x/vm/store/snapshotmulti/store.go b/x/vm/store/snapshotmulti/store.go new file mode 100644 index 000000000..259f866b6 --- /dev/null +++ b/x/vm/store/snapshotmulti/store.go @@ -0,0 +1,166 @@ +package snapshotmulti + +import ( + "fmt" + "io" + "sort" + + "github.com/cosmos/evm/x/vm/store/snapshotkv" + "github.com/cosmos/evm/x/vm/store/types" + vmtypes "github.com/cosmos/evm/x/vm/types" + + storetypes "cosmossdk.io/store/types" +) + +type Store struct { + stores map[storetypes.StoreKey]types.SnapshotKVStore + storeKeys []*storetypes.KVStoreKey // ordered keys + head int +} + +var _ types.SnapshotMultiStore = (*Store)(nil) + +// NewStore creates a new Store objectwith CacheMultiStore and KVStoreKeys +func NewStore(cms storetypes.CacheMultiStore, keys map[string]*storetypes.KVStoreKey) *Store { + s := &Store{ + stores: make(map[storetypes.StoreKey]types.SnapshotKVStore), + storeKeys: vmtypes.SortedKVStoreKeys(keys), + head: types.InitialHead, + } + + for _, key := range s.storeKeys { + store := cms.GetKVStore(key).(storetypes.CacheKVStore) + s.stores[key] = snapshotkv.NewStore(store) + } + + return s +} + +// NewStore creates a new Store object with KVStores +func NewStoreWithKVStores(stores map[*storetypes.KVStoreKey]storetypes.CacheWrap) *Store { + s := &Store{ + stores: make(map[storetypes.StoreKey]types.SnapshotKVStore), + head: types.InitialHead, + } + + for key, store := range stores { + s.stores[key] = snapshotkv.NewStore(store.(storetypes.CacheKVStore)) + s.storeKeys = append(s.storeKeys, key) + } + + sort.Slice(s.storeKeys, func(i, j int) bool { + return s.storeKeys[i].Name() < s.storeKeys[j].Name() + }) + + return s +} + +// Snapshot pushes a new cached context to the stack, +// and returns the index of it. +func (s *Store) Snapshot() int { + for _, key := range s.storeKeys { + s.stores[key].Snapshot() + } + s.head++ + + // latest snapshot is just before head + return s.head - 1 +} + +// RevertToSnapshot pops all the cached stores +// whose index is greator than or equal to target. +// The target should be snapshot index returned by `Snapshot`. +// This function panics if the index is out of bounds. +func (s *Store) RevertToSnapshot(target int) { + for _, key := range s.storeKeys { + s.stores[key].RevertToSnapshot(target) + } + s.head = target +} + +// GetStoreType returns the type of the store. +func (s *Store) GetStoreType() storetypes.StoreType { + return storetypes.StoreTypeMulti +} + +// Implements CacheWrapper. +func (s *Store) CacheWrap() storetypes.CacheWrap { + return s.CacheMultiStore().(storetypes.CacheWrap) +} + +// CacheWrapWithTrace implements the CacheWrapper interface. +// +// NOTE: CacheWrapWithTrace is a method that enables a Store to satisfy the CacheWrapper interface. +// Although it accepts an io.Writer and tracingContext as inputs, these are not used in the implementation. +// Instead, it simply adds an additional cache layer on top of the existing KVStores. +// As a result, while the return value differs, the behavior is effectively the same as the Snapshot() method. +func (s *Store) CacheWrapWithTrace(_ io.Writer, _ storetypes.TraceContext) storetypes.CacheWrap { + return s.CacheWrap() +} + +// CacheMultiStore snapshots store and return current store. +func (s *Store) CacheMultiStore() storetypes.CacheMultiStore { + s.Snapshot() + return s +} + +// CacheMultiStoreWithVersion load stores at a snapshot version. +// +// NOTE: CacheMultiStoreWithVersion is no-op function. +func (s *Store) CacheMultiStoreWithVersion(_ int64) (storetypes.CacheMultiStore, error) { + return s, nil +} + +// GetStore returns an underlying Store by key. +func (s *Store) GetStore(key storetypes.StoreKey) storetypes.Store { + store := s.stores[key] + if key == nil || store == nil { + panic(fmt.Sprintf("kv store with key %v has not been registered in stores", key)) + } + return store.CurrentStore() +} + +// GetKVStore returns an underlying KVStore by key. +func (s *Store) GetKVStore(key storetypes.StoreKey) storetypes.KVStore { + store := s.stores[key] + if key == nil || store == nil { + panic(fmt.Sprintf("kv store with key %v has not been registered in stores", key)) + } + return store.CurrentStore() +} + +// TracingEnabled returns if tracing is enabled for the MultiStore. +func (s *Store) TracingEnabled() bool { + return false +} + +// SetTracer sets the tracer for the MultiStore that the underlying +// stores will utilize to trace operations. A MultiStore is returned. +// +// NOTE: SetTracer no-op function. +func (s *Store) SetTracer(_ io.Writer) storetypes.MultiStore { + return s +} + +// SetTracingContext updates the tracing context for the MultiStore by merging +// the given context with the existing context by key. Any existing keys will +// be overwritten. It is implied that the caller should update the context when +// necessary between tracing operations. It returns a modified MultiStore. +// +// NOTE: SetTracingContext no-op function +func (s *Store) SetTracingContext(_ storetypes.TraceContext) storetypes.MultiStore { + return s +} + +// LatestVersion returns the branch version of the store +func (s *Store) LatestVersion() int64 { + return int64(s.head) +} + +// Write calls Write on each underlying store. +func (s *Store) Write() { + for _, key := range s.storeKeys { + s.stores[key].Commit() + } + s.head = types.InitialHead +} diff --git a/x/vm/store/snapshotmulti/store_bench_test.go b/x/vm/store/snapshotmulti/store_bench_test.go new file mode 100644 index 000000000..ed8a0f2dd --- /dev/null +++ b/x/vm/store/snapshotmulti/store_bench_test.go @@ -0,0 +1,70 @@ +package snapshotmulti + +import ( + "crypto/rand" + "flag" + "fmt" + "testing" + + dbm "github.com/cosmos/cosmos-db" + + "cosmossdk.io/store/cachekv" + "cosmossdk.io/store/dbadapter" + "cosmossdk.io/store/types" +) + +var ( + benchSink interface{} + benchTxCount = flag.Int("bench.numtx", 256, "number of transactions per benchmark run") +) + +// genBytes returns a byte slice of the given length filled with random bytes. +func genBytes(n int) []byte { + bz := make([]byte, n) + if _, err := rand.Read(bz); err != nil { + panic(err) + } + return bz +} + +func setupCacheMultiStoreWithKeys(numStores, numEntries int) (*Store, []*types.KVStoreKey) { + storeMap := make(map[*types.KVStoreKey]types.CacheWrap, numStores) + keys := make([]*types.KVStoreKey, numStores) + for i := 0; i < numStores; i++ { + key := types.NewKVStoreKey(fmt.Sprintf("store%d", i)) + kv := cachekv.NewStore(dbadapter.Store{DB: dbm.NewMemDB()}) + for j := 0; j < numEntries; j++ { + kv.Set([]byte(fmt.Sprintf("%s-key-%d", key.Name(), j)), genBytes(32)) + } + storeMap[key] = kv + keys[i] = key + } + return NewStoreWithKVStores(storeMap), keys +} + +func benchmarkSequential(b *testing.B) { + b.Helper() + cms, keys := setupCacheMultiStoreWithKeys(20, 200000) + selected := keys[:5] + txs := *benchTxCount + + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + for tx := 0; tx < txs; tx++ { + for j := 0; j < 10; j++ { + for _, key := range selected { + kv := cms.GetKVStore(key) + kv.Get([]byte(fmt.Sprintf("%s-key-%d", key.Name(), j%20))) + kv.Set([]byte(fmt.Sprintf("%s-tx-%d-%d", key.Name(), tx, j)), genBytes(32)) + } + snapshot := cms.Snapshot() + benchSink = snapshot + } + } + } +} + +func BenchmarkSequentialCacheMultiStore(b *testing.B) { + benchmarkSequential(b) +} diff --git a/x/vm/store/snapshotmulti/store_test.go b/x/vm/store/snapshotmulti/store_test.go new file mode 100644 index 000000000..ebba81e5a --- /dev/null +++ b/x/vm/store/snapshotmulti/store_test.go @@ -0,0 +1,191 @@ +package snapshotmulti_test + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/require" + + dbm "github.com/cosmos/cosmos-db" + "github.com/cosmos/evm/x/vm/store/snapshotmulti" + + "cosmossdk.io/store/cachekv" + "cosmossdk.io/store/dbadapter" + storetypes "cosmossdk.io/store/types" +) + +func setupStore() (*snapshotmulti.Store, *storetypes.KVStoreKey) { + key := storetypes.NewKVStoreKey("store") + kv := cachekv.NewStore(dbadapter.Store{DB: dbm.NewMemDB()}) + stores := map[*storetypes.KVStoreKey]storetypes.CacheWrap{key: kv} + return snapshotmulti.NewStoreWithKVStores(stores), key +} + +func TestSnapshotMultiIndexing(t *testing.T) { + snapshotStore, _ := setupStore() + + idx0 := snapshotStore.Snapshot() + require.Equal(t, 0, idx0) + + idx1 := snapshotStore.Snapshot() + require.Equal(t, 1, idx1) + + idx2 := snapshotStore.Snapshot() + require.Equal(t, 2, idx2) +} + +func TestSnapshotMultiRevertAndWrite(t *testing.T) { + snapshotStore, key := setupStore() + kv := snapshotStore.GetKVStore(key) + kv.Set([]byte("a"), []byte("1")) + + idx0 := snapshotStore.Snapshot() + snapshotStore.GetKVStore(key).Set([]byte("b"), []byte("2")) + + idx1 := snapshotStore.Snapshot() + snapshotStore.GetKVStore(key).Set([]byte("c"), []byte("3")) + + snapshotStore.RevertToSnapshot(idx1) + require.Nil(t, snapshotStore.GetKVStore(key).Get([]byte("c"))) + require.Equal(t, []byte("2"), snapshotStore.GetKVStore(key).Get([]byte("b"))) + + snapshotStore.RevertToSnapshot(idx0) + require.Nil(t, snapshotStore.GetKVStore(key).Get([]byte("b"))) + require.Equal(t, []byte("1"), snapshotStore.GetKVStore(key).Get([]byte("a"))) + + snapshotStore.Snapshot() + snapshotStore.GetKVStore(key).Set([]byte("d"), []byte("4")) + snapshotStore.Write() + + require.Equal(t, []byte("4"), kv.Get([]byte("d"))) + idx := snapshotStore.Snapshot() + require.Equal(t, 0, idx) +} + +func TestSnapshotMultiRevertOverwriteSameKey(t *testing.T) { + // Setup a fresh SnapshotMultiStore and a key + snapshotStore, key := setupStore() + kv := snapshotStore.GetKVStore(key) + + // Initial write under key "a" + kv.Set([]byte("a"), []byte("1")) + + // Overwrite "a" with "2" + idx0 := snapshotStore.Snapshot() + snapshotStore.GetKVStore(key).Set([]byte("a"), []byte("2")) + + // Overwrite "a" with "3" + idx1 := snapshotStore.Snapshot() + snapshotStore.GetKVStore(key).Set([]byte("a"), []byte("3")) + + // Revert to idx1: expect value "2" + snapshotStore.RevertToSnapshot(idx1) + require.Equal(t, []byte("2"), snapshotStore.GetKVStore(key).Get([]byte("a"))) + + // Revert to idx0: expect value "1" + snapshotStore.RevertToSnapshot(idx0) + require.Equal(t, []byte("1"), snapshotStore.GetKVStore(key).Get([]byte("a"))) + + // Overwrite "a" with "4" + idx2 := snapshotStore.Snapshot() + snapshotStore.GetKVStore(key).Set([]byte("a"), []byte("4")) + snapshotStore.Write() + + // After write, the base store should have "4" + require.Equal(t, []byte("4"), kv.Get([]byte("a"))) + + // Write clears the snapshot stack, so reverting to idx2 should panic + expectedErr := fmt.Sprintf("snapshot index %d out of bound [%d..%d)", idx2, 0, 0) + require.PanicsWithErrorf(t, expectedErr, func() { + snapshotStore.RevertToSnapshot(idx2) + }, "RevertToSnapshot should panic when idx out of bounds") +} + +func TestSnapshotMultiInvalidIndex(t *testing.T) { + snapshotStore, _ := setupStore() + snapshotStore.Snapshot() + + require.PanicsWithError(t, "snapshot index 1 out of bound [0..1)", func() { + snapshotStore.RevertToSnapshot(1) + }) + + require.PanicsWithError(t, "snapshot index -1 out of bound [0..1)", func() { + snapshotStore.RevertToSnapshot(-1) + }) +} + +func TestSnapshotMultiGetStore(t *testing.T) { + snapshotStore, key := setupStore() + + s := snapshotStore.GetStore(key) + require.NotNil(t, s) + require.Equal(t, snapshotStore.GetKVStore(key), s) + + badKey := storetypes.NewKVStoreKey("bad") + require.Panics(t, func() { snapshotStore.GetStore(badKey) }) + require.Panics(t, func() { snapshotStore.GetKVStore(badKey) }) +} + +func TestSnapshotMultiCacheWrap(t *testing.T) { + snapshotStore, _ := setupStore() + + wrap := snapshotStore.CacheWrap() + require.Equal(t, snapshotStore, wrap) + + idx := snapshotStore.Snapshot() + require.Equal(t, 1, idx) +} + +func TestSnapshotMultiCacheWrapWithTrace(t *testing.T) { + snapshotStore, _ := setupStore() + + // NOTES: CacheWrapWithTrace of snapshotmulti.Store is same with regualr CacheWrap, + // and arguments are not actually used. + wrap := snapshotStore.CacheWrapWithTrace(nil, nil) + require.Equal(t, snapshotStore, wrap) + + idx := snapshotStore.Snapshot() + require.Equal(t, 1, idx) +} + +func TestSnapshotMultiCacheMultiStore(t *testing.T) { + snapshotStore, _ := setupStore() + + m := snapshotStore.CacheMultiStore() + require.Equal(t, snapshotStore, m) + + idx := snapshotStore.Snapshot() + require.Equal(t, 1, idx) +} + +func TestSnapshotMultiCacheMultiStoreWithVersion(t *testing.T) { + snapshotStore, _ := setupStore() + + m, _ := snapshotStore.CacheMultiStoreWithVersion(1) + require.Equal(t, snapshotStore, m) +} + +func TestSnapshotMultiMetadata(t *testing.T) { + snapshotStore, _ := setupStore() + + require.Equal(t, storetypes.StoreTypeMulti, snapshotStore.GetStoreType()) + require.False(t, snapshotStore.TracingEnabled()) + require.Equal(t, snapshotStore, snapshotStore.SetTracer(nil)) + require.Equal(t, snapshotStore, snapshotStore.SetTracingContext(nil)) +} + +func TestSnapshotMultiLatestVersion(t *testing.T) { + snapshotStore, _ := setupStore() + + initialVersion := int64(0) + ver0 := snapshotStore.LatestVersion() + require.Equal(t, ver0, initialVersion) + + idx0 := snapshotStore.Snapshot() + ver1 := snapshotStore.LatestVersion() + require.Equal(t, ver1, int64(idx0+1)) + + idx1 := snapshotStore.Snapshot() + ver2 := snapshotStore.LatestVersion() + require.Equal(t, ver2, int64(idx1+1)) +} diff --git a/x/vm/store/types/store.go b/x/vm/store/types/store.go new file mode 100644 index 000000000..0521f219e --- /dev/null +++ b/x/vm/store/types/store.go @@ -0,0 +1,44 @@ +package types + +import ( + storetypes "cosmossdk.io/store/types" +) + +const InitialHead = 0 + +// Snapshotter defines behavior for taking and reverting to state snapshots. +type Snapshotter interface { + // Snapshot captures the current state and returns a snapshot identifier. + // The returned int can be used later to revert back to this exact state. + Snapshot() int + + // RevertToSnapshot rolls back the state to the snapshot corresponding + // to the given identifier. All changes made after that snapshot will be discarded. + RevertToSnapshot(int) +} + +// SnapshotKVStore extends Snapshotter with CacheKVStore-specific operations. +// +// It allows you to take/revert snapshots around KV-store operations, +// inspect the current active store, and commit changes. +type SnapshotKVStore interface { + Snapshotter + + // CurrentStore returns the underlying CacheKVStore that is currently + // active (i.e., where reads and writes will be applied). + CurrentStore() storetypes.CacheKVStore + + // Commit flushes all pending changes in the current store layer + // down to its parent, making them permanent. + Commit() +} + +// SnapshotMultiStore extends Snapshotter and CacheMultiStore. +// +// It allows snapshotting and rollback semantics on a multi-store +// (i.e., a collection of keyed sub-stores), leveraging the existing +// CacheMultiStore interface for basic store and cache management. +type SnapshotMultiStore interface { + Snapshotter + storetypes.CacheMultiStore +} diff --git a/x/vm/types/utils.go b/x/vm/types/utils.go index 0143493bd..dd345b27b 100644 --- a/x/vm/types/utils.go +++ b/x/vm/types/utils.go @@ -4,6 +4,7 @@ import ( "bytes" "fmt" "math/big" + "sort" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" @@ -11,6 +12,7 @@ import ( "github.com/cosmos/gogoproto/proto" errorsmod "cosmossdk.io/errors" + storetypes "cosmossdk.io/store/types" sdk "github.com/cosmos/cosmos-sdk/types" errortypes "github.com/cosmos/cosmos-sdk/types/errors" @@ -135,3 +137,18 @@ func EffectiveGasPrice(baseFee, feeCap, tipCap *big.Int) *big.Int { } return feeCap } + +// SortedKVStoreKeys returns a slice of *KVStoreKey sorted by their map key. +func SortedKVStoreKeys(keys map[string]*storetypes.KVStoreKey) []*storetypes.KVStoreKey { + names := make([]string, 0, len(keys)) + for name := range keys { + names = append(names, name) + } + sort.Strings(names) + + sorted := make([]*storetypes.KVStoreKey, 0, len(keys)) + for _, name := range names { + sorted = append(sorted, keys[name]) + } + return sorted +}