Skip to content
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

Implement Code.Run() method #5

Merged
merged 2 commits into from
Feb 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 1 addition & 6 deletions examples_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -368,11 +368,6 @@ func ExampleCode_sqrt() {
}

func compileAndRun[T interface{ []byte | [32]byte }](code Code, callData T) []byte {
compiled, err := code.Compile()
if err != nil {
log.Fatal(err)
}

var slice []byte
switch c := any(callData).(type) {
case []byte:
Expand All @@ -381,7 +376,7 @@ func compileAndRun[T interface{ []byte | [32]byte }](code Code, callData T) []by
slice = c[:]
}

got, err := runBytecode(compiled, slice)
got, err := code.Run(slice)
if err != nil {
log.Fatal(err)
}
Expand Down
67 changes: 67 additions & 0 deletions run.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package specialops

import (
"fmt"
"math/big"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/params"
"github.com/solidifylabs/specialops/runopts"
)

// Run calls c.Compile() and runs the compiled bytecode on a freshly
// instantiated vm.EVMInterpreter. The default EVM parameters MUST NOT be
// considered stable: they are currently such that code runs on the Cancun fork
// with no state DB.
func (c Code) Run(callData []byte, opts ...runopts.Option) ([]byte, error) {
compiled, err := c.Compile()
if err != nil {
return nil, fmt.Errorf("%T.Compile(): %v", c, err)
}
return runBytecode(compiled, callData, opts...)
}

func runBytecode(compiled, callData []byte, opts ...runopts.Option) ([]byte, error) {
cfg, err := newRunConfig(opts...)
if err != nil {
return nil, err
}
interp := vm.NewEVM(
cfg.BlockCtx,
cfg.TxCtx,
cfg.StateDB,
cfg.ChainConfig,
cfg.VMConfig,
).Interpreter()

cc := &vm.Contract{
Code: compiled,
Gas: 30e6,
}

out, err := interp.Run(cc, callData, cfg.ReadOnly)
if err != nil {
return nil, fmt.Errorf("%T.Run([%T.Compile()], [callData], readOnly=%t): %v", interp, Code{}, cfg.ReadOnly, err)
}
return out, nil
}

func newRunConfig(opts ...runopts.Option) (*runopts.Configuration, error) {
cfg := &runopts.Configuration{
BlockCtx: vm.BlockContext{
BlockNumber: big.NewInt(0),
Random: &common.Hash{}, // post merge
},
ChainConfig: &params.ChainConfig{
LondonBlock: big.NewInt(0),
CancunTime: new(uint64),
},
}
for _, o := range opts {
if err := o.Apply(cfg); err != nil {
return nil, fmt.Errorf("runopts.Option[%T].Apply(): %v", o, err)
}
}
return cfg, nil
}
44 changes: 44 additions & 0 deletions runopts/runopts.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// Package runopts provides configuration options for specialops.Code.Run().
package runopts

import (
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/params"
)

// A Configuration carries all values that can be modified to configure a call
// to specialops.Code.Run(). It is intially set by Run() and then passed to all
// Options to be modified.
type Configuration struct {
// vm.NewEVM()
BlockCtx vm.BlockContext
TxCtx vm.TxContext
StateDB vm.StateDB
ChainConfig *params.ChainConfig
VMConfig vm.Config
// EVMInterpreter.Run()
ReadOnly bool // static call
}

// An Option modifies a Configuration.
type Option interface {
Apply(*Configuration) error
}

// A FuncOption converts any function into an Option by calling itself as
// Apply().
type FuncOption func(*Configuration) error

// Apply returns f(c).
func (f FuncOption) Apply(c *Configuration) error {
return f(c)
}

// ReadOnly sets the `readOnly` argument to true when calling
// EVMInterpreter.Run(), equivalent to a static call.
func ReadOnly() Option {
return FuncOption(func(c *Configuration) error {
c.ReadOnly = true
return nil
})
}
35 changes: 4 additions & 31 deletions specialops_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,39 +4,12 @@ import (
"bytes"
"fmt"
"log"
"math/big"
"testing"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/params"
"github.com/holiman/uint256"
)

func runBytecode(compiled, callData []byte) ([]byte, error) {
interp := vm.NewEVM(
vm.BlockContext{
BlockNumber: big.NewInt(99),
Random: &common.MaxHash, // non-nil -> post merge
},
vm.TxContext{},
nil, /*statedb*/
&params.ChainConfig{
LondonBlock: big.NewInt(0),
CancunTime: new(uint64),
},
vm.Config{},
).Interpreter()

contract := &vm.Contract{
Code: compiled,
Gas: 30e6,
}

return interp.Run(contract, callData, false /*static*/)
}

// mustRunByteCode propagates arguments to runBytecode, calling log.Fatal() on
// error, otherwise returning the result. It's useful for testable examples that
// don't have access to t.Fatal().
Expand Down Expand Up @@ -211,14 +184,14 @@ func TestRunCompiled(t *testing.T) {
}
t.Logf("Bytecode: %#x", compiled)

got, err := runBytecode(compiled, tt.callData)
got, err := tt.code.Run(tt.callData)
if err != nil {
t.Fatalf("%T.Run([%T.Compile() output]) error %v", &vm.EVMInterpreter{}, tt.code, err)
t.Fatalf("%T.Run(%#x) error %v", tt.code, tt.callData, err)
}
if !bytes.Equal(got, tt.want) {
t.Errorf(
"%T.Run([%T.Compile() output]) got:\n%#x\n%v\n\nwant:\n%#x\n%v",
&vm.EVMInterpreter{}, tt.code,
"%T.Run(%#x) got:\n%#x\n%v\n\nwant:\n%#x\n%v",
tt.code, tt.callData,
got, new(uint256.Int).SetBytes(got),
tt.want, new(uint256.Int).SetBytes(tt.want),
)
Expand Down
Loading