From 392c21d50bdd48d7b4d3f4c792d76cfe38d0e559 Mon Sep 17 00:00:00 2001 From: Alex Freska Date: Tue, 5 Nov 2024 09:46:44 -0500 Subject: [PATCH] refactor(renterd): remove allowance --- .changeset/big-sloths-wave.md | 5 + .changeset/happy-scissors-approve.md | 5 + .changeset/selfish-books-jog.md | 5 + .changeset/silent-llamas-explode.md | 5 + .changeset/sixty-fireants-kick.md | 5 + .changeset/wet-planets-pull.md | 5 + .changeset/wise-rabbits-film.md | 5 + .../src/fixtures/configResetSettings.ts | 36 ++- apps/renterd-e2e/src/specs/config.spec.ts | 209 ++++--------- apps/renterd-e2e/src/specs/configTips.spec.ts | 253 ++++++---------- .../src/specs/externalData.spec.ts | 10 +- apps/renterd-e2e/src/specs/files.spec.ts | 5 - .../src/specs/recommendations.spec.ts | 2 - .../renterd/components/Config/ConfigStats.tsx | 102 ------- apps/renterd/components/Config/HangingNav.tsx | 22 ++ .../components/Config/HangingNavItem.tsx | 63 ++++ apps/renterd/components/Config/Layout.tsx | 6 +- .../components/Config/RebalancePrices.tsx | 241 +++++++++++++++ .../components/Config/Recommendations.tsx | 233 ++++++--------- .../components/Config/SpendingEstimate.tsx | 147 +++++++++ apps/renterd/components/Config/index.tsx | 38 +-- .../Config/useEstimatedSpending.tsx | 28 -- apps/renterd/components/OnboardingBar.tsx | 16 +- apps/renterd/contexts/alerts/data.tsx | 12 - .../contexts/config/fieldTips/Allowance.tsx | 177 ----------- .../config/fieldTips/MaxDownloadPrice.tsx | 52 +--- .../config/fieldTips/MaxStoragePrice.tsx | 53 +--- .../config/fieldTips/MaxUploadPrice.tsx | 55 +--- .../renterd/contexts/config/fieldTips/Tip.tsx | 26 -- apps/renterd/contexts/config/fields.tsx | 58 ---- ...riveAllowance.spec.ts => spending.spec.ts} | 200 ++++++------- .../{deriveAllowance.ts => spending.ts} | 70 +++-- ...veAllowanceConfig.ts => spendingConfig.ts} | 2 +- .../renterd/contexts/config/transform.spec.ts | 33 -- apps/renterd/contexts/config/transformDown.ts | 33 +- apps/renterd/contexts/config/transformUp.ts | 16 - apps/renterd/contexts/config/types.ts | 3 - .../config/useAllowanceDerivedPricing.tsx | 282 ------------------ .../config/useAutopilotEvaluations.tsx | 60 ++-- .../useEnabledPricingValuesInSiacoin.tsx | 76 +++++ .../contexts/config/useSpendingEstimate.tsx | 62 ++++ libs/design-system/src/core/SiacoinField.tsx | 8 +- libs/renterd-types/src/types.ts | 8 - libs/units/src/storage.ts | 2 +- 44 files changed, 1106 insertions(+), 1628 deletions(-) create mode 100644 .changeset/big-sloths-wave.md create mode 100644 .changeset/happy-scissors-approve.md create mode 100644 .changeset/selfish-books-jog.md create mode 100644 .changeset/silent-llamas-explode.md create mode 100644 .changeset/sixty-fireants-kick.md create mode 100644 .changeset/wet-planets-pull.md create mode 100644 .changeset/wise-rabbits-film.md delete mode 100644 apps/renterd/components/Config/ConfigStats.tsx create mode 100644 apps/renterd/components/Config/HangingNav.tsx create mode 100644 apps/renterd/components/Config/HangingNavItem.tsx create mode 100644 apps/renterd/components/Config/RebalancePrices.tsx create mode 100644 apps/renterd/components/Config/SpendingEstimate.tsx delete mode 100644 apps/renterd/components/Config/useEstimatedSpending.tsx delete mode 100644 apps/renterd/contexts/config/fieldTips/Allowance.tsx rename apps/renterd/contexts/config/{deriveAllowance.spec.ts => spending.spec.ts} (76%) rename apps/renterd/contexts/config/{deriveAllowance.ts => spending.ts} (70%) rename apps/renterd/contexts/config/{deriveAllowanceConfig.ts => spendingConfig.ts} (72%) delete mode 100644 apps/renterd/contexts/config/useAllowanceDerivedPricing.tsx create mode 100644 apps/renterd/contexts/config/useEnabledPricingValuesInSiacoin.tsx create mode 100644 apps/renterd/contexts/config/useSpendingEstimate.tsx diff --git a/.changeset/big-sloths-wave.md b/.changeset/big-sloths-wave.md new file mode 100644 index 000000000..d907fd002 --- /dev/null +++ b/.changeset/big-sloths-wave.md @@ -0,0 +1,5 @@ +--- +'@siafoundation/design-system': minor +--- + +SiacoinField border is no longer blue in readOnly state. diff --git a/.changeset/happy-scissors-approve.md b/.changeset/happy-scissors-approve.md new file mode 100644 index 000000000..5208e0200 --- /dev/null +++ b/.changeset/happy-scissors-approve.md @@ -0,0 +1,5 @@ +--- +'@siafoundation/design-system': minor +--- + +SiacoinField now has a unitsFiatPostfix prop. diff --git a/.changeset/selfish-books-jog.md b/.changeset/selfish-books-jog.md new file mode 100644 index 000000000..8bc5f4a81 --- /dev/null +++ b/.changeset/selfish-books-jog.md @@ -0,0 +1,5 @@ +--- +'@siafoundation/renterd-types': minor +--- + +Allowance was removed from autopilot contracts config API. diff --git a/.changeset/silent-llamas-explode.md b/.changeset/silent-llamas-explode.md new file mode 100644 index 000000000..3a1db3ec8 --- /dev/null +++ b/.changeset/silent-llamas-explode.md @@ -0,0 +1,5 @@ +--- +'renterd': minor +--- + +The allowance fitting and price fitting features were removed. diff --git a/.changeset/sixty-fireants-kick.md b/.changeset/sixty-fireants-kick.md new file mode 100644 index 000000000..3dc6cd578 --- /dev/null +++ b/.changeset/sixty-fireants-kick.md @@ -0,0 +1,5 @@ +--- +'renterd': minor +--- + +The configuration page has a new spending estimate widget that includes an option to rebalance prices within the current estimate. diff --git a/.changeset/wet-planets-pull.md b/.changeset/wet-planets-pull.md new file mode 100644 index 000000000..90d024ee9 --- /dev/null +++ b/.changeset/wet-planets-pull.md @@ -0,0 +1,5 @@ +--- +'@siafoundation/renterd-types': minor +--- + +The autopilots key was removed from the pinned settings API. diff --git a/.changeset/wise-rabbits-film.md b/.changeset/wise-rabbits-film.md new file mode 100644 index 000000000..d68fe5be5 --- /dev/null +++ b/.changeset/wise-rabbits-film.md @@ -0,0 +1,5 @@ +--- +'renterd': minor +--- + +The allowance concept was removed. diff --git a/apps/renterd-e2e/src/fixtures/configResetSettings.ts b/apps/renterd-e2e/src/fixtures/configResetSettings.ts index 03afd6f7c..1499cdbdf 100644 --- a/apps/renterd-e2e/src/fixtures/configResetSettings.ts +++ b/apps/renterd-e2e/src/fixtures/configResetSettings.ts @@ -24,10 +24,6 @@ export const configResetAllSettings = step( await fillTextInputByName(page, 'storageTB', '1') await fillTextInputByName(page, 'uploadTBMonth', '1') await fillTextInputByName(page, 'downloadTBMonth', '1') - await setSwitchByLabel(page, 'shouldPinAllowance', true) - await fillTextInputByName(page, 'allowanceMonthPinned', '10') - await setSwitchByLabel(page, 'shouldPinAllowance', false) - await fillTextInputByName(page, 'allowanceMonth', '21000') await fillTextInputByName(page, 'periodWeeks', '6') await fillTextInputByName(page, 'renewWindowWeeks', '2') await fillTextInputByName(page, 'amountHosts', '3') @@ -85,18 +81,20 @@ export const configResetAllSettings = step( export const configResetBasicSettings = step( 'config reset basic settings', - async ({ page }: { page: Page }) => { + async (page: Page) => { await navigateToConfig({ page }) - await setViewMode({ page, state: 'basic' }) + await setViewMode({ page, state: 'advanced' }) await fillTextInputByName(page, 'storageTB', '7') await fillTextInputByName(page, 'uploadTBMonth', '7') await fillTextInputByName(page, 'downloadTBMonth', '7') - await fillTextInputByName(page, 'allowanceMonth', '1000') - await fillTextInputByName(page, 'maxStoragePriceTBMonth', '3000') - await fillTextInputByName(page, 'maxUploadPriceTB', '3000') - await fillTextInputByName(page, 'maxDownloadPriceTB', '3000') + await configFillEstimatesSiacoin(page) + + await fillTextInputByName(page, 'minShards', '1') + await fillTextInputByName(page, 'totalShards', '3') + + await setViewMode({ page, state: 'basic' }) // save await clickIfEnabledAndWait( @@ -106,3 +104,21 @@ export const configResetBasicSettings = step( await clearToasts({ page }) } ) + +export const configFillEstimatesSiacoin = step( + 'config fill estimates and prices', + async (page: Page) => { + await fillTextInputByName(page, 'maxStoragePriceTBMonth', '3000') + await fillTextInputByName(page, 'maxUploadPriceTB', '3000') + await fillTextInputByName(page, 'maxDownloadPriceTB', '3000') + } +) + +export const configFillEstimatesFiat = step( + 'config fill estimates fiat', + async (page: Page) => { + await fillTextInputByName(page, 'maxStoragePriceTBMonthPinned', '11.832136') + await fillTextInputByName(page, 'maxUploadPriceTBPinned', '11.832136') + await fillTextInputByName(page, 'maxDownloadPriceTBPinned', '11.832136') + } +) diff --git a/apps/renterd-e2e/src/specs/config.spec.ts b/apps/renterd-e2e/src/specs/config.spec.ts index c80a0dc3b..54548d83d 100644 --- a/apps/renterd-e2e/src/specs/config.spec.ts +++ b/apps/renterd-e2e/src/specs/config.spec.ts @@ -33,12 +33,11 @@ test('field change and save behaviours', async ({ page }) => { await fillTextInputByName(page, 'maxStoragePriceTBMonth', '7000') await fillTextInputByName(page, 'maxUploadPriceTB', '7000') await fillTextInputByName(page, 'maxDownloadPriceTB', '7000') - await fillTextInputByName(page, 'allowanceMonth', '7000') // Correct number of changes is shown. - await expect(page.getByText('7 changes')).toBeVisible() + await expect(page.getByText('6 changes')).toBeVisible() await page.getByText('Save changes').click() - await expect(page.getByText('7 changes')).toBeHidden() + await expect(page.getByText('6 changes')).toBeHidden() // Values are the same after save. await expectTextInputByName(page, 'storageTB', '7') @@ -47,70 +46,58 @@ test('field change and save behaviours', async ({ page }) => { await expectTextInputByName(page, 'maxStoragePriceTBMonth', '7,000') await expectTextInputByName(page, 'maxUploadPriceTB', '7,000') await expectTextInputByName(page, 'maxDownloadPriceTB', '7,000') - await expectTextInputByName(page, 'allowanceMonth', '7,000') - // Estimate is based off the allowance. - const estimateParts = [ - 'Estimate', - '1,000 SC', - '($3.94)', - 'per TB/month with 3.0x redundancy', - '7,000 SC', - '($27.61)', - 'to store 7.00 TB/month with 3.0x redundancy', - ] + await expect( + page + .getByTestId('spendingEstimate') + .locator('input[name=estimatedSpendingPerMonth]') + ).toHaveValue('228,667') + await expect( + page + .getByTestId('spendingEstimate') + .locator('input[name=estimatedSpendingPerMonth-fiat]') + ).toHaveValue('$902') + + await fillSelectInputByName(page, 'spendingEstimateMode', 'tb') - for (const part of estimateParts) { - await expect(page.getByText(part)).toBeVisible() - } + await expect( + page + .getByTestId('spendingEstimate') + .locator('input[name=estimatedSpendingPerTBPerMonth]') + ).toHaveValue('32,667') + await expect( + page + .getByTestId('spendingEstimate') + .locator('input[name=estimatedSpendingPerTBPerMonth-fiat]') + ).toHaveValue('$129') }) -test('set max prices to fit current allowance', async ({ page }) => { +test('set max prices to fit current spending estimate', async ({ page }) => { // Reset state. await navigateToConfig({ page }) await setViewMode({ page, state: 'basic' }) - // Set all values that affect the pricing calculation. - await fillTextInputByName(page, 'storageTB', '10') - await fillTextInputByName(page, 'uploadTBMonth', '10') - await fillTextInputByName(page, 'downloadTBMonth', '4') - await fillTextInputByName(page, 'allowanceMonth', '95,000') - await page.getByText('Set max prices to fit current allowance').click() - - // The following values are fit to the allowance. - await expectTextInputByName(page, 'maxStoragePriceTBMonth', '3,352.941176') - await expectTextInputByName(page, 'maxUploadPriceTB', '838.235294') - await expectTextInputByName(page, 'maxDownloadPriceTB', '4,191.176471') + const spendingEstimate = page.getByTestId('spendingEstimate') + const rebalanceButton = spendingEstimate.getByLabel('rebalance prices') // If a component of the fit calculation is missing, it should not be shown. await fillTextInputByName(page, 'storageTB', '') - await expect( - page.getByText('Set max prices to fit current allowance') - ).toBeDisabled() -}) - -test('set allowance to fit current max prices', async ({ page }) => { - // Reset state. - await navigateToConfig({ page }) - await setViewMode({ page, state: 'basic' }) + await expect(spendingEstimate).toBeHidden() - // Set all values that affect the allowance calculation. + // Set all values that affect the pricing calculation. await fillTextInputByName(page, 'storageTB', '10') await fillTextInputByName(page, 'uploadTBMonth', '10') await fillTextInputByName(page, 'downloadTBMonth', '4') - await fillTextInputByName(page, 'maxStoragePriceTBMonth', '1500') - await fillTextInputByName(page, 'maxUploadPriceTB', '1000') - await fillTextInputByName(page, 'maxDownloadPriceTB', '5000') - await page.getByText('Set allowance to fit current max prices').click() - // The following allowance is fit to the max prices. - await expectTextInputByName(page, 'allowanceMonth', '63,333') + await rebalanceButton.click() - // If a component of the fit calculation is missing, it should not be shown. - await fillTextInputByName(page, 'storageTB', '') - await expect( - page.getByText('Set allowance to fit current max prices') - ).toBeDisabled() + // The following values are fit to the estimated spending. + await expectTextInputByName(page, 'maxStoragePriceTBMonth', '4,517.647059') + await expectTextInputByName(page, 'maxUploadPriceTB', '1,129.411765') + await expectTextInputByName(page, 'maxDownloadPriceTB', '5,647.058824') + + // If the prices fit the estimate the button should be disabled. + await expect(rebalanceButton).toBeDisabled() }) test('should show warning if pinning is not fully configured', async ({ @@ -120,132 +107,64 @@ test('should show warning if pinning is not fully configured', async ({ await navigateToConfig({ page }) await setViewMode({ page, state: 'basic' }) - await setSwitchByLabel(page, 'shouldPinAllowance', true) await setSwitchByLabel(page, 'shouldPinMaxStoragePrice', true) + await setSwitchByLabel(page, 'shouldPinMaxUploadPrice', true) + await setSwitchByLabel(page, 'shouldPinMaxDownloadPrice', true) await fillSelectInputByName(page, 'pinnedCurrency', '') await expect( page - .getByTestId('allowanceMonthGroup') + .getByTestId('maxStoragePriceTBMonthGroup') .getByText('Select a pinned currency') ).toBeVisible() await expect( page - .getByTestId('maxStoragePriceTBMonthGroup') + .getByTestId('maxUploadPriceTBGroup') .getByText('Select a pinned currency') ).toBeVisible() -}) - -test('set max prices with pinned fields to fit current allowance', async ({ - page, -}) => { - // Reset state. - await navigateToConfig({ page }) - await setViewMode({ page, state: 'basic' }) - await setSwitchByLabel(page, 'shouldPinMaxStoragePrice', true) - await setSwitchByLabel(page, 'shouldPinMaxUploadPrice', true) - await setSwitchByLabel(page, 'shouldPinMaxDownloadPrice', true) - - // Set all values that affect the pricing calculation. - await fillTextInputByName(page, 'storageTB', '10') - await fillTextInputByName(page, 'uploadTBMonth', '10') - await fillTextInputByName(page, 'downloadTBMonth', '4') - await fillTextInputByName(page, 'allowanceMonth', '95,000') - await expect( - page.getByText('Current pricing may not fit allowance') - ).toBeVisible() - await page.getByText('Set max prices to fit current allowance').click() - await expect(page.getByText('Current pricing fits allowance')).toBeVisible() - - // The following values are fit to the allowance. - await expectTextInputByName(page, 'maxStoragePriceTBMonthPinned', '$13.22') - await expectTextInputByName(page, 'maxUploadPriceTBPinned', '$3.31') - await expectTextInputByName(page, 'maxDownloadPriceTBPinned', '$16.53') - - await setSwitchByLabel(page, 'shouldPinMaxDownloadPrice', false) await expect( - page.getByText('Current pricing may not fit allowance') + page + .getByTestId('maxDownloadPriceTBGroup') + .getByText('Select a pinned currency') ).toBeVisible() - await page.getByText('Set max prices to fit current allowance').click() - await expect(page.getByText('Current pricing fits allowance')).toBeVisible() - - await expectTextInputByName(page, 'maxStoragePriceTBMonthPinned', '$13.22') - await expectTextInputByName(page, 'maxUploadPriceTBPinned', '$3.31') - await expectTextInputByName(page, 'maxDownloadPriceTB', '4,191.176471') }) -test('set max prices with pinned fields to fit pinned allowance', async ({ +test('set max prices with pinned fields to fit current spending estimate', async ({ page, }) => { // Reset state. await navigateToConfig({ page }) await setViewMode({ page, state: 'basic' }) - await setSwitchByLabel(page, 'shouldPinAllowance', true) await setSwitchByLabel(page, 'shouldPinMaxStoragePrice', true) await setSwitchByLabel(page, 'shouldPinMaxUploadPrice', true) + await setSwitchByLabel(page, 'shouldPinMaxDownloadPrice', true) // Set all values that affect the pricing calculation. await fillTextInputByName(page, 'storageTB', '10') await fillTextInputByName(page, 'uploadTBMonth', '10') await fillTextInputByName(page, 'downloadTBMonth', '4') - await fillTextInputByName(page, 'allowanceMonthPinned', '100') - await expect( - page.getByText('Current pricing may not fit allowance') - ).toBeVisible() - await page.getByText('Set max prices to fit current allowance').click() - await expect(page.getByText('Current pricing fits allowance')).toBeVisible() - // The following values are fit to the allowance. - await expectTextInputByName(page, 'maxStoragePriceTBMonthPinned', '$3.53') - await expectTextInputByName(page, 'maxUploadPriceTBPinned', '$0.88') - await expectTextInputByName(page, 'maxDownloadPriceTB', '1,118.588756') -}) + const spendingEstimate = page.getByTestId('spendingEstimate') + const rebalanceButton = spendingEstimate.getByLabel('rebalance prices') -test('set max prices via individual field tips', async ({ page }) => { - // Reset state. - await navigateToConfig({ page }) - await setViewMode({ page, state: 'basic' }) - await setSwitchByLabel(page, 'shouldPinAllowance', true) - await setSwitchByLabel(page, 'shouldPinMaxStoragePrice', true) - await setSwitchByLabel(page, 'shouldPinMaxUploadPrice', true) + await expect(rebalanceButton).toBeEnabled() + await rebalanceButton.click() + await expect(rebalanceButton).toBeDisabled() - // Set all values that affect the pricing calculation. - await fillTextInputByName(page, 'storageTB', '10') - await fillTextInputByName(page, 'uploadTBMonth', '10') - await fillTextInputByName(page, 'downloadTBMonth', '4') - await fillTextInputByName(page, 'allowanceMonthPinned', '100') - await expect( - page.getByText('Current pricing may not fit allowance') - ).toBeVisible() - const fitButton = page - .getByTestId('maxStoragePriceTBMonthGroup') - .getByLabel('Fit current allowance') - // TODO: remove the need to click twice, there is some sort of glitch after toggling the pinning switch. - await clickTwice(fitButton) - await page - .getByTestId('maxStoragePriceTBMonthGroup') - .getByLabel('Fit current allowance') - .click() - await expect( - page.getByText('Current pricing may not fit allowance') - ).toBeVisible() - await page - .getByTestId('maxUploadPriceTBGroup') - .getByLabel('Fit current allowance') - .click() - await expect( - page.getByText('Current pricing may not fit allowance') - ).toBeVisible() - await page - .getByTestId('maxDownloadPriceTBGroup') - .getByLabel('Fit current allowance') - .click() - await expect(page.getByText('Current pricing fits allowance')).toBeVisible() + // The following values are fit to the spending estimate. + await expectTextInputByName(page, 'maxStoragePriceTBMonthPinned', '$7.53') + await expectTextInputByName(page, 'maxUploadPriceTBPinned', '$1.88') + await expectTextInputByName(page, 'maxDownloadPriceTBPinned', '$9.41') + + // Unpin one field while leaving the other two pinned. + await setSwitchByLabel(page, 'shouldPinMaxDownloadPrice', false) + await expect(rebalanceButton).toBeEnabled() + await rebalanceButton.click() + await expect(rebalanceButton).toBeDisabled() - // The following values are fit to the allowance. - await expectTextInputByName(page, 'maxStoragePriceTBMonthPinned', '$3.53') - await expectTextInputByName(page, 'maxUploadPriceTBPinned', '$0.88') - await expectTextInputByName(page, 'maxDownloadPriceTB', '1,118.588756') + await expectTextInputByName(page, 'maxStoragePriceTBMonthPinned', '$7.76') + await expectTextInputByName(page, 'maxUploadPriceTBPinned', '$1.94') + await expectTextInputByName(page, 'maxDownloadPriceTB', '2,458.147059') }) test('pinned currency and app display currency can be different', async ({ diff --git a/apps/renterd-e2e/src/specs/configTips.spec.ts b/apps/renterd-e2e/src/specs/configTips.spec.ts index 25f84bc52..1cdc54742 100644 --- a/apps/renterd-e2e/src/specs/configTips.spec.ts +++ b/apps/renterd-e2e/src/specs/configTips.spec.ts @@ -1,17 +1,22 @@ import { test, expect } from '@playwright/test' -import { navigateToConfig } from '../fixtures/navigate' import { afterTest, beforeTest } from '../fixtures/beforeTest' import { clickTwice, setSwitchByLabel, - setViewMode, expectTextInputByName, - fillTextInputByName, setCurrencyDisplay, + setViewMode, } from '@siafoundation/e2e' +import { + configResetBasicSettings, + configFillEstimatesFiat, + configFillEstimatesSiacoin, +} from '../fixtures/configResetSettings' test.beforeEach(async ({ page }) => { + test.setTimeout(150_000) await beforeTest(page) + await configResetBasicSettings(page) }) test.afterEach(async () => { @@ -19,15 +24,8 @@ test.afterEach(async () => { }) test('field tips for storage', async ({ page }) => { - await navigateToConfig({ page }) - await setViewMode({ page, state: 'advanced' }) - - await fillTextInputByName(page, 'allowanceMonth', '7000') - await fillTextInputByName(page, 'storageTB', '7') - await fillTextInputByName(page, 'uploadTBMonth', '7') - await fillTextInputByName(page, 'downloadTBMonth', '7') - await fillTextInputByName(page, 'minShards', '1') - await fillTextInputByName(page, 'totalShards', '3') + const spendingEstimate = page.getByTestId('spendingEstimate') + const rebalanceButton = spendingEstimate.getByLabel('rebalance prices') // Storage siacoin. await setCurrencyDisplay(page, 'bothPreferSc', 'usd') @@ -38,13 +36,10 @@ test('field tips for storage', async ({ page }) => { await expect(storageNetworkAverage).toBeVisible() await clickTwice(storageNetworkAverage) await expectTextInputByName(page, 'maxStoragePriceTBMonth', '341') - let storageFitCurrentAllowance = page - .getByTestId('maxStoragePriceTBMonthGroup') - .getByLabel('Fit current allowance') - .getByText('300') - await expect(storageFitCurrentAllowance).toBeVisible() - await clickTwice(storageFitCurrentAllowance) - await expectTextInputByName(page, 'maxStoragePriceTBMonth', '300') + await rebalanceButton.click() + await expectTextInputByName(page, 'maxStoragePriceTBMonth', '2,604.6') + + await configFillEstimatesSiacoin(page) await setCurrencyDisplay(page, 'bothPreferFiat', 'usd') let storageNetworkAverageFiat = page .getByTestId('maxStoragePriceTBMonthGroup') @@ -53,13 +48,10 @@ test('field tips for storage', async ({ page }) => { await expect(storageNetworkAverageFiat).toBeVisible() await clickTwice(storageNetworkAverageFiat) await expectTextInputByName(page, 'maxStoragePriceTBMonth', '341') - let storageFitCurrentAllowanceFiat = page - .getByTestId('maxStoragePriceTBMonthGroup') - .getByLabel('Fit current allowance') - .getByText('$1.18') - await expect(storageFitCurrentAllowanceFiat).toBeVisible() - await clickTwice(storageFitCurrentAllowanceFiat) - await expectTextInputByName(page, 'maxStoragePriceTBMonth', '300') + await rebalanceButton.click() + await expectTextInputByName(page, 'maxStoragePriceTBMonth', '2,604.6') + + await configFillEstimatesSiacoin(page) await setCurrencyDisplay(page, 'bothPreferFiat', 'jpy') let storageNetworkAverageFiatJPY = page .getByTestId('maxStoragePriceTBMonthGroup') @@ -68,20 +60,16 @@ test('field tips for storage', async ({ page }) => { await expect(storageNetworkAverageFiatJPY).toBeVisible() await clickTwice(storageNetworkAverageFiatJPY) await expectTextInputByName(page, 'maxStoragePriceTBMonth', '341') - let storageFitCurrentAllowanceFiatJPY = page - .getByTestId('maxStoragePriceTBMonthGroup') - .getByLabel('Fit current allowance') - .getByText('¥218.33') - await expect(storageFitCurrentAllowanceFiatJPY).toBeVisible() - await clickTwice(storageFitCurrentAllowanceFiatJPY) - await expectTextInputByName(page, 'maxStoragePriceTBMonth', '300') + await rebalanceButton.click() + await expectTextInputByName(page, 'maxStoragePriceTBMonth', '2,604.6') // Fiat. - await setSwitchByLabel(page, 'shouldPinAllowance', true) await setSwitchByLabel(page, 'shouldPinMaxStoragePrice', true) - await fillTextInputByName(page, 'allowanceMonthPinned', '30') + await setSwitchByLabel(page, 'shouldPinMaxUploadPrice', true) + await setSwitchByLabel(page, 'shouldPinMaxDownloadPrice', true) // Storage fiat. + await configFillEstimatesFiat(page) await setCurrencyDisplay(page, 'bothPreferSc', 'usd') storageNetworkAverage = page .getByTestId('maxStoragePriceTBMonthGroup') @@ -90,13 +78,10 @@ test('field tips for storage', async ({ page }) => { await expect(storageNetworkAverage).toBeVisible() await clickTwice(storageNetworkAverage) await expectTextInputByName(page, 'maxStoragePriceTBMonthPinned', '$1.34') - storageFitCurrentAllowance = page - .getByTestId('maxStoragePriceTBMonthGroup') - .getByLabel('Fit current allowance') - .getByText('326') - await expect(storageFitCurrentAllowance).toBeVisible() - await clickTwice(storageFitCurrentAllowance) - await expectTextInputByName(page, 'maxStoragePriceTBMonthPinned', '$1.29') + await rebalanceButton.click() + await expectTextInputByName(page, 'maxStoragePriceTBMonthPinned', '$10.27') + + await configFillEstimatesFiat(page) await setCurrencyDisplay(page, 'bothPreferFiat', 'usd') storageNetworkAverageFiat = page .getByTestId('maxStoragePriceTBMonthGroup') @@ -105,13 +90,10 @@ test('field tips for storage', async ({ page }) => { await expect(storageNetworkAverageFiat).toBeVisible() await clickTwice(storageNetworkAverageFiat) await expectTextInputByName(page, 'maxStoragePriceTBMonthPinned', '$1.34') - storageFitCurrentAllowanceFiat = page - .getByTestId('maxStoragePriceTBMonthGroup') - .getByLabel('Fit current allowance') - .getByText('$1.29') - await expect(storageFitCurrentAllowanceFiat).toBeVisible() - await clickTwice(storageFitCurrentAllowanceFiat) - await expectTextInputByName(page, 'maxStoragePriceTBMonthPinned', '$1.29') + await rebalanceButton.click() + await expectTextInputByName(page, 'maxStoragePriceTBMonthPinned', '$10.27') + + await configFillEstimatesFiat(page) await setCurrencyDisplay(page, 'bothPreferFiat', 'jpy') storageNetworkAverageFiatJPY = page .getByTestId('maxStoragePriceTBMonthGroup') @@ -120,25 +102,13 @@ test('field tips for storage', async ({ page }) => { await expect(storageNetworkAverageFiatJPY).toBeVisible() await clickTwice(storageNetworkAverageFiatJPY) await expectTextInputByName(page, 'maxStoragePriceTBMonthPinned', '$1.34') - storageFitCurrentAllowanceFiatJPY = page - .getByTestId('maxStoragePriceTBMonthGroup') - .getByLabel('Fit current allowance') - .getByText('¥237.25') - await expect(storageFitCurrentAllowanceFiatJPY).toBeVisible() - await clickTwice(storageFitCurrentAllowanceFiatJPY) - await expectTextInputByName(page, 'maxStoragePriceTBMonthPinned', '$1.29') + await rebalanceButton.click() + await expectTextInputByName(page, 'maxStoragePriceTBMonthPinned', '$10.27') }) test('field tips for upload', async ({ page }) => { - await navigateToConfig({ page }) - await setViewMode({ page, state: 'advanced' }) - - await fillTextInputByName(page, 'allowanceMonth', '7000') - await fillTextInputByName(page, 'storageTB', '7') - await fillTextInputByName(page, 'uploadTBMonth', '7') - await fillTextInputByName(page, 'downloadTBMonth', '7') - await fillTextInputByName(page, 'minShards', '1') - await fillTextInputByName(page, 'totalShards', '3') + const spendingEstimate = page.getByTestId('spendingEstimate') + const rebalanceButton = spendingEstimate.getByLabel('rebalance prices') // Upload siacoin. await setCurrencyDisplay(page, 'bothPreferSc', 'usd') @@ -149,13 +119,10 @@ test('field tips for upload', async ({ page }) => { await expect(uploadNetworkAverage).toBeVisible() await clickTwice(uploadNetworkAverage) await expectTextInputByName(page, 'maxUploadPriceTB', '76') - let uploadFitCurrentAllowance = page - .getByTestId('maxUploadPriceTBGroup') - .getByLabel('Fit current allowance') - .getByText('75') - await expect(uploadFitCurrentAllowance).toBeVisible() - await clickTwice(uploadFitCurrentAllowance) - await expectTextInputByName(page, 'maxUploadPriceTB', '75') + await rebalanceButton.click() + await expectTextInputByName(page, 'maxUploadPriceTB', '611.4') + + await configFillEstimatesSiacoin(page) await setCurrencyDisplay(page, 'bothPreferFiat', 'usd') let uploadNetworkAverageFiat = page .getByTestId('maxUploadPriceTBGroup') @@ -164,13 +131,10 @@ test('field tips for upload', async ({ page }) => { await expect(uploadNetworkAverageFiat).toBeVisible() await clickTwice(uploadNetworkAverageFiat) await expectTextInputByName(page, 'maxUploadPriceTB', '76') - let uploadFitCurrentAllowanceFiat = page - .getByTestId('maxUploadPriceTBGroup') - .getByLabel('Fit current allowance') - .getByText('$0.30') - await expect(uploadFitCurrentAllowanceFiat).toBeVisible() - await clickTwice(uploadFitCurrentAllowanceFiat) - await expectTextInputByName(page, 'maxUploadPriceTB', '75') + await rebalanceButton.click() + await expectTextInputByName(page, 'maxUploadPriceTB', '611.4') + + await configFillEstimatesSiacoin(page) await setCurrencyDisplay(page, 'bothPreferFiat', 'jpy') let uploadNetworkAverageFiatJPY = page .getByTestId('maxUploadPriceTBGroup') @@ -179,20 +143,16 @@ test('field tips for upload', async ({ page }) => { await expect(uploadNetworkAverageFiatJPY).toBeVisible() await clickTwice(uploadNetworkAverageFiatJPY) await expectTextInputByName(page, 'maxUploadPriceTB', '76') - let uploadFitCurrentAllowanceFiatJPY = page - .getByTestId('maxUploadPriceTBGroup') - .getByLabel('Fit current allowance') - .getByText('¥54.58') - await expect(uploadFitCurrentAllowanceFiatJPY).toBeVisible() - await clickTwice(uploadFitCurrentAllowanceFiatJPY) - await expectTextInputByName(page, 'maxUploadPriceTB', '75') + await rebalanceButton.click() + await expectTextInputByName(page, 'maxUploadPriceTB', '611.4') // Fiat. - await setSwitchByLabel(page, 'shouldPinAllowance', true) + await setSwitchByLabel(page, 'shouldPinMaxStoragePrice', true) await setSwitchByLabel(page, 'shouldPinMaxUploadPrice', true) - await fillTextInputByName(page, 'allowanceMonthPinned', '30') + await setSwitchByLabel(page, 'shouldPinMaxDownloadPrice', true) // Upload fiat. + await configFillEstimatesFiat(page) await setCurrencyDisplay(page, 'bothPreferSc', 'usd') uploadNetworkAverage = page .getByTestId('maxUploadPriceTBGroup') @@ -201,13 +161,10 @@ test('field tips for upload', async ({ page }) => { await expect(uploadNetworkAverage).toBeVisible() await clickTwice(uploadNetworkAverage) await expectTextInputByName(page, 'maxUploadPriceTBPinned', '$0.30') - uploadFitCurrentAllowance = page - .getByTestId('maxUploadPriceTBGroup') - .getByLabel('Fit current allowance') - .getByText('81') - await expect(uploadFitCurrentAllowance).toBeVisible() - await clickTwice(uploadFitCurrentAllowance) - await expectTextInputByName(page, 'maxUploadPriceTBPinned', '$0.32') + await rebalanceButton.click() + await expectTextInputByName(page, 'maxUploadPriceTBPinned', '$2.41') + + await configFillEstimatesFiat(page) await setCurrencyDisplay(page, 'bothPreferFiat', 'usd') uploadNetworkAverageFiat = page .getByTestId('maxUploadPriceTBGroup') @@ -216,13 +173,10 @@ test('field tips for upload', async ({ page }) => { await expect(uploadNetworkAverageFiat).toBeVisible() await clickTwice(uploadNetworkAverageFiat) await expectTextInputByName(page, 'maxUploadPriceTBPinned', '$0.30') - uploadFitCurrentAllowanceFiat = page - .getByTestId('maxUploadPriceTBGroup') - .getByLabel('Fit current allowance') - .getByText('$0.32') - await expect(uploadFitCurrentAllowanceFiat).toBeVisible() - await clickTwice(uploadFitCurrentAllowanceFiat) - await expectTextInputByName(page, 'maxUploadPriceTBPinned', '$0.32') + await rebalanceButton.click() + await expectTextInputByName(page, 'maxUploadPriceTBPinned', '$2.41') + + await configFillEstimatesFiat(page) await setCurrencyDisplay(page, 'bothPreferFiat', 'jpy') uploadNetworkAverageFiatJPY = page .getByTestId('maxUploadPriceTBGroup') @@ -231,25 +185,13 @@ test('field tips for upload', async ({ page }) => { await expect(uploadNetworkAverageFiatJPY).toBeVisible() await clickTwice(uploadNetworkAverageFiatJPY) await expectTextInputByName(page, 'maxUploadPriceTBPinned', '$0.30') - uploadFitCurrentAllowanceFiatJPY = page - .getByTestId('maxUploadPriceTBGroup') - .getByLabel('Fit current allowance') - .getByText('¥59.31') - await expect(uploadFitCurrentAllowanceFiatJPY).toBeVisible() - await clickTwice(uploadFitCurrentAllowanceFiatJPY) - await expectTextInputByName(page, 'maxUploadPriceTBPinned', '$0.32') + await rebalanceButton.click() + await expectTextInputByName(page, 'maxUploadPriceTBPinned', '$2.41') }) test('field tips for download', async ({ page }) => { - await navigateToConfig({ page }) - await setViewMode({ page, state: 'advanced' }) - - await fillTextInputByName(page, 'allowanceMonth', '7000') - await fillTextInputByName(page, 'storageTB', '7') - await fillTextInputByName(page, 'uploadTBMonth', '7') - await fillTextInputByName(page, 'downloadTBMonth', '7') - await fillTextInputByName(page, 'minShards', '1') - await fillTextInputByName(page, 'totalShards', '3') + const spendingEstimate = page.getByTestId('spendingEstimate') + const rebalanceButton = spendingEstimate.getByLabel('rebalance prices') // Download siacoin. await setCurrencyDisplay(page, 'bothPreferSc', 'usd') @@ -260,13 +202,10 @@ test('field tips for download', async ({ page }) => { await expect(downloadNetworkAverage).toBeVisible() await clickTwice(downloadNetworkAverage) await expectTextInputByName(page, 'maxDownloadPriceTB', '899') - let downloadFitCurrentAllowance = page - .getByTestId('maxDownloadPriceTBGroup') - .getByLabel('Fit current allowance') - .getByText('375') - await expect(downloadFitCurrentAllowance).toBeVisible() - await clickTwice(downloadFitCurrentAllowance) - await expectTextInputByName(page, 'maxDownloadPriceTB', '375') + await rebalanceButton.click() + await expectTextInputByName(page, 'maxDownloadPriceTB', '4,724.732143') + + await configFillEstimatesSiacoin(page) await setCurrencyDisplay(page, 'bothPreferFiat', 'usd') let downloadNetworkAverageFiat = page .getByTestId('maxDownloadPriceTBGroup') @@ -275,13 +214,10 @@ test('field tips for download', async ({ page }) => { await expect(downloadNetworkAverageFiat).toBeVisible() await clickTwice(downloadNetworkAverageFiat) await expectTextInputByName(page, 'maxDownloadPriceTB', '899') - let downloadFitCurrentAllowanceFiat = page - .getByTestId('maxDownloadPriceTBGroup') - .getByLabel('Fit current allowance') - .getByText('$1.48') - await expect(downloadFitCurrentAllowanceFiat).toBeVisible() - await clickTwice(downloadFitCurrentAllowanceFiat) - await expectTextInputByName(page, 'maxDownloadPriceTB', '375') + await rebalanceButton.click() + await expectTextInputByName(page, 'maxDownloadPriceTB', '4,724.732143') + + await configFillEstimatesSiacoin(page) await setCurrencyDisplay(page, 'bothPreferFiat', 'jpy') let downloadNetworkAverageFiatJPY = page .getByTestId('maxDownloadPriceTBGroup') @@ -290,20 +226,16 @@ test('field tips for download', async ({ page }) => { await expect(downloadNetworkAverageFiatJPY).toBeVisible() await clickTwice(downloadNetworkAverageFiatJPY) await expectTextInputByName(page, 'maxDownloadPriceTB', '899') - let downloadFitCurrentAllowanceFiatJPY = page - .getByTestId('maxDownloadPriceTBGroup') - .getByLabel('Fit current allowance') - .getByText('¥272.92') - await expect(downloadFitCurrentAllowanceFiatJPY).toBeVisible() - await clickTwice(downloadFitCurrentAllowanceFiatJPY) - await expectTextInputByName(page, 'maxDownloadPriceTB', '375') + await rebalanceButton.click() + await expectTextInputByName(page, 'maxDownloadPriceTB', '4,724.732143') // Fiat. - await setSwitchByLabel(page, 'shouldPinAllowance', true) + await setSwitchByLabel(page, 'shouldPinMaxStoragePrice', true) + await setSwitchByLabel(page, 'shouldPinMaxUploadPrice', true) await setSwitchByLabel(page, 'shouldPinMaxDownloadPrice', true) - await fillTextInputByName(page, 'allowanceMonthPinned', '30') // Download fiat. + await configFillEstimatesFiat(page) await setCurrencyDisplay(page, 'bothPreferSc', 'usd') downloadNetworkAverage = page .getByTestId('maxDownloadPriceTBGroup') @@ -312,13 +244,10 @@ test('field tips for download', async ({ page }) => { await expect(downloadNetworkAverage).toBeVisible() await clickTwice(downloadNetworkAverage) await expectTextInputByName(page, 'maxDownloadPriceTBPinned', '$3.55') - downloadFitCurrentAllowance = page - .getByTestId('maxDownloadPriceTBGroup') - .getByLabel('Fit current allowance') - .getByText('407') - await expect(downloadFitCurrentAllowance).toBeVisible() - await clickTwice(downloadFitCurrentAllowance) - await expectTextInputByName(page, 'maxDownloadPriceTBPinned', '$1.61') + await rebalanceButton.click() + await expectTextInputByName(page, 'maxDownloadPriceTBPinned', '$18.63') + + await configFillEstimatesFiat(page) await setCurrencyDisplay(page, 'bothPreferFiat', 'usd') downloadNetworkAverageFiat = page .getByTestId('maxDownloadPriceTBGroup') @@ -327,13 +256,10 @@ test('field tips for download', async ({ page }) => { await expect(downloadNetworkAverageFiat).toBeVisible() await clickTwice(downloadNetworkAverageFiat) await expectTextInputByName(page, 'maxDownloadPriceTBPinned', '$3.55') - downloadFitCurrentAllowanceFiat = page - .getByTestId('maxDownloadPriceTBGroup') - .getByLabel('Fit current allowance') - .getByText('$1.61') - await expect(downloadFitCurrentAllowanceFiat).toBeVisible() - await clickTwice(downloadFitCurrentAllowanceFiat) - await expectTextInputByName(page, 'maxDownloadPriceTBPinned', '$1.61') + await rebalanceButton.click() + await expectTextInputByName(page, 'maxDownloadPriceTBPinned', '$18.63') + + await configFillEstimatesFiat(page) await setCurrencyDisplay(page, 'bothPreferFiat', 'jpy') downloadNetworkAverageFiatJPY = page .getByTestId('maxDownloadPriceTBGroup') @@ -342,26 +268,13 @@ test('field tips for download', async ({ page }) => { await expect(downloadNetworkAverageFiatJPY).toBeVisible() await clickTwice(downloadNetworkAverageFiatJPY) await expectTextInputByName(page, 'maxDownloadPriceTBPinned', '$3.55') - downloadFitCurrentAllowanceFiatJPY = page - .getByTestId('maxDownloadPriceTBGroup') - .getByLabel('Fit current allowance') - .getByText('¥296.56') - await expect(downloadFitCurrentAllowanceFiatJPY).toBeVisible() - await clickTwice(downloadFitCurrentAllowanceFiatJPY) - await expectTextInputByName(page, 'maxDownloadPriceTBPinned', '$1.61') + await rebalanceButton.click() + await expectTextInputByName(page, 'maxDownloadPriceTBPinned', '$18.63') }) test('field tips max contract and rpc prices', async ({ page }) => { - await navigateToConfig({ page }) await setViewMode({ page, state: 'advanced' }) - await fillTextInputByName(page, 'allowanceMonth', '7000') - await fillTextInputByName(page, 'storageTB', '7') - await fillTextInputByName(page, 'uploadTBMonth', '7') - await fillTextInputByName(page, 'downloadTBMonth', '7') - await fillTextInputByName(page, 'minShards', '1') - await fillTextInputByName(page, 'totalShards', '3') - // Contract. await setCurrencyDisplay(page, 'bothPreferSc', 'usd') const contractSuggestion = page diff --git a/apps/renterd-e2e/src/specs/externalData.spec.ts b/apps/renterd-e2e/src/specs/externalData.spec.ts index 347faf5c0..3b794dfd5 100644 --- a/apps/renterd-e2e/src/specs/externalData.spec.ts +++ b/apps/renterd-e2e/src/specs/externalData.spec.ts @@ -27,7 +27,7 @@ test.beforeEach(async ({ page }) => { address: renterdNode.apiAddress, password: renterdNode.password, }) - await configResetBasicSettings({ page }) + await configResetBasicSettings(page) }) test.afterEach(async () => { @@ -51,9 +51,9 @@ test('configuration shows not-enabled message when exchange rates API hangs', as await mockApiSiaScanExchangeRatesHanging({ page }) await page.reload() - await setSwitchByLabel(page, 'shouldPinAllowance', true) await setSwitchByLabel(page, 'shouldPinMaxStoragePrice', true) await setSwitchByLabel(page, 'shouldPinMaxUploadPrice', true) + await setSwitchByLabel(page, 'shouldPinMaxDownloadPrice', true) await fillSelectInputByName(page, 'pinnedCurrency', 'usd') @@ -65,17 +65,17 @@ test('configuration shows not-enabled message when exchange rates API hangs', as // Pinned fields show not-enabled message. await expect( page - .getByTestId('allowanceMonthGroup') + .getByTestId('maxStoragePriceTBMonthGroup') .getByText('Enable an exchange rate API') ).toBeVisible() await expect( page - .getByTestId('maxStoragePriceTBMonthGroup') + .getByTestId('maxUploadPriceTBGroup') .getByText('Enable an exchange rate API') ).toBeVisible() await expect( page - .getByTestId('maxUploadPriceTBGroup') + .getByTestId('maxDownloadPriceTBGroup') .getByText('Enable an exchange rate API') ).toBeVisible() }) diff --git a/apps/renterd-e2e/src/specs/files.spec.ts b/apps/renterd-e2e/src/specs/files.spec.ts index bd878523d..a5ca22d23 100644 --- a/apps/renterd-e2e/src/specs/files.spec.ts +++ b/apps/renterd-e2e/src/specs/files.spec.ts @@ -29,7 +29,6 @@ test.afterEach(async () => { }) test('can create directory and delete a directory', async ({ page }) => { - test.setTimeout(120_000) const bucketName = 'files-test' const dirName1 = 'test-dir' const dirName2 = 'test-dir-2-same-prefix' @@ -56,7 +55,6 @@ test('can create directory and delete a directory', async ({ page }) => { test('can create directory, upload file, rename file, navigate, delete a file, delete a directory', async ({ page, }) => { - test.setTimeout(120_000) const bucketName = 'files-test' const dirName = 'test-dir' const originalFileName = 'sample.txt' @@ -118,7 +116,6 @@ test('can create directory, upload file, rename file, navigate, delete a file, d test('shows a new intermediate directory when uploading nested files', async ({ page, }) => { - test.setTimeout(120_000) const bucketName = 'files-test' const containerDir = 'test-dir' const containerDirPath = `${bucketName}/${containerDir}/` @@ -176,7 +173,6 @@ test('shows a new intermediate directory when uploading nested files', async ({ }) test('batch delete across nested directories', async ({ page }) => { - test.setTimeout(120_000) const bucketName = 'bucket1' await navigateToBuckets({ page }) await createBucket(page, bucketName) @@ -222,7 +218,6 @@ test('batch delete across nested directories', async ({ page }) => { }) test('batch delete using the all files explorer mode', async ({ page }) => { - test.setTimeout(120_000) const bucketName = 'bucket1' await navigateToBuckets({ page }) await createBucket(page, bucketName) diff --git a/apps/renterd-e2e/src/specs/recommendations.spec.ts b/apps/renterd-e2e/src/specs/recommendations.spec.ts index 34f71f6d2..0e036fb6e 100644 --- a/apps/renterd-e2e/src/specs/recommendations.spec.ts +++ b/apps/renterd-e2e/src/specs/recommendations.spec.ts @@ -51,7 +51,6 @@ test('system offers recommendations', async ({ page }) => { await fillTextInputByName(page, 'storageTB', '1') await fillTextInputByName(page, 'uploadTBMonth', '1') await fillTextInputByName(page, 'downloadTBMonth', '1') - await fillTextInputByName(page, 'allowanceMonth', '1000') await fillTextInputByName(page, 'maxStoragePriceTBMonth', '300') await fillTextInputByName(page, 'maxUploadPriceTB', '75') await fillTextInputByName(page, 'maxDownloadPriceTB', '375') @@ -107,7 +106,6 @@ test('recommendations work with pinned fields', async ({ page }) => { await fillTextInputByName(page, 'storageTB', '1') await fillTextInputByName(page, 'uploadTBMonth', '1') await fillTextInputByName(page, 'downloadTBMonth', '1') - await fillTextInputByName(page, 'allowanceMonth', '1000') await fillTextInputByName(page, 'maxStoragePriceTBMonth', '300') await fillTextInputByName(page, 'maxUploadPriceTB', '75') await fillTextInputByName(page, 'maxDownloadPriceTB', '375') diff --git a/apps/renterd/components/Config/ConfigStats.tsx b/apps/renterd/components/Config/ConfigStats.tsx deleted file mode 100644 index 7a674660f..000000000 --- a/apps/renterd/components/Config/ConfigStats.tsx +++ /dev/null @@ -1,102 +0,0 @@ -import { - Text, - ValueSc, - ValueNum, - useSiacoinFiat, - ScrollArea, -} from '@siafoundation/design-system' -import { TBToBytes, humanBytes, toHastings } from '@siafoundation/units' -import { useConfig } from '../../contexts/config' -import { useApp } from '../../contexts/app' -import { useEstimatedSpending } from './useEstimatedSpending' - -export function ConfigStats() { - const { autopilotInfo } = useApp() - const { redundancyMultiplier, storageTB, configViewMode } = useConfig() - - const { estimatedSpendingPerMonth, estimatedSpendingPerTB } = - useEstimatedSpending() - - const perMonth = useSiacoinFiat({ sc: estimatedSpendingPerMonth }) - const perTB = useSiacoinFiat({ sc: estimatedSpendingPerTB }) - - if (!autopilotInfo.data?.isAutopilotEnabled) { - return null - } - const canEstimate = - estimatedSpendingPerMonth && estimatedSpendingPerTB && storageTB - - return !canEstimate ? ( - - {configViewMode === 'advanced' - ? 'Enter expected storage, period, and allowance values to estimate monthly spending.' - : 'Enter expected storage and max price to estimate monthly spending.'} - - ) : ( - -
- - Estimate: - -
- - {perTB.fiat && ( -
- - `(${perTB.currency.prefix}${v.toFixed(perTB.currency.fixed)})` - } - /> -
- )} - - per TB/month with {redundancyMultiplier.toFixed(1)}x redundancy - -
- {/* additionally show estimated spending for total storage if it's different from per TB */} - {!estimatedSpendingPerTB.eq(estimatedSpendingPerMonth) && ( -
- - {perMonth.fiat && ( -
- - `(${perMonth.currency.prefix}${v.toFixed( - perMonth.currency.fixed - )})` - } - /> -
- )} - - to store {humanBytes(TBToBytes(storageTB).toNumber())}/month with{' '} - {redundancyMultiplier.toFixed(1)}x redundancy - -
- )} -
-
- ) -} diff --git a/apps/renterd/components/Config/HangingNav.tsx b/apps/renterd/components/Config/HangingNav.tsx new file mode 100644 index 000000000..1d1fcc4d0 --- /dev/null +++ b/apps/renterd/components/Config/HangingNav.tsx @@ -0,0 +1,22 @@ +import { SpendingEstimate } from './SpendingEstimate' +import { Recommendations } from './Recommendations' +import { ScrollArea } from '@siafoundation/design-system' + +export function HangingNav() { + return ( +
+
+ +
+
+ +
+
+ +
+
+
+
+
+ ) +} diff --git a/apps/renterd/components/Config/HangingNavItem.tsx b/apps/renterd/components/Config/HangingNavItem.tsx new file mode 100644 index 000000000..dab52a695 --- /dev/null +++ b/apps/renterd/components/Config/HangingNavItem.tsx @@ -0,0 +1,63 @@ +import { Button, HoverCard, ScrollArea } from '@siafoundation/design-system' +import { Maximize16 } from '@siafoundation/react-icons' +import useLocalStorageState from 'use-local-storage-state' +import { cx } from 'class-variance-authority' + +export function HangingNavItem({ + localStorageKey, + heading, + children, + tip, + canMaximizeControls, + testId, +}: { + localStorageKey: string + heading: React.ReactNode + children?: React.ReactNode + tip?: React.ReactNode + canMaximizeControls?: boolean + testId?: string +}) { + const [maximized, setMaximized] = useLocalStorageState( + `v0/renterd/${localStorageKey}`, + { + defaultValue: true, + } + ) + const el = ( +
+
{ + if (canMaximizeControls) { + setMaximized((maximized) => !maximized) + } + }} + > + {heading} + {canMaximizeControls && ( + + )} +
+
+ ) + return ( +
+ + {tip ? {tip} : el} + {maximized && children} + +
+ ) +} diff --git a/apps/renterd/components/Config/Layout.tsx b/apps/renterd/components/Config/Layout.tsx index de496aa63..6ae3e5212 100644 --- a/apps/renterd/components/Config/Layout.tsx +++ b/apps/renterd/components/Config/Layout.tsx @@ -5,10 +5,9 @@ import { RenterdAuthedLayout, RenterdAuthedPageLayoutProps, } from '../RenterdAuthedLayout' -import { ConfigStats } from './ConfigStats' import { ConfigActions } from './ConfigActions' import { ConfigNav } from './ConfigNav' -import { Recommendations } from './Recommendations' +import { HangingNav } from './HangingNav' export const Layout = RenterdAuthedLayout export function useLayoutProps(): RenterdAuthedPageLayoutProps { @@ -18,9 +17,8 @@ export function useLayoutProps(): RenterdAuthedPageLayoutProps { routes, nav: , sidenav: , - stats: , actions: , - after: , + after: , openSettings: () => openDialog('settings'), size: '3', } diff --git a/apps/renterd/components/Config/RebalancePrices.tsx b/apps/renterd/components/Config/RebalancePrices.tsx new file mode 100644 index 000000000..1df2380ab --- /dev/null +++ b/apps/renterd/components/Config/RebalancePrices.tsx @@ -0,0 +1,241 @@ +import { useMemo } from 'react' +import { Scales16 } from '@siafoundation/react-icons' +import { useConfig } from '../../contexts/config' +import { + Button, + formSetFields, + Maybe, + Text, + Tooltip, +} from '@siafoundation/design-system' +import { InputValues } from '../../contexts/config/types' +import BigNumber from 'bignumber.js' +import { derivePricingFromSpendingEstimate } from '../../contexts/config/spending' +import { + maxPricingFactor, + storageWeight, + downloadWeight, + uploadWeight, +} from '../../contexts/config/spendingConfig' +import { useApp } from '../../contexts/app' +import { useRedundancyMultiplier } from '../../contexts/config/useRedundancyMultiplier' +import { useFormExchangeRate } from '../../contexts/config/useFormExchangeRate' +import { useSpendingEstimate } from '../../contexts/config/useSpendingEstimate' +import { useEnabledPricingValuesInSiacoin } from '../../contexts/config/useEnabledPricingValuesInSiacoin' + +export function RebalancePrices({ + estimatedSpendingPerMonth, +}: { + estimatedSpendingPerMonth: Maybe +}) { + const { form, fields } = useConfig() + const enabledDerivedPrices = useSpendingDerivedPricingFromEnabledFields({ + estimatedSpendingPerMonth, + }) + const isEnabled = useIsEnabledAndDifferentEnough() + + return ( + + + Rebalance prices + + + Update max prices for storage, upload, and download to suggested + values that take into account the current estimated usage values and + fit the current estimated spending value. The suggested values keep + storage, upload, and download prices proportional to each other + according to the following weights: {storageWeight}x storage,{' '} + {uploadWeight}x upload, {downloadWeight}x download. These weights + model a recommended pricing ratio across the categories. + + + } + > + + + ) +} + +// Convert the spending estimate to balanced pricing values. This method returns values for +// pinned or non-pinned values depending on which is enabled on a per-field basis. +function useSpendingDerivedPricingFromEnabledFields({ + estimatedSpendingPerMonth, +}: { + estimatedSpendingPerMonth?: BigNumber +}): Maybe<{ + maxStoragePriceTBMonth?: BigNumber + maxDownloadPriceTB?: BigNumber + maxUploadPriceTB?: BigNumber + maxStoragePriceTBMonthPinned?: BigNumber + maxDownloadPriceTBPinned?: BigNumber + maxUploadPriceTBPinned?: BigNumber +}> { + const { form } = useConfig() + const { isAutopilotEnabled } = useApp() + const storageTB = form.watch('storageTB') + const downloadTBMonth = form.watch('downloadTBMonth') + const uploadTBMonth = form.watch('uploadTBMonth') + const redundancyMultiplier = useRedundancyMultiplier({ + minShards: form.watch('minShards'), + totalShards: form.watch('totalShards'), + }) + const { rate } = useFormExchangeRate(form) + const values = useMemo(() => { + if (isAutopilotEnabled) { + const derivedPricing = derivePricingFromSpendingEstimate({ + estimatedSpendingPerMonth, + maxPricingFactor, + storageTB, + downloadTBMonth, + uploadTBMonth, + redundancyMultiplier, + storageWeight, + downloadWeight, + uploadWeight, + }) + if (!derivedPricing) { + return undefined + } + // Convert derived siacoin prices to pinned fiat prices. + const pinnedPricing = rate + ? { + maxStoragePriceTBMonthPinned: + derivedPricing?.maxStoragePriceTBMonth.times(rate), + maxDownloadPriceTBPinned: + derivedPricing?.maxDownloadPriceTB.times(rate), + maxUploadPriceTBPinned: + derivedPricing?.maxUploadPriceTB.times(rate), + } + : undefined + return { + ...derivedPricing, + ...pinnedPricing, + } + } + return undefined + }, [ + isAutopilotEnabled, + estimatedSpendingPerMonth, + storageTB, + downloadTBMonth, + uploadTBMonth, + redundancyMultiplier, + rate, + ]) + const shouldPinMaxStoragePrice = form.watch('shouldPinMaxStoragePrice') + const shouldPinMaxUploadPrice = form.watch('shouldPinMaxUploadPrice') + const shouldPinMaxDownloadPrice = form.watch('shouldPinMaxDownloadPrice') + const results: Partial = {} + if (!values) { + return undefined + } + if (shouldPinMaxStoragePrice) { + results.maxStoragePriceTBMonthPinned = values.maxStoragePriceTBMonthPinned + } else { + results.maxStoragePriceTBMonth = values.maxStoragePriceTBMonth + } + if (shouldPinMaxUploadPrice) { + results.maxUploadPriceTBPinned = values.maxUploadPriceTBPinned + } else { + results.maxUploadPriceTB = values.maxUploadPriceTB + } + if (shouldPinMaxDownloadPrice) { + results.maxDownloadPriceTBPinned = values.maxDownloadPriceTBPinned + } else { + results.maxDownloadPriceTB = values.maxDownloadPriceTB + } + return results +} + +function useIsEnabledAndDifferentEnough() { + const { form } = useConfig() + const currentPrices = useEnabledPricingValuesInSiacoin({ + form, + }) + const { estimatedSpendingPerMonth } = useSpendingEstimate() + const storageTB = form.watch('storageTB') + const downloadTBMonth = form.watch('downloadTBMonth') + const uploadTBMonth = form.watch('uploadTBMonth') + const redundancyMultiplier = useRedundancyMultiplier({ + minShards: form.watch('minShards'), + totalShards: form.watch('totalShards'), + }) + const canCalculatePrices = useMemo( + () => estimatedSpendingPerMonth?.gt(0), + [estimatedSpendingPerMonth] + ) + const differenceThreshold = 0.01 + // Calculate the difference between the current and optimal pricing values. + // Return true if the difference is greater than 1% for any of the values. + return useMemo(() => { + if (!canCalculatePrices) { + return new BigNumber(0) + } + const optimalPrices = derivePricingFromSpendingEstimate({ + estimatedSpendingPerMonth, + maxPricingFactor, + storageTB, + downloadTBMonth, + uploadTBMonth, + redundancyMultiplier, + storageWeight, + downloadWeight, + uploadWeight, + }) + + return ( + isPriceDifferenceGreaterThanThreshold( + optimalPrices?.maxStoragePriceTBMonth, + currentPrices?.maxStoragePriceTBMonth, + differenceThreshold + ) || + isPriceDifferenceGreaterThanThreshold( + optimalPrices?.maxUploadPriceTB, + currentPrices?.maxUploadPriceTB, + differenceThreshold + ) || + isPriceDifferenceGreaterThanThreshold( + optimalPrices?.maxDownloadPriceTB, + currentPrices?.maxDownloadPriceTB, + differenceThreshold + ) + ) + }, [ + canCalculatePrices, + currentPrices, + storageTB, + downloadTBMonth, + uploadTBMonth, + redundancyMultiplier, + estimatedSpendingPerMonth, + ]) +} + +function isPriceDifferenceGreaterThanThreshold( + a: BigNumber = new BigNumber(0), + b: BigNumber = new BigNumber(0), + threshold: number +) { + if (a.eq(0) || b.eq(0)) { + return new BigNumber(100) + } + return a.minus(b).div(a).abs().gt(threshold) +} diff --git a/apps/renterd/components/Config/Recommendations.tsx b/apps/renterd/components/Config/Recommendations.tsx index a7d49ec6b..116d1562a 100644 --- a/apps/renterd/components/Config/Recommendations.tsx +++ b/apps/renterd/components/Config/Recommendations.tsx @@ -1,15 +1,11 @@ import { Button, - HoverCard, Link, - ScrollArea, Separator, Text, formSetField, } from '@siafoundation/design-system' import { - Subtract24, - Add24, CaretUp16, CaretDown16, Information20, @@ -18,20 +14,12 @@ import { } from '@siafoundation/react-icons' import { useApp } from '../../contexts/app' import { routes } from '../../config/routes' -import useLocalStorageState from 'use-local-storage-state' import { useConfig } from '../../contexts/config' -import { cx } from 'class-variance-authority' import { pluralize } from '@siafoundation/units' +import { HangingNavItem } from './HangingNavItem' export function Recommendations() { const { autopilotInfo } = useApp() - const [maximized, setMaximized] = useLocalStorageState( - 'v0/renterd/config/recommendations', - { - defaultValue: true, - } - ) - const { form, fields, evaluation } = useConfig() const { hostMargin50, @@ -120,16 +108,17 @@ export function Recommendations() { if (!hasDataToEvaluate) { return ( - - + The system will review your configuration once all fields are filled @@ -139,21 +128,33 @@ export function Recommendations() { ) } + const matchingCountEl = ( +
+ + Matching + + + {usableHostsCurrent}/{hostTarget50} + + + hosts + +
+ ) + if (!needsRecommendations) { return ( - - - {usableHostsCurrent}/{hostTarget50} - + {matchingCountEl} Configuration matches with a sufficient number of hosts @@ -164,79 +165,79 @@ export function Recommendations() { } return ( - + canMaximizeControls={!!recommendations.length} + heading={ +
- - {usableHostsCurrent}/{hostTarget50} - - + {matchingCountEl} + {pluralize(recommendations.length, 'recommendation', { customZero: 'No recommendations', })}{' '} to match with more hosts - +
} > - {maximized && - foundRecommendation && - recommendations.map( - ({ - hrefId, - key, - title, - currentLabel, - targetLabel, - targetValue, - direction, - }) => ( -
- {direction === 'up' ? 'Increase ' : 'Decrease '} - - {title} - {' '} - from {currentLabel} to{' '} - - - } - action={ - - {direction === 'up' ? : } - - } - /> - ) - )} - + {foundRecommendation ? ( +
+ {recommendations.map( + ({ + hrefId, + key, + title, + currentLabel, + targetLabel, + targetValue, + direction, + }) => ( +
+ {direction === 'up' ? 'Increase ' : 'Decrease '} + + {title} + {' '} + from {currentLabel} to{' '} + + + } + action={ + + {direction === 'up' ? : } + + } + /> + ) + )} +
+ ) : null} + ) } @@ -259,57 +260,3 @@ function Section({ testId, title, action }: SectionProps) { ) } - -function Layout({ - children, - maximized, - setMaximized, - maximizeControls, - title, - tip, -}: { - children?: React.ReactNode - maximized: boolean - setMaximized: (maximized: boolean) => void - maximizeControls: boolean - title: React.ReactNode - tip?: React.ReactNode -}) { - const el = ( -
{ - if (maximizeControls) { - setMaximized(!maximized) - } - }} - > -
{title}
- {maximizeControls && ( - - )} -
- ) - return ( -
-
-
- - {tip ? {tip} : el} - {children && ( -
{children}
- )} -
-
-
-
- ) -} diff --git a/apps/renterd/components/Config/SpendingEstimate.tsx b/apps/renterd/components/Config/SpendingEstimate.tsx new file mode 100644 index 000000000..dcfad5d1a --- /dev/null +++ b/apps/renterd/components/Config/SpendingEstimate.tsx @@ -0,0 +1,147 @@ +import { + Option, + Select, + SiacoinField, + Text, + ValueScFiat, +} from '@siafoundation/design-system' +import { useConfig } from '../../contexts/config' +import { Information20, PendingFilled20 } from '@siafoundation/react-icons' +import { maxPricingFactor } from '../../contexts/config/spendingConfig' +import useLocalStorageState from 'use-local-storage-state' +import { useSpendingEstimate } from '../../contexts/config/useSpendingEstimate' +import { RebalancePrices } from './RebalancePrices' +import { useRedundancyMultiplier } from '../../contexts/config/useRedundancyMultiplier' +import { HangingNavItem } from './HangingNavItem' +import { toHastings } from '@siafoundation/units' + +export function SpendingEstimate() { + const { form } = useConfig() + const storageTB = form.watch('storageTB') + const [mode, setMode] = useLocalStorageState<'total' | 'tb'>( + 'v0/renterd/config/spendingEstimateMode', + { + defaultValue: 'total', + } + ) + + const minShards = form.watch('minShards') + const totalShards = form.watch('totalShards') + const { estimatedSpendingPerMonth, estimatedSpendingPerTB } = + useSpendingEstimate() + const redundancy = useRedundancyMultiplier({ + minShards, + totalShards, + }) + + const modeSelector = ( +
+ + +
+ ) + + if (!(estimatedSpendingPerMonth && estimatedSpendingPerTB && storageTB)) { + return null + } + + return ( + + + The spending estimate is calculated using the current estimated + usage and max price values. The estimate assumes spending will be + across a distribution of hosts with various prices that fit within + max price values, this is modeled with a factor of{' '} + {maxPricingFactor} + x. + + + } + heading={ +
+ + + + + Spending estimate + + {estimatedSpendingPerMonth && ( + + )} +
+ } + > +
+ {estimatedSpendingPerMonth && estimatedSpendingPerTB && storageTB ? ( + mode === 'total' ? ( + <> + {modeSelector} + + + ) : ( + <> + {modeSelector} + + + ) + ) : ( +
+ + + + + The system will estimate spending once expected usage and pricing + fields are filled. + +
+ )} +
+
+ ) +} diff --git a/apps/renterd/components/Config/index.tsx b/apps/renterd/components/Config/index.tsx index 8f3794042..a23edf706 100644 --- a/apps/renterd/components/Config/index.tsx +++ b/apps/renterd/components/Config/index.tsx @@ -16,7 +16,6 @@ export function Config() { const { form, fields, remoteError, configRef } = useConfig() const pinnedCurrency = form.watch('pinnedCurrency') - const shouldPinAllowance = form.watch('shouldPinAllowance') const shouldPinMaxStoragePrice = form.watch('shouldPinMaxStoragePrice') const shouldPinMaxUploadPrice = form.watch('shouldPinMaxUploadPrice') const shouldPinMaxDownloadPrice = form.watch('shouldPinMaxDownloadPrice') @@ -29,7 +28,7 @@ export function Config() { return remoteError ? ( ) : ( -
+
- - - {shouldPinAllowance ? ( - canUseExchangeRates ? ( - - ) : ( - - ) - ) : ( - - )} -
- } - /> { - if (!allowanceMonth?.gt(0)) { - return undefined - } - return allowanceMonth - }, [allowanceMonth]) - - const estimatedSpendingPerTB = useMemo(() => { - if (!estimatedSpendingPerMonth?.gt(0) || !storageTB?.gt(0)) { - return undefined - } - const totalCostPerMonthTB = estimatedSpendingPerMonth.div(storageTB) - return totalCostPerMonthTB - }, [estimatedSpendingPerMonth, storageTB]) - - return { - estimatedSpendingPerMonth, - estimatedSpendingPerTB, - } -} diff --git a/apps/renterd/components/OnboardingBar.tsx b/apps/renterd/components/OnboardingBar.tsx index e8b9087b6..4eb37a9df 100644 --- a/apps/renterd/components/OnboardingBar.tsx +++ b/apps/renterd/components/OnboardingBar.tsx @@ -20,24 +20,18 @@ import { useSyncStatus } from '../hooks/useSyncStatus' import { routes } from '../config/routes' import { useDialog } from '../contexts/dialog' import { useNotEnoughContracts } from './Files/checks/useNotEnoughContracts' -import { useAutopilotConfig, useWallet } from '@siafoundation/renterd-react' +import { useWallet } from '@siafoundation/renterd-react' import BigNumber from 'bignumber.js' import { humanSiacoin } from '@siafoundation/units' import { useAppSettings } from '@siafoundation/react-core' import useLocalStorageState from 'use-local-storage-state' +import { useSpendingEstimate } from '../contexts/config/useSpendingEstimate' export function OnboardingBar() { const { isUnlockedAndAuthedRoute } = useAppSettings() const { isAutopilotEnabled, autopilotInfo } = useApp() const { openDialog } = useDialog() const wallet = useWallet() - const autopilot = useAutopilotConfig({ - config: { - swr: { - errorRetryInterval: 10_000, - }, - }, - }) const [maximized, setMaximized] = useLocalStorageState( 'v0/renterd/onboarding/maximized', { @@ -47,6 +41,7 @@ export function OnboardingBar() { const syncStatus = useSyncStatus() const notEnoughContracts = useNotEnoughContracts() + const { estimatedSpendingPerMonth } = useSpendingEstimate() if (!isUnlockedAndAuthedRoute || !isAutopilotEnabled) { return null @@ -55,7 +50,6 @@ export function OnboardingBar() { const walletBalance = new BigNumber( wallet.data ? wallet.data.confirmed + wallet.data.unconfirmed : 0 ) - const allowance = new BigNumber(autopilot.data?.contracts.allowance || 0) const step1Configured = autopilotInfo.data?.state?.configured const step2Synced = syncStatus.isSynced @@ -171,8 +165,8 @@ export function OnboardingBar() { } description={`Fund your wallet with at least ${humanSiacoin( - allowance - )} siacoin to cover the required allowance.${ + estimatedSpendingPerMonth || 0 + )} siacoin to cover the estimated spending for a month.${ syncStatus.isWalletSynced ? '' : ' Balance will not be accurate until wallet is finished scanning.' diff --git a/apps/renterd/contexts/alerts/data.tsx b/apps/renterd/contexts/alerts/data.tsx index 3cc8be58b..be97a0ea6 100644 --- a/apps/renterd/contexts/alerts/data.tsx +++ b/apps/renterd/contexts/alerts/data.tsx @@ -259,18 +259,6 @@ export const dataFields: Record< ) } as Render, }, - allowance: { - render: function Component({ value }: { value: string }) { - return ( -
- - allowance - - -
- ) - } as Render, - }, balance: { render: function Component({ value }: { value: string }) { return ( diff --git a/apps/renterd/contexts/config/fieldTips/Allowance.tsx b/apps/renterd/contexts/config/fieldTips/Allowance.tsx deleted file mode 100644 index 89168d3ad..000000000 --- a/apps/renterd/contexts/config/fieldTips/Allowance.tsx +++ /dev/null @@ -1,177 +0,0 @@ -import { ConfigFields, Text, formSetFields } from '@siafoundation/design-system' -import BigNumber from 'bignumber.js' -import React, { useMemo } from 'react' -import { Categories, InputValues } from '../types' -import { calculateEstimatedSpending } from '@siafoundation/units' -import { - CheckmarkFilled16, - NextFilled16, - PreviousFilled16, - SubtractAlt16, - WarningFilled16, -} from '@siafoundation/react-icons' -import { UseFormReturn } from 'react-hook-form' -import { - useAllowanceDerivedPricingForEnabledFields, - useEnabledAllowanceInSiacoin, - useEnabledPricingValuesInSiacoin, - useEnabledAllowanceFromEnabledPricingValues, -} from '../useAllowanceDerivedPricing' -import { useRedundancyMultiplier } from '../useRedundancyMultiplier' -import { allowanceFactor } from '../deriveAllowanceConfig' -import { - TipAction, - TipReadOnly, - fitAllPricesToCurrentAllowanceTipContent, -} from './Tip' - -export function AllowanceTips({ - form, - fields, -}: { - form: UseFormReturn - fields: ConfigFields -}) { - const enabledDerivedPrices = useAllowanceDerivedPricingForEnabledFields({ - form, - }) - const enabledAllowance = useEnabledAllowanceFromEnabledPricingValues({ - form, - }) - const pricesInSiacoin = useEnabledPricingValuesInSiacoin({ - form, - }) - const allowanceMonth = useEnabledAllowanceInSiacoin({ - form, - }) - const storageTB = form.watch('storageTB') - const downloadTBMonth = form.watch('downloadTBMonth') - const uploadTBMonth = form.watch('uploadTBMonth') - const redundancyMultiplier = useRedundancyMultiplier({ - minShards: form.watch('minShards'), - totalShards: form.watch('totalShards'), - }) - - const canCalculateAllowance = useMemo(() => { - if (!pricesInSiacoin) { - return false - } - const { maxStoragePriceTBMonth, maxUploadPriceTB, maxDownloadPriceTB } = - pricesInSiacoin - return ( - maxStoragePriceTBMonth?.gt(0) && - maxUploadPriceTB?.gt(0) && - maxDownloadPriceTB?.gt(0) && - storageTB?.gt(0) && - downloadTBMonth?.gt(0) && - uploadTBMonth?.gt(0) - ) - }, [pricesInSiacoin, storageTB, downloadTBMonth, uploadTBMonth]) - - const canCalculatePrices = useMemo( - () => allowanceMonth?.gt(0), - [allowanceMonth] - ) - - const canCheckPricing = useMemo( - () => canCalculateAllowance && canCalculatePrices, - [canCalculateAllowance, canCalculatePrices] - ) - - // Calculate the difference between the current spending and the allowance. - // This does not check that the pricing values use the same weighting factors. - const differencePercentage = useMemo(() => { - if (!canCheckPricing) { - return new BigNumber(0) - } - const { maxStoragePriceTBMonth, maxUploadPriceTB, maxDownloadPriceTB } = - pricesInSiacoin || {} - const spending = calculateEstimatedSpending({ - maxStoragePriceTBMonth, - maxDownloadPriceTB, - maxUploadPriceTB, - storageTB, - downloadTBMonth, - uploadTBMonth, - redundancyMultiplier, - }) - const scaledAllowance = allowanceMonth?.times(allowanceFactor) - const diff = scaledAllowance?.minus(spending || 0).abs() - const percentage = diff?.div(scaledAllowance || 1).times(100) - return percentage - }, [ - canCheckPricing, - pricesInSiacoin, - storageTB, - downloadTBMonth, - uploadTBMonth, - redundancyMultiplier, - allowanceMonth, - ]) - - return ( - <> - {canCheckPricing ? ( - differencePercentage?.gt(1) ? ( - } iconColor="amber"> - - Current pricing may not fit allowance - - - ) : ( - } iconColor="green"> - - Current pricing fits allowance - - - ) - ) : ( - } iconColor="subtle"> - - Set allowance and pricing for feedback - - - )} -
- } - iconColor="contrast" - disabled={!enabledDerivedPrices} - tip={fitAllPricesToCurrentAllowanceTipContent} - onClick={() => { - if (!enabledDerivedPrices) { - return - } - formSetFields({ - form, - fields, - values: enabledDerivedPrices, - options: true, - }) - }} - > - Set max prices to fit current allowance - - } - iconColor="contrast" - disabled={!enabledAllowance} - tip="Set the allowance to fit the current max prices for storage, upload, and download." - onClick={() => { - if (!enabledAllowance) { - return - } - formSetFields({ - form, - fields, - values: enabledAllowance, - options: true, - }) - }} - > - Set allowance to fit current max prices - -
- - ) -} diff --git a/apps/renterd/contexts/config/fieldTips/MaxDownloadPrice.tsx b/apps/renterd/contexts/config/fieldTips/MaxDownloadPrice.tsx index 4eaec1c86..24f78b726 100644 --- a/apps/renterd/contexts/config/fieldTips/MaxDownloadPrice.tsx +++ b/apps/renterd/contexts/config/fieldTips/MaxDownloadPrice.tsx @@ -6,12 +6,8 @@ import { import { fiatToSiacoin, toHastings } from '@siafoundation/units' import { UseFormReturn } from 'react-hook-form' import { Categories, RecommendationItem, InputValues } from '../types' -import { useAllowanceDerivedPricingForEnabledFields } from '../useAllowanceDerivedPricing' import { useFormExchangeRate } from '../useFormExchangeRate' -import { - fitPriceToCurrentAllowanceTipContent, - recommendationTipContent, -} from './Tip' +import { recommendationTipContent } from './Tip' import { useAverages } from '../useAverages' export function MaxDownloadPriceTips({ @@ -24,9 +20,6 @@ export function MaxDownloadPriceTips({ recommendations: Partial> }) { const { downloadAverage } = useAverages() - const derived = useAllowanceDerivedPricingForEnabledFields({ - form, - }) const recommendationPrice = recommendations?.maxDownloadPriceTB?.targetValue return ( @@ -49,24 +42,6 @@ export function MaxDownloadPriceTips({ }} /> )} - {derived?.maxDownloadPriceTB && ( - { - formSetField({ - form, - fields, - name: 'maxDownloadPriceTB', - value: derived.maxDownloadPriceTB, - options: true, - }) - }} - /> - )} {recommendationPrice && ( recommendations: Partial> }) { - const derived = useAllowanceDerivedPricingForEnabledFields({ - form, - }) const { rate } = useFormExchangeRate(form) const { downloadAverage } = useAverages() - const derivedPriceInSiacoin = - derived?.maxDownloadPriceTBPinned && rate - ? fiatToSiacoin(derived.maxDownloadPriceTBPinned, rate) - : null const recommendationInFiat = recommendations?.maxDownloadPriceTBPinned?.targetValue const recommendationInSiacoin = @@ -133,24 +101,6 @@ export function MaxDownloadPricePinnedTips({ }} /> )} - {derivedPriceInSiacoin && derived?.maxDownloadPriceTBPinned && ( - { - formSetField({ - form, - fields, - name: 'maxDownloadPriceTBPinned', - value: derived.maxDownloadPriceTBPinned, - options: true, - }) - }} - /> - )} {recommendationInSiacoin && ( > }) { const { storageAverage } = useAverages() - const derived = useAllowanceDerivedPricingForEnabledFields({ - form, - }) const maxStoragePriceTBMonth = form.watch('maxStoragePriceTBMonth') const recommendationPrice = recommendations?.maxStoragePriceTBMonth?.targetValue @@ -52,24 +44,6 @@ export function MaxStoragePriceTips({ }} /> )} - {derived?.maxStoragePriceTBMonth && ( - { - formSetField({ - form, - fields, - name: 'maxStoragePriceTBMonth', - value: derived.maxStoragePriceTBMonth, - options: true, - }) - }} - /> - )} {recommendationPrice && ( recommendations: Partial> }) { - const derived = useAllowanceDerivedPricingForEnabledFields({ - form, - }) const { storageAverage } = useAverages() const { rate } = useFormExchangeRate(form) const maxStoragePriceTBMonthPinned = form.watch( @@ -118,10 +89,6 @@ export function MaxStoragePricePinnedTips({ maxStoragePriceTBMonthPinned && rate ? fiatToSiacoin(maxStoragePriceTBMonthPinned, rate) : undefined - const derivedPriceInSiacoin = - derived?.maxStoragePriceTBMonthPinned && rate - ? fiatToSiacoin(derived.maxStoragePriceTBMonthPinned, rate) - : undefined const recommendationInFiat = recommendations?.maxStoragePriceTBMonthPinned?.targetValue const recommendationInSiacoin = @@ -149,24 +116,6 @@ export function MaxStoragePricePinnedTips({ }} /> )} - {derivedPriceInSiacoin && derived?.maxStoragePriceTBMonthPinned && ( - { - formSetField({ - form, - fields, - name: 'maxStoragePriceTBMonthPinned', - value: derived.maxStoragePriceTBMonthPinned, - options: true, - }) - }} - /> - )} {recommendationInSiacoin && ( > }) { const { uploadAverage } = useAverages() - const derived = useAllowanceDerivedPricingForEnabledFields({ - form, - }) const maxUploadPriceTB = form.watch('maxUploadPriceTB') const recommendationPrice = recommendations?.maxUploadPriceTB?.targetValue @@ -51,29 +43,11 @@ export function MaxUploadPriceTips({ }} /> )} - {derived?.maxUploadPriceTB && ( - { - formSetField({ - form, - fields, - name: 'maxUploadPriceTB', - value: derived.maxUploadPriceTB, - options: true, - }) - }} - /> - )} {recommendationPrice && ( @@ -107,18 +81,11 @@ export function MaxUploadPricePinnedTips({ }) { const { rate } = useFormExchangeRate(form) const { uploadAverage } = useAverages() - const derived = useAllowanceDerivedPricingForEnabledFields({ - form, - }) const maxUploadPriceTBPinned = form.watch('maxUploadPriceTBPinned') const currentPriceInSiacoin = maxUploadPriceTBPinned && rate ? fiatToSiacoin(maxUploadPriceTBPinned, rate) : undefined - const derivedPriceInSiacoin = - derived?.maxUploadPriceTBPinned && rate - ? fiatToSiacoin(derived.maxUploadPriceTBPinned, rate) - : undefined const recommendationInFiat = recommendations?.maxUploadPriceTBPinned?.targetValue const recommendationInSiacoin = @@ -145,24 +112,6 @@ export function MaxUploadPricePinnedTips({ }} /> )} - {derivedPriceInSiacoin && derived?.maxUploadPriceTBPinned && ( - { - formSetField({ - form, - fields, - name: 'maxUploadPriceTBPinned', - value: derived.maxUploadPriceTBPinned, - options: true, - }) - }} - /> - )} {recommendationInSiacoin && ( - Set suggested max price that fits the current allowance spending target. - This suggested value takes into account the current estimated usage and - keeps storage, upload, and download upload prices proportional to each other - according to the following weights: {storageWeight}x storage, {uploadWeight} - x upload, {downloadWeight}x download. - -) - -export const fitAllPricesToCurrentAllowanceTipContent = ( - <> - Set suggested max prices for storage, upload, and download that fit the - current allowance spending target. The suggested values take into account - the current estimated usage and keeps storage, upload, and download upload - prices proportional to each other according to the following weights:{' '} - {storageWeight}x storage, {uploadWeight}x upload, {downloadWeight}x - download. - -) - export const recommendationTipContent = ( <> The system found a recommendation that would increase the number of usable diff --git a/apps/renterd/contexts/config/fields.tsx b/apps/renterd/contexts/config/fields.tsx index 6ca4d8c9f..2726beab4 100644 --- a/apps/renterd/contexts/config/fields.tsx +++ b/apps/renterd/contexts/config/fields.tsx @@ -16,7 +16,6 @@ import { AdvancedDefaults, } from './types' import { currencyOptions } from '@siafoundation/react-core' -import { AllowanceTips } from './fieldTips/Allowance' import { MaxStoragePriceTips, MaxStoragePricePinnedTips, @@ -99,62 +98,6 @@ export function getFields({ }, }, }, - shouldPinAllowance: { - title: '', - description: '', - type: 'boolean', - category: 'storage', - validation: {}, - }, - allowanceMonth: { - type: 'siacoin', - category: 'storage', - title: 'Allowance', - description: ( - <> - The amount you would like to spend per month. Choose whether to set - your allowance in siacoin per month or to pin the siacoin price to a - fixed fiat value per month. - - ), - units: 'SC/month', - decimalsLimitSc: scDecimalPlaces, - hidden: !isAutopilotEnabled, - validation: { - validate: { - required: requiredIfAutopilot(validationContext), - }, - }, - after: AllowanceTips, - }, - allowanceMonthPinned: { - title: '', - description: '', - units: '/month', - type: 'fiat', - category: 'storage', - validation: { - validate: { - required: requiredIfPinningEnabled('shouldPinAllowance'), - currency: requiredIfPinningEnabled( - 'shouldPinAllowance', - (_, values) => - !!values.pinnedCurrency || 'must select a pinned currency' - ), - range: requiredIfPinningEnabled( - 'shouldPinAllowance', - (value: Maybe, values) => { - return ( - !values.shouldPinAllowance || - value?.gt(0) || - 'must be greater than 0' - ) - } - ), - }, - }, - after: AllowanceTips, - }, periodWeeks: { type: 'number', category: 'storage', @@ -985,7 +928,6 @@ function requiredIfAutopilotAndAdvanced( } type ShouldPinField = - | 'shouldPinAllowance' | 'shouldPinMaxStoragePrice' | 'shouldPinMaxUploadPrice' | 'shouldPinMaxDownloadPrice' diff --git a/apps/renterd/contexts/config/deriveAllowance.spec.ts b/apps/renterd/contexts/config/spending.spec.ts similarity index 76% rename from apps/renterd/contexts/config/deriveAllowance.spec.ts rename to apps/renterd/contexts/config/spending.spec.ts index f53d0f328..2e230420a 100644 --- a/apps/renterd/contexts/config/deriveAllowance.spec.ts +++ b/apps/renterd/contexts/config/spending.spec.ts @@ -1,12 +1,12 @@ import BigNumber from 'bignumber.js' import { - calculateIdealAllowance, - derivePricingFromAllowance, -} from './deriveAllowance' + calculateSpendingEstimate as calculateSpendingEstimate, + derivePricingFromSpendingEstimate, +} from './spending' -test('with estimates of 1 TB, standard weights, 1x redundancy, 1x allowance factor', () => { - const allowanceMonth = new BigNumber(1000) - const allowanceFactor = 1 +test('with estimates of 1 TB, standard weights, 1x redundancy, 1x spending factor', () => { + const estimatedSpendingPerMonth = new BigNumber(1000) + const maxPricingFactor = 1 const storageTB = new BigNumber(1) const downloadTBMonth = new BigNumber(1) const uploadTBMonth = new BigNumber(1) @@ -15,9 +15,9 @@ test('with estimates of 1 TB, standard weights, 1x redundancy, 1x allowance fact const downloadWeight = 5 const uploadWeight = 1 - const prices = derivePricingFromAllowance({ - allowanceMonth, - allowanceFactor, + const prices = derivePricingFromSpendingEstimate({ + estimatedSpendingPerMonth, + maxPricingFactor, storageTB, downloadTBMonth, uploadTBMonth, @@ -38,22 +38,22 @@ test('with estimates of 1 TB, standard weights, 1x redundancy, 1x allowance fact maxUploadPriceTB: '100.00', }) expect( - calculateIdealAllowance({ + calculateSpendingEstimate({ maxStoragePriceTBMonth: prices?.maxStoragePriceTBMonth, maxDownloadPriceTB: prices?.maxDownloadPriceTB, maxUploadPriceTB: prices?.maxUploadPriceTB, - allowanceFactor, + maxPricingFactor, storageTB, downloadTBMonth, uploadTBMonth, redundancyMultiplier, }) - ).toEqual(allowanceMonth) + ).toEqual(estimatedSpendingPerMonth) }) -test('with estimates of 1 TB, standard weights, 2x redundancy, 1x allowance factor', () => { - const allowanceMonth = new BigNumber(1000) - const allowanceFactor = 1 +test('with estimates of 1 TB, standard weights, 2x redundancy, 1x spending factor', () => { + const estimatedSpendingPerMonth = new BigNumber(1000) + const maxPricingFactor = 1 const storageTB = new BigNumber(1) const downloadTBMonth = new BigNumber(1) const uploadTBMonth = new BigNumber(1) @@ -62,9 +62,9 @@ test('with estimates of 1 TB, standard weights, 2x redundancy, 1x allowance fact const downloadWeight = 5 const uploadWeight = 1 - const prices = derivePricingFromAllowance({ - allowanceMonth, - allowanceFactor, + const prices = derivePricingFromSpendingEstimate({ + estimatedSpendingPerMonth, + maxPricingFactor, storageTB, downloadTBMonth, uploadTBMonth, @@ -85,22 +85,22 @@ test('with estimates of 1 TB, standard weights, 2x redundancy, 1x allowance fact maxUploadPriceTB: '66.67', }) expect( - calculateIdealAllowance({ + calculateSpendingEstimate({ maxStoragePriceTBMonth: prices?.maxStoragePriceTBMonth, maxDownloadPriceTB: prices?.maxDownloadPriceTB, maxUploadPriceTB: prices?.maxUploadPriceTB, - allowanceFactor, + maxPricingFactor, storageTB, downloadTBMonth, uploadTBMonth, redundancyMultiplier, }) - ).toEqual(allowanceMonth) + ).toEqual(estimatedSpendingPerMonth) }) -test('with estimates of 1 TB, standard weights, 3x redunancy, 1x allowance factor', () => { - const allowanceMonth = new BigNumber(1000) - const allowanceFactor = 1 +test('with estimates of 1 TB, standard weights, 3x redunancy, 1x spending factor', () => { + const estimatedSpendingPerMonth = new BigNumber(1000) + const maxPricingFactor = 1 const storageTB = new BigNumber(1) const downloadTBMonth = new BigNumber(1) const uploadTBMonth = new BigNumber(1) @@ -109,9 +109,9 @@ test('with estimates of 1 TB, standard weights, 3x redunancy, 1x allowance facto const downloadWeight = 5 const uploadWeight = 1 - const prices = derivePricingFromAllowance({ - allowanceMonth, - allowanceFactor, + const prices = derivePricingFromSpendingEstimate({ + estimatedSpendingPerMonth, + maxPricingFactor, storageTB, downloadTBMonth, uploadTBMonth, @@ -132,22 +132,22 @@ test('with estimates of 1 TB, standard weights, 3x redunancy, 1x allowance facto maxUploadPriceTB: '50.00', }) expect( - calculateIdealAllowance({ + calculateSpendingEstimate({ maxStoragePriceTBMonth: prices?.maxStoragePriceTBMonth, maxDownloadPriceTB: prices?.maxDownloadPriceTB, maxUploadPriceTB: prices?.maxUploadPriceTB, - allowanceFactor, + maxPricingFactor, storageTB, downloadTBMonth, uploadTBMonth, redundancyMultiplier, }) - ).toEqual(allowanceMonth) + ).toEqual(estimatedSpendingPerMonth) }) -test('with estimates of 1 TB, standard weights, 1x redundancy, 2x allowance factor', () => { - const allowanceMonth = new BigNumber(1000) - const allowanceFactor = 2 +test('with estimates of 1 TB, standard weights, 1x redundancy, 2x spending factor', () => { + const estimatedSpendingPerMonth = new BigNumber(1000) + const maxPricingFactor = 2 const storageTB = new BigNumber(1) const downloadTBMonth = new BigNumber(1) const uploadTBMonth = new BigNumber(1) @@ -156,9 +156,9 @@ test('with estimates of 1 TB, standard weights, 1x redundancy, 2x allowance fact const downloadWeight = 5 const uploadWeight = 1 - const prices = derivePricingFromAllowance({ - allowanceMonth, - allowanceFactor, + const prices = derivePricingFromSpendingEstimate({ + estimatedSpendingPerMonth, + maxPricingFactor, storageTB, downloadTBMonth, uploadTBMonth, @@ -179,22 +179,22 @@ test('with estimates of 1 TB, standard weights, 1x redundancy, 2x allowance fact maxUploadPriceTB: '200.00', }) expect( - calculateIdealAllowance({ + calculateSpendingEstimate({ maxStoragePriceTBMonth: prices?.maxStoragePriceTBMonth, maxDownloadPriceTB: prices?.maxDownloadPriceTB, maxUploadPriceTB: prices?.maxUploadPriceTB, - allowanceFactor, + maxPricingFactor, storageTB, downloadTBMonth, uploadTBMonth, redundancyMultiplier, }) - ).toEqual(allowanceMonth) + ).toEqual(estimatedSpendingPerMonth) }) -test('with estimates of 1 TB, standard weights, 2x redundancy, 2x allowance factor', () => { - const allowanceMonth = new BigNumber(1000) - const allowanceFactor = 2 +test('with estimates of 1 TB, standard weights, 2x redundancy, 2x spending factor', () => { + const estimatedSpendingPerMonth = new BigNumber(1000) + const maxPricingFactor = 2 const storageTB = new BigNumber(1) const downloadTBMonth = new BigNumber(1) const uploadTBMonth = new BigNumber(1) @@ -203,9 +203,9 @@ test('with estimates of 1 TB, standard weights, 2x redundancy, 2x allowance fact const downloadWeight = 5 const uploadWeight = 1 - const prices = derivePricingFromAllowance({ - allowanceMonth, - allowanceFactor, + const prices = derivePricingFromSpendingEstimate({ + estimatedSpendingPerMonth, + maxPricingFactor, storageTB, downloadTBMonth, uploadTBMonth, @@ -226,22 +226,22 @@ test('with estimates of 1 TB, standard weights, 2x redundancy, 2x allowance fact maxUploadPriceTB: '133.33', }) expect( - calculateIdealAllowance({ + calculateSpendingEstimate({ maxStoragePriceTBMonth: prices?.maxStoragePriceTBMonth, maxDownloadPriceTB: prices?.maxDownloadPriceTB, maxUploadPriceTB: prices?.maxUploadPriceTB, - allowanceFactor, + maxPricingFactor, storageTB, downloadTBMonth, uploadTBMonth, redundancyMultiplier, }) - ).toEqual(allowanceMonth) + ).toEqual(estimatedSpendingPerMonth) }) -test('with estimates of 1 TB, standard weights, 3x redunancy, 2x allowance factor', () => { - const allowanceMonth = new BigNumber(1000) - const allowanceFactor = 2 +test('with estimates of 1 TB, standard weights, 3x redunancy, 2x spending factor', () => { + const estimatedSpendingPerMonth = new BigNumber(1000) + const maxPricingFactor = 2 const storageTB = new BigNumber(1) const downloadTBMonth = new BigNumber(1) const uploadTBMonth = new BigNumber(1) @@ -250,9 +250,9 @@ test('with estimates of 1 TB, standard weights, 3x redunancy, 2x allowance facto const downloadWeight = 5 const uploadWeight = 1 - const prices = derivePricingFromAllowance({ - allowanceMonth, - allowanceFactor, + const prices = derivePricingFromSpendingEstimate({ + estimatedSpendingPerMonth, + maxPricingFactor, storageTB, downloadTBMonth, uploadTBMonth, @@ -273,22 +273,22 @@ test('with estimates of 1 TB, standard weights, 3x redunancy, 2x allowance facto maxUploadPriceTB: '100.00', }) expect( - calculateIdealAllowance({ + calculateSpendingEstimate({ maxStoragePriceTBMonth: prices?.maxStoragePriceTBMonth, maxDownloadPriceTB: prices?.maxDownloadPriceTB, maxUploadPriceTB: prices?.maxUploadPriceTB, - allowanceFactor, + maxPricingFactor, storageTB, downloadTBMonth, uploadTBMonth, redundancyMultiplier, }) - ).toEqual(allowanceMonth) + ).toEqual(estimatedSpendingPerMonth) }) -test('with mostly storage, standard weights, 1x redunancy, 2x allowance factor', () => { - const allowanceMonth = new BigNumber(10_000) - const allowanceFactor = 2 +test('with mostly storage, standard weights, 1x redunancy, 2x spending factor', () => { + const estimatedSpendingPerMonth = new BigNumber(10_000) + const maxPricingFactor = 2 const storageTB = new BigNumber(1) const downloadTBMonth = new BigNumber(20) const uploadTBMonth = new BigNumber(1) @@ -297,9 +297,9 @@ test('with mostly storage, standard weights, 1x redunancy, 2x allowance factor', const downloadWeight = 5 const uploadWeight = 1 - const prices = derivePricingFromAllowance({ - allowanceMonth, - allowanceFactor, + const prices = derivePricingFromSpendingEstimate({ + estimatedSpendingPerMonth, + maxPricingFactor, storageTB, downloadTBMonth, uploadTBMonth, @@ -320,22 +320,22 @@ test('with mostly storage, standard weights, 1x redunancy, 2x allowance factor', maxUploadPriceTB: '190.48', }) expect( - calculateIdealAllowance({ + calculateSpendingEstimate({ maxStoragePriceTBMonth: prices?.maxStoragePriceTBMonth, maxDownloadPriceTB: prices?.maxDownloadPriceTB, maxUploadPriceTB: prices?.maxUploadPriceTB, - allowanceFactor, + maxPricingFactor, storageTB, downloadTBMonth, uploadTBMonth, redundancyMultiplier, }) - ).toEqual(allowanceMonth) + ).toEqual(estimatedSpendingPerMonth) }) -test('with varied estimates, standard weights, 1x redunancy, 2x allowance factor', () => { - const allowanceMonth = new BigNumber(1000) - const allowanceFactor = 2 +test('with varied estimates, standard weights, 1x redunancy, 2x spending factor', () => { + const estimatedSpendingPerMonth = new BigNumber(1000) + const maxPricingFactor = 2 const storageTB = new BigNumber(8) const downloadTBMonth = new BigNumber(15) const uploadTBMonth = new BigNumber(3) @@ -344,9 +344,9 @@ test('with varied estimates, standard weights, 1x redunancy, 2x allowance factor const downloadWeight = 5 const uploadWeight = 1 - const prices = derivePricingFromAllowance({ - allowanceMonth, - allowanceFactor, + const prices = derivePricingFromSpendingEstimate({ + estimatedSpendingPerMonth, + maxPricingFactor, storageTB, downloadTBMonth, uploadTBMonth, @@ -367,21 +367,21 @@ test('with varied estimates, standard weights, 1x redunancy, 2x allowance factor maxUploadPriceTB: '18.18', }) expect( - calculateIdealAllowance({ + calculateSpendingEstimate({ maxStoragePriceTBMonth: prices?.maxStoragePriceTBMonth, maxDownloadPriceTB: prices?.maxDownloadPriceTB, maxUploadPriceTB: prices?.maxUploadPriceTB, - allowanceFactor, + maxPricingFactor, storageTB, downloadTBMonth, uploadTBMonth, redundancyMultiplier, }) - ).toEqual(allowanceMonth) + ).toEqual(estimatedSpendingPerMonth) }) -test('with estimates of 1 TB, standard weights, 1x redundancy, 1.5x allowance factor', () => { - const allowanceMonth = new BigNumber(1000) +test('with estimates of 1 TB, standard weights, 1x redundancy, 1.5x spending factor', () => { + const estimatedSpendingPerMonth = new BigNumber(1000) const storageTB = new BigNumber(1) const downloadTBMonth = new BigNumber(1) const uploadTBMonth = new BigNumber(1) @@ -389,11 +389,11 @@ test('with estimates of 1 TB, standard weights, 1x redundancy, 1.5x allowance fa const storageWeight = 4 const downloadWeight = 5 const uploadWeight = 1 - const allowanceFactor = 1.5 + const maxPricingFactor = 1.5 - const prices = derivePricingFromAllowance({ - allowanceMonth, - allowanceFactor, + const prices = derivePricingFromSpendingEstimate({ + estimatedSpendingPerMonth, + maxPricingFactor, storageTB, downloadTBMonth, uploadTBMonth, @@ -414,21 +414,21 @@ test('with estimates of 1 TB, standard weights, 1x redundancy, 1.5x allowance fa maxUploadPriceTB: '150.00', }) expect( - calculateIdealAllowance({ + calculateSpendingEstimate({ maxStoragePriceTBMonth: prices?.maxStoragePriceTBMonth, maxDownloadPriceTB: prices?.maxDownloadPriceTB, maxUploadPriceTB: prices?.maxUploadPriceTB, - allowanceFactor, + maxPricingFactor, storageTB, downloadTBMonth, uploadTBMonth, redundancyMultiplier, }) - ).toEqual(allowanceMonth) + ).toEqual(estimatedSpendingPerMonth) }) -test('with varied estimates, standard weights, 1x redundancy, 1.5x allowance factor', () => { - const allowanceMonth = new BigNumber(1000) +test('with varied estimates, standard weights, 1x redundancy, 1.5x spending factor', () => { + const estimatedSpendingPerMonth = new BigNumber(1000) const storageTB = new BigNumber(8) const downloadTBMonth = new BigNumber(15) const uploadTBMonth = new BigNumber(3) @@ -436,11 +436,11 @@ test('with varied estimates, standard weights, 1x redundancy, 1.5x allowance fac const storageWeight = 4 const downloadWeight = 5 const uploadWeight = 1 - const allowanceFactor = 1.5 + const maxPricingFactor = 1.5 - const prices = derivePricingFromAllowance({ - allowanceMonth, - allowanceFactor, + const prices = derivePricingFromSpendingEstimate({ + estimatedSpendingPerMonth, + maxPricingFactor, storageTB, downloadTBMonth, uploadTBMonth, @@ -461,28 +461,28 @@ test('with varied estimates, standard weights, 1x redundancy, 1.5x allowance fac maxUploadPriceTB: '13.64', }) expect( - calculateIdealAllowance({ + calculateSpendingEstimate({ maxStoragePriceTBMonth: prices?.maxStoragePriceTBMonth, maxDownloadPriceTB: prices?.maxDownloadPriceTB, maxUploadPriceTB: prices?.maxUploadPriceTB, - allowanceFactor, + maxPricingFactor, storageTB, downloadTBMonth, uploadTBMonth, redundancyMultiplier, }) - ).toEqual(allowanceMonth) + ).toEqual(estimatedSpendingPerMonth) }) -test('with zero allowance returns null', () => { - const allowanceMonth = new BigNumber(0) +test('with zero spending returns null', () => { + const estimatedSpendingPerMonth = new BigNumber(0) const storageTB = new BigNumber(8) const downloadTBMonth = new BigNumber(15) const uploadTBMonth = new BigNumber(3) const redundancyMultiplier = new BigNumber(1) - const prices = derivePricingFromAllowance({ - allowanceMonth, + const prices = derivePricingFromSpendingEstimate({ + estimatedSpendingPerMonth, storageTB, downloadTBMonth, uploadTBMonth, @@ -501,16 +501,16 @@ test('with zero prices returns null', () => { const uploadTBMonth = new BigNumber(1) const redundancyMultiplier = new BigNumber(1) - const allowance = calculateIdealAllowance({ + const spending = calculateSpendingEstimate({ maxDownloadPriceTB, maxStoragePriceTBMonth, maxUploadPriceTB, - allowanceFactor: 1.5, + maxPricingFactor: 1.5, storageTB, downloadTBMonth, uploadTBMonth, redundancyMultiplier, }) - expect(allowance).toBeUndefined() + expect(spending).toBeUndefined() }) diff --git a/apps/renterd/contexts/config/deriveAllowance.ts b/apps/renterd/contexts/config/spending.ts similarity index 70% rename from apps/renterd/contexts/config/deriveAllowance.ts rename to apps/renterd/contexts/config/spending.ts index b1be817b5..bc6e80f0e 100644 --- a/apps/renterd/contexts/config/deriveAllowance.ts +++ b/apps/renterd/contexts/config/spending.ts @@ -1,15 +1,20 @@ -import { calculateEstimatedSpending } from '@siafoundation/units' +import { calculateMaxSpending } from '@siafoundation/units' import BigNumber from 'bignumber.js' +export const defaultMaxPricingFactor = 1.5 +export const defaultStorageWeight = 4 +export const defaultDownloadWeight = 5 +export const defaultUploadWeight = 1 + /** * This function calculates the max price per TB for storage, download, and - * upload given a total spending allowance and estimated usage in TB for each + * upload given a monthly spending estimate and usage estimates in TB for each * type. The prices are kept proportional to each other using the weight factors * provided as parameters. Storage and upload are also scaled by the redundancy * multiplier. * * The total cost equation is: - * allowance * allowanceFactor = scaledAllowance = + * spendingMonth * maxPricingFactor = scaledSpending = * storageTBWithRedundancy * storageWeight * unitCost + * downloadTBMonth * downloadWeight * unitCost + * uploadTBMonthWithRedundancy * uploadWeight * unitCost @@ -19,15 +24,15 @@ import BigNumber from 'bignumber.js' * maxDownloadPriceTB = unitCost * downloadWeight * maxStoragePriceTBMonth = unitCost * storageWeight * - * The function also includes an allowance scaling factor to account for + * The function also includes an spending scaling factor to account for * contract price variation. For example, if a user expects to pay $2 per TB, * they should set the max value to $4 per TB because the optimal contracts may * vary, eg: one contract at $1 and another at $3 which average to $2. The * scaling factor helps avoid unnecessary churn. * * @param params - The parameters for the function. - * @param params.allowanceMonth - The total spending allowance per month. - * @param params.allowanceFactor - The scaling factor to apply to the allowance. + * @param params.spendingMonth - The total spending per month. + * @param params.maxPricingFactor - The scaling factor to apply to the max prices. * @param params.storageTB - The estimated amount of storage in TB. * @param params.downloadTBMonth - The estimated amount of download in TB per * month. @@ -39,19 +44,19 @@ import BigNumber from 'bignumber.js' * @returns An object containing the price per TB for storage, download, and * upload. */ -export function derivePricingFromAllowance({ - allowanceMonth, - allowanceFactor = 1.5, +export function derivePricingFromSpendingEstimate({ + estimatedSpendingPerMonth, + maxPricingFactor = defaultMaxPricingFactor, storageTB, downloadTBMonth, uploadTBMonth, redundancyMultiplier, - storageWeight = 4, - downloadWeight = 5, - uploadWeight = 1, + storageWeight = defaultStorageWeight, + downloadWeight = defaultDownloadWeight, + uploadWeight = defaultUploadWeight, }: { - allowanceMonth?: BigNumber - allowanceFactor?: number + estimatedSpendingPerMonth?: BigNumber + maxPricingFactor?: number storageTB?: BigNumber downloadTBMonth?: BigNumber uploadTBMonth?: BigNumber @@ -62,8 +67,8 @@ export function derivePricingFromAllowance({ }) { // Return undefined if zero values are provided. if ( - !allowanceMonth?.gt(0) || - allowanceFactor <= 0 || + !estimatedSpendingPerMonth?.gt(0) || + maxPricingFactor <= 0 || !redundancyMultiplier?.gt(0) || !storageTB?.gt(0) || !downloadTBMonth?.gt(0) || @@ -71,14 +76,14 @@ export function derivePricingFromAllowance({ ) { return undefined } - // Apply scaling factor to allowance. - const scaledAllowance = allowanceMonth.times(allowanceFactor) + // Apply scaling factor to spending. + const scaledSpending = estimatedSpendingPerMonth.times(maxPricingFactor) const storageTBWithRedundancy = storageTB.times(redundancyMultiplier) const uploadTBMonthWithRedundancy = uploadTBMonth.times(redundancyMultiplier) - // Calculate the unit cost based on the provided allowance and usage estimates. - const unitCost = scaledAllowance.div( + // Calculate the unit cost based on the provided spending and usage estimates. + const unitCost = scaledSpending.div( storageTBWithRedundancy .times(storageWeight) .plus(downloadTBMonth.times(downloadWeight)) @@ -98,7 +103,7 @@ export function derivePricingFromAllowance({ } /** - * This function calculates the ideal allowance per month given the + * This function calculates the estimated spending per month given the * max price per TB for storage, download, and upload, along with estimated * usage in TB for each type. * @@ -106,18 +111,18 @@ export function derivePricingFromAllowance({ * @param params.maxStoragePriceTBMonth - The max price per TB per month for storage. * @param params.maxDownloadPriceTB - The max price per TB for download. * @param params.maxUploadPriceTB - The max price per TB for upload. - * @param params.allowanceFactor - The scaling factor applied to the allowance. + * @param params.maxPricingFactor - The scaling factor applied to the spending. * @param params.storageTB - The estimated amount of storage in TB. * @param params.downloadTBMonth - The estimated amount of download in TB per month. * @param params.uploadTBMonth - The estimated amount of upload in TB per month. * @param params.redundancyMultiplier - The redundancy multiplier. - * @returns The calculated allowance per month. + * @returns The calculated spending per month. */ -export function calculateIdealAllowance({ +export function calculateSpendingEstimate({ maxStoragePriceTBMonth, maxDownloadPriceTB, maxUploadPriceTB, - allowanceFactor = 1.5, + maxPricingFactor = defaultMaxPricingFactor, storageTB, downloadTBMonth, uploadTBMonth, @@ -126,7 +131,7 @@ export function calculateIdealAllowance({ maxStoragePriceTBMonth?: BigNumber maxDownloadPriceTB?: BigNumber maxUploadPriceTB?: BigNumber - allowanceFactor?: number + maxPricingFactor?: number storageTB?: BigNumber downloadTBMonth?: BigNumber uploadTBMonth?: BigNumber @@ -137,7 +142,7 @@ export function calculateIdealAllowance({ !maxStoragePriceTBMonth?.gt(0) || !maxDownloadPriceTB?.gt(0) || !maxUploadPriceTB?.gt(0) || - allowanceFactor <= 0 || + maxPricingFactor <= 0 || !redundancyMultiplier?.gt(0) || !storageTB?.gt(0) || !downloadTBMonth?.gt(0) || @@ -146,8 +151,8 @@ export function calculateIdealAllowance({ return undefined } - // Calculate the esimated spending. - const scaledSpending = calculateEstimatedSpending({ + // Calculate the max spending. + const maxSpending = calculateMaxSpending({ maxStoragePriceTBMonth, maxDownloadPriceTB, maxUploadPriceTB, @@ -157,8 +162,9 @@ export function calculateIdealAllowance({ redundancyMultiplier, }) - // Calculate the ideal allowance by dividing the spending by the allowance factor. - const allowanceMonth = scaledSpending?.div(allowanceFactor) + // Calculate the spending estimate by dividing the max spending by the + // max pricing factor. + const spendingMonth = maxSpending?.div(maxPricingFactor) - return allowanceMonth?.integerValue() + return spendingMonth?.integerValue() } diff --git a/apps/renterd/contexts/config/deriveAllowanceConfig.ts b/apps/renterd/contexts/config/spendingConfig.ts similarity index 72% rename from apps/renterd/contexts/config/deriveAllowanceConfig.ts rename to apps/renterd/contexts/config/spendingConfig.ts index 782b836c0..5b8b5ea11 100644 --- a/apps/renterd/contexts/config/deriveAllowanceConfig.ts +++ b/apps/renterd/contexts/config/spendingConfig.ts @@ -1,4 +1,4 @@ -export const allowanceFactor = 1.5 +export const maxPricingFactor = 1.5 export const storageWeight = 4 export const downloadWeight = 5 export const uploadWeight = 1 diff --git a/apps/renterd/contexts/config/transform.spec.ts b/apps/renterd/contexts/config/transform.spec.ts index 7d91f6448..3a0ea1734 100644 --- a/apps/renterd/contexts/config/transform.spec.ts +++ b/apps/renterd/contexts/config/transform.spec.ts @@ -11,7 +11,6 @@ import { InputValues, SubmitValues } from './types' import { blocksToWeeks, weeksToBlocks, - toHastings, valuePerPeriodToPerMonth, valuePerMonthToPerPeriod, } from '@siafoundation/units' @@ -46,7 +45,6 @@ describe('tansforms', () => { }) ).toEqual({ autopilotContractSet: 'autopilot', - allowanceMonth: new BigNumber('357.142857'), amountHosts: new BigNumber('51'), periodWeeks: new BigNumber('6'), renewWindowWeeks: new BigNumber('2.2301587301587302'), @@ -72,11 +70,9 @@ describe('tansforms', () => { migrationSurchargeMultiplier: new BigNumber(10), minShards: new BigNumber(10), totalShards: new BigNumber(30), - allowanceMonthPinned: new BigNumber('71.43'), maxStoragePriceTBMonthPinned: new BigNumber('5'), maxDownloadPriceTBPinned: new BigNumber('4'), maxUploadPriceTBPinned: new BigNumber('2'), - shouldPinAllowance: false, shouldPinMaxDownloadPrice: false, shouldPinMaxUploadPrice: false, shouldPinMaxStoragePrice: false, @@ -141,7 +137,6 @@ describe('tansforms', () => { 'mainnet', { autopilotContractSet: 'autopilot', - allowanceMonth: new BigNumber('6006'), amountHosts: new BigNumber('51'), periodWeeks: new BigNumber('6'), renewWindowWeeks: new BigNumber('2.2301587301587302'), @@ -169,7 +164,6 @@ describe('tansforms', () => { contracts: { set: 'autopilot', amount: 51, - allowance: '8408400000000000000000000000', period: 6048, renewWindow: 2248, download: 1099511627776, @@ -185,7 +179,6 @@ describe('tansforms', () => { 'mainnet', { autopilotContractSet: 'autopilot', - allowanceMonth: new BigNumber('6006'), amountHosts: new BigNumber('51'), periodWeeks: new BigNumber('6'), renewWindowWeeks: new BigNumber('2.2301587301587302'), @@ -226,7 +219,6 @@ describe('tansforms', () => { foobar: 'value', set: 'autopilot', amount: 51, - allowance: '8408400000000000000000000000', period: 6048, renewWindow: 2248, download: 1099511627776, @@ -242,7 +234,6 @@ describe('tansforms', () => { 'zen', { autopilotContractSet: 'autopilot', - allowanceMonth: new BigNumber('6006'), amountHosts: undefined, periodWeeks: new BigNumber('6'), renewWindowWeeks: new BigNumber('2.2301587301587302'), @@ -276,7 +267,6 @@ describe('tansforms', () => { contracts: { set: 'autopilot', amount: 12, - allowance: '8408400000000000000000000000', period: 6048, renewWindow: 2248, download: 1099511627776, @@ -294,7 +284,6 @@ describe('tansforms', () => { transformUpGouging( { autopilotContractSet: 'autopilot', - allowanceMonth: new BigNumber('6006'), amountHosts: new BigNumber('51'), periodWeeks: new BigNumber('6'), renewWindowWeeks: new BigNumber('2.2301587301587302'), @@ -322,11 +311,9 @@ describe('tansforms', () => { minShards: new BigNumber(10), totalShards: new BigNumber(30), migrationSurchargeMultiplier: new BigNumber(10), - allowanceMonthPinned: new BigNumber('0'), maxStoragePriceTBMonthPinned: new BigNumber('0'), maxDownloadPriceTBPinned: new BigNumber('0'), maxUploadPriceTBPinned: new BigNumber('0'), - shouldPinAllowance: false, shouldPinMaxDownloadPrice: false, shouldPinMaxUploadPrice: false, shouldPinMaxStoragePrice: false, @@ -362,8 +349,6 @@ describe('tansforms', () => { { pinnedCurrency: 'usd', pinnedThreshold: new BigNumber(1), - shouldPinAllowance: true, - allowanceMonthPinned: new BigNumber('1000'), shouldPinMaxStoragePrice: true, maxStoragePriceTBMonthPinned: new BigNumber('2000'), shouldPinMaxUploadPrice: true, @@ -382,14 +367,6 @@ describe('tansforms', () => { ).toEqual({ currency: 'usd' as CurrencyId, threshold: 0.01, - autopilots: { - autopilot: { - allowance: { - pinned: true, - value: 1400, - }, - }, - }, gougingSettingsPins: { maxStorage: { pinned: true, @@ -596,7 +573,6 @@ function buildAllResponses() { contracts: { set: 'autopilot', amount: 51, - allowance: toHastings(500).toString(), period: weeksToBlocks(6), renewWindow: 2248, download: 1099511627776, @@ -620,15 +596,6 @@ function buildAllResponses() { pinned: { currency: 'usd' as CurrencyId, threshold: 0.1, - autopilots: { - // The default autopilot named 'autopilot'. - autopilot: { - allowance: { - pinned: false, - value: 100, - }, - }, - }, gougingSettingsPins: { maxStorage: { pinned: false, diff --git a/apps/renterd/contexts/config/transformDown.ts b/apps/renterd/contexts/config/transformDown.ts index d259f2b72..4ecd6381f 100644 --- a/apps/renterd/contexts/config/transformDown.ts +++ b/apps/renterd/contexts/config/transformDown.ts @@ -1,8 +1,4 @@ -import { - toFixedMaxBigNumber, - toFixedMaxString, -} from '@siafoundation/design-system' -import { currencyOptions } from '@siafoundation/react-core' +import { toFixedMaxString } from '@siafoundation/design-system' import { AutopilotConfig, SettingsGouging, @@ -19,7 +15,6 @@ import { valuePerByteToPerTB, valuePerOneToPerMillion, valuePerPeriodToPerMonth, - weeksToBlocks, } from '@siafoundation/units' import BigNumber from 'bignumber.js' import { @@ -41,13 +36,6 @@ export function transformDownAutopilot( } const autopilotContractSet = config.contracts.set - const allowanceMonth = toSiacoins( - valuePerPeriodToPerMonth( - new BigNumber(config.contracts.allowance), - config.contracts.period - ), - scDecimalPlaces - ) const amountHosts = new BigNumber(config.contracts.amount) const periodWeeks = new BigNumber(blocksToWeeks(config.contracts.period)) const renewWindowWeeks = new BigNumber( @@ -77,7 +65,6 @@ export function transformDownAutopilot( return { // contracts autopilotContractSet, - allowanceMonth, amountHosts, periodWeeks, renewWindowWeeks, @@ -186,24 +173,10 @@ export function transformDownGouging({ } } -export function transformDownPinned( - p: SettingsPinned, - autopilotID = 'autopilot', - periodBlocks?: number -): ValuesPinned { - const fixedFiat = currencyOptions.find((c) => c.id === p.currency)?.fixed || 6 +export function transformDownPinned(p: SettingsPinned): ValuesPinned { return { pinnedCurrency: p.currency, pinnedThreshold: new BigNumber(p.threshold).times(100), - shouldPinAllowance: p.autopilots?.[autopilotID]?.allowance.pinned || false, - allowanceMonthPinned: toFixedMaxBigNumber( - valuePerPeriodToPerMonth( - new BigNumber(p.autopilots?.[autopilotID]?.allowance.value || 0), - // If pinned allowance is non zero, the period value will be defined. - periodBlocks || weeksToBlocks(6) - ), - fixedFiat - ), shouldPinMaxStoragePrice: p.gougingSettingsPins?.maxStorage.pinned, maxStoragePriceTBMonthPinned: new BigNumber( p.gougingSettingsPins.maxStorage.value @@ -263,7 +236,7 @@ export function transformDown({ hasBeenConfigured, }), // pinning - ...transformDownPinned(pinned, autopilotID, autopilot?.contracts.period), + ...transformDownPinned(pinned), // upload ...transformDownUpload(upload), } diff --git a/apps/renterd/contexts/config/transformUp.ts b/apps/renterd/contexts/config/transformUp.ts index 61c2afc3c..3e3598d46 100644 --- a/apps/renterd/contexts/config/transformUp.ts +++ b/apps/renterd/contexts/config/transformUp.ts @@ -52,9 +52,6 @@ export function transformUpAutopilot( ...existingValues?.contracts, set: v.autopilotContractSet, amount: Math.round(v.amountHosts.toNumber()), - allowance: toHastings( - valuePerMonthToPerPeriod(v.allowanceMonth, v.periodWeeks) - ).toString(), period: Math.round(weeksToBlocks(v.periodWeeks.toNumber())), renewWindow: Math.round(weeksToBlocks(v.renewWindowWeeks.toNumber())), download: Number( @@ -133,19 +130,6 @@ export function transformUpPinned( ...existingValues, currency: v.pinnedCurrency, threshold: v.pinnedThreshold.div(100).toNumber(), - autopilots: { - [autopilotID]: { - allowance: { - pinned: v.shouldPinAllowance, - value: valuePerMonthToPerPeriod( - v.allowanceMonthPinned, - // If autopilot is disabled the period value may be undefined, - // but in that case the pinned allowance is also unused. - v.periodWeeks || new BigNumber(6) - ).toNumber(), - }, - }, - }, gougingSettingsPins: { maxStorage: { pinned: v.shouldPinMaxStoragePrice, diff --git a/apps/renterd/contexts/config/types.ts b/apps/renterd/contexts/config/types.ts index 4facda895..b147953c1 100644 --- a/apps/renterd/contexts/config/types.ts +++ b/apps/renterd/contexts/config/types.ts @@ -21,7 +21,6 @@ export const inputValuesAutopilot = { // contracts autopilotContractSet: '', amountHosts: undefined as BigNumber | undefined, - allowanceMonth: undefined as BigNumber | undefined, periodWeeks: undefined as BigNumber | undefined, renewWindowWeeks: undefined as BigNumber | undefined, downloadTBMonth: undefined as BigNumber | undefined, @@ -57,8 +56,6 @@ export const inputValuesPinned = { maxDownloadPriceTBPinned: undefined as BigNumber | undefined, shouldPinMaxUploadPrice: false, maxUploadPriceTBPinned: undefined as BigNumber | undefined, - shouldPinAllowance: false, - allowanceMonthPinned: undefined as BigNumber | undefined, } export const inputValuesUpload = { diff --git a/apps/renterd/contexts/config/useAllowanceDerivedPricing.tsx b/apps/renterd/contexts/config/useAllowanceDerivedPricing.tsx deleted file mode 100644 index 81522e098..000000000 --- a/apps/renterd/contexts/config/useAllowanceDerivedPricing.tsx +++ /dev/null @@ -1,282 +0,0 @@ -import { fiatToSiacoin, siacoinToFiat } from '@siafoundation/units' -import BigNumber from 'bignumber.js' -import { useMemo } from 'react' -import { UseFormReturn } from 'react-hook-form' -import { useApp } from '../app' -import { - calculateIdealAllowance, - derivePricingFromAllowance, -} from './deriveAllowance' -import { - allowanceFactor, - downloadWeight, - storageWeight, - uploadWeight, -} from './deriveAllowanceConfig' -import { InputValues } from './types' -import { useFormExchangeRate } from './useFormExchangeRate' -import { useRedundancyMultiplier } from './useRedundancyMultiplier' -import { Maybe } from '@siafoundation/design-system' - -// Convert the allowance to pricing values. This method returns values for -// pinned or non-pinned values depending on which is enabled on a per-field basis. -export function useAllowanceDerivedPricingForEnabledFields({ - form, -}: { - form: UseFormReturn -}): Maybe<{ - maxStoragePriceTBMonth?: BigNumber - maxDownloadPriceTB?: BigNumber - maxUploadPriceTB?: BigNumber - maxStoragePriceTBMonthPinned?: BigNumber - maxDownloadPriceTBPinned?: BigNumber - maxUploadPriceTBPinned?: BigNumber -}> { - const { isAutopilotEnabled } = useApp() - const allowanceMonth = useEnabledAllowanceInSiacoin({ - form, - }) - const storageTB = form.watch('storageTB') - const downloadTBMonth = form.watch('downloadTBMonth') - const uploadTBMonth = form.watch('uploadTBMonth') - const redundancyMultiplier = useRedundancyMultiplier({ - minShards: form.watch('minShards'), - totalShards: form.watch('totalShards'), - }) - const { rate } = useFormExchangeRate(form) - - const values = useMemo(() => { - if (isAutopilotEnabled) { - const derivedPricing = derivePricingFromAllowance({ - allowanceMonth, - allowanceFactor, - storageTB, - downloadTBMonth, - uploadTBMonth, - redundancyMultiplier, - storageWeight, - downloadWeight, - uploadWeight, - }) - if (!derivedPricing) { - return undefined - } - // Convert derived siacoin prices to pinned fiat prices. - const pinnedPricing = rate - ? { - maxStoragePriceTBMonthPinned: - derivedPricing?.maxStoragePriceTBMonth.times(rate), - maxDownloadPriceTBPinned: - derivedPricing?.maxDownloadPriceTB.times(rate), - maxUploadPriceTBPinned: - derivedPricing?.maxUploadPriceTB.times(rate), - } - : undefined - return { - ...derivedPricing, - ...pinnedPricing, - } - } - return undefined - }, [ - isAutopilotEnabled, - allowanceMonth, - storageTB, - downloadTBMonth, - uploadTBMonth, - redundancyMultiplier, - rate, - ]) - - const shouldPinMaxStoragePrice = form.watch('shouldPinMaxStoragePrice') - const shouldPinMaxUploadPrice = form.watch('shouldPinMaxUploadPrice') - const shouldPinMaxDownloadPrice = form.watch('shouldPinMaxDownloadPrice') - - const results: Partial = {} - - if (!values) { - return undefined - } - - if (shouldPinMaxStoragePrice) { - results.maxStoragePriceTBMonthPinned = values.maxStoragePriceTBMonthPinned - } else { - results.maxStoragePriceTBMonth = values.maxStoragePriceTBMonth - } - - if (shouldPinMaxUploadPrice) { - results.maxUploadPriceTBPinned = values.maxUploadPriceTBPinned - } else { - results.maxUploadPriceTB = values.maxUploadPriceTB - } - - if (shouldPinMaxDownloadPrice) { - results.maxDownloadPriceTBPinned = values.maxDownloadPriceTBPinned - } else { - results.maxDownloadPriceTB = values.maxDownloadPriceTB - } - - return results -} - -// Return each pricing value as siacoin. The siacoin value is converted from the -// pinned or non-pinned value depending on which is enabled on a per-field basis. -export function useEnabledPricingValuesInSiacoin({ - form, -}: { - form: UseFormReturn -}) { - const shouldPinMaxStoragePrice = form.watch('shouldPinMaxStoragePrice') - const shouldPinMaxUploadPrice = form.watch('shouldPinMaxUploadPrice') - const shouldPinMaxDownloadPrice = form.watch('shouldPinMaxDownloadPrice') - const maxStoragePriceTBMonth = form.watch('maxStoragePriceTBMonth') - const maxStoragePriceTBMonthPinned = form.watch( - 'maxStoragePriceTBMonthPinned' - ) - const maxDownloadPriceTB = form.watch('maxDownloadPriceTB') - const maxDownloadPriceTBPinned = form.watch('maxDownloadPriceTBPinned') - const maxUploadPriceTB = form.watch('maxUploadPriceTB') - const maxUploadPriceTBPinned = form.watch('maxUploadPriceTBPinned') - const { rate } = useFormExchangeRate(form) - - const needsExchangeRate = - shouldPinMaxStoragePrice || - shouldPinMaxDownloadPrice || - shouldPinMaxUploadPrice - - if (needsExchangeRate && !rate) { - return undefined - } - - if (shouldPinMaxStoragePrice && !maxStoragePriceTBMonthPinned) { - return undefined - } - - if (shouldPinMaxDownloadPrice && !maxDownloadPriceTBPinned) { - return undefined - } - - if (shouldPinMaxUploadPrice && !maxUploadPriceTBPinned) { - return undefined - } - - return { - maxStoragePriceTBMonth: - shouldPinMaxStoragePrice && maxStoragePriceTBMonthPinned && rate - ? fiatToSiacoin(maxStoragePriceTBMonthPinned, rate) - : maxStoragePriceTBMonth, - maxDownloadPriceTB: - shouldPinMaxDownloadPrice && maxDownloadPriceTBPinned && rate - ? fiatToSiacoin(maxDownloadPriceTBPinned, rate) - : maxDownloadPriceTB, - maxUploadPriceTB: - shouldPinMaxUploadPrice && maxUploadPriceTBPinned && rate - ? fiatToSiacoin(maxUploadPriceTBPinned, rate) - : maxUploadPriceTB, - } -} - -// Use the current pricing values to calculate the ideal allowance and return -// it as either allowanceMonth or allowanceMonthPinned depending on which is enabled. -export function useEnabledAllowanceFromEnabledPricingValues({ - form, -}: { - form: UseFormReturn -}): - | { - allowanceMonth?: BigNumber - allowanceMonthPinned?: BigNumber - } - | undefined { - const minShards = form.watch('minShards') - const totalShards = form.watch('totalShards') - const storageTB = form.watch('storageTB') - const downloadTBMonth = form.watch('downloadTBMonth') - const uploadTBMonth = form.watch('uploadTBMonth') - const shouldPinAllowance = form.watch('shouldPinAllowance') - const redundancyMultiplier = useRedundancyMultiplier({ - minShards, - totalShards, - }) - const prices = useEnabledPricingValuesInSiacoin({ - form, - }) - const { rate } = useFormExchangeRate(form) - - if (!prices) { - return undefined - } - - const { maxStoragePriceTBMonth, maxUploadPriceTB, maxDownloadPriceTB } = - prices - const allowance = calculateIdealAllowance({ - maxStoragePriceTBMonth, - maxDownloadPriceTB, - maxUploadPriceTB, - storageTB, - downloadTBMonth, - uploadTBMonth, - redundancyMultiplier, - }) - - const results: Partial = {} - - if (!allowance) { - return undefined - } - - if (shouldPinAllowance) { - if (!rate) { - return undefined - } - results.allowanceMonthPinned = allowance.times(rate) - } else { - results.allowanceMonth = allowance - } - return results -} - -// Returns the value of allowanceMonth or allowanceMonthPinned in siacoin, -// depending on which is enabled. -export function useEnabledAllowanceInSiacoin({ - form, -}: { - form: UseFormReturn -}): Maybe { - const shouldPinAllowance = form.watch('shouldPinAllowance') - const allowanceMonth = form.watch('allowanceMonth') - const allowanceMonthPinned = form.watch('allowanceMonthPinned') - const { rate } = useFormExchangeRate(form) - const needsExchangeRate = shouldPinAllowance - if (needsExchangeRate) { - if (rate) { - return allowanceMonthPinned?.div(rate) - } - return undefined - } - return allowanceMonth -} - -export function pricesToPinnedPrices({ - exchangeRate, - maxStoragePriceTBMonth, - maxDownloadPriceTB, - maxUploadPriceTB, -}: { - exchangeRate?: BigNumber - maxStoragePriceTBMonth: BigNumber - maxDownloadPriceTB: BigNumber - maxUploadPriceTB: BigNumber -}) { - if (!exchangeRate) { - return undefined - } - return { - maxStoragePriceTBMonthPinned: siacoinToFiat( - maxStoragePriceTBMonth, - exchangeRate - ), - maxDownloadPriceTBPinned: siacoinToFiat(maxDownloadPriceTB, exchangeRate), - maxUploadPriceTBPinned: siacoinToFiat(maxUploadPriceTB, exchangeRate), - } -} diff --git a/apps/renterd/contexts/config/useAutopilotEvaluations.tsx b/apps/renterd/contexts/config/useAutopilotEvaluations.tsx index 532c22ffe..f1d411f83 100644 --- a/apps/renterd/contexts/config/useAutopilotEvaluations.tsx +++ b/apps/renterd/contexts/config/useAutopilotEvaluations.tsx @@ -5,7 +5,12 @@ import { useAutopilotState, useBusState, } from '@siafoundation/renterd-react' -import { humanNumber, humanSiacoin, toHastings } from '@siafoundation/units' +import { + humanNumber, + humanSiacoin, + siacoinToFiat, + toHastings, +} from '@siafoundation/units' import BigNumber from 'bignumber.js' import { useCallback, useMemo } from 'react' import { UseFormReturn } from 'react-hook-form' @@ -24,11 +29,7 @@ import { getAdvancedDefaults, Values, } from './types' -import { - pricesToPinnedPrices, - useEnabledAllowanceInSiacoin, - useEnabledPricingValuesInSiacoin, -} from './useAllowanceDerivedPricing' +import { useEnabledPricingValuesInSiacoin } from './useEnabledPricingValuesInSiacoin' import { useFormExchangeRate } from './useFormExchangeRate' import { AutopilotConfigEvaluatePayload } from '@siafoundation/renterd-types' @@ -71,28 +72,16 @@ export function useAutopilotEvaluations({ ]) // Convert any pinned fields over to siacoin values. - const allowance = useEnabledAllowanceInSiacoin({ - form, - }) const pricing = useEnabledPricingValuesInSiacoin({ form, }) - const anyPinnedValuesAsSiacoin = useMemo(() => { - if (allowance) { - return { - allowanceMonth: allowance, - ...pricing, - } - } - return pricing - }, [allowance, pricing]) const currentValuesWithPinnedOverridesAndDefaults = useMemo(() => { // Any pinned values are converted to siacoin and merged into the // corresponding non-pinned fields. const valuesWithPinnedOverrides = { ...values, - ...anyPinnedValuesAsSiacoin, + ...pricing, } // We need to pass valid settings data into transformUp to get the payloads. // The form can be invalid or have empty fields depending on the mode, so we @@ -102,11 +91,7 @@ export function useAutopilotEvaluations({ valuesWithPinnedOverrides, resources.autopilotInfo.data?.state?.network ) - }, [ - values, - anyPinnedValuesAsSiacoin, - resources.autopilotInfo.data?.state?.network, - ]) + }, [values, pricing, resources.autopilotInfo.data?.state?.network]) const payloads = useMemo(() => { if (!hasDataToEvaluate || !renterdState.data) { @@ -403,6 +388,30 @@ function getRecommendationItem({ return rec } +function pricesToPinnedPrices({ + exchangeRate, + maxStoragePriceTBMonth, + maxDownloadPriceTB, + maxUploadPriceTB, +}: { + exchangeRate?: BigNumber + maxStoragePriceTBMonth: BigNumber + maxDownloadPriceTB: BigNumber + maxUploadPriceTB: BigNumber +}) { + if (!exchangeRate) { + return undefined + } + return { + maxStoragePriceTBMonthPinned: siacoinToFiat( + maxStoragePriceTBMonth, + exchangeRate + ), + maxDownloadPriceTBPinned: siacoinToFiat(maxDownloadPriceTB, exchangeRate), + maxUploadPriceTBPinned: siacoinToFiat(maxUploadPriceTB, exchangeRate), + } +} + // We just need some of the static metadata so pass in dummy values. const fields = getFields({ validationContext: { @@ -457,9 +466,6 @@ const fieldToLabel: Record = { export const valuesZeroDefaults: Values = { autopilotContractSet: '', amountHosts: new BigNumber(0), - shouldPinAllowance: false, - allowanceMonth: new BigNumber(0), - allowanceMonthPinned: new BigNumber(0), periodWeeks: new BigNumber(0), renewWindowWeeks: new BigNumber(0), downloadTBMonth: new BigNumber(0), diff --git a/apps/renterd/contexts/config/useEnabledPricingValuesInSiacoin.tsx b/apps/renterd/contexts/config/useEnabledPricingValuesInSiacoin.tsx new file mode 100644 index 000000000..3a69d76fa --- /dev/null +++ b/apps/renterd/contexts/config/useEnabledPricingValuesInSiacoin.tsx @@ -0,0 +1,76 @@ +import { fiatToSiacoin } from '@siafoundation/units' +import { useMemo } from 'react' +import { UseFormReturn } from 'react-hook-form' +import { InputValues } from './types' +import { useFormExchangeRate } from './useFormExchangeRate' + +// Return each pricing value as siacoin. The siacoin value is converted from the +// pinned or non-pinned value depending on which is enabled on a per-field basis. +export function useEnabledPricingValuesInSiacoin({ + form, +}: { + form: UseFormReturn +}) { + const shouldPinMaxStoragePrice = form.watch('shouldPinMaxStoragePrice') + const shouldPinMaxDownloadPrice = form.watch('shouldPinMaxDownloadPrice') + const shouldPinMaxUploadPrice = form.watch('shouldPinMaxUploadPrice') + const maxStoragePriceTBMonth = form.watch('maxStoragePriceTBMonth') + const maxStoragePriceTBMonthPinned = form.watch( + 'maxStoragePriceTBMonthPinned' + ) + const maxDownloadPriceTB = form.watch('maxDownloadPriceTB') + const maxDownloadPriceTBPinned = form.watch('maxDownloadPriceTBPinned') + const maxUploadPriceTB = form.watch('maxUploadPriceTB') + const maxUploadPriceTBPinned = form.watch('maxUploadPriceTBPinned') + const { rate } = useFormExchangeRate(form) + + const needsExchangeRate = + shouldPinMaxStoragePrice || + shouldPinMaxDownloadPrice || + shouldPinMaxUploadPrice + + return useMemo(() => { + if (needsExchangeRate && !rate) { + return undefined + } + + if (shouldPinMaxStoragePrice && !maxStoragePriceTBMonthPinned) { + return undefined + } + + if (shouldPinMaxDownloadPrice && !maxDownloadPriceTBPinned) { + return undefined + } + + if (shouldPinMaxUploadPrice && !maxUploadPriceTBPinned) { + return undefined + } + + return { + maxStoragePriceTBMonth: + shouldPinMaxStoragePrice && maxStoragePriceTBMonthPinned && rate + ? fiatToSiacoin(maxStoragePriceTBMonthPinned, rate) + : maxStoragePriceTBMonth, + maxDownloadPriceTB: + shouldPinMaxDownloadPrice && maxDownloadPriceTBPinned && rate + ? fiatToSiacoin(maxDownloadPriceTBPinned, rate) + : maxDownloadPriceTB, + maxUploadPriceTB: + shouldPinMaxUploadPrice && maxUploadPriceTBPinned && rate + ? fiatToSiacoin(maxUploadPriceTBPinned, rate) + : maxUploadPriceTB, + } + }, [ + needsExchangeRate, + rate, + shouldPinMaxStoragePrice, + maxStoragePriceTBMonthPinned, + shouldPinMaxDownloadPrice, + maxDownloadPriceTBPinned, + shouldPinMaxUploadPrice, + maxUploadPriceTBPinned, + maxStoragePriceTBMonth, + maxDownloadPriceTB, + maxUploadPriceTB, + ]) +} diff --git a/apps/renterd/contexts/config/useSpendingEstimate.tsx b/apps/renterd/contexts/config/useSpendingEstimate.tsx new file mode 100644 index 000000000..0dfa780d2 --- /dev/null +++ b/apps/renterd/contexts/config/useSpendingEstimate.tsx @@ -0,0 +1,62 @@ +import { useConfig } from '.' +import { useMemo } from 'react' +import { Maybe } from '@siafoundation/design-system' +import BigNumber from 'bignumber.js' +import { useRedundancyMultiplier } from './useRedundancyMultiplier' +import { calculateSpendingEstimate } from './spending' +import { useEnabledPricingValuesInSiacoin } from './useEnabledPricingValuesInSiacoin' + +export function useSpendingEstimate() { + const { form } = useConfig() + const estimatedSpendingPerMonth = + useSpendingEstimateFromEnabledPricingValues() + const storageTB = form.watch('storageTB') + + const estimatedSpendingPerTB: Maybe = useMemo(() => { + if (!estimatedSpendingPerMonth?.gt(0) || !storageTB?.gt(0)) { + return undefined + } + const totalCostPerMonthTB = estimatedSpendingPerMonth.div(storageTB) + return totalCostPerMonthTB + }, [estimatedSpendingPerMonth, storageTB]) + + return { + estimatedSpendingPerMonth, + estimatedSpendingPerTB, + } +} + +// Use the current enabled pricing values to calculate the estimated spending in siacoin. +function useSpendingEstimateFromEnabledPricingValues(): Maybe { + const { form } = useConfig() + const minShards = form.watch('minShards') + const totalShards = form.watch('totalShards') + const storageTB = form.watch('storageTB') + const downloadTBMonth = form.watch('downloadTBMonth') + const uploadTBMonth = form.watch('uploadTBMonth') + const redundancyMultiplier = useRedundancyMultiplier({ + minShards, + totalShards, + }) + const prices = useEnabledPricingValuesInSiacoin({ + form, + }) + + return useMemo(() => { + if (!prices) { + return undefined + } + const { maxStoragePriceTBMonth, maxUploadPriceTB, maxDownloadPriceTB } = + prices + + return calculateSpendingEstimate({ + maxStoragePriceTBMonth, + maxDownloadPriceTB, + maxUploadPriceTB, + storageTB, + downloadTBMonth, + uploadTBMonth, + redundancyMultiplier, + }) + }, [prices, storageTB, downloadTBMonth, uploadTBMonth, redundancyMultiplier]) +} diff --git a/libs/design-system/src/core/SiacoinField.tsx b/libs/design-system/src/core/SiacoinField.tsx index 2bc278681..776ff3438 100644 --- a/libs/design-system/src/core/SiacoinField.tsx +++ b/libs/design-system/src/core/SiacoinField.tsx @@ -17,6 +17,7 @@ type Props = Omit< sc?: BigNumber onChange?: (sc?: BigNumber) => void units?: string + unitsFiatPostfix?: string decimalsLimitSc?: number decimalsLimitFiat?: number placeholder?: BigNumber @@ -33,6 +34,7 @@ export function SiacoinField({ onChange, size = 'medium', units = 'SC', + unitsFiatPostfix, showFiat = true, error, changed, @@ -157,9 +159,7 @@ export function SiacoinField({ ? 'bg-gray-200 dark:bg-graydark-300' : 'bg-white dark:bg-graydark-50', props.readOnly ? 'pointer-events-none' : '', - props.readOnly - ? 'border-blue-400 dark:border-blue-400' - : error + error ? 'border-red-500 dark:border-red-400' : changed ? 'border-green-500 dark:border-green-400' @@ -204,7 +204,7 @@ export function SiacoinField({ variant="ghost" focus="none" value={localFiat !== 'NaN' ? localFiat : ''} - units={settings.currency.label} + units={settings.currency.label + (unitsFiatPostfix || '')} decimalScale={decimalsLimitSc} allowNegative={false} onValueChange={(value) => { diff --git a/libs/renterd-types/src/types.ts b/libs/renterd-types/src/types.ts index d4969580c..bf7387bc6 100644 --- a/libs/renterd-types/src/types.ts +++ b/libs/renterd-types/src/types.ts @@ -176,10 +176,6 @@ export type GougingSettingsPins = { maxUpload: Pin } -export type AutopilotPins = { - allowance: Pin -} - // SettingsPinned holds the configuration for pinning certain settings to // a specific currency (e.g., USD). It uses a Forex API to fetch the current // exchange rate, allowing users to set prices in USD instead of SC. @@ -191,9 +187,6 @@ export type SettingsPinned = { // pinned settings are updated based on the exchange rate at the time. threshold: number - // Autopilots contains the pinned settings for every autopilot. - autopilots: Record - // GougingSettingsPins contains the pinned settings for the gouging // settings. gougingSettingsPins: GougingSettingsPins @@ -305,7 +298,6 @@ export type AutopilotHostsConfig = { export type AutopilotContractsConfig = { set: string amount: number - allowance: Currency period: number renewWindow: number download: number diff --git a/libs/units/src/storage.ts b/libs/units/src/storage.ts index e94c85b98..ffbed6cf0 100644 --- a/libs/units/src/storage.ts +++ b/libs/units/src/storage.ts @@ -105,7 +105,7 @@ export function getRedundancyMultiplier( return redundancyMult } -export function calculateEstimatedSpending({ +export function calculateMaxSpending({ maxStoragePriceTBMonth, maxDownloadPriceTB, maxUploadPriceTB,