Skip to content
This repository has been archived by the owner on May 24, 2024. It is now read-only.

view - compute borrow spy for given utilisation #182

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 77 additions & 9 deletions contracts/views/EulerGeneralView.sol
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,27 @@ contract EulerGeneralView is Constants {
}


// works only for markets with kink IRM configured
function computeSPY(address eulerContract, address underlying, uint totalBorrows, uint totalBalancesUnderlying) public view returns (uint borrowSPY) {
BaseIRMLinearKink irm = BaseIRMLinearKink(getIRMImplementation(eulerContract, underlying));

uint32 utilisation;
if (totalBalancesUnderlying == 0) utilisation = 0; // empty pool arbitrarily given utilisation of 0
else utilisation = uint32(totalBorrows * (uint(type(uint32).max) * 1e18) / totalBalancesUnderlying / 1e18);

uint kink = irm.kink();
uint slope1 = irm.slope1();
uint slope2 = irm.slope2();

borrowSPY = irm.baseRate();
if (utilisation <= kink) {
borrowSPY += utilisation * slope1;
} else {
borrowSPY += kink * slope1;
borrowSPY += slope2 * (utilisation - kink);
}
}


// Interest rate model queries

Expand All @@ -207,27 +228,66 @@ contract EulerGeneralView is Constants {
uint maxSupplyAPY;
}

function doQueryIRM(QueryIRM memory q) external view returns (ResponseIRM memory r) {
Euler eulerProxy = Euler(q.eulerContract);
Markets marketsProxy = Markets(eulerProxy.moduleIdToProxy(MODULEID__MARKETS));

uint moduleId = marketsProxy.interestRateModel(q.underlying);
address moduleImpl = eulerProxy.moduleIdToImplementation(moduleId);
struct ResponseIRMFull {
uint kink;
DataPointIRM[] dataPoints;
}

BaseIRMLinearKink irm = BaseIRMLinearKink(moduleImpl);
struct DataPointIRM {
uint utilisation;
uint borrowAPY;
uint supplyAPY;
}

function doQueryIRM(QueryIRM memory q) external view returns (ResponseIRM memory r) {
BaseIRMLinearKink irm = BaseIRMLinearKink(getIRMImplementation(q.eulerContract, q.underlying));
uint kink = r.kink = irm.kink();
uint32 reserveFee = marketsProxy.reserveFee(q.underlying);

uint baseSPY = irm.baseRate();
uint kinkSPY = baseSPY + (kink * irm.slope1());
uint maxSPY = kinkSPY + ((type(uint32).max - kink) * irm.slope2());

Markets marketsProxy = Markets(Euler(q.eulerContract).moduleIdToProxy(MODULEID__MARKETS));
uint32 reserveFee = marketsProxy.reserveFee(q.underlying);

(r.baseAPY, r.baseSupplyAPY) = computeAPYs(baseSPY, 0, type(uint32).max, reserveFee);
(r.kinkAPY, r.kinkSupplyAPY) = computeAPYs(kinkSPY, kink, type(uint32).max, reserveFee);
(r.maxAPY, r.maxSupplyAPY) = computeAPYs(maxSPY, type(uint32).max, type(uint32).max, reserveFee);
}

function doQueryIRMFull(QueryIRM memory q) external view returns (ResponseIRMFull memory r) {
uint preKinkIncrements = 14;
uint postKinkIncrements = 5;

Markets marketsProxy = Markets(Euler(q.eulerContract).moduleIdToProxy(MODULEID__MARKETS));
BaseIRMLinearKink irm = BaseIRMLinearKink(getIRMImplementation(q.eulerContract, q.underlying));

uint32 reserveFee = marketsProxy.reserveFee(q.underlying);

r.kink = irm.kink();
r.dataPoints = new DataPointIRM[](preKinkIncrements + postKinkIncrements + 1);

uint totalBorrows = 0;
for (uint i = 0; i < r.dataPoints.length; i++) {
uint borrowSPY = computeSPY(q.eulerContract, q.underlying, totalBorrows, type(uint32).max);
(uint borrowAPY, uint supplyAPY) = computeAPYs(borrowSPY, totalBorrows, type(uint32).max, reserveFee);

r.dataPoints[i] = (DataPointIRM({
utilisation: totalBorrows * (uint(type(uint32).max) * 1e18) / type(uint32).max / 1e18,
borrowAPY: borrowAPY,
supplyAPY: supplyAPY
}));

if (i < preKinkIncrements - 1) {
totalBorrows += (type(uint32).max - r.kink) / preKinkIncrements;
} else if (i == preKinkIncrements - 1) {
totalBorrows = r.kink;
} else if (i < preKinkIncrements + postKinkIncrements - 1) {
totalBorrows += r.kink / postKinkIncrements;
} else {
totalBorrows = type(uint32).max;
}
}
}



Expand Down Expand Up @@ -258,4 +318,12 @@ contract EulerGeneralView is Constants {

return result.length == 32 ? string(abi.encodePacked(result)) : abi.decode(result, (string));
}

function getIRMImplementation(address eulerContract, address underlying) private view returns (address) {
Euler eulerProxy = Euler(eulerContract);
Markets marketsProxy = Markets(eulerProxy.moduleIdToProxy(MODULEID__MARKETS));

uint moduleId = marketsProxy.interestRateModel(underlying);
return eulerProxy.moduleIdToImplementation(moduleId);
}
}
52 changes: 52 additions & 0 deletions test/view.js
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,58 @@ et.testSet({
})


