aboutsummaryrefslogtreecommitdiffstats
path: root/core/vm
diff options
context:
space:
mode:
Diffstat (limited to 'core/vm')
-rw-r--r--core/vm/dvm/dvm.go606
-rw-r--r--core/vm/dvm/eei.go1007
-rw-r--r--core/vm/dvm/linker.go119
-rw-r--r--core/vm/vm.go3
4 files changed, 1734 insertions, 1 deletions
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 <http://www.gnu.org/licenses/>.
+
+// 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 <http://www.gnu.org/licenses/>.
+
+// 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 <http://www.gnu.org/licenses/>.
+
+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