-
Notifications
You must be signed in to change notification settings - Fork 2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: vm.JumpTable
override
#30
base: main
Are you sure you want to change the base?
Changes from all commits
83002ea
7105d78
482edd9
ce10b13
55ef2a4
6a5de75
7d054b9
58ded5b
a0d8ef9
263a9cb
792eb29
0473ed8
59cf9d7
9d1bd9b
7e62c28
966d148
524167e
a312417
d80b492
f122da5
e2e9330
789efec
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,60 @@ | ||||||
// Copyright 2024 the libevm authors. | ||||||
// | ||||||
// The libevm additions to go-ethereum are free software: you can redistribute | ||||||
// them and/or modify them under the terms of the GNU Lesser General Public License | ||||||
// as published by the Free Software Foundation, either version 3 of the License, | ||||||
// or (at your option) any later version. | ||||||
// | ||||||
// The libevm additions are distributed in the hope that they will be useful, | ||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser | ||||||
// General Public License for more details. | ||||||
// | ||||||
// You should have received a copy of the GNU Lesser General Public License | ||||||
// along with the go-ethereum library. If not, see | ||||||
// <http://www.gnu.org/licenses/>. | ||||||
|
||||||
package vm | ||||||
|
||||||
// An OperationBuilder is a factory for a new operations to include in a | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
// [JumpTable]. | ||||||
type OperationBuilder struct { | ||||||
Execute OperationFunc | ||||||
ConstantGas uint64 | ||||||
DynamicGas func(_ *EVM, _ *Contract, _ *Stack, _ *Memory, requestedMemorySize uint64) (uint64, error) | ||||||
MinStack, MaxStack int | ||||||
MemorySize func(s *Stack) (size uint64, overflow bool) | ||||||
} | ||||||
|
||||||
// Build constructs the operation. | ||||||
func (b OperationBuilder) Build() *operation { | ||||||
o := &operation{ | ||||||
execute: b.Execute.internal(), | ||||||
constantGas: b.ConstantGas, | ||||||
dynamicGas: b.DynamicGas, | ||||||
minStack: b.MinStack, | ||||||
maxStack: b.MaxStack, | ||||||
memorySize: b.MemorySize, | ||||||
} | ||||||
return o | ||||||
} | ||||||
|
||||||
// An OperationFunc is the execution function of a custom instruction. | ||||||
type OperationFunc func(_ Environment, pc *uint64, _ *EVMInterpreter, _ *ScopeContext) ([]byte, error) | ||||||
|
||||||
// internal converts an exported [OperationFunc] into an un-exported | ||||||
// [executionFunc] as required to build an [operation]. | ||||||
func (fn OperationFunc) internal() executionFunc { | ||||||
return func(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { | ||||||
env := &environment{ | ||||||
evm: interpreter.evm, | ||||||
self: scope.Contract, | ||||||
// The CallType isn't exposed by an instruction's [Environment] and, | ||||||
// although [UnknownCallType] is the default value, it's explicitly | ||||||
// set to avoid future accidental setting without proper | ||||||
// justification. | ||||||
callType: UnknownCallType, | ||||||
} | ||||||
return fn(env, pc, interpreter, scope) | ||||||
} | ||||||
} |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,110 @@ | ||||||
// Copyright 2024 the libevm authors. | ||||||
// | ||||||
// The libevm additions to go-ethereum are free software: you can redistribute | ||||||
// them and/or modify them under the terms of the GNU Lesser General Public License | ||||||
// as published by the Free Software Foundation, either version 3 of the License, | ||||||
// or (at your option) any later version. | ||||||
// | ||||||
// The libevm additions are distributed in the hope that they will be useful, | ||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser | ||||||
// General Public License for more details. | ||||||
// | ||||||
// You should have received a copy of the GNU Lesser General Public License | ||||||
// along with the go-ethereum library. If not, see | ||||||
// <http://www.gnu.org/licenses/>. | ||||||
|
||||||
package vm_test | ||||||
|
||||||
import ( | ||||||
"fmt" | ||||||
"reflect" | ||||||
"testing" | ||||||
|
||||||
"github.com/holiman/uint256" | ||||||
"github.com/stretchr/testify/assert" | ||||||
"github.com/stretchr/testify/require" | ||||||
|
||||||
"github.com/ava-labs/libevm/common" | ||||||
"github.com/ava-labs/libevm/core/vm" | ||||||
"github.com/ava-labs/libevm/libevm/ethtest" | ||||||
"github.com/ava-labs/libevm/params" | ||||||
) | ||||||
|
||||||
type vmHooksStub struct { | ||||||
replacement *vm.JumpTable | ||||||
overridden bool | ||||||
} | ||||||
|
||||||
var _ vm.Hooks = (*vmHooksStub)(nil) | ||||||
|
||||||
// OverrideJumpTable overrides all non-nil operations from s.replacement . | ||||||
func (s *vmHooksStub) OverrideJumpTable(_ params.Rules, jt *vm.JumpTable) *vm.JumpTable { | ||||||
s.overridden = true | ||||||
for op, instr := range s.replacement { | ||||||
if instr != nil { | ||||||
fmt.Println(op, instr) | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Remove print I think?
Suggested change
|
||||||
jt[op] = instr | ||||||
} | ||||||
} | ||||||
return jt | ||||||
} | ||||||
|
||||||
func (*vmHooksStub) OverrideNewEVMArgs(a *vm.NewEVMArgs) *vm.NewEVMArgs { return a } | ||||||
|
||||||
func (*vmHooksStub) OverrideEVMResetArgs(r params.Rules, a *vm.EVMResetArgs) *vm.EVMResetArgs { | ||||||
return a | ||||||
} | ||||||
|
||||||
// An opRecorder is an instruction that records its inputs. | ||||||
type opRecorder struct { | ||||||
stateVal common.Hash | ||||||
} | ||||||
|
||||||
func (op *opRecorder) execute(env vm.Environment, pc *uint64, interpreter *vm.EVMInterpreter, scope *vm.ScopeContext) ([]byte, error) { | ||||||
op.stateVal = env.StateDB().GetState(scope.Contract.Address(), common.Hash{}) | ||||||
return nil, nil | ||||||
} | ||||||
|
||||||
func TestOverrideJumpTable(t *testing.T) { | ||||||
const ( | ||||||
opcode = 1 | ||||||
gasLimit uint64 = 1e6 | ||||||
) | ||||||
rng := ethtest.NewPseudoRand(142857) | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Any particular reason to use |
||||||
gasCost := 1 + rng.Uint64n(gasLimit) | ||||||
spy := &opRecorder{} | ||||||
|
||||||
vmHooks := &vmHooksStub{ | ||||||
replacement: &vm.JumpTable{ | ||||||
opcode: vm.OperationBuilder{ | ||||||
Execute: spy.execute, | ||||||
ConstantGas: gasCost, | ||||||
MemorySize: func(s *vm.Stack) (size uint64, overflow bool) { | ||||||
return 0, false | ||||||
}, | ||||||
}.Build(), | ||||||
}, | ||||||
} | ||||||
vm.RegisterHooks(vmHooks) | ||||||
|
||||||
state, evm := ethtest.NewZeroEVM(t) | ||||||
|
||||||
contract := rng.Address() | ||||||
state.CreateAccount(contract) | ||||||
state.SetCode(contract, []byte{opcode}) | ||||||
value := rng.Hash() | ||||||
state.SetState(contract, common.Hash{}, value) | ||||||
|
||||||
_, gasRemaining, err := evm.Call(vm.AccountRef(rng.Address()), contract, []byte{}, gasLimit, uint256.NewInt(0)) | ||||||
require.NoError(t, err, "evm.Call([contract with overridden opcode])") | ||||||
assert.Equal(t, gasLimit-gasCost, gasRemaining, "gas remaining") | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit I don't think there is a need to have an extra message, since assert.Equal would show the log line and it's relatively easy to understand what's going on without the
Suggested change
|
||||||
assert.Equal(t, spy.stateVal, value, "StateDB propagated") | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Shouldn't this be
Suggested change
|
||||||
} | ||||||
|
||||||
func TestOperationFieldCount(t *testing.T) { | ||||||
// The libevm OperationBuilder assumes that the 6 struct fields are the only | ||||||
// ones. | ||||||
op := vm.OperationBuilder{}.Build() | ||||||
require.Equalf(t, 6, reflect.TypeOf(*op).NumField(), "number of fields in %T struct", *op) | ||||||
} | ||||||
Comment on lines
+105
to
+110
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This test is to make sure we update the Build method to set all fields in the future right? |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -24,7 +24,12 @@ import ( | |
|
||
// LookupInstructionSet returns the instruction set for the fork configured by | ||
// the rules. | ||
func LookupInstructionSet(rules params.Rules) (JumpTable, error) { | ||
func LookupInstructionSet(rules params.Rules) (jt JumpTable, err error) { | ||
defer func() { | ||
if err == nil { // NOTE `err ==` NOT != | ||
jt = *overrideJumpTable(rules, &jt) | ||
} | ||
}() | ||
Comment on lines
+27
to
+32
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nice defer to lower diffs with base repo 😉 ! |
||
switch { | ||
case rules.IsVerkle: | ||
return newCancunInstructionSet(), errors.New("verkle-fork not defined yet") | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm thinking, if a user calls
overrideJumpTable
, maybe we should panic if the libevmHooks is nil? It sounds a bit strange to pass in rules which just gets silently discarded by this function if the hooks are nil 🤔 We could handle the libevmHooks nil check at the calling layer instead?