diff --git a/client/asset/eth/contractor.go b/client/asset/eth/contractor.go index fcd87540c3..341566a642 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) voidUnusedNonce() } @@ -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 { @@ -158,6 +179,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{ @@ -168,7 +194,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, @@ -192,19 +284,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{ @@ -218,7 +326,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) } @@ -279,7 +395,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) @@ -290,7 +406,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 } @@ -303,7 +419,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) } @@ -320,12 +436,51 @@ func (c *contractorV0) voidUnusedNonce() { } } +// erc20Contractor supports the ERC20 ABI. Embedded in token contractors. +type erc20Contractor struct { + tokenContract *erc20.IERC20 + acct common.Address + contract common.Address +} + +// balance exposes the read-only balanceOf method of the erc20 token contract. +func (c *erc20Contractor) balance(ctx context.Context) (*big.Int, error) { + callOpts := &bind.CallOpts{ + From: c.acct, + Context: ctx, + } + + return c.tokenContract.BalanceOf(callOpts, c.acct) +} + +// allowance exposes the read-only allowance method of the erc20 token contract. +func (c *erc20Contractor) allowance(ctx context.Context) (*big.Int, error) { + callOpts := &bind.CallOpts{ + Pending: true, + From: c.acct, + Context: ctx, + } + return c.tokenContract.Allowance(callOpts, c.acct, c.contract) +} + +// approve sends an approve transaction approving the linked contract to call +// transferFrom for the specified amount. +func (c *erc20Contractor) approve(txOpts *bind.TransactOpts, amount *big.Int) (*types.Transaction, error) { + return c.tokenContract.Approve(txOpts, c.contract, amount) +} + +// transfer calls the transfer method of the erc20 token contract. Used for +// sends or withdrawals. +func (c *erc20Contractor) transfer(txOpts *bind.TransactOpts, addr common.Address, amount *big.Int) (*types.Transaction, error) { + return c.tokenContract.Transfer(txOpts, addr, amount) +} + // tokenContractorV0 is a contractor that implements the tokenContractor // methods, providing access to the methods of the token's ERC20 contract. type tokenContractorV0 struct { *contractorV0 - tokenAddr common.Address - tokenContract *erc20.IERC20 + *erc20Contractor + tokenAddr common.Address } var _ contractor = (*tokenContractorV0)(nil) @@ -368,79 +523,402 @@ func newV0TokenContractor(net dex.Network, assetID uint32, acctAddr common.Addre evmify: token.AtomicToEVM, atomize: token.EVMToAtomic, }, - tokenAddr: tokenAddr, - tokenContract: tokenContract, + tokenAddr: tokenAddr, + erc20Contractor: &erc20Contractor{ + tokenContract: tokenContract, + acct: acctAddr, + contract: swapContractAddr, + }, }, nil } -// balance exposes the read-only balanceOf method of the erc20 token contract. -func (c *tokenContractorV0) balance(ctx context.Context) (*big.Int, error) { - callOpts := &bind.CallOpts{ - From: c.acctAddr, - Context: ctx, +// estimateApproveGas estimates the gas needed to send an approve tx. +func (c *tokenContractorV0) estimateApproveGas(ctx context.Context, amount *big.Int) (uint64, error) { + return c.estimateGas(ctx, "approve", c.contractAddr, amount) +} + +// estimateTransferGas esimates the gas needed for a transfer tx. The account +// needs to have > amount tokens to use this method. +func (c *tokenContractorV0) estimateTransferGas(ctx context.Context, amount *big.Int) (uint64, error) { + return c.estimateGas(ctx, "transfer", c.acctAddr, amount) +} + +// estimateGas estimates the gas needed for methods on the ERC20 token contract. +// For estimating methods on the swap contract, use (contractorV0).estimateGas. +func (c *tokenContractorV0) estimateGas(ctx context.Context, method string, args ...interface{}) (uint64, error) { + return estimateGas(ctx, c.acctAddr, c.contractAddr, c.abi, c.cb, new(big.Int), method, args...) +} + +// value finds incoming or outgoing value for the tx to either the swap contract +// or the erc20 token contract. For the token contract, only transfer and +// transferFrom are parsed. It is not an error if this tx is a call to another +// method of the token contract, but no values will be parsed. +func (c *tokenContractorV0) value(ctx context.Context, tx *types.Transaction) (in, out uint64, err error) { + to := *tx.To() + if to == c.contractAddr { + return c.contractorV0.value(ctx, tx) + } + if to != c.tokenAddr { + return 0, 0, nil + } + + // Consider removing. We'll never be sending transferFrom transactions + // directly. + if sender, _, value, err := erc20.ParseTransferFromData(tx.Data()); err == nil && sender == c.acctAddr { + return 0, c.atomize(value), nil + } + + if _, value, err := erc20.ParseTransferData(tx.Data()); err == nil { + return 0, c.atomize(value), nil } - return c.tokenContract.BalanceOf(callOpts, c.acctAddr) + 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, acctAddr common.Address, cb bind.ContractBackend) (contractor, error) { + contractAddr, exists := dexeth.ContractAddresses[1][net] + if !exists || contractAddr == (common.Address{}) { + return nil, fmt.Errorf("no contract address for version 0, net %s", net) + } + c, err := swapv1.NewETHSwap(contractAddr, cb) + if err != nil { + return nil, err } - return c.tokenContract.Allowance(callOpts, c.acctAddr, c.contractAddr) + return &contractorV1{ + contractV1: c, + abi: dexeth.ABIs[1], + net: net, + contractAddr: contractAddr, + acctAddr: acctAddr, + cb: cb, + atomize: dexeth.WeiToGwei, + }, nil } -// approve sends an approve transaction approving the linked contract to call -// transferFrom for the specified amount. -func (c *tokenContractorV0) approve(txOpts *bind.TransactOpts, amount *big.Int) (tx *types.Transaction, err error) { - return c.tokenContract.Approve(txOpts, c.contractAddr, amount) +func (c *contractorV1) status(ctx context.Context, locator []byte) (*dexeth.SwapStatus, error) { + v, err := dexeth.ParseV1Locator(locator) + if err != nil { + return nil, err + } + rec, err := c.Status(&bind.CallOpts{From: c.acctAddr}, dexeth.SwapVectorToAbigen(v)) + if err != nil { + return nil, err + } + return &dexeth.SwapStatus{ + Step: dexeth.SwapStep(rec.Step), + Secret: rec.Secret, + BlockHeight: rec.BlockNumber.Uint64(), + }, err } -// transfer calls the transfer method of the erc20 token contract. Used for -// sends or withdrawals. -func (c *tokenContractorV0) transfer(txOpts *bind.TransactOpts, addr common.Address, amount *big.Int) (tx *types.Transaction, err error) { - return c.tokenContract.Transfer(txOpts, addr, amount) +func (c *contractorV1) vector(ctx context.Context, locator []byte) (*dexeth.SwapVector, error) { + return dexeth.ParseV1Locator(locator) +} + +func (c *contractorV1) statusAndVector(ctx context.Context, locator []byte) (*dexeth.SwapStatus, *dexeth.SwapVector, error) { + v, err := dexeth.ParseV1Locator(locator) + if err != nil { + return nil, nil, err + } + + rec, err := c.Status(&bind.CallOpts{From: c.acctAddr}, dexeth.SwapVectorToAbigen(v)) + if err != nil { + return nil, nil, err + } + return &dexeth.SwapStatus{ + Step: dexeth.SwapStep(rec.Step), + Secret: rec.Secret, + BlockHeight: rec.BlockNumber.Uint64(), + }, v, err +} + +func (c *contractorV1) voidUnusedNonce() { + if mRPC, is := c.cb.(*multiRPCClient); is { + mRPC.voidUnusedNonce() + } +} + +// func (c *contractorV1) record(v *dexeth.SwapVector) (r [32]byte, err error) { +// abiVec := dexeth.SwapVectorToAbigen(v) +// ck, err := c.ContractKey(&bind.CallOpts{From: c.acctAddr}, abiVec) +// if err != nil { +// return r, fmt.Errorf("ContractKey error: %v", err) +// } +// return c.Swaps(&bind.CallOpts{From: c.acctAddr}, ck) +// } + +func (c *contractorV1) initiate(txOpts *bind.TransactOpts, contracts []*asset.Contract) (*types.Transaction, error) { + versionedContracts := make([]swapv1.ETHSwapVector, 0, len(contracts)) + for _, ac := range contracts { + v := &dexeth.SwapVector{ + From: c.acctAddr, + To: common.HexToAddress(ac.Address), + Value: ac.Value, + LockTime: ac.LockTime, + } + copy(v.SecretHash[:], ac.SecretHash) + versionedContracts = append(versionedContracts, dexeth.SwapVectorToAbigen(v)) + } + return c.Initiate(txOpts, versionedContracts) +} + +func (c *contractorV1) redeem(txOpts *bind.TransactOpts, redeems []*asset.Redemption) (*types.Transaction, error) { + versionedRedemptions := make([]swapv1.ETHSwapRedemption, 0, len(redeems)) + secretHashes := make(map[[32]byte]bool, len(redeems)) + for _, r := range redeems { + var secret [32]byte + copy(secret[:], r.Secret) + secretHash := sha256.Sum256(r.Secret) + if !bytes.Equal(secretHash[:], r.Spends.SecretHash) { + return nil, errors.New("wrong secret") + } + if secretHashes[secretHash] { + return nil, fmt.Errorf("duplicate secret hash %x", secretHash[:]) + } + secretHashes[secretHash] = true + + // Not checking version from DecodeLocator because it was already + // audited and incorrect version locator would err below anyway. + _, locator, err := dexeth.DecodeLocator(r.Spends.Contract) + if err != nil { + return nil, fmt.Errorf("error parsing locator redeem: %w", err) + } + v, err := dexeth.ParseV1Locator(locator) + if err != nil { + return nil, fmt.Errorf("error parsing locator: %w", err) + } + versionedRedemptions = append(versionedRedemptions, swapv1.ETHSwapRedemption{ + V: dexeth.SwapVectorToAbigen(v), + Secret: secret, + }) + } + return c.Redeem(txOpts, versionedRedemptions) +} + +func (c *contractorV1) refund(txOpts *bind.TransactOpts, locator []byte) (*types.Transaction, error) { + v, err := dexeth.ParseV1Locator(locator) + if err != nil { + return nil, err + } + return c.Refund(txOpts, dexeth.SwapVectorToAbigen(v)) +} + +func (c *contractorV1) estimateInitGas(ctx context.Context, n int) (uint64, error) { + initiations := make([]swapv1.ETHSwapVector, 0, n) + for j := 0; j < n; j++ { + var secretHash [32]byte + copy(secretHash[:], encode.RandomBytes(32)) + initiations = append(initiations, swapv1.ETHSwapVector{ + RefundTimestamp: 1, + SecretHash: secretHash, + Initiator: c.acctAddr, + Participant: common.BytesToAddress(encode.RandomBytes(20)), + Value: 1, + }) + } + + var value *big.Int + if !c.isToken { + value = dexeth.GweiToWei(uint64(n)) + } + + return c.estimateGas(ctx, value, "initiate", initiations) +} + +func (c *contractorV1) estimateGas(ctx context.Context, value *big.Int, method string, args ...interface{}) (uint64, error) { + return estimateGas(ctx, c.acctAddr, c.contractAddr, c.abi, c.cb, value, method, args...) +} + +func (c *contractorV1) estimateRedeemGas(ctx context.Context, secrets [][32]byte, locators [][]byte) (uint64, error) { + if len(secrets) != len(locators) { + return 0, fmt.Errorf("number of secrets (%d) does not match number of contracts (%d)", len(secrets), len(locators)) + } + + vectors := make([]*dexeth.SwapVector, len(locators)) + for i, loc := range locators { + v, err := dexeth.ParseV1Locator(loc) + if err != nil { + return 0, fmt.Errorf("unable to parse locator # %d (%x): %v", i, loc, err) + } + vectors[i] = v + } + + redemps := make([]swapv1.ETHSwapRedemption, 0, len(secrets)) + for i, secret := range secrets { + redemps = append(redemps, swapv1.ETHSwapRedemption{ + Secret: secret, + V: dexeth.SwapVectorToAbigen(vectors[i]), + }) + } + return c.estimateGas(ctx, nil, "redeem", redemps) +} + +func (c *contractorV1) estimateRefundGas(ctx context.Context, locator []byte) (uint64, error) { + v, err := dexeth.ParseV1Locator(locator) + if err != nil { + return 0, err + } + return c.estimateGas(ctx, nil, "refund", dexeth.SwapVectorToAbigen(v)) +} + +func (c *contractorV1) isRedeemable(locator []byte, secret [32]byte) (bool, error) { + v, err := dexeth.ParseV1Locator(locator) + if err != nil { + return false, err + } + if v.To != c.acctAddr { + return false, nil + } + if is, err := c.IsRedeemable(&bind.CallOpts{From: c.acctAddr}, dexeth.SwapVectorToAbigen(v)); err != nil || !is { + return is, err + } + return sha256.Sum256(secret[:]) == v.SecretHash, nil +} + +func (c *contractorV1) isRefundable(locator []byte) (bool, error) { + v, err := dexeth.ParseV1Locator(locator) + if err != nil { + return false, err + } + if is, err := c.IsRedeemable(&bind.CallOpts{From: c.acctAddr}, dexeth.SwapVectorToAbigen(v)); err != nil || !is { + return is, err + } + return time.Now().Unix() >= int64(v.LockTime), nil +} + +func (c *contractorV1) incomingValue(ctx context.Context, tx *types.Transaction) (uint64, error) { + if redeems, err := dexeth.ParseRedeemDataV1(tx.Data()); err == nil { + var redeemed uint64 + for _, r := range redeems { + redeemed += r.Contract.Value + } + return redeemed, nil + } + refund, err := dexeth.ParseRefundDataV1(tx.Data()) + if err != nil { + return 0, nil + } + return refund.Value, nil +} + +func (c *contractorV1) outgoingValue(tx *types.Transaction) (swapped uint64) { + if inits, err := dexeth.ParseInitiateDataV1(tx.Data()); err == nil { + for _, init := range inits { + swapped += init.Value + } + } + return +} + +func (c *contractorV1) value(ctx context.Context, tx *types.Transaction) (in, out uint64, err error) { + if *tx.To() != c.contractAddr { + return 0, 0, nil + } + + if v, err := c.incomingValue(ctx, tx); err != nil { + return 0, 0, fmt.Errorf("incomingValue error: %w", err) + } else if v > 0 { + return v, 0, nil + } + + return 0, c.outgoingValue(tx), nil +} + +type tokenContractorV1 struct { + *contractorV1 + *erc20Contractor + tokenAddr common.Address +} + +func newV1TokenContractor(net dex.Network, assetID uint32, acctAddr common.Address, cb bind.ContractBackend) (tokenContractor, error) { + token, tokenAddr, swapContractAddr, err := dexeth.VersionedNetworkToken(assetID, 1, net) + if err != nil { + return nil, err + } + + c, err := erc20v1.NewERC20Swap(swapContractAddr, cb) + if err != nil { + return nil, err + } + + tokenContract, err := erc20.NewIERC20(tokenAddr, cb) + if err != nil { + return nil, err + } + + if boundAddr, err := c.TokenAddress(&bind.CallOpts{ + Context: context.TODO(), + }); err != nil { + return nil, fmt.Errorf("error reading bound token address %q: %w", tokenAddr, err) + } else if boundAddr != tokenAddr { + return nil, fmt.Errorf("wrong bound address. expected %s, got %s", tokenAddr, boundAddr) + } + + return &tokenContractorV1{ + contractorV1: &contractorV1{ + contractV1: c, + abi: dexeth.ABIs[1], + net: net, + contractAddr: swapContractAddr, + acctAddr: acctAddr, + cb: cb, + evmify: token.AtomicToEVM, + atomize: token.EVMToAtomic, + }, + tokenAddr: tokenAddr, + erc20Contractor: &erc20Contractor{ + tokenContract: tokenContract, + acct: acctAddr, + contract: swapContractAddr, + }, + }, nil } // estimateApproveGas estimates the gas needed to send an approve tx. -func (c *tokenContractorV0) estimateApproveGas(ctx context.Context, amount *big.Int) (uint64, error) { - return c.estimateGas(ctx, "approve", c.contractAddr, amount) +func (c *tokenContractorV1) estimateApproveGas(ctx context.Context, amount *big.Int) (uint64, error) { + return c.estimateTokenContractGas(ctx, "approve", c.contractAddr, amount) } // estimateTransferGas estimates the gas needed for a transfer tx. The account // needs to have > amount tokens to use this method. -func (c *tokenContractorV0) estimateTransferGas(ctx context.Context, amount *big.Int) (uint64, error) { - return c.estimateGas(ctx, "transfer", c.acctAddr, amount) +func (c *tokenContractorV1) estimateTransferGas(ctx context.Context, amount *big.Int) (uint64, error) { + return c.estimateTokenContractGas(ctx, "transfer", c.acctAddr, amount) } // estimateGas estimates the gas needed for methods on the ERC20 token contract. // For estimating methods on the swap contract, use (contractorV0).estimateGas. -func (c *tokenContractorV0) estimateGas(ctx context.Context, method string, args ...interface{}) (uint64, error) { - data, err := erc20.ERC20ABI.Pack(method, args...) - if err != nil { - return 0, fmt.Errorf("token estimateGas Pack error: %v", err) - } - - return c.cb.EstimateGas(ctx, ethereum.CallMsg{ - From: c.acctAddr, - To: &c.tokenAddr, - Data: data, - }) +func (c *tokenContractorV1) estimateTokenContractGas(ctx context.Context, method string, args ...interface{}) (uint64, error) { + return estimateGas(ctx, c.acctAddr, c.tokenAddr, erc20.ERC20ABI, c.cb, new(big.Int), method, args...) } // value finds incoming or outgoing value for the tx to either the swap contract // or the erc20 token contract. For the token contract, only transfer and // transferFrom are parsed. It is not an error if this tx is a call to another // method of the token contract, but no values will be parsed. -func (c *tokenContractorV0) value(ctx context.Context, tx *types.Transaction) (in, out uint64, err error) { +func (c *tokenContractorV1) value(ctx context.Context, tx *types.Transaction) (in, out uint64, err error) { to := *tx.To() if to == c.contractAddr { - return c.contractorV0.value(ctx, tx) + return c.contractorV1.value(ctx, tx) } if to != c.tokenAddr { return 0, 0, nil @@ -461,14 +939,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 73e09b1108..7df9539761 100644 --- a/client/asset/eth/eth.go +++ b/client/asset/eth/eth.go @@ -97,6 +97,9 @@ const ( // TODO: Find a way to ask the host about their config set max fee and // gas values. maxTxFeeGwei = 1_000_000_000 + + contractVersionERC20 = ^uint32(0) + contractVersionUnknown = contractVersionERC20 - 1 ) var ( @@ -195,6 +198,10 @@ var ( } return blockGasLimit }() + // https://github.com/ethereum/go-ethereum/blob/16341e05636fd088aa04a27fca6dc5cda5dbab8f/eth/backend.go#L110-L113 + // ultimately results in a minimum fee rate by the filter applied at + // https://github.com/ethereum/go-ethereum/blob/4ebeca19d739a243dc0549bcaf014946cde95c4f/core/tx_pool.go#L626 + minGasPrice = ethconfig.Defaults.Miner.GasPrice ) // WalletConfig are wallet-level configuration settings. @@ -509,7 +516,7 @@ type assetWallet struct { } findRedemptionMtx sync.RWMutex - findRedemptionReqs map[[32]byte]*findRedemptionRequest + findRedemptionReqs map[string]*findRedemptionRequest approvalsMtx sync.RWMutex pendingApprovals map[uint32]*pendingApproval @@ -609,6 +616,25 @@ 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 0: + return 0 + case 1: + return 1 + default: + return contractVersionUnknown + } +} + +// CreateWallet creates a new internal ETH wallet and stores the private key +// derived from the wallet seed. +func CreateWallet(cfg *asset.CreateWalletParams) error { + return CreateEVMWallet(dexeth.ChainIDs[cfg.Net], cfg, false) +} + func CreateEVMWallet(chainID int64, createWalletParams *asset.CreateWalletParams, skipConnect bool) error { switch createWalletParams.Type { case walletTypeGeth: @@ -734,7 +760,7 @@ func NewEVMWallet(assetID uint32, chainID int64, assetCFG *asset.WalletConfig, l log: logger, assetID: assetID, tipChange: assetCFG.TipChange, - findRedemptionReqs: make(map[[32]byte]*findRedemptionRequest), + findRedemptionReqs: make(map[string]*findRedemptionRequest), pendingApprovals: make(map[uint32]*pendingApproval), approvalCache: make(map[uint32]bool), peersChange: assetCFG.PeersChange, @@ -1048,7 +1074,7 @@ func (w *ETHWallet) OpenTokenWallet(tokenCfg *asset.TokenConfig) (asset.Wallet, assetID: tokenCfg.AssetID, tipChange: tokenCfg.TipChange, peersChange: tokenCfg.PeersChange, - findRedemptionReqs: make(map[[32]byte]*findRedemptionRequest), + findRedemptionReqs: make(map[string]*findRedemptionRequest), pendingApprovals: make(map[uint32]*pendingApproval), approvalCache: make(map[uint32]bool), contractors: make(map[uint32]contractor), @@ -1213,8 +1239,8 @@ 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 @@ -1223,7 +1249,10 @@ func (w *assetWallet) maxOrder(lotSize uint64, maxFeeRate uint64, ver uint32, return &asset.SwapEstimate{}, nil } - g, err := w.initGasEstimate(1, ver, redeemVer, redeemAssetID) + contractVer := contractVersion(serverVer) + redeemVer := contractVersion(redeemServerVer) + + g, err := w.initGasEstimate(1, contractVer, redeemVer, redeemAssetID) if err != nil { return nil, fmt.Errorf("gasEstimate error: %w", err) } @@ -1249,7 +1278,7 @@ func (w *assetWallet) maxOrder(lotSize uint64, maxFeeRate uint64, ver uint32, if lots < 1 { return &asset.SwapEstimate{}, nil } - return w.estimateSwap(lots, lotSize, maxFeeRate, ver, redeemVer, redeemAssetID) + return w.estimateSwap(lots, lotSize, maxFeeRate, contractVer, redeemVer, redeemAssetID) } // PreSwap gets order estimates based on the available funds and the wallet @@ -1276,7 +1305,7 @@ func (w *assetWallet) preSwap(req *asset.PreSwapForm, feeWallet *assetWallet) (* } est, err := w.estimateSwap(req.Lots, req.LotSize, req.MaxFeeRate, - req.Version, req.RedeemVersion, req.RedeemAssetID) + contractVersion(req.Version), contractVersion(req.RedeemVersion), req.RedeemAssetID) if err != nil { return nil, err } @@ -1288,7 +1317,7 @@ func (w *assetWallet) preSwap(req *asset.PreSwapForm, feeWallet *assetWallet) (* // SingleLotSwapFees returns the fees for a swap transaction for a single lot. func (w *assetWallet) SingleLotSwapFees(version uint32, feeSuggestion uint64, _ map[string]string) (fees uint64, err error) { - g := w.gases(version) + g := w.gases(contractVersion(version)) if g == nil { return 0, fmt.Errorf("no gases known for %d version %d", w.assetID, version) } @@ -1297,7 +1326,7 @@ func (w *assetWallet) SingleLotSwapFees(version uint32, feeSuggestion uint64, _ // 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, +func (w *assetWallet) estimateSwap(lots, lotSize uint64, maxFeeRate uint64, contractVer uint32, redeemVer, redeemAssetID uint32) (*asset.SwapEstimate, error) { if lots == 0 { return &asset.SwapEstimate{}, nil @@ -1313,7 +1342,7 @@ func (w *assetWallet) estimateSwap(lots, lotSize uint64, maxFeeRate uint64, ver } // 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) } @@ -1342,7 +1371,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 } @@ -1356,10 +1385,10 @@ 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, options map[string]string) (fees uint64, err error) { - g := w.gases(version) +func (w *assetWallet) SingleLotRedeemFees(serverVer uint32, feeSuggestion uint64, options map[string]string) (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 } @@ -1408,7 +1437,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) @@ -1441,7 +1472,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) } @@ -1449,10 +1481,10 @@ func (w *TokenWallet) FundOrder(ord *asset.Order) (asset.Coins, []dex.Bytes, uin return nil, nil, 0, asset.ErrUnapprovedToken } - 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 @@ -1623,10 +1655,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 @@ -1654,7 +1686,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 { return 0, 0, err // Or we could go with what we know? But this estimate error could be a @@ -1670,7 +1702,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 @@ -1690,8 +1722,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) } @@ -1705,10 +1737,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 @@ -1826,13 +1858,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 } @@ -1853,7 +1885,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 @@ -1862,8 +1894,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 @@ -1901,9 +1933,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) } @@ -1929,7 +1961,7 @@ func (w *ETHWallet) Swap(swaps *asset.Swaps) ([]asset.Receipt, asset.Coin, uint6 } } - tx, err := w.initiate(w.ctx, w.assetID, swaps.Contracts, swaps.FeeRate, gasLimit, swaps.Version) + tx, err := w.initiate(w.ctx, w.assetID, swaps.Contracts, swaps.FeeRate, gasLimit, contractVer) if err != nil { return fail("Swap: initiate error: %w", err) } @@ -1939,15 +1971,13 @@ func (w *ETHWallet) Swap(swaps *asset.Swaps) ([]asset.Receipt, asset.Coin, uint6 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: dexeth.ContractAddresses[swaps.Version][w.net].String(), + locator: acToLocator(contractVer, swap, w.addr), + contractVer: contractVer, + contractAddr: dexeth.ContractAddresses[contractVer][w.net].String(), }) } @@ -1962,6 +1992,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. @@ -1994,7 +2044,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) } @@ -2014,7 +2065,7 @@ func (w *TokenWallet) Swap(swaps *asset.Swaps) ([]asset.Receipt, asset.Coin, uin } // See (*ETHWallet).Swap comments for a third option. } - tx, err := w.initiate(w.ctx, w.assetID, swaps.Contracts, swaps.FeeRate, gasLimit, swaps.Version) + tx, err := w.initiate(w.ctx, w.assetID, swaps.Contracts, swaps.FeeRate, gasLimit, contractVer) if err != nil { return fail("Swap: initiate error: %w", err) } @@ -2030,14 +2081,12 @@ func (w *TokenWallet) Swap(swaps *asset.Swaps) ([]asset.Receipt, asset.Coin, uin 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, }) } @@ -2088,16 +2137,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 { @@ -2112,20 +2164,26 @@ func (w *assetWallet) Redeem(form *asset.RedeemForm, feeWallet *assetWallet, non var secret [32]byte copy(secret[:], redemption.Secret) secrets = append(secrets, secret) - redeemable, err := w.isRedeemable(secretHash, secret, ver) + redeemable, err := w.isRedeemable(locator, secret, ver) if err != nil { return fail(fmt.Errorf("Redeem: failed to check if swap is redeemable: %w", err)) } if !redeemable { - return fail(fmt.Errorf("Redeem: secretHash %x not redeemable with secret %x", - secretHash, secret)) + return fail(fmt.Errorf("Redeem: version %d locator %x not redeemable with secret %x", + ver, locator, secret)) } - swapData, err := w.swap(w.ctx, secretHash, ver) - if err != nil { - return nil, nil, 0, fmt.Errorf("error finding swap state: %w", err) - } - redeemedValue += w.atomize(swapData.Value) + // DRAFT NOTE: With v0, we were pulling the swap value from the + // contract here. This was necessary, because we are spoofing + // asset.Redemptions in resubmitRedemption. Now, we're calling + // (contract).vector in resubmitRedemption to set the Coin. + // Net effects: a) one fewer contract call for version 0 for regular + // redemptions, b) same number of contract calls for version 0 for + // resubs. There are no contracts call for value retrieval for version + // 1, since the value is encoded in the locator, which has already been + // used in isRedeemable to verify the status. + + redeemedValue += redemption.Spends.Coin.Value() } g := w.gases(contractVer) @@ -2241,7 +2299,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 }) @@ -2249,8 +2307,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 }) @@ -2307,7 +2365,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) } @@ -2482,8 +2540,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") } @@ -2528,8 +2586,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") } @@ -2538,8 +2596,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") } @@ -2615,24 +2673,73 @@ func (w *assetWallet) AuditContract(coinID, contract, serializedTx dex.Bytes, re return nil, fmt.Errorf("AuditContract: coin id != txHash - coin id: %x, txHash: %s", coinID, tx.Hash()) } - version, secretHash, err := dexeth.DecodeContractData(contract) + version, locator, err := dexeth.DecodeLocator(contract) if err != nil { return nil, fmt.Errorf("AuditContract: failed to decode contract data: %w", err) } - initiations, err := dexeth.ParseInitiateData(tx.Data(), version) - if err != nil { - return nil, fmt.Errorf("AuditContract: failed to parse initiate data: %w", err) - } + var val uint64 + var participant string + var lockTime time.Time + var secretHashB []byte + switch version { + case 0: + initiations, err := dexeth.ParseInitiateDataV0(tx.Data()) + if err != nil { + return nil, fmt.Errorf("AuditContract: failed to parse initiate data: %w", err) + } + + secretHash, err := dexeth.ParseV0Locator(locator) + if err != nil { + return nil, fmt.Errorf("error parsing v0 locator (%x): %w", locator, err) + } - initiation, ok := initiations[secretHash] - if !ok { - return nil, errors.New("AuditContract: tx does not initiate secret hash") + initiation, ok := initiations[secretHash] + if !ok { + return nil, errors.New("AuditContract: tx does not initiate secret hash") + } + val = w.atomize(initiation.Value) + participant = initiation.Participant.String() + lockTime = initiation.LockTime + secretHashB = secretHash[:] + case 1: + vec, err := dexeth.ParseV1Locator(locator) + if err != nil { + return nil, err + } + txVectors, err := dexeth.ParseInitiateDataV1(tx.Data()) + if err != nil { + return nil, fmt.Errorf("AuditContract: failed to parse initiate data: %w", err) + } + txVec, ok := txVectors[vec.SecretHash] + if !ok { + return nil, errors.New("AuditContract: tx does not initiate secret hash") + } + // Check vector equivalence. Secret hash equivalence is implied by the + // vectors presence in the map returned from ParseInitiateData. + if vec.Value != txVec.Value { + return nil, errors.New("tx data value doesn't match reported locator data") + } + if vec.To != txVec.To { + return nil, errors.New("tx to address doesn't match reported locator data") + } + if vec.From != txVec.From { + return nil, errors.New("tx from address doesn't match reported locator data") + } + if vec.LockTime != txVec.LockTime { + return nil, errors.New("tx lock time doesn't match reported locator data") + } + val = vec.Value + participant = vec.To.String() + lockTime = time.Unix(int64(vec.LockTime), 0) + secretHashB = vec.SecretHash[:] + default: + return nil, fmt.Errorf("unknown contract version %d", version) } coin := &coin{ id: txHash, - value: w.atomize(initiation.Value), + value: val, } // The counter-party should have broadcasted the contract tx but rebroadcast @@ -2647,11 +2754,11 @@ func (w *assetWallet) AuditContract(coinID, contract, serializedTx dex.Bytes, re } return &asset.AuditInfo{ - Recipient: initiation.Participant.Hex(), - Expiration: initiation.LockTime, + Recipient: participant, + Expiration: lockTime, Coin: coin, Contract: contract, - SecretHash: secretHash[:], + SecretHash: secretHashB, }, nil } @@ -2669,26 +2776,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 } func (eth *baseWallet) addPendingTx(assetID uint32, txHash common.Hash, nonce, out, in, fees uint64) { @@ -2727,22 +2833,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 } @@ -2759,13 +2864,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 } @@ -2780,14 +2885,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() @@ -2798,11 +2905,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 { @@ -2812,73 +2919,75 @@ 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, err := w.status(w.ctx, locator, contractVer) if err != nil { return nil, err } // It's possible the swap was refunded by someone else. In that case we // cannot know the refunding tx hash. - switch swap.State { + switch status.Step { case dexeth.SSInitiated: // good, check refundability case dexeth.SSNone: return nil, asset.ErrSwapNotInitiated case dexeth.SSRefunded: - w.log.Infof("Swap with secret hash %x already refunded.", secretHash) + w.log.Infof("Swap with 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) } - tx, fees, err := w.refund(secretHash, feeRate, version) + tx, fees, err := w.refund(locator, feeRate, contractVer) if err != nil { return nil, fmt.Errorf("Refund: failed to call refund: %w", err) } + vector, err := w.vector(w.ctx, locator, contractVer) + if err != nil { + return nil, fmt.Errorf("Refund: failed to get vector: %w", err) + } + txHash := tx.Hash() - w.addPendingTx(w.assetID, txHash, tx.Nonce(), 0, dexeth.WeiToGwei(swap.Value), fees) + w.addPendingTx(w.assetID, txHash, tx.Nonce(), 0, vector.Value, fees) return txHash[:], nil } @@ -2932,7 +3041,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 @@ -3007,7 +3116,7 @@ func (w *TokenWallet) canSend(value uint64, verifyBalance, isPreEstimate bool) ( return 0, nil, fmt.Errorf("error getting max fee rate: %w", err) } - g := w.gases(contractVersionNewest) + g := w.gases(contractVersionERC20) if g == nil { return 0, nil, fmt.Errorf("gas table not found") } @@ -3099,7 +3208,7 @@ func (w *assetWallet) RestorationInfo(seed []byte) ([]*asset.WalletRestoration, // SwapConfirmations gets the number of confirmations and the spend status // for the specified swap. func (w *assetWallet) SwapConfirmations(ctx context.Context, coinID dex.Bytes, contract dex.Bytes, _ time.Time) (confs uint32, spent bool, err error) { - contractVer, secretHash, err := dexeth.DecodeContractData(contract) + contractVer, secretHash, err := dexeth.DecodeLocator(contract) if err != nil { return 0, false, err } @@ -3112,17 +3221,35 @@ func (w *assetWallet) SwapConfirmations(ctx context.Context, coinID dex.Bytes, c return 0, false, fmt.Errorf("error fetching best header: %w", err) } - swapData, err := w.swap(w.ctx, secretHash, contractVer) + status, err := w.status(w.ctx, secretHash, contractVer) if err != nil { return 0, false, fmt.Errorf("error finding swap state: %w", err) } - if swapData.State == dexeth.SSNone { + if status.Step == dexeth.SSNone { return 0, false, asset.ErrSwapNotInitiated } - spent = swapData.State >= dexeth.SSRedeemed - confs = uint32(hdr.Number.Uint64() - swapData.BlockHeight + 1) + spent = status.Step >= dexeth.SSRedeemed + confs = uint32(hdr.Number.Uint64() - status.BlockHeight + 1) + + // NOTE: confs will equal to the block number for the version 1 contract if spent == true, + // because BlockHeight will be zero. This is probably fine, since the caller + // will examine spent first? Otherwise, we could do this. + // + // if spent && contractVer == 1 { + // // If it's spent and the contract version is 1, the status will only + // // contain the secret OR the block number, not both. + // var txHash common.Hash + // copy(txHash[:], coinID) + // confs, err = w.node.transactionConfirmations(ctx, txHash) + // if err != nil { + // return 0, false, fmt.Errorf("error finding swap state: %w", err) + // } + // } + // + // Or maybe it'd be better to set confs to some constant, large, round + // number so it doesn't look dumb in logs. return } @@ -3260,7 +3387,7 @@ func (eth *assetWallet) DynamicRedemptionFeesPaid(ctx context.Context, coinID, c // secret hashes. func (eth *baseWallet) swapOrRedemptionFeesPaid(ctx context.Context, coinID, contractData dex.Bytes, isInit bool) (fee uint64, secretHashes [][]byte, err error) { - contractVer, secretHash, err := dexeth.DecodeContractData(contractData) + contractVer, locator, err := dexeth.DecodeLocator(contractData) if err != nil { return 0, nil, err } @@ -3292,41 +3419,80 @@ func (eth *baseWallet) swapOrRedemptionFeesPaid(ctx context.Context, coinID, con effectiveGasPrice := new(big.Int).Add(hdr.BaseFee, tx.EffectiveGasTipValue(hdr.BaseFee)) bigFees := new(big.Int).Mul(effectiveGasPrice, big.NewInt(int64(receipt.GasUsed))) - if isInit { - inits, err := dexeth.ParseInitiateData(tx.Data(), contractVer) - if err != nil { - return 0, nil, fmt.Errorf("invalid initiate data: %v", err) - } - secretHashes = make([][]byte, 0, len(inits)) - for k := range inits { - copyK := k - secretHashes = append(secretHashes, copyK[:]) - } - } else { - redeems, err := dexeth.ParseRedeemData(tx.Data(), contractVer) - if err != nil { - return 0, nil, fmt.Errorf("invalid redeem data: %v", err) - } - secretHashes = make([][]byte, 0, len(redeems)) - for k := range redeems { - copyK := k - secretHashes = append(secretHashes, copyK[:]) - } + locators, secretHashes, err := parseSecretHashes(tx, contractVer, isInit) + if err != nil { + return 0, nil, err } - sort.Slice(secretHashes, func(i, j int) bool { return bytes.Compare(secretHashes[i], secretHashes[j]) < 0 }) + + sort.Slice(locators, func(i, j int) bool { return bytes.Compare(locators[i], locators[j]) < 0 }) var found bool - for i := range secretHashes { - if bytes.Equal(secretHash[:], secretHashes[i]) { + for i := range locators { + if bytes.Equal(locator, locators[i]) { found = true break } } if !found { - return 0, nil, fmt.Errorf("secret hash %x not found in transaction", secretHash) + return 0, nil, fmt.Errorf("locator %x not found in transaction", locator) } return dexeth.WeiToGwei(bigFees), secretHashes, nil } +func parseSecretHashes(tx *types.Transaction, contractVer uint32, isInit bool) (locators, secretHashes [][]byte, err error) { + switch contractVer { + case 0: + if isInit { + inits, err := dexeth.ParseInitiateDataV0(tx.Data()) + if err != nil { + return nil, nil, fmt.Errorf("invalid initiate data: %v", err) + } + locators = make([][]byte, 0, len(inits)) + for k := range inits { + copyK := k // TODO: Is this really necessary? + locators = append(locators, copyK[:]) + } + } else { + redeems, err := dexeth.ParseRedeemDataV0(tx.Data()) + if err != nil { + return nil, nil, fmt.Errorf("invalid redeem data: %v", err) + } + locators = make([][]byte, 0, len(redeems)) + for k := range redeems { + copyK := k + locators = append(locators, copyK[:]) + } + } + return locators, locators, nil + case 1: + if isInit { + vectors, err := dexeth.ParseInitiateDataV1(tx.Data()) + if err != nil { + return nil, nil, fmt.Errorf("invalid initiate data: %v", err) + } + locators = make([][]byte, 0, len(vectors)) + secretHashes = make([][]byte, 0, len(vectors)) + for _, vec := range vectors { + locators = append(locators, vec.Locator()) + secretHashes = append(secretHashes, vec.SecretHash[:]) + } + } else { + redeems, err := dexeth.ParseRedeemDataV1(tx.Data()) + if err != nil { + return nil, nil, fmt.Errorf("invalid redeem data: %v", err) + } + locators = make([][]byte, 0, len(redeems)) + secretHashes = make([][]byte, 0, len(redeems)) + for secretHash, r := range redeems { + locators = append(locators, r.Contract.Locator()) + secretHashes = append(secretHashes, secretHash[:]) + } + } + return locators, secretHashes, nil + default: + return nil, nil, fmt.Errorf("unknown server version %d", contractVer) + } +} + // RegFeeConfirmations gets the number of confirmations for the specified // transaction. func (eth *baseWallet) RegFeeConfirmations(ctx context.Context, coinID dex.Bytes) (confs uint32, err error) { @@ -3603,33 +3769,50 @@ func (w *assetWallet) monitorTx(tx *types.Transaction, blockSubmitted uint64) { // in the batch that are still redeemable are included in the new transaction. // nonceOverride is set to a non-nil value when a specific nonce is required // (when a transaction has not been mined due to a low fee). -func (w *assetWallet) resubmitRedemption(tx *types.Transaction, contractVersion uint32, nonceOverride *uint64, feeWallet *assetWallet, monitoredTx *monitoredTx) (*common.Hash, error) { - parsedRedemptions, err := dexeth.ParseRedeemData(tx.Data(), contractVersion) +func (w *assetWallet) resubmitRedemption(tx *types.Transaction, contractVer uint32, + nonceOverride *uint64, feeWallet *assetWallet, monitoredTx *monitoredTx) (*common.Hash, error) { + + locators, secrets, err := parseRedeemLocatorsAndSecrets(tx, contractVer) if err != nil { - return nil, fmt.Errorf("failed to parse redeem data: %w", err) + return nil, err } - redemptions := make([]*asset.Redemption, 0, len(parsedRedemptions)) + redemptions := make([]*asset.Redemption, 0, len(locators)) // Whether or not a swap can be redeemed is checked in Redeem, but here // we filter out unredeemable swaps in case one of the swaps in the tx // was refunded/redeemed but the others were not. - for _, r := range parsedRedemptions { - redeemable, err := w.isRedeemable(r.SecretHash, r.Secret, contractVersion) + for i, locator := range locators { + secret := secrets[i] + redeemable, err := w.isRedeemable(locator, secret, contractVer) if err != nil { return nil, err } else if !redeemable { - w.log.Warnf("swap %x is not redeemable. not resubmitting", r.SecretHash) + w.log.Warnf("swap with locator %x is not redeemable. not resubmitting", locator) continue } - contractData := dexeth.EncodeContractData(contractVersion, r.SecretHash) + // DRAFT NOTE: This essentially replaces the call to (contractorV0).swap + // that was in (*assetWallet.).Redeem. For v0, it'll still be a + // contract call. For v1, we have the value encoded in the locator, so + // no contract call required. + vec, err := w.vector(w.ctx, locator, contractVer) + if err != nil { + return nil, err + } + redemptions = append(redemptions, &asset.Redemption{ Spends: &asset.AuditInfo{ - Contract: contractData, - SecretHash: r.SecretHash[:], + Recipient: vec.To.String(), + Expiration: time.Unix(int64(vec.LockTime), 0), + Coin: &coin{ + // id: txHash, // Just no way. + value: vec.Value, + }, + Contract: dexeth.EncodeContractData(contractVer, locator), + SecretHash: vec.SecretHash[:], }, - Secret: r.Secret[:], + Secret: secret[:], }) } if len(redemptions) == 0 { @@ -3660,15 +3843,46 @@ func (w *assetWallet) resubmitRedemption(tx *types.Transaction, contractVersion return &replacementHash, nil } +// parseRedeemLocatorsAndSecrets parses the locators and secrets for the given +// redemption transaction. +func parseRedeemLocatorsAndSecrets(tx *types.Transaction, contractVer uint32) (locators [][]byte, secrets [][32]byte, err error) { + switch contractVer { + case 0: + redemps, err := dexeth.ParseRedeemDataV0(tx.Data()) + if err != nil { + return nil, nil, fmt.Errorf("failed to parse v0 redeem data: %w", err) + } + locators, secrets = make([][]byte, 0, len(redemps)), make([][32]byte, 0, len(redemps)) + for _, r := range redemps { + locators = append(locators, r.SecretHash[:]) + secrets = append(secrets, r.Secret) + } + return locators, secrets, nil + case 1: + redemps, err := dexeth.ParseRedeemDataV1(tx.Data()) + if err != nil { + return nil, nil, fmt.Errorf("failed to parse v1 redeem data: %w", err) + } + locators, secrets = make([][]byte, 0, len(redemps)), make([][32]byte, 0, len(redemps)) + for _, r := range redemps { + locators = append(locators, r.Contract.Locator()) + secrets = append(secrets, r.Secret) + } + return locators, secrets, nil + default: + return nil, nil, fmt.Errorf("unknown redemption contract version %d", contractVer) + } +} + // swapIsRedeemed checks if a swap is in the redeemed state. ErrSwapRefunded // is returned if the swap has been refunded. -func (w *assetWallet) swapIsRedeemed(secretHash common.Hash, contractVersion uint32) (bool, error) { - swap, err := w.swap(w.ctx, secretHash, contractVersion) +func (w *assetWallet) swapIsRedeemed(locator []byte, contractVer uint32) (bool, error) { + status, err := w.status(w.ctx, locator, contractVer) if err != nil { return false, err } - switch swap.State { + switch status.Step { case dexeth.SSRedeemed: return true, nil case dexeth.SSRefunded: @@ -3718,9 +3932,9 @@ func confStatus(confs uint64, txHash common.Hash) *asset.ConfirmRedemptionStatus // -- resubmits the tx with a new nonce if it has been nonce replaced // -- resubmits the tx with the same nonce but higher fee if the fee is too low // -- otherwise, resubmits the same tx to ensure propagation -func (w *assetWallet) checkUnconfirmedRedemption(secretHash common.Hash, contractVer uint32, txHash common.Hash, tx *types.Transaction, feeWallet *assetWallet, monitoredTx *monitoredTx) (*asset.ConfirmRedemptionStatus, error) { +func (w *assetWallet) checkUnconfirmedRedemption(locator []byte, contractVer uint32, txHash common.Hash, tx *types.Transaction, currentTip uint64, feeWallet *assetWallet, monitoredTx *monitoredTx) (*asset.ConfirmRedemptionStatus, error) { // Check if the swap has been redeemed by another transaction we are unaware of. - swapIsRedeemed, err := w.swapIsRedeemed(secretHash, contractVer) + swapIsRedeemed, err := w.swapIsRedeemed(locator, contractVer) if err != nil { return nil, err } @@ -3777,7 +3991,7 @@ func (w *assetWallet) checkUnconfirmedRedemption(secretHash common.Hash, contrac // entire redemption batch, a new transaction containing only the swap we are // searching for will be created. func (w *assetWallet) confirmRedemptionWithoutMonitoredTx(txHash common.Hash, redemption *asset.Redemption, feeWallet *assetWallet) (*asset.ConfirmRedemptionStatus, error) { - contractVer, secretHash, err := dexeth.DecodeContractData(redemption.Spends.Contract) + contractVer, locator, err := dexeth.DecodeLocator(redemption.Spends.Contract) if err != nil { return nil, fmt.Errorf("failed to decode contract data: %w", err) } @@ -3790,7 +4004,7 @@ func (w *assetWallet) confirmRedemptionWithoutMonitoredTx(txHash common.Hash, re tx, txBlock, err := w.node.getTransaction(w.ctx, txHash) if errors.Is(err, asset.CoinNotFoundError) { w.log.Errorf("ConfirmRedemption: could not find tx: %s", txHash) - swapIsRedeemed, err := w.swapIsRedeemed(secretHash, contractVer) + swapIsRedeemed, err := w.swapIsRedeemed(locator, contractVer) if err != nil { return nil, err } @@ -3842,7 +4056,7 @@ func (w *assetWallet) confirmRedemptionWithoutMonitoredTx(txHash common.Hash, re return confStatus(confirmations, txHash), nil } - return w.checkUnconfirmedRedemption(secretHash, contractVer, txHash, tx, feeWallet, nil) + return w.checkUnconfirmedRedemption(locator, contractVer, txHash, tx, currentTip, feeWallet, nil) } // confirmRedemption checks the confirmation status of a redemption transaction. @@ -3870,7 +4084,7 @@ func (w *assetWallet) confirmRedemption(coinID dex.Bytes, redemption *asset.Rede txHash = monitoredTxHash } - contractVer, secretHash, err := dexeth.DecodeContractData(redemption.Spends.Contract) + contractVer, locator, err := dexeth.DecodeLocator(redemption.Spends.Contract) if err != nil { return nil, fmt.Errorf("failed to decode contract data: %w", err) } @@ -3895,7 +4109,7 @@ func (w *assetWallet) confirmRedemption(coinID dex.Bytes, redemption *asset.Rede return confStatus(0, txHash), nil } - swapIsRedeemed, err := w.swapIsRedeemed(secretHash, contractVer) + swapIsRedeemed, err := w.swapIsRedeemed(locator, contractVer) if err != nil { return nil, err } @@ -3944,17 +4158,18 @@ func (w *assetWallet) confirmRedemption(coinID dex.Bytes, redemption *asset.Rede return confStatus(0, txHash), nil } - return w.checkUnconfirmedRedemption(secretHash, contractVer, txHash, tx, feeWallet, monitoredTx) + return w.checkUnconfirmedRedemption(locator, contractVer, txHash, tx, currentTip, feeWallet, monitoredTx) } // checkFindRedemptions checks queued findRedemptionRequests. func (w *assetWallet) checkFindRedemptions() { - for secretHash, req := range w.findRedemptionRequests() { - secret, makerAddr, err := w.findSecret(secretHash, req.contractVer) + for loc, req := range w.findRedemptionRequests() { + 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) } } } @@ -4173,7 +4388,7 @@ func (w *ETHWallet) sendToAddr(addr common.Address, amt uint64, maxFeeRate *big. func (w *TokenWallet) sendToAddr(addr common.Address, amt uint64, maxFeeRate *big.Int) (tx *types.Transaction, err error) { w.baseWallet.nonceSendMtx.Lock() defer w.baseWallet.nonceSendMtx.Unlock() - g := w.gases(contractVersionNewest) + g := w.gases(contractVersionERC20) if g == nil { return nil, fmt.Errorf("no gas table") } @@ -4181,7 +4396,7 @@ func (w *TokenWallet) sendToAddr(addr common.Address, amt uint64, maxFeeRate *bi if err != nil { return nil, err } - return tx, w.withTokenContractor(w.assetID, contractVersionNewest, func(c tokenContractor) error { + return tx, w.withTokenContractor(w.assetID, contractVersionERC20, func(c tokenContractor) error { tx, err = c.transfer(txOpts, addr, w.evmify(amt)) if err != nil { c.voidUnusedNonce() @@ -4191,10 +4406,27 @@ func (w *TokenWallet) sendToAddr(addr common.Address, amt uint64, maxFeeRate *bi }) } -// swap gets a swap keyed by secretHash in the contract. -func (w *assetWallet) swap(ctx context.Context, secretHash [32]byte, contractVer uint32) (swap *dexeth.SwapState, err error) { - return swap, w.withContractor(contractVer, func(c contractor) error { - swap, err = c.swap(ctx, secretHash) +// status fetches the SwapStatus for the locator and contract version. +func (w *assetWallet) status(ctx context.Context, locator []byte, contractVer uint32) (s *dexeth.SwapStatus, err error) { + return s, w.withContractor(contractVer, func(c contractor) error { + s, err = c.status(ctx, locator) + return err + }) +} + +// vector fetches the SwapVector for the locator and contract version. +func (w *assetWallet) vector(ctx context.Context, locator []byte, contractVer uint32) (v *dexeth.SwapVector, err error) { + return v, w.withContractor(contractVer, func(c contractor) error { + v, err = c.vector(ctx, locator) + return err + }) +} + +// statusAndVector fetches the SwapStatus and SwapVector for the locator and +// contract version. +func (w *assetWallet) statusAndVector(ctx context.Context, locator []byte, contractVer uint32) (s *dexeth.SwapStatus, v *dexeth.SwapVector, err error) { + return s, v, w.withContractor(contractVer, func(c contractor) error { + s, v, err = c.statusAndVector(ctx, locator) return err }) } @@ -4239,17 +4471,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 }) } @@ -4287,7 +4519,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 { @@ -4319,7 +4552,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 }) @@ -4328,7 +4561,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 }) @@ -4367,7 +4600,7 @@ func (w *assetWallet) redeem(ctx context.Context, assetID uint32 /* ?? */, redem // refund refunds a swap contract using the account controlled by the wallet. // Any on-chain failure, such as the locktime not being past, will not cause // this to error. -func (w *assetWallet) refund(secretHash [32]byte, maxFeeRate uint64, contractVer uint32) (tx *types.Transaction, fees uint64, err error) { +func (w *assetWallet) refund(locator []byte, maxFeeRate uint64, contractVer uint32) (tx *types.Transaction, fees uint64, err error) { gas := w.gases(contractVer) if gas == nil { return nil, 0, fmt.Errorf("no gas table for asset %d, version %d", w.assetID, contractVer) @@ -4380,7 +4613,7 @@ func (w *assetWallet) refund(secretHash [32]byte, maxFeeRate uint64, contractVer } return tx, gas.Refund * maxFeeRate, w.withContractor(contractVer, func(c contractor) error { - tx, err = c.refund(txOpts, secretHash) + tx, err = c.refund(txOpts, locator) if err != nil { c.voidUnusedNonce() return err @@ -4389,24 +4622,28 @@ func (w *assetWallet) refund(secretHash [32]byte, maxFeeRate uint64, contractVer }) } -// 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 }) } @@ -4414,7 +4651,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 { @@ -4615,7 +4851,13 @@ func getGetGasClientWithEstimatesAndBalances(ctx context.Context, net dex.Networ walletDir, provider string, seed []byte, log dex.Logger) (cl *multiRPCClient, c contractor, g *dexeth.Gases, ethReq, swapReq, feeRate uint64, ethBal, tokenBal *big.Int, err error) { - g = gases(uint32(dexeth.ChainIDs[net]), assetID, contractVer, net) + ti := asset.TokenInfo(assetID) + parentID := assetID + if ti != nil { + parentID = ti.ParentID + } + + g = gases(parentID, assetID, contractVer, net) if g == nil { return nil, nil, nil, 0, 0, 0, nil, nil, fmt.Errorf("no gas table found for %s, contract version %d", dex.BipIDSymbol(assetID), contractVer) } @@ -4735,7 +4977,7 @@ func (getGas) EstimateFunding(ctx context.Context, net dex.Network, assetID, con if ethBal < ethReq { // Add 10% for fee drift. ethRecommended := ethReq * 11 / 10 - log.Infof("❌ Insufficient Ethereum Balance. Deposit about %s ETH before getting a gas estimate", ethFmt(ethRecommended-ethBal)) + log.Infof("❌ Insufficient Ethereum Balance. Deposit about %s ETH to %s before getting a gas estimate", ethFmt(ethRecommended-ethBal), cl.address()) } else if tokenBalOK { log.Infof("👍 You have sufficient funding to run a gas estimate") } @@ -4905,7 +5147,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, gases, log) + return getGasEstimates(ctx, cl, approvalClient, c, approvalContractor, maxSwaps, contractVer, gases, log) } // getGasEstimate is used to get a gas table for an asset's contract(s). The @@ -4920,7 +5162,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) @@ -5059,6 +5301,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 @@ -5068,7 +5311,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) } @@ -5102,9 +5345,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) } @@ -5115,6 +5356,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 88580daf98..011bd61e2a 100644 --- a/client/asset/eth/eth_test.go +++ b/client/asset/eth/eth_test.go @@ -44,17 +44,30 @@ var ( testAddressB = common.HexToAddress("8d83B207674bfd53B418a6E47DA148F5bFeCc652") testAddressC = common.HexToAddress("2b84C791b79Ee37De042AD2ffF1A253c3ce9bc27") - ethGases = dexeth.VersionedGases[0] - tokenGases = dexeth.Tokens[simnetTokenID].NetTokens[dex.Simnet].SwapContracts[0].Gas + ethGasesV0 = dexeth.VersionedGases[0] + tokenGasesV0 = dexeth.Tokens[simnetTokenID].NetTokens[dex.Simnet].SwapContracts[0].Gas + ethGasesV1 = dexeth.VersionedGases[1] + tokenGasesV1 = dexeth.Tokens[simnetTokenID].NetTokens[dex.Simnet].SwapContracts[1].Gas - tETH = &dex.Asset{ - // Version meaning? + tETHV0 = &dex.Asset{ + Version: 0, + ID: 60, + Symbol: "ETH", + MaxFeeRate: 100, + SwapSize: ethGasesV0.Swap, + SwapSizeBase: ethGasesV0.Swap, + RedeemSize: ethGasesV0.Redeem, + SwapConf: 1, + } + + tETHV1 = &dex.Asset{ + Version: 1, ID: 60, Symbol: "ETH", MaxFeeRate: 100, - SwapSize: ethGases.Swap, - SwapSizeBase: ethGases.Swap, - RedeemSize: ethGases.Redeem, + SwapSize: ethGasesV1.Swap, + SwapSizeBase: ethGasesV1.Swap, + RedeemSize: ethGasesV1.Redeem, SwapConf: 1, } @@ -68,13 +81,24 @@ var ( SwapConf: 1, } - tToken = &dex.Asset{ + tTokenV0 = &dex.Asset{ ID: simnetTokenID, Symbol: "dextt.eth", Version: 0, - SwapSize: tokenGases.Swap, - SwapSizeBase: tokenGases.Swap, - RedeemSize: tokenGases.Redeem, + SwapSize: tokenGasesV0.Swap, + SwapSizeBase: tokenGasesV0.Swap, + RedeemSize: tokenGasesV0.Redeem, + MaxFeeRate: 20, + SwapConf: 1, + } + + tTokenV1 = &dex.Asset{ + ID: simnetTokenID, + Symbol: "dextt.eth", + Version: 1, + SwapSize: tokenGasesV1.Swap, + SwapSizeBase: tokenGasesV1.Swap, + RedeemSize: tokenGasesV1.Redeem, MaxFeeRate: 20, SwapConf: 1, } @@ -283,6 +307,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 @@ -298,6 +325,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 @@ -319,8 +416,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 } @@ -329,22 +430,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 } @@ -553,7 +682,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), @@ -566,7 +695,7 @@ func newTestNode(assetID uint32) *tMempoolNode { allow: new(big.Int), } if assetID != BipID { - ttc.tContractor.gasEstimates = &tokenGases + ttc.tContractor.gasEstimates = &tokenGasesV0 c = ttc } @@ -609,8 +738,8 @@ func tassetWallet(assetID uint32) (asset.Wallet, *assetWallet, *tMempoolNode, co }, 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, maxSwapsInTx: 40, @@ -633,7 +762,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), @@ -796,13 +925,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) } } } @@ -1001,36 +1130,45 @@ func testRefund(t *testing.T, assetID uint32) { const gweiBal = 1e9 const ogRefundReserves = 1e8 - v1Contractor := &tContractor{ - swapMap: make(map[[32]byte]*dexeth.SwapState, 1), - gasEstimates: ethGases, - redeemTx: types.NewTx(&types.DynamicFeeTx{}), - } - var v1c contractor = v1Contractor - - gasesV1 := &dexeth.Gases{Refund: 1e5} - if assetID == BipID { - dexeth.VersionedGases[1] = gasesV1 - defer delete(dexeth.VersionedGases, 1) - } else { - tokenContracts := dexeth.Tokens[simnetTokenID].NetTokens[dex.Simnet].SwapContracts - tc := *tokenContracts[0] - tc.Gas = *gasesV1 - tokenContracts[1] = &tc - defer delete(tokenContracts, 1) - v1c = &tTokenContractor{tContractor: v1Contractor} - } - - eth.contractors[1] = v1c + // v1Contractor := &tContractor{ + // swapMap: make(map[[32]byte]*dexeth.SwapState, 1), + // gasEstimates: ethGasesV0, + // redeemTx: types.NewTx(&types.DynamicFeeTx{}), + // } + // var v1c contractor = v1Contractor + + // gasesV1 := &dexeth.Gases{Refund: 1e5} + // if assetID == BipID { + // dexeth.VersionedGases[1] = gasesV1 + // defer delete(dexeth.VersionedGases, 1) + // } else { + // tokenContracts := dexeth.Tokens[simnetTokenID].NetTokens[dex.Simnet].SwapContracts + // tc := *tokenContracts[0] + // tc.Gas = *gasesV1 + // tokenContracts[1] = &tc + // defer delete(tokenContracts, 1) + // v1c = &tTokenContractor{tContractor: v1Contractor} + // } + + // eth.contractors[1] = v1c var secretHash [32]byte copy(secretHash[:], encode.RandomBytes(32)) - v0Contract := dexeth.EncodeContractData(0, secretHash) - ss := &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 @@ -1043,7 +1181,7 @@ func testRefund(t *testing.T, assetID uint32) { wantZeroHash bool swapStep dexeth.SwapStep swapErr error - useV1Gases bool + v1 bool }{ { name: "ok", @@ -1056,7 +1194,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", @@ -1097,11 +1235,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{} } @@ -1180,11 +1317,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) @@ -1360,6 +1497,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 @@ -1475,10 +1613,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[simnetTokenID].NetTokens[dex.Simnet]. SwapContracts[fromAsset.Version].Gas.Swap @@ -1721,13 +1859,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 @@ -1776,9 +1913,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", @@ -1789,9 +1926,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", @@ -1816,9 +1953,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", @@ -1829,9 +1966,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", @@ -1853,11 +1990,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 = simnetTokenID - assetCfg = tToken + assetCfg = tTokenV0 } w, _, node, shutdown := tassetWallet(assetID) @@ -1865,7 +2003,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 { @@ -1879,7 +2017,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, }) @@ -1891,7 +2029,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 @@ -1929,6 +2067,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{}) @@ -1938,7 +2083,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()) } } @@ -1964,12 +2110,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) { @@ -2008,16 +2149,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() @@ -2087,10 +2229,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, @@ -2178,14 +2316,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, } @@ -2203,7 +2360,8 @@ func TestPreRedeem(t *testing.T) { w, _, _, shutdown2 := tassetWallet(simnetTokenID) defer shutdown2() - form.Version = tToken.Version + form.Version = tTokenV0.Version + node.tokenContractor.allow = unlimitedAllowanceReplenishThreshold preRedeem, err = w.PreRedeem(form) if err != nil { @@ -2224,37 +2382,26 @@ func testRedeem(t *testing.T, assetID uint32) { w, eth, node, shutdown := tassetWallet(assetID) defer shutdown() - // Test with a non-zero contract version to ensure it makes it into the receipt - contractVer := uint32(1) - dexeth.VersionedGases[1] = ethGases // for dexeth.RedeemGas(..., 1) - tokenContracts := dexeth.Tokens[simnetTokenID].NetTokens[dex.Simnet].SwapContracts - tokenContracts[1] = tokenContracts[0] - defer delete(dexeth.VersionedGases, 1) - defer delete(tokenContracts, 1) - - contractorV1 := &tContractor{ - swapMap: make(map[[32]byte]*dexeth.SwapState, 1), - gasEstimates: ethGases, - redeemTx: types.NewTx(&types.DynamicFeeTx{Data: []byte{1, 2, 3}}), - } - var c contractor = contractorV1 - if assetID != BipID { - c = &tTokenContractor{ - tContractor: contractorV1, - } + var contractor *tContractor + if assetID == BipID { + contractor = eth.contractors[0].(*tContractor) + } else { + contractor = eth.contractors[0].(*tTokenContractor).tContractor } - eth.contractors[1] = c + contractor.redeemTx = types.NewTx(&types.DynamicFeeTx{Data: []byte{1, 2, 3}}) + now := time.Now() + const value = 1e9 - addSwapToSwapMap := func(secretHash [32]byte, value uint64, step dexeth.SwapStep) { + addSwapToSwapMap := func(secretHash [32]byte, step dexeth.SwapStep) { swap := dexeth.SwapState{ BlockHeight: 1, - LockTime: time.Now(), + LockTime: now, Initiator: testAddressB, Participant: testAddressA, Value: dexeth.GweiToWei(value), State: step, } - contractorV1.swapMap[secretHash] = &swap + contractor.swapMap[secretHash] = &swap } numSecrets := 3 @@ -2268,20 +2415,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. @@ -2316,6 +2465,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 @@ -2328,6 +2511,7 @@ func testRedeem(t *testing.T, assetID uint32) { redeemGasOverride *uint64 expectedGasFeeCap *big.Int expectError bool + v1 bool }{ { name: "ok", @@ -2337,32 +2521,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, @@ -2372,28 +2548,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, }, }, @@ -2406,28 +2561,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, }, }, @@ -2439,28 +2573,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, }, }, @@ -2472,28 +2585,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, }, }, @@ -2506,28 +2598,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, }, }, @@ -2540,28 +2611,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, }, }, @@ -2577,28 +2627,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, }, }, @@ -2609,28 +2638,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, }, }, @@ -2642,18 +2650,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, }, }, @@ -2664,18 +2661,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, }, }, @@ -2693,11 +2679,11 @@ 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 } eth.monitoredTxsMtx.Lock() @@ -2707,6 +2693,11 @@ func testRedeem(t *testing.T, assetID uint32) { 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 { @@ -2724,10 +2715,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(BipID, assetID, contractVer, dex.Simnet) + 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) @@ -2736,42 +2725,49 @@ func testRedeem(t *testing.T, assetID uint32) { // Check that value of output coin is as axpected var totalSwapValue uint64 for _, redemption := range test.form.Redemptions { - _, secretHash, err := dexeth.DecodeContractData(redemption.Spends.Contract) + _, locator, err := dexeth.DecodeLocator(redemption.Spends.Contract) if err != nil { - t.Fatalf("DecodeContractData: %v", err) + t.Fatalf("DecodeLocator: %v", err) + } + var secretHash [32]byte + if test.v1 { + v, _ := dexeth.ParseV1Locator(locator) + secretHash = v.SecretHash + } else { + copy(secretHash[:], locator) } // secretHash should equal redemption.Spends.SecretHash, but it's // not part of the Redeem code, just the test input consistency. - swap := contractorV1.swapMap[secretHash] + swap := contractor.swapMap[secretHash] totalSwapValue += dexeth.WeiToGwei(swap.Value) } if out.Value() != totalSwapValue { - t.Fatalf("expected coin value to be %d but got %d", - totalSwapValue, out.Value()) + t.Fatalf("%s: expected coin value to be %d but got %d", + test.name, totalSwapValue, out.Value()) } // Check that gas limit in the transaction is as expected var expectedGasLimit uint64 - // if test.redeemGasOverride == nil { - if assetID == BipID { - expectedGasLimit = ethGases.Redeem * uint64(len(test.form.Redemptions)) + 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) } // Check that tx was stored in the monitored transactions - txHash := contractorV1.redeemTx.Hash() + txHash := contractor.redeemTx.Hash() eth.monitoredTxsMtx.RLock() monitoredTx, stored := eth.monitoredTxs[txHash] if !stored { @@ -2786,6 +2782,7 @@ func testRedeem(t *testing.T, assetID uint32) { func TestMaxOrder(t *testing.T) { const baseFee, tip = 42, 2 + const currentFee = baseFee + tip type testData struct { name string @@ -2793,7 +2790,6 @@ func TestMaxOrder(t *testing.T) { balErr error lotSize uint64 maxFeeRate uint64 - feeSuggestion uint64 token bool parentBal uint64 wantErr bool @@ -2803,113 +2799,123 @@ 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(""), - wantErr: true, + name: "balanceError", + bal: 51, + lotSize: 10, + maxFeeRate: 100, + balErr: errors.New(""), + wantErr: true, }, } runTest := func(t *testing.T, test testData) { var assetID uint32 = BipID - assetCfg := tETH + gases := ethGasesV0 if test.token { assetID = simnetTokenID - assetCfg = tToken + gases = &tokenGasesV0 + if test.v1 { + gases = &tokenGasesV1 + } + } else if test.v1 { + gases = ethGasesV1 } w, _, node, shutdown := tassetWallet(assetID) @@ -2917,7 +2923,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 { @@ -2925,11 +2932,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, @@ -3035,7 +3047,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, @@ -3055,7 +3067,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, @@ -3074,7 +3086,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, @@ -3093,13 +3105,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, @@ -3166,7 +3178,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) } @@ -3308,7 +3320,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 @@ -3324,7 +3338,7 @@ func TestSwapConfirmation(t *testing.T) { defer cancel() checkResult := func(expErr bool, expConfs uint32, expSpent bool) { - confs, spent, err := eth.SwapConfirmations(ctx, nil, dexeth.EncodeContractData(ver, secretHash), time.Time{}) + confs, spent, err := eth.SwapConfirmations(ctx, nil, dexeth.EncodeContractData(ver, secretHash[:]), time.Time{}) if err != nil { if expErr { return @@ -3358,7 +3372,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) } @@ -3528,6 +3542,7 @@ func TestLocktimeExpired(t *testing.T) { state := &dexeth.SwapState{ LockTime: time.Now(), State: dexeth.SSInitiated, + Value: dexeth.GweiToWei(1), } header := &types.Header{ @@ -3601,10 +3616,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 @@ -3648,7 +3664,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 @@ -3712,7 +3728,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() { @@ -3751,17 +3767,17 @@ func testRefundReserves(t *testing.T, assetID uint32) { feeWallet := eth gasesV0 := dexeth.VersionedGases[0] gasesV1 := &dexeth.Gases{Refund: 1e6} - assetV0 := *tETH - assetV1 := *tETH + assetV0 := *tETHV0 + assetV1 := *tETHV0 if assetID == BipID { dexeth.VersionedGases[1] = gasesV1 defer delete(dexeth.VersionedGases, 1) } else { feeWallet = node.tokenParent - assetV0 = *tToken - assetV1 = *tToken + assetV0 = *tTokenV0 + assetV1 = *tTokenV0 tokenContracts := dexeth.Tokens[simnetTokenID].NetTokens[dex.Simnet].SwapContracts - gasesV0 = &tokenGases + gasesV0 = &tokenGasesV0 tc := *tokenContracts[0] tc.Gas = *gasesV1 tokenContracts[1] = &tc @@ -3848,8 +3864,8 @@ func testRedemptionReserves(t *testing.T, assetID uint32) { gasesV1 := &dexeth.Gases{Redeem: 1e6, RedeemAdd: 85e5} gasesV0 := dexeth.VersionedGases[0] - assetV0 := *tETH - assetV1 := *tETH + assetV0 := *tETHV0 + assetV1 := *tETHV0 feeWallet := eth if assetID == BipID { dexeth.VersionedGases[1] = gasesV1 @@ -3857,10 +3873,10 @@ func testRedemptionReserves(t *testing.T, assetID uint32) { } else { node.tokenContractor.allow = unlimitedAllowanceReplenishThreshold feeWallet = node.tokenParent - assetV0 = *tToken - assetV1 = *tToken + assetV0 = *tTokenV0 + assetV1 = *tTokenV0 tokenContracts := dexeth.Tokens[simnetTokenID].NetTokens[dex.Simnet].SwapContracts - gasesV0 = &tokenGases + gasesV0 = &tokenGasesV0 tc := *tokenContracts[0] tc.Gas = *gasesV1 tokenContracts[1] = &tc @@ -3971,7 +3987,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" @@ -4111,7 +4127,11 @@ func testConfirmRedemption(t *testing.T, assetID uint32) { assetRedemption := func(secretHash, secret common.Hash) *asset.Redemption { return &asset.Redemption{ Spends: &asset.AuditInfo{ - Contract: dexeth.EncodeContractData(0, secretHash), + Contract: dexeth.EncodeContractData(0, secretHash[:]), + Coin: &coin{ + id: randomHash(), + value: 1e9, + }, }, Secret: secret[:], } @@ -4130,9 +4150,10 @@ func testConfirmRedemption(t *testing.T, assetID uint32) { expectSentSignedTransaction *types.Transaction expectedMonitoredTxs map[common.Hash]*monitoredTx - getTxResMap map[common.Hash]*txData - swapMap map[[32]byte]*dexeth.SwapState - monitoredTxs map[common.Hash]*monitoredTx + getTxResMap map[common.Hash]*txData + swapMap map[[32]byte]*dexeth.SwapState + monitoredTxs map[common.Hash]*monitoredTx + redeemableMap map[string]bool redeemTx *types.Transaction redeemErr error @@ -4255,9 +4276,9 @@ func testConfirmRedemption(t *testing.T, assetID uint32) { blockSubmitted: 19, }, }, - // redeemableMap: map[common.Hash]bool{ - // secretHashes[0]: true, - // }, + redeemableMap: map[string]bool{ + string(secretHashes[0][:]): true, + }, bestBlock: 19, expectedResult: &asset.ConfirmRedemptionStatus{ Confs: 0, @@ -4317,6 +4338,10 @@ func testConfirmRedemption(t *testing.T, assetID uint32) { State: dexeth.SSInitiated, }, }, + redeemableMap: map[string]bool{ + string(secretHashes[0][:]): true, + string(secretHashes[1][:]): true, + }, monitoredTxs: map[common.Hash]*monitoredTx{}, expectedMonitoredTxs: map[common.Hash]*monitoredTx{ (*toEthTxHash(4, 123, redeem0Data)): { @@ -4390,6 +4415,10 @@ func testConfirmRedemption(t *testing.T, assetID uint32) { blockSubmitted: 13, }, }, + redeemableMap: map[string]bool{ + string(secretHashes[0][:]): true, + string(secretHashes[1][:]): false, + }, bestBlock: 13, expectedResult: &asset.ConfirmRedemptionStatus{ Confs: 0, @@ -4433,6 +4462,9 @@ func testConfirmRedemption(t *testing.T, assetID uint32) { blockSubmitted: 13, }, }, + redeemableMap: map[string]bool{ + string(secretHashes[0][:]): true, + }, bestBlock: 13, expectedResult: &asset.ConfirmRedemptionStatus{ Confs: 0, @@ -4551,6 +4583,9 @@ func testConfirmRedemption(t *testing.T, assetID uint32) { blockSubmitted: 13, }, }, + redeemableMap: map[string]bool{ + string(secretHashes[0][:]): true, + }, bestBlock: 13, expectedResult: &asset.ConfirmRedemptionStatus{ Confs: 0, @@ -4640,6 +4675,9 @@ func testConfirmRedemption(t *testing.T, assetID uint32) { blockSubmitted: 3, }, }, + redeemableMap: map[string]bool{ + string(secretHashes[0][:]): true, + }, bestBlock: 13, expectedResult: &asset.ConfirmRedemptionStatus{ Confs: 0, @@ -4932,7 +4970,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 @@ -5079,7 +5117,7 @@ func TestSwapOrRedemptionFeesPaid(t *testing.T) { contractDataFn := func(ver uint32, secretH []byte) []byte { s := [32]byte{} copy(s[:], secretH) - return dexeth.EncodeContractData(ver, s) + return dexeth.EncodeContractData(ver, s[:]) } rcpt := &types.Receipt{ GasUsed: 100, diff --git a/client/asset/eth/nodeclient.go b/client/asset/eth/nodeclient.go index 745bc55643..533bf350a5 100644 --- a/client/asset/eth/nodeclient.go +++ b/client/asset/eth/nodeclient.go @@ -17,7 +17,6 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/eth/ethconfig" "github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/les" "github.com/ethereum/go-ethereum/node" @@ -29,14 +28,12 @@ import ( "github.com/ethereum/go-ethereum/consensus/misc" ) -const contractVersionNewest = ^uint32(0) +const ( + contractVersionNewest = ^uint32(0) + approveGas = 4e5 +) var ( - // https://github.com/ethereum/go-ethereum/blob/16341e05636fd088aa04a27fca6dc5cda5dbab8f/eth/backend.go#L110-L113 - // ultimately results in a minimum fee rate by the filter applied at - // https://github.com/ethereum/go-ethereum/blob/4ebeca19d739a243dc0549bcaf014946cde95c4f/core/tx_pool.go#L626 - minGasPrice = ethconfig.Defaults.Miner.GasPrice - // Check that nodeClient satisfies the ethFetcher interface. _ ethFetcher = (*nodeClient)(nil) ) @@ -432,7 +429,7 @@ func gases(parentID, assetID uint32, contractVer uint32, net dex.Network) *dexet return nil } - if contractVer != contractVersionNewest { + if contractVer != contractVersionERC20 { contract, found := netToken.SwapContracts[contractVer] if !found { return nil diff --git a/client/asset/eth/nodeclient_harness_test.go b/client/asset/eth/nodeclient_harness_test.go index b0f4f5bc8f..7dfb0e5bc1 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" @@ -70,6 +71,7 @@ const ( var ( homeDir = os.Getenv("HOME") + harnessCtlDir = filepath.Join(homeDir, "dextest", "eth", "harness-ctl") simnetWalletDir = filepath.Join(homeDir, "dextest", "eth", "client_rpc_tests", "simnet") participantWalletDir = filepath.Join(homeDir, "dextest", "eth", "client_rpc_tests", "participant") testnetWalletDir = filepath.Join(homeDir, "ethtest", "testnet_contract_tests", "walletA") @@ -93,7 +95,7 @@ var ( participantContractor contractor simnetTokenContractor tokenContractor participantTokenContractor tokenContractor - ethGases = dexeth.VersionedGases[0] + ethGases *dexeth.Gases tokenGases *dexeth.Gases testnetSecPerBlock = 15 * time.Second // secPerBlock is one for simnet, because it takes one second to mine a @@ -134,6 +136,9 @@ var ( usdcID, _ = dex.BipSymbolID("usdc.eth") testTokenID uint32 masterToken *dexeth.Token + + v1 bool + ver uint32 ) func newContract(stamp uint64, secretHash [32]byte, val uint64) *asset.Contract { @@ -145,10 +150,34 @@ func newContract(stamp uint64, secretHash [32]byte, val uint64) *asset.Contract } } -func newRedeem(secret, secretHash [32]byte) *asset.Redemption { +func acLocator(c *asset.Contract) []byte { + return makeLocator(bytesToArray(c.SecretHash), c.Value, c.LockTime) +} + +func makeLocator(secretHash [32]byte, valg, lockTime uint64) []byte { + if ver == 1 { + return (&dexeth.SwapVector{ + From: ethClient.address(), + To: participantEthClient.address(), + Value: valg, + SecretHash: secretHash, + LockTime: lockTime, + }).Locator() + } + return secretHash[:] +} + +func newRedeem(secret, secretHash [32]byte, valg, lockTime uint64) *asset.Redemption { return &asset.Redemption{ Spends: &asset.AuditInfo{ SecretHash: secretHash[:], + Recipient: participantEthClient.address().String(), + Expiration: time.Unix(int64(lockTime), 0), + Coin: &coin{ + // id: txHash, + value: valg, + }, + Contract: dexeth.EncodeContractData(ver, makeLocator(secretHash, valg, lockTime)), }, Secret: secret[:], } @@ -367,16 +396,15 @@ func runSimnet(m *testing.M) (int, error) { return 1, fmt.Errorf("error creating participant wallet dir: %v", err) } - tokenGases = &dexeth.Tokens[testTokenID].NetTokens[dex.Simnet].SwapContracts[0].Gas + tokenGases = &dexeth.Tokens[testTokenID].NetTokens[dex.Simnet].SwapContracts[ver].Gas // ETH swap contract. - masterToken = dexeth.Tokens[testTokenID] - token := masterToken.NetTokens[dex.Simnet] - fmt.Printf("ETH swap contract address is %v\n", dexeth.ContractAddresses[0][dex.Simnet]) - fmt.Printf("Token swap contract addr is %v\n", token.SwapContracts[0].Address) + token := dexeth.Tokens[testTokenID].NetTokens[dex.Simnet] + fmt.Printf("ETH swap contract address is %v\n", dexeth.ContractAddresses[ver][dex.Simnet]) + fmt.Printf("Token swap contract addr is %v\n", token.SwapContracts[ver].Address) fmt.Printf("Test token contract addr is %v\n", token.Address) - ethSwapContractAddr = dexeth.ContractAddresses[0][dex.Simnet] + ethSwapContractAddr = dexeth.ContractAddresses[ver][dex.Simnet] initiatorRPC, participantRPC := rpcEndpoints(dex.Simnet) @@ -411,24 +439,10 @@ func runSimnet(m *testing.M) (int, error) { simnetAddr = simnetAcct.Address participantAddr = participantAcct.Address - if simnetContractor, err = newV0Contractor(dex.Simnet, simnetAddr, ethClient.contractBackend()); err != nil { - return 1, fmt.Errorf("newV0Contractor error: %w", err) - } - if participantContractor, err = newV0Contractor(dex.Simnet, participantAddr, participantEthClient.contractBackend()); err != nil { - return 1, fmt.Errorf("participant newV0Contractor error: %w", err) - } - - if simnetTokenContractor, err = newV0TokenContractor(dex.Simnet, testTokenID, simnetAddr, ethClient.contractBackend()); err != nil { - return 1, fmt.Errorf("newV0TokenContractor error: %w", err) - } - - // I don't know why this is needed for the participant client but not - // the initiator. Without this, we'll get a bind.ErrNoCode from - // (*BoundContract).Call while calling (*ERC20Swap).TokenAddress. - time.Sleep(time.Second) - - if participantTokenContractor, err = newV0TokenContractor(dex.Simnet, testTokenID, participantAddr, participantEthClient.contractBackend()); err != nil { - return 1, fmt.Errorf("participant newV0TokenContractor error: %w", err) + if v1 { + prepareV1SimnetContractors() + } else { + prepareV0SimnetContractors() } if err := ethClient.unlock(pw); err != nil { @@ -439,11 +453,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 @@ -492,7 +501,7 @@ func runSimnet(m *testing.M) (int, error) { func runTestnet(m *testing.M) (int, error) { testTokenID = usdcID masterToken = dexeth.Tokens[testTokenID] - tokenGases = &masterToken.NetTokens[dex.Testnet].SwapContracts[0].Gas + tokenGases = &masterToken.NetTokens[dex.Testnet].SwapContracts[ver].Gas if testnetWalletSeed == "" || testnetParticipantWalletSeed == "" { return 1, errors.New("testnet seeds not set") } @@ -507,7 +516,7 @@ func runTestnet(m *testing.M) (int, error) { return 1, fmt.Errorf("error creating testnet participant wallet dir: %v", err) } secPerBlock = testnetSecPerBlock - ethSwapContractAddr = dexeth.ContractAddresses[0][dex.Testnet] + ethSwapContractAddr = dexeth.ContractAddresses[ver][dex.Testnet] fmt.Printf("ETH swap contract address is %v\n", ethSwapContractAddr) initiatorRPC, participantRPC := rpcEndpoints(dex.Testnet) @@ -557,10 +566,15 @@ func runTestnet(m *testing.M) (int, error) { simnetAddr = simnetAcct.Address participantAddr = participantAcct.Address - if simnetContractor, err = newV0Contractor(dex.Testnet, simnetAddr, ethClient.contractBackend()); err != nil { + ctor := newV0Contractor + if ver == 1 { + ctor = newV1Contractor + } + + if simnetContractor, err = ctor(dex.Testnet, simnetAddr, ethClient.contractBackend()); err != nil { return 1, fmt.Errorf("newV0Contractor error: %w", err) } - if participantContractor, err = newV0Contractor(dex.Testnet, participantAddr, participantEthClient.contractBackend()); err != nil { + if participantContractor, err = ctor(dex.Testnet, participantAddr, participantEthClient.contractBackend()); err != nil { return 1, fmt.Errorf("participant newV0Contractor error: %w", err) } @@ -600,6 +614,37 @@ func runTestnet(m *testing.M) (int, error) { return code, nil } +func prepareV0SimnetContractors() (err error) { + return prepareSimnetContractors(newV0Contractor, newV0TokenContractor) +} + +func prepareV1SimnetContractors() (err error) { + return prepareSimnetContractors(newV1Contractor, newV1TokenContractor) +} + +func prepareSimnetContractors(c contractorConstructor, tc tokenContractorConstructor) (err error) { + if simnetContractor, err = c(dex.Simnet, simnetAddr, ethClient.contractBackend()); err != nil { + return fmt.Errorf("new contractor error: %w", err) + } + if participantContractor, err = c(dex.Simnet, participantAddr, participantEthClient.contractBackend()); err != nil { + return fmt.Errorf("participant new contractor error: %w", err) + } + + if simnetTokenContractor, err = tc(dex.Simnet, testTokenID, simnetAddr, ethClient.contractBackend()); err != nil { + return fmt.Errorf("new token contractor error: %w", err) + } + + // I don't know why this is needed for the participant client but not + // the initiator. Without this, we'll get a bind.ErrNoCode from + // (*BoundContract).Call while calling (*ERC20Swap).TokenAddress. + time.Sleep(time.Second) + + if participantTokenContractor, err = tc(dex.Simnet, testTokenID, participantAddr, participantEthClient.contractBackend()); err != nil { + return fmt.Errorf("participant new token contractor error: %w", err) + } + return +} + func useTestnet() error { isTestnet = true b, err := os.ReadFile(testnetCredentialsPath) @@ -627,12 +672,20 @@ func useTestnet() error { } func TestMain(m *testing.M) { + rand.Seed(time.Now().UnixNano()) dexeth.MaybeReadSimnetAddrs() flag.BoolVar(&isTestnet, "testnet", false, "use testnet") flag.BoolVar(&useRPC, "rpc", false, "use RPC") + flag.BoolVar(&v1, "v1", true, "Use Version 1 contract") flag.Parse() + if v1 { + ver = 1 + } + + ethGases = dexeth.VersionedGases[ver] + if isTestnet { if err := useTestnet(); err != nil { fmt.Fprintf(os.Stderr, "error loading testnet: %v", err) @@ -796,7 +849,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) }) @@ -809,7 +862,7 @@ func TestGas(t *testing.T) { } func TestTokenContract(t *testing.T) { - t.Run("testTokenSwap", func(t *testing.T) { testSwap(t, testTokenID) }) + // t.Run("testTokenSwap", func(t *testing.T) { testSwap(t, testTokenID) }) // TODO: Replace with testTokenStatusAndVector? t.Run("testInitiateToken", func(t *testing.T) { testInitiate(t, testTokenID) }) t.Run("testRedeemToken", func(t *testing.T) { testRedeem(t, testTokenID) }) t.Run("testRefundToken", func(t *testing.T) { testRefund(t, testTokenID) }) @@ -1098,17 +1151,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 { @@ -1132,7 +1174,7 @@ func testInitiateGas(t *testing.T, assetID uint32) { if isTestnet { net = dex.Testnet } - gases := gases(BipID, assetID, 0, net) + gases := gases(BipID, assetID, ver, net) var previousGas uint64 maxSwaps := 50 @@ -1151,7 +1193,7 @@ func testInitiateGas(t *testing.T, assetID uint32) { expectedGas = gases.SwapAdd actualGas = gas - previousGas } - if actualGas > expectedGas || actualGas < expectedGas*75/100 { + if actualGas > expectedGas || actualGas < expectedGas/2 { t.Fatalf("Expected incremental gas for %d initiations to be close to %d but got %d", i, expectedGas, actualGas) } @@ -1249,15 +1291,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()) @@ -1276,10 +1310,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), }, }, { @@ -1335,28 +1369,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)) @@ -1365,7 +1400,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) @@ -1439,19 +1474,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) } } } @@ -1478,8 +1512,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 @@ -1517,19 +1554,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) } @@ -1543,9 +1580,9 @@ func testRedeemGas(t *testing.T, assetID uint32) { expectedGas = gases.RedeemAdd actualGas = gas - previous } - if actualGas > expectedGas || actualGas < (expectedGas/100*95) { + if actualGas > expectedGas || actualGas < (expectedGas/2) { // Use GetGasEstimates to better precision estimates. t.Fatalf("Expected incremental gas for %d redemptions to be close to %d but got %d", - i, expectedGas, actualGas) + i+1, expectedGas, actualGas) } fmt.Printf("\n\nGas used to redeem %d swaps: %d -- %d more than previous \n\n", i+1, gas, gas-previous) @@ -1580,6 +1617,8 @@ func testRedeem(t *testing.T, assetID uint32) { evmify = tc.evmify } + const val = 1 + tests := []struct { name string sleepNBlocks int @@ -1598,8 +1637,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, }, @@ -1610,12 +1649,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, @@ -1628,8 +1667,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, }, @@ -1639,19 +1678,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, }, @@ -1663,12 +1703,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, @@ -1680,14 +1720,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 @@ -1703,11 +1745,11 @@ func testRedeem(t *testing.T, assetID uint32) { } } - txOpts, err := test.redeemerClient.txOpts(ctx, optsVal, gases.SwapN(len(test.swaps)), dexeth.GweiToWei(maxFeeRate), nil) + txOpts, err := ethClient.txOpts(ctx, optsVal, gases.SwapN(len(test.swaps)), dexeth.GweiToWei(maxFeeRate), nil) if err != nil { t.Fatalf("%s: txOpts error: %v", test.name, err) } - tx, err := test.redeemerContractor.initiate(txOpts, test.swaps) + tx, err := c.initiate(txOpts, test.swaps) if err != nil { t.Fatalf("%s: initiate error: %v ", test.name, err) } @@ -1730,12 +1772,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) } } @@ -1836,15 +1878,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) } } } @@ -1876,7 +1917,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) } @@ -1884,22 +1927,21 @@ func testRefundGas(t *testing.T, assetID uint32) { t.Fatalf("unexpected error while waiting to mine: %v", err) } - swap, err := c.swap(ctx, secretHash) + status, _, err := c.statusAndVector(ctx, locator) if err != nil { t.Fatal("unable to get swap state") } - state := dexeth.SwapStep(swap.State) - if state != dexeth.SSInitiated { - t.Fatalf("unexpected swap state: want %s got %s", dexeth.SSInitiated, state) + if status.Step != dexeth.SSInitiated { + t.Fatalf("unexpected swap state: want %s got %s", dexeth.SSInitiated, status.Step) } - gas, err := c.estimateRefundGas(ctx, secretHash) + gas, err := c.estimateRefundGas(ctx, locator) if err != nil { t.Fatalf("Error estimating gas for refund function: %v", err) } if isETH { expGas := gases.Refund - if gas > expGas || gas < expGas*95/100 { + if gas > expGas || gas < expGas/2 { t.Fatalf("expected refund gas to be near %d, but got %d", expGas, gas) } @@ -1991,21 +2033,23 @@ func testRefund(t *testing.T, assetID uint32) { copy(secret[:], encode.RandomBytes(32)) secretHash := sha256.Sum256(secret[:]) - swap, err := test.refunderContractor.swap(ctx, secretHash) + inLocktime := uint64(time.Now().Add(test.addTime).Unix()) + ac := newContract(inLocktime, secretHash, amt) + locator := acLocator(ac) + + status, _, err := test.refunderContractor.statusAndVector(ctx, locator) if err != nil { t.Fatalf("%s: unable to get swap state pre-init", test.name) } - if swap.State != dexeth.SSNone { - t.Fatalf("unexpected swap state for test %v: want %s got %s", test.name, dexeth.SSNone, swap.State) + if status.Step != dexeth.SSNone { + t.Fatalf("unexpected swap state for test %v: want %s got %s", test.name, dexeth.SSNone, status.Step) } - inLocktime := uint64(time.Now().Add(test.addTime).Unix()) - txOpts, err := ethClient.txOpts(ctx, optsVal, gases.SwapN(1), nil, nil) if err != nil { t.Fatalf("%s: txOpts error: %v", test.name, err) } - _, err = c.initiate(txOpts, []*asset.Contract{newContract(inLocktime, secretHash, amt)}) + _, err = c.initiate(txOpts, []*asset.Contract{ac}) if err != nil { t.Fatalf("%s: initiate error: %v ", test.name, err) } @@ -2019,7 +2063,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) } @@ -2043,7 +2087,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) } @@ -2056,7 +2100,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) } @@ -2131,12 +2175,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) } } } @@ -2305,7 +2349,7 @@ func TestTokenGasEstimates(t *testing.T) { runSimnetMiner(ctx, 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 cb17807233..2bad4f19ec 100755 --- a/dex/networks/erc20/contracts/updatecontract.sh +++ b/dex/networks/erc20/contracts/updatecontract.sh @@ -77,3 +77,16 @@ if [ "$VERSION" -eq "0" ]; then # Reorder the imports since we rewrote go-ethereum/event to a dcrdex package. gofmt -s -w "$CONTRACT_FILE" fi + +if [ "$VERSION" -eq "1" ]; then + perl -0pi -e 's/go-ethereum\/event"/go-ethereum\/event"\n\tethv1 "decred.org\/dcrdex\/dex\/networks\/eth\/contracts\/v1"/' $CONTRACT_FILE + + perl -0pi -e 's/\/\/ ERC20SwapVector[^}]*}\n\n//' $CONTRACT_FILE + perl -0pi -e 's/ERC20SwapVector/ethv1.ETHSwapVector/g' $CONTRACT_FILE + + perl -0pi -e 's/\/\/ ERC20SwapRedemption[^}]*}\n\n//' $CONTRACT_FILE + perl -0pi -e 's/ERC20SwapRedemption/ethv1.ETHSwapRedemption/g' $CONTRACT_FILE + + perl -0pi -e 's/\/\/ ERC20SwapStatus[^}]*}\n\n//' $CONTRACT_FILE + perl -0pi -e 's/ERC20SwapStatus/ethv1.ETHSwapStatus/g' $CONTRACT_FILE +fi diff --git a/dex/networks/erc20/contracts/v1/BinRuntimeV1.go b/dex/networks/erc20/contracts/v1/BinRuntimeV1.go new file mode 100644 index 0000000000..790689b5be --- /dev/null +++ b/dex/networks/erc20/contracts/v1/BinRuntimeV1.go @@ -0,0 +1,6 @@ +// Code generated - DO NOT EDIT. +// This file is a generated binding and any manual changes will be lost. + +package v1 + +const ERC20SwapRuntimeBin = "6080604052600436106100865760003560e01c80637802689d116100595780637802689d146101265780638c8e8fee14610146578063d2544c0614610192578063eb84e7f2146101b2578063ed7cbed7146101ed57600080fd5b8063428b16e11461008b57806361a16e33146100ad57806364a97bff146100e357806377d7e031146100f6575b600080fd5b34801561009757600080fd5b506100ab6100a6366004610dea565b61020d565b005b3480156100b957600080fd5b506100cd6100c8366004610e5f565b610533565b6040516100da9190610e8d565b60405180910390f35b6100ab6100f1366004610ed0565b6105ef565b34801561010257600080fd5b50610116610111366004610f33565b610918565b60405190151581526020016100da565b34801561013257600080fd5b50610116610141366004610e5f565b610992565b34801561015257600080fd5b5061017a7f000000000000000000000000000000000000000000000000000000000000000081565b6040516001600160a01b0390911681526020016100da565b34801561019e57600080fd5b506100ab6101ad366004610e5f565b6109c5565b3480156101be57600080fd5b506101df6101cd366004610f55565b60006020819052908152604090205481565b6040519081526020016100da565b3480156101f957600080fd5b506101df610208366004610e5f565b610cc5565b3233146102355760405162461bcd60e51b815260040161022c90610f6e565b60405180910390fd5b6000805b82811015610407573684848381811061025457610254610f98565b60c0029190910191503390506102706080830160608401610fae565b6001600160a01b0316146102b35760405162461bcd60e51b815260206004820152600a6024820152691b9bdd08185d5d1a195960b21b604482015260640161022c565b600080806102c084610dbe565b9250925092506000811180156102d557504381105b6103115760405162461bcd60e51b815260206004820152600d60248201526c0756e66696c6c6564207377617609c1b604482015260640161022c565b61031c828535610918565b1561035c5760405162461bcd60e51b815260206004820152601060248201526f185b1c9958591e481c995919595b595960821b604482015260640161022c565b61036b60a08501358535610918565b6103a85760405162461bcd60e51b815260206004820152600e60248201526d1a5b9d985b1a59081cd958dc995d60921b604482015260640161022c565b600083815260208190526040902060a0850180359091556103cc9060808601610fde565b6103da90633b9aca0061101e565b6103ee9067ffffffffffffffff168761104e565b95505050505080806103ff90611066565b915050610239565b5060408051336024820152604480820184905282518083039091018152606490910182526020810180516001600160e01b031663a9059cbb60e01b17905290516000916060916001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016916104819161107f565b6000604051808303816000865af19150503d80600081146104be576040519150601f19603f3d011682016040523d82523d6000602084013e6104c3565b606091505b5090925090508180156104ee5750805115806104ee5750808060200190518101906104ee91906110ba565b61052c5760405162461bcd60e51b815260206004820152600f60248201526e1d1c985b9cd9995c8819985a5b1959608a1b604482015260640161022c565b5050505050565b60408051606081018252600080825260208201819052918101829052908061055a84610dbe565b92509250506105846040805160608101909152806000815260006020820181905260409091015290565b816000036105ab578060005b908160038111156105a3576105a3610e77565b9052506105e7565b600183016105bb57806003610590565b6105c6838635610918565b156105db5760028152602081018390526105e7565b60018152604081018290525b949350505050565b32331461060e5760405162461bcd60e51b815260040161022c90610f6e565b6000805b828110156107e8573684848381811061062d5761062d610f98565b905060a002019050600081608001602081019061064a9190610fde565b67ffffffffffffffff16116106895760405162461bcd60e51b81526020600482015260056024820152640c081d985b60da1b604482015260640161022c565b600061069b6060830160408401610fde565b67ffffffffffffffff16116106e65760405162461bcd60e51b815260206004820152601160248201527003020726566756e6454696d657374616d7607c1b604482015260640161022c565b60006106f182610cc5565b60008181526020819052604090205490915080156107425760405162461bcd60e51b815260206004820152600e60248201526d73776170206e6f7420656d70747960901b604482015260640161022c565b504361074f818435610918565b1561078d5760405162461bcd60e51b815260206004820152600e60248201526d3430b9b41031b7b63634b9b4b7b760911b604482015260640161022c565b60008281526020819052604090208190556107ae60a0840160808501610fde565b6107bc90633b9aca0061101e565b6107d09067ffffffffffffffff168661104e565b945050505080806107e090611066565b915050610612565b5060408051336024820152306044820152606480820184905282518083039091018152608490910182526020810180516001600160e01b03166323b872dd60e01b17905290516000916060916001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016916108689161107f565b6000604051808303816000865af19150503d80600081146108a5576040519150601f19603f3d011682016040523d82523d6000602084013e6108aa565b606091505b5090925090508180156108d55750805115806108d55750808060200190518101906108d591906110ba565b61052c5760405162461bcd60e51b81526020600482015260146024820152731d1c985b9cd9995c88199c9bdb4819985a5b195960621b604482015260640161022c565b60008160028460405160200161093091815260200190565b60408051601f198184030181529082905261094a9161107f565b602060405180830381855afa158015610967573d6000803e3d6000fd5b5050506040513d601f19601f8201168201806040525081019061098a91906110dc565b149392505050565b60008060006109a084610dbe565b9250925050806000141580156105e757506109bc828535610918565b15949350505050565b3233146109e45760405162461bcd60e51b815260040161022c90610f6e565b6109f46060820160408301610fde565b67ffffffffffffffff16421015610a445760405162461bcd60e51b81526020600482015260146024820152731b1bd8dadd1a5b59481b9bdd08195e1c1a5c995960621b604482015260640161022c565b6000806000610a5284610dbe565b925092509250600081118015610a685750438111155b610aa65760405162461bcd60e51b815260206004820152600f60248201526e73776170206e6f742061637469766560881b604482015260640161022c565b610ab1828535610918565b15610af65760405162461bcd60e51b81526020600482015260156024820152741cddd85c08185b1c9958591e481c995919595b5959605a1b604482015260640161022c565b60018201610b3e5760405162461bcd60e51b81526020600482015260156024820152741cddd85c08185b1c9958591e481c99599d5b991959605a1b604482015260640161022c565b6000838152602081905260408120600019905560606001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000167fa9059cbb2ab09eb219583f4a59a5d0623ade346d962bcd4e46b11da047c9049b33610baf60a08a0160808b01610fde565b6040516001600160a01b03909216602483015267ffffffffffffffff16604482015260640160408051601f198184030181529181526020820180516001600160e01b03166001600160e01b0319909416939093179092529051610c12919061107f565b6000604051808303816000865af19150503d8060008114610c4f576040519150601f19603f3d011682016040523d82523d6000602084013e610c54565b606091505b509092509050818015610c7f575080511580610c7f575080806020019051810190610c7f91906110ba565b610cbd5760405162461bcd60e51b815260206004820152600f60248201526e1d1c985b9cd9995c8819985a5b1959608a1b604482015260640161022c565b505050505050565b600060028235610cdb6040850160208601610fae565b60601b846060016020810190610cf19190610fae565b60601b610d0460a0870160808801610fde565b60c01b610d176060880160408901610fde565b6040805160208101969096526bffffffffffffffffffffffff19948516908601529190921660548401526001600160c01b0319918216606884015260c01b16607082015260780160408051601f1981840301815290829052610d789161107f565b602060405180830381855afa158015610d95573d6000803e3d6000fd5b5050506040513d601f19601f82011682018060405250810190610db891906110dc565b92915050565b600080600080610dcd85610cc5565b600081815260208190526040902054909690955085945092505050565b60008060208385031215610dfd57600080fd5b823567ffffffffffffffff80821115610e1557600080fd5b818501915085601f830112610e2957600080fd5b813581811115610e3857600080fd5b86602060c083028501011115610e4d57600080fd5b60209290920196919550909350505050565b600060a08284031215610e7157600080fd5b50919050565b634e487b7160e01b600052602160045260246000fd5b8151606082019060048110610eb257634e487b7160e01b600052602160045260246000fd5b80835250602083015160208301526040830151604083015292915050565b60008060208385031215610ee357600080fd5b823567ffffffffffffffff80821115610efb57600080fd5b818501915085601f830112610f0f57600080fd5b813581811115610f1e57600080fd5b86602060a083028501011115610e4d57600080fd5b60008060408385031215610f4657600080fd5b50508035926020909101359150565b600060208284031215610f6757600080fd5b5035919050565b60208082526010908201526f39b2b73232b910109e9037b934b3b4b760811b604082015260600190565b634e487b7160e01b600052603260045260246000fd5b600060208284031215610fc057600080fd5b81356001600160a01b0381168114610fd757600080fd5b9392505050565b600060208284031215610ff057600080fd5b813567ffffffffffffffff81168114610fd757600080fd5b634e487b7160e01b600052601160045260246000fd5b600067ffffffffffffffff8083168185168183048111821515161561104557611045611008565b02949350505050565b6000821982111561106157611061611008565b500190565b60006001820161107857611078611008565b5060010190565b6000825160005b818110156110a05760208186018101518583015201611086565b818111156110af576000828501525b509190910192915050565b6000602082840312156110cc57600080fd5b81518015158114610fd757600080fd5b6000602082840312156110ee57600080fd5b505191905056fea2646970667358221220319d89b87a0d5782925310133ed5d12d2ff562b0786611308396804fcad176fc64736f6c634300080f0033" diff --git a/dex/networks/erc20/contracts/v1/contract.go b/dex/networks/erc20/contracts/v1/contract.go new file mode 100644 index 0000000000..7afaf0f4f0 --- /dev/null +++ b/dex/networks/erc20/contracts/v1/contract.go @@ -0,0 +1,452 @@ +// Code generated - DO NOT EDIT. +// This file is a generated binding and any manual changes will be lost. + +package v1 + +import ( + "errors" + "math/big" + "strings" + + ethereum "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/event" + ethv1 "decred.org/dcrdex/dex/networks/eth/contracts/v1" +) + +// Reference imports to suppress errors if they are not otherwise used. +var ( + _ = errors.New + _ = big.NewInt + _ = strings.NewReader + _ = ethereum.NotFound + _ = bind.Bind + _ = common.Big1 + _ = types.BloomLookup + _ = event.NewSubscription +) + +// ERC20SwapMetaData contains all meta data concerning the ERC20Swap contract. +var ERC20SwapMetaData = &bind.MetaData{ + ABI: "[{\"inputs\":[{\"internalType\":\"address\",\"name\":\"token\",\"type\":\"address\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"inputs\":[{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"secretHash\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"initiator\",\"type\":\"address\"},{\"internalType\":\"uint64\",\"name\":\"refundTimestamp\",\"type\":\"uint64\"},{\"internalType\":\"address\",\"name\":\"participant\",\"type\":\"address\"},{\"internalType\":\"uint64\",\"name\":\"value\",\"type\":\"uint64\"}],\"internalType\":\"structERC20Swap.Vector\",\"name\":\"v\",\"type\":\"tuple\"}],\"name\":\"contractKey\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"secretHash\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"initiator\",\"type\":\"address\"},{\"internalType\":\"uint64\",\"name\":\"refundTimestamp\",\"type\":\"uint64\"},{\"internalType\":\"address\",\"name\":\"participant\",\"type\":\"address\"},{\"internalType\":\"uint64\",\"name\":\"value\",\"type\":\"uint64\"}],\"internalType\":\"structERC20Swap.Vector[]\",\"name\":\"contracts\",\"type\":\"tuple[]\"}],\"name\":\"initiate\",\"outputs\":[],\"stateMutability\":\"payable\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"secretHash\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"initiator\",\"type\":\"address\"},{\"internalType\":\"uint64\",\"name\":\"refundTimestamp\",\"type\":\"uint64\"},{\"internalType\":\"address\",\"name\":\"participant\",\"type\":\"address\"},{\"internalType\":\"uint64\",\"name\":\"value\",\"type\":\"uint64\"}],\"internalType\":\"structERC20Swap.Vector\",\"name\":\"v\",\"type\":\"tuple\"}],\"name\":\"isRedeemable\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"secretHash\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"initiator\",\"type\":\"address\"},{\"internalType\":\"uint64\",\"name\":\"refundTimestamp\",\"type\":\"uint64\"},{\"internalType\":\"address\",\"name\":\"participant\",\"type\":\"address\"},{\"internalType\":\"uint64\",\"name\":\"value\",\"type\":\"uint64\"}],\"internalType\":\"structERC20Swap.Vector\",\"name\":\"v\",\"type\":\"tuple\"},{\"internalType\":\"bytes32\",\"name\":\"secret\",\"type\":\"bytes32\"}],\"internalType\":\"structERC20Swap.Redemption[]\",\"name\":\"redemptions\",\"type\":\"tuple[]\"}],\"name\":\"redeem\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"secretHash\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"initiator\",\"type\":\"address\"},{\"internalType\":\"uint64\",\"name\":\"refundTimestamp\",\"type\":\"uint64\"},{\"internalType\":\"address\",\"name\":\"participant\",\"type\":\"address\"},{\"internalType\":\"uint64\",\"name\":\"value\",\"type\":\"uint64\"}],\"internalType\":\"structERC20Swap.Vector\",\"name\":\"v\",\"type\":\"tuple\"}],\"name\":\"refund\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"secret\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"secretHash\",\"type\":\"bytes32\"}],\"name\":\"secretValidates\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"secretHash\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"initiator\",\"type\":\"address\"},{\"internalType\":\"uint64\",\"name\":\"refundTimestamp\",\"type\":\"uint64\"},{\"internalType\":\"address\",\"name\":\"participant\",\"type\":\"address\"},{\"internalType\":\"uint64\",\"name\":\"value\",\"type\":\"uint64\"}],\"internalType\":\"structERC20Swap.Vector\",\"name\":\"v\",\"type\":\"tuple\"}],\"name\":\"status\",\"outputs\":[{\"components\":[{\"internalType\":\"enumERC20Swap.Step\",\"name\":\"step\",\"type\":\"uint8\"},{\"internalType\":\"bytes32\",\"name\":\"secret\",\"type\":\"bytes32\"},{\"internalType\":\"uint256\",\"name\":\"blockNumber\",\"type\":\"uint256\"}],\"internalType\":\"structERC20Swap.Status\",\"name\":\"\",\"type\":\"tuple\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"name\":\"swaps\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"token_address\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"}]", + Bin: "0x60a060405234801561001057600080fd5b506040516111cb3803806111cb83398101604081905261002f91610040565b6001600160a01b0316608052610070565b60006020828403121561005257600080fd5b81516001600160a01b038116811461006957600080fd5b9392505050565b60805161112b6100a060003960008181610158015281816104570152818161083e0152610b5d015261112b6000f3fe6080604052600436106100865760003560e01c80637802689d116100595780637802689d146101265780638c8e8fee14610146578063d2544c0614610192578063eb84e7f2146101b2578063ed7cbed7146101ed57600080fd5b8063428b16e11461008b57806361a16e33146100ad57806364a97bff146100e357806377d7e031146100f6575b600080fd5b34801561009757600080fd5b506100ab6100a6366004610dea565b61020d565b005b3480156100b957600080fd5b506100cd6100c8366004610e5f565b610533565b6040516100da9190610e8d565b60405180910390f35b6100ab6100f1366004610ed0565b6105ef565b34801561010257600080fd5b50610116610111366004610f33565b610918565b60405190151581526020016100da565b34801561013257600080fd5b50610116610141366004610e5f565b610992565b34801561015257600080fd5b5061017a7f000000000000000000000000000000000000000000000000000000000000000081565b6040516001600160a01b0390911681526020016100da565b34801561019e57600080fd5b506100ab6101ad366004610e5f565b6109c5565b3480156101be57600080fd5b506101df6101cd366004610f55565b60006020819052908152604090205481565b6040519081526020016100da565b3480156101f957600080fd5b506101df610208366004610e5f565b610cc5565b3233146102355760405162461bcd60e51b815260040161022c90610f6e565b60405180910390fd5b6000805b82811015610407573684848381811061025457610254610f98565b60c0029190910191503390506102706080830160608401610fae565b6001600160a01b0316146102b35760405162461bcd60e51b815260206004820152600a6024820152691b9bdd08185d5d1a195960b21b604482015260640161022c565b600080806102c084610dbe565b9250925092506000811180156102d557504381105b6103115760405162461bcd60e51b815260206004820152600d60248201526c0756e66696c6c6564207377617609c1b604482015260640161022c565b61031c828535610918565b1561035c5760405162461bcd60e51b815260206004820152601060248201526f185b1c9958591e481c995919595b595960821b604482015260640161022c565b61036b60a08501358535610918565b6103a85760405162461bcd60e51b815260206004820152600e60248201526d1a5b9d985b1a59081cd958dc995d60921b604482015260640161022c565b600083815260208190526040902060a0850180359091556103cc9060808601610fde565b6103da90633b9aca0061101e565b6103ee9067ffffffffffffffff168761104e565b95505050505080806103ff90611066565b915050610239565b5060408051336024820152604480820184905282518083039091018152606490910182526020810180516001600160e01b031663a9059cbb60e01b17905290516000916060916001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016916104819161107f565b6000604051808303816000865af19150503d80600081146104be576040519150601f19603f3d011682016040523d82523d6000602084013e6104c3565b606091505b5090925090508180156104ee5750805115806104ee5750808060200190518101906104ee91906110ba565b61052c5760405162461bcd60e51b815260206004820152600f60248201526e1d1c985b9cd9995c8819985a5b1959608a1b604482015260640161022c565b5050505050565b60408051606081018252600080825260208201819052918101829052908061055a84610dbe565b92509250506105846040805160608101909152806000815260006020820181905260409091015290565b816000036105ab578060005b908160038111156105a3576105a3610e77565b9052506105e7565b600183016105bb57806003610590565b6105c6838635610918565b156105db5760028152602081018390526105e7565b60018152604081018290525b949350505050565b32331461060e5760405162461bcd60e51b815260040161022c90610f6e565b6000805b828110156107e8573684848381811061062d5761062d610f98565b905060a002019050600081608001602081019061064a9190610fde565b67ffffffffffffffff16116106895760405162461bcd60e51b81526020600482015260056024820152640c081d985b60da1b604482015260640161022c565b600061069b6060830160408401610fde565b67ffffffffffffffff16116106e65760405162461bcd60e51b815260206004820152601160248201527003020726566756e6454696d657374616d7607c1b604482015260640161022c565b60006106f182610cc5565b60008181526020819052604090205490915080156107425760405162461bcd60e51b815260206004820152600e60248201526d73776170206e6f7420656d70747960901b604482015260640161022c565b504361074f818435610918565b1561078d5760405162461bcd60e51b815260206004820152600e60248201526d3430b9b41031b7b63634b9b4b7b760911b604482015260640161022c565b60008281526020819052604090208190556107ae60a0840160808501610fde565b6107bc90633b9aca0061101e565b6107d09067ffffffffffffffff168661104e565b945050505080806107e090611066565b915050610612565b5060408051336024820152306044820152606480820184905282518083039091018152608490910182526020810180516001600160e01b03166323b872dd60e01b17905290516000916060916001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016916108689161107f565b6000604051808303816000865af19150503d80600081146108a5576040519150601f19603f3d011682016040523d82523d6000602084013e6108aa565b606091505b5090925090508180156108d55750805115806108d55750808060200190518101906108d591906110ba565b61052c5760405162461bcd60e51b81526020600482015260146024820152731d1c985b9cd9995c88199c9bdb4819985a5b195960621b604482015260640161022c565b60008160028460405160200161093091815260200190565b60408051601f198184030181529082905261094a9161107f565b602060405180830381855afa158015610967573d6000803e3d6000fd5b5050506040513d601f19601f8201168201806040525081019061098a91906110dc565b149392505050565b60008060006109a084610dbe565b9250925050806000141580156105e757506109bc828535610918565b15949350505050565b3233146109e45760405162461bcd60e51b815260040161022c90610f6e565b6109f46060820160408301610fde565b67ffffffffffffffff16421015610a445760405162461bcd60e51b81526020600482015260146024820152731b1bd8dadd1a5b59481b9bdd08195e1c1a5c995960621b604482015260640161022c565b6000806000610a5284610dbe565b925092509250600081118015610a685750438111155b610aa65760405162461bcd60e51b815260206004820152600f60248201526e73776170206e6f742061637469766560881b604482015260640161022c565b610ab1828535610918565b15610af65760405162461bcd60e51b81526020600482015260156024820152741cddd85c08185b1c9958591e481c995919595b5959605a1b604482015260640161022c565b60018201610b3e5760405162461bcd60e51b81526020600482015260156024820152741cddd85c08185b1c9958591e481c99599d5b991959605a1b604482015260640161022c565b6000838152602081905260408120600019905560606001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000167fa9059cbb2ab09eb219583f4a59a5d0623ade346d962bcd4e46b11da047c9049b33610baf60a08a0160808b01610fde565b6040516001600160a01b03909216602483015267ffffffffffffffff16604482015260640160408051601f198184030181529181526020820180516001600160e01b03166001600160e01b0319909416939093179092529051610c12919061107f565b6000604051808303816000865af19150503d8060008114610c4f576040519150601f19603f3d011682016040523d82523d6000602084013e610c54565b606091505b509092509050818015610c7f575080511580610c7f575080806020019051810190610c7f91906110ba565b610cbd5760405162461bcd60e51b815260206004820152600f60248201526e1d1c985b9cd9995c8819985a5b1959608a1b604482015260640161022c565b505050505050565b600060028235610cdb6040850160208601610fae565b60601b846060016020810190610cf19190610fae565b60601b610d0460a0870160808801610fde565b60c01b610d176060880160408901610fde565b6040805160208101969096526bffffffffffffffffffffffff19948516908601529190921660548401526001600160c01b0319918216606884015260c01b16607082015260780160408051601f1981840301815290829052610d789161107f565b602060405180830381855afa158015610d95573d6000803e3d6000fd5b5050506040513d601f19601f82011682018060405250810190610db891906110dc565b92915050565b600080600080610dcd85610cc5565b600081815260208190526040902054909690955085945092505050565b60008060208385031215610dfd57600080fd5b823567ffffffffffffffff80821115610e1557600080fd5b818501915085601f830112610e2957600080fd5b813581811115610e3857600080fd5b86602060c083028501011115610e4d57600080fd5b60209290920196919550909350505050565b600060a08284031215610e7157600080fd5b50919050565b634e487b7160e01b600052602160045260246000fd5b8151606082019060048110610eb257634e487b7160e01b600052602160045260246000fd5b80835250602083015160208301526040830151604083015292915050565b60008060208385031215610ee357600080fd5b823567ffffffffffffffff80821115610efb57600080fd5b818501915085601f830112610f0f57600080fd5b813581811115610f1e57600080fd5b86602060a083028501011115610e4d57600080fd5b60008060408385031215610f4657600080fd5b50508035926020909101359150565b600060208284031215610f6757600080fd5b5035919050565b60208082526010908201526f39b2b73232b910109e9037b934b3b4b760811b604082015260600190565b634e487b7160e01b600052603260045260246000fd5b600060208284031215610fc057600080fd5b81356001600160a01b0381168114610fd757600080fd5b9392505050565b600060208284031215610ff057600080fd5b813567ffffffffffffffff81168114610fd757600080fd5b634e487b7160e01b600052601160045260246000fd5b600067ffffffffffffffff8083168185168183048111821515161561104557611045611008565b02949350505050565b6000821982111561106157611061611008565b500190565b60006001820161107857611078611008565b5060010190565b6000825160005b818110156110a05760208186018101518583015201611086565b818111156110af576000828501525b509190910192915050565b6000602082840312156110cc57600080fd5b81518015158114610fd757600080fd5b6000602082840312156110ee57600080fd5b505191905056fea2646970667358221220319d89b87a0d5782925310133ed5d12d2ff562b0786611308396804fcad176fc64736f6c634300080f0033", +} + +// ERC20SwapABI is the input ABI used to generate the binding from. +// Deprecated: Use ERC20SwapMetaData.ABI instead. +var ERC20SwapABI = ERC20SwapMetaData.ABI + +// ERC20SwapBin is the compiled bytecode used for deploying new contracts. +// Deprecated: Use ERC20SwapMetaData.Bin instead. +var ERC20SwapBin = ERC20SwapMetaData.Bin + +// DeployERC20Swap deploys a new Ethereum contract, binding an instance of ERC20Swap to it. +func DeployERC20Swap(auth *bind.TransactOpts, backend bind.ContractBackend, token common.Address) (common.Address, *types.Transaction, *ERC20Swap, error) { + parsed, err := ERC20SwapMetaData.GetAbi() + if err != nil { + return common.Address{}, nil, nil, err + } + if parsed == nil { + return common.Address{}, nil, nil, errors.New("GetABI returned nil") + } + + address, tx, contract, err := bind.DeployContract(auth, *parsed, common.FromHex(ERC20SwapBin), backend, token) + if err != nil { + return common.Address{}, nil, nil, err + } + return address, tx, &ERC20Swap{ERC20SwapCaller: ERC20SwapCaller{contract: contract}, ERC20SwapTransactor: ERC20SwapTransactor{contract: contract}, ERC20SwapFilterer: ERC20SwapFilterer{contract: contract}}, nil +} + +// ERC20Swap is an auto generated Go binding around an Ethereum contract. +type ERC20Swap struct { + ERC20SwapCaller // Read-only binding to the contract + ERC20SwapTransactor // Write-only binding to the contract + ERC20SwapFilterer // Log filterer for contract events +} + +// ERC20SwapCaller is an auto generated read-only Go binding around an Ethereum contract. +type ERC20SwapCaller struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// ERC20SwapTransactor is an auto generated write-only Go binding around an Ethereum contract. +type ERC20SwapTransactor struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// ERC20SwapFilterer is an auto generated log filtering Go binding around an Ethereum contract events. +type ERC20SwapFilterer struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// ERC20SwapSession is an auto generated Go binding around an Ethereum contract, +// with pre-set call and transact options. +type ERC20SwapSession struct { + Contract *ERC20Swap // Generic contract binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session +} + +// ERC20SwapCallerSession is an auto generated read-only Go binding around an Ethereum contract, +// with pre-set call options. +type ERC20SwapCallerSession struct { + Contract *ERC20SwapCaller // Generic contract caller binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session +} + +// ERC20SwapTransactorSession is an auto generated write-only Go binding around an Ethereum contract, +// with pre-set transact options. +type ERC20SwapTransactorSession struct { + Contract *ERC20SwapTransactor // Generic contract transactor binding to set the session for + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session +} + +// ERC20SwapRaw is an auto generated low-level Go binding around an Ethereum contract. +type ERC20SwapRaw struct { + Contract *ERC20Swap // Generic contract binding to access the raw methods on +} + +// ERC20SwapCallerRaw is an auto generated low-level read-only Go binding around an Ethereum contract. +type ERC20SwapCallerRaw struct { + Contract *ERC20SwapCaller // Generic read-only contract binding to access the raw methods on +} + +// ERC20SwapTransactorRaw is an auto generated low-level write-only Go binding around an Ethereum contract. +type ERC20SwapTransactorRaw struct { + Contract *ERC20SwapTransactor // Generic write-only contract binding to access the raw methods on +} + +// NewERC20Swap creates a new instance of ERC20Swap, bound to a specific deployed contract. +func NewERC20Swap(address common.Address, backend bind.ContractBackend) (*ERC20Swap, error) { + contract, err := bindERC20Swap(address, backend, backend, backend) + if err != nil { + return nil, err + } + return &ERC20Swap{ERC20SwapCaller: ERC20SwapCaller{contract: contract}, ERC20SwapTransactor: ERC20SwapTransactor{contract: contract}, ERC20SwapFilterer: ERC20SwapFilterer{contract: contract}}, nil +} + +// NewERC20SwapCaller creates a new read-only instance of ERC20Swap, bound to a specific deployed contract. +func NewERC20SwapCaller(address common.Address, caller bind.ContractCaller) (*ERC20SwapCaller, error) { + contract, err := bindERC20Swap(address, caller, nil, nil) + if err != nil { + return nil, err + } + return &ERC20SwapCaller{contract: contract}, nil +} + +// NewERC20SwapTransactor creates a new write-only instance of ERC20Swap, bound to a specific deployed contract. +func NewERC20SwapTransactor(address common.Address, transactor bind.ContractTransactor) (*ERC20SwapTransactor, error) { + contract, err := bindERC20Swap(address, nil, transactor, nil) + if err != nil { + return nil, err + } + return &ERC20SwapTransactor{contract: contract}, nil +} + +// NewERC20SwapFilterer creates a new log filterer instance of ERC20Swap, bound to a specific deployed contract. +func NewERC20SwapFilterer(address common.Address, filterer bind.ContractFilterer) (*ERC20SwapFilterer, error) { + contract, err := bindERC20Swap(address, nil, nil, filterer) + if err != nil { + return nil, err + } + return &ERC20SwapFilterer{contract: contract}, nil +} + +// bindERC20Swap binds a generic wrapper to an already deployed contract. +func bindERC20Swap(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor, filterer bind.ContractFilterer) (*bind.BoundContract, error) { + parsed, err := abi.JSON(strings.NewReader(ERC20SwapABI)) + if err != nil { + return nil, err + } + return bind.NewBoundContract(address, parsed, caller, transactor, filterer), nil +} + +// Call invokes the (constant) contract method with params as input values and +// sets the output to result. The result type might be a single field for simple +// returns, a slice of interfaces for anonymous returns and a struct for named +// returns. +func (_ERC20Swap *ERC20SwapRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { + return _ERC20Swap.Contract.ERC20SwapCaller.contract.Call(opts, result, method, params...) +} + +// Transfer initiates a plain transaction to move funds to the contract, calling +// its default method if one is available. +func (_ERC20Swap *ERC20SwapRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _ERC20Swap.Contract.ERC20SwapTransactor.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_ERC20Swap *ERC20SwapRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _ERC20Swap.Contract.ERC20SwapTransactor.contract.Transact(opts, method, params...) +} + +// Call invokes the (constant) contract method with params as input values and +// sets the output to result. The result type might be a single field for simple +// returns, a slice of interfaces for anonymous returns and a struct for named +// returns. +func (_ERC20Swap *ERC20SwapCallerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { + return _ERC20Swap.Contract.contract.Call(opts, result, method, params...) +} + +// Transfer initiates a plain transaction to move funds to the contract, calling +// its default method if one is available. +func (_ERC20Swap *ERC20SwapTransactorRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _ERC20Swap.Contract.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_ERC20Swap *ERC20SwapTransactorRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _ERC20Swap.Contract.contract.Transact(opts, method, params...) +} + +// ContractKey is a free data retrieval call binding the contract method 0xed7cbed7. +// +// Solidity: function contractKey((bytes32,address,uint64,address,uint64) v) pure returns(bytes32) +func (_ERC20Swap *ERC20SwapCaller) ContractKey(opts *bind.CallOpts, v ethv1.ETHSwapVector) ([32]byte, error) { + var out []interface{} + err := _ERC20Swap.contract.Call(opts, &out, "contractKey", v) + + if err != nil { + return *new([32]byte), err + } + + out0 := *abi.ConvertType(out[0], new([32]byte)).(*[32]byte) + + return out0, err + +} + +// ContractKey is a free data retrieval call binding the contract method 0xed7cbed7. +// +// Solidity: function contractKey((bytes32,address,uint64,address,uint64) v) pure returns(bytes32) +func (_ERC20Swap *ERC20SwapSession) ContractKey(v ethv1.ETHSwapVector) ([32]byte, error) { + return _ERC20Swap.Contract.ContractKey(&_ERC20Swap.CallOpts, v) +} + +// ContractKey is a free data retrieval call binding the contract method 0xed7cbed7. +// +// Solidity: function contractKey((bytes32,address,uint64,address,uint64) v) pure returns(bytes32) +func (_ERC20Swap *ERC20SwapCallerSession) ContractKey(v ethv1.ETHSwapVector) ([32]byte, error) { + return _ERC20Swap.Contract.ContractKey(&_ERC20Swap.CallOpts, v) +} + +// IsRedeemable is a free data retrieval call binding the contract method 0x7802689d. +// +// Solidity: function isRedeemable((bytes32,address,uint64,address,uint64) v) view returns(bool) +func (_ERC20Swap *ERC20SwapCaller) IsRedeemable(opts *bind.CallOpts, v ethv1.ETHSwapVector) (bool, error) { + var out []interface{} + err := _ERC20Swap.contract.Call(opts, &out, "isRedeemable", v) + + if err != nil { + return *new(bool), err + } + + out0 := *abi.ConvertType(out[0], new(bool)).(*bool) + + return out0, err + +} + +// IsRedeemable is a free data retrieval call binding the contract method 0x7802689d. +// +// Solidity: function isRedeemable((bytes32,address,uint64,address,uint64) v) view returns(bool) +func (_ERC20Swap *ERC20SwapSession) IsRedeemable(v ethv1.ETHSwapVector) (bool, error) { + return _ERC20Swap.Contract.IsRedeemable(&_ERC20Swap.CallOpts, v) +} + +// IsRedeemable is a free data retrieval call binding the contract method 0x7802689d. +// +// Solidity: function isRedeemable((bytes32,address,uint64,address,uint64) v) view returns(bool) +func (_ERC20Swap *ERC20SwapCallerSession) IsRedeemable(v ethv1.ETHSwapVector) (bool, error) { + return _ERC20Swap.Contract.IsRedeemable(&_ERC20Swap.CallOpts, v) +} + +// SecretValidates is a free data retrieval call binding the contract method 0x77d7e031. +// +// Solidity: function secretValidates(bytes32 secret, bytes32 secretHash) pure returns(bool) +func (_ERC20Swap *ERC20SwapCaller) SecretValidates(opts *bind.CallOpts, secret [32]byte, secretHash [32]byte) (bool, error) { + var out []interface{} + err := _ERC20Swap.contract.Call(opts, &out, "secretValidates", secret, secretHash) + + if err != nil { + return *new(bool), err + } + + out0 := *abi.ConvertType(out[0], new(bool)).(*bool) + + return out0, err + +} + +// SecretValidates is a free data retrieval call binding the contract method 0x77d7e031. +// +// Solidity: function secretValidates(bytes32 secret, bytes32 secretHash) pure returns(bool) +func (_ERC20Swap *ERC20SwapSession) SecretValidates(secret [32]byte, secretHash [32]byte) (bool, error) { + return _ERC20Swap.Contract.SecretValidates(&_ERC20Swap.CallOpts, secret, secretHash) +} + +// SecretValidates is a free data retrieval call binding the contract method 0x77d7e031. +// +// Solidity: function secretValidates(bytes32 secret, bytes32 secretHash) pure returns(bool) +func (_ERC20Swap *ERC20SwapCallerSession) SecretValidates(secret [32]byte, secretHash [32]byte) (bool, error) { + return _ERC20Swap.Contract.SecretValidates(&_ERC20Swap.CallOpts, secret, secretHash) +} + +// Status is a free data retrieval call binding the contract method 0x61a16e33. +// +// Solidity: function status((bytes32,address,uint64,address,uint64) v) view returns((uint8,bytes32,uint256)) +func (_ERC20Swap *ERC20SwapCaller) Status(opts *bind.CallOpts, v ethv1.ETHSwapVector) (ethv1.ETHSwapStatus, error) { + var out []interface{} + err := _ERC20Swap.contract.Call(opts, &out, "status", v) + + if err != nil { + return *new(ethv1.ETHSwapStatus), err + } + + out0 := *abi.ConvertType(out[0], new(ethv1.ETHSwapStatus)).(*ethv1.ETHSwapStatus) + + return out0, err + +} + +// Status is a free data retrieval call binding the contract method 0x61a16e33. +// +// Solidity: function status((bytes32,address,uint64,address,uint64) v) view returns((uint8,bytes32,uint256)) +func (_ERC20Swap *ERC20SwapSession) Status(v ethv1.ETHSwapVector) (ethv1.ETHSwapStatus, error) { + return _ERC20Swap.Contract.Status(&_ERC20Swap.CallOpts, v) +} + +// Status is a free data retrieval call binding the contract method 0x61a16e33. +// +// Solidity: function status((bytes32,address,uint64,address,uint64) v) view returns((uint8,bytes32,uint256)) +func (_ERC20Swap *ERC20SwapCallerSession) Status(v ethv1.ETHSwapVector) (ethv1.ETHSwapStatus, error) { + return _ERC20Swap.Contract.Status(&_ERC20Swap.CallOpts, v) +} + +// Swaps is a free data retrieval call binding the contract method 0xeb84e7f2. +// +// Solidity: function swaps(bytes32 ) view returns(bytes32) +func (_ERC20Swap *ERC20SwapCaller) Swaps(opts *bind.CallOpts, arg0 [32]byte) ([32]byte, error) { + var out []interface{} + err := _ERC20Swap.contract.Call(opts, &out, "swaps", arg0) + + if err != nil { + return *new([32]byte), err + } + + out0 := *abi.ConvertType(out[0], new([32]byte)).(*[32]byte) + + return out0, err + +} + +// Swaps is a free data retrieval call binding the contract method 0xeb84e7f2. +// +// Solidity: function swaps(bytes32 ) view returns(bytes32) +func (_ERC20Swap *ERC20SwapSession) Swaps(arg0 [32]byte) ([32]byte, error) { + return _ERC20Swap.Contract.Swaps(&_ERC20Swap.CallOpts, arg0) +} + +// Swaps is a free data retrieval call binding the contract method 0xeb84e7f2. +// +// Solidity: function swaps(bytes32 ) view returns(bytes32) +func (_ERC20Swap *ERC20SwapCallerSession) Swaps(arg0 [32]byte) ([32]byte, error) { + return _ERC20Swap.Contract.Swaps(&_ERC20Swap.CallOpts, arg0) +} + +// TokenAddress is a free data retrieval call binding the contract method 0x8c8e8fee. +// +// Solidity: function token_address() view returns(address) +func (_ERC20Swap *ERC20SwapCaller) TokenAddress(opts *bind.CallOpts) (common.Address, error) { + var out []interface{} + err := _ERC20Swap.contract.Call(opts, &out, "token_address") + + if err != nil { + return *new(common.Address), err + } + + out0 := *abi.ConvertType(out[0], new(common.Address)).(*common.Address) + + return out0, err + +} + +// TokenAddress is a free data retrieval call binding the contract method 0x8c8e8fee. +// +// Solidity: function token_address() view returns(address) +func (_ERC20Swap *ERC20SwapSession) TokenAddress() (common.Address, error) { + return _ERC20Swap.Contract.TokenAddress(&_ERC20Swap.CallOpts) +} + +// TokenAddress is a free data retrieval call binding the contract method 0x8c8e8fee. +// +// Solidity: function token_address() view returns(address) +func (_ERC20Swap *ERC20SwapCallerSession) TokenAddress() (common.Address, error) { + return _ERC20Swap.Contract.TokenAddress(&_ERC20Swap.CallOpts) +} + +// Initiate is a paid mutator transaction binding the contract method 0x64a97bff. +// +// Solidity: function initiate((bytes32,address,uint64,address,uint64)[] contracts) payable returns() +func (_ERC20Swap *ERC20SwapTransactor) Initiate(opts *bind.TransactOpts, contracts []ethv1.ETHSwapVector) (*types.Transaction, error) { + return _ERC20Swap.contract.Transact(opts, "initiate", contracts) +} + +// Initiate is a paid mutator transaction binding the contract method 0x64a97bff. +// +// Solidity: function initiate((bytes32,address,uint64,address,uint64)[] contracts) payable returns() +func (_ERC20Swap *ERC20SwapSession) Initiate(contracts []ethv1.ETHSwapVector) (*types.Transaction, error) { + return _ERC20Swap.Contract.Initiate(&_ERC20Swap.TransactOpts, contracts) +} + +// Initiate is a paid mutator transaction binding the contract method 0x64a97bff. +// +// Solidity: function initiate((bytes32,address,uint64,address,uint64)[] contracts) payable returns() +func (_ERC20Swap *ERC20SwapTransactorSession) Initiate(contracts []ethv1.ETHSwapVector) (*types.Transaction, error) { + return _ERC20Swap.Contract.Initiate(&_ERC20Swap.TransactOpts, contracts) +} + +// Redeem is a paid mutator transaction binding the contract method 0x428b16e1. +// +// Solidity: function redeem(((bytes32,address,uint64,address,uint64),bytes32)[] redemptions) returns() +func (_ERC20Swap *ERC20SwapTransactor) Redeem(opts *bind.TransactOpts, redemptions []ethv1.ETHSwapRedemption) (*types.Transaction, error) { + return _ERC20Swap.contract.Transact(opts, "redeem", redemptions) +} + +// Redeem is a paid mutator transaction binding the contract method 0x428b16e1. +// +// Solidity: function redeem(((bytes32,address,uint64,address,uint64),bytes32)[] redemptions) returns() +func (_ERC20Swap *ERC20SwapSession) Redeem(redemptions []ethv1.ETHSwapRedemption) (*types.Transaction, error) { + return _ERC20Swap.Contract.Redeem(&_ERC20Swap.TransactOpts, redemptions) +} + +// Redeem is a paid mutator transaction binding the contract method 0x428b16e1. +// +// Solidity: function redeem(((bytes32,address,uint64,address,uint64),bytes32)[] redemptions) returns() +func (_ERC20Swap *ERC20SwapTransactorSession) Redeem(redemptions []ethv1.ETHSwapRedemption) (*types.Transaction, error) { + return _ERC20Swap.Contract.Redeem(&_ERC20Swap.TransactOpts, redemptions) +} + +// Refund is a paid mutator transaction binding the contract method 0xd2544c06. +// +// Solidity: function refund((bytes32,address,uint64,address,uint64) v) returns() +func (_ERC20Swap *ERC20SwapTransactor) Refund(opts *bind.TransactOpts, v ethv1.ETHSwapVector) (*types.Transaction, error) { + return _ERC20Swap.contract.Transact(opts, "refund", v) +} + +// Refund is a paid mutator transaction binding the contract method 0xd2544c06. +// +// Solidity: function refund((bytes32,address,uint64,address,uint64) v) returns() +func (_ERC20Swap *ERC20SwapSession) Refund(v ethv1.ETHSwapVector) (*types.Transaction, error) { + return _ERC20Swap.Contract.Refund(&_ERC20Swap.TransactOpts, v) +} + +// Refund is a paid mutator transaction binding the contract method 0xd2544c06. +// +// Solidity: function refund((bytes32,address,uint64,address,uint64) v) returns() +func (_ERC20Swap *ERC20SwapTransactorSession) Refund(v ethv1.ETHSwapVector) (*types.Transaction, error) { + return _ERC20Swap.Contract.Refund(&_ERC20Swap.TransactOpts, v) +} diff --git a/dex/networks/eth/contracts/ETHSwapV1.sol b/dex/networks/eth/contracts/ETHSwapV1.sol new file mode 100644 index 0000000000..d1e115b67f --- /dev/null +++ b/dex/networks/eth/contracts/ETHSwapV1.sol @@ -0,0 +1,227 @@ +// SPDX-License-Identifier: BlueOak-1.0.0 +// pragma should be as specific as possible to allow easier validation. +pragma solidity = 0.8.15; + +// ETHSwap creates a contract to be deployed on an ethereum network. After +// deployed, it keeps a record of the state of a contract and enables +// redemption and refund of the contract when conditions are met. +// +// ETHSwap accomplishes this by holding funds sent to ETHSwap until certain +// conditions are met. An initiator sends a tx with the Vector(s) to fund and +// the requisite value to transfer to ETHSwap. At +// this point the funds belong to the contract, and cannot be accessed by +// anyone else, not even the contract's deployer. The swap Vector specifies +// the conditions necessary for refund and redeem. +// +// ETHSwap has no limits on gas used for any transactions. +// +// ETHSwap cannot be used by other contracts or by a third party mediating +// the swap or multisig wallets. +// +// This code should be verifiable as resulting in a certain on-chain contract +// by compiling with the correct version of solidity and comparing the +// resulting byte code to the data in the original transaction. +contract ETHSwap { + // Step is a type that hold's a contract's current step. Empty is the + // uninitiated or null value. + enum Step { Empty, Filled, Redeemed, Refunded } + + struct Status { + Step step; + bytes32 secret; + uint256 blockNumber; + } + + bytes32 constant RefundRecord = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF; + + // swaps is a map of contract hashes to the "swap record". The swap record + // has the following interpretation. + // if (record == bytes32(0x00)): contract is uninitiated + // else if (uint256(record) < block.number && sha256(record) != contract.secretHash): + // contract is initiated and redeemable by the participant with the secret. + // else if (sha256(record) == contract.secretHash): contract has been redeemed + // else if (record == RefundRecord): contract has been refunded + // else: invalid record. Should be impossible by construction + mapping(bytes32 => bytes32) public swaps; + + // Vector is the information necessary for initialization and redemption + // or refund. The Vector itself is not stored on-chain. Instead, a key + // unique to the Vector is generated from the Vector data and keys + // the swap record. + struct Vector { + bytes32 secretHash; + address initiator; + uint64 refundTimestamp; + address participant; + uint64 value; + } + + // contractKey generates a key hash which commits to the contract data. The + // generated hash is used as a key in the swaps map. + function contractKey(Vector calldata v) public pure returns (bytes32) { + return sha256(bytes.concat(v.secretHash, bytes20(v.initiator), bytes20(v.participant), bytes8(v.value), bytes8(v.refundTimestamp))); + } + + // Redemption is the information necessary to redeem a Vector. Since we + // don't store the Vector itself, it must be provided as part of the + // redemption. + struct Redemption { + Vector v; + bytes32 secret; + } + + function secretValidates(bytes32 secret, bytes32 secretHash) public pure returns (bool) { + return sha256(bytes.concat(secret)) == secretHash; + } + + // constructor is empty. This contract has no connection to the original + // sender after deployed. It can only be interacted with by users + // initiating, redeeming, and refunding swaps. + constructor() {} + + // senderIsOrigin ensures that this contract cannot be used by other + // contracts, which reduces possible attack vectors. + modifier senderIsOrigin() { + require(tx.origin == msg.sender, "sender != origin"); + _; + } + + // retrieveStatus retrieves the current swap record for the contract. + function retrieveStatus(Vector calldata v) + private view returns (bytes32, bytes32, uint256) + { + bytes32 k = contractKey(v); + bytes32 record = swaps[k]; + return (k, record, uint256(record)); + } + + // status returns the current state of the swap. + function status(Vector calldata v) + public view returns(Status memory) + { + (, bytes32 record, uint256 blockNum) = retrieveStatus(v); + Status memory r; + if (blockNum == 0) { + r.step = Step.Empty; + } else if (record == RefundRecord) { + r.step = Step.Refunded; + } else if (secretValidates(record, v.secretHash)) { + r.step = Step.Redeemed; + r.secret = record; + } else { + r.step = Step.Filled; + r.blockNumber = blockNum; + } + return r; + } + + // initiate initiates an array of Vectors. + function initiate(Vector[] calldata contracts) + public + payable + senderIsOrigin() + { + uint initVal = 0; + for (uint i = 0; i < contracts.length; i++) { + Vector calldata v = contracts[i]; + + require(v.value > 0, "0 val"); + require(v.refundTimestamp > 0, "0 refundTimestamp"); + + bytes32 k = contractKey(v); + bytes32 record = swaps[k]; + require(record == bytes32(0), "swap not empty"); + + record = bytes32(block.number); + require(!secretValidates(record, v.secretHash), "hash collision"); + + swaps[k] = record; + + initVal += v.value * 1 gwei; + } + + require(initVal == msg.value, "bad val"); + } + + // isRedeemable returns whether or not a swap identified by secretHash + // can be redeemed using secret. isRedeemable DOES NOT check if the caller + // is the participant in the vector. + function isRedeemable(Vector calldata v) + public + view + returns (bool) + { + (, bytes32 record, uint256 blockNum) = retrieveStatus(v); + return blockNum != 0 && !secretValidates(record, v.secretHash); + } + + // redeem redeems a Vector. It checks that the sender is not a contract, + // and that the secret hash hashes to secretHash. msg.value is tranfered + // from ETHSwap to the sender. + // + // To prevent reentry attack, it is very important to check the state of the + // contract first, and change the state before proceeding to send. That way, + // the nested attacking function will throw upon trying to call redeem a + // second time. Currently, reentry is also not possible because contracts + // cannot use this contract. + function redeem(Redemption[] calldata redemptions) + public + senderIsOrigin() + { + uint amountToRedeem = 0; + for (uint i = 0; i < redemptions.length; i++) { + Redemption calldata r = redemptions[i]; + + require(r.v.participant == msg.sender, "not authed"); + + (bytes32 k, bytes32 record, uint256 blockNum) = retrieveStatus(r.v); + + // To be redeemable, the record needs to represent a valid block + // number. + require(blockNum > 0 && blockNum < block.number, "unfilled swap"); + + // Can't already be redeemed. + require(!secretValidates(record, r.v.secretHash), "already redeemed"); + + // Are they presenting the correct secret? + require(secretValidates(r.secret, r.v.secretHash), "invalid secret"); + + swaps[k] = r.secret; + amountToRedeem += r.v.value * 1 gwei; + } + + (bool ok, ) = payable(msg.sender).call{value: amountToRedeem}(""); + require(ok == true, "transfer failed"); + } + + // refund refunds a Vector. It checks that the sender is not a contract + // and that the refund time has passed. msg.value is transfered from the + // contract to the sender = Vector.participant. + // + // It is important to note that this also uses call.value which comes with + // no restrictions on gas used. See redeem for more info. + function refund(Vector calldata v) + public + senderIsOrigin() + { + // Is this contract even in a refundable state? + require(block.timestamp >= v.refundTimestamp, "locktime not expired"); + + // Retrieve the record. + (bytes32 k, bytes32 record, uint256 blockNum) = retrieveStatus(v); + + // Is this swap initialized? + require(blockNum > 0 && blockNum <= block.number, "swap not active"); + + // Is it already redeemed? + require(!secretValidates(record, v.secretHash), "swap already redeemed"); + + // Is it already refunded? + require(record != RefundRecord, "swap already refunded"); + + swaps[k] = RefundRecord; + + (bool ok, ) = payable(v.initiator).call{value: v.value * 1 gwei}(""); + require(ok == true, "transfer failed"); + } +} diff --git a/dex/networks/eth/contracts/updatecontract.sh b/dex/networks/eth/contracts/updatecontract.sh index 5397986ddb..8f1214a459 100755 --- a/dex/networks/eth/contracts/updatecontract.sh +++ b/dex/networks/eth/contracts/updatecontract.sh @@ -23,6 +23,7 @@ then fi mkdir temp +mkdir -p ${PKG_NAME} solc --abi --bin --bin-runtime --overwrite --optimize ${SOLIDITY_FILE} -o ./temp/ BYTECODE=$(<./temp/${CONTRACT_NAME}.bin-runtime) diff --git a/dex/networks/eth/contracts/v1/BinRuntimeV1.go b/dex/networks/eth/contracts/v1/BinRuntimeV1.go new file mode 100644 index 0000000000..a7a9d0f326 --- /dev/null +++ b/dex/networks/eth/contracts/v1/BinRuntimeV1.go @@ -0,0 +1,6 @@ +// Code generated - DO NOT EDIT. +// This file is a generated binding and any manual changes will be lost. + +package v1 + +const ETHSwapRuntimeBin = "60806040526004361061007b5760003560e01c80637802689d1161004e5780637802689d1461011b578063d2544c061461013b578063eb84e7f21461015b578063ed7cbed71461019657600080fd5b8063428b16e11461008057806361a16e33146100a257806364a97bff146100d857806377d7e031146100eb575b600080fd5b34801561008c57600080fd5b506100a061009b366004610b6f565b6101b6565b005b3480156100ae57600080fd5b506100c26100bd366004610be4565b610447565b6040516100cf9190610c12565b60405180910390f35b6100a06100e6366004610c55565b610503565b3480156100f757600080fd5b5061010b610106366004610cb8565b61073b565b60405190151581526020016100cf565b34801561012757600080fd5b5061010b610136366004610be4565b6107b5565b34801561014757600080fd5b506100a0610156366004610be4565b6107e8565b34801561016757600080fd5b50610188610176366004610cda565b60006020819052908152604090205481565b6040519081526020016100cf565b3480156101a257600080fd5b506101886101b1366004610be4565b610a4a565b3233146101de5760405162461bcd60e51b81526004016101d590610cf3565b60405180910390fd5b6000805b828110156103b057368484838181106101fd576101fd610d1d565b60c0029190910191503390506102196080830160608401610d33565b6001600160a01b03161461025c5760405162461bcd60e51b815260206004820152600a6024820152691b9bdd08185d5d1a195960b21b60448201526064016101d5565b6000808061026984610b43565b92509250925060008111801561027e57504381105b6102ba5760405162461bcd60e51b815260206004820152600d60248201526c0756e66696c6c6564207377617609c1b60448201526064016101d5565b6102c582853561073b565b156103055760405162461bcd60e51b815260206004820152601060248201526f185b1c9958591e481c995919595b595960821b60448201526064016101d5565b61031460a0850135853561073b565b6103515760405162461bcd60e51b815260206004820152600e60248201526d1a5b9d985b1a59081cd958dc995d60921b60448201526064016101d5565b600083815260208190526040902060a0850180359091556103759060808601610d63565b61038390633b9aca00610da3565b6103979067ffffffffffffffff1687610dd3565b95505050505080806103a890610deb565b9150506101e2565b50604051600090339083908381818185875af1925050503d80600081146103f3576040519150601f19603f3d011682016040523d82523d6000602084013e6103f8565b606091505b50909150506001811515146104415760405162461bcd60e51b815260206004820152600f60248201526e1d1c985b9cd9995c8819985a5b1959608a1b60448201526064016101d5565b50505050565b60408051606081018252600080825260208201819052918101829052908061046e84610b43565b92509250506104986040805160608101909152806000815260006020820181905260409091015290565b816000036104bf578060005b908160038111156104b7576104b7610bfc565b9052506104fb565b600183016104cf578060036104a4565b6104da83863561073b565b156104ef5760028152602081018390526104fb565b60018152604081018290525b949350505050565b3233146105225760405162461bcd60e51b81526004016101d590610cf3565b6000805b828110156106fc573684848381811061054157610541610d1d565b905060a002019050600081608001602081019061055e9190610d63565b67ffffffffffffffff161161059d5760405162461bcd60e51b81526020600482015260056024820152640c081d985b60da1b60448201526064016101d5565b60006105af6060830160408401610d63565b67ffffffffffffffff16116105fa5760405162461bcd60e51b815260206004820152601160248201527003020726566756e6454696d657374616d7607c1b60448201526064016101d5565b600061060582610a4a565b60008181526020819052604090205490915080156106565760405162461bcd60e51b815260206004820152600e60248201526d73776170206e6f7420656d70747960901b60448201526064016101d5565b504361066381843561073b565b156106a15760405162461bcd60e51b815260206004820152600e60248201526d3430b9b41031b7b63634b9b4b7b760911b60448201526064016101d5565b60008281526020819052604090208190556106c260a0840160808501610d63565b6106d090633b9aca00610da3565b6106e49067ffffffffffffffff1686610dd3565b945050505080806106f490610deb565b915050610526565b503481146107365760405162461bcd60e51b8152602060048201526007602482015266189859081d985b60ca1b60448201526064016101d5565b505050565b60008160028460405160200161075391815260200190565b60408051601f198184030181529082905261076d91610e04565b602060405180830381855afa15801561078a573d6000803e3d6000fd5b5050506040513d601f19601f820116820180604052508101906107ad9190610e3f565b149392505050565b60008060006107c384610b43565b9250925050806000141580156104fb57506107df82853561073b565b15949350505050565b3233146108075760405162461bcd60e51b81526004016101d590610cf3565b6108176060820160408301610d63565b67ffffffffffffffff164210156108675760405162461bcd60e51b81526020600482015260146024820152731b1bd8dadd1a5b59481b9bdd08195e1c1a5c995960621b60448201526064016101d5565b600080600061087584610b43565b92509250925060008111801561088b5750438111155b6108c95760405162461bcd60e51b815260206004820152600f60248201526e73776170206e6f742061637469766560881b60448201526064016101d5565b6108d482853561073b565b156109195760405162461bcd60e51b81526020600482015260156024820152741cddd85c08185b1c9958591e481c995919595b5959605a1b60448201526064016101d5565b600182016109615760405162461bcd60e51b81526020600482015260156024820152741cddd85c08185b1c9958591e481c99599d5b991959605a1b60448201526064016101d5565b600083815260208181526040808320600019905561098491908701908701610d33565b6001600160a01b031661099d60a0870160808801610d63565b6109ab90633b9aca00610da3565b67ffffffffffffffff1660405160006040518083038185875af1925050503d80600081146109f5576040519150601f19603f3d011682016040523d82523d6000602084013e6109fa565b606091505b5090915050600181151514610a435760405162461bcd60e51b815260206004820152600f60248201526e1d1c985b9cd9995c8819985a5b1959608a1b60448201526064016101d5565b5050505050565b600060028235610a606040850160208601610d33565b60601b846060016020810190610a769190610d33565b60601b610a8960a0870160808801610d63565b60c01b610a9c6060880160408901610d63565b6040805160208101969096526bffffffffffffffffffffffff19948516908601529190921660548401526001600160c01b0319918216606884015260c01b16607082015260780160408051601f1981840301815290829052610afd91610e04565b602060405180830381855afa158015610b1a573d6000803e3d6000fd5b5050506040513d601f19601f82011682018060405250810190610b3d9190610e3f565b92915050565b600080600080610b5285610a4a565b600081815260208190526040902054909690955085945092505050565b60008060208385031215610b8257600080fd5b823567ffffffffffffffff80821115610b9a57600080fd5b818501915085601f830112610bae57600080fd5b813581811115610bbd57600080fd5b86602060c083028501011115610bd257600080fd5b60209290920196919550909350505050565b600060a08284031215610bf657600080fd5b50919050565b634e487b7160e01b600052602160045260246000fd5b8151606082019060048110610c3757634e487b7160e01b600052602160045260246000fd5b80835250602083015160208301526040830151604083015292915050565b60008060208385031215610c6857600080fd5b823567ffffffffffffffff80821115610c8057600080fd5b818501915085601f830112610c9457600080fd5b813581811115610ca357600080fd5b86602060a083028501011115610bd257600080fd5b60008060408385031215610ccb57600080fd5b50508035926020909101359150565b600060208284031215610cec57600080fd5b5035919050565b60208082526010908201526f39b2b73232b910109e9037b934b3b4b760811b604082015260600190565b634e487b7160e01b600052603260045260246000fd5b600060208284031215610d4557600080fd5b81356001600160a01b0381168114610d5c57600080fd5b9392505050565b600060208284031215610d7557600080fd5b813567ffffffffffffffff81168114610d5c57600080fd5b634e487b7160e01b600052601160045260246000fd5b600067ffffffffffffffff80831681851681830481118215151615610dca57610dca610d8d565b02949350505050565b60008219821115610de657610de6610d8d565b500190565b600060018201610dfd57610dfd610d8d565b5060010190565b6000825160005b81811015610e255760208186018101518583015201610e0b565b81811115610e34576000828501525b509190910192915050565b600060208284031215610e5157600080fd5b505191905056fea2646970667358221220d735868682c69bb69ed936f11e2f136e7da44cb347e0b1d67acd1e088ea13dd664736f6c634300080f0033" diff --git a/dex/networks/eth/contracts/v1/contract.go b/dex/networks/eth/contracts/v1/contract.go new file mode 100644 index 0000000000..07de2ac59e --- /dev/null +++ b/dex/networks/eth/contracts/v1/contract.go @@ -0,0 +1,442 @@ +// Code generated - DO NOT EDIT. +// This file is a generated binding and any manual changes will be lost. + +package v1 + +import ( + "errors" + "math/big" + "strings" + + ethereum "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/event" +) + +// Reference imports to suppress errors if they are not otherwise used. +var ( + _ = errors.New + _ = big.NewInt + _ = strings.NewReader + _ = ethereum.NotFound + _ = bind.Bind + _ = common.Big1 + _ = types.BloomLookup + _ = event.NewSubscription +) + +// ETHSwapRedemption is an auto generated low-level Go binding around an user-defined struct. +type ETHSwapRedemption struct { + V ETHSwapVector + Secret [32]byte +} + +// ETHSwapStatus is an auto generated low-level Go binding around an user-defined struct. +type ETHSwapStatus struct { + Step uint8 + Secret [32]byte + BlockNumber *big.Int +} + +// ETHSwapVector is an auto generated low-level Go binding around an user-defined struct. +type ETHSwapVector struct { + SecretHash [32]byte + Initiator common.Address + RefundTimestamp uint64 + Participant common.Address + Value uint64 +} + +// ETHSwapMetaData contains all meta data concerning the ETHSwap contract. +var ETHSwapMetaData = &bind.MetaData{ + ABI: "[{\"inputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"inputs\":[{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"secretHash\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"initiator\",\"type\":\"address\"},{\"internalType\":\"uint64\",\"name\":\"refundTimestamp\",\"type\":\"uint64\"},{\"internalType\":\"address\",\"name\":\"participant\",\"type\":\"address\"},{\"internalType\":\"uint64\",\"name\":\"value\",\"type\":\"uint64\"}],\"internalType\":\"structETHSwap.Vector\",\"name\":\"v\",\"type\":\"tuple\"}],\"name\":\"contractKey\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"secretHash\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"initiator\",\"type\":\"address\"},{\"internalType\":\"uint64\",\"name\":\"refundTimestamp\",\"type\":\"uint64\"},{\"internalType\":\"address\",\"name\":\"participant\",\"type\":\"address\"},{\"internalType\":\"uint64\",\"name\":\"value\",\"type\":\"uint64\"}],\"internalType\":\"structETHSwap.Vector[]\",\"name\":\"contracts\",\"type\":\"tuple[]\"}],\"name\":\"initiate\",\"outputs\":[],\"stateMutability\":\"payable\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"secretHash\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"initiator\",\"type\":\"address\"},{\"internalType\":\"uint64\",\"name\":\"refundTimestamp\",\"type\":\"uint64\"},{\"internalType\":\"address\",\"name\":\"participant\",\"type\":\"address\"},{\"internalType\":\"uint64\",\"name\":\"value\",\"type\":\"uint64\"}],\"internalType\":\"structETHSwap.Vector\",\"name\":\"v\",\"type\":\"tuple\"}],\"name\":\"isRedeemable\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"secretHash\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"initiator\",\"type\":\"address\"},{\"internalType\":\"uint64\",\"name\":\"refundTimestamp\",\"type\":\"uint64\"},{\"internalType\":\"address\",\"name\":\"participant\",\"type\":\"address\"},{\"internalType\":\"uint64\",\"name\":\"value\",\"type\":\"uint64\"}],\"internalType\":\"structETHSwap.Vector\",\"name\":\"v\",\"type\":\"tuple\"},{\"internalType\":\"bytes32\",\"name\":\"secret\",\"type\":\"bytes32\"}],\"internalType\":\"structETHSwap.Redemption[]\",\"name\":\"redemptions\",\"type\":\"tuple[]\"}],\"name\":\"redeem\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"secretHash\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"initiator\",\"type\":\"address\"},{\"internalType\":\"uint64\",\"name\":\"refundTimestamp\",\"type\":\"uint64\"},{\"internalType\":\"address\",\"name\":\"participant\",\"type\":\"address\"},{\"internalType\":\"uint64\",\"name\":\"value\",\"type\":\"uint64\"}],\"internalType\":\"structETHSwap.Vector\",\"name\":\"v\",\"type\":\"tuple\"}],\"name\":\"refund\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"secret\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"secretHash\",\"type\":\"bytes32\"}],\"name\":\"secretValidates\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"secretHash\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"initiator\",\"type\":\"address\"},{\"internalType\":\"uint64\",\"name\":\"refundTimestamp\",\"type\":\"uint64\"},{\"internalType\":\"address\",\"name\":\"participant\",\"type\":\"address\"},{\"internalType\":\"uint64\",\"name\":\"value\",\"type\":\"uint64\"}],\"internalType\":\"structETHSwap.Vector\",\"name\":\"v\",\"type\":\"tuple\"}],\"name\":\"status\",\"outputs\":[{\"components\":[{\"internalType\":\"enumETHSwap.Step\",\"name\":\"step\",\"type\":\"uint8\"},{\"internalType\":\"bytes32\",\"name\":\"secret\",\"type\":\"bytes32\"},{\"internalType\":\"uint256\",\"name\":\"blockNumber\",\"type\":\"uint256\"}],\"internalType\":\"structETHSwap.Status\",\"name\":\"\",\"type\":\"tuple\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"name\":\"swaps\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"}]", + Bin: "0x608060405234801561001057600080fd5b50610e8e806100206000396000f3fe60806040526004361061007b5760003560e01c80637802689d1161004e5780637802689d1461011b578063d2544c061461013b578063eb84e7f21461015b578063ed7cbed71461019657600080fd5b8063428b16e11461008057806361a16e33146100a257806364a97bff146100d857806377d7e031146100eb575b600080fd5b34801561008c57600080fd5b506100a061009b366004610b6f565b6101b6565b005b3480156100ae57600080fd5b506100c26100bd366004610be4565b610447565b6040516100cf9190610c12565b60405180910390f35b6100a06100e6366004610c55565b610503565b3480156100f757600080fd5b5061010b610106366004610cb8565b61073b565b60405190151581526020016100cf565b34801561012757600080fd5b5061010b610136366004610be4565b6107b5565b34801561014757600080fd5b506100a0610156366004610be4565b6107e8565b34801561016757600080fd5b50610188610176366004610cda565b60006020819052908152604090205481565b6040519081526020016100cf565b3480156101a257600080fd5b506101886101b1366004610be4565b610a4a565b3233146101de5760405162461bcd60e51b81526004016101d590610cf3565b60405180910390fd5b6000805b828110156103b057368484838181106101fd576101fd610d1d565b60c0029190910191503390506102196080830160608401610d33565b6001600160a01b03161461025c5760405162461bcd60e51b815260206004820152600a6024820152691b9bdd08185d5d1a195960b21b60448201526064016101d5565b6000808061026984610b43565b92509250925060008111801561027e57504381105b6102ba5760405162461bcd60e51b815260206004820152600d60248201526c0756e66696c6c6564207377617609c1b60448201526064016101d5565b6102c582853561073b565b156103055760405162461bcd60e51b815260206004820152601060248201526f185b1c9958591e481c995919595b595960821b60448201526064016101d5565b61031460a0850135853561073b565b6103515760405162461bcd60e51b815260206004820152600e60248201526d1a5b9d985b1a59081cd958dc995d60921b60448201526064016101d5565b600083815260208190526040902060a0850180359091556103759060808601610d63565b61038390633b9aca00610da3565b6103979067ffffffffffffffff1687610dd3565b95505050505080806103a890610deb565b9150506101e2565b50604051600090339083908381818185875af1925050503d80600081146103f3576040519150601f19603f3d011682016040523d82523d6000602084013e6103f8565b606091505b50909150506001811515146104415760405162461bcd60e51b815260206004820152600f60248201526e1d1c985b9cd9995c8819985a5b1959608a1b60448201526064016101d5565b50505050565b60408051606081018252600080825260208201819052918101829052908061046e84610b43565b92509250506104986040805160608101909152806000815260006020820181905260409091015290565b816000036104bf578060005b908160038111156104b7576104b7610bfc565b9052506104fb565b600183016104cf578060036104a4565b6104da83863561073b565b156104ef5760028152602081018390526104fb565b60018152604081018290525b949350505050565b3233146105225760405162461bcd60e51b81526004016101d590610cf3565b6000805b828110156106fc573684848381811061054157610541610d1d565b905060a002019050600081608001602081019061055e9190610d63565b67ffffffffffffffff161161059d5760405162461bcd60e51b81526020600482015260056024820152640c081d985b60da1b60448201526064016101d5565b60006105af6060830160408401610d63565b67ffffffffffffffff16116105fa5760405162461bcd60e51b815260206004820152601160248201527003020726566756e6454696d657374616d7607c1b60448201526064016101d5565b600061060582610a4a565b60008181526020819052604090205490915080156106565760405162461bcd60e51b815260206004820152600e60248201526d73776170206e6f7420656d70747960901b60448201526064016101d5565b504361066381843561073b565b156106a15760405162461bcd60e51b815260206004820152600e60248201526d3430b9b41031b7b63634b9b4b7b760911b60448201526064016101d5565b60008281526020819052604090208190556106c260a0840160808501610d63565b6106d090633b9aca00610da3565b6106e49067ffffffffffffffff1686610dd3565b945050505080806106f490610deb565b915050610526565b503481146107365760405162461bcd60e51b8152602060048201526007602482015266189859081d985b60ca1b60448201526064016101d5565b505050565b60008160028460405160200161075391815260200190565b60408051601f198184030181529082905261076d91610e04565b602060405180830381855afa15801561078a573d6000803e3d6000fd5b5050506040513d601f19601f820116820180604052508101906107ad9190610e3f565b149392505050565b60008060006107c384610b43565b9250925050806000141580156104fb57506107df82853561073b565b15949350505050565b3233146108075760405162461bcd60e51b81526004016101d590610cf3565b6108176060820160408301610d63565b67ffffffffffffffff164210156108675760405162461bcd60e51b81526020600482015260146024820152731b1bd8dadd1a5b59481b9bdd08195e1c1a5c995960621b60448201526064016101d5565b600080600061087584610b43565b92509250925060008111801561088b5750438111155b6108c95760405162461bcd60e51b815260206004820152600f60248201526e73776170206e6f742061637469766560881b60448201526064016101d5565b6108d482853561073b565b156109195760405162461bcd60e51b81526020600482015260156024820152741cddd85c08185b1c9958591e481c995919595b5959605a1b60448201526064016101d5565b600182016109615760405162461bcd60e51b81526020600482015260156024820152741cddd85c08185b1c9958591e481c99599d5b991959605a1b60448201526064016101d5565b600083815260208181526040808320600019905561098491908701908701610d33565b6001600160a01b031661099d60a0870160808801610d63565b6109ab90633b9aca00610da3565b67ffffffffffffffff1660405160006040518083038185875af1925050503d80600081146109f5576040519150601f19603f3d011682016040523d82523d6000602084013e6109fa565b606091505b5090915050600181151514610a435760405162461bcd60e51b815260206004820152600f60248201526e1d1c985b9cd9995c8819985a5b1959608a1b60448201526064016101d5565b5050505050565b600060028235610a606040850160208601610d33565b60601b846060016020810190610a769190610d33565b60601b610a8960a0870160808801610d63565b60c01b610a9c6060880160408901610d63565b6040805160208101969096526bffffffffffffffffffffffff19948516908601529190921660548401526001600160c01b0319918216606884015260c01b16607082015260780160408051601f1981840301815290829052610afd91610e04565b602060405180830381855afa158015610b1a573d6000803e3d6000fd5b5050506040513d601f19601f82011682018060405250810190610b3d9190610e3f565b92915050565b600080600080610b5285610a4a565b600081815260208190526040902054909690955085945092505050565b60008060208385031215610b8257600080fd5b823567ffffffffffffffff80821115610b9a57600080fd5b818501915085601f830112610bae57600080fd5b813581811115610bbd57600080fd5b86602060c083028501011115610bd257600080fd5b60209290920196919550909350505050565b600060a08284031215610bf657600080fd5b50919050565b634e487b7160e01b600052602160045260246000fd5b8151606082019060048110610c3757634e487b7160e01b600052602160045260246000fd5b80835250602083015160208301526040830151604083015292915050565b60008060208385031215610c6857600080fd5b823567ffffffffffffffff80821115610c8057600080fd5b818501915085601f830112610c9457600080fd5b813581811115610ca357600080fd5b86602060a083028501011115610bd257600080fd5b60008060408385031215610ccb57600080fd5b50508035926020909101359150565b600060208284031215610cec57600080fd5b5035919050565b60208082526010908201526f39b2b73232b910109e9037b934b3b4b760811b604082015260600190565b634e487b7160e01b600052603260045260246000fd5b600060208284031215610d4557600080fd5b81356001600160a01b0381168114610d5c57600080fd5b9392505050565b600060208284031215610d7557600080fd5b813567ffffffffffffffff81168114610d5c57600080fd5b634e487b7160e01b600052601160045260246000fd5b600067ffffffffffffffff80831681851681830481118215151615610dca57610dca610d8d565b02949350505050565b60008219821115610de657610de6610d8d565b500190565b600060018201610dfd57610dfd610d8d565b5060010190565b6000825160005b81811015610e255760208186018101518583015201610e0b565b81811115610e34576000828501525b509190910192915050565b600060208284031215610e5157600080fd5b505191905056fea2646970667358221220d735868682c69bb69ed936f11e2f136e7da44cb347e0b1d67acd1e088ea13dd664736f6c634300080f0033", +} + +// ETHSwapABI is the input ABI used to generate the binding from. +// Deprecated: Use ETHSwapMetaData.ABI instead. +var ETHSwapABI = ETHSwapMetaData.ABI + +// ETHSwapBin is the compiled bytecode used for deploying new contracts. +// Deprecated: Use ETHSwapMetaData.Bin instead. +var ETHSwapBin = ETHSwapMetaData.Bin + +// DeployETHSwap deploys a new Ethereum contract, binding an instance of ETHSwap to it. +func DeployETHSwap(auth *bind.TransactOpts, backend bind.ContractBackend) (common.Address, *types.Transaction, *ETHSwap, error) { + parsed, err := ETHSwapMetaData.GetAbi() + if err != nil { + return common.Address{}, nil, nil, err + } + if parsed == nil { + return common.Address{}, nil, nil, errors.New("GetABI returned nil") + } + + address, tx, contract, err := bind.DeployContract(auth, *parsed, common.FromHex(ETHSwapBin), backend) + if err != nil { + return common.Address{}, nil, nil, err + } + return address, tx, ÐSwap{ETHSwapCaller: ETHSwapCaller{contract: contract}, ETHSwapTransactor: ETHSwapTransactor{contract: contract}, ETHSwapFilterer: ETHSwapFilterer{contract: contract}}, nil +} + +// ETHSwap is an auto generated Go binding around an Ethereum contract. +type ETHSwap struct { + ETHSwapCaller // Read-only binding to the contract + ETHSwapTransactor // Write-only binding to the contract + ETHSwapFilterer // Log filterer for contract events +} + +// ETHSwapCaller is an auto generated read-only Go binding around an Ethereum contract. +type ETHSwapCaller struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// ETHSwapTransactor is an auto generated write-only Go binding around an Ethereum contract. +type ETHSwapTransactor struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// ETHSwapFilterer is an auto generated log filtering Go binding around an Ethereum contract events. +type ETHSwapFilterer struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// ETHSwapSession is an auto generated Go binding around an Ethereum contract, +// with pre-set call and transact options. +type ETHSwapSession struct { + Contract *ETHSwap // Generic contract binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session +} + +// ETHSwapCallerSession is an auto generated read-only Go binding around an Ethereum contract, +// with pre-set call options. +type ETHSwapCallerSession struct { + Contract *ETHSwapCaller // Generic contract caller binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session +} + +// ETHSwapTransactorSession is an auto generated write-only Go binding around an Ethereum contract, +// with pre-set transact options. +type ETHSwapTransactorSession struct { + Contract *ETHSwapTransactor // Generic contract transactor binding to set the session for + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session +} + +// ETHSwapRaw is an auto generated low-level Go binding around an Ethereum contract. +type ETHSwapRaw struct { + Contract *ETHSwap // Generic contract binding to access the raw methods on +} + +// ETHSwapCallerRaw is an auto generated low-level read-only Go binding around an Ethereum contract. +type ETHSwapCallerRaw struct { + Contract *ETHSwapCaller // Generic read-only contract binding to access the raw methods on +} + +// ETHSwapTransactorRaw is an auto generated low-level write-only Go binding around an Ethereum contract. +type ETHSwapTransactorRaw struct { + Contract *ETHSwapTransactor // Generic write-only contract binding to access the raw methods on +} + +// NewETHSwap creates a new instance of ETHSwap, bound to a specific deployed contract. +func NewETHSwap(address common.Address, backend bind.ContractBackend) (*ETHSwap, error) { + contract, err := bindETHSwap(address, backend, backend, backend) + if err != nil { + return nil, err + } + return ÐSwap{ETHSwapCaller: ETHSwapCaller{contract: contract}, ETHSwapTransactor: ETHSwapTransactor{contract: contract}, ETHSwapFilterer: ETHSwapFilterer{contract: contract}}, nil +} + +// NewETHSwapCaller creates a new read-only instance of ETHSwap, bound to a specific deployed contract. +func NewETHSwapCaller(address common.Address, caller bind.ContractCaller) (*ETHSwapCaller, error) { + contract, err := bindETHSwap(address, caller, nil, nil) + if err != nil { + return nil, err + } + return ÐSwapCaller{contract: contract}, nil +} + +// NewETHSwapTransactor creates a new write-only instance of ETHSwap, bound to a specific deployed contract. +func NewETHSwapTransactor(address common.Address, transactor bind.ContractTransactor) (*ETHSwapTransactor, error) { + contract, err := bindETHSwap(address, nil, transactor, nil) + if err != nil { + return nil, err + } + return ÐSwapTransactor{contract: contract}, nil +} + +// NewETHSwapFilterer creates a new log filterer instance of ETHSwap, bound to a specific deployed contract. +func NewETHSwapFilterer(address common.Address, filterer bind.ContractFilterer) (*ETHSwapFilterer, error) { + contract, err := bindETHSwap(address, nil, nil, filterer) + if err != nil { + return nil, err + } + return ÐSwapFilterer{contract: contract}, nil +} + +// bindETHSwap binds a generic wrapper to an already deployed contract. +func bindETHSwap(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor, filterer bind.ContractFilterer) (*bind.BoundContract, error) { + parsed, err := abi.JSON(strings.NewReader(ETHSwapABI)) + if err != nil { + return nil, err + } + return bind.NewBoundContract(address, parsed, caller, transactor, filterer), nil +} + +// Call invokes the (constant) contract method with params as input values and +// sets the output to result. The result type might be a single field for simple +// returns, a slice of interfaces for anonymous returns and a struct for named +// returns. +func (_ETHSwap *ETHSwapRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { + return _ETHSwap.Contract.ETHSwapCaller.contract.Call(opts, result, method, params...) +} + +// Transfer initiates a plain transaction to move funds to the contract, calling +// its default method if one is available. +func (_ETHSwap *ETHSwapRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _ETHSwap.Contract.ETHSwapTransactor.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_ETHSwap *ETHSwapRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _ETHSwap.Contract.ETHSwapTransactor.contract.Transact(opts, method, params...) +} + +// Call invokes the (constant) contract method with params as input values and +// sets the output to result. The result type might be a single field for simple +// returns, a slice of interfaces for anonymous returns and a struct for named +// returns. +func (_ETHSwap *ETHSwapCallerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { + return _ETHSwap.Contract.contract.Call(opts, result, method, params...) +} + +// Transfer initiates a plain transaction to move funds to the contract, calling +// its default method if one is available. +func (_ETHSwap *ETHSwapTransactorRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _ETHSwap.Contract.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_ETHSwap *ETHSwapTransactorRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _ETHSwap.Contract.contract.Transact(opts, method, params...) +} + +// ContractKey is a free data retrieval call binding the contract method 0xed7cbed7. +// +// Solidity: function contractKey((bytes32,address,uint64,address,uint64) v) pure returns(bytes32) +func (_ETHSwap *ETHSwapCaller) ContractKey(opts *bind.CallOpts, v ETHSwapVector) ([32]byte, error) { + var out []interface{} + err := _ETHSwap.contract.Call(opts, &out, "contractKey", v) + + if err != nil { + return *new([32]byte), err + } + + out0 := *abi.ConvertType(out[0], new([32]byte)).(*[32]byte) + + return out0, err + +} + +// ContractKey is a free data retrieval call binding the contract method 0xed7cbed7. +// +// Solidity: function contractKey((bytes32,address,uint64,address,uint64) v) pure returns(bytes32) +func (_ETHSwap *ETHSwapSession) ContractKey(v ETHSwapVector) ([32]byte, error) { + return _ETHSwap.Contract.ContractKey(&_ETHSwap.CallOpts, v) +} + +// ContractKey is a free data retrieval call binding the contract method 0xed7cbed7. +// +// Solidity: function contractKey((bytes32,address,uint64,address,uint64) v) pure returns(bytes32) +func (_ETHSwap *ETHSwapCallerSession) ContractKey(v ETHSwapVector) ([32]byte, error) { + return _ETHSwap.Contract.ContractKey(&_ETHSwap.CallOpts, v) +} + +// IsRedeemable is a free data retrieval call binding the contract method 0x7802689d. +// +// Solidity: function isRedeemable((bytes32,address,uint64,address,uint64) v) view returns(bool) +func (_ETHSwap *ETHSwapCaller) IsRedeemable(opts *bind.CallOpts, v ETHSwapVector) (bool, error) { + var out []interface{} + err := _ETHSwap.contract.Call(opts, &out, "isRedeemable", v) + + if err != nil { + return *new(bool), err + } + + out0 := *abi.ConvertType(out[0], new(bool)).(*bool) + + return out0, err + +} + +// IsRedeemable is a free data retrieval call binding the contract method 0x7802689d. +// +// Solidity: function isRedeemable((bytes32,address,uint64,address,uint64) v) view returns(bool) +func (_ETHSwap *ETHSwapSession) IsRedeemable(v ETHSwapVector) (bool, error) { + return _ETHSwap.Contract.IsRedeemable(&_ETHSwap.CallOpts, v) +} + +// IsRedeemable is a free data retrieval call binding the contract method 0x7802689d. +// +// Solidity: function isRedeemable((bytes32,address,uint64,address,uint64) v) view returns(bool) +func (_ETHSwap *ETHSwapCallerSession) IsRedeemable(v ETHSwapVector) (bool, error) { + return _ETHSwap.Contract.IsRedeemable(&_ETHSwap.CallOpts, v) +} + +// SecretValidates is a free data retrieval call binding the contract method 0x77d7e031. +// +// Solidity: function secretValidates(bytes32 secret, bytes32 secretHash) pure returns(bool) +func (_ETHSwap *ETHSwapCaller) SecretValidates(opts *bind.CallOpts, secret [32]byte, secretHash [32]byte) (bool, error) { + var out []interface{} + err := _ETHSwap.contract.Call(opts, &out, "secretValidates", secret, secretHash) + + if err != nil { + return *new(bool), err + } + + out0 := *abi.ConvertType(out[0], new(bool)).(*bool) + + return out0, err + +} + +// SecretValidates is a free data retrieval call binding the contract method 0x77d7e031. +// +// Solidity: function secretValidates(bytes32 secret, bytes32 secretHash) pure returns(bool) +func (_ETHSwap *ETHSwapSession) SecretValidates(secret [32]byte, secretHash [32]byte) (bool, error) { + return _ETHSwap.Contract.SecretValidates(&_ETHSwap.CallOpts, secret, secretHash) +} + +// SecretValidates is a free data retrieval call binding the contract method 0x77d7e031. +// +// Solidity: function secretValidates(bytes32 secret, bytes32 secretHash) pure returns(bool) +func (_ETHSwap *ETHSwapCallerSession) SecretValidates(secret [32]byte, secretHash [32]byte) (bool, error) { + return _ETHSwap.Contract.SecretValidates(&_ETHSwap.CallOpts, secret, secretHash) +} + +// Status is a free data retrieval call binding the contract method 0x61a16e33. +// +// Solidity: function status((bytes32,address,uint64,address,uint64) v) view returns((uint8,bytes32,uint256)) +func (_ETHSwap *ETHSwapCaller) Status(opts *bind.CallOpts, v ETHSwapVector) (ETHSwapStatus, error) { + var out []interface{} + err := _ETHSwap.contract.Call(opts, &out, "status", v) + + if err != nil { + return *new(ETHSwapStatus), err + } + + out0 := *abi.ConvertType(out[0], new(ETHSwapStatus)).(*ETHSwapStatus) + + return out0, err + +} + +// Status is a free data retrieval call binding the contract method 0x61a16e33. +// +// Solidity: function status((bytes32,address,uint64,address,uint64) v) view returns((uint8,bytes32,uint256)) +func (_ETHSwap *ETHSwapSession) Status(v ETHSwapVector) (ETHSwapStatus, error) { + return _ETHSwap.Contract.Status(&_ETHSwap.CallOpts, v) +} + +// Status is a free data retrieval call binding the contract method 0x61a16e33. +// +// Solidity: function status((bytes32,address,uint64,address,uint64) v) view returns((uint8,bytes32,uint256)) +func (_ETHSwap *ETHSwapCallerSession) Status(v ETHSwapVector) (ETHSwapStatus, error) { + return _ETHSwap.Contract.Status(&_ETHSwap.CallOpts, v) +} + +// Swaps is a free data retrieval call binding the contract method 0xeb84e7f2. +// +// Solidity: function swaps(bytes32 ) view returns(bytes32) +func (_ETHSwap *ETHSwapCaller) Swaps(opts *bind.CallOpts, arg0 [32]byte) ([32]byte, error) { + var out []interface{} + err := _ETHSwap.contract.Call(opts, &out, "swaps", arg0) + + if err != nil { + return *new([32]byte), err + } + + out0 := *abi.ConvertType(out[0], new([32]byte)).(*[32]byte) + + return out0, err + +} + +// Swaps is a free data retrieval call binding the contract method 0xeb84e7f2. +// +// Solidity: function swaps(bytes32 ) view returns(bytes32) +func (_ETHSwap *ETHSwapSession) Swaps(arg0 [32]byte) ([32]byte, error) { + return _ETHSwap.Contract.Swaps(&_ETHSwap.CallOpts, arg0) +} + +// Swaps is a free data retrieval call binding the contract method 0xeb84e7f2. +// +// Solidity: function swaps(bytes32 ) view returns(bytes32) +func (_ETHSwap *ETHSwapCallerSession) Swaps(arg0 [32]byte) ([32]byte, error) { + return _ETHSwap.Contract.Swaps(&_ETHSwap.CallOpts, arg0) +} + +// Initiate is a paid mutator transaction binding the contract method 0x64a97bff. +// +// Solidity: function initiate((bytes32,address,uint64,address,uint64)[] contracts) payable returns() +func (_ETHSwap *ETHSwapTransactor) Initiate(opts *bind.TransactOpts, contracts []ETHSwapVector) (*types.Transaction, error) { + return _ETHSwap.contract.Transact(opts, "initiate", contracts) +} + +// Initiate is a paid mutator transaction binding the contract method 0x64a97bff. +// +// Solidity: function initiate((bytes32,address,uint64,address,uint64)[] contracts) payable returns() +func (_ETHSwap *ETHSwapSession) Initiate(contracts []ETHSwapVector) (*types.Transaction, error) { + return _ETHSwap.Contract.Initiate(&_ETHSwap.TransactOpts, contracts) +} + +// Initiate is a paid mutator transaction binding the contract method 0x64a97bff. +// +// Solidity: function initiate((bytes32,address,uint64,address,uint64)[] contracts) payable returns() +func (_ETHSwap *ETHSwapTransactorSession) Initiate(contracts []ETHSwapVector) (*types.Transaction, error) { + return _ETHSwap.Contract.Initiate(&_ETHSwap.TransactOpts, contracts) +} + +// Redeem is a paid mutator transaction binding the contract method 0x428b16e1. +// +// Solidity: function redeem(((bytes32,address,uint64,address,uint64),bytes32)[] redemptions) returns() +func (_ETHSwap *ETHSwapTransactor) Redeem(opts *bind.TransactOpts, redemptions []ETHSwapRedemption) (*types.Transaction, error) { + return _ETHSwap.contract.Transact(opts, "redeem", redemptions) +} + +// Redeem is a paid mutator transaction binding the contract method 0x428b16e1. +// +// Solidity: function redeem(((bytes32,address,uint64,address,uint64),bytes32)[] redemptions) returns() +func (_ETHSwap *ETHSwapSession) Redeem(redemptions []ETHSwapRedemption) (*types.Transaction, error) { + return _ETHSwap.Contract.Redeem(&_ETHSwap.TransactOpts, redemptions) +} + +// Redeem is a paid mutator transaction binding the contract method 0x428b16e1. +// +// Solidity: function redeem(((bytes32,address,uint64,address,uint64),bytes32)[] redemptions) returns() +func (_ETHSwap *ETHSwapTransactorSession) Redeem(redemptions []ETHSwapRedemption) (*types.Transaction, error) { + return _ETHSwap.Contract.Redeem(&_ETHSwap.TransactOpts, redemptions) +} + +// Refund is a paid mutator transaction binding the contract method 0xd2544c06. +// +// Solidity: function refund((bytes32,address,uint64,address,uint64) v) returns() +func (_ETHSwap *ETHSwapTransactor) Refund(opts *bind.TransactOpts, v ETHSwapVector) (*types.Transaction, error) { + return _ETHSwap.contract.Transact(opts, "refund", v) +} + +// Refund is a paid mutator transaction binding the contract method 0xd2544c06. +// +// Solidity: function refund((bytes32,address,uint64,address,uint64) v) returns() +func (_ETHSwap *ETHSwapSession) Refund(v ETHSwapVector) (*types.Transaction, error) { + return _ETHSwap.Contract.Refund(&_ETHSwap.TransactOpts, v) +} + +// Refund is a paid mutator transaction binding the contract method 0xd2544c06. +// +// Solidity: function refund((bytes32,address,uint64,address,uint64) v) returns() +func (_ETHSwap *ETHSwapTransactorSession) Refund(v ETHSwapVector) (*types.Transaction, error) { + return _ETHSwap.Contract.Refund(&_ETHSwap.TransactOpts, v) +} diff --git a/dex/networks/eth/params.go b/dex/networks/eth/params.go index f3aefd96c8..6fb6a0745e 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" ) @@ -54,6 +55,7 @@ var ( VersionedGases = map[uint32]*Gases{ 0: v0Gases, + 1: v1Gases, } ContractAddresses = map[uint32]map[dex.Network]common.Address{ @@ -62,6 +64,11 @@ var ( dex.Simnet: common.HexToAddress("0x2f68e723b8989ba1c6a9f03e42f33cb7dc9d606f"), dex.Testnet: common.HexToAddress("0x198463496037754564e9bea5418Bf4117Db0520C"), }, + 1: { + dex.Mainnet: common.Address{}, + dex.Testnet: common.Address{}, + dex.Simnet: common.Address{}, + }, } ) @@ -73,6 +80,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) @@ -89,23 +115,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 } @@ -211,7 +251,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 @@ -292,3 +360,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 e2653713ad..53169e779c 100644 --- a/dex/networks/eth/tokens.go +++ b/dex/networks/eth/tokens.go @@ -140,6 +140,19 @@ var Tokens = map[uint32]*Token{ Transfer: 35_000, }, }, + 1: { + // DRAFT TODO + Address: common.Address{}, + Gas: Gases{ + Swap: 174_000, // [171756 284366 396976 509586 622184] + SwapAdd: 115_000, + Redeem: 70_000, // [63214 94858 126502 158135 189779] + RedeemAdd: 33_000, + Refund: 50_000, // [48127 48127 48127 48127 48127] + Approve: 46_000, // [44465 27365 27365 27365 27365] + Transfer: 35_000, // [32540 32540 32540 32540 32540] + }, + }, }, }, }, @@ -224,6 +237,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: { // no usdc on simnet, dextt instead @@ -272,14 +306,18 @@ func MaybeReadSimnetAddrs() { return } - ethSwapContractAddrFile := filepath.Join(ethPath, "eth_swap_contract_address.txt") - tokenSwapContractAddrFile := filepath.Join(ethPath, "erc20_swap_contract_address.txt") + ethSwapContractAddrFileV0 := filepath.Join(ethPath, "eth_swap_contract_address.txt") + tokenSwapContractAddrFileV0 := filepath.Join(ethPath, "erc20_swap_contract_address.txt") + ethSwapContractAddrFileV1 := filepath.Join(ethPath, "eth_swap_contract_address_v1.txt") + tokenSwapContractAddrFileV1 := filepath.Join(ethPath, "erc20_swap_contract_address_v1.txt") testTokenContractAddrFile := filepath.Join(ethPath, "test_token_contract_address.txt") - ContractAddresses[0][dex.Simnet] = getContractAddrFromFile(ethSwapContractAddrFile) + ContractAddresses[0][dex.Simnet] = getContractAddrFromFile(ethSwapContractAddrFileV0) + ContractAddresses[1][dex.Simnet] = getContractAddrFromFile(ethSwapContractAddrFileV1) token := Tokens[testTokenID].NetTokens[dex.Simnet] - token.SwapContracts[0].Address = getContractAddrFromFile(tokenSwapContractAddrFile) + token.SwapContracts[0].Address = getContractAddrFromFile(tokenSwapContractAddrFileV0) + token.SwapContracts[1].Address = getContractAddrFromFile(tokenSwapContractAddrFileV1) token.Address = getContractAddrFromFile(testTokenContractAddrFile) } diff --git a/dex/networks/eth/txdata.go b/dex/networks/eth/txdata.go index 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/testing/eth/create-node.sh b/dex/testing/eth/create-node.sh index b5555e9f6b..044865b219 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 3daa6d1be3..5e35e5d577 100755 --- a/dex/testing/eth/harness.sh +++ b/dex/testing/eth/harness.sh @@ -58,6 +58,9 @@ ETH_SWAP_V0="608060405234801561001057600080fd5b50610b7a806100206000396000f3fe608 ERC20_SWAP_V0="60a060405234801561001057600080fd5b50604051610e92380380610e9283398101604081905261002f91610040565b6001600160a01b0316608052610070565b60006020828403121561005257600080fd5b81516001600160a01b038116811461006957600080fd5b9392505050565b608051610df361009f6000396000818160c50152818161029b0152818161066b01526109f30152610df36000f3fe608060405234801561001057600080fd5b506004361061007d5760003560e01c8063a8793f941161005b578063a8793f94146100ff578063d0f761c014610112578063eb84e7f214610135578063f4fd17f9146101a457600080fd5b80637249fbb61461008257806376467cbd146100975780638c8e8fee146100c0575b600080fd5b610095610090366004610ac8565b6101b7565b005b6100aa6100a5366004610ac8565b610376565b6040516100b79190610b19565b60405180910390f35b6100e77f000000000000000000000000000000000000000000000000000000000000000081565b6040516001600160a01b0390911681526020016100b7565b61009561010d366004610b7e565b610451565b610125610120366004610ac8565b61074c565b60405190151581526020016100b7565b610191610143366004610ac8565b60006020819052908152604090208054600182015460028301546003840154600485015460059095015493949293919290916001600160a01b0391821691811690600160a01b900460ff1687565b6040516100b79796959493929190610bf3565b6100956101b2366004610c3f565b6107ac565b3233146101df5760405162461bcd60e51b81526004016101d690610ca2565b60405180910390fd5b6101e88161074c565b6102255760405162461bcd60e51b815260206004820152600e60248201526d6e6f7420726566756e6461626c6560901b60448201526064016101d6565b60008181526020818152604080832060058101805460ff60a01b1916600360a01b17905560018101548251336024820152604480820192909252835180820390920182526064018352928301805163a9059cbb60e01b6001600160e01b0390911617905290519092916060916001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016916102c591610ccc565b6000604051808303816000865af19150503d8060008114610302576040519150601f19603f3d011682016040523d82523d6000602084013e610307565b606091505b5090925090508180156103325750805115806103325750808060200190518101906103329190610cfb565b6103705760405162461bcd60e51b815260206004820152600f60248201526e1d1c985b9cd9995c8819985a5b1959608a1b60448201526064016101d6565b50505050565b6103b36040805160e081018252600080825260208201819052918101829052606081018290526080810182905260a081018290529060c082015290565b60008281526020818152604091829020825160e08101845281548152600182015492810192909252600281015492820192909252600380830154606083015260048301546001600160a01b039081166080840152600584015490811660a084015291929160c0840191600160a01b90910460ff169081111561043757610437610ae1565b600381111561044857610448610ae1565b90525092915050565b3233146104705760405162461bcd60e51b81526004016101d690610ca2565b6000805b82811015610615573684848381811061048f5761048f610d1d565b9050608002019050600080600083602001358152602001908152602001600020905060008260600135116104ed5760405162461bcd60e51b81526020600482015260056024820152640c081d985b60da1b60448201526064016101d6565b813561052f5760405162461bcd60e51b815260206004820152601160248201527003020726566756e6454696d657374616d7607c1b60448201526064016101d6565b60006005820154600160a01b900460ff16600381111561055157610551610ae1565b146105905760405162461bcd60e51b815260206004820152600f60248201526e0c8eae040e6cac6e4cae840d0c2e6d608b1b60448201526064016101d6565b436002820155813560038201556004810180546001600160a01b031916331790556105c16060830160408401610d33565b6005820180546060850135600185018190556001600160a01b03939093166001600160a81b031990911617600160a01b1790556105fe9085610d72565b93505050808061060d90610d8b565b915050610474565b5060408051336024820152306044820152606480820184905282518083039091018152608490910182526020810180516001600160e01b03166323b872dd60e01b17905290516000916060916001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000169161069591610ccc565b6000604051808303816000865af19150503d80600081146106d2576040519150601f19603f3d011682016040523d82523d6000602084013e6106d7565b606091505b5090925090508180156107025750805115806107025750808060200190518101906107029190610cfb565b6107455760405162461bcd60e51b81526020600482015260146024820152731d1c985b9cd9995c88199c9bdb4819985a5b195960621b60448201526064016101d6565b5050505050565b600081815260208190526040812060016005820154600160a01b900460ff16600381111561077c5761077c610ae1565b148015610795575060048101546001600160a01b031633145b80156107a5575080600301544210155b9392505050565b3233146107cb5760405162461bcd60e51b81526004016101d690610ca2565b6000805b828110156109a357368484838181106107ea576107ea610d1d565b6020604091820293909301838101356000908152938490529220919250600190506005820154600160a01b900460ff16600381111561082b5761082b610ae1565b146108645760405162461bcd60e51b815260206004820152600960248201526862616420737461746560b81b60448201526064016101d6565b60058101546001600160a01b031633146108b25760405162461bcd60e51b815260206004820152600f60248201526e189859081c185c9d1a58da5c185b9d608a1b60448201526064016101d6565b8160200135600283600001356040516020016108d091815260200190565b60408051601f19818403018152908290526108ea91610ccc565b602060405180830381855afa158015610907573d6000803e3d6000fd5b5050506040513d601f19601f8201168201806040525081019061092a9190610da4565b146109645760405162461bcd60e51b815260206004820152600a602482015269189859081cd958dc995d60b21b60448201526064016101d6565b60058101805460ff60a01b1916600160a11b17905581358155600181015461098c9085610d72565b93505050808061099b90610d8b565b9150506107cf565b5060408051336024820152604480820184905282518083039091018152606490910182526020810180516001600160e01b031663a9059cbb60e01b17905290516000916060916001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001691610a1d91610ccc565b6000604051808303816000865af19150503d8060008114610a5a576040519150601f19603f3d011682016040523d82523d6000602084013e610a5f565b606091505b509092509050818015610a8a575080511580610a8a575080806020019051810190610a8a9190610cfb565b6107455760405162461bcd60e51b815260206004820152600f60248201526e1d1c985b9cd9995c8819985a5b1959608a1b60448201526064016101d6565b600060208284031215610ada57600080fd5b5035919050565b634e487b7160e01b600052602160045260246000fd5b60048110610b1557634e487b7160e01b600052602160045260246000fd5b9052565b600060e08201905082518252602083015160208301526040830151604083015260608301516060830152608083015160018060a01b0380821660808501528060a08601511660a0850152505060c0830151610b7760c0840182610af7565b5092915050565b60008060208385031215610b9157600080fd5b823567ffffffffffffffff80821115610ba957600080fd5b818501915085601f830112610bbd57600080fd5b813581811115610bcc57600080fd5b8660208260071b8501011115610be157600080fd5b60209290920196919550909350505050565b8781526020810187905260408101869052606081018590526001600160a01b038481166080830152831660a082015260e08101610c3360c0830184610af7565b98975050505050505050565b60008060208385031215610c5257600080fd5b823567ffffffffffffffff80821115610c6a57600080fd5b818501915085601f830112610c7e57600080fd5b813581811115610c8d57600080fd5b8660208260061b8501011115610be157600080fd5b60208082526010908201526f39b2b73232b910109e9037b934b3b4b760811b604082015260600190565b6000825160005b81811015610ced5760208186018101518583015201610cd3565b506000920191825250919050565b600060208284031215610d0d57600080fd5b815180151581146107a557600080fd5b634e487b7160e01b600052603260045260246000fd5b600060208284031215610d4557600080fd5b81356001600160a01b03811681146107a557600080fd5b634e487b7160e01b600052601160045260246000fd5b80820180821115610d8557610d85610d5c565b92915050565b600060018201610d9d57610d9d610d5c565b5060010190565b600060208284031215610db657600080fd5b505191905056fea2646970667358221220a055a4890a5ecf3876dbee91dfbeb46ba11b5f7c09b6d935173932d93f8fb92264736f6c63430008120033" TEST_TOKEN="608060405234801561001057600080fd5b506040805180820190915260098152682a32b9ba2a37b5b2b760b91b602082015260039061003e90826101f5565b506040805180820190915260038152621514d560ea1b602082015260049061006690826101f5565b506909513ea9de0243800000600255600060208190526902544faa778090e000007f7d4921c2bc32c0110a31d16f4efb43c7a1228f1df7af765f608241dee5c62ebc8190557f59603491850c7d11499afe95b334ccfd92b48b36a15df31ef59ff5813fe370828190557f963f2e057cac0b71a4b8cff76a0e66200ffc6cc5498c1198bc1df3cb2bf751dc8190557fbc10d5a0a531ecf97938db2df6f3f5b59678ae655bd09be1d358f605f79153d481905573d12ab7cf72ccf1f3882ec99ddc53cd415635c3be9091527f5bd8dfce2dbb581d0922a094c40bab2f7d2f0ea9aaf275bf0fcc0f027a2ff91d556102b4565b634e487b7160e01b600052604160045260246000fd5b600181811c9082168061018057607f821691505b6020821081036101a057634e487b7160e01b600052602260045260246000fd5b50919050565b601f8211156101f057600081815260208120601f850160051c810160208610156101cd5750805b601f850160051c820191505b818110156101ec578281556001016101d9565b5050505b505050565b81516001600160401b0381111561020e5761020e610156565b6102228161021c845461016c565b846101a6565b602080601f831160018114610257576000841561023f5750858301515b600019600386901b1c1916600185901b1785556101ec565b600085815260208120601f198616915b8281101561028657888601518255948401946001909101908401610267565b50858210156102a45787850151600019600388901b60f8161c191681555b5050505050600190811b01905550565b6107c0806102c36000396000f3fe608060405234801561001057600080fd5b50600436106100a95760003560e01c806370a082311161007157806370a08231146101235780638ba4cc3c1461014c57806395d89b4114610161578063a9059cbb14610169578063ce714b511461017c578063dd62ed3e1461018f57600080fd5b806306fdde03146100ae578063095ea7b3146100cc57806318160ddd146100ef57806323b872dd14610101578063313ce56714610114575b600080fd5b6100b66101c8565b6040516100c3919061060a565b60405180910390f35b6100df6100da366004610674565b61025a565b60405190151581526020016100c3565b6002545b6040519081526020016100c3565b6100df61010f36600461069e565b610271565b604051601281526020016100c3565b6100f36101313660046106da565b6001600160a01b031660009081526020819052604090205490565b61015f61015a366004610674565b610320565b005b6100b6610368565b6100df610177366004610674565b610377565b6100df61018a36600461069e565b610384565b6100f361019d3660046106fc565b6001600160a01b03918216600090815260016020908152604080832093909416825291909152205490565b6060600380546101d79061072f565b80601f01602080910402602001604051908101604052809291908181526020018280546102039061072f565b80156102505780601f1061022557610100808354040283529160200191610250565b820191906000526020600020905b81548152906001019060200180831161023357829003601f168201915b5050505050905090565b600061026733848461039b565b5060015b92915050565b600061027e84848461048a565b6001600160a01b0384166000908152600160209081526040808320338452909152902054828110156103085760405162461bcd60e51b815260206004820152602860248201527f45524332303a207472616e7366657220616d6f756e74206578636565647320616044820152676c6c6f77616e636560c01b60648201526084015b60405180910390fd5b610315853385840361039b565b506001949350505050565b80600260008282546103329190610769565b90915550506001600160a01b0382166000908152602081905260408120805483929061035f908490610769565b90915550505050565b6060600480546101d79061072f565b600061026733848461048a565b600061039184848461039b565b5060019392505050565b6001600160a01b0383166103fd5760405162461bcd60e51b8152602060048201526024808201527f45524332303a20617070726f76652066726f6d20746865207a65726f206164646044820152637265737360e01b60648201526084016102ff565b6001600160a01b03821661045e5760405162461bcd60e51b815260206004820152602260248201527f45524332303a20617070726f766520746f20746865207a65726f206164647265604482015261737360f01b60648201526084016102ff565b6001600160a01b0392831660009081526001602090815260408083209490951682529290925291902055565b6001600160a01b0383166104ee5760405162461bcd60e51b815260206004820152602560248201527f45524332303a207472616e736665722066726f6d20746865207a65726f206164604482015264647265737360d81b60648201526084016102ff565b6001600160a01b0382166105505760405162461bcd60e51b815260206004820152602360248201527f45524332303a207472616e7366657220746f20746865207a65726f206164647260448201526265737360e81b60648201526084016102ff565b6001600160a01b038316600090815260208190526040902054818110156105c85760405162461bcd60e51b815260206004820152602660248201527f45524332303a207472616e7366657220616d6f756e7420657863656564732062604482015265616c616e636560d01b60648201526084016102ff565b6001600160a01b038085166000908152602081905260408082208585039055918516815290812080548492906105ff908490610769565b909155505050505050565b600060208083528351808285015260005b818110156106375785810183015185820160400152820161061b565b506000604082860101526040601f19601f8301168501019250505092915050565b80356001600160a01b038116811461066f57600080fd5b919050565b6000806040838503121561068757600080fd5b61069083610658565b946020939093013593505050565b6000806000606084860312156106b357600080fd5b6106bc84610658565b92506106ca60208501610658565b9150604084013590509250925092565b6000602082840312156106ec57600080fd5b6106f582610658565b9392505050565b6000806040838503121561070f57600080fd5b61071883610658565b915061072660208401610658565b90509250929050565b600181811c9082168061074357607f821691505b60208210810361076357634e487b7160e01b600052602260045260246000fd5b50919050565b8082018082111561026b57634e487b7160e01b600052601160045260246000fdfea2646970667358221220cb023f2d844674cc150df21a88f6e857049c95297fda38509e691e3b861aa9f164736f6c63430008120033" +ETH_SWAP_V1="608060405234801561001057600080fd5b50610e8e806100206000396000f3fe60806040526004361061007b5760003560e01c80637802689d1161004e5780637802689d1461011b578063d2544c061461013b578063eb84e7f21461015b578063ed7cbed71461019657600080fd5b8063428b16e11461008057806361a16e33146100a257806364a97bff146100d857806377d7e031146100eb575b600080fd5b34801561008c57600080fd5b506100a061009b366004610b6f565b6101b6565b005b3480156100ae57600080fd5b506100c26100bd366004610be4565b610447565b6040516100cf9190610c12565b60405180910390f35b6100a06100e6366004610c55565b610503565b3480156100f757600080fd5b5061010b610106366004610cb8565b61073b565b60405190151581526020016100cf565b34801561012757600080fd5b5061010b610136366004610be4565b6107b5565b34801561014757600080fd5b506100a0610156366004610be4565b6107e8565b34801561016757600080fd5b50610188610176366004610cda565b60006020819052908152604090205481565b6040519081526020016100cf565b3480156101a257600080fd5b506101886101b1366004610be4565b610a4a565b3233146101de5760405162461bcd60e51b81526004016101d590610cf3565b60405180910390fd5b6000805b828110156103b057368484838181106101fd576101fd610d1d565b60c0029190910191503390506102196080830160608401610d33565b6001600160a01b03161461025c5760405162461bcd60e51b815260206004820152600a6024820152691b9bdd08185d5d1a195960b21b60448201526064016101d5565b6000808061026984610b43565b92509250925060008111801561027e57504381105b6102ba5760405162461bcd60e51b815260206004820152600d60248201526c0756e66696c6c6564207377617609c1b60448201526064016101d5565b6102c582853561073b565b156103055760405162461bcd60e51b815260206004820152601060248201526f185b1c9958591e481c995919595b595960821b60448201526064016101d5565b61031460a0850135853561073b565b6103515760405162461bcd60e51b815260206004820152600e60248201526d1a5b9d985b1a59081cd958dc995d60921b60448201526064016101d5565b600083815260208190526040902060a0850180359091556103759060808601610d63565b61038390633b9aca00610da3565b6103979067ffffffffffffffff1687610dd3565b95505050505080806103a890610deb565b9150506101e2565b50604051600090339083908381818185875af1925050503d80600081146103f3576040519150601f19603f3d011682016040523d82523d6000602084013e6103f8565b606091505b50909150506001811515146104415760405162461bcd60e51b815260206004820152600f60248201526e1d1c985b9cd9995c8819985a5b1959608a1b60448201526064016101d5565b50505050565b60408051606081018252600080825260208201819052918101829052908061046e84610b43565b92509250506104986040805160608101909152806000815260006020820181905260409091015290565b816000036104bf578060005b908160038111156104b7576104b7610bfc565b9052506104fb565b600183016104cf578060036104a4565b6104da83863561073b565b156104ef5760028152602081018390526104fb565b60018152604081018290525b949350505050565b3233146105225760405162461bcd60e51b81526004016101d590610cf3565b6000805b828110156106fc573684848381811061054157610541610d1d565b905060a002019050600081608001602081019061055e9190610d63565b67ffffffffffffffff161161059d5760405162461bcd60e51b81526020600482015260056024820152640c081d985b60da1b60448201526064016101d5565b60006105af6060830160408401610d63565b67ffffffffffffffff16116105fa5760405162461bcd60e51b815260206004820152601160248201527003020726566756e6454696d657374616d7607c1b60448201526064016101d5565b600061060582610a4a565b60008181526020819052604090205490915080156106565760405162461bcd60e51b815260206004820152600e60248201526d73776170206e6f7420656d70747960901b60448201526064016101d5565b504361066381843561073b565b156106a15760405162461bcd60e51b815260206004820152600e60248201526d3430b9b41031b7b63634b9b4b7b760911b60448201526064016101d5565b60008281526020819052604090208190556106c260a0840160808501610d63565b6106d090633b9aca00610da3565b6106e49067ffffffffffffffff1686610dd3565b945050505080806106f490610deb565b915050610526565b503481146107365760405162461bcd60e51b8152602060048201526007602482015266189859081d985b60ca1b60448201526064016101d5565b505050565b60008160028460405160200161075391815260200190565b60408051601f198184030181529082905261076d91610e04565b602060405180830381855afa15801561078a573d6000803e3d6000fd5b5050506040513d601f19601f820116820180604052508101906107ad9190610e3f565b149392505050565b60008060006107c384610b43565b9250925050806000141580156104fb57506107df82853561073b565b15949350505050565b3233146108075760405162461bcd60e51b81526004016101d590610cf3565b6108176060820160408301610d63565b67ffffffffffffffff164210156108675760405162461bcd60e51b81526020600482015260146024820152731b1bd8dadd1a5b59481b9bdd08195e1c1a5c995960621b60448201526064016101d5565b600080600061087584610b43565b92509250925060008111801561088b5750438111155b6108c95760405162461bcd60e51b815260206004820152600f60248201526e73776170206e6f742061637469766560881b60448201526064016101d5565b6108d482853561073b565b156109195760405162461bcd60e51b81526020600482015260156024820152741cddd85c08185b1c9958591e481c995919595b5959605a1b60448201526064016101d5565b600182016109615760405162461bcd60e51b81526020600482015260156024820152741cddd85c08185b1c9958591e481c99599d5b991959605a1b60448201526064016101d5565b600083815260208181526040808320600019905561098491908701908701610d33565b6001600160a01b031661099d60a0870160808801610d63565b6109ab90633b9aca00610da3565b67ffffffffffffffff1660405160006040518083038185875af1925050503d80600081146109f5576040519150601f19603f3d011682016040523d82523d6000602084013e6109fa565b606091505b5090915050600181151514610a435760405162461bcd60e51b815260206004820152600f60248201526e1d1c985b9cd9995c8819985a5b1959608a1b60448201526064016101d5565b5050505050565b600060028235610a606040850160208601610d33565b60601b846060016020810190610a769190610d33565b60601b610a8960a0870160808801610d63565b60c01b610a9c6060880160408901610d63565b6040805160208101969096526bffffffffffffffffffffffff19948516908601529190921660548401526001600160c01b0319918216606884015260c01b16607082015260780160408051601f1981840301815290829052610afd91610e04565b602060405180830381855afa158015610b1a573d6000803e3d6000fd5b5050506040513d601f19601f82011682018060405250810190610b3d9190610e3f565b92915050565b600080600080610b5285610a4a565b600081815260208190526040902054909690955085945092505050565b60008060208385031215610b8257600080fd5b823567ffffffffffffffff80821115610b9a57600080fd5b818501915085601f830112610bae57600080fd5b813581811115610bbd57600080fd5b86602060c083028501011115610bd257600080fd5b60209290920196919550909350505050565b600060a08284031215610bf657600080fd5b50919050565b634e487b7160e01b600052602160045260246000fd5b8151606082019060048110610c3757634e487b7160e01b600052602160045260246000fd5b80835250602083015160208301526040830151604083015292915050565b60008060208385031215610c6857600080fd5b823567ffffffffffffffff80821115610c8057600080fd5b818501915085601f830112610c9457600080fd5b813581811115610ca357600080fd5b86602060a083028501011115610bd257600080fd5b60008060408385031215610ccb57600080fd5b50508035926020909101359150565b600060208284031215610cec57600080fd5b5035919050565b60208082526010908201526f39b2b73232b910109e9037b934b3b4b760811b604082015260600190565b634e487b7160e01b600052603260045260246000fd5b600060208284031215610d4557600080fd5b81356001600160a01b0381168114610d5c57600080fd5b9392505050565b600060208284031215610d7557600080fd5b813567ffffffffffffffff81168114610d5c57600080fd5b634e487b7160e01b600052601160045260246000fd5b600067ffffffffffffffff80831681851681830481118215151615610dca57610dca610d8d565b02949350505050565b60008219821115610de657610de6610d8d565b500190565b600060018201610dfd57610dfd610d8d565b5060010190565b6000825160005b81811015610e255760208186018101518583015201610e0b565b81811115610e34576000828501525b509190910192915050565b600060208284031215610e5157600080fd5b505191905056fea2646970667358221220d735868682c69bb69ed936f11e2f136e7da44cb347e0b1d67acd1e088ea13dd664736f6c634300080f0033" +ERC20_SWAP_V1="60a060405234801561001057600080fd5b506040516111cb3803806111cb83398101604081905261002f91610040565b6001600160a01b0316608052610070565b60006020828403121561005257600080fd5b81516001600160a01b038116811461006957600080fd5b9392505050565b60805161112b6100a060003960008181610158015281816104570152818161083e0152610b5d015261112b6000f3fe6080604052600436106100865760003560e01c80637802689d116100595780637802689d146101265780638c8e8fee14610146578063d2544c0614610192578063eb84e7f2146101b2578063ed7cbed7146101ed57600080fd5b8063428b16e11461008b57806361a16e33146100ad57806364a97bff146100e357806377d7e031146100f6575b600080fd5b34801561009757600080fd5b506100ab6100a6366004610dea565b61020d565b005b3480156100b957600080fd5b506100cd6100c8366004610e5f565b610533565b6040516100da9190610e8d565b60405180910390f35b6100ab6100f1366004610ed0565b6105ef565b34801561010257600080fd5b50610116610111366004610f33565b610918565b60405190151581526020016100da565b34801561013257600080fd5b50610116610141366004610e5f565b610992565b34801561015257600080fd5b5061017a7f000000000000000000000000000000000000000000000000000000000000000081565b6040516001600160a01b0390911681526020016100da565b34801561019e57600080fd5b506100ab6101ad366004610e5f565b6109c5565b3480156101be57600080fd5b506101df6101cd366004610f55565b60006020819052908152604090205481565b6040519081526020016100da565b3480156101f957600080fd5b506101df610208366004610e5f565b610cc5565b3233146102355760405162461bcd60e51b815260040161022c90610f6e565b60405180910390fd5b6000805b82811015610407573684848381811061025457610254610f98565b60c0029190910191503390506102706080830160608401610fae565b6001600160a01b0316146102b35760405162461bcd60e51b815260206004820152600a6024820152691b9bdd08185d5d1a195960b21b604482015260640161022c565b600080806102c084610dbe565b9250925092506000811180156102d557504381105b6103115760405162461bcd60e51b815260206004820152600d60248201526c0756e66696c6c6564207377617609c1b604482015260640161022c565b61031c828535610918565b1561035c5760405162461bcd60e51b815260206004820152601060248201526f185b1c9958591e481c995919595b595960821b604482015260640161022c565b61036b60a08501358535610918565b6103a85760405162461bcd60e51b815260206004820152600e60248201526d1a5b9d985b1a59081cd958dc995d60921b604482015260640161022c565b600083815260208190526040902060a0850180359091556103cc9060808601610fde565b6103da90633b9aca0061101e565b6103ee9067ffffffffffffffff168761104e565b95505050505080806103ff90611066565b915050610239565b5060408051336024820152604480820184905282518083039091018152606490910182526020810180516001600160e01b031663a9059cbb60e01b17905290516000916060916001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016916104819161107f565b6000604051808303816000865af19150503d80600081146104be576040519150601f19603f3d011682016040523d82523d6000602084013e6104c3565b606091505b5090925090508180156104ee5750805115806104ee5750808060200190518101906104ee91906110ba565b61052c5760405162461bcd60e51b815260206004820152600f60248201526e1d1c985b9cd9995c8819985a5b1959608a1b604482015260640161022c565b5050505050565b60408051606081018252600080825260208201819052918101829052908061055a84610dbe565b92509250506105846040805160608101909152806000815260006020820181905260409091015290565b816000036105ab578060005b908160038111156105a3576105a3610e77565b9052506105e7565b600183016105bb57806003610590565b6105c6838635610918565b156105db5760028152602081018390526105e7565b60018152604081018290525b949350505050565b32331461060e5760405162461bcd60e51b815260040161022c90610f6e565b6000805b828110156107e8573684848381811061062d5761062d610f98565b905060a002019050600081608001602081019061064a9190610fde565b67ffffffffffffffff16116106895760405162461bcd60e51b81526020600482015260056024820152640c081d985b60da1b604482015260640161022c565b600061069b6060830160408401610fde565b67ffffffffffffffff16116106e65760405162461bcd60e51b815260206004820152601160248201527003020726566756e6454696d657374616d7607c1b604482015260640161022c565b60006106f182610cc5565b60008181526020819052604090205490915080156107425760405162461bcd60e51b815260206004820152600e60248201526d73776170206e6f7420656d70747960901b604482015260640161022c565b504361074f818435610918565b1561078d5760405162461bcd60e51b815260206004820152600e60248201526d3430b9b41031b7b63634b9b4b7b760911b604482015260640161022c565b60008281526020819052604090208190556107ae60a0840160808501610fde565b6107bc90633b9aca0061101e565b6107d09067ffffffffffffffff168661104e565b945050505080806107e090611066565b915050610612565b5060408051336024820152306044820152606480820184905282518083039091018152608490910182526020810180516001600160e01b03166323b872dd60e01b17905290516000916060916001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016916108689161107f565b6000604051808303816000865af19150503d80600081146108a5576040519150601f19603f3d011682016040523d82523d6000602084013e6108aa565b606091505b5090925090508180156108d55750805115806108d55750808060200190518101906108d591906110ba565b61052c5760405162461bcd60e51b81526020600482015260146024820152731d1c985b9cd9995c88199c9bdb4819985a5b195960621b604482015260640161022c565b60008160028460405160200161093091815260200190565b60408051601f198184030181529082905261094a9161107f565b602060405180830381855afa158015610967573d6000803e3d6000fd5b5050506040513d601f19601f8201168201806040525081019061098a91906110dc565b149392505050565b60008060006109a084610dbe565b9250925050806000141580156105e757506109bc828535610918565b15949350505050565b3233146109e45760405162461bcd60e51b815260040161022c90610f6e565b6109f46060820160408301610fde565b67ffffffffffffffff16421015610a445760405162461bcd60e51b81526020600482015260146024820152731b1bd8dadd1a5b59481b9bdd08195e1c1a5c995960621b604482015260640161022c565b6000806000610a5284610dbe565b925092509250600081118015610a685750438111155b610aa65760405162461bcd60e51b815260206004820152600f60248201526e73776170206e6f742061637469766560881b604482015260640161022c565b610ab1828535610918565b15610af65760405162461bcd60e51b81526020600482015260156024820152741cddd85c08185b1c9958591e481c995919595b5959605a1b604482015260640161022c565b60018201610b3e5760405162461bcd60e51b81526020600482015260156024820152741cddd85c08185b1c9958591e481c99599d5b991959605a1b604482015260640161022c565b6000838152602081905260408120600019905560606001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000167fa9059cbb2ab09eb219583f4a59a5d0623ade346d962bcd4e46b11da047c9049b33610baf60a08a0160808b01610fde565b6040516001600160a01b03909216602483015267ffffffffffffffff16604482015260640160408051601f198184030181529181526020820180516001600160e01b03166001600160e01b0319909416939093179092529051610c12919061107f565b6000604051808303816000865af19150503d8060008114610c4f576040519150601f19603f3d011682016040523d82523d6000602084013e610c54565b606091505b509092509050818015610c7f575080511580610c7f575080806020019051810190610c7f91906110ba565b610cbd5760405162461bcd60e51b815260206004820152600f60248201526e1d1c985b9cd9995c8819985a5b1959608a1b604482015260640161022c565b505050505050565b600060028235610cdb6040850160208601610fae565b60601b846060016020810190610cf19190610fae565b60601b610d0460a0870160808801610fde565b60c01b610d176060880160408901610fde565b6040805160208101969096526bffffffffffffffffffffffff19948516908601529190921660548401526001600160c01b0319918216606884015260c01b16607082015260780160408051601f1981840301815290829052610d789161107f565b602060405180830381855afa158015610d95573d6000803e3d6000fd5b5050506040513d601f19601f82011682018060405250810190610db891906110dc565b92915050565b600080600080610dcd85610cc5565b600081815260208190526040902054909690955085945092505050565b60008060208385031215610dfd57600080fd5b823567ffffffffffffffff80821115610e1557600080fd5b818501915085601f830112610e2957600080fd5b813581811115610e3857600080fd5b86602060c083028501011115610e4d57600080fd5b60209290920196919550909350505050565b600060a08284031215610e7157600080fd5b50919050565b634e487b7160e01b600052602160045260246000fd5b8151606082019060048110610eb257634e487b7160e01b600052602160045260246000fd5b80835250602083015160208301526040830151604083015292915050565b60008060208385031215610ee357600080fd5b823567ffffffffffffffff80821115610efb57600080fd5b818501915085601f830112610f0f57600080fd5b813581811115610f1e57600080fd5b86602060a083028501011115610e4d57600080fd5b60008060408385031215610f4657600080fd5b50508035926020909101359150565b600060208284031215610f6757600080fd5b5035919050565b60208082526010908201526f39b2b73232b910109e9037b934b3b4b760811b604082015260600190565b634e487b7160e01b600052603260045260246000fd5b600060208284031215610fc057600080fd5b81356001600160a01b0381168114610fd757600080fd5b9392505050565b600060208284031215610ff057600080fd5b813567ffffffffffffffff81168114610fd757600080fd5b634e487b7160e01b600052601160045260246000fd5b600067ffffffffffffffff8083168185168183048111821515161561104557611045611008565b02949350505050565b6000821982111561106157611061611008565b500190565b60006001820161107857611078611008565b5060010190565b6000825160005b818110156110a05760208186018101518583015201611086565b818111156110af576000828501525b509190910192915050565b6000602082840312156110cc57600080fd5b81518015158114610fd757600080fd5b6000602082840312156110ee57600080fd5b505191905056fea2646970667358221220319d89b87a0d5782925310133ed5d12d2ff562b0786611308396804fcad176fc64736f6c634300080f0033" + # PASSWORD is the password used to unlock all accounts/wallets/addresses. PASSWORD="abc" @@ -325,6 +328,9 @@ 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') +echo "Deploying ETHSwap1 contract." +ETH_SWAP_CONTRACT_HASH_V1=$("${NODES_ROOT}/harness-ctl/alpha" "attach --preload ${NODES_ROOT}/harness-ctl/deploy.js --exec deploy(\"${ALPHA_ADDRESS}\",\"${ETH_SWAP_V1}\")" | sed 's/"//g') + echo "Deploying TestToken contract." TEST_TOKEN_CONTRACT_HASH=$("${NODES_ROOT}/harness-ctl/alpha" "attach --preload ${NODES_ROOT}/harness-ctl/deploy.js --exec deploy(\"${ALPHA_ADDRESS}\",\"${TEST_TOKEN}\")" | sed 's/"//g') @@ -362,6 +368,12 @@ cat > "${NODES_ROOT}/eth_swap_contract_address.txt" < "${NODES_ROOT}/eth_swap_contract_address_v1.txt" < "${NODES_ROOT}/test_token_contract_address.txt" < "${NODES_ROOT}/erc20_swap_contract_address.txt" < "${NODES_ROOT}/erc20_swap_contract_address_v1.txt" <