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 0000000000..bf71557108 Binary files /dev/null and b/dex/networks/erc20/contracts/v0/swap_contract.bin differ 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 0000000000..6ec95be8db Binary files /dev/null and b/dex/networks/erc20/contracts/v0/token_contract.bin differ 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 0000000000..195ca6c037 Binary files /dev/null and b/dex/networks/eth/contracts/multibalance/contract.bin differ diff --git a/dex/networks/eth/contracts/updatecontract.sh b/dex/networks/eth/contracts/updatecontract.sh index 6b25792108..75bf20b613 100755 --- a/dex/networks/eth/contracts/updatecontract.sh +++ b/dex/networks/eth/contracts/updatecontract.sh @@ -23,28 +23,14 @@ then fi mkdir temp +mkdir -p ${PKG_NAME} solc --abi --bin --bin-runtime --overwrite --optimize ${SOLIDITY_FILE} -o ./temp/ -BYTECODE=$(<./temp/${CONTRACT_NAME}.bin-runtime) - -cat > "./${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 0000000000..32395c9d6b Binary files /dev/null and b/dex/networks/eth/contracts/v0/contract.bin differ 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 0000000000..d0c7766781 Binary files /dev/null and b/dex/networks/eth/contracts/v1/contract.bin differ 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