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

retdatasize-retdatacopy-implementation #766

Closed
wants to merge 8 commits into from
2 changes: 2 additions & 0 deletions execution/evm/asm/opcodes.go
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,7 @@ const (
// 0x70 range - other
STATICCALL = 0xfa
REVERT = 0xfd
INVALID = 0xfe
SELFDESTRUCT = 0xff
)

Expand Down Expand Up @@ -347,6 +348,7 @@ var opCodeNames = map[OpCode]string{
STATICCALL: "STATICCALL",
// 0x70 range - other
REVERT: "REVERT",
INVALID: "INVALID",
SELFDESTRUCT: "SELFDESTRUCT",
}

Expand Down
57 changes: 54 additions & 3 deletions execution/evm/vm.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,14 @@ var (
ErrMemoryOutOfBounds = errors.New("Memory out of bounds")
ErrCodeOutOfBounds = errors.New("Code out of bounds")
ErrInputOutOfBounds = errors.New("Input out of bounds")
ErrReturnDataOutOfBounds = errors.New("Return data out of bounds")
ErrCallStackOverflow = errors.New("Call stack overflow")
ErrCallStackUnderflow = errors.New("Call stack underflow")
ErrDataStackOverflow = errors.New("Data stack overflow")
ErrDataStackUnderflow = errors.New("Data stack underflow")
ErrInvalidContract = errors.New("Invalid contract")
ErrNativeContractCodeCopy = errors.New("Tried to copy native contract code")
ErrExecutionAborted = errors.New("Execution aborted")
ErrExecutionReverted = errors.New("Execution reverted")
)

Expand Down Expand Up @@ -112,6 +114,7 @@ type VM struct {
nestedCallErrors []ErrNestedCall
publisher event.Publisher
logger *logging.Logger
returnData []byte
debugOpcodes bool
dumpTokens bool
}
Expand Down Expand Up @@ -144,6 +147,14 @@ func (vm *VM) SetPublisher(publisher event.Publisher) {
vm.publisher = publisher
}

func (vm *VM) setReturnDataBuffer(ret []byte) {
vm.returnData = ret
}

func (vm *VM) getReturnDataBuffer() (ret []byte) {
return vm.returnData
}

// CONTRACT: it is the duty of the contract writer to call known permissions
// we do not convey if a permission is not set
// (unlike in state/execution, where we guarantee HasPermission is called
Expand Down Expand Up @@ -665,6 +676,38 @@ func (vm *VM) call(caller acm.Account, callee acm.MutableAccount, code, input []
}
vm.Debugf(" => [%v, %v, %v] %X\n", memOff, codeOff, length, data)

case RETURNDATASIZE: // 0x3D
stack.Push64(int64(len(vm.getReturnDataBuffer())))
vm.Debugf(" => %d\n", len(vm.getReturnDataBuffer()))

case RETURNDATACOPY: // 0x3E
memOff := stack.PopBigInt()
outputOff, popErr := stack.Pop64()
if popErr != nil {
return nil, firstErr(err, popErr)
}
length, popErr := stack.Pop64()
if popErr != nil {
return nil, firstErr(err, popErr)
}

bigOff := big.NewInt(outputOff)
bigLength := big.NewInt(length)
end := new(big.Int).Add(bigOff, bigLength)

if end.BitLen() > 64 || uint64(len(vm.getReturnDataBuffer())) < end.Uint64() {
return nil, ErrReturnDataOutOfBounds
}

data := vm.getReturnDataBuffer()

memErr := memory.Write(memOff, data)
if memErr != nil {
vm.Debugf(" => Memory err: %s", memErr)
return nil, firstErr(err, ErrMemoryOutOfBounds)
}
vm.Debugf(" => [%v, %v, %v] %X\n", memOff, outputOff, length, data)

case BLOCKHASH: // 0x40
stack.Push(Zero256)
vm.Debugf(" => 0x%X (NOT SUPPORTED)\n", stack.Peek().Bytes())
Expand Down Expand Up @@ -870,6 +913,8 @@ func (vm *VM) call(caller acm.Account, callee acm.MutableAccount, code, input []
} else {
newAccount.SetCode(ret) // Set the code (ret need not be copied as per Call contract)
stack.Push(newAccount.Address().Word256())
emptyBuffer := []byte{}
vm.setReturnDataBuffer(emptyBuffer)
}

