feat: New synapse costs API and improvs#632
feat: New synapse costs API and improvs#632lordshashank wants to merge 2 commits intoFilOzone:masterfrom
Conversation
bdd0623 to
e269ba9
Compare
e269ba9 to
4883c02
Compare
docs/src/content/docs/developer-guides/storage/storage-costs.mdx
Outdated
Show resolved
Hide resolved
| - **Needs deposit + approval**: calls `depositWithPermitAndApproveOperator` | ||
| - **Needs approval only**: calls `approveService` | ||
| - **Needs deposit only**: calls `depositWithPermit` | ||
| - **Already ready**: returns `transaction: null` |
There was a problem hiding this comment.
Should exact API specs live outside of developer guides?
| * @param params - Raw account fields + current epoch | ||
| * @returns Settled account info including fundedUntilEpoch | ||
| */ | ||
| export function getAccountInfoIfSettled( |
There was a problem hiding this comment.
I would expect this method to return the account info if settled. There is no condition here, it always returns account info. What does "if settled" mean here?
There was a problem hiding this comment.
I mapped the exact onchain function https://github.com/FilOzone/filecoin-pay/blob/7bc77d86f94ef646721919426038a66290b862aa/src/FilecoinPayV1.sol#L1747
| /** max(0, funds - totalOwed) */ | ||
| availableFunds: bigint | ||
| /** Epoch when account runs out (maxUint256 if lockupRate == 0) */ | ||
| fundedUntilEpoch: bigint |
There was a problem hiding this comment.
these two are out of scope for the function. The consumer can simply do this themselves:
const [debt, info] = await Promise.all([
calculateAccountDebt(),
getAccountInfoIfSettled()
])I would suggest to only return the debt from this function and not return other possibly useful but already easy to get information.
There was a problem hiding this comment.
I did that as we needed fundedUntilEpoch in for buffer calcn, etc so made it more informative, can change to different functions if everyone wants to.
|
|
||
| export namespace depositWithPermitSync { | ||
| export type OptionsType = Simplify<depositWithPermit.OptionsType & ActionSyncCallback> | ||
| export type OutputType = ActionSyncOutput<typeof extractDepositWithPermitEvent> |
There was a problem hiding this comment.
I don't think <typeof extractDepositWithPermitEvent> can be inferred
There was a problem hiding this comment.
extractDepositWithPermitEvent is defined below in the same file and the typeof is needed because this is a type declaration, not a value expression. wdym here?
| } | ||
|
|
||
| if (needsApproval && options.amount === 0n) { | ||
| return setOperatorApproval(client, { |
There was a problem hiding this comment.
this code path is surprising: I would not expect a fund() method to not actually fund and only approve.
There was a problem hiding this comment.
yeah, handling the edge cases
|
|
||
| return { | ||
| ratePerEpoch, | ||
| ratePerMonth: ratePerEpoch * TIME_CONSTANTS.EPOCHS_PER_MONTH, |
There was a problem hiding this comment.
this is a convenience field and always wrong, wdyt about never returning per-month data, always only the correct per-epoch data, and exposing an extra method for converting per-epoch to per-month? This way the developer has to opt in to the conversion.
There was a problem hiding this comment.
This way, there's higher confidence in the return values of core methods, and it's more clear that the per-month value is rather for display or human readability.
There was a problem hiding this comment.
Actually on chain is per month so convenience field is per epoch one, kept it to let user calculate anuthing and is more generic
| * @returns Array of data set sizes in bytes {@link getMultiDatasetSize.OutputType} | ||
| * @throws Errors {@link getMultiDatasetSize.ErrorType} | ||
| */ | ||
| export async function getMultiDatasetSize( |
There was a problem hiding this comment.
is this method actually adventageous over calling getDataSetSize multiple times in parallel? If the benefit is not obvious already, I would suggest to remove this utility method, to reduce the maintenance cost
There was a problem hiding this comment.
Yeah, it reduces rpc calls for multi context cases in prepare through multicall, probably we can just keep this and remove single one and rename this to getDatasetsSize but I am ok with two functions as well let me know if this feels critical
| * Default extra runway in epochs beyond the required lockup. | ||
| * 0n means no additional runway beyond the lockup period. | ||
| */ | ||
| export const DEFAULT_RUNWAY_EPOCHS = 0n |
There was a problem hiding this comment.
Is the decision to go with 0 documented somewhere?
packages/synapse-core/src/warm-storage/calculate-additional-lockup-required.ts
Show resolved
Hide resolved
packages/synapse-core/src/warm-storage/calculate-additional-lockup-required.ts
Show resolved
Hide resolved
| if (currentDataSetSize > 0n && !isNewDataset) { | ||
| // Existing dataset: compute delta between new and current rates | ||
| const newRate = calculateEffectiveRate({ | ||
| ...rateParams, | ||
| sizeInBytes: currentDataSetSize + dataSize, | ||
| }) | ||
| const currentRate = calculateEffectiveRate({ | ||
| ...rateParams, | ||
| sizeInBytes: currentDataSetSize, | ||
| }) | ||
| rateDeltaPerEpoch = newRate.ratePerEpoch - currentRate.ratePerEpoch | ||
| // Floor-to-floor: if both sizes are below floor, delta is 0 | ||
| if (rateDeltaPerEpoch < 0n) rateDeltaPerEpoch = 0n | ||
| } else { | ||
| // New dataset or unknown current size: full rate for new data | ||
| const newRate = calculateEffectiveRate({ | ||
| ...rateParams, | ||
| sizeInBytes: dataSize, | ||
| }) | ||
| rateDeltaPerEpoch = newRate.ratePerEpoch | ||
| } |
There was a problem hiding this comment.
refactor: less branches
| if (currentDataSetSize > 0n && !isNewDataset) { | |
| // Existing dataset: compute delta between new and current rates | |
| const newRate = calculateEffectiveRate({ | |
| ...rateParams, | |
| sizeInBytes: currentDataSetSize + dataSize, | |
| }) | |
| const currentRate = calculateEffectiveRate({ | |
| ...rateParams, | |
| sizeInBytes: currentDataSetSize, | |
| }) | |
| rateDeltaPerEpoch = newRate.ratePerEpoch - currentRate.ratePerEpoch | |
| // Floor-to-floor: if both sizes are below floor, delta is 0 | |
| if (rateDeltaPerEpoch < 0n) rateDeltaPerEpoch = 0n | |
| } else { | |
| // New dataset or unknown current size: full rate for new data | |
| const newRate = calculateEffectiveRate({ | |
| ...rateParams, | |
| sizeInBytes: dataSize, | |
| }) | |
| rateDeltaPerEpoch = newRate.ratePerEpoch | |
| } | |
| const newRate = calculateEffectiveRate({ | |
| ...rateParams, | |
| sizeInBytes: currentDataSetSize + dataSize, | |
| }) | |
| const currentRate = calculateEffectiveRate({ | |
| ...rateParams, | |
| sizeInBytes: currentDataSetSize, | |
| }) | |
| rateDeltaPerEpoch = newRate.ratePerEpoch - currentRate.ratePerEpoch |
There was a problem hiding this comment.
this is wrong as for the case of new dataset it would return zero as calculateEffectiveRate would return base price for both
Resolves #360 , #437
Complete architecture and thought process: https://hackmd.io/@lordforever/SJ6Lwv2dWx (plz read to understand better and add comments)