Skip to content

Commit

Permalink
Update info multicall (#958)
Browse files Browse the repository at this point in the history
* add poolRatesAndFeesMulticall; add Multicall to poolInfoUtils

* add poolDetailsMulticall method

* add natspec; remove unused functions

* update tests

* add poolBalanceDetails for remaining subgraph rpc calls

* expand poolInfoUtilsMulticall unit tests

* normalize token balances in poolBalanceDetails

* replace internal multicall usage

---------

Co-authored-by: Mike <[email protected]>
  • Loading branch information
MikeHathaway and Mike authored Nov 8, 2023
1 parent d343cc9 commit 4313584
Show file tree
Hide file tree
Showing 3 changed files with 155 additions and 44 deletions.
3 changes: 2 additions & 1 deletion src/PoolInfoUtils.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
pragma solidity 0.8.18;

import { Math } from '@openzeppelin/contracts/utils/math/Math.sol';
import { Multicall } from '@openzeppelin/contracts/utils/Multicall.sol';

import { IPool, IERC20Token } from './interfaces/pool/IPool.sol';

Expand Down Expand Up @@ -32,7 +33,7 @@ import { PoolCommons } from './libraries/external/PoolCommons.sol';
* @notice Contract for providing information for any deployed pool.
* @dev Pool info is calculated using same helper functions / logic as in `Pool` contracts.
*/
contract PoolInfoUtils {
contract PoolInfoUtils is Multicall {

/**
* @notice Exposes status of a liquidation auction.
Expand Down
133 changes: 100 additions & 33 deletions src/PoolInfoUtilsMulticall.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,27 @@

pragma solidity 0.8.18;

import { Multicall } from '@openzeppelin/contracts/utils/Multicall.sol';
import { IERC20 } from '@openzeppelin/contracts/token/ERC20/IERC20.sol';
import { IERC721 } from "@openzeppelin/contracts/token/ERC721/IERC721.sol";

import { PoolInfoUtils } from "./PoolInfoUtils.sol";

import { IPool } from "./interfaces/pool/IPool.sol";
import { IERC20Pool } from "./interfaces/pool/erc20/IERC20Pool.sol";

contract PoolInfoUtilsMulticall {

PoolInfoUtils public immutable poolInfoUtils;

struct PoolLoansInfo {
uint256 poolSize;
uint256 loansCount;
address maxBorrower;
uint256 pendingInflator;
uint256 pendingInterestFactor;
}

struct PoolPriceInfo {
uint256 hpb;
uint256 hpbIndex;
Expand All @@ -17,6 +32,12 @@ contract PoolInfoUtilsMulticall {
uint256 lupIndex;
}

struct PoolRatesAndFees {
uint256 lenderInterestMargin;
uint256 borrowFeeRate;
uint256 depositFeeRate;
}

struct PoolReservesInfo {
uint256 reserves;
uint256 claimableReserves;
Expand All @@ -32,38 +53,47 @@ contract PoolInfoUtilsMulticall {
uint256 poolTargetUtilization;
}

struct BucketInfo {
uint256 price;
uint256 quoteTokens;
uint256 collateral;
uint256 bucketLP;
uint256 scale;
uint256 exchangeRate;
struct PoolBalanceDetails {
uint256 debt; // debtInfo()
uint256 accruedDebt; // debtInfo()
uint256 debtInAuction; // debtInfo()
uint256 t0Debt2ToCollateral; // debtInfo()
uint256 depositUpToIndex;
uint256 quoteTokenBalance;
uint256 collateralTokenBalance;
}

constructor(PoolInfoUtils poolInfoUtils_) {
poolInfoUtils = poolInfoUtils_;
}

/**
* @notice Retrieves PoolPriceInfo, PoolReservesInfo, PoolUtilizationInfo and BucketInfo
* @notice Retrieves PoolLoansInfo, PoolPriceInfo, PoolRatesAndFees, PoolReservesInfo and PoolUtilizationInfo
* @dev This function is used to retrieve pool details available from PoolInfoUtils in a single RPC call for Indexers.
* @param ajnaPool_ Address of `Ajna` pool
* @param bucketIndex_ The index of the bucket to retrieve
* @return poolLoansInfo_ Pool loans info struct
* @return poolPriceInfo_ Pool price info struct
* @return poolRatesAndFees_ Pool rates and fees struct
* @return poolReservesInfo_ Pool reserves info struct
* @return poolUtilizationInfo_ Pool utilization info struct
* @return bucketInfo_ Bucket info struct
*/
function poolDetailsAndBucketInfo(address ajnaPool_, uint256 bucketIndex_)
external
view
returns(
PoolPriceInfo memory poolPriceInfo_,
PoolReservesInfo memory poolReservesInfo_,
PoolUtilizationInfo memory poolUtilizationInfo_,
BucketInfo memory bucketInfo_
)
{
function poolDetailsMulticall(address ajnaPool_) external view returns (
PoolLoansInfo memory poolLoansInfo_,
PoolPriceInfo memory poolPriceInfo_,
PoolRatesAndFees memory poolRatesAndFees_,
PoolReservesInfo memory poolReservesInfo_,
PoolUtilizationInfo memory poolUtilizationInfo_
) {
// retrieve loans info
(
poolLoansInfo_.poolSize,
poolLoansInfo_.loansCount,
poolLoansInfo_.maxBorrower,
poolLoansInfo_.pendingInflator,
poolLoansInfo_.pendingInterestFactor
) = poolInfoUtils.poolLoansInfo(ajnaPool_);

// retrieve prices info
(
poolPriceInfo_.hpb,
poolPriceInfo_.hpbIndex,
Expand All @@ -73,6 +103,12 @@ contract PoolInfoUtilsMulticall {
poolPriceInfo_.lupIndex
) = poolInfoUtils.poolPricesInfo(ajnaPool_);

// retrieve rates and fees
poolRatesAndFees_.lenderInterestMargin = poolInfoUtils.lenderInterestMargin(ajnaPool_);
poolRatesAndFees_.borrowFeeRate = poolInfoUtils.borrowFeeRate(ajnaPool_);
poolRatesAndFees_.depositFeeRate = poolInfoUtils.unutilizedDepositFeeRate(ajnaPool_);

// retrieve reserves info
(
poolReservesInfo_.reserves,
poolReservesInfo_.claimableReserves,
Expand All @@ -81,21 +117,13 @@ contract PoolInfoUtilsMulticall {
poolReservesInfo_.timeRemaining
) = poolInfoUtils.poolReservesInfo(ajnaPool_);

// retrieve utilization info
(
poolUtilizationInfo_.poolMinDebtAmount,
poolUtilizationInfo_.poolCollateralization,
poolUtilizationInfo_.poolActualUtilization,
poolUtilizationInfo_.poolTargetUtilization
) = poolInfoUtils.poolUtilizationInfo(ajnaPool_);

(
bucketInfo_.price,
bucketInfo_.quoteTokens,
bucketInfo_.collateral,
bucketInfo_.bucketLP,
bucketInfo_.scale,
bucketInfo_.exchangeRate
) = poolInfoUtils.bucketInfo(ajnaPool_, bucketIndex_);
}

/**
Expand All @@ -105,21 +133,60 @@ contract PoolInfoUtilsMulticall {
* @return borrowFeeRate Borrow fee rate calculated from the pool interest ra
* @return depositFeeRate Deposit fee rate calculated from the pool interest rate
*/
function poolRatesAndFees(address ajnaPool_)
function poolRatesAndFeesMulticall(address ajnaPool_)
external
view
returns
returns
(
uint256 lenderInterestMargin,
uint256 borrowFeeRate,
uint256 depositFeeRate
)
)
{
lenderInterestMargin = poolInfoUtils.lenderInterestMargin(ajnaPool_);
borrowFeeRate = poolInfoUtils.borrowFeeRate(ajnaPool_);
depositFeeRate = poolInfoUtils.unutilizedDepositFeeRate(ajnaPool_);
}

/**
* @notice Retrieves pool debtInfo, depositUpToIndex, quoteTokenBalance and collateralTokenBalance
* @dev This function is used to retrieve pool balance details in a single RPC call for Indexers.
* @param ajnaPool_ Address of `Ajna` pool
* @param index_ Index of deposit
* @param quoteTokenAddress_ Address of quote token
* @param collateralTokenAddress_ Address of collateral token
* @param isNFT_ Boolean indicating if the pool is an NFT pool
* @return poolBalanceDetails_ Pool balance details struct
*/
function poolBalanceDetails(address ajnaPool_, uint256 index_, address quoteTokenAddress_, address collateralTokenAddress_, bool isNFT_)
external view
returns (PoolBalanceDetails memory poolBalanceDetails_)
{
IPool pool = IPool(ajnaPool_);

// pool debtInfo
(poolBalanceDetails_.debt, poolBalanceDetails_.accruedDebt, poolBalanceDetails_.debtInAuction, poolBalanceDetails_.t0Debt2ToCollateral) = pool.debtInfo();

// depositUpToIndex(index_)
poolBalanceDetails_.depositUpToIndex = pool.depositUpToIndex(index_);

// get pool quote token balance
uint256 poolQuoteBalance = IERC20(quoteTokenAddress_).balanceOf(ajnaPool_);
uint256 quoteScale = pool.quoteTokenScale();
// normalize token balance to WAD scale
poolBalanceDetails_.quoteTokenBalance = poolQuoteBalance * quoteScale;

// get pool collateral token balance
if (isNFT_) {
// convert whole NFT amounts to WAD to match pool accounting
poolBalanceDetails_.collateralTokenBalance = IERC721(collateralTokenAddress_).balanceOf(ajnaPool_) * 10**18;
} else {
// normalize token balance to WAD scale
uint256 collateralScale = IERC20Pool(ajnaPool_).collateralScale();
uint256 poolCollateralBalance = IERC20(collateralTokenAddress_).balanceOf(ajnaPool_);
poolBalanceDetails_.collateralTokenBalance = poolCollateralBalance * collateralScale;
}
}

/**
* @notice Aggregate results from multiple read-only function calls
* @param functionSignatures_ Array of signatures of read-only functions to be called
Expand Down
63 changes: 53 additions & 10 deletions tests/forge/unit/ERC20Pool/ERC20PoolInfoUtils.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -265,16 +265,6 @@ contract ERC20PoolInfoUtilsTest is ERC20HelperContract {
);
}

function testPoolInfoUtilsMulticallPoolAndBucketInfo() external {
PoolInfoUtilsMulticall poolUtilsMulticall = new PoolInfoUtilsMulticall(_poolUtils);

PoolInfoUtilsMulticall.BucketInfo memory bucketInfo;

(,,, bucketInfo) = poolUtilsMulticall.poolDetailsAndBucketInfo(address(_pool), high);

assertEq(bucketInfo.bucketLP, 10_000 * 1e18);
}

function testPoolInfoUtilsMulticall() external {
PoolInfoUtilsMulticall poolUtilsMulticall = new PoolInfoUtilsMulticall(_poolUtils);

Expand Down Expand Up @@ -311,4 +301,57 @@ contract ERC20PoolInfoUtilsTest is ERC20HelperContract {
assertEq(debt, 21_020.192307692307702000 * 1e18);
assertEq(abi.decode(result[1], (uint256)), _poolUtils.htp(address(_pool)));
}

function testPoolInfoUtilsMulticallRatesAndFees() external {
PoolInfoUtilsMulticall poolUtilsMulticall = new PoolInfoUtilsMulticall(_poolUtils);

(uint256 lim, uint256 bfr, uint256 dfr) = poolUtilsMulticall.poolRatesAndFeesMulticall(address(_pool));

assertGe(lim, 0.000136986301369863 * 1e18);
assertGe(bfr, 0.000961538461538462 * 1e18);
assertGe(dfr, 0.000136986301369863 * 1e18);
}

function testPoolInfoUtilsMulticallPoolDetails() external {
PoolInfoUtilsMulticall poolUtilsMulticall = new PoolInfoUtilsMulticall(_poolUtils);

(
PoolInfoUtilsMulticall.PoolLoansInfo memory poolLoansInfo,
PoolInfoUtilsMulticall.PoolPriceInfo memory poolPriceInfo,
PoolInfoUtilsMulticall.PoolRatesAndFees memory poolRatesAndFees,
PoolInfoUtilsMulticall.PoolReservesInfo memory poolReservesInfo,
PoolInfoUtilsMulticall.PoolUtilizationInfo memory poolUtilizationInfo
) = poolUtilsMulticall.poolDetailsMulticall(address(_pool));

assertEq(poolLoansInfo.poolSize, 50_000 * 1e18);

assertEq(poolPriceInfo.hpb, 3_010.892022197881557845 * 1e18);
assertEq(poolPriceInfo.hpbIndex, 2550);
assertEq(poolPriceInfo.htp, 210.201923076923077020 * 1e18);
assertEq(poolPriceInfo.htpIndex, 3083);
assertEq(poolPriceInfo.lup, 2981.007422784467321543 * 1e18);
assertEq(poolPriceInfo.lupIndex, 2552);

assertGe(poolRatesAndFees.lenderInterestMargin, 0.000136986301369863 * 1e18);
assertGe(poolRatesAndFees.borrowFeeRate, 0.000961538461538462 * 1e18);
assertGe(poolRatesAndFees.depositFeeRate, 0.000136986301369863 * 1e18);

assertEq(poolReservesInfo.reserves, 20.192307692307702000 * 1e18);

assertEq(poolUtilizationInfo.poolMinDebtAmount, 2_102.019230769230770200 * 1e18);
}

function testPoolInfoUtilsMulticallPoolBalanceDetails() external {
PoolInfoUtilsMulticall poolUtilsMulticall = new PoolInfoUtilsMulticall(_poolUtils);

uint256 meaningfulIndex = 5000;
address quoteTokenAddress = IPool(_pool).quoteTokenAddress();
address collateralTokenAddress = IPool(_pool).collateralAddress();

PoolInfoUtilsMulticall.PoolBalanceDetails memory poolBalanceDetails = poolUtilsMulticall.poolBalanceDetails(address(_pool), meaningfulIndex, quoteTokenAddress, collateralTokenAddress, false);

assertEq(poolBalanceDetails.debt, 21_020.192307692307702000 * 1e18);
assertEq(poolBalanceDetails.quoteTokenBalance, 29_000 * 1e18);
assertEq(poolBalanceDetails.collateralTokenBalance, 100 * 1e18);
}
}

0 comments on commit 4313584

Please sign in to comment.