if err_ == ErrExecutionReverted {
Expand Down Expand Up @@ -954,11 +999,13 @@ func (vm *VM) call(caller acm.Account, callee acm.MutableAccount, code, input []
return nil, firstErr(callErr, ErrUnknownAddress)
}
ret, callErr = vm.Call(callee, callee, acc.Code(), args, value, &gasLimit)
vm.setReturnDataBuffer(ret)
} else if op == DELEGATECALL {
if acc == nil {
return nil, firstErr(callErr, ErrUnknownAddress)
}
ret, callErr = vm.DelegateCall(caller, callee, acc.Code(), args, value, &gasLimit)
vm.setReturnDataBuffer(ret)
} else {
// nil account means we're sending funds to a new account
if acc == nil {
Expand All @@ -970,6 +1017,7 @@ func (vm *VM) call(caller acm.Account, callee acm.MutableAccount, code, input []
// add account to the tx cache
vm.stateWriter.UpdateAccount(acc)
ret, callErr = vm.Call(callee, acc, acc.Code(), args, value, &gasLimit)
vm.setReturnDataBuffer(ret)
}
}
// In case any calls deeper in the stack (particularly SNatives) has altered either of two accounts to which
Expand Down Expand Up @@ -1039,6 +1087,9 @@ func (vm *VM) call(caller acm.Account, callee acm.MutableAccount, code, input []
vm.Debugf(" => [%v, %v] (%d) 0x%X\n", offset, size, len(output), output)
return output, ErrExecutionReverted

case INVALID: //0xFE
return nil, ErrExecutionAborted

case SELFDESTRUCT: // 0xFF
addr := stack.Pop()
if useGasNegative(gas, GasGetAccount, &err) {
Expand Down Expand Up @@ -1076,11 +1127,11 @@ func (vm *VM) call(caller acm.Account, callee acm.MutableAccount, code, input []
case STOP: // 0x00
return nil, nil

case STATICCALL, SHL, SHR, SAR, RETURNDATASIZE, RETURNDATACOPY:
case STATICCALL, SHL, SHR, SAR:
return nil, fmt.Errorf("%s not yet implemented", op.Name())
default:
vm.Debugf("(pc) %-3v Invalid opcode %X\n", pc, op)
return nil, fmt.Errorf("invalid opcode %X", op)
vm.Debugf("(pc) %-3v Unknown opcode %X\n", pc, op)
return nil, fmt.Errorf("unknown opcode %X", op)
}
pc++
}
Expand Down
97 changes: 97 additions & 0 deletions execution/evm/vm_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -374,6 +374,103 @@ func TestMsgSender(t *testing.T) {

}

func TestInvalid(t *testing.T) {
ourVm := NewVM(newAppState(), newParams(), acm.ZeroAddress, nil, logger)

// Create accounts
account1 := newAccount(1)
account2 := newAccount(1, 0, 1)

var gas uint64 = 100000

bytecode := MustSplice(PUSH32, 0x72, 0x65, 0x76, 0x65, 0x72, 0x74, 0x20, 0x6D, 0x65, 0x73, 0x73, 0x61,
0x67, 0x65, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, PUSH1, 0x00, MSTORE, PUSH1, 0x0E, PUSH1, 0x00, INVALID)

output, err := ourVm.Call(account1, account2, bytecode, []byte{}, 0, &gas)
expected := "call error: " + ErrExecutionAborted.Error()
assert.EqualError(t, err, expected)
t.Logf("Output: %v Error: %v\n", output, err)

}

func TestReturnDataSize(t *testing.T) {
cache := state.NewCache(newAppState())
ourVm := NewVM(cache, newParams(), acm.ZeroAddress, nil, logger)

accountName := "account2addresstests"

callcode := MustSplice(PUSH32, 0x72, 0x65, 0x76, 0x65, 0x72, 0x74, 0x20, 0x6D, 0x65, 0x73, 0x73, 0x61,
0x67, 0x65, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, PUSH1, 0x00, MSTORE, PUSH1, 0x0E, PUSH1, 0x00, RETURN)

// Create accounts
account1 := newAccount(1)
account2, _ := makeAccountWithCode(cache, accountName, callcode)

var gas uint64 = 100000

gas1, gas2 := byte(0x1), byte(0x1)
value := byte(0x69)
inOff, inSize := byte(0x0), byte(0x0) // no call data
retOff, retSize := byte(0x0), byte(0x0E)

bytecode := MustSplice(PUSH1, retSize, PUSH1, retOff, PUSH1, inSize, PUSH1, inOff, PUSH1, value, PUSH20,
0x61, 0x63, 0x63, 0x6F, 0x75, 0x6E, 0x74, 0x32, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x74, 0x65,
0x73, 0x74, 0x73, PUSH2, gas1, gas2, CALL, RETURNDATASIZE, PUSH1, 0x00, MSTORE, PUSH1, 0x20, PUSH1, 0x00, RETURN)

expected := LeftPadBytes([]byte{0x0E}, 32)

output, err := ourVm.Call(account1, account2, bytecode, []byte{}, 0, &gas)

assert.Equal(t, expected, output)

t.Logf("Output: %v Error: %v\n", output, err)

if err != nil {
t.Fatal(err)
}
}

func TestReturnDataCopy(t *testing.T) {
cache := state.NewCache(newAppState())
ourVm := NewVM(cache, newParams(), acm.ZeroAddress, nil, logger)

accountName := "account2addresstests"

callcode := MustSplice(PUSH32, 0x72, 0x65, 0x76, 0x65, 0x72, 0x74, 0x20, 0x6D, 0x65, 0x73, 0x73, 0x61,
0x67, 0x65, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, PUSH1, 0x00, MSTORE, PUSH1, 0x0E, PUSH1, 0x00, RETURN)

// Create accounts
account1 := newAccount(1)
account2, _ := makeAccountWithCode(cache, accountName, callcode)

var gas uint64 = 100000

gas1, gas2 := byte(0x1), byte(0x1)
value := byte(0x69)
inOff, inSize := byte(0x0), byte(0x0) // no call data
retOff, retSize := byte(0x0), byte(0x0E)

bytecode := MustSplice(PUSH1, retSize, PUSH1, retOff, PUSH1, inSize, PUSH1, inOff, PUSH1, value, PUSH20,
0x61, 0x63, 0x63, 0x6F, 0x75, 0x6E, 0x74, 0x32, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x74, 0x65,
0x73, 0x74, 0x73, PUSH2, gas1, gas2, CALL, RETURNDATASIZE, PUSH1, 0x00, PUSH1, 0x00, RETURNDATACOPY,
RETURNDATASIZE, PUSH1, 0x00, RETURN)

expected := []byte{0x72, 0x65, 0x76, 0x65, 0x72, 0x74, 0x20, 0x6D, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65}

output, err := ourVm.Call(account1, account2, bytecode, []byte{}, 0, &gas)

assert.Equal(t, expected, output)

t.Logf("Output: %v Error: %v\n", output, err)

if err != nil {
t.Fatal(err)
}
}

// These code segment helpers exercise the MSTORE MLOAD MSTORE cycle to test
// both of the memory operations. Each MSTORE is done on the memory boundary
// (at MSIZE) which Solidity uses to find guaranteed unallocated memory.
Expand Down
4 changes: 4 additions & 0 deletions project/history.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ func FullVersion() string {
// To cut a new release add a release to the front of this slice then run the
// release tagging script: ./scripts/tag_release.sh
var History relic.ImmutableHistory = relic.NewHistory("Hyperledger Burrow").MustDeclareReleases(
"0.18.1",
`This is a minor release including:
- Introduce InputAccount param for RPC/v0 for integration in JS libs
- Resolve some issues with RPC/tm tests swallowing timeouts and not dealing with reordered events`,
"0.18.0",
`This is an extremely large release in terms of lines of code changed addressing several years of technical debt. Despite this efforts were made to maintain external interfaces as much as possible and an extended period of stabilisation has taken place on develop.

Expand Down
Loading