mirror of
https://github.com/AthanorLabs/atomic-swap.git
synced 2026-01-08 21:58:07 -05:00
feat: update SwapCreater.sol claimRelayer to no longer use forwarder (#449)
Co-authored-by: Dmitry Holodov <dimalinux@protonmail.com>
This commit is contained in:
@@ -31,7 +31,6 @@ var (
|
||||
|
||||
type contractAddresses struct {
|
||||
SwapCreatorAddr ethcommon.Address `json:"swapCreatorAddr" validate:"required"`
|
||||
ForwarderAddr ethcommon.Address `json:"forwarderAddr" validate:"required"`
|
||||
}
|
||||
|
||||
func getOrDeploySwapCreator(
|
||||
@@ -40,7 +39,6 @@ func getOrDeploySwapCreator(
|
||||
env common.Environment,
|
||||
dataDir string,
|
||||
ec extethclient.EthClient,
|
||||
forwarderAddr ethcommon.Address,
|
||||
) (ethcommon.Address, error) {
|
||||
var err error
|
||||
if (swapCreatorAddr == ethcommon.Address{}) {
|
||||
@@ -49,7 +47,7 @@ func getOrDeploySwapCreator(
|
||||
time.Sleep(10 * time.Second)
|
||||
}
|
||||
|
||||
swapCreatorAddr, _, err = deploySwapCreator(ctx, ec.Raw(), ec.PrivateKey(), forwarderAddr, dataDir)
|
||||
swapCreatorAddr, err = deploySwapCreator(ctx, ec.Raw(), ec.PrivateKey(), dataDir)
|
||||
if err != nil {
|
||||
return ethcommon.Address{}, fmt.Errorf("failed to deploy swap creator: %w", err)
|
||||
}
|
||||
@@ -57,7 +55,7 @@ func getOrDeploySwapCreator(
|
||||
// otherwise, load the contract from the given address
|
||||
// and check that its bytecode is valid (ie. matches the
|
||||
// bytecode of this repo's swap contract)
|
||||
_, err = contracts.CheckSwapCreatorContractCode(ctx, ec.Raw(), swapCreatorAddr)
|
||||
err = contracts.CheckSwapCreatorContractCode(ctx, ec.Raw(), swapCreatorAddr)
|
||||
if err != nil {
|
||||
return ethcommon.Address{}, err
|
||||
}
|
||||
@@ -70,31 +68,15 @@ func deploySwapCreator(
|
||||
ctx context.Context,
|
||||
ec *ethclient.Client,
|
||||
privkey *ecdsa.PrivateKey,
|
||||
forwarderAddr ethcommon.Address,
|
||||
dataDir string,
|
||||
) (ethcommon.Address, *contracts.SwapCreator, error) {
|
||||
|
||||
) (ethcommon.Address, error) {
|
||||
if privkey == nil {
|
||||
return ethcommon.Address{}, nil, errNoEthPrivateKey
|
||||
return ethcommon.Address{}, errNoEthPrivateKey
|
||||
}
|
||||
|
||||
if (forwarderAddr == ethcommon.Address{}) {
|
||||
// deploy forwarder contract as well
|
||||
var err error
|
||||
forwarderAddr, err = contracts.DeployGSNForwarderWithKey(ctx, ec, privkey)
|
||||
if err != nil {
|
||||
return ethcommon.Address{}, nil, err
|
||||
}
|
||||
} else {
|
||||
// TODO: ignore this if the forwarderAddr is the one that's hardcoded for this network
|
||||
if err := contracts.CheckForwarderContractCode(ctx, ec, forwarderAddr); err != nil {
|
||||
return ethcommon.Address{}, nil, err
|
||||
}
|
||||
}
|
||||
|
||||
swapCreatorAddr, sf, err := contracts.DeploySwapCreatorWithKey(ctx, ec, privkey, forwarderAddr)
|
||||
swapCreatorAddr, _, err := contracts.DeploySwapCreatorWithKey(ctx, ec, privkey)
|
||||
if err != nil {
|
||||
return ethcommon.Address{}, nil, err
|
||||
return ethcommon.Address{}, err
|
||||
}
|
||||
|
||||
// store the contract addresses on disk
|
||||
@@ -102,14 +84,13 @@ func deploySwapCreator(
|
||||
path.Join(dataDir, contractAddressesFile),
|
||||
&contractAddresses{
|
||||
SwapCreatorAddr: swapCreatorAddr,
|
||||
ForwarderAddr: forwarderAddr,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return ethcommon.Address{}, nil, fmt.Errorf("failed to write contract address to file: %w", err)
|
||||
return ethcommon.Address{}, fmt.Errorf("failed to write contract address to file: %w", err)
|
||||
}
|
||||
|
||||
return swapCreatorAddr, sf, nil
|
||||
return swapCreatorAddr, nil
|
||||
}
|
||||
|
||||
// writeContractAddressesToFile writes the contract addresses to the given file
|
||||
|
||||
@@ -8,7 +8,6 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/athanorlabs/atomic-swap/common"
|
||||
contracts "github.com/athanorlabs/atomic-swap/ethereum"
|
||||
"github.com/athanorlabs/atomic-swap/ethereum/extethclient"
|
||||
"github.com/athanorlabs/atomic-swap/tests"
|
||||
|
||||
@@ -16,26 +15,7 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestGetOrDeploySwapCreator_DeployNoForwarder(t *testing.T) {
|
||||
pk := tests.GetTakerTestKey(t)
|
||||
ec := extethclient.CreateTestClient(t, pk)
|
||||
tmpDir := t.TempDir()
|
||||
|
||||
forwarder, err := contracts.DeployGSNForwarderWithKey(context.Background(), ec.Raw(), pk)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = getOrDeploySwapCreator(
|
||||
context.Background(),
|
||||
ethcommon.Address{},
|
||||
common.Development,
|
||||
tmpDir,
|
||||
ec,
|
||||
forwarder,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestGetOrDeploySwapCreator_DeployForwarderAlso(t *testing.T) {
|
||||
func TestGetOrDeploySwapCreator_Deploy(t *testing.T) {
|
||||
pk := tests.GetTakerTestKey(t)
|
||||
ec := extethclient.CreateTestClient(t, pk)
|
||||
tmpDir := t.TempDir()
|
||||
@@ -46,7 +26,6 @@ func TestGetOrDeploySwapCreator_DeployForwarderAlso(t *testing.T) {
|
||||
common.Development,
|
||||
tmpDir,
|
||||
ec,
|
||||
ethcommon.Address{},
|
||||
)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
@@ -56,10 +35,6 @@ func TestGetOrDeploySwapCreator_Get(t *testing.T) {
|
||||
ec := extethclient.CreateTestClient(t, pk)
|
||||
tmpDir := t.TempDir()
|
||||
|
||||
forwarder, err := contracts.DeployGSNForwarderWithKey(context.Background(), ec.Raw(), pk)
|
||||
require.NoError(t, err)
|
||||
t.Log(forwarder)
|
||||
|
||||
// deploy and get address
|
||||
address, err := getOrDeploySwapCreator(
|
||||
context.Background(),
|
||||
@@ -67,7 +42,6 @@ func TestGetOrDeploySwapCreator_Get(t *testing.T) {
|
||||
common.Development,
|
||||
tmpDir,
|
||||
ec,
|
||||
forwarder,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -77,7 +51,6 @@ func TestGetOrDeploySwapCreator_Get(t *testing.T) {
|
||||
common.Development,
|
||||
tmpDir,
|
||||
ec,
|
||||
ethcommon.Address{},
|
||||
)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, address, addr2)
|
||||
|
||||
@@ -71,11 +71,10 @@ const (
|
||||
flagUseExternalSigner = "external-signer"
|
||||
flagRelayer = "relayer"
|
||||
|
||||
flagDevXMRTaker = "dev-xmrtaker"
|
||||
flagDevXMRMaker = "dev-xmrmaker"
|
||||
flagDeploy = "deploy"
|
||||
flagForwarderAddress = "forwarder-address"
|
||||
flagNoTransferBack = "no-transfer-back"
|
||||
flagDevXMRTaker = "dev-xmrtaker"
|
||||
flagDevXMRMaker = "dev-xmrmaker"
|
||||
flagDeploy = "deploy"
|
||||
flagNoTransferBack = "no-transfer-back"
|
||||
|
||||
flagLogLevel = cliutil.FlagLogLevel
|
||||
flagProfile = "profile"
|
||||
@@ -187,10 +186,6 @@ func cliApp() *cli.App {
|
||||
Name: flagDeploy,
|
||||
Usage: "Deploy an instance of the swap contract",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: flagForwarderAddress,
|
||||
Usage: "Ethereum address of the trusted forwarder contract to use when deploying the swap contract",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: flagNoTransferBack,
|
||||
Usage: "Leave XMR in generated swap wallet instead of sweeping funds to primary.",
|
||||
@@ -369,36 +364,18 @@ func validateOrDeployContracts(c *cli.Context, envConf *common.Config, ec exteth
|
||||
panic("contract address should have been zeroed when envConf was initialized")
|
||||
}
|
||||
|
||||
// forwarderAddr is set only if we're deploying the swap creator contract
|
||||
// and the --forwarder-address flag is set. Otherwise, if we're deploying
|
||||
// and this flag isn't set, we deploy both the forwarder and the swap
|
||||
// creator contracts.
|
||||
var forwarderAddr ethcommon.Address
|
||||
forwarderAddrStr := c.String(flagForwarderAddress)
|
||||
if deploy && forwarderAddrStr != "" {
|
||||
if !ethcommon.IsHexAddress(forwarderAddrStr) {
|
||||
return fmt.Errorf("%q requires a valid ethereum address", flagForwarderAddress)
|
||||
}
|
||||
|
||||
forwarderAddr = ethcommon.HexToAddress(forwarderAddrStr)
|
||||
} else if !deploy && forwarderAddrStr != "" {
|
||||
return fmt.Errorf("using flag %q requires the %q flag", flagForwarderAddress, flagDeploy)
|
||||
}
|
||||
|
||||
swapCreatorAddr, err := getOrDeploySwapCreator(
|
||||
c.Context,
|
||||
envConf.SwapCreatorAddr,
|
||||
envConf.Env,
|
||||
envConf.DataDir,
|
||||
ec,
|
||||
forwarderAddr,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
envConf.SwapCreatorAddr = swapCreatorAddr
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,6 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -89,8 +88,8 @@ func TestDaemon_DevXMRTaker(t *testing.T) {
|
||||
}
|
||||
|
||||
//
|
||||
// Validate that --deploy created a contract address file and that we
|
||||
// deployed a forwarder. At some future point, we will ask the RPC endpoint
|
||||
// Validate that --deploy created a contract address file.
|
||||
// At some future point, we will ask the RPC endpoint
|
||||
// what the contract addresses are instead of using this file.
|
||||
//
|
||||
data, err := os.ReadFile(path.Join(dataDir, contractAddressesFile))
|
||||
@@ -99,19 +98,10 @@ func TestDaemon_DevXMRTaker(t *testing.T) {
|
||||
require.NoError(t, json.Unmarshal(data, &m))
|
||||
swapCreatorAddr, ok := m["swapCreatorAddr"]
|
||||
require.True(t, ok)
|
||||
forwarderAddr, ok := m["forwarderAddr"]
|
||||
require.True(t, ok)
|
||||
|
||||
ec, _ := tests.NewEthClient(t)
|
||||
ecCtx := context.Background()
|
||||
discoveredForwarderAddr, err :=
|
||||
contracts.CheckSwapCreatorContractCode(ecCtx, ec, ethcommon.HexToAddress(swapCreatorAddr))
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, strings.ToLower(discoveredForwarderAddr.Hex()), forwarderAddr)
|
||||
|
||||
// something is seriously wrong if this next check fails, as CheckSwapCreatorContractCode
|
||||
// should have already validated the forwarder bytecode
|
||||
err = contracts.CheckForwarderContractCode(ecCtx, ec, ethcommon.HexToAddress(forwarderAddr))
|
||||
err = contracts.CheckSwapCreatorContractCode(ecCtx, ec, ethcommon.HexToAddress(swapCreatorAddr))
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
@@ -121,7 +111,7 @@ func TestDaemon_DevXMRMaker(t *testing.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, ethcommon.Address{}, t.TempDir())
|
||||
swapCreatorAddr, err := deploySwapCreator(context.Background(), ec, key, t.TempDir())
|
||||
require.NoError(t, err)
|
||||
|
||||
flags := []string{
|
||||
@@ -158,9 +148,7 @@ func TestDaemon_BadFlags(t *testing.T) {
|
||||
ec, _ := tests.NewEthClient(t)
|
||||
ctx, _ := newTestContext(t)
|
||||
|
||||
swapCreatorAddr, swapCreator, err := deploySwapCreator(ctx, ec, key, ethcommon.Address{}, t.TempDir())
|
||||
require.NoError(t, err)
|
||||
forwarderAddr, err := swapCreator.TrustedForwarder(nil)
|
||||
swapCreatorAddr, err := deploySwapCreator(ctx, ec, key, t.TempDir())
|
||||
require.NoError(t, err)
|
||||
|
||||
baseFlags := []string{
|
||||
@@ -184,35 +172,10 @@ func TestDaemon_BadFlags(t *testing.T) {
|
||||
extraFlags: nil,
|
||||
expectErr: `flag "deploy" or "contract-address" is required for env=dev`,
|
||||
},
|
||||
{
|
||||
description: "deploy SwapCreator with invalid forwarder",
|
||||
extraFlags: []string{
|
||||
fmt.Sprintf("--%s", flagDeploy),
|
||||
fmt.Sprintf("--%s=%s", flagForwarderAddress, swapCreatorAddr), // passing wrong contract
|
||||
},
|
||||
expectErr: "does not contain correct Forwarder code",
|
||||
},
|
||||
{
|
||||
description: "pass invalid forwarder address (wrong length)",
|
||||
extraFlags: []string{
|
||||
fmt.Sprintf("--%s", flagDeploy),
|
||||
fmt.Sprintf("--%s=%sAB", flagForwarderAddress, forwarderAddr), // one byte too long
|
||||
},
|
||||
expectErr: fmt.Sprintf(`"%s" requires a valid ethereum address`, flagForwarderAddress),
|
||||
},
|
||||
{
|
||||
description: "pass forwarder address without deploy flag",
|
||||
extraFlags: []string{
|
||||
fmt.Sprintf("--%s=%s", flagForwarderAddress, forwarderAddr),
|
||||
// next flag is needed, or we fail on a different error first
|
||||
fmt.Sprintf("--%s=%s", flagContractAddress, swapCreatorAddr),
|
||||
},
|
||||
expectErr: fmt.Sprintf(`using flag "%s" requires the "%s" flag`, flagForwarderAddress, flagDeploy),
|
||||
},
|
||||
{
|
||||
description: "pass invalid SwapCreator contract",
|
||||
extraFlags: []string{
|
||||
fmt.Sprintf("--%s=%s", flagContractAddress, forwarderAddr), // passing wrong contract
|
||||
fmt.Sprintf("--%s=%s", flagContractAddress, ethcommon.Address{9}), // passing wrong contract
|
||||
},
|
||||
expectErr: "does not contain correct SwapCreator code",
|
||||
},
|
||||
|
||||
@@ -69,6 +69,7 @@ func MainnetConfig() *Config {
|
||||
// Note: SwapCreator contract below is using GSN Forwarder address
|
||||
// 0xB2b5841DBeF766d4b521221732F9B618fCf34A87
|
||||
// https://docs.opengsn.org/networks/addresses.html
|
||||
// TODO: this needs to be redeployed before this PR is merged
|
||||
SwapCreatorAddr: ethcommon.HexToAddress("0xD3d19539D61bB0e7617E499C7262594E71CA1c66"),
|
||||
Bootnodes: []string{
|
||||
"/ip4/67.205.131.11/tcp/9909/p2p/12D3KooWGpCLC4y42rf6aR3cguVFJAruzFXT6mUEyp7C32jTsyJd",
|
||||
@@ -102,7 +103,7 @@ func StagenetConfig() *Config {
|
||||
Port: 38081,
|
||||
},
|
||||
},
|
||||
SwapCreatorAddr: ethcommon.HexToAddress("0xEd014568991A9BE34F381Bf46d9c3f7623D4DEa5"),
|
||||
SwapCreatorAddr: ethcommon.HexToAddress("0x90119FA88abE871B3e26DF3a57C29A450f006065"),
|
||||
Bootnodes: []string{
|
||||
"/ip4/134.122.115.208/tcp/9900/p2p/12D3KooWDqCzbjexHEa8Rut7bzxHFpRMZyDRW1L6TGkL1KY24JH5",
|
||||
"/ip4/143.198.123.27/tcp/9900/p2p/12D3KooWSc4yFkPWBFmPToTMbhChH3FAgGH96DNzSg5fio1pQYoN",
|
||||
|
||||
@@ -23,6 +23,7 @@ import (
|
||||
"github.com/athanorlabs/atomic-swap/cliutil"
|
||||
"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/monero"
|
||||
@@ -78,19 +79,11 @@ func transfer(t *testing.T, fromKey *ecdsa.PrivateKey, toAddress ethcommon.Addre
|
||||
func minimumFundAlice(t *testing.T, ec extethclient.EthClient, providesAmt *apd.Decimal) {
|
||||
fundingKey := tests.GetTakerTestKey(t)
|
||||
|
||||
// When this comment was written, sample gas costs were:
|
||||
// newSwap: 53787
|
||||
// setReady: 34452
|
||||
// refund: 46692
|
||||
// relayClaim: 130507
|
||||
//
|
||||
const (
|
||||
aliceGasRation = 150000 // roughly 10% more than newSwap+setRead+refund
|
||||
aliceGasRation = contracts.MaxNewSwapETHGas + contracts.MaxSetReadyGas + contracts.MaxRefundETHGas
|
||||
)
|
||||
// We give Alice enough gas money to refund if needed, but not enough to
|
||||
// relay a claim:
|
||||
// 150000 - (53787 + 34452) = 61761
|
||||
//
|
||||
// relay a claim
|
||||
suggestedGasPrice, err := ec.Raw().SuggestGasPrice(context.Background())
|
||||
require.NoError(t, err)
|
||||
gasCostWei := new(big.Int).Mul(suggestedGasPrice, big.NewInt(aliceGasRation))
|
||||
|
||||
@@ -148,10 +148,7 @@ func getSwapCreatorAddress(t *testing.T, ec *ethclient.Client) ethcommon.Address
|
||||
ctx := context.Background()
|
||||
ethKey := tests.GetTakerTestKey(t) // requester might not have ETH, so we don't pass the key in
|
||||
|
||||
forwarderAddr, err := contracts.DeployGSNForwarderWithKey(ctx, ec, ethKey)
|
||||
require.NoError(t, err)
|
||||
|
||||
swapCreatorAddr, _, err := contracts.DeploySwapCreatorWithKey(ctx, ec, ethKey, forwarderAddr)
|
||||
swapCreatorAddr, _, err := contracts.DeploySwapCreatorWithKey(ctx, ec, ethKey)
|
||||
require.NoError(t, err)
|
||||
|
||||
_swapCreatorAddr = &swapCreatorAddr
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -4,12 +4,10 @@
|
||||
package contracts
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"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/stretchr/testify/require"
|
||||
@@ -20,73 +18,33 @@ import (
|
||||
|
||||
// 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, forwarderAddr ethcommon.Address) []byte {
|
||||
func getContractCode(t *testing.T) []byte {
|
||||
ec, _ := tests.NewEthClient(t)
|
||||
pk := tests.GetMakerTestKey(t)
|
||||
contractAddr, _ := deploySwapCreatorWithForwarder(t, ec, pk, forwarderAddr)
|
||||
contractAddr, _ := deploySwapCreator(t, ec, pk)
|
||||
code, err := ec.CodeAt(context.Background(), contractAddr, nil)
|
||||
require.NoError(t, err)
|
||||
return code
|
||||
}
|
||||
|
||||
func TestCheckForwarderContractCode(t *testing.T) {
|
||||
ec, _ := tests.NewEthClient(t)
|
||||
pk := tests.GetMakerTestKey(t)
|
||||
forwarderAddr := deployForwarder(t, ec, pk)
|
||||
err := CheckForwarderContractCode(context.Background(), ec, forwarderAddr)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
// This test will fail if the compiled SwapCreator contract is updated, but the
|
||||
// expectedSwapCreatorBytecodeHex constant is not updated. Use this test to update the
|
||||
// constant.
|
||||
func TestExpectedSwapCreatorBytecodeHex(t *testing.T) {
|
||||
allZeroTrustedForwarder := ethcommon.Address{}
|
||||
codeHex := ethcommon.Bytes2Hex(getContractCode(t, allZeroTrustedForwarder))
|
||||
codeHex := ethcommon.Bytes2Hex(getContractCode(t))
|
||||
require.Equal(t, expectedSwapCreatorBytecodeHex, codeHex,
|
||||
"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
|
||||
// forwarderAddrIndexes slice of trusted forwarder locations is not updated. Use
|
||||
// this test to update the slice.
|
||||
func TestForwarderAddrIndexes(t *testing.T) {
|
||||
ec, _ := tests.NewEthClient(t)
|
||||
pk := tests.GetMakerTestKey(t)
|
||||
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], forwarderAddr[:]) {
|
||||
addressLocations = append(addressLocations, i)
|
||||
i += ethAddrByteLen - 1 // -1 since the loop will increment by 1
|
||||
}
|
||||
}
|
||||
|
||||
t.Logf("forwarderAddrIndexes: %v", addressLocations)
|
||||
require.EqualValues(t, forwarderAddrIndices, addressLocations,
|
||||
"update forwarderAddrIndexes with above logged indexes to fix this test")
|
||||
}
|
||||
|
||||
// Ensure that we correctly verify the SwapCreator contract when initialised with
|
||||
// different trusted forwarder addresses.
|
||||
func TestCheckSwapCreatorContractCode(t *testing.T) {
|
||||
ec, _ := tests.NewEthClient(t)
|
||||
pk := tests.GetMakerTestKey(t)
|
||||
forwarderAddrs := []string{
|
||||
deployForwarder(t, ec, pk).Hex(),
|
||||
deployForwarder(t, ec, pk).Hex(),
|
||||
deployForwarder(t, ec, pk).Hex(),
|
||||
}
|
||||
|
||||
for _, addrHex := range forwarderAddrs {
|
||||
tfAddr := ethcommon.HexToAddress(addrHex)
|
||||
contractAddr, _ := deploySwapCreatorWithForwarder(t, ec, pk, tfAddr)
|
||||
parsedTFAddr, err := CheckSwapCreatorContractCode(context.Background(), ec, contractAddr)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, addrHex, parsedTFAddr.Hex())
|
||||
}
|
||||
contractAddr, _ := deploySwapCreator(t, ec, pk)
|
||||
err := CheckSwapCreatorContractCode(context.Background(), ec, contractAddr)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
// Tests that we fail when the wrong contract byte code is found
|
||||
@@ -94,38 +52,37 @@ func TestCheckSwapCreatorContractCode_fail(t *testing.T) {
|
||||
ec, _ := tests.NewEthClient(t)
|
||||
pk := tests.GetMakerTestKey(t)
|
||||
|
||||
// Deploy a forwarder contract and then try to verify it as SwapCreator contract
|
||||
contractAddr := deployForwarder(t, ec, pk)
|
||||
_, err := CheckSwapCreatorContractCode(context.Background(), ec, contractAddr)
|
||||
// Deploy a token contract and then try to verify it as SwapCreator contract
|
||||
contractAddr, _ := deployERC20Token(t, ec, pk, "name", "symbol", 10, 100)
|
||||
err := CheckSwapCreatorContractCode(context.Background(), ec, contractAddr)
|
||||
require.ErrorIs(t, err, errInvalidSwapCreatorContract)
|
||||
}
|
||||
|
||||
func TestSepoliaContract(t *testing.T) {
|
||||
t.Skip("needs to be redeployed before merge")
|
||||
ctx := context.Background()
|
||||
ec := tests.NewEthSepoliaClient(t)
|
||||
|
||||
// temporarily place a funded sepolia private key below to deploy the test contract
|
||||
const sepoliaKey = ""
|
||||
|
||||
parsedTFAddr, err := CheckSwapCreatorContractCode(ctx, ec, common.StagenetConfig().SwapCreatorAddr)
|
||||
err := CheckSwapCreatorContractCode(ctx, ec, common.StagenetConfig().SwapCreatorAddr)
|
||||
if errors.Is(err, errInvalidSwapCreatorContract) && sepoliaKey != "" {
|
||||
pk, err := ethcrypto.HexToECDSA(sepoliaKey) //nolint:govet // shadow declaration of err
|
||||
require.NoError(t, err)
|
||||
forwarderAddr := ethcommon.HexToAddress(gsnforwarder.SepoliaForwarderAddrHex)
|
||||
swapCreatorAddr, _, err := DeploySwapCreatorWithKey(ctx, ec, pk, forwarderAddr)
|
||||
swapCreatorAddr, _, err := DeploySwapCreatorWithKey(ctx, ec, pk)
|
||||
require.NoError(t, err)
|
||||
t.Fatalf("Update common.StagenetConfig()'s SwapCreatorAddr with %s", swapCreatorAddr.Hex())
|
||||
}
|
||||
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, gsnforwarder.SepoliaForwarderAddrHex, parsedTFAddr.Hex())
|
||||
}
|
||||
|
||||
func TestMainnetContract(t *testing.T) {
|
||||
t.Skip("needs to be redeployed before merge")
|
||||
ctx := context.Background()
|
||||
ec := tests.NewEthMainnetClient(t)
|
||||
mainnetConf := common.MainnetConfig()
|
||||
parsedTFAddr, err := CheckSwapCreatorContractCode(ctx, ec, mainnetConf.SwapCreatorAddr)
|
||||
err := CheckSwapCreatorContractCode(ctx, ec, mainnetConf.SwapCreatorAddr)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, gsnforwarder.MainnetForwarderAddrHex, parsedTFAddr.Hex())
|
||||
}
|
||||
|
||||
@@ -4,12 +4,12 @@ package contracts
|
||||
// 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
|
||||
MaxNewSwapETHGas = 50639
|
||||
MaxNewSwapTokenGas = 86218
|
||||
MaxSetReadyGas = 31872
|
||||
MaxSetReadyGas = 32054
|
||||
MaxClaimETHGas = 43349
|
||||
MaxClaimTokenGas = 47522
|
||||
MaxRefundETHGas = 43120
|
||||
MaxRefundTokenGas = 47282
|
||||
MaxRefundETHGas = 43132
|
||||
MaxRefundTokenGas = 47294
|
||||
MaxTokenApproveGas = 47000 // 46223 with our contract
|
||||
)
|
||||
|
||||
@@ -3,6 +3,6 @@ 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
|
||||
maxSwapCreatorDeployGas = 1094089
|
||||
maxTestERC20DeployGas = 798286 // using long token names or symbols will increase this
|
||||
)
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// OpenZeppelin Contracts (last updated v4.7.0) (metatx/ERC2771Context.sol)
|
||||
|
||||
pragma solidity ^0.8.19;
|
||||
|
||||
import {Context} from "./Context.sol";
|
||||
|
||||
/**
|
||||
* @dev Context variant with ERC2771 support.
|
||||
*/
|
||||
abstract contract ERC2771Context is Context {
|
||||
/// @custom:oz-upgrades-unsafe-allow state-variable-immutable
|
||||
// TODO: this was modified to be public (is that ok?)
|
||||
address public immutable _trustedForwarder;
|
||||
|
||||
/// @custom:oz-upgrades-unsafe-allow constructor
|
||||
constructor(address trustedForwarder) {
|
||||
_trustedForwarder = trustedForwarder;
|
||||
}
|
||||
|
||||
function isTrustedForwarder(address forwarder) public view virtual returns (bool) {
|
||||
return forwarder == _trustedForwarder;
|
||||
}
|
||||
|
||||
function _msgSender() internal view virtual override returns (address sender) {
|
||||
if (isTrustedForwarder(msg.sender)) {
|
||||
// The assembly code is more direct than the Solidity version using `abi.decode`.
|
||||
/// @solidity memory-safe-assembly
|
||||
assembly {
|
||||
sender := shr(96, calldataload(sub(calldatasize(), 20)))
|
||||
}
|
||||
} else {
|
||||
return super._msgSender();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,10 @@
|
||||
// SPDX-License-Identifier: LGPLv3
|
||||
pragma solidity ^0.8.19;
|
||||
|
||||
import {ERC2771Context} from "./ERC2771Context.sol";
|
||||
import {IERC20} from "./IERC20.sol";
|
||||
import {Secp256k1} from "./Secp256k1.sol";
|
||||
|
||||
contract SwapCreator is ERC2771Context, Secp256k1 {
|
||||
contract SwapCreator is Secp256k1 {
|
||||
// Swap state is PENDING when the swap is first created and funded
|
||||
// Alice sets Stage to READY when she sees the funds locked on the other chain.
|
||||
// this prevents Bob from withdrawing funds without locking funds on the other chain first
|
||||
@@ -41,6 +40,20 @@ contract SwapCreator is ERC2771Context, Secp256k1 {
|
||||
uint256 nonce;
|
||||
}
|
||||
|
||||
// RelaySwap contains additional information required for relayed transactions.
|
||||
// This entire structure is encoded and signed by the swap claimer, and the signature is
|
||||
// passed to the `claimRelayer` function.
|
||||
struct RelaySwap {
|
||||
// the swap the transaction is for
|
||||
Swap swap;
|
||||
// the fee, in wei, paid to the relayer
|
||||
uint256 fee;
|
||||
// hash of (relayer's payout address || 4-byte salt)
|
||||
bytes32 relayerHash;
|
||||
// address of the swap contract this transaction is meant for
|
||||
address swapCreator;
|
||||
}
|
||||
|
||||
mapping(bytes32 => Stage) public swaps;
|
||||
|
||||
event New(
|
||||
@@ -80,9 +93,6 @@ contract SwapCreator is ERC2771Context, Secp256k1 {
|
||||
// returned when the caller of `setReady` or `refund` is not the swap owner
|
||||
error OnlySwapOwner();
|
||||
|
||||
// returned when `claimRelayer` is not called by the trusted forwarder
|
||||
error OnlyTrustedForwarder();
|
||||
|
||||
// returned when the signer of the relayed transaction is not the swap's claimer
|
||||
error OnlySwapClaimer();
|
||||
|
||||
@@ -104,7 +114,15 @@ contract SwapCreator is ERC2771Context, Secp256k1 {
|
||||
// returned when the provided secret does not match the expected public key
|
||||
error InvalidSecret();
|
||||
|
||||
constructor(address trustedForwarder) ERC2771Context(trustedForwarder) {} // solhint-disable-line
|
||||
// returned when the signature of a `RelaySwap` is invalid
|
||||
error InvalidSignature();
|
||||
|
||||
// returned when the SwapCreator address is a `RelaySwap` is not the addres of this contract
|
||||
error InvalidContractAddress();
|
||||
|
||||
// returned when the hash of the relayer address and salt passed to `claimRelayer`
|
||||
// does not match the relayer hash in `RelaySwap`
|
||||
error InvalidRelayerAddress();
|
||||
|
||||
// newSwap creates a new Swap instance with the given parameters.
|
||||
// it returns the swap's ID.
|
||||
@@ -174,8 +192,9 @@ contract SwapCreator is ERC2771Context, Secp256k1 {
|
||||
|
||||
// Bob can claim if:
|
||||
// - (Alice has set the swap to `ready` or it's past timeout0) and it's before timeout1
|
||||
function claim(Swap memory _swap, bytes32 _s) public {
|
||||
_claim(_swap, _s);
|
||||
function claim(Swap memory _swap, bytes32 _secret) public {
|
||||
if (msg.sender != _swap.claimer) revert OnlySwapClaimer();
|
||||
_claim(_swap, _secret);
|
||||
|
||||
// send ether to swap claimer
|
||||
if (_swap.asset == address(0)) {
|
||||
@@ -190,45 +209,59 @@ contract SwapCreator is ERC2771Context, Secp256k1 {
|
||||
|
||||
// Bob can claim if:
|
||||
// - (Alice has set the swap to `ready` or it's past timeout0) and it's before timeout1
|
||||
// This function is only callable by the trusted forwarder.
|
||||
// It transfers the fee to the originator of the transaction.
|
||||
function claimRelayer(Swap memory _swap, bytes32 _s, uint256 fee) public {
|
||||
if (!isTrustedForwarder(msg.sender)) revert OnlyTrustedForwarder();
|
||||
_claim(_swap, _s);
|
||||
// It transfers the fee to the relayer address specified in `_relaySwap`.
|
||||
// Note: this function will revert if the swap value is less than the relayer fee;
|
||||
// in that case, `claim` must be called instead.
|
||||
function claimRelayer(
|
||||
RelaySwap memory _relaySwap,
|
||||
bytes32 _secret,
|
||||
address payable _relayer,
|
||||
uint32 _salt,
|
||||
uint8 v,
|
||||
bytes32 r,
|
||||
bytes32 s
|
||||
) public {
|
||||
address signer = ecrecover(keccak256(abi.encode(_relaySwap)), v, r, s);
|
||||
if (signer != _relaySwap.swap.claimer) revert InvalidSignature();
|
||||
if (address(this) != _relaySwap.swapCreator) revert InvalidContractAddress();
|
||||
if (keccak256(abi.encodePacked(_relayer, _salt)) != _relaySwap.relayerHash)
|
||||
revert InvalidRelayerAddress();
|
||||
|
||||
_claim(_relaySwap.swap, _secret);
|
||||
|
||||
// send ether to swap claimer, subtracting the relayer fee
|
||||
// which is sent to the originator of the transaction.
|
||||
// tx.origin is okay here, since it isn't for authentication purposes.
|
||||
if (_swap.asset == address(0)) {
|
||||
_swap.claimer.transfer(_swap.value - fee);
|
||||
payable(tx.origin).transfer(fee); // solhint-disable-line
|
||||
if (_relaySwap.swap.asset == address(0)) {
|
||||
_relaySwap.swap.claimer.transfer(_relaySwap.swap.value - _relaySwap.fee);
|
||||
payable(_relayer).transfer(_relaySwap.fee);
|
||||
} else {
|
||||
// WARN: this will FAIL for fee-on-transfer or rebasing tokens if the token
|
||||
// transfer reverts (i.e. if this contract does not contain _swap.value tokens),
|
||||
// exposing Bob's secret while giving him nothing.
|
||||
IERC20(_swap.asset).transfer(_swap.claimer, _swap.value - fee);
|
||||
IERC20(_swap.asset).transfer(tx.origin, fee); // solhint-disable-line
|
||||
IERC20(_relaySwap.swap.asset).transfer(
|
||||
_relaySwap.swap.claimer,
|
||||
_relaySwap.swap.value - _relaySwap.fee
|
||||
);
|
||||
IERC20(_relaySwap.swap.asset).transfer(_relayer, _relaySwap.fee);
|
||||
}
|
||||
}
|
||||
|
||||
function _claim(Swap memory _swap, bytes32 _s) internal {
|
||||
function _claim(Swap memory _swap, bytes32 _secret) internal {
|
||||
bytes32 swapID = keccak256(abi.encode(_swap));
|
||||
Stage swapStage = swaps[swapID];
|
||||
if (swapStage == Stage.INVALID) revert InvalidSwap();
|
||||
if (swapStage == Stage.COMPLETED) revert SwapCompleted();
|
||||
if (_msgSender() != _swap.claimer) revert OnlySwapClaimer();
|
||||
if (block.timestamp < _swap.timeout0 && swapStage != Stage.READY) revert TooEarlyToClaim();
|
||||
if (block.timestamp >= _swap.timeout1) revert TooLateToClaim();
|
||||
|
||||
verifySecret(_s, _swap.pubKeyClaim);
|
||||
emit Claimed(swapID, _s);
|
||||
verifySecret(_secret, _swap.pubKeyClaim);
|
||||
emit Claimed(swapID, _secret);
|
||||
swaps[swapID] = Stage.COMPLETED;
|
||||
}
|
||||
|
||||
// Alice can claim a refund:
|
||||
// - Until timeout0 unless she calls setReady
|
||||
// - After timeout1
|
||||
function refund(Swap memory _swap, bytes32 _s) public {
|
||||
function refund(Swap memory _swap, bytes32 _secret) public {
|
||||
bytes32 swapID = keccak256(abi.encode(_swap));
|
||||
Stage swapStage = swaps[swapID];
|
||||
if (swapStage == Stage.INVALID) revert InvalidSwap();
|
||||
@@ -239,8 +272,8 @@ contract SwapCreator is ERC2771Context, Secp256k1 {
|
||||
(block.timestamp > _swap.timeout0 || swapStage == Stage.READY)
|
||||
) revert NotTimeToRefund();
|
||||
|
||||
verifySecret(_s, _swap.pubKeyRefund);
|
||||
emit Refunded(swapID, _s);
|
||||
verifySecret(_secret, _swap.pubKeyRefund);
|
||||
emit Refunded(swapID, _secret);
|
||||
|
||||
// send asset back to swap owner
|
||||
swaps[swapID] = Stage.COMPLETED;
|
||||
@@ -251,7 +284,7 @@ contract SwapCreator is ERC2771Context, Secp256k1 {
|
||||
}
|
||||
}
|
||||
|
||||
function verifySecret(bytes32 _s, bytes32 _hashedPubkey) internal pure {
|
||||
if (!mulVerify(uint256(_s), uint256(_hashedPubkey))) revert InvalidSecret();
|
||||
function verifySecret(bytes32 _secret, bytes32 _hashedPubkey) internal pure {
|
||||
if (!mulVerify(uint256(_secret), uint256(_hashedPubkey))) revert InvalidSecret();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,8 +8,6 @@ import (
|
||||
"crypto/ecdsa"
|
||||
"fmt"
|
||||
|
||||
"github.com/athanorlabs/go-relayer/common"
|
||||
"github.com/athanorlabs/go-relayer/impls/gsnforwarder"
|
||||
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
||||
ethcommon "github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/ethclient"
|
||||
@@ -21,26 +19,19 @@ import (
|
||||
var log = logging.Logger("contracts")
|
||||
|
||||
// DeploySwapCreatorWithKey deploys the SwapCreator contract using the passed privKey to
|
||||
// pay for the gas.
|
||||
// pay for the deployment.
|
||||
func DeploySwapCreatorWithKey(
|
||||
ctx context.Context,
|
||||
ec *ethclient.Client,
|
||||
privKey *ecdsa.PrivateKey,
|
||||
forwarderAddr ethcommon.Address,
|
||||
) (ethcommon.Address, *SwapCreator, error) {
|
||||
txOpts, err := newTXOpts(ctx, ec, privKey)
|
||||
if err != nil {
|
||||
return ethcommon.Address{}, nil, err
|
||||
}
|
||||
|
||||
if (forwarderAddr != ethcommon.Address{}) {
|
||||
if err = registerDomainSeparatorIfNeeded(ctx, ec, privKey, forwarderAddr); err != nil {
|
||||
return ethcommon.Address{}, nil, fmt.Errorf("failed to deploy swap creator: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
log.Infof("deploying SwapCreator.sol with forwarderAddr %s", forwarderAddr)
|
||||
address, tx, sf, err := DeploySwapCreator(txOpts, ec, forwarderAddr)
|
||||
log.Infof("deploying SwapCreator.sol")
|
||||
address, tx, sf, err := DeploySwapCreator(txOpts, ec)
|
||||
if err != nil {
|
||||
return ethcommon.Address{}, nil, fmt.Errorf("failed to deploy swap creator: %w", err)
|
||||
}
|
||||
@@ -54,109 +45,6 @@ func DeploySwapCreatorWithKey(
|
||||
return address, sf, nil
|
||||
}
|
||||
|
||||
// DeployGSNForwarderWithKey deploys and registers the GSN forwarder using the passed
|
||||
// private key to pay the gas fees.
|
||||
func DeployGSNForwarderWithKey(
|
||||
ctx context.Context,
|
||||
ec *ethclient.Client,
|
||||
privKey *ecdsa.PrivateKey,
|
||||
) (ethcommon.Address, error) {
|
||||
txOpts, err := newTXOpts(ctx, ec, privKey)
|
||||
if err != nil {
|
||||
return ethcommon.Address{}, err
|
||||
}
|
||||
|
||||
address, tx, contract, err := gsnforwarder.DeployForwarder(txOpts, ec)
|
||||
if err != nil {
|
||||
return ethcommon.Address{}, fmt.Errorf("failed to deploy Forwarder.sol: %w", err)
|
||||
}
|
||||
|
||||
_, err = block.WaitForReceipt(ctx, ec, tx.Hash())
|
||||
if err != nil {
|
||||
return ethcommon.Address{}, err
|
||||
}
|
||||
|
||||
err = registerDomainSeparator(ctx, ec, privKey, address, contract)
|
||||
if err != nil {
|
||||
return ethcommon.Address{}, err
|
||||
}
|
||||
|
||||
return address, nil
|
||||
}
|
||||
|
||||
func isDomainSeparatorRegistered(
|
||||
ctx context.Context,
|
||||
ec *ethclient.Client,
|
||||
forwarderAddr ethcommon.Address,
|
||||
forwarder *gsnforwarder.Forwarder,
|
||||
) (isRegistered bool, err error) {
|
||||
chainID, err := ec.ChainID(ctx)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
name := gsnforwarder.DefaultName
|
||||
version := gsnforwarder.DefaultVersion
|
||||
ds, err := common.GetEIP712DomainSeparator(name, version, chainID, forwarderAddr)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
opts := &bind.CallOpts{Context: ctx}
|
||||
return forwarder.Domains(opts, ds)
|
||||
}
|
||||
|
||||
func registerDomainSeparatorIfNeeded(
|
||||
ctx context.Context,
|
||||
ec *ethclient.Client,
|
||||
privKey *ecdsa.PrivateKey,
|
||||
forwarderAddr ethcommon.Address,
|
||||
) error {
|
||||
forwarder, err := gsnforwarder.NewForwarder(forwarderAddr, ec)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
isRegistered, err := isDomainSeparatorRegistered(ctx, ec, forwarderAddr, forwarder)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if isRegistered {
|
||||
return nil
|
||||
}
|
||||
|
||||
return registerDomainSeparator(ctx, ec, privKey, forwarderAddr, forwarder)
|
||||
}
|
||||
|
||||
func registerDomainSeparator(
|
||||
ctx context.Context,
|
||||
ec *ethclient.Client,
|
||||
privKey *ecdsa.PrivateKey,
|
||||
forwarderAddr ethcommon.Address,
|
||||
forwarder *gsnforwarder.Forwarder,
|
||||
) error {
|
||||
log.Infof("registering domain separator for forwarder %s", forwarderAddr)
|
||||
txOpts, err := newTXOpts(ctx, ec, privKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tx, err := forwarder.RegisterDomainSeparator(txOpts, gsnforwarder.DefaultName, gsnforwarder.DefaultVersion)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to register domain separator: %w", err)
|
||||
}
|
||||
|
||||
_, err = block.WaitForReceipt(ctx, ec, tx.Hash())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Debugf("registered domain separator in forwarder at %s: name=%s version=%s",
|
||||
forwarderAddr,
|
||||
gsnforwarder.DefaultName,
|
||||
gsnforwarder.DefaultVersion,
|
||||
)
|
||||
return nil
|
||||
}
|
||||
|
||||
func newTXOpts(ctx context.Context, ec *ethclient.Client, privkey *ecdsa.PrivateKey) (*bind.TransactOpts, error) {
|
||||
chainID, err := ec.ChainID(ctx)
|
||||
if err != nil {
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
// Copyright 2023 The AthanorLabs/atomic-swap Authors
|
||||
// SPDX-License-Identifier: LGPL-3.0-only
|
||||
|
||||
package contracts
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/athanorlabs/go-relayer/impls/gsnforwarder"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/athanorlabs/atomic-swap/tests"
|
||||
)
|
||||
|
||||
func Test_registerDomainSeparatorIfNeeded(t *testing.T) {
|
||||
ec, _ := tests.NewEthClient(t)
|
||||
ctx := context.Background()
|
||||
privKey := tests.GetMakerTestKey(t)
|
||||
|
||||
txOpts, err := newTXOpts(ctx, ec, privKey)
|
||||
require.NoError(t, err)
|
||||
|
||||
forwarderAddr, tx, forwarder, err := gsnforwarder.DeployForwarder(txOpts, ec)
|
||||
require.NoError(t, err)
|
||||
_ = tests.MineTransaction(t, ec, tx)
|
||||
|
||||
isRegistered, err := isDomainSeparatorRegistered(ctx, ec, forwarderAddr, forwarder)
|
||||
require.NoError(t, err)
|
||||
require.False(t, isRegistered)
|
||||
|
||||
err = registerDomainSeparatorIfNeeded(ctx, ec, privKey, forwarderAddr)
|
||||
require.NoError(t, err)
|
||||
|
||||
isRegistered, err = isDomainSeparatorRegistered(ctx, ec, forwarderAddr, forwarder)
|
||||
require.NoError(t, err)
|
||||
require.True(t, isRegistered)
|
||||
}
|
||||
@@ -4,81 +4,113 @@
|
||||
package contracts
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"math/big"
|
||||
"testing"
|
||||
|
||||
ethcommon "github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/ethclient"
|
||||
"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)
|
||||
func deployERC20Token(
|
||||
t *testing.T,
|
||||
ec *ethclient.Client,
|
||||
pk *ecdsa.PrivateKey, // token owner (and pays for deployment)
|
||||
name string,
|
||||
symbol string,
|
||||
decimals uint8,
|
||||
supplyStdUnits int64,
|
||||
) (ethcommon.Address, *TestERC20) {
|
||||
addr := crypto.PubkeyToAddress(pk.PublicKey)
|
||||
supply := new(big.Int).Mul(big.NewInt(supplyStdUnits),
|
||||
new(big.Int).Exp(big.NewInt(10), big.NewInt(int64(decimals)), nil),
|
||||
)
|
||||
|
||||
// deploy TestERC20
|
||||
erc20Addr, tx, erc20Contract, err :=
|
||||
DeployTestERC20(getAuth(t, pkA), ec, "Test of the ERC20 Token", "ERC20Token", 18, addr, big.NewInt(9999))
|
||||
tokenAddr, tx, tokenContract, err :=
|
||||
DeployTestERC20(getAuth(t, pk), ec, name, symbol, decimals, addr, supply)
|
||||
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)
|
||||
return tokenAddr, tokenContract
|
||||
}
|
||||
|
||||
func TestSwapCreator_NewSwap_ERC20(t *testing.T) {
|
||||
pkA := tests.GetTakerTestKey(t)
|
||||
ec, _ := tests.NewEthClient(t)
|
||||
|
||||
tokenAddr, tokenContract := deployERC20Token(
|
||||
t,
|
||||
ec,
|
||||
pkA,
|
||||
"Test of the ERC20 Token",
|
||||
"ERC20Token",
|
||||
18,
|
||||
9999,
|
||||
)
|
||||
|
||||
testNewSwap(t, types.EthAsset(tokenAddr), tokenContract)
|
||||
}
|
||||
|
||||
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))
|
||||
tokenAddr, tokenContract := deployERC20Token(
|
||||
t,
|
||||
ec,
|
||||
pkA,
|
||||
"TestERC20",
|
||||
"TEST",
|
||||
18,
|
||||
9999,
|
||||
)
|
||||
|
||||
// 3 logs:
|
||||
// Approval
|
||||
// Transfer
|
||||
// New
|
||||
testClaim(t, types.EthAsset(erc20Addr), 2, big.NewInt(99), erc20Contract)
|
||||
testClaim(t, types.EthAsset(tokenAddr), 2, big.NewInt(99), tokenContract)
|
||||
}
|
||||
|
||||
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))
|
||||
tokenAddr, tokenContract := deployERC20Token(
|
||||
t,
|
||||
ec,
|
||||
pkA,
|
||||
"TestERC20",
|
||||
"TEST",
|
||||
18,
|
||||
9999,
|
||||
)
|
||||
|
||||
testRefundBeforeT0(t, types.EthAsset(erc20Addr), erc20Contract, 2)
|
||||
testRefundBeforeT0(t, types.EthAsset(tokenAddr), tokenContract, 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))
|
||||
tokenAddr, tokenContract := deployERC20Token(
|
||||
t,
|
||||
ec,
|
||||
pkA,
|
||||
"TestERC20",
|
||||
"TEST",
|
||||
18,
|
||||
9999,
|
||||
)
|
||||
|
||||
testRefundAfterT1(t, types.EthAsset(erc20Addr), erc20Contract, 2)
|
||||
testRefundAfterT1(t, types.EthAsset(tokenAddr), tokenContract, 2)
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -63,19 +63,8 @@ func approveERC20(t *testing.T,
|
||||
require.GreaterOrEqual(t, MaxTokenApproveGas, int(receipt.GasUsed), "Token Approve")
|
||||
}
|
||||
|
||||
func deployForwarder(t *testing.T, ec *ethclient.Client, pk *ecdsa.PrivateKey) ethcommon.Address {
|
||||
forwarderAddr, err := DeployGSNForwarderWithKey(context.Background(), ec, pk)
|
||||
require.NoError(t, err)
|
||||
return forwarderAddr
|
||||
}
|
||||
|
||||
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)
|
||||
func deploySwapCreator(t *testing.T, ec *ethclient.Client, pk *ecdsa.PrivateKey) (ethcommon.Address, *SwapCreator) {
|
||||
swapCreatorAddr, tx, swapCreator, err := DeploySwapCreator(getAuth(t, pk), ec)
|
||||
require.NoError(t, err)
|
||||
receipt := getReceipt(t, ec, tx)
|
||||
|
||||
@@ -86,11 +75,6 @@ func deploySwapCreatorWithForwarder(
|
||||
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)
|
||||
|
||||
@@ -56,6 +56,86 @@ func StageToString(stage byte) string {
|
||||
}
|
||||
}
|
||||
|
||||
// Hash abi-encodes the RelaySwap and returns the keccak256 hash of the encoded value.
|
||||
func (s *SwapCreatorRelaySwap) Hash() types.Hash {
|
||||
uint256Ty, err := abi.NewType("uint256", "", nil)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("failed to create uint256 type: %s", err))
|
||||
}
|
||||
|
||||
bytes32Ty, err := abi.NewType("bytes32", "", nil)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("failed to create bytes32 type: %s", err))
|
||||
}
|
||||
|
||||
addressTy, err := abi.NewType("address", "", nil)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("failed to create address type: %s", err))
|
||||
}
|
||||
|
||||
arguments := abi.Arguments{
|
||||
{
|
||||
Type: addressTy,
|
||||
},
|
||||
{
|
||||
Type: addressTy,
|
||||
},
|
||||
{
|
||||
Type: bytes32Ty,
|
||||
},
|
||||
{
|
||||
Type: bytes32Ty,
|
||||
},
|
||||
{
|
||||
Type: uint256Ty,
|
||||
},
|
||||
{
|
||||
Type: uint256Ty,
|
||||
},
|
||||
{
|
||||
Type: addressTy,
|
||||
},
|
||||
{
|
||||
Type: uint256Ty,
|
||||
},
|
||||
{
|
||||
Type: uint256Ty,
|
||||
},
|
||||
{
|
||||
Type: uint256Ty,
|
||||
},
|
||||
{
|
||||
Type: bytes32Ty,
|
||||
},
|
||||
{
|
||||
Type: addressTy,
|
||||
},
|
||||
}
|
||||
|
||||
args, err := arguments.Pack(
|
||||
s.Swap.Owner,
|
||||
s.Swap.Claimer,
|
||||
s.Swap.PubKeyClaim,
|
||||
s.Swap.PubKeyRefund,
|
||||
s.Swap.Timeout0,
|
||||
s.Swap.Timeout1,
|
||||
s.Swap.Asset,
|
||||
s.Swap.Value,
|
||||
s.Swap.Nonce,
|
||||
s.Fee,
|
||||
s.RelayerHash,
|
||||
s.SwapCreator,
|
||||
)
|
||||
if err != nil {
|
||||
// As long as none of the *big.Int fields are nil, this cannot fail.
|
||||
// When receiving SwapCreatorRelaySwap objects from peers in
|
||||
// JSON, all *big.Int values are pre-validated to be non-nil.
|
||||
panic(fmt.Sprintf("failed to pack arguments: %s", err))
|
||||
}
|
||||
|
||||
return crypto.Keccak256Hash(args)
|
||||
}
|
||||
|
||||
// SwapID calculates and returns the same hashed swap identifier that newSwap
|
||||
// emits and that is used to track the on-chain stage of a swap.
|
||||
func (sfs *SwapCreatorSwap) SwapID() types.Hash {
|
||||
@@ -131,17 +211,6 @@ func GetSecretFromLog(log *ethtypes.Log, eventTopic [32]byte) (*mcrypto.PrivateS
|
||||
return nil, errors.New("invalid event, must be one of Claimed or Refunded")
|
||||
}
|
||||
|
||||
// abiSF, err := abi.JSON(strings.NewReader(SwapCreatorMetaData.ABI))
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
|
||||
// data := log.Data
|
||||
// res, err := abiSF.Unpack(event, data)
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
|
||||
if len(log.Topics) < 3 {
|
||||
return nil, errors.New("log had not enough parameters")
|
||||
}
|
||||
@@ -165,17 +234,6 @@ func CheckIfLogIDMatches(log ethtypes.Log, eventTopic, id [32]byte) (bool, error
|
||||
return false, errors.New("invalid event, must be one of Claimed or Refunded")
|
||||
}
|
||||
|
||||
// abi, err := abi.JSON(strings.NewReader(SwapCreatorMetaData.ABI))
|
||||
// if err != nil {
|
||||
// return false, err
|
||||
// }
|
||||
|
||||
// data := log.Data
|
||||
// res, err := abi.Unpack(event, data)
|
||||
// if err != nil {
|
||||
// return false, err
|
||||
// }
|
||||
|
||||
if len(log.Topics) < 2 {
|
||||
return false, errors.New("log had not enough parameters")
|
||||
}
|
||||
|
||||
1
go.mod
1
go.mod
@@ -9,7 +9,6 @@ require (
|
||||
github.com/Masterminds/semver/v3 v3.2.1
|
||||
github.com/athanorlabs/go-dleq v0.1.0
|
||||
github.com/athanorlabs/go-p2p-net v0.2.0
|
||||
github.com/athanorlabs/go-relayer v0.2.0
|
||||
github.com/btcsuite/btcd/btcutil v1.1.3
|
||||
github.com/cockroachdb/apd/v3 v3.1.2
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0
|
||||
|
||||
2
go.sum
2
go.sum
@@ -41,8 +41,6 @@ github.com/athanorlabs/go-dleq v0.1.0 h1:0/llWZG8fz2uintMBKOiBC502zCsDA8nt8vxI73
|
||||
github.com/athanorlabs/go-dleq v0.1.0/go.mod h1:DWry6jSD7A13MKmeZA0AX3/xBeQCXDoygX99VPwL3yU=
|
||||
github.com/athanorlabs/go-p2p-net v0.2.0 h1:+VpAN10Ys0B28QDXQRaDySvNfHS99Jt83Qq1sUhEnG4=
|
||||
github.com/athanorlabs/go-p2p-net v0.2.0/go.mod h1:egbDohZq6I4FzKaVytR+xZKUwA2OqTE6mr9dsNQPPbE=
|
||||
github.com/athanorlabs/go-relayer v0.2.0 h1:cYwEadgLWotWBlCx+uhLZphQna3EKEekHhNViHgaSSo=
|
||||
github.com/athanorlabs/go-relayer v0.2.0/go.mod h1:xh6P9KTXNS9zENAT3QyOBP3sdyHtefrtBLugKeI8oJ4=
|
||||
github.com/aymerick/raymond v2.0.3-0.20180322193309-b565731e1464+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g=
|
||||
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
|
||||
github.com/benbjohnson/clock v1.3.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
|
||||
|
||||
@@ -143,6 +143,7 @@ func (h *Host) SetHandlers(makerHandler MakerHandler, relayHandler RelayHandler)
|
||||
|
||||
h.h.SetStreamHandler(queryProtocolID, h.handleQueryStream)
|
||||
h.h.SetStreamHandler(relayProtocolID, h.handleRelayStream)
|
||||
h.h.SetStreamHandler(relayerQueryProtocolID, h.handleRelayerQueryStream)
|
||||
h.h.SetStreamHandler(swapID, h.handleProtocolStream)
|
||||
}
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ type mockMakerHandler struct {
|
||||
id types.Hash
|
||||
}
|
||||
|
||||
func (h *mockMakerHandler) GetOffers() []*types.Offer {
|
||||
func (*mockMakerHandler) GetOffers() []*types.Offer {
|
||||
return []*types.Offer{}
|
||||
}
|
||||
|
||||
@@ -50,7 +50,15 @@ type mockRelayHandler struct {
|
||||
t *testing.T
|
||||
}
|
||||
|
||||
func (h *mockRelayHandler) HandleRelayClaimRequest(_ peer.ID, _ *RelayClaimRequest) (*RelayClaimResponse, error) {
|
||||
func (*mockRelayHandler) GetRelayerAddressHash() (types.Hash, error) {
|
||||
return types.Hash{99}, nil
|
||||
}
|
||||
|
||||
func (*mockRelayHandler) HasOngoingSwapAsTaker(_ peer.ID) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (*mockRelayHandler) HandleRelayClaimRequest(_ peer.ID, _ *RelayClaimRequest) (*RelayClaimResponse, error) {
|
||||
return &RelayClaimResponse{
|
||||
TxHash: mockEthTXHash,
|
||||
}, nil
|
||||
@@ -68,11 +76,11 @@ func (s *mockSwapState) OfferID() types.Hash {
|
||||
return testID
|
||||
}
|
||||
|
||||
func (s *mockSwapState) HandleProtocolMessage(_ Message) error {
|
||||
func (*mockSwapState) HandleProtocolMessage(_ Message) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *mockSwapState) Exit() error {
|
||||
func (*mockSwapState) Exit() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ import (
|
||||
const (
|
||||
Unknown byte = iota // occupies the uninitialized value
|
||||
QueryResponseType
|
||||
RelayerQueryResponseType
|
||||
RelayClaimRequestType
|
||||
RelayClaimResponseType
|
||||
SendKeysType
|
||||
@@ -39,6 +40,8 @@ func TypeToString(t byte) string {
|
||||
return "SendKeysMessage"
|
||||
case NotifyETHLockedType:
|
||||
return "NotifyETHLocked"
|
||||
case RelayerQueryResponseType:
|
||||
return "RelayerQueryResponseType"
|
||||
case RelayClaimRequestType:
|
||||
return "RelayClaimRequestType"
|
||||
case RelayClaimResponseType:
|
||||
@@ -62,6 +65,8 @@ func DecodeMessage(b []byte) (common.Message, error) {
|
||||
switch msgType {
|
||||
case QueryResponseType:
|
||||
msg = new(QueryResponse)
|
||||
case RelayerQueryResponseType:
|
||||
msg = new(RelayerQueryResponse)
|
||||
case RelayClaimRequestType:
|
||||
msg = new(RelayClaimRequest)
|
||||
case RelayClaimResponseType:
|
||||
|
||||
@@ -13,21 +13,42 @@ import (
|
||||
contracts "github.com/athanorlabs/atomic-swap/ethereum"
|
||||
)
|
||||
|
||||
// RelayerQueryResponse is sent from a relayer to the opener of
|
||||
// a /relayerquery/0 stream.
|
||||
type RelayerQueryResponse struct {
|
||||
AddressHash []byte `json:"address" validate:"required,len=32"`
|
||||
}
|
||||
|
||||
// String converts the RelayerQueryResponse to a string usable for debugging purposes
|
||||
func (m *RelayerQueryResponse) String() string {
|
||||
return fmt.Sprintf("RelayerQueryResponse=%#v", m)
|
||||
}
|
||||
|
||||
// Encode implements the Encode() method of the common.Message interface which
|
||||
// prepends a message type byte before the message's JSON encoding.
|
||||
func (m *RelayerQueryResponse) Encode() ([]byte, error) {
|
||||
b, err := vjson.MarshalStruct(m)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return append([]byte{RelayerQueryResponseType}, b...), nil
|
||||
}
|
||||
|
||||
// Type implements the Type() method of the common.Message interface
|
||||
func (m *RelayerQueryResponse) Type() byte {
|
||||
return RelayerQueryResponseType
|
||||
}
|
||||
|
||||
// RelayClaimRequest implements common.Message for our p2p relay claim requests.
|
||||
type RelayClaimRequest struct {
|
||||
// OfferID is non-nil, if the request is from a maker to the taker of an
|
||||
// active swap. It is nil, if the request is being sent to a relay node,
|
||||
// because it advertised in the DHT.
|
||||
OfferID *types.Hash `json:"offerID"`
|
||||
SwapCreatorAddr ethcommon.Address `json:"swapCreatorAddr" validate:"required"`
|
||||
Swap *contracts.SwapCreatorSwap `json:"swap" validate:"required"`
|
||||
Secret []byte `json:"secret" validate:"required,len=32"`
|
||||
Signature []byte `json:"signature" validate:"required,len=65"`
|
||||
}
|
||||
|
||||
// RelayClaimResponse implements common.Message for our p2p relay claim responses
|
||||
type RelayClaimResponse struct {
|
||||
TxHash ethcommon.Hash `json:"transactionHash" validate:"required"`
|
||||
OfferID *types.Hash `json:"offerID"`
|
||||
RelaySwap *contracts.SwapCreatorRelaySwap `json:"relaySwap" validate:"required"`
|
||||
Secret []byte `json:"secret" validate:"required,len=32"`
|
||||
Signature []byte `json:"signature" validate:"required,len=65"`
|
||||
}
|
||||
|
||||
// String converts the RelayClaimRequest to a string usable for debugging purposes
|
||||
@@ -51,6 +72,11 @@ func (m *RelayClaimRequest) Type() byte {
|
||||
return RelayClaimRequestType
|
||||
}
|
||||
|
||||
// RelayClaimResponse implements common.Message for our p2p relay claim responses
|
||||
type RelayClaimResponse struct {
|
||||
TxHash ethcommon.Hash `json:"transactionHash" validate:"required"`
|
||||
}
|
||||
|
||||
// String converts the RelayClaimRequest to a string usable for debugging purposes
|
||||
func (m *RelayClaimResponse) String() string {
|
||||
return fmt.Sprintf("RelayClaimResponse=%#v", m)
|
||||
|
||||
90
net/relay.go
90
net/relay.go
@@ -13,12 +13,15 @@ import (
|
||||
libp2pnetwork "github.com/libp2p/go-libp2p/core/network"
|
||||
"github.com/libp2p/go-libp2p/core/peer"
|
||||
|
||||
"github.com/athanorlabs/atomic-swap/common/types"
|
||||
"github.com/athanorlabs/atomic-swap/net/message"
|
||||
)
|
||||
|
||||
const (
|
||||
relayProtocolID = "/relay/0"
|
||||
|
||||
relayerQueryProtocolID = "/relayerquery/0"
|
||||
|
||||
// RelayerProvidesStr is the DHT namespace advertised by nodes willing to relay
|
||||
// claims for arbitrary XMR makers.
|
||||
RelayerProvidesStr = "relayer"
|
||||
@@ -31,9 +34,87 @@ func (h *Host) DiscoverRelayers() ([]peer.ID, error) {
|
||||
return h.Discover(RelayerProvidesStr, defaultDiscoverTime)
|
||||
}
|
||||
|
||||
// we need the relayer to send a message containing
|
||||
// the address to send the fee to, so that the requester
|
||||
// can sign it.
|
||||
func (h *Host) handleRelayerQueryStream(stream libp2pnetwork.Stream) {
|
||||
defer func() { _ = stream.Close() }()
|
||||
|
||||
if !h.isRelayer {
|
||||
err := h.relayHandler.HasOngoingSwapAsTaker(stream.Conn().RemotePeer())
|
||||
if err != nil {
|
||||
// the returned error logs the peer ID
|
||||
log.Debugf("ignoring relayer query: %s", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
addressHash, err := h.relayHandler.GetRelayerAddressHash()
|
||||
if err != nil {
|
||||
log.Warnf("failed to get relayer address hash: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
addrResp := &message.RelayerQueryResponse{
|
||||
AddressHash: addressHash[:],
|
||||
}
|
||||
|
||||
log.Debugf("sending RelayerQueryResponse to peer %s", stream.Conn().RemotePeer())
|
||||
if err := p2pnet.WriteStreamMessage(stream, addrResp, stream.Conn().RemotePeer()); err != nil {
|
||||
log.Warnf("failed to send RelayClaimResponse message to peer: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
// QueryRelayerAddress opens a relay stream with a peer, and if they are a relayer,
|
||||
// they will respond with their relayer payout address.
|
||||
func (h *Host) QueryRelayerAddress(relayerID peer.ID) (types.Hash, error) {
|
||||
ctx, cancel := context.WithTimeout(h.ctx, connectionTimeout)
|
||||
defer cancel()
|
||||
|
||||
if err := h.h.Connect(ctx, peer.AddrInfo{ID: relayerID}); err != nil {
|
||||
return types.Hash{}, err
|
||||
}
|
||||
|
||||
stream, err := h.h.NewStream(ctx, relayerID, relayerQueryProtocolID)
|
||||
if err != nil {
|
||||
return types.Hash{}, fmt.Errorf("failed to open stream with peer: err=%w", err)
|
||||
}
|
||||
|
||||
log.Debugf("opened relayer query stream: %s", stream.Conn())
|
||||
resp, err := receiveRelayerQueryResponse(stream)
|
||||
if err != nil {
|
||||
return types.Hash{}, err
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func receiveRelayerQueryResponse(stream libp2pnetwork.Stream) (types.Hash, error) {
|
||||
const relayResponseTimeout = time.Second * 15
|
||||
|
||||
select {
|
||||
case msg := <-nextStreamMessage(stream, maxRelayMessageSize):
|
||||
if msg == nil {
|
||||
return types.Hash{}, errors.New("failed to read RelayerQueryResponse")
|
||||
}
|
||||
|
||||
resp, ok := msg.(*message.RelayerQueryResponse)
|
||||
if !ok {
|
||||
return types.Hash{}, fmt.Errorf("expected %s message but received %s",
|
||||
message.TypeToString(message.RelayClaimResponseType),
|
||||
message.TypeToString(msg.Type()))
|
||||
}
|
||||
|
||||
return [32]byte(resp.AddressHash), nil
|
||||
case <-time.After(relayResponseTimeout):
|
||||
return types.Hash{}, errors.New("timed out waiting for QueryResponse")
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Host) handleRelayStream(stream libp2pnetwork.Stream) {
|
||||
defer func() { _ = stream.Close() }()
|
||||
|
||||
// TODO: add timeout for receiving request
|
||||
msg, err := readStreamMessage(stream, maxRelayMessageSize)
|
||||
if err != nil {
|
||||
log.Debugf("error reading RelayClaimRequest: %s", err)
|
||||
@@ -72,15 +153,15 @@ func (h *Host) handleRelayStream(stream libp2pnetwork.Stream) {
|
||||
return
|
||||
}
|
||||
|
||||
log.Debugf("Relayed claim for %s with tx=%s", req.Swap.Claimer, resp.TxHash)
|
||||
log.Debugf("Relayed claim for %s with tx=%s", req.RelaySwap.Swap.Claimer, resp.TxHash)
|
||||
if err := p2pnet.WriteStreamMessage(stream, resp, stream.Conn().RemotePeer()); err != nil {
|
||||
log.Warnf("failed to send RelayClaimResponse message to peer: %s", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// SubmitClaimToRelayer sends a request to relay a swap claim to a peer.
|
||||
func (h *Host) SubmitClaimToRelayer(relayerID peer.ID, request *RelayClaimRequest) (*RelayClaimResponse, error) {
|
||||
// SubmitRelayRequest sends a request to relay a swap claim to a peer.
|
||||
func (h *Host) SubmitRelayRequest(relayerID peer.ID, request *RelayClaimRequest) (*RelayClaimResponse, error) {
|
||||
ctx, cancel := context.WithTimeout(h.ctx, connectionTimeout)
|
||||
defer cancel()
|
||||
|
||||
@@ -92,9 +173,8 @@ func (h *Host) SubmitClaimToRelayer(relayerID peer.ID, request *RelayClaimReques
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to open stream with peer: err=%w", err)
|
||||
}
|
||||
|
||||
defer func() { _ = stream.Close() }()
|
||||
log.Debugf("opened relay stream: %s", stream.Conn())
|
||||
log.Debugf("opened relay stream with peer %s", relayerID)
|
||||
|
||||
if err := p2pnet.WriteStreamMessage(stream, request, relayerID); err != nil {
|
||||
log.Warnf("failed to send RelayClaimRequest to peer: err=%s", err)
|
||||
|
||||
@@ -58,18 +58,24 @@ func createTestClaimRequest() *message.RelayClaimRequest {
|
||||
secret := [32]byte{0x1}
|
||||
sig := [65]byte{0x1}
|
||||
|
||||
swap := contracts.SwapCreatorSwap{
|
||||
Owner: ethcommon.Address{0x1},
|
||||
Claimer: ethcommon.Address{0x1},
|
||||
PubKeyClaim: [32]byte{0x1},
|
||||
PubKeyRefund: [32]byte{0x1},
|
||||
Timeout0: big.NewInt(time.Now().Add(30 * time.Minute).Unix()),
|
||||
Timeout1: big.NewInt(time.Now().Add(60 * time.Minute).Unix()),
|
||||
Asset: ethcommon.Address(types.EthAssetETH),
|
||||
Value: big.NewInt(1e18),
|
||||
Nonce: big.NewInt(1),
|
||||
}
|
||||
|
||||
req := &message.RelayClaimRequest{
|
||||
SwapCreatorAddr: ethcommon.Address{0x1},
|
||||
Swap: &contracts.SwapCreatorSwap{
|
||||
Owner: ethcommon.Address{0x1},
|
||||
Claimer: ethcommon.Address{0x1},
|
||||
PubKeyClaim: [32]byte{0x1},
|
||||
PubKeyRefund: [32]byte{0x1},
|
||||
Timeout0: big.NewInt(time.Now().Add(30 * time.Minute).Unix()),
|
||||
Timeout1: big.NewInt(time.Now().Add(60 * time.Minute).Unix()),
|
||||
Asset: ethcommon.Address(types.EthAssetETH),
|
||||
Value: big.NewInt(1e18),
|
||||
Nonce: big.NewInt(1),
|
||||
RelaySwap: &contracts.SwapCreatorRelaySwap{
|
||||
Swap: swap,
|
||||
Fee: big.NewInt(9e15),
|
||||
RelayerHash: [32]byte{1},
|
||||
SwapCreator: ethcommon.Address{0x3},
|
||||
},
|
||||
Secret: secret[:],
|
||||
Signature: sig[:],
|
||||
@@ -81,8 +87,11 @@ func createTestClaimRequest() *message.RelayClaimRequest {
|
||||
func TestHost_SubmitClaimToRelayer_dhtRelayer(t *testing.T) {
|
||||
ha, hb := twoHostRelayerSetup(t)
|
||||
|
||||
_, err := ha.QueryRelayerAddress(hb.PeerID())
|
||||
require.NoError(t, err)
|
||||
|
||||
// success path ha->hb, hb is a DHT relayer
|
||||
resp, err := ha.SubmitClaimToRelayer(hb.PeerID(), createTestClaimRequest())
|
||||
resp, err := ha.SubmitRelayRequest(hb.PeerID(), createTestClaimRequest())
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, mockEthTXHash.Hex(), resp.TxHash.Hex())
|
||||
|
||||
@@ -90,7 +99,7 @@ func TestHost_SubmitClaimToRelayer_dhtRelayer(t *testing.T) {
|
||||
// does not pass back the exact reason for rejecting a claim to avoid
|
||||
// possible privacy data leaks, but in this case it is because hb is not
|
||||
// a DHT advertising relayer.
|
||||
_, err = hb.SubmitClaimToRelayer(ha.PeerID(), createTestClaimRequest())
|
||||
_, err = hb.SubmitRelayRequest(ha.PeerID(), createTestClaimRequest())
|
||||
require.ErrorContains(t, err, "failed to read RelayClaimResponse")
|
||||
}
|
||||
|
||||
@@ -102,7 +111,7 @@ func TestHost_SubmitClaimToRelayer_xmrTakerRelayer(t *testing.T) {
|
||||
request.OfferID = &offerID
|
||||
|
||||
// should ignore offerID and succeed
|
||||
response, err := hb.SubmitClaimToRelayer(ha.PeerID(), request)
|
||||
response, err := hb.SubmitRelayRequest(ha.PeerID(), request)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, mockEthTXHash, response.TxHash)
|
||||
}
|
||||
@@ -112,11 +121,11 @@ func TestHost_SubmitClaimToRelayer_fail(t *testing.T) {
|
||||
|
||||
req := createTestClaimRequest()
|
||||
req.Secret = []byte{0x1} // wrong size
|
||||
_, err := ha.SubmitClaimToRelayer(hb.PeerID(), req)
|
||||
_, err := ha.SubmitRelayRequest(hb.PeerID(), req)
|
||||
require.ErrorContains(t, err, "Field validation for 'Secret' failed on the 'len' tag")
|
||||
|
||||
req = createTestClaimRequest()
|
||||
req.Signature = []byte{0x1, 0x2} // wrong size
|
||||
_, err = ha.SubmitClaimToRelayer(hb.PeerID(), req)
|
||||
_, err = ha.SubmitRelayRequest(hb.PeerID(), req)
|
||||
require.ErrorContains(t, err, "Field validation for 'Signature' failed on the 'len' tag")
|
||||
}
|
||||
|
||||
@@ -4,13 +4,12 @@
|
||||
package net
|
||||
|
||||
import (
|
||||
libp2pnetwork "github.com/libp2p/go-libp2p/core/network"
|
||||
"github.com/libp2p/go-libp2p/core/peer"
|
||||
|
||||
"github.com/athanorlabs/atomic-swap/common"
|
||||
"github.com/athanorlabs/atomic-swap/common/types"
|
||||
"github.com/athanorlabs/atomic-swap/net/message"
|
||||
|
||||
libp2pnetwork "github.com/libp2p/go-libp2p/core/network"
|
||||
)
|
||||
|
||||
type SwapState = common.SwapStateNet //nolint:revive
|
||||
@@ -35,7 +34,9 @@ type MakerHandler interface {
|
||||
// RelayHandler handles relay claim requests. It is implemented by
|
||||
// *backend.backend.
|
||||
type RelayHandler interface {
|
||||
GetRelayerAddressHash() (types.Hash, error)
|
||||
HandleRelayClaimRequest(remotePeer peer.ID, msg *RelayClaimRequest) (*RelayClaimResponse, error)
|
||||
HasOngoingSwapAsTaker(remotePeer peer.ID) error
|
||||
}
|
||||
|
||||
type swap struct {
|
||||
|
||||
@@ -7,12 +7,14 @@ package backend
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
ethcommon "github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/libp2p/go-libp2p/core/peer"
|
||||
|
||||
"github.com/athanorlabs/atomic-swap/common"
|
||||
@@ -32,8 +34,9 @@ import (
|
||||
type NetSender interface {
|
||||
SendSwapMessage(common.Message, types.Hash) error
|
||||
CloseProtocolStream(id types.Hash)
|
||||
DiscoverRelayers() ([]peer.ID, error) // Only used by Maker
|
||||
SubmitClaimToRelayer(peer.ID, *message.RelayClaimRequest) (*message.RelayClaimResponse, error) // Only used by Taker
|
||||
DiscoverRelayers() ([]peer.ID, error) // Only used by Maker
|
||||
QueryRelayerAddress(peer.ID) (types.Hash, error) // only used by taker
|
||||
SubmitRelayRequest(peer.ID, *message.RelayClaimRequest) (*message.RelayClaimResponse, error) // only used by taker
|
||||
}
|
||||
|
||||
// RecoveryDB is implemented by *db.RecoveryDB
|
||||
@@ -66,6 +69,14 @@ type Backend interface {
|
||||
// helpers
|
||||
NewSwapCreator(addr ethcommon.Address) (*contracts.SwapCreator, error)
|
||||
HandleRelayClaimRequest(remotePeer peer.ID, request *message.RelayClaimRequest) (*message.RelayClaimResponse, error)
|
||||
GetRelayerAddressHash() (types.Hash, error)
|
||||
HasOngoingSwapAsTaker(peer.ID) error
|
||||
SubmitClaimToRelayer(
|
||||
peer.ID,
|
||||
*types.Hash,
|
||||
*contracts.SwapCreatorRelaySwap,
|
||||
[32]byte,
|
||||
) (*message.RelayClaimResponse, error) // Only used by Taker
|
||||
|
||||
// getters
|
||||
Ctx() context.Context
|
||||
@@ -107,6 +118,10 @@ type backend struct {
|
||||
|
||||
// network interface
|
||||
NetSender
|
||||
|
||||
// map of hash(relayer address || salt) -> salt
|
||||
relayerHashMu sync.RWMutex
|
||||
relayerHash map[types.Hash][4]byte
|
||||
}
|
||||
|
||||
// Config is the config for the Backend
|
||||
@@ -144,6 +159,7 @@ func NewBackend(cfg *Config) (Backend, error) {
|
||||
NetSender: cfg.Net,
|
||||
perSwapXMRDepositAddr: make(map[types.Hash]*mcrypto.Address),
|
||||
recoveryDB: cfg.RecoveryDB,
|
||||
relayerHash: make(map[types.Hash][4]byte),
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -236,11 +252,34 @@ func (b *backend) ClearXMRDepositAddress(offerID types.Hash) {
|
||||
delete(b.perSwapXMRDepositAddr, offerID)
|
||||
}
|
||||
|
||||
// HasOngoingSwapAsTaker returns nil if we have an ongoing swap with the given peer where
|
||||
// we're the xmrtaker, otherwise returns an error.
|
||||
func (b *backend) HasOngoingSwapAsTaker(remotePeer peer.ID) error {
|
||||
swaps, err := b.swapManager.GetOngoingSwaps()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, swap := range swaps {
|
||||
if swap.PeerID != remotePeer {
|
||||
continue
|
||||
}
|
||||
|
||||
if swap.IsTaker() {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Errorf("do not have an ongoing swap with peer %s as taker", remotePeer)
|
||||
}
|
||||
|
||||
// HandleRelayClaimRequest validates and sends the transaction for a relay claim request
|
||||
func (b *backend) HandleRelayClaimRequest(
|
||||
remotePeer peer.ID,
|
||||
request *message.RelayClaimRequest,
|
||||
) (*message.RelayClaimResponse, error) {
|
||||
defer b.clearRelayerAddressHash(request.RelaySwap.RelayerHash)
|
||||
|
||||
if request.OfferID != nil {
|
||||
has := b.swapManager.HasOngoingSwap(*request.OfferID)
|
||||
if !has {
|
||||
@@ -273,15 +312,68 @@ func (b *backend) HandleRelayClaimRequest(
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("swap info for taker claim request not found: %w", err)
|
||||
}
|
||||
if swapInfo.SwapID != request.Swap.SwapID() {
|
||||
if swapInfo.SwapID != request.RelaySwap.Swap.SwapID() {
|
||||
return nil, errors.New("counterparty claim request has invalid swap ID")
|
||||
}
|
||||
}
|
||||
|
||||
b.relayerHashMu.RLock()
|
||||
salt := b.relayerHash[request.RelaySwap.RelayerHash]
|
||||
b.relayerHashMu.RUnlock()
|
||||
|
||||
return relayer.ValidateAndSendTransaction(
|
||||
b.Ctx(),
|
||||
request,
|
||||
b.ETHClient(),
|
||||
b.SwapCreatorAddr(),
|
||||
salt,
|
||||
)
|
||||
}
|
||||
|
||||
func (b *backend) GetRelayerAddressHash() (types.Hash, error) {
|
||||
address := b.ETHClient().Address()
|
||||
var salt [4]byte
|
||||
_, err := rand.Read(salt[:])
|
||||
if err != nil {
|
||||
return types.Hash{}, err
|
||||
}
|
||||
|
||||
hash := crypto.Keccak256Hash(append(address.Bytes(), salt[:]...))
|
||||
b.relayerHashMu.Lock()
|
||||
defer b.relayerHashMu.Unlock()
|
||||
b.relayerHash[hash] = salt
|
||||
return hash, nil
|
||||
}
|
||||
|
||||
func (b *backend) clearRelayerAddressHash(hash types.Hash) {
|
||||
b.relayerHashMu.Lock()
|
||||
defer b.relayerHashMu.Unlock()
|
||||
delete(b.relayerHash, hash)
|
||||
}
|
||||
|
||||
func (b *backend) SubmitClaimToRelayer(
|
||||
relayerID peer.ID,
|
||||
offerID *types.Hash,
|
||||
relaySwap *contracts.SwapCreatorRelaySwap,
|
||||
secret [32]byte,
|
||||
) (*message.RelayClaimResponse, error) {
|
||||
// get the relayer's address hash
|
||||
relayerAddrHash, err := b.QueryRelayerAddress(relayerID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// set relayer address hash and sign as front-run prevention
|
||||
relaySwap.RelayerHash = relayerAddrHash
|
||||
|
||||
req, err := relayer.CreateRelayClaimRequest(b.ETHClient().PrivateKey(), relaySwap, secret)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if offerID != nil {
|
||||
req.OfferID = offerID
|
||||
}
|
||||
|
||||
return b.SubmitRelayRequest(relayerID, req)
|
||||
}
|
||||
|
||||
@@ -11,7 +11,6 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum"
|
||||
"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/ethclient"
|
||||
@@ -19,10 +18,9 @@ import (
|
||||
"github.com/athanorlabs/atomic-swap/coins"
|
||||
"github.com/athanorlabs/atomic-swap/common"
|
||||
"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/net/message"
|
||||
"github.com/athanorlabs/atomic-swap/relayer"
|
||||
)
|
||||
|
||||
// claimFunds redeems XMRMaker's ETH funds by calling Claim() on the contract
|
||||
@@ -124,12 +122,17 @@ func checkForMinClaimBalance(ctx context.Context, ec extethclient.EthClient) (bo
|
||||
|
||||
// relayClaimWithXMRTaker relays the claim to the swap's XMR taker, who should
|
||||
// process the claim even if they are not relaying claims for everyone.
|
||||
func (s *swapState) relayClaimWithXMRTaker(request *message.RelayClaimRequest) (*ethtypes.Receipt, error) {
|
||||
// only requests to the XMR taker set the offerID field
|
||||
request.OfferID = &s.offer.ID
|
||||
defer func() { request.OfferID = nil }()
|
||||
func (s *swapState) relayClaimWithXMRTaker() (*ethtypes.Receipt, error) {
|
||||
secret := s.getSecret()
|
||||
relaySwap := &contracts.SwapCreatorRelaySwap{
|
||||
Swap: *s.contractSwap,
|
||||
SwapCreator: s.swapCreatorAddr,
|
||||
Fee: coins.RelayerFeeWei,
|
||||
// this is set when we receive the relayer's address hash
|
||||
RelayerHash: types.Hash{},
|
||||
}
|
||||
|
||||
response, err := s.Backend.SubmitClaimToRelayer(s.info.PeerID, request)
|
||||
response, err := s.Backend.SubmitClaimToRelayer(s.info.PeerID, &s.offer.ID, relaySwap, secret)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -153,7 +156,16 @@ func (s *swapState) relayClaimWithXMRTaker(request *message.RelayClaimRequest) (
|
||||
// claimWithAdvertisedRelayers relays the claim to nodes that advertise
|
||||
// themselves as relayers in the DHT until the claim succeeds, all relayers have
|
||||
// been tried, or the context is cancelled.
|
||||
func (s *swapState) claimWithAdvertisedRelayers(request *message.RelayClaimRequest) (*ethtypes.Receipt, error) {
|
||||
func (s *swapState) claimWithAdvertisedRelayers() (*ethtypes.Receipt, error) {
|
||||
secret := s.getSecret()
|
||||
relaySwap := &contracts.SwapCreatorRelaySwap{
|
||||
Swap: *s.contractSwap,
|
||||
SwapCreator: s.swapCreatorAddr,
|
||||
Fee: coins.RelayerFeeWei,
|
||||
// this is set when we receive the relayer's address hash
|
||||
RelayerHash: types.Hash{},
|
||||
}
|
||||
|
||||
relayers, err := s.Backend.DiscoverRelayers()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -170,7 +182,7 @@ func (s *swapState) claimWithAdvertisedRelayers(request *message.RelayClaimReque
|
||||
}
|
||||
|
||||
log.Debugf("submitting claim to relayer with peer ID %s", relayerPeerID)
|
||||
resp, err := s.Backend.SubmitClaimToRelayer(relayerPeerID, request)
|
||||
resp, err := s.Backend.SubmitClaimToRelayer(relayerPeerID, nil, relaySwap, secret)
|
||||
if err != nil {
|
||||
log.Warnf("failed to submit tx to relayer: %s", err)
|
||||
continue
|
||||
@@ -204,31 +216,11 @@ func (s *swapState) claimWithAdvertisedRelayers(request *message.RelayClaimReque
|
||||
// operations more generally. Note that the receipt returned is for a
|
||||
// transaction created by the remote relayer, not by us.
|
||||
func (s *swapState) claimWithRelay() (*ethtypes.Receipt, error) {
|
||||
forwarderAddr, err := s.SwapCreator().TrustedForwarder(&bind.CallOpts{Context: s.ctx})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
secret := s.getSecret()
|
||||
|
||||
request, err := relayer.CreateRelayClaimRequest(
|
||||
s.ctx,
|
||||
s.ETHClient().PrivateKey(),
|
||||
s.ETHClient().Raw(),
|
||||
s.swapCreatorAddr,
|
||||
forwarderAddr,
|
||||
s.contractSwap,
|
||||
&secret,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
receipt, err := s.claimWithAdvertisedRelayers(request)
|
||||
receipt, err := s.claimWithAdvertisedRelayers()
|
||||
if err != nil {
|
||||
log.Warnf("failed to relay with DHT-advertised relayers: %s", err)
|
||||
log.Infof("falling back to swap counterparty as relayer")
|
||||
return s.relayClaimWithXMRTaker(request)
|
||||
return s.relayClaimWithXMRTaker()
|
||||
}
|
||||
return receipt, nil
|
||||
}
|
||||
|
||||
@@ -31,7 +31,6 @@ import (
|
||||
"github.com/athanorlabs/atomic-swap/tests"
|
||||
|
||||
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
||||
ethcommon "github.com/ethereum/go-ethereum/common"
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
@@ -62,12 +61,16 @@ func (n *mockNet) DiscoverRelayers() ([]peer.ID, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (n *mockNet) SubmitClaimToRelayer(_ peer.ID, _ *message.RelayClaimRequest) (*message.RelayClaimResponse, error) {
|
||||
func (n *mockNet) SubmitRelayRequest(_ peer.ID, _ *message.RelayClaimRequest) (*message.RelayClaimResponse, error) {
|
||||
return new(message.RelayClaimResponse), nil
|
||||
}
|
||||
|
||||
func (n *mockNet) CloseProtocolStream(_ types.Hash) {}
|
||||
|
||||
func (n *mockNet) QueryRelayerAddress(_ peer.ID) (types.Hash, error) {
|
||||
return types.Hash{}, nil
|
||||
}
|
||||
|
||||
func newSwapManager(t *testing.T) pswap.Manager {
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
@@ -88,8 +91,7 @@ func newBackendAndNet(t *testing.T) (backend.Backend, *mockNet) {
|
||||
txOpts, err := bind.NewKeyedTransactorWithChainID(pk, chainID)
|
||||
require.NoError(t, err)
|
||||
|
||||
var forwarderAddr ethcommon.Address
|
||||
_, tx, _, err := contracts.DeploySwapCreator(txOpts, ec, forwarderAddr)
|
||||
_, tx, _, err := contracts.DeploySwapCreator(txOpts, ec)
|
||||
require.NoError(t, err)
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
|
||||
@@ -107,10 +107,7 @@ func (s *swapState) handleNotifyETHLocked(msg *message.NotifyETHLocked) error {
|
||||
}
|
||||
|
||||
contractAddr := msg.Address
|
||||
// note: this function verifies the forwarder code as well, even if we aren't using a relayer,
|
||||
// in which case it's not relevant to us and we don't need to verify it.
|
||||
// doesn't hurt though I suppose.
|
||||
_, err = contracts.CheckSwapCreatorContractCode(s.ctx, s.Backend.ETHClient().Raw(), contractAddr)
|
||||
err = contracts.CheckSwapCreatorContractCode(s.ctx, s.Backend.ETHClient().Raw(), contractAddr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -46,11 +46,7 @@ func validateMinBalance(
|
||||
// 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,
|
||||
)
|
||||
neededGas := big.NewInt(contracts.MaxNewSwapETHGas + contracts.MaxSetReadyGas + contracts.MaxRefundETHGas)
|
||||
neededWeiForGas := new(big.Int).Mul(neededGas, gasPriceWei)
|
||||
neededBalanceWei := new(big.Int).Add(providedAmtWei, neededWeiForGas)
|
||||
|
||||
|
||||
@@ -387,20 +387,24 @@ func (s *swapState) exit() error {
|
||||
// we should also refund in this case, since we might be past t1.
|
||||
receipt, err := s.tryRefund()
|
||||
if err != nil {
|
||||
if errors.Is(err, errRefundSwapCompleted) {
|
||||
s.clearNextExpectedEvent(types.CompletedRefund)
|
||||
log.Infof("swap was already refunded")
|
||||
return nil
|
||||
}
|
||||
if errors.Is(err, errRefundSwapCompleted) || strings.Contains(err.Error(), revertSwapCompleted) {
|
||||
log.Infof("swap was already completed")
|
||||
|
||||
if strings.Contains(err.Error(), revertSwapCompleted) {
|
||||
// note: this should NOT ever error; it could if the ethclient
|
||||
// or monero clients crash during the course of the claim,
|
||||
// but that would be very bad.
|
||||
err = s.tryClaim()
|
||||
if err != nil {
|
||||
if errors.Is(err, errNoClaimLogsFound) {
|
||||
// in this case, assume we refunded
|
||||
s.clearNextExpectedEvent(types.CompletedRefund)
|
||||
return nil
|
||||
}
|
||||
|
||||
// note: this should NOT occur; it could if the ethclient
|
||||
// or monero clients crash during the course of the claim,
|
||||
// but that would be very bad.
|
||||
return fmt.Errorf("failed to claim even though swap was completed on-chain: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("failed to refund: %w", err)
|
||||
|
||||
@@ -65,12 +65,16 @@ func (n *mockNet) DiscoverRelayers() ([]peer.ID, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (n *mockNet) SubmitClaimToRelayer(_ peer.ID, _ *message.RelayClaimRequest) (*message.RelayClaimResponse, error) {
|
||||
func (n *mockNet) SubmitRelayRequest(_ peer.ID, _ *message.RelayClaimRequest) (*message.RelayClaimResponse, error) {
|
||||
return new(message.RelayClaimResponse), nil
|
||||
}
|
||||
|
||||
func (n *mockNet) CloseProtocolStream(_ types.Hash) {}
|
||||
|
||||
func (*mockNet) QueryRelayerAddress(_ peer.ID) (types.Hash, error) {
|
||||
return types.Hash{99}, nil
|
||||
}
|
||||
|
||||
func newSwapManager(t *testing.T) pswap.Manager {
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
@@ -94,8 +98,7 @@ func newBackendAndNet(t *testing.T) (backend.Backend, *mockNet) {
|
||||
txOpts, err := bind.NewKeyedTransactorWithChainID(pk, ec.ChainID())
|
||||
require.NoError(t, err)
|
||||
|
||||
var forwarderAddr ethcommon.Address
|
||||
_, tx, _, err := contracts.DeploySwapCreator(txOpts, ec.Raw(), forwarderAddr)
|
||||
_, tx, _, err := contracts.DeploySwapCreator(txOpts, ec.Raw())
|
||||
require.NoError(t, err)
|
||||
|
||||
addr, err := bind.WaitDeployed(ctx, ec.Raw(), tx)
|
||||
|
||||
@@ -5,54 +5,67 @@
|
||||
package relayer
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/ecdsa"
|
||||
"fmt"
|
||||
|
||||
ethcommon "github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/ethclient"
|
||||
ethcrypto "github.com/ethereum/go-ethereum/crypto"
|
||||
logging "github.com/ipfs/go-log"
|
||||
|
||||
contracts "github.com/athanorlabs/atomic-swap/ethereum"
|
||||
"github.com/athanorlabs/atomic-swap/net/message"
|
||||
)
|
||||
|
||||
const (
|
||||
relayedClaimGas = 70000 // worst case gas usage for the claimRelayer swapFactory call
|
||||
forwarderClaimGas = 156000 // worst case gas usage when using forwarder to claim
|
||||
)
|
||||
|
||||
var log = logging.Logger("relayer")
|
||||
|
||||
// CreateRelayClaimRequest fills and returns a RelayClaimRequest ready for
|
||||
// submission to a relayer.
|
||||
func CreateRelayClaimRequest(
|
||||
ctx context.Context,
|
||||
claimerEthKey *ecdsa.PrivateKey,
|
||||
ec *ethclient.Client,
|
||||
swapCreatorAddr ethcommon.Address,
|
||||
forwarderAddr ethcommon.Address,
|
||||
swap *contracts.SwapCreatorSwap,
|
||||
secret *[32]byte,
|
||||
relaySwap *contracts.SwapCreatorRelaySwap,
|
||||
secret [32]byte,
|
||||
) (*message.RelayClaimRequest, error) {
|
||||
|
||||
signature, err := createForwarderSignature(
|
||||
ctx,
|
||||
signature, err := createRelayClaimSignature(
|
||||
claimerEthKey,
|
||||
ec,
|
||||
swapCreatorAddr,
|
||||
forwarderAddr,
|
||||
swap,
|
||||
secret,
|
||||
relaySwap,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &message.RelayClaimRequest{
|
||||
OfferID: nil, // set elsewhere if sending to counterparty
|
||||
SwapCreatorAddr: swapCreatorAddr,
|
||||
Swap: swap,
|
||||
Secret: secret[:],
|
||||
Signature: signature,
|
||||
OfferID: nil, // set elsewhere if sending to counterparty
|
||||
RelaySwap: relaySwap,
|
||||
Secret: secret[:],
|
||||
Signature: signature,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func createRelayClaimSignature(
|
||||
claimerEthKey *ecdsa.PrivateKey,
|
||||
relaySwap *contracts.SwapCreatorRelaySwap,
|
||||
) ([]byte, error) {
|
||||
signerAddress := ethcrypto.PubkeyToAddress(claimerEthKey.PublicKey)
|
||||
if relaySwap.Swap.Claimer != signerAddress {
|
||||
return nil, fmt.Errorf("signing key %s does not match claimer %s", signerAddress, relaySwap.Swap.Claimer)
|
||||
}
|
||||
|
||||
// signature format is (r || s || v), v = 27/28
|
||||
signature, err := Sign(claimerEthKey, relaySwap.Hash())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to sign relay request: %w", err)
|
||||
}
|
||||
|
||||
return signature, nil
|
||||
}
|
||||
|
||||
// Sign signs the given digest and returns a 65-byte signature in (r,s,v) format.
|
||||
func Sign(key *ecdsa.PrivateKey, digest [32]byte) ([]byte, error) {
|
||||
sig, err := ethcrypto.Sign(digest[:], key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Ethereum wants 27/28 for v
|
||||
sig[64] += 27
|
||||
return sig, nil
|
||||
}
|
||||
|
||||
@@ -22,24 +22,19 @@ import (
|
||||
|
||||
// Speed up tests a little by giving deployContracts(...) a package-level cache.
|
||||
// These variables should not be accessed by other functions.
|
||||
var _forwarderAddr *ethcommon.Address
|
||||
var _swapCreatorAddr *ethcommon.Address
|
||||
|
||||
// deployContracts deploys and returns the swapCreator and forwarder addresses.
|
||||
func deployContracts(t *testing.T, ec *ethclient.Client, key *ecdsa.PrivateKey) (ethcommon.Address, ethcommon.Address) {
|
||||
// deployContracts deploys and returns the swapCreator addresses.
|
||||
func deployContracts(t *testing.T, ec *ethclient.Client, key *ecdsa.PrivateKey) ethcommon.Address {
|
||||
ctx := context.Background()
|
||||
|
||||
if _forwarderAddr == nil || _swapCreatorAddr == nil {
|
||||
forwarderAddr, err := contracts.DeployGSNForwarderWithKey(ctx, ec, key)
|
||||
require.NoError(t, err)
|
||||
_forwarderAddr = &forwarderAddr
|
||||
|
||||
swapCreatorAddr, _, err := contracts.DeploySwapCreatorWithKey(ctx, ec, key, forwarderAddr)
|
||||
if _swapCreatorAddr == nil {
|
||||
swapCreatorAddr, _, err := contracts.DeploySwapCreatorWithKey(ctx, ec, key)
|
||||
require.NoError(t, err)
|
||||
_swapCreatorAddr = &swapCreatorAddr
|
||||
}
|
||||
|
||||
return *_swapCreatorAddr, *_forwarderAddr
|
||||
return *_swapCreatorAddr
|
||||
}
|
||||
|
||||
func createTestSwap(claimer ethcommon.Address) *contracts.SwapCreatorSwap {
|
||||
@@ -57,21 +52,26 @@ func createTestSwap(claimer ethcommon.Address) *contracts.SwapCreatorSwap {
|
||||
}
|
||||
|
||||
func TestCreateRelayClaimRequest(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
ethKey := tests.GetMakerTestKey(t)
|
||||
claimer := crypto.PubkeyToAddress(*ethKey.Public().(*ecdsa.PublicKey))
|
||||
ec, _ := tests.NewEthClient(t)
|
||||
secret := [32]byte{0x1}
|
||||
swapCreatorAddr, forwarderAddr := deployContracts(t, ec, ethKey)
|
||||
swapCreatorAddr := deployContracts(t, ec, ethKey)
|
||||
|
||||
// success path
|
||||
swap := createTestSwap(claimer)
|
||||
req, err := CreateRelayClaimRequest(ctx, ethKey, ec, swapCreatorAddr, forwarderAddr, swap, &secret)
|
||||
relaySwap := &contracts.SwapCreatorRelaySwap{
|
||||
Swap: *swap,
|
||||
Fee: big.NewInt(1),
|
||||
SwapCreator: swapCreatorAddr,
|
||||
RelayerHash: types.Hash{},
|
||||
}
|
||||
req, err := CreateRelayClaimRequest(ethKey, relaySwap, secret)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, req)
|
||||
|
||||
// change the ethkey to not match the claimer address to trigger the error path
|
||||
ethKey = tests.GetTakerTestKey(t)
|
||||
_, err = CreateRelayClaimRequest(ctx, ethKey, ec, swapCreatorAddr, forwarderAddr, swap, &secret)
|
||||
require.ErrorContains(t, err, "signing key does not match claimer")
|
||||
_, err = CreateRelayClaimRequest(ethKey, relaySwap, secret)
|
||||
require.ErrorContains(t, err, "does not match claimer")
|
||||
}
|
||||
|
||||
@@ -1,124 +0,0 @@
|
||||
// Copyright 2023 The AthanorLabs/atomic-swap Authors
|
||||
// SPDX-License-Identifier: LGPL-3.0-only
|
||||
|
||||
package relayer
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/ecdsa"
|
||||
"fmt"
|
||||
"math/big"
|
||||
|
||||
rcommon "github.com/athanorlabs/go-relayer/common"
|
||||
"github.com/athanorlabs/go-relayer/impls/gsnforwarder"
|
||||
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
||||
ethcommon "github.com/ethereum/go-ethereum/common"
|
||||
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"
|
||||
)
|
||||
|
||||
func createForwarderSignature(
|
||||
ctx context.Context,
|
||||
claimerEthKey *ecdsa.PrivateKey,
|
||||
ec *ethclient.Client,
|
||||
swapCreatorAddr ethcommon.Address,
|
||||
forwarderAddr ethcommon.Address,
|
||||
swap *contracts.SwapCreatorSwap,
|
||||
secret *[32]byte,
|
||||
) ([]byte, error) {
|
||||
|
||||
if swap.Claimer != ethcrypto.PubkeyToAddress(claimerEthKey.PublicKey) {
|
||||
return nil, fmt.Errorf("signing key does not match claimer %s", swap.Claimer)
|
||||
}
|
||||
|
||||
forwarder, domainSeparator, err := getForwarderAndDomainSeparator(ctx, ec, forwarderAddr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
nonce, err := forwarder.GetNonce(&bind.CallOpts{Context: ctx}, swap.Claimer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
forwarderReq, err := createForwarderRequest(
|
||||
nonce,
|
||||
swapCreatorAddr,
|
||||
swap,
|
||||
secret,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
digest, err := rcommon.GetForwardRequestDigestToSign(forwarderReq, *domainSeparator, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get forward request digest: %w", err)
|
||||
}
|
||||
|
||||
signature, err := rcommon.NewKeyFromPrivateKey(claimerEthKey).Sign(digest)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to sign forward request digest: %w", err)
|
||||
}
|
||||
|
||||
return signature, nil
|
||||
}
|
||||
|
||||
// createForwarderRequest creates the forwarder request, which we sign the digest of.
|
||||
func createForwarderRequest(
|
||||
nonce *big.Int,
|
||||
swapCreatorAddr ethcommon.Address,
|
||||
swap *contracts.SwapCreatorSwap,
|
||||
secret *[32]byte,
|
||||
) (*gsnforwarder.IForwarderForwardRequest, error) {
|
||||
|
||||
calldata, err := getClaimRelayerTxCalldata(coins.RelayerFeeWei, swap, secret)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req := &gsnforwarder.IForwarderForwardRequest{
|
||||
From: swap.Claimer,
|
||||
To: swapCreatorAddr,
|
||||
Value: big.NewInt(0),
|
||||
Gas: big.NewInt(relayedClaimGas),
|
||||
Nonce: nonce,
|
||||
Data: calldata,
|
||||
ValidUntilTime: big.NewInt(0),
|
||||
}
|
||||
|
||||
return req, nil
|
||||
}
|
||||
|
||||
// getClaimRelayerTxCalldata returns the call data to be used when invoking the
|
||||
// claimRelayer method on the SwapCreator contract.
|
||||
func getClaimRelayerTxCalldata(feeWei *big.Int, swap *contracts.SwapCreatorSwap, secret *[32]byte) ([]byte, error) {
|
||||
return contracts.SwapCreatorParsedABI.Pack("claimRelayer", *swap, *secret, feeWei)
|
||||
}
|
||||
|
||||
func getForwarderAndDomainSeparator(
|
||||
ctx context.Context,
|
||||
ec *ethclient.Client,
|
||||
forwarderAddr ethcommon.Address,
|
||||
) (*gsnforwarder.Forwarder, *[32]byte, error) {
|
||||
chainID, err := ec.ChainID(ctx)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
forwarder, err := gsnforwarder.NewForwarder(forwarderAddr, ec)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
domainSeparator, err := rcommon.GetEIP712DomainSeparator(gsnforwarder.DefaultName,
|
||||
gsnforwarder.DefaultVersion, chainID, forwarderAddr)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to get EIP712 domain separator: %w", err)
|
||||
}
|
||||
|
||||
return forwarder, &domainSeparator, nil
|
||||
}
|
||||
@@ -5,11 +5,10 @@ package relayer
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"encoding/binary"
|
||||
"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"
|
||||
@@ -23,46 +22,31 @@ import (
|
||||
"github.com/athanorlabs/atomic-swap/net/message"
|
||||
)
|
||||
|
||||
const (
|
||||
maxClaimRelayerETHGas = 100000 // worst case gas usage for the claimRelayer call (ether)
|
||||
// actual cost is 85040 but that fails in unit tests on "out of gas".
|
||||
)
|
||||
|
||||
// ValidateAndSendTransaction sends the relayed transaction to the network if it validates successfully.
|
||||
func ValidateAndSendTransaction(
|
||||
ctx context.Context,
|
||||
req *message.RelayClaimRequest,
|
||||
ec extethclient.EthClient,
|
||||
ourSFContractAddr ethcommon.Address,
|
||||
ourSwapCreatorAddr ethcommon.Address,
|
||||
salt [4]byte,
|
||||
) (*message.RelayClaimResponse, error) {
|
||||
|
||||
err := validateClaimRequest(ctx, req, ec.Raw(), ourSFContractAddr)
|
||||
err := validateClaimRequest(ctx, req, ec.Raw(), ec.Address(), salt, ourSwapCreatorAddr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
reqSwapCreator, err := contracts.NewSwapCreator(req.SwapCreatorAddr, ec.Raw())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
reqForwarderAddr, err := reqSwapCreator.TrustedForwarder(&bind.CallOpts{Context: ctx})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
reqForwarder, domainSeparator, err := getForwarderAndDomainSeparator(ctx, ec.Raw(), reqForwarderAddr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
nonce, err := reqForwarder.GetNonce(&bind.CallOpts{Context: ctx}, req.Swap.Claimer)
|
||||
reqSwapCreator, err := contracts.NewSwapCreator(req.RelaySwap.SwapCreator, ec.Raw())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// The size of request.Secret was vetted when it was deserialized
|
||||
secret := (*[32]byte)(req.Secret)
|
||||
|
||||
forwarderReq, err := createForwarderRequest(nonce, req.SwapCreatorAddr, req.Swap, secret)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
secret := [32]byte(req.Secret)
|
||||
|
||||
gasPrice, err := checkForMinClaimBalance(ctx, ec)
|
||||
if err != nil {
|
||||
@@ -78,32 +62,40 @@ func ValidateAndSendTransaction(
|
||||
return nil, err
|
||||
}
|
||||
txOpts.GasPrice = gasPrice
|
||||
txOpts.GasLimit = forwarderClaimGas
|
||||
txOpts.GasLimit = maxClaimRelayerETHGas
|
||||
log.Debugf("relaying tx with gas price %s and gas limit %d", gasPrice, txOpts.GasLimit)
|
||||
|
||||
err = simulateExecute(
|
||||
v := req.Signature[64]
|
||||
r := [32]byte(req.Signature[:32])
|
||||
s := [32]byte(req.Signature[32:64])
|
||||
|
||||
saltU32 := binary.BigEndian.Uint32(salt[:])
|
||||
err = simulateClaimRelayer(
|
||||
ctx,
|
||||
ec,
|
||||
&reqForwarderAddr,
|
||||
txOpts,
|
||||
*forwarderReq,
|
||||
*domainSeparator,
|
||||
req.Signature,
|
||||
req.RelaySwap,
|
||||
secret,
|
||||
ec.Address(),
|
||||
saltU32,
|
||||
v, r, s,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tx, err := reqForwarder.Execute(
|
||||
tx, err := reqSwapCreator.ClaimRelayer(
|
||||
txOpts,
|
||||
*forwarderReq,
|
||||
*domainSeparator,
|
||||
gsnforwarder.ForwardRequestTypehash,
|
||||
nil,
|
||||
req.Signature,
|
||||
*req.RelaySwap,
|
||||
secret,
|
||||
ec.Address(),
|
||||
saltU32,
|
||||
v,
|
||||
r,
|
||||
s,
|
||||
)
|
||||
if err != nil {
|
||||
log.Errorf("failed to call execute: %s", err)
|
||||
log.Errorf("failed to call ClaimRelayer: %s", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -113,7 +105,6 @@ func ValidateAndSendTransaction(
|
||||
}
|
||||
|
||||
log.Infof("relayed claim %s", common.ReceiptInfo(receipt))
|
||||
|
||||
return &message.RelayClaimResponse{TxHash: tx.Hash()}, nil
|
||||
}
|
||||
|
||||
@@ -130,7 +121,7 @@ func checkForMinClaimBalance(ctx context.Context, ec extethclient.EthClient) (*b
|
||||
return nil, err
|
||||
}
|
||||
|
||||
txCost := new(big.Int).Mul(gasPrice, big.NewInt(forwarderClaimGas))
|
||||
txCost := new(big.Int).Mul(gasPrice, big.NewInt(maxClaimRelayerETHGas))
|
||||
if balance.BigInt().Cmp(txCost) < 0 {
|
||||
return nil, fmt.Errorf("balance %s ETH is under the minimum %s ETH to relay claim",
|
||||
balance.AsEtherString(), coins.FmtWeiAsETH(txCost))
|
||||
@@ -139,30 +130,30 @@ func checkForMinClaimBalance(ctx context.Context, ec extethclient.EthClient) (*b
|
||||
return gasPrice, nil
|
||||
}
|
||||
|
||||
// simulateExecute calls the forwarder's execute method (defined in Forwarder.sol)
|
||||
// simulateExecute calls the swap creator's ClaimRelayer function
|
||||
// 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(
|
||||
func simulateClaimRelayer(
|
||||
ctx context.Context,
|
||||
ec extethclient.EthClient,
|
||||
reqForwarderAddr *ethcommon.Address,
|
||||
txOpts *bind.TransactOpts,
|
||||
forwarderReq gsnforwarder.IForwarderForwardRequest,
|
||||
domainSeparator [32]byte,
|
||||
sig []byte,
|
||||
relaySwap *contracts.SwapCreatorRelaySwap,
|
||||
secret [32]byte,
|
||||
relayer ethcommon.Address,
|
||||
salt uint32,
|
||||
v uint8,
|
||||
r, s [32]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,
|
||||
// Pack the "claimRelayer" method call
|
||||
packed, err := contracts.SwapCreatorParsedABI.Pack(
|
||||
"claimRelayer",
|
||||
*relaySwap,
|
||||
secret,
|
||||
relayer,
|
||||
salt,
|
||||
v,
|
||||
r,
|
||||
s,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -170,7 +161,7 @@ func simulateExecute(
|
||||
|
||||
callMessage := ethereum.CallMsg{
|
||||
From: txOpts.From,
|
||||
To: reqForwarderAddr,
|
||||
To: &relaySwap.SwapCreator,
|
||||
Gas: txOpts.GasLimit,
|
||||
GasPrice: txOpts.GasPrice,
|
||||
GasFeeCap: txOpts.GasFeeCap,
|
||||
@@ -180,26 +171,12 @@ func simulateExecute(
|
||||
AccessList: []types.AccessTuple{},
|
||||
}
|
||||
|
||||
// Call the "execute" method
|
||||
data, err := ec.Raw().CallContract(ctx, callMessage, nil)
|
||||
// Call the "claimRelayer" method
|
||||
// will return a revert error on failure
|
||||
_, 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
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ package relayer
|
||||
import (
|
||||
"context"
|
||||
"crypto/ecdsa"
|
||||
"crypto/rand"
|
||||
"math/big"
|
||||
"testing"
|
||||
|
||||
@@ -21,7 +22,11 @@ import (
|
||||
)
|
||||
|
||||
func Test_ValidateAndSendTransaction(t *testing.T) {
|
||||
sk := tests.GetMakerTestKey(t)
|
||||
sk := tests.GetMakerTestKey(t) // name of this is a bit misleading
|
||||
relayerPub := sk.Public().(*ecdsa.PublicKey)
|
||||
relayerAddr := crypto.PubkeyToAddress(*relayerPub)
|
||||
t.Log("relayerAddr: ", relayerAddr)
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
@@ -39,11 +44,14 @@ func Test_ValidateAndSendTransaction(t *testing.T) {
|
||||
// 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)
|
||||
// generate claimer key; should be different from relayer key
|
||||
claimerSk, err := crypto.GenerateKey()
|
||||
require.NoError(t, err)
|
||||
pub := claimerSk.Public().(*ecdsa.PublicKey)
|
||||
claimerAddr := crypto.PubkeyToAddress(*pub)
|
||||
t.Log("claimerAddr: ", claimerAddr)
|
||||
|
||||
swapCreatorAddr := deployContracts(t, ec.Raw(), sk)
|
||||
swapCreator, err := contracts.NewSwapCreator(swapCreatorAddr, ec.Raw())
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -55,8 +63,17 @@ func Test_ValidateAndSendTransaction(t *testing.T) {
|
||||
txOpts.Value = value
|
||||
|
||||
refundKey := [32]byte{1}
|
||||
tx, err := swapCreator.NewSwap(txOpts, cmt, refundKey, addr,
|
||||
testT0Timeout, testT1Timeout, types.EthAssetETH.Address(), value, nonce)
|
||||
tx, err := swapCreator.NewSwap(
|
||||
txOpts,
|
||||
cmt,
|
||||
refundKey,
|
||||
claimerAddr,
|
||||
testT0Timeout,
|
||||
testT1Timeout,
|
||||
types.EthAssetETH.Address(),
|
||||
value,
|
||||
nonce,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
receipt, err := block.WaitForReceipt(ctx, ec.Raw(), tx.Hash())
|
||||
require.NoError(t, err)
|
||||
@@ -71,9 +88,9 @@ func Test_ValidateAndSendTransaction(t *testing.T) {
|
||||
t0, t1, err := contracts.GetTimeoutsFromLog(receipt.Logs[logIndex])
|
||||
require.NoError(t, err)
|
||||
|
||||
swap := &contracts.SwapCreatorSwap{
|
||||
Owner: addr,
|
||||
Claimer: addr,
|
||||
swap := contracts.SwapCreatorSwap{
|
||||
Owner: relayerAddr,
|
||||
Claimer: claimerAddr,
|
||||
PubKeyClaim: cmt,
|
||||
PubKeyRefund: refundKey,
|
||||
Timeout0: t0,
|
||||
@@ -84,7 +101,7 @@ func Test_ValidateAndSendTransaction(t *testing.T) {
|
||||
}
|
||||
|
||||
// set contract to Ready
|
||||
tx, err = swapCreator.SetReady(txOpts, *swap)
|
||||
tx, err = swapCreator.SetReady(txOpts, swap)
|
||||
require.NoError(t, err)
|
||||
receipt, err = block.WaitForReceipt(ctx, ec.Raw(), tx.Hash())
|
||||
require.NoError(t, err)
|
||||
@@ -92,15 +109,32 @@ func Test_ValidateAndSendTransaction(t *testing.T) {
|
||||
|
||||
secret := proof.Secret()
|
||||
|
||||
// generate relayer hash
|
||||
var salt [4]byte
|
||||
_, err = rand.Read(salt[:])
|
||||
require.NoError(t, err)
|
||||
relayerHash := crypto.Keccak256Hash(relayerAddr[:], salt[:])
|
||||
|
||||
// now let's try to claim
|
||||
req, err := CreateRelayClaimRequest(ctx, sk, ec.Raw(), swapCreatorAddr, forwarderAddr, swap, &secret)
|
||||
relaySwap := &contracts.SwapCreatorRelaySwap{
|
||||
Swap: swap,
|
||||
SwapCreator: swapCreatorAddr,
|
||||
RelayerHash: relayerHash,
|
||||
Fee: big.NewInt(1),
|
||||
}
|
||||
|
||||
req, err := CreateRelayClaimRequest(claimerSk, relaySwap, secret)
|
||||
require.NoError(t, err)
|
||||
|
||||
resp, err := ValidateAndSendTransaction(ctx, req, ec, swapCreatorAddr)
|
||||
resp, err := ValidateAndSendTransaction(ctx, req, ec, swapCreatorAddr, salt)
|
||||
require.NoError(t, err)
|
||||
|
||||
receipt, err = block.WaitForReceipt(ctx, ec.Raw(), resp.TxHash)
|
||||
require.NoError(t, err)
|
||||
t.Logf("gas cost to call claimRelayer: %d (delta %d)",
|
||||
receipt.GasUsed, maxClaimRelayerETHGas-int(receipt.GasUsed))
|
||||
require.GreaterOrEqual(t, maxClaimRelayerETHGas, int(receipt.GasUsed), "claimRelayer")
|
||||
|
||||
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)
|
||||
@@ -110,11 +144,11 @@ func Test_ValidateAndSendTransaction(t *testing.T) {
|
||||
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
|
||||
// Now let's 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)
|
||||
req, err = CreateRelayClaimRequest(claimerSk, relaySwap, secret)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = ValidateAndSendTransaction(ctx, req, ec, swapCreatorAddr)
|
||||
require.ErrorContains(t, err, "relayed transaction failed on simulation")
|
||||
_, err = ValidateAndSendTransaction(ctx, req, ec, swapCreatorAddr, salt)
|
||||
require.ErrorContains(t, err, "revert")
|
||||
}
|
||||
|
||||
@@ -7,9 +7,8 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/athanorlabs/go-relayer/impls/gsnforwarder"
|
||||
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
||||
ethcommon "github.com/ethereum/go-ethereum/common"
|
||||
ethcrypto "github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/ethclient"
|
||||
|
||||
"github.com/athanorlabs/atomic-swap/coins"
|
||||
@@ -22,52 +21,63 @@ func validateClaimRequest(
|
||||
ctx context.Context,
|
||||
request *message.RelayClaimRequest,
|
||||
ec *ethclient.Client,
|
||||
ourSFContractAddr ethcommon.Address,
|
||||
ourAddress ethcommon.Address,
|
||||
salt [4]byte,
|
||||
ourSwapCreatorAddr ethcommon.Address,
|
||||
) error {
|
||||
err := validateClaimValues(ctx, request, ec, ourSFContractAddr)
|
||||
err := validateClaimValues(ctx, request, ec, ourAddress, salt, ourSwapCreatorAddr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return validateClaimSignature(ctx, ec, request)
|
||||
return validateClaimSignature(request)
|
||||
}
|
||||
|
||||
// validateClaimValues validates the non-signature aspects of the claim request:
|
||||
// 1. the claim request's swap creator and forwarder contract bytecode matches ours
|
||||
// 1. the claim request's SwapCreator bytecode matches ours
|
||||
// 2. the swap is for ETH and not an ERC20 token
|
||||
// 3. the swap value is strictly greater than the relayer fee
|
||||
// 4. TODO: Validate that the swap exists and is in a claimable state?
|
||||
// 4. the claim request's relayer hash matches keccak256(ourAddress || salt)
|
||||
func validateClaimValues(
|
||||
ctx context.Context,
|
||||
request *message.RelayClaimRequest,
|
||||
ec *ethclient.Client,
|
||||
ourAddress ethcommon.Address,
|
||||
salt [4]byte,
|
||||
ourSwapCreatorAddr ethcommon.Address,
|
||||
) error {
|
||||
isTakerRelay := request.OfferID != nil
|
||||
|
||||
// Validate the deployed SwapCreator contract, if it is not at the same address
|
||||
// as our own. The CheckSwapCreatorContractCode method validates both the
|
||||
// SwapCreator bytecode and the Forwarder bytecode.
|
||||
if request.SwapCreatorAddr != ourSwapCreatorAddr {
|
||||
// Validate the requested SwapCreator contract, if it is not at the same address
|
||||
// as our own.
|
||||
if request.RelaySwap.SwapCreator != ourSwapCreatorAddr {
|
||||
if isTakerRelay {
|
||||
return fmt.Errorf("taker claim swap creator mismatch found=%s expected=%s",
|
||||
request.SwapCreatorAddr, ourSwapCreatorAddr)
|
||||
request.RelaySwap.SwapCreator, ourSwapCreatorAddr)
|
||||
}
|
||||
_, err := contracts.CheckSwapCreatorContractCode(ctx, ec, request.SwapCreatorAddr)
|
||||
err := contracts.CheckSwapCreatorContractCode(ctx, ec, request.RelaySwap.SwapCreator)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
asset := types.EthAsset(request.Swap.Asset)
|
||||
asset := types.EthAsset(request.RelaySwap.Swap.Asset)
|
||||
if asset != types.EthAssetETH {
|
||||
return fmt.Errorf("relaying for ETH Asset %s is not supported", asset)
|
||||
}
|
||||
|
||||
// The relayer fee must be strictly less than the swap value
|
||||
if coins.RelayerFeeWei.Cmp(request.Swap.Value) >= 0 {
|
||||
if coins.RelayerFeeWei.Cmp(request.RelaySwap.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(coins.RelayerFeeWei))
|
||||
coins.FmtWeiAsETH(request.RelaySwap.Swap.Value), coins.RelayerFeeETH.Text('f'))
|
||||
}
|
||||
|
||||
hash := ethcrypto.Keccak256Hash(append(ourAddress.Bytes(), salt[:]...))
|
||||
if request.RelaySwap.RelayerHash != hash {
|
||||
return fmt.Errorf("relay request payout address hash %s does not match expected (%s)",
|
||||
request.RelaySwap.RelayerHash,
|
||||
hash,
|
||||
)
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -76,57 +86,25 @@ func validateClaimValues(
|
||||
// validateClaimSignature validates the claim signature. It is assumed that the
|
||||
// request fields have already been validated.
|
||||
func validateClaimSignature(
|
||||
ctx context.Context,
|
||||
ec *ethclient.Client,
|
||||
request *message.RelayClaimRequest,
|
||||
) error {
|
||||
callOpts := &bind.CallOpts{
|
||||
Context: ctx,
|
||||
From: ethcommon.Address{0xFF}, // can be any value but zero, which will validate all signatures
|
||||
}
|
||||
msg := request.RelaySwap.Hash()
|
||||
var sig [65]byte
|
||||
copy(sig[:], request.Signature)
|
||||
sig[64] -= 27 // ecrecover requires 0/1 while EVM requires 27/28
|
||||
|
||||
swapCreator, err := contracts.NewSwapCreator(request.SwapCreatorAddr, ec)
|
||||
signer, err := ethcrypto.Ecrecover(msg[:], sig[:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
forwarderAddr, err := swapCreator.TrustedForwarder(&bind.CallOpts{Context: ctx})
|
||||
pubkey, err := ethcrypto.UnmarshalPubkey(signer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
forwarder, domainSeparator, err := getForwarderAndDomainSeparator(ctx, ec, forwarderAddr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
nonce, err := forwarder.GetNonce(callOpts, request.Swap.Claimer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
secret := (*[32]byte)(request.Secret)
|
||||
|
||||
forwarderRequest, err := createForwarderRequest(
|
||||
nonce,
|
||||
request.SwapCreatorAddr,
|
||||
request.Swap,
|
||||
secret,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = forwarder.Verify(
|
||||
callOpts,
|
||||
*forwarderRequest,
|
||||
*domainSeparator,
|
||||
gsnforwarder.ForwardRequestTypehash,
|
||||
nil,
|
||||
request.Signature,
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to verify signature: %w", err)
|
||||
if ethcrypto.PubkeyToAddress(*pubkey) != request.RelaySwap.Swap.Claimer {
|
||||
return fmt.Errorf("signer of message is not swap claimer")
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
@@ -25,7 +25,11 @@ func TestValidateRelayerFee(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
ec, _ := tests.NewEthClient(t)
|
||||
key := tests.GetTakerTestKey(t)
|
||||
swapCreatorAddr, _ := deployContracts(t, ec, key)
|
||||
swapCreatorAddr := deployContracts(t, ec, key)
|
||||
|
||||
// 20-byte empty address, 4-byte zero salt
|
||||
empty := [24]byte{}
|
||||
relayerHash := crypto.Keccak256Hash(empty[:])
|
||||
|
||||
type testCase struct {
|
||||
description string
|
||||
@@ -51,7 +55,7 @@ func TestValidateRelayerFee(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
swap := &contracts.SwapCreatorSwap{
|
||||
swap := contracts.SwapCreatorSwap{
|
||||
Owner: ethcommon.Address{},
|
||||
Claimer: ethcommon.Address{},
|
||||
PubKeyClaim: [32]byte{},
|
||||
@@ -64,12 +68,16 @@ func TestValidateRelayerFee(t *testing.T) {
|
||||
}
|
||||
|
||||
request := &message.RelayClaimRequest{
|
||||
SwapCreatorAddr: swapCreatorAddr,
|
||||
Swap: swap,
|
||||
Secret: make([]byte, 32),
|
||||
RelaySwap: &contracts.SwapCreatorRelaySwap{
|
||||
Swap: swap,
|
||||
Fee: big.NewInt(1),
|
||||
SwapCreator: swapCreatorAddr,
|
||||
RelayerHash: relayerHash,
|
||||
},
|
||||
Secret: make([]byte, 32),
|
||||
}
|
||||
|
||||
err := validateClaimValues(ctx, request, ec, swapCreatorAddr)
|
||||
err := validateClaimValues(ctx, request, ec, ethcommon.Address{}, [4]byte{}, swapCreatorAddr)
|
||||
if tc.expectErr != "" {
|
||||
require.ErrorContains(t, err, tc.expectErr, tc.description)
|
||||
} else {
|
||||
@@ -87,13 +95,14 @@ func Test_validateClaimValues_takerClaim_contractAddressNotEqualFail(t *testing.
|
||||
swapCreatorAddrOurs := ethcommon.Address{0x2} // passed to validateClaimValues
|
||||
|
||||
request := &message.RelayClaimRequest{
|
||||
OfferID: &offerID,
|
||||
SwapCreatorAddr: swapCreatorAddrInClaim,
|
||||
Secret: make([]byte, 32),
|
||||
Swap: new(contracts.SwapCreatorSwap), // test fails before we validate this
|
||||
OfferID: &offerID,
|
||||
Secret: make([]byte, 32),
|
||||
RelaySwap: &contracts.SwapCreatorRelaySwap{
|
||||
SwapCreator: swapCreatorAddrInClaim,
|
||||
},
|
||||
}
|
||||
|
||||
err := validateClaimValues(context.Background(), request, nil, swapCreatorAddrOurs)
|
||||
err := validateClaimValues(context.Background(), request, nil, ethcommon.Address{}, [4]byte{}, swapCreatorAddrOurs)
|
||||
require.ErrorContains(t, err, "taker claim swap creator mismatch")
|
||||
}
|
||||
|
||||
@@ -103,39 +112,47 @@ func Test_validateClaimValues_takerClaim_contractAddressNotEqualFail(t *testing.
|
||||
func Test_validateClaimValues_dhtClaim_contractAddressNotEqual(t *testing.T) {
|
||||
ec, _ := tests.NewEthClient(t)
|
||||
key := tests.GetTakerTestKey(t)
|
||||
swapCreatorAddr, forwarderAddr := deployContracts(t, ec, key)
|
||||
swapCreatorAddr := deployContracts(t, ec, key)
|
||||
|
||||
request := &message.RelayClaimRequest{
|
||||
OfferID: nil, // DHT relayer claim
|
||||
SwapCreatorAddr: forwarderAddr, // not a valid swap creator contract
|
||||
Secret: make([]byte, 32),
|
||||
Swap: new(contracts.SwapCreatorSwap), // test fails before we validate this
|
||||
OfferID: nil, // DHT relayer claim
|
||||
Secret: make([]byte, 32),
|
||||
RelaySwap: &contracts.SwapCreatorRelaySwap{
|
||||
SwapCreator: ethcommon.Address{1}, // not a valid swap creator contract
|
||||
},
|
||||
}
|
||||
|
||||
err := validateClaimValues(context.Background(), request, ec, swapCreatorAddr)
|
||||
err := validateClaimValues(context.Background(), request, ec, ethcommon.Address{}, [4]byte{}, swapCreatorAddr)
|
||||
require.ErrorContains(t, err, "contract address does not contain correct SwapCreator code")
|
||||
}
|
||||
|
||||
func Test_validateSignature(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
ethKey := tests.GetMakerTestKey(t)
|
||||
claimer := crypto.PubkeyToAddress(*ethKey.Public().(*ecdsa.PublicKey))
|
||||
ec, _ := tests.NewEthClient(t)
|
||||
secret := [32]byte{0x1}
|
||||
swapCreatorAddr, forwarderAddr := deployContracts(t, ec, ethKey)
|
||||
swapCreatorAddr := deployContracts(t, ec, ethKey)
|
||||
|
||||
swap := createTestSwap(claimer)
|
||||
req, err := CreateRelayClaimRequest(ctx, ethKey, ec, swapCreatorAddr, forwarderAddr, swap, &secret)
|
||||
relaySwap := &contracts.SwapCreatorRelaySwap{
|
||||
SwapCreator: swapCreatorAddr,
|
||||
Swap: *swap,
|
||||
RelayerHash: types.Hash{},
|
||||
Fee: big.NewInt(1),
|
||||
}
|
||||
|
||||
req, err := CreateRelayClaimRequest(ethKey, relaySwap, secret)
|
||||
require.NoError(t, err)
|
||||
|
||||
// success path
|
||||
err = validateClaimSignature(ctx, ec, req)
|
||||
err = validateClaimSignature(req)
|
||||
require.NoError(t, err)
|
||||
|
||||
// failure path (tamper with an arbitrary byte of the signature)
|
||||
req.Signature[10]++
|
||||
err = validateClaimSignature(ctx, ec, req)
|
||||
require.ErrorContains(t, err, "failed to verify signature")
|
||||
err = validateClaimSignature(req)
|
||||
// can be "recovery failed" or "signer of message is not swap claimer"
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
func Test_validateClaimRequest(t *testing.T) {
|
||||
@@ -144,19 +161,30 @@ func Test_validateClaimRequest(t *testing.T) {
|
||||
claimer := crypto.PubkeyToAddress(*ethKey.Public().(*ecdsa.PublicKey))
|
||||
ec, _ := tests.NewEthClient(t)
|
||||
secret := [32]byte{0x1}
|
||||
swapCreatorAddr, forwarderAddr := deployContracts(t, ec, ethKey)
|
||||
swapCreatorAddr := deployContracts(t, ec, ethKey)
|
||||
|
||||
// 20-byte empty address, 4-byte zero salt
|
||||
empty := [24]byte{}
|
||||
relayerHash := crypto.Keccak256Hash(empty[:])
|
||||
|
||||
swap := createTestSwap(claimer)
|
||||
req, err := CreateRelayClaimRequest(ctx, ethKey, ec, swapCreatorAddr, forwarderAddr, swap, &secret)
|
||||
relaySwap := &contracts.SwapCreatorRelaySwap{
|
||||
SwapCreator: swapCreatorAddr,
|
||||
Swap: *swap,
|
||||
RelayerHash: relayerHash,
|
||||
Fee: big.NewInt(1),
|
||||
}
|
||||
|
||||
req, err := CreateRelayClaimRequest(ethKey, relaySwap, secret)
|
||||
require.NoError(t, err)
|
||||
|
||||
// success path
|
||||
err = validateClaimRequest(ctx, req, ec, swapCreatorAddr)
|
||||
err = validateClaimRequest(ctx, req, ec, ethcommon.Address{}, [4]byte{}, swapCreatorAddr)
|
||||
require.NoError(t, err)
|
||||
|
||||
// test failure path by passing a non-eth asset
|
||||
asset := ethcommon.Address{0x1}
|
||||
req.Swap.Asset = asset
|
||||
err = validateClaimRequest(ctx, req, ec, swapCreatorAddr)
|
||||
req.RelaySwap.Swap.Asset = asset
|
||||
err = validateClaimRequest(ctx, req, ec, ethcommon.Address{}, [4]byte{}, swapCreatorAddr)
|
||||
require.ErrorContains(t, err, fmt.Sprintf("relaying for ETH Asset %s is not supported", types.EthAsset(asset)))
|
||||
}
|
||||
|
||||
@@ -28,6 +28,6 @@ compile-contract() {
|
||||
}
|
||||
|
||||
compile-contract SwapCreator SwapCreator swap_creator
|
||||
compile-contract TestERC20 TestERC20 erc20_mock
|
||||
compile-contract TestERC20 TestERC20 erc20_token
|
||||
compile-contract IERC20Metadata IERC20 ierc20
|
||||
compile-contract AggregatorV3Interface AggregatorV3Interface aggregator_v3_interface
|
||||
|
||||
@@ -106,8 +106,7 @@ start-daemons() {
|
||||
fi
|
||||
|
||||
SWAP_CREATOR_ADDR="$(jq -r .swapCreatorAddr "${CONTRACT_ADDR_FILE}")"
|
||||
FORWARDER_ADDR="$(jq -r .forwarderAddr "${CONTRACT_ADDR_FILE}")"
|
||||
if [[ -z "${SWAP_CREATOR_ADDR}" ]] || [[ -z "${FORWARDER_ADDR}" ]]; then
|
||||
if [[ -z "${SWAP_CREATOR_ADDR}" ]]; then
|
||||
echo "Failed to get Alice's deployed contract addresses"
|
||||
stop-daemons
|
||||
exit 1
|
||||
|
||||
@@ -25,7 +25,6 @@ func (s *IntegrationTestSuite) TestXMRMaker_DiscoverRelayer() {
|
||||
ctx := context.Background()
|
||||
c := rpcclient.NewClient(ctx, defaultXMRMakerSwapdEndpoint)
|
||||
|
||||
// see https://github.com/AthanorLabs/go-relayer/blob/master/net/host.go#L20
|
||||
peerIDs, err := c.Discover("relayer", defaultDiscoverTimeout)
|
||||
require.NoError(s.T(), err)
|
||||
require.Equal(s.T(), 1, len(peerIDs))
|
||||
|
||||
Reference in New Issue
Block a user