From 50ac869ff1c1b74a48ae03dfbd50f928b601613f Mon Sep 17 00:00:00 2001 From: Brian Stafford Date: Sun, 11 Dec 2022 20:50:23 -0600 Subject: [PATCH] eth contract v1 Implements the version 1 contracts for ethereum and tokens. Based on feedback in #1426, everything is now encoded in the "contract data". This "contract data", is the msgjson.Init.Contract -> msgjson.Audit.Contract -> MatchMetaData.Proof.CounterContract, AuditInfo.Contract -> Redemption.Spends.Contract. A few new terms are introduced to differentiate various encodings and data sets. The aforementioned contract data did encode a version and a secret hash. It now encodes a version and a "locator", which is a []byte whose length and content depend on the version. For version 0, the locator is still just the secretHash[:]. For v1, the locator encodes all of the immutable data that defines the swap. This immutable data is now collected in something called a "vector" (dexeth.SwapVector). For version 0, some vector data is stored on-chain indexed by the secret hash. For version 1, all vector data is encoded in the locator. I've also made an effort to standardize the use of status/step, and eliminated the use of ambiguous "ver" variables throughout. A "status" is now the collection of mutable contract data: the step, the init block height, and the secret. The status and vector collectively fully characterize the swap. client/asset/eth: New contractV1 and tokenContractorV1 interfaces. To avoid duplication, the ERC20 parts of the tokenContractors are separated into a new type erc20Contractor that is embedded by both versions. Getters for status and vector are added in place of the old method "swap". assetWallet and embedding types are updated to work with the new version-dependent locators and the status and vector model. dex/networks/{eth,erc20}: New contracts added. New methods for dealing with locators. Simnet entries added for eth and dextt.eth in the ContractAddresses and Tokens maps. txDataHandler interace is replaced with versioned package-level functions. server/asset/eth: Server is fully switched to version 1. No option to use version 0. Translation to new version was straightforward, with one notable difference that we can no longer get a block height from the contract once the swap is redeemed. --- client/asset/eth/contractor.go | 633 +++++++++++-- client/asset/eth/contractor_test.go | 14 +- client/asset/eth/eth.go | 633 +++++++++---- client/asset/eth/eth_test.go | 867 +++++++++--------- client/asset/eth/nodeclient.go | 13 +- client/asset/eth/nodeclient_harness_test.go | 337 ++++--- dex/networks/erc20/contracts/ERC20SwapV0.sol | 2 +- dex/networks/erc20/contracts/ERC20SwapV1.sol | 241 +++++ .../erc20/contracts/updatecontract.sh | 13 + .../erc20/contracts/v0/BinRuntimeV0.go | 2 +- dex/networks/erc20/contracts/v0/contract.go | 2 +- .../erc20/contracts/v1/BinRuntimeV1.go | 6 + dex/networks/erc20/contracts/v1/contract.go | 452 +++++++++ dex/networks/eth/contracts/ETHSwapV1.sol | 227 +++++ dex/networks/eth/contracts/updatecontract.sh | 1 + dex/networks/eth/contracts/v1/BinRuntimeV1.go | 6 + dex/networks/eth/contracts/v1/contract.go | 442 +++++++++ dex/networks/eth/params.go | 128 ++- dex/networks/eth/params_test.go | 2 +- dex/networks/eth/tokens.go | 48 +- dex/networks/eth/txdata.go | 249 +++-- dex/networks/eth/txdata_test.go | 6 +- dex/testing/eth/create-node.sh | 2 +- dex/testing/eth/harness.sh | 31 +- server/asset/eth/coiner.go | 126 ++- server/asset/eth/coiner_test.go | 114 +-- server/asset/eth/eth.go | 68 +- server/asset/eth/eth_test.go | 227 +++-- server/asset/eth/rpcclient.go | 25 +- server/asset/eth/rpcclient_harness_test.go | 10 +- server/asset/eth/tokener.go | 46 +- 31 files changed, 3735 insertions(+), 1238 deletions(-) create mode 100644 dex/networks/erc20/contracts/ERC20SwapV1.sol create mode 100644 dex/networks/erc20/contracts/v1/BinRuntimeV1.go create mode 100644 dex/networks/erc20/contracts/v1/contract.go create mode 100644 dex/networks/eth/contracts/ETHSwapV1.sol create mode 100644 dex/networks/eth/contracts/v1/BinRuntimeV1.go create mode 100644 dex/networks/eth/contracts/v1/contract.go diff --git a/client/asset/eth/contractor.go b/client/asset/eth/contractor.go index 88f48f9b16..89d334f233 100644 --- a/client/asset/eth/contractor.go +++ b/client/asset/eth/contractor.go @@ -6,8 +6,10 @@ package eth import ( + "bytes" "context" "crypto/sha256" + "errors" "fmt" "math/big" "time" @@ -17,8 +19,10 @@ import ( "decred.org/dcrdex/dex/encode" "decred.org/dcrdex/dex/networks/erc20" erc20v0 "decred.org/dcrdex/dex/networks/erc20/contracts/v0" + erc20v1 "decred.org/dcrdex/dex/networks/erc20/contracts/v1" dexeth "decred.org/dcrdex/dex/networks/eth" swapv0 "decred.org/dcrdex/dex/networks/eth/contracts/v0" + swapv1 "decred.org/dcrdex/dex/networks/eth/contracts/v1" "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/accounts/abi/bind" @@ -30,20 +34,22 @@ import ( // The intention is that if a new contract is implemented, the contractor // interface itself will not require any updates. type contractor interface { - swap(ctx context.Context, secretHash [32]byte) (*dexeth.SwapState, error) + status(ctx context.Context, locator []byte) (*dexeth.SwapStatus, error) + vector(ctx context.Context, locator []byte) (*dexeth.SwapVector, error) + statusAndVector(ctx context.Context, locator []byte) (*dexeth.SwapStatus, *dexeth.SwapVector, error) initiate(*bind.TransactOpts, []*asset.Contract) (*types.Transaction, error) redeem(txOpts *bind.TransactOpts, redeems []*asset.Redemption) (*types.Transaction, error) - refund(opts *bind.TransactOpts, secretHash [32]byte) (*types.Transaction, error) + refund(opts *bind.TransactOpts, locator []byte) (*types.Transaction, error) estimateInitGas(ctx context.Context, n int) (uint64, error) - estimateRedeemGas(ctx context.Context, secrets [][32]byte) (uint64, error) - estimateRefundGas(ctx context.Context, secretHash [32]byte) (uint64, error) - isRedeemable(secretHash, secret [32]byte) (bool, error) + estimateRedeemGas(ctx context.Context, secrets [][32]byte, locators [][]byte) (uint64, error) + estimateRefundGas(ctx context.Context, locator []byte) (uint64, error) + isRedeemable(locator []byte, secret [32]byte) (bool, error) // value checks the incoming or outgoing contract value. This is just the // one of redeem, refund, or initiate values. It is not an error if the // transaction does not pay to the contract, and the values returned in that // case will always be zero. value(context.Context, *types.Transaction) (incoming, outgoing uint64, err error) - isRefundable(secretHash [32]byte) (bool, error) + isRefundable(locator []byte) (bool, error) voidUnusedNonce() } @@ -74,6 +80,21 @@ type contractV0 interface { IsRefundable(opts *bind.CallOpts, secretHash [32]byte) (bool, error) } +var _ contractV0 = (*swapv0.ETHSwap)(nil) + +type contractV1 interface { + Initiate(opts *bind.TransactOpts, contracts []swapv1.ETHSwapVector) (*types.Transaction, error) + Redeem(opts *bind.TransactOpts, redemptions []swapv1.ETHSwapRedemption) (*types.Transaction, error) + Status(opts *bind.CallOpts, c swapv1.ETHSwapVector) (swapv1.ETHSwapStatus, error) + Refund(opts *bind.TransactOpts, c swapv1.ETHSwapVector) (*types.Transaction, error) + IsRedeemable(opts *bind.CallOpts, c swapv1.ETHSwapVector) (bool, error) + + ContractKey(opts *bind.CallOpts, v swapv1.ETHSwapVector) ([32]byte, error) + Swaps(opts *bind.CallOpts, arg0 [32]byte) ([32]byte, error) +} + +var _ contractV1 = (*swapv1.ETHSwap)(nil) + // contractorV0 is the contractor for contract version 0. // Redeem and Refund methods of swapv0.ETHSwap already have suitable return types. type contractorV0 struct { @@ -162,6 +183,11 @@ func (c *contractorV0) redeem(txOpts *bind.TransactOpts, redemptions []*asset.Re if secretHashes[secretHash] { return nil, fmt.Errorf("duplicate secret hash %x", secretHash[:]) } + checkHash := sha256.Sum256(secretB) + if checkHash != secretHash { + return nil, errors.New("wrong secret") + } + secretHashes[secretHash] = true redemps = append(redemps, swapv0.ETHSwapRedemption{ @@ -172,7 +198,73 @@ func (c *contractorV0) redeem(txOpts *bind.TransactOpts, redemptions []*asset.Re return c.contractV0.Redeem(txOpts, redemps) } -// swap retrieves the swap info from the read-only swap method. +// status fetches the SwapStatus, which specifies the current state of mutable +// swap data. +func (c *contractorV0) status(ctx context.Context, locator []byte) (*dexeth.SwapStatus, error) { + secretHash, err := dexeth.ParseV0Locator(locator) + if err != nil { + return nil, err + } + swap, err := c.swap(ctx, secretHash) + if err != nil { + return nil, err + } + status := &dexeth.SwapStatus{ + Step: swap.State, + Secret: swap.Secret, + BlockHeight: swap.BlockHeight, + } + return status, nil +} + +// vector generates a SwapVector, containing the immutable data that defines +// the swap. +func (c *contractorV0) vector(ctx context.Context, locator []byte) (*dexeth.SwapVector, error) { + secretHash, err := dexeth.ParseV0Locator(locator) + if err != nil { + return nil, err + } + swap, err := c.swap(ctx, secretHash) + if err != nil { + return nil, err + } + vector := &dexeth.SwapVector{ + From: swap.Participant, + To: swap.Initiator, + Value: dexeth.WeiToGwei(swap.Value), + SecretHash: secretHash, + LockTime: uint64(swap.LockTime.UnixMilli()), + } + return vector, nil +} + +// statusAndVector generates both the status and the vector simultaneously. For +// version 0, this is better than calling status and vector separately, since +// each makes an identical call to c.swap. +func (c *contractorV0) statusAndVector(ctx context.Context, locator []byte) (*dexeth.SwapStatus, *dexeth.SwapVector, error) { + secretHash, err := dexeth.ParseV0Locator(locator) + if err != nil { + return nil, nil, err + } + swap, err := c.swap(ctx, secretHash) + if err != nil { + return nil, nil, err + } + vector := &dexeth.SwapVector{ + From: swap.Participant, + To: swap.Initiator, + Value: dexeth.WeiToGwei(swap.Value), + SecretHash: secretHash, + LockTime: uint64(swap.LockTime.UnixMilli()), + } + status := &dexeth.SwapStatus{ + Step: swap.State, + Secret: swap.Secret, + BlockHeight: swap.BlockHeight, + } + return status, vector, nil +} + func (c *contractorV0) swap(ctx context.Context, secretHash [32]byte) (*dexeth.SwapState, error) { callOpts := &bind.CallOpts{ From: c.acctAddr, @@ -196,24 +288,48 @@ func (c *contractorV0) swap(ctx context.Context, secretHash [32]byte) (*dexeth.S // refund issues the refund command to the swap contract. Use isRefundable first // to ensure the refund will be accepted. -func (c *contractorV0) refund(txOpts *bind.TransactOpts, secretHash [32]byte) (tx *types.Transaction, err error) { +func (c *contractorV0) refund(txOpts *bind.TransactOpts, locator []byte) (tx *types.Transaction, err error) { + secretHash, err := dexeth.ParseV0Locator(locator) + if err != nil { + return nil, err + } + return c.refundImpl(txOpts, secretHash) +} + +func (c *contractorV0) refundImpl(txOpts *bind.TransactOpts, secretHash [32]byte) (*types.Transaction, error) { return c.contractV0.Refund(txOpts, secretHash) } // isRedeemable exposes the isRedeemable method of the swap contract. -func (c *contractorV0) isRedeemable(secretHash, secret [32]byte) (bool, error) { +func (c *contractorV0) isRedeemable(locator []byte, secret [32]byte) (bool, error) { + secretHash, err := dexeth.ParseV0Locator(locator) + if err != nil { + return false, err + } + return c.isRedeemableImpl(secretHash, secret) +} + +func (c *contractorV0) isRedeemableImpl(secretHash, secret [32]byte) (bool, error) { return c.contractV0.IsRedeemable(&bind.CallOpts{From: c.acctAddr}, secretHash, secret) } // isRefundable exposes the isRefundable method of the swap contract. -func (c *contractorV0) isRefundable(secretHash [32]byte) (bool, error) { +func (c *contractorV0) isRefundable(locator []byte) (bool, error) { + secretHash, err := dexeth.ParseV0Locator(locator) + if err != nil { + return false, err + } + return c.isRefundableImpl(secretHash) +} + +func (c *contractorV0) isRefundableImpl(secretHash [32]byte) (bool, error) { return c.contractV0.IsRefundable(&bind.CallOpts{From: c.acctAddr}, secretHash) } // estimateRedeemGas estimates the gas used to redeem. The secret hashes // supplied must reference existing swaps, so this method can't be used until // the swap is initiated. -func (c *contractorV0) estimateRedeemGas(ctx context.Context, secrets [][32]byte) (uint64, error) { +func (c *contractorV0) estimateRedeemGas(ctx context.Context, secrets [][32]byte, _ [][]byte) (uint64, error) { redemps := make([]swapv0.ETHSwapRedemption, 0, len(secrets)) for _, secret := range secrets { redemps = append(redemps, swapv0.ETHSwapRedemption{ @@ -227,7 +343,15 @@ func (c *contractorV0) estimateRedeemGas(ctx context.Context, secrets [][32]byte // estimateRefundGas estimates the gas used to refund. The secret hashes // supplied must reference existing swaps that are refundable, so this method // can't be used until the swap is initiated and the lock time has expired. -func (c *contractorV0) estimateRefundGas(ctx context.Context, secretHash [32]byte) (uint64, error) { +func (c *contractorV0) estimateRefundGas(ctx context.Context, locator []byte) (uint64, error) { + secretHash, err := dexeth.ParseV0Locator(locator) + if err != nil { + return 0, err + } + return c.estimateRefundGasImpl(ctx, secretHash) +} + +func (c *contractorV0) estimateRefundGasImpl(ctx context.Context, secretHash [32]byte) (uint64, error) { return c.estimateGas(ctx, nil, "refund", secretHash) } @@ -288,7 +412,7 @@ func (c *contractorV0) value(ctx context.Context, tx *types.Transaction) (in, ou // incomingValue calculates the value being redeemed for refunded in the tx. func (c *contractorV0) incomingValue(ctx context.Context, tx *types.Transaction) (uint64, error) { - if redeems, err := dexeth.ParseRedeemData(tx.Data(), 0); err == nil { + if redeems, err := dexeth.ParseRedeemDataV0(tx.Data()); err == nil { var redeemed uint64 for _, redeem := range redeems { swap, err := c.swap(ctx, redeem.SecretHash) @@ -299,7 +423,7 @@ func (c *contractorV0) incomingValue(ctx context.Context, tx *types.Transaction) } return redeemed, nil } - secretHash, err := dexeth.ParseRefundData(tx.Data(), 0) + secretHash, err := dexeth.ParseRefundDataV0(tx.Data()) if err != nil { return 0, nil } @@ -312,7 +436,7 @@ func (c *contractorV0) incomingValue(ctx context.Context, tx *types.Transaction) // outgoingValue calculates the value sent in swaps in the tx. func (c *contractorV0) outgoingValue(tx *types.Transaction) (swapped uint64) { - if inits, err := dexeth.ParseInitiateData(tx.Data(), 0); err == nil { + if inits, err := dexeth.ParseInitiateDataV0(tx.Data()); err == nil { for _, init := range inits { swapped += c.atomize(init.Value) } @@ -329,12 +453,51 @@ func (c *contractorV0) voidUnusedNonce() { } } +// erc20Contractor supports the ERC20 ABI. Embedded in token contractors. +type erc20Contractor struct { + tokenContract *erc20.IERC20 + acct common.Address + contract common.Address +} + +// balance exposes the read-only balanceOf method of the erc20 token contract. +func (c *erc20Contractor) balance(ctx context.Context) (*big.Int, error) { + callOpts := &bind.CallOpts{ + From: c.acct, + Context: ctx, + } + + return c.tokenContract.BalanceOf(callOpts, c.acct) +} + +// allowance exposes the read-only allowance method of the erc20 token contract. +func (c *erc20Contractor) allowance(ctx context.Context) (*big.Int, error) { + callOpts := &bind.CallOpts{ + Pending: true, + From: c.acct, + Context: ctx, + } + return c.tokenContract.Allowance(callOpts, c.acct, c.contract) +} + +// approve sends an approve transaction approving the linked contract to call +// transferFrom for the specified amount. +func (c *erc20Contractor) approve(txOpts *bind.TransactOpts, amount *big.Int) (*types.Transaction, error) { + return c.tokenContract.Approve(txOpts, c.contract, amount) +} + +// transfer calls the transfer method of the erc20 token contract. Used for +// sends or withdrawals. +func (c *erc20Contractor) transfer(txOpts *bind.TransactOpts, addr common.Address, amount *big.Int) (*types.Transaction, error) { + return c.tokenContract.Transfer(txOpts, addr, amount) +} + // tokenContractorV0 is a contractor that implements the tokenContractor // methods, providing access to the methods of the token's ERC20 contract. type tokenContractorV0 struct { *contractorV0 - tokenAddr common.Address - tokenContract *erc20.IERC20 + *erc20Contractor + tokenAddr common.Address } var _ contractor = (*tokenContractorV0)(nil) @@ -377,79 +540,402 @@ func newV0TokenContractor(net dex.Network, assetID uint32, acctAddr common.Addre evmify: token.AtomicToEVM, atomize: token.EVMToAtomic, }, - tokenAddr: tokenAddr, - tokenContract: tokenContract, + tokenAddr: tokenAddr, + erc20Contractor: &erc20Contractor{ + tokenContract: tokenContract, + acct: acctAddr, + contract: swapContractAddr, + }, }, nil } -// balance exposes the read-only balanceOf method of the erc20 token contract. -func (c *tokenContractorV0) balance(ctx context.Context) (*big.Int, error) { - callOpts := &bind.CallOpts{ - From: c.acctAddr, - Context: ctx, +// estimateApproveGas estimates the gas needed to send an approve tx. +func (c *tokenContractorV0) estimateApproveGas(ctx context.Context, amount *big.Int) (uint64, error) { + return c.estimateGas(ctx, "approve", c.contractAddr, amount) +} + +// estimateTransferGas esimates the gas needed for a transfer tx. The account +// needs to have > amount tokens to use this method. +func (c *tokenContractorV0) estimateTransferGas(ctx context.Context, amount *big.Int) (uint64, error) { + return c.estimateGas(ctx, "transfer", c.acctAddr, amount) +} + +// estimateGas estimates the gas needed for methods on the ERC20 token contract. +// For estimating methods on the swap contract, use (contractorV0).estimateGas. +func (c *tokenContractorV0) estimateGas(ctx context.Context, method string, args ...interface{}) (uint64, error) { + return estimateGas(ctx, c.acctAddr, c.contractAddr, c.abi, c.cb, new(big.Int), method, args...) +} + +// value finds incoming or outgoing value for the tx to either the swap contract +// or the erc20 token contract. For the token contract, only transfer and +// transferFrom are parsed. It is not an error if this tx is a call to another +// method of the token contract, but no values will be parsed. +func (c *tokenContractorV0) value(ctx context.Context, tx *types.Transaction) (in, out uint64, err error) { + to := *tx.To() + if to == c.contractAddr { + return c.contractorV0.value(ctx, tx) + } + if to != c.tokenAddr { + return 0, 0, nil + } + + // Consider removing. We'll never be sending transferFrom transactions + // directly. + if sender, _, value, err := erc20.ParseTransferFromData(tx.Data()); err == nil && sender == c.acctAddr { + return 0, c.atomize(value), nil } - return c.tokenContract.BalanceOf(callOpts, c.acctAddr) + if _, value, err := erc20.ParseTransferData(tx.Data()); err == nil { + return 0, c.atomize(value), nil + } + + return 0, 0, nil +} + +// tokenAddress exposes the token_address immutable address of the token-bound +// swap contract. +func (c *tokenContractorV0) tokenAddress() common.Address { + return c.tokenAddr } -// allowance exposes the read-only allowance method of the erc20 token contract. -func (c *tokenContractorV0) allowance(ctx context.Context) (*big.Int, error) { - // See if we support the pending state. - _, pendingUnavailable := c.cb.(*multiRPCClient) - callOpts := &bind.CallOpts{ - Pending: !pendingUnavailable, - From: c.acctAddr, - Context: ctx, +type contractorV1 struct { + contractV1 + abi *abi.ABI + net dex.Network + contractAddr common.Address + acctAddr common.Address + cb bind.ContractBackend + isToken bool + evmify func(uint64) *big.Int + atomize func(*big.Int) uint64 +} + +var _ contractor = (*contractorV1)(nil) + +func newV1Contractor(net dex.Network, acctAddr common.Address, cb bind.ContractBackend) (contractor, error) { + contractAddr, exists := dexeth.ContractAddresses[1][net] + if !exists || contractAddr == (common.Address{}) { + return nil, fmt.Errorf("no contract address for version 0, net %s", net) + } + c, err := swapv1.NewETHSwap(contractAddr, cb) + if err != nil { + return nil, err } - return c.tokenContract.Allowance(callOpts, c.acctAddr, c.contractAddr) + return &contractorV1{ + contractV1: c, + abi: dexeth.ABIs[1], + net: net, + contractAddr: contractAddr, + acctAddr: acctAddr, + cb: cb, + atomize: dexeth.WeiToGwei, + }, nil } -// approve sends an approve transaction approving the linked contract to call -// transferFrom for the specified amount. -func (c *tokenContractorV0) approve(txOpts *bind.TransactOpts, amount *big.Int) (tx *types.Transaction, err error) { - return c.tokenContract.Approve(txOpts, c.contractAddr, amount) +func (c *contractorV1) status(ctx context.Context, locator []byte) (*dexeth.SwapStatus, error) { + v, err := dexeth.ParseV1Locator(locator) + if err != nil { + return nil, err + } + rec, err := c.Status(&bind.CallOpts{From: c.acctAddr}, dexeth.SwapVectorToAbigen(v)) + if err != nil { + return nil, err + } + return &dexeth.SwapStatus{ + Step: dexeth.SwapStep(rec.Step), + Secret: rec.Secret, + BlockHeight: rec.BlockNumber.Uint64(), + }, err } -// transfer calls the transfer method of the erc20 token contract. Used for -// sends or withdrawals. -func (c *tokenContractorV0) transfer(txOpts *bind.TransactOpts, addr common.Address, amount *big.Int) (tx *types.Transaction, err error) { - return c.tokenContract.Transfer(txOpts, addr, amount) +func (c *contractorV1) vector(ctx context.Context, locator []byte) (*dexeth.SwapVector, error) { + return dexeth.ParseV1Locator(locator) +} + +func (c *contractorV1) statusAndVector(ctx context.Context, locator []byte) (*dexeth.SwapStatus, *dexeth.SwapVector, error) { + v, err := dexeth.ParseV1Locator(locator) + if err != nil { + return nil, nil, err + } + + rec, err := c.Status(&bind.CallOpts{From: c.acctAddr}, dexeth.SwapVectorToAbigen(v)) + if err != nil { + return nil, nil, err + } + return &dexeth.SwapStatus{ + Step: dexeth.SwapStep(rec.Step), + Secret: rec.Secret, + BlockHeight: rec.BlockNumber.Uint64(), + }, v, err +} + +func (c *contractorV1) voidUnusedNonce() { + if mRPC, is := c.cb.(*multiRPCClient); is { + mRPC.voidUnusedNonce() + } +} + +// func (c *contractorV1) record(v *dexeth.SwapVector) (r [32]byte, err error) { +// abiVec := dexeth.SwapVectorToAbigen(v) +// ck, err := c.ContractKey(&bind.CallOpts{From: c.acctAddr}, abiVec) +// if err != nil { +// return r, fmt.Errorf("ContractKey error: %v", err) +// } +// return c.Swaps(&bind.CallOpts{From: c.acctAddr}, ck) +// } + +func (c *contractorV1) initiate(txOpts *bind.TransactOpts, contracts []*asset.Contract) (*types.Transaction, error) { + versionedContracts := make([]swapv1.ETHSwapVector, 0, len(contracts)) + for _, ac := range contracts { + v := &dexeth.SwapVector{ + From: c.acctAddr, + To: common.HexToAddress(ac.Address), + Value: ac.Value, + LockTime: ac.LockTime, + } + copy(v.SecretHash[:], ac.SecretHash) + versionedContracts = append(versionedContracts, dexeth.SwapVectorToAbigen(v)) + } + return c.Initiate(txOpts, versionedContracts) +} + +func (c *contractorV1) redeem(txOpts *bind.TransactOpts, redeems []*asset.Redemption) (*types.Transaction, error) { + versionedRedemptions := make([]swapv1.ETHSwapRedemption, 0, len(redeems)) + secretHashes := make(map[[32]byte]bool, len(redeems)) + for _, r := range redeems { + var secret [32]byte + copy(secret[:], r.Secret) + secretHash := sha256.Sum256(r.Secret) + if !bytes.Equal(secretHash[:], r.Spends.SecretHash) { + return nil, errors.New("wrong secret") + } + if secretHashes[secretHash] { + return nil, fmt.Errorf("duplicate secret hash %x", secretHash[:]) + } + secretHashes[secretHash] = true + + // Not checking version from DecodeLocator because it was already + // audited and incorrect version locator would err below anyway. + _, locator, err := dexeth.DecodeLocator(r.Spends.Contract) + if err != nil { + return nil, fmt.Errorf("error parsing locator redeem: %w", err) + } + v, err := dexeth.ParseV1Locator(locator) + if err != nil { + return nil, fmt.Errorf("error parsing locator: %w", err) + } + versionedRedemptions = append(versionedRedemptions, swapv1.ETHSwapRedemption{ + V: dexeth.SwapVectorToAbigen(v), + Secret: secret, + }) + } + return c.Redeem(txOpts, versionedRedemptions) +} + +func (c *contractorV1) refund(txOpts *bind.TransactOpts, locator []byte) (*types.Transaction, error) { + v, err := dexeth.ParseV1Locator(locator) + if err != nil { + return nil, err + } + return c.Refund(txOpts, dexeth.SwapVectorToAbigen(v)) +} + +func (c *contractorV1) estimateInitGas(ctx context.Context, n int) (uint64, error) { + initiations := make([]swapv1.ETHSwapVector, 0, n) + for j := 0; j < n; j++ { + var secretHash [32]byte + copy(secretHash[:], encode.RandomBytes(32)) + initiations = append(initiations, swapv1.ETHSwapVector{ + RefundTimestamp: 1, + SecretHash: secretHash, + Initiator: c.acctAddr, + Participant: common.BytesToAddress(encode.RandomBytes(20)), + Value: 1, + }) + } + + var value *big.Int + if !c.isToken { + value = dexeth.GweiToWei(uint64(n)) + } + + return c.estimateGas(ctx, value, "initiate", initiations) +} + +func (c *contractorV1) estimateGas(ctx context.Context, value *big.Int, method string, args ...interface{}) (uint64, error) { + return estimateGas(ctx, c.acctAddr, c.contractAddr, c.abi, c.cb, value, method, args...) +} + +func (c *contractorV1) estimateRedeemGas(ctx context.Context, secrets [][32]byte, locators [][]byte) (uint64, error) { + if len(secrets) != len(locators) { + return 0, fmt.Errorf("number of secrets (%d) does not match number of contracts (%d)", len(secrets), len(locators)) + } + + vectors := make([]*dexeth.SwapVector, len(locators)) + for i, loc := range locators { + v, err := dexeth.ParseV1Locator(loc) + if err != nil { + return 0, fmt.Errorf("unable to parse locator # %d (%x): %v", i, loc, err) + } + vectors[i] = v + } + + redemps := make([]swapv1.ETHSwapRedemption, 0, len(secrets)) + for i, secret := range secrets { + redemps = append(redemps, swapv1.ETHSwapRedemption{ + Secret: secret, + V: dexeth.SwapVectorToAbigen(vectors[i]), + }) + } + return c.estimateGas(ctx, nil, "redeem", redemps) +} + +func (c *contractorV1) estimateRefundGas(ctx context.Context, locator []byte) (uint64, error) { + v, err := dexeth.ParseV1Locator(locator) + if err != nil { + return 0, err + } + return c.estimateGas(ctx, nil, "refund", dexeth.SwapVectorToAbigen(v)) +} + +func (c *contractorV1) isRedeemable(locator []byte, secret [32]byte) (bool, error) { + v, err := dexeth.ParseV1Locator(locator) + if err != nil { + return false, err + } + if v.To != c.acctAddr { + return false, nil + } + if is, err := c.IsRedeemable(&bind.CallOpts{From: c.acctAddr}, dexeth.SwapVectorToAbigen(v)); err != nil || !is { + return is, err + } + return sha256.Sum256(secret[:]) == v.SecretHash, nil +} + +func (c *contractorV1) isRefundable(locator []byte) (bool, error) { + v, err := dexeth.ParseV1Locator(locator) + if err != nil { + return false, err + } + if is, err := c.IsRedeemable(&bind.CallOpts{From: c.acctAddr}, dexeth.SwapVectorToAbigen(v)); err != nil || !is { + return is, err + } + return time.Now().Unix() >= int64(v.LockTime), nil +} + +func (c *contractorV1) incomingValue(ctx context.Context, tx *types.Transaction) (uint64, error) { + if redeems, err := dexeth.ParseRedeemDataV1(tx.Data()); err == nil { + var redeemed uint64 + for _, r := range redeems { + redeemed += r.Contract.Value + } + return redeemed, nil + } + refund, err := dexeth.ParseRefundDataV1(tx.Data()) + if err != nil { + return 0, nil + } + return refund.Value, nil +} + +func (c *contractorV1) outgoingValue(tx *types.Transaction) (swapped uint64) { + if inits, err := dexeth.ParseInitiateDataV1(tx.Data()); err == nil { + for _, init := range inits { + swapped += init.Value + } + } + return +} + +func (c *contractorV1) value(ctx context.Context, tx *types.Transaction) (in, out uint64, err error) { + if *tx.To() != c.contractAddr { + return 0, 0, nil + } + + if v, err := c.incomingValue(ctx, tx); err != nil { + return 0, 0, fmt.Errorf("incomingValue error: %w", err) + } else if v > 0 { + return v, 0, nil + } + + return 0, c.outgoingValue(tx), nil +} + +type tokenContractorV1 struct { + *contractorV1 + *erc20Contractor + tokenAddr common.Address +} + +func newV1TokenContractor(net dex.Network, assetID uint32, acctAddr common.Address, cb bind.ContractBackend) (tokenContractor, error) { + token, tokenAddr, swapContractAddr, err := dexeth.VersionedNetworkToken(assetID, 1, net) + if err != nil { + return nil, err + } + + c, err := erc20v1.NewERC20Swap(swapContractAddr, cb) + if err != nil { + return nil, err + } + + tokenContract, err := erc20.NewIERC20(tokenAddr, cb) + if err != nil { + return nil, err + } + + if boundAddr, err := c.TokenAddress(&bind.CallOpts{ + Context: context.TODO(), + }); err != nil { + return nil, fmt.Errorf("error reading bound token address %q: %w", tokenAddr, err) + } else if boundAddr != tokenAddr { + return nil, fmt.Errorf("wrong bound address. expected %s, got %s", tokenAddr, boundAddr) + } + + return &tokenContractorV1{ + contractorV1: &contractorV1{ + contractV1: c, + abi: dexeth.ABIs[1], + net: net, + contractAddr: swapContractAddr, + acctAddr: acctAddr, + cb: cb, + evmify: token.AtomicToEVM, + atomize: token.EVMToAtomic, + }, + tokenAddr: tokenAddr, + erc20Contractor: &erc20Contractor{ + tokenContract: tokenContract, + acct: acctAddr, + contract: swapContractAddr, + }, + }, nil } // estimateApproveGas estimates the gas needed to send an approve tx. -func (c *tokenContractorV0) estimateApproveGas(ctx context.Context, amount *big.Int) (uint64, error) { - return c.estimateGas(ctx, "approve", c.contractAddr, amount) +func (c *tokenContractorV1) estimateApproveGas(ctx context.Context, amount *big.Int) (uint64, error) { + return c.estimateTokenContractGas(ctx, "approve", c.contractAddr, amount) } // estimateTransferGas estimates the gas needed for a transfer tx. The account // needs to have > amount tokens to use this method. -func (c *tokenContractorV0) estimateTransferGas(ctx context.Context, amount *big.Int) (uint64, error) { - return c.estimateGas(ctx, "transfer", c.acctAddr, amount) +func (c *tokenContractorV1) estimateTransferGas(ctx context.Context, amount *big.Int) (uint64, error) { + return c.estimateTokenContractGas(ctx, "transfer", c.acctAddr, amount) } // estimateGas estimates the gas needed for methods on the ERC20 token contract. // For estimating methods on the swap contract, use (contractorV0).estimateGas. -func (c *tokenContractorV0) estimateGas(ctx context.Context, method string, args ...interface{}) (uint64, error) { - data, err := erc20.ERC20ABI.Pack(method, args...) - if err != nil { - return 0, fmt.Errorf("token estimateGas Pack error: %v", err) - } - - return c.cb.EstimateGas(ctx, ethereum.CallMsg{ - From: c.acctAddr, - To: &c.tokenAddr, - Data: data, - }) +func (c *tokenContractorV1) estimateTokenContractGas(ctx context.Context, method string, args ...interface{}) (uint64, error) { + return estimateGas(ctx, c.acctAddr, c.tokenAddr, erc20.ERC20ABI, c.cb, new(big.Int), method, args...) } // value finds incoming or outgoing value for the tx to either the swap contract // or the erc20 token contract. For the token contract, only transfer and // transferFrom are parsed. It is not an error if this tx is a call to another // method of the token contract, but no values will be parsed. -func (c *tokenContractorV0) value(ctx context.Context, tx *types.Transaction) (in, out uint64, err error) { +func (c *tokenContractorV1) value(ctx context.Context, tx *types.Transaction) (in, out uint64, err error) { to := *tx.To() if to == c.contractAddr { - return c.contractorV0.value(ctx, tx) + return c.contractorV1.value(ctx, tx) } if to != c.tokenAddr { return 0, 0, nil @@ -470,14 +956,41 @@ func (c *tokenContractorV0) value(ctx context.Context, tx *types.Transaction) (i // tokenAddress exposes the token_address immutable address of the token-bound // swap contract. -func (c *tokenContractorV0) tokenAddress() common.Address { +func (c *tokenContractorV1) tokenAddress() common.Address { return c.tokenAddr } +var _ contractor = (*tokenContractorV1)(nil) +var _ tokenContractor = (*tokenContractorV1)(nil) + +// readOnlyCallOpts is the CallOpts used for read-only contract method calls. +func readOnlyCallOpts(ctx context.Context) *bind.CallOpts { + return &bind.CallOpts{ + Pending: true, + Context: ctx, + } +} + +func estimateGas(ctx context.Context, from, to common.Address, abi *abi.ABI, cb bind.ContractBackend, value *big.Int, method string, args ...interface{}) (uint64, error) { + data, err := abi.Pack(method, args...) + if err != nil { + return 0, fmt.Errorf("Pack error: %v", err) + } + + return cb.EstimateGas(ctx, ethereum.CallMsg{ + From: from, + To: &to, + Data: data, + Value: value, + }) +} + var contractorConstructors = map[uint32]contractorConstructor{ 0: newV0Contractor, + 1: newV1Contractor, } var tokenContractorConstructors = map[uint32]tokenContractorConstructor{ 0: newV0TokenContractor, + 1: newV1TokenContractor, } diff --git a/client/asset/eth/contractor_test.go b/client/asset/eth/contractor_test.go index f0d5614db8..16d46e271f 100644 --- a/client/asset/eth/contractor_test.go +++ b/client/asset/eth/contractor_test.go @@ -4,6 +4,7 @@ package eth import ( "bytes" + "crypto/sha256" "fmt" "math/big" "testing" @@ -134,7 +135,8 @@ func TestRedeemV0(t *testing.T) { c := contractorV0{contractV0: abiContract, evmify: dexeth.GweiToWei} secretB := encode.RandomBytes(32) - secretHashB := encode.RandomBytes(32) + secretHash := sha256.Sum256(secretB) + secretHashB := secretHash[:] redemption := &asset.Redemption{ Secret: secretB, @@ -166,12 +168,12 @@ func TestRedeemV0(t *testing.T) { // bad secret hash length redemption.Spends.SecretHash = encode.RandomBytes(20) checkResult("bad secret hash length", true) - redemption.Spends.SecretHash = encode.RandomBytes(32) + redemption.Spends.SecretHash = secretHashB // bad secret length redemption.Secret = encode.RandomBytes(20) checkResult("bad secret length", true) - redemption.Secret = encode.RandomBytes(32) + redemption.Secret = secretB // Redeem error abiContract.redeemErr = fmt.Errorf("test error") @@ -183,9 +185,11 @@ func TestRedeemV0(t *testing.T) { checkResult("dupe error", true) // two OK + secretB2 := encode.RandomBytes(32) + secretHash2 := sha256.Sum256(secretB2) redemption2 := &asset.Redemption{ - Secret: encode.RandomBytes(32), - Spends: &asset.AuditInfo{SecretHash: encode.RandomBytes(32)}, + Secret: secretB2, + Spends: &asset.AuditInfo{SecretHash: secretHash2[:]}, } redemptions = []*asset.Redemption{redemption, redemption2} checkResult("two ok", false) diff --git a/client/asset/eth/eth.go b/client/asset/eth/eth.go index 232c11832c..53431a6c76 100644 --- a/client/asset/eth/eth.go +++ b/client/asset/eth/eth.go @@ -80,6 +80,9 @@ const ( // seconds. This value may need to be adjusted in the future. confCheckTimeout = 4 * time.Second dynamicSwapOrRedemptionFeesConfs = 2 + + contractVersionERC20 = ^uint32(0) + contractVersionUnknown = contractVersionERC20 - 1 ) var ( @@ -158,6 +161,11 @@ var ( 0, // branch 0 0, // index 0 } + + // https://github.com/ethereum/go-ethereum/blob/16341e05636fd088aa04a27fca6dc5cda5dbab8f/eth/backend.go#L110-L113 + // ultimately results in a minimum fee rate by the filter applied at + // https://github.com/ethereum/go-ethereum/blob/4ebeca19d739a243dc0549bcaf014946cde95c4f/core/tx_pool.go#L626 + minGasPrice = ethconfig.Defaults.Miner.GasPrice ) // WalletConfig are wallet-level configuration settings. @@ -422,7 +430,7 @@ type assetWallet struct { } findRedemptionMtx sync.RWMutex - findRedemptionReqs map[[32]byte]*findRedemptionRequest + findRedemptionReqs map[string]*findRedemptionRequest lastPeerCount uint32 peersChange func(uint32, error) @@ -510,6 +518,19 @@ func genWalletSeed(entropy []byte) ([]byte, error) { return bip39.NewSeed(mnemonic, ""), nil } +// contractVersion converts a server version to a contract version. It applies +// to both tokens and eth right now, but that may not always be the case. +func contractVersion(serverVer uint32) uint32 { + switch serverVer { + case 0: + return 0 + case 1: + return 1 + default: + return contractVersionUnknown + } +} + // CreateWallet creates a new internal ETH wallet and stores the private key // derived from the wallet seed. func CreateWallet(cfg *asset.CreateWalletParams) error { @@ -664,7 +685,7 @@ func NewWallet(assetCFG *asset.WalletConfig, logger dex.Logger, net dex.Network) log: logger.SubLogger("ETH"), assetID: BipID, tipChange: assetCFG.TipChange, - findRedemptionReqs: make(map[[32]byte]*findRedemptionRequest), + findRedemptionReqs: make(map[string]*findRedemptionRequest), peersChange: assetCFG.PeersChange, contractors: make(map[uint32]contractor), evmify: dexeth.GweiToWei, @@ -940,7 +961,7 @@ func (w *ETHWallet) OpenTokenWallet(tokenCfg *asset.TokenConfig) (asset.Wallet, assetID: tokenCfg.AssetID, tipChange: tokenCfg.TipChange, peersChange: tokenCfg.PeersChange, - findRedemptionReqs: make(map[[32]byte]*findRedemptionRequest), + findRedemptionReqs: make(map[string]*findRedemptionRequest), contractors: make(map[uint32]contractor), evmify: token.AtomicToEVM, atomize: token.EVMToAtomic, @@ -1094,8 +1115,8 @@ func (w *TokenWallet) MaxOrder(ord *asset.MaxOrderForm) (*asset.SwapEstimate, er ord.RedeemVersion, ord.RedeemAssetID, w.parent) } -func (w *assetWallet) maxOrder(lotSize uint64, feeSuggestion, maxFeeRate uint64, ver uint32, - redeemVer, redeemAssetID uint32, feeWallet *assetWallet) (*asset.SwapEstimate, error) { +func (w *assetWallet) maxOrder(lotSize uint64, feeSuggestion, maxFeeRate uint64, serverVer uint32, + redeemServerVer, redeemAssetID uint32, feeWallet *assetWallet) (*asset.SwapEstimate, error) { balance, err := w.Balance() if err != nil { return nil, err @@ -1104,7 +1125,10 @@ func (w *assetWallet) maxOrder(lotSize uint64, feeSuggestion, maxFeeRate uint64, return &asset.SwapEstimate{}, nil } - g, err := w.initGasEstimate(1, ver, redeemVer, redeemAssetID) + contractVer := contractVersion(serverVer) + redeemVer := contractVersion(redeemServerVer) + + g, err := w.initGasEstimate(1, contractVer, redeemVer, redeemAssetID) if err != nil { return nil, fmt.Errorf("gasEstimate error: %w", err) } @@ -1129,7 +1153,7 @@ func (w *assetWallet) maxOrder(lotSize uint64, feeSuggestion, maxFeeRate uint64, if lots < 1 { return &asset.SwapEstimate{}, nil } - return w.estimateSwap(lots, lotSize, feeSuggestion, maxFeeRate, ver, redeemVer, redeemAssetID) + return w.estimateSwap(lots, lotSize, feeSuggestion, maxFeeRate, contractVer, redeemVer, redeemAssetID) } // PreSwap gets order estimates based on the available funds and the wallet @@ -1156,7 +1180,7 @@ func (w *assetWallet) preSwap(req *asset.PreSwapForm, feeWallet *assetWallet) (* } est, err := w.estimateSwap(req.Lots, req.LotSize, req.FeeSuggestion, req.MaxFeeRate, - req.Version, req.RedeemVersion, req.RedeemAssetID) + contractVersion(req.Version), contractVersion(req.RedeemVersion), req.RedeemAssetID) if err != nil { return nil, err } @@ -1170,7 +1194,7 @@ func (w *assetWallet) preSwap(req *asset.PreSwapForm, feeWallet *assetWallet) (* // aren't available. The returned fees are the RealisticWorstCase. The Lots // field of the PreSwapForm is ignored and assumed to be a single lot. func (w *assetWallet) SingleLotSwapFees(form *asset.PreSwapForm) (fees uint64, err error) { - g := w.gases(form.Version) + g := w.gases(contractVersion(form.Version)) if g == nil { return 0, fmt.Errorf("no gases known for %d version %d", w.assetID, form.Version) } @@ -1179,13 +1203,13 @@ func (w *assetWallet) SingleLotSwapFees(form *asset.PreSwapForm) (fees uint64, e // estimateSwap prepares an *asset.SwapEstimate. The estimate does not include // funds that might be locked for refunds. -func (w *assetWallet) estimateSwap(lots, lotSize, feeSuggestion uint64, maxFeeRate uint64, ver uint32, +func (w *assetWallet) estimateSwap(lots, lotSize, feeSuggestion uint64, maxFeeRate uint64, contractVer uint32, redeemVer, redeemAssetID uint32) (*asset.SwapEstimate, error) { if lots == 0 { return &asset.SwapEstimate{}, nil } - oneSwap, nSwap, _, err := w.swapGas(int(lots), ver) + oneSwap, nSwap, _, err := w.swapGas(int(lots), contractVer) if err != nil { return nil, fmt.Errorf("error getting swap gas estimate: %w", err) } @@ -1212,7 +1236,7 @@ func (w *assetWallet) estimateSwap(lots, lotSize, feeSuggestion uint64, maxFeeRa // for a token. This is required to redeem ERC20 tokens, and the assetID and // version correspond to the redeem asset. If the asset is not a fee-family // erc20 asset, no error is returned and the return value will be zero. -func (w *assetWallet) allowanceGasRequired(ver, assetID uint32) (uint64, error) { +func (w *assetWallet) allowanceGasRequired(contractVer, assetID uint32) (uint64, error) { if assetID == BipID { return 0, nil // it's eth (no allowance) } @@ -1227,7 +1251,7 @@ func (w *assetWallet) allowanceGasRequired(ver, assetID uint32) (uint64, error) // No reason to do anything if the allowance is > the unlimited // allowance approval threshold. if currentAllowance.Cmp(unlimitedAllowanceReplenishThreshold) < 0 { - return redeemWallet.approvalGas(unlimitedAllowance, ver) + return redeemWallet.approvalGas(unlimitedAllowance, contractVer) } return 0, nil } @@ -1240,7 +1264,7 @@ func (w *assetWallet) gases(contractVer uint32) *dexeth.Gases { // PreRedeem generates an estimate of the range of redemption fees that could // be assessed. func (w *assetWallet) PreRedeem(req *asset.PreRedeemForm) (*asset.PreRedeem, error) { - oneRedeem, nRedeem, err := w.redeemGas(int(req.Lots), req.Version) + oneRedeem, nRedeem, err := w.redeemGas(int(req.Lots), contractVersion(req.Version)) if err != nil { return nil, err } @@ -1257,9 +1281,10 @@ func (w *assetWallet) PreRedeem(req *asset.PreRedeemForm) (*asset.PreRedeem, err // funds aren't available. The returned fees are the RealisticWorstCase. The // Lots field of the PreSwapForm is ignored and assumed to be a single lot. func (w *assetWallet) SingleLotRedeemFees(form *asset.PreRedeemForm) (fees uint64, err error) { - g := w.gases(form.Version) + contractVer := contractVersion(form.Version) + g := w.gases(contractVer) if g == nil { - return 0, fmt.Errorf("no gases known for %d version %d", w.assetID, form.Version) + return 0, fmt.Errorf("no gases known for asset %d, contract version %d", w.assetID, contractVer) } return g.Redeem * form.FeeSuggestion, nil } @@ -1308,7 +1333,9 @@ func (w *ETHWallet) FundOrder(ord *asset.Order) (asset.Coins, []dex.Bytes, error dex.BipIDSymbol(w.assetID), ord.MaxFeeRate, w.gasFeeLimit()) } - g, err := w.initGasEstimate(int(ord.MaxSwapCount), ord.Version, + contractVer := contractVersion(ord.Version) + + g, err := w.initGasEstimate(int(ord.MaxSwapCount), contractVer, ord.RedeemVersion, ord.RedeemAssetID) if err != nil { return nil, nil, fmt.Errorf("error estimating swap gas: %v", err) @@ -1340,15 +1367,16 @@ func (w *TokenWallet) FundOrder(ord *asset.Order) (asset.Coins, []dex.Bytes, err dex.BipIDSymbol(w.assetID), ord.MaxFeeRate, w.gasFeeLimit()) } - g, err := w.initGasEstimate(int(ord.MaxSwapCount), ord.Version, - ord.RedeemVersion, ord.RedeemAssetID) + contractVer := contractVersion(ord.Version) + + g, err := w.initGasEstimate(int(ord.MaxSwapCount), contractVer, ord.RedeemVersion, ord.RedeemAssetID) if err != nil { return nil, nil, fmt.Errorf("error estimating swap gas: %v", err) } ethToLock := ord.MaxFeeRate * g.Swap * ord.MaxSwapCount - if err := w.maybeApproveTokenSwapContract(ord.Version, ord.MaxFeeRate, ethToLock); err != nil { + if err := w.maybeApproveTokenSwapContract(contractVer, ord.MaxFeeRate, ethToLock); err != nil { return nil, nil, fmt.Errorf("error issuing approval: %w", err) } @@ -1417,10 +1445,10 @@ func (w *assetWallet) initGasEstimate(n int, initVer, redeemVer, redeemAssetID u // swapGas estimates gas for a number of initiations. swapGas will error if we // cannot get a live estimate from the contractor, which will happen if the // wallet has no balance. -func (w *assetWallet) swapGas(n int, ver uint32) (oneSwap, nSwap uint64, approved bool, err error) { - g := w.gases(ver) +func (w *assetWallet) swapGas(n int, contractVer uint32) (oneSwap, nSwap uint64, approved bool, err error) { + g := w.gases(contractVer) if g == nil { - return 0, 0, false, fmt.Errorf("no gases known for %d version %d", w.assetID, ver) + return 0, 0, false, fmt.Errorf("no gases known for %d contract version %d", w.assetID, contractVer) } oneSwap = g.Swap @@ -1443,12 +1471,12 @@ func (w *assetWallet) swapGas(n int, ver uint32) (oneSwap, nSwap uint64, approve // If a live estimate is greater than our estimate from configured values, // use the live estimate with a warning. - if gasEst, err := w.estimateInitGas(w.ctx, n, ver); err != nil { + if gasEst, err := w.estimateInitGas(w.ctx, n, contractVer); err != nil { w.log.Errorf("(%d) error estimating swap gas: %v", w.assetID, err) // TODO: investigate "gas required exceeds allowance". return 0, 0, false, err } else if gasEst > nSwap { - w.log.Warnf("Swap gas estimate %d is greater than the server's configured value %d. Using live estimate + 10%.", gasEst, nSwap) + w.log.Warnf("Swap gas estimate %d is greater than the server's configured value %d. Using live estimate + 10%%.", gasEst, nSwap) nSwap = gasEst * 11 / 10 // 10% buffer if n == 1 && nSwap > oneSwap { oneSwap = nSwap @@ -1459,8 +1487,8 @@ func (w *assetWallet) swapGas(n int, ver uint32) (oneSwap, nSwap uint64, approve // redeemGas gets an accurate estimate for redemption gas. We allow a DEX server // some latitude in adjusting the redemption gas, up to 2x our local estimate. -func (w *assetWallet) redeemGas(n int, ver uint32) (oneGas, nGas uint64, err error) { - g := w.gases(ver) +func (w *assetWallet) redeemGas(n int, contractVer uint32) (oneGas, nGas uint64, err error) { + g := w.gases(contractVer) if g == nil { return 0, 0, fmt.Errorf("no gas table for redemption asset %d", w.assetID) } @@ -1474,10 +1502,10 @@ func (w *assetWallet) redeemGas(n int, ver uint32) (oneGas, nGas uint64, err err // the greater of the asset's registered value and a live estimate. It is an // error if a live estimate cannot be retrieved, which will be the case if the // user's eth balance is insufficient to cover tx fees for the approval. -func (w *assetWallet) approvalGas(newGas *big.Int, ver uint32) (uint64, error) { - ourGas := w.gases(ver) +func (w *assetWallet) approvalGas(newGas *big.Int, contractVer uint32) (uint64, error) { + ourGas := w.gases(contractVer) if ourGas == nil { - return 0, fmt.Errorf("no gases known for %d version %d", w.assetID, ver) + return 0, fmt.Errorf("no gases known for %d contract version %d", w.assetID, contractVer) } approveGas := ourGas.Approve @@ -1595,13 +1623,13 @@ func (w *TokenWallet) FundingCoins(ids []dex.Bytes) (asset.Coins, error) { // swapReceipt implements the asset.Receipt interface for ETH. type swapReceipt struct { - txHash common.Hash - secretHash [dexeth.SecretHashSize]byte + txHash common.Hash + locator []byte // expiration and value can be determined with a blockchain // lookup, but we cache these values to avoid this. expiration time.Time value uint64 - ver uint32 + contractVer uint32 contractAddr string // specified by ver, here for naive consumers } @@ -1622,7 +1650,7 @@ func (r *swapReceipt) Coin() asset.Coin { // Contract returns the swap's identifying data, which the concatenation of the // contract version and the secret hash. func (r *swapReceipt) Contract() dex.Bytes { - return dexeth.EncodeContractData(r.ver, r.secretHash) + return dexeth.EncodeContractData(r.contractVer, r.locator) } // String returns a string representation of the swapReceipt. The secret hash @@ -1631,8 +1659,8 @@ func (r *swapReceipt) Contract() dex.Bytes { // the user can pick this information from the transaction's "to" address and // the calldata, this simplifies the process. func (r *swapReceipt) String() string { - return fmt.Sprintf("{ tx hash: %s, contract address: %s, secret hash: %x }", - r.txHash, r.contractAddr, r.secretHash) + return fmt.Sprintf("{ tx hash: %s, contract address: %s, locator: %x }", + r.txHash, r.contractAddr, r.locator) } // SignedRefund returns an empty byte array. ETH does not support a pre-signed @@ -1668,34 +1696,32 @@ func (w *ETHWallet) Swap(swaps *asset.Swaps) ([]asset.Receipt, asset.Coin, uint6 swapVal += contract.Value } - oneSwap, _, _, err := w.swapGas(1, swaps.Version) + contractVer := contractVersion(swaps.Version) + oneSwap, _, _, err := w.swapGas(1, contractVer) if err != nil { return fail("error getting gas fees: %v", err) } gasLimit := oneSwap * uint64(len(swaps.Contracts)) fees := gasLimit * swaps.FeeRate - if swapVal+fees > reservedVal { return fail("unfunded swap: %d < %d", reservedVal, swapVal+fees) } - tx, err := w.initiate(w.ctx, w.assetID, swaps.Contracts, swaps.FeeRate, gasLimit, swaps.Version) + tx, err := w.initiate(w.ctx, w.assetID, swaps.Contracts, swaps.FeeRate, gasLimit, contractVer) if err != nil { return fail("Swap: initiate error: %w", err) } txHash := tx.Hash() for _, swap := range swaps.Contracts { - var secretHash [dexeth.SecretHashSize]byte - copy(secretHash[:], swap.SecretHash) receipts = append(receipts, &swapReceipt{ expiration: time.Unix(int64(swap.LockTime), 0), value: swap.Value, txHash: txHash, - secretHash: secretHash, - ver: swaps.Version, - contractAddr: dexeth.ContractAddresses[swaps.Version][w.net].String(), + locator: acToLocator(contractVer, swap, w.addr), + contractVer: contractVer, + contractAddr: dexeth.ContractAddresses[contractVer][w.net].String(), }) } @@ -1710,6 +1736,26 @@ func (w *ETHWallet) Swap(swaps *asset.Swaps) ([]asset.Receipt, asset.Coin, uint6 return receipts, change, fees, nil } +// acToLocator converts the asset.Contract to a version-specific locator. +func acToLocator(contractVer uint32, swap *asset.Contract, from common.Address) []byte { + switch contractVer { + case 0: + return swap.SecretHash + case 1: + var secretHash [32]byte + copy(secretHash[:], swap.SecretHash) + return (&dexeth.SwapVector{ + From: from, + To: common.HexToAddress(swap.Address), + Value: swap.Value, + SecretHash: secretHash, + LockTime: swap.LockTime, + }).Locator() + default: + panic("need to add a version in acToLocator") + } +} + // Swap sends the swaps in a single transaction. The fees used returned are the // max fees that will possibly be used, since in ethereum with EIP-1559 we cannot // know exactly how much fees will be used. @@ -1735,7 +1781,9 @@ func (w *TokenWallet) Swap(swaps *asset.Swaps) ([]asset.Receipt, asset.Coin, uin swapVal += contract.Value } - oneSwap, _, approved, err := w.swapGas(1, swaps.Version) + contractVer := contractVersion(swaps.Version) + + oneSwap, _, approved, err := w.swapGas(1, contractVer) if err != nil { return fail("error getting gas fees: %v", err) } @@ -1754,7 +1802,7 @@ func (w *TokenWallet) Swap(swaps *asset.Swaps) ([]asset.Receipt, asset.Coin, uin return fail("unfunded token swap fees: %d < %d", reservedParent, fees) } - tx, err := w.initiate(w.ctx, w.assetID, swaps.Contracts, swaps.FeeRate, gasLimit, swaps.Version) + tx, err := w.initiate(w.ctx, w.assetID, swaps.Contracts, swaps.FeeRate, gasLimit, contractVer) if err != nil { return fail("Swap: initiate error: %w", err) } @@ -1767,14 +1815,12 @@ func (w *TokenWallet) Swap(swaps *asset.Swaps) ([]asset.Receipt, asset.Coin, uin txHash := tx.Hash() for _, swap := range swaps.Contracts { - var secretHash [dexeth.SecretHashSize]byte - copy(secretHash[:], swap.SecretHash) receipts = append(receipts, &swapReceipt{ expiration: time.Unix(int64(swap.LockTime), 0), value: swap.Value, txHash: txHash, - secretHash: secretHash, - ver: swaps.Version, + locator: acToLocator(contractVer, swap, w.addr), + contractVer: contractVer, contractAddr: contractAddr, }) } @@ -1825,16 +1871,19 @@ func (w *assetWallet) Redeem(form *asset.RedeemForm, feeWallet *assetWallet, non var contractVer uint32 // require a consistent version since this is a single transaction secrets := make([][32]byte, 0, n) + locators := make([][]byte, 0, n) var redeemedValue uint64 for i, redemption := range form.Redemptions { // NOTE: redemption.Spends.SecretHash is a dup of the hash extracted // from redemption.Spends.Contract. Even for scriptable UTXO assets, the // redeem script in this Contract field is redundant with the SecretHash // field as ExtractSwapDetails can be applied to extract the hash. - ver, secretHash, err := dexeth.DecodeContractData(redemption.Spends.Contract) + ver, locator, err := dexeth.DecodeLocator(redemption.Spends.Contract) if err != nil { return fail(fmt.Errorf("Redeem: invalid versioned swap contract data: %w", err)) } + + locators = append(locators, locator) if i == 0 { contractVer = ver } else if contractVer != ver { @@ -1849,20 +1898,26 @@ func (w *assetWallet) Redeem(form *asset.RedeemForm, feeWallet *assetWallet, non var secret [32]byte copy(secret[:], redemption.Secret) secrets = append(secrets, secret) - redeemable, err := w.isRedeemable(secretHash, secret, ver) + redeemable, err := w.isRedeemable(locator, secret, ver) if err != nil { return fail(fmt.Errorf("Redeem: failed to check if swap is redeemable: %w", err)) } if !redeemable { - return fail(fmt.Errorf("Redeem: secretHash %x not redeemable with secret %x", - secretHash, secret)) + return fail(fmt.Errorf("Redeem: version %d locator %x not redeemable with secret %x", + ver, locator, secret)) } - swapData, err := w.swap(w.ctx, secretHash, ver) - if err != nil { - return nil, nil, 0, fmt.Errorf("error finding swap state: %w", err) - } - redeemedValue += w.atomize(swapData.Value) + // DRAFT NOTE: With v0, we were pulling the swap value from the + // contract here. This was necessary, because we are spoofing + // asset.Redemptions in resubmitRedemption. Now, we're calling + // (contract).vector in resubmitRedemption to set the Coin. + // Net effects: a) one fewer contract call for version 0 for regular + // redemptions, b) same number of contract calls for version 0 for + // resubs. There are no contracts call for value retrieval for version + // 1, since the value is encoded in the locator, which has already been + // used in isRedeemable to verify the status. + + redeemedValue += redemption.Spends.Coin.Value() } g := w.gases(contractVer) @@ -1880,7 +1935,7 @@ func (w *assetWallet) Redeem(form *asset.RedeemForm, feeWallet *assetWallet, non gasLimit, gasFeeCap := g.Redeem*n, form.FeeSuggestion originalFundsReserved := gasLimit * gasFeeCap - gasEst, err := w.estimateRedeemGas(w.ctx, secrets, contractVer) + gasEst, err := w.estimateRedeemGas(w.ctx, secrets, locators, contractVer) if err != nil { return fail(fmt.Errorf("error getting redemption estimate: %w", err)) } @@ -1984,7 +2039,7 @@ func (w *assetWallet) isApproved() (bool, error) { // maybeApproveTokenSwapContract checks whether a token's swap contract needs // to be approved for the wallet address on the erc20 contract. -func (w *TokenWallet) maybeApproveTokenSwapContract(ver uint32, maxFeeRate, swapReserves uint64) error { +func (w *TokenWallet) maybeApproveTokenSwapContract(contractVer uint32, maxFeeRate, swapReserves uint64) error { // Check if we need to up the allowance. if approved, err := w.isApproved(); err != nil { return err @@ -2002,7 +2057,7 @@ func (w *TokenWallet) maybeApproveTokenSwapContract(ver uint32, maxFeeRate, swap if err != nil { return fmt.Errorf("error getting eth balance: %w", err) } - approveGas, err := w.approvalGas(unlimitedAllowance, ver) + approveGas, err := w.approvalGas(unlimitedAllowance, contractVer) if err != nil { return fmt.Errorf("error estimating allowance gas: %w", err) } @@ -2010,7 +2065,7 @@ func (w *TokenWallet) maybeApproveTokenSwapContract(ver uint32, maxFeeRate, swap return fmt.Errorf("parent balance %d doesn't cover contract approval (%d) and tx fees (%d)", ethBal.Available, approveGas*maxFeeRate, swapReserves) } - tx, err := w.approveToken(unlimitedAllowance, maxFeeRate, ver) + tx, err := w.approveToken(unlimitedAllowance, maxFeeRate, contractVer) if err != nil { return fmt.Errorf("token contract approval error (using max fee rate %d): %w", maxFeeRate, err) } @@ -2021,7 +2076,7 @@ func (w *TokenWallet) maybeApproveTokenSwapContract(ver uint32, maxFeeRate, swap // tokenBalance checks the token balance of the account handled by the wallet. func (w *assetWallet) tokenBalance() (bal *big.Int, err error) { // We don't care about the version. - return bal, w.withTokenContractor(w.assetID, contractVersionNewest, func(c tokenContractor) error { + return bal, w.withTokenContractor(w.assetID, contractVersionERC20, func(c tokenContractor) error { bal, err = c.balance(w.ctx) return err }) @@ -2030,7 +2085,7 @@ func (w *assetWallet) tokenBalance() (bal *big.Int, err error) { // tokenAllowance checks the amount of tokens that the swap contract is approved // to spend on behalf of the account handled by the wallet. func (w *assetWallet) tokenAllowance() (allowance *big.Int, err error) { - return allowance, w.withTokenContractor(w.assetID, contractVersionNewest, func(c tokenContractor) error { + return allowance, w.withTokenContractor(w.assetID, contractVersionERC20, func(c tokenContractor) error { allowance, err = c.allowance(w.ctx) return err }) @@ -2060,8 +2115,8 @@ func (w *assetWallet) approveToken(amount *big.Int, maxFeeRate uint64, contractV // ReserveNRedemptions locks funds for redemption. It is an error if there // is insufficient spendable balance. Part of the AccountLocker interface. -func (w *ETHWallet) ReserveNRedemptions(n uint64, ver uint32, maxFeeRate uint64) (uint64, error) { - g := w.gases(ver) +func (w *ETHWallet) ReserveNRedemptions(n uint64, serverVer uint32, maxFeeRate uint64) (uint64, error) { + g := w.gases(contractVersion(serverVer)) if g == nil { return 0, fmt.Errorf("no gas table") } @@ -2079,8 +2134,8 @@ func (w *ETHWallet) ReserveNRedemptions(n uint64, ver uint32, maxFeeRate uint64) // is insufficient spendable balance. If an approval is necessary to increase // the allowance to facilitate redemption, the approval is performed here. // Part of the AccountLocker interface. -func (w *TokenWallet) ReserveNRedemptions(n uint64, ver uint32, maxFeeRate uint64) (uint64, error) { - g := w.gases(ver) +func (w *TokenWallet) ReserveNRedemptions(n uint64, serverVer uint32, maxFeeRate uint64) (uint64, error) { + g := w.gases(contractVersion(serverVer)) if g == nil { return 0, fmt.Errorf("no gas table") } @@ -2125,8 +2180,8 @@ func (w *TokenWallet) ReReserveRedemption(req uint64) error { // ReserveNRefunds locks funds for doing refunds. It is an error if there // is insufficient spendable balance. Part of the AccountLocker interface. -func (w *ETHWallet) ReserveNRefunds(n uint64, ver uint32, maxFeeRate uint64) (uint64, error) { - g := w.gases(ver) +func (w *ETHWallet) ReserveNRefunds(n uint64, serverVer uint32, maxFeeRate uint64) (uint64, error) { + g := w.gases(contractVersion(serverVer)) if g == nil { return 0, errors.New("no gas table") } @@ -2135,8 +2190,8 @@ func (w *ETHWallet) ReserveNRefunds(n uint64, ver uint32, maxFeeRate uint64) (ui // ReserveNRefunds locks funds for doing refunds. It is an error if there // is insufficient spendable balance. Part of the AccountLocker interface. -func (w *TokenWallet) ReserveNRefunds(n uint64, ver uint32, maxFeeRate uint64) (uint64, error) { - g := w.gases(ver) +func (w *TokenWallet) ReserveNRefunds(n uint64, serverVer uint32, maxFeeRate uint64) (uint64, error) { + g := w.gases(contractVersion(serverVer)) if g == nil { return 0, errors.New("no gas table") } @@ -2212,24 +2267,73 @@ func (w *assetWallet) AuditContract(coinID, contract, serializedTx dex.Bytes, re return nil, fmt.Errorf("AuditContract: coin id != txHash - coin id: %x, txHash: %s", coinID, tx.Hash()) } - version, secretHash, err := dexeth.DecodeContractData(contract) + version, locator, err := dexeth.DecodeLocator(contract) if err != nil { return nil, fmt.Errorf("AuditContract: failed to decode contract data: %w", err) } - initiations, err := dexeth.ParseInitiateData(tx.Data(), version) - if err != nil { - return nil, fmt.Errorf("AuditContract: failed to parse initiate data: %w", err) - } + var val uint64 + var participant string + var lockTime time.Time + var secretHashB []byte + switch version { + case 0: + initiations, err := dexeth.ParseInitiateDataV0(tx.Data()) + if err != nil { + return nil, fmt.Errorf("AuditContract: failed to parse initiate data: %w", err) + } + + secretHash, err := dexeth.ParseV0Locator(locator) + if err != nil { + return nil, fmt.Errorf("error parsing v0 locator (%x): %w", locator, err) + } - initiation, ok := initiations[secretHash] - if !ok { - return nil, errors.New("AuditContract: tx does not initiate secret hash") + initiation, ok := initiations[secretHash] + if !ok { + return nil, errors.New("AuditContract: tx does not initiate secret hash") + } + val = w.atomize(initiation.Value) + participant = initiation.Participant.String() + lockTime = initiation.LockTime + secretHashB = secretHash[:] + case 1: + vec, err := dexeth.ParseV1Locator(locator) + if err != nil { + return nil, err + } + txVectors, err := dexeth.ParseInitiateDataV1(tx.Data()) + if err != nil { + return nil, fmt.Errorf("AuditContract: failed to parse initiate data: %w", err) + } + txVec, ok := txVectors[vec.SecretHash] + if !ok { + return nil, errors.New("AuditContract: tx does not initiate secret hash") + } + // Check vector equivalence. Secret hash equivalence is implied by the + // vectors presence in the map returned from ParseInitiateData. + if vec.Value != txVec.Value { + return nil, errors.New("tx data value doesn't match reported locator data") + } + if vec.To != txVec.To { + return nil, errors.New("tx to address doesn't match reported locator data") + } + if vec.From != txVec.From { + return nil, errors.New("tx from address doesn't match reported locator data") + } + if vec.LockTime != txVec.LockTime { + return nil, errors.New("tx lock time doesn't match reported locator data") + } + val = vec.Value + participant = vec.To.String() + lockTime = time.Unix(int64(vec.LockTime), 0) + secretHashB = vec.SecretHash[:] + default: + return nil, fmt.Errorf("unknown contract version %d", version) } coin := &coin{ id: txHash, - value: w.atomize(initiation.Value), + value: val, } // The counter-party should have broadcasted the contract tx but rebroadcast @@ -2244,11 +2348,11 @@ func (w *assetWallet) AuditContract(coinID, contract, serializedTx dex.Bytes, re } return &asset.AuditInfo{ - Recipient: initiation.Participant.Hex(), - Expiration: initiation.LockTime, + Recipient: participant, + Expiration: lockTime, Coin: coin, Contract: contract, - SecretHash: secretHash[:], + SecretHash: secretHashB, }, nil } @@ -2266,26 +2370,25 @@ func (w *assetWallet) LockTimeExpired(ctx context.Context, lockTime time.Time) ( // ContractLockTimeExpired returns true if the specified contract's locktime has // expired, making it possible to issue a Refund. func (w *assetWallet) ContractLockTimeExpired(ctx context.Context, contract dex.Bytes) (bool, time.Time, error) { - contractVer, secretHash, err := dexeth.DecodeContractData(contract) + contractVer, locator, err := dexeth.DecodeLocator(contract) if err != nil { return false, time.Time{}, err } - swap, err := w.swap(ctx, secretHash, contractVer) + status, vec, err := w.statusAndVector(ctx, locator, contractVer) if err != nil { return false, time.Time{}, err - } - - // Time is not yet set for uninitiated swaps. - if swap.State == dexeth.SSNone { + } else if status.Step == dexeth.SSNone { return false, time.Time{}, asset.ErrSwapNotInitiated } - expired, err := w.LockTimeExpired(ctx, swap.LockTime) + lockTime := time.Unix(int64(vec.LockTime), 0) + + expired, err := w.LockTimeExpired(ctx, lockTime) if err != nil { return false, time.Time{}, err } - return expired, swap.LockTime, nil + return expired, lockTime, nil } // findRedemptionResult is used internally for queued findRedemptionRequests. @@ -2302,21 +2405,21 @@ type findRedemptionRequest struct { // sendFindRedemptionResult sends the result or logs a message if it cannot be // sent. -func (eth *baseWallet) sendFindRedemptionResult(req *findRedemptionRequest, secretHash [32]byte, secret []byte, err error) { +func (eth *baseWallet) sendFindRedemptionResult(req *findRedemptionRequest, locator, secret []byte, err error) { select { case req.res <- &findRedemptionResult{secret: secret, err: err}: default: - eth.log.Info("findRedemptionResult channel blocking for request %s", secretHash) + eth.log.Info("findRedemptionResult channel blocking for request %x", locator) } } // findRedemptionRequests creates a copy of the findRedemptionReqs map. -func (w *assetWallet) findRedemptionRequests() map[[32]byte]*findRedemptionRequest { +func (w *assetWallet) findRedemptionRequests() map[string]*findRedemptionRequest { w.findRedemptionMtx.RLock() defer w.findRedemptionMtx.RUnlock() - reqs := make(map[[32]byte]*findRedemptionRequest, len(w.findRedemptionReqs)) - for secretHash, req := range w.findRedemptionReqs { - reqs[secretHash] = req + reqs := make(map[string]*findRedemptionRequest, len(w.findRedemptionReqs)) + for loc, req := range w.findRedemptionReqs { + reqs[loc] = req } return reqs } @@ -2325,13 +2428,13 @@ func (w *assetWallet) findRedemptionRequests() map[[32]byte]*findRedemptionReque // but un-redeemed and un-refunded, FindRedemption will block until a redemption // is seen. func (w *assetWallet) FindRedemption(ctx context.Context, _, contract dex.Bytes) (redemptionCoin, secret dex.Bytes, err error) { - contractVer, secretHash, err := dexeth.DecodeContractData(contract) + contractVer, locator, err := dexeth.DecodeLocator(contract) if err != nil { return nil, nil, err } // See if it's ready right away. - secret, err = w.findSecret(secretHash, contractVer) + secret, err = w.findSecret(locator, contractVer) if err != nil { return nil, nil, err } @@ -2346,14 +2449,16 @@ func (w *assetWallet) FindRedemption(ctx context.Context, _, contract dex.Bytes) res: make(chan *findRedemptionResult, 1), } + locatorKey := string(locator) + w.findRedemptionMtx.Lock() - if w.findRedemptionReqs[secretHash] != nil { + if w.findRedemptionReqs[locatorKey] != nil { w.findRedemptionMtx.Unlock() - return nil, nil, fmt.Errorf("duplicate find redemption request for %x", secretHash) + return nil, nil, fmt.Errorf("duplicate find redemption request for %x", locator) } - w.findRedemptionReqs[secretHash] = req + w.findRedemptionReqs[locatorKey] = req w.findRedemptionMtx.Unlock() @@ -2364,11 +2469,11 @@ func (w *assetWallet) FindRedemption(ctx context.Context, _, contract dex.Bytes) } w.findRedemptionMtx.Lock() - delete(w.findRedemptionReqs, secretHash) + delete(w.findRedemptionReqs, locatorKey) w.findRedemptionMtx.Unlock() if res == nil { - return nil, nil, fmt.Errorf("context cancelled for find redemption request %x", secretHash) + return nil, nil, fmt.Errorf("context cancelled for find redemption request %x", locator) } if res.err != nil { @@ -2378,64 +2483,64 @@ func (w *assetWallet) FindRedemption(ctx context.Context, _, contract dex.Bytes) return findRedemptionCoinID, res.secret[:], nil } -func (w *assetWallet) findSecret(secretHash [32]byte, contractVer uint32) ([]byte, error) { +func (w *assetWallet) findSecret(locator []byte, contractVer uint32) ([]byte, error) { ctx, cancel := context.WithTimeout(w.ctx, 10*time.Second) defer cancel() - swap, err := w.swap(ctx, secretHash, contractVer) + status, err := w.status(ctx, locator, contractVer) if err != nil { return nil, err } - switch swap.State { + switch status.Step { case dexeth.SSInitiated: return nil, nil // no redeem yet, but keep checking case dexeth.SSRedeemed: - return swap.Secret[:], nil + return status.Secret[:], nil case dexeth.SSNone: - return nil, fmt.Errorf("swap %x does not exist", secretHash) + return nil, fmt.Errorf("swap version %d at %x does not exist", contractVer, locator) case dexeth.SSRefunded: - return nil, fmt.Errorf("swap %x is already refunded", secretHash) + return nil, fmt.Errorf("swap version %d at %x is already refunded", contractVer, locator) } - return nil, fmt.Errorf("unrecognized swap state %v", swap.State) + return nil, fmt.Errorf("unrecognized swap state %v", status.Step) } // Refund refunds a contract. This can only be used after the time lock has // expired. func (w *assetWallet) Refund(_, contract dex.Bytes, feeRate uint64) (dex.Bytes, error) { - version, secretHash, err := dexeth.DecodeContractData(contract) + contractVer, locator, err := dexeth.DecodeLocator(contract) if err != nil { return nil, fmt.Errorf("Refund: failed to decode contract: %w", err) } - swap, err := w.swap(w.ctx, secretHash, version) + status, err := w.status(w.ctx, locator, contractVer) if err != nil { return nil, err } // It's possible the swap was refunded by someone else. In that case we // cannot know the refunding tx hash. - switch swap.State { + switch status.Step { case dexeth.SSInitiated: // good, check refundability case dexeth.SSNone: return nil, asset.ErrSwapNotInitiated case dexeth.SSRefunded: - w.log.Infof("Swap with secret hash %x already refunded.", secretHash) + w.log.Infof("Swap with secret hash %x already refunded.", locator) zeroHash := common.Hash{} return zeroHash[:], nil case dexeth.SSRedeemed: - w.log.Infof("Swap with secret hash %x already redeemed with secret key %x.", - secretHash, swap.Secret) + w.log.Infof("Swap with locator %x already redeemed with secret key %x.", + locator, status.Secret) return nil, asset.CoinNotFoundError // so caller knows to FindRedemption } - refundable, err := w.isRefundable(secretHash, version) + refundable, err := w.isRefundable(locator, contractVer) if err != nil { return nil, fmt.Errorf("Refund: failed to check isRefundable: %w", err) } if !refundable { - return nil, fmt.Errorf("Refund: swap with secret hash %x is not refundable", secretHash) + return nil, fmt.Errorf("Refund: swap with locator %x is not refundable", locator) } - tx, err := w.refund(secretHash, feeRate, version) + tx, err := w.refund(locator, feeRate, contractVer) if err != nil { return nil, fmt.Errorf("Refund: failed to call refund: %w", err) } @@ -2506,7 +2611,7 @@ func (w *ETHWallet) EstimateRegistrationTxFee(feeRate uint64) uint64 { // EstimateRegistrationTxFee returns an estimate for the tx fee needed to // pay the registration fee using the provided feeRate. func (w *TokenWallet) EstimateRegistrationTxFee(feeRate uint64) uint64 { - g := w.gases(contractVersionNewest) + g := w.gases(contractVersionERC20) if g == nil { w.log.Errorf("no gas table") return math.MaxUint64 @@ -2577,7 +2682,7 @@ func (w *TokenWallet) canSend(value uint64, isPreEstimate bool) (uint64, *big.In return 0, nil, fmt.Errorf("error getting max fee rate: %w", err) } - g := w.gases(contractVersionNewest) + g := w.gases(contractVersionERC20) if g == nil { return 0, nil, fmt.Errorf("gas table not found") } @@ -2676,7 +2781,7 @@ func (w *assetWallet) RestorationInfo(seed []byte) ([]*asset.WalletRestoration, // SwapConfirmations gets the number of confirmations and the spend status // for the specified swap. func (w *assetWallet) SwapConfirmations(ctx context.Context, coinID dex.Bytes, contract dex.Bytes, _ time.Time) (confs uint32, spent bool, err error) { - contractVer, secretHash, err := dexeth.DecodeContractData(contract) + contractVer, secretHash, err := dexeth.DecodeLocator(contract) if err != nil { return 0, false, err } @@ -2689,17 +2794,35 @@ func (w *assetWallet) SwapConfirmations(ctx context.Context, coinID dex.Bytes, c return 0, false, fmt.Errorf("error fetching best header: %w", err) } - swapData, err := w.swap(w.ctx, secretHash, contractVer) + status, err := w.status(w.ctx, secretHash, contractVer) if err != nil { return 0, false, fmt.Errorf("error finding swap state: %w", err) } - if swapData.State == dexeth.SSNone { + if status.Step == dexeth.SSNone { return 0, false, asset.ErrSwapNotInitiated } - spent = swapData.State >= dexeth.SSRedeemed - confs = uint32(hdr.Number.Uint64() - swapData.BlockHeight + 1) + spent = status.Step >= dexeth.SSRedeemed + confs = uint32(hdr.Number.Uint64() - status.BlockHeight + 1) + + // NOTE: confs will equal to the block number for the version 1 contract if spent == true, + // because BlockHeight will be zero. This is probably fine, since the caller + // will examine spent first? Otherwise, we could do this. + // + // if spent && contractVer == 1 { + // // If it's spent and the contract version is 1, the status will only + // // contain the secret OR the block number, not both. + // var txHash common.Hash + // copy(txHash[:], coinID) + // confs, err = w.node.transactionConfirmations(ctx, txHash) + // if err != nil { + // return 0, false, fmt.Errorf("error finding swap state: %w", err) + // } + // } + // + // Or maybe it'd be better to set confs to some constant, large, round + // number so it doesn't look dumb in logs. return } @@ -2822,7 +2945,7 @@ func (eth *assetWallet) DynamicRedemptionFeesPaid(ctx context.Context, coinID, c // secret hashes. func (eth *baseWallet) swapOrRedemptionFeesPaid(ctx context.Context, coinID, contractData dex.Bytes, isInit bool) (fee uint64, secretHashes [][]byte, err error) { - contractVer, secretHash, err := dexeth.DecodeContractData(contractData) + contractVer, locator, err := dexeth.DecodeLocator(contractData) if err != nil { return 0, nil, err } @@ -2854,41 +2977,80 @@ func (eth *baseWallet) swapOrRedemptionFeesPaid(ctx context.Context, coinID, con effectiveGasPrice := new(big.Int).Add(hdr.BaseFee, tx.EffectiveGasTipValue(hdr.BaseFee)) bigFees := new(big.Int).Mul(effectiveGasPrice, big.NewInt(int64(receipt.GasUsed))) - if isInit { - inits, err := dexeth.ParseInitiateData(tx.Data(), contractVer) - if err != nil { - return 0, nil, fmt.Errorf("invalid initiate data: %v", err) - } - secretHashes = make([][]byte, 0, len(inits)) - for k := range inits { - copyK := k - secretHashes = append(secretHashes, copyK[:]) - } - } else { - redeems, err := dexeth.ParseRedeemData(tx.Data(), contractVer) - if err != nil { - return 0, nil, fmt.Errorf("invalid redeem data: %v", err) - } - secretHashes = make([][]byte, 0, len(redeems)) - for k := range redeems { - copyK := k - secretHashes = append(secretHashes, copyK[:]) - } + locators, secretHashes, err := parseSecretHashes(tx, contractVer, isInit) + if err != nil { + return 0, nil, err } - sort.Slice(secretHashes, func(i, j int) bool { return bytes.Compare(secretHashes[i], secretHashes[j]) < 0 }) + + sort.Slice(locators, func(i, j int) bool { return bytes.Compare(locators[i], locators[j]) < 0 }) var found bool - for i := range secretHashes { - if bytes.Equal(secretHash[:], secretHashes[i]) { + for i := range locators { + if bytes.Equal(locator, locators[i]) { found = true break } } if !found { - return 0, nil, fmt.Errorf("secret hash %x not found in transaction", secretHash) + return 0, nil, fmt.Errorf("locator %x not found in transaction", locator) } return dexeth.WeiToGwei(bigFees), secretHashes, nil } +func parseSecretHashes(tx *types.Transaction, contractVer uint32, isInit bool) (locators, secretHashes [][]byte, err error) { + switch contractVer { + case 0: + if isInit { + inits, err := dexeth.ParseInitiateDataV0(tx.Data()) + if err != nil { + return nil, nil, fmt.Errorf("invalid initiate data: %v", err) + } + locators = make([][]byte, 0, len(inits)) + for k := range inits { + copyK := k // TODO: Is this really necessary? + locators = append(locators, copyK[:]) + } + } else { + redeems, err := dexeth.ParseRedeemDataV0(tx.Data()) + if err != nil { + return nil, nil, fmt.Errorf("invalid redeem data: %v", err) + } + locators = make([][]byte, 0, len(redeems)) + for k := range redeems { + copyK := k + locators = append(locators, copyK[:]) + } + } + return locators, locators, nil + case 1: + if isInit { + vectors, err := dexeth.ParseInitiateDataV1(tx.Data()) + if err != nil { + return nil, nil, fmt.Errorf("invalid initiate data: %v", err) + } + locators = make([][]byte, 0, len(vectors)) + secretHashes = make([][]byte, 0, len(vectors)) + for _, vec := range vectors { + locators = append(locators, vec.Locator()) + secretHashes = append(secretHashes, vec.SecretHash[:]) + } + } else { + redeems, err := dexeth.ParseRedeemDataV1(tx.Data()) + if err != nil { + return nil, nil, fmt.Errorf("invalid redeem data: %v", err) + } + locators = make([][]byte, 0, len(redeems)) + secretHashes = make([][]byte, 0, len(redeems)) + for secretHash, r := range redeems { + locators = append(locators, r.Contract.Locator()) + secretHashes = append(secretHashes, secretHash[:]) + } + } + return locators, secretHashes, nil + default: + return nil, nil, fmt.Errorf("unknown server version %d", contractVer) + } +} + // RegFeeConfirmations gets the number of confirmations for the specified // transaction. func (eth *baseWallet) RegFeeConfirmations(ctx context.Context, coinID dex.Bytes) (confs uint32, err error) { @@ -3145,33 +3307,50 @@ func (w *assetWallet) monitorTx(tx *types.Transaction, blockSubmitted uint64) { // in the batch that are still redeemable are included in the new transaction. // nonceOverride is set to a non-nil value when a specific nonce is required // (when a transaction has not been mined due to a low fee). -func (w *assetWallet) resubmitRedemption(tx *types.Transaction, contractVersion uint32, nonceOverride *uint64, feeWallet *assetWallet, monitoredTx *monitoredTx) (*common.Hash, error) { - parsedRedemptions, err := dexeth.ParseRedeemData(tx.Data(), contractVersion) +func (w *assetWallet) resubmitRedemption(tx *types.Transaction, contractVer uint32, + nonceOverride *uint64, feeWallet *assetWallet, monitoredTx *monitoredTx) (*common.Hash, error) { + + locators, secrets, err := parseRedeemLocatorsAndSecrets(tx, contractVer) if err != nil { - return nil, fmt.Errorf("failed to parse redeem data: %w", err) + return nil, err } - redemptions := make([]*asset.Redemption, 0, len(parsedRedemptions)) + redemptions := make([]*asset.Redemption, 0, len(locators)) // Whether or not a swap can be redeemed is checked in Redeem, but here // we filter out unredeemable swaps in case one of the swaps in the tx // was refunded/redeemed but the others were not. - for _, r := range parsedRedemptions { - redeemable, err := w.isRedeemable(r.SecretHash, r.Secret, contractVersion) + for i, locator := range locators { + secret := secrets[i] + redeemable, err := w.isRedeemable(locator, secret, contractVer) if err != nil { return nil, err } else if !redeemable { - w.log.Warnf("swap %x is not redeemable. not resubmitting", r.SecretHash) + w.log.Warnf("swap with locator %x is not redeemable. not resubmitting", locator) continue } - contractData := dexeth.EncodeContractData(contractVersion, r.SecretHash) + // DRAFT NOTE: This essentially replaces the call to (contractorV0).swap + // that was in (*assetWallet.).Redeem. For v0, it'll still be a + // contract call. For v1, we have the value encoded in the locator, so + // no contract call required. + vec, err := w.vector(w.ctx, locator, contractVer) + if err != nil { + return nil, err + } + redemptions = append(redemptions, &asset.Redemption{ Spends: &asset.AuditInfo{ - Contract: contractData, - SecretHash: r.SecretHash[:], + Recipient: vec.To.String(), + Expiration: time.Unix(int64(vec.LockTime), 0), + Coin: &coin{ + // id: txHash, // Just no way. + value: vec.Value, + }, + Contract: dexeth.EncodeContractData(contractVer, locator), + SecretHash: vec.SecretHash[:], }, - Secret: r.Secret[:], + Secret: secret[:], }) } if len(redemptions) == 0 { @@ -3202,15 +3381,46 @@ func (w *assetWallet) resubmitRedemption(tx *types.Transaction, contractVersion return &replacementHash, nil } +// parseRedeemLocatorsAndSecrets parses the locators and secrets for the given +// redemption transaction. +func parseRedeemLocatorsAndSecrets(tx *types.Transaction, contractVer uint32) (locators [][]byte, secrets [][32]byte, err error) { + switch contractVer { + case 0: + redemps, err := dexeth.ParseRedeemDataV0(tx.Data()) + if err != nil { + return nil, nil, fmt.Errorf("failed to parse v0 redeem data: %w", err) + } + locators, secrets = make([][]byte, 0, len(redemps)), make([][32]byte, 0, len(redemps)) + for _, r := range redemps { + locators = append(locators, r.SecretHash[:]) + secrets = append(secrets, r.Secret) + } + return locators, secrets, nil + case 1: + redemps, err := dexeth.ParseRedeemDataV1(tx.Data()) + if err != nil { + return nil, nil, fmt.Errorf("failed to parse v1 redeem data: %w", err) + } + locators, secrets = make([][]byte, 0, len(redemps)), make([][32]byte, 0, len(redemps)) + for _, r := range redemps { + locators = append(locators, r.Contract.Locator()) + secrets = append(secrets, r.Secret) + } + return locators, secrets, nil + default: + return nil, nil, fmt.Errorf("unknown redemption contract version %d", contractVer) + } +} + // swapIsRedeemed checks if a swap is in the redeemed state. ErrSwapRefunded // is returned if the swap has been refunded. -func (w *assetWallet) swapIsRedeemed(secretHash common.Hash, contractVersion uint32) (bool, error) { - swap, err := w.swap(w.ctx, secretHash, contractVersion) +func (w *assetWallet) swapIsRedeemed(locator []byte, contractVer uint32) (bool, error) { + status, err := w.status(w.ctx, locator, contractVer) if err != nil { return false, err } - switch swap.State { + switch status.Step { case dexeth.SSRedeemed: return true, nil case dexeth.SSRefunded: @@ -3258,9 +3468,9 @@ func confStatus(confs uint64, txHash common.Hash) *asset.ConfirmRedemptionStatus // -- resubmits the tx with a new nonce if it has been nonce replaced // -- resubmits the tx with the same nonce but higher fee if the fee is too low // -- otherwise, resubmits the same tx to ensure propagation -func (w *assetWallet) checkUnconfirmedRedemption(secretHash common.Hash, contractVer uint32, txHash common.Hash, tx *types.Transaction, currentTip uint64, feeWallet *assetWallet, monitoredTx *monitoredTx) (*asset.ConfirmRedemptionStatus, error) { +func (w *assetWallet) checkUnconfirmedRedemption(locator []byte, contractVer uint32, txHash common.Hash, tx *types.Transaction, currentTip uint64, feeWallet *assetWallet, monitoredTx *monitoredTx) (*asset.ConfirmRedemptionStatus, error) { // Check if the swap has been redeemed by another transaction we are unaware of. - swapIsRedeemed, err := w.swapIsRedeemed(secretHash, contractVer) + swapIsRedeemed, err := w.swapIsRedeemed(locator, contractVer) if err != nil { return nil, err } @@ -3317,7 +3527,7 @@ func (w *assetWallet) checkUnconfirmedRedemption(secretHash common.Hash, contrac // entire redemption batch, a new transaction containing only the swap we are // searching for will be created. func (w *assetWallet) confirmRedemptionWithoutMonitoredTx(txHash common.Hash, redemption *asset.Redemption, feeWallet *assetWallet) (*asset.ConfirmRedemptionStatus, error) { - contractVer, secretHash, err := dexeth.DecodeContractData(redemption.Spends.Contract) + contractVer, locator, err := dexeth.DecodeLocator(redemption.Spends.Contract) if err != nil { return nil, fmt.Errorf("failed to decode contract data: %w", err) } @@ -3330,7 +3540,7 @@ func (w *assetWallet) confirmRedemptionWithoutMonitoredTx(txHash common.Hash, re tx, txBlock, err := w.node.getTransaction(w.ctx, txHash) if errors.Is(err, asset.CoinNotFoundError) { w.log.Errorf("ConfirmRedemption: geth could not find tx: %s", txHash) - swapIsRedeemed, err := w.swapIsRedeemed(secretHash, contractVer) + swapIsRedeemed, err := w.swapIsRedeemed(locator, contractVer) if err != nil { return nil, err } @@ -3382,7 +3592,7 @@ func (w *assetWallet) confirmRedemptionWithoutMonitoredTx(txHash common.Hash, re return confStatus(confirmations, txHash), nil } - return w.checkUnconfirmedRedemption(secretHash, contractVer, txHash, tx, currentTip, feeWallet, nil) + return w.checkUnconfirmedRedemption(locator, contractVer, txHash, tx, currentTip, feeWallet, nil) } // confirmRedemption checks the confirmation status of a redemption transaction. @@ -3410,7 +3620,7 @@ func (w *assetWallet) confirmRedemption(coinID dex.Bytes, redemption *asset.Rede txHash = monitoredTxHash } - contractVer, secretHash, err := dexeth.DecodeContractData(redemption.Spends.Contract) + contractVer, locator, err := dexeth.DecodeLocator(redemption.Spends.Contract) if err != nil { return nil, fmt.Errorf("failed to decode contract data: %w", err) } @@ -3435,7 +3645,7 @@ func (w *assetWallet) confirmRedemption(coinID dex.Bytes, redemption *asset.Rede return confStatus(0, txHash), nil } - swapIsRedeemed, err := w.swapIsRedeemed(secretHash, contractVer) + swapIsRedeemed, err := w.swapIsRedeemed(locator, contractVer) if err != nil { return nil, err } @@ -3484,17 +3694,18 @@ func (w *assetWallet) confirmRedemption(coinID dex.Bytes, redemption *asset.Rede return confStatus(0, txHash), nil } - return w.checkUnconfirmedRedemption(secretHash, contractVer, txHash, tx, currentTip, feeWallet, monitoredTx) + return w.checkUnconfirmedRedemption(locator, contractVer, txHash, tx, currentTip, feeWallet, monitoredTx) } // checkFindRedemptions checks queued findRedemptionRequests. func (w *assetWallet) checkFindRedemptions() { - for secretHash, req := range w.findRedemptionRequests() { - secret, err := w.findSecret(secretHash, req.contractVer) + for loc, req := range w.findRedemptionRequests() { + locator := []byte(loc) + secret, err := w.findSecret(locator, req.contractVer) if err != nil { - w.sendFindRedemptionResult(req, secretHash, nil, err) + w.sendFindRedemptionResult(req, locator, nil, err) } else if len(secret) > 0 { - w.sendFindRedemptionResult(req, secretHash, secret, nil) + w.sendFindRedemptionResult(req, locator, secret, nil) } } } @@ -3596,7 +3807,7 @@ func (w *ETHWallet) sendToAddr(addr common.Address, amt uint64, maxFeeRate *big. func (w *TokenWallet) sendToAddr(addr common.Address, amt uint64, maxFeeRate *big.Int) (tx *types.Transaction, err error) { w.baseWallet.nonceSendMtx.Lock() defer w.baseWallet.nonceSendMtx.Unlock() - g := w.gases(contractVersionNewest) + g := w.gases(contractVersionERC20) if g == nil { return nil, fmt.Errorf("no gas table") } @@ -3604,7 +3815,7 @@ func (w *TokenWallet) sendToAddr(addr common.Address, amt uint64, maxFeeRate *bi if err != nil { return nil, err } - return tx, w.withTokenContractor(w.assetID, contractVersionNewest, func(c tokenContractor) error { + return tx, w.withTokenContractor(w.assetID, contractVersionERC20, func(c tokenContractor) error { tx, err = c.transfer(txOpts, addr, w.evmify(amt)) if err != nil { c.voidUnusedNonce() @@ -3614,10 +3825,27 @@ func (w *TokenWallet) sendToAddr(addr common.Address, amt uint64, maxFeeRate *bi }) } -// swap gets a swap keyed by secretHash in the contract. -func (w *assetWallet) swap(ctx context.Context, secretHash [32]byte, contractVer uint32) (swap *dexeth.SwapState, err error) { - return swap, w.withContractor(contractVer, func(c contractor) error { - swap, err = c.swap(ctx, secretHash) +// status fetches the SwapStatus for the locator and contract version. +func (w *assetWallet) status(ctx context.Context, locator []byte, contractVer uint32) (s *dexeth.SwapStatus, err error) { + return s, w.withContractor(contractVer, func(c contractor) error { + s, err = c.status(ctx, locator) + return err + }) +} + +// vector fetches the SwapVector for the locator and contract version. +func (w *assetWallet) vector(ctx context.Context, locator []byte, contractVer uint32) (v *dexeth.SwapVector, err error) { + return v, w.withContractor(contractVer, func(c contractor) error { + v, err = c.vector(ctx, locator) + return err + }) +} + +// statusAndVector fetches the SwapStatus and SwapVector for the locator and +// contract version. +func (w *assetWallet) statusAndVector(ctx context.Context, locator []byte, contractVer uint32) (s *dexeth.SwapStatus, v *dexeth.SwapVector, err error) { + return s, v, w.withContractor(contractVer, func(c contractor) error { + s, v, err = c.statusAndVector(ctx, locator) return err }) } @@ -3658,17 +3886,17 @@ func (w *assetWallet) estimateInitGas(ctx context.Context, numSwaps int, contrac } // estimateRedeemGas checks the amount of gas that is used for the redemption. -func (w *assetWallet) estimateRedeemGas(ctx context.Context, secrets [][32]byte, contractVer uint32) (gas uint64, err error) { +func (w *assetWallet) estimateRedeemGas(ctx context.Context, secrets [][32]byte, locators [][]byte, contractVer uint32) (gas uint64, err error) { return gas, w.withContractor(contractVer, func(c contractor) error { - gas, err = c.estimateRedeemGas(ctx, secrets) + gas, err = c.estimateRedeemGas(ctx, secrets, locators) return err }) } // estimateRefundGas checks the amount of gas that is used for a refund. -func (w *assetWallet) estimateRefundGas(ctx context.Context, secretHash [32]byte, contractVer uint32) (gas uint64, err error) { +func (w *assetWallet) estimateRefundGas(ctx context.Context, locator []byte, contractVer uint32) (gas uint64, err error) { return gas, w.withContractor(contractVer, func(c contractor) error { - gas, err = c.estimateRefundGas(ctx, secretHash) + gas, err = c.estimateRefundGas(ctx, locator) return err }) } @@ -3706,7 +3934,8 @@ func (w *assetWallet) loadContractors() error { // withContractor runs the provided function with the versioned contractor. func (w *assetWallet) withContractor(contractVer uint32, f func(contractor) error) error { - if contractVer == contractVersionNewest { + if contractVer == contractVersionERC20 { + // For ERC02 methods, use the most recent contractor version. var bestVer uint32 var bestContractor contractor for ver, c := range w.contractors { @@ -3738,7 +3967,7 @@ func (w *assetWallet) withTokenContractor(assetID, ver uint32, f func(tokenContr // estimateApproveGas estimates the gas required for a transaction approving a // spender for an ERC20 contract. func (w *assetWallet) estimateApproveGas(newGas *big.Int) (gas uint64, err error) { - return gas, w.withTokenContractor(w.assetID, contractVersionNewest, func(c tokenContractor) error { + return gas, w.withTokenContractor(w.assetID, contractVersionERC20, func(c tokenContractor) error { gas, err = c.estimateApproveGas(w.ctx, newGas) return err }) @@ -3747,7 +3976,7 @@ func (w *assetWallet) estimateApproveGas(newGas *big.Int) (gas uint64, err error // estimateTransferGas estimates the gas needed for a token transfer call to an // ERC20 contract. func (w *assetWallet) estimateTransferGas(val uint64) (gas uint64, err error) { - return gas, w.withTokenContractor(w.assetID, contractVersionNewest, func(c tokenContractor) error { + return gas, w.withTokenContractor(w.assetID, contractVersionERC20, func(c tokenContractor) error { gas, err = c.estimateTransferGas(w.ctx, w.evmify(val)) return err }) @@ -3785,7 +4014,7 @@ func (w *assetWallet) redeem(ctx context.Context, assetID uint32, redemptions [] // refund refunds a swap contract using the account controlled by the wallet. // Any on-chain failure, such as the locktime not being past, will not cause // this to error. -func (w *assetWallet) refund(secretHash [32]byte, maxFeeRate uint64, contractVer uint32) (tx *types.Transaction, err error) { +func (w *assetWallet) refund(locator []byte, maxFeeRate uint64, contractVer uint32) (tx *types.Transaction, err error) { gas := w.gases(contractVer) if gas == nil { return nil, fmt.Errorf("no gas table for asset %d, version %d", w.assetID, contractVer) @@ -3798,7 +4027,7 @@ func (w *assetWallet) refund(secretHash [32]byte, maxFeeRate uint64, contractVer } return tx, w.withContractor(contractVer, func(c contractor) error { - tx, err = c.refund(txOpts, secretHash) + tx, err = c.refund(txOpts, locator) if err != nil { c.voidUnusedNonce() return err @@ -3808,16 +4037,16 @@ func (w *assetWallet) refund(secretHash [32]byte, maxFeeRate uint64, contractVer } // isRedeemable checks if the swap identified by secretHash is redeemable using secret. -func (w *assetWallet) isRedeemable(secretHash [32]byte, secret [32]byte, contractVer uint32) (redeemable bool, err error) { +func (w *assetWallet) isRedeemable(locator []byte, secret [32]byte, contractVer uint32) (redeemable bool, err error) { return redeemable, w.withContractor(contractVer, func(c contractor) error { - redeemable, err = c.isRedeemable(secretHash, secret) + redeemable, err = c.isRedeemable(locator, secret) return err }) } -func (w *assetWallet) isRefundable(secretHash [32]byte, contractVer uint32) (refundable bool, err error) { +func (w *assetWallet) isRefundable(locator []byte, contractVer uint32) (refundable bool, err error) { return refundable, w.withContractor(contractVer, func(c contractor) error { - refundable, err = c.isRefundable(secretHash) + refundable, err = c.isRefundable(locator) return err }) } @@ -3825,7 +4054,6 @@ func (w *assetWallet) isRefundable(secretHash [32]byte, contractVer uint32) (ref func checkTxStatus(receipt *types.Receipt, gasLimit uint64) error { if receipt.Status != types.ReceiptStatusSuccessful { return fmt.Errorf("transaction status failed") - } if receipt.GasUsed > gasLimit { @@ -3981,9 +4209,8 @@ func GetGasEstimates(ctx context.Context, cl ethFetcher, c contractor, maxSwaps } // Estimate a refund - var firstSecretHash [32]byte - copy(firstSecretHash[:], contracts[0].SecretHash) - refundGas, err := c.estimateRefundGas(ctx, firstSecretHash) + // TODO: Update for version 1. + refundGas, err := c.estimateRefundGas(ctx, contracts[0].SecretHash) if err != nil { return fmt.Errorf("error estimate refund gas: %w", err) } diff --git a/client/asset/eth/eth_test.go b/client/asset/eth/eth_test.go index 5d386787eb..7c7b1b4b1d 100644 --- a/client/asset/eth/eth_test.go +++ b/client/asset/eth/eth_test.go @@ -45,17 +45,30 @@ var ( testAddressB = common.HexToAddress("8d83B207674bfd53B418a6E47DA148F5bFeCc652") testAddressC = common.HexToAddress("2b84C791b79Ee37De042AD2ffF1A253c3ce9bc27") - ethGases = dexeth.VersionedGases[0] - tokenGases = dexeth.Tokens[simnetTokenID].NetTokens[dex.Simnet].SwapContracts[0].Gas + ethGasesV0 = dexeth.VersionedGases[0] + tokenGasesV0 = dexeth.Tokens[simnetTokenID].NetTokens[dex.Simnet].SwapContracts[0].Gas + ethGasesV1 = dexeth.VersionedGases[1] + tokenGasesV1 = dexeth.Tokens[simnetTokenID].NetTokens[dex.Simnet].SwapContracts[1].Gas - tETH = &dex.Asset{ - // Version meaning? + tETHV0 = &dex.Asset{ + Version: 0, + ID: 60, + Symbol: "ETH", + MaxFeeRate: 100, + SwapSize: ethGasesV0.Swap, + SwapSizeBase: ethGasesV0.Swap, + RedeemSize: ethGasesV0.Redeem, + SwapConf: 1, + } + + tETHV1 = &dex.Asset{ + Version: 1, ID: 60, Symbol: "ETH", MaxFeeRate: 100, - SwapSize: ethGases.Swap, - SwapSizeBase: ethGases.Swap, - RedeemSize: ethGases.Redeem, + SwapSize: ethGasesV1.Swap, + SwapSizeBase: ethGasesV1.Swap, + RedeemSize: ethGasesV1.Redeem, SwapConf: 1, } @@ -69,13 +82,24 @@ var ( SwapConf: 1, } - tToken = &dex.Asset{ + tTokenV0 = &dex.Asset{ ID: simnetTokenID, Symbol: "dextt.eth", Version: 0, - SwapSize: tokenGases.Swap, - SwapSizeBase: tokenGases.Swap, - RedeemSize: tokenGases.Redeem, + SwapSize: tokenGasesV0.Swap, + SwapSizeBase: tokenGasesV0.Swap, + RedeemSize: tokenGasesV0.Redeem, + MaxFeeRate: 20, + SwapConf: 1, + } + + tTokenV1 = &dex.Asset{ + ID: simnetTokenID, + Symbol: "dextt.eth", + Version: 1, + SwapSize: tokenGasesV1.Swap, + SwapSizeBase: tokenGasesV1.Swap, + RedeemSize: tokenGasesV1.Redeem, MaxFeeRate: 20, SwapConf: 1, } @@ -275,7 +299,7 @@ type tContractor struct { redeemGasOverride *uint64 redeemable bool redeemableErr error - redeemableMap map[common.Hash]bool + redeemableMap map[string]bool valueIn map[common.Hash]uint64 valueOut map[common.Hash]uint64 valueErr error @@ -291,6 +315,76 @@ type tContractor struct { } } +func (c *tContractor) status(ctx context.Context, locator []byte) (*dexeth.SwapStatus, error) { + if c.swapErr != nil { + return nil, c.swapErr + } + vector, err := c.vector(ctx, locator) + if err != nil { + return nil, err + } + swap, ok := c.swapMap[vector.SecretHash] + if !ok { + return nil, errors.New("swap not in map") + } + s := &dexeth.SwapStatus{ + Step: swap.State, + Secret: swap.Secret, + BlockHeight: swap.BlockHeight, + } + return s, nil +} + +func (c *tContractor) vector(ctx context.Context, locator []byte) (*dexeth.SwapVector, error) { + if c.swapErr != nil { + return nil, c.swapErr + } + if len(locator) == dexeth.LocatorV1Length { + return dexeth.ParseV1Locator(locator) + } + var secretHash [32]byte + copy(secretHash[:], locator) + swap, ok := c.swapMap[secretHash] + if !ok { + return nil, errors.New("swap not in map") + } + v := &dexeth.SwapVector{ + From: swap.Participant, + To: swap.Initiator, + Value: dexeth.WeiToGwei(swap.Value), + SecretHash: secretHash, + LockTime: uint64(swap.LockTime.Unix()), + } + return v, nil +} + +func (c *tContractor) statusAndVector(ctx context.Context, locator []byte) (*dexeth.SwapStatus, *dexeth.SwapVector, error) { + if c.swapErr != nil { + return nil, nil, c.swapErr + } + vector, err := c.vector(ctx, locator) + if err != nil { + return nil, nil, err + } + swap, ok := c.swapMap[vector.SecretHash] + if !ok { + return nil, nil, errors.New("swap not in map") + } + v := &dexeth.SwapVector{ + From: swap.Participant, + To: swap.Initiator, + Value: dexeth.WeiToGwei(swap.Value), + SecretHash: vector.SecretHash, + LockTime: uint64(swap.LockTime.Unix()), + } + s := &dexeth.SwapStatus{ + Step: swap.State, + Secret: swap.Secret, + BlockHeight: swap.BlockHeight, + } + return s, v, nil +} + func (c *tContractor) swap(ctx context.Context, secretHash [32]byte) (*dexeth.SwapState, error) { if c.swapErr != nil { return nil, c.swapErr @@ -312,8 +406,12 @@ func (c *tContractor) redeem(txOpts *bind.TransactOpts, redeems []*asset.Redempt return c.redeemTx, c.redeemErr } -func (c *tContractor) refund(opts *bind.TransactOpts, secretHash [32]byte) (*types.Transaction, error) { - c.lastRefund.secretHash = secretHash +func (c *tContractor) refund(opts *bind.TransactOpts, locator []byte) (*types.Transaction, error) { + vector, err := c.vector(context.Background(), locator) + if err != nil { + return nil, err + } + c.lastRefund.secretHash = vector.SecretHash c.lastRefund.maxFeeRate = opts.GasFeeCap return c.refundTx, c.refundErr } @@ -322,34 +420,50 @@ func (c *tContractor) estimateInitGas(ctx context.Context, n int) (uint64, error return c.gasEstimates.SwapN(n), c.initGasErr } -func (c *tContractor) estimateRedeemGas(ctx context.Context, secrets [][32]byte) (uint64, error) { +func (c *tContractor) estimateRedeemGas(ctx context.Context, secrets [][32]byte, locators [][]byte) (uint64, error) { if c.redeemGasOverride != nil { return *c.redeemGasOverride, nil } return c.gasEstimates.RedeemN(len(secrets)), c.redeemGasErr } -func (c *tContractor) estimateRefundGas(ctx context.Context, secretHash [32]byte) (uint64, error) { +func (c *tContractor) estimateRefundGas(ctx context.Context, locator []byte) (uint64, error) { return c.gasEstimates.Refund, c.refundGasErr } -func (c *tContractor) isRedeemable(secretHash, secret [32]byte) (bool, error) { +func (c *tContractor) isRedeemable(locator []byte, secret [32]byte) (bool, error) { if c.redeemableErr != nil { return false, c.redeemableErr } + vector, err := c.vector(context.Background(), locator) + if err != nil { + return false, err + } + + if c.swapMap != nil && c.swapMap[vector.SecretHash] == nil { + return false, fmt.Errorf("test error: no swap in swap map") + } + if c.redeemableMap != nil { - return c.redeemableMap[secretHash], nil + return c.redeemableMap[string(locator)], nil } return c.redeemable, c.redeemableErr } func (c *tContractor) value(_ context.Context, tx *types.Transaction) (incoming, outgoing uint64, err error) { - return c.valueIn[tx.Hash()], c.valueOut[tx.Hash()], c.valueErr + incoming, outgoing = c.valueIn[tx.Hash()], c.valueOut[tx.Hash()] + if incoming > 0 { + delete(c.valueIn, tx.Hash()) + } + if outgoing > 0 { + delete(c.valueOut, tx.Hash()) + } + return incoming, outgoing, c.valueErr } -func (c *tContractor) isRefundable(secretHash [32]byte) (bool, error) { +func (c *tContractor) isRefundable(locator []byte) (bool, error) { return c.refundable, c.refundableErr } @@ -557,7 +671,7 @@ func newTestNode(assetID uint32) *testNode { } tc := &tContractor{ - gasEstimates: ethGases, + gasEstimates: ethGasesV0, swapMap: make(map[[32]byte]*dexeth.SwapState), valueIn: make(map[common.Hash]uint64), valueOut: make(map[common.Hash]uint64), @@ -608,8 +722,8 @@ func tassetWallet(assetID uint32) (asset.Wallet, *assetWallet, *testNode, contex }, log: tLogger.SubLogger(strings.ToUpper(dex.BipIDSymbol(assetID))), assetID: assetID, - contractors: map[uint32]contractor{0: c}, - findRedemptionReqs: make(map[[32]byte]*findRedemptionRequest), + contractors: map[uint32]contractor{0: c, 1: c}, + findRedemptionReqs: make(map[string]*findRedemptionRequest), evmify: dexeth.GweiToWei, atomize: dexeth.WeiToGwei, } @@ -627,7 +741,7 @@ func tassetWallet(assetID uint32) (asset.Wallet, *assetWallet, *testNode, contex node.tokenParent = &assetWallet{ baseWallet: aw.baseWallet, log: tLogger.SubLogger("ETH"), - contractors: map[uint32]contractor{0: node.tContractor}, + contractors: map[uint32]contractor{0: node.tContractor, 1: node.tContractor}, assetID: BipID, atomize: dexeth.WeiToGwei, } @@ -788,13 +902,13 @@ func TestBalance(t *testing.T) { t.Fatalf("unexpected error for test %q: %v", test.name, err) } if bal.Available != test.wantBal { - t.Fatalf("want available balance %v got %v for test %q", test.wantBal, bal.Available, test.name) + t.Fatalf("%s: want available balance %v got %v for test %q", test.name, test.wantBal, bal.Available, test.name) } if bal.Immature != test.wantImmature { - t.Fatalf("want immature balance %v got %v for test %q", test.wantImmature, bal.Immature, test.name) + t.Fatalf("%s: want immature balance %v got %v for test %q", test.name, test.wantImmature, bal.Immature, test.name) } if bal.Locked != test.wantLocked { - t.Fatalf("want locked balance %v got %v for test %q", test.wantLocked, bal.Locked, test.name) + t.Fatalf("%s: want locked balance %v got %v for test %q", test.name, test.wantLocked, bal.Locked, test.name) } } } @@ -858,36 +972,45 @@ func testRefund(t *testing.T, assetID uint32) { const gweiBal = 1e9 const ogRefundReserves = 1e8 - v1Contractor := &tContractor{ - swapMap: make(map[[32]byte]*dexeth.SwapState, 1), - gasEstimates: ethGases, - redeemTx: types.NewTx(&types.DynamicFeeTx{}), - } - var v1c contractor = v1Contractor - - gasesV1 := &dexeth.Gases{Refund: 1e5} - if assetID == BipID { - dexeth.VersionedGases[1] = gasesV1 - defer delete(dexeth.VersionedGases, 1) - } else { - tokenContracts := dexeth.Tokens[simnetTokenID].NetTokens[dex.Simnet].SwapContracts - tc := *tokenContracts[0] - tc.Gas = *gasesV1 - tokenContracts[1] = &tc - defer delete(tokenContracts, 1) - v1c = &tTokenContractor{tContractor: v1Contractor} - } - - eth.contractors[1] = v1c + // v1Contractor := &tContractor{ + // swapMap: make(map[[32]byte]*dexeth.SwapState, 1), + // gasEstimates: ethGasesV0, + // redeemTx: types.NewTx(&types.DynamicFeeTx{}), + // } + // var v1c contractor = v1Contractor + + // gasesV1 := &dexeth.Gases{Refund: 1e5} + // if assetID == BipID { + // dexeth.VersionedGases[1] = gasesV1 + // defer delete(dexeth.VersionedGases, 1) + // } else { + // tokenContracts := dexeth.Tokens[simnetTokenID].NetTokens[dex.Simnet].SwapContracts + // tc := *tokenContracts[0] + // tc.Gas = *gasesV1 + // tokenContracts[1] = &tc + // defer delete(tokenContracts, 1) + // v1c = &tTokenContractor{tContractor: v1Contractor} + // } + + // eth.contractors[1] = v1c var secretHash [32]byte copy(secretHash[:], encode.RandomBytes(32)) - v0Contract := dexeth.EncodeContractData(0, secretHash) - ss := new(dexeth.SwapState) - v0Contractor := node.tContractor + v0Contract := dexeth.EncodeContractData(0, secretHash[:]) + v1Vector := dexeth.SwapVector{ + From: testAddressA, + To: testAddressB, + Value: 1, + SecretHash: secretHash, + LockTime: uint64(time.Now().Unix()), + } + v1Contract := dexeth.EncodeContractData(1, v1Vector.Locator()) + + ss := &dexeth.SwapState{ + Value: dexeth.GweiToWei(1), + } - v0Contractor.swapMap[secretHash] = ss - v1Contractor.swapMap[secretHash] = ss + node.tContractor.swapMap[secretHash] = ss tests := []struct { name string @@ -900,7 +1023,7 @@ func testRefund(t *testing.T, assetID uint32) { wantZeroHash bool swapStep dexeth.SwapStep swapErr error - useV1Gases bool + v1 bool }{ { name: "ok", @@ -913,7 +1036,7 @@ func testRefund(t *testing.T, assetID uint32) { swapStep: dexeth.SSInitiated, isRefundable: true, wantLocked: ogRefundReserves - feeSuggestion*dexeth.RefundGas(1), - useV1Gases: true, + v1: true, }, { name: "ok refunded", @@ -954,11 +1077,10 @@ func testRefund(t *testing.T, assetID uint32) { } for _, test := range tests { + c := node.tContractor contract := v0Contract - c := v0Contractor - if test.useV1Gases { - contract = dexeth.EncodeContractData(1, secretHash) - c = v1Contractor + if test.v1 { + contract = v1Contract } else if test.badContract { contract = []byte{} } @@ -1037,11 +1159,11 @@ func testFundOrderReturnCoinsFundingCoins(t *testing.T, assetID uint32) { w, eth, node, shutdown := tassetWallet(assetID) defer shutdown() walletBalanceGwei := uint64(dexeth.GweiFactor) - fromAsset := tETH + fromAsset := tETHV0 if assetID == BipID { node.bal = dexeth.GweiToWei(walletBalanceGwei) } else { - fromAsset = tToken + fromAsset = tTokenV0 node.tokenContractor.bal = dexeth.GweiToWei(walletBalanceGwei) node.tokenParent.node.(*testNode).bal = dexeth.GweiToWei(walletBalanceGwei) } @@ -1211,6 +1333,7 @@ func testFundOrderReturnCoinsFundingCoins(t *testing.T, assetID uint32) { defer shutdown2() eth2.node = node eth2.contractors[0] = node.tokenContractor + eth2.contractors[1] = node.tokenContractor node.tokenContractor.bal = dexeth.GweiToWei(walletBalanceGwei) // Test reloading coins from first order @@ -1319,10 +1442,10 @@ func testFundOrderReturnCoinsFundingCoins(t *testing.T, assetID uint32) { func TestPreSwap(t *testing.T) { const feeSuggestion = 90 const lotSize = 10e9 - oneFee := ethGases.Swap * tETH.MaxFeeRate + oneFee := ethGasesV0.Swap * tETHV0.MaxFeeRate oneLock := lotSize + oneFee - oneFeeToken := tokenGases.Swap * tToken.MaxFeeRate + oneFeeToken := tokenGasesV0.Swap * tTokenV0.MaxFeeRate tests := []struct { name string @@ -1369,9 +1492,9 @@ func TestPreSwap(t *testing.T) { wantLots: 1, wantValue: lotSize, - wantMaxFees: tETH.MaxFeeRate * ethGases.Swap, - wantBestCase: feeSuggestion * ethGases.Swap, - wantWorstCase: feeSuggestion * ethGases.Swap, + wantMaxFees: tETHV0.MaxFeeRate * ethGasesV0.Swap, + wantBestCase: feeSuggestion * ethGasesV0.Swap, + wantWorstCase: feeSuggestion * ethGasesV0.Swap, }, { name: "one lot enough for fees - token", @@ -1382,9 +1505,9 @@ func TestPreSwap(t *testing.T) { wantLots: 1, wantValue: lotSize, - wantMaxFees: tToken.MaxFeeRate * tokenGases.Swap, - wantBestCase: feeSuggestion * tokenGases.Swap, - wantWorstCase: feeSuggestion * tokenGases.Swap, + wantMaxFees: tTokenV0.MaxFeeRate * tokenGasesV0.Swap, + wantBestCase: feeSuggestion * tokenGasesV0.Swap, + wantWorstCase: feeSuggestion * tokenGasesV0.Swap, }, { name: "more lots than max lots", @@ -1409,9 +1532,9 @@ func TestPreSwap(t *testing.T) { wantLots: 4, wantValue: 4 * lotSize, - wantMaxFees: 4 * tETH.MaxFeeRate * ethGases.Swap, - wantBestCase: feeSuggestion * ethGases.SwapN(4), - wantWorstCase: 4 * feeSuggestion * ethGases.Swap, + wantMaxFees: 4 * tETHV0.MaxFeeRate * ethGasesV0.Swap, + wantBestCase: feeSuggestion * ethGasesV0.SwapN(4), + wantWorstCase: 4 * feeSuggestion * ethGasesV0.Swap, }, { name: "fewer than max lots - token", @@ -1422,9 +1545,9 @@ func TestPreSwap(t *testing.T) { wantLots: 4, wantValue: 4 * lotSize, - wantMaxFees: 4 * tToken.MaxFeeRate * tokenGases.Swap, - wantBestCase: feeSuggestion * tokenGases.SwapN(4), - wantWorstCase: 4 * feeSuggestion * tokenGases.Swap, + wantMaxFees: 4 * tTokenV0.MaxFeeRate * tokenGasesV0.Swap, + wantBestCase: feeSuggestion * tokenGasesV0.SwapN(4), + wantWorstCase: 4 * feeSuggestion * tokenGasesV0.Swap, }, { name: "balanceError", @@ -1447,10 +1570,10 @@ func TestPreSwap(t *testing.T) { for _, test := range tests { var assetID uint32 = BipID - assetCfg := tETH + assetCfg := tETHV0 if test.token { assetID = simnetTokenID - assetCfg = tToken + assetCfg = tTokenV0 } w, _, node, shutdown := tassetWallet(assetID) @@ -1514,6 +1637,13 @@ func testSwap(t *testing.T, assetID uint32) { w, eth, node, shutdown := tassetWallet(assetID) defer shutdown() + assetCfg := tETHV0 + gases := ethGasesV0 + if assetID != BipID { + assetCfg = tTokenV0 + gases = &tokenGasesV0 + } + receivingAddress := "0x2b84C791b79Ee37De042AD2ffF1A253c3ce9bc27" node.tContractor.initTx = types.NewTx(&types.DynamicFeeTx{}) node.tokenContractor.allow = unlimitedAllowance @@ -1524,7 +1654,8 @@ func testSwap(t *testing.T, assetID uint32) { if assetID == BipID { coinIDs = append(coinIDs, createFundingCoin(eth.addr, amt).RecoveryID()) } else { - fees := n * tokenGases.Swap * tToken.MaxFeeRate + // Not gonna version the fees here unless it matters. + fees := n * gases.Swap * assetCfg.MaxFeeRate coinIDs = append(coinIDs, createTokenFundingCoin(eth.addr, amt, fees).RecoveryID()) } } @@ -1550,12 +1681,7 @@ func testSwap(t *testing.T, assetID uint32) { } gasNeededForSwaps := func(numSwaps int) uint64 { - if assetID == BipID { - return ethGases.Swap * uint64(numSwaps) - } else { - return tokenGases.Swap * uint64(numSwaps) - } - + return gases.Swap * uint64(numSwaps) } testSwap := func(testName string, swaps asset.Swaps, expectError bool) { @@ -1594,16 +1720,17 @@ func testSwap(t *testing.T, assetID uint32) { testName, receipt.Coin().Value(), contract.Value) } contractData := receipt.Contract() - ver, secretHash, err := dexeth.DecodeContractData(contractData) + contractVer, locator, err := dexeth.DecodeLocator(contractData) if err != nil { t.Fatalf("failed to decode contract data: %v", err) } - if swaps.Version != ver { + if swaps.Version != contractVer { t.Fatal("wrong contract version") } - if !bytes.Equal(contract.SecretHash, secretHash[:]) { - t.Fatalf("%v, contract: %x != secret hash in input: %x", - testName, receipt.Contract(), secretHash) + chkLocator := acToLocator(contractVer, contract, node.addr) + if !bytes.Equal(locator, chkLocator) { + t.Fatalf("%v, contract: %x != locator in input: %x", + testName, receipt.Contract(), locator) } totalCoinValue += receipt.Coin().Value() @@ -1673,10 +1800,6 @@ func testSwap(t *testing.T, assetID uint32) { }, } inputs := refreshWalletAndFundCoins(5, []uint64{ethToGwei(2)}, 1) - assetCfg := tETH - if assetID != BipID { - assetCfg = tToken - } swaps := asset.Swaps{ Version: assetCfg.Version, Inputs: inputs, @@ -1764,6 +1887,25 @@ func testSwap(t *testing.T, assetID uint32) { LockChange: false, } testSwap("exact change", swaps, false) + + // Version 1 + assetCfg = tETHV1 + gases = ethGasesV1 + if assetID != BipID { + assetCfg = tTokenV1 + gases = &tokenGasesV1 + } + node.tContractor.gasEstimates = gases + + inputs = refreshWalletAndFundCoins(5, []uint64{ethToGwei(2) + (2 * 200 * dexeth.InitGas(1, 1))}, 2) + swaps = asset.Swaps{ + Inputs: inputs, + Version: assetCfg.Version, + Contracts: contracts, + FeeRate: assetCfg.MaxFeeRate, + LockChange: false, + } + testSwap("v1", swaps, false) } func TestPreRedeem(t *testing.T) { @@ -1771,7 +1913,7 @@ func TestPreRedeem(t *testing.T) { defer shutdown() form := &asset.PreRedeemForm{ - Version: tETH.Version, + Version: tETHV0.Version, Lots: 5, FeeSuggestion: 100, } @@ -1789,7 +1931,7 @@ func TestPreRedeem(t *testing.T) { w, _, node, shutdown2 := tassetWallet(simnetTokenID) defer shutdown2() - form.Version = tToken.Version + form.Version = tTokenV0.Version node.tokenContractor.allow = unlimitedAllowanceReplenishThreshold preRedeem, err = w.PreRedeem(form) @@ -1811,37 +1953,26 @@ func testRedeem(t *testing.T, assetID uint32) { w, eth, node, shutdown := tassetWallet(assetID) defer shutdown() - // Test with a non-zero contract version to ensure it makes it into the receipt - contractVer := uint32(1) - dexeth.VersionedGases[1] = ethGases // for dexeth.RedeemGas(..., 1) - tokenContracts := dexeth.Tokens[simnetTokenID].NetTokens[dex.Simnet].SwapContracts - tokenContracts[1] = tokenContracts[0] - defer delete(dexeth.VersionedGases, 1) - defer delete(tokenContracts, 1) - - contractorV1 := &tContractor{ - swapMap: make(map[[32]byte]*dexeth.SwapState, 1), - gasEstimates: ethGases, - redeemTx: types.NewTx(&types.DynamicFeeTx{Data: []byte{1, 2, 3}}), - } - var c contractor = contractorV1 - if assetID != BipID { - c = &tTokenContractor{ - tContractor: contractorV1, - } + var contractor *tContractor + if assetID == BipID { + contractor = eth.contractors[0].(*tContractor) + } else { + contractor = eth.contractors[0].(*tTokenContractor).tContractor } - eth.contractors[1] = c + contractor.redeemTx = types.NewTx(&types.DynamicFeeTx{Data: []byte{1, 2, 3}}) + now := time.Now() + const value = 1e9 - addSwapToSwapMap := func(secretHash [32]byte, value uint64, step dexeth.SwapStep) { + addSwapToSwapMap := func(secretHash [32]byte, step dexeth.SwapStep) { swap := dexeth.SwapState{ BlockHeight: 1, - LockTime: time.Now(), + LockTime: now, Initiator: testAddressB, Participant: testAddressA, Value: dexeth.GweiToWei(value), State: step, } - contractorV1.swapMap[secretHash] = &swap + contractor.swapMap[secretHash] = &swap } numSecrets := 3 @@ -1855,15 +1986,18 @@ func testRedeem(t *testing.T, assetID uint32) { secretHashes = append(secretHashes, secretHash) } - addSwapToSwapMap(secretHashes[0], 1e9, dexeth.SSInitiated) - addSwapToSwapMap(secretHashes[1], 1e9, dexeth.SSInitiated) + addSwapToSwapMap(secretHashes[0], dexeth.SSInitiated) + addSwapToSwapMap(secretHashes[1], dexeth.SSInitiated) - var redeemGas uint64 + var redeemGasesV0, redeemGasesV1 *dexeth.Gases if assetID == BipID { - redeemGas = ethGases.Redeem + redeemGasesV0 = ethGasesV0 + redeemGasesV1 = ethGasesV1 } else { - redeemGas = tokenGases.Redeem + redeemGasesV0 = &tokenGasesV0 + redeemGasesV1 = &tokenGasesV1 } + redeemGas := redeemGasesV0.Redeem var higherGasEstimate uint64 = redeemGas * 2 * 12 / 10 // 120% of estimate var doubleGasEstimate uint64 = (redeemGas * 2 * 2) * 10 / 11 // 200% of estimate after 10% increase var moreThanDoubleGasEstimate uint64 = (redeemGas * 2 * 21 / 10) * 10 / 11 // > 200% of estimate after 10% increase @@ -1896,6 +2030,41 @@ func testRedeem(t *testing.T, assetID uint32) { Number: big.NewInt(bestBlock), } + newRedeem := func(idx int) *asset.Redemption { + return &asset.Redemption{ + Spends: &asset.AuditInfo{ + Contract: dexeth.EncodeContractData(0, secretHashes[idx][:]), + SecretHash: secretHashes[idx][:], // redundant for all current assets, unused with eth + Coin: &coin{ + id: randomHash(), + value: value, + }, + }, + Secret: secrets[idx][:], + } + } + + newRedeemV1 := func(idx int) *asset.Redemption { + locator := (&dexeth.SwapVector{ + From: testAddressA, + To: testAddressB, + Value: value, + SecretHash: secretHashes[idx], + LockTime: uint64(now.Unix()), + }).Locator() + return &asset.Redemption{ + Spends: &asset.AuditInfo{ + Contract: dexeth.EncodeContractData(1, locator), + SecretHash: secretHashes[idx][:], // redundant for all current assets, unused with eth + Coin: &coin{ + id: randomHash(), + value: value, + }, + }, + Secret: secrets[idx][:], + } + } + tests := []struct { name string form asset.RedeemForm @@ -1907,6 +2076,7 @@ func testRedeem(t *testing.T, assetID uint32) { redeemGasOverride *uint64 expectedGasFeeCap *big.Int expectError bool + v1 bool }{ { name: "ok", @@ -1916,31 +2086,23 @@ func testRedeem(t *testing.T, assetID uint32) { baseFee: dexeth.GweiToWei(100), expectedGasFeeCap: dexeth.GweiToWei(100), form: asset.RedeemForm{ - Redemptions: []*asset.Redemption{ - { - Spends: &asset.AuditInfo{ - Contract: dexeth.EncodeContractData(contractVer, secretHashes[0]), - SecretHash: secretHashes[0][:], // redundant for all current assets, unused with eth - Coin: &coin{ - id: randomHash(), - }, - }, - Secret: secrets[0][:], - }, - { - Spends: &asset.AuditInfo{ - Contract: dexeth.EncodeContractData(contractVer, secretHashes[1]), - SecretHash: secretHashes[1][:], - Coin: &coin{ - id: randomHash(), - }, - }, - Secret: secrets[1][:], - }, - }, + Redemptions: []*asset.Redemption{newRedeem(0), newRedeem(1)}, FeeSuggestion: 100, }, }, + { + name: "ok-v1", + expectError: false, + isRedeemable: true, + ethBal: dexeth.GweiToWei(10e9), + baseFee: dexeth.GweiToWei(100), + expectedGasFeeCap: dexeth.GweiToWei(100), + form: asset.RedeemForm{ + Redemptions: []*asset.Redemption{newRedeemV1(0), newRedeemV1(1)}, + FeeSuggestion: 100, + }, + v1: true, + }, { name: "higher gas estimate than reserved", expectError: false, @@ -1950,28 +2112,7 @@ func testRedeem(t *testing.T, assetID uint32) { expectedGasFeeCap: dexeth.GweiToWei(100), redeemGasOverride: &higherGasEstimate, form: asset.RedeemForm{ - Redemptions: []*asset.Redemption{ - { - Spends: &asset.AuditInfo{ - Contract: dexeth.EncodeContractData(contractVer, secretHashes[0]), - SecretHash: secretHashes[0][:], // redundant for all current assets, unused with eth - Coin: &coin{ - id: randomHash(), - }, - }, - Secret: secrets[0][:], - }, - { - Spends: &asset.AuditInfo{ - Contract: dexeth.EncodeContractData(contractVer, secretHashes[1]), - SecretHash: secretHashes[1][:], - Coin: &coin{ - id: randomHash(), - }, - }, - Secret: secrets[1][:], - }, - }, + Redemptions: []*asset.Redemption{newRedeem(0), newRedeem(1)}, FeeSuggestion: 100, }, }, @@ -1984,28 +2125,7 @@ func testRedeem(t *testing.T, assetID uint32) { expectedGasFeeCap: dexeth.GweiToWei(100), redeemGasOverride: &doubleGasEstimate, form: asset.RedeemForm{ - Redemptions: []*asset.Redemption{ - { - Spends: &asset.AuditInfo{ - Contract: dexeth.EncodeContractData(contractVer, secretHashes[0]), - SecretHash: secretHashes[0][:], // redundant for all current assets, unused with eth - Coin: &coin{ - id: randomHash(), - }, - }, - Secret: secrets[0][:], - }, - { - Spends: &asset.AuditInfo{ - Contract: dexeth.EncodeContractData(contractVer, secretHashes[1]), - SecretHash: secretHashes[1][:], - Coin: &coin{ - id: randomHash(), - }, - }, - Secret: secrets[1][:], - }, - }, + Redemptions: []*asset.Redemption{newRedeem(0), newRedeem(1)}, FeeSuggestion: 100, }, }, @@ -2017,28 +2137,7 @@ func testRedeem(t *testing.T, assetID uint32) { baseFee: dexeth.GweiToWei(100), redeemGasOverride: &moreThanDoubleGasEstimate, form: asset.RedeemForm{ - Redemptions: []*asset.Redemption{ - { - Spends: &asset.AuditInfo{ - Contract: dexeth.EncodeContractData(contractVer, secretHashes[0]), - SecretHash: secretHashes[0][:], // redundant for all current assets, unused with eth - Coin: &coin{ - id: randomHash(), - }, - }, - Secret: secrets[0][:], - }, - { - Spends: &asset.AuditInfo{ - Contract: dexeth.EncodeContractData(contractVer, secretHashes[1]), - SecretHash: secretHashes[1][:], - Coin: &coin{ - id: randomHash(), - }, - }, - Secret: secrets[1][:], - }, - }, + Redemptions: []*asset.Redemption{newRedeem(0), newRedeem(1)}, FeeSuggestion: 100, }, }, @@ -2050,28 +2149,7 @@ func testRedeem(t *testing.T, assetID uint32) { baseFee: dexeth.GweiToWei(100), redeemGasOverride: &higherGasEstimate, form: asset.RedeemForm{ - Redemptions: []*asset.Redemption{ - { - Spends: &asset.AuditInfo{ - Contract: dexeth.EncodeContractData(contractVer, secretHashes[0]), - SecretHash: secretHashes[0][:], // redundant for all current assets, unused with eth - Coin: &coin{ - id: randomHash(), - }, - }, - Secret: secrets[0][:], - }, - { - Spends: &asset.AuditInfo{ - Contract: dexeth.EncodeContractData(contractVer, secretHashes[1]), - SecretHash: secretHashes[1][:], - Coin: &coin{ - id: randomHash(), - }, - }, - Secret: secrets[1][:], - }, - }, + Redemptions: []*asset.Redemption{newRedeem(0), newRedeem(1)}, FeeSuggestion: 100, }, }, @@ -2084,28 +2162,7 @@ func testRedeem(t *testing.T, assetID uint32) { expectedGasFeeCap: dexeth.GweiToWei(300), redeemGasOverride: &higherGasEstimate, form: asset.RedeemForm{ - Redemptions: []*asset.Redemption{ - { - Spends: &asset.AuditInfo{ - Contract: dexeth.EncodeContractData(contractVer, secretHashes[0]), - SecretHash: secretHashes[0][:], // redundant for all current assets, unused with eth - Coin: &coin{ - id: randomHash(), - }, - }, - Secret: secrets[0][:], - }, - { - Spends: &asset.AuditInfo{ - Contract: dexeth.EncodeContractData(contractVer, secretHashes[1]), - SecretHash: secretHashes[1][:], - Coin: &coin{ - id: randomHash(), - }, - }, - Secret: secrets[1][:], - }, - }, + Redemptions: []*asset.Redemption{newRedeem(0), newRedeem(1)}, FeeSuggestion: 100, }, }, @@ -2118,28 +2175,7 @@ func testRedeem(t *testing.T, assetID uint32) { expectedGasFeeCap: dexeth.GweiToWei(298), redeemGasOverride: &higherGasEstimate, form: asset.RedeemForm{ - Redemptions: []*asset.Redemption{ - { - Spends: &asset.AuditInfo{ - Contract: dexeth.EncodeContractData(contractVer, secretHashes[0]), - SecretHash: secretHashes[0][:], // redundant for all current assets, unused with eth - Coin: &coin{ - id: randomHash(), - }, - }, - Secret: secrets[0][:], - }, - { - Spends: &asset.AuditInfo{ - Contract: dexeth.EncodeContractData(contractVer, secretHashes[1]), - SecretHash: secretHashes[1][:], - Coin: &coin{ - id: randomHash(), - }, - }, - Secret: secrets[1][:], - }, - }, + Redemptions: []*asset.Redemption{newRedeem(0), newRedeem(1)}, FeeSuggestion: 100, }, }, @@ -2151,28 +2187,7 @@ func testRedeem(t *testing.T, assetID uint32) { baseFee: dexeth.GweiToWei(100), form: asset.RedeemForm{ - Redemptions: []*asset.Redemption{ - { - Spends: &asset.AuditInfo{ - Contract: dexeth.EncodeContractData(contractVer, secretHashes[0]), - SecretHash: secretHashes[0][:], - Coin: &coin{ - id: randomHash(), - }, - }, - Secret: secrets[0][:], - }, - { - Spends: &asset.AuditInfo{ - Contract: dexeth.EncodeContractData(contractVer, secretHashes[1]), - SecretHash: secretHashes[1][:], - Coin: &coin{ - id: randomHash(), - }, - }, - Secret: secrets[1][:], - }, - }, + Redemptions: []*asset.Redemption{newRedeem(0), newRedeem(1)}, FeeSuggestion: 100, }, }, @@ -2184,28 +2199,7 @@ func testRedeem(t *testing.T, assetID uint32) { baseFee: dexeth.GweiToWei(100), isRedeemableErr: errors.New(""), form: asset.RedeemForm{ - Redemptions: []*asset.Redemption{ - { - Spends: &asset.AuditInfo{ - Contract: dexeth.EncodeContractData(contractVer, secretHashes[0]), - SecretHash: secretHashes[0][:], - Coin: &coin{ - id: randomHash(), - }, - }, - Secret: secrets[0][:], - }, - { - Spends: &asset.AuditInfo{ - Contract: dexeth.EncodeContractData(contractVer, secretHashes[1]), - SecretHash: secretHashes[1][:], - Coin: &coin{ - id: randomHash(), - }, - }, - Secret: secrets[1][:], - }, - }, + Redemptions: []*asset.Redemption{newRedeem(0), newRedeem(1)}, FeeSuggestion: 100, }, }, @@ -2217,18 +2211,7 @@ func testRedeem(t *testing.T, assetID uint32) { ethBal: dexeth.GweiToWei(10e9), baseFee: dexeth.GweiToWei(100), form: asset.RedeemForm{ - Redemptions: []*asset.Redemption{ - { - Spends: &asset.AuditInfo{ - Contract: dexeth.EncodeContractData(contractVer, secretHashes[0]), - SecretHash: secretHashes[0][:], - Coin: &coin{ - id: randomHash(), - }, - }, - Secret: secrets[0][:], - }, - }, + Redemptions: []*asset.Redemption{newRedeem(0)}, FeeSuggestion: 200, }, }, @@ -2239,18 +2222,7 @@ func testRedeem(t *testing.T, assetID uint32) { ethBal: dexeth.GweiToWei(10e9), baseFee: dexeth.GweiToWei(100), form: asset.RedeemForm{ - Redemptions: []*asset.Redemption{ - { - Spends: &asset.AuditInfo{ - Contract: dexeth.EncodeContractData(contractVer, secretHashes[2]), - SecretHash: secretHashes[2][:], - Coin: &coin{ - id: randomHash(), - }, - }, - Secret: secrets[2][:], - }, - }, + Redemptions: []*asset.Redemption{newRedeem(2)}, FeeSuggestion: 100, }, }, @@ -2268,10 +2240,10 @@ func testRedeem(t *testing.T, assetID uint32) { } for _, test := range tests { - contractorV1.redeemErr = test.redeemErr - contractorV1.redeemable = test.isRedeemable - contractorV1.redeemableErr = test.isRedeemableErr - contractorV1.redeemGasOverride = test.redeemGasOverride + contractor.redeemErr = test.redeemErr + contractor.redeemable = test.isRedeemable + contractor.redeemableErr = test.isRedeemableErr + contractor.redeemGasOverride = test.redeemGasOverride eth.monitoredTxsMtx.Lock() eth.monitoredTxs = make(map[common.Hash]*monitoredTx) @@ -2280,6 +2252,13 @@ func testRedeem(t *testing.T, assetID uint32) { node.bal = test.ethBal node.baseFee = test.baseFee + redeemGas = redeemGasesV0.Redeem + redeemAdd := redeemGasesV0.RedeemAdd + if test.v1 { + redeemGas = redeemGasesV1.Redeem + redeemAdd = redeemGasesV1.RedeemAdd + } + txs, out, fees, err := w.Redeem(&test.form) if test.expectError { if err == nil { @@ -2297,10 +2276,7 @@ func testRedeem(t *testing.T, assetID uint32) { } // Check fees returned from Redeem are as expected - expectedGas := dexeth.RedeemGas(len(test.form.Redemptions), 0) - if assetID != BipID { - expectedGas = tokenGases.Redeem + (uint64(len(test.form.Redemptions))-1)*tokenGases.RedeemAdd - } + expectedGas := redeemGas + (uint64(len(test.form.Redemptions))-1)*redeemAdd expectedFees := expectedGas * test.form.FeeSuggestion if fees != expectedFees { t.Fatalf("%v: expected fees %d, but got %d", test.name, expectedFees, fees) @@ -2309,42 +2285,49 @@ func testRedeem(t *testing.T, assetID uint32) { // Check that value of output coin is as axpected var totalSwapValue uint64 for _, redemption := range test.form.Redemptions { - _, secretHash, err := dexeth.DecodeContractData(redemption.Spends.Contract) + _, locator, err := dexeth.DecodeLocator(redemption.Spends.Contract) if err != nil { - t.Fatalf("DecodeContractData: %v", err) + t.Fatalf("DecodeLocator: %v", err) + } + var secretHash [32]byte + if test.v1 { + v, _ := dexeth.ParseV1Locator(locator) + secretHash = v.SecretHash + } else { + copy(secretHash[:], locator) } // secretHash should equal redemption.Spends.SecretHash, but it's // not part of the Redeem code, just the test input consistency. - swap := contractorV1.swapMap[secretHash] + swap := contractor.swapMap[secretHash] totalSwapValue += dexeth.WeiToGwei(swap.Value) } if out.Value() != totalSwapValue { - t.Fatalf("expected coin value to be %d but got %d", - totalSwapValue, out.Value()) + t.Fatalf("%s: expected coin value to be %d but got %d", + test.name, totalSwapValue, out.Value()) } // Check that gas limit in the transaction is as expected var expectedGasLimit uint64 if test.redeemGasOverride == nil { if assetID == BipID { - expectedGasLimit = ethGases.Redeem * uint64(len(test.form.Redemptions)) + expectedGasLimit = redeemGas * uint64(len(test.form.Redemptions)) } else { - expectedGasLimit = tokenGases.Redeem * uint64(len(test.form.Redemptions)) + expectedGasLimit = redeemGas * uint64(len(test.form.Redemptions)) } } else { expectedGasLimit = *test.redeemGasOverride * 11 / 10 } - if contractorV1.lastRedeemOpts.GasLimit != expectedGasLimit { - t.Fatalf("%s: expected gas limit %d, but got %d", test.name, expectedGasLimit, contractorV1.lastRedeemOpts.GasLimit) + if contractor.lastRedeemOpts.GasLimit != expectedGasLimit { + t.Fatalf("%s: expected gas limit %d, but got %d", test.name, expectedGasLimit, contractor.lastRedeemOpts.GasLimit) } // Check that the gas fee cap in the transaction is as expected - if contractorV1.lastRedeemOpts.GasFeeCap.Cmp(test.expectedGasFeeCap) != 0 { - t.Fatalf("%s: expected gas fee cap %v, but got %v", test.name, test.expectedGasFeeCap, contractorV1.lastRedeemOpts.GasFeeCap) + if contractor.lastRedeemOpts.GasFeeCap.Cmp(test.expectedGasFeeCap) != 0 { + t.Fatalf("%s: expected gas fee cap %v, but got %v", test.name, test.expectedGasFeeCap, contractor.lastRedeemOpts.GasFeeCap) } // Check that tx was stored in the monitored transactions - txHash := contractorV1.redeemTx.Hash() + txHash := contractor.redeemTx.Hash() eth.monitoredTxsMtx.RLock() monitoredTx, stored := eth.monitoredTxs[txHash] if !stored { @@ -2374,6 +2357,7 @@ func TestMaxOrder(t *testing.T) { wantWorstCase uint64 wantBestCase uint64 wantLocked uint64 + v1 bool }{ { name: "no balance", @@ -2415,10 +2399,24 @@ func TestMaxOrder(t *testing.T) { maxFeeRate: 100, wantLots: 1, wantValue: ethToGwei(10), - wantMaxFees: 100 * ethGases.Swap, - wantBestCase: 90 * ethGases.Swap, - wantWorstCase: 90 * ethGases.Swap, - wantLocked: ethToGwei(10) + (100 * ethGases.Swap), + wantMaxFees: 100 * ethGasesV0.Swap, + wantBestCase: 90 * ethGasesV0.Swap, + wantWorstCase: 90 * ethGasesV0.Swap, + wantLocked: ethToGwei(10) + (100 * ethGasesV0.Swap), + }, + { + name: "one lot enough for fees - v1", + bal: 11, + lotSize: 10, + feeSuggestion: 90, + maxFeeRate: 100, + wantLots: 1, + wantValue: ethToGwei(10), + wantMaxFees: 100 * ethGasesV1.Swap, + wantBestCase: 90 * ethGasesV1.Swap, + wantWorstCase: 90 * ethGasesV1.Swap, + wantLocked: ethToGwei(10) + (100 * ethGasesV0.Swap), + v1: true, }, { name: "one lot enough for fees - token", @@ -2430,10 +2428,10 @@ func TestMaxOrder(t *testing.T) { parentBal: 1, wantLots: 1, wantValue: ethToGwei(10), - wantMaxFees: 100 * tokenGases.Swap, - wantBestCase: 90 * tokenGases.Swap, - wantWorstCase: 90 * tokenGases.Swap, - wantLocked: ethToGwei(10) + (100 * tokenGases.Swap), + wantMaxFees: 100 * tokenGasesV0.Swap, + wantBestCase: 90 * tokenGasesV0.Swap, + wantWorstCase: 90 * tokenGasesV0.Swap, + wantLocked: ethToGwei(10) + (100 * tokenGasesV0.Swap), }, { name: "multiple lots", @@ -2443,10 +2441,10 @@ func TestMaxOrder(t *testing.T) { maxFeeRate: 100, wantLots: 5, wantValue: ethToGwei(50), - wantMaxFees: 5 * 100 * ethGases.Swap, - wantBestCase: 90 * ethGases.SwapN(5), - wantWorstCase: 5 * 90 * ethGases.Swap, - wantLocked: ethToGwei(50) + (5 * 100 * ethGases.Swap), + wantMaxFees: 5 * 100 * ethGasesV0.Swap, + wantBestCase: 90 * ethGasesV0.SwapN(5), + wantWorstCase: 5 * 90 * ethGasesV0.Swap, + wantLocked: ethToGwei(50) + (5 * 100 * ethGasesV0.Swap), }, { name: "multiple lots - token", @@ -2458,10 +2456,10 @@ func TestMaxOrder(t *testing.T) { parentBal: 1, wantLots: 5, wantValue: ethToGwei(50), - wantMaxFees: 5 * 100 * tokenGases.Swap, - wantBestCase: 90 * tokenGases.SwapN(5), - wantWorstCase: 5 * 90 * tokenGases.Swap, - wantLocked: ethToGwei(50) + (5 * 100 * tokenGases.Swap), + wantMaxFees: 5 * 100 * tokenGasesV0.Swap, + wantBestCase: 90 * tokenGasesV0.SwapN(5), + wantWorstCase: 5 * 90 * tokenGasesV0.Swap, + wantLocked: ethToGwei(50) + (5 * 100 * tokenGasesV0.Swap), }, { name: "balanceError", @@ -2477,16 +2475,26 @@ func TestMaxOrder(t *testing.T) { for _, test := range tests { var assetID uint32 = BipID - dexAsset := tETH + dexAsset := tETHV0 + gases := ethGasesV0 if test.token { assetID = simnetTokenID + dexAsset = tTokenV0 + gases = &tokenGasesV0 + if test.v1 { + dexAsset = tTokenV1 + gases = &tokenGasesV1 + } + } else if test.v1 { + dexAsset = tETHV1 + gases = ethGasesV1 } w, _, node, shutdown := tassetWallet(assetID) defer shutdown() if test.token { - dexAsset = tToken + dexAsset = tTokenV0 node.tokenContractor.bal = dexeth.GweiToWei(ethToGwei(test.bal)) node.bal = dexeth.GweiToWei(ethToGwei(test.parentBal)) } else { @@ -2494,6 +2502,7 @@ func TestMaxOrder(t *testing.T) { } node.balErr = test.balErr + node.tContractor.gasEstimates = gases dexAsset.MaxFeeRate = test.maxFeeRate @@ -2600,7 +2609,7 @@ func testAuditContract(t *testing.T, assetID uint32) { }{ { name: "ok", - contract: dexeth.EncodeContractData(0, secretHashes[1]), + contract: dexeth.EncodeContractData(0, secretHashes[1][:]), initiations: []*dexeth.Initiation{ { LockTime: now, @@ -2620,7 +2629,7 @@ func testAuditContract(t *testing.T, assetID uint32) { }, { name: "coin id different than tx hash", - contract: dexeth.EncodeContractData(0, secretHashes[0]), + contract: dexeth.EncodeContractData(0, secretHashes[0][:]), initiations: []*dexeth.Initiation{ { LockTime: now, @@ -2639,7 +2648,7 @@ func testAuditContract(t *testing.T, assetID uint32) { }, { name: "contract not part of transaction", - contract: dexeth.EncodeContractData(0, secretHashes[2]), + contract: dexeth.EncodeContractData(0, secretHashes[2][:]), initiations: []*dexeth.Initiation{ { LockTime: now, @@ -2658,13 +2667,13 @@ func testAuditContract(t *testing.T, assetID uint32) { }, { name: "cannot parse tx data", - contract: dexeth.EncodeContractData(0, secretHashes[2]), + contract: dexeth.EncodeContractData(0, secretHashes[2][:]), badTxData: true, wantErr: true, }, { name: "cannot unmarshal tx binary", - contract: dexeth.EncodeContractData(0, secretHashes[1]), + contract: dexeth.EncodeContractData(0, secretHashes[1][:]), initiations: []*dexeth.Initiation{ { LockTime: now, @@ -2731,7 +2740,7 @@ func testAuditContract(t *testing.T, assetID uint32) { t.Fatalf(`"%v": expected contract %x != actual %x`, test.name, test.contract, auditInfo.Contract) } - _, expectedSecretHash, err := dexeth.DecodeContractData(test.contract) + _, expectedSecretHash, err := dexeth.DecodeLocator(test.contract) if err != nil { t.Fatalf(`"%v": failed to decode versioned bytes: %v`, test.name, err) } @@ -2873,7 +2882,9 @@ func TestSwapConfirmation(t *testing.T) { var secretHash [32]byte copy(secretHash[:], encode.RandomBytes(32)) - state := &dexeth.SwapState{} + state := &dexeth.SwapState{ + Value: dexeth.GweiToWei(1), + } hdr := &types.Header{} node.tContractor.swapMap[secretHash] = state @@ -2889,7 +2900,7 @@ func TestSwapConfirmation(t *testing.T) { defer cancel() checkResult := func(expErr bool, expConfs uint32, expSpent bool) { - confs, spent, err := eth.SwapConfirmations(ctx, nil, dexeth.EncodeContractData(ver, secretHash), time.Time{}) + confs, spent, err := eth.SwapConfirmations(ctx, nil, dexeth.EncodeContractData(ver, secretHash[:]), time.Time{}) if err != nil { if expErr { return @@ -2923,7 +2934,7 @@ func TestSwapConfirmation(t *testing.T) { // ErrSwapNotInitiated state.State = dexeth.SSNone - _, _, err := eth.SwapConfirmations(ctx, nil, dexeth.EncodeContractData(0, secretHash), time.Time{}) + _, _, err := eth.SwapConfirmations(ctx, nil, dexeth.EncodeContractData(0, secretHash[:]), time.Time{}) if !errors.Is(err, asset.ErrSwapNotInitiated) { t.Fatalf("expected ErrSwapNotInitiated, got %v", err) } @@ -3093,6 +3104,7 @@ func TestLocktimeExpired(t *testing.T) { state := &dexeth.SwapState{ LockTime: time.Now(), State: dexeth.SSInitiated, + Value: dexeth.GweiToWei(1), } header := &types.Header{ @@ -3166,10 +3178,11 @@ func testFindRedemption(t *testing.T, assetID uint32) { copy(secret[:], encode.RandomBytes(32)) secretHash := sha256.Sum256(secret[:]) - contract := dexeth.EncodeContractData(0, secretHash) + contract := dexeth.EncodeContractData(0, secretHash[:]) state := &dexeth.SwapState{ Secret: secret, State: dexeth.SSInitiated, + Value: dexeth.GweiToWei(1), } node.tContractor.swapMap[secretHash] = state @@ -3213,7 +3226,7 @@ func testFindRedemption(t *testing.T, assetID uint32) { select { case <-time.After(time.Millisecond): eth.findRedemptionMtx.RLock() - pending := eth.findRedemptionReqs[secretHash] != nil + pending := eth.findRedemptionReqs[string(secretHash[:])] != nil eth.findRedemptionMtx.RUnlock() if !pending { continue @@ -3277,7 +3290,7 @@ func testFindRedemption(t *testing.T, assetID uint32) { // dupe eth.findRedemptionMtx.Lock() - eth.findRedemptionReqs[secretHash] = &findRedemptionRequest{} + eth.findRedemptionReqs[string(secretHash[:])] = &findRedemptionRequest{} eth.findRedemptionMtx.Unlock() res := make(chan error, 1) go func() { @@ -3316,17 +3329,17 @@ func testRefundReserves(t *testing.T, assetID uint32) { feeWallet := eth gasesV0 := dexeth.VersionedGases[0] gasesV1 := &dexeth.Gases{Refund: 1e6} - assetV0 := *tETH - assetV1 := *tETH + assetV0 := *tETHV0 + assetV1 := *tETHV0 if assetID == BipID { dexeth.VersionedGases[1] = gasesV1 defer delete(dexeth.VersionedGases, 1) } else { feeWallet = node.tokenParent - assetV0 = *tToken - assetV1 = *tToken + assetV0 = *tTokenV0 + assetV1 = *tTokenV0 tokenContracts := dexeth.Tokens[simnetTokenID].NetTokens[dex.Simnet].SwapContracts - gasesV0 = &tokenGases + gasesV0 = &tokenGasesV0 tc := *tokenContracts[0] tc.Gas = *gasesV1 tokenContracts[1] = &tc @@ -3409,8 +3422,8 @@ func testRedemptionReserves(t *testing.T, assetID uint32) { gasesV1 := &dexeth.Gases{Redeem: 1e6, RedeemAdd: 85e5} gasesV0 := dexeth.VersionedGases[0] - assetV0 := *tETH - assetV1 := *tETH + assetV0 := *tETHV0 + assetV1 := *tETHV0 feeWallet := eth if assetID == BipID { dexeth.VersionedGases[1] = gasesV1 @@ -3418,10 +3431,10 @@ func testRedemptionReserves(t *testing.T, assetID uint32) { } else { node.tokenContractor.allow = unlimitedAllowanceReplenishThreshold feeWallet = node.tokenParent - assetV0 = *tToken - assetV1 = *tToken + assetV0 = *tTokenV0 + assetV1 = *tTokenV0 tokenContracts := dexeth.Tokens[simnetTokenID].NetTokens[dex.Simnet].SwapContracts - gasesV0 = &tokenGases + gasesV0 = &tokenGasesV0 tc := *tokenContracts[0] tc.Gas = *gasesV1 tokenContracts[1] = &tc @@ -3539,7 +3552,7 @@ func testSend(t *testing.T, assetID uint32) { maxFeeRate, _ := eth.recommendedMaxFeeRate(eth.ctx) ethFees := dexeth.WeiToGwei(maxFeeRate) * defaultSendGasLimit - tokenFees := dexeth.WeiToGwei(maxFeeRate) * tokenGases.Transfer + tokenFees := dexeth.WeiToGwei(maxFeeRate) * tokenGasesV0.Transfer const val = 10e9 const testAddr = "dd93b447f7eBCA361805eBe056259853F3912E04" @@ -3681,7 +3694,11 @@ func testConfirmRedemption(t *testing.T, assetID uint32) { assetRedemption := func(secretHash, secret common.Hash) *asset.Redemption { return &asset.Redemption{ Spends: &asset.AuditInfo{ - Contract: dexeth.EncodeContractData(0, secretHash), + Contract: dexeth.EncodeContractData(0, secretHash[:]), + Coin: &coin{ + id: randomHash(), + value: 1e9, + }, }, Secret: secret[:], } @@ -3703,7 +3720,7 @@ func testConfirmRedemption(t *testing.T, assetID uint32) { getTxResMap map[common.Hash]*txData swapMap map[[32]byte]*dexeth.SwapState monitoredTxs map[common.Hash]*monitoredTx - redeemableMap map[common.Hash]bool + redeemableMap map[string]bool redeemTx *types.Transaction redeemErr error @@ -3826,8 +3843,8 @@ func testConfirmRedemption(t *testing.T, assetID uint32) { blockSubmitted: 19, }, }, - redeemableMap: map[common.Hash]bool{ - secretHashes[0]: true, + redeemableMap: map[string]bool{ + string(secretHashes[0][:]): true, }, bestBlock: 19, expectedResult: &asset.ConfirmRedemptionStatus{ @@ -3888,8 +3905,8 @@ func testConfirmRedemption(t *testing.T, assetID uint32) { State: dexeth.SSInitiated, }, }, - redeemableMap: map[common.Hash]bool{ - secretHashes[0]: true, + redeemableMap: map[string]bool{ + string(secretHashes[0][:]): true, }, monitoredTxs: map[common.Hash]*monitoredTx{}, expectedMonitoredTxs: map[common.Hash]*monitoredTx{ @@ -3964,9 +3981,9 @@ func testConfirmRedemption(t *testing.T, assetID uint32) { blockSubmitted: 13, }, }, - redeemableMap: map[common.Hash]bool{ - secretHashes[0]: true, - secretHashes[1]: true, + redeemableMap: map[string]bool{ + string(secretHashes[0][:]): true, + string(secretHashes[1][:]): true, }, bestBlock: 13, expectedResult: &asset.ConfirmRedemptionStatus{ @@ -4011,9 +4028,9 @@ func testConfirmRedemption(t *testing.T, assetID uint32) { blockSubmitted: 13, }, }, - redeemableMap: map[common.Hash]bool{ - secretHashes[0]: true, - secretHashes[1]: false, + redeemableMap: map[string]bool{ + string(secretHashes[0][:]): true, + string(secretHashes[1][:]): false, }, bestBlock: 13, expectedResult: &asset.ConfirmRedemptionStatus{ @@ -4133,8 +4150,8 @@ func testConfirmRedemption(t *testing.T, assetID uint32) { blockSubmitted: 13, }, }, - redeemableMap: map[common.Hash]bool{ - secretHashes[0]: true, + redeemableMap: map[string]bool{ + string(secretHashes[0][:]): true, }, bestBlock: 13, expectedResult: &asset.ConfirmRedemptionStatus{ @@ -4183,8 +4200,8 @@ func testConfirmRedemption(t *testing.T, assetID uint32) { blockSubmitted: 13, }, }, - redeemableMap: map[common.Hash]bool{ - secretHashes[0]: true, + redeemableMap: map[string]bool{ + string(secretHashes[0][:]): true, }, bestBlock: 13, expectedResult: &asset.ConfirmRedemptionStatus{ @@ -4228,8 +4245,8 @@ func testConfirmRedemption(t *testing.T, assetID uint32) { blockSubmitted: 3, }, }, - redeemableMap: map[common.Hash]bool{ - secretHashes[0]: true, + redeemableMap: map[string]bool{ + string(secretHashes[0][:]): true, }, bestBlock: 13, expectedResult: &asset.ConfirmRedemptionStatus{ @@ -4474,7 +4491,7 @@ func testEstimateSendTxFee(t *testing.T, assetID uint32) { maxFeeRate, _ := eth.recommendedMaxFeeRate(eth.ctx) ethFees := dexeth.WeiToGwei(maxFeeRate) * defaultSendGasLimit - tokenFees := dexeth.WeiToGwei(maxFeeRate) * tokenGases.Transfer + tokenFees := dexeth.WeiToGwei(maxFeeRate) * tokenGasesV0.Transfer const testAddr = "dd93b447f7eBCA361805eBe056259853F3912E04" @@ -4618,7 +4635,7 @@ func TestSwapOrRedemptionFeesPaid(t *testing.T) { contractDataFn := func(ver uint32, secretH []byte) []byte { s := [32]byte{} copy(s[:], secretH) - return dexeth.EncodeContractData(ver, s) + return dexeth.EncodeContractData(ver, s[:]) } rcpt := &types.Receipt{ GasUsed: 100, diff --git a/client/asset/eth/nodeclient.go b/client/asset/eth/nodeclient.go index 999b3cf88e..621fd4a249 100644 --- a/client/asset/eth/nodeclient.go +++ b/client/asset/eth/nodeclient.go @@ -19,7 +19,6 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/eth/ethconfig" "github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/les" "github.com/ethereum/go-ethereum/node" @@ -32,16 +31,10 @@ import ( ) const ( - contractVersionNewest = ^uint32(0) - approveGas = 4e5 + approveGas = 4e5 ) var ( - // https://github.com/ethereum/go-ethereum/blob/16341e05636fd088aa04a27fca6dc5cda5dbab8f/eth/backend.go#L110-L113 - // ultimately results in a minimum fee rate by the filter applied at - // https://github.com/ethereum/go-ethereum/blob/4ebeca19d739a243dc0549bcaf014946cde95c4f/core/tx_pool.go#L626 - minGasPrice = ethconfig.Defaults.Miner.GasPrice - // Check that nodeClient satisfies the ethFetcher interface. _ ethFetcher = (*nodeClient)(nil) ) @@ -404,7 +397,7 @@ func newTxOpts(ctx context.Context, from common.Address, val, maxGas uint64, max func gases(assetID uint32, contractVer uint32, net dex.Network) *dexeth.Gases { if assetID == BipID { - if contractVer != contractVersionNewest { + if contractVer != contractVersionERC20 { return dexeth.VersionedGases[contractVer] } var bestVer uint32 @@ -427,7 +420,7 @@ func gases(assetID uint32, contractVer uint32, net dex.Network) *dexeth.Gases { return nil } - if contractVer != contractVersionNewest { + if contractVer != contractVersionERC20 { contract, found := netToken.SwapContracts[contractVer] if !found { return nil diff --git a/client/asset/eth/nodeclient_harness_test.go b/client/asset/eth/nodeclient_harness_test.go index e14b698ef6..e11d071cec 100644 --- a/client/asset/eth/nodeclient_harness_test.go +++ b/client/asset/eth/nodeclient_harness_test.go @@ -27,6 +27,7 @@ import ( "fmt" "math" "math/big" + "math/rand" "os" "os/exec" "os/signal" @@ -71,6 +72,7 @@ const ( var ( homeDir = os.Getenv("HOME") + harnessCtlDir = filepath.Join(homeDir, "dextest", "eth", "harness-ctl") simnetWalletDir = filepath.Join(homeDir, "dextest", "eth", "client_rpc_tests", "simnet") participantWalletDir = filepath.Join(homeDir, "dextest", "eth", "client_rpc_tests", "participant") testnetWalletDir = filepath.Join(homeDir, "ethtest", "testnet_contract_tests", "walletA") @@ -94,7 +96,7 @@ var ( participantContractor contractor simnetTokenContractor tokenContractor participantTokenContractor tokenContractor - ethGases = dexeth.VersionedGases[0] + ethGases *dexeth.Gases tokenGases *dexeth.Gases testnetSecPerBlock = 15 * time.Second // secPerBlock is one for simnet, because it takes one second to mine a @@ -135,6 +137,9 @@ var ( usdcID, _ = dex.BipSymbolID("usdc.eth") testTokenID uint32 masterToken *dexeth.Token + + v1 bool + ver uint32 ) func newContract(stamp uint64, secretHash [32]byte, val uint64) *asset.Contract { @@ -146,10 +151,34 @@ func newContract(stamp uint64, secretHash [32]byte, val uint64) *asset.Contract } } -func newRedeem(secret, secretHash [32]byte) *asset.Redemption { +func acLocator(c *asset.Contract) []byte { + return makeLocator(bytesToArray(c.SecretHash), c.Value, c.LockTime) +} + +func makeLocator(secretHash [32]byte, valg, lockTime uint64) []byte { + if ver == 1 { + return (&dexeth.SwapVector{ + From: ethClient.address(), + To: participantEthClient.address(), + Value: valg, + SecretHash: secretHash, + LockTime: lockTime, + }).Locator() + } + return secretHash[:] +} + +func newRedeem(secret, secretHash [32]byte, valg, lockTime uint64) *asset.Redemption { return &asset.Redemption{ Spends: &asset.AuditInfo{ SecretHash: secretHash[:], + Recipient: participantEthClient.address().String(), + Expiration: time.Unix(int64(lockTime), 0), + Coin: &coin{ + // id: txHash, + value: valg, + }, + Contract: dexeth.EncodeContractData(ver, makeLocator(secretHash, valg, lockTime)), }, Secret: secret[:], } @@ -367,16 +396,15 @@ func runSimnet(m *testing.M) (int, error) { return 1, fmt.Errorf("error creating participant wallet dir: %v", err) } - tokenGases = &dexeth.Tokens[testTokenID].NetTokens[dex.Simnet].SwapContracts[0].Gas + tokenGases = &dexeth.Tokens[testTokenID].NetTokens[dex.Simnet].SwapContracts[ver].Gas // ETH swap contract. - masterToken = dexeth.Tokens[testTokenID] - token := masterToken.NetTokens[dex.Simnet] - fmt.Printf("ETH swap contract address is %v\n", dexeth.ContractAddresses[0][dex.Simnet]) - fmt.Printf("Token swap contract addr is %v\n", token.SwapContracts[0].Address) + token := dexeth.Tokens[testTokenID].NetTokens[dex.Simnet] + fmt.Printf("ETH swap contract address is %v\n", dexeth.ContractAddresses[ver][dex.Simnet]) + fmt.Printf("Token swap contract addr is %v\n", token.SwapContracts[ver].Address) fmt.Printf("Test token contract addr is %v\n", token.Address) - ethSwapContractAddr = dexeth.ContractAddresses[0][dex.Simnet] + ethSwapContractAddr = dexeth.ContractAddresses[ver][dex.Simnet] initiatorRPC, participantRPC := rpcEndpoints(dex.Simnet) @@ -411,24 +439,10 @@ func runSimnet(m *testing.M) (int, error) { simnetAddr = simnetAcct.Address participantAddr = participantAcct.Address - if simnetContractor, err = newV0Contractor(dex.Simnet, simnetAddr, ethClient.contractBackend()); err != nil { - return 1, fmt.Errorf("newV0Contractor error: %w", err) - } - if participantContractor, err = newV0Contractor(dex.Simnet, participantAddr, participantEthClient.contractBackend()); err != nil { - return 1, fmt.Errorf("participant newV0Contractor error: %w", err) - } - - if simnetTokenContractor, err = newV0TokenContractor(dex.Simnet, testTokenID, simnetAddr, ethClient.contractBackend()); err != nil { - return 1, fmt.Errorf("newV0TokenContractor error: %w", err) - } - - // I don't know why this is needed for the participant client but not - // the initiator. Without this, we'll get a bind.ErrNoCode from - // (*BoundContract).Call while calling (*ERC20Swap).TokenAddress. - time.Sleep(time.Second) - - if participantTokenContractor, err = newV0TokenContractor(dex.Simnet, testTokenID, participantAddr, participantEthClient.contractBackend()); err != nil { - return 1, fmt.Errorf("participant newV0TokenContractor error: %w", err) + if v1 { + prepareV1SimnetContractors() + } else { + prepareV0SimnetContractors() } if err := ethClient.unlock(pw); err != nil { @@ -439,8 +453,6 @@ func runSimnet(m *testing.M) (int, error) { } // Fund the wallets. - homeDir, _ := os.UserHomeDir() - harnessCtlDir := filepath.Join(homeDir, "dextest", "eth", "harness-ctl") send := func(exe, addr, amt string) error { cmd := exec.CommandContext(ctx, exe, addr, amt) cmd.Dir = harnessCtlDir @@ -489,7 +501,7 @@ func runSimnet(m *testing.M) (int, error) { func runTestnet(m *testing.M) (int, error) { testTokenID = usdcID masterToken = dexeth.Tokens[testTokenID] - tokenGases = &masterToken.NetTokens[dex.Testnet].SwapContracts[0].Gas + tokenGases = &masterToken.NetTokens[dex.Testnet].SwapContracts[ver].Gas if testnetWalletSeed == "" || testnetParticipantWalletSeed == "" { return 1, errors.New("testnet seeds not set") } @@ -504,7 +516,7 @@ func runTestnet(m *testing.M) (int, error) { return 1, fmt.Errorf("error creating testnet participant wallet dir: %v", err) } secPerBlock = testnetSecPerBlock - ethSwapContractAddr = dexeth.ContractAddresses[0][dex.Testnet] + ethSwapContractAddr = dexeth.ContractAddresses[ver][dex.Testnet] fmt.Printf("ETH swap contract address is %v\n", ethSwapContractAddr) initiatorRPC, participantRPC := rpcEndpoints(dex.Testnet) @@ -554,10 +566,15 @@ func runTestnet(m *testing.M) (int, error) { simnetAddr = simnetAcct.Address participantAddr = participantAcct.Address - if simnetContractor, err = newV0Contractor(dex.Testnet, simnetAddr, ethClient.contractBackend()); err != nil { + ctor := newV0Contractor + if ver == 1 { + ctor = newV1Contractor + } + + if simnetContractor, err = ctor(dex.Testnet, simnetAddr, ethClient.contractBackend()); err != nil { return 1, fmt.Errorf("newV0Contractor error: %w", err) } - if participantContractor, err = newV0Contractor(dex.Testnet, participantAddr, participantEthClient.contractBackend()); err != nil { + if participantContractor, err = ctor(dex.Testnet, participantAddr, participantEthClient.contractBackend()); err != nil { return 1, fmt.Errorf("participant newV0Contractor error: %w", err) } @@ -597,6 +614,37 @@ func runTestnet(m *testing.M) (int, error) { return code, nil } +func prepareV0SimnetContractors() (err error) { + return prepareSimnetContractors(newV0Contractor, newV0TokenContractor) +} + +func prepareV1SimnetContractors() (err error) { + return prepareSimnetContractors(newV1Contractor, newV1TokenContractor) +} + +func prepareSimnetContractors(c contractorConstructor, tc tokenContractorConstructor) (err error) { + if simnetContractor, err = c(dex.Simnet, simnetAddr, ethClient.contractBackend()); err != nil { + return fmt.Errorf("new contractor error: %w", err) + } + if participantContractor, err = c(dex.Simnet, participantAddr, participantEthClient.contractBackend()); err != nil { + return fmt.Errorf("participant new contractor error: %w", err) + } + + if simnetTokenContractor, err = tc(dex.Simnet, testTokenID, simnetAddr, ethClient.contractBackend()); err != nil { + return fmt.Errorf("new token contractor error: %w", err) + } + + // I don't know why this is needed for the participant client but not + // the initiator. Without this, we'll get a bind.ErrNoCode from + // (*BoundContract).Call while calling (*ERC20Swap).TokenAddress. + time.Sleep(time.Second) + + if participantTokenContractor, err = tc(dex.Simnet, testTokenID, participantAddr, participantEthClient.contractBackend()); err != nil { + return fmt.Errorf("participant new token contractor error: %w", err) + } + return +} + func useTestnet() error { isTestnet = true b, err := os.ReadFile(testnetCredentialsPath) @@ -624,12 +672,20 @@ func useTestnet() error { } func TestMain(m *testing.M) { + rand.Seed(time.Now().UnixNano()) dexeth.MaybeReadSimnetAddrs() flag.BoolVar(&isTestnet, "testnet", false, "use testnet") flag.BoolVar(&useRPC, "rpc", false, "use RPC") + flag.BoolVar(&v1, "v1", true, "Use Version 1 contract") flag.Parse() + if v1 { + ver = 1 + } + + ethGases = dexeth.VersionedGases[ver] + if isTestnet { if err := useTestnet(); err != nil { fmt.Fprintf(os.Stderr, "error loading testnet: %v", err) @@ -802,7 +858,7 @@ func TestContract(t *testing.T) { if !t.Run("testAddressesHaveFunds", testAddressesHaveFundsFn(100_000_000 /* gwei */)) { t.Fatal("not enough funds") } - t.Run("testSwap", func(t *testing.T) { testSwap(t, BipID) }) + // t.Run("testSwap", func(t *testing.T) { testSwap(t, BipID) }) // TODO: Replace with testStatusAndVector? t.Run("testInitiate", func(t *testing.T) { testInitiate(t, BipID) }) t.Run("testRedeem", func(t *testing.T) { testRedeem(t, BipID) }) t.Run("testRefund", func(t *testing.T) { testRefund(t, BipID) }) @@ -815,7 +871,7 @@ func TestGas(t *testing.T) { } func TestTokenContract(t *testing.T) { - t.Run("testTokenSwap", func(t *testing.T) { testSwap(t, testTokenID) }) + // t.Run("testTokenSwap", func(t *testing.T) { testSwap(t, testTokenID) }) // TODO: Replace with testTokenStatusAndVector? t.Run("testInitiateToken", func(t *testing.T) { testInitiate(t, testTokenID) }) t.Run("testRedeemToken", func(t *testing.T) { testRedeem(t, testTokenID) }) t.Run("testRefundToken", func(t *testing.T) { testRefund(t, testTokenID) }) @@ -1100,17 +1156,6 @@ func testPendingTransactions(t *testing.T) { spew.Dump(txs) } -func testSwap(t *testing.T, assetID uint32) { - var secretHash [32]byte - copy(secretHash[:], encode.RandomBytes(32)) - swap, err := simnetContractor.swap(ctx, secretHash) - if err != nil { - t.Fatal(err) - } - // Should be empty. - spew.Dump(swap) -} - func testSyncProgress(t *testing.T) { p, err := ethClient.syncProgress(ctx) if err != nil { @@ -1134,7 +1179,7 @@ func testInitiateGas(t *testing.T, assetID uint32) { if isTestnet { net = dex.Testnet } - gases := gases(assetID, 0, net) + gases := gases(assetID, ver, net) var previousGas uint64 maxSwaps := 50 @@ -1153,7 +1198,7 @@ func testInitiateGas(t *testing.T, assetID uint32) { expectedGas = gases.SwapAdd actualGas = gas - previousGas } - if actualGas > expectedGas || actualGas < expectedGas*75/100 { + if actualGas > expectedGas || actualGas < expectedGas/2 { t.Fatalf("Expected incremental gas for %d initiations to be close to %d but got %d", i, expectedGas, actualGas) } @@ -1251,15 +1296,7 @@ func testInitiate(t *testing.T, assetID uint32) { numSecretHashes := 10 secretHashes := make([][32]byte, numSecretHashes) for i := 0; i < numSecretHashes; i++ { - copy(secretHashes[i][:], encode.RandomBytes(32)) - swap, err := sc.swap(ctx, secretHashes[i]) - if err != nil { - t.Fatal("unable to get swap state") - } - state := dexeth.SwapStep(swap.State) - if state != dexeth.SSNone { - t.Fatalf("unexpected swap state: want %s got %s", dexeth.SSNone, state) - } + secretHashes[i] = bytesToArray(encode.RandomBytes(32)) } now := uint64(time.Now().Unix()) @@ -1278,10 +1315,10 @@ func testInitiate(t *testing.T, assetID uint32) { }, }, { - name: "1 swap with existing hash", + name: "1 duplicate swap", success: false, swaps: []*asset.Contract{ - newContract(now, secretHashes[0], 1), + newContract(now, secretHashes[0], 2), }, }, { @@ -1337,28 +1374,29 @@ func testInitiate(t *testing.T, assetID uint32) { t.Fatalf("balance error for asset %d, test %s: %v", assetID, test.name, err) } + if !isETH { + originalParentBal, err = ethClient.addressBalance(ctx, ethClient.address()) + if err != nil { + t.Fatalf("balance error for eth, test %s: %v", test.name, err) + } + } + var totalVal uint64 originalStates := make(map[string]dexeth.SwapStep) for _, tSwap := range test.swaps { - swap, err := sc.swap(ctx, bytesToArray(tSwap.SecretHash)) + status, _, err := sc.statusAndVector(ctx, acLocator(tSwap)) if err != nil { t.Fatalf("%s: swap error: %v", test.name, err) } - originalStates[tSwap.SecretHash.String()] = dexeth.SwapStep(swap.State) + originalStates[tSwap.SecretHash.String()] = status.Step totalVal += tSwap.Value } optsVal := totalVal - if !isETH { - optsVal = 0 - originalParentBal, err = ethClient.addressBalance(ctx, ethClient.address()) - if err != nil { - t.Fatalf("balance error for eth, test %s: %v", test.name, err) - } - } - if test.overflow { optsVal = 2 + } else if !isETH { + optsVal = 0 } expGas := gases.SwapN(len(test.swaps)) @@ -1367,7 +1405,7 @@ func testInitiate(t *testing.T, assetID uint32) { t.Fatalf("%s: txOpts error: %v", test.name, err) } var tx *types.Transaction - if test.overflow { + if test.overflow && !v1 { // We're limited by uint64 in v1 switch c := sc.(type) { case *contractorV0: tx, err = initiateOverflow(c, txOpts, test.swaps) @@ -1441,19 +1479,18 @@ func testInitiate(t *testing.T, assetID uint32) { } for _, tSwap := range test.swaps { - swap, err := sc.swap(ctx, bytesToArray(tSwap.SecretHash)) + status, _, err := sc.statusAndVector(ctx, acLocator(tSwap)) if err != nil { t.Fatalf("%s: swap error post-init: %v", test.name, err) } - state := dexeth.SwapStep(swap.State) - if test.success && state != dexeth.SSInitiated { - t.Fatalf("%s: wrong success swap state: want %s got %s", test.name, dexeth.SSInitiated, state) + if test.success && status.Step != dexeth.SSInitiated { + t.Fatalf("%s: wrong success swap state: want %s got %s", test.name, dexeth.SSInitiated, status.Step) } originalState := originalStates[hex.EncodeToString(tSwap.SecretHash[:])] - if !test.success && state != originalState { - t.Fatalf("%s: wrong error swap state: want %s got %s", test.name, originalState, state) + if !test.success && status.Step != originalState { + t.Fatalf("%s: wrong error swap state: want %s got %s", test.name, originalState, status.Step) } } } @@ -1480,8 +1517,11 @@ func testRedeemGas(t *testing.T, assetID uint32) { now := uint64(time.Now().Unix()) swaps := make([]*asset.Contract, 0, numSwaps) + locators := make([][]byte, 0, numSwaps) for i := 0; i < numSwaps; i++ { - swaps = append(swaps, newContract(now, secretHashes[i], 1)) + c := newContract(now, secretHashes[i], 1) + swaps = append(swaps, c) + locators = append(locators, acLocator(c)) } gases := ethGases @@ -1519,19 +1559,19 @@ func testRedeemGas(t *testing.T, assetID uint32) { // Make sure swaps were properly initiated for i := range swaps { - swap, err := c.swap(ctx, bytesToArray(swaps[i].SecretHash)) + status, _, err := c.statusAndVector(ctx, locators[i]) if err != nil { t.Fatal("unable to get swap state") } - if swap.State != dexeth.SSInitiated { - t.Fatalf("unexpected swap state: want %s got %s", dexeth.SSInitiated, swap.State) + if status.Step != dexeth.SSInitiated { + t.Fatalf("unexpected swap state: want %s got %s", dexeth.SSInitiated, status.Step) } } // Test gas usage of redeem function var previous uint64 for i := 0; i < numSwaps; i++ { - gas, err := pc.estimateRedeemGas(ctx, secrets[:i+1]) + gas, err := pc.estimateRedeemGas(ctx, secrets[:i+1], locators[:i+1]) if err != nil { t.Fatalf("Error estimating gas for redeem function: %v", err) } @@ -1545,9 +1585,9 @@ func testRedeemGas(t *testing.T, assetID uint32) { expectedGas = gases.RedeemAdd actualGas = gas - previous } - if actualGas > expectedGas || actualGas < (expectedGas/100*95) { + if actualGas > expectedGas || actualGas < (expectedGas/2) { // Use GetGasEstimates to better precision estimates. t.Fatalf("Expected incremental gas for %d redemptions to be close to %d but got %d", - i, expectedGas, actualGas) + i+1, expectedGas, actualGas) } fmt.Printf("\n\nGas used to redeem %d swaps: %d -- %d more than previous \n\n", i+1, gas, gas-previous) @@ -1582,6 +1622,8 @@ func testRedeem(t *testing.T, assetID uint32) { evmify = tc.evmify } + const val = 1 + tests := []struct { name string sleepNBlocks int @@ -1601,8 +1643,8 @@ func testRedeem(t *testing.T, assetID uint32) { redeemerClient: participantEthClient, redeemer: participantAcct, redeemerContractor: pc, - swaps: []*asset.Contract{newContract(lockTime, secretHashes[0], 1)}, - redemptions: []*asset.Redemption{newRedeem(secrets[0], secretHashes[0])}, + swaps: []*asset.Contract{newContract(lockTime, secretHashes[0], val)}, + redemptions: []*asset.Redemption{newRedeem(secrets[0], secretHashes[0], val, lockTime)}, isRedeemable: []bool{true}, finalStates: []dexeth.SwapStep{dexeth.SSRedeemed}, addAmt: true, @@ -1614,12 +1656,12 @@ func testRedeem(t *testing.T, assetID uint32) { redeemer: participantAcct, redeemerContractor: pc, swaps: []*asset.Contract{ - newContract(lockTime, secretHashes[1], 1), - newContract(lockTime, secretHashes[2], 1), + newContract(lockTime, secretHashes[1], val), + newContract(lockTime, secretHashes[2], val), }, redemptions: []*asset.Redemption{ - newRedeem(secrets[1], secretHashes[1]), - newRedeem(secrets[2], secretHashes[2]), + newRedeem(secrets[1], secretHashes[1], val, lockTime), + newRedeem(secrets[2], secretHashes[2], val, lockTime), }, isRedeemable: []bool{true, true}, finalStates: []dexeth.SwapStep{ @@ -1633,8 +1675,8 @@ func testRedeem(t *testing.T, assetID uint32) { redeemerClient: participantEthClient, redeemer: participantAcct, redeemerContractor: pc, - swaps: []*asset.Contract{newContract(lockTime, secretHashes[3], 1)}, - redemptions: []*asset.Redemption{newRedeem(secrets[3], secretHashes[3])}, + swaps: []*asset.Contract{newContract(lockTime, secretHashes[3], val)}, + redemptions: []*asset.Redemption{newRedeem(secrets[3], secretHashes[3], val, lockTime)}, isRedeemable: []bool{true}, finalStates: []dexeth.SwapStep{dexeth.SSRedeemed}, addAmt: true, @@ -1645,20 +1687,21 @@ func testRedeem(t *testing.T, assetID uint32) { redeemerClient: ethClient, redeemer: simnetAcct, redeemerContractor: c, - swaps: []*asset.Contract{newContract(lockTime, secretHashes[4], 1)}, - redemptions: []*asset.Redemption{newRedeem(secrets[4], secretHashes[4])}, + swaps: []*asset.Contract{newContract(lockTime, secretHashes[4], val)}, + redemptions: []*asset.Redemption{newRedeem(secrets[4], secretHashes[4], val, lockTime)}, isRedeemable: []bool{false}, finalStates: []dexeth.SwapStep{dexeth.SSInitiated}, addAmt: false, }, { name: "bad secret", + expectRedeemErr: true, sleepNBlocks: 8, redeemerClient: participantEthClient, redeemer: participantAcct, redeemerContractor: pc, - swaps: []*asset.Contract{newContract(lockTime, secretHashes[5], 1)}, - redemptions: []*asset.Redemption{newRedeem(secrets[6], secretHashes[5])}, + swaps: []*asset.Contract{newContract(lockTime, secretHashes[5], val)}, + redemptions: []*asset.Redemption{newRedeem(secrets[6], secretHashes[5], val, lockTime)}, isRedeemable: []bool{false}, finalStates: []dexeth.SwapStep{dexeth.SSInitiated}, addAmt: false, @@ -1671,12 +1714,12 @@ func testRedeem(t *testing.T, assetID uint32) { redeemer: participantAcct, redeemerContractor: pc, swaps: []*asset.Contract{ - newContract(lockTime, secretHashes[7], 1), - newContract(lockTime, secretHashes[8], 1), + newContract(lockTime, secretHashes[7], val), + newContract(lockTime, secretHashes[8], val), }, redemptions: []*asset.Redemption{ - newRedeem(secrets[7], secretHashes[7]), - newRedeem(secrets[7], secretHashes[7]), + newRedeem(secrets[7], secretHashes[7], val, lockTime), + newRedeem(secrets[7], secretHashes[7], val, lockTime), }, isRedeemable: []bool{true, true}, finalStates: []dexeth.SwapStep{ @@ -1689,14 +1732,16 @@ func testRedeem(t *testing.T, assetID uint32) { for _, test := range tests { var optsVal uint64 - for i, contract := range test.swaps { - swap, err := c.swap(ctx, bytesToArray(test.swaps[i].SecretHash)) + locators := make([][]byte, 0, len(test.swaps)) + for _, contract := range test.swaps { + locator := acLocator(contract) + locators = append(locators, locator) + status, _, err := c.statusAndVector(ctx, locator) if err != nil { t.Fatal("unable to get swap state") } - state := dexeth.SwapStep(swap.State) - if state != dexeth.SSNone { - t.Fatalf("unexpected swap state for test %v: want %s got %s", test.name, dexeth.SSNone, state) + if status.Step != dexeth.SSNone { + t.Fatalf("unexpected swap state for test %v: want %s got %s", test.name, dexeth.SSNone, status.Step) } if isETH { optsVal += contract.Value @@ -1712,11 +1757,11 @@ func testRedeem(t *testing.T, assetID uint32) { } } - txOpts, err := test.redeemerClient.txOpts(ctx, optsVal, gases.SwapN(len(test.swaps)), dexeth.GweiToWei(maxFeeRate), nil) + txOpts, err := ethClient.txOpts(ctx, optsVal, gases.SwapN(len(test.swaps)), dexeth.GweiToWei(maxFeeRate), nil) if err != nil { t.Fatalf("%s: txOpts error: %v", test.name, err) } - tx, err := test.redeemerContractor.initiate(txOpts, test.swaps) + tx, err := c.initiate(txOpts, test.swaps) if err != nil { t.Fatalf("%s: initiate error: %v ", test.name, err) } @@ -1739,22 +1784,27 @@ func testRedeem(t *testing.T, assetID uint32) { fmt.Printf("Gas used for %d inits: %d \n", len(test.swaps), receipt.GasUsed) for i := range test.swaps { - swap, err := test.redeemerContractor.swap(ctx, bytesToArray(test.swaps[i].SecretHash)) + status, _, err := test.redeemerContractor.statusAndVector(ctx, locators[i]) if err != nil { t.Fatal("unable to get swap state") } - if swap.State != dexeth.SSInitiated { - t.Fatalf("unexpected swap state for test %v: want %s got %s", test.name, dexeth.SSInitiated, swap.State) + if status.Step != dexeth.SSInitiated { + t.Fatalf("unexpected swap state for test %v: want %s got %s", test.name, dexeth.SSInitiated, status.Step) } expected := test.isRedeemable[i] redemption := test.redemptions[i] - isRedeemable, err := test.redeemerContractor.isRedeemable(bytesToArray(redemption.Spends.SecretHash), bytesToArray(redemption.Secret)) + _, locator, err := dexeth.DecodeLocator(redemption.Spends.Contract) + if err != nil { + t.Fatalf("%s: error decoding locator: %v", test.name, err) + } + + isRedeemable, err := test.redeemerContractor.isRedeemable(locator, bytesToArray(redemption.Secret)) if err != nil { t.Fatalf(`test "%v": error calling isRedeemable: %v`, test.name, err) } else if isRedeemable != expected { - t.Fatalf(`test "%v": expected isRedeemable to be %v, but got %v. swap = %+v`, test.name, expected, isRedeemable, swap) + t.Fatalf(`test "%v": expected isRedeemable to be %v, but got %v. status = %+v`, test.name, expected, isRedeemable, status) } } @@ -1855,15 +1905,14 @@ func testRedeem(t *testing.T, assetID uint32) { test.name, wantBal, bal, diff) } - for i, redemption := range test.redemptions { - swap, err := c.swap(ctx, bytesToArray(redemption.Spends.SecretHash)) + for i := range test.redemptions { + status, _, err := c.statusAndVector(ctx, locators[i]) if err != nil { t.Fatalf("unexpected error for test %v: %v", test.name, err) } - state := dexeth.SwapStep(swap.State) - if state != test.finalStates[i] { + if status.Step != test.finalStates[i] { t.Fatalf("unexpected swap state for test %v [%d]: want %s got %s", - test.name, i, test.finalStates[i], state) + test.name, i, test.finalStates[i], status.Step) } } } @@ -1895,7 +1944,9 @@ func testRefundGas(t *testing.T, assetID uint32) { if err != nil { t.Fatalf("txOpts error: %v", err) } - _, err = c.initiate(txOpts, []*asset.Contract{newContract(lockTime, secretHash, 1)}) + ac := newContract(lockTime, secretHash, 1) + locator := acLocator(ac) + _, err = c.initiate(txOpts, []*asset.Contract{ac}) if err != nil { t.Fatalf("Unable to initiate swap: %v ", err) } @@ -1903,22 +1954,21 @@ func testRefundGas(t *testing.T, assetID uint32) { t.Fatalf("unexpected error while waiting to mine: %v", err) } - swap, err := c.swap(ctx, secretHash) + status, _, err := c.statusAndVector(ctx, locator) if err != nil { t.Fatal("unable to get swap state") } - state := dexeth.SwapStep(swap.State) - if state != dexeth.SSInitiated { - t.Fatalf("unexpected swap state: want %s got %s", dexeth.SSInitiated, state) + if status.Step != dexeth.SSInitiated { + t.Fatalf("unexpected swap state: want %s got %s", dexeth.SSInitiated, status.Step) } - gas, err := c.estimateRefundGas(ctx, secretHash) + gas, err := c.estimateRefundGas(ctx, locator) if err != nil { t.Fatalf("Error estimating gas for refund function: %v", err) } if isETH { expGas := gases.Refund - if gas > expGas || gas < expGas*95/100 { + if gas > expGas || gas < expGas/2 { t.Fatalf("expected refund gas to be near %d, but got %d", expGas, gas) } @@ -2010,21 +2060,23 @@ func testRefund(t *testing.T, assetID uint32) { copy(secret[:], encode.RandomBytes(32)) secretHash := sha256.Sum256(secret[:]) - swap, err := test.refunderContractor.swap(ctx, secretHash) + inLocktime := uint64(time.Now().Add(test.addTime).Unix()) + ac := newContract(inLocktime, secretHash, amt) + locator := acLocator(ac) + + status, _, err := test.refunderContractor.statusAndVector(ctx, locator) if err != nil { t.Fatalf("%s: unable to get swap state pre-init", test.name) } - if swap.State != dexeth.SSNone { - t.Fatalf("unexpected swap state for test %v: want %s got %s", test.name, dexeth.SSNone, swap.State) + if status.Step != dexeth.SSNone { + t.Fatalf("unexpected swap state for test %v: want %s got %s", test.name, dexeth.SSNone, status.Step) } - inLocktime := uint64(time.Now().Add(test.addTime).Unix()) - txOpts, err := ethClient.txOpts(ctx, optsVal, gases.SwapN(1), nil, nil) if err != nil { t.Fatalf("%s: txOpts error: %v", test.name, err) } - _, err = c.initiate(txOpts, []*asset.Contract{newContract(inLocktime, secretHash, amt)}) + _, err = c.initiate(txOpts, []*asset.Contract{ac}) if err != nil { t.Fatalf("%s: initiate error: %v ", test.name, err) } @@ -2035,7 +2087,7 @@ func testRefund(t *testing.T, assetID uint32) { } txOpts, _ = participantEthClient.txOpts(ctx, 0, gases.RedeemN(1), nil, nil) - _, err := pc.redeem(txOpts, []*asset.Redemption{newRedeem(secret, secretHash)}) + _, err := pc.redeem(txOpts, []*asset.Redemption{newRedeem(secret, secretHash, amt, inLocktime)}) if err != nil { t.Fatalf("%s: redeem error: %v", test.name, err) } @@ -2059,7 +2111,7 @@ func testRefund(t *testing.T, assetID uint32) { t.Fatalf("%s: balance error: %v", test.name, err) } - isRefundable, err := test.refunderContractor.isRefundable(secretHash) + isRefundable, err := test.refunderContractor.isRefundable(locator) if err != nil { t.Fatalf("%s: isRefundable error %v", test.name, err) } @@ -2072,7 +2124,7 @@ func testRefund(t *testing.T, assetID uint32) { if err != nil { t.Fatalf("%s: txOpts error: %v", test.name, err) } - tx, err := test.refunderContractor.refund(txOpts, secretHash) + tx, err := test.refunderContractor.refund(txOpts, locator) if err != nil { t.Fatalf("%s: refund error: %v", test.name, err) } @@ -2147,12 +2199,12 @@ func testRefund(t *testing.T, assetID uint32) { test.name, wantBal, bal, diff) } - swap, err = test.refunderContractor.swap(ctx, secretHash) + status, _, err = test.refunderContractor.statusAndVector(ctx, locator) if err != nil { t.Fatalf("%s: post-refund swap error: %v", test.name, err) } - if swap.State != test.finalState { - t.Fatalf("%s: wrong swap state: want %s got %s", test.name, test.finalState, swap.State) + if status.Step != test.finalState { + t.Fatalf("%s: wrong swap state: want %s got %s", test.name, test.finalState, status.Step) } } } @@ -2238,6 +2290,8 @@ func TestReplayAttack(t *testing.T) { } var secretHash [32]byte + var inLocktime uint64 + const val = 1 // Make four swaps that should be locked and refundable and one that is // soon refundable. for i := 0; i < 5; i++ { @@ -2246,13 +2300,13 @@ func TestReplayAttack(t *testing.T) { secretHash = sha256.Sum256(secret[:]) if i != 4 { - inLocktime := uint64(time.Now().Add(time.Hour).Unix()) + inLocktime = uint64(time.Now().Add(time.Hour).Unix()) txOpts, err := cl.txOpts(ctx, 1, ethGases.SwapN(1), nil, nil) if err != nil { t.Fatalf("txOpts error: %v", err) } - _, err = simnetContractor.initiate(txOpts, []*asset.Contract{newContract(inLocktime, secretHash, 1)}) + _, err = simnetContractor.initiate(txOpts, []*asset.Contract{newContract(inLocktime, secretHash, val)}) if err != nil { t.Fatalf("unable to initiate swap: %v ", err) } @@ -2266,7 +2320,7 @@ func TestReplayAttack(t *testing.T) { intermediateContractVal, _ := cl.addressBalance(ctx, ethSwapContractAddr) t.Logf("intermediate contract value %d", dexeth.WeiToGwei(intermediateContractVal)) - inLocktime := time.Now().Add(-1 * time.Second).Unix() + inLocktime = uint64(time.Now().Add(-1 * time.Second).Unix()) // Set some variables in the contract used for the exploit. This // will fail (silently) due to require(msg.origin == msg.sender) // in the real contract. @@ -2274,7 +2328,7 @@ func TestReplayAttack(t *testing.T) { if err != nil { t.Fatalf("txOpts error: %v", err) } - _, err = reentryContract.SetUsUpTheBomb(txOpts, ethSwapContractAddr, secretHash, big.NewInt(inLocktime), participantAddr) + _, err = reentryContract.SetUsUpTheBomb(txOpts, ethSwapContractAddr, secretHash, big.NewInt(int64(inLocktime)), participantAddr) if err != nil { t.Fatalf("unable to set up the bomb: %v", err) } @@ -2359,13 +2413,12 @@ func TestReplayAttack(t *testing.T) { // The exploit failed and status should be SSNone because initiation also // failed. - swap, err := simnetContractor.swap(ctx, secretHash) + status, _, err := simnetContractor.statusAndVector(ctx, makeLocator(secretHash, val, inLocktime)) if err != nil { t.Fatal(err) } - state := dexeth.SwapStep(swap.State) - if state != dexeth.SSNone { - t.Fatalf("unexpected swap state: want %s got %s", dexeth.SSNone, state) + if status.Step != dexeth.SSNone { + t.Fatalf("unexpected swap state: want %s got %s", dexeth.SSNone, status.Step) } // The contract should hold four more ether because initiation of one diff --git a/dex/networks/erc20/contracts/ERC20SwapV0.sol b/dex/networks/erc20/contracts/ERC20SwapV0.sol index a70d188486..dfd98c9663 100644 --- a/dex/networks/erc20/contracts/ERC20SwapV0.sol +++ b/dex/networks/erc20/contracts/ERC20SwapV0.sol @@ -2,7 +2,7 @@ // pragma should be as specific as possible to allow easier validation. pragma solidity = 0.8.15; -// ETHSwap creates a contract to be deployed on an ethereum network. In +// ERC20Swap creates a contract to be deployed on an ethereum network. In // order to save on gas fees, a separate ERC20Swap contract is deployed // for each ERC20 token. After deployed, it keeps a map of swaps that // facilitates atomic swapping of ERC20 tokens with other crypto currencies diff --git a/dex/networks/erc20/contracts/ERC20SwapV1.sol b/dex/networks/erc20/contracts/ERC20SwapV1.sol new file mode 100644 index 0000000000..e5a7eaef45 --- /dev/null +++ b/dex/networks/erc20/contracts/ERC20SwapV1.sol @@ -0,0 +1,241 @@ +// SPDX-License-Identifier: BlueOak-1.0.0 +// pragma should be as specific as possible to allow easier validation. +pragma solidity = 0.8.15; + +// ERC20Swap creates a contract to be deployed on an ethereum network. In +// order to save on gas fees, a separate ERC20Swap contract is deployed +// for each ERC20 token. After deployed, it keeps a map of swaps that +// facilitates atomic swapping of ERC20 tokens with other crypto currencies +// that support time locks. +// +// It accomplishes this by holding tokens acquired during a swap initiation +// until conditions are met. Prior to initiating a swap, the initiator must +// approve the ERC20Swap contract to be able to spend the initiator's tokens. +// When calling initiate, the necessary tokens for swaps are transferred to +// the swap contract. 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 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. +contract ERC20Swap { + bytes4 private constant TRANSFER_FROM_SELECTOR = bytes4(keccak256("transferFrom(address,address,uint256)")); + bytes4 private constant TRANSFER_SELECTOR = bytes4(keccak256("transfer(address,uint256)")); + + address public immutable token_address; + + // Step is a type that hold's a contract's current step. Empty is the + // uninitiated or null value. + enum Step { Empty, Filled, Redeemed, Refunded } + + struct Status { + Step step; + bytes32 secret; + uint256 blockNumber; + } + + bytes32 constant RefundRecord = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF; + + // swaps is a map of contract hashes to the "swap record". The swap record + // has the following interpretation. + // if (record == bytes32(0x00)): contract is uninitiated + // else if (uint256(record) < block.number && sha256(record) != contract.secretHash): + // contract is initiated and redeemable by the participant with the secret. + // else if (sha256(record) == contract.secretHash): contract has been redeemed + // else if (record == RefundRecord): contract has been refunded + // else: invalid record. Should be impossible by construction + mapping(bytes32 => bytes32) public swaps; + + // Vector is the information necessary for initialization and redemption + // or refund. The Vector itself is not stored on-chain. Instead, a key + // unique to the Vector is generated from the Vector data and keys + // the swap record. + struct Vector { + bytes32 secretHash; + address initiator; + uint64 refundTimestamp; + address participant; + uint64 value; + } + + // contractKey generates a key hash which commits to the contract data. The + // generated hash is used as a key in the swaps map. + function contractKey(Vector calldata v) public pure returns (bytes32) { + return sha256(bytes.concat(v.secretHash, bytes20(v.initiator), bytes20(v.participant), bytes8(v.value), bytes8(v.refundTimestamp))); + } + + // Redemption is the information necessary to redeem a Vector. Since we + // don't store the Vector itself, it must be provided as part of the + // redemption. + struct Redemption { + Vector v; + bytes32 secret; + } + + function secretValidates(bytes32 secret, bytes32 secretHash) public pure returns (bool) { + return sha256(bytes.concat(secret)) == secretHash; + } + + constructor(address token) { + token_address = token; + } + + // senderIsOrigin ensures that this contract cannot be used by other + // contracts, which reduces possible attack vectors. + modifier senderIsOrigin() { + require(tx.origin == msg.sender, "sender != origin"); + _; + } + + // retrieveStatus retrieves the current swap record for the contract. + function retrieveStatus(Vector calldata v) + private view returns (bytes32, bytes32, uint256) + { + bytes32 k = contractKey(v); + bytes32 record = swaps[k]; + return (k, record, uint256(record)); + } + + // state returns the current state of the swap. + function status(Vector calldata v) + public view returns(Status memory) + { + (, bytes32 record, uint256 blockNum) = retrieveStatus(v); + Status memory r; + if (blockNum == 0) { + r.step = Step.Empty; + } else if (record == RefundRecord) { + r.step = Step.Refunded; + } else if (secretValidates(record, v.secretHash)) { + r.step = Step.Redeemed; + r.secret = record; + } else { + r.step = Step.Filled; + r.blockNumber = blockNum; + } + return r; + } + + // initiate initiates an array of Vectors. + function initiate(Vector[] calldata contracts) + public + payable + senderIsOrigin() + { + uint initVal = 0; + for (uint i = 0; i < contracts.length; i++) { + Vector calldata v = contracts[i]; + + require(v.value > 0, "0 val"); + require(v.refundTimestamp > 0, "0 refundTimestamp"); + + bytes32 k = contractKey(v); + bytes32 record = swaps[k]; + require(record == bytes32(0), "swap not empty"); + + record = bytes32(block.number); + require(!secretValidates(record, v.secretHash), "hash collision"); + + swaps[k] = record; + + initVal += v.value * 1 gwei; + } + + bool success; + bytes memory data; + (success, data) = token_address.call(abi.encodeWithSelector(TRANSFER_FROM_SELECTOR, msg.sender, address(this), initVal)); + require(success && (data.length == 0 || abi.decode(data, (bool))), 'transfer from failed'); + } + + // isRedeemable returns whether or not a swap identified by secretHash + // can be redeemed using secret. + function isRedeemable(Vector calldata v) + public + view + returns (bool) + { + (, bytes32 record, uint256 blockNum) = retrieveStatus(v); + return blockNum != 0 && !secretValidates(record, v.secretHash); + } + + // redeem redeems a Vector. It checks that the sender is not a contract, + // and that the secret hash hashes to secretHash. msg.value is tranfered + // from ETHSwap to the sender. + // + // To prevent reentry attack, 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. + function redeem(Redemption[] calldata redemptions) + public + senderIsOrigin() + { + uint amountToRedeem = 0; + for (uint i = 0; i < redemptions.length; i++) { + Redemption calldata r = redemptions[i]; + + require(r.v.participant == msg.sender, "not authed"); + + (bytes32 k, bytes32 record, uint256 blockNum) = retrieveStatus(r.v); + + // To be redeemable, the record needs to represent a valid block + // number. + require(blockNum > 0 && blockNum < block.number, "unfilled swap"); + + // Can't already be redeemed. + require(!secretValidates(record, r.v.secretHash), "already redeemed"); + + // Are they presenting the correct secret? + require(secretValidates(r.secret, r.v.secretHash), "invalid secret"); + + swaps[k] = r.secret; + amountToRedeem += r.v.value * 1 gwei; + } + + bool success; + bytes memory data; + (success, data) = token_address.call(abi.encodeWithSelector(TRANSFER_SELECTOR, msg.sender, amountToRedeem)); + require(success && (data.length == 0 || abi.decode(data, (bool))), 'transfer failed'); + } + + + // refund refunds a Vector. It checks that the sender is not a contract + // and that the refund time has passed. msg.value is transfered from the + // contract to the sender = Vector.participant. + // + // It is important to note that this also uses call.value which comes with + // no restrictions on gas used. See redeem for more info. + function refund(Vector calldata v) + public + senderIsOrigin() + { + // Is this contract even in a refundable state? + require(block.timestamp >= v.refundTimestamp, "locktime not expired"); + + // Retrieve the record. + (bytes32 k, bytes32 record, uint256 blockNum) = retrieveStatus(v); + + // Is this swap initialized? + require(blockNum > 0 && blockNum <= block.number, "swap not active"); + + // Is it already redeemed? + require(!secretValidates(record, v.secretHash), "swap already redeemed"); + + // Is it already refunded? + require(record != RefundRecord, "swap already refunded"); + + swaps[k] = RefundRecord; + + bool success; + bytes memory data; + (success, data) = token_address.call(abi.encodeWithSelector(TRANSFER_SELECTOR, msg.sender, v.value)); + require(success && (data.length == 0 || abi.decode(data, (bool))), 'transfer failed'); + } +} diff --git a/dex/networks/erc20/contracts/updatecontract.sh b/dex/networks/erc20/contracts/updatecontract.sh index cb17807233..2bad4f19ec 100755 --- a/dex/networks/erc20/contracts/updatecontract.sh +++ b/dex/networks/erc20/contracts/updatecontract.sh @@ -77,3 +77,16 @@ if [ "$VERSION" -eq "0" ]; then # Reorder the imports since we rewrote go-ethereum/event to a dcrdex package. gofmt -s -w "$CONTRACT_FILE" fi + +if [ "$VERSION" -eq "1" ]; then + perl -0pi -e 's/go-ethereum\/event"/go-ethereum\/event"\n\tethv1 "decred.org\/dcrdex\/dex\/networks\/eth\/contracts\/v1"/' $CONTRACT_FILE + + perl -0pi -e 's/\/\/ ERC20SwapVector[^}]*}\n\n//' $CONTRACT_FILE + perl -0pi -e 's/ERC20SwapVector/ethv1.ETHSwapVector/g' $CONTRACT_FILE + + perl -0pi -e 's/\/\/ ERC20SwapRedemption[^}]*}\n\n//' $CONTRACT_FILE + perl -0pi -e 's/ERC20SwapRedemption/ethv1.ETHSwapRedemption/g' $CONTRACT_FILE + + perl -0pi -e 's/\/\/ ERC20SwapStatus[^}]*}\n\n//' $CONTRACT_FILE + perl -0pi -e 's/ERC20SwapStatus/ethv1.ETHSwapStatus/g' $CONTRACT_FILE +fi diff --git a/dex/networks/erc20/contracts/v0/BinRuntimeV0.go b/dex/networks/erc20/contracts/v0/BinRuntimeV0.go index eefa5f592b..8320967042 100644 --- a/dex/networks/erc20/contracts/v0/BinRuntimeV0.go +++ b/dex/networks/erc20/contracts/v0/BinRuntimeV0.go @@ -3,4 +3,4 @@ package v0 -const ERC20SwapRuntimeBin = "608060405234801561001057600080fd5b50600436106100885760003560e01c8063bfd2fd971161005b578063bfd2fd971461011d578063d0f761c014610140578063eb84e7f214610153578063f4fd17f9146101c257600080fd5b80637249fbb61461008d57806376467cbd146100a25780638c8e8fee146100cb578063a8793f941461010a575b600080fd5b6100a061009b366004610bb0565b6101d5565b005b6100b56100b0366004610bb0565b610394565b6040516100c29190610c01565b60405180910390f35b6100f27f000000000000000000000000000000000000000000000000000000000000000081565b6040516001600160a01b0390911681526020016100c2565b6100a0610118366004610c66565b61046f565b61013061012b366004610cdb565b61076a565b60405190151581526020016100c2565b61013061014e366004610bb0565b610834565b6101af610161366004610bb0565b60006020819052908152604090208054600182015460028301546003840154600485015460059095015493949293919290916001600160a01b0391821691811690600160a01b900460ff1687565b6040516100c29796959493929190610cfd565b6100a06101d0366004610d49565b610894565b3233146101fd5760405162461bcd60e51b81526004016101f490610dac565b60405180910390fd5b61020681610834565b6102435760405162461bcd60e51b815260206004820152600e60248201526d6e6f7420726566756e6461626c6560901b60448201526064016101f4565b60008181526020818152604080832060058101805460ff60a01b1916600360a01b17905560018101548251336024820152604480820192909252835180820390920182526064018352928301805163a9059cbb60e01b6001600160e01b0390911617905290519092916060916001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016916102e391610dd6565b6000604051808303816000865af19150503d8060008114610320576040519150601f19603f3d011682016040523d82523d6000602084013e610325565b606091505b5090925090508180156103505750805115806103505750808060200190518101906103509190610e11565b61038e5760405162461bcd60e51b815260206004820152600f60248201526e1d1c985b9cd9995c8819985a5b1959608a1b60448201526064016101f4565b50505050565b6103d16040805160e081018252600080825260208201819052918101829052606081018290526080810182905260a081018290529060c082015290565b60008281526020818152604091829020825160e08101845281548152600182015492810192909252600281015492820192909252600380830154606083015260048301546001600160a01b039081166080840152600584015490811660a084015291929160c0840191600160a01b90910460ff169081111561045557610455610bc9565b600381111561046657610466610bc9565b90525092915050565b32331461048e5760405162461bcd60e51b81526004016101f490610dac565b6000805b8281101561063357368484838181106104ad576104ad610e33565b90506080020190506000806000836020013581526020019081526020016000209050600082606001351161050b5760405162461bcd60e51b81526020600482015260056024820152640c081d985b60da1b60448201526064016101f4565b813561054d5760405162461bcd60e51b815260206004820152601160248201527003020726566756e6454696d657374616d7607c1b60448201526064016101f4565b60006005820154600160a01b900460ff16600381111561056f5761056f610bc9565b146105ae5760405162461bcd60e51b815260206004820152600f60248201526e0c8eae040e6cac6e4cae840d0c2e6d608b1b60448201526064016101f4565b436002820155813560038201556004810180546001600160a01b031916331790556105df6060830160408401610e49565b6005820180546060850135600185018190556001600160a01b03939093166001600160a81b031990911617600160a01b17905561061c9085610e88565b93505050808061062b90610ea0565b915050610492565b5060408051336024820152306044820152606480820184905282518083039091018152608490910182526020810180516001600160e01b03166323b872dd60e01b17905290516000916060916001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016916106b391610dd6565b6000604051808303816000865af19150503d80600081146106f0576040519150601f19603f3d011682016040523d82523d6000602084013e6106f5565b606091505b5090925090508180156107205750805115806107205750808060200190518101906107209190610e11565b6107635760405162461bcd60e51b81526020600482015260146024820152731d1c985b9cd9995c88199c9bdb4819985a5b195960621b60448201526064016101f4565b5050505050565b600082815260208190526040812060016005820154600160a01b900460ff16600381111561079a5761079a610bc9565b1480156107b3575060058101546001600160a01b031633145b801561082c5750836002846040516020016107d091815260200190565b60408051601f19818403018152908290526107ea91610dd6565b602060405180830381855afa158015610807573d6000803e3d6000fd5b5050506040513d601f19601f8201168201806040525081019061082a9190610eb9565b145b949350505050565b600081815260208190526040812060016005820154600160a01b900460ff16600381111561086457610864610bc9565b14801561087d575060048101546001600160a01b031633145b801561088d575080600301544210155b9392505050565b3233146108b35760405162461bcd60e51b81526004016101f490610dac565b6000805b82811015610a8b57368484838181106108d2576108d2610e33565b6020604091820293909301838101356000908152938490529220919250600190506005820154600160a01b900460ff16600381111561091357610913610bc9565b1461094c5760405162461bcd60e51b815260206004820152600960248201526862616420737461746560b81b60448201526064016101f4565b60058101546001600160a01b0316331461099a5760405162461bcd60e51b815260206004820152600f60248201526e189859081c185c9d1a58da5c185b9d608a1b60448201526064016101f4565b8160200135600283600001356040516020016109b891815260200190565b60408051601f19818403018152908290526109d291610dd6565b602060405180830381855afa1580156109ef573d6000803e3d6000fd5b5050506040513d601f19601f82011682018060405250810190610a129190610eb9565b14610a4c5760405162461bcd60e51b815260206004820152600a602482015269189859081cd958dc995d60b21b60448201526064016101f4565b60058101805460ff60a01b1916600160a11b179055813581556001810154610a749085610e88565b935050508080610a8390610ea0565b9150506108b7565b5060408051336024820152604480820184905282518083039091018152606490910182526020810180516001600160e01b031663a9059cbb60e01b17905290516000916060916001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001691610b0591610dd6565b6000604051808303816000865af19150503d8060008114610b42576040519150601f19603f3d011682016040523d82523d6000602084013e610b47565b606091505b509092509050818015610b72575080511580610b72575080806020019051810190610b729190610e11565b6107635760405162461bcd60e51b815260206004820152600f60248201526e1d1c985b9cd9995c8819985a5b1959608a1b60448201526064016101f4565b600060208284031215610bc257600080fd5b5035919050565b634e487b7160e01b600052602160045260246000fd5b60048110610bfd57634e487b7160e01b600052602160045260246000fd5b9052565b600060e08201905082518252602083015160208301526040830151604083015260608301516060830152608083015160018060a01b0380821660808501528060a08601511660a0850152505060c0830151610c5f60c0840182610bdf565b5092915050565b60008060208385031215610c7957600080fd5b823567ffffffffffffffff80821115610c9157600080fd5b818501915085601f830112610ca557600080fd5b813581811115610cb457600080fd5b8660208260071b8501011115610cc957600080fd5b60209290920196919550909350505050565b60008060408385031215610cee57600080fd5b50508035926020909101359150565b8781526020810187905260408101869052606081018590526001600160a01b038481166080830152831660a082015260e08101610d3d60c0830184610bdf565b98975050505050505050565b60008060208385031215610d5c57600080fd5b823567ffffffffffffffff80821115610d7457600080fd5b818501915085601f830112610d8857600080fd5b813581811115610d9757600080fd5b8660208260061b8501011115610cc957600080fd5b60208082526010908201526f39b2b73232b910109e9037b934b3b4b760811b604082015260600190565b6000825160005b81811015610df75760208186018101518583015201610ddd565b81811115610e06576000828501525b509190910192915050565b600060208284031215610e2357600080fd5b8151801515811461088d57600080fd5b634e487b7160e01b600052603260045260246000fd5b600060208284031215610e5b57600080fd5b81356001600160a01b038116811461088d57600080fd5b634e487b7160e01b600052601160045260246000fd5b60008219821115610e9b57610e9b610e72565b500190565b600060018201610eb257610eb2610e72565b5060010190565b600060208284031215610ecb57600080fd5b505191905056fea26469706673582212203e5af33d9672cc61834e91e1c391f408502e89feacb59762150c3e8ad4b8eb8764736f6c634300080f0033" +const ERC20SwapRuntimeBin = "608060405234801561001057600080fd5b50600436106100885760003560e01c8063bfd2fd971161005b578063bfd2fd971461011d578063d0f761c014610140578063eb84e7f214610153578063f4fd17f9146101c257600080fd5b80637249fbb61461008d57806376467cbd146100a25780638c8e8fee146100cb578063a8793f941461010a575b600080fd5b6100a061009b366004610bb0565b6101d5565b005b6100b56100b0366004610bb0565b610394565b6040516100c29190610c01565b60405180910390f35b6100f27f000000000000000000000000000000000000000000000000000000000000000081565b6040516001600160a01b0390911681526020016100c2565b6100a0610118366004610c66565b61046f565b61013061012b366004610cdb565b61076a565b60405190151581526020016100c2565b61013061014e366004610bb0565b610834565b6101af610161366004610bb0565b60006020819052908152604090208054600182015460028301546003840154600485015460059095015493949293919290916001600160a01b0391821691811690600160a01b900460ff1687565b6040516100c29796959493929190610cfd565b6100a06101d0366004610d49565b610894565b3233146101fd5760405162461bcd60e51b81526004016101f490610dac565b60405180910390fd5b61020681610834565b6102435760405162461bcd60e51b815260206004820152600e60248201526d6e6f7420726566756e6461626c6560901b60448201526064016101f4565b60008181526020818152604080832060058101805460ff60a01b1916600360a01b17905560018101548251336024820152604480820192909252835180820390920182526064018352928301805163a9059cbb60e01b6001600160e01b0390911617905290519092916060916001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016916102e391610dd6565b6000604051808303816000865af19150503d8060008114610320576040519150601f19603f3d011682016040523d82523d6000602084013e610325565b606091505b5090925090508180156103505750805115806103505750808060200190518101906103509190610e11565b61038e5760405162461bcd60e51b815260206004820152600f60248201526e1d1c985b9cd9995c8819985a5b1959608a1b60448201526064016101f4565b50505050565b6103d16040805160e081018252600080825260208201819052918101829052606081018290526080810182905260a081018290529060c082015290565b60008281526020818152604091829020825160e08101845281548152600182015492810192909252600281015492820192909252600380830154606083015260048301546001600160a01b039081166080840152600584015490811660a084015291929160c0840191600160a01b90910460ff169081111561045557610455610bc9565b600381111561046657610466610bc9565b90525092915050565b32331461048e5760405162461bcd60e51b81526004016101f490610dac565b6000805b8281101561063357368484838181106104ad576104ad610e33565b90506080020190506000806000836020013581526020019081526020016000209050600082606001351161050b5760405162461bcd60e51b81526020600482015260056024820152640c081d985b60da1b60448201526064016101f4565b813561054d5760405162461bcd60e51b815260206004820152601160248201527003020726566756e6454696d657374616d7607c1b60448201526064016101f4565b60006005820154600160a01b900460ff16600381111561056f5761056f610bc9565b146105ae5760405162461bcd60e51b815260206004820152600f60248201526e0c8eae040e6cac6e4cae840d0c2e6d608b1b60448201526064016101f4565b436002820155813560038201556004810180546001600160a01b031916331790556105df6060830160408401610e49565b6005820180546060850135600185018190556001600160a01b03939093166001600160a81b031990911617600160a01b17905561061c9085610e88565b93505050808061062b90610ea0565b915050610492565b5060408051336024820152306044820152606480820184905282518083039091018152608490910182526020810180516001600160e01b03166323b872dd60e01b17905290516000916060916001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016916106b391610dd6565b6000604051808303816000865af19150503d80600081146106f0576040519150601f19603f3d011682016040523d82523d6000602084013e6106f5565b606091505b5090925090508180156107205750805115806107205750808060200190518101906107209190610e11565b6107635760405162461bcd60e51b81526020600482015260146024820152731d1c985b9cd9995c88199c9bdb4819985a5b195960621b60448201526064016101f4565b5050505050565b600082815260208190526040812060016005820154600160a01b900460ff16600381111561079a5761079a610bc9565b1480156107b3575060058101546001600160a01b031633145b801561082c5750836002846040516020016107d091815260200190565b60408051601f19818403018152908290526107ea91610dd6565b602060405180830381855afa158015610807573d6000803e3d6000fd5b5050506040513d601f19601f8201168201806040525081019061082a9190610eb9565b145b949350505050565b600081815260208190526040812060016005820154600160a01b900460ff16600381111561086457610864610bc9565b14801561087d575060048101546001600160a01b031633145b801561088d575080600301544210155b9392505050565b3233146108b35760405162461bcd60e51b81526004016101f490610dac565b6000805b82811015610a8b57368484838181106108d2576108d2610e33565b6020604091820293909301838101356000908152938490529220919250600190506005820154600160a01b900460ff16600381111561091357610913610bc9565b1461094c5760405162461bcd60e51b815260206004820152600960248201526862616420737461746560b81b60448201526064016101f4565b60058101546001600160a01b0316331461099a5760405162461bcd60e51b815260206004820152600f60248201526e189859081c185c9d1a58da5c185b9d608a1b60448201526064016101f4565b8160200135600283600001356040516020016109b891815260200190565b60408051601f19818403018152908290526109d291610dd6565b602060405180830381855afa1580156109ef573d6000803e3d6000fd5b5050506040513d601f19601f82011682018060405250810190610a129190610eb9565b14610a4c5760405162461bcd60e51b815260206004820152600a602482015269189859081cd958dc995d60b21b60448201526064016101f4565b60058101805460ff60a01b1916600160a11b179055813581556001810154610a749085610e88565b935050508080610a8390610ea0565b9150506108b7565b5060408051336024820152604480820184905282518083039091018152606490910182526020810180516001600160e01b031663a9059cbb60e01b17905290516000916060916001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001691610b0591610dd6565b6000604051808303816000865af19150503d8060008114610b42576040519150601f19603f3d011682016040523d82523d6000602084013e610b47565b606091505b509092509050818015610b72575080511580610b72575080806020019051810190610b729190610e11565b6107635760405162461bcd60e51b815260206004820152600f60248201526e1d1c985b9cd9995c8819985a5b1959608a1b60448201526064016101f4565b600060208284031215610bc257600080fd5b5035919050565b634e487b7160e01b600052602160045260246000fd5b60048110610bfd57634e487b7160e01b600052602160045260246000fd5b9052565b600060e08201905082518252602083015160208301526040830151604083015260608301516060830152608083015160018060a01b0380821660808501528060a08601511660a0850152505060c0830151610c5f60c0840182610bdf565b5092915050565b60008060208385031215610c7957600080fd5b823567ffffffffffffffff80821115610c9157600080fd5b818501915085601f830112610ca557600080fd5b813581811115610cb457600080fd5b8660208260071b8501011115610cc957600080fd5b60209290920196919550909350505050565b60008060408385031215610cee57600080fd5b50508035926020909101359150565b8781526020810187905260408101869052606081018590526001600160a01b038481166080830152831660a082015260e08101610d3d60c0830184610bdf565b98975050505050505050565b60008060208385031215610d5c57600080fd5b823567ffffffffffffffff80821115610d7457600080fd5b818501915085601f830112610d8857600080fd5b813581811115610d9757600080fd5b8660208260061b8501011115610cc957600080fd5b60208082526010908201526f39b2b73232b910109e9037b934b3b4b760811b604082015260600190565b6000825160005b81811015610df75760208186018101518583015201610ddd565b81811115610e06576000828501525b509190910192915050565b600060208284031215610e2357600080fd5b8151801515811461088d57600080fd5b634e487b7160e01b600052603260045260246000fd5b600060208284031215610e5b57600080fd5b81356001600160a01b038116811461088d57600080fd5b634e487b7160e01b600052601160045260246000fd5b60008219821115610e9b57610e9b610e72565b500190565b600060018201610eb257610eb2610e72565b5060010190565b600060208284031215610ecb57600080fd5b505191905056fea26469706673582212205ce0bdeea5c2627b54456a4cd32401114ec803a0ad67156f74d2652004edeff064736f6c634300080f0033" diff --git a/dex/networks/erc20/contracts/v0/contract.go b/dex/networks/erc20/contracts/v0/contract.go index e5287d1080..c520440542 100644 --- a/dex/networks/erc20/contracts/v0/contract.go +++ b/dex/networks/erc20/contracts/v0/contract.go @@ -32,7 +32,7 @@ var ( // ERC20SwapMetaData contains all meta data concerning the ERC20Swap contract. var ERC20SwapMetaData = &bind.MetaData{ ABI: "[{\"inputs\":[{\"internalType\":\"address\",\"name\":\"token\",\"type\":\"address\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"inputs\":[{\"components\":[{\"internalType\":\"uint256\",\"name\":\"refundTimestamp\",\"type\":\"uint256\"},{\"internalType\":\"bytes32\",\"name\":\"secretHash\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"participant\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"value\",\"type\":\"uint256\"}],\"internalType\":\"structERC20Swap.Initiation[]\",\"name\":\"initiations\",\"type\":\"tuple[]\"}],\"name\":\"initiate\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"secretHash\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"secret\",\"type\":\"bytes32\"}],\"name\":\"isRedeemable\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"secretHash\",\"type\":\"bytes32\"}],\"name\":\"isRefundable\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"secret\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"secretHash\",\"type\":\"bytes32\"}],\"internalType\":\"structERC20Swap.Redemption[]\",\"name\":\"redemptions\",\"type\":\"tuple[]\"}],\"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\":\"bytes32\",\"name\":\"secret\",\"type\":\"bytes32\"},{\"internalType\":\"uint256\",\"name\":\"value\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"initBlockNumber\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"refundBlockTimestamp\",\"type\":\"uint256\"},{\"internalType\":\"address\",\"name\":\"initiator\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"participant\",\"type\":\"address\"},{\"internalType\":\"enumERC20Swap.State\",\"name\":\"state\",\"type\":\"uint8\"}],\"internalType\":\"structERC20Swap.Swap\",\"name\":\"\",\"type\":\"tuple\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"name\":\"swaps\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"secret\",\"type\":\"bytes32\"},{\"internalType\":\"uint256\",\"name\":\"value\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"initBlockNumber\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"refundBlockTimestamp\",\"type\":\"uint256\"},{\"internalType\":\"address\",\"name\":\"initiator\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"participant\",\"type\":\"address\"},{\"internalType\":\"enumERC20Swap.State\",\"name\":\"state\",\"type\":\"uint8\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"token_address\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"}]", - Bin: "0x60a060405234801561001057600080fd5b50604051610fa7380380610fa783398101604081905261002f91610040565b6001600160a01b0316608052610070565b60006020828403121561005257600080fd5b81516001600160a01b038116811461006957600080fd5b9392505050565b608051610f0861009f6000396000818160d0015281816102b9015281816106890152610adb0152610f086000f3fe608060405234801561001057600080fd5b50600436106100885760003560e01c8063bfd2fd971161005b578063bfd2fd971461011d578063d0f761c014610140578063eb84e7f214610153578063f4fd17f9146101c257600080fd5b80637249fbb61461008d57806376467cbd146100a25780638c8e8fee146100cb578063a8793f941461010a575b600080fd5b6100a061009b366004610bb0565b6101d5565b005b6100b56100b0366004610bb0565b610394565b6040516100c29190610c01565b60405180910390f35b6100f27f000000000000000000000000000000000000000000000000000000000000000081565b6040516001600160a01b0390911681526020016100c2565b6100a0610118366004610c66565b61046f565b61013061012b366004610cdb565b61076a565b60405190151581526020016100c2565b61013061014e366004610bb0565b610834565b6101af610161366004610bb0565b60006020819052908152604090208054600182015460028301546003840154600485015460059095015493949293919290916001600160a01b0391821691811690600160a01b900460ff1687565b6040516100c29796959493929190610cfd565b6100a06101d0366004610d49565b610894565b3233146101fd5760405162461bcd60e51b81526004016101f490610dac565b60405180910390fd5b61020681610834565b6102435760405162461bcd60e51b815260206004820152600e60248201526d6e6f7420726566756e6461626c6560901b60448201526064016101f4565b60008181526020818152604080832060058101805460ff60a01b1916600360a01b17905560018101548251336024820152604480820192909252835180820390920182526064018352928301805163a9059cbb60e01b6001600160e01b0390911617905290519092916060916001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016916102e391610dd6565b6000604051808303816000865af19150503d8060008114610320576040519150601f19603f3d011682016040523d82523d6000602084013e610325565b606091505b5090925090508180156103505750805115806103505750808060200190518101906103509190610e11565b61038e5760405162461bcd60e51b815260206004820152600f60248201526e1d1c985b9cd9995c8819985a5b1959608a1b60448201526064016101f4565b50505050565b6103d16040805160e081018252600080825260208201819052918101829052606081018290526080810182905260a081018290529060c082015290565b60008281526020818152604091829020825160e08101845281548152600182015492810192909252600281015492820192909252600380830154606083015260048301546001600160a01b039081166080840152600584015490811660a084015291929160c0840191600160a01b90910460ff169081111561045557610455610bc9565b600381111561046657610466610bc9565b90525092915050565b32331461048e5760405162461bcd60e51b81526004016101f490610dac565b6000805b8281101561063357368484838181106104ad576104ad610e33565b90506080020190506000806000836020013581526020019081526020016000209050600082606001351161050b5760405162461bcd60e51b81526020600482015260056024820152640c081d985b60da1b60448201526064016101f4565b813561054d5760405162461bcd60e51b815260206004820152601160248201527003020726566756e6454696d657374616d7607c1b60448201526064016101f4565b60006005820154600160a01b900460ff16600381111561056f5761056f610bc9565b146105ae5760405162461bcd60e51b815260206004820152600f60248201526e0c8eae040e6cac6e4cae840d0c2e6d608b1b60448201526064016101f4565b436002820155813560038201556004810180546001600160a01b031916331790556105df6060830160408401610e49565b6005820180546060850135600185018190556001600160a01b03939093166001600160a81b031990911617600160a01b17905561061c9085610e88565b93505050808061062b90610ea0565b915050610492565b5060408051336024820152306044820152606480820184905282518083039091018152608490910182526020810180516001600160e01b03166323b872dd60e01b17905290516000916060916001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016916106b391610dd6565b6000604051808303816000865af19150503d80600081146106f0576040519150601f19603f3d011682016040523d82523d6000602084013e6106f5565b606091505b5090925090508180156107205750805115806107205750808060200190518101906107209190610e11565b6107635760405162461bcd60e51b81526020600482015260146024820152731d1c985b9cd9995c88199c9bdb4819985a5b195960621b60448201526064016101f4565b5050505050565b600082815260208190526040812060016005820154600160a01b900460ff16600381111561079a5761079a610bc9565b1480156107b3575060058101546001600160a01b031633145b801561082c5750836002846040516020016107d091815260200190565b60408051601f19818403018152908290526107ea91610dd6565b602060405180830381855afa158015610807573d6000803e3d6000fd5b5050506040513d601f19601f8201168201806040525081019061082a9190610eb9565b145b949350505050565b600081815260208190526040812060016005820154600160a01b900460ff16600381111561086457610864610bc9565b14801561087d575060048101546001600160a01b031633145b801561088d575080600301544210155b9392505050565b3233146108b35760405162461bcd60e51b81526004016101f490610dac565b6000805b82811015610a8b57368484838181106108d2576108d2610e33565b6020604091820293909301838101356000908152938490529220919250600190506005820154600160a01b900460ff16600381111561091357610913610bc9565b1461094c5760405162461bcd60e51b815260206004820152600960248201526862616420737461746560b81b60448201526064016101f4565b60058101546001600160a01b0316331461099a5760405162461bcd60e51b815260206004820152600f60248201526e189859081c185c9d1a58da5c185b9d608a1b60448201526064016101f4565b8160200135600283600001356040516020016109b891815260200190565b60408051601f19818403018152908290526109d291610dd6565b602060405180830381855afa1580156109ef573d6000803e3d6000fd5b5050506040513d601f19601f82011682018060405250810190610a129190610eb9565b14610a4c5760405162461bcd60e51b815260206004820152600a602482015269189859081cd958dc995d60b21b60448201526064016101f4565b60058101805460ff60a01b1916600160a11b179055813581556001810154610a749085610e88565b935050508080610a8390610ea0565b9150506108b7565b5060408051336024820152604480820184905282518083039091018152606490910182526020810180516001600160e01b031663a9059cbb60e01b17905290516000916060916001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001691610b0591610dd6565b6000604051808303816000865af19150503d8060008114610b42576040519150601f19603f3d011682016040523d82523d6000602084013e610b47565b606091505b509092509050818015610b72575080511580610b72575080806020019051810190610b729190610e11565b6107635760405162461bcd60e51b815260206004820152600f60248201526e1d1c985b9cd9995c8819985a5b1959608a1b60448201526064016101f4565b600060208284031215610bc257600080fd5b5035919050565b634e487b7160e01b600052602160045260246000fd5b60048110610bfd57634e487b7160e01b600052602160045260246000fd5b9052565b600060e08201905082518252602083015160208301526040830151604083015260608301516060830152608083015160018060a01b0380821660808501528060a08601511660a0850152505060c0830151610c5f60c0840182610bdf565b5092915050565b60008060208385031215610c7957600080fd5b823567ffffffffffffffff80821115610c9157600080fd5b818501915085601f830112610ca557600080fd5b813581811115610cb457600080fd5b8660208260071b8501011115610cc957600080fd5b60209290920196919550909350505050565b60008060408385031215610cee57600080fd5b50508035926020909101359150565b8781526020810187905260408101869052606081018590526001600160a01b038481166080830152831660a082015260e08101610d3d60c0830184610bdf565b98975050505050505050565b60008060208385031215610d5c57600080fd5b823567ffffffffffffffff80821115610d7457600080fd5b818501915085601f830112610d8857600080fd5b813581811115610d9757600080fd5b8660208260061b8501011115610cc957600080fd5b60208082526010908201526f39b2b73232b910109e9037b934b3b4b760811b604082015260600190565b6000825160005b81811015610df75760208186018101518583015201610ddd565b81811115610e06576000828501525b509190910192915050565b600060208284031215610e2357600080fd5b8151801515811461088d57600080fd5b634e487b7160e01b600052603260045260246000fd5b600060208284031215610e5b57600080fd5b81356001600160a01b038116811461088d57600080fd5b634e487b7160e01b600052601160045260246000fd5b60008219821115610e9b57610e9b610e72565b500190565b600060018201610eb257610eb2610e72565b5060010190565b600060208284031215610ecb57600080fd5b505191905056fea26469706673582212203e5af33d9672cc61834e91e1c391f408502e89feacb59762150c3e8ad4b8eb8764736f6c634300080f0033", + Bin: "0x60a060405234801561001057600080fd5b50604051610fa7380380610fa783398101604081905261002f91610040565b6001600160a01b0316608052610070565b60006020828403121561005257600080fd5b81516001600160a01b038116811461006957600080fd5b9392505050565b608051610f0861009f6000396000818160d0015281816102b9015281816106890152610adb0152610f086000f3fe608060405234801561001057600080fd5b50600436106100885760003560e01c8063bfd2fd971161005b578063bfd2fd971461011d578063d0f761c014610140578063eb84e7f214610153578063f4fd17f9146101c257600080fd5b80637249fbb61461008d57806376467cbd146100a25780638c8e8fee146100cb578063a8793f941461010a575b600080fd5b6100a061009b366004610bb0565b6101d5565b005b6100b56100b0366004610bb0565b610394565b6040516100c29190610c01565b60405180910390f35b6100f27f000000000000000000000000000000000000000000000000000000000000000081565b6040516001600160a01b0390911681526020016100c2565b6100a0610118366004610c66565b61046f565b61013061012b366004610cdb565b61076a565b60405190151581526020016100c2565b61013061014e366004610bb0565b610834565b6101af610161366004610bb0565b60006020819052908152604090208054600182015460028301546003840154600485015460059095015493949293919290916001600160a01b0391821691811690600160a01b900460ff1687565b6040516100c29796959493929190610cfd565b6100a06101d0366004610d49565b610894565b3233146101fd5760405162461bcd60e51b81526004016101f490610dac565b60405180910390fd5b61020681610834565b6102435760405162461bcd60e51b815260206004820152600e60248201526d6e6f7420726566756e6461626c6560901b60448201526064016101f4565b60008181526020818152604080832060058101805460ff60a01b1916600360a01b17905560018101548251336024820152604480820192909252835180820390920182526064018352928301805163a9059cbb60e01b6001600160e01b0390911617905290519092916060916001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016916102e391610dd6565b6000604051808303816000865af19150503d8060008114610320576040519150601f19603f3d011682016040523d82523d6000602084013e610325565b606091505b5090925090508180156103505750805115806103505750808060200190518101906103509190610e11565b61038e5760405162461bcd60e51b815260206004820152600f60248201526e1d1c985b9cd9995c8819985a5b1959608a1b60448201526064016101f4565b50505050565b6103d16040805160e081018252600080825260208201819052918101829052606081018290526080810182905260a081018290529060c082015290565b60008281526020818152604091829020825160e08101845281548152600182015492810192909252600281015492820192909252600380830154606083015260048301546001600160a01b039081166080840152600584015490811660a084015291929160c0840191600160a01b90910460ff169081111561045557610455610bc9565b600381111561046657610466610bc9565b90525092915050565b32331461048e5760405162461bcd60e51b81526004016101f490610dac565b6000805b8281101561063357368484838181106104ad576104ad610e33565b90506080020190506000806000836020013581526020019081526020016000209050600082606001351161050b5760405162461bcd60e51b81526020600482015260056024820152640c081d985b60da1b60448201526064016101f4565b813561054d5760405162461bcd60e51b815260206004820152601160248201527003020726566756e6454696d657374616d7607c1b60448201526064016101f4565b60006005820154600160a01b900460ff16600381111561056f5761056f610bc9565b146105ae5760405162461bcd60e51b815260206004820152600f60248201526e0c8eae040e6cac6e4cae840d0c2e6d608b1b60448201526064016101f4565b436002820155813560038201556004810180546001600160a01b031916331790556105df6060830160408401610e49565b6005820180546060850135600185018190556001600160a01b03939093166001600160a81b031990911617600160a01b17905561061c9085610e88565b93505050808061062b90610ea0565b915050610492565b5060408051336024820152306044820152606480820184905282518083039091018152608490910182526020810180516001600160e01b03166323b872dd60e01b17905290516000916060916001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016916106b391610dd6565b6000604051808303816000865af19150503d80600081146106f0576040519150601f19603f3d011682016040523d82523d6000602084013e6106f5565b606091505b5090925090508180156107205750805115806107205750808060200190518101906107209190610e11565b6107635760405162461bcd60e51b81526020600482015260146024820152731d1c985b9cd9995c88199c9bdb4819985a5b195960621b60448201526064016101f4565b5050505050565b600082815260208190526040812060016005820154600160a01b900460ff16600381111561079a5761079a610bc9565b1480156107b3575060058101546001600160a01b031633145b801561082c5750836002846040516020016107d091815260200190565b60408051601f19818403018152908290526107ea91610dd6565b602060405180830381855afa158015610807573d6000803e3d6000fd5b5050506040513d601f19601f8201168201806040525081019061082a9190610eb9565b145b949350505050565b600081815260208190526040812060016005820154600160a01b900460ff16600381111561086457610864610bc9565b14801561087d575060048101546001600160a01b031633145b801561088d575080600301544210155b9392505050565b3233146108b35760405162461bcd60e51b81526004016101f490610dac565b6000805b82811015610a8b57368484838181106108d2576108d2610e33565b6020604091820293909301838101356000908152938490529220919250600190506005820154600160a01b900460ff16600381111561091357610913610bc9565b1461094c5760405162461bcd60e51b815260206004820152600960248201526862616420737461746560b81b60448201526064016101f4565b60058101546001600160a01b0316331461099a5760405162461bcd60e51b815260206004820152600f60248201526e189859081c185c9d1a58da5c185b9d608a1b60448201526064016101f4565b8160200135600283600001356040516020016109b891815260200190565b60408051601f19818403018152908290526109d291610dd6565b602060405180830381855afa1580156109ef573d6000803e3d6000fd5b5050506040513d601f19601f82011682018060405250810190610a129190610eb9565b14610a4c5760405162461bcd60e51b815260206004820152600a602482015269189859081cd958dc995d60b21b60448201526064016101f4565b60058101805460ff60a01b1916600160a11b179055813581556001810154610a749085610e88565b935050508080610a8390610ea0565b9150506108b7565b5060408051336024820152604480820184905282518083039091018152606490910182526020810180516001600160e01b031663a9059cbb60e01b17905290516000916060916001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001691610b0591610dd6565b6000604051808303816000865af19150503d8060008114610b42576040519150601f19603f3d011682016040523d82523d6000602084013e610b47565b606091505b509092509050818015610b72575080511580610b72575080806020019051810190610b729190610e11565b6107635760405162461bcd60e51b815260206004820152600f60248201526e1d1c985b9cd9995c8819985a5b1959608a1b60448201526064016101f4565b600060208284031215610bc257600080fd5b5035919050565b634e487b7160e01b600052602160045260246000fd5b60048110610bfd57634e487b7160e01b600052602160045260246000fd5b9052565b600060e08201905082518252602083015160208301526040830151604083015260608301516060830152608083015160018060a01b0380821660808501528060a08601511660a0850152505060c0830151610c5f60c0840182610bdf565b5092915050565b60008060208385031215610c7957600080fd5b823567ffffffffffffffff80821115610c9157600080fd5b818501915085601f830112610ca557600080fd5b813581811115610cb457600080fd5b8660208260071b8501011115610cc957600080fd5b60209290920196919550909350505050565b60008060408385031215610cee57600080fd5b50508035926020909101359150565b8781526020810187905260408101869052606081018590526001600160a01b038481166080830152831660a082015260e08101610d3d60c0830184610bdf565b98975050505050505050565b60008060208385031215610d5c57600080fd5b823567ffffffffffffffff80821115610d7457600080fd5b818501915085601f830112610d8857600080fd5b813581811115610d9757600080fd5b8660208260061b8501011115610cc957600080fd5b60208082526010908201526f39b2b73232b910109e9037b934b3b4b760811b604082015260600190565b6000825160005b81811015610df75760208186018101518583015201610ddd565b81811115610e06576000828501525b509190910192915050565b600060208284031215610e2357600080fd5b8151801515811461088d57600080fd5b634e487b7160e01b600052603260045260246000fd5b600060208284031215610e5b57600080fd5b81356001600160a01b038116811461088d57600080fd5b634e487b7160e01b600052601160045260246000fd5b60008219821115610e9b57610e9b610e72565b500190565b600060018201610eb257610eb2610e72565b5060010190565b600060208284031215610ecb57600080fd5b505191905056fea26469706673582212205ce0bdeea5c2627b54456a4cd32401114ec803a0ad67156f74d2652004edeff064736f6c634300080f0033", } // ERC20SwapABI is the input ABI used to generate the binding from. diff --git a/dex/networks/erc20/contracts/v1/BinRuntimeV1.go b/dex/networks/erc20/contracts/v1/BinRuntimeV1.go new file mode 100644 index 0000000000..790689b5be --- /dev/null +++ b/dex/networks/erc20/contracts/v1/BinRuntimeV1.go @@ -0,0 +1,6 @@ +// Code generated - DO NOT EDIT. +// This file is a generated binding and any manual changes will be lost. + +package v1 + +const ERC20SwapRuntimeBin = "6080604052600436106100865760003560e01c80637802689d116100595780637802689d146101265780638c8e8fee14610146578063d2544c0614610192578063eb84e7f2146101b2578063ed7cbed7146101ed57600080fd5b8063428b16e11461008b57806361a16e33146100ad57806364a97bff146100e357806377d7e031146100f6575b600080fd5b34801561009757600080fd5b506100ab6100a6366004610dea565b61020d565b005b3480156100b957600080fd5b506100cd6100c8366004610e5f565b610533565b6040516100da9190610e8d565b60405180910390f35b6100ab6100f1366004610ed0565b6105ef565b34801561010257600080fd5b50610116610111366004610f33565b610918565b60405190151581526020016100da565b34801561013257600080fd5b50610116610141366004610e5f565b610992565b34801561015257600080fd5b5061017a7f000000000000000000000000000000000000000000000000000000000000000081565b6040516001600160a01b0390911681526020016100da565b34801561019e57600080fd5b506100ab6101ad366004610e5f565b6109c5565b3480156101be57600080fd5b506101df6101cd366004610f55565b60006020819052908152604090205481565b6040519081526020016100da565b3480156101f957600080fd5b506101df610208366004610e5f565b610cc5565b3233146102355760405162461bcd60e51b815260040161022c90610f6e565b60405180910390fd5b6000805b82811015610407573684848381811061025457610254610f98565b60c0029190910191503390506102706080830160608401610fae565b6001600160a01b0316146102b35760405162461bcd60e51b815260206004820152600a6024820152691b9bdd08185d5d1a195960b21b604482015260640161022c565b600080806102c084610dbe565b9250925092506000811180156102d557504381105b6103115760405162461bcd60e51b815260206004820152600d60248201526c0756e66696c6c6564207377617609c1b604482015260640161022c565b61031c828535610918565b1561035c5760405162461bcd60e51b815260206004820152601060248201526f185b1c9958591e481c995919595b595960821b604482015260640161022c565b61036b60a08501358535610918565b6103a85760405162461bcd60e51b815260206004820152600e60248201526d1a5b9d985b1a59081cd958dc995d60921b604482015260640161022c565b600083815260208190526040902060a0850180359091556103cc9060808601610fde565b6103da90633b9aca0061101e565b6103ee9067ffffffffffffffff168761104e565b95505050505080806103ff90611066565b915050610239565b5060408051336024820152604480820184905282518083039091018152606490910182526020810180516001600160e01b031663a9059cbb60e01b17905290516000916060916001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016916104819161107f565b6000604051808303816000865af19150503d80600081146104be576040519150601f19603f3d011682016040523d82523d6000602084013e6104c3565b606091505b5090925090508180156104ee5750805115806104ee5750808060200190518101906104ee91906110ba565b61052c5760405162461bcd60e51b815260206004820152600f60248201526e1d1c985b9cd9995c8819985a5b1959608a1b604482015260640161022c565b5050505050565b60408051606081018252600080825260208201819052918101829052908061055a84610dbe565b92509250506105846040805160608101909152806000815260006020820181905260409091015290565b816000036105ab578060005b908160038111156105a3576105a3610e77565b9052506105e7565b600183016105bb57806003610590565b6105c6838635610918565b156105db5760028152602081018390526105e7565b60018152604081018290525b949350505050565b32331461060e5760405162461bcd60e51b815260040161022c90610f6e565b6000805b828110156107e8573684848381811061062d5761062d610f98565b905060a002019050600081608001602081019061064a9190610fde565b67ffffffffffffffff16116106895760405162461bcd60e51b81526020600482015260056024820152640c081d985b60da1b604482015260640161022c565b600061069b6060830160408401610fde565b67ffffffffffffffff16116106e65760405162461bcd60e51b815260206004820152601160248201527003020726566756e6454696d657374616d7607c1b604482015260640161022c565b60006106f182610cc5565b60008181526020819052604090205490915080156107425760405162461bcd60e51b815260206004820152600e60248201526d73776170206e6f7420656d70747960901b604482015260640161022c565b504361074f818435610918565b1561078d5760405162461bcd60e51b815260206004820152600e60248201526d3430b9b41031b7b63634b9b4b7b760911b604482015260640161022c565b60008281526020819052604090208190556107ae60a0840160808501610fde565b6107bc90633b9aca0061101e565b6107d09067ffffffffffffffff168661104e565b945050505080806107e090611066565b915050610612565b5060408051336024820152306044820152606480820184905282518083039091018152608490910182526020810180516001600160e01b03166323b872dd60e01b17905290516000916060916001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016916108689161107f565b6000604051808303816000865af19150503d80600081146108a5576040519150601f19603f3d011682016040523d82523d6000602084013e6108aa565b606091505b5090925090508180156108d55750805115806108d55750808060200190518101906108d591906110ba565b61052c5760405162461bcd60e51b81526020600482015260146024820152731d1c985b9cd9995c88199c9bdb4819985a5b195960621b604482015260640161022c565b60008160028460405160200161093091815260200190565b60408051601f198184030181529082905261094a9161107f565b602060405180830381855afa158015610967573d6000803e3d6000fd5b5050506040513d601f19601f8201168201806040525081019061098a91906110dc565b149392505050565b60008060006109a084610dbe565b9250925050806000141580156105e757506109bc828535610918565b15949350505050565b3233146109e45760405162461bcd60e51b815260040161022c90610f6e565b6109f46060820160408301610fde565b67ffffffffffffffff16421015610a445760405162461bcd60e51b81526020600482015260146024820152731b1bd8dadd1a5b59481b9bdd08195e1c1a5c995960621b604482015260640161022c565b6000806000610a5284610dbe565b925092509250600081118015610a685750438111155b610aa65760405162461bcd60e51b815260206004820152600f60248201526e73776170206e6f742061637469766560881b604482015260640161022c565b610ab1828535610918565b15610af65760405162461bcd60e51b81526020600482015260156024820152741cddd85c08185b1c9958591e481c995919595b5959605a1b604482015260640161022c565b60018201610b3e5760405162461bcd60e51b81526020600482015260156024820152741cddd85c08185b1c9958591e481c99599d5b991959605a1b604482015260640161022c565b6000838152602081905260408120600019905560606001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000167fa9059cbb2ab09eb219583f4a59a5d0623ade346d962bcd4e46b11da047c9049b33610baf60a08a0160808b01610fde565b6040516001600160a01b03909216602483015267ffffffffffffffff16604482015260640160408051601f198184030181529181526020820180516001600160e01b03166001600160e01b0319909416939093179092529051610c12919061107f565b6000604051808303816000865af19150503d8060008114610c4f576040519150601f19603f3d011682016040523d82523d6000602084013e610c54565b606091505b509092509050818015610c7f575080511580610c7f575080806020019051810190610c7f91906110ba565b610cbd5760405162461bcd60e51b815260206004820152600f60248201526e1d1c985b9cd9995c8819985a5b1959608a1b604482015260640161022c565b505050505050565b600060028235610cdb6040850160208601610fae565b60601b846060016020810190610cf19190610fae565b60601b610d0460a0870160808801610fde565b60c01b610d176060880160408901610fde565b6040805160208101969096526bffffffffffffffffffffffff19948516908601529190921660548401526001600160c01b0319918216606884015260c01b16607082015260780160408051601f1981840301815290829052610d789161107f565b602060405180830381855afa158015610d95573d6000803e3d6000fd5b5050506040513d601f19601f82011682018060405250810190610db891906110dc565b92915050565b600080600080610dcd85610cc5565b600081815260208190526040902054909690955085945092505050565b60008060208385031215610dfd57600080fd5b823567ffffffffffffffff80821115610e1557600080fd5b818501915085601f830112610e2957600080fd5b813581811115610e3857600080fd5b86602060c083028501011115610e4d57600080fd5b60209290920196919550909350505050565b600060a08284031215610e7157600080fd5b50919050565b634e487b7160e01b600052602160045260246000fd5b8151606082019060048110610eb257634e487b7160e01b600052602160045260246000fd5b80835250602083015160208301526040830151604083015292915050565b60008060208385031215610ee357600080fd5b823567ffffffffffffffff80821115610efb57600080fd5b818501915085601f830112610f0f57600080fd5b813581811115610f1e57600080fd5b86602060a083028501011115610e4d57600080fd5b60008060408385031215610f4657600080fd5b50508035926020909101359150565b600060208284031215610f6757600080fd5b5035919050565b60208082526010908201526f39b2b73232b910109e9037b934b3b4b760811b604082015260600190565b634e487b7160e01b600052603260045260246000fd5b600060208284031215610fc057600080fd5b81356001600160a01b0381168114610fd757600080fd5b9392505050565b600060208284031215610ff057600080fd5b813567ffffffffffffffff81168114610fd757600080fd5b634e487b7160e01b600052601160045260246000fd5b600067ffffffffffffffff8083168185168183048111821515161561104557611045611008565b02949350505050565b6000821982111561106157611061611008565b500190565b60006001820161107857611078611008565b5060010190565b6000825160005b818110156110a05760208186018101518583015201611086565b818111156110af576000828501525b509190910192915050565b6000602082840312156110cc57600080fd5b81518015158114610fd757600080fd5b6000602082840312156110ee57600080fd5b505191905056fea2646970667358221220319d89b87a0d5782925310133ed5d12d2ff562b0786611308396804fcad176fc64736f6c634300080f0033" diff --git a/dex/networks/erc20/contracts/v1/contract.go b/dex/networks/erc20/contracts/v1/contract.go new file mode 100644 index 0000000000..7afaf0f4f0 --- /dev/null +++ b/dex/networks/erc20/contracts/v1/contract.go @@ -0,0 +1,452 @@ +// Code generated - DO NOT EDIT. +// This file is a generated binding and any manual changes will be lost. + +package v1 + +import ( + "errors" + "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" + ethv1 "decred.org/dcrdex/dex/networks/eth/contracts/v1" +) + +// Reference imports to suppress errors if they are not otherwise used. +var ( + _ = errors.New + _ = big.NewInt + _ = strings.NewReader + _ = ethereum.NotFound + _ = bind.Bind + _ = common.Big1 + _ = types.BloomLookup + _ = event.NewSubscription +) + +// ERC20SwapMetaData contains all meta data concerning the ERC20Swap contract. +var ERC20SwapMetaData = &bind.MetaData{ + ABI: "[{\"inputs\":[{\"internalType\":\"address\",\"name\":\"token\",\"type\":\"address\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"inputs\":[{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"secretHash\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"initiator\",\"type\":\"address\"},{\"internalType\":\"uint64\",\"name\":\"refundTimestamp\",\"type\":\"uint64\"},{\"internalType\":\"address\",\"name\":\"participant\",\"type\":\"address\"},{\"internalType\":\"uint64\",\"name\":\"value\",\"type\":\"uint64\"}],\"internalType\":\"structERC20Swap.Vector\",\"name\":\"v\",\"type\":\"tuple\"}],\"name\":\"contractKey\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"secretHash\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"initiator\",\"type\":\"address\"},{\"internalType\":\"uint64\",\"name\":\"refundTimestamp\",\"type\":\"uint64\"},{\"internalType\":\"address\",\"name\":\"participant\",\"type\":\"address\"},{\"internalType\":\"uint64\",\"name\":\"value\",\"type\":\"uint64\"}],\"internalType\":\"structERC20Swap.Vector[]\",\"name\":\"contracts\",\"type\":\"tuple[]\"}],\"name\":\"initiate\",\"outputs\":[],\"stateMutability\":\"payable\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"secretHash\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"initiator\",\"type\":\"address\"},{\"internalType\":\"uint64\",\"name\":\"refundTimestamp\",\"type\":\"uint64\"},{\"internalType\":\"address\",\"name\":\"participant\",\"type\":\"address\"},{\"internalType\":\"uint64\",\"name\":\"value\",\"type\":\"uint64\"}],\"internalType\":\"structERC20Swap.Vector\",\"name\":\"v\",\"type\":\"tuple\"}],\"name\":\"isRedeemable\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"secretHash\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"initiator\",\"type\":\"address\"},{\"internalType\":\"uint64\",\"name\":\"refundTimestamp\",\"type\":\"uint64\"},{\"internalType\":\"address\",\"name\":\"participant\",\"type\":\"address\"},{\"internalType\":\"uint64\",\"name\":\"value\",\"type\":\"uint64\"}],\"internalType\":\"structERC20Swap.Vector\",\"name\":\"v\",\"type\":\"tuple\"},{\"internalType\":\"bytes32\",\"name\":\"secret\",\"type\":\"bytes32\"}],\"internalType\":\"structERC20Swap.Redemption[]\",\"name\":\"redemptions\",\"type\":\"tuple[]\"}],\"name\":\"redeem\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"secretHash\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"initiator\",\"type\":\"address\"},{\"internalType\":\"uint64\",\"name\":\"refundTimestamp\",\"type\":\"uint64\"},{\"internalType\":\"address\",\"name\":\"participant\",\"type\":\"address\"},{\"internalType\":\"uint64\",\"name\":\"value\",\"type\":\"uint64\"}],\"internalType\":\"structERC20Swap.Vector\",\"name\":\"v\",\"type\":\"tuple\"}],\"name\":\"refund\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"secret\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"secretHash\",\"type\":\"bytes32\"}],\"name\":\"secretValidates\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"secretHash\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"initiator\",\"type\":\"address\"},{\"internalType\":\"uint64\",\"name\":\"refundTimestamp\",\"type\":\"uint64\"},{\"internalType\":\"address\",\"name\":\"participant\",\"type\":\"address\"},{\"internalType\":\"uint64\",\"name\":\"value\",\"type\":\"uint64\"}],\"internalType\":\"structERC20Swap.Vector\",\"name\":\"v\",\"type\":\"tuple\"}],\"name\":\"status\",\"outputs\":[{\"components\":[{\"internalType\":\"enumERC20Swap.Step\",\"name\":\"step\",\"type\":\"uint8\"},{\"internalType\":\"bytes32\",\"name\":\"secret\",\"type\":\"bytes32\"},{\"internalType\":\"uint256\",\"name\":\"blockNumber\",\"type\":\"uint256\"}],\"internalType\":\"structERC20Swap.Status\",\"name\":\"\",\"type\":\"tuple\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"name\":\"swaps\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"token_address\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"}]", + Bin: "0x60a060405234801561001057600080fd5b506040516111cb3803806111cb83398101604081905261002f91610040565b6001600160a01b0316608052610070565b60006020828403121561005257600080fd5b81516001600160a01b038116811461006957600080fd5b9392505050565b60805161112b6100a060003960008181610158015281816104570152818161083e0152610b5d015261112b6000f3fe6080604052600436106100865760003560e01c80637802689d116100595780637802689d146101265780638c8e8fee14610146578063d2544c0614610192578063eb84e7f2146101b2578063ed7cbed7146101ed57600080fd5b8063428b16e11461008b57806361a16e33146100ad57806364a97bff146100e357806377d7e031146100f6575b600080fd5b34801561009757600080fd5b506100ab6100a6366004610dea565b61020d565b005b3480156100b957600080fd5b506100cd6100c8366004610e5f565b610533565b6040516100da9190610e8d565b60405180910390f35b6100ab6100f1366004610ed0565b6105ef565b34801561010257600080fd5b50610116610111366004610f33565b610918565b60405190151581526020016100da565b34801561013257600080fd5b50610116610141366004610e5f565b610992565b34801561015257600080fd5b5061017a7f000000000000000000000000000000000000000000000000000000000000000081565b6040516001600160a01b0390911681526020016100da565b34801561019e57600080fd5b506100ab6101ad366004610e5f565b6109c5565b3480156101be57600080fd5b506101df6101cd366004610f55565b60006020819052908152604090205481565b6040519081526020016100da565b3480156101f957600080fd5b506101df610208366004610e5f565b610cc5565b3233146102355760405162461bcd60e51b815260040161022c90610f6e565b60405180910390fd5b6000805b82811015610407573684848381811061025457610254610f98565b60c0029190910191503390506102706080830160608401610fae565b6001600160a01b0316146102b35760405162461bcd60e51b815260206004820152600a6024820152691b9bdd08185d5d1a195960b21b604482015260640161022c565b600080806102c084610dbe565b9250925092506000811180156102d557504381105b6103115760405162461bcd60e51b815260206004820152600d60248201526c0756e66696c6c6564207377617609c1b604482015260640161022c565b61031c828535610918565b1561035c5760405162461bcd60e51b815260206004820152601060248201526f185b1c9958591e481c995919595b595960821b604482015260640161022c565b61036b60a08501358535610918565b6103a85760405162461bcd60e51b815260206004820152600e60248201526d1a5b9d985b1a59081cd958dc995d60921b604482015260640161022c565b600083815260208190526040902060a0850180359091556103cc9060808601610fde565b6103da90633b9aca0061101e565b6103ee9067ffffffffffffffff168761104e565b95505050505080806103ff90611066565b915050610239565b5060408051336024820152604480820184905282518083039091018152606490910182526020810180516001600160e01b031663a9059cbb60e01b17905290516000916060916001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016916104819161107f565b6000604051808303816000865af19150503d80600081146104be576040519150601f19603f3d011682016040523d82523d6000602084013e6104c3565b606091505b5090925090508180156104ee5750805115806104ee5750808060200190518101906104ee91906110ba565b61052c5760405162461bcd60e51b815260206004820152600f60248201526e1d1c985b9cd9995c8819985a5b1959608a1b604482015260640161022c565b5050505050565b60408051606081018252600080825260208201819052918101829052908061055a84610dbe565b92509250506105846040805160608101909152806000815260006020820181905260409091015290565b816000036105ab578060005b908160038111156105a3576105a3610e77565b9052506105e7565b600183016105bb57806003610590565b6105c6838635610918565b156105db5760028152602081018390526105e7565b60018152604081018290525b949350505050565b32331461060e5760405162461bcd60e51b815260040161022c90610f6e565b6000805b828110156107e8573684848381811061062d5761062d610f98565b905060a002019050600081608001602081019061064a9190610fde565b67ffffffffffffffff16116106895760405162461bcd60e51b81526020600482015260056024820152640c081d985b60da1b604482015260640161022c565b600061069b6060830160408401610fde565b67ffffffffffffffff16116106e65760405162461bcd60e51b815260206004820152601160248201527003020726566756e6454696d657374616d7607c1b604482015260640161022c565b60006106f182610cc5565b60008181526020819052604090205490915080156107425760405162461bcd60e51b815260206004820152600e60248201526d73776170206e6f7420656d70747960901b604482015260640161022c565b504361074f818435610918565b1561078d5760405162461bcd60e51b815260206004820152600e60248201526d3430b9b41031b7b63634b9b4b7b760911b604482015260640161022c565b60008281526020819052604090208190556107ae60a0840160808501610fde565b6107bc90633b9aca0061101e565b6107d09067ffffffffffffffff168661104e565b945050505080806107e090611066565b915050610612565b5060408051336024820152306044820152606480820184905282518083039091018152608490910182526020810180516001600160e01b03166323b872dd60e01b17905290516000916060916001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016916108689161107f565b6000604051808303816000865af19150503d80600081146108a5576040519150601f19603f3d011682016040523d82523d6000602084013e6108aa565b606091505b5090925090508180156108d55750805115806108d55750808060200190518101906108d591906110ba565b61052c5760405162461bcd60e51b81526020600482015260146024820152731d1c985b9cd9995c88199c9bdb4819985a5b195960621b604482015260640161022c565b60008160028460405160200161093091815260200190565b60408051601f198184030181529082905261094a9161107f565b602060405180830381855afa158015610967573d6000803e3d6000fd5b5050506040513d601f19601f8201168201806040525081019061098a91906110dc565b149392505050565b60008060006109a084610dbe565b9250925050806000141580156105e757506109bc828535610918565b15949350505050565b3233146109e45760405162461bcd60e51b815260040161022c90610f6e565b6109f46060820160408301610fde565b67ffffffffffffffff16421015610a445760405162461bcd60e51b81526020600482015260146024820152731b1bd8dadd1a5b59481b9bdd08195e1c1a5c995960621b604482015260640161022c565b6000806000610a5284610dbe565b925092509250600081118015610a685750438111155b610aa65760405162461bcd60e51b815260206004820152600f60248201526e73776170206e6f742061637469766560881b604482015260640161022c565b610ab1828535610918565b15610af65760405162461bcd60e51b81526020600482015260156024820152741cddd85c08185b1c9958591e481c995919595b5959605a1b604482015260640161022c565b60018201610b3e5760405162461bcd60e51b81526020600482015260156024820152741cddd85c08185b1c9958591e481c99599d5b991959605a1b604482015260640161022c565b6000838152602081905260408120600019905560606001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000167fa9059cbb2ab09eb219583f4a59a5d0623ade346d962bcd4e46b11da047c9049b33610baf60a08a0160808b01610fde565b6040516001600160a01b03909216602483015267ffffffffffffffff16604482015260640160408051601f198184030181529181526020820180516001600160e01b03166001600160e01b0319909416939093179092529051610c12919061107f565b6000604051808303816000865af19150503d8060008114610c4f576040519150601f19603f3d011682016040523d82523d6000602084013e610c54565b606091505b509092509050818015610c7f575080511580610c7f575080806020019051810190610c7f91906110ba565b610cbd5760405162461bcd60e51b815260206004820152600f60248201526e1d1c985b9cd9995c8819985a5b1959608a1b604482015260640161022c565b505050505050565b600060028235610cdb6040850160208601610fae565b60601b846060016020810190610cf19190610fae565b60601b610d0460a0870160808801610fde565b60c01b610d176060880160408901610fde565b6040805160208101969096526bffffffffffffffffffffffff19948516908601529190921660548401526001600160c01b0319918216606884015260c01b16607082015260780160408051601f1981840301815290829052610d789161107f565b602060405180830381855afa158015610d95573d6000803e3d6000fd5b5050506040513d601f19601f82011682018060405250810190610db891906110dc565b92915050565b600080600080610dcd85610cc5565b600081815260208190526040902054909690955085945092505050565b60008060208385031215610dfd57600080fd5b823567ffffffffffffffff80821115610e1557600080fd5b818501915085601f830112610e2957600080fd5b813581811115610e3857600080fd5b86602060c083028501011115610e4d57600080fd5b60209290920196919550909350505050565b600060a08284031215610e7157600080fd5b50919050565b634e487b7160e01b600052602160045260246000fd5b8151606082019060048110610eb257634e487b7160e01b600052602160045260246000fd5b80835250602083015160208301526040830151604083015292915050565b60008060208385031215610ee357600080fd5b823567ffffffffffffffff80821115610efb57600080fd5b818501915085601f830112610f0f57600080fd5b813581811115610f1e57600080fd5b86602060a083028501011115610e4d57600080fd5b60008060408385031215610f4657600080fd5b50508035926020909101359150565b600060208284031215610f6757600080fd5b5035919050565b60208082526010908201526f39b2b73232b910109e9037b934b3b4b760811b604082015260600190565b634e487b7160e01b600052603260045260246000fd5b600060208284031215610fc057600080fd5b81356001600160a01b0381168114610fd757600080fd5b9392505050565b600060208284031215610ff057600080fd5b813567ffffffffffffffff81168114610fd757600080fd5b634e487b7160e01b600052601160045260246000fd5b600067ffffffffffffffff8083168185168183048111821515161561104557611045611008565b02949350505050565b6000821982111561106157611061611008565b500190565b60006001820161107857611078611008565b5060010190565b6000825160005b818110156110a05760208186018101518583015201611086565b818111156110af576000828501525b509190910192915050565b6000602082840312156110cc57600080fd5b81518015158114610fd757600080fd5b6000602082840312156110ee57600080fd5b505191905056fea2646970667358221220319d89b87a0d5782925310133ed5d12d2ff562b0786611308396804fcad176fc64736f6c634300080f0033", +} + +// ERC20SwapABI is the input ABI used to generate the binding from. +// Deprecated: Use ERC20SwapMetaData.ABI instead. +var ERC20SwapABI = ERC20SwapMetaData.ABI + +// ERC20SwapBin is the compiled bytecode used for deploying new contracts. +// Deprecated: Use ERC20SwapMetaData.Bin instead. +var ERC20SwapBin = ERC20SwapMetaData.Bin + +// DeployERC20Swap deploys a new Ethereum contract, binding an instance of ERC20Swap to it. +func DeployERC20Swap(auth *bind.TransactOpts, backend bind.ContractBackend, token common.Address) (common.Address, *types.Transaction, *ERC20Swap, error) { + parsed, err := ERC20SwapMetaData.GetAbi() + if err != nil { + return common.Address{}, nil, nil, err + } + if parsed == nil { + return common.Address{}, nil, nil, errors.New("GetABI returned nil") + } + + address, tx, contract, err := bind.DeployContract(auth, *parsed, common.FromHex(ERC20SwapBin), backend, token) + if err != nil { + return common.Address{}, nil, nil, err + } + return address, tx, &ERC20Swap{ERC20SwapCaller: ERC20SwapCaller{contract: contract}, ERC20SwapTransactor: ERC20SwapTransactor{contract: contract}, ERC20SwapFilterer: ERC20SwapFilterer{contract: contract}}, nil +} + +// ERC20Swap is an auto generated Go binding around an Ethereum contract. +type ERC20Swap struct { + ERC20SwapCaller // Read-only binding to the contract + ERC20SwapTransactor // Write-only binding to the contract + ERC20SwapFilterer // Log filterer for contract events +} + +// ERC20SwapCaller is an auto generated read-only Go binding around an Ethereum contract. +type ERC20SwapCaller struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// ERC20SwapTransactor is an auto generated write-only Go binding around an Ethereum contract. +type ERC20SwapTransactor struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// ERC20SwapFilterer is an auto generated log filtering Go binding around an Ethereum contract events. +type ERC20SwapFilterer struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// ERC20SwapSession is an auto generated Go binding around an Ethereum contract, +// with pre-set call and transact options. +type ERC20SwapSession struct { + Contract *ERC20Swap // 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 +} + +// ERC20SwapCallerSession is an auto generated read-only Go binding around an Ethereum contract, +// with pre-set call options. +type ERC20SwapCallerSession struct { + Contract *ERC20SwapCaller // Generic contract caller binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session +} + +// ERC20SwapTransactorSession is an auto generated write-only Go binding around an Ethereum contract, +// with pre-set transact options. +type ERC20SwapTransactorSession struct { + Contract *ERC20SwapTransactor // Generic contract transactor binding to set the session for + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session +} + +// ERC20SwapRaw is an auto generated low-level Go binding around an Ethereum contract. +type ERC20SwapRaw struct { + Contract *ERC20Swap // Generic contract binding to access the raw methods on +} + +// ERC20SwapCallerRaw is an auto generated low-level read-only Go binding around an Ethereum contract. +type ERC20SwapCallerRaw struct { + Contract *ERC20SwapCaller // Generic read-only contract binding to access the raw methods on +} + +// ERC20SwapTransactorRaw is an auto generated low-level write-only Go binding around an Ethereum contract. +type ERC20SwapTransactorRaw struct { + Contract *ERC20SwapTransactor // Generic write-only contract binding to access the raw methods on +} + +// NewERC20Swap creates a new instance of ERC20Swap, bound to a specific deployed contract. +func NewERC20Swap(address common.Address, backend bind.ContractBackend) (*ERC20Swap, error) { + contract, err := bindERC20Swap(address, backend, backend, backend) + if err != nil { + return nil, err + } + return &ERC20Swap{ERC20SwapCaller: ERC20SwapCaller{contract: contract}, ERC20SwapTransactor: ERC20SwapTransactor{contract: contract}, ERC20SwapFilterer: ERC20SwapFilterer{contract: contract}}, nil +} + +// NewERC20SwapCaller creates a new read-only instance of ERC20Swap, bound to a specific deployed contract. +func NewERC20SwapCaller(address common.Address, caller bind.ContractCaller) (*ERC20SwapCaller, error) { + contract, err := bindERC20Swap(address, caller, nil, nil) + if err != nil { + return nil, err + } + return &ERC20SwapCaller{contract: contract}, nil +} + +// NewERC20SwapTransactor creates a new write-only instance of ERC20Swap, bound to a specific deployed contract. +func NewERC20SwapTransactor(address common.Address, transactor bind.ContractTransactor) (*ERC20SwapTransactor, error) { + contract, err := bindERC20Swap(address, nil, transactor, nil) + if err != nil { + return nil, err + } + return &ERC20SwapTransactor{contract: contract}, nil +} + +// NewERC20SwapFilterer creates a new log filterer instance of ERC20Swap, bound to a specific deployed contract. +func NewERC20SwapFilterer(address common.Address, filterer bind.ContractFilterer) (*ERC20SwapFilterer, error) { + contract, err := bindERC20Swap(address, nil, nil, filterer) + if err != nil { + return nil, err + } + return &ERC20SwapFilterer{contract: contract}, nil +} + +// bindERC20Swap binds a generic wrapper to an already deployed contract. +func bindERC20Swap(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor, filterer bind.ContractFilterer) (*bind.BoundContract, error) { + parsed, err := abi.JSON(strings.NewReader(ERC20SwapABI)) + 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 (_ERC20Swap *ERC20SwapRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { + return _ERC20Swap.Contract.ERC20SwapCaller.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 (_ERC20Swap *ERC20SwapRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _ERC20Swap.Contract.ERC20SwapTransactor.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_ERC20Swap *ERC20SwapRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _ERC20Swap.Contract.ERC20SwapTransactor.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 (_ERC20Swap *ERC20SwapCallerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { + return _ERC20Swap.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 (_ERC20Swap *ERC20SwapTransactorRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _ERC20Swap.Contract.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_ERC20Swap *ERC20SwapTransactorRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _ERC20Swap.Contract.contract.Transact(opts, method, params...) +} + +// ContractKey is a free data retrieval call binding the contract method 0xed7cbed7. +// +// Solidity: function contractKey((bytes32,address,uint64,address,uint64) v) pure returns(bytes32) +func (_ERC20Swap *ERC20SwapCaller) ContractKey(opts *bind.CallOpts, v ethv1.ETHSwapVector) ([32]byte, error) { + var out []interface{} + err := _ERC20Swap.contract.Call(opts, &out, "contractKey", v) + + if err != nil { + return *new([32]byte), err + } + + out0 := *abi.ConvertType(out[0], new([32]byte)).(*[32]byte) + + return out0, err + +} + +// ContractKey is a free data retrieval call binding the contract method 0xed7cbed7. +// +// Solidity: function contractKey((bytes32,address,uint64,address,uint64) v) pure returns(bytes32) +func (_ERC20Swap *ERC20SwapSession) ContractKey(v ethv1.ETHSwapVector) ([32]byte, error) { + return _ERC20Swap.Contract.ContractKey(&_ERC20Swap.CallOpts, v) +} + +// ContractKey is a free data retrieval call binding the contract method 0xed7cbed7. +// +// Solidity: function contractKey((bytes32,address,uint64,address,uint64) v) pure returns(bytes32) +func (_ERC20Swap *ERC20SwapCallerSession) ContractKey(v ethv1.ETHSwapVector) ([32]byte, error) { + return _ERC20Swap.Contract.ContractKey(&_ERC20Swap.CallOpts, v) +} + +// IsRedeemable is a free data retrieval call binding the contract method 0x7802689d. +// +// Solidity: function isRedeemable((bytes32,address,uint64,address,uint64) v) view returns(bool) +func (_ERC20Swap *ERC20SwapCaller) IsRedeemable(opts *bind.CallOpts, v ethv1.ETHSwapVector) (bool, error) { + var out []interface{} + err := _ERC20Swap.contract.Call(opts, &out, "isRedeemable", v) + + if err != nil { + return *new(bool), err + } + + out0 := *abi.ConvertType(out[0], new(bool)).(*bool) + + return out0, err + +} + +// IsRedeemable is a free data retrieval call binding the contract method 0x7802689d. +// +// Solidity: function isRedeemable((bytes32,address,uint64,address,uint64) v) view returns(bool) +func (_ERC20Swap *ERC20SwapSession) IsRedeemable(v ethv1.ETHSwapVector) (bool, error) { + return _ERC20Swap.Contract.IsRedeemable(&_ERC20Swap.CallOpts, v) +} + +// IsRedeemable is a free data retrieval call binding the contract method 0x7802689d. +// +// Solidity: function isRedeemable((bytes32,address,uint64,address,uint64) v) view returns(bool) +func (_ERC20Swap *ERC20SwapCallerSession) IsRedeemable(v ethv1.ETHSwapVector) (bool, error) { + return _ERC20Swap.Contract.IsRedeemable(&_ERC20Swap.CallOpts, v) +} + +// SecretValidates is a free data retrieval call binding the contract method 0x77d7e031. +// +// Solidity: function secretValidates(bytes32 secret, bytes32 secretHash) pure returns(bool) +func (_ERC20Swap *ERC20SwapCaller) SecretValidates(opts *bind.CallOpts, secret [32]byte, secretHash [32]byte) (bool, error) { + var out []interface{} + err := _ERC20Swap.contract.Call(opts, &out, "secretValidates", secret, secretHash) + + if err != nil { + return *new(bool), err + } + + out0 := *abi.ConvertType(out[0], new(bool)).(*bool) + + return out0, err + +} + +// SecretValidates is a free data retrieval call binding the contract method 0x77d7e031. +// +// Solidity: function secretValidates(bytes32 secret, bytes32 secretHash) pure returns(bool) +func (_ERC20Swap *ERC20SwapSession) SecretValidates(secret [32]byte, secretHash [32]byte) (bool, error) { + return _ERC20Swap.Contract.SecretValidates(&_ERC20Swap.CallOpts, secret, secretHash) +} + +// SecretValidates is a free data retrieval call binding the contract method 0x77d7e031. +// +// Solidity: function secretValidates(bytes32 secret, bytes32 secretHash) pure returns(bool) +func (_ERC20Swap *ERC20SwapCallerSession) SecretValidates(secret [32]byte, secretHash [32]byte) (bool, error) { + return _ERC20Swap.Contract.SecretValidates(&_ERC20Swap.CallOpts, secret, secretHash) +} + +// Status is a free data retrieval call binding the contract method 0x61a16e33. +// +// Solidity: function status((bytes32,address,uint64,address,uint64) v) view returns((uint8,bytes32,uint256)) +func (_ERC20Swap *ERC20SwapCaller) Status(opts *bind.CallOpts, v ethv1.ETHSwapVector) (ethv1.ETHSwapStatus, error) { + var out []interface{} + err := _ERC20Swap.contract.Call(opts, &out, "status", v) + + if err != nil { + return *new(ethv1.ETHSwapStatus), err + } + + out0 := *abi.ConvertType(out[0], new(ethv1.ETHSwapStatus)).(*ethv1.ETHSwapStatus) + + return out0, err + +} + +// Status is a free data retrieval call binding the contract method 0x61a16e33. +// +// Solidity: function status((bytes32,address,uint64,address,uint64) v) view returns((uint8,bytes32,uint256)) +func (_ERC20Swap *ERC20SwapSession) Status(v ethv1.ETHSwapVector) (ethv1.ETHSwapStatus, error) { + return _ERC20Swap.Contract.Status(&_ERC20Swap.CallOpts, v) +} + +// Status is a free data retrieval call binding the contract method 0x61a16e33. +// +// Solidity: function status((bytes32,address,uint64,address,uint64) v) view returns((uint8,bytes32,uint256)) +func (_ERC20Swap *ERC20SwapCallerSession) Status(v ethv1.ETHSwapVector) (ethv1.ETHSwapStatus, error) { + return _ERC20Swap.Contract.Status(&_ERC20Swap.CallOpts, v) +} + +// Swaps is a free data retrieval call binding the contract method 0xeb84e7f2. +// +// Solidity: function swaps(bytes32 ) view returns(bytes32) +func (_ERC20Swap *ERC20SwapCaller) Swaps(opts *bind.CallOpts, arg0 [32]byte) ([32]byte, error) { + var out []interface{} + err := _ERC20Swap.contract.Call(opts, &out, "swaps", arg0) + + if err != nil { + return *new([32]byte), err + } + + out0 := *abi.ConvertType(out[0], new([32]byte)).(*[32]byte) + + return out0, err + +} + +// Swaps is a free data retrieval call binding the contract method 0xeb84e7f2. +// +// Solidity: function swaps(bytes32 ) view returns(bytes32) +func (_ERC20Swap *ERC20SwapSession) Swaps(arg0 [32]byte) ([32]byte, error) { + return _ERC20Swap.Contract.Swaps(&_ERC20Swap.CallOpts, arg0) +} + +// Swaps is a free data retrieval call binding the contract method 0xeb84e7f2. +// +// Solidity: function swaps(bytes32 ) view returns(bytes32) +func (_ERC20Swap *ERC20SwapCallerSession) Swaps(arg0 [32]byte) ([32]byte, error) { + return _ERC20Swap.Contract.Swaps(&_ERC20Swap.CallOpts, arg0) +} + +// TokenAddress is a free data retrieval call binding the contract method 0x8c8e8fee. +// +// Solidity: function token_address() view returns(address) +func (_ERC20Swap *ERC20SwapCaller) TokenAddress(opts *bind.CallOpts) (common.Address, error) { + var out []interface{} + err := _ERC20Swap.contract.Call(opts, &out, "token_address") + + if err != nil { + return *new(common.Address), err + } + + out0 := *abi.ConvertType(out[0], new(common.Address)).(*common.Address) + + return out0, err + +} + +// TokenAddress is a free data retrieval call binding the contract method 0x8c8e8fee. +// +// Solidity: function token_address() view returns(address) +func (_ERC20Swap *ERC20SwapSession) TokenAddress() (common.Address, error) { + return _ERC20Swap.Contract.TokenAddress(&_ERC20Swap.CallOpts) +} + +// TokenAddress is a free data retrieval call binding the contract method 0x8c8e8fee. +// +// Solidity: function token_address() view returns(address) +func (_ERC20Swap *ERC20SwapCallerSession) TokenAddress() (common.Address, error) { + return _ERC20Swap.Contract.TokenAddress(&_ERC20Swap.CallOpts) +} + +// Initiate is a paid mutator transaction binding the contract method 0x64a97bff. +// +// Solidity: function initiate((bytes32,address,uint64,address,uint64)[] contracts) payable returns() +func (_ERC20Swap *ERC20SwapTransactor) Initiate(opts *bind.TransactOpts, contracts []ethv1.ETHSwapVector) (*types.Transaction, error) { + return _ERC20Swap.contract.Transact(opts, "initiate", contracts) +} + +// Initiate is a paid mutator transaction binding the contract method 0x64a97bff. +// +// Solidity: function initiate((bytes32,address,uint64,address,uint64)[] contracts) payable returns() +func (_ERC20Swap *ERC20SwapSession) Initiate(contracts []ethv1.ETHSwapVector) (*types.Transaction, error) { + return _ERC20Swap.Contract.Initiate(&_ERC20Swap.TransactOpts, contracts) +} + +// Initiate is a paid mutator transaction binding the contract method 0x64a97bff. +// +// Solidity: function initiate((bytes32,address,uint64,address,uint64)[] contracts) payable returns() +func (_ERC20Swap *ERC20SwapTransactorSession) Initiate(contracts []ethv1.ETHSwapVector) (*types.Transaction, error) { + return _ERC20Swap.Contract.Initiate(&_ERC20Swap.TransactOpts, contracts) +} + +// Redeem is a paid mutator transaction binding the contract method 0x428b16e1. +// +// Solidity: function redeem(((bytes32,address,uint64,address,uint64),bytes32)[] redemptions) returns() +func (_ERC20Swap *ERC20SwapTransactor) Redeem(opts *bind.TransactOpts, redemptions []ethv1.ETHSwapRedemption) (*types.Transaction, error) { + return _ERC20Swap.contract.Transact(opts, "redeem", redemptions) +} + +// Redeem is a paid mutator transaction binding the contract method 0x428b16e1. +// +// Solidity: function redeem(((bytes32,address,uint64,address,uint64),bytes32)[] redemptions) returns() +func (_ERC20Swap *ERC20SwapSession) Redeem(redemptions []ethv1.ETHSwapRedemption) (*types.Transaction, error) { + return _ERC20Swap.Contract.Redeem(&_ERC20Swap.TransactOpts, redemptions) +} + +// Redeem is a paid mutator transaction binding the contract method 0x428b16e1. +// +// Solidity: function redeem(((bytes32,address,uint64,address,uint64),bytes32)[] redemptions) returns() +func (_ERC20Swap *ERC20SwapTransactorSession) Redeem(redemptions []ethv1.ETHSwapRedemption) (*types.Transaction, error) { + return _ERC20Swap.Contract.Redeem(&_ERC20Swap.TransactOpts, redemptions) +} + +// Refund is a paid mutator transaction binding the contract method 0xd2544c06. +// +// Solidity: function refund((bytes32,address,uint64,address,uint64) v) returns() +func (_ERC20Swap *ERC20SwapTransactor) Refund(opts *bind.TransactOpts, v ethv1.ETHSwapVector) (*types.Transaction, error) { + return _ERC20Swap.contract.Transact(opts, "refund", v) +} + +// Refund is a paid mutator transaction binding the contract method 0xd2544c06. +// +// Solidity: function refund((bytes32,address,uint64,address,uint64) v) returns() +func (_ERC20Swap *ERC20SwapSession) Refund(v ethv1.ETHSwapVector) (*types.Transaction, error) { + return _ERC20Swap.Contract.Refund(&_ERC20Swap.TransactOpts, v) +} + +// Refund is a paid mutator transaction binding the contract method 0xd2544c06. +// +// Solidity: function refund((bytes32,address,uint64,address,uint64) v) returns() +func (_ERC20Swap *ERC20SwapTransactorSession) Refund(v ethv1.ETHSwapVector) (*types.Transaction, error) { + return _ERC20Swap.Contract.Refund(&_ERC20Swap.TransactOpts, v) +} diff --git a/dex/networks/eth/contracts/ETHSwapV1.sol b/dex/networks/eth/contracts/ETHSwapV1.sol new file mode 100644 index 0000000000..d1e115b67f --- /dev/null +++ b/dex/networks/eth/contracts/ETHSwapV1.sol @@ -0,0 +1,227 @@ +// SPDX-License-Identifier: BlueOak-1.0.0 +// pragma should be as specific as possible to allow easier validation. +pragma solidity = 0.8.15; + +// ETHSwap creates a contract to be deployed on an ethereum network. After +// deployed, it keeps a record of the state of a contract and enables +// redemption and refund of the contract when conditions are met. +// +// ETHSwap accomplishes this by holding funds sent to ETHSwap until certain +// conditions are met. An initiator sends a tx with the Vector(s) to fund and +// the requisite value to transfer to ETHSwap. At +// this point the funds belong to the contract, and cannot be accessed by +// anyone else, not even the contract's deployer. The swap Vector specifies +// the conditions necessary for refund and redeem. +// +// ETHSwap has no limits on gas used for any transactions. +// +// ETHSwap 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 { + // Step is a type that hold's a contract's current step. Empty is the + // uninitiated or null value. + enum Step { Empty, Filled, Redeemed, Refunded } + + struct Status { + Step step; + bytes32 secret; + uint256 blockNumber; + } + + bytes32 constant RefundRecord = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF; + + // swaps is a map of contract hashes to the "swap record". The swap record + // has the following interpretation. + // if (record == bytes32(0x00)): contract is uninitiated + // else if (uint256(record) < block.number && sha256(record) != contract.secretHash): + // contract is initiated and redeemable by the participant with the secret. + // else if (sha256(record) == contract.secretHash): contract has been redeemed + // else if (record == RefundRecord): contract has been refunded + // else: invalid record. Should be impossible by construction + mapping(bytes32 => bytes32) public swaps; + + // Vector is the information necessary for initialization and redemption + // or refund. The Vector itself is not stored on-chain. Instead, a key + // unique to the Vector is generated from the Vector data and keys + // the swap record. + struct Vector { + bytes32 secretHash; + address initiator; + uint64 refundTimestamp; + address participant; + uint64 value; + } + + // contractKey generates a key hash which commits to the contract data. The + // generated hash is used as a key in the swaps map. + function contractKey(Vector calldata v) public pure returns (bytes32) { + return sha256(bytes.concat(v.secretHash, bytes20(v.initiator), bytes20(v.participant), bytes8(v.value), bytes8(v.refundTimestamp))); + } + + // Redemption is the information necessary to redeem a Vector. Since we + // don't store the Vector itself, it must be provided as part of the + // redemption. + struct Redemption { + Vector v; + bytes32 secret; + } + + function secretValidates(bytes32 secret, bytes32 secretHash) public pure returns (bool) { + return sha256(bytes.concat(secret)) == secretHash; + } + + // 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() {} + + // senderIsOrigin ensures that this contract cannot be used by other + // contracts, which reduces possible attack vectors. + modifier senderIsOrigin() { + require(tx.origin == msg.sender, "sender != origin"); + _; + } + + // retrieveStatus retrieves the current swap record for the contract. + function retrieveStatus(Vector calldata v) + private view returns (bytes32, bytes32, uint256) + { + bytes32 k = contractKey(v); + bytes32 record = swaps[k]; + return (k, record, uint256(record)); + } + + // status returns the current state of the swap. + function status(Vector calldata v) + public view returns(Status memory) + { + (, bytes32 record, uint256 blockNum) = retrieveStatus(v); + Status memory r; + if (blockNum == 0) { + r.step = Step.Empty; + } else if (record == RefundRecord) { + r.step = Step.Refunded; + } else if (secretValidates(record, v.secretHash)) { + r.step = Step.Redeemed; + r.secret = record; + } else { + r.step = Step.Filled; + r.blockNumber = blockNum; + } + return r; + } + + // initiate initiates an array of Vectors. + function initiate(Vector[] calldata contracts) + public + payable + senderIsOrigin() + { + uint initVal = 0; + for (uint i = 0; i < contracts.length; i++) { + Vector calldata v = contracts[i]; + + require(v.value > 0, "0 val"); + require(v.refundTimestamp > 0, "0 refundTimestamp"); + + bytes32 k = contractKey(v); + bytes32 record = swaps[k]; + require(record == bytes32(0), "swap not empty"); + + record = bytes32(block.number); + require(!secretValidates(record, v.secretHash), "hash collision"); + + swaps[k] = record; + + initVal += v.value * 1 gwei; + } + + require(initVal == msg.value, "bad val"); + } + + // isRedeemable returns whether or not a swap identified by secretHash + // can be redeemed using secret. isRedeemable DOES NOT check if the caller + // is the participant in the vector. + function isRedeemable(Vector calldata v) + public + view + returns (bool) + { + (, bytes32 record, uint256 blockNum) = retrieveStatus(v); + return blockNum != 0 && !secretValidates(record, v.secretHash); + } + + // redeem redeems a Vector. It checks that the sender is not a contract, + // and that the secret hash hashes to secretHash. msg.value is tranfered + // from ETHSwap to the sender. + // + // To prevent reentry attack, 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. + function redeem(Redemption[] calldata redemptions) + public + senderIsOrigin() + { + uint amountToRedeem = 0; + for (uint i = 0; i < redemptions.length; i++) { + Redemption calldata r = redemptions[i]; + + require(r.v.participant == msg.sender, "not authed"); + + (bytes32 k, bytes32 record, uint256 blockNum) = retrieveStatus(r.v); + + // To be redeemable, the record needs to represent a valid block + // number. + require(blockNum > 0 && blockNum < block.number, "unfilled swap"); + + // Can't already be redeemed. + require(!secretValidates(record, r.v.secretHash), "already redeemed"); + + // Are they presenting the correct secret? + require(secretValidates(r.secret, r.v.secretHash), "invalid secret"); + + swaps[k] = r.secret; + amountToRedeem += r.v.value * 1 gwei; + } + + (bool ok, ) = payable(msg.sender).call{value: amountToRedeem}(""); + require(ok == true, "transfer failed"); + } + + // refund refunds a Vector. It checks that the sender is not a contract + // and that the refund time has passed. msg.value is transfered from the + // contract to the sender = Vector.participant. + // + // It is important to note that this also uses call.value which comes with + // no restrictions on gas used. See redeem for more info. + function refund(Vector calldata v) + public + senderIsOrigin() + { + // Is this contract even in a refundable state? + require(block.timestamp >= v.refundTimestamp, "locktime not expired"); + + // Retrieve the record. + (bytes32 k, bytes32 record, uint256 blockNum) = retrieveStatus(v); + + // Is this swap initialized? + require(blockNum > 0 && blockNum <= block.number, "swap not active"); + + // Is it already redeemed? + require(!secretValidates(record, v.secretHash), "swap already redeemed"); + + // Is it already refunded? + require(record != RefundRecord, "swap already refunded"); + + swaps[k] = RefundRecord; + + (bool ok, ) = payable(v.initiator).call{value: v.value * 1 gwei}(""); + require(ok == true, "transfer failed"); + } +} diff --git a/dex/networks/eth/contracts/updatecontract.sh b/dex/networks/eth/contracts/updatecontract.sh index 5397986ddb..8f1214a459 100755 --- a/dex/networks/eth/contracts/updatecontract.sh +++ b/dex/networks/eth/contracts/updatecontract.sh @@ -23,6 +23,7 @@ then fi mkdir temp +mkdir -p ${PKG_NAME} solc --abi --bin --bin-runtime --overwrite --optimize ${SOLIDITY_FILE} -o ./temp/ BYTECODE=$(<./temp/${CONTRACT_NAME}.bin-runtime) diff --git a/dex/networks/eth/contracts/v1/BinRuntimeV1.go b/dex/networks/eth/contracts/v1/BinRuntimeV1.go new file mode 100644 index 0000000000..a7a9d0f326 --- /dev/null +++ b/dex/networks/eth/contracts/v1/BinRuntimeV1.go @@ -0,0 +1,6 @@ +// Code generated - DO NOT EDIT. +// This file is a generated binding and any manual changes will be lost. + +package v1 + +const ETHSwapRuntimeBin = "60806040526004361061007b5760003560e01c80637802689d1161004e5780637802689d1461011b578063d2544c061461013b578063eb84e7f21461015b578063ed7cbed71461019657600080fd5b8063428b16e11461008057806361a16e33146100a257806364a97bff146100d857806377d7e031146100eb575b600080fd5b34801561008c57600080fd5b506100a061009b366004610b6f565b6101b6565b005b3480156100ae57600080fd5b506100c26100bd366004610be4565b610447565b6040516100cf9190610c12565b60405180910390f35b6100a06100e6366004610c55565b610503565b3480156100f757600080fd5b5061010b610106366004610cb8565b61073b565b60405190151581526020016100cf565b34801561012757600080fd5b5061010b610136366004610be4565b6107b5565b34801561014757600080fd5b506100a0610156366004610be4565b6107e8565b34801561016757600080fd5b50610188610176366004610cda565b60006020819052908152604090205481565b6040519081526020016100cf565b3480156101a257600080fd5b506101886101b1366004610be4565b610a4a565b3233146101de5760405162461bcd60e51b81526004016101d590610cf3565b60405180910390fd5b6000805b828110156103b057368484838181106101fd576101fd610d1d565b60c0029190910191503390506102196080830160608401610d33565b6001600160a01b03161461025c5760405162461bcd60e51b815260206004820152600a6024820152691b9bdd08185d5d1a195960b21b60448201526064016101d5565b6000808061026984610b43565b92509250925060008111801561027e57504381105b6102ba5760405162461bcd60e51b815260206004820152600d60248201526c0756e66696c6c6564207377617609c1b60448201526064016101d5565b6102c582853561073b565b156103055760405162461bcd60e51b815260206004820152601060248201526f185b1c9958591e481c995919595b595960821b60448201526064016101d5565b61031460a0850135853561073b565b6103515760405162461bcd60e51b815260206004820152600e60248201526d1a5b9d985b1a59081cd958dc995d60921b60448201526064016101d5565b600083815260208190526040902060a0850180359091556103759060808601610d63565b61038390633b9aca00610da3565b6103979067ffffffffffffffff1687610dd3565b95505050505080806103a890610deb565b9150506101e2565b50604051600090339083908381818185875af1925050503d80600081146103f3576040519150601f19603f3d011682016040523d82523d6000602084013e6103f8565b606091505b50909150506001811515146104415760405162461bcd60e51b815260206004820152600f60248201526e1d1c985b9cd9995c8819985a5b1959608a1b60448201526064016101d5565b50505050565b60408051606081018252600080825260208201819052918101829052908061046e84610b43565b92509250506104986040805160608101909152806000815260006020820181905260409091015290565b816000036104bf578060005b908160038111156104b7576104b7610bfc565b9052506104fb565b600183016104cf578060036104a4565b6104da83863561073b565b156104ef5760028152602081018390526104fb565b60018152604081018290525b949350505050565b3233146105225760405162461bcd60e51b81526004016101d590610cf3565b6000805b828110156106fc573684848381811061054157610541610d1d565b905060a002019050600081608001602081019061055e9190610d63565b67ffffffffffffffff161161059d5760405162461bcd60e51b81526020600482015260056024820152640c081d985b60da1b60448201526064016101d5565b60006105af6060830160408401610d63565b67ffffffffffffffff16116105fa5760405162461bcd60e51b815260206004820152601160248201527003020726566756e6454696d657374616d7607c1b60448201526064016101d5565b600061060582610a4a565b60008181526020819052604090205490915080156106565760405162461bcd60e51b815260206004820152600e60248201526d73776170206e6f7420656d70747960901b60448201526064016101d5565b504361066381843561073b565b156106a15760405162461bcd60e51b815260206004820152600e60248201526d3430b9b41031b7b63634b9b4b7b760911b60448201526064016101d5565b60008281526020819052604090208190556106c260a0840160808501610d63565b6106d090633b9aca00610da3565b6106e49067ffffffffffffffff1686610dd3565b945050505080806106f490610deb565b915050610526565b503481146107365760405162461bcd60e51b8152602060048201526007602482015266189859081d985b60ca1b60448201526064016101d5565b505050565b60008160028460405160200161075391815260200190565b60408051601f198184030181529082905261076d91610e04565b602060405180830381855afa15801561078a573d6000803e3d6000fd5b5050506040513d601f19601f820116820180604052508101906107ad9190610e3f565b149392505050565b60008060006107c384610b43565b9250925050806000141580156104fb57506107df82853561073b565b15949350505050565b3233146108075760405162461bcd60e51b81526004016101d590610cf3565b6108176060820160408301610d63565b67ffffffffffffffff164210156108675760405162461bcd60e51b81526020600482015260146024820152731b1bd8dadd1a5b59481b9bdd08195e1c1a5c995960621b60448201526064016101d5565b600080600061087584610b43565b92509250925060008111801561088b5750438111155b6108c95760405162461bcd60e51b815260206004820152600f60248201526e73776170206e6f742061637469766560881b60448201526064016101d5565b6108d482853561073b565b156109195760405162461bcd60e51b81526020600482015260156024820152741cddd85c08185b1c9958591e481c995919595b5959605a1b60448201526064016101d5565b600182016109615760405162461bcd60e51b81526020600482015260156024820152741cddd85c08185b1c9958591e481c99599d5b991959605a1b60448201526064016101d5565b600083815260208181526040808320600019905561098491908701908701610d33565b6001600160a01b031661099d60a0870160808801610d63565b6109ab90633b9aca00610da3565b67ffffffffffffffff1660405160006040518083038185875af1925050503d80600081146109f5576040519150601f19603f3d011682016040523d82523d6000602084013e6109fa565b606091505b5090915050600181151514610a435760405162461bcd60e51b815260206004820152600f60248201526e1d1c985b9cd9995c8819985a5b1959608a1b60448201526064016101d5565b5050505050565b600060028235610a606040850160208601610d33565b60601b846060016020810190610a769190610d33565b60601b610a8960a0870160808801610d63565b60c01b610a9c6060880160408901610d63565b6040805160208101969096526bffffffffffffffffffffffff19948516908601529190921660548401526001600160c01b0319918216606884015260c01b16607082015260780160408051601f1981840301815290829052610afd91610e04565b602060405180830381855afa158015610b1a573d6000803e3d6000fd5b5050506040513d601f19601f82011682018060405250810190610b3d9190610e3f565b92915050565b600080600080610b5285610a4a565b600081815260208190526040902054909690955085945092505050565b60008060208385031215610b8257600080fd5b823567ffffffffffffffff80821115610b9a57600080fd5b818501915085601f830112610bae57600080fd5b813581811115610bbd57600080fd5b86602060c083028501011115610bd257600080fd5b60209290920196919550909350505050565b600060a08284031215610bf657600080fd5b50919050565b634e487b7160e01b600052602160045260246000fd5b8151606082019060048110610c3757634e487b7160e01b600052602160045260246000fd5b80835250602083015160208301526040830151604083015292915050565b60008060208385031215610c6857600080fd5b823567ffffffffffffffff80821115610c8057600080fd5b818501915085601f830112610c9457600080fd5b813581811115610ca357600080fd5b86602060a083028501011115610bd257600080fd5b60008060408385031215610ccb57600080fd5b50508035926020909101359150565b600060208284031215610cec57600080fd5b5035919050565b60208082526010908201526f39b2b73232b910109e9037b934b3b4b760811b604082015260600190565b634e487b7160e01b600052603260045260246000fd5b600060208284031215610d4557600080fd5b81356001600160a01b0381168114610d5c57600080fd5b9392505050565b600060208284031215610d7557600080fd5b813567ffffffffffffffff81168114610d5c57600080fd5b634e487b7160e01b600052601160045260246000fd5b600067ffffffffffffffff80831681851681830481118215151615610dca57610dca610d8d565b02949350505050565b60008219821115610de657610de6610d8d565b500190565b600060018201610dfd57610dfd610d8d565b5060010190565b6000825160005b81811015610e255760208186018101518583015201610e0b565b81811115610e34576000828501525b509190910192915050565b600060208284031215610e5157600080fd5b505191905056fea2646970667358221220d735868682c69bb69ed936f11e2f136e7da44cb347e0b1d67acd1e088ea13dd664736f6c634300080f0033" diff --git a/dex/networks/eth/contracts/v1/contract.go b/dex/networks/eth/contracts/v1/contract.go new file mode 100644 index 0000000000..07de2ac59e --- /dev/null +++ b/dex/networks/eth/contracts/v1/contract.go @@ -0,0 +1,442 @@ +// Code generated - DO NOT EDIT. +// This file is a generated binding and any manual changes will be lost. + +package v1 + +import ( + "errors" + "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 ( + _ = errors.New + _ = big.NewInt + _ = strings.NewReader + _ = ethereum.NotFound + _ = bind.Bind + _ = common.Big1 + _ = types.BloomLookup + _ = event.NewSubscription +) + +// ETHSwapRedemption is an auto generated low-level Go binding around an user-defined struct. +type ETHSwapRedemption struct { + V ETHSwapVector + Secret [32]byte +} + +// ETHSwapStatus is an auto generated low-level Go binding around an user-defined struct. +type ETHSwapStatus struct { + Step uint8 + Secret [32]byte + BlockNumber *big.Int +} + +// ETHSwapVector is an auto generated low-level Go binding around an user-defined struct. +type ETHSwapVector struct { + SecretHash [32]byte + Initiator common.Address + RefundTimestamp uint64 + Participant common.Address + Value uint64 +} + +// ETHSwapMetaData contains all meta data concerning the ETHSwap contract. +var ETHSwapMetaData = &bind.MetaData{ + ABI: "[{\"inputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"inputs\":[{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"secretHash\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"initiator\",\"type\":\"address\"},{\"internalType\":\"uint64\",\"name\":\"refundTimestamp\",\"type\":\"uint64\"},{\"internalType\":\"address\",\"name\":\"participant\",\"type\":\"address\"},{\"internalType\":\"uint64\",\"name\":\"value\",\"type\":\"uint64\"}],\"internalType\":\"structETHSwap.Vector\",\"name\":\"v\",\"type\":\"tuple\"}],\"name\":\"contractKey\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"secretHash\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"initiator\",\"type\":\"address\"},{\"internalType\":\"uint64\",\"name\":\"refundTimestamp\",\"type\":\"uint64\"},{\"internalType\":\"address\",\"name\":\"participant\",\"type\":\"address\"},{\"internalType\":\"uint64\",\"name\":\"value\",\"type\":\"uint64\"}],\"internalType\":\"structETHSwap.Vector[]\",\"name\":\"contracts\",\"type\":\"tuple[]\"}],\"name\":\"initiate\",\"outputs\":[],\"stateMutability\":\"payable\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"secretHash\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"initiator\",\"type\":\"address\"},{\"internalType\":\"uint64\",\"name\":\"refundTimestamp\",\"type\":\"uint64\"},{\"internalType\":\"address\",\"name\":\"participant\",\"type\":\"address\"},{\"internalType\":\"uint64\",\"name\":\"value\",\"type\":\"uint64\"}],\"internalType\":\"structETHSwap.Vector\",\"name\":\"v\",\"type\":\"tuple\"}],\"name\":\"isRedeemable\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"secretHash\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"initiator\",\"type\":\"address\"},{\"internalType\":\"uint64\",\"name\":\"refundTimestamp\",\"type\":\"uint64\"},{\"internalType\":\"address\",\"name\":\"participant\",\"type\":\"address\"},{\"internalType\":\"uint64\",\"name\":\"value\",\"type\":\"uint64\"}],\"internalType\":\"structETHSwap.Vector\",\"name\":\"v\",\"type\":\"tuple\"},{\"internalType\":\"bytes32\",\"name\":\"secret\",\"type\":\"bytes32\"}],\"internalType\":\"structETHSwap.Redemption[]\",\"name\":\"redemptions\",\"type\":\"tuple[]\"}],\"name\":\"redeem\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"secretHash\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"initiator\",\"type\":\"address\"},{\"internalType\":\"uint64\",\"name\":\"refundTimestamp\",\"type\":\"uint64\"},{\"internalType\":\"address\",\"name\":\"participant\",\"type\":\"address\"},{\"internalType\":\"uint64\",\"name\":\"value\",\"type\":\"uint64\"}],\"internalType\":\"structETHSwap.Vector\",\"name\":\"v\",\"type\":\"tuple\"}],\"name\":\"refund\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"secret\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"secretHash\",\"type\":\"bytes32\"}],\"name\":\"secretValidates\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"secretHash\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"initiator\",\"type\":\"address\"},{\"internalType\":\"uint64\",\"name\":\"refundTimestamp\",\"type\":\"uint64\"},{\"internalType\":\"address\",\"name\":\"participant\",\"type\":\"address\"},{\"internalType\":\"uint64\",\"name\":\"value\",\"type\":\"uint64\"}],\"internalType\":\"structETHSwap.Vector\",\"name\":\"v\",\"type\":\"tuple\"}],\"name\":\"status\",\"outputs\":[{\"components\":[{\"internalType\":\"enumETHSwap.Step\",\"name\":\"step\",\"type\":\"uint8\"},{\"internalType\":\"bytes32\",\"name\":\"secret\",\"type\":\"bytes32\"},{\"internalType\":\"uint256\",\"name\":\"blockNumber\",\"type\":\"uint256\"}],\"internalType\":\"structETHSwap.Status\",\"name\":\"\",\"type\":\"tuple\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"name\":\"swaps\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"}]", + Bin: "0x608060405234801561001057600080fd5b50610e8e806100206000396000f3fe60806040526004361061007b5760003560e01c80637802689d1161004e5780637802689d1461011b578063d2544c061461013b578063eb84e7f21461015b578063ed7cbed71461019657600080fd5b8063428b16e11461008057806361a16e33146100a257806364a97bff146100d857806377d7e031146100eb575b600080fd5b34801561008c57600080fd5b506100a061009b366004610b6f565b6101b6565b005b3480156100ae57600080fd5b506100c26100bd366004610be4565b610447565b6040516100cf9190610c12565b60405180910390f35b6100a06100e6366004610c55565b610503565b3480156100f757600080fd5b5061010b610106366004610cb8565b61073b565b60405190151581526020016100cf565b34801561012757600080fd5b5061010b610136366004610be4565b6107b5565b34801561014757600080fd5b506100a0610156366004610be4565b6107e8565b34801561016757600080fd5b50610188610176366004610cda565b60006020819052908152604090205481565b6040519081526020016100cf565b3480156101a257600080fd5b506101886101b1366004610be4565b610a4a565b3233146101de5760405162461bcd60e51b81526004016101d590610cf3565b60405180910390fd5b6000805b828110156103b057368484838181106101fd576101fd610d1d565b60c0029190910191503390506102196080830160608401610d33565b6001600160a01b03161461025c5760405162461bcd60e51b815260206004820152600a6024820152691b9bdd08185d5d1a195960b21b60448201526064016101d5565b6000808061026984610b43565b92509250925060008111801561027e57504381105b6102ba5760405162461bcd60e51b815260206004820152600d60248201526c0756e66696c6c6564207377617609c1b60448201526064016101d5565b6102c582853561073b565b156103055760405162461bcd60e51b815260206004820152601060248201526f185b1c9958591e481c995919595b595960821b60448201526064016101d5565b61031460a0850135853561073b565b6103515760405162461bcd60e51b815260206004820152600e60248201526d1a5b9d985b1a59081cd958dc995d60921b60448201526064016101d5565b600083815260208190526040902060a0850180359091556103759060808601610d63565b61038390633b9aca00610da3565b6103979067ffffffffffffffff1687610dd3565b95505050505080806103a890610deb565b9150506101e2565b50604051600090339083908381818185875af1925050503d80600081146103f3576040519150601f19603f3d011682016040523d82523d6000602084013e6103f8565b606091505b50909150506001811515146104415760405162461bcd60e51b815260206004820152600f60248201526e1d1c985b9cd9995c8819985a5b1959608a1b60448201526064016101d5565b50505050565b60408051606081018252600080825260208201819052918101829052908061046e84610b43565b92509250506104986040805160608101909152806000815260006020820181905260409091015290565b816000036104bf578060005b908160038111156104b7576104b7610bfc565b9052506104fb565b600183016104cf578060036104a4565b6104da83863561073b565b156104ef5760028152602081018390526104fb565b60018152604081018290525b949350505050565b3233146105225760405162461bcd60e51b81526004016101d590610cf3565b6000805b828110156106fc573684848381811061054157610541610d1d565b905060a002019050600081608001602081019061055e9190610d63565b67ffffffffffffffff161161059d5760405162461bcd60e51b81526020600482015260056024820152640c081d985b60da1b60448201526064016101d5565b60006105af6060830160408401610d63565b67ffffffffffffffff16116105fa5760405162461bcd60e51b815260206004820152601160248201527003020726566756e6454696d657374616d7607c1b60448201526064016101d5565b600061060582610a4a565b60008181526020819052604090205490915080156106565760405162461bcd60e51b815260206004820152600e60248201526d73776170206e6f7420656d70747960901b60448201526064016101d5565b504361066381843561073b565b156106a15760405162461bcd60e51b815260206004820152600e60248201526d3430b9b41031b7b63634b9b4b7b760911b60448201526064016101d5565b60008281526020819052604090208190556106c260a0840160808501610d63565b6106d090633b9aca00610da3565b6106e49067ffffffffffffffff1686610dd3565b945050505080806106f490610deb565b915050610526565b503481146107365760405162461bcd60e51b8152602060048201526007602482015266189859081d985b60ca1b60448201526064016101d5565b505050565b60008160028460405160200161075391815260200190565b60408051601f198184030181529082905261076d91610e04565b602060405180830381855afa15801561078a573d6000803e3d6000fd5b5050506040513d601f19601f820116820180604052508101906107ad9190610e3f565b149392505050565b60008060006107c384610b43565b9250925050806000141580156104fb57506107df82853561073b565b15949350505050565b3233146108075760405162461bcd60e51b81526004016101d590610cf3565b6108176060820160408301610d63565b67ffffffffffffffff164210156108675760405162461bcd60e51b81526020600482015260146024820152731b1bd8dadd1a5b59481b9bdd08195e1c1a5c995960621b60448201526064016101d5565b600080600061087584610b43565b92509250925060008111801561088b5750438111155b6108c95760405162461bcd60e51b815260206004820152600f60248201526e73776170206e6f742061637469766560881b60448201526064016101d5565b6108d482853561073b565b156109195760405162461bcd60e51b81526020600482015260156024820152741cddd85c08185b1c9958591e481c995919595b5959605a1b60448201526064016101d5565b600182016109615760405162461bcd60e51b81526020600482015260156024820152741cddd85c08185b1c9958591e481c99599d5b991959605a1b60448201526064016101d5565b600083815260208181526040808320600019905561098491908701908701610d33565b6001600160a01b031661099d60a0870160808801610d63565b6109ab90633b9aca00610da3565b67ffffffffffffffff1660405160006040518083038185875af1925050503d80600081146109f5576040519150601f19603f3d011682016040523d82523d6000602084013e6109fa565b606091505b5090915050600181151514610a435760405162461bcd60e51b815260206004820152600f60248201526e1d1c985b9cd9995c8819985a5b1959608a1b60448201526064016101d5565b5050505050565b600060028235610a606040850160208601610d33565b60601b846060016020810190610a769190610d33565b60601b610a8960a0870160808801610d63565b60c01b610a9c6060880160408901610d63565b6040805160208101969096526bffffffffffffffffffffffff19948516908601529190921660548401526001600160c01b0319918216606884015260c01b16607082015260780160408051601f1981840301815290829052610afd91610e04565b602060405180830381855afa158015610b1a573d6000803e3d6000fd5b5050506040513d601f19601f82011682018060405250810190610b3d9190610e3f565b92915050565b600080600080610b5285610a4a565b600081815260208190526040902054909690955085945092505050565b60008060208385031215610b8257600080fd5b823567ffffffffffffffff80821115610b9a57600080fd5b818501915085601f830112610bae57600080fd5b813581811115610bbd57600080fd5b86602060c083028501011115610bd257600080fd5b60209290920196919550909350505050565b600060a08284031215610bf657600080fd5b50919050565b634e487b7160e01b600052602160045260246000fd5b8151606082019060048110610c3757634e487b7160e01b600052602160045260246000fd5b80835250602083015160208301526040830151604083015292915050565b60008060208385031215610c6857600080fd5b823567ffffffffffffffff80821115610c8057600080fd5b818501915085601f830112610c9457600080fd5b813581811115610ca357600080fd5b86602060a083028501011115610bd257600080fd5b60008060408385031215610ccb57600080fd5b50508035926020909101359150565b600060208284031215610cec57600080fd5b5035919050565b60208082526010908201526f39b2b73232b910109e9037b934b3b4b760811b604082015260600190565b634e487b7160e01b600052603260045260246000fd5b600060208284031215610d4557600080fd5b81356001600160a01b0381168114610d5c57600080fd5b9392505050565b600060208284031215610d7557600080fd5b813567ffffffffffffffff81168114610d5c57600080fd5b634e487b7160e01b600052601160045260246000fd5b600067ffffffffffffffff80831681851681830481118215151615610dca57610dca610d8d565b02949350505050565b60008219821115610de657610de6610d8d565b500190565b600060018201610dfd57610dfd610d8d565b5060010190565b6000825160005b81811015610e255760208186018101518583015201610e0b565b81811115610e34576000828501525b509190910192915050565b600060208284031215610e5157600080fd5b505191905056fea2646970667358221220d735868682c69bb69ed936f11e2f136e7da44cb347e0b1d67acd1e088ea13dd664736f6c634300080f0033", +} + +// ETHSwapABI is the input ABI used to generate the binding from. +// Deprecated: Use ETHSwapMetaData.ABI instead. +var ETHSwapABI = ETHSwapMetaData.ABI + +// ETHSwapBin is the compiled bytecode used for deploying new contracts. +// Deprecated: Use ETHSwapMetaData.Bin instead. +var ETHSwapBin = ETHSwapMetaData.Bin + +// 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 := ETHSwapMetaData.GetAbi() + if err != nil { + return common.Address{}, nil, nil, err + } + if parsed == nil { + return common.Address{}, nil, nil, errors.New("GetABI returned nil") + } + + 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...) +} + +// ContractKey is a free data retrieval call binding the contract method 0xed7cbed7. +// +// Solidity: function contractKey((bytes32,address,uint64,address,uint64) v) pure returns(bytes32) +func (_ETHSwap *ETHSwapCaller) ContractKey(opts *bind.CallOpts, v ETHSwapVector) ([32]byte, error) { + var out []interface{} + err := _ETHSwap.contract.Call(opts, &out, "contractKey", v) + + if err != nil { + return *new([32]byte), err + } + + out0 := *abi.ConvertType(out[0], new([32]byte)).(*[32]byte) + + return out0, err + +} + +// ContractKey is a free data retrieval call binding the contract method 0xed7cbed7. +// +// Solidity: function contractKey((bytes32,address,uint64,address,uint64) v) pure returns(bytes32) +func (_ETHSwap *ETHSwapSession) ContractKey(v ETHSwapVector) ([32]byte, error) { + return _ETHSwap.Contract.ContractKey(&_ETHSwap.CallOpts, v) +} + +// ContractKey is a free data retrieval call binding the contract method 0xed7cbed7. +// +// Solidity: function contractKey((bytes32,address,uint64,address,uint64) v) pure returns(bytes32) +func (_ETHSwap *ETHSwapCallerSession) ContractKey(v ETHSwapVector) ([32]byte, error) { + return _ETHSwap.Contract.ContractKey(&_ETHSwap.CallOpts, v) +} + +// IsRedeemable is a free data retrieval call binding the contract method 0x7802689d. +// +// Solidity: function isRedeemable((bytes32,address,uint64,address,uint64) v) view returns(bool) +func (_ETHSwap *ETHSwapCaller) IsRedeemable(opts *bind.CallOpts, v ETHSwapVector) (bool, error) { + var out []interface{} + err := _ETHSwap.contract.Call(opts, &out, "isRedeemable", v) + + if err != nil { + return *new(bool), err + } + + out0 := *abi.ConvertType(out[0], new(bool)).(*bool) + + return out0, err + +} + +// IsRedeemable is a free data retrieval call binding the contract method 0x7802689d. +// +// Solidity: function isRedeemable((bytes32,address,uint64,address,uint64) v) view returns(bool) +func (_ETHSwap *ETHSwapSession) IsRedeemable(v ETHSwapVector) (bool, error) { + return _ETHSwap.Contract.IsRedeemable(&_ETHSwap.CallOpts, v) +} + +// IsRedeemable is a free data retrieval call binding the contract method 0x7802689d. +// +// Solidity: function isRedeemable((bytes32,address,uint64,address,uint64) v) view returns(bool) +func (_ETHSwap *ETHSwapCallerSession) IsRedeemable(v ETHSwapVector) (bool, error) { + return _ETHSwap.Contract.IsRedeemable(&_ETHSwap.CallOpts, v) +} + +// SecretValidates is a free data retrieval call binding the contract method 0x77d7e031. +// +// Solidity: function secretValidates(bytes32 secret, bytes32 secretHash) pure returns(bool) +func (_ETHSwap *ETHSwapCaller) SecretValidates(opts *bind.CallOpts, secret [32]byte, secretHash [32]byte) (bool, error) { + var out []interface{} + err := _ETHSwap.contract.Call(opts, &out, "secretValidates", secret, secretHash) + + if err != nil { + return *new(bool), err + } + + out0 := *abi.ConvertType(out[0], new(bool)).(*bool) + + return out0, err + +} + +// SecretValidates is a free data retrieval call binding the contract method 0x77d7e031. +// +// Solidity: function secretValidates(bytes32 secret, bytes32 secretHash) pure returns(bool) +func (_ETHSwap *ETHSwapSession) SecretValidates(secret [32]byte, secretHash [32]byte) (bool, error) { + return _ETHSwap.Contract.SecretValidates(&_ETHSwap.CallOpts, secret, secretHash) +} + +// SecretValidates is a free data retrieval call binding the contract method 0x77d7e031. +// +// Solidity: function secretValidates(bytes32 secret, bytes32 secretHash) pure returns(bool) +func (_ETHSwap *ETHSwapCallerSession) SecretValidates(secret [32]byte, secretHash [32]byte) (bool, error) { + return _ETHSwap.Contract.SecretValidates(&_ETHSwap.CallOpts, secret, secretHash) +} + +// Status is a free data retrieval call binding the contract method 0x61a16e33. +// +// Solidity: function status((bytes32,address,uint64,address,uint64) v) view returns((uint8,bytes32,uint256)) +func (_ETHSwap *ETHSwapCaller) Status(opts *bind.CallOpts, v ETHSwapVector) (ETHSwapStatus, error) { + var out []interface{} + err := _ETHSwap.contract.Call(opts, &out, "status", v) + + if err != nil { + return *new(ETHSwapStatus), err + } + + out0 := *abi.ConvertType(out[0], new(ETHSwapStatus)).(*ETHSwapStatus) + + return out0, err + +} + +// Status is a free data retrieval call binding the contract method 0x61a16e33. +// +// Solidity: function status((bytes32,address,uint64,address,uint64) v) view returns((uint8,bytes32,uint256)) +func (_ETHSwap *ETHSwapSession) Status(v ETHSwapVector) (ETHSwapStatus, error) { + return _ETHSwap.Contract.Status(&_ETHSwap.CallOpts, v) +} + +// Status is a free data retrieval call binding the contract method 0x61a16e33. +// +// Solidity: function status((bytes32,address,uint64,address,uint64) v) view returns((uint8,bytes32,uint256)) +func (_ETHSwap *ETHSwapCallerSession) Status(v ETHSwapVector) (ETHSwapStatus, error) { + return _ETHSwap.Contract.Status(&_ETHSwap.CallOpts, v) +} + +// Swaps is a free data retrieval call binding the contract method 0xeb84e7f2. +// +// Solidity: function swaps(bytes32 ) view returns(bytes32) +func (_ETHSwap *ETHSwapCaller) Swaps(opts *bind.CallOpts, arg0 [32]byte) ([32]byte, error) { + var out []interface{} + err := _ETHSwap.contract.Call(opts, &out, "swaps", arg0) + + if err != nil { + return *new([32]byte), err + } + + out0 := *abi.ConvertType(out[0], new([32]byte)).(*[32]byte) + + return out0, err + +} + +// Swaps is a free data retrieval call binding the contract method 0xeb84e7f2. +// +// Solidity: function swaps(bytes32 ) view returns(bytes32) +func (_ETHSwap *ETHSwapSession) Swaps(arg0 [32]byte) ([32]byte, 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(bytes32) +func (_ETHSwap *ETHSwapCallerSession) Swaps(arg0 [32]byte) ([32]byte, error) { + return _ETHSwap.Contract.Swaps(&_ETHSwap.CallOpts, arg0) +} + +// Initiate is a paid mutator transaction binding the contract method 0x64a97bff. +// +// Solidity: function initiate((bytes32,address,uint64,address,uint64)[] contracts) payable returns() +func (_ETHSwap *ETHSwapTransactor) Initiate(opts *bind.TransactOpts, contracts []ETHSwapVector) (*types.Transaction, error) { + return _ETHSwap.contract.Transact(opts, "initiate", contracts) +} + +// Initiate is a paid mutator transaction binding the contract method 0x64a97bff. +// +// Solidity: function initiate((bytes32,address,uint64,address,uint64)[] contracts) payable returns() +func (_ETHSwap *ETHSwapSession) Initiate(contracts []ETHSwapVector) (*types.Transaction, error) { + return _ETHSwap.Contract.Initiate(&_ETHSwap.TransactOpts, contracts) +} + +// Initiate is a paid mutator transaction binding the contract method 0x64a97bff. +// +// Solidity: function initiate((bytes32,address,uint64,address,uint64)[] contracts) payable returns() +func (_ETHSwap *ETHSwapTransactorSession) Initiate(contracts []ETHSwapVector) (*types.Transaction, error) { + return _ETHSwap.Contract.Initiate(&_ETHSwap.TransactOpts, contracts) +} + +// Redeem is a paid mutator transaction binding the contract method 0x428b16e1. +// +// Solidity: function redeem(((bytes32,address,uint64,address,uint64),bytes32)[] redemptions) returns() +func (_ETHSwap *ETHSwapTransactor) Redeem(opts *bind.TransactOpts, redemptions []ETHSwapRedemption) (*types.Transaction, error) { + return _ETHSwap.contract.Transact(opts, "redeem", redemptions) +} + +// Redeem is a paid mutator transaction binding the contract method 0x428b16e1. +// +// Solidity: function redeem(((bytes32,address,uint64,address,uint64),bytes32)[] redemptions) returns() +func (_ETHSwap *ETHSwapSession) Redeem(redemptions []ETHSwapRedemption) (*types.Transaction, error) { + return _ETHSwap.Contract.Redeem(&_ETHSwap.TransactOpts, redemptions) +} + +// Redeem is a paid mutator transaction binding the contract method 0x428b16e1. +// +// Solidity: function redeem(((bytes32,address,uint64,address,uint64),bytes32)[] redemptions) returns() +func (_ETHSwap *ETHSwapTransactorSession) Redeem(redemptions []ETHSwapRedemption) (*types.Transaction, error) { + return _ETHSwap.Contract.Redeem(&_ETHSwap.TransactOpts, redemptions) +} + +// Refund is a paid mutator transaction binding the contract method 0xd2544c06. +// +// Solidity: function refund((bytes32,address,uint64,address,uint64) v) returns() +func (_ETHSwap *ETHSwapTransactor) Refund(opts *bind.TransactOpts, v ETHSwapVector) (*types.Transaction, error) { + return _ETHSwap.contract.Transact(opts, "refund", v) +} + +// Refund is a paid mutator transaction binding the contract method 0xd2544c06. +// +// Solidity: function refund((bytes32,address,uint64,address,uint64) v) returns() +func (_ETHSwap *ETHSwapSession) Refund(v ETHSwapVector) (*types.Transaction, error) { + return _ETHSwap.Contract.Refund(&_ETHSwap.TransactOpts, v) +} + +// Refund is a paid mutator transaction binding the contract method 0xd2544c06. +// +// Solidity: function refund((bytes32,address,uint64,address,uint64) v) returns() +func (_ETHSwap *ETHSwapTransactorSession) Refund(v ETHSwapVector) (*types.Transaction, error) { + return _ETHSwap.Contract.Refund(&_ETHSwap.TransactOpts, v) +} diff --git a/dex/networks/eth/params.go b/dex/networks/eth/params.go index c45af9b585..86970165d6 100644 --- a/dex/networks/eth/params.go +++ b/dex/networks/eth/params.go @@ -17,6 +17,7 @@ import ( "decred.org/dcrdex/dex" v0 "decred.org/dcrdex/dex/networks/eth/contracts/v0" + swapv1 "decred.org/dcrdex/dex/networks/eth/contracts/v1" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core" ) @@ -42,23 +43,37 @@ var ( VersionedGases = map[uint32]*Gases{ 0: v0Gases, + 1: v1Gases, } ContractAddresses = map[uint32]map[dex.Network]common.Address{ 0: { dex.Mainnet: common.Address{}, - dex.Simnet: common.HexToAddress("0x2f68e723b8989ba1c6a9f03e42f33cb7dc9d606f"), dex.Testnet: common.HexToAddress("0xa483b6166dA8Da6748B29Af35f96C4F9388c456C"), + dex.Simnet: common.Address{}, + }, + 1: { + dex.Mainnet: common.Address{}, + dex.Testnet: common.Address{}, + dex.Simnet: common.Address{}, }, } ) var v0Gases = &Gases{ - Swap: 135000, - SwapAdd: 113000, - Redeem: 63000, - RedeemAdd: 32000, - Refund: 43000, + Swap: 135_000, + SwapAdd: 113_000, + Redeem: 63_000, + RedeemAdd: 32_000, + Refund: 43_000, +} + +var v1Gases = &Gases{ + Swap: 52_000, // [48072 74276 100477 126682 152874] + SwapAdd: 30_000, + Redeem: 50_000, // [39832 50894 61956 73016 84067] + RedeemAdd: 14_000, + Refund: 45_000, // [37961 37937 37961 37961] } // LoadGenesisFile loads a Genesis config from a json file. @@ -77,23 +92,37 @@ func LoadGenesisFile(genesisFile string) (*core.Genesis, error) { return &genesis, nil } -// EncodeContractData packs the contract version and the secret hash into a byte +// EncodeContractData packs the contract version and the locator into a byte // slice for communicating a swap's identity. -func EncodeContractData(contractVersion uint32, swapKey [SecretHashSize]byte) []byte { - b := make([]byte, SecretHashSize+4) +func EncodeContractData(contractVersion uint32, locator []byte) []byte { + b := make([]byte, len(locator)+4) binary.BigEndian.PutUint32(b[:4], contractVersion) - copy(b[4:], swapKey[:]) + copy(b[4:], locator[:]) return b } -// DecodeContractData unpacks the contract version and secret hash. -func DecodeContractData(data []byte) (contractVersion uint32, swapKey [SecretHashSize]byte, err error) { - if len(data) != SecretHashSize+4 { - err = errors.New("invalid swap data") +// DecodeLocator unpacks the contract version and secret hash. +func DecodeLocator(data []byte) (contractVersion uint32, locator []byte, err error) { + if len(data) < 4 { + err = errors.New("invalid short encoding") return } + locator = data[4:] contractVersion = binary.BigEndian.Uint32(data[:4]) - copy(swapKey[:], data[4:]) + switch contractVersion { + case 0: + if len(locator) != SecretHashSize { + err = fmt.Errorf("v0 locator is too small. expected %d, got %d", SecretHashSize, len(locator)) + return + } + case 1: + if len(locator) != LocatorV1Length { + err = fmt.Errorf("v1 locator is too small. expected %d, got %d", LocatorV1Length, len(locator)) + return + } + default: + err = fmt.Errorf("unkown contract version %d", contractVersion) + } return } @@ -199,7 +228,35 @@ func (ss SwapStep) String() string { return "unknown" } -// SwapState is the current state of an in-process swap. +// SwapVector is immutable contract data. +type SwapVector struct { + From common.Address + To common.Address + Value uint64 + SecretHash [32]byte + LockTime uint64 +} + +// Locator encodes a version 1 locator for the SwapVector. +func (v *SwapVector) Locator() []byte { + locator := make([]byte, LocatorV1Length) + copy(locator[0:20], v.From[:]) + copy(locator[20:40], v.To[:]) + binary.BigEndian.PutUint64(locator[40:48], v.Value) + copy(locator[48:80], v.SecretHash[:]) + binary.BigEndian.PutUint64(locator[80:88], v.LockTime) + return locator +} + +// SwapStatus is the contract data that specifies the current contract state. +type SwapStatus struct { + BlockHeight uint64 + Secret [32]byte + Step SwapStep +} + +// SwapState is the current state of an in-process swap, as stored on-chain by +// the v0 contract. type SwapState struct { BlockHeight uint64 LockTime time.Time @@ -280,3 +337,42 @@ func (g *Gases) RedeemN(n int) uint64 { } return g.Redeem + g.RedeemAdd*(uint64(n)-1) } + +func ParseV0Locator(locator []byte) (secretHash [32]byte, err error) { + if len(locator) == SecretHashSize { + copy(secretHash[:], locator) + } else { + err = fmt.Errorf("wrong v0 locator length. wanted %d, got %d", SecretHashSize, len(locator)) + } + return +} + +// LocatorV1Length = from 20 + to 20 + value 8 + secretHash 32 + +// lockTime 8 = 88 bytes +const LocatorV1Length = 88 + +func ParseV1Locator(locator []byte) (v *SwapVector, err error) { + // from 20 + to 20 + value 8 + secretHash 32 + lockTime 8 + if len(locator) == LocatorV1Length { + v = &SwapVector{ + From: common.BytesToAddress(locator[:20]), + To: common.BytesToAddress(locator[20:40]), + Value: binary.BigEndian.Uint64(locator[40:48]), + LockTime: binary.BigEndian.Uint64(locator[80:88]), + } + copy(v.SecretHash[:], locator[48:80]) + } else { + err = fmt.Errorf("wrong v1 locator length. wanted %d, got %d", LocatorV1Length, len(locator)) + } + return +} + +func SwapVectorToAbigen(c *SwapVector) swapv1.ETHSwapVector { + return swapv1.ETHSwapVector{ + SecretHash: c.SecretHash, + Initiator: c.From, + RefundTimestamp: c.LockTime, + Participant: c.To, + Value: c.Value, + } +} diff --git a/dex/networks/eth/params_test.go b/dex/networks/eth/params_test.go index c40e142c6f..1a905bb684 100644 --- a/dex/networks/eth/params_test.go +++ b/dex/networks/eth/params_test.go @@ -71,7 +71,7 @@ func TestVersionedGases(t *testing.T) { expRefundGas: v0Gases.Refund, }, { - ver: 1, + ver: 2, expInitGases: []uint64{0, math.MaxUint64}, expRedeemGases: []uint64{0, math.MaxUint64}, expRefundGas: math.MaxUint64, diff --git a/dex/networks/eth/tokens.go b/dex/networks/eth/tokens.go index a5fd01d84f..0c6884db88 100644 --- a/dex/networks/eth/tokens.go +++ b/dex/networks/eth/tokens.go @@ -142,6 +142,19 @@ var Tokens = map[uint32]*Token{ Transfer: 35_000, }, }, + 1: { + // DRAFT TODO + Address: common.Address{}, + Gas: Gases{ + Swap: 174_000, // [171756 284366 396976 509586 622184] + SwapAdd: 115_000, + Redeem: 70_000, // [63214 94858 126502 158135 189779] + RedeemAdd: 33_000, + Refund: 50_000, // [48127 48127 48127 48127 48127] + Approve: 46_000, // [44465 27365 27365 27365 27365] + Transfer: 35_000, // [32540 32540 32540 32540 32540] + }, + }, }, }, }, @@ -205,7 +218,28 @@ var Tokens = map[uint32]*Token{ RedeemAdd: 33_000, Refund: 67_000, Approve: 65_000, - Transfer: 70_000, + Transfer: 33_000, // [24964 24964 24964 24964 24964] + }, + }, + 1: { + // Swap contract address. The simnet harness writes this + // address to file. Live tests must populate this field. + Address: common.Address{}, + Gas: Gases{ + Swap: 95_000, // [86009 112920 139831 166742 193651] + SwapAdd: 30_000, // avg SwapAdd 26910 + Redeem: 50_000, // [42569 53614 64646 75703 86734] + RedeemAdd: 14_000, // avg RedeemAdd 11038 + Refund: 50_000, // [45306 45306 45306 45306 45294] avg: 45303 + // Approve is the gas used to call the approve + // method of the contract. For Approve transactions, + // the very first approval for an account-spender + // pair takes more than subsequent approvals. The + // results are repeated for a different account's + // first approvals on the same contract, so it's not + // just the global first. + Approve: 46_000, + Transfer: 33_000, }, }, }, @@ -256,14 +290,18 @@ func MaybeReadSimnetAddrs() { return } - ethSwapContractAddrFile := filepath.Join(ethPath, "eth_swap_contract_address.txt") - tokenSwapContractAddrFile := filepath.Join(ethPath, "erc20_swap_contract_address.txt") + ethSwapContractAddrFileV0 := filepath.Join(ethPath, "eth_swap_contract_address.txt") + tokenSwapContractAddrFileV0 := filepath.Join(ethPath, "erc20_swap_contract_address.txt") + ethSwapContractAddrFileV1 := filepath.Join(ethPath, "eth_swap_contract_address_v1.txt") + tokenSwapContractAddrFileV1 := filepath.Join(ethPath, "erc20_swap_contract_address_v1.txt") testTokenContractAddrFile := filepath.Join(ethPath, "test_token_contract_address.txt") - ContractAddresses[0][dex.Simnet] = getContractAddrFromFile(ethSwapContractAddrFile) + ContractAddresses[0][dex.Simnet] = getContractAddrFromFile(ethSwapContractAddrFileV0) + ContractAddresses[1][dex.Simnet] = getContractAddrFromFile(ethSwapContractAddrFileV1) token := Tokens[testTokenID].NetTokens[dex.Simnet] - token.SwapContracts[0].Address = getContractAddrFromFile(tokenSwapContractAddrFile) + token.SwapContracts[0].Address = getContractAddrFromFile(tokenSwapContractAddrFileV0) + token.SwapContracts[1].Address = getContractAddrFromFile(tokenSwapContractAddrFileV1) token.Address = getContractAddrFromFile(testTokenContractAddrFile) } diff --git a/dex/networks/eth/txdata.go b/dex/networks/eth/txdata.go index 0af8d861f4..7d3007a236 100644 --- a/dex/networks/eth/txdata.go +++ b/dex/networks/eth/txdata.go @@ -12,47 +12,16 @@ import ( "time" swapv0 "decred.org/dcrdex/dex/networks/eth/contracts/v0" + swapv1 "decred.org/dcrdex/dex/networks/eth/contracts/v1" "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" ) -// ParseInitiateData parses the calldata used to call the initiate function of a -// specific version of the swap contract. It returns the the list of initiations -// done in the call and errors if the call data does not call initiate initiate -// with expected argument types. -func ParseInitiateData(calldata []byte, contractVersion uint32) (map[[SecretHashSize]byte]*Initiation, error) { - txDataHandler, ok := txDataHandlers[contractVersion] - if !ok { - return nil, fmt.Errorf("contract version %v does not exist", contractVersion) - } - - return txDataHandler.parseInitiateData(calldata) -} - -// ParseRedeemData parses the calldata used to call the redeem function of a -// specific version of the swap contract. It returns the the list of redemptions -// done in the call and errors if the call data does not call redeem with expected -// argument types. -func ParseRedeemData(calldata []byte, contractVersion uint32) (map[[SecretHashSize]byte]*Redemption, error) { - txDataHandler, ok := txDataHandlers[contractVersion] - if !ok { - return nil, fmt.Errorf("contract version %v does not exist", contractVersion) - } - - return txDataHandler.parseRedeemData(calldata) -} - -// ParseRefundData parses the calldata used to call the refund function of a -// specific version of the swap contract. It returns the secret hash and errors -// if the call data does not call refund with expected argument types. -func ParseRefundData(calldata []byte, contractVersion uint32) ([SecretHashSize]byte, error) { - txDataHandler, ok := txDataHandlers[contractVersion] - if !ok { - return [32]byte{}, fmt.Errorf("contract version %v does not exist", contractVersion) - } - - return txDataHandler.parseRefundData(calldata) -} +const ( + InitiateMethodName = "initiate" + RedeemMethodName = "redeem" + RefundMethodName = "refund" +) // ABIs maps each swap contract's version to that version's parsed ABI. var ABIs = initAbis() @@ -60,45 +29,31 @@ var ABIs = initAbis() func initAbis() map[uint32]*abi.ABI { v0ABI, err := abi.JSON(strings.NewReader(swapv0.ETHSwapABI)) if err != nil { - panic(fmt.Sprintf("failed to parse abi: %v", err)) + panic(fmt.Sprintf("failed to parse v0 abi: %v", err)) } - return map[uint32]*abi.ABI{ - 0: &v0ABI, + v1ABI, err := abi.JSON(strings.NewReader(swapv1.ETHSwapABI)) + if err != nil { + panic(fmt.Sprintf("failed to parse v1 abi: %v", err)) } -} - -type txDataHandler interface { - parseInitiateData([]byte) (map[[SecretHashSize]byte]*Initiation, error) - parseRedeemData([]byte) (map[[SecretHashSize]byte]*Redemption, error) - parseRefundData([]byte) ([32]byte, error) -} - -var txDataHandlers = map[uint32]txDataHandler{ - 0: newTxDataV0(), -} -type txDataHandlerV0 struct { - initiateFuncName string - redeemFuncName string - refundFuncName string -} - -func newTxDataV0() *txDataHandlerV0 { - return &txDataHandlerV0{ - initiateFuncName: "initiate", - redeemFuncName: "redeem", - refundFuncName: "refund", + return map[uint32]*abi.ABI{ + 0: &v0ABI, + 1: &v1ABI, } } -func (t *txDataHandlerV0) parseInitiateData(calldata []byte) (map[[SecretHashSize]byte]*Initiation, error) { +// ParseInitiateData parses the calldata used to call the initiate function of a +// specific version of the swap contract. It returns the list of initiations +// done in the call and errors if the call data does not call initiate with +// expected argument types. +func ParseInitiateDataV0(calldata []byte) (map[[SecretHashSize]byte]*Initiation, error) { decoded, err := ParseCallData(calldata, ABIs[0]) if err != nil { return nil, fmt.Errorf("unable to parse call data: %v", err) } - if decoded.Name != t.initiateFuncName { - return nil, fmt.Errorf("expected %v function but got %v", t.initiateFuncName, decoded.Name) + if decoded.Name != InitiateMethodName { + return nil, fmt.Errorf("expected %v function but got %v", InitiateMethodName, decoded.Name) } args := decoded.inputs // Any difference in number of args and types than what we expect @@ -138,13 +93,17 @@ func (t *txDataHandlerV0) parseInitiateData(calldata []byte) (map[[SecretHashSiz return toReturn, nil } -func (t *txDataHandlerV0) parseRedeemData(calldata []byte) (map[[SecretHashSize]byte]*Redemption, error) { +// ParseRedeemData parses the calldata used to call the redeem function of a +// specific version of the swap contract. It returns the the list of redemptions +// done in the call and errors if the call data does not call redeem with expected +// argument types. +func ParseRedeemDataV0(calldata []byte) (map[[SecretHashSize]byte]*Redemption, error) { decoded, err := ParseCallData(calldata, ABIs[0]) if err != nil { return nil, fmt.Errorf("unable to parse call data: %v", err) } - if decoded.Name != t.redeemFuncName { - return nil, fmt.Errorf("expected %v function but got %v", t.redeemFuncName, decoded.Name) + if decoded.Name != RedeemMethodName { + return nil, fmt.Errorf("expected %v function but got %v", RedeemMethodName, decoded.Name) } args := decoded.inputs // Any difference in number of args and types than what we expect @@ -180,15 +139,18 @@ func (t *txDataHandlerV0) parseRedeemData(calldata []byte) (map[[SecretHashSize] return toReturn, nil } -func (t *txDataHandlerV0) parseRefundData(calldata []byte) ([32]byte, error) { +// ParseRefundData parses the calldata used to call the refund function of a +// specific version of the swap contract. It returns the secret hash and errors +// if the call data does not call refund with expected argument types. +func ParseRefundDataV0(calldata []byte) ([32]byte, error) { var secretHash [32]byte decoded, err := ParseCallData(calldata, ABIs[0]) if err != nil { return secretHash, fmt.Errorf("unable to parse call data: %v", err) } - if decoded.Name != t.refundFuncName { - return secretHash, fmt.Errorf("expected %v function but got %v", t.refundFuncName, decoded.Name) + if decoded.Name != RefundMethodName { + return secretHash, fmt.Errorf("expected %v function but got %v", RefundMethodName, decoded.Name) } args := decoded.inputs // Any difference in number of args and types than what we expect @@ -206,3 +168,148 @@ func (t *txDataHandlerV0) parseRefundData(calldata []byte) ([32]byte, error) { return secretHash, nil } + +type RedemptionV1 struct { + Secret [32]byte + Contract *SwapVector +} + +func ParseInitiateDataV1(calldata []byte) (map[[SecretHashSize]byte]*SwapVector, error) { + decoded, err := ParseCallData(calldata, ABIs[1]) + if err != nil { + return nil, fmt.Errorf("unable to parse call data: %v", err) + } + if decoded.Name != InitiateMethodName { + return nil, fmt.Errorf("expected %v function but got %v", InitiateMethodName, decoded.Name) + } + args := decoded.inputs + // Any difference in number of args and types than what we expect + // should be caught by ParseCallData, but checking again anyway. + // + // TODO: If any of the checks prove redundant, remove them. + const numArgs = 1 + if len(args) != numArgs { + return nil, fmt.Errorf("expected %v input args but got %v", numArgs, len(args)) + } + initiations, ok := args[0].value.([]struct { + SecretHash [32]byte `json:"secretHash"` + Initiator common.Address `json:"initiator"` + RefundTimestamp uint64 `json:"refundTimestamp"` + Participant common.Address `json:"participant"` + Value uint64 `json:"value"` + }) + if !ok { + return nil, fmt.Errorf("expected first arg of type []swapv1.ETHSwapContract but got %T", args[0].value) + } + + // This is done for the compiler to ensure that the type defined above and + // swapv0.ETHSwapInitiation are the same, other than the tags. + if len(initiations) > 0 { + _ = swapv1.ETHSwapVector(initiations[0]) + } + + toReturn := make(map[[SecretHashSize]byte]*SwapVector, len(initiations)) + for _, init := range initiations { + toReturn[init.SecretHash] = &SwapVector{ + From: init.Initiator, + To: init.Participant, + Value: init.Value, + SecretHash: init.SecretHash, + LockTime: init.RefundTimestamp, + } + } + + return toReturn, nil +} + +func ParseRedeemDataV1(calldata []byte) (map[[SecretHashSize]byte]*RedemptionV1, error) { + decoded, err := ParseCallData(calldata, ABIs[1]) + if err != nil { + return nil, fmt.Errorf("unable to parse call data: %v", err) + } + if decoded.Name != RedeemMethodName { + return nil, fmt.Errorf("expected %v function but got %v", RedeemMethodName, decoded.Name) + } + args := decoded.inputs + // Any difference in number of args and types than what we expect + // should be caught by parseCallData, but checking again anyway. + // + // TODO: If any of the checks prove redundant, remove them. + const numArgs = 1 + if len(args) != numArgs { + return nil, fmt.Errorf("expected %v redeem args but got %v", numArgs, len(args)) + } + + redemptions, ok := args[0].value.([]struct { + V struct { + SecretHash [32]uint8 `json:"secretHash"` + Initiator common.Address `json:"initiator"` + RefundTimestamp uint64 `json:"refundTimestamp"` + Participant common.Address `json:"participant"` + Value uint64 `json:"value"` + } `json:"v"` + Secret [32]uint8 `json:"secret"` + }) + if !ok { + return nil, fmt.Errorf("expected first arg of type []swapv1.ETHSwapRedemption but got %T", args[0].value) + } + + // This is done for the compiler to ensure that the type defined above and + // swapv0.ETHSwapRedemption are the same, other than the tags. + if len(redemptions) > 0 { + // Why can't I do ETHSwapRedemption directly? + _ = swapv1.ETHSwapVector(redemptions[0].V) + } + toReturn := make(map[[SecretHashSize]byte]*RedemptionV1, len(redemptions)) + for _, r := range redemptions { + toReturn[r.V.SecretHash] = &RedemptionV1{ + Contract: &SwapVector{ + From: r.V.Initiator, + To: r.V.Participant, + Value: r.V.Value, + SecretHash: r.V.SecretHash, + LockTime: r.V.RefundTimestamp, + }, + Secret: r.Secret, + } + } + + return toReturn, nil +} + +func ParseRefundDataV1(calldata []byte) (*SwapVector, error) { + decoded, err := ParseCallData(calldata, ABIs[1]) + if err != nil { + return nil, fmt.Errorf("unable to parse call data: %v", err) + } + if decoded.Name != RefundMethodName { + return nil, fmt.Errorf("expected %v function but got %v", RefundMethodName, decoded.Name) + } + args := decoded.inputs + // Any difference in number of args and types than what we expect + // should be caught by parseCallData, but checking again anyway. + // + // TODO: If any of the checks prove redundant, remove them. + const numArgs = 1 + if len(args) != numArgs { + return nil, fmt.Errorf("expected %v redeem args but got %v", numArgs, len(args)) + } + contract, ok := args[0].value.(struct { + SecretHash [32]byte `json:"secretHash"` + Initiator common.Address `json:"initiator"` + RefundTimestamp uint64 `json:"refundTimestamp"` + Participant common.Address `json:"participant"` + Value uint64 `json:"value"` + }) + if !ok { + return nil, fmt.Errorf("expected first arg of type [32]byte but got %T", args[0].value) + } + + return &SwapVector{ + From: contract.Initiator, + To: contract.Participant, + Value: contract.Value, + LockTime: contract.RefundTimestamp, + SecretHash: contract.SecretHash, + }, nil +} diff --git a/dex/networks/eth/txdata_test.go b/dex/networks/eth/txdata_test.go index 30c3eca486..816678786d 100644 --- a/dex/networks/eth/txdata_test.go +++ b/dex/networks/eth/txdata_test.go @@ -126,7 +126,7 @@ func TestParseInitiateDataV0(t *testing.T) { }} for _, test := range tests { - parsedInitiations, err := ParseInitiateData(test.calldata, 0) + parsedInitiations, err := ParseInitiateDataV0(test.calldata) if test.wantErr { if err == nil { t.Fatalf("expected error for test %q", test.name) @@ -219,7 +219,7 @@ func TestParseRedeemDataV0(t *testing.T) { }} for _, test := range tests { - parsedRedemptions, err := ParseRedeemData(test.calldata, 0) + parsedRedemptions, err := ParseRedeemDataV0(test.calldata) if test.wantErr { if err == nil { t.Fatalf("expected error for test %q", test.name) @@ -284,7 +284,7 @@ func TestParseRefundDataV0(t *testing.T) { }} for _, test := range tests { - parsedSecretHash, err := ParseRefundData(test.calldata, 0) + parsedSecretHash, err := ParseRefundDataV0(test.calldata) if test.wantErr { if err == nil { t.Fatalf("expected error for test %q", test.name) diff --git a/dex/testing/eth/create-node.sh b/dex/testing/eth/create-node.sh index 34bf35247f..55fab1743d 100755 --- a/dex/testing/eth/create-node.sh +++ b/dex/testing/eth/create-node.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash # Script for creating eth nodes. -set -ex +set -e # The following are required script arguments. TMUX_WIN_ID=$1 diff --git a/dex/testing/eth/harness.sh b/dex/testing/eth/harness.sh index b315bd8f30..668505e847 100755 --- a/dex/testing/eth/harness.sh +++ b/dex/testing/eth/harness.sh @@ -51,9 +51,12 @@ export DELTA_WS_PORT="38557" TESTING_ADDRESS="b6de8bb5ed28e6be6d671975cad20c03931be981" SIMNET_TOKEN_ADDRESS="946dfaB1AD7caCFeF77dE70ea68819a30acD4577" ETH_SWAP_V0="608060405234801561001057600080fd5b50610cb6806100206000396000f3fe6080604052600436106100705760003560e01c8063bfd2fd971161004e578063bfd2fd97146100e0578063d0f761c014610110578063eb84e7f214610130578063f4fd17f9146101ac57600080fd5b80637249fbb61461007557806376467cbd14610097578063a8793f94146100cd575b600080fd5b34801561008157600080fd5b50610095610090366004610980565b6101cc565b005b3480156100a357600080fd5b506100b76100b2366004610980565b610304565b6040516100c491906109d1565b60405180910390f35b6100956100db366004610a36565b6103df565b3480156100ec57600080fd5b506101006100fb366004610aab565b6105d6565b60405190151581526020016100c4565b34801561011c57600080fd5b5061010061012b366004610980565b6106ad565b34801561013c57600080fd5b5061019961014b366004610980565b60006020819052908152604090208054600182015460028301546003840154600485015460059095015493949293919290916001600160a01b0391821691811690600160a01b900460ff1687565b6040516100c49796959493929190610acd565b3480156101b857600080fd5b506100956101c7366004610b19565b6106f2565b3233146101f45760405162461bcd60e51b81526004016101eb90610b7c565b60405180910390fd5b6101fd816106ad565b61023a5760405162461bcd60e51b815260206004820152600e60248201526d6e6f7420726566756e6461626c6560901b60448201526064016101eb565b60008181526020819052604080822060058101805460ff60a01b1916600360a01b1790556004810154600182015492519193926001600160a01b03909116918381818185875af1925050503d80600081146102b1576040519150601f19603f3d011682016040523d82523d6000602084013e6102b6565b606091505b50909150506001811515146102ff5760405162461bcd60e51b815260206004820152600f60248201526e1d1c985b9cd9995c8819985a5b1959608a1b60448201526064016101eb565b505050565b6103416040805160e081018252600080825260208201819052918101829052606081018290526080810182905260a081018290529060c082015290565b60008281526020818152604091829020825160e08101845281548152600182015492810192909252600281015492820192909252600380830154606083015260048301546001600160a01b039081166080840152600584015490811660a084015291929160c0840191600160a01b90910460ff16908111156103c5576103c5610999565b60038111156103d6576103d6610999565b90525092915050565b3233146103fe5760405162461bcd60e51b81526004016101eb90610b7c565b6000805b8281101561059c573684848381811061041d5761041d610ba6565b90506080020190506000806000836020013581526020019081526020016000209050600082606001351161047b5760405162461bcd60e51b81526020600482015260056024820152640c081d985b60da1b60448201526064016101eb565b81356104bd5760405162461bcd60e51b815260206004820152601160248201527003020726566756e6454696d657374616d7607c1b60448201526064016101eb565b60006005820154600160a01b900460ff1660038111156104df576104df610999565b146105175760405162461bcd60e51b8152602060048201526008602482015267064757020737761760c41b60448201526064016101eb565b436002820155813560038201556004810180546001600160a01b031916331790556105486060830160408401610bbc565b6005820180546060850135600185018190556001600160a01b03939093166001600160a81b031990911617600160a01b1790556105859085610bfb565b93505050808061059490610c13565b915050610402565b503481146102ff5760405162461bcd60e51b8152602060048201526007602482015266189859081d985b60ca1b60448201526064016101eb565b60006001600084815260208190526040902060050154600160a01b900460ff16600381111561060757610607610999565b14801561062d57506000838152602081905260409020600501546001600160a01b031633145b80156106a657508260028360405160200161064a91815260200190565b60408051601f198184030181529082905261066491610c2c565b602060405180830381855afa158015610681573d6000803e3d6000fd5b5050506040513d601f19601f820116820180604052508101906106a49190610c67565b145b9392505050565b600081815260208190526040812060016005820154600160a01b900460ff1660038111156106dd576106dd610999565b1480156106a657506003015442101592915050565b3233146107115760405162461bcd60e51b81526004016101eb90610b7c565b6000805b828110156108e9573684848381811061073057610730610ba6565b6020604091820293909301838101356000908152938490529220919250600190506005820154600160a01b900460ff16600381111561077157610771610999565b146107aa5760405162461bcd60e51b815260206004820152600960248201526862616420737461746560b81b60448201526064016101eb565b60058101546001600160a01b031633146107f85760405162461bcd60e51b815260206004820152600f60248201526e189859081c185c9d1a58da5c185b9d608a1b60448201526064016101eb565b81602001356002836000013560405160200161081691815260200190565b60408051601f198184030181529082905261083091610c2c565b602060405180830381855afa15801561084d573d6000803e3d6000fd5b5050506040513d601f19601f820116820180604052508101906108709190610c67565b146108aa5760405162461bcd60e51b815260206004820152600a602482015269189859081cd958dc995d60b21b60448201526064016101eb565b60058101805460ff60a01b1916600160a11b1790558135815560018101546108d29085610bfb565b9350505080806108e190610c13565b915050610715565b50604051600090339083908381818185875af1925050503d806000811461092c576040519150601f19603f3d011682016040523d82523d6000602084013e610931565b606091505b509091505060018115151461097a5760405162461bcd60e51b815260206004820152600f60248201526e1d1c985b9cd9995c8819985a5b1959608a1b60448201526064016101eb565b50505050565b60006020828403121561099257600080fd5b5035919050565b634e487b7160e01b600052602160045260246000fd5b600481106109cd57634e487b7160e01b600052602160045260246000fd5b9052565b600060e08201905082518252602083015160208301526040830151604083015260608301516060830152608083015160018060a01b0380821660808501528060a08601511660a0850152505060c0830151610a2f60c08401826109af565b5092915050565b60008060208385031215610a4957600080fd5b823567ffffffffffffffff80821115610a6157600080fd5b818501915085601f830112610a7557600080fd5b813581811115610a8457600080fd5b8660208260071b8501011115610a9957600080fd5b60209290920196919550909350505050565b60008060408385031215610abe57600080fd5b50508035926020909101359150565b8781526020810187905260408101869052606081018590526001600160a01b038481166080830152831660a082015260e08101610b0d60c08301846109af565b98975050505050505050565b60008060208385031215610b2c57600080fd5b823567ffffffffffffffff80821115610b4457600080fd5b818501915085601f830112610b5857600080fd5b813581811115610b6757600080fd5b8660208260061b8501011115610a9957600080fd5b60208082526010908201526f39b2b73232b910109e9037b934b3b4b760811b604082015260600190565b634e487b7160e01b600052603260045260246000fd5b600060208284031215610bce57600080fd5b81356001600160a01b03811681146106a657600080fd5b634e487b7160e01b600052601160045260246000fd5b60008219821115610c0e57610c0e610be5565b500190565b600060018201610c2557610c25610be5565b5060010190565b6000825160005b81811015610c4d5760208186018101518583015201610c33565b81811115610c5c576000828501525b509190910192915050565b600060208284031215610c7957600080fd5b505191905056fea26469706673582212209c8d8d137b7639af24fe8215dd5976b16323176cb4b9be076c90eeb32eaf744664736f6c634300080f0033" -ERC20_SWAP_V0="60a060405234801561001057600080fd5b50604051610fa7380380610fa783398101604081905261002f91610040565b6001600160a01b0316608052610070565b60006020828403121561005257600080fd5b81516001600160a01b038116811461006957600080fd5b9392505050565b608051610f0861009f6000396000818160d0015281816102b9015281816106890152610adb0152610f086000f3fe608060405234801561001057600080fd5b50600436106100885760003560e01c8063bfd2fd971161005b578063bfd2fd971461011d578063d0f761c014610140578063eb84e7f214610153578063f4fd17f9146101c257600080fd5b80637249fbb61461008d57806376467cbd146100a25780638c8e8fee146100cb578063a8793f941461010a575b600080fd5b6100a061009b366004610bb0565b6101d5565b005b6100b56100b0366004610bb0565b610394565b6040516100c29190610c01565b60405180910390f35b6100f27f000000000000000000000000000000000000000000000000000000000000000081565b6040516001600160a01b0390911681526020016100c2565b6100a0610118366004610c66565b61046f565b61013061012b366004610cdb565b61076a565b60405190151581526020016100c2565b61013061014e366004610bb0565b610834565b6101af610161366004610bb0565b60006020819052908152604090208054600182015460028301546003840154600485015460059095015493949293919290916001600160a01b0391821691811690600160a01b900460ff1687565b6040516100c29796959493929190610cfd565b6100a06101d0366004610d49565b610894565b3233146101fd5760405162461bcd60e51b81526004016101f490610dac565b60405180910390fd5b61020681610834565b6102435760405162461bcd60e51b815260206004820152600e60248201526d6e6f7420726566756e6461626c6560901b60448201526064016101f4565b60008181526020818152604080832060058101805460ff60a01b1916600360a01b17905560018101548251336024820152604480820192909252835180820390920182526064018352928301805163a9059cbb60e01b6001600160e01b0390911617905290519092916060916001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016916102e391610dd6565b6000604051808303816000865af19150503d8060008114610320576040519150601f19603f3d011682016040523d82523d6000602084013e610325565b606091505b5090925090508180156103505750805115806103505750808060200190518101906103509190610e11565b61038e5760405162461bcd60e51b815260206004820152600f60248201526e1d1c985b9cd9995c8819985a5b1959608a1b60448201526064016101f4565b50505050565b6103d16040805160e081018252600080825260208201819052918101829052606081018290526080810182905260a081018290529060c082015290565b60008281526020818152604091829020825160e08101845281548152600182015492810192909252600281015492820192909252600380830154606083015260048301546001600160a01b039081166080840152600584015490811660a084015291929160c0840191600160a01b90910460ff169081111561045557610455610bc9565b600381111561046657610466610bc9565b90525092915050565b32331461048e5760405162461bcd60e51b81526004016101f490610dac565b6000805b8281101561063357368484838181106104ad576104ad610e33565b90506080020190506000806000836020013581526020019081526020016000209050600082606001351161050b5760405162461bcd60e51b81526020600482015260056024820152640c081d985b60da1b60448201526064016101f4565b813561054d5760405162461bcd60e51b815260206004820152601160248201527003020726566756e6454696d657374616d7607c1b60448201526064016101f4565b60006005820154600160a01b900460ff16600381111561056f5761056f610bc9565b146105ae5760405162461bcd60e51b815260206004820152600f60248201526e0c8eae040e6cac6e4cae840d0c2e6d608b1b60448201526064016101f4565b436002820155813560038201556004810180546001600160a01b031916331790556105df6060830160408401610e49565b6005820180546060850135600185018190556001600160a01b03939093166001600160a81b031990911617600160a01b17905561061c9085610e88565b93505050808061062b90610ea0565b915050610492565b5060408051336024820152306044820152606480820184905282518083039091018152608490910182526020810180516001600160e01b03166323b872dd60e01b17905290516000916060916001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016916106b391610dd6565b6000604051808303816000865af19150503d80600081146106f0576040519150601f19603f3d011682016040523d82523d6000602084013e6106f5565b606091505b5090925090508180156107205750805115806107205750808060200190518101906107209190610e11565b6107635760405162461bcd60e51b81526020600482015260146024820152731d1c985b9cd9995c88199c9bdb4819985a5b195960621b60448201526064016101f4565b5050505050565b600082815260208190526040812060016005820154600160a01b900460ff16600381111561079a5761079a610bc9565b1480156107b3575060058101546001600160a01b031633145b801561082c5750836002846040516020016107d091815260200190565b60408051601f19818403018152908290526107ea91610dd6565b602060405180830381855afa158015610807573d6000803e3d6000fd5b5050506040513d601f19601f8201168201806040525081019061082a9190610eb9565b145b949350505050565b600081815260208190526040812060016005820154600160a01b900460ff16600381111561086457610864610bc9565b14801561087d575060048101546001600160a01b031633145b801561088d575080600301544210155b9392505050565b3233146108b35760405162461bcd60e51b81526004016101f490610dac565b6000805b82811015610a8b57368484838181106108d2576108d2610e33565b6020604091820293909301838101356000908152938490529220919250600190506005820154600160a01b900460ff16600381111561091357610913610bc9565b1461094c5760405162461bcd60e51b815260206004820152600960248201526862616420737461746560b81b60448201526064016101f4565b60058101546001600160a01b0316331461099a5760405162461bcd60e51b815260206004820152600f60248201526e189859081c185c9d1a58da5c185b9d608a1b60448201526064016101f4565b8160200135600283600001356040516020016109b891815260200190565b60408051601f19818403018152908290526109d291610dd6565b602060405180830381855afa1580156109ef573d6000803e3d6000fd5b5050506040513d601f19601f82011682018060405250810190610a129190610eb9565b14610a4c5760405162461bcd60e51b815260206004820152600a602482015269189859081cd958dc995d60b21b60448201526064016101f4565b60058101805460ff60a01b1916600160a11b179055813581556001810154610a749085610e88565b935050508080610a8390610ea0565b9150506108b7565b5060408051336024820152604480820184905282518083039091018152606490910182526020810180516001600160e01b031663a9059cbb60e01b17905290516000916060916001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001691610b0591610dd6565b6000604051808303816000865af19150503d8060008114610b42576040519150601f19603f3d011682016040523d82523d6000602084013e610b47565b606091505b509092509050818015610b72575080511580610b72575080806020019051810190610b729190610e11565b6107635760405162461bcd60e51b815260206004820152600f60248201526e1d1c985b9cd9995c8819985a5b1959608a1b60448201526064016101f4565b600060208284031215610bc257600080fd5b5035919050565b634e487b7160e01b600052602160045260246000fd5b60048110610bfd57634e487b7160e01b600052602160045260246000fd5b9052565b600060e08201905082518252602083015160208301526040830151604083015260608301516060830152608083015160018060a01b0380821660808501528060a08601511660a0850152505060c0830151610c5f60c0840182610bdf565b5092915050565b60008060208385031215610c7957600080fd5b823567ffffffffffffffff80821115610c9157600080fd5b818501915085601f830112610ca557600080fd5b813581811115610cb457600080fd5b8660208260071b8501011115610cc957600080fd5b60209290920196919550909350505050565b60008060408385031215610cee57600080fd5b50508035926020909101359150565b8781526020810187905260408101869052606081018590526001600160a01b038481166080830152831660a082015260e08101610d3d60c0830184610bdf565b98975050505050505050565b60008060208385031215610d5c57600080fd5b823567ffffffffffffffff80821115610d7457600080fd5b818501915085601f830112610d8857600080fd5b813581811115610d9757600080fd5b8660208260061b8501011115610cc957600080fd5b60208082526010908201526f39b2b73232b910109e9037b934b3b4b760811b604082015260600190565b6000825160005b81811015610df75760208186018101518583015201610ddd565b81811115610e06576000828501525b509190910192915050565b600060208284031215610e2357600080fd5b8151801515811461088d57600080fd5b634e487b7160e01b600052603260045260246000fd5b600060208284031215610e5b57600080fd5b81356001600160a01b038116811461088d57600080fd5b634e487b7160e01b600052601160045260246000fd5b60008219821115610e9b57610e9b610e72565b500190565b600060018201610eb257610eb2610e72565b5060010190565b600060208284031215610ecb57600080fd5b505191905056fea26469706673582212203e5af33d9672cc61834e91e1c391f408502e89feacb59762150c3e8ad4b8eb8764736f6c634300080f0033" +ERC20_SWAP_V0="60a060405234801561001057600080fd5b50604051610fa7380380610fa783398101604081905261002f91610040565b6001600160a01b0316608052610070565b60006020828403121561005257600080fd5b81516001600160a01b038116811461006957600080fd5b9392505050565b608051610f0861009f6000396000818160d0015281816102b9015281816106890152610adb0152610f086000f3fe608060405234801561001057600080fd5b50600436106100885760003560e01c8063bfd2fd971161005b578063bfd2fd971461011d578063d0f761c014610140578063eb84e7f214610153578063f4fd17f9146101c257600080fd5b80637249fbb61461008d57806376467cbd146100a25780638c8e8fee146100cb578063a8793f941461010a575b600080fd5b6100a061009b366004610bb0565b6101d5565b005b6100b56100b0366004610bb0565b610394565b6040516100c29190610c01565b60405180910390f35b6100f27f000000000000000000000000000000000000000000000000000000000000000081565b6040516001600160a01b0390911681526020016100c2565b6100a0610118366004610c66565b61046f565b61013061012b366004610cdb565b61076a565b60405190151581526020016100c2565b61013061014e366004610bb0565b610834565b6101af610161366004610bb0565b60006020819052908152604090208054600182015460028301546003840154600485015460059095015493949293919290916001600160a01b0391821691811690600160a01b900460ff1687565b6040516100c29796959493929190610cfd565b6100a06101d0366004610d49565b610894565b3233146101fd5760405162461bcd60e51b81526004016101f490610dac565b60405180910390fd5b61020681610834565b6102435760405162461bcd60e51b815260206004820152600e60248201526d6e6f7420726566756e6461626c6560901b60448201526064016101f4565b60008181526020818152604080832060058101805460ff60a01b1916600360a01b17905560018101548251336024820152604480820192909252835180820390920182526064018352928301805163a9059cbb60e01b6001600160e01b0390911617905290519092916060916001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016916102e391610dd6565b6000604051808303816000865af19150503d8060008114610320576040519150601f19603f3d011682016040523d82523d6000602084013e610325565b606091505b5090925090508180156103505750805115806103505750808060200190518101906103509190610e11565b61038e5760405162461bcd60e51b815260206004820152600f60248201526e1d1c985b9cd9995c8819985a5b1959608a1b60448201526064016101f4565b50505050565b6103d16040805160e081018252600080825260208201819052918101829052606081018290526080810182905260a081018290529060c082015290565b60008281526020818152604091829020825160e08101845281548152600182015492810192909252600281015492820192909252600380830154606083015260048301546001600160a01b039081166080840152600584015490811660a084015291929160c0840191600160a01b90910460ff169081111561045557610455610bc9565b600381111561046657610466610bc9565b90525092915050565b32331461048e5760405162461bcd60e51b81526004016101f490610dac565b6000805b8281101561063357368484838181106104ad576104ad610e33565b90506080020190506000806000836020013581526020019081526020016000209050600082606001351161050b5760405162461bcd60e51b81526020600482015260056024820152640c081d985b60da1b60448201526064016101f4565b813561054d5760405162461bcd60e51b815260206004820152601160248201527003020726566756e6454696d657374616d7607c1b60448201526064016101f4565b60006005820154600160a01b900460ff16600381111561056f5761056f610bc9565b146105ae5760405162461bcd60e51b815260206004820152600f60248201526e0c8eae040e6cac6e4cae840d0c2e6d608b1b60448201526064016101f4565b436002820155813560038201556004810180546001600160a01b031916331790556105df6060830160408401610e49565b6005820180546060850135600185018190556001600160a01b03939093166001600160a81b031990911617600160a01b17905561061c9085610e88565b93505050808061062b90610ea0565b915050610492565b5060408051336024820152306044820152606480820184905282518083039091018152608490910182526020810180516001600160e01b03166323b872dd60e01b17905290516000916060916001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016916106b391610dd6565b6000604051808303816000865af19150503d80600081146106f0576040519150601f19603f3d011682016040523d82523d6000602084013e6106f5565b606091505b5090925090508180156107205750805115806107205750808060200190518101906107209190610e11565b6107635760405162461bcd60e51b81526020600482015260146024820152731d1c985b9cd9995c88199c9bdb4819985a5b195960621b60448201526064016101f4565b5050505050565b600082815260208190526040812060016005820154600160a01b900460ff16600381111561079a5761079a610bc9565b1480156107b3575060058101546001600160a01b031633145b801561082c5750836002846040516020016107d091815260200190565b60408051601f19818403018152908290526107ea91610dd6565b602060405180830381855afa158015610807573d6000803e3d6000fd5b5050506040513d601f19601f8201168201806040525081019061082a9190610eb9565b145b949350505050565b600081815260208190526040812060016005820154600160a01b900460ff16600381111561086457610864610bc9565b14801561087d575060048101546001600160a01b031633145b801561088d575080600301544210155b9392505050565b3233146108b35760405162461bcd60e51b81526004016101f490610dac565b6000805b82811015610a8b57368484838181106108d2576108d2610e33565b6020604091820293909301838101356000908152938490529220919250600190506005820154600160a01b900460ff16600381111561091357610913610bc9565b1461094c5760405162461bcd60e51b815260206004820152600960248201526862616420737461746560b81b60448201526064016101f4565b60058101546001600160a01b0316331461099a5760405162461bcd60e51b815260206004820152600f60248201526e189859081c185c9d1a58da5c185b9d608a1b60448201526064016101f4565b8160200135600283600001356040516020016109b891815260200190565b60408051601f19818403018152908290526109d291610dd6565b602060405180830381855afa1580156109ef573d6000803e3d6000fd5b5050506040513d601f19601f82011682018060405250810190610a129190610eb9565b14610a4c5760405162461bcd60e51b815260206004820152600a602482015269189859081cd958dc995d60b21b60448201526064016101f4565b60058101805460ff60a01b1916600160a11b179055813581556001810154610a749085610e88565b935050508080610a8390610ea0565b9150506108b7565b5060408051336024820152604480820184905282518083039091018152606490910182526020810180516001600160e01b031663a9059cbb60e01b17905290516000916060916001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001691610b0591610dd6565b6000604051808303816000865af19150503d8060008114610b42576040519150601f19603f3d011682016040523d82523d6000602084013e610b47565b606091505b509092509050818015610b72575080511580610b72575080806020019051810190610b729190610e11565b6107635760405162461bcd60e51b815260206004820152600f60248201526e1d1c985b9cd9995c8819985a5b1959608a1b60448201526064016101f4565b600060208284031215610bc257600080fd5b5035919050565b634e487b7160e01b600052602160045260246000fd5b60048110610bfd57634e487b7160e01b600052602160045260246000fd5b9052565b600060e08201905082518252602083015160208301526040830151604083015260608301516060830152608083015160018060a01b0380821660808501528060a08601511660a0850152505060c0830151610c5f60c0840182610bdf565b5092915050565b60008060208385031215610c7957600080fd5b823567ffffffffffffffff80821115610c9157600080fd5b818501915085601f830112610ca557600080fd5b813581811115610cb457600080fd5b8660208260071b8501011115610cc957600080fd5b60209290920196919550909350505050565b60008060408385031215610cee57600080fd5b50508035926020909101359150565b8781526020810187905260408101869052606081018590526001600160a01b038481166080830152831660a082015260e08101610d3d60c0830184610bdf565b98975050505050505050565b60008060208385031215610d5c57600080fd5b823567ffffffffffffffff80821115610d7457600080fd5b818501915085601f830112610d8857600080fd5b813581811115610d9757600080fd5b8660208260061b8501011115610cc957600080fd5b60208082526010908201526f39b2b73232b910109e9037b934b3b4b760811b604082015260600190565b6000825160005b81811015610df75760208186018101518583015201610ddd565b81811115610e06576000828501525b509190910192915050565b600060208284031215610e2357600080fd5b8151801515811461088d57600080fd5b634e487b7160e01b600052603260045260246000fd5b600060208284031215610e5b57600080fd5b81356001600160a01b038116811461088d57600080fd5b634e487b7160e01b600052601160045260246000fd5b60008219821115610e9b57610e9b610e72565b500190565b600060018201610eb257610eb2610e72565b5060010190565b600060208284031215610ecb57600080fd5b505191905056fea26469706673582212205ce0bdeea5c2627b54456a4cd32401114ec803a0ad67156f74d2652004edeff064736f6c634300080f0033" TEST_TOKEN="608060405234801561001057600080fd5b506040805180820190915260098152682a32b9ba2a37b5b2b760b91b602082015260039061003e90826101d1565b506040805180820190915260038152621514d560ea1b602082015260049061006690826101d1565b506909513ea9de0243800000600255600060208190526902544faa778090e000007f7d4921c2bc32c0110a31d16f4efb43c7a1228f1df7af765f608241dee5c62ebc8190557f59603491850c7d11499afe95b334ccfd92b48b36a15df31ef59ff5813fe370828190557f963f2e057cac0b71a4b8cff76a0e66200ffc6cc5498c1198bc1df3cb2bf751dc819055731d4f2ee206474b136af4868b887c7b166693c1949091527fbc10d5a0a531ecf97938db2df6f3f5b59678ae655bd09be1d358f605f79153d455610290565b634e487b7160e01b600052604160045260246000fd5b600181811c9082168061015c57607f821691505b60208210810361017c57634e487b7160e01b600052602260045260246000fd5b50919050565b601f8211156101cc57600081815260208120601f850160051c810160208610156101a95750805b601f850160051c820191505b818110156101c8578281556001016101b5565b5050505b505050565b81516001600160401b038111156101ea576101ea610132565b6101fe816101f88454610148565b84610182565b602080601f831160018114610233576000841561021b5750858301515b600019600386901b1c1916600185901b1785556101c8565b600085815260208120601f198616915b8281101561026257888601518255948401946001909101908401610243565b50858210156102805787850151600019600388901b60f8161c191681555b5050505050600190811b01905550565b6107968061029f6000396000f3fe608060405234801561001057600080fd5b506004361061009e5760003560e01c806370a082311161006657806370a08231146101185780638ba4cc3c1461014157806395d89b4114610156578063a9059cbb1461015e578063dd62ed3e1461017157600080fd5b806306fdde03146100a3578063095ea7b3146100c157806318160ddd146100e457806323b872dd146100f6578063313ce56714610109575b600080fd5b6100ab6101aa565b6040516100b891906105d4565b60405180910390f35b6100d46100cf366004610645565b61023c565b60405190151581526020016100b8565b6002545b6040519081526020016100b8565b6100d461010436600461066f565b610252565b604051601281526020016100b8565b6100e86101263660046106ab565b6001600160a01b031660009081526020819052604090205490565b61015461014f366004610645565b610301565b005b6100ab610349565b6100d461016c366004610645565b610358565b6100e861017f3660046106cd565b6001600160a01b03918216600090815260016020908152604080832093909416825291909152205490565b6060600380546101b990610700565b80601f01602080910402602001604051908101604052809291908181526020018280546101e590610700565b80156102325780601f1061020757610100808354040283529160200191610232565b820191906000526020600020905b81548152906001019060200180831161021557829003601f168201915b5050505050905090565b6000610249338484610365565b50600192915050565b600061025f848484610454565b6001600160a01b0384166000908152600160209081526040808320338452909152902054828110156102e95760405162461bcd60e51b815260206004820152602860248201527f45524332303a207472616e7366657220616d6f756e74206578636565647320616044820152676c6c6f77616e636560c01b60648201526084015b60405180910390fd5b6102f68533858403610365565b506001949350505050565b8060026000828254610313919061073a565b90915550506001600160a01b0382166000908152602081905260408120805483929061034090849061073a565b90915550505050565b6060600480546101b990610700565b6000610249338484610454565b6001600160a01b0383166103c75760405162461bcd60e51b8152602060048201526024808201527f45524332303a20617070726f76652066726f6d20746865207a65726f206164646044820152637265737360e01b60648201526084016102e0565b6001600160a01b0382166104285760405162461bcd60e51b815260206004820152602260248201527f45524332303a20617070726f766520746f20746865207a65726f206164647265604482015261737360f01b60648201526084016102e0565b6001600160a01b0392831660009081526001602090815260408083209490951682529290925291902055565b6001600160a01b0383166104b85760405162461bcd60e51b815260206004820152602560248201527f45524332303a207472616e736665722066726f6d20746865207a65726f206164604482015264647265737360d81b60648201526084016102e0565b6001600160a01b03821661051a5760405162461bcd60e51b815260206004820152602360248201527f45524332303a207472616e7366657220746f20746865207a65726f206164647260448201526265737360e81b60648201526084016102e0565b6001600160a01b038316600090815260208190526040902054818110156105925760405162461bcd60e51b815260206004820152602660248201527f45524332303a207472616e7366657220616d6f756e7420657863656564732062604482015265616c616e636560d01b60648201526084016102e0565b6001600160a01b038085166000908152602081905260408082208585039055918516815290812080548492906105c990849061073a565b909155505050505050565b600060208083528351808285015260005b81811015610601578581018301518582016040015282016105e5565b81811115610613576000604083870101525b50601f01601f1916929092016040019392505050565b80356001600160a01b038116811461064057600080fd5b919050565b6000806040838503121561065857600080fd5b61066183610629565b946020939093013593505050565b60008060006060848603121561068457600080fd5b61068d84610629565b925061069b60208501610629565b9150604084013590509250925092565b6000602082840312156106bd57600080fd5b6106c682610629565b9392505050565b600080604083850312156106e057600080fd5b6106e983610629565b91506106f760208401610629565b90509250929050565b600181811c9082168061071457607f821691505b60208210810361073457634e487b7160e01b600052602260045260246000fd5b50919050565b6000821982111561075b57634e487b7160e01b600052601160045260246000fd5b50019056fea26469706673582212201528eada32f708041b3309a694f6ffe74e05ebdb01eafc2fb478927580375b6d64736f6c634300080f0033" +ETH_SWAP_V1="608060405234801561001057600080fd5b50610e8e806100206000396000f3fe60806040526004361061007b5760003560e01c80637802689d1161004e5780637802689d1461011b578063d2544c061461013b578063eb84e7f21461015b578063ed7cbed71461019657600080fd5b8063428b16e11461008057806361a16e33146100a257806364a97bff146100d857806377d7e031146100eb575b600080fd5b34801561008c57600080fd5b506100a061009b366004610b6f565b6101b6565b005b3480156100ae57600080fd5b506100c26100bd366004610be4565b610447565b6040516100cf9190610c12565b60405180910390f35b6100a06100e6366004610c55565b610503565b3480156100f757600080fd5b5061010b610106366004610cb8565b61073b565b60405190151581526020016100cf565b34801561012757600080fd5b5061010b610136366004610be4565b6107b5565b34801561014757600080fd5b506100a0610156366004610be4565b6107e8565b34801561016757600080fd5b50610188610176366004610cda565b60006020819052908152604090205481565b6040519081526020016100cf565b3480156101a257600080fd5b506101886101b1366004610be4565b610a4a565b3233146101de5760405162461bcd60e51b81526004016101d590610cf3565b60405180910390fd5b6000805b828110156103b057368484838181106101fd576101fd610d1d565b60c0029190910191503390506102196080830160608401610d33565b6001600160a01b03161461025c5760405162461bcd60e51b815260206004820152600a6024820152691b9bdd08185d5d1a195960b21b60448201526064016101d5565b6000808061026984610b43565b92509250925060008111801561027e57504381105b6102ba5760405162461bcd60e51b815260206004820152600d60248201526c0756e66696c6c6564207377617609c1b60448201526064016101d5565b6102c582853561073b565b156103055760405162461bcd60e51b815260206004820152601060248201526f185b1c9958591e481c995919595b595960821b60448201526064016101d5565b61031460a0850135853561073b565b6103515760405162461bcd60e51b815260206004820152600e60248201526d1a5b9d985b1a59081cd958dc995d60921b60448201526064016101d5565b600083815260208190526040902060a0850180359091556103759060808601610d63565b61038390633b9aca00610da3565b6103979067ffffffffffffffff1687610dd3565b95505050505080806103a890610deb565b9150506101e2565b50604051600090339083908381818185875af1925050503d80600081146103f3576040519150601f19603f3d011682016040523d82523d6000602084013e6103f8565b606091505b50909150506001811515146104415760405162461bcd60e51b815260206004820152600f60248201526e1d1c985b9cd9995c8819985a5b1959608a1b60448201526064016101d5565b50505050565b60408051606081018252600080825260208201819052918101829052908061046e84610b43565b92509250506104986040805160608101909152806000815260006020820181905260409091015290565b816000036104bf578060005b908160038111156104b7576104b7610bfc565b9052506104fb565b600183016104cf578060036104a4565b6104da83863561073b565b156104ef5760028152602081018390526104fb565b60018152604081018290525b949350505050565b3233146105225760405162461bcd60e51b81526004016101d590610cf3565b6000805b828110156106fc573684848381811061054157610541610d1d565b905060a002019050600081608001602081019061055e9190610d63565b67ffffffffffffffff161161059d5760405162461bcd60e51b81526020600482015260056024820152640c081d985b60da1b60448201526064016101d5565b60006105af6060830160408401610d63565b67ffffffffffffffff16116105fa5760405162461bcd60e51b815260206004820152601160248201527003020726566756e6454696d657374616d7607c1b60448201526064016101d5565b600061060582610a4a565b60008181526020819052604090205490915080156106565760405162461bcd60e51b815260206004820152600e60248201526d73776170206e6f7420656d70747960901b60448201526064016101d5565b504361066381843561073b565b156106a15760405162461bcd60e51b815260206004820152600e60248201526d3430b9b41031b7b63634b9b4b7b760911b60448201526064016101d5565b60008281526020819052604090208190556106c260a0840160808501610d63565b6106d090633b9aca00610da3565b6106e49067ffffffffffffffff1686610dd3565b945050505080806106f490610deb565b915050610526565b503481146107365760405162461bcd60e51b8152602060048201526007602482015266189859081d985b60ca1b60448201526064016101d5565b505050565b60008160028460405160200161075391815260200190565b60408051601f198184030181529082905261076d91610e04565b602060405180830381855afa15801561078a573d6000803e3d6000fd5b5050506040513d601f19601f820116820180604052508101906107ad9190610e3f565b149392505050565b60008060006107c384610b43565b9250925050806000141580156104fb57506107df82853561073b565b15949350505050565b3233146108075760405162461bcd60e51b81526004016101d590610cf3565b6108176060820160408301610d63565b67ffffffffffffffff164210156108675760405162461bcd60e51b81526020600482015260146024820152731b1bd8dadd1a5b59481b9bdd08195e1c1a5c995960621b60448201526064016101d5565b600080600061087584610b43565b92509250925060008111801561088b5750438111155b6108c95760405162461bcd60e51b815260206004820152600f60248201526e73776170206e6f742061637469766560881b60448201526064016101d5565b6108d482853561073b565b156109195760405162461bcd60e51b81526020600482015260156024820152741cddd85c08185b1c9958591e481c995919595b5959605a1b60448201526064016101d5565b600182016109615760405162461bcd60e51b81526020600482015260156024820152741cddd85c08185b1c9958591e481c99599d5b991959605a1b60448201526064016101d5565b600083815260208181526040808320600019905561098491908701908701610d33565b6001600160a01b031661099d60a0870160808801610d63565b6109ab90633b9aca00610da3565b67ffffffffffffffff1660405160006040518083038185875af1925050503d80600081146109f5576040519150601f19603f3d011682016040523d82523d6000602084013e6109fa565b606091505b5090915050600181151514610a435760405162461bcd60e51b815260206004820152600f60248201526e1d1c985b9cd9995c8819985a5b1959608a1b60448201526064016101d5565b5050505050565b600060028235610a606040850160208601610d33565b60601b846060016020810190610a769190610d33565b60601b610a8960a0870160808801610d63565b60c01b610a9c6060880160408901610d63565b6040805160208101969096526bffffffffffffffffffffffff19948516908601529190921660548401526001600160c01b0319918216606884015260c01b16607082015260780160408051601f1981840301815290829052610afd91610e04565b602060405180830381855afa158015610b1a573d6000803e3d6000fd5b5050506040513d601f19601f82011682018060405250810190610b3d9190610e3f565b92915050565b600080600080610b5285610a4a565b600081815260208190526040902054909690955085945092505050565b60008060208385031215610b8257600080fd5b823567ffffffffffffffff80821115610b9a57600080fd5b818501915085601f830112610bae57600080fd5b813581811115610bbd57600080fd5b86602060c083028501011115610bd257600080fd5b60209290920196919550909350505050565b600060a08284031215610bf657600080fd5b50919050565b634e487b7160e01b600052602160045260246000fd5b8151606082019060048110610c3757634e487b7160e01b600052602160045260246000fd5b80835250602083015160208301526040830151604083015292915050565b60008060208385031215610c6857600080fd5b823567ffffffffffffffff80821115610c8057600080fd5b818501915085601f830112610c9457600080fd5b813581811115610ca357600080fd5b86602060a083028501011115610bd257600080fd5b60008060408385031215610ccb57600080fd5b50508035926020909101359150565b600060208284031215610cec57600080fd5b5035919050565b60208082526010908201526f39b2b73232b910109e9037b934b3b4b760811b604082015260600190565b634e487b7160e01b600052603260045260246000fd5b600060208284031215610d4557600080fd5b81356001600160a01b0381168114610d5c57600080fd5b9392505050565b600060208284031215610d7557600080fd5b813567ffffffffffffffff81168114610d5c57600080fd5b634e487b7160e01b600052601160045260246000fd5b600067ffffffffffffffff80831681851681830481118215151615610dca57610dca610d8d565b02949350505050565b60008219821115610de657610de6610d8d565b500190565b600060018201610dfd57610dfd610d8d565b5060010190565b6000825160005b81811015610e255760208186018101518583015201610e0b565b81811115610e34576000828501525b509190910192915050565b600060208284031215610e5157600080fd5b505191905056fea2646970667358221220d735868682c69bb69ed936f11e2f136e7da44cb347e0b1d67acd1e088ea13dd664736f6c634300080f0033" +ERC20_SWAP_V1="60a060405234801561001057600080fd5b506040516111cb3803806111cb83398101604081905261002f91610040565b6001600160a01b0316608052610070565b60006020828403121561005257600080fd5b81516001600160a01b038116811461006957600080fd5b9392505050565b60805161112b6100a060003960008181610158015281816104570152818161083e0152610b5d015261112b6000f3fe6080604052600436106100865760003560e01c80637802689d116100595780637802689d146101265780638c8e8fee14610146578063d2544c0614610192578063eb84e7f2146101b2578063ed7cbed7146101ed57600080fd5b8063428b16e11461008b57806361a16e33146100ad57806364a97bff146100e357806377d7e031146100f6575b600080fd5b34801561009757600080fd5b506100ab6100a6366004610dea565b61020d565b005b3480156100b957600080fd5b506100cd6100c8366004610e5f565b610533565b6040516100da9190610e8d565b60405180910390f35b6100ab6100f1366004610ed0565b6105ef565b34801561010257600080fd5b50610116610111366004610f33565b610918565b60405190151581526020016100da565b34801561013257600080fd5b50610116610141366004610e5f565b610992565b34801561015257600080fd5b5061017a7f000000000000000000000000000000000000000000000000000000000000000081565b6040516001600160a01b0390911681526020016100da565b34801561019e57600080fd5b506100ab6101ad366004610e5f565b6109c5565b3480156101be57600080fd5b506101df6101cd366004610f55565b60006020819052908152604090205481565b6040519081526020016100da565b3480156101f957600080fd5b506101df610208366004610e5f565b610cc5565b3233146102355760405162461bcd60e51b815260040161022c90610f6e565b60405180910390fd5b6000805b82811015610407573684848381811061025457610254610f98565b60c0029190910191503390506102706080830160608401610fae565b6001600160a01b0316146102b35760405162461bcd60e51b815260206004820152600a6024820152691b9bdd08185d5d1a195960b21b604482015260640161022c565b600080806102c084610dbe565b9250925092506000811180156102d557504381105b6103115760405162461bcd60e51b815260206004820152600d60248201526c0756e66696c6c6564207377617609c1b604482015260640161022c565b61031c828535610918565b1561035c5760405162461bcd60e51b815260206004820152601060248201526f185b1c9958591e481c995919595b595960821b604482015260640161022c565b61036b60a08501358535610918565b6103a85760405162461bcd60e51b815260206004820152600e60248201526d1a5b9d985b1a59081cd958dc995d60921b604482015260640161022c565b600083815260208190526040902060a0850180359091556103cc9060808601610fde565b6103da90633b9aca0061101e565b6103ee9067ffffffffffffffff168761104e565b95505050505080806103ff90611066565b915050610239565b5060408051336024820152604480820184905282518083039091018152606490910182526020810180516001600160e01b031663a9059cbb60e01b17905290516000916060916001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016916104819161107f565b6000604051808303816000865af19150503d80600081146104be576040519150601f19603f3d011682016040523d82523d6000602084013e6104c3565b606091505b5090925090508180156104ee5750805115806104ee5750808060200190518101906104ee91906110ba565b61052c5760405162461bcd60e51b815260206004820152600f60248201526e1d1c985b9cd9995c8819985a5b1959608a1b604482015260640161022c565b5050505050565b60408051606081018252600080825260208201819052918101829052908061055a84610dbe565b92509250506105846040805160608101909152806000815260006020820181905260409091015290565b816000036105ab578060005b908160038111156105a3576105a3610e77565b9052506105e7565b600183016105bb57806003610590565b6105c6838635610918565b156105db5760028152602081018390526105e7565b60018152604081018290525b949350505050565b32331461060e5760405162461bcd60e51b815260040161022c90610f6e565b6000805b828110156107e8573684848381811061062d5761062d610f98565b905060a002019050600081608001602081019061064a9190610fde565b67ffffffffffffffff16116106895760405162461bcd60e51b81526020600482015260056024820152640c081d985b60da1b604482015260640161022c565b600061069b6060830160408401610fde565b67ffffffffffffffff16116106e65760405162461bcd60e51b815260206004820152601160248201527003020726566756e6454696d657374616d7607c1b604482015260640161022c565b60006106f182610cc5565b60008181526020819052604090205490915080156107425760405162461bcd60e51b815260206004820152600e60248201526d73776170206e6f7420656d70747960901b604482015260640161022c565b504361074f818435610918565b1561078d5760405162461bcd60e51b815260206004820152600e60248201526d3430b9b41031b7b63634b9b4b7b760911b604482015260640161022c565b60008281526020819052604090208190556107ae60a0840160808501610fde565b6107bc90633b9aca0061101e565b6107d09067ffffffffffffffff168661104e565b945050505080806107e090611066565b915050610612565b5060408051336024820152306044820152606480820184905282518083039091018152608490910182526020810180516001600160e01b03166323b872dd60e01b17905290516000916060916001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016916108689161107f565b6000604051808303816000865af19150503d80600081146108a5576040519150601f19603f3d011682016040523d82523d6000602084013e6108aa565b606091505b5090925090508180156108d55750805115806108d55750808060200190518101906108d591906110ba565b61052c5760405162461bcd60e51b81526020600482015260146024820152731d1c985b9cd9995c88199c9bdb4819985a5b195960621b604482015260640161022c565b60008160028460405160200161093091815260200190565b60408051601f198184030181529082905261094a9161107f565b602060405180830381855afa158015610967573d6000803e3d6000fd5b5050506040513d601f19601f8201168201806040525081019061098a91906110dc565b149392505050565b60008060006109a084610dbe565b9250925050806000141580156105e757506109bc828535610918565b15949350505050565b3233146109e45760405162461bcd60e51b815260040161022c90610f6e565b6109f46060820160408301610fde565b67ffffffffffffffff16421015610a445760405162461bcd60e51b81526020600482015260146024820152731b1bd8dadd1a5b59481b9bdd08195e1c1a5c995960621b604482015260640161022c565b6000806000610a5284610dbe565b925092509250600081118015610a685750438111155b610aa65760405162461bcd60e51b815260206004820152600f60248201526e73776170206e6f742061637469766560881b604482015260640161022c565b610ab1828535610918565b15610af65760405162461bcd60e51b81526020600482015260156024820152741cddd85c08185b1c9958591e481c995919595b5959605a1b604482015260640161022c565b60018201610b3e5760405162461bcd60e51b81526020600482015260156024820152741cddd85c08185b1c9958591e481c99599d5b991959605a1b604482015260640161022c565b6000838152602081905260408120600019905560606001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000167fa9059cbb2ab09eb219583f4a59a5d0623ade346d962bcd4e46b11da047c9049b33610baf60a08a0160808b01610fde565b6040516001600160a01b03909216602483015267ffffffffffffffff16604482015260640160408051601f198184030181529181526020820180516001600160e01b03166001600160e01b0319909416939093179092529051610c12919061107f565b6000604051808303816000865af19150503d8060008114610c4f576040519150601f19603f3d011682016040523d82523d6000602084013e610c54565b606091505b509092509050818015610c7f575080511580610c7f575080806020019051810190610c7f91906110ba565b610cbd5760405162461bcd60e51b815260206004820152600f60248201526e1d1c985b9cd9995c8819985a5b1959608a1b604482015260640161022c565b505050505050565b600060028235610cdb6040850160208601610fae565b60601b846060016020810190610cf19190610fae565b60601b610d0460a0870160808801610fde565b60c01b610d176060880160408901610fde565b6040805160208101969096526bffffffffffffffffffffffff19948516908601529190921660548401526001600160c01b0319918216606884015260c01b16607082015260780160408051601f1981840301815290829052610d789161107f565b602060405180830381855afa158015610d95573d6000803e3d6000fd5b5050506040513d601f19601f82011682018060405250810190610db891906110dc565b92915050565b600080600080610dcd85610cc5565b600081815260208190526040902054909690955085945092505050565b60008060208385031215610dfd57600080fd5b823567ffffffffffffffff80821115610e1557600080fd5b818501915085601f830112610e2957600080fd5b813581811115610e3857600080fd5b86602060c083028501011115610e4d57600080fd5b60209290920196919550909350505050565b600060a08284031215610e7157600080fd5b50919050565b634e487b7160e01b600052602160045260246000fd5b8151606082019060048110610eb257634e487b7160e01b600052602160045260246000fd5b80835250602083015160208301526040830151604083015292915050565b60008060208385031215610ee357600080fd5b823567ffffffffffffffff80821115610efb57600080fd5b818501915085601f830112610f0f57600080fd5b813581811115610f1e57600080fd5b86602060a083028501011115610e4d57600080fd5b60008060408385031215610f4657600080fd5b50508035926020909101359150565b600060208284031215610f6757600080fd5b5035919050565b60208082526010908201526f39b2b73232b910109e9037b934b3b4b760811b604082015260600190565b634e487b7160e01b600052603260045260246000fd5b600060208284031215610fc057600080fd5b81356001600160a01b0381168114610fd757600080fd5b9392505050565b600060208284031215610ff057600080fd5b813567ffffffffffffffff81168114610fd757600080fd5b634e487b7160e01b600052601160045260246000fd5b600067ffffffffffffffff8083168185168183048111821515161561104557611045611008565b02949350505050565b6000821982111561106157611061611008565b500190565b60006001820161107857611078611008565b5060010190565b6000825160005b818110156110a05760208186018101518583015201611086565b818111156110af576000828501525b509190910192915050565b6000602082840312156110cc57600080fd5b81518015158114610fd757600080fd5b6000602082840312156110ee57600080fd5b505191905056fea2646970667358221220319d89b87a0d5782925310133ed5d12d2ff562b0786611308396804fcad176fc64736f6c634300080f0033" + # PASSWORD is the password used to unlock all accounts/wallets/addresses. PASSWORD="abc" @@ -310,6 +313,9 @@ echo "Sending 5000 eth to delta and gamma and testing." echo "Deploying ETHSwapV0 contract." ETH_SWAP_CONTRACT_HASH=$("${NODES_ROOT}/harness-ctl/alpha" "attach --preload ${NODES_ROOT}/harness-ctl/deploy.js --exec deploy(\"${ALPHA_ADDRESS}\",\"${ETH_SWAP_V0}\")" | sed 's/"//g') +echo "Deploying ETHSwap1 contract." +ETH_SWAP_CONTRACT_HASH_V1=$("${NODES_ROOT}/harness-ctl/alpha" "attach --preload ${NODES_ROOT}/harness-ctl/deploy.js --exec deploy(\"${ALPHA_ADDRESS}\",\"${ETH_SWAP_V1}\")" | sed 's/"//g') + echo "Deploying TestToken contract." TEST_TOKEN_CONTRACT_HASH=$("${NODES_ROOT}/harness-ctl/alpha" "attach --preload ${NODES_ROOT}/harness-ctl/deploy.js --exec deploy(\"${ALPHA_ADDRESS}\",\"${TEST_TOKEN}\")" | sed 's/"//g') @@ -347,6 +353,12 @@ cat > "${NODES_ROOT}/eth_swap_contract_address.txt" < "${NODES_ROOT}/eth_swap_contract_address_v1.txt" < "${NODES_ROOT}/test_token_contract_address.txt" < "${NODES_ROOT}/erc20_swap_contract_address.txt" < "${NODES_ROOT}/erc20_swap_contract_address_v1.txt" < %d", dexeth.WeiToGwei(diff), uint64(vGwei)) } } diff --git a/server/asset/eth/tokener.go b/server/asset/eth/tokener.go index 895be5904b..397e4c4681 100644 --- a/server/asset/eth/tokener.go +++ b/server/asset/eth/tokener.go @@ -12,16 +12,16 @@ import ( "decred.org/dcrdex/dex" "decred.org/dcrdex/dex/networks/erc20" - erc20v0 "decred.org/dcrdex/dex/networks/erc20/contracts/v0" + erc20v1 "decred.org/dcrdex/dex/networks/erc20/contracts/v1" dexeth "decred.org/dcrdex/dex/networks/eth" - swapv0 "decred.org/dcrdex/dex/networks/eth/contracts/v0" + swapv1 "decred.org/dcrdex/dex/networks/eth/contracts/v1" "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" ) // swapContract is a generic source of swap contract data. type swapContract interface { - Swap(context.Context, [32]byte) (*dexeth.SwapState, error) + Status(context.Context, *dexeth.SwapVector) (*dexeth.SwapStatus, error) } // erc2Contract exposes methods of a token's ERC20 contract. @@ -44,11 +44,11 @@ func newTokener(ctx context.Context, assetID uint32, net dex.Network, be bind.Co return nil, err } - if token.ver != 0 { - return nil, fmt.Errorf("only version 0 contracts supported") + if token.ver != version { + return nil, fmt.Errorf("wrong contract version. wanted %d, got %d", version, token.ver) } - es, err := erc20v0.NewERC20Swap(swapContract.Address, be) + es, err := erc20v1.NewERC20Swap(swapContract.Address, be) if err != nil { return nil, err } @@ -71,7 +71,7 @@ func newTokener(ctx context.Context, assetID uint32, net dex.Network, be bind.Co tkn := &tokener{ registeredToken: token, - swapContract: &swapSourceV0{es}, + swapContract: &swapSourceV1{es}, erc20Contract: erc20, contractAddr: swapContract.Address, tokenAddr: netToken.Address, @@ -92,15 +92,15 @@ func (t *tokener) transferred(txData []byte) *big.Int { // swapped calculates the value sent to the swap contracts initiate method. func (t *tokener) swapped(txData []byte) *big.Int { - inits, err := dexeth.ParseInitiateData(txData, t.ver) + vectors, err := dexeth.ParseInitiateDataV1(txData) if err != nil { return nil } - v := new(big.Int) - for _, init := range inits { - v.Add(v, init.Value) + var v uint64 + for _, vector := range vectors { + v += vector.Value } - return v + return dexeth.GweiToWei(v) } // balanceOf checks the account's token balance. @@ -108,25 +108,29 @@ func (t *tokener) balanceOf(ctx context.Context, addr common.Address) (*big.Int, return t.BalanceOf(readOnlyCallOpts(ctx, false), addr) } -// swapContractV0 represents a version 0 swap contract for ETH or a token. -type swapContractV0 interface { - Swap(opts *bind.CallOpts, secretHash [32]byte) (swapv0.ETHSwapSwap, error) +// swapContractV1 represents a version 0 swap contract for ETH or a token. +type swapContractV1 interface { + Status(opts *bind.CallOpts, c swapv1.ETHSwapVector) (swapv1.ETHSwapStatus, error) } -// swapSourceV0 wraps a swapContractV0 and translates the swap data to satisfy +// swapSourceV1 wraps a swapContractV0 and translates the swap data to satisfy // swapSource. -type swapSourceV0 struct { - contract swapContractV0 // *swapv0.ETHSwap or *erc20v0.ERCSwap +type swapSourceV1 struct { + contract swapContractV1 // *swapv0.ETHSwap or *erc20v0.ERCSwap } // Swap translates the version 0 swap data to the more general SwapState to // satisfy the swapSource interface. -func (s *swapSourceV0) Swap(ctx context.Context, secretHash [32]byte) (*dexeth.SwapState, error) { - state, err := s.contract.Swap(readOnlyCallOpts(ctx, true), secretHash) +func (s *swapSourceV1) Status(ctx context.Context, vector *dexeth.SwapVector) (*dexeth.SwapStatus, error) { + rec, err := s.contract.Status(readOnlyCallOpts(ctx, true), dexeth.SwapVectorToAbigen(vector)) if err != nil { return nil, fmt.Errorf("Swap error: %w", err) } - return dexeth.SwapStateFromV0(&state), nil + return &dexeth.SwapStatus{ + BlockHeight: rec.BlockNumber.Uint64(), + Secret: rec.Secret, + Step: dexeth.SwapStep(rec.Step), + }, nil } // readOnlyCallOpts is the CallOpts used for read-only contract method calls.