diff --git a/client/asset/eth/eth.go b/client/asset/eth/eth.go index 9af21d4b84..05abfdc9f3 100644 --- a/client/asset/eth/eth.go +++ b/client/asset/eth/eth.go @@ -168,6 +168,7 @@ type ethFetcher interface { unlock(ctx context.Context, pw string, acct *accounts.Account) error signData(addr common.Address, data []byte) ([]byte, error) suggestGasTipCap(ctx context.Context) (*big.Int, error) + suggestGasPrice(ctx context.Context) (*big.Int, error) } // Check that ExchangeWallet satisfies the asset.Wallet interface. @@ -737,20 +738,9 @@ func (eth *ExchangeWallet) Swap(swaps *asset.Swaps) ([]asset.Receipt, asset.Coin return fail(fmt.Errorf("Swap: coin inputs value %d < required %d", totalInputValue, totalUsedValue)) } - suggestedGasTipCap, err := eth.node.suggestGasTipCap(eth.ctx) + gasTipCap, err := eth.gasTipCapToUse() if err != nil { - return fail(fmt.Errorf("Swap: failed to get suggested gas tip cap %w", err)) - } - - var gasTipCap *big.Int - suggestedGasTipCapGwei, err := srveth.ToGwei(suggestedGasTipCap) - if err != nil { - return fail(fmt.Errorf("Swap: failed to convert to gwei: %w", err)) - } - if suggestedGasTipCapGwei > MinGasTipCap { - gasTipCap = suggestedGasTipCap - } else { - gasTipCap = big.NewInt(MinGasTipCap * srveth.GweiFactor) + return fail(fmt.Errorf("Swap: unable to determine gas tip cap to use: %w", err)) } opts := bind.TransactOpts{ @@ -796,10 +786,102 @@ func (eth *ExchangeWallet) Swap(swaps *asset.Swaps) ([]asset.Receipt, asset.Coin return receipts, change, maxPossibleFee, nil } +// gasTipCapToUse return the maximum of the gas tip cap suggested by +// the eth client and MinGasTipCap. +func (eth *ExchangeWallet) gasTipCapToUse() (*big.Int, error) { + suggestedGasTipCap, err := eth.node.suggestGasTipCap(eth.ctx) + if err != nil { + return nil, err + } + + minGasTipCapWei := srveth.ToWei(MinGasTipCap) + + if suggestedGasTipCap.Cmp(minGasTipCapWei) > 0 { + return suggestedGasTipCap, nil + } else { + return minGasTipCapWei, nil + } +} + // Redeem sends the redemption transaction, which may contain more than one // redemption. -func (*ExchangeWallet) Redeem(form *asset.RedeemForm) ([]dex.Bytes, asset.Coin, uint64, error) { - return nil, nil, 0, asset.ErrNotImplemented +func (eth *ExchangeWallet) Redeem(form *asset.RedeemForm) ([]dex.Bytes, asset.Coin, uint64, error) { + fail := func(err error) ([]dex.Bytes, asset.Coin, uint64, error) { + return nil, nil, 0, err + } + + if len(form.Redemptions) == 0 { + return fail(errors.New("Redeem: must be called with at least 1 redemption")) + } + + inputs := make([]dex.Bytes, 0, len(form.Redemptions)) + redemptions := make([]dexeth.ETHSwapRedemption, 0, len(form.Redemptions)) + var redeemedValue uint64 + for _, redemption := range form.Redemptions { + coinID := redemption.Spends.Coin.ID() + inputs = append(inputs, coinID) + + decodedID, err := srveth.DecodeCoinID(coinID) + if err != nil { + return fail(err) + } + swapCoinID, ok := decodedID.(*srveth.SwapCoinID) + if !ok { + return fail( + fmt.Errorf("Redeem: coin id in redemption should be swap coin id, but got %T", + swapCoinID)) + } + swap, err := eth.node.swap(eth.ctx, eth.acct, swapCoinID.SecretHash) + if err != nil { + return fail(fmt.Errorf("Redeem: unable to retrieve swap: %w", err)) + } + swapValue, err := srveth.ToGwei(swap.Value) + if err != nil { + return fail(fmt.Errorf("Redeem: unable to convert value to gwei: %w", err)) + } + redeemedValue += swapValue + + var secret [32]byte + var secretHash [32]byte + copy(secret[:], redemption.Secret) + copy(secretHash[:], redemption.Spends.SecretHash) + redemptions = append(redemptions, + dexeth.ETHSwapRedemption{ + Secret: secret, + SecretHash: secretHash, + }) + } + + gasTipCap, err := eth.gasTipCapToUse() + if err != nil { + return fail(fmt.Errorf("Swap: unable to determine gas tip cap to use: %w", err)) + } + + // TODO: make sure the amount we locked for redemption is enough to cover the gas + // fees. + gasFeeCap := srveth.ToWei(form.FeeSuggestion) + suggestedFeeRate, err := eth.node.suggestGasPrice(eth.ctx) + if err != nil { + return fail(fmt.Errorf("Swap: unable to retrieve suggested gas price: %w", err)) + } + if suggestedFeeRate.Cmp(gasFeeCap) > 0 { + gasFeeCap = suggestedFeeRate + } + + opts := bind.TransactOpts{ + From: eth.acct.Address, + GasFeeCap: gasFeeCap, + GasTipCap: gasTipCap, + Context: eth.ctx, + } + _, err = eth.node.redeem(&opts, eth.networkID, redemptions) + if err != nil { + return fail(fmt.Errorf("Redeem: redeem error: %w", err)) + } + + outputCoin := eth.createAmountCoin(redeemedValue) + gasRequired := srveth.GasToRedeemSwaps(uint64(len(redemptions))) + return inputs, outputCoin, gasRequired * form.FeeSuggestion, nil } // SignMessage signs the message with the private key associated with the diff --git a/client/asset/eth/eth_test.go b/client/asset/eth/eth_test.go index f0adc8d582..277a08ba22 100644 --- a/client/asset/eth/eth_test.go +++ b/client/asset/eth/eth_test.go @@ -20,7 +20,6 @@ import ( "decred.org/dcrdex/dex" "decred.org/dcrdex/dex/encode" dexeth "decred.org/dcrdex/dex/networks/eth" - swap "decred.org/dcrdex/dex/networks/eth" srveth "decred.org/dcrdex/server/asset/eth" "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/accounts" @@ -36,13 +35,22 @@ import ( var ( _ ethFetcher = (*testNode)(nil) tLogger = dex.StdOutLogger("ETHTEST", dex.LevelTrace) + + minGasTipCapWei = srveth.ToWei(MinGasTipCap) ) type initTx struct { hash common.Hash + opts *bind.TransactOpts initiations []dexeth.ETHSwapInitiation } +type redeemTx struct { + hash common.Hash + opts *bind.TransactOpts + redemptions []dexeth.ETHSwapRedemption +} + type testNode struct { connectErr error bestHdr *types.Header @@ -61,12 +69,17 @@ type testNode struct { balErr error signDataErr error privKeyForSigning *ecdsa.PrivateKey - initErr error nonce uint64 lastInitiation initTx - lastTxGasTipCap *big.Int + initErr error + lastRedemption redeemTx + redeemErr error suggestedGasTipCap *big.Int suggestGasTipCapErr error + suggestedGasPrice *big.Int + suggestGasPriceErr error + swapMap map[[32]byte]dexeth.ETHSwapSwap + swapErr error } func (n *testNode) connect(ctx context.Context, node *node.Node, addr *common.Address) error { @@ -138,18 +151,44 @@ func (n *testNode) initiate(opts *bind.TransactOpts, netID int64, initiations [] n.lastInitiation = initTx{ initiations: initiations, hash: tx.Hash(), + opts: opts, } - n.lastTxGasTipCap = opts.GasTipCap return tx, nil } -func (n *testNode) redeem(opts *bind.TransactOpts, netID int64, redemptions []swap.ETHSwapRedemption) (*types.Transaction, error) { - return nil, nil +func (n *testNode) redeem(opts *bind.TransactOpts, netID int64, redemptions []dexeth.ETHSwapRedemption) (*types.Transaction, error) { + if n.redeemErr != nil { + return nil, n.redeemErr + } + baseTx := &types.DynamicFeeTx{ + Nonce: n.nonce, + GasFeeCap: opts.GasFeeCap, + GasTipCap: opts.GasTipCap, + Gas: opts.GasLimit, + Value: opts.Value, + Data: []byte{}, + } + tx := types.NewTx(baseTx) + n.nonce++ + n.lastRedemption = redeemTx{ + redemptions: redemptions, + hash: tx.Hash(), + opts: opts, + } + return tx, nil } func (n *testNode) refund(opts *bind.TransactOpts, netID int64, secretHash [32]byte) (*types.Transaction, error) { return nil, nil } func (n *testNode) swap(ctx context.Context, from *accounts.Account, secretHash [32]byte) (*dexeth.ETHSwapSwap, error) { - return nil, nil + if n.swapErr != nil { + return nil, n.swapErr + } + swap, ok := n.swapMap[secretHash] + if !ok { + return nil, errors.New("") + } + + return &swap, nil } func (n *testNode) transactionReceipt(ctx context.Context, txHash common.Hash) (*types.Receipt, error) { return nil, nil @@ -163,6 +202,13 @@ func (n *testNode) suggestGasTipCap(ctx context.Context) (*big.Int, error) { } return n.suggestedGasTipCap, nil } +func (n *testNode) suggestGasPrice(ctx context.Context) (*big.Int, error) { + if n.suggestGasPriceErr != nil { + return nil, n.suggestGasPriceErr + } + return n.suggestedGasPrice, nil +} + func (n *testNode) signData(addr common.Address, data []byte) ([]byte, error) { if n.signDataErr != nil { return nil, n.signDataErr @@ -679,8 +725,6 @@ func TestSwap(t *testing.T) { return big.NewInt(0).Mul(big.NewInt(eth*srveth.GweiFactor), big.NewInt(srveth.GweiFactor)) } - minGasTipCapWei := big.NewInt(srveth.GweiFactor * MinGasTipCap) - node := &testNode{} node.suggestedGasTipCap = big.NewInt(srveth.GweiFactor) address := "0xB6De8BB5ed28E6bE6d671975cad20C03931bE981" @@ -752,16 +796,17 @@ func TestSwap(t *testing.T) { testName, len(receipts), len(swaps.Contracts)) } + lastGasTipCap := node.lastInitiation.opts.GasTipCap if node.suggestedGasTipCap.Cmp(minGasTipCapWei) <= 0 && - node.lastTxGasTipCap.Cmp(minGasTipCapWei) != 0 { + lastGasTipCap.Cmp(minGasTipCapWei) != 0 { t.Fatalf("%v: tip cap expected to be %v but got %v", - testName, minGasTipCapWei, node.lastTxGasTipCap) + testName, minGasTipCapWei, lastGasTipCap) } if node.suggestedGasTipCap.Cmp(minGasTipCapWei) > 0 && - node.lastTxGasTipCap.Cmp(node.suggestedGasTipCap) != 0 { + lastGasTipCap.Cmp(node.suggestedGasTipCap) != 0 { t.Fatalf("%v: tip cap expected to be %v but got %v", - testName, node.suggestedGasTipCap, node.lastTxGasTipCap) + testName, node.suggestedGasTipCap, lastGasTipCap) } var totalCoinValue uint64 @@ -875,21 +920,6 @@ func TestSwap(t *testing.T) { secretHash2 := sha256.Sum256(secret2) expiration := time.Now().Add(time.Hour * 8).Unix() - // Ensure error when gas estimation errors - swaps := asset.Swaps{ - Inputs: refreshWalletAndFundCoins(5, []uint64{ethToGwei(3)}), - Contracts: []*asset.Contract{ - { - Address: receivingAddress, - Value: ethToGwei(1), - SecretHash: secretHash[:], - LockTime: uint64(expiration), - }, - }, - FeeRate: 200, - LockChange: false, - } - // Ensure error with invalid secret hash contracts := []*asset.Contract{ { @@ -900,7 +930,7 @@ func TestSwap(t *testing.T) { }, } inputs := refreshWalletAndFundCoins(5, []uint64{ethToGwei(2)}) - swaps = asset.Swaps{ + swaps := asset.Swaps{ Inputs: inputs, Contracts: contracts, FeeRate: 200, @@ -1263,6 +1293,385 @@ func TestPreRedeem(t *testing.T) { } } +func TestRedeem(t *testing.T) { + ethToWei := func(eth int64) *big.Int { + return big.NewInt(0).Mul(big.NewInt(eth*srveth.GweiFactor), big.NewInt(srveth.GweiFactor)) + } + + node := &testNode{} + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + eth := &ExchangeWallet{ + node: node, + ctx: ctx, + log: tLogger, + acct: new(accounts.Account), + } + node.swapMap = make(map[[32]byte]dexeth.ETHSwapSwap) + addSwapToSwapMap := func(secretHash [32]byte, value *big.Int, state srveth.SwapState) { + swap := dexeth.ETHSwapSwap{ + InitBlockNumber: big.NewInt(1), + RefundBlockTimestamp: big.NewInt(1), + Initiator: common.HexToAddress("0x2b84C791b79Ee37De042AD2ffF1A253c3ce9bc27"), + Participant: common.HexToAddress("B6De8BB5ed28E6bE6d671975cad20C03931bE981"), + Value: value, + State: uint8(state), + } + node.swapMap[secretHash] = swap + } + + getSwapCoinID := func(secretHash [32]byte) srveth.CoinID { + return &srveth.SwapCoinID{ + ContractAddress: common.HexToAddress(""), + SecretHash: secretHash, + } + } + + numSecrets := 3 + secrets := make([][32]byte, 0, numSecrets) + secretHashes := make([][32]byte, 0, numSecrets) + for i := 0; i < numSecrets; i++ { + var secret [32]byte + copy(secret[:], encode.RandomBytes(32)) + secretHash := sha256.Sum256(secret[:]) + secrets = append(secrets, secret) + secretHashes = append(secretHashes, secretHash) + } + + addSwapToSwapMap(secretHashes[0], ethToWei(1), srveth.SSInitiated) + addSwapToSwapMap(secretHashes[1], ethToWei(1), srveth.SSInitiated) + + var badCoin badCoin + var randomTxHash common.Hash + copy(randomTxHash[:], encode.RandomBytes(32)) + + tests := []struct { + name string + form asset.RedeemForm + suggestedGasPrice uint64 + suggestedGasTipCap uint64 + redeemErr error + suggestGasTipCapErr error + suggestGasPriceErr error + expectError bool + }{ + { + name: "node suggested gas price higher", + expectError: false, + suggestedGasPrice: 200, + suggestedGasTipCap: 1, + form: asset.RedeemForm{ + Redemptions: []*asset.Redemption{ + { + Spends: &asset.AuditInfo{ + SecretHash: secretHashes[0][:], + Coin: &coin{ + id: getSwapCoinID(secretHashes[0]), + }, + }, + Secret: secrets[0][:], + }, + }, + FeeSuggestion: 100, + }, + }, + { + name: "server suggested gas price higher", + expectError: false, + suggestedGasPrice: 100, + suggestedGasTipCap: 1, + form: asset.RedeemForm{ + Redemptions: []*asset.Redemption{ + { + Spends: &asset.AuditInfo{ + SecretHash: secretHashes[0][:], + Coin: &coin{ + id: getSwapCoinID(secretHashes[0]), + }, + }, + Secret: secrets[0][:], + }, + }, + FeeSuggestion: 200, + }, + }, + { + name: "suggested fee cap higher than min", + expectError: false, + suggestedGasPrice: 100, + suggestedGasTipCap: MinGasTipCap + 1, + form: asset.RedeemForm{ + Redemptions: []*asset.Redemption{ + { + Spends: &asset.AuditInfo{ + SecretHash: secretHashes[0][:], + Coin: &coin{ + id: getSwapCoinID(secretHashes[0]), + }, + }, + Secret: secrets[0][:], + }, + }, + FeeSuggestion: 200, + }, + }, + { + name: "two redeems ok", + expectError: false, + suggestedGasPrice: 100, + suggestedGasTipCap: 1, + form: asset.RedeemForm{ + Redemptions: []*asset.Redemption{ + { + Spends: &asset.AuditInfo{ + SecretHash: secretHashes[0][:], + Coin: &coin{ + id: getSwapCoinID(secretHashes[0]), + }, + }, + Secret: secrets[0][:], + }, + { + Spends: &asset.AuditInfo{ + SecretHash: secretHashes[1][:], + Coin: &coin{ + id: getSwapCoinID(secretHashes[1]), + }, + }, + Secret: secrets[1][:], + }, + }, + FeeSuggestion: 100, + }, + }, + { + name: "redeem error", + redeemErr: errors.New(""), + expectError: true, + suggestedGasPrice: 100, + suggestedGasTipCap: 1, + form: asset.RedeemForm{ + Redemptions: []*asset.Redemption{ + { + Spends: &asset.AuditInfo{ + SecretHash: secretHashes[0][:], + Coin: &coin{ + id: getSwapCoinID(secretHashes[0]), + }, + }, + Secret: secrets[0][:], + }, + }, + FeeSuggestion: 200, + }, + }, + { + name: "suggest tip cap error", + suggestGasTipCapErr: errors.New(""), + expectError: true, + suggestedGasPrice: 100, + suggestedGasTipCap: 1, + form: asset.RedeemForm{ + Redemptions: []*asset.Redemption{ + { + Spends: &asset.AuditInfo{ + SecretHash: secretHashes[0][:], + Coin: &coin{ + id: getSwapCoinID(secretHashes[0]), + }, + }, + Secret: secrets[0][:], + }, + }, + FeeSuggestion: 200, + }, + }, + { + name: "suggest gas price error", + suggestGasPriceErr: errors.New(""), + expectError: true, + suggestedGasPrice: 100, + suggestedGasTipCap: 1, + form: asset.RedeemForm{ + Redemptions: []*asset.Redemption{ + { + Spends: &asset.AuditInfo{ + SecretHash: secretHashes[0][:], + Coin: &coin{ + id: getSwapCoinID(secretHashes[0]), + }, + }, + Secret: secrets[0][:], + }, + }, + FeeSuggestion: 200, + }, + }, + { + name: "invalid coin id error", + expectError: true, + suggestedGasPrice: 100, + suggestedGasTipCap: 1, + form: asset.RedeemForm{ + Redemptions: []*asset.Redemption{ + { + Spends: &asset.AuditInfo{ + SecretHash: secretHashes[0][:], + Coin: &badCoin, + }, + Secret: secrets[0][:], + }, + }, + FeeSuggestion: 200, + }, + }, + { + name: "non swapcoinid error", + expectError: true, + suggestedGasPrice: 100, + suggestedGasTipCap: 1, + form: asset.RedeemForm{ + Redemptions: []*asset.Redemption{ + { + Spends: &asset.AuditInfo{ + SecretHash: secretHashes[0][:], + Coin: &coin{ + id: &srveth.TxCoinID{ + TxID: randomTxHash, + }, + }, + }, + Secret: secrets[0][:], + }, + }, + FeeSuggestion: 200, + }, + }, + { + name: "0 redemptions error", + expectError: true, + suggestedGasPrice: 100, + suggestedGasTipCap: 1, + form: asset.RedeemForm{ + Redemptions: []*asset.Redemption{}, + FeeSuggestion: 200, + }, + }, + { + name: "swap not found in contract", + expectError: true, + suggestedGasPrice: 100, + suggestedGasTipCap: 1, + form: asset.RedeemForm{ + Redemptions: []*asset.Redemption{ + { + Spends: &asset.AuditInfo{ + SecretHash: secretHashes[2][:], + Coin: &coin{ + id: getSwapCoinID(secretHashes[2]), + }, + }, + Secret: secrets[2][:], + }, + }, + FeeSuggestion: 100, + }, + }, + } + + for _, test := range tests { + node.suggestedGasTipCap = srveth.ToWei(test.suggestedGasTipCap) + node.suggestedGasPrice = srveth.ToWei(test.suggestedGasPrice) + node.redeemErr = test.redeemErr + node.suggestGasPriceErr = test.suggestGasPriceErr + node.suggestGasTipCapErr = test.suggestGasTipCapErr + + ins, out, fees, err := eth.Redeem(&test.form) + if test.expectError { + if err == nil { + t.Fatalf("%v: expected error", test.name) + } + continue + } + if err != nil { + t.Fatalf("%v: unexpected error: %v", test.name, err) + } + + if len(ins) != len(test.form.Redemptions) { + t.Fatalf("%v: expected %d inputs but got %d", + test.name, len(test.form.Redemptions), len(ins)) + } + + // Check fees returned from Redeem are as expected + expectedGas := srveth.GasToRedeemSwaps(uint64(len(test.form.Redemptions))) + expectedFees := expectedGas * test.form.FeeSuggestion + if fees != expectedFees { + t.Fatalf("%v: expected fees %d, but got %d", test.name, expectedFees, fees) + } + + // Make sure tip cap was properly set + lastGasTipCap := node.lastRedemption.opts.GasTipCap + if node.suggestedGasTipCap.Cmp(minGasTipCapWei) <= 0 && + lastGasTipCap.Cmp(minGasTipCapWei) != 0 { + t.Fatalf("%v: tip cap expected to be %v but got %v", + test.name, minGasTipCapWei, lastGasTipCap) + } + if node.suggestedGasTipCap.Cmp(minGasTipCapWei) > 0 && + lastGasTipCap.Cmp(node.suggestedGasTipCap) != 0 { + t.Fatalf("%v: tip cap expected to be %v but got %v", + test.name, node.suggestedGasTipCap, lastGasTipCap) + } + + // Make sure gas fee cap was properly set + lastGasFeeCap := node.lastRedemption.opts.GasFeeCap + serverSuggestedFee := srveth.ToWei(test.form.FeeSuggestion) + if node.suggestedGasPrice.Cmp(serverSuggestedFee) <= 0 && + lastGasFeeCap.Cmp(serverSuggestedFee) != 0 { + t.Fatalf("%v: gas price expected to be %v but got %v", + test.name, serverSuggestedFee, lastGasFeeCap) + } + if node.suggestedGasPrice.Cmp(serverSuggestedFee) > 0 && + lastGasFeeCap.Cmp(node.suggestedGasPrice) != 0 { + t.Fatalf("%v: gas price expected to be %v but got %v", + test.name, node.suggestedGasPrice, lastGasFeeCap) + } + + var totalSwapValue uint64 + for i, redemption := range test.form.Redemptions { + coinID := redemption.Spends.Coin.ID() + if !bytes.Equal(coinID, ins[i]) { + t.Fatalf("%v: expected input %x to equal coin id %x", + test.name, coinID, ins[i]) + } + decodedCoinID, err := srveth.DecodeCoinID(coinID) + if err != nil { + t.Fatalf("%v: unable to decode coin id: %v", test.name, err) + } + swapCoinID, ok := decodedCoinID.(*srveth.SwapCoinID) + if !ok { + t.Fatalf("%v: expected coin id to be swapCoinID but got: %T", test.name, decodedCoinID) + } + swap := node.swapMap[swapCoinID.SecretHash] + swapValue, err := srveth.ToGwei(swap.Value) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + totalSwapValue += swapValue + + redemptionInTx := node.lastRedemption.redemptions[i] + if !bytes.Equal(redemptionInTx.Secret[:], redemption.Secret) { + t.Fatalf(`"%v" - expected secret in tx %x to be same as in request %x`, + test.name, redemptionInTx.Secret, redemption.Secret) + } + } + + if out.Value() != totalSwapValue { + t.Fatalf("expected coin value to be %d but got %d", + totalSwapValue, out.Value()) + } + } +} + func TestMaxOrder(t *testing.T) { ethToGwei := func(eth uint64) uint64 { return eth * srveth.GweiFactor diff --git a/client/asset/eth/rpcclient.go b/client/asset/eth/rpcclient.go index b445c61069..8a3c3db82b 100644 --- a/client/asset/eth/rpcclient.go +++ b/client/asset/eth/rpcclient.go @@ -313,3 +313,9 @@ func (c *rpcclient) getCodeAt(ctx context.Context, contractAddr *common.Address) func (c *rpcclient) suggestGasTipCap(ctx context.Context) (*big.Int, error) { return c.ec.SuggestGasTipCap(ctx) } + +// suggestGasPrice retrieves the currently suggested gas price to allow a timely +// execution of a transaction. +func (c *rpcclient) suggestGasPrice(ctx context.Context) (*big.Int, error) { + return c.ec.SuggestGasPrice(ctx) +} diff --git a/dex/networks/eth/txdata.go b/dex/networks/eth/txdata.go index fa933d313d..03d4c56f2c 100644 --- a/dex/networks/eth/txdata.go +++ b/dex/networks/eth/txdata.go @@ -9,7 +9,9 @@ package eth import ( "fmt" "math/big" + "strings" + "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" ) @@ -58,6 +60,14 @@ func ParseInitiateData(calldata []byte) ([]ETHSwapInitiation, error) { return toReturn, nil } +func PackInitiateData(initiations []ETHSwapInitiation) ([]byte, error) { + parsedAbi, err := abi.JSON(strings.NewReader(ETHSwapABI)) + if err != nil { + return nil, err + } + return parsedAbi.Pack("initiate", initiations) +} + // ParseRedeemData accepts call data from a transaction that pays to a // contract with extra data. It will error if the call data does not call // redeem with expected argument types. It returns the secret and secret hash @@ -92,3 +102,11 @@ func ParseRedeemData(calldata []byte) ([]ETHSwapRedemption, error) { return toReturn, nil } + +func PackRedeemData(redemptions []ETHSwapRedemption) ([]byte, error) { + parsedAbi, err := abi.JSON(strings.NewReader(ETHSwapABI)) + if err != nil { + return nil, err + } + return parsedAbi.Pack("redeem", redemptions) +} diff --git a/dex/networks/eth/txdata_test.go b/dex/networks/eth/txdata_test.go index 7cd7f3be2e..66d21b8804 100644 --- a/dex/networks/eth/txdata_test.go +++ b/dex/networks/eth/txdata_test.go @@ -7,10 +7,8 @@ import ( "bytes" "encoding/hex" "math/big" - "strings" "testing" - "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" ) @@ -52,11 +50,7 @@ func TestParseInitiateData(t *testing.T) { Value: big.NewInt(1), }, } - parsedABI, err := abi.JSON(strings.NewReader(ETHSwapABI)) - if err != nil { - t.Fatalf("unable to parse abi: %v", err) - } - calldata, err := parsedABI.Pack("initiate", initiations) + calldata, err := PackInitiateData(initiations) if err != nil { t.Fatalf("unale to pack abi: %v", err) } @@ -149,13 +143,9 @@ func TestParseRedeemData(t *testing.T) { SecretHash: secretHashB, }, } - parsedABI, err := abi.JSON(strings.NewReader(ETHSwapABI)) + calldata, err := PackRedeemData(redemptions) if err != nil { - t.Fatalf("unable to parse abi: %v", err) - } - calldata, err := parsedABI.Pack("redeem", redemptions) - if err != nil { - t.Fatalf("unale to pack abi: %v", err) + t.Fatalf("unable to pack abi: %v", err) } redeemCallData := mustParseHex("f4fd17f9000000000000000000000000000000000" + "000000000000000000000000000002000000000000000000000000000000000000" + diff --git a/server/asset/eth/common.go b/server/asset/eth/common.go index ce6edf2306..7ebf4dd8e5 100644 --- a/server/asset/eth/common.go +++ b/server/asset/eth/common.go @@ -274,6 +274,26 @@ const ( AdditionalRedeemGas = 34000 ) +// GasToInitiateSwaps returns the amount of gas required to initiate +// a certain number of swaps. +func GasToInitiateSwaps(numSwaps uint64) uint64 { + if numSwaps == 0 { + return 0 + } + + return InitGas + (numSwaps-1)*AdditionalInitGas +} + +// GasToRedeemSwaps returns the amount of gas required to redeem +// a certain number of swaps. +func GasToRedeemSwaps(numSwaps uint64) uint64 { + if numSwaps == 0 { + return 0 + } + + return RedeemGas + (numSwaps-1)*AdditionalRedeemGas +} + // ToGwei converts a *big.Int in wei (1e18 unit) to gwei (1e9 unit) as a uint64. // Errors if the amount of gwei is too big to fit fully into a uint64. func ToGwei(wei *big.Int) (uint64, error) { @@ -285,6 +305,11 @@ func ToGwei(wei *big.Int) (uint64, error) { return gwei.Uint64(), nil } +// ToWei converts a uint64 in gwei (1e9 unit) to wei (1e18 unit) as a *big.Int. +func ToWei(gwei uint64) *big.Int { + return big.NewInt(0).Mul(big.NewInt(int64(gwei)), big.NewInt(GweiFactor)) +} + // String satisfies the Stringer interface. func (ss SwapState) String() string { switch ss {