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

64bit memory calculations #19210

Merged
merged 10 commits into from
Mar 12, 2019
3 changes: 0 additions & 3 deletions common/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -202,9 +202,6 @@ func IsHexAddress(s string) bool {
// Bytes gets the string representation of the underlying address.
func (a Address) Bytes() []byte { return a[:] }

// Big converts an address to a big integer.
func (a Address) Big() *big.Int { return new(big.Int).SetBytes(a[:]) }

// Hash converts an address to a hash by left-padding it with zeros.
func (a Address) Hash() Hash { return BytesToHash(a[:]) }

Expand Down
4 changes: 2 additions & 2 deletions common/types_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,8 +114,8 @@ func TestAddressUnmarshalJSON(t *testing.T) {
if test.ShouldErr {
t.Errorf("test #%d: expected error, got none", i)
}
if v.Big().Cmp(test.Output) != 0 {
t.Errorf("test #%d: address mismatch: have %v, want %v", i, v.Big(), test.Output)
if got := new(big.Int).SetBytes(v.Bytes()); got.Cmp(test.Output) != 0 {
t.Errorf("test #%d: address mismatch: have %v, want %v", i, got, test.Output)
}
}
}
Expand Down
3 changes: 2 additions & 1 deletion core/mkalloc.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,8 @@ func makelist(g *core.Genesis) allocList {
if len(account.Storage) > 0 || len(account.Code) > 0 || account.Nonce != 0 {
panic(fmt.Sprintf("can't encode account %x", addr))
}
a = append(a, allocItem{addr.Big(), account.Balance})
bigAddr := new(big.Int).SetBytes(addr.Bytes())
a = append(a, allocItem{bigAddr, account.Balance})
}
sort.Sort(a)
return a
Expand Down
30 changes: 24 additions & 6 deletions core/vm/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,31 @@ import (
"github.com/ethereum/go-ethereum/common/math"
)

// calculates the memory size required for a step
func calcMemSize(off, l *big.Int) *big.Int {
if l.Sign() == 0 {
return common.Big0
// calcMemSize64 calculates the required memory size, and returns
// the size and whether the result overflowed uint64
func calcMemSize64(off, l *big.Int) (uint64, bool) {
Copy link
Contributor

@fjl fjl Mar 11, 2019

Choose a reason for hiding this comment

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

if !l.IsUint64() {
    return 0, true
}
return calcMemSizeWithUint(off, l.Uint64())

if !l.IsUint64() {
return 0, true
}
return calcMemSize64WithUint(off, l.Uint64())
}

return new(big.Int).Add(off, l)
// calcMemSize64WithUint calculates the required memory size, and returns
// the size and whether the result overflowed uint64
// Identical to calcMemSize64, but length is a uint64
func calcMemSize64WithUint(off *big.Int, length64 uint64) (uint64, bool) {
// if length is zero, memsize is always zero, regardless of offset
if length64 == 0 {
return 0, false
}
// Check that offset doesn't overflow
if !off.IsUint64() {
return 0, true
}
offset64 := off.Uint64()
val := offset64 + length64
// if value < either of it's parts, then it overflowed
return val, val < offset64
}

// getData returns a slice from the data based on the start and size and pads
Expand Down Expand Up @@ -59,7 +77,7 @@ func getDataBig(data []byte, start *big.Int, size *big.Int) []byte {
// bigUint64 returns the integer casted to a uint64 and returns whether it
// overflowed in the process.
func bigUint64(v *big.Int) (uint64, bool) {
return v.Uint64(), v.BitLen() > 64
return v.Uint64(), !v.IsUint64()
}

// toWordSize returns the ceiled word size required for memory expansion.
Expand Down
4 changes: 2 additions & 2 deletions core/vm/gas.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,11 @@ func callGas(gasTable params.GasTable, availableGas, base uint64, callCost *big.
// If the bit length exceeds 64 bit we know that the newly calculated "gas" for EIP150
// is smaller than the requested amount. Therefor we return the new gas instead
// of returning an error.
if callCost.BitLen() > 64 || gas < callCost.Uint64() {
if !callCost.IsUint64() || gas < callCost.Uint64() {
return gas, nil
}
}
if callCost.BitLen() > 64 {
if !callCost.IsUint64() {
return 0, errGasUintOverflow
}

Expand Down
18 changes: 0 additions & 18 deletions core/vm/gas_table.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,12 +57,6 @@ func memoryGasCost(mem *Memory, newMemSize uint64) (uint64, error) {
return 0, nil
}

func constGasFunc(gas uint64) gasFunc {
return func(gt params.GasTable, evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
return gas, nil
}
}

func gasCallDataCopy(gt params.GasTable, evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
gas, err := memoryGasCost(mem, memorySize)
if err != nil {
Expand Down Expand Up @@ -521,15 +515,3 @@ func gasStaticCall(gt params.GasTable, evm *EVM, contract *Contract, stack *Stac
}
return gas, nil
}

func gasPush(gt params.GasTable, evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
return GasFastestStep, nil
}

func gasSwap(gt params.GasTable, evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
return GasFastestStep, nil
}

func gasDup(gt params.GasTable, evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
return GasFastestStep, nil
}
37 changes: 25 additions & 12 deletions core/vm/instructions.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ package vm

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

"github.com/ethereum/go-ethereum/common"
Expand All @@ -35,6 +34,7 @@ var (
errReturnDataOutOfBounds = errors.New("evm: return data out of bounds")
errExecutionReverted = errors.New("evm: execution reverted")
errMaxCodeSizeExceeded = errors.New("evm: max code size exceeded")
errInvalidJump = errors.New("evm: invalid jump destination")
)

func opAdd(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) {
Expand Down Expand Up @@ -405,7 +405,7 @@ func opSha3(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory
}

func opAddress(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) {
stack.push(contract.Address().Big())
stack.push(interpreter.intPool.get().SetBytes(contract.Address().Bytes()))
return nil, nil
}

Expand All @@ -416,12 +416,12 @@ func opBalance(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memo
}

func opOrigin(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) {
stack.push(interpreter.evm.Origin.Big())
stack.push(interpreter.intPool.get().SetBytes(interpreter.evm.Origin.Bytes()))
return nil, nil
}

func opCaller(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) {
stack.push(contract.Caller().Big())
stack.push(interpreter.intPool.get().SetBytes(contract.Caller().Bytes()))
return nil, nil
}

Expand Down Expand Up @@ -467,7 +467,7 @@ func opReturnDataCopy(pc *uint64, interpreter *EVMInterpreter, contract *Contrac
)
defer interpreter.intPool.put(memOffset, dataOffset, length, end)

if end.BitLen() > 64 || uint64(len(interpreter.returnData)) < end.Uint64() {
if !end.IsUint64() || uint64(len(interpreter.returnData)) < end.Uint64() {
return nil, errReturnDataOutOfBounds
}
memory.Set(memOffset.Uint64(), length.Uint64(), interpreter.returnData[dataOffset.Uint64():end.Uint64()])
Expand Down Expand Up @@ -572,7 +572,7 @@ func opBlockhash(pc *uint64, interpreter *EVMInterpreter, contract *Contract, me
}

func opCoinbase(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) {
stack.push(interpreter.evm.Coinbase.Big())
stack.push(interpreter.intPool.get().SetBytes(interpreter.evm.Coinbase.Bytes()))
return nil, nil
}

Expand Down Expand Up @@ -645,8 +645,7 @@ func opSstore(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memor
func opJump(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) {
pos := stack.pop()
if !contract.validJumpdest(pos) {
nop := contract.GetOp(pos.Uint64())
return nil, fmt.Errorf("invalid jump destination (%v) %v", nop, pos)
return nil, errInvalidJump
}
*pc = pos.Uint64()

Expand All @@ -658,8 +657,7 @@ func opJumpi(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory
pos, cond := stack.pop(), stack.pop()
if cond.Sign() != 0 {
if !contract.validJumpdest(pos) {
nop := contract.GetOp(pos.Uint64())
return nil, fmt.Errorf("invalid jump destination (%v) %v", nop, pos)
return nil, errInvalidJump
}
*pc = pos.Uint64()
} else {
Expand Down Expand Up @@ -711,7 +709,7 @@ func opCreate(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memor
} else if suberr != nil && suberr != ErrCodeStoreOutOfGas {
stack.push(interpreter.intPool.getZero())
} else {
stack.push(addr.Big())
stack.push(interpreter.intPool.get().SetBytes(addr.Bytes()))
}
contract.Gas += returnGas
interpreter.intPool.put(value, offset, size)
Expand Down Expand Up @@ -739,7 +737,7 @@ func opCreate2(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memo
if suberr != nil {
stack.push(interpreter.intPool.getZero())
} else {
stack.push(addr.Big())
stack.push(interpreter.intPool.get().SetBytes(addr.Bytes()))
}
contract.Gas += returnGas
interpreter.intPool.put(endowment, offset, size, salt)
Expand Down Expand Up @@ -912,6 +910,21 @@ func makeLog(size int) executionFunc {
}
}

// opPush1 is a specialized version of pushN
func opPush1(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) {
var (
codeLen = uint64(len(contract.Code))
integer = interpreter.intPool.get()
)
*pc += 1
if *pc < codeLen {
stack.push(integer.SetUint64(uint64(contract.Code[*pc])))
} else {
stack.push(integer.SetUint64(0))
}
return nil, nil
}

// make push instruction function
func makePush(size uint64, pushByteSize int) executionFunc {
return func(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) {
Expand Down
51 changes: 27 additions & 24 deletions core/vm/interpreter.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,22 +118,6 @@ func NewEVMInterpreter(evm *EVM, cfg Config) *EVMInterpreter {
}
}

func (in *EVMInterpreter) enforceRestrictions(op OpCode, operation operation, stack *Stack) error {
if in.evm.chainRules.IsByzantium {
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. 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).BitLen() > 0) {
return errWriteProtection
}
}
}
return nil
}

// Run loops and evaluates the contract's code with the given input data and returns
// the return byte-slice and an error if one occurred.
//
Expand Down Expand Up @@ -217,19 +201,35 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (
if !operation.valid {
return nil, fmt.Errorf("invalid opcode 0x%x", int(op))
}
if err = operation.validateStack(stack); err != nil {
return nil, err
// Validate stack
if sLen := stack.len(); sLen < operation.minStack {
return nil, fmt.Errorf("stack underflow (%d <=> %d)", sLen, operation.minStack)
} else if sLen > operation.maxStack {
return nil, fmt.Errorf("stack limit reached %d (%d)", sLen, operation.maxStack)
}
// If the operation is valid, enforce and write restrictions
if err = in.enforceRestrictions(op, operation, stack); err != nil {
return nil, err
if in.readOnly && in.evm.chainRules.IsByzantium {
// 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
}
}
// Static portion of gas
if !contract.UseGas(operation.constantGas) {
return nil, ErrOutOfGas
}

var memorySize uint64
// calculate the new memory size and expand the memory to fit
// the operation
// Memory check needs to be done prior to evaluating the dynamic gas portion,
// to detect calculation overflows
if operation.memorySize != nil {
memSize, overflow := bigUint64(operation.memorySize(stack))
memSize, overflow := operation.memorySize(stack)
if overflow {
return nil, errGasUintOverflow
}
Expand All @@ -239,11 +239,14 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (
return nil, errGasUintOverflow
}
}
// Dynamic portion of gas
// consume the gas and return an error if not enough gas is available.
// cost is explicitly set so that the capture state defer method can get the proper cost
cost, err = operation.gasCost(in.gasTable, in.evm, contract, stack, mem, memorySize)
if err != nil || !contract.UseGas(cost) {
return nil, ErrOutOfGas
if operation.dynamicGas != nil {
cost, err = operation.dynamicGas(in.gasTable, in.evm, contract, stack, mem, memorySize)
if err != nil || !contract.UseGas(cost) {
return nil, ErrOutOfGas
}
}
if memorySize > 0 {
mem.Resize(memorySize)
Expand Down
Loading