core/vm: implement metropolis static call opcode

pull/14978/head
Jeffrey Wilcke 7 years ago committed by Péter Szilágyi
parent 9facf6423d
commit 3d123bcde6
No known key found for this signature in database
GPG Key ID: E9AE538CEDF8293D
  1. 45
      core/vm/evm.go
  2. 27
      core/vm/gas_table.go
  3. 31
      core/vm/instructions.go
  4. 16
      core/vm/interpreter.go
  5. 21
      core/vm/jump_table.go
  6. 7
      core/vm/memory_table.go
  7. 3
      core/vm/opcodes.go

@ -245,6 +245,51 @@ func (evm *EVM) DelegateCall(caller ContractRef, addr common.Address, input []by
return ret, contract.Gas, err return ret, contract.Gas, err
} }
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
}
// Depth check execution. Fail if we're trying to execute above the
// 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()
)
if !evm.StateDB.Exist(addr) {
return nil, gas, nil
}
// initialise a new contract and set the code that is to be used by the
// EVM. The contract is a scoped evmironment for this execution context
// only.
contract := NewContract(caller, to, new(big.Int), gas)
contract.SetCallCode(&addr, evm.StateDB.GetCodeHash(addr), evm.StateDB.GetCode(addr))
ret, err = evm.interpreter.Run(snapshot, contract, input)
// 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.
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. // 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) { 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 { if evm.vmConfig.NoRecursion && evm.depth > 0 {

@ -423,6 +423,33 @@ func gasDelegateCall(gt params.GasTable, evm *EVM, contract *Contract, stack *St
return gas, nil 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) { func gasPush(gt params.GasTable, evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
return GasFastestStep, nil return GasFastestStep, nil
} }

