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, params: implemented STATIC_CALL - eip#116 #3634

Closed
wants to merge 1 commit into from
Closed
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
1 change: 1 addition & 0 deletions build/ci.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ var (
allToolsArchiveFiles = []string{
"COPYING",
executablePath("abigen"),
executablePath("bootnode"),
executablePath("evm"),
executablePath("geth"),
executablePath("swarm"),
Expand Down
45 changes: 45 additions & 0 deletions core/vm/evm.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,51 @@ func (evm *EVM) Cancel() {
atomic.StoreInt32(&evm.abort, 1)
}

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, 0, errWriteProtection
}

// initialise a new contract and set the code that is to be used by the
// E 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(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
}

// 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.
Expand Down
40 changes: 39 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 @@ -27,7 +28,10 @@ import (
"github.com/ethereum/go-ethereum/params"
)

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

func opAdd(pc *uint64, evm *EVM, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) {
x, y := stack.pop(), stack.pop()
Expand Down Expand Up @@ -569,6 +573,40 @@ func opCreate(pc *uint64, evm *EVM, contract *Contract, memory *Memory, stack *S
return nil, nil
}

func opStaticCall(pc *uint64, evm *EVM, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) {
// EIP116 availability check; return an invalid opcode error on fault
if !evm.ChainConfig().IsMetropolis(evm.BlockNumber) {
return nil, fmt.Errorf("invalid opcode %x", STATIC_CALL)
}

// 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 nil, nil
}

func opCall(pc *uint64, evm *EVM, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) {
gas := stack.pop().Uint64()
// pop gas and value of the stack.
Expand Down
11 changes: 11 additions & 0 deletions core/vm/interpreter.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ type Interpreter struct {
cfg Config
gasTable params.GasTable
intPool *intPool

readonly bool
}

// NewInterpreter returns a new instance of the Interpreter.
Expand Down Expand Up @@ -137,6 +139,15 @@ func (evm *Interpreter) Run(contract *Contract, input []byte) (ret []byte, err e

// get the operation from the jump table matching the opcode
operation := evm.cfg.JumpTable[op]
// if the interpreter is operating in readonly mode, make sure no
// state-modifying operation is performed.
if evm.readonly && evm.env.chainConfig.IsMetropolis(evm.env.BlockNumber) {
if operation.writes ||
((op == CALL || op == CALLCODE) && stack.Back(3).BitLen() > 0) {
return nil, errWriteProtection
}

}

// if the op is invalid abort the process and return an error
if !operation.valid {
Expand Down
5 changes: 5 additions & 0 deletions core/vm/jump_table.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ type operation struct {
// jumps indicates whether operation made a jump. This prevents the program
// counter from further incrementing.
jumps bool
// writes determines whether this a state modifying operation
writes bool
// valid is used to check whether the retrieved operation is valid and known
valid bool
}
Expand Down Expand Up @@ -357,6 +359,7 @@ func NewJumpTable() [256]operation {
gasCost: gasSStore,
validateStack: makeStackFunc(2, 0),
valid: true,
writes: true,
},
JUMP: {
execute: opJump,
Expand Down Expand Up @@ -821,6 +824,7 @@ func NewJumpTable() [256]operation {
validateStack: makeStackFunc(3, 1),
memorySize: memoryCreate,
valid: true,
writes: true,
},
CALL: {
execute: opCall,
Expand Down Expand Up @@ -857,6 +861,7 @@ func NewJumpTable() [256]operation {
validateStack: makeStackFunc(1, 0),
halts: true,
valid: true,
writes: true,
},
}
}
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
STATIC_CALL

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

PUSH: "PUSH",
DUP: "DUP",
Expand Down Expand Up @@ -405,6 +407,7 @@ var stringToOp = map[string]OpCode{
"CALLDATASIZE": CALLDATASIZE,
"CALLDATACOPY": CALLDATACOPY,
"DELEGATECALL": DELEGATECALL,
"STATIC_CALL": STATIC_CALL,
"CODESIZE": CODESIZE,
"CODECOPY": CODECOPY,
"GASPRICE": GASPRICE,
Expand Down
54 changes: 34 additions & 20 deletions params/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,26 +25,28 @@ import (

// MainnetChainConfig is the chain parameters to run a node on the main network.
var MainnetChainConfig = &ChainConfig{
ChainId: MainNetChainID,
HomesteadBlock: MainNetHomesteadBlock,
DAOForkBlock: MainNetDAOForkBlock,
DAOForkSupport: true,
EIP150Block: MainNetHomesteadGasRepriceBlock,
EIP150Hash: MainNetHomesteadGasRepriceHash,
EIP155Block: MainNetSpuriousDragon,
EIP158Block: MainNetSpuriousDragon,
ChainId: MainNetChainID,
HomesteadBlock: MainNetHomesteadBlock,
DAOForkBlock: MainNetDAOForkBlock,
DAOForkSupport: true,
EIP150Block: MainNetHomesteadGasRepriceBlock,
EIP150Hash: MainNetHomesteadGasRepriceHash,
EIP155Block: MainNetSpuriousDragon,
EIP158Block: MainNetSpuriousDragon,
MetropolisBlock: MainNetMetropolisBlock,
}

// TestnetChainConfig is the chain parameters to run a node on the test network.
var TestnetChainConfig = &ChainConfig{
ChainId: big.NewInt(3),
HomesteadBlock: big.NewInt(0),
DAOForkBlock: nil,
DAOForkSupport: true,
EIP150Block: big.NewInt(0),
EIP150Hash: common.HexToHash("0x41941023680923e0fe4d74a34bdac8141f2540e3ae90623718e47d66d1ca4a2d"),
EIP155Block: big.NewInt(10),
EIP158Block: big.NewInt(10),
ChainId: big.NewInt(3),
HomesteadBlock: big.NewInt(0),
DAOForkBlock: nil,
DAOForkSupport: true,
EIP150Block: big.NewInt(0),
EIP150Hash: common.HexToHash("0x41941023680923e0fe4d74a34bdac8141f2540e3ae90623718e47d66d1ca4a2d"),
EIP155Block: big.NewInt(10),
EIP158Block: big.NewInt(10),
MetropolisBlock: big.NewInt(0),
}

// AllProtocolChanges contains every protocol change (EIPs)
Expand All @@ -55,7 +57,7 @@ var TestnetChainConfig = &ChainConfig{
// means that all fields must be set at all times. This forces
// anyone adding flags to the config to also have to set these
// fields.
var AllProtocolChanges = &ChainConfig{big.NewInt(1337), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0)}
var AllProtocolChanges = &ChainConfig{big.NewInt(1337), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0)}

// ChainConfig is the core config which determines the blockchain settings.
//
Expand All @@ -75,23 +77,26 @@ type ChainConfig struct {

EIP155Block *big.Int `json:"eip155Block"` // EIP155 HF block
EIP158Block *big.Int `json:"eip158Block"` // EIP158 HF block

MetropolisBlock *big.Int `json:"metropolisBlock"` // Metropolis switch block (nil = no fork, 0 = alraedy on homestead)
}

// String implements the Stringer interface.
func (c *ChainConfig) String() string {
return fmt.Sprintf("{ChainID: %v Homestead: %v DAO: %v DAOSupport: %v EIP150: %v EIP155: %v EIP158: %v}",
return fmt.Sprintf("{ChainID: %v Homestead: %v DAO: %v DAOSupport: %v EIP150: %v EIP155: %v EIP158: %v Metropolis: %v}",
c.ChainId,
c.HomesteadBlock,
c.DAOForkBlock,
c.DAOForkSupport,
c.EIP150Block,
c.EIP155Block,
c.EIP158Block,
c.MetropolisBlock,
)
}

var (
TestChainConfig = &ChainConfig{big.NewInt(1), new(big.Int), new(big.Int), true, new(big.Int), common.Hash{}, new(big.Int), new(big.Int)}
TestChainConfig = &ChainConfig{big.NewInt(1), new(big.Int), new(big.Int), true, new(big.Int), common.Hash{}, new(big.Int), new(big.Int), new(big.Int)}
TestRules = TestChainConfig.Rules(new(big.Int))
)

Expand Down Expand Up @@ -145,6 +150,14 @@ func (c *ChainConfig) IsEIP158(num *big.Int) bool {

}

func (c *ChainConfig) IsMetropolis(num *big.Int) bool {
if c.MetropolisBlock == nil || num == nil {
return false
}
return num.Cmp(c.MetropolisBlock) >= 0

}

// Rules wraps ChainConfig and is merely syntatic sugar or can be used for functions
// that do not have or require information about the block.
//
Expand All @@ -153,8 +166,9 @@ func (c *ChainConfig) IsEIP158(num *big.Int) bool {
type Rules struct {
ChainId *big.Int
IsHomestead, IsEIP150, IsEIP155, IsEIP158 bool
IsMetropolis bool
}

func (c *ChainConfig) Rules(num *big.Int) Rules {
return Rules{ChainId: new(big.Int).Set(c.ChainId), IsHomestead: c.IsHomestead(num), IsEIP150: c.IsEIP150(num), IsEIP155: c.IsEIP155(num), IsEIP158: c.IsEIP158(num)}
return Rules{ChainId: new(big.Int).Set(c.ChainId), IsHomestead: c.IsHomestead(num), IsEIP150: c.IsEIP150(num), IsEIP155: c.IsEIP155(num), IsEIP158: c.IsEIP158(num), IsMetropolis: c.IsMetropolis(num)}
}
2 changes: 2 additions & 0 deletions params/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ var (
TestNetSpuriousDragon = big.NewInt(10)
MainNetSpuriousDragon = big.NewInt(2675000)

MainNetMetropolisBlock = big.NewInt(5000000)

TestNetChainID = big.NewInt(3) // Test net default chain ID
MainNetChainID = big.NewInt(1) // main net default chain ID
)