UNPKG

@sovryn-zero/lib-base

Version:
161 lines (144 loc) 5.3 kB
import assert from "assert"; import { Decimal, Decimalish } from "./Decimal"; import { MAXIMUM_BORROWING_RATE, MINIMUM_BORROWING_RATE, MINIMUM_REDEMPTION_RATE } from "./constants"; /** * Calculator for fees. * * @remarks * Returned by the {@link ReadableLiquity.getFees | getFees()} function. * * @public */ export class Fees { private readonly _baseRateWithoutDecay: Decimal; private readonly _minuteDecayFactor: Decimal; private readonly _beta: Decimal; private readonly _lastFeeOperation: Date; private readonly _timeOfLatestBlock: Date; private readonly _recoveryMode: boolean; /** @internal */ constructor( baseRateWithoutDecay: Decimalish, minuteDecayFactor: Decimalish, beta: Decimalish, lastFeeOperation: Date, timeOfLatestBlock: Date, recoveryMode: boolean ) { this._baseRateWithoutDecay = Decimal.from(baseRateWithoutDecay); this._minuteDecayFactor = Decimal.from(minuteDecayFactor); this._beta = Decimal.from(beta); this._lastFeeOperation = lastFeeOperation; this._timeOfLatestBlock = timeOfLatestBlock; this._recoveryMode = recoveryMode; assert(this._minuteDecayFactor.lt(1)); } /** @internal */ _setRecoveryMode(recoveryMode: boolean): Fees { return new Fees( this._baseRateWithoutDecay, this._minuteDecayFactor, this._beta, this._lastFeeOperation, this._timeOfLatestBlock, recoveryMode ); } /** * Compare to another instance of `Fees`. */ equals(that: Fees): boolean { return ( this._baseRateWithoutDecay.eq(that._baseRateWithoutDecay) && this._minuteDecayFactor.eq(that._minuteDecayFactor) && this._beta.eq(that._beta) && this._lastFeeOperation.getTime() === that._lastFeeOperation.getTime() && this._timeOfLatestBlock.getTime() === that._timeOfLatestBlock.getTime() && this._recoveryMode === that._recoveryMode ); } /** @internal */ toString(): string { return ( `{ baseRateWithoutDecay: ${this._baseRateWithoutDecay}` + `, lastFeeOperation: "${this._lastFeeOperation.toLocaleString()}"` + `, recoveryMode: ${this._recoveryMode} } ` ); } /** @internal */ baseRate(when = this._timeOfLatestBlock): Decimal { const millisecondsSinceLastFeeOperation = Math.max( when.getTime() - this._lastFeeOperation.getTime(), 0 // Clamp negative elapsed time to 0, in case the client's time is in the past. // We will calculate slightly higher than actual fees, which is fine. ); const minutesSinceLastFeeOperation = Math.floor(millisecondsSinceLastFeeOperation / 60000); return this._minuteDecayFactor.pow(minutesSinceLastFeeOperation).mul(this._baseRateWithoutDecay); } /** * Calculate the current borrowing rate. * * @param when - Optional timestamp that can be used to calculate what the borrowing rate would * decay to at a point of time in the future. * * @remarks * By default, the fee is calculated at the time of the latest block. This can be overridden using * the `when` parameter. * * To calculate the borrowing fee in ZUSD, multiply the borrowed ZUSD amount by the borrowing rate. * * @example * ```typescript * const fees = await zero.getFees(); * * const borrowedZUSDAmount = 100; * const borrowingRate = fees.borrowingRate(); * const borrowingFeeZUSD = borrowingRate.mul(borrowedZUSDAmount); * ``` */ borrowingRate(when?: Date): Decimal { return this._recoveryMode ? Decimal.ZERO : Decimal.min(MINIMUM_BORROWING_RATE.add(this.baseRate(when)), MAXIMUM_BORROWING_RATE); } /** * Calculate the current redemption rate. * * @param redeemedFractionOfSupply - The amount of ZUSD being redeemed divided by the total supply. * @param when - Optional timestamp that can be used to calculate what the redemption rate would * decay to at a point of time in the future. * * @remarks * By default, the fee is calculated at the time of the latest block. This can be overridden using * the `when` parameter. * Unlike the borrowing rate, the redemption rate depends on the amount being redeemed. To be more * precise, it depends on the fraction of the redeemed amount compared to the total ZUSD supply, * which must be passed as a parameter. * * To calculate the redemption fee in ZUSD, multiply the redeemed ZUSD amount with the redemption * rate. * * @example * ```typescript * const fees = await zero.getFees(); * const total = await zero.getTotal(); * * const redeemedZUSDAmount = Decimal.from(100); * const redeemedFractionOfSupply = redeemedZUSDAmount.div(total.debt); * const redemptionRate = fees.redemptionRate(redeemedFractionOfSupply); * const redemptionFeeZUSD = redemptionRate.mul(redeemedZUSDAmount); * ``` */ redemptionRate(redeemedFractionOfSupply: Decimalish = Decimal.ZERO, when?: Date): Decimal { redeemedFractionOfSupply = Decimal.from(redeemedFractionOfSupply); let baseRate = this.baseRate(when); if (redeemedFractionOfSupply.nonZero) { baseRate = redeemedFractionOfSupply.div(this._beta).add(baseRate); } return Decimal.min(MINIMUM_REDEMPTION_RATE.add(baseRate), Decimal.ONE); } }