.test({
desc: "compute SPY and APY",
actions: ctx => [
{ action: 'setIRM', underlying: 'TST', irm: 'IRM_DEFAULT', },
{ action: 'setIRM', underlying: 'TST2', irm: 'IRM_DEFAULT', },
{ action: 'setReserveFee', underlying: 'TST', fee: 0.1, },
{ action: 'setReserveFee', underlying: 'TST2', fee: 0.3, },
{ action: 'cb', cb: async () => {
const uint32Max = et.BN(2).pow(32).sub(1)
const keysToCompare = ['base', 'kink', 'max']
const fixedParams = [
[ctx.contracts.euler.address, ctx.contracts.tokens.TST.address],
[ctx.contracts.euler.address, ctx.contracts.tokens.TST2.address]
]
const reserveParams = [0.1 * 4e9, 0.3 * 4e9]
const utilisationParams = [
[0, uint32Max],
[uint32Max.div(2).add(1), uint32Max], // kink 50% as per IRM_DEFAULT
[uint32Max, uint32Max]
]

const doQueryIRMBatchResults = []
const computeSPYAPYResults = []
for (const i of [0, 1]) {
doQueryIRMBatchResults.push(await ctx.contracts.eulerGeneralView.doQueryIRM(
{ eulerContract: fixedParams[i][0], underlying: fixedParams[i][1], }
))

computeSPYAPYResults.push({})

for (const [j, key] of keysToCompare.entries()) {
computeSPYAPYResults[i][key] = await ctx.contracts.eulerGeneralView.computeAPYs(
await ctx.contracts.eulerGeneralView.computeSPY(
...fixedParams[i], ...utilisationParams[j]
),
...utilisationParams[j],
reserveParams[i],
)
}
}

for (const i of [0, 1]) {
for (const key of keysToCompare) {
et.expect(doQueryIRMBatchResults[i][key + 'APY']).to.equal(computeSPYAPYResults[i][key]['borrowAPY'])
et.expect(doQueryIRMBatchResults[i][key + 'SupplyAPY']).to.equal(computeSPYAPYResults[i][key]['supplyAPY'])
}
}
}},
],
})


.test({
desc: "handle MKR like tokens returning bytes32 for name and symbol",
actions: ctx => [
Expand Down