@sovryn-zero/lib-base
Version:
Sovryn Zero SDK shared interfaces
161 lines (144 loc) • 5.3 kB
text/typescript
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);
}
}