diff --git a/action/protocol/execution/evm/evm.go b/action/protocol/execution/evm/evm.go index 275d9202d4..929ac09092 100644 --- a/action/protocol/execution/evm/evm.go +++ b/action/protocol/execution/evm/evm.go @@ -450,6 +450,12 @@ func getChainConfig(g genesis.Blockchain, height uint64, id uint32, getBlockTime } sumatraTimestamp := (uint64)(sumatraTime.Unix()) chainConfig.ShanghaiTime = &sumatraTimestamp + upernavikTime, err := getBlockTime(g.UpernavikBlockHeight) + if err != nil { + return nil, err + } + upernavikTimestamp := (uint64)(upernavikTime.Unix()) + chainConfig.CancunTime = &upernavikTimestamp return &chainConfig, nil } diff --git a/action/protocol/execution/evm/evm_test.go b/action/protocol/execution/evm/evm_test.go index 2d5df1c204..f995bfbab4 100644 --- a/action/protocol/execution/evm/evm_test.go +++ b/action/protocol/execution/evm/evm_test.go @@ -252,11 +252,20 @@ func TestConstantinople(t *testing.T) { "io1pcg2ja9krrhujpazswgz77ss46xgt88afqlk6y", 29275560, }, - // after Tsunami + // after Tsunami - Upernavik { action.EmptyAddress, 29275561, }, + { + "io1pcg2ja9krrhujpazswgz77ss46xgt88afqlk6y", + 39275560, + }, + // after Upernavik + { + action.EmptyAddress, + 39275561, + }, { "io1pcg2ja9krrhujpazswgz77ss46xgt88afqlk6y", 1261440000, // = 200*365*24*3600/5, around 200 years later @@ -355,8 +364,10 @@ func TestConstantinople(t *testing.T) { require.Equal(isSumatra, chainRules.IsMerge) require.Equal(isSumatra, chainRules.IsShanghai) - // Cancun, Prague not yet enabled - require.False(evmChainConfig.IsCancun(big.NewInt(int64(e.height)), evm.Context.Time)) + // Upernavik = enable Cancun + isUpernavik := g.IsUpernavik(e.height) + require.Equal(isUpernavik, chainRules.IsCancun) + //Prague not yet enabled require.False(evmChainConfig.IsPrague(big.NewInt(int64(e.height)), evm.Context.Time)) // test basefee diff --git a/action/protocol/execution/evm/evmstatedbadapter.go b/action/protocol/execution/evm/evmstatedbadapter.go index 2a86218cb6..0ce4712cee 100644 --- a/action/protocol/execution/evm/evmstatedbadapter.go +++ b/action/protocol/execution/evm/evmstatedbadapter.go @@ -43,24 +43,27 @@ type ( // StateDBAdapter represents the state db adapter for evm to access iotx blockchain StateDBAdapter struct { - sm protocol.StateManager - logs []*action.Log - transactionLogs []*action.TransactionLog - err error - blockHeight uint64 - executionHash hash.Hash256 - lastAddBalanceAddr string - lastAddBalanceAmount *big.Int - refund uint64 - refundSnapshot map[int]uint64 - cachedContract contractMap - contractSnapshot map[int]contractMap // snapshots of contracts - selfDestructed deleteAccount // account/contract calling SelfDestruct - selfDestructedSnapshot map[int]deleteAccount // snapshots of SelfDestruct accounts - preimages preimageMap - preimageSnapshot map[int]preimageMap - accessList *accessList // per-transaction access list - accessListSnapshot map[int]*accessList + sm protocol.StateManager + logs []*action.Log + transactionLogs []*action.TransactionLog + err error + blockHeight uint64 + executionHash hash.Hash256 + lastAddBalanceAddr string + lastAddBalanceAmount *big.Int + refund uint64 + refundSnapshot map[int]uint64 + cachedContract contractMap + contractSnapshot map[int]contractMap // snapshots of contracts + selfDestructed deleteAccount // account/contract calling SelfDestruct + selfDestructedSnapshot map[int]deleteAccount // snapshots of SelfDestruct accounts + preimages preimageMap + preimageSnapshot map[int]preimageMap + accessList *accessList // per-transaction access list + accessListSnapshot map[int]*accessList + // Transient storage + transientStorage transientStorage + transientStorageSnapshot map[int]transientStorage logsSnapshot map[int]int // logs is an array, save len(logs) at time of snapshot suffices txLogsSnapshot map[int]int notFixTopicCopyBug bool @@ -179,23 +182,25 @@ func NewStateDBAdapter( opts ...StateDBAdapterOption, ) (*StateDBAdapter, error) { s := &StateDBAdapter{ - sm: sm, - logs: []*action.Log{}, - err: nil, - blockHeight: blockHeight, - executionHash: executionHash, - lastAddBalanceAmount: new(big.Int), - refundSnapshot: make(map[int]uint64), - cachedContract: make(contractMap), - contractSnapshot: make(map[int]contractMap), - selfDestructed: make(deleteAccount), - selfDestructedSnapshot: make(map[int]deleteAccount), - preimages: make(preimageMap), - preimageSnapshot: make(map[int]preimageMap), - accessList: newAccessList(), - accessListSnapshot: make(map[int]*accessList), - logsSnapshot: make(map[int]int), - txLogsSnapshot: make(map[int]int), + sm: sm, + logs: []*action.Log{}, + err: nil, + blockHeight: blockHeight, + executionHash: executionHash, + lastAddBalanceAmount: new(big.Int), + refundSnapshot: make(map[int]uint64), + cachedContract: make(contractMap), + contractSnapshot: make(map[int]contractMap), + selfDestructed: make(deleteAccount), + selfDestructedSnapshot: make(map[int]deleteAccount), + preimages: make(preimageMap), + preimageSnapshot: make(map[int]preimageMap), + accessList: newAccessList(), + accessListSnapshot: make(map[int]*accessList), + transientStorage: newTransientStorage(), + transientStorageSnapshot: make(map[int]transientStorage), + logsSnapshot: make(map[int]int), + txLogsSnapshot: make(map[int]int), } for _, opt := range opts { if err := opt(s); err != nil { @@ -483,13 +488,16 @@ func (stateDB *StateDBAdapter) HasSelfDestructed(evmAddr common.Address) bool { // SetTransientState sets transient storage for a given account func (stateDB *StateDBAdapter) SetTransientState(addr common.Address, key, value common.Hash) { - log.S().Panic("SetTransientState not implemented") + prev := stateDB.transientStorage.Get(addr, key) + if prev == value { + return + } + stateDB.transientStorage.Set(addr, key, value) } // GetTransientState gets transient storage for a given account. func (stateDB *StateDBAdapter) GetTransientState(addr common.Address, key common.Hash) common.Hash { - log.S().Panic("GetTransientState not implemented") - return common.Hash{} + return stateDB.transientStorage.Get(addr, key) } // Selfdestruct6780 implements EIP-6780 @@ -631,6 +639,18 @@ func (stateDB *StateDBAdapter) RevertToSnapshot(snapshot int) { } } } + //restore transientStorage + stateDB.transientStorage = stateDB.transientStorageSnapshot[snapshot] + { + delete(stateDB.transientStorageSnapshot, snapshot) + for i := snapshot + 1; ; i++ { + if _, ok := stateDB.transientStorageSnapshot[i]; ok { + delete(stateDB.transientStorageSnapshot, i) + } else { + break + } + } + } // restore logs and txLogs if stateDB.revertLog { stateDB.logs = stateDB.logs[:stateDB.logsSnapshot[snapshot]] @@ -758,6 +778,8 @@ func (stateDB *StateDBAdapter) Snapshot() int { stateDB.preimageSnapshot[sn] = p // save a copy of access list stateDB.accessListSnapshot[sn] = stateDB.accessList.Copy() + // save a copy of transient storage + stateDB.transientStorageSnapshot[sn] = stateDB.transientStorage.Copy() return sn } @@ -1084,6 +1106,8 @@ func (stateDB *StateDBAdapter) clear() { stateDB.preimageSnapshot = make(map[int]preimageMap) stateDB.accessList = newAccessList() stateDB.accessListSnapshot = make(map[int]*accessList) + stateDB.transientStorage = newTransientStorage() + stateDB.transientStorageSnapshot = make(map[int]transientStorage) stateDB.logsSnapshot = make(map[int]int) stateDB.txLogsSnapshot = make(map[int]int) stateDB.logs = []*action.Log{} diff --git a/action/protocol/execution/evm/evmstatedbadapter_test.go b/action/protocol/execution/evm/evmstatedbadapter_test.go index 46bee0717d..960c5f5432 100644 --- a/action/protocol/execution/evm/evmstatedbadapter_test.go +++ b/action/protocol/execution/evm/evmstatedbadapter_test.go @@ -342,10 +342,13 @@ var tests = []stateDBTest{ []access{ {_c1, []common.Hash{_k1, _k2}, []common.Hash{_k3, _k4}, false}, }, + []transient{ + {_c1, _k1, _v1}, + }, []*types.Log{ newTestLog(_c3), newTestLog(_c2), newTestLog(_c1), }, - 3, 0, + 3, 0, 1, "io1q87zge3ngux0v2hz49tdy85dfqwr560pj9mk7r", "io1q87zge3ngux0v2hz49tdy85dfqwr560pj9mk7r", "", }, { @@ -373,10 +376,13 @@ var tests = []stateDBTest{ {_c1, []common.Hash{_k3, _k4}, nil, true}, {_c2, []common.Hash{_k1, _k3}, []common.Hash{_k2, _k4}, false}, }, + []transient{ + {_c2, _k2, _v2}, + }, []*types.Log{ newTestLog(_c4), }, - 4, 1, + 4, 1, 2, "io1zg0qrlpyvc68pnmz4c4f2mfc6jqu8f57jjy09q", "io1j4kjr6x5s8p6dyqlcfrxxdrsea32u2hpvpl5us", "io1x3cv7c4w922k6wx5s8p6d8sjrcqlcfrxhkn5xe", @@ -398,10 +404,13 @@ var tests = []stateDBTest{ []access{ {_c2, []common.Hash{_k2, _k4}, nil, true}, }, + []transient{ + {_c3, _k3, _v3}, + }, []*types.Log{ newTestLog(_c1), newTestLog(_c2), }, - 6, 2, + 6, 2, 3, "io1x3cv7c4w922k6wx5s8p6d8sjrcqlcfrxhkn5xe", "io1q2hz49tdy85dfqwr560pge3ngux0vf0vmhanad", "io1q87zge3ngux0v2hz49tdy85dfqwr560pj9mk7r", @@ -481,13 +490,17 @@ func TestSnapshotRevertAndCommit(t *testing.T) { require.False(sOk) } } + //set transient storage + for _, e := range test.transient { + stateDB.SetTransientState(e.addr, e.k, e.v) + } // set logs and txLogs for _, l := range test.logs { stateDB.AddLog(l) } - require.Equal(test.logSize, len(stateDB.logs)) require.Equal(test.txLogSize, len(stateDB.transactionLogs)) + require.Equal(test.transientSize, len(stateDB.transientStorage)) require.Equal(test.logAddr, stateDB.logs[test.logSize-1].Address) if test.txLogSize > 0 { require.Equal(test.txSender, stateDB.transactionLogs[test.txLogSize-1].Sender) @@ -526,8 +539,11 @@ func TestSnapshotRevertAndCommit(t *testing.T) { {_c1, []common.Hash{_k1, _k2, _k3, _k4}, nil, true}, {_c2, []common.Hash{_k1, _k2, _k3, _k4}, nil, true}, }, + []transient{ + {_c3, _k3, _v3}, + }, nil, - 6, 2, + 6, 2, 3, "io1x3cv7c4w922k6wx5s8p6d8sjrcqlcfrxhkn5xe", "io1q2hz49tdy85dfqwr560pge3ngux0vf0vmhanad", "io1q87zge3ngux0v2hz49tdy85dfqwr560pj9mk7r", @@ -556,8 +572,11 @@ func TestSnapshotRevertAndCommit(t *testing.T) { {_c1, []common.Hash{_k1, _k2, _k3, _k4}, nil, true}, {_c2, []common.Hash{_k1, _k3}, []common.Hash{_k2, _k4}, true}, }, + []transient{ + {_c2, _k2, _v2}, + }, nil, - 4, 1, + 4, 1, 2, "io1zg0qrlpyvc68pnmz4c4f2mfc6jqu8f57jjy09q", "io1j4kjr6x5s8p6dyqlcfrxxdrsea32u2hpvpl5us", "io1x3cv7c4w922k6wx5s8p6d8sjrcqlcfrxhkn5xe", @@ -590,8 +609,11 @@ func TestSnapshotRevertAndCommit(t *testing.T) { {_c1, []common.Hash{_k1, _k2}, []common.Hash{_k3, _k4}, true}, {_c2, nil, []common.Hash{_k1, _k2, _k3, _k4}, false}, }, + []transient{ + {_c1, _k1, _v1}, + }, nil, - 3, 0, + 3, 0, 1, "io1q87zge3ngux0v2hz49tdy85dfqwr560pj9mk7r", "io1q87zge3ngux0v2hz49tdy85dfqwr560pj9mk7r", "", @@ -638,6 +660,10 @@ func TestSnapshotRevertAndCommit(t *testing.T) { require.False(sOk) } } + //test transient storage + for _, e := range test.transient { + require.Equal(e.v, stateDB.GetTransientState(e.addr, e.k)) + } } // test SelfDestruct/exist for _, e := range test.selfDestruct { @@ -669,14 +695,16 @@ func TestSnapshotRevertAndCommit(t *testing.T) { require.Equal(1, len(stateDB.selfDestructedSnapshot)) require.Equal(1, len(stateDB.preimageSnapshot)) require.Equal(1, len(stateDB.accessListSnapshot)) + require.Equal(1, len(stateDB.transientStorageSnapshot)) require.Equal(1, len(stateDB.refundSnapshot)) } else { require.Equal(3, len(stateDB.contractSnapshot)) require.Equal(3, len(stateDB.selfDestructedSnapshot)) require.Equal(3, len(stateDB.preimageSnapshot)) - // refund fix and accessList are introduced after fixSnapshot + // refund fix, accessList, and transient storage are introduced after fixSnapshot // so their snapshot are always properly cleared require.Zero(len(stateDB.accessListSnapshot)) + require.Zero(len(stateDB.transientStorageSnapshot)) require.Zero(len(stateDB.refundSnapshot)) } // commit snapshot 0's state @@ -944,3 +972,53 @@ func TestSortMap(t *testing.T) { require.True(testFunc(t, sm)) }) } + +func TestStateDBTransientStorage(t *testing.T) { + require := require.New(t) + ctrl := gomock.NewController(t) + sm, err := initMockStateManager(ctrl) + require.NoError(err) + var opts []StateDBAdapterOption + opts = append(opts, + NotFixTopicCopyBugOption(), + FixSnapshotOrderOption(), + ) + state, err := NewStateDBAdapter(sm, 1, hash.ZeroHash256, opts...) + if err != nil { + t.Fatal(err) + } + var ( + addr0 = common.Address{} + addr1 = common.HexToAddress("1234567890") + k1 = common.Hash{} + k2 = common.HexToHash("34567890ab") + v1 = common.HexToHash("567890abcd") + v2 = common.HexToHash("7890abcdef") + ) + tests := []struct { + addr common.Address + key, val common.Hash + }{ + {addr0, k1, v1}, + {addr0, k2, v2}, + {addr1, k1, v2}, + {addr1, k2, v1}, + } + for _, test := range tests { + addr := test.addr + key := test.key + value := test.val + sn := state.Snapshot() + state.SetTransientState(addr, key, value) + require.Equal(value, state.GetTransientState(addr, key)) + + // revert the transient state being set and then check that the + // value is now the empty hash + state.RevertToSnapshot(sn) + require.Equal(common.Hash{}, state.GetTransientState(addr, key)) + + state.SetTransientState(addr, key, value) + require.Equal(value, state.GetTransientState(addr, key)) + } + +} diff --git a/action/protocol/execution/evm/testdata_test.go b/action/protocol/execution/evm/testdata_test.go index 8d3830a70e..88c982a43f 100644 --- a/action/protocol/execution/evm/testdata_test.go +++ b/action/protocol/execution/evm/testdata_test.go @@ -56,17 +56,23 @@ type ( nx []common.Hash exist bool } + transient struct { + addr common.Address + k common.Hash + v common.Hash + } stateDBTest struct { - balance []bal - codes []code - states []evmSet - refund uint64 - selfDestruct []sui - preimage []image - accessList []access - logs []*types.Log - logSize, txLogSize int - logAddr, txSender, txReceiver string + balance []bal + codes []code + states []evmSet + refund uint64 + selfDestruct []sui + preimage []image + accessList []access + transient []transient + logs []*types.Log + logSize, txLogSize, transientSize int + logAddr, txSender, txReceiver string } ) diff --git a/action/protocol/execution/protocol_test.go b/action/protocol/execution/protocol_test.go index 21edfc01dc..44d425abae 100644 --- a/action/protocol/execution/protocol_test.go +++ b/action/protocol/execution/protocol_test.go @@ -71,6 +71,7 @@ type ( IsIceland bool `json:"isIceland"` IsLondon bool `json:"isLondon"` IsShanghai bool `json:"isShanghai"` + IsCancun bool `json:"isCancun"` } Log struct { @@ -437,6 +438,9 @@ func (sct *SmartContractTest) prepareBlockchain( cfg.Genesis.Blockchain.SumatraBlockHeight = 0 cfg.Genesis.ActionGasLimit = 10000000 } + if sct.InitGenesis.IsCancun { + cfg.Genesis.Blockchain.UpernavikBlockHeight = 0 + } for _, expectedBalance := range sct.InitBalances { cfg.Genesis.InitBalanceMap[expectedBalance.Account] = expectedBalance.Balance().String() } @@ -1321,6 +1325,12 @@ func TestShanghaiEVM(t *testing.T) { }) } +func TestCancunEVM(t *testing.T) { + t.Run("eip1153-transientstorage", func(t *testing.T) { + NewSmartContractTest(t, "testdata-cancun/transientstorage.json") + }) +} + func benchmarkHotContractWithFactory(b *testing.B, async bool) { sct := SmartContractTest{ InitBalances: []ExpectedBalance{ diff --git a/action/protocol/execution/testdata-cancun/transientstorage.json b/action/protocol/execution/testdata-cancun/transientstorage.json new file mode 100644 index 0000000000..5cb939d9e4 --- /dev/null +++ b/action/protocol/execution/testdata-cancun/transientstorage.json @@ -0,0 +1,49 @@ +{ + "initGenesis": { + "isBering" : true, + "isIceland" : true, + "isLondon" : true, + "isShanghai" : true, + "isCancun": true + }, + "initBalances": [{ + "account": "io1mflp9m6hcgm2qcghchsdqj3z3eccrnekx9p0ms", + "rawBalance": "1000000000000000000000000000" + }], + "deployments": [{ + "rawByteCode": "608060405234801561000f575f80fd5b506102898061001d5f395ff3fe608060405234801561000f575f80fd5b506004361061004a575f3560e01c80630198214f1461004e57806338cc48311461006a5780639c6f010c14610088578063f2c9ecd8146100b8575b5f80fd5b6100686004803603810190610063919061016a565b6100d6565b005b6100726100dd565b60405161007f91906101e7565b60405180910390f35b6100a2600480360381019061009d9190610200565b61010f565b6040516100af919061023a565b60405180910390f35b6100c0610119565b6040516100cd919061023a565b60405180910390f35b80825d5050565b5f6101006001600373ffffffffffffffffffffffffffffffffffffffff166100d6565b61010a600161010f565b905090565b5f815c9050919050565b5f6101255f60216100d6565b61012e5f61010f565b905090565b5f80fd5b5f819050919050565b61014981610137565b8114610153575f80fd5b50565b5f8135905061016481610140565b92915050565b5f80604083850312156101805761017f610133565b5b5f61018d85828601610156565b925050602061019e85828601610156565b9150509250929050565b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f6101d1826101a8565b9050919050565b6101e1816101c7565b82525050565b5f6020820190506101fa5f8301846101d8565b92915050565b5f6020828403121561021557610214610133565b5b5f61022284828501610156565b91505092915050565b61023481610137565b82525050565b5f60208201905061024d5f83018461022b565b9291505056fea2646970667358221220482553b58e8f517e223d2d9d4345c179916b57ace9585c2ff1965520d3a2221064736f6c63430008180033", + "rawPrivateKey": "cfa6ef757dee2e50351620dca002d32b9c090cfda55fb81f37f1d26b273743f1", + "rawAmount": "0", + "rawGasLimit": 5000000, + "rawGasPrice": "0", + "rawExpectedGasConsumed": 207775, + "expectedStatus": 1, + "expectedBalances": [], + "comment": "deploy transientstore contract" + }], + "executions": [{ + "rawPrivateKey": "cfa6ef757dee2e50351620dca002d32b9c090cfda55fb81f37f1d26b273743f1", + "rawByteCode": "f2c9ecd8", + "rawAmount": "0", + "rawGasLimit": 1000000, + "rawGasPrice": "0", + "rawAccessList": [], + "rawExpectedGasConsumed": 11056, + "expectedStatus": 1, + "readOnly": true, + "rawReturnValue": "0000000000000000000000000000000000000000000000000000000000000021", + "comment": "call getNumber" + },{ + "rawPrivateKey": "cfa6ef757dee2e50351620dca002d32b9c090cfda55fb81f37f1d26b273743f1", + "rawByteCode": "38cc4831", + "rawAmount": "0", + "rawGasLimit": 1000000, + "rawGasPrice": "0", + "rawAccessList": [], + "rawExpectedGasConsumed": 11068, + "expectedStatus": 1, + "readOnly": true, + "rawReturnValue": "0000000000000000000000000000000000000000000000000000000000000003", + "comment": "call getAddress" + }] +} diff --git a/action/protocol/execution/testdata-cancun/transientstorage.sol b/action/protocol/execution/testdata-cancun/transientstorage.sol new file mode 100644 index 0000000000..46d648ffa1 --- /dev/null +++ b/action/protocol/execution/testdata-cancun/transientstorage.sol @@ -0,0 +1,30 @@ +// This support was introduced with Solidity 0.8.24 +pragma solidity 0.8.24; +// SPDX-License-Identifier: Unlicensed + +contract TransientStorage { + + // Sets a number in the transient storage + function getNumber() public returns (uint) { + tstore(0, uint(33)); + return uint(tload(0)); + } + + // Sets an address in the transient storage + function getAddress() public returns (address) { + tstore(1, uint(uint160(address(3)))); + return address(uint160(tload(1))); + } + + function tstore(uint location, uint value) public { + assembly { + tstore(location, value) + } + } + + function tload(uint location) public view returns (uint value) { + assembly { + value := tload(location) + } + } +}