From 0a07ee8862faeecb1eb6ab76545994c5ba611dd9 Mon Sep 17 00:00:00 2001 From: Goran Rojovic Date: Fri, 9 Feb 2024 16:15:37 +0100 Subject: [PATCH 01/16] Tests fixes --- state/executor.go | 46 +++++++++++++++++++++++++------ state/runtime/precompiled/base.go | 6 +++- state/runtime/runtime.go | 5 ++-- state/txn.go | 19 +++++++++++-- tests/state_test_util.go | 31 +++++++++++++-------- 5 files changed, 80 insertions(+), 27 deletions(-) diff --git a/state/executor.go b/state/executor.go index fba376da62..65bc5a1a86 100644 --- a/state/executor.go +++ b/state/executor.go @@ -485,10 +485,17 @@ func (t *Transition) subGasLimitPrice(msg *types.Transaction) error { } func (t *Transition) nonceCheck(msg *types.Transaction) error { - nonce := t.state.GetNonce(msg.From()) + currentNonce := t.state.GetNonce(msg.From()) - if nonce != msg.Nonce() { - return ErrNonceIncorrect + if msgNonce := msg.Nonce(); currentNonce < msgNonce { + return fmt.Errorf("%w: address %v, tx: %d state: %d", ErrNonceTooHigh, + msg.From(), msgNonce, currentNonce) + } else if currentNonce > msgNonce { + return fmt.Errorf("%w: address %v, tx: %d state: %d", ErrNonceTooLow, + msg.From(), msgNonce, currentNonce) + } else if currentNonce+1 < currentNonce { + return fmt.Errorf("%w: address %v, nonce: %d", ErrNonceMax, + msg.From(), currentNonce) } return nil @@ -536,7 +543,9 @@ func (t *Transition) checkDynamicFees(msg *types.Transaction) error { // surfacing of these errors reject the transaction thus not including it in the block var ( - ErrNonceIncorrect = errors.New("incorrect nonce") + ErrNonceTooLow = errors.New("nonce too low") + ErrNonceTooHigh = errors.New("nonce too high") + ErrNonceMax = errors.New("nonce has max value") ErrSenderNoEOA = errors.New("sender not an eoa") ErrNotEnoughFundsForGas = errors.New("not enough funds to cover gas costs") ErrBlockLimitReached = errors.New("gas limit reached in the pool") @@ -652,8 +661,13 @@ func (t *Transition) apply(msg *types.Transaction) (*runtime.ExecutionResult, er result = t.Call2(msg.From(), *(msg.To()), msg.Input(), value, gasLeft, initialAccessList) } + refundQuotient := LegacyRefundQuotient + if t.config.London { + refundQuotient = LondonRefundQuotient + } + refund := t.state.GetRefund() - result.UpdateGasUsed(msg.Gas(), refund) + result.UpdateGasUsed(msg.Gas(), refund, refundQuotient) if t.ctx.Tracer != nil { t.ctx.Tracer.TxEnd(result.GasLeft) @@ -681,10 +695,10 @@ func (t *Transition) apply(msg *types.Transaction) (*runtime.ExecutionResult, er // Burn some amount if the london hardfork is applied. // Basically, burn amount is just transferred to the current burn contract. - if t.config.London && msg.Type() != types.StateTx { - burnAmount := new(big.Int).Mul(new(big.Int).SetUint64(result.GasUsed), t.ctx.BaseFee) - t.state.AddBalance(t.ctx.BurnContract, burnAmount) - } + // if t.config.London && msg.Type() != types.StateTx { + // burnAmount := new(big.Int).Mul(new(big.Int).SetUint64(result.GasUsed), t.ctx.BaseFee) + // t.state.AddBalance(t.ctx.BurnContract, burnAmount) + // } // return gas to the pool t.addGasPool(result.GasLeft) @@ -969,6 +983,20 @@ func (t *Transition) applyCreate(c *runtime.Contract, host runtime.Host) *runtim } } + // Reject code starting with 0xEF if EIP-3541 is enabled. + if result.Err == nil && len(result.ReturnValue) >= 1 && result.ReturnValue[0] == 0xEF && t.config.London { + if err := t.state.RevertToSnapshot(snapshot); err != nil { + return &runtime.ExecutionResult{ + Err: err, + } + } + + return &runtime.ExecutionResult{ + GasLeft: 0, + Err: runtime.ErrInvalidCode, + } + } + gasCost := uint64(len(result.ReturnValue)) * 200 if result.GasLeft < gasCost { diff --git a/state/runtime/precompiled/base.go b/state/runtime/precompiled/base.go index 06f1e76429..9997d4ebdc 100644 --- a/state/runtime/precompiled/base.go +++ b/state/runtime/precompiled/base.go @@ -39,7 +39,11 @@ func (e *ecrecover) run(input []byte, caller types.Address, _ runtime.Host) ([]b return nil, nil } - pubKey, err := crypto.Ecrecover(input[:32], append(input[64:128], v)) + sig := make([]byte, 65) + copy(sig, input[64:128]) + sig[64] = v + + pubKey, err := crypto.Ecrecover(input[:32], sig) if err != nil { return nil, nil } diff --git a/state/runtime/runtime.go b/state/runtime/runtime.go index cfe865b2b9..fa4ecddbe2 100644 --- a/state/runtime/runtime.go +++ b/state/runtime/runtime.go @@ -119,11 +119,11 @@ func (r *ExecutionResult) Succeeded() bool { return r.Err == nil } func (r *ExecutionResult) Failed() bool { return r.Err != nil } func (r *ExecutionResult) Reverted() bool { return errors.Is(r.Err, ErrExecutionReverted) } -func (r *ExecutionResult) UpdateGasUsed(gasLimit uint64, refund uint64) { +func (r *ExecutionResult) UpdateGasUsed(gasLimit uint64, refund, refundQuotient uint64) { r.GasUsed = gasLimit - r.GasLeft // Refund can go up to half the gas used - if maxRefund := r.GasUsed / 2; refund > maxRefund { + if maxRefund := r.GasUsed / refundQuotient; refund > maxRefund { refund = maxRefund } @@ -143,6 +143,7 @@ var ( ErrUnauthorizedCaller = errors.New("unauthorized caller") ErrInvalidInputData = errors.New("invalid input data") ErrNotAuth = errors.New("not in allow list") + ErrInvalidCode = errors.New("invalid code: must not begin with 0xef") ) // StackUnderflowError wraps an evm error when the items on the stack less diff --git a/state/txn.go b/state/txn.go index c4578ae333..0eeb618ff7 100644 --- a/state/txn.go +++ b/state/txn.go @@ -16,6 +16,14 @@ import ( var emptyStateHash = types.StringToHash("0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421") +const ( + BerlinClearingRefund = uint64(15000) + LondonClearingRefund = uint64(4800) + + LegacyRefundQuotient = uint64(2) + LondonRefundQuotient = uint64(5) +) + type readSnapshot interface { GetStorage(addr types.Address, root types.Hash, key types.Hash) types.Hash GetAccount(addr types.Address) (*Account, error) @@ -247,13 +255,18 @@ func (txn *Txn) SetStorage( return runtime.StorageModified } + clearingRefund := BerlinClearingRefund + if config.London { + clearingRefund = LondonClearingRefund + } + if original == current { if original == types.ZeroHash { // create slot (2.1.1) return runtime.StorageAdded } if value == types.ZeroHash { // delete slot (2.1.2b) - txn.AddRefund(15000) + txn.AddRefund(clearingRefund) return runtime.StorageDeleted } @@ -263,9 +276,9 @@ func (txn *Txn) SetStorage( if original != types.ZeroHash { // Storage slot was populated before this transaction started if current == types.ZeroHash { // recreate slot (2.2.1.1) - txn.SubRefund(15000) + txn.SubRefund(clearingRefund) } else if value == types.ZeroHash { // delete slot (2.2.1.2) - txn.AddRefund(15000) + txn.AddRefund(clearingRefund) } } diff --git a/tests/state_test_util.go b/tests/state_test_util.go index 31ee9e21bd..eb9e9c021b 100644 --- a/tests/state_test_util.go +++ b/tests/state_test_util.go @@ -348,7 +348,13 @@ func (t *stTransaction) At(i indexes, baseFee *big.Int) (*types.Transaction, err }), nil } + txType := types.LegacyTx + if isDynamicTransaction { + txType = types.DynamicFeeTx + } + return types.NewTx(&types.MixedTxn{ + Type: txType, From: t.From, To: t.To, Nonce: t.Nonce, @@ -576,18 +582,19 @@ var Forks = map[string]*chain.Forks{ chain.Berlin: chain.NewFork(0), chain.London: chain.NewFork(5), }, - // "London": { - // chain.EIP3607: chain.NewFork(0), - // chain.Homestead: chain.NewFork(0), - // chain.EIP150: chain.NewFork(0), - // chain.EIP155: chain.NewFork(0), - // chain.EIP158: chain.NewFork(0), - // chain.Byzantium: chain.NewFork(0), - // chain.Constantinople: chain.NewFork(0), - // chain.Petersburg: chain.NewFork(0), - // chain.Istanbul: chain.NewFork(0), - // chain.London: chain.NewFork(0), - // }, + "London": { + chain.EIP3607: chain.NewFork(0), + chain.Homestead: chain.NewFork(0), + chain.EIP150: chain.NewFork(0), + chain.EIP155: chain.NewFork(0), + chain.EIP158: chain.NewFork(0), + chain.Byzantium: chain.NewFork(0), + chain.Constantinople: chain.NewFork(0), + chain.Petersburg: chain.NewFork(0), + chain.Istanbul: chain.NewFork(0), + chain.Berlin: chain.NewFork(0), + chain.London: chain.NewFork(0), + }, } func contains(l []string, name string) bool { From 7f0961b597f1aa6581f258fe6140773f451829f0 Mon Sep 17 00:00:00 2001 From: Goran Rojovic Date: Fri, 9 Feb 2024 16:55:18 +0100 Subject: [PATCH 02/16] SelfDestruct fix --- state/executor.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/state/executor.go b/state/executor.go index 65bc5a1a86..c004e007dd 100644 --- a/state/executor.go +++ b/state/executor.go @@ -1117,7 +1117,7 @@ func (t *Transition) GetNonce(addr types.Address) uint64 { } func (t *Transition) Selfdestruct(addr types.Address, beneficiary types.Address) { - if !t.state.HasSuicided(addr) { + if !t.config.London && !t.state.HasSuicided(addr) { t.state.AddRefund(24000) } From 2b206a43806cd56130944bbc75825942e7235a49 Mon Sep 17 00:00:00 2001 From: Goran Rojovic Date: Fri, 9 Feb 2024 20:46:59 +0100 Subject: [PATCH 03/16] Rebase fix --- tests/state_test_util.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/state_test_util.go b/tests/state_test_util.go index eb9e9c021b..41f1d1cc30 100644 --- a/tests/state_test_util.go +++ b/tests/state_test_util.go @@ -349,7 +349,7 @@ func (t *stTransaction) At(i indexes, baseFee *big.Int) (*types.Transaction, err } txType := types.LegacyTx - if isDynamicTransaction { + if isDynamiFeeTx { txType = types.DynamicFeeTx } From ed7ea05367581aa22d2531e4cd18ea62874549a8 Mon Sep 17 00:00:00 2001 From: Goran Rojovic Date: Sat, 10 Feb 2024 14:23:39 +0100 Subject: [PATCH 04/16] Journaling fix --- state/executor.go | 109 ++++++++++++++++++++++++------ state/executor_test.go | 3 +- state/runtime/evm/instructions.go | 29 +++----- state/runtime/journal.go | 28 +++++--- state/runtime/runtime.go | 26 +++---- state/transition_test.go | 7 +- 6 files changed, 129 insertions(+), 73 deletions(-) diff --git a/state/executor.go b/state/executor.go index c004e007dd..8a8efbe41d 100644 --- a/state/executor.go +++ b/state/executor.go @@ -5,6 +5,7 @@ import ( "fmt" "math" "math/big" + "sort" "github.com/hashicorp/go-hclog" @@ -88,6 +89,7 @@ func (e *Executor) WriteGenesis( gasPool: uint64(env.GasLimit), config: config, precompiles: precompiled.NewPrecompiled(), + journal: &runtime.Journal{}, } for addr, account := range alloc { @@ -221,6 +223,7 @@ func (e *Executor) BeginTxn( evm: evm.NewEVM(), precompiles: precompiled.NewPrecompiled(), PostHook: e.PostHook, + journal: &runtime.Journal{}, } // enable contract deployment allow list (if any) @@ -283,6 +286,12 @@ type Transition struct { txnBlockList *addresslist.AddressList bridgeAllowList *addresslist.AddressList bridgeBlockList *addresslist.AddressList + + // journaling + journal *runtime.Journal + journalRevisions []runtime.JournalRevision + + accessList *runtime.AccessList } func NewTransition(config chain.ForksInTime, snap Snapshot, radix *Txn) *Transition { @@ -292,6 +301,7 @@ func NewTransition(config chain.ForksInTime, snap Snapshot, radix *Txn) *Transit snap: snap, evm: evm.NewEVM(), precompiles: precompiled.NewPrecompiled(), + journal: &runtime.Journal{}, } } @@ -448,11 +458,11 @@ func (t *Transition) Apply(msg *types.Transaction) (*runtime.ExecutionResult, er t.state.GetCodeHash(sender).String()) } - s := t.state.Snapshot() + s := t.Snapshot() result, err := t.apply(msg) if err != nil { - if revertErr := t.state.RevertToSnapshot(s); revertErr != nil { + if revertErr := t.RevertToSnapshot(s); revertErr != nil { return nil, revertErr } } @@ -650,15 +660,17 @@ func (t *Transition) apply(msg *types.Transaction) (*runtime.ExecutionResult, er initialAccessList.PrepareAccessList(msg.From(), msg.To(), t.precompiles.Addrs, msg.AccessList()) } + t.accessList = initialAccessList + var result *runtime.ExecutionResult if msg.IsContractCreation() { - result = t.Create2(msg.From(), msg.Input(), value, gasLeft, initialAccessList) + result = t.Create2(msg.From(), msg.Input(), value, gasLeft) } else { if err := t.state.IncrNonce(msg.From()); err != nil { return nil, err } - result = t.Call2(msg.From(), *(msg.To()), msg.Input(), value, gasLeft, initialAccessList) + result = t.Call2(msg.From(), *(msg.To()), msg.Input(), value, gasLeft) } refundQuotient := LegacyRefundQuotient @@ -693,6 +705,8 @@ func (t *Transition) apply(msg *types.Transaction) (*runtime.ExecutionResult, er coinbaseFee := new(big.Int).Mul(new(big.Int).SetUint64(result.GasUsed), effectiveTip) t.state.AddBalance(t.ctx.Coinbase, coinbaseFee) + // nolint:godox + // TODO - burning of base fee should not be done in the EVM // Burn some amount if the london hardfork is applied. // Basically, burn amount is just transferred to the current burn contract. // if t.config.London && msg.Type() != types.StateTx { @@ -711,10 +725,9 @@ func (t *Transition) Create2( code []byte, value *big.Int, gas uint64, - initialAccessList *runtime.AccessList, ) *runtime.ExecutionResult { address := crypto.CreateAddress(caller, t.state.GetNonce(caller)) - contract := runtime.NewContractCreation(1, caller, caller, address, value, gas, code, initialAccessList) + contract := runtime.NewContractCreation(1, caller, caller, address, value, gas, code) return t.applyCreate(contract, t) } @@ -725,9 +738,8 @@ func (t *Transition) Call2( input []byte, value *big.Int, gas uint64, - initialAccessList *runtime.AccessList, ) *runtime.ExecutionResult { - c := runtime.NewContractCall(1, caller, caller, to, value, gas, t.state.GetCode(to), input, initialAccessList) + c := runtime.NewContractCall(1, caller, caller, to, value, gas, t.state.GetCode(to), input) return t.applyCall(c, runtime.Call, t) } @@ -816,7 +828,7 @@ func (t *Transition) applyCall( } } - snapshot := t.state.Snapshot() + snapshot := t.Snapshot() t.state.TouchAccount(c.Address) if callType == runtime.Call { @@ -835,9 +847,7 @@ func (t *Transition) applyCall( result = t.run(c, host) if result.Failed() { - c.RevertJournal() - - if err := t.state.RevertToSnapshot(snapshot); err != nil { + if err := t.RevertToSnapshot(snapshot); err != nil { return &runtime.ExecutionResult{ GasLeft: c.Gas, Err: err, @@ -882,8 +892,7 @@ func (t *Transition) applyCreate(c *runtime.Contract, host runtime.Host) *runtim // 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.Berlin { - c.AddToJournal(&runtime.AccessListAddAccountChange{Address: c.Address}) - c.AccessList.AddAddress(c.Address) + t.AddAddressToAccessList(c.Address) } // Check if there is a collision and the address already exists @@ -895,7 +904,7 @@ func (t *Transition) applyCreate(c *runtime.Contract, host runtime.Host) *runtim } // Take snapshot of the current state - snapshot := t.state.Snapshot() + snapshot := t.Snapshot() if t.config.EIP158 { // Force the creation of the account @@ -958,9 +967,7 @@ func (t *Transition) applyCreate(c *runtime.Contract, host runtime.Host) *runtim result = t.run(c, host) if result.Failed() { - c.RevertJournal() - - if err := t.state.RevertToSnapshot(snapshot); err != nil { + if err := t.RevertToSnapshot(snapshot); err != nil { return &runtime.ExecutionResult{ Err: err, } @@ -971,7 +978,7 @@ func (t *Transition) applyCreate(c *runtime.Contract, host runtime.Host) *runtim if t.config.EIP158 && len(result.ReturnValue) > SpuriousDragonMaxCodeSize { // Contract size exceeds 'SpuriousDragon' size limit - if err := t.state.RevertToSnapshot(snapshot); err != nil { + if err := t.RevertToSnapshot(snapshot); err != nil { return &runtime.ExecutionResult{ Err: err, } @@ -985,7 +992,7 @@ func (t *Transition) applyCreate(c *runtime.Contract, host runtime.Host) *runtim // Reject code starting with 0xEF if EIP-3541 is enabled. if result.Err == nil && len(result.ReturnValue) >= 1 && result.ReturnValue[0] == 0xEF && t.config.London { - if err := t.state.RevertToSnapshot(snapshot); err != nil { + if err := t.RevertToSnapshot(snapshot); err != nil { return &runtime.ExecutionResult{ Err: err, } @@ -1005,7 +1012,7 @@ func (t *Transition) applyCreate(c *runtime.Contract, host runtime.Host) *runtim // Out of gas creating the contract if t.config.Homestead { - if err := t.state.RevertToSnapshot(snapshot); err != nil { + if err := t.RevertToSnapshot(snapshot); err != nil { return &runtime.ExecutionResult{ Err: err, } @@ -1319,3 +1326,63 @@ func (t *Transition) captureCallEnd(c *runtime.Contract, result *runtime.Executi result.Err, ) } + +func (t *Transition) AddToJournal(j runtime.JournalEntry) { + t.journal.Append(j) +} + +func (t *Transition) Snapshot() int { + snapshot := t.state.Snapshot() + t.journalRevisions = append(t.journalRevisions, runtime.JournalRevision{ID: snapshot, Index: t.journal.Len()}) + + return snapshot +} + +func (t *Transition) RevertToSnapshot(snapshot int) error { + if err := t.state.RevertToSnapshot(snapshot); err != nil { + return err + } + + // Find the snapshot in the stack of valid snapshots. + idx := sort.Search(len(t.journalRevisions), func(i int) bool { + return t.journalRevisions[i].ID >= snapshot + }) + + if idx == len(t.journalRevisions) || t.journalRevisions[idx].ID != snapshot { + panic(fmt.Errorf("journal revision id %v cannot be reverted", snapshot)) + } + + journalIndex := t.journalRevisions[idx].Index + + // Replay the journal to undo changes and remove invalidated snapshots + t.journal.Revert(t, journalIndex) + t.journalRevisions = t.journalRevisions[:idx] + + return nil +} + +func (t *Transition) AddSlotToAccessList(addr types.Address, slot types.Hash) { + t.journal.Append(&runtime.AccessListAddSlotChange{Address: addr, Slot: slot}) + t.accessList.AddSlot(addr, slot) +} + +func (t *Transition) AddAddressToAccessList(addr types.Address) { + t.journal.Append(&runtime.AccessListAddAccountChange{Address: addr}) + t.accessList.AddAddress(addr) +} + +func (t *Transition) ContainsAccessListAddress(addr types.Address) bool { + return t.accessList.ContainsAddress(addr) +} + +func (t *Transition) ContainsAccessListSlot(addr types.Address, slot types.Hash) (bool, bool) { + return t.accessList.Contains(addr, slot) +} + +func (t *Transition) DeleteAccessListAddress(addr types.Address) { + t.accessList.DeleteAddress(addr) +} + +func (t *Transition) DeleteAccessListSlot(addr types.Address, slot types.Hash) { + t.accessList.DeleteSlot(addr, slot) +} diff --git a/state/executor_test.go b/state/executor_test.go index 0ea86367dd..870afa636e 100644 --- a/state/executor_test.go +++ b/state/executor_test.go @@ -262,8 +262,9 @@ func Test_Transition_EIP2929(t *testing.T) { transition := NewTransition(enabledForks, state, txn) initialAccessList := runtime.NewAccessList() initialAccessList.PrepareAccessList(transition.ctx.Origin, &addr, transition.precompiles.Addrs, nil) + transition.accessList = initialAccessList - result := transition.Call2(transition.ctx.Origin, addr, nil, big.NewInt(0), uint64(1000000), initialAccessList) + 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/evm/instructions.go b/state/runtime/evm/instructions.go index 2592e0aabe..1cf114bb43 100644 --- a/state/runtime/evm/instructions.go +++ b/state/runtime/evm/instructions.go @@ -30,13 +30,12 @@ var ( func (c *state) calculateGasForEIP2929(addr types.Address) uint64 { var gas uint64 - if c.msg.AccessList.ContainsAddress(addr) { + if c.host.ContainsAccessListAddress(addr) { gas = WarmStorageReadCostEIP2929 } else { gas = ColdAccountAccessCostEIP2929 - c.msg.AddToJournal(&runtime.AccessListAddAccountChange{Address: addr}) - c.msg.AccessList.AddAddress(addr) + c.host.AddAddressToAccessList(addr) } return gas @@ -487,10 +486,10 @@ func opSload(c *state) { var gas uint64 if c.config.Berlin { - if _, slotPresent := c.msg.AccessList.Contains(c.msg.Address, bigToHash(loc)); !slotPresent { + if _, slotPresent := c.host.ContainsAccessListSlot(c.msg.Address, bigToHash(loc)); !slotPresent { gas = ColdStorageReadCostEIP2929 - c.addAccessListSlot(c.msg.Address, bigToHash(loc)) + c.host.AddSlotToAccessList(c.msg.Address, bigToHash(loc)) } else { gas = WarmStorageReadCostEIP2929 } @@ -533,10 +532,10 @@ func opSStore(c *state) { cost := uint64(0) if c.config.Berlin { - if _, slotPresent := c.msg.AccessList.Contains(c.msg.Address, key); !slotPresent { + if _, slotPresent := c.host.ContainsAccessListSlot(c.msg.Address, key); !slotPresent { cost = ColdStorageReadCostEIP2929 - c.addAccessListSlot(c.msg.Address, key) + c.host.AddSlotToAccessList(c.msg.Address, key) } } @@ -1006,10 +1005,10 @@ func opSelfDestruct(c *state) { } // EIP 2929 gas - if c.config.Berlin && !c.msg.AccessList.ContainsAddress(address) { + if c.config.Berlin && !c.host.ContainsAccessListAddress(address) { gas += ColdAccountAccessCostEIP2929 - c.addAccessListAddress(address) + c.host.AddAddressToAccessList(address) } if !c.consumeGas(gas) { @@ -1380,7 +1379,6 @@ func (c *state) buildCallContract(op OpCode) (*runtime.Contract, uint64, uint64, gas, c.host.GetCode(addr), args, - c.msg.AccessList, ) if op == STATICCALL || parent.msg.Static { @@ -1480,22 +1478,11 @@ func (c *state) buildCreateContract(op OpCode) (*runtime.Contract, error) { value, gas, input, - c.msg.AccessList, ) return contract, nil } -func (c *state) addAccessListSlot(address types.Address, slot types.Hash) { - c.msg.AddToJournal(&runtime.AccessListAddSlotChange{Address: address, Slot: slot}) - c.msg.AccessList.AddSlot(address, slot) -} - -func (c *state) addAccessListAddress(address types.Address) { - c.msg.AddToJournal(&runtime.AccessListAddAccountChange{Address: address}) - c.msg.AccessList.AddAddress(address) -} - func opHalt(op OpCode) instruction { return func(c *state) { if op == REVERT && !c.config.Byzantium { diff --git a/state/runtime/journal.go b/state/runtime/journal.go index 2aac5e4f4e..86048eaed3 100644 --- a/state/runtime/journal.go +++ b/state/runtime/journal.go @@ -4,8 +4,13 @@ import ( "github.com/0xPolygon/polygon-edge/types" ) +type JournalRevision struct { + ID int + Index int +} + type JournalEntry interface { - Revert(c *Contract) + Revert(host Host) } type Journal struct { @@ -16,12 +21,17 @@ func (j *Journal) Append(entry JournalEntry) { j.entries = append(j.entries, entry) } -func (j *Journal) Revert(c *Contract) { - for i := len(j.entries) - 1; i >= 0; i-- { - j.entries[i].Revert(c) +func (j *Journal) Revert(host Host, snapshot int) { + for i := len(j.entries) - 1; i >= snapshot; i-- { + // Undo the changes made by the operation + j.entries[i].Revert(host) } - j.entries = j.entries[:0] + j.entries = j.entries[:snapshot] +} + +func (j *Journal) Len() int { + return len(j.entries) } type ( @@ -36,12 +46,12 @@ type ( var _ JournalEntry = (*AccessListAddAccountChange)(nil) -func (ch AccessListAddAccountChange) Revert(c *Contract) { - c.AccessList.DeleteAddress(ch.Address) +func (ch AccessListAddAccountChange) Revert(host Host) { + host.DeleteAccessListAddress(ch.Address) } var _ JournalEntry = (*AccessListAddSlotChange)(nil) -func (ch AccessListAddSlotChange) Revert(c *Contract) { - c.AccessList.DeleteSlot(ch.Address, ch.Slot) +func (ch AccessListAddSlotChange) Revert(host Host) { + host.DeleteAccessListSlot(ch.Address, ch.Slot) } diff --git a/state/runtime/runtime.go b/state/runtime/runtime.go index fa4ecddbe2..c180c7c777 100644 --- a/state/runtime/runtime.go +++ b/state/runtime/runtime.go @@ -80,6 +80,12 @@ type Host interface { Transfer(from types.Address, to types.Address, amount *big.Int) error GetTracer() VMTracer GetRefund() uint64 + AddSlotToAccessList(addr types.Address, slot types.Hash) + AddAddressToAccessList(addr types.Address) + ContainsAccessListAddress(addr types.Address) bool + ContainsAccessListSlot(addr types.Address, slot types.Hash) (bool, bool) + DeleteAccessListAddress(addr types.Address) + DeleteAccessListSlot(addr types.Address, slot types.Hash) } type VMTracer interface { @@ -199,9 +205,6 @@ type Contract struct { Input []byte Gas uint64 Static bool - AccessList *AccessList - - Journal *Journal } func NewContract( @@ -212,7 +215,6 @@ func NewContract( value *big.Int, gas uint64, code []byte, - accessList *AccessList, ) *Contract { f := &Contract{ Caller: from, @@ -223,8 +225,6 @@ func NewContract( Value: value, Code: code, Depth: depth, - AccessList: accessList, - Journal: &Journal{}, } return f @@ -238,9 +238,8 @@ func NewContractCreation( value *big.Int, gas uint64, code []byte, - accessList *AccessList, ) *Contract { - c := NewContract(depth, origin, from, to, value, gas, code, accessList) + c := NewContract(depth, origin, from, to, value, gas, code) return c } @@ -254,18 +253,9 @@ func NewContractCall( gas uint64, code []byte, input []byte, - accessList *AccessList, ) *Contract { - c := NewContract(depth, origin, from, to, value, gas, code, accessList) + c := NewContract(depth, origin, from, to, value, gas, code) c.Input = input return c } - -func (c *Contract) RevertJournal() { - c.Journal.Revert(c) -} - -func (c *Contract) AddToJournal(e JournalEntry) { - c.Journal.Append(e) -} diff --git a/state/transition_test.go b/state/transition_test.go index 5bfba6edff..c683538071 100644 --- a/state/transition_test.go +++ b/state/transition_test.go @@ -16,9 +16,10 @@ func newTestTransition(preState map[types.Address]*PreState) *Transition { } return &Transition{ - logger: hclog.NewNullLogger(), - state: newTestTxn(preState), - ctx: runtime.TxContext{BaseFee: big.NewInt(0)}, + logger: hclog.NewNullLogger(), + state: newTestTxn(preState), + ctx: runtime.TxContext{BaseFee: big.NewInt(0)}, + journal: &runtime.Journal{}, } } From 04d5457acd18ae591e9951183b70edbb39dafa28 Mon Sep 17 00:00:00 2001 From: Goran Rojovic Date: Sat, 10 Feb 2024 16:30:55 +0100 Subject: [PATCH 05/16] Tests fix --- consensus/polybft/blockchain_wrapper.go | 2 - consensus/polybft/contracts_initializer.go | 3 +- consensus/polybft/sc_integration_test.go | 21 ++++----- consensus/polybft/system_state_test.go | 5 +- helper/predeployment/predeployment.go | 1 - state/executor.go | 1 + state/runtime/evm/evm_fuzz_test.go | 9 ++++ state/runtime/evm/evm_test.go | 28 ++++++++++- state/runtime/evm/instructions_test.go | 47 +++++++++++++++---- .../precompiled/native_transfer_test.go | 28 +++++++++++ 10 files changed, 114 insertions(+), 31 deletions(-) diff --git a/consensus/polybft/blockchain_wrapper.go b/consensus/polybft/blockchain_wrapper.go index dfee8b4761..902f2bca19 100644 --- a/consensus/polybft/blockchain_wrapper.go +++ b/consensus/polybft/blockchain_wrapper.go @@ -12,7 +12,6 @@ import ( "github.com/0xPolygon/polygon-edge/consensus" "github.com/0xPolygon/polygon-edge/contracts" "github.com/0xPolygon/polygon-edge/state" - "github.com/0xPolygon/polygon-edge/state/runtime" "github.com/0xPolygon/polygon-edge/types" "github.com/umbracle/ethgo" "github.com/umbracle/ethgo/contract" @@ -219,7 +218,6 @@ func (s *stateProvider) Call(addr ethgo.Address, input []byte, opts *contract.Ca input, big.NewInt(0), 10000000, - runtime.NewAccessList(), ) if result.Failed() { return nil, result.Err diff --git a/consensus/polybft/contracts_initializer.go b/consensus/polybft/contracts_initializer.go index 810421dc1d..7d70f59646 100644 --- a/consensus/polybft/contracts_initializer.go +++ b/consensus/polybft/contracts_initializer.go @@ -10,7 +10,6 @@ import ( "github.com/0xPolygon/polygon-edge/contracts" "github.com/0xPolygon/polygon-edge/helper/hex" "github.com/0xPolygon/polygon-edge/state" - "github.com/0xPolygon/polygon-edge/state/runtime" "github.com/0xPolygon/polygon-edge/types" "github.com/umbracle/ethgo/abi" ) @@ -413,7 +412,7 @@ func approveEpochManagerAsSpender(polyBFTConfig PolyBFTConfig, transition *state // callContract calls given smart contract function, encoded in input parameter func callContract(from, to types.Address, input []byte, contractName string, transition *state.Transition) error { - result := transition.Call2(from, to, input, big.NewInt(0), contractCallGasLimit, runtime.NewAccessList()) + result := transition.Call2(from, to, input, big.NewInt(0), contractCallGasLimit) if result.Failed() { if result.Reverted() { if revertReason, err := abi.UnpackRevertError(result.ReturnValue); err == nil { diff --git a/consensus/polybft/sc_integration_test.go b/consensus/polybft/sc_integration_test.go index 340cf04aab..7d9df17c20 100644 --- a/consensus/polybft/sc_integration_test.go +++ b/consensus/polybft/sc_integration_test.go @@ -23,7 +23,6 @@ import ( "github.com/0xPolygon/polygon-edge/helper/common" "github.com/0xPolygon/polygon-edge/helper/hex" "github.com/0xPolygon/polygon-edge/state" - "github.com/0xPolygon/polygon-edge/state/runtime" "github.com/0xPolygon/polygon-edge/types" ) @@ -58,7 +57,7 @@ func TestIntegration_PerformExit(t *testing.T) { input, err := abi.GetMethod(function).Encode(args) require.NoError(t, err) - result := transition.Call2(deployerAddress, addr, input, big.NewInt(0), gasLimit, runtime.NewAccessList()) + result := transition.Call2(deployerAddress, addr, input, big.NewInt(0), gasLimit) require.True(t, result.Succeeded()) return result.ReturnValue @@ -119,7 +118,7 @@ func TestIntegration_PerformExit(t *testing.T) { }).EncodeAbi() require.NoError(t, err) - result := transition.Call2(deployerAddress, rootERC20Addr, mintInput, nil, gasLimit, runtime.NewAccessList()) + result := transition.Call2(deployerAddress, rootERC20Addr, mintInput, nil, gasLimit) require.NoError(t, result.Err) // approve @@ -129,7 +128,7 @@ func TestIntegration_PerformExit(t *testing.T) { }).EncodeAbi() require.NoError(t, err) - result = transition.Call2(senderAddress, rootERC20Addr, approveInput, big.NewInt(0), gasLimit, runtime.NewAccessList()) + result = transition.Call2(senderAddress, rootERC20Addr, approveInput, big.NewInt(0), gasLimit) require.NoError(t, result.Err) // deposit @@ -141,7 +140,7 @@ func TestIntegration_PerformExit(t *testing.T) { require.NoError(t, err) // send sync events to childchain so that receiver can obtain tokens - result = transition.Call2(senderAddress, rootERC20PredicateAddr, depositInput, big.NewInt(0), gasLimit, runtime.NewAccessList()) + result = transition.Call2(senderAddress, rootERC20PredicateAddr, depositInput, big.NewInt(0), gasLimit) require.NoError(t, result.Err) // simulate withdrawal from childchain to rootchain @@ -231,7 +230,7 @@ func TestIntegration_PerformExit(t *testing.T) { submitCheckpointEncoded, err := cm.abiEncodeCheckpointBlock(blockNumber, blockHash, extra, accSet) require.NoError(t, err) - result = transition.Call2(senderAddress, checkpointManagerAddr, submitCheckpointEncoded, big.NewInt(0), gasLimit, runtime.NewAccessList()) + result = transition.Call2(senderAddress, checkpointManagerAddr, submitCheckpointEncoded, big.NewInt(0), gasLimit) require.NoError(t, result.Err) require.Equal(t, getField(checkpointManagerAddr, contractsapi.CheckpointManager.Abi, "currentCheckpointBlockNumber")[31], uint8(1)) @@ -256,7 +255,7 @@ func TestIntegration_PerformExit(t *testing.T) { }).EncodeAbi() require.NoError(t, err) - result = transition.Call2(senderAddress, exitHelperContractAddress, exitFnInput, big.NewInt(0), gasLimit, runtime.NewAccessList()) + result = transition.Call2(senderAddress, exitHelperContractAddress, exitFnInput, big.NewInt(0), gasLimit) require.NoError(t, result.Err) // check that first exit event is processed @@ -383,7 +382,7 @@ func TestIntegration_CommitEpoch(t *testing.T) { require.NoError(t, err) // call commit epoch - result := transition.Call2(contracts.SystemCaller, contracts.EpochManagerContract, input, big.NewInt(0), 10000000000, runtime.NewAccessList()) + result := transition.Call2(contracts.SystemCaller, contracts.EpochManagerContract, input, big.NewInt(0), 10000000000) require.NoError(t, result.Err) t.Logf("Number of validators %d on commit epoch, Gas used %+v\n", accSet.Len(), result.GasUsed) @@ -392,7 +391,7 @@ func TestIntegration_CommitEpoch(t *testing.T) { require.NoError(t, err) // call commit epoch - result = transition.Call2(contracts.SystemCaller, contracts.EpochManagerContract, input, big.NewInt(0), 10000000000, runtime.NewAccessList()) + result = transition.Call2(contracts.SystemCaller, contracts.EpochManagerContract, input, big.NewInt(0), 10000000000) require.NoError(t, result.Err) t.Logf("Number of validators %d on commit epoch, Gas used %+v\n", accSet.Len(), result.GasUsed) } @@ -402,14 +401,14 @@ func deployAndInitContract(t *testing.T, transition *state.Transition, bytecode initCallback func() ([]byte, error)) types.Address { t.Helper() - deployResult := transition.Create2(sender, bytecode, big.NewInt(0), 1e9, runtime.NewAccessList()) + deployResult := transition.Create2(sender, bytecode, big.NewInt(0), 1e9) assert.NoError(t, deployResult.Err) if initCallback != nil { initInput, err := initCallback() require.NoError(t, err) - result := transition.Call2(sender, deployResult.Address, initInput, big.NewInt(0), 1e9, runtime.NewAccessList()) + result := transition.Call2(sender, deployResult.Address, initInput, big.NewInt(0), 1e9) require.NoError(t, result.Err) } diff --git a/consensus/polybft/system_state_test.go b/consensus/polybft/system_state_test.go index 8be1195954..ea001fe48b 100644 --- a/consensus/polybft/system_state_test.go +++ b/consensus/polybft/system_state_test.go @@ -9,7 +9,6 @@ import ( "github.com/0xPolygon/polygon-edge/contracts" "github.com/0xPolygon/polygon-edge/state" itrie "github.com/0xPolygon/polygon-edge/state/immutable-trie" - "github.com/0xPolygon/polygon-edge/state/runtime" "github.com/0xPolygon/polygon-edge/types" "github.com/hashicorp/go-hclog" "github.com/stretchr/testify/assert" @@ -46,7 +45,7 @@ func TestSystemState_GetNextCommittedIndex(t *testing.T) { transition := newTestTransition(t, nil) // deploy a contract - result := transition.Create2(types.Address{}, bin, big.NewInt(0), 1000000000, runtime.NewAccessList()) + result := transition.Create2(types.Address{}, bin, big.NewInt(0), 1000000000) assert.NoError(t, result.Err) provider := &stateProvider{ @@ -93,7 +92,7 @@ func TestSystemState_GetEpoch(t *testing.T) { transition := newTestTransition(t, nil) // deploy a contract - result := transition.Create2(types.Address{}, bin, big.NewInt(0), 1000000000, runtime.NewAccessList()) + result := transition.Create2(types.Address{}, bin, big.NewInt(0), 1000000000) assert.NoError(t, result.Err) provider := &stateProvider{ diff --git a/helper/predeployment/predeployment.go b/helper/predeployment/predeployment.go index a655b79d27..56a56553ca 100644 --- a/helper/predeployment/predeployment.go +++ b/helper/predeployment/predeployment.go @@ -61,7 +61,6 @@ func getPredeployAccount(address types.Address, input []byte, big.NewInt(0), math.MaxInt64, input, - runtime.NewAccessList(), ) // Enable all forks diff --git a/state/executor.go b/state/executor.go index 8a8efbe41d..ba327989c3 100644 --- a/state/executor.go +++ b/state/executor.go @@ -224,6 +224,7 @@ func (e *Executor) BeginTxn( precompiles: precompiled.NewPrecompiled(), PostHook: e.PostHook, journal: &runtime.Journal{}, + accessList: runtime.NewAccessList(), } // enable contract deployment allow list (if any) diff --git a/state/runtime/evm/evm_fuzz_test.go b/state/runtime/evm/evm_fuzz_test.go index 933768b993..ed611e443f 100644 --- a/state/runtime/evm/evm_fuzz_test.go +++ b/state/runtime/evm/evm_fuzz_test.go @@ -138,6 +138,15 @@ func (m *mockHostF) GetRefund() uint64 { return m.refund } +func (m *mockHostF) AddSlotToAccessList(addr types.Address, slot types.Hash) {} +func (m *mockHostF) AddAddressToAccessList(addr types.Address) {} +func (m *mockHostF) ContainsAccessListAddress(addr types.Address) bool { return false } +func (m *mockHostF) ContainsAccessListSlot(addr types.Address, slot types.Hash) (bool, bool) { + return false, false +} +func (m *mockHostF) DeleteAccessListAddress(addr types.Address) {} +func (m *mockHostF) DeleteAccessListSlot(addr types.Address, slot types.Hash) {} + func FuzzTestEVM(f *testing.F) { seed := []byte{ PUSH1, 0x01, PUSH1, 0x02, ADD, diff --git a/state/runtime/evm/evm_test.go b/state/runtime/evm/evm_test.go index 2613667a21..6b20b48e29 100644 --- a/state/runtime/evm/evm_test.go +++ b/state/runtime/evm/evm_test.go @@ -21,7 +21,6 @@ func newMockContract(value *big.Int, gas uint64, code []byte) *runtime.Contract value, gas, code, - runtime.NewAccessList(), ) } @@ -30,7 +29,8 @@ func newMockContract(value *big.Int, gas uint64, code []byte) *runtime.Contract type mockHost struct { mock.Mock - tracer runtime.VMTracer + tracer runtime.VMTracer + accessList *runtime.AccessList } func (m *mockHost) AccountExists(addr types.Address) bool { @@ -136,6 +136,30 @@ func (m *mockHost) GetRefund() uint64 { panic("Not implemented in tests") //nolint:gocritic } +func (m *mockHost) AddSlotToAccessList(addr types.Address, slot types.Hash) { + m.accessList.AddSlot(addr, slot) +} + +func (m *mockHost) AddAddressToAccessList(addr types.Address) { + m.accessList.AddAddress(addr) +} + +func (m *mockHost) ContainsAccessListAddress(addr types.Address) bool { + return m.accessList.ContainsAddress(addr) +} + +func (m *mockHost) ContainsAccessListSlot(addr types.Address, slot types.Hash) (bool, bool) { + return m.accessList.Contains(addr, slot) +} + +func (m *mockHost) DeleteAccessListAddress(addr types.Address) { + m.accessList.DeleteAddress(addr) +} + +func (m *mockHost) DeleteAccessListSlot(addr types.Address, slot types.Hash) { + m.accessList.DeleteSlot(addr, slot) +} + func TestRun(t *testing.T) { t.Parallel() diff --git a/state/runtime/evm/instructions_test.go b/state/runtime/evm/instructions_test.go index 39c269f0ea..82592593d7 100644 --- a/state/runtime/evm/instructions_test.go +++ b/state/runtime/evm/instructions_test.go @@ -1487,7 +1487,6 @@ func Test_opSload(t *testing.T) { op: SLOAD, contract: &runtime.Contract{ Address: address1, - Journal: &runtime.Journal{}, }, config: &chain.ForksInTime{ Berlin: true, @@ -1525,6 +1524,9 @@ func Test_opSload(t *testing.T) { key1: val1, }, }, + mockHost: mockHost{ + accessList: runtime.NewAccessList(), + }, }, }, { @@ -1532,7 +1534,6 @@ func Test_opSload(t *testing.T) { op: SLOAD, contract: &runtime.Contract{ Address: address1, - Journal: &runtime.Journal{}, }, config: &chain.ForksInTime{ Berlin: true, @@ -1574,6 +1575,9 @@ func Test_opSload(t *testing.T) { key1: val1, }, }, + mockHost: mockHost{ + accessList: runtime.NewAccessList(), + }, }, }, { @@ -1581,7 +1585,6 @@ func Test_opSload(t *testing.T) { op: SLOAD, contract: &runtime.Contract{ Address: address1, - Journal: &runtime.Journal{}, }, config: &chain.ForksInTime{ Berlin: false, @@ -1616,6 +1619,9 @@ func Test_opSload(t *testing.T) { key1: val1, }, }, + mockHost: mockHost{ + accessList: runtime.NewAccessList(), + }, }, }, } @@ -1634,8 +1640,8 @@ func Test_opSload(t *testing.T) { s.stack = tt.initState.stack s.memory = tt.initState.memory s.config = tt.config + tt.mockHost.accessList = tt.initState.accessList s.host = tt.mockHost - tt.contract.AccessList = tt.initState.accessList opSload(s) @@ -1643,7 +1649,7 @@ func Test_opSload(t *testing.T) { 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, tt.contract.AccessList, "accesslist in state after execution is not correct") + assert.Equal(t, tt.resultState.accessList, tt.mockHost.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") }) @@ -1711,6 +1717,9 @@ func TestCreate(t *testing.T) { GasLeft: 500, GasUsed: 500, }, + mockHost: mockHost{ + accessList: runtime.NewAccessList(), + }, }, }, { @@ -1749,7 +1758,11 @@ func TestCreate(t *testing.T) { stop: true, err: errWriteProtection, }, - mockHost: &mockHostForInstructions{}, + mockHost: &mockHostForInstructions{ + mockHost: mockHost{ + accessList: runtime.NewAccessList(), + }, + }, }, { name: "should throw errOpCodeNotFound when op is CREATE2 and config.Constantinople is disabled", @@ -1787,7 +1800,11 @@ func TestCreate(t *testing.T) { stop: true, err: errOpCodeNotFound, }, - mockHost: &mockHostForInstructions{}, + mockHost: &mockHostForInstructions{ + mockHost: mockHost{ + accessList: runtime.NewAccessList(), + }, + }, }, { name: "should set zero address if op is CREATE and contract call throws ErrCodeStoreOutOfGas", @@ -1835,6 +1852,9 @@ func TestCreate(t *testing.T) { GasLeft: 1000, Err: runtime.ErrCodeStoreOutOfGas, }, + mockHost: mockHost{ + accessList: runtime.NewAccessList(), + }, }, }, { @@ -1883,6 +1903,9 @@ func TestCreate(t *testing.T) { GasLeft: 1000, Err: errRevert, }, + mockHost: mockHost{ + accessList: runtime.NewAccessList(), + }, }, }, { @@ -1934,6 +1957,9 @@ func TestCreate(t *testing.T) { GasLeft: 0, Err: runtime.ErrCodeStoreOutOfGas, }, + mockHost: mockHost{ + accessList: runtime.NewAccessList(), + }, }, }, } @@ -2284,9 +2310,7 @@ func Test_opCall(t *testing.T) { name: "should not copy result into memory if outSize is 0", op: STATICCALL, contract: &runtime.Contract{ - Static: true, - Journal: &runtime.Journal{}, - AccessList: runtime.NewAccessList(), + Static: true, }, config: allEnabledForks, initState: &state{ @@ -2311,6 +2335,9 @@ func Test_opCall(t *testing.T) { callxResult: &runtime.ExecutionResult{ ReturnValue: []byte{0x03}, }, + mockHost: mockHost{ + accessList: runtime.NewAccessList(), + }, }, }, // { diff --git a/state/runtime/precompiled/native_transfer_test.go b/state/runtime/precompiled/native_transfer_test.go index a6309f6d42..4bdf95b4fa 100644 --- a/state/runtime/precompiled/native_transfer_test.go +++ b/state/runtime/precompiled/native_transfer_test.go @@ -171,6 +171,34 @@ func (d dummyHost) GetNonce(addr types.Address) uint64 { return 0 } +func (d *dummyHost) AddSlotToAccessList(addr types.Address, slot types.Hash) { + d.t.Fatalf("AddSlotToAccessList is not implemented") +} + +func (d *dummyHost) AddAddressToAccessList(addr types.Address) { + d.t.Fatalf("AddAddressToAccessList is not implemented") +} + +func (d *dummyHost) ContainsAccessListAddress(addr types.Address) bool { + d.t.Fatalf("ContainsAccessListAddress is not implemented") + + return false +} + +func (d *dummyHost) ContainsAccessListSlot(addr types.Address, slot types.Hash) (bool, bool) { + d.t.Fatalf("ContainsAccessListSlot is not implemented") + + return false, false +} + +func (d *dummyHost) DeleteAccessListAddress(addr types.Address) { + d.t.Fatalf("DeleteAccessListAddress is not implemented") +} + +func (d *dummyHost) DeleteAccessListSlot(addr types.Address, slot types.Hash) { + d.t.Fatalf("DeleteAccessListSlot is not implemented") +} + func (d dummyHost) Transfer(from types.Address, to types.Address, amount *big.Int) error { if d.balances == nil { d.balances = map[types.Address]*big.Int{} From e14bb8859fac6473cd24c00749056e31f67876a1 Mon Sep 17 00:00:00 2001 From: Goran Rojovic Date: Sun, 11 Feb 2024 00:33:37 +0100 Subject: [PATCH 06/16] e2e fix --- state/executor.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/state/executor.go b/state/executor.go index ba327989c3..5adde83079 100644 --- a/state/executor.go +++ b/state/executor.go @@ -90,6 +90,7 @@ func (e *Executor) WriteGenesis( config: config, precompiles: precompiled.NewPrecompiled(), journal: &runtime.Journal{}, + accessList: runtime.NewAccessList(), } for addr, account := range alloc { @@ -303,6 +304,7 @@ func NewTransition(config chain.ForksInTime, snap Snapshot, radix *Txn) *Transit evm: evm.NewEVM(), precompiles: precompiled.NewPrecompiled(), journal: &runtime.Journal{}, + accessList: runtime.NewAccessList(), } } From 01d79ea891b980e8a4d04edd411fe98c669a0839 Mon Sep 17 00:00:00 2001 From: Goran Rojovic Date: Sun, 11 Feb 2024 20:35:49 +0100 Subject: [PATCH 07/16] subGasLimit fix --- state/executor.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/state/executor.go b/state/executor.go index 5adde83079..f5e54955ee 100644 --- a/state/executor.go +++ b/state/executor.go @@ -485,6 +485,14 @@ func (t *Transition) ContextPtr() *runtime.TxContext { func (t *Transition) subGasLimitPrice(msg *types.Transaction) error { upfrontGasCost := new(big.Int).Mul(new(big.Int).SetUint64(msg.Gas()), msg.GetGasPrice(t.ctx.BaseFee.Uint64())) + balanceCheck := new(big.Int).Set(upfrontGasCost) + if msg.Type() == types.DynamicFeeTx { + balanceCheck.Add(balanceCheck, msg.Value()) + } + + if have, want := t.state.GetBalance(msg.From()), balanceCheck; have.Cmp(want) < 0 { + return fmt.Errorf("%w: address %v have %v want %v", ErrInsufficientFunds, msg.From(), have, want) + } if err := t.state.SubBalance(msg.From(), upfrontGasCost); err != nil { if errors.Is(err, runtime.ErrNotEnoughFunds) { @@ -564,6 +572,7 @@ var ( ErrBlockLimitReached = errors.New("gas limit reached in the pool") ErrIntrinsicGasOverflow = errors.New("overflow in intrinsic gas calculation") ErrNotEnoughIntrinsicGas = errors.New("not enough gas supplied for intrinsic gas costs") + ErrInsufficientFunds = errors.New("insufficient funds for gas * price + value") // ErrTipAboveFeeCap is a sanity error to ensure no one is able to specify a // transaction with a tip higher than the total fee cap. From e2c116e21a823c08de70a315ed45809dc8669b6f Mon Sep 17 00:00:00 2001 From: Goran Rojovic Date: Mon, 12 Feb 2024 08:21:56 +0100 Subject: [PATCH 08/16] Fix --- state/executor.go | 3 +++ state/transition_test.go | 9 +++++++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/state/executor.go b/state/executor.go index f5e54955ee..b397ebf11f 100644 --- a/state/executor.go +++ b/state/executor.go @@ -488,6 +488,9 @@ func (t *Transition) subGasLimitPrice(msg *types.Transaction) error { balanceCheck := new(big.Int).Set(upfrontGasCost) if msg.Type() == types.DynamicFeeTx { balanceCheck.Add(balanceCheck, msg.Value()) + balanceCheck.SetUint64(msg.Gas()) + balanceCheck = balanceCheck.Mul(balanceCheck, msg.GasFeeCap()) + balanceCheck.Add(balanceCheck, msg.Value()) } if have, want := t.state.GetBalance(msg.From()), balanceCheck; have.Cmp(want) < 0 { diff --git a/state/transition_test.go b/state/transition_test.go index c683538071..dc1bc5512c 100644 --- a/state/transition_test.go +++ b/state/transition_test.go @@ -60,7 +60,7 @@ func TestSubGasLimitPrice(t *testing.T) { gas: 10, gasPrice: 10, // should return ErrNotEnoughFundsForGas when state.SubBalance returns ErrNotEnoughFunds - expectedErr: ErrNotEnoughFundsForGas, + expectedErr: ErrInsufficientFunds, }, } @@ -78,7 +78,12 @@ func TestSubGasLimitPrice(t *testing.T) { err := transition.subGasLimitPrice(msg) - assert.Equal(t, tt.expectedErr, err) + if tt.expectedErr != nil { + assert.ErrorContains(t, err, tt.expectedErr.Error()) + } else { + assert.NoError(t, err) + } + if err == nil { // should reduce cost for gas from balance reducedAmount := new(big.Int).Mul(msg.GasPrice(), big.NewInt(int64(msg.Gas()))) From 1b368946bd551c90c8cfaf0b551772976d478886 Mon Sep 17 00:00:00 2001 From: Goran Rojovic Date: Mon, 12 Feb 2024 08:49:58 +0100 Subject: [PATCH 09/16] Skip CheckEIP1559 e2e test --- e2e-polybft/e2e/consensus_test.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/e2e-polybft/e2e/consensus_test.go b/e2e-polybft/e2e/consensus_test.go index 5830b62436..0115d6bcb1 100644 --- a/e2e-polybft/e2e/consensus_test.go +++ b/e2e-polybft/e2e/consensus_test.go @@ -513,6 +513,8 @@ func TestE2E_Consensus_CustomRewardToken(t *testing.T) { // and check if balance of sender, receiver, burn contract and miner is updates correctly // in accordance with EIP-1559 specifications func TestE2E_Consensus_EIP1559Check(t *testing.T) { + t.Skip("TODO - since we removed burn from evm, this should be fixed after the burn solution") + sender, err := wallet.GenerateKey() require.NoError(t, err) From a4b2d36cfc300c8d53229b2c47c0ca2a65c69882 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20Negovanovi=C4=87?= Date: Tue, 13 Feb 2024 13:28:44 +0100 Subject: [PATCH 10/16] Remove auxState field from Transition and use NewTransition function --- helper/predeployment/predeployment.go | 3 +- state/executor.go | 62 +++++++++------------------ state/executor_test.go | 5 ++- 3 files changed, 25 insertions(+), 45 deletions(-) diff --git a/helper/predeployment/predeployment.go b/helper/predeployment/predeployment.go index 56a56553ca..ed152d7de5 100644 --- a/helper/predeployment/predeployment.go +++ b/helper/predeployment/predeployment.go @@ -5,6 +5,7 @@ import ( "math" "math/big" + "github.com/hashicorp/go-hclog" "github.com/umbracle/ethgo/abi" "github.com/0xPolygon/polygon-edge/chain" @@ -67,7 +68,7 @@ func getPredeployAccount(address types.Address, input []byte, config := chain.AllForksEnabled.At(0) // Create a transition - transition := state.NewTransition(config, snapshot, radix) + transition := state.NewTransition(hclog.NewNullLogger(), config, snapshot, radix) transition.ContextPtr().ChainID = chainID // Run the transition through the EVM diff --git a/state/executor.go b/state/executor.go index b397ebf11f..59bb8be818 100644 --- a/state/executor.go +++ b/state/executor.go @@ -81,17 +81,8 @@ func (e *Executor) WriteGenesis( ChainID: e.config.ChainID, } - transition := &Transition{ - logger: e.logger, - ctx: env, - state: txn, - auxState: e.state, - gasPool: uint64(env.GasLimit), - config: config, - precompiles: precompiled.NewPrecompiled(), - journal: &runtime.Journal{}, - accessList: runtime.NewAccessList(), - } + transition := NewTransition(e.logger, config, snap, txn) + transition.ctx = env for addr, account := range alloc { if account.Balance != nil { @@ -182,7 +173,7 @@ func (e *Executor) BeginTxn( ) (*Transition, error) { forkConfig := e.config.Forks.At(header.Number) - auxSnap2, err := e.state.NewSnapshotAt(parentRoot) + snap, err := e.state.NewSnapshotAt(parentRoot) if err != nil { return nil, err } @@ -195,7 +186,7 @@ func (e *Executor) BeginTxn( } } - newTxn := NewTxn(auxSnap2) + newTxn := NewTxn(snap) txCtx := runtime.TxContext{ Coinbase: coinbaseReceiver, @@ -208,62 +199,47 @@ func (e *Executor) BeginTxn( BurnContract: burnContract, } - txn := &Transition{ - logger: e.logger, - ctx: txCtx, - state: newTxn, - snap: auxSnap2, - getHash: e.GetHash(header), - auxState: e.state, - config: forkConfig, - gasPool: uint64(txCtx.GasLimit), - - receipts: []*types.Receipt{}, - totalGas: 0, - - evm: evm.NewEVM(), - precompiles: precompiled.NewPrecompiled(), - PostHook: e.PostHook, - journal: &runtime.Journal{}, - accessList: runtime.NewAccessList(), - } + t := NewTransition(e.logger, forkConfig, snap, newTxn) + t.PostHook = e.PostHook + t.getHash = e.GetHash(header) + t.ctx = txCtx + t.gasPool = uint64(txCtx.GasLimit) // enable contract deployment allow list (if any) if e.config.ContractDeployerAllowList != nil { - txn.deploymentAllowList = addresslist.NewAddressList(txn, contracts.AllowListContractsAddr) + t.deploymentAllowList = addresslist.NewAddressList(t, contracts.AllowListContractsAddr) } if e.config.ContractDeployerBlockList != nil { - txn.deploymentBlockList = addresslist.NewAddressList(txn, contracts.BlockListContractsAddr) + t.deploymentBlockList = addresslist.NewAddressList(t, contracts.BlockListContractsAddr) } // enable transactions allow list (if any) if e.config.TransactionsAllowList != nil { - txn.txnAllowList = addresslist.NewAddressList(txn, contracts.AllowListTransactionsAddr) + t.txnAllowList = addresslist.NewAddressList(t, contracts.AllowListTransactionsAddr) } if e.config.TransactionsBlockList != nil { - txn.txnBlockList = addresslist.NewAddressList(txn, contracts.BlockListTransactionsAddr) + t.txnBlockList = addresslist.NewAddressList(t, contracts.BlockListTransactionsAddr) } // enable transactions allow list (if any) if e.config.BridgeAllowList != nil { - txn.bridgeAllowList = addresslist.NewAddressList(txn, contracts.AllowListBridgeAddr) + t.bridgeAllowList = addresslist.NewAddressList(t, contracts.AllowListBridgeAddr) } if e.config.BridgeBlockList != nil { - txn.bridgeBlockList = addresslist.NewAddressList(txn, contracts.BlockListBridgeAddr) + t.bridgeBlockList = addresslist.NewAddressList(t, contracts.BlockListBridgeAddr) } - return txn, nil + return t, nil } type Transition struct { logger hclog.Logger // dummy - auxState State - snap Snapshot + snap Snapshot config chain.ForksInTime state *Txn @@ -296,8 +272,9 @@ type Transition struct { accessList *runtime.AccessList } -func NewTransition(config chain.ForksInTime, snap Snapshot, radix *Txn) *Transition { +func NewTransition(logger hclog.Logger, config chain.ForksInTime, snap Snapshot, radix *Txn) *Transition { return &Transition{ + logger: logger, config: config, state: radix, snap: snap, @@ -486,6 +463,7 @@ func (t *Transition) ContextPtr() *runtime.TxContext { func (t *Transition) subGasLimitPrice(msg *types.Transaction) error { upfrontGasCost := new(big.Int).Mul(new(big.Int).SetUint64(msg.Gas()), msg.GetGasPrice(t.ctx.BaseFee.Uint64())) balanceCheck := new(big.Int).Set(upfrontGasCost) + if msg.Type() == types.DynamicFeeTx { balanceCheck.Add(balanceCheck, msg.Value()) balanceCheck.SetUint64(msg.Gas()) diff --git a/state/executor_test.go b/state/executor_test.go index 870afa636e..31428cbab5 100644 --- a/state/executor_test.go +++ b/state/executor_test.go @@ -5,6 +5,7 @@ import ( "math/big" "testing" + "github.com/hashicorp/go-hclog" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -36,7 +37,7 @@ func TestOverride(t *testing.T) { balance := big.NewInt(2) code := []byte{0x1} - tt := NewTransition(chain.ForksInTime{}, state, newTxn(state)) + tt := NewTransition(hclog.NewNullLogger(), chain.ForksInTime{}, state, newTxn(state)) require.Empty(t, tt.state.GetCode(types.ZeroAddress)) @@ -259,7 +260,7 @@ func Test_Transition_EIP2929(t *testing.T) { txn.SetCode(addr, tt.code) enabledForks := chain.AllForksEnabled.At(0) - transition := NewTransition(enabledForks, state, txn) + transition := NewTransition(hclog.NewNullLogger(), enabledForks, state, txn) initialAccessList := runtime.NewAccessList() initialAccessList.PrepareAccessList(transition.ctx.Origin, &addr, transition.precompiles.Addrs, nil) transition.accessList = initialAccessList From 72899fa76d44afc6303043233005351cfcda5ee6 Mon Sep 17 00:00:00 2001 From: Goran Rojovic Date: Wed, 14 Feb 2024 09:08:56 +0100 Subject: [PATCH 11/16] increase timeout for UT --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index d3d0fce9c1..7e9c9301c1 100644 --- a/Makefile +++ b/Makefile @@ -58,7 +58,7 @@ generate-bsd-licenses: check-git .PHONY: unit-test unit-test: check-go - go test -race -shuffle=on -coverprofile coverage.out -timeout 20m `go list ./... | grep -v e2e` + go test -race -shuffle=on -coverprofile coverage.out -timeout 1h `go list ./... | grep -v e2e` .PHONY: fuzz-test fuzz-test: check-go From 24c4fe67a9614d05dfa36d2175baf01b686a2a35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20Negovanovi=C4=87?= Date: Thu, 15 Feb 2024 12:41:37 +0100 Subject: [PATCH 12/16] Suppress linting warnings --- state/executor.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/state/executor.go b/state/executor.go index 59bb8be818..d1ac8d7443 100644 --- a/state/executor.go +++ b/state/executor.go @@ -698,7 +698,7 @@ func (t *Transition) apply(msg *types.Transaction) (*runtime.ExecutionResult, er coinbaseFee := new(big.Int).Mul(new(big.Int).SetUint64(result.GasUsed), effectiveTip) t.state.AddBalance(t.ctx.Coinbase, coinbaseFee) - // nolint:godox + //nolint:godox // TODO - burning of base fee should not be done in the EVM // Burn some amount if the london hardfork is applied. // Basically, burn amount is just transferred to the current burn contract. @@ -1342,6 +1342,7 @@ func (t *Transition) RevertToSnapshot(snapshot int) error { }) if idx == len(t.journalRevisions) || t.journalRevisions[idx].ID != snapshot { + //nolint:gocritic panic(fmt.Errorf("journal revision id %v cannot be reverted", snapshot)) } From b1d90707298eb7134c06220ed748b8e09a84b5bb Mon Sep 17 00:00:00 2001 From: Goran Rojovic Date: Mon, 19 Feb 2024 10:29:19 +0100 Subject: [PATCH 13/16] underflow test fix --- state/runtime/evm/dispatch_table.go | 2 +- state/runtime/evm/instructions.go | 20 ++++++-------------- state/transition_test.go | 4 ++-- 3 files changed, 9 insertions(+), 17 deletions(-) diff --git a/state/runtime/evm/dispatch_table.go b/state/runtime/evm/dispatch_table.go index b20372e1a7..ec7b44b4c5 100644 --- a/state/runtime/evm/dispatch_table.go +++ b/state/runtime/evm/dispatch_table.go @@ -61,7 +61,7 @@ func init() { register(SLT, handler{inst: opSlt, stack: 2, gas: 3}) register(SGT, handler{inst: opSgt, stack: 2, gas: 3}) - register(SIGNEXTEND, handler{inst: opSignExtension, stack: 1, gas: 5}) + register(SIGNEXTEND, handler{inst: opSignExtension, stack: 2, gas: 5}) register(SHL, handler{inst: opShl, stack: 2, gas: 3}) register(SHR, handler{inst: opShr, stack: 2, gas: 3}) diff --git a/state/runtime/evm/instructions.go b/state/runtime/evm/instructions.go index 1cf114bb43..8fc6d3bb19 100644 --- a/state/runtime/evm/instructions.go +++ b/state/runtime/evm/instructions.go @@ -1418,9 +1418,6 @@ func (c *state) buildCreateContract(op OpCode) (*runtime.Contract, error) { // Calculate and consume gas cost - // var overflow bool - var gasCost uint64 - // Both CREATE and CREATE2 use memory var input []byte @@ -1431,17 +1428,6 @@ func (c *state) buildCreateContract(op OpCode) (*runtime.Contract, error) { return nil, nil } - // Consume memory resize gas (TODO, change with get2) (to be fixed in EVM-528) //nolint:godox - if !c.consumeGas(gasCost) { - return nil, nil - } - - if hasTransfer { - if c.host.GetBalance(c.msg.Address).Cmp(value) < 0 { - return nil, types.ErrInsufficientFunds - } - } - if op == CREATE2 { // Consume sha3 gas cost size := length.Uint64() @@ -1450,6 +1436,12 @@ func (c *state) buildCreateContract(op OpCode) (*runtime.Contract, error) { } } + if hasTransfer { + if c.host.GetBalance(c.msg.Address).Cmp(value) < 0 { + return nil, types.ErrInsufficientFunds + } + } + // Calculate and consume gas for the call gas := c.gas diff --git a/state/transition_test.go b/state/transition_test.go index dc1bc5512c..1779d38c72 100644 --- a/state/transition_test.go +++ b/state/transition_test.go @@ -48,7 +48,7 @@ func TestSubGasLimitPrice(t *testing.T) { expectedErr: nil, }, { - name: "should fail by ErrNotEnoughFunds", + name: "should fail by ErrInsufficientFunds", preState: map[types.Address]*PreState{ addr1: { Nonce: 0, @@ -59,7 +59,7 @@ func TestSubGasLimitPrice(t *testing.T) { from: addr1, gas: 10, gasPrice: 10, - // should return ErrNotEnoughFundsForGas when state.SubBalance returns ErrNotEnoughFunds + // should return ErrInsufficientFunds when state.SubBalance returns ErrNotEnoughFunds expectedErr: ErrInsufficientFunds, }, } From 2f6cca8288551ce013e9f07745845e2852ccab59 Mon Sep 17 00:00:00 2001 From: Goran Rojovic Date: Mon, 19 Feb 2024 10:32:24 +0100 Subject: [PATCH 14/16] Revert make file --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 7e9c9301c1..d3d0fce9c1 100644 --- a/Makefile +++ b/Makefile @@ -58,7 +58,7 @@ generate-bsd-licenses: check-git .PHONY: unit-test unit-test: check-go - go test -race -shuffle=on -coverprofile coverage.out -timeout 1h `go list ./... | grep -v e2e` + go test -race -shuffle=on -coverprofile coverage.out -timeout 20m `go list ./... | grep -v e2e` .PHONY: fuzz-test fuzz-test: check-go From ff499db0ce438373adc5bfa6c57b699401f1cf4c Mon Sep 17 00:00:00 2001 From: Goran Rojovic Date: Mon, 19 Feb 2024 10:38:53 +0100 Subject: [PATCH 15/16] Remove panic --- state/executor.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/state/executor.go b/state/executor.go index d1ac8d7443..d8cf8d3472 100644 --- a/state/executor.go +++ b/state/executor.go @@ -1342,8 +1342,7 @@ func (t *Transition) RevertToSnapshot(snapshot int) error { }) if idx == len(t.journalRevisions) || t.journalRevisions[idx].ID != snapshot { - //nolint:gocritic - panic(fmt.Errorf("journal revision id %v cannot be reverted", snapshot)) + return fmt.Errorf("journal revision id %v cannot be reverted", snapshot) } journalIndex := t.journalRevisions[idx].Index From 2bb1a14844238ac80daacd960fbe27fd51a514d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20Negovanovi=C4=87?= Date: Mon, 19 Feb 2024 10:40:44 +0100 Subject: [PATCH 16/16] Use %d in the error formatting --- state/executor.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/state/executor.go b/state/executor.go index d8cf8d3472..362b103ad6 100644 --- a/state/executor.go +++ b/state/executor.go @@ -1342,7 +1342,7 @@ func (t *Transition) RevertToSnapshot(snapshot int) error { }) if idx == len(t.journalRevisions) || t.journalRevisions[idx].ID != snapshot { - return fmt.Errorf("journal revision id %v cannot be reverted", snapshot) + return fmt.Errorf("journal revision id %d cannot be reverted", snapshot) } journalIndex := t.journalRevisions[idx].Index