Skip to content

Commit

Permalink
refactor: renterd allowance auto max
Browse files Browse the repository at this point in the history
  • Loading branch information
alexfreska committed Jul 1, 2024
1 parent bdbc4d5 commit f2ee837
Show file tree
Hide file tree
Showing 9 changed files with 478 additions and 118 deletions.
281 changes: 281 additions & 0 deletions apps/renterd/contexts/config/derivePricesFromAllowance.spec.ts
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()
})
95 changes: 95 additions & 0 deletions apps/renterd/contexts/config/derivePricesFromAllowance.ts
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,
}
}
Loading

0 comments on commit f2ee837

Please sign in to comment.