mirror of
https://github.com/AthanorLabs/atomic-swap.git
synced 2026-01-08 21:58:07 -05:00
296 lines
7.9 KiB
Go
296 lines
7.9 KiB
Go
// Copyright 2023 The AthanorLabs/atomic-swap Authors
|
|
// SPDX-License-Identifier: LGPL-3.0-only
|
|
|
|
package main
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"os"
|
|
"path"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/cockroachdb/apd/v3"
|
|
ethcommon "github.com/ethereum/go-ethereum/common"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
"golang.org/x/sys/unix"
|
|
|
|
"github.com/athanorlabs/atomic-swap/coins"
|
|
"github.com/athanorlabs/atomic-swap/common"
|
|
"github.com/athanorlabs/atomic-swap/common/rpctypes"
|
|
"github.com/athanorlabs/atomic-swap/common/types"
|
|
"github.com/athanorlabs/atomic-swap/daemon"
|
|
contracts "github.com/athanorlabs/atomic-swap/ethereum"
|
|
"github.com/athanorlabs/atomic-swap/monero"
|
|
"github.com/athanorlabs/atomic-swap/rpcclient"
|
|
"github.com/athanorlabs/atomic-swap/tests"
|
|
)
|
|
|
|
func newTestContext(t *testing.T) (context.Context, context.CancelFunc) {
|
|
// The only external program any test in this package calls is monero-wallet-rpc, so we
|
|
// make monero-bin the only directory in our path.
|
|
curDir, err := os.Getwd()
|
|
require.NoError(t, err)
|
|
projectRoot := path.Dir(path.Dir(curDir)) // 2 dirs up from cmd/swaprecover
|
|
t.Setenv("PATH", path.Join(projectRoot, "monero-bin"))
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
t.Cleanup(func() {
|
|
cancel()
|
|
})
|
|
|
|
return ctx, cancel
|
|
}
|
|
|
|
func getFreePort(t *testing.T) uint16 {
|
|
port, err := common.GetFreeTCPPort()
|
|
require.NoError(t, err)
|
|
return uint16(port)
|
|
}
|
|
|
|
func TestDaemon_DevXMRTaker(t *testing.T) {
|
|
rpcPort := getFreePort(t)
|
|
dataDir := t.TempDir()
|
|
|
|
flags := []string{
|
|
"testSwapd",
|
|
fmt.Sprintf("--%s=dev", flagEnv),
|
|
fmt.Sprintf("--%s=debug", flagLogLevel),
|
|
fmt.Sprintf("--%s=true", flagDevXMRTaker),
|
|
fmt.Sprintf("--%s=true", flagDeploy),
|
|
fmt.Sprintf("--%s=%s", flagDataDir, dataDir),
|
|
fmt.Sprintf("--%s=%d", flagRPCPort, rpcPort),
|
|
fmt.Sprintf("--%s=0", flagLibp2pPort),
|
|
}
|
|
|
|
ctx, cancel := newTestContext(t)
|
|
|
|
var wg sync.WaitGroup
|
|
wg.Add(1)
|
|
|
|
go func() {
|
|
defer wg.Done()
|
|
err := cliApp().RunContext(ctx, flags)
|
|
assert.NoError(t, err)
|
|
}()
|
|
|
|
// Ensure the daemon fully before we query the contract address
|
|
daemon.WaitForSwapdStart(t, rpcPort)
|
|
|
|
cli := rpcclient.NewClient(ctx, fmt.Sprintf("http://127.0.0.1:%d", rpcPort))
|
|
versionResp, err := cli.Version()
|
|
require.NoError(t, err)
|
|
|
|
// We check the contract code below, but we don't need the daemon for that
|
|
cancel()
|
|
wg.Wait()
|
|
|
|
if t.Failed() {
|
|
return
|
|
}
|
|
|
|
ec, _ := tests.NewEthClient(t)
|
|
ecCtx := context.Background()
|
|
err = contracts.CheckSwapCreatorContractCode(ecCtx, ec, *versionResp.SwapCreatorAddr)
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
func TestDaemon_DevXMRMaker(t *testing.T) {
|
|
rpcPort := getFreePort(t)
|
|
key := tests.GetMakerTestKey(t)
|
|
ec, _ := tests.NewEthClient(t)
|
|
|
|
// We tested --deploy with the taker, so test passing the contract address here
|
|
swapCreatorAddr, err := deploySwapCreator(context.Background(), ec, key)
|
|
require.NoError(t, err)
|
|
|
|
flags := []string{
|
|
"testSwapd",
|
|
fmt.Sprintf("--%s", flagDevXMRMaker),
|
|
fmt.Sprintf("--%s=dev", flagEnv),
|
|
fmt.Sprintf("--%s=debug", flagLogLevel),
|
|
fmt.Sprintf("--%s=%s", flagContractAddress, swapCreatorAddr),
|
|
fmt.Sprintf("--%s=%s", flagDataDir, t.TempDir()),
|
|
fmt.Sprintf("--%s=%d", flagRPCPort, rpcPort),
|
|
fmt.Sprintf("--%s=0", flagLibp2pPort),
|
|
}
|
|
|
|
ctx, cancel := newTestContext(t)
|
|
|
|
var wg sync.WaitGroup
|
|
wg.Add(1)
|
|
|
|
go func() {
|
|
defer wg.Done()
|
|
err := cliApp().RunContext(ctx, flags)
|
|
assert.NoError(t, err)
|
|
}()
|
|
|
|
// Ensure the daemon fully started before we cancel the context
|
|
daemon.WaitForSwapdStart(t, rpcPort)
|
|
cancel()
|
|
|
|
wg.Wait()
|
|
}
|
|
|
|
func TestDaemon_BadFlags(t *testing.T) {
|
|
key := tests.GetMakerTestKey(t)
|
|
ec, _ := tests.NewEthClient(t)
|
|
ctx, _ := newTestContext(t)
|
|
|
|
swapCreatorAddr, err := deploySwapCreator(ctx, ec, key)
|
|
require.NoError(t, err)
|
|
|
|
baseFlags := []string{
|
|
"testSwapd",
|
|
fmt.Sprintf("--%s=dev", flagEnv),
|
|
fmt.Sprintf("--%s=debug", flagLogLevel),
|
|
fmt.Sprintf("--%s=%s", flagDataDir, t.TempDir()),
|
|
fmt.Sprintf("--%s=0", flagRPCPort),
|
|
fmt.Sprintf("--%s=0", flagLibp2pPort),
|
|
}
|
|
|
|
type testCase struct {
|
|
description string
|
|
extraFlags []string
|
|
expectErr string
|
|
}
|
|
|
|
testCases := []testCase{
|
|
{
|
|
description: "no contract and no request to deploy",
|
|
extraFlags: nil,
|
|
expectErr: `flag "deploy" or "contract-address" is required for env=dev`,
|
|
},
|
|
{
|
|
description: "pass invalid SwapCreator contract",
|
|
extraFlags: []string{
|
|
fmt.Sprintf("--%s=%s", flagContractAddress, ethcommon.Address{9}), // passing wrong contract
|
|
},
|
|
expectErr: "does not contain correct SwapCreator code",
|
|
},
|
|
{
|
|
description: "pass SwapCreator contract an invalid address (wrong length)",
|
|
extraFlags: []string{
|
|
fmt.Sprintf("--%s=%s", flagContractAddress, "0xFFFF"), // too short
|
|
},
|
|
expectErr: fmt.Sprintf(`"%s" requires a valid ethereum address`, flagContractAddress),
|
|
},
|
|
{
|
|
// this one also happens when people accidentally confuse swapd with swapcli
|
|
description: "forgot to prefix the flag name with dashes",
|
|
extraFlags: []string{
|
|
flagContractAddress, swapCreatorAddr.String(),
|
|
},
|
|
expectErr: fmt.Sprintf("unknown command %q", flagContractAddress),
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
func() { // so we can call defer inside loop
|
|
testCtx, cancel := context.WithTimeout(ctx, time.Second*15)
|
|
defer cancel()
|
|
flags := append(append([]string{}, baseFlags...), tc.extraFlags...)
|
|
err := cliApp().RunContext(testCtx, flags)
|
|
assert.ErrorContains(t, err, tc.expectErr, tc.description)
|
|
}()
|
|
}
|
|
}
|
|
|
|
func TestDaemon_PersistOffers(t *testing.T) {
|
|
dataDir := t.TempDir()
|
|
walletDir := path.Join(dataDir, "wallet")
|
|
|
|
defer func() {
|
|
// CI has issues with the filesystem still being written to when it is
|
|
// recursively deleting dataDir. Can't be replicated outside of CI.
|
|
unix.Sync()
|
|
time.Sleep(500 * time.Millisecond)
|
|
}()
|
|
|
|
wc := monero.CreateWalletClientWithWalletDir(t, walletDir)
|
|
one := apd.New(1, 0)
|
|
monero.MineMinXMRBalance(t, wc, coins.MoneroToPiconero(one))
|
|
walletName := wc.WalletName()
|
|
wc.Close() // wallet file stays in place with mined monero
|
|
|
|
rpcPort := getFreePort(t)
|
|
rpcEndpoint := fmt.Sprintf("http://127.0.0.1:%d", rpcPort)
|
|
|
|
flags := []string{
|
|
"testSwapd",
|
|
fmt.Sprintf("--%s", flagDevXMRMaker),
|
|
fmt.Sprintf("--%s=dev", flagEnv),
|
|
fmt.Sprintf("--%s=debug", flagLogLevel),
|
|
fmt.Sprintf("--%s", flagDeploy),
|
|
fmt.Sprintf("--%s=%s", flagDataDir, dataDir),
|
|
fmt.Sprintf("--%s=%d", flagRPCPort, rpcPort),
|
|
fmt.Sprintf("--%s=0", flagLibp2pPort),
|
|
fmt.Sprintf("--%s=%s", flagMoneroWalletPath, path.Join(walletDir, walletName)),
|
|
}
|
|
|
|
ctx1, cancel1 := newTestContext(t)
|
|
|
|
var wg1 sync.WaitGroup
|
|
wg1.Add(1)
|
|
|
|
go func() {
|
|
defer wg1.Done()
|
|
err := cliApp().RunContext(ctx1, flags)
|
|
assert.NoError(t, err)
|
|
t.Logf("initial swapd instance exited")
|
|
}()
|
|
|
|
daemon.WaitForSwapdStart(t, rpcPort)
|
|
if t.Failed() {
|
|
return
|
|
}
|
|
|
|
// make an offer
|
|
client := rpcclient.NewClient(ctx1, rpcEndpoint)
|
|
balance, err := client.Balances(new(rpctypes.BalancesRequest))
|
|
require.NoError(t, err)
|
|
require.GreaterOrEqual(t, balance.PiconeroUnlockedBalance.Cmp(coins.MoneroToPiconero(one)), 0)
|
|
|
|
minXMRAmt := coins.StrToDecimal("0.1")
|
|
maxXMRAmt := one
|
|
xRate := coins.ToExchangeRate(one)
|
|
|
|
offerResp, err := client.MakeOffer(minXMRAmt, maxXMRAmt, xRate, types.EthAssetETH, false)
|
|
require.NoError(t, err)
|
|
|
|
// shut down the daemon to verify that the offer still exists on restart
|
|
t.Logf("shutting down initial swapd instance")
|
|
cancel1()
|
|
wg1.Wait()
|
|
|
|
// restart daemon
|
|
t.Log("restarting daemon")
|
|
ctx2, cancel2 := newTestContext(t)
|
|
|
|
var wg2 sync.WaitGroup
|
|
wg2.Add(1)
|
|
t.Cleanup(func() {
|
|
cancel2()
|
|
wg2.Wait()
|
|
})
|
|
|
|
go func() {
|
|
defer wg2.Done()
|
|
err := cliApp().RunContext(ctx2, flags) //nolint:govet
|
|
assert.NoError(t, err)
|
|
}()
|
|
|
|
daemon.WaitForSwapdStart(t, rpcPort)
|
|
|
|
client = rpcclient.NewClient(ctx2, rpcEndpoint)
|
|
resp, err := client.GetOffers()
|
|
require.NoError(t, err)
|
|
require.Equal(t, offerResp.PeerID, resp.PeerID)
|
|
require.Equal(t, 1, len(resp.Offers))
|
|
require.Equal(t, offerResp.OfferID, resp.Offers[0].ID)
|
|
}
|