Files
atomic-swap/ethereum/swap_creator_test.go
2023-06-17 04:30:20 -05:00

630 lines
20 KiB
Go

// Copyright 2023 The AthanorLabs/atomic-swap Authors
// SPDX-License-Identifier: LGPL-3.0-only
package contracts
import (
"context"
"crypto/ecdsa"
"crypto/rand"
"encoding/hex"
"math/big"
"sync"
"testing"
"time"
"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"
"github.com/athanorlabs/atomic-swap/common"
"github.com/athanorlabs/atomic-swap/common/types"
"github.com/athanorlabs/atomic-swap/crypto/secp256k1"
"github.com/athanorlabs/atomic-swap/dleq"
"github.com/athanorlabs/atomic-swap/ethereum/block"
"github.com/athanorlabs/atomic-swap/tests"
)
var (
defaultTimeoutDuration = big.NewInt(60) // 60 seconds
defaultSwapValue = big.NewInt(100)
dummySwapKey = [32]byte{1} // dummy non-zero value for claim/refund key
)
func getAuth(t *testing.T, pk *ecdsa.PrivateKey) *bind.TransactOpts {
txOpts, err := bind.NewKeyedTransactorWithChainID(pk, big.NewInt(common.GanacheChainID))
require.NoError(t, err)
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 receipt
}
func approveERC20(t *testing.T,
ec *ethclient.Client,
pk *ecdsa.PrivateKey,
erc20Contract *TestERC20,
swapCreatorAddress ethcommon.Address,
value *big.Int,
) {
require.NotNil(t, erc20Contract)
tx, err := erc20Contract.Approve(getAuth(t, pk), swapCreatorAddress, value)
require.NoError(t, err)
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 types.EthAsset, erc20Contract *TestERC20) {
pk := tests.GetTakerTestKey(t)
ec, _ := tests.NewEthClient(t)
swapCreatorAddr, swapCreator := DevDeploySwapCreator(t, ec, pk)
owner := crypto.PubkeyToAddress(pk.PublicKey)
claimer := common.EthereumPrivateKeyToAddress(tests.GetMakerTestKey(t))
var claimCommitment, refundCommitment [32]byte
_, err := rand.Read(claimCommitment[:])
require.NoError(t, err)
_, err = rand.Read(refundCommitment[:])
require.NoError(t, err)
nonce, err := rand.Prime(rand.Reader, 256)
require.NoError(t, err)
txOpts := getAuth(t, pk)
value := defaultSwapValue
if asset.IsETH() {
txOpts.Value = value
} else {
approveERC20(t, ec, pk, erc20Contract, swapCreatorAddr, value)
}
tx, err := swapCreator.NewSwap(
txOpts,
claimCommitment,
refundCommitment,
claimer,
defaultTimeoutDuration,
defaultTimeoutDuration,
asset.Address(),
value,
nonce,
)
require.NoError(t, err)
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 asset.IsToken() {
newSwapLogIndex = 2
}
require.Equal(t, newSwapLogIndex+1, len(receipt.Logs))
swapID, err := GetIDFromLog(receipt.Logs[newSwapLogIndex])
require.NoError(t, err)
t1, t2, err := GetTimeoutsFromLog(receipt.Logs[newSwapLogIndex])
require.NoError(t, err)
// validate that off-chain swapID calculation matches the on-chain value
swap := SwapCreatorSwap{
Owner: owner,
Claimer: claimer,
ClaimCommitment: claimCommitment,
RefundCommitment: refundCommitment,
Timeout1: t1,
Timeout2: t2,
Asset: asset.Address(),
Value: value,
Nonce: nonce,
}
// validate our off-net calculation of the SwapID
require.Equal(t, types.Hash(swapID).Hex(), swap.SwapID().Hex())
}
func TestSwapCreator_NewSwap(t *testing.T) {
testNewSwap(t, types.EthAssetETH, nil)
}
func TestSwapCreator_Claim_vec(t *testing.T) {
secret, err := hex.DecodeString("D30519BCAE8D180DBFCC94FE0B8383DC310185B0BE97B4365083EBCECCD75759")
require.NoError(t, err)
pubX, err := hex.DecodeString("3AF1E1EFA4D1E1AD5CB9E3967E98E901DAFCD37C44CF0BFB6C216997F5EE51DF")
require.NoError(t, err)
pubY, err := hex.DecodeString("E4ACAC3E6F139E0C7DB2BD736824F51392BDA176965A1C59EB9C3C5FF9E85D7A")
require.NoError(t, err)
var s, x, y [32]byte
copy(s[:], secret)
copy(x[:], pubX)
copy(y[:], pubY)
pk := secp256k1.NewPublicKey(x, y)
cmt := pk.Keccak256()
// deploy swap contract with claim key hash
pkA := tests.GetTakerTestKey(t)
ec, _ := tests.NewEthClient(t)
addr := crypto.PubkeyToAddress(pkA.PublicKey)
_, swapCreator := DevDeploySwapCreator(t, ec, pkA)
txOpts := getAuth(t, pkA)
txOpts.Value = defaultSwapValue
nonce := big.NewInt(0)
tx, err := swapCreator.NewSwap(txOpts, cmt, dummySwapKey, addr, defaultTimeoutDuration,
defaultTimeoutDuration, ethcommon.Address(types.EthAssetETH), defaultSwapValue, nonce)
require.NoError(t, err)
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])
require.NoError(t, err)
t1, t2, err := GetTimeoutsFromLog(receipt.Logs[0])
require.NoError(t, err)
swap := SwapCreatorSwap{
Owner: addr,
Claimer: addr,
ClaimCommitment: cmt,
RefundCommitment: dummySwapKey,
Timeout1: t1,
Timeout2: t2,
Asset: ethcommon.Address(types.EthAssetETH),
Value: defaultSwapValue,
Nonce: nonce,
}
// set contract to Ready
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
tx, err = swapCreator.Claim(getAuth(t, pkA), swap, s)
require.NoError(t, err)
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 := swapCreator.Swaps(nil, id)
require.NoError(t, err)
require.Equal(t, StageCompleted, stage)
}
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()
require.NoError(t, err)
res, err := dleq.Verify(proof)
require.NoError(t, err)
// hash public key
cmt := res.Secp256k1PublicKey().Keccak256()
// deploy swap contract with claim key hash
pkA := tests.GetTakerTestKey(t)
ec, _ := tests.NewEthClient(t)
addr := crypto.PubkeyToAddress(pkA.PublicKey)
swapCreatorAddr, swapCreator := DevDeploySwapCreator(t, ec, pkA)
if asset.IsToken() {
approveERC20(t, ec, pkA, erc20Contract, swapCreatorAddr, value)
}
txOpts := getAuth(t, pkA)
require.NoError(t, err)
if asset.IsETH() {
txOpts.Value = value
}
nonce := GenerateNewSwapNonce()
tx, err := swapCreator.NewSwap(txOpts, cmt, dummySwapKey, addr,
defaultTimeoutDuration, defaultTimeoutDuration, asset.Address(), value, nonce)
require.NoError(t, err)
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])
require.NoError(t, err)
t1, t2, err := GetTimeoutsFromLog(receipt.Logs[newLogIndex])
require.NoError(t, err)
swap := SwapCreatorSwap{
Owner: addr,
Claimer: addr,
ClaimCommitment: cmt,
RefundCommitment: dummySwapKey,
Timeout1: t1,
Timeout2: t2,
Asset: asset.Address(),
Value: value,
Nonce: nonce,
}
// ensure we can't claim before setting contract to Ready
_, err = swapCreator.Claim(getAuth(t, pkA), swap, proof.Secret())
require.ErrorContains(t, err, "VM Exception while processing transaction: revert")
// set contract to Ready
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
tx, err = swapCreator.Claim(getAuth(t, pkA), swap, proof.Secret())
require.NoError(t, err)
receipt = getReceipt(t, ec, tx)
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, types.EthAssetETH, 0, defaultSwapValue, nil)
}
func testRefundBeforeT1(t *testing.T, asset types.EthAsset, erc20Contract *TestERC20, newLogIndex int) {
// generate refund secret and public key
dleq := &dleq.DefaultDLEq{}
proof, err := dleq.Prove()
require.NoError(t, err)
res, err := dleq.Verify(proof)
require.NoError(t, err)
// hash public key
cmt := res.Secp256k1PublicKey().Keccak256()
// deploy swap contract with refund key hash
pkA := tests.GetTakerTestKey(t)
ec, _ := tests.NewEthClient(t)
addr := crypto.PubkeyToAddress(pkA.PublicKey)
swapCreatorAddr, swapCreator := DevDeploySwapCreator(t, ec, pkA)
if asset.IsToken() {
approveERC20(t, ec, pkA, erc20Contract, swapCreatorAddr, defaultSwapValue)
}
txOpts := getAuth(t, pkA)
txOpts.Value = defaultSwapValue
nonce := GenerateNewSwapNonce()
tx, err := swapCreator.NewSwap(txOpts, dummySwapKey, cmt, addr, defaultTimeoutDuration, defaultTimeoutDuration,
asset.Address(), defaultSwapValue, nonce)
require.NoError(t, err)
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])
require.NoError(t, err)
t1, t2, err := GetTimeoutsFromLog(receipt.Logs[newLogIndex])
require.NoError(t, err)
swap := SwapCreatorSwap{
Owner: addr,
Claimer: addr,
ClaimCommitment: dummySwapKey,
RefundCommitment: cmt,
Timeout1: t1,
Timeout2: t2,
Asset: asset.Address(),
Value: defaultSwapValue,
Nonce: nonce,
}
// now let's try to refund
tx, err = swapCreator.Refund(getAuth(t, pkA), swap, proof.Secret())
require.NoError(t, err)
receipt = getReceipt(t, ec, tx)
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_beforeT1(t *testing.T) {
testRefundBeforeT1(t, types.EthAssetETH, nil, 0)
}
func testRefundAfterT2(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()
require.NoError(t, err)
res, err := dleq.Verify(proof)
require.NoError(t, err)
// hash public key
cmt := res.Secp256k1PublicKey().Keccak256()
// deploy swap contract with refund key hash
pkA := tests.GetTakerTestKey(t)
ec, _ := tests.NewEthClient(t)
addr := crypto.PubkeyToAddress(pkA.PublicKey)
swapCreatorAddr, swapCreator := DevDeploySwapCreator(t, ec, pkA)
if asset.IsToken() {
approveERC20(t, ec, pkA, erc20Contract, swapCreatorAddr, defaultSwapValue)
}
txOpts := getAuth(t, pkA)
txOpts.Value = defaultSwapValue
nonce := GenerateNewSwapNonce()
timeout := big.NewInt(3)
tx, err := swapCreator.NewSwap(txOpts, dummySwapKey, cmt, addr, timeout, timeout,
asset.Address(), defaultSwapValue, nonce)
require.NoError(t, err)
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])
require.NoError(t, err)
t1, t2, err := GetTimeoutsFromLog(receipt.Logs[newLogIndex])
require.NoError(t, err)
// ensure we can't refund between T1 and T2
<-time.After(time.Until(time.Unix(t1.Int64()+1, 0)))
swap := SwapCreatorSwap{
Owner: addr,
Claimer: addr,
ClaimCommitment: dummySwapKey,
RefundCommitment: cmt,
Timeout1: t1,
Timeout2: t2,
Asset: asset.Address(),
Value: defaultSwapValue,
Nonce: nonce,
}
secret := proof.Secret()
tx, err = swapCreator.Refund(getAuth(t, pkA), swap, secret)
require.NoError(t, err)
_, err = block.WaitForReceipt(ctx, ec, tx.Hash())
require.ErrorContains(t, err, "VM Exception while processing transaction: revert")
<-time.After(time.Until(time.Unix(t2.Int64()+1, 0)))
// now let's try to refund
tx, err = swapCreator.Refund(getAuth(t, pkA), swap, secret)
require.NoError(t, err)
receipt = getReceipt(t, ec, tx)
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")
}
callOpts := &bind.CallOpts{Context: ctx}
stage, err := swapCreator.Swaps(callOpts, id)
require.NoError(t, err)
require.Equal(t, StageCompleted, stage)
}
func TestSwapCreator_Refund_afterT2(t *testing.T) {
testRefundAfterT2(t, types.EthAssetETH, nil, 0)
}
// test case where contract has multiple swaps happening at once
func TestSwapCreator_MultipleSwaps(t *testing.T) {
pkContractCreator := tests.GetTestKeyByIndex(t, 0)
ec, _ := tests.NewEthClient(t)
_, swapCreator := DevDeploySwapCreator(t, ec, pkContractCreator)
const numSwaps = 16
type swapCase struct {
index int // index in the swap array
walletKey *ecdsa.PrivateKey
id [32]byte
secret [32]byte
swap SwapCreatorSwap
}
swapCases := [numSwaps]swapCase{}
// setup all swap instances
for i := 0; i < numSwaps; i++ {
sc := &swapCases[i]
sc.index = i
// generate claim secret and public key
dleq := &dleq.DefaultDLEq{}
proof, err := dleq.Prove()
require.NoError(t, err)
res, err := dleq.Verify(proof)
require.NoError(t, err)
sc.secret = proof.Secret()
sc.walletKey = tests.GetTestKeyByIndex(t, i)
addrSwap := crypto.PubkeyToAddress(*sc.walletKey.Public().(*ecdsa.PublicKey))
sc.swap = SwapCreatorSwap{
Owner: addrSwap,
Claimer: addrSwap,
ClaimCommitment: res.Secp256k1PublicKey().Keccak256(),
RefundCommitment: dummySwapKey, // no one calls refund in this test
Timeout1: nil, // timeouts initialised when swap is created
Timeout2: nil,
Asset: ethcommon.Address(types.EthAssetETH),
Value: defaultSwapValue,
Nonce: big.NewInt(int64(i)),
}
}
// We create all transactions in parallel, so the transactions of each swap stage can get bundled up
// into one or two blocks and greatly speed up the test.
var wg sync.WaitGroup
// create swap instances
wg.Add(numSwaps)
for i := 0; i < numSwaps; i++ {
go func(sc *swapCase) {
defer wg.Done()
auth := getAuth(t, sc.walletKey)
auth.Value = sc.swap.Value
tx, err := swapCreator.NewSwap(
auth,
sc.swap.ClaimCommitment,
sc.swap.RefundCommitment,
sc.swap.Claimer,
defaultTimeoutDuration,
defaultTimeoutDuration,
types.EthAssetETH.Address(),
sc.swap.Value,
sc.swap.Nonce,
)
require.NoError(t, err)
receipt := getReceipt(t, ec, tx)
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])
require.NoError(t, err)
sc.swap.Timeout1, sc.swap.Timeout2, err = GetTimeoutsFromLog(receipt.Logs[0])
require.NoError(t, err)
}(&swapCases[i])
}
wg.Wait() // all swaps created
// set all swaps to Ready
wg.Add(numSwaps)
for i := 0; i < numSwaps; i++ {
go func(sc *swapCase) {
defer wg.Done()
tx, err := swapCreator.SetReady(getAuth(t, sc.walletKey), sc.swap)
require.NoError(t, err)
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
// call claim on all the swaps
wg.Add(numSwaps)
for i := 0; i < numSwaps; i++ {
go func(sc *swapCase) {
defer wg.Done()
tx, err := swapCreator.Claim(getAuth(t, sc.walletKey), sc.swap, sc.secret)
require.NoError(t, err)
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
// ensure all swaps are completed
wg.Add(numSwaps)
for i := 0; i < numSwaps; i++ {
go func(sc *swapCase) {
defer wg.Done()
stage, err := swapCreator.Swaps(nil, sc.id)
require.NoError(t, err)
require.Equal(t, StageToString(StageCompleted), StageToString(stage))
}(&swapCases[i])
}
wg.Wait() // status of all swaps checked
}