@ -17,6 +17,7 @@
package vm package vm
import ( import (
"errors"
"fmt" "fmt"
"math/big" "math/big"
@ -29,6 +30,7 @@ import (
var ( 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) { func opAdd(pc *uint64, evm *EVM, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) {
@ -656,6 +658,35 @@ func opDelegateCall(pc *uint64, evm *EVM, contract *Contract, memory *Memory, st
return ret, nil 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)
return ret, nil
}
func opReturn(pc *uint64, evm *EVM, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { func opReturn(pc *uint64, evm *EVM, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) {
offset, size := stack.pop(), stack.pop() offset, size := stack.pop(), stack.pop()
ret := memory.GetPtr(offset.Int64(), size.Int64()) ret := memory.GetPtr(offset.Int64(), size.Int64())

@ -69,6 +69,8 @@ func NewInterpreter(evm *EVM, cfg Config) *Interpreter {
// we'll set the default jump table. // we'll set the default jump table.
if !cfg.JumpTable[STOP].valid { if !cfg.JumpTable[STOP].valid {
switch { switch {
case evm.ChainConfig().IsMetropolis(evm.BlockNumber):
cfg.JumpTable = metropolisInstructionSet
case evm.ChainConfig().IsHomestead(evm.BlockNumber): case evm.ChainConfig().IsHomestead(evm.BlockNumber):
cfg.JumpTable = homesteadInstructionSet cfg.JumpTable = homesteadInstructionSet
default: default:
@ -85,6 +87,19 @@ func NewInterpreter(evm *EVM, cfg Config) *Interpreter {
} }
func (in *Interpreter) enforceRestrictions(op OpCode, operation operation, stack *Stack) error { 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 4th 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 || op == CALLCODE) && stack.Back(3).BitLen() > 0) {
return errWriteProtection
}
}
}
return nil return nil
} }
@ -95,6 +110,7 @@ func (in *Interpreter) enforceRestrictions(op OpCode, operation operation, stack
// considered a revert-and-consume-all-gas operation. No error specific checks // considered a revert-and-consume-all-gas operation. No error specific checks
// should be handled to reduce complexity and errors further down the in. // 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) { 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++ in.evm.depth++
defer func() { in.evm.depth-- }() defer func() { in.evm.depth-- }()

@ -58,8 +58,24 @@ type operation struct {
var ( var (
frontierInstructionSet = NewFrontierInstructionSet() frontierInstructionSet = NewFrontierInstructionSet()
homesteadInstructionSet = NewHomesteadInstructionSet() 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 // NewHomesteadInstructionSet returns the frontier and homestead
// instructions that can be executed during the homestead phase. // instructions that can be executed during the homestead phase.
func NewHomesteadInstructionSet() [256]operation { func NewHomesteadInstructionSet() [256]operation {
@ -810,6 +826,7 @@ func NewFrontierInstructionSet() [256]operation {
validateStack: makeStackFunc(2, 0), validateStack: makeStackFunc(2, 0),
memorySize: memoryLog, memorySize: memoryLog,
valid: true, valid: true,
writes: true,
}, },
LOG1: { LOG1: {
execute: makeLog(1), execute: makeLog(1),
@ -817,6 +834,7 @@ func NewFrontierInstructionSet() [256]operation {
validateStack: makeStackFunc(3, 0), validateStack: makeStackFunc(3, 0),
memorySize: memoryLog, memorySize: memoryLog,
valid: true, valid: true,
writes: true,
}, },
LOG2: { LOG2: {
execute: makeLog(2), execute: makeLog(2),
@ -824,6 +842,7 @@ func NewFrontierInstructionSet() [256]operation {
validateStack: makeStackFunc(4, 0), validateStack: makeStackFunc(4, 0),
memorySize: memoryLog, memorySize: memoryLog,
valid: true, valid: true,
writes: true,
}, },
LOG3: { LOG3: {
execute: makeLog(3), execute: makeLog(3),
@ -831,6 +850,7 @@ func NewFrontierInstructionSet() [256]operation {
validateStack: makeStackFunc(5, 0), validateStack: makeStackFunc(5, 0),
memorySize: memoryLog, memorySize: memoryLog,
valid: true, valid: true,
writes: true,
}, },
LOG4: { LOG4: {
execute: makeLog(4), execute: makeLog(4),
@ -838,6 +858,7 @@ func NewFrontierInstructionSet() [256]operation {
validateStack: makeStackFunc(6, 0), validateStack: makeStackFunc(6, 0),
memorySize: memoryLog, memorySize: memoryLog,
valid: true, valid: true,
writes: true,
}, },
CREATE: { CREATE: {
execute: opCreate, execute: opCreate,

@ -74,6 +74,13 @@ func memoryDelegateCall(stack *Stack) *big.Int {
return math.BigMax(x, y) 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 { func memoryReturn(stack *Stack) *big.Int {
return calcMemSize(stack.Back(0), stack.Back(1)) return calcMemSize(stack.Back(0), stack.Back(1))
} }

@ -201,6 +201,7 @@ const (
CALLCODE CALLCODE
RETURN RETURN
DELEGATECALL DELEGATECALL
STATICCALL = 0xfa
SELFDESTRUCT = 0xff SELFDESTRUCT = 0xff
) )
@ -355,6 +356,7 @@ var opCodeToString = map[OpCode]string{
RETURN: "RETURN", RETURN: "RETURN",
CALLCODE: "CALLCODE", CALLCODE: "CALLCODE",
DELEGATECALL: "DELEGATECALL", DELEGATECALL: "DELEGATECALL",
STATICCALL: "STATICCALL",
SELFDESTRUCT: "SELFDESTRUCT", SELFDESTRUCT: "SELFDESTRUCT",
PUSH: "PUSH", PUSH: "PUSH",
@ -405,6 +407,7 @@ var stringToOp = map[string]OpCode{
"CALLDATASIZE": CALLDATASIZE, "CALLDATASIZE": CALLDATASIZE,
"CALLDATACOPY": CALLDATACOPY, "CALLDATACOPY": CALLDATACOPY,
"DELEGATECALL": DELEGATECALL, "DELEGATECALL": DELEGATECALL,
"STATICCALL": STATICCALL,
"CODESIZE": CODESIZE, "CODESIZE": CODESIZE,
"CODECOPY": CODECOPY, "CODECOPY": CODECOPY,
"GASPRICE": GASPRICE, "GASPRICE": GASPRICE,

Loading…
Cancel
Save