Skip to content
This repository has been archived by the owner on Aug 13, 2019. It is now read-only.

Commit

Permalink
EIP 214 STATICCALL (#40)
Browse files Browse the repository at this point in the history
* Cherry pick STATICALL commit

* Removed implemented skipped tests

* Removed implemented tests
  • Loading branch information
austinabell authored and GregTheGreek committed May 31, 2019
1 parent 5aff51c commit bfb67d2
Show file tree
Hide file tree
Showing 13 changed files with 137 additions and 65 deletions.
4 changes: 4 additions & 0 deletions cmd/evm/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,10 @@ func (self *VMEnv) DelegateCall(caller vm.ContractRef, addr common.Address, data
return core.DelegateCall(self, caller, addr, data, gas, price)
}

func (self *VMEnv) StaticCall(caller vm.ContractRef, addr common.Address, data []byte, gas, price *big.Int) ([]byte, error) {
return core.StaticCall(self, caller, addr, data, gas, price)
}

func (self *VMEnv) Create(caller vm.ContractRef, data []byte, gas, price, value *big.Int) ([]byte, common.Address, error) {
return core.Create(self, caller, data, gas, price, value)
}
18 changes: 12 additions & 6 deletions core/execution.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,14 @@ var (

// Call executes within the given contract
func Call(env vm.Environment, caller vm.ContractRef, addr common.Address, input []byte, gas, gasPrice, value *big.Int) (ret []byte, err error) {
ret, _, err = exec(env, caller, &addr, &addr, env.Db().GetCodeHash(addr), input, env.Db().GetCode(addr), gas, gasPrice, value)
ret, _, err = exec(env, caller, &addr, &addr, env.Db().GetCodeHash(addr), input, env.Db().GetCode(addr), gas, gasPrice, value, false)
return ret, err
}

// CallCode executes the given address' code as the given contract address
func CallCode(env vm.Environment, caller vm.ContractRef, addr common.Address, input []byte, gas, gasPrice, value *big.Int) (ret []byte, err error) {
callerAddr := caller.Address()
ret, _, err = exec(env, caller, &callerAddr, &addr, env.Db().GetCodeHash(addr), input, env.Db().GetCode(addr), gas, gasPrice, value)
ret, _, err = exec(env, caller, &callerAddr, &addr, env.Db().GetCodeHash(addr), input, env.Db().GetCode(addr), gas, gasPrice, value, false)
return ret, err
}

Expand All @@ -55,9 +55,15 @@ func DelegateCall(env vm.Environment, caller vm.ContractRef, addr common.Address
return ret, err
}

// StaticCall executes within the given contract and throws exception if state is attempted to be changed
func StaticCall(env vm.Environment, caller vm.ContractRef, addr common.Address, input []byte, gas, gasPrice *big.Int) (ret []byte, err error) {
ret, _, err = exec(env, caller, &addr, &addr, env.Db().GetCodeHash(addr), input, env.Db().GetCode(addr), gas, gasPrice, new(big.Int), true)
return ret, err
}

// Create creates a new contract with the given code
func Create(env vm.Environment, caller vm.ContractRef, code []byte, gas, gasPrice, value *big.Int) (ret []byte, address common.Address, err error) {
ret, address, err = exec(env, caller, nil, nil, crypto.Keccak256Hash(code), nil, code, gas, gasPrice, value)
ret, address, err = exec(env, caller, nil, nil, crypto.Keccak256Hash(code), nil, code, gas, gasPrice, value, false)
// Here we get an error if we run into maximum stack depth,
// See: https://github.com/ethereum/yellowpaper/pull/131
// and YP definitions for CREATE
Expand All @@ -69,7 +75,7 @@ func Create(env vm.Environment, caller vm.ContractRef, code []byte, gas, gasPric
return ret, address, err
}

func exec(env vm.Environment, caller vm.ContractRef, address, codeAddr *common.Address, codeHash common.Hash, input, code []byte, gas, gasPrice, value *big.Int) (ret []byte, addr common.Address, err error) {
func exec(env vm.Environment, caller vm.ContractRef, address, codeAddr *common.Address, codeHash common.Hash, input, code []byte, gas, gasPrice, value *big.Int, readOnly bool) (ret []byte, addr common.Address, err error) {
evm := env.Vm()
// Depth check execution. Fail if we're trying to execute above the
// limit.
Expand Down Expand Up @@ -129,7 +135,7 @@ func exec(env vm.Environment, caller vm.ContractRef, address, codeAddr *common.A
contract.SetCallCode(codeAddr, codeHash, code)
defer contract.Finalise()

ret, err = evm.Run(contract, input)
ret, err = evm.Run(contract, input, readOnly)

maxCodeSizeExceeded := len(ret) > maxCodeSize && env.RuleSet().IsAtlantis(env.BlockNumber())
// if the contract creation ran successfully and no errors were returned
Expand Down Expand Up @@ -188,7 +194,7 @@ func execDelegateCall(env vm.Environment, caller vm.ContractRef, originAddr, toA
contract.SetCallCode(codeAddr, codeHash, code)
defer contract.Finalise()

ret, err = evm.Run(contract, input)
ret, err = evm.Run(contract, input, false)

if err != nil {
env.RevertToSnapshot(snapshot)
Expand Down
4 changes: 3 additions & 1 deletion core/vm/environment.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,8 @@ type Environment interface {
CallCode(me ContractRef, addr common.Address, data []byte, gas, price, value *big.Int) ([]byte, error)
// Same as CallCode except sender and value is propagated from parent to child scope
DelegateCall(me ContractRef, addr common.Address, data []byte, gas, price *big.Int) ([]byte, error)
// Call another contract and disallow any state changing operations
StaticCall(me ContractRef, addr common.Address, data []byte, gas, price *big.Int) ([]byte, error)
// Create a new contract
Create(me ContractRef, data []byte, gas, price, value *big.Int) ([]byte, common.Address, error)
}
Expand All @@ -88,7 +90,7 @@ type Vm interface {
// Run should execute the given contract with the input given in in
// and return the contract execution return bytes or an error if it
// failed.
Run(c *Contract, in []byte) ([]byte, error)
Run(c *Contract, in []byte, readOnly bool) ([]byte, error)
}

// Database is a EVM database for full state querying.
Expand Down
1 change: 1 addition & 0 deletions core/vm/gas.go
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,7 @@ var _baseCheck = map[OpCode]req{
CALL: {7, new(big.Int), 1},
CALLCODE: {7, new(big.Int), 1},
DELEGATECALL: {6, new(big.Int), 1},
STATICCALL: {6, new(big.Int), 1},
REVERT: {2, new(big.Int), 0},
RETURNDATACOPY: {3, new(big.Int), 0},
SUICIDE: {1, new(big.Int), 0},
Expand Down
18 changes: 18 additions & 0 deletions core/vm/instructions.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ type instruction struct {
}

var (
errWriteProtection = errors.New("evm: write protection")
errReturnDataOutOfBounds = errors.New("evm: return data out of bounds")
errInvalidJump = errors.New("evm: invalid jump destination")
)
Expand Down Expand Up @@ -605,6 +606,23 @@ func opDelegateCall(pc *uint64, env Environment, contract *Contract, memory *Mem
return ret, nil
}

func opStaticCall(pc *uint64, env Environment, contract *Contract, memory *Memory, stack *stack) ([]byte, error) {
gas, addr, inOffset, inSize, outOffset, outSize := stack.pop(), stack.pop(), stack.pop(), stack.pop(), stack.pop(), stack.pop()

toAddr := common.BigToAddress(addr)
args := memory.Get(inOffset.Int64(), inSize.Int64())
ret, err := env.StaticCall(contract, toAddr, args, gas, contract.Price)
if err != nil {
stack.push(new(big.Int))
} else {
stack.push(big.NewInt(1))
}
if err == nil || err == ErrRevert {
memory.Set(outOffset.Uint64(), outSize.Uint64(), ret)
}
return ret, nil
}

func opReturn(pc *uint64, env Environment, contract *Contract, memory *Memory, stack *stack) ([]byte, error) {
offset, size := stack.pop(), stack.pop()
ret := memory.GetPtr(offset.Int64(), size.Int64())
Expand Down
44 changes: 29 additions & 15 deletions core/vm/jump_table.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ type jumpPtr struct {
halts bool // indicates whether the operation should halt further execution
reverts bool // determines whether the operation reverts state (implicitly halts)
returns bool // Indicates whether return data should be overwritten
writes bool // determines whether this a state modifying operation
}

type vmJumpTable [256]jumpPtr
Expand Down Expand Up @@ -58,6 +59,11 @@ func newJumpTable(ruleset RuleSet, blockNumber *big.Int) vmJumpTable {
fn: opReturnDataCopy,
valid: true,
}
jumpTable[STATICCALL] = jumpPtr{
fn: opStaticCall,
valid: true,
returns: true,
}
}

return jumpTable
Expand Down Expand Up @@ -254,8 +260,9 @@ func newFrontierInstructionSet() vmJumpTable {
valid: true,
},
SSTORE: {
fn: opSstore,
valid: true,
fn: opSstore,
valid: true,
writes: true,
},
JUMPDEST: {
fn: opJumpdest,
Expand All @@ -276,6 +283,7 @@ func newFrontierInstructionSet() vmJumpTable {
CREATE: {
fn: opCreate,
valid: true,
writes: true,
returns: true,
},
CALL: {
Expand All @@ -289,24 +297,29 @@ func newFrontierInstructionSet() vmJumpTable {
returns: true,
},
LOG0: {
fn: makeLog(0),
valid: true,
fn: makeLog(0),
valid: true,
writes: true,
},
LOG1: {
fn: makeLog(1),
valid: true,
fn: makeLog(1),
valid: true,
writes: true,
},
LOG2: {
fn: makeLog(2),
valid: true,
fn: makeLog(2),
valid: true,
writes: true,
},
LOG3: {
fn: makeLog(3),
valid: true,
fn: makeLog(3),
valid: true,
writes: true,
},
LOG4: {
fn: makeLog(4),
valid: true,
fn: makeLog(4),
valid: true,
writes: true,
},
SWAP1: {
fn: makeSwap(1),
Expand Down Expand Up @@ -570,9 +583,10 @@ func newFrontierInstructionSet() vmJumpTable {
halts: true,
},
SUICIDE: {
fn: opSuicide,
valid: true,
halts: true,
fn: opSuicide,
valid: true,
halts: true,
writes: true,
},
JUMP: {
fn: opJump,
Expand Down
3 changes: 3 additions & 0 deletions core/vm/opcodes.go
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,7 @@ const (
CALLCODE
RETURN
DELEGATECALL
STATICCALL = 0xfa

REVERT = 0xfd
SUICIDE = 0xff
Expand Down Expand Up @@ -360,6 +361,7 @@ var opCodeToString = map[OpCode]string{
RETURN: "RETURN",
CALLCODE: "CALLCODE",
DELEGATECALL: "DELEGATECALL",
STATICCALL: "STATICCALL",
REVERT: "REVERT",
SUICIDE: "SUICIDE",

Expand Down Expand Up @@ -509,6 +511,7 @@ var stringToOp = map[string]OpCode{
"CALL": CALL,
"RETURN": RETURN,
"CALLCODE": CALLCODE,
"STATICCALL": STATICCALL,
"REVERT": REVERT,
"SUICIDE": SUICIDE,
}
Expand Down
4 changes: 4 additions & 0 deletions core/vm/runtime/env.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,10 @@ func (self *Env) DelegateCall(me vm.ContractRef, addr common.Address, data []byt
return core.DelegateCall(self, me, addr, data, gas, price)
}

func (self *Env) StaticCall(me vm.ContractRef, addr common.Address, data []byte, gas, price *big.Int) ([]byte, error) {
return core.StaticCall(self, me, addr, data, gas, price)
}

func (self *Env) Create(caller vm.ContractRef, data []byte, gas, price, value *big.Int) ([]byte, common.Address, error) {
return core.Create(self, caller, data, gas, price, value)
}
43 changes: 41 additions & 2 deletions core/vm/vm.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ type EVM struct {
env Environment
jumpTable vmJumpTable
gasTable GasTable
readOnly bool
}

// New returns a new instance of the EVM.
Expand All @@ -59,10 +60,17 @@ func New(env Environment) *EVM {
}

// Run loops and evaluates the contract's code with the given input data
func (evm *EVM) Run(contract *Contract, input []byte) (ret []byte, err error) {
func (evm *EVM) Run(contract *Contract, input []byte, readOnly bool) (ret []byte, err error) {
evm.env.SetDepth(evm.env.Depth() + 1)
defer evm.env.SetDepth(evm.env.Depth() - 1)

// 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 readOnly && !evm.readOnly {
evm.readOnly = true
defer func() { evm.readOnly = false }()
}

// Reset the previous call's return data. It's unimportant to preserve the old buffer
// as every returning call will return new data anyway.
evm.env.SetReturnData(nil)
Expand Down Expand Up @@ -94,6 +102,8 @@ func (evm *EVM) Run(contract *Contract, input []byte) (ret []byte, err error) {
caller = contract.caller
instrCount = 0

isAtlantis = evm.env.RuleSet().IsAtlantis(evm.env.BlockNumber())

op OpCode // current opcode
mem = NewMemory() // bound memory
stack = newstack() // local stack
Expand All @@ -118,12 +128,25 @@ func (evm *EVM) Run(contract *Contract, input []byte) (ret []byte, err error) {
for ; ; instrCount++ {
// Get the memory location of pc
op = contract.GetOp(pc)
operation := evm.jumpTable[op]
// calculate the new memory size and gas price for the current executing opcode
newMemSize, cost, err = calculateGasAndSize(&evm.gasTable, evm.env, contract, caller, op, statedb, mem, stack)
if err != nil {
return nil, err
}

// If the operation is valid, enforce and write restrictions
if evm.readOnly && isAtlantis {
// 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. Transferring 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).Sign() != 0) {
return nil, errWriteProtection
}
}

// Use the calculated gas. When insufficient gas is present, use all gas and return an
// Out Of Gas error
if !contract.UseGas(cost) {
Expand All @@ -132,7 +155,6 @@ func (evm *EVM) Run(contract *Contract, input []byte) (ret []byte, err error) {

// Resize the memory calculated previously
mem.Resize(newMemSize.Uint64())
operation := evm.jumpTable[op]
if !operation.valid {
return nil, fmt.Errorf("Invalid opcode %x", op)
}
Expand Down Expand Up @@ -365,7 +387,24 @@ func calculateGasAndSize(gasTable *GasTable, env Environment, contract *Contract
// called.
stack.data[stack.len()-1] = cg
gas.Add(gas, cg)
case STATICCALL:
gas.Set(gasTable.Calls)

x := calcMemSize(stack.back(4), stack.back(5))
y := calcMemSize(stack.back(2), stack.back(3))

newMemSize = common.BigMax(x, y)

quadMemGas(mem, newMemSize, gas)

cg := callGas(gasTable, contract.Gas, gas, stack.back(0))
// 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] = cg
gas.Add(gas, cg)
}

return newMemSize, gas, nil
Expand Down
4 changes: 4 additions & 0 deletions core/vm_env.go
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,10 @@ func (self *VMEnv) DelegateCall(me vm.ContractRef, addr common.Address, data []b
return DelegateCall(self, me, addr, data, gas, price)
}

func (self *VMEnv) StaticCall(me vm.ContractRef, addr common.Address, data []byte, gas, price *big.Int) ([]byte, error) {
return StaticCall(self, me, addr, data, gas, price)
}

func (self *VMEnv) Create(me vm.ContractRef, data []byte, gas, price, value *big.Int) ([]byte, common.Address, error) {
return Create(self, me, data, gas, price, value)
}
10 changes: 6 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ github.com/eth-classic/benchmark v0.0.0-20190401191651-0f5bf26f7cd8/go.mod h1:/H
github.com/eth-classic/ethash v0.0.0-20190401191819-b3fdb17512de h1:pITmUG7+V+zKZuytaz1p92qJafw5tQDOdp5ir5L9Eic=
github.com/eth-classic/ethash v0.0.0-20190401191819-b3fdb17512de/go.mod h1:W/nJfwtfsIfe4wL6cn4/MB251HfunQ+2YspZFvQ1lXA=
github.com/eth-classic/go-ethereum v0.0.0-20190521151733-61b178b4deeb/go.mod h1:SE2Tm8M3TSoN2s9L1cJvCNQoY0mzUxV3Do8/nsNbrWQ=
github.com/eth-classic/go-ethereum/accounts/abi/bind v0.0.0-20190521151733-aaaa9d2e032a h1:YW6bRzag/8DZo3gNlpbhcpbUuZ77WdcOfvZl5SBYkRw=
github.com/eth-classic/go-ethereum/accounts/abi/bind v0.0.0-20190521151733-aaaa9d2e032a/go.mod h1:N45ToIEdDjjzHVQq1I7qeyg+X3Mz76sA5FdIaPwquGU=
github.com/eth-classic/go-ethereum/accounts/abi/bind v0.0.0-20190521151733-fe17e9e1e2ce h1:s4NMPGFYsBRX+LGJAFZP4HG6DzZMhvQ++H0/Y712pFw=
github.com/eth-classic/go-ethereum/accounts/abi/bind v0.0.0-20190521151733-fe17e9e1e2ce/go.mod h1:N45ToIEdDjjzHVQq1I7qeyg+X3Mz76sA5FdIaPwquGU=
github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
Expand Down Expand Up @@ -40,11 +40,13 @@ github.com/maruel/panicparse v1.1.1 h1:k62YPcEoLncEEpjMt92GtG5ugb8WL/510Ys3/h5Ik
github.com/maruel/panicparse v1.1.1/go.mod h1:nty42YY5QByNC5MM7q/nj938VbgPU7avs45z6NClpxI=
github.com/maruel/panicparse v1.2.0 h1:lcFfc3+EidyWRSHT1OPIIQYzCmZ30u0Z+qio+IL9KFQ=
github.com/maruel/panicparse v1.2.0/go.mod h1:vszMjr5QQ4F5FSRfraldcIA/BCw5xrdLL+zEcU2nRBs=
github.com/mattn/go-colorable v0.1.1 h1:G1f5SKeVxmagw/IyvzvtZE4Gybcc4Tr1tf7I8z0XgOg=
github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU=
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.7 h1:UvyT9uN+3r7yLEYSlJsbQGdsaB/a0DlgWP3pql6iwOc=
github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.8 h1:HLtExJ+uU2HOZ+wI0Tt5DtUDrx8yhUqDcp7fYERX4CE=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-runewidth v0.0.3 h1:a+kO+98RDGEfo6asOGMmpodZq4FNtnGP54yps8BzLR4=
github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
Expand Down
Loading

0 comments on commit bfb67d2

Please sign in to comment.