From ac29f81f805f40f79ee2b072d7cfa720fd661394 Mon Sep 17 00:00:00 2001 From: Guillaume Ballet Date: Thu, 22 Nov 2018 19:01:55 +0100 Subject: [PATCH] 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 --- core/vm/evm.go | 1 + core/vm/ewasm.go | 159 +++++++++++++++++++++++++++------------ core/vm/ewasm_eei.go | 137 +++++++++++++++++++++++++-------- tests/state_test_util.go | 1 + 4 files changed, 216 insertions(+), 82 deletions(-) diff --git a/core/vm/evm.go b/core/vm/evm.go index 8bebcbde92..fa9c382a1d 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -420,6 +420,7 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, for _, interpreter := range evm.interpreters { if interpreter.CanRun(contract.Code) { ret, err = interpreter.PostContractCreation(ret) + break } } } diff --git a/core/vm/ewasm.go b/core/vm/ewasm.go index 654f8d79fe..267e3ec800 100644 --- a/core/vm/ewasm.go +++ b/core/vm/ewasm.go @@ -21,7 +21,6 @@ package vm import ( "bytes" - "encoding/binary" "errors" "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)) if err != nil { + in.terminationType = TerminateInvalid 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) if err != nil { + in.terminationType = TerminateInvalid return nil, fmt.Errorf("could not create the vm: %v", err) } vm.RecoverPanic = true in.vm = vm - // Look for the "main" function and execute it after checking it - // has the right kind of signature. - for name, entry := range module.Export.Entries { - if name == "main" && entry.Kind == wasm.ExternalFunction { - - // Check input and output types - sig := module.FunctionIndexSpace[entry.Index].Sig - if len(sig.ParamTypes) == 0 && len(sig.ReturnTypes) == 0 { - _, err = vm.ExecCode(int64(entry.Index)) - - if err != nil { - in.terminationType = TerminateInvalid - } - - 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) - } - err = nil - } - - return in.returnData, err - } - - // Found a main but it doesn't have the right signature - fail - break - } + mainIndex, err := validateModule(module) + if err != nil { + in.terminationType = TerminateInvalid + return nil, err } + + // Check input and output types + sig := module.FunctionIndexSpace[mainIndex].Sig + if len(sig.ParamTypes) == 0 && len(sig.ReturnTypes) == 0 { + _, err = vm.ExecCode(int64(mainIndex)) + + if err != nil && err != errExecutionReverted { + in.terminationType = TerminateInvalid + } + + if in.StateDB.HasSuicided(contract.Address()) { + in.StateDB.AddRefund(params.SuicideRefundGas) + err = nil + } + + return in.returnData, err + } + + in.terminationType = TerminateInvalid 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. func (in *InterpreterEWASM) CanRun(file []byte) bool { // Check the header - if len(file) <= 8 || string(file[:4]) != "\000asm" { + if len(file) < 4 || string(file[:4]) != "\000asm" { return false } - // Check the version - ver := binary.LittleEndian.Uint32(file[4:]) - if ver != 1 { - return false - } + return true } @@ -200,15 +186,88 @@ func (in *InterpreterEWASM) PreContractCreation(code []byte, contract *Contract) return code, nil } -// PostContractCreation meters the contract once its init code has -// been run. -func (in *InterpreterEWASM) PostContractCreation(code []byte) ([]byte, error) { - if in.metering { - metered, _, err := sentinel(in, code) - if len(metered) < 5 || err != nil { - return nil, fmt.Errorf("Error metering the generated contract code, err=%v", err) - } - return metered, 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 +// 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) { + if in.CanRun(code) { + if in.metering { + code, _, err := sentinel(in, code) + if len(code) < 5 || err != nil { + return nil, fmt.Errorf("Error metering the generated contract code, err=%v", err) + } + + 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 } diff --git a/core/vm/ewasm_eei.go b/core/vm/ewasm_eei.go index 2648a5291d..4de1ad59bc 100644 --- a/core/vm/ewasm_eei.go +++ b/core/vm/ewasm_eei.go @@ -60,6 +60,7 @@ const ( GasCostCall = 700 GasCostCallValue = 9000 GasCostCallStipend = 2300 + GasCostNewAccount = 25000 GasCostLog = 375 GasCostLogData = 8 GasCostLogTopic = 375 @@ -141,7 +142,7 @@ func (in *InterpreterEWASM) gasAccounting(cost uint64) { panic("nil contract") } 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 } @@ -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 { if in.evm.depth > maxCallDepth { - contract.UseGas(contract.Gas) 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 { - in.gasAccounting(GasCostCall) - contract := in.contract // 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) // requires this operation to be U128, which is incompatible with the EVM version that expects // 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 { in.gasAccounting(GasCostCallValue) - gas += GasCostCallStipend } // Get the arguments. @@ -448,31 +462,65 @@ func call(p *exec.Process, in *InterpreterEWASM, gas int64, addressOffset int32, 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 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 } + // 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 check that EIP-150 is respected // Add amount to recipient in.evm.Transfer(in.StateDB, contract.Address(), addr, value) // 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) + if len(code) == 0 { + in.contract.Gas += calleeGas + return EEICallSuccess + } 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) { @@ -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 { - in.gasAccounting(GasCostCall) - contract := in.contract // 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() - // Check that the contract exists - if !in.StateDB.Exist(addr) { - // TODO check that no new account creation stuff is required - in.StateDB.CreateAccount(addr) - } + in.gasAccounting(GasCostCall) - // Check that there is enough balance to transfer the value - 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 + if in.evm.depth > maxCallDepth { 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 check that EIP-150 is respected // Add amount to recipient in.evm.Transfer(in.StateDB, contract.Address(), addr, value) // 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) + if len(code) == 0 { + in.contract.Gas += calleeGas + return EEICallSuccess + } targetContract.SetCallCode(&addr, in.StateDB.GetCodeHash(addr), code) + savedVM := in.vm saveStatic := in.staticMode in.staticMode = true 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) { @@ -904,7 +977,6 @@ func selfDestruct(p *exec.Process, in *InterpreterEWASM, addressOffset int32) { balance := in.StateDB.GetBalance(contract.Address()) addr := common.BytesToAddress(mem[addressOffset : addressOffset+common.AddressLength]) - in.StateDB.AddBalance(addr, balance) totalGas := in.gasTable.Suicide // 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.StateDB.AddBalance(addr, balance) in.StateDB.Suicide(contract.Address()) // Same as for `revert` and `return`, I need to forcefully terminate diff --git a/tests/state_test_util.go b/tests/state_test_util.go index 3683aae320..49a35b307a 100644 --- a/tests/state_test_util.go +++ b/tests/state_test_util.go @@ -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) { return statedb, fmt.Errorf("post state logs hash mismatch: got %x, want %x", logs, post.Logs) } + return statedb, nil }