diff --git a/contracts/cancun_opcodes.go b/contracts/cancun_opcodes.go new file mode 100644 index 000000000..0bbed4856 --- /dev/null +++ b/contracts/cancun_opcodes.go @@ -0,0 +1,27 @@ +package contracts + +import ( + _ "embed" + + contractutils "github.com/cosmos/evm/contracts/utils" + evmtypes "github.com/cosmos/evm/x/vm/types" +) + +var ( + // CancunOpcodesJSON are the compiled bytes of the CancunOpcodesContract + // + //go:embed solidity/CancunOpcodes.json + CancunOpcodesJSON []byte + + // CancunOpcodesContract is the compiled cancun opcodes contract + CancunOpcodesContract evmtypes.CompiledContract +) + +func init() { + var err error + if CancunOpcodesContract, err = contractutils.ConvertHardhatBytesToCompiledContract( + CancunOpcodesJSON, + ); err != nil { + panic(err) + } +} diff --git a/contracts/hardhat.config.js b/contracts/hardhat.config.js index e170ff747..8788be037 100644 --- a/contracts/hardhat.config.js +++ b/contracts/hardhat.config.js @@ -3,7 +3,10 @@ module.exports = { solidity: { compilers: [ { - version: "0.8.20", + version: "0.8.25", + settings: { + evmVersion: "cancun", + } }, // This version is required to compile the werc9 contract. { diff --git a/contracts/solidity/CancunOpcodes.json b/contracts/solidity/CancunOpcodes.json new file mode 100644 index 000000000..efbda3057 --- /dev/null +++ b/contracts/solidity/CancunOpcodes.json @@ -0,0 +1,81 @@ +{ + "_format": "hh-sol-artifact-1", + "contractName": "CancunOpcodes", + "sourceName": "solidity/CancunOpcodes.sol", + "abi": [ + { + "inputs": [], + "name": "testBlobBaseFee", + "outputs": [ + { + "internalType": "uint256", + "name": "fee", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "index", + "type": "uint256" + } + ], + "name": "testBlobHash", + "outputs": [ + { + "internalType": "bytes32", + "name": "hash", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint8", + "name": "value", + "type": "uint8" + } + ], + "name": "testSimpleMCopy", + "outputs": [ + { + "internalType": "bytes32", + "name": "copied", + "type": "bytes32" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "testTstoreTload", + "outputs": [ + { + "internalType": "uint256", + "name": "loadedValue", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + } + ], + "bytecode": "0x6080604052348015600e575f80fd5b506102918061001c5f395ff3fe608060405234801561000f575f80fd5b506004361061004a575f3560e01c806307f641e71461004e578063109a972b1461007e5780636a037df3146100ae578063f48357f1146100de575b5f80fd5b61006860048036038101906100639190610176565b6100fc565b60405161007591906101b9565b60405180910390f35b61009860048036038101906100939190610208565b610106565b6040516100a591906101b9565b60405180910390f35b6100c860048036038101906100c39190610176565b610129565b6040516100d59190610242565b60405180910390f35b6100e6610138565b6040516100f39190610242565b60405180910390f35b5f81499050919050565b5f60405182815360408101604052602081602083015e6020810151915050919050565b5f8160425d60425c9050919050565b5f4a905090565b5f80fd5b5f819050919050565b61015581610143565b811461015f575f80fd5b50565b5f813590506101708161014c565b92915050565b5f6020828403121561018b5761018a61013f565b5b5f61019884828501610162565b91505092915050565b5f819050919050565b6101b3816101a1565b82525050565b5f6020820190506101cc5f8301846101aa565b92915050565b5f60ff82169050919050565b6101e7816101d2565b81146101f1575f80fd5b50565b5f81359050610202816101de565b92915050565b5f6020828403121561021d5761021c61013f565b5b5f61022a848285016101f4565b91505092915050565b61023c81610143565b82525050565b5f6020820190506102555f830184610233565b9291505056fea2646970667358221220557c6520cf75983003b61b499d2c575700eeca53ee5de7ae1760ea2a9795b39964736f6c63430008190033", + "deployedBytecode": "0x608060405234801561000f575f80fd5b506004361061004a575f3560e01c806307f641e71461004e578063109a972b1461007e5780636a037df3146100ae578063f48357f1146100de575b5f80fd5b61006860048036038101906100639190610176565b6100fc565b60405161007591906101b9565b60405180910390f35b61009860048036038101906100939190610208565b610106565b6040516100a591906101b9565b60405180910390f35b6100c860048036038101906100c39190610176565b610129565b6040516100d59190610242565b60405180910390f35b6100e6610138565b6040516100f39190610242565b60405180910390f35b5f81499050919050565b5f60405182815360408101604052602081602083015e6020810151915050919050565b5f8160425d60425c9050919050565b5f4a905090565b5f80fd5b5f819050919050565b61015581610143565b811461015f575f80fd5b50565b5f813590506101708161014c565b92915050565b5f6020828403121561018b5761018a61013f565b5b5f61019884828501610162565b91505092915050565b5f819050919050565b6101b3816101a1565b82525050565b5f6020820190506101cc5f8301846101aa565b92915050565b5f60ff82169050919050565b6101e7816101d2565b81146101f1575f80fd5b50565b5f81359050610202816101de565b92915050565b5f6020828403121561021d5761021c61013f565b5b5f61022a848285016101f4565b91505092915050565b61023c81610143565b82525050565b5f6020820190506102555f830184610233565b9291505056fea2646970667358221220557c6520cf75983003b61b499d2c575700eeca53ee5de7ae1760ea2a9795b39964736f6c63430008190033", + "linkReferences": {}, + "deployedLinkReferences": {} +} diff --git a/contracts/solidity/CancunOpcodes.sol b/contracts/solidity/CancunOpcodes.sol new file mode 100644 index 000000000..b9b1a9375 --- /dev/null +++ b/contracts/solidity/CancunOpcodes.sol @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: LGPL-3.0-only + +pragma solidity ^0.8.25; + +contract CancunOpcodes { + /// @notice TSTORE + TLOAD using fixed slot (0x42) + /// @param value The value to store in transient storage + /// @return loadedValue Value loaded from transient slot 0x42 + function testTstoreTload(uint256 value) external returns (uint256 loadedValue) { + assembly { + tstore(0x42, value) + loadedValue := tload(0x42) + } + } + + /// @notice Store 1-byte value in memory, copy it via MCOPY, then load and return + /// @param value The 1-byte value to test + /// @return copied 32-byte memory word after MCOPY + function testSimpleMCopy(uint8 value) external pure returns (bytes32 copied) { + assembly { + let dst := mload(0x40) + mstore8(dst, value) // Store exactly 1 byte at dst position + mstore(0x40, add(dst, 0x40)) + mcopy(add(dst, 0x20), dst, 0x20) // Use 32 byte offset to read 32 bytes after mcopy + copied := mload(add(dst, 0x20)) + } + } + + /// @notice Returns the current blob base fee + /// @return fee Blob base fee (expected 0 on most networks) + function testBlobBaseFee() external view returns (uint256 fee) { + assembly { + fee := blobbasefee() + } + } + + /// @notice Returns the blob hash for a given index + /// @param index Blob index to query + /// @return hash 32-byte blob commitment hash + function testBlobHash(uint256 index) external view returns (bytes32 hash) { + assembly { + hash := blobhash(index) + } + } +} diff --git a/go.mod b/go.mod index b0201c83c..f8f654ca8 100644 --- a/go.mod +++ b/go.mod @@ -257,6 +257,7 @@ replace ( cosmossdk.io/store => github.com/b-harvest/cosmos-sdk/store v0.0.0-20250324051345-ddad0e6f75cb github.com/cometbft/cometbft => github.com/b-harvest/cometbft v0.0.0-20250324035653-78e589510249 github.com/cosmos/cosmos-sdk => github.com/b-harvest/cosmos-sdk v0.0.0-20250324051359-31fe6618dc61 + github.com/ethereum/go-ethereum => github.com/b-harvest/go-ethereum v0.0.0-20250416151150-60099d9e9cdd ) replace ( @@ -264,8 +265,6 @@ replace ( cosmossdk.io/core => cosmossdk.io/core v0.11.0 // use cosmos fork of keyring github.com/99designs/keyring => github.com/cosmos/keyring v1.2.0 - // use Evmos geth fork - github.com/ethereum/go-ethereum => github.com/evmos/go-ethereum v1.10.26-evmos-rc4 // Security Advisory https://github.com/advisories/GHSA-h395-qcrw-5vmq github.com/gin-gonic/gin => github.com/gin-gonic/gin v1.9.1 // replace broken goleveldb diff --git a/go.sum b/go.sum index 54d696c8c..c3498a8f4 100644 --- a/go.sum +++ b/go.sum @@ -271,6 +271,8 @@ github.com/b-harvest/cosmos-sdk v0.0.0-20250324051359-31fe6618dc61 h1:vxFQZq9hto github.com/b-harvest/cosmos-sdk v0.0.0-20250324051359-31fe6618dc61/go.mod h1:dWbaOXueH9KzRlHBkluKFp9t0tZwahej3mT1wj8vQq4= github.com/b-harvest/cosmos-sdk/store v0.0.0-20250324051345-ddad0e6f75cb h1:E5IaRS16dPYT/rwdm/k+5Ytq8t3rTVOAR7yrFPpv7VU= github.com/b-harvest/cosmos-sdk/store v0.0.0-20250324051345-ddad0e6f75cb/go.mod h1:8DwVTz83/2PSI366FERGbWSH7hL6sB7HbYp8bqksNwM= +github.com/b-harvest/go-ethereum v0.0.0-20250416151150-60099d9e9cdd h1:NUeDjcZ6EhFuxBvn8BDcn2l7BB6e37dfaMievdszE/M= +github.com/b-harvest/go-ethereum v0.0.0-20250416151150-60099d9e9cdd/go.mod h1:/6CsT5Ceen2WPLI/oCA3xMcZ5sWMF/D46SjM/ayY0Oo= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= @@ -484,8 +486,6 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.m github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/evmos/go-ethereum v1.10.26-evmos-rc4 h1:vwDVMScuB2KSu8ze5oWUuxm6v3bMUp6dL3PWvJNJY+I= -github.com/evmos/go-ethereum v1.10.26-evmos-rc4/go.mod h1:/6CsT5Ceen2WPLI/oCA3xMcZ5sWMF/D46SjM/ayY0Oo= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs= diff --git a/rpc/backend/blocks.go b/rpc/backend/blocks.go index 5b958bccf..cb8b6e05c 100644 --- a/rpc/backend/blocks.go +++ b/rpc/backend/blocks.go @@ -376,7 +376,7 @@ func (b *Backend) ReceiptsRoot(blockRes *tmrpctypes.ResultBlockResults) (common. for _, attr := range event.Attributes { if attr.Key == evmtypes.AttributeKeyEthereumReceiptsRoot { - return common.Hash([]byte(attr.Value)), nil + return common.HexToHash(attr.Value), nil } } } diff --git a/x/vm/core/vm/eips.go b/x/vm/core/vm/eips.go index 9b399f816..6c2d57eda 100644 --- a/x/vm/core/vm/eips.go +++ b/x/vm/core/vm/eips.go @@ -22,6 +22,7 @@ import ( "strconv" "strings" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/params" "github.com/holiman/uint256" ) @@ -34,6 +35,12 @@ var activators = map[string]func(*JumpTable){ "ethereum_2200": enable2200, "ethereum_1884": enable1884, "ethereum_1344": enable1344, + + // v1.13.14 + "ethereum_1153": enable1153, + "ethereum_5656": enable5656, + "ethereum_4844": enable4844, + "ethereum_7516": enable7516, } // EnableEIP enables the given EIP on the config. @@ -215,3 +222,101 @@ func opPush0(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]by scope.Stack.Push(new(uint256.Int)) return nil, nil } + +// enable1153 applies EIP-1153 "Transient Storage" +// - Adds TLOAD that reads from transient storage +// - Adds TSTORE that writes to transient storage +func enable1153(jt *JumpTable) { + jt[TLOAD] = &operation{ + execute: opTload, + constantGas: params.WarmStorageReadCostEIP2929, + minStack: minStack(1, 1), + maxStack: maxStack(1, 1), + } + + jt[TSTORE] = &operation{ + execute: opTstore, + constantGas: params.WarmStorageReadCostEIP2929, + minStack: minStack(2, 0), + maxStack: maxStack(2, 0), + } +} + +// opTload implements TLOAD opcode +func opTload(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + loc := scope.Stack.Peek() + hash := common.Hash(loc.Bytes32()) + val := interpreter.evm.StateDB.GetTransientState(scope.Contract.Address(), hash) + loc.SetBytes(val.Bytes()) + return nil, nil +} + +// opTstore implements TSTORE opcode +func opTstore(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + if interpreter.readOnly { + return nil, ErrWriteProtection + } + loc := scope.Stack.Pop() + val := scope.Stack.Pop() + interpreter.evm.StateDB.SetTransientState(scope.Contract.Address(), loc.Bytes32(), val.Bytes32()) + return nil, nil +} + +// enable5656 enables EIP-5656 (MCOPY opcode) +// https://eips.ethereum.org/EIPS/eip-5656 +func enable5656(jt *JumpTable) { + jt[MCOPY] = &operation{ + execute: opMcopy, + constantGas: GasFastestStep, + dynamicGas: gasMcopy, + minStack: minStack(3, 0), + maxStack: maxStack(3, 0), + memorySize: memoryMcopy, + } +} + +// opMcopy implements the MCOPY opcode (https://eips.ethereum.org/EIPS/eip-5656) +func opMcopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + var ( + dst = scope.Stack.Pop() + src = scope.Stack.Pop() + length = scope.Stack.Pop() + ) + // These values are checked for overflow during memory expansion calculation + // (the memorySize function on the opcode). + scope.Memory.Copy(dst.Uint64(), src.Uint64(), length.Uint64()) + return nil, nil +} + +// enable4844 applies EIP-4844 (BLOBHASH opcode) +func enable4844(jt *JumpTable) { + jt[BLOBHASH] = &operation{ + execute: opBlobHash, + constantGas: GasFastestStep, + minStack: minStack(1, 1), + maxStack: maxStack(1, 1), + } +} + +// opBlobHash implements the BLOBHASH opcode +func opBlobHash(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + index := scope.Stack.Peek() + index.Clear() + return nil, nil +} + +// enable7516 applies EIP-7516 (BLOBBASEFEE opcode) +func enable7516(jt *JumpTable) { + jt[BLOBBASEFEE] = &operation{ + execute: opBlobBaseFee, + constantGas: GasQuickStep, + minStack: minStack(0, 1), + maxStack: maxStack(0, 1), + } +} + +// opBlobBaseFee implements BLOBBASEFEE opcode +func opBlobBaseFee(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + scope.Stack.Push(uint256.NewInt(0)) + return nil, nil +} diff --git a/x/vm/core/vm/gas_table.go b/x/vm/core/vm/gas_table.go index f37d914c2..371cda0bc 100644 --- a/x/vm/core/vm/gas_table.go +++ b/x/vm/core/vm/gas_table.go @@ -60,6 +60,7 @@ func memoryGasCost(mem *Memory, newMemSize uint64) (uint64, error) { // as argument: // CALLDATACOPY (stack position 2) // CODECOPY (stack position 2) +// MCOPY (stack position 2) // EXTCODECOPY (stack position 3) // RETURNDATACOPY (stack position 2) func memoryCopierGas(stackpos int) gasFunc { @@ -89,6 +90,7 @@ func memoryCopierGas(stackpos int) gasFunc { var ( gasCallDataCopy = memoryCopierGas(2) gasCodeCopy = memoryCopierGas(2) + gasMcopy = memoryCopierGas(2) gasExtCodeCopy = memoryCopierGas(3) gasReturnDataCopy = memoryCopierGas(2) ) diff --git a/x/vm/core/vm/interface.go b/x/vm/core/vm/interface.go index b67f71b75..a457166b5 100644 --- a/x/vm/core/vm/interface.go +++ b/x/vm/core/vm/interface.go @@ -47,6 +47,9 @@ type StateDB interface { GetState(common.Address, common.Hash) common.Hash SetState(common.Address, common.Hash, common.Hash) + GetTransientState(addr common.Address, key common.Hash) common.Hash + SetTransientState(addr common.Address, key, value common.Hash) + Suicide(common.Address) bool HasSuicided(common.Address) bool diff --git a/x/vm/core/vm/memory.go b/x/vm/core/vm/memory.go index ea757a17d..a940c8efa 100644 --- a/x/vm/core/vm/memory.go +++ b/x/vm/core/vm/memory.go @@ -109,3 +109,14 @@ func (m *Memory) Data() []byte { func (m *Memory) GasCost(newMemSize uint64) (uint64, error) { return memoryGasCost(m, newMemSize) } + +// Copy copies data from the src position slice into the dst position. +// The source and destination may overlap. +// OBS: This operation assumes that any necessary memory expansion has already been performed, +// and this method may panic otherwise. +func (m *Memory) Copy(dst, src, len uint64) { + if len == 0 { + return + } + copy(m.store[dst:], m.store[src:src+len]) +} diff --git a/x/vm/core/vm/memory_table.go b/x/vm/core/vm/memory_table.go index f83deb389..63ad96785 100644 --- a/x/vm/core/vm/memory_table.go +++ b/x/vm/core/vm/memory_table.go @@ -48,6 +48,14 @@ func memoryMStore(stack *Stack) (uint64, bool) { return calcMemSize64WithUint(stack.Back(0), 32) } +func memoryMcopy(stack *Stack) (uint64, bool) { + mStart := stack.Back(0) // stack[0]: dest + if stack.Back(1).Gt(mStart) { + mStart = stack.Back(1) // stack[1]: source + } + return calcMemSize64(mStart, stack.Back(2)) // stack[2]: length +} + func memoryCreate(stack *Stack) (uint64, bool) { return calcMemSize64(stack.Back(1), stack.Back(2)) } diff --git a/x/vm/core/vm/opcodes.go b/x/vm/core/vm/opcodes.go index 7e3a87024..2dbaaab82 100644 --- a/x/vm/core/vm/opcodes.go +++ b/x/vm/core/vm/opcodes.go @@ -103,6 +103,8 @@ const ( CHAINID OpCode = 0x46 SELFBALANCE OpCode = 0x47 BASEFEE OpCode = 0x48 + BLOBHASH OpCode = 0x49 + BLOBBASEFEE OpCode = 0x4a ) // 0x50 range - 'storage' and execution. @@ -119,6 +121,9 @@ const ( MSIZE OpCode = 0x59 GAS OpCode = 0x5a JUMPDEST OpCode = 0x5b + TLOAD OpCode = 0x5c + TSTORE OpCode = 0x5d + MCOPY OpCode = 0x5e PUSH0 OpCode = 0x5f ) @@ -285,6 +290,8 @@ var opCodeToString = map[OpCode]string{ CHAINID: "CHAINID", SELFBALANCE: "SELFBALANCE", BASEFEE: "BASEFEE", + BLOBHASH: "BLOBHASH", + BLOBBASEFEE: "BLOBBASEFEE", // 0x50 range - 'storage' and execution. POP: "POP", @@ -301,6 +308,9 @@ var opCodeToString = map[OpCode]string{ MSIZE: "MSIZE", GAS: "GAS", JUMPDEST: "JUMPDEST", + TLOAD: "TLOAD", + TSTORE: "TSTORE", + MCOPY: "MCOPY", PUSH0: "PUSH0", // 0x60 range - push. @@ -436,6 +446,8 @@ var stringToOp = map[string]OpCode{ "CALLDATACOPY": CALLDATACOPY, "CHAINID": CHAINID, "BASEFEE": BASEFEE, + "BLOBHASH": BLOBHASH, + "BLOBBASEFEE": BLOBBASEFEE, "DELEGATECALL": DELEGATECALL, "STATICCALL": STATICCALL, "CODESIZE": CODESIZE, @@ -465,6 +477,9 @@ var stringToOp = map[string]OpCode{ "MSIZE": MSIZE, "GAS": GAS, "JUMPDEST": JUMPDEST, + "TLOAD": TLOAD, + "TSTORE": TSTORE, + "MCOPY": MCOPY, "PUSH0": PUSH0, "PUSH1": PUSH1, "PUSH2": PUSH2, diff --git a/x/vm/keeper/integration_test.go b/x/vm/keeper/integration_test.go index 09633d056..d68168fef 100644 --- a/x/vm/keeper/integration_test.go +++ b/x/vm/keeper/integration_test.go @@ -2,6 +2,7 @@ package keeper_test import ( "math/big" + "testing" //nolint:revive // dot imports are fine for Ginkgo . "github.com/onsi/ginkgo/v2" @@ -12,7 +13,6 @@ import ( abcitypes "github.com/cometbft/cometbft/abci/types" sdktypes "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/evm/contracts" - "github.com/cosmos/evm/precompiles/staking" "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" @@ -30,6 +30,11 @@ type IntegrationTestSuite struct { keyring testkeyring.Keyring } +func TestIntegration(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Keeper Integration Suite") +} + // This test suite is meant to test the EVM module in the context of the ATOM. // It uses the integration test framework to spin up a local ATOM network and // perform transactions on it. @@ -288,183 +293,183 @@ var _ = Describe("Handling a MsgEthereumTx message", Label("EVM"), Ordered, func }) }) - DescribeTable("Performs transfer and contract call", func(getTestParams func() evmtypes.Params, transferParams, contractCallParams PermissionsTableTest) { - params := getTestParams() - err := integrationutils.UpdateEvmParams( - integrationutils.UpdateParamsInput{ - Tf: s.factory, - Network: s.network, - Pk: s.keyring.GetPrivKey(0), - Params: params, - }, - ) - Expect(err).To(BeNil()) - - err = s.network.NextBlock() - Expect(err).To(BeNil()) - - signer := s.keyring.GetKey(transferParams.SignerIndex) - receiver := s.keyring.GetKey(1) - txArgs := evmtypes.EvmTxArgs{ - To: &receiver.Addr, - Amount: big.NewInt(1000), - // Hard coded gas limit to avoid failure on gas estimation because - // of the param - GasLimit: 100000, - } - res, err := s.factory.ExecuteEthTx(signer.Priv, txArgs) - if transferParams.ExpFail { - Expect(err).NotTo(BeNil()) - Expect(err.Error()).To(ContainSubstring("does not have permission to perform a call")) - } else { - Expect(err).To(BeNil()) - Expect(res.IsOK()).To(Equal(true), "transaction should have succeeded", res.GetLog()) - } - - senderKey := s.keyring.GetKey(contractCallParams.SignerIndex) - contractAddress := common.HexToAddress(evmtypes.StakingPrecompileAddress) - validatorAddress := s.network.GetValidators()[1].OperatorAddress - contractABI, err := staking.LoadABI() - Expect(err).To(BeNil()) - - // If grpc query fails, that means there were no previous delegations - prevDelegation := big.NewInt(0) - prevDelegationRes, err := s.grpcHandler.GetDelegation(senderKey.AccAddr.String(), validatorAddress) - if err == nil { - prevDelegation = prevDelegationRes.DelegationResponse.Balance.Amount.BigInt() - } - - amountToDelegate := big.NewInt(200) - totalSupplyTxArgs := evmtypes.EvmTxArgs{ - To: &contractAddress, - } - - // Perform a delegate transaction to the staking precompile - delegateArgs := factory.CallArgs{ - ContractABI: contractABI, - MethodName: staking.DelegateMethod, - Args: []interface{}{senderKey.Addr, validatorAddress, amountToDelegate}, - } - delegateResponse, err := s.factory.ExecuteContractCall(senderKey.Priv, totalSupplyTxArgs, delegateArgs) - if contractCallParams.ExpFail { - Expect(err).NotTo(BeNil()) - Expect(err.Error()).To(ContainSubstring("does not have permission to perform a call")) - } else { - Expect(err).To(BeNil()) - Expect(delegateResponse.IsOK()).To(Equal(true), "transaction should have succeeded", delegateResponse.GetLog()) - - err = s.network.NextBlock() - Expect(err).To(BeNil()) - - // Perform query to check the delegation was successful - queryDelegationArgs := factory.CallArgs{ - ContractABI: contractABI, - MethodName: staking.DelegationMethod, - Args: []interface{}{senderKey.Addr, validatorAddress}, - } - queryDelegationResponse, err := s.factory.ExecuteContractCall(senderKey.Priv, totalSupplyTxArgs, queryDelegationArgs) - Expect(err).To(BeNil()) - Expect(queryDelegationResponse.IsOK()).To(Equal(true), "transaction should have succeeded", queryDelegationResponse.GetLog()) - - // Make sure the delegation amount is correct - var delegationOutput staking.DelegationOutput - err = integrationutils.DecodeContractCallResponse(&delegationOutput, queryDelegationArgs, queryDelegationResponse) - Expect(err).To(BeNil()) - - expectedDelegationAmt := amountToDelegate.Add(amountToDelegate, prevDelegation) - Expect(delegationOutput.Balance.Amount.String()).To(Equal(expectedDelegationAmt.String())) - } - }, - // Entry("transfer and call fail with CALL permission policy set to restricted", func() evmtypes.Params { - // // Set params to default values - // defaultParams := evmtypes.DefaultParams() - // defaultParams.AccessControl.Call = evmtypes.AccessControlType{ - // AccessType: evmtypes.AccessTypeRestricted, - // } - // return defaultParams - // }, - // OpcodeTestTable{ExpFail: true, SignerIndex: 0}, - // OpcodeTestTable{ExpFail: true, SignerIndex: 0}, - // ), - Entry("transfer and call succeed with CALL permission policy set to default and CREATE permission policy set to restricted", func() evmtypes.Params { - blockedSignerIndex := 1 - // Set params to default values - defaultParams := evmtypes.DefaultParams() - defaultParams.AccessControl.Create = evmtypes.AccessControlType{ - AccessType: evmtypes.AccessTypeRestricted, - AccessControlList: []string{s.keyring.GetAddr(blockedSignerIndex).String()}, - } - return defaultParams - }, - PermissionsTableTest{ExpFail: false, SignerIndex: 0}, - PermissionsTableTest{ExpFail: false, SignerIndex: 0}, - ), - Entry("transfer and call are successful with CALL permission policy set to permissionless and address not blocked", func() evmtypes.Params { - blockedSignerIndex := 1 - // Set params to default values - defaultParams := evmtypes.DefaultParams() - defaultParams.AccessControl.Call = evmtypes.AccessControlType{ - AccessType: evmtypes.AccessTypePermissionless, - AccessControlList: []string{s.keyring.GetAddr(blockedSignerIndex).String()}, - } - return defaultParams - }, - PermissionsTableTest{ExpFail: false, SignerIndex: 0}, - PermissionsTableTest{ExpFail: false, SignerIndex: 0}, - ), - Entry("transfer fails with signer blocked and call succeeds with signer NOT blocked permission policy set to permissionless", func() evmtypes.Params { - blockedSignerIndex := 1 - // Set params to default values - defaultParams := evmtypes.DefaultParams() - defaultParams.AccessControl.Call = evmtypes.AccessControlType{ - AccessType: evmtypes.AccessTypePermissionless, - AccessControlList: []string{s.keyring.GetAddr(blockedSignerIndex).String()}, - } - return defaultParams - }, - PermissionsTableTest{ExpFail: true, SignerIndex: 1}, - PermissionsTableTest{ExpFail: false, SignerIndex: 0}, - ), - Entry("transfer succeeds with signer NOT blocked and call fails with signer blocked permission policy set to permissionless", func() evmtypes.Params { - blockedSignerIndex := 1 - // Set params to default values - defaultParams := evmtypes.DefaultParams() - defaultParams.AccessControl.Call = evmtypes.AccessControlType{ - AccessType: evmtypes.AccessTypePermissionless, - AccessControlList: []string{s.keyring.GetAddr(blockedSignerIndex).String()}, - } - return defaultParams - }, - PermissionsTableTest{ExpFail: false, SignerIndex: 0}, - PermissionsTableTest{ExpFail: true, SignerIndex: 1}, - ), - Entry("transfer and call succeeds with CALL permission policy set to permissioned and signer whitelisted on both", func() evmtypes.Params { - blockedSignerIndex := 1 - // Set params to default values - defaultParams := evmtypes.DefaultParams() - defaultParams.AccessControl.Call = evmtypes.AccessControlType{ - AccessType: evmtypes.AccessTypePermissioned, - AccessControlList: []string{s.keyring.GetAddr(blockedSignerIndex).String()}, - } - return defaultParams - }, - PermissionsTableTest{ExpFail: false, SignerIndex: 1}, - PermissionsTableTest{ExpFail: false, SignerIndex: 1}, - ), - Entry("transfer and call fails with CALL permission policy set to permissioned and signer not whitelisted on both", func() evmtypes.Params { - blockedSignerIndex := 1 - // Set params to default values - defaultParams := evmtypes.DefaultParams() - defaultParams.AccessControl.Call = evmtypes.AccessControlType{ - AccessType: evmtypes.AccessTypePermissioned, - AccessControlList: []string{s.keyring.GetAddr(blockedSignerIndex).String()}, - } - return defaultParams - }, - PermissionsTableTest{ExpFail: true, SignerIndex: 0}, - PermissionsTableTest{ExpFail: true, SignerIndex: 0}, - ), - ) + // DescribeTable("Performs transfer and contract call", func(getTestParams func() evmtypes.Params, transferParams, contractCallParams PermissionsTableTest) { + // params := getTestParams() + // err := integrationutils.UpdateEvmParams( + // integrationutils.UpdateParamsInput{ + // Tf: s.factory, + // Network: s.network, + // Pk: s.keyring.GetPrivKey(0), + // Params: params, + // }, + // ) + // Expect(err).To(BeNil()) + + // err = s.network.NextBlock() + // Expect(err).To(BeNil()) + + // signer := s.keyring.GetKey(transferParams.SignerIndex) + // receiver := s.keyring.GetKey(1) + // txArgs := evmtypes.EvmTxArgs{ + // To: &receiver.Addr, + // Amount: big.NewInt(1000), + // // Hard coded gas limit to avoid failure on gas estimation because + // // of the param + // GasLimit: 100000, + // } + // res, err := s.factory.ExecuteEthTx(signer.Priv, txArgs) + // if transferParams.ExpFail { + // Expect(err).NotTo(BeNil()) + // Expect(err.Error()).To(ContainSubstring("does not have permission to perform a call")) + // } else { + // Expect(err).To(BeNil()) + // Expect(res.IsOK()).To(Equal(true), "transaction should have succeeded", res.GetLog()) + // } + + // senderKey := s.keyring.GetKey(contractCallParams.SignerIndex) + // contractAddress := common.HexToAddress(evmtypes.StakingPrecompileAddress) + // validatorAddress := s.network.GetValidators()[1].OperatorAddress + // contractABI, err := staking.LoadABI() + // Expect(err).To(BeNil()) + + // // If grpc query fails, that means there were no previous delegations + // prevDelegation := big.NewInt(0) + // prevDelegationRes, err := s.grpcHandler.GetDelegation(senderKey.AccAddr.String(), validatorAddress) + // if err == nil { + // prevDelegation = prevDelegationRes.DelegationResponse.Balance.Amount.BigInt() + // } + + // amountToDelegate := big.NewInt(200) + // totalSupplyTxArgs := evmtypes.EvmTxArgs{ + // To: &contractAddress, + // } + + // // Perform a delegate transaction to the staking precompile + // delegateArgs := factory.CallArgs{ + // ContractABI: contractABI, + // MethodName: staking.DelegateMethod, + // Args: []interface{}{senderKey.Addr, validatorAddress, amountToDelegate}, + // } + // delegateResponse, err := s.factory.ExecuteContractCall(senderKey.Priv, totalSupplyTxArgs, delegateArgs) + // if contractCallParams.ExpFail { + // Expect(err).NotTo(BeNil()) + // Expect(err.Error()).To(ContainSubstring("does not have permission to perform a call")) + // } else { + // Expect(err).To(BeNil()) + // Expect(delegateResponse.IsOK()).To(Equal(true), "transaction should have succeeded", delegateResponse.GetLog()) + + // err = s.network.NextBlock() + // Expect(err).To(BeNil()) + + // // Perform query to check the delegation was successful + // queryDelegationArgs := factory.CallArgs{ + // ContractABI: contractABI, + // MethodName: staking.DelegationMethod, + // Args: []interface{}{senderKey.Addr, validatorAddress}, + // } + // queryDelegationResponse, err := s.factory.ExecuteContractCall(senderKey.Priv, totalSupplyTxArgs, queryDelegationArgs) + // Expect(err).To(BeNil()) + // Expect(queryDelegationResponse.IsOK()).To(Equal(true), "transaction should have succeeded", queryDelegationResponse.GetLog()) + + // // Make sure the delegation amount is correct + // var delegationOutput staking.DelegationOutput + // err = integrationutils.DecodeContractCallResponse(&delegationOutput, queryDelegationArgs, queryDelegationResponse) + // Expect(err).To(BeNil()) + + // expectedDelegationAmt := amountToDelegate.Add(amountToDelegate, prevDelegation) + // Expect(delegationOutput.Balance.Amount.String()).To(Equal(expectedDelegationAmt.String())) + // } + // }, + // Entry("transfer and call fail with CALL permission policy set to restricted", func() evmtypes.Params { + // // Set params to default values + // defaultParams := evmtypes.DefaultParams() + // defaultParams.AccessControl.Call = evmtypes.AccessControlType{ + // AccessType: evmtypes.AccessTypeRestricted, + // } + // return defaultParams + // }, + // OpcodeTestTable{ExpFail: true, SignerIndex: 0}, + // OpcodeTestTable{ExpFail: true, SignerIndex: 0}, + // ), + // Entry("transfer and call succeed with CALL permission policy set to default and CREATE permission policy set to restricted", func() evmtypes.Params { + // blockedSignerIndex := 1 + // // Set params to default values + // defaultParams := evmtypes.DefaultParams() + // defaultParams.AccessControl.Create = evmtypes.AccessControlType{ + // AccessType: evmtypes.AccessTypeRestricted, + // AccessControlList: []string{s.keyring.GetAddr(blockedSignerIndex).String()}, + // } + // return defaultParams + // }, + // PermissionsTableTest{ExpFail: false, SignerIndex: 0}, + // PermissionsTableTest{ExpFail: false, SignerIndex: 0}, + // ), + // Entry("transfer and call are successful with CALL permission policy set to permissionless and address not blocked", func() evmtypes.Params { + // blockedSignerIndex := 1 + // // Set params to default values + // defaultParams := evmtypes.DefaultParams() + // defaultParams.AccessControl.Call = evmtypes.AccessControlType{ + // AccessType: evmtypes.AccessTypePermissionless, + // AccessControlList: []string{s.keyring.GetAddr(blockedSignerIndex).String()}, + // } + // return defaultParams + // }, + // PermissionsTableTest{ExpFail: false, SignerIndex: 0}, + // PermissionsTableTest{ExpFail: false, SignerIndex: 0}, + // ), + // Entry("transfer fails with signer blocked and call succeeds with signer NOT blocked permission policy set to permissionless", func() evmtypes.Params { + // blockedSignerIndex := 1 + // // Set params to default values + // defaultParams := evmtypes.DefaultParams() + // defaultParams.AccessControl.Call = evmtypes.AccessControlType{ + // AccessType: evmtypes.AccessTypePermissionless, + // AccessControlList: []string{s.keyring.GetAddr(blockedSignerIndex).String()}, + // } + // return defaultParams + // }, + // PermissionsTableTest{ExpFail: true, SignerIndex: 1}, + // PermissionsTableTest{ExpFail: false, SignerIndex: 0}, + // ), + // Entry("transfer succeeds with signer NOT blocked and call fails with signer blocked permission policy set to permissionless", func() evmtypes.Params { + // blockedSignerIndex := 1 + // // Set params to default values + // defaultParams := evmtypes.DefaultParams() + // defaultParams.AccessControl.Call = evmtypes.AccessControlType{ + // AccessType: evmtypes.AccessTypePermissionless, + // AccessControlList: []string{s.keyring.GetAddr(blockedSignerIndex).String()}, + // } + // return defaultParams + // }, + // PermissionsTableTest{ExpFail: false, SignerIndex: 0}, + // PermissionsTableTest{ExpFail: true, SignerIndex: 1}, + // ), + // Entry("transfer and call succeeds with CALL permission policy set to permissioned and signer whitelisted on both", func() evmtypes.Params { + // blockedSignerIndex := 1 + // // Set params to default values + // defaultParams := evmtypes.DefaultParams() + // defaultParams.AccessControl.Call = evmtypes.AccessControlType{ + // AccessType: evmtypes.AccessTypePermissioned, + // AccessControlList: []string{s.keyring.GetAddr(blockedSignerIndex).String()}, + // } + // return defaultParams + // }, + // PermissionsTableTest{ExpFail: false, SignerIndex: 1}, + // PermissionsTableTest{ExpFail: false, SignerIndex: 1}, + // ), + // Entry("transfer and call fails with CALL permission policy set to permissioned and signer not whitelisted on both", func() evmtypes.Params { + // blockedSignerIndex := 1 + // // Set params to default values + // defaultParams := evmtypes.DefaultParams() + // defaultParams.AccessControl.Call = evmtypes.AccessControlType{ + // AccessType: evmtypes.AccessTypePermissioned, + // AccessControlList: []string{s.keyring.GetAddr(blockedSignerIndex).String()}, + // } + // return defaultParams + // }, + // PermissionsTableTest{ExpFail: true, SignerIndex: 0}, + // PermissionsTableTest{ExpFail: true, SignerIndex: 0}, + // ), + // ) DescribeTable("Performs contract deployment and contract call with AccessControl", func(getTestParams func() evmtypes.Params, createParams, callParams PermissionsTableTest) { params := getTestParams() @@ -667,3 +672,221 @@ func checkMintTopics(res abcitypes.ExecTxResult) error { } return integrationutils.CheckTxTopics(res, expectedTopics) } + +var _ = Describe("Testing Cancun Opcodes", Label("EVM"), Ordered, func() { + var ( + s *IntegrationTestSuite + contractAddr common.Address + ) + + BeforeAll(func() { + keyring := testkeyring.New(4) + integrationNetwork := network.New( + network.WithPreFundedAccounts(keyring.GetAllAccAddrs()...), + ) + grpcHandler := grpc.NewIntegrationHandler(integrationNetwork) + txFactory := factory.New(integrationNetwork, grpcHandler) + s = &IntegrationTestSuite{ + network: integrationNetwork, + factory: txFactory, + grpcHandler: grpcHandler, + keyring: keyring, + } + + // Set params to default values + defaultParams := evmtypes.DefaultParams() + err := integrationutils.UpdateEvmParams( + integrationutils.UpdateParamsInput{ + Tf: s.factory, + Network: s.network, + Pk: s.keyring.GetPrivKey(0), + Params: defaultParams, + }, + ) + Expect(err).To(BeNil()) + + // Deploy contract once for all tests + senderPriv := s.keyring.GetPrivKey(0) + compiledContract := contracts.CancunOpcodesContract + contractAddr, err = s.factory.DeployContract( + senderPriv, + evmtypes.EvmTxArgs{}, + factory.ContractDeploymentData{ + Contract: compiledContract, + ConstructorArgs: []interface{}{}, + }, + ) + Expect(err).To(BeNil()) + Expect(contractAddr).ToNot(Equal(common.Address{})) + + err = s.network.NextBlock() + Expect(err).To(BeNil()) + }) + + AfterEach(func() { + err := s.network.NextBlock() + Expect(err).To(BeNil()) + }) + + DescribeTable("Testing TSTORE/TLOAD opcodes", func(getTxArgs func() evmtypes.EvmTxArgs) { + senderPriv := s.keyring.GetPrivKey(0) + testValue := big.NewInt(42) + + txArgs := getTxArgs() + txArgs.To = &contractAddr + + tstoreArgs := factory.CallArgs{ + ContractABI: contracts.CancunOpcodesContract.ABI, + MethodName: "testTstoreTload", + Args: []interface{}{testValue}, + } + + res, err := s.factory.ExecuteContractCall(senderPriv, txArgs, tstoreArgs) + Expect(err).To(BeNil()) + Expect(res.IsOK()).To(Equal(true), "transaction should have succeeded", res.GetLog()) + + var result *big.Int + err = integrationutils.DecodeContractCallResponse(&result, tstoreArgs, res) + Expect(err).To(BeNil()) + Expect(result).To(Equal(testValue)) + }, + Entry("as a DynamicFeeTx", func() evmtypes.EvmTxArgs { return evmtypes.EvmTxArgs{} }), + Entry("as an AccessListTx", func() evmtypes.EvmTxArgs { + return evmtypes.EvmTxArgs{ + Accesses: ðtypes.AccessList{{ + Address: s.keyring.GetAddr(1), + StorageKeys: []common.Hash{{0}}, + }}, + } + }), + Entry("as a LegacyTx", func() evmtypes.EvmTxArgs { + return evmtypes.EvmTxArgs{ + GasPrice: big.NewInt(1e9), + } + }), + ) + + DescribeTable("Testing MCOPY opcode", func(getTxArgs func() evmtypes.EvmTxArgs) { + senderPriv := s.keyring.GetPrivKey(0) + inputValue := uint8(0xAB) // test 1-byte value + + txArgs := getTxArgs() + txArgs.To = &contractAddr + + mcopyArgs := factory.CallArgs{ + ContractABI: contracts.CancunOpcodesContract.ABI, + MethodName: "testSimpleMCopy", + Args: []interface{}{inputValue}, + } + + res, err := s.factory.ExecuteContractCall(senderPriv, txArgs, mcopyArgs) + Expect(err).To(BeNil()) + Expect(res.IsOK()).To(BeTrue(), res.GetLog()) + + var result [32]byte + err = integrationutils.DecodeContractCallResponse(&result, mcopyArgs, res) + Expect(err).To(BeNil()) + + expected := [32]byte{} + expected[0] = inputValue // first byte only + Expect(result).To(Equal(expected)) + }, + Entry("as a DynamicFeeTx", func() evmtypes.EvmTxArgs { return evmtypes.EvmTxArgs{} }), + Entry("as an AccessListTx", func() evmtypes.EvmTxArgs { + return evmtypes.EvmTxArgs{ + Accesses: ðtypes.AccessList{{ + Address: s.keyring.GetAddr(1), + StorageKeys: []common.Hash{{0}}, + }}, + } + }), + Entry("as a LegacyTx", func() evmtypes.EvmTxArgs { + return evmtypes.EvmTxArgs{ + GasPrice: big.NewInt(1e9), + } + }), + ) + + DescribeTable("Testing BLOBBASEFEE opcode", func(getTxArgs func() evmtypes.EvmTxArgs) { + senderPriv := s.keyring.GetPrivKey(0) + + txArgs := getTxArgs() + txArgs.To = &contractAddr + + blobbasefeeArgs := factory.CallArgs{ + ContractABI: contracts.CancunOpcodesContract.ABI, + MethodName: "testBlobBaseFee", + Args: []interface{}{}, + } + + res, err := s.factory.ExecuteContractCall(senderPriv, txArgs, blobbasefeeArgs) + Expect(err).To(BeNil()) + Expect(res.IsOK()).To(Equal(true), "transaction should have succeeded", res.GetLog()) + + var result *big.Int + err = integrationutils.DecodeContractCallResponse(&result, blobbasefeeArgs, res) + Expect(err).To(BeNil()) + Expect(result.Cmp(big.NewInt(0))).To(Equal(0), "Expected blobbasefee to be 0") + }, + Entry("as a DynamicFeeTx", func() evmtypes.EvmTxArgs { return evmtypes.EvmTxArgs{} }), + Entry("as an AccessListTx", func() evmtypes.EvmTxArgs { + return evmtypes.EvmTxArgs{ + Accesses: ðtypes.AccessList{{ + Address: s.keyring.GetAddr(1), + StorageKeys: []common.Hash{{0}}, + }}, + } + }), + Entry("as a LegacyTx", func() evmtypes.EvmTxArgs { + return evmtypes.EvmTxArgs{ + GasPrice: big.NewInt(1e9), + } + }), + ) + + DescribeTable("Testing BLOBHASH opcode", func(getTxArgs func() evmtypes.EvmTxArgs) { + senderPriv := s.keyring.GetPrivKey(0) + testIndex := big.NewInt(0) + + txArgs := getTxArgs() + txArgs.To = &contractAddr + + blobhashArgs := factory.CallArgs{ + ContractABI: contracts.CancunOpcodesContract.ABI, + MethodName: "testBlobHash", + Args: []interface{}{testIndex}, + } + + res, err := s.factory.ExecuteContractCall(senderPriv, txArgs, blobhashArgs) + Expect(err).To(BeNil()) + Expect(res.IsOK()).To(Equal(true), "transaction should have succeeded", res.GetLog()) + + var result [32]byte + err = integrationutils.DecodeContractCallResponse(&result, blobhashArgs, res) + Expect(err).To(BeNil()) + + isZero := true + for _, b := range result { + if b != 0 { + isZero = false + break + } + } + Expect(isZero).To(BeTrue(), "Blob hash should be all zeros") + }, + Entry("as a DynamicFeeTx", func() evmtypes.EvmTxArgs { return evmtypes.EvmTxArgs{} }), + Entry("as an AccessListTx", func() evmtypes.EvmTxArgs { + return evmtypes.EvmTxArgs{ + Accesses: ðtypes.AccessList{{ + Address: s.keyring.GetAddr(1), + StorageKeys: []common.Hash{{0}}, + }}, + } + }), + Entry("as a LegacyTx", func() evmtypes.EvmTxArgs { + return evmtypes.EvmTxArgs{ + GasPrice: big.NewInt(1e9), + } + }), + ) +}) diff --git a/x/vm/keeper/keeper.go b/x/vm/keeper/keeper.go index acd4e0918..6b9bbeecd 100644 --- a/x/vm/keeper/keeper.go +++ b/x/vm/keeper/keeper.go @@ -162,7 +162,7 @@ func (k Keeper) EmitReceiptsRootEvent(ctx sdk.Context, receiptsRoot common.Hash) ctx.EventManager().EmitEvent( sdk.NewEvent( types.EventTypeReceiptsRoot, - sdk.NewAttribute(types.AttributeKeyEthereumReceiptsRoot, string(receiptsRoot.Bytes())), + sdk.NewAttribute(types.AttributeKeyEthereumReceiptsRoot, receiptsRoot.Hex()), ), ) } diff --git a/x/vm/statedb/journal.go b/x/vm/statedb/journal.go index d1919cb2d..5dffe39c3 100644 --- a/x/vm/statedb/journal.go +++ b/x/vm/statedb/journal.go @@ -143,6 +143,13 @@ type ( multiStore storetypes.CacheMultiStore events sdk.Events } + + // v1.13.14 + + transientStorageChange struct { + account *common.Address + key, prevalue common.Hash + } ) var ( @@ -158,6 +165,8 @@ var ( _ JournalEntry = accessListAddAccountChange{} _ JournalEntry = accessListAddSlotChange{} _ JournalEntry = precompileCallChange{} + + _ JournalEntry = transientStorageChange{} ) func (pc precompileCallChange) Revert(s *StateDB) { @@ -276,3 +285,11 @@ func (ch accessListAddSlotChange) Revert(s *StateDB) { func (ch accessListAddSlotChange) Dirtied() *common.Address { return nil } + +func (ch transientStorageChange) Revert(s *StateDB) { + s.setTransientState(*ch.account, ch.key, ch.prevalue) +} + +func (ch transientStorageChange) Dirtied() *common.Address { + return nil +} diff --git a/x/vm/statedb/statedb.go b/x/vm/statedb/statedb.go index 03b1a5457..77e172466 100644 --- a/x/vm/statedb/statedb.go +++ b/x/vm/statedb/statedb.go @@ -59,6 +59,9 @@ type StateDB struct { // Per-transaction access list accessList *accessList + // Transient storage + transientStorage transientStorage + // The count of calls to precompiles precompileCallsCounter uint8 } @@ -440,6 +443,8 @@ func (s *StateDB) PrepareAccessList(sender common.Address, dst *common.Address, s.AddSlotToAccessList(el.Address, key) } } + // Reset transient storage at the beginning of transaction execution + s.transientStorage = newTransientStorage() } // AddAddressToAccessList adds the given address to the access list @@ -552,3 +557,32 @@ func (s *StateDB) commitWithCtx(ctx sdk.Context) error { } return nil } + +// SetTransientState sets transient storage for a given account. It +// adds the change to the journal so that it can be rolled back +// to its previous value if there is a revert. +func (s *StateDB) SetTransientState(addr common.Address, key, value common.Hash) { + prev := s.GetTransientState(addr, key) + if prev == value { + return + } + + s.journal.append(transientStorageChange{ + account: &addr, + key: key, + prevalue: prev, + }) + + s.setTransientState(addr, key, value) +} + +// setTransientState is a lower level setter for transient storage. It +// is called during a revert to prevent modifications to the journal. +func (s *StateDB) setTransientState(addr common.Address, key, value common.Hash) { + s.transientStorage.Set(addr, key, value) +} + +// GetTransientState gets transient storage for a given account. +func (s *StateDB) GetTransientState(addr common.Address, key common.Hash) common.Hash { + return s.transientStorage.Get(addr, key) +} diff --git a/x/vm/statedb/transient_storage.go b/x/vm/statedb/transient_storage.go new file mode 100644 index 000000000..0ce98cbea --- /dev/null +++ b/x/vm/statedb/transient_storage.go @@ -0,0 +1,42 @@ +package statedb + +import ( + "github.com/ethereum/go-ethereum/common" +) + +// transientStorage is a representation of EIP-1153 "Transient Storage". +type transientStorage map[common.Address]Storage + +// newTransientStorage creates a new instance of a transientStorage. +func newTransientStorage() transientStorage { + return make(transientStorage) +} + +// Set sets the transient-storage `value` for `key` at the given `addr`. +func (t transientStorage) Set(addr common.Address, key, value common.Hash) { + if _, ok := t[addr]; !ok { + t[addr] = make(Storage) + } + t[addr][key] = value +} + +// Get gets the transient storage for `key` at the given `addr`. +func (t transientStorage) Get(addr common.Address, key common.Hash) common.Hash { + val, ok := t[addr] + if !ok { + return common.Hash{} + } + return val[key] +} + +// Copy does a deep copy of the transientStorage +func (t transientStorage) Copy() transientStorage { + storage := make(transientStorage) + for key, value := range t { + storage[key] = make(Storage) + for k, v := range value { + storage[key][k] = v + } + } + return storage +} diff --git a/x/vm/types/params.go b/x/vm/types/params.go index 2f692ff3f..9a9ef5006 100644 --- a/x/vm/types/params.go +++ b/x/vm/types/params.go @@ -24,6 +24,12 @@ var ( // On v15, EIP 3855 was enabled DefaultExtraEIPs = []string{ "ethereum_3855", // NOTE: we suggest to enable EIP-3855 on all chains to support new Solidity versions >=v0.8.20 + + // v1.13.14 + "ethereum_1153", // Transient Storage + "ethereum_5656", // MCOPY opcode + "ethereum_4844", // BLOBHASH opcode + "ethereum_7516", // BLOBBASEFEE opcode } // DefaultEVMChannels defines a list of IBC channels that connect to EVM chains like injective or cronos. DefaultEVMChannels []string