From 3a56eb23eed435e6d643ed58e6100ecf781c1dab Mon Sep 17 00:00:00 2001 From: Brian Stafford Date: Thu, 23 May 2024 09:05:45 -0500 Subject: [PATCH] use erc20 contractor for token contract gas estimates --- client/asset/eth/contractor.go | 89 ++++++++----------- client/asset/eth/eth.go | 13 ++- client/asset/eth/nodeclient_harness_test.go | 2 - dex/networks/eth/tokens.go | 8 +- dex/testing/dcrdex/harness.sh | 11 +++ dex/testing/eth/harness.sh | 6 +- server/asset/eth/eth.go | 12 +++ server/cmd/dcrdex/evm-protocol-overrides.json | 2 + 8 files changed, 74 insertions(+), 69 deletions(-) diff --git a/client/asset/eth/contractor.go b/client/asset/eth/contractor.go index aff7084f89..c9db902b80 100644 --- a/client/asset/eth/contractor.go +++ b/client/asset/eth/contractor.go @@ -361,7 +361,7 @@ func (c *contractorV0) estimateInitGas(ctx context.Context, n int) (uint64, erro func (c *contractorV0) estimateGas(ctx context.Context, value *big.Int, method string, args ...any) (uint64, error) { data, err := c.abi.Pack(method, args...) if err != nil { - return 0, fmt.Errorf("Pack error: %v", err) + return 0, fmt.Errorf("pack error: %v", err) } return c.cb.EstimateGas(ctx, ethereum.CallMsg{ @@ -424,34 +424,36 @@ func (c *contractorV0) outgoingValue(tx *types.Transaction) (swapped uint64) { // erc20Contractor supports the ERC20 ABI. Embedded in token contractors. type erc20Contractor struct { - tokenContract *erc20.IERC20 - acct common.Address - contract common.Address + cb bind.ContractBackend + tokenContract *erc20.IERC20 + tokenAddr common.Address + acctAddr common.Address + swapContractAddr common.Address } // balance exposes the read-only balanceOf method of the erc20 token contract. func (c *erc20Contractor) balance(ctx context.Context) (*big.Int, error) { callOpts := &bind.CallOpts{ - From: c.acct, + From: c.acctAddr, Context: ctx, } - return c.tokenContract.BalanceOf(callOpts, c.acct) + return c.tokenContract.BalanceOf(callOpts, c.acctAddr) } // allowance exposes the read-only allowance method of the erc20 token contract. func (c *erc20Contractor) allowance(ctx context.Context) (*big.Int, error) { callOpts := &bind.CallOpts{ - From: c.acct, + From: c.acctAddr, Context: ctx, } - return c.tokenContract.Allowance(callOpts, c.acct, c.contract) + return c.tokenContract.Allowance(callOpts, c.acctAddr, c.swapContractAddr) } // approve sends an approve transaction approving the linked contract to call // transferFrom for the specified amount. func (c *erc20Contractor) approve(txOpts *bind.TransactOpts, amount *big.Int) (*types.Transaction, error) { - return c.tokenContract.Approve(txOpts, c.contract, amount) + return c.tokenContract.Approve(txOpts, c.swapContractAddr, amount) } // transfer calls the transfer method of the erc20 token contract. Used for @@ -460,12 +462,22 @@ func (c *erc20Contractor) transfer(txOpts *bind.TransactOpts, addr common.Addres return c.tokenContract.Transfer(txOpts, addr, amount) } +// estimateApproveGas estimates the gas needed to send an approve tx. +func (c *erc20Contractor) estimateApproveGas(ctx context.Context, amount *big.Int) (uint64, error) { + return estimateGas(ctx, c.acctAddr, c.tokenAddr, erc20.ERC20ABI, c.cb, new(big.Int), "approve", c.swapContractAddr, amount) +} + +// estimateTransferGas estimates the gas needed for a transfer tx. The account +// needs to have > amount tokens to use this method. +func (c *erc20Contractor) estimateTransferGas(ctx context.Context, amount *big.Int) (uint64, error) { + return estimateGas(ctx, c.acctAddr, c.swapContractAddr, erc20.ERC20ABI, c.cb, new(big.Int), "transfer", c.acctAddr, amount) +} + // tokenContractorV0 is a contractor that implements the tokenContractor // methods, providing access to the methods of the token's ERC20 contract. type tokenContractorV0 struct { *contractorV0 *erc20Contractor - tokenAddr common.Address } var _ contractor = (*tokenContractorV0)(nil) @@ -514,32 +526,17 @@ func newV0TokenContractor(net dex.Network, token *dexeth.Token, acctAddr common. evmify: token.AtomicToEVM, atomize: token.EVMToAtomic, }, - tokenAddr: tokenAddr, + erc20Contractor: &erc20Contractor{ - tokenContract: tokenContract, - acct: acctAddr, - contract: swapContractAddr, + cb: cb, + tokenContract: tokenContract, + tokenAddr: tokenAddr, + acctAddr: acctAddr, + swapContractAddr: swapContractAddr, }, }, nil } -// estimateApproveGas estimates the gas needed to send an approve tx. -func (c *tokenContractorV0) estimateApproveGas(ctx context.Context, amount *big.Int) (uint64, error) { - return c.estimateGas(ctx, "approve", c.contractAddr, amount) -} - -// estimateTransferGas esimates the gas needed for a transfer tx. The account -// needs to have > amount tokens to use this method. -func (c *tokenContractorV0) estimateTransferGas(ctx context.Context, amount *big.Int) (uint64, error) { - return c.estimateGas(ctx, "transfer", c.acctAddr, amount) -} - -// estimateGas estimates the gas needed for methods on the ERC20 token contract. -// For estimating methods on the swap contract, use (contractorV0).estimateGas. -func (c *tokenContractorV0) estimateGas(ctx context.Context, method string, args ...interface{}) (uint64, error) { - return estimateGas(ctx, c.acctAddr, c.contractAddr, c.abi, c.cb, new(big.Int), method, args...) -} - // value finds incoming or outgoing value for the tx to either the swap contract // or the erc20 token contract. For the token contract, only transfer and // transferFrom are parsed. It is not an error if this tx is a call to another @@ -555,7 +552,7 @@ func (c *tokenContractorV0) value(ctx context.Context, tx *types.Transaction) (i // Consider removing. We'll never be sending transferFrom transactions // directly. - if sender, _, value, err := erc20.ParseTransferFromData(tx.Data()); err == nil && sender == c.acctAddr { + if sender, _, value, err := erc20.ParseTransferFromData(tx.Data()); err == nil && sender == c.contractorV0.acctAddr { return 0, c.atomize(value), nil } @@ -827,7 +824,6 @@ func (c *contractorV1) value(ctx context.Context, tx *types.Transaction) (in, ou type tokenContractorV1 struct { *contractorV1 *erc20Contractor - tokenAddr common.Address } func newV1TokenContractor(net dex.Network, token *dexeth.Token, acctAddr common.Address, cb bind.ContractBackend) (tokenContractor, error) { @@ -871,30 +867,17 @@ func newV1TokenContractor(net dex.Network, token *dexeth.Token, acctAddr common. evmify: token.AtomicToEVM, atomize: token.EVMToAtomic, }, - tokenAddr: tokenAddr, + erc20Contractor: &erc20Contractor{ - tokenContract: tokenContract, - acct: acctAddr, - contract: swapContractAddr, + cb: cb, + tokenContract: tokenContract, + tokenAddr: tokenAddr, + acctAddr: acctAddr, + swapContractAddr: swapContractAddr, }, }, nil } -// estimateApproveGas estimates the gas needed to send an approve tx. -func (c *tokenContractorV1) estimateApproveGas(ctx context.Context, amount *big.Int) (uint64, error) { - return c.estimateTokenContractGas(ctx, "approve", c.contractAddr, amount) -} - -// estimateTransferGas estimates the gas needed for a transfer tx. The account -// needs to have > amount tokens to use this method. -func (c *tokenContractorV1) estimateTransferGas(ctx context.Context, amount *big.Int) (uint64, error) { - return c.estimateTokenContractGas(ctx, "transfer", c.acctAddr, amount) -} - -func (c *tokenContractorV1) estimateTokenContractGas(ctx context.Context, method string, args ...interface{}) (uint64, error) { - return estimateGas(ctx, c.acctAddr, c.tokenAddr, erc20.ERC20ABI, c.cb, new(big.Int), method, args...) -} - // value finds incoming or outgoing value for the tx to either the swap contract // or the erc20 token contract. For the token contract, only transfer and // transferFrom are parsed. It is not an error if this tx is a call to another @@ -910,7 +893,7 @@ func (c *tokenContractorV1) value(ctx context.Context, tx *types.Transaction) (i // Consider removing. We'll never be sending transferFrom transactions // directly. - if sender, _, value, err := erc20.ParseTransferFromData(tx.Data()); err == nil && sender == c.acctAddr { + if sender, _, value, err := erc20.ParseTransferFromData(tx.Data()); err == nil && sender == c.contractorV1.acctAddr { return 0, c.atomize(value), nil } diff --git a/client/asset/eth/eth.go b/client/asset/eth/eth.go index 863ff4ce22..d15b335a58 100644 --- a/client/asset/eth/eth.go +++ b/client/asset/eth/eth.go @@ -121,9 +121,6 @@ const ( // unverified on-chain before we halt broadcasting of new txs. maxUnindexedTxs = 10 peerCountTicker = 5 * time.Second // no rpc calls here - - contractVersionERC20 = ^uint32(0) - contractVersionUnknown = contractVersionERC20 - 1 ) var ( @@ -4352,7 +4349,7 @@ func (w *ETHWallet) sendToAddr(addr common.Address, amt uint64, maxFeeRate, tipR // sendToAddr sends funds to the address. func (w *TokenWallet) sendToAddr(addr common.Address, amt uint64, maxFeeRate, tipRate *big.Int) (tx *types.Transaction, err error) { - g := w.gases(contractVersionERC20) + g := w.gases(dexeth.ContractVersionERC20) if g == nil { return nil, fmt.Errorf("no gas table") } @@ -4365,7 +4362,7 @@ func (w *TokenWallet) sendToAddr(addr common.Address, amt uint64, maxFeeRate, ti if addr == w.addr { txType = asset.SelfSend } - return tx, txType, amt, w.withTokenContractor(w.assetID, contractVersionERC20, func(c tokenContractor) error { + return tx, txType, amt, w.withTokenContractor(w.assetID, dexeth.ContractVersionERC20, func(c tokenContractor) error { tx, err = c.transfer(txOpts, addr, w.evmify(amt)) if err != nil { return err @@ -6149,7 +6146,7 @@ func getGasEstimates(ctx context.Context, cl, acl ethFetcher, c contractor, ac t if err = checkTxStatus(receipt, txOpts.GasLimit); err != nil { return fmt.Errorf("init tx failed status check: %w", err) } - log.Infof("%d gas used for %d initiation txs", receipt.GasUsed, n) + log.Infof("%d gas used for %d initiations in tx %s", receipt.GasUsed, n, tx.Hash()) stats.swaps = append(stats.swaps, receipt.GasUsed) // Estimate a refund @@ -6177,7 +6174,7 @@ func getGasEstimates(ctx context.Context, cl, acl ethFetcher, c contractor, ac t if err != nil { return fmt.Errorf("error constructing signed tx opts for %d redeems: %v", n, err) } - log.Debugf("Sending %d redemption txs", n) + log.Debugf("Sending %d redemptions", n) tx, err = c.redeem(txOpts, redemptions) if err != nil { return fmt.Errorf("redeem error for %d swaps: %v", n, err) @@ -6192,7 +6189,7 @@ func getGasEstimates(ctx context.Context, cl, acl ethFetcher, c contractor, ac t if err = checkTxStatus(receipt, txOpts.GasLimit); err != nil { return fmt.Errorf("redeem tx failed status check: %w", err) } - log.Infof("%d gas used for %d redemptions", receipt.GasUsed, n) + log.Infof("%d gas used for %d redemptions in tx %s", receipt.GasUsed, n, tx.Hash()) stats.redeems = append(stats.redeems, receipt.GasUsed) } diff --git a/client/asset/eth/nodeclient_harness_test.go b/client/asset/eth/nodeclient_harness_test.go index 1fef32d9fb..07223ccbaa 100644 --- a/client/asset/eth/nodeclient_harness_test.go +++ b/client/asset/eth/nodeclient_harness_test.go @@ -2048,8 +2048,6 @@ func testRefund(t *testing.T, assetID uint32) { t.Fatalf("unexpected swap state for test %v: want %s got %s", test.name, dexeth.SSNone, status.Step) } - inLocktime := uint64(time.Now().Add(test.addTime).Unix()) - txOpts, err := ethClient.txOpts(ctx, optsVal, gases.SwapN(1), nil, nil, nil) if err != nil { t.Fatalf("%s: txOpts error: %v", test.name, err) diff --git a/dex/networks/eth/tokens.go b/dex/networks/eth/tokens.go index 050e2c02c9..d6e6c9f4d4 100644 --- a/dex/networks/eth/tokens.go +++ b/dex/networks/eth/tokens.go @@ -336,13 +336,13 @@ func MaybeReadSimnetAddrsDir( return } - ethSwapContractAddrFileV0 := filepath.Join(harnessDir, "eth_swap_contract_address.txt") - testUSDCSwapContractAddrFileV0 := filepath.Join(harnessDir, "usdc_swap_contract_address.txt") + ethSwapContractAddrFileV0 := filepath.Join(harnessDir, "eth_swap_contract_address_v0.txt") + ethSwapContractAddrFileV1 := filepath.Join(harnessDir, "eth_swap_contract_address_v1.txt") + testUSDCSwapContractAddrFileV0 := filepath.Join(harnessDir, "usdc_swap_contract_address_v0.txt") + testUSDCSwapContractAddrFileV1 := filepath.Join(harnessDir, "usdc_swap_contract_address_v1.txt") testUSDCContractAddrFile := filepath.Join(harnessDir, "test_usdc_contract_address.txt") testUSDTSwapContractAddrFileV0 := filepath.Join(harnessDir, "usdt_swap_contract_address.txt") testUSDTContractAddrFile := filepath.Join(harnessDir, "test_usdt_contract_address.txt") - ethSwapContractAddrFileV1 := filepath.Join(harnessDir, "eth_swap_contract_address_v1.txt") - testUSDCSwapContractAddrFileV1 := filepath.Join(harnessDir, "usdc_swap_contract_address_v1.txt") multiBalanceContractAddrFile := filepath.Join(harnessDir, "multibalance_address.txt") contractAddrs[0][dex.Simnet] = maybeGetContractAddrFromFile(ethSwapContractAddrFileV0) diff --git a/dex/testing/dcrdex/harness.sh b/dex/testing/dcrdex/harness.sh index 0811b52b6b..edb7f267ed 100755 --- a/dex/testing/dcrdex/harness.sh +++ b/dex/testing/dcrdex/harness.sh @@ -124,6 +124,17 @@ tmux kill-session -t $SESSION EOF chmod +x "${DCRDEX_DATA_DIR}/quit" +cat > "${DCRDEX_DATA_DIR}/evm-protocol-overrides.json" < "${DCRDEX_DATA_DIR}/run" <