Skip to content

Commit

Permalink
use erc20 contractor for token contract gas estimates
Browse files Browse the repository at this point in the history
  • Loading branch information
buck54321 committed Jun 3, 2024
1 parent eb16ea9 commit 56b14df
Show file tree
Hide file tree
Showing 8 changed files with 74 additions and 69 deletions.
89 changes: 36 additions & 53 deletions client/asset/eth/contractor.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{
Expand Down Expand Up @@ -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
Expand All @@ -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)
Expand Down Expand Up @@ -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
Expand All @@ -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
}

Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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
Expand All @@ -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
}

Expand Down
13 changes: 5 additions & 8 deletions client/asset/eth/eth.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand Down Expand Up @@ -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")
}
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand All @@ -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)
}

Expand Down
2 changes: 0 additions & 2 deletions client/asset/eth/nodeclient_harness_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
8 changes: 4 additions & 4 deletions dex/networks/eth/tokens.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
11 changes: 11 additions & 0 deletions dex/testing/dcrdex/harness.sh
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,17 @@ tmux kill-session -t $SESSION
EOF
chmod +x "${DCRDEX_DATA_DIR}/quit"

cat > "${DCRDEX_DATA_DIR}/evm-protocol-overrides.json" <<EOF
{
"usdt.eth": 0,
"polygon": 0,
"usdc.polygon": 0,
"usdt.polygon": 0,
"weth.polygon": 0,
"wbtc.polygon": 0
}
EOF

cat > "${DCRDEX_DATA_DIR}/run" <<EOF
#!/usr/bin/env bash
${HARNESS_DIR}/genmarkets.sh
Expand Down
6 changes: 4 additions & 2 deletions dex/testing/eth/harness.sh
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,10 @@ PASSWORD="abc"
export NODES_ROOT=~/dextest/eth
export GENESIS_JSON_FILE_LOCATION="${NODES_ROOT}/genesis.json"

# Ensure we can create the session and that there's not a session already
# running before we nuke the data directory.
tmux new-session -d -s $SESSION "${SHELL}"

if [ -d "${NODES_ROOT}" ]; then
rm -R "${NODES_ROOT}"
fi
Expand Down Expand Up @@ -234,8 +238,6 @@ chmod +x "${NODES_ROOT}/harness-ctl/quit"
# Start harness
################################################################################

echo "Starting harness"
tmux new-session -d -s $SESSION "${SHELL}"
tmux rename-window -t $SESSION:0 'harness-ctl'
tmux send-keys -t $SESSION:0 "set +o history" C-m
tmux send-keys -t $SESSION:0 "cd ${NODES_ROOT}/harness-ctl" C-m
Expand Down
12 changes: 12 additions & 0 deletions server/asset/eth/eth.go
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,18 @@ func (d *Driver) Setup(cfg *asset.BackendConfig) (asset.Backend, error) {
chainID = 42
}

for _, tkn := range registeredTokens {
netToken, found := tkn.NetTokens[cfg.Net]
if !found {
return nil, fmt.Errorf("no %s token for %s", tkn.Name, cfg.Net)
}
if _, found = netToken.SwapContracts[tkn.ContractVersion]; !found {
return nil, fmt.Errorf("no version %d swap contract adddress for %s on %s. "+
"Do you need a version override in evm-protocol-overrides.json?",
tkn.ContractVersion, tkn.Name, cfg.Net)
}
}

return NewEVMBackend(cfg, chainID, dexeth.ContractAddresses, registeredTokens)
}

Expand Down
2 changes: 2 additions & 0 deletions server/cmd/dcrdex/evm-protocol-overrides.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
{
"eth": 0,
"usdc.eth": 0,
"usdt.eth": 0,
"polygon": 0,
"usdc.polygon": 0,
"usdt.polygon": 0,
"wbtc.polygon": 0,
"weth.polygon": 0
}

0 comments on commit 56b14df

Please sign in to comment.