mirror of
https://github.com/AthanorLabs/atomic-swap.git
synced 2026-01-06 21:13:50 -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
|
||||
}
|
||||
|
||||
func TestRunIntegrationTests(t *testing.T) {
|
||||
func TestRunSwapcliWithDaemonTests(t *testing.T) {
|
||||
s := new(swapCLITestSuite)
|
||||
|
||||
s.conf = daemon.CreateTestConf(t, tests.GetMakerTestKey(t))
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
@@ -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"`),
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
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"
|
||||
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])
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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'),
|
||||
)
|
||||
}
|
||||
|
||||
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
|
||||
)
|
||||
|
||||
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'),
|
||||
)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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,
|
||||
)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)),
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user