Simulate relayer tx and check return value (#395)

Co-authored-by: Dmitry Holodov <dimalinux@protonmail.com>
This commit is contained in:
Matt
2023-04-21 17:47:10 +02:00
committed by GitHub
parent 0a90f6bd4d
commit dda4cacaea
3 changed files with 200 additions and 195 deletions

View File

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

View File

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

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