min and max offer amount checks (#445)

Co-authored-by: noot <36753753+noot@users.noreply.github.com>
This commit is contained in:
Dmitry Holodov
2023-04-30 17:36:31 -05:00
committed by GitHub
parent 906a5c69a3
commit f9a63ff7c3
31 changed files with 862 additions and 405 deletions

View File

@@ -23,7 +23,7 @@ type swapCLITestSuite struct {
mockTokens map[string]ethcommon.Address
}
func TestRunIntegrationTests(t *testing.T) {
func TestRunSwapcliWithDaemonTests(t *testing.T) {
s := new(swapCLITestSuite)
s.conf = daemon.CreateTestConf(t, tests.GetMakerTestKey(t))

View File

@@ -18,12 +18,12 @@ import (
"github.com/urfave/cli/v2"
"github.com/athanorlabs/atomic-swap/cliutil"
"github.com/athanorlabs/atomic-swap/coins"
"github.com/athanorlabs/atomic-swap/common"
mcrypto "github.com/athanorlabs/atomic-swap/crypto/monero"
"github.com/athanorlabs/atomic-swap/daemon"
"github.com/athanorlabs/atomic-swap/ethereum/extethclient"
"github.com/athanorlabs/atomic-swap/monero"
"github.com/athanorlabs/atomic-swap/relayer"
)
const (
@@ -209,7 +209,7 @@ func cliApp() *cli.App {
Name: flagRelayer,
Usage: fmt.Sprintf(
"Relay claims for XMR makers and earn %s ETH (minus gas fees) per transaction",
relayer.FeeEth.Text('f'),
coins.RelayerFeeETH.Text('f'),
),
Value: false,
},

View File

@@ -15,7 +15,7 @@ coverage:
ignore:
- ethereum/swap_creator.go
- ethereum/aggregator_v3_interface.go
- ethereum/erc20_mock.go
- ethereum/erc20_token.go
- ethereum/ierc20.go
- ethereum/block/ut_contract_test.go
- cmd/swapd/profile.go

View File

@@ -4,6 +4,8 @@
package coins
import (
"math/big"
"github.com/cockroachdb/apd/v3"
logging "github.com/ipfs/go-log"
)
@@ -20,6 +22,13 @@ const (
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 (
// decimalCtx is the apd context used for math operations on our coins
decimalCtx = apd.BaseContext.WithPrecision(MaxCoinPrecision)

View File

@@ -22,6 +22,12 @@ var (
// CurOfferVersion is the latest supported version of a serialised Offer struct
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`)
errOfferIDNotSet = errors.New(`"offerID" is not set`)
errExchangeRateNil = errors.New(`"exchangeRate" is not set`)
@@ -131,6 +137,7 @@ func (o *Offer) validate() error {
return err
}
// It is an error if the min is greater than the max
if o.MinAmount.Cmp(o.MaxAmount) > 0 {
return errMinGreaterThanMax
}
@@ -141,6 +148,25 @@ func (o *Offer) validate() error {
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() {
return errors.New("hash of offer fields does not match offer ID")
}

View File

@@ -175,6 +175,11 @@ func TestOffer_UnmarshalJSON_BadAmountsOrRate(t *testing.T) {
jsonData: fmt.Sprintf(offerJSON, `"-1"`, `"1"`, `"0.1"`),
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
{
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"`),
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
{
jsonData: fmt.Sprintf(offerJSON, `"0.11"`, `"0.1"`, `"0.1"`),

View File

@@ -27,7 +27,6 @@ import (
"github.com/athanorlabs/atomic-swap/ethereum/extethclient"
"github.com/athanorlabs/atomic-swap/monero"
"github.com/athanorlabs/atomic-swap/net"
"github.com/athanorlabs/atomic-swap/relayer"
"github.com/athanorlabs/atomic-swap/rpcclient"
"github.com/athanorlabs/atomic-swap/rpcclient/wsclient"
"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
//
expectedBal := new(apd.Decimal)
_, err = coins.DecimalCtx().Sub(expectedBal, providesAmt, relayer.FeeEth)
_, err = coins.DecimalCtx().Sub(expectedBal, providesAmt, coins.RelayerFeeETH)
require.NoError(t, err)
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
//
bobExpectedBal := new(apd.Decimal)
_, err = coins.DecimalCtx().Sub(bobExpectedBal, providesAmt, relayer.FeeEth)
_, err = coins.DecimalCtx().Sub(bobExpectedBal, providesAmt, coins.RelayerFeeETH)
require.NoError(t, err)
bobBalance, err := bobConf.EthereumClient.Balance(ctx)
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
//
bobExpectedBal := new(apd.Decimal)
_, err = coins.DecimalCtx().Sub(bobExpectedBal, providesAmt, relayer.FeeEth)
_, err = coins.DecimalCtx().Sub(bobExpectedBal, providesAmt, coins.RelayerFeeETH)
require.NoError(t, err)
bobBalance, err := bobConf.EthereumClient.Balance(ctx)
require.NoError(t, err)

View File

@@ -6,46 +6,24 @@ package contracts
import (
"bytes"
"context"
"crypto/ecdsa"
"errors"
"testing"
"github.com/athanorlabs/go-relayer/impls/gsnforwarder"
ethcommon "github.com/ethereum/go-ethereum/common"
ethcrypto "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/stretchr/testify/require"
"github.com/athanorlabs/atomic-swap/common"
"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
// 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)
pk := tests.GetMakerTestKey(t)
contractAddr := deployContract(t, ec, pk, trustedForwarder)
contractAddr, _ := deploySwapCreatorWithForwarder(t, ec, pk, forwarderAddr)
code, err := ec.CodeAt(context.Background(), contractAddr, nil)
require.NoError(t, err)
return code
@@ -54,8 +32,8 @@ func getContractCode(t *testing.T, trustedForwarder ethcommon.Address) []byte {
func TestCheckForwarderContractCode(t *testing.T) {
ec, _ := tests.NewEthClient(t)
pk := tests.GetMakerTestKey(t)
trustedForwarder := deployForwarder(t, ec, pk)
err := CheckForwarderContractCode(context.Background(), ec, trustedForwarder)
forwarderAddr := deployForwarder(t, ec, pk)
err := CheckForwarderContractCode(context.Background(), ec, forwarderAddr)
require.NoError(t, err)
}
@@ -66,7 +44,7 @@ func TestExpectedSwapCreatorBytecodeHex(t *testing.T) {
allZeroTrustedForwarder := ethcommon.Address{}
codeHex := ethcommon.Bytes2Hex(getContractCode(t, allZeroTrustedForwarder))
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
@@ -75,12 +53,12 @@ func TestExpectedSwapCreatorBytecodeHex(t *testing.T) {
func TestForwarderAddrIndexes(t *testing.T) {
ec, _ := tests.NewEthClient(t)
pk := tests.GetMakerTestKey(t)
trustedForwarder := deployForwarder(t, ec, pk)
contactBytes := getContractCode(t, trustedForwarder)
forwarderAddr := deployForwarder(t, ec, pk)
contactBytes := getContractCode(t, forwarderAddr)
addressLocations := make([]int, 0) // at the current time, there should always be 2
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)
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) {
ec, _ := tests.NewEthClient(t)
pk := tests.GetMakerTestKey(t)
trustedForwarderAddrs := []string{
forwarderAddrs := []string{
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)
contractAddr := deployContract(t, ec, pk, tfAddr)
contractAddr, _ := deploySwapCreatorWithForwarder(t, ec, pk, tfAddr)
parsedTFAddr, err := CheckSwapCreatorContractCode(context.Background(), ec, contractAddr)
require.NoError(t, err)
require.Equal(t, addrHex, parsedTFAddr.Hex())

15
ethereum/consts.go Normal file
View 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
View 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
)

View File

@@ -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)
}

View 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)
}

View File

@@ -15,6 +15,7 @@ import (
"github.com/ethereum/go-ethereum/accounts/abi/bind"
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/ethclient"
"github.com/stretchr/testify/require"
@@ -29,53 +30,78 @@ import (
var (
defaultTimeoutDuration = big.NewInt(60) // 60 seconds
ethAssetAddress = ethcommon.Address(types.EthAssetETH)
defaultSwapValue = big.NewInt(100)
dummySwapKey = [32]byte{1} // dummy non-zero value for claim/refund key
)
func setupXMRTakerAuth(t *testing.T) (*bind.TransactOpts, *ethclient.Client, *ecdsa.PrivateKey) {
conn, _ := tests.NewEthClient(t)
pkA := tests.GetTakerTestKey(t)
chainID, err := conn.ChainID(context.Background())
func getAuth(t *testing.T, pk *ecdsa.PrivateKey) *bind.TransactOpts {
txOpts, err := bind.NewKeyedTransactorWithChainID(pk, big.NewInt(common.GanacheChainID))
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)
return auth, conn, pkA
return receipt
}
func approveERC20(t *testing.T,
auth *bind.TransactOpts,
conn *ethclient.Client,
ec *ethclient.Client,
pk *ecdsa.PrivateKey,
erc20Contract *TestERC20,
swapCreatorAddress ethcommon.Address,
value *big.Int,
) {
require.NotNil(t, erc20Contract)
tx, err := erc20Contract.Approve(auth, swapCreatorAddress, value)
tx, err := erc20Contract.Approve(getAuth(t, pk), swapCreatorAddress, value)
require.NoError(t, err)
receipt, err := block.WaitForReceipt(context.Background(), conn, tx.Hash())
require.NoError(t, err)
t.Logf("gas cost to call Approve: %d", receipt.GasUsed)
receipt := getReceipt(t, ec, tx)
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) {
auth, conn, _ := setupXMRTakerAuth(t)
address, tx, contract, err := DeploySwapCreator(auth, conn, ethcommon.Address{})
func deployForwarder(t *testing.T, ec *ethclient.Client, pk *ecdsa.PrivateKey) ethcommon.Address {
forwarderAddr, err := DeployGSNForwarderWithKey(context.Background(), ec, pk)
require.NoError(t, err)
require.NotEqual(t, ethcommon.Address{}, address)
require.NotNil(t, tx)
require.NotNil(t, contract)
return forwarderAddr
}
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)
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))
var pubKeyClaim, pubKeyRefund [32]byte
_, err = rand.Read(pubKeyClaim[:])
_, err := rand.Read(pubKeyClaim[:])
require.NoError(t, err)
_, err = rand.Read(pubKeyRefund[:])
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)
require.NoError(t, err)
txOpts := getAuth(t, pk)
value := defaultSwapValue
isEthAsset := asset == ethAssetAddress
if isEthAsset {
auth.Value = value
if asset.IsETH() {
txOpts.Value = value
} else {
approveERC20(t, auth, conn, erc20Contract, address, value)
approveERC20(t, ec, pk, erc20Contract, swapCreatorAddr, value)
}
tx, err = contract.NewSwap(
auth,
tx, err := swapCreator.NewSwap(
txOpts,
pubKeyClaim,
pubKeyRefund,
claimer,
defaultTimeoutDuration,
defaultTimeoutDuration,
asset,
asset.Address(),
value,
nonce,
)
require.NoError(t, err)
receipt, err = block.WaitForReceipt(context.Background(), conn, tx.Hash())
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")
}
newSwapLogIndex := 0
if !isEthAsset {
if asset.IsToken() {
newSwapLogIndex = 2
}
require.Equal(t, newSwapLogIndex+1, len(receipt.Logs))
@@ -129,7 +161,7 @@ func testNewSwap(t *testing.T, asset ethcommon.Address, erc20Contract *TestERC20
PubKeyRefund: pubKeyRefund,
Timeout0: t0,
Timeout1: t1,
Asset: asset,
Asset: asset.Address(),
Value: value,
Nonce: nonce,
}
@@ -139,7 +171,7 @@ func testNewSwap(t *testing.T, asset ethcommon.Address, erc20Contract *TestERC20
}
func TestSwapCreator_NewSwap(t *testing.T) {
testNewSwap(t, ethAssetAddress, nil)
testNewSwap(t, types.EthAssetETH, nil)
}
func TestSwapCreator_Claim_vec(t *testing.T) {
@@ -159,26 +191,23 @@ func TestSwapCreator_Claim_vec(t *testing.T) {
cmt := pk.Keccak256()
// deploy swap contract with claim key hash
auth, conn, pkA := setupXMRTakerAuth(t)
pub := pkA.Public().(*ecdsa.PublicKey)
addr := crypto.PubkeyToAddress(*pub)
pkA := tests.GetTakerTestKey(t)
ec, _ := tests.NewEthClient(t)
addr := crypto.PubkeyToAddress(pkA.PublicKey)
_, tx, contract, err := DeploySwapCreator(auth, 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)
_, swapCreator := deploySwapCreator(t, ec, pkA)
txOpts := getAuth(t, pkA)
txOpts.Value = defaultSwapValue
nonce := big.NewInt(0)
auth.Value = defaultSwapValue
tx, err = contract.NewSwap(auth, cmt, dummySwapKey, addr, defaultTimeoutDuration,
tx, err := swapCreator.NewSwap(txOpts, cmt, dummySwapKey, addr, defaultTimeoutDuration,
defaultTimeoutDuration, ethcommon.Address(types.EthAssetETH), defaultSwapValue, nonce)
require.NoError(t, err)
auth.Value = nil
receipt, err = block.WaitForReceipt(context.Background(), conn, tx.Hash())
require.NoError(t, err)
t.Logf("gas cost to call new_swap: %d", receipt.GasUsed)
receipt := getReceipt(t, ec, tx)
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")
require.Equal(t, 1, len(receipt.Logs))
id, err := GetIDFromLog(receipt.Logs[0])
@@ -200,25 +229,26 @@ func TestSwapCreator_Claim_vec(t *testing.T) {
}
// set contract to Ready
tx, err = contract.SetReady(auth, swap)
tx, err = swapCreator.SetReady(getAuth(t, pkA), swap)
require.NoError(t, err)
receipt, err = block.WaitForReceipt(context.Background(), conn, tx.Hash())
require.NoError(t, err)
t.Logf("gas cost to call set_ready: %d", receipt.GasUsed)
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
tx, err = contract.Claim(auth, swap, s)
tx, err = swapCreator.Claim(getAuth(t, pkA), swap, s)
require.NoError(t, err)
receipt, err = block.WaitForReceipt(context.Background(), conn, tx.Hash())
require.NoError(t, err)
t.Logf("gas cost to call claim: %d", receipt.GasUsed)
receipt = getReceipt(t, ec, tx)
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")
stage, err := contract.Swaps(nil, id)
stage, err := swapCreator.Swaps(nil, id)
require.NoError(t, err)
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
dleq := &dleq.DefaultDLEq{}
proof, err := dleq.Prove()
@@ -230,36 +260,37 @@ func testClaim(t *testing.T, asset ethcommon.Address, newLogIndex int, value *bi
cmt := res.Secp256k1PublicKey().Keccak256()
// deploy swap contract with claim key hash
authOrig, conn, pkA := setupXMRTakerAuth(t)
pub := pkA.Public().(*ecdsa.PublicKey)
addr := crypto.PubkeyToAddress(*pub)
pkA := tests.GetTakerTestKey(t)
ec, _ := tests.NewEthClient(t)
addr := crypto.PubkeyToAddress(pkA.PublicKey)
// TODO: Rewrite this code to avoid the awkward use of txOpts. Code was using
// 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)
swapCreatorAddr, swapCreator := deploySwapCreator(t, ec, pkA)
if asset != ethAssetAddress {
approveERC20(t, authOrig, conn, erc20Contract, swapCreatorAddr, value)
if asset.IsToken() {
approveERC20(t, ec, pkA, erc20Contract, swapCreatorAddr, value)
}
nonce := big.NewInt(0)
txOpts = *authOrig
if asset == ethAssetAddress {
txOpts := getAuth(t, pkA)
require.NoError(t, err)
if asset.IsETH() {
txOpts.Value = value
}
tx, err = contract.NewSwap(&txOpts, cmt, dummySwapKey, addr,
defaultTimeoutDuration, defaultTimeoutDuration, asset, value, nonce)
nonce := GenerateNewSwapNonce()
tx, err := swapCreator.NewSwap(txOpts, cmt, dummySwapKey, addr,
defaultTimeoutDuration, defaultTimeoutDuration, asset.Address(), value, nonce)
require.NoError(t, err)
receipt, err = block.WaitForReceipt(context.Background(), conn, tx.Hash())
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))
id, err := GetIDFromLog(receipt.Logs[newLogIndex])
@@ -275,42 +306,45 @@ func testClaim(t *testing.T, asset ethcommon.Address, newLogIndex int, value *bi
PubKeyRefund: dummySwapKey,
Timeout0: t0,
Timeout1: t1,
Asset: asset,
Asset: asset.Address(),
Value: value,
Nonce: nonce,
}
// ensure we can't claim before setting contract to Ready
txOpts = *authOrig
_, err = contract.Claim(&txOpts, swap, proof.Secret())
_, err = swapCreator.Claim(getAuth(t, pkA), swap, proof.Secret())
require.ErrorContains(t, err, "VM Exception while processing transaction: revert")
// set contract to Ready
txOpts = *authOrig
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)
tx, err = swapCreator.SetReady(getAuth(t, pkA), swap)
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
txOpts = *authOrig
tx, err = contract.Claim(&txOpts, swap, proof.Secret())
tx, err = swapCreator.Claim(getAuth(t, pkA), swap, proof.Secret())
require.NoError(t, err)
receipt, err = block.WaitForReceipt(context.Background(), conn, tx.Hash())
require.NoError(t, err)
t.Logf("gas cost to call Claim: %d", receipt.GasUsed)
receipt = getReceipt(t, ec, tx)
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.Equal(t, StageCompleted, stage)
}
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
dleq := &dleq.DefaultDLEq{}
proof, err := dleq.Prove()
@@ -322,30 +356,34 @@ func testRefundBeforeT0(t *testing.T, asset ethcommon.Address, erc20Contract *Te
cmt := res.Secp256k1PublicKey().Keccak256()
// deploy swap contract with refund key hash
auth, conn, pkA := setupXMRTakerAuth(t)
pub := pkA.Public().(*ecdsa.PublicKey)
addr := crypto.PubkeyToAddress(*pub)
pkA := tests.GetTakerTestKey(t)
ec, _ := tests.NewEthClient(t)
addr := crypto.PubkeyToAddress(pkA.PublicKey)
address, tx, contract, err := DeploySwapCreator(auth, 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)
swapCreatorAddr, swapCreator := deploySwapCreator(t, ec, pkA)
if asset != ethAssetAddress {
approveERC20(t, auth, conn, erc20Contract, address, defaultSwapValue)
if asset.IsToken() {
approveERC20(t, ec, pkA, erc20Contract, swapCreatorAddr, defaultSwapValue)
}
nonce := big.NewInt(0)
auth.Value = defaultSwapValue
tx, err = contract.NewSwap(auth, dummySwapKey, cmt, addr, defaultTimeoutDuration, defaultTimeoutDuration,
asset, defaultSwapValue, nonce)
require.NoError(t, err)
auth.Value = nil
txOpts := getAuth(t, pkA)
txOpts.Value = defaultSwapValue
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)
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))
id, err := GetIDFromLog(receipt.Logs[newLogIndex])
@@ -361,28 +399,38 @@ func testRefundBeforeT0(t *testing.T, asset ethcommon.Address, erc20Contract *Te
PubKeyRefund: cmt,
Timeout0: t0,
Timeout1: t1,
Asset: asset,
Asset: asset.Address(),
Value: defaultSwapValue,
Nonce: nonce,
}
// 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)
receipt, err = block.WaitForReceipt(context.Background(), conn, tx.Hash())
require.NoError(t, err)
t.Logf("gas cost to call Refund: %d", receipt.GasUsed)
receipt = getReceipt(t, ec, tx)
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.Equal(t, StageCompleted, stage)
}
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
dleq := &dleq.DefaultDLEq{}
proof, err := dleq.Prove()
@@ -394,31 +442,35 @@ func testRefundAfterT1(t *testing.T, asset ethcommon.Address, erc20Contract *Tes
cmt := res.Secp256k1PublicKey().Keccak256()
// deploy swap contract with refund key hash
auth, conn, pkA := setupXMRTakerAuth(t)
pub := pkA.Public().(*ecdsa.PublicKey)
addr := crypto.PubkeyToAddress(*pub)
pkA := tests.GetTakerTestKey(t)
ec, _ := tests.NewEthClient(t)
addr := crypto.PubkeyToAddress(pkA.PublicKey)
address, tx, contract, err := DeploySwapCreator(auth, 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)
swapCreatorAddr, swapCreator := deploySwapCreator(t, ec, pkA)
if asset != ethAssetAddress {
approveERC20(t, auth, conn, erc20Contract, address, defaultSwapValue)
if asset.IsToken() {
approveERC20(t, ec, pkA, erc20Contract, swapCreatorAddr, defaultSwapValue)
}
nonce := big.NewInt(0)
timeout := big.NewInt(3)
auth.Value = defaultSwapValue
tx, err = contract.NewSwap(auth, dummySwapKey, cmt, addr, timeout, timeout,
asset, defaultSwapValue, nonce)
require.NoError(t, err)
auth.Value = nil
txOpts := getAuth(t, pkA)
txOpts.Value = defaultSwapValue
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)
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))
id, err := GetIDFromLog(receipt.Logs[newLogIndex])
@@ -436,53 +488,50 @@ func testRefundAfterT1(t *testing.T, asset ethcommon.Address, erc20Contract *Tes
PubKeyRefund: cmt,
Timeout0: t0,
Timeout1: t1,
Asset: asset,
Asset: asset.Address(),
Value: defaultSwapValue,
Nonce: nonce,
}
secret := proof.Secret()
tx, err = contract.Refund(auth, swap, secret)
tx, err = swapCreator.Refund(getAuth(t, pkA), swap, secret)
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")
<-time.After(time.Until(time.Unix(t1.Int64()+1, 0)))
// 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)
receipt, err = block.WaitForReceipt(context.Background(), conn, tx.Hash())
require.NoError(t, err)
t.Logf("gas cost to call Refund: %d", receipt.GasUsed)
receipt = getReceipt(t, ec, tx)
callOpts := &bind.CallOpts{
From: crypto.PubkeyToAddress(*pub),
Context: context.Background(),
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 := contract.Swaps(callOpts, id)
callOpts := &bind.CallOpts{Context: ctx}
stage, err := swapCreator.Swaps(callOpts, id)
require.NoError(t, err)
require.Equal(t, StageCompleted, stage)
}
func TestSwapCreator_Refund_afterT1(t *testing.T) {
testRefundAfterT1(t, ethAssetAddress, nil, 0)
testRefundAfterT1(t, types.EthAssetETH, nil, 0)
}
// test case where contract has multiple swaps happening at once
func TestSwapCreator_MultipleSwaps(t *testing.T) {
// test case where contract has multiple swaps happening at once
conn, chainID := tests.NewEthClient(t)
pkContractCreator := tests.GetTestKeyByIndex(t, 0)
auth, err := bind.NewKeyedTransactorWithChainID(pkContractCreator, chainID)
require.NoError(t, err)
ec, _ := tests.NewEthClient(t)
_, tx, contract, err := DeploySwapCreator(auth, 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)
_, swapCreator := deploySwapCreator(t, ec, pkContractCreator)
const numSwaps = 16
type swapCase struct {
@@ -493,12 +542,6 @@ func TestSwapCreator_MultipleSwaps(t *testing.T) {
swap SwapCreatorSwap
}
getAuth := func(sc *swapCase) *bind.TransactOpts {
auth, err := bind.NewKeyedTransactorWithChainID(sc.walletKey, chainID)
require.NoError(t, err)
return auth
}
swapCases := [numSwaps]swapCase{}
// setup all swap instances
@@ -539,25 +582,26 @@ func TestSwapCreator_MultipleSwaps(t *testing.T) {
for i := 0; i < numSwaps; i++ {
go func(sc *swapCase) {
defer wg.Done()
auth := getAuth(sc)
auth := getAuth(t, sc.walletKey)
auth.Value = sc.swap.Value
tx, err := contract.NewSwap(
tx, err := swapCreator.NewSwap(
auth,
sc.swap.PubKeyClaim,
sc.swap.PubKeyRefund,
sc.swap.Claimer,
defaultTimeoutDuration,
defaultTimeoutDuration,
ethcommon.Address(types.EthAssetETH),
types.EthAssetETH.Address(),
sc.swap.Value,
sc.swap.Nonce,
)
require.NoError(t, err)
auth.Value = nil
receipt := getReceipt(t, ec, tx)
receipt, err := block.WaitForReceipt(context.Background(), conn, tx.Hash())
require.NoError(t, err)
t.Logf("gas cost to call new_swap[%d]: %d", sc.index, receipt.GasUsed)
t.Logf("gas cost to call ETH NewSwap[%d]: %d (delta %d)",
sc.index, receipt.GasUsed, MaxNewSwapETHGas-int(receipt.GasUsed))
require.GreaterOrEqual(t, MaxNewSwapETHGas, int(receipt.GasUsed), "ETH NewSwap")
require.Equal(t, 1, len(receipt.Logs))
sc.id, err = GetIDFromLog(receipt.Logs[0])
@@ -574,11 +618,12 @@ func TestSwapCreator_MultipleSwaps(t *testing.T) {
for i := 0; i < numSwaps; i++ {
go func(sc *swapCase) {
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)
receipt, err := block.WaitForReceipt(context.Background(), conn, tx.Hash())
require.NoError(t, err)
t.Logf("gas cost to call SetReady[%d]: %d", sc.index, receipt.GasUsed)
receipt := getReceipt(t, ec, tx)
t.Logf("gas cost to call SetReady[%d]: %d (delta %d)",
sc.index, receipt.GasUsed, MaxSetReadyGas-int(receipt.GasUsed))
require.GreaterOrEqual(t, MaxSetReadyGas, int(receipt.GasUsed), "SetReady")
}(&swapCases[i])
}
wg.Wait() // set_ready called on all swaps
@@ -588,11 +633,12 @@ func TestSwapCreator_MultipleSwaps(t *testing.T) {
for i := 0; i < numSwaps; i++ {
go func(sc *swapCase) {
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)
receipt, err := block.WaitForReceipt(context.Background(), conn, tx.Hash())
require.NoError(t, err)
t.Logf("gas cost to call Claim[%d]: %d", sc.index, receipt.GasUsed)
receipt := getReceipt(t, ec, tx)
t.Logf("gas cost to call ETH Claim[%d]: %d (delta %d)",
sc.index, receipt.GasUsed, MaxClaimETHGas-int(receipt.GasUsed))
require.GreaterOrEqual(t, MaxClaimETHGas, int(receipt.GasUsed), "ETH Claim")
}(&swapCases[i])
}
wg.Wait() // claim called on all swaps
@@ -602,7 +648,7 @@ func TestSwapCreator_MultipleSwaps(t *testing.T) {
for i := 0; i < numSwaps; i++ {
go func(sc *swapCase) {
defer wg.Done()
stage, err := contract.Swaps(nil, sc.id)
stage, err := swapCreator.Swaps(nil, sc.id)
require.NoError(t, err)
require.Equal(t, StageToString(StageCompleted), StageToString(stage))
}(&swapCases[i])

View File

@@ -7,6 +7,7 @@ package contracts
import (
"bytes"
"crypto/rand"
"errors"
"fmt"
"math/big"
@@ -235,3 +236,12 @@ func GetTimeoutsFromLog(log *ethtypes.Log) (*big.Int, *big.Int, error) {
t1 := res[4].(*big.Int)
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
}

View File

@@ -4,7 +4,6 @@
package xmrmaker
import (
"github.com/athanorlabs/atomic-swap/coins"
"github.com/athanorlabs/atomic-swap/common/types"
)
@@ -13,17 +12,17 @@ func (inst *Instance) MakeOffer(
o *types.Offer,
useRelayer bool,
) (*types.OfferExtra, error) {
// get monero balance
balance, err := inst.backend.XMRClient().GetBalance(0)
err := validateMinBalance(
inst.backend.Ctx(),
inst.backend.XMRClient(),
inst.backend.ETHClient(),
o.MaxAmount,
o.EthAsset,
)
if err != nil {
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() {
return nil, errRelayingWithNonEthAsset
}

View File

@@ -86,3 +86,15 @@ func (e errUnlockedBalanceTooLow) Error() 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'),
)
}

View 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
}

View 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())
}

View File

@@ -31,15 +31,15 @@ var (
errInvalidStageForRecovery = errors.New("cannot create ongoing swap state if stage is not ETHLocked or ContractReady") //nolint:lll
)
type errAssetBalanceTooLow struct {
providedAmount *apd.Decimal
balance *apd.Decimal
type errTokenBalanceTooLow struct {
providedAmount *apd.Decimal // standard units
tokenBalance *apd.Decimal // standard units
symbol string
}
func (e errAssetBalanceTooLow) Error() string {
func (e errTokenBalanceTooLow) Error() string {
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,
)
}
@@ -50,25 +50,37 @@ func errContractAddrMismatch(addr string) error {
}
type errAmountProvidedTooLow struct {
providedAmount *apd.Decimal
minAmount *apd.Decimal
providedAmtETH *apd.Decimal
offerMinAmtETH *apd.Decimal
}
func (e errAmountProvidedTooLow) Error() string {
return fmt.Sprintf("%s ETH provided is under offer minimum of %s XMR",
e.providedAmount.String(),
e.minAmount.String(),
return fmt.Sprintf("%s ETH provided is under offer minimum of %s ETH",
e.providedAmtETH.Text('f'),
e.offerMinAmtETH.Text('f'),
)
}
type errAmountProvidedTooHigh struct {
providedAmount *apd.Decimal
maxAmount *apd.Decimal
providedAmtETH *apd.Decimal
offerMaxETH *apd.Decimal
}
func (e errAmountProvidedTooHigh) Error() string {
return fmt.Sprintf("%s ETH provided is over offer maximum of %s XMR",
e.providedAmount.String(),
e.maxAmount.String(),
return fmt.Sprintf("%s ETH provided is over offer maximum of %s ETH",
e.providedAmtETH.Text('f'),
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'),
)
}

View 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
}

View 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)
}

View File

@@ -32,17 +32,32 @@ func (inst *Instance) InitiateProtocol(
return nil, err
}
expectedAmount, err := offer.ExchangeRate.ToXMR(providesAmount)
offerMinETH, err := offer.ExchangeRate.ToETH(offer.MinAmount)
if err != nil {
return nil, err
}
if expectedAmount.Cmp(offer.MinAmount) < 0 {
return nil, errAmountProvidedTooLow{providesAmount, offer.MinAmount}
offerMaxETH, err := offer.ExchangeRate.ToETH(offer.MaxAmount)
if err != nil {
return nil, err
}
if expectedAmount.Cmp(offer.MaxAmount) > 0 {
return nil, errAmountProvidedTooHigh{providesAmount, offer.MaxAmount}
if offerMinETH.Cmp(providesAmount) > 0 {
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(
@@ -55,8 +70,7 @@ func (inst *Instance) InitiateProtocol(
return nil, err
}
state, err := inst.initiate(makerPeerID, providedAmount, coins.MoneroToPiconero(expectedAmount),
offer.ExchangeRate, offer.EthAsset, offer.ID)
state, err := inst.initiate(makerPeerID, providedAmount, offer.ExchangeRate, offer.EthAsset, offer.ID)
if err != nil {
return nil, err
}
@@ -67,7 +81,6 @@ func (inst *Instance) InitiateProtocol(
func (inst *Instance) initiate(
makerPeerID peer.ID,
providesAmount coins.EthAssetAmount,
expectedAmount *coins.PiconeroAmount,
exchangeRate *coins.ExchangeRate,
ethAsset types.EthAsset,
offerID types.Hash,
@@ -79,44 +92,12 @@ func (inst *Instance) initiate(
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(
inst.backend,
makerPeerID,
offerID,
inst.noTransferBack,
providesAmount,
expectedAmount,
exchangeRate,
ethAsset,
)

View File

@@ -7,7 +7,6 @@ package xmrtaker
import (
"context"
"crypto/rand"
"errors"
"fmt"
"math/big"
@@ -101,7 +100,6 @@ func newSwapStateFromStart(
offerID types.Hash,
noTransferBack bool,
providedAmount coins.EthAssetAmount,
expectedAmount *coins.PiconeroAmount,
exchangeRate *coins.ExchangeRate,
ethAsset types.EthAsset,
) (*swapState, error) {
@@ -123,12 +121,17 @@ func newSwapStateFromStart(
return nil, err
}
expectedAmount, err := exchangeRate.ToXMR(providedAmount.AsStandard())
if err != nil {
return nil, err
}
info := pswap.NewInfo(
makerPeerID,
offerID,
coins.ProvidesETH,
providedAmount.AsStandard(),
expectedAmount.AsMonero(),
expectedAmount,
exchangeRate,
ethAsset,
stage,
@@ -562,7 +565,7 @@ func (s *swapState) lockAsset() (*ethtypes.Receipt, error) {
log.Debugf("locking %s %s in contract", providedAmt.AsStandard(), providedAmt.StandardSymbol())
nonce := generateNonce()
nonce := contracts.GenerateNewSwapNonce()
receipt, err := s.sender.NewSwap(
cmtXMRMaker,
cmtXMRTaker,
@@ -698,10 +701,3 @@ func (s *swapState) refund() (*ethtypes.Receipt, error) {
func generateKeys() (*pcommon.KeysAndProof, error) {
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
}

View File

@@ -135,10 +135,9 @@ func newBackend(t *testing.T) backend.Backend {
func newTestSwapStateAndNet(t *testing.T) (*swapState, *mockNet) {
b, net := newBackendAndNet(t)
providedAmt := coins.EtherToWei(coins.StrToDecimal("1"))
expectedAmt := coins.MoneroToPiconero(coins.StrToDecimal("1"))
exchangeRate := coins.ToExchangeRate(coins.StrToDecimal("1.0")) // 100%
swapState, err := newSwapStateFromStart(b, testPeerID, types.Hash{}, true,
providedAmt, expectedAmt, exchangeRate, types.EthAssetETH)
providedAmt, exchangeRate, types.EthAssetETH)
require.NoError(t, err)
return swapState, net
}
@@ -187,9 +186,8 @@ func newTestSwapStateWithERC20(t *testing.T, providesAmt *apd.Decimal) (*swapSta
providesEthAssetAmt := coins.NewERC20TokenAmountFromDecimals(providesAmt, tokenInfo)
exchangeRate := coins.ToExchangeRate(apd.New(1, 0)) // 100%
zeroPiconeros := coins.NewPiconeroAmount(0)
swapState, err := newSwapStateFromStart(b, testPeerID, types.Hash{}, false,
providesEthAssetAmt, zeroPiconeros, exchangeRate, types.EthAsset(addr))
providesEthAssetAmt, exchangeRate, types.EthAsset(addr))
require.NoError(t, err)
return swapState, contract
}

View File

@@ -7,13 +7,11 @@ package relayer
import (
"context"
"crypto/ecdsa"
"math/big"
ethcommon "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/ethclient"
logging "github.com/ipfs/go-log"
"github.com/athanorlabs/atomic-swap/coins"
contracts "github.com/athanorlabs/atomic-swap/ethereum"
"github.com/athanorlabs/atomic-swap/net/message"
)
@@ -23,12 +21,6 @@ const (
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")
// CreateRelayClaimRequest fills and returns a RelayClaimRequest ready for

View File

@@ -16,6 +16,7 @@ import (
ethcrypto "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/athanorlabs/atomic-swap/coins"
contracts "github.com/athanorlabs/atomic-swap/ethereum"
)
@@ -74,7 +75,7 @@ func createForwarderRequest(
secret *[32]byte,
) (*gsnforwarder.IForwarderForwardRequest, error) {
calldata, err := getClaimRelayerTxCalldata(FeeWei, swap, secret)
calldata, err := getClaimRelayerTxCalldata(coins.RelayerFeeWei, swap, secret)
if err != nil {
return nil, err
}

View File

@@ -60,7 +60,7 @@ func Test_ValidateAndSendTransaction(t *testing.T) {
require.NoError(t, err)
receipt, err := block.WaitForReceipt(ctx, ec.Raw(), tx.Hash())
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)
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)
require.NoError(t, err)
receipt, err = block.WaitForReceipt(ctx, ec.Raw(), tx.Hash())
t.Logf("gas cost to call SetReady: %d", receipt.GasUsed)
require.NoError(t, err)
require.GreaterOrEqual(t, contracts.MaxSetReadyGas, int(receipt.GasUsed))
secret := proof.Secret()

View File

@@ -65,9 +65,9 @@ func validateClaimValues(
}
// 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",
coins.FmtWeiAsETH(request.Swap.Value), coins.FmtWeiAsETH(FeeWei))
coins.FmtWeiAsETH(request.Swap.Value), coins.FmtWeiAsETH(coins.RelayerFeeWei))
}
return nil

View File

@@ -14,6 +14,7 @@ import (
"github.com/ethereum/go-ethereum/crypto"
"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/net/message"
@@ -35,17 +36,17 @@ func TestValidateRelayerFee(t *testing.T) {
testCases := []testCase{
{
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",
},
{
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",
},
{
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)),
},
}

View File

@@ -187,7 +187,7 @@ func (s *IntegrationTestSuite) testSuccessOneSwap(asset types.EthAsset, useRelay
defer cancel()
bwsc := s.newSwapdWSClient(ctx, defaultXMRMakerSwapdWSEndpoint)
min := coins.StrToDecimal("0.1")
min := coins.StrToDecimal("0.2")
offerResp, statusCh, err := bwsc.MakeOfferAndSubscribe(min, xmrmakerProvideAmount,
exchangeRate, asset, useRelayer)
require.NoError(s.T(), err)
@@ -529,7 +529,7 @@ func (s *IntegrationTestSuite) testAbortXMRTakerCancels(asset types.EthAsset) {
bwsc := s.newSwapdWSClient(ctx, defaultXMRMakerSwapdWSEndpoint)
min := coins.StrToDecimal("0.1")
min := coins.StrToDecimal("0.2")
offerResp, statusCh, err := bwsc.MakeOfferAndSubscribe(min, xmrmakerProvideAmount,
exchangeRate, asset, false)
require.NoError(s.T(), err)