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

core/vm: implement metropolis static call opcode #14978

Merged
merged 2 commits into from
Aug 16, 2017
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
80 changes: 61 additions & 19 deletions core/vm/evm.go
Original file line number Diff line number Diff line change
Expand Up @@ -123,19 +123,20 @@ func (evm *EVM) Cancel() {
atomic.StoreInt32(&evm.abort, 1)
}

// Call executes the contract associated with the addr with the given input as parameters. It also handles any
// necessary value transfer required and takes the necessary steps to create accounts and reverses the state in
// case of an execution error or failed value transfer.
// Call executes the contract associated with the addr with the given input as
// parameters. It also handles any necessary value transfer required and takes
// the necessary steps to create accounts and reverses the state in case of an
// execution error or failed value transfer.
func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas uint64, value *big.Int) (ret []byte, leftOverGas uint64, err error) {
if evm.vmConfig.NoRecursion && evm.depth > 0 {
return nil, gas, nil
}

// Depth check execution. Fail if we're trying to execute above the
// limit.
// Fail if we're trying to execute above the call depth limit
if evm.depth > int(params.CallCreateDepth) {
return nil, gas, ErrDepth
}
// Fail if we're trying to transfer more than the available balance
if !evm.Context.CanTransfer(evm.StateDB, caller.Address(), value) {
return nil, gas, ErrInsufficientBalance
}
Expand Down Expand Up @@ -173,21 +174,23 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas
return ret, contract.Gas, err
}

