diff --git a/accounts/abi/abi.go b/accounts/abi/abi.go index 824651c02..92da9d1f0 100644 --- a/accounts/abi/abi.go +++ b/accounts/abi/abi.go @@ -21,6 +21,7 @@ import ( "encoding/json" "errors" "fmt" + "github.com/core-coin/go-core/crypto" "io" "github.com/core-coin/go-core/common" @@ -236,3 +237,25 @@ func (abi *ABI) HasFallback() bool { func (abi *ABI) HasReceive() bool { return abi.Receive.Type == Receive } + +// revertSelector is a special function selector for revert reason unpacking. +var revertSelector = crypto.SHA3([]byte("Error(string)"))[:4] + +// UnpackRevert resolves the abi-encoded revert reason. According to the solidity +// spec https://solidity.readthedocs.io/en/latest/control-structures.html#revert, +// the provided revert reason is abi-encoded as if it were a call to a function +// `Error(string)`. So it's a special tool for it. +func UnpackRevert(data []byte) (string, error) { + if len(data) < 4 { + return "", errors.New("invalid data for unpacking") + } + if !bytes.Equal(data[:4], revertSelector) { + return "", errors.New("invalid data for unpacking") + } + var reason string + typ, _ := NewType("string", "", nil) + if err := (Arguments{{Type: typ}}).Unpack(&reason, data[4:]); err != nil { + return "", err + } + return reason, nil +} diff --git a/accounts/abi/abi_test.go b/accounts/abi/abi_test.go index fc9f6f18a..f7014455a 100644 --- a/accounts/abi/abi_test.go +++ b/accounts/abi/abi_test.go @@ -19,6 +19,7 @@ package abi import ( "bytes" "encoding/hex" + "errors" "fmt" "math/big" "reflect" @@ -1082,3 +1083,34 @@ func TestUnnamedEventParam(t *testing.T) { t.Fatalf("Could not find input") } } + +func TestUnpackRevert(t *testing.T) { + t.Parallel() + + var cases = []struct { + input string + expect string + expectErr error + }{ + {"", "", errors.New("invalid data for unpacking")}, + {"08c379a1", "", errors.New("invalid data for unpacking")}, + {"4e401cbe0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000d72657665727420726561736f6e00000000000000000000000000000000000000", "revert reason", nil}, + } + for index, c := range cases { + t.Run(fmt.Sprintf("case %d", index), func(t *testing.T) { + got, err := UnpackRevert(common.Hex2Bytes(c.input)) + if c.expectErr != nil { + if err == nil { + t.Fatalf("Expected non-nil error") + } + if err.Error() != c.expectErr.Error() { + t.Fatalf("Expected error mismatch, want %v, got %v", c.expectErr, err) + } + return + } + if c.expect != got { + t.Fatalf("Output mismatch, want %v, got %v", c.expect, got) + } + }) + } +} diff --git a/accounts/abi/bind/backends/simulated.go b/accounts/abi/bind/backends/simulated.go index 5dcaecd89..fc1e6963a 100644 --- a/accounts/abi/bind/backends/simulated.go +++ b/accounts/abi/bind/backends/simulated.go @@ -20,6 +20,7 @@ import ( "context" "errors" "fmt" + "github.com/core-coin/go-core/accounts/abi" "math/big" "sync" "time" @@ -49,7 +50,6 @@ var ( errBlockNumberUnsupported = errors.New("simulatedBackend cannot access blocks other than the latest block") errBlockDoesNotExist = errors.New("block does not exist in blockchain") errTransactionDoesNotExist = errors.New("transaction does not exist") - errEnergyEstimationFailed = errors.New("energy required exceeds allowance or always failing transaction") ) // SimulatedBackend implements bind.ContractBackend, simulating a blockchain in @@ -349,8 +349,11 @@ func (b *SimulatedBackend) CallContract(ctx context.Context, call gocore.CallMsg if err != nil { return nil, err } - rval, _, _, err := b.callContract(ctx, call, b.blockchain.CurrentBlock(), state) - return rval, err + res, err := b.callContract(ctx, call, b.blockchain.CurrentBlock(), state) + if err != nil { + return nil, err + } + return res.Return(), nil } // PendingCallContract executes a contract call on the pending state. @@ -359,8 +362,11 @@ func (b *SimulatedBackend) PendingCallContract(ctx context.Context, call gocore. defer b.mu.Unlock() defer b.pendingState.RevertToSnapshot(b.pendingState.Snapshot()) - rval, _, _, err := b.callContract(ctx, call, b.pendingBlock, b.pendingState) - return rval, err + res, err := b.callContract(ctx, call, b.pendingBlock, b.pendingState) + if err != nil { + return nil, err + } + return res.Return(), nil } // PendingNonceAt implements PendingStateReader.PendingNonceAt, retrieving @@ -397,23 +403,34 @@ func (b *SimulatedBackend) EstimateEnergy(ctx context.Context, call gocore.CallM } cap = hi - // Create a helper to check if a energy allowance results in an executable transaction - executable := func(energy uint64) bool { + // Create a helper to check if an energy allowance results in an executable transaction + executable := func(energy uint64) (bool, *core.ExecutionResult, error) { call.Energy = energy snapshot := b.pendingState.Snapshot() - _, _, failed, err := b.callContract(ctx, call, b.pendingBlock, b.pendingState) + res, err := b.callContract(ctx, call, b.pendingBlock, b.pendingState) b.pendingState.RevertToSnapshot(snapshot) - if err != nil || failed { - return false + if err != nil { + if err == core.ErrIntrinsicEnergy { + return true, nil, nil // Special case, raise energy limit + } + return true, nil, err // Bail out } - return true + return res.Failed(), res, nil } // Execute the binary search and hone in on an executable energy limit for lo+1 < hi { mid := (hi + lo) / 2 - if !executable(mid) { + failed, _, err := executable(mid) + + // If the error is not nil(consensus error), it means the provided message + // call or transaction will never be accepted no matter how much gas it is + // assigned. Return the error directly, don't struggle any more + if err != nil { + return 0, err + } + if failed { lo = mid } else { hi = mid @@ -421,8 +438,25 @@ func (b *SimulatedBackend) EstimateEnergy(ctx context.Context, call gocore.CallM } // Reject the transaction as invalid if it still fails at the highest allowance if hi == cap { - if !executable(hi) { - return 0, errEnergyEstimationFailed + failed, result, err := executable(hi) + if err != nil { + return 0, err + } + if failed { + if result != nil && result.Err != vm.ErrOutOfEnergy { + errMsg := fmt.Sprintf("always failing transaction (%v)", result.Err) + if len(result.Revert()) > 0 { + ret, err := abi.UnpackRevert(result.Revert()) + if err != nil { + errMsg += fmt.Sprintf(" (%#x)", result.Revert()) + } else { + errMsg += fmt.Sprintf(" (%s)", ret) + } + } + return 0, errors.New(errMsg) + } + // Otherwise, the specified energy cap is too low + return 0, fmt.Errorf("energy required exceeds allowance (%d)", cap) } } return hi, nil @@ -430,7 +464,7 @@ func (b *SimulatedBackend) EstimateEnergy(ctx context.Context, call gocore.CallM // callContract implements common code between normal and pending contract calls. // state is modified during execution, make sure to copy it if necessary. -func (b *SimulatedBackend) callContract(ctx context.Context, call gocore.CallMsg, block *types.Block, statedb *state.StateDB) ([]byte, uint64, bool, error) { +func (b *SimulatedBackend) callContract(ctx context.Context, call gocore.CallMsg, block *types.Block, statedb *state.StateDB) (*core.ExecutionResult, error) { // Ensure message is initialized properly. if call.EnergyPrice == nil { call.EnergyPrice = big.NewInt(1) diff --git a/accounts/abi/bind/backends/simulated_test.go b/accounts/abi/bind/backends/simulated_test.go index 542bfeb6b..b6674a7ec 100644 --- a/accounts/abi/bind/backends/simulated_test.go +++ b/accounts/abi/bind/backends/simulated_test.go @@ -20,6 +20,7 @@ import ( "bytes" "context" "crypto/rand" + "errors" eddsa "github.com/core-coin/go-goldilocks" "math/big" "strings" @@ -366,26 +367,113 @@ func TestSimulatedBackend_TransactionByHash(t *testing.T) { } func TestSimulatedBackend_EstimateEnergy(t *testing.T) { - sim := NewSimulatedBackend( - core.GenesisAlloc{}, 10000000, - ) + /* + pragma solidity ^0.6.4; + contract EnergyEstimation { + function PureRevert() public { revert(); } + function Revert() public { revert("revert reason");} + function OOG() public { for (uint i = 0; ; i++) {}} + function Assert() public { assert(false);} + function Valid() public {} + }*/ + const contractAbi = "[{\"inputs\":[],\"name\":\"Assert\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"OOG\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"PureRevert\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"Revert\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"Valid\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]" + const contractBin = "608060405234801561001057600080fd5b5061027a806100206000396000f3fe608060405234801561001057600080fd5b50600436106100575760003560e01c80633ae247141461005c578063593b9361146100665780636c5fcd821461007057806396be6e031461007a578063a842ede314610084575b600080fd5b61006461008e565b005b61006e6100c9565b005b6100786100ce565b005b6100826100e4565b005b61008c6100e6565b005b6040517f4e401cbe0000000000000000000000000000000000000000000000000000000081526004016100c090610140565b60405180910390fd5b600080fd5b60005b80806100dc9061017b565b9150506100d1565b565b600061011b577f4b1f2ce300000000000000000000000000000000000000000000000000000000600052600160045260246000fd5b565b600061012a600d83610160565b9150610135826101f3565b602082019050919050565b600060208201905081810360008301526101598161011d565b9050919050565b600082825260208201905092915050565b6000819050919050565b600061018682610171565b91507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8214156101b9576101b86101c4565b5b600182019050919050565b7f4b1f2ce300000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b7f72657665727420726561736f6e0000000000000000000000000000000000000060008201525056fea26469706673582212202458901c98e418f9c6e0efe666fc13c1536b98db06e04c1d7bae3eede256401f64736f6c63782a302e382e342d646576656c6f702e323032322e372e382b636f6d6d69742e30353336326564342e6d6f64005b" + + key, _ := crypto.GenerateKey(rand.Reader) + pub := eddsa.Ed448DerivePublicKey(*key) + addr := crypto.PubkeyToAddress(pub) + opts := bind.NewKeyedTransactor(key) + + sim := NewSimulatedBackend(core.GenesisAlloc{addr: {Balance: big.NewInt(params.Core)}}, 10000000) defer sim.Close() - bgCtx := context.Background() - pub := eddsa.Ed448DerivePublicKey(*testKey) - testAddr := crypto.PubkeyToAddress(pub) - energy, err := sim.EstimateEnergy(bgCtx, gocore.CallMsg{ - From: testAddr, - To: &testAddr, - Value: big.NewInt(1000), - Data: []byte{}, - }) - if err != nil { - t.Errorf("could not estimate energy: %v", err) - } + parsed, _ := abi.JSON(strings.NewReader(contractAbi)) + contractAddr, _, _, _ := bind.DeployContract(opts, parsed, common.FromHex(contractBin), sim) + sim.Commit() - if energy != params.TxEnergy { - t.Errorf("expected 21000 energy cost for a transaction got %v", energy) + var cases = []struct { + name string + message gocore.CallMsg + expect uint64 + expectError error + }{ + {"plain transfer(valid)", gocore.CallMsg{ + From: addr, + To: &addr, + Energy: 0, + EnergyPrice: big.NewInt(0), + Value: big.NewInt(1), + Data: nil, + }, params.TxEnergy, nil}, + + {"plain transfer(invalid)", gocore.CallMsg{ + From: addr, + To: &contractAddr, + Energy: 0, + EnergyPrice: big.NewInt(0), + Value: big.NewInt(1), + Data: nil, + }, 0, errors.New("always failing transaction (execution reverted)")}, + + {"Revert", gocore.CallMsg{ + From: addr, + To: &contractAddr, + Energy: 0, + EnergyPrice: big.NewInt(0), + Value: nil, + Data: common.Hex2Bytes("3ae24714"), + }, 0, errors.New("always failing transaction (execution reverted) (revert reason)")}, + + {"PureRevert", gocore.CallMsg{ + From: addr, + To: &contractAddr, + Energy: 0, + EnergyPrice: big.NewInt(0), + Value: nil, + Data: common.Hex2Bytes("593b9361"), + }, 0, errors.New("always failing transaction (execution reverted)")}, + + {"OOG", gocore.CallMsg{ + From: addr, + To: &contractAddr, + Energy: 100000, + EnergyPrice: big.NewInt(0), + Value: nil, + Data: common.Hex2Bytes("6c5fcd82"), + }, 0, errors.New("energy required exceeds allowance (100000)")}, + + {"Assert", gocore.CallMsg{ + From: addr, + To: &contractAddr, + Energy: 100000, + EnergyPrice: big.NewInt(0), + Value: nil, + Data: common.Hex2Bytes("a842ede3"), + }, 0, errors.New("always failing transaction (execution reverted) (0x4b1f2ce30000000000000000000000000000000000000000000000000000000000000001)")}, + + {"Valid", gocore.CallMsg{ + From: addr, + To: &contractAddr, + Energy: 100000, + EnergyPrice: big.NewInt(0), + Value: nil, + Data: common.Hex2Bytes("96be6e03"), + }, 21252, nil}, + } + for _, c := range cases { + got, err := sim.EstimateEnergy(context.Background(), c.message) + if c.expectError != nil { + if err == nil { + t.Fatalf("Expect error, got nil") + } + if c.expectError.Error() != err.Error() { + t.Fatalf("Expect error, want %v, got %v", c.expectError, err) + } + continue + } + if got != c.expect { + t.Fatalf("Energy estimation mismatch, want %d, got %d", c.expect, got) + } } } diff --git a/cmd/gocore/retestxcb.go b/cmd/gocore/retestxcb.go index 63860542d..bfe56b616 100644 --- a/cmd/gocore/retestxcb.go +++ b/cmd/gocore/retestxcb.go @@ -592,7 +592,7 @@ func (api *RetestxcbAPI) AccountRange(ctx context.Context, context := core.NewCVMContext(msg, block.Header(), api.blockchain, nil) // Not yet the searched for transaction, execute on top of the current state vmenv := vm.NewCVM(context, statedb, api.blockchain.Config(), vm.Config{}) - if _, _, _, err := core.ApplyMessage(vmenv, msg, new(core.EnergyPool).AddEnergy(tx.Energy())); err != nil { + if _, err := core.ApplyMessage(vmenv, msg, new(core.EnergyPool).AddEnergy(tx.Energy())); err != nil { return AccountRangeResult{}, fmt.Errorf("transaction %#x failed: %v", tx.Hash(), err) } // Ensure any modifications are committed to the state @@ -701,7 +701,7 @@ func (api *RetestxcbAPI) StorageRangeAt(ctx context.Context, context := core.NewCVMContext(msg, block.Header(), api.blockchain, nil) // Not yet the searched for transaction, execute on top of the current state vmenv := vm.NewCVM(context, statedb, api.blockchain.Config(), vm.Config{}) - if _, _, _, err := core.ApplyMessage(vmenv, msg, new(core.EnergyPool).AddEnergy(tx.Energy())); err != nil { + if _, err := core.ApplyMessage(vmenv, msg, new(core.EnergyPool).AddEnergy(tx.Energy())); err != nil { return StorageRangeResult{}, fmt.Errorf("transaction %#x failed: %v", tx.Hash(), err) } // Ensure any modifications are committed to the state diff --git a/core/error.go b/core/error.go index 4b3d665ec..daf4caa4e 100644 --- a/core/error.go +++ b/core/error.go @@ -22,17 +22,45 @@ var ( // ErrKnownBlock is returned when a block to import is already known locally. ErrKnownBlock = errors.New("block already known") - // ErrEnergyLimitReached is returned by the energy pool if the amount of energy required - // by a transaction is higher than what's left in the block. - ErrEnergyLimitReached = errors.New("energy limit reached") - // ErrBlacklistedHash is returned if a block to import is on the blacklist. ErrBlacklistedHash = errors.New("blacklisted hash") + // ErrNoGenesis is returned when there is no Genesis Block. + ErrNoGenesis = errors.New("genesis not found in chain") +) + +// List of cvm-call-message pre-checking errors. All state transtion messages will +// be pre-checked before execution. If any invalidation detected, the corresponding +// error should be returned which is defined here. +// +// - If the pre-checking happens in the miner, then the transaction won't be packed. +// - If the pre-checking happens in the block processing procedure, then a "BAD BLOCk" +// error should be emitted. +var ( + // ErrNonceTooLow is returned if the nonce of a transaction is lower than the + // one present in the local chain. + ErrNonceTooLow = errors.New("nonce too low") + // ErrNonceTooHigh is returned if the nonce of a transaction is higher than the // next one expected based on the local chain. ErrNonceTooHigh = errors.New("nonce too high") - // ErrNoGenesis is returned when there is no Genesis Block. - ErrNoGenesis = errors.New("genesis not found in chain") + // ErrEnergyLimitReached is returned by the energy pool if the amount of energy required + // by a transaction is higher than what's left in the block. + ErrEnergyLimitReached = errors.New("energy limit reached") + + // ErrInsufficientFundsForTransfer is returned if the transaction sender doesn't + // have enough funds for transfer(topmost call only). + ErrInsufficientFundsForTransfer = errors.New("insufficient funds for transfer") + + // ErrInsufficientFunds is returned if the total cost of executing a transaction + // is higher than the balance of the user's account. + ErrInsufficientFunds = errors.New("insufficient funds for energy * price + value") + + // ErrEnergyUintOverflow is returned when calculating energy usage. + ErrEnergyUintOverflow = errors.New("energy uint64 overflow") + + // ErrIntrinsicEnergy is returned if the transaction is specified to use less energy + // than required to start the invocation. + ErrIntrinsicEnergy = errors.New("intrinsic energy too low") ) diff --git a/core/state/snapshot/difflayer_test.go b/core/state/snapshot/difflayer_test.go index 9038e3402..9f942b7ba 100644 --- a/core/state/snapshot/difflayer_test.go +++ b/core/state/snapshot/difflayer_test.go @@ -131,7 +131,7 @@ func TestMergeDelete(t *testing.T) { flipDrops := func() map[common.Hash]struct{} { return map[common.Hash]struct{}{ - h2: struct{}{}, + h2: {}, } } flipAccs := func() map[common.Hash][]byte { @@ -141,7 +141,7 @@ func TestMergeDelete(t *testing.T) { } flopDrops := func() map[common.Hash]struct{} { return map[common.Hash]struct{}{ - h1: struct{}{}, + h1: {}, } } flopAccs := func() map[common.Hash][]byte { diff --git a/core/state/snapshot/disklayer_test.go b/core/state/snapshot/disklayer_test.go index dfa4d33f0..854579187 100644 --- a/core/state/snapshot/disklayer_test.go +++ b/core/state/snapshot/disklayer_test.go @@ -121,10 +121,10 @@ func TestDiskMerge(t *testing.T) { // Modify or delete some accounts, flatten everything onto disk if err := snaps.Update(diffRoot, baseRoot, map[common.Hash]struct{}{ - accDelNoCache: struct{}{}, - accDelCache: struct{}{}, - conNukeNoCache: struct{}{}, - conNukeCache: struct{}{}, + accDelNoCache: {}, + accDelCache: {}, + conNukeNoCache: {}, + conNukeCache: {}, }, map[common.Hash][]byte{ accModNoCache: reverse(accModNoCache[:]), accModCache: reverse(accModCache[:]), @@ -344,10 +344,10 @@ func TestDiskPartialMerge(t *testing.T) { // Modify or delete some accounts, flatten everything onto disk if err := snaps.Update(diffRoot, baseRoot, map[common.Hash]struct{}{ - accDelNoCache: struct{}{}, - accDelCache: struct{}{}, - conNukeNoCache: struct{}{}, - conNukeCache: struct{}{}, + accDelNoCache: {}, + accDelCache: {}, + conNukeNoCache: {}, + conNukeCache: {}, }, map[common.Hash][]byte{ accModNoCache: reverse(accModNoCache[:]), accModCache: reverse(accModCache[:]), diff --git a/core/state/snapshot/iterator_test.go b/core/state/snapshot/iterator_test.go index 53d56b117..13ba67743 100644 --- a/core/state/snapshot/iterator_test.go +++ b/core/state/snapshot/iterator_test.go @@ -402,7 +402,7 @@ func TestIteratorDeletions(t *testing.T) { deleted := common.HexToHash("0x22") destructed := map[common.Hash]struct{}{ - deleted: struct{}{}, + deleted: {}, } snaps.Update(common.HexToHash("0x03"), common.HexToHash("0x02"), destructed, randomAccountSet("0x11", "0x33"), nil) diff --git a/core/state_prefetcher.go b/core/state_prefetcher.go index d9b71507b..ab7cf569f 100644 --- a/core/state_prefetcher.go +++ b/core/state_prefetcher.go @@ -81,6 +81,6 @@ func precacheTransaction(config *params.ChainConfig, bc ChainContext, author *co context := NewCVMContext(msg, header, bc, author) vm := vm.NewCVM(context, statedb, config, cfg) - _, _, _, err = ApplyMessage(vm, msg, energypool) + _, err = ApplyMessage(vm, msg, energypool) return err } diff --git a/core/state_processor.go b/core/state_processor.go index 43c7477a7..eb6144233 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -91,20 +91,20 @@ func ApplyTransaction(config *params.ChainConfig, bc ChainContext, author *commo // about the transaction and calling mechanisms. vmenv := vm.NewCVM(context, statedb, config, cfg) // Apply the transaction to the current state (included in the env) - _, energy, failed, err := ApplyMessage(vmenv, msg, gp) + result, err := ApplyMessage(vmenv, msg, gp) if err != nil { return nil, err } // Update the state with pending changes var root []byte statedb.Finalise(true) - *usedEnergy += energy + *usedEnergy += result.UsedEnergy // Create a new receipt for the transaction, storing the intermediate root and energy used by the tx // based on the cip phase, we're passing whether the root touch-delete accounts. - receipt := types.NewReceipt(root, failed, *usedEnergy) + receipt := types.NewReceipt(root, result.Failed(), *usedEnergy) receipt.TxHash = tx.Hash() - receipt.EnergyUsed = energy + receipt.EnergyUsed = result.UsedEnergy // if the transaction created a contract, store the creation address in the receipt. if msg.To() == nil { receipt.ContractAddress = crypto.CreateAddress(vmenv.Context.Origin, tx.Nonce()) diff --git a/core/state_transition.go b/core/state_transition.go index 0848e9e7f..28305657c 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -17,20 +17,14 @@ package core import ( - "errors" "math" "math/big" "github.com/core-coin/go-core/common" "github.com/core-coin/go-core/core/vm" - "github.com/core-coin/go-core/log" "github.com/core-coin/go-core/params" ) -var ( - errInsufficientBalanceForEnergy = errors.New("insufficient balance to pay for energy") -) - /* The State Transitioning Model @@ -74,6 +68,41 @@ type Message interface { Data() []byte } +// ExecutionResult includes all output after executing given evm +// message no matter the execution itself is successful or not. +type ExecutionResult struct { + UsedEnergy uint64 // Total used energy but include the refunded energy + Err error // Any error encountered during the execution(listed in core/vm/errors.go) + ReturnData []byte // Returned data from evm(function result or data supplied with revert opcode) +} + +// Unwrap returns the internal evm error which allows us for further +// analysis outside. +func (result *ExecutionResult) Unwrap() error { + return result.Err +} + +// Failed returns the indicator whether the execution is successful or not +func (result *ExecutionResult) Failed() bool { return result.Err != nil } + +// Return is a helper function to help caller distinguish between revert reason +// and function return. Return returns the data after execution if no error occurs. +func (result *ExecutionResult) Return() []byte { + if result.Err != nil { + return nil + } + return common.CopyBytes(result.ReturnData) +} + +// Revert returns the concrete revert reason if the execution is aborted by `REVERT` +// opcode. Note the reason can be nil if no data supplied with revert opcode. +func (result *ExecutionResult) Revert() []byte { + if result.Err != vm.ErrExecutionReverted { + return nil + } + return common.CopyBytes(result.ReturnData) +} + // IntrinsicEnergy computes the 'intrinsic energy' for a message with the given data. func IntrinsicEnergy(data []byte, contractCreation bool) (uint64, error) { // Set the starting energy for the raw transaction @@ -95,13 +124,13 @@ func IntrinsicEnergy(data []byte, contractCreation bool) (uint64, error) { // Make sure we don't exceed uint64 for all data combinations nonZeroEnergy := params.TxDataNonZeroEnergy if (math.MaxUint64-energy)/nonZeroEnergy < nz { - return 0, vm.ErrOutOfEnergy + return 0, ErrEnergyUintOverflow } energy += nz * nonZeroEnergy z := uint64(len(data)) - nz if (math.MaxUint64-energy)/params.TxDataZeroEnergy < z { - return 0, vm.ErrOutOfEnergy + return 0, ErrEnergyUintOverflow } energy += z * params.TxDataZeroEnergy } @@ -128,7 +157,7 @@ func NewStateTransition(cvm *vm.CVM, msg Message, gp *EnergyPool) *StateTransiti // the energy used (which includes energy refunds) and an error if it failed. An error always // indicates a core error meaning that the message would always fail for that particular // state and would never be accepted within a block. -func ApplyMessage(cvm *vm.CVM, msg Message, gp *EnergyPool) ([]byte, uint64, bool, error) { +func ApplyMessage(cvm *vm.CVM, msg Message, gp *EnergyPool) (*ExecutionResult, error) { return NewStateTransition(cvm, msg, gp).TransitionDb() } @@ -140,19 +169,10 @@ func (st *StateTransition) to() common.Address { return *st.msg.To() } -func (st *StateTransition) useEnergy(amount uint64) error { - if st.energy < amount { - return vm.ErrOutOfEnergy - } - st.energy -= amount - - return nil -} - func (st *StateTransition) buyEnergy() error { mgval := new(big.Int).Mul(new(big.Int).SetUint64(st.msg.Energy()), st.energyPrice) if st.state.GetBalance(st.msg.From()).Cmp(mgval) < 0 { - return errInsufficientBalanceForEnergy + return ErrInsufficientFunds } if err := st.gp.SubEnergy(st.msg.Energy()); err != nil { return err @@ -178,52 +198,70 @@ func (st *StateTransition) preCheck() error { } // TransitionDb will transition the state by applying the current message and -// returning the result including the used energy. It returns an error if failed. -// An error indicates a consensus issue. -func (st *StateTransition) TransitionDb() (ret []byte, usedEnergy uint64, failed bool, err error) { - if err = st.preCheck(); err != nil { - return +// returning the evm execution result with following fields. +// +// - used energy: +// total energy used (including energy being refunded) +// - returndata: +// the returned data from cvm +// - concrete execution error: +// various **CVM** error which aborts the execution, +// e.g. ErrOutOfEnergy, ErrExecutionReverted +// +// However if any consensus issue encountered, return the error directly with +// nil evm execution result. +func (st *StateTransition) TransitionDb() (*ExecutionResult, error) { + // First check this message satisfies all consensus rules before + // applying the message. The rules include these clauses + // + // 1. the nonce of the message caller is correct + // 2. caller has enough balance to cover transaction fee(energylimit * energyprice) + // 3. the amount of energy required is available in the block + // 4. the purchased energy is enough to cover intrinsic usage + // 5. there is no overflow when calculating intrinsic energy + // 6. caller has enough balance to cover asset transfer for **topmost** call + + // Check clauses 1-3, buy energy if everything is correct + if err := st.preCheck(); err != nil { + return nil, err } msg := st.msg sender := vm.AccountRef(msg.From()) contractCreation := msg.To() == nil - // Pay intrinsic energy + // Check clauses 4-5, subtract intrinsic energy if everything is correct energy, err := IntrinsicEnergy(st.data, contractCreation) if err != nil { - return nil, 0, false, err + return nil, err } - if err = st.useEnergy(energy); err != nil { - return nil, 0, false, err + if st.energy < energy { + return nil, ErrIntrinsicEnergy } + st.energy -= energy + // Check clause 6 + if msg.Value().Sign() > 0 && !st.cvm.CanTransfer(st.state, msg.From(), msg.Value()) { + return nil, ErrInsufficientFundsForTransfer + } var ( - cvm = st.cvm - // vm errors do not effect consensus and are therefor - // not assigned to err, except for insufficient balance - // error. - vmerr error + ret []byte + vmerr error // vm errors do not effect consensus and are therefore not assigned to err ) if contractCreation { - ret, _, st.energy, vmerr = cvm.Create(sender, st.data, st.energy, st.value) + ret, _, st.energy, vmerr = st.cvm.Create(sender, st.data, st.energy, st.value) } else { // Increment the nonce for the next transaction st.state.SetNonce(msg.From(), st.state.GetNonce(sender.Address())+1) - ret, st.energy, vmerr = cvm.Call(sender, st.to(), st.data, st.energy, st.value) - } - if vmerr != nil { - log.Debug("VM returned with error", "err", vmerr) - // The only possible consensus-error would be if there wasn't - // sufficient balance to make the transfer happen. The first - // balance transfer may never fail. - if vmerr == vm.ErrInsufficientBalance { - return nil, 0, false, vmerr - } + ret, st.energy, vmerr = st.cvm.Call(sender, st.to(), st.data, st.energy, st.value) } st.refundEnergy() st.state.AddBalance(st.cvm.Coinbase, new(big.Int).Mul(new(big.Int).SetUint64(st.energyUsed()), st.energyPrice)) - return ret, st.energyUsed(), vmerr != nil, err + return &ExecutionResult{ + UsedEnergy: st.energyUsed(), + Err: vmerr, + ReturnData: ret, + }, nil } func (st *StateTransition) refundEnergy() { diff --git a/core/tx_pool.go b/core/tx_pool.go index 976cd7274..507c564d9 100644 --- a/core/tx_pool.go +++ b/core/tx_pool.go @@ -59,10 +59,6 @@ var ( // ErrInvalidSender is returned if the transaction contains an invalid signature. ErrInvalidSender = errors.New("invalid sender") - // ErrNonceTooLow is returned if the nonce of a transaction is lower than the - // one present in the local chain. - ErrNonceTooLow = errors.New("nonce too low") - // ErrUnderpriced is returned if a transaction's energy price is below the minimum // configured for the transaction pool. ErrUnderpriced = errors.New("transaction underpriced") @@ -71,14 +67,6 @@ var ( // with a different one without the required price bump. ErrReplaceUnderpriced = errors.New("replacement transaction underpriced") - // ErrInsufficientFunds is returned if the total cost of executing a transaction - // is higher than the balance of the user's account. - ErrInsufficientFunds = errors.New("insufficient funds for energy * price + value") - - // ErrIntrinsicEnergy is returned if the transaction is specified to use less energy - // than required to start the invocation. - ErrIntrinsicEnergy = errors.New("intrinsic energy too low") - // ErrEnergyLimit is returned if a transaction's requested energy limit exceeds the // maximum allowance of the current block. ErrEnergyLimit = errors.New("exceeds block energy limit") diff --git a/core/vm/cvm.go b/core/vm/cvm.go index 0040d766b..1ed6aba41 100644 --- a/core/vm/cvm.go +++ b/core/vm/cvm.go @@ -17,6 +17,7 @@ package vm import ( + "errors" "math/big" "sync/atomic" "time" @@ -61,7 +62,7 @@ func run(cvm *CVM, contract *Contract, input []byte, readOnly bool) ([]byte, err return interpreter.Run(contract, input, readOnly) } } - return nil, ErrNoCompatibleInterpreter + return nil, errors.New("no compatible interpreter") } // Context provides the CVM with auxiliary information. Once provided @@ -233,7 +234,7 @@ func (cvm *CVM) Call(caller ContractRef, addr common.Address, input []byte, ener // above we revert to the snapshot and consume any energy remaining. if err != nil { cvm.StateDB.RevertToSnapshot(snapshot) - if err != errExecutionReverted { + if err != ErrExecutionReverted { contract.UseEnergy(contract.Energy) } } @@ -257,7 +258,10 @@ func (cvm *CVM) CallCode(caller ContractRef, addr common.Address, input []byte, return nil, energy, ErrDepth } // Fail if we're trying to transfer more than the available balance - if !cvm.CanTransfer(cvm.StateDB, caller.Address(), value) { + // Note although it's noop to transfer X core to caller itself. But + // if caller doesn't have enough balance, it would be an error to allow + // over-charging itself. So the check here is necessary. + if !cvm.Context.CanTransfer(cvm.StateDB, caller.Address(), value) { return nil, energy, ErrInsufficientBalance } @@ -273,7 +277,7 @@ func (cvm *CVM) CallCode(caller ContractRef, addr common.Address, input []byte, ret, err = run(cvm, contract, input, false) if err != nil { cvm.StateDB.RevertToSnapshot(snapshot) - if err != errExecutionReverted { + if err != ErrExecutionReverted { contract.UseEnergy(contract.Energy) } } @@ -306,7 +310,7 @@ func (cvm *CVM) DelegateCall(caller ContractRef, addr common.Address, input []by ret, err = run(cvm, contract, input, false) if err != nil { cvm.StateDB.RevertToSnapshot(snapshot) - if err != errExecutionReverted { + if err != ErrExecutionReverted { contract.UseEnergy(contract.Energy) } } @@ -345,7 +349,7 @@ func (cvm *CVM) StaticCall(caller ContractRef, addr common.Address, input []byte ret, err = run(cvm, contract, input, true) if err != nil { cvm.StateDB.RevertToSnapshot(snapshot) - if err != errExecutionReverted { + if err != ErrExecutionReverted { contract.UseEnergy(contract.Energy) } } @@ -423,13 +427,13 @@ func (cvm *CVM) create(caller ContractRef, codeAndHash *codeAndHash, energy uint // above we revert to the snapshot and consume any energy remaining. if maxCodeSizeExceeded || (err != nil && err != ErrCodeStoreOutOfEnergy) { cvm.StateDB.RevertToSnapshot(snapshot) - if err != errExecutionReverted { + if err != ErrExecutionReverted { contract.UseEnergy(contract.Energy) } } // Assign err if contract code size exceeds the max while the err is still empty. if maxCodeSizeExceeded && err == nil { - err = errMaxCodeSizeExceeded + err = ErrMaxCodeSizeExceeded } if cvm.vmConfig.Debug && cvm.depth == 0 { cvm.vmConfig.Tracer.CaptureEnd(ret, energy-contract.Energy, time.Since(start), err) diff --git a/core/vm/energy.go b/core/vm/energy.go index 7a709d810..d52b49aee 100644 --- a/core/vm/energy.go +++ b/core/vm/energy.go @@ -43,7 +43,7 @@ func callEnergy(availableEnergy, base uint64, callCost *uint256.Int) (uint64, er return energy, nil } if !callCost.IsUint64() { - return 0, errEnergyUintOverflow + return 0, ErrEnergyUintOverflow } return callCost.Uint64(), nil diff --git a/core/vm/energy_table.go b/core/vm/energy_table.go index 7e33d90c1..4b7a7c274 100644 --- a/core/vm/energy_table.go +++ b/core/vm/energy_table.go @@ -36,7 +36,7 @@ func memoryEnergyCost(mem *Memory, newMemSize uint64) (uint64, error) { // overflow. The constant 0x1FFFFFFFE0 is the highest number that can be used // without overflowing the energy calculation. if newMemSize > 0x1FFFFFFFE0 { - return 0, errEnergyUintOverflow + return 0, ErrEnergyUintOverflow } newMemSizeWords := toWordSize(newMemSize) newMemSize = newMemSizeWords * 32 @@ -72,15 +72,15 @@ func memoryCopierEnergy(stackpos int) energyFunc { // And energy for copying data, charged per word at param.CopyEnergy words, overflow := stack.Back(stackpos).Uint64WithOverflow() if overflow { - return 0, errEnergyUintOverflow + return 0, ErrEnergyUintOverflow } if words, overflow = math.SafeMul(toWordSize(words), params.CopyEnergy); overflow { - return 0, errEnergyUintOverflow + return 0, ErrEnergyUintOverflow } if energy, overflow = math.SafeAdd(energy, words); overflow { - return 0, errEnergyUintOverflow + return 0, ErrEnergyUintOverflow } return energy, nil } @@ -152,7 +152,7 @@ func makeEnergyLog(n uint64) energyFunc { return func(cvm *CVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { requestedSize, overflow := stack.Back(1).Uint64WithOverflow() if overflow { - return 0, errEnergyUintOverflow + return 0, ErrEnergyUintOverflow } energy, err := memoryEnergyCost(mem, memorySize) @@ -161,18 +161,18 @@ func makeEnergyLog(n uint64) energyFunc { } if energy, overflow = math.SafeAdd(energy, params.LogEnergy); overflow { - return 0, errEnergyUintOverflow + return 0, ErrEnergyUintOverflow } if energy, overflow = math.SafeAdd(energy, n*params.LogTopicEnergy); overflow { - return 0, errEnergyUintOverflow + return 0, ErrEnergyUintOverflow } var memorySizeEnergy uint64 if memorySizeEnergy, overflow = math.SafeMul(requestedSize, params.LogDataEnergy); overflow { - return 0, errEnergyUintOverflow + return 0, ErrEnergyUintOverflow } if energy, overflow = math.SafeAdd(energy, memorySizeEnergy); overflow { - return 0, errEnergyUintOverflow + return 0, ErrEnergyUintOverflow } return energy, nil } @@ -185,13 +185,13 @@ func energySha3(cvm *CVM, contract *Contract, stack *Stack, mem *Memory, memoryS } wordEnergy, overflow := stack.Back(1).Uint64WithOverflow() if overflow { - return 0, errEnergyUintOverflow + return 0, ErrEnergyUintOverflow } if wordEnergy, overflow = math.SafeMul(toWordSize(wordEnergy), params.Sha3WordEnergy); overflow { - return 0, errEnergyUintOverflow + return 0, ErrEnergyUintOverflow } if energy, overflow = math.SafeAdd(energy, wordEnergy); overflow { - return 0, errEnergyUintOverflow + return 0, ErrEnergyUintOverflow } return energy, nil } @@ -219,13 +219,13 @@ func energyCreate2(cvm *CVM, contract *Contract, stack *Stack, mem *Memory, memo } wordEnergy, overflow := stack.Back(2).Uint64WithOverflow() if overflow { - return 0, errEnergyUintOverflow + return 0, ErrEnergyUintOverflow } if wordEnergy, overflow = math.SafeMul(toWordSize(wordEnergy), params.Sha3WordEnergy); overflow { - return 0, errEnergyUintOverflow + return 0, ErrEnergyUintOverflow } if energy, overflow = math.SafeAdd(energy, wordEnergy); overflow { - return 0, errEnergyUintOverflow + return 0, ErrEnergyUintOverflow } return energy, nil } @@ -238,7 +238,7 @@ func energyExp(cvm *CVM, contract *Contract, stack *Stack, mem *Memory, memorySi overflow bool ) if energy, overflow = math.SafeAdd(energy, params.ExpEnergy); overflow { - return 0, errEnergyUintOverflow + return 0, ErrEnergyUintOverflow } return energy, nil } @@ -261,7 +261,7 @@ func energyCall(cvm *CVM, contract *Contract, stack *Stack, mem *Memory, memoryS } var overflow bool if energy, overflow = math.SafeAdd(energy, memoryEnergy); overflow { - return 0, errEnergyUintOverflow + return 0, ErrEnergyUintOverflow } cvm.callEnergyTemp, err = callEnergy(contract.Energy, energy, stack.Back(0)) @@ -269,7 +269,7 @@ func energyCall(cvm *CVM, contract *Contract, stack *Stack, mem *Memory, memoryS return 0, err } if energy, overflow = math.SafeAdd(energy, cvm.callEnergyTemp); overflow { - return 0, errEnergyUintOverflow + return 0, ErrEnergyUintOverflow } return energy, nil } @@ -287,14 +287,14 @@ func energyCallCode(cvm *CVM, contract *Contract, stack *Stack, mem *Memory, mem energy += params.CallValueTransferEnergy } if energy, overflow = math.SafeAdd(energy, memoryEnergy); overflow { - return 0, errEnergyUintOverflow + return 0, ErrEnergyUintOverflow } cvm.callEnergyTemp, err = callEnergy(contract.Energy, energy, stack.Back(0)) if err != nil { return 0, err } if energy, overflow = math.SafeAdd(energy, cvm.callEnergyTemp); overflow { - return 0, errEnergyUintOverflow + return 0, ErrEnergyUintOverflow } return energy, nil } @@ -310,7 +310,7 @@ func energyDelegateCall(cvm *CVM, contract *Contract, stack *Stack, mem *Memory, } var overflow bool if energy, overflow = math.SafeAdd(energy, cvm.callEnergyTemp); overflow { - return 0, errEnergyUintOverflow + return 0, ErrEnergyUintOverflow } return energy, nil } @@ -326,7 +326,7 @@ func energyStaticCall(cvm *CVM, contract *Contract, stack *Stack, mem *Memory, m } var overflow bool if energy, overflow = math.SafeAdd(energy, cvm.callEnergyTemp); overflow { - return 0, errEnergyUintOverflow + return 0, ErrEnergyUintOverflow } return energy, nil } diff --git a/core/vm/energy_table_test.go b/core/vm/energy_table_test.go index c36b7c805..9b8b4197b 100644 --- a/core/vm/energy_table_test.go +++ b/core/vm/energy_table_test.go @@ -39,8 +39,8 @@ func TestMemoryEnergyCost(t *testing.T) { } for i, tt := range tests { v, err := memoryEnergyCost(&Memory{}, tt.size) - if (err == errEnergyUintOverflow) != tt.overflow { - t.Errorf("test %d: overflow mismatch: have %v, want %v", i, err == errEnergyUintOverflow, tt.overflow) + if (err == ErrEnergyUintOverflow) != tt.overflow { + t.Errorf("test %d: overflow mismatch: have %v, want %v", i, err == ErrEnergyUintOverflow, tt.overflow) } if v != tt.cost { t.Errorf("test %d: energy cost mismatch: have %v, want %v", i, v, tt.cost) diff --git a/core/vm/errors.go b/core/vm/errors.go index 0b48584f9..7ccbe7d59 100644 --- a/core/vm/errors.go +++ b/core/vm/errors.go @@ -16,15 +16,51 @@ package vm -import "errors" +import ( + "errors" + "fmt" +) -// List execution errors +// List cvm execution errors var ( - ErrOutOfEnergy = errors.New("out of energy") - ErrCodeStoreOutOfEnergy = errors.New("contract creation code storage out of energy") + ErrOutOfEnergy = errors.New("out of energy") + ErrCodeStoreOutOfEnergy = errors.New("contract creation code storage out of energy") ErrDepth = errors.New("max call depth exceeded") - ErrTraceLimitReached = errors.New("the number of logs reached the specified limit") ErrInsufficientBalance = errors.New("insufficient balance for transfer") ErrContractAddressCollision = errors.New("contract address collision") - ErrNoCompatibleInterpreter = errors.New("no compatible interpreter") + ErrExecutionReverted = errors.New("execution reverted") + ErrMaxCodeSizeExceeded = errors.New("max code size exceeded") + ErrInvalidJump = errors.New("invalid jump destination") + ErrWriteProtection = errors.New("write protection") + ErrReturnDataOutOfBounds = errors.New("return data out of bounds") + ErrEnergyUintOverflow = errors.New("energy uint64 overflow") ) + +// ErrStackUnderflow wraps an cvm error when the items on the stack less +// than the minimal requirement. +type ErrStackUnderflow struct { + stackLen int + required int +} + +func (e *ErrStackUnderflow) Error() string { + return fmt.Sprintf("stack underflow (%d <=> %d)", e.stackLen, e.required) +} + +// ErrStackOverflow wraps an cvm error when the items on the stack exceeds +// the maximum allowance. +type ErrStackOverflow struct { + stackLen int + limit int +} + +func (e *ErrStackOverflow) Error() string { + return fmt.Sprintf("stack limit reached %d (%d)", e.stackLen, e.limit) +} + +// ErrInvalidOpCode wraps an cvm error when an invalid opcode is encountered. +type ErrInvalidOpCode struct { + opcode OpCode +} + +func (e *ErrInvalidOpCode) Error() string { return fmt.Sprintf("invalid opcode: %s", e.opcode) } diff --git a/core/vm/instructions.go b/core/vm/instructions.go index 9dce1deee..9ad393e7a 100644 --- a/core/vm/instructions.go +++ b/core/vm/instructions.go @@ -17,7 +17,6 @@ package vm import ( - "errors" "github.com/core-coin/go-core/common" "github.com/core-coin/go-core/core/types" "github.com/core-coin/go-core/params" @@ -25,14 +24,6 @@ import ( "golang.org/x/crypto/sha3" ) -var ( - errWriteProtection = errors.New("cvm: write protection") - errReturnDataOutOfBounds = errors.New("cvm: return data out of bounds") - errExecutionReverted = errors.New("cvm: execution reverted") - errMaxCodeSizeExceeded = errors.New("cvm: max code size exceeded") - errInvalidJump = errors.New("cvm: invalid jump destination") -) - func opAdd(pc *uint64, interpreter *CVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { x, y := stack.pop(), stack.peek() y.Add(&x, y) @@ -340,14 +331,14 @@ func opReturnDataCopy(pc *uint64, interpreter *CVMInterpreter, contract *Contrac offset64, overflow := dataOffset.Uint64WithOverflow() if overflow { - return nil, errReturnDataOutOfBounds + return nil, ErrReturnDataOutOfBounds } // we can reuse dataOffset now (aliasing it for clarity) var end = dataOffset end.Add(&dataOffset, &length) end64, overflow := end.Uint64WithOverflow() if overflow || uint64(len(interpreter.returnData)) < end64 { - return nil, errReturnDataOutOfBounds + return nil, ErrReturnDataOutOfBounds } memory.Set(memOffset.Uint64(), length.Uint64(), interpreter.returnData[offset64:end64]) return nil, nil @@ -539,7 +530,7 @@ func opSstore(pc *uint64, interpreter *CVMInterpreter, contract *Contract, memor func opJump(pc *uint64, interpreter *CVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { pos := stack.pop() if !contract.validJumpdest(&pos) { - return nil, errInvalidJump + return nil, ErrInvalidJump } *pc = pos.Uint64() @@ -550,7 +541,7 @@ func opJumpi(pc *uint64, interpreter *CVMInterpreter, contract *Contract, memory pos, cond := stack.pop(), stack.pop() if !cond.IsZero() { if !contract.validJumpdest(&pos) { - return nil, errInvalidJump + return nil, ErrInvalidJump } *pc = pos.Uint64() } else { @@ -605,7 +596,7 @@ func opCreate(pc *uint64, interpreter *CVMInterpreter, contract *Contract, memor stack.push(&stackvalue) contract.Energy += returnEnergy - if suberr == errExecutionReverted { + if suberr == ErrExecutionReverted { return res, nil } return nil, nil @@ -636,7 +627,7 @@ func opCreate2(pc *uint64, interpreter *CVMInterpreter, contract *Contract, memo stack.push(&stackvalue) contract.Energy += returnEnergy - if suberr == errExecutionReverted { + if suberr == ErrExecutionReverted { return res, nil } return nil, nil @@ -663,7 +654,7 @@ func opCall(pc *uint64, interpreter *CVMInterpreter, contract *Contract, memory temp.SetOne() } stack.push(&temp) - if err == nil || err == errExecutionReverted { + if err == nil || err == ErrExecutionReverted { memory.Set(retOffset.Uint64(), retSize.Uint64(), ret) } contract.Energy += returnEnergy @@ -691,7 +682,7 @@ func opCallCode(pc *uint64, interpreter *CVMInterpreter, contract *Contract, mem temp.SetOne() } stack.push(&temp) - if err == nil || err == errExecutionReverted { + if err == nil || err == ErrExecutionReverted { memory.Set(retOffset.Uint64(), retSize.Uint64(), ret) } contract.Energy += returnEnergy @@ -716,7 +707,7 @@ func opDelegateCall(pc *uint64, interpreter *CVMInterpreter, contract *Contract, temp.SetOne() } stack.push(&temp) - if err == nil || err == errExecutionReverted { + if err == nil || err == ErrExecutionReverted { memory.Set(retOffset.Uint64(), retSize.Uint64(), ret) } contract.Energy += returnEnergy @@ -741,7 +732,7 @@ func opStaticCall(pc *uint64, interpreter *CVMInterpreter, contract *Contract, m temp.SetOne() } stack.push(&temp) - if err == nil || err == errExecutionReverted { + if err == nil || err == ErrExecutionReverted { memory.Set(retOffset.Uint64(), retSize.Uint64(), ret) } contract.Energy += returnEnergy diff --git a/core/vm/interpreter.go b/core/vm/interpreter.go index af66a0088..3a61854fd 100644 --- a/core/vm/interpreter.go +++ b/core/vm/interpreter.go @@ -17,7 +17,6 @@ package vm import ( - "fmt" "hash" "sync/atomic" @@ -107,7 +106,7 @@ func NewCVMInterpreter(cvm *CVM, cfg Config) *CVMInterpreter { // // It's important to note that any errors returned by the interpreter should be // considered a revert-and-consume-all-energy operation except for -// errExecutionReverted which means revert-and-keep-energy-left. +// ErrExecutionReverted which means revert-and-keep-energy-left. func (in *CVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (ret []byte, err error) { // Increment the call depth which is restricted to 1024 in.cvm.depth++ @@ -172,13 +171,13 @@ func (in *CVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) ( op = contract.GetOp(pc) operation := in.cfg.JumpTable[op] if operation == nil { - return nil, fmt.Errorf("invalid opcode 0x%x", int(op)) + return nil, &ErrInvalidOpCode{opcode: op} } // Validate stack if sLen := stack.len(); sLen < operation.minStack { - return nil, fmt.Errorf("stack underflow (%d <=> %d)", sLen, operation.minStack) + return nil, &ErrStackUnderflow{stackLen: sLen, required: operation.minStack} } else if sLen > operation.maxStack { - return nil, fmt.Errorf("stack limit reached %d (%d)", sLen, operation.maxStack) + return nil, &ErrStackOverflow{stackLen: sLen, limit: operation.maxStack} } // If the operation is valid, enforce and write restrictions if in.readOnly { @@ -188,7 +187,7 @@ func (in *CVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) ( // account to the others means the state is modified and should also // return with an error. if operation.writes || (op == CALL && stack.Back(2).Sign() != 0) { - return nil, errWriteProtection + return nil, ErrWriteProtection } } // Static portion of energy @@ -205,12 +204,12 @@ func (in *CVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) ( if operation.memorySize != nil { memSize, overflow := operation.memorySize(stack) if overflow { - return nil, errEnergyUintOverflow + return nil, ErrEnergyUintOverflow } // memory is expanded in words of 32 bytes. Energy // is also calculated in words. if memorySize, overflow = math.SafeMul(toWordSize(memSize), 32); overflow { - return nil, errEnergyUintOverflow + return nil, ErrEnergyUintOverflow } } // Dynamic portion of energy @@ -245,7 +244,7 @@ func (in *CVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) ( case err != nil: return nil, err case operation.reverts: - return res, errExecutionReverted + return res, ErrExecutionReverted case operation.halts: return res, nil case !operation.jumps: diff --git a/core/vm/jump_table.go b/core/vm/jump_table.go index 6ac78029f..1a05e2c98 100644 --- a/core/vm/jump_table.go +++ b/core/vm/jump_table.go @@ -17,8 +17,6 @@ package vm import ( - "errors" - "github.com/core-coin/go-core/params" ) @@ -29,8 +27,6 @@ type ( memorySizeFunc func(*Stack) (size uint64, overflow bool) ) -var errEnergyUintOverflow = errors.New("energy uint64 overflow") - type operation struct { // execute is the operation function execute executionFunc diff --git a/core/vm/logger.go b/core/vm/logger.go index c5bb829eb..9cee61d36 100644 --- a/core/vm/logger.go +++ b/core/vm/logger.go @@ -18,6 +18,7 @@ package vm import ( "encoding/hex" + "errors" "fmt" "io" "math/big" @@ -29,6 +30,8 @@ import ( "github.com/core-coin/go-core/core/types" ) +var errTraceLimitReached = errors.New("the number of logs reached the specified limit") + // Storage represents a contract's storage. type Storage map[common.Hash]common.Hash @@ -140,7 +143,7 @@ func (l *StructLogger) CaptureStart(from common.Address, to common.Address, crea func (l *StructLogger) CaptureState(env *CVM, pc uint64, op OpCode, energy, cost uint64, memory *Memory, stack *Stack, contract *Contract, depth int, err error) error { // check if already accumulated the specified number of logs if l.cfg.Limit != 0 && l.cfg.Limit <= len(l.logs) { - return ErrTraceLimitReached + return errTraceLimitReached } // initialise new changed values storage container for this contract diff --git a/core/vm/opcodes.go b/core/vm/opcodes.go index 976690163..16cdc0f57 100644 --- a/core/vm/opcodes.go +++ b/core/vm/opcodes.go @@ -389,7 +389,7 @@ var opCodeToString = map[OpCode]string{ func (op OpCode) String() string { str := opCodeToString[op] if len(str) == 0 { - return fmt.Sprintf("Missing opcode 0x%x", int(op)) + return fmt.Sprintf("opcode 0x%x not defined", int(op)) } return str diff --git a/go.mod b/go.mod index 03654a4a8..517a31d70 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( github.com/aws/aws-sdk-go v1.25.48 github.com/cespare/cp v0.1.0 github.com/cloudflare/cloudflare-go v0.14.0 - github.com/core-coin/go-goldilocks v1.0.9 + github.com/core-coin/go-goldilocks v1.0.12 github.com/core-coin/go-randomy v0.0.18 github.com/core-coin/uint256 v1.0.0 github.com/davecgh/go-spew v1.1.1 diff --git a/go.sum b/go.sum index 4478fedbe..085211c1d 100644 --- a/go.sum +++ b/go.sum @@ -73,6 +73,8 @@ github.com/cloudflare/cloudflare-go v0.14.0 h1:gFqGlGl/5f9UGXAaKapCGUfaTCgRKKnzu github.com/cloudflare/cloudflare-go v0.14.0/go.mod h1:EnwdgGMaFOruiPZRFSgn+TsQ3hQ7C/YWzIGLeu5c304= github.com/core-coin/go-goldilocks v1.0.9 h1:NEX5+LXeLpj3yJPKQgEffj2ly33rOvANsWDcQR8jBUs= github.com/core-coin/go-goldilocks v1.0.9/go.mod h1:r6mSidt/OMBXorR8jBJYJttsver3m2EBAcSuf+m2Js0= +github.com/core-coin/go-goldilocks v1.0.12 h1:wEoAtrgDGrM09pfyAvrcWHQn9dy+7afDkK9TDRTAccA= +github.com/core-coin/go-goldilocks v1.0.12/go.mod h1:r6mSidt/OMBXorR8jBJYJttsver3m2EBAcSuf+m2Js0= github.com/core-coin/go-randomy v0.0.18 h1:m0IW81uirjpT0fCAnS+5PxPrbI+s/zviFK8hfQbhRcs= github.com/core-coin/go-randomy v0.0.18/go.mod h1:7YzU3Hrss60CzXlzziTZNAQUN6u+eLAHH1cL1JRGLBE= github.com/core-coin/uint256 v1.0.0 h1:AzgINl9YCnYDRJBlqFlWsVb4sr814LcvPAvkPyVBFVo= diff --git a/graphql/graphql.go b/graphql/graphql.go index 81bf58d3b..053d2e8df 100644 --- a/graphql/graphql.go +++ b/graphql/graphql.go @@ -764,16 +764,19 @@ func (b *Block) Call(ctx context.Context, args struct { return nil, err } } - result, energy, failed, err := xcbapi.DoCall(ctx, b.backend, args.Data, *b.numberOrHash, nil, vm.Config{}, 5*time.Second, b.backend.RPCEnergyCap()) + result, err := xcbapi.DoCall(ctx, b.backend, args.Data, *b.numberOrHash, nil, vm.Config{}, 5*time.Second, b.backend.RPCEnergyCap()) + if err != nil { + return nil, err + } status := hexutil.Uint64(1) - if failed { + if result.Failed() { status = 0 } return &CallResult{ - data: hexutil.Bytes(result), - energyUsed: hexutil.Uint64(energy), + data: result.Return(), + energyUsed: hexutil.Uint64(result.UsedEnergy), status: status, - }, err + }, nil } func (b *Block) EstimateEnergy(ctx context.Context, args struct { @@ -830,16 +833,19 @@ func (p *Pending) Call(ctx context.Context, args struct { Data xcbapi.CallArgs }) (*CallResult, error) { pendingBlockNr := rpc.BlockNumberOrHashWithNumber(rpc.PendingBlockNumber) - result, energy, failed, err := xcbapi.DoCall(ctx, p.backend, args.Data, pendingBlockNr, nil, vm.Config{}, 5*time.Second, p.backend.RPCEnergyCap()) + result, err := xcbapi.DoCall(ctx, p.backend, args.Data, pendingBlockNr, nil, vm.Config{}, 5*time.Second, p.backend.RPCEnergyCap()) + if err != nil { + return nil, err + } status := hexutil.Uint64(1) - if failed { + if result.Failed() { status = 0 } return &CallResult{ - data: hexutil.Bytes(result), - energyUsed: hexutil.Uint64(energy), + data: result.Return(), + energyUsed: hexutil.Uint64(result.UsedEnergy), status: status, - }, err + }, nil } func (p *Pending) EstimateEnergy(ctx context.Context, args struct { diff --git a/internal/xcbapi/api.go b/internal/xcbapi/api.go index 490fe2467..7ed49347e 100644 --- a/internal/xcbapi/api.go +++ b/internal/xcbapi/api.go @@ -21,6 +21,7 @@ import ( "context" "errors" "fmt" + "github.com/core-coin/go-core/accounts/abi" "math/big" "strings" "time" @@ -793,12 +794,12 @@ type account struct { StateDiff *map[common.Hash]common.Hash `json:"stateDiff"` } -func DoCall(ctx context.Context, b Backend, args CallArgs, blockNrOrHash rpc.BlockNumberOrHash, overrides map[common.Address]account, vmCfg vm.Config, timeout time.Duration, globalEnergyCap *big.Int) ([]byte, uint64, bool, error) { +func DoCall(ctx context.Context, b Backend, args CallArgs, blockNrOrHash rpc.BlockNumberOrHash, overrides map[common.Address]account, vmCfg vm.Config, timeout time.Duration, globalEnergyCap *big.Int) (*core.ExecutionResult, error) { defer func(start time.Time) { log.Debug("Executing CVM call finished", "runtime", time.Since(start)) }(time.Now()) state, header, err := b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash) if state == nil || err != nil { - return nil, 0, false, err + return nil, err } // Override the fields of specified contracts before execution. @@ -816,7 +817,7 @@ func DoCall(ctx context.Context, b Backend, args CallArgs, blockNrOrHash rpc.Blo state.SetBalance(addr, (*big.Int)(*account.Balance)) } if account.State != nil && account.StateDiff != nil { - return nil, 0, false, fmt.Errorf("account %s has both 'state' and 'stateDiff'", addr.Hex()) + return nil, fmt.Errorf("account %s has both 'state' and 'stateDiff'", addr.Hex()) } // Replace entire state if caller requires. if account.State != nil { @@ -846,7 +847,7 @@ func DoCall(ctx context.Context, b Backend, args CallArgs, blockNrOrHash rpc.Blo msg := args.ToMessage(globalEnergyCap) cvm, vmError, err := b.GetCVM(ctx, msg, state, header) if err != nil { - return nil, 0, false, err + return nil, err } // Wait for the context to be done and cancel the cvm. Even if the // CVM has finished, cancelling may be done (repeatedly) @@ -858,15 +859,15 @@ func DoCall(ctx context.Context, b Backend, args CallArgs, blockNrOrHash rpc.Blo // Setup the energy pool (also for unmetered requests) // and apply the message. gp := new(core.EnergyPool).AddEnergy(math.MaxUint64) - res, energy, failed, err := core.ApplyMessage(cvm, msg, gp) + result, err := core.ApplyMessage(cvm, msg, gp) if err := vmError(); err != nil { - return nil, 0, false, err + return nil, err } // If the timer caused an abort, return an appropriate error message if cvm.Cancelled() { - return nil, 0, false, fmt.Errorf("execution aborted (timeout = %v)", timeout) + return nil, fmt.Errorf("execution aborted (timeout = %v)", timeout) } - return res, energy, failed, err + return result, err } // Call executes the given transaction on the state for the given block number. @@ -880,10 +881,29 @@ func (s *PublicBlockChainAPI) Call(ctx context.Context, args CallArgs, blockNrOr if overrides != nil { accounts = *overrides } - result, _, _, err := DoCall(ctx, s.b, args, blockNrOrHash, accounts, vm.Config{}, 5*time.Second, s.b.RPCEnergyCap()) - return (hexutil.Bytes)(result), err + result, err := DoCall(ctx, s.b, args, blockNrOrHash, accounts, vm.Config{}, 5*time.Second, s.b.RPCEnergyCap()) + if err != nil { + return nil, err + } + return result.Return(), nil } +type estimateEnergyError struct { + error string // Concrete error type if it's failed to estimate energy usage + vmerr error // Additional field, it's non-nil if the given transaction is invalid + revert string // Additional field, it's non-empty if the transaction is reverted and reason is provided +} + +func (e estimateEnergyError) Error() string { + errMsg := e.error + if e.vmerr != nil { + errMsg += fmt.Sprintf(" (%v)", e.vmerr) + } + if e.revert != "" { + errMsg += fmt.Sprintf(" (%s)", e.revert) + } + return errMsg +} func DoEstimateEnergy(ctx context.Context, b Backend, args CallArgs, blockNrOrHash rpc.BlockNumberOrHash, energyCap *big.Int) (hexutil.Uint64, error) { // Binary search the energy requirement, as it may be higher than the amount used var ( @@ -914,20 +934,31 @@ func DoEstimateEnergy(ctx context.Context, b Backend, args CallArgs, blockNrOrHa if args.From == nil { args.From = new(common.Address) } - // Create a helper to check if a energy allowance results in an executable transaction - executable := func(energy uint64) bool { + // Create a helper to check if an energy allowance results in an executable transaction + executable := func(energy uint64) (bool, *core.ExecutionResult, error) { args.Energy = (*hexutil.Uint64)(&energy) - _, _, failed, err := DoCall(ctx, b, args, blockNrOrHash, nil, vm.Config{}, 0, energyCap) - if err != nil || failed { - return false + result, err := DoCall(ctx, b, args, blockNrOrHash, nil, vm.Config{}, 0, energyCap) + if err != nil { + if err == core.ErrIntrinsicEnergy { + return true, nil, nil // Special case, raise energy limit + } + return true, nil, err // Bail out } - return true + return result.Failed(), result, nil } // Execute the binary search and hone in on an executable energy limit for lo+1 < hi { mid := (hi + lo) / 2 - if !executable(mid) { + failed, _, err := executable(mid) + + // If the error is not nil(consensus error), it means the provided message + // call or transaction will never be accepted no matter how much energy it is + // assigened. Return the error directly, don't struggle any more. + if err != nil { + return 0, err + } + if failed { lo = mid } else { hi = mid @@ -935,8 +966,29 @@ func DoEstimateEnergy(ctx context.Context, b Backend, args CallArgs, blockNrOrHa } // Reject the transaction as invalid if it still fails at the highest allowance if hi == cap { - if !executable(hi) { - return 0, fmt.Errorf("energy required exceeds allowance (%d) or always failing transaction", cap) + failed, result, err := executable(hi) + if err != nil { + return 0, err + } + if failed { + if result != nil && result.Err != vm.ErrOutOfEnergy { + var revert string + if len(result.Revert()) > 0 { + ret, err := abi.UnpackRevert(result.Revert()) + if err != nil { + revert = hexutil.Encode(result.Revert()) + } else { + revert = ret + } + } + return 0, estimateEnergyError{ + error: "always failing transaction", + vmerr: result.Err, + revert: revert, + } + } + // Otherwise, the specified energy cap is too low + return 0, estimateEnergyError{error: fmt.Sprintf("energy required exceeds allowance (%d)", cap)} } } return hexutil.Uint64(hi), nil diff --git a/les/odr_test.go b/les/odr_test.go index 27800c3ec..8f3da92a8 100644 --- a/les/odr_test.go +++ b/les/odr_test.go @@ -138,8 +138,8 @@ func odrContractCall(ctx context.Context, db xcbdb.Database, config *params.Chai //vmenv := core.NewEnv(statedb, config, bc, msg, header, vm.Config{}) gp := new(core.EnergyPool).AddEnergy(math.MaxUint64) - ret, _, _, _ := core.ApplyMessage(vmenv, msg, gp) - res = append(res, ret...) + result, _ := core.ApplyMessage(vmenv, msg, gp) + res = append(res, result.Return()...) } } else { header := lc.GetHeaderByHash(bhash) @@ -149,9 +149,9 @@ func odrContractCall(ctx context.Context, db xcbdb.Database, config *params.Chai context := core.NewCVMContext(msg, header, lc, nil) vmenv := vm.NewCVM(context, state, config, vm.Config{}) gp := new(core.EnergyPool).AddEnergy(math.MaxUint64) - ret, _, _, _ := core.ApplyMessage(vmenv, msg, gp) + result, _ := core.ApplyMessage(vmenv, msg, gp) if state.Error() == nil { - res = append(res, ret...) + res = append(res, result.Return()...) } } } diff --git a/light/odr_test.go b/light/odr_test.go index bd7700a96..df739c151 100644 --- a/light/odr_test.go +++ b/light/odr_test.go @@ -206,8 +206,8 @@ func odrContractCall(ctx context.Context, db xcbdb.Database, bc *core.BlockChain context := core.NewCVMContext(msg, header, chain, nil) vmenv := vm.NewCVM(context, st, config, vm.Config{}) gp := new(core.EnergyPool).AddEnergy(math.MaxUint64) - ret, _, _, _ := core.ApplyMessage(vmenv, msg, gp) - res = append(res, ret...) + result, _ := core.ApplyMessage(vmenv, msg, gp) + res = append(res, result.Return()...) if st.Error() != nil { return res, st.Error() } diff --git a/signer/fourbyte/4byte.go b/signer/fourbyte/4byte.go index 5b9174688..3b741c30f 100644 --- a/signer/fourbyte/4byte.go +++ b/signer/fourbyte/4byte.go @@ -146899,8 +146899,8 @@ func _4byteJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "4byte.json", size: 5954391, mode: os.FileMode(0664), modTime: time.Unix(1653306979, 0)} - a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x78, 0xa2, 0xbc, 0xfb, 0x95, 0x5a, 0x5f, 0x6c, 0xdd, 0xc4, 0x5, 0x2c, 0xd0, 0x37, 0x7a, 0x8f, 0x25, 0xd2, 0x9a, 0x27, 0x3c, 0xb2, 0x91, 0x82, 0x97, 0x5e, 0xcf, 0x86, 0x46, 0x9c, 0x83, 0x13}} + info := bindataFileInfo{name: "4byte.json", size: 5954391, mode: os.FileMode(0664), modTime: time.Unix(1657275512, 0)} + a := &asset{bytes: bytes, info: info, digest: [32]uint8{0xca, 0x20, 0x33, 0xe4, 0xd0, 0xaf, 0x28, 0x3f, 0xd0, 0xe8, 0x24, 0xb9, 0x5a, 0xd9, 0x95, 0x9c, 0x8a, 0x7e, 0x98, 0x18, 0xba, 0x8b, 0xd2, 0x8c, 0x82, 0x6b, 0x59, 0x11, 0x36, 0xd, 0x8a, 0x66}} return a, nil } diff --git a/tests/state_test_util.go b/tests/state_test_util.go index 8137c4678..f7aa80c3d 100644 --- a/tests/state_test_util.go +++ b/tests/state_test_util.go @@ -186,7 +186,7 @@ func (t *StateTest) RunNoVerify(subtest StateSubtest, vmconfig vm.Config, snapsh energypool := new(core.EnergyPool) energypool.AddEnergy(block.EnergyLimit()) snapshot := statedb.Snapshot() - if _, _, _, err := core.ApplyMessage(cvm, msg, energypool); err != nil { + if _, err := core.ApplyMessage(cvm, msg, energypool); err != nil { statedb.RevertToSnapshot(snapshot) } // Commit block diff --git a/xcb/api_tracer.go b/xcb/api_tracer.go index 291cc3db5..6008b9892 100644 --- a/xcb/api_tracer.go +++ b/xcb/api_tracer.go @@ -501,7 +501,7 @@ func (api *PrivateDebugAPI) traceBlock(ctx context.Context, block *types.Block, vmctx := core.NewCVMContext(msg, block.Header(), api.xcb.blockchain, nil) vmenv := vm.NewCVM(vmctx, statedb, api.xcb.blockchain.Config(), vm.Config{}) - if _, _, _, err := core.ApplyMessage(vmenv, msg, new(core.EnergyPool).AddEnergy(msg.Energy())); err != nil { + if _, err := core.ApplyMessage(vmenv, msg, new(core.EnergyPool).AddEnergy(msg.Energy())); err != nil { failed = err break } @@ -594,7 +594,7 @@ func (api *PrivateDebugAPI) standardTraceBlockToFile(ctx context.Context, block } // Execute the transaction and flush any traces to disk vmenv := vm.NewCVM(vmctx, statedb, api.xcb.blockchain.Config(), vmConf) - _, _, _, err = core.ApplyMessage(vmenv, msg, new(core.EnergyPool).AddEnergy(msg.Energy())) + _, err = core.ApplyMessage(vmenv, msg, new(core.EnergyPool).AddEnergy(msg.Energy())) if writer != nil { writer.Flush() } @@ -794,7 +794,7 @@ func (api *PrivateDebugAPI) traceTx(ctx context.Context, message core.Message, v // Run the transaction with tracing enabled. vmenv := vm.NewCVM(vmctx, statedb, api.xcb.blockchain.Config(), vm.Config{Debug: true, Tracer: tracer}) - ret, energy, failed, err := core.ApplyMessage(vmenv, message, new(core.EnergyPool).AddEnergy(message.Energy())) + result, err := core.ApplyMessage(vmenv, message, new(core.EnergyPool).AddEnergy(message.Energy())) if err != nil { return nil, fmt.Errorf("tracing failed: %v", err) } @@ -802,9 +802,9 @@ func (api *PrivateDebugAPI) traceTx(ctx context.Context, message core.Message, v switch tracer := tracer.(type) { case *vm.StructLogger: return &xcbapi.ExecutionResult{ - Energy: energy, - Failed: failed, - ReturnValue: fmt.Sprintf("%x", ret), + Energy: result.UsedEnergy, + Failed: result.Failed(), + ReturnValue: fmt.Sprintf("%x", result.Return()), StructLogs: xcbapi.FormatLogs(tracer.StructLogs()), }, nil @@ -844,7 +844,7 @@ func (api *PrivateDebugAPI) computeTxEnv(block *types.Block, txIndex int, reexec } // Not yet the searched for transaction, execute on top of the current state vmenv := vm.NewCVM(context, statedb, api.xcb.blockchain.Config(), vm.Config{}) - if _, _, _, err := core.ApplyMessage(vmenv, msg, new(core.EnergyPool).AddEnergy(tx.Energy())); err != nil { + if _, err := core.ApplyMessage(vmenv, msg, new(core.EnergyPool).AddEnergy(tx.Energy())); err != nil { return nil, vm.Context{}, nil, fmt.Errorf("transaction %#x failed: %v", tx.Hash(), err) } // Ensure any modifications are committed to the state diff --git a/xcb/tracers/testdata/call_tracer_inner_create_oog_outer_throw.json b/xcb/tracers/testdata/call_tracer_inner_create_oog_outer_throw.json index fa4237c03..816907d96 100644 --- a/xcb/tracers/testdata/call_tracer_inner_create_oog_outer_throw.json +++ b/xcb/tracers/testdata/call_tracer_inner_create_oog_outer_throw.json @@ -59,7 +59,7 @@ "value": "0x0" } ], - "error": "cvm: invalid jump destination", + "error": "invalid jump destination", "from": "0xe4a13bc304682a903e9472f469c33801dd18d9e8", "energy": "0x435c8", "energyUsed": "0x435c8", diff --git a/xcb/tracers/testdata/call_tracer_inner_throw_outer_revert.json b/xcb/tracers/testdata/call_tracer_inner_throw_outer_revert.json index 2fa493574..d636f8e11 100644 --- a/xcb/tracers/testdata/call_tracer_inner_throw_outer_revert.json +++ b/xcb/tracers/testdata/call_tracer_inner_throw_outer_revert.json @@ -53,7 +53,7 @@ "result": { "calls": [ { - "error": "invalid opcode 0xfe", + "error": "invalid opcode: opcode 0xfe not defined", "from": "0x33056b5dcac09a9b4becad0e1dcf92c19bd0af76", "energy": "0x75fe3", "energyUsed": "0x75fe3", diff --git a/xcb/tracers/testdata/call_tracer_throw.json b/xcb/tracers/testdata/call_tracer_throw.json index 60ac238a0..134c71607 100644 --- a/xcb/tracers/testdata/call_tracer_throw.json +++ b/xcb/tracers/testdata/call_tracer_throw.json @@ -44,7 +44,7 @@ }, "input": "0xf8a08206668504a817c8008303d09094c212e03b9e060e36facad5fd8f4435412ca22e6b80a451a34eb8000000000000000000000000000000000000000000000027fad02094277c000029a0692a3b4e7b2842f8dd7832e712c21e09f451f416c8976d5b8d02e8c0c2b4bea9a07645e90fc421b63dd755767fd93d3c03b4ec0c4d8fafa059558d08cf11d597509470c9217d814985faef62b124420f8dfbddd96433", "result": { - "error": "cvm: invalid jump destination", + "error": "invalid jump destination", "from": "0x70c9217d814985faef62b124420f8dfbddd96433", "energy": "0x37b38", "energyUsed": "0x37b38", diff --git a/xcb/tracers/tracers_test.go b/xcb/tracers/tracers_test.go index a04017d21..e69ef0ed8 100644 --- a/xcb/tracers/tracers_test.go +++ b/xcb/tracers/tracers_test.go @@ -185,7 +185,7 @@ func TestPrestateTracerCreate2(t *testing.T) { t.Fatalf("failed to prepare transaction for tracing: %v", err) } st := core.NewStateTransition(cvm, msg, new(core.EnergyPool).AddEnergy(tx.Energy())) - if _, _, _, err = st.TransitionDb(); err != nil { + if _, err = st.TransitionDb(); err != nil { t.Fatalf("failed to execute transaction: %v", err) } // Retrieve the trace result and compare against the etalon @@ -260,7 +260,7 @@ func TestCallTracer(t *testing.T) { t.Fatalf("failed to prepare transaction for tracing: %v", err) } st := core.NewStateTransition(cvm, msg, new(core.EnergyPool).AddEnergy(tx.Energy())) - if _, _, _, err = st.TransitionDb(); err != nil { + if _, err = st.TransitionDb(); err != nil { t.Fatalf("failed to execute transaction: %v", err) } // Retrieve the trace result and compare against the etalon