mirror of
https://github.com/AthanorLabs/atomic-swap.git
synced 2026-01-08 21:58:07 -05:00
Simulate relayer tx and check return value (#395)
Co-authored-by: Dmitry Holodov <dimalinux@protonmail.com>
This commit is contained in:
@@ -1,195 +0,0 @@
|
||||
// Copyright 2023 The AthanorLabs/atomic-swap Authors
|
||||
// SPDX-License-Identifier: LGPL-3.0-only
|
||||
|
||||
package xmrmaker
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/ecdsa"
|
||||
"math/big"
|
||||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/athanorlabs/go-relayer/impls/gsnforwarder"
|
||||
|
||||
"github.com/athanorlabs/atomic-swap/common/types"
|
||||
"github.com/athanorlabs/atomic-swap/dleq"
|
||||
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/relayer"
|
||||
"github.com/athanorlabs/atomic-swap/tests"
|
||||
)
|
||||
|
||||
var (
|
||||
defaultTestTimeoutDuration = big.NewInt(60 * 5)
|
||||
)
|
||||
|
||||
func TestSwapState_ClaimRelayer_ERC20(t *testing.T) {
|
||||
t.Skip("Claiming ERC20 tokens via relayer is not yet supported")
|
||||
|
||||
initialBalance := big.NewInt(90000000000000000)
|
||||
|
||||
sk := tests.GetMakerTestKey(t)
|
||||
conn, chainID := tests.NewEthClient(t)
|
||||
|
||||
pub := sk.Public().(*ecdsa.PublicKey)
|
||||
addr := crypto.PubkeyToAddress(*pub)
|
||||
|
||||
txOpts, err := bind.NewKeyedTransactorWithChainID(sk, chainID)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, tx, _, err := contracts.DeployTestERC20(
|
||||
txOpts,
|
||||
conn,
|
||||
"Mock",
|
||||
"MOCK",
|
||||
18,
|
||||
addr,
|
||||
initialBalance,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
contractAddr, err := bind.WaitDeployed(context.Background(), conn, tx)
|
||||
require.NoError(t, err)
|
||||
|
||||
testSwapStateClaimRelayer(t, sk, types.EthAsset(contractAddr))
|
||||
}
|
||||
|
||||
func TestSwapState_ClaimRelayer_ETH(t *testing.T) {
|
||||
sk := tests.GetMakerTestKey(t)
|
||||
testSwapStateClaimRelayer(t, sk, types.EthAssetETH)
|
||||
}
|
||||
|
||||
func testSwapStateClaimRelayer(t *testing.T, sk *ecdsa.PrivateKey, asset types.EthAsset) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
ec := extethclient.CreateTestClient(t, sk)
|
||||
txOpts, err := ec.TxOpts(ctx)
|
||||
require.NoError(t, err)
|
||||
|
||||
// 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 of claim secret
|
||||
cmt := res.Secp256k1PublicKey().Keccak256()
|
||||
|
||||
pub := sk.Public().(*ecdsa.PublicKey)
|
||||
addr := crypto.PubkeyToAddress(*pub)
|
||||
|
||||
// deploy forwarder
|
||||
forwarderAddr, tx, forwarderContract, err := gsnforwarder.DeployForwarder(txOpts, ec.Raw())
|
||||
require.NoError(t, err)
|
||||
receipt, err := block.WaitForReceipt(ctx, ec.Raw(), tx.Hash())
|
||||
require.NoError(t, err)
|
||||
t.Logf("gas cost to deploy Forwarder.sol: %d", receipt.GasUsed)
|
||||
|
||||
tx, err = forwarderContract.RegisterDomainSeparator(txOpts, gsnforwarder.DefaultName, gsnforwarder.DefaultVersion)
|
||||
require.NoError(t, err)
|
||||
receipt, err = block.WaitForReceipt(ctx, ec.Raw(), tx.Hash())
|
||||
require.NoError(t, err)
|
||||
t.Logf("gas cost to call RegisterDomainSeparator: %d", receipt.GasUsed)
|
||||
|
||||
// deploy swap contract with claim key hash
|
||||
contractAddr, tx, contract, err := contracts.DeploySwapCreator(txOpts, ec.Raw(), forwarderAddr)
|
||||
require.NoError(t, err)
|
||||
receipt, err = block.WaitForReceipt(ctx, ec.Raw(), tx.Hash())
|
||||
require.NoError(t, err)
|
||||
t.Logf("gas cost to deploy SwapCreator.sol: %d", receipt.GasUsed)
|
||||
|
||||
if asset != types.EthAssetETH {
|
||||
token, err := contracts.NewIERC20(asset.Address(), ec.Raw()) //nolint:govet
|
||||
require.NoError(t, err)
|
||||
|
||||
balance, err := token.BalanceOf(&bind.CallOpts{}, addr)
|
||||
require.NoError(t, err)
|
||||
|
||||
tx, err = token.Approve(txOpts, contractAddr, balance)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = block.WaitForReceipt(ctx, ec.Raw(), tx.Hash())
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
value := big.NewInt(90000000000000000)
|
||||
nonce := big.NewInt(0)
|
||||
txOpts.Value = value
|
||||
|
||||
tx, err = contract.NewSwap(txOpts, cmt, [32]byte{}, addr,
|
||||
defaultTestTimeoutDuration, defaultTestTimeoutDuration, asset.Address(), value, nonce)
|
||||
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)
|
||||
txOpts.Value = big.NewInt(0)
|
||||
|
||||
logIndex := 0
|
||||
if asset != types.EthAssetETH {
|
||||
logIndex = 2
|
||||
}
|
||||
|
||||
require.Equal(t, logIndex+1, len(receipt.Logs))
|
||||
id, err := contracts.GetIDFromLog(receipt.Logs[logIndex])
|
||||
require.NoError(t, err)
|
||||
|
||||
t0, t1, err := contracts.GetTimeoutsFromLog(receipt.Logs[logIndex])
|
||||
require.NoError(t, err)
|
||||
|
||||
swap := &contracts.SwapCreatorSwap{
|
||||
Owner: addr,
|
||||
Claimer: addr,
|
||||
PubKeyClaim: cmt,
|
||||
PubKeyRefund: [32]byte{},
|
||||
Timeout0: t0,
|
||||
Timeout1: t1,
|
||||
Asset: asset.Address(),
|
||||
Value: value,
|
||||
Nonce: nonce,
|
||||
}
|
||||
|
||||
// set contract to Ready
|
||||
tx, err = contract.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)
|
||||
|
||||
secret := proof.Secret()
|
||||
|
||||
// now let's try to claim
|
||||
req, err := relayer.CreateRelayClaimRequest(
|
||||
ctx,
|
||||
sk,
|
||||
ec.Raw(),
|
||||
contractAddr,
|
||||
forwarderAddr,
|
||||
swap,
|
||||
&secret,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
resp, err := relayer.ValidateAndSendTransaction(ctx, req, ec, contractAddr)
|
||||
require.NoError(t, err)
|
||||
|
||||
receipt, err = block.WaitForReceipt(ctx, ec.Raw(), resp.TxHash)
|
||||
require.NoError(t, err)
|
||||
t.Logf("gas cost to call Claim via relayer: %d", receipt.GasUsed)
|
||||
|
||||
if asset != types.EthAssetETH {
|
||||
require.Equal(t, 3, len(receipt.Logs))
|
||||
} else {
|
||||
// expected 1 Claimed log
|
||||
require.Equal(t, 1, len(receipt.Logs))
|
||||
}
|
||||
|
||||
stage, err := contract.Swaps(nil, id)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, contracts.StageCompleted, stage)
|
||||
}
|
||||
@@ -5,12 +5,15 @@ package relayer
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
|
||||
"github.com/athanorlabs/go-relayer/impls/gsnforwarder"
|
||||
"github.com/ethereum/go-ethereum"
|
||||
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
||||
ethcommon "github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
|
||||
"github.com/athanorlabs/atomic-swap/coins"
|
||||
"github.com/athanorlabs/atomic-swap/common"
|
||||
@@ -76,6 +79,19 @@ func ValidateAndSendTransaction(
|
||||
}
|
||||
txOpts.GasPrice = gasPrice
|
||||
|
||||
err = simulateExecute(
|
||||
ctx,
|
||||
ec,
|
||||
&reqForwarderAddr,
|
||||
txOpts,
|
||||
*forwarderReq,
|
||||
*domainSeparator,
|
||||
req.Signature,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tx, err := reqForwarder.Execute(
|
||||
txOpts,
|
||||
*forwarderReq,
|
||||
@@ -119,3 +135,68 @@ func checkForMinClaimBalance(ctx context.Context, ec extethclient.EthClient) (*b
|
||||
|
||||
return gasPrice, nil
|
||||
}
|
||||
|
||||
// simulateExecute calls the forwarder's execute method (defined in Forwarder.sol)
|
||||
// with CallContract which executes the method call without mining it into the blockchain.
|
||||
// https://pkg.go.dev/github.com/ethereum/go-ethereum/ethclient#Client.CallContract
|
||||
func simulateExecute(
|
||||
ctx context.Context,
|
||||
ec extethclient.EthClient,
|
||||
reqForwarderAddr *ethcommon.Address,
|
||||
txOpts *bind.TransactOpts,
|
||||
forwarderReq gsnforwarder.IForwarderForwardRequest,
|
||||
domainSeparator [32]byte,
|
||||
sig []byte,
|
||||
) error {
|
||||
forwarderABI, err := gsnforwarder.ForwarderMetaData.GetAbi()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Pack the "execute" method call
|
||||
packed, err := forwarderABI.Pack(
|
||||
"execute",
|
||||
forwarderReq,
|
||||
domainSeparator,
|
||||
gsnforwarder.ForwardRequestTypehash,
|
||||
[]byte{},
|
||||
sig,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
callMessage := ethereum.CallMsg{
|
||||
From: txOpts.From,
|
||||
To: reqForwarderAddr,
|
||||
Gas: txOpts.GasLimit,
|
||||
GasPrice: txOpts.GasPrice,
|
||||
GasFeeCap: txOpts.GasFeeCap,
|
||||
GasTipCap: txOpts.GasTipCap,
|
||||
Value: txOpts.Value,
|
||||
Data: packed,
|
||||
AccessList: []types.AccessTuple{},
|
||||
}
|
||||
|
||||
// Call the "execute" method
|
||||
data, err := ec.Raw().CallContract(ctx, callMessage, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Unpack the response data
|
||||
response := struct {
|
||||
Success bool
|
||||
Ret []byte
|
||||
}{Success: false, Ret: []byte{}}
|
||||
|
||||
err = forwarderABI.UnpackIntoInterface(&response, "execute", data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !response.Success {
|
||||
return errors.New("relayed transaction failed on simulation")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
119
relayer/submit_transaction_test.go
Normal file
119
relayer/submit_transaction_test.go
Normal file
@@ -0,0 +1,119 @@
|
||||
// Copyright 2023 The AthanorLabs/atomic-swap Authors
|
||||
// SPDX-License-Identifier: LGPL-3.0-only
|
||||
|
||||
package relayer
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/ecdsa"
|
||||
"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/dleq"
|
||||
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 Test_ValidateAndSendTransaction(t *testing.T) {
|
||||
sk := tests.GetMakerTestKey(t)
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
ec := extethclient.CreateTestClient(t, sk)
|
||||
txOpts, err := ec.TxOpts(ctx)
|
||||
require.NoError(t, err)
|
||||
|
||||
// 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 of claim secret
|
||||
cmt := res.Secp256k1PublicKey().Keccak256()
|
||||
|
||||
pub := sk.Public().(*ecdsa.PublicKey)
|
||||
addr := crypto.PubkeyToAddress(*pub)
|
||||
|
||||
swapCreatorAddr, forwarderAddr := deployContracts(t, ec.Raw(), sk)
|
||||
|
||||
swapCreator, err := contracts.NewSwapCreator(swapCreatorAddr, ec.Raw())
|
||||
require.NoError(t, err)
|
||||
|
||||
testT0Timeout := big.NewInt(300) // 5 minutes
|
||||
testT1Timeout := testT0Timeout
|
||||
|
||||
value := big.NewInt(9e16)
|
||||
nonce := big.NewInt(0)
|
||||
txOpts.Value = value
|
||||
|
||||
tx, err := swapCreator.NewSwap(txOpts, cmt, [32]byte{}, addr,
|
||||
testT0Timeout, testT1Timeout, types.EthAssetETH.Address(), value, nonce)
|
||||
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)
|
||||
txOpts.Value = big.NewInt(0)
|
||||
|
||||
logIndex := 0 // change to 2 for ERC20, but ERC20 swaps cannot use the relayer
|
||||
require.Equal(t, logIndex+1, len(receipt.Logs))
|
||||
id, err := contracts.GetIDFromLog(receipt.Logs[logIndex])
|
||||
require.NoError(t, err)
|
||||
|
||||
t0, t1, err := contracts.GetTimeoutsFromLog(receipt.Logs[logIndex])
|
||||
require.NoError(t, err)
|
||||
|
||||
swap := &contracts.SwapCreatorSwap{
|
||||
Owner: addr,
|
||||
Claimer: addr,
|
||||
PubKeyClaim: cmt,
|
||||
PubKeyRefund: [32]byte{},
|
||||
Timeout0: t0,
|
||||
Timeout1: t1,
|
||||
Asset: types.EthAssetETH.Address(),
|
||||
Value: value,
|
||||
Nonce: nonce,
|
||||
}
|
||||
|
||||
// set contract to Ready
|
||||
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)
|
||||
|
||||
secret := proof.Secret()
|
||||
|
||||
// now let's try to claim
|
||||
req, err := CreateRelayClaimRequest(ctx, sk, ec.Raw(), swapCreatorAddr, forwarderAddr, swap, &secret)
|
||||
require.NoError(t, err)
|
||||
|
||||
resp, err := ValidateAndSendTransaction(ctx, req, ec, swapCreatorAddr)
|
||||
require.NoError(t, err)
|
||||
|
||||
receipt, err = block.WaitForReceipt(ctx, ec.Raw(), resp.TxHash)
|
||||
require.NoError(t, err)
|
||||
t.Logf("gas cost to call Claim via relayer: %d", receipt.GasUsed)
|
||||
|
||||
// expected 1 Claimed log (ERC20 swaps have 3, but we don't support relaying with ERC20 swaps)
|
||||
require.Equal(t, 1, len(receipt.Logs))
|
||||
|
||||
stage, err := swapCreator.Swaps(nil, id)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, contracts.StageCompleted, stage)
|
||||
|
||||
// Now lets try to claim a second time and verify that we fail on the simulated
|
||||
// execution.
|
||||
req, err = CreateRelayClaimRequest(ctx, sk, ec.Raw(), swapCreatorAddr, forwarderAddr, swap, &secret)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = ValidateAndSendTransaction(ctx, req, ec, swapCreatorAddr)
|
||||
require.ErrorContains(t, err, "relayed transaction failed on simulation")
|
||||
}
|
||||
Reference in New Issue
Block a user