From 223e9525842e34fe1f7b733e34bcdd0d36ece83a Mon Sep 17 00:00:00 2001 From: JoeGruff Date: Fri, 5 Mar 2021 17:27:04 +0900 Subject: [PATCH] multi: Add ETHSwapV0. Have the eth harness start with a contract. Add contract bindings and basic calls to clients. --- client/asset/eth/eth.go | 6 + client/asset/eth/eth_test.go | 14 + client/asset/eth/rpcclient.go | 74 +++- client/asset/eth/rpcclient_harness_test.go | 395 ++++++++++++++++++++- dex/networks/eth/contract.go | 379 ++++++++++++++++++++ dex/networks/eth/contracts/ETHSwapV0.sol | 84 +++++ dex/networks/eth/contracts/README.md | 13 + dex/testing/eth/harness.sh | 35 +- server/asset/eth/common.go | 23 ++ 9 files changed, 1002 insertions(+), 21 deletions(-) create mode 100644 dex/networks/eth/contract.go create mode 100644 dex/networks/eth/contracts/ETHSwapV0.sol create mode 100644 dex/networks/eth/contracts/README.md diff --git a/client/asset/eth/eth.go b/client/asset/eth/eth.go index 3d7cb76b89..e84a40970d 100644 --- a/client/asset/eth/eth.go +++ b/client/asset/eth/eth.go @@ -16,10 +16,12 @@ import ( "decred.org/dcrdex/client/asset" "decred.org/dcrdex/dex" + swap "decred.org/dcrdex/dex/networks/eth" dexeth "decred.org/dcrdex/server/asset/eth" "github.com/decred/dcrd/dcrutil/v4" "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/accounts" + "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/node" @@ -112,6 +114,7 @@ type ethFetcher interface { connect(ctx context.Context, node *node.Node, contractAddr common.Address) error importAccount(pw string, privKeyB []byte) (*accounts.Account, error) listWallets(ctx context.Context) ([]rawWallet, error) + initiate(opts *bind.TransactOpts, netID int64, refundTimestamp int64, secretHash [32]byte, participant common.Address) (*types.Transaction, error) lock(ctx context.Context, acct *accounts.Account) error nodeInfo(ctx context.Context) (*p2p.NodeInfo, error) pendingTransactions(ctx context.Context) ([]*types.Transaction, error) @@ -120,6 +123,9 @@ type ethFetcher interface { sendTransaction(ctx context.Context, tx map[string]string) (common.Hash, error) shutdown() syncProgress(ctx context.Context) (*ethereum.SyncProgress, error) + redeem(opts *bind.TransactOpts, netID int64, secret, secretHash [32]byte) (*types.Transaction, error) + refund(opts *bind.TransactOpts, netID int64, secretHash [32]byte) (*types.Transaction, error) + swap(ctx context.Context, from *accounts.Account, secretHash [32]byte) (*swap.ETHSwapSwap, error) unlock(ctx context.Context, pw string, acct *accounts.Account) error } diff --git a/client/asset/eth/eth_test.go b/client/asset/eth/eth_test.go index 982de672aa..7976c25957 100644 --- a/client/asset/eth/eth_test.go +++ b/client/asset/eth/eth_test.go @@ -12,9 +12,11 @@ import ( "time" "decred.org/dcrdex/dex" + swap "decred.org/dcrdex/dex/networks/eth" dexeth "decred.org/dcrdex/server/asset/eth" "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/accounts" + "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/node" @@ -96,6 +98,18 @@ func (n *testNode) syncProgress(ctx context.Context) (*ethereum.SyncProgress, er func (n *testNode) pendingTransactions(ctx context.Context) ([]*types.Transaction, error) { return nil, nil } +func (n *testNode) initiate(opts *bind.TransactOpts, netID int64, refundTimestamp int64, secretHash [32]byte, participant common.Address) (*types.Transaction, error) { + return nil, nil +} +func (n *testNode) redeem(opts *bind.TransactOpts, netID int64, secret, secretHash [32]byte) (*types.Transaction, error) { + return nil, nil +} +func (n *testNode) refund(opts *bind.TransactOpts, netID int64, secretHash [32]byte) (*types.Transaction, error) { + return nil, nil +} +func (n *testNode) swap(ctx context.Context, from *accounts.Account, secretHash [32]byte) (*swap.ETHSwapSwap, error) { + return nil, nil +} func (n *testNode) transactionReceipt(ctx context.Context, txHash common.Hash) (*types.Receipt, error) { return nil, nil } diff --git a/client/asset/eth/rpcclient.go b/client/asset/eth/rpcclient.go index b3a1bb3ea5..a46076f9e1 100644 --- a/client/asset/eth/rpcclient.go +++ b/client/asset/eth/rpcclient.go @@ -8,8 +8,10 @@ import ( "fmt" "math/big" + swap "decred.org/dcrdex/dex/networks/eth" "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/accounts" + "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/accounts/keystore" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" @@ -31,11 +33,12 @@ type rpcclient struct { // ec wraps the client with some useful calls. ec *ethclient.Client n *node.Node + es *swap.ETHSwap } // connect connects to a node. It then wraps ethclient's client and // bundles commands in a form we can easily use. -func (c *rpcclient) connect(ctx context.Context, node *node.Node, contractAddr common.Address) error { // contractAddr will be used soonTM +func (c *rpcclient) connect(ctx context.Context, node *node.Node, contractAddr common.Address) error { client, err := node.Attach() if err != nil { return fmt.Errorf("unable to dial rpc: %v", err) @@ -43,6 +46,10 @@ func (c *rpcclient) connect(ctx context.Context, node *node.Node, contractAddr c c.c = client c.ec = ethclient.NewClient(client) c.n = node + c.es, err = swap.NewETHSwap(contractAddr, c.ec) + if err != nil { + return fmt.Errorf("unable to find swap contract: %v", err) + } return nil } @@ -203,3 +210,68 @@ func (c *rpcclient) peers(ctx context.Context) ([]*p2p.PeerInfo, error) { } return peers, nil } + +// swap gets a swap keyed by secretHash in the contract. +func (c *rpcclient) swap(ctx context.Context, from *accounts.Account, secretHash [32]byte) (*swap.ETHSwapSwap, error) { + callOpts := &bind.CallOpts{ + Pending: true, + From: from.Address, + Context: ctx, + } + swap, err := c.es.Swap(callOpts, secretHash) + if err != nil { + return nil, err + } + return &swap, nil +} + +// wallet returns a wallet that owns acct from an ethereum wallet. +func (c *rpcclient) wallet(acct accounts.Account) (accounts.Wallet, error) { + wallet, err := c.n.AccountManager().Find(acct) + if err != nil { + return nil, fmt.Errorf("error finding wallet for account %s: %v \n", acct.Address, err) + } + return wallet, nil +} + +// initiate creates a swap contract. The initiator will be the account at +// txOpts.From. Any on-chain failure, such as this secret hash already existing +// in the swaps map, will not cause this to error. +func (c *rpcclient) initiate(txOpts *bind.TransactOpts, netID int64, refundTimestamp int64, secretHash [32]byte, participant common.Address) (*types.Transaction, error) { + wallet, err := c.wallet(accounts.Account{Address: txOpts.From}) + if err != nil { + return nil, err + } + txOpts.Signer = func(addr common.Address, tx *types.Transaction) (*types.Transaction, error) { + return wallet.SignTx(accounts.Account{Address: addr}, tx, big.NewInt(netID)) + } + return c.es.Initiate(txOpts, big.NewInt(refundTimestamp), secretHash, participant) +} + +// redeem redeems a swap contract. The redeemer will be the account at txOpts.From. +// Any on-chain failure, such as this secret not matching the hash, will not cause +// this to error. +func (c *rpcclient) redeem(txOpts *bind.TransactOpts, netID int64, secret, secretHash [32]byte) (*types.Transaction, error) { + wallet, err := c.wallet(accounts.Account{Address: txOpts.From}) + if err != nil { + return nil, err + } + txOpts.Signer = func(addr common.Address, tx *types.Transaction) (*types.Transaction, error) { + return wallet.SignTx(accounts.Account{Address: addr}, tx, big.NewInt(netID)) + } + return c.es.Redeem(txOpts, secret, secretHash) +} + +// refund refunds a swap contract. The refunder will be the account at txOpts.From. +// Any on-chain failure, such as the locktime not being past, will not cause +// this to error. +func (c *rpcclient) refund(txOpts *bind.TransactOpts, netID int64, secretHash [32]byte) (*types.Transaction, error) { + wallet, err := c.wallet(accounts.Account{Address: txOpts.From}) + if err != nil { + return nil, err + } + txOpts.Signer = func(addr common.Address, tx *types.Transaction) (*types.Transaction, error) { + return wallet.SignTx(accounts.Account{Address: addr}, tx, big.NewInt(netID)) + } + return c.es.Refund(txOpts, secretHash) +} diff --git a/client/asset/eth/rpcclient_harness_test.go b/client/asset/eth/rpcclient_harness_test.go index be88c4bc03..873d952638 100644 --- a/client/asset/eth/rpcclient_harness_test.go +++ b/client/asset/eth/rpcclient_harness_test.go @@ -6,7 +6,10 @@ // These tests are expected to be run in descending as some depend on the tests before. They cannot // be run in parallel. // -// NOTE: Occationally tests will fail with "timed out". Please try again... +// TODO: Occasionally tests will fail with "timed out" because transactions are +// not being mined. If one tx is not mined, following txs from the same account +// with a higher nonce also cannot be mined, so it causes all tests after to +// fail as well. Find out why the first tx fails to be broadcast. // // TODO: Running these tests many times eventually results in all transactions // returning "unexpeted error for test ok: exceeds block gas limit". Find out @@ -16,6 +19,7 @@ package eth import ( "context" + "crypto/sha256" "encoding/hex" "errors" "fmt" @@ -29,6 +33,8 @@ import ( "decred.org/dcrdex/client/asset" "decred.org/dcrdex/dex" + "decred.org/dcrdex/dex/encode" + "decred.org/dcrdex/server/asset/eth" "github.com/davecgh/go-spew/spew" "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/accounts/abi/bind" @@ -41,18 +47,19 @@ const ( ) var ( - gasPrice = big.NewInt(82e9) - homeDir = os.Getenv("HOME") - alphaNodeDir = filepath.Join(homeDir, "dextest", "eth", "alpha", "node") - ethClient = new(rpcclient) - ctx context.Context - tLogger = dex.StdOutLogger("ETHTEST", dex.LevelTrace) - simnetAddr = common.HexToAddress("2b84C791b79Ee37De042AD2ffF1A253c3ce9bc27") - simnetAcct = &accounts.Account{Address: simnetAddr} - participantAddr = common.HexToAddress("345853e21b1d475582E71cC269124eD5e2dD3422") - participantAcct = &accounts.Account{Address: participantAddr} - simnetID = int64(42) - newTXOpts = func(ctx context.Context, from common.Address, value *big.Int) *bind.TransactOpts { + gasPrice = big.NewInt(82e9) + homeDir = os.Getenv("HOME") + contractAddrFile = filepath.Join(homeDir, "dextest", "eth", "contract_addr.txt") + alphaNodeDir = filepath.Join(homeDir, "dextest", "eth", "alpha", "node") + ethClient = new(rpcclient) + ctx context.Context + tLogger = dex.StdOutLogger("ETHTEST", dex.LevelTrace) + simnetAddr = common.HexToAddress("2b84C791b79Ee37De042AD2ffF1A253c3ce9bc27") + simnetAcct = &accounts.Account{Address: simnetAddr} + participantAddr = common.HexToAddress("345853e21b1d475582E71cC269124eD5e2dD3422") + participantAcct = &accounts.Account{Address: participantAddr} + simnetID = int64(42) + newTXOpts = func(ctx context.Context, from common.Address, value *big.Int) *bind.TransactOpts { return &bind.TransactOpts{ GasPrice: gasPrice, GasLimit: 1e6, @@ -107,6 +114,18 @@ func TestMain(m *testing.M) { os.Exit(1) } defer os.RemoveAll(tmpDir) + addrBytes, err := ioutil.ReadFile(contractAddrFile) + if err != nil { + fmt.Printf("error reading contract address: %v\n", err) + os.Exit(1) + } + addrLen := len(addrBytes) + if addrLen == 0 { + fmt.Printf("no contract address found at %v\n", contractAddrFile) + os.Exit(1) + } + addrBytes = addrBytes[:addrLen-1] + fmt.Printf("Contract address is %v\n", string(addrBytes)) settings := map[string]string{ "appdir": tmpDir, "nodelistenaddr": "localhost:30355", @@ -121,7 +140,7 @@ func TestMain(m *testing.M) { wallet.internalNode.Close() wallet.internalNode.Wait() }() - if err := ethClient.connect(ctx, wallet.internalNode, common.Address{}); err != nil { + if err := ethClient.connect(ctx, wallet.internalNode, common.HexToAddress(string(addrBytes))); err != nil { fmt.Printf("connect error: %v\n", err) os.Exit(1) } @@ -302,6 +321,17 @@ func TestPendingTransactions(t *testing.T) { spew.Dump(txs) } +func TestSwap(t *testing.T) { + var secretHash [32]byte + copy(secretHash[:], encode.RandomBytes(32)) + swap, err := ethClient.swap(ctx, simnetAcct, secretHash) + if err != nil { + t.Fatal(err) + } + // Should be empty. + spew.Dump(swap) +} + func TestSyncProgress(t *testing.T) { progress, err := ethClient.syncProgress(ctx) if err != nil { @@ -317,3 +347,340 @@ func TestPeers(t *testing.T) { } spew.Dump(peers) } + +func TestInitiate(t *testing.T) { + now := time.Now().Unix() + var secretHash [32]byte + copy(secretHash[:], encode.RandomBytes(32)) + err := ethClient.unlock(ctx, pw, simnetAcct) + if err != nil { + t.Fatal(err) + } + amt := big.NewInt(1e18) + txOpts := newTXOpts(ctx, simnetAddr, amt) + swap, err := ethClient.swap(ctx, simnetAcct, secretHash) + if err != nil { + t.Fatal("unable to get swap state") + } + spew.Dump(swap) + state := eth.SwapState(swap.State) + if state != eth.None { + t.Fatalf("unexpeted swap state: want %s got %s", eth.None, state) + } + + tests := []struct { + name string + subAmt bool + finalState eth.SwapState + }{{ + name: "ok", + finalState: eth.Initiated, + subAmt: true, + }, { + // If the hash already exists, the contract should subtract only + // the tx fee from the account. + name: "secret hash already exists", + finalState: eth.Initiated, + }} + + for _, test := range tests { + originalBal, err := ethClient.balance(ctx, simnetAcct) + if err != nil { + t.Fatalf("unexpeted error for test %v: %v", test.name, err) + } + + tx, err := ethClient.initiate(txOpts, simnetID, now, secretHash, participantAddr) + if err != nil { + t.Fatalf("unexpeted error for test %v: %v", test.name, err) + } + spew.Dump(tx) + + if err := waitForMined(t, time.Second*10, false); err != nil { + t.Fatalf("unexpeted error for test %v: %v", test.name, err) + } + + // It appears the receipt is only accessable after the tx is mined. + receipt, err := ethClient.transactionReceipt(ctx, tx.Hash()) + if err != nil { + t.Fatalf("unexpeted error for test %v: %v", test.name, err) + } + spew.Dump(receipt) + + // Balance should be reduced by a certain amount depending on + // whether initiate completed successfully on-chain. If + // unsuccessful the fee is subtracted. If successful, amt is + // also subtracted. + bal, err := ethClient.balance(ctx, simnetAcct) + if err != nil { + t.Fatalf("unexpeted error for test %v: %v", test.name, err) + } + txFee := big.NewInt(0).Mul(big.NewInt(int64(receipt.GasUsed)), gasPrice) + wantBal := big.NewInt(0).Sub(originalBal, txFee) + if test.subAmt { + wantBal.Sub(wantBal, amt) + } + if bal.Cmp(wantBal) != 0 { + t.Fatalf("unexpeted balance change for test %v: want %v got %v", test.name, wantBal, bal) + } + + swap, err = ethClient.swap(ctx, simnetAcct, secretHash) + if err != nil { + t.Fatalf("unexpeted error for test %v: %v", test.name, err) + } + state := eth.SwapState(swap.State) + if state != test.finalState { + t.Fatalf("unexpeted swap state for test %v: want %s got %s", test.name, test.finalState, state) + } + } +} + +func TestRedeem(t *testing.T) { + amt := big.NewInt(1e18) + locktime := time.Second * 12 + tests := []struct { + name string + sleep time.Duration + redeemer *accounts.Account + finalState eth.SwapState + addAmt, badSecret bool + }{{ + name: "ok before locktime", + sleep: time.Second * 8, + redeemer: participantAcct, + finalState: eth.Redeemed, + addAmt: true, + }, { + name: "ok after locktime", + sleep: time.Second * 16, + redeemer: participantAcct, + finalState: eth.Redeemed, + addAmt: true, + }, { + name: "bad secret", + sleep: time.Second * 8, + redeemer: participantAcct, + finalState: eth.Initiated, + badSecret: true, + }, { + name: "wrong redeemer", + sleep: time.Second * 8, + finalState: eth.Initiated, + redeemer: simnetAcct, + }} + + for _, test := range tests { + err := ethClient.unlock(ctx, pw, simnetAcct) + if err != nil { + t.Fatal(err) + } + err = ethClient.unlock(ctx, pw, participantAcct) + if err != nil { + t.Fatal(err) + } + txOpts := newTXOpts(ctx, simnetAddr, amt) + var secret [32]byte + copy(secret[:], encode.RandomBytes(32)) + secretHash := sha256.Sum256(secret[:]) + + swap, err := ethClient.swap(ctx, simnetAcct, secretHash) + if err != nil { + t.Fatal("unable to get swap state") + } + state := eth.SwapState(swap.State) + if state != eth.None { + t.Fatalf("unexpeted swap state for test %v: want %s got %s", test.name, eth.None, state) + } + + // Create a secret that doesn't has to secredHash. + if test.badSecret { + copy(secret[:], encode.RandomBytes(32)) + } + + inLocktime := time.Now().Add(locktime).Unix() + _, err = ethClient.initiate(txOpts, simnetID, inLocktime, secretHash, participantAcct.Address) + if err != nil { + t.Fatalf("unable to initiate swap for test %v: %v ", test.name, err) + } + + // This waitForMined will always take test.sleep to complete. + if err := waitForMined(t, test.sleep, true); err != nil { + t.Fatalf("unexpeted error for test %v: %v", test.name, err) + } + originalBal, err := ethClient.balance(ctx, test.redeemer) + if err != nil { + t.Fatalf("unexpeted error for test %v: %v", test.name, err) + } + txOpts = newTXOpts(ctx, test.redeemer.Address, nil) + tx, err := ethClient.redeem(txOpts, simnetID, secret, secretHash) + if err != nil { + t.Fatalf("unexpeted error for test %v: %v", test.name, err) + } + spew.Dump(tx) + + if err := waitForMined(t, time.Second*10, false); err != nil { + t.Fatalf("unexpeted error for test %v: %v", test.name, err) + } + + // It appears the receipt is only accessable after the tx is mined. + receipt, err := ethClient.transactionReceipt(ctx, tx.Hash()) + if err != nil { + t.Fatalf("unexpeted error for test %v: %v", test.name, err) + } + spew.Dump(receipt) + + // Balance should increase or decrease by a certain amount + // depending on whether redeem completed successfully on-chain. + // If unsuccessful the fee is subtracted. If successful, amt is + // added. + bal, err := ethClient.balance(ctx, test.redeemer) + if err != nil { + t.Fatalf("unexpeted error for test %v: %v", test.name, err) + } + txFee := big.NewInt(0).Mul(big.NewInt(int64(receipt.GasUsed)), gasPrice) + wantBal := big.NewInt(0).Sub(originalBal, txFee) + if test.addAmt { + wantBal.Add(wantBal, amt) + } + if bal.Cmp(wantBal) != 0 { + t.Fatalf("unexpeted balance change for test %v: want %v got %v", test.name, wantBal, bal) + } + + swap, err = ethClient.swap(ctx, simnetAcct, secretHash) + if err != nil { + t.Fatalf("unexpeted error for test %v: %v", test.name, err) + } + state = eth.SwapState(swap.State) + if state != test.finalState { + t.Fatalf("unexpeted swap state for test %v: want %s got %s", test.name, test.finalState, state) + } + } +} + +func TestRefund(t *testing.T) { + amt := big.NewInt(1e18) + locktime := time.Second * 12 + tests := []struct { + name string + sleep time.Duration + refunder *accounts.Account + finalState eth.SwapState + addAmt, redeem bool + }{{ + name: "ok", + sleep: time.Second * 16, + refunder: simnetAcct, + addAmt: true, + finalState: eth.Refunded, + }, { + name: "before locktime", + sleep: time.Second * 8, + refunder: simnetAcct, + finalState: eth.Initiated, + }, { + name: "wrong refunder", + sleep: time.Second * 16, + refunder: participantAcct, + finalState: eth.Initiated, + }, { + name: "already redeemed", + sleep: time.Second * 16, + refunder: simnetAcct, + redeem: true, + finalState: eth.Redeemed, + }} + + for _, test := range tests { + err := ethClient.unlock(ctx, pw, simnetAcct) + if err != nil { + t.Fatal(err) + } + err = ethClient.unlock(ctx, pw, participantAcct) + if err != nil { + t.Fatal(err) + } + txOpts := newTXOpts(ctx, simnetAddr, amt) + var secret [32]byte + copy(secret[:], encode.RandomBytes(32)) + secretHash := sha256.Sum256(secret[:]) + + swap, err := ethClient.swap(ctx, simnetAcct, secretHash) + if err != nil { + t.Fatal("unable to get swap state") + } + state := eth.SwapState(swap.State) + if state != eth.None { + t.Fatalf("unexpeted swap state for test %v: want %s got %s", test.name, eth.None, state) + } + + inLocktime := time.Now().Add(locktime).Unix() + _, err = ethClient.initiate(txOpts, simnetID, inLocktime, secretHash, participantAcct.Address) + if err != nil { + t.Fatalf("unable to initiate swap for test %v: %v ", test.name, err) + } + + if test.redeem { + if err := waitForMined(t, time.Second*8, false); err != nil { + t.Fatalf("unexpeted error for test %v: %v", test.name, err) + } + txOpts = newTXOpts(ctx, participantAddr, nil) + _, err := ethClient.redeem(txOpts, simnetID, secret, secretHash) + if err != nil { + t.Fatalf("unexpeted error for test %v: %v", test.name, err) + } + } + + // This waitForMined will always take test.sleep to complete. + if err := waitForMined(t, test.sleep, true); err != nil { + t.Fatalf("unexpeted error for test %v: %v", test.name, err) + } + + originalBal, err := ethClient.balance(ctx, test.refunder) + if err != nil { + t.Fatalf("unexpeted error for test %v: %v", test.name, err) + } + + txOpts = newTXOpts(ctx, test.refunder.Address, nil) + tx, err := ethClient.refund(txOpts, simnetID, secretHash) + if err != nil { + t.Fatalf("unexpeted error for test %v: %v", test.name, err) + } + spew.Dump(tx) + + if err := waitForMined(t, time.Second*10, false); err != nil { + t.Fatalf("unexpeted error for test %v: %v", test.name, err) + } + + // It appears the receipt is only accessable after the tx is mined. + receipt, err := ethClient.transactionReceipt(ctx, tx.Hash()) + if err != nil { + t.Fatalf("unexpeted error for test %v: %v", test.name, err) + } + spew.Dump(receipt) + + // Balance should increase or decrease by a certain amount + // depending on whether redeem completed successfully on-chain. + // If unsuccessful the fee is subtracted. If successful, amt is + // added. + bal, err := ethClient.balance(ctx, test.refunder) + if err != nil { + t.Fatalf("unexpeted error for test %v: %v", test.name, err) + } + txFee := big.NewInt(0).Mul(big.NewInt(int64(receipt.GasUsed)), gasPrice) + wantBal := big.NewInt(0).Sub(originalBal, txFee) + if test.addAmt { + wantBal.Add(wantBal, amt) + } + if bal.Cmp(wantBal) != 0 { + t.Fatalf("unexpeted balance change for test %v: want %v got %v", test.name, wantBal, bal) + } + + swap, err = ethClient.swap(ctx, simnetAcct, secretHash) + if err != nil { + t.Fatalf("unexpeted error for test %v: %v", test.name, err) + } + state = eth.SwapState(swap.State) + if state != test.finalState { + t.Fatalf("unexpeted swap state for test %v: want %s got %s", test.name, test.finalState, state) + } + } +} diff --git a/dex/networks/eth/contract.go b/dex/networks/eth/contract.go new file mode 100644 index 0000000000..d31258dd96 --- /dev/null +++ b/dex/networks/eth/contract.go @@ -0,0 +1,379 @@ +// Code generated - DO NOT EDIT. +// This file is a generated binding and any manual changes will be lost. + +package eth + +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 +) + +// ETHSwapSwap is an auto generated low-level Go binding around an user-defined struct. +type ETHSwapSwap struct { + InitBlockNumber *big.Int + RefundBlockTimestamp *big.Int + SecretHash [32]byte + Secret [32]byte + Initiator common.Address + Participant common.Address + Value *big.Int + State uint8 +} + +// ETHSwapABI is the input ABI used to generate the binding from. +const ETHSwapABI = "[{\"inputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"refundTimestamp\",\"type\":\"uint256\"},{\"internalType\":\"bytes32\",\"name\":\"secretHash\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"participant\",\"type\":\"address\"}],\"name\":\"initiate\",\"outputs\":[],\"stateMutability\":\"payable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"secret\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"secretHash\",\"type\":\"bytes32\"}],\"name\":\"redeem\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"secretHash\",\"type\":\"bytes32\"}],\"name\":\"refund\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"secretHash\",\"type\":\"bytes32\"}],\"name\":\"swap\",\"outputs\":[{\"components\":[{\"internalType\":\"uint256\",\"name\":\"initBlockNumber\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"refundBlockTimestamp\",\"type\":\"uint256\"},{\"internalType\":\"bytes32\",\"name\":\"secretHash\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"secret\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"initiator\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"participant\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"value\",\"type\":\"uint256\"},{\"internalType\":\"enumETHSwap.State\",\"name\":\"state\",\"type\":\"uint8\"}],\"internalType\":\"structETHSwap.Swap\",\"name\":\"\",\"type\":\"tuple\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"name\":\"swaps\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"initBlockNumber\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"refundBlockTimestamp\",\"type\":\"uint256\"},{\"internalType\":\"bytes32\",\"name\":\"secretHash\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"secret\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"initiator\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"participant\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"value\",\"type\":\"uint256\"},{\"internalType\":\"enumETHSwap.State\",\"name\":\"state\",\"type\":\"uint8\"}],\"stateMutability\":\"view\",\"type\":\"function\"}]" + +// ETHSwapFuncSigs maps the 4-byte function signature to its string representation. +var ETHSwapFuncSigs = map[string]string{ + "ae052147": "initiate(uint256,bytes32,address)", + "b31597ad": "redeem(bytes32,bytes32)", + "7249fbb6": "refund(bytes32)", + "76467cbd": "swap(bytes32)", + "eb84e7f2": "swaps(bytes32)", +} + +// ETHSwapBin is the compiled bytecode used for deploying new contracts. +var ETHSwapBin = "0x608060405234801561001057600080fd5b5061071e806100206000396000f3fe60806040526004361061004a5760003560e01c80637249fbb61461004f57806376467cbd14610071578063ae052147146100a7578063b31597ad146100ba578063eb84e7f2146100da575b600080fd5b34801561005b57600080fd5b5061006f61006a366004610517565b61015c565b005b34801561007d57600080fd5b5061009161008c366004610517565b610237565b60405161009e919061060d565b60405180910390f35b61006f6100b536600461056b565b610322565b3480156100c657600080fd5b5061006f6100d5366004610549565b6103db565b3480156100e657600080fd5b506101486100f5366004610517565b6000602081905290815260409020805460018201546002830154600384015460048501546005860154600687015460079097015495969495939492936001600160a01b0392831693919092169160ff1688565b60405161009e98979695949392919061067d565b8033600160008381526020819052604090206007015460ff166003811115610186576101866106d2565b1461019057600080fd5b6000828152602081905260409020600401546001600160a01b038281169116146101b957600080fd5b600082815260208190526040902060010154428111156101d857600080fd5b600084815260208190526040808220600601549051339282156108fc02929190818181858888f19350505050158015610215573d6000803e3d6000fd5b505050600091825250602081905260409020600701805460ff19166003179055565b61027c6040805161010081018252600080825260208201819052918101829052606081018290526080810182905260a0810182905260c081018290529060e082015290565b6000828152602081815260409182902082516101008101845281548152600182015492810192909252600281015492820192909252600380830154606083015260048301546001600160a01b03908116608084015260058401541660a0830152600683015460c0830152600783015491929160e084019160ff90911690811115610308576103086106d2565b6003811115610319576103196106d2565b90525092915050565b826000341161033057600080fd5b6000811161033d57600080fd5b826000808281526020819052604090206007015460ff166003811115610365576103656106d2565b1461036f57600080fd5b6000848152602081905260409020438155600180820187905560028201869055600482018054336001600160a01b0319918216179091556005830180549091166001600160a01b0387161790553460068301556007909101805460ff1916828002179055505050505050565b808233600160008481526020819052604090206007015460ff166003811115610406576104066106d2565b1461041057600080fd5b6000838152602081905260409020600501546001600160a01b0382811691161461043957600080fd5b8260028360405160200161044f91815260200190565b60408051601f1981840301815290829052610469916105d2565b602060405180830381855afa158015610486573d6000803e3d6000fd5b5050506040513d601f19601f820116820180604052508101906104a99190610530565b146104b357600080fd5b600084815260208190526040808220600601549051339282156108fc02929190818181858888f193505050501580156104f0573d6000803e3d6000fd5b50505060009182525060208190526040902060078101805460ff1916600217905560030155565b60006020828403121561052957600080fd5b5035919050565b60006020828403121561054257600080fd5b5051919050565b6000806040838503121561055c57600080fd5b50508035926020909101359150565b60008060006060848603121561058057600080fd5b833592506020840135915060408401356001600160a01b03811681146105a557600080fd5b809150509250925092565b600481106105ce57634e487b7160e01b600052602160045260246000fd5b9052565b6000825160005b818110156105f357602081860181015185830152016105d9565b81811115610602576000828501525b509190910192915050565b60006101008201905082518252602083015160208301526040830151604083015260608301516060830152608083015160018060a01b0380821660808501528060a08601511660a0850152505060c083015160c083015260e083015161067660e08401826105b0565b5092915050565b8881526020810188905260408101879052606081018690526001600160a01b038581166080830152841660a082015260c0810183905261010081016106c560e08301846105b0565b9998505050505050505050565b634e487b7160e01b600052602160045260246000fdfea2646970667358221220bf264bafbf3c64ff7138804a752d4591d22b4d776201ed7117afc2f282bf000d64736f6c63430008060033" + +// DeployETHSwap deploys a new Ethereum contract, binding an instance of ETHSwap to it. +func DeployETHSwap(auth *bind.TransactOpts, backend bind.ContractBackend) (common.Address, *types.Transaction, *ETHSwap, error) { + parsed, err := abi.JSON(strings.NewReader(ETHSwapABI)) + if err != nil { + return common.Address{}, nil, nil, err + } + + address, tx, contract, err := bind.DeployContract(auth, parsed, common.FromHex(ETHSwapBin), backend) + if err != nil { + return common.Address{}, nil, nil, err + } + return address, tx, ÐSwap{ETHSwapCaller: ETHSwapCaller{contract: contract}, ETHSwapTransactor: ETHSwapTransactor{contract: contract}, ETHSwapFilterer: ETHSwapFilterer{contract: contract}}, nil +} + +// ETHSwap is an auto generated Go binding around an Ethereum contract. +type ETHSwap struct { + ETHSwapCaller // Read-only binding to the contract + ETHSwapTransactor // Write-only binding to the contract + ETHSwapFilterer // Log filterer for contract events +} + +// ETHSwapCaller is an auto generated read-only Go binding around an Ethereum contract. +type ETHSwapCaller struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// ETHSwapTransactor is an auto generated write-only Go binding around an Ethereum contract. +type ETHSwapTransactor struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// ETHSwapFilterer is an auto generated log filtering Go binding around an Ethereum contract events. +type ETHSwapFilterer struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// ETHSwapSession is an auto generated Go binding around an Ethereum contract, +// with pre-set call and transact options. +type ETHSwapSession struct { + Contract *ETHSwap // 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 +} + +// ETHSwapCallerSession is an auto generated read-only Go binding around an Ethereum contract, +// with pre-set call options. +type ETHSwapCallerSession struct { + Contract *ETHSwapCaller // Generic contract caller binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session +} + +// ETHSwapTransactorSession is an auto generated write-only Go binding around an Ethereum contract, +// with pre-set transact options. +type ETHSwapTransactorSession struct { + Contract *ETHSwapTransactor // Generic contract transactor binding to set the session for + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session +} + +// ETHSwapRaw is an auto generated low-level Go binding around an Ethereum contract. +type ETHSwapRaw struct { + Contract *ETHSwap // Generic contract binding to access the raw methods on +} + +// ETHSwapCallerRaw is an auto generated low-level read-only Go binding around an Ethereum contract. +type ETHSwapCallerRaw struct { + Contract *ETHSwapCaller // Generic read-only contract binding to access the raw methods on +} + +// ETHSwapTransactorRaw is an auto generated low-level write-only Go binding around an Ethereum contract. +type ETHSwapTransactorRaw struct { + Contract *ETHSwapTransactor // Generic write-only contract binding to access the raw methods on +} + +// NewETHSwap creates a new instance of ETHSwap, bound to a specific deployed contract. +func NewETHSwap(address common.Address, backend bind.ContractBackend) (*ETHSwap, error) { + contract, err := bindETHSwap(address, backend, backend, backend) + if err != nil { + return nil, err + } + return ÐSwap{ETHSwapCaller: ETHSwapCaller{contract: contract}, ETHSwapTransactor: ETHSwapTransactor{contract: contract}, ETHSwapFilterer: ETHSwapFilterer{contract: contract}}, nil +} + +// NewETHSwapCaller creates a new read-only instance of ETHSwap, bound to a specific deployed contract. +func NewETHSwapCaller(address common.Address, caller bind.ContractCaller) (*ETHSwapCaller, error) { + contract, err := bindETHSwap(address, caller, nil, nil) + if err != nil { + return nil, err + } + return ÐSwapCaller{contract: contract}, nil +} + +// NewETHSwapTransactor creates a new write-only instance of ETHSwap, bound to a specific deployed contract. +func NewETHSwapTransactor(address common.Address, transactor bind.ContractTransactor) (*ETHSwapTransactor, error) { + contract, err := bindETHSwap(address, nil, transactor, nil) + if err != nil { + return nil, err + } + return ÐSwapTransactor{contract: contract}, nil +} + +// NewETHSwapFilterer creates a new log filterer instance of ETHSwap, bound to a specific deployed contract. +func NewETHSwapFilterer(address common.Address, filterer bind.ContractFilterer) (*ETHSwapFilterer, error) { + contract, err := bindETHSwap(address, nil, nil, filterer) + if err != nil { + return nil, err + } + return ÐSwapFilterer{contract: contract}, nil +} + +// bindETHSwap binds a generic wrapper to an already deployed contract. +func bindETHSwap(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor, filterer bind.ContractFilterer) (*bind.BoundContract, error) { + parsed, err := abi.JSON(strings.NewReader(ETHSwapABI)) + 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 (_ETHSwap *ETHSwapRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { + return _ETHSwap.Contract.ETHSwapCaller.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 (_ETHSwap *ETHSwapRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _ETHSwap.Contract.ETHSwapTransactor.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_ETHSwap *ETHSwapRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _ETHSwap.Contract.ETHSwapTransactor.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 (_ETHSwap *ETHSwapCallerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { + return _ETHSwap.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 (_ETHSwap *ETHSwapTransactorRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _ETHSwap.Contract.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_ETHSwap *ETHSwapTransactorRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _ETHSwap.Contract.contract.Transact(opts, method, params...) +} + +// Swap is a free data retrieval call binding the contract method 0x76467cbd. +// +// Solidity: function swap(bytes32 secretHash) view returns((uint256,uint256,bytes32,bytes32,address,address,uint256,uint8)) +func (_ETHSwap *ETHSwapCaller) Swap(opts *bind.CallOpts, secretHash [32]byte) (ETHSwapSwap, error) { + var out []interface{} + err := _ETHSwap.contract.Call(opts, &out, "swap", secretHash) + + if err != nil { + return *new(ETHSwapSwap), err + } + + out0 := *abi.ConvertType(out[0], new(ETHSwapSwap)).(*ETHSwapSwap) + + return out0, err + +} + +// Swap is a free data retrieval call binding the contract method 0x76467cbd. +// +// Solidity: function swap(bytes32 secretHash) view returns((uint256,uint256,bytes32,bytes32,address,address,uint256,uint8)) +func (_ETHSwap *ETHSwapSession) Swap(secretHash [32]byte) (ETHSwapSwap, error) { + return _ETHSwap.Contract.Swap(&_ETHSwap.CallOpts, secretHash) +} + +// Swap is a free data retrieval call binding the contract method 0x76467cbd. +// +// Solidity: function swap(bytes32 secretHash) view returns((uint256,uint256,bytes32,bytes32,address,address,uint256,uint8)) +func (_ETHSwap *ETHSwapCallerSession) Swap(secretHash [32]byte) (ETHSwapSwap, error) { + return _ETHSwap.Contract.Swap(&_ETHSwap.CallOpts, secretHash) +} + +// Swaps is a free data retrieval call binding the contract method 0xeb84e7f2. +// +// Solidity: function swaps(bytes32 ) view returns(uint256 initBlockNumber, uint256 refundBlockTimestamp, bytes32 secretHash, bytes32 secret, address initiator, address participant, uint256 value, uint8 state) +func (_ETHSwap *ETHSwapCaller) Swaps(opts *bind.CallOpts, arg0 [32]byte) (struct { + InitBlockNumber *big.Int + RefundBlockTimestamp *big.Int + SecretHash [32]byte + Secret [32]byte + Initiator common.Address + Participant common.Address + Value *big.Int + State uint8 +}, error) { + var out []interface{} + err := _ETHSwap.contract.Call(opts, &out, "swaps", arg0) + + outstruct := new(struct { + InitBlockNumber *big.Int + RefundBlockTimestamp *big.Int + SecretHash [32]byte + Secret [32]byte + Initiator common.Address + Participant common.Address + Value *big.Int + State uint8 + }) + if err != nil { + return *outstruct, err + } + + outstruct.InitBlockNumber = *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) + outstruct.RefundBlockTimestamp = *abi.ConvertType(out[1], new(*big.Int)).(**big.Int) + outstruct.SecretHash = *abi.ConvertType(out[2], new([32]byte)).(*[32]byte) + outstruct.Secret = *abi.ConvertType(out[3], new([32]byte)).(*[32]byte) + outstruct.Initiator = *abi.ConvertType(out[4], new(common.Address)).(*common.Address) + outstruct.Participant = *abi.ConvertType(out[5], new(common.Address)).(*common.Address) + outstruct.Value = *abi.ConvertType(out[6], new(*big.Int)).(**big.Int) + outstruct.State = *abi.ConvertType(out[7], new(uint8)).(*uint8) + + return *outstruct, err + +} + +// Swaps is a free data retrieval call binding the contract method 0xeb84e7f2. +// +// Solidity: function swaps(bytes32 ) view returns(uint256 initBlockNumber, uint256 refundBlockTimestamp, bytes32 secretHash, bytes32 secret, address initiator, address participant, uint256 value, uint8 state) +func (_ETHSwap *ETHSwapSession) Swaps(arg0 [32]byte) (struct { + InitBlockNumber *big.Int + RefundBlockTimestamp *big.Int + SecretHash [32]byte + Secret [32]byte + Initiator common.Address + Participant common.Address + Value *big.Int + State uint8 +}, error) { + return _ETHSwap.Contract.Swaps(&_ETHSwap.CallOpts, arg0) +} + +// Swaps is a free data retrieval call binding the contract method 0xeb84e7f2. +// +// Solidity: function swaps(bytes32 ) view returns(uint256 initBlockNumber, uint256 refundBlockTimestamp, bytes32 secretHash, bytes32 secret, address initiator, address participant, uint256 value, uint8 state) +func (_ETHSwap *ETHSwapCallerSession) Swaps(arg0 [32]byte) (struct { + InitBlockNumber *big.Int + RefundBlockTimestamp *big.Int + SecretHash [32]byte + Secret [32]byte + Initiator common.Address + Participant common.Address + Value *big.Int + State uint8 +}, error) { + return _ETHSwap.Contract.Swaps(&_ETHSwap.CallOpts, arg0) +} + +// Initiate is a paid mutator transaction binding the contract method 0xae052147. +// +// Solidity: function initiate(uint256 refundTimestamp, bytes32 secretHash, address participant) payable returns() +func (_ETHSwap *ETHSwapTransactor) Initiate(opts *bind.TransactOpts, refundTimestamp *big.Int, secretHash [32]byte, participant common.Address) (*types.Transaction, error) { + return _ETHSwap.contract.Transact(opts, "initiate", refundTimestamp, secretHash, participant) +} + +// Initiate is a paid mutator transaction binding the contract method 0xae052147. +// +// Solidity: function initiate(uint256 refundTimestamp, bytes32 secretHash, address participant) payable returns() +func (_ETHSwap *ETHSwapSession) Initiate(refundTimestamp *big.Int, secretHash [32]byte, participant common.Address) (*types.Transaction, error) { + return _ETHSwap.Contract.Initiate(&_ETHSwap.TransactOpts, refundTimestamp, secretHash, participant) +} + +// Initiate is a paid mutator transaction binding the contract method 0xae052147. +// +// Solidity: function initiate(uint256 refundTimestamp, bytes32 secretHash, address participant) payable returns() +func (_ETHSwap *ETHSwapTransactorSession) Initiate(refundTimestamp *big.Int, secretHash [32]byte, participant common.Address) (*types.Transaction, error) { + return _ETHSwap.Contract.Initiate(&_ETHSwap.TransactOpts, refundTimestamp, secretHash, participant) +} + +// Redeem is a paid mutator transaction binding the contract method 0xb31597ad. +// +// Solidity: function redeem(bytes32 secret, bytes32 secretHash) returns() +func (_ETHSwap *ETHSwapTransactor) Redeem(opts *bind.TransactOpts, secret [32]byte, secretHash [32]byte) (*types.Transaction, error) { + return _ETHSwap.contract.Transact(opts, "redeem", secret, secretHash) +} + +// Redeem is a paid mutator transaction binding the contract method 0xb31597ad. +// +// Solidity: function redeem(bytes32 secret, bytes32 secretHash) returns() +func (_ETHSwap *ETHSwapSession) Redeem(secret [32]byte, secretHash [32]byte) (*types.Transaction, error) { + return _ETHSwap.Contract.Redeem(&_ETHSwap.TransactOpts, secret, secretHash) +} + +// Redeem is a paid mutator transaction binding the contract method 0xb31597ad. +// +// Solidity: function redeem(bytes32 secret, bytes32 secretHash) returns() +func (_ETHSwap *ETHSwapTransactorSession) Redeem(secret [32]byte, secretHash [32]byte) (*types.Transaction, error) { + return _ETHSwap.Contract.Redeem(&_ETHSwap.TransactOpts, secret, secretHash) +} + +// Refund is a paid mutator transaction binding the contract method 0x7249fbb6. +// +// Solidity: function refund(bytes32 secretHash) returns() +func (_ETHSwap *ETHSwapTransactor) Refund(opts *bind.TransactOpts, secretHash [32]byte) (*types.Transaction, error) { + return _ETHSwap.contract.Transact(opts, "refund", secretHash) +} + +// Refund is a paid mutator transaction binding the contract method 0x7249fbb6. +// +// Solidity: function refund(bytes32 secretHash) returns() +func (_ETHSwap *ETHSwapSession) Refund(secretHash [32]byte) (*types.Transaction, error) { + return _ETHSwap.Contract.Refund(&_ETHSwap.TransactOpts, secretHash) +} + +// Refund is a paid mutator transaction binding the contract method 0x7249fbb6. +// +// Solidity: function refund(bytes32 secretHash) returns() +func (_ETHSwap *ETHSwapTransactorSession) Refund(secretHash [32]byte) (*types.Transaction, error) { + return _ETHSwap.Contract.Refund(&_ETHSwap.TransactOpts, secretHash) +} diff --git a/dex/networks/eth/contracts/ETHSwapV0.sol b/dex/networks/eth/contracts/ETHSwapV0.sol new file mode 100644 index 0000000000..5e03179f4d --- /dev/null +++ b/dex/networks/eth/contracts/ETHSwapV0.sol @@ -0,0 +1,84 @@ +// SPDX-License-Identifier: BlueOak-1.0.0 +pragma solidity >=0.7.0 <0.9.0; +contract ETHSwap { + enum State { Empty, Filled, Redeemed, Refunded } + + struct Swap { + uint initBlockNumber; + uint refundBlockTimestamp; + bytes32 secretHash; + bytes32 secret; + address initiator; + address participant; + uint256 value; + State state; + } + + mapping(bytes32 => Swap) public swaps; + + constructor() {} + + modifier isRefundable(bytes32 secretHash, address refunder) { + require(swaps[secretHash].state == State.Filled); + require(swaps[secretHash].initiator == refunder); + uint refundBlockTimestamp = swaps[secretHash].refundBlockTimestamp; + require(block.timestamp >= refundBlockTimestamp); + _; + } + + modifier isRedeemable(bytes32 secretHash, bytes32 secret, address redeemer) { + require(swaps[secretHash].state == State.Filled); + require(swaps[secretHash].participant == redeemer); + require(sha256(abi.encodePacked(secret)) == secretHash); + _; + } + + modifier isNotInitiated(bytes32 secretHash) { + require(swaps[secretHash].state == State.Empty); + _; + } + + modifier hasNoNilValues(uint refundTime) { + require(msg.value > 0); + require(refundTime > 0); + _; + } + + function swap(bytes32 secretHash) + public view returns(Swap memory) + { + return swaps[secretHash]; + } + + function initiate(uint refundTimestamp, bytes32 secretHash, address participant) + public + payable + hasNoNilValues(refundTimestamp) + isNotInitiated(secretHash) + { + swaps[secretHash].initBlockNumber = block.number; + swaps[secretHash].refundBlockTimestamp = refundTimestamp; + swaps[secretHash].secretHash = secretHash; + swaps[secretHash].initiator = msg.sender; + swaps[secretHash].participant = participant; + swaps[secretHash].value = msg.value; + swaps[secretHash].state = State.Filled; + } + + function redeem(bytes32 secret, bytes32 secretHash) + public + isRedeemable(secretHash, secret, msg.sender) + { + payable(msg.sender).transfer(swaps[secretHash].value); + swaps[secretHash].state = State.Redeemed; + swaps[secretHash].secret = secret; + } + + function refund(bytes32 secretHash) + public + isRefundable(secretHash, msg.sender) + { + payable(msg.sender).transfer(swaps[secretHash].value); + swaps[secretHash].state = State.Refunded; + } +} diff --git a/dex/networks/eth/contracts/README.md b/dex/networks/eth/contracts/README.md new file mode 100644 index 0000000000..25882d57e3 --- /dev/null +++ b/dex/networks/eth/contracts/README.md @@ -0,0 +1,13 @@ +## Eth Contract Creation + +Have `solc` and `abigen` installed on your system and run from this directory: + +`abigen --sol ETHSwapV{version}.sol --pkg eth --out ../contract.go` + +## History + +### V0 + +mainnet addres is `to be determined` + +ETHSwapV0.sol is the first interation of the the eth swap smart contract. diff --git a/dex/testing/eth/harness.sh b/dex/testing/eth/harness.sh index 8b6c90ae6a..1787aeae2e 100755 --- a/dex/testing/eth/harness.sh +++ b/dex/testing/eth/harness.sh @@ -41,6 +41,8 @@ DELTA_NODE_KEY="725394672587b34bbf15580c59e5199c75c2c7e998ba8df3cb38cc4347d46e2b DELTA_ENODE="ca414c361d1a38716170923e4900d9dc9203dbaf8fdcaee73e1f861df9fdf20a1453b76fd218c18bc6f3c7e13cbca0b3416af02a53b8e31188faa45aab398d1c" DELTA_NODE_PORT="30307" +ETH_SWAP_V0="608060405234801561001057600080fd5b5061071e806100206000396000f3fe60806040526004361061004a5760003560e01c80637249fbb61461004f57806376467cbd14610071578063ae052147146100a7578063b31597ad146100ba578063eb84e7f2146100da575b600080fd5b34801561005b57600080fd5b5061006f61006a366004610517565b61015c565b005b34801561007d57600080fd5b5061009161008c366004610517565b610237565b60405161009e919061060d565b60405180910390f35b61006f6100b536600461056b565b610322565b3480156100c657600080fd5b5061006f6100d5366004610549565b6103db565b3480156100e657600080fd5b506101486100f5366004610517565b6000602081905290815260409020805460018201546002830154600384015460048501546005860154600687015460079097015495969495939492936001600160a01b0392831693919092169160ff1688565b60405161009e98979695949392919061067d565b8033600160008381526020819052604090206007015460ff166003811115610186576101866106d2565b1461019057600080fd5b6000828152602081905260409020600401546001600160a01b038281169116146101b957600080fd5b600082815260208190526040902060010154428111156101d857600080fd5b600084815260208190526040808220600601549051339282156108fc02929190818181858888f19350505050158015610215573d6000803e3d6000fd5b505050600091825250602081905260409020600701805460ff19166003179055565b61027c6040805161010081018252600080825260208201819052918101829052606081018290526080810182905260a0810182905260c081018290529060e082015290565b6000828152602081815260409182902082516101008101845281548152600182015492810192909252600281015492820192909252600380830154606083015260048301546001600160a01b03908116608084015260058401541660a0830152600683015460c0830152600783015491929160e084019160ff90911690811115610308576103086106d2565b6003811115610319576103196106d2565b90525092915050565b826000341161033057600080fd5b6000811161033d57600080fd5b826000808281526020819052604090206007015460ff166003811115610365576103656106d2565b1461036f57600080fd5b6000848152602081905260409020438155600180820187905560028201869055600482018054336001600160a01b0319918216179091556005830180549091166001600160a01b0387161790553460068301556007909101805460ff1916828002179055505050505050565b808233600160008481526020819052604090206007015460ff166003811115610406576104066106d2565b1461041057600080fd5b6000838152602081905260409020600501546001600160a01b0382811691161461043957600080fd5b8260028360405160200161044f91815260200190565b60408051601f1981840301815290829052610469916105d2565b602060405180830381855afa158015610486573d6000803e3d6000fd5b5050506040513d601f19601f820116820180604052508101906104a99190610530565b146104b357600080fd5b600084815260208190526040808220600601549051339282156108fc02929190818181858888f193505050501580156104f0573d6000803e3d6000fd5b50505060009182525060208190526040902060078101805460ff1916600217905560030155565b60006020828403121561052957600080fd5b5035919050565b60006020828403121561054257600080fd5b5051919050565b6000806040838503121561055c57600080fd5b50508035926020909101359150565b60008060006060848603121561058057600080fd5b833592506020840135915060408401356001600160a01b03811681146105a557600080fd5b809150509250925092565b600481106105ce57634e487b7160e01b600052602160045260246000fd5b9052565b6000825160005b818110156105f357602081860181015185830152016105d9565b81811115610602576000828501525b509190910192915050565b60006101008201905082518252602083015160208301526040830151604083015260608301516060830152608083015160018060a01b0380821660808501528060a08601511660a0850152505060c083015160c083015260e083015161067660e08401826105b0565b5092915050565b8881526020810188905260408101879052606081018690526001600160a01b038581166080830152841660a082015260c0810183905261010081016106c560e08301846105b0565b9998505050505050505050565b634e487b7160e01b600052602160045260246000fdfea2646970667358221220bf264bafbf3c64ff7138804a752d4591d22b4d776201ed7117afc2f282bf000d64736f6c63430008060033" + # PASSWORD is the password used to unlock all accounts/wallets/addresses. PASSWORD="abc" @@ -114,6 +116,20 @@ function send(from, to, amt) { } EOF +cat > "${NODES_ROOT}/harness-ctl/deploy.js" < "${NODES_ROOT}/harness-ctl/contractAddress.js" < "${NODES_ROOT}/contract_addr.txt" <