mirror of
https://github.com/AthanorLabs/atomic-swap.git
synced 2026-01-09 14:18:03 -05:00
feat: update SwapCreater.sol claimRelayer to no longer use forwarder (#449)
Co-authored-by: Dmitry Holodov <dimalinux@protonmail.com>
This commit is contained in:
File diff suppressed because one or more lines are too long
@@ -4,12 +4,10 @@
|
||||
package contracts
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/athanorlabs/go-relayer/impls/gsnforwarder"
|
||||
ethcommon "github.com/ethereum/go-ethereum/common"
|
||||
ethcrypto "github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/stretchr/testify/require"
|
||||
@@ -20,73 +18,33 @@ import (
|
||||
|
||||
// getContractCode is a test helper that deploys the swap creator contract to read back
|
||||
// and return the finalised byte code post deployment.
|
||||
func getContractCode(t *testing.T, forwarderAddr ethcommon.Address) []byte {
|
||||
func getContractCode(t *testing.T) []byte {
|
||||
ec, _ := tests.NewEthClient(t)
|
||||
pk := tests.GetMakerTestKey(t)
|
||||
contractAddr, _ := deploySwapCreatorWithForwarder(t, ec, pk, forwarderAddr)
|
||||
contractAddr, _ := deploySwapCreator(t, ec, pk)
|
||||
code, err := ec.CodeAt(context.Background(), contractAddr, nil)
|
||||
require.NoError(t, err)
|
||||
return code
|
||||
}
|
||||
|
||||
func TestCheckForwarderContractCode(t *testing.T) {
|
||||
ec, _ := tests.NewEthClient(t)
|
||||
pk := tests.GetMakerTestKey(t)
|
||||
forwarderAddr := deployForwarder(t, ec, pk)
|
||||
err := CheckForwarderContractCode(context.Background(), ec, forwarderAddr)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
// This test will fail if the compiled SwapCreator contract is updated, but the
|
||||
// expectedSwapCreatorBytecodeHex constant is not updated. Use this test to update the
|
||||
// constant.
|
||||
func TestExpectedSwapCreatorBytecodeHex(t *testing.T) {
|
||||
allZeroTrustedForwarder := ethcommon.Address{}
|
||||
codeHex := ethcommon.Bytes2Hex(getContractCode(t, allZeroTrustedForwarder))
|
||||
codeHex := ethcommon.Bytes2Hex(getContractCode(t))
|
||||
require.Equal(t, expectedSwapCreatorBytecodeHex, codeHex,
|
||||
"update the expectedSwapCreatorBytecodeHex constant with the actual value to fix this test")
|
||||
}
|
||||
|
||||
// This test will fail if the compiled SwapCreator contract is updated, but the
|
||||
// forwarderAddrIndexes slice of trusted forwarder locations is not updated. Use
|
||||
// this test to update the slice.
|
||||
func TestForwarderAddrIndexes(t *testing.T) {
|
||||
ec, _ := tests.NewEthClient(t)
|
||||
pk := tests.GetMakerTestKey(t)
|
||||
forwarderAddr := deployForwarder(t, ec, pk)
|
||||
contactBytes := getContractCode(t, forwarderAddr)
|
||||
|
||||
addressLocations := make([]int, 0) // at the current time, there should always be 2
|
||||
for i := 0; i < len(contactBytes)-ethAddrByteLen; i++ {
|
||||
if bytes.Equal(contactBytes[i:i+ethAddrByteLen], forwarderAddr[:]) {
|
||||
addressLocations = append(addressLocations, i)
|
||||
i += ethAddrByteLen - 1 // -1 since the loop will increment by 1
|
||||
}
|
||||
}
|
||||
|
||||
t.Logf("forwarderAddrIndexes: %v", addressLocations)
|
||||
require.EqualValues(t, forwarderAddrIndices, addressLocations,
|
||||
"update forwarderAddrIndexes with above logged indexes to fix this test")
|
||||
}
|
||||
|
||||
// Ensure that we correctly verify the SwapCreator contract when initialised with
|
||||
// different trusted forwarder addresses.
|
||||
func TestCheckSwapCreatorContractCode(t *testing.T) {
|
||||
ec, _ := tests.NewEthClient(t)
|
||||
pk := tests.GetMakerTestKey(t)
|
||||
forwarderAddrs := []string{
|
||||
deployForwarder(t, ec, pk).Hex(),
|
||||
deployForwarder(t, ec, pk).Hex(),
|
||||
deployForwarder(t, ec, pk).Hex(),
|
||||
}
|
||||
|
||||
for _, addrHex := range forwarderAddrs {
|
||||
tfAddr := ethcommon.HexToAddress(addrHex)
|
||||
contractAddr, _ := deploySwapCreatorWithForwarder(t, ec, pk, tfAddr)
|
||||
parsedTFAddr, err := CheckSwapCreatorContractCode(context.Background(), ec, contractAddr)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, addrHex, parsedTFAddr.Hex())
|
||||
}
|
||||
contractAddr, _ := deploySwapCreator(t, ec, pk)
|
||||
err := CheckSwapCreatorContractCode(context.Background(), ec, contractAddr)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
// Tests that we fail when the wrong contract byte code is found
|
||||
@@ -94,38 +52,37 @@ func TestCheckSwapCreatorContractCode_fail(t *testing.T) {
|
||||
ec, _ := tests.NewEthClient(t)
|
||||
pk := tests.GetMakerTestKey(t)
|
||||
|
||||
// Deploy a forwarder contract and then try to verify it as SwapCreator contract
|
||||
contractAddr := deployForwarder(t, ec, pk)
|
||||
_, err := CheckSwapCreatorContractCode(context.Background(), ec, contractAddr)
|
||||
// Deploy a token contract and then try to verify it as SwapCreator contract
|
||||
contractAddr, _ := deployERC20Token(t, ec, pk, "name", "symbol", 10, 100)
|
||||
err := CheckSwapCreatorContractCode(context.Background(), ec, contractAddr)
|
||||
require.ErrorIs(t, err, errInvalidSwapCreatorContract)
|
||||
}
|
||||
|
||||
func TestSepoliaContract(t *testing.T) {
|
||||
t.Skip("needs to be redeployed before merge")
|
||||
ctx := context.Background()
|
||||
ec := tests.NewEthSepoliaClient(t)
|
||||
|
||||
// temporarily place a funded sepolia private key below to deploy the test contract
|
||||
const sepoliaKey = ""
|
||||
|
||||
parsedTFAddr, err := CheckSwapCreatorContractCode(ctx, ec, common.StagenetConfig().SwapCreatorAddr)
|
||||
err := CheckSwapCreatorContractCode(ctx, ec, common.StagenetConfig().SwapCreatorAddr)
|
||||
if errors.Is(err, errInvalidSwapCreatorContract) && sepoliaKey != "" {
|
||||
pk, err := ethcrypto.HexToECDSA(sepoliaKey) //nolint:govet // shadow declaration of err
|
||||
require.NoError(t, err)
|
||||
forwarderAddr := ethcommon.HexToAddress(gsnforwarder.SepoliaForwarderAddrHex)
|
||||
swapCreatorAddr, _, err := DeploySwapCreatorWithKey(ctx, ec, pk, forwarderAddr)
|
||||
swapCreatorAddr, _, err := DeploySwapCreatorWithKey(ctx, ec, pk)
|
||||
require.NoError(t, err)
|
||||
t.Fatalf("Update common.StagenetConfig()'s SwapCreatorAddr with %s", swapCreatorAddr.Hex())
|
||||
}
|
||||
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, gsnforwarder.SepoliaForwarderAddrHex, parsedTFAddr.Hex())
|
||||
}
|
||||
|
||||
func TestMainnetContract(t *testing.T) {
|
||||
t.Skip("needs to be redeployed before merge")
|
||||
ctx := context.Background()
|
||||
ec := tests.NewEthMainnetClient(t)
|
||||
mainnetConf := common.MainnetConfig()
|
||||
parsedTFAddr, err := CheckSwapCreatorContractCode(ctx, ec, mainnetConf.SwapCreatorAddr)
|
||||
err := CheckSwapCreatorContractCode(ctx, ec, mainnetConf.SwapCreatorAddr)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, gsnforwarder.MainnetForwarderAddrHex, parsedTFAddr.Hex())
|
||||
}
|
||||
|
||||
@@ -4,12 +4,12 @@ package contracts
|
||||
// ever see in a test, so you would need to adjust upwards a little to use as a
|
||||
// gas limit. We use these values to estimate minimum required balances.
|
||||
const (
|
||||
MaxNewSwapETHGas = 50589
|
||||
MaxNewSwapETHGas = 50639
|
||||
MaxNewSwapTokenGas = 86218
|
||||
MaxSetReadyGas = 31872
|
||||
MaxSetReadyGas = 32054
|
||||
MaxClaimETHGas = 43349
|
||||
MaxClaimTokenGas = 47522
|
||||
MaxRefundETHGas = 43120
|
||||
MaxRefundTokenGas = 47282
|
||||
MaxRefundETHGas = 43132
|
||||
MaxRefundTokenGas = 47294
|
||||
MaxTokenApproveGas = 47000 // 46223 with our contract
|
||||
)
|
||||
|
||||
@@ -3,6 +3,6 @@ package contracts
|
||||
// We don't deploy SwapCreator contracts or ERC20 token contracts in swaps, so
|
||||
// these constants are only compiled in for test files.
|
||||
const (
|
||||
maxSwapCreatorDeployGas = 1005177
|
||||
maxTestERC20DeployGas = 798226 // using long token names or symbols will increase this
|
||||
maxSwapCreatorDeployGas = 1094089
|
||||
maxTestERC20DeployGas = 798286 // using long token names or symbols will increase this
|
||||
)
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// OpenZeppelin Contracts (last updated v4.7.0) (metatx/ERC2771Context.sol)
|
||||
|
||||
pragma solidity ^0.8.19;
|
||||
|
||||
import {Context} from "./Context.sol";
|
||||
|
||||
/**
|
||||
* @dev Context variant with ERC2771 support.
|
||||
*/
|
||||
abstract contract ERC2771Context is Context {
|
||||
/// @custom:oz-upgrades-unsafe-allow state-variable-immutable
|
||||
// TODO: this was modified to be public (is that ok?)
|
||||
address public immutable _trustedForwarder;
|
||||
|
||||
/// @custom:oz-upgrades-unsafe-allow constructor
|
||||
constructor(address trustedForwarder) {
|
||||
_trustedForwarder = trustedForwarder;
|
||||
}
|
||||
|
||||
function isTrustedForwarder(address forwarder) public view virtual returns (bool) {
|
||||
return forwarder == _trustedForwarder;
|
||||
}
|
||||
|
||||
function _msgSender() internal view virtual override returns (address sender) {
|
||||
if (isTrustedForwarder(msg.sender)) {
|
||||
// The assembly code is more direct than the Solidity version using `abi.decode`.
|
||||
/// @solidity memory-safe-assembly
|
||||
assembly {
|
||||
sender := shr(96, calldataload(sub(calldatasize(), 20)))
|
||||
}
|
||||
} else {
|
||||
return super._msgSender();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,10 @@
|
||||
// SPDX-License-Identifier: LGPLv3
|
||||
pragma solidity ^0.8.19;
|
||||
|
||||
import {ERC2771Context} from "./ERC2771Context.sol";
|
||||
import {IERC20} from "./IERC20.sol";
|
||||
import {Secp256k1} from "./Secp256k1.sol";
|
||||
|
||||
contract SwapCreator is ERC2771Context, Secp256k1 {
|
||||
contract SwapCreator is Secp256k1 {
|
||||
// Swap state is PENDING when the swap is first created and funded
|
||||
// Alice sets Stage to READY when she sees the funds locked on the other chain.
|
||||
// this prevents Bob from withdrawing funds without locking funds on the other chain first
|
||||
@@ -41,6 +40,20 @@ contract SwapCreator is ERC2771Context, Secp256k1 {
|
||||
uint256 nonce;
|
||||
}
|
||||
|
||||
// RelaySwap contains additional information required for relayed transactions.
|
||||
// This entire structure is encoded and signed by the swap claimer, and the signature is
|
||||
// passed to the `claimRelayer` function.
|
||||
struct RelaySwap {
|
||||
// the swap the transaction is for
|
||||
Swap swap;
|
||||
// the fee, in wei, paid to the relayer
|
||||
uint256 fee;
|
||||
// hash of (relayer's payout address || 4-byte salt)
|
||||
bytes32 relayerHash;
|
||||
// address of the swap contract this transaction is meant for
|
||||
address swapCreator;
|
||||
}
|
||||
|
||||
mapping(bytes32 => Stage) public swaps;
|
||||
|
||||
event New(
|
||||
@@ -80,9 +93,6 @@ contract SwapCreator is ERC2771Context, Secp256k1 {
|
||||
// returned when the caller of `setReady` or `refund` is not the swap owner
|
||||
error OnlySwapOwner();
|
||||
|
||||
// returned when `claimRelayer` is not called by the trusted forwarder
|
||||
error OnlyTrustedForwarder();
|
||||
|
||||
// returned when the signer of the relayed transaction is not the swap's claimer
|
||||
error OnlySwapClaimer();
|
||||
|
||||
@@ -104,7 +114,15 @@ contract SwapCreator is ERC2771Context, Secp256k1 {
|
||||
// returned when the provided secret does not match the expected public key
|
||||
error InvalidSecret();
|
||||
|
||||
constructor(address trustedForwarder) ERC2771Context(trustedForwarder) {} // solhint-disable-line
|
||||
// returned when the signature of a `RelaySwap` is invalid
|
||||
error InvalidSignature();
|
||||
|
||||
// returned when the SwapCreator address is a `RelaySwap` is not the addres of this contract
|
||||
error InvalidContractAddress();
|
||||
|
||||
// returned when the hash of the relayer address and salt passed to `claimRelayer`
|
||||
// does not match the relayer hash in `RelaySwap`
|
||||
error InvalidRelayerAddress();
|
||||
|
||||
// newSwap creates a new Swap instance with the given parameters.
|
||||
// it returns the swap's ID.
|
||||
@@ -174,8 +192,9 @@ contract SwapCreator is ERC2771Context, Secp256k1 {
|
||||
|
||||
// Bob can claim if:
|
||||
// - (Alice has set the swap to `ready` or it's past timeout0) and it's before timeout1
|
||||
function claim(Swap memory _swap, bytes32 _s) public {
|
||||
_claim(_swap, _s);
|
||||
function claim(Swap memory _swap, bytes32 _secret) public {
|
||||
if (msg.sender != _swap.claimer) revert OnlySwapClaimer();
|
||||
_claim(_swap, _secret);
|
||||
|
||||
// send ether to swap claimer
|
||||
if (_swap.asset == address(0)) {
|
||||
@@ -190,45 +209,59 @@ contract SwapCreator is ERC2771Context, Secp256k1 {
|
||||
|
||||
// Bob can claim if:
|
||||
// - (Alice has set the swap to `ready` or it's past timeout0) and it's before timeout1
|
||||
// This function is only callable by the trusted forwarder.
|
||||
// It transfers the fee to the originator of the transaction.
|
||||
function claimRelayer(Swap memory _swap, bytes32 _s, uint256 fee) public {
|
||||
if (!isTrustedForwarder(msg.sender)) revert OnlyTrustedForwarder();
|
||||
_claim(_swap, _s);
|
||||
// It transfers the fee to the relayer address specified in `_relaySwap`.
|
||||
// Note: this function will revert if the swap value is less than the relayer fee;
|
||||
// in that case, `claim` must be called instead.
|
||||
function claimRelayer(
|
||||
RelaySwap memory _relaySwap,
|
||||
bytes32 _secret,
|
||||
address payable _relayer,
|
||||
uint32 _salt,
|
||||
uint8 v,
|
||||
bytes32 r,
|
||||
bytes32 s
|
||||
) public {
|
||||
address signer = ecrecover(keccak256(abi.encode(_relaySwap)), v, r, s);
|
||||
if (signer != _relaySwap.swap.claimer) revert InvalidSignature();
|
||||
if (address(this) != _relaySwap.swapCreator) revert InvalidContractAddress();
|
||||
if (keccak256(abi.encodePacked(_relayer, _salt)) != _relaySwap.relayerHash)
|
||||
revert InvalidRelayerAddress();
|
||||
|
||||
_claim(_relaySwap.swap, _secret);
|
||||
|
||||
// send ether to swap claimer, subtracting the relayer fee
|
||||
// which is sent to the originator of the transaction.
|
||||
// tx.origin is okay here, since it isn't for authentication purposes.
|
||||
if (_swap.asset == address(0)) {
|
||||
_swap.claimer.transfer(_swap.value - fee);
|
||||
payable(tx.origin).transfer(fee); // solhint-disable-line
|
||||
if (_relaySwap.swap.asset == address(0)) {
|
||||
_relaySwap.swap.claimer.transfer(_relaySwap.swap.value - _relaySwap.fee);
|
||||
payable(_relayer).transfer(_relaySwap.fee);
|
||||
} else {
|
||||
// WARN: this will FAIL for fee-on-transfer or rebasing tokens if the token
|
||||
// transfer reverts (i.e. if this contract does not contain _swap.value tokens),
|
||||
// exposing Bob's secret while giving him nothing.
|
||||
IERC20(_swap.asset).transfer(_swap.claimer, _swap.value - fee);
|
||||
IERC20(_swap.asset).transfer(tx.origin, fee); // solhint-disable-line
|
||||
IERC20(_relaySwap.swap.asset).transfer(
|
||||
_relaySwap.swap.claimer,
|
||||
_relaySwap.swap.value - _relaySwap.fee
|
||||
);
|
||||
IERC20(_relaySwap.swap.asset).transfer(_relayer, _relaySwap.fee);
|
||||
}
|
||||
}
|
||||
|
||||
function _claim(Swap memory _swap, bytes32 _s) internal {
|
||||
function _claim(Swap memory _swap, bytes32 _secret) internal {
|
||||
bytes32 swapID = keccak256(abi.encode(_swap));
|
||||
Stage swapStage = swaps[swapID];
|
||||
if (swapStage == Stage.INVALID) revert InvalidSwap();
|
||||
if (swapStage == Stage.COMPLETED) revert SwapCompleted();
|
||||
if (_msgSender() != _swap.claimer) revert OnlySwapClaimer();
|
||||
if (block.timestamp < _swap.timeout0 && swapStage != Stage.READY) revert TooEarlyToClaim();
|
||||
if (block.timestamp >= _swap.timeout1) revert TooLateToClaim();
|
||||
|
||||
verifySecret(_s, _swap.pubKeyClaim);
|
||||
emit Claimed(swapID, _s);
|
||||
verifySecret(_secret, _swap.pubKeyClaim);
|
||||
emit Claimed(swapID, _secret);
|
||||
swaps[swapID] = Stage.COMPLETED;
|
||||
}
|
||||
|
||||
// Alice can claim a refund:
|
||||
// - Until timeout0 unless she calls setReady
|
||||
// - After timeout1
|
||||
function refund(Swap memory _swap, bytes32 _s) public {
|
||||
function refund(Swap memory _swap, bytes32 _secret) public {
|
||||
bytes32 swapID = keccak256(abi.encode(_swap));
|
||||
Stage swapStage = swaps[swapID];
|
||||
if (swapStage == Stage.INVALID) revert InvalidSwap();
|
||||
@@ -239,8 +272,8 @@ contract SwapCreator is ERC2771Context, Secp256k1 {
|
||||
(block.timestamp > _swap.timeout0 || swapStage == Stage.READY)
|
||||
) revert NotTimeToRefund();
|
||||
|
||||
verifySecret(_s, _swap.pubKeyRefund);
|
||||
emit Refunded(swapID, _s);
|
||||
verifySecret(_secret, _swap.pubKeyRefund);
|
||||
emit Refunded(swapID, _secret);
|
||||
|
||||
// send asset back to swap owner
|
||||
swaps[swapID] = Stage.COMPLETED;
|
||||
@@ -251,7 +284,7 @@ contract SwapCreator is ERC2771Context, Secp256k1 {
|
||||
}
|
||||
}
|
||||
|
||||
function verifySecret(bytes32 _s, bytes32 _hashedPubkey) internal pure {
|
||||
if (!mulVerify(uint256(_s), uint256(_hashedPubkey))) revert InvalidSecret();
|
||||
function verifySecret(bytes32 _secret, bytes32 _hashedPubkey) internal pure {
|
||||
if (!mulVerify(uint256(_secret), uint256(_hashedPubkey))) revert InvalidSecret();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,8 +8,6 @@ import (
|
||||
"crypto/ecdsa"
|
||||
"fmt"
|
||||
|
||||
"github.com/athanorlabs/go-relayer/common"
|
||||
"github.com/athanorlabs/go-relayer/impls/gsnforwarder"
|
||||
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
||||
ethcommon "github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/ethclient"
|
||||
@@ -21,26 +19,19 @@ import (
|
||||
var log = logging.Logger("contracts")
|
||||
|
||||
// DeploySwapCreatorWithKey deploys the SwapCreator contract using the passed privKey to
|
||||
// pay for the gas.
|
||||
// pay for the deployment.
|
||||
func DeploySwapCreatorWithKey(
|
||||
ctx context.Context,
|
||||
ec *ethclient.Client,
|
||||
privKey *ecdsa.PrivateKey,
|
||||
forwarderAddr ethcommon.Address,
|
||||
) (ethcommon.Address, *SwapCreator, error) {
|
||||
txOpts, err := newTXOpts(ctx, ec, privKey)
|
||||
if err != nil {
|
||||
return ethcommon.Address{}, nil, err
|
||||
}
|
||||
|
||||
if (forwarderAddr != ethcommon.Address{}) {
|
||||
if err = registerDomainSeparatorIfNeeded(ctx, ec, privKey, forwarderAddr); err != nil {
|
||||
return ethcommon.Address{}, nil, fmt.Errorf("failed to deploy swap creator: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
log.Infof("deploying SwapCreator.sol with forwarderAddr %s", forwarderAddr)
|
||||
address, tx, sf, err := DeploySwapCreator(txOpts, ec, forwarderAddr)
|
||||
log.Infof("deploying SwapCreator.sol")
|
||||
address, tx, sf, err := DeploySwapCreator(txOpts, ec)
|
||||
if err != nil {
|
||||
return ethcommon.Address{}, nil, fmt.Errorf("failed to deploy swap creator: %w", err)
|
||||
}
|
||||
@@ -54,109 +45,6 @@ func DeploySwapCreatorWithKey(
|
||||
return address, sf, nil
|
||||
}
|
||||
|
||||
// DeployGSNForwarderWithKey deploys and registers the GSN forwarder using the passed
|
||||
// private key to pay the gas fees.
|
||||
func DeployGSNForwarderWithKey(
|
||||
ctx context.Context,
|
||||
ec *ethclient.Client,
|
||||
privKey *ecdsa.PrivateKey,
|
||||
) (ethcommon.Address, error) {
|
||||
txOpts, err := newTXOpts(ctx, ec, privKey)
|
||||
if err != nil {
|
||||
return ethcommon.Address{}, err
|
||||
}
|
||||
|
||||
address, tx, contract, err := gsnforwarder.DeployForwarder(txOpts, ec)
|
||||
if err != nil {
|
||||
return ethcommon.Address{}, fmt.Errorf("failed to deploy Forwarder.sol: %w", err)
|
||||
}
|
||||
|
||||
_, err = block.WaitForReceipt(ctx, ec, tx.Hash())
|
||||
if err != nil {
|
||||
return ethcommon.Address{}, err
|
||||
}
|
||||
|
||||
err = registerDomainSeparator(ctx, ec, privKey, address, contract)
|
||||
if err != nil {
|
||||
return ethcommon.Address{}, err
|
||||
}
|
||||
|
||||
return address, nil
|
||||
}
|
||||
|
||||
func isDomainSeparatorRegistered(
|
||||
ctx context.Context,
|
||||
ec *ethclient.Client,
|
||||
forwarderAddr ethcommon.Address,
|
||||
forwarder *gsnforwarder.Forwarder,
|
||||
) (isRegistered bool, err error) {
|
||||
chainID, err := ec.ChainID(ctx)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
name := gsnforwarder.DefaultName
|
||||
version := gsnforwarder.DefaultVersion
|
||||
ds, err := common.GetEIP712DomainSeparator(name, version, chainID, forwarderAddr)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
opts := &bind.CallOpts{Context: ctx}
|
||||
return forwarder.Domains(opts, ds)
|
||||
}
|
||||
|
||||
func registerDomainSeparatorIfNeeded(
|
||||
ctx context.Context,
|
||||
ec *ethclient.Client,
|
||||
privKey *ecdsa.PrivateKey,
|
||||
forwarderAddr ethcommon.Address,
|
||||
) error {
|
||||
forwarder, err := gsnforwarder.NewForwarder(forwarderAddr, ec)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
isRegistered, err := isDomainSeparatorRegistered(ctx, ec, forwarderAddr, forwarder)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if isRegistered {
|
||||
return nil
|
||||
}
|
||||
|
||||
return registerDomainSeparator(ctx, ec, privKey, forwarderAddr, forwarder)
|
||||
}
|
||||
|
||||
func registerDomainSeparator(
|
||||
ctx context.Context,
|
||||
ec *ethclient.Client,
|
||||
privKey *ecdsa.PrivateKey,
|
||||
forwarderAddr ethcommon.Address,
|
||||
forwarder *gsnforwarder.Forwarder,
|
||||
) error {
|
||||
log.Infof("registering domain separator for forwarder %s", forwarderAddr)
|
||||
txOpts, err := newTXOpts(ctx, ec, privKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tx, err := forwarder.RegisterDomainSeparator(txOpts, gsnforwarder.DefaultName, gsnforwarder.DefaultVersion)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to register domain separator: %w", err)
|
||||
}
|
||||
|
||||
_, err = block.WaitForReceipt(ctx, ec, tx.Hash())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Debugf("registered domain separator in forwarder at %s: name=%s version=%s",
|
||||
forwarderAddr,
|
||||
gsnforwarder.DefaultName,
|
||||
gsnforwarder.DefaultVersion,
|
||||
)
|
||||
return nil
|
||||
}
|
||||
|
||||
func newTXOpts(ctx context.Context, ec *ethclient.Client, privkey *ecdsa.PrivateKey) (*bind.TransactOpts, error) {
|
||||
chainID, err := ec.ChainID(ctx)
|
||||
if err != nil {
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
// Copyright 2023 The AthanorLabs/atomic-swap Authors
|
||||
// SPDX-License-Identifier: LGPL-3.0-only
|
||||
|
||||
package contracts
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/athanorlabs/go-relayer/impls/gsnforwarder"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/athanorlabs/atomic-swap/tests"
|
||||
)
|
||||
|
||||
func Test_registerDomainSeparatorIfNeeded(t *testing.T) {
|
||||
ec, _ := tests.NewEthClient(t)
|
||||
ctx := context.Background()
|
||||
privKey := tests.GetMakerTestKey(t)
|
||||
|
||||
txOpts, err := newTXOpts(ctx, ec, privKey)
|
||||
require.NoError(t, err)
|
||||
|
||||
forwarderAddr, tx, forwarder, err := gsnforwarder.DeployForwarder(txOpts, ec)
|
||||
require.NoError(t, err)
|
||||
_ = tests.MineTransaction(t, ec, tx)
|
||||
|
||||
isRegistered, err := isDomainSeparatorRegistered(ctx, ec, forwarderAddr, forwarder)
|
||||
require.NoError(t, err)
|
||||
require.False(t, isRegistered)
|
||||
|
||||
err = registerDomainSeparatorIfNeeded(ctx, ec, privKey, forwarderAddr)
|
||||
require.NoError(t, err)
|
||||
|
||||
isRegistered, err = isDomainSeparatorRegistered(ctx, ec, forwarderAddr, forwarder)
|
||||
require.NoError(t, err)
|
||||
require.True(t, isRegistered)
|
||||
}
|
||||
@@ -4,81 +4,113 @@
|
||||
package contracts
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"math/big"
|
||||
"testing"
|
||||
|
||||
ethcommon "github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/ethclient"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/athanorlabs/atomic-swap/common/types"
|
||||
"github.com/athanorlabs/atomic-swap/tests"
|
||||
)
|
||||
|
||||
func TestSwapCreator_NewSwap_ERC20(t *testing.T) {
|
||||
pkA := tests.GetTakerTestKey(t)
|
||||
ec, _ := tests.NewEthClient(t)
|
||||
addr := crypto.PubkeyToAddress(pkA.PublicKey)
|
||||
func deployERC20Token(
|
||||
t *testing.T,
|
||||
ec *ethclient.Client,
|
||||
pk *ecdsa.PrivateKey, // token owner (and pays for deployment)
|
||||
name string,
|
||||
symbol string,
|
||||
decimals uint8,
|
||||
supplyStdUnits int64,
|
||||
) (ethcommon.Address, *TestERC20) {
|
||||
addr := crypto.PubkeyToAddress(pk.PublicKey)
|
||||
supply := new(big.Int).Mul(big.NewInt(supplyStdUnits),
|
||||
new(big.Int).Exp(big.NewInt(10), big.NewInt(int64(decimals)), nil),
|
||||
)
|
||||
|
||||
// deploy TestERC20
|
||||
erc20Addr, tx, erc20Contract, err :=
|
||||
DeployTestERC20(getAuth(t, pkA), ec, "Test of the ERC20 Token", "ERC20Token", 18, addr, big.NewInt(9999))
|
||||
tokenAddr, tx, tokenContract, err :=
|
||||
DeployTestERC20(getAuth(t, pk), ec, name, symbol, decimals, addr, supply)
|
||||
require.NoError(t, err)
|
||||
receipt := getReceipt(t, ec, tx)
|
||||
|
||||
t.Logf("gas cost to deploy TestERC20.sol: %d (delta %d)",
|
||||
receipt.GasUsed, maxTestERC20DeployGas-int(receipt.GasUsed))
|
||||
require.GreaterOrEqual(t, maxTestERC20DeployGas, int(receipt.GasUsed))
|
||||
|
||||
testNewSwap(t, types.EthAsset(erc20Addr), erc20Contract)
|
||||
return tokenAddr, tokenContract
|
||||
}
|
||||
|
||||
func TestSwapCreator_NewSwap_ERC20(t *testing.T) {
|
||||
pkA := tests.GetTakerTestKey(t)
|
||||
ec, _ := tests.NewEthClient(t)
|
||||
|
||||
tokenAddr, tokenContract := deployERC20Token(
|
||||
t,
|
||||
ec,
|
||||
pkA,
|
||||
"Test of the ERC20 Token",
|
||||
"ERC20Token",
|
||||
18,
|
||||
9999,
|
||||
)
|
||||
|
||||
testNewSwap(t, types.EthAsset(tokenAddr), tokenContract)
|
||||
}
|
||||
|
||||
func TestSwapCreator_Claim_ERC20(t *testing.T) {
|
||||
pkA := tests.GetTakerTestKey(t)
|
||||
ec, _ := tests.NewEthClient(t)
|
||||
addr := crypto.PubkeyToAddress(pkA.PublicKey)
|
||||
|
||||
erc20Addr, tx, erc20Contract, err :=
|
||||
DeployTestERC20(getAuth(t, pkA), ec, "TestERC20", "TEST", 18, addr, big.NewInt(9999))
|
||||
require.NoError(t, err)
|
||||
receipt := getReceipt(t, ec, tx)
|
||||
t.Logf("gas cost to deploy TestERC20.sol: %d (delta %d)",
|
||||
receipt.GasUsed, maxTestERC20DeployGas-int(receipt.GasUsed))
|
||||
require.GreaterOrEqual(t, maxTestERC20DeployGas, int(receipt.GasUsed))
|
||||
tokenAddr, tokenContract := deployERC20Token(
|
||||
t,
|
||||
ec,
|
||||
pkA,
|
||||
"TestERC20",
|
||||
"TEST",
|
||||
18,
|
||||
9999,
|
||||
)
|
||||
|
||||
// 3 logs:
|
||||
// Approval
|
||||
// Transfer
|
||||
// New
|
||||
testClaim(t, types.EthAsset(erc20Addr), 2, big.NewInt(99), erc20Contract)
|
||||
testClaim(t, types.EthAsset(tokenAddr), 2, big.NewInt(99), tokenContract)
|
||||
}
|
||||
|
||||
func TestSwapCreator_RefundBeforeT0_ERC20(t *testing.T) {
|
||||
pkA := tests.GetTakerTestKey(t)
|
||||
ec, _ := tests.NewEthClient(t)
|
||||
addr := crypto.PubkeyToAddress(pkA.PublicKey)
|
||||
|
||||
erc20Addr, tx, erc20Contract, err :=
|
||||
DeployTestERC20(getAuth(t, pkA), ec, "TestERC20", "TEST", 18, addr, big.NewInt(9999))
|
||||
require.NoError(t, err)
|
||||
receipt := getReceipt(t, ec, tx)
|
||||
t.Logf("gas cost to deploy TestERC20.sol: %d (delta %d)",
|
||||
receipt.GasUsed, maxTestERC20DeployGas-int(receipt.GasUsed))
|
||||
require.GreaterOrEqual(t, maxTestERC20DeployGas, int(receipt.GasUsed))
|
||||
tokenAddr, tokenContract := deployERC20Token(
|
||||
t,
|
||||
ec,
|
||||
pkA,
|
||||
"TestERC20",
|
||||
"TEST",
|
||||
18,
|
||||
9999,
|
||||
)
|
||||
|
||||
testRefundBeforeT0(t, types.EthAsset(erc20Addr), erc20Contract, 2)
|
||||
testRefundBeforeT0(t, types.EthAsset(tokenAddr), tokenContract, 2)
|
||||
}
|
||||
|
||||
func TestSwapCreator_RefundAfterT1_ERC20(t *testing.T) {
|
||||
pkA := tests.GetTakerTestKey(t)
|
||||
ec, _ := tests.NewEthClient(t)
|
||||
addr := crypto.PubkeyToAddress(pkA.PublicKey)
|
||||
|
||||
erc20Addr, tx, erc20Contract, err :=
|
||||
DeployTestERC20(getAuth(t, pkA), ec, "TestERC20", "TestERC20", 18, addr, big.NewInt(9999))
|
||||
require.NoError(t, err)
|
||||
receipt := getReceipt(t, ec, tx)
|
||||
t.Logf("gas cost to deploy TestERC20.sol: %d (delta %d)",
|
||||
receipt.GasUsed, maxTestERC20DeployGas-int(receipt.GasUsed))
|
||||
require.GreaterOrEqual(t, maxTestERC20DeployGas, int(receipt.GasUsed))
|
||||
tokenAddr, tokenContract := deployERC20Token(
|
||||
t,
|
||||
ec,
|
||||
pkA,
|
||||
"TestERC20",
|
||||
"TEST",
|
||||
18,
|
||||
9999,
|
||||
)
|
||||
|
||||
testRefundAfterT1(t, types.EthAsset(erc20Addr), erc20Contract, 2)
|
||||
testRefundAfterT1(t, types.EthAsset(tokenAddr), tokenContract, 2)
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -63,19 +63,8 @@ func approveERC20(t *testing.T,
|
||||
require.GreaterOrEqual(t, MaxTokenApproveGas, int(receipt.GasUsed), "Token Approve")
|
||||
}
|
||||
|
||||
func deployForwarder(t *testing.T, ec *ethclient.Client, pk *ecdsa.PrivateKey) ethcommon.Address {
|
||||
forwarderAddr, err := DeployGSNForwarderWithKey(context.Background(), ec, pk)
|
||||
require.NoError(t, err)
|
||||
return forwarderAddr
|
||||
}
|
||||
|
||||
func deploySwapCreatorWithForwarder(
|
||||
t *testing.T,
|
||||
ec *ethclient.Client,
|
||||
pk *ecdsa.PrivateKey,
|
||||
forwarderAddr ethcommon.Address,
|
||||
) (ethcommon.Address, *SwapCreator) {
|
||||
swapCreatorAddr, tx, swapCreator, err := DeploySwapCreator(getAuth(t, pk), ec, forwarderAddr)
|
||||
func deploySwapCreator(t *testing.T, ec *ethclient.Client, pk *ecdsa.PrivateKey) (ethcommon.Address, *SwapCreator) {
|
||||
swapCreatorAddr, tx, swapCreator, err := DeploySwapCreator(getAuth(t, pk), ec)
|
||||
require.NoError(t, err)
|
||||
receipt := getReceipt(t, ec, tx)
|
||||
|
||||
@@ -86,11 +75,6 @@ func deploySwapCreatorWithForwarder(
|
||||
return swapCreatorAddr, swapCreator
|
||||
}
|
||||
|
||||
func deploySwapCreator(t *testing.T, ec *ethclient.Client, pk *ecdsa.PrivateKey) (ethcommon.Address, *SwapCreator) {
|
||||
forwarderAddr := deployForwarder(t, ec, pk)
|
||||
return deploySwapCreatorWithForwarder(t, ec, pk, forwarderAddr)
|
||||
}
|
||||
|
||||
func testNewSwap(t *testing.T, asset types.EthAsset, erc20Contract *TestERC20) {
|
||||
pk := tests.GetTakerTestKey(t)
|
||||
ec, _ := tests.NewEthClient(t)
|
||||
|
||||
@@ -56,6 +56,86 @@ func StageToString(stage byte) string {
|
||||
}
|
||||
}
|
||||
|
||||
// Hash abi-encodes the RelaySwap and returns the keccak256 hash of the encoded value.
|
||||
func (s *SwapCreatorRelaySwap) Hash() types.Hash {
|
||||
uint256Ty, err := abi.NewType("uint256", "", nil)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("failed to create uint256 type: %s", err))
|
||||
}
|
||||
|
||||
bytes32Ty, err := abi.NewType("bytes32", "", nil)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("failed to create bytes32 type: %s", err))
|
||||
}
|
||||
|
||||
addressTy, err := abi.NewType("address", "", nil)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("failed to create address type: %s", err))
|
||||
}
|
||||
|
||||
arguments := abi.Arguments{
|
||||
{
|
||||
Type: addressTy,
|
||||
},
|
||||
{
|
||||
Type: addressTy,
|
||||
},
|
||||
{
|
||||
Type: bytes32Ty,
|
||||
},
|
||||
{
|
||||
Type: bytes32Ty,
|
||||
},
|
||||
{
|
||||
Type: uint256Ty,
|
||||
},
|
||||
{
|
||||
Type: uint256Ty,
|
||||
},
|
||||
{
|
||||
Type: addressTy,
|
||||
},
|
||||
{
|
||||
Type: uint256Ty,
|
||||
},
|
||||
{
|
||||
Type: uint256Ty,
|
||||
},
|
||||
{
|
||||
Type: uint256Ty,
|
||||
},
|
||||
{
|
||||
Type: bytes32Ty,
|
||||
},
|
||||
{
|
||||
Type: addressTy,
|
||||
},
|
||||
}
|
||||
|
||||
args, err := arguments.Pack(
|
||||
s.Swap.Owner,
|
||||
s.Swap.Claimer,
|
||||
s.Swap.PubKeyClaim,
|
||||
s.Swap.PubKeyRefund,
|
||||
s.Swap.Timeout0,
|
||||
s.Swap.Timeout1,
|
||||
s.Swap.Asset,
|
||||
s.Swap.Value,
|
||||
s.Swap.Nonce,
|
||||
s.Fee,
|
||||
s.RelayerHash,
|
||||
s.SwapCreator,
|
||||
)
|
||||
if err != nil {
|
||||
// As long as none of the *big.Int fields are nil, this cannot fail.
|
||||
// When receiving SwapCreatorRelaySwap objects from peers in
|
||||
// JSON, all *big.Int values are pre-validated to be non-nil.
|
||||
panic(fmt.Sprintf("failed to pack arguments: %s", err))
|
||||
}
|
||||
|
||||
return crypto.Keccak256Hash(args)
|
||||
}
|
||||
|
||||
// SwapID calculates and returns the same hashed swap identifier that newSwap
|
||||
// emits and that is used to track the on-chain stage of a swap.
|
||||
func (sfs *SwapCreatorSwap) SwapID() types.Hash {
|
||||
@@ -131,17 +211,6 @@ func GetSecretFromLog(log *ethtypes.Log, eventTopic [32]byte) (*mcrypto.PrivateS
|
||||
return nil, errors.New("invalid event, must be one of Claimed or Refunded")
|
||||
}
|
||||
|
||||
// abiSF, err := abi.JSON(strings.NewReader(SwapCreatorMetaData.ABI))
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
|
||||
// data := log.Data
|
||||
// res, err := abiSF.Unpack(event, data)
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
|
||||
if len(log.Topics) < 3 {
|
||||
return nil, errors.New("log had not enough parameters")
|
||||
}
|
||||
@@ -165,17 +234,6 @@ func CheckIfLogIDMatches(log ethtypes.Log, eventTopic, id [32]byte) (bool, error
|
||||
return false, errors.New("invalid event, must be one of Claimed or Refunded")
|
||||
}
|
||||
|
||||
// abi, err := abi.JSON(strings.NewReader(SwapCreatorMetaData.ABI))
|
||||
// if err != nil {
|
||||
// return false, err
|
||||
// }
|
||||
|
||||
// data := log.Data
|
||||
// res, err := abi.Unpack(event, data)
|
||||
// if err != nil {
|
||||
// return false, err
|
||||
// }
|
||||
|
||||
if len(log.Topics) < 2 {
|
||||
return false, errors.New("log had not enough parameters")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user