mirror of
https://github.com/AthanorLabs/atomic-swap.git
synced 2026-01-08 21:58:07 -05:00
min and max offer amount checks (#445)
Co-authored-by: noot <36753753+noot@users.noreply.github.com>
This commit is contained in:
@@ -23,7 +23,7 @@ type swapCLITestSuite struct {
|
|||||||
mockTokens map[string]ethcommon.Address
|
mockTokens map[string]ethcommon.Address
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRunIntegrationTests(t *testing.T) {
|
func TestRunSwapcliWithDaemonTests(t *testing.T) {
|
||||||
s := new(swapCLITestSuite)
|
s := new(swapCLITestSuite)
|
||||||
|
|
||||||
s.conf = daemon.CreateTestConf(t, tests.GetMakerTestKey(t))
|
s.conf = daemon.CreateTestConf(t, tests.GetMakerTestKey(t))
|
||||||
|
|||||||
@@ -18,12 +18,12 @@ import (
|
|||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
|
|
||||||
"github.com/athanorlabs/atomic-swap/cliutil"
|
"github.com/athanorlabs/atomic-swap/cliutil"
|
||||||
|
"github.com/athanorlabs/atomic-swap/coins"
|
||||||
"github.com/athanorlabs/atomic-swap/common"
|
"github.com/athanorlabs/atomic-swap/common"
|
||||||
mcrypto "github.com/athanorlabs/atomic-swap/crypto/monero"
|
mcrypto "github.com/athanorlabs/atomic-swap/crypto/monero"
|
||||||
"github.com/athanorlabs/atomic-swap/daemon"
|
"github.com/athanorlabs/atomic-swap/daemon"
|
||||||
"github.com/athanorlabs/atomic-swap/ethereum/extethclient"
|
"github.com/athanorlabs/atomic-swap/ethereum/extethclient"
|
||||||
"github.com/athanorlabs/atomic-swap/monero"
|
"github.com/athanorlabs/atomic-swap/monero"
|
||||||
"github.com/athanorlabs/atomic-swap/relayer"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -209,7 +209,7 @@ func cliApp() *cli.App {
|
|||||||
Name: flagRelayer,
|
Name: flagRelayer,
|
||||||
Usage: fmt.Sprintf(
|
Usage: fmt.Sprintf(
|
||||||
"Relay claims for XMR makers and earn %s ETH (minus gas fees) per transaction",
|
"Relay claims for XMR makers and earn %s ETH (minus gas fees) per transaction",
|
||||||
relayer.FeeEth.Text('f'),
|
coins.RelayerFeeETH.Text('f'),
|
||||||
),
|
),
|
||||||
Value: false,
|
Value: false,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ coverage:
|
|||||||
ignore:
|
ignore:
|
||||||
- ethereum/swap_creator.go
|
- ethereum/swap_creator.go
|
||||||
- ethereum/aggregator_v3_interface.go
|
- ethereum/aggregator_v3_interface.go
|
||||||
- ethereum/erc20_mock.go
|
- ethereum/erc20_token.go
|
||||||
- ethereum/ierc20.go
|
- ethereum/ierc20.go
|
||||||
- ethereum/block/ut_contract_test.go
|
- ethereum/block/ut_contract_test.go
|
||||||
- cmd/swapd/profile.go
|
- cmd/swapd/profile.go
|
||||||
|
|||||||
@@ -4,6 +4,8 @@
|
|||||||
package coins
|
package coins
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"math/big"
|
||||||
|
|
||||||
"github.com/cockroachdb/apd/v3"
|
"github.com/cockroachdb/apd/v3"
|
||||||
logging "github.com/ipfs/go-log"
|
logging "github.com/ipfs/go-log"
|
||||||
)
|
)
|
||||||
@@ -20,6 +22,13 @@ const (
|
|||||||
MaxCoinPrecision = 100
|
MaxCoinPrecision = 100
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// RelayerFeeWei and RelayerFeeETH are the fixed 0.009 ETH fee for using a swap
|
||||||
|
// relayer to claim.
|
||||||
|
var (
|
||||||
|
RelayerFeeWei = big.NewInt(9e15)
|
||||||
|
RelayerFeeETH = NewWeiAmount(RelayerFeeWei).AsEther()
|
||||||
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
// decimalCtx is the apd context used for math operations on our coins
|
// decimalCtx is the apd context used for math operations on our coins
|
||||||
decimalCtx = apd.BaseContext.WithPrecision(MaxCoinPrecision)
|
decimalCtx = apd.BaseContext.WithPrecision(MaxCoinPrecision)
|
||||||
|
|||||||
@@ -22,6 +22,12 @@ var (
|
|||||||
// CurOfferVersion is the latest supported version of a serialised Offer struct
|
// CurOfferVersion is the latest supported version of a serialised Offer struct
|
||||||
CurOfferVersion, _ = semver.NewVersion("1.0.0")
|
CurOfferVersion, _ = semver.NewVersion("1.0.0")
|
||||||
|
|
||||||
|
// Don't allow offers over 1000 XMR. Mainly to prevent fat-finger errors, it
|
||||||
|
// could be raised if users need it.
|
||||||
|
maxOfferValue = apd.New(1, 3) // 1000 XMR
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
errOfferVersionMissing = errors.New(`required "version" field missing in offer`)
|
errOfferVersionMissing = errors.New(`required "version" field missing in offer`)
|
||||||
errOfferIDNotSet = errors.New(`"offerID" is not set`)
|
errOfferIDNotSet = errors.New(`"offerID" is not set`)
|
||||||
errExchangeRateNil = errors.New(`"exchangeRate" is not set`)
|
errExchangeRateNil = errors.New(`"exchangeRate" is not set`)
|
||||||
@@ -131,6 +137,7 @@ func (o *Offer) validate() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// It is an error if the min is greater than the max
|
||||||
if o.MinAmount.Cmp(o.MaxAmount) > 0 {
|
if o.MinAmount.Cmp(o.MaxAmount) > 0 {
|
||||||
return errMinGreaterThanMax
|
return errMinGreaterThanMax
|
||||||
}
|
}
|
||||||
@@ -141,6 +148,25 @@ func (o *Offer) validate() error {
|
|||||||
return errExchangeRateNil
|
return errExchangeRateNil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// We want to prevent offers whose claim value is so low that a relayer
|
||||||
|
// can't be used to complete the swap if the maker does not have sufficient
|
||||||
|
// ETH to make the claim themselves. While relayers are not used with ERC20
|
||||||
|
// swaps, we still want a minimum swap amount, so we use the same value.
|
||||||
|
relayerFeeAsXMR, err := o.ExchangeRate.ToXMR(coins.RelayerFeeETH)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if o.MinAmount.Cmp(relayerFeeAsXMR) <= 0 {
|
||||||
|
return fmt.Errorf(
|
||||||
|
"min amount must be greater than %s ETH when converted (%s XMR)",
|
||||||
|
coins.RelayerFeeETH.Text('f'), relayerFeeAsXMR.Text('f'))
|
||||||
|
}
|
||||||
|
|
||||||
|
if o.MaxAmount.Cmp(maxOfferValue) > 0 {
|
||||||
|
return fmt.Errorf("%s XMR exceeds max offer amount of %s XMR",
|
||||||
|
o.MaxAmount.Text('f'), maxOfferValue.Text('f'))
|
||||||
|
}
|
||||||
|
|
||||||
if o.ID != o.hash() {
|
if o.ID != o.hash() {
|
||||||
return errors.New("hash of offer fields does not match offer ID")
|
return errors.New("hash of offer fields does not match offer ID")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -175,6 +175,11 @@ func TestOffer_UnmarshalJSON_BadAmountsOrRate(t *testing.T) {
|
|||||||
jsonData: fmt.Sprintf(offerJSON, `"-1"`, `"1"`, `"0.1"`),
|
jsonData: fmt.Sprintf(offerJSON, `"-1"`, `"1"`, `"0.1"`),
|
||||||
errContains: `"minAmount" cannot be negative`,
|
errContains: `"minAmount" cannot be negative`,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
// 0.009 relayer fee is 0.09 XMR with exchange rate of 0.1
|
||||||
|
jsonData: fmt.Sprintf(offerJSON, `"0.09"`, `"10"`, `"0.1"`),
|
||||||
|
errContains: `min amount must be greater than 0.009 ETH when converted (0.09 XMR)`,
|
||||||
|
},
|
||||||
// Max Amount checks
|
// Max Amount checks
|
||||||
{
|
{
|
||||||
jsonData: fmt.Sprintf(offerJSON, `"1"`, `null`, `"0.1"`),
|
jsonData: fmt.Sprintf(offerJSON, `"1"`, `null`, `"0.1"`),
|
||||||
@@ -188,6 +193,10 @@ func TestOffer_UnmarshalJSON_BadAmountsOrRate(t *testing.T) {
|
|||||||
jsonData: fmt.Sprintf(offerJSON, `"1"`, `"-1E1"`, `"0.1"`),
|
jsonData: fmt.Sprintf(offerJSON, `"1"`, `"-1E1"`, `"0.1"`),
|
||||||
errContains: `"maxAmount" cannot be negative`,
|
errContains: `"maxAmount" cannot be negative`,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
jsonData: fmt.Sprintf(offerJSON, `"100"`, `"1000.1"`, `"0.1"`),
|
||||||
|
errContains: `1000.1 XMR exceeds max offer amount of 1000 XMR`,
|
||||||
|
},
|
||||||
// Combo min/max check
|
// Combo min/max check
|
||||||
{
|
{
|
||||||
jsonData: fmt.Sprintf(offerJSON, `"0.11"`, `"0.1"`, `"0.1"`),
|
jsonData: fmt.Sprintf(offerJSON, `"0.11"`, `"0.1"`, `"0.1"`),
|
||||||
|
|||||||
@@ -27,7 +27,6 @@ import (
|
|||||||
"github.com/athanorlabs/atomic-swap/ethereum/extethclient"
|
"github.com/athanorlabs/atomic-swap/ethereum/extethclient"
|
||||||
"github.com/athanorlabs/atomic-swap/monero"
|
"github.com/athanorlabs/atomic-swap/monero"
|
||||||
"github.com/athanorlabs/atomic-swap/net"
|
"github.com/athanorlabs/atomic-swap/net"
|
||||||
"github.com/athanorlabs/atomic-swap/relayer"
|
|
||||||
"github.com/athanorlabs/atomic-swap/rpcclient"
|
"github.com/athanorlabs/atomic-swap/rpcclient"
|
||||||
"github.com/athanorlabs/atomic-swap/rpcclient/wsclient"
|
"github.com/athanorlabs/atomic-swap/rpcclient/wsclient"
|
||||||
"github.com/athanorlabs/atomic-swap/tests"
|
"github.com/athanorlabs/atomic-swap/tests"
|
||||||
@@ -184,7 +183,7 @@ func TestRunSwapDaemon_SwapBobHasNoEth_AliceRelaysClaim(t *testing.T) {
|
|||||||
// Bob's ending balance should be Alice's provided amount minus the relayer fee
|
// Bob's ending balance should be Alice's provided amount minus the relayer fee
|
||||||
//
|
//
|
||||||
expectedBal := new(apd.Decimal)
|
expectedBal := new(apd.Decimal)
|
||||||
_, err = coins.DecimalCtx().Sub(expectedBal, providesAmt, relayer.FeeEth)
|
_, err = coins.DecimalCtx().Sub(expectedBal, providesAmt, coins.RelayerFeeETH)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
bobBalance, err := bobConf.EthereumClient.Balance(ctx)
|
bobBalance, err := bobConf.EthereumClient.Balance(ctx)
|
||||||
@@ -362,7 +361,7 @@ func TestRunSwapDaemon_CharlieRelays(t *testing.T) {
|
|||||||
// Bob's ending balance should be Alice's provided amount minus the relayer fee
|
// Bob's ending balance should be Alice's provided amount minus the relayer fee
|
||||||
//
|
//
|
||||||
bobExpectedBal := new(apd.Decimal)
|
bobExpectedBal := new(apd.Decimal)
|
||||||
_, err = coins.DecimalCtx().Sub(bobExpectedBal, providesAmt, relayer.FeeEth)
|
_, err = coins.DecimalCtx().Sub(bobExpectedBal, providesAmt, coins.RelayerFeeETH)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
bobBalance, err := bobConf.EthereumClient.Balance(ctx)
|
bobBalance, err := bobConf.EthereumClient.Balance(ctx)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@@ -467,7 +466,7 @@ func TestRunSwapDaemon_CharlieIsBroke_AliceRelays(t *testing.T) {
|
|||||||
// Bob's ending balance should be Alice's provided amount minus the relayer fee
|
// Bob's ending balance should be Alice's provided amount minus the relayer fee
|
||||||
//
|
//
|
||||||
bobExpectedBal := new(apd.Decimal)
|
bobExpectedBal := new(apd.Decimal)
|
||||||
_, err = coins.DecimalCtx().Sub(bobExpectedBal, providesAmt, relayer.FeeEth)
|
_, err = coins.DecimalCtx().Sub(bobExpectedBal, providesAmt, coins.RelayerFeeETH)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
bobBalance, err := bobConf.EthereumClient.Balance(ctx)
|
bobBalance, err := bobConf.EthereumClient.Balance(ctx)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|||||||
@@ -6,46 +6,24 @@ package contracts
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"crypto/ecdsa"
|
|
||||||
"errors"
|
"errors"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/athanorlabs/go-relayer/impls/gsnforwarder"
|
"github.com/athanorlabs/go-relayer/impls/gsnforwarder"
|
||||||
ethcommon "github.com/ethereum/go-ethereum/common"
|
ethcommon "github.com/ethereum/go-ethereum/common"
|
||||||
ethcrypto "github.com/ethereum/go-ethereum/crypto"
|
ethcrypto "github.com/ethereum/go-ethereum/crypto"
|
||||||
"github.com/ethereum/go-ethereum/ethclient"
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
"github.com/athanorlabs/atomic-swap/common"
|
"github.com/athanorlabs/atomic-swap/common"
|
||||||
"github.com/athanorlabs/atomic-swap/tests"
|
"github.com/athanorlabs/atomic-swap/tests"
|
||||||
)
|
)
|
||||||
|
|
||||||
// deployContract is a test helper that deploys the SwapCreator contract and returns the
|
|
||||||
// deployed address
|
|
||||||
func deployContract(
|
|
||||||
t *testing.T,
|
|
||||||
ec *ethclient.Client,
|
|
||||||
pk *ecdsa.PrivateKey,
|
|
||||||
trustedForwarder ethcommon.Address,
|
|
||||||
) ethcommon.Address {
|
|
||||||
ctx := context.Background()
|
|
||||||
contractAddr, _, err := DeploySwapCreatorWithKey(ctx, ec, pk, trustedForwarder)
|
|
||||||
require.NoError(t, err)
|
|
||||||
return contractAddr
|
|
||||||
}
|
|
||||||
|
|
||||||
func deployForwarder(t *testing.T, ec *ethclient.Client, pk *ecdsa.PrivateKey) ethcommon.Address {
|
|
||||||
addr, err := DeployGSNForwarderWithKey(context.Background(), ec, pk)
|
|
||||||
require.NoError(t, err)
|
|
||||||
return addr
|
|
||||||
}
|
|
||||||
|
|
||||||
// getContractCode is a test helper that deploys the swap creator contract to read back
|
// getContractCode is a test helper that deploys the swap creator contract to read back
|
||||||
// and return the finalised byte code post deployment.
|
// and return the finalised byte code post deployment.
|
||||||
func getContractCode(t *testing.T, trustedForwarder ethcommon.Address) []byte {
|
func getContractCode(t *testing.T, forwarderAddr ethcommon.Address) []byte {
|
||||||
ec, _ := tests.NewEthClient(t)
|
ec, _ := tests.NewEthClient(t)
|
||||||
pk := tests.GetMakerTestKey(t)
|
pk := tests.GetMakerTestKey(t)
|
||||||
contractAddr := deployContract(t, ec, pk, trustedForwarder)
|
contractAddr, _ := deploySwapCreatorWithForwarder(t, ec, pk, forwarderAddr)
|
||||||
code, err := ec.CodeAt(context.Background(), contractAddr, nil)
|
code, err := ec.CodeAt(context.Background(), contractAddr, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
return code
|
return code
|
||||||
@@ -54,8 +32,8 @@ func getContractCode(t *testing.T, trustedForwarder ethcommon.Address) []byte {
|
|||||||
func TestCheckForwarderContractCode(t *testing.T) {
|
func TestCheckForwarderContractCode(t *testing.T) {
|
||||||
ec, _ := tests.NewEthClient(t)
|
ec, _ := tests.NewEthClient(t)
|
||||||
pk := tests.GetMakerTestKey(t)
|
pk := tests.GetMakerTestKey(t)
|
||||||
trustedForwarder := deployForwarder(t, ec, pk)
|
forwarderAddr := deployForwarder(t, ec, pk)
|
||||||
err := CheckForwarderContractCode(context.Background(), ec, trustedForwarder)
|
err := CheckForwarderContractCode(context.Background(), ec, forwarderAddr)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -66,7 +44,7 @@ func TestExpectedSwapCreatorBytecodeHex(t *testing.T) {
|
|||||||
allZeroTrustedForwarder := ethcommon.Address{}
|
allZeroTrustedForwarder := ethcommon.Address{}
|
||||||
codeHex := ethcommon.Bytes2Hex(getContractCode(t, allZeroTrustedForwarder))
|
codeHex := ethcommon.Bytes2Hex(getContractCode(t, allZeroTrustedForwarder))
|
||||||
require.Equal(t, expectedSwapCreatorBytecodeHex, codeHex,
|
require.Equal(t, expectedSwapCreatorBytecodeHex, codeHex,
|
||||||
"update the expectedSwapCreatorBytecodeHex constant with the actual value tocat fix this test")
|
"update the expectedSwapCreatorBytecodeHex constant with the actual value to fix this test")
|
||||||
}
|
}
|
||||||
|
|
||||||
// This test will fail if the compiled SwapCreator contract is updated, but the
|
// This test will fail if the compiled SwapCreator contract is updated, but the
|
||||||
@@ -75,12 +53,12 @@ func TestExpectedSwapCreatorBytecodeHex(t *testing.T) {
|
|||||||
func TestForwarderAddrIndexes(t *testing.T) {
|
func TestForwarderAddrIndexes(t *testing.T) {
|
||||||
ec, _ := tests.NewEthClient(t)
|
ec, _ := tests.NewEthClient(t)
|
||||||
pk := tests.GetMakerTestKey(t)
|
pk := tests.GetMakerTestKey(t)
|
||||||
trustedForwarder := deployForwarder(t, ec, pk)
|
forwarderAddr := deployForwarder(t, ec, pk)
|
||||||
contactBytes := getContractCode(t, trustedForwarder)
|
contactBytes := getContractCode(t, forwarderAddr)
|
||||||
|
|
||||||
addressLocations := make([]int, 0) // at the current time, there should always be 2
|
addressLocations := make([]int, 0) // at the current time, there should always be 2
|
||||||
for i := 0; i < len(contactBytes)-ethAddrByteLen; i++ {
|
for i := 0; i < len(contactBytes)-ethAddrByteLen; i++ {
|
||||||
if bytes.Equal(contactBytes[i:i+ethAddrByteLen], trustedForwarder[:]) {
|
if bytes.Equal(contactBytes[i:i+ethAddrByteLen], forwarderAddr[:]) {
|
||||||
addressLocations = append(addressLocations, i)
|
addressLocations = append(addressLocations, i)
|
||||||
i += ethAddrByteLen - 1 // -1 since the loop will increment by 1
|
i += ethAddrByteLen - 1 // -1 since the loop will increment by 1
|
||||||
}
|
}
|
||||||
@@ -96,15 +74,15 @@ func TestForwarderAddrIndexes(t *testing.T) {
|
|||||||
func TestCheckSwapCreatorContractCode(t *testing.T) {
|
func TestCheckSwapCreatorContractCode(t *testing.T) {
|
||||||
ec, _ := tests.NewEthClient(t)
|
ec, _ := tests.NewEthClient(t)
|
||||||
pk := tests.GetMakerTestKey(t)
|
pk := tests.GetMakerTestKey(t)
|
||||||
trustedForwarderAddrs := []string{
|
forwarderAddrs := []string{
|
||||||
deployForwarder(t, ec, pk).Hex(),
|
deployForwarder(t, ec, pk).Hex(),
|
||||||
deployForwarder(t, ec, pk).Hex(),
|
deployForwarder(t, ec, pk).Hex(),
|
||||||
deployForwarder(t, ec, pk).Hex(),
|
deployForwarder(t, ec, pk).Hex(),
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, addrHex := range trustedForwarderAddrs {
|
for _, addrHex := range forwarderAddrs {
|
||||||
tfAddr := ethcommon.HexToAddress(addrHex)
|
tfAddr := ethcommon.HexToAddress(addrHex)
|
||||||
contractAddr := deployContract(t, ec, pk, tfAddr)
|
contractAddr, _ := deploySwapCreatorWithForwarder(t, ec, pk, tfAddr)
|
||||||
parsedTFAddr, err := CheckSwapCreatorContractCode(context.Background(), ec, contractAddr)
|
parsedTFAddr, err := CheckSwapCreatorContractCode(context.Background(), ec, contractAddr)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, addrHex, parsedTFAddr.Hex())
|
require.Equal(t, addrHex, parsedTFAddr.Hex())
|
||||||
|
|||||||
15
ethereum/consts.go
Normal file
15
ethereum/consts.go
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
package contracts
|
||||||
|
|
||||||
|
// Gas prices for our operations. Most of these are set by the highest value we
|
||||||
|
// ever see in a test, so you would need to adjust upwards a little to use as a
|
||||||
|
// gas limit. We use these values to estimate minimum required balances.
|
||||||
|
const (
|
||||||
|
MaxNewSwapETHGas = 50589
|
||||||
|
MaxNewSwapTokenGas = 86218
|
||||||
|
MaxSetReadyGas = 31872
|
||||||
|
MaxClaimETHGas = 43349
|
||||||
|
MaxClaimTokenGas = 47522
|
||||||
|
MaxRefundETHGas = 43120
|
||||||
|
MaxRefundTokenGas = 47282
|
||||||
|
MaxTokenApproveGas = 47000 // 46223 with our contract
|
||||||
|
)
|
||||||
8
ethereum/consts_test.go
Normal file
8
ethereum/consts_test.go
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
package contracts
|
||||||
|
|
||||||
|
// We don't deploy SwapCreator contracts or ERC20 token contracts in swaps, so
|
||||||
|
// these constants are only compiled in for test files.
|
||||||
|
const (
|
||||||
|
maxSwapCreatorDeployGas = 1005177
|
||||||
|
maxTestERC20DeployGas = 798226 // using long token names or symbols will increase this
|
||||||
|
)
|
||||||
@@ -1,80 +0,0 @@
|
|||||||
// Copyright 2023 The AthanorLabs/atomic-swap Authors
|
|
||||||
// SPDX-License-Identifier: LGPL-3.0-only
|
|
||||||
|
|
||||||
package contracts
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"crypto/ecdsa"
|
|
||||||
"math/big"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
|
|
||||||
"github.com/athanorlabs/atomic-swap/ethereum/block"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestSwapCreator_NewSwap_ERC20(t *testing.T) {
|
|
||||||
auth, conn, pkA := setupXMRTakerAuth(t)
|
|
||||||
pub := pkA.Public().(*ecdsa.PublicKey)
|
|
||||||
addr := crypto.PubkeyToAddress(*pub)
|
|
||||||
|
|
||||||
// deploy TestERC20
|
|
||||||
erc20Addr, erc20Tx, erc20Contract, err :=
|
|
||||||
DeployTestERC20(auth, conn, "TestERC20", "MOCK", 18, addr, big.NewInt(9999))
|
|
||||||
require.NoError(t, err)
|
|
||||||
receipt, err := block.WaitForReceipt(context.Background(), conn, erc20Tx.Hash())
|
|
||||||
require.NoError(t, err)
|
|
||||||
t.Logf("gas cost to deploy TestERC20.sol: %d", receipt.GasUsed)
|
|
||||||
|
|
||||||
testNewSwap(t, erc20Addr, erc20Contract)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSwapCreator_Claim_ERC20(t *testing.T) {
|
|
||||||
auth, conn, pkA := setupXMRTakerAuth(t)
|
|
||||||
pub := pkA.Public().(*ecdsa.PublicKey)
|
|
||||||
addr := crypto.PubkeyToAddress(*pub)
|
|
||||||
|
|
||||||
erc20Addr, erc20Tx, erc20Contract, err := DeployTestERC20(auth, conn, "TestERC20", "TEST", 18, addr, big.NewInt(9999))
|
|
||||||
require.NoError(t, err)
|
|
||||||
receipt, err := block.WaitForReceipt(context.Background(), conn, erc20Tx.Hash())
|
|
||||||
require.NoError(t, err)
|
|
||||||
t.Logf("gas cost to deploy TestERC20.sol: %d", receipt.GasUsed)
|
|
||||||
|
|
||||||
// 3 logs:
|
|
||||||
// Approval
|
|
||||||
// Transfer
|
|
||||||
// New
|
|
||||||
testClaim(t, erc20Addr, 2, big.NewInt(99), erc20Contract)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSwapCreator_RefundBeforeT0_ERC20(t *testing.T) {
|
|
||||||
auth, conn, pkA := setupXMRTakerAuth(t)
|
|
||||||
pub := pkA.Public().(*ecdsa.PublicKey)
|
|
||||||
addr := crypto.PubkeyToAddress(*pub)
|
|
||||||
|
|
||||||
erc20Addr, erc20Tx, erc20Contract, err :=
|
|
||||||
DeployTestERC20(auth, conn, "TestERC20", "TEST", 18, addr, big.NewInt(9999))
|
|
||||||
require.NoError(t, err)
|
|
||||||
receipt, err := block.WaitForReceipt(context.Background(), conn, erc20Tx.Hash())
|
|
||||||
require.NoError(t, err)
|
|
||||||
t.Logf("gas cost to deploy TestERC20.sol: %d", receipt.GasUsed)
|
|
||||||
|
|
||||||
testRefundBeforeT0(t, erc20Addr, erc20Contract, 2)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSwapCreator_RefundAfterT1_ERC20(t *testing.T) {
|
|
||||||
auth, conn, pkA := setupXMRTakerAuth(t)
|
|
||||||
pub := pkA.Public().(*ecdsa.PublicKey)
|
|
||||||
addr := crypto.PubkeyToAddress(*pub)
|
|
||||||
|
|
||||||
erc20Addr, erc20Tx, erc20Contract, err :=
|
|
||||||
DeployTestERC20(auth, conn, "TestERC20", "TestERC20", 18, addr, big.NewInt(9999))
|
|
||||||
require.NoError(t, err)
|
|
||||||
receipt, err := block.WaitForReceipt(context.Background(), conn, erc20Tx.Hash())
|
|
||||||
require.NoError(t, err)
|
|
||||||
t.Logf("gas cost to deploy TestERC20.sol: %d", receipt.GasUsed)
|
|
||||||
|
|
||||||
testRefundAfterT1(t, erc20Addr, erc20Contract, 2)
|
|
||||||
}
|
|
||||||
84
ethereum/erc20_token_test.go
Normal file
84
ethereum/erc20_token_test.go
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
// Copyright 2023 The AthanorLabs/atomic-swap Authors
|
||||||
|
// SPDX-License-Identifier: LGPL-3.0-only
|
||||||
|
|
||||||
|
package contracts
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math/big"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
"github.com/athanorlabs/atomic-swap/common/types"
|
||||||
|
"github.com/athanorlabs/atomic-swap/tests"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSwapCreator_NewSwap_ERC20(t *testing.T) {
|
||||||
|
pkA := tests.GetTakerTestKey(t)
|
||||||
|
ec, _ := tests.NewEthClient(t)
|
||||||
|
addr := crypto.PubkeyToAddress(pkA.PublicKey)
|
||||||
|
|
||||||
|
// deploy TestERC20
|
||||||
|
erc20Addr, tx, erc20Contract, err :=
|
||||||
|
DeployTestERC20(getAuth(t, pkA), ec, "Test of the ERC20 Token", "ERC20Token", 18, addr, big.NewInt(9999))
|
||||||
|
require.NoError(t, err)
|
||||||
|
receipt := getReceipt(t, ec, tx)
|
||||||
|
t.Logf("gas cost to deploy TestERC20.sol: %d (delta %d)",
|
||||||
|
receipt.GasUsed, maxTestERC20DeployGas-int(receipt.GasUsed))
|
||||||
|
require.GreaterOrEqual(t, maxTestERC20DeployGas, int(receipt.GasUsed))
|
||||||
|
|
||||||
|
testNewSwap(t, types.EthAsset(erc20Addr), erc20Contract)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSwapCreator_Claim_ERC20(t *testing.T) {
|
||||||
|
pkA := tests.GetTakerTestKey(t)
|
||||||
|
ec, _ := tests.NewEthClient(t)
|
||||||
|
addr := crypto.PubkeyToAddress(pkA.PublicKey)
|
||||||
|
|
||||||
|
erc20Addr, tx, erc20Contract, err :=
|
||||||
|
DeployTestERC20(getAuth(t, pkA), ec, "TestERC20", "TEST", 18, addr, big.NewInt(9999))
|
||||||
|
require.NoError(t, err)
|
||||||
|
receipt := getReceipt(t, ec, tx)
|
||||||
|
t.Logf("gas cost to deploy TestERC20.sol: %d (delta %d)",
|
||||||
|
receipt.GasUsed, maxTestERC20DeployGas-int(receipt.GasUsed))
|
||||||
|
require.GreaterOrEqual(t, maxTestERC20DeployGas, int(receipt.GasUsed))
|
||||||
|
|
||||||
|
// 3 logs:
|
||||||
|
// Approval
|
||||||
|
// Transfer
|
||||||
|
// New
|
||||||
|
testClaim(t, types.EthAsset(erc20Addr), 2, big.NewInt(99), erc20Contract)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSwapCreator_RefundBeforeT0_ERC20(t *testing.T) {
|
||||||
|
pkA := tests.GetTakerTestKey(t)
|
||||||
|
ec, _ := tests.NewEthClient(t)
|
||||||
|
addr := crypto.PubkeyToAddress(pkA.PublicKey)
|
||||||
|
|
||||||
|
erc20Addr, tx, erc20Contract, err :=
|
||||||
|
DeployTestERC20(getAuth(t, pkA), ec, "TestERC20", "TEST", 18, addr, big.NewInt(9999))
|
||||||
|
require.NoError(t, err)
|
||||||
|
receipt := getReceipt(t, ec, tx)
|
||||||
|
t.Logf("gas cost to deploy TestERC20.sol: %d (delta %d)",
|
||||||
|
receipt.GasUsed, maxTestERC20DeployGas-int(receipt.GasUsed))
|
||||||
|
require.GreaterOrEqual(t, maxTestERC20DeployGas, int(receipt.GasUsed))
|
||||||
|
|
||||||
|
testRefundBeforeT0(t, types.EthAsset(erc20Addr), erc20Contract, 2)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSwapCreator_RefundAfterT1_ERC20(t *testing.T) {
|
||||||
|
pkA := tests.GetTakerTestKey(t)
|
||||||
|
ec, _ := tests.NewEthClient(t)
|
||||||
|
addr := crypto.PubkeyToAddress(pkA.PublicKey)
|
||||||
|
|
||||||
|
erc20Addr, tx, erc20Contract, err :=
|
||||||
|
DeployTestERC20(getAuth(t, pkA), ec, "TestERC20", "TestERC20", 18, addr, big.NewInt(9999))
|
||||||
|
require.NoError(t, err)
|
||||||
|
receipt := getReceipt(t, ec, tx)
|
||||||
|
t.Logf("gas cost to deploy TestERC20.sol: %d (delta %d)",
|
||||||
|
receipt.GasUsed, maxTestERC20DeployGas-int(receipt.GasUsed))
|
||||||
|
require.GreaterOrEqual(t, maxTestERC20DeployGas, int(receipt.GasUsed))
|
||||||
|
|
||||||
|
testRefundAfterT1(t, types.EthAsset(erc20Addr), erc20Contract, 2)
|
||||||
|
}
|
||||||
@@ -15,6 +15,7 @@ import (
|
|||||||
|
|
||||||
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
||||||
ethcommon "github.com/ethereum/go-ethereum/common"
|
ethcommon "github.com/ethereum/go-ethereum/common"
|
||||||
|
ethtypes "github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
"github.com/ethereum/go-ethereum/ethclient"
|
"github.com/ethereum/go-ethereum/ethclient"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
@@ -29,53 +30,78 @@ import (
|
|||||||
|
|
||||||
var (
|
var (
|
||||||
defaultTimeoutDuration = big.NewInt(60) // 60 seconds
|
defaultTimeoutDuration = big.NewInt(60) // 60 seconds
|
||||||
ethAssetAddress = ethcommon.Address(types.EthAssetETH)
|
|
||||||
defaultSwapValue = big.NewInt(100)
|
defaultSwapValue = big.NewInt(100)
|
||||||
dummySwapKey = [32]byte{1} // dummy non-zero value for claim/refund key
|
dummySwapKey = [32]byte{1} // dummy non-zero value for claim/refund key
|
||||||
)
|
)
|
||||||
|
|
||||||
func setupXMRTakerAuth(t *testing.T) (*bind.TransactOpts, *ethclient.Client, *ecdsa.PrivateKey) {
|
func getAuth(t *testing.T, pk *ecdsa.PrivateKey) *bind.TransactOpts {
|
||||||
conn, _ := tests.NewEthClient(t)
|
txOpts, err := bind.NewKeyedTransactorWithChainID(pk, big.NewInt(common.GanacheChainID))
|
||||||
pkA := tests.GetTakerTestKey(t)
|
|
||||||
chainID, err := conn.ChainID(context.Background())
|
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
auth, err := bind.NewKeyedTransactorWithChainID(pkA, chainID)
|
return txOpts
|
||||||
|
}
|
||||||
|
|
||||||
|
func getReceipt(t *testing.T, ec *ethclient.Client, tx *ethtypes.Transaction) *ethtypes.Receipt {
|
||||||
|
receipt, err := block.WaitForReceipt(context.Background(), ec, tx.Hash())
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
return auth, conn, pkA
|
return receipt
|
||||||
}
|
}
|
||||||
|
|
||||||
func approveERC20(t *testing.T,
|
func approveERC20(t *testing.T,
|
||||||
auth *bind.TransactOpts,
|
ec *ethclient.Client,
|
||||||
conn *ethclient.Client,
|
pk *ecdsa.PrivateKey,
|
||||||
erc20Contract *TestERC20,
|
erc20Contract *TestERC20,
|
||||||
swapCreatorAddress ethcommon.Address,
|
swapCreatorAddress ethcommon.Address,
|
||||||
value *big.Int,
|
value *big.Int,
|
||||||
) {
|
) {
|
||||||
require.NotNil(t, erc20Contract)
|
require.NotNil(t, erc20Contract)
|
||||||
tx, err := erc20Contract.Approve(auth, swapCreatorAddress, value)
|
|
||||||
|
tx, err := erc20Contract.Approve(getAuth(t, pk), swapCreatorAddress, value)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
receipt, err := block.WaitForReceipt(context.Background(), conn, tx.Hash())
|
receipt := getReceipt(t, ec, tx)
|
||||||
require.NoError(t, err)
|
|
||||||
t.Logf("gas cost to call Approve: %d", receipt.GasUsed)
|
t.Logf("gas cost to call Approve %d (delta %d)", receipt.GasUsed, MaxTokenApproveGas-int(receipt.GasUsed))
|
||||||
|
require.GreaterOrEqual(t, MaxTokenApproveGas, int(receipt.GasUsed), "Token Approve")
|
||||||
}
|
}
|
||||||
|
|
||||||
func testNewSwap(t *testing.T, asset ethcommon.Address, erc20Contract *TestERC20) {
|
func deployForwarder(t *testing.T, ec *ethclient.Client, pk *ecdsa.PrivateKey) ethcommon.Address {
|
||||||
auth, conn, _ := setupXMRTakerAuth(t)
|
forwarderAddr, err := DeployGSNForwarderWithKey(context.Background(), ec, pk)
|
||||||
address, tx, contract, err := DeploySwapCreator(auth, conn, ethcommon.Address{})
|
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.NotEqual(t, ethcommon.Address{}, address)
|
return forwarderAddr
|
||||||
require.NotNil(t, tx)
|
}
|
||||||
require.NotNil(t, contract)
|
|
||||||
|
|
||||||
receipt, err := block.WaitForReceipt(context.Background(), conn, tx.Hash())
|
func deploySwapCreatorWithForwarder(
|
||||||
|
t *testing.T,
|
||||||
|
ec *ethclient.Client,
|
||||||
|
pk *ecdsa.PrivateKey,
|
||||||
|
forwarderAddr ethcommon.Address,
|
||||||
|
) (ethcommon.Address, *SwapCreator) {
|
||||||
|
swapCreatorAddr, tx, swapCreator, err := DeploySwapCreator(getAuth(t, pk), ec, forwarderAddr)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
t.Logf("gas cost to deploy SwapCreator.sol: %d", receipt.GasUsed)
|
receipt := getReceipt(t, ec, tx)
|
||||||
|
|
||||||
owner := auth.From
|
t.Logf("gas cost to deploy SwapCreator.sol: %d (delta %d)",
|
||||||
|
receipt.GasUsed, maxSwapCreatorDeployGas-int(receipt.GasUsed))
|
||||||
|
require.GreaterOrEqual(t, maxSwapCreatorDeployGas, int(receipt.GasUsed), "deploy SwapCreator")
|
||||||
|
|
||||||
|
return swapCreatorAddr, swapCreator
|
||||||
|
}
|
||||||
|
|
||||||
|
func deploySwapCreator(t *testing.T, ec *ethclient.Client, pk *ecdsa.PrivateKey) (ethcommon.Address, *SwapCreator) {
|
||||||
|
forwarderAddr := deployForwarder(t, ec, pk)
|
||||||
|
return deploySwapCreatorWithForwarder(t, ec, pk, forwarderAddr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testNewSwap(t *testing.T, asset types.EthAsset, erc20Contract *TestERC20) {
|
||||||
|
pk := tests.GetTakerTestKey(t)
|
||||||
|
ec, _ := tests.NewEthClient(t)
|
||||||
|
|
||||||
|
swapCreatorAddr, swapCreator := deploySwapCreator(t, ec, pk)
|
||||||
|
|
||||||
|
owner := crypto.PubkeyToAddress(pk.PublicKey)
|
||||||
claimer := common.EthereumPrivateKeyToAddress(tests.GetMakerTestKey(t))
|
claimer := common.EthereumPrivateKeyToAddress(tests.GetMakerTestKey(t))
|
||||||
|
|
||||||
var pubKeyClaim, pubKeyRefund [32]byte
|
var pubKeyClaim, pubKeyRefund [32]byte
|
||||||
_, err = rand.Read(pubKeyClaim[:])
|
_, err := rand.Read(pubKeyClaim[:])
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
_, err = rand.Read(pubKeyRefund[:])
|
_, err = rand.Read(pubKeyRefund[:])
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@@ -83,34 +109,40 @@ func testNewSwap(t *testing.T, asset ethcommon.Address, erc20Contract *TestERC20
|
|||||||
nonce, err := rand.Prime(rand.Reader, 256)
|
nonce, err := rand.Prime(rand.Reader, 256)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
txOpts := getAuth(t, pk)
|
||||||
value := defaultSwapValue
|
value := defaultSwapValue
|
||||||
isEthAsset := asset == ethAssetAddress
|
if asset.IsETH() {
|
||||||
|
txOpts.Value = value
|
||||||
if isEthAsset {
|
|
||||||
auth.Value = value
|
|
||||||
} else {
|
} else {
|
||||||
approveERC20(t, auth, conn, erc20Contract, address, value)
|
approveERC20(t, ec, pk, erc20Contract, swapCreatorAddr, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
tx, err = contract.NewSwap(
|
tx, err := swapCreator.NewSwap(
|
||||||
auth,
|
txOpts,
|
||||||
pubKeyClaim,
|
pubKeyClaim,
|
||||||
pubKeyRefund,
|
pubKeyRefund,
|
||||||
claimer,
|
claimer,
|
||||||
defaultTimeoutDuration,
|
defaultTimeoutDuration,
|
||||||
defaultTimeoutDuration,
|
defaultTimeoutDuration,
|
||||||
asset,
|
asset.Address(),
|
||||||
value,
|
value,
|
||||||
nonce,
|
nonce,
|
||||||
)
|
)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
receipt, err = block.WaitForReceipt(context.Background(), conn, tx.Hash())
|
receipt := getReceipt(t, ec, tx)
|
||||||
require.NoError(t, err)
|
if asset.IsETH() {
|
||||||
t.Logf("gas cost to call new_swap: %d", receipt.GasUsed)
|
t.Logf("gas cost to call ETH NewSwap: %d (delta %d)",
|
||||||
|
receipt.GasUsed, MaxNewSwapETHGas-int(receipt.GasUsed))
|
||||||
|
require.GreaterOrEqual(t, MaxNewSwapETHGas, int(receipt.GasUsed), "ETH NewSwap")
|
||||||
|
} else {
|
||||||
|
t.Logf("gas cost to call token NewSwap: %d (delta %d)",
|
||||||
|
receipt.GasUsed, MaxNewSwapTokenGas-int(receipt.GasUsed))
|
||||||
|
require.GreaterOrEqual(t, MaxNewSwapTokenGas, int(receipt.GasUsed), "Token NewSwap")
|
||||||
|
}
|
||||||
|
|
||||||
newSwapLogIndex := 0
|
newSwapLogIndex := 0
|
||||||
if !isEthAsset {
|
if asset.IsToken() {
|
||||||
newSwapLogIndex = 2
|
newSwapLogIndex = 2
|
||||||
}
|
}
|
||||||
require.Equal(t, newSwapLogIndex+1, len(receipt.Logs))
|
require.Equal(t, newSwapLogIndex+1, len(receipt.Logs))
|
||||||
@@ -129,7 +161,7 @@ func testNewSwap(t *testing.T, asset ethcommon.Address, erc20Contract *TestERC20
|
|||||||
PubKeyRefund: pubKeyRefund,
|
PubKeyRefund: pubKeyRefund,
|
||||||
Timeout0: t0,
|
Timeout0: t0,
|
||||||
Timeout1: t1,
|
Timeout1: t1,
|
||||||
Asset: asset,
|
Asset: asset.Address(),
|
||||||
Value: value,
|
Value: value,
|
||||||
Nonce: nonce,
|
Nonce: nonce,
|
||||||
}
|
}
|
||||||
@@ -139,7 +171,7 @@ func testNewSwap(t *testing.T, asset ethcommon.Address, erc20Contract *TestERC20
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestSwapCreator_NewSwap(t *testing.T) {
|
func TestSwapCreator_NewSwap(t *testing.T) {
|
||||||
testNewSwap(t, ethAssetAddress, nil)
|
testNewSwap(t, types.EthAssetETH, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSwapCreator_Claim_vec(t *testing.T) {
|
func TestSwapCreator_Claim_vec(t *testing.T) {
|
||||||
@@ -159,26 +191,23 @@ func TestSwapCreator_Claim_vec(t *testing.T) {
|
|||||||
cmt := pk.Keccak256()
|
cmt := pk.Keccak256()
|
||||||
|
|
||||||
// deploy swap contract with claim key hash
|
// deploy swap contract with claim key hash
|
||||||
auth, conn, pkA := setupXMRTakerAuth(t)
|
pkA := tests.GetTakerTestKey(t)
|
||||||
pub := pkA.Public().(*ecdsa.PublicKey)
|
ec, _ := tests.NewEthClient(t)
|
||||||
addr := crypto.PubkeyToAddress(*pub)
|
addr := crypto.PubkeyToAddress(pkA.PublicKey)
|
||||||
|
|
||||||
_, tx, contract, err := DeploySwapCreator(auth, conn, ethcommon.Address{})
|
_, swapCreator := deploySwapCreator(t, ec, pkA)
|
||||||
require.NoError(t, err)
|
|
||||||
receipt, err := block.WaitForReceipt(context.Background(), conn, tx.Hash())
|
txOpts := getAuth(t, pkA)
|
||||||
require.NoError(t, err)
|
txOpts.Value = defaultSwapValue
|
||||||
t.Logf("gas cost to deploy SwapCreator.sol: %d", receipt.GasUsed)
|
|
||||||
|
|
||||||
nonce := big.NewInt(0)
|
nonce := big.NewInt(0)
|
||||||
auth.Value = defaultSwapValue
|
tx, err := swapCreator.NewSwap(txOpts, cmt, dummySwapKey, addr, defaultTimeoutDuration,
|
||||||
tx, err = contract.NewSwap(auth, cmt, dummySwapKey, addr, defaultTimeoutDuration,
|
|
||||||
defaultTimeoutDuration, ethcommon.Address(types.EthAssetETH), defaultSwapValue, nonce)
|
defaultTimeoutDuration, ethcommon.Address(types.EthAssetETH), defaultSwapValue, nonce)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
auth.Value = nil
|
|
||||||
|
|
||||||
receipt, err = block.WaitForReceipt(context.Background(), conn, tx.Hash())
|
receipt := getReceipt(t, ec, tx)
|
||||||
require.NoError(t, err)
|
t.Logf("gas cost to call ETH NewSwap: %d (delta %d)", receipt.GasUsed, MaxNewSwapETHGas-int(receipt.GasUsed))
|
||||||
t.Logf("gas cost to call new_swap: %d", receipt.GasUsed)
|
require.GreaterOrEqual(t, MaxNewSwapETHGas, int(receipt.GasUsed), "ETH NewSwap")
|
||||||
|
|
||||||
require.Equal(t, 1, len(receipt.Logs))
|
require.Equal(t, 1, len(receipt.Logs))
|
||||||
id, err := GetIDFromLog(receipt.Logs[0])
|
id, err := GetIDFromLog(receipt.Logs[0])
|
||||||
@@ -200,25 +229,26 @@ func TestSwapCreator_Claim_vec(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// set contract to Ready
|
// set contract to Ready
|
||||||
tx, err = contract.SetReady(auth, swap)
|
tx, err = swapCreator.SetReady(getAuth(t, pkA), swap)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
receipt, err = block.WaitForReceipt(context.Background(), conn, tx.Hash())
|
receipt = getReceipt(t, ec, tx)
|
||||||
require.NoError(t, err)
|
|
||||||
t.Logf("gas cost to call set_ready: %d", receipt.GasUsed)
|
t.Logf("gas cost to call SetReady: %d (delta %d)", receipt.GasUsed, MaxSetReadyGas-int(receipt.GasUsed))
|
||||||
|
require.GreaterOrEqual(t, MaxSetReadyGas, int(receipt.GasUsed), "SetReady")
|
||||||
|
|
||||||
// now let's try to claim
|
// now let's try to claim
|
||||||
tx, err = contract.Claim(auth, swap, s)
|
tx, err = swapCreator.Claim(getAuth(t, pkA), swap, s)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
receipt, err = block.WaitForReceipt(context.Background(), conn, tx.Hash())
|
receipt = getReceipt(t, ec, tx)
|
||||||
require.NoError(t, err)
|
t.Logf("gas cost to call ETH Claim: %d (delta %d)", receipt.GasUsed, MaxClaimETHGas-int(receipt.GasUsed))
|
||||||
t.Logf("gas cost to call claim: %d", receipt.GasUsed)
|
require.GreaterOrEqual(t, MaxClaimETHGas, int(receipt.GasUsed), "ETH Claim")
|
||||||
|
|
||||||
stage, err := contract.Swaps(nil, id)
|
stage, err := swapCreator.Swaps(nil, id)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, StageCompleted, stage)
|
require.Equal(t, StageCompleted, stage)
|
||||||
}
|
}
|
||||||
|
|
||||||
func testClaim(t *testing.T, asset ethcommon.Address, newLogIndex int, value *big.Int, erc20Contract *TestERC20) {
|
func testClaim(t *testing.T, asset types.EthAsset, newLogIndex int, value *big.Int, erc20Contract *TestERC20) {
|
||||||
// generate claim secret and public key
|
// generate claim secret and public key
|
||||||
dleq := &dleq.DefaultDLEq{}
|
dleq := &dleq.DefaultDLEq{}
|
||||||
proof, err := dleq.Prove()
|
proof, err := dleq.Prove()
|
||||||
@@ -230,36 +260,37 @@ func testClaim(t *testing.T, asset ethcommon.Address, newLogIndex int, value *bi
|
|||||||
cmt := res.Secp256k1PublicKey().Keccak256()
|
cmt := res.Secp256k1PublicKey().Keccak256()
|
||||||
|
|
||||||
// deploy swap contract with claim key hash
|
// deploy swap contract with claim key hash
|
||||||
authOrig, conn, pkA := setupXMRTakerAuth(t)
|
pkA := tests.GetTakerTestKey(t)
|
||||||
pub := pkA.Public().(*ecdsa.PublicKey)
|
ec, _ := tests.NewEthClient(t)
|
||||||
addr := crypto.PubkeyToAddress(*pub)
|
addr := crypto.PubkeyToAddress(pkA.PublicKey)
|
||||||
|
|
||||||
// TODO: Rewrite this code to avoid the awkward use of txOpts. Code was using
|
swapCreatorAddr, swapCreator := deploySwapCreator(t, ec, pkA)
|
||||||
// same TxOpts for multiple transactions and we needed a quick fix to get
|
|
||||||
// CI working.
|
|
||||||
txOpts := *authOrig
|
|
||||||
swapCreatorAddr, tx, contract, err := DeploySwapCreator(&txOpts, conn, ethcommon.Address{})
|
|
||||||
require.NoError(t, err)
|
|
||||||
receipt, err := block.WaitForReceipt(context.Background(), conn, tx.Hash())
|
|
||||||
require.NoError(t, err)
|
|
||||||
t.Logf("gas cost to deploy SwapCreator.sol: %d", receipt.GasUsed)
|
|
||||||
|
|
||||||
if asset != ethAssetAddress {
|
if asset.IsToken() {
|
||||||
approveERC20(t, authOrig, conn, erc20Contract, swapCreatorAddr, value)
|
approveERC20(t, ec, pkA, erc20Contract, swapCreatorAddr, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
nonce := big.NewInt(0)
|
txOpts := getAuth(t, pkA)
|
||||||
txOpts = *authOrig
|
require.NoError(t, err)
|
||||||
if asset == ethAssetAddress {
|
if asset.IsETH() {
|
||||||
txOpts.Value = value
|
txOpts.Value = value
|
||||||
}
|
}
|
||||||
|
|
||||||
tx, err = contract.NewSwap(&txOpts, cmt, dummySwapKey, addr,
|
nonce := GenerateNewSwapNonce()
|
||||||
defaultTimeoutDuration, defaultTimeoutDuration, asset, value, nonce)
|
tx, err := swapCreator.NewSwap(txOpts, cmt, dummySwapKey, addr,
|
||||||
|
defaultTimeoutDuration, defaultTimeoutDuration, asset.Address(), value, nonce)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
receipt, err = block.WaitForReceipt(context.Background(), conn, tx.Hash())
|
|
||||||
require.NoError(t, err)
|
receipt := getReceipt(t, ec, tx)
|
||||||
t.Logf("gas cost to call new_swap: %d", receipt.GasUsed)
|
if asset.IsETH() {
|
||||||
|
t.Logf("gas cost to call ETH NewSwap: %d (delta %d)",
|
||||||
|
receipt.GasUsed, MaxNewSwapETHGas-int(receipt.GasUsed))
|
||||||
|
require.GreaterOrEqual(t, MaxNewSwapETHGas, int(receipt.GasUsed), "ETH NewSwap")
|
||||||
|
} else {
|
||||||
|
t.Logf("gas cost to call token NewSwap: %d (delta %d)",
|
||||||
|
receipt.GasUsed, MaxNewSwapTokenGas-int(receipt.GasUsed))
|
||||||
|
require.GreaterOrEqual(t, MaxNewSwapTokenGas, int(receipt.GasUsed), "Token NewSwap")
|
||||||
|
}
|
||||||
|
|
||||||
require.Equal(t, newLogIndex+1, len(receipt.Logs))
|
require.Equal(t, newLogIndex+1, len(receipt.Logs))
|
||||||
id, err := GetIDFromLog(receipt.Logs[newLogIndex])
|
id, err := GetIDFromLog(receipt.Logs[newLogIndex])
|
||||||
@@ -275,42 +306,45 @@ func testClaim(t *testing.T, asset ethcommon.Address, newLogIndex int, value *bi
|
|||||||
PubKeyRefund: dummySwapKey,
|
PubKeyRefund: dummySwapKey,
|
||||||
Timeout0: t0,
|
Timeout0: t0,
|
||||||
Timeout1: t1,
|
Timeout1: t1,
|
||||||
Asset: asset,
|
Asset: asset.Address(),
|
||||||
Value: value,
|
Value: value,
|
||||||
Nonce: nonce,
|
Nonce: nonce,
|
||||||
}
|
}
|
||||||
|
|
||||||
// ensure we can't claim before setting contract to Ready
|
// ensure we can't claim before setting contract to Ready
|
||||||
txOpts = *authOrig
|
_, err = swapCreator.Claim(getAuth(t, pkA), swap, proof.Secret())
|
||||||
_, err = contract.Claim(&txOpts, swap, proof.Secret())
|
|
||||||
require.ErrorContains(t, err, "VM Exception while processing transaction: revert")
|
require.ErrorContains(t, err, "VM Exception while processing transaction: revert")
|
||||||
|
|
||||||
// set contract to Ready
|
// set contract to Ready
|
||||||
txOpts = *authOrig
|
tx, err = swapCreator.SetReady(getAuth(t, pkA), swap)
|
||||||
tx, err = contract.SetReady(&txOpts, swap)
|
|
||||||
require.NoError(t, err)
|
|
||||||
receipt, err = block.WaitForReceipt(context.Background(), conn, tx.Hash())
|
|
||||||
t.Logf("gas cost to call SetReady: %d", receipt.GasUsed)
|
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
receipt = getReceipt(t, ec, tx)
|
||||||
|
t.Logf("gas cost to call SetReady: %d (delta %d)", receipt.GasUsed, MaxSetReadyGas-int(receipt.GasUsed))
|
||||||
|
require.GreaterOrEqual(t, MaxSetReadyGas, int(receipt.GasUsed), "SetReady")
|
||||||
|
|
||||||
// now let's try to claim
|
// now let's try to claim
|
||||||
txOpts = *authOrig
|
tx, err = swapCreator.Claim(getAuth(t, pkA), swap, proof.Secret())
|
||||||
tx, err = contract.Claim(&txOpts, swap, proof.Secret())
|
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
receipt, err = block.WaitForReceipt(context.Background(), conn, tx.Hash())
|
receipt = getReceipt(t, ec, tx)
|
||||||
require.NoError(t, err)
|
|
||||||
t.Logf("gas cost to call Claim: %d", receipt.GasUsed)
|
|
||||||
|
|
||||||
stage, err := contract.Swaps(nil, id)
|
if asset.IsETH() {
|
||||||
|
t.Logf("gas cost to call ETH Claim: %d (delta %d)", receipt.GasUsed, MaxClaimETHGas-int(receipt.GasUsed))
|
||||||
|
require.GreaterOrEqual(t, MaxClaimETHGas, int(receipt.GasUsed), "ETH Claim")
|
||||||
|
} else {
|
||||||
|
t.Logf("gas cost to call token Claim: %d (delta %d)", receipt.GasUsed, MaxClaimTokenGas-int(receipt.GasUsed))
|
||||||
|
require.GreaterOrEqual(t, MaxClaimTokenGas, int(receipt.GasUsed), "Token Claim")
|
||||||
|
}
|
||||||
|
|
||||||
|
stage, err := swapCreator.Swaps(nil, id)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, StageCompleted, stage)
|
require.Equal(t, StageCompleted, stage)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSwapCreator_Claim_random(t *testing.T) {
|
func TestSwapCreator_Claim_random(t *testing.T) {
|
||||||
testClaim(t, ethAssetAddress, 0, defaultSwapValue, nil)
|
testClaim(t, types.EthAssetETH, 0, defaultSwapValue, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func testRefundBeforeT0(t *testing.T, asset ethcommon.Address, erc20Contract *TestERC20, newLogIndex int) {
|
func testRefundBeforeT0(t *testing.T, asset types.EthAsset, erc20Contract *TestERC20, newLogIndex int) {
|
||||||
// generate refund secret and public key
|
// generate refund secret and public key
|
||||||
dleq := &dleq.DefaultDLEq{}
|
dleq := &dleq.DefaultDLEq{}
|
||||||
proof, err := dleq.Prove()
|
proof, err := dleq.Prove()
|
||||||
@@ -322,30 +356,34 @@ func testRefundBeforeT0(t *testing.T, asset ethcommon.Address, erc20Contract *Te
|
|||||||
cmt := res.Secp256k1PublicKey().Keccak256()
|
cmt := res.Secp256k1PublicKey().Keccak256()
|
||||||
|
|
||||||
// deploy swap contract with refund key hash
|
// deploy swap contract with refund key hash
|
||||||
auth, conn, pkA := setupXMRTakerAuth(t)
|
pkA := tests.GetTakerTestKey(t)
|
||||||
pub := pkA.Public().(*ecdsa.PublicKey)
|
ec, _ := tests.NewEthClient(t)
|
||||||
addr := crypto.PubkeyToAddress(*pub)
|
addr := crypto.PubkeyToAddress(pkA.PublicKey)
|
||||||
|
|
||||||
address, tx, contract, err := DeploySwapCreator(auth, conn, ethcommon.Address{})
|
swapCreatorAddr, swapCreator := deploySwapCreator(t, ec, pkA)
|
||||||
require.NoError(t, err)
|
|
||||||
receipt, err := block.WaitForReceipt(context.Background(), conn, tx.Hash())
|
|
||||||
require.NoError(t, err)
|
|
||||||
t.Logf("gas cost to deploy SwapCreator.sol: %d", receipt.GasUsed)
|
|
||||||
|
|
||||||
if asset != ethAssetAddress {
|
if asset.IsToken() {
|
||||||
approveERC20(t, auth, conn, erc20Contract, address, defaultSwapValue)
|
approveERC20(t, ec, pkA, erc20Contract, swapCreatorAddr, defaultSwapValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
nonce := big.NewInt(0)
|
txOpts := getAuth(t, pkA)
|
||||||
auth.Value = defaultSwapValue
|
txOpts.Value = defaultSwapValue
|
||||||
tx, err = contract.NewSwap(auth, dummySwapKey, cmt, addr, defaultTimeoutDuration, defaultTimeoutDuration,
|
|
||||||
asset, defaultSwapValue, nonce)
|
|
||||||
require.NoError(t, err)
|
|
||||||
auth.Value = nil
|
|
||||||
|
|
||||||
receipt, err = block.WaitForReceipt(context.Background(), conn, tx.Hash())
|
nonce := GenerateNewSwapNonce()
|
||||||
|
tx, err := swapCreator.NewSwap(txOpts, dummySwapKey, cmt, addr, defaultTimeoutDuration, defaultTimeoutDuration,
|
||||||
|
asset.Address(), defaultSwapValue, nonce)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
t.Logf("gas cost to call new_swap: %d", receipt.GasUsed)
|
|
||||||
|
receipt := getReceipt(t, ec, tx)
|
||||||
|
if asset.IsETH() {
|
||||||
|
t.Logf("gas cost to call ETH NewSwap: %d (delta %d)",
|
||||||
|
receipt.GasUsed, MaxNewSwapETHGas-int(receipt.GasUsed))
|
||||||
|
require.GreaterOrEqual(t, MaxNewSwapETHGas, int(receipt.GasUsed), "ETH NewSwap")
|
||||||
|
} else {
|
||||||
|
t.Logf("gas cost to call token NewSwap: %d (delta %d)",
|
||||||
|
receipt.GasUsed, MaxNewSwapTokenGas-int(receipt.GasUsed))
|
||||||
|
require.GreaterOrEqual(t, MaxNewSwapTokenGas, int(receipt.GasUsed), "ETH NewSwap")
|
||||||
|
}
|
||||||
|
|
||||||
require.Equal(t, newLogIndex+1, len(receipt.Logs))
|
require.Equal(t, newLogIndex+1, len(receipt.Logs))
|
||||||
id, err := GetIDFromLog(receipt.Logs[newLogIndex])
|
id, err := GetIDFromLog(receipt.Logs[newLogIndex])
|
||||||
@@ -361,28 +399,38 @@ func testRefundBeforeT0(t *testing.T, asset ethcommon.Address, erc20Contract *Te
|
|||||||
PubKeyRefund: cmt,
|
PubKeyRefund: cmt,
|
||||||
Timeout0: t0,
|
Timeout0: t0,
|
||||||
Timeout1: t1,
|
Timeout1: t1,
|
||||||
Asset: asset,
|
Asset: asset.Address(),
|
||||||
Value: defaultSwapValue,
|
Value: defaultSwapValue,
|
||||||
Nonce: nonce,
|
Nonce: nonce,
|
||||||
}
|
}
|
||||||
|
|
||||||
// now let's try to refund
|
// now let's try to refund
|
||||||
tx, err = contract.Refund(auth, swap, proof.Secret())
|
tx, err = swapCreator.Refund(getAuth(t, pkA), swap, proof.Secret())
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
receipt, err = block.WaitForReceipt(context.Background(), conn, tx.Hash())
|
receipt = getReceipt(t, ec, tx)
|
||||||
require.NoError(t, err)
|
|
||||||
t.Logf("gas cost to call Refund: %d", receipt.GasUsed)
|
|
||||||
|
|
||||||
stage, err := contract.Swaps(nil, id)
|
if asset.IsETH() {
|
||||||
|
t.Logf("gas cost to call ETH Refund: %d (delta %d)",
|
||||||
|
receipt.GasUsed, MaxRefundETHGas-int(receipt.GasUsed))
|
||||||
|
require.GreaterOrEqual(t, MaxRefundETHGas, int(receipt.GasUsed), "ETH Refund")
|
||||||
|
} else {
|
||||||
|
t.Logf("gas cost to call token Refund: %d (delta %d)",
|
||||||
|
receipt.GasUsed, MaxRefundTokenGas-int(receipt.GasUsed))
|
||||||
|
require.GreaterOrEqual(t, MaxRefundTokenGas, int(receipt.GasUsed), "Token Refund")
|
||||||
|
}
|
||||||
|
|
||||||
|
stage, err := swapCreator.Swaps(nil, id)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, StageCompleted, stage)
|
require.Equal(t, StageCompleted, stage)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSwapCreator_Refund_beforeT0(t *testing.T) {
|
func TestSwapCreator_Refund_beforeT0(t *testing.T) {
|
||||||
testRefundBeforeT0(t, ethAssetAddress, nil, 0)
|
testRefundBeforeT0(t, types.EthAssetETH, nil, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func testRefundAfterT1(t *testing.T, asset ethcommon.Address, erc20Contract *TestERC20, newLogIndex int) {
|
func testRefundAfterT1(t *testing.T, asset types.EthAsset, erc20Contract *TestERC20, newLogIndex int) {
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
// generate refund secret and public key
|
// generate refund secret and public key
|
||||||
dleq := &dleq.DefaultDLEq{}
|
dleq := &dleq.DefaultDLEq{}
|
||||||
proof, err := dleq.Prove()
|
proof, err := dleq.Prove()
|
||||||
@@ -394,31 +442,35 @@ func testRefundAfterT1(t *testing.T, asset ethcommon.Address, erc20Contract *Tes
|
|||||||
cmt := res.Secp256k1PublicKey().Keccak256()
|
cmt := res.Secp256k1PublicKey().Keccak256()
|
||||||
|
|
||||||
// deploy swap contract with refund key hash
|
// deploy swap contract with refund key hash
|
||||||
auth, conn, pkA := setupXMRTakerAuth(t)
|
pkA := tests.GetTakerTestKey(t)
|
||||||
pub := pkA.Public().(*ecdsa.PublicKey)
|
ec, _ := tests.NewEthClient(t)
|
||||||
addr := crypto.PubkeyToAddress(*pub)
|
addr := crypto.PubkeyToAddress(pkA.PublicKey)
|
||||||
|
|
||||||
address, tx, contract, err := DeploySwapCreator(auth, conn, ethcommon.Address{})
|
swapCreatorAddr, swapCreator := deploySwapCreator(t, ec, pkA)
|
||||||
require.NoError(t, err)
|
|
||||||
receipt, err := block.WaitForReceipt(context.Background(), conn, tx.Hash())
|
|
||||||
require.NoError(t, err)
|
|
||||||
t.Logf("gas cost to deploy SwapCreator.sol: %d", receipt.GasUsed)
|
|
||||||
|
|
||||||
if asset != ethAssetAddress {
|
if asset.IsToken() {
|
||||||
approveERC20(t, auth, conn, erc20Contract, address, defaultSwapValue)
|
approveERC20(t, ec, pkA, erc20Contract, swapCreatorAddr, defaultSwapValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
nonce := big.NewInt(0)
|
txOpts := getAuth(t, pkA)
|
||||||
timeout := big.NewInt(3)
|
txOpts.Value = defaultSwapValue
|
||||||
auth.Value = defaultSwapValue
|
|
||||||
tx, err = contract.NewSwap(auth, dummySwapKey, cmt, addr, timeout, timeout,
|
|
||||||
asset, defaultSwapValue, nonce)
|
|
||||||
require.NoError(t, err)
|
|
||||||
auth.Value = nil
|
|
||||||
|
|
||||||
receipt, err = block.WaitForReceipt(context.Background(), conn, tx.Hash())
|
nonce := GenerateNewSwapNonce()
|
||||||
|
timeout := big.NewInt(3)
|
||||||
|
tx, err := swapCreator.NewSwap(txOpts, dummySwapKey, cmt, addr, timeout, timeout,
|
||||||
|
asset.Address(), defaultSwapValue, nonce)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
t.Logf("gas cost to call new_swap: %d", receipt.GasUsed)
|
receipt := getReceipt(t, ec, tx)
|
||||||
|
|
||||||
|
if asset.IsETH() {
|
||||||
|
t.Logf("gas cost to call ETH NewSwap: %d (delta %d)",
|
||||||
|
receipt.GasUsed, MaxNewSwapETHGas-int(receipt.GasUsed))
|
||||||
|
require.GreaterOrEqual(t, MaxNewSwapETHGas, int(receipt.GasUsed), "ETH NewSwap")
|
||||||
|
} else {
|
||||||
|
t.Logf("gas cost to call token NewSwap: %d (delta %d)",
|
||||||
|
receipt.GasUsed, MaxNewSwapTokenGas-int(receipt.GasUsed))
|
||||||
|
require.GreaterOrEqual(t, MaxNewSwapTokenGas, int(receipt.GasUsed), "Token NewSwap")
|
||||||
|
}
|
||||||
|
|
||||||
require.Equal(t, newLogIndex+1, len(receipt.Logs))
|
require.Equal(t, newLogIndex+1, len(receipt.Logs))
|
||||||
id, err := GetIDFromLog(receipt.Logs[newLogIndex])
|
id, err := GetIDFromLog(receipt.Logs[newLogIndex])
|
||||||
@@ -436,53 +488,50 @@ func testRefundAfterT1(t *testing.T, asset ethcommon.Address, erc20Contract *Tes
|
|||||||
PubKeyRefund: cmt,
|
PubKeyRefund: cmt,
|
||||||
Timeout0: t0,
|
Timeout0: t0,
|
||||||
Timeout1: t1,
|
Timeout1: t1,
|
||||||
Asset: asset,
|
Asset: asset.Address(),
|
||||||
Value: defaultSwapValue,
|
Value: defaultSwapValue,
|
||||||
Nonce: nonce,
|
Nonce: nonce,
|
||||||
}
|
}
|
||||||
|
|
||||||
secret := proof.Secret()
|
secret := proof.Secret()
|
||||||
tx, err = contract.Refund(auth, swap, secret)
|
tx, err = swapCreator.Refund(getAuth(t, pkA), swap, secret)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
_, err = block.WaitForReceipt(context.Background(), conn, tx.Hash())
|
_, err = block.WaitForReceipt(ctx, ec, tx.Hash())
|
||||||
require.ErrorContains(t, err, "VM Exception while processing transaction: revert")
|
require.ErrorContains(t, err, "VM Exception while processing transaction: revert")
|
||||||
|
|
||||||
<-time.After(time.Until(time.Unix(t1.Int64()+1, 0)))
|
<-time.After(time.Until(time.Unix(t1.Int64()+1, 0)))
|
||||||
|
|
||||||
// now let's try to refund
|
// now let's try to refund
|
||||||
tx, err = contract.Refund(auth, swap, secret)
|
tx, err = swapCreator.Refund(getAuth(t, pkA), swap, secret)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
receipt, err = block.WaitForReceipt(context.Background(), conn, tx.Hash())
|
receipt = getReceipt(t, ec, tx)
|
||||||
require.NoError(t, err)
|
|
||||||
t.Logf("gas cost to call Refund: %d", receipt.GasUsed)
|
|
||||||
|
|
||||||
callOpts := &bind.CallOpts{
|
if asset.IsETH() {
|
||||||
From: crypto.PubkeyToAddress(*pub),
|
t.Logf("gas cost to call ETH Refund: %d (delta %d)",
|
||||||
Context: context.Background(),
|
receipt.GasUsed, MaxRefundETHGas-int(receipt.GasUsed))
|
||||||
|
require.GreaterOrEqual(t, MaxRefundETHGas, int(receipt.GasUsed), "ETH Refund")
|
||||||
|
} else {
|
||||||
|
t.Logf("gas cost to call token Refund: %d (delta %d)",
|
||||||
|
receipt.GasUsed, MaxRefundTokenGas-int(receipt.GasUsed))
|
||||||
|
require.GreaterOrEqual(t, MaxRefundTokenGas, int(receipt.GasUsed), "Token Refund")
|
||||||
}
|
}
|
||||||
|
|
||||||
stage, err := contract.Swaps(callOpts, id)
|
callOpts := &bind.CallOpts{Context: ctx}
|
||||||
|
stage, err := swapCreator.Swaps(callOpts, id)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, StageCompleted, stage)
|
require.Equal(t, StageCompleted, stage)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSwapCreator_Refund_afterT1(t *testing.T) {
|
func TestSwapCreator_Refund_afterT1(t *testing.T) {
|
||||||
testRefundAfterT1(t, ethAssetAddress, nil, 0)
|
testRefundAfterT1(t, types.EthAssetETH, nil, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSwapCreator_MultipleSwaps(t *testing.T) {
|
|
||||||
// test case where contract has multiple swaps happening at once
|
// test case where contract has multiple swaps happening at once
|
||||||
conn, chainID := tests.NewEthClient(t)
|
func TestSwapCreator_MultipleSwaps(t *testing.T) {
|
||||||
|
|
||||||
pkContractCreator := tests.GetTestKeyByIndex(t, 0)
|
pkContractCreator := tests.GetTestKeyByIndex(t, 0)
|
||||||
auth, err := bind.NewKeyedTransactorWithChainID(pkContractCreator, chainID)
|
ec, _ := tests.NewEthClient(t)
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
_, tx, contract, err := DeploySwapCreator(auth, conn, ethcommon.Address{})
|
_, swapCreator := deploySwapCreator(t, ec, pkContractCreator)
|
||||||
require.NoError(t, err)
|
|
||||||
receipt, err := block.WaitForReceipt(context.Background(), conn, tx.Hash())
|
|
||||||
require.NoError(t, err)
|
|
||||||
t.Logf("gas cost to deploy SwapCreator.sol: %d", receipt.GasUsed)
|
|
||||||
|
|
||||||
const numSwaps = 16
|
const numSwaps = 16
|
||||||
type swapCase struct {
|
type swapCase struct {
|
||||||
@@ -493,12 +542,6 @@ func TestSwapCreator_MultipleSwaps(t *testing.T) {
|
|||||||
swap SwapCreatorSwap
|
swap SwapCreatorSwap
|
||||||
}
|
}
|
||||||
|
|
||||||
getAuth := func(sc *swapCase) *bind.TransactOpts {
|
|
||||||
auth, err := bind.NewKeyedTransactorWithChainID(sc.walletKey, chainID)
|
|
||||||
require.NoError(t, err)
|
|
||||||
return auth
|
|
||||||
}
|
|
||||||
|
|
||||||
swapCases := [numSwaps]swapCase{}
|
swapCases := [numSwaps]swapCase{}
|
||||||
|
|
||||||
// setup all swap instances
|
// setup all swap instances
|
||||||
@@ -539,25 +582,26 @@ func TestSwapCreator_MultipleSwaps(t *testing.T) {
|
|||||||
for i := 0; i < numSwaps; i++ {
|
for i := 0; i < numSwaps; i++ {
|
||||||
go func(sc *swapCase) {
|
go func(sc *swapCase) {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
auth := getAuth(sc)
|
|
||||||
|
auth := getAuth(t, sc.walletKey)
|
||||||
auth.Value = sc.swap.Value
|
auth.Value = sc.swap.Value
|
||||||
tx, err := contract.NewSwap(
|
tx, err := swapCreator.NewSwap(
|
||||||
auth,
|
auth,
|
||||||
sc.swap.PubKeyClaim,
|
sc.swap.PubKeyClaim,
|
||||||
sc.swap.PubKeyRefund,
|
sc.swap.PubKeyRefund,
|
||||||
sc.swap.Claimer,
|
sc.swap.Claimer,
|
||||||
defaultTimeoutDuration,
|
defaultTimeoutDuration,
|
||||||
defaultTimeoutDuration,
|
defaultTimeoutDuration,
|
||||||
ethcommon.Address(types.EthAssetETH),
|
types.EthAssetETH.Address(),
|
||||||
sc.swap.Value,
|
sc.swap.Value,
|
||||||
sc.swap.Nonce,
|
sc.swap.Nonce,
|
||||||
)
|
)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
auth.Value = nil
|
receipt := getReceipt(t, ec, tx)
|
||||||
|
|
||||||
receipt, err := block.WaitForReceipt(context.Background(), conn, tx.Hash())
|
t.Logf("gas cost to call ETH NewSwap[%d]: %d (delta %d)",
|
||||||
require.NoError(t, err)
|
sc.index, receipt.GasUsed, MaxNewSwapETHGas-int(receipt.GasUsed))
|
||||||
t.Logf("gas cost to call new_swap[%d]: %d", sc.index, receipt.GasUsed)
|
require.GreaterOrEqual(t, MaxNewSwapETHGas, int(receipt.GasUsed), "ETH NewSwap")
|
||||||
|
|
||||||
require.Equal(t, 1, len(receipt.Logs))
|
require.Equal(t, 1, len(receipt.Logs))
|
||||||
sc.id, err = GetIDFromLog(receipt.Logs[0])
|
sc.id, err = GetIDFromLog(receipt.Logs[0])
|
||||||
@@ -574,11 +618,12 @@ func TestSwapCreator_MultipleSwaps(t *testing.T) {
|
|||||||
for i := 0; i < numSwaps; i++ {
|
for i := 0; i < numSwaps; i++ {
|
||||||
go func(sc *swapCase) {
|
go func(sc *swapCase) {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
tx, err := contract.SetReady(getAuth(sc), sc.swap)
|
tx, err := swapCreator.SetReady(getAuth(t, sc.walletKey), sc.swap)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
receipt, err := block.WaitForReceipt(context.Background(), conn, tx.Hash())
|
receipt := getReceipt(t, ec, tx)
|
||||||
require.NoError(t, err)
|
t.Logf("gas cost to call SetReady[%d]: %d (delta %d)",
|
||||||
t.Logf("gas cost to call SetReady[%d]: %d", sc.index, receipt.GasUsed)
|
sc.index, receipt.GasUsed, MaxSetReadyGas-int(receipt.GasUsed))
|
||||||
|
require.GreaterOrEqual(t, MaxSetReadyGas, int(receipt.GasUsed), "SetReady")
|
||||||
}(&swapCases[i])
|
}(&swapCases[i])
|
||||||
}
|
}
|
||||||
wg.Wait() // set_ready called on all swaps
|
wg.Wait() // set_ready called on all swaps
|
||||||
@@ -588,11 +633,12 @@ func TestSwapCreator_MultipleSwaps(t *testing.T) {
|
|||||||
for i := 0; i < numSwaps; i++ {
|
for i := 0; i < numSwaps; i++ {
|
||||||
go func(sc *swapCase) {
|
go func(sc *swapCase) {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
tx, err := contract.Claim(getAuth(sc), sc.swap, sc.secret)
|
tx, err := swapCreator.Claim(getAuth(t, sc.walletKey), sc.swap, sc.secret)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
receipt, err := block.WaitForReceipt(context.Background(), conn, tx.Hash())
|
receipt := getReceipt(t, ec, tx)
|
||||||
require.NoError(t, err)
|
t.Logf("gas cost to call ETH Claim[%d]: %d (delta %d)",
|
||||||
t.Logf("gas cost to call Claim[%d]: %d", sc.index, receipt.GasUsed)
|
sc.index, receipt.GasUsed, MaxClaimETHGas-int(receipt.GasUsed))
|
||||||
|
require.GreaterOrEqual(t, MaxClaimETHGas, int(receipt.GasUsed), "ETH Claim")
|
||||||
}(&swapCases[i])
|
}(&swapCases[i])
|
||||||
}
|
}
|
||||||
wg.Wait() // claim called on all swaps
|
wg.Wait() // claim called on all swaps
|
||||||
@@ -602,7 +648,7 @@ func TestSwapCreator_MultipleSwaps(t *testing.T) {
|
|||||||
for i := 0; i < numSwaps; i++ {
|
for i := 0; i < numSwaps; i++ {
|
||||||
go func(sc *swapCase) {
|
go func(sc *swapCase) {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
stage, err := contract.Swaps(nil, sc.id)
|
stage, err := swapCreator.Swaps(nil, sc.id)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, StageToString(StageCompleted), StageToString(stage))
|
require.Equal(t, StageToString(StageCompleted), StageToString(stage))
|
||||||
}(&swapCases[i])
|
}(&swapCases[i])
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ package contracts
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"crypto/rand"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/big"
|
"math/big"
|
||||||
@@ -235,3 +236,12 @@ func GetTimeoutsFromLog(log *ethtypes.Log) (*big.Int, *big.Int, error) {
|
|||||||
t1 := res[4].(*big.Int)
|
t1 := res[4].(*big.Int)
|
||||||
return t0, t1, nil
|
return t0, t1, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GenerateNewSwapNonce generates a random nonce value for use with NewSwap
|
||||||
|
// transactions.
|
||||||
|
func GenerateNewSwapNonce() *big.Int {
|
||||||
|
u256PlusOne := new(big.Int).Lsh(big.NewInt(1), 256)
|
||||||
|
maxU256 := new(big.Int).Sub(u256PlusOne, big.NewInt(1))
|
||||||
|
n, _ := rand.Int(rand.Reader, maxU256)
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|||||||
@@ -4,7 +4,6 @@
|
|||||||
package xmrmaker
|
package xmrmaker
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/athanorlabs/atomic-swap/coins"
|
|
||||||
"github.com/athanorlabs/atomic-swap/common/types"
|
"github.com/athanorlabs/atomic-swap/common/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -13,17 +12,17 @@ func (inst *Instance) MakeOffer(
|
|||||||
o *types.Offer,
|
o *types.Offer,
|
||||||
useRelayer bool,
|
useRelayer bool,
|
||||||
) (*types.OfferExtra, error) {
|
) (*types.OfferExtra, error) {
|
||||||
// get monero balance
|
err := validateMinBalance(
|
||||||
balance, err := inst.backend.XMRClient().GetBalance(0)
|
inst.backend.Ctx(),
|
||||||
|
inst.backend.XMRClient(),
|
||||||
|
inst.backend.ETHClient(),
|
||||||
|
o.MaxAmount,
|
||||||
|
o.EthAsset,
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
unlockedBalance := coins.NewPiconeroAmount(balance.UnlockedBalance).AsMonero()
|
|
||||||
if unlockedBalance.Cmp(o.MaxAmount) <= 0 {
|
|
||||||
return nil, errUnlockedBalanceTooLow{o.MaxAmount, unlockedBalance}
|
|
||||||
}
|
|
||||||
|
|
||||||
if useRelayer && o.EthAsset.IsToken() {
|
if useRelayer && o.EthAsset.IsToken() {
|
||||||
return nil, errRelayingWithNonEthAsset
|
return nil, errRelayingWithNonEthAsset
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -86,3 +86,15 @@ func (e errUnlockedBalanceTooLow) Error() string {
|
|||||||
e.maxOfferAmount.String(),
|
e.maxOfferAmount.String(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type errETHBalanceTooLowForTokenSwap struct {
|
||||||
|
ethBalance *apd.Decimal
|
||||||
|
requiredETHToClaim *apd.Decimal
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e errETHBalanceTooLowForTokenSwap) Error() string {
|
||||||
|
return fmt.Sprintf("balance of %s ETH insufficient for token swap, %s ETH required to claim",
|
||||||
|
e.ethBalance.Text('f'),
|
||||||
|
e.requiredETHToClaim.Text('f'),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|||||||
66
protocol/xmrmaker/min_balance.go
Normal file
66
protocol/xmrmaker/min_balance.go
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
package xmrmaker
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"math/big"
|
||||||
|
|
||||||
|
"github.com/cockroachdb/apd/v3"
|
||||||
|
|
||||||
|
"github.com/athanorlabs/atomic-swap/coins"
|
||||||
|
"github.com/athanorlabs/atomic-swap/common/types"
|
||||||
|
contracts "github.com/athanorlabs/atomic-swap/ethereum"
|
||||||
|
"github.com/athanorlabs/atomic-swap/ethereum/extethclient"
|
||||||
|
"github.com/athanorlabs/atomic-swap/monero"
|
||||||
|
)
|
||||||
|
|
||||||
|
// validateMinBalance validates that the Maker has sufficient funds to make an
|
||||||
|
// XMR for ETH or XMR for token offer.
|
||||||
|
func validateMinBalance(
|
||||||
|
ctx context.Context,
|
||||||
|
mc monero.WalletClient,
|
||||||
|
ec extethclient.EthClient,
|
||||||
|
offerMaxAmt *apd.Decimal,
|
||||||
|
ethAsset types.EthAsset,
|
||||||
|
) error {
|
||||||
|
piconeroBalance, err := mc.GetBalance(0)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Maker needs a sufficient XMR balance regardless if it is an ETH or token swap
|
||||||
|
unlockedBalance := coins.NewPiconeroAmount(piconeroBalance.UnlockedBalance).AsMonero()
|
||||||
|
if unlockedBalance.Cmp(offerMaxAmt) <= 0 {
|
||||||
|
return errUnlockedBalanceTooLow{offerMaxAmt, unlockedBalance}
|
||||||
|
}
|
||||||
|
|
||||||
|
// For a token swap, we also check if the maker has sufficient ETH funds to make a
|
||||||
|
// claim at the end of the swap.
|
||||||
|
if ethAsset.IsToken() {
|
||||||
|
gasPriceWei, err := ec.SuggestGasPrice(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
requiredETHToClaimTokens := coins.NewWeiAmount(
|
||||||
|
new(big.Int).Mul(big.NewInt(contracts.MaxClaimTokenGas), gasPriceWei),
|
||||||
|
).AsEther()
|
||||||
|
|
||||||
|
weiBalance, err := ec.Balance(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
ethBalance := weiBalance.AsEther()
|
||||||
|
|
||||||
|
if ethBalance.Cmp(requiredETHToClaimTokens) <= 0 {
|
||||||
|
log.Warnf("Ethereum account has insufficient funds for token claim, balance=%s ETH, required=%s ETH",
|
||||||
|
ethBalance.Text('f'), requiredETHToClaimTokens.Text('f'))
|
||||||
|
return errETHBalanceTooLowForTokenSwap{
|
||||||
|
ethBalance: ethBalance,
|
||||||
|
requiredETHToClaim: requiredETHToClaimTokens,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
59
protocol/xmrmaker/min_balance_test.go
Normal file
59
protocol/xmrmaker/min_balance_test.go
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
package xmrmaker
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
ethcommon "github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
"github.com/athanorlabs/atomic-swap/coins"
|
||||||
|
"github.com/athanorlabs/atomic-swap/common/types"
|
||||||
|
"github.com/athanorlabs/atomic-swap/ethereum/extethclient"
|
||||||
|
"github.com/athanorlabs/atomic-swap/monero"
|
||||||
|
"github.com/athanorlabs/atomic-swap/tests"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_validateMinBalance(t *testing.T) {
|
||||||
|
ctx := context.Background()
|
||||||
|
mc := monero.CreateWalletClient(t)
|
||||||
|
ec := extethclient.CreateTestClient(t, tests.GetMakerTestKey(t))
|
||||||
|
offerMax := coins.StrToDecimal("0.4")
|
||||||
|
tokenAsset := types.EthAsset(ethcommon.Address{0x1}) // arbitrary token asset
|
||||||
|
|
||||||
|
monero.MineMinXMRBalance(t, mc, coins.MoneroToPiconero(offerMax))
|
||||||
|
|
||||||
|
err := validateMinBalance(ctx, mc, ec, offerMax, tokenAsset)
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_validateMinBalance_insufficientXMR(t *testing.T) {
|
||||||
|
ctx := context.Background()
|
||||||
|
ec := extethclient.CreateTestClient(t, tests.GetMakerTestKey(t))
|
||||||
|
mc := monero.CreateWalletClient(t)
|
||||||
|
offerMax := coins.StrToDecimal("0.5")
|
||||||
|
|
||||||
|
// We didn't mine any XMR, so balance is zero
|
||||||
|
|
||||||
|
err := validateMinBalance(ctx, mc, ec, offerMax, types.EthAssetETH)
|
||||||
|
require.ErrorContains(t, err, "balance 0 XMR is too low for maximum offer amount of 0.5 XMR")
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_validateMinBalance_insufficientETH(t *testing.T) {
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
mc := monero.CreateWalletClient(t)
|
||||||
|
pk, err := crypto.GenerateKey() // new eth key with no balance
|
||||||
|
require.NoError(t, err)
|
||||||
|
ec := extethclient.CreateTestClient(t, pk)
|
||||||
|
|
||||||
|
offerMax := coins.StrToDecimal("0.5")
|
||||||
|
tokenAsset := types.EthAsset(ethcommon.Address{0x1}) // arbitrary token asset
|
||||||
|
|
||||||
|
monero.MineMinXMRBalance(t, mc, coins.MoneroToPiconero(offerMax))
|
||||||
|
|
||||||
|
err = validateMinBalance(ctx, mc, ec, offerMax, tokenAsset)
|
||||||
|
require.Error(t, err)
|
||||||
|
require.Regexp(t, "balance of 0 ETH insufficient for token swap, 0.000\\d+ ETH required to claim", err.Error())
|
||||||
|
}
|
||||||
@@ -31,15 +31,15 @@ var (
|
|||||||
errInvalidStageForRecovery = errors.New("cannot create ongoing swap state if stage is not ETHLocked or ContractReady") //nolint:lll
|
errInvalidStageForRecovery = errors.New("cannot create ongoing swap state if stage is not ETHLocked or ContractReady") //nolint:lll
|
||||||
)
|
)
|
||||||
|
|
||||||
type errAssetBalanceTooLow struct {
|
type errTokenBalanceTooLow struct {
|
||||||
providedAmount *apd.Decimal
|
providedAmount *apd.Decimal // standard units
|
||||||
balance *apd.Decimal
|
tokenBalance *apd.Decimal // standard units
|
||||||
symbol string
|
symbol string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e errAssetBalanceTooLow) Error() string {
|
func (e errTokenBalanceTooLow) Error() string {
|
||||||
return fmt.Sprintf("balance of %s %s is below provided %s %s",
|
return fmt.Sprintf("balance of %s %s is below provided %s %s",
|
||||||
e.balance.Text('f'), e.symbol,
|
e.tokenBalance.Text('f'), e.symbol,
|
||||||
e.providedAmount.Text('f'), e.symbol,
|
e.providedAmount.Text('f'), e.symbol,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -50,25 +50,37 @@ func errContractAddrMismatch(addr string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type errAmountProvidedTooLow struct {
|
type errAmountProvidedTooLow struct {
|
||||||
providedAmount *apd.Decimal
|
providedAmtETH *apd.Decimal
|
||||||
minAmount *apd.Decimal
|
offerMinAmtETH *apd.Decimal
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e errAmountProvidedTooLow) Error() string {
|
func (e errAmountProvidedTooLow) Error() string {
|
||||||
return fmt.Sprintf("%s ETH provided is under offer minimum of %s XMR",
|
return fmt.Sprintf("%s ETH provided is under offer minimum of %s ETH",
|
||||||
e.providedAmount.String(),
|
e.providedAmtETH.Text('f'),
|
||||||
e.minAmount.String(),
|
e.offerMinAmtETH.Text('f'),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
type errAmountProvidedTooHigh struct {
|
type errAmountProvidedTooHigh struct {
|
||||||
providedAmount *apd.Decimal
|
providedAmtETH *apd.Decimal
|
||||||
maxAmount *apd.Decimal
|
offerMaxETH *apd.Decimal
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e errAmountProvidedTooHigh) Error() string {
|
func (e errAmountProvidedTooHigh) Error() string {
|
||||||
return fmt.Sprintf("%s ETH provided is over offer maximum of %s XMR",
|
return fmt.Sprintf("%s ETH provided is over offer maximum of %s ETH",
|
||||||
e.providedAmount.String(),
|
e.providedAmtETH.Text('f'),
|
||||||
e.maxAmount.String(),
|
e.offerMaxETH.Text('f'),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
type errETHBalanceTooLow struct {
|
||||||
|
currentBalanceETH *apd.Decimal
|
||||||
|
requiredBalanceETH *apd.Decimal
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e errETHBalanceTooLow) Error() string {
|
||||||
|
return fmt.Sprintf("balance of %s ETH is under required amount of %s ETH",
|
||||||
|
e.currentBalanceETH.Text('f'),
|
||||||
|
e.requiredBalanceETH.Text('f'),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
106
protocol/xmrtaker/min_balance.go
Normal file
106
protocol/xmrtaker/min_balance.go
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
package xmrtaker
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"math/big"
|
||||||
|
|
||||||
|
"github.com/cockroachdb/apd/v3"
|
||||||
|
|
||||||
|
"github.com/athanorlabs/atomic-swap/coins"
|
||||||
|
"github.com/athanorlabs/atomic-swap/common/types"
|
||||||
|
contracts "github.com/athanorlabs/atomic-swap/ethereum"
|
||||||
|
"github.com/athanorlabs/atomic-swap/ethereum/extethclient"
|
||||||
|
)
|
||||||
|
|
||||||
|
// validateMinBalance validates that the Taker has sufficient funds to take an
|
||||||
|
// offer
|
||||||
|
func validateMinBalance(
|
||||||
|
ctx context.Context,
|
||||||
|
ec extethclient.EthClient,
|
||||||
|
providesAmt *apd.Decimal,
|
||||||
|
asset types.EthAsset,
|
||||||
|
) error {
|
||||||
|
gasPrice, err := ec.SuggestGasPrice(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
weiBalance, err := ec.Balance(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if asset.IsETH() {
|
||||||
|
return validateMinBalForETHSwap(weiBalance, providesAmt, gasPrice)
|
||||||
|
}
|
||||||
|
|
||||||
|
tokenBalance, err := ec.ERC20Balance(ctx, asset.Address())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return validateMinBalForTokenSwap(weiBalance, tokenBalance, providesAmt, gasPrice)
|
||||||
|
}
|
||||||
|
|
||||||
|
// validateMinBalForETHSwap validates that the Taker has sufficient funds to take an
|
||||||
|
// offer of XMR for ETH
|
||||||
|
func validateMinBalForETHSwap(weiBalance *coins.WeiAmount, providesAmt *apd.Decimal, gasPriceWei *big.Int) error {
|
||||||
|
providedAmtWei := coins.EtherToWei(providesAmt).BigInt()
|
||||||
|
neededGas := big.NewInt(
|
||||||
|
contracts.MaxNewSwapETHGas +
|
||||||
|
contracts.MaxSetReadyGas +
|
||||||
|
contracts.MaxRefundTokenGas,
|
||||||
|
)
|
||||||
|
neededWeiForGas := new(big.Int).Mul(neededGas, gasPriceWei)
|
||||||
|
neededBalanceWei := new(big.Int).Add(providedAmtWei, neededWeiForGas)
|
||||||
|
|
||||||
|
if weiBalance.BigInt().Cmp(neededBalanceWei) < 0 {
|
||||||
|
log.Warnf("Ethereum account needs additional funds, balance=%s ETH, required=%s ETH",
|
||||||
|
weiBalance.AsEtherString(), coins.NewWeiAmount(neededBalanceWei).AsEtherString())
|
||||||
|
return errETHBalanceTooLow{
|
||||||
|
currentBalanceETH: weiBalance.AsEther(),
|
||||||
|
requiredBalanceETH: coins.NewWeiAmount(neededBalanceWei).AsEther(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// validateMinBalForTokenSwap validates that the Taker has sufficient tokens for
|
||||||
|
// to take an XMR->Token swap, and sufficient ETH funds to pay for the gas of
|
||||||
|
// the swap.
|
||||||
|
func validateMinBalForTokenSwap(
|
||||||
|
weiBalance *coins.WeiAmount,
|
||||||
|
tokenBalance *coins.ERC20TokenAmount,
|
||||||
|
providesAmt *apd.Decimal, // standard units
|
||||||
|
gasPriceWei *big.Int,
|
||||||
|
) error {
|
||||||
|
if tokenBalance.AsStandard().Cmp(providesAmt) < 0 {
|
||||||
|
return errTokenBalanceTooLow{
|
||||||
|
providedAmount: providesAmt,
|
||||||
|
tokenBalance: tokenBalance.AsStandard(),
|
||||||
|
symbol: tokenBalance.StandardSymbol(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// For a token swap, we only need an ETH balance to pay for gas. While we
|
||||||
|
// hopefully won't need gas to call refund, we don't want to start the swap
|
||||||
|
// if we don't have enough ETH to call it.
|
||||||
|
neededGas := big.NewInt(contracts.MaxTokenApproveGas +
|
||||||
|
contracts.MaxNewSwapTokenGas +
|
||||||
|
contracts.MaxSetReadyGas +
|
||||||
|
contracts.MaxRefundTokenGas,
|
||||||
|
)
|
||||||
|
neededWeiForGas := new(big.Int).Mul(neededGas, gasPriceWei)
|
||||||
|
|
||||||
|
if weiBalance.BigInt().Cmp(neededWeiForGas) < 0 {
|
||||||
|
log.Warnf("Ethereum account has infufficient balance to pay for gas, balance=%s ETH, required=%s ETH",
|
||||||
|
weiBalance.AsEtherString(), coins.NewWeiAmount(neededWeiForGas).AsEtherString())
|
||||||
|
return errETHBalanceTooLow{
|
||||||
|
currentBalanceETH: weiBalance.AsEther(),
|
||||||
|
requiredBalanceETH: coins.NewWeiAmount(neededWeiForGas).AsEther(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
130
protocol/xmrtaker/min_balance_test.go
Normal file
130
protocol/xmrtaker/min_balance_test.go
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
package xmrtaker
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/ecdsa"
|
||||||
|
"math/big"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
ethcommon "github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
"github.com/athanorlabs/atomic-swap/coins"
|
||||||
|
"github.com/athanorlabs/atomic-swap/common/types"
|
||||||
|
contracts "github.com/athanorlabs/atomic-swap/ethereum"
|
||||||
|
"github.com/athanorlabs/atomic-swap/ethereum/block"
|
||||||
|
"github.com/athanorlabs/atomic-swap/ethereum/extethclient"
|
||||||
|
"github.com/athanorlabs/atomic-swap/tests"
|
||||||
|
)
|
||||||
|
|
||||||
|
func deployTestToken(t *testing.T, ownerKey *ecdsa.PrivateKey) (ethcommon.Address, *coins.ERC20TokenInfo) {
|
||||||
|
ctx := context.Background()
|
||||||
|
ec := extethclient.CreateTestClient(t, ownerKey)
|
||||||
|
|
||||||
|
txOpts, err := ec.TxOpts(ctx)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
tokenAddr, tx, _, err := contracts.DeployTestERC20(
|
||||||
|
txOpts,
|
||||||
|
ec.Raw(),
|
||||||
|
"Min Max Testing Token",
|
||||||
|
"MMTT",
|
||||||
|
6,
|
||||||
|
ec.Address(),
|
||||||
|
big.NewInt(1000e6), // 1000 standard units
|
||||||
|
)
|
||||||
|
require.NoError(t, err)
|
||||||
|
_, err = block.WaitForReceipt(ctx, ec.Raw(), tx.Hash())
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
tokenInfo, err := ec.ERC20Info(ctx, tokenAddr)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
return tokenAddr, tokenInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_validateMinBalForETHSwap(t *testing.T) {
|
||||||
|
balanceWei := coins.EtherToWei(coins.StrToDecimal("0.5"))
|
||||||
|
providesAmt := coins.StrToDecimal("0.25")
|
||||||
|
gasPriceWei := big.NewInt(35e9) // 35 GWei
|
||||||
|
err := validateMinBalForETHSwap(balanceWei, providesAmt, gasPriceWei)
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_validateMinBalForETHSwap_InsufficientBalance(t *testing.T) {
|
||||||
|
balanceWei := coins.EtherToWei(coins.StrToDecimal("0.5"))
|
||||||
|
providesAmt := balanceWei.AsEther() // provided amount consumes whole balance leaving nothing for gas
|
||||||
|
gasPriceWei := big.NewInt(35e9) // 35 GWei
|
||||||
|
err := validateMinBalForETHSwap(balanceWei, providesAmt, gasPriceWei)
|
||||||
|
// Amount in check below is truncated, so minor adjustments in the
|
||||||
|
// expected gas won't break the test. The full message looks like:
|
||||||
|
// "balance of 0.5 ETH is under required amount of 0.504541005 ETH"
|
||||||
|
require.ErrorContains(t, err, "balance of 0.5 ETH is under required amount of 0.504")
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_validateMinBalForTokenSwap(t *testing.T) {
|
||||||
|
tokenInfo := &coins.ERC20TokenInfo{
|
||||||
|
Address: ethcommon.Address{0x1},
|
||||||
|
NumDecimals: 6,
|
||||||
|
Name: "Token",
|
||||||
|
Symbol: "TK",
|
||||||
|
}
|
||||||
|
balanceWei := coins.EtherToWei(coins.StrToDecimal("0.5"))
|
||||||
|
tokenBalance := coins.NewERC20TokenAmountFromDecimals(coins.StrToDecimal("10"), tokenInfo)
|
||||||
|
providesAmt := coins.StrToDecimal("5")
|
||||||
|
gasPriceWei := big.NewInt(35e9) // 35 GWei
|
||||||
|
err := validateMinBalForTokenSwap(balanceWei, tokenBalance, providesAmt, gasPriceWei)
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_validateMinBalForTokenSwap_InsufficientTokenBalance(t *testing.T) {
|
||||||
|
tokenInfo := &coins.ERC20TokenInfo{
|
||||||
|
Address: ethcommon.Address{0x1},
|
||||||
|
NumDecimals: 6,
|
||||||
|
Name: "Token",
|
||||||
|
Symbol: "TK",
|
||||||
|
}
|
||||||
|
balanceWei := coins.EtherToWei(coins.StrToDecimal("0.5"))
|
||||||
|
tokenBalance := coins.NewERC20TokenAmountFromDecimals(coins.StrToDecimal("10"), tokenInfo)
|
||||||
|
providesAmt := coins.StrToDecimal("20")
|
||||||
|
gasPriceWei := big.NewInt(35e9) // 35 GWei
|
||||||
|
err := validateMinBalForTokenSwap(balanceWei, tokenBalance, providesAmt, gasPriceWei)
|
||||||
|
require.ErrorContains(t, err, `balance of 10 "TK" is below provided 20 "TK"`)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_validateMinBalForTokenSwap_InsufficientETHBalance(t *testing.T) {
|
||||||
|
tokenInfo := &coins.ERC20TokenInfo{
|
||||||
|
Address: ethcommon.Address{0x1},
|
||||||
|
NumDecimals: 6,
|
||||||
|
Name: "Token",
|
||||||
|
Symbol: "TK",
|
||||||
|
}
|
||||||
|
balanceWei := coins.EtherToWei(coins.StrToDecimal("0.007"))
|
||||||
|
tokenBalance := coins.NewERC20TokenAmountFromDecimals(coins.StrToDecimal("10"), tokenInfo)
|
||||||
|
providesAmt := coins.StrToDecimal("1")
|
||||||
|
gasPriceWei := big.NewInt(35e9) // 35 GWei
|
||||||
|
err := validateMinBalForTokenSwap(balanceWei, tokenBalance, providesAmt, gasPriceWei)
|
||||||
|
// Amount in check below is truncated, so minor adjustments in the
|
||||||
|
// expected gas won't break the test. The full message looks like:
|
||||||
|
// "balance of 0.007 ETH is under required amount of 0.00743302 ETH"
|
||||||
|
require.ErrorContains(t, err, `balance of 0.007 ETH is under required amount of 0.0074`)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_validateMinBalanceETH(t *testing.T) {
|
||||||
|
ctx := context.Background()
|
||||||
|
pk := tests.GetTakerTestKey(t)
|
||||||
|
ec := extethclient.CreateTestClient(t, pk)
|
||||||
|
providesAmt := coins.StrToDecimal("0.3") // ganache taker key will have more than this
|
||||||
|
err := validateMinBalance(ctx, ec, providesAmt, types.EthAssetETH)
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_validateMinBalanceToken(t *testing.T) {
|
||||||
|
ctx := context.Background()
|
||||||
|
pk := tests.GetTakerTestKey(t)
|
||||||
|
ec := extethclient.CreateTestClient(t, pk)
|
||||||
|
tokenAddr, _ := deployTestToken(t, pk)
|
||||||
|
providesAmt := coins.StrToDecimal("1") // less than the 1k tokens belonging to the taker
|
||||||
|
err := validateMinBalance(ctx, ec, providesAmt, types.EthAsset(tokenAddr))
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
||||||
@@ -32,17 +32,32 @@ func (inst *Instance) InitiateProtocol(
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
expectedAmount, err := offer.ExchangeRate.ToXMR(providesAmount)
|
offerMinETH, err := offer.ExchangeRate.ToETH(offer.MinAmount)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if expectedAmount.Cmp(offer.MinAmount) < 0 {
|
offerMaxETH, err := offer.ExchangeRate.ToETH(offer.MaxAmount)
|
||||||
return nil, errAmountProvidedTooLow{providesAmount, offer.MinAmount}
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if expectedAmount.Cmp(offer.MaxAmount) > 0 {
|
if offerMinETH.Cmp(providesAmount) > 0 {
|
||||||
return nil, errAmountProvidedTooHigh{providesAmount, offer.MaxAmount}
|
return nil, errAmountProvidedTooLow{providesAmount, offerMinETH}
|
||||||
|
}
|
||||||
|
|
||||||
|
if offerMaxETH.Cmp(providesAmount) < 0 {
|
||||||
|
return nil, errAmountProvidedTooHigh{providesAmount, offerMaxETH}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = validateMinBalance(
|
||||||
|
inst.backend.Ctx(),
|
||||||
|
inst.backend.ETHClient(),
|
||||||
|
providesAmount,
|
||||||
|
offer.EthAsset,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
providedAmount, err := pcommon.GetEthAssetAmount(
|
providedAmount, err := pcommon.GetEthAssetAmount(
|
||||||
@@ -55,8 +70,7 @@ func (inst *Instance) InitiateProtocol(
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
state, err := inst.initiate(makerPeerID, providedAmount, coins.MoneroToPiconero(expectedAmount),
|
state, err := inst.initiate(makerPeerID, providedAmount, offer.ExchangeRate, offer.EthAsset, offer.ID)
|
||||||
offer.ExchangeRate, offer.EthAsset, offer.ID)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -67,7 +81,6 @@ func (inst *Instance) InitiateProtocol(
|
|||||||
func (inst *Instance) initiate(
|
func (inst *Instance) initiate(
|
||||||
makerPeerID peer.ID,
|
makerPeerID peer.ID,
|
||||||
providesAmount coins.EthAssetAmount,
|
providesAmount coins.EthAssetAmount,
|
||||||
expectedAmount *coins.PiconeroAmount,
|
|
||||||
exchangeRate *coins.ExchangeRate,
|
exchangeRate *coins.ExchangeRate,
|
||||||
ethAsset types.EthAsset,
|
ethAsset types.EthAsset,
|
||||||
offerID types.Hash,
|
offerID types.Hash,
|
||||||
@@ -79,44 +92,12 @@ func (inst *Instance) initiate(
|
|||||||
return nil, errProtocolAlreadyInProgress
|
return nil, errProtocolAlreadyInProgress
|
||||||
}
|
}
|
||||||
|
|
||||||
ethBalance, err := inst.backend.ETHClient().Balance(inst.backend.Ctx())
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure the user's balance is strictly greater than the amount they will provide
|
|
||||||
if ethAsset.IsETH() && ethBalance.Cmp(providesAmount.(*coins.WeiAmount)) <= 0 {
|
|
||||||
log.Warnf("Account %s needs additional funds for swap balance=%s ETH providesAmount=%s ETH",
|
|
||||||
inst.backend.ETHClient().Address(), ethBalance.AsEtherString(), providesAmount.AsStandard())
|
|
||||||
return nil, errAssetBalanceTooLow{
|
|
||||||
providedAmount: providesAmount.AsStandard(),
|
|
||||||
balance: ethBalance.AsEther(),
|
|
||||||
symbol: "ETH",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ethAsset.IsToken() {
|
|
||||||
tokenBalance, err := inst.backend.ETHClient().ERC20Balance(inst.backend.Ctx(), ethAsset.Address()) //nolint:govet
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if tokenBalance.AsStandard().Cmp(providesAmount.AsStandard()) <= 0 {
|
|
||||||
return nil, errAssetBalanceTooLow{
|
|
||||||
providedAmount: providesAmount.AsStandard(),
|
|
||||||
balance: tokenBalance.AsStandard(),
|
|
||||||
symbol: tokenBalance.StandardSymbol(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
s, err := newSwapStateFromStart(
|
s, err := newSwapStateFromStart(
|
||||||
inst.backend,
|
inst.backend,
|
||||||
makerPeerID,
|
makerPeerID,
|
||||||
offerID,
|
offerID,
|
||||||
inst.noTransferBack,
|
inst.noTransferBack,
|
||||||
providesAmount,
|
providesAmount,
|
||||||
expectedAmount,
|
|
||||||
exchangeRate,
|
exchangeRate,
|
||||||
ethAsset,
|
ethAsset,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ package xmrtaker
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/rand"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/big"
|
"math/big"
|
||||||
@@ -101,7 +100,6 @@ func newSwapStateFromStart(
|
|||||||
offerID types.Hash,
|
offerID types.Hash,
|
||||||
noTransferBack bool,
|
noTransferBack bool,
|
||||||
providedAmount coins.EthAssetAmount,
|
providedAmount coins.EthAssetAmount,
|
||||||
expectedAmount *coins.PiconeroAmount,
|
|
||||||
exchangeRate *coins.ExchangeRate,
|
exchangeRate *coins.ExchangeRate,
|
||||||
ethAsset types.EthAsset,
|
ethAsset types.EthAsset,
|
||||||
) (*swapState, error) {
|
) (*swapState, error) {
|
||||||
@@ -123,12 +121,17 @@ func newSwapStateFromStart(
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
expectedAmount, err := exchangeRate.ToXMR(providedAmount.AsStandard())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
info := pswap.NewInfo(
|
info := pswap.NewInfo(
|
||||||
makerPeerID,
|
makerPeerID,
|
||||||
offerID,
|
offerID,
|
||||||
coins.ProvidesETH,
|
coins.ProvidesETH,
|
||||||
providedAmount.AsStandard(),
|
providedAmount.AsStandard(),
|
||||||
expectedAmount.AsMonero(),
|
expectedAmount,
|
||||||
exchangeRate,
|
exchangeRate,
|
||||||
ethAsset,
|
ethAsset,
|
||||||
stage,
|
stage,
|
||||||
@@ -562,7 +565,7 @@ func (s *swapState) lockAsset() (*ethtypes.Receipt, error) {
|
|||||||
|
|
||||||
log.Debugf("locking %s %s in contract", providedAmt.AsStandard(), providedAmt.StandardSymbol())
|
log.Debugf("locking %s %s in contract", providedAmt.AsStandard(), providedAmt.StandardSymbol())
|
||||||
|
|
||||||
nonce := generateNonce()
|
nonce := contracts.GenerateNewSwapNonce()
|
||||||
receipt, err := s.sender.NewSwap(
|
receipt, err := s.sender.NewSwap(
|
||||||
cmtXMRMaker,
|
cmtXMRMaker,
|
||||||
cmtXMRTaker,
|
cmtXMRTaker,
|
||||||
@@ -698,10 +701,3 @@ func (s *swapState) refund() (*ethtypes.Receipt, error) {
|
|||||||
func generateKeys() (*pcommon.KeysAndProof, error) {
|
func generateKeys() (*pcommon.KeysAndProof, error) {
|
||||||
return pcommon.GenerateKeysAndProof()
|
return pcommon.GenerateKeysAndProof()
|
||||||
}
|
}
|
||||||
|
|
||||||
func generateNonce() *big.Int {
|
|
||||||
u256PlusOne := new(big.Int).Lsh(big.NewInt(1), 256)
|
|
||||||
maxU256 := new(big.Int).Sub(u256PlusOne, big.NewInt(1))
|
|
||||||
n, _ := rand.Int(rand.Reader, maxU256)
|
|
||||||
return n
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -135,10 +135,9 @@ func newBackend(t *testing.T) backend.Backend {
|
|||||||
func newTestSwapStateAndNet(t *testing.T) (*swapState, *mockNet) {
|
func newTestSwapStateAndNet(t *testing.T) (*swapState, *mockNet) {
|
||||||
b, net := newBackendAndNet(t)
|
b, net := newBackendAndNet(t)
|
||||||
providedAmt := coins.EtherToWei(coins.StrToDecimal("1"))
|
providedAmt := coins.EtherToWei(coins.StrToDecimal("1"))
|
||||||
expectedAmt := coins.MoneroToPiconero(coins.StrToDecimal("1"))
|
|
||||||
exchangeRate := coins.ToExchangeRate(coins.StrToDecimal("1.0")) // 100%
|
exchangeRate := coins.ToExchangeRate(coins.StrToDecimal("1.0")) // 100%
|
||||||
swapState, err := newSwapStateFromStart(b, testPeerID, types.Hash{}, true,
|
swapState, err := newSwapStateFromStart(b, testPeerID, types.Hash{}, true,
|
||||||
providedAmt, expectedAmt, exchangeRate, types.EthAssetETH)
|
providedAmt, exchangeRate, types.EthAssetETH)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
return swapState, net
|
return swapState, net
|
||||||
}
|
}
|
||||||
@@ -187,9 +186,8 @@ func newTestSwapStateWithERC20(t *testing.T, providesAmt *apd.Decimal) (*swapSta
|
|||||||
providesEthAssetAmt := coins.NewERC20TokenAmountFromDecimals(providesAmt, tokenInfo)
|
providesEthAssetAmt := coins.NewERC20TokenAmountFromDecimals(providesAmt, tokenInfo)
|
||||||
|
|
||||||
exchangeRate := coins.ToExchangeRate(apd.New(1, 0)) // 100%
|
exchangeRate := coins.ToExchangeRate(apd.New(1, 0)) // 100%
|
||||||
zeroPiconeros := coins.NewPiconeroAmount(0)
|
|
||||||
swapState, err := newSwapStateFromStart(b, testPeerID, types.Hash{}, false,
|
swapState, err := newSwapStateFromStart(b, testPeerID, types.Hash{}, false,
|
||||||
providesEthAssetAmt, zeroPiconeros, exchangeRate, types.EthAsset(addr))
|
providesEthAssetAmt, exchangeRate, types.EthAsset(addr))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
return swapState, contract
|
return swapState, contract
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,13 +7,11 @@ package relayer
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/ecdsa"
|
"crypto/ecdsa"
|
||||||
"math/big"
|
|
||||||
|
|
||||||
ethcommon "github.com/ethereum/go-ethereum/common"
|
ethcommon "github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/ethclient"
|
"github.com/ethereum/go-ethereum/ethclient"
|
||||||
logging "github.com/ipfs/go-log"
|
logging "github.com/ipfs/go-log"
|
||||||
|
|
||||||
"github.com/athanorlabs/atomic-swap/coins"
|
|
||||||
contracts "github.com/athanorlabs/atomic-swap/ethereum"
|
contracts "github.com/athanorlabs/atomic-swap/ethereum"
|
||||||
"github.com/athanorlabs/atomic-swap/net/message"
|
"github.com/athanorlabs/atomic-swap/net/message"
|
||||||
)
|
)
|
||||||
@@ -23,12 +21,6 @@ const (
|
|||||||
forwarderClaimGas = 156000 // worst case gas usage when using forwarder to claim
|
forwarderClaimGas = 156000 // worst case gas usage when using forwarder to claim
|
||||||
)
|
)
|
||||||
|
|
||||||
// FeeWei and FeeEth are the fixed 0.009 ETH fee for using a swap relayer to claim.
|
|
||||||
var (
|
|
||||||
FeeWei = big.NewInt(9e15)
|
|
||||||
FeeEth = coins.NewWeiAmount(FeeWei).AsEther()
|
|
||||||
)
|
|
||||||
|
|
||||||
var log = logging.Logger("relayer")
|
var log = logging.Logger("relayer")
|
||||||
|
|
||||||
// CreateRelayClaimRequest fills and returns a RelayClaimRequest ready for
|
// CreateRelayClaimRequest fills and returns a RelayClaimRequest ready for
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import (
|
|||||||
ethcrypto "github.com/ethereum/go-ethereum/crypto"
|
ethcrypto "github.com/ethereum/go-ethereum/crypto"
|
||||||
"github.com/ethereum/go-ethereum/ethclient"
|
"github.com/ethereum/go-ethereum/ethclient"
|
||||||
|
|
||||||
|
"github.com/athanorlabs/atomic-swap/coins"
|
||||||
contracts "github.com/athanorlabs/atomic-swap/ethereum"
|
contracts "github.com/athanorlabs/atomic-swap/ethereum"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -74,7 +75,7 @@ func createForwarderRequest(
|
|||||||
secret *[32]byte,
|
secret *[32]byte,
|
||||||
) (*gsnforwarder.IForwarderForwardRequest, error) {
|
) (*gsnforwarder.IForwarderForwardRequest, error) {
|
||||||
|
|
||||||
calldata, err := getClaimRelayerTxCalldata(FeeWei, swap, secret)
|
calldata, err := getClaimRelayerTxCalldata(coins.RelayerFeeWei, swap, secret)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ func Test_ValidateAndSendTransaction(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
receipt, err := block.WaitForReceipt(ctx, ec.Raw(), tx.Hash())
|
receipt, err := block.WaitForReceipt(ctx, ec.Raw(), tx.Hash())
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
t.Logf("gas cost to call new_swap: %d", receipt.GasUsed)
|
require.GreaterOrEqual(t, contracts.MaxNewSwapETHGas, int(receipt.GasUsed))
|
||||||
txOpts.Value = big.NewInt(0)
|
txOpts.Value = big.NewInt(0)
|
||||||
|
|
||||||
logIndex := 0 // change to 2 for ERC20, but ERC20 swaps cannot use the relayer
|
logIndex := 0 // change to 2 for ERC20, but ERC20 swaps cannot use the relayer
|
||||||
@@ -87,8 +87,8 @@ func Test_ValidateAndSendTransaction(t *testing.T) {
|
|||||||
tx, err = swapCreator.SetReady(txOpts, *swap)
|
tx, err = swapCreator.SetReady(txOpts, *swap)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
receipt, err = block.WaitForReceipt(ctx, ec.Raw(), tx.Hash())
|
receipt, err = block.WaitForReceipt(ctx, ec.Raw(), tx.Hash())
|
||||||
t.Logf("gas cost to call SetReady: %d", receipt.GasUsed)
|
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
require.GreaterOrEqual(t, contracts.MaxSetReadyGas, int(receipt.GasUsed))
|
||||||
|
|
||||||
secret := proof.Secret()
|
secret := proof.Secret()
|
||||||
|
|
||||||
|
|||||||
@@ -65,9 +65,9 @@ func validateClaimValues(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// The relayer fee must be strictly less than the swap value
|
// The relayer fee must be strictly less than the swap value
|
||||||
if FeeWei.Cmp(request.Swap.Value) >= 0 {
|
if coins.RelayerFeeWei.Cmp(request.Swap.Value) >= 0 {
|
||||||
return fmt.Errorf("swap value of %s ETH is too low to support %s ETH relayer fee",
|
return fmt.Errorf("swap value of %s ETH is too low to support %s ETH relayer fee",
|
||||||
coins.FmtWeiAsETH(request.Swap.Value), coins.FmtWeiAsETH(FeeWei))
|
coins.FmtWeiAsETH(request.Swap.Value), coins.FmtWeiAsETH(coins.RelayerFeeWei))
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import (
|
|||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
"github.com/athanorlabs/atomic-swap/coins"
|
||||||
"github.com/athanorlabs/atomic-swap/common/types"
|
"github.com/athanorlabs/atomic-swap/common/types"
|
||||||
contracts "github.com/athanorlabs/atomic-swap/ethereum"
|
contracts "github.com/athanorlabs/atomic-swap/ethereum"
|
||||||
"github.com/athanorlabs/atomic-swap/net/message"
|
"github.com/athanorlabs/atomic-swap/net/message"
|
||||||
@@ -35,17 +36,17 @@ func TestValidateRelayerFee(t *testing.T) {
|
|||||||
testCases := []testCase{
|
testCases := []testCase{
|
||||||
{
|
{
|
||||||
description: "swap value equal to relayer fee",
|
description: "swap value equal to relayer fee",
|
||||||
value: FeeWei,
|
value: coins.RelayerFeeWei,
|
||||||
expectErr: "swap value of 0.009 ETH is too low to support 0.009 ETH relayer fee",
|
expectErr: "swap value of 0.009 ETH is too low to support 0.009 ETH relayer fee",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
description: "swap value less than relayer fee",
|
description: "swap value less than relayer fee",
|
||||||
value: new(big.Int).Sub(FeeWei, big.NewInt(1e15)),
|
value: new(big.Int).Sub(coins.RelayerFeeWei, big.NewInt(1e15)),
|
||||||
expectErr: "swap value of 0.008 ETH is too low to support 0.009 ETH relayer fee",
|
expectErr: "swap value of 0.008 ETH is too low to support 0.009 ETH relayer fee",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
description: "swap value larger than min fee",
|
description: "swap value larger than min fee",
|
||||||
value: new(big.Int).Add(FeeWei, big.NewInt(1e15)),
|
value: new(big.Int).Add(coins.RelayerFeeWei, big.NewInt(1e15)),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -187,7 +187,7 @@ func (s *IntegrationTestSuite) testSuccessOneSwap(asset types.EthAsset, useRelay
|
|||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
bwsc := s.newSwapdWSClient(ctx, defaultXMRMakerSwapdWSEndpoint)
|
bwsc := s.newSwapdWSClient(ctx, defaultXMRMakerSwapdWSEndpoint)
|
||||||
min := coins.StrToDecimal("0.1")
|
min := coins.StrToDecimal("0.2")
|
||||||
offerResp, statusCh, err := bwsc.MakeOfferAndSubscribe(min, xmrmakerProvideAmount,
|
offerResp, statusCh, err := bwsc.MakeOfferAndSubscribe(min, xmrmakerProvideAmount,
|
||||||
exchangeRate, asset, useRelayer)
|
exchangeRate, asset, useRelayer)
|
||||||
require.NoError(s.T(), err)
|
require.NoError(s.T(), err)
|
||||||
@@ -529,7 +529,7 @@ func (s *IntegrationTestSuite) testAbortXMRTakerCancels(asset types.EthAsset) {
|
|||||||
|
|
||||||
bwsc := s.newSwapdWSClient(ctx, defaultXMRMakerSwapdWSEndpoint)
|
bwsc := s.newSwapdWSClient(ctx, defaultXMRMakerSwapdWSEndpoint)
|
||||||
|
|
||||||
min := coins.StrToDecimal("0.1")
|
min := coins.StrToDecimal("0.2")
|
||||||
offerResp, statusCh, err := bwsc.MakeOfferAndSubscribe(min, xmrmakerProvideAmount,
|
offerResp, statusCh, err := bwsc.MakeOfferAndSubscribe(min, xmrmakerProvideAmount,
|
||||||
exchangeRate, asset, false)
|
exchangeRate, asset, false)
|
||||||
require.NoError(s.T(), err)
|
require.NoError(s.T(), err)
|
||||||
|
|||||||
Reference in New Issue
Block a user