From e4de44424e181dbbdd26d6ff9889cf3ce279ff11 Mon Sep 17 00:00:00 2001 From: Kamen Stoykov Date: Thu, 29 Aug 2024 15:01:47 +0000 Subject: [PATCH 1/2] update instructions for execution last opcode only --- core/vm/instructions_zkevm.go | 134 ++++++++++++++++++++++++++++++++++ core/vm/interpreter_zkevm.go | 8 ++ core/vm/jump_table_zkevm.go | 15 ++++ 3 files changed, 157 insertions(+) diff --git a/core/vm/instructions_zkevm.go b/core/vm/instructions_zkevm.go index 0b261e39d58..5c3e68e9faf 100644 --- a/core/vm/instructions_zkevm.go +++ b/core/vm/instructions_zkevm.go @@ -7,6 +7,7 @@ import ( "github.com/holiman/uint256" "github.com/ledgerwatch/erigon/common" "github.com/ledgerwatch/erigon/core/types" + "github.com/ledgerwatch/erigon/crypto" "github.com/ledgerwatch/erigon/params" ) @@ -360,3 +361,136 @@ func opDelegateCall_zkevm(pc *uint64, interpreter *EVMInterpreter, scope *ScopeC interpreter.returnData = ret return ret, nil } + +// OpCoded execution overrides that are used for executing the last opcode in case of an error +func opBlockhash_zkevm_lastOpCode(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + num := scope.Stack.Peek() + + ibs := interpreter.evm.IntraBlockState() + ibs.GetBlockStateRoot(num) + + return nil, nil +} + +func opCodeSize_lastOpCode(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + return nil, nil +} + +func opExtCodeSize_lastOpCode(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + slot := scope.Stack.Peek() + interpreter.evm.IntraBlockState().GetCodeSize(slot.Bytes20()) + return nil, nil +} + +func opExtCodeCopy_lastOpCode(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + var ( + stack = scope.Stack + a = stack.Pop() + ) + addr := libcommon.Address(a.Bytes20()) + interpreter.evm.IntraBlockState().GetCode(addr) + return nil, nil +} + +func opExtCodeHash_zkevm_lastOpCode(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + slot := scope.Stack.Peek() + address := libcommon.Address(slot.Bytes20()) + ibs := interpreter.evm.IntraBlockState() + ibs.GetCodeSize(address) + ibs.GetCodeHash(address) + return nil, nil +} + +func opSelfBalance_lastOpCode(pc *uint64, interpreter *EVMInterpreter, callContext *ScopeContext) ([]byte, error) { + interpreter.evm.IntraBlockState().GetBalance(callContext.Contract.Address()) + return nil, nil +} + +func opBalance_lastOpCode(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + slot := scope.Stack.Peek() + address := libcommon.Address(slot.Bytes20()) + interpreter.evm.IntraBlockState().GetBalance(address) + return nil, nil +} + +func opCreate_zkevm_lastOpCode(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + if interpreter.readOnly { + return nil, ErrWriteProtection + } + var ( + value = scope.Stack.Pop() + gas = scope.Contract.Gas + ) + if interpreter.evm.ChainRules().IsTangerineWhistle { + gas -= gas / 64 + } + + caller := scope.Contract + address := crypto.CreateAddress(caller.Address(), interpreter.evm.IntraBlockState().GetNonce(caller.Address())) + + interpreter.evm.IntraBlockState().GetBalance(caller.Address()) + nonce := interpreter.evm.IntraBlockState().GetNonce(caller.Address()) + interpreter.evm.IntraBlockState().SetNonce(caller.Address(), nonce+1) + interpreter.evm.IntraBlockState().AddAddressToAccessList(address) + interpreter.evm.IntraBlockState().GetCodeHash(address) + interpreter.evm.IntraBlockState().GetNonce(address) + interpreter.evm.IntraBlockState().CreateAccount(address, true) + interpreter.evm.IntraBlockState().SetNonce(address, 1) + interpreter.evm.IntraBlockState().SubBalance(caller.Address(), &value) + interpreter.evm.IntraBlockState().AddBalance(address, &value) + interpreter.evm.IntraBlockState().SetCode(address, []byte{0}) + + return nil, nil +} + +func opCreate2_zkevm_lastOpCode(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + if interpreter.readOnly { + return nil, ErrWriteProtection + } + var ( + endowment = scope.Stack.Pop() + offset, size = scope.Stack.Pop(), scope.Stack.Pop() + salt = scope.Stack.Pop() + input = scope.Memory.GetCopy(int64(offset.Uint64()), int64(size.Uint64())) + ) + + caller := scope.Contract + codeAndHash := &codeAndHash{code: input} + address := crypto.CreateAddress2(caller.Address(), salt.Bytes32(), codeAndHash.Hash().Bytes()) + + interpreter.evm.IntraBlockState().GetBalance(caller.Address()) + nonce := interpreter.evm.IntraBlockState().GetNonce(caller.Address()) + interpreter.evm.IntraBlockState().SetNonce(caller.Address(), nonce+1) + interpreter.evm.IntraBlockState().AddAddressToAccessList(address) + interpreter.evm.IntraBlockState().GetCodeHash(address) + interpreter.evm.IntraBlockState().GetNonce(address) + interpreter.evm.IntraBlockState().CreateAccount(address, true) + interpreter.evm.IntraBlockState().SetNonce(address, 1) + interpreter.evm.IntraBlockState().SubBalance(caller.Address(), &endowment) + interpreter.evm.IntraBlockState().AddBalance(address, &endowment) + interpreter.evm.IntraBlockState().SetCode(address, []byte{0}) + + return nil, nil +} + +func opReturn_lastOpCode(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + return nil, nil +} + +func opSload_lastOpCode(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + loc := scope.Stack.Peek() + interpreter.hasherBuf = loc.Bytes32() + interpreter.evm.IntraBlockState().GetState(scope.Contract.Address(), &interpreter.hasherBuf, loc) + return nil, nil +} + +func opSstore_lastOpCode(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + if interpreter.readOnly { + return nil, ErrWriteProtection + } + loc := scope.Stack.Pop() + val := scope.Stack.Pop() + interpreter.hasherBuf = loc.Bytes32() + interpreter.evm.IntraBlockState().SetState(scope.Contract.Address(), &interpreter.hasherBuf, val) + return nil, nil +} diff --git a/core/vm/interpreter_zkevm.go b/core/vm/interpreter_zkevm.go index 5d8aba50704..1ac6eed7ec5 100644 --- a/core/vm/interpreter_zkevm.go +++ b/core/vm/interpreter_zkevm.go @@ -44,6 +44,12 @@ func getJumpTable(cr *chain.Rules) *JumpTable { return jt } +func overrideJumpTableForLastOpcode(jt *JumpTable, cr *chain.Rules) { + // currently an opcode execution does not change in any fork from 4 do 12. + // If, in future, there is a change then we can override it based on fork id + overrideJumpTableForLastOpcodeForkId12(jt) +} + func shouldExecuteLastOpCode(op OpCode) bool { switch op { case BLOCKHASH: @@ -211,6 +217,8 @@ func (in *EVMInterpreter) RunZk(contract *Contract, input []byte, readOnly bool) // we can safely use pc here instead of pcCopy, // because pc and pcCopy can be different only if the main loop finishes normally without error // but is it finishes normally without error then "ret" != nil and the .execute below will never be invoked at all + + overrideJumpTableForLastOpcode(in.jt, in.evm.ChainRules()) in.jt[op].execute(pc, in, callContext) } }() diff --git a/core/vm/jump_table_zkevm.go b/core/vm/jump_table_zkevm.go index 963bf103d19..8bcf0bd6460 100644 --- a/core/vm/jump_table_zkevm.go +++ b/core/vm/jump_table_zkevm.go @@ -78,3 +78,18 @@ func newForkID8InstructionSet() JumpTable { validateAndFillMaxStack(&instructionSet) return instructionSet } + +func overrideJumpTableForLastOpcodeForkId12(jt *JumpTable) { + jt[BLOCKHASH].execute = opBlockhash_zkevm_lastOpCode + jt[CODESIZE].execute = opCodeSize_lastOpCode + jt[EXTCODESIZE].execute = opExtCodeSize_lastOpCode + jt[EXTCODECOPY].execute = opExtCodeCopy_lastOpCode + jt[EXTCODEHASH].execute = opExtCodeHash_zkevm_lastOpCode + jt[SELFBALANCE].execute = opSelfBalance_lastOpCode + jt[BALANCE].execute = opBalance_lastOpCode + jt[CREATE].execute = opCreate_zkevm_lastOpCode + jt[RETURN].execute = opReturn_lastOpCode + jt[CREATE2].execute = opCreate2_zkevm_lastOpCode + jt[SLOAD].execute = opSload_lastOpCode + jt[SSTORE].execute = opSstore_lastOpCode +} From b628bd0ad314cbe66ff902624c9d9e2c4cd1bb81 Mon Sep 17 00:00:00 2001 From: Kamen Stoykov Date: Fri, 30 Aug 2024 08:34:42 +0000 Subject: [PATCH 2/2] copy jump table before executing last opcode --- core/vm/interpreter_zkevm.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/core/vm/interpreter_zkevm.go b/core/vm/interpreter_zkevm.go index 1ac6eed7ec5..203d8019623 100644 --- a/core/vm/interpreter_zkevm.go +++ b/core/vm/interpreter_zkevm.go @@ -218,8 +218,9 @@ func (in *EVMInterpreter) RunZk(contract *Contract, input []byte, readOnly bool) // because pc and pcCopy can be different only if the main loop finishes normally without error // but is it finishes normally without error then "ret" != nil and the .execute below will never be invoked at all - overrideJumpTableForLastOpcode(in.jt, in.evm.ChainRules()) - in.jt[op].execute(pc, in, callContext) + jtForLastOpCode := copyJumpTable(in.jt) + overrideJumpTableForLastOpcode(jtForLastOpCode, in.evm.ChainRules()) + jtForLastOpCode[op].execute(pc, in, callContext) } }()