github.com/cgcardona/r-subnet-evm@v0.1.5/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/cgcardona/r-subnet-evm/core/types" 15 "github.com/cgcardona/r-subnet-evm/ethclient" 16 "github.com/cgcardona/r-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 }