@ -27,6 +27,7 @@ import (
"github.com/davecgh/go-spew/spew"
"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/scwallet"
"github.com/ethereum/go-ethereum/common"
@ -789,14 +790,13 @@ type account struct {
StateDiff * map [ common . Hash ] common . Hash ` json:"stateDiff" `
}
func DoCall ( ctx context . Context , b Backend , args CallArgs , blockNrOrHash rpc . BlockNumberOrHash , overrides map [ common . Address ] account , vmCfg vm . Config , timeout time . Duration , 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 ( ) )
state , header , err := b . StateAndHeaderByNumberOrHash ( ctx , blockNrOrHash )
if state == nil || err != nil {
return nil , 0 , false , err
return nil , err
}
// Override the fields of specified contracts before execution.
for addr , account := range overrides {
// 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 ) )
}
if account . State != nil && account . StateDiff != nil {
return nil , 0 , false , fmt . Errorf ( "account %s has both 'state' and 'stateDiff'" , addr . Hex ( ) )
return nil , fmt . Errorf ( "account %s has both 'state' and 'stateDiff'" , addr . Hex ( ) )
}
// Replace entire state if caller requires.
if account . State != nil {
@ -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
// or, in case of unmetered gas, setup a context with a timeout.
var cancel context . CancelFunc
@ -842,7 +841,7 @@ func DoCall(ctx context.Context, b Backend, args CallArgs, blockNrOrHash rpc.Blo
msg := args . ToMessage ( globalGasCap )
evm , vmError , err := b . GetEVM ( ctx , msg , state , header )
if err != nil {
return nil , 0 , false , err
return nil , err
}
// Wait for the context to be done and cancel the evm. Even if the
// 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)
// and apply the message.
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 {
return nil , 0 , false , err
return nil , err
}
// If the timer caused an abort, return an appropriate error message
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.
@ -876,8 +875,28 @@ func (s *PublicBlockChainAPI) Call(ctx context.Context, args CallArgs, blockNrOr
if overrides != nil {
accounts = * overrides
}
result , _ , _ , err := DoCall ( ctx , s . b , args , blockNrOrHash , accounts , vm . Config { } , 5 * time . Second , s . b . RPCGasCap ( ) )
return ( hexutil . Bytes ) ( result ) , err
result , err := DoCall ( ctx , s . b , args , blockNrOrHash , accounts , vm . Config { } , 5 * time . Second , s . b . RPCGasCap ( ) )
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 ) {
@ -908,19 +927,30 @@ func DoEstimateGas(ctx context.Context, b Backend, args CallArgs, blockNrOrHash
args . From = new ( common . Address )
}
// 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 )
_ , _ , failed , err := DoCall ( ctx , b , args , blockNrOrHash , nil , vm . Config { } , 0 , gasCap )
if err != nil || failed {
return false
result , err := DoCall ( ctx , b , args , blockNrOrHash , nil , vm . Config { } , 0 , gasCap )
if err != nil {
if err == core . ErrIntrinsicGas {
return true , nil , nil // Special case, raise gas limit
}
return true
return true , nil , err // Bail out
}
return result . Failed ( ) , result , nil
}
// Execute the binary search and hone in on an executable gas limit
for lo + 1 < hi {
mid := ( hi + lo ) / 2
if ! executable ( mid ) {
failed , _ , err := executable ( mid )
// If the error is not nil(consensus error), it means the provided message
// call or transaction will never be accepted no matter how much gas it is
// assigened. Return the error directly, don't struggle any more.
if err != nil {
return 0 , err
}
if failed {
lo = mid
} else {
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
if hi == cap {
if ! executable ( hi ) {
return 0 , fmt . Errorf ( "gas required exceeds allowance (%d) or always failing transaction" , cap )
failed , result , err := executable ( hi )
if err != nil {
return 0 , err
}
if failed {
if result != nil && result . Err != vm . 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