Skip to content

Commit

Permalink
feat: untested vm.PrecompileEnvironment.Create() and Create2()
Browse files Browse the repository at this point in the history
  • Loading branch information
ARR4N committed Nov 18, 2024
1 parent d2697b1 commit 44f8fa6
Show file tree
Hide file tree
Showing 3 changed files with 94 additions and 32 deletions.
9 changes: 9 additions & 0 deletions core/vm/contracts.libevm.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,12 @@ const (
CallCode = CallType(CALLCODE)
DelegateCall = CallType(DELEGATECALL)
StaticCall = CallType(STATICCALL)

// Although not technically calls, the CREATE OpCodes also enter a new call
// frame and therefore have similar setup to _outgoing_ *CALL* types. They
// are only used internally, by [environment].
create = CallType(CREATE)
create2 = CallType(CREATE2)
)

func (t CallType) isValid() bool {
Expand Down Expand Up @@ -162,6 +168,9 @@ type PrecompileEnvironment interface {
// removed and automatically determined according to the type of call that
// invoked the precompile.
Call(addr common.Address, input []byte, gas uint64, value *uint256.Int, _ ...CallOption) (ret []byte, _ error)

Create(code []byte, gas uint64, value *uint256.Int) ([]byte, common.Address, error)
Create2(code []byte, gas uint64, value, salt *uint256.Int) ([]byte, common.Address, error)
}

func (args *evmCallArgs) env() *environment {
Expand Down
92 changes: 67 additions & 25 deletions core/vm/environment.libevm.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,49 +100,63 @@ func (e *environment) BlockHeader() (types.Header, error) {
return *hdr, nil
}

func (e *environment) Call(addr common.Address, input []byte, gas uint64, value *uint256.Int, opts ...CallOption) ([]byte, error) {
return e.callContract(Call, addr, input, gas, value, opts...)
}

func (e *environment) callContract(typ CallType, addr common.Address, input []byte, gas uint64, value *uint256.Int, opts ...CallOption) (retData []byte, retErr error) {
func (e *environment) beforeNewCallFrame(typ CallType, gas uint64, value *uint256.Int, opts ...CallOption) (ContractRef, func(), error) {
// Depth and read-only setting are handled by [EVMInterpreter.Run], which
// isn't used for precompiles, so we need to do it ourselves to maintain the
// expected invariants.
in := e.evm.interpreter
var deferred []func()

in.evm.depth++
defer func() { in.evm.depth-- }()
deferred = append(deferred, func() { in.evm.depth-- })

if e.ReadOnly() && !in.readOnly { // i.e. the precompile was StaticCall()ed
in.readOnly = true
defer func() { in.readOnly = false }()
deferred = append(deferred, func() { in.readOnly = false })
}

var caller ContractRef = e.self
for _, o := range opts {
switch o := o.(type) {
case callOptUNSAFECallerAddressProxy:
// Note that, in addition to being unsafe, this breaks an EVM
// assumption that the caller ContractRef is always a *Contract.
caller = AccountRef(e.self.CallerAddress)
if e.callType == DelegateCall {
// self was created with AsDelegate(), which means that
// CallerAddress was inherited.
caller = AccountRef(e.self.Address())
}
case nil:
default:
return nil, fmt.Errorf("unsupported option %T", o)
var (
caller ContractRef = e.self
config libevmCallConfig
)
config.apply(opts...)
if config.proxyCallerAddressUNSAFE {
// Note that, in addition to being unsafe, this breaks an EVM
// assumption that the caller ContractRef is always a *Contract.
caller = AccountRef(e.self.CallerAddress)
if e.callType == DelegateCall {
// self was created with AsDelegate(), which means that
// CallerAddress was inherited.
caller = AccountRef(e.self.Address())
}
}

if in.readOnly && value != nil && !value.IsZero() {
return nil, ErrWriteProtection
writes := (value != nil && !value.IsZero()) || typ == create || typ == create2
if in.readOnly && writes {
return nil, nil, ErrWriteProtection
}
if !e.UseGas(gas) {
return nil, ErrOutOfGas
return nil, nil, ErrOutOfGas
}

return caller, func() {
for _, fn := range deferred {
fn()
}
}, nil
}

func (e *environment) Call(addr common.Address, input []byte, gas uint64, value *uint256.Int, opts ...CallOption) ([]byte, error) {
return e.callContract(Call, addr, input, gas, value, opts...)
}

func (e *environment) callContract(typ CallType, addr common.Address, input []byte, gas uint64, value *uint256.Int, opts ...CallOption) (retData []byte, retErr error) {
caller, cleanup, err := e.beforeNewCallFrame(typ, gas, value, opts...)
if err != nil {
return nil, err
}
defer cleanup()

if t := e.evm.Config.Tracer; t != nil {
var bigVal *big.Int
if value != nil {
Expand Down Expand Up @@ -174,3 +188,31 @@ func (e *environment) callContract(typ CallType, addr common.Address, input []by
return nil, fmt.Errorf("unimplemented precompile call type %v", typ)
}
}

func (e *environment) Create(code []byte, gas uint64, value *uint256.Int) ([]byte, common.Address, error) {
return e.create(create, gas, value, func(caller ContractRef) ([]byte, common.Address, uint64, error) {
return e.evm.Create(caller, code, gas, value)
})
}

func (e *environment) Create2(code []byte, gas uint64, value, salt *uint256.Int) ([]byte, common.Address, error) {
return e.create(create2, gas, value, func(caller ContractRef) ([]byte, common.Address, uint64, error) {
return e.evm.Create2(caller, code, gas, value, salt)
})
}

type creator func(ContractRef) ([]byte, common.Address, uint64, error)

func (e *environment) create(typ CallType, gas uint64, value *uint256.Int, do creator) ([]byte, common.Address, error) {
caller, cleanup, err := e.beforeNewCallFrame(typ, gas, value)
if err != nil {
return nil, common.Address{}, err
}
defer cleanup()

ret, contract, returnGas, err := do(caller)
if err := e.refundGas(returnGas); err != nil {
return nil, common.Address{}, err
}
return ret, contract, err
}
25 changes: 18 additions & 7 deletions core/vm/options.libevm.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,23 @@ package vm

// A CallOption modifies the default behaviour of a contract call.
type CallOption interface {
libevmCallOption() // noop to only allow internally defined options
apply(*libevmCallConfig)
}

type libevmCallConfig struct {
proxyCallerAddressUNSAFE bool
}

func (c *libevmCallConfig) apply(opts ...CallOption) {
for _, o := range opts {
o.apply(c)
}
}

type libevmFuncOpt func(*libevmCallConfig)

func (f libevmFuncOpt) apply(c *libevmCallConfig) { f(c) }

// WithUNSAFECallerAddressProxying results in precompiles making contract calls
// specifying their own caller's address as the caller. This is NOT SAFE for
// regular use as callers of the precompile may not understand that they are
Expand All @@ -29,10 +43,7 @@ type CallOption interface {
// Deprecated: this option MUST NOT be used other than to allow migration to
// libevm when backwards compatibility is required.
func WithUNSAFECallerAddressProxying() CallOption {
return callOptUNSAFECallerAddressProxy{}
return libevmFuncOpt(func(c *libevmCallConfig) {
c.proxyCallerAddressUNSAFE = true
})
}

// Deprecated: see [WithUNSAFECallerAddressProxying].
type callOptUNSAFECallerAddressProxy struct{}

func (callOptUNSAFECallerAddressProxy) libevmCallOption() {}

0 comments on commit 44f8fa6

Please sign in to comment.