diff --git a/client/asset/driver.go b/client/asset/driver.go index 2f589c21f9..2600a80801 100644 --- a/client/asset/driver.go +++ b/client/asset/driver.go @@ -16,7 +16,8 @@ import ( // networks enables filtering out tokens via the package's SetNetwork. type nettedToken struct { *Token - addrs map[dex.Network]string + erc20NetAddrs map[dex.Network]string + netSupportedAssetVersions map[dex.Network][]uint32 } var ( @@ -85,7 +86,13 @@ func Register(assetID uint32, driver Driver) { // RegisterToken should be called to register tokens. If no nets are specified // the token will be registered for all networks. The user must invoke // SetNetwork to enable net-based filtering of package function output. -func RegisterToken(tokenID uint32, token *dex.Token, walletDef *WalletDefinition, addrs map[dex.Network]string) { +func RegisterToken( + tokenID uint32, + token *dex.Token, + walletDef *WalletDefinition, + erc20NetAddrs map[dex.Network]string, + netSupportedAssetVersions map[dex.Network][]uint32, +) { driversMtx.Lock() defer driversMtx.Unlock() if _, exists := tokens[tokenID]; exists { @@ -101,7 +108,8 @@ func RegisterToken(tokenID uint32, token *dex.Token, walletDef *WalletDefinition Definition: walletDef, // ContractAddress specified in SetNetwork. }, - addrs: addrs, + erc20NetAddrs: erc20NetAddrs, + netSupportedAssetVersions: netSupportedAssetVersions, } } @@ -262,12 +270,13 @@ func UnitInfo(assetID uint32) (dex.UnitInfo, error) { // network. SetNetwork need only be called once during initialization. func SetNetwork(net dex.Network) { for assetID, nt := range tokens { - addr, exists := nt.addrs[net] + addr, exists := nt.erc20NetAddrs[net] if !exists { delete(tokens, assetID) continue } nt.Token.ContractAddress = addr + nt.Token.SupportedAssetVersions = nt.netSupportedAssetVersions[net] } } diff --git a/client/asset/eth/eth.go b/client/asset/eth/eth.go index cf6ea71af4..36cf08be46 100644 --- a/client/asset/eth/eth.go +++ b/client/asset/eth/eth.go @@ -50,14 +50,19 @@ func registerToken(tokenID uint32, desc string) { panic("token " + strconv.Itoa(int(tokenID)) + " not known") } netAddrs := make(map[dex.Network]string) + netAssetVersions := make(map[dex.Network][]uint32, 3) for net, netToken := range token.NetTokens { netAddrs[net] = netToken.Address.String() + netAssetVersions[net] = make([]uint32, 0, 1) + for ver := range netToken.SwapContracts { + netAssetVersions[net] = append(netAssetVersions[net], ver) + } } asset.RegisterToken(tokenID, token.Token, &asset.WalletDefinition{ Type: walletTypeToken, Tab: "Ethereum token", Description: desc, - }, netAddrs) + }, netAddrs, netAssetVersions) } func init() { @@ -1231,11 +1236,13 @@ func (w *ETHWallet) OpenTokenWallet(tokenCfg *asset.TokenConfig) (asset.Wallet, return nil, fmt.Errorf("could not find token with ID %d on network %s", w.assetID, w.net) } + supportedAssetVersions := make([]uint32, 0, 1) contracts := make(map[uint32]common.Address) gases := make(map[uint32]*dexeth.Gases) for ver, c := range netToken.SwapContracts { contracts[ver] = c.Address gases[ver] = &c.Gas + supportedAssetVersions = append(supportedAssetVersions, ver) } aw := &assetWallet{ @@ -1254,7 +1261,7 @@ func (w *ETHWallet) OpenTokenWallet(tokenCfg *asset.TokenConfig) (asset.Wallet, ui: token.UnitInfo, wi: asset.WalletInfo{ Name: token.Name, - SupportedVersions: w.wi.SupportedVersions, + SupportedVersions: supportedAssetVersions, UnitInfo: token.UnitInfo, }, tokenAddr: netToken.Address, @@ -1671,8 +1678,7 @@ func (w *TokenWallet) FundOrder(ord *asset.Order) (asset.Coins, []dex.Bytes, uin dex.BipIDSymbol(w.assetID), ord.MaxFeeRate, w.gasFeeLimit()) } - contractVer := contractVersion(ord.AssetVersion) - approvalStatus, err := w.approvalStatus(contractVer) + approvalStatus, err := w.approvalStatus(ord.AssetVersion) if err != nil { return nil, nil, 0, fmt.Errorf("error getting approval status: %v", err) } @@ -1686,7 +1692,7 @@ func (w *TokenWallet) FundOrder(ord *asset.Order) (asset.Coins, []dex.Bytes, uin return nil, nil, 0, fmt.Errorf("unknown approval status %d", approvalStatus) } - g, err := w.initGasEstimate(int(ord.MaxSwapCount), contractVer, + g, err := w.initGasEstimate(int(ord.MaxSwapCount), contractVersion(ord.AssetVersion), ord.RedeemVersion, ord.RedeemAssetID, ord.MaxFeeRate) if err != nil { return nil, nil, 0, fmt.Errorf("error estimating swap gas: %v", err) @@ -1724,7 +1730,7 @@ func (w *ETHWallet) FundMultiOrder(ord *asset.MultiOrder, maxLock uint64) ([]ass dex.BipIDSymbol(w.assetID), ord.MaxFeeRate, w.gasFeeLimit()) } - g, err := w.initGasEstimate(1, ord.Version, ord.RedeemVersion, ord.RedeemAssetID, ord.MaxFeeRate) + g, err := w.initGasEstimate(1, ord.AssetVersion, ord.RedeemVersion, ord.RedeemAssetID, ord.MaxFeeRate) if err != nil { return nil, nil, 0, fmt.Errorf("error estimating swap gas: %v", err) } @@ -1762,7 +1768,7 @@ func (w *TokenWallet) FundMultiOrder(ord *asset.MultiOrder, maxLock uint64) ([]a dex.BipIDSymbol(w.assetID), ord.MaxFeeRate, w.gasFeeLimit()) } - approvalStatus, err := w.approvalStatus(ord.Version) + approvalStatus, err := w.approvalStatus(ord.AssetVersion) if err != nil { return nil, nil, 0, fmt.Errorf("error getting approval status: %v", err) } @@ -1776,7 +1782,7 @@ func (w *TokenWallet) FundMultiOrder(ord *asset.MultiOrder, maxLock uint64) ([]a return nil, nil, 0, fmt.Errorf("unknown approval status %d", approvalStatus) } - g, err := w.initGasEstimate(1, ord.Version, + g, err := w.initGasEstimate(1, ord.AssetVersion, ord.RedeemVersion, ord.RedeemAssetID, ord.MaxFeeRate) if err != nil { return nil, nil, 0, fmt.Errorf("error estimating swap gas: %v", err) @@ -1936,7 +1942,7 @@ func (w *assetWallet) approvalGas(newGas *big.Int, contractVer uint32) (uint64, approveGas := ourGas.Approve - if approveEst, err := w.estimateApproveGas(newGas); err != nil { + if approveEst, err := w.estimateApproveGas(contractVer, newGas); err != nil { return 0, fmt.Errorf("error estimating approve gas: %v", err) } else if approveEst > approveGas { w.log.Warnf("Approve gas estimate %d is greater than the expected value %d. Using live estimate + 10%%.", approveEst, approveGas) @@ -2495,8 +2501,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() (allowance *big.Int, err error) { - return allowance, w.withTokenContractor(w.assetID, dexeth.ContractVersionERC20, func(c tokenContractor) error { +func (w *assetWallet) tokenAllowance(contractVer uint32) (allowance *big.Int, err error) { + return allowance, w.withTokenContractor(w.assetID, contractVer, func(c tokenContractor) error { allowance, err = c.allowance(w.ctx) return err }) @@ -2523,16 +2529,20 @@ func (w *assetWallet) approveToken(ctx context.Context, amount *big.Int, gasLimi }) } -func (w *assetWallet) approvalStatus(version uint32) (asset.ApprovalStatus, error) { +func (w *assetWallet) approvalStatus(assetVer uint32) (asset.ApprovalStatus, error) { if w.assetID == w.baseChainID { return asset.Approved, nil } + contractVer := contractVersion(assetVer) + // If the result has been cached, return what is in the cache. // The cache is cleared if an approval/unapproval tx is done. w.approvalsMtx.RLock() - if approved, cached := w.approvalCache[version]; cached { - w.approvalsMtx.RUnlock() + approved, cached := w.approvalCache[contractVer] + _, pending := w.pendingApprovals[contractVer] + w.approvalsMtx.RUnlock() + if cached { if approved { return asset.Approved, nil } else { @@ -2540,24 +2550,22 @@ func (w *assetWallet) approvalStatus(version uint32) (asset.ApprovalStatus, erro } } - if _, pending := w.pendingApprovals[version]; pending { - w.approvalsMtx.RUnlock() + if pending { return asset.Pending, nil } - w.approvalsMtx.RUnlock() w.approvalsMtx.Lock() defer w.approvalsMtx.Unlock() - currentAllowance, err := w.tokenAllowance() + currentAllowance, err := w.tokenAllowance(contractVer) if err != nil { return asset.NotApproved, fmt.Errorf("error retrieving current allowance: %w", err) } if currentAllowance.Cmp(unlimitedAllowanceReplenishThreshold) >= 0 { - w.approvalCache[version] = true + w.approvalCache[contractVer] = true return asset.Approved, nil } - w.approvalCache[version] = false + w.approvalCache[contractVer] = false return asset.NotApproved, nil } @@ -2687,16 +2695,14 @@ func (w *TokenWallet) ApprovalFee(assetVer uint32, approve bool) (uint64, error) // ApprovalStatus returns the approval status for each version of the // token's swap contract. func (w *TokenWallet) ApprovalStatus() map[uint32]asset.ApprovalStatus { - versions := w.Info().SupportedVersions - statuses := map[uint32]asset.ApprovalStatus{} - for _, version := range versions { - status, err := w.approvalStatus(version) + for _, assetVer := range w.wi.SupportedVersions { + status, err := w.approvalStatus(assetVer) if err != nil { - w.log.Errorf("error checking approval status for version %d: %w", version, err) + w.log.Errorf("error checking approval status for swap contract version %d: %w", assetVer, err) continue } - statuses[version] = status + statuses[assetVer] = status } return statuses @@ -4456,7 +4462,7 @@ func (w *assetWallet) withTokenContractor(assetID, contractVer uint32, f func(to return w.withContractor(contractVer, func(c contractor) error { tc, is := c.(tokenContractor) if !is { - return fmt.Errorf("contractor for %d %T is not a tokenContractor", assetID, c) + return fmt.Errorf("contractor for %s version %d is not a tokenContractor. type = %T", w.ui.Conventional.Unit, contractVer, c) } return f(tc) }) @@ -4464,23 +4470,13 @@ func (w *assetWallet) withTokenContractor(assetID, contractVer uint32, f func(to // 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, dexeth.ContractVersionERC20, func(c tokenContractor) error { +func (w *assetWallet) estimateApproveGas(contractVer uint32, newGas *big.Int) (gas uint64, err error) { + return gas, w.withTokenContractor(w.assetID, contractVer, func(c tokenContractor) error { gas, err = c.estimateApproveGas(w.ctx, newGas) return err }) } -// estimateTransferGas estimates the gas needed for a token transfer call to an -// ERC20 contract. -// TODO: Delete this and contractor methods. Unused. -func (w *assetWallet) estimateTransferGas(val uint64) (gas uint64, err error) { - return gas, w.withTokenContractor(w.assetID, dexeth.ContractVersionERC20, func(c tokenContractor) error { - gas, err = c.estimateTransferGas(w.ctx, w.evmify(val)) - return err - }) -} - // Can uncomment here and in redeem to test rejected redemption reauthorization. // var firstRedemptionBorked atomic.Bool diff --git a/client/asset/eth/eth_test.go b/client/asset/eth/eth_test.go index bddc67e818..c3af569bfb 100644 --- a/client/asset/eth/eth_test.go +++ b/client/asset/eth/eth_test.go @@ -2189,8 +2189,8 @@ func testFundMultiOrder(t *testing.T, assetID uint32) { tokenBal: uint64(dexeth.GweiFactor), parentBal: uint64(dexeth.GweiFactor), multiOrder: &asset.MultiOrder{ - Version: fromAsset.Version, - MaxFeeRate: fromAsset.MaxFeeRate, + AssetVersion: fromAsset.Version, + MaxFeeRate: fromAsset.MaxFeeRate, Values: []*asset.MultiOrderValue{ { Value: uint64(dexeth.GweiFactor) / 2, @@ -2209,8 +2209,8 @@ func testFundMultiOrder(t *testing.T, assetID uint32) { tokenBal: uint64(dexeth.GweiFactor), parentBal: uint64(dexeth.GweiFactor), multiOrder: &asset.MultiOrder{ - Version: fromAsset.Version, - MaxFeeRate: fromAsset.MaxFeeRate, + AssetVersion: fromAsset.Version, + MaxFeeRate: fromAsset.MaxFeeRate, Values: []*asset.MultiOrderValue{ { Value: uint64(dexeth.GweiFactor) / 2, @@ -2231,8 +2231,8 @@ func testFundMultiOrder(t *testing.T, assetID uint32) { tokenBal: uint64(dexeth.GweiFactor), parentBal: uint64(dexeth.GweiFactor), multiOrder: &asset.MultiOrder{ - Version: fromAsset.Version, - MaxFeeRate: fromAsset.MaxFeeRate, + AssetVersion: fromAsset.Version, + MaxFeeRate: fromAsset.MaxFeeRate, Values: []*asset.MultiOrderValue{ { Value: uint64(dexeth.GweiFactor) / 2, @@ -2254,8 +2254,8 @@ func testFundMultiOrder(t *testing.T, assetID uint32) { tokenBal: uint64(dexeth.GweiFactor), parentBal: uint64(dexeth.GweiFactor), multiOrder: &asset.MultiOrder{ - Version: fromAsset.Version, - MaxFeeRate: fromAsset.MaxFeeRate, + AssetVersion: fromAsset.Version, + MaxFeeRate: fromAsset.MaxFeeRate, Values: []*asset.MultiOrderValue{ { Value: uint64(dexeth.GweiFactor) / 2, @@ -2276,8 +2276,8 @@ func testFundMultiOrder(t *testing.T, assetID uint32) { tokenBal: uint64(dexeth.GweiFactor), parentBal: uint64(dexeth.GweiFactor), multiOrder: &asset.MultiOrder{ - Version: fromAsset.Version, - MaxFeeRate: fromAsset.MaxFeeRate, + AssetVersion: fromAsset.Version, + MaxFeeRate: fromAsset.MaxFeeRate, Values: []*asset.MultiOrderValue{ { Value: uint64(dexeth.GweiFactor) / 2, @@ -2298,8 +2298,8 @@ func testFundMultiOrder(t *testing.T, assetID uint32) { tokenBal: uint64(dexeth.GweiFactor) - 1, parentBal: uint64(dexeth.GweiFactor), multiOrder: &asset.MultiOrder{ - Version: fromAsset.Version, - MaxFeeRate: fromAsset.MaxFeeRate, + AssetVersion: fromAsset.Version, + MaxFeeRate: fromAsset.MaxFeeRate, Values: []*asset.MultiOrderValue{ { Value: uint64(dexeth.GweiFactor) / 2, @@ -2319,8 +2319,8 @@ func testFundMultiOrder(t *testing.T, assetID uint32) { tokenBal: uint64(dexeth.GweiFactor), parentBal: swapGas * 4 * fromAsset.MaxFeeRate, multiOrder: &asset.MultiOrder{ - Version: fromAsset.Version, - MaxFeeRate: fromAsset.MaxFeeRate, + AssetVersion: fromAsset.Version, + MaxFeeRate: fromAsset.MaxFeeRate, Values: []*asset.MultiOrderValue{ { Value: uint64(dexeth.GweiFactor) / 2, @@ -2339,8 +2339,8 @@ func testFundMultiOrder(t *testing.T, assetID uint32) { tokenBal: uint64(dexeth.GweiFactor), parentBal: swapGas*4*fromAsset.MaxFeeRate - 1, multiOrder: &asset.MultiOrder{ - Version: fromAsset.Version, - MaxFeeRate: fromAsset.MaxFeeRate, + AssetVersion: fromAsset.Version, + MaxFeeRate: fromAsset.MaxFeeRate, Values: []*asset.MultiOrderValue{ { Value: uint64(dexeth.GweiFactor) / 2, diff --git a/client/asset/interface.go b/client/asset/interface.go index 3f3b81e546..bd4df0d192 100644 --- a/client/asset/interface.go +++ b/client/asset/interface.go @@ -300,8 +300,9 @@ type WalletDefinition struct { // Token combines the generic dex.Token with a WalletDefinition. type Token struct { *dex.Token - Definition *WalletDefinition `json:"definition"` - ContractAddress string `json:"contractAddress"` // Set in SetNetwork + Definition *WalletDefinition `json:"definition"` + ContractAddress string `json:"contractAddress"` // Set in SetNetwork + SupportedAssetVersions []uint32 `json:"supportedAssetVersions"` } // WalletInfo is auxiliary information about an ExchangeWallet. @@ -1440,10 +1441,10 @@ type MultiOrderValue struct { // MultiOrder is order details needed for FundMultiOrder. type MultiOrder struct { - // Version is the asset version of the "from" asset with the init + // AssetVersion is the asset version of the "from" asset with the init // transaction. - Version uint32 - Values []*MultiOrderValue + AssetVersion uint32 + Values []*MultiOrderValue // MaxFeeRate is the largest possible fee rate for the init transaction (of // this "from" asset) specific to and provided by a particular server, and // is used to calculate the funding required to cover fees. diff --git a/client/asset/polygon/polygon.go b/client/asset/polygon/polygon.go index 4574a79dbe..50ace5d1df 100644 --- a/client/asset/polygon/polygon.go +++ b/client/asset/polygon/polygon.go @@ -27,14 +27,19 @@ func registerToken(tokenID uint32, desc string, nets ...dex.Network) { panic("token " + strconv.Itoa(int(tokenID)) + " not known") } netAddrs := make(map[dex.Network]string) + netVersions := make(map[dex.Network][]uint32, 3) for net, netToken := range token.NetTokens { netAddrs[net] = netToken.Address.String() + netVersions[net] = make([]uint32, 0, 1) + for ver := range netToken.SwapContracts { + netVersions[net] = append(netVersions[net], ver) + } } asset.RegisterToken(tokenID, token.Token, &asset.WalletDefinition{ Type: walletTypeToken, Tab: "Polygon token", Description: desc, - }, netAddrs) + }, netAddrs, netVersions) } func init() { diff --git a/client/asset/zec/regnet_test.go b/client/asset/zec/regnet_test.go index 419949426e..6088018d1d 100644 --- a/client/asset/zec/regnet_test.go +++ b/client/asset/zec/regnet_test.go @@ -266,7 +266,7 @@ func TestMultiSplit(t *testing.T) { // All funds should be transparent now. multiFund := &asset.MultiOrder{ - Version: version, + AssetVersion: version, Values: []*asset.MultiOrderValue{ {Value: v0, MaxSwapCount: 1}, {Value: v1, MaxSwapCount: 2}, diff --git a/client/cmd/bwctl/simnet-setup.sh b/client/cmd/bwctl/simnet-setup.sh index aaf6a888d8..05a6f99dae 100755 --- a/client/cmd/bwctl/simnet-setup.sh +++ b/client/cmd/bwctl/simnet-setup.sh @@ -3,7 +3,7 @@ # dcrdex, bisonw, and the wallet simnet harnesses should all be running before # calling this script. # -# bisonw can be built with -ldflags "-X 'decred.org/dcrdex/dex.testLockTimeTaker=30s' -X 'decred.org/dcrdex/dex.testLockTimeMaker=1m'" +# bisonw can be built with -ldflags "-X 'decred.org/dcrdex/dex.testLockTimeTaker=3m' -X 'decred.org/dcrdex/dex.testLockTimeMaker=6m'" # in order to set simnet locktimes. set +e diff --git a/client/cmd/simnet-trade-tests/run b/client/cmd/simnet-trade-tests/run index 4303d20354..f2bf586ea9 100755 --- a/client/cmd/simnet-trade-tests/run +++ b/client/cmd/simnet-trade-tests/run @@ -2,8 +2,8 @@ set -e go build -tags harness -ldflags \ - "-X 'decred.org/dcrdex/dex.testLockTimeTaker=1m' \ - -X 'decred.org/dcrdex/dex.testLockTimeMaker=2m'" + "-X 'decred.org/dcrdex/dex.testLockTimeTaker=3m' \ + -X 'decred.org/dcrdex/dex.testLockTimeMaker=6m'" case $1 in diff --git a/client/core/core.go b/client/core/core.go index 73347f2c4e..d44666082b 100644 --- a/client/core/core.go +++ b/client/core/core.go @@ -6325,7 +6325,7 @@ func (c *Core) prepareMultiTradeRequests(pw []byte, form *MultiTradeForm) ([]*tr } allCoins, allRedeemScripts, fundingFees, err := fromWallet.FundMultiOrder(&asset.MultiOrder{ - Version: assetConfigs.fromAsset.Version, + AssetVersion: assetConfigs.fromAsset.Version, Values: orderValues, MaxFeeRate: assetConfigs.fromAsset.MaxFeeRate, FeeSuggestion: c.feeSuggestion(dc, assetConfigs.fromAsset.ID), diff --git a/client/webserver/site/src/css/main.scss b/client/webserver/site/src/css/main.scss index 60db707897..224193a193 100644 --- a/client/webserver/site/src/css/main.scss +++ b/client/webserver/site/src/css/main.scss @@ -12,14 +12,11 @@ body { bottom: 0; left: 0; right: 0; + display: flex; flex-direction: column; justify-content: flex-start; background-color: var(--body-bg); color: var(--text-color); - - &.loaded { - display: flex; - } } header#header { diff --git a/client/webserver/site/src/html/bodybuilder.tmpl b/client/webserver/site/src/html/bodybuilder.tmpl index 4c65135dc6..690e3168f7 100644 --- a/client/webserver/site/src/html/bodybuilder.tmpl +++ b/client/webserver/site/src/html/bodybuilder.tmpl @@ -9,14 +9,9 @@ {{.Title}} - - +