diff --git a/chain/params.go b/chain/params.go index 8fdbcb96e7..6ac4704a99 100644 --- a/chain/params.go +++ b/chain/params.go @@ -89,6 +89,7 @@ const ( EIP155 = "EIP155" QuorumCalcAlignment = "quorumcalcalignment" TxHashWithType = "txHashWithType" + EIP2929 = "EIP2929" EIP2565 = "EIP2565" LondonFix = "londonfix" ) @@ -126,6 +127,7 @@ func (f *Forks) At(block uint64) ForksInTime { EIP155: f.IsActive(EIP155, block), QuorumCalcAlignment: f.IsActive(QuorumCalcAlignment, block), TxHashWithType: f.IsActive(TxHashWithType, block), + EIP2929: f.IsActive(EIP2929, block), EIP2565: f.IsActive(EIP2565, block), LondonFix: f.IsActive(LondonFix, block), } @@ -157,6 +159,7 @@ type ForksInTime struct { EIP155, QuorumCalcAlignment, TxHashWithType, + EIP2929, EIP2565, LondonFix bool } @@ -174,6 +177,7 @@ var AllForksEnabled = &Forks{ London: NewFork(0), QuorumCalcAlignment: NewFork(0), TxHashWithType: NewFork(0), + EIP2929: NewFork(0), EIP2565: NewFork(0), LondonFix: NewFork(0), } diff --git a/helper/predeployment/predeployment.go b/helper/predeployment/predeployment.go index 08e445b033..71435d90b6 100644 --- a/helper/predeployment/predeployment.go +++ b/helper/predeployment/predeployment.go @@ -136,6 +136,7 @@ func getPredeployAccount(address types.Address, input []byte, chainID int64) (*c big.NewInt(0), math.MaxInt64, input, + runtime.NewAccessList(), ) // Enable all forks diff --git a/state/executor.go b/state/executor.go index 9e607e0d60..b60a618abe 100644 --- a/state/executor.go +++ b/state/executor.go @@ -648,7 +648,13 @@ func (t *Transition) Create2( gas uint64, ) *runtime.ExecutionResult { address := crypto.CreateAddress(caller, t.state.GetNonce(caller)) - contract := runtime.NewContractCreation(1, caller, caller, address, value, gas, code) + contract := runtime.NewContractCreation(1, caller, caller, address, value, gas, code, runtime.NewAccessList()) + + if t.config.EIP2929 { + contract.AccessList.AddAddress(caller) + // add all precompiles to access list + contract.AccessList.AddAddress(t.precompiles.ContractAddr...) + } return t.applyCreate(contract, t) } @@ -660,7 +666,14 @@ func (t *Transition) Call2( value *big.Int, gas uint64, ) *runtime.ExecutionResult { - c := runtime.NewContractCall(1, caller, caller, to, value, gas, t.state.GetCode(to), input) + c := runtime.NewContractCall(1, caller, caller, to, value, gas, t.state.GetCode(to), input, runtime.NewAccessList()) + + if t.config.EIP2929 { + c.AccessList.AddAddress(caller) + c.AccessList.AddAddress(to) + // add all precompiles to access list + c.AccessList.AddAddress(t.precompiles.ContractAddr...) + } return t.applyCall(c, runtime.Call, t) } @@ -766,8 +779,15 @@ func (t *Transition) applyCall( t.captureCallStart(c, callType) + // create a deep copy of access list for reverted transaction + al := c.AccessList.Copy() + result = t.run(c, host) if result.Failed() { + if result.Reverted() { + c.AccessList = al + } + if err := t.state.RevertToSnapshot(snapshot); err != nil { return &runtime.ExecutionResult{ GasLeft: c.Gas, @@ -806,6 +826,13 @@ func (t *Transition) applyCreate(c *runtime.Contract, host runtime.Host) *runtim return &runtime.ExecutionResult{Err: err} } + //EIP2929: check + // we add this to the access-list before taking a snapshot. Even if the creation fails, + // the access-list change should not be rolled back according to EIP2929 specs + if t.config.EIP2929 { + c.AccessList.AddAddress(c.Address) + } + // Check if there is a collision and the address already exists if t.hasCodeOrNonce(c.Address) { return &runtime.ExecutionResult{ diff --git a/state/executor_test.go b/state/executor_test.go index 6d3ecb82d0..4813e84ac9 100644 --- a/state/executor_test.go +++ b/state/executor_test.go @@ -10,6 +10,7 @@ import ( "github.com/0xPolygon/polygon-edge/chain" "github.com/0xPolygon/polygon-edge/state/runtime" + "github.com/0xPolygon/polygon-edge/state/runtime/evm" "github.com/0xPolygon/polygon-edge/types" ) @@ -157,3 +158,111 @@ func Test_Transition_checkDynamicFees(t *testing.T) { }) } } + +// Tests for EIP-2929 +func Test_Transition_EIP2929(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + code []byte + gasConsumed uint64 + }{ + { + name: "Test 1: Check access list for EXTCODEHASH,EXTCODESIZE and BALANCE opcodes", + code: []byte{ + // WarmStorageReadCostEIP2929 should be charged since precompiles address are in access list + uint8(evm.PUSH1), 1, uint8(evm.EXTCODEHASH), uint8(evm.POP), + uint8(evm.PUSH1), 2, uint8(evm.EXTCODESIZE), uint8(evm.POP), + uint8(evm.PUSH1), 3, uint8(evm.BALANCE), uint8(evm.POP), + + uint8(evm.PUSH1), 0xe1, uint8(evm.EXTCODEHASH), uint8(evm.POP), + uint8(evm.PUSH1), 0xe2, uint8(evm.EXTCODESIZE), uint8(evm.POP), + uint8(evm.PUSH1), 0xe3, uint8(evm.BALANCE), uint8(evm.POP), + // cost should be WarmStorageReadCostEIP2929, since addresses are present in access list + uint8(evm.PUSH1), 0xe2, uint8(evm.EXTCODEHASH), uint8(evm.POP), + uint8(evm.PUSH1), 0xe3, uint8(evm.EXTCODESIZE), uint8(evm.POP), + uint8(evm.PUSH1), 0xe1, uint8(evm.BALANCE), uint8(evm.POP), + + uint8(evm.ORIGIN), uint8(evm.BALANCE), uint8(evm.POP), + uint8(evm.ADDRESS), uint8(evm.BALANCE), uint8(evm.POP), + + uint8(evm.STOP), + }, + gasConsumed: uint64(8653), + }, + { + name: "Test 2: Check Storage opcodes", + code: []byte{ + // Add slot `0xe1` to access list, ColdAccountAccessCostEIP2929 charged + uint8(evm.PUSH1), 0xe1, uint8(evm.SLOAD), uint8(evm.POP), + // Write to `0xe1` which is already in access list, WarmStorageReadCostEIP2929 charged + uint8(evm.PUSH1), 0xf1, uint8(evm.PUSH1), 0xe1, uint8(evm.SSTORE), + // Write to `0xe2` which is not in access list, ColdAccountAccessCostEIP2929 charged + uint8(evm.PUSH1), 0xf1, uint8(evm.PUSH1), 0xe2, uint8(evm.SSTORE), + // Write again to `0xe2`, WarmStorageReadCostEIP2929 charged since `0xe2` already in access list + uint8(evm.PUSH1), 0x11, uint8(evm.PUSH1), 0xe2, uint8(evm.SSTORE), + // SLOAD `0xe2`, address present in access list + uint8(evm.PUSH1), 0xe2, uint8(evm.SLOAD), + // SLOAD `0xe3`, ColdStorageReadCostEIP2929 charged since address not present in access list + uint8(evm.PUSH1), 0xe3, uint8(evm.SLOAD), + }, + gasConsumed: uint64(46529), + }, + { + name: "Test 3: Check EXTCODECOPY opcode", + code: []byte{ + // extcodecopy( 0xff,0,0,0,0) + uint8(evm.PUSH1), 0x00, uint8(evm.PUSH1), 0x00, uint8(evm.PUSH1), 0x00, + uint8(evm.PUSH1), 0xff, uint8(evm.EXTCODECOPY), + // extcodecopy( 0xff,0,0,0,0) + uint8(evm.PUSH1), 0x00, uint8(evm.PUSH1), 0x00, uint8(evm.PUSH1), 0x00, + uint8(evm.PUSH1), 0xff, uint8(evm.EXTCODECOPY), + // extcodecopy( this,0,0,0,0) + uint8(evm.PUSH1), 0x00, uint8(evm.PUSH1), 0x00, uint8(evm.PUSH1), 0x00, + uint8(evm.ADDRESS), uint8(evm.EXTCODECOPY), + uint8(evm.STOP), + }, + gasConsumed: uint64(2835), + }, + { + name: "Test 4: Check Call opcodes", + code: []byte{ + // Precompile `0x02` + uint8(evm.PUSH1), 0x0, uint8(evm.DUP1), uint8(evm.DUP1), uint8(evm.DUP1), uint8(evm.DUP1), + uint8(evm.PUSH1), 0x02, uint8(evm.PUSH1), 0x0, uint8(evm.CALL), uint8(evm.POP), + + // Call `0xce` + uint8(evm.PUSH1), 0x0, uint8(evm.DUP1), uint8(evm.DUP1), uint8(evm.DUP1), uint8(evm.DUP1), + uint8(evm.PUSH1), 0xce, uint8(evm.PUSH1), 0x0, uint8(evm.CALL), uint8(evm.POP), + + // Delegate Call `0xce` + uint8(evm.PUSH1), 0x0, uint8(evm.DUP1), uint8(evm.DUP1), uint8(evm.DUP1), uint8(evm.DUP1), + uint8(evm.PUSH1), 0xce, uint8(evm.PUSH1), 0x0, uint8(evm.DELEGATECALL), uint8(evm.POP), + + // Static Call `0xbb` + uint8(evm.PUSH1), 0x0, uint8(evm.DUP1), uint8(evm.DUP1), uint8(evm.DUP1), uint8(evm.DUP1), + uint8(evm.PUSH1), 0xbb, uint8(evm.PUSH1), 0x0, uint8(evm.STATICCALL), uint8(evm.POP), + }, + gasConsumed: uint64(5492), + }, + } + + for _, testCase := range tests { + tt := testCase + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + state := newStateWithPreState(nil) + addr := types.BytesToAddress([]byte("contract")) + txn := newTxn(state) + txn.SetCode(addr, tt.code) + + enabledForks := chain.AllForksEnabled.At(0) + transition := NewTransition(enabledForks, state, txn) + + result := transition.Call2(transition.ctx.Origin, addr, nil, big.NewInt(0), uint64(1000000)) + assert.Equal(t, tt.gasConsumed, result.GasUsed, "Gas consumption for %s is inaccurate according to EIP 2929", tt.name) + }) + } +} diff --git a/state/runtime/access_list.go b/state/runtime/access_list.go new file mode 100644 index 0000000000..8fd5fe83ef --- /dev/null +++ b/state/runtime/access_list.go @@ -0,0 +1,77 @@ +package runtime + +import ( + "github.com/0xPolygon/polygon-edge/types" +) + +type AccessList map[types.Address]map[types.Hash]struct{} + +func NewAccessList() *AccessList { + al := make(AccessList) + + return &al +} + +// Checks if address is present within the access list. +func (al *AccessList) ContainsAddress(address types.Address) bool { + _, ok := (*al)[address] + + return ok +} + +// Contains checks if a slot is present in an account. +// Returns two boolean flags: `accountPresent` and `slotPresent`. +func (al *AccessList) Contains(address types.Address, slot types.Hash) (bool, bool) { + _, addrPresent := (*al)[address] + _, slotPresent := (*al)[address][slot] + + return addrPresent, slotPresent +} + +// Copy creates an deep copy of provided AccessList. +func (al *AccessList) Copy() *AccessList { + cp := make(AccessList, len(*al)) + + for addr, slotMap := range *al { + cpSlotMap := make(map[types.Hash]struct{}, len(slotMap)) + for slotHash := range slotMap { + cpSlotMap[slotHash] = struct{}{} + } + + cp[addr] = cpSlotMap + } + + return &cp +} + +// AddAddress adds an address to the access list +// returns 'true' if the operation results in a change (i.e., the address was not already present in the list). +func (al *AccessList) AddAddress(address ...types.Address) { + for _, addr := range address { + if _, exists := (*al)[addr]; exists { + continue + } + + (*al)[addr] = make(map[types.Hash]struct{}) + } +} + +// This function adds the specified address and slot pair to the access list. +// The return values indicate whether the address was newly added and whether the slot was newly added. +func (al *AccessList) AddSlot(address types.Address, slot types.Hash) (addrChange bool, slotChange bool) { + slotMap, addressExists := (*al)[address] + if !addressExists { + slotMap = make(map[types.Hash]struct{}) + (*al)[address] = slotMap + } + + _, slotPresent := slotMap[slot] + if !slotPresent { + slotMap[slot] = struct{}{} + + return !addressExists, true + } + + // slot and address were already present in access list + return false, false +} diff --git a/state/runtime/access_list_test.go b/state/runtime/access_list_test.go new file mode 100644 index 0000000000..9120df1c27 --- /dev/null +++ b/state/runtime/access_list_test.go @@ -0,0 +1,99 @@ +package runtime + +import ( + "testing" + + "github.com/0xPolygon/polygon-edge/types" + "github.com/stretchr/testify/assert" +) + +var ( + address1 = types.BytesToAddress([]byte("address1")) + address2 = types.BytesToAddress([]byte("address2")) + address3 = types.BytesToAddress([]byte("address3")) + slotHash = types.BytesToHash([]byte("slot")) +) + +func createInitialAccessList() *AccessList { + initialAccessList := NewAccessList() + (*initialAccessList)[address1] = make(map[types.Hash]struct{}) + (*initialAccessList)[address1][slotHash] = struct{}{} + (*initialAccessList)[address2] = make(map[types.Hash]struct{}) + + return initialAccessList +} + +func TestContainsAddress(t *testing.T) { + t.Parallel() + + initialAccessList := createInitialAccessList() + assert.True(t, initialAccessList.ContainsAddress(address1)) + assert.False(t, initialAccessList.ContainsAddress(address3)) +} + +func TestContains(t *testing.T) { + t.Parallel() + + initialAccessList := createInitialAccessList() + + address1Present, slotHashPresent := initialAccessList.Contains(address1, slotHash) + assert.True(t, address1Present) + assert.True(t, slotHashPresent) + + address3Present, slotHashPresent := initialAccessList.Contains(address3, slotHash) + assert.False(t, address3Present) + assert.False(t, slotHashPresent) +} + +func TestCopy(t *testing.T) { + t.Parallel() + + initialAccessList := createInitialAccessList() + copiedAccessList := initialAccessList.Copy() + assert.Equal(t, initialAccessList, copiedAccessList) +} + +func TestAddAddress(t *testing.T) { + t.Parallel() + + initialAccessList := createInitialAccessList() + finalAccessList := createInitialAccessList() + + initialAccessList.AddAddress(address1) + assert.Equal(t, finalAccessList, initialAccessList) + + initialAccessList.AddAddress(address3) + + (*finalAccessList)[address3] = make(map[types.Hash]struct{}) + assert.Equal(t, finalAccessList, initialAccessList) +} + +func TestAddSlot(t *testing.T) { + t.Parallel() + + initialAccessList := createInitialAccessList() + finalAccessList := createInitialAccessList() + + // add address1 and slotHash + addr1Exists, slot1Exists := initialAccessList.AddSlot(address1, slotHash) + assert.False(t, addr1Exists) + assert.False(t, slot1Exists) + assert.Equal(t, finalAccessList, initialAccessList) + + // add address2 and slotHash + addr2Exists, slot2Exists := initialAccessList.AddSlot(address2, slotHash) + assert.False(t, addr2Exists) + assert.True(t, slot2Exists) + + (*finalAccessList)[address2][slotHash] = struct{}{} + assert.Equal(t, finalAccessList, initialAccessList) + + // add address3 and slotHash + addr3Exists, slot3Exists := initialAccessList.AddSlot(address3, slotHash) + assert.True(t, addr3Exists) + assert.True(t, slot3Exists) + + (*finalAccessList)[address3] = make(map[types.Hash]struct{}) + (*finalAccessList)[address3][slotHash] = struct{}{} + assert.Equal(t, finalAccessList, initialAccessList) +} diff --git a/state/runtime/evm/evm.go b/state/runtime/evm/evm.go index 7563291da1..c8959b1bcf 100644 --- a/state/runtime/evm/evm.go +++ b/state/runtime/evm/evm.go @@ -39,6 +39,7 @@ func (e *EVM) Run(c *runtime.Contract, host runtime.Host, config *chain.ForksInT contract.gas = c.Gas contract.host = host contract.config = config + contract.accessList = c.AccessList contract.bitmap.setCode(c.Code) diff --git a/state/runtime/evm/evm_test.go b/state/runtime/evm/evm_test.go index f22900a968..2df7e4444d 100644 --- a/state/runtime/evm/evm_test.go +++ b/state/runtime/evm/evm_test.go @@ -20,6 +20,7 @@ func newMockContract(value *big.Int, gas uint64, code []byte) *runtime.Contract value, gas, code, + runtime.NewAccessList(), ) } diff --git a/state/runtime/evm/instructions.go b/state/runtime/evm/instructions.go index 4f9c798e11..dc0f677f0e 100644 --- a/state/runtime/evm/instructions.go +++ b/state/runtime/evm/instructions.go @@ -16,12 +16,30 @@ import ( type instruction func(c *state) +const ( + ColdAccountAccessCostEIP2929 = uint64(2600) // COLD_ACCOUNT_ACCESS_COST + ColdStorageReadCostEIP2929 = uint64(2100) // COLD_SLOAD_COST_EIP2929 + WarmStorageReadCostEIP2929 = uint64(100) // WARM_STORAGE_READ_COST +) + var ( zero = big.NewInt(0) one = big.NewInt(1) wordSize = big.NewInt(32) ) +func (c *state) calculateGasForEIP2929(addr types.Address) uint64 { + var gas uint64 + if c.accessList.ContainsAddress(addr) { + gas = WarmStorageReadCostEIP2929 + } else { + gas = ColdAccountAccessCostEIP2929 + c.accessList.AddAddress(addr) + } + + return gas +} + func opAdd(c *state) { a := c.pop() b := c.top() @@ -465,7 +483,16 @@ func opSload(c *state) { loc := c.top() var gas uint64 - if c.config.Istanbul { + + if c.config.EIP2929 { + if _, slotPresent := c.accessList.Contains(c.msg.Address, bigToHash(loc)); !slotPresent { + gas = ColdStorageReadCostEIP2929 + + c.accessList.AddSlot(c.msg.Address, bigToHash(loc)) + } else { + gas = WarmStorageReadCostEIP2929 + } + } else if c.config.Istanbul { // eip-1884 gas = 800 } else if c.config.EIP150 { @@ -503,9 +530,19 @@ func opSStore(c *state) { status := c.host.SetStorage(c.msg.Address, key, val, c.config) cost := uint64(0) + if c.config.EIP2929 { + if _, slotPresent := c.accessList.Contains(c.msg.Address, key); !slotPresent { + cost = ColdStorageReadCostEIP2929 + + c.accessList.AddSlot(c.msg.Address, key) + } + } + switch status { case runtime.StorageUnchanged: - if c.config.Istanbul { + if c.config.EIP2929 { + cost += WarmStorageReadCostEIP2929 + } else if c.config.Istanbul { // eip-2200 cost = 800 } else if legacyGasMetering { @@ -515,10 +552,15 @@ func opSStore(c *state) { } case runtime.StorageModified: - cost = 5000 + cost += 5000 + if c.config.EIP2929 { + cost -= ColdStorageReadCostEIP2929 + } case runtime.StorageModifiedAgain: - if c.config.Istanbul { + if c.config.EIP2929 { + cost += WarmStorageReadCostEIP2929 + } else if c.config.Istanbul { // eip-2200 cost = 800 } else if legacyGasMetering { @@ -528,10 +570,13 @@ func opSStore(c *state) { } case runtime.StorageAdded: - cost = 20000 + cost += 20000 case runtime.StorageDeleted: - cost = 5000 + cost += 5000 + if c.config.EIP2929 { + cost -= ColdStorageReadCostEIP2929 + } } if !c.consumeGas(cost) { @@ -575,7 +620,10 @@ func opBalance(c *state) { addr, _ := c.popAddr() var gas uint64 - if c.config.Istanbul { + + if c.config.EIP2929 { + gas = c.calculateGasForEIP2929(addr) + } else if c.config.Istanbul { // eip-1884 gas = 700 } else if c.config.EIP150 { @@ -658,7 +706,10 @@ func opExtCodeSize(c *state) { addr, _ := c.popAddr() var gas uint64 - if c.config.EIP150 { + + if c.config.EIP2929 { + gas = c.calculateGasForEIP2929(addr) + } else if c.config.EIP150 { gas = 700 } else { gas = 20 @@ -693,7 +744,10 @@ func opExtCodeHash(c *state) { address, _ := c.popAddr() var gas uint64 - if c.config.Istanbul { + + if c.config.EIP2929 { + gas = c.calculateGasForEIP2929(address) + } else if c.config.Istanbul { gas = 700 } else { gas = 400 @@ -767,7 +821,10 @@ func opExtCodeCopy(c *state) { } var gas uint64 - if c.config.EIP150 { + + if c.config.EIP2929 { + gas = c.calculateGasForEIP2929(address) + } else if c.config.EIP150 { gas = 700 } else { gas = 20 @@ -943,6 +1000,13 @@ func opSelfDestruct(c *state) { } } + // EIP 2929 gas + if c.config.EIP2929 && !c.accessList.ContainsAddress(address) { + gas += ColdAccountAccessCostEIP2929 + + c.accessList.AddAddress(address) + } + if !c.consumeGas(gas) { return } @@ -1226,7 +1290,10 @@ func (c *state) buildCallContract(op OpCode) (*runtime.Contract, uint64, uint64, } var gasCost uint64 - if c.config.EIP150 { + + if c.config.EIP2929 { + gasCost = c.calculateGasForEIP2929(addr) + } else if c.config.EIP150 { gasCost = 700 } else { gasCost = 40 @@ -1293,6 +1360,7 @@ func (c *state) buildCallContract(op OpCode) (*runtime.Contract, uint64, uint64, gas, c.host.GetCode(addr), args, + c.accessList, ) if op == STATICCALL || parent.msg.Static { @@ -1384,7 +1452,16 @@ func (c *state) buildCreateContract(op OpCode) (*runtime.Contract, error) { address = crypto.CreateAddress2(c.msg.Address, bigToHash(salt), input) } - contract := runtime.NewContractCreation(c.msg.Depth+1, c.msg.Origin, c.msg.Address, address, value, gas, input) + contract := runtime.NewContractCreation( + c.msg.Depth+1, + c.msg.Origin, + c.msg.Address, + address, + value, + gas, + input, + c.accessList, + ) return contract, nil } diff --git a/state/runtime/evm/instructions_test.go b/state/runtime/evm/instructions_test.go index e17f79fdcc..9c59543ba5 100644 --- a/state/runtime/evm/instructions_test.go +++ b/state/runtime/evm/instructions_test.go @@ -106,6 +106,8 @@ type mockHostForInstructions struct { nonce uint64 code []byte callxResult *runtime.ExecutionResult + addresses map[types.Address]int + storages []map[types.Hash]types.Hash } func (m *mockHostForInstructions) GetNonce(types.Address) uint64 { @@ -120,10 +122,210 @@ func (m *mockHostForInstructions) GetCode(addr types.Address) []byte { return m.code } +func (m *mockHostForInstructions) GetStorage(addr types.Address, key types.Hash) types.Hash { + idx, ok := m.addresses[addr] + if !ok { + return types.ZeroHash + } + + res, ok := m.storages[idx][key] + if !ok { + return types.ZeroHash + } + + return res +} + var ( addr1 = types.StringToAddress("1") ) +func Test_opSload(t *testing.T) { + t.Parallel() + + type state struct { + gas uint64 + sp int + stack []*big.Int + memory []byte + accessList *runtime.AccessList + stop bool + err error + } + + address1 := types.StringToAddress("address1") + key1 := types.StringToHash("1") + val1 := types.StringToHash("2") + tests := []struct { + name string + op OpCode + contract *runtime.Contract + config *chain.ForksInTime + initState *state + resultState *state + mockHost *mockHostForInstructions + }{ + { + name: "charge ColdStorageReadCostEIP2929 if the (address, storage_key) pair is not accessed_storage_keys", + op: SLOAD, + contract: &runtime.Contract{ + Address: address1, + }, + config: &chain.ForksInTime{ + EIP2929: true, + }, + initState: &state{ + gas: 10000, + sp: 1, + stack: []*big.Int{ + new(big.Int).SetBytes(key1.Bytes()), + }, + memory: []byte{0x01}, + accessList: runtime.NewAccessList(), + }, + resultState: &state{ + gas: 7900, + sp: 1, + stack: []*big.Int{ + new(big.Int).SetBytes(val1.Bytes()), + }, + memory: []byte{0x01}, + stop: false, + err: nil, + accessList: &runtime.AccessList{ + address1: { + key1: struct{}{}, + }, + }, + }, + mockHost: &mockHostForInstructions{ + addresses: map[types.Address]int{ + address1: 0, + }, + storages: []map[types.Hash]types.Hash{ + { + key1: val1, + }, + }, + }, + }, + { + name: "charge WarmStorageReadCostEIP2929 if the (address, storage_key) pair is in access list", + op: SLOAD, + contract: &runtime.Contract{ + Address: address1, + }, + config: &chain.ForksInTime{ + EIP2929: true, + }, + initState: &state{ + gas: 10000, + sp: 1, + stack: []*big.Int{ + new(big.Int).SetBytes(key1.Bytes()), + }, + memory: []byte{0x01}, + accessList: &runtime.AccessList{ + address1: { + key1: struct{}{}, + }, + }, + }, + resultState: &state{ + gas: 9900, + sp: 1, + stack: []*big.Int{ + new(big.Int).SetBytes(val1.Bytes()), + }, + memory: []byte{0x01}, + stop: false, + err: nil, + accessList: &runtime.AccessList{ + address1: { + key1: struct{}{}, + }, + }, + }, + mockHost: &mockHostForInstructions{ + addresses: map[types.Address]int{ + address1: 0, + }, + storages: []map[types.Hash]types.Hash{ + { + key1: val1, + }, + }, + }, + }, + { + name: "charge Gas 800 when EIP2929 is not enabled and Istanbul is enabled", + op: SLOAD, + contract: &runtime.Contract{ + Address: address1, + }, + config: &chain.ForksInTime{ + EIP2929: false, + Istanbul: true, + }, + initState: &state{ + gas: 10000, + sp: 1, + stack: []*big.Int{ + new(big.Int).SetBytes(key1.Bytes()), + }, + memory: []byte{0x01}, + accessList: nil, + }, + resultState: &state{ + gas: 9200, + sp: 1, + stack: []*big.Int{ + new(big.Int).SetBytes(val1.Bytes()), + }, + memory: []byte{0x01}, + stop: false, + err: nil, + accessList: nil, + }, + mockHost: &mockHostForInstructions{ + addresses: map[types.Address]int{ + address1: 0, + }, + storages: []map[types.Hash]types.Hash{ + { + key1: val1, + }, + }, + }, + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + s, closeFn := getState() + defer closeFn() + s.msg = tt.contract + s.gas = tt.initState.gas + s.sp = tt.initState.sp + s.stack = tt.initState.stack + s.memory = tt.initState.memory + s.config = tt.config + s.host = tt.mockHost + s.accessList = tt.initState.accessList + opSload(s) + assert.Equal(t, tt.resultState.gas, s.gas, "gas in state after execution is not correct") + assert.Equal(t, tt.resultState.sp, s.sp, "sp in state after execution is not correct") + assert.Equal(t, tt.resultState.stack, s.stack, "stack in state after execution is not correct") + assert.Equal(t, tt.resultState.memory, s.memory, "memory in state after execution is not correct") + assert.Equal(t, tt.resultState.accessList, s.accessList, "accesslist in state after execution is not correct") + assert.Equal(t, tt.resultState.stop, s.stop, "stop in state after execution is not correct") + assert.Equal(t, tt.resultState.err, s.err, "err in state after execution is not correct") + }) + } +} + func TestCreate(t *testing.T) { type state struct { gas uint64 @@ -650,6 +852,7 @@ func Test_opReturnDataCopy(t *testing.T) { state.evm = nil state.bitmap = bitmap{} state.ret = nil + state.accessList = nil state.currentConsumedGas = 0 opReturnDataCopy(state) @@ -680,7 +883,7 @@ func Test_opCall(t *testing.T) { }, config: &allEnabledForks, initState: &state{ - gas: 1000, + gas: 2600, //EIP2929: check gas increased to remove error, org gas 1000 sp: 6, stack: []*big.Int{ big.NewInt(0x00), // outSize @@ -690,7 +893,8 @@ func Test_opCall(t *testing.T) { big.NewInt(0x00), // address big.NewInt(0x00), // initialGas }, - memory: []byte{0x01}, + memory: []byte{0x01}, + accessList: runtime.NewAccessList(), }, resultState: &state{ memory: []byte{0x01}, @@ -719,6 +923,7 @@ func Test_opCall(t *testing.T) { state.memory = test.initState.memory state.config = test.config state.host = test.mockHost + state.accessList = test.initState.accessList opCall(test.op)(state) diff --git a/state/runtime/evm/state.go b/state/runtime/evm/state.go index 9a849cecb4..75d06d5f3f 100644 --- a/state/runtime/evm/state.go +++ b/state/runtime/evm/state.go @@ -79,6 +79,8 @@ type state struct { returnData []byte ret []byte + + accessList *runtime.AccessList } func (c *state) reset() { diff --git a/state/runtime/precompiled/precompiled.go b/state/runtime/precompiled/precompiled.go index 8b33d5eb80..fc9632c86b 100644 --- a/state/runtime/precompiled/precompiled.go +++ b/state/runtime/precompiled/precompiled.go @@ -43,8 +43,9 @@ type contract interface { // Precompiled is the runtime for the precompiled contracts type Precompiled struct { - buf []byte - contracts map[types.Address]contract + buf []byte + contracts map[types.Address]contract + ContractAddr []types.Address } // NewPrecompiled creates a new runtime for the precompiled contracts @@ -86,6 +87,7 @@ func (p *Precompiled) register(addrStr string, b contract) { } p.contracts[types.StringToAddress(addrStr)] = b + p.ContractAddr = append(p.ContractAddr, types.StringToAddress(addrStr)) } var ( diff --git a/state/runtime/runtime.go b/state/runtime/runtime.go index 4ca7c2aa83..13ee8113c6 100644 --- a/state/runtime/runtime.go +++ b/state/runtime/runtime.go @@ -196,6 +196,7 @@ type Contract struct { Input []byte Gas uint64 Static bool + AccessList *AccessList } func NewContract( @@ -206,6 +207,7 @@ func NewContract( value *big.Int, gas uint64, code []byte, + accessList *AccessList, ) *Contract { f := &Contract{ Caller: from, @@ -216,6 +218,7 @@ func NewContract( Value: value, Code: code, Depth: depth, + AccessList: accessList, } return f @@ -229,8 +232,9 @@ func NewContractCreation( value *big.Int, gas uint64, code []byte, + accessList *AccessList, ) *Contract { - c := NewContract(depth, origin, from, to, value, gas, code) + c := NewContract(depth, origin, from, to, value, gas, code, accessList) return c } @@ -244,8 +248,9 @@ func NewContractCall( gas uint64, code []byte, input []byte, + accessList *AccessList, ) *Contract { - c := NewContract(depth, origin, from, to, value, gas, code) + c := NewContract(depth, origin, from, to, value, gas, code, accessList) c.Input = input return c diff --git a/state/txn.go b/state/txn.go index 04ba50751c..e323f9f4d8 100644 --- a/state/txn.go +++ b/state/txn.go @@ -351,13 +351,19 @@ func (txn *Txn) SetStorage( if original == value { if original == types.ZeroHash { // reset to original nonexistent slot (2.2.2.1) // Storage was used as memory (allocation and deallocation occurred within the same contract) - if config.Istanbul { + if config.EIP2929 { + // Refund: SstoreSetGasEIP2200 - WarmStorageReadCostEIP2929 + txn.AddRefund(19900) + } else if config.Istanbul { txn.AddRefund(19200) } else { txn.AddRefund(19800) } } else { // reset to original existing slot (2.2.2.2) - if config.Istanbul { + if config.EIP2929 { + // Refund: SstoreResetGasEIP2200 - ColdStorageReadCostEIP2929 - WarmStorageReadCostEIP2929 + txn.AddRefund(2800) + } else if config.Istanbul { txn.AddRefund(4200) } else { txn.AddRefund(4800)