ethclient: add RevertErrorData function and example (#30669)
Here I'm adding a new helper function that extracts the revert reason of a contract call. Unfortunately, this aspect of the API is underspecified. See these spec issues for more detail: - https://github.com/ethereum/execution-apis/issues/232 - https://github.com/ethereum/execution-apis/issues/463 - https://github.com/ethereum/execution-apis/issues/523 The function added here only works with Geth-like servers that return error code `3`. We will not be able to support all possible servers. However, if there is a specific server implementation that makes it possible to extract the same info, we could add it in the same function as well. --------- Co-authored-by: Marius van der Wijden <m.vanderwijden@live.de>
This commit is contained in:
parent
4bac6e669e
commit
e92e22a7cf
|
@ -630,6 +630,23 @@ func (ec *Client) SendTransaction(ctx context.Context, tx *types.Transaction) er
|
||||||
return ec.c.CallContext(ctx, nil, "eth_sendRawTransaction", hexutil.Encode(data))
|
return ec.c.CallContext(ctx, nil, "eth_sendRawTransaction", hexutil.Encode(data))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RevertErrorData returns the 'revert reason' data of a contract call.
|
||||||
|
//
|
||||||
|
// This can be used with CallContract and EstimateGas, and only when the server is Geth.
|
||||||
|
func RevertErrorData(err error) ([]byte, bool) {
|
||||||
|
var ec rpc.Error
|
||||||
|
var ed rpc.DataError
|
||||||
|
if errors.As(err, &ec) && errors.As(err, &ed) && ec.ErrorCode() == 3 {
|
||||||
|
if eds, ok := ed.ErrorData().(string); ok {
|
||||||
|
revertData, err := hexutil.Decode(eds)
|
||||||
|
if err == nil {
|
||||||
|
return revertData, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
func toBlockNumArg(number *big.Int) string {
|
func toBlockNumArg(number *big.Int) string {
|
||||||
if number == nil {
|
if number == nil {
|
||||||
return "latest"
|
return "latest"
|
||||||
|
|
|
@ -14,18 +14,20 @@
|
||||||
// You should have received a copy of the GNU Lesser General Public License
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
package ethclient
|
package ethclient_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"math/big"
|
"math/big"
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum"
|
"github.com/ethereum/go-ethereum"
|
||||||
|
"github.com/ethereum/go-ethereum/accounts/abi"
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/consensus/ethash"
|
"github.com/ethereum/go-ethereum/consensus/ethash"
|
||||||
"github.com/ethereum/go-ethereum/core"
|
"github.com/ethereum/go-ethereum/core"
|
||||||
|
@ -33,6 +35,7 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
"github.com/ethereum/go-ethereum/eth"
|
"github.com/ethereum/go-ethereum/eth"
|
||||||
"github.com/ethereum/go-ethereum/eth/ethconfig"
|
"github.com/ethereum/go-ethereum/eth/ethconfig"
|
||||||
|
"github.com/ethereum/go-ethereum/ethclient"
|
||||||
"github.com/ethereum/go-ethereum/node"
|
"github.com/ethereum/go-ethereum/node"
|
||||||
"github.com/ethereum/go-ethereum/params"
|
"github.com/ethereum/go-ethereum/params"
|
||||||
"github.com/ethereum/go-ethereum/rpc"
|
"github.com/ethereum/go-ethereum/rpc"
|
||||||
|
@ -40,154 +43,33 @@ import (
|
||||||
|
|
||||||
// Verify that Client implements the ethereum interfaces.
|
// Verify that Client implements the ethereum interfaces.
|
||||||
var (
|
var (
|
||||||
_ = ethereum.ChainReader(&Client{})
|
_ = ethereum.ChainReader(ðclient.Client{})
|
||||||
_ = ethereum.TransactionReader(&Client{})
|
_ = ethereum.TransactionReader(ðclient.Client{})
|
||||||
_ = ethereum.ChainStateReader(&Client{})
|
_ = ethereum.ChainStateReader(ðclient.Client{})
|
||||||
_ = ethereum.ChainSyncReader(&Client{})
|
_ = ethereum.ChainSyncReader(ðclient.Client{})
|
||||||
_ = ethereum.ContractCaller(&Client{})
|
_ = ethereum.ContractCaller(ðclient.Client{})
|
||||||
_ = ethereum.GasEstimator(&Client{})
|
_ = ethereum.GasEstimator(ðclient.Client{})
|
||||||
_ = ethereum.GasPricer(&Client{})
|
_ = ethereum.GasPricer(ðclient.Client{})
|
||||||
_ = ethereum.LogFilterer(&Client{})
|
_ = ethereum.LogFilterer(ðclient.Client{})
|
||||||
_ = ethereum.PendingStateReader(&Client{})
|
_ = ethereum.PendingStateReader(ðclient.Client{})
|
||||||
// _ = ethereum.PendingStateEventer(&Client{})
|
// _ = ethereum.PendingStateEventer(ðclient.Client{})
|
||||||
_ = ethereum.PendingContractCaller(&Client{})
|
_ = ethereum.PendingContractCaller(ðclient.Client{})
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestToFilterArg(t *testing.T) {
|
|
||||||
blockHashErr := errors.New("cannot specify both BlockHash and FromBlock/ToBlock")
|
|
||||||
addresses := []common.Address{
|
|
||||||
common.HexToAddress("0xD36722ADeC3EdCB29c8e7b5a47f352D701393462"),
|
|
||||||
}
|
|
||||||
blockHash := common.HexToHash(
|
|
||||||
"0xeb94bb7d78b73657a9d7a99792413f50c0a45c51fc62bdcb08a53f18e9a2b4eb",
|
|
||||||
)
|
|
||||||
|
|
||||||
for _, testCase := range []struct {
|
|
||||||
name string
|
|
||||||
input ethereum.FilterQuery
|
|
||||||
output interface{}
|
|
||||||
err error
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
"without BlockHash",
|
|
||||||
ethereum.FilterQuery{
|
|
||||||
Addresses: addresses,
|
|
||||||
FromBlock: big.NewInt(1),
|
|
||||||
ToBlock: big.NewInt(2),
|
|
||||||
Topics: [][]common.Hash{},
|
|
||||||
},
|
|
||||||
map[string]interface{}{
|
|
||||||
"address": addresses,
|
|
||||||
"fromBlock": "0x1",
|
|
||||||
"toBlock": "0x2",
|
|
||||||
"topics": [][]common.Hash{},
|
|
||||||
},
|
|
||||||
nil,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"with nil fromBlock and nil toBlock",
|
|
||||||
ethereum.FilterQuery{
|
|
||||||
Addresses: addresses,
|
|
||||||
Topics: [][]common.Hash{},
|
|
||||||
},
|
|
||||||
map[string]interface{}{
|
|
||||||
"address": addresses,
|
|
||||||
"fromBlock": "0x0",
|
|
||||||
"toBlock": "latest",
|
|
||||||
"topics": [][]common.Hash{},
|
|
||||||
},
|
|
||||||
nil,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"with negative fromBlock and negative toBlock",
|
|
||||||
ethereum.FilterQuery{
|
|
||||||
Addresses: addresses,
|
|
||||||
FromBlock: big.NewInt(-1),
|
|
||||||
ToBlock: big.NewInt(-1),
|
|
||||||
Topics: [][]common.Hash{},
|
|
||||||
},
|
|
||||||
map[string]interface{}{
|
|
||||||
"address": addresses,
|
|
||||||
"fromBlock": "pending",
|
|
||||||
"toBlock": "pending",
|
|
||||||
"topics": [][]common.Hash{},
|
|
||||||
},
|
|
||||||
nil,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"with blockhash",
|
|
||||||
ethereum.FilterQuery{
|
|
||||||
Addresses: addresses,
|
|
||||||
BlockHash: &blockHash,
|
|
||||||
Topics: [][]common.Hash{},
|
|
||||||
},
|
|
||||||
map[string]interface{}{
|
|
||||||
"address": addresses,
|
|
||||||
"blockHash": blockHash,
|
|
||||||
"topics": [][]common.Hash{},
|
|
||||||
},
|
|
||||||
nil,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"with blockhash and from block",
|
|
||||||
ethereum.FilterQuery{
|
|
||||||
Addresses: addresses,
|
|
||||||
BlockHash: &blockHash,
|
|
||||||
FromBlock: big.NewInt(1),
|
|
||||||
Topics: [][]common.Hash{},
|
|
||||||
},
|
|
||||||
nil,
|
|
||||||
blockHashErr,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"with blockhash and to block",
|
|
||||||
ethereum.FilterQuery{
|
|
||||||
Addresses: addresses,
|
|
||||||
BlockHash: &blockHash,
|
|
||||||
ToBlock: big.NewInt(1),
|
|
||||||
Topics: [][]common.Hash{},
|
|
||||||
},
|
|
||||||
nil,
|
|
||||||
blockHashErr,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"with blockhash and both from / to block",
|
|
||||||
ethereum.FilterQuery{
|
|
||||||
Addresses: addresses,
|
|
||||||
BlockHash: &blockHash,
|
|
||||||
FromBlock: big.NewInt(1),
|
|
||||||
ToBlock: big.NewInt(2),
|
|
||||||
Topics: [][]common.Hash{},
|
|
||||||
},
|
|
||||||
nil,
|
|
||||||
blockHashErr,
|
|
||||||
},
|
|
||||||
} {
|
|
||||||
t.Run(testCase.name, func(t *testing.T) {
|
|
||||||
output, err := toFilterArg(testCase.input)
|
|
||||||
if (testCase.err == nil) != (err == nil) {
|
|
||||||
t.Fatalf("expected error %v but got %v", testCase.err, err)
|
|
||||||
}
|
|
||||||
if testCase.err != nil {
|
|
||||||
if testCase.err.Error() != err.Error() {
|
|
||||||
t.Fatalf("expected error %v but got %v", testCase.err, err)
|
|
||||||
}
|
|
||||||
} else if !reflect.DeepEqual(testCase.output, output) {
|
|
||||||
t.Fatalf("expected filter arg %v but got %v", testCase.output, output)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
|
testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
|
||||||
testAddr = crypto.PubkeyToAddress(testKey.PublicKey)
|
testAddr = crypto.PubkeyToAddress(testKey.PublicKey)
|
||||||
testBalance = big.NewInt(2e15)
|
testBalance = big.NewInt(2e15)
|
||||||
|
revertContractAddr = common.HexToAddress("290f1b36649a61e369c6276f6d29463335b4400c")
|
||||||
|
revertCode = common.FromHex("7f08c379a0000000000000000000000000000000000000000000000000000000006000526020600452600a6024527f75736572206572726f7200000000000000000000000000000000000000000000604452604e6000fd")
|
||||||
)
|
)
|
||||||
|
|
||||||
var genesis = &core.Genesis{
|
var genesis = &core.Genesis{
|
||||||
Config: params.AllEthashProtocolChanges,
|
Config: params.AllEthashProtocolChanges,
|
||||||
Alloc: types.GenesisAlloc{testAddr: {Balance: testBalance}},
|
Alloc: types.GenesisAlloc{
|
||||||
|
testAddr: {Balance: testBalance},
|
||||||
|
revertContractAddr: {Code: revertCode},
|
||||||
|
},
|
||||||
ExtraData: []byte("test genesis"),
|
ExtraData: []byte("test genesis"),
|
||||||
Timestamp: 9000,
|
Timestamp: 9000,
|
||||||
BaseFee: big.NewInt(params.InitialBaseFee),
|
BaseFee: big.NewInt(params.InitialBaseFee),
|
||||||
|
@ -209,27 +91,30 @@ var testTx2 = types.MustSignNewTx(testKey, types.LatestSigner(genesis.Config), &
|
||||||
To: &common.Address{2},
|
To: &common.Address{2},
|
||||||
})
|
})
|
||||||
|
|
||||||
func newTestBackend(t *testing.T) (*node.Node, []*types.Block) {
|
func newTestBackend(config *node.Config) (*node.Node, []*types.Block, error) {
|
||||||
// Generate test chain.
|
// Generate test chain.
|
||||||
blocks := generateTestChain()
|
blocks := generateTestChain()
|
||||||
|
|
||||||
// Create node
|
// Create node
|
||||||
n, err := node.New(&node.Config{})
|
if config == nil {
|
||||||
|
config = new(node.Config)
|
||||||
|
}
|
||||||
|
n, err := node.New(config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("can't create new node: %v", err)
|
return nil, nil, fmt.Errorf("can't create new node: %v", err)
|
||||||
}
|
}
|
||||||
// Create Ethereum Service
|
// Create Ethereum Service
|
||||||
config := ðconfig.Config{Genesis: genesis, RPCGasCap: 1000000}
|
ecfg := ðconfig.Config{Genesis: genesis, RPCGasCap: 1000000}
|
||||||
ethservice, err := eth.New(n, config)
|
ethservice, err := eth.New(n, ecfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("can't create new ethereum service: %v", err)
|
return nil, nil, fmt.Errorf("can't create new ethereum service: %v", err)
|
||||||
}
|
}
|
||||||
// Import the test chain.
|
// Import the test chain.
|
||||||
if err := n.Start(); err != nil {
|
if err := n.Start(); err != nil {
|
||||||
t.Fatalf("can't start test node: %v", err)
|
return nil, nil, fmt.Errorf("can't start test node: %v", err)
|
||||||
}
|
}
|
||||||
if _, err := ethservice.BlockChain().InsertChain(blocks[1:]); err != nil {
|
if _, err := ethservice.BlockChain().InsertChain(blocks[1:]); err != nil {
|
||||||
t.Fatalf("can't import test blocks: %v", err)
|
return nil, nil, fmt.Errorf("can't import test blocks: %v", err)
|
||||||
}
|
}
|
||||||
// Ensure the tx indexing is fully generated
|
// Ensure the tx indexing is fully generated
|
||||||
for ; ; time.Sleep(time.Millisecond * 100) {
|
for ; ; time.Sleep(time.Millisecond * 100) {
|
||||||
|
@ -238,7 +123,7 @@ func newTestBackend(t *testing.T) (*node.Node, []*types.Block) {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return n, blocks
|
return n, blocks, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func generateTestChain() []*types.Block {
|
func generateTestChain() []*types.Block {
|
||||||
|
@ -256,7 +141,10 @@ func generateTestChain() []*types.Block {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestEthClient(t *testing.T) {
|
func TestEthClient(t *testing.T) {
|
||||||
backend, chain := newTestBackend(t)
|
backend, chain, err := newTestBackend(nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
client := backend.Attach()
|
client := backend.Attach()
|
||||||
defer backend.Close()
|
defer backend.Close()
|
||||||
defer client.Close()
|
defer client.Close()
|
||||||
|
@ -324,7 +212,7 @@ func testHeader(t *testing.T, chain []*types.Block, client *rpc.Client) {
|
||||||
}
|
}
|
||||||
for name, tt := range tests {
|
for name, tt := range tests {
|
||||||
t.Run(name, func(t *testing.T) {
|
t.Run(name, func(t *testing.T) {
|
||||||
ec := NewClient(client)
|
ec := ethclient.NewClient(client)
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
|
ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
|
@ -373,7 +261,7 @@ func testBalanceAt(t *testing.T, client *rpc.Client) {
|
||||||
}
|
}
|
||||||
for name, tt := range tests {
|
for name, tt := range tests {
|
||||||
t.Run(name, func(t *testing.T) {
|
t.Run(name, func(t *testing.T) {
|
||||||
ec := NewClient(client)
|
ec := ethclient.NewClient(client)
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
|
ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
|
@ -389,7 +277,7 @@ func testBalanceAt(t *testing.T, client *rpc.Client) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func testTransactionInBlock(t *testing.T, client *rpc.Client) {
|
func testTransactionInBlock(t *testing.T, client *rpc.Client) {
|
||||||
ec := NewClient(client)
|
ec := ethclient.NewClient(client)
|
||||||
|
|
||||||
// Get current block by number.
|
// Get current block by number.
|
||||||
block, err := ec.BlockByNumber(context.Background(), nil)
|
block, err := ec.BlockByNumber(context.Background(), nil)
|
||||||
|
@ -421,7 +309,7 @@ func testTransactionInBlock(t *testing.T, client *rpc.Client) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func testChainID(t *testing.T, client *rpc.Client) {
|
func testChainID(t *testing.T, client *rpc.Client) {
|
||||||
ec := NewClient(client)
|
ec := ethclient.NewClient(client)
|
||||||
id, err := ec.ChainID(context.Background())
|
id, err := ec.ChainID(context.Background())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unexpected error: %v", err)
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
@ -432,7 +320,7 @@ func testChainID(t *testing.T, client *rpc.Client) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func testGetBlock(t *testing.T, client *rpc.Client) {
|
func testGetBlock(t *testing.T, client *rpc.Client) {
|
||||||
ec := NewClient(client)
|
ec := ethclient.NewClient(client)
|
||||||
|
|
||||||
// Get current block number
|
// Get current block number
|
||||||
blockNumber, err := ec.BlockNumber(context.Background())
|
blockNumber, err := ec.BlockNumber(context.Background())
|
||||||
|
@ -477,7 +365,7 @@ func testGetBlock(t *testing.T, client *rpc.Client) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func testStatusFunctions(t *testing.T, client *rpc.Client) {
|
func testStatusFunctions(t *testing.T, client *rpc.Client) {
|
||||||
ec := NewClient(client)
|
ec := ethclient.NewClient(client)
|
||||||
|
|
||||||
// Sync progress
|
// Sync progress
|
||||||
progress, err := ec.SyncProgress(context.Background())
|
progress, err := ec.SyncProgress(context.Background())
|
||||||
|
@ -540,7 +428,7 @@ func testStatusFunctions(t *testing.T, client *rpc.Client) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func testCallContractAtHash(t *testing.T, client *rpc.Client) {
|
func testCallContractAtHash(t *testing.T, client *rpc.Client) {
|
||||||
ec := NewClient(client)
|
ec := ethclient.NewClient(client)
|
||||||
|
|
||||||
// EstimateGas
|
// EstimateGas
|
||||||
msg := ethereum.CallMsg{
|
msg := ethereum.CallMsg{
|
||||||
|
@ -567,7 +455,7 @@ func testCallContractAtHash(t *testing.T, client *rpc.Client) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func testCallContract(t *testing.T, client *rpc.Client) {
|
func testCallContract(t *testing.T, client *rpc.Client) {
|
||||||
ec := NewClient(client)
|
ec := ethclient.NewClient(client)
|
||||||
|
|
||||||
// EstimateGas
|
// EstimateGas
|
||||||
msg := ethereum.CallMsg{
|
msg := ethereum.CallMsg{
|
||||||
|
@ -594,7 +482,7 @@ func testCallContract(t *testing.T, client *rpc.Client) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func testAtFunctions(t *testing.T, client *rpc.Client) {
|
func testAtFunctions(t *testing.T, client *rpc.Client) {
|
||||||
ec := NewClient(client)
|
ec := ethclient.NewClient(client)
|
||||||
|
|
||||||
block, err := ec.HeaderByNumber(context.Background(), big.NewInt(1))
|
block, err := ec.HeaderByNumber(context.Background(), big.NewInt(1))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -697,7 +585,7 @@ func testAtFunctions(t *testing.T, client *rpc.Client) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func testTransactionSender(t *testing.T, client *rpc.Client) {
|
func testTransactionSender(t *testing.T, client *rpc.Client) {
|
||||||
ec := NewClient(client)
|
ec := ethclient.NewClient(client)
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
// Retrieve testTx1 via RPC.
|
// Retrieve testTx1 via RPC.
|
||||||
|
@ -737,7 +625,7 @@ func testTransactionSender(t *testing.T, client *rpc.Client) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func sendTransaction(ec *Client) error {
|
func sendTransaction(ec *ethclient.Client) error {
|
||||||
chainID, err := ec.ChainID(context.Background())
|
chainID, err := ec.ChainID(context.Background())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -760,3 +648,40 @@ func sendTransaction(ec *Client) error {
|
||||||
}
|
}
|
||||||
return ec.SendTransaction(context.Background(), tx)
|
return ec.SendTransaction(context.Background(), tx)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Here we show how to get the error message of reverted contract call.
|
||||||
|
func ExampleRevertErrorData() {
|
||||||
|
// First create an ethclient.Client instance.
|
||||||
|
ctx := context.Background()
|
||||||
|
ec, _ := ethclient.DialContext(ctx, exampleNode.HTTPEndpoint())
|
||||||
|
|
||||||
|
// Call the contract.
|
||||||
|
// Note we expect the call to return an error.
|
||||||
|
contract := common.HexToAddress("290f1b36649a61e369c6276f6d29463335b4400c")
|
||||||
|
call := ethereum.CallMsg{To: &contract, Gas: 30000}
|
||||||
|
result, err := ec.CallContract(ctx, call, nil)
|
||||||
|
if len(result) > 0 {
|
||||||
|
panic("got result")
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
panic("call did not return error")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract the low-level revert data from the error.
|
||||||
|
revertData, ok := ethclient.RevertErrorData(err)
|
||||||
|
if !ok {
|
||||||
|
panic("unpacking revert failed")
|
||||||
|
}
|
||||||
|
fmt.Printf("revert: %x\n", revertData)
|
||||||
|
|
||||||
|
// Parse the revert data to obtain the error message.
|
||||||
|
message, err := abi.UnpackRevert(revertData)
|
||||||
|
if err != nil {
|
||||||
|
panic("parsing ABI error failed: " + err.Error())
|
||||||
|
}
|
||||||
|
fmt.Println("message:", message)
|
||||||
|
|
||||||
|
// Output:
|
||||||
|
// revert: 08c379a00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000a75736572206572726f72
|
||||||
|
// message: user error
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,35 @@
|
||||||
|
// Copyright 2024 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package ethclient_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/ethereum/go-ethereum/node"
|
||||||
|
)
|
||||||
|
|
||||||
|
var exampleNode *node.Node
|
||||||
|
|
||||||
|
// launch example server
|
||||||
|
func init() {
|
||||||
|
config := &node.Config{
|
||||||
|
HTTPHost: "127.0.0.1",
|
||||||
|
}
|
||||||
|
n, _, err := newTestBackend(config)
|
||||||
|
if err != nil {
|
||||||
|
panic("can't launch node: " + err.Error())
|
||||||
|
}
|
||||||
|
exampleNode = n
|
||||||
|
}
|
|
@ -0,0 +1,153 @@
|
||||||
|
// Copyright 2016 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package ethclient
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"math/big"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum"
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestToFilterArg(t *testing.T) {
|
||||||
|
blockHashErr := errors.New("cannot specify both BlockHash and FromBlock/ToBlock")
|
||||||
|
addresses := []common.Address{
|
||||||
|
common.HexToAddress("0xD36722ADeC3EdCB29c8e7b5a47f352D701393462"),
|
||||||
|
}
|
||||||
|
blockHash := common.HexToHash(
|
||||||
|
"0xeb94bb7d78b73657a9d7a99792413f50c0a45c51fc62bdcb08a53f18e9a2b4eb",
|
||||||
|
)
|
||||||
|
|
||||||
|
for _, testCase := range []struct {
|
||||||
|
name string
|
||||||
|
input ethereum.FilterQuery
|
||||||
|
output interface{}
|
||||||
|
err error
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"without BlockHash",
|
||||||
|
ethereum.FilterQuery{
|
||||||
|
Addresses: addresses,
|
||||||
|
FromBlock: big.NewInt(1),
|
||||||
|
ToBlock: big.NewInt(2),
|
||||||
|
Topics: [][]common.Hash{},
|
||||||
|
},
|
||||||
|
map[string]interface{}{
|
||||||
|
"address": addresses,
|
||||||
|
"fromBlock": "0x1",
|
||||||
|
"toBlock": "0x2",
|
||||||
|
"topics": [][]common.Hash{},
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"with nil fromBlock and nil toBlock",
|
||||||
|
ethereum.FilterQuery{
|
||||||
|
Addresses: addresses,
|
||||||
|
Topics: [][]common.Hash{},
|
||||||
|
},
|
||||||
|
map[string]interface{}{
|
||||||
|
"address": addresses,
|
||||||
|
"fromBlock": "0x0",
|
||||||
|
"toBlock": "latest",
|
||||||
|
"topics": [][]common.Hash{},
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"with negative fromBlock and negative toBlock",
|
||||||
|
ethereum.FilterQuery{
|
||||||
|
Addresses: addresses,
|
||||||
|
FromBlock: big.NewInt(-1),
|
||||||
|
ToBlock: big.NewInt(-1),
|
||||||
|
Topics: [][]common.Hash{},
|
||||||
|
},
|
||||||
|
map[string]interface{}{
|
||||||
|
"address": addresses,
|
||||||
|
"fromBlock": "pending",
|
||||||
|
"toBlock": "pending",
|
||||||
|
"topics": [][]common.Hash{},
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"with blockhash",
|
||||||
|
ethereum.FilterQuery{
|
||||||
|
Addresses: addresses,
|
||||||
|
BlockHash: &blockHash,
|
||||||
|
Topics: [][]common.Hash{},
|
||||||
|
},
|
||||||
|
map[string]interface{}{
|
||||||
|
"address": addresses,
|
||||||
|
"blockHash": blockHash,
|
||||||
|
"topics": [][]common.Hash{},
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"with blockhash and from block",
|
||||||
|
ethereum.FilterQuery{
|
||||||
|
Addresses: addresses,
|
||||||
|
BlockHash: &blockHash,
|
||||||
|
FromBlock: big.NewInt(1),
|
||||||
|
Topics: [][]common.Hash{},
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
blockHashErr,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"with blockhash and to block",
|
||||||
|
ethereum.FilterQuery{
|
||||||
|
Addresses: addresses,
|
||||||
|
BlockHash: &blockHash,
|
||||||
|
ToBlock: big.NewInt(1),
|
||||||
|
Topics: [][]common.Hash{},
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
blockHashErr,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"with blockhash and both from / to block",
|
||||||
|
ethereum.FilterQuery{
|
||||||
|
Addresses: addresses,
|
||||||
|
BlockHash: &blockHash,
|
||||||
|
FromBlock: big.NewInt(1),
|
||||||
|
ToBlock: big.NewInt(2),
|
||||||
|
Topics: [][]common.Hash{},
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
blockHashErr,
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
t.Run(testCase.name, func(t *testing.T) {
|
||||||
|
output, err := toFilterArg(testCase.input)
|
||||||
|
if (testCase.err == nil) != (err == nil) {
|
||||||
|
t.Fatalf("expected error %v but got %v", testCase.err, err)
|
||||||
|
}
|
||||||
|
if testCase.err != nil {
|
||||||
|
if testCase.err.Error() != err.Error() {
|
||||||
|
t.Fatalf("expected error %v but got %v", testCase.err, err)
|
||||||
|
}
|
||||||
|
} else if !reflect.DeepEqual(testCase.output, output) {
|
||||||
|
t.Fatalf("expected filter arg %v but got %v", testCase.output, output)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue