From fb95751ddaaf01700f4ef443d10a4cccf566a41b Mon Sep 17 00:00:00 2001 From: noot <36753753+noot@users.noreply.github.com> Date: Mon, 1 May 2023 19:23:17 -0400 Subject: [PATCH] feat: update SwapCreater.sol `claimRelayer` to no longer use forwarder (#449) Co-authored-by: Dmitry Holodov --- cmd/swapd/contract.go | 35 ++--- cmd/swapd/contract_test.go | 29 +--- cmd/swapd/main.go | 31 +---- cmd/swapd/main_test.go | 49 +------ common/config.go | 3 +- daemon/swap_daemon_test.go | 13 +- daemon/test_support.go | 5 +- ethereum/check_swap_creator_contract.go | 96 ++----------- ethereum/check_swap_creator_contract_test.go | 71 ++-------- ethereum/consts.go | 8 +- ethereum/consts_test.go | 4 +- ethereum/contracts/ERC2771Context.sol | 36 ----- ethereum/contracts/SwapCreator.sol | 91 ++++++++---- ethereum/deploy_util.go | 118 +--------------- ethereum/deploy_util_test.go | 38 ----- ethereum/erc20_token_test.go | 102 +++++++++----- ethereum/swap_creator.go | 138 ++++++------------- ethereum/swap_creator_test.go | 20 +-- ethereum/utils.go | 102 +++++++++++--- go.mod | 1 - go.sum | 2 - net/host.go | 1 + net/host_test.go | 16 ++- net/message/message.go | 5 + net/message/relay_message.go | 46 +++++-- net/relay.go | 90 +++++++++++- net/relay_test.go | 41 +++--- net/types.go | 5 +- protocol/backend/backend.go | 98 ++++++++++++- protocol/xmrmaker/claim.go | 56 ++++---- protocol/xmrmaker/instance_test.go | 10 +- protocol/xmrmaker/message_handler.go | 5 +- protocol/xmrtaker/min_balance.go | 6 +- protocol/xmrtaker/swap_state.go | 22 +-- protocol/xmrtaker/swap_state_test.go | 9 +- relayer/claim_request.go | 67 +++++---- relayer/claim_request_test.go | 30 ++-- relayer/forwarder.go | 124 ----------------- relayer/submit_transaction.go | 133 ++++++++---------- relayer/submit_transaction_test.go | 68 ++++++--- relayer/validate.go | 90 +++++------- relayer/validate_test.go | 84 +++++++---- scripts/generate-bindings.sh | 2 +- scripts/run-integration-tests.sh | 3 +- tests/relayer_integration_test.go | 1 - 45 files changed, 878 insertions(+), 1126 deletions(-) delete mode 100644 ethereum/contracts/ERC2771Context.sol delete mode 100644 ethereum/deploy_util_test.go delete mode 100644 relayer/forwarder.go diff --git a/cmd/swapd/contract.go b/cmd/swapd/contract.go index 47a7f49c..c9b89189 100644 --- a/cmd/swapd/contract.go +++ b/cmd/swapd/contract.go @@ -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 diff --git a/cmd/swapd/contract_test.go b/cmd/swapd/contract_test.go index 170a4bed..d2936b99 100644 --- a/cmd/swapd/contract_test.go +++ b/cmd/swapd/contract_test.go @@ -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) diff --git a/cmd/swapd/main.go b/cmd/swapd/main.go index 3b95767f..aefcf6a8 100644 --- a/cmd/swapd/main.go +++ b/cmd/swapd/main.go @@ -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 } diff --git a/cmd/swapd/main_test.go b/cmd/swapd/main_test.go index 6092b2bc..d319877c 100644 --- a/cmd/swapd/main_test.go +++ b/cmd/swapd/main_test.go @@ -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", }, diff --git a/common/config.go b/common/config.go index 9512ba35..4c4d6623 100644 --- a/common/config.go +++ b/common/config.go @@ -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", diff --git a/daemon/swap_daemon_test.go b/daemon/swap_daemon_test.go index 73eb69f4..7d26537f 100644 --- a/daemon/swap_daemon_test.go +++ b/daemon/swap_daemon_test.go @@ -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)) diff --git a/daemon/test_support.go b/daemon/test_support.go index dba2e716..8a4aa3e1 100644 --- a/daemon/test_support.go +++ b/daemon/test_support.go @@ -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 diff --git a/ethereum/check_swap_creator_contract.go b/ethereum/check_swap_creator_contract.go index 9898aa5a..291f5fbd 100644 --- a/ethereum/check_swap_creator_contract.go +++ b/ethereum/check_swap_creator_contract.go @@ -9,116 +9,42 @@ import ( "errors" "fmt" - "github.com/athanorlabs/atomic-swap/common" - - "github.com/athanorlabs/go-relayer/impls/gsnforwarder" ethcommon "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/ethclient" ) -// expectedSwapCreatorBytecodeHex is generated by deploying an instance of SwapCreator.sol -// with the trustedForwarder address set to all zeros and reading back the bytecode. See -// the unit test TestExpectedSwapCreatorBytecodeHex if you need to update this value. +// expectedSwapCreatorBytecodeHex is generated by deploying an instance of +// SwapCreator.sol and reading back the bytecode. See the unit test +// TestExpectedSwapCreatorBytecodeHex if you need to update this value. const ( - expectedSwapCreatorBytecodeHex = "6080604052600436106100865760003560e01c806373e4771c1161005957806373e4771c1461014e578063b32d1b4f1461016e578063c41e46cf1461018e578063eb84e7f2146101af578063fcaf229c146101ec57600080fd5b80631e6c5acc1461008b57806356c022bb146100ad578063572b6c05146100fe5780635cb969161461012e575b600080fd5b34801561009757600080fd5b506100ab6100a6366004610e9e565b61020c565b005b3480156100b957600080fd5b506100e17f000000000000000000000000000000000000000000000000000000000000000081565b6040516001600160a01b0390911681526020015b60405180910390f35b34801561010a57600080fd5b5061011e610119366004610ecb565b610456565b60405190151581526020016100f5565b34801561013a57600080fd5b506100ab610149366004610e9e565b610488565b34801561015a57600080fd5b506100ab610169366004610eef565b610571565b34801561017a57600080fd5b5061011e610189366004610f26565b610739565b6101a161019c366004610f48565b610809565b6040519081526020016100f5565b3480156101bb57600080fd5b506101df6101ca366004610fb9565b60006020819052908152604090205460ff1681565b6040516100f59190610fe8565b3480156101f857600080fd5b506100ab610207366004611010565b610aeb565b60008260405160200161021f919061102d565b60408051601f1981840301815291815281516020928301206000818152928390529082205490925060ff169081600381111561025d5761025d610fd2565b0361027b57604051631115766760e01b815260040160405180910390fd5b600381600381111561028f5761028f610fd2565b036102ad5760405163066916a960e01b815260040160405180910390fd5b83516001600160a01b031633146102d75760405163148ca24360e11b815260040160405180910390fd5b8360a0015142108015610308575083608001514211806103085750600281600381111561030657610306610fd2565b145b15610326576040516332a1860f60e11b815260040160405180910390fd5b610334838560600151610bc9565b604051839083907e7c875846b687732a7579c19bb1dade66cd14e9f4f809565e2b2b5e76c72b4f90600090a36000828152602081905260409020805460ff1916600317905560c08401516001600160a01b03166103ce57835160e08501516040516001600160a01b039092169181156108fc0291906000818181858888f193505050501580156103c8573d6000803e3d6000fd5b50610450565b60c0840151845160e086015160405163a9059cbb60e01b81526001600160a01b039283166004820152602481019190915291169063a9059cbb906044016020604051808303816000875af115801561042a573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061044e919061109c565b505b50505050565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0390811691161490565b6104928282610bf0565b60c08201516001600160a01b03166104ea5781602001516001600160a01b03166108fc8360e001519081150290604051600060405180830381858888f193505050501580156104e5573d6000803e3d6000fd5b505050565b60c0820151602083015160e084015160405163a9059cbb60e01b81526001600160a01b039283166004820152602481019190915291169063a9059cbb906044016020604051808303816000875af1158015610549573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906104e5919061109c565b5050565b61057a33610456565b61059757604051637e2ea6d560e11b815260040160405180910390fd5b6105a18383610bf0565b60c08301516001600160a01b031661062a5782602001516001600160a01b03166108fc828560e001516105d491906110d4565b6040518115909202916000818181858888f193505050501580156105fc573d6000803e3d6000fd5b50604051329082156108fc029083906000818181858888f19350505050158015610450573d6000803e3d6000fd5b8260c001516001600160a01b031663a9059cbb8460200151838660e0015161065291906110d4565b6040516001600160e01b031960e085901b1681526001600160a01b03909216600483015260248201526044016020604051808303816000875af115801561069d573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906106c1919061109c565b5060c083015160405163a9059cbb60e01b8152326004820152602481018390526001600160a01b039091169063a9059cbb906044016020604051808303816000875af1158015610715573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610450919061109c565b600080600181601b7f79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f8179870014551231950b75fc4402da1732fc9bebe197f79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f8179889096040805160008152602081018083529590955260ff909316928401929092526060830152608082015260a0016020604051602081039080840390855afa1580156107e6573d6000803e3d6000fd5b5050604051601f1901516001600160a01b03858116911614925050505b92915050565b60008260000361082c57604051637c946ed760e01b815260040160405180910390fd5b6001600160a01b03841661085f5734831461085a57604051632a9ffab760e21b815260040160405180910390fd5b6108d8565b6040516323b872dd60e01b8152336004820152306024820152604481018490526001600160a01b038516906323b872dd906064016020604051808303816000875af11580156108b2573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906108d6919061109c565b505b8815806108e3575087155b1561090157604051631bc61bed60e11b815260040160405180910390fd5b6001600160a01b038716610927576040516208978560e71b815260040160405180910390fd5b851580610932575084155b1561095057604051631ffb86f160e21b815260040160405180910390fd5b6000604051806101200160405280336001600160a01b03168152602001896001600160a01b031681526020018b81526020018a8152602001884261099491906110e7565b8152602001876109a48a426110e7565b6109ae91906110e7565b8152602001866001600160a01b031681526020018581526020018481525090506000816040516020016109e1919061102d565b60408051601f19818403018152919052805160209091012090506000808281526020819052604090205460ff166003811115610a1f57610a1f610fd2565b14610a3d576040516339a2986760e11b815260040160405180910390fd5b7f91446ce035ac29998b5473504609a5ef5e961005daba4630a1684b63be848f56818c8c85608001518660a001518760c001518860e00151604051610abc979695949392919096875260208701959095526040860193909352606085019190915260808401526001600160a01b031660a083015260c082015260e00190565b60405180910390a16000818152602081905260409020805460ff191660011790559a9950505050505050505050565b600081604051602001610afe919061102d565b60408051601f1981840301815291905280516020909101209050600160008281526020819052604090205460ff166003811115610b3d57610b3d610fd2565b14610b5b57604051630fe0fb5160e11b815260040160405180910390fd5b81516001600160a01b03163314610b855760405163148ca24360e11b815260040160405180910390fd5b600081815260208190526040808220805460ff191660021790555182917f5fc23b25552757626e08b316cc2387ad1bc70ee1594af7204db4ce0c39f5d15f91a25050565b610bd38282610739565b61056d5760405163abab6bd760e01b815260040160405180910390fd5b600082604051602001610c03919061102d565b60408051601f1981840301815291815281516020928301206000818152928390529082205490925060ff1690816003811115610c4157610c41610fd2565b03610c5f57604051631115766760e01b815260040160405180910390fd5b6003816003811115610c7357610c73610fd2565b03610c915760405163066916a960e01b815260040160405180910390fd5b83602001516001600160a01b0316610ca7610d8e565b6001600160a01b031614610cce57604051633471640960e11b815260040160405180910390fd5b836080015142108015610cf357506002816003811115610cf057610cf0610fd2565b14155b15610d115760405163d71d60b560e01b815260040160405180910390fd5b8360a001514210610d355760405163497df9d160e01b815260040160405180910390fd5b610d43838560400151610bc9565b604051839083907f38d6042dbdae8e73a7f6afbabd3fbe0873f9f5ed3cd71294591c3908c2e65fee90600090a3506000908152602081905260409020805460ff191660031790555050565b6000610d9933610456565b15610dab575060131936013560601c90565b503390565b604051610120810167ffffffffffffffff81118282101715610de257634e487b7160e01b600052604160045260246000fd5b60405290565b6001600160a01b0381168114610dfd57600080fd5b50565b8035610e0b81610de8565b919050565b60006101208284031215610e2357600080fd5b610e2b610db0565b9050610e3682610e00565b8152610e4460208301610e00565b602082015260408201356040820152606082013560608201526080820135608082015260a082013560a0820152610e7d60c08301610e00565b60c082015260e082013560e082015261010080830135818301525092915050565b6000806101408385031215610eb257600080fd5b610ebc8484610e10565b94610120939093013593505050565b600060208284031215610edd57600080fd5b8135610ee881610de8565b9392505050565b60008060006101608486031215610f0557600080fd5b610f0f8585610e10565b956101208501359550610140909401359392505050565b60008060408385031215610f3957600080fd5b50508035926020909101359150565b600080600080600080600080610100898b031215610f6557600080fd5b88359750602089013596506040890135610f7e81610de8565b9550606089013594506080890135935060a0890135610f9c81610de8565b979a969950949793969295929450505060c08201359160e0013590565b600060208284031215610fcb57600080fd5b5035919050565b634e487b7160e01b600052602160045260246000fd5b602081016004831061100a57634e487b7160e01b600052602160045260246000fd5b91905290565b6000610120828403121561102357600080fd5b610ee88383610e10565b81516001600160a01b03908116825260208084015182169083015260408084015190830152606080840151908301526080808401519083015260a0808401519083015260c0808401519091169082015260e0808301519082015261010091820151918101919091526101200190565b6000602082840312156110ae57600080fd5b81518015158114610ee857600080fd5b634e487b7160e01b600052601160045260246000fd5b81810381811115610803576108036110be565b80820180821115610803576108036110be56fea2646970667358221220a75326d41574d36189871c40b37894bb93ca35029fb6761e949335295c76985064736f6c63430008130033" //nolint:lll - - ethAddrByteLen = len(ethcommon.Address{}) // 20 bytes + expectedSwapCreatorBytecodeHex = "6080604052600436106100705760003560e01c8063b32d1b4f1161004e578063b32d1b4f146100d7578063c41e46cf1461010c578063eb84e7f21461012d578063fcaf229c1461016a57600080fd5b80631e6c5acc146100755780635cb969161461009757806387065c49146100b7575b600080fd5b34801561008157600080fd5b50610095610090366004610f67565b61018a565b005b3480156100a357600080fd5b506100956100b2366004610f67565b6103d4565b3480156100c357600080fd5b506100956100d2366004610fb9565b6104f3565b3480156100e357600080fd5b506100f76100f2366004611077565b610830565b60405190151581526020015b60405180910390f35b61011f61011a366004611099565b610900565b604051908152602001610103565b34801561013957600080fd5b5061015d61014836600461110a565b60006020819052908152604090205460ff1681565b6040516101039190611139565b34801561017657600080fd5b50610095610185366004611161565b610be2565b60008260405160200161019d91906111ed565b60408051601f1981840301815291815281516020928301206000818152928390529082205490925060ff16908160038111156101db576101db611123565b036101f957604051631115766760e01b815260040160405180910390fd5b600381600381111561020d5761020d611123565b0361022b5760405163066916a960e01b815260040160405180910390fd5b83516001600160a01b031633146102555760405163148ca24360e11b815260040160405180910390fd5b8360a0015142108015610286575083608001514211806102865750600281600381111561028457610284611123565b145b156102a4576040516332a1860f60e11b815260040160405180910390fd5b6102b2838560600151610cc0565b604051839083907e7c875846b687732a7579c19bb1dade66cd14e9f4f809565e2b2b5e76c72b4f90600090a36000828152602081905260409020805460ff1916600317905560c08401516001600160a01b031661034c57835160e08501516040516001600160a01b039092169181156108fc0291906000818181858888f19350505050158015610346573d6000803e3d6000fd5b506103ce565b60c0840151845160e086015160405163a9059cbb60e01b81526001600160a01b039283166004820152602481019190915291169063a9059cbb906044016020604051808303816000875af11580156103a8573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906103cc91906111fc565b505b50505050565b81602001516001600160a01b0316336001600160a01b03161461040a57604051633471640960e11b815260040160405180910390fd5b6104148282610ce7565b60c08201516001600160a01b031661046c5781602001516001600160a01b03166108fc8360e001519081150290604051600060405180830381858888f19350505050158015610467573d6000803e3d6000fd5b505050565b60c0820151602083015160e084015160405163a9059cbb60e01b81526001600160a01b039283166004820152602481019190915291169063a9059cbb906044016020604051808303816000875af11580156104cb573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061046791906111fc565b5050565b6000600188604051602001610508919061121e565b60408051601f198184030181528282528051602091820120600084529083018083525260ff871690820152606081018590526080810184905260a0016020604051602081039080840390855afa158015610566573d6000803e3d6000fd5b5050506020604051035190508760000151602001516001600160a01b0316816001600160a01b0316146105ac57604051638baa579f60e01b815260040160405180910390fd5b87606001516001600160a01b0316306001600160a01b0316146105e25760405163a710429d60e01b815260040160405180910390fd5b60408089015190516bffffffffffffffffffffffff19606089901b1660208201526001600160e01b031960e088901b166034820152603801604051602081830303815290604052805190602001201461064e5760405163fe16c3c560e01b815260040160405180910390fd5b875161065a9088610ce7565b875160c001516001600160a01b0316610702578760000151602001516001600160a01b03166108fc89602001518a6000015160e0015161069a9190611277565b6040518115909202916000818181858888f193505050501580156106c2573d6000803e3d6000fd5b5060208801516040516001600160a01b0388169180156108fc02916000818181858888f193505050501580156106fc573d6000803e3d6000fd5b50610826565b875160c0810151602080830151908b015160e0909301516001600160a01b039092169263a9059cbb926107359190611277565b6040516001600160e01b031960e085901b1681526001600160a01b03909216600483015260248201526044016020604051808303816000875af1158015610780573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906107a491906111fc565b50875160c00151602089015160405163a9059cbb60e01b81526001600160a01b038981166004830152602482019290925291169063a9059cbb906044016020604051808303816000875af1158015610800573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061082491906111fc565b505b5050505050505050565b600080600181601b7f79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f8179870014551231950b75fc4402da1732fc9bebe197f79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f8179889096040805160008152602081018083529590955260ff909316928401929092526060830152608082015260a0016020604051602081039080840390855afa1580156108dd573d6000803e3d6000fd5b5050604051601f1901516001600160a01b03858116911614925050505b92915050565b60008260000361092357604051637c946ed760e01b815260040160405180910390fd5b6001600160a01b0384166109565734831461095157604051632a9ffab760e21b815260040160405180910390fd5b6109cf565b6040516323b872dd60e01b8152336004820152306024820152604481018490526001600160a01b038516906323b872dd906064016020604051808303816000875af11580156109a9573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906109cd91906111fc565b505b8815806109da575087155b156109f857604051631bc61bed60e11b815260040160405180910390fd5b6001600160a01b038716610a1e576040516208978560e71b815260040160405180910390fd5b851580610a29575084155b15610a4757604051631ffb86f160e21b815260040160405180910390fd5b6000604051806101200160405280336001600160a01b03168152602001896001600160a01b031681526020018b81526020018a81526020018842610a8b919061128a565b815260200187610a9b8a4261128a565b610aa5919061128a565b8152602001866001600160a01b03168152602001858152602001848152509050600081604051602001610ad891906111ed565b60408051601f19818403018152919052805160209091012090506000808281526020819052604090205460ff166003811115610b1657610b16611123565b14610b34576040516339a2986760e11b815260040160405180910390fd5b7f91446ce035ac29998b5473504609a5ef5e961005daba4630a1684b63be848f56818c8c85608001518660a001518760c001518860e00151604051610bb3979695949392919096875260208701959095526040860193909352606085019190915260808401526001600160a01b031660a083015260c082015260e00190565b60405180910390a16000818152602081905260409020805460ff191660011790559a9950505050505050505050565b600081604051602001610bf591906111ed565b60408051601f1981840301815291905280516020909101209050600160008281526020819052604090205460ff166003811115610c3457610c34611123565b14610c5257604051630fe0fb5160e11b815260040160405180910390fd5b81516001600160a01b03163314610c7c5760405163148ca24360e11b815260040160405180910390fd5b600081815260208190526040808220805460ff191660021790555182917f5fc23b25552757626e08b316cc2387ad1bc70ee1594af7204db4ce0c39f5d15f91a25050565b610cca8282610830565b6104ef5760405163abab6bd760e01b815260040160405180910390fd5b600082604051602001610cfa91906111ed565b60408051601f1981840301815291815281516020928301206000818152928390529082205490925060ff1690816003811115610d3857610d38611123565b03610d5657604051631115766760e01b815260040160405180910390fd5b6003816003811115610d6a57610d6a611123565b03610d885760405163066916a960e01b815260040160405180910390fd5b836080015142108015610dad57506002816003811115610daa57610daa611123565b14155b15610dcb5760405163d71d60b560e01b815260040160405180910390fd5b8360a001514210610def5760405163497df9d160e01b815260040160405180910390fd5b610dfd838560400151610cc0565b604051839083907f38d6042dbdae8e73a7f6afbabd3fbe0873f9f5ed3cd71294591c3908c2e65fee90600090a3506000908152602081905260409020805460ff191660031790555050565b604051610120810167ffffffffffffffff81118282101715610e7a57634e487b7160e01b600052604160045260246000fd5b60405290565b6040516080810167ffffffffffffffff81118282101715610e7a57634e487b7160e01b600052604160045260246000fd5b6001600160a01b0381168114610ec657600080fd5b50565b8035610ed481610eb1565b919050565b60006101208284031215610eec57600080fd5b610ef4610e48565b9050610eff82610ec9565b8152610f0d60208301610ec9565b602082015260408201356040820152606082013560608201526080820135608082015260a082013560a0820152610f4660c08301610ec9565b60c082015260e082013560e082015261010080830135818301525092915050565b6000806101408385031215610f7b57600080fd5b610f858484610ed9565b94610120939093013593505050565b803563ffffffff81168114610ed457600080fd5b803560ff81168114610ed457600080fd5b6000806000806000806000878903610240811215610fd657600080fd5b61018080821215610fe657600080fd5b610fee610e80565b9150610ffa8b8b610ed9565b82526101208a013560208301526101408a013560408301526101608a013561102181610eb1565b6060830152909750880135955061103b6101a08901610ec9565b945061104a6101c08901610f94565b93506110596101e08901610fa8565b92506102008801359150610220880135905092959891949750929550565b6000806040838503121561108a57600080fd5b50508035926020909101359150565b600080600080600080600080610100898b0312156110b657600080fd5b883597506020890135965060408901356110cf81610eb1565b9550606089013594506080890135935060a08901356110ed81610eb1565b979a969950949793969295929450505060c08201359160e0013590565b60006020828403121561111c57600080fd5b5035919050565b634e487b7160e01b600052602160045260246000fd5b602081016004831061115b57634e487b7160e01b600052602160045260246000fd5b91905290565b6000610120828403121561117457600080fd5b61117e8383610ed9565b9392505050565b60018060a01b0380825116835280602083015116602084015260408201516040840152606082015160608401526080820151608084015260a082015160a08401528060c08301511660c08401525060e081015160e08301526101008082015181840152505050565b61012081016108fa8284611185565b60006020828403121561120e57600080fd5b8151801515811461117e57600080fd5b600061018082019050611232828451611185565b602083015161012083015260408301516101408301526060909201516001600160a01b03166101609091015290565b634e487b7160e01b600052601160045260246000fd5b818103818111156108fa576108fa611261565b808201808211156108fa576108fa61126156fea26469706673582212208e46497effc64f18265779b96dd009969e1f0fac0b390d6faee990d2944ed19b64736f6c63430008130033" //nolint:lll ) -// forwarderAddrIndices is a slice of the start indices where the trusted forwarder -// address is compiled into the deployed contract byte code. When verifying the bytecode -// of a deployed contract, we need special treatment for these identical 20-byte address -// blocks. See TestForwarderAddrIndexes to update the values. -var forwarderAddrIndices = []int{203, 1124} - var ( errInvalidSwapCreatorContract = errors.New("given contract address does not contain correct SwapCreator code") ) -// CheckSwapCreatorContractCode checks that the bytecode at the given address matches the -// SwapCreator.sol contract. The trusted forwarder address that the contract was deployed -// with is parsed out from the byte code and returned. +// CheckSwapCreatorContractCode checks that the bytecode at the given address +// matches the SwapCreator.sol contract. func CheckSwapCreatorContractCode( ctx context.Context, ec *ethclient.Client, contractAddr ethcommon.Address, -) (ethcommon.Address, error) { +) error { code, err := ec.CodeAt(ctx, contractAddr, nil) if err != nil { - return ethcommon.Address{}, err + return fmt.Errorf("failed to get code at %s: %w", contractAddr, err) } expectedCode := ethcommon.FromHex(expectedSwapCreatorBytecodeHex) if len(code) != len(expectedCode) { - return ethcommon.Address{}, fmt.Errorf("length mismatch: %w", errInvalidSwapCreatorContract) + return fmt.Errorf("length mismatch: %w", errInvalidSwapCreatorContract) } - allZeroAddr := ethcommon.Address{} - - // we fill this in with the trusted forwarder that the contract was deployed with - var forwarderAddr ethcommon.Address - - for i, addrIndex := range forwarderAddrIndices { - curAddr := code[addrIndex : addrIndex+ethAddrByteLen] - if i == 0 { - // initialise the trusted forwarder address on the first index - copy(forwarderAddr[:], curAddr) - } else { - // check that any remaining forwarder addresses match the one we found at the first index - if !bytes.Equal(curAddr, forwarderAddr[:]) { - return ethcommon.Address{}, errInvalidSwapCreatorContract - } - } - - // Zero out the trusted forwarder address in the code, so that we can compare the - // read in byte code with a copy of the contract code that was deployed using an - // all-zero trusted forwarder address. curAddr and code have the same backing - // array, so we are updating expectedCode as well here: - copy(curAddr, allZeroAddr[:]) - } - - // Now that the trusted forwarder addresses have been zeroed out, the read-in contract code should - // match the expected code. if !bytes.Equal(expectedCode, code) { - return ethcommon.Address{}, errInvalidSwapCreatorContract + return errInvalidSwapCreatorContract } - if (forwarderAddr == ethcommon.Address{}) { - return forwarderAddr, nil - } - - err = CheckForwarderContractCode(ctx, ec, forwarderAddr) - if err != nil { - return ethcommon.Address{}, fmt.Errorf("%w: %s", errInvalidSwapCreatorContract, err) - } - - // return the trusted forwarder address that was parsed from the deployed contract byte code - return forwarderAddr, nil -} - -// CheckForwarderContractCode checks that the trusted forwarder contract used by -// the given swap contract has the expected bytecode. -func CheckForwarderContractCode( - ctx context.Context, - ec *ethclient.Client, - contractAddr ethcommon.Address, -) error { - chainID, err := ec.ChainID(ctx) - if err != nil { - return err - } - - switch chainID.Uint64() { - case common.MainnetChainID: - if contractAddr == ethcommon.HexToAddress(gsnforwarder.MainnetForwarderAddrHex) { - return nil - } - case common.SepoliaChainID: - if contractAddr == ethcommon.HexToAddress(gsnforwarder.SepoliaForwarderAddrHex) { - return nil - } - } - - return gsnforwarder.CheckForwarderContractCode(ctx, ec, contractAddr) + return nil } diff --git a/ethereum/check_swap_creator_contract_test.go b/ethereum/check_swap_creator_contract_test.go index 70011e18..14e72f87 100644 --- a/ethereum/check_swap_creator_contract_test.go +++ b/ethereum/check_swap_creator_contract_test.go @@ -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()) } diff --git a/ethereum/consts.go b/ethereum/consts.go index 594515f7..3a946ce2 100644 --- a/ethereum/consts.go +++ b/ethereum/consts.go @@ -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 ) diff --git a/ethereum/consts_test.go b/ethereum/consts_test.go index 79a1a240..51fa5a9e 100644 --- a/ethereum/consts_test.go +++ b/ethereum/consts_test.go @@ -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 ) diff --git a/ethereum/contracts/ERC2771Context.sol b/ethereum/contracts/ERC2771Context.sol deleted file mode 100644 index 34b1210a..00000000 --- a/ethereum/contracts/ERC2771Context.sol +++ /dev/null @@ -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(); - } - } -} diff --git a/ethereum/contracts/SwapCreator.sol b/ethereum/contracts/SwapCreator.sol index 3d6660ff..1b2f80ce 100644 --- a/ethereum/contracts/SwapCreator.sol +++ b/ethereum/contracts/SwapCreator.sol @@ -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(); } } diff --git a/ethereum/deploy_util.go b/ethereum/deploy_util.go index 896aedf1..95d50e31 100644 --- a/ethereum/deploy_util.go +++ b/ethereum/deploy_util.go @@ -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 { diff --git a/ethereum/deploy_util_test.go b/ethereum/deploy_util_test.go deleted file mode 100644 index 88551f30..00000000 --- a/ethereum/deploy_util_test.go +++ /dev/null @@ -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) -} diff --git a/ethereum/erc20_token_test.go b/ethereum/erc20_token_test.go index 74d836b8..0df15d28 100644 --- a/ethereum/erc20_token_test.go +++ b/ethereum/erc20_token_test.go @@ -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) } diff --git a/ethereum/swap_creator.go b/ethereum/swap_creator.go index 1547bd03..27bb3a82 100644 --- a/ethereum/swap_creator.go +++ b/ethereum/swap_creator.go @@ -29,6 +29,14 @@ var ( _ = abi.ConvertType ) +// SwapCreatorRelaySwap is an auto generated low-level Go binding around an user-defined struct. +type SwapCreatorRelaySwap struct { + Swap SwapCreatorSwap + Fee *big.Int + RelayerHash [32]byte + SwapCreator common.Address +} + // SwapCreatorSwap is an auto generated low-level Go binding around an user-defined struct. type SwapCreatorSwap struct { Owner common.Address @@ -44,8 +52,8 @@ type SwapCreatorSwap struct { // SwapCreatorMetaData contains all meta data concerning the SwapCreator contract. var SwapCreatorMetaData = &bind.MetaData{ - ABI: "[{\"inputs\":[{\"internalType\":\"address\",\"name\":\"trustedForwarder\",\"type\":\"address\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"inputs\":[],\"name\":\"InvalidClaimer\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InvalidSecret\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InvalidSwap\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InvalidSwapKey\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InvalidTimeout\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InvalidValue\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"NotTimeToRefund\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"OnlySwapClaimer\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"OnlySwapOwner\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"OnlyTrustedForwarder\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"SwapAlreadyExists\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"SwapCompleted\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"SwapNotPending\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"TooEarlyToClaim\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"TooLateToClaim\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"ZeroValue\",\"type\":\"error\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"swapID\",\"type\":\"bytes32\"},{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"s\",\"type\":\"bytes32\"}],\"name\":\"Claimed\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"bytes32\",\"name\":\"swapID\",\"type\":\"bytes32\"},{\"indexed\":false,\"internalType\":\"bytes32\",\"name\":\"claimKey\",\"type\":\"bytes32\"},{\"indexed\":false,\"internalType\":\"bytes32\",\"name\":\"refundKey\",\"type\":\"bytes32\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"timeout0\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"timeout1\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"asset\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"value\",\"type\":\"uint256\"}],\"name\":\"New\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"swapID\",\"type\":\"bytes32\"}],\"name\":\"Ready\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"swapID\",\"type\":\"bytes32\"},{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"s\",\"type\":\"bytes32\"}],\"name\":\"Refunded\",\"type\":\"event\"},{\"inputs\":[],\"name\":\"_trustedForwarder\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"addresspayable\",\"name\":\"owner\",\"type\":\"address\"},{\"internalType\":\"addresspayable\",\"name\":\"claimer\",\"type\":\"address\"},{\"internalType\":\"bytes32\",\"name\":\"pubKeyClaim\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"pubKeyRefund\",\"type\":\"bytes32\"},{\"internalType\":\"uint256\",\"name\":\"timeout0\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"timeout1\",\"type\":\"uint256\"},{\"internalType\":\"address\",\"name\":\"asset\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"value\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"nonce\",\"type\":\"uint256\"}],\"internalType\":\"structSwapCreator.Swap\",\"name\":\"_swap\",\"type\":\"tuple\"},{\"internalType\":\"bytes32\",\"name\":\"_s\",\"type\":\"bytes32\"}],\"name\":\"claim\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"addresspayable\",\"name\":\"owner\",\"type\":\"address\"},{\"internalType\":\"addresspayable\",\"name\":\"claimer\",\"type\":\"address\"},{\"internalType\":\"bytes32\",\"name\":\"pubKeyClaim\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"pubKeyRefund\",\"type\":\"bytes32\"},{\"internalType\":\"uint256\",\"name\":\"timeout0\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"timeout1\",\"type\":\"uint256\"},{\"internalType\":\"address\",\"name\":\"asset\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"value\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"nonce\",\"type\":\"uint256\"}],\"internalType\":\"structSwapCreator.Swap\",\"name\":\"_swap\",\"type\":\"tuple\"},{\"internalType\":\"bytes32\",\"name\":\"_s\",\"type\":\"bytes32\"},{\"internalType\":\"uint256\",\"name\":\"fee\",\"type\":\"uint256\"}],\"name\":\"claimRelayer\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"forwarder\",\"type\":\"address\"}],\"name\":\"isTrustedForwarder\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"scalar\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"qKeccak\",\"type\":\"uint256\"}],\"name\":\"mulVerify\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"_pubKeyClaim\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"_pubKeyRefund\",\"type\":\"bytes32\"},{\"internalType\":\"addresspayable\",\"name\":\"_claimer\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"_timeoutDuration0\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"_timeoutDuration1\",\"type\":\"uint256\"},{\"internalType\":\"address\",\"name\":\"_asset\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"_value\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"_nonce\",\"type\":\"uint256\"}],\"name\":\"newSwap\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"payable\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"addresspayable\",\"name\":\"owner\",\"type\":\"address\"},{\"internalType\":\"addresspayable\",\"name\":\"claimer\",\"type\":\"address\"},{\"internalType\":\"bytes32\",\"name\":\"pubKeyClaim\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"pubKeyRefund\",\"type\":\"bytes32\"},{\"internalType\":\"uint256\",\"name\":\"timeout0\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"timeout1\",\"type\":\"uint256\"},{\"internalType\":\"address\",\"name\":\"asset\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"value\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"nonce\",\"type\":\"uint256\"}],\"internalType\":\"structSwapCreator.Swap\",\"name\":\"_swap\",\"type\":\"tuple\"},{\"internalType\":\"bytes32\",\"name\":\"_s\",\"type\":\"bytes32\"}],\"name\":\"refund\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"addresspayable\",\"name\":\"owner\",\"type\":\"address\"},{\"internalType\":\"addresspayable\",\"name\":\"claimer\",\"type\":\"address\"},{\"internalType\":\"bytes32\",\"name\":\"pubKeyClaim\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"pubKeyRefund\",\"type\":\"bytes32\"},{\"internalType\":\"uint256\",\"name\":\"timeout0\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"timeout1\",\"type\":\"uint256\"},{\"internalType\":\"address\",\"name\":\"asset\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"value\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"nonce\",\"type\":\"uint256\"}],\"internalType\":\"structSwapCreator.Swap\",\"name\":\"_swap\",\"type\":\"tuple\"}],\"name\":\"setReady\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"name\":\"swaps\",\"outputs\":[{\"internalType\":\"enumSwapCreator.Stage\",\"name\":\"\",\"type\":\"uint8\"}],\"stateMutability\":\"view\",\"type\":\"function\"}]", - Bin: "0x60a060405234801561001057600080fd5b506040516111c13803806111c183398101604081905261002f91610040565b6001600160a01b0316608052610070565b60006020828403121561005257600080fd5b81516001600160a01b038116811461006957600080fd5b9392505050565b6080516111306100916000396000818160bf015261045801526111306000f3fe6080604052600436106100865760003560e01c806373e4771c1161005957806373e4771c1461014e578063b32d1b4f1461016e578063c41e46cf1461018e578063eb84e7f2146101af578063fcaf229c146101ec57600080fd5b80631e6c5acc1461008b57806356c022bb146100ad578063572b6c05146100fe5780635cb969161461012e575b600080fd5b34801561009757600080fd5b506100ab6100a6366004610e9e565b61020c565b005b3480156100b957600080fd5b506100e17f000000000000000000000000000000000000000000000000000000000000000081565b6040516001600160a01b0390911681526020015b60405180910390f35b34801561010a57600080fd5b5061011e610119366004610ecb565b610456565b60405190151581526020016100f5565b34801561013a57600080fd5b506100ab610149366004610e9e565b610488565b34801561015a57600080fd5b506100ab610169366004610eef565b610571565b34801561017a57600080fd5b5061011e610189366004610f26565b610739565b6101a161019c366004610f48565b610809565b6040519081526020016100f5565b3480156101bb57600080fd5b506101df6101ca366004610fb9565b60006020819052908152604090205460ff1681565b6040516100f59190610fe8565b3480156101f857600080fd5b506100ab610207366004611010565b610aeb565b60008260405160200161021f919061102d565b60408051601f1981840301815291815281516020928301206000818152928390529082205490925060ff169081600381111561025d5761025d610fd2565b0361027b57604051631115766760e01b815260040160405180910390fd5b600381600381111561028f5761028f610fd2565b036102ad5760405163066916a960e01b815260040160405180910390fd5b83516001600160a01b031633146102d75760405163148ca24360e11b815260040160405180910390fd5b8360a0015142108015610308575083608001514211806103085750600281600381111561030657610306610fd2565b145b15610326576040516332a1860f60e11b815260040160405180910390fd5b610334838560600151610bc9565b604051839083907e7c875846b687732a7579c19bb1dade66cd14e9f4f809565e2b2b5e76c72b4f90600090a36000828152602081905260409020805460ff1916600317905560c08401516001600160a01b03166103ce57835160e08501516040516001600160a01b039092169181156108fc0291906000818181858888f193505050501580156103c8573d6000803e3d6000fd5b50610450565b60c0840151845160e086015160405163a9059cbb60e01b81526001600160a01b039283166004820152602481019190915291169063a9059cbb906044016020604051808303816000875af115801561042a573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061044e919061109c565b505b50505050565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0390811691161490565b6104928282610bf0565b60c08201516001600160a01b03166104ea5781602001516001600160a01b03166108fc8360e001519081150290604051600060405180830381858888f193505050501580156104e5573d6000803e3d6000fd5b505050565b60c0820151602083015160e084015160405163a9059cbb60e01b81526001600160a01b039283166004820152602481019190915291169063a9059cbb906044016020604051808303816000875af1158015610549573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906104e5919061109c565b5050565b61057a33610456565b61059757604051637e2ea6d560e11b815260040160405180910390fd5b6105a18383610bf0565b60c08301516001600160a01b031661062a5782602001516001600160a01b03166108fc828560e001516105d491906110d4565b6040518115909202916000818181858888f193505050501580156105fc573d6000803e3d6000fd5b50604051329082156108fc029083906000818181858888f19350505050158015610450573d6000803e3d6000fd5b8260c001516001600160a01b031663a9059cbb8460200151838660e0015161065291906110d4565b6040516001600160e01b031960e085901b1681526001600160a01b03909216600483015260248201526044016020604051808303816000875af115801561069d573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906106c1919061109c565b5060c083015160405163a9059cbb60e01b8152326004820152602481018390526001600160a01b039091169063a9059cbb906044016020604051808303816000875af1158015610715573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610450919061109c565b600080600181601b7f79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f8179870014551231950b75fc4402da1732fc9bebe197f79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f8179889096040805160008152602081018083529590955260ff909316928401929092526060830152608082015260a0016020604051602081039080840390855afa1580156107e6573d6000803e3d6000fd5b5050604051601f1901516001600160a01b03858116911614925050505b92915050565b60008260000361082c57604051637c946ed760e01b815260040160405180910390fd5b6001600160a01b03841661085f5734831461085a57604051632a9ffab760e21b815260040160405180910390fd5b6108d8565b6040516323b872dd60e01b8152336004820152306024820152604481018490526001600160a01b038516906323b872dd906064016020604051808303816000875af11580156108b2573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906108d6919061109c565b505b8815806108e3575087155b1561090157604051631bc61bed60e11b815260040160405180910390fd5b6001600160a01b038716610927576040516208978560e71b815260040160405180910390fd5b851580610932575084155b1561095057604051631ffb86f160e21b815260040160405180910390fd5b6000604051806101200160405280336001600160a01b03168152602001896001600160a01b031681526020018b81526020018a8152602001884261099491906110e7565b8152602001876109a48a426110e7565b6109ae91906110e7565b8152602001866001600160a01b031681526020018581526020018481525090506000816040516020016109e1919061102d565b60408051601f19818403018152919052805160209091012090506000808281526020819052604090205460ff166003811115610a1f57610a1f610fd2565b14610a3d576040516339a2986760e11b815260040160405180910390fd5b7f91446ce035ac29998b5473504609a5ef5e961005daba4630a1684b63be848f56818c8c85608001518660a001518760c001518860e00151604051610abc979695949392919096875260208701959095526040860193909352606085019190915260808401526001600160a01b031660a083015260c082015260e00190565b60405180910390a16000818152602081905260409020805460ff191660011790559a9950505050505050505050565b600081604051602001610afe919061102d565b60408051601f1981840301815291905280516020909101209050600160008281526020819052604090205460ff166003811115610b3d57610b3d610fd2565b14610b5b57604051630fe0fb5160e11b815260040160405180910390fd5b81516001600160a01b03163314610b855760405163148ca24360e11b815260040160405180910390fd5b600081815260208190526040808220805460ff191660021790555182917f5fc23b25552757626e08b316cc2387ad1bc70ee1594af7204db4ce0c39f5d15f91a25050565b610bd38282610739565b61056d5760405163abab6bd760e01b815260040160405180910390fd5b600082604051602001610c03919061102d565b60408051601f1981840301815291815281516020928301206000818152928390529082205490925060ff1690816003811115610c4157610c41610fd2565b03610c5f57604051631115766760e01b815260040160405180910390fd5b6003816003811115610c7357610c73610fd2565b03610c915760405163066916a960e01b815260040160405180910390fd5b83602001516001600160a01b0316610ca7610d8e565b6001600160a01b031614610cce57604051633471640960e11b815260040160405180910390fd5b836080015142108015610cf357506002816003811115610cf057610cf0610fd2565b14155b15610d115760405163d71d60b560e01b815260040160405180910390fd5b8360a001514210610d355760405163497df9d160e01b815260040160405180910390fd5b610d43838560400151610bc9565b604051839083907f38d6042dbdae8e73a7f6afbabd3fbe0873f9f5ed3cd71294591c3908c2e65fee90600090a3506000908152602081905260409020805460ff191660031790555050565b6000610d9933610456565b15610dab575060131936013560601c90565b503390565b604051610120810167ffffffffffffffff81118282101715610de257634e487b7160e01b600052604160045260246000fd5b60405290565b6001600160a01b0381168114610dfd57600080fd5b50565b8035610e0b81610de8565b919050565b60006101208284031215610e2357600080fd5b610e2b610db0565b9050610e3682610e00565b8152610e4460208301610e00565b602082015260408201356040820152606082013560608201526080820135608082015260a082013560a0820152610e7d60c08301610e00565b60c082015260e082013560e082015261010080830135818301525092915050565b6000806101408385031215610eb257600080fd5b610ebc8484610e10565b94610120939093013593505050565b600060208284031215610edd57600080fd5b8135610ee881610de8565b9392505050565b60008060006101608486031215610f0557600080fd5b610f0f8585610e10565b956101208501359550610140909401359392505050565b60008060408385031215610f3957600080fd5b50508035926020909101359150565b600080600080600080600080610100898b031215610f6557600080fd5b88359750602089013596506040890135610f7e81610de8565b9550606089013594506080890135935060a0890135610f9c81610de8565b979a969950949793969295929450505060c08201359160e0013590565b600060208284031215610fcb57600080fd5b5035919050565b634e487b7160e01b600052602160045260246000fd5b602081016004831061100a57634e487b7160e01b600052602160045260246000fd5b91905290565b6000610120828403121561102357600080fd5b610ee88383610e10565b81516001600160a01b03908116825260208084015182169083015260408084015190830152606080840151908301526080808401519083015260a0808401519083015260c0808401519091169082015260e0808301519082015261010091820151918101919091526101200190565b6000602082840312156110ae57600080fd5b81518015158114610ee857600080fd5b634e487b7160e01b600052601160045260246000fd5b81810381811115610803576108036110be565b80820180821115610803576108036110be56fea2646970667358221220a75326d41574d36189871c40b37894bb93ca35029fb6761e949335295c76985064736f6c63430008130033", + ABI: "[{\"inputs\":[],\"name\":\"InvalidClaimer\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InvalidContractAddress\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InvalidRelayerAddress\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InvalidSecret\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InvalidSignature\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InvalidSwap\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InvalidSwapKey\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InvalidTimeout\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InvalidValue\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"NotTimeToRefund\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"OnlySwapClaimer\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"OnlySwapOwner\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"SwapAlreadyExists\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"SwapCompleted\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"SwapNotPending\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"TooEarlyToClaim\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"TooLateToClaim\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"ZeroValue\",\"type\":\"error\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"swapID\",\"type\":\"bytes32\"},{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"s\",\"type\":\"bytes32\"}],\"name\":\"Claimed\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"bytes32\",\"name\":\"swapID\",\"type\":\"bytes32\"},{\"indexed\":false,\"internalType\":\"bytes32\",\"name\":\"claimKey\",\"type\":\"bytes32\"},{\"indexed\":false,\"internalType\":\"bytes32\",\"name\":\"refundKey\",\"type\":\"bytes32\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"timeout0\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"timeout1\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"asset\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"value\",\"type\":\"uint256\"}],\"name\":\"New\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"swapID\",\"type\":\"bytes32\"}],\"name\":\"Ready\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"swapID\",\"type\":\"bytes32\"},{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"s\",\"type\":\"bytes32\"}],\"name\":\"Refunded\",\"type\":\"event\"},{\"inputs\":[{\"components\":[{\"internalType\":\"addresspayable\",\"name\":\"owner\",\"type\":\"address\"},{\"internalType\":\"addresspayable\",\"name\":\"claimer\",\"type\":\"address\"},{\"internalType\":\"bytes32\",\"name\":\"pubKeyClaim\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"pubKeyRefund\",\"type\":\"bytes32\"},{\"internalType\":\"uint256\",\"name\":\"timeout0\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"timeout1\",\"type\":\"uint256\"},{\"internalType\":\"address\",\"name\":\"asset\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"value\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"nonce\",\"type\":\"uint256\"}],\"internalType\":\"structSwapCreator.Swap\",\"name\":\"_swap\",\"type\":\"tuple\"},{\"internalType\":\"bytes32\",\"name\":\"_secret\",\"type\":\"bytes32\"}],\"name\":\"claim\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"components\":[{\"internalType\":\"addresspayable\",\"name\":\"owner\",\"type\":\"address\"},{\"internalType\":\"addresspayable\",\"name\":\"claimer\",\"type\":\"address\"},{\"internalType\":\"bytes32\",\"name\":\"pubKeyClaim\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"pubKeyRefund\",\"type\":\"bytes32\"},{\"internalType\":\"uint256\",\"name\":\"timeout0\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"timeout1\",\"type\":\"uint256\"},{\"internalType\":\"address\",\"name\":\"asset\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"value\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"nonce\",\"type\":\"uint256\"}],\"internalType\":\"structSwapCreator.Swap\",\"name\":\"swap\",\"type\":\"tuple\"},{\"internalType\":\"uint256\",\"name\":\"fee\",\"type\":\"uint256\"},{\"internalType\":\"bytes32\",\"name\":\"relayerHash\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"swapCreator\",\"type\":\"address\"}],\"internalType\":\"structSwapCreator.RelaySwap\",\"name\":\"_relaySwap\",\"type\":\"tuple\"},{\"internalType\":\"bytes32\",\"name\":\"_secret\",\"type\":\"bytes32\"},{\"internalType\":\"addresspayable\",\"name\":\"_relayer\",\"type\":\"address\"},{\"internalType\":\"uint32\",\"name\":\"_salt\",\"type\":\"uint32\"},{\"internalType\":\"uint8\",\"name\":\"v\",\"type\":\"uint8\"},{\"internalType\":\"bytes32\",\"name\":\"r\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"s\",\"type\":\"bytes32\"}],\"name\":\"claimRelayer\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"scalar\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"qKeccak\",\"type\":\"uint256\"}],\"name\":\"mulVerify\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"_pubKeyClaim\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"_pubKeyRefund\",\"type\":\"bytes32\"},{\"internalType\":\"addresspayable\",\"name\":\"_claimer\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"_timeoutDuration0\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"_timeoutDuration1\",\"type\":\"uint256\"},{\"internalType\":\"address\",\"name\":\"_asset\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"_value\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"_nonce\",\"type\":\"uint256\"}],\"name\":\"newSwap\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"payable\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"addresspayable\",\"name\":\"owner\",\"type\":\"address\"},{\"internalType\":\"addresspayable\",\"name\":\"claimer\",\"type\":\"address\"},{\"internalType\":\"bytes32\",\"name\":\"pubKeyClaim\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"pubKeyRefund\",\"type\":\"bytes32\"},{\"internalType\":\"uint256\",\"name\":\"timeout0\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"timeout1\",\"type\":\"uint256\"},{\"internalType\":\"address\",\"name\":\"asset\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"value\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"nonce\",\"type\":\"uint256\"}],\"internalType\":\"structSwapCreator.Swap\",\"name\":\"_swap\",\"type\":\"tuple\"},{\"internalType\":\"bytes32\",\"name\":\"_secret\",\"type\":\"bytes32\"}],\"name\":\"refund\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"addresspayable\",\"name\":\"owner\",\"type\":\"address\"},{\"internalType\":\"addresspayable\",\"name\":\"claimer\",\"type\":\"address\"},{\"internalType\":\"bytes32\",\"name\":\"pubKeyClaim\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"pubKeyRefund\",\"type\":\"bytes32\"},{\"internalType\":\"uint256\",\"name\":\"timeout0\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"timeout1\",\"type\":\"uint256\"},{\"internalType\":\"address\",\"name\":\"asset\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"value\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"nonce\",\"type\":\"uint256\"}],\"internalType\":\"structSwapCreator.Swap\",\"name\":\"_swap\",\"type\":\"tuple\"}],\"name\":\"setReady\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"name\":\"swaps\",\"outputs\":[{\"internalType\":\"enumSwapCreator.Stage\",\"name\":\"\",\"type\":\"uint8\"}],\"stateMutability\":\"view\",\"type\":\"function\"}]", + Bin: "0x608060405234801561001057600080fd5b506112d3806100206000396000f3fe6080604052600436106100705760003560e01c8063b32d1b4f1161004e578063b32d1b4f146100d7578063c41e46cf1461010c578063eb84e7f21461012d578063fcaf229c1461016a57600080fd5b80631e6c5acc146100755780635cb969161461009757806387065c49146100b7575b600080fd5b34801561008157600080fd5b50610095610090366004610f67565b61018a565b005b3480156100a357600080fd5b506100956100b2366004610f67565b6103d4565b3480156100c357600080fd5b506100956100d2366004610fb9565b6104f3565b3480156100e357600080fd5b506100f76100f2366004611077565b610830565b60405190151581526020015b60405180910390f35b61011f61011a366004611099565b610900565b604051908152602001610103565b34801561013957600080fd5b5061015d61014836600461110a565b60006020819052908152604090205460ff1681565b6040516101039190611139565b34801561017657600080fd5b50610095610185366004611161565b610be2565b60008260405160200161019d91906111ed565b60408051601f1981840301815291815281516020928301206000818152928390529082205490925060ff16908160038111156101db576101db611123565b036101f957604051631115766760e01b815260040160405180910390fd5b600381600381111561020d5761020d611123565b0361022b5760405163066916a960e01b815260040160405180910390fd5b83516001600160a01b031633146102555760405163148ca24360e11b815260040160405180910390fd5b8360a0015142108015610286575083608001514211806102865750600281600381111561028457610284611123565b145b156102a4576040516332a1860f60e11b815260040160405180910390fd5b6102b2838560600151610cc0565b604051839083907e7c875846b687732a7579c19bb1dade66cd14e9f4f809565e2b2b5e76c72b4f90600090a36000828152602081905260409020805460ff1916600317905560c08401516001600160a01b031661034c57835160e08501516040516001600160a01b039092169181156108fc0291906000818181858888f19350505050158015610346573d6000803e3d6000fd5b506103ce565b60c0840151845160e086015160405163a9059cbb60e01b81526001600160a01b039283166004820152602481019190915291169063a9059cbb906044016020604051808303816000875af11580156103a8573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906103cc91906111fc565b505b50505050565b81602001516001600160a01b0316336001600160a01b03161461040a57604051633471640960e11b815260040160405180910390fd5b6104148282610ce7565b60c08201516001600160a01b031661046c5781602001516001600160a01b03166108fc8360e001519081150290604051600060405180830381858888f19350505050158015610467573d6000803e3d6000fd5b505050565b60c0820151602083015160e084015160405163a9059cbb60e01b81526001600160a01b039283166004820152602481019190915291169063a9059cbb906044016020604051808303816000875af11580156104cb573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061046791906111fc565b5050565b6000600188604051602001610508919061121e565b60408051601f198184030181528282528051602091820120600084529083018083525260ff871690820152606081018590526080810184905260a0016020604051602081039080840390855afa158015610566573d6000803e3d6000fd5b5050506020604051035190508760000151602001516001600160a01b0316816001600160a01b0316146105ac57604051638baa579f60e01b815260040160405180910390fd5b87606001516001600160a01b0316306001600160a01b0316146105e25760405163a710429d60e01b815260040160405180910390fd5b60408089015190516bffffffffffffffffffffffff19606089901b1660208201526001600160e01b031960e088901b166034820152603801604051602081830303815290604052805190602001201461064e5760405163fe16c3c560e01b815260040160405180910390fd5b875161065a9088610ce7565b875160c001516001600160a01b0316610702578760000151602001516001600160a01b03166108fc89602001518a6000015160e0015161069a9190611277565b6040518115909202916000818181858888f193505050501580156106c2573d6000803e3d6000fd5b5060208801516040516001600160a01b0388169180156108fc02916000818181858888f193505050501580156106fc573d6000803e3d6000fd5b50610826565b875160c0810151602080830151908b015160e0909301516001600160a01b039092169263a9059cbb926107359190611277565b6040516001600160e01b031960e085901b1681526001600160a01b03909216600483015260248201526044016020604051808303816000875af1158015610780573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906107a491906111fc565b50875160c00151602089015160405163a9059cbb60e01b81526001600160a01b038981166004830152602482019290925291169063a9059cbb906044016020604051808303816000875af1158015610800573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061082491906111fc565b505b5050505050505050565b600080600181601b7f79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f8179870014551231950b75fc4402da1732fc9bebe197f79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f8179889096040805160008152602081018083529590955260ff909316928401929092526060830152608082015260a0016020604051602081039080840390855afa1580156108dd573d6000803e3d6000fd5b5050604051601f1901516001600160a01b03858116911614925050505b92915050565b60008260000361092357604051637c946ed760e01b815260040160405180910390fd5b6001600160a01b0384166109565734831461095157604051632a9ffab760e21b815260040160405180910390fd5b6109cf565b6040516323b872dd60e01b8152336004820152306024820152604481018490526001600160a01b038516906323b872dd906064016020604051808303816000875af11580156109a9573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906109cd91906111fc565b505b8815806109da575087155b156109f857604051631bc61bed60e11b815260040160405180910390fd5b6001600160a01b038716610a1e576040516208978560e71b815260040160405180910390fd5b851580610a29575084155b15610a4757604051631ffb86f160e21b815260040160405180910390fd5b6000604051806101200160405280336001600160a01b03168152602001896001600160a01b031681526020018b81526020018a81526020018842610a8b919061128a565b815260200187610a9b8a4261128a565b610aa5919061128a565b8152602001866001600160a01b03168152602001858152602001848152509050600081604051602001610ad891906111ed565b60408051601f19818403018152919052805160209091012090506000808281526020819052604090205460ff166003811115610b1657610b16611123565b14610b34576040516339a2986760e11b815260040160405180910390fd5b7f91446ce035ac29998b5473504609a5ef5e961005daba4630a1684b63be848f56818c8c85608001518660a001518760c001518860e00151604051610bb3979695949392919096875260208701959095526040860193909352606085019190915260808401526001600160a01b031660a083015260c082015260e00190565b60405180910390a16000818152602081905260409020805460ff191660011790559a9950505050505050505050565b600081604051602001610bf591906111ed565b60408051601f1981840301815291905280516020909101209050600160008281526020819052604090205460ff166003811115610c3457610c34611123565b14610c5257604051630fe0fb5160e11b815260040160405180910390fd5b81516001600160a01b03163314610c7c5760405163148ca24360e11b815260040160405180910390fd5b600081815260208190526040808220805460ff191660021790555182917f5fc23b25552757626e08b316cc2387ad1bc70ee1594af7204db4ce0c39f5d15f91a25050565b610cca8282610830565b6104ef5760405163abab6bd760e01b815260040160405180910390fd5b600082604051602001610cfa91906111ed565b60408051601f1981840301815291815281516020928301206000818152928390529082205490925060ff1690816003811115610d3857610d38611123565b03610d5657604051631115766760e01b815260040160405180910390fd5b6003816003811115610d6a57610d6a611123565b03610d885760405163066916a960e01b815260040160405180910390fd5b836080015142108015610dad57506002816003811115610daa57610daa611123565b14155b15610dcb5760405163d71d60b560e01b815260040160405180910390fd5b8360a001514210610def5760405163497df9d160e01b815260040160405180910390fd5b610dfd838560400151610cc0565b604051839083907f38d6042dbdae8e73a7f6afbabd3fbe0873f9f5ed3cd71294591c3908c2e65fee90600090a3506000908152602081905260409020805460ff191660031790555050565b604051610120810167ffffffffffffffff81118282101715610e7a57634e487b7160e01b600052604160045260246000fd5b60405290565b6040516080810167ffffffffffffffff81118282101715610e7a57634e487b7160e01b600052604160045260246000fd5b6001600160a01b0381168114610ec657600080fd5b50565b8035610ed481610eb1565b919050565b60006101208284031215610eec57600080fd5b610ef4610e48565b9050610eff82610ec9565b8152610f0d60208301610ec9565b602082015260408201356040820152606082013560608201526080820135608082015260a082013560a0820152610f4660c08301610ec9565b60c082015260e082013560e082015261010080830135818301525092915050565b6000806101408385031215610f7b57600080fd5b610f858484610ed9565b94610120939093013593505050565b803563ffffffff81168114610ed457600080fd5b803560ff81168114610ed457600080fd5b6000806000806000806000878903610240811215610fd657600080fd5b61018080821215610fe657600080fd5b610fee610e80565b9150610ffa8b8b610ed9565b82526101208a013560208301526101408a013560408301526101608a013561102181610eb1565b6060830152909750880135955061103b6101a08901610ec9565b945061104a6101c08901610f94565b93506110596101e08901610fa8565b92506102008801359150610220880135905092959891949750929550565b6000806040838503121561108a57600080fd5b50508035926020909101359150565b600080600080600080600080610100898b0312156110b657600080fd5b883597506020890135965060408901356110cf81610eb1565b9550606089013594506080890135935060a08901356110ed81610eb1565b979a969950949793969295929450505060c08201359160e0013590565b60006020828403121561111c57600080fd5b5035919050565b634e487b7160e01b600052602160045260246000fd5b602081016004831061115b57634e487b7160e01b600052602160045260246000fd5b91905290565b6000610120828403121561117457600080fd5b61117e8383610ed9565b9392505050565b60018060a01b0380825116835280602083015116602084015260408201516040840152606082015160608401526080820151608084015260a082015160a08401528060c08301511660c08401525060e081015160e08301526101008082015181840152505050565b61012081016108fa8284611185565b60006020828403121561120e57600080fd5b8151801515811461117e57600080fd5b600061018082019050611232828451611185565b602083015161012083015260408301516101408301526060909201516001600160a01b03166101609091015290565b634e487b7160e01b600052601160045260246000fd5b818103818111156108fa576108fa611261565b808201808211156108fa576108fa61126156fea26469706673582212208e46497effc64f18265779b96dd009969e1f0fac0b390d6faee990d2944ed19b64736f6c63430008130033", } // SwapCreatorABI is the input ABI used to generate the binding from. @@ -57,7 +65,7 @@ var SwapCreatorABI = SwapCreatorMetaData.ABI var SwapCreatorBin = SwapCreatorMetaData.Bin // DeploySwapCreator deploys a new Ethereum contract, binding an instance of SwapCreator to it. -func DeploySwapCreator(auth *bind.TransactOpts, backend bind.ContractBackend, trustedForwarder common.Address) (common.Address, *types.Transaction, *SwapCreator, error) { +func DeploySwapCreator(auth *bind.TransactOpts, backend bind.ContractBackend) (common.Address, *types.Transaction, *SwapCreator, error) { parsed, err := SwapCreatorMetaData.GetAbi() if err != nil { return common.Address{}, nil, nil, err @@ -66,7 +74,7 @@ func DeploySwapCreator(auth *bind.TransactOpts, backend bind.ContractBackend, tr return common.Address{}, nil, nil, errors.New("GetABI returned nil") } - address, tx, contract, err := bind.DeployContract(auth, *parsed, common.FromHex(SwapCreatorBin), backend, trustedForwarder) + address, tx, contract, err := bind.DeployContract(auth, *parsed, common.FromHex(SwapCreatorBin), backend) if err != nil { return common.Address{}, nil, nil, err } @@ -215,68 +223,6 @@ func (_SwapCreator *SwapCreatorTransactorRaw) Transact(opts *bind.TransactOpts, return _SwapCreator.Contract.contract.Transact(opts, method, params...) } -// TrustedForwarder is a free data retrieval call binding the contract method 0x56c022bb. -// -// Solidity: function _trustedForwarder() view returns(address) -func (_SwapCreator *SwapCreatorCaller) TrustedForwarder(opts *bind.CallOpts) (common.Address, error) { - var out []interface{} - err := _SwapCreator.contract.Call(opts, &out, "_trustedForwarder") - - if err != nil { - return *new(common.Address), err - } - - out0 := *abi.ConvertType(out[0], new(common.Address)).(*common.Address) - - return out0, err - -} - -// TrustedForwarder is a free data retrieval call binding the contract method 0x56c022bb. -// -// Solidity: function _trustedForwarder() view returns(address) -func (_SwapCreator *SwapCreatorSession) TrustedForwarder() (common.Address, error) { - return _SwapCreator.Contract.TrustedForwarder(&_SwapCreator.CallOpts) -} - -// TrustedForwarder is a free data retrieval call binding the contract method 0x56c022bb. -// -// Solidity: function _trustedForwarder() view returns(address) -func (_SwapCreator *SwapCreatorCallerSession) TrustedForwarder() (common.Address, error) { - return _SwapCreator.Contract.TrustedForwarder(&_SwapCreator.CallOpts) -} - -// IsTrustedForwarder is a free data retrieval call binding the contract method 0x572b6c05. -// -// Solidity: function isTrustedForwarder(address forwarder) view returns(bool) -func (_SwapCreator *SwapCreatorCaller) IsTrustedForwarder(opts *bind.CallOpts, forwarder common.Address) (bool, error) { - var out []interface{} - err := _SwapCreator.contract.Call(opts, &out, "isTrustedForwarder", forwarder) - - if err != nil { - return *new(bool), err - } - - out0 := *abi.ConvertType(out[0], new(bool)).(*bool) - - return out0, err - -} - -// IsTrustedForwarder is a free data retrieval call binding the contract method 0x572b6c05. -// -// Solidity: function isTrustedForwarder(address forwarder) view returns(bool) -func (_SwapCreator *SwapCreatorSession) IsTrustedForwarder(forwarder common.Address) (bool, error) { - return _SwapCreator.Contract.IsTrustedForwarder(&_SwapCreator.CallOpts, forwarder) -} - -// IsTrustedForwarder is a free data retrieval call binding the contract method 0x572b6c05. -// -// Solidity: function isTrustedForwarder(address forwarder) view returns(bool) -func (_SwapCreator *SwapCreatorCallerSession) IsTrustedForwarder(forwarder common.Address) (bool, error) { - return _SwapCreator.Contract.IsTrustedForwarder(&_SwapCreator.CallOpts, forwarder) -} - // MulVerify is a free data retrieval call binding the contract method 0xb32d1b4f. // // Solidity: function mulVerify(uint256 scalar, uint256 qKeccak) pure returns(bool) @@ -341,44 +287,44 @@ func (_SwapCreator *SwapCreatorCallerSession) Swaps(arg0 [32]byte) (uint8, error // Claim is a paid mutator transaction binding the contract method 0x5cb96916. // -// Solidity: function claim((address,address,bytes32,bytes32,uint256,uint256,address,uint256,uint256) _swap, bytes32 _s) returns() -func (_SwapCreator *SwapCreatorTransactor) Claim(opts *bind.TransactOpts, _swap SwapCreatorSwap, _s [32]byte) (*types.Transaction, error) { - return _SwapCreator.contract.Transact(opts, "claim", _swap, _s) +// Solidity: function claim((address,address,bytes32,bytes32,uint256,uint256,address,uint256,uint256) _swap, bytes32 _secret) returns() +func (_SwapCreator *SwapCreatorTransactor) Claim(opts *bind.TransactOpts, _swap SwapCreatorSwap, _secret [32]byte) (*types.Transaction, error) { + return _SwapCreator.contract.Transact(opts, "claim", _swap, _secret) } // Claim is a paid mutator transaction binding the contract method 0x5cb96916. // -// Solidity: function claim((address,address,bytes32,bytes32,uint256,uint256,address,uint256,uint256) _swap, bytes32 _s) returns() -func (_SwapCreator *SwapCreatorSession) Claim(_swap SwapCreatorSwap, _s [32]byte) (*types.Transaction, error) { - return _SwapCreator.Contract.Claim(&_SwapCreator.TransactOpts, _swap, _s) +// Solidity: function claim((address,address,bytes32,bytes32,uint256,uint256,address,uint256,uint256) _swap, bytes32 _secret) returns() +func (_SwapCreator *SwapCreatorSession) Claim(_swap SwapCreatorSwap, _secret [32]byte) (*types.Transaction, error) { + return _SwapCreator.Contract.Claim(&_SwapCreator.TransactOpts, _swap, _secret) } // Claim is a paid mutator transaction binding the contract method 0x5cb96916. // -// Solidity: function claim((address,address,bytes32,bytes32,uint256,uint256,address,uint256,uint256) _swap, bytes32 _s) returns() -func (_SwapCreator *SwapCreatorTransactorSession) Claim(_swap SwapCreatorSwap, _s [32]byte) (*types.Transaction, error) { - return _SwapCreator.Contract.Claim(&_SwapCreator.TransactOpts, _swap, _s) +// Solidity: function claim((address,address,bytes32,bytes32,uint256,uint256,address,uint256,uint256) _swap, bytes32 _secret) returns() +func (_SwapCreator *SwapCreatorTransactorSession) Claim(_swap SwapCreatorSwap, _secret [32]byte) (*types.Transaction, error) { + return _SwapCreator.Contract.Claim(&_SwapCreator.TransactOpts, _swap, _secret) } -// ClaimRelayer is a paid mutator transaction binding the contract method 0x73e4771c. +// ClaimRelayer is a paid mutator transaction binding the contract method 0x87065c49. // -// Solidity: function claimRelayer((address,address,bytes32,bytes32,uint256,uint256,address,uint256,uint256) _swap, bytes32 _s, uint256 fee) returns() -func (_SwapCreator *SwapCreatorTransactor) ClaimRelayer(opts *bind.TransactOpts, _swap SwapCreatorSwap, _s [32]byte, fee *big.Int) (*types.Transaction, error) { - return _SwapCreator.contract.Transact(opts, "claimRelayer", _swap, _s, fee) +// Solidity: function claimRelayer(((address,address,bytes32,bytes32,uint256,uint256,address,uint256,uint256),uint256,bytes32,address) _relaySwap, bytes32 _secret, address _relayer, uint32 _salt, uint8 v, bytes32 r, bytes32 s) returns() +func (_SwapCreator *SwapCreatorTransactor) ClaimRelayer(opts *bind.TransactOpts, _relaySwap SwapCreatorRelaySwap, _secret [32]byte, _relayer common.Address, _salt uint32, v uint8, r [32]byte, s [32]byte) (*types.Transaction, error) { + return _SwapCreator.contract.Transact(opts, "claimRelayer", _relaySwap, _secret, _relayer, _salt, v, r, s) } -// ClaimRelayer is a paid mutator transaction binding the contract method 0x73e4771c. +// ClaimRelayer is a paid mutator transaction binding the contract method 0x87065c49. // -// Solidity: function claimRelayer((address,address,bytes32,bytes32,uint256,uint256,address,uint256,uint256) _swap, bytes32 _s, uint256 fee) returns() -func (_SwapCreator *SwapCreatorSession) ClaimRelayer(_swap SwapCreatorSwap, _s [32]byte, fee *big.Int) (*types.Transaction, error) { - return _SwapCreator.Contract.ClaimRelayer(&_SwapCreator.TransactOpts, _swap, _s, fee) +// Solidity: function claimRelayer(((address,address,bytes32,bytes32,uint256,uint256,address,uint256,uint256),uint256,bytes32,address) _relaySwap, bytes32 _secret, address _relayer, uint32 _salt, uint8 v, bytes32 r, bytes32 s) returns() +func (_SwapCreator *SwapCreatorSession) ClaimRelayer(_relaySwap SwapCreatorRelaySwap, _secret [32]byte, _relayer common.Address, _salt uint32, v uint8, r [32]byte, s [32]byte) (*types.Transaction, error) { + return _SwapCreator.Contract.ClaimRelayer(&_SwapCreator.TransactOpts, _relaySwap, _secret, _relayer, _salt, v, r, s) } -// ClaimRelayer is a paid mutator transaction binding the contract method 0x73e4771c. +// ClaimRelayer is a paid mutator transaction binding the contract method 0x87065c49. // -// Solidity: function claimRelayer((address,address,bytes32,bytes32,uint256,uint256,address,uint256,uint256) _swap, bytes32 _s, uint256 fee) returns() -func (_SwapCreator *SwapCreatorTransactorSession) ClaimRelayer(_swap SwapCreatorSwap, _s [32]byte, fee *big.Int) (*types.Transaction, error) { - return _SwapCreator.Contract.ClaimRelayer(&_SwapCreator.TransactOpts, _swap, _s, fee) +// Solidity: function claimRelayer(((address,address,bytes32,bytes32,uint256,uint256,address,uint256,uint256),uint256,bytes32,address) _relaySwap, bytes32 _secret, address _relayer, uint32 _salt, uint8 v, bytes32 r, bytes32 s) returns() +func (_SwapCreator *SwapCreatorTransactorSession) ClaimRelayer(_relaySwap SwapCreatorRelaySwap, _secret [32]byte, _relayer common.Address, _salt uint32, v uint8, r [32]byte, s [32]byte) (*types.Transaction, error) { + return _SwapCreator.Contract.ClaimRelayer(&_SwapCreator.TransactOpts, _relaySwap, _secret, _relayer, _salt, v, r, s) } // NewSwap is a paid mutator transaction binding the contract method 0xc41e46cf. @@ -404,23 +350,23 @@ func (_SwapCreator *SwapCreatorTransactorSession) NewSwap(_pubKeyClaim [32]byte, // Refund is a paid mutator transaction binding the contract method 0x1e6c5acc. // -// Solidity: function refund((address,address,bytes32,bytes32,uint256,uint256,address,uint256,uint256) _swap, bytes32 _s) returns() -func (_SwapCreator *SwapCreatorTransactor) Refund(opts *bind.TransactOpts, _swap SwapCreatorSwap, _s [32]byte) (*types.Transaction, error) { - return _SwapCreator.contract.Transact(opts, "refund", _swap, _s) +// Solidity: function refund((address,address,bytes32,bytes32,uint256,uint256,address,uint256,uint256) _swap, bytes32 _secret) returns() +func (_SwapCreator *SwapCreatorTransactor) Refund(opts *bind.TransactOpts, _swap SwapCreatorSwap, _secret [32]byte) (*types.Transaction, error) { + return _SwapCreator.contract.Transact(opts, "refund", _swap, _secret) } // Refund is a paid mutator transaction binding the contract method 0x1e6c5acc. // -// Solidity: function refund((address,address,bytes32,bytes32,uint256,uint256,address,uint256,uint256) _swap, bytes32 _s) returns() -func (_SwapCreator *SwapCreatorSession) Refund(_swap SwapCreatorSwap, _s [32]byte) (*types.Transaction, error) { - return _SwapCreator.Contract.Refund(&_SwapCreator.TransactOpts, _swap, _s) +// Solidity: function refund((address,address,bytes32,bytes32,uint256,uint256,address,uint256,uint256) _swap, bytes32 _secret) returns() +func (_SwapCreator *SwapCreatorSession) Refund(_swap SwapCreatorSwap, _secret [32]byte) (*types.Transaction, error) { + return _SwapCreator.Contract.Refund(&_SwapCreator.TransactOpts, _swap, _secret) } // Refund is a paid mutator transaction binding the contract method 0x1e6c5acc. // -// Solidity: function refund((address,address,bytes32,bytes32,uint256,uint256,address,uint256,uint256) _swap, bytes32 _s) returns() -func (_SwapCreator *SwapCreatorTransactorSession) Refund(_swap SwapCreatorSwap, _s [32]byte) (*types.Transaction, error) { - return _SwapCreator.Contract.Refund(&_SwapCreator.TransactOpts, _swap, _s) +// Solidity: function refund((address,address,bytes32,bytes32,uint256,uint256,address,uint256,uint256) _swap, bytes32 _secret) returns() +func (_SwapCreator *SwapCreatorTransactorSession) Refund(_swap SwapCreatorSwap, _secret [32]byte) (*types.Transaction, error) { + return _SwapCreator.Contract.Refund(&_SwapCreator.TransactOpts, _swap, _secret) } // SetReady is a paid mutator transaction binding the contract method 0xfcaf229c. diff --git a/ethereum/swap_creator_test.go b/ethereum/swap_creator_test.go index bbf18578..3f95f145 100644 --- a/ethereum/swap_creator_test.go +++ b/ethereum/swap_creator_test.go @@ -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) diff --git a/ethereum/utils.go b/ethereum/utils.go index 13b78ead..e0189cab 100644 --- a/ethereum/utils.go +++ b/ethereum/utils.go @@ -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") } diff --git a/go.mod b/go.mod index 1976c76d..dfb52545 100644 --- a/go.mod +++ b/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 diff --git a/go.sum b/go.sum index b5b29445..519c2b08 100644 --- a/go.sum +++ b/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= diff --git a/net/host.go b/net/host.go index 89eb23b7..b8ef9934 100644 --- a/net/host.go +++ b/net/host.go @@ -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) } diff --git a/net/host_test.go b/net/host_test.go index aefd7a87..621fb7cd 100644 --- a/net/host_test.go +++ b/net/host_test.go @@ -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 } diff --git a/net/message/message.go b/net/message/message.go index 38db53d0..f37a3e0f 100644 --- a/net/message/message.go +++ b/net/message/message.go @@ -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: diff --git a/net/message/relay_message.go b/net/message/relay_message.go index 072d981a..e439a392 100644 --- a/net/message/relay_message.go +++ b/net/message/relay_message.go @@ -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) diff --git a/net/relay.go b/net/relay.go index a3f25517..fd6f8914 100644 --- a/net/relay.go +++ b/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) diff --git a/net/relay_test.go b/net/relay_test.go index 6e81e0df..52dd38fc 100644 --- a/net/relay_test.go +++ b/net/relay_test.go @@ -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") } diff --git a/net/types.go b/net/types.go index 89d44a92..58ed9737 100644 --- a/net/types.go +++ b/net/types.go @@ -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 { diff --git a/protocol/backend/backend.go b/protocol/backend/backend.go index f524765c..feab63f3 100644 --- a/protocol/backend/backend.go +++ b/protocol/backend/backend.go @@ -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) +} diff --git a/protocol/xmrmaker/claim.go b/protocol/xmrmaker/claim.go index de939c3b..0836e505 100644 --- a/protocol/xmrmaker/claim.go +++ b/protocol/xmrmaker/claim.go @@ -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 } diff --git a/protocol/xmrmaker/instance_test.go b/protocol/xmrmaker/instance_test.go index 8908dff9..148678a2 100644 --- a/protocol/xmrmaker/instance_test.go +++ b/protocol/xmrmaker/instance_test.go @@ -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()) diff --git a/protocol/xmrmaker/message_handler.go b/protocol/xmrmaker/message_handler.go index e99b4c61..0f1537ff 100644 --- a/protocol/xmrmaker/message_handler.go +++ b/protocol/xmrmaker/message_handler.go @@ -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 } diff --git a/protocol/xmrtaker/min_balance.go b/protocol/xmrtaker/min_balance.go index f09ea8fa..0e40b985 100644 --- a/protocol/xmrtaker/min_balance.go +++ b/protocol/xmrtaker/min_balance.go @@ -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) diff --git a/protocol/xmrtaker/swap_state.go b/protocol/xmrtaker/swap_state.go index 6f87be5c..dbeacd23 100644 --- a/protocol/xmrtaker/swap_state.go +++ b/protocol/xmrtaker/swap_state.go @@ -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) diff --git a/protocol/xmrtaker/swap_state_test.go b/protocol/xmrtaker/swap_state_test.go index aa9d380f..322618e4 100644 --- a/protocol/xmrtaker/swap_state_test.go +++ b/protocol/xmrtaker/swap_state_test.go @@ -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) diff --git a/relayer/claim_request.go b/relayer/claim_request.go index 058c822a..7f27ebdc 100644 --- a/relayer/claim_request.go +++ b/relayer/claim_request.go @@ -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 +} diff --git a/relayer/claim_request_test.go b/relayer/claim_request_test.go index 174baa58..1e02eeb0 100644 --- a/relayer/claim_request_test.go +++ b/relayer/claim_request_test.go @@ -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") } diff --git a/relayer/forwarder.go b/relayer/forwarder.go deleted file mode 100644 index 5e6d5ee9..00000000 --- a/relayer/forwarder.go +++ /dev/null @@ -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 -} diff --git a/relayer/submit_transaction.go b/relayer/submit_transaction.go index 494e72a8..ac2520bb 100644 --- a/relayer/submit_transaction.go +++ b/relayer/submit_transaction.go @@ -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 } diff --git a/relayer/submit_transaction_test.go b/relayer/submit_transaction_test.go index a53e3bb9..5a34b219 100644 --- a/relayer/submit_transaction_test.go +++ b/relayer/submit_transaction_test.go @@ -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") } diff --git a/relayer/validate.go b/relayer/validate.go index d36f37d7..cd6ee620 100644 --- a/relayer/validate.go +++ b/relayer/validate.go @@ -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 diff --git a/relayer/validate_test.go b/relayer/validate_test.go index f05e232a..6bcfc706 100644 --- a/relayer/validate_test.go +++ b/relayer/validate_test.go @@ -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))) } diff --git a/scripts/generate-bindings.sh b/scripts/generate-bindings.sh index 2a676ca1..da4640ca 100755 --- a/scripts/generate-bindings.sh +++ b/scripts/generate-bindings.sh @@ -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 diff --git a/scripts/run-integration-tests.sh b/scripts/run-integration-tests.sh index a01fca67..cb625b73 100755 --- a/scripts/run-integration-tests.sh +++ b/scripts/run-integration-tests.sh @@ -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 diff --git a/tests/relayer_integration_test.go b/tests/relayer_integration_test.go index 0ac71200..bc06e8e7 100644 --- a/tests/relayer_integration_test.go +++ b/tests/relayer_integration_test.go @@ -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))