diff --git a/core/vm/contract.go b/core/vm/contract.go index 165ca833f8..25b6dad66a 100644 --- a/core/vm/contract.go +++ b/core/vm/contract.go @@ -47,12 +47,12 @@ type Contract struct { } // NewContract returns a new contract environment for the execution of EVM. -func NewContract(caller common.Address, address common.Address, value *uint256.Int, gas uint64, jumpDests JumpDestCache) *Contract { +func NewContract(caller common.Address, address common.Address, value *uint256.Int, gas uint64, jumpDests JumpDestCache) Contract { // Initialize the jump analysis cache if it's nil, mostly for tests if jumpDests == nil { jumpDests = newMapJumpDests() } - return &Contract{ + return Contract{ caller: caller, address: address, jumpDests: jumpDests, diff --git a/core/vm/evm.go b/core/vm/evm.go index e360187f7b..68b69b453b 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -130,6 +130,8 @@ type EVM struct { readOnly bool // Whether to throw on stateful modifications returnData []byte // Last CALL's return data for subsequent reuse + + ScopeContext // Current Run context } // NewEVM constructs an EVM instance with the supplied block context, state @@ -294,7 +296,7 @@ func (evm *EVM) Call(caller common.Address, addr common.Address, input []byte, g contract := NewContract(caller, addr, value, gas, evm.jumpDests) contract.IsSystemCall = isSystemCall(caller) contract.SetCallCode(evm.resolveCodeHash(addr), code) - ret, err = evm.Run(contract, input, false) + ret, err = evm.Run(&contract, input, false) gas = contract.Gas } } @@ -353,7 +355,7 @@ func (evm *EVM) CallCode(caller common.Address, addr common.Address, input []byt // The contract is a scoped environment for this execution context only. contract := NewContract(caller, caller, value, gas, evm.jumpDests) contract.SetCallCode(evm.resolveCodeHash(addr), evm.resolveCode(addr)) - ret, err = evm.Run(contract, input, false) + ret, err = evm.Run(&contract, input, false) gas = contract.Gas } if err != nil { @@ -397,7 +399,7 @@ func (evm *EVM) DelegateCall(originCaller common.Address, caller common.Address, // Note: The value refers to the original value from the parent call. contract := NewContract(originCaller, caller, value, gas, evm.jumpDests) contract.SetCallCode(evm.resolveCodeHash(addr), evm.resolveCode(addr)) - ret, err = evm.Run(contract, input, false) + ret, err = evm.Run(&contract, input, false) gas = contract.Gas } if err != nil { @@ -452,7 +454,7 @@ func (evm *EVM) StaticCall(caller common.Address, addr common.Address, input []b // When an error was returned by the EVM or when setting the creation code // above we revert to the snapshot and consume any gas remaining. Additionally // when we're in Homestead this also counts for code storage gas errors. - ret, err = evm.Run(contract, input, true) + ret, err = evm.Run(&contract, input, true) gas = contract.Gas } if err != nil { @@ -560,7 +562,7 @@ func (evm *EVM) create(caller common.Address, code []byte, gas uint64, value *ui contract.SetCallCode(common.Hash{}, code) contract.IsDeployment = true - ret, err = evm.initNewContract(contract, address) + ret, err = evm.initNewContract(&contract, address) if err != nil && (evm.chainRules.IsHomestead || err != ErrCodeStoreOutOfGas) { evm.StateDB.RevertToSnapshot(snapshot) if err != ErrExecutionReverted { diff --git a/core/vm/instructions_test.go b/core/vm/instructions_test.go index cd31829a7e..7f20f056cc 100644 --- a/core/vm/instructions_test.go +++ b/core/vm/instructions_test.go @@ -107,7 +107,7 @@ func testTwoOperandOp(t *testing.T, tests []TwoOperandTestcase, opFn executionFu expected := new(uint256.Int).SetBytes(common.Hex2Bytes(test.Expected)) stack.push(x) stack.push(y) - opFn(&pc, evm, &ScopeContext{nil, stack, nil}) + opFn(&pc, evm, &ScopeContext{Stack: stack}) if len(stack.data) != 1 { t.Errorf("Expected one item on stack after %v, got %d: ", name, len(stack.data)) } @@ -221,7 +221,7 @@ func TestAddMod(t *testing.T) { stack.push(z) stack.push(y) stack.push(x) - opAddmod(&pc, evm, &ScopeContext{nil, stack, nil}) + opAddmod(&pc, evm, &ScopeContext{Stack: stack}) actual := stack.pop() if actual.Cmp(expected) != 0 { t.Errorf("Testcase %d, expected %x, got %x", i, expected, actual) @@ -247,7 +247,7 @@ func TestWriteExpectedValues(t *testing.T) { y := new(uint256.Int).SetBytes(common.Hex2Bytes(param.y)) stack.push(x) stack.push(y) - opFn(&pc, evm, &ScopeContext{nil, stack, nil}) + opFn(&pc, evm, &ScopeContext{Stack: stack}) actual := stack.pop() result[i] = TwoOperandTestcase{param.x, param.y, fmt.Sprintf("%064x", actual)} } @@ -283,7 +283,7 @@ func opBenchmark(bench *testing.B, op executionFunc, args ...string) { var ( evm = NewEVM(BlockContext{}, nil, params.TestChainConfig, Config{}) stack = newstack() - scope = &ScopeContext{nil, stack, nil} + scope = &ScopeContext{Stack: stack} ) // convert args intArgs := make([]*uint256.Int, len(args)) @@ -528,13 +528,13 @@ func TestOpMstore(t *testing.T) { v := "abcdef00000000000000abba000000000deaf000000c0de00100000000133700" stack.push(new(uint256.Int).SetBytes(common.Hex2Bytes(v))) stack.push(new(uint256.Int)) - opMstore(&pc, evm, &ScopeContext{mem, stack, nil}) + opMstore(&pc, evm, &ScopeContext{Memory: mem, Stack: stack}) if got := common.Bytes2Hex(mem.GetCopy(0, 32)); got != v { t.Fatalf("Mstore fail, got %v, expected %v", got, v) } stack.push(new(uint256.Int).SetUint64(0x1)) stack.push(new(uint256.Int)) - opMstore(&pc, evm, &ScopeContext{mem, stack, nil}) + opMstore(&pc, evm, &ScopeContext{Memory: mem, Stack: stack}) if common.Bytes2Hex(mem.GetCopy(0, 32)) != "0000000000000000000000000000000000000000000000000000000000000001" { t.Fatalf("Mstore failed to overwrite previous value") } @@ -555,7 +555,7 @@ func BenchmarkOpMstore(bench *testing.B) { for i := 0; i < bench.N; i++ { stack.push(value) stack.push(memStart) - opMstore(&pc, evm, &ScopeContext{mem, stack, nil}) + opMstore(&pc, evm, &ScopeContext{Memory: mem, Stack: stack}) } } @@ -568,7 +568,7 @@ func TestOpTstore(t *testing.T) { caller = common.Address{} to = common.Address{1} contract = NewContract(caller, to, new(uint256.Int), 0, nil) - scopeContext = ScopeContext{mem, stack, contract} + scopeContext = ScopeContext{Memory: mem, Stack: stack, Contract: contract} value = common.Hex2Bytes("abcdef00000000000000abba000000000deaf000000c0de00100000000133700") ) @@ -613,7 +613,7 @@ func BenchmarkOpKeccak256(bench *testing.B) { for i := 0; i < bench.N; i++ { stack.push(uint256.NewInt(32)) stack.push(start) - opKeccak256(&pc, evm, &ScopeContext{mem, stack, nil}) + opKeccak256(&pc, evm, &ScopeContext{Memory: mem, Stack: stack}) } } @@ -707,7 +707,7 @@ func TestRandom(t *testing.T) { stack = newstack() pc = uint64(0) ) - opRandom(&pc, evm, &ScopeContext{nil, stack, nil}) + opRandom(&pc, evm, &ScopeContext{Stack: stack}) if len(stack.data) != 1 { t.Errorf("Expected one item on stack after %v, got %d: ", tt.name, len(stack.data)) } @@ -749,7 +749,7 @@ func TestBlobHash(t *testing.T) { ) evm.SetTxContext(TxContext{BlobHashes: tt.hashes}) stack.push(uint256.NewInt(tt.idx)) - opBlobHash(&pc, evm, &ScopeContext{nil, stack, nil}) + opBlobHash(&pc, evm, &ScopeContext{Stack: stack}) if len(stack.data) != 1 { t.Errorf("Expected one item on stack after %v, got %d: ", tt.name, len(stack.data)) } @@ -889,7 +889,7 @@ func TestOpMCopy(t *testing.T) { mem.Resize(memorySize) } // Do the copy - opMcopy(&pc, evm, &ScopeContext{mem, stack, nil}) + opMcopy(&pc, evm, &ScopeContext{Memory: mem, Stack: stack}) want := common.FromHex(strings.ReplaceAll(tc.want, " ", "")) if have := mem.store; !bytes.Equal(want, have) { t.Errorf("case %d: \nwant: %#x\nhave: %#x\n", i, want, have) @@ -911,7 +911,7 @@ func TestPush(t *testing.T) { scope := &ScopeContext{ Memory: nil, Stack: newstack(), - Contract: &Contract{ + Contract: Contract{ Code: code, }, } diff --git a/core/vm/interpreter.go b/core/vm/interpreter.go index a0637a6800..996adbb69f 100644 --- a/core/vm/interpreter.go +++ b/core/vm/interpreter.go @@ -35,12 +35,13 @@ type Config struct { StatelessSelfValidation bool // Generate execution witnesses and self-check against them (testing purpose) } -// ScopeContext contains the things that are per-call, such as stack and memory, -// but not transients like pc and gas +// ScopeContext contains the things that are per-call type ScopeContext struct { + pc uint64 + Memory *Memory Stack *Stack - Contract *Contract + Contract Contract } // MemoryData returns the underlying memory slice. Callers must not modify the contents @@ -94,9 +95,30 @@ func (ctx *ScopeContext) ContractCode() []byte { // considered a revert-and-consume-all-gas operation except for // ErrExecutionReverted which means revert-and-keep-gas-left. func (evm *EVM) Run(contract *Contract, input []byte, readOnly bool) (ret []byte, err error) { + // Don't bother with the execution if there's no code. + if len(contract.Code) == 0 { + return nil, nil + } + // Increment the call depth which is restricted to 1024 + prevContext := evm.ScopeContext + evm.ScopeContext = ScopeContext{ + pc: 0, + Memory: NewMemory(), + Stack: newstack(), + Contract: *contract, + } evm.depth++ - defer func() { evm.depth-- }() + defer func() { + evm.depth-- + *contract = evm.Contract + // Don't move this deferred function, it's placed before the OnOpcode-deferred method, + // so that it gets executed _after_: the OnOpcode needs the stacks before + // they are returned to the pools + returnStack(evm.Stack) + evm.Memory.Free() + evm.ScopeContext = prevContext + }() // Make sure the readOnly is only set if we aren't in readOnly yet. // This also makes sure that the readOnly flag isn't removed for child calls. @@ -109,25 +131,12 @@ func (evm *EVM) Run(contract *Contract, input []byte, readOnly bool) (ret []byte // as every returning call will return new data anyway. evm.returnData = nil - // Don't bother with the execution if there's no code. - if len(contract.Code) == 0 { - return nil, nil - } - var ( - op OpCode // current opcode - jumpTable *JumpTable = evm.table - mem = NewMemory() // bound memory - stack = newstack() // local stack - callContext = &ScopeContext{ - Memory: mem, - Stack: stack, - Contract: contract, - } + op OpCode // current opcode + jumpTable *JumpTable = evm.table // For optimisation reason we're using uint64 as the program counter. // It's theoretically possible to go above 2^64. The YP defines the PC // to be uint256. Practically much less so feasible. - pc = uint64(0) // program counter cost uint64 // copies used by tracer pcCopy uint64 // needed for the deferred EVMLogger @@ -137,14 +146,7 @@ func (evm *EVM) Run(contract *Contract, input []byte, readOnly bool) (ret []byte debug = evm.Config.Tracer != nil isEIP4762 = evm.chainRules.IsEIP4762 ) - // Don't move this deferred function, it's placed before the OnOpcode-deferred method, - // so that it gets executed _after_: the OnOpcode needs the stacks before - // they are returned to the pools - defer func() { - returnStack(stack) - mem.Free() - }() - contract.Input = input + evm.Contract.Input = input if debug { defer func() { // this deferred method handles exit-with-error @@ -152,10 +154,10 @@ func (evm *EVM) Run(contract *Contract, input []byte, readOnly bool) (ret []byte return } if !logged && evm.Config.Tracer.OnOpcode != nil { - evm.Config.Tracer.OnOpcode(pcCopy, byte(op), gasCopy, cost, callContext, evm.returnData, evm.depth, VMErrorFromErr(err)) + evm.Config.Tracer.OnOpcode(pcCopy, byte(op), gasCopy, cost, &evm.ScopeContext, evm.returnData, evm.depth, VMErrorFromErr(err)) } if logged && evm.Config.Tracer.OnFault != nil { - evm.Config.Tracer.OnFault(pcCopy, byte(op), gasCopy, cost, callContext, evm.depth, VMErrorFromErr(err)) + evm.Config.Tracer.OnFault(pcCopy, byte(op), gasCopy, cost, &evm.ScopeContext, evm.depth, VMErrorFromErr(err)) } }() } @@ -167,14 +169,14 @@ func (evm *EVM) Run(contract *Contract, input []byte, readOnly bool) (ret []byte for { if debug { // Capture pre-execution values for tracing. - logged, pcCopy, gasCopy = false, pc, contract.Gas + logged, pcCopy, gasCopy = false, evm.pc, evm.Contract.Gas } if isEIP4762 && !contract.IsDeployment && !contract.IsSystemCall { // if the PC ends up in a new "chunk" of verkleized code, charge the // associated costs. contractAddr := contract.Address() - consumed, wanted := evm.TxContext.AccessEvents.CodeChunksRangeGas(contractAddr, pc, 1, uint64(len(contract.Code)), false, contract.Gas) + consumed, wanted := evm.TxContext.AccessEvents.CodeChunksRangeGas(contractAddr, evm.pc, 1, uint64(len(contract.Code)), false, contract.Gas) contract.UseGas(consumed, evm.Config.Tracer, tracing.GasChangeWitnessCodeChunk) if consumed < wanted { return nil, ErrOutOfGas @@ -183,20 +185,20 @@ func (evm *EVM) Run(contract *Contract, input []byte, readOnly bool) (ret []byte // Get the operation from the jump table and validate the stack to ensure there are // enough stack items available to perform the operation. - op = contract.GetOp(pc) + op = evm.Contract.GetOp(evm.pc) operation := jumpTable[op] cost = operation.constantGas // For tracing // Validate stack - if sLen := stack.len(); sLen < operation.minStack { + if sLen := evm.Stack.len(); sLen < operation.minStack { return nil, &ErrStackUnderflow{stackLen: sLen, required: operation.minStack} } else if sLen > operation.maxStack { return nil, &ErrStackOverflow{stackLen: sLen, limit: operation.maxStack} } // for tracing: this gas consumption event is emitted below in the debug section. - if contract.Gas < cost { + if evm.Contract.Gas < cost { return nil, ErrOutOfGas } else { - contract.Gas -= cost + evm.Contract.Gas -= cost } // All ops with a dynamic memory usage also has a dynamic gas cost. @@ -207,7 +209,7 @@ func (evm *EVM) Run(contract *Contract, input []byte, readOnly bool) (ret []byte // Memory check needs to be done prior to evaluating the dynamic gas portion, // to detect calculation overflows if operation.memorySize != nil { - memSize, overflow := operation.memorySize(stack) + memSize, overflow := operation.memorySize(evm.Stack) if overflow { return nil, ErrGasUintOverflow } @@ -220,16 +222,16 @@ func (evm *EVM) Run(contract *Contract, input []byte, readOnly bool) (ret []byte // Consume the gas and return an error if not enough gas is available. // cost is explicitly set so that the capture state defer method can get the proper cost var dynamicCost uint64 - dynamicCost, err = operation.dynamicGas(evm, contract, stack, mem, memorySize) + dynamicCost, err = operation.dynamicGas(evm, &evm.Contract, evm.Stack, evm.Memory, memorySize) cost += dynamicCost // for tracing if err != nil { return nil, fmt.Errorf("%w: %v", ErrOutOfGas, err) } // for tracing: this gas consumption event is emitted below in the debug section. - if contract.Gas < dynamicCost { + if evm.Contract.Gas < dynamicCost { return nil, ErrOutOfGas } else { - contract.Gas -= dynamicCost + evm.Contract.Gas -= dynamicCost } } @@ -239,20 +241,20 @@ func (evm *EVM) Run(contract *Contract, input []byte, readOnly bool) (ret []byte evm.Config.Tracer.OnGasChange(gasCopy, gasCopy-cost, tracing.GasChangeCallOpCode) } if evm.Config.Tracer.OnOpcode != nil { - evm.Config.Tracer.OnOpcode(pc, byte(op), gasCopy, cost, callContext, evm.returnData, evm.depth, VMErrorFromErr(err)) + evm.Config.Tracer.OnOpcode(evm.pc, byte(op), gasCopy, cost, &evm.ScopeContext, evm.returnData, evm.depth, VMErrorFromErr(err)) logged = true } } if memorySize > 0 { - mem.Resize(memorySize) + evm.Memory.Resize(memorySize) } // execute the operation - res, err = operation.execute(&pc, evm, callContext) + res, err = operation.execute(&evm.pc, evm, &evm.ScopeContext) if err != nil { break } - pc++ + evm.pc++ } if err == errStopToken { diff --git a/core/vm/interpreter_test.go b/core/vm/interpreter_test.go index 8ed512316b..86b1a33e2a 100644 --- a/core/vm/interpreter_test.go +++ b/core/vm/interpreter_test.go @@ -91,6 +91,6 @@ func BenchmarkInterpreter(b *testing.B) { gasSStoreEIP3529 = makeGasSStoreFunc(params.SstoreClearsScheduleRefundEIP3529) b.ResetTimer() for i := 0; i < b.N; i++ { - gasSStoreEIP3529(evm, contract, stack, mem, 1234) + gasSStoreEIP3529(evm, &contract, stack, mem, 1234) } } diff --git a/eth/tracers/js/tracer_test.go b/eth/tracers/js/tracer_test.go index 7f376a27fc..63284eefce 100644 --- a/eth/tracers/js/tracer_test.go +++ b/eth/tracers/js/tracer_test.go @@ -65,7 +65,7 @@ func runTrace(tracer *tracers.Tracer, vmctx *vmContext, chaincfg *params.ChainCo tracer.OnTxStart(evm.GetVMContext(), types.NewTx(&types.LegacyTx{Gas: gasLimit, GasPrice: vmctx.txCtx.GasPrice}), contract.Caller()) tracer.OnEnter(0, byte(vm.CALL), contract.Caller(), contract.Address(), []byte{}, startGas, value.ToBig()) - ret, err := evm.Run(contract, []byte{}, false) + ret, err := evm.Run(&contract, []byte{}, false) tracer.OnExit(0, ret, startGas-contract.Gas, err, true) // Rest gas assumes no refund tracer.OnTxEnd(&types.Receipt{GasUsed: gasLimit - contract.Gas}, nil) @@ -182,8 +182,9 @@ func TestHaltBetweenSteps(t *testing.T) { if err != nil { t.Fatal(err) } + contract := vm.NewContract(common.Address{}, common.Address{}, uint256.NewInt(0), 0, nil) scope := &vm.ScopeContext{ - Contract: vm.NewContract(common.Address{}, common.Address{}, uint256.NewInt(0), 0, nil), + Contract: contract, } evm := vm.NewEVM(vm.BlockContext{BlockNumber: big.NewInt(1)}, &dummyStatedb{}, chainConfig, vm.Config{Tracer: tracer.Hooks}) evm.SetTxContext(vm.TxContext{GasPrice: big.NewInt(1)}) @@ -280,8 +281,9 @@ func TestEnterExit(t *testing.T) { if err != nil { t.Fatal(err) } + contract := vm.NewContract(common.Address{}, common.Address{}, uint256.NewInt(0), 0, nil) scope := &vm.ScopeContext{ - Contract: vm.NewContract(common.Address{}, common.Address{}, uint256.NewInt(0), 0, nil), + Contract: contract, } tracer.OnEnter(1, byte(vm.CALL), scope.Contract.Caller(), scope.Contract.Address(), []byte{}, 1000, new(big.Int)) tracer.OnExit(1, []byte{}, 400, nil, false) diff --git a/eth/tracers/logger/logger_test.go b/eth/tracers/logger/logger_test.go index acc3069e70..5d0ab36a62 100644 --- a/eth/tracers/logger/logger_test.go +++ b/eth/tracers/logger/logger_test.go @@ -52,7 +52,7 @@ func TestStoreCapture(t *testing.T) { contract.Code = []byte{byte(vm.PUSH1), 0x1, byte(vm.PUSH1), 0x0, byte(vm.SSTORE)} var index common.Hash logger.OnTxStart(evm.GetVMContext(), nil, common.Address{}) - _, err := evm.Run(contract, []byte{}, false) + _, err := evm.Run(&contract, []byte{}, false) if err != nil { t.Fatal(err) }