all: seperate consensus error and evm internal error (#20830)
* all: seperate consensus error and evm internal error There are actually two types of error will be returned when a tranaction/message call is executed: (a) consensus error (b) evm internal error. The former should be converted to a consensus issue, e.g. The sender doesn't enough asset to purchase the gas it specifies. The latter is allowed since evm itself is a blackbox and internal error is allowed to happen. This PR emphasizes the difference by introducing a executionResult structure. The evm error is embedded inside. So if any error returned, it indicates consensus issue happens. And also this PR improve the `EstimateGas` API to return the concrete revert reason if the transaction always fails * all: polish * accounts/abi/bind/backends: add tests * accounts/abi/bind/backends, internal: cleanup error message * all: address comments * core: fix lint * accounts, core, eth, internal: address comments * accounts, internal: resolve revert reason if possible * accounts, internal: address comments
This commit is contained in:
parent
c60c0c97e7
commit
b9df7ecdc3
@ -24,6 +24,7 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
)
|
)
|
||||||
|
|
||||||
// The ABI holds information about a contract's context and available
|
// The ABI holds information about a contract's context and available
|
||||||
@ -234,3 +235,25 @@ func (abi *ABI) HasFallback() bool {
|
|||||||
func (abi *ABI) HasReceive() bool {
|
func (abi *ABI) HasReceive() bool {
|
||||||
return abi.Receive.Type == Receive
|
return abi.Receive.Type == Receive
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// revertSelector is a special function selector for revert reason unpacking.
|
||||||
|
var revertSelector = crypto.Keccak256([]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
|
||||||
|
}
|
||||||
|
@ -19,6 +19,7 @@ package abi
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/big"
|
"math/big"
|
||||||
"reflect"
|
"reflect"
|
||||||
@ -1070,3 +1071,34 @@ func TestUnnamedEventParam(t *testing.T) {
|
|||||||
t.Fatalf("Could not find input")
|
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")},
|
||||||
|
{"08c379a00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000d72657665727420726561736f6e00000000000000000000000000000000000000", "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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -25,6 +25,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum"
|
"github.com/ethereum/go-ethereum"
|
||||||
|
"github.com/ethereum/go-ethereum/accounts/abi"
|
||||||
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/common/math"
|
"github.com/ethereum/go-ethereum/common/math"
|
||||||
@ -49,7 +50,6 @@ var (
|
|||||||
errBlockNumberUnsupported = errors.New("simulatedBackend cannot access blocks other than the latest block")
|
errBlockNumberUnsupported = errors.New("simulatedBackend cannot access blocks other than the latest block")
|
||||||
errBlockDoesNotExist = errors.New("block does not exist in blockchain")
|
errBlockDoesNotExist = errors.New("block does not exist in blockchain")
|
||||||
errTransactionDoesNotExist = errors.New("transaction does not exist")
|
errTransactionDoesNotExist = errors.New("transaction does not exist")
|
||||||
errGasEstimationFailed = errors.New("gas required exceeds allowance or always failing transaction")
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// SimulatedBackend implements bind.ContractBackend, simulating a blockchain in
|
// SimulatedBackend implements bind.ContractBackend, simulating a blockchain in
|
||||||
@ -349,8 +349,11 @@ func (b *SimulatedBackend) CallContract(ctx context.Context, call ethereum.CallM
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
rval, _, _, err := b.callContract(ctx, call, b.blockchain.CurrentBlock(), state)
|
res, err := b.callContract(ctx, call, b.blockchain.CurrentBlock(), state)
|
||||||
return rval, err
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return res.Return(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// PendingCallContract executes a contract call on the pending state.
|
// PendingCallContract executes a contract call on the pending state.
|
||||||
@ -359,8 +362,11 @@ func (b *SimulatedBackend) PendingCallContract(ctx context.Context, call ethereu
|
|||||||
defer b.mu.Unlock()
|
defer b.mu.Unlock()
|
||||||
defer b.pendingState.RevertToSnapshot(b.pendingState.Snapshot())
|
defer b.pendingState.RevertToSnapshot(b.pendingState.Snapshot())
|
||||||
|
|
||||||
rval, _, _, err := b.callContract(ctx, call, b.pendingBlock, b.pendingState)
|
res, err := b.callContract(ctx, call, b.pendingBlock, b.pendingState)
|
||||||
return rval, err
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return res.Return(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// PendingNonceAt implements PendingStateReader.PendingNonceAt, retrieving
|
// PendingNonceAt implements PendingStateReader.PendingNonceAt, retrieving
|
||||||
@ -398,22 +404,33 @@ func (b *SimulatedBackend) EstimateGas(ctx context.Context, call ethereum.CallMs
|
|||||||
cap = hi
|
cap = hi
|
||||||
|
|
||||||
// Create a helper to check if a gas allowance results in an executable transaction
|
// Create a helper to check if a gas allowance results in an executable transaction
|
||||||
executable := func(gas uint64) bool {
|
executable := func(gas uint64) (bool, *core.ExecutionResult, error) {
|
||||||
call.Gas = gas
|
call.Gas = gas
|
||||||
|
|
||||||
snapshot := b.pendingState.Snapshot()
|
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)
|
b.pendingState.RevertToSnapshot(snapshot)
|
||||||
|
|
||||||
if err != nil || failed {
|
if err != nil {
|
||||||
return false
|
if err == core.ErrIntrinsicGas {
|
||||||
|
return true, nil, nil // Special case, raise gas limit
|
||||||
|
}
|
||||||
|
return true, nil, err // Bail out
|
||||||
}
|
}
|
||||||
return true
|
return res.Failed(), res, nil
|
||||||
}
|
}
|
||||||
// Execute the binary search and hone in on an executable gas limit
|
// Execute the binary search and hone in on an executable gas limit
|
||||||
for lo+1 < hi {
|
for lo+1 < hi {
|
||||||
mid := (hi + lo) / 2
|
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
|
lo = mid
|
||||||
} else {
|
} else {
|
||||||
hi = mid
|
hi = mid
|
||||||
@ -421,8 +438,25 @@ func (b *SimulatedBackend) EstimateGas(ctx context.Context, call ethereum.CallMs
|
|||||||
}
|
}
|
||||||
// Reject the transaction as invalid if it still fails at the highest allowance
|
// Reject the transaction as invalid if it still fails at the highest allowance
|
||||||
if hi == cap {
|
if hi == cap {
|
||||||
if !executable(hi) {
|
failed, result, err := executable(hi)
|
||||||
return 0, errGasEstimationFailed
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
if failed {
|
||||||
|
if result != nil && result.Err != vm.ErrOutOfGas {
|
||||||
|
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 gas cap is too low
|
||||||
|
return 0, fmt.Errorf("gas required exceeds allowance (%d)", cap)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return hi, nil
|
return hi, nil
|
||||||
@ -430,7 +464,7 @@ func (b *SimulatedBackend) EstimateGas(ctx context.Context, call ethereum.CallMs
|
|||||||
|
|
||||||
// callContract implements common code between normal and pending contract calls.
|
// callContract implements common code between normal and pending contract calls.
|
||||||
// state is modified during execution, make sure to copy it if necessary.
|
// state is modified during execution, make sure to copy it if necessary.
|
||||||
func (b *SimulatedBackend) callContract(ctx context.Context, call ethereum.CallMsg, block *types.Block, statedb *state.StateDB) ([]byte, uint64, bool, error) {
|
func (b *SimulatedBackend) callContract(ctx context.Context, call ethereum.CallMsg, block *types.Block, statedb *state.StateDB) (*core.ExecutionResult, error) {
|
||||||
// Ensure message is initialized properly.
|
// Ensure message is initialized properly.
|
||||||
if call.GasPrice == nil {
|
if call.GasPrice == nil {
|
||||||
call.GasPrice = big.NewInt(1)
|
call.GasPrice = big.NewInt(1)
|
||||||
|
@ -19,6 +19,7 @@ package backends
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"math/big"
|
"math/big"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
@ -356,25 +357,112 @@ func TestSimulatedBackend_TransactionByHash(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestSimulatedBackend_EstimateGas(t *testing.T) {
|
func TestSimulatedBackend_EstimateGas(t *testing.T) {
|
||||||
sim := NewSimulatedBackend(
|
/*
|
||||||
core.GenesisAlloc{}, 10000000,
|
pragma solidity ^0.6.4;
|
||||||
)
|
contract GasEstimation {
|
||||||
|
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 = "0x60806040523480156100115760006000fd5b50610017565b61016e806100266000396000f3fe60806040523480156100115760006000fd5b506004361061005c5760003560e01c806350f6fe3414610062578063aa8b1d301461006c578063b9b046f914610076578063d8b9839114610080578063e09fface1461008a5761005c565b60006000fd5b61006a610094565b005b6100746100ad565b005b61007e6100b5565b005b6100886100c2565b005b610092610135565b005b6000600090505b5b808060010191505061009b565b505b565b60006000fd5b565b600015156100bf57fe5b5b565b6040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252600d8152602001807f72657665727420726561736f6e0000000000000000000000000000000000000081526020015060200191505060405180910390fd5b565b5b56fea2646970667358221220345bbcbb1a5ecf22b53a78eaebf95f8ee0eceff6d10d4b9643495084d2ec934a64736f6c63430006040033"
|
||||||
|
|
||||||
|
key, _ := crypto.GenerateKey()
|
||||||
|
addr := crypto.PubkeyToAddress(key.PublicKey)
|
||||||
|
opts := bind.NewKeyedTransactor(key)
|
||||||
|
|
||||||
|
sim := NewSimulatedBackend(core.GenesisAlloc{addr: {Balance: big.NewInt(params.Ether)}}, 10000000)
|
||||||
defer sim.Close()
|
defer sim.Close()
|
||||||
bgCtx := context.Background()
|
|
||||||
testAddr := crypto.PubkeyToAddress(testKey.PublicKey)
|
|
||||||
|
|
||||||
gas, err := sim.EstimateGas(bgCtx, ethereum.CallMsg{
|
parsed, _ := abi.JSON(strings.NewReader(contractAbi))
|
||||||
From: testAddr,
|
contractAddr, _, _, _ := bind.DeployContract(opts, parsed, common.FromHex(contractBin), sim)
|
||||||
To: &testAddr,
|
sim.Commit()
|
||||||
Value: big.NewInt(1000),
|
|
||||||
Data: []byte{},
|
var cases = []struct {
|
||||||
})
|
name string
|
||||||
if err != nil {
|
message ethereum.CallMsg
|
||||||
t.Errorf("could not estimate gas: %v", err)
|
expect uint64
|
||||||
|
expectError error
|
||||||
|
}{
|
||||||
|
{"plain transfer(valid)", ethereum.CallMsg{
|
||||||
|
From: addr,
|
||||||
|
To: &addr,
|
||||||
|
Gas: 0,
|
||||||
|
GasPrice: big.NewInt(0),
|
||||||
|
Value: big.NewInt(1),
|
||||||
|
Data: nil,
|
||||||
|
}, params.TxGas, nil},
|
||||||
|
|
||||||
|
{"plain transfer(invalid)", ethereum.CallMsg{
|
||||||
|
From: addr,
|
||||||
|
To: &contractAddr,
|
||||||
|
Gas: 0,
|
||||||
|
GasPrice: big.NewInt(0),
|
||||||
|
Value: big.NewInt(1),
|
||||||
|
Data: nil,
|
||||||
|
}, 0, errors.New("always failing transaction (execution reverted)")},
|
||||||
|
|
||||||
|
{"Revert", ethereum.CallMsg{
|
||||||
|
From: addr,
|
||||||
|
To: &contractAddr,
|
||||||
|
Gas: 0,
|
||||||
|
GasPrice: big.NewInt(0),
|
||||||
|
Value: nil,
|
||||||
|
Data: common.Hex2Bytes("d8b98391"),
|
||||||
|
}, 0, errors.New("always failing transaction (execution reverted) (revert reason)")},
|
||||||
|
|
||||||
|
{"PureRevert", ethereum.CallMsg{
|
||||||
|
From: addr,
|
||||||
|
To: &contractAddr,
|
||||||
|
Gas: 0,
|
||||||
|
GasPrice: big.NewInt(0),
|
||||||
|
Value: nil,
|
||||||
|
Data: common.Hex2Bytes("aa8b1d30"),
|
||||||
|
}, 0, errors.New("always failing transaction (execution reverted)")},
|
||||||
|
|
||||||
|
{"OOG", ethereum.CallMsg{
|
||||||
|
From: addr,
|
||||||
|
To: &contractAddr,
|
||||||
|
Gas: 100000,
|
||||||
|
GasPrice: big.NewInt(0),
|
||||||
|
Value: nil,
|
||||||
|
Data: common.Hex2Bytes("50f6fe34"),
|
||||||
|
}, 0, errors.New("gas required exceeds allowance (100000)")},
|
||||||
|
|
||||||
|
{"Assert", ethereum.CallMsg{
|
||||||
|
From: addr,
|
||||||
|
To: &contractAddr,
|
||||||
|
Gas: 100000,
|
||||||
|
GasPrice: big.NewInt(0),
|
||||||
|
Value: nil,
|
||||||
|
Data: common.Hex2Bytes("b9b046f9"),
|
||||||
|
}, 0, errors.New("always failing transaction (invalid opcode: opcode 0xfe not defined)")},
|
||||||
|
|
||||||
|
{"Valid", ethereum.CallMsg{
|
||||||
|
From: addr,
|
||||||
|
To: &contractAddr,
|
||||||
|
Gas: 100000,
|
||||||
|
GasPrice: big.NewInt(0),
|
||||||
|
Value: nil,
|
||||||
|
Data: common.Hex2Bytes("e09fface"),
|
||||||
|
}, 21275, nil},
|
||||||
}
|
}
|
||||||
|
for _, c := range cases {
|
||||||
if gas != params.TxGas {
|
got, err := sim.EstimateGas(context.Background(), c.message)
|
||||||
t.Errorf("expected 21000 gas cost for a transaction got %v", gas)
|
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("Gas estimation mismatch, want %d, got %d", c.expect, got)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -678,7 +678,7 @@ func (api *RetestethAPI) AccountRange(ctx context.Context,
|
|||||||
context := core.NewEVMContext(msg, block.Header(), api.blockchain, nil)
|
context := core.NewEVMContext(msg, block.Header(), api.blockchain, nil)
|
||||||
// Not yet the searched for transaction, execute on top of the current state
|
// Not yet the searched for transaction, execute on top of the current state
|
||||||
vmenv := vm.NewEVM(context, statedb, api.blockchain.Config(), vm.Config{})
|
vmenv := vm.NewEVM(context, statedb, api.blockchain.Config(), vm.Config{})
|
||||||
if _, _, _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(tx.Gas())); err != nil {
|
if _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(tx.Gas())); err != nil {
|
||||||
return AccountRangeResult{}, fmt.Errorf("transaction %#x failed: %v", tx.Hash(), err)
|
return AccountRangeResult{}, fmt.Errorf("transaction %#x failed: %v", tx.Hash(), err)
|
||||||
}
|
}
|
||||||
// Ensure any modifications are committed to the state
|
// Ensure any modifications are committed to the state
|
||||||
@ -788,7 +788,7 @@ func (api *RetestethAPI) StorageRangeAt(ctx context.Context,
|
|||||||
context := core.NewEVMContext(msg, block.Header(), api.blockchain, nil)
|
context := core.NewEVMContext(msg, block.Header(), api.blockchain, nil)
|
||||||
// Not yet the searched for transaction, execute on top of the current state
|
// Not yet the searched for transaction, execute on top of the current state
|
||||||
vmenv := vm.NewEVM(context, statedb, api.blockchain.Config(), vm.Config{})
|
vmenv := vm.NewEVM(context, statedb, api.blockchain.Config(), vm.Config{})
|
||||||
if _, _, _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(tx.Gas())); err != nil {
|
if _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(tx.Gas())); err != nil {
|
||||||
return StorageRangeResult{}, fmt.Errorf("transaction %#x failed: %v", tx.Hash(), err)
|
return StorageRangeResult{}, fmt.Errorf("transaction %#x failed: %v", tx.Hash(), err)
|
||||||
}
|
}
|
||||||
// Ensure any modifications are committed to the state
|
// Ensure any modifications are committed to the state
|
||||||
|
@ -22,17 +22,45 @@ var (
|
|||||||
// ErrKnownBlock is returned when a block to import is already known locally.
|
// ErrKnownBlock is returned when a block to import is already known locally.
|
||||||
ErrKnownBlock = errors.New("block already known")
|
ErrKnownBlock = errors.New("block already known")
|
||||||
|
|
||||||
// ErrGasLimitReached is returned by the gas pool if the amount of gas required
|
|
||||||
// by a transaction is higher than what's left in the block.
|
|
||||||
ErrGasLimitReached = errors.New("gas limit reached")
|
|
||||||
|
|
||||||
// ErrBlacklistedHash is returned if a block to import is on the blacklist.
|
// ErrBlacklistedHash is returned if a block to import is on the blacklist.
|
||||||
ErrBlacklistedHash = errors.New("blacklisted hash")
|
ErrBlacklistedHash = errors.New("blacklisted hash")
|
||||||
|
|
||||||
|
// ErrNoGenesis is returned when there is no Genesis Block.
|
||||||
|
ErrNoGenesis = errors.New("genesis not found in chain")
|
||||||
|
)
|
||||||
|
|
||||||
|
// List of evm-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
|
// ErrNonceTooHigh is returned if the nonce of a transaction is higher than the
|
||||||
// next one expected based on the local chain.
|
// next one expected based on the local chain.
|
||||||
ErrNonceTooHigh = errors.New("nonce too high")
|
ErrNonceTooHigh = errors.New("nonce too high")
|
||||||
|
|
||||||
// ErrNoGenesis is returned when there is no Genesis Block.
|
// ErrGasLimitReached is returned by the gas pool if the amount of gas required
|
||||||
ErrNoGenesis = errors.New("genesis not found in chain")
|
// by a transaction is higher than what's left in the block.
|
||||||
|
ErrGasLimitReached = errors.New("gas 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 gas * price + value")
|
||||||
|
|
||||||
|
// ErrGasUintOverflow is returned when calculating gas usage.
|
||||||
|
ErrGasUintOverflow = errors.New("gas uint64 overflow")
|
||||||
|
|
||||||
|
// ErrIntrinsicGas is returned if the transaction is specified to use less gas
|
||||||
|
// than required to start the invocation.
|
||||||
|
ErrIntrinsicGas = errors.New("intrinsic gas too low")
|
||||||
)
|
)
|
||||||
|
@ -131,7 +131,7 @@ func TestMergeDelete(t *testing.T) {
|
|||||||
|
|
||||||
flipDrops := func() map[common.Hash]struct{} {
|
flipDrops := func() map[common.Hash]struct{} {
|
||||||
return map[common.Hash]struct{}{
|
return map[common.Hash]struct{}{
|
||||||
h2: struct{}{},
|
h2: {},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
flipAccs := func() map[common.Hash][]byte {
|
flipAccs := func() map[common.Hash][]byte {
|
||||||
@ -141,7 +141,7 @@ func TestMergeDelete(t *testing.T) {
|
|||||||
}
|
}
|
||||||
flopDrops := func() map[common.Hash]struct{} {
|
flopDrops := func() map[common.Hash]struct{} {
|
||||||
return map[common.Hash]struct{}{
|
return map[common.Hash]struct{}{
|
||||||
h1: struct{}{},
|
h1: {},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
flopAccs := func() map[common.Hash][]byte {
|
flopAccs := func() map[common.Hash][]byte {
|
||||||
|
@ -121,10 +121,10 @@ func TestDiskMerge(t *testing.T) {
|
|||||||
|
|
||||||
// Modify or delete some accounts, flatten everything onto disk
|
// Modify or delete some accounts, flatten everything onto disk
|
||||||
if err := snaps.Update(diffRoot, baseRoot, map[common.Hash]struct{}{
|
if err := snaps.Update(diffRoot, baseRoot, map[common.Hash]struct{}{
|
||||||
accDelNoCache: struct{}{},
|
accDelNoCache: {},
|
||||||
accDelCache: struct{}{},
|
accDelCache: {},
|
||||||
conNukeNoCache: struct{}{},
|
conNukeNoCache: {},
|
||||||
conNukeCache: struct{}{},
|
conNukeCache: {},
|
||||||
}, map[common.Hash][]byte{
|
}, map[common.Hash][]byte{
|
||||||
accModNoCache: reverse(accModNoCache[:]),
|
accModNoCache: reverse(accModNoCache[:]),
|
||||||
accModCache: reverse(accModCache[:]),
|
accModCache: reverse(accModCache[:]),
|
||||||
@ -344,10 +344,10 @@ func TestDiskPartialMerge(t *testing.T) {
|
|||||||
|
|
||||||
// Modify or delete some accounts, flatten everything onto disk
|
// Modify or delete some accounts, flatten everything onto disk
|
||||||
if err := snaps.Update(diffRoot, baseRoot, map[common.Hash]struct{}{
|
if err := snaps.Update(diffRoot, baseRoot, map[common.Hash]struct{}{
|
||||||
accDelNoCache: struct{}{},
|
accDelNoCache: {},
|
||||||
accDelCache: struct{}{},
|
accDelCache: {},
|
||||||
conNukeNoCache: struct{}{},
|
conNukeNoCache: {},
|
||||||
conNukeCache: struct{}{},
|
conNukeCache: {},
|
||||||
}, map[common.Hash][]byte{
|
}, map[common.Hash][]byte{
|
||||||
accModNoCache: reverse(accModNoCache[:]),
|
accModNoCache: reverse(accModNoCache[:]),
|
||||||
accModCache: reverse(accModCache[:]),
|
accModCache: reverse(accModCache[:]),
|
||||||
|
@ -415,7 +415,7 @@ func TestIteratorDeletions(t *testing.T) {
|
|||||||
|
|
||||||
deleted := common.HexToHash("0x22")
|
deleted := common.HexToHash("0x22")
|
||||||
destructed := map[common.Hash]struct{}{
|
destructed := map[common.Hash]struct{}{
|
||||||
deleted: struct{}{},
|
deleted: {},
|
||||||
}
|
}
|
||||||
snaps.Update(common.HexToHash("0x03"), common.HexToHash("0x02"),
|
snaps.Update(common.HexToHash("0x03"), common.HexToHash("0x02"),
|
||||||
destructed, randomAccountSet("0x11", "0x33"), nil)
|
destructed, randomAccountSet("0x11", "0x33"), nil)
|
||||||
|
@ -89,6 +89,6 @@ func precacheTransaction(config *params.ChainConfig, bc ChainContext, author *co
|
|||||||
context := NewEVMContext(msg, header, bc, author)
|
context := NewEVMContext(msg, header, bc, author)
|
||||||
vm := vm.NewEVM(context, statedb, config, cfg)
|
vm := vm.NewEVM(context, statedb, config, cfg)
|
||||||
|
|
||||||
_, _, _, err = ApplyMessage(vm, msg, gaspool)
|
_, err = ApplyMessage(vm, msg, gaspool)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -96,7 +96,7 @@ func ApplyTransaction(config *params.ChainConfig, bc ChainContext, author *commo
|
|||||||
// about the transaction and calling mechanisms.
|
// about the transaction and calling mechanisms.
|
||||||
vmenv := vm.NewEVM(context, statedb, config, cfg)
|
vmenv := vm.NewEVM(context, statedb, config, cfg)
|
||||||
// Apply the transaction to the current state (included in the env)
|
// Apply the transaction to the current state (included in the env)
|
||||||
_, gas, failed, err := ApplyMessage(vmenv, msg, gp)
|
result, err := ApplyMessage(vmenv, msg, gp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -107,13 +107,13 @@ func ApplyTransaction(config *params.ChainConfig, bc ChainContext, author *commo
|
|||||||
} else {
|
} else {
|
||||||
root = statedb.IntermediateRoot(config.IsEIP158(header.Number)).Bytes()
|
root = statedb.IntermediateRoot(config.IsEIP158(header.Number)).Bytes()
|
||||||
}
|
}
|
||||||
*usedGas += gas
|
*usedGas += result.UsedGas
|
||||||
|
|
||||||
// Create a new receipt for the transaction, storing the intermediate root and gas used by the tx
|
// Create a new receipt for the transaction, storing the intermediate root and gas used by the tx
|
||||||
// based on the eip phase, we're passing whether the root touch-delete accounts.
|
// based on the eip phase, we're passing whether the root touch-delete accounts.
|
||||||
receipt := types.NewReceipt(root, failed, *usedGas)
|
receipt := types.NewReceipt(root, result.Failed(), *usedGas)
|
||||||
receipt.TxHash = tx.Hash()
|
receipt.TxHash = tx.Hash()
|
||||||
receipt.GasUsed = gas
|
receipt.GasUsed = result.UsedGas
|
||||||
// if the transaction created a contract, store the creation address in the receipt.
|
// if the transaction created a contract, store the creation address in the receipt.
|
||||||
if msg.To() == nil {
|
if msg.To() == nil {
|
||||||
receipt.ContractAddress = crypto.CreateAddress(vmenv.Context.Origin, tx.Nonce())
|
receipt.ContractAddress = crypto.CreateAddress(vmenv.Context.Origin, tx.Nonce())
|
||||||
|
@ -17,20 +17,14 @@
|
|||||||
package core
|
package core
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"math"
|
"math"
|
||||||
"math/big"
|
"math/big"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/core/vm"
|
"github.com/ethereum/go-ethereum/core/vm"
|
||||||
"github.com/ethereum/go-ethereum/log"
|
|
||||||
"github.com/ethereum/go-ethereum/params"
|
"github.com/ethereum/go-ethereum/params"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
|
||||||
errInsufficientBalanceForGas = errors.New("insufficient balance to pay for gas")
|
|
||||||
)
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
The State Transitioning Model
|
The State Transitioning Model
|
||||||
|
|
||||||
@ -63,7 +57,6 @@ type StateTransition struct {
|
|||||||
// Message represents a message sent to a contract.
|
// Message represents a message sent to a contract.
|
||||||
type Message interface {
|
type Message interface {
|
||||||
From() common.Address
|
From() common.Address
|
||||||
//FromFrontier() (common.Address, error)
|
|
||||||
To() *common.Address
|
To() *common.Address
|
||||||
|
|
||||||
GasPrice() *big.Int
|
GasPrice() *big.Int
|
||||||
@ -75,6 +68,41 @@ type Message interface {
|
|||||||
Data() []byte
|
Data() []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ExecutionResult includes all output after executing given evm
|
||||||
|
// message no matter the execution itself is successful or not.
|
||||||
|
type ExecutionResult struct {
|
||||||
|
UsedGas uint64 // Total used gas but include the refunded gas
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
// IntrinsicGas computes the 'intrinsic gas' for a message with the given data.
|
// IntrinsicGas computes the 'intrinsic gas' for a message with the given data.
|
||||||
func IntrinsicGas(data []byte, contractCreation, isHomestead bool, isEIP2028 bool) (uint64, error) {
|
func IntrinsicGas(data []byte, contractCreation, isHomestead bool, isEIP2028 bool) (uint64, error) {
|
||||||
// Set the starting gas for the raw transaction
|
// Set the starting gas for the raw transaction
|
||||||
@ -99,13 +127,13 @@ func IntrinsicGas(data []byte, contractCreation, isHomestead bool, isEIP2028 boo
|
|||||||
nonZeroGas = params.TxDataNonZeroGasEIP2028
|
nonZeroGas = params.TxDataNonZeroGasEIP2028
|
||||||
}
|
}
|
||||||
if (math.MaxUint64-gas)/nonZeroGas < nz {
|
if (math.MaxUint64-gas)/nonZeroGas < nz {
|
||||||
return 0, vm.ErrOutOfGas
|
return 0, ErrGasUintOverflow
|
||||||
}
|
}
|
||||||
gas += nz * nonZeroGas
|
gas += nz * nonZeroGas
|
||||||
|
|
||||||
z := uint64(len(data)) - nz
|
z := uint64(len(data)) - nz
|
||||||
if (math.MaxUint64-gas)/params.TxDataZeroGas < z {
|
if (math.MaxUint64-gas)/params.TxDataZeroGas < z {
|
||||||
return 0, vm.ErrOutOfGas
|
return 0, ErrGasUintOverflow
|
||||||
}
|
}
|
||||||
gas += z * params.TxDataZeroGas
|
gas += z * params.TxDataZeroGas
|
||||||
}
|
}
|
||||||
@ -132,7 +160,7 @@ func NewStateTransition(evm *vm.EVM, msg Message, gp *GasPool) *StateTransition
|
|||||||
// the gas used (which includes gas refunds) and an error if it failed. An error always
|
// the gas used (which includes gas refunds) and an error if it failed. An error always
|
||||||
// indicates a core error meaning that the message would always fail for that particular
|
// indicates a core error meaning that the message would always fail for that particular
|
||||||
// state and would never be accepted within a block.
|
// state and would never be accepted within a block.
|
||||||
func ApplyMessage(evm *vm.EVM, msg Message, gp *GasPool) ([]byte, uint64, bool, error) {
|
func ApplyMessage(evm *vm.EVM, msg Message, gp *GasPool) (*ExecutionResult, error) {
|
||||||
return NewStateTransition(evm, msg, gp).TransitionDb()
|
return NewStateTransition(evm, msg, gp).TransitionDb()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -144,19 +172,10 @@ func (st *StateTransition) to() common.Address {
|
|||||||
return *st.msg.To()
|
return *st.msg.To()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (st *StateTransition) useGas(amount uint64) error {
|
|
||||||
if st.gas < amount {
|
|
||||||
return vm.ErrOutOfGas
|
|
||||||
}
|
|
||||||
st.gas -= amount
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (st *StateTransition) buyGas() error {
|
func (st *StateTransition) buyGas() error {
|
||||||
mgval := new(big.Int).Mul(new(big.Int).SetUint64(st.msg.Gas()), st.gasPrice)
|
mgval := new(big.Int).Mul(new(big.Int).SetUint64(st.msg.Gas()), st.gasPrice)
|
||||||
if st.state.GetBalance(st.msg.From()).Cmp(mgval) < 0 {
|
if st.state.GetBalance(st.msg.From()).Cmp(mgval) < 0 {
|
||||||
return errInsufficientBalanceForGas
|
return ErrInsufficientFunds
|
||||||
}
|
}
|
||||||
if err := st.gp.SubGas(st.msg.Gas()); err != nil {
|
if err := st.gp.SubGas(st.msg.Gas()); err != nil {
|
||||||
return err
|
return err
|
||||||
@ -182,11 +201,32 @@ func (st *StateTransition) preCheck() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TransitionDb will transition the state by applying the current message and
|
// TransitionDb will transition the state by applying the current message and
|
||||||
// returning the result including the used gas. It returns an error if failed.
|
// returning the evm execution result with following fields.
|
||||||
// An error indicates a consensus issue.
|
//
|
||||||
func (st *StateTransition) TransitionDb() (ret []byte, usedGas uint64, failed bool, err error) {
|
// - used gas:
|
||||||
if err = st.preCheck(); err != nil {
|
// total gas used (including gas being refunded)
|
||||||
return
|
// - returndata:
|
||||||
|
// the returned data from evm
|
||||||
|
// - concrete execution error:
|
||||||
|
// various **EVM** error which aborts the execution,
|
||||||
|
// e.g. ErrOutOfGas, 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(gaslimit * gasprice)
|
||||||
|
// 3. the amount of gas required is available in the block
|
||||||
|
// 4. the purchased gas is enough to cover intrinsic usage
|
||||||
|
// 5. there is no overflow when calculating intrinsic gas
|
||||||
|
// 6. caller has enough balance to cover asset transfer for **topmost** call
|
||||||
|
|
||||||
|
// Check clauses 1-3, buy gas if everything is correct
|
||||||
|
if err := st.preCheck(); err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
msg := st.msg
|
msg := st.msg
|
||||||
sender := vm.AccountRef(msg.From())
|
sender := vm.AccountRef(msg.From())
|
||||||
@ -194,42 +234,39 @@ func (st *StateTransition) TransitionDb() (ret []byte, usedGas uint64, failed bo
|
|||||||
istanbul := st.evm.ChainConfig().IsIstanbul(st.evm.BlockNumber)
|
istanbul := st.evm.ChainConfig().IsIstanbul(st.evm.BlockNumber)
|
||||||
contractCreation := msg.To() == nil
|
contractCreation := msg.To() == nil
|
||||||
|
|
||||||
// Pay intrinsic gas
|
// Check clauses 4-5, subtract intrinsic gas if everything is correct
|
||||||
gas, err := IntrinsicGas(st.data, contractCreation, homestead, istanbul)
|
gas, err := IntrinsicGas(st.data, contractCreation, homestead, istanbul)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, 0, false, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if err = st.useGas(gas); err != nil {
|
if st.gas < gas {
|
||||||
return nil, 0, false, err
|
return nil, ErrIntrinsicGas
|
||||||
}
|
}
|
||||||
|
st.gas -= gas
|
||||||
|
|
||||||
|
// Check clause 6
|
||||||
|
if msg.Value().Sign() > 0 && !st.evm.CanTransfer(st.state, msg.From(), msg.Value()) {
|
||||||
|
return nil, ErrInsufficientFundsForTransfer
|
||||||
|
}
|
||||||
var (
|
var (
|
||||||
evm = st.evm
|
ret []byte
|
||||||
// vm errors do not effect consensus and are therefor
|
vmerr error // vm errors do not effect consensus and are therefore not assigned to err
|
||||||
// not assigned to err, except for insufficient balance
|
|
||||||
// error.
|
|
||||||
vmerr error
|
|
||||||
)
|
)
|
||||||
if contractCreation {
|
if contractCreation {
|
||||||
ret, _, st.gas, vmerr = evm.Create(sender, st.data, st.gas, st.value)
|
ret, _, st.gas, vmerr = st.evm.Create(sender, st.data, st.gas, st.value)
|
||||||
} else {
|
} else {
|
||||||
// Increment the nonce for the next transaction
|
// Increment the nonce for the next transaction
|
||||||
st.state.SetNonce(msg.From(), st.state.GetNonce(sender.Address())+1)
|
st.state.SetNonce(msg.From(), st.state.GetNonce(sender.Address())+1)
|
||||||
ret, st.gas, vmerr = evm.Call(sender, st.to(), st.data, st.gas, st.value)
|
ret, st.gas, vmerr = st.evm.Call(sender, st.to(), st.data, st.gas, 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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
st.refundGas()
|
st.refundGas()
|
||||||
st.state.AddBalance(st.evm.Coinbase, new(big.Int).Mul(new(big.Int).SetUint64(st.gasUsed()), st.gasPrice))
|
st.state.AddBalance(st.evm.Coinbase, new(big.Int).Mul(new(big.Int).SetUint64(st.gasUsed()), st.gasPrice))
|
||||||
|
|
||||||
return ret, st.gasUsed(), vmerr != nil, err
|
return &ExecutionResult{
|
||||||
|
UsedGas: st.gasUsed(),
|
||||||
|
Err: vmerr,
|
||||||
|
ReturnData: ret,
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (st *StateTransition) refundGas() {
|
func (st *StateTransition) refundGas() {
|
||||||
|
@ -59,10 +59,6 @@ var (
|
|||||||
// ErrInvalidSender is returned if the transaction contains an invalid signature.
|
// ErrInvalidSender is returned if the transaction contains an invalid signature.
|
||||||
ErrInvalidSender = errors.New("invalid sender")
|
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 gas price is below the minimum
|
// ErrUnderpriced is returned if a transaction's gas price is below the minimum
|
||||||
// configured for the transaction pool.
|
// configured for the transaction pool.
|
||||||
ErrUnderpriced = errors.New("transaction underpriced")
|
ErrUnderpriced = errors.New("transaction underpriced")
|
||||||
@ -71,14 +67,6 @@ var (
|
|||||||
// with a different one without the required price bump.
|
// with a different one without the required price bump.
|
||||||
ErrReplaceUnderpriced = errors.New("replacement transaction underpriced")
|
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 gas * price + value")
|
|
||||||
|
|
||||||
// ErrIntrinsicGas is returned if the transaction is specified to use less gas
|
|
||||||
// than required to start the invocation.
|
|
||||||
ErrIntrinsicGas = errors.New("intrinsic gas too low")
|
|
||||||
|
|
||||||
// ErrGasLimit is returned if a transaction's requested gas limit exceeds the
|
// ErrGasLimit is returned if a transaction's requested gas limit exceeds the
|
||||||
// maximum allowance of the current block.
|
// maximum allowance of the current block.
|
||||||
ErrGasLimit = errors.New("exceeds block gas limit")
|
ErrGasLimit = errors.New("exceeds block gas limit")
|
||||||
|
@ -16,15 +16,51 @@
|
|||||||
|
|
||||||
package vm
|
package vm
|
||||||
|
|
||||||
import "errors"
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
// List execution errors
|
// List evm execution errors
|
||||||
var (
|
var (
|
||||||
ErrOutOfGas = errors.New("out of gas")
|
ErrOutOfGas = errors.New("out of gas")
|
||||||
ErrCodeStoreOutOfGas = errors.New("contract creation code storage out of gas")
|
ErrCodeStoreOutOfGas = errors.New("contract creation code storage out of gas")
|
||||||
ErrDepth = errors.New("max call depth exceeded")
|
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")
|
ErrInsufficientBalance = errors.New("insufficient balance for transfer")
|
||||||
ErrContractAddressCollision = errors.New("contract address collision")
|
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")
|
||||||
|
ErrGasUintOverflow = errors.New("gas uint64 overflow")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// ErrStackUnderflow wraps an evm 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 evm 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 evm 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) }
|
||||||
|
@ -17,6 +17,7 @@
|
|||||||
package vm
|
package vm
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"math/big"
|
"math/big"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
@ -67,7 +68,7 @@ func run(evm *EVM, contract *Contract, input []byte, readOnly bool) ([]byte, err
|
|||||||
return interpreter.Run(contract, input, readOnly)
|
return interpreter.Run(contract, input, readOnly)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil, ErrNoCompatibleInterpreter
|
return nil, errors.New("no compatible interpreter")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Context provides the EVM with auxiliary information. Once provided
|
// Context provides the EVM with auxiliary information. Once provided
|
||||||
@ -190,7 +191,6 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas
|
|||||||
if evm.vmConfig.NoRecursion && evm.depth > 0 {
|
if evm.vmConfig.NoRecursion && evm.depth > 0 {
|
||||||
return nil, gas, nil
|
return nil, gas, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fail if we're trying to execute above the call depth limit
|
// Fail if we're trying to execute above the call depth limit
|
||||||
if evm.depth > int(params.CallCreateDepth) {
|
if evm.depth > int(params.CallCreateDepth) {
|
||||||
return nil, gas, ErrDepth
|
return nil, gas, ErrDepth
|
||||||
@ -199,7 +199,6 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas
|
|||||||
if !evm.Context.CanTransfer(evm.StateDB, caller.Address(), value) {
|
if !evm.Context.CanTransfer(evm.StateDB, caller.Address(), value) {
|
||||||
return nil, gas, ErrInsufficientBalance
|
return nil, gas, ErrInsufficientBalance
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
to = AccountRef(addr)
|
to = AccountRef(addr)
|
||||||
snapshot = evm.StateDB.Snapshot()
|
snapshot = evm.StateDB.Snapshot()
|
||||||
@ -246,7 +245,7 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas
|
|||||||
// when we're in homestead this also counts for code storage gas errors.
|
// when we're in homestead this also counts for code storage gas errors.
|
||||||
if err != nil {
|
if err != nil {
|
||||||
evm.StateDB.RevertToSnapshot(snapshot)
|
evm.StateDB.RevertToSnapshot(snapshot)
|
||||||
if err != errExecutionReverted {
|
if err != ErrExecutionReverted {
|
||||||
contract.UseGas(contract.Gas)
|
contract.UseGas(contract.Gas)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -264,16 +263,17 @@ func (evm *EVM) CallCode(caller ContractRef, addr common.Address, input []byte,
|
|||||||
if evm.vmConfig.NoRecursion && evm.depth > 0 {
|
if evm.vmConfig.NoRecursion && evm.depth > 0 {
|
||||||
return nil, gas, nil
|
return nil, gas, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fail if we're trying to execute above the call depth limit
|
// Fail if we're trying to execute above the call depth limit
|
||||||
if evm.depth > int(params.CallCreateDepth) {
|
if evm.depth > int(params.CallCreateDepth) {
|
||||||
return nil, gas, ErrDepth
|
return nil, gas, ErrDepth
|
||||||
}
|
}
|
||||||
// Fail if we're trying to transfer more than the available balance
|
// Fail if we're trying to transfer more than the available balance
|
||||||
if !evm.CanTransfer(evm.StateDB, caller.Address(), value) {
|
// Note although it's noop to transfer X ether 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 !evm.Context.CanTransfer(evm.StateDB, caller.Address(), value) {
|
||||||
return nil, gas, ErrInsufficientBalance
|
return nil, gas, ErrInsufficientBalance
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
snapshot = evm.StateDB.Snapshot()
|
snapshot = evm.StateDB.Snapshot()
|
||||||
to = AccountRef(caller.Address())
|
to = AccountRef(caller.Address())
|
||||||
@ -286,7 +286,7 @@ func (evm *EVM) CallCode(caller ContractRef, addr common.Address, input []byte,
|
|||||||
ret, err = run(evm, contract, input, false)
|
ret, err = run(evm, contract, input, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
evm.StateDB.RevertToSnapshot(snapshot)
|
evm.StateDB.RevertToSnapshot(snapshot)
|
||||||
if err != errExecutionReverted {
|
if err != ErrExecutionReverted {
|
||||||
contract.UseGas(contract.Gas)
|
contract.UseGas(contract.Gas)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -306,12 +306,10 @@ func (evm *EVM) DelegateCall(caller ContractRef, addr common.Address, input []by
|
|||||||
if evm.depth > int(params.CallCreateDepth) {
|
if evm.depth > int(params.CallCreateDepth) {
|
||||||
return nil, gas, ErrDepth
|
return nil, gas, ErrDepth
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
snapshot = evm.StateDB.Snapshot()
|
snapshot = evm.StateDB.Snapshot()
|
||||||
to = AccountRef(caller.Address())
|
to = AccountRef(caller.Address())
|
||||||
)
|
)
|
||||||
|
|
||||||
// Initialise a new contract and make initialise the delegate values
|
// Initialise a new contract and make initialise the delegate values
|
||||||
contract := NewContract(caller, to, nil, gas).AsDelegate()
|
contract := NewContract(caller, to, nil, gas).AsDelegate()
|
||||||
contract.SetCallCode(&addr, evm.StateDB.GetCodeHash(addr), evm.StateDB.GetCode(addr))
|
contract.SetCallCode(&addr, evm.StateDB.GetCodeHash(addr), evm.StateDB.GetCode(addr))
|
||||||
@ -319,7 +317,7 @@ func (evm *EVM) DelegateCall(caller ContractRef, addr common.Address, input []by
|
|||||||
ret, err = run(evm, contract, input, false)
|
ret, err = run(evm, contract, input, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
evm.StateDB.RevertToSnapshot(snapshot)
|
evm.StateDB.RevertToSnapshot(snapshot)
|
||||||
if err != errExecutionReverted {
|
if err != ErrExecutionReverted {
|
||||||
contract.UseGas(contract.Gas)
|
contract.UseGas(contract.Gas)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -338,7 +336,6 @@ func (evm *EVM) StaticCall(caller ContractRef, addr common.Address, input []byte
|
|||||||
if evm.depth > int(params.CallCreateDepth) {
|
if evm.depth > int(params.CallCreateDepth) {
|
||||||
return nil, gas, ErrDepth
|
return nil, gas, ErrDepth
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
to = AccountRef(addr)
|
to = AccountRef(addr)
|
||||||
snapshot = evm.StateDB.Snapshot()
|
snapshot = evm.StateDB.Snapshot()
|
||||||
@ -360,7 +357,7 @@ func (evm *EVM) StaticCall(caller ContractRef, addr common.Address, input []byte
|
|||||||
ret, err = run(evm, contract, input, true)
|
ret, err = run(evm, contract, input, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
evm.StateDB.RevertToSnapshot(snapshot)
|
evm.StateDB.RevertToSnapshot(snapshot)
|
||||||
if err != errExecutionReverted {
|
if err != ErrExecutionReverted {
|
||||||
contract.UseGas(contract.Gas)
|
contract.UseGas(contract.Gas)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -441,13 +438,13 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64,
|
|||||||
// when we're in homestead this also counts for code storage gas errors.
|
// when we're in homestead this also counts for code storage gas errors.
|
||||||
if maxCodeSizeExceeded || (err != nil && (evm.chainRules.IsHomestead || err != ErrCodeStoreOutOfGas)) {
|
if maxCodeSizeExceeded || (err != nil && (evm.chainRules.IsHomestead || err != ErrCodeStoreOutOfGas)) {
|
||||||
evm.StateDB.RevertToSnapshot(snapshot)
|
evm.StateDB.RevertToSnapshot(snapshot)
|
||||||
if err != errExecutionReverted {
|
if err != ErrExecutionReverted {
|
||||||
contract.UseGas(contract.Gas)
|
contract.UseGas(contract.Gas)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Assign err if contract code size exceeds the max while the err is still empty.
|
// Assign err if contract code size exceeds the max while the err is still empty.
|
||||||
if maxCodeSizeExceeded && err == nil {
|
if maxCodeSizeExceeded && err == nil {
|
||||||
err = errMaxCodeSizeExceeded
|
err = ErrMaxCodeSizeExceeded
|
||||||
}
|
}
|
||||||
if evm.vmConfig.Debug && evm.depth == 0 {
|
if evm.vmConfig.Debug && evm.depth == 0 {
|
||||||
evm.vmConfig.Tracer.CaptureEnd(ret, gas-contract.Gas, time.Since(start), err)
|
evm.vmConfig.Tracer.CaptureEnd(ret, gas-contract.Gas, time.Since(start), err)
|
||||||
|
@ -46,7 +46,7 @@ func callGas(isEip150 bool, availableGas, base uint64, callCost *big.Int) (uint6
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !callCost.IsUint64() {
|
if !callCost.IsUint64() {
|
||||||
return 0, errGasUintOverflow
|
return 0, ErrGasUintOverflow
|
||||||
}
|
}
|
||||||
|
|
||||||
return callCost.Uint64(), nil
|
return callCost.Uint64(), nil
|
||||||
|
@ -36,7 +36,7 @@ func memoryGasCost(mem *Memory, newMemSize uint64) (uint64, error) {
|
|||||||
// overflow. The constant 0x1FFFFFFFE0 is the highest number that can be used
|
// overflow. The constant 0x1FFFFFFFE0 is the highest number that can be used
|
||||||
// without overflowing the gas calculation.
|
// without overflowing the gas calculation.
|
||||||
if newMemSize > 0x1FFFFFFFE0 {
|
if newMemSize > 0x1FFFFFFFE0 {
|
||||||
return 0, errGasUintOverflow
|
return 0, ErrGasUintOverflow
|
||||||
}
|
}
|
||||||
newMemSizeWords := toWordSize(newMemSize)
|
newMemSizeWords := toWordSize(newMemSize)
|
||||||
newMemSize = newMemSizeWords * 32
|
newMemSize = newMemSizeWords * 32
|
||||||
@ -72,15 +72,15 @@ func memoryCopierGas(stackpos int) gasFunc {
|
|||||||
// And gas for copying data, charged per word at param.CopyGas
|
// And gas for copying data, charged per word at param.CopyGas
|
||||||
words, overflow := bigUint64(stack.Back(stackpos))
|
words, overflow := bigUint64(stack.Back(stackpos))
|
||||||
if overflow {
|
if overflow {
|
||||||
return 0, errGasUintOverflow
|
return 0, ErrGasUintOverflow
|
||||||
}
|
}
|
||||||
|
|
||||||
if words, overflow = math.SafeMul(toWordSize(words), params.CopyGas); overflow {
|
if words, overflow = math.SafeMul(toWordSize(words), params.CopyGas); overflow {
|
||||||
return 0, errGasUintOverflow
|
return 0, ErrGasUintOverflow
|
||||||
}
|
}
|
||||||
|
|
||||||
if gas, overflow = math.SafeAdd(gas, words); overflow {
|
if gas, overflow = math.SafeAdd(gas, words); overflow {
|
||||||
return 0, errGasUintOverflow
|
return 0, ErrGasUintOverflow
|
||||||
}
|
}
|
||||||
return gas, nil
|
return gas, nil
|
||||||
}
|
}
|
||||||
@ -221,7 +221,7 @@ func makeGasLog(n uint64) gasFunc {
|
|||||||
return func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
|
return func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
|
||||||
requestedSize, overflow := bigUint64(stack.Back(1))
|
requestedSize, overflow := bigUint64(stack.Back(1))
|
||||||
if overflow {
|
if overflow {
|
||||||
return 0, errGasUintOverflow
|
return 0, ErrGasUintOverflow
|
||||||
}
|
}
|
||||||
|
|
||||||
gas, err := memoryGasCost(mem, memorySize)
|
gas, err := memoryGasCost(mem, memorySize)
|
||||||
@ -230,18 +230,18 @@ func makeGasLog(n uint64) gasFunc {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if gas, overflow = math.SafeAdd(gas, params.LogGas); overflow {
|
if gas, overflow = math.SafeAdd(gas, params.LogGas); overflow {
|
||||||
return 0, errGasUintOverflow
|
return 0, ErrGasUintOverflow
|
||||||
}
|
}
|
||||||
if gas, overflow = math.SafeAdd(gas, n*params.LogTopicGas); overflow {
|
if gas, overflow = math.SafeAdd(gas, n*params.LogTopicGas); overflow {
|
||||||
return 0, errGasUintOverflow
|
return 0, ErrGasUintOverflow
|
||||||
}
|
}
|
||||||
|
|
||||||
var memorySizeGas uint64
|
var memorySizeGas uint64
|
||||||
if memorySizeGas, overflow = math.SafeMul(requestedSize, params.LogDataGas); overflow {
|
if memorySizeGas, overflow = math.SafeMul(requestedSize, params.LogDataGas); overflow {
|
||||||
return 0, errGasUintOverflow
|
return 0, ErrGasUintOverflow
|
||||||
}
|
}
|
||||||
if gas, overflow = math.SafeAdd(gas, memorySizeGas); overflow {
|
if gas, overflow = math.SafeAdd(gas, memorySizeGas); overflow {
|
||||||
return 0, errGasUintOverflow
|
return 0, ErrGasUintOverflow
|
||||||
}
|
}
|
||||||
return gas, nil
|
return gas, nil
|
||||||
}
|
}
|
||||||
@ -254,13 +254,13 @@ func gasSha3(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize
|
|||||||
}
|
}
|
||||||
wordGas, overflow := bigUint64(stack.Back(1))
|
wordGas, overflow := bigUint64(stack.Back(1))
|
||||||
if overflow {
|
if overflow {
|
||||||
return 0, errGasUintOverflow
|
return 0, ErrGasUintOverflow
|
||||||
}
|
}
|
||||||
if wordGas, overflow = math.SafeMul(toWordSize(wordGas), params.Sha3WordGas); overflow {
|
if wordGas, overflow = math.SafeMul(toWordSize(wordGas), params.Sha3WordGas); overflow {
|
||||||
return 0, errGasUintOverflow
|
return 0, ErrGasUintOverflow
|
||||||
}
|
}
|
||||||
if gas, overflow = math.SafeAdd(gas, wordGas); overflow {
|
if gas, overflow = math.SafeAdd(gas, wordGas); overflow {
|
||||||
return 0, errGasUintOverflow
|
return 0, ErrGasUintOverflow
|
||||||
}
|
}
|
||||||
return gas, nil
|
return gas, nil
|
||||||
}
|
}
|
||||||
@ -288,13 +288,13 @@ func gasCreate2(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memoryS
|
|||||||
}
|
}
|
||||||
wordGas, overflow := bigUint64(stack.Back(2))
|
wordGas, overflow := bigUint64(stack.Back(2))
|
||||||
if overflow {
|
if overflow {
|
||||||
return 0, errGasUintOverflow
|
return 0, ErrGasUintOverflow
|
||||||
}
|
}
|
||||||
if wordGas, overflow = math.SafeMul(toWordSize(wordGas), params.Sha3WordGas); overflow {
|
if wordGas, overflow = math.SafeMul(toWordSize(wordGas), params.Sha3WordGas); overflow {
|
||||||
return 0, errGasUintOverflow
|
return 0, ErrGasUintOverflow
|
||||||
}
|
}
|
||||||
if gas, overflow = math.SafeAdd(gas, wordGas); overflow {
|
if gas, overflow = math.SafeAdd(gas, wordGas); overflow {
|
||||||
return 0, errGasUintOverflow
|
return 0, ErrGasUintOverflow
|
||||||
}
|
}
|
||||||
return gas, nil
|
return gas, nil
|
||||||
}
|
}
|
||||||
@ -307,7 +307,7 @@ func gasExpFrontier(evm *EVM, contract *Contract, stack *Stack, mem *Memory, mem
|
|||||||
overflow bool
|
overflow bool
|
||||||
)
|
)
|
||||||
if gas, overflow = math.SafeAdd(gas, params.ExpGas); overflow {
|
if gas, overflow = math.SafeAdd(gas, params.ExpGas); overflow {
|
||||||
return 0, errGasUintOverflow
|
return 0, ErrGasUintOverflow
|
||||||
}
|
}
|
||||||
return gas, nil
|
return gas, nil
|
||||||
}
|
}
|
||||||
@ -320,7 +320,7 @@ func gasExpEIP158(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memor
|
|||||||
overflow bool
|
overflow bool
|
||||||
)
|
)
|
||||||
if gas, overflow = math.SafeAdd(gas, params.ExpGas); overflow {
|
if gas, overflow = math.SafeAdd(gas, params.ExpGas); overflow {
|
||||||
return 0, errGasUintOverflow
|
return 0, ErrGasUintOverflow
|
||||||
}
|
}
|
||||||
return gas, nil
|
return gas, nil
|
||||||
}
|
}
|
||||||
@ -347,7 +347,7 @@ func gasCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize
|
|||||||
}
|
}
|
||||||
var overflow bool
|
var overflow bool
|
||||||
if gas, overflow = math.SafeAdd(gas, memoryGas); overflow {
|
if gas, overflow = math.SafeAdd(gas, memoryGas); overflow {
|
||||||
return 0, errGasUintOverflow
|
return 0, ErrGasUintOverflow
|
||||||
}
|
}
|
||||||
|
|
||||||
evm.callGasTemp, err = callGas(evm.chainRules.IsEIP150, contract.Gas, gas, stack.Back(0))
|
evm.callGasTemp, err = callGas(evm.chainRules.IsEIP150, contract.Gas, gas, stack.Back(0))
|
||||||
@ -355,7 +355,7 @@ func gasCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize
|
|||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
if gas, overflow = math.SafeAdd(gas, evm.callGasTemp); overflow {
|
if gas, overflow = math.SafeAdd(gas, evm.callGasTemp); overflow {
|
||||||
return 0, errGasUintOverflow
|
return 0, ErrGasUintOverflow
|
||||||
}
|
}
|
||||||
return gas, nil
|
return gas, nil
|
||||||
}
|
}
|
||||||
@ -373,14 +373,14 @@ func gasCallCode(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memory
|
|||||||
gas += params.CallValueTransferGas
|
gas += params.CallValueTransferGas
|
||||||
}
|
}
|
||||||
if gas, overflow = math.SafeAdd(gas, memoryGas); overflow {
|
if gas, overflow = math.SafeAdd(gas, memoryGas); overflow {
|
||||||
return 0, errGasUintOverflow
|
return 0, ErrGasUintOverflow
|
||||||
}
|
}
|
||||||
evm.callGasTemp, err = callGas(evm.chainRules.IsEIP150, contract.Gas, gas, stack.Back(0))
|
evm.callGasTemp, err = callGas(evm.chainRules.IsEIP150, contract.Gas, gas, stack.Back(0))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
if gas, overflow = math.SafeAdd(gas, evm.callGasTemp); overflow {
|
if gas, overflow = math.SafeAdd(gas, evm.callGasTemp); overflow {
|
||||||
return 0, errGasUintOverflow
|
return 0, ErrGasUintOverflow
|
||||||
}
|
}
|
||||||
return gas, nil
|
return gas, nil
|
||||||
}
|
}
|
||||||
@ -396,7 +396,7 @@ func gasDelegateCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, me
|
|||||||
}
|
}
|
||||||
var overflow bool
|
var overflow bool
|
||||||
if gas, overflow = math.SafeAdd(gas, evm.callGasTemp); overflow {
|
if gas, overflow = math.SafeAdd(gas, evm.callGasTemp); overflow {
|
||||||
return 0, errGasUintOverflow
|
return 0, ErrGasUintOverflow
|
||||||
}
|
}
|
||||||
return gas, nil
|
return gas, nil
|
||||||
}
|
}
|
||||||
@ -412,7 +412,7 @@ func gasStaticCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memo
|
|||||||
}
|
}
|
||||||
var overflow bool
|
var overflow bool
|
||||||
if gas, overflow = math.SafeAdd(gas, evm.callGasTemp); overflow {
|
if gas, overflow = math.SafeAdd(gas, evm.callGasTemp); overflow {
|
||||||
return 0, errGasUintOverflow
|
return 0, ErrGasUintOverflow
|
||||||
}
|
}
|
||||||
return gas, nil
|
return gas, nil
|
||||||
}
|
}
|
||||||
|
@ -39,8 +39,8 @@ func TestMemoryGasCost(t *testing.T) {
|
|||||||
}
|
}
|
||||||
for i, tt := range tests {
|
for i, tt := range tests {
|
||||||
v, err := memoryGasCost(&Memory{}, tt.size)
|
v, err := memoryGasCost(&Memory{}, tt.size)
|
||||||
if (err == errGasUintOverflow) != tt.overflow {
|
if (err == ErrGasUintOverflow) != tt.overflow {
|
||||||
t.Errorf("test %d: overflow mismatch: have %v, want %v", i, err == errGasUintOverflow, tt.overflow)
|
t.Errorf("test %d: overflow mismatch: have %v, want %v", i, err == ErrGasUintOverflow, tt.overflow)
|
||||||
}
|
}
|
||||||
if v != tt.cost {
|
if v != tt.cost {
|
||||||
t.Errorf("test %d: gas cost mismatch: have %v, want %v", i, v, tt.cost)
|
t.Errorf("test %d: gas cost mismatch: have %v, want %v", i, v, tt.cost)
|
||||||
|
@ -17,7 +17,6 @@
|
|||||||
package vm
|
package vm
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"math/big"
|
"math/big"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
@ -28,13 +27,8 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
bigZero = new(big.Int)
|
bigZero = new(big.Int)
|
||||||
tt255 = math.BigPow(2, 255)
|
tt255 = math.BigPow(2, 255)
|
||||||
errWriteProtection = errors.New("evm: write protection")
|
|
||||||
errReturnDataOutOfBounds = errors.New("evm: return data out of bounds")
|
|
||||||
errExecutionReverted = errors.New("evm: execution reverted")
|
|
||||||
errMaxCodeSizeExceeded = errors.New("evm: max code size exceeded")
|
|
||||||
errInvalidJump = errors.New("evm: invalid jump destination")
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func opAdd(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) {
|
func opAdd(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) {
|
||||||
@ -468,7 +462,7 @@ func opReturnDataCopy(pc *uint64, interpreter *EVMInterpreter, callContext *call
|
|||||||
defer interpreter.intPool.put(memOffset, dataOffset, length, end)
|
defer interpreter.intPool.put(memOffset, dataOffset, length, end)
|
||||||
|
|
||||||
if !end.IsUint64() || uint64(len(interpreter.returnData)) < end.Uint64() {
|
if !end.IsUint64() || uint64(len(interpreter.returnData)) < end.Uint64() {
|
||||||
return nil, errReturnDataOutOfBounds
|
return nil, ErrReturnDataOutOfBounds
|
||||||
}
|
}
|
||||||
callContext.memory.Set(memOffset.Uint64(), length.Uint64(), interpreter.returnData[dataOffset.Uint64():end.Uint64()])
|
callContext.memory.Set(memOffset.Uint64(), length.Uint64(), interpreter.returnData[dataOffset.Uint64():end.Uint64()])
|
||||||
|
|
||||||
@ -643,7 +637,7 @@ func opSstore(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]
|
|||||||
func opJump(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) {
|
func opJump(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) {
|
||||||
pos := callContext.stack.pop()
|
pos := callContext.stack.pop()
|
||||||
if !callContext.contract.validJumpdest(pos) {
|
if !callContext.contract.validJumpdest(pos) {
|
||||||
return nil, errInvalidJump
|
return nil, ErrInvalidJump
|
||||||
}
|
}
|
||||||
*pc = pos.Uint64()
|
*pc = pos.Uint64()
|
||||||
|
|
||||||
@ -655,7 +649,7 @@ func opJumpi(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]b
|
|||||||
pos, cond := callContext.stack.pop(), callContext.stack.pop()
|
pos, cond := callContext.stack.pop(), callContext.stack.pop()
|
||||||
if cond.Sign() != 0 {
|
if cond.Sign() != 0 {
|
||||||
if !callContext.contract.validJumpdest(pos) {
|
if !callContext.contract.validJumpdest(pos) {
|
||||||
return nil, errInvalidJump
|
return nil, ErrInvalidJump
|
||||||
}
|
}
|
||||||
*pc = pos.Uint64()
|
*pc = pos.Uint64()
|
||||||
} else {
|
} else {
|
||||||
@ -712,7 +706,7 @@ func opCreate(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]
|
|||||||
callContext.contract.Gas += returnGas
|
callContext.contract.Gas += returnGas
|
||||||
interpreter.intPool.put(value, offset, size)
|
interpreter.intPool.put(value, offset, size)
|
||||||
|
|
||||||
if suberr == errExecutionReverted {
|
if suberr == ErrExecutionReverted {
|
||||||
return res, nil
|
return res, nil
|
||||||
}
|
}
|
||||||
return nil, nil
|
return nil, nil
|
||||||
@ -740,7 +734,7 @@ func opCreate2(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([
|
|||||||
callContext.contract.Gas += returnGas
|
callContext.contract.Gas += returnGas
|
||||||
interpreter.intPool.put(endowment, offset, size, salt)
|
interpreter.intPool.put(endowment, offset, size, salt)
|
||||||
|
|
||||||
if suberr == errExecutionReverted {
|
if suberr == ErrExecutionReverted {
|
||||||
return res, nil
|
return res, nil
|
||||||
}
|
}
|
||||||
return nil, nil
|
return nil, nil
|
||||||
@ -766,7 +760,7 @@ func opCall(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]by
|
|||||||
} else {
|
} else {
|
||||||
callContext.stack.push(interpreter.intPool.get().SetUint64(1))
|
callContext.stack.push(interpreter.intPool.get().SetUint64(1))
|
||||||
}
|
}
|
||||||
if err == nil || err == errExecutionReverted {
|
if err == nil || err == ErrExecutionReverted {
|
||||||
callContext.memory.Set(retOffset.Uint64(), retSize.Uint64(), ret)
|
callContext.memory.Set(retOffset.Uint64(), retSize.Uint64(), ret)
|
||||||
}
|
}
|
||||||
callContext.contract.Gas += returnGas
|
callContext.contract.Gas += returnGas
|
||||||
@ -795,7 +789,7 @@ func opCallCode(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) (
|
|||||||
} else {
|
} else {
|
||||||
callContext.stack.push(interpreter.intPool.get().SetUint64(1))
|
callContext.stack.push(interpreter.intPool.get().SetUint64(1))
|
||||||
}
|
}
|
||||||
if err == nil || err == errExecutionReverted {
|
if err == nil || err == ErrExecutionReverted {
|
||||||
callContext.memory.Set(retOffset.Uint64(), retSize.Uint64(), ret)
|
callContext.memory.Set(retOffset.Uint64(), retSize.Uint64(), ret)
|
||||||
}
|
}
|
||||||
callContext.contract.Gas += returnGas
|
callContext.contract.Gas += returnGas
|
||||||
@ -820,7 +814,7 @@ func opDelegateCall(pc *uint64, interpreter *EVMInterpreter, callContext *callCt
|
|||||||
} else {
|
} else {
|
||||||
callContext.stack.push(interpreter.intPool.get().SetUint64(1))
|
callContext.stack.push(interpreter.intPool.get().SetUint64(1))
|
||||||
}
|
}
|
||||||
if err == nil || err == errExecutionReverted {
|
if err == nil || err == ErrExecutionReverted {
|
||||||
callContext.memory.Set(retOffset.Uint64(), retSize.Uint64(), ret)
|
callContext.memory.Set(retOffset.Uint64(), retSize.Uint64(), ret)
|
||||||
}
|
}
|
||||||
callContext.contract.Gas += returnGas
|
callContext.contract.Gas += returnGas
|
||||||
@ -845,7 +839,7 @@ func opStaticCall(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx)
|
|||||||
} else {
|
} else {
|
||||||
callContext.stack.push(interpreter.intPool.get().SetUint64(1))
|
callContext.stack.push(interpreter.intPool.get().SetUint64(1))
|
||||||
}
|
}
|
||||||
if err == nil || err == errExecutionReverted {
|
if err == nil || err == ErrExecutionReverted {
|
||||||
callContext.memory.Set(retOffset.Uint64(), retSize.Uint64(), ret)
|
callContext.memory.Set(retOffset.Uint64(), retSize.Uint64(), ret)
|
||||||
}
|
}
|
||||||
callContext.contract.Gas += returnGas
|
callContext.contract.Gas += returnGas
|
||||||
|
@ -17,7 +17,6 @@
|
|||||||
package vm
|
package vm
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"hash"
|
"hash"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
|
|
||||||
@ -137,7 +136,7 @@ func NewEVMInterpreter(evm *EVM, cfg Config) *EVMInterpreter {
|
|||||||
//
|
//
|
||||||
// It's important to note that any errors returned by the interpreter should be
|
// It's important to note that any errors returned by the interpreter should be
|
||||||
// considered a revert-and-consume-all-gas operation except for
|
// considered a revert-and-consume-all-gas operation except for
|
||||||
// errExecutionReverted which means revert-and-keep-gas-left.
|
// ErrExecutionReverted which means revert-and-keep-gas-left.
|
||||||
func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (ret []byte, err error) {
|
func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (ret []byte, err error) {
|
||||||
if in.intPool == nil {
|
if in.intPool == nil {
|
||||||
in.intPool = poolOfIntPools.get()
|
in.intPool = poolOfIntPools.get()
|
||||||
@ -223,13 +222,13 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (
|
|||||||
op = contract.GetOp(pc)
|
op = contract.GetOp(pc)
|
||||||
operation := in.cfg.JumpTable[op]
|
operation := in.cfg.JumpTable[op]
|
||||||
if !operation.valid {
|
if !operation.valid {
|
||||||
return nil, fmt.Errorf("invalid opcode 0x%x", int(op))
|
return nil, &ErrInvalidOpCode{opcode: op}
|
||||||
}
|
}
|
||||||
// Validate stack
|
// Validate stack
|
||||||
if sLen := stack.len(); sLen < operation.minStack {
|
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 {
|
} 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 the operation is valid, enforce and write restrictions
|
||||||
if in.readOnly && in.evm.chainRules.IsByzantium {
|
if in.readOnly && in.evm.chainRules.IsByzantium {
|
||||||
@ -239,7 +238,7 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (
|
|||||||
// account to the others means the state is modified and should also
|
// account to the others means the state is modified and should also
|
||||||
// return with an error.
|
// return with an error.
|
||||||
if operation.writes || (op == CALL && stack.Back(2).Sign() != 0) {
|
if operation.writes || (op == CALL && stack.Back(2).Sign() != 0) {
|
||||||
return nil, errWriteProtection
|
return nil, ErrWriteProtection
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Static portion of gas
|
// Static portion of gas
|
||||||
@ -256,12 +255,12 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (
|
|||||||
if operation.memorySize != nil {
|
if operation.memorySize != nil {
|
||||||
memSize, overflow := operation.memorySize(stack)
|
memSize, overflow := operation.memorySize(stack)
|
||||||
if overflow {
|
if overflow {
|
||||||
return nil, errGasUintOverflow
|
return nil, ErrGasUintOverflow
|
||||||
}
|
}
|
||||||
// memory is expanded in words of 32 bytes. Gas
|
// memory is expanded in words of 32 bytes. Gas
|
||||||
// is also calculated in words.
|
// is also calculated in words.
|
||||||
if memorySize, overflow = math.SafeMul(toWordSize(memSize), 32); overflow {
|
if memorySize, overflow = math.SafeMul(toWordSize(memSize), 32); overflow {
|
||||||
return nil, errGasUintOverflow
|
return nil, ErrGasUintOverflow
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Dynamic portion of gas
|
// Dynamic portion of gas
|
||||||
@ -301,7 +300,7 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (
|
|||||||
case err != nil:
|
case err != nil:
|
||||||
return nil, err
|
return nil, err
|
||||||
case operation.reverts:
|
case operation.reverts:
|
||||||
return res, errExecutionReverted
|
return res, ErrExecutionReverted
|
||||||
case operation.halts:
|
case operation.halts:
|
||||||
return res, nil
|
return res, nil
|
||||||
case !operation.jumps:
|
case !operation.jumps:
|
||||||
|
@ -17,8 +17,6 @@
|
|||||||
package vm
|
package vm
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/params"
|
"github.com/ethereum/go-ethereum/params"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -29,8 +27,6 @@ type (
|
|||||||
memorySizeFunc func(*Stack) (size uint64, overflow bool)
|
memorySizeFunc func(*Stack) (size uint64, overflow bool)
|
||||||
)
|
)
|
||||||
|
|
||||||
var errGasUintOverflow = errors.New("gas uint64 overflow")
|
|
||||||
|
|
||||||
type operation struct {
|
type operation struct {
|
||||||
// execute is the operation function
|
// execute is the operation function
|
||||||
execute executionFunc
|
execute executionFunc
|
||||||
|
@ -18,6 +18,7 @@ package vm
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"math/big"
|
"math/big"
|
||||||
@ -29,6 +30,8 @@ import (
|
|||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var errTraceLimitReached = errors.New("the number of logs reached the specified limit")
|
||||||
|
|
||||||
// Storage represents a contract's storage.
|
// Storage represents a contract's storage.
|
||||||
type Storage map[common.Hash]common.Hash
|
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 *EVM, pc uint64, op OpCode, gas, cost uint64, memory *Memory, stack *Stack, contract *Contract, depth int, err error) error {
|
func (l *StructLogger) CaptureState(env *EVM, pc uint64, op OpCode, gas, cost uint64, memory *Memory, stack *Stack, contract *Contract, depth int, err error) error {
|
||||||
// check if already accumulated the specified number of logs
|
// check if already accumulated the specified number of logs
|
||||||
if l.cfg.Limit != 0 && l.cfg.Limit <= len(l.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
|
// initialise new changed values storage container for this contract
|
||||||
|
@ -389,7 +389,7 @@ var opCodeToString = map[OpCode]string{
|
|||||||
func (op OpCode) String() string {
|
func (op OpCode) String() string {
|
||||||
str := opCodeToString[op]
|
str := opCodeToString[op]
|
||||||
if len(str) == 0 {
|
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
|
return str
|
||||||
|
@ -502,7 +502,7 @@ func (api *PrivateDebugAPI) traceBlock(ctx context.Context, block *types.Block,
|
|||||||
vmctx := core.NewEVMContext(msg, block.Header(), api.eth.blockchain, nil)
|
vmctx := core.NewEVMContext(msg, block.Header(), api.eth.blockchain, nil)
|
||||||
|
|
||||||
vmenv := vm.NewEVM(vmctx, statedb, api.eth.blockchain.Config(), vm.Config{})
|
vmenv := vm.NewEVM(vmctx, statedb, api.eth.blockchain.Config(), vm.Config{})
|
||||||
if _, _, _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(msg.Gas())); err != nil {
|
if _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(msg.Gas())); err != nil {
|
||||||
failed = err
|
failed = err
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@ -596,7 +596,7 @@ func (api *PrivateDebugAPI) standardTraceBlockToFile(ctx context.Context, block
|
|||||||
}
|
}
|
||||||
// Execute the transaction and flush any traces to disk
|
// Execute the transaction and flush any traces to disk
|
||||||
vmenv := vm.NewEVM(vmctx, statedb, api.eth.blockchain.Config(), vmConf)
|
vmenv := vm.NewEVM(vmctx, statedb, api.eth.blockchain.Config(), vmConf)
|
||||||
_, _, _, err = core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(msg.Gas()))
|
_, err = core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(msg.Gas()))
|
||||||
if writer != nil {
|
if writer != nil {
|
||||||
writer.Flush()
|
writer.Flush()
|
||||||
}
|
}
|
||||||
@ -758,7 +758,7 @@ func (api *PrivateDebugAPI) traceTx(ctx context.Context, message core.Message, v
|
|||||||
// Run the transaction with tracing enabled.
|
// Run the transaction with tracing enabled.
|
||||||
vmenv := vm.NewEVM(vmctx, statedb, api.eth.blockchain.Config(), vm.Config{Debug: true, Tracer: tracer})
|
vmenv := vm.NewEVM(vmctx, statedb, api.eth.blockchain.Config(), vm.Config{Debug: true, Tracer: tracer})
|
||||||
|
|
||||||
ret, gas, failed, err := core.ApplyMessage(vmenv, message, new(core.GasPool).AddGas(message.Gas()))
|
result, err := core.ApplyMessage(vmenv, message, new(core.GasPool).AddGas(message.Gas()))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("tracing failed: %v", err)
|
return nil, fmt.Errorf("tracing failed: %v", err)
|
||||||
}
|
}
|
||||||
@ -766,9 +766,9 @@ func (api *PrivateDebugAPI) traceTx(ctx context.Context, message core.Message, v
|
|||||||
switch tracer := tracer.(type) {
|
switch tracer := tracer.(type) {
|
||||||
case *vm.StructLogger:
|
case *vm.StructLogger:
|
||||||
return ðapi.ExecutionResult{
|
return ðapi.ExecutionResult{
|
||||||
Gas: gas,
|
Gas: result.UsedGas,
|
||||||
Failed: failed,
|
Failed: result.Failed(),
|
||||||
ReturnValue: fmt.Sprintf("%x", ret),
|
ReturnValue: fmt.Sprintf("%x", result.Return()),
|
||||||
StructLogs: ethapi.FormatLogs(tracer.StructLogs()),
|
StructLogs: ethapi.FormatLogs(tracer.StructLogs()),
|
||||||
}, nil
|
}, nil
|
||||||
|
|
||||||
@ -812,7 +812,7 @@ func (api *PrivateDebugAPI) computeTxEnv(blockHash common.Hash, txIndex int, ree
|
|||||||
}
|
}
|
||||||
// Not yet the searched for transaction, execute on top of the current state
|
// Not yet the searched for transaction, execute on top of the current state
|
||||||
vmenv := vm.NewEVM(context, statedb, api.eth.blockchain.Config(), vm.Config{})
|
vmenv := vm.NewEVM(context, statedb, api.eth.blockchain.Config(), vm.Config{})
|
||||||
if _, _, _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(tx.Gas())); err != nil {
|
if _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(tx.Gas())); err != nil {
|
||||||
return nil, vm.Context{}, nil, fmt.Errorf("transaction %#x failed: %v", tx.Hash(), err)
|
return nil, vm.Context{}, nil, fmt.Errorf("transaction %#x failed: %v", tx.Hash(), err)
|
||||||
}
|
}
|
||||||
// Ensure any modifications are committed to the state
|
// Ensure any modifications are committed to the state
|
||||||
|
@ -65,7 +65,7 @@
|
|||||||
"value": "0x0"
|
"value": "0x0"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"error": "evm: invalid jump destination",
|
"error": "invalid jump destination",
|
||||||
"from": "0xe4a13bc304682a903e9472f469c33801dd18d9e8",
|
"from": "0xe4a13bc304682a903e9472f469c33801dd18d9e8",
|
||||||
"gas": "0x435c8",
|
"gas": "0x435c8",
|
||||||
"gasUsed": "0x435c8",
|
"gasUsed": "0x435c8",
|
||||||
|
@ -59,7 +59,7 @@
|
|||||||
"result": {
|
"result": {
|
||||||
"calls": [
|
"calls": [
|
||||||
{
|
{
|
||||||
"error": "invalid opcode 0xfe",
|
"error": "invalid opcode: opcode 0xfe not defined",
|
||||||
"from": "0x33056b5dcac09a9b4becad0e1dcf92c19bd0af76",
|
"from": "0x33056b5dcac09a9b4becad0e1dcf92c19bd0af76",
|
||||||
"gas": "0x75fe3",
|
"gas": "0x75fe3",
|
||||||
"gasUsed": "0x75fe3",
|
"gasUsed": "0x75fe3",
|
||||||
|
2
eth/tracers/testdata/call_tracer_throw.json
vendored
2
eth/tracers/testdata/call_tracer_throw.json
vendored
@ -50,7 +50,7 @@
|
|||||||
},
|
},
|
||||||
"input": "0xf88b8206668504a817c8008303d09094c212e03b9e060e36facad5fd8f4435412ca22e6b80a451a34eb8000000000000000000000000000000000000000000000027fad02094277c000029a0692a3b4e7b2842f8dd7832e712c21e09f451f416c8976d5b8d02e8c0c2b4bea9a07645e90fc421b63dd755767fd93d3c03b4ec0c4d8fafa059558d08cf11d59750",
|
"input": "0xf88b8206668504a817c8008303d09094c212e03b9e060e36facad5fd8f4435412ca22e6b80a451a34eb8000000000000000000000000000000000000000000000027fad02094277c000029a0692a3b4e7b2842f8dd7832e712c21e09f451f416c8976d5b8d02e8c0c2b4bea9a07645e90fc421b63dd755767fd93d3c03b4ec0c4d8fafa059558d08cf11d59750",
|
||||||
"result": {
|
"result": {
|
||||||
"error": "evm: invalid jump destination",
|
"error": "invalid jump destination",
|
||||||
"from": "0x70c9217d814985faef62b124420f8dfbddd96433",
|
"from": "0x70c9217d814985faef62b124420f8dfbddd96433",
|
||||||
"gas": "0x37b38",
|
"gas": "0x37b38",
|
||||||
"gasUsed": "0x37b38",
|
"gasUsed": "0x37b38",
|
||||||
|
@ -182,7 +182,7 @@ func TestPrestateTracerCreate2(t *testing.T) {
|
|||||||
t.Fatalf("failed to prepare transaction for tracing: %v", err)
|
t.Fatalf("failed to prepare transaction for tracing: %v", err)
|
||||||
}
|
}
|
||||||
st := core.NewStateTransition(evm, msg, new(core.GasPool).AddGas(tx.Gas()))
|
st := core.NewStateTransition(evm, msg, new(core.GasPool).AddGas(tx.Gas()))
|
||||||
if _, _, _, err = st.TransitionDb(); err != nil {
|
if _, err = st.TransitionDb(); err != nil {
|
||||||
t.Fatalf("failed to execute transaction: %v", err)
|
t.Fatalf("failed to execute transaction: %v", err)
|
||||||
}
|
}
|
||||||
// Retrieve the trace result and compare against the etalon
|
// Retrieve the trace result and compare against the etalon
|
||||||
@ -256,7 +256,7 @@ func TestCallTracer(t *testing.T) {
|
|||||||
t.Fatalf("failed to prepare transaction for tracing: %v", err)
|
t.Fatalf("failed to prepare transaction for tracing: %v", err)
|
||||||
}
|
}
|
||||||
st := core.NewStateTransition(evm, msg, new(core.GasPool).AddGas(tx.Gas()))
|
st := core.NewStateTransition(evm, msg, new(core.GasPool).AddGas(tx.Gas()))
|
||||||
if _, _, _, err = st.TransitionDb(); err != nil {
|
if _, err = st.TransitionDb(); err != nil {
|
||||||
t.Fatalf("failed to execute transaction: %v", err)
|
t.Fatalf("failed to execute transaction: %v", err)
|
||||||
}
|
}
|
||||||
// Retrieve the trace result and compare against the etalon
|
// Retrieve the trace result and compare against the etalon
|
||||||
|
@ -803,16 +803,19 @@ func (b *Block) Call(ctx context.Context, args struct {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
result, gas, failed, err := ethapi.DoCall(ctx, b.backend, args.Data, *b.numberOrHash, nil, vm.Config{}, 5*time.Second, b.backend.RPCGasCap())
|
result, err := ethapi.DoCall(ctx, b.backend, args.Data, *b.numberOrHash, nil, vm.Config{}, 5*time.Second, b.backend.RPCGasCap())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
status := hexutil.Uint64(1)
|
status := hexutil.Uint64(1)
|
||||||
if failed {
|
if result.Failed() {
|
||||||
status = 0
|
status = 0
|
||||||
}
|
}
|
||||||
return &CallResult{
|
return &CallResult{
|
||||||
data: hexutil.Bytes(result),
|
data: result.Return(),
|
||||||
gasUsed: hexutil.Uint64(gas),
|
gasUsed: hexutil.Uint64(result.UsedGas),
|
||||||
status: status,
|
status: status,
|
||||||
}, err
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Block) EstimateGas(ctx context.Context, args struct {
|
func (b *Block) EstimateGas(ctx context.Context, args struct {
|
||||||
@ -869,16 +872,19 @@ func (p *Pending) Call(ctx context.Context, args struct {
|
|||||||
Data ethapi.CallArgs
|
Data ethapi.CallArgs
|
||||||
}) (*CallResult, error) {
|
}) (*CallResult, error) {
|
||||||
pendingBlockNr := rpc.BlockNumberOrHashWithNumber(rpc.PendingBlockNumber)
|
pendingBlockNr := rpc.BlockNumberOrHashWithNumber(rpc.PendingBlockNumber)
|
||||||
result, gas, failed, err := ethapi.DoCall(ctx, p.backend, args.Data, pendingBlockNr, nil, vm.Config{}, 5*time.Second, p.backend.RPCGasCap())
|
result, err := ethapi.DoCall(ctx, p.backend, args.Data, pendingBlockNr, nil, vm.Config{}, 5*time.Second, p.backend.RPCGasCap())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
status := hexutil.Uint64(1)
|
status := hexutil.Uint64(1)
|
||||||
if failed {
|
if result.Failed() {
|
||||||
status = 0
|
status = 0
|
||||||
}
|
}
|
||||||
return &CallResult{
|
return &CallResult{
|
||||||
data: hexutil.Bytes(result),
|
data: result.Return(),
|
||||||
gasUsed: hexutil.Uint64(gas),
|
gasUsed: hexutil.Uint64(result.UsedGas),
|
||||||
status: status,
|
status: status,
|
||||||
}, err
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Pending) EstimateGas(ctx context.Context, args struct {
|
func (p *Pending) EstimateGas(ctx context.Context, args struct {
|
||||||
|
@ -27,6 +27,7 @@ import (
|
|||||||
|
|
||||||
"github.com/davecgh/go-spew/spew"
|
"github.com/davecgh/go-spew/spew"
|
||||||
"github.com/ethereum/go-ethereum/accounts"
|
"github.com/ethereum/go-ethereum/accounts"
|
||||||
|
"github.com/ethereum/go-ethereum/accounts/abi"
|
||||||
"github.com/ethereum/go-ethereum/accounts/keystore"
|
"github.com/ethereum/go-ethereum/accounts/keystore"
|
||||||
"github.com/ethereum/go-ethereum/accounts/scwallet"
|
"github.com/ethereum/go-ethereum/accounts/scwallet"
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
@ -789,14 +790,13 @@ type account struct {
|
|||||||
StateDiff *map[common.Hash]common.Hash `json:"stateDiff"`
|
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, globalGasCap *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, globalGasCap *big.Int) (*core.ExecutionResult, error) {
|
||||||
defer func(start time.Time) { log.Debug("Executing EVM call finished", "runtime", time.Since(start)) }(time.Now())
|
defer func(start time.Time) { log.Debug("Executing EVM call finished", "runtime", time.Since(start)) }(time.Now())
|
||||||
|
|
||||||
state, header, err := b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash)
|
state, header, err := b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash)
|
||||||
if state == nil || err != nil {
|
if state == nil || err != nil {
|
||||||
return nil, 0, false, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Override the fields of specified contracts before execution.
|
// Override the fields of specified contracts before execution.
|
||||||
for addr, account := range overrides {
|
for addr, account := range overrides {
|
||||||
// Override account nonce.
|
// Override account nonce.
|
||||||
@ -812,7 +812,7 @@ func DoCall(ctx context.Context, b Backend, args CallArgs, blockNrOrHash rpc.Blo
|
|||||||
state.SetBalance(addr, (*big.Int)(*account.Balance))
|
state.SetBalance(addr, (*big.Int)(*account.Balance))
|
||||||
}
|
}
|
||||||
if account.State != nil && account.StateDiff != nil {
|
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.
|
// Replace entire state if caller requires.
|
||||||
if account.State != nil {
|
if account.State != nil {
|
||||||
@ -825,7 +825,6 @@ func DoCall(ctx context.Context, b Backend, args CallArgs, blockNrOrHash rpc.Blo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Setup context so it may be cancelled the call has completed
|
// Setup context so it may be cancelled the call has completed
|
||||||
// or, in case of unmetered gas, setup a context with a timeout.
|
// or, in case of unmetered gas, setup a context with a timeout.
|
||||||
var cancel context.CancelFunc
|
var cancel context.CancelFunc
|
||||||
@ -842,7 +841,7 @@ func DoCall(ctx context.Context, b Backend, args CallArgs, blockNrOrHash rpc.Blo
|
|||||||
msg := args.ToMessage(globalGasCap)
|
msg := args.ToMessage(globalGasCap)
|
||||||
evm, vmError, err := b.GetEVM(ctx, msg, state, header)
|
evm, vmError, err := b.GetEVM(ctx, msg, state, header)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, 0, false, err
|
return nil, err
|
||||||
}
|
}
|
||||||
// Wait for the context to be done and cancel the evm. Even if the
|
// Wait for the context to be done and cancel the evm. Even if the
|
||||||
// EVM has finished, cancelling may be done (repeatedly)
|
// EVM has finished, cancelling may be done (repeatedly)
|
||||||
@ -854,15 +853,15 @@ func DoCall(ctx context.Context, b Backend, args CallArgs, blockNrOrHash rpc.Blo
|
|||||||
// Setup the gas pool (also for unmetered requests)
|
// Setup the gas pool (also for unmetered requests)
|
||||||
// and apply the message.
|
// and apply the message.
|
||||||
gp := new(core.GasPool).AddGas(math.MaxUint64)
|
gp := new(core.GasPool).AddGas(math.MaxUint64)
|
||||||
res, gas, failed, err := core.ApplyMessage(evm, msg, gp)
|
result, err := core.ApplyMessage(evm, msg, gp)
|
||||||
if err := vmError(); err != nil {
|
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 the timer caused an abort, return an appropriate error message
|
||||||
if evm.Cancelled() {
|
if evm.Cancelled() {
|
||||||
return nil, 0, false, fmt.Errorf("execution aborted (timeout = %v)", timeout)
|
return nil, fmt.Errorf("execution aborted (timeout = %v)", timeout)
|
||||||
}
|
}
|
||||||
return res, gas, failed, err
|
return result, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Call executes the given transaction on the state for the given block number.
|
// Call executes the given transaction on the state for the given block number.
|
||||||
@ -876,8 +875,28 @@ func (s *PublicBlockChainAPI) Call(ctx context.Context, args CallArgs, blockNrOr
|
|||||||
if overrides != nil {
|
if overrides != nil {
|
||||||
accounts = *overrides
|
accounts = *overrides
|
||||||
}
|
}
|
||||||
result, _, _, err := DoCall(ctx, s.b, args, blockNrOrHash, accounts, vm.Config{}, 5*time.Second, s.b.RPCGasCap())
|
result, err := DoCall(ctx, s.b, args, blockNrOrHash, accounts, vm.Config{}, 5*time.Second, s.b.RPCGasCap())
|
||||||
return (hexutil.Bytes)(result), err
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return result.Return(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type estimateGasError struct {
|
||||||
|
error string // Concrete error type if it's failed to estimate gas 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 estimateGasError) 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 DoEstimateGas(ctx context.Context, b Backend, args CallArgs, blockNrOrHash rpc.BlockNumberOrHash, gasCap *big.Int) (hexutil.Uint64, error) {
|
func DoEstimateGas(ctx context.Context, b Backend, args CallArgs, blockNrOrHash rpc.BlockNumberOrHash, gasCap *big.Int) (hexutil.Uint64, error) {
|
||||||
@ -908,19 +927,30 @@ func DoEstimateGas(ctx context.Context, b Backend, args CallArgs, blockNrOrHash
|
|||||||
args.From = new(common.Address)
|
args.From = new(common.Address)
|
||||||
}
|
}
|
||||||
// Create a helper to check if a gas allowance results in an executable transaction
|
// Create a helper to check if a gas allowance results in an executable transaction
|
||||||
executable := func(gas uint64) bool {
|
executable := func(gas uint64) (bool, *core.ExecutionResult, error) {
|
||||||
args.Gas = (*hexutil.Uint64)(&gas)
|
args.Gas = (*hexutil.Uint64)(&gas)
|
||||||
|
|
||||||
_, _, failed, err := DoCall(ctx, b, args, blockNrOrHash, nil, vm.Config{}, 0, gasCap)
|
result, err := DoCall(ctx, b, args, blockNrOrHash, nil, vm.Config{}, 0, gasCap)
|
||||||
if err != nil || failed {
|
if err != nil {
|
||||||
return false
|
if err == core.ErrIntrinsicGas {
|
||||||
|
return true, nil, nil // Special case, raise gas limit
|
||||||
|
}
|
||||||
|
return true, nil, err // Bail out
|
||||||
}
|
}
|
||||||
return true
|
return result.Failed(), result, nil
|
||||||
}
|
}
|
||||||
// Execute the binary search and hone in on an executable gas limit
|
// Execute the binary search and hone in on an executable gas limit
|
||||||
for lo+1 < hi {
|
for lo+1 < hi {
|
||||||
mid := (hi + lo) / 2
|
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
|
||||||
|
// assigened. Return the error directly, don't struggle any more.
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
if failed {
|
||||||
lo = mid
|
lo = mid
|
||||||
} else {
|
} else {
|
||||||
hi = mid
|
hi = mid
|
||||||
@ -928,8 +958,29 @@ func DoEstimateGas(ctx context.Context, b Backend, args CallArgs, blockNrOrHash
|
|||||||
}
|
}
|
||||||
// Reject the transaction as invalid if it still fails at the highest allowance
|
// Reject the transaction as invalid if it still fails at the highest allowance
|
||||||
if hi == cap {
|
if hi == cap {
|
||||||
if !executable(hi) {
|
failed, result, err := executable(hi)
|
||||||
return 0, fmt.Errorf("gas required exceeds allowance (%d) or always failing transaction", cap)
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
if failed {
|
||||||
|
if result != nil && result.Err != vm.ErrOutOfGas {
|
||||||
|
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, estimateGasError{
|
||||||
|
error: "always failing transaction",
|
||||||
|
vmerr: result.Err,
|
||||||
|
revert: revert,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Otherwise, the specified gas cap is too low
|
||||||
|
return 0, estimateGasError{error: fmt.Sprintf("gas required exceeds allowance (%d)", cap)}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return hexutil.Uint64(hi), nil
|
return hexutil.Uint64(hi), nil
|
||||||
|
@ -135,8 +135,8 @@ func odrContractCall(ctx context.Context, db ethdb.Database, config *params.Chai
|
|||||||
|
|
||||||
//vmenv := core.NewEnv(statedb, config, bc, msg, header, vm.Config{})
|
//vmenv := core.NewEnv(statedb, config, bc, msg, header, vm.Config{})
|
||||||
gp := new(core.GasPool).AddGas(math.MaxUint64)
|
gp := new(core.GasPool).AddGas(math.MaxUint64)
|
||||||
ret, _, _, _ := core.ApplyMessage(vmenv, msg, gp)
|
result, _ := core.ApplyMessage(vmenv, msg, gp)
|
||||||
res = append(res, ret...)
|
res = append(res, result.Return()...)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
header := lc.GetHeaderByHash(bhash)
|
header := lc.GetHeaderByHash(bhash)
|
||||||
@ -146,9 +146,9 @@ func odrContractCall(ctx context.Context, db ethdb.Database, config *params.Chai
|
|||||||
context := core.NewEVMContext(msg, header, lc, nil)
|
context := core.NewEVMContext(msg, header, lc, nil)
|
||||||
vmenv := vm.NewEVM(context, state, config, vm.Config{})
|
vmenv := vm.NewEVM(context, state, config, vm.Config{})
|
||||||
gp := new(core.GasPool).AddGas(math.MaxUint64)
|
gp := new(core.GasPool).AddGas(math.MaxUint64)
|
||||||
ret, _, _, _ := core.ApplyMessage(vmenv, msg, gp)
|
result, _ := core.ApplyMessage(vmenv, msg, gp)
|
||||||
if state.Error() == nil {
|
if state.Error() == nil {
|
||||||
res = append(res, ret...)
|
res = append(res, result.Return()...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -198,8 +198,8 @@ func odrContractCall(ctx context.Context, db ethdb.Database, bc *core.BlockChain
|
|||||||
context := core.NewEVMContext(msg, header, chain, nil)
|
context := core.NewEVMContext(msg, header, chain, nil)
|
||||||
vmenv := vm.NewEVM(context, st, config, vm.Config{})
|
vmenv := vm.NewEVM(context, st, config, vm.Config{})
|
||||||
gp := new(core.GasPool).AddGas(math.MaxUint64)
|
gp := new(core.GasPool).AddGas(math.MaxUint64)
|
||||||
ret, _, _, _ := core.ApplyMessage(vmenv, msg, gp)
|
result, _ := core.ApplyMessage(vmenv, msg, gp)
|
||||||
res = append(res, ret...)
|
res = append(res, result.Return()...)
|
||||||
if st.Error() != nil {
|
if st.Error() != nil {
|
||||||
return res, st.Error()
|
return res, st.Error()
|
||||||
}
|
}
|
||||||
|
@ -186,7 +186,7 @@ func (t *StateTest) RunNoVerify(subtest StateSubtest, vmconfig vm.Config, snapsh
|
|||||||
gaspool := new(core.GasPool)
|
gaspool := new(core.GasPool)
|
||||||
gaspool.AddGas(block.GasLimit())
|
gaspool.AddGas(block.GasLimit())
|
||||||
snapshot := statedb.Snapshot()
|
snapshot := statedb.Snapshot()
|
||||||
if _, _, _, err := core.ApplyMessage(evm, msg, gaspool); err != nil {
|
if _, err := core.ApplyMessage(evm, msg, gaspool); err != nil {
|
||||||
statedb.RevertToSnapshot(snapshot)
|
statedb.RevertToSnapshot(snapshot)
|
||||||
}
|
}
|
||||||
// Commit block
|
// Commit block
|
||||||
|
Loading…
Reference in New Issue
Block a user