github.com/MetalBlockchain/subnet-evm@v0.4.9/tests/utils/evm_client.go (about)

     1  // Copyright (C) 2019-2022, Ava Labs, Inc. All rights reserved.
     2  // See the file LICENSE for licensing terms.
     3  
     4  package utils
     5  
     6  import (
     7  	"context"
     8  	"crypto/ecdsa"
     9  	"fmt"
    10  	"log"
    11  	"math/big"
    12  	"time"
    13  
    14  	"github.com/MetalBlockchain/subnet-evm/core/types"
    15  	"github.com/MetalBlockchain/subnet-evm/ethclient"
    16  	"github.com/MetalBlockchain/subnet-evm/params"
    17  
    18  	"github.com/ethereum/go-ethereum/common"
    19  )
    20  
    21  type EvmClient struct {
    22  	rpcEp string
    23  
    24  	ethClient ethclient.Client
    25  	chainID   *big.Int
    26  	signer    types.Signer
    27  
    28  	feeCap      *big.Int
    29  	priorityFee *big.Int
    30  }
    31  
    32  func NewEvmClient(ep string, baseFee uint64, priorityFee uint64) (*EvmClient, error) {
    33  	ethCli, err := ethclient.Dial(ep)
    34  	if err != nil {
    35  		return nil, err
    36  	}
    37  
    38  	ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
    39  	defer cancel()
    40  
    41  	chainID, err := ethCli.ChainID(ctx)
    42  	if err != nil {
    43  		return nil, err
    44  	}
    45  
    46  	pFee := new(big.Int).SetUint64(priorityFee * params.GWei)
    47  	feeCap := new(big.Int).Add(new(big.Int).SetUint64(baseFee*params.GWei), pFee)
    48  
    49  	return &EvmClient{
    50  		rpcEp: ep,
    51  
    52  		ethClient: ethCli,
    53  		chainID:   chainID,
    54  		signer:    types.LatestSignerForChainID(chainID),
    55  
    56  		feeCap:      feeCap,
    57  		priorityFee: pFee,
    58  	}, nil
    59  }
    60  
    61  func (ec *EvmClient) FetchBalance(ctx context.Context, addr common.Address) (*big.Int, error) {
    62  	for ctx.Err() == nil {
    63  		balance, err := ec.ethClient.BalanceAt(ctx, addr, nil)
    64  		if err != nil {
    65  			log.Printf("could not get balance: %s", err.Error())
    66  			time.Sleep(time.Second)
    67  			continue
    68  		}
    69  		return balance, nil
    70  	}
    71  	return nil, ctx.Err()
    72  }
    73  
    74  func (ec *EvmClient) FetchNonce(ctx context.Context, addr common.Address) (uint64, error) {
    75  	for ctx.Err() == nil {
    76  		nonce, err := ec.ethClient.NonceAt(ctx, addr, nil)
    77  		if err != nil {
    78  			log.Printf("could not get nonce: %s", err.Error())
    79  			time.Sleep(time.Second)
    80  			continue
    81  		}
    82  		return nonce, nil
    83  	}
    84  	return 0, ctx.Err()
    85  }
    86  
    87  func (ec *EvmClient) WaitForBalance(ctx context.Context, addr common.Address, minBalance *big.Int) error {
    88  	for ctx.Err() == nil {
    89  		bal, err := ec.FetchBalance(ctx, addr)
    90  		if err != nil {
    91  			log.Printf("could not get balance: %s", err.Error())
    92  			time.Sleep(time.Second)
    93  			continue
    94  		}
    95  
    96  		if bal.Cmp(minBalance) >= 0 {
    97  			log.Printf("found balance of %s", bal.String())
    98  			return nil
    99  		}
   100  
   101  		log.Printf("waiting for balance of %s on %s", minBalance.String(), addr.Hex())
   102  		time.Sleep(5 * time.Second)
   103  	}
   104  
   105  	return ctx.Err()
   106  }
   107  
   108  func (ec *EvmClient) ConfirmTx(ctx context.Context, txHash common.Hash) (*big.Int, error) {
   109  	for ctx.Err() == nil {
   110  		result, pending, _ := ec.ethClient.TransactionByHash(ctx, txHash)
   111  		if result == nil || pending {
   112  			time.Sleep(time.Second)
   113  			continue
   114  		}
   115  		// XXX: this uses gas instead of gas used, so it may be incorrect if the transaction does more than a simple transfer
   116  		return result.Cost(), nil
   117  	}
   118  	return nil, ctx.Err()
   119  }
   120  
   121  // makes transfer tx and returns the new balance of sender
   122  func (ec *EvmClient) TransferTx(
   123  	ctx context.Context,
   124  	sender common.Address,
   125  	senderPriv *ecdsa.PrivateKey,
   126  	recipient common.Address,
   127  	transferAmount *big.Int) (*big.Int, error) {
   128  	for ctx.Err() == nil {
   129  		senderBal, err := ec.FetchBalance(ctx, sender)
   130  		if err != nil {
   131  			return nil, fmt.Errorf("failed to fetch balance: %w", err)
   132  		}
   133  
   134  		if senderBal.Cmp(transferAmount) < 0 {
   135  			return nil, fmt.Errorf("not enough balance %s to transfer %s", senderBal, transferAmount)
   136  		}
   137  
   138  		nonce, err := ec.FetchNonce(ctx, sender)
   139  		if err != nil {
   140  			return nil, err
   141  		}
   142  
   143  		signedTx, err := types.SignTx(
   144  			types.NewTx(&types.DynamicFeeTx{
   145  				ChainID:   ec.chainID,
   146  				Nonce:     nonce,
   147  				To:        &recipient,
   148  				Gas:       uint64(21000),
   149  				GasFeeCap: ec.feeCap,
   150  				GasTipCap: ec.priorityFee,
   151  				Value:     transferAmount,
   152  			}),
   153  			ec.signer,
   154  			senderPriv,
   155  		)
   156  		if err != nil {
   157  			return nil, fmt.Errorf("failed to sign transaction: %w", err)
   158  		}
   159  
   160  		if err := ec.ethClient.SendTransaction(ctx, signedTx); err != nil {
   161  			log.Printf("failed to send transaction: %v (key address %s)", err, sender)
   162  			return nil, err
   163  		}
   164  
   165  		txHash := signedTx.Hash()
   166  		cost, err := ec.ConfirmTx(ctx, txHash)
   167  		if err != nil {
   168  			return nil, err
   169  		}
   170  
   171  		senderBal = new(big.Int).Sub(senderBal, cost)
   172  		senderBal = new(big.Int).Sub(senderBal, transferAmount)
   173  		return senderBal, nil
   174  	}
   175  
   176  	return nil, ctx.Err()
   177  }