core/vm: Fix all fixable tests

Non-passing tests still need to be discussed in
https://github.com/ewasm/hera/issues/457 and
https://github.com/ewasm/hera/issues/456
This commit is contained in:
Guillaume Ballet 2018-11-22 19:01:55 +01:00
parent 43b668a471
commit ac29f81f80
4 changed files with 216 additions and 82 deletions

View file

@ -420,6 +420,7 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64,
for _, interpreter := range evm.interpreters { for _, interpreter := range evm.interpreters {
if interpreter.CanRun(contract.Code) { if interpreter.CanRun(contract.Code) {
ret, err = interpreter.PostContractCreation(ret) ret, err = interpreter.PostContractCreation(ret)
break
} }
} }
} }

View file

@ -21,7 +21,6 @@ package vm
import ( import (
"bytes" "bytes"
"encoding/binary"
"errors" "errors"
"fmt" "fmt"
@ -114,52 +113,43 @@ func (in *InterpreterEWASM) Run(contract *Contract, input []byte, ro bool) ([]by
module, err := wasm.ReadModule(bytes.NewReader(contract.Code), WrappedModuleResolver(in)) module, err := wasm.ReadModule(bytes.NewReader(contract.Code), WrappedModuleResolver(in))
if err != nil { if err != nil {
in.terminationType = TerminateInvalid
return nil, fmt.Errorf("Error decoding module at address %s: %v", contract.Address().Hex(), err) return nil, fmt.Errorf("Error decoding module at address %s: %v", contract.Address().Hex(), err)
} }
// The module should not have any start function
if module.Start != nil {
return nil, fmt.Errorf("A contract should not have a start function: found #%d", module.Start.Index)
}
vm, err := exec.NewVM(module) vm, err := exec.NewVM(module)
if err != nil { if err != nil {
in.terminationType = TerminateInvalid
return nil, fmt.Errorf("could not create the vm: %v", err) return nil, fmt.Errorf("could not create the vm: %v", err)
} }
vm.RecoverPanic = true vm.RecoverPanic = true
in.vm = vm in.vm = vm
// Look for the "main" function and execute it after checking it mainIndex, err := validateModule(module)
// has the right kind of signature. if err != nil {
for name, entry := range module.Export.Entries { in.terminationType = TerminateInvalid
if name == "main" && entry.Kind == wasm.ExternalFunction { return nil, err
}
// Check input and output types // Check input and output types
sig := module.FunctionIndexSpace[entry.Index].Sig sig := module.FunctionIndexSpace[mainIndex].Sig
if len(sig.ParamTypes) == 0 && len(sig.ReturnTypes) == 0 { if len(sig.ParamTypes) == 0 && len(sig.ReturnTypes) == 0 {
_, err = vm.ExecCode(int64(entry.Index)) _, err = vm.ExecCode(int64(mainIndex))
if err != nil { if err != nil && err != errExecutionReverted {
in.terminationType = TerminateInvalid in.terminationType = TerminateInvalid
} }
if in.StateDB.HasSuicided(contract.Address()) { if in.StateDB.HasSuicided(contract.Address()) {
if initialGas-contract.Gas-params.TxGas < 2*params.SuicideRefundGas {
in.StateDB.AddRefund((initialGas - contract.Gas - params.TxGas) / 2)
} else {
in.StateDB.AddRefund(params.SuicideRefundGas) in.StateDB.AddRefund(params.SuicideRefundGas)
}
err = nil err = nil
} }
return in.returnData, err return in.returnData, err
} }
// Found a main but it doesn't have the right signature - fail in.terminationType = TerminateInvalid
break
}
}
return nil, errors.New("Could not find a suitable 'main' function in that contract") return nil, errors.New("Could not find a suitable 'main' function in that contract")
} }
@ -167,15 +157,11 @@ func (in *InterpreterEWASM) Run(contract *Contract, input []byte, ro bool) ([]by
// if it matches. // if it matches.
func (in *InterpreterEWASM) CanRun(file []byte) bool { func (in *InterpreterEWASM) CanRun(file []byte) bool {
// Check the header // Check the header
if len(file) <= 8 || string(file[:4]) != "\000asm" { if len(file) < 4 || string(file[:4]) != "\000asm" {
return false return false
} }
// Check the version
ver := binary.LittleEndian.Uint32(file[4:])
if ver != 1 {
return false
}
return true return true
} }
@ -200,15 +186,88 @@ func (in *InterpreterEWASM) PreContractCreation(code []byte, contract *Contract)
return code, nil return code, nil
} }
func validateModule(m *wasm.Module) (int, error) {
// A module should not have a start section
if m.Start != nil {
return -1, fmt.Errorf("Module has a start section")
}
// Only two exports are authorized: "main" and "memory"
if m.Export == nil {
return -1, fmt.Errorf("Module has no exports instead of 2")
}
if len(m.Export.Entries) != 2 {
return -1, fmt.Errorf("Module has %d exports instead of 2", len(m.Export.Entries))
}
mainIndex := -1
for name, entry := range m.Export.Entries {
switch name {
case "main":
if entry.Kind != wasm.ExternalFunction {
return -1, fmt.Errorf("Main is not a function in module")
}
mainIndex = int(entry.Index)
break
case "memory":
if entry.Kind != wasm.ExternalMemory {
return -1, fmt.Errorf("'memory' is not a memory in module")
}
break
default:
return -1, fmt.Errorf("A symbol named %s has been exported. Only main and memory should exist", name)
}
}
if m.Import != nil {
OUTER:
for _, entry := range m.Import.Entries {
if entry.ModuleName == "ethereum" {
if entry.Type.Kind() == wasm.ExternalFunction {
for _, name := range eeiFunctionList {
if name == entry.FieldName {
continue OUTER
}
}
return -1, fmt.Errorf("%s could not be found in the list of ethereum-provided functions", entry.FieldName)
}
}
}
}
return mainIndex, nil
}
// PostContractCreation meters the contract once its init code has // PostContractCreation meters the contract once its init code has
// been run. // been run. It also validates the module's format before it is to
// be committed to disk.
func (in *InterpreterEWASM) PostContractCreation(code []byte) ([]byte, error) { func (in *InterpreterEWASM) PostContractCreation(code []byte) ([]byte, error) {
if in.CanRun(code) {
if in.metering { if in.metering {
metered, _, err := sentinel(in, code) code, _, err := sentinel(in, code)
if len(metered) < 5 || err != nil { if len(code) < 5 || err != nil {
return nil, fmt.Errorf("Error metering the generated contract code, err=%v", err) return nil, fmt.Errorf("Error metering the generated contract code, err=%v", err)
} }
return metered, nil
if len(code) < 8 {
return nil, fmt.Errorf("Invalid contract code")
} }
}
if len(code) > 8 {
// Check the validity of the module
m, err := wasm.DecodeModule(bytes.NewReader(code))
if err != nil {
return nil, fmt.Errorf("Error decoding the module produced by init code: %v", err)
}
_, err = validateModule(m)
if err != nil {
in.terminationType = TerminateInvalid
return nil, err
}
}
}
return code, nil return code, nil
} }

