diff --git a/client/asset/eth/contractor.go b/client/asset/eth/contractor.go index 6fa30124c3..69d97e26a6 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" @@ -15,8 +17,10 @@ import ( "decred.org/dcrdex/dex/encode" "decred.org/dcrdex/dex/networks/erc20" erc20v0 "decred.org/dcrdex/dex/networks/erc20/contracts/v0" + erc20v1 "decred.org/dcrdex/dex/networks/erc20/contracts/v1" dexeth "decred.org/dcrdex/dex/networks/eth" swapv0 "decred.org/dcrdex/dex/networks/eth/contracts/v0" + swapv1 "decred.org/dcrdex/dex/networks/eth/contracts/v1" "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/accounts/abi/bind" @@ -28,19 +32,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,7 +63,7 @@ type tokenContractor interface { estimateTransferGas(context.Context, *big.Int) (uint64, error) } -type contractorConstructor func(contractAddr, addr common.Address, ec bind.ContractBackend) (contractor, error) +type contractorConstructor func(net dex.Network, 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) // contractV0 is the interface common to a version 0 swap contract or version 0 @@ -70,6 +76,21 @@ type contractV0 interface { IsRefundable(opts *bind.CallOpts, secretHash [32]byte) (bool, error) } +var _ contractV0 = (*swapv0.ETHSwap)(nil) + +type contractV1 interface { + Initiate(opts *bind.TransactOpts, contracts []swapv1.ETHSwapVector) (*types.Transaction, error) + Redeem(opts *bind.TransactOpts, redemptions []swapv1.ETHSwapRedemption) (*types.Transaction, error) + Status(opts *bind.CallOpts, c swapv1.ETHSwapVector) (swapv1.ETHSwapStatus, error) + Refund(opts *bind.TransactOpts, c swapv1.ETHSwapVector) (*types.Transaction, error) + IsRedeemable(opts *bind.CallOpts, c swapv1.ETHSwapVector) (bool, error) + + ContractKey(opts *bind.CallOpts, v swapv1.ETHSwapVector) ([32]byte, error) + Swaps(opts *bind.CallOpts, arg0 [32]byte) ([32]byte, error) +} + +var _ contractV1 = (*swapv1.ETHSwap)(nil) + // contractorV0 is the contractor for contract version 0. // Redeem and Refund methods of swapv0.ETHSwap already have suitable return types. type contractorV0 struct { @@ -88,7 +109,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 +175,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 +190,73 @@ func (c *contractorV0) redeem(txOpts *bind.TransactOpts, redemptions []*asset.Re return c.contractV0.Redeem(txOpts, redemps) } -// swap retrieves the swap info from the read-only swap method. +// status fetches the SwapStatus, which specifies the current state of mutable +// swap data. +func (c *contractorV0) status(ctx context.Context, locator []byte) (*dexeth.SwapStatus, error) { + secretHash, err := dexeth.ParseV0Locator(locator) + if err != nil { + return nil, err + } + swap, err := c.swap(ctx, secretHash) + if err != nil { + return nil, err + } + status := &dexeth.SwapStatus{ + Step: swap.State, + Secret: swap.Secret, + BlockHeight: swap.BlockHeight, + } + return status, nil +} + +// vector generates a SwapVector, containing the immutable data that defines +// the swap. +func (c *contractorV0) vector(ctx context.Context, locator []byte) (*dexeth.SwapVector, error) { + secretHash, err := dexeth.ParseV0Locator(locator) + if err != nil { + return nil, err + } + swap, err := c.swap(ctx, secretHash) + if err != nil { + return nil, err + } + vector := &dexeth.SwapVector{ + From: swap.Participant, + To: swap.Initiator, + Value: dexeth.WeiToGwei(swap.Value), + SecretHash: secretHash, + LockTime: uint64(swap.LockTime.UnixMilli()), + } + return vector, nil +} + +// statusAndVector generates both the status and the vector simultaneously. For +// version 0, this is better than calling status and vector separately, since +// each makes an identical call to c.swap. +func (c *contractorV0) statusAndVector(ctx context.Context, locator []byte) (*dexeth.SwapStatus, *dexeth.SwapVector, error) { + secretHash, err := dexeth.ParseV0Locator(locator) + if err != nil { + return nil, nil, err + } + swap, err := c.swap(ctx, secretHash) + if err != nil { + return nil, nil, err + } + vector := &dexeth.SwapVector{ + From: swap.Participant, + To: swap.Initiator, + Value: dexeth.WeiToGwei(swap.Value), + SecretHash: secretHash, + LockTime: uint64(swap.LockTime.UnixMilli()), + } + status := &dexeth.SwapStatus{ + Step: swap.State, + Secret: swap.Secret, + BlockHeight: swap.BlockHeight, + } + return status, vector, nil +} + func (c *contractorV0) swap(ctx context.Context, secretHash [32]byte) (*dexeth.SwapState, error) { callOpts := &bind.CallOpts{ From: c.acctAddr, @@ -188,19 +280,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 +322,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) } @@ -275,7 +391,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 +402,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 +415,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 +423,73 @@ func (c *contractorV0) outgoingValue(tx *types.Transaction) (swapped uint64) { return } +// erc20Contractor supports the ERC20 ABI. Embedded in token contractors. +type erc20Contractor struct { + tokenContract *erc20.IERC20 + acct common.Address + contract common.Address +} + +// balance exposes the read-only balanceOf method of the erc20 token contract. +func (c *erc20Contractor) balance(ctx context.Context) (*big.Int, error) { + callOpts := &bind.CallOpts{ + From: c.acct, + Context: ctx, + } + + return c.tokenContract.BalanceOf(callOpts, c.acct) +} + +// allowance exposes the read-only allowance method of the erc20 token contract. +func (c *erc20Contractor) allowance(ctx context.Context) (*big.Int, error) { + callOpts := &bind.CallOpts{ + Pending: true, + From: c.acct, + Context: ctx, + } + return c.tokenContract.Allowance(callOpts, c.acct, c.contract) +} + +// approve sends an approve transaction approving the linked contract to call +// transferFrom for the specified amount. +func (c *erc20Contractor) approve(txOpts *bind.TransactOpts, amount *big.Int) (*types.Transaction, error) { + return c.tokenContract.Approve(txOpts, c.contract, amount) +} + +// transfer calls the transfer method of the erc20 token contract. Used for +// sends or withdrawals. +func (c *erc20Contractor) transfer(txOpts *bind.TransactOpts, addr common.Address, amount *big.Int) (*types.Transaction, error) { + return c.tokenContract.Transfer(txOpts, addr, amount) +} + +func (c *erc20Contractor) parseTransfer(receipt *types.Receipt) (uint64, error) { + var transferredAmt uint64 + for _, log := range receipt.Logs { + if log.Address != c.contract { + continue + } + transfer, err := c.tokenContract.ParseTransfer(*log) + if err != nil { + continue + } + if transfer.To == c.acct { + transferredAmt += transfer.Value.Uint64() + } + } + + if transferredAmt > 0 { + return transferredAmt, nil + } + + return 0, fmt.Errorf("transfer log to %s not found", c.contract) +} + // tokenContractorV0 is a contractor that implements the tokenContractor // methods, providing access to the methods of the token's ERC20 contract. type tokenContractorV0 struct { *contractorV0 - tokenAddr common.Address - tokenContract *erc20.IERC20 + *erc20Contractor + tokenAddr common.Address } var _ contractor = (*tokenContractorV0)(nil) @@ -361,101 +538,397 @@ func newV0TokenContractor(net dex.Network, token *dexeth.Token, acctAddr common. evmify: token.AtomicToEVM, atomize: token.EVMToAtomic, }, - tokenAddr: tokenAddr, - tokenContract: tokenContract, + tokenAddr: tokenAddr, + erc20Contractor: &erc20Contractor{ + tokenContract: tokenContract, + acct: acctAddr, + contract: swapContractAddr, + }, }, nil } -// balance exposes the read-only balanceOf method of the erc20 token contract. -func (c *tokenContractorV0) balance(ctx context.Context) (*big.Int, error) { - callOpts := &bind.CallOpts{ - From: c.acctAddr, - Context: ctx, +// estimateApproveGas estimates the gas needed to send an approve tx. +func (c *tokenContractorV0) estimateApproveGas(ctx context.Context, amount *big.Int) (uint64, error) { + return c.estimateGas(ctx, "approve", c.contractAddr, amount) +} + +// estimateTransferGas esimates the gas needed for a transfer tx. The account +// needs to have > amount tokens to use this method. +func (c *tokenContractorV0) estimateTransferGas(ctx context.Context, amount *big.Int) (uint64, error) { + return c.estimateGas(ctx, "transfer", c.acctAddr, amount) +} + +// estimateGas estimates the gas needed for methods on the ERC20 token contract. +// For estimating methods on the swap contract, use (contractorV0).estimateGas. +func (c *tokenContractorV0) estimateGas(ctx context.Context, method string, args ...interface{}) (uint64, error) { + return estimateGas(ctx, c.acctAddr, c.contractAddr, c.abi, c.cb, new(big.Int), method, args...) +} + +// value finds incoming or outgoing value for the tx to either the swap contract +// or the erc20 token contract. For the token contract, only transfer and +// transferFrom are parsed. It is not an error if this tx is a call to another +// method of the token contract, but no values will be parsed. +func (c *tokenContractorV0) value(ctx context.Context, tx *types.Transaction) (in, out uint64, err error) { + to := *tx.To() + if to == c.contractAddr { + return c.contractorV0.value(ctx, tx) + } + if to != c.tokenAddr { + return 0, 0, nil + } + + // Consider removing. We'll never be sending transferFrom transactions + // directly. + if sender, _, value, err := erc20.ParseTransferFromData(tx.Data()); err == nil && sender == c.acctAddr { + return 0, c.atomize(value), nil } - return c.tokenContract.BalanceOf(callOpts, c.acctAddr) + if _, value, err := erc20.ParseTransferData(tx.Data()); err == nil { + return 0, c.atomize(value), nil + } + + return 0, 0, nil } -// 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.tokenAddr +} + +type contractorV1 struct { + contractV1 + abi *abi.ABI + // net dex.Network + contractAddr common.Address + acctAddr common.Address + cb bind.ContractBackend + isToken bool + evmify func(uint64) *big.Int + atomize func(*big.Int) uint64 +} + +var _ contractor = (*contractorV1)(nil) + +func newV1Contractor(net dex.Network, contractAddr, acctAddr common.Address, cb bind.ContractBackend) (contractor, error) { + + c, err := swapv1.NewETHSwap(contractAddr, cb) + if err != nil { + return nil, err } - return c.tokenContract.Allowance(callOpts, c.acctAddr, c.contractAddr) + return &contractorV1{ + contractV1: c, + abi: dexeth.ABIs[1], + // net: net, + contractAddr: contractAddr, + acctAddr: acctAddr, + cb: cb, + atomize: dexeth.WeiToGwei, + }, nil } -// approve sends an approve transaction approving the linked contract to call -// transferFrom for the specified amount. -func (c *tokenContractorV0) approve(txOpts *bind.TransactOpts, amount *big.Int) (tx *types.Transaction, err error) { - return c.tokenContract.Approve(txOpts, c.contractAddr, amount) +func (c *contractorV1) status(ctx context.Context, locator []byte) (*dexeth.SwapStatus, error) { + v, err := dexeth.ParseV1Locator(locator) + if err != nil { + return nil, err + } + rec, err := c.Status(&bind.CallOpts{From: c.acctAddr}, dexeth.SwapVectorToAbigen(v)) + if err != nil { + return nil, err + } + return &dexeth.SwapStatus{ + Step: dexeth.SwapStep(rec.Step), + Secret: rec.Secret, + BlockHeight: rec.BlockNumber.Uint64(), + }, err } -// transfer calls the transfer method of the erc20 token contract. Used for -// sends or withdrawals. -func (c *tokenContractorV0) transfer(txOpts *bind.TransactOpts, addr common.Address, amount *big.Int) (tx *types.Transaction, err error) { - return c.tokenContract.Transfer(txOpts, addr, amount) +func (c *contractorV1) vector(ctx context.Context, locator []byte) (*dexeth.SwapVector, error) { + return dexeth.ParseV1Locator(locator) } -func (c *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}, 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: 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, versionedContracts) +} + +func (c *contractorV1) redeem(txOpts *bind.TransactOpts, redeems []*asset.Redemption) (*types.Transaction, error) { + versionedRedemptions := make([]swapv1.ETHSwapRedemption, 0, len(redeems)) + secretHashes := make(map[[32]byte]bool, len(redeems)) + for _, r := range redeems { + var secret [32]byte + copy(secret[:], r.Secret) + secretHash := sha256.Sum256(r.Secret) + if !bytes.Equal(secretHash[:], r.Spends.SecretHash) { + return nil, errors.New("wrong secret") + } + if secretHashes[secretHash] { + return nil, fmt.Errorf("duplicate secret hash %x", secretHash[:]) + } + secretHashes[secretHash] = true + + // Not checking version from DecodeLocator because it was already + // audited and incorrect version locator would err below anyway. + _, locator, err := dexeth.DecodeLocator(r.Spends.Contract) if err != nil { - 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, 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, dexeth.SwapVectorToAbigen(v)) +} + +func (c *contractorV1) estimateInitGas(ctx context.Context, n int) (uint64, error) { + initiations := make([]swapv1.ETHSwapVector, 0, n) + for j := 0; j < n; j++ { + var secretHash [32]byte + copy(secretHash[:], encode.RandomBytes(32)) + initiations = append(initiations, swapv1.ETHSwapVector{ + RefundTimestamp: 1, + SecretHash: secretHash, + Initiator: c.acctAddr, + Participant: common.BytesToAddress(encode.RandomBytes(20)), + Value: 1, + }) + } + + var value *big.Int + if !c.isToken { + value = dexeth.GweiToWei(uint64(n)) } - return 0, fmt.Errorf("transfer log to %s not found", c.acctAddr) + return c.estimateGas(ctx, value, "initiate", 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.contractAddr, 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", 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", 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}, dexeth.SwapVectorToAbigen(v)); err != nil || !is { + return is, err + } + return sha256.Sum256(secret[:]) == v.SecretHash, nil +} + +func (c *contractorV1) isRefundable(locator []byte) (bool, error) { + v, err := dexeth.ParseV1Locator(locator) + if err != nil { + return false, err + } + if is, err := c.IsRedeemable(&bind.CallOpts{From: c.acctAddr}, dexeth.SwapVectorToAbigen(v)); err != nil || !is { + return is, err + } + return time.Now().Unix() >= int64(v.LockTime), nil +} + +func (c *contractorV1) incomingValue(ctx context.Context, tx *types.Transaction) (uint64, error) { + if redeems, err := dexeth.ParseRedeemDataV1(tx.Data()); err == nil { + var redeemed uint64 + for _, r := range redeems { + redeemed += r.Contract.Value + } + return redeemed, nil + } + refund, err := dexeth.ParseRefundDataV1(tx.Data()) + if err != nil { + return 0, nil + } + return refund.Value, nil +} + +func (c *contractorV1) outgoingValue(tx *types.Transaction) (swapped uint64) { + if inits, err := dexeth.ParseInitiateDataV1(tx.Data()); err == nil { + for _, init := range inits { + swapped += init.Value + } + } + return +} + +func (c *contractorV1) value(ctx context.Context, tx *types.Transaction) (in, out uint64, err error) { + if *tx.To() != c.contractAddr { + return 0, 0, nil + } + + if v, err := c.incomingValue(ctx, tx); err != nil { + return 0, 0, fmt.Errorf("incomingValue error: %w", err) + } else if v > 0 { + return v, 0, nil + } + + return 0, c.outgoingValue(tx), nil +} + +type tokenContractorV1 struct { + *contractorV1 + *erc20Contractor + tokenAddr common.Address +} + +func newV1TokenContractor(net dex.Network, token *dexeth.Token, acctAddr common.Address, cb bind.ContractBackend) (tokenContractor, error) { + netToken, found := token.NetTokens[net] + if !found { + return nil, fmt.Errorf("token %s has no network %s", token.Name, net) + } + tokenAddr := netToken.Address + contract, found := netToken.SwapContracts[1] // contract version 1 + if !found { + return nil, fmt.Errorf("token %s version 0 has no network %s token info", token.Name, net) + } + swapContractAddr := contract.Address + + c, err := erc20v1.NewERC20Swap(swapContractAddr, cb) + if err != nil { + return nil, err + } + + tokenContract, err := erc20.NewIERC20(tokenAddr, cb) + if err != nil { + return nil, err + } + + if boundAddr, err := c.TokenAddress(&bind.CallOpts{ + Context: context.TODO(), + }); err != nil { + return nil, fmt.Errorf("error reading bound token address %q: %w", tokenAddr, err) + } else if boundAddr != tokenAddr { + return nil, fmt.Errorf("wrong bound address. expected %s, got %s", tokenAddr, boundAddr) + } + + return &tokenContractorV1{ + contractorV1: &contractorV1{ + contractV1: c, + abi: dexeth.ABIs[1], + // net: net, + contractAddr: swapContractAddr, + acctAddr: acctAddr, + cb: cb, + evmify: token.AtomicToEVM, + atomize: token.EVMToAtomic, + }, + tokenAddr: tokenAddr, + erc20Contractor: &erc20Contractor{ + tokenContract: tokenContract, + acct: acctAddr, + contract: swapContractAddr, + }, + }, nil +} + +// estimateApproveGas estimates the gas needed to send an approve tx. +func (c *tokenContractorV1) estimateApproveGas(ctx context.Context, amount *big.Int) (uint64, error) { + return c.estimateTokenContractGas(ctx, "approve", c.contractAddr, amount) +} + +// estimateTransferGas estimates the gas needed for a transfer tx. The account +// needs to have > amount tokens to use this method. +func (c *tokenContractorV1) estimateTransferGas(ctx context.Context, amount *big.Int) (uint64, error) { + return c.estimateTokenContractGas(ctx, "transfer", c.acctAddr, amount) +} + +func (c *tokenContractorV1) estimateTokenContractGas(ctx context.Context, method string, args ...interface{}) (uint64, error) { + return estimateGas(ctx, c.acctAddr, c.tokenAddr, erc20.ERC20ABI, c.cb, new(big.Int), method, args...) } // value finds incoming or outgoing value for the tx to either the swap contract // or the erc20 token contract. For the token contract, only transfer and // transferFrom are parsed. It is not an error if this tx is a call to another // method of the token contract, but no values will be parsed. -func (c *tokenContractorV0) value(ctx context.Context, tx *types.Transaction) (in, out uint64, err error) { +func (c *tokenContractorV1) value(ctx context.Context, tx *types.Transaction) (in, out uint64, err error) { to := *tx.To() if to == c.contractAddr { - return c.contractorV0.value(ctx, tx) + return c.contractorV1.value(ctx, tx) } if to != c.tokenAddr { return 0, 0, nil @@ -476,14 +949,41 @@ func (c *tokenContractorV0) value(ctx context.Context, tx *types.Transaction) (i // tokenAddress exposes the token_address immutable address of the token-bound // swap contract. -func (c *tokenContractorV0) tokenAddress() common.Address { +func (c *tokenContractorV1) tokenAddress() common.Address { return c.tokenAddr } +var _ contractor = (*tokenContractorV1)(nil) +var _ tokenContractor = (*tokenContractorV1)(nil) + +// readOnlyCallOpts is the CallOpts used for read-only contract method calls. +func readOnlyCallOpts(ctx context.Context) *bind.CallOpts { + return &bind.CallOpts{ + Pending: true, + Context: ctx, + } +} + +func estimateGas(ctx context.Context, from, to common.Address, abi *abi.ABI, cb bind.ContractBackend, value *big.Int, method string, args ...interface{}) (uint64, error) { + data, err := abi.Pack(method, args...) + if err != nil { + return 0, fmt.Errorf("Pack error: %v", err) + } + + return cb.EstimateGas(ctx, ethereum.CallMsg{ + From: from, + To: &to, + Data: data, + Value: value, + }) +} + var contractorConstructors = map[uint32]contractorConstructor{ 0: newV0Contractor, + 1: newV1Contractor, } var tokenContractorConstructors = map[uint32]tokenContractorConstructor{ 0: newV0TokenContractor, + 1: newV1TokenContractor, } diff --git a/client/asset/eth/contractor_test.go b/client/asset/eth/contractor_test.go index 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/eth.go b/client/asset/eth/eth.go index 31c020688c..b89905ca7f 100644 --- a/client/asset/eth/eth.go +++ b/client/asset/eth/eth.go @@ -121,6 +121,10 @@ const ( // unverified on-chain before we halt broadcasting of new txs. maxUnindexedTxs = 10 peerCountTicker = 5 * time.Second // no rpc calls here + + contractVersionERC20 = ^uint32(0) + contractVersionNewest = contractVersionERC20 // same thing + contractVersionUnknown = contractVersionERC20 - 1 ) var ( @@ -502,7 +506,7 @@ type assetWallet struct { } findRedemptionMtx sync.RWMutex - findRedemptionReqs map[[32]byte]*findRedemptionRequest + findRedemptionReqs map[string]*findRedemptionRequest approvalsMtx sync.RWMutex pendingApprovals map[uint32]*pendingApproval @@ -592,6 +596,21 @@ 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 { + switch serverVer { + case asset.VersionNewest: + return contractVersionNewest + case 0: + return 0 + case 1: + return 1 + default: + return contractVersionUnknown + } +} + func CreateEVMWallet(chainID int64, createWalletParams *asset.CreateWalletParams, compat *CompatibilityData, skipConnect bool) error { switch createWalletParams.Type { case walletTypeGeth: @@ -803,7 +822,7 @@ 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, @@ -875,7 +894,7 @@ func (w *ETHWallet) Connect(ctx context.Context) (_ *sync.WaitGroup, err error) if !exists || contractAddr == (common.Address{}) { return nil, fmt.Errorf("no contract address for version %d, net %s", ver, w.net) } - 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) } @@ -1267,7 +1286,7 @@ 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), @@ -1436,18 +1455,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) @@ -1477,7 +1497,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 @@ -1504,7 +1524,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 } @@ -1520,13 +1540,10 @@ 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) { + g := w.gases(contractVersion(serverVer)) 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 version %d", w.assetID, contractVersion(serverVer)) } return g.Swap * feeSuggestion, g.Refund * feeSuggestion, nil } @@ -1534,7 +1551,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 { @@ -1549,7 +1566,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) } @@ -1579,7 +1596,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 } @@ -1593,15 +1610,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 } @@ -1658,7 +1671,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) @@ -1696,7 +1711,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) } @@ -1710,10 +1726,10 @@ 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) + return nil, nil, 0, fmt.Errorf("error estimating swap gas: %vlaptop apart comic equip remove adult system tuna office discover toddler can keep fury aware amazing injury typical", err) } ethToLock := ord.MaxFeeRate * g.Swap * ord.MaxSwapCount @@ -1892,10 +1908,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 @@ -1924,7 +1940,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 @@ -1941,7 +1957,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 @@ -1961,8 +1977,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) } @@ -1976,10 +1992,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 @@ -2097,13 +2113,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 } @@ -2124,7 +2140,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 @@ -2133,8 +2149,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 @@ -2172,9 +2188,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) } @@ -2206,7 +2222,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) } @@ -2214,15 +2230,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, w.addr), + contractVer: contractVer, + contractAddr: w.versionedContracts[contractVer].String(), }) } @@ -2237,6 +2251,26 @@ func (w *ETHWallet) Swap(swaps *asset.Swaps) ([]asset.Receipt, asset.Coin, uint6 return receipts, change, fees, nil } +// acToLocator converts the asset.Contract to a version-specific locator. +func acToLocator(contractVer uint32, swap *asset.Contract, from common.Address) []byte { + switch contractVer { + case 0: + return swap.SecretHash + case 1: + var secretHash [32]byte + copy(secretHash[:], swap.SecretHash) + return (&dexeth.SwapVector{ + From: from, + To: common.HexToAddress(swap.Address), + Value: swap.Value, + SecretHash: secretHash, + LockTime: swap.LockTime, + }).Locator() + default: + panic("need to add a version in acToLocator") + } +} + // Swap sends the swaps in a single transaction. The fees used returned are the // max fees that will possibly be used, since in ethereum with EIP-1559 we cannot // know exactly how much fees will be used. @@ -2269,7 +2303,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) } @@ -2295,7 +2330,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) } @@ -2309,14 +2344,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.addr), + contractVer: contractVer, contractAddr: contractAddr, }) } @@ -2367,16 +2400,19 @@ func (w *assetWallet) Redeem(form *asset.RedeemForm, feeWallet *assetWallet, non var contractVer uint32 // require a consistent version since this is a single transaction secrets := make([][32]byte, 0, n) + locators := make([][]byte, 0, n) var redeemedValue uint64 for i, redemption := range form.Redemptions { // NOTE: redemption.Spends.SecretHash is a dup of the hash extracted // from redemption.Spends.Contract. Even for scriptable UTXO assets, the // redeem script in this Contract field is redundant with the SecretHash // field as ExtractSwapDetails can be applied to extract the hash. - ver, secretHash, err := dexeth.DecodeContractData(redemption.Spends.Contract) + ver, locator, err := dexeth.DecodeLocator(redemption.Spends.Contract) if err != nil { return fail(fmt.Errorf("Redeem: invalid versioned swap contract data: %w", err)) } + + locators = append(locators, locator) if i == 0 { contractVer = ver } else if contractVer != ver { @@ -2391,23 +2427,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 += vector.Value } g := w.gases(contractVer) @@ -2515,7 +2551,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, contractVersionERC20, func(c tokenContractor) error { bal, err = c.balance(w.ctx) return err }) @@ -2523,8 +2559,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, contractVersionERC20, func(c tokenContractor) error { allowance, err = c.allowance(w.ctx) return err }) @@ -2577,7 +2613,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) } @@ -2750,8 +2786,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") } @@ -2796,8 +2832,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") } @@ -2806,8 +2842,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") } @@ -2883,32 +2919,81 @@ func (w *assetWallet) AuditContract(coinID, contract, serializedTx dex.Bytes, re return nil, fmt.Errorf("AuditContract: coin id != txHash - coin id: %x, txHash: %s", coinID, tx.Hash()) } - version, secretHash, err := dexeth.DecodeContractData(contract) + version, locator, err := dexeth.DecodeLocator(contract) if err != nil { return nil, fmt.Errorf("AuditContract: failed to decode contract data: %w", err) } - initiations, err := dexeth.ParseInitiateData(tx.Data(), version) - if err != nil { - return nil, fmt.Errorf("AuditContract: failed to parse initiate data: %w", err) - } + var val uint64 + var participant string + var lockTime time.Time + var secretHashB []byte + switch version { + case 0: + initiations, err := dexeth.ParseInitiateDataV0(tx.Data()) + if err != nil { + return nil, fmt.Errorf("AuditContract: failed to parse initiate data: %w", err) + } - initiation, ok := initiations[secretHash] - if !ok { - return nil, errors.New("AuditContract: tx does not initiate secret hash") + 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") + } + val = w.atomize(initiation.Value) + participant = initiation.Participant.String() + lockTime = initiation.LockTime + secretHashB = secretHash[:] + case 1: + vec, err := dexeth.ParseV1Locator(locator) + if err != nil { + return nil, err + } + txVectors, err := dexeth.ParseInitiateDataV1(tx.Data()) + if err != nil { + return nil, fmt.Errorf("AuditContract: failed to parse initiate data: %w", err) + } + txVec, ok := txVectors[vec.SecretHash] + if !ok { + return nil, errors.New("AuditContract: tx does not initiate secret hash") + } + // Check vector equivalence. Secret hash equivalence is implied by the + // vectors presence in the map returned from ParseInitiateData. + if vec.Value != txVec.Value { + return nil, errors.New("tx data value doesn't match reported locator data") + } + if vec.To != txVec.To { + return nil, errors.New("tx to address doesn't match reported locator data") + } + if vec.From != txVec.From { + return nil, errors.New("tx from address doesn't match reported locator data") + } + if vec.LockTime != txVec.LockTime { + return nil, errors.New("tx lock time doesn't match reported locator data") + } + val = vec.Value + participant = vec.To.String() + lockTime = time.Unix(int64(vec.LockTime), 0) + secretHashB = vec.SecretHash[:] + default: + return nil, fmt.Errorf("unknown contract version %d", version) } coin := &coin{ id: txHash, - value: w.atomize(initiation.Value), + value: val, } return &asset.AuditInfo{ - Recipient: initiation.Participant.Hex(), - Expiration: initiation.LockTime, + Recipient: participant, + Expiration: lockTime, Coin: coin, Contract: contract, - SecretHash: secretHash[:], + SecretHash: secretHashB, }, nil } @@ -2926,26 +3011,25 @@ func (w *assetWallet) LockTimeExpired(ctx context.Context, lockTime time.Time) ( // ContractLockTimeExpired returns true if the specified contract's locktime has // expired, making it possible to issue a Refund. func (w *assetWallet) ContractLockTimeExpired(ctx context.Context, contract dex.Bytes) (bool, time.Time, error) { - contractVer, secretHash, err := dexeth.DecodeContractData(contract) + contractVer, locator, err := dexeth.DecodeLocator(contract) if err != nil { return false, time.Time{}, err } - swap, err := w.swap(ctx, secretHash, contractVer) + status, vec, err := w.statusAndVector(ctx, locator, contractVer) if err != nil { return false, time.Time{}, err - } - - // Time is not yet set for uninitiated swaps. - if swap.State == dexeth.SSNone { + } else if status.Step == dexeth.SSNone { return false, time.Time{}, asset.ErrSwapNotInitiated } - expired, err := w.LockTimeExpired(ctx, swap.LockTime) + lockTime := time.Unix(int64(vec.LockTime), 0) + + expired, err := w.LockTimeExpired(ctx, lockTime) if err != nil { return false, time.Time{}, err } - return expired, swap.LockTime, nil + return expired, lockTime, nil } // findRedemptionResult is used internally for queued findRedemptionRequests. @@ -2963,22 +3047,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 } @@ -2995,13 +3078,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.DecodeLocator(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 } @@ -3016,14 +3099,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() @@ -3034,11 +3119,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 { @@ -3048,64 +3133,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.DecodeLocator(contract) if err != nil { return nil, fmt.Errorf("Refund: failed to decode contract: %w", err) } - swap, err := w.swap(w.ctx, secretHash, version) + status, 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) @@ -3114,7 +3196,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, vector.Value, maxFeeRate, tipRate, contractVer) if err != nil { return nil, fmt.Errorf("Refund: failed to call refund: %w", err) } @@ -3172,7 +3254,7 @@ func (w *ETHWallet) EstimateRegistrationTxFee(feeRate uint64) uint64 { // EstimateRegistrationTxFee returns an estimate for the tx fee needed to // pay the registration fee using the provided feeRate. func (w *TokenWallet) EstimateRegistrationTxFee(feeRate uint64) uint64 { - g := w.gases(contractVersionNewest) + g := w.gases(contractVersionERC20) if g == nil { w.log.Errorf("no gas table") return math.MaxUint64 @@ -3249,7 +3331,7 @@ func (w *TokenWallet) canSend(value uint64, verifyBalance, isPreEstimate bool) ( } maxFeeRateGwei := dexeth.WeiToGweiCeil(maxFeeRate) - g := w.gases(contractVersionNewest) + g := w.gases(contractVersionERC20) if g == nil { return 0, nil, nil, fmt.Errorf("gas table not found") } @@ -3355,7 +3437,7 @@ func (w *ETHWallet) RestorationInfo(seed []byte) ([]*asset.WalletRestoration, er // SwapConfirmations gets the number of confirmations and the spend status // for the specified swap. func (w *assetWallet) SwapConfirmations(ctx context.Context, coinID dex.Bytes, contract dex.Bytes, _ time.Time) (confs uint32, spent bool, err error) { - contractVer, secretHash, err := dexeth.DecodeContractData(contract) + contractVer, secretHash, err := dexeth.DecodeLocator(contract) if err != nil { return 0, false, err } @@ -3363,24 +3445,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 } @@ -3507,37 +3599,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 @@ -3552,16 +3613,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.DecodeLocator(contractData) if err != nil { return 0, nil, err } + var txHash common.Hash + copy(txHash[:], coinID) + tip := w.tipHeight() var blockNum uint64 @@ -3577,7 +3637,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 } @@ -3595,21 +3655,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 // TODO: Is this really necessary? + locators = append(locators, copyK[:]) + } + } else { + redeems, err := dexeth.ParseRedeemDataV0(tx.Data()) + if err != nil { + return nil, nil, fmt.Errorf("invalid redeem data: %v", err) + } + locators = make([][]byte, 0, len(redeems)) + for k := range redeems { + copyK := k + locators = append(locators, copyK[:]) + } + } + return locators, locators, nil + case 1: + if isInit { + vectors, err := dexeth.ParseInitiateDataV1(tx.Data()) + if err != nil { + return nil, nil, fmt.Errorf("invalid initiate data: %v", err) + } + locators = make([][]byte, 0, len(vectors)) + secretHashes = make([][]byte, 0, len(vectors)) + for _, vec := range vectors { + locators = append(locators, vec.Locator()) + secretHashes = append(secretHashes, vec.SecretHash[:]) + } + } else { + redeems, err := dexeth.ParseRedeemDataV1(tx.Data()) + if err != nil { + return nil, nil, fmt.Errorf("invalid redeem data: %v", err) + } + locators = make([][]byte, 0, len(redeems)) + secretHashes = make([][]byte, 0, len(redeems)) + for secretHash, r := range redeems { + locators = append(locators, r.Contract.Locator()) + secretHashes = append(secretHashes, secretHash[:]) + } + } + return locators, secretHashes, nil + default: + return nil, nil, fmt.Errorf("unknown server version %d", contractVer) } - return dexeth.WeiToGweiCeil(bigFees), secretHashes, nil } // RegFeeConfirmations gets the number of confirmations for the specified @@ -3806,7 +3929,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.DecodeLocator(redemption.Spends.Contract) if err != nil { return nil, fmt.Errorf("failed to decode contract data: %w", err) } @@ -3861,11 +3984,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 @@ -3939,15 +4062,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) } } } @@ -4265,7 +4389,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(contractVersionERC20) if g == nil { return nil, fmt.Errorf("no gas table") } @@ -4278,7 +4402,7 @@ func (w *TokenWallet) sendToAddr(addr common.Address, amt uint64, maxFeeRate, ti if addr == w.addr { txType = asset.SelfSend } - return tx, txType, amt, w.withTokenContractor(w.assetID, contractVersionNewest, func(c tokenContractor) error { + return tx, txType, amt, w.withTokenContractor(w.assetID, contractVersionERC20, func(c tokenContractor) error { tx, err = c.transfer(txOpts, addr, w.evmify(amt)) if err != nil { return err @@ -4289,10 +4413,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 }) } @@ -4336,17 +4477,17 @@ 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 }) } @@ -4384,7 +4525,8 @@ func (w *assetWallet) loadContractors() error { // withContractor runs the provided function with the versioned contractor. func (w *assetWallet) withContractor(contractVer uint32, f func(contractor) error) error { - if contractVer == contractVersionNewest { + if contractVer == contractVersionERC20 { + // For ERC02 methods, use the most recent contractor version. var bestVer uint32 var bestContractor contractor for ver, c := range w.contractors { @@ -4416,7 +4558,7 @@ func (w *assetWallet) withTokenContractor(assetID, ver uint32, f func(tokenContr // estimateApproveGas estimates the gas required for a transaction approving a // spender for an ERC20 contract. func (w *assetWallet) estimateApproveGas(newGas *big.Int) (gas uint64, err error) { - return gas, w.withTokenContractor(w.assetID, contractVersionNewest, func(c tokenContractor) error { + return gas, w.withTokenContractor(w.assetID, contractVersionERC20, func(c tokenContractor) error { gas, err = c.estimateApproveGas(w.ctx, newGas) return err }) @@ -4425,7 +4567,7 @@ func (w *assetWallet) estimateApproveGas(newGas *big.Int) (gas uint64, err error // estimateTransferGas estimates the gas needed for a token transfer call to an // ERC20 contract. func (w *assetWallet) estimateTransferGas(val uint64) (gas uint64, err error) { - return gas, w.withTokenContractor(w.assetID, contractVersionNewest, func(c tokenContractor) error { + return gas, w.withTokenContractor(w.assetID, contractVersionERC20, func(c tokenContractor) error { gas, err = c.estimateTransferGas(w.ctx, w.evmify(val)) return err }) @@ -4479,7 +4621,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) @@ -4490,30 +4632,34 @@ func (w *assetWallet) refund(secretHash [32]byte, amt uint64, maxFeeRate, tipRat return nil, 0, 0, err } return tx, asset.Refund, amt, 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 }) } @@ -4521,7 +4667,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 { @@ -5384,7 +5529,7 @@ 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) } @@ -5868,7 +6013,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, log) } // getGasEstimate is used to get a gas table for an asset's contract(s). The @@ -5883,7 +6028,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, log dex.Logger) (err error) { tc, isToken := c.(tokenContractor) @@ -6027,6 +6172,7 @@ func getGasEstimates(ctx context.Context, cl, acl ethFetcher, c contractor, ac t 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 @@ -6036,7 +6182,7 @@ func getGasEstimates(ctx context.Context, cl, acl ethFetcher, c contractor, ac t Address: cl.address().String(), // trading with self Value: 1, SecretHash: secretHash[:], - LockTime: uint64(time.Now().Add(-time.Hour).Unix()), + LockTime: uint64(lockTime.Unix()), }) secrets = append(secrets, secret) } @@ -6070,9 +6216,7 @@ func getGasEstimates(ctx context.Context, cl, acl ethFetcher, c contractor, ac t 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], cl.address())) if err != nil { return fmt.Errorf("error estimate refund gas: %w", err) } @@ -6083,6 +6227,9 @@ 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, cl.address())), SecretHash: contract.SecretHash, }, Secret: secrets[i][:], diff --git a/client/asset/eth/eth_test.go b/client/asset/eth/eth_test.go index 014b7f6642..6b78da6d64 100644 --- a/client/asset/eth/eth_test.go +++ b/client/asset/eth/eth_test.go @@ -47,17 +47,30 @@ 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, + SwapSize: ethGasesV0.Swap, + SwapSizeBase: ethGasesV0.Swap, + RedeemSize: ethGasesV0.Redeem, + SwapConf: 1, + } + + tETHV1 = &dex.Asset{ + Version: 1, ID: 60, Symbol: "ETH", MaxFeeRate: 100, - SwapSize: ethGases.Swap, - SwapSizeBase: ethGases.Swap, - RedeemSize: ethGases.Redeem, + SwapSize: ethGasesV1.Swap, + SwapSizeBase: ethGasesV1.Swap, + RedeemSize: ethGasesV1.Redeem, SwapConf: 1, } @@ -71,13 +84,24 @@ var ( SwapConf: 1, } - tToken = &dex.Asset{ + tTokenV0 = &dex.Asset{ ID: usdcTokenID, Symbol: "usdc.eth", Version: 0, - SwapSize: tokenGases.Swap, - SwapSizeBase: tokenGases.Swap, - RedeemSize: tokenGases.Redeem, + SwapSize: tokenGasesV0.Swap, + SwapSizeBase: tokenGasesV0.Swap, + RedeemSize: tokenGasesV0.Redeem, + MaxFeeRate: 20, + SwapConf: 1, + } + + tTokenV1 = &dex.Asset{ + ID: usdcTokenID, + Symbol: "dextt.eth", + Version: 1, + SwapSize: tokenGasesV1.Swap, + SwapSizeBase: tokenGasesV1.Swap, + RedeemSize: tokenGasesV1.Redeem, MaxFeeRate: 20, SwapConf: 1, } @@ -338,6 +362,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 @@ -353,6 +380,76 @@ type tContractor struct { } } +func (c *tContractor) status(ctx context.Context, locator []byte) (*dexeth.SwapStatus, error) { + if c.swapErr != nil { + return nil, c.swapErr + } + vector, err := c.vector(ctx, locator) + if err != nil { + return nil, err + } + swap, ok := c.swapMap[vector.SecretHash] + if !ok { + return nil, errors.New("swap not in map") + } + s := &dexeth.SwapStatus{ + Step: swap.State, + Secret: swap.Secret, + BlockHeight: swap.BlockHeight, + } + return s, nil +} + +func (c *tContractor) vector(ctx context.Context, locator []byte) (*dexeth.SwapVector, error) { + if c.swapErr != nil { + return nil, c.swapErr + } + if len(locator) == dexeth.LocatorV1Length { + return dexeth.ParseV1Locator(locator) + } + var secretHash [32]byte + copy(secretHash[:], locator) + swap, ok := c.swapMap[secretHash] + if !ok { + return nil, errors.New("swap not in map") + } + v := &dexeth.SwapVector{ + From: swap.Participant, + To: swap.Initiator, + Value: dexeth.WeiToGwei(swap.Value), + SecretHash: secretHash, + LockTime: uint64(swap.LockTime.Unix()), + } + return v, nil +} + +func (c *tContractor) statusAndVector(ctx context.Context, locator []byte) (*dexeth.SwapStatus, *dexeth.SwapVector, error) { + if c.swapErr != nil { + return nil, nil, c.swapErr + } + vector, err := c.vector(ctx, locator) + if err != nil { + return nil, nil, err + } + swap, ok := c.swapMap[vector.SecretHash] + if !ok { + return nil, nil, errors.New("swap not in map") + } + v := &dexeth.SwapVector{ + From: swap.Participant, + To: swap.Initiator, + Value: dexeth.WeiToGwei(swap.Value), + SecretHash: vector.SecretHash, + LockTime: uint64(swap.LockTime.Unix()), + } + s := &dexeth.SwapStatus{ + Step: swap.State, + Secret: swap.Secret, + BlockHeight: swap.BlockHeight, + } + return s, v, nil +} + func (c *tContractor) swap(ctx context.Context, secretHash [32]byte) (*dexeth.SwapState, error) { if c.swapErr != nil { return nil, c.swapErr @@ -374,8 +471,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 } @@ -384,22 +485,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 } @@ -1104,7 +1233,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), @@ -1117,7 +1246,7 @@ func newTestNode(assetID uint32) *tMempoolNode { allow: new(big.Int), } if assetID != BipID { - ttc.tContractor.gasEstimates = &tokenGases + ttc.tContractor.gasEstimates = &tokenGasesV0 c = ttc } @@ -1195,8 +1324,8 @@ 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), + contractors: map[uint32]contractor{0: c, 1: c}, + findRedemptionReqs: make(map[string]*findRedemptionRequest), evmify: dexeth.GweiToWei, atomize: dexeth.WeiToGwei, pendingTxCheckBal: new(big.Int), @@ -1220,7 +1349,7 @@ 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}, + contractors: map[uint32]contractor{0: node.tContractor, 1: node.tContractor}, assetID: BipID, atomize: dexeth.WeiToGwei, pendingApprovals: make(map[uint32]*pendingApproval), @@ -1376,13 +1505,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) } } } @@ -1564,31 +1693,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: 1, + SecretHash: secretHash, + LockTime: uint64(time.Now().Unix()), + } + v1Contract := dexeth.EncodeContractData(1, v1Vector.Locator()) + + ss := &dexeth.SwapState{ + Value: dexeth.GweiToWei(1), + } - v0Contractor.swapMap[secretHash] = ss - v1Contractor.swapMap[secretHash] = ss + node.tContractor.swapMap[secretHash] = ss tests := []struct { name string @@ -1601,7 +1744,7 @@ func testRefund(t *testing.T, assetID uint32) { wantZeroHash bool swapStep dexeth.SwapStep swapErr error - useV1Gases bool + v1 bool }{ { name: "ok", @@ -1614,7 +1757,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", @@ -1655,11 +1798,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{} } @@ -1741,11 +1883,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) @@ -1921,6 +2063,7 @@ func testFundOrderReturnCoinsFundingCoins(t *testing.T, assetID uint32) { defer shutdown2() eth2.node = node eth2.contractors[0] = node.tokenContractor + eth2.contractors[1] = node.tokenContractor node.tokenContractor.bal = dexeth.GweiToWei(walletBalanceGwei) // Test reloading coins from first order @@ -2036,10 +2179,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 @@ -2283,13 +2426,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 @@ -2338,9 +2480,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", @@ -2351,9 +2493,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", @@ -2378,9 +2520,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", @@ -2391,9 +2533,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", @@ -2415,11 +2557,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) @@ -2427,7 +2570,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 { @@ -2441,7 +2584,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, }) @@ -2453,7 +2596,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 @@ -2491,6 +2634,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{}) @@ -2500,7 +2650,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()) } } @@ -2526,12 +2677,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) { @@ -2569,16 +2715,17 @@ func testSwap(t *testing.T, assetID uint32) { testName, receipt.Coin().Value(), contract.Value) } contractData := receipt.Contract() - ver, secretHash, err := dexeth.DecodeContractData(contractData) + contractVer, locator, err := dexeth.DecodeLocator(contractData) if err != nil { t.Fatalf("failed to decode contract data: %v", err) } - if swaps.Version != ver { + if swaps.Version != contractVer { t.Fatal("wrong contract version") } - if !bytes.Equal(contract.SecretHash, secretHash[:]) { - t.Fatalf("%v, contract: %x != secret hash in input: %x", - testName, receipt.Contract(), secretHash) + chkLocator := acToLocator(contractVer, contract, node.addr) + if !bytes.Equal(locator, chkLocator) { + t.Fatalf("%v, contract: %x != locator in input: %x", + testName, receipt.Contract(), locator) } totalCoinValue += receipt.Coin().Value() @@ -2648,10 +2795,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, @@ -2739,14 +2882,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, } @@ -2764,7 +2926,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 { @@ -2785,41 +2948,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.contractors[0].(*tContractor) + } else { + contractor = eth.contractors[0].(*tTokenContractor).tContractor } - eth.contractors[1] = c + contractor.redeemTx = types.NewTx(&types.DynamicFeeTx{Data: []byte{1, 2, 3}}) + now := time.Now() + const value = 1e9 - addSwapToSwapMap := func(secretHash [32]byte, value uint64, step dexeth.SwapStep) { + addSwapToSwapMap := func(secretHash [32]byte, step dexeth.SwapStep) { swap := dexeth.SwapState{ BlockHeight: 1, - LockTime: time.Now(), + LockTime: now, Initiator: testAddressB, Participant: testAddressA, Value: dexeth.GweiToWei(value), State: step, } - contractorV1.swapMap[secretHash] = &swap + contractor.swapMap[secretHash] = &swap } numSecrets := 3 @@ -2833,20 +3004,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. @@ -2881,6 +3054,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 @@ -2893,6 +3100,7 @@ func testRedeem(t *testing.T, assetID uint32) { redeemGasOverride *uint64 expectedGasFeeCap *big.Int expectError bool + v1 bool }{ { name: "ok", @@ -2902,32 +3110,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, @@ -2937,28 +3137,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, }, }, @@ -2971,28 +3150,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, }, }, @@ -3004,28 +3162,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, }, }, @@ -3037,28 +3174,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, }, }, @@ -3071,28 +3187,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, }, }, @@ -3105,28 +3200,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, }, }, @@ -3142,28 +3216,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, }, }, @@ -3174,28 +3227,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, }, }, @@ -3207,18 +3239,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, }, }, @@ -3229,18 +3250,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, }, }, @@ -3258,15 +3268,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 { @@ -3284,10 +3299,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) @@ -3296,44 +3309,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.DecodeLocator(redemption.Spends.Contract) if err != nil { - t.Fatalf("DecodeContractData: %v", err) + t.Fatalf("DecodeLocator: %v", err) + } + var secretHash [32]byte + if test.v1 { + v, _ := dexeth.ParseV1Locator(locator) + secretHash = v.SecretHash + } else { + copy(secretHash[:], locator) } // secretHash should equal redemption.Spends.SecretHash, but it's // not part of the Redeem code, just the test input consistency. - swap := contractorV1.swapMap[secretHash] + swap := contractor.swapMap[secretHash] totalSwapValue += dexeth.WeiToGwei(swap.Value) } if out.Value() != totalSwapValue { - t.Fatalf("expected coin value to be %d but got %d", - totalSwapValue, out.Value()) + t.Fatalf("%s: expected coin value to be %d but got %d", + test.name, totalSwapValue, out.Value()) } // Check that gas limit in the transaction is as expected var expectedGasLimit uint64 - // if test.redeemGasOverride == nil { - if assetID == BipID { - expectedGasLimit = ethGases.Redeem * uint64(len(test.form.Redemptions)) + 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 @@ -3341,7 +3362,6 @@ func TestMaxOrder(t *testing.T) { balErr error lotSize uint64 maxFeeRate uint64 - feeSuggestion uint64 token bool parentBal uint64 wantErr bool @@ -3351,113 +3371,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) @@ -3465,7 +3496,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 { @@ -3473,11 +3505,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, @@ -3583,7 +3620,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, @@ -3603,7 +3640,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, @@ -3622,7 +3659,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, @@ -3641,13 +3678,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, @@ -3714,7 +3751,7 @@ func testAuditContract(t *testing.T, assetID uint32) { t.Fatalf(`"%v": expected contract %x != actual %x`, test.name, test.contract, auditInfo.Contract) } - _, expectedSecretHash, err := dexeth.DecodeContractData(test.contract) + _, expectedSecretHash, err := dexeth.DecodeLocator(test.contract) if err != nil { t.Fatalf(`"%v": failed to decode versioned bytes: %v`, test.name, err) } @@ -3858,7 +3895,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 @@ -3875,7 +3914,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 @@ -3904,7 +3943,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) } @@ -4074,6 +4113,7 @@ func TestLocktimeExpired(t *testing.T) { state := &dexeth.SwapState{ LockTime: time.Now(), State: dexeth.SSInitiated, + Value: dexeth.GweiToWei(1), } header := &types.Header{ @@ -4147,10 +4187,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 @@ -4194,7 +4235,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 @@ -4258,7 +4299,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() { @@ -4297,21 +4338,20 @@ func testRefundReserves(t *testing.T, assetID uint32) { feeWallet := eth gasesV0 := dexeth.VersionedGases[0] gasesV1 := &dexeth.Gases{Refund: 1e6} - assetV0 := *tETH - - assetV1 := *tETH + assetV0 := *tETHV0 + assetV1 := *tETHV0 if assetID == BipID { eth.versionedGases[1] = gasesV1 } else { feeWallet = node.tokenParent - assetV0 = *tToken - assetV1 = *tToken - tokenContracts := eth.tokens[usdcTokenID].NetTokens[dex.Simnet].SwapContracts + assetV0 = *tTokenV0 + assetV1 = *tTokenV0 + tokenContracts := dexeth.Tokens[usdcTokenID].NetTokens[dex.Simnet].SwapContracts + gasesV0 = &tokenGasesV0 tc := *tokenContracts[0] tc.Gas = *gasesV1 tokenContracts[1] = &tc defer delete(tokenContracts, 1) - gasesV0 = &tokenGases eth.versionedGases[0] = gasesV0 eth.versionedGases[1] = gasesV1 node.tokenContractor.bal = dexeth.GweiToWei(1e9) @@ -4395,20 +4435,22 @@ func testRedemptionReserves(t *testing.T, assetID uint32) { node.tContractor.swapMap[secretHash] = &dexeth.SwapState{} gasesV1 := &dexeth.Gases{Redeem: 1e6, RedeemAdd: 85e5} + eth.versionedGases[1] = gasesV1 gasesV0 := dexeth.VersionedGases[0] - assetV0 := *tETH - assetV1 := *tETH + 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 + tokenContracts := dexeth.Tokens[usdcTokenID].NetTokens[dex.Simnet].SwapContracts + gasesV0 = &tokenGasesV0 + tc := *tokenContracts[0] + tc.Gas = *gasesV1 + tokenContracts[1] = &tc + defer delete(tokenContracts, 1) } assetV0.MaxFeeRate = 45 @@ -4515,7 +4557,7 @@ func testSend(t *testing.T, assetID uint32) { maxFeeRate, _, _ := eth.recommendedMaxFeeRate(eth.ctx) ethFees := dexeth.WeiToGwei(maxFeeRate) * defaultSendGasLimit - tokenFees := dexeth.WeiToGwei(maxFeeRate) * tokenGases.Transfer + tokenFees := dexeth.WeiToGwei(maxFeeRate) * tokenGasesV0.Transfer const val = 10e9 const testAddr = "dd93b447f7eBCA361805eBe056259853F3912E04" @@ -4603,7 +4645,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[:], } @@ -4705,7 +4747,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) @@ -4813,7 +4855,7 @@ func testEstimateSendTxFee(t *testing.T, assetID uint32) { maxFeeRate, _, _ := eth.recommendedMaxFeeRate(eth.ctx) ethFees := dexeth.WeiToGwei(maxFeeRate) * defaultSendGasLimit - tokenFees := dexeth.WeiToGwei(maxFeeRate) * tokenGases.Transfer + tokenFees := dexeth.WeiToGwei(maxFeeRate) * tokenGasesV0.Transfer ethFees = ethFees * 12 / 10 tokenFees = tokenFees * 12 / 10 @@ -4961,7 +5003,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.go b/client/asset/eth/nodeclient.go index 1331debdbd..25358dba87 100644 --- a/client/asset/eth/nodeclient.go +++ b/client/asset/eth/nodeclient.go @@ -29,7 +29,9 @@ import ( "github.com/ethereum/go-ethereum/consensus/misc" ) -const contractVersionNewest = ^uint32(0) +const ( + approveGas = 4e5 +) var ( // https://github.com/ethereum/go-ethereum/blob/16341e05636fd088aa04a27fca6dc5cda5dbab8f/eth/backend.go#L110-L113 @@ -423,7 +425,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 != contractVersionERC20 { return versionedGases[contractVer] } var bestVer uint32 diff --git a/client/asset/eth/nodeclient_harness_test.go b/client/asset/eth/nodeclient_harness_test.go index 2c25d0b0fa..c6e1b22a85 100644 --- a/client/asset/eth/nodeclient_harness_test.go +++ b/client/asset/eth/nodeclient_harness_test.go @@ -27,6 +27,7 @@ import ( "fmt" "math" "math/big" + "math/rand" "os" "os/exec" "os/signal" @@ -71,6 +72,7 @@ const ( var ( homeDir = os.Getenv("HOME") + harnessCtlDir = filepath.Join(homeDir, "dextest", "eth", "harness-ctl") simnetWalletDir = filepath.Join(homeDir, "dextest", "eth", "client_rpc_tests", "simnet") participantWalletDir = filepath.Join(homeDir, "dextest", "eth", "client_rpc_tests", "participant") testnetWalletDir string @@ -96,8 +98,13 @@ var ( 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 @@ -122,6 +129,9 @@ var ( testnetParticipantWalletSeed string usdcID, _ = dex.BipSymbolID("usdc.eth") masterToken *dexeth.Token + + v1 bool + ver uint32 ) func newContract(stamp uint64, secretHash [32]byte, val uint64) *asset.Contract { @@ -133,10 +143,34 @@ func newContract(stamp uint64, secretHash [32]byte, val uint64) *asset.Contract } } -func newRedeem(secret, secretHash [32]byte) *asset.Redemption { +func acLocator(c *asset.Contract) []byte { + return makeLocator(bytesToArray(c.SecretHash), c.Value, c.LockTime) +} + +func makeLocator(secretHash [32]byte, valg, lockTime uint64) []byte { + if ver == 1 { + return (&dexeth.SwapVector{ + From: ethClient.address(), + To: participantEthClient.address(), + Value: valg, + SecretHash: secretHash, + LockTime: lockTime, + }).Locator() + } + return secretHash[:] +} + +func newRedeem(secret, secretHash [32]byte, valg, lockTime uint64) *asset.Redemption { return &asset.Redemption{ Spends: &asset.AuditInfo{ SecretHash: secretHash[:], + Recipient: participantEthClient.address().String(), + Expiration: time.Unix(int64(lockTime), 0), + Coin: &coin{ + // id: txHash, + value: valg, + }, + Contract: dexeth.EncodeContractData(ver, makeLocator(secretHash, valg, lockTime)), }, Secret: secret[:], } @@ -349,18 +383,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 + tokenGases = &dexeth.Tokens[usdcID].NetTokens[dex.Simnet].SwapContracts[ver].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("ETH swap contract address is %v\n", dexeth.ContractAddresses[ver][dex.Simnet]) + fmt.Printf("Token swap contract addr is %v\n", token.SwapContracts[ver].Address) fmt.Printf("Test token contract addr is %v\n", token.Address) - ethSwapContractAddr = dexeth.ContractAddresses[contractVer][dex.Simnet] + ethSwapContractAddr = dexeth.ContractAddresses[ver][dex.Simnet] initiatorProviders, participantProviders := rpcEndpoints(dex.Simnet) @@ -395,29 +427,15 @@ func runSimnet(m *testing.M) (int, error) { simnetAddr = simnetAcct.Address participantAddr = participantAcct.Address - contractAddr, exists := dexeth.ContractAddresses[contractVer][dex.Simnet] + contractAddr, exists := dexeth.ContractAddresses[ver][dex.Simnet] if !exists || contractAddr == (common.Address{}) { - return 1, fmt.Errorf("no contract address for 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) + return 1, fmt.Errorf("no contract address for version %d", ver) } - // 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) + if v1 { + prepareV1SimnetContractors() + } else { + prepareV0SimnetContractors() } if err := ethClient.unlock(pw); err != nil { @@ -428,11 +446,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 @@ -479,9 +492,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[ver].Gas if testnetWalletSeed == "" || testnetParticipantWalletSeed == "" { return 1, errors.New("testnet seeds not set") } @@ -495,8 +507,8 @@ 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] + secPerBlock = testnetSecPerBlock + ethSwapContractAddr = dexeth.ContractAddresses[ver][dex.Testnet] fmt.Printf("ETH swap contract address is %v\n", ethSwapContractAddr) initiatorRPC, participantRPC := rpcEndpoints(dex.Testnet) @@ -551,10 +563,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, tokenCtor := newV0Contractor, newV0TokenContractor + if ver == 1 { + ctor = newV1Contractor, newV1TokenContractor + } + + if simnetContractor, err = ctor(dex.Testnet, 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, participantAddr, participantEthClient.contractBackend()); err != nil { return 1, fmt.Errorf("participant newV0Contractor error: %w", err) } @@ -565,7 +582,7 @@ 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 { + if simnetTokenContractor, err = tokenCtor(dex.Testnet, dexeth.Tokens[usdcID], simnetAddr, ethClient.contractBackend()); err != nil { return 1, fmt.Errorf("newV0TokenContractor error: %w", err) } @@ -574,7 +591,7 @@ func runTestnet(m *testing.M) (int, error) { // (*BoundContract).Call while calling (*ERC20Swap).TokenAddress. time.Sleep(time.Second) - if participantTokenContractor, err = newV0TokenContractor(dex.Testnet, dexeth.Tokens[usdcID], participantAddr, participantEthClient.contractBackend()); err != nil { + if participantTokenContractor, err = tokenCtor(dex.Testnet, dexeth.Tokens[usdcID], participantAddr, participantEthClient.contractBackend()); err != nil { return 1, fmt.Errorf("participant newV0TokenContractor error: %w", err) } @@ -594,6 +611,37 @@ func runTestnet(m *testing.M) (int, error) { return code, nil } +func prepareV0SimnetContractors() (err error) { + return prepareSimnetContractors(newV0Contractor, newV0TokenContractor) +} + +func prepareV1SimnetContractors() (err error) { + return prepareSimnetContractors(newV1Contractor, newV1TokenContractor) +} + +func prepareSimnetContractors(c contractorConstructor, tc tokenContractorConstructor) (err error) { + if simnetContractor, err = c(dex.Simnet, simnetAddr, ethClient.contractBackend()); err != nil { + return fmt.Errorf("new contractor error: %w", err) + } + if participantContractor, err = c(dex.Simnet, participantAddr, participantEthClient.contractBackend()); err != nil { + return fmt.Errorf("participant new contractor error: %w", err) + } + + if simnetTokenContractor, err = tc(dex.Simnet, testTokenID, simnetAddr, ethClient.contractBackend()); err != nil { + return fmt.Errorf("new token contractor error: %w", err) + } + + // I don't know why this is needed for the participant client but not + // the initiator. Without this, we'll get a bind.ErrNoCode from + // (*BoundContract).Call while calling (*ERC20Swap).TokenAddress. + time.Sleep(time.Second) + + if participantTokenContractor, err = tc(dex.Simnet, testTokenID, participantAddr, participantEthClient.contractBackend()); err != nil { + return fmt.Errorf("participant new token contractor error: %w", err) + } + return +} + func useTestnet() error { isTestnet = true b, err := os.ReadFile(filepath.Join(homeDir, "dextest", "credentials.json")) @@ -615,12 +663,20 @@ func useTestnet() error { } func TestMain(m *testing.M) { + rand.Seed(time.Now().UnixNano()) dexeth.MaybeReadSimnetAddrs() flag.BoolVar(&isTestnet, "testnet", false, "use testnet") flag.BoolVar(&useRPC, "rpc", true, "use RPC") + flag.BoolVar(&v1, "v1", true, "Use Version 1 contract") flag.Parse() + if v1 { + ver = 1 + } + + ethGases = dexeth.VersionedGases[ver] + if isTestnet { tmpDir, err := os.MkdirTemp("", "") if err != nil { @@ -797,7 +853,7 @@ func TestContract(t *testing.T) { if !t.Run("testAddressesHaveFunds", testAddressesHaveFundsFn(100_000_000 /* gwei */)) { t.Fatal("not enough funds") } - t.Run("testSwap", func(t *testing.T) { testSwap(t, BipID) }) + // t.Run("testSwap", func(t *testing.T) { testSwap(t, BipID) }) // TODO: Replace with testStatusAndVector? t.Run("testInitiate", func(t *testing.T) { testInitiate(t, BipID) }) t.Run("testRedeem", func(t *testing.T) { testRedeem(t, BipID) }) t.Run("testRefund", func(t *testing.T) { testRefund(t, BipID) }) @@ -810,7 +866,7 @@ func TestGas(t *testing.T) { } 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) }) @@ -1100,17 +1156,6 @@ func testPendingTransactions(t *testing.T) { spew.Dump(txs) } -func testSwap(t *testing.T, assetID uint32) { - var secretHash [32]byte - copy(secretHash[:], encode.RandomBytes(32)) - swap, err := simnetContractor.swap(ctx, secretHash) - if err != nil { - t.Fatal(err) - } - // Should be empty. - spew.Dump(swap) -} - func testSyncProgress(t *testing.T) { p, _, err := ethClient.syncProgress(ctx) if err != nil { @@ -1128,17 +1173,7 @@ func testInitiateGas(t *testing.T, assetID uint32) { if isTestnet { net = dex.Testnet } - - 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 - } - } - gases := gases(0, versionedGases) + gases := gases(BipID, assetID, ver, net) var previousGas uint64 maxSwaps := 50 @@ -1157,7 +1192,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) } @@ -1255,15 +1290,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()) @@ -1282,10 +1309,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), }, }, { @@ -1341,28 +1368,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)) @@ -1371,7 +1399,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) @@ -1444,19 +1472,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) } } } @@ -1483,8 +1510,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 @@ -1522,19 +1552,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) } @@ -1548,9 +1578,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) @@ -1585,6 +1615,8 @@ func testRedeem(t *testing.T, assetID uint32) { evmify = tc.evmify } + const val = 1 + tests := []struct { name string sleepNBlocks int @@ -1603,8 +1635,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, }, @@ -1615,12 +1647,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, @@ -1633,8 +1665,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, }, @@ -1644,19 +1676,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, }, @@ -1668,12 +1701,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, @@ -1685,14 +1718,16 @@ func testRedeem(t *testing.T, assetID uint32) { for _, test := range tests { var optsVal uint64 - for i, contract := range test.swaps { - swap, err := c.swap(ctx, bytesToArray(test.swaps[i].SecretHash)) + locators := make([][]byte, 0, len(test.swaps)) + for _, contract := range test.swaps { + locator := acLocator(contract) + locators = append(locators, locator) + status, _, err := c.statusAndVector(ctx, locator) if err != nil { t.Fatal("unable to get swap state") } - state := dexeth.SwapStep(swap.State) - if state != dexeth.SSNone { - t.Fatalf("unexpected swap state for test %v: want %s got %s", test.name, dexeth.SSNone, state) + if status.Step != dexeth.SSNone { + t.Fatalf("unexpected swap state for test %v: want %s got %s", test.name, dexeth.SSNone, status.Step) } if isETH { optsVal += contract.Value @@ -1712,7 +1747,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) } @@ -1735,12 +1770,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) } } @@ -1840,15 +1875,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) } } } @@ -1880,7 +1914,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) } @@ -1888,22 +1924,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) } @@ -1995,12 +2030,16 @@ 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()) @@ -2009,7 +2048,7 @@ func testRefund(t *testing.T, assetID uint32) { 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) } @@ -2023,7 +2062,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) } @@ -2047,7 +2086,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) } @@ -2060,7 +2099,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) } @@ -2135,12 +2174,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) } } } @@ -2309,7 +2348,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, ver, tokenGases, tLogger); err != nil { t.Fatalf("getGasEstimates error: %v", err) } } diff --git a/dex/networks/erc20/contracts/ERC20SwapV0.sol b/dex/networks/erc20/contracts/ERC20SwapV0.sol index be6440b5e0..4037f3bed5 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 diff --git a/dex/networks/erc20/contracts/ERC20SwapV1.sol b/dex/networks/erc20/contracts/ERC20SwapV1.sol new file mode 100644 index 0000000000..e5a7eaef45 --- /dev/null +++ b/dex/networks/erc20/contracts/ERC20SwapV1.sol @@ -0,0 +1,241 @@ +// SPDX-License-Identifier: BlueOak-1.0.0 +// pragma should be as specific as possible to allow easier validation. +pragma solidity = 0.8.15; + +// ERC20Swap creates a contract to be deployed on an ethereum network. In +// order to save on gas fees, a separate ERC20Swap contract is deployed +// for each ERC20 token. After deployed, it keeps a map of swaps that +// facilitates atomic swapping of ERC20 tokens with other crypto currencies +// that support time locks. +// +// It accomplishes this by holding tokens acquired during a swap initiation +// until conditions are met. Prior to initiating a swap, the initiator must +// approve the ERC20Swap contract to be able to spend the initiator's tokens. +// When calling initiate, the necessary tokens for swaps are transferred to +// the swap contract. At this point the funds belong to the contract, and +// cannot be accessed by anyone else, not even the contract's deployer. The +// initiator sets a secret hash, a blocktime the funds will be accessible should +// they not be redeemed, and a participant who can redeem before or after the +// locktime. The participant can redeem at any time after the initiation +// transaction is mined if they have the secret that hashes to the secret hash. +// Otherwise, the initiator can refund funds any time after the locktime. +// +// This contract has no limits on gas used for any transactions. +// +// This contract cannot be used by other contracts or by a third party mediating +// the swap or multisig wallets. +contract ERC20Swap { + bytes4 private constant TRANSFER_FROM_SELECTOR = bytes4(keccak256("transferFrom(address,address,uint256)")); + bytes4 private constant TRANSFER_SELECTOR = bytes4(keccak256("transfer(address,uint256)")); + + address public immutable token_address; + + // Step is a type that hold's a contract's current step. Empty is the + // uninitiated or null value. + enum Step { Empty, Filled, Redeemed, Refunded } + + struct Status { + Step step; + bytes32 secret; + uint256 blockNumber; + } + + bytes32 constant RefundRecord = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF; + + // swaps is a map of contract hashes to the "swap record". The swap record + // has the following interpretation. + // if (record == bytes32(0x00)): contract is uninitiated + // else if (uint256(record) < block.number && sha256(record) != contract.secretHash): + // contract is initiated and redeemable by the participant with the secret. + // else if (sha256(record) == contract.secretHash): contract has been redeemed + // else if (record == RefundRecord): contract has been refunded + // else: invalid record. Should be impossible by construction + mapping(bytes32 => bytes32) public swaps; + + // Vector is the information necessary for initialization and redemption + // or refund. The Vector itself is not stored on-chain. Instead, a key + // unique to the Vector is generated from the Vector data and keys + // the swap record. + struct Vector { + bytes32 secretHash; + address initiator; + uint64 refundTimestamp; + address participant; + uint64 value; + } + + // contractKey generates a key hash which commits to the contract data. The + // generated hash is used as a key in the swaps map. + function contractKey(Vector calldata v) public pure returns (bytes32) { + return sha256(bytes.concat(v.secretHash, bytes20(v.initiator), bytes20(v.participant), bytes8(v.value), bytes8(v.refundTimestamp))); + } + + // Redemption is the information necessary to redeem a Vector. Since we + // don't store the Vector itself, it must be provided as part of the + // redemption. + struct Redemption { + Vector v; + bytes32 secret; + } + + function secretValidates(bytes32 secret, bytes32 secretHash) public pure returns (bool) { + return sha256(bytes.concat(secret)) == secretHash; + } + + constructor(address token) { + token_address = token; + } + + // senderIsOrigin ensures that this contract cannot be used by other + // contracts, which reduces possible attack vectors. + modifier senderIsOrigin() { + require(tx.origin == msg.sender, "sender != origin"); + _; + } + + // retrieveStatus retrieves the current swap record for the contract. + function retrieveStatus(Vector calldata v) + private view returns (bytes32, bytes32, uint256) + { + bytes32 k = contractKey(v); + bytes32 record = swaps[k]; + return (k, record, uint256(record)); + } + + // state returns the current state of the swap. + function status(Vector calldata v) + public view returns(Status memory) + { + (, bytes32 record, uint256 blockNum) = retrieveStatus(v); + Status memory r; + if (blockNum == 0) { + r.step = Step.Empty; + } else if (record == RefundRecord) { + r.step = Step.Refunded; + } else if (secretValidates(record, v.secretHash)) { + r.step = Step.Redeemed; + r.secret = record; + } else { + r.step = Step.Filled; + r.blockNumber = blockNum; + } + return r; + } + + // initiate initiates an array of Vectors. + function initiate(Vector[] calldata contracts) + public + payable + senderIsOrigin() + { + uint initVal = 0; + for (uint i = 0; i < contracts.length; i++) { + Vector calldata v = contracts[i]; + + require(v.value > 0, "0 val"); + require(v.refundTimestamp > 0, "0 refundTimestamp"); + + bytes32 k = contractKey(v); + bytes32 record = swaps[k]; + require(record == bytes32(0), "swap not empty"); + + record = bytes32(block.number); + require(!secretValidates(record, v.secretHash), "hash collision"); + + swaps[k] = record; + + initVal += v.value * 1 gwei; + } + + bool success; + bytes memory data; + (success, data) = token_address.call(abi.encodeWithSelector(TRANSFER_FROM_SELECTOR, msg.sender, address(this), initVal)); + require(success && (data.length == 0 || abi.decode(data, (bool))), 'transfer from failed'); + } + + // isRedeemable returns whether or not a swap identified by secretHash + // can be redeemed using secret. + function isRedeemable(Vector calldata v) + public + view + returns (bool) + { + (, bytes32 record, uint256 blockNum) = retrieveStatus(v); + return blockNum != 0 && !secretValidates(record, v.secretHash); + } + + // redeem redeems a Vector. It checks that the sender is not a contract, + // and that the secret hash hashes to secretHash. msg.value is tranfered + // from ETHSwap to the sender. + // + // To prevent reentry attack, it is very important to check the state of the + // contract first, and change the state before proceeding to send. That way, + // the nested attacking function will throw upon trying to call redeem a + // second time. Currently, reentry is also not possible because contracts + // cannot use this contract. + function redeem(Redemption[] calldata redemptions) + public + senderIsOrigin() + { + uint amountToRedeem = 0; + for (uint i = 0; i < redemptions.length; i++) { + Redemption calldata r = redemptions[i]; + + require(r.v.participant == msg.sender, "not authed"); + + (bytes32 k, bytes32 record, uint256 blockNum) = retrieveStatus(r.v); + + // To be redeemable, the record needs to represent a valid block + // number. + require(blockNum > 0 && blockNum < block.number, "unfilled swap"); + + // Can't already be redeemed. + require(!secretValidates(record, r.v.secretHash), "already redeemed"); + + // Are they presenting the correct secret? + require(secretValidates(r.secret, r.v.secretHash), "invalid secret"); + + swaps[k] = r.secret; + amountToRedeem += r.v.value * 1 gwei; + } + + bool success; + bytes memory data; + (success, data) = token_address.call(abi.encodeWithSelector(TRANSFER_SELECTOR, msg.sender, amountToRedeem)); + require(success && (data.length == 0 || abi.decode(data, (bool))), 'transfer failed'); + } + + + // refund refunds a Vector. It checks that the sender is not a contract + // and that the refund time has passed. msg.value is transfered from the + // contract to the sender = Vector.participant. + // + // It is important to note that this also uses call.value which comes with + // no restrictions on gas used. See redeem for more info. + function refund(Vector calldata v) + public + senderIsOrigin() + { + // Is this contract even in a refundable state? + require(block.timestamp >= v.refundTimestamp, "locktime not expired"); + + // Retrieve the record. + (bytes32 k, bytes32 record, uint256 blockNum) = retrieveStatus(v); + + // Is this swap initialized? + require(blockNum > 0 && blockNum <= block.number, "swap not active"); + + // Is it already redeemed? + require(!secretValidates(record, v.secretHash), "swap already redeemed"); + + // Is it already refunded? + require(record != RefundRecord, "swap already refunded"); + + swaps[k] = RefundRecord; + + bool success; + bytes memory data; + (success, data) = token_address.call(abi.encodeWithSelector(TRANSFER_SELECTOR, msg.sender, v.value)); + require(success && (data.length == 0 || abi.decode(data, (bool))), 'transfer failed'); + } +} diff --git a/dex/networks/erc20/contracts/updatecontract.sh b/dex/networks/erc20/contracts/updatecontract.sh index ef2a888b93..4ab633e891 100755 --- a/dex/networks/erc20/contracts/updatecontract.sh +++ b/dex/networks/erc20/contracts/updatecontract.sh @@ -84,3 +84,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/v1/BinRuntimeV1.go b/dex/networks/erc20/contracts/v1/BinRuntimeV1.go new file mode 100644 index 0000000000..790689b5be --- /dev/null +++ b/dex/networks/erc20/contracts/v1/BinRuntimeV1.go @@ -0,0 +1,6 @@ +// Code generated - DO NOT EDIT. +// This file is a generated binding and any manual changes will be lost. + +package v1 + +const ERC20SwapRuntimeBin = "6080604052600436106100865760003560e01c80637802689d116100595780637802689d146101265780638c8e8fee14610146578063d2544c0614610192578063eb84e7f2146101b2578063ed7cbed7146101ed57600080fd5b8063428b16e11461008b57806361a16e33146100ad57806364a97bff146100e357806377d7e031146100f6575b600080fd5b34801561009757600080fd5b506100ab6100a6366004610dea565b61020d565b005b3480156100b957600080fd5b506100cd6100c8366004610e5f565b610533565b6040516100da9190610e8d565b60405180910390f35b6100ab6100f1366004610ed0565b6105ef565b34801561010257600080fd5b50610116610111366004610f33565b610918565b60405190151581526020016100da565b34801561013257600080fd5b50610116610141366004610e5f565b610992565b34801561015257600080fd5b5061017a7f000000000000000000000000000000000000000000000000000000000000000081565b6040516001600160a01b0390911681526020016100da565b34801561019e57600080fd5b506100ab6101ad366004610e5f565b6109c5565b3480156101be57600080fd5b506101df6101cd366004610f55565b60006020819052908152604090205481565b6040519081526020016100da565b3480156101f957600080fd5b506101df610208366004610e5f565b610cc5565b3233146102355760405162461bcd60e51b815260040161022c90610f6e565b60405180910390fd5b6000805b82811015610407573684848381811061025457610254610f98565b60c0029190910191503390506102706080830160608401610fae565b6001600160a01b0316146102b35760405162461bcd60e51b815260206004820152600a6024820152691b9bdd08185d5d1a195960b21b604482015260640161022c565b600080806102c084610dbe565b9250925092506000811180156102d557504381105b6103115760405162461bcd60e51b815260206004820152600d60248201526c0756e66696c6c6564207377617609c1b604482015260640161022c565b61031c828535610918565b1561035c5760405162461bcd60e51b815260206004820152601060248201526f185b1c9958591e481c995919595b595960821b604482015260640161022c565b61036b60a08501358535610918565b6103a85760405162461bcd60e51b815260206004820152600e60248201526d1a5b9d985b1a59081cd958dc995d60921b604482015260640161022c565b600083815260208190526040902060a0850180359091556103cc9060808601610fde565b6103da90633b9aca0061101e565b6103ee9067ffffffffffffffff168761104e565b95505050505080806103ff90611066565b915050610239565b5060408051336024820152604480820184905282518083039091018152606490910182526020810180516001600160e01b031663a9059cbb60e01b17905290516000916060916001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016916104819161107f565b6000604051808303816000865af19150503d80600081146104be576040519150601f19603f3d011682016040523d82523d6000602084013e6104c3565b606091505b5090925090508180156104ee5750805115806104ee5750808060200190518101906104ee91906110ba565b61052c5760405162461bcd60e51b815260206004820152600f60248201526e1d1c985b9cd9995c8819985a5b1959608a1b604482015260640161022c565b5050505050565b60408051606081018252600080825260208201819052918101829052908061055a84610dbe565b92509250506105846040805160608101909152806000815260006020820181905260409091015290565b816000036105ab578060005b908160038111156105a3576105a3610e77565b9052506105e7565b600183016105bb57806003610590565b6105c6838635610918565b156105db5760028152602081018390526105e7565b60018152604081018290525b949350505050565b32331461060e5760405162461bcd60e51b815260040161022c90610f6e565b6000805b828110156107e8573684848381811061062d5761062d610f98565b905060a002019050600081608001602081019061064a9190610fde565b67ffffffffffffffff16116106895760405162461bcd60e51b81526020600482015260056024820152640c081d985b60da1b604482015260640161022c565b600061069b6060830160408401610fde565b67ffffffffffffffff16116106e65760405162461bcd60e51b815260206004820152601160248201527003020726566756e6454696d657374616d7607c1b604482015260640161022c565b60006106f182610cc5565b60008181526020819052604090205490915080156107425760405162461bcd60e51b815260206004820152600e60248201526d73776170206e6f7420656d70747960901b604482015260640161022c565b504361074f818435610918565b1561078d5760405162461bcd60e51b815260206004820152600e60248201526d3430b9b41031b7b63634b9b4b7b760911b604482015260640161022c565b60008281526020819052604090208190556107ae60a0840160808501610fde565b6107bc90633b9aca0061101e565b6107d09067ffffffffffffffff168661104e565b945050505080806107e090611066565b915050610612565b5060408051336024820152306044820152606480820184905282518083039091018152608490910182526020810180516001600160e01b03166323b872dd60e01b17905290516000916060916001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016916108689161107f565b6000604051808303816000865af19150503d80600081146108a5576040519150601f19603f3d011682016040523d82523d6000602084013e6108aa565b606091505b5090925090508180156108d55750805115806108d55750808060200190518101906108d591906110ba565b61052c5760405162461bcd60e51b81526020600482015260146024820152731d1c985b9cd9995c88199c9bdb4819985a5b195960621b604482015260640161022c565b60008160028460405160200161093091815260200190565b60408051601f198184030181529082905261094a9161107f565b602060405180830381855afa158015610967573d6000803e3d6000fd5b5050506040513d601f19601f8201168201806040525081019061098a91906110dc565b149392505050565b60008060006109a084610dbe565b9250925050806000141580156105e757506109bc828535610918565b15949350505050565b3233146109e45760405162461bcd60e51b815260040161022c90610f6e565b6109f46060820160408301610fde565b67ffffffffffffffff16421015610a445760405162461bcd60e51b81526020600482015260146024820152731b1bd8dadd1a5b59481b9bdd08195e1c1a5c995960621b604482015260640161022c565b6000806000610a5284610dbe565b925092509250600081118015610a685750438111155b610aa65760405162461bcd60e51b815260206004820152600f60248201526e73776170206e6f742061637469766560881b604482015260640161022c565b610ab1828535610918565b15610af65760405162461bcd60e51b81526020600482015260156024820152741cddd85c08185b1c9958591e481c995919595b5959605a1b604482015260640161022c565b60018201610b3e5760405162461bcd60e51b81526020600482015260156024820152741cddd85c08185b1c9958591e481c99599d5b991959605a1b604482015260640161022c565b6000838152602081905260408120600019905560606001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000167fa9059cbb2ab09eb219583f4a59a5d0623ade346d962bcd4e46b11da047c9049b33610baf60a08a0160808b01610fde565b6040516001600160a01b03909216602483015267ffffffffffffffff16604482015260640160408051601f198184030181529181526020820180516001600160e01b03166001600160e01b0319909416939093179092529051610c12919061107f565b6000604051808303816000865af19150503d8060008114610c4f576040519150601f19603f3d011682016040523d82523d6000602084013e610c54565b606091505b509092509050818015610c7f575080511580610c7f575080806020019051810190610c7f91906110ba565b610cbd5760405162461bcd60e51b815260206004820152600f60248201526e1d1c985b9cd9995c8819985a5b1959608a1b604482015260640161022c565b505050505050565b600060028235610cdb6040850160208601610fae565b60601b846060016020810190610cf19190610fae565b60601b610d0460a0870160808801610fde565b60c01b610d176060880160408901610fde565b6040805160208101969096526bffffffffffffffffffffffff19948516908601529190921660548401526001600160c01b0319918216606884015260c01b16607082015260780160408051601f1981840301815290829052610d789161107f565b602060405180830381855afa158015610d95573d6000803e3d6000fd5b5050506040513d601f19601f82011682018060405250810190610db891906110dc565b92915050565b600080600080610dcd85610cc5565b600081815260208190526040902054909690955085945092505050565b60008060208385031215610dfd57600080fd5b823567ffffffffffffffff80821115610e1557600080fd5b818501915085601f830112610e2957600080fd5b813581811115610e3857600080fd5b86602060c083028501011115610e4d57600080fd5b60209290920196919550909350505050565b600060a08284031215610e7157600080fd5b50919050565b634e487b7160e01b600052602160045260246000fd5b8151606082019060048110610eb257634e487b7160e01b600052602160045260246000fd5b80835250602083015160208301526040830151604083015292915050565b60008060208385031215610ee357600080fd5b823567ffffffffffffffff80821115610efb57600080fd5b818501915085601f830112610f0f57600080fd5b813581811115610f1e57600080fd5b86602060a083028501011115610e4d57600080fd5b60008060408385031215610f4657600080fd5b50508035926020909101359150565b600060208284031215610f6757600080fd5b5035919050565b60208082526010908201526f39b2b73232b910109e9037b934b3b4b760811b604082015260600190565b634e487b7160e01b600052603260045260246000fd5b600060208284031215610fc057600080fd5b81356001600160a01b0381168114610fd757600080fd5b9392505050565b600060208284031215610ff057600080fd5b813567ffffffffffffffff81168114610fd757600080fd5b634e487b7160e01b600052601160045260246000fd5b600067ffffffffffffffff8083168185168183048111821515161561104557611045611008565b02949350505050565b6000821982111561106157611061611008565b500190565b60006001820161107857611078611008565b5060010190565b6000825160005b818110156110a05760208186018101518583015201611086565b818111156110af576000828501525b509190910192915050565b6000602082840312156110cc57600080fd5b81518015158114610fd757600080fd5b6000602082840312156110ee57600080fd5b505191905056fea2646970667358221220319d89b87a0d5782925310133ed5d12d2ff562b0786611308396804fcad176fc64736f6c634300080f0033" diff --git a/dex/networks/erc20/contracts/v1/contract.go b/dex/networks/erc20/contracts/v1/contract.go new file mode 100644 index 0000000000..7afaf0f4f0 --- /dev/null +++ b/dex/networks/erc20/contracts/v1/contract.go @@ -0,0 +1,452 @@ +// Code generated - DO NOT EDIT. +// This file is a generated binding and any manual changes will be lost. + +package v1 + +import ( + "errors" + "math/big" + "strings" + + ethereum "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/event" + ethv1 "decred.org/dcrdex/dex/networks/eth/contracts/v1" +) + +// Reference imports to suppress errors if they are not otherwise used. +var ( + _ = errors.New + _ = big.NewInt + _ = strings.NewReader + _ = ethereum.NotFound + _ = bind.Bind + _ = common.Big1 + _ = types.BloomLookup + _ = event.NewSubscription +) + +// ERC20SwapMetaData contains all meta data concerning the ERC20Swap contract. +var ERC20SwapMetaData = &bind.MetaData{ + ABI: "[{\"inputs\":[{\"internalType\":\"address\",\"name\":\"token\",\"type\":\"address\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"inputs\":[{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"secretHash\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"initiator\",\"type\":\"address\"},{\"internalType\":\"uint64\",\"name\":\"refundTimestamp\",\"type\":\"uint64\"},{\"internalType\":\"address\",\"name\":\"participant\",\"type\":\"address\"},{\"internalType\":\"uint64\",\"name\":\"value\",\"type\":\"uint64\"}],\"internalType\":\"structERC20Swap.Vector\",\"name\":\"v\",\"type\":\"tuple\"}],\"name\":\"contractKey\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"secretHash\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"initiator\",\"type\":\"address\"},{\"internalType\":\"uint64\",\"name\":\"refundTimestamp\",\"type\":\"uint64\"},{\"internalType\":\"address\",\"name\":\"participant\",\"type\":\"address\"},{\"internalType\":\"uint64\",\"name\":\"value\",\"type\":\"uint64\"}],\"internalType\":\"structERC20Swap.Vector[]\",\"name\":\"contracts\",\"type\":\"tuple[]\"}],\"name\":\"initiate\",\"outputs\":[],\"stateMutability\":\"payable\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"secretHash\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"initiator\",\"type\":\"address\"},{\"internalType\":\"uint64\",\"name\":\"refundTimestamp\",\"type\":\"uint64\"},{\"internalType\":\"address\",\"name\":\"participant\",\"type\":\"address\"},{\"internalType\":\"uint64\",\"name\":\"value\",\"type\":\"uint64\"}],\"internalType\":\"structERC20Swap.Vector\",\"name\":\"v\",\"type\":\"tuple\"}],\"name\":\"isRedeemable\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"secretHash\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"initiator\",\"type\":\"address\"},{\"internalType\":\"uint64\",\"name\":\"refundTimestamp\",\"type\":\"uint64\"},{\"internalType\":\"address\",\"name\":\"participant\",\"type\":\"address\"},{\"internalType\":\"uint64\",\"name\":\"value\",\"type\":\"uint64\"}],\"internalType\":\"structERC20Swap.Vector\",\"name\":\"v\",\"type\":\"tuple\"},{\"internalType\":\"bytes32\",\"name\":\"secret\",\"type\":\"bytes32\"}],\"internalType\":\"structERC20Swap.Redemption[]\",\"name\":\"redemptions\",\"type\":\"tuple[]\"}],\"name\":\"redeem\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"secretHash\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"initiator\",\"type\":\"address\"},{\"internalType\":\"uint64\",\"name\":\"refundTimestamp\",\"type\":\"uint64\"},{\"internalType\":\"address\",\"name\":\"participant\",\"type\":\"address\"},{\"internalType\":\"uint64\",\"name\":\"value\",\"type\":\"uint64\"}],\"internalType\":\"structERC20Swap.Vector\",\"name\":\"v\",\"type\":\"tuple\"}],\"name\":\"refund\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"secret\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"secretHash\",\"type\":\"bytes32\"}],\"name\":\"secretValidates\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"secretHash\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"initiator\",\"type\":\"address\"},{\"internalType\":\"uint64\",\"name\":\"refundTimestamp\",\"type\":\"uint64\"},{\"internalType\":\"address\",\"name\":\"participant\",\"type\":\"address\"},{\"internalType\":\"uint64\",\"name\":\"value\",\"type\":\"uint64\"}],\"internalType\":\"structERC20Swap.Vector\",\"name\":\"v\",\"type\":\"tuple\"}],\"name\":\"status\",\"outputs\":[{\"components\":[{\"internalType\":\"enumERC20Swap.Step\",\"name\":\"step\",\"type\":\"uint8\"},{\"internalType\":\"bytes32\",\"name\":\"secret\",\"type\":\"bytes32\"},{\"internalType\":\"uint256\",\"name\":\"blockNumber\",\"type\":\"uint256\"}],\"internalType\":\"structERC20Swap.Status\",\"name\":\"\",\"type\":\"tuple\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"name\":\"swaps\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"token_address\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"}]", + Bin: "0x60a060405234801561001057600080fd5b506040516111cb3803806111cb83398101604081905261002f91610040565b6001600160a01b0316608052610070565b60006020828403121561005257600080fd5b81516001600160a01b038116811461006957600080fd5b9392505050565b60805161112b6100a060003960008181610158015281816104570152818161083e0152610b5d015261112b6000f3fe6080604052600436106100865760003560e01c80637802689d116100595780637802689d146101265780638c8e8fee14610146578063d2544c0614610192578063eb84e7f2146101b2578063ed7cbed7146101ed57600080fd5b8063428b16e11461008b57806361a16e33146100ad57806364a97bff146100e357806377d7e031146100f6575b600080fd5b34801561009757600080fd5b506100ab6100a6366004610dea565b61020d565b005b3480156100b957600080fd5b506100cd6100c8366004610e5f565b610533565b6040516100da9190610e8d565b60405180910390f35b6100ab6100f1366004610ed0565b6105ef565b34801561010257600080fd5b50610116610111366004610f33565b610918565b60405190151581526020016100da565b34801561013257600080fd5b50610116610141366004610e5f565b610992565b34801561015257600080fd5b5061017a7f000000000000000000000000000000000000000000000000000000000000000081565b6040516001600160a01b0390911681526020016100da565b34801561019e57600080fd5b506100ab6101ad366004610e5f565b6109c5565b3480156101be57600080fd5b506101df6101cd366004610f55565b60006020819052908152604090205481565b6040519081526020016100da565b3480156101f957600080fd5b506101df610208366004610e5f565b610cc5565b3233146102355760405162461bcd60e51b815260040161022c90610f6e565b60405180910390fd5b6000805b82811015610407573684848381811061025457610254610f98565b60c0029190910191503390506102706080830160608401610fae565b6001600160a01b0316146102b35760405162461bcd60e51b815260206004820152600a6024820152691b9bdd08185d5d1a195960b21b604482015260640161022c565b600080806102c084610dbe565b9250925092506000811180156102d557504381105b6103115760405162461bcd60e51b815260206004820152600d60248201526c0756e66696c6c6564207377617609c1b604482015260640161022c565b61031c828535610918565b1561035c5760405162461bcd60e51b815260206004820152601060248201526f185b1c9958591e481c995919595b595960821b604482015260640161022c565b61036b60a08501358535610918565b6103a85760405162461bcd60e51b815260206004820152600e60248201526d1a5b9d985b1a59081cd958dc995d60921b604482015260640161022c565b600083815260208190526040902060a0850180359091556103cc9060808601610fde565b6103da90633b9aca0061101e565b6103ee9067ffffffffffffffff168761104e565b95505050505080806103ff90611066565b915050610239565b5060408051336024820152604480820184905282518083039091018152606490910182526020810180516001600160e01b031663a9059cbb60e01b17905290516000916060916001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016916104819161107f565b6000604051808303816000865af19150503d80600081146104be576040519150601f19603f3d011682016040523d82523d6000602084013e6104c3565b606091505b5090925090508180156104ee5750805115806104ee5750808060200190518101906104ee91906110ba565b61052c5760405162461bcd60e51b815260206004820152600f60248201526e1d1c985b9cd9995c8819985a5b1959608a1b604482015260640161022c565b5050505050565b60408051606081018252600080825260208201819052918101829052908061055a84610dbe565b92509250506105846040805160608101909152806000815260006020820181905260409091015290565b816000036105ab578060005b908160038111156105a3576105a3610e77565b9052506105e7565b600183016105bb57806003610590565b6105c6838635610918565b156105db5760028152602081018390526105e7565b60018152604081018290525b949350505050565b32331461060e5760405162461bcd60e51b815260040161022c90610f6e565b6000805b828110156107e8573684848381811061062d5761062d610f98565b905060a002019050600081608001602081019061064a9190610fde565b67ffffffffffffffff16116106895760405162461bcd60e51b81526020600482015260056024820152640c081d985b60da1b604482015260640161022c565b600061069b6060830160408401610fde565b67ffffffffffffffff16116106e65760405162461bcd60e51b815260206004820152601160248201527003020726566756e6454696d657374616d7607c1b604482015260640161022c565b60006106f182610cc5565b60008181526020819052604090205490915080156107425760405162461bcd60e51b815260206004820152600e60248201526d73776170206e6f7420656d70747960901b604482015260640161022c565b504361074f818435610918565b1561078d5760405162461bcd60e51b815260206004820152600e60248201526d3430b9b41031b7b63634b9b4b7b760911b604482015260640161022c565b60008281526020819052604090208190556107ae60a0840160808501610fde565b6107bc90633b9aca0061101e565b6107d09067ffffffffffffffff168661104e565b945050505080806107e090611066565b915050610612565b5060408051336024820152306044820152606480820184905282518083039091018152608490910182526020810180516001600160e01b03166323b872dd60e01b17905290516000916060916001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016916108689161107f565b6000604051808303816000865af19150503d80600081146108a5576040519150601f19603f3d011682016040523d82523d6000602084013e6108aa565b606091505b5090925090508180156108d55750805115806108d55750808060200190518101906108d591906110ba565b61052c5760405162461bcd60e51b81526020600482015260146024820152731d1c985b9cd9995c88199c9bdb4819985a5b195960621b604482015260640161022c565b60008160028460405160200161093091815260200190565b60408051601f198184030181529082905261094a9161107f565b602060405180830381855afa158015610967573d6000803e3d6000fd5b5050506040513d601f19601f8201168201806040525081019061098a91906110dc565b149392505050565b60008060006109a084610dbe565b9250925050806000141580156105e757506109bc828535610918565b15949350505050565b3233146109e45760405162461bcd60e51b815260040161022c90610f6e565b6109f46060820160408301610fde565b67ffffffffffffffff16421015610a445760405162461bcd60e51b81526020600482015260146024820152731b1bd8dadd1a5b59481b9bdd08195e1c1a5c995960621b604482015260640161022c565b6000806000610a5284610dbe565b925092509250600081118015610a685750438111155b610aa65760405162461bcd60e51b815260206004820152600f60248201526e73776170206e6f742061637469766560881b604482015260640161022c565b610ab1828535610918565b15610af65760405162461bcd60e51b81526020600482015260156024820152741cddd85c08185b1c9958591e481c995919595b5959605a1b604482015260640161022c565b60018201610b3e5760405162461bcd60e51b81526020600482015260156024820152741cddd85c08185b1c9958591e481c99599d5b991959605a1b604482015260640161022c565b6000838152602081905260408120600019905560606001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000167fa9059cbb2ab09eb219583f4a59a5d0623ade346d962bcd4e46b11da047c9049b33610baf60a08a0160808b01610fde565b6040516001600160a01b03909216602483015267ffffffffffffffff16604482015260640160408051601f198184030181529181526020820180516001600160e01b03166001600160e01b0319909416939093179092529051610c12919061107f565b6000604051808303816000865af19150503d8060008114610c4f576040519150601f19603f3d011682016040523d82523d6000602084013e610c54565b606091505b509092509050818015610c7f575080511580610c7f575080806020019051810190610c7f91906110ba565b610cbd5760405162461bcd60e51b815260206004820152600f60248201526e1d1c985b9cd9995c8819985a5b1959608a1b604482015260640161022c565b505050505050565b600060028235610cdb6040850160208601610fae565b60601b846060016020810190610cf19190610fae565b60601b610d0460a0870160808801610fde565b60c01b610d176060880160408901610fde565b6040805160208101969096526bffffffffffffffffffffffff19948516908601529190921660548401526001600160c01b0319918216606884015260c01b16607082015260780160408051601f1981840301815290829052610d789161107f565b602060405180830381855afa158015610d95573d6000803e3d6000fd5b5050506040513d601f19601f82011682018060405250810190610db891906110dc565b92915050565b600080600080610dcd85610cc5565b600081815260208190526040902054909690955085945092505050565b60008060208385031215610dfd57600080fd5b823567ffffffffffffffff80821115610e1557600080fd5b818501915085601f830112610e2957600080fd5b813581811115610e3857600080fd5b86602060c083028501011115610e4d57600080fd5b60209290920196919550909350505050565b600060a08284031215610e7157600080fd5b50919050565b634e487b7160e01b600052602160045260246000fd5b8151606082019060048110610eb257634e487b7160e01b600052602160045260246000fd5b80835250602083015160208301526040830151604083015292915050565b60008060208385031215610ee357600080fd5b823567ffffffffffffffff80821115610efb57600080fd5b818501915085601f830112610f0f57600080fd5b813581811115610f1e57600080fd5b86602060a083028501011115610e4d57600080fd5b60008060408385031215610f4657600080fd5b50508035926020909101359150565b600060208284031215610f6757600080fd5b5035919050565b60208082526010908201526f39b2b73232b910109e9037b934b3b4b760811b604082015260600190565b634e487b7160e01b600052603260045260246000fd5b600060208284031215610fc057600080fd5b81356001600160a01b0381168114610fd757600080fd5b9392505050565b600060208284031215610ff057600080fd5b813567ffffffffffffffff81168114610fd757600080fd5b634e487b7160e01b600052601160045260246000fd5b600067ffffffffffffffff8083168185168183048111821515161561104557611045611008565b02949350505050565b6000821982111561106157611061611008565b500190565b60006001820161107857611078611008565b5060010190565b6000825160005b818110156110a05760208186018101518583015201611086565b818111156110af576000828501525b509190910192915050565b6000602082840312156110cc57600080fd5b81518015158114610fd757600080fd5b6000602082840312156110ee57600080fd5b505191905056fea2646970667358221220319d89b87a0d5782925310133ed5d12d2ff562b0786611308396804fcad176fc64736f6c634300080f0033", +} + +// ERC20SwapABI is the input ABI used to generate the binding from. +// Deprecated: Use ERC20SwapMetaData.ABI instead. +var ERC20SwapABI = ERC20SwapMetaData.ABI + +// ERC20SwapBin is the compiled bytecode used for deploying new contracts. +// Deprecated: Use ERC20SwapMetaData.Bin instead. +var ERC20SwapBin = ERC20SwapMetaData.Bin + +// DeployERC20Swap deploys a new Ethereum contract, binding an instance of ERC20Swap to it. +func DeployERC20Swap(auth *bind.TransactOpts, backend bind.ContractBackend, token common.Address) (common.Address, *types.Transaction, *ERC20Swap, error) { + parsed, err := ERC20SwapMetaData.GetAbi() + if err != nil { + return common.Address{}, nil, nil, err + } + if parsed == nil { + return common.Address{}, nil, nil, errors.New("GetABI returned nil") + } + + address, tx, contract, err := bind.DeployContract(auth, *parsed, common.FromHex(ERC20SwapBin), backend, token) + if err != nil { + return common.Address{}, nil, nil, err + } + return address, tx, &ERC20Swap{ERC20SwapCaller: ERC20SwapCaller{contract: contract}, ERC20SwapTransactor: ERC20SwapTransactor{contract: contract}, ERC20SwapFilterer: ERC20SwapFilterer{contract: contract}}, nil +} + +// ERC20Swap is an auto generated Go binding around an Ethereum contract. +type ERC20Swap struct { + ERC20SwapCaller // Read-only binding to the contract + ERC20SwapTransactor // Write-only binding to the contract + ERC20SwapFilterer // Log filterer for contract events +} + +// ERC20SwapCaller is an auto generated read-only Go binding around an Ethereum contract. +type ERC20SwapCaller struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// ERC20SwapTransactor is an auto generated write-only Go binding around an Ethereum contract. +type ERC20SwapTransactor struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// ERC20SwapFilterer is an auto generated log filtering Go binding around an Ethereum contract events. +type ERC20SwapFilterer struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// ERC20SwapSession is an auto generated Go binding around an Ethereum contract, +// with pre-set call and transact options. +type ERC20SwapSession struct { + Contract *ERC20Swap // Generic contract binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session +} + +// ERC20SwapCallerSession is an auto generated read-only Go binding around an Ethereum contract, +// with pre-set call options. +type ERC20SwapCallerSession struct { + Contract *ERC20SwapCaller // Generic contract caller binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session +} + +// ERC20SwapTransactorSession is an auto generated write-only Go binding around an Ethereum contract, +// with pre-set transact options. +type ERC20SwapTransactorSession struct { + Contract *ERC20SwapTransactor // Generic contract transactor binding to set the session for + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session +} + +// ERC20SwapRaw is an auto generated low-level Go binding around an Ethereum contract. +type ERC20SwapRaw struct { + Contract *ERC20Swap // Generic contract binding to access the raw methods on +} + +// ERC20SwapCallerRaw is an auto generated low-level read-only Go binding around an Ethereum contract. +type ERC20SwapCallerRaw struct { + Contract *ERC20SwapCaller // Generic read-only contract binding to access the raw methods on +} + +// ERC20SwapTransactorRaw is an auto generated low-level write-only Go binding around an Ethereum contract. +type ERC20SwapTransactorRaw struct { + Contract *ERC20SwapTransactor // Generic write-only contract binding to access the raw methods on +} + +// NewERC20Swap creates a new instance of ERC20Swap, bound to a specific deployed contract. +func NewERC20Swap(address common.Address, backend bind.ContractBackend) (*ERC20Swap, error) { + contract, err := bindERC20Swap(address, backend, backend, backend) + if err != nil { + return nil, err + } + return &ERC20Swap{ERC20SwapCaller: ERC20SwapCaller{contract: contract}, ERC20SwapTransactor: ERC20SwapTransactor{contract: contract}, ERC20SwapFilterer: ERC20SwapFilterer{contract: contract}}, nil +} + +// NewERC20SwapCaller creates a new read-only instance of ERC20Swap, bound to a specific deployed contract. +func NewERC20SwapCaller(address common.Address, caller bind.ContractCaller) (*ERC20SwapCaller, error) { + contract, err := bindERC20Swap(address, caller, nil, nil) + if err != nil { + return nil, err + } + return &ERC20SwapCaller{contract: contract}, nil +} + +// NewERC20SwapTransactor creates a new write-only instance of ERC20Swap, bound to a specific deployed contract. +func NewERC20SwapTransactor(address common.Address, transactor bind.ContractTransactor) (*ERC20SwapTransactor, error) { + contract, err := bindERC20Swap(address, nil, transactor, nil) + if err != nil { + return nil, err + } + return &ERC20SwapTransactor{contract: contract}, nil +} + +// NewERC20SwapFilterer creates a new log filterer instance of ERC20Swap, bound to a specific deployed contract. +func NewERC20SwapFilterer(address common.Address, filterer bind.ContractFilterer) (*ERC20SwapFilterer, error) { + contract, err := bindERC20Swap(address, nil, nil, filterer) + if err != nil { + return nil, err + } + return &ERC20SwapFilterer{contract: contract}, nil +} + +// bindERC20Swap binds a generic wrapper to an already deployed contract. +func bindERC20Swap(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor, filterer bind.ContractFilterer) (*bind.BoundContract, error) { + parsed, err := abi.JSON(strings.NewReader(ERC20SwapABI)) + if err != nil { + return nil, err + } + return bind.NewBoundContract(address, parsed, caller, transactor, filterer), nil +} + +// Call invokes the (constant) contract method with params as input values and +// sets the output to result. The result type might be a single field for simple +// returns, a slice of interfaces for anonymous returns and a struct for named +// returns. +func (_ERC20Swap *ERC20SwapRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { + return _ERC20Swap.Contract.ERC20SwapCaller.contract.Call(opts, result, method, params...) +} + +// Transfer initiates a plain transaction to move funds to the contract, calling +// its default method if one is available. +func (_ERC20Swap *ERC20SwapRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _ERC20Swap.Contract.ERC20SwapTransactor.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_ERC20Swap *ERC20SwapRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _ERC20Swap.Contract.ERC20SwapTransactor.contract.Transact(opts, method, params...) +} + +// Call invokes the (constant) contract method with params as input values and +// sets the output to result. The result type might be a single field for simple +// returns, a slice of interfaces for anonymous returns and a struct for named +// returns. +func (_ERC20Swap *ERC20SwapCallerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { + return _ERC20Swap.Contract.contract.Call(opts, result, method, params...) +} + +// Transfer initiates a plain transaction to move funds to the contract, calling +// its default method if one is available. +func (_ERC20Swap *ERC20SwapTransactorRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _ERC20Swap.Contract.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_ERC20Swap *ERC20SwapTransactorRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _ERC20Swap.Contract.contract.Transact(opts, method, params...) +} + +// ContractKey is a free data retrieval call binding the contract method 0xed7cbed7. +// +// Solidity: function contractKey((bytes32,address,uint64,address,uint64) v) pure returns(bytes32) +func (_ERC20Swap *ERC20SwapCaller) ContractKey(opts *bind.CallOpts, v ethv1.ETHSwapVector) ([32]byte, error) { + var out []interface{} + err := _ERC20Swap.contract.Call(opts, &out, "contractKey", v) + + if err != nil { + return *new([32]byte), err + } + + out0 := *abi.ConvertType(out[0], new([32]byte)).(*[32]byte) + + return out0, err + +} + +// ContractKey is a free data retrieval call binding the contract method 0xed7cbed7. +// +// Solidity: function contractKey((bytes32,address,uint64,address,uint64) v) pure returns(bytes32) +func (_ERC20Swap *ERC20SwapSession) ContractKey(v ethv1.ETHSwapVector) ([32]byte, error) { + return _ERC20Swap.Contract.ContractKey(&_ERC20Swap.CallOpts, v) +} + +// ContractKey is a free data retrieval call binding the contract method 0xed7cbed7. +// +// Solidity: function contractKey((bytes32,address,uint64,address,uint64) v) pure returns(bytes32) +func (_ERC20Swap *ERC20SwapCallerSession) ContractKey(v ethv1.ETHSwapVector) ([32]byte, error) { + return _ERC20Swap.Contract.ContractKey(&_ERC20Swap.CallOpts, v) +} + +// IsRedeemable is a free data retrieval call binding the contract method 0x7802689d. +// +// Solidity: function isRedeemable((bytes32,address,uint64,address,uint64) v) view returns(bool) +func (_ERC20Swap *ERC20SwapCaller) IsRedeemable(opts *bind.CallOpts, v ethv1.ETHSwapVector) (bool, error) { + var out []interface{} + err := _ERC20Swap.contract.Call(opts, &out, "isRedeemable", v) + + if err != nil { + return *new(bool), err + } + + out0 := *abi.ConvertType(out[0], new(bool)).(*bool) + + return out0, err + +} + +// IsRedeemable is a free data retrieval call binding the contract method 0x7802689d. +// +// Solidity: function isRedeemable((bytes32,address,uint64,address,uint64) v) view returns(bool) +func (_ERC20Swap *ERC20SwapSession) IsRedeemable(v ethv1.ETHSwapVector) (bool, error) { + return _ERC20Swap.Contract.IsRedeemable(&_ERC20Swap.CallOpts, v) +} + +// IsRedeemable is a free data retrieval call binding the contract method 0x7802689d. +// +// Solidity: function isRedeemable((bytes32,address,uint64,address,uint64) v) view returns(bool) +func (_ERC20Swap *ERC20SwapCallerSession) IsRedeemable(v ethv1.ETHSwapVector) (bool, error) { + return _ERC20Swap.Contract.IsRedeemable(&_ERC20Swap.CallOpts, v) +} + +// SecretValidates is a free data retrieval call binding the contract method 0x77d7e031. +// +// Solidity: function secretValidates(bytes32 secret, bytes32 secretHash) pure returns(bool) +func (_ERC20Swap *ERC20SwapCaller) SecretValidates(opts *bind.CallOpts, secret [32]byte, secretHash [32]byte) (bool, error) { + var out []interface{} + err := _ERC20Swap.contract.Call(opts, &out, "secretValidates", secret, secretHash) + + if err != nil { + return *new(bool), err + } + + out0 := *abi.ConvertType(out[0], new(bool)).(*bool) + + return out0, err + +} + +// SecretValidates is a free data retrieval call binding the contract method 0x77d7e031. +// +// Solidity: function secretValidates(bytes32 secret, bytes32 secretHash) pure returns(bool) +func (_ERC20Swap *ERC20SwapSession) SecretValidates(secret [32]byte, secretHash [32]byte) (bool, error) { + return _ERC20Swap.Contract.SecretValidates(&_ERC20Swap.CallOpts, secret, secretHash) +} + +// SecretValidates is a free data retrieval call binding the contract method 0x77d7e031. +// +// Solidity: function secretValidates(bytes32 secret, bytes32 secretHash) pure returns(bool) +func (_ERC20Swap *ERC20SwapCallerSession) SecretValidates(secret [32]byte, secretHash [32]byte) (bool, error) { + return _ERC20Swap.Contract.SecretValidates(&_ERC20Swap.CallOpts, secret, secretHash) +} + +// Status is a free data retrieval call binding the contract method 0x61a16e33. +// +// Solidity: function status((bytes32,address,uint64,address,uint64) v) view returns((uint8,bytes32,uint256)) +func (_ERC20Swap *ERC20SwapCaller) Status(opts *bind.CallOpts, v ethv1.ETHSwapVector) (ethv1.ETHSwapStatus, error) { + var out []interface{} + err := _ERC20Swap.contract.Call(opts, &out, "status", v) + + if err != nil { + return *new(ethv1.ETHSwapStatus), err + } + + out0 := *abi.ConvertType(out[0], new(ethv1.ETHSwapStatus)).(*ethv1.ETHSwapStatus) + + return out0, err + +} + +// Status is a free data retrieval call binding the contract method 0x61a16e33. +// +// Solidity: function status((bytes32,address,uint64,address,uint64) v) view returns((uint8,bytes32,uint256)) +func (_ERC20Swap *ERC20SwapSession) Status(v ethv1.ETHSwapVector) (ethv1.ETHSwapStatus, error) { + return _ERC20Swap.Contract.Status(&_ERC20Swap.CallOpts, v) +} + +// Status is a free data retrieval call binding the contract method 0x61a16e33. +// +// Solidity: function status((bytes32,address,uint64,address,uint64) v) view returns((uint8,bytes32,uint256)) +func (_ERC20Swap *ERC20SwapCallerSession) Status(v ethv1.ETHSwapVector) (ethv1.ETHSwapStatus, error) { + return _ERC20Swap.Contract.Status(&_ERC20Swap.CallOpts, v) +} + +// Swaps is a free data retrieval call binding the contract method 0xeb84e7f2. +// +// Solidity: function swaps(bytes32 ) view returns(bytes32) +func (_ERC20Swap *ERC20SwapCaller) Swaps(opts *bind.CallOpts, arg0 [32]byte) ([32]byte, error) { + var out []interface{} + err := _ERC20Swap.contract.Call(opts, &out, "swaps", arg0) + + if err != nil { + return *new([32]byte), err + } + + out0 := *abi.ConvertType(out[0], new([32]byte)).(*[32]byte) + + return out0, err + +} + +// Swaps is a free data retrieval call binding the contract method 0xeb84e7f2. +// +// Solidity: function swaps(bytes32 ) view returns(bytes32) +func (_ERC20Swap *ERC20SwapSession) Swaps(arg0 [32]byte) ([32]byte, error) { + return _ERC20Swap.Contract.Swaps(&_ERC20Swap.CallOpts, arg0) +} + +// Swaps is a free data retrieval call binding the contract method 0xeb84e7f2. +// +// Solidity: function swaps(bytes32 ) view returns(bytes32) +func (_ERC20Swap *ERC20SwapCallerSession) Swaps(arg0 [32]byte) ([32]byte, error) { + return _ERC20Swap.Contract.Swaps(&_ERC20Swap.CallOpts, arg0) +} + +// TokenAddress is a free data retrieval call binding the contract method 0x8c8e8fee. +// +// Solidity: function token_address() view returns(address) +func (_ERC20Swap *ERC20SwapCaller) TokenAddress(opts *bind.CallOpts) (common.Address, error) { + var out []interface{} + err := _ERC20Swap.contract.Call(opts, &out, "token_address") + + if err != nil { + return *new(common.Address), err + } + + out0 := *abi.ConvertType(out[0], new(common.Address)).(*common.Address) + + return out0, err + +} + +// TokenAddress is a free data retrieval call binding the contract method 0x8c8e8fee. +// +// Solidity: function token_address() view returns(address) +func (_ERC20Swap *ERC20SwapSession) TokenAddress() (common.Address, error) { + return _ERC20Swap.Contract.TokenAddress(&_ERC20Swap.CallOpts) +} + +// TokenAddress is a free data retrieval call binding the contract method 0x8c8e8fee. +// +// Solidity: function token_address() view returns(address) +func (_ERC20Swap *ERC20SwapCallerSession) TokenAddress() (common.Address, error) { + return _ERC20Swap.Contract.TokenAddress(&_ERC20Swap.CallOpts) +} + +// Initiate is a paid mutator transaction binding the contract method 0x64a97bff. +// +// Solidity: function initiate((bytes32,address,uint64,address,uint64)[] contracts) payable returns() +func (_ERC20Swap *ERC20SwapTransactor) Initiate(opts *bind.TransactOpts, contracts []ethv1.ETHSwapVector) (*types.Transaction, error) { + return _ERC20Swap.contract.Transact(opts, "initiate", contracts) +} + +// Initiate is a paid mutator transaction binding the contract method 0x64a97bff. +// +// Solidity: function initiate((bytes32,address,uint64,address,uint64)[] contracts) payable returns() +func (_ERC20Swap *ERC20SwapSession) Initiate(contracts []ethv1.ETHSwapVector) (*types.Transaction, error) { + return _ERC20Swap.Contract.Initiate(&_ERC20Swap.TransactOpts, contracts) +} + +// Initiate is a paid mutator transaction binding the contract method 0x64a97bff. +// +// Solidity: function initiate((bytes32,address,uint64,address,uint64)[] contracts) payable returns() +func (_ERC20Swap *ERC20SwapTransactorSession) Initiate(contracts []ethv1.ETHSwapVector) (*types.Transaction, error) { + return _ERC20Swap.Contract.Initiate(&_ERC20Swap.TransactOpts, contracts) +} + +// Redeem is a paid mutator transaction binding the contract method 0x428b16e1. +// +// Solidity: function redeem(((bytes32,address,uint64,address,uint64),bytes32)[] redemptions) returns() +func (_ERC20Swap *ERC20SwapTransactor) Redeem(opts *bind.TransactOpts, redemptions []ethv1.ETHSwapRedemption) (*types.Transaction, error) { + return _ERC20Swap.contract.Transact(opts, "redeem", redemptions) +} + +// Redeem is a paid mutator transaction binding the contract method 0x428b16e1. +// +// Solidity: function redeem(((bytes32,address,uint64,address,uint64),bytes32)[] redemptions) returns() +func (_ERC20Swap *ERC20SwapSession) Redeem(redemptions []ethv1.ETHSwapRedemption) (*types.Transaction, error) { + return _ERC20Swap.Contract.Redeem(&_ERC20Swap.TransactOpts, redemptions) +} + +// Redeem is a paid mutator transaction binding the contract method 0x428b16e1. +// +// Solidity: function redeem(((bytes32,address,uint64,address,uint64),bytes32)[] redemptions) returns() +func (_ERC20Swap *ERC20SwapTransactorSession) Redeem(redemptions []ethv1.ETHSwapRedemption) (*types.Transaction, error) { + return _ERC20Swap.Contract.Redeem(&_ERC20Swap.TransactOpts, redemptions) +} + +// Refund is a paid mutator transaction binding the contract method 0xd2544c06. +// +// Solidity: function refund((bytes32,address,uint64,address,uint64) v) returns() +func (_ERC20Swap *ERC20SwapTransactor) Refund(opts *bind.TransactOpts, v ethv1.ETHSwapVector) (*types.Transaction, error) { + return _ERC20Swap.contract.Transact(opts, "refund", v) +} + +// Refund is a paid mutator transaction binding the contract method 0xd2544c06. +// +// Solidity: function refund((bytes32,address,uint64,address,uint64) v) returns() +func (_ERC20Swap *ERC20SwapSession) Refund(v ethv1.ETHSwapVector) (*types.Transaction, error) { + return _ERC20Swap.Contract.Refund(&_ERC20Swap.TransactOpts, v) +} + +// Refund is a paid mutator transaction binding the contract method 0xd2544c06. +// +// Solidity: function refund((bytes32,address,uint64,address,uint64) v) returns() +func (_ERC20Swap *ERC20SwapTransactorSession) Refund(v ethv1.ETHSwapVector) (*types.Transaction, error) { + return _ERC20Swap.Contract.Refund(&_ERC20Swap.TransactOpts, v) +} diff --git a/dex/networks/eth/contracts/ETHSwapV1.sol b/dex/networks/eth/contracts/ETHSwapV1.sol new file mode 100644 index 0000000000..d1e115b67f --- /dev/null +++ b/dex/networks/eth/contracts/ETHSwapV1.sol @@ -0,0 +1,227 @@ +// SPDX-License-Identifier: BlueOak-1.0.0 +// pragma should be as specific as possible to allow easier validation. +pragma solidity = 0.8.15; + +// ETHSwap creates a contract to be deployed on an ethereum network. After +// deployed, it keeps a record of the state of a contract and enables +// redemption and refund of the contract when conditions are met. +// +// ETHSwap accomplishes this by holding funds sent to ETHSwap until certain +// conditions are met. An initiator sends a tx with the Vector(s) to fund and +// the requisite value to transfer to ETHSwap. At +// this point the funds belong to the contract, and cannot be accessed by +// anyone else, not even the contract's deployer. The swap Vector specifies +// the conditions necessary for refund and redeem. +// +// ETHSwap has no limits on gas used for any transactions. +// +// ETHSwap cannot be used by other contracts or by a third party mediating +// the swap or multisig wallets. +// +// This code should be verifiable as resulting in a certain on-chain contract +// by compiling with the correct version of solidity and comparing the +// resulting byte code to the data in the original transaction. +contract ETHSwap { + // Step is a type that hold's a contract's current step. Empty is the + // uninitiated or null value. + enum Step { Empty, Filled, Redeemed, Refunded } + + struct Status { + Step step; + bytes32 secret; + uint256 blockNumber; + } + + bytes32 constant RefundRecord = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF; + + // swaps is a map of contract hashes to the "swap record". The swap record + // has the following interpretation. + // if (record == bytes32(0x00)): contract is uninitiated + // else if (uint256(record) < block.number && sha256(record) != contract.secretHash): + // contract is initiated and redeemable by the participant with the secret. + // else if (sha256(record) == contract.secretHash): contract has been redeemed + // else if (record == RefundRecord): contract has been refunded + // else: invalid record. Should be impossible by construction + mapping(bytes32 => bytes32) public swaps; + + // Vector is the information necessary for initialization and redemption + // or refund. The Vector itself is not stored on-chain. Instead, a key + // unique to the Vector is generated from the Vector data and keys + // the swap record. + struct Vector { + bytes32 secretHash; + address initiator; + uint64 refundTimestamp; + address participant; + uint64 value; + } + + // contractKey generates a key hash which commits to the contract data. The + // generated hash is used as a key in the swaps map. + function contractKey(Vector calldata v) public pure returns (bytes32) { + return sha256(bytes.concat(v.secretHash, bytes20(v.initiator), bytes20(v.participant), bytes8(v.value), bytes8(v.refundTimestamp))); + } + + // Redemption is the information necessary to redeem a Vector. Since we + // don't store the Vector itself, it must be provided as part of the + // redemption. + struct Redemption { + Vector v; + bytes32 secret; + } + + function secretValidates(bytes32 secret, bytes32 secretHash) public pure returns (bool) { + return sha256(bytes.concat(secret)) == secretHash; + } + + // constructor is empty. This contract has no connection to the original + // sender after deployed. It can only be interacted with by users + // initiating, redeeming, and refunding swaps. + constructor() {} + + // senderIsOrigin ensures that this contract cannot be used by other + // contracts, which reduces possible attack vectors. + modifier senderIsOrigin() { + require(tx.origin == msg.sender, "sender != origin"); + _; + } + + // retrieveStatus retrieves the current swap record for the contract. + function retrieveStatus(Vector calldata v) + private view returns (bytes32, bytes32, uint256) + { + bytes32 k = contractKey(v); + bytes32 record = swaps[k]; + return (k, record, uint256(record)); + } + + // status returns the current state of the swap. + function status(Vector calldata v) + public view returns(Status memory) + { + (, bytes32 record, uint256 blockNum) = retrieveStatus(v); + Status memory r; + if (blockNum == 0) { + r.step = Step.Empty; + } else if (record == RefundRecord) { + r.step = Step.Refunded; + } else if (secretValidates(record, v.secretHash)) { + r.step = Step.Redeemed; + r.secret = record; + } else { + r.step = Step.Filled; + r.blockNumber = blockNum; + } + return r; + } + + // initiate initiates an array of Vectors. + function initiate(Vector[] calldata contracts) + public + payable + senderIsOrigin() + { + uint initVal = 0; + for (uint i = 0; i < contracts.length; i++) { + Vector calldata v = contracts[i]; + + require(v.value > 0, "0 val"); + require(v.refundTimestamp > 0, "0 refundTimestamp"); + + bytes32 k = contractKey(v); + bytes32 record = swaps[k]; + require(record == bytes32(0), "swap not empty"); + + record = bytes32(block.number); + require(!secretValidates(record, v.secretHash), "hash collision"); + + swaps[k] = record; + + initVal += v.value * 1 gwei; + } + + require(initVal == msg.value, "bad val"); + } + + // isRedeemable returns whether or not a swap identified by secretHash + // can be redeemed using secret. isRedeemable DOES NOT check if the caller + // is the participant in the vector. + function isRedeemable(Vector calldata v) + public + view + returns (bool) + { + (, bytes32 record, uint256 blockNum) = retrieveStatus(v); + return blockNum != 0 && !secretValidates(record, v.secretHash); + } + + // redeem redeems a Vector. It checks that the sender is not a contract, + // and that the secret hash hashes to secretHash. msg.value is tranfered + // from ETHSwap to the sender. + // + // To prevent reentry attack, it is very important to check the state of the + // contract first, and change the state before proceeding to send. That way, + // the nested attacking function will throw upon trying to call redeem a + // second time. Currently, reentry is also not possible because contracts + // cannot use this contract. + function redeem(Redemption[] calldata redemptions) + public + senderIsOrigin() + { + uint amountToRedeem = 0; + for (uint i = 0; i < redemptions.length; i++) { + Redemption calldata r = redemptions[i]; + + require(r.v.participant == msg.sender, "not authed"); + + (bytes32 k, bytes32 record, uint256 blockNum) = retrieveStatus(r.v); + + // To be redeemable, the record needs to represent a valid block + // number. + require(blockNum > 0 && blockNum < block.number, "unfilled swap"); + + // Can't already be redeemed. + require(!secretValidates(record, r.v.secretHash), "already redeemed"); + + // Are they presenting the correct secret? + require(secretValidates(r.secret, r.v.secretHash), "invalid secret"); + + swaps[k] = r.secret; + amountToRedeem += r.v.value * 1 gwei; + } + + (bool ok, ) = payable(msg.sender).call{value: amountToRedeem}(""); + require(ok == true, "transfer failed"); + } + + // refund refunds a Vector. It checks that the sender is not a contract + // and that the refund time has passed. msg.value is transfered from the + // contract to the sender = Vector.participant. + // + // It is important to note that this also uses call.value which comes with + // no restrictions on gas used. See redeem for more info. + function refund(Vector calldata v) + public + senderIsOrigin() + { + // Is this contract even in a refundable state? + require(block.timestamp >= v.refundTimestamp, "locktime not expired"); + + // Retrieve the record. + (bytes32 k, bytes32 record, uint256 blockNum) = retrieveStatus(v); + + // Is this swap initialized? + require(blockNum > 0 && blockNum <= block.number, "swap not active"); + + // Is it already redeemed? + require(!secretValidates(record, v.secretHash), "swap already redeemed"); + + // Is it already refunded? + require(record != RefundRecord, "swap already refunded"); + + swaps[k] = RefundRecord; + + (bool ok, ) = payable(v.initiator).call{value: v.value * 1 gwei}(""); + require(ok == true, "transfer failed"); + } +} diff --git a/dex/networks/eth/contracts/updatecontract.sh b/dex/networks/eth/contracts/updatecontract.sh index 6b25792108..623d7885db 100755 --- a/dex/networks/eth/contracts/updatecontract.sh +++ b/dex/networks/eth/contracts/updatecontract.sh @@ -23,6 +23,7 @@ then fi mkdir temp +mkdir -p ${PKG_NAME} solc --abi --bin --bin-runtime --overwrite --optimize ${SOLIDITY_FILE} -o ./temp/ BYTECODE=$(<./temp/${CONTRACT_NAME}.bin-runtime) diff --git a/dex/networks/eth/contracts/v1/BinRuntimeV1.go b/dex/networks/eth/contracts/v1/BinRuntimeV1.go new file mode 100644 index 0000000000..a7a9d0f326 --- /dev/null +++ b/dex/networks/eth/contracts/v1/BinRuntimeV1.go @@ -0,0 +1,6 @@ +// Code generated - DO NOT EDIT. +// This file is a generated binding and any manual changes will be lost. + +package v1 + +const ETHSwapRuntimeBin = "60806040526004361061007b5760003560e01c80637802689d1161004e5780637802689d1461011b578063d2544c061461013b578063eb84e7f21461015b578063ed7cbed71461019657600080fd5b8063428b16e11461008057806361a16e33146100a257806364a97bff146100d857806377d7e031146100eb575b600080fd5b34801561008c57600080fd5b506100a061009b366004610b6f565b6101b6565b005b3480156100ae57600080fd5b506100c26100bd366004610be4565b610447565b6040516100cf9190610c12565b60405180910390f35b6100a06100e6366004610c55565b610503565b3480156100f757600080fd5b5061010b610106366004610cb8565b61073b565b60405190151581526020016100cf565b34801561012757600080fd5b5061010b610136366004610be4565b6107b5565b34801561014757600080fd5b506100a0610156366004610be4565b6107e8565b34801561016757600080fd5b50610188610176366004610cda565b60006020819052908152604090205481565b6040519081526020016100cf565b3480156101a257600080fd5b506101886101b1366004610be4565b610a4a565b3233146101de5760405162461bcd60e51b81526004016101d590610cf3565b60405180910390fd5b6000805b828110156103b057368484838181106101fd576101fd610d1d565b60c0029190910191503390506102196080830160608401610d33565b6001600160a01b03161461025c5760405162461bcd60e51b815260206004820152600a6024820152691b9bdd08185d5d1a195960b21b60448201526064016101d5565b6000808061026984610b43565b92509250925060008111801561027e57504381105b6102ba5760405162461bcd60e51b815260206004820152600d60248201526c0756e66696c6c6564207377617609c1b60448201526064016101d5565b6102c582853561073b565b156103055760405162461bcd60e51b815260206004820152601060248201526f185b1c9958591e481c995919595b595960821b60448201526064016101d5565b61031460a0850135853561073b565b6103515760405162461bcd60e51b815260206004820152600e60248201526d1a5b9d985b1a59081cd958dc995d60921b60448201526064016101d5565b600083815260208190526040902060a0850180359091556103759060808601610d63565b61038390633b9aca00610da3565b6103979067ffffffffffffffff1687610dd3565b95505050505080806103a890610deb565b9150506101e2565b50604051600090339083908381818185875af1925050503d80600081146103f3576040519150601f19603f3d011682016040523d82523d6000602084013e6103f8565b606091505b50909150506001811515146104415760405162461bcd60e51b815260206004820152600f60248201526e1d1c985b9cd9995c8819985a5b1959608a1b60448201526064016101d5565b50505050565b60408051606081018252600080825260208201819052918101829052908061046e84610b43565b92509250506104986040805160608101909152806000815260006020820181905260409091015290565b816000036104bf578060005b908160038111156104b7576104b7610bfc565b9052506104fb565b600183016104cf578060036104a4565b6104da83863561073b565b156104ef5760028152602081018390526104fb565b60018152604081018290525b949350505050565b3233146105225760405162461bcd60e51b81526004016101d590610cf3565b6000805b828110156106fc573684848381811061054157610541610d1d565b905060a002019050600081608001602081019061055e9190610d63565b67ffffffffffffffff161161059d5760405162461bcd60e51b81526020600482015260056024820152640c081d985b60da1b60448201526064016101d5565b60006105af6060830160408401610d63565b67ffffffffffffffff16116105fa5760405162461bcd60e51b815260206004820152601160248201527003020726566756e6454696d657374616d7607c1b60448201526064016101d5565b600061060582610a4a565b60008181526020819052604090205490915080156106565760405162461bcd60e51b815260206004820152600e60248201526d73776170206e6f7420656d70747960901b60448201526064016101d5565b504361066381843561073b565b156106a15760405162461bcd60e51b815260206004820152600e60248201526d3430b9b41031b7b63634b9b4b7b760911b60448201526064016101d5565b60008281526020819052604090208190556106c260a0840160808501610d63565b6106d090633b9aca00610da3565b6106e49067ffffffffffffffff1686610dd3565b945050505080806106f490610deb565b915050610526565b503481146107365760405162461bcd60e51b8152602060048201526007602482015266189859081d985b60ca1b60448201526064016101d5565b505050565b60008160028460405160200161075391815260200190565b60408051601f198184030181529082905261076d91610e04565b602060405180830381855afa15801561078a573d6000803e3d6000fd5b5050506040513d601f19601f820116820180604052508101906107ad9190610e3f565b149392505050565b60008060006107c384610b43565b9250925050806000141580156104fb57506107df82853561073b565b15949350505050565b3233146108075760405162461bcd60e51b81526004016101d590610cf3565b6108176060820160408301610d63565b67ffffffffffffffff164210156108675760405162461bcd60e51b81526020600482015260146024820152731b1bd8dadd1a5b59481b9bdd08195e1c1a5c995960621b60448201526064016101d5565b600080600061087584610b43565b92509250925060008111801561088b5750438111155b6108c95760405162461bcd60e51b815260206004820152600f60248201526e73776170206e6f742061637469766560881b60448201526064016101d5565b6108d482853561073b565b156109195760405162461bcd60e51b81526020600482015260156024820152741cddd85c08185b1c9958591e481c995919595b5959605a1b60448201526064016101d5565b600182016109615760405162461bcd60e51b81526020600482015260156024820152741cddd85c08185b1c9958591e481c99599d5b991959605a1b60448201526064016101d5565b600083815260208181526040808320600019905561098491908701908701610d33565b6001600160a01b031661099d60a0870160808801610d63565b6109ab90633b9aca00610da3565b67ffffffffffffffff1660405160006040518083038185875af1925050503d80600081146109f5576040519150601f19603f3d011682016040523d82523d6000602084013e6109fa565b606091505b5090915050600181151514610a435760405162461bcd60e51b815260206004820152600f60248201526e1d1c985b9cd9995c8819985a5b1959608a1b60448201526064016101d5565b5050505050565b600060028235610a606040850160208601610d33565b60601b846060016020810190610a769190610d33565b60601b610a8960a0870160808801610d63565b60c01b610a9c6060880160408901610d63565b6040805160208101969096526bffffffffffffffffffffffff19948516908601529190921660548401526001600160c01b0319918216606884015260c01b16607082015260780160408051601f1981840301815290829052610afd91610e04565b602060405180830381855afa158015610b1a573d6000803e3d6000fd5b5050506040513d601f19601f82011682018060405250810190610b3d9190610e3f565b92915050565b600080600080610b5285610a4a565b600081815260208190526040902054909690955085945092505050565b60008060208385031215610b8257600080fd5b823567ffffffffffffffff80821115610b9a57600080fd5b818501915085601f830112610bae57600080fd5b813581811115610bbd57600080fd5b86602060c083028501011115610bd257600080fd5b60209290920196919550909350505050565b600060a08284031215610bf657600080fd5b50919050565b634e487b7160e01b600052602160045260246000fd5b8151606082019060048110610c3757634e487b7160e01b600052602160045260246000fd5b80835250602083015160208301526040830151604083015292915050565b60008060208385031215610c6857600080fd5b823567ffffffffffffffff80821115610c8057600080fd5b818501915085601f830112610c9457600080fd5b813581811115610ca357600080fd5b86602060a083028501011115610bd257600080fd5b60008060408385031215610ccb57600080fd5b50508035926020909101359150565b600060208284031215610cec57600080fd5b5035919050565b60208082526010908201526f39b2b73232b910109e9037b934b3b4b760811b604082015260600190565b634e487b7160e01b600052603260045260246000fd5b600060208284031215610d4557600080fd5b81356001600160a01b0381168114610d5c57600080fd5b9392505050565b600060208284031215610d7557600080fd5b813567ffffffffffffffff81168114610d5c57600080fd5b634e487b7160e01b600052601160045260246000fd5b600067ffffffffffffffff80831681851681830481118215151615610dca57610dca610d8d565b02949350505050565b60008219821115610de657610de6610d8d565b500190565b600060018201610dfd57610dfd610d8d565b5060010190565b6000825160005b81811015610e255760208186018101518583015201610e0b565b81811115610e34576000828501525b509190910192915050565b600060208284031215610e5157600080fd5b505191905056fea2646970667358221220d735868682c69bb69ed936f11e2f136e7da44cb347e0b1d67acd1e088ea13dd664736f6c634300080f0033" diff --git a/dex/networks/eth/contracts/v1/contract.go b/dex/networks/eth/contracts/v1/contract.go new file mode 100644 index 0000000000..07de2ac59e --- /dev/null +++ b/dex/networks/eth/contracts/v1/contract.go @@ -0,0 +1,442 @@ +// Code generated - DO NOT EDIT. +// This file is a generated binding and any manual changes will be lost. + +package v1 + +import ( + "errors" + "math/big" + "strings" + + ethereum "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/event" +) + +// Reference imports to suppress errors if they are not otherwise used. +var ( + _ = errors.New + _ = big.NewInt + _ = strings.NewReader + _ = ethereum.NotFound + _ = bind.Bind + _ = common.Big1 + _ = types.BloomLookup + _ = event.NewSubscription +) + +// ETHSwapRedemption is an auto generated low-level Go binding around an user-defined struct. +type ETHSwapRedemption struct { + V ETHSwapVector + Secret [32]byte +} + +// ETHSwapStatus is an auto generated low-level Go binding around an user-defined struct. +type ETHSwapStatus struct { + Step uint8 + Secret [32]byte + BlockNumber *big.Int +} + +// ETHSwapVector is an auto generated low-level Go binding around an user-defined struct. +type ETHSwapVector struct { + SecretHash [32]byte + Initiator common.Address + RefundTimestamp uint64 + Participant common.Address + Value uint64 +} + +// ETHSwapMetaData contains all meta data concerning the ETHSwap contract. +var ETHSwapMetaData = &bind.MetaData{ + ABI: "[{\"inputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"inputs\":[{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"secretHash\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"initiator\",\"type\":\"address\"},{\"internalType\":\"uint64\",\"name\":\"refundTimestamp\",\"type\":\"uint64\"},{\"internalType\":\"address\",\"name\":\"participant\",\"type\":\"address\"},{\"internalType\":\"uint64\",\"name\":\"value\",\"type\":\"uint64\"}],\"internalType\":\"structETHSwap.Vector\",\"name\":\"v\",\"type\":\"tuple\"}],\"name\":\"contractKey\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"secretHash\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"initiator\",\"type\":\"address\"},{\"internalType\":\"uint64\",\"name\":\"refundTimestamp\",\"type\":\"uint64\"},{\"internalType\":\"address\",\"name\":\"participant\",\"type\":\"address\"},{\"internalType\":\"uint64\",\"name\":\"value\",\"type\":\"uint64\"}],\"internalType\":\"structETHSwap.Vector[]\",\"name\":\"contracts\",\"type\":\"tuple[]\"}],\"name\":\"initiate\",\"outputs\":[],\"stateMutability\":\"payable\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"secretHash\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"initiator\",\"type\":\"address\"},{\"internalType\":\"uint64\",\"name\":\"refundTimestamp\",\"type\":\"uint64\"},{\"internalType\":\"address\",\"name\":\"participant\",\"type\":\"address\"},{\"internalType\":\"uint64\",\"name\":\"value\",\"type\":\"uint64\"}],\"internalType\":\"structETHSwap.Vector\",\"name\":\"v\",\"type\":\"tuple\"}],\"name\":\"isRedeemable\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"secretHash\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"initiator\",\"type\":\"address\"},{\"internalType\":\"uint64\",\"name\":\"refundTimestamp\",\"type\":\"uint64\"},{\"internalType\":\"address\",\"name\":\"participant\",\"type\":\"address\"},{\"internalType\":\"uint64\",\"name\":\"value\",\"type\":\"uint64\"}],\"internalType\":\"structETHSwap.Vector\",\"name\":\"v\",\"type\":\"tuple\"},{\"internalType\":\"bytes32\",\"name\":\"secret\",\"type\":\"bytes32\"}],\"internalType\":\"structETHSwap.Redemption[]\",\"name\":\"redemptions\",\"type\":\"tuple[]\"}],\"name\":\"redeem\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"secretHash\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"initiator\",\"type\":\"address\"},{\"internalType\":\"uint64\",\"name\":\"refundTimestamp\",\"type\":\"uint64\"},{\"internalType\":\"address\",\"name\":\"participant\",\"type\":\"address\"},{\"internalType\":\"uint64\",\"name\":\"value\",\"type\":\"uint64\"}],\"internalType\":\"structETHSwap.Vector\",\"name\":\"v\",\"type\":\"tuple\"}],\"name\":\"refund\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"secret\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"secretHash\",\"type\":\"bytes32\"}],\"name\":\"secretValidates\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"secretHash\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"initiator\",\"type\":\"address\"},{\"internalType\":\"uint64\",\"name\":\"refundTimestamp\",\"type\":\"uint64\"},{\"internalType\":\"address\",\"name\":\"participant\",\"type\":\"address\"},{\"internalType\":\"uint64\",\"name\":\"value\",\"type\":\"uint64\"}],\"internalType\":\"structETHSwap.Vector\",\"name\":\"v\",\"type\":\"tuple\"}],\"name\":\"status\",\"outputs\":[{\"components\":[{\"internalType\":\"enumETHSwap.Step\",\"name\":\"step\",\"type\":\"uint8\"},{\"internalType\":\"bytes32\",\"name\":\"secret\",\"type\":\"bytes32\"},{\"internalType\":\"uint256\",\"name\":\"blockNumber\",\"type\":\"uint256\"}],\"internalType\":\"structETHSwap.Status\",\"name\":\"\",\"type\":\"tuple\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"name\":\"swaps\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"}]", + Bin: "0x608060405234801561001057600080fd5b50610e8e806100206000396000f3fe60806040526004361061007b5760003560e01c80637802689d1161004e5780637802689d1461011b578063d2544c061461013b578063eb84e7f21461015b578063ed7cbed71461019657600080fd5b8063428b16e11461008057806361a16e33146100a257806364a97bff146100d857806377d7e031146100eb575b600080fd5b34801561008c57600080fd5b506100a061009b366004610b6f565b6101b6565b005b3480156100ae57600080fd5b506100c26100bd366004610be4565b610447565b6040516100cf9190610c12565b60405180910390f35b6100a06100e6366004610c55565b610503565b3480156100f757600080fd5b5061010b610106366004610cb8565b61073b565b60405190151581526020016100cf565b34801561012757600080fd5b5061010b610136366004610be4565b6107b5565b34801561014757600080fd5b506100a0610156366004610be4565b6107e8565b34801561016757600080fd5b50610188610176366004610cda565b60006020819052908152604090205481565b6040519081526020016100cf565b3480156101a257600080fd5b506101886101b1366004610be4565b610a4a565b3233146101de5760405162461bcd60e51b81526004016101d590610cf3565b60405180910390fd5b6000805b828110156103b057368484838181106101fd576101fd610d1d565b60c0029190910191503390506102196080830160608401610d33565b6001600160a01b03161461025c5760405162461bcd60e51b815260206004820152600a6024820152691b9bdd08185d5d1a195960b21b60448201526064016101d5565b6000808061026984610b43565b92509250925060008111801561027e57504381105b6102ba5760405162461bcd60e51b815260206004820152600d60248201526c0756e66696c6c6564207377617609c1b60448201526064016101d5565b6102c582853561073b565b156103055760405162461bcd60e51b815260206004820152601060248201526f185b1c9958591e481c995919595b595960821b60448201526064016101d5565b61031460a0850135853561073b565b6103515760405162461bcd60e51b815260206004820152600e60248201526d1a5b9d985b1a59081cd958dc995d60921b60448201526064016101d5565b600083815260208190526040902060a0850180359091556103759060808601610d63565b61038390633b9aca00610da3565b6103979067ffffffffffffffff1687610dd3565b95505050505080806103a890610deb565b9150506101e2565b50604051600090339083908381818185875af1925050503d80600081146103f3576040519150601f19603f3d011682016040523d82523d6000602084013e6103f8565b606091505b50909150506001811515146104415760405162461bcd60e51b815260206004820152600f60248201526e1d1c985b9cd9995c8819985a5b1959608a1b60448201526064016101d5565b50505050565b60408051606081018252600080825260208201819052918101829052908061046e84610b43565b92509250506104986040805160608101909152806000815260006020820181905260409091015290565b816000036104bf578060005b908160038111156104b7576104b7610bfc565b9052506104fb565b600183016104cf578060036104a4565b6104da83863561073b565b156104ef5760028152602081018390526104fb565b60018152604081018290525b949350505050565b3233146105225760405162461bcd60e51b81526004016101d590610cf3565b6000805b828110156106fc573684848381811061054157610541610d1d565b905060a002019050600081608001602081019061055e9190610d63565b67ffffffffffffffff161161059d5760405162461bcd60e51b81526020600482015260056024820152640c081d985b60da1b60448201526064016101d5565b60006105af6060830160408401610d63565b67ffffffffffffffff16116105fa5760405162461bcd60e51b815260206004820152601160248201527003020726566756e6454696d657374616d7607c1b60448201526064016101d5565b600061060582610a4a565b60008181526020819052604090205490915080156106565760405162461bcd60e51b815260206004820152600e60248201526d73776170206e6f7420656d70747960901b60448201526064016101d5565b504361066381843561073b565b156106a15760405162461bcd60e51b815260206004820152600e60248201526d3430b9b41031b7b63634b9b4b7b760911b60448201526064016101d5565b60008281526020819052604090208190556106c260a0840160808501610d63565b6106d090633b9aca00610da3565b6106e49067ffffffffffffffff1686610dd3565b945050505080806106f490610deb565b915050610526565b503481146107365760405162461bcd60e51b8152602060048201526007602482015266189859081d985b60ca1b60448201526064016101d5565b505050565b60008160028460405160200161075391815260200190565b60408051601f198184030181529082905261076d91610e04565b602060405180830381855afa15801561078a573d6000803e3d6000fd5b5050506040513d601f19601f820116820180604052508101906107ad9190610e3f565b149392505050565b60008060006107c384610b43565b9250925050806000141580156104fb57506107df82853561073b565b15949350505050565b3233146108075760405162461bcd60e51b81526004016101d590610cf3565b6108176060820160408301610d63565b67ffffffffffffffff164210156108675760405162461bcd60e51b81526020600482015260146024820152731b1bd8dadd1a5b59481b9bdd08195e1c1a5c995960621b60448201526064016101d5565b600080600061087584610b43565b92509250925060008111801561088b5750438111155b6108c95760405162461bcd60e51b815260206004820152600f60248201526e73776170206e6f742061637469766560881b60448201526064016101d5565b6108d482853561073b565b156109195760405162461bcd60e51b81526020600482015260156024820152741cddd85c08185b1c9958591e481c995919595b5959605a1b60448201526064016101d5565b600182016109615760405162461bcd60e51b81526020600482015260156024820152741cddd85c08185b1c9958591e481c99599d5b991959605a1b60448201526064016101d5565b600083815260208181526040808320600019905561098491908701908701610d33565b6001600160a01b031661099d60a0870160808801610d63565b6109ab90633b9aca00610da3565b67ffffffffffffffff1660405160006040518083038185875af1925050503d80600081146109f5576040519150601f19603f3d011682016040523d82523d6000602084013e6109fa565b606091505b5090915050600181151514610a435760405162461bcd60e51b815260206004820152600f60248201526e1d1c985b9cd9995c8819985a5b1959608a1b60448201526064016101d5565b5050505050565b600060028235610a606040850160208601610d33565b60601b846060016020810190610a769190610d33565b60601b610a8960a0870160808801610d63565b60c01b610a9c6060880160408901610d63565b6040805160208101969096526bffffffffffffffffffffffff19948516908601529190921660548401526001600160c01b0319918216606884015260c01b16607082015260780160408051601f1981840301815290829052610afd91610e04565b602060405180830381855afa158015610b1a573d6000803e3d6000fd5b5050506040513d601f19601f82011682018060405250810190610b3d9190610e3f565b92915050565b600080600080610b5285610a4a565b600081815260208190526040902054909690955085945092505050565b60008060208385031215610b8257600080fd5b823567ffffffffffffffff80821115610b9a57600080fd5b818501915085601f830112610bae57600080fd5b813581811115610bbd57600080fd5b86602060c083028501011115610bd257600080fd5b60209290920196919550909350505050565b600060a08284031215610bf657600080fd5b50919050565b634e487b7160e01b600052602160045260246000fd5b8151606082019060048110610c3757634e487b7160e01b600052602160045260246000fd5b80835250602083015160208301526040830151604083015292915050565b60008060208385031215610c6857600080fd5b823567ffffffffffffffff80821115610c8057600080fd5b818501915085601f830112610c9457600080fd5b813581811115610ca357600080fd5b86602060a083028501011115610bd257600080fd5b60008060408385031215610ccb57600080fd5b50508035926020909101359150565b600060208284031215610cec57600080fd5b5035919050565b60208082526010908201526f39b2b73232b910109e9037b934b3b4b760811b604082015260600190565b634e487b7160e01b600052603260045260246000fd5b600060208284031215610d4557600080fd5b81356001600160a01b0381168114610d5c57600080fd5b9392505050565b600060208284031215610d7557600080fd5b813567ffffffffffffffff81168114610d5c57600080fd5b634e487b7160e01b600052601160045260246000fd5b600067ffffffffffffffff80831681851681830481118215151615610dca57610dca610d8d565b02949350505050565b60008219821115610de657610de6610d8d565b500190565b600060018201610dfd57610dfd610d8d565b5060010190565b6000825160005b81811015610e255760208186018101518583015201610e0b565b81811115610e34576000828501525b509190910192915050565b600060208284031215610e5157600080fd5b505191905056fea2646970667358221220d735868682c69bb69ed936f11e2f136e7da44cb347e0b1d67acd1e088ea13dd664736f6c634300080f0033", +} + +// ETHSwapABI is the input ABI used to generate the binding from. +// Deprecated: Use ETHSwapMetaData.ABI instead. +var ETHSwapABI = ETHSwapMetaData.ABI + +// ETHSwapBin is the compiled bytecode used for deploying new contracts. +// Deprecated: Use ETHSwapMetaData.Bin instead. +var ETHSwapBin = ETHSwapMetaData.Bin + +// DeployETHSwap deploys a new Ethereum contract, binding an instance of ETHSwap to it. +func DeployETHSwap(auth *bind.TransactOpts, backend bind.ContractBackend) (common.Address, *types.Transaction, *ETHSwap, error) { + parsed, err := ETHSwapMetaData.GetAbi() + if err != nil { + return common.Address{}, nil, nil, err + } + if parsed == nil { + return common.Address{}, nil, nil, errors.New("GetABI returned nil") + } + + address, tx, contract, err := bind.DeployContract(auth, *parsed, common.FromHex(ETHSwapBin), backend) + if err != nil { + return common.Address{}, nil, nil, err + } + return address, tx, ÐSwap{ETHSwapCaller: ETHSwapCaller{contract: contract}, ETHSwapTransactor: ETHSwapTransactor{contract: contract}, ETHSwapFilterer: ETHSwapFilterer{contract: contract}}, nil +} + +// ETHSwap is an auto generated Go binding around an Ethereum contract. +type ETHSwap struct { + ETHSwapCaller // Read-only binding to the contract + ETHSwapTransactor // Write-only binding to the contract + ETHSwapFilterer // Log filterer for contract events +} + +// ETHSwapCaller is an auto generated read-only Go binding around an Ethereum contract. +type ETHSwapCaller struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// ETHSwapTransactor is an auto generated write-only Go binding around an Ethereum contract. +type ETHSwapTransactor struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// ETHSwapFilterer is an auto generated log filtering Go binding around an Ethereum contract events. +type ETHSwapFilterer struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// ETHSwapSession is an auto generated Go binding around an Ethereum contract, +// with pre-set call and transact options. +type ETHSwapSession struct { + Contract *ETHSwap // Generic contract binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session +} + +// ETHSwapCallerSession is an auto generated read-only Go binding around an Ethereum contract, +// with pre-set call options. +type ETHSwapCallerSession struct { + Contract *ETHSwapCaller // Generic contract caller binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session +} + +// ETHSwapTransactorSession is an auto generated write-only Go binding around an Ethereum contract, +// with pre-set transact options. +type ETHSwapTransactorSession struct { + Contract *ETHSwapTransactor // Generic contract transactor binding to set the session for + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session +} + +// ETHSwapRaw is an auto generated low-level Go binding around an Ethereum contract. +type ETHSwapRaw struct { + Contract *ETHSwap // Generic contract binding to access the raw methods on +} + +// ETHSwapCallerRaw is an auto generated low-level read-only Go binding around an Ethereum contract. +type ETHSwapCallerRaw struct { + Contract *ETHSwapCaller // Generic read-only contract binding to access the raw methods on +} + +// ETHSwapTransactorRaw is an auto generated low-level write-only Go binding around an Ethereum contract. +type ETHSwapTransactorRaw struct { + Contract *ETHSwapTransactor // Generic write-only contract binding to access the raw methods on +} + +// NewETHSwap creates a new instance of ETHSwap, bound to a specific deployed contract. +func NewETHSwap(address common.Address, backend bind.ContractBackend) (*ETHSwap, error) { + contract, err := bindETHSwap(address, backend, backend, backend) + if err != nil { + return nil, err + } + return ÐSwap{ETHSwapCaller: ETHSwapCaller{contract: contract}, ETHSwapTransactor: ETHSwapTransactor{contract: contract}, ETHSwapFilterer: ETHSwapFilterer{contract: contract}}, nil +} + +// NewETHSwapCaller creates a new read-only instance of ETHSwap, bound to a specific deployed contract. +func NewETHSwapCaller(address common.Address, caller bind.ContractCaller) (*ETHSwapCaller, error) { + contract, err := bindETHSwap(address, caller, nil, nil) + if err != nil { + return nil, err + } + return ÐSwapCaller{contract: contract}, nil +} + +// NewETHSwapTransactor creates a new write-only instance of ETHSwap, bound to a specific deployed contract. +func NewETHSwapTransactor(address common.Address, transactor bind.ContractTransactor) (*ETHSwapTransactor, error) { + contract, err := bindETHSwap(address, nil, transactor, nil) + if err != nil { + return nil, err + } + return ÐSwapTransactor{contract: contract}, nil +} + +// NewETHSwapFilterer creates a new log filterer instance of ETHSwap, bound to a specific deployed contract. +func NewETHSwapFilterer(address common.Address, filterer bind.ContractFilterer) (*ETHSwapFilterer, error) { + contract, err := bindETHSwap(address, nil, nil, filterer) + if err != nil { + return nil, err + } + return ÐSwapFilterer{contract: contract}, nil +} + +// bindETHSwap binds a generic wrapper to an already deployed contract. +func bindETHSwap(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor, filterer bind.ContractFilterer) (*bind.BoundContract, error) { + parsed, err := abi.JSON(strings.NewReader(ETHSwapABI)) + if err != nil { + return nil, err + } + return bind.NewBoundContract(address, parsed, caller, transactor, filterer), nil +} + +// Call invokes the (constant) contract method with params as input values and +// sets the output to result. The result type might be a single field for simple +// returns, a slice of interfaces for anonymous returns and a struct for named +// returns. +func (_ETHSwap *ETHSwapRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { + return _ETHSwap.Contract.ETHSwapCaller.contract.Call(opts, result, method, params...) +} + +// Transfer initiates a plain transaction to move funds to the contract, calling +// its default method if one is available. +func (_ETHSwap *ETHSwapRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _ETHSwap.Contract.ETHSwapTransactor.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_ETHSwap *ETHSwapRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _ETHSwap.Contract.ETHSwapTransactor.contract.Transact(opts, method, params...) +} + +// Call invokes the (constant) contract method with params as input values and +// sets the output to result. The result type might be a single field for simple +// returns, a slice of interfaces for anonymous returns and a struct for named +// returns. +func (_ETHSwap *ETHSwapCallerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { + return _ETHSwap.Contract.contract.Call(opts, result, method, params...) +} + +// Transfer initiates a plain transaction to move funds to the contract, calling +// its default method if one is available. +func (_ETHSwap *ETHSwapTransactorRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _ETHSwap.Contract.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_ETHSwap *ETHSwapTransactorRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _ETHSwap.Contract.contract.Transact(opts, method, params...) +} + +// ContractKey is a free data retrieval call binding the contract method 0xed7cbed7. +// +// Solidity: function contractKey((bytes32,address,uint64,address,uint64) v) pure returns(bytes32) +func (_ETHSwap *ETHSwapCaller) ContractKey(opts *bind.CallOpts, v ETHSwapVector) ([32]byte, error) { + var out []interface{} + err := _ETHSwap.contract.Call(opts, &out, "contractKey", v) + + if err != nil { + return *new([32]byte), err + } + + out0 := *abi.ConvertType(out[0], new([32]byte)).(*[32]byte) + + return out0, err + +} + +// ContractKey is a free data retrieval call binding the contract method 0xed7cbed7. +// +// Solidity: function contractKey((bytes32,address,uint64,address,uint64) v) pure returns(bytes32) +func (_ETHSwap *ETHSwapSession) ContractKey(v ETHSwapVector) ([32]byte, error) { + return _ETHSwap.Contract.ContractKey(&_ETHSwap.CallOpts, v) +} + +// ContractKey is a free data retrieval call binding the contract method 0xed7cbed7. +// +// Solidity: function contractKey((bytes32,address,uint64,address,uint64) v) pure returns(bytes32) +func (_ETHSwap *ETHSwapCallerSession) ContractKey(v ETHSwapVector) ([32]byte, error) { + return _ETHSwap.Contract.ContractKey(&_ETHSwap.CallOpts, v) +} + +// IsRedeemable is a free data retrieval call binding the contract method 0x7802689d. +// +// Solidity: function isRedeemable((bytes32,address,uint64,address,uint64) v) view returns(bool) +func (_ETHSwap *ETHSwapCaller) IsRedeemable(opts *bind.CallOpts, v ETHSwapVector) (bool, error) { + var out []interface{} + err := _ETHSwap.contract.Call(opts, &out, "isRedeemable", v) + + if err != nil { + return *new(bool), err + } + + out0 := *abi.ConvertType(out[0], new(bool)).(*bool) + + return out0, err + +} + +// IsRedeemable is a free data retrieval call binding the contract method 0x7802689d. +// +// Solidity: function isRedeemable((bytes32,address,uint64,address,uint64) v) view returns(bool) +func (_ETHSwap *ETHSwapSession) IsRedeemable(v ETHSwapVector) (bool, error) { + return _ETHSwap.Contract.IsRedeemable(&_ETHSwap.CallOpts, v) +} + +// IsRedeemable is a free data retrieval call binding the contract method 0x7802689d. +// +// Solidity: function isRedeemable((bytes32,address,uint64,address,uint64) v) view returns(bool) +func (_ETHSwap *ETHSwapCallerSession) IsRedeemable(v ETHSwapVector) (bool, error) { + return _ETHSwap.Contract.IsRedeemable(&_ETHSwap.CallOpts, v) +} + +// SecretValidates is a free data retrieval call binding the contract method 0x77d7e031. +// +// Solidity: function secretValidates(bytes32 secret, bytes32 secretHash) pure returns(bool) +func (_ETHSwap *ETHSwapCaller) SecretValidates(opts *bind.CallOpts, secret [32]byte, secretHash [32]byte) (bool, error) { + var out []interface{} + err := _ETHSwap.contract.Call(opts, &out, "secretValidates", secret, secretHash) + + if err != nil { + return *new(bool), err + } + + out0 := *abi.ConvertType(out[0], new(bool)).(*bool) + + return out0, err + +} + +// SecretValidates is a free data retrieval call binding the contract method 0x77d7e031. +// +// Solidity: function secretValidates(bytes32 secret, bytes32 secretHash) pure returns(bool) +func (_ETHSwap *ETHSwapSession) SecretValidates(secret [32]byte, secretHash [32]byte) (bool, error) { + return _ETHSwap.Contract.SecretValidates(&_ETHSwap.CallOpts, secret, secretHash) +} + +// SecretValidates is a free data retrieval call binding the contract method 0x77d7e031. +// +// Solidity: function secretValidates(bytes32 secret, bytes32 secretHash) pure returns(bool) +func (_ETHSwap *ETHSwapCallerSession) SecretValidates(secret [32]byte, secretHash [32]byte) (bool, error) { + return _ETHSwap.Contract.SecretValidates(&_ETHSwap.CallOpts, secret, secretHash) +} + +// Status is a free data retrieval call binding the contract method 0x61a16e33. +// +// Solidity: function status((bytes32,address,uint64,address,uint64) v) view returns((uint8,bytes32,uint256)) +func (_ETHSwap *ETHSwapCaller) Status(opts *bind.CallOpts, v ETHSwapVector) (ETHSwapStatus, error) { + var out []interface{} + err := _ETHSwap.contract.Call(opts, &out, "status", v) + + if err != nil { + return *new(ETHSwapStatus), err + } + + out0 := *abi.ConvertType(out[0], new(ETHSwapStatus)).(*ETHSwapStatus) + + return out0, err + +} + +// Status is a free data retrieval call binding the contract method 0x61a16e33. +// +// Solidity: function status((bytes32,address,uint64,address,uint64) v) view returns((uint8,bytes32,uint256)) +func (_ETHSwap *ETHSwapSession) Status(v ETHSwapVector) (ETHSwapStatus, error) { + return _ETHSwap.Contract.Status(&_ETHSwap.CallOpts, v) +} + +// Status is a free data retrieval call binding the contract method 0x61a16e33. +// +// Solidity: function status((bytes32,address,uint64,address,uint64) v) view returns((uint8,bytes32,uint256)) +func (_ETHSwap *ETHSwapCallerSession) Status(v ETHSwapVector) (ETHSwapStatus, error) { + return _ETHSwap.Contract.Status(&_ETHSwap.CallOpts, v) +} + +// Swaps is a free data retrieval call binding the contract method 0xeb84e7f2. +// +// Solidity: function swaps(bytes32 ) view returns(bytes32) +func (_ETHSwap *ETHSwapCaller) Swaps(opts *bind.CallOpts, arg0 [32]byte) ([32]byte, error) { + var out []interface{} + err := _ETHSwap.contract.Call(opts, &out, "swaps", arg0) + + if err != nil { + return *new([32]byte), err + } + + out0 := *abi.ConvertType(out[0], new([32]byte)).(*[32]byte) + + return out0, err + +} + +// Swaps is a free data retrieval call binding the contract method 0xeb84e7f2. +// +// Solidity: function swaps(bytes32 ) view returns(bytes32) +func (_ETHSwap *ETHSwapSession) Swaps(arg0 [32]byte) ([32]byte, error) { + return _ETHSwap.Contract.Swaps(&_ETHSwap.CallOpts, arg0) +} + +// Swaps is a free data retrieval call binding the contract method 0xeb84e7f2. +// +// Solidity: function swaps(bytes32 ) view returns(bytes32) +func (_ETHSwap *ETHSwapCallerSession) Swaps(arg0 [32]byte) ([32]byte, error) { + return _ETHSwap.Contract.Swaps(&_ETHSwap.CallOpts, arg0) +} + +// Initiate is a paid mutator transaction binding the contract method 0x64a97bff. +// +// Solidity: function initiate((bytes32,address,uint64,address,uint64)[] contracts) payable returns() +func (_ETHSwap *ETHSwapTransactor) Initiate(opts *bind.TransactOpts, contracts []ETHSwapVector) (*types.Transaction, error) { + return _ETHSwap.contract.Transact(opts, "initiate", contracts) +} + +// Initiate is a paid mutator transaction binding the contract method 0x64a97bff. +// +// Solidity: function initiate((bytes32,address,uint64,address,uint64)[] contracts) payable returns() +func (_ETHSwap *ETHSwapSession) Initiate(contracts []ETHSwapVector) (*types.Transaction, error) { + return _ETHSwap.Contract.Initiate(&_ETHSwap.TransactOpts, contracts) +} + +// Initiate is a paid mutator transaction binding the contract method 0x64a97bff. +// +// Solidity: function initiate((bytes32,address,uint64,address,uint64)[] contracts) payable returns() +func (_ETHSwap *ETHSwapTransactorSession) Initiate(contracts []ETHSwapVector) (*types.Transaction, error) { + return _ETHSwap.Contract.Initiate(&_ETHSwap.TransactOpts, contracts) +} + +// Redeem is a paid mutator transaction binding the contract method 0x428b16e1. +// +// Solidity: function redeem(((bytes32,address,uint64,address,uint64),bytes32)[] redemptions) returns() +func (_ETHSwap *ETHSwapTransactor) Redeem(opts *bind.TransactOpts, redemptions []ETHSwapRedemption) (*types.Transaction, error) { + return _ETHSwap.contract.Transact(opts, "redeem", redemptions) +} + +// Redeem is a paid mutator transaction binding the contract method 0x428b16e1. +// +// Solidity: function redeem(((bytes32,address,uint64,address,uint64),bytes32)[] redemptions) returns() +func (_ETHSwap *ETHSwapSession) Redeem(redemptions []ETHSwapRedemption) (*types.Transaction, error) { + return _ETHSwap.Contract.Redeem(&_ETHSwap.TransactOpts, redemptions) +} + +// Redeem is a paid mutator transaction binding the contract method 0x428b16e1. +// +// Solidity: function redeem(((bytes32,address,uint64,address,uint64),bytes32)[] redemptions) returns() +func (_ETHSwap *ETHSwapTransactorSession) Redeem(redemptions []ETHSwapRedemption) (*types.Transaction, error) { + return _ETHSwap.Contract.Redeem(&_ETHSwap.TransactOpts, redemptions) +} + +// Refund is a paid mutator transaction binding the contract method 0xd2544c06. +// +// Solidity: function refund((bytes32,address,uint64,address,uint64) v) returns() +func (_ETHSwap *ETHSwapTransactor) Refund(opts *bind.TransactOpts, v ETHSwapVector) (*types.Transaction, error) { + return _ETHSwap.contract.Transact(opts, "refund", v) +} + +// Refund is a paid mutator transaction binding the contract method 0xd2544c06. +// +// Solidity: function refund((bytes32,address,uint64,address,uint64) v) returns() +func (_ETHSwap *ETHSwapSession) Refund(v ETHSwapVector) (*types.Transaction, error) { + return _ETHSwap.Contract.Refund(&_ETHSwap.TransactOpts, v) +} + +// Refund is a paid mutator transaction binding the contract method 0xd2544c06. +// +// Solidity: function refund((bytes32,address,uint64,address,uint64) v) returns() +func (_ETHSwap *ETHSwapTransactorSession) Refund(v ETHSwapVector) (*types.Transaction, error) { + return _ETHSwap.Contract.Refund(&_ETHSwap.TransactOpts, v) +} diff --git a/dex/networks/eth/params.go b/dex/networks/eth/params.go index 7f5211ff11..19059217c3 100644 --- a/dex/networks/eth/params.go +++ b/dex/networks/eth/params.go @@ -15,6 +15,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 +66,7 @@ var ( VersionedGases = map[uint32]*Gases{ 0: v0Gases, + 1: v1Gases, } ContractAddresses = map[uint32]map[dex.Network]common.Address{ @@ -73,6 +75,11 @@ var ( dex.Testnet: common.HexToAddress("0x73bc803A2604b2c58B8680c3CE1b14489842EF16"), // tx 0xb24b44beebc0e34fa57bd9f08f9aaf70f40c654f3ddbe0b15dd942ee23ce02f4 dex.Simnet: common.HexToAddress("0x2f68e723b8989ba1c6a9f03e42f33cb7dc9d606f"), }, + 1: { + dex.Mainnet: common.Address{}, + dex.Testnet: common.Address{}, + dex.Simnet: common.Address{}, + }, } MultiBalanceAddresses = map[dex.Network]common.Address{ @@ -89,6 +96,25 @@ var v0Gases = &Gases{ Refund: 57000, // 43,014 actual -- https://goerli.etherscan.io/tx/0x586ed4cb7dab043f98d4cc08930d9eb291b0052d140d949b20232ceb6ad15f25 } +var v1Gases = &Gases{ + // First swap used 48769 gas Recommended Gases.Swap = 63399 + // 4 additional swaps averaged 26904 gas each. Recommended Gases.SwapAdd = 34975 + // [48769 75679 102590 129486 156385] + Swap: 63_399, + SwapAdd: 34_975, + // First redeem used 39792 gas. Recommended Gases.Redeem = 51729 + // 4 additional redeems averaged 11037 gas each. recommended Gases.RedeemAdd = 14348 + // [39792 50836 61880 72898 83943] + + // Compare expected Swap + Redeem = 88k with UniSwap v2: 102k, v3: 127k + // A 1-match order is cheaper that UniSwap with v1 gases. + Redeem: 51_729, + RedeemAdd: 14_348, + // Average of 5 refunds: 40155. Recommended Gases.Refund = 52201 + // [40158 40158 40158 40158 40146] + Refund: 52_201, +} + // LoadGenesisFile loads a Genesis config from a json file. func LoadGenesisFile(genesisFile string) (*core.Genesis, error) { fid, err := os.Open(genesisFile) @@ -105,23 +131,37 @@ func LoadGenesisFile(genesisFile string) (*core.Genesis, error) { return &genesis, nil } -// EncodeContractData packs the contract version and the secret hash into a byte +// EncodeContractData packs the contract version and the locator into a byte // slice for communicating a swap's identity. -func EncodeContractData(contractVersion uint32, swapKey [SecretHashSize]byte) []byte { - b := make([]byte, SecretHashSize+4) +func EncodeContractData(contractVersion uint32, locator []byte) []byte { + b := make([]byte, len(locator)+4) binary.BigEndian.PutUint32(b[:4], contractVersion) - copy(b[4:], swapKey[:]) + copy(b[4:], locator[:]) return b } -// DecodeContractData unpacks the contract version and secret hash. -func DecodeContractData(data []byte) (contractVersion uint32, swapKey [SecretHashSize]byte, err error) { - if len(data) != SecretHashSize+4 { - err = errors.New("invalid swap data") +// DecodeLocator unpacks the contract version and secret hash. +func DecodeLocator(data []byte) (contractVersion uint32, locator []byte, err error) { + if len(data) < 4 { + err = errors.New("invalid short encoding") return } + locator = data[4:] contractVersion = binary.BigEndian.Uint32(data[:4]) - copy(swapKey[:], data[4:]) + switch contractVersion { + case 0: + if len(locator) != SecretHashSize { + err = fmt.Errorf("v0 locator is too small. expected %d, got %d", SecretHashSize, len(locator)) + return + } + case 1: + if len(locator) != LocatorV1Length { + err = fmt.Errorf("v1 locator is too small. expected %d, got %d", LocatorV1Length, len(locator)) + return + } + default: + err = fmt.Errorf("unkown contract version %d", contractVersion) + } return } @@ -250,7 +290,35 @@ func (ss SwapStep) String() string { return "unknown" } -// SwapState is the current state of an in-process swap. +// SwapVector is immutable contract data. +type SwapVector struct { + From common.Address + To common.Address + Value uint64 + SecretHash [32]byte + LockTime uint64 +} + +// Locator encodes a version 1 locator for the SwapVector. +func (v *SwapVector) Locator() []byte { + locator := make([]byte, LocatorV1Length) + copy(locator[0:20], v.From[:]) + copy(locator[20:40], v.To[:]) + binary.BigEndian.PutUint64(locator[40:48], v.Value) + copy(locator[48:80], v.SecretHash[:]) + binary.BigEndian.PutUint64(locator[80:88], v.LockTime) + return locator +} + +// SwapStatus is the contract data that specifies the current contract state. +type SwapStatus struct { + BlockHeight uint64 + Secret [32]byte + Step SwapStep +} + +// SwapState is the current state of an in-process swap, as stored on-chain by +// the v0 contract. type SwapState struct { BlockHeight uint64 LockTime time.Time @@ -331,3 +399,42 @@ func (g *Gases) RedeemN(n int) uint64 { } return g.Redeem + g.RedeemAdd*(uint64(n)-1) } + +func ParseV0Locator(locator []byte) (secretHash [32]byte, err error) { + if len(locator) == SecretHashSize { + copy(secretHash[:], locator) + } else { + err = fmt.Errorf("wrong v0 locator length. wanted %d, got %d", SecretHashSize, len(locator)) + } + return +} + +// LocatorV1Length = from 20 + to 20 + value 8 + secretHash 32 + +// lockTime 8 = 88 bytes +const LocatorV1Length = 88 + +func ParseV1Locator(locator []byte) (v *SwapVector, err error) { + // from 20 + to 20 + value 8 + secretHash 32 + lockTime 8 + if len(locator) == LocatorV1Length { + v = &SwapVector{ + From: common.BytesToAddress(locator[:20]), + To: common.BytesToAddress(locator[20:40]), + Value: binary.BigEndian.Uint64(locator[40:48]), + LockTime: binary.BigEndian.Uint64(locator[80:88]), + } + copy(v.SecretHash[:], locator[48:80]) + } else { + err = fmt.Errorf("wrong v1 locator length. wanted %d, got %d", LocatorV1Length, len(locator)) + } + return +} + +func SwapVectorToAbigen(c *SwapVector) swapv1.ETHSwapVector { + return swapv1.ETHSwapVector{ + SecretHash: c.SecretHash, + Initiator: c.From, + RefundTimestamp: c.LockTime, + Participant: c.To, + Value: c.Value, + } +} diff --git a/dex/networks/eth/params_test.go b/dex/networks/eth/params_test.go index 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 f816ddcaba..9c5b321038 100644 --- a/dex/networks/eth/tokens.go +++ b/dex/networks/eth/tokens.go @@ -160,6 +160,27 @@ var Tokens = map[uint32]*Token{ Transfer: 85_100, // actual ~65,524 (initial receive, subsequent 48,424) }, }, + 1: { + // Swap contract address. The simnet harness writes this + // address to file. Live tests must populate this field. + Address: common.Address{}, + Gas: Gases{ + Swap: 95_000, // [86009 112920 139831 166742 193651] + SwapAdd: 30_000, // avg SwapAdd 26910 + Redeem: 50_000, // [42569 53614 64646 75703 86734] + RedeemAdd: 14_000, // avg RedeemAdd 11038 + Refund: 50_000, // [45306 45306 45306 45306 45294] avg: 45303 + // Approve is the gas used to call the approve + // method of the contract. For Approve transactions, + // the very first approval for an account-spender + // pair takes more than subsequent approvals. The + // results are repeated for a different account's + // first approvals on the same contract, so it's not + // just the global first. + Approve: 46_000, + Transfer: 33_000, + }, + }, }, }, dex.Simnet: { @@ -175,7 +196,20 @@ var Tokens = map[uint32]*Token{ Refund: 77_000, Approve: 78_400, Transfer: 85_100, - }}, + }, + }, + 1: { + Address: common.Address{}, + Gas: Gases{ + Swap: 174_000, // [171756 284366 396976 509586 622184] + SwapAdd: 115_000, + Redeem: 70_000, // [63214 94858 126502 158135 189779] + RedeemAdd: 33_000, + Refund: 50_000, // [48127 48127 48127 48127 48127] + Approve: 46_000, // [44465 27365 27365 27365 27365] + Transfer: 35_000, // [32540 32540 32540 32540 32540] + }, + }, }, }, }, @@ -309,20 +343,24 @@ 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.txt") + testUSDCSwapContractAddrFileV0 := filepath.Join(harnessDir, "usdc_swap_contract_address.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") + ethSwapContractAddrFileV1 := filepath.Join(harnessDir, "eth_swap_contract_address_v1.txt") + testUSDCSwapContractAddrFileV1 := filepath.Join(harnessDir, "usdc_swap_contract_address_v1.txt") multiBalanceContractAddrFile := filepath.Join(harnessDir, "multibalance_address.txt") - contractsAddrs[0][dex.Simnet] = maybeGetContractAddrFromFile(ethSwapContractAddrFile) + contractsAddrs[0][dex.Simnet] = maybeGetContractAddrFromFile(ethSwapContractAddrFileV0) + contractsAddrs[1][dex.Simnet] = maybeGetContractAddrFromFile(ethSwapContractAddrFileV1) multiBalandAddresses[dex.Simnet] = maybeGetContractAddrFromFile(multiBalanceContractAddrFile) - usdcToken.SwapContracts[0].Address = maybeGetContractAddrFromFile(testUSDCSwapContractAddrFile) + usdcToken.SwapContracts[0].Address = maybeGetContractAddrFromFile(testUSDCSwapContractAddrFileV0) + usdcToken.SwapContracts[1].Address = maybeGetContractAddrFromFile(testUSDCSwapContractAddrFileV1) 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..e89db1e066 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) { +// ParseRedeemData parses the calldata used to call the redeem function of a +// specific version of the swap contract. It returns the the list of redemptions +// done in the call and errors if the call data does not call redeem with expected +// argument types. +func ParseRedeemDataV0(calldata []byte) (map[[SecretHashSize]byte]*Redemption, error) { decoded, err := ParseCallData(calldata, ABIs[0]) if err != nil { return nil, fmt.Errorf("unable to parse call data: %v", err) } - if decoded.Name != t.redeemFuncName { - return nil, fmt.Errorf("expected %v function but got %v", t.redeemFuncName, decoded.Name) + if decoded.Name != RedeemMethodName { + return nil, fmt.Errorf("expected %v function but got %v", RedeemMethodName, decoded.Name) } args := decoded.inputs // Any difference in number of args and types than what we expect @@ -178,15 +137,18 @@ func (t *txDataHandlerV0) parseRedeemData(calldata []byte) (map[[SecretHashSize] return toReturn, nil } -func (t *txDataHandlerV0) parseRefundData(calldata []byte) ([32]byte, error) { +// ParseRefundData parses the calldata used to call the refund function of a +// specific version of the swap contract. It returns the secret hash and errors +// if the call data does not call refund with expected argument types. +func ParseRefundDataV0(calldata []byte) ([32]byte, error) { var secretHash [32]byte decoded, err := ParseCallData(calldata, ABIs[0]) if err != nil { return secretHash, fmt.Errorf("unable to parse call data: %v", err) } - if decoded.Name != t.refundFuncName { - return secretHash, fmt.Errorf("expected %v function but got %v", t.refundFuncName, decoded.Name) + if decoded.Name != RefundMethodName { + return secretHash, fmt.Errorf("expected %v function but got %v", RefundMethodName, decoded.Name) } args := decoded.inputs // Any difference in number of args and types than what we expect @@ -204,3 +166,148 @@ func (t *txDataHandlerV0) parseRefundData(calldata []byte) ([32]byte, error) { return secretHash, nil } + +type RedemptionV1 struct { + Secret [32]byte + Contract *SwapVector +} + +func ParseInitiateDataV1(calldata []byte) (map[[SecretHashSize]byte]*SwapVector, error) { + decoded, err := ParseCallData(calldata, ABIs[1]) + if err != nil { + return nil, fmt.Errorf("unable to parse call data: %v", err) + } + if decoded.Name != InitiateMethodName { + return nil, fmt.Errorf("expected %v function but got %v", InitiateMethodName, decoded.Name) + } + args := decoded.inputs + // Any difference in number of args and types than what we expect + // should be caught by ParseCallData, but checking again anyway. + // + // TODO: If any of the checks prove redundant, remove them. + const numArgs = 1 + if len(args) != numArgs { + return nil, fmt.Errorf("expected %v input args but got %v", numArgs, len(args)) + } + initiations, ok := args[0].value.([]struct { + SecretHash [32]byte `json:"secretHash"` + Initiator common.Address `json:"initiator"` + RefundTimestamp uint64 `json:"refundTimestamp"` + Participant common.Address `json:"participant"` + Value uint64 `json:"value"` + }) + if !ok { + return nil, fmt.Errorf("expected first arg of type []swapv1.ETHSwapContract but got %T", args[0].value) + } + + // This is done for the compiler to ensure that the type defined above and + // swapv0.ETHSwapInitiation are the same, other than the tags. + if len(initiations) > 0 { + _ = swapv1.ETHSwapVector(initiations[0]) + } + + toReturn := make(map[[SecretHashSize]byte]*SwapVector, len(initiations)) + for _, init := range initiations { + toReturn[init.SecretHash] = &SwapVector{ + From: init.Initiator, + To: init.Participant, + Value: init.Value, + SecretHash: init.SecretHash, + LockTime: init.RefundTimestamp, + } + } + + return toReturn, nil +} + +func ParseRedeemDataV1(calldata []byte) (map[[SecretHashSize]byte]*RedemptionV1, error) { + decoded, err := ParseCallData(calldata, ABIs[1]) + if err != nil { + return nil, fmt.Errorf("unable to parse call data: %v", err) + } + if decoded.Name != RedeemMethodName { + return nil, fmt.Errorf("expected %v function but got %v", RedeemMethodName, decoded.Name) + } + args := decoded.inputs + // Any difference in number of args and types than what we expect + // should be caught by parseCallData, but checking again anyway. + // + // TODO: If any of the checks prove redundant, remove them. + const numArgs = 1 + if len(args) != numArgs { + return nil, fmt.Errorf("expected %v redeem args but got %v", numArgs, len(args)) + } + + redemptions, ok := args[0].value.([]struct { + V struct { + SecretHash [32]uint8 `json:"secretHash"` + Initiator common.Address `json:"initiator"` + RefundTimestamp uint64 `json:"refundTimestamp"` + Participant common.Address `json:"participant"` + Value uint64 `json:"value"` + } `json:"v"` + Secret [32]uint8 `json:"secret"` + }) + if !ok { + return nil, fmt.Errorf("expected first arg of type []swapv1.ETHSwapRedemption but got %T", args[0].value) + } + + // This is done for the compiler to ensure that the type defined above and + // swapv0.ETHSwapRedemption are the same, other than the tags. + if len(redemptions) > 0 { + // Why can't I do ETHSwapRedemption directly? + _ = swapv1.ETHSwapVector(redemptions[0].V) + } + toReturn := make(map[[SecretHashSize]byte]*RedemptionV1, len(redemptions)) + for _, r := range redemptions { + toReturn[r.V.SecretHash] = &RedemptionV1{ + Contract: &SwapVector{ + From: r.V.Initiator, + To: r.V.Participant, + Value: r.V.Value, + SecretHash: r.V.SecretHash, + LockTime: r.V.RefundTimestamp, + }, + Secret: r.Secret, + } + } + + return toReturn, nil +} + +func ParseRefundDataV1(calldata []byte) (*SwapVector, error) { + decoded, err := ParseCallData(calldata, ABIs[1]) + if err != nil { + return nil, fmt.Errorf("unable to parse call data: %v", err) + } + if decoded.Name != RefundMethodName { + return nil, fmt.Errorf("expected %v function but got %v", RefundMethodName, decoded.Name) + } + args := decoded.inputs + // Any difference in number of args and types than what we expect + // should be caught by parseCallData, but checking again anyway. + // + // TODO: If any of the checks prove redundant, remove them. + const numArgs = 1 + if len(args) != numArgs { + return nil, fmt.Errorf("expected %v redeem args but got %v", numArgs, len(args)) + } + contract, ok := args[0].value.(struct { + SecretHash [32]byte `json:"secretHash"` + Initiator common.Address `json:"initiator"` + RefundTimestamp uint64 `json:"refundTimestamp"` + Participant common.Address `json:"participant"` + Value uint64 `json:"value"` + }) + if !ok { + return nil, fmt.Errorf("expected first arg of type [32]byte but got %T", args[0].value) + } + + return &SwapVector{ + From: contract.Initiator, + To: contract.Participant, + Value: contract.Value, + LockTime: contract.RefundTimestamp, + SecretHash: contract.SecretHash, + }, nil +} diff --git a/dex/networks/eth/txdata_test.go b/dex/networks/eth/txdata_test.go index 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 a3d4933eff..fb28bbf531 100644 --- a/dex/networks/polygon/params.go +++ b/dex/networks/polygon/params.go @@ -59,6 +59,7 @@ var ( dex.Testnet: common.HexToAddress("0x73bc803A2604b2c58B8680c3CE1b14489842EF16"), // txid: 0x88f656a8e432fdd50f33e67bdc39a66d24f663e33792bfab16b033dd2c609a99 dex.Simnet: common.HexToAddress(""), // Filled in by MaybeReadSimnetAddrs }, + 1: {}, } MultiBalanceAddresses = map[dex.Network]common.Address{ @@ -176,6 +177,18 @@ var ( Transfer: 64_539, }, }, + 1: { + Address: common.Address{}, + Gas: dexeth.Gases{ + Swap: 174_000, // [171756 284366 396976 509586 622184] + SwapAdd: 115_000, + Redeem: 70_000, // [63214 94858 126502 158135 189779] + RedeemAdd: 33_000, + Refund: 50_000, // [48127 48127 48127 48127 48127] + Approve: 46_000, // [44465 27365 27365 27365 27365] + Transfer: 35_000, // [32540 32540 32540 32540 32540] + }, + }, }, }, }, diff --git a/dex/testing/eth/create-node.sh b/dex/testing/eth/create-node.sh index 594071ec45..c5c4e1e3be 100755 --- a/dex/testing/eth/create-node.sh +++ b/dex/testing/eth/create-node.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash # Script for creating eth nodes. -set -ex +set -e # The following are required script arguments. TMUX_WIN_ID=$1 diff --git a/dex/testing/eth/harness.sh b/dex/testing/eth/harness.sh index 1f1ee3e2f0..d1f98d18ce 100755 --- a/dex/testing/eth/harness.sh +++ b/dex/testing/eth/harness.sh @@ -45,6 +45,9 @@ ERC20_SWAP_V0="60a060405234801561001057600080fd5b50604051610e92380380610e9283398 TEST_TOKEN="608060405234801561001057600080fd5b50604051610aec380380610aec83398101604081905261002f91610140565b6040805180820190915260098152682a32b9ba2a37b5b2b760b91b602082015260039061005c9082610209565b506040805180820190915260038152621514d560ea1b60208201526004906100849082610209565b506005805460ff191660ff929092169190911790556909513ea9de0243800000600255600060208190526902544faa778090e000007f7d4921c2bc32c0110a31d16f4efb43c7a1228f1df7af765f608241dee5c62ebc8190557f59603491850c7d11499afe95b334ccfd92b48b36a15df31ef59ff5813fe3708281905573d12ab7cf72ccf1f3882ec99ddc53cd415635c3be9091527f5bd8dfce2dbb581d0922a094c40bab2f7d2f0ea9aaf275bf0fcc0f027a2ff91d556102c8565b60006020828403121561015257600080fd5b815160ff8116811461016357600080fd5b9392505050565b634e487b7160e01b600052604160045260246000fd5b600181811c9082168061019457607f821691505b6020821081036101b457634e487b7160e01b600052602260045260246000fd5b50919050565b601f82111561020457600081815260208120601f850160051c810160208610156101e15750805b601f850160051c820191505b81811015610200578281556001016101ed565b5050505b505050565b81516001600160401b038111156102225761022261016a565b610236816102308454610180565b846101ba565b602080601f83116001811461026b57600084156102535750858301515b600019600386901b1c1916600185901b178555610200565b600085815260208120601f198616915b8281101561029a5788860151825594840194600190910190840161027b565b50858210156102b85787850151600019600388901b60f8161c191681555b5050505050600190811b01905550565b610815806102d76000396000f3fe608060405234801561001057600080fd5b50600436106100a95760003560e01c806370a082311161007157806370a08231146101295780638ba4cc3c1461015257806395d89b4114610167578063a9059cbb1461016f578063ce714b5114610182578063dd62ed3e1461019557600080fd5b806306fdde03146100ae578063095ea7b3146100cc57806318160ddd146100ef57806323b872dd14610101578063313ce56714610114575b600080fd5b6100b66101ce565b6040516100c3919061065f565b60405180910390f35b6100df6100da3660046106c9565b610260565b60405190151581526020016100c3565b6002545b6040519081526020016100c3565b6100df61010f3660046106f3565b610277565b60055460405160ff90911681526020016100c3565b6100f361013736600461072f565b6001600160a01b031660009081526020819052604090205490565b6101656101603660046106c9565b610326565b005b6100b661036e565b6100df61017d3660046106c9565b61037d565b6100df6101903660046106f3565b61038a565b6100f36101a3366004610751565b6001600160a01b03918216600090815260016020908152604080832093909416825291909152205490565b6060600380546101dd90610784565b80601f016020809104026020016040519081016040528092919081815260200182805461020990610784565b80156102565780601f1061022b57610100808354040283529160200191610256565b820191906000526020600020905b81548152906001019060200180831161023957829003601f168201915b5050505050905090565b600061026d3384846103a1565b5060015b92915050565b6000610284848484610490565b6001600160a01b03841660009081526001602090815260408083203384529091529020548281101561030e5760405162461bcd60e51b815260206004820152602860248201527f45524332303a207472616e7366657220616d6f756e74206578636565647320616044820152676c6c6f77616e636560c01b60648201526084015b60405180910390fd5b61031b85338584036103a1565b506001949350505050565b806002600082825461033891906107be565b90915550506001600160a01b038216600090815260208190526040812080548392906103659084906107be565b90915550505050565b6060600480546101dd90610784565b600061026d338484610490565b60006103978484846103a1565b5060019392505050565b6001600160a01b0383166104035760405162461bcd60e51b8152602060048201526024808201527f45524332303a20617070726f76652066726f6d20746865207a65726f206164646044820152637265737360e01b6064820152608401610305565b6001600160a01b0382166104645760405162461bcd60e51b815260206004820152602260248201527f45524332303a20617070726f766520746f20746865207a65726f206164647265604482015261737360f01b6064820152608401610305565b6001600160a01b0392831660009081526001602090815260408083209490951682529290925291902055565b6001600160a01b0383166104f45760405162461bcd60e51b815260206004820152602560248201527f45524332303a207472616e736665722066726f6d20746865207a65726f206164604482015264647265737360d81b6064820152608401610305565b6001600160a01b0382166105565760405162461bcd60e51b815260206004820152602360248201527f45524332303a207472616e7366657220746f20746865207a65726f206164647260448201526265737360e81b6064820152608401610305565b6001600160a01b038316600090815260208190526040902054818110156105ce5760405162461bcd60e51b815260206004820152602660248201527f45524332303a207472616e7366657220616d6f756e7420657863656564732062604482015265616c616e636560d01b6064820152608401610305565b6001600160a01b038085166000908152602081905260408082208585039055918516815290812080548492906106059084906107be565b92505081905550826001600160a01b0316846001600160a01b03167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef8460405161065191815260200190565b60405180910390a350505050565b600060208083528351808285015260005b8181101561068c57858101830151858201604001528201610670565b506000604082860101526040601f19601f8301168501019250505092915050565b80356001600160a01b03811681146106c457600080fd5b919050565b600080604083850312156106dc57600080fd5b6106e5836106ad565b946020939093013593505050565b60008060006060848603121561070857600080fd5b610711846106ad565b925061071f602085016106ad565b9150604084013590509250925092565b60006020828403121561074157600080fd5b61074a826106ad565b9392505050565b6000806040838503121561076457600080fd5b61076d836106ad565b915061077b602084016106ad565b90509250929050565b600181811c9082168061079857607f821691505b6020821081036107b857634e487b7160e01b600052602260045260246000fd5b50919050565b8082018082111561027157634e487b7160e01b600052601160045260246000fdfea2646970667358221220a1dec430e536a5a10a28fc4b864936f9d148be4c0a5db1246a0865556ca3bdde64736f6c63430008120033" MULTIBALANCE_BIN="608060405234801561001057600080fd5b50610483806100206000396000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c8063d3e5ca8714610030575b600080fd5b61004361003e3660046102a5565b610059565b604051610050919061032b565b60405180910390f35b60606000610068836001610385565b67ffffffffffffffff8111156100805761008061039e565b6040519080825280602002602001820160405280156100a9578160200160208202803683370190505b509050846001600160a01b031631816000815181106100ca576100ca6103b4565b60200260200101818152505060005b838110156102805760008585838181106100f5576100f56103b4565b905060200201602081019061010a91906103ca565b905060006060826001600160a01b03167f70a08231b98ef4ca268c9cc3f6b4590e4bfec28280db06bb5d45e689f2a360be8a60405160240161015b91906001600160a01b0391909116815260200190565b60408051601f198184030181529181526020820180516001600160e01b03166001600160e01b031990941693909317909252905161019991906103ec565b600060405180830381855afa9150503d80600081146101d4576040519150601f19603f3d011682016040523d82523d6000602084013e6101d9565b606091505b509092509050816102285760405162461bcd60e51b815260206004820152601560248201527418985b185b98d953d98818d85b1b0819985a5b1959605a1b604482015260640160405180910390fd5b60008180602001905181019061023e919061041b565b9050808661024d876001610385565b8151811061025d5761025d6103b4565b60200260200101818152505050505050808061027890610434565b9150506100d9565b50949350505050565b80356001600160a01b03811681146102a057600080fd5b919050565b6000806000604084860312156102ba57600080fd5b6102c384610289565b9250602084013567ffffffffffffffff808211156102e057600080fd5b818601915086601f8301126102f457600080fd5b81358181111561030357600080fd5b8760208260051b850101111561031857600080fd5b6020830194508093505050509250925092565b6020808252825182820181905260009190848201906040850190845b8181101561036357835183529284019291840191600101610347565b50909695505050505050565b634e487b7160e01b600052601160045260246000fd5b808201808211156103985761039861036f565b92915050565b634e487b7160e01b600052604160045260246000fd5b634e487b7160e01b600052603260045260246000fd5b6000602082840312156103dc57600080fd5b6103e582610289565b9392505050565b6000825160005b8181101561040d57602081860181015185830152016103f3565b506000920191825250919050565b60006020828403121561042d57600080fd5b5051919050565b6000600182016104465761044661036f565b506001019056fea26469706673582212207074e3e189a2692e2841f7943a9de24bcdbca943ee6bfcbd83a2fa6e43ec497b64736f6c63430008120033" +ETH_SWAP_V1="608060405234801561001057600080fd5b50610e8e806100206000396000f3fe60806040526004361061007b5760003560e01c80637802689d1161004e5780637802689d1461011b578063d2544c061461013b578063eb84e7f21461015b578063ed7cbed71461019657600080fd5b8063428b16e11461008057806361a16e33146100a257806364a97bff146100d857806377d7e031146100eb575b600080fd5b34801561008c57600080fd5b506100a061009b366004610b6f565b6101b6565b005b3480156100ae57600080fd5b506100c26100bd366004610be4565b610447565b6040516100cf9190610c12565b60405180910390f35b6100a06100e6366004610c55565b610503565b3480156100f757600080fd5b5061010b610106366004610cb8565b61073b565b60405190151581526020016100cf565b34801561012757600080fd5b5061010b610136366004610be4565b6107b5565b34801561014757600080fd5b506100a0610156366004610be4565b6107e8565b34801561016757600080fd5b50610188610176366004610cda565b60006020819052908152604090205481565b6040519081526020016100cf565b3480156101a257600080fd5b506101886101b1366004610be4565b610a4a565b3233146101de5760405162461bcd60e51b81526004016101d590610cf3565b60405180910390fd5b6000805b828110156103b057368484838181106101fd576101fd610d1d565b60c0029190910191503390506102196080830160608401610d33565b6001600160a01b03161461025c5760405162461bcd60e51b815260206004820152600a6024820152691b9bdd08185d5d1a195960b21b60448201526064016101d5565b6000808061026984610b43565b92509250925060008111801561027e57504381105b6102ba5760405162461bcd60e51b815260206004820152600d60248201526c0756e66696c6c6564207377617609c1b60448201526064016101d5565b6102c582853561073b565b156103055760405162461bcd60e51b815260206004820152601060248201526f185b1c9958591e481c995919595b595960821b60448201526064016101d5565b61031460a0850135853561073b565b6103515760405162461bcd60e51b815260206004820152600e60248201526d1a5b9d985b1a59081cd958dc995d60921b60448201526064016101d5565b600083815260208190526040902060a0850180359091556103759060808601610d63565b61038390633b9aca00610da3565b6103979067ffffffffffffffff1687610dd3565b95505050505080806103a890610deb565b9150506101e2565b50604051600090339083908381818185875af1925050503d80600081146103f3576040519150601f19603f3d011682016040523d82523d6000602084013e6103f8565b606091505b50909150506001811515146104415760405162461bcd60e51b815260206004820152600f60248201526e1d1c985b9cd9995c8819985a5b1959608a1b60448201526064016101d5565b50505050565b60408051606081018252600080825260208201819052918101829052908061046e84610b43565b92509250506104986040805160608101909152806000815260006020820181905260409091015290565b816000036104bf578060005b908160038111156104b7576104b7610bfc565b9052506104fb565b600183016104cf578060036104a4565b6104da83863561073b565b156104ef5760028152602081018390526104fb565b60018152604081018290525b949350505050565b3233146105225760405162461bcd60e51b81526004016101d590610cf3565b6000805b828110156106fc573684848381811061054157610541610d1d565b905060a002019050600081608001602081019061055e9190610d63565b67ffffffffffffffff161161059d5760405162461bcd60e51b81526020600482015260056024820152640c081d985b60da1b60448201526064016101d5565b60006105af6060830160408401610d63565b67ffffffffffffffff16116105fa5760405162461bcd60e51b815260206004820152601160248201527003020726566756e6454696d657374616d7607c1b60448201526064016101d5565b600061060582610a4a565b60008181526020819052604090205490915080156106565760405162461bcd60e51b815260206004820152600e60248201526d73776170206e6f7420656d70747960901b60448201526064016101d5565b504361066381843561073b565b156106a15760405162461bcd60e51b815260206004820152600e60248201526d3430b9b41031b7b63634b9b4b7b760911b60448201526064016101d5565b60008281526020819052604090208190556106c260a0840160808501610d63565b6106d090633b9aca00610da3565b6106e49067ffffffffffffffff1686610dd3565b945050505080806106f490610deb565b915050610526565b503481146107365760405162461bcd60e51b8152602060048201526007602482015266189859081d985b60ca1b60448201526064016101d5565b505050565b60008160028460405160200161075391815260200190565b60408051601f198184030181529082905261076d91610e04565b602060405180830381855afa15801561078a573d6000803e3d6000fd5b5050506040513d601f19601f820116820180604052508101906107ad9190610e3f565b149392505050565b60008060006107c384610b43565b9250925050806000141580156104fb57506107df82853561073b565b15949350505050565b3233146108075760405162461bcd60e51b81526004016101d590610cf3565b6108176060820160408301610d63565b67ffffffffffffffff164210156108675760405162461bcd60e51b81526020600482015260146024820152731b1bd8dadd1a5b59481b9bdd08195e1c1a5c995960621b60448201526064016101d5565b600080600061087584610b43565b92509250925060008111801561088b5750438111155b6108c95760405162461bcd60e51b815260206004820152600f60248201526e73776170206e6f742061637469766560881b60448201526064016101d5565b6108d482853561073b565b156109195760405162461bcd60e51b81526020600482015260156024820152741cddd85c08185b1c9958591e481c995919595b5959605a1b60448201526064016101d5565b600182016109615760405162461bcd60e51b81526020600482015260156024820152741cddd85c08185b1c9958591e481c99599d5b991959605a1b60448201526064016101d5565b600083815260208181526040808320600019905561098491908701908701610d33565b6001600160a01b031661099d60a0870160808801610d63565b6109ab90633b9aca00610da3565b67ffffffffffffffff1660405160006040518083038185875af1925050503d80600081146109f5576040519150601f19603f3d011682016040523d82523d6000602084013e6109fa565b606091505b5090915050600181151514610a435760405162461bcd60e51b815260206004820152600f60248201526e1d1c985b9cd9995c8819985a5b1959608a1b60448201526064016101d5565b5050505050565b600060028235610a606040850160208601610d33565b60601b846060016020810190610a769190610d33565b60601b610a8960a0870160808801610d63565b60c01b610a9c6060880160408901610d63565b6040805160208101969096526bffffffffffffffffffffffff19948516908601529190921660548401526001600160c01b0319918216606884015260c01b16607082015260780160408051601f1981840301815290829052610afd91610e04565b602060405180830381855afa158015610b1a573d6000803e3d6000fd5b5050506040513d601f19601f82011682018060405250810190610b3d9190610e3f565b92915050565b600080600080610b5285610a4a565b600081815260208190526040902054909690955085945092505050565b60008060208385031215610b8257600080fd5b823567ffffffffffffffff80821115610b9a57600080fd5b818501915085601f830112610bae57600080fd5b813581811115610bbd57600080fd5b86602060c083028501011115610bd257600080fd5b60209290920196919550909350505050565b600060a08284031215610bf657600080fd5b50919050565b634e487b7160e01b600052602160045260246000fd5b8151606082019060048110610c3757634e487b7160e01b600052602160045260246000fd5b80835250602083015160208301526040830151604083015292915050565b60008060208385031215610c6857600080fd5b823567ffffffffffffffff80821115610c8057600080fd5b818501915085601f830112610c9457600080fd5b813581811115610ca357600080fd5b86602060a083028501011115610bd257600080fd5b60008060408385031215610ccb57600080fd5b50508035926020909101359150565b600060208284031215610cec57600080fd5b5035919050565b60208082526010908201526f39b2b73232b910109e9037b934b3b4b760811b604082015260600190565b634e487b7160e01b600052603260045260246000fd5b600060208284031215610d4557600080fd5b81356001600160a01b0381168114610d5c57600080fd5b9392505050565b600060208284031215610d7557600080fd5b813567ffffffffffffffff81168114610d5c57600080fd5b634e487b7160e01b600052601160045260246000fd5b600067ffffffffffffffff80831681851681830481118215151615610dca57610dca610d8d565b02949350505050565b60008219821115610de657610de6610d8d565b500190565b600060018201610dfd57610dfd610d8d565b5060010190565b6000825160005b81811015610e255760208186018101518583015201610e0b565b81811115610e34576000828501525b509190910192915050565b600060208284031215610e5157600080fd5b505191905056fea2646970667358221220d735868682c69bb69ed936f11e2f136e7da44cb347e0b1d67acd1e088ea13dd664736f6c634300080f0033" +ERC20_SWAP_V1="60a060405234801561001057600080fd5b506040516111cb3803806111cb83398101604081905261002f91610040565b6001600160a01b0316608052610070565b60006020828403121561005257600080fd5b81516001600160a01b038116811461006957600080fd5b9392505050565b60805161112b6100a060003960008181610158015281816104570152818161083e0152610b5d015261112b6000f3fe6080604052600436106100865760003560e01c80637802689d116100595780637802689d146101265780638c8e8fee14610146578063d2544c0614610192578063eb84e7f2146101b2578063ed7cbed7146101ed57600080fd5b8063428b16e11461008b57806361a16e33146100ad57806364a97bff146100e357806377d7e031146100f6575b600080fd5b34801561009757600080fd5b506100ab6100a6366004610dea565b61020d565b005b3480156100b957600080fd5b506100cd6100c8366004610e5f565b610533565b6040516100da9190610e8d565b60405180910390f35b6100ab6100f1366004610ed0565b6105ef565b34801561010257600080fd5b50610116610111366004610f33565b610918565b60405190151581526020016100da565b34801561013257600080fd5b50610116610141366004610e5f565b610992565b34801561015257600080fd5b5061017a7f000000000000000000000000000000000000000000000000000000000000000081565b6040516001600160a01b0390911681526020016100da565b34801561019e57600080fd5b506100ab6101ad366004610e5f565b6109c5565b3480156101be57600080fd5b506101df6101cd366004610f55565b60006020819052908152604090205481565b6040519081526020016100da565b3480156101f957600080fd5b506101df610208366004610e5f565b610cc5565b3233146102355760405162461bcd60e51b815260040161022c90610f6e565b60405180910390fd5b6000805b82811015610407573684848381811061025457610254610f98565b60c0029190910191503390506102706080830160608401610fae565b6001600160a01b0316146102b35760405162461bcd60e51b815260206004820152600a6024820152691b9bdd08185d5d1a195960b21b604482015260640161022c565b600080806102c084610dbe565b9250925092506000811180156102d557504381105b6103115760405162461bcd60e51b815260206004820152600d60248201526c0756e66696c6c6564207377617609c1b604482015260640161022c565b61031c828535610918565b1561035c5760405162461bcd60e51b815260206004820152601060248201526f185b1c9958591e481c995919595b595960821b604482015260640161022c565b61036b60a08501358535610918565b6103a85760405162461bcd60e51b815260206004820152600e60248201526d1a5b9d985b1a59081cd958dc995d60921b604482015260640161022c565b600083815260208190526040902060a0850180359091556103cc9060808601610fde565b6103da90633b9aca0061101e565b6103ee9067ffffffffffffffff168761104e565b95505050505080806103ff90611066565b915050610239565b5060408051336024820152604480820184905282518083039091018152606490910182526020810180516001600160e01b031663a9059cbb60e01b17905290516000916060916001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016916104819161107f565b6000604051808303816000865af19150503d80600081146104be576040519150601f19603f3d011682016040523d82523d6000602084013e6104c3565b606091505b5090925090508180156104ee5750805115806104ee5750808060200190518101906104ee91906110ba565b61052c5760405162461bcd60e51b815260206004820152600f60248201526e1d1c985b9cd9995c8819985a5b1959608a1b604482015260640161022c565b5050505050565b60408051606081018252600080825260208201819052918101829052908061055a84610dbe565b92509250506105846040805160608101909152806000815260006020820181905260409091015290565b816000036105ab578060005b908160038111156105a3576105a3610e77565b9052506105e7565b600183016105bb57806003610590565b6105c6838635610918565b156105db5760028152602081018390526105e7565b60018152604081018290525b949350505050565b32331461060e5760405162461bcd60e51b815260040161022c90610f6e565b6000805b828110156107e8573684848381811061062d5761062d610f98565b905060a002019050600081608001602081019061064a9190610fde565b67ffffffffffffffff16116106895760405162461bcd60e51b81526020600482015260056024820152640c081d985b60da1b604482015260640161022c565b600061069b6060830160408401610fde565b67ffffffffffffffff16116106e65760405162461bcd60e51b815260206004820152601160248201527003020726566756e6454696d657374616d7607c1b604482015260640161022c565b60006106f182610cc5565b60008181526020819052604090205490915080156107425760405162461bcd60e51b815260206004820152600e60248201526d73776170206e6f7420656d70747960901b604482015260640161022c565b504361074f818435610918565b1561078d5760405162461bcd60e51b815260206004820152600e60248201526d3430b9b41031b7b63634b9b4b7b760911b604482015260640161022c565b60008281526020819052604090208190556107ae60a0840160808501610fde565b6107bc90633b9aca0061101e565b6107d09067ffffffffffffffff168661104e565b945050505080806107e090611066565b915050610612565b5060408051336024820152306044820152606480820184905282518083039091018152608490910182526020810180516001600160e01b03166323b872dd60e01b17905290516000916060916001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016916108689161107f565b6000604051808303816000865af19150503d80600081146108a5576040519150601f19603f3d011682016040523d82523d6000602084013e6108aa565b606091505b5090925090508180156108d55750805115806108d55750808060200190518101906108d591906110ba565b61052c5760405162461bcd60e51b81526020600482015260146024820152731d1c985b9cd9995c88199c9bdb4819985a5b195960621b604482015260640161022c565b60008160028460405160200161093091815260200190565b60408051601f198184030181529082905261094a9161107f565b602060405180830381855afa158015610967573d6000803e3d6000fd5b5050506040513d601f19601f8201168201806040525081019061098a91906110dc565b149392505050565b60008060006109a084610dbe565b9250925050806000141580156105e757506109bc828535610918565b15949350505050565b3233146109e45760405162461bcd60e51b815260040161022c90610f6e565b6109f46060820160408301610fde565b67ffffffffffffffff16421015610a445760405162461bcd60e51b81526020600482015260146024820152731b1bd8dadd1a5b59481b9bdd08195e1c1a5c995960621b604482015260640161022c565b6000806000610a5284610dbe565b925092509250600081118015610a685750438111155b610aa65760405162461bcd60e51b815260206004820152600f60248201526e73776170206e6f742061637469766560881b604482015260640161022c565b610ab1828535610918565b15610af65760405162461bcd60e51b81526020600482015260156024820152741cddd85c08185b1c9958591e481c995919595b5959605a1b604482015260640161022c565b60018201610b3e5760405162461bcd60e51b81526020600482015260156024820152741cddd85c08185b1c9958591e481c99599d5b991959605a1b604482015260640161022c565b6000838152602081905260408120600019905560606001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000167fa9059cbb2ab09eb219583f4a59a5d0623ade346d962bcd4e46b11da047c9049b33610baf60a08a0160808b01610fde565b6040516001600160a01b03909216602483015267ffffffffffffffff16604482015260640160408051601f198184030181529181526020820180516001600160e01b03166001600160e01b0319909416939093179092529051610c12919061107f565b6000604051808303816000865af19150503d8060008114610c4f576040519150601f19603f3d011682016040523d82523d6000602084013e610c54565b606091505b509092509050818015610c7f575080511580610c7f575080806020019051810190610c7f91906110ba565b610cbd5760405162461bcd60e51b815260206004820152600f60248201526e1d1c985b9cd9995c8819985a5b1959608a1b604482015260640161022c565b505050505050565b600060028235610cdb6040850160208601610fae565b60601b846060016020810190610cf19190610fae565b60601b610d0460a0870160808801610fde565b60c01b610d176060880160408901610fde565b6040805160208101969096526bffffffffffffffffffffffff19948516908601529190921660548401526001600160c01b0319918216606884015260c01b16607082015260780160408051601f1981840301815290829052610d789161107f565b602060405180830381855afa158015610d95573d6000803e3d6000fd5b5050506040513d601f19601f82011682018060405250810190610db891906110dc565b92915050565b600080600080610dcd85610cc5565b600081815260208190526040902054909690955085945092505050565b60008060208385031215610dfd57600080fd5b823567ffffffffffffffff80821115610e1557600080fd5b818501915085601f830112610e2957600080fd5b813581811115610e3857600080fd5b86602060c083028501011115610e4d57600080fd5b60209290920196919550909350505050565b600060a08284031215610e7157600080fd5b50919050565b634e487b7160e01b600052602160045260246000fd5b8151606082019060048110610eb257634e487b7160e01b600052602160045260246000fd5b80835250602083015160208301526040830151604083015292915050565b60008060208385031215610ee357600080fd5b823567ffffffffffffffff80821115610efb57600080fd5b818501915085601f830112610f0f57600080fd5b813581811115610f1e57600080fd5b86602060a083028501011115610e4d57600080fd5b60008060408385031215610f4657600080fd5b50508035926020909101359150565b600060208284031215610f6757600080fd5b5035919050565b60208082526010908201526f39b2b73232b910109e9037b934b3b4b760811b604082015260600190565b634e487b7160e01b600052603260045260246000fd5b600060208284031215610fc057600080fd5b81356001600160a01b0381168114610fd757600080fd5b9392505050565b600060208284031215610ff057600080fd5b813567ffffffffffffffff81168114610fd757600080fd5b634e487b7160e01b600052601160045260246000fd5b600067ffffffffffffffff8083168185168183048111821515161561104557611045611008565b02949350505050565b6000821982111561106157611061611008565b500190565b60006001820161107857611078611008565b5060010190565b6000825160005b818110156110a05760208186018101518583015201611086565b818111156110af576000828501525b509190910192915050565b6000602082840312156110cc57600080fd5b81518015158114610fd757600080fd5b6000602082840312156110ee57600080fd5b505191905056fea2646970667358221220319d89b87a0d5782925310133ed5d12d2ff562b0786611308396804fcad176fc64736f6c634300080f0033" + # PASSWORD is the password used to unlock all accounts/wallets/addresses. PASSWORD="abc" @@ -284,7 +287,10 @@ ${TEST_TX_HASH} EOF echo "Deploying ETHSwapV0 contract." -ETH_SWAP_CONTRACT_HASH=$("${NODES_ROOT}/harness-ctl/alpha" "attach --preload ${NODES_ROOT}/harness-ctl/deploy.js --exec deploy(\"${ALPHA_ADDRESS}\",\"${ETH_SWAP_V0}\")" | sed 's/"//g') +ETH_SWAP_CONTRACT_HASH_V0=$("${NODES_ROOT}/harness-ctl/alpha" "attach --preload ${NODES_ROOT}/harness-ctl/deploy.js --exec deploy(\"${ALPHA_ADDRESS}\",\"${ETH_SWAP_V0}\")" | sed 's/"//g') + +echo "Deploying ETHSwap1 contract." +ETH_SWAP_CONTRACT_HASH_V1=$("${NODES_ROOT}/harness-ctl/alpha" "attach --preload ${NODES_ROOT}/harness-ctl/deploy.js --exec deploy(\"${ALPHA_ADDRESS}\",\"${ETH_SWAP_V1}\")" | sed 's/"//g') echo "Deploying USDC contract." TEST_USDC_CONTRACT_HASH=$("${NODES_ROOT}/harness-ctl/alpha" "attach --preload ${NODES_ROOT}/harness-ctl/deploy.js --exec deployERC20(\"${ALPHA_ADDRESS}\",\"${TEST_TOKEN}\",6)" | sed 's/"//g') @@ -309,10 +315,16 @@ mine_pending_txs() { mine_pending_txs -ETH_SWAP_CONTRACT_ADDR=$("${NODES_ROOT}/harness-ctl/alpha" "attach --preload ${NODES_ROOT}/harness-ctl/contractAddress.js --exec contractAddress(\"${ETH_SWAP_CONTRACT_HASH}\")" | sed 's/"//g') -echo "ETH SWAP contract address is ${ETH_SWAP_CONTRACT_ADDR}. Saving to ${NODES_ROOT}/eth_swap_contract_address.txt" -cat > "${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}/usdc_swap_contract_address_v1.txt" < "${NODES_ROOT}/eth_swap_contract_address.txt" < "${NODES_ROOT}/eth_swap_contract_address_v0.txt" < "${NODES_ROOT}/usdc_swap_contract_address.txt" < "${NODES_ROOT}/usdc_swap_contract_address_v0.txt" <