From d62765a1ff618813a4f407d941c356d5d3f3b460 Mon Sep 17 00:00:00 2001 From: noot <36753753+noot@users.noreply.github.com> Date: Wed, 26 Jan 2022 19:27:29 -0500 Subject: [PATCH] implement SwapFactory.sol and integrate into codebase (#85) --- cmd/daemon/contract.go | 61 ++ cmd/daemon/main.go | 53 +- cmd/recover/main.go | 52 +- docs/developing.md | 30 + ethereum/contracts/Swap.sol | 99 --- ethereum/contracts/SwapFactory.sol | 133 ++++ ethereum/scripts/deploy.js | 13 + net/message.go | 6 +- protocol/alice/instance.go | 36 +- protocol/alice/message_handler.go | 11 +- protocol/alice/recovery.go | 26 +- protocol/alice/recovery_test.go | 6 +- protocol/alice/swap_state.go | 68 +- protocol/alice/swap_state_test.go | 42 +- protocol/bob/instance.go | 26 +- protocol/bob/message_handler.go | 11 +- protocol/bob/recovery.go | 18 +- protocol/bob/recovery_test.go | 9 +- protocol/bob/swap_state.go | 103 ++- protocol/bob/swap_state_test.go | 104 +-- recover/recovery.go | 9 +- scripts/generate-bindings.sh | 8 +- swap-contract/swap.go => swap-contract | 2 +- swap-contract/swap_test.go | 190 ----- swapfactory/swap_factory.go | 893 ++++++++++++++++++++++++ swapfactory/swap_factory_test.go | 342 +++++++++ {swap-contract => swapfactory}/utils.go | 34 +- 27 files changed, 1837 insertions(+), 548 deletions(-) create mode 100644 cmd/daemon/contract.go delete mode 100644 ethereum/contracts/Swap.sol create mode 100644 ethereum/contracts/SwapFactory.sol create mode 100644 ethereum/scripts/deploy.js rename swap-contract/swap.go => swap-contract (99%) delete mode 100644 swap-contract/swap_test.go create mode 100644 swapfactory/swap_factory.go create mode 100644 swapfactory/swap_factory_test.go rename {swap-contract => swapfactory}/utils.go (56%) diff --git a/cmd/daemon/contract.go b/cmd/daemon/contract.go new file mode 100644 index 00000000..8d92f2ae --- /dev/null +++ b/cmd/daemon/contract.go @@ -0,0 +1,61 @@ +package main + +import ( + "crypto/ecdsa" + "fmt" + "math/big" + + "github.com/noot/atomic-swap/common" + "github.com/noot/atomic-swap/swapfactory" + + "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" +) + +func getOrDeploySwapFactory(address ethcommon.Address, env common.Environment, basepath string, chainID *big.Int, + privkey *ecdsa.PrivateKey, ec *ethclient.Client) (*swapfactory.SwapFactory, ethcommon.Address, error) { + var ( + sf *swapfactory.SwapFactory + ) + + if env == common.Development && (address == ethcommon.Address{}) { + txOpts, err := bind.NewKeyedTransactorWithChainID(privkey, chainID) + if err != nil { + return nil, ethcommon.Address{}, err + } + + // deploy SwapFactory.sol + var tx *ethtypes.Transaction + address, tx, sf, err = deploySwapFactory(ec, txOpts) + if err != nil { + return nil, ethcommon.Address{}, err + } + + log.Infof("deployed SwapFactory.sol: address=%s tx hash=%s", address, tx.Hash()) + + // store the contract address on disk + fp := fmt.Sprintf("%s/contractaddress", basepath) + if err = common.WriteContractAddressToFile(fp, address.String()); err != nil { + return nil, ethcommon.Address{}, fmt.Errorf("failed to write contract address to file: %w", err) + } + } else { + var err error + sf, err = getSwapFactory(ec, address) + if err != nil { + return nil, ethcommon.Address{}, err + } + log.Infof("loaded SwapFactory.sol from address %s", address) + } + + return sf, address, nil +} + +func getSwapFactory(client *ethclient.Client, addr ethcommon.Address) (*swapfactory.SwapFactory, error) { + return swapfactory.NewSwapFactory(addr, client) +} + +func deploySwapFactory(client *ethclient.Client, txOpts *bind.TransactOpts) (ethcommon.Address, *ethtypes.Transaction, *swapfactory.SwapFactory, error) { //nolint:lll + return swapfactory.DeploySwapFactory(txOpts, client) +} diff --git a/cmd/daemon/main.go b/cmd/daemon/main.go index 03cd2608..59061aec 100644 --- a/cmd/daemon/main.go +++ b/cmd/daemon/main.go @@ -6,6 +6,9 @@ import ( "os" "strings" + ethcommon "github.com/ethereum/go-ethereum/common" + ethcrypto "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/ethclient" "github.com/urfave/cli" "github.com/noot/atomic-swap/cmd/utils" @@ -15,6 +18,7 @@ import ( "github.com/noot/atomic-swap/protocol/bob" "github.com/noot/atomic-swap/protocol/swap" "github.com/noot/atomic-swap/rpc" + "github.com/noot/atomic-swap/swapfactory" logging "github.com/ipfs/go-log" ) @@ -100,6 +104,10 @@ var ( Name: "ethereum-chain-id", Usage: "ethereum chain ID; eg. mainnet=1, ropsten=3, rinkeby=4, goerli=5, ganache=1337", }, + &cli.StringFlag{ + Name: "contract-address", + Usage: "address of instance of SwapFactory.sol already deployed on-chain", + }, &cli.StringFlag{ Name: "bootnodes", Usage: "comma-separated string of libp2p bootnodes", @@ -259,7 +267,7 @@ func runDaemon(c *cli.Context) error { } }() - log.Info("started swapd with basepath %d", + log.Infof("started swapd with basepath %d", cfg.Basepath, ) wait(ctx) @@ -303,17 +311,46 @@ func getProtocolInstances(ctx context.Context, c *cli.Context, env common.Enviro gasPrice = big.NewInt(int64(c.Uint("gas-price"))) } + var contractAddr ethcommon.Address + contractAddrStr := c.String("contract-address") + if contractAddrStr == "" { + contractAddr = ethcommon.Address{} + } else { + contractAddr = ethcommon.HexToAddress(contractAddrStr) + } + + pk, err := ethcrypto.HexToECDSA(ethPrivKey) + if err != nil { + return nil, nil, err + } + + ec, err := ethclient.Dial(ethEndpoint) + if err != nil { + return nil, nil, err + } + + var contract *swapfactory.SwapFactory + if !devBob { + contract, contractAddr, err = getOrDeploySwapFactory(contractAddr, env, cfg.Basepath, + big.NewInt(chainID), pk, ec) + if err != nil { + return nil, nil, err + } + } + aliceCfg := &alice.Config{ Ctx: ctx, Basepath: cfg.Basepath, MoneroWalletEndpoint: moneroEndpoint, - EthereumEndpoint: ethEndpoint, - EthereumPrivateKey: ethPrivKey, + EthereumClient: ec, + EthereumPrivateKey: pk, Environment: env, - ChainID: chainID, + ChainID: big.NewInt(chainID), GasPrice: gasPrice, GasLimit: uint64(c.Uint("gas-limit")), SwapManager: sm, + SwapContract: contract, + SwapContractAddress: contractAddr, } a, err = alice.NewInstance(aliceCfg) @@ -333,10 +370,10 @@ func getProtocolInstances(ctx context.Context, c *cli.Context, env common.Enviro MoneroDaemonEndpoint: daemonEndpoint, WalletFile: walletFile, WalletPassword: walletPassword, - EthereumEndpoint: ethEndpoint, - EthereumPrivateKey: ethPrivKey, + EthereumClient: ec, + EthereumPrivateKey: pk, Environment: env, - ChainID: chainID, + ChainID: big.NewInt(chainID), GasPrice: gasPrice, GasLimit: uint64(c.Uint("gas-limit")), SwapManager: sm, @@ -347,7 +384,7 @@ func getProtocolInstances(ctx context.Context, c *cli.Context, env common.Enviro return nil, nil, err } - log.Info("created swap protocol module with monero endpoint %s and ethereum endpoint %s", + log.Infof("created swap protocol module with monero endpoint %s and ethereum endpoint %s", moneroEndpoint, ethEndpoint, ) diff --git a/cmd/recover/main.go b/cmd/recover/main.go index b06b7a61..1f2a29ef 100644 --- a/cmd/recover/main.go +++ b/cmd/recover/main.go @@ -6,6 +6,8 @@ import ( "math/big" "os" + ethcrypto "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/ethclient" "github.com/urfave/cli" "github.com/noot/atomic-swap/cmd/utils" @@ -32,7 +34,6 @@ var ( app = &cli.App{ Name: "swaprecover", Usage: "A program for recovering swap funds due to unexpected shutdowns", - //Action: runRecover, Flags: []cli.Flag{ &cli.StringFlag{ Name: "env", @@ -66,6 +67,10 @@ var ( Name: "gas-limit", Usage: "ethereum gas limit to use for transactions. if not set, the gas limit is estimated for each transaction.", }, + &cli.UintFlag{ + Name: "contract-swap-id", + Usage: "ID of the swap within the SwapFactory.sol contract", + }, }, Commands: []cli.Command{ { @@ -102,8 +107,8 @@ func main() { // MoneroRecoverer is implemented by a backend which is able to recover monero type MoneroRecoverer interface { WalletFromSecrets(aliceSecret, bobSecret string) (mcrypto.Address, error) - RecoverFromBobSecretAndContract(b *bob.Instance, bobSecret, contractAddr string) (*bob.RecoveryResult, error) - RecoverFromAliceSecretAndContract(a *alice.Instance, aliceSecret, contractAddr string) (*alice.RecoveryResult, error) + RecoverFromBobSecretAndContract(b *bob.Instance, bobSecret, contractAddr string, swapID *big.Int) (*bob.RecoveryResult, error) //nolint:lll + RecoverFromAliceSecretAndContract(a *alice.Instance, aliceSecret, contractAddr string, swapID *big.Int) (*alice.RecoveryResult, error) //nolint:lll } func runRecoverMonero(c *cli.Context) error { @@ -128,6 +133,11 @@ func runRecoverMonero(c *cli.Context) error { return errors.New("must also provide one of --contract-addr or --bob-secret") } + swapID := big.NewInt(int64(c.Uint("contract-swap-id"))) + if swapID.Uint64() == 0 { + log.Warn("provided contract swap ID of 0, this is likely not correct (unless you deployed the contract)") + } + r, err := getRecoverer(c, env) if err != nil { return err @@ -149,7 +159,7 @@ func runRecoverMonero(c *cli.Context) error { return err } - res, err := r.RecoverFromBobSecretAndContract(b, bs, contractAddr) + res, err := r.RecoverFromBobSecretAndContract(b, bs, contractAddr, swapID) if err != nil { return err } @@ -171,7 +181,7 @@ func runRecoverMonero(c *cli.Context) error { return err } - res, err := r.RecoverFromAliceSecretAndContract(a, as, contractAddr) + res, err := r.RecoverFromAliceSecretAndContract(a, as, contractAddr, swapID) if err != nil { return err } @@ -249,14 +259,24 @@ func createAliceInstance(ctx context.Context, c *cli.Context, env common.Environ gasPrice = big.NewInt(int64(c.Uint("gas-price"))) } + pk, err := ethcrypto.HexToECDSA(ethPrivKey) + if err != nil { + return nil, err + } + + ec, err := ethclient.Dial(ethEndpoint) + if err != nil { + return nil, err + } + aliceCfg := &alice.Config{ Ctx: ctx, Basepath: cfg.Basepath, MoneroWalletEndpoint: moneroEndpoint, - EthereumEndpoint: ethEndpoint, - EthereumPrivateKey: ethPrivKey, + EthereumClient: ec, + EthereumPrivateKey: pk, Environment: env, - ChainID: chainID, + ChainID: big.NewInt(chainID), GasPrice: gasPrice, GasLimit: uint64(c.Uint("gas-limit")), } @@ -298,14 +318,24 @@ func createBobInstance(ctx context.Context, c *cli.Context, env common.Environme gasPrice = big.NewInt(int64(c.Uint("gas-price"))) } + pk, err := ethcrypto.HexToECDSA(ethPrivKey) + if err != nil { + return nil, err + } + + ec, err := ethclient.Dial(ethEndpoint) + if err != nil { + return nil, err + } + bobCfg := &bob.Config{ Ctx: ctx, Basepath: cfg.Basepath, MoneroWalletEndpoint: moneroEndpoint, - EthereumEndpoint: ethEndpoint, - EthereumPrivateKey: ethPrivKey, + EthereumClient: ec, + EthereumPrivateKey: pk, Environment: env, - ChainID: chainID, + ChainID: big.NewInt(chainID), GasPrice: gasPrice, GasLimit: uint64(c.Uint("gas-limit")), } diff --git a/docs/developing.md b/docs/developing.md index 4e19f305..3a318bff 100644 --- a/docs/developing.md +++ b/docs/developing.md @@ -1,5 +1,35 @@ # Developing +## Deploying or using deployed SwapFactory.sol + +The swap program uses a "factory" contract for the Ethereum side to reduce gas costs from deploying a new contract for each swap. The contract can be found in [here](../ethereum/contracts/SwapFactory.sol). For each new swap, the eth-holding party will call `NewSwap` on the factory contract, initiating a swap instance inside the contract. + +If you're developing on a local network, running a `swapd` instance with the `--dev-alice` flag will automatically deploy an instance of `SwapFactory.sol` for you. You should see the following log shortly after starting `./swapd --dev-alice`: +```bash +# 2022-01-26T18:39:04.600-0500 INFO cmd daemon/contract.go:35 deployed SwapFactory.sol: address=0x3F2aF34E4250de94242Ac2B8A38550fd4503696d tx hash=0x638caf280178b3cfe06854b8a76a4ce355d38c5d81187836f0733cad1287b657 +``` + +If you wish to use an instance of `SwapFactory.sol` that's already deployed on-chain, you can use the `--contract-address` flag to specify the address. For example: +```bash +$ ./swapd --dev-alice --contract-address 0x3F2aF34E4250de94242Ac2B8A38550fd4503696d +# 2022-01-26T18:56:31.627-0500 INFO cmd daemon/contract.go:42 loaded SwapFactory.sol from address 0x3F2aF34E4250de94242Ac2B8A38550fd4503696d +``` + +If you want to deploy the contract without running `swapd`, you can use hardhat. You will need node.js installed. +```bash +cd ethereum +npm install --save-dev hardhat +``` + +Then, you can run the deployment script against your desired network to deploy the contract. + +```bash +$ npx hardhat run --network localhost scripts/deploy.js +# Compiling 2 files with 0.8.5 +# Compilation finished successfully +# SwapFactory deployed to: 0xB0f6EC177Ab867E372479C0bfdBB0068bb5E1554 +``` + ## Compiling DLEq binaries The program utilizes a Rust DLEq library implemented by Farcaster. diff --git a/ethereum/contracts/Swap.sol b/ethereum/contracts/Swap.sol deleted file mode 100644 index 0fd6f06c..00000000 --- a/ethereum/contracts/Swap.sol +++ /dev/null @@ -1,99 +0,0 @@ -// SPDX-License-Identifier: LGPLv3 - -pragma solidity ^0.8.5; - -import "./Secp256k1.sol"; - -contract Swap { - // Ed25519 library - Secp256k1 immutable secp256k1; - - // contract creator, Alice - address payable immutable owner; - - // address allowed to claim the ether in this contract - address payable immutable claimer; - - // the keccak256 hash of the expected public key derived from the secret `s_b`. - // this public key is a point on the secp256k1 curve - bytes32 public immutable pubKeyClaim; - - // the keccak256 hash of the expected public key derived from the secret `s_a`. - // this public key is a point on the secp256k1 curve - bytes32 public immutable pubKeyRefund; - - // timestamp (set at contract creation) - // before which Alice can call either set_ready or refund - uint256 public immutable timeout_0; - - // timestamp after which Bob cannot claim, only Alice can refund. - uint256 public immutable timeout_1; - - // Alice sets ready to true when she sees the funds locked on the other chain. - // this prevents Bob from withdrawing funds without locking funds on the other chain first - bool public isReady = false; - - event Constructed(bytes32 claimKey, bytes32 refundKey); - event Ready(bool b); - event Claimed(bytes32 s); - event Refunded(bytes32 s); - - constructor(bytes32 _pubKeyClaim, bytes32 _pubKeyRefund, address payable _claimer, uint256 _timeoutDuration) payable { - owner = payable(msg.sender); - pubKeyClaim = _pubKeyClaim; - pubKeyRefund = _pubKeyRefund; - claimer = _claimer; - timeout_0 = block.timestamp + _timeoutDuration; - timeout_1 = block.timestamp + (_timeoutDuration * 2); - secp256k1 = new Secp256k1(); - emit Constructed(_pubKeyClaim, _pubKeyRefund); - } - - // Alice must call set_ready() within t_0 once she verifies the XMR has been locked - function set_ready() external { - require(!isReady && msg.sender == owner); - isReady = true; - emit Ready(true); - } - - // Bob can claim if: - // - Alice doesn't call set_ready or refund within t_0, or - // - Alice calls ready within t_0, in which case Bob can call claim until t_1 - function claim(bytes32 _s) external { - require(msg.sender == claimer, "only claimer can claim!"); - require((block.timestamp >= timeout_0 || isReady), "too early to claim!"); - require(block.timestamp < timeout_1, "too late to claim!"); - - verifySecret(_s, pubKeyClaim); - emit Claimed(_s); - - // send eth to caller (Bob) - //selfdestruct(payable(msg.sender)); - claimer.transfer(address(this).balance); - } - - // Alice can claim a refund: - // - Until t_0 unless she calls set_ready - // - After t_1, if she called set_ready - function refund(bytes32 _s) external { - require(msg.sender == owner); - require( - block.timestamp >= timeout_1 || ( block.timestamp < timeout_0 && !isReady), - "It's Bob's turn now, please wait!" - ); - - verifySecret(_s, pubKeyRefund); - emit Refunded(_s); - - // send eth back to owner==caller (Alice) - //selfdestruct(owner); - owner.transfer(address(this).balance); - } - - function verifySecret(bytes32 _s, bytes32 pubKey) internal view { - require( - secp256k1.mulVerify(uint256(_s), uint256(pubKey)), - "provided secret does not match the expected pubKey" - ); - } -} diff --git a/ethereum/contracts/SwapFactory.sol b/ethereum/contracts/SwapFactory.sol new file mode 100644 index 00000000..28d48bc8 --- /dev/null +++ b/ethereum/contracts/SwapFactory.sol @@ -0,0 +1,133 @@ +// SPDX-License-Identifier: LGPLv3 +pragma solidity ^0.8.5; + +import "./Secp256k1.sol"; + +contract SwapFactory { + Secp256k1 immutable secp256k1; + + uint256 nextID; + + struct Swap { + // contract creator, Alice + address payable owner; + + // address allowed to claim the ether in this contract + address payable claimer; + + // the keccak256 hash of the expected public key derived from the secret `s_b`. + // this public key is a point on the secp256k1 curve + bytes32 pubKeyClaim; + + // the keccak256 hash of the expected public key derived from the secret `s_a`. + // this public key is a point on the secp256k1 curve + bytes32 pubKeyRefund; + + // timestamp (set at contract creation) + // before which Alice can call either set_ready or refund + uint256 timeout_0; + + // timestamp after which Bob cannot claim, only Alice can refund. + uint256 timeout_1; + + // Alice sets ready to true when she sees the funds locked on the other chain. + // this prevents Bob from withdrawing funds without locking funds on the other chain first + bool isReady; + + // set to true upon the swap value being claimed or refunded. + bool completed; + + // the value of this swap. + uint256 value; + } + + mapping(uint256 => Swap) public swaps; + + event New(uint256 swapID, bytes32 claimKey, bytes32 refundKey); + event Ready(uint256 swapID); + event Claimed(uint256 swapID, bytes32 s); + event Refunded(uint256 swapID, bytes32 s); + + constructor() { + secp256k1 = new Secp256k1(); + } + + // new_swap creates a new Swap instance with the given parameters. + // it returns the swap's ID. + function new_swap(bytes32 _pubKeyClaim, + bytes32 _pubKeyRefund, + address payable _claimer, + uint256 _timeoutDuration + ) public payable returns (uint256) { + uint256 id = nextID; + + Swap memory swap; + swap.owner = payable(msg.sender); + swap.claimer = _claimer; + swap.pubKeyClaim = _pubKeyClaim; + swap.pubKeyRefund = _pubKeyRefund; + swap.timeout_0 = block.timestamp + _timeoutDuration; + swap.timeout_1 = block.timestamp + (_timeoutDuration * 2); + swap.value = msg.value; + + emit New(id, _pubKeyClaim, _pubKeyRefund); + nextID += 1; + swaps[id] = swap; + return id; + } + + // Alice must call set_ready() within t_0 once she verifies the XMR has been locked + function set_ready(uint256 id) public { + require(!swaps[id].completed, "swap is already completed"); + require(swaps[id].owner == msg.sender, "only the swap owner can call set_ready"); + require(!swaps[id].isReady, "swap was already set to ready"); + swaps[id].isReady = true; + emit Ready(id); + } + + // Bob can claim if: + // - Alice doesn't call set_ready or refund within t_0, or + // - Alice calls ready within t_0, in which case Bob can call claim until t_1 + function claim(uint256 id, bytes32 _s) public { + Swap memory swap = swaps[id]; + require(!swap.completed, "swap is already completed"); + require(msg.sender == swap.claimer, "only claimer can claim!"); + require((block.timestamp >= swap.timeout_0 || swap.isReady), "too early to claim!"); + require(block.timestamp < swap.timeout_1, "too late to claim!"); + + verifySecret(_s, swap.pubKeyClaim); + emit Claimed(id, _s); + + // send eth to caller (Bob) + swap.claimer.transfer(swap.value); + swaps[id].completed = true; + } + + // Alice can claim a refund: + // - Until t_0 unless she calls set_ready + // - After t_1, if she called set_ready + function refund(uint256 id, bytes32 _s) public { + Swap memory swap = swaps[id]; + require(!swap.completed, "swap is already completed"); + require(msg.sender == swap.owner, "refund must be called by the swap owner"); + require( + block.timestamp >= swap.timeout_1 || + (block.timestamp < swap.timeout_0 && !swap.isReady), + "it's the counterparty's turn, unable to refund, try again later" + ); + + verifySecret(_s, swap.pubKeyRefund); + emit Refunded(id, _s); + + // send eth back to owner==caller (Alice) + swap.owner.transfer(swap.value); + swaps[id].completed = true; + } + + function verifySecret(bytes32 _s, bytes32 pubKey) internal view { + require( + secp256k1.mulVerify(uint256(_s), uint256(pubKey)), + "provided secret does not match the expected public key" + ); + } +} diff --git a/ethereum/scripts/deploy.js b/ethereum/scripts/deploy.js new file mode 100644 index 00000000..ba57d223 --- /dev/null +++ b/ethereum/scripts/deploy.js @@ -0,0 +1,13 @@ +async function main() { + const SwapFactory = await ethers.getContractFactory("SwapFactory"); + const contract = await SwapFactory.deploy(); + + console.log("SwapFactory deployed to:", contract.address); +} + +main() + .then(() => process.exit(0)) + .catch((error) => { + console.error(error); + process.exit(1); + }); \ No newline at end of file diff --git a/net/message.go b/net/message.go index 6917e62f..72bdbb46 100644 --- a/net/message.go +++ b/net/message.go @@ -4,6 +4,7 @@ import ( "encoding/json" "errors" "fmt" + "math/big" "github.com/noot/atomic-swap/common/types" ) @@ -168,12 +169,13 @@ func (m *SendKeysMessage) Type() MessageType { // NotifyContractDeployed is sent by Alice to Bob after deploying the swap contract // and locking her ether in it type NotifyContractDeployed struct { - Address string + Address string + ContractSwapID *big.Int } // String ... func (m *NotifyContractDeployed) String() string { - return "NotifyContractDeployed" + return fmt.Sprintf("NotifyContractDeployed Address=%s ContractSwapID=%d", m.Address, m.ContractSwapID) } // Encode ... diff --git a/protocol/alice/instance.go b/protocol/alice/instance.go index be0887d8..4564a566 100644 --- a/protocol/alice/instance.go +++ b/protocol/alice/instance.go @@ -7,6 +7,7 @@ import ( "sync" "github.com/ethereum/go-ethereum/accounts/abi/bind" + ethcommon "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/ethclient" @@ -14,6 +15,7 @@ import ( "github.com/noot/atomic-swap/monero" "github.com/noot/atomic-swap/net" "github.com/noot/atomic-swap/protocol/swap" + "github.com/noot/atomic-swap/swapfactory" logging "github.com/ipfs/go-log" ) @@ -45,7 +47,9 @@ type Instance struct { swapMu sync.Mutex swapState *swapState - swapManager *swap.Manager + swapManager *swap.Manager + contract *swapfactory.SwapFactory + contractAddr ethcommon.Address } // Config contains the configuration values for a new Alice instance. @@ -53,10 +57,12 @@ type Config struct { Ctx context.Context Basepath string MoneroWalletEndpoint string - EthereumEndpoint string - EthereumPrivateKey string + EthereumClient *ethclient.Client + EthereumPrivateKey *ecdsa.PrivateKey + SwapContract *swapfactory.SwapFactory + SwapContractAddress ethcommon.Address Environment common.Environment - ChainID int64 + ChainID *big.Int GasPrice *big.Int GasLimit uint64 SwapManager *swap.Manager @@ -66,32 +72,24 @@ type Config struct { // It accepts an endpoint to a monero-wallet-rpc instance where Alice will generate // the account in which the XMR will be deposited. func NewInstance(cfg *Config) (*Instance, error) { - pk, err := crypto.HexToECDSA(cfg.EthereumPrivateKey) - if err != nil { - return nil, err - } - - ec, err := ethclient.Dial(cfg.EthereumEndpoint) - if err != nil { - return nil, err - } - - pub := pk.Public().(*ecdsa.PublicKey) + pub := cfg.EthereumPrivateKey.Public().(*ecdsa.PublicKey) // TODO: check that Alice's monero-wallet-cli endpoint has wallet-dir configured return &Instance{ ctx: cfg.Ctx, basepath: cfg.Basepath, env: cfg.Environment, - ethPrivKey: pk, - ethClient: ec, + ethPrivKey: cfg.EthereumPrivateKey, + ethClient: cfg.EthereumClient, client: monero.NewClient(cfg.MoneroWalletEndpoint), callOpts: &bind.CallOpts{ From: crypto.PubkeyToAddress(*pub), Context: cfg.Ctx, }, - chainID: big.NewInt(cfg.ChainID), - swapManager: cfg.SwapManager, + chainID: cfg.ChainID, + swapManager: cfg.SwapManager, + contract: cfg.SwapContract, + contractAddr: cfg.SwapContractAddress, }, nil } diff --git a/protocol/alice/message_handler.go b/protocol/alice/message_handler.go index de185d0e..04498e2e 100644 --- a/protocol/alice/message_handler.go +++ b/protocol/alice/message_handler.go @@ -11,7 +11,7 @@ import ( "github.com/noot/atomic-swap/net" pcommon "github.com/noot/atomic-swap/protocol" pswap "github.com/noot/atomic-swap/protocol/swap" - "github.com/noot/atomic-swap/swap-contract" + "github.com/noot/atomic-swap/swapfactory" ethcommon "github.com/ethereum/go-ethereum/common" "github.com/fatih/color" //nolint:misspell @@ -104,12 +104,12 @@ func (s *swapState) handleSendKeysMessage(msg *net.SendKeysMessage) (net.Message } s.setBobKeys(sk, vk, secp256k1Pub) - address, err := s.deployAndLockETH(s.providedAmountInWei()) + err = s.lockETH(s.providedAmountInWei()) if err != nil { return nil, fmt.Errorf("failed to deploy contract: %w", err) } - log.Info("deployed Swap contract, waiting for XMR to be locked: contract address=", address) + log.Info("locked ether in swap contract, waiting for XMR to be locked") // set t0 and t1 // TODO: these sometimes fail with "attempting to unmarshall an empty string while arguments are expected" @@ -150,7 +150,8 @@ func (s *swapState) handleSendKeysMessage(msg *net.SendKeysMessage) (net.Message s.nextExpectedMessage = &net.NotifyXMRLock{} out := &net.NotifyContractDeployed{ - Address: address.String(), + Address: s.alice.contractAddr.String(), + ContractSwapID: s.contractSwapID, } return out, nil @@ -290,7 +291,7 @@ func (s *swapState) handleNotifyClaimed(txHash string) (mcrypto.Address, error) return "", errors.New("claim transaction has no logs") } - skB, err := swap.GetSecretFromLog(receipt.Logs[0], "Claimed") + skB, err := swapfactory.GetSecretFromLog(receipt.Logs[0], "Claimed") if err != nil { return "", fmt.Errorf("failed to get secret from log: %w", err) } diff --git a/protocol/alice/recovery.go b/protocol/alice/recovery.go index 44adbb09..a27ea7ac 100644 --- a/protocol/alice/recovery.go +++ b/protocol/alice/recovery.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "math/big" eth "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/accounts/abi/bind" @@ -11,10 +12,10 @@ import ( mcrypto "github.com/noot/atomic-swap/crypto/monero" "github.com/noot/atomic-swap/dleq" - "github.com/noot/atomic-swap/swap-contract" + "github.com/noot/atomic-swap/swapfactory" ) -var claimedTopic = ethcommon.HexToHash("0xeddf608ef698454af2fb41c1df7b7e5154ff0d46969f895e0f39c7dfe7e6380a") +var claimedTopic = ethcommon.HexToHash("0xd5a2476fc450083bbb092dd3f4be92698ffdc2d213e6f1e730c7f44a52f1ccfc") var ( errNoClaimLogsFound = errors.New("no Claimed logs found") @@ -28,7 +29,7 @@ type recoveryState struct { // NewRecoveryState returns a new *bob.recoveryState, // which has methods to either claim ether or reclaim monero from an initiated swap. func NewRecoveryState(a *Instance, secret *mcrypto.PrivateSpendKey, - contractAddr ethcommon.Address) (*recoveryState, error) { //nolint:revive + contractAddr ethcommon.Address, contractSwapID *big.Int) (*recoveryState, error) { //nolint:revive txOpts, err := bind.NewKeyedTransactorWithChainID(a.ethPrivKey, a.chainID) if err != nil { return nil, err @@ -49,13 +50,14 @@ func NewRecoveryState(a *Instance, secret *mcrypto.PrivateSpendKey, ctx, cancel := context.WithCancel(a.ctx) s := &swapState{ - ctx: ctx, - cancel: cancel, - alice: a, - txOpts: txOpts, - privkeys: kp, - pubkeys: pubkp, - dleqProof: dleq.NewProofWithSecret(sc), + ctx: ctx, + cancel: cancel, + alice: a, + txOpts: txOpts, + privkeys: kp, + pubkeys: pubkp, + dleqProof: dleq.NewProofWithSecret(sc), + contractSwapID: contractSwapID, } rs := &recoveryState{ @@ -127,7 +129,7 @@ func (rs *recoveryState) ClaimOrRefund() (*RecoveryResult, error) { func (rs *recoveryState) setContract(address ethcommon.Address) error { var err error rs.contractAddr = address - rs.ss.contract, err = swap.NewSwap(address, rs.ss.alice.ethClient) + rs.ss.alice.contract, err = swapfactory.NewSwapFactory(address, rs.ss.alice.ethClient) return err } @@ -144,7 +146,7 @@ func (rs *recoveryState) filterForClaim() (*mcrypto.PrivateSpendKey, error) { return nil, errNoClaimLogsFound } - sa, err := swap.GetSecretFromLog(&logs[0], "Claimed") + sa, err := swapfactory.GetSecretFromLog(&logs[0], "Claimed") if err != nil { return nil, fmt.Errorf("failed to get secret from log: %w", err) } diff --git a/protocol/alice/recovery_test.go b/protocol/alice/recovery_test.go index 361fef23..73196641 100644 --- a/protocol/alice/recovery_test.go +++ b/protocol/alice/recovery_test.go @@ -21,10 +21,10 @@ func newTestRecoveryState(t *testing.T) *recoveryState { s.setBobKeys(s.pubkeys.SpendKey(), s.privkeys.ViewKey(), akp.Secp256k1PublicKey) s.bobAddress = inst.callOpts.From - addr, err := s.deployAndLockETH(common.NewEtherAmount(1)) + err = s.lockETH(common.NewEtherAmount(1)) require.NoError(t, err) - rs, err := NewRecoveryState(inst, s.privkeys.SpendKey(), addr) + rs, err := NewRecoveryState(inst, s.privkeys.SpendKey(), inst.contractAddr, s.contractSwapID) require.NoError(t, err) return rs } @@ -40,7 +40,7 @@ func TestClaimOrRefund_Claim(t *testing.T) { // call swap.Claim() sc := rs.ss.getSecret() - _, err = rs.ss.contract.Claim(rs.ss.txOpts, sc) + _, err = rs.ss.alice.contract.Claim(rs.ss.txOpts, rs.ss.contractSwapID, sc) require.NoError(t, err) // assert we can claim the monero diff --git a/protocol/alice/swap_state.go b/protocol/alice/swap_state.go index e317c6e3..f5ac363a 100644 --- a/protocol/alice/swap_state.go +++ b/protocol/alice/swap_state.go @@ -5,6 +5,7 @@ import ( "encoding/hex" "errors" "fmt" + "math/big" "sync" "time" @@ -16,7 +17,7 @@ import ( "github.com/noot/atomic-swap/net" pcommon "github.com/noot/atomic-swap/protocol" pswap "github.com/noot/atomic-swap/protocol/swap" - "github.com/noot/atomic-swap/swap-contract" + "github.com/noot/atomic-swap/swapfactory" "github.com/ethereum/go-ethereum/accounts/abi/bind" ethcommon "github.com/ethereum/go-ethereum/common" @@ -51,9 +52,9 @@ type swapState struct { bobAddress ethcommon.Address // swap contract and timeouts in it; set once contract is deployed - contract *swap.Swap - t0, t1 time.Time - txOpts *bind.TransactOpts + contractSwapID *big.Int + t0, t1 time.Time + txOpts *bind.TransactOpts // next expected network message nextExpectedMessage net.Message // TODO: change to type? @@ -196,7 +197,7 @@ func (s *swapState) tryRefund() (ethcommon.Hash, error) { } func (s *swapState) setTimeouts() error { - if s.contract == nil { + if s.alice.contract == nil { return errors.New("contract is nil") } @@ -206,28 +207,16 @@ func (s *swapState) setTimeouts() error { // TODO: add maxRetries for { - log.Debug("attempting to fetch t0 from contract") + log.Debug("attempting to fetch timestamps from contract") - st0, err := s.contract.Timeout0(s.alice.callOpts) + info, err := s.alice.contract.Swaps(s.alice.callOpts, s.contractSwapID) if err != nil { time.Sleep(time.Second * 10) continue } - s.t0 = time.Unix(st0.Int64(), 0) - break - } - - for { - log.Debug("attempting to fetch t1 from contract") - - st1, err := s.contract.Timeout1(s.alice.callOpts) - if err != nil { - time.Sleep(time.Second * 10) - continue - } - - s.t1 = time.Unix(st1.Int64(), 0) + s.t0 = time.Unix(info.Timeout0.Int64(), 0) + s.t1 = time.Unix(info.Timeout1.Int64(), 0) break } @@ -279,14 +268,14 @@ func (s *swapState) setBobKeys(sk *mcrypto.PublicKey, vk *mcrypto.PrivateViewKey s.bobSecp256k1PublicKey = secp256k1Pub } -// deployAndLockETH deploys an instance of the Swap contract and locks `amount` ether in it. -func (s *swapState) deployAndLockETH(amount common.EtherAmount) (ethcommon.Address, error) { +// lockETH the Swap contract function new_swap and locks `amount` ether in it. +func (s *swapState) lockETH(amount common.EtherAmount) error { if s.pubkeys == nil { - return ethcommon.Address{}, errors.New("public keys aren't set") + return errors.New("public keys aren't set") } if s.bobPublicSpendKey == nil || s.bobPrivateViewKey == nil { - return ethcommon.Address{}, errors.New("bob's keys aren't set") + return errors.New("bob's keys aren't set") } cmtAlice := s.secp256k1Pub.Keccak256() @@ -297,38 +286,35 @@ func (s *swapState) deployAndLockETH(amount common.EtherAmount) (ethcommon.Addre s.txOpts.Value = nil }() - address, tx, swap, err := swap.DeploySwap(s.txOpts, s.alice.ethClient, + tx, err := s.alice.contract.NewSwap(s.txOpts, cmtBob, cmtAlice, s.bobAddress, defaultTimeoutDuration) if err != nil { - return ethcommon.Address{}, fmt.Errorf("failed to deploy Swap.sol: %w", err) + return fmt.Errorf("failed to deploy Swap.sol: %w", err) } log.Debugf("deploying Swap.sol, amount=%s txHash=%s", amount, tx.Hash()) - if _, ok := common.WaitForReceipt(s.ctx, s.alice.ethClient, tx.Hash()); !ok { - return ethcommon.Address{}, errors.New("failed to deploy Swap.sol") + receipt, ok := common.WaitForReceipt(s.ctx, s.alice.ethClient, tx.Hash()) + if !ok { + return errors.New("failed to call new_swap in contract") } - fp := fmt.Sprintf("%s/%d/contractaddress", s.alice.basepath, s.info.ID()) - if err = common.WriteContractAddressToFile(fp, address.String()); err != nil { - return ethcommon.Address{}, fmt.Errorf("failed to write contract address to file: %w", err) + if len(receipt.Logs) == 0 { + return errors.New("expected 1 log, got 0") } - balance, err := s.alice.ethClient.BalanceAt(s.ctx, address, nil) + s.contractSwapID, err = swapfactory.GetIDFromLog(receipt.Logs[0]) if err != nil { - return ethcommon.Address{}, err + return err } - log.Debug("contract balance: ", balance) - - s.contract = swap - return address, nil + return nil } // ready calls the Ready() method on the Swap contract, indicating to Bob he has until time t_1 to // call Claim(). Ready() should only be called once Alice sees Bob lock his XMR. // If time t_0 has passed, there is no point of calling Ready(). func (s *swapState) ready() error { - tx, err := s.contract.SetReady(s.txOpts) + tx, err := s.alice.contract.SetReady(s.txOpts, s.contractSwapID) if err != nil { return err } @@ -344,14 +330,14 @@ func (s *swapState) ready() error { // and returns to her the ether in the contract. // If time t_1 passes and Claim() has not been called, Alice should call Refund(). func (s *swapState) refund() (ethcommon.Hash, error) { - if s.contract == nil { + if s.alice.contract == nil { return ethcommon.Hash{}, errors.New("contract is nil") } sc := s.getSecret() log.Infof("attempting to call Refund()...") - tx, err := s.contract.Refund(s.txOpts, sc) + tx, err := s.alice.contract.Refund(s.txOpts, s.contractSwapID, sc) if err != nil { return ethcommon.Hash{}, err } diff --git a/protocol/alice/swap_state_test.go b/protocol/alice/swap_state_test.go index 1e2ee56a..be6c5a72 100644 --- a/protocol/alice/swap_state_test.go +++ b/protocol/alice/swap_state_test.go @@ -7,13 +7,17 @@ import ( "testing" "time" - ethcommon "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + ethcrypto "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/ethclient" + "github.com/noot/atomic-swap/common" mcrypto "github.com/noot/atomic-swap/crypto/monero" "github.com/noot/atomic-swap/monero" "github.com/noot/atomic-swap/net" pcommon "github.com/noot/atomic-swap/protocol" pswap "github.com/noot/atomic-swap/protocol/swap" + "github.com/noot/atomic-swap/swapfactory" logging "github.com/ipfs/go-log" "github.com/stretchr/testify/require" @@ -31,15 +35,28 @@ func (n *mockNet) SendSwapMessage(msg net.Message) error { } func newTestInstance(t *testing.T) (*Instance, *swapState) { + pk, err := ethcrypto.HexToECDSA(common.DefaultPrivKeyAlice) + require.NoError(t, err) + + ec, err := ethclient.Dial(common.DefaultEthEndpoint) + require.NoError(t, err) + + txOpts, err := bind.NewKeyedTransactorWithChainID(pk, big.NewInt(common.MainnetConfig.EthereumChainID)) + require.NoError(t, err) + addr, _, contract, err := swapfactory.DeploySwapFactory(txOpts, ec) + require.NoError(t, err) + cfg := &Config{ Ctx: context.Background(), Basepath: "/tmp/alice", MoneroWalletEndpoint: common.DefaultAliceMoneroEndpoint, - EthereumEndpoint: common.DefaultEthEndpoint, - EthereumPrivateKey: common.DefaultPrivKeyAlice, + EthereumClient: ec, + EthereumPrivateKey: pk, Environment: common.Development, - ChainID: common.MainnetConfig.EthereumChainID, + ChainID: big.NewInt(common.MainnetConfig.EthereumChainID), SwapManager: pswap.NewManager(), + SwapContract: contract, + SwapContractAddress: addr, } alice, err := NewInstance(cfg) @@ -115,18 +132,15 @@ func TestSwapState_HandleProtocolMessage_SendKeysMessage_Refund(t *testing.T) { require.Equal(t, bobKeysAndProof.PublicKeyPair.SpendKey().Hex(), s.bobPublicSpendKey.Hex()) require.Equal(t, bobKeysAndProof.PrivateKeyPair.ViewKey().Hex(), s.bobPrivateViewKey.Hex()) - cdMsg, ok := resp.(*net.NotifyContractDeployed) - require.True(t, ok) - // ensure we refund before t0 time.Sleep(time.Second * 15) require.NotNil(t, s.alice.net.(*mockNet).msg) require.Equal(t, net.NotifyRefundType, s.alice.net.(*mockNet).msg.Type()) - // check balance of contract is 0 - balance, err := s.alice.ethClient.BalanceAt(s.ctx, ethcommon.HexToAddress(cdMsg.Address), nil) + // check swap is marked completed + info, err := s.alice.contract.Swaps(s.alice.callOpts, s.contractSwapID) require.NoError(t, err) - require.Equal(t, int64(0), balance.Int64()) + require.True(t, info.Completed) } func TestSwapState_NotifyXMRLock(t *testing.T) { @@ -143,7 +157,7 @@ func TestSwapState_NotifyXMRLock(t *testing.T) { s.setBobKeys(bobKeysAndProof.PublicKeyPair.SpendKey(), bobKeysAndProof.PrivateKeyPair.ViewKey(), bobKeysAndProof.Secp256k1PublicKey) - _, err = s.deployAndLockETH(common.NewEtherAmount(1)) + err = s.lockETH(common.NewEtherAmount(1)) require.NoError(t, err) s.info.SetReceivedAmount(0) @@ -185,7 +199,7 @@ func TestSwapState_NotifyXMRLock_Refund(t *testing.T) { s.setBobKeys(bobKeysAndProof.PublicKeyPair.SpendKey(), bobKeysAndProof.PrivateKeyPair.ViewKey(), bobKeysAndProof.Secp256k1PublicKey) - contractAddr, err := s.deployAndLockETH(common.NewEtherAmount(1)) + err = s.lockETH(common.NewEtherAmount(1)) require.NoError(t, err) s.info.SetReceivedAmount(0) @@ -210,7 +224,7 @@ func TestSwapState_NotifyXMRLock_Refund(t *testing.T) { require.Equal(t, net.NotifyRefundType, s.alice.net.(*mockNet).msg.Type()) // check balance of contract is 0 - balance, err := s.alice.ethClient.BalanceAt(s.ctx, contractAddr, nil) + balance, err := s.alice.ethClient.BalanceAt(s.ctx, s.alice.contractAddr, nil) require.NoError(t, err) require.Equal(t, uint64(0), balance.Uint64()) } @@ -284,7 +298,7 @@ func TestSwapState_NotifyClaimed(t *testing.T) { var sc [32]byte copy(sc[:], common.Reverse(secret)) - tx, err := s.contract.Claim(s.txOpts, sc) + tx, err := s.alice.contract.Claim(s.txOpts, s.contractSwapID, sc) require.NoError(t, err) cmsg := &net.NotifyClaimed{ diff --git a/protocol/bob/instance.go b/protocol/bob/instance.go index 06a7b2f3..b4526161 100644 --- a/protocol/bob/instance.go +++ b/protocol/bob/instance.go @@ -59,10 +59,10 @@ type Config struct { MoneroWalletEndpoint string MoneroDaemonEndpoint string // only needed for development WalletFile, WalletPassword string - EthereumEndpoint string - EthereumPrivateKey string + EthereumClient *ethclient.Client + EthereumPrivateKey *ecdsa.PrivateKey Environment common.Environment - ChainID int64 + ChainID *big.Int GasPrice *big.Int SwapManager *swap.Manager GasLimit uint64 @@ -75,17 +75,7 @@ func NewInstance(cfg *Config) (*Instance, error) { return nil, errors.New("environment is development, must provide monero daemon endpoint") } - pk, err := crypto.HexToECDSA(cfg.EthereumPrivateKey) - if err != nil { - return nil, err - } - - ec, err := ethclient.Dial(cfg.EthereumEndpoint) - if err != nil { - return nil, err - } - - pub := pk.Public().(*ecdsa.PublicKey) + pub := cfg.EthereumPrivateKey.Public().(*ecdsa.PublicKey) addr := crypto.PubkeyToAddress(*pub) // monero-wallet-rpc client @@ -93,7 +83,7 @@ func NewInstance(cfg *Config) (*Instance, error) { // open Bob's XMR wallet if cfg.WalletFile != "" { - if err = walletClient.OpenWallet(cfg.WalletFile, cfg.WalletPassword); err != nil { + if err := walletClient.OpenWallet(cfg.WalletFile, cfg.WalletPassword); err != nil { return nil, err } } else { @@ -114,14 +104,14 @@ func NewInstance(cfg *Config) (*Instance, error) { daemonClient: daemonClient, walletFile: cfg.WalletFile, walletPassword: cfg.WalletPassword, - ethClient: ec, - ethPrivKey: pk, + ethClient: cfg.EthereumClient, + ethPrivKey: cfg.EthereumPrivateKey, callOpts: &bind.CallOpts{ From: addr, Context: cfg.Ctx, }, ethAddress: addr, - chainID: big.NewInt(cfg.ChainID), + chainID: cfg.ChainID, offerManager: newOfferManager(), swapManager: cfg.SwapManager, }, nil diff --git a/protocol/bob/message_handler.go b/protocol/bob/message_handler.go index 21bb588e..9e9b5f76 100644 --- a/protocol/bob/message_handler.go +++ b/protocol/bob/message_handler.go @@ -12,7 +12,7 @@ import ( "github.com/noot/atomic-swap/net" pcommon "github.com/noot/atomic-swap/protocol" pswap "github.com/noot/atomic-swap/protocol/swap" - "github.com/noot/atomic-swap/swap-contract" + "github.com/noot/atomic-swap/swapfactory" ) // HandleProtocolMessage is called by the network to handle an incoming message. @@ -94,7 +94,12 @@ func (s *swapState) handleNotifyContractDeployed(msg *net.NotifyContractDeployed return nil, errMissingAddress } - log.Infof("got Swap contract address! address=%s", msg.Address) + if msg.ContractSwapID == nil { + return nil, errors.New("expected swapID in NotifyContractDeployed message") + } + + log.Infof("got NotifyContractDeployed; address=%s contract swap ID=%d", msg.Address, msg.ContractSwapID) + s.contractSwapID = msg.ContractSwapID if err := s.setContract(ethcommon.HexToAddress(msg.Address)); err != nil { return nil, fmt.Errorf("failed to instantiate contract instance: %w", err) @@ -191,7 +196,7 @@ func (s *swapState) handleRefund(txHash string) (mcrypto.Address, error) { return "", errors.New("claim transaction has no logs") } - sa, err := swap.GetSecretFromLog(receipt.Logs[0], "Refunded") + sa, err := swapfactory.GetSecretFromLog(receipt.Logs[0], "Refunded") if err != nil { return "", err } diff --git a/protocol/bob/recovery.go b/protocol/bob/recovery.go index f53693a9..d1110a19 100644 --- a/protocol/bob/recovery.go +++ b/protocol/bob/recovery.go @@ -3,6 +3,7 @@ package bob import ( "context" "errors" + "math/big" "github.com/ethereum/go-ethereum/accounts/abi/bind" ethcommon "github.com/ethereum/go-ethereum/common" @@ -18,7 +19,7 @@ type recoveryState struct { // NewRecoveryState returns a new *bob.recoveryState, // which has methods to either claim ether or reclaim monero from an initiated swap. func NewRecoveryState(b *Instance, secret *mcrypto.PrivateSpendKey, - contractAddr ethcommon.Address) (*recoveryState, error) { //nolint:revive + contractAddr ethcommon.Address, contractSwapID *big.Int) (*recoveryState, error) { //nolint:revive txOpts, err := bind.NewKeyedTransactorWithChainID(b.ethPrivKey, b.chainID) if err != nil { return nil, err @@ -39,13 +40,14 @@ func NewRecoveryState(b *Instance, secret *mcrypto.PrivateSpendKey, ctx, cancel := context.WithCancel(b.ctx) s := &swapState{ - ctx: ctx, - cancel: cancel, - bob: b, - txOpts: txOpts, - privkeys: kp, - pubkeys: pubkp, - dleqProof: dleq.NewProofWithSecret(sc), + ctx: ctx, + cancel: cancel, + bob: b, + txOpts: txOpts, + privkeys: kp, + pubkeys: pubkp, + dleqProof: dleq.NewProofWithSecret(sc), + contractSwapID: contractSwapID, } if err := s.setContract(contractAddr); err != nil { diff --git a/protocol/bob/recovery_test.go b/protocol/bob/recovery_test.go index e30a0fb3..c345f900 100644 --- a/protocol/bob/recovery_test.go +++ b/protocol/bob/recovery_test.go @@ -21,8 +21,9 @@ func newTestRecoveryState(t *testing.T) *recoveryState { duration, err := time.ParseDuration("1440m") require.NoError(t, err) - addr, _ := deploySwap(t, inst, s, sr, big.NewInt(1), duration) - rs, err := NewRecoveryState(inst, s.privkeys.SpendKey(), addr) + addr, _ := newSwap(t, inst, s, [32]byte{}, sr, big.NewInt(1), duration) + + rs, err := NewRecoveryState(inst, s.privkeys.SpendKey(), addr, defaultContractSwapID) require.NoError(t, err) return rs @@ -33,7 +34,7 @@ func TestClaimOrRecover_Claim(t *testing.T) { rs := newTestRecoveryState(t) // set contract to Ready - _, err := rs.ss.contract.SetReady(rs.ss.txOpts) + _, err := rs.ss.contract.SetReady(rs.ss.txOpts, rs.ss.contractSwapID) require.NoError(t, err) // assert we can claim ether @@ -58,7 +59,7 @@ func TestClaimOrRecover_Recover(t *testing.T) { // call refund w/ Alice's spend key sc := rs.ss.getSecret() - _, err = rs.ss.contract.Refund(rs.ss.txOpts, sc) + _, err = rs.ss.contract.Refund(rs.ss.txOpts, rs.ss.contractSwapID, sc) require.NoError(t, err) // assert Bob can reclaim his monero diff --git a/protocol/bob/swap_state.go b/protocol/bob/swap_state.go index 17df4c20..13c47577 100644 --- a/protocol/bob/swap_state.go +++ b/protocol/bob/swap_state.go @@ -7,12 +7,11 @@ import ( "encoding/hex" "errors" "fmt" - "strings" + "math/big" "sync" "time" eth "github.com/ethereum/go-ethereum" - "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/accounts/abi/bind" ethcommon "github.com/ethereum/go-ethereum/common" ethcrypto "github.com/ethereum/go-ethereum/crypto" @@ -27,7 +26,7 @@ import ( "github.com/noot/atomic-swap/net" pcommon "github.com/noot/atomic-swap/protocol" pswap "github.com/noot/atomic-swap/protocol/swap" - "github.com/noot/atomic-swap/swap-contract" + "github.com/noot/atomic-swap/swapfactory" ) var ( @@ -40,7 +39,7 @@ var ( var ( // this is from the autogenerated swap.go // TODO: generate this ourselves instead of hard-coding - refundedTopic = ethcommon.HexToHash("0xfe509803c09416b28ff3d8f690c8b0c61462a892c46d5430c8fb20abe472daf0") + refundedTopic = ethcommon.HexToHash("0x4fd30f3ee0d64f7eaa62d0e005ca64c6a560652156d6c33f23ea8ca4936106e0") ) type swapState struct { @@ -59,10 +58,11 @@ type swapState struct { pubkeys *mcrypto.PublicKeyPair // swap contract and timeouts in it; set once contract is deployed - contract *swap.Swap - contractAddr ethcommon.Address - t0, t1 time.Time - txOpts *bind.TransactOpts + contract *swapfactory.SwapFactory + contractSwapID *big.Int + contractAddr ethcommon.Address + t0, t1 time.Time + txOpts *bind.TransactOpts // Alice's keys for this session alicePublicKeys *mcrypto.PublicKeyPair @@ -245,7 +245,7 @@ func (s *swapState) filterForRefund() (*mcrypto.PrivateSpendKey, error) { return nil, errNoRefundLogsFound } - sa, err := swap.GetSecretFromLog(&logs[0], "Refunded") + sa, err := swapfactory.GetSecretFromLog(&logs[0], "Refunded") if err != nil { return nil, fmt.Errorf("failed to get secret from log: %w", err) } @@ -256,12 +256,12 @@ func (s *swapState) filterForRefund() (*mcrypto.PrivateSpendKey, error) { func (s *swapState) tryClaim() (ethcommon.Hash, error) { untilT0 := time.Until(s.t0) untilT1 := time.Until(s.t1) - isReady, err := s.contract.IsReady(s.bob.callOpts) + info, err := s.contract.Swaps(s.bob.callOpts, s.contractSwapID) if err != nil { return ethcommon.Hash{}, err } - if untilT0 > 0 && !isReady { + if untilT0 > 0 && !info.IsReady { // we need to wait until t0 to claim log.Infof("waiting until time %s to claim, time now=%s", s.t0, time.Now()) <-time.After(untilT0 + time.Second) @@ -324,24 +324,18 @@ func (s *swapState) setAlicePublicKeys(sk *mcrypto.PublicKeyPair, secp256k1Pub * func (s *swapState) setContract(address ethcommon.Address) error { var err error s.contractAddr = address - s.contract, err = swap.NewSwap(address, s.bob.ethClient) + s.contract, err = swapfactory.NewSwapFactory(address, s.bob.ethClient) return err } func (s *swapState) setTimeouts() error { - st0, err := s.contract.Timeout0(s.bob.callOpts) + info, err := s.contract.Swaps(s.bob.callOpts, s.contractSwapID) if err != nil { - return fmt.Errorf("failed to get timeout0 from contract: err=%w", err) + return fmt.Errorf("failed to get swap info from contract: err=%w", err) } - s.t0 = time.Unix(st0.Int64(), 0) - - st1, err := s.contract.Timeout1(s.bob.callOpts) - if err != nil { - return fmt.Errorf("failed to get timeout1 from contract: err=%w", err) - } - - s.t1 = time.Unix(st1.Int64(), 0) + s.t0 = time.Unix(info.Timeout0.Int64(), 0) + s.t1 = time.Unix(info.Timeout1.Int64(), 0) return nil } @@ -349,56 +343,57 @@ func (s *swapState) setTimeouts() error { // if the balance doesn't match what we're expecting to receive, or the public keys in the contract // aren't what we expect, we error and abort the swap. func (s *swapState) checkContract() error { - balance, err := s.bob.ethClient.BalanceAt(s.ctx, s.contractAddr, nil) - if err != nil { - return err - } - - expected := common.EtherToWei(s.info.ReceivedAmount()).BigInt() - if balance.Cmp(expected) < 0 { - return fmt.Errorf("contract does not have expected balance: got %s, expected %s", balance, expected) - } - - constructedTopic := ethcommon.HexToHash("0x8d36aa70807342c3036697a846281194626fd4afa892356ad5979e03831ab080") + newTopic := ethcommon.HexToHash("0x982a99d883f17ecd5797205d5b3674205d7882bb28a9487d736d3799422cd055") logs, err := s.bob.ethClient.FilterLogs(s.ctx, eth.FilterQuery{ Addresses: []ethcommon.Address{s.contractAddr}, - Topics: [][]ethcommon.Hash{{constructedTopic}}, + Topics: [][]ethcommon.Hash{{newTopic}}, }) if err != nil { return fmt.Errorf("failed to filter logs: %w", err) } if len(logs) == 0 { - return errors.New("cannot find Constructed log") + return errors.New("cannot find New log") } - abi, err := abi.JSON(strings.NewReader(swap.SwapABI)) - if err != nil { - return err + // search for log pertaining to our swap ID + var event *swapfactory.SwapFactoryNew + for i := len(logs) - 1; i >= 0; i-- { + newEvent, err := s.contract.ParseNew(logs[i]) //nolint:govet + if err != nil { + return err + } + + if newEvent.SwapID.Cmp(s.contractSwapID) == 0 { + event = newEvent + break + } } - data := logs[0].Data - res, err := abi.Unpack("Constructed", data) - if err != nil { - return err + if event == nil { + return fmt.Errorf("failed to find New event with given swap ID %d", s.contractSwapID) } - if len(res) < 2 { - return errors.New("constructed event was missing parameters") - } - - pkClaim := res[0].([32]byte) - pkRefund := res[0].([32]byte) - // check that contract was constructed with correct secp256k1 keys skOurs := s.secp256k1Pub.Keccak256() - if !bytes.Equal(pkClaim[:], skOurs[:]) { - return fmt.Errorf("contract claim key is not expected: got 0x%x, expected 0x%x", pkClaim, skOurs) + if !bytes.Equal(event.ClaimKey[:], skOurs[:]) { + return fmt.Errorf("contract claim key is not expected: got 0x%x, expected 0x%x", event.ClaimKey, skOurs) } skTheirs := s.aliceSecp256K1PublicKey.Keccak256() - if !bytes.Equal(pkRefund[:], skOurs[:]) { - return fmt.Errorf("contract claim key is not expected: got 0x%x, expected 0x%x", pkRefund, skTheirs) + if !bytes.Equal(event.RefundKey[:], skTheirs[:]) { + return fmt.Errorf("contract refund key is not expected: got 0x%x, expected 0x%x", event.RefundKey, skTheirs) + } + + // check value of created swap + info, err := s.contract.Swaps(s.bob.callOpts, s.contractSwapID) + if err != nil { + return err + } + + expected := common.EtherToWei(s.info.ReceivedAmount()).BigInt() + if info.Value.Cmp(expected) < 0 { + return fmt.Errorf("contract does not have expected balance: got %s, expected %s", info.Value, expected) } return nil @@ -465,7 +460,7 @@ func (s *swapState) claimFunds() (ethcommon.Hash, error) { // call swap.Swap.Claim() w/ b.privkeys.sk, revealing Bob's secret spend key sc := s.getSecret() - tx, err := s.contract.Claim(s.txOpts, sc) + tx, err := s.contract.Claim(s.txOpts, s.contractSwapID, sc) if err != nil { return ethcommon.Hash{}, err } diff --git a/protocol/bob/swap_state_test.go b/protocol/bob/swap_state_test.go index 66a94f3c..cd85f90f 100644 --- a/protocol/bob/swap_state_test.go +++ b/protocol/bob/swap_state_test.go @@ -12,9 +12,10 @@ import ( "github.com/noot/atomic-swap/net" pcommon "github.com/noot/atomic-swap/protocol" pswap "github.com/noot/atomic-swap/protocol/swap" - "github.com/noot/atomic-swap/swap-contract" + "github.com/noot/atomic-swap/swapfactory" ethcommon "github.com/ethereum/go-ethereum/common" + ethcrypto "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/ethclient" logging "github.com/ipfs/go-log" "github.com/stretchr/testify/require" @@ -35,9 +36,18 @@ func (n *mockNet) SendSwapMessage(msg net.Message) error { return nil } -var defaultTimeoutDuration = big.NewInt(60 * 60 * 24) // 1 day = 60s * 60min * 24hr +var ( + defaultTimeoutDuration, _ = time.ParseDuration("86400s") // 1 day = 60s * 60min * 24hr + defaultContractSwapID = big.NewInt(0) +) func newTestInstance(t *testing.T) (*Instance, *swapState) { + pk, err := ethcrypto.HexToECDSA(common.DefaultPrivKeyBob) + require.NoError(t, err) + + ec, err := ethclient.Dial(common.DefaultEthEndpoint) + require.NoError(t, err) + cfg := &Config{ Ctx: context.Background(), Basepath: "/tmp/bob", @@ -45,10 +55,10 @@ func newTestInstance(t *testing.T) (*Instance, *swapState) { MoneroDaemonEndpoint: common.DefaultMoneroDaemonEndpoint, WalletFile: testWallet, WalletPassword: "", - EthereumEndpoint: common.DefaultEthEndpoint, - EthereumPrivateKey: common.DefaultPrivKeyBob, + EthereumClient: ec, + EthereumPrivateKey: pk, Environment: common.Development, - ChainID: common.MainnetConfig.EthereumChainID, + ChainID: big.NewInt(common.MainnetConfig.EthereumChainID), SwapManager: pswap.NewManager(), } @@ -79,6 +89,33 @@ func newTestAliceSendKeySMessage(t *testing.T) (*net.SendKeysMessage, *pcommon.K return msg, keysAndProof } +func newSwap(t *testing.T, bob *Instance, swapState *swapState, claimKey, refundKey [32]byte, amount *big.Int, + timeout time.Duration) (ethcommon.Address, *swapfactory.SwapFactory) { + tm := big.NewInt(int64(timeout.Seconds())) + if claimKey == [32]byte{} { + claimKey = swapState.secp256k1Pub.Keccak256() + } + + addr, _, contract, err := swapfactory.DeploySwapFactory(swapState.txOpts, bob.ethClient) + require.NoError(t, err) + + swapState.txOpts.Value = amount + defer func() { + swapState.txOpts.Value = nil + }() + + tx, err := contract.NewSwap(swapState.txOpts, claimKey, refundKey, bob.ethAddress, tm) + require.NoError(t, err) + + receipt, err := bob.ethClient.TransactionReceipt(context.Background(), tx.Hash()) + require.NoError(t, err) + require.Equal(t, 1, len(receipt.Logs)) + swapState.contractSwapID, err = swapfactory.GetIDFromLog(receipt.Logs[0]) + require.NoError(t, err) + + return addr, contract +} + func TestSwapState_GenerateAndSetKeys(t *testing.T) { _, swapState := newTestInstance(t) @@ -94,15 +131,11 @@ func TestSwapState_ClaimFunds(t *testing.T) { err := swapState.generateAndSetKeys() require.NoError(t, err) - conn, err := ethclient.Dial(common.DefaultEthEndpoint) - require.NoError(t, err) - claimKey := swapState.secp256k1Pub.Keccak256() - swapState.contractAddr, _, swapState.contract, err = swap.DeploySwap(swapState.txOpts, conn, - claimKey, [32]byte{}, bob.ethAddress, defaultTimeoutDuration) - require.NoError(t, err) + swapState.contractAddr, swapState.contract = newSwap(t, bob, swapState, claimKey, + [32]byte{}, big.NewInt(33), defaultTimeoutDuration) - _, err = swapState.contract.SetReady(swapState.txOpts) + _, err = swapState.contract.SetReady(swapState.txOpts, defaultContractSwapID) require.NoError(t, err) txHash, err := swapState.claimFunds() @@ -127,25 +160,6 @@ func TestSwapState_handleSendKeysMessage(t *testing.T) { require.Equal(t, alicePubKeys.ViewKey().Hex(), s.alicePublicKeys.ViewKey().Hex()) } -func deploySwap(t *testing.T, bob *Instance, swapState *swapState, refundKey [32]byte, amount *big.Int, - timeout time.Duration) (ethcommon.Address, *swap.Swap) { - conn, err := ethclient.Dial(common.DefaultEthEndpoint) - require.NoError(t, err) - - tm := big.NewInt(int64(timeout.Seconds())) - - claimKey := swapState.secp256k1Pub.Keccak256() - - swapState.txOpts.Value = amount - defer func() { - swapState.txOpts.Value = nil - }() - - addr, _, contract, err := swap.DeploySwap(swapState.txOpts, conn, claimKey, refundKey, bob.ethAddress, tm) - require.NoError(t, err) - return addr, contract -} - func TestSwapState_HandleProtocolMessage_NotifyContractDeployed_ok(t *testing.T) { bob, s := newTestInstance(t) defer s.cancel() @@ -165,10 +179,12 @@ func TestSwapState_HandleProtocolMessage_NotifyContractDeployed_ok(t *testing.T) duration, err := time.ParseDuration("2s") require.NoError(t, err) - addr, _ := deploySwap(t, bob, s, [32]byte{}, desiredAmout.BigInt(), duration) + addr, _ := newSwap(t, bob, s, s.secp256k1Pub.Keccak256(), s.aliceSecp256K1PublicKey.Keccak256(), + desiredAmout.BigInt(), duration) msg = &net.NotifyContractDeployed{ - Address: addr.String(), + Address: addr.String(), + ContractSwapID: defaultContractSwapID, } resp, done, err = s.HandleProtocolMessage(msg) @@ -202,10 +218,12 @@ func TestSwapState_HandleProtocolMessage_NotifyContractDeployed_timeout(t *testi duration, err := time.ParseDuration("15s") require.NoError(t, err) - addr, _ := deploySwap(t, bob, s, [32]byte{}, desiredAmout.BigInt(), duration) + addr, _ := newSwap(t, bob, s, s.secp256k1Pub.Keccak256(), s.aliceSecp256K1PublicKey.Keccak256(), + desiredAmout.BigInt(), duration) msg = &net.NotifyContractDeployed{ - Address: addr.String(), + Address: addr.String(), + ContractSwapID: defaultContractSwapID, } resp, done, err = s.HandleProtocolMessage(msg) @@ -232,9 +250,9 @@ func TestSwapState_HandleProtocolMessage_NotifyReady(t *testing.T) { duration, err := time.ParseDuration("10m") require.NoError(t, err) - _, s.contract = deploySwap(t, bob, s, [32]byte{}, desiredAmout.BigInt(), duration) + _, s.contract = newSwap(t, bob, s, [32]byte{}, [32]byte{}, desiredAmout.BigInt(), duration) - _, err = s.contract.SetReady(s.txOpts) + _, err = s.contract.SetReady(s.txOpts, defaultContractSwapID) require.NoError(t, err) msg := &net.NotifyReady{} @@ -260,7 +278,7 @@ func TestSwapState_handleRefund(t *testing.T) { require.NoError(t, err) refundKey := aliceKeysAndProof.Secp256k1PublicKey.Keccak256() - _, s.contract = deploySwap(t, bob, s, refundKey, desiredAmout.BigInt(), duration) + _, s.contract = newSwap(t, bob, s, [32]byte{}, refundKey, desiredAmout.BigInt(), duration) // lock XMR addrAB, err := s.lockFunds(common.MoneroToPiconero(s.info.ProvidedAmount())) @@ -271,7 +289,7 @@ func TestSwapState_handleRefund(t *testing.T) { var sc [32]byte copy(sc[:], common.Reverse(secret)) - tx, err := s.contract.Refund(s.txOpts, sc) + tx, err := s.contract.Refund(s.txOpts, defaultContractSwapID, sc) require.NoError(t, err) addr, err := s.handleRefund(tx.Hash().String()) @@ -293,7 +311,7 @@ func TestSwapState_HandleProtocolMessage_NotifyRefund(t *testing.T) { require.NoError(t, err) refundKey := aliceKeysAndProof.Secp256k1PublicKey.Keccak256() - _, s.contract = deploySwap(t, bob, s, refundKey, desiredAmout.BigInt(), duration) + _, s.contract = newSwap(t, bob, s, [32]byte{}, refundKey, desiredAmout.BigInt(), duration) // lock XMR _, err = s.lockFunds(common.MoneroToPiconero(s.info.ProvidedAmount())) @@ -304,7 +322,7 @@ func TestSwapState_HandleProtocolMessage_NotifyRefund(t *testing.T) { var sc [32]byte copy(sc[:], common.Reverse(secret[:])) - tx, err := s.contract.Refund(s.txOpts, sc) + tx, err := s.contract.Refund(s.txOpts, defaultContractSwapID, sc) require.NoError(t, err) msg := &net.NotifyRefund{ @@ -332,7 +350,7 @@ func TestSwapState_ProtocolExited_Reclaim(t *testing.T) { require.NoError(t, err) refundKey := aliceKeysAndProof.Secp256k1PublicKey.Keccak256() - s.contractAddr, s.contract = deploySwap(t, bob, s, refundKey, desiredAmout.BigInt(), duration) + s.contractAddr, s.contract = newSwap(t, bob, s, [32]byte{}, refundKey, desiredAmout.BigInt(), duration) // lock XMR _, err = s.lockFunds(common.MoneroToPiconero(s.info.ProvidedAmount())) @@ -343,7 +361,7 @@ func TestSwapState_ProtocolExited_Reclaim(t *testing.T) { var sc [32]byte copy(sc[:], common.Reverse(secret[:])) - tx, err := s.contract.Refund(s.txOpts, sc) + tx, err := s.contract.Refund(s.txOpts, defaultContractSwapID, sc) require.NoError(t, err) receipt, err := bob.ethClient.TransactionReceipt(s.ctx, tx.Hash()) diff --git a/recover/recovery.go b/recover/recovery.go index 6a611a55..058cc59c 100644 --- a/recover/recovery.go +++ b/recover/recovery.go @@ -3,6 +3,7 @@ package recovery import ( "encoding/hex" "fmt" + "math/big" "github.com/noot/atomic-swap/common" mcrypto "github.com/noot/atomic-swap/crypto/monero" @@ -67,7 +68,7 @@ func (r *recoverer) WalletFromSecrets(aliceSecret, bobSecret string) (mcrypto.Ad // RecoverFromBobSecretAndContract recovers funds by either claiming ether or reclaiming locked monero. func (r *recoverer) RecoverFromBobSecretAndContract(b *bob.Instance, - bobSecret, contractAddr string) (*bob.RecoveryResult, error) { + bobSecret, contractAddr string, swapID *big.Int) (*bob.RecoveryResult, error) { bs, err := hex.DecodeString(bobSecret) if err != nil { return nil, fmt.Errorf("failed to decode Bob's secret: %w", err) @@ -79,7 +80,7 @@ func (r *recoverer) RecoverFromBobSecretAndContract(b *bob.Instance, } addr := ethcommon.HexToAddress(contractAddr) - rs, err := bob.NewRecoveryState(b, bk, addr) + rs, err := bob.NewRecoveryState(b, bk, addr, swapID) if err != nil { return nil, err } @@ -89,7 +90,7 @@ func (r *recoverer) RecoverFromBobSecretAndContract(b *bob.Instance, // RecoverFromAliceSecretAndContract recovers funds by either claiming locked monero or refunding ether. func (r *recoverer) RecoverFromAliceSecretAndContract(a *alice.Instance, - aliceSecret, contractAddr string) (*alice.RecoveryResult, error) { + aliceSecret, contractAddr string, swapID *big.Int) (*alice.RecoveryResult, error) { as, err := hex.DecodeString(aliceSecret) if err != nil { return nil, fmt.Errorf("failed to decode Alice's secret: %w", err) @@ -101,7 +102,7 @@ func (r *recoverer) RecoverFromAliceSecretAndContract(a *alice.Instance, } addr := ethcommon.HexToAddress(contractAddr) - rs, err := alice.NewRecoveryState(a, ak, addr) + rs, err := alice.NewRecoveryState(a, ak, addr, swapID) if err != nil { return nil, err } diff --git a/scripts/generate-bindings.sh b/scripts/generate-bindings.sh index cf40801b..a1e9f613 100755 --- a/scripts/generate-bindings.sh +++ b/scripts/generate-bindings.sh @@ -1,6 +1,6 @@ #!/bin/bash -$SOLC_BIN --abi ethereum/contracts/Swap.sol -o ethereum/abi/ --overwrite -$SOLC_BIN --bin ethereum/contracts/Swap.sol -o ethereum/bin/ --overwrite -abigen --abi ethereum/abi/Swap.abi --pkg swap --type Swap --out swap.go --bin ethereum/bin/Swap.bin -mv swap.go ./swap-contract \ No newline at end of file +$SOLC_BIN --abi ethereum/contracts/SwapFactory.sol -o ethereum/abi/ --overwrite +$SOLC_BIN --bin ethereum/contracts/SwapFactory.sol -o ethereum/bin/ --overwrite +abigen --abi ethereum/abi/SwapFactory.abi --pkg swapfactory --type SwapFactory --out swap_factory.go --bin ethereum/bin/SwapFactory.bin +mv swap_factory.go ./swapfactory diff --git a/swap-contract/swap.go b/swap-contract similarity index 99% rename from swap-contract/swap.go rename to swap-contract index b4973305..56af6eb5 100644 --- a/swap-contract/swap.go +++ b/swap-contract @@ -30,7 +30,7 @@ var ( const SwapABI = "[{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"_pubKeyClaim\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"_pubKeyRefund\",\"type\":\"bytes32\"},{\"internalType\":\"addresspayable\",\"name\":\"_claimer\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"_timeoutDuration\",\"type\":\"uint256\"}],\"stateMutability\":\"payable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"bytes32\",\"name\":\"s\",\"type\":\"bytes32\"}],\"name\":\"Claimed\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"bytes32\",\"name\":\"claimKey\",\"type\":\"bytes32\"},{\"indexed\":false,\"internalType\":\"bytes32\",\"name\":\"refundKey\",\"type\":\"bytes32\"}],\"name\":\"Constructed\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"bool\",\"name\":\"b\",\"type\":\"bool\"}],\"name\":\"Ready\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"bytes32\",\"name\":\"s\",\"type\":\"bytes32\"}],\"name\":\"Refunded\",\"type\":\"event\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"_s\",\"type\":\"bytes32\"}],\"name\":\"claim\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"isReady\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"pubKeyClaim\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"pubKeyRefund\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"_s\",\"type\":\"bytes32\"}],\"name\":\"refund\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"set_ready\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"timeout_0\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"timeout_1\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"}]" // SwapBin is the compiled bytecode used for deploying new contracts. -var SwapBin = "0x61016060405260008060006101000a81548160ff0219169083151502179055506040516200142838038062001428833981810160405281019062000044919062000289565b3373ffffffffffffffffffffffffffffffffffffffff1660a08173ffffffffffffffffffffffffffffffffffffffff16815250508360e081815250508261010081815250508173ffffffffffffffffffffffffffffffffffffffff1660c08173ffffffffffffffffffffffffffffffffffffffff16815250508042620000cb91906200032a565b6101208181525050600281620000e2919062000387565b42620000ef91906200032a565b610140818152505060405162000105906200019b565b604051809103906000f08015801562000122573d6000803e3d6000fd5b5073ffffffffffffffffffffffffffffffffffffffff1660808173ffffffffffffffffffffffffffffffffffffffff16815250507f8d36aa70807342c3036697a846281194626fd4afa892356ad5979e03831ab080848460405162000189929190620003f9565b60405180910390a15050505062000426565b610393806200109583390190565b600080fd5b6000819050919050565b620001c381620001ae565b8114620001cf57600080fd5b50565b600081519050620001e381620001b8565b92915050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b60006200021682620001e9565b9050919050565b620002288162000209565b81146200023457600080fd5b50565b60008151905062000248816200021d565b92915050565b6000819050919050565b62000263816200024e565b81146200026f57600080fd5b50565b600081519050620002838162000258565b92915050565b60008060008060808587031215620002a657620002a5620001a9565b5b6000620002b687828801620001d2565b9450506020620002c987828801620001d2565b9350506040620002dc8782880162000237565b9250506060620002ef8782880162000272565b91505092959194509250565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b600062000337826200024e565b915062000344836200024e565b9250827fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff038211156200037c576200037b620002fb565b5b828201905092915050565b600062000394826200024e565b9150620003a1836200024e565b9250817fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0483118215151615620003dd57620003dc620002fb565b5b828202905092915050565b620003f381620001ae565b82525050565b6000604082019050620004106000830185620003e8565b6200041f6020830184620003e8565b9392505050565b60805160a05160c05160e051610100516101205161014051610bd5620004c06000396000818161018b0152818161022b01526105980152600081816101af01528181610255015261052001526000818161016701526102d301526000818161039a01526105fe015260008181610492015261065b0152600081816101d30152818161033001526103d4015260006106c50152610bd56000f3fe608060405234801561001057600080fd5b50600436106100885760003560e01c8063736290f81161005b578063736290f81461010357806374d7c13814610121578063a094a0311461012b578063bd66528a1461014957610088565b806303f7e2461461008d57806345bb8e09146100ab5780634ded8d52146100c95780637249fbb6146100e7575b600080fd5b610095610165565b6040516100a291906107c1565b60405180910390f35b6100b3610189565b6040516100c091906107f5565b60405180910390f35b6100d16101ad565b6040516100de91906107f5565b60405180910390f35b61010160048036038101906100fc9190610841565b6101d1565b005b61010b610398565b60405161011891906107c1565b60405180910390f35b6101296103bc565b005b61013361047f565b6040516101409190610889565b60405180910390f35b610163600480360381019061015e9190610841565b610490565b005b7f000000000000000000000000000000000000000000000000000000000000000081565b7f000000000000000000000000000000000000000000000000000000000000000081565b7f000000000000000000000000000000000000000000000000000000000000000081565b7f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161461022957600080fd5b7f00000000000000000000000000000000000000000000000000000000000000004210158061028e57507f00000000000000000000000000000000000000000000000000000000000000004210801561028d575060008054906101000a900460ff16155b5b6102cd576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016102c490610927565b60405180910390fd5b6102f7817f00000000000000000000000000000000000000000000000000000000000000006106c3565b7ffe509803c09416b28ff3d8f690c8b0c61462a892c46d5430c8fb20abe472daf08160405161032691906107c1565b60405180910390a17f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff166108fc479081150290604051600060405180830381858888f19350505050158015610394573d6000803e3d6000fd5b5050565b7f000000000000000000000000000000000000000000000000000000000000000081565b60008054906101000a900460ff1615801561042257507f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16145b61042b57600080fd5b60016000806101000a81548160ff0219169083151502179055507fb54ee60cc7bf27004d4c21b3226232af966dcdb31a046c95533970b3eea24ae960016040516104759190610889565b60405180910390a1565b60008054906101000a900460ff1681565b7f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161461051e576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161051590610993565b60405180910390fd5b7f000000000000000000000000000000000000000000000000000000000000000042101580610557575060008054906101000a900460ff165b610596576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161058d906109ff565b60405180910390fd5b7f000000000000000000000000000000000000000000000000000000000000000042106105f8576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016105ef90610a6b565b60405180910390fd5b610622817f00000000000000000000000000000000000000000000000000000000000000006106c3565b7feddf608ef698454af2fb41c1df7b7e5154ff0d46969f895e0f39c7dfe7e6380a8160405161065191906107c1565b60405180910390a17f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff166108fc479081150290604051600060405180830381858888f193505050501580156106bf573d6000803e3d6000fd5b5050565b7f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff1663b32d1b4f8360001c8360001c6040518363ffffffff1660e01b8152600401610724929190610a8b565b602060405180830381865afa158015610741573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906107659190610ae0565b6107a4576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161079b90610b7f565b60405180910390fd5b5050565b6000819050919050565b6107bb816107a8565b82525050565b60006020820190506107d660008301846107b2565b92915050565b6000819050919050565b6107ef816107dc565b82525050565b600060208201905061080a60008301846107e6565b92915050565b600080fd5b61081e816107a8565b811461082957600080fd5b50565b60008135905061083b81610815565b92915050565b60006020828403121561085757610856610810565b5b60006108658482850161082c565b91505092915050565b60008115159050919050565b6108838161086e565b82525050565b600060208201905061089e600083018461087a565b92915050565b600082825260208201905092915050565b7f4974277320426f622773207475726e206e6f772c20706c65617365207761697460008201527f2100000000000000000000000000000000000000000000000000000000000000602082015250565b60006109116021836108a4565b915061091c826108b5565b604082019050919050565b6000602082019050818103600083015261094081610904565b9050919050565b7f6f6e6c7920636c61696d65722063616e20636c61696d21000000000000000000600082015250565b600061097d6017836108a4565b915061098882610947565b602082019050919050565b600060208201905081810360008301526109ac81610970565b9050919050565b7f746f6f206561726c7920746f20636c61696d2100000000000000000000000000600082015250565b60006109e96013836108a4565b91506109f4826109b3565b602082019050919050565b60006020820190508181036000830152610a18816109dc565b9050919050565b7f746f6f206c61746520746f20636c61696d210000000000000000000000000000600082015250565b6000610a556012836108a4565b9150610a6082610a1f565b602082019050919050565b60006020820190508181036000830152610a8481610a48565b9050919050565b6000604082019050610aa060008301856107e6565b610aad60208301846107e6565b9392505050565b610abd8161086e565b8114610ac857600080fd5b50565b600081519050610ada81610ab4565b92915050565b600060208284031215610af657610af5610810565b5b6000610b0484828501610acb565b91505092915050565b7f70726f76696465642073656372657420646f6573206e6f74206d61746368207460008201527f6865206578706563746564207075624b65790000000000000000000000000000602082015250565b6000610b696032836108a4565b9150610b7482610b0d565b604082019050919050565b60006020820190508181036000830152610b9881610b5c565b905091905056fea2646970667358221220f3e479ca3bdf78fda3bde7ba25f8da3d3690710a6d422322d1268505f25d15d164736f6c634300080a0033608060405234801561001057600080fd5b50610373806100206000396000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c8063b32d1b4f14610030575b600080fd5b61004a600480360381019061004591906101a0565b610060565b60405161005791906101fb565b60405180910390f35b60008060016000601b7f79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f8179860001b7ffffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141806100bc576100bb610216565b5b7f79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798890960001b604051600081526020016040526040516100ff94939291906102f8565b6020604051602081039080840390855afa158015610121573d6000803e3d6000fd5b5050506020604051035190508073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff161491505092915050565b600080fd5b6000819050919050565b61017d8161016a565b811461018857600080fd5b50565b60008135905061019a81610174565b92915050565b600080604083850312156101b7576101b6610165565b5b60006101c58582860161018b565b92505060206101d68582860161018b565b9150509250929050565b60008115159050919050565b6101f5816101e0565b82525050565b600060208201905061021060008301846101ec565b92915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601260045260246000fd5b6000819050919050565b6000819050919050565b60008160001b9050919050565b600061028161027c61027784610245565b610259565b61024f565b9050919050565b61029181610266565b82525050565b6000819050919050565b600060ff82169050919050565b6000819050919050565b60006102d36102ce6102c984610297565b6102ae565b6102a1565b9050919050565b6102e3816102b8565b82525050565b6102f28161024f565b82525050565b600060808201905061030d6000830187610288565b61031a60208301866102da565b61032760408301856102e9565b61033460608301846102e9565b9594505050505056fea2646970667358221220ee85e0d8d34d053fc1c14aef7a8d86a3f1f8da9061855ddfe8b5075730286b0364736f6c634300080a0033" +var SwapBin = "0x61016060405260008060006101000a81548160ff0219169083151502179055506040516200142838038062001428833981810160405281019062000044919062000289565b3373ffffffffffffffffffffffffffffffffffffffff1660a08173ffffffffffffffffffffffffffffffffffffffff16815250508360e081815250508261010081815250508173ffffffffffffffffffffffffffffffffffffffff1660c08173ffffffffffffffffffffffffffffffffffffffff16815250508042620000cb91906200032a565b6101208181525050600281620000e2919062000387565b42620000ef91906200032a565b610140818152505060405162000105906200019b565b604051809103906000f08015801562000122573d6000803e3d6000fd5b5073ffffffffffffffffffffffffffffffffffffffff1660808173ffffffffffffffffffffffffffffffffffffffff16815250507f8d36aa70807342c3036697a846281194626fd4afa892356ad5979e03831ab080848460405162000189929190620003f9565b60405180910390a15050505062000426565b610393806200109583390190565b600080fd5b6000819050919050565b620001c381620001ae565b8114620001cf57600080fd5b50565b600081519050620001e381620001b8565b92915050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b60006200021682620001e9565b9050919050565b620002288162000209565b81146200023457600080fd5b50565b60008151905062000248816200021d565b92915050565b6000819050919050565b62000263816200024e565b81146200026f57600080fd5b50565b600081519050620002838162000258565b92915050565b60008060008060808587031215620002a657620002a5620001a9565b5b6000620002b687828801620001d2565b9450506020620002c987828801620001d2565b9350506040620002dc8782880162000237565b9250506060620002ef8782880162000272565b91505092959194509250565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b600062000337826200024e565b915062000344836200024e565b9250827fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff038211156200037c576200037b620002fb565b5b828201905092915050565b600062000394826200024e565b9150620003a1836200024e565b9250817fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0483118215151615620003dd57620003dc620002fb565b5b828202905092915050565b620003f381620001ae565b82525050565b6000604082019050620004106000830185620003e8565b6200041f6020830184620003e8565b9392505050565b60805160a05160c05160e051610100516101205161014051610bd5620004c06000396000818161018b0152818161022b01526105980152600081816101af01528181610255015261052001526000818161016701526102d301526000818161039a01526105fe015260008181610492015261065b0152600081816101d30152818161033001526103d4015260006106c50152610bd56000f3fe608060405234801561001057600080fd5b50600436106100885760003560e01c8063736290f81161005b578063736290f81461010357806374d7c13814610121578063a094a0311461012b578063bd66528a1461014957610088565b806303f7e2461461008d57806345bb8e09146100ab5780634ded8d52146100c95780637249fbb6146100e7575b600080fd5b610095610165565b6040516100a291906107c1565b60405180910390f35b6100b3610189565b6040516100c091906107f5565b60405180910390f35b6100d16101ad565b6040516100de91906107f5565b60405180910390f35b61010160048036038101906100fc9190610841565b6101d1565b005b61010b610398565b60405161011891906107c1565b60405180910390f35b6101296103bc565b005b61013361047f565b6040516101409190610889565b60405180910390f35b610163600480360381019061015e9190610841565b610490565b005b7f000000000000000000000000000000000000000000000000000000000000000081565b7f000000000000000000000000000000000000000000000000000000000000000081565b7f000000000000000000000000000000000000000000000000000000000000000081565b7f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161461022957600080fd5b7f00000000000000000000000000000000000000000000000000000000000000004210158061028e57507f00000000000000000000000000000000000000000000000000000000000000004210801561028d575060008054906101000a900460ff16155b5b6102cd576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016102c490610927565b60405180910390fd5b6102f7817f00000000000000000000000000000000000000000000000000000000000000006106c3565b7ffe509803c09416b28ff3d8f690c8b0c61462a892c46d5430c8fb20abe472daf08160405161032691906107c1565b60405180910390a17f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff166108fc479081150290604051600060405180830381858888f19350505050158015610394573d6000803e3d6000fd5b5050565b7f000000000000000000000000000000000000000000000000000000000000000081565b60008054906101000a900460ff1615801561042257507f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16145b61042b57600080fd5b60016000806101000a81548160ff0219169083151502179055507fb54ee60cc7bf27004d4c21b3226232af966dcdb31a046c95533970b3eea24ae960016040516104759190610889565b60405180910390a1565b60008054906101000a900460ff1681565b7f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161461051e576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161051590610993565b60405180910390fd5b7f000000000000000000000000000000000000000000000000000000000000000042101580610557575060008054906101000a900460ff165b610596576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161058d906109ff565b60405180910390fd5b7f000000000000000000000000000000000000000000000000000000000000000042106105f8576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016105ef90610a6b565b60405180910390fd5b610622817f00000000000000000000000000000000000000000000000000000000000000006106c3565b7feddf608ef698454af2fb41c1df7b7e5154ff0d46969f895e0f39c7dfe7e6380a8160405161065191906107c1565b60405180910390a17f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff166108fc479081150290604051600060405180830381858888f193505050501580156106bf573d6000803e3d6000fd5b5050565b7f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff1663b32d1b4f8360001c8360001c6040518363ffffffff1660e01b8152600401610724929190610a8b565b602060405180830381865afa158015610741573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906107659190610ae0565b6107a4576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161079b90610b7f565b60405180910390fd5b5050565b6000819050919050565b6107bb816107a8565b82525050565b60006020820190506107d660008301846107b2565b92915050565b6000819050919050565b6107ef816107dc565b82525050565b600060208201905061080a60008301846107e6565b92915050565b600080fd5b61081e816107a8565b811461082957600080fd5b50565b60008135905061083b81610815565b92915050565b60006020828403121561085757610856610810565b5b60006108658482850161082c565b91505092915050565b60008115159050919050565b6108838161086e565b82525050565b600060208201905061089e600083018461087a565b92915050565b600082825260208201905092915050565b7f4974277320426f622773207475726e206e6f772c20706c65617365207761697460008201527f2100000000000000000000000000000000000000000000000000000000000000602082015250565b60006109116021836108a4565b915061091c826108b5565b604082019050919050565b6000602082019050818103600083015261094081610904565b9050919050565b7f6f6e6c7920636c61696d65722063616e20636c61696d21000000000000000000600082015250565b600061097d6017836108a4565b915061098882610947565b602082019050919050565b600060208201905081810360008301526109ac81610970565b9050919050565b7f746f6f206561726c7920746f20636c61696d2100000000000000000000000000600082015250565b60006109e96013836108a4565b91506109f4826109b3565b602082019050919050565b60006020820190508181036000830152610a18816109dc565b9050919050565b7f746f6f206c61746520746f20636c61696d210000000000000000000000000000600082015250565b6000610a556012836108a4565b9150610a6082610a1f565b602082019050919050565b60006020820190508181036000830152610a8481610a48565b9050919050565b6000604082019050610aa060008301856107e6565b610aad60208301846107e6565b9392505050565b610abd8161086e565b8114610ac857600080fd5b50565b600081519050610ada81610ab4565b92915050565b600060208284031215610af657610af5610810565b5b6000610b0484828501610acb565b91505092915050565b7f70726f76696465642073656372657420646f6573206e6f74206d61746368207460008201527f6865206578706563746564207075624b65790000000000000000000000000000602082015250565b6000610b696032836108a4565b9150610b7482610b0d565b604082019050919050565b60006020820190508181036000830152610b9881610b5c565b905091905056fea2646970667358221220eef10a07ee0fe899e2863bb85e0f177ddaed24fd30fe378029ed71b7874d195964736f6c634300080a0033608060405234801561001057600080fd5b50610373806100206000396000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c8063b32d1b4f14610030575b600080fd5b61004a600480360381019061004591906101a0565b610060565b60405161005791906101fb565b60405180910390f35b60008060016000601b7f79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f8179860001b7ffffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141806100bc576100bb610216565b5b7f79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798890960001b604051600081526020016040526040516100ff94939291906102f8565b6020604051602081039080840390855afa158015610121573d6000803e3d6000fd5b5050506020604051035190508073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff161491505092915050565b600080fd5b6000819050919050565b61017d8161016a565b811461018857600080fd5b50565b60008135905061019a81610174565b92915050565b600080604083850312156101b7576101b6610165565b5b60006101c58582860161018b565b92505060206101d68582860161018b565b9150509250929050565b60008115159050919050565b6101f5816101e0565b82525050565b600060208201905061021060008301846101ec565b92915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601260045260246000fd5b6000819050919050565b6000819050919050565b60008160001b9050919050565b600061028161027c61027784610245565b610259565b61024f565b9050919050565b61029181610266565b82525050565b6000819050919050565b600060ff82169050919050565b6000819050919050565b60006102d36102ce6102c984610297565b6102ae565b6102a1565b9050919050565b6102e3816102b8565b82525050565b6102f28161024f565b82525050565b600060808201905061030d6000830187610288565b61031a60208301866102da565b61032760408301856102e9565b61033460608301846102e9565b9594505050505056fea2646970667358221220366f5349c99cc5aedbea5b41a0bba96eef36652cb460171cf7386f8afd621fde64736f6c634300080a0033" // DeploySwap deploys a new Ethereum contract, binding an instance of Swap to it. func DeploySwap(auth *bind.TransactOpts, backend bind.ContractBackend, _pubKeyClaim [32]byte, _pubKeyRefund [32]byte, _claimer common.Address, _timeoutDuration *big.Int) (common.Address, *types.Transaction, *Swap, error) { diff --git a/swap-contract/swap_test.go b/swap-contract/swap_test.go deleted file mode 100644 index 242422bb..00000000 --- a/swap-contract/swap_test.go +++ /dev/null @@ -1,190 +0,0 @@ -package swap - -import ( - "crypto/ecdsa" - "encoding/hex" - "math/big" - "testing" - - "github.com/ethereum/go-ethereum/accounts/abi/bind" - ethcommon "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/ethclient" - "github.com/ethereum/go-ethereum/rpc" - "github.com/stretchr/testify/require" - - "github.com/noot/atomic-swap/common" - "github.com/noot/atomic-swap/crypto/secp256k1" - "github.com/noot/atomic-swap/dleq" -) - -var defaultTimeoutDuration = big.NewInt(60) // 60 seconds - -func setupAliceAuth(t *testing.T) (*bind.TransactOpts, *ethclient.Client, *ecdsa.PrivateKey) { - conn, err := ethclient.Dial(common.DefaultEthEndpoint) - require.NoError(t, err) - pkA, err := crypto.HexToECDSA(common.DefaultPrivKeyAlice) - require.NoError(t, err) - auth, err := bind.NewKeyedTransactorWithChainID(pkA, big.NewInt(common.GanacheChainID)) - require.NoError(t, err) - return auth, conn, pkA -} - -func TestDeploySwap(t *testing.T) { - auth, conn, _ := setupAliceAuth(t) - address, tx, swapContract, err := DeploySwap(auth, conn, [32]byte{}, [32]byte{}, - ethcommon.Address{}, defaultTimeoutDuration) - require.NoError(t, err) - require.NotEqual(t, ethcommon.Address{}, address) - require.NotNil(t, tx) - require.NotNil(t, swapContract) -} - -func TestSwap_Claim_vec(t *testing.T) { - secret, err := hex.DecodeString("D30519BCAE8D180DBFCC94FE0B8383DC310185B0BE97B4365083EBCECCD75759") - require.NoError(t, err) - pubX, err := hex.DecodeString("3AF1E1EFA4D1E1AD5CB9E3967E98E901DAFCD37C44CF0BFB6C216997F5EE51DF") - require.NoError(t, err) - pubY, err := hex.DecodeString("E4ACAC3E6F139E0C7DB2BD736824F51392BDA176965A1C59EB9C3C5FF9E85D7A") - require.NoError(t, err) - - var s, x, y [32]byte - copy(s[:], secret) - copy(x[:], pubX) - copy(y[:], pubY) - - pk := secp256k1.NewPublicKey(x, y) - cmt := pk.Keccak256() - - // deploy swap contract with claim key hash - auth, conn, pkA := setupAliceAuth(t) - pub := pkA.Public().(*ecdsa.PublicKey) - addr := crypto.PubkeyToAddress(*pub) - t.Logf("commitment: 0x%x", cmt) - - _, deployTx, swap, err := DeploySwap(auth, conn, cmt, [32]byte{}, addr, - defaultTimeoutDuration) - require.NoError(t, err) - t.Logf("gas cost to deploy Swap.sol: %d", deployTx.Gas()) - - // set contract to Ready - _, err = swap.SetReady(auth) - require.NoError(t, err) - - // now let's try to claim - tx, err := swap.Claim(auth, s) - require.NoError(t, err) - t.Log(tx.Hash()) -} - -func TestSwap_Claim_random(t *testing.T) { - // generate claim secret and public key - dleq := &dleq.FarcasterDLEq{} - proof, err := dleq.Prove() - require.NoError(t, err) - res, err := dleq.Verify(proof) - require.NoError(t, err) - - // hash public key - cmt := res.Secp256k1PublicKey().Keccak256() - - // deploy swap contract with claim key hash - auth, conn, pkA := setupAliceAuth(t) - pub := pkA.Public().(*ecdsa.PublicKey) - addr := crypto.PubkeyToAddress(*pub) - - _, deployTx, swap, err := DeploySwap(auth, conn, cmt, [32]byte{}, addr, - defaultTimeoutDuration) - require.NoError(t, err) - t.Logf("gas cost to deploy Swap.sol: %d", deployTx.Gas()) - - // set contract to Ready - tx, err := swap.SetReady(auth) - require.NoError(t, err) - t.Logf("gas cost to call SetReady: %d", tx.Gas()) - - // now let's try to claim - var s [32]byte - secret := proof.Secret() - copy(s[:], common.Reverse(secret[:])) - tx, err = swap.Claim(auth, s) - require.NoError(t, err) - t.Logf("gas cost to call Claim: %d", tx.Gas()) -} - -func TestSwap_Refund_beforeT0(t *testing.T) { - // generate refund secret and public key - dleq := &dleq.FarcasterDLEq{} - proof, err := dleq.Prove() - require.NoError(t, err) - res, err := dleq.Verify(proof) - require.NoError(t, err) - - // hash public key - cmt := res.Secp256k1PublicKey().Keccak256() - - // deploy swap contract with claim key hash - auth, conn, pkA := setupAliceAuth(t) - pub := pkA.Public().(*ecdsa.PublicKey) - addr := crypto.PubkeyToAddress(*pub) - - _, deployTx, swap, err := DeploySwap(auth, conn, [32]byte{}, cmt, addr, - defaultTimeoutDuration) - require.NoError(t, err) - t.Logf("gas cost to deploy Swap.sol: %d", deployTx.Gas()) - - // now let's try to claim - var s [32]byte - secret := proof.Secret() - copy(s[:], common.Reverse(secret[:])) - tx, err := swap.Refund(auth, s) - require.NoError(t, err) - t.Logf("gas cost to call Refund: %d", tx.Gas()) -} - -func TestSwap_Refund_afterT1(t *testing.T) { - // generate refund secret and public key - dleq := &dleq.FarcasterDLEq{} - proof, err := dleq.Prove() - require.NoError(t, err) - res, err := dleq.Verify(proof) - require.NoError(t, err) - - // hash public key - cmt := res.Secp256k1PublicKey().Keccak256() - - // deploy swap contract with claim key hash - auth, conn, pkA := setupAliceAuth(t) - pub := pkA.Public().(*ecdsa.PublicKey) - addr := crypto.PubkeyToAddress(*pub) - - _, deployTx, swap, err := DeploySwap(auth, conn, [32]byte{}, cmt, addr, - defaultTimeoutDuration) - require.NoError(t, err) - t.Logf("gas cost to deploy Swap.sol: %d", deployTx.Gas()) - - // fast forward past t1 - rpcClient, err := rpc.Dial(common.DefaultEthEndpoint) - require.NoError(t, err) - - var result string - err = rpcClient.Call(&result, "evm_snapshot") - require.NoError(t, err) - - err = rpcClient.Call(nil, "evm_increaseTime", defaultTimeoutDuration.Int64()*2+60) - require.NoError(t, err) - - defer func() { - var ok bool - err = rpcClient.Call(&ok, "evm_revert", result) - require.NoError(t, err) - }() - - // now let's try to claim - var s [32]byte - secret := proof.Secret() - copy(s[:], common.Reverse(secret[:])) - tx, err := swap.Refund(auth, s) - require.NoError(t, err) - t.Logf("gas cost to call Refund: %d", tx.Gas()) -} diff --git a/swapfactory/swap_factory.go b/swapfactory/swap_factory.go new file mode 100644 index 00000000..dc260dc7 --- /dev/null +++ b/swapfactory/swap_factory.go @@ -0,0 +1,893 @@ +// Code generated - DO NOT EDIT. +// This file is a generated binding and any manual changes will be lost. + +package swapfactory + +import ( + "math/big" + "strings" + + ethereum "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/event" +) + +// Reference imports to suppress errors if they are not otherwise used. +var ( + _ = big.NewInt + _ = strings.NewReader + _ = ethereum.NotFound + _ = bind.Bind + _ = common.Big1 + _ = types.BloomLookup + _ = event.NewSubscription +) + +// SwapFactoryABI is the input ABI used to generate the binding from. +const SwapFactoryABI = "[{\"inputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"swapID\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"bytes32\",\"name\":\"s\",\"type\":\"bytes32\"}],\"name\":\"Claimed\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"swapID\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"bytes32\",\"name\":\"claimKey\",\"type\":\"bytes32\"},{\"indexed\":false,\"internalType\":\"bytes32\",\"name\":\"refundKey\",\"type\":\"bytes32\"}],\"name\":\"New\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"swapID\",\"type\":\"uint256\"}],\"name\":\"Ready\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"swapID\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"bytes32\",\"name\":\"s\",\"type\":\"bytes32\"}],\"name\":\"Refunded\",\"type\":\"event\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"id\",\"type\":\"uint256\"},{\"internalType\":\"bytes32\",\"name\":\"_s\",\"type\":\"bytes32\"}],\"name\":\"claim\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"_pubKeyClaim\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"_pubKeyRefund\",\"type\":\"bytes32\"},{\"internalType\":\"addresspayable\",\"name\":\"_claimer\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"_timeoutDuration\",\"type\":\"uint256\"}],\"name\":\"new_swap\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"payable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"id\",\"type\":\"uint256\"},{\"internalType\":\"bytes32\",\"name\":\"_s\",\"type\":\"bytes32\"}],\"name\":\"refund\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"id\",\"type\":\"uint256\"}],\"name\":\"set_ready\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"name\":\"swaps\",\"outputs\":[{\"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\":\"timeout_0\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"timeout_1\",\"type\":\"uint256\"},{\"internalType\":\"bool\",\"name\":\"isReady\",\"type\":\"bool\"},{\"internalType\":\"bool\",\"name\":\"completed\",\"type\":\"bool\"},{\"internalType\":\"uint256\",\"name\":\"value\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"}]" + +// SwapFactoryBin is the compiled bytecode used for deploying new contracts. +var SwapFactoryBin = "0x60a060405234801561001057600080fd5b5060405161001d90610072565b604051809103906000f080158015610039573d6000803e3d6000fd5b5073ffffffffffffffffffffffffffffffffffffffff1660808173ffffffffffffffffffffffffffffffffffffffff168152505061007f565b6103938061177583390190565b6080516116db61009a6000396000610c7701526116db6000f3fe60806040526004361061004a5760003560e01c80630deeecba1461004f57806331d144571461007f57806337da2ecf146100a857806371eedb88146100d1578063f09c5829146100fa575b600080fd5b61006960048036038101906100649190610eab565b61013f565b6040516100769190610f21565b60405180910390f35b34801561008b57600080fd5b506100a660048036038101906100a19190610f3c565b61038d565b005b3480156100b457600080fd5b506100cf60048036038101906100ca9190610f7c565b6106de565b005b3480156100dd57600080fd5b506100f860048036038101906100f39190610f3c565b6108b2565b005b34801561010657600080fd5b50610121600480360381019061011c9190610f7c565b610bcd565b60405161013699989796959493929190610fe2565b60405180910390f35b600080600054905061014f610d5a565b33816000019073ffffffffffffffffffffffffffffffffffffffff16908173ffffffffffffffffffffffffffffffffffffffff168152505084816020019073ffffffffffffffffffffffffffffffffffffffff16908173ffffffffffffffffffffffffffffffffffffffff1681525050868160400181815250508581606001818152505083426101df919061109e565b8160800181815250506002846101f591906110f4565b42610200919061109e565b8160a001818152505034816101000181815250507f982a99d883f17ecd5797205d5b3674205d7882bb28a9487d736d3799422cd0558288886040516102479392919061114e565b60405180910390a16001600080828254610261919061109e565b92505081905550806001600084815260200190815260200160002060008201518160000160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555060208201518160010160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555060408201518160020155606082015181600301556080820151816004015560a0820151816005015560c08201518160060160006101000a81548160ff02191690831515021790555060e08201518160060160016101000a81548160ff02191690831515021790555061010082015181600701559050508192505050949350505050565b600060016000848152602001908152602001600020604051806101200160405290816000820160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020016001820160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001600282015481526020016003820154815260200160048201548152602001600582015481526020016006820160009054906101000a900460ff161515151581526020016006820160019054906101000a900460ff1615151515815260200160078201548152505090508060e0015115610509576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610500906111e2565b60405180910390fd5b806020015173ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161461057b576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016105729061124e565b60405180910390fd5b80608001514210158061058f57508060c001515b6105ce576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016105c5906112ba565b60405180910390fd5b8060a001514210610614576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161060b90611326565b60405180910390fd5b610622828260400151610c75565b7fd5a2476fc450083bbb092dd3f4be92698ffdc2d213e6f1e730c7f44a52f1ccfc8383604051610653929190611346565b60405180910390a1806020015173ffffffffffffffffffffffffffffffffffffffff166108fc8261010001519081150290604051600060405180830381858888f193505050501580156106aa573d6000803e3d6000fd5b50600180600085815260200190815260200160002060060160016101000a81548160ff021916908315150217905550505050565b6001600082815260200190815260200160002060060160019054906101000a900460ff1615610742576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610739906111e2565b60405180910390fd5b3373ffffffffffffffffffffffffffffffffffffffff166001600083815260200190815260200160002060000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16146107e6576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016107dd906113e1565b60405180910390fd5b6001600082815260200190815260200160002060060160009054906101000a900460ff161561084a576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016108419061144d565b60405180910390fd5b600180600083815260200190815260200160002060060160006101000a81548160ff0219169083151502179055507f0b217ad5c70346c7cd952bd2463c6684a56f9ed229f5780947586625781b4770816040516108a79190610f21565b60405180910390a150565b600060016000848152602001908152602001600020604051806101200160405290816000820160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020016001820160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001600282015481526020016003820154815260200160048201548152602001600582015481526020016006820160009054906101000a900460ff161515151581526020016006820160019054906101000a900460ff1615151515815260200160078201548152505090508060e0015115610a2e576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610a25906111e2565b60405180910390fd5b806000015173ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614610aa0576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610a97906114df565b60405180910390fd5b8060a0015142101580610ac45750806080015142108015610ac357508060c00151155b5b610b03576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610afa90611571565b60405180910390fd5b610b11828260600151610c75565b7f4fd30f3ee0d64f7eaa62d0e005ca64c6a560652156d6c33f23ea8ca4936106e08383604051610b42929190611346565b60405180910390a1806000015173ffffffffffffffffffffffffffffffffffffffff166108fc8261010001519081150290604051600060405180830381858888f19350505050158015610b99573d6000803e3d6000fd5b50600180600085815260200190815260200160002060060160016101000a81548160ff021916908315150217905550505050565b60016020528060005260406000206000915090508060000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16908060010160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16908060020154908060030154908060040154908060050154908060060160009054906101000a900460ff16908060060160019054906101000a900460ff16908060070154905089565b7f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff1663b32d1b4f8360001c8360001c6040518363ffffffff1660e01b8152600401610cd6929190611591565b602060405180830381865afa158015610cf3573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610d1791906115e6565b610d56576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610d4d90611685565b60405180910390fd5b5050565b604051806101200160405280600073ffffffffffffffffffffffffffffffffffffffff168152602001600073ffffffffffffffffffffffffffffffffffffffff16815260200160008019168152602001600080191681526020016000815260200160008152602001600015158152602001600015158152602001600081525090565b600080fd5b6000819050919050565b610df481610de1565b8114610dff57600080fd5b50565b600081359050610e1181610deb565b92915050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000610e4282610e17565b9050919050565b610e5281610e37565b8114610e5d57600080fd5b50565b600081359050610e6f81610e49565b92915050565b6000819050919050565b610e8881610e75565b8114610e9357600080fd5b50565b600081359050610ea581610e7f565b92915050565b60008060008060808587031215610ec557610ec4610ddc565b5b6000610ed387828801610e02565b9450506020610ee487828801610e02565b9350506040610ef587828801610e60565b9250506060610f0687828801610e96565b91505092959194509250565b610f1b81610e75565b82525050565b6000602082019050610f366000830184610f12565b92915050565b60008060408385031215610f5357610f52610ddc565b5b6000610f6185828601610e96565b9250506020610f7285828601610e02565b9150509250929050565b600060208284031215610f9257610f91610ddc565b5b6000610fa084828501610e96565b91505092915050565b610fb281610e37565b82525050565b610fc181610de1565b82525050565b60008115159050919050565b610fdc81610fc7565b82525050565b600061012082019050610ff8600083018c610fa9565b611005602083018b610fa9565b611012604083018a610fb8565b61101f6060830189610fb8565b61102c6080830188610f12565b61103960a0830187610f12565b61104660c0830186610fd3565b61105360e0830185610fd3565b611061610100830184610f12565b9a9950505050505050505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b60006110a982610e75565b91506110b483610e75565b9250827fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff038211156110e9576110e861106f565b5b828201905092915050565b60006110ff82610e75565b915061110a83610e75565b9250817fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff04831182151516156111435761114261106f565b5b828202905092915050565b60006060820190506111636000830186610f12565b6111706020830185610fb8565b61117d6040830184610fb8565b949350505050565b600082825260208201905092915050565b7f7377617020697320616c726561647920636f6d706c6574656400000000000000600082015250565b60006111cc601983611185565b91506111d782611196565b602082019050919050565b600060208201905081810360008301526111fb816111bf565b9050919050565b7f6f6e6c7920636c61696d65722063616e20636c61696d21000000000000000000600082015250565b6000611238601783611185565b915061124382611202565b602082019050919050565b600060208201905081810360008301526112678161122b565b9050919050565b7f746f6f206561726c7920746f20636c61696d2100000000000000000000000000600082015250565b60006112a4601383611185565b91506112af8261126e565b602082019050919050565b600060208201905081810360008301526112d381611297565b9050919050565b7f746f6f206c61746520746f20636c61696d210000000000000000000000000000600082015250565b6000611310601283611185565b915061131b826112da565b602082019050919050565b6000602082019050818103600083015261133f81611303565b9050919050565b600060408201905061135b6000830185610f12565b6113686020830184610fb8565b9392505050565b7f6f6e6c79207468652073776170206f776e65722063616e2063616c6c2073657460008201527f5f72656164790000000000000000000000000000000000000000000000000000602082015250565b60006113cb602683611185565b91506113d68261136f565b604082019050919050565b600060208201905081810360008301526113fa816113be565b9050919050565b7f737761702077617320616c72656164792073657420746f207265616479000000600082015250565b6000611437601d83611185565b915061144282611401565b602082019050919050565b600060208201905081810360008301526114668161142a565b9050919050565b7f726566756e64206d7573742062652063616c6c6564206279207468652073776160008201527f70206f776e657200000000000000000000000000000000000000000000000000602082015250565b60006114c9602783611185565b91506114d48261146d565b604082019050919050565b600060208201905081810360008301526114f8816114bc565b9050919050565b7f697427732074686520636f756e74657270617274792773207475726e2c20756e60008201527f61626c6520746f20726566756e642c2074727920616761696e206c6174657200602082015250565b600061155b603f83611185565b9150611566826114ff565b604082019050919050565b6000602082019050818103600083015261158a8161154e565b9050919050565b60006040820190506115a66000830185610f12565b6115b36020830184610f12565b9392505050565b6115c381610fc7565b81146115ce57600080fd5b50565b6000815190506115e0816115ba565b92915050565b6000602082840312156115fc576115fb610ddc565b5b600061160a848285016115d1565b91505092915050565b7f70726f76696465642073656372657420646f6573206e6f74206d61746368207460008201527f6865206578706563746564207075626c6963206b657900000000000000000000602082015250565b600061166f603683611185565b915061167a82611613565b604082019050919050565b6000602082019050818103600083015261169e81611662565b905091905056fea264697066735822122013e2005a5cd702e769c1f2524c52239967861439c28bbdb76af5392b2a177d4b64736f6c634300080a0033608060405234801561001057600080fd5b50610373806100206000396000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c8063b32d1b4f14610030575b600080fd5b61004a600480360381019061004591906101a0565b610060565b60405161005791906101fb565b60405180910390f35b60008060016000601b7f79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f8179860001b7ffffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141806100bc576100bb610216565b5b7f79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798890960001b604051600081526020016040526040516100ff94939291906102f8565b6020604051602081039080840390855afa158015610121573d6000803e3d6000fd5b5050506020604051035190508073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff161491505092915050565b600080fd5b6000819050919050565b61017d8161016a565b811461018857600080fd5b50565b60008135905061019a81610174565b92915050565b600080604083850312156101b7576101b6610165565b5b60006101c58582860161018b565b92505060206101d68582860161018b565b9150509250929050565b60008115159050919050565b6101f5816101e0565b82525050565b600060208201905061021060008301846101ec565b92915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601260045260246000fd5b6000819050919050565b6000819050919050565b60008160001b9050919050565b600061028161027c61027784610245565b610259565b61024f565b9050919050565b61029181610266565b82525050565b6000819050919050565b600060ff82169050919050565b6000819050919050565b60006102d36102ce6102c984610297565b6102ae565b6102a1565b9050919050565b6102e3816102b8565b82525050565b6102f28161024f565b82525050565b600060808201905061030d6000830187610288565b61031a60208301866102da565b61032760408301856102e9565b61033460608301846102e9565b9594505050505056fea2646970667358221220366f5349c99cc5aedbea5b41a0bba96eef36652cb460171cf7386f8afd621fde64736f6c634300080a0033" + +// DeploySwapFactory deploys a new Ethereum contract, binding an instance of SwapFactory to it. +func DeploySwapFactory(auth *bind.TransactOpts, backend bind.ContractBackend) (common.Address, *types.Transaction, *SwapFactory, error) { + parsed, err := abi.JSON(strings.NewReader(SwapFactoryABI)) + if err != nil { + return common.Address{}, nil, nil, err + } + + address, tx, contract, err := bind.DeployContract(auth, parsed, common.FromHex(SwapFactoryBin), backend) + if err != nil { + return common.Address{}, nil, nil, err + } + return address, tx, &SwapFactory{SwapFactoryCaller: SwapFactoryCaller{contract: contract}, SwapFactoryTransactor: SwapFactoryTransactor{contract: contract}, SwapFactoryFilterer: SwapFactoryFilterer{contract: contract}}, nil +} + +// SwapFactory is an auto generated Go binding around an Ethereum contract. +type SwapFactory struct { + SwapFactoryCaller // Read-only binding to the contract + SwapFactoryTransactor // Write-only binding to the contract + SwapFactoryFilterer // Log filterer for contract events +} + +// SwapFactoryCaller is an auto generated read-only Go binding around an Ethereum contract. +type SwapFactoryCaller struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// SwapFactoryTransactor is an auto generated write-only Go binding around an Ethereum contract. +type SwapFactoryTransactor struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// SwapFactoryFilterer is an auto generated log filtering Go binding around an Ethereum contract events. +type SwapFactoryFilterer struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// SwapFactorySession is an auto generated Go binding around an Ethereum contract, +// with pre-set call and transact options. +type SwapFactorySession struct { + Contract *SwapFactory // Generic contract binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session +} + +// SwapFactoryCallerSession is an auto generated read-only Go binding around an Ethereum contract, +// with pre-set call options. +type SwapFactoryCallerSession struct { + Contract *SwapFactoryCaller // Generic contract caller binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session +} + +// SwapFactoryTransactorSession is an auto generated write-only Go binding around an Ethereum contract, +// with pre-set transact options. +type SwapFactoryTransactorSession struct { + Contract *SwapFactoryTransactor // Generic contract transactor binding to set the session for + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session +} + +// SwapFactoryRaw is an auto generated low-level Go binding around an Ethereum contract. +type SwapFactoryRaw struct { + Contract *SwapFactory // Generic contract binding to access the raw methods on +} + +// SwapFactoryCallerRaw is an auto generated low-level read-only Go binding around an Ethereum contract. +type SwapFactoryCallerRaw struct { + Contract *SwapFactoryCaller // Generic read-only contract binding to access the raw methods on +} + +// SwapFactoryTransactorRaw is an auto generated low-level write-only Go binding around an Ethereum contract. +type SwapFactoryTransactorRaw struct { + Contract *SwapFactoryTransactor // Generic write-only contract binding to access the raw methods on +} + +// NewSwapFactory creates a new instance of SwapFactory, bound to a specific deployed contract. +func NewSwapFactory(address common.Address, backend bind.ContractBackend) (*SwapFactory, error) { + contract, err := bindSwapFactory(address, backend, backend, backend) + if err != nil { + return nil, err + } + return &SwapFactory{SwapFactoryCaller: SwapFactoryCaller{contract: contract}, SwapFactoryTransactor: SwapFactoryTransactor{contract: contract}, SwapFactoryFilterer: SwapFactoryFilterer{contract: contract}}, nil +} + +// NewSwapFactoryCaller creates a new read-only instance of SwapFactory, bound to a specific deployed contract. +func NewSwapFactoryCaller(address common.Address, caller bind.ContractCaller) (*SwapFactoryCaller, error) { + contract, err := bindSwapFactory(address, caller, nil, nil) + if err != nil { + return nil, err + } + return &SwapFactoryCaller{contract: contract}, nil +} + +// NewSwapFactoryTransactor creates a new write-only instance of SwapFactory, bound to a specific deployed contract. +func NewSwapFactoryTransactor(address common.Address, transactor bind.ContractTransactor) (*SwapFactoryTransactor, error) { + contract, err := bindSwapFactory(address, nil, transactor, nil) + if err != nil { + return nil, err + } + return &SwapFactoryTransactor{contract: contract}, nil +} + +// NewSwapFactoryFilterer creates a new log filterer instance of SwapFactory, bound to a specific deployed contract. +func NewSwapFactoryFilterer(address common.Address, filterer bind.ContractFilterer) (*SwapFactoryFilterer, error) { + contract, err := bindSwapFactory(address, nil, nil, filterer) + if err != nil { + return nil, err + } + return &SwapFactoryFilterer{contract: contract}, nil +} + +// bindSwapFactory binds a generic wrapper to an already deployed contract. +func bindSwapFactory(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor, filterer bind.ContractFilterer) (*bind.BoundContract, error) { + parsed, err := abi.JSON(strings.NewReader(SwapFactoryABI)) + if err != nil { + return nil, err + } + return bind.NewBoundContract(address, parsed, caller, transactor, filterer), nil +} + +// Call invokes the (constant) contract method with params as input values and +// sets the output to result. The result type might be a single field for simple +// returns, a slice of interfaces for anonymous returns and a struct for named +// returns. +func (_SwapFactory *SwapFactoryRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { + return _SwapFactory.Contract.SwapFactoryCaller.contract.Call(opts, result, method, params...) +} + +// Transfer initiates a plain transaction to move funds to the contract, calling +// its default method if one is available. +func (_SwapFactory *SwapFactoryRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _SwapFactory.Contract.SwapFactoryTransactor.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_SwapFactory *SwapFactoryRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _SwapFactory.Contract.SwapFactoryTransactor.contract.Transact(opts, method, params...) +} + +// Call invokes the (constant) contract method with params as input values and +// sets the output to result. The result type might be a single field for simple +// returns, a slice of interfaces for anonymous returns and a struct for named +// returns. +func (_SwapFactory *SwapFactoryCallerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { + return _SwapFactory.Contract.contract.Call(opts, result, method, params...) +} + +// Transfer initiates a plain transaction to move funds to the contract, calling +// its default method if one is available. +func (_SwapFactory *SwapFactoryTransactorRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _SwapFactory.Contract.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_SwapFactory *SwapFactoryTransactorRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _SwapFactory.Contract.contract.Transact(opts, method, params...) +} + +// Swaps is a free data retrieval call binding the contract method 0xf09c5829. +// +// Solidity: function swaps(uint256 ) view returns(address owner, address claimer, bytes32 pubKeyClaim, bytes32 pubKeyRefund, uint256 timeout_0, uint256 timeout_1, bool isReady, bool completed, uint256 value) +func (_SwapFactory *SwapFactoryCaller) Swaps(opts *bind.CallOpts, arg0 *big.Int) (struct { + Owner common.Address + Claimer common.Address + PubKeyClaim [32]byte + PubKeyRefund [32]byte + Timeout0 *big.Int + Timeout1 *big.Int + IsReady bool + Completed bool + Value *big.Int +}, error) { + var out []interface{} + err := _SwapFactory.contract.Call(opts, &out, "swaps", arg0) + + outstruct := new(struct { + Owner common.Address + Claimer common.Address + PubKeyClaim [32]byte + PubKeyRefund [32]byte + Timeout0 *big.Int + Timeout1 *big.Int + IsReady bool + Completed bool + Value *big.Int + }) + if err != nil { + return *outstruct, err + } + + outstruct.Owner = *abi.ConvertType(out[0], new(common.Address)).(*common.Address) + outstruct.Claimer = *abi.ConvertType(out[1], new(common.Address)).(*common.Address) + outstruct.PubKeyClaim = *abi.ConvertType(out[2], new([32]byte)).(*[32]byte) + outstruct.PubKeyRefund = *abi.ConvertType(out[3], new([32]byte)).(*[32]byte) + outstruct.Timeout0 = *abi.ConvertType(out[4], new(*big.Int)).(**big.Int) + outstruct.Timeout1 = *abi.ConvertType(out[5], new(*big.Int)).(**big.Int) + outstruct.IsReady = *abi.ConvertType(out[6], new(bool)).(*bool) + outstruct.Completed = *abi.ConvertType(out[7], new(bool)).(*bool) + outstruct.Value = *abi.ConvertType(out[8], new(*big.Int)).(**big.Int) + + return *outstruct, err + +} + +// Swaps is a free data retrieval call binding the contract method 0xf09c5829. +// +// Solidity: function swaps(uint256 ) view returns(address owner, address claimer, bytes32 pubKeyClaim, bytes32 pubKeyRefund, uint256 timeout_0, uint256 timeout_1, bool isReady, bool completed, uint256 value) +func (_SwapFactory *SwapFactorySession) Swaps(arg0 *big.Int) (struct { + Owner common.Address + Claimer common.Address + PubKeyClaim [32]byte + PubKeyRefund [32]byte + Timeout0 *big.Int + Timeout1 *big.Int + IsReady bool + Completed bool + Value *big.Int +}, error) { + return _SwapFactory.Contract.Swaps(&_SwapFactory.CallOpts, arg0) +} + +// Swaps is a free data retrieval call binding the contract method 0xf09c5829. +// +// Solidity: function swaps(uint256 ) view returns(address owner, address claimer, bytes32 pubKeyClaim, bytes32 pubKeyRefund, uint256 timeout_0, uint256 timeout_1, bool isReady, bool completed, uint256 value) +func (_SwapFactory *SwapFactoryCallerSession) Swaps(arg0 *big.Int) (struct { + Owner common.Address + Claimer common.Address + PubKeyClaim [32]byte + PubKeyRefund [32]byte + Timeout0 *big.Int + Timeout1 *big.Int + IsReady bool + Completed bool + Value *big.Int +}, error) { + return _SwapFactory.Contract.Swaps(&_SwapFactory.CallOpts, arg0) +} + +// Claim is a paid mutator transaction binding the contract method 0x31d14457. +// +// Solidity: function claim(uint256 id, bytes32 _s) returns() +func (_SwapFactory *SwapFactoryTransactor) Claim(opts *bind.TransactOpts, id *big.Int, _s [32]byte) (*types.Transaction, error) { + return _SwapFactory.contract.Transact(opts, "claim", id, _s) +} + +// Claim is a paid mutator transaction binding the contract method 0x31d14457. +// +// Solidity: function claim(uint256 id, bytes32 _s) returns() +func (_SwapFactory *SwapFactorySession) Claim(id *big.Int, _s [32]byte) (*types.Transaction, error) { + return _SwapFactory.Contract.Claim(&_SwapFactory.TransactOpts, id, _s) +} + +// Claim is a paid mutator transaction binding the contract method 0x31d14457. +// +// Solidity: function claim(uint256 id, bytes32 _s) returns() +func (_SwapFactory *SwapFactoryTransactorSession) Claim(id *big.Int, _s [32]byte) (*types.Transaction, error) { + return _SwapFactory.Contract.Claim(&_SwapFactory.TransactOpts, id, _s) +} + +// NewSwap is a paid mutator transaction binding the contract method 0x0deeecba. +// +// Solidity: function new_swap(bytes32 _pubKeyClaim, bytes32 _pubKeyRefund, address _claimer, uint256 _timeoutDuration) payable returns(uint256) +func (_SwapFactory *SwapFactoryTransactor) NewSwap(opts *bind.TransactOpts, _pubKeyClaim [32]byte, _pubKeyRefund [32]byte, _claimer common.Address, _timeoutDuration *big.Int) (*types.Transaction, error) { + return _SwapFactory.contract.Transact(opts, "new_swap", _pubKeyClaim, _pubKeyRefund, _claimer, _timeoutDuration) +} + +// NewSwap is a paid mutator transaction binding the contract method 0x0deeecba. +// +// Solidity: function new_swap(bytes32 _pubKeyClaim, bytes32 _pubKeyRefund, address _claimer, uint256 _timeoutDuration) payable returns(uint256) +func (_SwapFactory *SwapFactorySession) NewSwap(_pubKeyClaim [32]byte, _pubKeyRefund [32]byte, _claimer common.Address, _timeoutDuration *big.Int) (*types.Transaction, error) { + return _SwapFactory.Contract.NewSwap(&_SwapFactory.TransactOpts, _pubKeyClaim, _pubKeyRefund, _claimer, _timeoutDuration) +} + +// NewSwap is a paid mutator transaction binding the contract method 0x0deeecba. +// +// Solidity: function new_swap(bytes32 _pubKeyClaim, bytes32 _pubKeyRefund, address _claimer, uint256 _timeoutDuration) payable returns(uint256) +func (_SwapFactory *SwapFactoryTransactorSession) NewSwap(_pubKeyClaim [32]byte, _pubKeyRefund [32]byte, _claimer common.Address, _timeoutDuration *big.Int) (*types.Transaction, error) { + return _SwapFactory.Contract.NewSwap(&_SwapFactory.TransactOpts, _pubKeyClaim, _pubKeyRefund, _claimer, _timeoutDuration) +} + +// Refund is a paid mutator transaction binding the contract method 0x71eedb88. +// +// Solidity: function refund(uint256 id, bytes32 _s) returns() +func (_SwapFactory *SwapFactoryTransactor) Refund(opts *bind.TransactOpts, id *big.Int, _s [32]byte) (*types.Transaction, error) { + return _SwapFactory.contract.Transact(opts, "refund", id, _s) +} + +// Refund is a paid mutator transaction binding the contract method 0x71eedb88. +// +// Solidity: function refund(uint256 id, bytes32 _s) returns() +func (_SwapFactory *SwapFactorySession) Refund(id *big.Int, _s [32]byte) (*types.Transaction, error) { + return _SwapFactory.Contract.Refund(&_SwapFactory.TransactOpts, id, _s) +} + +// Refund is a paid mutator transaction binding the contract method 0x71eedb88. +// +// Solidity: function refund(uint256 id, bytes32 _s) returns() +func (_SwapFactory *SwapFactoryTransactorSession) Refund(id *big.Int, _s [32]byte) (*types.Transaction, error) { + return _SwapFactory.Contract.Refund(&_SwapFactory.TransactOpts, id, _s) +} + +// SetReady is a paid mutator transaction binding the contract method 0x37da2ecf. +// +// Solidity: function set_ready(uint256 id) returns() +func (_SwapFactory *SwapFactoryTransactor) SetReady(opts *bind.TransactOpts, id *big.Int) (*types.Transaction, error) { + return _SwapFactory.contract.Transact(opts, "set_ready", id) +} + +// SetReady is a paid mutator transaction binding the contract method 0x37da2ecf. +// +// Solidity: function set_ready(uint256 id) returns() +func (_SwapFactory *SwapFactorySession) SetReady(id *big.Int) (*types.Transaction, error) { + return _SwapFactory.Contract.SetReady(&_SwapFactory.TransactOpts, id) +} + +// SetReady is a paid mutator transaction binding the contract method 0x37da2ecf. +// +// Solidity: function set_ready(uint256 id) returns() +func (_SwapFactory *SwapFactoryTransactorSession) SetReady(id *big.Int) (*types.Transaction, error) { + return _SwapFactory.Contract.SetReady(&_SwapFactory.TransactOpts, id) +} + +// SwapFactoryClaimedIterator is returned from FilterClaimed and is used to iterate over the raw logs and unpacked data for Claimed events raised by the SwapFactory contract. +type SwapFactoryClaimedIterator struct { + Event *SwapFactoryClaimed // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *SwapFactoryClaimedIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(SwapFactoryClaimed) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(SwapFactoryClaimed) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *SwapFactoryClaimedIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *SwapFactoryClaimedIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// SwapFactoryClaimed represents a Claimed event raised by the SwapFactory contract. +type SwapFactoryClaimed struct { + SwapID *big.Int + S [32]byte + Raw types.Log // Blockchain specific contextual infos +} + +// FilterClaimed is a free log retrieval operation binding the contract event 0xd5a2476fc450083bbb092dd3f4be92698ffdc2d213e6f1e730c7f44a52f1ccfc. +// +// Solidity: event Claimed(uint256 swapID, bytes32 s) +func (_SwapFactory *SwapFactoryFilterer) FilterClaimed(opts *bind.FilterOpts) (*SwapFactoryClaimedIterator, error) { + + logs, sub, err := _SwapFactory.contract.FilterLogs(opts, "Claimed") + if err != nil { + return nil, err + } + return &SwapFactoryClaimedIterator{contract: _SwapFactory.contract, event: "Claimed", logs: logs, sub: sub}, nil +} + +// WatchClaimed is a free log subscription operation binding the contract event 0xd5a2476fc450083bbb092dd3f4be92698ffdc2d213e6f1e730c7f44a52f1ccfc. +// +// Solidity: event Claimed(uint256 swapID, bytes32 s) +func (_SwapFactory *SwapFactoryFilterer) WatchClaimed(opts *bind.WatchOpts, sink chan<- *SwapFactoryClaimed) (event.Subscription, error) { + + logs, sub, err := _SwapFactory.contract.WatchLogs(opts, "Claimed") + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(SwapFactoryClaimed) + if err := _SwapFactory.contract.UnpackLog(event, "Claimed", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// ParseClaimed is a log parse operation binding the contract event 0xd5a2476fc450083bbb092dd3f4be92698ffdc2d213e6f1e730c7f44a52f1ccfc. +// +// Solidity: event Claimed(uint256 swapID, bytes32 s) +func (_SwapFactory *SwapFactoryFilterer) ParseClaimed(log types.Log) (*SwapFactoryClaimed, error) { + event := new(SwapFactoryClaimed) + if err := _SwapFactory.contract.UnpackLog(event, "Claimed", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +// SwapFactoryNewIterator is returned from FilterNew and is used to iterate over the raw logs and unpacked data for New events raised by the SwapFactory contract. +type SwapFactoryNewIterator struct { + Event *SwapFactoryNew // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *SwapFactoryNewIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(SwapFactoryNew) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(SwapFactoryNew) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *SwapFactoryNewIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *SwapFactoryNewIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// SwapFactoryNew represents a New event raised by the SwapFactory contract. +type SwapFactoryNew struct { + SwapID *big.Int + ClaimKey [32]byte + RefundKey [32]byte + Raw types.Log // Blockchain specific contextual infos +} + +// FilterNew is a free log retrieval operation binding the contract event 0x982a99d883f17ecd5797205d5b3674205d7882bb28a9487d736d3799422cd055. +// +// Solidity: event New(uint256 swapID, bytes32 claimKey, bytes32 refundKey) +func (_SwapFactory *SwapFactoryFilterer) FilterNew(opts *bind.FilterOpts) (*SwapFactoryNewIterator, error) { + + logs, sub, err := _SwapFactory.contract.FilterLogs(opts, "New") + if err != nil { + return nil, err + } + return &SwapFactoryNewIterator{contract: _SwapFactory.contract, event: "New", logs: logs, sub: sub}, nil +} + +// WatchNew is a free log subscription operation binding the contract event 0x982a99d883f17ecd5797205d5b3674205d7882bb28a9487d736d3799422cd055. +// +// Solidity: event New(uint256 swapID, bytes32 claimKey, bytes32 refundKey) +func (_SwapFactory *SwapFactoryFilterer) WatchNew(opts *bind.WatchOpts, sink chan<- *SwapFactoryNew) (event.Subscription, error) { + + logs, sub, err := _SwapFactory.contract.WatchLogs(opts, "New") + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(SwapFactoryNew) + if err := _SwapFactory.contract.UnpackLog(event, "New", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// ParseNew is a log parse operation binding the contract event 0x982a99d883f17ecd5797205d5b3674205d7882bb28a9487d736d3799422cd055. +// +// Solidity: event New(uint256 swapID, bytes32 claimKey, bytes32 refundKey) +func (_SwapFactory *SwapFactoryFilterer) ParseNew(log types.Log) (*SwapFactoryNew, error) { + event := new(SwapFactoryNew) + if err := _SwapFactory.contract.UnpackLog(event, "New", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +// SwapFactoryReadyIterator is returned from FilterReady and is used to iterate over the raw logs and unpacked data for Ready events raised by the SwapFactory contract. +type SwapFactoryReadyIterator struct { + Event *SwapFactoryReady // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *SwapFactoryReadyIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(SwapFactoryReady) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(SwapFactoryReady) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *SwapFactoryReadyIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *SwapFactoryReadyIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// SwapFactoryReady represents a Ready event raised by the SwapFactory contract. +type SwapFactoryReady struct { + SwapID *big.Int + Raw types.Log // Blockchain specific contextual infos +} + +// FilterReady is a free log retrieval operation binding the contract event 0x0b217ad5c70346c7cd952bd2463c6684a56f9ed229f5780947586625781b4770. +// +// Solidity: event Ready(uint256 swapID) +func (_SwapFactory *SwapFactoryFilterer) FilterReady(opts *bind.FilterOpts) (*SwapFactoryReadyIterator, error) { + + logs, sub, err := _SwapFactory.contract.FilterLogs(opts, "Ready") + if err != nil { + return nil, err + } + return &SwapFactoryReadyIterator{contract: _SwapFactory.contract, event: "Ready", logs: logs, sub: sub}, nil +} + +// WatchReady is a free log subscription operation binding the contract event 0x0b217ad5c70346c7cd952bd2463c6684a56f9ed229f5780947586625781b4770. +// +// Solidity: event Ready(uint256 swapID) +func (_SwapFactory *SwapFactoryFilterer) WatchReady(opts *bind.WatchOpts, sink chan<- *SwapFactoryReady) (event.Subscription, error) { + + logs, sub, err := _SwapFactory.contract.WatchLogs(opts, "Ready") + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(SwapFactoryReady) + if err := _SwapFactory.contract.UnpackLog(event, "Ready", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// ParseReady is a log parse operation binding the contract event 0x0b217ad5c70346c7cd952bd2463c6684a56f9ed229f5780947586625781b4770. +// +// Solidity: event Ready(uint256 swapID) +func (_SwapFactory *SwapFactoryFilterer) ParseReady(log types.Log) (*SwapFactoryReady, error) { + event := new(SwapFactoryReady) + if err := _SwapFactory.contract.UnpackLog(event, "Ready", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +// SwapFactoryRefundedIterator is returned from FilterRefunded and is used to iterate over the raw logs and unpacked data for Refunded events raised by the SwapFactory contract. +type SwapFactoryRefundedIterator struct { + Event *SwapFactoryRefunded // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *SwapFactoryRefundedIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(SwapFactoryRefunded) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(SwapFactoryRefunded) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *SwapFactoryRefundedIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *SwapFactoryRefundedIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// SwapFactoryRefunded represents a Refunded event raised by the SwapFactory contract. +type SwapFactoryRefunded struct { + SwapID *big.Int + S [32]byte + Raw types.Log // Blockchain specific contextual infos +} + +// FilterRefunded is a free log retrieval operation binding the contract event 0x4fd30f3ee0d64f7eaa62d0e005ca64c6a560652156d6c33f23ea8ca4936106e0. +// +// Solidity: event Refunded(uint256 swapID, bytes32 s) +func (_SwapFactory *SwapFactoryFilterer) FilterRefunded(opts *bind.FilterOpts) (*SwapFactoryRefundedIterator, error) { + + logs, sub, err := _SwapFactory.contract.FilterLogs(opts, "Refunded") + if err != nil { + return nil, err + } + return &SwapFactoryRefundedIterator{contract: _SwapFactory.contract, event: "Refunded", logs: logs, sub: sub}, nil +} + +// WatchRefunded is a free log subscription operation binding the contract event 0x4fd30f3ee0d64f7eaa62d0e005ca64c6a560652156d6c33f23ea8ca4936106e0. +// +// Solidity: event Refunded(uint256 swapID, bytes32 s) +func (_SwapFactory *SwapFactoryFilterer) WatchRefunded(opts *bind.WatchOpts, sink chan<- *SwapFactoryRefunded) (event.Subscription, error) { + + logs, sub, err := _SwapFactory.contract.WatchLogs(opts, "Refunded") + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(SwapFactoryRefunded) + if err := _SwapFactory.contract.UnpackLog(event, "Refunded", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// ParseRefunded is a log parse operation binding the contract event 0x4fd30f3ee0d64f7eaa62d0e005ca64c6a560652156d6c33f23ea8ca4936106e0. +// +// Solidity: event Refunded(uint256 swapID, bytes32 s) +func (_SwapFactory *SwapFactoryFilterer) ParseRefunded(log types.Log) (*SwapFactoryRefunded, error) { + event := new(SwapFactoryRefunded) + if err := _SwapFactory.contract.UnpackLog(event, "Refunded", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} diff --git a/swapfactory/swap_factory_test.go b/swapfactory/swap_factory_test.go new file mode 100644 index 00000000..3e9c8d12 --- /dev/null +++ b/swapfactory/swap_factory_test.go @@ -0,0 +1,342 @@ +package swapfactory + +import ( + "context" + "crypto/ecdsa" + "encoding/hex" + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + ethcommon "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/ethclient" + "github.com/ethereum/go-ethereum/rpc" + "github.com/stretchr/testify/require" + + "github.com/noot/atomic-swap/common" + "github.com/noot/atomic-swap/crypto/secp256k1" + "github.com/noot/atomic-swap/dleq" +) + +var defaultTimeoutDuration = big.NewInt(60) // 60 seconds + +func setupAliceAuth(t *testing.T) (*bind.TransactOpts, *ethclient.Client, *ecdsa.PrivateKey) { + conn, err := ethclient.Dial(common.DefaultEthEndpoint) + require.NoError(t, err) + pkA, err := crypto.HexToECDSA(common.DefaultPrivKeyAlice) + require.NoError(t, err) + auth, err := bind.NewKeyedTransactorWithChainID(pkA, big.NewInt(common.GanacheChainID)) + require.NoError(t, err) + return auth, conn, pkA +} + +func TestSwapFactory_NewSwap(t *testing.T) { + auth, conn, _ := setupAliceAuth(t) + address, tx, contract, err := DeploySwapFactory(auth, conn) + require.NoError(t, err) + require.NotEqual(t, ethcommon.Address{}, address) + require.NotNil(t, tx) + require.NotNil(t, contract) + t.Logf("gas cost to deploy SwapFactory.sol: %d", tx.Gas()) + + tx, err = contract.NewSwap(auth, [32]byte{}, [32]byte{}, + ethcommon.Address{}, defaultTimeoutDuration) + require.NoError(t, err) + t.Logf("gas cost to call new_swap: %d", tx.Gas()) +} + +func TestSwapFactory_Claim_vec(t *testing.T) { + secret, err := hex.DecodeString("D30519BCAE8D180DBFCC94FE0B8383DC310185B0BE97B4365083EBCECCD75759") + require.NoError(t, err) + pubX, err := hex.DecodeString("3AF1E1EFA4D1E1AD5CB9E3967E98E901DAFCD37C44CF0BFB6C216997F5EE51DF") + require.NoError(t, err) + pubY, err := hex.DecodeString("E4ACAC3E6F139E0C7DB2BD736824F51392BDA176965A1C59EB9C3C5FF9E85D7A") + require.NoError(t, err) + + var s, x, y [32]byte + copy(s[:], secret) + copy(x[:], pubX) + copy(y[:], pubY) + + pk := secp256k1.NewPublicKey(x, y) + cmt := pk.Keccak256() + + // deploy swap contract with claim key hash + auth, conn, pkA := setupAliceAuth(t) + pub := pkA.Public().(*ecdsa.PublicKey) + addr := crypto.PubkeyToAddress(*pub) + t.Logf("commitment: 0x%x", cmt) + + _, tx, contract, err := DeploySwapFactory(auth, conn) + require.NoError(t, err) + t.Logf("gas cost to deploy SwapFactory.sol: %d", tx.Gas()) + + tx, err = contract.NewSwap(auth, cmt, [32]byte{}, addr, + defaultTimeoutDuration) + require.NoError(t, err) + t.Logf("gas cost to call new_swap: %d", tx.Gas()) + + receipt, err := conn.TransactionReceipt(context.Background(), tx.Hash()) + require.NoError(t, err) + require.Equal(t, 1, len(receipt.Logs)) + id, err := GetIDFromLog(receipt.Logs[0]) + require.NoError(t, err) + + // set contract to Ready + tx, err = contract.SetReady(auth, id) + require.NoError(t, err) + t.Logf("gas cost to call set_ready: %d", tx.Gas()) + + // now let's try to claim + tx, err = contract.Claim(auth, id, s) + require.NoError(t, err) + t.Logf("gas cost to call claim: %d", tx.Gas()) + + callOpts := &bind.CallOpts{ + From: crypto.PubkeyToAddress(*pub), + Context: context.Background(), + } + + info, err := contract.Swaps(callOpts, id) + require.NoError(t, err) + require.True(t, info.Completed) +} + +func TestSwap_Claim_random(t *testing.T) { + // generate claim secret and public key + dleq := &dleq.FarcasterDLEq{} + proof, err := dleq.Prove() + require.NoError(t, err) + res, err := dleq.Verify(proof) + require.NoError(t, err) + + // hash public key + cmt := res.Secp256k1PublicKey().Keccak256() + + // deploy swap contract with claim key hash + auth, conn, pkA := setupAliceAuth(t) + pub := pkA.Public().(*ecdsa.PublicKey) + addr := crypto.PubkeyToAddress(*pub) + + _, tx, contract, err := DeploySwapFactory(auth, conn) + require.NoError(t, err) + t.Logf("gas cost to deploy SwapFactory.sol: %d", tx.Gas()) + + tx, err = contract.NewSwap(auth, cmt, [32]byte{}, addr, + defaultTimeoutDuration) + require.NoError(t, err) + t.Logf("gas cost to call new_swap: %d", tx.Gas()) + + receipt, err := conn.TransactionReceipt(context.Background(), tx.Hash()) + require.NoError(t, err) + require.Equal(t, 1, len(receipt.Logs)) + id, err := GetIDFromLog(receipt.Logs[0]) + require.NoError(t, err) + + // set contract to Ready + tx, err = contract.SetReady(auth, id) + require.NoError(t, err) + t.Logf("gas cost to call SetReady: %d", tx.Gas()) + + // now let's try to claim + var s [32]byte + secret := proof.Secret() + copy(s[:], common.Reverse(secret[:])) + tx, err = contract.Claim(auth, id, s) + require.NoError(t, err) + t.Logf("gas cost to call Claim: %d", tx.Gas()) + + callOpts := &bind.CallOpts{ + From: crypto.PubkeyToAddress(*pub), + Context: context.Background(), + } + + info, err := contract.Swaps(callOpts, id) + require.NoError(t, err) + require.True(t, info.Completed) +} + +func TestSwap_Refund_beforeT0(t *testing.T) { + // generate refund secret and public key + dleq := &dleq.FarcasterDLEq{} + proof, err := dleq.Prove() + require.NoError(t, err) + res, err := dleq.Verify(proof) + require.NoError(t, err) + + // hash public key + cmt := res.Secp256k1PublicKey().Keccak256() + + // deploy swap contract with refund key hash + auth, conn, pkA := setupAliceAuth(t) + pub := pkA.Public().(*ecdsa.PublicKey) + addr := crypto.PubkeyToAddress(*pub) + + _, tx, contract, err := DeploySwapFactory(auth, conn) + require.NoError(t, err) + t.Logf("gas cost to deploy SwapFactory.sol: %d", tx.Gas()) + + tx, err = contract.NewSwap(auth, [32]byte{}, cmt, addr, + defaultTimeoutDuration) + require.NoError(t, err) + t.Logf("gas cost to call new_swap: %d", tx.Gas()) + + receipt, err := conn.TransactionReceipt(context.Background(), tx.Hash()) + require.NoError(t, err) + require.Equal(t, 1, len(receipt.Logs)) + id, err := GetIDFromLog(receipt.Logs[0]) + require.NoError(t, err) + + // now let's try to refund + var s [32]byte + secret := proof.Secret() + copy(s[:], common.Reverse(secret[:])) + tx, err = contract.Refund(auth, id, s) + require.NoError(t, err) + t.Logf("gas cost to call Refund: %d", tx.Gas()) + + callOpts := &bind.CallOpts{ + From: crypto.PubkeyToAddress(*pub), + Context: context.Background(), + } + + info, err := contract.Swaps(callOpts, id) + require.NoError(t, err) + require.True(t, info.Completed) +} + +func TestSwap_Refund_afterT1(t *testing.T) { + // generate refund secret and public key + dleq := &dleq.FarcasterDLEq{} + proof, err := dleq.Prove() + require.NoError(t, err) + res, err := dleq.Verify(proof) + require.NoError(t, err) + + // hash public key + cmt := res.Secp256k1PublicKey().Keccak256() + + // deploy swap contract with refund key hash + auth, conn, pkA := setupAliceAuth(t) + pub := pkA.Public().(*ecdsa.PublicKey) + addr := crypto.PubkeyToAddress(*pub) + + _, tx, contract, err := DeploySwapFactory(auth, conn) + require.NoError(t, err) + t.Logf("gas cost to deploy SwapFactory.sol: %d", tx.Gas()) + + tx, err = contract.NewSwap(auth, [32]byte{}, cmt, addr, + defaultTimeoutDuration) + require.NoError(t, err) + t.Logf("gas cost to call new_swap: %d", tx.Gas()) + + receipt, err := conn.TransactionReceipt(context.Background(), tx.Hash()) + require.NoError(t, err) + require.Equal(t, 1, len(receipt.Logs)) + id, err := GetIDFromLog(receipt.Logs[0]) + require.NoError(t, err) + + // fast forward past t1 + rpcClient, err := rpc.Dial(common.DefaultEthEndpoint) + require.NoError(t, err) + + var result string + err = rpcClient.Call(&result, "evm_snapshot") + require.NoError(t, err) + + err = rpcClient.Call(nil, "evm_increaseTime", defaultTimeoutDuration.Int64()*2+60) + require.NoError(t, err) + + defer func() { + var ok bool + err = rpcClient.Call(&ok, "evm_revert", result) + require.NoError(t, err) + }() + + // now let's try to refund + var s [32]byte + secret := proof.Secret() + copy(s[:], common.Reverse(secret[:])) + tx, err = contract.Refund(auth, id, s) + require.NoError(t, err) + t.Logf("gas cost to call Refund: %d", tx.Gas()) + + callOpts := &bind.CallOpts{ + From: crypto.PubkeyToAddress(*pub), + Context: context.Background(), + } + + info, err := contract.Swaps(callOpts, id) + require.NoError(t, err) + require.True(t, info.Completed) +} + +func TestSwap_MultipleSwaps(t *testing.T) { + // test case where contract has multiple swaps happening at once + auth, conn, pkA := setupAliceAuth(t) + pub := pkA.Public().(*ecdsa.PublicKey) + addr := crypto.PubkeyToAddress(*pub) + + _, tx, contract, err := DeploySwapFactory(auth, conn) + require.NoError(t, err) + t.Logf("gas cost to deploy SwapFactory.sol: %d", tx.Gas()) + + numSwaps := 16 + type swapCase struct { + id *big.Int + secret [32]byte + } + + // setup all swap instances in contract + swapCases := []*swapCase{} + for i := 0; i < numSwaps; i++ { + sc := &swapCase{} + + // generate claim secret and public key + dleq := &dleq.FarcasterDLEq{} + proof, err := dleq.Prove() //nolint:govet + require.NoError(t, err) + res, err := dleq.Verify(proof) + require.NoError(t, err) + + // hash public key + cmt := res.Secp256k1PublicKey().Keccak256() + secret := proof.Secret() + copy(sc.secret[:], common.Reverse(secret[:])) + + tx, err = contract.NewSwap(auth, cmt, [32]byte{}, addr, + defaultTimeoutDuration) + require.NoError(t, err) + t.Logf("gas cost to call new_swap: %d", tx.Gas()) + + receipt, err := conn.TransactionReceipt(context.Background(), tx.Hash()) + require.NoError(t, err) + require.Equal(t, 1, len(receipt.Logs)) + sc.id, err = GetIDFromLog(receipt.Logs[0]) + require.NoError(t, err) + + swapCases = append(swapCases, sc) + } + + for _, sc := range swapCases { + // set contract to Ready + tx, err = contract.SetReady(auth, sc.id) + require.NoError(t, err) + t.Logf("gas cost to call SetReady: %d", tx.Gas()) + + // now let's try to claim + tx, err = contract.Claim(auth, sc.id, sc.secret) + require.NoError(t, err) + t.Logf("gas cost to call Claim: %d", tx.Gas()) + + callOpts := &bind.CallOpts{ + From: crypto.PubkeyToAddress(*pub), + Context: context.Background(), + } + + info, err := contract.Swaps(callOpts, sc.id) + require.NoError(t, err) + require.True(t, info.Completed) + } +} diff --git a/swap-contract/utils.go b/swapfactory/utils.go similarity index 56% rename from swap-contract/utils.go rename to swapfactory/utils.go index 32927bc9..e489459a 100644 --- a/swap-contract/utils.go +++ b/swapfactory/utils.go @@ -1,7 +1,8 @@ -package swap +package swapfactory import ( "errors" + "math/big" "strings" "github.com/ethereum/go-ethereum/accounts/abi" @@ -17,7 +18,7 @@ func GetSecretFromLog(log *ethtypes.Log, event string) (*mcrypto.PrivateSpendKey return nil, errors.New("invalid event name, must be one of Claimed or Refunded") } - abi, err := abi.JSON(strings.NewReader(SwapABI)) + abi, err := abi.JSON(strings.NewReader(SwapFactoryABI)) if err != nil { return nil, err } @@ -28,11 +29,11 @@ func GetSecretFromLog(log *ethtypes.Log, event string) (*mcrypto.PrivateSpendKey return nil, err } - if len(res) == 0 { - return nil, errors.New("log had no parameters") + if len(res) < 2 { + return nil, errors.New("log had not enough parameters") } - s := res[0].([32]byte) + s := res[1].([32]byte) sk, err := mcrypto.NewPrivateSpendKey(common.Reverse(s[:])) if err != nil { @@ -41,3 +42,26 @@ func GetSecretFromLog(log *ethtypes.Log, event string) (*mcrypto.PrivateSpendKey return sk, nil } + +// GetIDFromLog returns the swap ID from a New log. +func GetIDFromLog(log *ethtypes.Log) (*big.Int, error) { + abi, err := abi.JSON(strings.NewReader(SwapFactoryABI)) + if err != nil { + return nil, err + } + + const event = "New" + + data := log.Data + res, err := abi.Unpack(event, data) + if err != nil { + return nil, err + } + + if len(res) == 0 { + return nil, errors.New("log had not enough parameters") + } + + id := res[0].(*big.Int) + return id, nil +}