View file

@ -60,6 +60,7 @@ const (
GasCostCall = 700 GasCostCall = 700
GasCostCallValue = 9000 GasCostCallValue = 9000
GasCostCallStipend = 2300 GasCostCallStipend = 2300
GasCostNewAccount = 25000
GasCostLog = 375 GasCostLog = 375
GasCostLogData = 8 GasCostLogData = 8
GasCostLogTopic = 375 GasCostLogTopic = 375
@ -141,7 +142,7 @@ func (in *InterpreterEWASM) gasAccounting(cost uint64) {
panic("nil contract") panic("nil contract")
} }
if cost > in.contract.Gas { if cost > in.contract.Gas {
panic("out of gas") panic(fmt.Sprintf("out of gas %d > %d", cost, in.contract.Gas))
} }
in.contract.Gas -= cost in.contract.Gas -= cost
} }
@ -392,7 +393,6 @@ func getBlockHash(p *exec.Process, in *InterpreterEWASM, number int64, resultOff
func callCommon(in *InterpreterEWASM, contract, targetContract *Contract, input []byte, value *big.Int, snapshot int, gas int64, ro bool) int32 { func callCommon(in *InterpreterEWASM, contract, targetContract *Contract, input []byte, value *big.Int, snapshot int, gas int64, ro bool) int32 {
if in.evm.depth > maxCallDepth { if in.evm.depth > maxCallDepth {
contract.UseGas(contract.Gas)
return ErrEEICallFailure return ErrEEICallFailure
} }
@ -423,8 +423,6 @@ func callCommon(in *InterpreterEWASM, contract, targetContract *Contract, input
} }
func call(p *exec.Process, in *InterpreterEWASM, gas int64, addressOffset int32, valueOffset int32, dataOffset int32, dataLength int32) int32 { func call(p *exec.Process, in *InterpreterEWASM, gas int64, addressOffset int32, valueOffset int32, dataOffset int32, dataLength int32) int32 {
in.gasAccounting(GasCostCall)
contract := in.contract contract := in.contract
// Get the address of the contract to call // Get the address of the contract to call
@ -433,11 +431,27 @@ func call(p *exec.Process, in *InterpreterEWASM, gas int64, addressOffset int32,
// Get the value. The [spec](https://github.com/ewasm/design/blob/master/eth_interface.md#call) // Get the value. The [spec](https://github.com/ewasm/design/blob/master/eth_interface.md#call)
// requires this operation to be U128, which is incompatible with the EVM version that expects // requires this operation to be U128, which is incompatible with the EVM version that expects
// a u256. // a u256.
value := big.NewInt(0).SetBytes(swapEndian(readSize(p, valueOffset, u128Len))) // To be compatible with hera, one must read a u256 value, then check that this is a u128.
value := big.NewInt(0).SetBytes(swapEndian(readSize(p, valueOffset, u256Len)))
check128bits := big.NewInt(1)
check128bits.Lsh(check128bits, 128)
if value.Cmp(check128bits) > 0 {
return ErrEEICallFailure
}
if in.staticMode == true && value.Cmp(big.NewInt(0)) != 0 {
in.gasAccounting(in.contract.Gas)
return ErrEEICallFailure
}
in.gasAccounting(GasCostCall)
if in.evm.depth > maxCallDepth {
return ErrEEICallFailure
}
if value.Cmp(big.NewInt(0)) != 0 { if value.Cmp(big.NewInt(0)) != 0 {
in.gasAccounting(GasCostCallValue) in.gasAccounting(GasCostCallValue)
gas += GasCostCallStipend
} }
// Get the arguments. // Get the arguments.
@ -448,31 +462,65 @@ func call(p *exec.Process, in *InterpreterEWASM, gas int64, addressOffset int32,
snapshot := in.StateDB.Snapshot() snapshot := in.StateDB.Snapshot()
// Check that the contract exists
if !in.StateDB.Exist(addr) {
// TODO check that no new account creation stuff is required
in.StateDB.CreateAccount(addr)
}
// Check that there is enough balance to transfer the value // Check that there is enough balance to transfer the value
if in.StateDB.GetBalance(contract.Address()).Cmp(value) < 0 { if in.StateDB.GetBalance(contract.Address()).Cmp(value) < 0 {
fmt.Printf("Not enough balance: wanted to use %v, got %v\n", value, in.StateDB.GetBalance(addr))
in.contract.Gas += GasCostCallStipend
return ErrEEICallFailure return ErrEEICallFailure
} }
// Check that the contract exists
if !in.StateDB.Exist(addr) {
in.gasAccounting(GasCostNewAccount)
in.StateDB.CreateAccount(addr)
}
var calleeGas uint64
if uint64(gas) > ((63 * contract.Gas) / 64) {
calleeGas = contract.Gas - (contract.Gas / 64)
} else {
calleeGas = uint64(gas)
}
in.gasAccounting(calleeGas)
if value.Cmp(big.NewInt(0)) != 0 {
calleeGas += GasCostCallStipend
}
// TODO tracing // TODO tracing
// TODO check that EIP-150 is respected
// Add amount to recipient // Add amount to recipient
in.evm.Transfer(in.StateDB, contract.Address(), addr, value) in.evm.Transfer(in.StateDB, contract.Address(), addr, value)
// Load the contract code in a new VM structure // Load the contract code in a new VM structure
targetContract := NewContract(contract, AccountRef(addr), value, uint64(gas)) targetContract := NewContract(contract, AccountRef(addr), value, calleeGas)
code := in.StateDB.GetCode(addr) code := in.StateDB.GetCode(addr)
if len(code) == 0 {
in.contract.Gas += calleeGas
return EEICallSuccess
}
targetContract.SetCallCode(&addr, in.StateDB.GetCodeHash(addr), code) targetContract.SetCallCode(&addr, in.StateDB.GetCodeHash(addr), code)
return callCommon(in, contract, targetContract, input, value, snapshot, gas, false) savedVM := in.vm
in.Run(targetContract, input, false)
in.vm = savedVM
in.contract = contract
// Add leftover gas
in.contract.Gas += targetContract.Gas
defer func() { in.terminationType = TerminateFinish }()
switch in.terminationType {
case TerminateFinish:
return EEICallSuccess
case TerminateRevert:
in.StateDB.RevertToSnapshot(snapshot)
return ErrEEICallRevert
default:
in.StateDB.RevertToSnapshot(snapshot)
contract.UseGas(targetContract.Gas)
return ErrEEICallFailure
}
} }
func callDataCopy(p *exec.Process, in *InterpreterEWASM, resultOffset int32, dataOffset int32, length int32) { func callDataCopy(p *exec.Process, in *InterpreterEWASM, resultOffset int32, dataOffset int32, length int32) {
@ -573,8 +621,6 @@ func callDelegate(p *exec.Process, in *InterpreterEWASM, gas int64, addressOffse
} }
func callStatic(p *exec.Process, in *InterpreterEWASM, gas int64, addressOffset int32, dataOffset int32, dataLength int32) int32 { func callStatic(p *exec.Process, in *InterpreterEWASM, gas int64, addressOffset int32, dataOffset int32, dataLength int32) int32 {
in.gasAccounting(GasCostCall)
contract := in.contract contract := in.contract
// Get the address of the contract to call // Get the address of the contract to call
@ -590,35 +636,62 @@ func callStatic(p *exec.Process, in *InterpreterEWASM, gas int64, addressOffset
snapshot := in.StateDB.Snapshot() snapshot := in.StateDB.Snapshot()
// Check that the contract exists in.gasAccounting(GasCostCall)
if !in.StateDB.Exist(addr) {
// TODO check that no new account creation stuff is required
in.StateDB.CreateAccount(addr)
}
// Check that there is enough balance to transfer the value if in.evm.depth > maxCallDepth {
if in.StateDB.GetBalance(addr).Cmp(value) < 0 {
fmt.Printf("Not enough balance: wanted to use %v, got %v\n", value, in.StateDB.GetBalance(addr))
in.contract.Gas += GasCostCallStipend
return ErrEEICallFailure return ErrEEICallFailure
} }
// Check that the contract exists
if !in.StateDB.Exist(addr) {
in.gasAccounting(GasCostNewAccount)
in.StateDB.CreateAccount(addr)
}
calleeGas := uint64(gas)
if calleeGas > ((63 * contract.Gas) / 64) {
calleeGas -= ((63 * contract.Gas) / 64)
}
in.gasAccounting(calleeGas)
// TODO tracing // TODO tracing
// TODO check that EIP-150 is respected
// Add amount to recipient // Add amount to recipient
in.evm.Transfer(in.StateDB, contract.Address(), addr, value) in.evm.Transfer(in.StateDB, contract.Address(), addr, value)
// Load the contract code in a new VM structure // Load the contract code in a new VM structure
targetContract := NewContract(contract, AccountRef(addr), value, uint64(gas)) targetContract := NewContract(contract, AccountRef(addr), value, calleeGas)
code := in.StateDB.GetCode(addr) code := in.StateDB.GetCode(addr)
if len(code) == 0 {
in.contract.Gas += calleeGas
return EEICallSuccess
}
targetContract.SetCallCode(&addr, in.StateDB.GetCodeHash(addr), code) targetContract.SetCallCode(&addr, in.StateDB.GetCodeHash(addr), code)
savedVM := in.vm
saveStatic := in.staticMode saveStatic := in.staticMode
in.staticMode = true in.staticMode = true
defer func() { in.staticMode = saveStatic }() defer func() { in.staticMode = saveStatic }()
return callCommon(in, contract, targetContract, input, value, snapshot, gas, true) in.Run(targetContract, input, false)
in.vm = savedVM
in.contract = contract
// Add leftover gas
in.contract.Gas += targetContract.Gas
switch in.terminationType {
case TerminateFinish:
return EEICallSuccess
case TerminateRevert:
in.StateDB.RevertToSnapshot(snapshot)
return ErrEEICallRevert
default:
in.StateDB.RevertToSnapshot(snapshot)
contract.UseGas(targetContract.Gas)
return ErrEEICallFailure
}
} }
func storageStore(p *exec.Process, interpreter *InterpreterEWASM, pathOffset int32, valueOffset int32) { func storageStore(p *exec.Process, interpreter *InterpreterEWASM, pathOffset int32, valueOffset int32) {
@ -904,7 +977,6 @@ func selfDestruct(p *exec.Process, in *InterpreterEWASM, addressOffset int32) {
balance := in.StateDB.GetBalance(contract.Address()) balance := in.StateDB.GetBalance(contract.Address())
addr := common.BytesToAddress(mem[addressOffset : addressOffset+common.AddressLength]) addr := common.BytesToAddress(mem[addressOffset : addressOffset+common.AddressLength])
in.StateDB.AddBalance(addr, balance)
totalGas := in.gasTable.Suicide totalGas := in.gasTable.Suicide
// If the destination address doesn't exist, add the account creation costs // If the destination address doesn't exist, add the account creation costs
@ -913,6 +985,7 @@ func selfDestruct(p *exec.Process, in *InterpreterEWASM, addressOffset int32) {
} }
in.gasAccounting(totalGas) in.gasAccounting(totalGas)
in.StateDB.AddBalance(addr, balance)
in.StateDB.Suicide(contract.Address()) in.StateDB.Suicide(contract.Address())
// Same as for `revert` and `return`, I need to forcefully terminate // Same as for `revert` and `return`, I need to forcefully terminate

View file

@ -161,6 +161,7 @@ func (t *StateTest) Run(subtest StateSubtest, vmconfig vm.Config) (*state.StateD
if logs := rlpHash(statedb.Logs()); logs != common.Hash(post.Logs) { if logs := rlpHash(statedb.Logs()); logs != common.Hash(post.Logs) {
return statedb, fmt.Errorf("post state logs hash mismatch: got %x, want %x", logs, post.Logs) return statedb, fmt.Errorf("post state logs hash mismatch: got %x, want %x", logs, post.Logs)
} }
return statedb, nil return statedb, nil
} }