From f7c52968a77741af4c40a7c0a88986681afd44c5 Mon Sep 17 00:00:00 2001 From: buck54321 Date: Fri, 20 Oct 2023 15:38:51 -0700 Subject: [PATCH] eth v1 contract 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. --- .gitignore | 1 + client/asset/eth/cmd/getgas/main.go | 13 +- client/asset/eth/contractor.go | 619 +++++++++-- client/asset/eth/contractor_test.go | 14 +- client/asset/eth/deploy.go | 52 +- client/asset/eth/eth.go | 752 ++++++++----- client/asset/eth/eth_test.go | 985 +++++++++--------- client/asset/eth/nodeclient_harness_test.go | 393 ++++--- client/asset/polygon/polygon.go | 2 +- client/core/simnet_trade.go | 2 +- client/webserver/site/src/js/markets.ts | 2 +- dex/networks/erc20/contracts/ERC20SwapV0.sol | 4 +- .../erc20/contracts/updatecontract.sh | 34 +- .../erc20/contracts/v0/BinRuntimeV0.go | 6 - dex/networks/erc20/contracts/v0/contract.go | 7 +- .../erc20/contracts/v0/swap_contract.bin | Bin 0 -> 3730 bytes .../erc20/contracts/v0/token_contract.bin | Bin 0 -> 2796 bytes dex/networks/eth/contracts/ETHSwapV0.sol | 2 +- dex/networks/eth/contracts/ETHSwapV1.sol | 261 +++++ .../eth/contracts/build-multibalance.sh | 17 +- .../multibalance/BinRuntimeMultiBalanceV0.go | 6 - .../eth/contracts/multibalance/contract.bin | Bin 0 -> 1187 bytes dex/networks/eth/contracts/updatecontract.sh | 18 +- dex/networks/eth/contracts/v0/BinRuntimeV0.go | 6 - dex/networks/eth/contracts/v0/contract.bin | Bin 0 -> 2970 bytes dex/networks/eth/contracts/v0/contract.go | 2 +- dex/networks/eth/contracts/v1/contract.bin | Bin 0 -> 4500 bytes dex/networks/eth/contracts/v1/contract.go | 442 ++++++++ dex/networks/eth/params.go | 178 +++- dex/networks/eth/params_test.go | 2 +- dex/networks/eth/tokens.go | 141 ++- dex/networks/eth/txdata.go | 259 +++-- dex/networks/eth/txdata_test.go | 6 +- dex/networks/polygon/params.go | 150 +++ dex/testing/dcrdex/harness.sh | 9 + dex/testing/eth/create-node.sh | 2 +- dex/testing/eth/harness.sh | 51 +- dex/testing/polygon/harness.sh | 41 +- server/asset/eth/coiner.go | 230 ++-- server/asset/eth/coiner_test.go | 527 ++++++---- server/asset/eth/eth.go | 240 +++-- server/asset/eth/eth_test.go | 457 +++++--- server/asset/eth/rpcclient.go | 94 +- server/asset/eth/rpcclient_harness_test.go | 31 +- server/asset/eth/tokener.go | 211 +++- server/asset/polygon/polygon.go | 45 +- server/cmd/dcrdex/evm-protocol-overrides.json | 1 + 47 files changed, 4435 insertions(+), 1880 deletions(-) delete mode 100644 dex/networks/erc20/contracts/v0/BinRuntimeV0.go create mode 100644 dex/networks/erc20/contracts/v0/swap_contract.bin create mode 100644 dex/networks/erc20/contracts/v0/token_contract.bin create mode 100644 dex/networks/eth/contracts/ETHSwapV1.sol mode change 100644 => 100755 dex/networks/eth/contracts/build-multibalance.sh delete mode 100644 dex/networks/eth/contracts/multibalance/BinRuntimeMultiBalanceV0.go create mode 100644 dex/networks/eth/contracts/multibalance/contract.bin delete mode 100644 dex/networks/eth/contracts/v0/BinRuntimeV0.go create mode 100644 dex/networks/eth/contracts/v0/contract.bin create mode 100644 dex/networks/eth/contracts/v1/contract.bin create mode 100644 dex/networks/eth/contracts/v1/contract.go create mode 100644 server/cmd/dcrdex/evm-protocol-overrides.json diff --git a/.gitignore b/.gitignore index 0e82613dfd..7a4aaae9bf 100644 --- a/.gitignore +++ b/.gitignore @@ -49,3 +49,4 @@ client/cmd/translationsreport/translationsreport client/cmd/translationsreport/worksheets server/cmd/dexadm/dexadm server/cmd/geogame/geogame +server/cmd/dcrdex/evm-protocol-overrides.json diff --git a/client/asset/eth/cmd/getgas/main.go b/client/asset/eth/cmd/getgas/main.go index d02f3bb64e..c9b9722701 100644 --- a/client/asset/eth/cmd/getgas/main.go +++ b/client/asset/eth/cmd/getgas/main.go @@ -48,8 +48,8 @@ func mainErr() error { flag.BoolVar(&useMainnet, "mainnet", false, "use mainnet") flag.BoolVar(&useTestnet, "testnet", false, "use testnet") flag.BoolVar(&useSimnet, "simnet", false, "use simnet") - flag.BoolVar(&trace, "trace", false, "use simnet") - flag.BoolVar(&debug, "debug", false, "use simnet") + flag.BoolVar(&trace, "trace", false, "use trace logging") + flag.BoolVar(&debug, "debug", false, "use debug logging") flag.IntVar(&maxSwaps, "n", 5, "max number of swaps per transaction. minimum is 2. test will run from 2 swap up to n swaps.") flag.StringVar(&chain, "chain", "eth", "symbol of the base chain") flag.StringVar(&token, "token", "", "symbol of the token. if token is not specified, will check gas for base chain") @@ -125,7 +125,8 @@ func mainErr() error { wParams := new(eth.GetGasWalletParams) wParams.BaseUnitInfo = bui - if token != chain { + isToken := token != chain + if isToken { var exists bool tkn, exists := tokens[assetID] if !exists { @@ -139,16 +140,18 @@ func mainErr() error { } swapContract, exists := netToken.SwapContracts[contractVer] if !exists { - return nil, fmt.Errorf("no verion %d contract for %s token on %s network %s", contractVer, tkn.Name, chain, net) + return nil, fmt.Errorf("no version %d contract for %s token on %s network %s", contractVer, tkn.Name, chain, net) } wParams.Gas = &swapContract.Gas } else { wParams.UnitInfo = bui g, exists := gases[contractVer] if !exists { - return nil, fmt.Errorf("no verion %d contract for %s network %s", contractVer, chain, net) + return nil, fmt.Errorf("no version %d contract for %s network %s", contractVer, chain, net) } wParams.Gas = g + } + if !isToken || contractVer == 1 { cs, exists := contracts[contractVer] if !exists { return nil, fmt.Errorf("no version %d base chain swap contract on %s", contractVer, chain) diff --git a/client/asset/eth/contractor.go b/client/asset/eth/contractor.go index 6fa30124c3..b663644b39 100644 --- a/client/asset/eth/contractor.go +++ b/client/asset/eth/contractor.go @@ -4,8 +4,10 @@ package eth import ( + "bytes" "context" "crypto/sha256" + "errors" "fmt" "math/big" "time" @@ -17,6 +19,7 @@ import ( erc20v0 "decred.org/dcrdex/dex/networks/erc20/contracts/v0" 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" @@ -28,19 +31,21 @@ 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) + estimateRedeemGas(ctx context.Context, secrets [][32]byte, locators [][]byte) (uint64, error) + estimateRefundGas(ctx context.Context, locator []byte) (uint64, 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) } // tokenContractor interacts with an ERC20 token contract and a token swap @@ -57,8 +62,11 @@ type tokenContractor interface { estimateTransferGas(context.Context, *big.Int) (uint64, error) } -type contractorConstructor func(contractAddr, addr common.Address, ec bind.ContractBackend) (contractor, error) -type tokenContractorConstructor func(net dex.Network, token *dexeth.Token, acctAddr common.Address, ec bind.ContractBackend) (tokenContractor, error) +type unifiedContractor interface { + tokenContractor(token *dexeth.Token) (tokenContractor, error) +} + +type contractorConstructor func(net dex.Network, contractAddr, acctAddr common.Address, ec bind.ContractBackend) (contractor, error) // contractV0 is the interface common to a version 0 swap contract or version 0 // token swap contract. @@ -70,6 +78,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, token common.Address, contracts []swapv1.ETHSwapVector) (*types.Transaction, error) + Redeem(opts *bind.TransactOpts, token common.Address, redemptions []swapv1.ETHSwapRedemption) (*types.Transaction, error) + Status(opts *bind.CallOpts, token common.Address, c swapv1.ETHSwapVector) (swapv1.ETHSwapStatus, error) + Refund(opts *bind.TransactOpts, token common.Address, c swapv1.ETHSwapVector) (*types.Transaction, error) + IsRedeemable(opts *bind.CallOpts, token common.Address, c swapv1.ETHSwapVector) (bool, error) + + ContractKey(opts *bind.CallOpts, token common.Address, 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 { @@ -88,7 +111,7 @@ var _ contractor = (*contractorV0)(nil) // newV0Contractor is the constructor for a version 0 ETH swap contract. For // token swap contracts, use newV0TokenContractor to construct a // tokenContractorV0. -func newV0Contractor(contractAddr, acctAddr common.Address, cb bind.ContractBackend) (contractor, error) { +func newV0Contractor(_ dex.Network, contractAddr, acctAddr common.Address, cb bind.ContractBackend) (contractor, error) { c, err := swapv0.NewETHSwap(contractAddr, cb) if err != nil { return nil, err @@ -154,6 +177,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{ @@ -164,7 +192,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.Initiator, + To: swap.Participant, + Value: swap.Value, + SecretHash: secretHash, + LockTime: uint64(swap.LockTime.Unix()), + } + 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.Initiator, + To: swap.Participant, + Value: swap.Value, + SecretHash: secretHash, + LockTime: uint64(swap.LockTime.Unix()), + } + 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, @@ -188,19 +282,35 @@ 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) } // 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{ @@ -214,7 +324,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) } @@ -246,7 +364,7 @@ func (c *contractorV0) estimateInitGas(ctx context.Context, n int) (uint64, erro func (c *contractorV0) estimateGas(ctx context.Context, value *big.Int, method string, args ...any) (uint64, error) { data, err := c.abi.Pack(method, args...) if err != nil { - return 0, fmt.Errorf("Pack error: %v", err) + return 0, fmt.Errorf("pack error: %v", err) } return c.cb.EstimateGas(ctx, ethereum.CallMsg{ @@ -275,7 +393,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) @@ -286,7 +404,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 } @@ -299,7 +417,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) } @@ -307,12 +425,84 @@ func (c *contractorV0) outgoingValue(tx *types.Transaction) (swapped uint64) { return } +// erc20Contractor supports the ERC20 ABI. Embedded in token contractors. +type erc20Contractor struct { + cb bind.ContractBackend + tokenContract *erc20.IERC20 + tokenContractAddr common.Address + acctAddr common.Address + swapAddr 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.acctAddr, + Context: ctx, + } + + return c.tokenContract.BalanceOf(callOpts, c.acctAddr) +} + +// 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{ + From: c.acctAddr, + Context: ctx, + } + return c.tokenContract.Allowance(callOpts, c.acctAddr, c.swapAddr) +} + +// 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.swapAddr, 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) +} + +// estimateApproveGas estimates the gas needed to send an approve tx. +func (c *erc20Contractor) estimateApproveGas(ctx context.Context, amount *big.Int) (uint64, error) { + return estimateGas(ctx, c.acctAddr, c.tokenContractAddr, erc20.ERC20ABI, c.cb, new(big.Int), "approve", c.swapAddr, amount) +} + +// estimateTransferGas estimates the gas needed for a transfer tx. The account +// needs to have > amount tokens to use this method. +func (c *erc20Contractor) estimateTransferGas(ctx context.Context, amount *big.Int) (uint64, error) { + return estimateGas(ctx, c.acctAddr, c.swapAddr, erc20.ERC20ABI, c.cb, new(big.Int), "transfer", c.acctAddr, amount) +} + +func (c *erc20Contractor) parseTransfer(receipt *types.Receipt) (uint64, error) { + var transferredAmt uint64 + for _, log := range receipt.Logs { + if log.Address != c.tokenContractAddr { + continue + } + transfer, err := c.tokenContract.ParseTransfer(*log) + if err != nil { + continue + } + if transfer.To == c.acctAddr { + transferredAmt += transfer.Value.Uint64() + } + } + + if transferredAmt > 0 { + return transferredAmt, nil + } + + return 0, fmt.Errorf("transfer log to %s not found", c.tokenContractAddr) +} + // 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 } var _ contractor = (*tokenContractorV0)(nil) @@ -361,101 +551,352 @@ func newV0TokenContractor(net dex.Network, token *dexeth.Token, acctAddr common. evmify: token.AtomicToEVM, atomize: token.EVMToAtomic, }, - tokenAddr: tokenAddr, - tokenContract: tokenContract, + + erc20Contractor: &erc20Contractor{ + cb: cb, + tokenContract: tokenContract, + tokenContractAddr: tokenAddr, + acctAddr: acctAddr, + swapAddr: 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, +// 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.tokenContractAddr { + return 0, 0, nil } - return c.tokenContract.BalanceOf(callOpts, c.acctAddr) + // Consider removing. We'll never be sending transferFrom transactions + // directly. + if sender, _, value, err := erc20.ParseTransferFromData(tx.Data()); err == nil && sender == c.contractorV0.acctAddr { + return 0, c.atomize(value), nil + } + + if _, value, err := erc20.ParseTransferData(tx.Data()); err == nil { + return 0, c.atomize(value), nil + } + + return 0, 0, nil } -// 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, +// tokenAddress exposes the token_address immutable address of the token-bound +// swap contract. +func (c *tokenContractorV0) tokenAddress() common.Address { + return c.tokenContractAddr +} + +type contractorV1 struct { + contractV1 + net dex.Network + abi *abi.ABI + tokenAddr common.Address // zero-address for base-chain asset, e.g. ETH, POL + swapContractAddr 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, swapContractAddr, acctAddr common.Address, cb bind.ContractBackend) (contractor, error) { + c, err := swapv1.NewETHSwap(swapContractAddr, cb) + if err != nil { + return nil, err } - return c.tokenContract.Allowance(callOpts, c.acctAddr, c.contractAddr) + return &contractorV1{ + contractV1: c, + net: net, + abi: dexeth.ABIs[1], + swapContractAddr: swapContractAddr, + acctAddr: acctAddr, + cb: cb, + atomize: dexeth.WeiToGwei, + evmify: dexeth.GweiToWei, + }, 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}, c.tokenAddr, 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 *tokenContractorV0) parseTransfer(receipt *types.Receipt) (uint64, error) { - var transferredAmt uint64 - for _, log := range receipt.Logs { - if log.Address != c.tokenAddr { - continue +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}, c.tokenAddr, 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) 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: c.evmify(ac.Value), + LockTime: ac.LockTime, } - transfer, err := c.tokenContract.ParseTransfer(*log) + copy(v.SecretHash[:], ac.SecretHash) + versionedContracts = append(versionedContracts, dexeth.SwapVectorToAbigen(v)) + } + return c.Initiate(txOpts, c.tokenAddr, 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.DecodeContractData(r.Spends.Contract) if err != nil { - continue + return nil, fmt.Errorf("error parsing locator redeem: %w", err) } - if transfer.To == c.acctAddr { - transferredAmt += transfer.Value.Uint64() + 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, c.tokenAddr, versionedRedemptions) +} - if transferredAmt > 0 { - return transferredAmt, nil +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, c.tokenAddr, dexeth.SwapVectorToAbigen(v)) +} - return 0, fmt.Errorf("transfer log to %s not found", c.acctAddr) +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: big.NewInt(dexeth.GweiFactor), + }) + } + + var value *big.Int + if !c.isToken { + value = dexeth.GweiToWei(uint64(n)) + } + + return c.estimateGas(ctx, value, "initiate", c.tokenAddr, initiations) } -// 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 *contractorV1) estimateGas(ctx context.Context, value *big.Int, method string, args ...interface{}) (uint64, error) { + return estimateGas(ctx, c.acctAddr, c.swapContractAddr, c.abi, c.cb, value, method, args...) } -// 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 *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", c.tokenAddr, redemps) } -// 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 ...any) (uint64, error) { - data, err := erc20.ERC20ABI.Pack(method, args...) +func (c *contractorV1) estimateRefundGas(ctx context.Context, locator []byte) (uint64, error) { + v, err := dexeth.ParseV1Locator(locator) if err != nil { - return 0, fmt.Errorf("token estimateGas Pack error: %v", err) + return 0, err } + return c.estimateGas(ctx, nil, "refund", c.tokenAddr, dexeth.SwapVectorToAbigen(v)) +} - return c.cb.EstimateGas(ctx, ethereum.CallMsg{ - From: c.acctAddr, - To: &c.tokenAddr, - Data: data, - }) +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}, c.tokenAddr, 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}, c.tokenAddr, 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 *big.Int + for _, r := range redeems { + redeemed.Add(redeemed, r.Contract.Value) + } + return c.atomize(redeemed), nil + } + refund, err := dexeth.ParseRefundDataV1(tx.Data()) + if err != nil { + return 0, nil + } + return c.atomize(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 += c.atomize(init.Value) + } + } + return +} + +func (c *contractorV1) value(ctx context.Context, tx *types.Transaction) (in, out uint64, err error) { + if *tx.To() != c.swapContractAddr { + 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 +} + +func (c *contractorV1) tokenContractor(token *dexeth.Token) (tokenContractor, error) { + netToken, found := token.NetTokens[c.net] + if !found { + return nil, fmt.Errorf("token %s has no network %s", token.Name, c.net) + } + tokenAddr := netToken.Address + + tokenContract, err := erc20.NewIERC20(tokenAddr, c.cb) + if err != nil { + return nil, err + } + + return &tokenContractorV1{ + contractorV1: &contractorV1{ + contractV1: c.contractV1, + net: c.net, + abi: c.abi, + tokenAddr: tokenAddr, + swapContractAddr: c.swapContractAddr, + acctAddr: c.acctAddr, + cb: c.cb, + isToken: true, + evmify: token.AtomicToEVM, + atomize: token.EVMToAtomic, + }, + erc20Contractor: &erc20Contractor{ + cb: c.cb, + tokenContract: tokenContract, + tokenContractAddr: tokenAddr, + acctAddr: c.acctAddr, + swapAddr: c.swapContractAddr, + }, + }, nil } // 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) + if to == c.swapContractAddr { + return c.contractorV1.value(ctx, tx) } if to != c.tokenAddr { return 0, 0, nil @@ -463,7 +904,7 @@ func (c *tokenContractorV0) value(ctx context.Context, tx *types.Transaction) (i // Consider removing. We'll never be sending transferFrom transactions // directly. - if sender, _, value, err := erc20.ParseTransferFromData(tx.Data()); err == nil && sender == c.acctAddr { + if sender, _, value, err := erc20.ParseTransferFromData(tx.Data()); err == nil && sender == c.contractorV1.acctAddr { return 0, c.atomize(value), nil } @@ -476,14 +917,28 @@ 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 contractorConstructors = map[uint32]contractorConstructor{ - 0: newV0Contractor, +var _ contractor = (*tokenContractorV1)(nil) +var _ tokenContractor = (*tokenContractorV1)(nil) + +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 tokenContractorConstructors = map[uint32]tokenContractorConstructor{ - 0: newV0TokenContractor, +var contractorConstructors = map[uint32]contractorConstructor{ + 0: newV0Contractor, + 1: newV1Contractor, } diff --git a/client/asset/eth/contractor_test.go b/client/asset/eth/contractor_test.go index c7f4175238..3033bee3a6 100644 --- a/client/asset/eth/contractor_test.go +++ b/client/asset/eth/contractor_test.go @@ -2,6 +2,7 @@ package eth import ( "bytes" + "crypto/sha256" "fmt" "math/big" "testing" @@ -128,7 +129,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, @@ -160,12 +162,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") @@ -177,9 +179,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/deploy.go b/client/asset/eth/deploy.go index 975f155ec2..f043a019ae 100644 --- a/client/asset/eth/deploy.go +++ b/client/asset/eth/deploy.go @@ -16,6 +16,7 @@ import ( dexeth "decred.org/dcrdex/dex/networks/eth" multibal "decred.org/dcrdex/dex/networks/eth/contracts/multibalance" ethv0 "decred.org/dcrdex/dex/networks/eth/contracts/v0" + ethv1 "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" @@ -140,37 +141,32 @@ func (contractDeployer) EstimateMultiBalanceDeployFunding( } func (contractDeployer) txData(contractVer uint32, tokenAddr common.Address) (txData []byte, err error) { - var abi *abi.ABI - var bytecode []byte - isToken := tokenAddr != (common.Address{}) - if isToken { + if tokenAddr == (common.Address{}) { switch contractVer { case 0: - bytecode = common.FromHex(erc20v0.ERC20SwapBin) - abi, err = erc20v0.ERC20SwapMetaData.GetAbi() - } - } else { - switch contractVer { - case 0: - bytecode = common.FromHex(ethv0.ETHSwapBin) - abi, err = ethv0.ETHSwapMetaData.GetAbi() + return common.FromHex(ethv0.ETHSwapBin), nil + case 1: + return common.FromHex(ethv1.ETHSwapBin), nil } } + var abi *abi.ABI + var bytecode []byte + switch contractVer { + case 0: + bytecode = common.FromHex(erc20v0.ERC20SwapBin) + abi, err = erc20v0.ERC20SwapMetaData.GetAbi() + } if err != nil { return nil, fmt.Errorf("error parsing ABI: %w", err) } if abi == nil { return nil, fmt.Errorf("no abi data for version %d", contractVer) } - txData = bytecode - if isToken { - argData, err := abi.Pack("", tokenAddr) - if err != nil { - return nil, fmt.Errorf("error packing token address: %w", err) - } - txData = append(txData, argData...) + argData, err := abi.Pack("", tokenAddr) + if err != nil { + return nil, fmt.Errorf("error packing token address: %w", err) } - return + return append(bytecode, argData...), nil } // DeployContract deployes a dcrdex swap contract. @@ -199,7 +195,6 @@ func (contractDeployer) DeployContract( contractAddr, tx, _, err := erc20v0.DeployERC20Swap(txOpts, cb, tokenAddress) return contractAddr, tx, err } - } } else { switch contractVer { @@ -208,6 +203,11 @@ func (contractDeployer) DeployContract( contractAddr, tx, _, err := ethv0.DeployETHSwap(txOpts, cb) return contractAddr, tx, err } + case 1: + deployer = func(txOpts *bind.TransactOpts, cb bind.ContractBackend) (common.Address, *types.Transaction, error) { + contractAddr, tx, _, err := ethv1.DeployETHSwap(txOpts, cb) + return contractAddr, tx, err + } } } if deployer == nil { @@ -264,7 +264,7 @@ func (contractDeployer) deployContract( } feeRate := dexeth.WeiToGweiCeil(maxFeeRate) - log.Infof("Estimated fees: %s gwei / gas", ui.ConventionalString(feeRate*gas)) + log.Infof("Estimated fees: %s gwei", ui.ConventionalString(feeRate*gas)) gas *= 5 / 4 // Add 20% buffer feesWithBuffer := feeRate * gas @@ -287,7 +287,13 @@ func (contractDeployer) deployContract( return err } - log.Infof("👍 Contract %s launched with tx %s", contractAddr, tx.Hash()) + log.Infof("Contract %s launched with tx %s", contractAddr, tx.Hash()) + + if err = waitForConfirmation(ctx, "deploy", cl, tx.Hash(), log); err != nil { + return fmt.Errorf("error waiting for deployment transaction status: %w", err) + } + + log.Info("👍 Transaction confirmed") return nil } diff --git a/client/asset/eth/eth.go b/client/asset/eth/eth.go index 8eef685728..74a7ba69dd 100644 --- a/client/asset/eth/eth.go +++ b/client/asset/eth/eth.go @@ -45,10 +45,6 @@ import ( "github.com/tyler-smith/go-bip39" ) -func init() { - dexeth.MaybeReadSimnetAddrs() -} - func registerToken(tokenID uint32, desc string) { token, found := dexeth.Tokens[tokenID] if !found { @@ -66,6 +62,7 @@ func registerToken(tokenID uint32, desc string) { } func init() { + dexeth.MaybeReadSimnetAddrs() asset.Register(BipID, &Driver{}) registerToken(usdcTokenID, "The USDC Ethereum ERC20 token.") registerToken(usdtTokenID, "The USDT Ethereum ERC20 token.") @@ -119,9 +116,8 @@ const ( stateUpdateTick = time.Second * 5 // maxUnindexedTxs is the number of pending txs we will allow to be // unverified on-chain before we halt broadcasting of new txs. - maxUnindexedTxs = 10 - peerCountTicker = 5 * time.Second // no rpc calls here - contractVersionNewest = ^uint32(0) + maxUnindexedTxs = 10 + peerCountTicker = 5 * time.Second // no rpc calls here ) var ( @@ -161,7 +157,7 @@ var ( // exposed though any Driver methods or assets/driver functions. Use the // parent wallet's WalletInfo via (*Driver).Info if you need a token's // supported versions before a wallet is available. - SupportedVersions: []uint32{0}, + SupportedVersions: []uint32{0, 1}, UnitInfo: dexeth.UnitInfo, AvailableWallets: []*asset.WalletDefinition{ // { @@ -490,6 +486,7 @@ type assetWallet struct { ui dex.UnitInfo connected atomic.Bool wi asset.WalletInfo + tokenAddr common.Address // empty address for base chain asset versionedContracts map[uint32]common.Address versionedGases map[uint32]*dexeth.Gases @@ -505,7 +502,7 @@ type assetWallet struct { } findRedemptionMtx sync.RWMutex - findRedemptionReqs map[[32]byte]*findRedemptionRequest + findRedemptionReqs map[string]*findRedemptionRequest approvalsMtx sync.RWMutex pendingApprovals map[uint32]*pendingApproval @@ -514,7 +511,8 @@ type assetWallet struct { lastPeerCount uint32 peersChange func(uint32, error) - contractors map[uint32]contractor // version -> contractor + contractorV0 contractor + contractorV1 contractor evmify func(uint64) *big.Int atomize func(*big.Int) uint64 @@ -595,6 +593,15 @@ func privKeyFromSeed(seed []byte) (pk []byte, zero func(), err error) { return pk, extKey.Zero, 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 { + if serverVer == asset.VersionNewest { + return dexeth.ContractVersionNewest + } + return dexeth.ProtocolVersion(serverVer).ContractVersion() +} + func CreateEVMWallet(chainID int64, createWalletParams *asset.CreateWalletParams, compat *CompatibilityData, skipConnect bool) error { switch createWalletParams.Type { case walletTypeGeth: @@ -613,16 +620,6 @@ func CreateEVMWallet(chainID int64, createWalletParams *asset.CreateWalletParams defer zero() switch createWalletParams.Type { - // case walletTypeGeth: - // node, err := prepareNode(&nodeConfig{ - // net: createWalletParams.Net, - // appDir: walletDir, - // }) - // if err != nil { - // return err - // } - // defer node.Close() - // return importKeyToNode(node, privateKey, createWalletParams.Pass) case walletTypeRPC: // Make the wallet dir if it does not exist, otherwise we may fail to // write the compliant-providers.json file. Create the keystore @@ -806,11 +803,10 @@ func NewEVMWallet(cfg *EVMWalletConfig) (w *ETHWallet, err error) { maxSwapGas: maxSwapGas, maxRedeemGas: maxRedeemGas, emit: cfg.AssetCfg.Emit, - findRedemptionReqs: make(map[[32]byte]*findRedemptionRequest), + findRedemptionReqs: make(map[string]*findRedemptionRequest), pendingApprovals: make(map[uint32]*pendingApproval), approvalCache: make(map[uint32]bool), peersChange: cfg.AssetCfg.PeersChange, - contractors: make(map[uint32]contractor), evmify: dexeth.GweiToWei, atomize: dexeth.WeiToGwei, ui: dexeth.UnitInfo, @@ -876,13 +872,19 @@ func (w *ETHWallet) Connect(ctx context.Context) (_ *sync.WaitGroup, err error) for ver, constructor := range contractorConstructors { contractAddr, exists := w.versionedContracts[ver] if !exists || contractAddr == (common.Address{}) { - return nil, fmt.Errorf("no contract address for version %d, net %s", ver, w.net) + w.log.Debugf("no eth swap contract address for version %d, net %s", ver, w.net) + continue } - c, err := constructor(contractAddr, w.addr, w.node.contractBackend()) + c, err := constructor(w.net, contractAddr, w.addr, w.node.contractBackend()) if err != nil { return nil, fmt.Errorf("error constructor version %d contractor: %v", ver, err) } - w.contractors[ver] = c + switch ver { + case 0: + w.contractorV0 = c + case 1: + w.contractorV1 = c + } } if w.multiBalanceAddress != (common.Address{}) { @@ -983,7 +985,7 @@ func (w *TokenWallet) Connect(ctx context.Context) (*sync.WaitGroup, error) { return nil, fmt.Errorf("parent wallet not connected") } - err := w.loadContractors() + err := w.loadContractors(w.parent) if err != nil { return nil, err } @@ -1273,10 +1275,9 @@ func (w *ETHWallet) OpenTokenWallet(tokenCfg *asset.TokenConfig) (asset.Wallet, maxRedeemGas: maxRedeemGas, emit: tokenCfg.Emit, peersChange: tokenCfg.PeersChange, - findRedemptionReqs: make(map[[32]byte]*findRedemptionRequest), + findRedemptionReqs: make(map[string]*findRedemptionRequest), pendingApprovals: make(map[uint32]*pendingApproval), approvalCache: make(map[uint32]bool), - contractors: make(map[uint32]contractor), evmify: token.AtomicToEVM, atomize: token.EVMToAtomic, ui: token.UnitInfo, @@ -1285,6 +1286,7 @@ func (w *ETHWallet) OpenTokenWallet(tokenCfg *asset.TokenConfig) (asset.Wallet, SupportedVersions: w.wi.SupportedVersions, UnitInfo: token.UnitInfo, }, + tokenAddr: netToken.Address, pendingTxCheckBal: new(big.Int), } @@ -1441,18 +1443,19 @@ func (w *TokenWallet) MaxOrder(ord *asset.MaxOrderForm) (*asset.SwapEstimate, er ord.RedeemVersion, ord.RedeemAssetID, w.parent) } -func (w *assetWallet) maxOrder(lotSize uint64, maxFeeRate uint64, ver uint32, - redeemVer, redeemAssetID uint32, feeWallet *assetWallet) (*asset.SwapEstimate, error) { +func (w *assetWallet) maxOrder(lotSize uint64, maxFeeRate uint64, serverVer uint32, + redeemServerVer, redeemAssetID uint32, feeWallet *assetWallet) (*asset.SwapEstimate, error) { balance, err := w.Balance() if err != nil { return nil, err } + contractVer := contractVersion(serverVer) // Get the refund gas. - if g := w.gases(ver); g == nil { + if g := w.gases(contractVer); g == nil { return nil, fmt.Errorf("no gas table") } - g, err := w.initGasEstimate(1, ver, redeemVer, redeemAssetID) + g, err := w.initGasEstimate(1, contractVer, contractVersion(redeemServerVer), redeemAssetID) liveEstimateFailed := errors.Is(err, LiveEstimateFailedError) if err != nil && !liveEstimateFailed { return nil, fmt.Errorf("gasEstimate error: %w", err) @@ -1482,7 +1485,7 @@ func (w *assetWallet) maxOrder(lotSize uint64, maxFeeRate uint64, ver uint32, FeeReservesPerLot: feeReservesPerLot, }, nil } - return w.estimateSwap(lots, lotSize, maxFeeRate, ver, feeReservesPerLot) + return w.estimateSwap(lots, lotSize, maxFeeRate, contractVer, feeReservesPerLot) } // PreSwap gets order estimates based on the available funds and the wallet @@ -1509,7 +1512,7 @@ func (w *assetWallet) preSwap(req *asset.PreSwapForm, feeWallet *assetWallet) (* } est, err := w.estimateSwap(req.Lots, req.LotSize, req.MaxFeeRate, - req.Version, maxEst.FeeReservesPerLot) + contractVersion(req.Version), maxEst.FeeReservesPerLot) if err != nil { return nil, err } @@ -1525,13 +1528,11 @@ func (w *baseWallet) MaxFundingFees(_ uint32, _ uint64, _ map[string]string) uin } // SingleLotSwapRefundFees returns the fees for a swap transaction for a single lot. -func (w *assetWallet) SingleLotSwapRefundFees(version uint32, feeSuggestion uint64, _ bool) (swapFees uint64, refundFees uint64, err error) { - if version == asset.VersionNewest { - version = contractVersionNewest - } - g := w.gases(version) +func (w *assetWallet) SingleLotSwapRefundFees(serverVer uint32, feeSuggestion uint64, _ bool) (swapFees uint64, refundFees uint64, err error) { + contractVer := contractVersion(serverVer) + g := w.gases(contractVer) if g == nil { - return 0, 0, fmt.Errorf("no gases known for %d version %d", w.assetID, version) + return 0, 0, fmt.Errorf("no gases known for %d contract version %d", w.assetID, contractVersion(serverVer)) } return g.Swap * feeSuggestion, g.Refund * feeSuggestion, nil } @@ -1539,7 +1540,7 @@ func (w *assetWallet) SingleLotSwapRefundFees(version uint32, feeSuggestion uint // estimateSwap prepares an *asset.SwapEstimate. The estimate does not include // funds that might be locked for refunds. func (w *assetWallet) estimateSwap( - lots, lotSize uint64, maxFeeRate uint64, ver uint32, feeReservesPerLot uint64, + lots, lotSize uint64, maxFeeRate uint64, contractVer uint32, feeReservesPerLot uint64, ) (*asset.SwapEstimate, error) { if lots == 0 { @@ -1554,7 +1555,7 @@ func (w *assetWallet) estimateSwap( } feeRateGwei := dexeth.WeiToGweiCeil(feeRate) // This is an estimate, so we use the (lower) live gas estimates. - oneSwap, err := w.estimateInitGas(w.ctx, 1, ver) + oneSwap, err := w.estimateInitGas(w.ctx, 1, contractVer) if err != nil { return nil, fmt.Errorf("(%d) error estimating swap gas: %v", w.assetID, err) } @@ -1584,7 +1585,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 } @@ -1598,15 +1599,11 @@ func (w *assetWallet) PreRedeem(req *asset.PreRedeemForm) (*asset.PreRedeem, err } // SingleLotRedeemFees returns the fees for a redeem transaction for a single lot. -func (w *assetWallet) SingleLotRedeemFees(version uint32, feeSuggestion uint64) (fees uint64, err error) { - if version == asset.VersionNewest { - version = contractVersionNewest - } - g := w.gases(version) +func (w *assetWallet) SingleLotRedeemFees(serverVer uint32, feeSuggestion uint64) (fees uint64, err error) { + g := w.gases(contractVersion(serverVer)) if g == nil { - return 0, fmt.Errorf("no gases known for %d version %d", w.assetID, version) + return 0, fmt.Errorf("no gases known for %d, constract version %d", w.assetID, contractVersion(serverVer)) } - return g.Redeem * feeSuggestion, nil } @@ -1663,7 +1660,9 @@ func (w *ETHWallet) FundOrder(ord *asset.Order) (asset.Coins, []dex.Bytes, uint6 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, 0, fmt.Errorf("error estimating swap gas: %v", err) @@ -1701,7 +1700,8 @@ func (w *TokenWallet) FundOrder(ord *asset.Order) (asset.Coins, []dex.Bytes, uin dex.BipIDSymbol(w.assetID), ord.MaxFeeRate, w.gasFeeLimit()) } - approvalStatus, err := w.approvalStatus(ord.Version) + contractVer := contractVersion(ord.Version) + approvalStatus, err := w.approvalStatus(contractVer) if err != nil { return nil, nil, 0, fmt.Errorf("error getting approval status: %v", err) } @@ -1715,7 +1715,7 @@ func (w *TokenWallet) FundOrder(ord *asset.Order) (asset.Coins, []dex.Bytes, uin return nil, nil, 0, fmt.Errorf("unknown approval status %d", approvalStatus) } - g, err := w.initGasEstimate(int(ord.MaxSwapCount), ord.Version, + g, err := w.initGasEstimate(int(ord.MaxSwapCount), contractVer, ord.RedeemVersion, ord.RedeemAssetID) if err != nil { return nil, nil, 0, fmt.Errorf("error estimating swap gas: %v", err) @@ -1896,10 +1896,10 @@ func (w *assetWallet) initGasEstimate(n int, initVer, redeemVer, redeemAssetID u // cannot get a live estimate from the contractor, which will happen if the // wallet has no balance. A live gas estimate will always be attempted, and used // if our expected gas values are lower (anomalous). -func (w *assetWallet) swapGas(n int, ver uint32) (oneSwap, nSwap uint64, err error) { - g := w.gases(ver) +func (w *assetWallet) swapGas(n int, contractVer uint32) (oneSwap, nSwap uint64, err error) { + g := w.gases(contractVer) if g == nil { - return 0, 0, fmt.Errorf("no gases known for %d version %d", w.assetID, ver) + return 0, 0, fmt.Errorf("no gases known for %d contract version %d", w.assetID, contractVer) } oneSwap = g.Swap @@ -1928,7 +1928,7 @@ func (w *assetWallet) swapGas(n int, ver uint32) (oneSwap, nSwap uint64, err err // If a live estimate is greater than our estimate from configured values, // use the live estimate with a warning. - gasEst, err := w.estimateInitGas(w.ctx, nMax, ver) + gasEst, err := w.estimateInitGas(w.ctx, nMax, contractVer) if err != nil { err = errors.Join(err, LiveEstimateFailedError) return @@ -1945,7 +1945,7 @@ func (w *assetWallet) swapGas(n int, ver uint32) (oneSwap, nSwap uint64, err err // transactions and add the estimate of the remainder. gasEst *= uint64(nFull) if nRemain > 0 { - remainEst, err := w.estimateInitGas(w.ctx, nRemain, ver) + remainEst, err := w.estimateInitGas(w.ctx, nRemain, contractVer) if err != nil { w.log.Errorf("(%d) error estimating swap gas for remainder: %v", w.assetID, err) return 0, 0, err @@ -1965,8 +1965,8 @@ func (w *assetWallet) swapGas(n int, ver uint32) (oneSwap, nSwap uint64, err err // 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) } @@ -1980,10 +1980,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 @@ -2101,13 +2101,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 } @@ -2128,7 +2128,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 @@ -2137,8 +2137,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 @@ -2176,9 +2176,9 @@ func (w *ETHWallet) Swap(swaps *asset.Swaps) ([]asset.Receipt, asset.Coin, uint6 swapVal += contract.Value } - // Set the gas limit as high as reserves will allow. + contractVer := contractVersion(swaps.Version) n := len(swaps.Contracts) - oneSwap, nSwap, err := w.swapGas(n, swaps.Version) + oneSwap, nSwap, err := w.swapGas(n, contractVer) if err != nil { return fail("error getting gas fees: %v", err) } @@ -2210,7 +2210,7 @@ func (w *ETHWallet) Swap(swaps *asset.Swaps) ([]asset.Receipt, asset.Coin, uint6 return fail("Swap: failed to get network tip cap: %w", err) } - tx, err := w.initiate(w.ctx, w.assetID, swaps.Contracts, gasLimit, maxFeeRate, tipRate, swaps.Version) + tx, err := w.initiate(w.ctx, w.assetID, swaps.Contracts, gasLimit, maxFeeRate, tipRate, contractVer) if err != nil { return fail("Swap: initiate error: %w", err) } @@ -2218,15 +2218,13 @@ func (w *ETHWallet) Swap(swaps *asset.Swaps) ([]asset.Receipt, asset.Coin, uint6 txHash := tx.Hash() receipts := make([]asset.Receipt, 0, n) 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: w.versionedContracts[swaps.Version].String(), + locator: acToLocator(contractVer, swap, dexeth.GweiToWei(swap.Value), w.addr), + contractVer: contractVer, + contractAddr: w.versionedContracts[contractVer].String(), }) } @@ -2241,6 +2239,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, evmValue *big.Int, 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: evmValue, + 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. @@ -2273,7 +2291,8 @@ func (w *TokenWallet) Swap(swaps *asset.Swaps) ([]asset.Receipt, asset.Coin, uin } n := len(swaps.Contracts) - oneSwap, nSwap, err := w.swapGas(n, swaps.Version) + contractVer := contractVersion(swaps.Version) + oneSwap, nSwap, err := w.swapGas(n, contractVer) if err != nil { return fail("error getting gas fees: %v", err) } @@ -2299,7 +2318,7 @@ func (w *TokenWallet) Swap(swaps *asset.Swaps) ([]asset.Receipt, asset.Coin, uin return fail("Swap: failed to get network tip cap: %w", err) } - tx, err := w.initiate(w.ctx, w.assetID, swaps.Contracts, gasLimit, maxFeeRate, tipRate, swaps.Version) + tx, err := w.initiate(w.ctx, w.assetID, swaps.Contracts, gasLimit, maxFeeRate, tipRate, contractVer) if err != nil { return fail("Swap: initiate error: %w", err) } @@ -2313,14 +2332,12 @@ func (w *TokenWallet) Swap(swaps *asset.Swaps) ([]asset.Receipt, asset.Coin, uin txHash := tx.Hash() receipts := make([]asset.Receipt, 0, n) 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.evmify(swap.Value), w.addr), + contractVer: contractVer, contractAddr: contractAddr, }) } @@ -2371,16 +2388,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.DecodeContractData(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 { @@ -2395,23 +2415,23 @@ 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) + status, vector, err := w.statusAndVector(w.ctx, locator, contractVer) if err != nil { return nil, nil, 0, fmt.Errorf("error finding swap state: %w", err) } - if swapData.State != dexeth.SSInitiated { + if status.Step != dexeth.SSInitiated { return nil, nil, 0, asset.ErrSwapNotInitiated } - redeemedValue += w.atomize(swapData.Value) + redeemedValue += w.atomize(vector.Value) } g := w.gases(contractVer) @@ -2519,7 +2539,7 @@ func recoverPubkey(msgHash, sig []byte) ([]byte, error) { // 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, dexeth.ContractVersionERC20, func(c tokenContractor) error { bal, err = c.balance(w.ctx) return err }) @@ -2527,8 +2547,8 @@ 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(version uint32) (allowance *big.Int, err error) { - return allowance, w.withTokenContractor(w.assetID, version, func(c tokenContractor) error { +func (w *assetWallet) tokenAllowance() (allowance *big.Int, err error) { + return allowance, w.withTokenContractor(w.assetID, dexeth.ContractVersionERC20, func(c tokenContractor) error { allowance, err = c.allowance(w.ctx) return err }) @@ -2581,7 +2601,7 @@ func (w *assetWallet) approvalStatus(version uint32) (asset.ApprovalStatus, erro w.approvalsMtx.Lock() defer w.approvalsMtx.Unlock() - currentAllowance, err := w.tokenAllowance(version) + currentAllowance, err := w.tokenAllowance() if err != nil { return asset.NotApproved, fmt.Errorf("error retrieving current allowance: %w", err) } @@ -2754,8 +2774,8 @@ func (w *ETHWallet) ReserveNRedemptions(n uint64, ver uint32, maxFeeRate uint64) // ReserveNRedemptions locks funds for redemption. It is an error if there // is insufficient spendable balance. // 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(serverVer) if g == nil { return 0, fmt.Errorf("no gas table") } @@ -2800,8 +2820,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") } @@ -2810,8 +2830,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") } @@ -2887,32 +2907,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.DecodeContractData(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 + } + tokenAddr, txVectors, err := dexeth.ParseInitiateDataV1(tx.Data()) + if err != nil { + return nil, fmt.Errorf("AuditContract: failed to parse initiate data: %w", err) + } + if tokenAddr != w.tokenAddr { + return nil, fmt.Errorf("address in init tx data is incorrect. %s != %s", tokenAddr, w.tokenAddr) + } + txVec, ok := txVectors[vec.SecretHash] + if !ok { + return nil, errors.New("AuditContract: tx does not initiate secret hash") + } + if !dexeth.CompareVectors(vec, txVec) { + return nil, fmt.Errorf("tx vector doesn't match expectation. %+v != %+v", txVec, vec) + } + val = w.atomize(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, } return &asset.AuditInfo{ - Recipient: initiation.Participant.Hex(), - Expiration: initiation.LockTime, + Recipient: participant, + Expiration: lockTime, Coin: coin, Contract: contract, - SecretHash: secretHash[:], + SecretHash: secretHashB, }, nil } @@ -2930,26 +2991,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.DecodeContractData(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. @@ -2967,22 +3027,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, makerAddr string, err error) { +func (eth *baseWallet) sendFindRedemptionResult(req *findRedemptionRequest, locator, secret []byte, makerAddr string, err error) { select { case req.res <- &findRedemptionResult{secret: secret, makerAddr: makerAddr, 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 } @@ -2999,13 +3058,13 @@ func (w *assetWallet) FindRedemption(ctx context.Context, _, contract dex.Bytes) // contract, so we are basically doing the next best thing here. const coinIDTmpl = coinIDTakerFoundMakerRedemption + "%s" - contractVer, secretHash, err := dexeth.DecodeContractData(contract) + contractVer, locator, err := dexeth.DecodeContractData(contract) if err != nil { return nil, nil, err } // See if it's ready right away. - secret, makerAddr, err := w.findSecret(secretHash, contractVer) + secret, makerAddr, err := w.findSecret(locator, contractVer) if err != nil { return nil, nil, err } @@ -3020,14 +3079,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() @@ -3038,11 +3099,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 { @@ -3052,64 +3113,61 @@ func (w *assetWallet) FindRedemption(ctx context.Context, _, contract dex.Bytes) return dex.Bytes(fmt.Sprintf(coinIDTmpl, res.makerAddr)), res.secret[:], nil } -// findSecret returns redemption secret from smart contract that Maker put there -// redeeming Taker swap along with Maker Ethereum account address. Returns empty -// values if Maker hasn't redeemed yet. -func (w *assetWallet) findSecret(secretHash [32]byte, contractVer uint32) ([]byte, string, error) { +func (w *assetWallet) findSecret(locator []byte, contractVer uint32) ([]byte, string, error) { ctx, cancel := context.WithTimeout(w.ctx, 10*time.Second) defer cancel() - swap, err := w.swap(ctx, secretHash, contractVer) + status, vector, err := w.statusAndVector(ctx, locator, contractVer) if err != nil { return nil, "", err } - switch swap.State { + switch status.Step { case dexeth.SSInitiated: return nil, "", nil // no Maker redeem yet, but keep checking case dexeth.SSRedeemed: - return swap.Secret[:], swap.Initiator.String(), nil + return status.Secret[:], vector.From.String(), nil case dexeth.SSNone: - return nil, "", fmt.Errorf("swap %x does not exist", secretHash) + return nil, "", fmt.Errorf("swap %x does not exist", locator) case dexeth.SSRefunded: - return nil, "", fmt.Errorf("swap %x is already refunded", secretHash) + return nil, "", fmt.Errorf("swap %x is already refunded", 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.DecodeContractData(contract) if err != nil { return nil, fmt.Errorf("Refund: failed to decode contract: %w", err) } - swap, err := w.swap(w.ctx, secretHash, version) + status, vector, err := w.statusAndVector(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 locator %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) } maxFeeRate := dexeth.GweiToWei(feeRate) @@ -3118,7 +3176,7 @@ func (w *assetWallet) Refund(_, contract dex.Bytes, feeRate uint64) (dex.Bytes, return nil, fmt.Errorf("Refund: failed to get network tip cap: %w", err) } - tx, err := w.refund(secretHash, w.atomize(swap.Value), maxFeeRate, tipRate, version) + tx, err := w.refund(locator, w.atomize(vector.Value), maxFeeRate, tipRate, contractVer) if err != nil { return nil, fmt.Errorf("Refund: failed to call refund: %w", err) } @@ -3236,7 +3294,7 @@ func (w *TokenWallet) canSend(value uint64, verifyBalance, isPreEstimate bool) ( } maxFeeRateGwei := dexeth.WeiToGweiCeil(maxFeeRate) - g := w.gases(contractVersionNewest) + g := w.gases(dexeth.ContractVersionERC20) if g == nil { return 0, nil, nil, fmt.Errorf("gas table not found") } @@ -3307,7 +3365,7 @@ func (w *TokenWallet) EstimateSendTxFee(addr string, value, _ uint64, _, maxWith // StandardSendFees returns the fees for a simple send tx. func (w *TokenWallet) StandardSendFee(feeRate uint64) uint64 { - g := w.gases(contractVersionNewest) + g := w.gases(dexeth.ContractVersionNewest) if g == nil { w.log.Errorf("error getting gases for token %s", w.token.Name) return 0 @@ -3350,24 +3408,34 @@ func (w *assetWallet) SwapConfirmations(ctx context.Context, coinID dex.Bytes, c ctx, cancel := context.WithTimeout(ctx, confCheckTimeout) defer cancel() - swapData, err := w.swap(ctx, secretHash, contractVer) + tip := w.tipHeight() + + status, err := w.status(ctx, secretHash, contractVer) if err != nil { return 0, false, fmt.Errorf("error finding swap state: %w", err) } - if swapData.State == dexeth.SSNone { - // Check if we know about the tx ourselves. If it's not in pendingTxs - // or the database, assume it's lost. + if status.Step == dexeth.SSNone { return 0, false, asset.ErrSwapNotInitiated } - spent = swapData.State >= dexeth.SSRedeemed - tip := w.tipHeight() + spent = status.Step >= dexeth.SSRedeemed + if spent && contractVer == 1 { + // Gotta get the confirimations directly. + 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) + } + return + } + // TODO: If tip < swapData.BlockHeight (which has been observed), what does // that mean? Are we using the wrong provider in a multi-provider setup? How // do we resolve provider relevance? - if tip >= swapData.BlockHeight { - confs = uint32(tip - swapData.BlockHeight + 1) + if tip >= status.BlockHeight { + confs = uint32(w.tipHeight() - status.BlockHeight + 1) } return } @@ -3469,37 +3537,6 @@ func (eth *assetWallet) DynamicRedemptionFeesPaid(ctx context.Context, coinID, c return eth.swapOrRedemptionFeesPaid(ctx, coinID, contractData, false) } -// extractSecretHashes extracts the secret hashes from the reedeem or swap tx -// data. The returned hashes are sorted lexicographically. -func extractSecretHashes(isInit bool, txData []byte, contractVer uint32) (secretHashes [][]byte, _ error) { - defer func() { - sort.Slice(secretHashes, func(i, j int) bool { return bytes.Compare(secretHashes[i], secretHashes[j]) < 0 }) - }() - if isInit { - inits, err := dexeth.ParseInitiateData(txData, contractVer) - if err != nil { - return nil, fmt.Errorf("invalid initiate data: %v", err) - } - secretHashes = make([][]byte, 0, len(inits)) - for k := range inits { - copyK := k - secretHashes = append(secretHashes, copyK[:]) - } - return secretHashes, nil - } - // redeem - redeems, err := dexeth.ParseRedeemData(txData, contractVer) - if err != nil { - return nil, fmt.Errorf("invalid redeem data: %v", err) - } - secretHashes = make([][]byte, 0, len(redeems)) - for k := range redeems { - copyK := k - secretHashes = append(secretHashes, copyK[:]) - } - return secretHashes, nil -} - // swapOrRedemptionFeesPaid returns exactly how much gwei was used to send an // initiation or redemption transaction. It also returns the secret hashes // included with this init or redeem. Secret hashes are sorted so returns are @@ -3514,16 +3551,15 @@ func (w *baseWallet) swapOrRedemptionFeesPaid( coinID dex.Bytes, contractData dex.Bytes, isInit bool, -) (fee uint64, secretHashes [][]byte, err error) { - - var txHash common.Hash - copy(txHash[:], coinID) - - contractVer, secretHash, err := dexeth.DecodeContractData(contractData) +) (fee uint64, locators [][]byte, err error) { + contractVer, locator, err := dexeth.DecodeContractData(contractData) if err != nil { return 0, nil, err } + var txHash common.Hash + copy(txHash[:], coinID) + tip := w.tipHeight() var blockNum uint64 @@ -3539,7 +3575,7 @@ func (w *baseWallet) swapOrRedemptionFeesPaid( if confs := safeConfs(tip, blockNum); confs < w.finalizeConfs { return 0, nil, asset.ErrNotEnoughConfirms } - secretHashes, err = extractSecretHashes(isInit, tx.Data(), contractVer) + locators, _, err = extractSecretHashes(tx, contractVer, isInit) return } @@ -3557,21 +3593,84 @@ func (w *baseWallet) swapOrRedemptionFeesPaid( bigFees := new(big.Int).Mul(receipt.EffectiveGasPrice, big.NewInt(int64(receipt.GasUsed))) fee = dexeth.WeiToGweiCeil(bigFees) - secretHashes, err = extractSecretHashes(isInit, tx.Data(), contractVer) + locators, _, err = extractSecretHashes(tx, contractVer, isInit) if err != nil { return 0, nil, err } + + 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.WeiToGweiCeil(bigFees), locators, nil +} + +// extractSecretHashes extracts the secret hashes from the reedeem or swap tx +// data. The returned hashes are sorted lexicographically. +func extractSecretHashes(tx *types.Transaction, contractVer uint32, isInit bool) (locators, secretHashes [][]byte, err error) { + defer func() { + sort.Slice(secretHashes, func(i, j int) bool { return bytes.Compare(secretHashes[i], secretHashes[j]) < 0 }) + }() + + 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 + 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) } - return dexeth.WeiToGweiCeil(bigFees), secretHashes, nil } // RegFeeConfirmations gets the number of confirmations for the specified @@ -3768,7 +3867,7 @@ func (w *assetWallet) confirmRedemption(coinID dex.Bytes, redemption *asset.Rede var txHash common.Hash copy(txHash[:], coinID) - contractVer, secretHash, err := dexeth.DecodeContractData(redemption.Spends.Contract) + contractVer, locator, err := dexeth.DecodeContractData(redemption.Spends.Contract) if err != nil { return nil, fmt.Errorf("failed to decode contract data: %w", err) } @@ -3823,11 +3922,11 @@ func (w *assetWallet) confirmRedemption(coinID dex.Bytes, redemption *asset.Rede } // We weren't able to redeem. Perhaps fees were too low, but we'll // check the status in the contract for a couple of other conditions. - swap, err := w.swap(w.ctx, secretHash, contractVer) + status, err := w.status(w.ctx, locator, contractVer) if err != nil { return nil, fmt.Errorf("error pulling swap data from contract: %v", err) } - switch swap.State { + switch status.Step { case dexeth.SSRedeemed: w.log.Infof("Redemption in tx %s was apparently redeemed by another tx. OK.", txHash) return confStatus(w.finalizeConfs, w.finalizeConfs, txHash), nil @@ -3901,15 +4000,16 @@ func (w *baseWallet) localTxStatus(txHash common.Hash) (_ bool, s *walletTxStatu // checkFindRedemptions checks queued findRedemptionRequests. func (w *assetWallet) checkFindRedemptions() { - for secretHash, req := range w.findRedemptionRequests() { + for loc, req := range w.findRedemptionRequests() { if w.ctx.Err() != nil { return } - secret, makerAddr, err := w.findSecret(secretHash, req.contractVer) + locator := []byte(loc) + secret, makerAddr, 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, makerAddr, nil) + w.sendFindRedemptionResult(req, locator, secret, makerAddr, nil) } } } @@ -4060,6 +4160,10 @@ func (w *assetWallet) getConfirmedBalance() (*big.Int, error) { return reqBal, nil } +func (w *assetWallet) contractors() map[uint32]contractor { + return map[uint32]contractor{0: w.contractorV0, 1: w.contractorV1} +} + func (w *assetWallet) balanceWithTxPool() (*Balance, error) { isToken := w.assetID != w.baseChainID confirmed, err := w.getConfirmedBalance() @@ -4129,7 +4233,7 @@ func (w *assetWallet) balanceWithTxPool() (*Balance, error) { } var contractOut uint64 - for ver, c := range w.contractors { + for ver, c := range w.contractors() { in, out, err := c.value(w.ctx, tx) if err != nil { w.log.Errorf("version %d contractor incomingValue error: %v", ver, err) @@ -4228,7 +4332,7 @@ func (w *ETHWallet) sendToAddr(addr common.Address, amt uint64, maxFeeRate, tipR // sendToAddr sends funds to the address. func (w *TokenWallet) sendToAddr(addr common.Address, amt uint64, maxFeeRate, tipRate *big.Int) (tx *types.Transaction, err error) { - g := w.gases(contractVersionNewest) + g := w.gases(dexeth.ContractVersionERC20) if g == nil { return nil, fmt.Errorf("no gas table") } @@ -4242,7 +4346,7 @@ func (w *TokenWallet) sendToAddr(addr common.Address, amt uint64, maxFeeRate, ti txType = asset.SelfSend } recipient := addr.Hex() - return tx, txType, amt, &recipient, w.withTokenContractor(w.assetID, contractVersionNewest, func(c tokenContractor) error { + return tx, txType, amt, &recipient, w.withTokenContractor(w.assetID, dexeth.ContractVersionERC20, func(c tokenContractor) error { tx, err = c.transfer(txOpts, addr, w.evmify(amt)) if err != nil { return err @@ -4253,10 +4357,27 @@ func (w *TokenWallet) sendToAddr(addr common.Address, amt uint64, maxFeeRate, ti } -// 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 }) } @@ -4300,23 +4421,23 @@ func (w *assetWallet) estimateInitGas(ctx context.Context, numSwaps int, contrac // nodeclient_harness_test.go suite (GetGasEstimates, testRedeemGas, etc.). // Never use this with a public RPC provider, especially as maker, since it // reveals the secret keys. -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 }) } // loadContractors prepares the token contractors and add them to the map. -func (w *assetWallet) loadContractors() error { +func (w *assetWallet) loadContractors(parent *assetWallet) error { token, found := w.tokens[w.assetID] if !found { return fmt.Errorf("token %d not found", w.assetID) @@ -4326,32 +4447,41 @@ func (w *assetWallet) loadContractors() error { return fmt.Errorf("token %d not found", w.assetID) } - for ver := range netToken.SwapContracts { - constructor, found := tokenContractorConstructors[ver] - if !found { - w.log.Errorf("contractor constructor not found for token %s, version %d", token.Name, ver) - continue - } - c, err := constructor(w.net, token, w.addr, w.node.contractBackend()) + if _, found := netToken.SwapContracts[0]; found { + c, err := newV0TokenContractor(w.net, token, w.addr, w.node.contractBackend()) if err != nil { - return fmt.Errorf("error constructing token %s contractor version %d: %w", token.Name, ver, err) + return fmt.Errorf("error constructing token %s contractor version 0: %w", token.Name, err) } - if netToken.Address != c.tokenAddress() { return fmt.Errorf("wrong %s token address. expected %s, got %s", token.Name, netToken.Address, c.tokenAddress()) } + w.contractorV0 = c + } - w.contractors[ver] = c + if _, found := netToken.SwapContracts[1]; found { + if parent.contractorV1 == nil { + return errors.New("can't construct version 1 contractor if parent doesn't have the unified contractor") + } + cgen, ok := parent.contractorV1.(unifiedContractor) + if !ok { + return errors.New("parent contractor ain't unified") + } + c, err := cgen.tokenContractor(token) + if err != nil { + return fmt.Errorf("error constructing version 1 token %s contractor: %w", token.Name, err) + } + w.contractorV1 = c } return nil } // 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 == dexeth.ContractVersionERC20 { + // For ERC20 methods, use the most recent contractor version. var bestVer uint32 var bestContractor contractor - for ver, c := range w.contractors { + for ver, c := range w.contractors() { if ver >= bestVer { bestContractor = c bestVer = ver @@ -4359,11 +4489,20 @@ func (w *assetWallet) withContractor(contractVer uint32, f func(contractor) erro } return f(bestContractor) } - contractor, found := w.contractors[contractVer] - if !found { - return fmt.Errorf("no version %d contractor for asset %d", contractVer, w.assetID) + var c contractor + switch contractVer { + case 0: + if w.contractorV0 == nil { + return errors.New("no version 0 contractor") + } + c = w.contractorV0 + case 1: + if w.contractorV1 == nil { + return errors.New("no version 1 contractor") + } + c = w.contractorV1 } - return f(contractor) + return f(c) } // withTokenContractor runs the provided function with the tokenContractor. @@ -4380,7 +4519,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, dexeth.ContractVersionERC20, func(c tokenContractor) error { gas, err = c.estimateApproveGas(w.ctx, newGas) return err }) @@ -4388,8 +4527,9 @@ 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. +// TODO: Delete this and contractor methods. Unused. 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, dexeth.ContractVersionERC20, func(c tokenContractor) error { gas, err = c.estimateTransferGas(w.ctx, w.evmify(val)) return err }) @@ -4443,7 +4583,7 @@ func (w *assetWallet) redeem( // 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, amt uint64, maxFeeRate, tipRate *big.Int, contractVer uint32) (tx *types.Transaction, err error) { +func (w *assetWallet) refund(locator []byte, amt uint64, maxFeeRate, tipRate *big.Int, 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) @@ -4454,30 +4594,34 @@ func (w *assetWallet) refund(secretHash [32]byte, amt uint64, maxFeeRate, tipRat return nil, 0, 0, nil, err } return tx, asset.Refund, amt, nil, w.withContractor(contractVer, func(c contractor) error { - tx, err = c.refund(txOpts, secretHash) + tx, err = c.refund(txOpts, locator) return err }) }) } -// isRedeemable checks if the swap identified by secretHash is redeemable using -// secret. This must NOT be a contractor call. -func (w *assetWallet) isRedeemable(secretHash [32]byte, secret [32]byte, contractVer uint32) (redeemable bool, err error) { - swap, err := w.swap(w.ctx, secretHash, contractVer) +// isRedeemable checks if the swap identified by secretHash is redeemable using secret. +func (w *assetWallet) isRedeemable(locator []byte, secret [32]byte, contractVer uint32) (redeemable bool, err error) { + status, err := w.status(w.ctx, locator, contractVer) if err != nil { return false, err } - if swap.State != dexeth.SSInitiated { + if status.Step != dexeth.SSInitiated { return false, nil } - return w.ValidateSecret(secret[:], secretHash[:]), nil + vector, err := w.vector(w.ctx, locator, contractVer) + if err != nil { + return false, err + } + + return w.ValidateSecret(secret[:], vector.SecretHash[:]), nil } -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 }) } @@ -4485,7 +4629,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 { @@ -5221,7 +5364,7 @@ func (w *ETHWallet) WalletTransaction(ctx context.Context, txID string) (*asset. // transaction, finds the log that sends tokens to the wallet's address, // and returns the value of the transfer. func (w *TokenWallet) extractValueFromTransferLog(receipt *types.Receipt) (v uint64, err error) { - return v, w.withTokenContractor(w.assetID, contractVersionNewest, func(c tokenContractor) error { + return v, w.withTokenContractor(w.assetID, dexeth.ContractVersionERC20, func(c tokenContractor) error { v, err = c.parseTransfer(receipt) return err }) @@ -5367,18 +5510,26 @@ func quickNode(ctx context.Context, walletDir string, contractVer uint32, if ctor == nil { return nil, nil, fmt.Errorf("no contractor constructor for eth contract version %d", contractVer) } - c, err = ctor(wParams.ContractAddr, cl.address(), cl.contractBackend()) + c, err = ctor(net, wParams.ContractAddr, cl.address(), cl.contractBackend()) if err != nil { return nil, nil, fmt.Errorf("contractor constructor error: %v", err) } } else { - ctor := tokenContractorConstructors[contractVer] - if ctor == nil { - return nil, nil, fmt.Errorf("no token contractor constructor for eth contract version %d", contractVer) - } - c, err = ctor(net, wParams.Token, cl.address(), cl.contractBackend()) - if err != nil { - return nil, nil, fmt.Errorf("token contractor constructor error: %v", err) + switch contractVer { + case 0: + c, err = newV0TokenContractor(net, wParams.Token, cl.address(), cl.contractBackend()) + if err != nil { + return nil, nil, fmt.Errorf("token contractor constructor error: %v", err) + } + case 1: + bc, err := newV1Contractor(net, wParams.ContractAddr, cl.address(), cl.contractBackend()) + if err != nil { + return nil, nil, fmt.Errorf("base contractor constructor error: %v", err) + } + c, err = bc.(unifiedContractor).tokenContractor(wParams.Token) + if err != nil { + return nil, nil, fmt.Errorf("tokenContractor error: %v", err) + } } } success = true @@ -5491,9 +5642,26 @@ func (getGas) ReadCredentials(chain, credentialsPath string, net dex.Network) (a return } -func getGetGasClientWithEstimatesAndBalances(ctx context.Context, net dex.Network, contractVer uint32, maxSwaps int, - walletDir string, providers []string, seed []byte, wParams *GetGasWalletParams, log dex.Logger) (cl *multiRPCClient, c contractor, - ethReq, swapReq, feeRate uint64, ethBal, tokenBal *big.Int, err error) { +func getGetGasClientWithEstimatesAndBalances( + ctx context.Context, + net dex.Network, + contractVer uint32, + maxSwaps int, + walletDir string, + providers []string, + seed []byte, + wParams *GetGasWalletParams, + log dex.Logger, +) ( + cl *multiRPCClient, + c contractor, + ethReq, + swapReq, + feeRate uint64, + ethBal, + tokenBal *big.Int, + err error, +) { cl, c, err = quickNode(ctx, walletDir, contractVer, seed, providers, wParams, net, log) if err != nil { @@ -5744,6 +5912,7 @@ func (getGas) returnFunds( } remainder := ethBal - fees + txOpts, err := cl.txOpts(ctx, remainder, defaultSendGasLimit, maxFeeRate, tipRate, nil) if err != nil { return fmt.Errorf("error generating tx opts: %w", err) @@ -5768,6 +5937,10 @@ func (getGas) returnFunds( func (getGas) Estimate(ctx context.Context, net dex.Network, assetID, contractVer uint32, maxSwaps int, credentialsPath string, wParams *GetGasWalletParams, log dex.Logger) error { + if *wParams.Gas == (dexeth.Gases{}) { + return fmt.Errorf("empty gas table. put some estimates in VersionedGases or Tokens for this contract") + } + symbol := dex.BipIDSymbol(assetID) log.Infof("Getting gas estimates for up to %d swaps of asset %s, contract version %d on %s", maxSwaps, symbol, contractVer, symbol) @@ -5813,8 +5986,9 @@ func (getGas) Estimate(ctx context.Context, net dex.Network, assetID, contractVe var approvalClient *multiRPCClient var approvalContractor tokenContractor + evmify := dexeth.GweiToWei if isToken { - + evmify = wParams.Token.AtomicToEVM atomicBal := wParams.Token.EVMToAtomic(tokenBal) convUnit := ui.Conventional.Unit @@ -5857,7 +6031,7 @@ func (getGas) Estimate(ctx context.Context, net dex.Network, assetID, contractVe } log.Debugf("Getting gas estimates") - return getGasEstimates(ctx, cl, approvalClient, c, approvalContractor, maxSwaps, wParams.Gas, log) + return getGasEstimates(ctx, cl, approvalClient, c, approvalContractor, maxSwaps, contractVer, wParams.Gas, evmify, log) } // getGasEstimate is used to get a gas table for an asset's contract(s). The @@ -5872,7 +6046,7 @@ func (getGas) Estimate(ctx context.Context, net dex.Network, assetID, contractVe // gas estimate. These are only needed when the asset is a token. For eth, they // can be nil. func getGasEstimates(ctx context.Context, cl, acl ethFetcher, c contractor, ac tokenContractor, - maxSwaps int, g *dexeth.Gases, log dex.Logger) (err error) { + maxSwaps int, contractVer uint32, g *dexeth.Gases, evmify func(v uint64) *big.Int, log dex.Logger) (err error) { tc, isToken := c.(tokenContractor) @@ -5909,6 +6083,8 @@ func getGasEstimates(ctx context.Context, cl, acl ethFetcher, c contractor, ac t return fmt.Errorf("error getting network fees: %v", err) } + maxFeeRate := new(big.Int).Add(tipRate, new(big.Int).Mul(baseRate, big.NewInt(2))) + defer func() { if len(stats.swaps) == 0 { return @@ -5949,10 +6125,15 @@ func getGasEstimates(ctx context.Context, cl, acl ethFetcher, c contractor, ac t fmt.Printf(" %+v \n", stats.transfers) }() + logTx := func(tag string, n int, tx *types.Transaction) { + log.Infof("%s %d tx, hash = %s, nonce = %d, maxFeeRate = %s, tip cap = %s", + tag, n, tx.Hash(), tx.Nonce(), tx.GasFeeCap(), tx.GasTipCap()) + } + // Estimate approve for tokens. if isToken { sendApprove := func(cl ethFetcher, c tokenContractor) error { - txOpts, err := cl.txOpts(ctx, 0, g.Approve*2, baseRate, tipRate, nil) + txOpts, err := cl.txOpts(ctx, 0, g.Approve*2, maxFeeRate, tipRate, nil) if err != nil { return fmt.Errorf("error constructing signed tx opts for approve: %w", err) } @@ -5960,6 +6141,7 @@ func getGasEstimates(ctx context.Context, cl, acl ethFetcher, c contractor, ac t if err != nil { return fmt.Errorf("error estimating approve gas: %w", err) } + logTx("Approve", 1, tx) if err = waitForConfirmation(ctx, "approval", cl, tx.Hash(), log); err != nil { return fmt.Errorf("error waiting for approve transaction: %w", err) } @@ -5986,7 +6168,7 @@ func getGasEstimates(ctx context.Context, cl, acl ethFetcher, c contractor, ac t return fmt.Errorf("error sending approve transaction for the initiator: %w", err) } - txOpts, err := cl.txOpts(ctx, 0, g.Transfer*2, baseRate, tipRate, nil) + txOpts, err := cl.txOpts(ctx, 0, g.Transfer*2, maxFeeRate, tipRate, nil) if err != nil { return fmt.Errorf("error constructing signed tx opts for transfer: %w", err) } @@ -5999,6 +6181,7 @@ func getGasEstimates(ctx context.Context, cl, acl ethFetcher, c contractor, ac t if err != nil { return fmt.Errorf("transfer error: %w", err) } + logTx("Transfer", 1, transferTx) if err = waitForConfirmation(ctx, "transfer", cl, transferTx.Hash(), log); err != nil { return fmt.Errorf("error waiting for transfer tx: %w", err) } @@ -6013,9 +6196,11 @@ func getGasEstimates(ctx context.Context, cl, acl ethFetcher, c contractor, ac t stats.transfers = append(stats.transfers, receipt.GasUsed) } + var v uint64 = 1 for n := 1; n <= maxSwaps; n++ { contracts := make([]*asset.Contract, 0, n) secrets := make([][32]byte, 0, n) + lockTime := time.Now().Add(-time.Hour) for i := 0; i < n; i++ { secretB := encode.RandomBytes(32) var secret [32]byte @@ -6023,9 +6208,9 @@ func getGasEstimates(ctx context.Context, cl, acl ethFetcher, c contractor, ac t secretHash := sha256.Sum256(secretB) contracts = append(contracts, &asset.Contract{ Address: cl.address().String(), // trading with self - Value: 1, + Value: v, SecretHash: secretHash[:], - LockTime: uint64(time.Now().Add(-time.Hour).Unix()), + LockTime: uint64(lockTime.Unix()), }) secrets = append(secrets, secret) } @@ -6036,7 +6221,7 @@ func getGasEstimates(ctx context.Context, cl, acl ethFetcher, c contractor, ac t } // Send the inits - txOpts, err := cl.txOpts(ctx, optsVal, g.SwapN(n)*2, baseRate, tipRate, nil) + txOpts, err := cl.txOpts(ctx, optsVal, g.SwapN(n)*2, maxFeeRate, tipRate, nil) if err != nil { return fmt.Errorf("error constructing signed tx opts for %d swaps: %v", n, err) } @@ -6045,6 +6230,7 @@ func getGasEstimates(ctx context.Context, cl, acl ethFetcher, c contractor, ac t if err != nil { return fmt.Errorf("initiate error for %d swaps: %v", n, err) } + logTx("Initiate", n, tx) if err = waitForConfirmation(ctx, "init", cl, tx.Hash(), log); err != nil { return fmt.Errorf("error waiting for init tx to be mined: %w", err) } @@ -6055,13 +6241,11 @@ func getGasEstimates(ctx context.Context, cl, acl ethFetcher, c contractor, ac t if err = checkTxStatus(receipt, txOpts.GasLimit); err != nil { return fmt.Errorf("init tx failed status check: %w", err) } - log.Infof("%d gas used for %d initiation txs", receipt.GasUsed, n) + log.Infof("%d gas used for %d initiations in tx %s", receipt.GasUsed, n, tx.Hash()) stats.swaps = append(stats.swaps, receipt.GasUsed) // Estimate a refund - var firstSecretHash [32]byte - copy(firstSecretHash[:], contracts[0].SecretHash) - refundGas, err := c.estimateRefundGas(ctx, firstSecretHash) + refundGas, err := c.estimateRefundGas(ctx, acToLocator(contractVer, contracts[0], evmify(v), cl.address())) if err != nil { return fmt.Errorf("error estimate refund gas: %w", err) } @@ -6072,21 +6256,25 @@ func getGasEstimates(ctx context.Context, cl, acl ethFetcher, c contractor, ac t for i, contract := range contracts { redemptions = append(redemptions, &asset.Redemption{ Spends: &asset.AuditInfo{ + Recipient: cl.address().String(), + Expiration: lockTime, + Contract: dexeth.EncodeContractData(contractVer, acToLocator(contractVer, contract, evmify(v), cl.address())), SecretHash: contract.SecretHash, }, Secret: secrets[i][:], }) } - txOpts, err = cl.txOpts(ctx, 0, g.RedeemN(n)*2, baseRate, tipRate, nil) + txOpts, err = cl.txOpts(ctx, 0, g.RedeemN(n)*2, maxFeeRate, tipRate, nil) if err != nil { return fmt.Errorf("error constructing signed tx opts for %d redeems: %v", n, err) } - log.Debugf("Sending %d redemption txs", n) + log.Debugf("Sending %d redemptions", n) tx, err = c.redeem(txOpts, redemptions) if err != nil { return fmt.Errorf("redeem error for %d swaps: %v", n, err) } + logTx("Redeem", n, tx) if err = waitForConfirmation(ctx, "redeem", cl, tx.Hash(), log); err != nil { return fmt.Errorf("error waiting for redeem tx to be mined: %w", err) } @@ -6097,7 +6285,7 @@ func getGasEstimates(ctx context.Context, cl, acl ethFetcher, c contractor, ac t if err = checkTxStatus(receipt, txOpts.GasLimit); err != nil { return fmt.Errorf("redeem tx failed status check: %w", err) } - log.Infof("%d gas used for %d redemptions", receipt.GasUsed, n) + log.Infof("%d gas used for %d redemptions in tx %s", receipt.GasUsed, n, tx.Hash()) stats.redeems = append(stats.redeems, receipt.GasUsed) } @@ -6128,7 +6316,7 @@ func newTxOpts(ctx context.Context, from common.Address, val, maxGas uint64, max } func gases(contractVer uint32, versionedGases map[uint32]*dexeth.Gases) *dexeth.Gases { - if contractVer != contractVersionNewest { + if contractVer != dexeth.ContractVersionNewest { return versionedGases[contractVer] } var bestVer uint32 diff --git a/client/asset/eth/eth_test.go b/client/asset/eth/eth_test.go index 36e7752fe6..7e2642830a 100644 --- a/client/asset/eth/eth_test.go +++ b/client/asset/eth/eth_test.go @@ -47,11 +47,21 @@ var ( testAddressB = common.HexToAddress("8d83B207674bfd53B418a6E47DA148F5bFeCc652") testAddressC = common.HexToAddress("2b84C791b79Ee37De042AD2ffF1A253c3ce9bc27") - ethGases = dexeth.VersionedGases[0] - tokenGases = dexeth.Tokens[usdcTokenID].NetTokens[dex.Simnet].SwapContracts[0].Gas + ethGasesV0 = dexeth.VersionedGases[0] + tokenGasesV0 = dexeth.Tokens[usdcTokenID].NetTokens[dex.Simnet].SwapContracts[0].Gas + ethGasesV1 = dexeth.VersionedGases[1] + tokenGasesV1 = dexeth.Tokens[usdcTokenID].NetTokens[dex.Simnet].SwapContracts[1].Gas - tETH = &dex.Asset{ - // Version meaning? + tETHV0 = &dex.Asset{ + Version: 0, + ID: 60, + Symbol: "ETH", + MaxFeeRate: 100, + SwapConf: 1, + } + + tETHV1 = &dex.Asset{ + Version: 1, ID: 60, Symbol: "ETH", MaxFeeRate: 100, @@ -66,7 +76,7 @@ var ( SwapConf: 1, } - tToken = &dex.Asset{ + tTokenV0 = &dex.Asset{ ID: usdcTokenID, Symbol: "usdc.eth", Version: 0, @@ -74,6 +84,14 @@ var ( SwapConf: 1, } + tTokenV1 = &dex.Asset{ + ID: usdcTokenID, + Symbol: "dextt.eth", + Version: 1, + MaxFeeRate: 20, + SwapConf: 1, + } + signer = types.LatestSigner(params.AllEthashProtocolChanges) // simBackend = backends.NewSimulatedBackend(core.GenesisAlloc{ @@ -330,6 +348,9 @@ type tContractor struct { redeemGasErr error refundGasErr error redeemGasOverride *uint64 + redeemable bool + redeemableErr error + redeemableMap map[string]bool valueIn map[common.Hash]uint64 valueOut map[common.Hash]uint64 valueErr error @@ -345,6 +366,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.Initiator, + To: swap.Participant, + Value: 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.Initiator, + To: swap.Participant, + Value: 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 @@ -366,8 +457,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 } @@ -376,22 +471,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(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[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 } @@ -1090,7 +1213,7 @@ func newTestNode(assetID uint32) *tMempoolNode { } 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), @@ -1103,7 +1226,7 @@ func newTestNode(assetID uint32) *tMempoolNode { allow: new(big.Int), } if assetID != BipID { - ttc.tContractor.gasEstimates = &tokenGases + ttc.tContractor.gasEstimates = &tokenGasesV0 c = ttc } @@ -1181,8 +1304,9 @@ func tassetWallet(assetID uint32) (asset.Wallet, *assetWallet, *tMempoolNode, co maxRedeemGas: versionedGases[0].Redeem, log: tLogger.SubLogger(strings.ToUpper(dex.BipIDSymbol(assetID))), assetID: assetID, - contractors: map[uint32]contractor{0: c}, - findRedemptionReqs: make(map[[32]byte]*findRedemptionRequest), + contractorV0: c, + contractorV1: c, + findRedemptionReqs: make(map[string]*findRedemptionRequest), evmify: dexeth.GweiToWei, atomize: dexeth.WeiToGwei, pendingTxCheckBal: new(big.Int), @@ -1206,7 +1330,8 @@ func tassetWallet(assetID uint32) (asset.Wallet, *assetWallet, *tMempoolNode, co node.tokenParent = &assetWallet{ baseWallet: aw.baseWallet, log: tLogger.SubLogger("ETH"), - contractors: map[uint32]contractor{0: node.tContractor}, + contractorV0: node.tContractor, + contractorV1: node.tContractor, assetID: BipID, atomize: dexeth.WeiToGwei, pendingApprovals: make(map[uint32]*pendingApproval), @@ -1362,13 +1487,13 @@ func TestBalanceWithMempool(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) } } } @@ -1550,31 +1675,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 { - eth.versionedGases[1] = gasesV1 - } else { - eth.versionedGases[1] = &dexeth.Tokens[usdcTokenID].NetTokens[dex.Simnet].SwapContracts[0].Gas - 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[usdcTokenID].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 := &dexeth.SwapState{Value: dexeth.GweiToWei(1)} - v0Contractor := node.tContractor + v0Contract := dexeth.EncodeContractData(0, secretHash[:]) + v1Vector := dexeth.SwapVector{ + From: testAddressA, + To: testAddressB, + Value: dexeth.GweiToWei(1), + SecretHash: secretHash, + LockTime: uint64(time.Now().Unix()), + } + v1Contract := dexeth.EncodeContractData(1, v1Vector.Locator()) - v0Contractor.swapMap[secretHash] = ss - v1Contractor.swapMap[secretHash] = ss + ss := &dexeth.SwapState{ + Value: dexeth.GweiToWei(1), + } + + node.tContractor.swapMap[secretHash] = ss tests := []struct { name string @@ -1587,7 +1726,7 @@ func testRefund(t *testing.T, assetID uint32) { wantZeroHash bool swapStep dexeth.SwapStep swapErr error - useV1Gases bool + v1 bool }{ { name: "ok", @@ -1600,7 +1739,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", @@ -1641,11 +1780,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{} } @@ -1727,11 +1865,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.tokenContractor.allow = unlimitedAllowance node.tokenParent.node.(*tMempoolNode).bal = dexeth.GweiToWei(walletBalanceGwei) @@ -1905,7 +2043,8 @@ func testFundOrderReturnCoinsFundingCoins(t *testing.T, assetID uint32) { w2, eth2, _, shutdown2 := tassetWallet(assetID) defer shutdown2() eth2.node = node - eth2.contractors[0] = node.tokenContractor + eth2.contractorV0 = node.tokenContractor + eth2.contractorV1 = node.tokenContractor node.tokenContractor.bal = dexeth.GweiToWei(walletBalanceGwei) // Test reloading coins from first order @@ -2021,10 +2160,10 @@ func testFundMultiOrder(t *testing.T, assetID uint32) { defer shutdown() - fromAsset := tETH + fromAsset := tETHV0 swapGas := dexeth.VersionedGases[fromAsset.Version].Swap if assetID != BipID { - fromAsset = tToken + fromAsset = tTokenV0 node.tokenContractor.allow = unlimitedAllowance swapGas = dexeth.Tokens[usdcTokenID].NetTokens[dex.Simnet]. SwapContracts[fromAsset.Version].Gas.Swap @@ -2268,13 +2407,12 @@ func testFundMultiOrder(t *testing.T, assetID uint32) { func TestPreSwap(t *testing.T) { const baseFee, tip = 42, 2 - const feeSuggestion = 90 // ignored by eth's PreSwap + const currentFees = 44 const lotSize = 10e9 - oneFee := ethGases.Swap * tETH.MaxFeeRate - refund := ethGases.Refund * tETH.MaxFeeRate + oneFee := ethGasesV0.Swap * tETHV0.MaxFeeRate + refund := ethGasesV0.Refund * tETHV0.MaxFeeRate oneLock := lotSize + oneFee + refund - - oneFeeToken := tokenGases.Swap*tToken.MaxFeeRate + tokenGases.Refund*tToken.MaxFeeRate + oneFeeToken := tokenGasesV0.Swap*tTokenV0.MaxFeeRate + tokenGasesV0.Refund*tTokenV0.MaxFeeRate type testData struct { name string @@ -2323,9 +2461,9 @@ func TestPreSwap(t *testing.T) { wantLots: 1, wantValue: lotSize, - wantMaxFees: tETH.MaxFeeRate * ethGases.Swap, - wantBestCase: (baseFee + tip) * ethGases.Swap, - wantWorstCase: (baseFee + tip) * ethGases.Swap, + wantMaxFees: tETHV0.MaxFeeRate * ethGasesV0.Swap, + wantBestCase: currentFees * ethGasesV0.Swap, + wantWorstCase: currentFees * ethGasesV0.Swap, }, { name: "one lot enough for fees - token", @@ -2336,9 +2474,9 @@ func TestPreSwap(t *testing.T) { wantLots: 1, wantValue: lotSize, - wantMaxFees: tToken.MaxFeeRate * tokenGases.Swap, - wantBestCase: (baseFee + tip) * tokenGases.Swap, - wantWorstCase: (baseFee + tip) * tokenGases.Swap, + wantMaxFees: tTokenV0.MaxFeeRate * tokenGasesV0.Swap, + wantBestCase: currentFees * tokenGasesV0.Swap, + wantWorstCase: currentFees * tokenGasesV0.Swap, }, { name: "more lots than max lots", @@ -2363,9 +2501,9 @@ func TestPreSwap(t *testing.T) { wantLots: 4, wantValue: 4 * lotSize, - wantMaxFees: 4 * tETH.MaxFeeRate * ethGases.Swap, - wantBestCase: (baseFee + tip) * ethGases.Swap, - wantWorstCase: 4 * (baseFee + tip) * ethGases.Swap, + wantMaxFees: 4 * tETHV0.MaxFeeRate * ethGasesV0.Swap, + wantBestCase: currentFees * ethGasesV0.Swap, + wantWorstCase: 4 * currentFees * ethGasesV0.Swap, }, { name: "fewer than max lots - token", @@ -2376,9 +2514,9 @@ func TestPreSwap(t *testing.T) { wantLots: 4, wantValue: 4 * lotSize, - wantMaxFees: 4 * tToken.MaxFeeRate * tokenGases.Swap, - wantBestCase: (baseFee + tip) * tokenGases.Swap, - wantWorstCase: 4 * (baseFee + tip) * tokenGases.Swap, + wantMaxFees: 4 * tTokenV0.MaxFeeRate * tokenGasesV0.Swap, + wantBestCase: currentFees * tokenGasesV0.Swap, + wantWorstCase: 4 * currentFees * tokenGasesV0.Swap, }, { name: "balanceError", @@ -2400,11 +2538,12 @@ func TestPreSwap(t *testing.T) { } runTest := func(t *testing.T, test testData) { + var assetID uint32 = BipID - assetCfg := tETH + assetCfg := tETHV0 if test.token { assetID = usdcTokenID - assetCfg = tToken + assetCfg = tTokenV0 } w, _, node, shutdown := tassetWallet(assetID) @@ -2412,7 +2551,7 @@ func TestPreSwap(t *testing.T) { node.baseFee, node.tip = dexeth.GweiToWei(baseFee), dexeth.GweiToWei(tip) if test.token { - node.tContractor.gasEstimates = &tokenGases + node.tContractor.gasEstimates = &tokenGasesV0 node.tokenContractor.bal = dexeth.GweiToWei(test.bal) node.bal = dexeth.GweiToWei(test.parentBal) } else { @@ -2426,7 +2565,7 @@ func TestPreSwap(t *testing.T) { LotSize: lotSize, Lots: test.lots, MaxFeeRate: assetCfg.MaxFeeRate, - FeeSuggestion: feeSuggestion, // ignored + FeeSuggestion: currentFees, // ignored RedeemVersion: tBTC.Version, RedeemAssetID: tBTC.ID, }) @@ -2438,7 +2577,7 @@ func TestPreSwap(t *testing.T) { return } if err != nil { - t.Fatalf("unexpected error: %v", err) + t.Fatalf("%q: %v", test.name, err) } est := preSwap.Estimate @@ -2476,6 +2615,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{}) @@ -2485,7 +2631,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()) } } @@ -2511,12 +2658,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) { @@ -2554,16 +2696,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.DecodeContractData(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, dexeth.GweiToWei(contract.Value), node.addr) + if !bytes.Equal(locator, chkLocator) { + t.Fatalf("%v, contract: %x != locator in input: %x", + testName, receipt.Contract(), locator) } totalCoinValue += receipt.Coin().Value() @@ -2633,10 +2776,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, @@ -2724,14 +2863,33 @@ 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) { - w, _, _, shutdown := tassetWallet(BipID) + w, _, node, shutdown := tassetWallet(BipID) defer shutdown() form := &asset.PreRedeemForm{ - Version: tETH.Version, + Version: tETHV0.Version, Lots: 5, FeeSuggestion: 100, } @@ -2749,7 +2907,8 @@ func TestPreRedeem(t *testing.T) { w, _, _, shutdown2 := tassetWallet(usdcTokenID) defer shutdown2() - form.Version = tToken.Version + form.Version = tTokenV0.Version + node.tokenContractor.allow = unlimitedAllowanceReplenishThreshold preRedeem, err = w.PreRedeem(form) if err != nil { @@ -2770,41 +2929,49 @@ 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) - - eth.versionedGases[1] = ethGases - if assetID != BipID { - eth.versionedGases[1] = &tokenGases - } - - tokenContracts := eth.tokens[usdcTokenID].NetTokens[dex.Simnet].SwapContracts - tokenContracts[1] = tokenContracts[0] - 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, - } + // // Test with a non-zero contract version to ensure it makes it into the receipt + // contractVer := uint32(1) + + // eth.versionedGases[1] = ethGases + // if assetID != BipID { + // eth.versionedGases[1] = &tokenGases + // } + + // tokenContracts := eth.tokens[usdcTokenID].NetTokens[dex.Simnet].SwapContracts + // tokenContracts[1] = tokenContracts[0] + // 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.contractorV0.(*tContractor) + } else { + contractor = eth.contractorV1.(*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 @@ -2818,20 +2985,22 @@ func testRedeem(t *testing.T, assetID uint32) { secretHashes = append(secretHashes, secretHash) } - addSwapToSwapMap(secretHashes[0], 1e9, dexeth.SSInitiated) // states will be reset by tests though - addSwapToSwapMap(secretHashes[1], 1e9, dexeth.SSInitiated) + addSwapToSwapMap(secretHashes[0], dexeth.SSInitiated) + addSwapToSwapMap(secretHashes[1], dexeth.SSInitiated) /* COMMENTED while estimateRedeemGas is on the $#!t list - 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 } - - 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 + 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 // additionalFundsNeeded calculates the amount of available funds that we be // needed to use a higher gas estimate than the original, and double the base // fee if it is higher than the server's max fee rate. @@ -2866,6 +3035,40 @@ func testRedeem(t *testing.T, assetID uint32) { secretHashes[0]: dexeth.SSInitiated, secretHashes[1]: dexeth.SSInitiated, } + 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 @@ -2878,6 +3081,7 @@ func testRedeem(t *testing.T, assetID uint32) { redeemGasOverride *uint64 expectedGasFeeCap *big.Int expectError bool + v1 bool }{ { name: "ok", @@ -2887,32 +3091,24 @@ 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, }, }, /* COMMENTED while estimateRedeemGas is on the $#!t list + { + 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, @@ -2922,28 +3118,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, }, }, @@ -2956,28 +3131,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, }, }, @@ -2989,28 +3143,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, }, }, @@ -3022,28 +3155,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, }, }, @@ -3056,28 +3168,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, }, }, @@ -3090,28 +3181,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, }, }, @@ -3127,28 +3197,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, }, }, @@ -3159,28 +3208,7 @@ func testRedeem(t *testing.T, assetID uint32) { baseFee: dexeth.GweiToWei(100), swapErr: errors.New("swap() error"), 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, }, }, @@ -3192,18 +3220,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, }, }, @@ -3214,18 +3231,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, }, }, @@ -3243,15 +3249,20 @@ func testRedeem(t *testing.T, assetID uint32) { } for _, test := range tests { - contractorV1.redeemErr = test.redeemErr - contractorV1.swapErr = test.swapErr - contractorV1.redeemGasOverride = test.redeemGasOverride + contractor.redeemErr = test.redeemErr + contractor.swapErr = test.swapErr + contractor.redeemGasOverride = test.redeemGasOverride for secretHash, step := range test.swapMap { - contractorV1.swapMap[secretHash].State = step + contractor.swapMap[secretHash].State = step } node.bal = test.ethBal node.baseFee = test.baseFee + var contractVer uint32 + if test.v1 { + contractVer = 1 + } + txs, out, fees, err := w.Redeem(&test.form) if test.expectError { if err == nil { @@ -3269,10 +3280,8 @@ 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 - } + rg := gases(contractVer, eth.versionedGases) + expectedGas := rg.Redeem + (uint64(len(test.form.Redemptions))-1)*rg.RedeemAdd expectedFees := expectedGas * test.form.FeeSuggestion if fees != expectedFees { t.Fatalf("%v: expected fees %d, but got %d", test.name, expectedFees, fees) @@ -3281,44 +3290,52 @@ 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.DecodeContractData(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)) + if test.redeemGasOverride == nil { + if assetID == BipID { + expectedGasLimit = rg.Redeem * uint64(len(test.form.Redemptions)) + } else { + expectedGasLimit = rg.Redeem * uint64(len(test.form.Redemptions)) + } } else { - expectedGasLimit = tokenGases.Redeem * uint64(len(test.form.Redemptions)) + expectedGasLimit = rg.Redeem * 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) } } } func TestMaxOrder(t *testing.T) { const baseFee, tip = 42, 2 + const currentFee = baseFee + tip type testData struct { name string @@ -3326,7 +3343,6 @@ func TestMaxOrder(t *testing.T) { balErr error lotSize uint64 maxFeeRate uint64 - feeSuggestion uint64 token bool parentBal uint64 wantErr bool @@ -3336,113 +3352,124 @@ func TestMaxOrder(t *testing.T) { wantWorstCase uint64 wantBestCase uint64 wantLocked uint64 + v1 bool } tests := []testData{ { - name: "no balance", - bal: 0, - lotSize: 10, - feeSuggestion: 90, - maxFeeRate: 100, + name: "no balance", + bal: 0, + lotSize: 10, + maxFeeRate: 100, }, { - name: "no balance - token", - bal: 0, - lotSize: 10, - feeSuggestion: 90, - maxFeeRate: 100, - token: true, - parentBal: 100, + name: "no balance - token", + bal: 0, + lotSize: 10, + maxFeeRate: 100, + token: true, + parentBal: 100, }, { - name: "not enough for fees", - bal: 10, - lotSize: 10, - feeSuggestion: 90, - maxFeeRate: 100, + name: "not enough for fees", + bal: 10, + lotSize: 10, + maxFeeRate: 100, }, { - name: "not enough for fees - token", - bal: 10, - token: true, - parentBal: 0, + name: "not enough for fees - token", + bal: 10, + token: true, + parentBal: 0, + lotSize: 10, + maxFeeRate: 100, + }, + { + name: "one lot enough for fees", + bal: 11, lotSize: 10, - feeSuggestion: 90, maxFeeRate: 100, + wantLots: 1, + wantValue: ethToGwei(10), + wantMaxFees: 100 * ethGasesV0.Swap, + wantBestCase: currentFee * ethGasesV0.Swap, + wantWorstCase: currentFee * ethGasesV0.Swap, + wantLocked: ethToGwei(10) + (100 * ethGasesV0.Swap), }, { - name: "one lot enough for fees", + name: "one lot enough for fees - v1", bal: 11, lotSize: 10, - feeSuggestion: 90, maxFeeRate: 100, wantLots: 1, wantValue: ethToGwei(10), - wantMaxFees: 100 * ethGases.Swap, - wantBestCase: (baseFee + tip) * ethGases.Swap, - wantWorstCase: (baseFee + tip) * ethGases.Swap, - wantLocked: ethToGwei(10) + (100 * ethGases.Swap), + wantMaxFees: 100 * ethGasesV1.Swap, + wantBestCase: currentFee * ethGasesV1.Swap, + wantWorstCase: currentFee * ethGasesV1.Swap, + wantLocked: ethToGwei(10) + (100 * ethGasesV0.Swap), + v1: true, }, { name: "one lot enough for fees - token", bal: 11, lotSize: 10, - feeSuggestion: 90, maxFeeRate: 100, token: true, parentBal: 1, wantLots: 1, wantValue: ethToGwei(10), - wantMaxFees: 100 * tokenGases.Swap, - wantBestCase: (baseFee + tip) * tokenGases.Swap, - wantWorstCase: (baseFee + tip) * tokenGases.Swap, - wantLocked: ethToGwei(10) + (100 * tokenGases.Swap), + wantMaxFees: 100 * tokenGasesV0.Swap, + wantBestCase: currentFee * tokenGasesV0.Swap, + wantWorstCase: currentFee * tokenGasesV0.Swap, + wantLocked: ethToGwei(10) + (100 * tokenGasesV0.Swap), }, { name: "multiple lots", bal: 51, lotSize: 10, - feeSuggestion: 90, maxFeeRate: 100, wantLots: 5, wantValue: ethToGwei(50), - wantMaxFees: 5 * 100 * ethGases.Swap, - wantBestCase: (baseFee + tip) * ethGases.Swap, - wantWorstCase: 5 * (baseFee + tip) * ethGases.Swap, - wantLocked: ethToGwei(50) + (5 * 100 * ethGases.Swap), + wantMaxFees: 5 * 100 * ethGasesV0.Swap, + wantBestCase: currentFee * ethGasesV0.Swap, + wantWorstCase: 5 * currentFee * ethGasesV0.Swap, + wantLocked: ethToGwei(50) + (5 * 100 * ethGasesV0.Swap), }, { name: "multiple lots - token", bal: 51, lotSize: 10, - feeSuggestion: 90, maxFeeRate: 100, token: true, parentBal: 1, wantLots: 5, wantValue: ethToGwei(50), - wantMaxFees: 5 * 100 * tokenGases.Swap, - wantBestCase: (baseFee + tip) * tokenGases.Swap, - wantWorstCase: 5 * (baseFee + tip) * tokenGases.Swap, - wantLocked: ethToGwei(50) + (5 * 100 * tokenGases.Swap), + wantMaxFees: 5 * 100 * tokenGasesV0.Swap, + wantBestCase: currentFee * tokenGasesV0.Swap, + wantWorstCase: 5 * currentFee * tokenGasesV0.Swap, + wantLocked: ethToGwei(50) + (5 * 100 * tokenGasesV0.Swap), }, { - name: "balanceError", - bal: 51, - lotSize: 10, - feeSuggestion: 90, - maxFeeRate: 100, - balErr: errors.New("test error"), - wantErr: true, + name: "balanceError", + bal: 51, + lotSize: 10, + // feeSuggestion: 90, + maxFeeRate: 100, + balErr: errors.New("test error"), + wantErr: true, }, } runTest := func(t *testing.T, test testData) { var assetID uint32 = BipID - assetCfg := tETH + gases := ethGasesV0 if test.token { assetID = usdcTokenID - assetCfg = tToken + gases = &tokenGasesV0 + if test.v1 { + gases = &tokenGasesV1 + } + } else if test.v1 { + gases = ethGasesV1 } w, _, node, shutdown := tassetWallet(assetID) @@ -3450,7 +3477,8 @@ func TestMaxOrder(t *testing.T) { node.baseFee, node.tip = dexeth.GweiToWei(baseFee), dexeth.GweiToWei(tip) if test.token { - node.tContractor.gasEstimates = &tokenGases + node.tContractor.gasEstimates = &tokenGasesV0 + // dexAsset = tTokenV0 node.tokenContractor.bal = dexeth.GweiToWei(ethToGwei(test.bal)) node.bal = dexeth.GweiToWei(ethToGwei(test.parentBal)) } else { @@ -3458,11 +3486,16 @@ func TestMaxOrder(t *testing.T) { } node.balErr = test.balErr + node.tContractor.gasEstimates = gases + + var serverVer uint32 + if test.v1 { + serverVer = 1 + } maxOrder, err := w.MaxOrder(&asset.MaxOrderForm{ LotSize: ethToGwei(test.lotSize), - FeeSuggestion: test.feeSuggestion, // ignored - AssetVersion: assetCfg.Version, + AssetVersion: serverVer, MaxFeeRate: test.maxFeeRate, RedeemVersion: tBTC.Version, RedeemAssetID: tBTC.ID, @@ -3501,15 +3534,6 @@ func TestMaxOrder(t *testing.T) { } } -func overMaxWei() *big.Int { - maxInt := ^uint64(0) - maxWei := new(big.Int).SetUint64(maxInt) - gweiFactorBig := big.NewInt(dexeth.GweiFactor) - maxWei.Mul(maxWei, gweiFactorBig) - overMaxWei := new(big.Int).Set(maxWei) - return overMaxWei.Add(overMaxWei, gweiFactorBig) -} - func packInitiateDataV0(initiations []*dexeth.Initiation) ([]byte, error) { abiInitiations := make([]swapv0.ETHSwapInitiation, 0, len(initiations)) for _, init := range initiations { @@ -3568,7 +3592,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, @@ -3588,7 +3612,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, @@ -3607,7 +3631,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, @@ -3626,13 +3650,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, @@ -3843,7 +3867,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 @@ -3860,7 +3886,7 @@ func TestSwapConfirmation(t *testing.T) { checkResult := func(expErr bool, expConfs uint32, expSpent bool) { t.Helper() - 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 @@ -3889,7 +3915,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) } @@ -4059,6 +4085,7 @@ func TestLocktimeExpired(t *testing.T) { state := &dexeth.SwapState{ LockTime: time.Now(), State: dexeth.SSInitiated, + Value: dexeth.GweiToWei(1), } header := &types.Header{ @@ -4132,10 +4159,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 @@ -4179,7 +4207,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 @@ -4243,7 +4271,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() { @@ -4280,25 +4308,14 @@ func testRefundReserves(t *testing.T, assetID uint32) { node.swapMap = map[[32]byte]*dexeth.SwapState{secretHash: {}} feeWallet := eth - gasesV0 := dexeth.VersionedGases[0] - gasesV1 := &dexeth.Gases{Refund: 1e6} - assetV0 := *tETH - - assetV1 := *tETH - if assetID == BipID { - eth.versionedGases[1] = gasesV1 - } else { + gasesV0 := eth.versionedGases[0] + gasesV1 := eth.versionedGases[1] + assetV0 := *tETHV0 + assetV1 := *tETHV0 + if assetID != BipID { feeWallet = node.tokenParent - assetV0 = *tToken - assetV1 = *tToken - tokenContracts := eth.tokens[usdcTokenID].NetTokens[dex.Simnet].SwapContracts - tc := *tokenContracts[0] - tc.Gas = *gasesV1 - tokenContracts[1] = &tc - defer delete(tokenContracts, 1) - gasesV0 = &tokenGases - eth.versionedGases[0] = gasesV0 - eth.versionedGases[1] = gasesV1 + assetV0 = *tTokenV0 + assetV1 = *tTokenV0 node.tokenContractor.bal = dexeth.GweiToWei(1e9) } @@ -4379,21 +4396,17 @@ func testRedemptionReserves(t *testing.T, assetID uint32) { var secretHash [32]byte node.tContractor.swapMap[secretHash] = &dexeth.SwapState{} - gasesV1 := &dexeth.Gases{Redeem: 1e6, RedeemAdd: 85e5} - gasesV0 := dexeth.VersionedGases[0] - assetV0 := *tETH - assetV1 := *tETH + gasesV0 := eth.versionedGases[0] + gasesV1 := eth.versionedGases[1] + eth.versionedGases[1] = gasesV1 + assetV0 := *tETHV0 + assetV1 := *tETHV0 feeWallet := eth - if assetID == BipID { - eth.versionedGases[1] = gasesV1 - } else { + if assetID != BipID { node.tokenContractor.allow = unlimitedAllowanceReplenishThreshold feeWallet = node.tokenParent - assetV0 = *tToken - assetV1 = *tToken - gasesV0 = &tokenGases - eth.versionedGases[0] = gasesV0 - eth.versionedGases[1] = gasesV1 + assetV0 = *tTokenV0 + assetV1 = *tTokenV0 } assetV0.MaxFeeRate = 45 @@ -4500,7 +4513,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) * tokenGasesV1.Transfer const val = 10e9 const testAddr = "dd93b447f7eBCA361805eBe056259853F3912E04" @@ -4553,12 +4566,12 @@ func testSend(t *testing.T, assetID uint32) { coin, err := w.Send(test.addr, val, 0) if test.wantErr { if err == nil { - t.Fatalf("expected error for test %v", test.name) + t.Fatalf("expected error for test %q", test.name) } continue } if err != nil { - t.Fatalf("unexpected error for test %v: %v", test.name, err) + t.Fatalf("unexpected error for test %q: %v", test.name, err) } if !bytes.Equal(txHash[:], coin.ID()) { t.Fatal("coin is not the tx hash") @@ -4588,7 +4601,7 @@ func testConfirmRedemption(t *testing.T, assetID uint32) { redemption := &asset.Redemption{ Spends: &asset.AuditInfo{ - Contract: dexeth.EncodeContractData(0, secretHash), + Contract: dexeth.EncodeContractData(0, secretHash[:]), }, Secret: secret[:], } @@ -4690,7 +4703,7 @@ func testConfirmRedemption(t *testing.T, assetID uint32) { fmt.Printf("###### %s ###### \n", test.name) node.tContractor.swapMap = map[[32]byte]*dexeth.SwapState{ - secretHash: {State: test.step}, + secretHash: {State: test.step, Value: big.NewInt(1)}, } node.tContractor.lastRedeems = nil node.tokenContractor.bal = big.NewInt(1e9) @@ -4798,7 +4811,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) * tokenGasesV1.Transfer ethFees = ethFees * 12 / 10 tokenFees = tokenFees * 12 / 10 @@ -4946,7 +4959,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[:]) } const tip = 100 const feeRate = 2 // gwei / gas diff --git a/client/asset/eth/nodeclient_harness_test.go b/client/asset/eth/nodeclient_harness_test.go index de0a19721f..e80399570e 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" @@ -66,6 +67,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 string @@ -85,14 +87,17 @@ var ( participantAddr common.Address participantAcct *accounts.Account participantEthClient ethFetcher - ethSwapContractAddr common.Address simnetContractor contractor participantContractor contractor simnetTokenContractor tokenContractor participantTokenContractor tokenContractor - ethGases = dexeth.VersionedGases[0] + ethGases *dexeth.Gases tokenGases *dexeth.Gases - secPerBlock = 15 * time.Second + testnetSecPerBlock = 15 * time.Second + // secPerBlock is one for simnet, because it takes one second to mine a + // block currently. Is set in code to testnetSecPerBlock if running on + // testnet. + secPerBlock = time.Second // If you are testing on testnet, you must specify the rpcNode. You can also // specify it in the testnet-credentials.json file. rpcProviders []string @@ -115,6 +120,9 @@ var ( testnetParticipantWalletSeed string usdcID, _ = dex.BipSymbolID("usdc.eth") masterToken *dexeth.Token + + v1 bool + contractVer uint32 ) func newContract(stamp uint64, secretHash [32]byte, val uint64) *asset.Contract { @@ -126,10 +134,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 contractVer == 1 { + return (&dexeth.SwapVector{ + From: ethClient.address(), + To: participantEthClient.address(), + Value: dexeth.GweiToWei(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(contractVer, makeLocator(secretHash, valg, lockTime)), }, Secret: secret[:], } @@ -168,7 +200,7 @@ func waitForMined() error { if err != nil { return err } - const targetConfs = 1 + const targetConfs = 3 currentHeight := hdr.Number barrierHeight := new(big.Int).Add(currentHeight, big.NewInt(targetConfs)) fmt.Println("Waiting for RPC blocks") @@ -245,18 +277,16 @@ func runSimnet(m *testing.M) (int, error) { return 1, fmt.Errorf("error creating participant wallet dir: %v", err) } - const contractVer = 0 - tokenGases = &dexeth.Tokens[usdcID].NetTokens[dex.Simnet].SwapContracts[contractVer].Gas // ETH swap contract. masterToken = dexeth.Tokens[usdcID] token := masterToken.NetTokens[dex.Simnet] fmt.Printf("ETH swap contract address is %v\n", dexeth.ContractAddresses[contractVer][dex.Simnet]) - fmt.Printf("Token swap contract addr is %v\n", token.SwapContracts[0].Address) + fmt.Printf("Token swap contract addr is %v\n", token.SwapContracts[contractVer].Address) fmt.Printf("Test token contract addr is %v\n", token.Address) - ethSwapContractAddr = dexeth.ContractAddresses[contractVer][dex.Simnet] + contractAddr := dexeth.ContractAddresses[contractVer][dex.Simnet] initiatorProviders, participantProviders := rpcEndpoints(dex.Simnet) @@ -288,28 +318,10 @@ func runSimnet(m *testing.M) (int, error) { contractAddr, exists := dexeth.ContractAddresses[contractVer][dex.Simnet] if !exists || contractAddr == (common.Address{}) { - return 1, fmt.Errorf("no contract address for version %d", contractVer) + return 1, fmt.Errorf("no contract address for contract version %d", contractVer) } - if simnetContractor, err = newV0Contractor(contractAddr, simnetAddr, ethClient.contractBackend()); err != nil { - return 1, fmt.Errorf("newV0Contractor error: %w", err) - } - if participantContractor, err = newV0Contractor(contractAddr, participantAddr, participantEthClient.contractBackend()); err != nil { - return 1, fmt.Errorf("participant newV0Contractor error: %w", err) - } - - if simnetTokenContractor, err = newV0TokenContractor(dex.Simnet, dexeth.Tokens[usdcID], 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, dexeth.Tokens[usdcID], participantAddr, participantEthClient.contractBackend()); err != nil { - return 1, fmt.Errorf("participant newV0TokenContractor error: %w", err) - } + prepareSimnetContractors() if err := ethClient.unlock(pw); err != nil { return 1, fmt.Errorf("error unlocking initiator client: %w", err) @@ -319,11 +331,6 @@ func runSimnet(m *testing.M) (int, error) { } // Fund the wallets. - homeDir, err := os.UserHomeDir() - if err != nil { - return 1, err - } - 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 @@ -370,9 +377,8 @@ func runSimnet(m *testing.M) (int, error) { } func runTestnet(m *testing.M) (int, error) { - usdcID = usdcID masterToken = dexeth.Tokens[usdcID] - tokenGases = &masterToken.NetTokens[dex.Testnet].SwapContracts[0].Gas + tokenGases = &masterToken.NetTokens[dex.Testnet].SwapContracts[contractVer].Gas if testnetWalletSeed == "" || testnetParticipantWalletSeed == "" { return 1, errors.New("testnet seeds not set") } @@ -386,9 +392,9 @@ func runTestnet(m *testing.M) (int, error) { if err != nil { return 1, fmt.Errorf("error creating testnet participant wallet dir: %v", err) } - const contractVer = 0 - ethSwapContractAddr = dexeth.ContractAddresses[contractVer][dex.Testnet] - fmt.Printf("ETH swap contract address is %v\n", ethSwapContractAddr) + secPerBlock = testnetSecPerBlock + contractAddr := dexeth.ContractAddresses[contractVer][dex.Testnet] + fmt.Printf("Swap contract address is %v\n", contractAddr) initiatorRPC, participantRPC := rpcEndpoints(dex.Testnet) @@ -437,10 +443,15 @@ func runTestnet(m *testing.M) (int, error) { return 1, fmt.Errorf("no contract address for version %d", contractVer) } - if simnetContractor, err = newV0Contractor(contractAddr, simnetAddr, ethClient.contractBackend()); err != nil { + ctor := newV0Contractor + if contractVer == 1 { + ctor = newV1Contractor + } + + if simnetContractor, err = ctor(dex.Testnet, contractAddr, simnetAddr, ethClient.contractBackend()); err != nil { return 1, fmt.Errorf("newV0Contractor error: %w", err) } - if participantContractor, err = newV0Contractor(contractAddr, participantAddr, participantEthClient.contractBackend()); err != nil { + if participantContractor, err = ctor(dex.Testnet, contractAddr, participantAddr, participantEthClient.contractBackend()); err != nil { return 1, fmt.Errorf("participant newV0Contractor error: %w", err) } @@ -451,17 +462,28 @@ func runTestnet(m *testing.M) (int, error) { return 1, fmt.Errorf("error unlocking initiator client: %w", err) } - if simnetTokenContractor, err = newV0TokenContractor(dex.Testnet, dexeth.Tokens[usdcID], simnetAddr, ethClient.contractBackend()); err != nil { - return 1, fmt.Errorf("newV0TokenContractor error: %w", err) - } + if v1 { + scv1 := simnetContractor.(*contractorV1) + if simnetTokenContractor, err = scv1.tokenContractor(dexeth.Tokens[usdcID]); err != nil { + return 1, fmt.Errorf("v1 tokenContractor error: %w", err) + } + pcv1 := participantContractor.(*contractorV1) + if participantTokenContractor, err = pcv1.tokenContractor(dexeth.Tokens[usdcID]); err != nil { + return 1, fmt.Errorf("participant v1 tokenContractor error: %w", err) + } + } else { + if simnetTokenContractor, err = newV0TokenContractor(dex.Testnet, dexeth.Tokens[usdcID], 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) + // 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.Testnet, dexeth.Tokens[usdcID], participantAddr, participantEthClient.contractBackend()); err != nil { - return 1, fmt.Errorf("participant newV0TokenContractor error: %w", err) + if participantTokenContractor, err = newV0TokenContractor(dex.Testnet, dexeth.Tokens[usdcID], participantAddr, participantEthClient.contractBackend()); err != nil { + return 1, fmt.Errorf("participant newV0TokenContractor error: %w", err) + } } code := m.Run() @@ -480,6 +502,50 @@ func runTestnet(m *testing.M) (int, error) { return code, nil } +func prepareSimnetContractors() (err error) { + contractAddr := dexeth.ContractAddresses[contractVer][dex.Simnet] + + ctor := newV0Contractor + if contractVer == 1 { + ctor = newV1Contractor + } + + if simnetContractor, err = ctor(dex.Simnet, contractAddr, simnetAddr, ethClient.contractBackend()); err != nil { + return fmt.Errorf("new contractor error: %w", err) + } + if participantContractor, err = ctor(dex.Simnet, contractAddr, participantAddr, participantEthClient.contractBackend()); err != nil { + return fmt.Errorf("participant new contractor error: %w", err) + } + + token := dexeth.Tokens[usdcID] + + if v1 { + scv1 := simnetContractor.(*contractorV1) + if simnetTokenContractor, err = scv1.tokenContractor(token); err != nil { + return fmt.Errorf("v1 tokenContractor error: %w", err) + } + pcv1 := participantContractor.(*contractorV1) + if participantTokenContractor, err = pcv1.tokenContractor(token); err != nil { + return fmt.Errorf("participant v1 tokenContractor error: %w", err) + } + } else { + if simnetTokenContractor, err = newV0TokenContractor(dex.Simnet, token, simnetAddr, ethClient.contractBackend()); err != nil { + return 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, token, participantAddr, participantEthClient.contractBackend()); err != nil { + return fmt.Errorf("participant newV0TokenContractor error: %w", err) + } + } + + return +} + func useTestnet() error { isTestnet = true b, err := os.ReadFile(filepath.Join(homeDir, "dextest", "credentials.json")) @@ -501,11 +567,19 @@ func useTestnet() error { } func TestMain(m *testing.M) { + rand.Seed(time.Now().UnixNano()) dexeth.MaybeReadSimnetAddrs() flag.BoolVar(&isTestnet, "testnet", false, "use testnet") + flag.BoolVar(&v1, "v1", true, "Use Version 1 contract") flag.Parse() + if v1 { + contractVer = 1 + } + + ethGases = dexeth.VersionedGases[contractVer] + if isTestnet { tmpDir, err := os.MkdirTemp("", "") if err != nil { @@ -674,20 +748,20 @@ 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) }) } func TestGas(t *testing.T) { - t.Run("testInitiateGas", func(t *testing.T) { testInitiateGas(t, BipID) }) + // t.Run("testInitiateGas", func(t *testing.T) { testInitiateGas(t, BipID) }) t.Run("testRedeemGas", func(t *testing.T) { testRedeemGas(t, BipID) }) t.Run("testRefundGas", func(t *testing.T) { testRefundGas(t, BipID) }) } func TestTokenContract(t *testing.T) { - t.Run("testTokenSwap", func(t *testing.T) { testSwap(t, usdcID) }) + // t.Run("testTokenSwap", func(t *testing.T) { testSwap(t, usdcID) }) // TODO: Replace with testTokenStatusAndVector? t.Run("testInitiateToken", func(t *testing.T) { testInitiate(t, usdcID) }) t.Run("testRedeemToken", func(t *testing.T) { testRedeem(t, usdcID) }) t.Run("testRefundToken", func(t *testing.T) { testRefund(t, usdcID) }) @@ -696,7 +770,7 @@ func TestTokenContract(t *testing.T) { func TestTokenGas(t *testing.T) { t.Run("testTransferGas", testTransferGas) t.Run("testApproveGas", testApproveGas) - t.Run("testInitiateTokenGas", func(t *testing.T) { testInitiateGas(t, usdcID) }) + // t.Run("testInitiateTokenGas", func(t *testing.T) { testInitiateGas(t, usdcID) }) t.Run("testRedeemTokenGas", func(t *testing.T) { testRedeemGas(t, usdcID) }) t.Run("testRefundTokenGas", func(t *testing.T) { testRefundGas(t, usdcID) }) } @@ -957,17 +1031,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 { @@ -977,25 +1040,13 @@ func testSyncProgress(t *testing.T) { } func testInitiateGas(t *testing.T, assetID uint32) { - if assetID != BipID { - prepareTokenClients(t) - } - - net := dex.Simnet - if isTestnet { - net = dex.Testnet - } - + gases := ethGases c := simnetContractor - versionedGases := dexeth.VersionedGases if assetID != BipID { c = simnetTokenContractor - versionedGases = make(map[uint32]*dexeth.Gases) - for ver, c := range dexeth.Tokens[assetID].NetTokens[net].SwapContracts { - versionedGases[ver] = &c.Gas - } + prepareTokenClients(t) + gases = tokenGases } - gases := gases(0, versionedGases) var previousGas uint64 maxSwaps := 50 @@ -1014,7 +1065,7 @@ func testInitiateGas(t *testing.T, assetID uint32) { expectedGas = gases.SwapAdd actualGas = gas - previousGas } - if actualGas > expectedGas || actualGas < expectedGas*70/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) } @@ -1097,8 +1148,12 @@ func testInitiate(t *testing.T, assetID uint32) { return simnetTokenContractor.balance(ctx) } gases = tokenGases - tc := sc.(*tokenContractorV0) - evmify = tc.evmify + switch contractVer { + case 0: + evmify = sc.(*tokenContractorV0).evmify + case 1: + evmify = sc.(*tokenContractorV1).evmify + } } // Create a slice of random secret hashes that can be used in the tests and @@ -1106,15 +1161,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()) @@ -1133,10 +1180,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), }, }, { @@ -1192,28 +1239,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)) @@ -1222,7 +1270,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) @@ -1295,19 +1343,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) } } } @@ -1334,8 +1381,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 @@ -1373,19 +1423,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) } @@ -1399,9 +1449,9 @@ func testRedeemGas(t *testing.T, assetID uint32) { expectedGas = gases.RedeemAdd actualGas = gas - previous } - if actualGas > expectedGas || actualGas < (expectedGas*70/100) { + 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) @@ -1436,6 +1486,8 @@ func testRedeem(t *testing.T, assetID uint32) { evmify = tc.evmify } + const val = 1 + tests := []struct { name string sleepNBlocks int @@ -1454,8 +1506,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)}, finalStates: []dexeth.SwapStep{dexeth.SSRedeemed}, addAmt: true, }, @@ -1466,12 +1518,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), }, finalStates: []dexeth.SwapStep{ dexeth.SSRedeemed, dexeth.SSRedeemed, @@ -1484,8 +1536,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)}, finalStates: []dexeth.SwapStep{dexeth.SSRedeemed}, addAmt: true, }, @@ -1495,19 +1547,20 @@ 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)}, 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)}, finalStates: []dexeth.SwapStep{dexeth.SSInitiated}, addAmt: false, }, @@ -1519,12 +1572,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), }, finalStates: []dexeth.SwapStep{ dexeth.SSInitiated, @@ -1536,14 +1589,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 @@ -1563,7 +1618,7 @@ func testRedeem(t *testing.T, assetID uint32) { 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) } @@ -1586,12 +1641,12 @@ 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) } } @@ -1691,15 +1746,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) } } } @@ -1731,7 +1785,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) } @@ -1739,22 +1795,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*70/100 { + if gas > expGas || gas < expGas/2 { t.Fatalf("expected refund gas to be near %d, but got %d", expGas, gas) } @@ -1845,21 +1900,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, 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) } @@ -1873,7 +1930,7 @@ func testRefund(t *testing.T, assetID uint32) { if err != nil { t.Fatalf("%s: txOpts error: %v", test.name, err) } - _, 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) } @@ -1897,7 +1954,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) } @@ -1910,7 +1967,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) } @@ -1985,12 +2042,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) } } } @@ -2141,7 +2198,7 @@ func TestTokenGasEstimates(t *testing.T) { runSimnetMiner(ctx, "eth", tLogger) prepareTokenClients(t) tLogger.SetLevel(dex.LevelInfo) - if err := getGasEstimates(ctx, ethClient, participantEthClient, simnetTokenContractor, participantTokenContractor, 5, tokenGases, tLogger); err != nil { + if err := getGasEstimates(ctx, ethClient, participantEthClient, simnetTokenContractor, participantTokenContractor, 5, contractVer, tokenGases, dexeth.GweiToWei, tLogger); err != nil { t.Fatalf("getGasEstimates error: %v", err) } } diff --git a/client/asset/polygon/polygon.go b/client/asset/polygon/polygon.go index 6e65cf8dff..3dd3a390ec 100644 --- a/client/asset/polygon/polygon.go +++ b/client/asset/polygon/polygon.go @@ -73,7 +73,7 @@ var ( } WalletInfo = asset.WalletInfo{ Name: "Polygon", - SupportedVersions: []uint32{0}, + SupportedVersions: []uint32{0, 1}, UnitInfo: dexpolygon.UnitInfo, AvailableWallets: []*asset.WalletDefinition{ { diff --git a/client/core/simnet_trade.go b/client/core/simnet_trade.go index 2e94f27341..a4015973b0 100644 --- a/client/core/simnet_trade.go +++ b/client/core/simnet_trade.go @@ -2130,7 +2130,7 @@ func (s *simulationTest) assertBalanceChanges(client *simulationClient, isRefund if isRefund { // NOTE: Gas price may be higher if the eth harness has // had a lot of use. The minimum is the gas tip cap. - ethRefundFees := int64(dexeth.RefundGas(0 /*version*/)) * dexeth.MinGasTipCap + ethRefundFees := int64(dexeth.RefundGas(1 /*version*/)) * dexeth.MinGasTipCap msgTx := wire.NewMsgTx(0) prevOut := wire.NewOutPoint(&chainhash.Hash{}, 0) diff --git a/client/webserver/site/src/js/markets.ts b/client/webserver/site/src/js/markets.ts index 48d8b74e89..b3cc3aa597 100644 --- a/client/webserver/site/src/js/markets.ts +++ b/client/webserver/site/src/js/markets.ts @@ -2159,7 +2159,7 @@ export default class MarketsPage extends BasePage { if (!app().checkResponse(res)) { throw Error('error unlocking wallet ' + res.msg) } - this.balanceWgt.updateAsset(assetID) + if (this.openAsset) this.balanceWgt.updateAsset(assetID) } /* diff --git a/dex/networks/erc20/contracts/ERC20SwapV0.sol b/dex/networks/erc20/contracts/ERC20SwapV0.sol index be6440b5e0..76ba9d4c0a 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.18; -// 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 @@ -120,7 +120,7 @@ contract ERC20Swap { } // redeem redeems an array of swaps contract. It checks that the sender is - // not a contract, and that the secret hash hashes to secretHash. The ERC20 + // not a contract, and that the secret hashes to secretHash. The ERC20 // tokens are transferred from the contract to the sender. function redeem(Redemption[] calldata redemptions) public diff --git a/dex/networks/erc20/contracts/updatecontract.sh b/dex/networks/erc20/contracts/updatecontract.sh index ef2a888b93..328badfc95 100755 --- a/dex/networks/erc20/contracts/updatecontract.sh +++ b/dex/networks/erc20/contracts/updatecontract.sh @@ -36,16 +36,6 @@ fi mkdir temp solc --abi --bin --bin-runtime --overwrite --optimize ${SOLIDITY_FILE} -o ./temp/ -BYTECODE=$(<./temp/${CONTRACT_NAME}.bin-runtime) - -cat > "./${PKG_NAME}/BinRuntimeV${VERSION}.go" < "v${VERSION}/swap_contract.bin" +echo "${TEST_TOKEN_BYTECODE}" | xxd -r -p > "v${VERSION}/token_contract.bin" rm -fr temp @@ -84,3 +67,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 deleted file mode 100644 index 9d5a079e5d..0000000000 --- a/dex/networks/erc20/contracts/v0/BinRuntimeV0.go +++ /dev/null @@ -1,6 +0,0 @@ -// Code generated - DO NOT EDIT. -// This file is a generated binding and any manual changes will be lost. - -package v0 - -const ERC20SwapRuntimeBin = "608060405234801561001057600080fd5b506004361061007d5760003560e01c8063a8793f941161005b578063a8793f94146100ff578063d0f761c014610112578063eb84e7f214610135578063f4fd17f9146101a457600080fd5b80637249fbb61461008257806376467cbd146100975780638c8e8fee146100c0575b600080fd5b610095610090366004610ac8565b6101b7565b005b6100aa6100a5366004610ac8565b610376565b6040516100b79190610b19565b60405180910390f35b6100e77f000000000000000000000000000000000000000000000000000000000000000081565b6040516001600160a01b0390911681526020016100b7565b61009561010d366004610b7e565b610451565b610125610120366004610ac8565b61074c565b60405190151581526020016100b7565b610191610143366004610ac8565b60006020819052908152604090208054600182015460028301546003840154600485015460059095015493949293919290916001600160a01b0391821691811690600160a01b900460ff1687565b6040516100b79796959493929190610bf3565b6100956101b2366004610c3f565b6107ac565b3233146101df5760405162461bcd60e51b81526004016101d690610ca2565b60405180910390fd5b6101e88161074c565b6102255760405162461bcd60e51b815260206004820152600e60248201526d6e6f7420726566756e6461626c6560901b60448201526064016101d6565b60008181526020818152604080832060058101805460ff60a01b1916600360a01b17905560018101548251336024820152604480820192909252835180820390920182526064018352928301805163a9059cbb60e01b6001600160e01b0390911617905290519092916060916001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016916102c591610ccc565b6000604051808303816000865af19150503d8060008114610302576040519150601f19603f3d011682016040523d82523d6000602084013e610307565b606091505b5090925090508180156103325750805115806103325750808060200190518101906103329190610cfb565b6103705760405162461bcd60e51b815260206004820152600f60248201526e1d1c985b9cd9995c8819985a5b1959608a1b60448201526064016101d6565b50505050565b6103b36040805160e081018252600080825260208201819052918101829052606081018290526080810182905260a081018290529060c082015290565b60008281526020818152604091829020825160e08101845281548152600182015492810192909252600281015492820192909252600380830154606083015260048301546001600160a01b039081166080840152600584015490811660a084015291929160c0840191600160a01b90910460ff169081111561043757610437610ae1565b600381111561044857610448610ae1565b90525092915050565b3233146104705760405162461bcd60e51b81526004016101d690610ca2565b6000805b82811015610615573684848381811061048f5761048f610d1d565b9050608002019050600080600083602001358152602001908152602001600020905060008260600135116104ed5760405162461bcd60e51b81526020600482015260056024820152640c081d985b60da1b60448201526064016101d6565b813561052f5760405162461bcd60e51b815260206004820152601160248201527003020726566756e6454696d657374616d7607c1b60448201526064016101d6565b60006005820154600160a01b900460ff16600381111561055157610551610ae1565b146105905760405162461bcd60e51b815260206004820152600f60248201526e0c8eae040e6cac6e4cae840d0c2e6d608b1b60448201526064016101d6565b436002820155813560038201556004810180546001600160a01b031916331790556105c16060830160408401610d33565b6005820180546060850135600185018190556001600160a01b03939093166001600160a81b031990911617600160a01b1790556105fe9085610d72565b93505050808061060d90610d8b565b915050610474565b5060408051336024820152306044820152606480820184905282518083039091018152608490910182526020810180516001600160e01b03166323b872dd60e01b17905290516000916060916001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000169161069591610ccc565b6000604051808303816000865af19150503d80600081146106d2576040519150601f19603f3d011682016040523d82523d6000602084013e6106d7565b606091505b5090925090508180156107025750805115806107025750808060200190518101906107029190610cfb565b6107455760405162461bcd60e51b81526020600482015260146024820152731d1c985b9cd9995c88199c9bdb4819985a5b195960621b60448201526064016101d6565b5050505050565b600081815260208190526040812060016005820154600160a01b900460ff16600381111561077c5761077c610ae1565b148015610795575060048101546001600160a01b031633145b80156107a5575080600301544210155b9392505050565b3233146107cb5760405162461bcd60e51b81526004016101d690610ca2565b6000805b828110156109a357368484838181106107ea576107ea610d1d565b6020604091820293909301838101356000908152938490529220919250600190506005820154600160a01b900460ff16600381111561082b5761082b610ae1565b146108645760405162461bcd60e51b815260206004820152600960248201526862616420737461746560b81b60448201526064016101d6565b60058101546001600160a01b031633146108b25760405162461bcd60e51b815260206004820152600f60248201526e189859081c185c9d1a58da5c185b9d608a1b60448201526064016101d6565b8160200135600283600001356040516020016108d091815260200190565b60408051601f19818403018152908290526108ea91610ccc565b602060405180830381855afa158015610907573d6000803e3d6000fd5b5050506040513d601f19601f8201168201806040525081019061092a9190610da4565b146109645760405162461bcd60e51b815260206004820152600a602482015269189859081cd958dc995d60b21b60448201526064016101d6565b60058101805460ff60a01b1916600160a11b17905581358155600181015461098c9085610d72565b93505050808061099b90610d8b565b9150506107cf565b5060408051336024820152604480820184905282518083039091018152606490910182526020810180516001600160e01b031663a9059cbb60e01b17905290516000916060916001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001691610a1d91610ccc565b6000604051808303816000865af19150503d8060008114610a5a576040519150601f19603f3d011682016040523d82523d6000602084013e610a5f565b606091505b509092509050818015610a8a575080511580610a8a575080806020019051810190610a8a9190610cfb565b6107455760405162461bcd60e51b815260206004820152600f60248201526e1d1c985b9cd9995c8819985a5b1959608a1b60448201526064016101d6565b600060208284031215610ada57600080fd5b5035919050565b634e487b7160e01b600052602160045260246000fd5b60048110610b1557634e487b7160e01b600052602160045260246000fd5b9052565b600060e08201905082518252602083015160208301526040830151604083015260608301516060830152608083015160018060a01b0380821660808501528060a08601511660a0850152505060c0830151610b7760c0840182610af7565b5092915050565b60008060208385031215610b9157600080fd5b823567ffffffffffffffff80821115610ba957600080fd5b818501915085601f830112610bbd57600080fd5b813581811115610bcc57600080fd5b8660208260071b8501011115610be157600080fd5b60209290920196919550909350505050565b8781526020810187905260408101869052606081018590526001600160a01b038481166080830152831660a082015260e08101610c3360c0830184610af7565b98975050505050505050565b60008060208385031215610c5257600080fd5b823567ffffffffffffffff80821115610c6a57600080fd5b818501915085601f830112610c7e57600080fd5b813581811115610c8d57600080fd5b8660208260061b8501011115610be157600080fd5b60208082526010908201526f39b2b73232b910109e9037b934b3b4b760811b604082015260600190565b6000825160005b81811015610ced5760208186018101518583015201610cd3565b506000920191825250919050565b600060208284031215610d0d57600080fd5b815180151581146107a557600080fd5b634e487b7160e01b600052603260045260246000fd5b600060208284031215610d4557600080fd5b81356001600160a01b03811681146107a557600080fd5b634e487b7160e01b600052601160045260246000fd5b80820180821115610d8557610d85610d5c565b92915050565b600060018201610d9d57610d9d610d5c565b5060010190565b600060208284031215610db657600080fd5b505191905056fea2646970667358221220a055a4890a5ecf3876dbee91dfbeb46ba11b5f7c09b6d935173932d93f8fb92264736f6c63430008120033" diff --git a/dex/networks/erc20/contracts/v0/contract.go b/dex/networks/erc20/contracts/v0/contract.go index 3f9d522c31..4b366daf0e 100644 --- a/dex/networks/erc20/contracts/v0/contract.go +++ b/dex/networks/erc20/contracts/v0/contract.go @@ -27,13 +27,12 @@ var ( _ = common.Big1 _ = types.BloomLookup _ = event.NewSubscription - _ = abi.ConvertType ) // 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\"}],\"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: "0x60a060405234801561001057600080fd5b50604051610e92380380610e9283398101604081905261002f91610040565b6001600160a01b0316608052610070565b60006020828403121561005257600080fd5b81516001600160a01b038116811461006957600080fd5b9392505050565b608051610df361009f6000396000818160c50152818161029b0152818161066b01526109f30152610df36000f3fe608060405234801561001057600080fd5b506004361061007d5760003560e01c8063a8793f941161005b578063a8793f94146100ff578063d0f761c014610112578063eb84e7f214610135578063f4fd17f9146101a457600080fd5b80637249fbb61461008257806376467cbd146100975780638c8e8fee146100c0575b600080fd5b610095610090366004610ac8565b6101b7565b005b6100aa6100a5366004610ac8565b610376565b6040516100b79190610b19565b60405180910390f35b6100e77f000000000000000000000000000000000000000000000000000000000000000081565b6040516001600160a01b0390911681526020016100b7565b61009561010d366004610b7e565b610451565b610125610120366004610ac8565b61074c565b60405190151581526020016100b7565b610191610143366004610ac8565b60006020819052908152604090208054600182015460028301546003840154600485015460059095015493949293919290916001600160a01b0391821691811690600160a01b900460ff1687565b6040516100b79796959493929190610bf3565b6100956101b2366004610c3f565b6107ac565b3233146101df5760405162461bcd60e51b81526004016101d690610ca2565b60405180910390fd5b6101e88161074c565b6102255760405162461bcd60e51b815260206004820152600e60248201526d6e6f7420726566756e6461626c6560901b60448201526064016101d6565b60008181526020818152604080832060058101805460ff60a01b1916600360a01b17905560018101548251336024820152604480820192909252835180820390920182526064018352928301805163a9059cbb60e01b6001600160e01b0390911617905290519092916060916001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016916102c591610ccc565b6000604051808303816000865af19150503d8060008114610302576040519150601f19603f3d011682016040523d82523d6000602084013e610307565b606091505b5090925090508180156103325750805115806103325750808060200190518101906103329190610cfb565b6103705760405162461bcd60e51b815260206004820152600f60248201526e1d1c985b9cd9995c8819985a5b1959608a1b60448201526064016101d6565b50505050565b6103b36040805160e081018252600080825260208201819052918101829052606081018290526080810182905260a081018290529060c082015290565b60008281526020818152604091829020825160e08101845281548152600182015492810192909252600281015492820192909252600380830154606083015260048301546001600160a01b039081166080840152600584015490811660a084015291929160c0840191600160a01b90910460ff169081111561043757610437610ae1565b600381111561044857610448610ae1565b90525092915050565b3233146104705760405162461bcd60e51b81526004016101d690610ca2565b6000805b82811015610615573684848381811061048f5761048f610d1d565b9050608002019050600080600083602001358152602001908152602001600020905060008260600135116104ed5760405162461bcd60e51b81526020600482015260056024820152640c081d985b60da1b60448201526064016101d6565b813561052f5760405162461bcd60e51b815260206004820152601160248201527003020726566756e6454696d657374616d7607c1b60448201526064016101d6565b60006005820154600160a01b900460ff16600381111561055157610551610ae1565b146105905760405162461bcd60e51b815260206004820152600f60248201526e0c8eae040e6cac6e4cae840d0c2e6d608b1b60448201526064016101d6565b436002820155813560038201556004810180546001600160a01b031916331790556105c16060830160408401610d33565b6005820180546060850135600185018190556001600160a01b03939093166001600160a81b031990911617600160a01b1790556105fe9085610d72565b93505050808061060d90610d8b565b915050610474565b5060408051336024820152306044820152606480820184905282518083039091018152608490910182526020810180516001600160e01b03166323b872dd60e01b17905290516000916060916001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000169161069591610ccc565b6000604051808303816000865af19150503d80600081146106d2576040519150601f19603f3d011682016040523d82523d6000602084013e6106d7565b606091505b5090925090508180156107025750805115806107025750808060200190518101906107029190610cfb565b6107455760405162461bcd60e51b81526020600482015260146024820152731d1c985b9cd9995c88199c9bdb4819985a5b195960621b60448201526064016101d6565b5050505050565b600081815260208190526040812060016005820154600160a01b900460ff16600381111561077c5761077c610ae1565b148015610795575060048101546001600160a01b031633145b80156107a5575080600301544210155b9392505050565b3233146107cb5760405162461bcd60e51b81526004016101d690610ca2565b6000805b828110156109a357368484838181106107ea576107ea610d1d565b6020604091820293909301838101356000908152938490529220919250600190506005820154600160a01b900460ff16600381111561082b5761082b610ae1565b146108645760405162461bcd60e51b815260206004820152600960248201526862616420737461746560b81b60448201526064016101d6565b60058101546001600160a01b031633146108b25760405162461bcd60e51b815260206004820152600f60248201526e189859081c185c9d1a58da5c185b9d608a1b60448201526064016101d6565b8160200135600283600001356040516020016108d091815260200190565b60408051601f19818403018152908290526108ea91610ccc565b602060405180830381855afa158015610907573d6000803e3d6000fd5b5050506040513d601f19601f8201168201806040525081019061092a9190610da4565b146109645760405162461bcd60e51b815260206004820152600a602482015269189859081cd958dc995d60b21b60448201526064016101d6565b60058101805460ff60a01b1916600160a11b17905581358155600181015461098c9085610d72565b93505050808061099b90610d8b565b9150506107cf565b5060408051336024820152604480820184905282518083039091018152606490910182526020810180516001600160e01b031663a9059cbb60e01b17905290516000916060916001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001691610a1d91610ccc565b6000604051808303816000865af19150503d8060008114610a5a576040519150601f19603f3d011682016040523d82523d6000602084013e610a5f565b606091505b509092509050818015610a8a575080511580610a8a575080806020019051810190610a8a9190610cfb565b6107455760405162461bcd60e51b815260206004820152600f60248201526e1d1c985b9cd9995c8819985a5b1959608a1b60448201526064016101d6565b600060208284031215610ada57600080fd5b5035919050565b634e487b7160e01b600052602160045260246000fd5b60048110610b1557634e487b7160e01b600052602160045260246000fd5b9052565b600060e08201905082518252602083015160208301526040830151604083015260608301516060830152608083015160018060a01b0380821660808501528060a08601511660a0850152505060c0830151610b7760c0840182610af7565b5092915050565b60008060208385031215610b9157600080fd5b823567ffffffffffffffff80821115610ba957600080fd5b818501915085601f830112610bbd57600080fd5b813581811115610bcc57600080fd5b8660208260071b8501011115610be157600080fd5b60209290920196919550909350505050565b8781526020810187905260408101869052606081018590526001600160a01b038481166080830152831660a082015260e08101610c3360c0830184610af7565b98975050505050505050565b60008060208385031215610c5257600080fd5b823567ffffffffffffffff80821115610c6a57600080fd5b818501915085601f830112610c7e57600080fd5b813581811115610c8d57600080fd5b8660208260061b8501011115610be157600080fd5b60208082526010908201526f39b2b73232b910109e9037b934b3b4b760811b604082015260600190565b6000825160005b81811015610ced5760208186018101518583015201610cd3565b506000920191825250919050565b600060208284031215610d0d57600080fd5b815180151581146107a557600080fd5b634e487b7160e01b600052603260045260246000fd5b600060208284031215610d4557600080fd5b81356001600160a01b03811681146107a557600080fd5b634e487b7160e01b600052601160045260246000fd5b80820180821115610d8557610d85610d5c565b92915050565b600060018201610d9d57610d9d610d5c565b5060010190565b600060208284031215610db657600080fd5b505191905056fea2646970667358221220a055a4890a5ecf3876dbee91dfbeb46ba11b5f7c09b6d935173932d93f8fb92264736f6c63430008120033", + Bin: "0x60a060405234801561001057600080fd5b50604051610e92380380610e9283398101604081905261002f91610040565b6001600160a01b0316608052610070565b60006020828403121561005257600080fd5b81516001600160a01b038116811461006957600080fd5b9392505050565b608051610df361009f6000396000818160c50152818161029b0152818161066b01526109f30152610df36000f3fe608060405234801561001057600080fd5b506004361061007d5760003560e01c8063a8793f941161005b578063a8793f94146100ff578063d0f761c014610112578063eb84e7f214610135578063f4fd17f9146101a457600080fd5b80637249fbb61461008257806376467cbd146100975780638c8e8fee146100c0575b600080fd5b610095610090366004610ac8565b6101b7565b005b6100aa6100a5366004610ac8565b610376565b6040516100b79190610b19565b60405180910390f35b6100e77f000000000000000000000000000000000000000000000000000000000000000081565b6040516001600160a01b0390911681526020016100b7565b61009561010d366004610b7e565b610451565b610125610120366004610ac8565b61074c565b60405190151581526020016100b7565b610191610143366004610ac8565b60006020819052908152604090208054600182015460028301546003840154600485015460059095015493949293919290916001600160a01b0391821691811690600160a01b900460ff1687565b6040516100b79796959493929190610bf3565b6100956101b2366004610c3f565b6107ac565b3233146101df5760405162461bcd60e51b81526004016101d690610ca2565b60405180910390fd5b6101e88161074c565b6102255760405162461bcd60e51b815260206004820152600e60248201526d6e6f7420726566756e6461626c6560901b60448201526064016101d6565b60008181526020818152604080832060058101805460ff60a01b1916600360a01b17905560018101548251336024820152604480820192909252835180820390920182526064018352928301805163a9059cbb60e01b6001600160e01b0390911617905290519092916060916001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016916102c591610ccc565b6000604051808303816000865af19150503d8060008114610302576040519150601f19603f3d011682016040523d82523d6000602084013e610307565b606091505b5090925090508180156103325750805115806103325750808060200190518101906103329190610cfb565b6103705760405162461bcd60e51b815260206004820152600f60248201526e1d1c985b9cd9995c8819985a5b1959608a1b60448201526064016101d6565b50505050565b6103b36040805160e081018252600080825260208201819052918101829052606081018290526080810182905260a081018290529060c082015290565b60008281526020818152604091829020825160e08101845281548152600182015492810192909252600281015492820192909252600380830154606083015260048301546001600160a01b039081166080840152600584015490811660a084015291929160c0840191600160a01b90910460ff169081111561043757610437610ae1565b600381111561044857610448610ae1565b90525092915050565b3233146104705760405162461bcd60e51b81526004016101d690610ca2565b6000805b82811015610615573684848381811061048f5761048f610d1d565b9050608002019050600080600083602001358152602001908152602001600020905060008260600135116104ed5760405162461bcd60e51b81526020600482015260056024820152640c081d985b60da1b60448201526064016101d6565b813561052f5760405162461bcd60e51b815260206004820152601160248201527003020726566756e6454696d657374616d7607c1b60448201526064016101d6565b60006005820154600160a01b900460ff16600381111561055157610551610ae1565b146105905760405162461bcd60e51b815260206004820152600f60248201526e0c8eae040e6cac6e4cae840d0c2e6d608b1b60448201526064016101d6565b436002820155813560038201556004810180546001600160a01b031916331790556105c16060830160408401610d33565b6005820180546060850135600185018190556001600160a01b03939093166001600160a81b031990911617600160a01b1790556105fe9085610d72565b93505050808061060d90610d8b565b915050610474565b5060408051336024820152306044820152606480820184905282518083039091018152608490910182526020810180516001600160e01b03166323b872dd60e01b17905290516000916060916001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000169161069591610ccc565b6000604051808303816000865af19150503d80600081146106d2576040519150601f19603f3d011682016040523d82523d6000602084013e6106d7565b606091505b5090925090508180156107025750805115806107025750808060200190518101906107029190610cfb565b6107455760405162461bcd60e51b81526020600482015260146024820152731d1c985b9cd9995c88199c9bdb4819985a5b195960621b60448201526064016101d6565b5050505050565b600081815260208190526040812060016005820154600160a01b900460ff16600381111561077c5761077c610ae1565b148015610795575060048101546001600160a01b031633145b80156107a5575080600301544210155b9392505050565b3233146107cb5760405162461bcd60e51b81526004016101d690610ca2565b6000805b828110156109a357368484838181106107ea576107ea610d1d565b6020604091820293909301838101356000908152938490529220919250600190506005820154600160a01b900460ff16600381111561082b5761082b610ae1565b146108645760405162461bcd60e51b815260206004820152600960248201526862616420737461746560b81b60448201526064016101d6565b60058101546001600160a01b031633146108b25760405162461bcd60e51b815260206004820152600f60248201526e189859081c185c9d1a58da5c185b9d608a1b60448201526064016101d6565b8160200135600283600001356040516020016108d091815260200190565b60408051601f19818403018152908290526108ea91610ccc565b602060405180830381855afa158015610907573d6000803e3d6000fd5b5050506040513d601f19601f8201168201806040525081019061092a9190610da4565b146109645760405162461bcd60e51b815260206004820152600a602482015269189859081cd958dc995d60b21b60448201526064016101d6565b60058101805460ff60a01b1916600160a11b17905581358155600181015461098c9085610d72565b93505050808061099b90610d8b565b9150506107cf565b5060408051336024820152604480820184905282518083039091018152606490910182526020810180516001600160e01b031663a9059cbb60e01b17905290516000916060916001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001691610a1d91610ccc565b6000604051808303816000865af19150503d8060008114610a5a576040519150601f19603f3d011682016040523d82523d6000602084013e610a5f565b606091505b509092509050818015610a8a575080511580610a8a575080806020019051810190610a8a9190610cfb565b6107455760405162461bcd60e51b815260206004820152600f60248201526e1d1c985b9cd9995c8819985a5b1959608a1b60448201526064016101d6565b600060208284031215610ada57600080fd5b5035919050565b634e487b7160e01b600052602160045260246000fd5b60048110610b1557634e487b7160e01b600052602160045260246000fd5b9052565b600060e08201905082518252602083015160208301526040830151604083015260608301516060830152608083015160018060a01b0380821660808501528060a08601511660a0850152505060c0830151610b7760c0840182610af7565b5092915050565b60008060208385031215610b9157600080fd5b823567ffffffffffffffff80821115610ba957600080fd5b818501915085601f830112610bbd57600080fd5b813581811115610bcc57600080fd5b8660208260071b8501011115610be157600080fd5b60209290920196919550909350505050565b8781526020810187905260408101869052606081018590526001600160a01b038481166080830152831660a082015260e08101610c3360c0830184610af7565b98975050505050505050565b60008060208385031215610c5257600080fd5b823567ffffffffffffffff80821115610c6a57600080fd5b818501915085601f830112610c7e57600080fd5b813581811115610c8d57600080fd5b8660208260061b8501011115610be157600080fd5b60208082526010908201526f39b2b73232b910109e9037b934b3b4b760811b604082015260600190565b6000825160005b81811015610ced5760208186018101518583015201610cd3565b506000920191825250919050565b600060208284031215610d0d57600080fd5b815180151581146107a557600080fd5b634e487b7160e01b600052603260045260246000fd5b600060208284031215610d4557600080fd5b81356001600160a01b03811681146107a557600080fd5b634e487b7160e01b600052601160045260246000fd5b80820180821115610d8557610d85610d5c565b92915050565b600060018201610d9d57610d9d610d5c565b5060010190565b600060208284031215610db657600080fd5b505191905056fea26469706673582212206e467d69294766316da063165c1299fd448b484e03da316a32f5fc033f36ef0d64736f6c63430008120033", } // ERC20SwapABI is the input ABI used to generate the binding from. @@ -158,11 +157,11 @@ func NewERC20SwapFilterer(address common.Address, filterer bind.ContractFilterer // 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 := ERC20SwapMetaData.GetAbi() + parsed, err := abi.JSON(strings.NewReader(ERC20SwapABI)) if err != nil { return nil, err } - return bind.NewBoundContract(address, *parsed, caller, transactor, filterer), nil + return bind.NewBoundContract(address, parsed, caller, transactor, filterer), nil } // Call invokes the (constant) contract method with params as input values and diff --git a/dex/networks/erc20/contracts/v0/swap_contract.bin b/dex/networks/erc20/contracts/v0/swap_contract.bin new file mode 100644 index 0000000000000000000000000000000000000000..bf7155710808cda0bc8467c5cf60fa0b71e7b166 GIT binary patch literal 3730 zcmbVPZ)_W98Nb(O$Btu{B#PYzx9R~K5=>$&nOpiHt3|sIq-nZ4Wp>vK;(eR6qoYkr zTZRzbxc6+Qs2%HjPSd7p%d)Xb8=Cf^L+d6qDSViO#1Iomn^r(%A7H2m5Ck;AXv^=p zyEt)UwSc3%e;(hx@AK#Pd!EO9j+g9v-JH<6D_nE0IVLYzVol?7x{I;8MKWHJ$`;z) zzR*f#hco=m74$rJAv_3y#tpBg_u>RQJV9DZ56Qgj6WZQTJl!x&6VJPr$Sw-)X|8SI znw0zvW=knz=Ypx@tIQUiix{$tTwA=(-Bs$vH+2i`ahzG?3+vtS=fAo2RFBY{O1Q** zH?Vx`12NwxSTB};tiAs?EQ?tF{o2}pV)l+Lux(3^P=pXd zh3`wsa7Q`w*eH)xG3Yf6@j8Ztnqi~UryA3KL-|Xp_+H+Zc@;!l#rci=u_ZIloSr!~ z-5_HZBPB1(d60d(7r*_#K$`PaZg89NN9->yr~=*S_6c75H}gc7l$?aljE=MnlQ18ayEx%)9}d z_UE~dX{~yQGs#9hYcM#EZ+AV`Pz}3kxt^{X%%iiacB9H%Yy5@8`5*CxLg0mkf{uW! zh5Ibk@Hub#=C)DI`y%#+FEVeEesF|a)g{-acK+2j%^?>m_X#~lHv1;u*w42PvAhSe z*+ZT^L?NrO`-Gmv%iK2|Q#DLwO7K)4sF<#mbHjxTpeRc+g)O3y`4FDe54QE>M*_{s^&cAzh>^uFlJDvWA`SDg)f}DlWiy}I&rh^ou#Lw&_K+-c9TSfSM2lk8 zpC1IzMB>L3Mqa8hksU-NpbyB%>#WFG9T)-=1NHQ2HJuOE&lmK5B3U2~j<~MsB73Op zOaqOsD>|}DWKWbG1W?3}l&MC!5tBa~B?8xDHD!CmGUYRbqlSrK3k}111SaRlKk@3J z-+6`zx_h|j@dna|1C5}%Xzw~bd{0ExrPd_I5f zS0fRrceMtMNKK_TN|PXPNgD8WJE$a&Rz&h>2oQAMA5Tei3SOosE9J zJLg2Dosxd?r>)AiqgLnlo775P#cfvuYH@>?d@lx6u&M-DG>~cM%r!{o%dcIu|? zs)*EG5wcSgt(QkNq%-i;S9XaBRS_#x8tx z$Ca^l&V`#ZsHCib^2#+#B?~)|ddm;f1Y$sGbz{G*=>VxhE)}V(5vX8)P$cR*|CNJ> zJChaE0rx%(=*mp$%Z8{>Z1kv5xTq8+<^9f211H(%%1)o?Y;S%#Fn@2L_PaZNefCTI z(rWvc>m}pQP`#8z8P-hE`D7~_JI^h%G5PZrHU@RIm4_2;c^H-0|C5L54Y%fDdMCy2 z7CcOUc@+=S$0HsFi)9|BkKcxe|6N_BiXOJf=__ITX%>A2QjR}3@~y8?x1rhmV?1v2 zJ1E|uT({`TRoZ{6Y$7vX@W2YsqAXWs02&p~60Is5Dpf!&tMAtW{s(K>*0b&{!ODc0lwNBlAmmjL_6(6|~eOwPkx9f#aF6+!U>wIIC z^@^_7BUw>W8SZ*B+)lv-JXxqSM#A?(Ave&6vD5yHsVLjSBgcps2ubYMxyiv#Z?b}k+plh zr}?^ze4Z+x4U>eUAS18?)d ANB{r; literal 0 HcmV?d00001 diff --git a/dex/networks/erc20/contracts/v0/token_contract.bin b/dex/networks/erc20/contracts/v0/token_contract.bin new file mode 100644 index 0000000000000000000000000000000000000000..6ec95be8db4603cb5127309f27b98d0bc64cb1e5 GIT binary patch literal 2796 zcmaJ@Yitx%6ux(6yG!eXmb9y6!HxwDLZrM}z!2RAH6T8A$9hJuZv_I|7I5(6oLj<8EJGF4aPJG>S5ia+)<&@ zP}j_yx95YI3*UYFeb^HK#iT08oRNFE$)w7d@Kw^54Yjqd_G|d1pyxq7tE5+wwcP$t^oqK0%^T5i{D^9*} z(zxNL4ij}c56^u6%go^M(Jiw-fBw5Wzi5dy9o&cUb!$hyAG-F#yQWq)Z0kF8(W{MJ zjhF7&dE%juCk{@O9%(!^HOi#VFd<0HwB$*3jH>wz8euqV_>Edd6ZwVSTqqQZX^GpH zZC($@0zgz~0gnpPft=QW8pafE`gKP8;?QmSosfnes|}gacjD*TG?Z*Y4o81bZ`RZ? z$%7z~F?Ymj^`lO99{7Mk1{57}87#*|D3 zxrYa);DJ(`2QIUt2r5!#7kN(Z@jII7vUw)HrH6*iXrD#<01Jz9i!y05GP6y=c6a%f~FXS5)1k2m_}UUayxDc zw63wuuEG*85s)7fONh1I#R`vMIc5P3%T|B}^{mp%`~0Sg7lGiwveFFGE=Yye@>y7`vH_HsA#Kv6%-Ff+`Kpj6g`s7aVDp@b&j$dHh|}x^C>0A%>rTa zQz9jWOcid!B1YEFJaXCar{kO{MbA1qGU<&eWmP7xsm@Mu zwH6|0a>ZEsO+-kRMQ9k8;3C9^^f-V_%7tJE@JD%oT=v2P?&JMfz&OpYh?VFV84>QE zWyxGMj!fya&kxuolT5k+A1TiV1Fndb#h6we7-pUWc@1I`x)ma#inDu&8} zUR@sayfSWeK~t>FE#j}r&^|io{pFO+wz?*CSUHHV#{#maaPaGQX&p3I{Z6~Mg`rX3jVcxJK+L4N8&iB&x zeB+3zuV>XPzJ+Y;HIkpK-pAv%L2(4yCSFKXH7WCUi0gqs(Cne87=-%CI5?@_rc}HN z^0$WGROIEp8@z||BPBoO368R?Egl%&@AB)+JCbGI*J4^9;6>R>n?yGwL)0VyR*%s2 zO(^L4YAjbb7h=A8P;8`xmJUA6EZW3FIq@rOi?0}8OVRKa(^W0@>lhCoc9T1`eCzT& z9Q3^z6r9__x9gM(LZzpT@9FcNHoo2e`?SH%*WJg>`f+MC#TU?-7n5r{R(0OhFi9C0 wJ~U^f`Q?GC8UL>6S>Al=@UnfkR;_$%`U4YE(YEa$4JSL(ZHc91!Xz^PKk<9OZ2$lO literal 0 HcmV?d00001 diff --git a/dex/networks/eth/contracts/ETHSwapV0.sol b/dex/networks/eth/contracts/ETHSwapV0.sol index e65c188285..ca53936269 100644 --- a/dex/networks/eth/contracts/ETHSwapV0.sol +++ b/dex/networks/eth/contracts/ETHSwapV0.sol @@ -122,7 +122,7 @@ contract ETHSwap { } // redeem redeems a contract. It checks that the sender is not a contract, - // and that the secret hash hashes to secretHash. msg.value is tranfered + // and that the secret hashes to secretHash. msg.value is tranfered // from the contract to the sender. // // It is important to note that this uses call.value which comes with no diff --git a/dex/networks/eth/contracts/ETHSwapV1.sol b/dex/networks/eth/contracts/ETHSwapV1.sol new file mode 100644 index 0000000000..5ea52c4e83 --- /dev/null +++ b/dex/networks/eth/contracts/ETHSwapV1.sol @@ -0,0 +1,261 @@ +// SPDX-License-Identifier: BlueOak-1.0.0 +// pragma should be as specific as possible to allow easier validation. +pragma solidity = 0.8.18; + +// 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 { + bytes4 private constant TRANSFER_FROM_SELECTOR = bytes4(keccak256("transferFrom(address,address,uint256)")); + bytes4 private constant TRANSFER_SELECTOR = bytes4(keccak256("transfer(address,uint256)")); + // 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; + bytes32 constant RefundRecordHash = 0xAF9613760F72635FBDB44A5A0A63C39F12AF30F950A6EE5C971BE188E89C4051; + + // 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; + uint256 value; + address initiator; + uint64 refundTimestamp; + address participant; + } + + // 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(address token, Vector calldata v) public pure returns (bytes32) { + return sha256( + bytes.concat( + v.secretHash, + bytes20(v.initiator), + bytes20(v.participant), + bytes32(v.value), + bytes8(v.refundTimestamp), + bytes20(token) + ) + ); + } + + // 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(address token, Vector calldata v) + private view returns (bytes32, bytes32, uint256) + { + bytes32 k = contractKey(token, v); + bytes32 record = swaps[k]; + return (k, record, uint256(record)); + } + + // status returns the current state of the swap. + function status(address token, Vector calldata v) + public view returns(Status memory) + { + (, bytes32 record, uint256 blockNum) = retrieveStatus(token, 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(address token, 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"); + require(v.secretHash != RefundRecordHash, "illegal secret hash (refund record hash)"); + + bytes32 k = contractKey(token, 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; + } + + if (token == address(0)) { + require(initVal == msg.value, "bad val"); + } else { + bool success; + bytes memory data; + (success, data) = token.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 vector + // can be redeemed using secret. isRedeemable DOES NOT check if the caller + // is the participant in the vector. + function isRedeemable(address token, Vector calldata v) + public + view + returns (bool) + { + (, bytes32 record, uint256 blockNum) = retrieveStatus(token, 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 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(address token, 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(token, 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; + } + + if (token == address(0)) { + (bool ok, ) = payable(msg.sender).call{value: amountToRedeem}(""); + require(ok == true, "transfer failed"); + } else { + bool success; + bytes memory data; + (success, data) = token.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(address token, 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(token, v); + + // Is this swap initialized? + // This check also guarantees that the swap has not already been + // refunded i.e. record != RefundRecord, since RefundRecord is certainly + // greater than block.number. + require(blockNum > 0 && blockNum <= block.number, "swap not active"); + + // Is it already redeemed? + require(!secretValidates(record, v.secretHash), "swap already redeemed"); + + swaps[k] = RefundRecord; + + if (token == address(0)) { + (bool ok, ) = payable(v.initiator).call{value: v.value}(""); + require(ok == true, "transfer failed"); + } else { + bool success; + bytes memory data; + (success, data) = token.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/eth/contracts/build-multibalance.sh b/dex/networks/eth/contracts/build-multibalance.sh old mode 100644 new mode 100755 index 0ddd19b447..c9e6989e91 --- a/dex/networks/eth/contracts/build-multibalance.sh +++ b/dex/networks/eth/contracts/build-multibalance.sh @@ -18,26 +18,11 @@ fi mkdir temp solc --abi --bin --bin-runtime --overwrite --optimize ${SOLIDITY_FILE} -o ./temp/ -BYTECODE=$(<./temp/${CONTRACT_NAME}.bin-runtime) - -cat > "./${PKG_NAME}/BinRuntime${CONTRACT_NAME}.go" < "${PKG_NAME}/contract.bin" rm -fr temp diff --git a/dex/networks/eth/contracts/multibalance/BinRuntimeMultiBalanceV0.go b/dex/networks/eth/contracts/multibalance/BinRuntimeMultiBalanceV0.go deleted file mode 100644 index 7f70e53a0e..0000000000 --- a/dex/networks/eth/contracts/multibalance/BinRuntimeMultiBalanceV0.go +++ /dev/null @@ -1,6 +0,0 @@ -// Code generated - DO NOT EDIT. -// This file is a generated binding and any manual changes will be lost. - -package multibalance - -const MultiBalanceV0RuntimeBin = "608060405234801561001057600080fd5b506004361061002b5760003560e01c8063d3e5ca8714610030575b600080fd5b61004361003e3660046102a5565b610059565b604051610050919061032b565b60405180910390f35b60606000610068836001610385565b67ffffffffffffffff8111156100805761008061039e565b6040519080825280602002602001820160405280156100a9578160200160208202803683370190505b509050846001600160a01b031631816000815181106100ca576100ca6103b4565b60200260200101818152505060005b838110156102805760008585838181106100f5576100f56103b4565b905060200201602081019061010a91906103ca565b905060006060826001600160a01b03167f70a08231b98ef4ca268c9cc3f6b4590e4bfec28280db06bb5d45e689f2a360be8a60405160240161015b91906001600160a01b0391909116815260200190565b60408051601f198184030181529181526020820180516001600160e01b03166001600160e01b031990941693909317909252905161019991906103ec565b600060405180830381855afa9150503d80600081146101d4576040519150601f19603f3d011682016040523d82523d6000602084013e6101d9565b606091505b509092509050816102285760405162461bcd60e51b815260206004820152601560248201527418985b185b98d953d98818d85b1b0819985a5b1959605a1b604482015260640160405180910390fd5b60008180602001905181019061023e919061041b565b9050808661024d876001610385565b8151811061025d5761025d6103b4565b60200260200101818152505050505050808061027890610434565b9150506100d9565b50949350505050565b80356001600160a01b03811681146102a057600080fd5b919050565b6000806000604084860312156102ba57600080fd5b6102c384610289565b9250602084013567ffffffffffffffff808211156102e057600080fd5b818601915086601f8301126102f457600080fd5b81358181111561030357600080fd5b8760208260051b850101111561031857600080fd5b6020830194508093505050509250925092565b6020808252825182820181905260009190848201906040850190845b8181101561036357835183529284019291840191600101610347565b50909695505050505050565b634e487b7160e01b600052601160045260246000fd5b808201808211156103985761039861036f565b92915050565b634e487b7160e01b600052604160045260246000fd5b634e487b7160e01b600052603260045260246000fd5b6000602082840312156103dc57600080fd5b6103e582610289565b9392505050565b6000825160005b8181101561040d57602081860181015185830152016103f3565b506000920191825250919050565b60006020828403121561042d57600080fd5b5051919050565b6000600182016104465761044661036f565b506001019056fea26469706673582212207074e3e189a2692e2841f7943a9de24bcdbca943ee6bfcbd83a2fa6e43ec497b64736f6c63430008120033" diff --git a/dex/networks/eth/contracts/multibalance/contract.bin b/dex/networks/eth/contracts/multibalance/contract.bin new file mode 100644 index 0000000000000000000000000000000000000000..195ca6c0377535310b6280a230cfd213df054796 GIT binary patch literal 1187 zcmZWoU1$|Y6rQuYiWFL6Ft-V}kV^?7h*GUdX(4Gss;!7JyTVxDYzWDo~Zx?*hhX#E}mGnt`Fs6bu(KCC!!Qw8%y>sR}XTR?| zXHa2(v`4i`))FIAtAfiQQIeIAeTi&&4c|M*pr=K$?S$^e#ZT1e{iXWk7Rf$~MV>F& zpk#eL7)bAOSx7ci7T6z3#xpDZ?fFJ!zFiiG$RztQL6Cl>EPi(9lcdqCcc4=ff)I_vSmFm*JN|o{opmoGK zGLh((Ht8wSQK=-_L01U6l0z*gFjd!JC4AtR*UN%}4AHEA z)uE3Ltlcr{;l>*yd;ghwehF`%BH`Et5=3U3S((iWIwI=UNmC(qcIeax9c398|}X*>O~twlDJaL&U|=A^HteFGlkKyjUCb^VUihQ^FHzjo zF*__ehH$uu-#E}1wIg)xTqQc1dIUCfo_T#lGAKIFR8@KhC$Z7wyd~a9EWMHUTEgj3 zN_xLh=AN=3NsdiIgHM4X^ljrP6t9~o>^m^k&dUsRlO_fEB zx|u@v`h+P%BjznOP`U~kuVQBctjh$+MkVSN&h&BB7^=9A6SRKTV(fo)J=d%okRYkh6d>`ncSi2 zTK;ilCZwB@a}7R>Y5uFAA*>`25z}R2imV2iiRd!tt~Uz?I~oOf*gT+{DuaD7t(+|< zt+u>2KXz>V`x8e#Z7q$TeDe3y{IQ+e242p5b>Z=WdpEBPKKtpvTgm*ZUk0D "./${PKG_NAME}/BinRuntimeV${VERSION}.go" < "v${VERSION}/contract.bin" rm -fr temp diff --git a/dex/networks/eth/contracts/v0/BinRuntimeV0.go b/dex/networks/eth/contracts/v0/BinRuntimeV0.go deleted file mode 100644 index 35f5a11c05..0000000000 --- a/dex/networks/eth/contracts/v0/BinRuntimeV0.go +++ /dev/null @@ -1,6 +0,0 @@ -// Code generated - DO NOT EDIT. -// This file is a generated binding and any manual changes will be lost. - -package v0 - -const ETHSwapRuntimeBin = "6080604052600436106100555760003560e01c80637249fbb61461005a57806376467cbd1461007c578063a8793f94146100b2578063d0f761c0146100c5578063eb84e7f2146100f5578063f4fd17f914610171575b600080fd5b34801561006657600080fd5b5061007a610075366004610871565b610191565b005b34801561008857600080fd5b5061009c610097366004610871565b6102c9565b6040516100a991906108c2565b60405180910390f35b61007a6100c0366004610927565b6103a4565b3480156100d157600080fd5b506100e56100e0366004610871565b61059b565b60405190151581526020016100a9565b34801561010157600080fd5b5061015e610110366004610871565b60006020819052908152604090208054600182015460028301546003840154600485015460059095015493949293919290916001600160a01b0391821691811690600160a01b900460ff1687565b6040516100a9979695949392919061099c565b34801561017d57600080fd5b5061007a61018c3660046109e8565b6105e3565b3233146101b95760405162461bcd60e51b81526004016101b090610a4b565b60405180910390fd5b6101c28161059b565b6101ff5760405162461bcd60e51b815260206004820152600e60248201526d6e6f7420726566756e6461626c6560901b60448201526064016101b0565b60008181526020819052604080822060058101805460ff60a01b1916600360a01b1790556004810154600182015492519193926001600160a01b03909116918381818185875af1925050503d8060008114610276576040519150601f19603f3d011682016040523d82523d6000602084013e61027b565b606091505b50909150506001811515146102c45760405162461bcd60e51b815260206004820152600f60248201526e1d1c985b9cd9995c8819985a5b1959608a1b60448201526064016101b0565b505050565b6103066040805160e081018252600080825260208201819052918101829052606081018290526080810182905260a081018290529060c082015290565b60008281526020818152604091829020825160e08101845281548152600182015492810192909252600281015492820192909252600380830154606083015260048301546001600160a01b039081166080840152600584015490811660a084015291929160c0840191600160a01b90910460ff169081111561038a5761038a61088a565b600381111561039b5761039b61088a565b90525092915050565b3233146103c35760405162461bcd60e51b81526004016101b090610a4b565b6000805b8281101561056157368484838181106103e2576103e2610a75565b9050608002019050600080600083602001358152602001908152602001600020905060008260600135116104405760405162461bcd60e51b81526020600482015260056024820152640c081d985b60da1b60448201526064016101b0565b81356104825760405162461bcd60e51b815260206004820152601160248201527003020726566756e6454696d657374616d7607c1b60448201526064016101b0565b60006005820154600160a01b900460ff1660038111156104a4576104a461088a565b146104dc5760405162461bcd60e51b8152602060048201526008602482015267064757020737761760c41b60448201526064016101b0565b436002820155813560038201556004810180546001600160a01b0319163317905561050d6060830160408401610a8b565b6005820180546060850135600185018190556001600160a01b03939093166001600160a81b031990911617600160a01b17905561054a9085610aca565b93505050808061055990610ae3565b9150506103c7565b503481146102c45760405162461bcd60e51b8152602060048201526007602482015266189859081d985b60ca1b60448201526064016101b0565b600081815260208190526040812060016005820154600160a01b900460ff1660038111156105cb576105cb61088a565b1480156105dc575080600301544210155b9392505050565b3233146106025760405162461bcd60e51b81526004016101b090610a4b565b6000805b828110156107da573684848381811061062157610621610a75565b6020604091820293909301838101356000908152938490529220919250600190506005820154600160a01b900460ff1660038111156106625761066261088a565b1461069b5760405162461bcd60e51b815260206004820152600960248201526862616420737461746560b81b60448201526064016101b0565b60058101546001600160a01b031633146106e95760405162461bcd60e51b815260206004820152600f60248201526e189859081c185c9d1a58da5c185b9d608a1b60448201526064016101b0565b81602001356002836000013560405160200161070791815260200190565b60408051601f198184030181529082905261072191610afc565b602060405180830381855afa15801561073e573d6000803e3d6000fd5b5050506040513d601f19601f820116820180604052508101906107619190610b2b565b1461079b5760405162461bcd60e51b815260206004820152600a602482015269189859081cd958dc995d60b21b60448201526064016101b0565b60058101805460ff60a01b1916600160a11b1790558135815560018101546107c39085610aca565b9350505080806107d290610ae3565b915050610606565b50604051600090339083908381818185875af1925050503d806000811461081d576040519150601f19603f3d011682016040523d82523d6000602084013e610822565b606091505b509091505060018115151461086b5760405162461bcd60e51b815260206004820152600f60248201526e1d1c985b9cd9995c8819985a5b1959608a1b60448201526064016101b0565b50505050565b60006020828403121561088357600080fd5b5035919050565b634e487b7160e01b600052602160045260246000fd5b600481106108be57634e487b7160e01b600052602160045260246000fd5b9052565b600060e08201905082518252602083015160208301526040830151604083015260608301516060830152608083015160018060a01b0380821660808501528060a08601511660a0850152505060c083015161092060c08401826108a0565b5092915050565b6000806020838503121561093a57600080fd5b823567ffffffffffffffff8082111561095257600080fd5b818501915085601f83011261096657600080fd5b81358181111561097557600080fd5b8660208260071b850101111561098a57600080fd5b60209290920196919550909350505050565b8781526020810187905260408101869052606081018590526001600160a01b038481166080830152831660a082015260e081016109dc60c08301846108a0565b98975050505050505050565b600080602083850312156109fb57600080fd5b823567ffffffffffffffff80821115610a1357600080fd5b818501915085601f830112610a2757600080fd5b813581811115610a3657600080fd5b8660208260061b850101111561098a57600080fd5b60208082526010908201526f39b2b73232b910109e9037b934b3b4b760811b604082015260600190565b634e487b7160e01b600052603260045260246000fd5b600060208284031215610a9d57600080fd5b81356001600160a01b03811681146105dc57600080fd5b634e487b7160e01b600052601160045260246000fd5b80820180821115610add57610add610ab4565b92915050565b600060018201610af557610af5610ab4565b5060010190565b6000825160005b81811015610b1d5760208186018101518583015201610b03565b506000920191825250919050565b600060208284031215610b3d57600080fd5b505191905056fea2646970667358221220d288c9a18362adf67607179f5c8585d0abe014bdb904b6e878451ac0c393a04364736f6c63430008120033" diff --git a/dex/networks/eth/contracts/v0/contract.bin b/dex/networks/eth/contracts/v0/contract.bin new file mode 100644 index 0000000000000000000000000000000000000000..32395c9d6baf07fbd07c060ac16599b9ba3025ab GIT binary patch literal 2970 zcmai0TZ|M%6z!Ux_xc!cXJ8!&jWPP+j{ydv$>PKZz7o^ZS!RGnb8A^Zqk_mIQ8DT2 znPJUpV)YD{7ABjn@Oolke&mFEHg9m#a{d zYGLYngfb76!$6;Q0)5Mk=}w^7e*LY?W`#o*L%?u7W9{|q0nRe*^<uJ_^K`qK9Z?|>&Jc*N5L40fEs6;S%ol)X zAV(%1bL2{j3Sd?|G)h6U(Z(x-Tu>E4)JNC+(y(oN&_l3(9@(Kz6WFw1fNmLNIiEpc zgT6DUn60tpJn|H_rNDM=8Q3;4ka}zrMUK2024m2RdYQmW&V0P$&uWy>5d3EeXSOeUZuX7_Tg?)sT}6H@`-huX%cV*_AeV z5r)=4f~qKi0x(%jDcoXYSZc;6Vue{eTI}AWrKFh7!pLRG`Qt8~>gHY)c+~It)hzwh zV`e0AU3p{qyE|6w{k*blMeYmQ*&PA8h^ZRW%qU_)BBF;XJbFkI(a)o&dv1}`TWpNq zS&1Z;?U+OFcSpXBB0gIw3S{D;RIJKb?_&<+7DL%QcDc$Q6>E&ApG69A5Uim(D-f$g zJS*Glo3feH{nzs50F*yRhtVPKk#(N_xa>9|L(k)})hJZdF*3)^56iB-L={DEc_;Mw zH_RPht*QOE`}qH^O<`ffUNps~Dm*q7(gd52ovzp(nQZ9ROxA6xvJB-Dk8~5*OFoHH z9r?r;R(O1&mJeB~BqAVyRRwr4Ae?4RIt?pmpn9R6UdM_LBfxMgd-46TISFbv?cCLHyVv`U``7m-a@F z#uJGkECpmaLW76v^1H)epplXOR>(q5>3nqDj!|(fUD7*bU(HnkR!+%I0`{b(AFWgUuFU3D&#*AK8W-JNk^h?U}s|=}CmRwXl zG;n^^L*tOAddLxOYtM&E*O`NtU91F?Mjy$X#0s9Wlvn#aIRO?!3s&Z7RqN{5shh)N z`-P1cT0fo-D^EG$xTv$huG4}lo6S?L|9_A*W$vl%;gVgTfD-vSV`^|L93%@j4BH#* zv8A#SFfyvtDcGpGoCVtzUSf4TS|(OgHw|!AaeQ^BPYcFVrxk{pKKuAS6-WIjmSzC_ z)f+(is!IW+uWyZo0MbjtR}CP32>^BJW+aZX`KH6i7A-oS$-E?&9bamYa}U+0@{se>|uKG&Z*eY)cvmx9~s%Y`H^+E8Ohnk G;(q{V2kWr_ literal 0 HcmV?d00001 diff --git a/dex/networks/eth/contracts/v0/contract.go b/dex/networks/eth/contracts/v0/contract.go index d11ea6cff3..5e44d70cf3 100644 --- a/dex/networks/eth/contracts/v0/contract.go +++ b/dex/networks/eth/contracts/v0/contract.go @@ -56,7 +56,7 @@ type ETHSwapSwap struct { // ETHSwapMetaData contains all meta data concerning the ETHSwap contract. var ETHSwapMetaData = &bind.MetaData{ ABI: "[{\"inputs\":[],\"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\":\"structETHSwap.Initiation[]\",\"name\":\"initiations\",\"type\":\"tuple[]\"}],\"name\":\"initiate\",\"outputs\":[],\"stateMutability\":\"payable\",\"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\":\"structETHSwap.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\":\"enumETHSwap.State\",\"name\":\"state\",\"type\":\"uint8\"}],\"internalType\":\"structETHSwap.Swap\",\"name\":\"\",\"type\":\"tuple\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"name\":\"swaps\",\"outputs\":[{\"internalType\":\"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\":\"enumETHSwap.State\",\"name\":\"state\",\"type\":\"uint8\"}],\"stateMutability\":\"view\",\"type\":\"function\"}]", - Bin: "0x608060405234801561001057600080fd5b50610b7a806100206000396000f3fe6080604052600436106100555760003560e01c80637249fbb61461005a57806376467cbd1461007c578063a8793f94146100b2578063d0f761c0146100c5578063eb84e7f2146100f5578063f4fd17f914610171575b600080fd5b34801561006657600080fd5b5061007a610075366004610871565b610191565b005b34801561008857600080fd5b5061009c610097366004610871565b6102c9565b6040516100a991906108c2565b60405180910390f35b61007a6100c0366004610927565b6103a4565b3480156100d157600080fd5b506100e56100e0366004610871565b61059b565b60405190151581526020016100a9565b34801561010157600080fd5b5061015e610110366004610871565b60006020819052908152604090208054600182015460028301546003840154600485015460059095015493949293919290916001600160a01b0391821691811690600160a01b900460ff1687565b6040516100a9979695949392919061099c565b34801561017d57600080fd5b5061007a61018c3660046109e8565b6105e3565b3233146101b95760405162461bcd60e51b81526004016101b090610a4b565b60405180910390fd5b6101c28161059b565b6101ff5760405162461bcd60e51b815260206004820152600e60248201526d6e6f7420726566756e6461626c6560901b60448201526064016101b0565b60008181526020819052604080822060058101805460ff60a01b1916600360a01b1790556004810154600182015492519193926001600160a01b03909116918381818185875af1925050503d8060008114610276576040519150601f19603f3d011682016040523d82523d6000602084013e61027b565b606091505b50909150506001811515146102c45760405162461bcd60e51b815260206004820152600f60248201526e1d1c985b9cd9995c8819985a5b1959608a1b60448201526064016101b0565b505050565b6103066040805160e081018252600080825260208201819052918101829052606081018290526080810182905260a081018290529060c082015290565b60008281526020818152604091829020825160e08101845281548152600182015492810192909252600281015492820192909252600380830154606083015260048301546001600160a01b039081166080840152600584015490811660a084015291929160c0840191600160a01b90910460ff169081111561038a5761038a61088a565b600381111561039b5761039b61088a565b90525092915050565b3233146103c35760405162461bcd60e51b81526004016101b090610a4b565b6000805b8281101561056157368484838181106103e2576103e2610a75565b9050608002019050600080600083602001358152602001908152602001600020905060008260600135116104405760405162461bcd60e51b81526020600482015260056024820152640c081d985b60da1b60448201526064016101b0565b81356104825760405162461bcd60e51b815260206004820152601160248201527003020726566756e6454696d657374616d7607c1b60448201526064016101b0565b60006005820154600160a01b900460ff1660038111156104a4576104a461088a565b146104dc5760405162461bcd60e51b8152602060048201526008602482015267064757020737761760c41b60448201526064016101b0565b436002820155813560038201556004810180546001600160a01b0319163317905561050d6060830160408401610a8b565b6005820180546060850135600185018190556001600160a01b03939093166001600160a81b031990911617600160a01b17905561054a9085610aca565b93505050808061055990610ae3565b9150506103c7565b503481146102c45760405162461bcd60e51b8152602060048201526007602482015266189859081d985b60ca1b60448201526064016101b0565b600081815260208190526040812060016005820154600160a01b900460ff1660038111156105cb576105cb61088a565b1480156105dc575080600301544210155b9392505050565b3233146106025760405162461bcd60e51b81526004016101b090610a4b565b6000805b828110156107da573684848381811061062157610621610a75565b6020604091820293909301838101356000908152938490529220919250600190506005820154600160a01b900460ff1660038111156106625761066261088a565b1461069b5760405162461bcd60e51b815260206004820152600960248201526862616420737461746560b81b60448201526064016101b0565b60058101546001600160a01b031633146106e95760405162461bcd60e51b815260206004820152600f60248201526e189859081c185c9d1a58da5c185b9d608a1b60448201526064016101b0565b81602001356002836000013560405160200161070791815260200190565b60408051601f198184030181529082905261072191610afc565b602060405180830381855afa15801561073e573d6000803e3d6000fd5b5050506040513d601f19601f820116820180604052508101906107619190610b2b565b1461079b5760405162461bcd60e51b815260206004820152600a602482015269189859081cd958dc995d60b21b60448201526064016101b0565b60058101805460ff60a01b1916600160a11b1790558135815560018101546107c39085610aca565b9350505080806107d290610ae3565b915050610606565b50604051600090339083908381818185875af1925050503d806000811461081d576040519150601f19603f3d011682016040523d82523d6000602084013e610822565b606091505b509091505060018115151461086b5760405162461bcd60e51b815260206004820152600f60248201526e1d1c985b9cd9995c8819985a5b1959608a1b60448201526064016101b0565b50505050565b60006020828403121561088357600080fd5b5035919050565b634e487b7160e01b600052602160045260246000fd5b600481106108be57634e487b7160e01b600052602160045260246000fd5b9052565b600060e08201905082518252602083015160208301526040830151604083015260608301516060830152608083015160018060a01b0380821660808501528060a08601511660a0850152505060c083015161092060c08401826108a0565b5092915050565b6000806020838503121561093a57600080fd5b823567ffffffffffffffff8082111561095257600080fd5b818501915085601f83011261096657600080fd5b81358181111561097557600080fd5b8660208260071b850101111561098a57600080fd5b60209290920196919550909350505050565b8781526020810187905260408101869052606081018590526001600160a01b038481166080830152831660a082015260e081016109dc60c08301846108a0565b98975050505050505050565b600080602083850312156109fb57600080fd5b823567ffffffffffffffff80821115610a1357600080fd5b818501915085601f830112610a2757600080fd5b813581811115610a3657600080fd5b8660208260061b850101111561098a57600080fd5b60208082526010908201526f39b2b73232b910109e9037b934b3b4b760811b604082015260600190565b634e487b7160e01b600052603260045260246000fd5b600060208284031215610a9d57600080fd5b81356001600160a01b03811681146105dc57600080fd5b634e487b7160e01b600052601160045260246000fd5b80820180821115610add57610add610ab4565b92915050565b600060018201610af557610af5610ab4565b5060010190565b6000825160005b81811015610b1d5760208186018101518583015201610b03565b506000920191825250919050565b600060208284031215610b3d57600080fd5b505191905056fea2646970667358221220d288c9a18362adf67607179f5c8585d0abe014bdb904b6e878451ac0c393a04364736f6c63430008120033", + Bin: "0x608060405234801561001057600080fd5b50610b7a806100206000396000f3fe6080604052600436106100555760003560e01c80637249fbb61461005a57806376467cbd1461007c578063a8793f94146100b2578063d0f761c0146100c5578063eb84e7f2146100f5578063f4fd17f914610171575b600080fd5b34801561006657600080fd5b5061007a610075366004610871565b610191565b005b34801561008857600080fd5b5061009c610097366004610871565b6102c9565b6040516100a991906108c2565b60405180910390f35b61007a6100c0366004610927565b6103a4565b3480156100d157600080fd5b506100e56100e0366004610871565b61059b565b60405190151581526020016100a9565b34801561010157600080fd5b5061015e610110366004610871565b60006020819052908152604090208054600182015460028301546003840154600485015460059095015493949293919290916001600160a01b0391821691811690600160a01b900460ff1687565b6040516100a9979695949392919061099c565b34801561017d57600080fd5b5061007a61018c3660046109e8565b6105e3565b3233146101b95760405162461bcd60e51b81526004016101b090610a4b565b60405180910390fd5b6101c28161059b565b6101ff5760405162461bcd60e51b815260206004820152600e60248201526d6e6f7420726566756e6461626c6560901b60448201526064016101b0565b60008181526020819052604080822060058101805460ff60a01b1916600360a01b1790556004810154600182015492519193926001600160a01b03909116918381818185875af1925050503d8060008114610276576040519150601f19603f3d011682016040523d82523d6000602084013e61027b565b606091505b50909150506001811515146102c45760405162461bcd60e51b815260206004820152600f60248201526e1d1c985b9cd9995c8819985a5b1959608a1b60448201526064016101b0565b505050565b6103066040805160e081018252600080825260208201819052918101829052606081018290526080810182905260a081018290529060c082015290565b60008281526020818152604091829020825160e08101845281548152600182015492810192909252600281015492820192909252600380830154606083015260048301546001600160a01b039081166080840152600584015490811660a084015291929160c0840191600160a01b90910460ff169081111561038a5761038a61088a565b600381111561039b5761039b61088a565b90525092915050565b3233146103c35760405162461bcd60e51b81526004016101b090610a4b565b6000805b8281101561056157368484838181106103e2576103e2610a75565b9050608002019050600080600083602001358152602001908152602001600020905060008260600135116104405760405162461bcd60e51b81526020600482015260056024820152640c081d985b60da1b60448201526064016101b0565b81356104825760405162461bcd60e51b815260206004820152601160248201527003020726566756e6454696d657374616d7607c1b60448201526064016101b0565b60006005820154600160a01b900460ff1660038111156104a4576104a461088a565b146104dc5760405162461bcd60e51b8152602060048201526008602482015267064757020737761760c41b60448201526064016101b0565b436002820155813560038201556004810180546001600160a01b0319163317905561050d6060830160408401610a8b565b6005820180546060850135600185018190556001600160a01b03939093166001600160a81b031990911617600160a01b17905561054a9085610aca565b93505050808061055990610ae3565b9150506103c7565b503481146102c45760405162461bcd60e51b8152602060048201526007602482015266189859081d985b60ca1b60448201526064016101b0565b600081815260208190526040812060016005820154600160a01b900460ff1660038111156105cb576105cb61088a565b1480156105dc575080600301544210155b9392505050565b3233146106025760405162461bcd60e51b81526004016101b090610a4b565b6000805b828110156107da573684848381811061062157610621610a75565b6020604091820293909301838101356000908152938490529220919250600190506005820154600160a01b900460ff1660038111156106625761066261088a565b1461069b5760405162461bcd60e51b815260206004820152600960248201526862616420737461746560b81b60448201526064016101b0565b60058101546001600160a01b031633146106e95760405162461bcd60e51b815260206004820152600f60248201526e189859081c185c9d1a58da5c185b9d608a1b60448201526064016101b0565b81602001356002836000013560405160200161070791815260200190565b60408051601f198184030181529082905261072191610afc565b602060405180830381855afa15801561073e573d6000803e3d6000fd5b5050506040513d601f19601f820116820180604052508101906107619190610b2b565b1461079b5760405162461bcd60e51b815260206004820152600a602482015269189859081cd958dc995d60b21b60448201526064016101b0565b60058101805460ff60a01b1916600160a11b1790558135815560018101546107c39085610aca565b9350505080806107d290610ae3565b915050610606565b50604051600090339083908381818185875af1925050503d806000811461081d576040519150601f19603f3d011682016040523d82523d6000602084013e610822565b606091505b509091505060018115151461086b5760405162461bcd60e51b815260206004820152600f60248201526e1d1c985b9cd9995c8819985a5b1959608a1b60448201526064016101b0565b50505050565b60006020828403121561088357600080fd5b5035919050565b634e487b7160e01b600052602160045260246000fd5b600481106108be57634e487b7160e01b600052602160045260246000fd5b9052565b600060e08201905082518252602083015160208301526040830151604083015260608301516060830152608083015160018060a01b0380821660808501528060a08601511660a0850152505060c083015161092060c08401826108a0565b5092915050565b6000806020838503121561093a57600080fd5b823567ffffffffffffffff8082111561095257600080fd5b818501915085601f83011261096657600080fd5b81358181111561097557600080fd5b8660208260071b850101111561098a57600080fd5b60209290920196919550909350505050565b8781526020810187905260408101869052606081018590526001600160a01b038481166080830152831660a082015260e081016109dc60c08301846108a0565b98975050505050505050565b600080602083850312156109fb57600080fd5b823567ffffffffffffffff80821115610a1357600080fd5b818501915085601f830112610a2757600080fd5b813581811115610a3657600080fd5b8660208260061b850101111561098a57600080fd5b60208082526010908201526f39b2b73232b910109e9037b934b3b4b760811b604082015260600190565b634e487b7160e01b600052603260045260246000fd5b600060208284031215610a9d57600080fd5b81356001600160a01b03811681146105dc57600080fd5b634e487b7160e01b600052601160045260246000fd5b80820180821115610add57610add610ab4565b92915050565b600060018201610af557610af5610ab4565b5060010190565b6000825160005b81811015610b1d5760208186018101518583015201610b03565b506000920191825250919050565b600060208284031215610b3d57600080fd5b505191905056fea2646970667358221220648f19b7cbecc5d8f334599230fe401b87cd978a6b609a47cb0554cb5da562b364736f6c63430008120033", } // ETHSwapABI is the input ABI used to generate the binding from. diff --git a/dex/networks/eth/contracts/v1/contract.bin b/dex/networks/eth/contracts/v1/contract.bin new file mode 100644 index 0000000000000000000000000000000000000000..d0c77667810eab740ae88a3090fd08994d23adf3 GIT binary patch literal 4500 zcma)A4UAM*6@GVqc4udQ<_(=0*qv>8RxsGq3M>O9mAdj*Yz@8{$~<--=(*h=*tq=e z0zsPa-kYBVqwReIT|ib`t(ut7R4G42+jNZyQ4*~+ky3OEjg%&}CbU$OYH2<9zBjWo z%XBMb-rRZj+;h)4_dDM?cfo-6)B+<)C0PT>xTUK!JZMl!0ckNvH$MVC559#mmG+`Q z0=JvdpoU5y3lm7qzNV7ENSu|PCN7eF3pcp;urJd zs3EY~nJ2O0Jh_LG*`@V?w;W4^9aK8uN-r;$UL&MGMnMS_lcg96w=?BY(-q!c63$U# zlvLN@{^%GgwHXcF=L#P$3Ztjw87>^!j&=sW&(#|sQSzHyjyC==trygSs^XhS6i=C4 z(Ns#ttz1I56vbIp)eE+&ruF%45$c(y;a-oejQ;`NjH7)%LOpf3?KNQo5AH3lU$;y- zN`3RSh1qPzG-c|Ur{RanO=+E}FzO-rluSI6M3{PZR_av1XOR^6VHSRMHw5lHqQm7l zEaxw~`F`95>KOtfLjbab%Cpk?#*f{9F;w{HstUtG;Wn?Q_H*ft>FX<24qwz3t&F`2 zk5?++r}n*b;KuhxMwdQv`ltUI`4aq7eYxh@j_}i)uHChJde=+Onh8R^YpU)sN9?h$ zufM-%BkflPdb)P>3@V%Gz$RtRj-K9~Te_7UJzZOcGaGkwi{nHXjc2Z04A(U^m^SY}q=f^laWXxCdCJcj_|gebLOi=S#i+ zol4b$x#v#F^Uj`GxZvEWvuDAsR0WbHY{N%8vlWZ2rM?!HC5b$hyVLq1gpO($)OQb~ z^6s>bAfldFT33PK|IxTN8>7B`+5$5|ec!HXMXhkCcgB$p0fGya+f%>IQD-FEgxFjc zA%;b=EM+B)jF;I2ZOh`9#bZc}q}cju+#Boq?DIRu;c9$3=2;RX8^A7_wLfN4-|IG& z83!8YjEvU=Dc|`ndawjD1r7GcxxuyyGn?VdOGwNjNP|nP)DoV%S@IzDE4UbJRaaR- zWvYp%=6^&}jbzkt1_pwkB~8NcCnm1^5@x6WiK=0U2>%A|Y(1inymoZ`{^pTme_hpl zq*HJ1fX6Chg2~FG?j)=KOr<*CM6y!nhx7p!vOwH^vjw$er4`Uo^9?;ZfW;m+Tnmtwb2?>9dkd@$)8|r+QQ>P1;+K0J$Q^2M6>m|4# z$bgv9XoS}8s&vXdKlwwRsjs3v%PjM#DT5_g{{lG&m_QhZV`+rLgkCE>T9t%nH7C;UGkaqz9aat$CC}P4WVglk08l-q05Fki9 zjMm-9-NZ5_48>Q&Y&i9;V#(s)&4+R^mM1AY6N5){Bn6wY_`t%zAvPBGHpgIp2H!xQ z#bdyDwfG6zG1=|9;p%SJJ@M5rUj2ah1D-7YLHVlR_f((qLM{X`PwiMINBR)f#?i2wOIm!uI0|#)%*?1$$8p zr61sp%l1sZShIq84LqnE%GJ0&!?BxF4{F6Mk>|Q`X5b@WBnUd^~gfc z*4uc|nyl&ujOIwfZ3$_^7piJ=n~xU#2S@aljzH^c>;86h0~9KC6*-n47Iq9gaT3@y zG^h+W7!qB>Q~dCF+t5HK+J@kZm2E@AJKP44SR2bQY%tA9eB1CVK0gKDHek7R%ku=^ zHe9dttb%VVn+;^z$!$h=)i&dksg}1HjVq?6+D7YB{Ae61`B9|W#*;49iopccHl8d} z?c_G2%ui9b4chp6K3}!{Jj{mG{8Or%VMd}@0nHdTMNM}&mkyf=!ZfU`7skwKQQCB` zGeMgYS=xx@3$Z?mtZNZI`BnF-0!jw21$Z5Cm-Awp&3m~>e{ucJ(t7N&5~Zf!u#iTY z+&iS1$E?l6%nX@En||i3G!rIgnx@mv&mlnUWjr}T_{$5-E-2_H8W{?^0tw-N*XmV!w_}k7DFvSaUkYaNien<3T7-cQ z??j-Mu5hQtG zao5t>3Fy>abl#V*50}yM@xTYly{Ch!!7ELNM{-z^=Q-MQ14f2TYw6FS0JFT|fwl6?D*e%#%+t#@GE-P4qN7u|j6E6Kw*=PZ75rn&cn ihu5y#b4Se+&%AQ~iMfh4_h77hU~7NZGAS@kYWp8DyaPA@ literal 0 HcmV?d00001 diff --git a/dex/networks/eth/contracts/v1/contract.go b/dex/networks/eth/contracts/v1/contract.go new file mode 100644 index 0000000000..c2b7449d64 --- /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 + Value *big.Int + Initiator common.Address + RefundTimestamp uint64 + Participant common.Address +} + +// ETHSwapMetaData contains all meta data concerning the ETHSwap contract. +var ETHSwapMetaData = &bind.MetaData{ + ABI: "[{\"inputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"token\",\"type\":\"address\"},{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"secretHash\",\"type\":\"bytes32\"},{\"internalType\":\"uint256\",\"name\":\"value\",\"type\":\"uint256\"},{\"internalType\":\"address\",\"name\":\"initiator\",\"type\":\"address\"},{\"internalType\":\"uint64\",\"name\":\"refundTimestamp\",\"type\":\"uint64\"},{\"internalType\":\"address\",\"name\":\"participant\",\"type\":\"address\"}],\"internalType\":\"structETHSwap.Vector\",\"name\":\"v\",\"type\":\"tuple\"}],\"name\":\"contractKey\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"token\",\"type\":\"address\"},{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"secretHash\",\"type\":\"bytes32\"},{\"internalType\":\"uint256\",\"name\":\"value\",\"type\":\"uint256\"},{\"internalType\":\"address\",\"name\":\"initiator\",\"type\":\"address\"},{\"internalType\":\"uint64\",\"name\":\"refundTimestamp\",\"type\":\"uint64\"},{\"internalType\":\"address\",\"name\":\"participant\",\"type\":\"address\"}],\"internalType\":\"structETHSwap.Vector[]\",\"name\":\"contracts\",\"type\":\"tuple[]\"}],\"name\":\"initiate\",\"outputs\":[],\"stateMutability\":\"payable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"token\",\"type\":\"address\"},{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"secretHash\",\"type\":\"bytes32\"},{\"internalType\":\"uint256\",\"name\":\"value\",\"type\":\"uint256\"},{\"internalType\":\"address\",\"name\":\"initiator\",\"type\":\"address\"},{\"internalType\":\"uint64\",\"name\":\"refundTimestamp\",\"type\":\"uint64\"},{\"internalType\":\"address\",\"name\":\"participant\",\"type\":\"address\"}],\"internalType\":\"structETHSwap.Vector\",\"name\":\"v\",\"type\":\"tuple\"}],\"name\":\"isRedeemable\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"token\",\"type\":\"address\"},{\"components\":[{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"secretHash\",\"type\":\"bytes32\"},{\"internalType\":\"uint256\",\"name\":\"value\",\"type\":\"uint256\"},{\"internalType\":\"address\",\"name\":\"initiator\",\"type\":\"address\"},{\"internalType\":\"uint64\",\"name\":\"refundTimestamp\",\"type\":\"uint64\"},{\"internalType\":\"address\",\"name\":\"participant\",\"type\":\"address\"}],\"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\":[{\"internalType\":\"address\",\"name\":\"token\",\"type\":\"address\"},{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"secretHash\",\"type\":\"bytes32\"},{\"internalType\":\"uint256\",\"name\":\"value\",\"type\":\"uint256\"},{\"internalType\":\"address\",\"name\":\"initiator\",\"type\":\"address\"},{\"internalType\":\"uint64\",\"name\":\"refundTimestamp\",\"type\":\"uint64\"},{\"internalType\":\"address\",\"name\":\"participant\",\"type\":\"address\"}],\"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\":[{\"internalType\":\"address\",\"name\":\"token\",\"type\":\"address\"},{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"secretHash\",\"type\":\"bytes32\"},{\"internalType\":\"uint256\",\"name\":\"value\",\"type\":\"uint256\"},{\"internalType\":\"address\",\"name\":\"initiator\",\"type\":\"address\"},{\"internalType\":\"uint64\",\"name\":\"refundTimestamp\",\"type\":\"uint64\"},{\"internalType\":\"address\",\"name\":\"participant\",\"type\":\"address\"}],\"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: "0x608060405234801561001057600080fd5b50611174806100206000396000f3fe60806040526004361061007b5760003560e01c80639ef07b4c1161004e5780639ef07b4c1461010a578063eb84e7f214610138578063f0e3b8d514610165578063f9f2e0f41461019257600080fd5b806333a3bcb41461008057806352145bc0146100b557806371b6d011146100ca57806377d7e031146100ea575b600080fd5b34801561008c57600080fd5b506100a061009b366004610e18565b6101b2565b60405190151581526020015b60405180910390f35b6100c86100c3366004610e57565b6101ea565b005b3480156100d657600080fd5b506100c86100e5366004610e18565b610597565b3480156100f657600080fd5b506100a0610105366004610edd565b61086d565b34801561011657600080fd5b5061012a610125366004610e18565b6108e7565b6040519081526020016100ac565b34801561014457600080fd5b5061012a610153366004610eff565b60006020819052908152604090205481565b34801561017157600080fd5b50610185610180366004610e18565b6109dd565b6040516100ac9190610f2e565b34801561019e57600080fd5b506100c86101ad366004610f71565b610a9a565b60008060006101c18585610dce565b9250925050806000141580156101df57506101dd82853561086d565b155b925050505b92915050565b3233146102125760405162461bcd60e51b815260040161020990610fe4565b60405180910390fd5b6000805b8281101561043257368484838181106102315761023161100e565b905060a00201905060008160200135116102755760405162461bcd60e51b81526020600482015260056024820152640c081d985b60da1b6044820152606401610209565b60006102876080830160608401611024565b67ffffffffffffffff16116102d25760405162461bcd60e51b815260206004820152601160248201527003020726566756e6454696d657374616d7607c1b6044820152606401610209565b7f5069ec89f08d9ca0424bb5a5f59c3c60ed50cf06af5911a368e41e771763bfaf8135016103535760405162461bcd60e51b815260206004820152602860248201527f696c6c6567616c2073656372657420686173682028726566756e64207265636f604482015267726420686173682960c01b6064820152608401610209565b600061035f87836108e7565b60008181526020819052604090205490915080156103b05760405162461bcd60e51b815260206004820152600e60248201526d73776170206e6f7420656d70747960901b6044820152606401610209565b50436103bd81843561086d565b156103fb5760405162461bcd60e51b815260206004820152600e60248201526d3430b9b41031b7b63634b9b4b7b760911b6044820152606401610209565b60008281526020818152604090912082905561041a9084013586611064565b9450505050808061042a90611077565b915050610216565b506001600160a01b03841661047f5734811461047a5760405162461bcd60e51b8152602060048201526007602482015266189859081d985b60ca1b6044820152606401610209565b610591565b60408051336024820152306044820152606480820184905282518083039091018152608490910182526020810180516001600160e01b03166323b872dd60e01b17905290516000916060916001600160a01b038816916104de91611090565b6000604051808303816000865af19150503d806000811461051b576040519150601f19603f3d011682016040523d82523d6000602084013e610520565b606091505b50909250905081801561054b57508051158061054b57508080602001905181019061054b91906110bf565b61058e5760405162461bcd60e51b81526020600482015260146024820152731d1c985b9cd9995c88199c9bdb4819985a5b195960621b6044820152606401610209565b50505b50505050565b3233146105b65760405162461bcd60e51b815260040161020990610fe4565b6105c66080820160608301611024565b67ffffffffffffffff164210156106165760405162461bcd60e51b81526020600482015260146024820152731b1bd8dadd1a5b59481b9bdd08195e1c1a5c995960621b6044820152606401610209565b60008060006106258585610dce565b92509250925060008111801561063b5750438111155b6106795760405162461bcd60e51b815260206004820152600f60248201526e73776170206e6f742061637469766560881b6044820152606401610209565b61068482853561086d565b156106c95760405162461bcd60e51b81526020600482015260156024820152741cddd85c08185b1c9958591e481c995919595b5959605a1b6044820152606401610209565b600083815260208190526040902060001990556001600160a01b03851661077c5760006106fc60608601604087016110e1565b6001600160a01b0316856020013560405160006040518083038185875af1925050503d806000811461074a576040519150601f19603f3d011682016040523d82523d6000602084013e61074f565b606091505b50909150506001811515146107765760405162461bcd60e51b8152600401610209906110fc565b50610866565b604080513360248201526020868101356044808401919091528351808403909101815260649092018352810180516001600160e01b031663a9059cbb60e01b17905290516000916060916001600160a01b038916916107da91611090565b6000604051808303816000865af19150503d8060008114610817576040519150601f19603f3d011682016040523d82523d6000602084013e61081c565b606091505b50909250905081801561084757508051158061084757508080602001905181019061084791906110bf565b6108635760405162461bcd60e51b8152600401610209906110fc565b50505b5050505050565b60008160028460405160200161088591815260200190565b60408051601f198184030181529082905261089f91611090565b602060405180830381855afa1580156108bc573d6000803e3d6000fd5b5050506040513d601f19601f820116820180604052508101906108df9190611125565b149392505050565b6000600282356108fd60608501604086016110e1565b60601b61091060a08601608087016110e1565b60601b856020013560001b86606001602081019061092e9190611024565b6040805160208101969096526bffffffffffffffffffffffff19948516908601529183166054850152606884015260c01b6001600160c01b0319166088830152606086901b16609082015260a40160408051601f198184030181529082905261099691611090565b602060405180830381855afa1580156109b3573d6000803e3d6000fd5b5050506040513d601f19601f820116820180604052508101906109d69190611125565b9392505050565b604080516060810182526000808252602082018190529181018290529080610a058585610dce565b9250925050610a2f6040805160608101909152806000815260006020820181905260409091015290565b81600003610a56578060005b90816003811115610a4e57610a4e610f18565b9052506101df565b60018301610a6657806003610a3b565b610a7183863561086d565b15610a865760028152602081018390526101df565b600181526040810191909152949350505050565b323314610ab95760405162461bcd60e51b815260040161020990610fe4565b6000805b82811015610c695736848483818110610ad857610ad861100e565b60c002919091019150339050610af460a08301608084016110e1565b6001600160a01b031614610b375760405162461bcd60e51b815260206004820152600a6024820152691b9bdd08185d5d1a195960b21b6044820152606401610209565b60008080610b458985610dce565b925092509250600081118015610b5a57504381105b610b965760405162461bcd60e51b815260206004820152600d60248201526c0756e66696c6c6564207377617609c1b6044820152606401610209565b610ba182853561086d565b15610be15760405162461bcd60e51b815260206004820152601060248201526f185b1c9958591e481c995919595b595960821b6044820152606401610209565b610bf060a0850135853561086d565b610c2d5760405162461bcd60e51b815260206004820152600e60248201526d1a5b9d985b1a59081cd958dc995d60921b6044820152606401610209565b60008381526020818152604090912060a08601359055610c509085013587611064565b9550505050508080610c6190611077565b915050610abd565b506001600160a01b038416610cec57604051600090339083908381818185875af1925050503d8060008114610cba576040519150601f19603f3d011682016040523d82523d6000602084013e610cbf565b606091505b5090915050600181151514610ce65760405162461bcd60e51b8152600401610209906110fc565b50610591565b60408051336024820152604480820184905282518083039091018152606490910182526020810180516001600160e01b031663a9059cbb60e01b17905290516000916060916001600160a01b03881691610d4591611090565b6000604051808303816000865af19150503d8060008114610d82576040519150601f19603f3d011682016040523d82523d6000602084013e610d87565b606091505b509092509050818015610db2575080511580610db2575080806020019051810190610db291906110bf565b61058e5760405162461bcd60e51b8152600401610209906110fc565b600080600080610dde86866108e7565b60008181526020819052604090205490979096508695509350505050565b80356001600160a01b0381168114610e1357600080fd5b919050565b60008082840360c0811215610e2c57600080fd5b610e3584610dfc565b925060a0601f1982011215610e4957600080fd5b506020830190509250929050565b600080600060408486031215610e6c57600080fd5b610e7584610dfc565b9250602084013567ffffffffffffffff80821115610e9257600080fd5b818601915086601f830112610ea657600080fd5b813581811115610eb557600080fd5b87602060a083028501011115610eca57600080fd5b6020830194508093505050509250925092565b60008060408385031215610ef057600080fd5b50508035926020909101359150565b600060208284031215610f1157600080fd5b5035919050565b634e487b7160e01b600052602160045260246000fd5b8151606082019060048110610f5357634e487b7160e01b600052602160045260246000fd5b80835250602083015160208301526040830151604083015292915050565b600080600060408486031215610f8657600080fd5b610f8f84610dfc565b9250602084013567ffffffffffffffff80821115610fac57600080fd5b818601915086601f830112610fc057600080fd5b813581811115610fcf57600080fd5b87602060c083028501011115610eca57600080fd5b60208082526010908201526f39b2b73232b910109e9037b934b3b4b760811b604082015260600190565b634e487b7160e01b600052603260045260246000fd5b60006020828403121561103657600080fd5b813567ffffffffffffffff811681146109d657600080fd5b634e487b7160e01b600052601160045260246000fd5b808201808211156101e4576101e461104e565b6000600182016110895761108961104e565b5060010190565b6000825160005b818110156110b15760208186018101518583015201611097565b506000920191825250919050565b6000602082840312156110d157600080fd5b815180151581146109d657600080fd5b6000602082840312156110f357600080fd5b6109d682610dfc565b6020808252600f908201526e1d1c985b9cd9995c8819985a5b1959608a1b604082015260600190565b60006020828403121561113757600080fd5b505191905056fea26469706673582212202c372294415197f328398f1f817bf94a55587913068eadd138ac30205730931664736f6c63430008120033", +} + +// 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 0x9ef07b4c. +// +// Solidity: function contractKey(address token, (bytes32,uint256,address,uint64,address) v) pure returns(bytes32) +func (_ETHSwap *ETHSwapCaller) ContractKey(opts *bind.CallOpts, token common.Address, v ETHSwapVector) ([32]byte, error) { + var out []interface{} + err := _ETHSwap.contract.Call(opts, &out, "contractKey", token, 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 0x9ef07b4c. +// +// Solidity: function contractKey(address token, (bytes32,uint256,address,uint64,address) v) pure returns(bytes32) +func (_ETHSwap *ETHSwapSession) ContractKey(token common.Address, v ETHSwapVector) ([32]byte, error) { + return _ETHSwap.Contract.ContractKey(&_ETHSwap.CallOpts, token, v) +} + +// ContractKey is a free data retrieval call binding the contract method 0x9ef07b4c. +// +// Solidity: function contractKey(address token, (bytes32,uint256,address,uint64,address) v) pure returns(bytes32) +func (_ETHSwap *ETHSwapCallerSession) ContractKey(token common.Address, v ETHSwapVector) ([32]byte, error) { + return _ETHSwap.Contract.ContractKey(&_ETHSwap.CallOpts, token, v) +} + +// IsRedeemable is a free data retrieval call binding the contract method 0x33a3bcb4. +// +// Solidity: function isRedeemable(address token, (bytes32,uint256,address,uint64,address) v) view returns(bool) +func (_ETHSwap *ETHSwapCaller) IsRedeemable(opts *bind.CallOpts, token common.Address, v ETHSwapVector) (bool, error) { + var out []interface{} + err := _ETHSwap.contract.Call(opts, &out, "isRedeemable", token, 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 0x33a3bcb4. +// +// Solidity: function isRedeemable(address token, (bytes32,uint256,address,uint64,address) v) view returns(bool) +func (_ETHSwap *ETHSwapSession) IsRedeemable(token common.Address, v ETHSwapVector) (bool, error) { + return _ETHSwap.Contract.IsRedeemable(&_ETHSwap.CallOpts, token, v) +} + +// IsRedeemable is a free data retrieval call binding the contract method 0x33a3bcb4. +// +// Solidity: function isRedeemable(address token, (bytes32,uint256,address,uint64,address) v) view returns(bool) +func (_ETHSwap *ETHSwapCallerSession) IsRedeemable(token common.Address, v ETHSwapVector) (bool, error) { + return _ETHSwap.Contract.IsRedeemable(&_ETHSwap.CallOpts, token, 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 0xf0e3b8d5. +// +// Solidity: function status(address token, (bytes32,uint256,address,uint64,address) v) view returns((uint8,bytes32,uint256)) +func (_ETHSwap *ETHSwapCaller) Status(opts *bind.CallOpts, token common.Address, v ETHSwapVector) (ETHSwapStatus, error) { + var out []interface{} + err := _ETHSwap.contract.Call(opts, &out, "status", token, 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 0xf0e3b8d5. +// +// Solidity: function status(address token, (bytes32,uint256,address,uint64,address) v) view returns((uint8,bytes32,uint256)) +func (_ETHSwap *ETHSwapSession) Status(token common.Address, v ETHSwapVector) (ETHSwapStatus, error) { + return _ETHSwap.Contract.Status(&_ETHSwap.CallOpts, token, v) +} + +// Status is a free data retrieval call binding the contract method 0xf0e3b8d5. +// +// Solidity: function status(address token, (bytes32,uint256,address,uint64,address) v) view returns((uint8,bytes32,uint256)) +func (_ETHSwap *ETHSwapCallerSession) Status(token common.Address, v ETHSwapVector) (ETHSwapStatus, error) { + return _ETHSwap.Contract.Status(&_ETHSwap.CallOpts, token, 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 0x52145bc0. +// +// Solidity: function initiate(address token, (bytes32,uint256,address,uint64,address)[] contracts) payable returns() +func (_ETHSwap *ETHSwapTransactor) Initiate(opts *bind.TransactOpts, token common.Address, contracts []ETHSwapVector) (*types.Transaction, error) { + return _ETHSwap.contract.Transact(opts, "initiate", token, contracts) +} + +// Initiate is a paid mutator transaction binding the contract method 0x52145bc0. +// +// Solidity: function initiate(address token, (bytes32,uint256,address,uint64,address)[] contracts) payable returns() +func (_ETHSwap *ETHSwapSession) Initiate(token common.Address, contracts []ETHSwapVector) (*types.Transaction, error) { + return _ETHSwap.Contract.Initiate(&_ETHSwap.TransactOpts, token, contracts) +} + +// Initiate is a paid mutator transaction binding the contract method 0x52145bc0. +// +// Solidity: function initiate(address token, (bytes32,uint256,address,uint64,address)[] contracts) payable returns() +func (_ETHSwap *ETHSwapTransactorSession) Initiate(token common.Address, contracts []ETHSwapVector) (*types.Transaction, error) { + return _ETHSwap.Contract.Initiate(&_ETHSwap.TransactOpts, token, contracts) +} + +// Redeem is a paid mutator transaction binding the contract method 0xf9f2e0f4. +// +// Solidity: function redeem(address token, ((bytes32,uint256,address,uint64,address),bytes32)[] redemptions) returns() +func (_ETHSwap *ETHSwapTransactor) Redeem(opts *bind.TransactOpts, token common.Address, redemptions []ETHSwapRedemption) (*types.Transaction, error) { + return _ETHSwap.contract.Transact(opts, "redeem", token, redemptions) +} + +// Redeem is a paid mutator transaction binding the contract method 0xf9f2e0f4. +// +// Solidity: function redeem(address token, ((bytes32,uint256,address,uint64,address),bytes32)[] redemptions) returns() +func (_ETHSwap *ETHSwapSession) Redeem(token common.Address, redemptions []ETHSwapRedemption) (*types.Transaction, error) { + return _ETHSwap.Contract.Redeem(&_ETHSwap.TransactOpts, token, redemptions) +} + +// Redeem is a paid mutator transaction binding the contract method 0xf9f2e0f4. +// +// Solidity: function redeem(address token, ((bytes32,uint256,address,uint64,address),bytes32)[] redemptions) returns() +func (_ETHSwap *ETHSwapTransactorSession) Redeem(token common.Address, redemptions []ETHSwapRedemption) (*types.Transaction, error) { + return _ETHSwap.Contract.Redeem(&_ETHSwap.TransactOpts, token, redemptions) +} + +// Refund is a paid mutator transaction binding the contract method 0x71b6d011. +// +// Solidity: function refund(address token, (bytes32,uint256,address,uint64,address) v) returns() +func (_ETHSwap *ETHSwapTransactor) Refund(opts *bind.TransactOpts, token common.Address, v ETHSwapVector) (*types.Transaction, error) { + return _ETHSwap.contract.Transact(opts, "refund", token, v) +} + +// Refund is a paid mutator transaction binding the contract method 0x71b6d011. +// +// Solidity: function refund(address token, (bytes32,uint256,address,uint64,address) v) returns() +func (_ETHSwap *ETHSwapSession) Refund(token common.Address, v ETHSwapVector) (*types.Transaction, error) { + return _ETHSwap.Contract.Refund(&_ETHSwap.TransactOpts, token, v) +} + +// Refund is a paid mutator transaction binding the contract method 0x71b6d011. +// +// Solidity: function refund(address token, (bytes32,uint256,address,uint64,address) v) returns() +func (_ETHSwap *ETHSwapTransactorSession) Refund(token common.Address, v ETHSwapVector) (*types.Transaction, error) { + return _ETHSwap.Contract.Refund(&_ETHSwap.TransactOpts, token, v) +} diff --git a/dex/networks/eth/params.go b/dex/networks/eth/params.go index b78bb2d930..d2d0f3ae61 100644 --- a/dex/networks/eth/params.go +++ b/dex/networks/eth/params.go @@ -5,6 +5,7 @@ package eth import ( "encoding/binary" + "encoding/hex" "encoding/json" "errors" "fmt" @@ -15,6 +16,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" ) @@ -65,6 +67,7 @@ var ( VersionedGases = map[uint32]*Gases{ 0: v0Gases, + 1: v1Gases, } ContractAddresses = map[uint32]map[dex.Network]common.Address{ @@ -73,6 +76,11 @@ var ( dex.Testnet: common.HexToAddress("0x73bc803A2604b2c58B8680c3CE1b14489842EF16"), // tx 0xb24b44beebc0e34fa57bd9f08f9aaf70f40c654f3ddbe0b15dd942ee23ce02f4 dex.Simnet: common.HexToAddress("0x2f68e723b8989ba1c6a9f03e42f33cb7dc9d606f"), }, + 1: { + dex.Mainnet: common.HexToAddress("0xa958d5B8a3a29E3f5f41742Fbb939A0dd93EB418"), // tx 0x4adf0314237c454acee1f8d33e97f84126af612245cad0794471693f0906610e + dex.Testnet: common.HexToAddress("0x9CDe3c347021F0AA63E2780dAD867B5949c5E083"), // tx 0x90f18e70121598a48fc49a5d5b0328358eb34441e2c5dee439dda2dfc7bf3dd8 + dex.Simnet: common.HexToAddress("0x2f68e723b8989ba1c6a9f03e42f33cb7dc9d606f"), + }, } MultiBalanceAddresses = map[dex.Network]common.Address{ @@ -89,6 +97,24 @@ var v0Gases = &Gases{ Refund: 57000, // 43,014 actual -- https://goerli.etherscan.io/tx/0x586ed4cb7dab043f98d4cc08930d9eb291b0052d140d949b20232ceb6ad15f25 } +var v1Gases = &Gases{ + // First swap used 48801 gas Recommended Gases.Swap = 63441 + Swap: 63_441, + // 4 additional swaps averaged 26695 gas each. Recommended Gases.SwapAdd = 34703 + // [48801 75511 102209 128895 155582] + SwapAdd: 34_703, + // First redeem used 40032 gas. Recommended Gases.Redeem = 52041 + Redeem: 52_041, + // 4 additional redeems averaged 10950 gas each. recommended Gases.RedeemAdd = 14235 + // [40032 50996 61949 72890 83832] + RedeemAdd: 14_235, + // *** Compare expected Swap + Redeem = 88k with UniSwap v2: 102k, v3: 127k + // *** A 1-match order is cheaper than UniSwap. + // Average of 5 refunds: 40390. Recommended Gases.Refund = 52507 + // [40381 40393 40393 40393 40393] + Refund: 52_507, +} + // LoadGenesisFile loads a Genesis config from a json file. func LoadGenesisFile(genesisFile string) (*core.Genesis, error) { fid, err := os.Open(genesisFile) @@ -105,23 +131,49 @@ 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") +func DecodeContractDataV0(data []byte) (secretHash [32]byte, err error) { + contractVer, secretHashB, err := DecodeContractData(data) + if err != nil { + return secretHash, err + } + if contractVer != 0 { + return secretHash, errors.New("not contract version 0") + } + copy(secretHash[:], secretHashB) + return +} + +// DecodeContractData unpacks the contract version and the locator. +func DecodeContractData(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 } @@ -250,7 +302,46 @@ 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 *big.Int + SecretHash [32]byte + LockTime uint64 // seconds +} + +// 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[:]) + v.Value.FillBytes(locator[40:72]) + copy(locator[72:104], v.SecretHash[:]) + binary.BigEndian.PutUint64(locator[104:112], v.LockTime) + return locator +} + +func (v *SwapVector) String() string { + return fmt.Sprintf("{ from = %s, to = %s, value = %d, secret hash = %s, locktime = %s }", + v.From, v.To, v.Value, hex.EncodeToString(v.SecretHash[:]), time.UnixMilli(int64(v.LockTime))) +} + +func CompareVectors(v1, v2 *SwapVector) bool { + // Check vector equivalence. + return v1.Value.Cmp(v2.Value) == 0 && v1.To == v2.To && v1.From == v2.From && + v1.LockTime == v2.LockTime && v1.SecretHash == v2.SecretHash +} + +// 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 @@ -334,3 +425,70 @@ 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 32 + secretHash 32 + +// lockTime 8 = 112 bytes +const LocatorV1Length = 112 + +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: new(big.Int).SetBytes(locator[40:72]), + LockTime: binary.BigEndian.Uint64(locator[104:112]), + } + copy(v.SecretHash[:], locator[72:104]) + } else { + err = fmt.Errorf("wrong v1 locator length. wanted %d, got %d", LocatorV1Length, len(locator)) + } + return +} + +func SwapVectorToAbigen(v *SwapVector) swapv1.ETHSwapVector { + return swapv1.ETHSwapVector{ + SecretHash: v.SecretHash, + Initiator: v.From, + RefundTimestamp: v.LockTime, + Participant: v.To, + Value: v.Value, + } +} + +// ProtocolVersion assists in mapping the dex.Asset.Version to a contract +// version. +type ProtocolVersion uint32 + +const ( + ProtocolVersionZero ProtocolVersion = iota + ProtocolVersionV1Contracts +) + +func (v ProtocolVersion) ContractVersion() uint32 { + switch v { + case ProtocolVersionZero: + return 0 + case ProtocolVersionV1Contracts: + return 1 + default: + return ContractVersionUnknown + } +} + +var ( + // ContractVersionERC20 is passed as the contract version when calling + // ERC20 contract methods. + ContractVersionERC20 = ^uint32(0) + ContractVersionNewest = ContractVersionERC20 // same thing + ContractVersionUnknown = ContractVersionERC20 - 1 +) diff --git a/dex/networks/eth/params_test.go b/dex/networks/eth/params_test.go index da4b2db902..341610a3c4 100644 --- a/dex/networks/eth/params_test.go +++ b/dex/networks/eth/params_test.go @@ -69,7 +69,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 659cf7024d..3402c54c26 100644 --- a/dex/networks/eth/tokens.go +++ b/dex/networks/eth/tokens.go @@ -64,7 +64,7 @@ type NetToken struct { // SwapContract represents a single swap contract instance. type SwapContract struct { - Address common.Address + Address common.Address // Only used for v0 contracts Gas Gases } @@ -108,6 +108,29 @@ var Tokens = map[uint32]*Token{ Transfer: 85_100, }, }, + 1: { + Gas: Gases{ + // First swap used 98443 gas Recommended Gases.Swap = 127975 + // 1 additional swaps averaged 26491 gas each. Recommended Gases.SwapAdd = 34438 + // [98443 124934] + // First redeem used 54761 gas. Recommended Gases.Redeem = 71189 + // 1 additional redeems averaged 10722 gas each. recommended Gases.RedeemAdd = 13938 + // [54761 65483] + // Average of 2 refunds: 58328. Recommended Gases.Refund = 75826 + // [58289 58367] + // Average of 2 approvals: 55882. Recommended Gases.Approve = 72646 + // [55882 55882] + // Average of 1 transfers: 62224. Recommended Gases.Transfer = 80891 + // [62224] + Swap: 127_975, + SwapAdd: 34_438, + Redeem: 71_189, + RedeemAdd: 13_938, + Refund: 75_826, + Approve: 72_646, + Transfer: 80_891, + }, + }, }, }, dex.Testnet: { @@ -160,6 +183,29 @@ var Tokens = map[uint32]*Token{ Transfer: 85_100, // actual ~65,524 (initial receive, subsequent 48,424) }, }, + 1: { + Gas: Gases{ + // First swap used 98310 gas Recommended Gases.Swap = 127803 + // 2 additional swaps averaged 26507 gas each. Recommended Gases.SwapAdd = 34459 + // [98310 124825 151325] + // First redeem used 54672 gas. Recommended Gases.Redeem = 71073 + // 2 additional redeems averaged 10726 gas each. recommended Gases.RedeemAdd = 13943 + // [54672 65406 76124] + // Average of 3 refunds: 58056. Recommended Gases.Refund = 75472 + // [58187 58278 57705] + // Average of 2 approvals: 55785. Recommended Gases.Approve = 72520 + // [55785 55785] + // Average of 1 transfers: 62135. Recommended Gases.Transfer = 80775 + // [62135] + Swap: 127_803, + SwapAdd: 34_459, + Redeem: 71_073, + RedeemAdd: 13_943, + Refund: 75_472, + Approve: 72_520, + Transfer: 80_775, + }, + }, }, }, dex.Simnet: { @@ -175,7 +221,31 @@ var Tokens = map[uint32]*Token{ Refund: 77_000, Approve: 78_400, Transfer: 85_100, - }}, + }, + }, + 1: { + Gas: Gases{ + // First swap used 88089 gas Recommended Gases.Swap = 114515 + Swap: 114_515, + // 4 additional swaps averaged 26671 gas each. Recommended Gases.SwapAdd = 34672 + // [88089 114763 141438 168100 194775] + SwapAdd: 34_672, + // First redeem used 44825 gas. Recommended Gases.Redeem = 58272 + Redeem: 58_272, + // 4 additional redeems averaged 10929 gas each. recommended Gases.RedeemAdd = 14207 + // [44825 55765 66682 77611 88541] + RedeemAdd: 14_207, + // Average of 5 refunds: 47624. Recommended Gases.Refund = 61911 + // [47624 47624 47624 47624 47624] + Refund: 61_911, + // Average of 2 approvals: 44754. Recommended Gases.Approve = 58180 + // [44754 44754] + Approve: 58_180, + // Average of 1 transfers: 51509. Recommended Gases.Transfer = 66961 + // [51509] + Transfer: 66_961, + }, + }, }, }, }, @@ -227,6 +297,29 @@ var Tokens = map[uint32]*Token{ Transfer: 82_124, }, }, + 1: { + // First swap used 95314 gas Recommended Gases.Swap = 123908 + // 1 additional swaps averaged 26503 gas each. Recommended Gases.SwapAdd = 34453 + // [95314 121817] + // First redeem used 55512 gas. Recommended Gases.Redeem = 72165 + // 1 additional redeems averaged 10710 gas each. recommended Gases.RedeemAdd = 13923 + // [55512 66222] + // Average of 2 refunds: 59072. Recommended Gases.Refund = 76793 + // [59036 59109] + // Average of 2 approvals: 48897. Recommended Gases.Approve = 63566 + // [48897 48897] + // Average of 1 transfers: 63173. Recommended Gases.Transfer = 82124 + // [63173] + Gas: Gases{ + Swap: 123_908, + SwapAdd: 34_453, + Redeem: 72_165, + RedeemAdd: 13_923, + Refund: 76_793, + Approve: 63_566, + Transfer: 82_124, + }, + }, }, }, dex.Testnet: { @@ -258,13 +351,36 @@ var Tokens = map[uint32]*Token{ Transfer: 82_196, }, }, + 1: { + Gas: Gases{ + // First swap used 95350 gas Recommended Gases.Swap = 123955 + // 2 additional swaps averaged 26501 gas each. Recommended Gases.SwapAdd = 34451 + // [95350 121853 148353] + // First redeem used 55567 gas. Recommended Gases.Redeem = 72237 + // 2 additional redeems averaged 10714 gas each. recommended Gases.RedeemAdd = 13928 + // [55567 66289 76995] + // Average of 3 refunds: 58950. Recommended Gases.Refund = 76635 + // [59092 58595 59164] + // Average of 2 approvals: 48930. Recommended Gases.Approve = 63609 + // [48930 48930] + // Average of 1 transfers: 63216. Recommended Gases.Transfer = 82180 + // [63216] + Swap: 123_955, + SwapAdd: 34_451, + Redeem: 72_237, + RedeemAdd: 13_928, + Refund: 76_635, + Approve: 63_609, + Transfer: 82_180, + }, + }, }, }, dex.Simnet: { Address: common.Address{}, SwapContracts: map[uint32]*SwapContract{ 0: { - Address: common.Address{}, + Address: common.Address{}, // Filled in by MaybeReadSimnetAddrs Gas: Gases{ Swap: 242_000, SwapAdd: 146_400, @@ -273,7 +389,8 @@ var Tokens = map[uint32]*Token{ Refund: 77_000, Approve: 78_400, Transfer: 85_100, - }}, + }, + }, }, }, }, @@ -348,7 +465,7 @@ func MaybeReadSimnetAddrs() { func MaybeReadSimnetAddrsDir( dir string, - contractsAddrs map[uint32]map[dex.Network]common.Address, + contractAddrs map[uint32]map[dex.Network]common.Address, multiBalandAddresses map[dex.Network]common.Address, usdcToken *NetToken, usdtToken *NetToken, @@ -368,20 +485,22 @@ func MaybeReadSimnetAddrsDir( return } - ethSwapContractAddrFile := filepath.Join(harnessDir, "eth_swap_contract_address.txt") - testUSDCSwapContractAddrFile := filepath.Join(harnessDir, "usdc_swap_contract_address.txt") + ethSwapContractAddrFileV0 := filepath.Join(harnessDir, "eth_swap_contract_address_v0.txt") + ethSwapContractAddrFileV1 := filepath.Join(harnessDir, "eth_swap_contract_address_v1.txt") + testUSDCSwapContractAddrFileV0 := filepath.Join(harnessDir, "usdc_swap_contract_address_v0.txt") testUSDCContractAddrFile := filepath.Join(harnessDir, "test_usdc_contract_address.txt") - testUSDTSwapContractAddrFile := filepath.Join(harnessDir, "usdt_swap_contract_address.txt") + testUSDTSwapContractAddrFileV0 := filepath.Join(harnessDir, "usdt_swap_contract_address.txt") testUSDTContractAddrFile := filepath.Join(harnessDir, "test_usdt_contract_address.txt") multiBalanceContractAddrFile := filepath.Join(harnessDir, "multibalance_address.txt") - contractsAddrs[0][dex.Simnet] = maybeGetContractAddrFromFile(ethSwapContractAddrFile) + contractAddrs[0][dex.Simnet] = maybeGetContractAddrFromFile(ethSwapContractAddrFileV0) + contractAddrs[1][dex.Simnet] = maybeGetContractAddrFromFile(ethSwapContractAddrFileV1) multiBalandAddresses[dex.Simnet] = maybeGetContractAddrFromFile(multiBalanceContractAddrFile) - usdcToken.SwapContracts[0].Address = maybeGetContractAddrFromFile(testUSDCSwapContractAddrFile) + usdcToken.SwapContracts[0].Address = maybeGetContractAddrFromFile(testUSDCSwapContractAddrFileV0) usdcToken.Address = maybeGetContractAddrFromFile(testUSDCContractAddrFile) - usdtToken.SwapContracts[0].Address = maybeGetContractAddrFromFile(testUSDTSwapContractAddrFile) + usdtToken.SwapContracts[0].Address = maybeGetContractAddrFromFile(testUSDTSwapContractAddrFileV0) usdtToken.Address = maybeGetContractAddrFromFile(testUSDTContractAddrFile) } diff --git a/dex/networks/eth/txdata.go b/dex/networks/eth/txdata.go index 82e9732fb9..1eff822573 100644 --- a/dex/networks/eth/txdata.go +++ b/dex/networks/eth/txdata.go @@ -10,47 +10,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() @@ -58,45 +27,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 @@ -136,13 +91,17 @@ func (t *txDataHandlerV0) parseInitiateData(calldata []byte) (map[[SecretHashSiz return toReturn, nil } -func (t *txDataHandlerV0) parseRedeemData(calldata []byte) (map[[SecretHashSize]byte]*Redemption, error) { +// ParseRedeemDataV0 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 @@ -178,15 +137,18 @@ func (t *txDataHandlerV0) parseRedeemData(calldata []byte) (map[[SecretHashSize] return toReturn, nil } -func (t *txDataHandlerV0) parseRefundData(calldata []byte) ([32]byte, error) { +// ParseRefundDataV0 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 @@ -204,3 +166,158 @@ func (t *txDataHandlerV0) parseRefundData(calldata []byte) ([32]byte, error) { return secretHash, nil } + +type RedemptionV1 struct { + Secret [32]byte + Contract *SwapVector +} + +func ParseInitiateDataV1(calldata []byte) (common.Address, map[[SecretHashSize]byte]*SwapVector, error) { + decoded, err := ParseCallData(calldata, ABIs[1]) + if err != nil { + return common.Address{}, nil, fmt.Errorf("unable to parse call data: %v", err) + } + if decoded.Name != InitiateMethodName { + return common.Address{}, 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 = 2 + if len(args) != numArgs { + return common.Address{}, nil, fmt.Errorf("expected %v input args but got %v", numArgs, len(args)) + } + + tokenAddr, ok := args[0].value.(common.Address) + if !ok { + return common.Address{}, nil, fmt.Errorf("expected first init arg to be an address but was %T", args[0].value) + } + + initiations, ok := args[1].value.([]struct { + SecretHash [32]byte `json:"secretHash"` + Value *big.Int `json:"value"` + Initiator common.Address `json:"initiator"` + RefundTimestamp uint64 `json:"refundTimestamp"` + Participant common.Address `json:"participant"` + }) + if !ok { + return common.Address{}, nil, fmt.Errorf("expected second 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 + // swapv1.ETHSwapVector 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 tokenAddr, toReturn, nil +} + +func ParseRedeemDataV1(calldata []byte) (common.Address, map[[SecretHashSize]byte]*RedemptionV1, error) { + decoded, err := ParseCallData(calldata, ABIs[1]) + if err != nil { + return common.Address{}, nil, fmt.Errorf("unable to parse call data: %v", err) + } + if decoded.Name != RedeemMethodName { + return common.Address{}, 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 = 2 + if len(args) != numArgs { + return common.Address{}, nil, fmt.Errorf("expected %v redeem args but got %v", numArgs, len(args)) + } + + tokenAddr, ok := args[0].value.(common.Address) + if !ok { + return common.Address{}, nil, fmt.Errorf("expected first redeem arg to be an address but was %T", args[0].value) + } + + redemptions, ok := args[1].value.([]struct { + V struct { + SecretHash [32]uint8 `json:"secretHash"` + Value *big.Int `json:"value"` + Initiator common.Address `json:"initiator"` + RefundTimestamp uint64 `json:"refundTimestamp"` + Participant common.Address `json:"participant"` + } `json:"v"` + Secret [32]uint8 `json:"secret"` + }) + if !ok { + return common.Address{}, nil, fmt.Errorf("expected second 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 + // swapv1.ETHSwapVector are the same, other than the tags. + if len(redemptions) > 0 { + _ = 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 tokenAddr, 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"` + Value *big.Int `json:"value"` + Initiator common.Address `json:"initiator"` + RefundTimestamp uint64 `json:"refundTimestamp"` + Participant common.Address `json:"participant"` + }) + 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 95af80496a..a46ae89c41 100644 --- a/dex/networks/eth/txdata_test.go +++ b/dex/networks/eth/txdata_test.go @@ -124,7 +124,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) @@ -217,7 +217,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) @@ -282,7 +282,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/networks/polygon/params.go b/dex/networks/polygon/params.go index d866530d2f..bba3fb7013 100644 --- a/dex/networks/polygon/params.go +++ b/dex/networks/polygon/params.go @@ -49,8 +49,27 @@ var ( Refund: 55_000, } + v1Gases = &dexeth.Gases{ + // First swap used 48801 gas Recommended Gases.Swap = 63441 + Swap: 63_441, + // 4 additional swaps averaged 26695 gas each. Recommended Gases.SwapAdd = 34703 + // [48801 75511 102209 128895 155582] + SwapAdd: 34_703, + // First redeem used 40032 gas. Recommended Gases.Redeem = 52041 + Redeem: 52_041, + // 4 additional redeems averaged 10950 gas each. recommended Gases.RedeemAdd = 14235 + // [40032 50996 61949 72890 83832] + RedeemAdd: 14_235, + // *** Compare expected Swap + Redeem = 88k with UniSwap v2: 102k, v3: 127k + // *** A 1-match order is cheaper than UniSwap. + // Average of 5 refunds: 40390. Recommended Gases.Refund = 52507 + // [40381 40393 40393 40393 40393] + Refund: 52_507, + } + VersionedGases = map[uint32]*dexeth.Gases{ 0: v0Gases, + 1: v1Gases, } ContractAddresses = map[uint32]map[dex.Network]common.Address{ @@ -59,6 +78,11 @@ var ( dex.Testnet: common.HexToAddress("0x73bc803A2604b2c58B8680c3CE1b14489842EF16"), // txid: 0x88f656a8e432fdd50f33e67bdc39a66d24f663e33792bfab16b033dd2c609a99 dex.Simnet: common.HexToAddress(""), // Filled in by MaybeReadSimnetAddrs }, + 1: { + dex.Mainnet: common.HexToAddress("0xcb9B5AD64FD3fc20215f744293d95887c888B8a5"), // txid: 0x35e5318f3b91b9890a59b0907c6fe9603cc46651111ee18e4df142c7a39cdc10 + dex.Testnet: common.HexToAddress("0xFbF60393F5AB800139F283cc6e090a17db6cC7a1"), // txid: 0xab730f7c64f4af013a590e0c9521a9caa29f549462de842c67c7c9c6c08f8c3e + dex.Simnet: common.HexToAddress(""), // Filled in by MaybeReadSimnetAddrs + }, } MultiBalanceAddresses = map[dex.Network]common.Address{ @@ -128,6 +152,29 @@ var ( Transfer: 85_150, }, }, + 1: { + Gas: dexeth.Gases{ + // First swap used 98322 gas Recommended Gases.Swap = 127818 + // 1 additional swaps averaged 26503 gas each. Recommended Gases.SwapAdd = 34453 + // [98322 124825] + // First redeem used 54684 gas. Recommended Gases.Redeem = 71089 + // 1 additional redeems averaged 10722 gas each. recommended Gases.RedeemAdd = 13938 + // [54684 65406] + // Average of 2 refunds: 60205. Recommended Gases.Refund = 78266 + // [60205 60205] + // Average of 2 approvals: 55785. Recommended Gases.Approve = 72520 + // [55785 55785] + // Average of 1 transfers: 62135. Recommended Gases.Transfer = 80775 + // [62135] + Swap: 127_818, + SwapAdd: 34_453, + Redeem: 71_089, + RedeemAdd: 13_938, + Refund: 78_266, + Approve: 72_520, + Transfer: 80_775, + }, + }, }, }, dex.Testnet: { @@ -159,6 +206,29 @@ var ( Transfer: 82_816, }, }, + 1: { + Gas: dexeth.Gases{ + // First swap used 98322 gas Recommended Gases.Swap = 127818 + // 2 additional swaps averaged 26495 gas each. Recommended Gases.SwapAdd = 34443 + // [98322 124825 151313] + // First redeem used 54684 gas. Recommended Gases.Redeem = 71089 + // 2 additional redeems averaged 10708 gas each. recommended Gases.RedeemAdd = 13920 + // [54684 65406 76100] + // Average of 3 refunds: 57705. Recommended Gases.Refund = 75016 + // [57705 57705 57705] + // Average of 2 approvals: 55785. Recommended Gases.Approve = 72520 + // [55785 55785] + // Average of 1 transfers: 62135. Recommended Gases.Transfer = 80775 + // [62135] + Swap: 127_818, + SwapAdd: 34_443, + Redeem: 71_089, + RedeemAdd: 13_920, + Refund: 75_016, + Approve: 72_520, + Transfer: 80_775, + }, + }, }, }, dex.Simnet: { @@ -176,6 +246,17 @@ var ( Transfer: 64_539, }, }, + 1: { + Gas: dexeth.Gases{ + Swap: 114_515, + SwapAdd: 34_672, + Redeem: 58_272, + RedeemAdd: 14_207, + Refund: 61_911, + Approve: 58_180, + Transfer: 66_961, + }, + }, }, }, }, @@ -225,6 +306,29 @@ var ( Transfer: 74_451, }, }, + 1: { + Gas: dexeth.Gases{ + // First swap used 95187 gas Recommended Gases.Swap = 123743 + // 1 additional swaps averaged 26503 gas each. Recommended Gases.SwapAdd = 34453 + // [95187 121690] + // First redeem used 49819 gas. Recommended Gases.Redeem = 64764 + // 1 additional redeems averaged 10722 gas each. recommended Gases.RedeemAdd = 13938 + // [49819 60541] + // Average of 2 refunds: 62502. Recommended Gases.Refund = 81252 + // [62502 62502] + // Average of 2 approvals: 52072. Recommended Gases.Approve = 67693 + // [52072 52072] + // Average of 1 transfers: 57270. Recommended Gases.Transfer = 74451 + // [57270] + Swap: 123_743, + SwapAdd: 34_453, + Redeem: 72_237, // using eth testnet value which is higher + RedeemAdd: 13_928, + Refund: 81_252, + Approve: 67_693, + Transfer: 82_180, // using eth testnet value which is higher + }, + }, }, }, dex.Simnet: { @@ -302,6 +406,29 @@ var ( Transfer: 67_483, }, }, + 1: { + Gas: dexeth.Gases{ + // First swap used 89867 gas Recommended Gases.Swap = 116827 + // 1 additional swaps averaged 26527 gas each. Recommended Gases.SwapAdd = 34485 + // [89867 116394] + // First redeem used 44483 gas. Recommended Gases.Redeem = 57827 + // 1 additional redeems averaged 10746 gas each. recommended Gases.RedeemAdd = 13969 + // [44483 55229] + // Average of 2 refunds: 49766. Recommended Gases.Refund = 64695 + // [49766 49766] + // Average of 2 approvals: 46712. Recommended Gases.Approve = 60725 + // [46712 46712] + // Average of 1 transfers: 51910. Recommended Gases.Transfer = 67483 + // [51910] + Swap: 116_827, + SwapAdd: 34_485, + Redeem: 57_827, + RedeemAdd: 13_969, + Refund: 64_695, + Approve: 60_725, + Transfer: 67_483, + }, + }, }, }, }, @@ -365,6 +492,29 @@ var ( Transfer: 74_451, }, }, + 1: { + Gas: dexeth.Gases{ + // First swap used 95187 gas Recommended Gases.Swap = 123743 + // 1 additional swaps averaged 26503 gas each. Recommended Gases.SwapAdd = 34453 + // [95187 121690] + // First redeem used 49819 gas. Recommended Gases.Redeem = 64764 + // 1 additional redeems averaged 10722 gas each. recommended Gases.RedeemAdd = 13938 + // [49819 60541] + // Average of 2 refunds: 62502. Recommended Gases.Refund = 81252 + // [62502 62502] + // Average of 2 approvals: 52072. Recommended Gases.Approve = 67693 + // [52072 52072] + // Average of 1 transfers: 57270. Recommended Gases.Transfer = 74451 + // [57270] + Swap: 123_743, + SwapAdd: 34_453, + Redeem: 64_764, + RedeemAdd: 13_938, + Refund: 81_252, + Approve: 67_693, + Transfer: 74_451, + }, + }, }, }, }, diff --git a/dex/testing/dcrdex/harness.sh b/dex/testing/dcrdex/harness.sh index 25c5a8e0e6..4c44463288 100755 --- a/dex/testing/dcrdex/harness.sh +++ b/dex/testing/dcrdex/harness.sh @@ -125,6 +125,15 @@ tmux kill-session -t $SESSION EOF chmod +x "${DCRDEX_DATA_DIR}/quit" +cat > "${DCRDEX_DATA_DIR}/evm-protocol-overrides.json" < "${DCRDEX_DATA_DIR}/run" < "${NODES_ROOT}/eth_swap_contract_address.txt" < "${NODES_ROOT}/eth_swap_contract_address_v0.txt" < "${NODES_ROOT}/eth_swap_contract_address_v1.txt" < "${NODES_ROOT}/test_usdc_contract_address.txt" < "${NODES_ROOT}/harness-ctl/sendUSDT" < "${NODES_ROOT}/usdc_swap_contract_address.txt" < "${NODES_ROOT}/usdc_swap_contract_address_v0.txt" < "${NODES_ROOT}/eth_swap_contract_address.txt" < "${NODES_ROOT}/eth_swap_contract_address_v0.txt" < "${NODES_ROOT}/eth_swap_contract_address_v1.txt" < "${HARNESS_DIR}/sendUSDT" < "${NODES_ROOT}/usdc_swap_contract_address.txt" < "${NODES_ROOT}/usdc_swap_contract_address_v0.txt" < 0 { + var symbolVers map[string]uint32 + if err := json.Unmarshal(b, &symbolVers); err != nil { + panic(fmt.Sprintf("provided protocol override file at %q did not parse: %v", protocolVersionsFilePath, err)) + } + for symbol, v := range symbolVers { + assetID, found := dex.BipSymbolID(symbol) + if !found { + panic(fmt.Sprintf("asset %s specified in protocol override file is not known", symbol)) + } + protocolVersionsOverrides[assetID] = dexeth.ProtocolVersion(v) + } + } + asset.Register(BipID, &Driver{ DriverBase: DriverBase{ - Ver: version, - UI: dexeth.UnitInfo, - Nam: "Ethereum", + ProtocolVersion: ProtocolVersion(BipID), + UI: dexeth.UnitInfo, + Nam: "Ethereum", }, }) - registerToken(usdcID, 0) - registerToken(usdtID, 0) - registerToken(maticID, 0) + registerToken(usdcID, ProtocolVersion(usdcID)) + registerToken(usdtID, ProtocolVersion(usdtID)) + registerToken(maticID, ProtocolVersion(maticID)) } -const ( - BipID = 60 - ethContractVersion = 0 - version = 0 -) +// ProtocolVersion returns the default protocol version unless a reversion is +// specified in the file at protocolVersionsFilePath. +func ProtocolVersion(assetID uint32) dexeth.ProtocolVersion { + v, found := protocolVersionsOverrides[assetID] + if found { + return v + } + return defaultProtocolVersion +} -var ( - _ asset.Driver = (*Driver)(nil) - _ asset.TokenBacker = (*ETHBackend)(nil) +type VersionedToken struct { + *dexeth.Token + ContractVersion uint32 +} - backendInfo = &asset.BackendInfo{ - SupportsDynamicTxFee: true, - } +var ( + registeredTokens = make(map[uint32]*VersionedToken) usdcID, _ = dex.BipSymbolID("usdc.eth") usdtID, _ = dex.BipSymbolID("usdt.eth") maticID, _ = dex.BipSymbolID("matic.eth") ) +func registerToken(assetID uint32, protocolVer dexeth.ProtocolVersion) { + token, exists := dexeth.Tokens[assetID] + if !exists { + panic(fmt.Sprintf("no token constructor for asset ID %d", assetID)) + } + drv := &TokenDriver{ + DriverBase: DriverBase{ + ProtocolVersion: protocolVer, + UI: token.UnitInfo, + Nam: token.Name, + }, + Token: token.Token, + } + asset.RegisterToken(assetID, drv) + registeredTokens[assetID] = &VersionedToken{ + Token: token, + ContractVersion: protocolVer.ContractVersion(), + } +} + func networkToken(vToken *VersionedToken, net dex.Network) (netToken *dexeth.NetToken, contract *dexeth.SwapContract, err error) { netToken, found := vToken.NetTokens[net] if !found { return nil, nil, fmt.Errorf("no addresses for %s on %s", vToken.Name, net) } - contract, found = netToken.SwapContracts[vToken.Ver] + + contract, found = netToken.SwapContracts[vToken.ContractVersion] if !found || contract.Address == (common.Address{}) { - return nil, nil, fmt.Errorf("no version %d address for %s on %s", vToken.Ver, vToken.Name, net) + return nil, nil, fmt.Errorf("no version %d address for %s on %s", vToken.ContractVersion, vToken.Name, net) } return } type DriverBase struct { - UI dex.UnitInfo - Ver uint32 - Nam string + UI dex.UnitInfo + ProtocolVersion dexeth.ProtocolVersion + Nam string } // Version returns the Backend implementation's version number. func (d *DriverBase) Version() uint32 { - return d.Ver + return uint32(d.ProtocolVersion) } // DecodeCoinID creates a human-readable representation of a coin ID for @@ -148,6 +186,18 @@ func (d *Driver) Setup(cfg *asset.BackendConfig) (asset.Backend, error) { chainID = 42 } + for _, tkn := range registeredTokens { + netToken, found := tkn.NetTokens[cfg.Net] + if !found { + return nil, fmt.Errorf("no %s token for %s", tkn.Name, cfg.Net) + } + if _, found = netToken.SwapContracts[tkn.ContractVersion]; !found { + return nil, fmt.Errorf("no version %d swap contract adddress for %s on %s. "+ + "Do you need a version override in evm-protocol-overrides.json?", + tkn.ContractVersion, tkn.Name, cfg.Net) + } + } + return NewEVMBackend(cfg, chainID, dexeth.ContractAddresses, registeredTokens) } @@ -175,9 +225,12 @@ type ethFetcher interface { connect(ctx context.Context) error suggestGasTipCap(ctx context.Context) (*big.Int, error) transaction(ctx context.Context, hash common.Hash) (tx *types.Transaction, isMempool bool, err error) + transactionReceipt(ctx context.Context, txHash common.Hash) (*types.Receipt, error) // token- and asset-specific methods loadToken(ctx context.Context, assetID uint32, vToken *VersionedToken) error - swap(ctx context.Context, assetID uint32, secretHash [32]byte) (*dexeth.SwapState, error) + status(ctx context.Context, assetID uint32, token common.Address, locator []byte) (*dexeth.SwapStatus, error) + vector(ctx context.Context, assetID uint32, locator []byte) (*dexeth.SwapVector, error) + statusAndVector(ctx context.Context, assetID uint32, locator []byte) (*dexeth.SwapStatus, *dexeth.SwapVector, error) accountBalance(ctx context.Context, assetID uint32, addr common.Address) (*big.Int, error) } @@ -211,9 +264,10 @@ type baseBackend struct { // DEX-related blockchain info. type AssetBackend struct { *baseBackend - assetID uint32 - log dex.Logger - atomize func(*big.Int) uint64 // atomize takes floor. use for values, not fee rates + assetID uint32 + tokenAddr common.Address + log dex.Logger + atomize func(*big.Int) uint64 // atomize takes floor. use for values, not fee rates // The backend provides block notification channels through the BlockChannel // method. @@ -224,7 +278,9 @@ type AssetBackend struct { initTxSize uint64 redeemSize uint64 - contractAddr common.Address + contractAddr common.Address // could be v0 or v1 + contractAddrV1 common.Address // required regardless + contractVer uint32 } // ETHBackend implements some Ethereum-specific methods. @@ -248,11 +304,7 @@ var _ asset.AccountBalancer = (*ETHBackend)(nil) // unconnectedETH returns a Backend without a node. The node should be set // before use. -func unconnectedETH(bipID uint32, contractAddr common.Address, vTokens map[uint32]*VersionedToken, logger dex.Logger, net dex.Network) (*ETHBackend, error) { - // TODO: At some point multiple contracts will need to be used, at - // least for transitory periods when updating the contract, and - // possibly a random contract setup, and so this section will need to - // change to support multiple contracts. +func unconnectedETH(bipID, contractVer uint32, contractAddr, contractAddrV1 common.Address, vTokens map[uint32]*VersionedToken, logger dex.Logger, net dex.Network) (*ETHBackend, error) { return ÐBackend{&AssetBackend{ baseBackend: &baseBackend{ net: net, @@ -262,13 +314,15 @@ func unconnectedETH(bipID uint32, contractAddr common.Address, vTokens map[uint3 baseChainName: strings.ToUpper(dex.BipIDSymbol(bipID)), versionedTokens: vTokens, }, - log: logger, - contractAddr: contractAddr, - blockChans: make(map[chan *asset.BlockUpdate]struct{}), - initTxSize: dexeth.InitGas(1, ethContractVersion), - redeemSize: dexeth.RedeemGas(1, ethContractVersion), - assetID: bipID, - atomize: dexeth.WeiToGwei, + log: logger, + contractAddr: contractAddr, + contractAddrV1: contractAddrV1, + blockChans: make(map[chan *asset.BlockUpdate]struct{}), + initTxSize: dexeth.InitGas(1, contractVer), + redeemSize: dexeth.RedeemGas(1, contractVer), + assetID: bipID, + atomize: dexeth.WeiToGwei, + contractVer: contractVer, }}, nil } @@ -350,22 +404,34 @@ func NewEVMBackend( baseChainID, net, log := cfg.AssetID, cfg.Net, cfg.Logger assetName := strings.ToUpper(dex.BipIDSymbol(baseChainID)) + contractVer := ProtocolVersion(baseChainID).ContractVersion() - netAddrs, found := contractAddrs[ethContractVersion] + netAddrs, found := contractAddrs[contractVer] if !found { - return nil, fmt.Errorf("no contract address for %s version %d", assetName, ethContractVersion) + return nil, fmt.Errorf("no contract address for %s version %d", assetName, contractVer) } contractAddr, found := netAddrs[net] if !found { - return nil, fmt.Errorf("no contract address for %s version %d on %s", assetName, ethContractVersion, net) + return nil, fmt.Errorf("no contract address for %s version %d on %s", assetName, contractVer, net) + } + + // v1 contract is required even if the base chain is using v0 for some + // reason, because tokens might use v1. + netAddrsV1, found := contractAddrs[1] + if !found { + return nil, fmt.Errorf("no v1 contract address for %s", assetName) + } + contractAddrV1, found := netAddrsV1[net] + if !found { + return nil, fmt.Errorf("no v1 contract address for %s on %s", assetName, net) } - eth, err := unconnectedETH(baseChainID, contractAddr, vTokens, log, net) + eth, err := unconnectedETH(baseChainID, contractVer, contractAddr, contractAddrV1, vTokens, log, net) if err != nil { return nil, err } - eth.node = newRPCClient(baseChainID, chainID, net, endpoints, contractAddr, log.SubLogger("RPC")) + eth.node = newRPCClient(baseChainID, chainID, net, endpoints, contractVer, contractAddr, contractAddrV1, log.SubLogger("RPC")) return eth, nil } @@ -430,7 +496,7 @@ func (eth *ETHBackend) TokenBackend(assetID uint32, configPath string) (asset.Ba return nil, fmt.Errorf("no token for asset ID %d", assetID) } - _, swapContract, err := networkToken(vToken, eth.net) + netToken, swapContract, err := networkToken(vToken, eth.net) if err != nil { return nil, err } @@ -455,6 +521,7 @@ func (eth *ETHBackend) TokenBackend(assetID uint32, configPath string) (asset.Ba be := &TokenBackend{ AssetBackend: &AssetBackend{ baseBackend: eth.baseBackend, + tokenAddr: netToken.Address, log: eth.baseLogger.SubLogger(strings.ToUpper(dex.BipIDSymbol(assetID))), assetID: assetID, blockChans: make(map[chan *asset.BlockUpdate]struct{}), @@ -582,21 +649,17 @@ func (be *AssetBackend) sendBlockUpdate(u *asset.BlockUpdate) { // ValidateContract ensures that contractData encodes both the expected contract // version and a secret hash. func (eth *ETHBackend) ValidateContract(contractData []byte) error { - ver, _, err := dexeth.DecodeContractData(contractData) + _, _, err := dexeth.DecodeContractData(contractData) if err != nil { // ensures secretHash is proper length return err } - - if ver != version { - return fmt.Errorf("incorrect swap contract version %d, wanted %d", ver, version) - } return nil } // ValidateContract ensures that contractData encodes both the expected swap // contract version and a secret hash. func (eth *TokenBackend) ValidateContract(contractData []byte) error { - ver, _, err := dexeth.DecodeContractData(contractData) + contractVer, _, err := dexeth.DecodeContractData(contractData) if err != nil { // ensures secretHash is proper length return err } @@ -604,8 +667,8 @@ func (eth *TokenBackend) ValidateContract(contractData []byte) error { if err != nil { return fmt.Errorf("error locating token: %v", err) } - if ver != eth.VersionedToken.Ver { - return fmt.Errorf("incorrect token swap contract version %d, wanted %d", ver, version) + if contractVer != eth.VersionedToken.ContractVersion { + return fmt.Errorf("incorrect token swap contract version %d, wanted %d", contractVer, eth.VersionedToken.ContractVersion) } return nil @@ -630,20 +693,37 @@ func (be *AssetBackend) Contract(coinID, contractData []byte) (*asset.Contract, } return &asset.Contract{ Coin: sc, - SwapAddress: sc.init.Participant.String(), + SwapAddress: sc.vector.To.String(), ContractData: contractData, - SecretHash: sc.secretHash[:], + SecretHash: sc.vector.SecretHash[:], TxData: sc.serializedTx, - LockTime: sc.init.LockTime, + LockTime: time.Unix(int64(sc.vector.LockTime), 0), }, nil } // ValidateSecret checks that the secret satisfies the secret hash. -func (eth *baseBackend) ValidateSecret(secret, contractData []byte) bool { - _, secretHash, err := dexeth.DecodeContractData(contractData) +func (eth *AssetBackend) ValidateSecret(secret, contractData []byte) bool { + contractVer, locator, err := dexeth.DecodeContractData(contractData) if err != nil { + eth.log.Errorf("Error decoding contract data for validation: %v", err) + return false + } + var secretHash [32]byte + switch contractVer { + case 0: + copy(secretHash[:], locator) + case 1: + v, err := dexeth.ParseV1Locator(locator) + if err != nil { + eth.log.Errorf("ValidateSecret locator parsing error: %v", err) + return false + } + secretHash = v.SecretHash + default: + eth.log.Errorf("ValidateSecret received unknown contract version: %d", contractVer) return false } + sh := sha256.Sum256(secret) return bytes.Equal(sh[:], secretHash[:]) } diff --git a/server/asset/eth/eth_test.go b/server/asset/eth/eth_test.go index 7f29349c24..18c186647e 100644 --- a/server/asset/eth/eth_test.go +++ b/server/asset/eth/eth_test.go @@ -8,7 +8,6 @@ import ( "bytes" "context" "crypto/sha256" - "encoding/binary" "encoding/hex" "errors" "math/big" @@ -31,42 +30,93 @@ import ( const initLocktime = 1632112916 var ( - _ ethFetcher = (*testNode)(nil) - tLogger = dex.StdOutLogger("ETHTEST", dex.LevelTrace) - tCtx context.Context - initCalldata = mustParseHex("a8793f94000000000000000000000000000" + - "0000000000000000000000000000000000020000000000000000000000000000000000" + - "0000000000000000000000000000002000000000000000000000000000000000000000" + - "00000000000000000614811148b3e4acc53b664f9cf6fcac0adcd328e95d62ba1f4379" + - "650ae3e1460a0f9d1a1000000000000000000000000345853e21b1d475582e71cc2691" + - "24ed5e2dd342200000000000000000000000000000000000000000000000022b1c8c12" + - "27a0000000000000000000000000000000000000000000000000000000000006148111" + - "4ebdc4c31b88d0c8f4d644591a8e00e92b607f920ad8050deb7c7469767d9c56100000" + - "0000000000000000000345853e21b1d475582e71cc269124ed5e2dd342200000000000" + - "000000000000000000000000000000000000022b1c8c1227a0000") + _ ethFetcher = (*testNode)(nil) + tLogger = dex.StdOutLogger("ETHTEST", dex.LevelTrace) + tCtx context.Context + + initiatorAddr = common.HexToAddress("0x2b84C791b79Ee37De042AD2ffF1A253c3ce9bc27") + participantAddr = common.HexToAddress("345853e21b1d475582E71cC269124eD5e2dD3422") + secretA = mustArray32("557cf82e5e72e8ba23963a5e2832767a7e2a3e0a58ac00d319605f4b34b46de2") + secretHashA = mustArray32("09439d8fdc46a777590a5345704042c2774061d5322c6a94352c98a6f6a3630a") + secretB = mustArray32("87eac09638c0c38b4e735b79f053cb869167ee770640ac5df5c4ab030813122a") + secretHashB = mustArray32("ebdc4c31b88d0c8f4d644591a8e00e92b607f920ad8050deb7c7469767d9c561") + initValue uint64 = 2_500_000_000 + swapVectorA = &dexeth.SwapVector{ + From: initiatorAddr, + To: participantAddr, + Value: dexeth.GweiToWei(initValue), + SecretHash: secretHashA, + LockTime: initLocktime, + } + locatorA = swapVectorA.Locator() + swapVectorB = &dexeth.SwapVector{ + From: initiatorAddr, + To: participantAddr, + Value: dexeth.GweiToWei(initValue), + SecretHash: secretHashB, + LockTime: initLocktime, + } + locatorB = swapVectorB.Locator() + + initCalldataV0 = mustParseHex("a8793f94000000000000000000000000000000000000" + + "0000000000000000000000000020000000000000000000000000000000000000000000" + + "0000000000000000000002000000000000000000000000000000000000000000000000" + + "000000006148111409439d8fdc46a777590a5345704042c2774061d5322c6a94352c98" + + "a6f6a3630a000000000000000000000000345853e21b1d475582e71cc269124ed5e2dd" + + "342200000000000000000000000000000000000000000000000022b1c8c1227a000000" + + "00000000000000000000000000000000000000000000000000000061481114ebdc4c31" + + "b88d0c8f4d644591a8e00e92b607f920ad8050deb7c7469767d9c56100000000000000" + + "0000000000345853e21b1d475582e71cc269124ed5e2dd342200000000000000000000" + + "000000000000000000000000000022b1c8c1227a0000") /* initCallData parses to: [ETHSwapInitiation { RefundTimestamp: 1632112916 SecretHash: 8b3e4acc53b664f9cf6fcac0adcd328e95d62ba1f4379650ae3e1460a0f9d1a1 - Value: 5e9 gwei + Value: 2.5e9 gwei Participant: 0x345853e21b1d475582e71cc269124ed5e2dd3422 }, ETHSwapInitiation { RefundTimestamp: 1632112916 SecretHash: ebdc4c31b88d0c8f4d644591a8e00e92b607f920ad8050deb7c7469767d9c561 - Value: 5e9 gwei + Value: 2.5e9 gwei Participant: 0x345853e21b1d475582e71cc269124ed5e2dd3422 }] */ - initSecretHashA = mustParseHex("8b3e4acc53b664f9cf6fcac0adcd328e95d62ba1f4379650ae3e1460a0f9d1a1") - initSecretHashB = mustParseHex("ebdc4c31b88d0c8f4d644591a8e00e92b607f920ad8050deb7c7469767d9c561") - initParticipantAddr = common.HexToAddress("345853e21b1d475582E71cC269124eD5e2dD3422") - redeemCalldata = mustParseHex("f4fd17f90000000000000000000000000000000000000" + - "000000000000000000000000020000000000000000000000000000000000000000000000000" + - "00000000000000022c0a304c9321402dc11cbb5898b9f2af3029ce1c76ec6702c4cd5bb965f" + - "d3e7399d971975c09331eb00f5e0dc1eaeca9bf4ee2d086d3fe1de489f920007d654687eac0" + - "9638c0c38b4e735b79f053cb869167ee770640ac5df5c4ab030813122aebdc4c31b88d0c8f4" + - "d644591a8e00e92b607f920ad8050deb7c7469767d9c561") + initCalldataV1 = mustParseHex("52145bc0000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000040000000000000000000000000000000000000000000000000" + + "000000000000000209439d8fdc46a777590a5345704042c2774061d5322c6a94352c98" + + "a6f6a3630a00000000000000000000000000000000000000000000000022b1c8c1227a" + + "00000000000000000000000000002b84c791b79ee37de042ad2fff1a253c3ce9bc2700" + + "0000000000000000000000000000000000000000000000000000006148111400000000" + + "0000000000000000345853e21b1d475582e71cc269124ed5e2dd3422ebdc4c31b88d0c" + + "8f4d644591a8e00e92b607f920ad8050deb7c7469767d9c56100000000000000000000" + + "000000000000000000000000000022b1c8c1227a00000000000000000000000000002b" + + "84c791b79ee37de042ad2fff1a253c3ce9bc2700000000000000000000000000000000" + + "00000000000000000000000061481114000000000000000000000000345853e21b1d47" + + "5582e71cc269124ed5e2dd3422") + + redeemCalldataV0 = mustParseHex("f4fd17f90000000000000000000000000000000000" + + "0000000000000000000000000000200000000000000000000000000000000000000000" + + "000000000000000000000002557cf82e5e72e8ba23963a5e2832767a7e2a3e0a58ac00" + + "d319605f4b34b46de209439d8fdc46a777590a5345704042c2774061d5322c6a94352c" + + "98a6f6a3630a87eac09638c0c38b4e735b79f053cb869167ee770640ac5df5c4ab0308" + + "13122aebdc4c31b88d0c8f4d644591a8e00e92b607f920ad8050deb7c7469767d9c561") + + redeemCalldataV1 = mustParseHex("f9f2e0f40000000000000000000000000000000000" + + "00000000000000000000000000000000000000000000000000000000000000000000000000" + + "00000000000000000040000000000000000000000000000000000000000000000000000000" + + "000000000209439d8fdc46a777590a5345704042c2774061d5322c6a94352c98a6f6a3630a" + + "00000000000000000000000000000000000000000000000022b1c8c1227a00000000000000" + + "000000000000002b84c791b79ee37de042ad2fff1a253c3ce9bc2700000000000000000000" + + "00000000000000000000000000000000000061481114000000000000000000000000345853" + + "e21b1d475582e71cc269124ed5e2dd3422557cf82e5e72e8ba23963a5e2832767a7e2a3e0a" + + "58ac00d319605f4b34b46de2ebdc4c31b88d0c8f4d644591a8e00e92b607f920ad8050deb7" + + "c7469767d9c56100000000000000000000000000000000000000000000000022b1c8c1227a" + + "00000000000000000000000000002b84c791b79ee37de042ad2fff1a253c3ce9bc27000000" + + "00000000000000000000000000000000000000000000000000614811140000000000000000" + + "00000000345853e21b1d475582e71cc269124ed5e2dd342287eac09638c0c38b4e735b79f0" + + "53cb869167ee770640ac5df5c4ab030813122a") /* redeemCallData parses to: [ETHSwapRedemption { @@ -78,9 +128,6 @@ var ( Secret: 87eac09638c0c38b4e735b79f053cb869167ee770640ac5df5c4ab030813122a }] */ - redeemSecretHashA = mustParseHex("99d971975c09331eb00f5e0dc1eaeca9bf4ee2d086d3fe1de489f920007d6546") - redeemSecretHashB = mustParseHex("ebdc4c31b88d0c8f4d644591a8e00e92b607f920ad8050deb7c7469767d9c561") - redeemSecretB = mustParseHex("87eac09638c0c38b4e735b79f053cb869167ee770640ac5df5c4ab030813122a") ) func mustParseHex(s string) []byte { @@ -91,6 +138,11 @@ func mustParseHex(s string) []byte { return b } +func mustArray32(s string) (b [32]byte) { + copy(b[:], mustParseHex(s)) + return +} + type testNode struct { connectErr error bestHdr *types.Header @@ -103,11 +155,12 @@ type testNode struct { syncProgErr error suggGasTipCap *big.Int suggGasTipCapErr error - swp *dexeth.SwapState + swp map[string]*dexeth.SwapState swpErr error tx *types.Transaction txIsMempool bool txErr error + receipt *types.Receipt acctBal *big.Int acctBalErr error } @@ -142,14 +195,53 @@ func (n *testNode) suggestGasTipCap(ctx context.Context) (*big.Int, error) { return n.suggGasTipCap, n.suggGasTipCapErr } -func (n *testNode) swap(ctx context.Context, assetID uint32, secretHash [32]byte) (*dexeth.SwapState, error) { - return n.swp, n.swpErr +func (n *testNode) status(ctx context.Context, assetID uint32, token common.Address, locator []byte) (*dexeth.SwapStatus, error) { + if s := n.swp[string(locator)]; s != nil { + return &dexeth.SwapStatus{ + BlockHeight: s.BlockHeight, + Secret: s.Secret, + Step: s.State, + }, n.swpErr + } + return nil, n.swpErr +} + +func (n *testNode) vector(ctx context.Context, assetID uint32, locator []byte) (*dexeth.SwapVector, error) { + var secretHash [32]byte + switch len(locator) { + case dexeth.LocatorV1Length: + vec, _ := dexeth.ParseV1Locator(locator) + secretHash = vec.SecretHash + default: + copy(secretHash[:], locator) + } + + if s := n.swp[string(locator)]; s != nil { + return &dexeth.SwapVector{ + From: s.Initiator, + To: s.Participant, + Value: s.Value, + SecretHash: secretHash, + LockTime: uint64(s.LockTime.Unix()), + }, n.swpErr + } + return nil, n.swpErr +} + +func (n *testNode) statusAndVector(ctx context.Context, assetID uint32, locator []byte) (*dexeth.SwapStatus, *dexeth.SwapVector, error) { + status, _ := n.status(ctx, assetID, common.Address{}, locator) + vec, _ := n.vector(ctx, assetID, locator) + return status, vec, n.swpErr } -func (n *testNode) transaction(ctx context.Context, hash common.Hash) (tx *types.Transaction, isMempool bool, err error) { +func (n *testNode) transaction(ctx context.Context, txHash common.Hash) (tx *types.Transaction, isMempool bool, err error) { return n.tx, n.txIsMempool, n.txErr } +func (n *testNode) transactionReceipt(ctx context.Context, txHash common.Hash) (tx *types.Receipt, err error) { + return n.receipt, nil +} + func (n *testNode) accountBalance(ctx context.Context, assetID uint32, addr common.Address) (*big.Int, error) { return n.acctBal, n.acctBalErr } @@ -166,7 +258,9 @@ func tSwap(bn, locktime int64, value uint64, secret [32]byte, state dexeth.SwapS } func tNewBackend(assetID uint32) (*AssetBackend, *testNode) { - node := &testNode{} + node := &testNode{ + swp: make(map[string]*dexeth.SwapState), + } return &AssetBackend{ baseBackend: &baseBackend{ net: dex.Simnet, @@ -187,6 +281,9 @@ func TestMain(m *testing.M) { doIt := func() int { defer shutdown() dexeth.Tokens[usdcID].NetTokens[dex.Simnet].SwapContracts[0].Address = common.BytesToAddress(encode.RandomBytes(20)) + dexeth.Tokens[usdcID].NetTokens[dex.Simnet].SwapContracts[1].Address = common.BytesToAddress(encode.RandomBytes(20)) + dexeth.ContractAddresses[0][dex.Simnet] = common.BytesToAddress(encode.RandomBytes(20)) + dexeth.ContractAddresses[1][dex.Simnet] = common.BytesToAddress(encode.RandomBytes(20)) return m.Run() } os.Exit(doIt()) @@ -238,7 +335,7 @@ func TestDecodeCoinID(t *testing.T) { func TestRun(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) - backend, err := unconnectedETH(BipID, dexeth.ContractAddresses[0][dex.Simnet], registeredTokens, tLogger, dex.Simnet) + backend, err := unconnectedETH(BipID, defaultProtocolVersion.ContractVersion(), common.Address{}, common.Address{}, registeredTokens, tLogger, dex.Simnet) if err != nil { t.Fatalf("unconnectedETH error: %v", err) } @@ -389,8 +486,7 @@ func TestSynced(t *testing.T) { // TestRequiredOrderFunds ensures that a fee calculation in the calc package // will come up with the correct required funds. func TestRequiredOrderFunds(t *testing.T) { - - initTxSize := dexeth.InitGas(1, ethContractVersion) + initTxSize := dexeth.InitGas(1, 0) swapVal := uint64(1000000000) // gwei numSwaps := uint64(17) // swaps feeRate := uint64(30) // gwei / gas @@ -425,60 +521,75 @@ func TestContract(t *testing.T) { const gasTipCap = 2 const swapVal = 25e8 const txVal = 5e9 - var secret, secretHash [32]byte - copy(secret[:], redeemSecretB) - copy(secretHash[:], redeemSecretHashB) + secret0, secret1, secretHash0, secretHash1 := secretA, secretB, secretHashA, secretHashB + locator0, locator1 := locatorA, locatorB + swaps := map[string]*dexeth.SwapState{ + string(secretHash0[:]): tSwap(97, initLocktime, swapVal, secret0, dexeth.SSInitiated, &participantAddr), + string(secretHash1[:]): tSwap(97, initLocktime, swapVal, secret1, dexeth.SSInitiated, &participantAddr), + string(locator1): tSwap(97, initLocktime, swapVal, secret1, dexeth.SSInitiated, &participantAddr), + string(locator0): tSwap(97, initLocktime, swapVal, secret0, dexeth.SSInitiated, &participantAddr), + string(locator1): tSwap(97, initLocktime, swapVal, secret1, dexeth.SSInitiated, &participantAddr), + } tests := []struct { - name string - coinID []byte - contract []byte - tx *types.Transaction - swap *dexeth.SwapState - swapErr, txErr error - wantErr bool - }{{ - name: "ok", - tx: tTx(gasPrice, gasTipCap, txVal, contractAddr, initCalldata), - contract: dexeth.EncodeContractData(0, secretHash), - swap: tSwap(97, initLocktime, swapVal, secret, dexeth.SSInitiated, &initParticipantAddr), - coinID: txHash[:], - }, { - name: "new coiner error, wrong tx type", - tx: tTx(gasPrice, gasTipCap, txVal, contractAddr, initCalldata), - contract: dexeth.EncodeContractData(0, secretHash), - swap: tSwap(97, initLocktime, swapVal, secret, dexeth.SSInitiated, &initParticipantAddr), - coinID: txHash[1:], - wantErr: true, - }, { - name: "confirmations error, swap error", - tx: tTx(gasPrice, gasTipCap, txVal, contractAddr, initCalldata), - contract: dexeth.EncodeContractData(0, secretHash), - coinID: txHash[:], - swapErr: errors.New(""), - wantErr: true, - }} + name string + ver uint32 + coinID []byte + tx *types.Transaction + locators [][]byte + swapErr error + wantErr bool + }{ + { + name: "ok v0", + tx: tTx(gasPrice, gasTipCap, txVal, contractAddr, initCalldataV0), + locators: [][]byte{secretHash0[:], secretHash1[:]}, + coinID: txHash[:], + }, { + name: "ok v1", + ver: 1, + tx: tTx(gasPrice, gasTipCap, txVal, contractAddr, initCalldataV1), + locators: [][]byte{locator0, locator1}, + coinID: txHash[:], + }, { + name: "new coiner error, wrong tx type", + ver: 1, + tx: tTx(gasPrice, gasTipCap, txVal, contractAddr, initCalldataV1), + locators: [][]byte{locator0, locator1}, + coinID: txHash[1:], + wantErr: true, + }, { + name: "confirmations error, swap error", + ver: 1, + tx: tTx(gasPrice, gasTipCap, txVal, contractAddr, initCalldataV1), + locators: [][]byte{locator0, locator1}, + coinID: txHash[:], + swapErr: errors.New(""), + wantErr: true, + }, + } for _, test := range tests { eth, node := tNewBackend(BipID) node.tx = test.tx - node.txErr = test.txErr - node.swp = test.swap + node.swp = swaps node.swpErr = test.swapErr eth.contractAddr = *contractAddr - contractData := dexeth.EncodeContractData(0, secretHash) // matches initCalldata - contract, err := eth.Contract(test.coinID, contractData) - if test.wantErr { - if err == nil { - t.Fatalf("expected error for test %q", test.name) + for _, locator := range test.locators { + contractData := dexeth.EncodeContractData(test.ver, locator) + contract, err := eth.Contract(test.coinID, contractData) + if test.wantErr { + if err == nil { + t.Fatalf("expected error for test %q", test.name) + } + continue + } + if err != nil { + t.Fatalf("unexpected error for test %q: %v", test.name, err) + } + if contract.SwapAddress != participantAddr.String() || + contract.LockTime.Unix() != initLocktime { + t.Fatalf("returns do not match expected for test %q", test.name) } - continue - } - if err != nil { - t.Fatalf("unexpected error for test %q: %v", test.name, err) - } - if contract.SwapAddress != initParticipantAddr.String() || - contract.LockTime.Unix() != initLocktime { - t.Fatalf("returns do not match expected for test %q", test.name) } } } @@ -513,26 +624,32 @@ func TestValidateFeeRate(t *testing.T) { } func TestValidateSecret(t *testing.T) { - secret, blankHash := [32]byte{}, [32]byte{} - copy(secret[:], encode.RandomBytes(32)) - secretHash := sha256.Sum256(secret[:]) + v := &dexeth.SwapVector{SecretHash: sha256.Sum256(secretA[:]), Value: new(big.Int)} + badV := &dexeth.SwapVector{SecretHash: [32]byte{}, Value: new(big.Int)} + tests := []struct { name string contractData []byte want bool - }{{ - name: "ok", - contractData: dexeth.EncodeContractData(0, secretHash), - want: true, - }, { - name: "not the right hash", - contractData: dexeth.EncodeContractData(0, blankHash), - }, { - name: "bad contract data", - }} + }{ + { + name: "ok v0", + contractData: dexeth.EncodeContractData(0, secretHashA[:]), + want: true, + }, { + name: "ok v1", + contractData: dexeth.EncodeContractData(1, v.Locator()), + want: true, + }, { + name: "not the right hash", + contractData: dexeth.EncodeContractData(1, badV.Locator()), + }, { + name: "bad contract data", + }, + } for _, test := range tests { eth, _ := tNewBackend(BipID) - got := eth.ValidateSecret(secret[:], test.contractData) + got := eth.ValidateSecret(secretA[:], test.contractData) if test.want != got { t.Fatalf("expected %v but got %v for test %q", test.want, got, test.name) } @@ -543,57 +660,68 @@ func TestRedemption(t *testing.T) { receiverAddr, contractAddr := new(common.Address), new(common.Address) copy(receiverAddr[:], encode.RandomBytes(20)) copy(contractAddr[:], encode.RandomBytes(20)) - var secret, secretHash, txHash [32]byte - copy(secret[:], redeemSecretB) - copy(secretHash[:], redeemSecretHashB) + secret, secretHash := secretB, secretHashB + var txHash [32]byte copy(txHash[:], encode.RandomBytes(32)) const gasPrice = 30 const gasTipCap = 2 + tests := []struct { - name string - coinID, contractID []byte - swp *dexeth.SwapState - tx *types.Transaction - txIsMempool bool - swpErr, txErr error - wantErr bool - }{{ - name: "ok", - tx: tTx(gasPrice, gasTipCap, 0, contractAddr, redeemCalldata), - contractID: dexeth.EncodeContractData(0, secretHash), - coinID: txHash[:], - swp: tSwap(0, 0, 0, secret, dexeth.SSRedeemed, receiverAddr), - }, { - name: "new coiner error, wrong tx type", - tx: tTx(gasPrice, gasTipCap, 0, contractAddr, redeemCalldata), - contractID: dexeth.EncodeContractData(0, secretHash), - coinID: txHash[1:], - wantErr: true, - }, { - name: "confirmations error, swap wrong state", - tx: tTx(gasPrice, gasTipCap, 0, contractAddr, redeemCalldata), - contractID: dexeth.EncodeContractData(0, secretHash), - swp: tSwap(0, 0, 0, secret, dexeth.SSRefunded, receiverAddr), - coinID: txHash[:], - wantErr: true, - }, { - name: "validate redeem error", - tx: tTx(gasPrice, gasTipCap, 0, contractAddr, redeemCalldata), - contractID: secretHash[:31], - coinID: txHash[:], - swp: tSwap(0, 0, 0, secret, dexeth.SSRedeemed, receiverAddr), - wantErr: true, - }} + name string + ver uint32 + coinID []byte + locator []byte + swp *dexeth.SwapState + tx *types.Transaction + wantErr bool + }{ + { + name: "ok v0", + tx: tTx(gasPrice, gasTipCap, 0, contractAddr, redeemCalldataV0), + locator: secretHash[:], + coinID: txHash[:], + swp: tSwap(0, 0, 0, secret, dexeth.SSRedeemed, receiverAddr), + }, { + name: "ok v1", + ver: 1, + tx: tTx(gasPrice, gasTipCap, 0, contractAddr, redeemCalldataV1), + locator: locatorA, + coinID: txHash[:], + swp: tSwap(0, 0, 0, secret, dexeth.SSRedeemed, receiverAddr), + }, { + name: "new coiner error, wrong tx type", + tx: tTx(gasPrice, gasTipCap, 0, contractAddr, redeemCalldataV0), + locator: secretHash[:], + coinID: txHash[1:], + wantErr: true, + }, { + name: "confirmations error, swap wrong state", + tx: tTx(gasPrice, gasTipCap, 0, contractAddr, redeemCalldataV0), + locator: secretHash[:], + swp: tSwap(0, 0, 0, secret, dexeth.SSRefunded, receiverAddr), + coinID: txHash[:], + wantErr: true, + }, { + name: "validate redeem error", + tx: tTx(gasPrice, gasTipCap, 0, contractAddr, redeemCalldataV0), + locator: secretHash[:31], + coinID: txHash[:], + swp: tSwap(0, 0, 0, secret, dexeth.SSRedeemed, receiverAddr), + wantErr: true, + }, + } + for _, test := range tests { eth, node := tNewBackend(BipID) node.tx = test.tx - node.txIsMempool = test.txIsMempool - node.txErr = test.txErr - node.swp = test.swp - node.swpErr = test.swpErr + node.receipt = &types.Receipt{ + BlockNumber: big.NewInt(5), + } + node.swp[string(test.locator)] = test.swp eth.contractAddr = *contractAddr - _, err := eth.Redemption(test.coinID, nil, test.contractID) + contract := dexeth.EncodeContractData(test.ver, test.locator) + _, err := eth.Redemption(test.coinID, nil, contract) if test.wantErr { if err == nil { t.Fatalf("expected error for test %q", test.name) @@ -662,24 +790,32 @@ func TestValidateContract(t *testing.T) { } func testValidateContract(t *testing.T, assetID uint32) { + badLoc := append(locatorA, 8) tests := []struct { - name string - ver uint32 - secretHash []byte - wantErr bool - }{{ - name: "ok", - secretHash: make([]byte, dexeth.SecretHashSize), - }, { - name: "wrong size", - secretHash: make([]byte, dexeth.SecretHashSize-1), - wantErr: true, - }, { - name: "wrong version", - ver: 1, - secretHash: make([]byte, dexeth.SecretHashSize), - wantErr: true, - }} + name string + ver uint32 + locator []byte + wantErr bool + }{ + { + name: "ok v0", + locator: secretHashA[:], + }, { + name: "ok v1", + ver: 1, + locator: locatorA[:], + }, { + name: "wrong size", + ver: 1, + locator: badLoc, + wantErr: true, + }, { + name: "wrong version", + ver: 0, + locator: locatorA[:], // should be secretHashA + wantErr: true, + }, + } type contractValidator interface { ValidateContract([]byte) error @@ -694,17 +830,14 @@ func testValidateContract(t *testing.T, assetID uint32) { cv = &TokenBackend{ AssetBackend: eth, VersionedToken: &VersionedToken{ - Token: dexeth.Tokens[usdcID], - Ver: 0, + Token: dexeth.Tokens[usdcID], + ContractVersion: test.ver, }, } } - swapData := make([]byte, 4+len(test.secretHash)) - binary.BigEndian.PutUint32(swapData[:4], test.ver) - copy(swapData[4:], test.secretHash) - - err := cv.ValidateContract(swapData) + contractData := dexeth.EncodeContractData(test.ver, test.locator) + err := cv.ValidateContract(contractData) if test.wantErr { if err == nil { t.Fatalf("expected error for test %q", test.name) diff --git a/server/asset/eth/rpcclient.go b/server/asset/eth/rpcclient.go index 5a7c00d096..9503ce034e 100644 --- a/server/asset/eth/rpcclient.go +++ b/server/asset/eth/rpcclient.go @@ -18,6 +18,7 @@ import ( "decred.org/dcrdex/dex" 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/common" "github.com/ethereum/go-ethereum/common/hexutil" @@ -171,23 +172,27 @@ type rpcclient struct { neverConnectedEndpoints []endpoint healthCheckCounter int tokensLoaded map[uint32]*VersionedToken + ethContractVer uint32 ethContractAddr common.Address + ethContractAddrV1 common.Address // the order of clients will change based on the health of the connections. clientsMtx sync.RWMutex clients []*ethConn } -func newRPCClient(baseChainID uint32, chainID uint64, net dex.Network, endpoints []endpoint, ethContractAddr common.Address, log dex.Logger) *rpcclient { +func newRPCClient(baseChainID uint32, chainID uint64, net dex.Network, endpoints []endpoint, ethContractVer uint32, ethContractAddr, ethContractAddrV1 common.Address, log dex.Logger) *rpcclient { return &rpcclient{ - baseChainID: baseChainID, - genesisChainID: chainID, - baseChainName: strings.ToUpper(dex.BipIDSymbol(baseChainID)), - net: net, - endpoints: endpoints, - log: log, - ethContractAddr: ethContractAddr, - tokensLoaded: make(map[uint32]*VersionedToken), + baseChainID: baseChainID, + genesisChainID: chainID, + baseChainName: strings.ToUpper(dex.BipIDSymbol(baseChainID)), + net: net, + endpoints: endpoints, + log: log, + ethContractVer: ethContractVer, + ethContractAddr: ethContractAddr, + ethContractAddrV1: ethContractAddrV1, + tokensLoaded: make(map[uint32]*VersionedToken), } } @@ -257,14 +262,23 @@ func (c *rpcclient) connectToEndpoint(ctx context.Context, endpoint endpoint) (* ec.txPoolSupported = true } - es, err := swapv0.NewETHSwap(c.ethContractAddr, ec.Client) - if err != nil { - return nil, fmt.Errorf("unable to initialize %v contract for %q: %v", c.baseChainName, endpoint, err) + switch c.ethContractVer { + case 0: + es0, err := swapv0.NewETHSwap(c.ethContractAddr, ec.Client) + if err != nil { + return nil, err + } + ec.swapContract = &swapSourceV0{es0} + case 1: + es1, err := swapv1.NewETHSwap(c.ethContractAddr, ec.Client) + if err != nil { + return nil, err + } + ec.swapContract = &swapSourceV1{es1, c.ethContractAddrV1} } - ec.swapContract = &swapSourceV0{es} for assetID, vToken := range c.tokensLoaded { - tkn, err := newTokener(ctx, vToken, c.net, ec.Client) + tkn, err := newTokener(ctx, assetID, vToken, c.net, ec.Client, c.ethContractAddrV1) if err != nil { return nil, fmt.Errorf("error constructing ERC20Swap: %w", err) } @@ -443,7 +457,7 @@ func (c *rpcclient) withClient(f func(ec *ethConn) error, haltOnNotFound ...bool return nil } if len(haltOnNotFound) > 0 && haltOnNotFound[0] && (errors.Is(err, ethereum.NotFound) || strings.Contains(err.Error(), "not found")) { - return err + return ethereum.NotFound } c.log.Errorf("Unpropagated error from %q: %v", ec.endpoint, err) @@ -505,7 +519,7 @@ func (c *rpcclient) loadToken(ctx context.Context, assetID uint32, vToken *Versi c.tokensLoaded[assetID] = vToken for _, cl := range c.clientsCopy() { - tkn, err := newTokener(ctx, vToken, c.net, cl.Client) + tkn, err := newTokener(ctx, assetID, vToken, c.net, cl.Client, c.ethContractAddrV1) if err != nil { return fmt.Errorf("error constructing ERC20Swap: %w", err) } @@ -522,7 +536,17 @@ func (c *rpcclient) withTokener(assetID uint32, f func(*tokener) error) error { } return f(tkn) }) +} +func (c *rpcclient) withSwapContract(assetID uint32, f func(swapContract) error) error { + if assetID == c.baseChainID { + return c.withClient(func(ec *ethConn) error { + return f(ec.swapContract) + }) + } + return c.withTokener(assetID, func(tkn *tokener) error { + return f(tkn) + }) } // bestHeader gets the best header at the time of calling. @@ -561,16 +585,23 @@ func (c *rpcclient) blockNumber(ctx context.Context) (bn uint64, err error) { }) } -// swap gets a swap keyed by secretHash in the contract. -func (c *rpcclient) swap(ctx context.Context, assetID uint32, secretHash [32]byte) (state *dexeth.SwapState, err error) { - if assetID == c.baseChainID { - return state, c.withClient(func(ec *ethConn) error { - state, err = ec.swapContract.Swap(ctx, secretHash) - return err - }) - } - return state, c.withTokener(assetID, func(tkn *tokener) error { - state, err = tkn.Swap(ctx, secretHash) +func (c *rpcclient) status(ctx context.Context, assetID uint32, token common.Address, locator []byte) (status *dexeth.SwapStatus, err error) { + return status, c.withSwapContract(assetID, func(sc swapContract) error { + status, err = sc.status(ctx, token, locator) + return err + }) +} + +func (c *rpcclient) vector(ctx context.Context, assetID uint32, locator []byte) (vec *dexeth.SwapVector, err error) { + return vec, c.withSwapContract(assetID, func(sc swapContract) error { + vec, err = sc.vector(ctx, locator) + return err + }) +} + +func (c *rpcclient) statusAndVector(ctx context.Context, assetID uint32, locator []byte) (status *dexeth.SwapStatus, vec *dexeth.SwapVector, err error) { + return status, vec, c.withSwapContract(assetID, func(sc swapContract) error { + status, vec, err = sc.statusAndVector(ctx, locator) return err }) } @@ -584,6 +615,17 @@ func (c *rpcclient) transaction(ctx context.Context, hash common.Hash) (tx *type }, true) // stop on first provider with "not found", because this should be an error if tx does not exist } +func (c *rpcclient) transactionReceipt(ctx context.Context, txHash common.Hash) (r *types.Receipt, err error) { + return r, c.withClient(func(ec *ethConn) error { + r, err = ec.TransactionReceipt(ctx, txHash) + return err + }) +} + +func isNotFoundError(err error) bool { + return strings.Contains(err.Error(), "not found") +} + // dumbBalance gets the account balance, ignoring the effects of unmined // transactions. func (c *rpcclient) dumbBalance(ctx context.Context, ec *ethConn, assetID uint32, addr common.Address) (bal *big.Int, err error) { diff --git a/server/asset/eth/rpcclient_harness_test.go b/server/asset/eth/rpcclient_harness_test.go index 4211e99d2a..b52294b6c8 100644 --- a/server/asset/eth/rpcclient_harness_test.go +++ b/server/asset/eth/rpcclient_harness_test.go @@ -6,16 +6,15 @@ package eth import ( + "context" "errors" "fmt" "math/big" "os" "os/exec" "path/filepath" - "time" - - "context" "testing" + "time" "decred.org/dcrdex/dex" "decred.org/dcrdex/dex/encode" @@ -29,8 +28,8 @@ var ( homeDir = os.Getenv("HOME") alphaIPCFile = filepath.Join(homeDir, "dextest", "eth", "alpha", "node", "geth.ipc") - contractAddrFile = filepath.Join(homeDir, "dextest", "eth", "eth_swap_contract_address.txt") - tokenSwapAddrFile = filepath.Join(homeDir, "dextest", "eth", "erc20_swap_contract_address.txt") + contractAddrFile = filepath.Join(homeDir, "dextest", "eth", "eth_swap_contract_address_v0.txt") + tokenSwapAddrFile = filepath.Join(homeDir, "dextest", "eth", "usdc_swap_contract_address_v0.txt") tokenErc20AddrFile = filepath.Join(homeDir, "dextest", "eth", "test_usdc_contract_address.txt") deltaAddress = "d12ab7cf72ccf1f3882ec99ddc53cd415635c3be" gammaAddress = "41293c2032bac60aa747374e966f79f575d42379" @@ -48,16 +47,14 @@ func TestMain(m *testing.M) { defer cancel() log := dex.StdOutLogger("T", dex.LevelTrace) - netAddrs, found := dexeth.ContractAddresses[ethContractVersion] - if !found { - return 1, fmt.Errorf("no contract address for eth version %d", ethContractVersion) - } - ethContractAddr, found := netAddrs[dex.Simnet] - if !found { - return 1, fmt.Errorf("no contract address for eth version %d on %s", ethContractVersion, dex.Simnet) - } + contractVer := defaultProtocolVersion.ContractVersion() + netAddrs := dexeth.ContractAddresses[contractVer] + ethContractAddr := netAddrs[dex.Simnet] + + netAddrsV1 := dexeth.ContractAddresses[1] + ethContractAddrV1 := netAddrsV1[dex.Simnet] - ethClient = newRPCClient(BipID, 42, dex.Simnet, []endpoint{{url: wsEndpoint}, {url: alphaIPCFile}}, ethContractAddr, log) + ethClient = newRPCClient(BipID, 42, dex.Simnet, []endpoint{{url: wsEndpoint}, {url: alphaIPCFile}}, contractVer, ethContractAddr, ethContractAddrV1, log) dexeth.ContractAddresses[0][dex.Simnet] = getContractAddrFromFile(contractAddrFile) @@ -110,10 +107,10 @@ func TestSuggestGasTipCap(t *testing.T) { } } -func TestSwap(t *testing.T) { +func TestStatus(t *testing.T) { var secretHash [32]byte copy(secretHash[:], encode.RandomBytes(32)) - _, err := ethClient.swap(ctx, BipID, secretHash) + _, err := ethClient.status(ctx, BipID, common.Address{}, secretHash[:]) if err != nil { t.Fatal(err) } @@ -203,7 +200,7 @@ func TestHeaderSubscription(t *testing.T) { ctx, cancel := context.WithTimeout(ctx, headerExpirationTime) defer cancel() ept := endpoint{url: wsEndpoint} - cl := newRPCClient(BipID, 42, dex.Simnet, []endpoint{ept}, ethClient.ethContractAddr, ethClient.log) + cl := newRPCClient(BipID, 42, dex.Simnet, []endpoint{ept}, ethClient.ethContractVer, ethClient.ethContractAddr, ethClient.ethContractAddrV1, ethClient.log) ec, err := cl.connectToEndpoint(ctx, ept) if err != nil { t.Fatalf("connectToEndpoint error: %v", err) diff --git a/server/asset/eth/tokener.go b/server/asset/eth/tokener.go index 878db2096a..993a58b0a0 100644 --- a/server/asset/eth/tokener.go +++ b/server/asset/eth/tokener.go @@ -13,13 +13,16 @@ import ( erc20v0 "decred.org/dcrdex/dex/networks/erc20/contracts/v0" 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(ctx context.Context, token common.Address, 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) } // erc2Contract exposes methods of a token's ERC20 contract. @@ -36,19 +39,47 @@ type tokener struct { } // newTokener is a constructor for a tokener. -func newTokener(ctx context.Context, vToken *VersionedToken, net dex.Network, be bind.ContractBackend) (*tokener, error) { - netToken, swapContract, err := networkToken(vToken, net) +func newTokener( + ctx context.Context, + assetID uint32, + vToken *VersionedToken, + net dex.Network, + be bind.ContractBackend, + v1addr common.Address, +) (*tokener, error) { + + netToken, contract, err := networkToken(vToken, net) if err != nil { return nil, err } - if vToken.Ver != 0 { - return nil, fmt.Errorf("only version 0 contracts supported") - } + var sc swapContract + switch vToken.ContractVersion { + case 0: + es, err := erc20v0.NewERC20Swap(contract.Address, be) + if err != nil { + return nil, err + } + sc = &swapSourceV0{es} - es, err := erc20v0.NewERC20Swap(swapContract.Address, be) - if err != nil { - return nil, err + boundAddr, err := es.TokenAddress(readOnlyCallOpts(ctx)) + if err != nil { + return nil, fmt.Errorf("error retrieving bound address for %s version %d contract: %w", + vToken.Name, vToken.ContractVersion, err) + } + + if boundAddr != netToken.Address { + return nil, fmt.Errorf("wrong bound address for %s version %d contract. wanted %s, got %s", + vToken.Name, vToken.ContractVersion, netToken.Address, boundAddr) + } + case 1: + es, err := swapv1.NewETHSwap(v1addr, be) + if err != nil { + return nil, err + } + sc = &swapSourceV1{contract: es, tokenAddr: netToken.Address} + default: + return nil, fmt.Errorf("unsupported contract version %d", vToken.ContractVersion) } erc20, err := erc20.NewIERC20(netToken.Address, be) @@ -56,22 +87,11 @@ func newTokener(ctx context.Context, vToken *VersionedToken, net dex.Network, be return nil, err } - boundAddr, err := es.TokenAddress(readOnlyCallOpts(ctx, false)) - if err != nil { - return nil, fmt.Errorf("error retrieving bound address for %s version %d contract: %w", - vToken.Name, vToken.Ver, err) - } - - if boundAddr != netToken.Address { - return nil, fmt.Errorf("wrong bound address for %s version %d contract. wanted %s, got %s", - vToken.Name, vToken.Ver, netToken.Address, boundAddr) - } - tkn := &tokener{ VersionedToken: vToken, - swapContract: &swapSourceV0{es}, + swapContract: sc, erc20Contract: erc20, - contractAddr: swapContract.Address, + contractAddr: contract.Address, tokenAddr: netToken.Address, } @@ -90,7 +110,7 @@ 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) + inits, err := dexeth.ParseInitiateDataV0(txData) if err != nil { return nil } @@ -103,7 +123,7 @@ func (t *tokener) swapped(txData []byte) *big.Int { // balanceOf checks the account's token balance. func (t *tokener) balanceOf(ctx context.Context, addr common.Address) (*big.Int, error) { - return t.BalanceOf(readOnlyCallOpts(ctx, false), addr) + return t.BalanceOf(readOnlyCallOpts(ctx), addr) } // swapContractV0 represents a version 0 swap contract for ETH or a token. @@ -117,20 +137,149 @@ type swapSourceV0 struct { contract swapContractV0 // *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) +// swap gets the swap state for the secretHash on the version 0 contract. +func (s *swapSourceV0) swap(ctx context.Context, secretHash [32]byte) (*dexeth.SwapState, error) { + state, err := s.contract.Swap(readOnlyCallOpts(ctx), secretHash) if err != nil { - return nil, fmt.Errorf("Swap error: %w", err) + return nil, fmt.Errorf("swap error: %w", err) } return dexeth.SwapStateFromV0(&state), nil } +// status fetches the SwapStatus, which specifies the current state of mutable +// swap data. +func (s *swapSourceV0) status(ctx context.Context, _ common.Address, locator []byte) (*dexeth.SwapStatus, error) { + secretHash, err := dexeth.ParseV0Locator(locator) + if err != nil { + return nil, err + } + swap, err := s.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 (s *swapSourceV0) vector(ctx context.Context, locator []byte) (*dexeth.SwapVector, error) { + secretHash, err := dexeth.ParseV0Locator(locator) + if err != nil { + return nil, err + } + swap, err := s.swap(ctx, secretHash) + if err != nil { + return nil, err + } + vector := &dexeth.SwapVector{ + From: swap.Initiator, + To: swap.Participant, + Value: swap.Value, + SecretHash: secretHash, + LockTime: uint64(swap.LockTime.Unix()), + } + 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 (s *swapSourceV0) 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 := s.swap(ctx, secretHash) + if err != nil { + return nil, nil, err + } + vector := &dexeth.SwapVector{ + From: swap.Initiator, + To: swap.Participant, + Value: swap.Value, + SecretHash: secretHash, + LockTime: uint64(swap.LockTime.Unix()), + } + status := &dexeth.SwapStatus{ + Step: swap.State, + Secret: swap.Secret, + BlockHeight: swap.BlockHeight, + } + return status, vector, nil +} + +type swapContractV1 interface { + Status(opts *bind.CallOpts, token common.Address, c swapv1.ETHSwapVector) (swapv1.ETHSwapStatus, error) +} + +type swapSourceV1 struct { + contract swapContractV1 // *swapv0.ETHSwap or *erc20v0.ERCSwap + tokenAddr common.Address +} + +func (s *swapSourceV1) status(ctx context.Context, token common.Address, locator []byte) (*dexeth.SwapStatus, error) { + v, err := dexeth.ParseV1Locator(locator) + if err != nil { + return nil, err + } + rec, err := s.contract.Status(readOnlyCallOpts(ctx), token, 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 +} + +func (s *swapSourceV1) vector(ctx context.Context, locator []byte) (*dexeth.SwapVector, error) { + return dexeth.ParseV1Locator(locator) +} + +func (s *swapSourceV1) 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 := s.contract.Status(readOnlyCallOpts(ctx), s.tokenAddr, 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 (s *swapSourceV1) Status(ctx context.Context, locator []byte) (*dexeth.SwapStatus, error) { + vec, err := dexeth.ParseV1Locator(locator) + if err != nil { + return nil, err + } + + status, err := s.contract.Status(readOnlyCallOpts(ctx), s.tokenAddr, dexeth.SwapVectorToAbigen(vec)) + if err != nil { + return nil, err + } + + return &dexeth.SwapStatus{ + Step: dexeth.SwapStep(status.Step), + Secret: status.Secret, + BlockHeight: status.BlockNumber.Uint64(), + }, err +} + // readOnlyCallOpts is the CallOpts used for read-only contract method calls. -func readOnlyCallOpts(ctx context.Context, includePending bool) *bind.CallOpts { +func readOnlyCallOpts(ctx context.Context) *bind.CallOpts { return &bind.CallOpts{ - Pending: includePending, Context: ctx, } } diff --git a/server/asset/polygon/polygon.go b/server/asset/polygon/polygon.go index b6cfadde31..91f4638115 100644 --- a/server/asset/polygon/polygon.go +++ b/server/asset/polygon/polygon.go @@ -5,9 +5,9 @@ package polygon import ( "fmt" - "time" "decred.org/dcrdex/dex" + dexeth "decred.org/dcrdex/dex/networks/eth" dexpolygon "decred.org/dcrdex/dex/networks/polygon" "decred.org/dcrdex/server/asset" "decred.org/dcrdex/server/asset/eth" @@ -15,51 +15,42 @@ import ( var registeredTokens = make(map[uint32]*eth.VersionedToken) -func registerToken(assetID uint32, ver uint32) { +func registerToken(assetID uint32, protocolVersion dexeth.ProtocolVersion) { token, exists := dexpolygon.Tokens[assetID] if !exists { panic(fmt.Sprintf("no token constructor for asset ID %d", assetID)) } asset.RegisterToken(assetID, ð.TokenDriver{ DriverBase: eth.DriverBase{ - Ver: ver, - UI: token.UnitInfo, - Nam: token.Name, + ProtocolVersion: protocolVersion, + UI: token.UnitInfo, + Nam: token.Name, }, Token: token.Token, }) registeredTokens[assetID] = ð.VersionedToken{ - Token: token, - Ver: ver, + Token: token, + ContractVersion: protocolVersion.ContractVersion(), } } func init() { asset.Register(BipID, &Driver{eth.Driver{ DriverBase: eth.DriverBase{ - Ver: version, - UI: dexpolygon.UnitInfo, - Nam: "Polygon", + ProtocolVersion: eth.ProtocolVersion(BipID), + UI: dexpolygon.UnitInfo, + Nam: "Polygon", }, }}) - registerToken(usdcID, 0) - registerToken(usdtID, 0) - registerToken(wethTokenID, 0) - registerToken(wbtcTokenID, 0) - - if blockPollIntervalStr != "" { - blockPollInterval, _ = time.ParseDuration(blockPollIntervalStr) - if blockPollInterval < time.Second { - panic(fmt.Sprintf("invalid value for blockPollIntervalStr: %q", blockPollIntervalStr)) - } - } + registerToken(usdcID, eth.ProtocolVersion(usdcID)) + registerToken(usdtID, eth.ProtocolVersion(usdtID)) + registerToken(wethTokenID, eth.ProtocolVersion(wethTokenID)) + registerToken(wbtcTokenID, eth.ProtocolVersion(wbtcTokenID)) } const ( - BipID = 966 - ethContractVersion = 0 - version = 0 + BipID = 966 ) var ( @@ -67,12 +58,6 @@ var ( usdtID, _ = dex.BipSymbolID("usdt.polygon") wethTokenID, _ = dex.BipSymbolID("weth.polygon") wbtcTokenID, _ = dex.BipSymbolID("wbtc.polygon") - - // blockPollInterval is the delay between calls to bestBlockHash to check - // for new blocks. Modify at compile time via blockPollIntervalStr: - // go build -tags lgpl -ldflags "-X 'decred.org/dcrdex/server/asset/polygon.blockPollIntervalStr=10s'" - blockPollInterval = time.Second - blockPollIntervalStr string ) type Driver struct { diff --git a/server/cmd/dcrdex/evm-protocol-overrides.json b/server/cmd/dcrdex/evm-protocol-overrides.json new file mode 100644 index 0000000000..9e26dfeeb6 --- /dev/null +++ b/server/cmd/dcrdex/evm-protocol-overrides.json @@ -0,0 +1 @@ +{} \ No newline at end of file