mirror of
https://github.com/luxfi/sdk.git
synced 2026-10-30 15:19:23 +00:00
github.com/luxfi/codec is gone from this module — no import, no call, no require. Nothing here marshals or unmarshals a transaction any more. WHY IT BROKE. codec deleted its jsonrpc subpackage (1b67d6a, "delete dead subpackages") while nine files imported it. It looked dead because the PUBLISHED [email protected] still ships it, so only a workspace build — which resolves codec from the local checkout — ever saw the break. THE WIRE. A tx already carries its own ZAP encoding, so the codec was re-deriving what the tx could hand over: txs.Codec.Marshal(txs.CodecVersion, &tx.Unsigned) -> tx.Unsigned.Bytes() txs.UnsignedBytes(tx.Unsigned) txs.Codec.Marshal(txs.CodecVersion, tx) -> tx.Initialize() txs.Codec.Unmarshal(txBytes, &tx) -> txs.Parse(txBytes) codec.Unmarshal(utxoBytes, &utxo) -> utxo.ParseUTXO(...) These are the same calls the node makes in txs.Tx.Sign. AddAllUTXOs no longer takes a codec.Manager, and SortTransferableOutputs / state.Sort / SortOperations no longer get one — upstream dropped the parameter. PARALLEL IMPLEMENTATIONS, DELETED. wallet/chain/{p,x} carried a second copy of the builder and signer beside the subpackages that already held them — p/signer_visitor.go and p/signer/visitor.go were both 399 lines of the same thing, and each defect had to be fixed twice. The flat files are gone; the package keeps the facade it already had in wallet_exports.go, which re-exports the subpackage. -4787 lines. wallet/chain/c is deleted. Its whole reason to exist was a codec.Manager scaffold for C-chain atomic-tx wire that nothing runs: NewWallet was passed nil for it, and the builder was commented out. The two symbols anything used move to where they are read — Account, a balance and a nonce off an EVM client, and EVMKeychain, the geth-address half of a keychain. Wallet no longer advertises a C() that only ever returned nil. ALIGNED. XAssetID -> UTXOAssetID, matching runtime.Runtime and netrunner, which already expected it. Keychain GetEth -> GetByEVM, EthAddrs -> EVMAddrs. warp UnsignedMessage and Message collapsed into one flat Message, so a signature is an Envelope rather than a second message type. netrunner's HealthResponse is read as a field: the getter form is protobuf, behind //go:build grpc, and the default build is ZAP-native. VERIFIED on a machine with the native libraries: go build ./... exits 0 with no codec require in go.mod, and go test ./... is 23 ok. Three packages still fail to build their tests — profiles, wallet/keychain, wallet/primary — and all three fail identically on the tree before this change: Consensus, VerifyHybridSecp256k1MLDSA and isXChainNotEnabled are referenced by tests and defined nowhere. evm failed there too and now passes.
2196 lines
60 KiB
Go
2196 lines
60 KiB
Go
// Copyright (C) 2025, Lux Industries Inc All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
package evm
|
|
|
|
import (
|
|
"context"
|
|
"encoding/hex"
|
|
"errors"
|
|
"math/big"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/luxfi/constants"
|
|
"github.com/luxfi/crypto"
|
|
chainethclient "github.com/luxfi/evm/ethclient"
|
|
ethereum "github.com/luxfi/geth"
|
|
"github.com/luxfi/geth/common"
|
|
"github.com/luxfi/geth/core/types"
|
|
"github.com/luxfi/ids"
|
|
mockethclient "github.com/luxfi/sdk/mocks/ethclient"
|
|
luxWarp "github.com/luxfi/warp"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
"go.uber.org/mock/gomock"
|
|
)
|
|
|
|
func TestHasScheme(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
rpcURL string
|
|
expected bool
|
|
errors bool
|
|
}{
|
|
{
|
|
name: "with http scheme",
|
|
rpcURL: "http://localhost:8545",
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "with https scheme",
|
|
rpcURL: "https://localhost:8545",
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "with ws scheme",
|
|
rpcURL: "ws://localhost:8545",
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "with wss scheme",
|
|
rpcURL: "wss://localhost:8545",
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "without scheme",
|
|
rpcURL: "localhost:8545",
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "IP without scheme",
|
|
rpcURL: "127.0.0.1:123",
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "IP with scheme",
|
|
rpcURL: "ws://127.0.0.1:123",
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "invalid url",
|
|
rpcURL: "http://:invalid",
|
|
expected: false,
|
|
errors: true,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
hasScheme, err := HasScheme(tt.rpcURL)
|
|
if tt.errors {
|
|
require.Error(t, err)
|
|
} else {
|
|
require.NoError(t, err)
|
|
require.Equal(t, tt.expected, hasScheme)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGetClientWithoutScheme(t *testing.T) {
|
|
originalSleepBetweenRepeats := sleepBetweenRepeats
|
|
sleepBetweenRepeats = 1 * time.Millisecond
|
|
defer func() {
|
|
sleepBetweenRepeats = originalSleepBetweenRepeats
|
|
}()
|
|
// Save original function to restore later
|
|
originalDialContext := ethclientDialContext
|
|
defer func() {
|
|
ethclientDialContext = originalDialContext
|
|
}()
|
|
tests := []struct {
|
|
name string
|
|
rpcURL string
|
|
mockDialFunc func(context.Context, string) (chainethclient.Client, error)
|
|
expectedScheme string
|
|
expectError bool
|
|
}{
|
|
{
|
|
name: "invalid url",
|
|
rpcURL: "http://:invalid",
|
|
expectError: true,
|
|
},
|
|
{
|
|
name: "success with ws scheme",
|
|
rpcURL: "localhost:8545",
|
|
mockDialFunc: func(_ context.Context, url string) (chainethclient.Client, error) {
|
|
if strings.HasPrefix(url, "ws://") {
|
|
return mockethclient.NewMockClient(gomock.NewController(t)), nil
|
|
}
|
|
return nil, errors.New("invalid")
|
|
},
|
|
expectedScheme: "ws://",
|
|
expectError: false,
|
|
},
|
|
{
|
|
name: "failure with ws scheme",
|
|
rpcURL: "localhost:8545",
|
|
mockDialFunc: func(_ context.Context, url string) (chainethclient.Client, error) {
|
|
if strings.HasPrefix(url, "ws://") {
|
|
return nil, errors.New("unexpected error on ws connection")
|
|
}
|
|
return nil, errors.New("invalid")
|
|
},
|
|
expectError: true,
|
|
},
|
|
{
|
|
name: "success with wss scheme",
|
|
rpcURL: "localhost:8545",
|
|
mockDialFunc: func(_ context.Context, url string) (chainethclient.Client, error) {
|
|
if strings.HasPrefix(url, "ws://") {
|
|
return nil, errors.New("websocket: bad handshake")
|
|
}
|
|
if strings.HasPrefix(url, "wss://") {
|
|
return mockethclient.NewMockClient(gomock.NewController(t)), nil
|
|
}
|
|
return nil, errors.New("invalid")
|
|
},
|
|
expectedScheme: "wss://",
|
|
expectError: false,
|
|
},
|
|
{
|
|
name: "failure with wss scheme",
|
|
rpcURL: "localhost:8545",
|
|
mockDialFunc: func(_ context.Context, url string) (chainethclient.Client, error) {
|
|
if strings.HasPrefix(url, "ws://") {
|
|
return nil, errors.New("websocket: bad handshake")
|
|
}
|
|
if strings.HasPrefix(url, "wss://") {
|
|
return nil, errors.New("unexpected error on wss connection")
|
|
}
|
|
return nil, errors.New("invalid")
|
|
},
|
|
expectError: true,
|
|
},
|
|
{
|
|
name: "success with https scheme",
|
|
rpcURL: "localhost:8545",
|
|
mockDialFunc: func(_ context.Context, url string) (chainethclient.Client, error) {
|
|
if strings.HasPrefix(url, "ws://") {
|
|
return nil, errors.New("websocket: bad handshake")
|
|
}
|
|
if strings.HasPrefix(url, "wss://") {
|
|
return nil, errors.New("websocket: bad handshake")
|
|
}
|
|
if strings.HasPrefix(url, "https://") {
|
|
mockClient := mockethclient.NewMockClient(gomock.NewController(t))
|
|
mockClient.EXPECT().ChainID(gomock.Any()).Return(big.NewInt(1), nil)
|
|
return mockClient, nil
|
|
}
|
|
return nil, errors.New("invalid")
|
|
},
|
|
expectedScheme: "https://",
|
|
expectError: false,
|
|
},
|
|
{
|
|
name: "failure with https scheme",
|
|
rpcURL: "localhost:8545",
|
|
mockDialFunc: func(_ context.Context, url string) (chainethclient.Client, error) {
|
|
if strings.HasPrefix(url, "ws://") {
|
|
return nil, errors.New("websocket: bad handshake")
|
|
}
|
|
if strings.HasPrefix(url, "wss://") {
|
|
return nil, errors.New("websocket: bad handshake")
|
|
}
|
|
if strings.HasPrefix(url, "https://") {
|
|
mockClient := mockethclient.NewMockClient(gomock.NewController(t))
|
|
mockClient.EXPECT().ChainID(gomock.Any()).Return(big.NewInt(1), errors.New("unexpected error on https connection"))
|
|
return mockClient, nil
|
|
}
|
|
return nil, errors.New("invalid")
|
|
},
|
|
expectError: true,
|
|
},
|
|
{
|
|
name: "success with http scheme",
|
|
rpcURL: "localhost:8545",
|
|
mockDialFunc: func(_ context.Context, url string) (chainethclient.Client, error) {
|
|
if strings.HasPrefix(url, "ws://") {
|
|
return nil, errors.New("websocket: bad handshake")
|
|
}
|
|
if strings.HasPrefix(url, "wss://") {
|
|
return nil, errors.New("websocket: bad handshake")
|
|
}
|
|
if strings.HasPrefix(url, "https://") {
|
|
mockClient := mockethclient.NewMockClient(gomock.NewController(t))
|
|
mockClient.EXPECT().ChainID(gomock.Any()).Return(big.NewInt(1), errors.New("server gave HTTP response to HTTPS client"))
|
|
return mockClient, nil
|
|
}
|
|
if strings.HasPrefix(url, "http://") {
|
|
return mockethclient.NewMockClient(gomock.NewController(t)), nil
|
|
}
|
|
return nil, errors.New("invalid")
|
|
},
|
|
expectedScheme: "http://",
|
|
expectError: false,
|
|
},
|
|
{
|
|
name: "error - url with scheme",
|
|
rpcURL: "http://localhost:8545",
|
|
mockDialFunc: func(_ context.Context, _ string) (chainethclient.Client, error) {
|
|
return nil, nil
|
|
},
|
|
expectError: true,
|
|
},
|
|
{
|
|
name: "error - unknown protocol",
|
|
rpcURL: "localhost:8545",
|
|
mockDialFunc: func(_ context.Context, _ string) (chainethclient.Client, error) {
|
|
return nil, errors.New("unknown protocol")
|
|
},
|
|
expectError: true,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
// Replace the dial function with our mock
|
|
ethclientDialContext = tt.mockDialFunc
|
|
client, scheme, err := GetClientWithoutScheme(tt.rpcURL)
|
|
if tt.expectError {
|
|
require.Error(t, err)
|
|
} else {
|
|
require.NoError(t, err)
|
|
require.NotNil(t, client)
|
|
require.Equal(t, tt.expectedScheme, scheme)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGetClient(t *testing.T) {
|
|
originalSleepBetweenRepeats := sleepBetweenRepeats
|
|
sleepBetweenRepeats = 1 * time.Millisecond
|
|
defer func() {
|
|
sleepBetweenRepeats = originalSleepBetweenRepeats
|
|
}()
|
|
// Save original function to restore later
|
|
originalDialContext := ethclientDialContext
|
|
defer func() {
|
|
ethclientDialContext = originalDialContext
|
|
}()
|
|
ctrl := gomock.NewController(t)
|
|
defer ctrl.Finish()
|
|
failuresCount := 0
|
|
tests := []struct {
|
|
name string
|
|
rpcURL string
|
|
mockDialFunc func(context.Context, string) (chainethclient.Client, error)
|
|
expectError bool
|
|
}{
|
|
{
|
|
name: "invalid url",
|
|
rpcURL: "http://:invalid",
|
|
expectError: true,
|
|
},
|
|
{
|
|
name: "with scheme, total failure",
|
|
rpcURL: "http://localhost:8545",
|
|
mockDialFunc: func(_ context.Context, _ string) (chainethclient.Client, error) {
|
|
failuresCount++
|
|
if failuresCount <= repeatsOnFailure {
|
|
return nil, errors.New("connection error")
|
|
}
|
|
return mockethclient.NewMockClient(ctrl), nil
|
|
},
|
|
expectError: true,
|
|
},
|
|
{
|
|
name: "with scheme, 2 failures",
|
|
rpcURL: "http://localhost:8545",
|
|
mockDialFunc: func(_ context.Context, _ string) (chainethclient.Client, error) {
|
|
failuresCount++
|
|
if failuresCount < repeatsOnFailure {
|
|
return nil, errors.New("connection error")
|
|
}
|
|
return mockethclient.NewMockClient(ctrl), nil
|
|
},
|
|
expectError: false,
|
|
},
|
|
{
|
|
name: "with scheme",
|
|
rpcURL: "http://localhost:8545",
|
|
mockDialFunc: func(_ context.Context, _ string) (chainethclient.Client, error) {
|
|
return mockethclient.NewMockClient(ctrl), nil
|
|
},
|
|
expectError: false,
|
|
},
|
|
{
|
|
name: "without scheme",
|
|
rpcURL: "localhost:8545",
|
|
mockDialFunc: func(_ context.Context, url string) (chainethclient.Client, error) {
|
|
if strings.HasPrefix(url, "ws://") {
|
|
return nil, errors.New("websocket: bad handshake")
|
|
}
|
|
if strings.HasPrefix(url, "wss://") {
|
|
return nil, errors.New("websocket: bad handshake")
|
|
}
|
|
if strings.HasPrefix(url, "https://") {
|
|
mockClient := mockethclient.NewMockClient(gomock.NewController(t))
|
|
mockClient.EXPECT().ChainID(gomock.Any()).Return(big.NewInt(1), errors.New("server gave HTTP response to HTTPS client"))
|
|
return mockClient, nil
|
|
}
|
|
if strings.HasPrefix(url, "http://") {
|
|
return mockethclient.NewMockClient(gomock.NewController(t)), nil
|
|
}
|
|
return nil, errors.New("invalid")
|
|
},
|
|
expectError: false,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
// Replace the dial function with our mock
|
|
ethclientDialContext = tt.mockDialFunc
|
|
failuresCount = 0
|
|
client, err := GetClient(tt.rpcURL)
|
|
if tt.expectError {
|
|
require.Error(t, err)
|
|
require.Contains(t, err.Error(), tt.rpcURL)
|
|
} else {
|
|
require.NoError(t, err)
|
|
require.Equal(t, tt.rpcURL, client.URL)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestClose(t *testing.T) {
|
|
ctrl := gomock.NewController(t)
|
|
mockClient := mockethclient.NewMockClient(ctrl)
|
|
client := Client{
|
|
EthClient: mockClient,
|
|
URL: "http://localhost:8545",
|
|
}
|
|
// Expect Close to be called exactly once
|
|
mockClient.EXPECT().Close().Times(1)
|
|
// Call Close
|
|
client.Close()
|
|
// Verify all expectations were met
|
|
ctrl.Finish()
|
|
}
|
|
|
|
func TestContractAlreadyDeployed(t *testing.T) {
|
|
originalSleepBetweenRepeats := sleepBetweenRepeats
|
|
sleepBetweenRepeats = 1 * time.Millisecond
|
|
defer func() {
|
|
sleepBetweenRepeats = originalSleepBetweenRepeats
|
|
}()
|
|
ctrl := gomock.NewController(t)
|
|
defer ctrl.Finish()
|
|
mockClient := mockethclient.NewMockClient(ctrl)
|
|
client := Client{
|
|
EthClient: mockClient,
|
|
URL: "http://localhost:8545",
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
contractAddr string
|
|
setupMock func()
|
|
expected bool
|
|
expectError bool
|
|
}{
|
|
{
|
|
name: "contract deployed",
|
|
contractAddr: "0x1234567890123456789012345678901234567890",
|
|
setupMock: func() {
|
|
mockClient.EXPECT().CodeAt(gomock.Any(), gomock.Any(), gomock.Any()).
|
|
Return([]byte{1, 2, 3}, nil)
|
|
},
|
|
expected: true,
|
|
expectError: false,
|
|
},
|
|
{
|
|
name: "contract not deployed",
|
|
contractAddr: "0x1234567890123456789012345678901234567890",
|
|
setupMock: func() {
|
|
mockClient.EXPECT().CodeAt(gomock.Any(), gomock.Any(), gomock.Any()).
|
|
Return([]byte{}, nil)
|
|
},
|
|
expected: false,
|
|
expectError: false,
|
|
},
|
|
{
|
|
name: "error getting code",
|
|
contractAddr: "0x1234567890123456789012345678901234567890",
|
|
setupMock: func() {
|
|
for i := 0; i < repeatsOnFailure; i++ {
|
|
mockClient.EXPECT().CodeAt(gomock.Any(), gomock.Any(), gomock.Any()).
|
|
Return(nil, errors.New("failed to get code"))
|
|
}
|
|
},
|
|
expected: false,
|
|
expectError: true,
|
|
},
|
|
{
|
|
name: "getting code after max failues",
|
|
contractAddr: "0x1234567890123456789012345678901234567890",
|
|
setupMock: func() {
|
|
for i := 0; i < repeatsOnFailure-1; i++ {
|
|
mockClient.EXPECT().CodeAt(gomock.Any(), gomock.Any(), gomock.Any()).
|
|
Return(nil, errors.New("failed to get code"))
|
|
}
|
|
mockClient.EXPECT().CodeAt(gomock.Any(), gomock.Any(), gomock.Any()).
|
|
Return([]byte{1, 2, 3}, nil)
|
|
},
|
|
expected: true,
|
|
expectError: false,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
tt.setupMock()
|
|
deployed, err := client.ContractAlreadyDeployed(tt.contractAddr)
|
|
if tt.expectError {
|
|
require.Error(t, err)
|
|
} else {
|
|
require.NoError(t, err)
|
|
require.Equal(t, tt.expected, deployed)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGetAddressBalance(t *testing.T) {
|
|
originalSleepBetweenRepeats := sleepBetweenRepeats
|
|
sleepBetweenRepeats = 1 * time.Millisecond
|
|
defer func() {
|
|
sleepBetweenRepeats = originalSleepBetweenRepeats
|
|
}()
|
|
ctrl := gomock.NewController(t)
|
|
defer ctrl.Finish()
|
|
mockClient := mockethclient.NewMockClient(ctrl)
|
|
client := Client{
|
|
EthClient: mockClient,
|
|
URL: "http://localhost:8545",
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
address string
|
|
setupMock func()
|
|
expected *big.Int
|
|
expectError bool
|
|
}{
|
|
{
|
|
name: "successful balance check",
|
|
address: "0x1234567890123456789012345678901234567890",
|
|
setupMock: func() {
|
|
mockClient.EXPECT().BalanceAt(gomock.Any(), gomock.Any(), gomock.Any()).
|
|
Return(big.NewInt(1000), nil)
|
|
},
|
|
expected: big.NewInt(1000),
|
|
expectError: false,
|
|
},
|
|
{
|
|
name: "error getting balance",
|
|
address: "0x1234567890123456789012345678901234567890",
|
|
setupMock: func() {
|
|
for i := 0; i < repeatsOnFailure; i++ {
|
|
mockClient.EXPECT().BalanceAt(gomock.Any(), gomock.Any(), gomock.Any()).
|
|
Return(nil, errors.New("failed to get balance"))
|
|
}
|
|
},
|
|
expected: nil,
|
|
expectError: true,
|
|
},
|
|
{
|
|
name: "successful balance check after max failures",
|
|
address: "0x1234567890123456789012345678901234567890",
|
|
setupMock: func() {
|
|
for i := 0; i < repeatsOnFailure-1; i++ {
|
|
mockClient.EXPECT().BalanceAt(gomock.Any(), gomock.Any(), gomock.Any()).
|
|
Return(nil, errors.New("failed to get balance"))
|
|
}
|
|
mockClient.EXPECT().BalanceAt(gomock.Any(), gomock.Any(), gomock.Any()).
|
|
Return(big.NewInt(1000), nil)
|
|
},
|
|
expected: big.NewInt(1000),
|
|
expectError: false,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
tt.setupMock()
|
|
balance, err := client.GetAddressBalance(tt.address)
|
|
if tt.expectError {
|
|
require.Error(t, err)
|
|
require.Contains(t, err.Error(), tt.address)
|
|
} else {
|
|
require.NoError(t, err)
|
|
require.Equal(t, tt.expected, balance)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestNonceAt(t *testing.T) {
|
|
originalSleepBetweenRepeats := sleepBetweenRepeats
|
|
sleepBetweenRepeats = 1 * time.Millisecond
|
|
defer func() {
|
|
sleepBetweenRepeats = originalSleepBetweenRepeats
|
|
}()
|
|
ctrl := gomock.NewController(t)
|
|
defer ctrl.Finish()
|
|
mockClient := mockethclient.NewMockClient(ctrl)
|
|
client := Client{
|
|
EthClient: mockClient,
|
|
URL: "http://localhost:8545",
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
address string
|
|
setupMock func()
|
|
expected uint64
|
|
expectError bool
|
|
}{
|
|
{
|
|
name: "successful nonce check",
|
|
address: "0x1234567890123456789012345678901234567890",
|
|
setupMock: func() {
|
|
mockClient.EXPECT().NonceAt(gomock.Any(), gomock.Any(), gomock.Any()).
|
|
Return(uint64(42), nil)
|
|
},
|
|
expected: 42,
|
|
expectError: false,
|
|
},
|
|
{
|
|
name: "error getting nonce",
|
|
address: "0x1234567890123456789012345678901234567890",
|
|
setupMock: func() {
|
|
for i := 0; i < repeatsOnFailure; i++ {
|
|
mockClient.EXPECT().NonceAt(gomock.Any(), gomock.Any(), gomock.Any()).
|
|
Return(uint64(0), errors.New("failed to get nonce"))
|
|
}
|
|
},
|
|
expected: 0,
|
|
expectError: true,
|
|
},
|
|
{
|
|
name: "successful after max failures",
|
|
address: "0x1234567890123456789012345678901234567890",
|
|
setupMock: func() {
|
|
for i := 0; i < repeatsOnFailure-1; i++ {
|
|
mockClient.EXPECT().NonceAt(gomock.Any(), gomock.Any(), gomock.Any()).
|
|
Return(uint64(0), errors.New("failed to get nonce"))
|
|
}
|
|
mockClient.EXPECT().NonceAt(gomock.Any(), gomock.Any(), gomock.Any()).
|
|
Return(uint64(42), nil)
|
|
},
|
|
expected: 42,
|
|
expectError: false,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
tt.setupMock()
|
|
|
|
nonce, err := client.NonceAt(tt.address)
|
|
if tt.expectError {
|
|
require.Error(t, err)
|
|
require.Contains(t, err.Error(), tt.address)
|
|
} else {
|
|
require.NoError(t, err)
|
|
require.Equal(t, tt.expected, nonce)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestSuggestGasTipCap(t *testing.T) {
|
|
originalSleepBetweenRepeats := sleepBetweenRepeats
|
|
sleepBetweenRepeats = 1 * time.Millisecond
|
|
defer func() {
|
|
sleepBetweenRepeats = originalSleepBetweenRepeats
|
|
}()
|
|
ctrl := gomock.NewController(t)
|
|
defer ctrl.Finish()
|
|
mockClient := mockethclient.NewMockClient(ctrl)
|
|
client := Client{
|
|
EthClient: mockClient,
|
|
URL: "http://localhost:8545",
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
setupMock func()
|
|
expected *big.Int
|
|
expectError bool
|
|
}{
|
|
{
|
|
name: "successful gas tip cap suggestion",
|
|
setupMock: func() {
|
|
mockClient.EXPECT().SuggestGasTipCap(gomock.Any()).
|
|
Return(big.NewInt(1000000000), nil)
|
|
},
|
|
expected: big.NewInt(1000000000),
|
|
expectError: false,
|
|
},
|
|
{
|
|
name: "error getting gas tip cap",
|
|
setupMock: func() {
|
|
for i := 0; i < repeatsOnFailure; i++ {
|
|
mockClient.EXPECT().SuggestGasTipCap(gomock.Any()).
|
|
Return(nil, errors.New("failed to get gas tip cap"))
|
|
}
|
|
},
|
|
expected: nil,
|
|
expectError: true,
|
|
},
|
|
{
|
|
name: "successful after max failures",
|
|
setupMock: func() {
|
|
for i := 0; i < repeatsOnFailure-1; i++ {
|
|
mockClient.EXPECT().SuggestGasTipCap(gomock.Any()).
|
|
Return(nil, errors.New("failed to get gas tip cap"))
|
|
}
|
|
mockClient.EXPECT().SuggestGasTipCap(gomock.Any()).
|
|
Return(big.NewInt(100), nil)
|
|
},
|
|
expected: big.NewInt(100),
|
|
expectError: false,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
tt.setupMock()
|
|
gasTipCap, err := client.SuggestGasTipCap()
|
|
if tt.expectError {
|
|
require.Error(t, err)
|
|
require.Contains(t, err.Error(), "gas tip cap")
|
|
} else {
|
|
require.NoError(t, err)
|
|
require.Equal(t, tt.expected, gasTipCap)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestEstimateBaseFee(t *testing.T) {
|
|
originalSleepBetweenRepeats := sleepBetweenRepeats
|
|
sleepBetweenRepeats = 1 * time.Millisecond
|
|
defer func() {
|
|
sleepBetweenRepeats = originalSleepBetweenRepeats
|
|
}()
|
|
ctrl := gomock.NewController(t)
|
|
defer ctrl.Finish()
|
|
mockClient := mockethclient.NewMockClient(ctrl)
|
|
client := Client{
|
|
EthClient: mockClient,
|
|
URL: "http://localhost:8545",
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
setupMock func()
|
|
expected *big.Int
|
|
expectError bool
|
|
}{
|
|
{
|
|
name: "successful base fee estimation",
|
|
setupMock: func() {
|
|
mockClient.EXPECT().EstimateBaseFee(gomock.Any()).
|
|
Return(big.NewInt(10000000000), nil)
|
|
},
|
|
expected: big.NewInt(10000000000),
|
|
expectError: false,
|
|
},
|
|
{
|
|
name: "error estimating base fee",
|
|
setupMock: func() {
|
|
for i := 0; i < repeatsOnFailure; i++ {
|
|
mockClient.EXPECT().EstimateBaseFee(gomock.Any()).
|
|
Return(nil, errors.New("failed to estimate base fee"))
|
|
}
|
|
},
|
|
expected: nil,
|
|
expectError: true,
|
|
},
|
|
{
|
|
name: "successful after max failures",
|
|
setupMock: func() {
|
|
for i := 0; i < repeatsOnFailure-1; i++ {
|
|
mockClient.EXPECT().EstimateBaseFee(gomock.Any()).
|
|
Return(nil, errors.New("failed to estimate base fee"))
|
|
}
|
|
mockClient.EXPECT().EstimateBaseFee(gomock.Any()).
|
|
Return(big.NewInt(100), nil)
|
|
},
|
|
expected: big.NewInt(100),
|
|
expectError: false,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
tt.setupMock()
|
|
baseFee, err := client.EstimateBaseFee()
|
|
if tt.expectError {
|
|
require.Error(t, err)
|
|
require.Contains(t, err.Error(), "base fee")
|
|
} else {
|
|
require.NoError(t, err)
|
|
require.Equal(t, tt.expected, baseFee)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestEstimateGasLimit(t *testing.T) {
|
|
originalSleepBetweenRepeats := sleepBetweenRepeats
|
|
sleepBetweenRepeats = 1 * time.Millisecond
|
|
defer func() {
|
|
sleepBetweenRepeats = originalSleepBetweenRepeats
|
|
}()
|
|
ctrl := gomock.NewController(t)
|
|
defer ctrl.Finish()
|
|
mockClient := mockethclient.NewMockClient(ctrl)
|
|
client := Client{
|
|
EthClient: mockClient,
|
|
URL: "http://localhost:8545",
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
setupMock func()
|
|
expected uint64
|
|
expectError bool
|
|
}{
|
|
{
|
|
name: "successful gas limit estimation",
|
|
setupMock: func() {
|
|
mockClient.EXPECT().EstimateGas(gomock.Any(), gomock.Any()).
|
|
Return(uint64(21000), nil)
|
|
},
|
|
expected: 21000,
|
|
expectError: false,
|
|
},
|
|
{
|
|
name: "error estimating gas limit",
|
|
setupMock: func() {
|
|
for i := 0; i < repeatsOnFailure; i++ {
|
|
mockClient.EXPECT().EstimateGas(gomock.Any(), gomock.Any()).
|
|
Return(uint64(0), errors.New("failed to estimate gas"))
|
|
}
|
|
},
|
|
expected: 0,
|
|
expectError: true,
|
|
},
|
|
{
|
|
name: "successful after max failures",
|
|
setupMock: func() {
|
|
for i := 0; i < repeatsOnFailure-1; i++ {
|
|
mockClient.EXPECT().EstimateGas(gomock.Any(), gomock.Any()).
|
|
Return(uint64(0), errors.New("failed to estimate gas"))
|
|
}
|
|
mockClient.EXPECT().EstimateGas(gomock.Any(), gomock.Any()).
|
|
Return(uint64(21000), nil)
|
|
},
|
|
expected: 21000,
|
|
expectError: false,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
tt.setupMock()
|
|
gasLimit, err := client.EstimateGasLimit(ethereum.CallMsg{})
|
|
if tt.expectError {
|
|
require.Error(t, err)
|
|
require.Contains(t, err.Error(), "gas limit")
|
|
} else {
|
|
require.NoError(t, err)
|
|
require.Equal(t, tt.expected, gasLimit)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGetChainID(t *testing.T) {
|
|
originalSleepBetweenRepeats := sleepBetweenRepeats
|
|
sleepBetweenRepeats = 1 * time.Millisecond
|
|
defer func() {
|
|
sleepBetweenRepeats = originalSleepBetweenRepeats
|
|
}()
|
|
ctrl := gomock.NewController(t)
|
|
defer ctrl.Finish()
|
|
mockClient := mockethclient.NewMockClient(ctrl)
|
|
client := Client{
|
|
EthClient: mockClient,
|
|
URL: "http://localhost:8545",
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
setupMock func()
|
|
expected *big.Int
|
|
expectError bool
|
|
}{
|
|
{
|
|
name: "successful chain ID retrieval",
|
|
setupMock: func() {
|
|
mockClient.EXPECT().ChainID(gomock.Any()).
|
|
Return(big.NewInt(43114), nil)
|
|
},
|
|
expected: big.NewInt(43114),
|
|
expectError: false,
|
|
},
|
|
{
|
|
name: "error getting chain ID",
|
|
setupMock: func() {
|
|
for i := 0; i < repeatsOnFailure; i++ {
|
|
mockClient.EXPECT().ChainID(gomock.Any()).
|
|
Return(nil, errors.New("failed to get chain ID"))
|
|
}
|
|
},
|
|
expected: nil,
|
|
expectError: true,
|
|
},
|
|
{
|
|
name: "successful after max failures",
|
|
setupMock: func() {
|
|
for i := 0; i < repeatsOnFailure-1; i++ {
|
|
mockClient.EXPECT().ChainID(gomock.Any()).
|
|
Return(nil, errors.New("failed to get chain ID"))
|
|
}
|
|
mockClient.EXPECT().ChainID(gomock.Any()).
|
|
Return(big.NewInt(43114), nil)
|
|
},
|
|
expected: big.NewInt(43114),
|
|
expectError: false,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
tt.setupMock()
|
|
chainID, err := client.GetChainID()
|
|
if tt.expectError {
|
|
require.Error(t, err)
|
|
require.Contains(t, err.Error(), "chain id")
|
|
} else {
|
|
require.NoError(t, err)
|
|
require.Equal(t, tt.expected, chainID)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestSendTransaction(t *testing.T) {
|
|
originalSleepBetweenRepeats := sleepBetweenRepeats
|
|
sleepBetweenRepeats = 1 * time.Millisecond
|
|
defer func() {
|
|
sleepBetweenRepeats = originalSleepBetweenRepeats
|
|
}()
|
|
ctrl := gomock.NewController(t)
|
|
defer ctrl.Finish()
|
|
mockClient := mockethclient.NewMockClient(ctrl)
|
|
client := Client{
|
|
EthClient: mockClient,
|
|
URL: "http://localhost:8545",
|
|
}
|
|
tx := types.NewTransaction(0, common.Address{}, nil, 0, nil, nil)
|
|
tests := []struct {
|
|
name string
|
|
setupMock func()
|
|
expectError bool
|
|
}{
|
|
{
|
|
name: "successful transaction send",
|
|
setupMock: func() {
|
|
mockClient.EXPECT().SendTransaction(gomock.Any(), tx).
|
|
Return(nil)
|
|
},
|
|
expectError: false,
|
|
},
|
|
{
|
|
name: "error sending transaction",
|
|
setupMock: func() {
|
|
for i := 0; i < repeatsOnFailure; i++ {
|
|
mockClient.EXPECT().SendTransaction(gomock.Any(), tx).
|
|
Return(errors.New("failed to send transaction"))
|
|
}
|
|
},
|
|
expectError: true,
|
|
},
|
|
{
|
|
name: "successful after max failures",
|
|
setupMock: func() {
|
|
for i := 0; i < repeatsOnFailure-1; i++ {
|
|
mockClient.EXPECT().SendTransaction(gomock.Any(), tx).
|
|
Return(errors.New("failed to send transaction"))
|
|
}
|
|
mockClient.EXPECT().SendTransaction(gomock.Any(), tx).
|
|
Return(nil)
|
|
},
|
|
expectError: false,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
tt.setupMock()
|
|
err := client.SendTransaction(tx)
|
|
if tt.expectError {
|
|
require.Error(t, err)
|
|
require.Contains(t, err.Error(), "sending transaction")
|
|
} else {
|
|
require.NoError(t, err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestWaitForTransaction(t *testing.T) {
|
|
originalSleepBetweenRepeats := sleepBetweenRepeats
|
|
sleepBetweenRepeats = 1 * time.Millisecond
|
|
defer func() {
|
|
sleepBetweenRepeats = originalSleepBetweenRepeats
|
|
}()
|
|
ctrl := gomock.NewController(t)
|
|
defer ctrl.Finish()
|
|
mockClient := mockethclient.NewMockClient(ctrl)
|
|
client := Client{
|
|
EthClient: mockClient,
|
|
URL: "http://localhost:8545",
|
|
}
|
|
tx := types.NewTransaction(0, common.Address{}, nil, 0, nil, nil)
|
|
successfulReceipt := &types.Receipt{Status: types.ReceiptStatusSuccessful}
|
|
failedReceipt := &types.Receipt{Status: types.ReceiptStatusFailed}
|
|
tests := []struct {
|
|
name string
|
|
setupMock func()
|
|
expected *types.Receipt
|
|
success bool
|
|
expectError bool
|
|
}{
|
|
{
|
|
name: "successful transaction",
|
|
setupMock: func() {
|
|
mockClient.EXPECT().TransactionReceipt(gomock.Any(), tx.Hash()).
|
|
Return(successfulReceipt, nil)
|
|
},
|
|
expected: successfulReceipt,
|
|
success: true,
|
|
expectError: false,
|
|
},
|
|
{
|
|
name: "failed transaction",
|
|
setupMock: func() {
|
|
mockClient.EXPECT().TransactionReceipt(gomock.Any(), tx.Hash()).
|
|
Return(failedReceipt, nil)
|
|
},
|
|
expected: failedReceipt,
|
|
success: false,
|
|
expectError: false,
|
|
},
|
|
{
|
|
name: "error waiting for transaction",
|
|
setupMock: func() {
|
|
steps := int(constants.APIRequestLargeTimeout.Seconds())
|
|
for i := 0; i < steps*repeatsOnFailure; i++ {
|
|
mockClient.EXPECT().TransactionReceipt(gomock.Any(), tx.Hash()).
|
|
Return(nil, errors.New("failed to get receipt"))
|
|
}
|
|
},
|
|
expected: nil,
|
|
success: false,
|
|
expectError: true,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
tt.setupMock()
|
|
receipt, success, err := client.WaitForTransaction(tx)
|
|
if tt.expectError {
|
|
require.Error(t, err)
|
|
require.Contains(t, err.Error(), "waiting for tx")
|
|
} else {
|
|
require.NoError(t, err)
|
|
require.Equal(t, tt.expected, receipt)
|
|
require.Equal(t, tt.success, success)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestBlockByNumber(t *testing.T) {
|
|
originalSleepBetweenRepeats := sleepBetweenRepeats
|
|
sleepBetweenRepeats = 1 * time.Millisecond
|
|
defer func() {
|
|
sleepBetweenRepeats = originalSleepBetweenRepeats
|
|
}()
|
|
ctrl := gomock.NewController(t)
|
|
defer ctrl.Finish()
|
|
mockClient := mockethclient.NewMockClient(ctrl)
|
|
client := Client{
|
|
EthClient: mockClient,
|
|
URL: "http://localhost:8545",
|
|
}
|
|
blockNumber := big.NewInt(1)
|
|
header := &types.Header{
|
|
Number: blockNumber,
|
|
}
|
|
block := types.NewBlockWithHeader(header)
|
|
tests := []struct {
|
|
name string
|
|
setupMock func()
|
|
expected *types.Block
|
|
expectError bool
|
|
}{
|
|
{
|
|
name: "successful block retrieval",
|
|
setupMock: func() {
|
|
mockClient.EXPECT().BlockByNumber(gomock.Any(), blockNumber).
|
|
Return(block, nil)
|
|
},
|
|
expected: block,
|
|
expectError: false,
|
|
},
|
|
{
|
|
name: "error getting block",
|
|
setupMock: func() {
|
|
for i := 0; i < repeatsOnFailure; i++ {
|
|
mockClient.EXPECT().BlockByNumber(gomock.Any(), blockNumber).
|
|
Return(nil, errors.New("failed to get block"))
|
|
}
|
|
},
|
|
expected: nil,
|
|
expectError: true,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
tt.setupMock()
|
|
result, err := client.BlockByNumber(blockNumber)
|
|
if tt.expectError {
|
|
require.Error(t, err)
|
|
require.Contains(t, err.Error(), "retrieving block")
|
|
} else {
|
|
require.NoError(t, err)
|
|
require.Equal(t, tt.expected, result)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestFilterLogs(t *testing.T) {
|
|
originalSleepBetweenRepeats := sleepBetweenRepeats
|
|
sleepBetweenRepeats = 1 * time.Millisecond
|
|
defer func() {
|
|
sleepBetweenRepeats = originalSleepBetweenRepeats
|
|
}()
|
|
ctrl := gomock.NewController(t)
|
|
defer ctrl.Finish()
|
|
mockClient := mockethclient.NewMockClient(ctrl)
|
|
client := Client{
|
|
EthClient: mockClient,
|
|
URL: "http://localhost:8545",
|
|
}
|
|
logs := []types.Log{
|
|
{Address: common.HexToAddress("0x123")},
|
|
{Address: common.HexToAddress("0x456")},
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
setupMock func()
|
|
expected []types.Log
|
|
expectError bool
|
|
}{
|
|
{
|
|
name: "successful log filtering",
|
|
setupMock: func() {
|
|
mockClient.EXPECT().FilterLogs(gomock.Any(), gomock.Any()).
|
|
Return(logs, nil)
|
|
},
|
|
expected: logs,
|
|
expectError: false,
|
|
},
|
|
{
|
|
name: "error filtering logs",
|
|
setupMock: func() {
|
|
for i := 0; i < repeatsOnFailure; i++ {
|
|
mockClient.EXPECT().FilterLogs(gomock.Any(), gomock.Any()).
|
|
Return(nil, errors.New("failed to filter logs"))
|
|
}
|
|
},
|
|
expected: nil,
|
|
expectError: true,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
tt.setupMock()
|
|
result, err := client.FilterLogs(ethereum.FilterQuery{})
|
|
if tt.expectError {
|
|
require.Error(t, err)
|
|
require.Contains(t, err.Error(), "retrieving logs")
|
|
} else {
|
|
require.NoError(t, err)
|
|
require.Equal(t, tt.expected, result)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestTransactionReceipt(t *testing.T) {
|
|
originalSleepBetweenRepeats := sleepBetweenRepeats
|
|
sleepBetweenRepeats = 1 * time.Millisecond
|
|
defer func() {
|
|
sleepBetweenRepeats = originalSleepBetweenRepeats
|
|
}()
|
|
ctrl := gomock.NewController(t)
|
|
defer ctrl.Finish()
|
|
mockClient := mockethclient.NewMockClient(ctrl)
|
|
client := Client{
|
|
EthClient: mockClient,
|
|
URL: "http://localhost:8545",
|
|
}
|
|
hash := common.HexToHash("0x123")
|
|
receipt := &types.Receipt{Status: types.ReceiptStatusSuccessful}
|
|
tests := []struct {
|
|
name string
|
|
setupMock func()
|
|
expected *types.Receipt
|
|
expectError bool
|
|
}{
|
|
{
|
|
name: "successful receipt retrieval",
|
|
setupMock: func() {
|
|
mockClient.EXPECT().TransactionReceipt(gomock.Any(), hash).
|
|
Return(receipt, nil)
|
|
},
|
|
expected: receipt,
|
|
expectError: false,
|
|
},
|
|
{
|
|
name: "error getting receipt",
|
|
setupMock: func() {
|
|
for i := 0; i < repeatsOnFailure; i++ {
|
|
mockClient.EXPECT().TransactionReceipt(gomock.Any(), hash).
|
|
Return(nil, errors.New("failed to get receipt"))
|
|
}
|
|
},
|
|
expected: nil,
|
|
expectError: true,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
tt.setupMock()
|
|
result, err := client.TransactionReceipt(hash)
|
|
if tt.expectError {
|
|
require.Error(t, err)
|
|
require.Contains(t, err.Error(), "retrieving receipt")
|
|
} else {
|
|
require.NoError(t, err)
|
|
require.Equal(t, tt.expected, result)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestBlockNumber(t *testing.T) {
|
|
originalSleepBetweenRepeats := sleepBetweenRepeats
|
|
sleepBetweenRepeats = 1 * time.Millisecond
|
|
defer func() {
|
|
sleepBetweenRepeats = originalSleepBetweenRepeats
|
|
}()
|
|
ctrl := gomock.NewController(t)
|
|
defer ctrl.Finish()
|
|
mockClient := mockethclient.NewMockClient(ctrl)
|
|
client := Client{
|
|
EthClient: mockClient,
|
|
URL: "http://localhost:8545",
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
setupMock func()
|
|
expected uint64
|
|
expectError bool
|
|
}{
|
|
{
|
|
name: "successful block number retrieval",
|
|
setupMock: func() {
|
|
mockClient.EXPECT().BlockNumber(gomock.Any()).
|
|
Return(uint64(1000), nil)
|
|
},
|
|
expected: 1000,
|
|
expectError: false,
|
|
},
|
|
{
|
|
name: "error getting block number",
|
|
setupMock: func() {
|
|
for i := 0; i < repeatsOnFailure; i++ {
|
|
mockClient.EXPECT().BlockNumber(gomock.Any()).
|
|
Return(uint64(0), errors.New("failed to get block number"))
|
|
}
|
|
},
|
|
expected: 0,
|
|
expectError: true,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
tt.setupMock()
|
|
result, err := client.BlockNumber()
|
|
if tt.expectError {
|
|
require.Error(t, err)
|
|
require.Contains(t, err.Error(), "retrieving height")
|
|
} else {
|
|
require.NoError(t, err)
|
|
require.Equal(t, tt.expected, result)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGetPrivateKeyBalance(t *testing.T) {
|
|
originalSleepBetweenRepeats := sleepBetweenRepeats
|
|
sleepBetweenRepeats = 1 * time.Millisecond
|
|
defer func() {
|
|
sleepBetweenRepeats = originalSleepBetweenRepeats
|
|
}()
|
|
ctrl := gomock.NewController(t)
|
|
defer ctrl.Finish()
|
|
mockClient := mockethclient.NewMockClient(ctrl)
|
|
client := Client{
|
|
EthClient: mockClient,
|
|
URL: "http://localhost:8545",
|
|
}
|
|
privateKey, err := crypto.GenerateKey()
|
|
require.NoError(t, err)
|
|
privateKeyHex := hex.EncodeToString(crypto.FromECDSA(privateKey))
|
|
address := common.Address(crypto.PubkeyToAddress(privateKey.PublicKey))
|
|
tests := []struct {
|
|
name string
|
|
privateKey string
|
|
setupMock func()
|
|
expected *big.Int
|
|
expectError bool
|
|
}{
|
|
{
|
|
name: "successful balance check",
|
|
privateKey: privateKeyHex,
|
|
setupMock: func() {
|
|
mockClient.EXPECT().BalanceAt(gomock.Any(), address, gomock.Any()).
|
|
Return(big.NewInt(1000), nil)
|
|
},
|
|
expected: big.NewInt(1000),
|
|
expectError: false,
|
|
},
|
|
{
|
|
name: "error getting balance",
|
|
privateKey: privateKeyHex,
|
|
setupMock: func() {
|
|
for i := 0; i < repeatsOnFailure; i++ {
|
|
mockClient.EXPECT().BalanceAt(gomock.Any(), address, gomock.Any()).
|
|
Return(nil, errors.New("failed to get balance"))
|
|
}
|
|
},
|
|
expected: nil,
|
|
expectError: true,
|
|
},
|
|
{
|
|
name: "successful after max failures",
|
|
privateKey: privateKeyHex,
|
|
setupMock: func() {
|
|
for i := 0; i < repeatsOnFailure-1; i++ {
|
|
mockClient.EXPECT().BalanceAt(gomock.Any(), address, gomock.Any()).
|
|
Return(nil, errors.New("failed to get balance"))
|
|
}
|
|
mockClient.EXPECT().BalanceAt(gomock.Any(), address, gomock.Any()).
|
|
Return(big.NewInt(100), nil)
|
|
},
|
|
expected: big.NewInt(100),
|
|
expectError: false,
|
|
},
|
|
{
|
|
name: "invalid private key",
|
|
privateKey: "invalid",
|
|
setupMock: func() {},
|
|
expected: nil,
|
|
expectError: true,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
tt.setupMock()
|
|
balance, err := client.GetPrivateKeyBalance(tt.privateKey)
|
|
if tt.expectError {
|
|
require.Error(t, err)
|
|
if tt.privateKey != "invalid" {
|
|
require.Contains(t, err.Error(), "obtaining balance")
|
|
}
|
|
} else {
|
|
require.NoError(t, err)
|
|
require.Equal(t, tt.expected, balance)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestCalculateTxParams(t *testing.T) {
|
|
originalSleepBetweenRepeats := sleepBetweenRepeats
|
|
sleepBetweenRepeats = 1 * time.Millisecond
|
|
defer func() {
|
|
sleepBetweenRepeats = originalSleepBetweenRepeats
|
|
}()
|
|
ctrl := gomock.NewController(t)
|
|
defer ctrl.Finish()
|
|
mockClient := mockethclient.NewMockClient(ctrl)
|
|
client := Client{
|
|
EthClient: mockClient,
|
|
URL: "http://localhost:8545",
|
|
}
|
|
address := common.HexToAddress("0x1234567890123456789012345678901234567890")
|
|
tests := []struct {
|
|
name string
|
|
address string
|
|
setupMock func()
|
|
expectedFee *big.Int
|
|
expectedTip *big.Int
|
|
expectedNonce uint64
|
|
expectError bool
|
|
}{
|
|
{
|
|
name: "successful calculation",
|
|
address: address.Hex(),
|
|
setupMock: func() {
|
|
// Base fee estimation
|
|
mockClient.EXPECT().EstimateBaseFee(gomock.Any()).
|
|
Return(big.NewInt(10000000000), nil)
|
|
// Gas tip cap suggestion
|
|
mockClient.EXPECT().SuggestGasTipCap(gomock.Any()).
|
|
Return(big.NewInt(1000000000), nil)
|
|
// Nonce retrieval
|
|
mockClient.EXPECT().NonceAt(gomock.Any(), address, gomock.Any()).
|
|
Return(uint64(42), nil)
|
|
},
|
|
expectedFee: big.NewInt(22500000000),
|
|
expectedTip: big.NewInt(1000000000),
|
|
expectedNonce: 42,
|
|
expectError: false,
|
|
},
|
|
{
|
|
name: "error estimating base fee",
|
|
address: address.Hex(),
|
|
setupMock: func() {
|
|
for i := 0; i < repeatsOnFailure; i++ {
|
|
mockClient.EXPECT().EstimateBaseFee(gomock.Any()).
|
|
Return(nil, errors.New("failed to estimate base fee"))
|
|
}
|
|
},
|
|
expectError: true,
|
|
},
|
|
{
|
|
name: "error suggesting gas tip cap",
|
|
address: address.Hex(),
|
|
setupMock: func() {
|
|
// Base fee estimation succeeds
|
|
mockClient.EXPECT().EstimateBaseFee(gomock.Any()).
|
|
Return(big.NewInt(10000000000), nil)
|
|
// Gas tip cap suggestion fails
|
|
for i := 0; i < repeatsOnFailure; i++ {
|
|
mockClient.EXPECT().SuggestGasTipCap(gomock.Any()).
|
|
Return(nil, errors.New("failed to suggest gas tip cap"))
|
|
}
|
|
},
|
|
expectError: true,
|
|
},
|
|
{
|
|
name: "error getting nonce",
|
|
address: address.Hex(),
|
|
setupMock: func() {
|
|
// Base fee estimation succeeds
|
|
mockClient.EXPECT().EstimateBaseFee(gomock.Any()).
|
|
Return(big.NewInt(10000000000), nil)
|
|
// Gas tip cap suggestion succeeds
|
|
mockClient.EXPECT().SuggestGasTipCap(gomock.Any()).
|
|
Return(big.NewInt(1000000000), nil)
|
|
// Nonce retrieval fails
|
|
for i := 0; i < repeatsOnFailure; i++ {
|
|
mockClient.EXPECT().NonceAt(gomock.Any(), address, gomock.Any()).
|
|
Return(uint64(0), errors.New("failed to get nonce"))
|
|
}
|
|
},
|
|
expectError: true,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
tt.setupMock()
|
|
gasFeeCap, gasTipCap, nonce, err := client.CalculateTxParams(tt.address)
|
|
if tt.expectError {
|
|
require.Error(t, err)
|
|
require.Contains(t, err.Error(), "failure")
|
|
} else {
|
|
require.NoError(t, err)
|
|
require.Equal(t, tt.expectedFee, gasFeeCap)
|
|
require.Equal(t, tt.expectedTip, gasTipCap)
|
|
require.Equal(t, tt.expectedNonce, nonce)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestFundAddress(t *testing.T) {
|
|
originalSleepBetweenRepeats := sleepBetweenRepeats
|
|
sleepBetweenRepeats = 1 * time.Millisecond
|
|
defer func() {
|
|
sleepBetweenRepeats = originalSleepBetweenRepeats
|
|
}()
|
|
ctrl := gomock.NewController(t)
|
|
defer ctrl.Finish()
|
|
mockClient := mockethclient.NewMockClient(ctrl)
|
|
client := Client{
|
|
EthClient: mockClient,
|
|
URL: "http://localhost:8545",
|
|
}
|
|
privateKey, err := crypto.GenerateKey()
|
|
require.NoError(t, err)
|
|
privateKeyHex := hex.EncodeToString(crypto.FromECDSA(privateKey))
|
|
sourceAddress := common.Address(crypto.PubkeyToAddress(privateKey.PublicKey))
|
|
targetAddress := common.HexToAddress("0x1234567890123456789012345678901234567890")
|
|
amount := big.NewInt(1000000000000000000) // 1 ETH
|
|
tests := []struct {
|
|
name string
|
|
setupMock func()
|
|
expectError bool
|
|
}{
|
|
{
|
|
name: "successful fund transfer",
|
|
setupMock: func() {
|
|
// CalculateTxParams
|
|
mockClient.EXPECT().EstimateBaseFee(gomock.Any()).
|
|
Return(big.NewInt(10000000000), nil)
|
|
mockClient.EXPECT().SuggestGasTipCap(gomock.Any()).
|
|
Return(big.NewInt(1000000000), nil)
|
|
mockClient.EXPECT().NonceAt(gomock.Any(), sourceAddress, gomock.Any()).
|
|
Return(uint64(42), nil)
|
|
// GetChainID
|
|
mockClient.EXPECT().ChainID(gomock.Any()).
|
|
Return(big.NewInt(43114), nil)
|
|
// SendTransaction
|
|
mockClient.EXPECT().SendTransaction(gomock.Any(), gomock.Any()).
|
|
Return(nil)
|
|
// TransactionReceipt
|
|
mockClient.EXPECT().TransactionReceipt(gomock.Any(), gomock.Any()).
|
|
Return(&types.Receipt{Status: types.ReceiptStatusSuccessful}, nil)
|
|
},
|
|
expectError: false,
|
|
},
|
|
{
|
|
name: "error calculating tx params",
|
|
setupMock: func() {
|
|
for i := 0; i < repeatsOnFailure; i++ {
|
|
mockClient.EXPECT().EstimateBaseFee(gomock.Any()).
|
|
Return(nil, errors.New("failed to estimate base fee"))
|
|
}
|
|
},
|
|
expectError: true,
|
|
},
|
|
{
|
|
name: "error getting chain ID",
|
|
setupMock: func() {
|
|
// CalculateTxParams succeeds
|
|
mockClient.EXPECT().EstimateBaseFee(gomock.Any()).
|
|
Return(big.NewInt(10000000000), nil)
|
|
mockClient.EXPECT().SuggestGasTipCap(gomock.Any()).
|
|
Return(big.NewInt(1000000000), nil)
|
|
mockClient.EXPECT().NonceAt(gomock.Any(), sourceAddress, gomock.Any()).
|
|
Return(uint64(42), nil)
|
|
// GetChainID fails
|
|
for i := 0; i < repeatsOnFailure; i++ {
|
|
mockClient.EXPECT().ChainID(gomock.Any()).
|
|
Return(nil, errors.New("failed to get chain ID"))
|
|
}
|
|
},
|
|
expectError: true,
|
|
},
|
|
{
|
|
name: "error sending transaction",
|
|
setupMock: func() {
|
|
// CalculateTxParams succeeds
|
|
mockClient.EXPECT().EstimateBaseFee(gomock.Any()).
|
|
Return(big.NewInt(10000000000), nil)
|
|
mockClient.EXPECT().SuggestGasTipCap(gomock.Any()).
|
|
Return(big.NewInt(1000000000), nil)
|
|
mockClient.EXPECT().NonceAt(gomock.Any(), sourceAddress, gomock.Any()).
|
|
Return(uint64(42), nil)
|
|
// GetChainID succeeds
|
|
mockClient.EXPECT().ChainID(gomock.Any()).
|
|
Return(big.NewInt(43114), nil)
|
|
// SendTransaction fails
|
|
for i := 0; i < repeatsOnFailure; i++ {
|
|
mockClient.EXPECT().SendTransaction(gomock.Any(), gomock.Any()).
|
|
Return(errors.New("failed to send transaction"))
|
|
}
|
|
},
|
|
expectError: true,
|
|
},
|
|
{
|
|
name: "transaction failed",
|
|
setupMock: func() {
|
|
// CalculateTxParams succeeds
|
|
mockClient.EXPECT().EstimateBaseFee(gomock.Any()).
|
|
Return(big.NewInt(10000000000), nil)
|
|
mockClient.EXPECT().SuggestGasTipCap(gomock.Any()).
|
|
Return(big.NewInt(1000000000), nil)
|
|
mockClient.EXPECT().NonceAt(gomock.Any(), sourceAddress, gomock.Any()).
|
|
Return(uint64(42), nil)
|
|
// GetChainID succeeds
|
|
mockClient.EXPECT().ChainID(gomock.Any()).
|
|
Return(big.NewInt(43114), nil)
|
|
// SendTransaction succeeds
|
|
mockClient.EXPECT().SendTransaction(gomock.Any(), gomock.Any()).
|
|
Return(nil)
|
|
// TransactionReceipt returns failed status
|
|
mockClient.EXPECT().TransactionReceipt(gomock.Any(), gomock.Any()).
|
|
Return(&types.Receipt{Status: types.ReceiptStatusFailed}, nil)
|
|
},
|
|
expectError: true,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
tt.setupMock()
|
|
_, err := client.FundAddress(privateKeyHex, targetAddress.Hex(), amount)
|
|
if tt.expectError {
|
|
require.Error(t, err)
|
|
require.Contains(t, err.Error(), "failure")
|
|
} else {
|
|
require.NoError(t, err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestIssueTx(t *testing.T) {
|
|
originalSleepBetweenRepeats := sleepBetweenRepeats
|
|
sleepBetweenRepeats = 1 * time.Millisecond
|
|
defer func() {
|
|
sleepBetweenRepeats = originalSleepBetweenRepeats
|
|
}()
|
|
ctrl := gomock.NewController(t)
|
|
defer ctrl.Finish()
|
|
mockClient := mockethclient.NewMockClient(ctrl)
|
|
client := Client{
|
|
EthClient: mockClient,
|
|
URL: "http://localhost:8545",
|
|
}
|
|
tx := types.NewTransaction(0, common.Address{}, nil, 0, nil, nil)
|
|
txBytes, err := tx.MarshalBinary()
|
|
require.NoError(t, err)
|
|
txHex := hex.EncodeToString(txBytes)
|
|
tests := []struct {
|
|
name string
|
|
txStr string
|
|
setupMock func()
|
|
expectError bool
|
|
}{
|
|
{
|
|
name: "successful transaction",
|
|
txStr: txHex,
|
|
setupMock: func() {
|
|
// SendTransaction
|
|
mockClient.EXPECT().SendTransaction(gomock.Any(), gomock.Any()).
|
|
Return(nil)
|
|
// TransactionReceipt
|
|
mockClient.EXPECT().TransactionReceipt(gomock.Any(), gomock.Any()).
|
|
Return(&types.Receipt{Status: types.ReceiptStatusSuccessful}, nil)
|
|
},
|
|
expectError: false,
|
|
},
|
|
{
|
|
name: "invalid transaction hex",
|
|
txStr: "invalid",
|
|
setupMock: func() {},
|
|
expectError: true,
|
|
},
|
|
{
|
|
name: "error sending transaction",
|
|
txStr: txHex,
|
|
setupMock: func() {
|
|
for i := 0; i < repeatsOnFailure; i++ {
|
|
mockClient.EXPECT().SendTransaction(gomock.Any(), gomock.Any()).
|
|
Return(errors.New("failed to send transaction"))
|
|
}
|
|
},
|
|
expectError: true,
|
|
},
|
|
{
|
|
name: "transaction failed",
|
|
txStr: txHex,
|
|
setupMock: func() {
|
|
// SendTransaction succeeds
|
|
mockClient.EXPECT().SendTransaction(gomock.Any(), gomock.Any()).
|
|
Return(nil)
|
|
// TransactionReceipt returns failed status
|
|
mockClient.EXPECT().TransactionReceipt(gomock.Any(), gomock.Any()).
|
|
Return(&types.Receipt{Status: types.ReceiptStatusFailed}, nil)
|
|
},
|
|
expectError: true,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
tt.setupMock()
|
|
err := client.IssueTx(tt.txStr)
|
|
if tt.expectError {
|
|
require.Error(t, err)
|
|
if tt.name != "invalid transaction hex" {
|
|
require.Contains(t, err.Error(), "failure")
|
|
}
|
|
} else {
|
|
require.NoError(t, err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGetTxOptsWithSigner(t *testing.T) {
|
|
originalSleepBetweenRepeats := sleepBetweenRepeats
|
|
sleepBetweenRepeats = 1 * time.Millisecond
|
|
defer func() {
|
|
sleepBetweenRepeats = originalSleepBetweenRepeats
|
|
}()
|
|
ctrl := gomock.NewController(t)
|
|
defer ctrl.Finish()
|
|
mockClient := mockethclient.NewMockClient(ctrl)
|
|
client := Client{
|
|
EthClient: mockClient,
|
|
URL: "http://localhost:8545",
|
|
}
|
|
privateKey, err := crypto.GenerateKey()
|
|
require.NoError(t, err)
|
|
privateKeyHex := hex.EncodeToString(crypto.FromECDSA(privateKey))
|
|
tests := []struct {
|
|
name string
|
|
privateKey string
|
|
setupMock func()
|
|
expectError bool
|
|
}{
|
|
{
|
|
name: "successful signer creation",
|
|
privateKey: privateKeyHex,
|
|
setupMock: func() {
|
|
mockClient.EXPECT().ChainID(gomock.Any()).
|
|
Return(big.NewInt(43114), nil)
|
|
},
|
|
expectError: false,
|
|
},
|
|
{
|
|
name: "invalid private key",
|
|
privateKey: "invalid",
|
|
setupMock: func() {},
|
|
expectError: true,
|
|
},
|
|
{
|
|
name: "error getting chain ID",
|
|
privateKey: privateKeyHex,
|
|
setupMock: func() {
|
|
for i := 0; i < repeatsOnFailure; i++ {
|
|
mockClient.EXPECT().ChainID(gomock.Any()).
|
|
Return(nil, errors.New("failed to get chain ID"))
|
|
}
|
|
},
|
|
expectError: true,
|
|
},
|
|
{
|
|
name: "successful after max failures",
|
|
privateKey: privateKeyHex,
|
|
setupMock: func() {
|
|
for i := 0; i < repeatsOnFailure-1; i++ {
|
|
mockClient.EXPECT().ChainID(gomock.Any()).
|
|
Return(nil, errors.New("failed to get chain ID"))
|
|
}
|
|
mockClient.EXPECT().ChainID(gomock.Any()).
|
|
Return(big.NewInt(43114), nil)
|
|
},
|
|
expectError: false,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
tt.setupMock()
|
|
opts, err := client.GetTxOptsWithSigner(tt.privateKey)
|
|
if tt.expectError {
|
|
require.Error(t, err)
|
|
if tt.privateKey != "invalid" {
|
|
require.Contains(t, err.Error(), "failure generating signer")
|
|
}
|
|
} else {
|
|
require.NoError(t, err)
|
|
require.NotNil(t, opts)
|
|
require.NotNil(t, opts.Signer)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestWaitForEVMBootstrapped(t *testing.T) {
|
|
originalSleepBetweenRepeats := sleepBetweenRepeats
|
|
sleepBetweenRepeats = 1 * time.Millisecond
|
|
defer func() {
|
|
sleepBetweenRepeats = originalSleepBetweenRepeats
|
|
}()
|
|
ctrl := gomock.NewController(t)
|
|
defer ctrl.Finish()
|
|
mockClient := mockethclient.NewMockClient(ctrl)
|
|
client := Client{
|
|
EthClient: mockClient,
|
|
URL: "http://localhost:8545",
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
timeout time.Duration
|
|
setupMock func()
|
|
expectError bool
|
|
}{
|
|
{
|
|
name: "successful bootstrap",
|
|
timeout: 1 * time.Second,
|
|
setupMock: func() {
|
|
mockClient.EXPECT().ChainID(gomock.Any()).
|
|
Return(big.NewInt(43114), nil)
|
|
},
|
|
expectError: false,
|
|
},
|
|
{
|
|
name: "timeout waiting for bootstrap",
|
|
timeout: 100 * time.Millisecond,
|
|
setupMock: func() {
|
|
// Simulate multiple failures until timeout
|
|
for i := 0; i < 10; i++ {
|
|
mockClient.EXPECT().ChainID(gomock.Any()).
|
|
Return(nil, errors.New("not bootstrapped"))
|
|
}
|
|
},
|
|
expectError: true,
|
|
},
|
|
{
|
|
name: "default timeout",
|
|
timeout: 0,
|
|
setupMock: func() {
|
|
mockClient.EXPECT().ChainID(gomock.Any()).
|
|
Return(big.NewInt(43114), nil)
|
|
},
|
|
expectError: false,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
tt.setupMock()
|
|
err := client.WaitForEVMBootstrapped(tt.timeout)
|
|
if tt.expectError {
|
|
require.Error(t, err)
|
|
require.Contains(t, err.Error(), "not bootstrapped")
|
|
} else {
|
|
require.NoError(t, err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestTransactWithWarpMessage(t *testing.T) {
|
|
originalSleepBetweenRepeats := sleepBetweenRepeats
|
|
sleepBetweenRepeats = 1 * time.Millisecond
|
|
defer func() {
|
|
sleepBetweenRepeats = originalSleepBetweenRepeats
|
|
}()
|
|
ctrl := gomock.NewController(t)
|
|
defer ctrl.Finish()
|
|
mockClient := mockethclient.NewMockClient(ctrl)
|
|
client := Client{
|
|
EthClient: mockClient,
|
|
URL: "http://localhost:8545",
|
|
}
|
|
privateKey, err := crypto.GenerateKey()
|
|
require.NoError(t, err)
|
|
privateKeyHex := hex.EncodeToString(crypto.FromECDSA(privateKey))
|
|
fromAddress := common.Address(crypto.PubkeyToAddress(privateKey.PublicKey))
|
|
contractAddress := common.HexToAddress("0x1234567890123456789012345678901234567890")
|
|
callData := []byte{1, 2, 3, 4, 5}
|
|
value := big.NewInt(1000000000000000000) // 1 ETH
|
|
sourceChainID := ids.ID{1, 2, 3}
|
|
unsignedMessage, err := luxWarp.NewMessage(
|
|
0, // NetworkID
|
|
sourceChainID,
|
|
[]byte{4, 5, 6},
|
|
)
|
|
require.NoError(t, err)
|
|
// A Message is flat now — it carries no signature of its own, so there is
|
|
// no second type to wrap it in. Signatures live on an Envelope.
|
|
warpMessage := unsignedMessage
|
|
tests := []struct {
|
|
name string
|
|
from crypto.Address
|
|
privateKey string
|
|
warpMessage *luxWarp.Message
|
|
contract crypto.Address
|
|
callData []byte
|
|
value *big.Int
|
|
generateRawTxOnly bool
|
|
setupMock func()
|
|
expectError bool
|
|
}{
|
|
{
|
|
name: "successful transaction with private key",
|
|
from: crypto.Address{},
|
|
privateKey: privateKeyHex,
|
|
warpMessage: warpMessage,
|
|
contract: crypto.Address(contractAddress),
|
|
callData: callData,
|
|
value: value,
|
|
generateRawTxOnly: false,
|
|
setupMock: func() {
|
|
// CalculateTxParams
|
|
mockClient.EXPECT().EstimateBaseFee(gomock.Any()).
|
|
Return(big.NewInt(10000000000), nil)
|
|
mockClient.EXPECT().SuggestGasTipCap(gomock.Any()).
|
|
Return(big.NewInt(1000000000), nil)
|
|
mockClient.EXPECT().NonceAt(gomock.Any(), fromAddress, gomock.Any()).
|
|
Return(uint64(42), nil)
|
|
// GetChainID
|
|
mockClient.EXPECT().ChainID(gomock.Any()).
|
|
Return(big.NewInt(43114), nil)
|
|
// EstimateGasLimit
|
|
mockClient.EXPECT().EstimateGas(gomock.Any(), gomock.Any()).
|
|
Return(uint64(21000), nil)
|
|
},
|
|
expectError: false,
|
|
},
|
|
{
|
|
name: "successful raw transaction with from address",
|
|
from: crypto.Address(fromAddress),
|
|
privateKey: "",
|
|
warpMessage: warpMessage,
|
|
contract: crypto.Address(contractAddress),
|
|
callData: callData,
|
|
value: value,
|
|
generateRawTxOnly: true,
|
|
setupMock: func() {
|
|
// CalculateTxParams
|
|
mockClient.EXPECT().EstimateBaseFee(gomock.Any()).
|
|
Return(big.NewInt(10000000000), nil)
|
|
mockClient.EXPECT().SuggestGasTipCap(gomock.Any()).
|
|
Return(big.NewInt(1000000000), nil)
|
|
mockClient.EXPECT().NonceAt(gomock.Any(), fromAddress, gomock.Any()).
|
|
Return(uint64(42), nil)
|
|
// GetChainID
|
|
mockClient.EXPECT().ChainID(gomock.Any()).
|
|
Return(big.NewInt(43114), nil)
|
|
// EstimateGasLimit
|
|
mockClient.EXPECT().EstimateGas(gomock.Any(), gomock.Any()).
|
|
Return(uint64(21000), nil)
|
|
},
|
|
expectError: false,
|
|
},
|
|
{
|
|
name: "error calculating tx params",
|
|
from: crypto.Address{},
|
|
privateKey: privateKeyHex,
|
|
warpMessage: warpMessage,
|
|
contract: crypto.Address(contractAddress),
|
|
callData: callData,
|
|
value: value,
|
|
generateRawTxOnly: false,
|
|
setupMock: func() {
|
|
for i := 0; i < repeatsOnFailure; i++ {
|
|
mockClient.EXPECT().EstimateBaseFee(gomock.Any()).
|
|
Return(nil, errors.New("failed to estimate base fee"))
|
|
}
|
|
},
|
|
expectError: true,
|
|
},
|
|
{
|
|
name: "error getting chain ID",
|
|
from: crypto.Address{},
|
|
privateKey: privateKeyHex,
|
|
warpMessage: warpMessage,
|
|
contract: crypto.Address(contractAddress),
|
|
callData: callData,
|
|
value: value,
|
|
generateRawTxOnly: false,
|
|
setupMock: func() {
|
|
// CalculateTxParams succeeds
|
|
mockClient.EXPECT().EstimateBaseFee(gomock.Any()).
|
|
Return(big.NewInt(10000000000), nil)
|
|
mockClient.EXPECT().SuggestGasTipCap(gomock.Any()).
|
|
Return(big.NewInt(1000000000), nil)
|
|
mockClient.EXPECT().NonceAt(gomock.Any(), fromAddress, gomock.Any()).
|
|
Return(uint64(42), nil)
|
|
// GetChainID fails
|
|
for i := 0; i < repeatsOnFailure; i++ {
|
|
mockClient.EXPECT().ChainID(gomock.Any()).
|
|
Return(nil, errors.New("failed to get chain ID"))
|
|
}
|
|
},
|
|
expectError: true,
|
|
},
|
|
{
|
|
name: "error estimating gas limit",
|
|
from: crypto.Address{},
|
|
privateKey: privateKeyHex,
|
|
warpMessage: warpMessage,
|
|
contract: crypto.Address(contractAddress),
|
|
callData: callData,
|
|
value: value,
|
|
generateRawTxOnly: false,
|
|
setupMock: func() {
|
|
// CalculateTxParams succeeds
|
|
mockClient.EXPECT().EstimateBaseFee(gomock.Any()).
|
|
Return(big.NewInt(10000000000), nil)
|
|
mockClient.EXPECT().SuggestGasTipCap(gomock.Any()).
|
|
Return(big.NewInt(1000000000), nil)
|
|
mockClient.EXPECT().NonceAt(gomock.Any(), fromAddress, gomock.Any()).
|
|
Return(uint64(42), nil)
|
|
// GetChainID succeeds
|
|
mockClient.EXPECT().ChainID(gomock.Any()).
|
|
Return(big.NewInt(43114), nil)
|
|
// EstimateGasLimit fails
|
|
for i := 0; i < repeatsOnFailure; i++ {
|
|
mockClient.EXPECT().EstimateGas(gomock.Any(), gomock.Any()).
|
|
Return(uint64(0), errors.New("failed to estimate gas"))
|
|
}
|
|
},
|
|
expectError: false, // Should use default gas limit
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
tt.setupMock()
|
|
tx, err := client.TransactWithWarpMessage(
|
|
tt.from,
|
|
tt.privateKey,
|
|
tt.warpMessage,
|
|
tt.contract,
|
|
tt.callData,
|
|
tt.value,
|
|
tt.generateRawTxOnly,
|
|
)
|
|
if tt.expectError {
|
|
require.Error(t, err)
|
|
require.Contains(t, err.Error(), "failure")
|
|
} else {
|
|
require.NoError(t, err)
|
|
require.NotNil(t, tx)
|
|
if !tt.generateRawTxOnly {
|
|
require.NotNil(t, tx.Hash())
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestWaitForNewBlock(t *testing.T) {
|
|
originalSleepBetweenRepeats := sleepBetweenRepeats
|
|
sleepBetweenRepeats = 1 * time.Millisecond
|
|
defer func() {
|
|
sleepBetweenRepeats = originalSleepBetweenRepeats
|
|
}()
|
|
ctrl := gomock.NewController(t)
|
|
defer ctrl.Finish()
|
|
mockClient := mockethclient.NewMockClient(ctrl)
|
|
client := Client{
|
|
EthClient: mockClient,
|
|
URL: "http://localhost:8545",
|
|
}
|
|
initialBlock := uint64(1000)
|
|
newBlock := uint64(1001)
|
|
tests := []struct {
|
|
name string
|
|
prevBlockNumber uint64
|
|
totalDuration time.Duration
|
|
setupMock func()
|
|
expectError bool
|
|
}{
|
|
{
|
|
name: "successful block retrieval",
|
|
prevBlockNumber: initialBlock,
|
|
setupMock: func() {
|
|
// new block found
|
|
mockClient.EXPECT().BlockNumber(gomock.Any()).
|
|
Return(newBlock, nil)
|
|
},
|
|
expectError: false,
|
|
},
|
|
{
|
|
name: "timeout waiting for new block",
|
|
prevBlockNumber: initialBlock,
|
|
setupMock: func() {
|
|
// Simulate multiple checks with same block number until timeout
|
|
for i := 0; i < 10; i++ {
|
|
mockClient.EXPECT().BlockNumber(gomock.Any()).
|
|
Return(initialBlock, nil)
|
|
}
|
|
},
|
|
expectError: true,
|
|
},
|
|
{
|
|
name: "error getting block number",
|
|
prevBlockNumber: initialBlock,
|
|
setupMock: func() {
|
|
for i := 0; i < repeatsOnFailure; i++ {
|
|
mockClient.EXPECT().BlockNumber(gomock.Any()).
|
|
Return(uint64(0), errors.New("failed to get block number"))
|
|
}
|
|
},
|
|
expectError: true,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
tt.setupMock()
|
|
err := client.WaitForNewBlock(tt.prevBlockNumber, tt.totalDuration)
|
|
if tt.expectError {
|
|
require.Error(t, err)
|
|
if tt.name == "timeout waiting for new block" {
|
|
require.Contains(t, err.Error(), "no new block produced")
|
|
} else {
|
|
require.Contains(t, err.Error(), "failure")
|
|
}
|
|
} else {
|
|
require.NoError(t, err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestSetupProposerVM(t *testing.T) {
|
|
t.Skip("Skipping - test has mock timing issues causing timeout")
|
|
originalSleepBetweenRepeats := sleepBetweenRepeats
|
|
sleepBetweenRepeats = 1 * time.Millisecond
|
|
defer func() {
|
|
sleepBetweenRepeats = originalSleepBetweenRepeats
|
|
}()
|
|
|
|
privateKey, err := crypto.GenerateKey()
|
|
require.NoError(t, err)
|
|
privateKeyHex := hex.EncodeToString(crypto.FromECDSA(privateKey))
|
|
address := common.Address(crypto.PubkeyToAddress(privateKey.PublicKey))
|
|
chainID := big.NewInt(43114)
|
|
tests := []struct {
|
|
name string
|
|
privateKey string
|
|
setupMock func(*mockethclient.MockClient)
|
|
expectError bool
|
|
}{
|
|
{
|
|
name: "successful setup",
|
|
privateKey: privateKeyHex,
|
|
setupMock: func(mockClient *mockethclient.MockClient) {
|
|
// Allow flexible calls since the implementation does retries and checks
|
|
mockClient.EXPECT().ChainID(gomock.Any()).
|
|
Return(chainID, nil).AnyTimes()
|
|
|
|
// Track block progression
|
|
blockNum := uint64(1000)
|
|
mockClient.EXPECT().BlockNumber(gomock.Any()).
|
|
DoAndReturn(func(_ interface{}) (uint64, error) {
|
|
return blockNum, nil
|
|
}).AnyTimes()
|
|
|
|
// Track nonce
|
|
nonce := uint64(0)
|
|
mockClient.EXPECT().NonceAt(gomock.Any(), address, gomock.Any()).
|
|
DoAndReturn(func(_ interface{}, _ interface{}, _ interface{}) (uint64, error) {
|
|
return nonce, nil
|
|
}).AnyTimes()
|
|
|
|
// Accept transactions and increment state
|
|
mockClient.EXPECT().SendTransaction(gomock.Any(), gomock.Any()).
|
|
DoAndReturn(func(_ interface{}, _ interface{}) error {
|
|
blockNum++
|
|
nonce++
|
|
return nil
|
|
}).Times(2) // Expect exactly 2 transactions for 2 blocks
|
|
},
|
|
expectError: false,
|
|
},
|
|
{
|
|
name: "error getting chain ID",
|
|
privateKey: privateKeyHex,
|
|
setupMock: func(mockClient *mockethclient.MockClient) {
|
|
for i := 0; i < repeatsOnFailure*repeatsOnFailure; i++ {
|
|
mockClient.EXPECT().ChainID(gomock.Any()).
|
|
Return(nil, errors.New("failed to get chain ID"))
|
|
}
|
|
},
|
|
expectError: true,
|
|
},
|
|
{
|
|
name: "error getting block number",
|
|
privateKey: privateKeyHex,
|
|
setupMock: func(mockClient *mockethclient.MockClient) {
|
|
for i := 0; i < repeatsOnFailure; i++ {
|
|
// GetChainID succeeds
|
|
mockClient.EXPECT().ChainID(gomock.Any()).
|
|
Return(chainID, nil)
|
|
// First block - error getting block number
|
|
for i := 0; i < repeatsOnFailure; i++ {
|
|
mockClient.EXPECT().BlockNumber(gomock.Any()).
|
|
Return(uint64(0), errors.New("failed to get block number"))
|
|
}
|
|
}
|
|
},
|
|
expectError: true,
|
|
},
|
|
{
|
|
name: "error getting nonce",
|
|
privateKey: privateKeyHex,
|
|
setupMock: func(mockClient *mockethclient.MockClient) {
|
|
for i := 0; i < repeatsOnFailure; i++ {
|
|
// GetChainID succeeds
|
|
mockClient.EXPECT().ChainID(gomock.Any()).
|
|
Return(chainID, nil)
|
|
// First block - error getting nonce
|
|
mockClient.EXPECT().BlockNumber(gomock.Any()).
|
|
Return(uint64(1000), nil)
|
|
for i := 0; i < repeatsOnFailure; i++ {
|
|
mockClient.EXPECT().NonceAt(gomock.Any(), address, gomock.Any()).
|
|
Return(uint64(0), errors.New("failed to get nonce"))
|
|
}
|
|
}
|
|
},
|
|
expectError: true,
|
|
},
|
|
{
|
|
name: "error sending transaction",
|
|
privateKey: privateKeyHex,
|
|
setupMock: func(mockClient *mockethclient.MockClient) {
|
|
// Allow any number of these calls since retries can vary
|
|
mockClient.EXPECT().ChainID(gomock.Any()).
|
|
Return(chainID, nil).AnyTimes()
|
|
mockClient.EXPECT().BlockNumber(gomock.Any()).
|
|
Return(uint64(1000), nil).AnyTimes()
|
|
mockClient.EXPECT().NonceAt(gomock.Any(), address, gomock.Any()).
|
|
Return(uint64(0), nil).AnyTimes()
|
|
// Always fail on SendTransaction
|
|
mockClient.EXPECT().SendTransaction(gomock.Any(), gomock.Any()).
|
|
Return(errors.New("failed to send transaction")).AnyTimes()
|
|
},
|
|
expectError: true,
|
|
},
|
|
{
|
|
name: "invalid private key",
|
|
privateKey: "invalid",
|
|
setupMock: func(mockClient *mockethclient.MockClient) {},
|
|
expectError: true,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
ctrl := gomock.NewController(t)
|
|
defer ctrl.Finish()
|
|
mockClient := mockethclient.NewMockClient(ctrl)
|
|
client := Client{
|
|
EthClient: mockClient,
|
|
URL: "http://localhost:8545",
|
|
}
|
|
tt.setupMock(mockClient)
|
|
err := client.SetupProposerVM(tt.privateKey)
|
|
if tt.expectError {
|
|
require.Error(t, err)
|
|
if tt.name != "invalid private key" {
|
|
require.Contains(t, err.Error(), "failure")
|
|
}
|
|
} else {
|
|
require.NoError(t, err)
|
|
}
|
|
})
|
|
}
|
|
}
|