// CallCode executes the contract associated with the addr with the given input as parameters. It also handles any
// necessary value transfer required and takes the necessary steps to create accounts and reverses the state in
// case of an execution error or failed value transfer.
// CallCode executes the contract associated with the addr with the given input
// as parameters. It also handles any necessary value transfer required and takes
// the necessary steps to create accounts and reverses the state in case of an
// execution error or failed value transfer.
//
// CallCode differs from Call in the sense that it executes the given address' code with the caller as context.
// CallCode differs from Call in the sense that it executes the given address'
// code with the caller as context.
func (evm *EVM) CallCode(caller ContractRef, addr common.Address, input []byte, gas uint64, value *big.Int) (ret []byte, leftOverGas uint64, err error) {
if evm.vmConfig.NoRecursion && evm.depth > 0 {
return nil, gas, nil
}

// Depth check execution. Fail if we're trying to execute above the
// limit.
// Fail if we're trying to execute above the call depth limit
if evm.depth > int(params.CallCreateDepth) {
return nil, gas, ErrDepth
}
// Fail if we're trying to transfer more than the available balance
if !evm.CanTransfer(evm.StateDB, caller.Address(), value) {
return nil, gas, ErrInsufficientBalance
}
Expand All @@ -211,18 +214,16 @@ func (evm *EVM) CallCode(caller ContractRef, addr common.Address, input []byte,
return ret, contract.Gas, err
}

// DelegateCall executes the contract associated with the addr with the given input as parameters.
// It reverses the state in case of an execution error.
// DelegateCall executes the contract associated with the addr with the given input
// as parameters. It reverses the state in case of an execution error.
//
// DelegateCall differs from CallCode in the sense that it executes the given address' code with the caller as context
// and the caller is set to the caller of the caller.
// DelegateCall differs from CallCode in the sense that it executes the given address'
// code with the caller as context and the caller is set to the caller of the caller.
func (evm *EVM) DelegateCall(caller ContractRef, addr common.Address, input []byte, gas uint64) (ret []byte, leftOverGas uint64, err error) {
if evm.vmConfig.NoRecursion && evm.depth > 0 {
return nil, gas, nil
}

// Depth check execution. Fail if we're trying to execute above the
// limit.
// Fail if we're trying to execute above the call depth limit
if evm.depth > int(params.CallCreateDepth) {
return nil, gas, ErrDepth
}
Expand All @@ -232,7 +233,7 @@ func (evm *EVM) DelegateCall(caller ContractRef, addr common.Address, input []by
to = AccountRef(caller.Address())
)

// Iinitialise a new contract and make initialise the delegate values
// Initialise a new contract and make initialise the delegate values
contract := NewContract(caller, to, nil, gas).AsDelegate()
contract.SetCallCode(&addr, evm.StateDB.GetCodeHash(addr), evm.StateDB.GetCode(addr))

Expand All @@ -245,6 +246,47 @@ func (evm *EVM) DelegateCall(caller ContractRef, addr common.Address, input []by
return ret, contract.Gas, err
}

// StaticCall executes the contract associated with the addr with the given input
// as parameters while disallowing any modifications to the state during the call.
// Opcodes that attempt to perform such modifications will result in exceptions
// instead of performing the modifications.
func (evm *EVM) StaticCall(caller ContractRef, addr common.Address, input []byte, gas uint64) (ret []byte, leftOverGas uint64, err error) {
if evm.vmConfig.NoRecursion && evm.depth > 0 {
return nil, gas, nil
}
// Fail if we're trying to execute above the call depth limit
if evm.depth > int(params.CallCreateDepth) {
return nil, gas, ErrDepth
}
// Make sure the readonly is only set if we aren't in readonly yet
// this makes also sure that the readonly flag isn't removed for
// child calls.
if !evm.interpreter.readonly {
evm.interpreter.readonly = true
defer func() { evm.interpreter.readonly = false }()
}

var (
to = AccountRef(addr)
snapshot = evm.StateDB.Snapshot()
)
// Initialise a new contract and set the code that is to be used by the
// EVM. The contract is a scoped environment for this execution context
// only.
contract := NewContract(caller, to, new(big.Int), gas)
contract.SetCallCode(&addr, evm.StateDB.GetCodeHash(addr), evm.StateDB.GetCode(addr))

// 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 = run(evm, snapshot, contract, input)
if err != nil {
contract.UseGas(contract.Gas)
evm.StateDB.RevertToSnapshot(snapshot)
}
return ret, contract.Gas, err
}

// Create creates a new contract using code as deployment code.
func (evm *EVM) Create(caller ContractRef, code []byte, gas uint64, value *big.Int) (ret []byte, contractAddr common.Address, leftOverGas uint64, err error) {
if evm.vmConfig.NoRecursion && evm.depth > 0 {
Expand Down
27 changes: 27 additions & 0 deletions core/vm/gas_table.go
Original file line number Diff line number Diff line change
Expand Up @@ -423,6 +423,33 @@ func gasDelegateCall(gt params.GasTable, evm *EVM, contract *Contract, stack *St
return gas, nil
}

func gasStaticCall(gt params.GasTable, evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
gas, err := memoryGasCost(mem, memorySize)
if err != nil {
return 0, err
}
var overflow bool
if gas, overflow = math.SafeAdd(gas, gt.Calls); overflow {
return 0, errGasUintOverflow
}

cg, err := callGas(gt, contract.Gas, gas, stack.Back(0))
if err != nil {
return 0, err
}
// Replace the stack item with the new gas calculation. This means that
// either the original item is left on the stack or the item is replaced by:
// (availableGas - gas) * 63 / 64
// We replace the stack item so that it's available when the opCall instruction is
// called.
stack.data[stack.len()-1] = new(big.Int).SetUint64(cg)

if gas, overflow = math.SafeAdd(gas, cg); overflow {
return 0, errGasUintOverflow
}
return gas, nil
}

func gasPush(gt params.GasTable, evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
return GasFastestStep, nil
}
Expand Down
33 changes: 32 additions & 1 deletion core/vm/instructions.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package vm

import (
"errors"
"fmt"
"math/big"

Expand All @@ -28,7 +29,8 @@ import (
)

var (
bigZero = new(big.Int)
bigZero = new(big.Int)
errWriteProtection = errors.New("evm: write protection")
)

func opAdd(pc *uint64, evm *EVM, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) {
Expand Down Expand Up @@ -656,6 +658,35 @@ func opDelegateCall(pc *uint64, evm *EVM, contract *Contract, memory *Memory, st
return ret, nil
}

func opStaticCall(pc *uint64, evm *EVM, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) {
// pop gas
gas := stack.pop().Uint64()
// pop address
addr := stack.pop()
// pop input size and offset
inOffset, inSize := stack.pop(), stack.pop()
// pop return size and offset
retOffset, retSize := stack.pop(), stack.pop()

address := common.BigToAddress(addr)

// Get the arguments from the memory
args := memory.Get(inOffset.Int64(), inSize.Int64())

ret, returnGas, err := evm.StaticCall(contract, address, args, gas)
if err != nil {
stack.push(new(big.Int))
} else {
stack.push(big.NewInt(1))

memory.Set(retOffset.Uint64(), retSize.Uint64(), ret)
}
contract.Gas += returnGas

evm.interpreter.intPool.put(addr, inOffset, inSize, retOffset, retSize)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of allocating a new bigint, and returning e.g. addr to the intpool, you could do

addr.setUint64(0 or 1)
stack.push(addr)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking through the other instructions, similar tricks could be done throughout the code. Also there are a lot of stack.pop().Uint64() invocations which don't even return the big int into the pool, just silently discard it.

All in all I think it would be wiser to do a whole refactor for all the opcode and do that in a separate PR where we can review the optimization changes without metro additions in there.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I have one such laying around, which also uses peek instead of pop, so you just make sure that the result winds up there and everything is dandy.

return ret, nil
}

func opReturn(pc *uint64, evm *EVM, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) {
offset, size := stack.pop(), stack.pop()
ret := memory.GetPtr(offset.Int64(), size.Int64())
Expand Down
15 changes: 15 additions & 0 deletions core/vm/interpreter.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ func NewInterpreter(evm *EVM, cfg Config) *Interpreter {
// we'll set the default jump table.
if !cfg.JumpTable[STOP].valid {
switch {
case evm.ChainConfig().IsMetropolis(evm.BlockNumber):
cfg.JumpTable = metropolisInstructionSet
case evm.ChainConfig().IsHomestead(evm.BlockNumber):
cfg.JumpTable = homesteadInstructionSet
default:
Expand All @@ -85,6 +87,18 @@ func NewInterpreter(evm *EVM, cfg Config) *Interpreter {
}

func (in *Interpreter) enforceRestrictions(op OpCode, operation operation, stack *Stack) error {
if in.evm.chainRules.IsMetropolis {
if in.readonly {
// If the interpreter is operating in readonly mode, make sure no
// state-modifying operation is performed. The 3rd stack item
// for a call operation is the value. Transfering value from one
// account to the others means the state is modified and should also
// return with an error.
if operation.writes || (op == CALL && stack.Back(2).BitLen() > 0) {
return errWriteProtection
}
}
}
return nil
}

Expand All @@ -95,6 +109,7 @@ func (in *Interpreter) enforceRestrictions(op OpCode, operation operation, stack
// considered a revert-and-consume-all-gas operation. No error specific checks
// should be handled to reduce complexity and errors further down the in.
func (in *Interpreter) Run(snapshot int, contract *Contract, input []byte) (ret []byte, err error) {
// Increment the call depth which is restricted to 1024
in.evm.depth++
defer func() { in.evm.depth-- }()

Expand Down
25 changes: 23 additions & 2 deletions core/vm/jump_table.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,10 +56,26 @@ type operation struct {
}

var (
frontierInstructionSet = NewFrontierInstructionSet()
homesteadInstructionSet = NewHomesteadInstructionSet()
frontierInstructionSet = NewFrontierInstructionSet()
homesteadInstructionSet = NewHomesteadInstructionSet()
metropolisInstructionSet = NewMetropolisInstructionSet()
)

// NewMetropolisInstructionSet returns the frontier, homestead and
// metropolis instructions.
func NewMetropolisInstructionSet() [256]operation {
// instructions that can be executed during the homestead phase.
instructionSet := NewHomesteadInstructionSet()
instructionSet[STATICCALL] = operation{
execute: opStaticCall,
gasCost: gasStaticCall,
validateStack: makeStackFunc(6, 1),
memorySize: memoryStaticCall,
valid: true,
}
return instructionSet
}

// NewHomesteadInstructionSet returns the frontier and homestead
// instructions that can be executed during the homestead phase.
func NewHomesteadInstructionSet() [256]operation {
Expand Down Expand Up @@ -810,34 +826,39 @@ func NewFrontierInstructionSet() [256]operation {
validateStack: makeStackFunc(2, 0),
memorySize: memoryLog,
valid: true,
writes: true,
},
LOG1: {
execute: makeLog(1),
gasCost: makeGasLog(1),
validateStack: makeStackFunc(3, 0),
memorySize: memoryLog,
valid: true,
writes: true,
},
LOG2: {
execute: makeLog(2),
gasCost: makeGasLog(2),
validateStack: makeStackFunc(4, 0),
memorySize: memoryLog,
valid: true,
writes: true,
},
LOG3: {
execute: makeLog(3),
gasCost: makeGasLog(3),
validateStack: makeStackFunc(5, 0),
memorySize: memoryLog,
valid: true,
writes: true,
},
LOG4: {
execute: makeLog(4),
gasCost: makeGasLog(4),
validateStack: makeStackFunc(6, 0),
memorySize: memoryLog,
valid: true,
writes: true,
},
CREATE: {
execute: opCreate,
Expand Down
7 changes: 7 additions & 0 deletions core/vm/memory_table.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,13 @@ func memoryDelegateCall(stack *Stack) *big.Int {
return math.BigMax(x, y)
}

func memoryStaticCall(stack *Stack) *big.Int {
x := calcMemSize(stack.Back(4), stack.Back(5))
y := calcMemSize(stack.Back(2), stack.Back(3))

return math.BigMax(x, y)
}

func memoryReturn(stack *Stack) *big.Int {
return calcMemSize(stack.Back(0), stack.Back(1))
}
Expand Down
3 changes: 3 additions & 0 deletions core/vm/opcodes.go
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,7 @@ const (
CALLCODE
RETURN
DELEGATECALL
STATICCALL = 0xfa

SELFDESTRUCT = 0xff
)
Expand Down Expand Up @@ -355,6 +356,7 @@ var opCodeToString = map[OpCode]string{
RETURN: "RETURN",
CALLCODE: "CALLCODE",
DELEGATECALL: "DELEGATECALL",
STATICCALL: "STATICCALL",
SELFDESTRUCT: "SELFDESTRUCT",

PUSH: "PUSH",
Expand Down Expand Up @@ -405,6 +407,7 @@ var stringToOp = map[string]OpCode{
"CALLDATASIZE": CALLDATASIZE,
"CALLDATACOPY": CALLDATACOPY,
"DELEGATECALL": DELEGATECALL,
"STATICCALL": STATICCALL,
"CODESIZE": CODESIZE,
"CODECOPY": CODECOPY,
"GASPRICE": GASPRICE,
Expand Down