aboutsummaryrefslogtreecommitdiffstats
path: root/core/vm
diff options
context:
space:
mode:
Diffstat (limited to 'core/vm')
-rw-r--r--core/vm/contract.go48
-rw-r--r--core/vm/contracts.go3
-rw-r--r--core/vm/environment.go3
-rw-r--r--core/vm/errors.go1
-rw-r--r--core/vm/gas.go1
-rw-r--r--core/vm/instructions.go40
-rw-r--r--core/vm/jit.go21
-rw-r--r--core/vm/jit_test.go5
-rw-r--r--core/vm/jump_table.go20
-rw-r--r--core/vm/jump_table_test.go24
-rw-r--r--core/vm/opcodes.go13
-rw-r--r--core/vm/runtime/env.go4
-rw-r--r--core/vm/vm.go31
13 files changed, 160 insertions, 54 deletions
diff --git a/core/vm/contract.go b/core/vm/contract.go
index 5981dcca0..d23995218 100644
--- a/core/vm/contract.go
+++ b/core/vm/contract.go
@@ -26,15 +26,20 @@ import (
type ContractRef interface {
ReturnGas(*big.Int, *big.Int)
Address() common.Address
+ Value() *big.Int
SetCode([]byte)
EachStorage(cb func(key, value []byte))
}
// Contract represents an ethereum contract in the state database. It contains
-// the the contract code, calling arguments. Contract implements ContractReg
+// the the contract code, calling arguments. Contract implements ContractRef
type Contract struct {
- caller ContractRef
- self ContractRef
+ // CallerAddress is the result of the caller which initialised this
+ // contract. However when the "call method" is delegated this value
+ // needs to be initialised to that of the caller's caller.
+ CallerAddress common.Address
+ caller ContractRef
+ self ContractRef
jumpdests destinations // result of JUMPDEST analysis.
@@ -45,11 +50,13 @@ type Contract struct {
value, Gas, UsedGas, Price *big.Int
Args []byte
+
+ DelegateCall bool
}
-// Create a new context for the given data items.
+// NewContract returns a new contract environment for the execution of EVM.
func NewContract(caller ContractRef, object ContractRef, value, gas, price *big.Int) *Contract {
- c := &Contract{caller: caller, self: object, Args: nil}
+ c := &Contract{CallerAddress: caller.Address(), caller: caller, self: object, Args: nil}
if parent, ok := caller.(*Contract); ok {
// Reuse JUMPDEST analysis from parent context if available.
@@ -70,6 +77,16 @@ func NewContract(caller ContractRef, object ContractRef, value, gas, price *big.
return c
}
+// AsDelegate sets the contract to be a delegate call and returns the current
+// contract (for chaining calls)
+func (c *Contract) AsDelegate() *Contract {
+ c.DelegateCall = true
+ // NOTE: caller must, at all times be a contract. It should never happen
+ // that caller is something other than a Contract.
+ c.CallerAddress = c.caller.(*Contract).CallerAddress
+ return c
+}
+
// GetOp returns the n'th element in the contract's byte array
func (c *Contract) GetOp(n uint64) OpCode {
return OpCode(c.GetByte(n))
@@ -84,13 +101,19 @@ func (c *Contract) GetByte(n uint64) byte {
return 0
}
-// Return returns the given ret argument and returns any remaining gas to the
-// caller
-func (c *Contract) Return(ret []byte) []byte {
+// Caller returns the caller of the contract.
+//
+// Caller will recursively call caller when the contract is a delegate
+// call, including that of caller's caller.
+func (c *Contract) Caller() common.Address {
+ return c.CallerAddress
+}
+
+// Finalise finalises the contract and returning any remaining gas to the original
+// caller.
+func (c *Contract) Finalise() {
// Return the remaining gas to the caller
c.caller.ReturnGas(c.Gas, c.Price)
-
- return ret
}
// UseGas attempts the use gas and subtracts it and returns true on success
@@ -114,6 +137,11 @@ func (c *Contract) Address() common.Address {
return c.self.Address()
}
+// Value returns the contracts value (sent to it from it's caller)
+func (c *Contract) Value() *big.Int {
+ return c.value
+}
+
// SetCode sets the code to the contract
func (self *Contract) SetCode(code []byte) {
self.Code = code
diff --git a/core/vm/contracts.go b/core/vm/contracts.go
index 22cb9eab2..f204432a2 100644
--- a/core/vm/contracts.go
+++ b/core/vm/contracts.go
@@ -93,7 +93,8 @@ func ecrecoverFunc(in []byte) []byte {
vbig := common.Bytes2Big(in[32:64])
v := byte(vbig.Uint64())
- if !crypto.ValidateSignatureValues(v, r, s) {
+ // tighter sig s values in homestead only apply to tx sigs
+ if !crypto.ValidateSignatureValues(v, r, s, false) {
glog.V(logger.Debug).Infof("EC RECOVER FAIL: v, r or s value invalid")
return nil
}
diff --git a/core/vm/environment.go b/core/vm/environment.go
index 4fee583bf..a58e3ba2b 100644
--- a/core/vm/environment.go
+++ b/core/vm/environment.go
@@ -70,6 +70,8 @@ type Environment interface {
Call(me ContractRef, addr common.Address, data []byte, gas, price, value *big.Int) ([]byte, error)
// Take another's contract code and execute within our own context
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)
// Create a new contract
Create(me ContractRef, data []byte, gas, price, value *big.Int) ([]byte, common.Address, error)
}
@@ -122,4 +124,5 @@ type Account interface {
ReturnGas(*big.Int, *big.Int)
SetCode([]byte)
EachStorage(cb func(key, value []byte))
+ Value() *big.Int
}
diff --git a/core/vm/errors.go b/core/vm/errors.go
index e2fc84065..116fbe456 100644
--- a/core/vm/errors.go
+++ b/core/vm/errors.go
@@ -24,4 +24,5 @@ import (
)
var OutOfGasError = errors.New("Out of gas")
+var CodeStoreOutOfGasError = errors.New("Contract creation code storage out of gas")
var DepthError = fmt.Errorf("Max call depth exceeded (%d)", params.CallCreateDepth)
diff --git a/core/vm/gas.go b/core/vm/gas.go
index bff0ac91b..09feddd7d 100644
--- a/core/vm/gas.go
+++ b/core/vm/gas.go
@@ -136,6 +136,7 @@ var _baseCheck = map[OpCode]req{
CREATE: {3, params.CreateGas, 1},
CALL: {7, params.CallGas, 1},
CALLCODE: {7, params.CallGas, 1},
+ DELEGATECALL: {6, params.CallGas, 1},
JUMPDEST: {0, params.JumpdestGas, 0},
SUICIDE: {1, Zero, 0},
RETURN: {2, Zero, 0},
diff --git a/core/vm/instructions.go b/core/vm/instructions.go
index 2e868521e..26f7671ff 100644
--- a/core/vm/instructions.go
+++ b/core/vm/instructions.go
@@ -337,7 +337,7 @@ func opOrigin(instr instruction, pc *uint64, env Environment, contract *Contract
}
func opCaller(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *stack) {
- stack.push(common.Bytes2Big(contract.caller.Address().Bytes()))
+ stack.push(contract.Caller().Big())
}
func opCallValue(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *stack) {
@@ -485,7 +485,6 @@ func opSload(instr instruction, pc *uint64, env Environment, contract *Contract,
func opSstore(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *stack) {
loc := common.BigToHash(stack.pop())
val := stack.pop()
-
env.Db().SetState(contract.Address(), loc, common.BigToHash(val))
}
@@ -514,25 +513,19 @@ func opCreate(instr instruction, pc *uint64, env Environment, contract *Contract
offset, size = stack.pop(), stack.pop()
input = memory.Get(offset.Int64(), size.Int64())
gas = new(big.Int).Set(contract.Gas)
- addr common.Address
- ret []byte
- suberr error
)
-
contract.UseGas(contract.Gas)
- ret, addr, suberr = env.Create(contract, input, gas, contract.Price, value)
- if suberr != nil {
+ _, addr, suberr := env.Create(contract, input, gas, contract.Price, value)
+ // Push item on the stack based on the returned error. If the ruleset is
+ // homestead we must check for CodeStoreOutOfGasError (homestead only
+ // rule) and treat as an error, if the ruleset is frontier we must
+ // ignore this error and pretend the operation was successful.
+ if params.IsHomestead(env.BlockNumber()) && suberr == CodeStoreOutOfGasError {
+ stack.push(new(big.Int))
+ } else if suberr != nil && suberr != CodeStoreOutOfGasError {
stack.push(new(big.Int))
} else {
- // gas < len(ret) * Createinstr.dataGas == NO_CODE
- dataGas := big.NewInt(int64(len(ret)))
- dataGas.Mul(dataGas, params.CreateDataGas)
- if contract.UseGas(dataGas) {
- env.Db().SetCode(addr, ret)
- }
-
stack.push(addr.Big())
-
}
}
@@ -598,6 +591,21 @@ func opCallCode(instr instruction, pc *uint64, env Environment, contract *Contra
}
}
+func opDelegateCall(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *stack) {
+ gas, to, inOffset, inSize, outOffset, outSize := stack.pop(), stack.pop(), stack.pop(), stack.pop(), stack.pop(), stack.pop()
+
+ toAddr := common.BigToAddress(to)
+ args := memory.Get(inOffset.Int64(), inSize.Int64())
+ ret, err := env.DelegateCall(contract, toAddr, args, gas, contract.Price)
+
+ if err != nil {
+ stack.push(new(big.Int))
+ } else {
+ stack.push(big.NewInt(1))
+ memory.Set(outOffset.Uint64(), outSize.Uint64(), ret)
+ }
+}
+
func opReturn(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *stack) {
}
func opStop(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *stack) {
diff --git a/core/vm/jit.go b/core/vm/jit.go
index 1aa7d7ef2..504aab523 100644
--- a/core/vm/jit.go
+++ b/core/vm/jit.go
@@ -275,6 +275,11 @@ func CompileProgram(program *Program) (err error) {
program.addInstr(op, pc, opGas, nil)
case CREATE:
program.addInstr(op, pc, opCreate, nil)
+ case DELEGATECALL:
+ // Instruction added regardless of homestead phase.
+ // Homestead (and execution of the opcode) is checked during
+ // runtime.
+ program.addInstr(op, pc, opDelegateCall, nil)
case CALL:
program.addInstr(op, pc, opCall, nil)
case CALLCODE:
@@ -317,10 +322,14 @@ func runProgram(program *Program, pcstart uint64, mem *Memory, stack *stack, env
}()
}
+ homestead := params.IsHomestead(env.BlockNumber())
for pc < uint64(len(program.instructions)) {
instrCount++
instr := program.instructions[pc]
+ if instr.Op() == DELEGATECALL && !homestead {
+ return nil, fmt.Errorf("Invalid opcode 0x%x", instr.Op())
+ }
ret, err := instr.do(program, &pc, env, contract, mem, stack)
if err != nil {
@@ -328,13 +337,13 @@ func runProgram(program *Program, pcstart uint64, mem *Memory, stack *stack, env
}
if instr.halts() {
- return contract.Return(ret), nil
+ return ret, nil
}
}
contract.Input = nil
- return contract.Return(nil), nil
+ return nil, nil
}
// validDest checks if the given distination is a valid one given the
@@ -457,7 +466,6 @@ func jitCalculateGasAndSize(env Environment, contract *Contract, instr instructi
gas.Add(gas, stack.data[stack.len()-1])
if op == CALL {
- //if env.Db().GetStateObject(common.BigToAddress(stack.data[stack.len()-2])) == nil {
if !env.Db().Exist(common.BigToAddress(stack.data[stack.len()-2])) {
gas.Add(gas, params.CallNewAccountGas)
}
@@ -471,6 +479,13 @@ func jitCalculateGasAndSize(env Environment, contract *Contract, instr instructi
y := calcMemSize(stack.data[stack.len()-4], stack.data[stack.len()-5])
newMemSize = common.BigMax(x, y)
+ case DELEGATECALL:
+ gas.Add(gas, stack.data[stack.len()-1])
+
+ x := calcMemSize(stack.data[stack.len()-5], stack.data[stack.len()-6])
+ y := calcMemSize(stack.data[stack.len()-3], stack.data[stack.len()-4])
+
+ newMemSize = common.BigMax(x, y)
}
quadMemGas(mem, newMemSize, gas)
diff --git a/core/vm/jit_test.go b/core/vm/jit_test.go
index 8c50ed0f5..e8e078a46 100644
--- a/core/vm/jit_test.go
+++ b/core/vm/jit_test.go
@@ -127,6 +127,8 @@ type account struct{}
func (account) SubBalance(amount *big.Int) {}
func (account) AddBalance(amount *big.Int) {}
+func (account) SetAddress(common.Address) {}
+func (account) Value() *big.Int { return nil }
func (account) SetBalance(*big.Int) {}
func (account) SetNonce(uint64) {}
func (account) Balance() *big.Int { return nil }
@@ -206,3 +208,6 @@ func (self *Env) CallCode(caller ContractRef, addr common.Address, data []byte,
func (self *Env) Create(caller ContractRef, data []byte, gas, price, value *big.Int) ([]byte, common.Address, error) {
return nil, common.Address{}, nil
}
+func (self *Env) DelegateCall(me ContractRef, addr common.Address, data []byte, gas, price *big.Int) ([]byte, error) {
+ return nil, nil
+}
diff --git a/core/vm/jump_table.go b/core/vm/jump_table.go
index ab899647f..37d7bb160 100644
--- a/core/vm/jump_table.go
+++ b/core/vm/jump_table.go
@@ -1,13 +1,29 @@
package vm
-import "math/big"
+import (
+ "math/big"
+
+ "github.com/ethereum/go-ethereum/params"
+)
type jumpPtr struct {
fn instrFn
valid bool
}
-var jumpTable [256]jumpPtr
+type vmJumpTable [256]jumpPtr
+
+func (jt vmJumpTable) init(blockNumber *big.Int) {
+ // when initialising a new VM execution we must first check the homestead
+ // changes.
+ if params.IsHomestead(blockNumber) {
+ jumpTable[DELEGATECALL] = jumpPtr{opDelegateCall, true}
+ } else {
+ jumpTable[DELEGATECALL] = jumpPtr{nil, false}
+ }
+}
+
+var jumpTable vmJumpTable
func init() {
jumpTable[ADD] = jumpPtr{opAdd, true}
diff --git a/core/vm/jump_table_test.go b/core/vm/jump_table_test.go
new file mode 100644
index 000000000..98d34bef2
--- /dev/null
+++ b/core/vm/jump_table_test.go
@@ -0,0 +1,24 @@
+package vm
+
+import (
+ "math/big"
+ "testing"
+
+ "github.com/ethereum/go-ethereum/params"
+)
+
+func TestInit(t *testing.T) {
+ params.HomesteadBlock = big.NewInt(1)
+
+ jumpTable.init(big.NewInt(0))
+ if jumpTable[DELEGATECALL].valid {
+ t.Error("Expected DELEGATECALL not to be present")
+ }
+
+ for _, n := range []int64{1, 2, 100} {
+ jumpTable.init(big.NewInt(n))
+ if !jumpTable[DELEGATECALL].valid {
+ t.Error("Expected DELEGATECALL to be present for block", n)
+ }
+ }
+}
diff --git a/core/vm/opcodes.go b/core/vm/opcodes.go
index dc4139092..7d861f1de 100644
--- a/core/vm/opcodes.go
+++ b/core/vm/opcodes.go
@@ -200,6 +200,7 @@ const (
CALL
CALLCODE
RETURN
+ DELEGATECALL
SUICIDE = 0xff
)
@@ -349,11 +350,12 @@ var opCodeToString = map[OpCode]string{
LOG4: "LOG4",
// 0xf0 range
- CREATE: "CREATE",
- CALL: "CALL",
- RETURN: "RETURN",
- CALLCODE: "CALLCODE",
- SUICIDE: "SUICIDE",
+ CREATE: "CREATE",
+ CALL: "CALL",
+ RETURN: "RETURN",
+ CALLCODE: "CALLCODE",
+ DELEGATECALL: "DELEGATECALL",
+ SUICIDE: "SUICIDE",
PUSH: "PUSH",
DUP: "DUP",
@@ -402,6 +404,7 @@ var stringToOp = map[string]OpCode{
"CALLDATALOAD": CALLDATALOAD,
"CALLDATASIZE": CALLDATASIZE,
"CALLDATACOPY": CALLDATACOPY,
+ "DELEGATECALL": DELEGATECALL,
"CODESIZE": CODESIZE,
"CODECOPY": CODECOPY,
"GASPRICE": GASPRICE,
diff --git a/core/vm/runtime/env.go b/core/vm/runtime/env.go
index 22f9ea14d..77519df81 100644
--- a/core/vm/runtime/env.go
+++ b/core/vm/runtime/env.go
@@ -101,6 +101,10 @@ func (self *Env) CallCode(caller vm.ContractRef, addr common.Address, data []byt
return core.CallCode(self, caller, addr, data, gas, price, value)
}
+func (self *Env) DelegateCall(me vm.ContractRef, addr common.Address, data []byte, gas, price *big.Int) ([]byte, error) {
+ return core.DelegateCall(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)
}
diff --git a/core/vm/vm.go b/core/vm/vm.go
index 0c6bbcd42..320135ff2 100644
--- a/core/vm/vm.go
+++ b/core/vm/vm.go
@@ -35,6 +35,9 @@ type Vm struct {
// New returns a new Vm
func New(env Environment) *Vm {
+ // init the jump table. Also prepares the homestead changes
+ jumpTable.init(env.BlockNumber())
+
return &Vm{env: env}
}
@@ -43,16 +46,6 @@ func (self *Vm) Run(contract *Contract, input []byte) (ret []byte, err error) {
self.env.SetDepth(self.env.Depth() + 1)
defer self.env.SetDepth(self.env.Depth() - 1)
- // User defer pattern to check for an error and, based on the error being nil or not, use all gas and return.
- defer func() {
- if err != nil {
- // In case of a VM exception (known exceptions) all gas consumed (panics NOT included).
- contract.UseGas(contract.Gas)
-
- ret = contract.Return(nil)
- }
- }()
-
if contract.CodeAddr != nil {
if p := Precompiled[contract.CodeAddr.Str()]; p != nil {
return self.RunPrecompiled(p, input, contract)
@@ -61,7 +54,7 @@ func (self *Vm) Run(contract *Contract, input []byte) (ret []byte, err error) {
// Don't bother with the execution if there's no code.
if len(contract.Code) == 0 {
- return contract.Return(nil), nil
+ return nil, nil
}
var (
@@ -160,7 +153,6 @@ func (self *Vm) Run(contract *Contract, input []byte) (ret []byte, err error) {
// Get the memory location of pc
op = contract.GetOp(pc)
-
// calculate the new memory size and gas price for the current executing opcode
newMemSize, cost, err = calculateGasAndSize(self.env, contract, caller, op, statedb, mem, stack)
if err != nil {
@@ -177,7 +169,6 @@ func (self *Vm) Run(contract *Contract, input []byte) (ret []byte, err error) {
mem.Resize(newMemSize.Uint64())
// Add a log message
self.log(pc, op, contract.Gas, cost, mem, stack, contract, nil)
-
if opPtr := jumpTable[op]; opPtr.valid {
if opPtr.fn != nil {
opPtr.fn(instruction{}, &pc, self.env, contract, mem, stack)
@@ -205,13 +196,13 @@ func (self *Vm) Run(contract *Contract, input []byte) (ret []byte, err error) {
offset, size := stack.pop(), stack.pop()
ret := mem.GetPtr(offset.Int64(), size.Int64())
- return contract.Return(ret), nil
+ return ret, nil
case SUICIDE:
opSuicide(instruction{}, nil, self.env, contract, mem, stack)
fallthrough
case STOP: // Stop the contract
- return contract.Return(nil), nil
+ return nil, nil
}
}
} else {
@@ -332,7 +323,6 @@ func calculateGasAndSize(env Environment, contract *Contract, caller ContractRef
gas.Add(gas, stack.data[stack.len()-1])
if op == CALL {
- //if env.Db().GetStateObject(common.BigToAddress(stack.data[stack.len()-2])) == nil {
if !env.Db().Exist(common.BigToAddress(stack.data[stack.len()-2])) {
gas.Add(gas, params.CallNewAccountGas)
}
@@ -346,6 +336,13 @@ func calculateGasAndSize(env Environment, contract *Contract, caller ContractRef
y := calcMemSize(stack.data[stack.len()-4], stack.data[stack.len()-5])
newMemSize = common.BigMax(x, y)
+ case DELEGATECALL:
+ gas.Add(gas, stack.data[stack.len()-1])
+
+ x := calcMemSize(stack.data[stack.len()-5], stack.data[stack.len()-6])
+ y := calcMemSize(stack.data[stack.len()-3], stack.data[stack.len()-4])
+
+ newMemSize = common.BigMax(x, y)
}
quadMemGas(mem, newMemSize, gas)
@@ -358,7 +355,7 @@ func (self *Vm) RunPrecompiled(p *PrecompiledAccount, input []byte, contract *Co
if contract.UseGas(gas) {
ret = p.Call(input)
- return contract.Return(ret), nil
+ return ret, nil
} else {
return nil, OutOfGasError
}