Skip to content

Commit

Permalink
Eip 2929 (0xPolygon#1942)
Browse files Browse the repository at this point in the history
* implemented changes for EIP-2929 (access list)

* added missing files

* fixed unit test Test_opCall

* populate access_list with precompiles only if eip-2929 is activated

* added address to access list for Balance opcode

* added test cases and for EIP-2929 and also call stack output

* changed structure of access list and removed some comments

* fixed linting errors

* fixed AddSlot method

* added test cases for access list

* removed call stack output for Test_Transition_EIP2929

* corrected indentation

* fixed linting errors

* fixed linting errors

* fixed Test_opReturnDataCopy

* removed ActivePrecompile Address slice

* changed the const name

* changed the const name in comments

* change the AddAddress function to a variadic function

* refactored

* added test cases for SLOAD opcode

* fixed linting errors

* refactored code

* EIP-2565 Implementation (0xPolygon#1704)

* implemented EIP-2565

* fixed linting error

* fixed linting error

* fixed TestModExpWithEIP2565

* resolved comments

* resolved comments

* resolved merge conflicts

* resolved merge conflicts

* fixed EIP-2929 test
  • Loading branch information
rachit77 authored Sep 28, 2023
1 parent 4c6847c commit 781c691
Show file tree
Hide file tree
Showing 14 changed files with 638 additions and 22 deletions.
4 changes: 4 additions & 0 deletions chain/params.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ const (
EIP155 = "EIP155"
QuorumCalcAlignment = "quorumcalcalignment"
TxHashWithType = "txHashWithType"
EIP2929 = "EIP2929"
EIP2565 = "EIP2565"
LondonFix = "londonfix"
)
Expand Down Expand Up @@ -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),
}
Expand Down Expand Up @@ -157,6 +159,7 @@ type ForksInTime struct {
EIP155,
QuorumCalcAlignment,
TxHashWithType,
EIP2929,
EIP2565,
LondonFix bool
}
Expand All @@ -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),
}
1 change: 1 addition & 0 deletions helper/predeployment/predeployment.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
31 changes: 29 additions & 2 deletions state/executor.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand All @@ -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)
}
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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{
Expand Down
109 changes: 109 additions & 0 deletions state/executor_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)

Expand Down Expand Up @@ -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)
})
}
}
77 changes: 77 additions & 0 deletions state/runtime/access_list.go
Original file line number Diff line number Diff line change
@@ -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
}
99 changes: 99 additions & 0 deletions state/runtime/access_list_test.go
Original file line number Diff line number Diff line change
@@ -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)
}
Loading

0 comments on commit 781c691

Please sign in to comment.