mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-08-19 18:32:23 +00:00
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:
parent
43b668a471
commit
ac29f81f80
4 changed files with 216 additions and 82 deletions
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
123
core/vm/ewasm.go
123
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 {
|
||||
mainIndex, err := validateModule(module)
|
||||
if err != nil {
|
||||
in.terminationType = TerminateInvalid
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
||||
// 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 {
|
||||
_, err = vm.ExecCode(int64(entry.Index))
|
||||
_, err = vm.ExecCode(int64(mainIndex))
|
||||
|
||||
if err != nil {
|
||||
if err != nil && err != errExecutionReverted {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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.
|
||||
// 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 {
|
||||
metered, _, err := sentinel(in, code)
|
||||
if len(metered) < 5 || err != nil {
|
||||
code, _, err := sentinel(in, code)
|
||||
if len(code) < 5 || err != nil {
|
||||
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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue