-
Notifications
You must be signed in to change notification settings - Fork 9
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
refactor: renterd allowance auto max
- Loading branch information
1 parent
bdbc4d5
commit f2ee837
Showing
9 changed files
with
478 additions
and
118 deletions.
There are no files selected for viewing
281 changes: 281 additions & 0 deletions
281
apps/renterd/contexts/config/derivePricesFromAllowance.spec.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,281 @@ | ||
import BigNumber from 'bignumber.js' | ||
import { derivePricingFromAllowance } from './derivePricesFromAllowance' | ||
|
||
test('with estimates of 1 TB, standard weights, 1x redundancy, 1x allowance factor', () => { | ||
const allowanceMonth = new BigNumber(1000) | ||
const allowanceFactor = 1 | ||
const storageTB = new BigNumber(1) | ||
const downloadTBMonth = new BigNumber(1) | ||
const uploadTBMonth = new BigNumber(1) | ||
const redundancyMultiplier = new BigNumber(1) | ||
const storageWeight = 4 | ||
const downloadWeight = 5 | ||
const uploadWeight = 1 | ||
|
||
const prices = derivePricingFromAllowance({ | ||
allowanceMonth, | ||
allowanceFactor, | ||
storageTB, | ||
downloadTBMonth, | ||
uploadTBMonth, | ||
redundancyMultiplier, | ||
storageWeight, | ||
downloadWeight, | ||
uploadWeight, | ||
}) | ||
|
||
expect(prices).not.toBeNull() | ||
expect(prices.maxStoragePriceTBMonth.toFixed(2)).toBe('400.00') | ||
expect(prices.maxDownloadPriceTB.toFixed(2)).toBe('500.00') | ||
expect(prices.maxUploadPriceTB.toFixed(2)).toBe('100.00') | ||
}) | ||
|
||
test('with estimates of 1 TB, standard weights, 2x redundancy, 1x allowance factor', () => { | ||
const allowanceMonth = new BigNumber(1000) | ||
const allowanceFactor = 1 | ||
const storageTB = new BigNumber(1) | ||
const downloadTBMonth = new BigNumber(1) | ||
const uploadTBMonth = new BigNumber(1) | ||
const redundancyMultiplier = new BigNumber(2) | ||
const storageWeight = 4 | ||
const downloadWeight = 5 | ||
const uploadWeight = 1 | ||
|
||
const prices = derivePricingFromAllowance({ | ||
allowanceMonth, | ||
allowanceFactor, | ||
storageTB, | ||
downloadTBMonth, | ||
uploadTBMonth, | ||
redundancyMultiplier, | ||
storageWeight, | ||
downloadWeight, | ||
uploadWeight, | ||
}) | ||
|
||
expect(prices).not.toBeNull() | ||
expect(prices.maxStoragePriceTBMonth.toFixed(2)).toBe('266.67') | ||
expect(prices.maxDownloadPriceTB.toFixed(2)).toBe('333.33') | ||
expect(prices.maxUploadPriceTB.toFixed(2)).toBe('66.67') | ||
}) | ||
|
||
test('with estimates of 1 TB, standard weights, 3x redunancy, 1x allowance factor', () => { | ||
const allowanceMonth = new BigNumber(1000) | ||
const allowanceFactor = 1 | ||
const storageTB = new BigNumber(1) | ||
const downloadTBMonth = new BigNumber(1) | ||
const uploadTBMonth = new BigNumber(1) | ||
const redundancyMultiplier = new BigNumber(3) | ||
const storageWeight = 4 | ||
const downloadWeight = 5 | ||
const uploadWeight = 1 | ||
|
||
const prices = derivePricingFromAllowance({ | ||
allowanceMonth, | ||
allowanceFactor, | ||
storageTB, | ||
downloadTBMonth, | ||
uploadTBMonth, | ||
redundancyMultiplier, | ||
storageWeight, | ||
downloadWeight, | ||
uploadWeight, | ||
}) | ||
|
||
expect(prices).not.toBeNull() | ||
expect(prices.maxStoragePriceTBMonth.toFixed(2)).toBe('200.00') | ||
expect(prices.maxDownloadPriceTB.toFixed(2)).toBe('250.00') | ||
expect(prices.maxUploadPriceTB.toFixed(2)).toBe('50.00') | ||
}) | ||
|
||
test('with estimates of 1 TB, standard weights, 1x redundancy, 2x allowance factor', () => { | ||
const allowanceMonth = new BigNumber(1000) | ||
const allowanceFactor = 2 | ||
const storageTB = new BigNumber(1) | ||
const downloadTBMonth = new BigNumber(1) | ||
const uploadTBMonth = new BigNumber(1) | ||
const redundancyMultiplier = new BigNumber(1) | ||
const storageWeight = 4 | ||
const downloadWeight = 5 | ||
const uploadWeight = 1 | ||
|
||
const prices = derivePricingFromAllowance({ | ||
allowanceMonth, | ||
allowanceFactor, | ||
storageTB, | ||
downloadTBMonth, | ||
uploadTBMonth, | ||
redundancyMultiplier, | ||
storageWeight, | ||
downloadWeight, | ||
uploadWeight, | ||
}) | ||
|
||
expect(prices).not.toBeNull() | ||
expect(prices.maxStoragePriceTBMonth.toFixed(2)).toBe('800.00') | ||
expect(prices.maxDownloadPriceTB.toFixed(2)).toBe('1000.00') | ||
expect(prices.maxUploadPriceTB.toFixed(2)).toBe('200.00') | ||
}) | ||
|
||
test('with estimates of 1 TB, standard weights, 2x redundancy, 2x allowance factor', () => { | ||
const allowanceMonth = new BigNumber(1000) | ||
const allowanceFactor = 2 | ||
const storageTB = new BigNumber(1) | ||
const downloadTBMonth = new BigNumber(1) | ||
const uploadTBMonth = new BigNumber(1) | ||
const redundancyMultiplier = new BigNumber(2) | ||
const storageWeight = 4 | ||
const downloadWeight = 5 | ||
const uploadWeight = 1 | ||
|
||
const prices = derivePricingFromAllowance({ | ||
allowanceMonth, | ||
allowanceFactor, | ||
storageTB, | ||
downloadTBMonth, | ||
uploadTBMonth, | ||
redundancyMultiplier, | ||
storageWeight, | ||
downloadWeight, | ||
uploadWeight, | ||
}) | ||
|
||
expect(prices).not.toBeNull() | ||
expect(prices.maxStoragePriceTBMonth.toFixed(2)).toBe('533.33') | ||
expect(prices.maxDownloadPriceTB.toFixed(2)).toBe('666.67') | ||
expect(prices.maxUploadPriceTB.toFixed(2)).toBe('133.33') | ||
}) | ||
|
||
test('with estimates of 1 TB, standard weights, 3x redunancy, 2x allowance factor', () => { | ||
const allowanceMonth = new BigNumber(1000) | ||
const allowanceFactor = 2 | ||
const storageTB = new BigNumber(1) | ||
const downloadTBMonth = new BigNumber(1) | ||
const uploadTBMonth = new BigNumber(1) | ||
const redundancyMultiplier = new BigNumber(3) | ||
const storageWeight = 4 | ||
const downloadWeight = 5 | ||
const uploadWeight = 1 | ||
|
||
const prices = derivePricingFromAllowance({ | ||
allowanceMonth, | ||
allowanceFactor, | ||
storageTB, | ||
downloadTBMonth, | ||
uploadTBMonth, | ||
redundancyMultiplier, | ||
storageWeight, | ||
downloadWeight, | ||
uploadWeight, | ||
}) | ||
|
||
expect(prices).not.toBeNull() | ||
expect(prices.maxStoragePriceTBMonth.toFixed(2)).toBe('400.00') | ||
expect(prices.maxDownloadPriceTB.toFixed(2)).toBe('500.00') | ||
expect(prices.maxUploadPriceTB.toFixed(2)).toBe('100.00') | ||
}) | ||
|
||
test('with varied estimates, standard weights, 1x redunancy, 2x allowance factor', () => { | ||
const allowanceMonth = new BigNumber(1000) | ||
const allowanceFactor = 2 | ||
const storageTB = new BigNumber(8) | ||
const downloadTBMonth = new BigNumber(15) | ||
const uploadTBMonth = new BigNumber(3) | ||
const redundancyMultiplier = new BigNumber(1) | ||
const storageWeight = 4 | ||
const downloadWeight = 5 | ||
const uploadWeight = 1 | ||
|
||
const prices = derivePricingFromAllowance({ | ||
allowanceMonth, | ||
allowanceFactor, | ||
storageTB, | ||
downloadTBMonth, | ||
uploadTBMonth, | ||
redundancyMultiplier, | ||
storageWeight, | ||
downloadWeight, | ||
uploadWeight, | ||
}) | ||
|
||
expect(prices).not.toBeNull() | ||
expect(prices.maxStoragePriceTBMonth.toFixed(2)).toBe('72.73') | ||
expect(prices.maxDownloadPriceTB.toFixed(2)).toBe('90.91') | ||
expect(prices.maxUploadPriceTB.toFixed(2)).toBe('18.18') | ||
}) | ||
|
||
test('with estimates of 1 TB, standard weights, 1x redundancy, 1.5x allowance factor', () => { | ||
const allowanceMonth = new BigNumber(1000) | ||
const storageTB = new BigNumber(1) | ||
const downloadTBMonth = new BigNumber(1) | ||
const uploadTBMonth = new BigNumber(1) | ||
const redundancyMultiplier = new BigNumber(1) | ||
const storageWeight = 4 | ||
const downloadWeight = 5 | ||
const uploadWeight = 1 | ||
const allowanceFactor = 1.5 | ||
|
||
const prices = derivePricingFromAllowance({ | ||
allowanceMonth, | ||
allowanceFactor, | ||
storageTB, | ||
downloadTBMonth, | ||
uploadTBMonth, | ||
redundancyMultiplier, | ||
storageWeight, | ||
downloadWeight, | ||
uploadWeight, | ||
}) | ||
|
||
expect(prices).not.toBeNull() | ||
expect(prices.maxStoragePriceTBMonth.toFixed(2)).toBe('600.00') | ||
expect(prices.maxDownloadPriceTB.toFixed(2)).toBe('750.00') | ||
expect(prices.maxUploadPriceTB.toFixed(2)).toBe('150.00') | ||
}) | ||
|
||
test('with varied estimates, standard weights, 1x redundancy, 1.5x allowance factor', () => { | ||
const allowanceMonth = new BigNumber(1000) | ||
const storageTB = new BigNumber(8) | ||
const downloadTBMonth = new BigNumber(15) | ||
const uploadTBMonth = new BigNumber(3) | ||
const redundancyMultiplier = new BigNumber(1) | ||
const storageWeight = 4 | ||
const downloadWeight = 5 | ||
const uploadWeight = 1 | ||
const allowanceFactor = 1.5 | ||
|
||
const prices = derivePricingFromAllowance({ | ||
allowanceMonth, | ||
allowanceFactor, | ||
storageTB, | ||
downloadTBMonth, | ||
uploadTBMonth, | ||
redundancyMultiplier, | ||
storageWeight, | ||
downloadWeight, | ||
uploadWeight, | ||
}) | ||
|
||
expect(prices).not.toBeNull() | ||
expect(prices.maxStoragePriceTBMonth.toFixed(2)).toBe('54.55') | ||
expect(prices.maxDownloadPriceTB.toFixed(2)).toBe('68.18') | ||
expect(prices.maxUploadPriceTB.toFixed(2)).toBe('13.64') | ||
}) | ||
|
||
test('with zero allowance returns null', () => { | ||
const allowanceMonth = 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, | ||
storageTB, | ||
downloadTBMonth, | ||
uploadTBMonth, | ||
redundancyMultiplier, | ||
}) | ||
|
||
expect(prices).toBeNull() | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
import BigNumber from 'bignumber.js' | ||
|
||
/** | ||
* This function calculates the price per TB for storage, download, and upload | ||
* given a total spending allowance and estimated usage in TB for each type. The | ||
* prices are fixed relative to each other using weight factors provided as | ||
* parameters. Storage and upload are also scaled to the amount of storage | ||
* required by the redundancy multiplier. | ||
* | ||
* The total cost equation is: A = S * storageWeight * unitCost + D * | ||
* downloadWeight * unitCost + U * uploadWeight * unitCost Solving for unitCost: | ||
* unitCost = A / (S * storageWeight + D * downloadWeight + U * uploadWeight) | ||
* | ||
* Once the unit cost is determined, the individual prices can be calculated: | ||
* - P_u = unitCost * uploadWeight | ||
* - P_d = unitCost * downloadWeight | ||
* - P_s = unitCost * storageWeight | ||
* | ||
* The function also includes an allowance scaling factor to account for | ||
* potential contract variations. 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.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. | ||
* @param params.storageWeight - The weight factor for storage. | ||
* @param params.downloadWeight - The weight factor for download. | ||
* @param params.uploadWeight - The weight factor for upload. | ||
* @returns An object containing the price per TB for storage, download, and | ||
* upload. | ||
*/ | ||
export function derivePricingFromAllowance({ | ||
allowanceMonth, | ||
allowanceFactor = 2, | ||
storageTB, | ||
downloadTBMonth, | ||
uploadTBMonth, | ||
redundancyMultiplier, | ||
storageWeight = 4, | ||
downloadWeight = 5, | ||
uploadWeight = 1, | ||
}: { | ||
allowanceMonth: BigNumber | ||
allowanceFactor?: number | ||
storageTB: BigNumber | ||
downloadTBMonth: BigNumber | ||
uploadTBMonth: BigNumber | ||
redundancyMultiplier: BigNumber | ||
storageWeight?: number | ||
downloadWeight?: number | ||
uploadWeight?: number | ||
}) { | ||
// Return null if zero values are provided. | ||
if ( | ||
!allowanceMonth?.gt(0) || | ||
allowanceFactor <= 0 || | ||
!redundancyMultiplier?.gt(0) || | ||
!storageTB?.gt(0) || | ||
!downloadTBMonth?.gt(0) || | ||
!uploadTBMonth?.gt(0) | ||
) { | ||
return null | ||
} | ||
// Apply scaling factor to allowance. | ||
const scaledAllowance = allowanceMonth.times(allowanceFactor) | ||
|
||
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( | ||
storageTBWithRedundancy | ||
.times(storageWeight) | ||
.plus(downloadTBMonth.times(downloadWeight)) | ||
.plus(uploadTBMonthWithRedundancy.times(uploadWeight)) | ||
) | ||
|
||
// Calculate the price per TB for each type of usage. | ||
const maxUploadPriceTB = unitCost.times(uploadWeight) | ||
const maxDownloadPriceTB = unitCost.times(downloadWeight) | ||
const maxStoragePriceTBMonth = unitCost.times(storageWeight) | ||
|
||
return { | ||
maxUploadPriceTB, | ||
maxDownloadPriceTB, | ||
maxStoragePriceTBMonth, | ||
} | ||
} |
Oops, something went wrong.