UNPKG

ox

Version:

Ethereum Standard Library

236 lines (217 loc) 6.85 kB
import * as Errors from '../core/Errors.js' /** Basis-point denominator used by slippage bounds. */ export const basisPointScale = 10_000 /** * Tempo Earn `VaultAdapter` conversion anchor. * * The adapter prices vault shares against venue shares through this pair: * `engineShares` venue shares are worth `shareSupply` vault shares. It is initialised * 1:1 and restated on `contribute` and `migrateEngine`. * * These conversions are raw and fee-blind; they ignore pending fee dilution * and are unsuitable for user-facing value (use the adapter's `previewRedeem`). */ export type Anchor = { /** Venue shares held by the engine at the anchor point. */ engineShares: bigint /** Vault share supply at the anchor point. */ shareSupply: bigint } /** * Converts venue shares to a vault share amount at the anchor rate, rounding down. * * Mirrors `VaultAdapter.sharesToTokens`. * * @example * ```ts twoslash * import { EarnShares } from 'ox/tempo' * * const shareAmount = EarnShares.toAmount( * { engineShares: 3n, shareSupply: 2n }, * 7n * ) * // @log: 4n * ``` * * @param anchor - The conversion anchor. * @param venueShareAmount - Venue share amount, base units. * @returns Vault share amount, rounded down. */ export function toAmount(anchor: Anchor, venueShareAmount: bigint): bigint { return (venueShareAmount * anchor.shareSupply) / anchor.engineShares } export declare namespace toAmount { type ErrorType = Errors.GlobalErrorType } /** * Converts venue shares to a vault share amount at the anchor rate, rounding up. * * Mirrors the adapter's ceiling conversion used by exact-asset exits. * * @example * ```ts twoslash * import { EarnShares } from 'ox/tempo' * * const shareAmount = EarnShares.toAmountUp( * { engineShares: 3n, shareSupply: 2n }, * 7n * ) * // @log: 5n * ``` * * @param anchor - The conversion anchor. * @param venueShareAmount - Venue share amount, base units. * @returns Vault share amount, rounded up. */ export function toAmountUp(anchor: Anchor, venueShareAmount: bigint): bigint { const { engineShares, shareSupply } = anchor return (venueShareAmount * shareSupply + engineShares - 1n) / engineShares } export declare namespace toAmountUp { type ErrorType = Errors.GlobalErrorType } /** * Converts a vault share amount to venue shares at the anchor rate, rounding down. * * Mirrors `VaultAdapter.tokensToShares`. * * @example * ```ts twoslash * import { EarnShares } from 'ox/tempo' * * const venueShareAmount = EarnShares.toVenueAmount( * { engineShares: 3n, shareSupply: 2n }, * 7n * ) * // @log: 10n * ``` * * @param anchor - The conversion anchor. * @param shareAmount - Vault share amount, base units. * @returns Venue share amount, rounded down. */ export function toVenueAmount(anchor: Anchor, shareAmount: bigint): bigint { return (shareAmount * anchor.engineShares) / anchor.shareSupply } export declare namespace toVenueAmount { type ErrorType = Errors.GlobalErrorType } /** * Computes the dilution-correct vault shares minted for an asset-denominated fee. * * Mirrors `FeeMath`: * `feeShares = floor(fee * shareSupply / (activeAssets - fee))`, zero when the * fee is zero or not smaller than the active assets. Minting this amount to the * fee ledger prices the fee at post-mint value per share. * * @example * ```ts twoslash * import { EarnShares } from 'ox/tempo' * * const shares = EarnShares.feeShares({ * activeAssets: 1_100n, * shareSupply: 1_000n, * totalFeeAssets: 100n * }) * // @log: 100n * ``` * * @param options - Fee accrual inputs. * @returns Vault shares to mint for the fee, rounded down. */ export function feeShares(options: feeShares.Options): bigint { const { activeAssets, shareSupply, totalFeeAssets } = options if (totalFeeAssets === 0n || totalFeeAssets >= activeAssets) return 0n return (totalFeeAssets * shareSupply) / (activeAssets - totalFeeAssets) } export declare namespace feeShares { export type Options = { /** Assets backing the active (non-queued) supply, base units. */ activeAssets: bigint /** Active vault share supply, base units. */ shareSupply: bigint /** Total fee liability in asset units. */ totalFeeAssets: bigint } export type ErrorType = Errors.GlobalErrorType } /** * Lowers an expected output by a basis-point slippage tolerance, flooring to `1n`. * * Suitable for lower bounds such as a deposit's minimum shares or a redeem's * minimum assets; not for upper bounds such as an exact withdrawal's maximum * shares. * * @example * ```ts twoslash * import { EarnShares } from 'ox/tempo' * * const minimumShares = EarnShares.minimumOutput( * 1_000_000n, * 50 * ) * // @log: 995_000n * ``` * * @param expectedAmount - Expected output in base units. * @param slippageBps - Allowed slippage in basis points from `0` through `9_999`. * @returns The minimum accepted output, floored to `1n`. * @throws `InvalidExpectedOutputError` when `expectedAmount` is not positive. * @throws `InvalidSlippageError` when `slippageBps` is outside its valid range. */ export function minimumOutput( expectedAmount: bigint, slippageBps: number, ): bigint { if (expectedAmount <= 0n) throw new InvalidExpectedOutputError({ expectedAmount }) if ( !Number.isInteger(slippageBps) || slippageBps < 0 || slippageBps >= basisPointScale ) throw new InvalidSlippageError({ slippageBps }) const scale = BigInt(basisPointScale) const bounded = (expectedAmount * (scale - BigInt(slippageBps))) / scale return bounded === 0n ? 1n : bounded } export declare namespace minimumOutput { type ErrorType = | InvalidExpectedOutputError | InvalidSlippageError | Errors.GlobalErrorType } /** * Error thrown when an expected output is not positive. */ export class InvalidExpectedOutputError extends Errors.BaseError { override readonly name = 'EarnShares.InvalidExpectedOutputError' constructor(options: InvalidExpectedOutputError.Options) { super( `Expected output \`${options.expectedAmount}\` must be greater than zero.`, ) } } export declare namespace InvalidExpectedOutputError { export type Options = { expectedAmount: bigint } } /** * Error thrown when a slippage tolerance is not an integer from `0` through `9_999`. */ export class InvalidSlippageError extends Errors.BaseError { override readonly name = 'EarnShares.InvalidSlippageError' constructor(options: InvalidSlippageError.Options) { super(`Slippage tolerance \`${options.slippageBps}\` is invalid.`, { metaMessages: [ `Slippage must be a whole number from 0 through ${basisPointScale - 1} basis points.`, ], }) } } export declare namespace InvalidSlippageError { export type Options = { slippageBps: number } }