From 8d0abcaefbd5f6ae5c8a3eae7446aaabcade11bf Mon Sep 17 00:00:00 2001 From: dm4 Date: Thu, 21 Mar 2019 02:46:07 +0800 Subject: core/vm: add dvm, a wasm-based virtual machine DVM follows DEXON VM interface. It checks bytecode and executes it in a WASM VM. --- core/vm/dvm/dvm.go | 606 +++++++++++++++++++++++++++++ core/vm/dvm/eei.go | 1007 +++++++++++++++++++++++++++++++++++++++++++++++++ core/vm/dvm/linker.go | 119 ++++++ core/vm/vm.go | 3 +- 4 files changed, 1734 insertions(+), 1 deletion(-) create mode 100644 core/vm/dvm/dvm.go create mode 100644 core/vm/dvm/eei.go create mode 100644 core/vm/dvm/linker.go (limited to 'core/vm') diff --git a/core/vm/dvm/dvm.go b/core/vm/dvm/dvm.go new file mode 100644 index 000000000..44f5434da --- /dev/null +++ b/core/vm/dvm/dvm.go @@ -0,0 +1,606 @@ +// Copyright 2018 The go-ethereum Authors +// This file is part of go-ethereum. +// +// go-ethereum is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// go-ethereum is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with go-ethereum. If not, see . + +// This file lists the EEI functions, so that they can be bound to any +// ewasm-compatible module, as well as the types of these functions + +package dvm + +import ( + "bytes" + "errors" + "fmt" + "math/big" + + "github.com/dexon-foundation/dexon/common" + "github.com/dexon-foundation/dexon/core" + "github.com/dexon-foundation/dexon/core/vm" + "github.com/dexon-foundation/dexon/crypto" + "github.com/dexon-foundation/dexon/params" + + "github.com/go-interpreter/wagon/exec" + "github.com/go-interpreter/wagon/wasm" +) + +type terminationType int + +// List of termination reasons +const ( + TerminateFinish = iota + TerminateRevert + TerminateSuicide + TerminateInvalid +) + +// emptyCodeHash is used by create to ensure deployment is disallowed to already +// deployed contract addresses (relevant after the account abstraction). +var ( + emptyCodeHash = crypto.Keccak256Hash(nil) + errExecutionReverted = errors.New("dvm: execution reverted") + errMaxCodeSizeExceeded = errors.New("dvm: max code size exceeded") + u256Len = 32 + u128Len = 16 + maxCallDepth = 1024 + sentinelContractAddress = "0x000000000000000000000000000000000000000a" +) + +type DVM struct { + // Context provides auxiliary blockchain related information + vm.Context + // StateDB gives access to the underlying state + statedb vm.StateDB + // Depth is the current call stack + depth int + + // chainConfig contains information about the current chain + chainConfig *params.ChainConfig + // chain rules contains the chain rules for the current epoch + chainRules params.Rules + // abort is used to abort the SQLVM calling operations + // NOTE: must be set atomically + abort int32 + // callGasTemp holds the gas available for the current call. This is needed because the + // available gas is calculated in gasCall* according to the 63/64 rule and later + // applied in opCall*. + callGasTemp uint64 + + // vm is wasm VM to execute bytecode + vm *exec.VM + + gasTable params.GasTable + contract *vm.Contract + returnData []byte + terminationType terminationType + staticMode bool + + metering bool + meteringContract *vm.Contract + meteringModule *wasm.Module + meteringStartIndex int64 +} + +type Config struct { + Metering bool +} + +func init() { + vm.Register(vm.DVM, &DVM{}) +} + +// NewDVM creates a new wagon-based ewasm vm. +func NewDVM(statedb vm.StateDB, config Config) *DVM { + ctx := vm.Context{ + CanTransfer: core.CanTransfer, + Transfer: core.Transfer, + } + dvm := &DVM{ + Context: ctx, + statedb: statedb, + metering: config.Metering, + } + + if dvm.metering { + meteringContractAddress := common.HexToAddress(sentinelContractAddress) + meteringCode := dvm.StateDB().GetCode(meteringContractAddress) + + var err error + dvm.meteringModule, err = wasm.ReadModule(bytes.NewReader(meteringCode), WrappedModuleResolver(dvm)) + if err != nil { + panic(fmt.Sprintf("Error loading the metering contract: %v", err)) + } + // TODO when the metering contract abides by that rule, check that it + // only exports "main" and "memory". + dvm.meteringStartIndex = int64(dvm.meteringModule.Export.Entries["main"].Index) + mainSig := dvm.meteringModule.FunctionIndexSpace[dvm.meteringStartIndex].Sig + if len(mainSig.ParamTypes) != 0 || len(mainSig.ReturnTypes) != 0 { + panic(fmt.Sprintf("Invalid main function for the metering contract: index=%d sig=%v", dvm.meteringStartIndex, mainSig)) + } + } + + return dvm +} + +func (dvm *DVM) StateDB() vm.StateDB { + return dvm.statedb +} + +func (dvm *DVM) Create(caller vm.ContractRef, code []byte, gas uint64, value *big.Int, in vm.Interpreter) ([]byte, common.Address, uint64, error) { + contractAddr := crypto.CreateAddress(caller.Address(), in.(*DVM).statedb.GetNonce(caller.Address())) + return in.(*DVM).create(caller, &vm.CodeAndHash{Code: code}, gas, value, contractAddr) +} + +func (dvm *DVM) Create2(caller vm.ContractRef, code []byte, gas uint64, endowment *big.Int, salt *big.Int, in vm.Interpreter) ([]byte, common.Address, uint64, error) { + codeAndHash := &vm.CodeAndHash{Code: code} + contractAddr := crypto.CreateAddress2(caller.Address(), common.BigToHash(salt), codeAndHash.Hash().Bytes()) + return dvm.create(caller, codeAndHash, gas, endowment, contractAddr) +} + +// Call executes the contract associated with the addr with the given input as +// parameters. It also handles any necessary value transfer required and takes +// the necessary steps to create accounts and reverses the state in case of an +// execution error or failed value transfer. +func (dvm *DVM) Call(caller vm.ContractRef, addr common.Address, input []byte, gas uint64, value *big.Int, in vm.Interpreter) ([]byte, uint64, error) { + // TODO: do we need these checks? + // if evm.vmConfig.NoRecursion && evm.depth > 0 { + // return nil, gas, nil + // } + + // Fail if we're trying to execute above the call depth limit + if dvm.depth > int(params.CallCreateDepth) { + return nil, gas, vm.ErrDepth + } + // Fail if we're trying to transfer more than the available balance + if !dvm.Context.CanTransfer(dvm.StateDB(), caller.Address(), value) { + return nil, gas, vm.ErrInsufficientBalance + } + + var ( + to = vm.AccountRef(addr) + snapshot = dvm.StateDB().Snapshot() + ) + if !dvm.StateDB().Exist(addr) { + precompiles := vm.PrecompiledContractsByzantium + if precompiles[addr] == nil && value.Sign() == 0 { + return nil, gas, nil + } + dvm.StateDB().CreateAccount(addr) + } + dvm.Transfer(dvm.StateDB(), caller.Address(), to.Address(), value) + + // Initialise a new contract and set the code that is to be used by the EVM. + // The contract is a scoped environment for this execution context only. + contract := vm.NewContract(caller, to, value, gas) + code := dvm.StateDB().GetCode(addr) + if len(code) > 0 && code[0] == vm.DVM && vm.MULTIVM { + code = code[1:] + } + codeAndHash := vm.CodeAndHash{Code: code} + contract.SetCodeOptionalHash(&addr, &codeAndHash) + + // Even if the account has no code, we need to continue because it might be a precompile + ret, err := dvm.run(contract, input, false) + + // When an error was returned by the DVM or when setting the creation code + // above we revert to the snapshot and consume any gas remaining. Additionally + // when we're in homestead this also counts for code storage gas errors. + if err != nil { + dvm.StateDB().RevertToSnapshot(snapshot) + if err != errExecutionReverted { + contract.UseGas(contract.Gas) + } + } + return ret, contract.Gas, err +} + +// CallCode executes the contract associated with the addr with the given input +// as parameters. It also handles any necessary value transfer required and takes +// the necessary steps to create accounts and reverses the state in case of an +// execution error or failed value transfer. +// +// CallCode differs from Call in the sense that it executes the given address' +// code with the caller as context. +func (dvm *DVM) CallCode(caller vm.ContractRef, addr common.Address, input []byte, gas uint64, value *big.Int, in vm.Interpreter) ([]byte, uint64, error) { + // TODO: do we need these checks? + // if evm.vmConfig.NoRecursion && evm.depth > 0 { + // return nil, gas, nil + // } + + // Fail if we're trying to execute above the call depth limit + if dvm.depth > int(params.CallCreateDepth) { + return nil, gas, vm.ErrDepth + } + // Fail if we're trying to transfer more than the available balance + if !dvm.Context.CanTransfer(dvm.StateDB(), caller.Address(), value) { + return nil, gas, vm.ErrInsufficientBalance + } + + var ( + snapshot = dvm.StateDB().Snapshot() + to = vm.AccountRef(caller.Address()) + ) + + // initialise a new contract and set the code that is to be used by the + // DVM. The contract is a scoped environment for this execution context + // only. + contract := vm.NewContract(caller, to, value, gas) + code := dvm.StateDB().GetCode(addr) + if len(code) > 0 && vm.MULTIVM { + code = code[1:] + } + codeAndHash := vm.CodeAndHash{Code: code} + contract.SetCodeOptionalHash(&addr, &codeAndHash) + + ret, err := dvm.run(contract, input, false) + if err != nil { + dvm.StateDB().RevertToSnapshot(snapshot) + if err != errExecutionReverted { + contract.UseGas(contract.Gas) + } + } + return ret, contract.Gas, err +} + +// DelegateCall executes the contract associated with the addr with the given input +// as parameters. It reverses the state in case of an execution error. +// +// DelegateCall differs from CallCode in the sense that it executes the given address' +// code with the caller as context and the caller is set to the caller of the caller. +func (dvm *DVM) DelegateCall(caller vm.ContractRef, addr common.Address, input []byte, gas uint64, in vm.Interpreter) ([]byte, uint64, error) { + // TODO: do we need these checks? + // if evm.vmConfig.NoRecursion && evm.depth > 0 { + // return nil, gas, nil + // } + + // Fail if we're trying to execute above the call depth limit + if dvm.depth > int(params.CallCreateDepth) { + return nil, gas, vm.ErrDepth + } + + var ( + snapshot = dvm.StateDB().Snapshot() + to = vm.AccountRef(caller.Address()) + ) + + // Initialise a new contract and make initialise the delegate values + contract := vm.NewContract(caller, to, nil, gas).AsDelegate() + code := dvm.StateDB().GetCode(addr) + if len(code) > 0 && vm.MULTIVM { + code = code[1:] + } + codeAndHash := vm.CodeAndHash{Code: code} + contract.SetCodeOptionalHash(&addr, &codeAndHash) + + ret, err := dvm.run(contract, input, false) + if err != nil { + dvm.StateDB().RevertToSnapshot(snapshot) + if err != errExecutionReverted { + contract.UseGas(contract.Gas) + } + } + return ret, contract.Gas, err +} + +// StaticCall executes the contract associated with the addr with the given input +// as parameters while disallowing any modifications to the state during the call. +// Opcodes that attempt to perform such modifications will result in exceptions +// instead of performing the modifications. +func (dvm *DVM) StaticCall(caller vm.ContractRef, addr common.Address, input []byte, gas uint64, in vm.Interpreter) ([]byte, uint64, error) { + // TODO: do we need these checks? + // if evm.vmConfig.NoRecursion && evm.depth > 0 { + // return nil, gas, nil + // } + + // Fail if we're trying to execute above the call depth limit + if dvm.depth > int(params.CallCreateDepth) { + return nil, gas, vm.ErrDepth + } + + var ( + to = vm.AccountRef(addr) + snapshot = dvm.StateDB().Snapshot() + ) + + // Initialise a new contract and set the code that is to be used by the + // DVM. The contract is a scoped environment for this execution context + // only. + contract := vm.NewContract(caller, to, new(big.Int), gas) + code := dvm.StateDB().GetCode(addr) + if len(code) > 0 && vm.MULTIVM { + code = code[1:] + } + codeAndHash := vm.CodeAndHash{Code: code} + contract.SetCodeOptionalHash(&addr, &codeAndHash) + + // We do an AddBalance of zero here, just in order to trigger a touch. + // This doesn't matter on Mainnet, where all empties are gone at the time of Byzantium, + // but is the correct thing to do and matters on other networks, in tests, and potential + // future scenarios + dvm.StateDB().AddBalance(addr, new(big.Int)) + + // When an error was returned by the DVM or when setting the creation code + // above we revert to the snapshot and consume any gas remaining. Additionally + // when we're in Homestead this also counts for code storage gas errors. + ret, err := dvm.run(contract, input, true) + if err != nil { + dvm.StateDB().RevertToSnapshot(snapshot) + if err != errExecutionReverted { + contract.UseGas(contract.Gas) + } + } + return ret, contract.Gas, err +} + +func (dvm *DVM) create(caller vm.ContractRef, codeAndHash *vm.CodeAndHash, gas uint64, value *big.Int, address common.Address) ([]byte, common.Address, uint64, error) { + // Depth check execution. Fail if we're trying to execute above the + // limit. + if dvm.depth > int(params.CallCreateDepth) { + return nil, common.Address{}, gas, vm.ErrDepth + } + if !dvm.Context.CanTransfer(dvm.statedb, caller.Address(), value) { + return nil, common.Address{}, gas, vm.ErrInsufficientBalance + } + nonce := dvm.statedb.GetNonce(caller.Address()) + dvm.statedb.SetNonce(caller.Address(), nonce+1) + + // Ensure there's no existing contract already at the designated address + contractHash := dvm.statedb.GetCodeHash(address) + if dvm.statedb.GetNonce(address) != 0 || (contractHash != (common.Hash{}) && contractHash != emptyCodeHash) { + return nil, common.Address{}, 0, vm.ErrContractAddressCollision + } + + // Create a new account on the state + snapshot := dvm.statedb.Snapshot() + dvm.statedb.CreateAccount(address) + dvm.statedb.SetNonce(address, 1) + dvm.Context.Transfer(dvm.statedb, caller.Address(), address, value) + + // initialise a new contract and set the code that is to be used by the + // DVM. The contract is a scoped environment for this execution context + // only. + contract := vm.NewContract(caller, vm.AccountRef(address), value, gas) + meteredCode, err := dvm.PreContractCreation(codeAndHash.Code, contract) + if err != nil { + return nil, address, gas, nil + } + codeAndHash.Code = meteredCode + contract.SetCodeOptionalHash(&address, codeAndHash) + + // TODO: do we need these checks? + // if evm.vmConfig.NoRecursion && evm.depth > 0 { + // return nil, address, gas, nil + // } + + ret, err := dvm.run(contract, nil, false) + + // The new contract needs to be metered after it has executed the constructor + if err != nil { + if dvm.CanRun(contract.Code) { + ret, err = dvm.PostContractCreation(ret) + } + } + + // check whether the max code size has been exceeded + maxCodeSizeExceeded := len(ret) > params.MaxCodeSize + // if the contract creation ran successfully and no errors were returned + // calculate the gas required to store the code. If the code could not + // be stored due to not enough gas set an error and let it be handled + // by the error checking condition below. + if err == nil && !maxCodeSizeExceeded { + createDataGas := uint64(len(ret)) * params.CreateDataGas + if contract.UseGas(createDataGas) { + dvm.statedb.SetCode(address, ret) + } else { + err = vm.ErrCodeStoreOutOfGas + } + } + + // When an error was returned by the DVM or when setting the creation code + // above we revert to the snapshot and consume any gas remaining. Additionally + // when we're in homestead this also counts for code storage gas errors. + if maxCodeSizeExceeded || err != nil { + dvm.statedb.RevertToSnapshot(snapshot) + if err != errExecutionReverted { + contract.UseGas(contract.Gas) + } + } + // Assign err if contract code size exceeds the max while the err is still empty. + if maxCodeSizeExceeded && err == nil { + err = errMaxCodeSizeExceeded + } + + return ret, address, contract.Gas, err +} + +// 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. +func (dvm *DVM) run(contract *vm.Contract, input []byte, ro bool) ([]byte, error) { + // Take care of running precompile contracts + if contract.CodeAddr != nil { + precompiles := vm.PrecompiledContractsByzantium + if p := precompiles[*contract.CodeAddr]; p != nil { + return vm.RunPrecompiledContract(p, input, contract) + } + } + + // Increment the call depth which is restricted to 1024 + dvm.depth++ + defer func() { dvm.depth-- }() + + fmt.Printf("contract.Code %v\n", contract.Code) + dvm.contract = contract + dvm.contract.Input = input + + module, err := wasm.ReadModule(bytes.NewReader(contract.Code), WrappedModuleResolver(dvm)) + if err != nil { + dvm.terminationType = TerminateInvalid + return nil, fmt.Errorf("Error decoding module at address %s: %v", contract.Address().Hex(), err) + } + + wavm, err := exec.NewVM(module) + if err != nil { + dvm.terminationType = TerminateInvalid + return nil, fmt.Errorf("could not create the vm: %v", err) + } + wavm.RecoverPanic = true + dvm.vm = wavm + + mainIndex, err := validateModule(module) + if err != nil { + dvm.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 = wavm.ExecCode(int64(mainIndex)) + + if err != nil && err != errExecutionReverted { + dvm.terminationType = TerminateInvalid + } + + if dvm.StateDB().HasSuicided(contract.Address()) { + dvm.StateDB().AddRefund(params.SuicideRefundGas) + err = nil + } + + return dvm.returnData, err + } + + dvm.terminationType = TerminateInvalid + return nil, errors.New("Could not find a suitable 'main' function in that contract") +} + +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) + case "memory": + if entry.Kind != wasm.ExternalMemory { + return -1, fmt.Errorf("'memory' is not a memory in module") + } + 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 +} + +// CanRun checks the binary for a WASM header and accepts the binary blob +// if it matches. +func (dvm *DVM) CanRun(file []byte) bool { + // Check the header + if len(file) < 4 || string(file[:4]) != "\000asm" { + return false + } + + return true +} + +// PreContractCreation meters the contract's its init code before it +// is run. +func (dvm *DVM) PreContractCreation(code []byte, contract *vm.Contract) ([]byte, error) { + savedContract := dvm.contract + dvm.contract = contract + + defer func() { + dvm.contract = savedContract + }() + + if dvm.metering { + metered, _, err := sentinel(dvm, code) + if len(metered) < 5 || err != nil { + return nil, fmt.Errorf("Error metering the init contract code, err=%v", err) + } + return metered, nil + } + return code, 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 (dvm *DVM) PostContractCreation(code []byte) ([]byte, error) { + // If a REVERT has been encountered, then return the code and + if dvm.terminationType == TerminateRevert { + return nil, errExecutionReverted + } + + if dvm.CanRun(code) { + if dvm.metering { + meteredCode, _, err := sentinel(dvm, code) + code = meteredCode + 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 { + dvm.terminationType = TerminateInvalid + return nil, err + } + } + } + + return code, nil +} diff --git a/core/vm/dvm/eei.go b/core/vm/dvm/eei.go new file mode 100644 index 000000000..df97438b6 --- /dev/null +++ b/core/vm/dvm/eei.go @@ -0,0 +1,1007 @@ +// Copyright 2018 The go-ethereum Authors +// This file is part of go-ethereum. +// +// go-ethereum is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// go-ethereum is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with go-ethereum. If not, see . + +// This file lists the EEI functions, so that they can be bound to any +// ewasm-compatible module, as well as the types of these functions + +package dvm + +import ( + "fmt" + "math/big" + "reflect" + + "github.com/dexon-foundation/dexon/common" + "github.com/dexon-foundation/dexon/core/types" + "github.com/dexon-foundation/dexon/core/vm" + "github.com/dexon-foundation/dexon/crypto" + + "github.com/go-interpreter/wagon/exec" + "github.com/go-interpreter/wagon/wasm" +) + +const ( + // EEICallSuccess is the return value in case of a successful contract execution + EEICallSuccess = 0 + // ErrEEICallFailure is the return value in case of a contract execution failture + ErrEEICallFailure = 1 + // ErrEEICallRevert is the return value in case a contract calls `revert` + ErrEEICallRevert = 2 +) + +// List of gas costs +const ( + GasCostZero = 0 + GasCostBase = 2 + GasCostVeryLow = 3 + GasCostLow = 5 + GasCostMid = 8 + GasCostHigh = 10 + GasCostExtCode = 700 + GasCostBalance = 400 + GasCostSLoad = 200 + GasCostJumpDest = 1 + GasCostSSet = 20000 + GasCostSReset = 5000 + GasRefundSClear = 15000 + GasRefundSelfDestruct = 24000 + GasCostCreate = 32000 + GasCostCall = 700 + GasCostCallValue = 9000 + GasCostCallStipend = 2300 + GasCostNewAccount = 25000 + GasCostLog = 375 + GasCostLogData = 8 + GasCostLogTopic = 375 + GasCostCopy = 3 + GasCostBlockHash = 800 +) + +var eeiTypes = &wasm.SectionTypes{ + Entries: []wasm.FunctionSig{ + { + ParamTypes: []wasm.ValueType{wasm.ValueTypeI64}, + ReturnTypes: []wasm.ValueType{}, + }, + { + ParamTypes: []wasm.ValueType{wasm.ValueTypeI32}, + ReturnTypes: []wasm.ValueType{}, + }, + { + ParamTypes: []wasm.ValueType{wasm.ValueTypeI32, wasm.ValueTypeI32}, + ReturnTypes: []wasm.ValueType{}, + }, + { + ParamTypes: []wasm.ValueType{wasm.ValueTypeI64, wasm.ValueTypeI32}, + ReturnTypes: []wasm.ValueType{wasm.ValueTypeI32}, + }, + { + ParamTypes: []wasm.ValueType{wasm.ValueTypeI64, wasm.ValueTypeI32, wasm.ValueTypeI32, wasm.ValueTypeI32, wasm.ValueTypeI32}, + ReturnTypes: []wasm.ValueType{wasm.ValueTypeI32}, + }, + { + ParamTypes: []wasm.ValueType{wasm.ValueTypeI32}, + ReturnTypes: []wasm.ValueType{wasm.ValueTypeI32}, + }, + { + ParamTypes: []wasm.ValueType{wasm.ValueTypeI32, wasm.ValueTypeI32, wasm.ValueTypeI32}, + ReturnTypes: []wasm.ValueType{}, + }, + { + ParamTypes: []wasm.ValueType{}, + ReturnTypes: []wasm.ValueType{wasm.ValueTypeI32}, + }, + { + ParamTypes: []wasm.ValueType{wasm.ValueTypeI64, wasm.ValueTypeI32, wasm.ValueTypeI32, wasm.ValueTypeI32}, + ReturnTypes: []wasm.ValueType{wasm.ValueTypeI32}, + }, + { + ParamTypes: []wasm.ValueType{wasm.ValueTypeI64, wasm.ValueTypeI32, wasm.ValueTypeI32, wasm.ValueTypeI32, wasm.ValueTypeI32}, + ReturnTypes: []wasm.ValueType{wasm.ValueTypeI32}, + }, + { + ParamTypes: []wasm.ValueType{wasm.ValueTypeI32, wasm.ValueTypeI32, wasm.ValueTypeI32, wasm.ValueTypeI32}, + ReturnTypes: []wasm.ValueType{wasm.ValueTypeI32}, + }, + { + ParamTypes: []wasm.ValueType{wasm.ValueTypeI32, wasm.ValueTypeI32, wasm.ValueTypeI32, wasm.ValueTypeI32}, + ReturnTypes: []wasm.ValueType{}, + }, + { + ParamTypes: []wasm.ValueType{}, + ReturnTypes: []wasm.ValueType{wasm.ValueTypeI64}, + }, + { + ParamTypes: []wasm.ValueType{wasm.ValueTypeI32, wasm.ValueTypeI32, wasm.ValueTypeI32, wasm.ValueTypeI32, wasm.ValueTypeI32, wasm.ValueTypeI32, wasm.ValueTypeI32}, + ReturnTypes: []wasm.ValueType{}, + }, + }, +} + +func swapEndian(src []byte) []byte { + ret := make([]byte, len(src)) + for i, v := range src { + ret[len(src)-i-1] = v + } + return ret +} + +func (in *DVM) gasAccounting(cost uint64) { + if in.contract == nil { + panic("nil contract") + } + if cost > in.contract.Gas { + panic(fmt.Sprintf("out of gas %d > %d", cost, in.contract.Gas)) + } + in.contract.Gas -= cost +} + +func getDebugFuncs(in *DVM) []wasm.Function { + return []wasm.Function{ + { + Sig: &eeiTypes.Entries[2], + Host: reflect.ValueOf(func(p *exec.Process, o, l int32) { printMemHex(p, in, o, l) }), + Body: &wasm.FunctionBody{}, + }, + { + Sig: &eeiTypes.Entries[1], + Host: reflect.ValueOf(func(p *exec.Process, o int32) { printStorageHex(p, in, o) }), + Body: &wasm.FunctionBody{}, + }, + } +} + +func printMemHex(p *exec.Process, in *DVM, offset, length int32) { + data := readSize(p, offset, int(length)) + for _, v := range data { + fmt.Printf("%02x", v) + } + fmt.Println("") +} + +func printStorageHex(p *exec.Process, in *DVM, pathOffset int32) { + + path := common.BytesToHash(readSize(p, pathOffset, common.HashLength)) + val := in.StateDB().GetState(in.contract.Address(), path) + for v := range val { + fmt.Printf("%02x", v) + } + fmt.Println("") +} + +// Return the list of function descriptors. This is a function instead of +// a variable in order to avoid an initialization loop. +func eeiFuncs(in *DVM) []wasm.Function { + return []wasm.Function{ + { + Sig: &eeiTypes.Entries[0], // TODO use constants or find the right entry in the list + Host: reflect.ValueOf(func(p *exec.Process, a int64) { useGas(p, in, a) }), + Body: &wasm.FunctionBody{}, + }, + { + Sig: &eeiTypes.Entries[1], + Host: reflect.ValueOf(func(p *exec.Process, r int32) { getAddress(p, in, r) }), + Body: &wasm.FunctionBody{}, + }, + { + Sig: &eeiTypes.Entries[2], + Host: reflect.ValueOf(func(p *exec.Process, a, r int32) { getExternalBalance(p, in, a, r) }), + Body: &wasm.FunctionBody{}, + }, + { + Sig: &eeiTypes.Entries[3], + Host: reflect.ValueOf(func(p *exec.Process, n int64, r int32) int32 { return getBlockHash(p, in, n, r) }), + Body: &wasm.FunctionBody{}, + }, + { + Sig: &eeiTypes.Entries[4], + Host: reflect.ValueOf(func(p *exec.Process, g int64, a, v, d, l int32) int32 { return call(p, in, g, a, v, d, l) }), + Body: &wasm.FunctionBody{}, + }, + { + Sig: &eeiTypes.Entries[6], + Host: reflect.ValueOf(func(p *exec.Process, r, d, l int32) { callDataCopy(p, in, r, d, l) }), + Body: &wasm.FunctionBody{}, + }, + { + Sig: &eeiTypes.Entries[7], + Host: reflect.ValueOf(func(p *exec.Process) int32 { return getCallDataSize(p, in) }), + Body: &wasm.FunctionBody{}, + }, + { + Sig: &eeiTypes.Entries[9], + Host: reflect.ValueOf(func(p *exec.Process, g int64, a, v, d, l int32) int32 { return callCode(p, in, g, a, v, d, l) }), + Body: &wasm.FunctionBody{}, + }, + { + Sig: &eeiTypes.Entries[8], + Host: reflect.ValueOf(func(p *exec.Process, g int64, a, d, l int32) int32 { return callDelegate(p, in, g, a, d, l) }), + Body: &wasm.FunctionBody{}, + }, + { + Sig: &eeiTypes.Entries[8], + Host: reflect.ValueOf(func(p *exec.Process, g int64, a, d, l int32) int32 { return callStatic(p, in, g, a, d, l) }), + Body: &wasm.FunctionBody{}, + }, + { + Sig: &eeiTypes.Entries[2], + Host: reflect.ValueOf(func(pr *exec.Process, p, v int32) { storageStore(pr, in, p, v) }), + Body: &wasm.FunctionBody{}, + }, + { + Sig: &eeiTypes.Entries[2], + Host: reflect.ValueOf(func(pr *exec.Process, p, r int32) { storageLoad(pr, in, p, r) }), + Body: &wasm.FunctionBody{}, + }, + { + Sig: &eeiTypes.Entries[1], + Host: reflect.ValueOf(func(p *exec.Process, r int32) { getCaller(p, in, r) }), + Body: &wasm.FunctionBody{}, + }, + { + Sig: &eeiTypes.Entries[1], + Host: reflect.ValueOf(func(p *exec.Process, r int32) { getCallValue(p, in, r) }), + Body: &wasm.FunctionBody{}, + }, + { + Sig: &eeiTypes.Entries[6], + Host: reflect.ValueOf(func(p *exec.Process, r, c, l int32) { codeCopy(p, in, r, c, l) }), + Body: &wasm.FunctionBody{}, + }, + { + Sig: &eeiTypes.Entries[7], + Host: reflect.ValueOf(func(p *exec.Process) int32 { return getCodeSize(p, in) }), + Body: &wasm.FunctionBody{}, + }, + { + Sig: &eeiTypes.Entries[1], + Host: reflect.ValueOf(func(p *exec.Process, r int32) { getBlockCoinbase(p, in, r) }), + Body: &wasm.FunctionBody{}, + }, + { + Sig: &eeiTypes.Entries[10], + Host: reflect.ValueOf(func(p *exec.Process, v, d, l, r uint32) int32 { return create(p, in, v, d, l, r) }), + Body: &wasm.FunctionBody{}, + }, + { + Sig: &eeiTypes.Entries[1], + Host: reflect.ValueOf(func(p *exec.Process, r int32) { getBlockDifficulty(p, in, r) }), + Body: &wasm.FunctionBody{}, + }, + { + Sig: &eeiTypes.Entries[11], + Host: reflect.ValueOf(func(p *exec.Process, a, r, c, l int32) { externalCodeCopy(p, in, a, r, c, l) }), + Body: &wasm.FunctionBody{}, + }, + { + Sig: &eeiTypes.Entries[5], + Host: reflect.ValueOf(func(p *exec.Process, a int32) int32 { return getExternalCodeSize(p, in, a) }), + Body: &wasm.FunctionBody{}, + }, + { + Sig: &eeiTypes.Entries[12], + Host: reflect.ValueOf(func(p *exec.Process) int64 { return getGasLeft(p, in) }), + Body: &wasm.FunctionBody{}, + }, + { + Sig: &eeiTypes.Entries[12], + Host: reflect.ValueOf(func(p *exec.Process) int64 { return getBlockGasLimit(p, in) }), + Body: &wasm.FunctionBody{}, + }, + { + Sig: &eeiTypes.Entries[1], + Host: reflect.ValueOf(func(p *exec.Process, v int32) { getTxGasPrice(p, in, v) }), + Body: &wasm.FunctionBody{}, + }, + { + Sig: &eeiTypes.Entries[13], + Host: reflect.ValueOf(func(p *exec.Process, d, l, n, t1, t2, t3, t4 int32) { log(p, in, d, l, n, t1, t2, t3, t4) }), + Body: &wasm.FunctionBody{}, + }, + { + Sig: &eeiTypes.Entries[12], + Host: reflect.ValueOf(func(p *exec.Process) int64 { return getBlockNumber(p, in) }), + Body: &wasm.FunctionBody{}, + }, + { + Sig: &eeiTypes.Entries[1], + Host: reflect.ValueOf(func(p *exec.Process, r int32) { getTxOrigin(p, in, r) }), + Body: &wasm.FunctionBody{}, + }, + { + Sig: &eeiTypes.Entries[2], + Host: reflect.ValueOf(func(p *exec.Process, d, l int32) { finish(p, in, d, l) }), + Body: &wasm.FunctionBody{}, + }, + { + Sig: &eeiTypes.Entries[2], + Host: reflect.ValueOf(func(p *exec.Process, d, l int32) { revert(p, in, d, l) }), + Body: &wasm.FunctionBody{}, + }, + { + Sig: &eeiTypes.Entries[7], + Host: reflect.ValueOf(func(p *exec.Process) int32 { return getReturnDataSize(p, in) }), + Body: &wasm.FunctionBody{}, + }, + { + Sig: &eeiTypes.Entries[6], + Host: reflect.ValueOf(func(p *exec.Process, r, d, l int32) { returnDataCopy(p, in, r, d, l) }), + Body: &wasm.FunctionBody{}, + }, + { + Sig: &eeiTypes.Entries[1], + Host: reflect.ValueOf(func(p *exec.Process, a int32) { selfDestruct(p, in, a) }), + Body: &wasm.FunctionBody{}, + }, + { + Sig: &eeiTypes.Entries[12], + Host: reflect.ValueOf(func(p *exec.Process) int64 { return getBlockTimestamp(p, in) }), + Body: &wasm.FunctionBody{}, + }, + } +} + +func readSize(p *exec.Process, offset int32, size int) []byte { + // TODO modify the process interface to find out how much memory is + // available on the system. + val := make([]byte, size) + p.ReadAt(val, int64(offset)) + return val +} + +func useGas(p *exec.Process, in *DVM, amount int64) { + in.gasAccounting(uint64(amount)) +} + +func getAddress(p *exec.Process, in *DVM, resultOffset int32) { + in.gasAccounting(GasCostBase) + contractBytes := in.contract.CodeAddr.Bytes() + p.WriteAt(contractBytes, int64(resultOffset)) +} + +func getExternalBalance(p *exec.Process, in *DVM, addressOffset int32, resultOffset int32) { + in.gasAccounting(in.gasTable.Balance) + addr := common.BytesToAddress(readSize(p, addressOffset, common.AddressLength)) + balance := swapEndian(in.StateDB().GetBalance(addr).Bytes()) + p.WriteAt(balance, int64(resultOffset)) +} + +func getBlockHash(p *exec.Process, in *DVM, number int64, resultOffset int32) int32 { + in.gasAccounting(GasCostBlockHash) + n := big.NewInt(number) + n.Sub(in.Context.BlockNumber, n) + if n.Cmp(big.NewInt(256)) > 0 || n.Cmp(big.NewInt(0)) <= 0 { + return 1 + } + h := in.GetHash(uint64(number)) + p.WriteAt(h.Bytes(), int64(resultOffset)) + return 0 +} + +func callCommon(in *DVM, contract, targetContract *vm.Contract, input []byte, value *big.Int, snapshot int, gas int64, ro bool) int32 { + if in.depth > maxCallDepth { + return ErrEEICallFailure + } + + savedVM := in.vm + + in.run(targetContract, input, ro) + + in.vm = savedVM + in.contract = contract + + if value.Cmp(big.NewInt(0)) != 0 { + in.gasAccounting(uint64(gas) - targetContract.Gas - GasCostCallStipend) + } else { + in.gasAccounting(uint64(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 call(p *exec.Process, in *DVM, gas int64, addressOffset int32, valueOffset int32, dataOffset int32, dataLength int32) int32 { + contract := in.contract + + // Get the address of the contract to call + addr := common.BytesToAddress(readSize(p, addressOffset, common.AddressLength)) + + // 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. + // 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 + } + + // Fail if the account's balance is greater than 128bits as discussed + // in https://github.com/ewasm/hera/issues/456 + if in.StateDB().GetBalance(contract.Address()).Cmp(check128bits) > 0 { + in.gasAccounting(contract.Gas) + return ErrEEICallRevert + } + + if in.staticMode && value.Cmp(big.NewInt(0)) != 0 { + in.gasAccounting(in.contract.Gas) + return ErrEEICallFailure + } + + in.gasAccounting(GasCostCall) + + if in.depth > maxCallDepth { + return ErrEEICallFailure + } + + if value.Cmp(big.NewInt(0)) != 0 { + in.gasAccounting(GasCostCallValue) + } + + // Get the arguments. + // TODO check the need for callvalue (seems not, a lot of that stuff is + // already accounted for in the functions that I already called - need to + // refactor all that) + input := readSize(p, dataOffset, int(dataLength)) + + snapshot := in.StateDB().Snapshot() + + // Check that there is enough balance to transfer the value + if in.StateDB().GetBalance(contract.Address()).Cmp(value) < 0 { + 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 + + // Add amount to recipient + in.Transfer(in.StateDB(), contract.Address(), addr, value) + + // Load the contract code in a new VM structure + targetContract := vm.NewContract(contract, vm.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 + + 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 *DVM, resultOffset int32, dataOffset int32, length int32) { + in.gasAccounting(GasCostVeryLow + GasCostCopy*(uint64(length+31)>>5)) + p.WriteAt(in.contract.Input[dataOffset:dataOffset+length], int64(resultOffset)) +} + +func getCallDataSize(p *exec.Process, in *DVM) int32 { + in.gasAccounting(GasCostBase) + return int32(len(in.contract.Input)) +} + +func callCode(p *exec.Process, in *DVM, 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 + addr := common.BytesToAddress(readSize(p, addressOffset, common.AddressLength)) + + // 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(readSize(p, valueOffset, u128Len)) + + if value.Cmp(big.NewInt(0)) != 0 { + in.gasAccounting(GasCostCallValue) + gas += GasCostCallStipend + } + + // Get the arguments. + // TODO check the need for callvalue (seems not, a lot of that stuff is + // already accounted for in the functions that I already called - need to + // refactor all that) + input := readSize(p, dataOffset, int(dataLength)) + + snapshot := in.StateDB().Snapshot() + + // 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)) + return ErrEEICallFailure + } + + // TODO tracing + // TODO check that EIP-150 is respected + + // Load the contract code in a new VM structure + targetContract := vm.NewContract(vm.AccountRef(contract.Caller()), vm.AccountRef(contract.Address()), value, uint64(gas)) + code := in.StateDB().GetCode(addr) + targetContract.SetCallCode(&addr, in.StateDB().GetCodeHash(addr), code) + + return callCommon(in, contract, targetContract, input, value, snapshot, gas, false) +} + +func callDelegate(p *exec.Process, in *DVM, gas int64, addressOffset int32, dataOffset int32, dataLength int32) int32 { + in.gasAccounting(GasCostCall) + + contract := in.contract + + // Get the address of the contract to call + addr := common.BytesToAddress(readSize(p, addressOffset, common.AddressLength)) + + // 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 := contract.Value + + if value.Cmp(big.NewInt(0)) != 0 { + in.gasAccounting(GasCostCallValue) + gas += GasCostCallStipend + } + + // Get the arguments. + // TODO check the need for callvalue (seems not, a lot of that stuff is + // already accounted for in the functions that I already called - need to + // refactor all that) + input := readSize(p, dataOffset, int(dataLength)) + + snapshot := in.StateDB().Snapshot() + + // 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)) + return ErrEEICallFailure + } + + // TODO tracing + // TODO check that EIP-150 is respected + + // Load the contract code in a new VM structure + targetContract := vm.NewContract(vm.AccountRef(contract.Address()), vm.AccountRef(contract.Address()), value, uint64(gas)) + code := in.StateDB().GetCode(addr) + caddr := contract.Address() + targetContract.SetCallCode(&caddr, in.StateDB().GetCodeHash(addr), code) + + return callCommon(in, contract, targetContract, input, value, snapshot, gas, false) +} + +func callStatic(p *exec.Process, in *DVM, gas int64, addressOffset int32, dataOffset int32, dataLength int32) int32 { + contract := in.contract + + // Get the address of the contract to call + addr := common.BytesToAddress(readSize(p, addressOffset, common.AddressLength)) + + value := big.NewInt(0) + + // Get the arguments. + // TODO check the need for callvalue (seems not, a lot of that stuff is + // already accounted for in the functions that I already called - need to + // refactor all that) + input := readSize(p, dataOffset, int(dataLength)) + + snapshot := in.StateDB().Snapshot() + + in.gasAccounting(GasCostCall) + + if in.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 + + // Add amount to recipient + in.Transfer(in.StateDB(), contract.Address(), addr, value) + + // Load the contract code in a new VM structure + targetContract := vm.NewContract(contract, vm.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 }() + + 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 *DVM, pathOffset int32, valueOffset int32) { + if interpreter.staticMode { + panic("Static mode violation in storageStore") + } + + loc := common.BytesToHash(readSize(p, pathOffset, u256Len)) + val := common.BytesToHash(readSize(p, valueOffset, u256Len)) + + nonZeroBytes := 0 + for _, b := range val.Bytes() { + if b != 0 { + nonZeroBytes++ + } + } + + oldValue := interpreter.StateDB().GetState(interpreter.contract.Address(), loc) + oldNonZeroBytes := 0 + for _, b := range oldValue.Bytes() { + if b != 0 { + oldNonZeroBytes++ + } + } + + if (nonZeroBytes > 0 && oldNonZeroBytes != nonZeroBytes) || (oldNonZeroBytes != 0 && nonZeroBytes == 0) { + interpreter.gasAccounting(GasCostSSet) + } else { + // Refund for setting one value to 0 or if the "zeroness" remains + // unchanged. + interpreter.gasAccounting(GasCostSReset) + } + + interpreter.StateDB().SetState(interpreter.contract.Address(), loc, val) +} + +func storageLoad(p *exec.Process, interpreter *DVM, pathOffset int32, resultOffset int32) { + interpreter.gasAccounting(interpreter.gasTable.SLoad) + loc := common.BytesToHash(readSize(p, pathOffset, u256Len)) + valBytes := interpreter.StateDB().GetState(interpreter.contract.Address(), loc).Bytes() + p.WriteAt(valBytes, int64(resultOffset)) +} + +func getCaller(p *exec.Process, in *DVM, resultOffset int32) { + callerAddress := in.contract.CallerAddress + in.gasAccounting(GasCostBase) + p.WriteAt(callerAddress.Bytes(), int64(resultOffset)) +} + +func getCallValue(p *exec.Process, in *DVM, resultOffset int32) { + in.gasAccounting(GasCostBase) + p.WriteAt(swapEndian(in.contract.Value.Bytes()), int64(resultOffset)) +} + +func codeCopy(p *exec.Process, in *DVM, resultOffset int32, codeOffset int32, length int32) { + in.gasAccounting(GasCostVeryLow + GasCostCopy*(uint64(length+31)>>5)) + code := in.contract.Code + p.WriteAt(code[codeOffset:codeOffset+length], int64(resultOffset)) +} + +func getCodeSize(p *exec.Process, in *DVM) int32 { + in.gasAccounting(GasCostBase) + code := in.StateDB().GetCode(*in.contract.CodeAddr) + return int32(len(code)) +} + +func getBlockCoinbase(p *exec.Process, in *DVM, resultOffset int32) { + in.gasAccounting(GasCostBase) + p.WriteAt(in.Coinbase.Bytes(), int64(resultOffset)) +} + +func sentinel(in *DVM, input []byte) ([]byte, uint64, error) { + savedContract := in.contract + savedVM := in.vm + defer func() { + in.contract = savedContract + in.vm = savedVM + }() + meteringContractAddress := common.HexToAddress(sentinelContractAddress) + meteringCode := in.StateDB().GetCode(meteringContractAddress) + in.contract = vm.NewContract(in.contract, vm.AccountRef(meteringContractAddress), &big.Int{}, in.contract.Gas) + in.contract.SetCallCode(&meteringContractAddress, crypto.Keccak256Hash(meteringCode), meteringCode) + vm, err := exec.NewVM(in.meteringModule) + vm.RecoverPanic = true + in.vm = vm + if err != nil { + panic(fmt.Sprintf("Error allocating metering VM: %v", err)) + } + in.contract.Input = input + meteredCode, err := in.vm.ExecCode(in.meteringStartIndex) + if meteredCode == nil { + meteredCode = in.returnData + } + + var asBytes []byte + if err == nil { + asBytes = meteredCode.([]byte) + } + + return asBytes, savedContract.Gas - in.contract.Gas, err +} + +func create(p *exec.Process, in *DVM, valueOffset uint32, codeOffset uint32, length uint32, resultOffset uint32) int32 { + in.gasAccounting(GasCostCreate) + savedVM := in.vm + savedContract := in.contract + defer func() { + in.vm = savedVM + in.contract = savedContract + }() + in.terminationType = TerminateInvalid + + if int(codeOffset)+int(length) > len(in.vm.Memory()) { + return ErrEEICallFailure + } + input := readSize(p, int32(codeOffset), int(length)) + + if (int(valueOffset) + u128Len) > len(in.vm.Memory()) { + return ErrEEICallFailure + } + value := swapEndian(readSize(p, int32(valueOffset), u128Len)) + + in.terminationType = TerminateFinish + + // EIP150 says that the calling contract should keep 1/64th of the + // leftover gas. + gas := in.contract.Gas - in.contract.Gas/64 + in.gasAccounting(gas) + + /* Meter the contract code if metering is enabled */ + if in.metering { + input, _, _ = sentinel(in, input) + if len(input) < 5 { + return ErrEEICallFailure + } + } + + _, addr, gasLeft, _ := in.Create(in.contract, input, gas, big.NewInt(0).SetBytes(value), in) + + switch in.terminationType { + case TerminateFinish: + savedContract.Gas += gasLeft + p.WriteAt(addr.Bytes(), int64(resultOffset)) + return EEICallSuccess + case TerminateRevert: + savedContract.Gas += gas + return ErrEEICallRevert + default: + savedContract.Gas += gasLeft + return ErrEEICallFailure + } +} + +func getBlockDifficulty(p *exec.Process, in *DVM, resultOffset int32) { + in.gasAccounting(GasCostBase) + p.WriteAt(swapEndian(in.Difficulty.Bytes()), int64(resultOffset)) +} + +func externalCodeCopy(p *exec.Process, in *DVM, addressOffset int32, resultOffset int32, codeOffset int32, length int32) { + in.gasAccounting(in.gasTable.ExtcodeCopy + GasCostCopy*(uint64(length+31)>>5)) + addr := common.BytesToAddress(readSize(p, addressOffset, common.AddressLength)) + code := in.StateDB().GetCode(addr) + p.WriteAt(code[codeOffset:codeOffset+length], int64(resultOffset)) +} + +func getExternalCodeSize(p *exec.Process, in *DVM, addressOffset int32) int32 { + in.gasAccounting(in.gasTable.ExtcodeSize) + addr := common.BytesToAddress(readSize(p, addressOffset, common.AddressLength)) + code := in.StateDB().GetCode(addr) + return int32(len(code)) +} + +func getGasLeft(p *exec.Process, in *DVM) int64 { + in.gasAccounting(GasCostBase) + return int64(in.contract.Gas) +} + +func getBlockGasLimit(p *exec.Process, in *DVM) int64 { + in.gasAccounting(GasCostBase) + return int64(in.GasLimit) +} + +func getTxGasPrice(p *exec.Process, in *DVM, valueOffset int32) { + in.gasAccounting(GasCostBase) + p.WriteAt(in.GasPrice.Bytes(), int64(valueOffset)) +} + +// It would be nice to be able to use variadic functions to pass the number of topics, +// however this imposes a change in wagon because the number of arguments is being +// checked when calling a function. +func log(p *exec.Process, in *DVM, dataOffset int32, length int32, numberOfTopics int32, topic1 int32, topic2 int32, topic3 int32, topic4 int32) { + in.gasAccounting(GasCostLog + GasCostLogData*uint64(length) + uint64(numberOfTopics)*GasCostLogTopic) + + // TODO need to add some info about the memory boundary on wagon + if uint64(len(in.vm.Memory())) <= uint64(length)+uint64(dataOffset) { + panic("out of memory") + } + data := readSize(p, dataOffset, int(uint32(length))) + topics := make([]common.Hash, numberOfTopics) + + if numberOfTopics > 4 || numberOfTopics < 0 { + in.terminationType = TerminateInvalid + p.Terminate() + } + + // Variadic functions FTW + if numberOfTopics > 0 { + if uint64(len(in.vm.Memory())) <= uint64(topic1) { + panic("out of memory") + } + topics[0] = common.BigToHash(big.NewInt(0).SetBytes(readSize(p, topic1, u256Len))) + } + if numberOfTopics > 1 { + if uint64(len(in.vm.Memory())) <= uint64(topic2) { + panic("out of memory") + } + topics[1] = common.BigToHash(big.NewInt(0).SetBytes(readSize(p, topic2, u256Len))) + } + if numberOfTopics > 2 { + if uint64(len(in.vm.Memory())) <= uint64(topic3) { + panic("out of memory") + } + topics[2] = common.BigToHash(big.NewInt(0).SetBytes(readSize(p, topic3, u256Len))) + } + if numberOfTopics > 3 { + if uint64(len(in.vm.Memory())) <= uint64(topic3) { + panic("out of memory") + } + topics[3] = common.BigToHash(big.NewInt(0).SetBytes(readSize(p, topic4, u256Len))) + } + + in.StateDB().AddLog(&types.Log{ + Address: in.contract.Address(), + Topics: topics, + Data: data, + BlockNumber: in.BlockNumber.Uint64(), + }) +} + +func getBlockNumber(p *exec.Process, in *DVM) int64 { + in.gasAccounting(GasCostBase) + return in.BlockNumber.Int64() +} + +func getTxOrigin(p *exec.Process, in *DVM, resultOffset int32) { + in.gasAccounting(GasCostBase) + p.WriteAt(in.Origin.Big().Bytes(), int64(resultOffset)) +} + +func unWindContract(p *exec.Process, in *DVM, dataOffset int32, length int32) { + in.returnData = make([]byte, length) + p.ReadAt(in.returnData, int64(dataOffset)) +} + +func finish(p *exec.Process, in *DVM, dataOffset int32, length int32) { + unWindContract(p, in, dataOffset, length) + + in.terminationType = TerminateFinish + p.Terminate() +} + +func revert(p *exec.Process, in *DVM, dataOffset int32, length int32) { + unWindContract(p, in, dataOffset, length) + + in.terminationType = TerminateRevert + p.Terminate() +} + +func getReturnDataSize(p *exec.Process, in *DVM) int32 { + in.gasAccounting(GasCostBase) + return int32(len(in.returnData)) +} + +func returnDataCopy(p *exec.Process, in *DVM, resultOffset int32, dataOffset int32, length int32) { + in.gasAccounting(GasCostVeryLow + GasCostCopy*(uint64(length+31)>>5)) + p.WriteAt(in.returnData[dataOffset:dataOffset+length], int64(resultOffset)) +} + +func selfDestruct(p *exec.Process, in *DVM, addressOffset int32) { + contract := in.contract + mem := in.vm.Memory() + + balance := in.StateDB().GetBalance(contract.Address()) + + addr := common.BytesToAddress(mem[addressOffset : addressOffset+common.AddressLength]) + + totalGas := in.gasTable.Suicide + // If the destination address doesn't exist, add the account creation costs + if in.StateDB().Empty(addr) && balance.Sign() != 0 { + totalGas += in.gasTable.CreateBySuicide + } + in.gasAccounting(totalGas) + + in.StateDB().AddBalance(addr, balance) + in.StateDB().Suicide(contract.Address()) + + // Same as for `revert` and `return`, I need to forcefully terminate + // the execution of the contract. + in.terminationType = TerminateSuicide + p.Terminate() +} + +func getBlockTimestamp(p *exec.Process, in *DVM) int64 { + in.gasAccounting(GasCostBase) + return in.Time.Int64() +} diff --git a/core/vm/dvm/linker.go b/core/vm/dvm/linker.go new file mode 100644 index 000000000..eb57f8b79 --- /dev/null +++ b/core/vm/dvm/linker.go @@ -0,0 +1,119 @@ +// Copyright 2018 The go-ethereum Authors +// This file is part of go-ethereum. +// +// go-ethereum is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// go-ethereum is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with go-ethereum. If not, see . + +package dvm + +import ( + "fmt" + + "github.com/go-interpreter/wagon/wasm" +) + +var eeiFunctionList = []string{ + "useGas", + "getAddress", + "getExternalBalance", + "getBlockHash", + "call", + "callDataCopy", + "getCallDataSize", + "callCode", + "callDelegate", + "callStatic", + "storageStore", + "storageLoad", + "getCaller", + "getCallValue", + "codeCopy", + "getCodeSize", + "getBlockCoinbase", + "create", + "getBlockDifficulty", + "externalCodeCopy", + "getExternalCodeSize", + "getGasLeft", + "getBlockGasLimit", + "getTxGasPrice", + "log", + "getBlockNumber", + "getTxOrigin", + "finish", + "revert", + "getReturnDataSize", + "returnDataCopy", + "selfDestruct", + "getBlockTimestamp", +} + +var debugFunctionList = []string{ + "printMemHex", + "printStorageHex", +} + +// ModuleResolver matches all EEI functions to native go functions +func ModuleResolver(dvm *DVM, name string) (*wasm.Module, error) { + if name == "debug" { + debugModule := wasm.NewModule() + debugModule.Types = eeiTypes + debugModule.FunctionIndexSpace = getDebugFuncs(dvm) + entries := make(map[string]wasm.ExportEntry) + for idx, name := range debugFunctionList { + entries[name] = wasm.ExportEntry{ + FieldStr: name, + Kind: wasm.ExternalFunction, + Index: uint32(idx), + } + } + debugModule.Export = &wasm.SectionExports{ + Entries: entries, + } + return debugModule, nil + } + + if name != "ethereum" { + return nil, fmt.Errorf("Unknown module name: %s", name) + } + + m := wasm.NewModule() + m.Types = eeiTypes + m.FunctionIndexSpace = eeiFuncs(dvm) + + entries := make(map[string]wasm.ExportEntry) + + for idx, name := range eeiFunctionList { + entries[name] = wasm.ExportEntry{ + FieldStr: name, + Kind: wasm.ExternalFunction, + Index: uint32(idx), + } + } + + m.Export = &wasm.SectionExports{ + Entries: entries, + } + + return m, nil +} + +// WrappedModuleResolver returns a module resolver function that whose +// EEI functions are closure-bound to a given interpreter. +// This is the first step to closure hell, the plan is to improve PR #59 +// in wagon to be able to pass some context. +func WrappedModuleResolver(dvm *DVM) wasm.ResolveFunc { + return func(name string) (*wasm.Module, error) { + return ModuleResolver(dvm, name) + } +} diff --git a/core/vm/vm.go b/core/vm/vm.go index 99ab2ed90..04e1b1520 100644 --- a/core/vm/vm.go +++ b/core/vm/vm.go @@ -9,6 +9,7 @@ import ( const ( EVM = byte(iota) SQLVM + DVM ) var ( @@ -93,7 +94,7 @@ func StaticCall(caller ContractRef, addr common.Address, input []byte, func getVMAndCode(code []byte) (byte, []byte) { if MULTIVM && len(code) > 0 { switch code[0] { - case EVM, SQLVM: + case EVM, SQLVM, DVM: return code[0], code[1:] default: return EVM, code -- cgit v1.2.3