Skip to content

Commit

Permalink
🐛 ethereum: use legacy IRM in ethereum mainnet
Browse files Browse the repository at this point in the history
  • Loading branch information
franm91 committed Mar 27, 2024
1 parent 9e4ff9b commit a6b237e
Show file tree
Hide file tree
Showing 3 changed files with 300 additions and 18 deletions.
78 changes: 60 additions & 18 deletions hooks/useFloatingPoolAPR.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ import useDelayedEffect from './useDelayedEffect';
import { useWeb3 } from './useWeb3';
import { useGlobalError } from 'contexts/GlobalErrorContext';
import useIRM from './useIRM';
import { floatingInterestRateCurve } from 'utils/interestRateCurve';
import { mainnet } from 'wagmi';
import { useMarketFloatingBackupBorrowed } from 'types/abi';

type FloatingPoolAPR = {
depositAPR: number | undefined;
Expand All @@ -34,26 +37,65 @@ export default (
const [loading, setLoading] = useState<boolean>(true);
const { setIndexerError } = useGlobalError();

const { data: _floatingBackupBorrowed } = useMarketFloatingBackupBorrowed({
address: marketAccount?.market,
chainId: chain.id,
});

const borrowAPR = useMemo((): number | undefined => {
if (!marketAccount || !irm || operation === 'deposit') {
return undefined;
}
if (chain.id === mainnet.id) {
if (!marketAccount || _floatingBackupBorrowed === undefined) {
return undefined;
}

const { interestRateModel, totalFloatingDepositAssets, totalFloatingBorrowAssets, decimals } = marketAccount;
const delta = parseUnits(qty || '0', decimals);

const debt = totalFloatingBorrowAssets + delta;

const { A, B, uMax } = {
A: 'floatingCurveA' in interestRateModel && interestRateModel.floatingCurveA,
B: 'floatingCurveB' in interestRateModel && interestRateModel.floatingCurveB,
uMax: 'floatingMaxUtilization' in interestRateModel && interestRateModel.floatingMaxUtilization,
};

if (!A || !B || !uMax) return undefined;

const { totalFloatingDepositAssets, totalFloatingBorrowAssets, decimals, floatingBackupBorrowed } = marketAccount;
const delta = parseUnits(qty || '0', decimals);

const debt = totalFloatingBorrowAssets + delta;

return (
Number(
floatingRate(
floatingUtilization(totalFloatingDepositAssets, debt),
globalUtilization(totalFloatingDepositAssets, debt, floatingBackupBorrowed),
irm,
),
) / 1e18
);
}, [marketAccount, irm, operation, qty]);
const curve = floatingInterestRateCurve({
A,
B,
maxUtilization: uMax,
naturalUtilization: 700000000000000000n,
sigmoidSpeed: 2500000000000000000n,
growthSpeed: 1000000000000000000n,
maxRate: 150000000000000000000n,
});

const uF = floatingUtilization(totalFloatingDepositAssets, debt);
const uG = globalUtilization(totalFloatingDepositAssets, debt, _floatingBackupBorrowed);

return Number(curve(uF, uG)) / 1e18;
} else {
if (!marketAccount || !irm || operation === 'deposit') {
return undefined;
}

const { totalFloatingDepositAssets, totalFloatingBorrowAssets, decimals, floatingBackupBorrowed } = marketAccount;
const delta = parseUnits(qty || '0', decimals);

const debt = totalFloatingBorrowAssets + delta;

return (
Number(
floatingRate(
floatingUtilization(totalFloatingDepositAssets, debt),
globalUtilization(totalFloatingDepositAssets, debt, floatingBackupBorrowed),
irm,
),
) / 1e18
);
}
}, [chain.id, marketAccount, _floatingBackupBorrowed, qty, irm, operation]);

const fetchAPRs = useCallback(
async (cancelled: () => boolean) => {
Expand Down
121 changes: 121 additions & 0 deletions utils/fixedMath.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
export const WAD = 10n ** 18n;

function log2(x: bigint) {
if (x <= 0n) throw new Error('UNDEFINED');

let r = BigInt(0xffffffffffffffffffffffffffffffffn < x) << 7n;
r |= BigInt(0xffffffffffffffffn < x >> r) << 6n;
r |= BigInt(0xffffffffn < x >> r) << 5n;
r |= BigInt(0xffffn < x >> r) << 4n;
r |= BigInt(0xffn < x >> r) << 3n;
r |= BigInt(0xfn < x >> r) << 2n;
r |= BigInt(0x3n < x >> r) << 1n;
r |= BigInt(0x1n < x >> r);

return r;
}

export function lnWad(x: bigint) {
if (x <= 0n) throw new Error('UNDEFINED');

const k = log2(x) - 96n;
x <<= 159n - k;
x >>= 159n;

let p = x + 3273285459638523848632254066296n;
p = ((p * x) >> 96n) + 24828157081833163892658089445524n;
p = ((p * x) >> 96n) + 43456485725739037958740375743393n;
p = ((p * x) >> 96n) - 11111509109440967052023855526967n;
p = ((p * x) >> 96n) - 45023709667254063763336534515857n;
p = ((p * x) >> 96n) - 14706773417378608786704636184526n;
p = p * x - (795164235651350426258249787498n << 96n);

let q = x + 5573035233440673466300451813936n;
q = ((q * x) >> 96n) + 71694874799317883764090561454958n;
q = ((q * x) >> 96n) + 283447036172924575727196451306956n;
q = ((q * x) >> 96n) + 401686690394027663651624208769553n;
q = ((q * x) >> 96n) + 204048457590392012362485061816622n;
q = ((q * x) >> 96n) + 31853899698501571402653359427138n;
q = ((q * x) >> 96n) + 909429971244387300277376558375n;

let r = p / q;

r *= 1677202110996718588342820967067443963516166n;
r += 16597577552685614221487285958193947469193820559219878177908093499208371n * k;
r += 600920179829731861736702779321621459595472258049074101567377883020018308n;
r >>= 174n;

return r;
}

export function expWad(x: bigint): bigint {
if (x <= -42139678854452767551n) return 0n;
if (x >= 135305999368893231589n) throw new Error('EXP_OVERFLOW');

x = (x << 78n) / 5n ** 18n; // Convert to (-42, 136) * 2**96

const k = ((x << 96n) / 54916777467707473351141471128n + 2n ** 95n) >> 96n;
x -= k * 54916777467707473351141471128n;

let y = x + 1346386616545796478920950773328n;
y = ((y * x) >> 96n) + 57155421227552351082224309758442n;
let p = y + x - 94201549194550492254356042504812n;
p = ((p * y) >> 96n) + 28719021644029726153956944680412240n;
p = p * x + (4385272521454847904659076985693276n << 96n);

let q = x - 2855989394907223263936484059900n;
q = ((q * x) >> 96n) + 50020603652535783019961831881945n;
q = ((q * x) >> 96n) - 533845033583426703283633433725380n;
q = ((q * x) >> 96n) + 3604857256930695427073651918091429n;
q = ((q * x) >> 96n) - 14423608567350463180887372962807573n;
q = ((q * x) >> 96n) + 26449188498355588339934803723976023n;

const r = p / q;
return (r * 3822833074963236453042738258902158003155416615667n) >> (195n - k);
}

export function bmax(...args: bigint[]): bigint {
return args.reduce((p, c) => (p > c ? p : c), 0n);
}

export function bmin(...args: bigint[]): bigint {
return args.reduce((p, c) => (p < c ? p : c), 0n);
}

export function abs(x: bigint): bigint {
return x < 0n ? -x : x;
}

export function sqrtWad(x: bigint): bigint {
if (x === 0n) return 0n;

let y = x;
let z = 181n;

if (y >= 0x10000000000000000000000000000000000n) {
y >>= 128n;
z <<= 64n;
}
if (y >= 0x1000000000000000000n) {
y >>= 64n;
z <<= 32n;
}
if (y >= 0x10000000000n) {
y >>= 32n;
z <<= 16n;
}
if (y >= 0x1000000n) {
y >>= 16n;
z <<= 8n;
}

z = (z * (y + 65536n)) >> 18n;

for (let i = 0; i < 7; ++i) {
z = (z + x / z) >> 1n;
}

if (x / z < z) z--;

return z;
}
119 changes: 119 additions & 0 deletions utils/interestRateCurve.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
import { WAD, expWad, lnWad, sqrtWad } from './fixedMath';

const MAX_UINT256 = 2n ** 256n - 1n;

type FloatingInterestRateCurve = (uFloating: bigint, uGlobal: bigint) => bigint;

export function floatingUtilization(assets: bigint, debt: bigint): bigint {
return assets !== 0n ? (debt * WAD) / assets : 0n;
}

export function globalUtilization(assets: bigint, debt: bigint, backupBorrowed: bigint): bigint {
return assets !== 0n ? ((debt + backupBorrowed) * WAD) / assets : 0n;
}

export function fixedUtilization(supplied: bigint, borrowed: bigint, assets: bigint): bigint {
return assets !== 0n && borrowed > supplied ? ((borrowed - supplied) * WAD) / assets : 0n;
}

export type FloatingParameters = {
A: bigint;
B: bigint;
maxUtilization: bigint;
naturalUtilization: bigint;
sigmoidSpeed: bigint;
growthSpeed: bigint;
maxRate: bigint;
};

const EXP_THRESHOLD = 135305999368893231588n;

export function floatingInterestRateCurve(parameters: FloatingParameters): FloatingInterestRateCurve {
return (uF: bigint, uG: bigint): bigint => {
const { A, B, maxUtilization, naturalUtilization, sigmoidSpeed, growthSpeed, maxRate } = parameters;

const r = (A * WAD) / (maxUtilization - uF) + B;
if (uG >= WAD) return maxRate;
if (uG === 0n) return r;

if (uG >= uF) {
const auxSigmoid = lnWad((naturalUtilization * WAD) / (WAD - naturalUtilization));
let x = -((sigmoidSpeed * (lnWad((uG * WAD) / (WAD - uG)) - auxSigmoid)) / WAD);
const sigmoid = x > EXP_THRESHOLD ? 0n : (WAD * WAD) / (WAD + expWad(x));

x = (-growthSpeed * lnWad(WAD - (sigmoid * uG) / WAD)) / WAD;
const globalFactor = expWad(x > EXP_THRESHOLD ? EXP_THRESHOLD : x);

if (globalFactor > MAX_UINT256 / r) {
return maxRate;
}

const rate = (r * globalFactor) / WAD;
return rate > maxRate ? maxRate : rate;
}

return r;
};
}

export type FixedParameters = FloatingParameters & {
maxPools: bigint;
maturity: bigint;
spreadFactor: bigint;
timestamp?: bigint;
timePreference: bigint;
fixedAllocation: bigint;
maturitySpeed: bigint;
};

export function fixedRate(parameters: FixedParameters, uFixed: bigint, uFloating: bigint, uGlobal: bigint) {
const { maxPools, spreadFactor, timePreference, maturitySpeed, fixedAllocation, maturity, timestamp } = parameters;
const base = floatingInterestRateCurve(parameters)(uFloating, uGlobal);
if (uGlobal === 0n) return { rate: base, z: WAD };

const sqAlpha = (maxPools * WAD * WAD) / fixedAllocation;
const alpha = sqrtWad(sqAlpha * WAD);
const sqX = (maxPools * uFixed * WAD * WAD) / (uGlobal * fixedAllocation);
const x = sqrtWad(sqX * WAD);
const a = ((2n * WAD - sqAlpha) * WAD) / ((alpha * (WAD - alpha)) / WAD);
const z = (a * x) / WAD + ((WAD - a) * sqX) / WAD - WAD;

const time = timestamp !== undefined ? timestamp : BigInt(Math.floor(Date.now() / 1000));
if (time >= maturity) throw new Error('Already matured');

const ttm = maturity - time;
const interval = 4n * 7n * 24n * 60n * 60n;
const ttMaxM = time + maxPools * interval - (time % interval);

return {
rate:
(base *
(WAD +
(expWad((maturitySpeed * lnWad((ttm * WAD) / ttMaxM)) / WAD) * (timePreference + (spreadFactor * z) / WAD)) /
WAD)) /
WAD,
z,
};
}

export function spreadModel(parameters: Omit<FixedParameters, 'maturity'>, uFloating: bigint, uGlobal: bigint) {
const time = parameters.timestamp ? parameters.timestamp : BigInt(Math.floor(Date.now() / 1000));

return (maturity: bigint, z: bigint) => {
const { maxPools, spreadFactor, timePreference, maturitySpeed } = parameters;
const base = floatingInterestRateCurve(parameters)(uFloating, uGlobal);
if (maturity === time) return base;

const ttm = maturity - time;
const interval = 4n * 7n * 24n * 60n * 60n;
const ttMaxM = time + maxPools * interval - (time % interval);

return (
(base *
(WAD +
(expWad((maturitySpeed * lnWad((ttm * WAD) / ttMaxM)) / WAD) * (timePreference + (spreadFactor * z) / WAD)) /
WAD)) /
WAD
);
};
}

0 comments on commit a6b237e

Please sign in to comment.