Skip to content

Commit

Permalink
Add totalAvailableAssets methods which account for withdrawable/red…
Browse files Browse the repository at this point in the history
…eemable funds (#58)

* Add total assets available method

* Properly handle liquidity reserve in crank
  • Loading branch information
venables authored Oct 24, 2022
1 parent 56eaa58 commit f2555b5
Show file tree
Hide file tree
Showing 7 changed files with 208 additions and 58 deletions.
61 changes: 49 additions & 12 deletions contracts/Pool.sol
Original file line number Diff line number Diff line change
Expand Up @@ -355,6 +355,42 @@ contract Pool is IPool, ERC20 {
);
}

/**
* @dev Calculate the total amount of underlying assets held by the vault,
* excluding any assets due for withdrawal.
*/
function totalAvailableAssets() public view returns (uint256 assets) {
assets = PoolLib.calculateTotalAvailableAssets(
address(_liquidityAsset),
address(this),
_accountings.outstandingLoanPrincipals,
totalWithdrawableAssets()
);
}

/**
* @dev The total available supply that is not marked for withdrawal
*/
function totalAvailableSupply() public view returns (uint256 shares) {
shares = PoolLib.calculateTotalAvailableShares(
address(this),
totalRedeemableShares()
);
}

/**
* @dev The sum of all assets available in the liquidity pool, excluding
* any assets that are marked for withdrawal.
*/
function liquidityPoolAssets() public view returns (uint256 assets) {
assets = PoolLib.calculateTotalAvailableAssets(
address(_liquidityAsset),
address(this),
0, // do not include any loan principles
totalWithdrawableAssets()
);
}

/*//////////////////////////////////////////////////////////////
Crank
//////////////////////////////////////////////////////////////*/
Expand All @@ -364,17 +400,17 @@ contract Pool is IPool, ERC20 {
*/
function crank() external returns (uint256 redeemableShares) {
// Calculate the amount available for withdrawal
// TODO: Find the real available liquidity
uint256 availableLiquidity = totalSupply();
uint256 liquidAssets = liquidityPoolAssets();

// How much is available to redeem for this period
uint256 availableShares = availableLiquidity
uint256 availableAssets = liquidAssets
.mul(_poolSettings.withdrawGateBps)
.mul(PoolLib.RAY)
.div(10_000)
.div(PoolLib.RAY);

if (availableShares <= 0) {
uint256 availableShares = convertToShares(availableAssets);

if (availableAssets <= 0 || availableShares <= 0) {
// unable to redeem anything
redeemableShares = 0;
return 0;
Expand All @@ -393,6 +429,7 @@ contract Pool is IPool, ERC20 {
globalState.eligibleShares
);

// Update the global withdraw state
_globalWithdrawState = PoolLib.updateWithdrawStateForWithdraw(
globalState,
convertToAssets(redeemableShares),
Expand Down Expand Up @@ -626,15 +663,15 @@ contract Pool is IPool, ERC20 {
* @dev Returns the number of shares that are available to be redeemed
* overall in the current block.
*/
function totalRedeemableBalance() external view returns (uint256 shares) {
function totalRedeemableShares() public view returns (uint256 shares) {
shares = _currentGlobalWithdrawState().redeemableShares;
}

/**
* @dev Returns the number of `assets` that are available to be withdrawn
* overall in the current block.
*/
function totalWithdrawableBalance() external view returns (uint256 assets) {
function totalWithdrawableAssets() public view returns (uint256 assets) {
assets = _currentGlobalWithdrawState().withdrawableAssets;
}

Expand Down Expand Up @@ -758,8 +795,8 @@ contract Pool is IPool, ERC20 {
return
PoolLib.calculateAssetsToShares(
assets,
totalSupply(),
totalAssets()
totalAvailableSupply(),
totalAvailableAssets()
);
}

Expand All @@ -775,8 +812,8 @@ contract Pool is IPool, ERC20 {
return
PoolLib.calculateSharesToAssets(
shares,
totalSupply(),
totalAssets()
totalAvailableSupply(),
totalAvailableAssets()
);
}

Expand All @@ -794,7 +831,7 @@ contract Pool is IPool, ERC20 {
PoolLib.calculateMaxDeposit(
lifeCycleState(),
_poolSettings.maxCapacity,
totalAssets()
totalAvailableAssets()
);
}

Expand Down
1 change: 1 addition & 0 deletions contracts/interfaces/IERC4626.sol
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ interface IERC4626 is IERC20 {

/**
* @dev Calculate the total amount of underlying assets held by the vault.
* NOTE: This method includes assets that are marked for withdrawal.
*/
function totalAssets() external view returns (uint256);

Expand Down
80 changes: 60 additions & 20 deletions contracts/libraries/PoolLib.sol
Original file line number Diff line number Diff line change
Expand Up @@ -86,9 +86,13 @@ library PoolLib {
event LoanDefaulted(address indexed loan);

/**
* @dev Math `ceil` method to round up on division
* @dev Divide two numbers and round the result up
*/
function ceil(uint256 lhs, uint256 rhs) internal pure returns (uint256) {
function divideCeil(uint256 lhs, uint256 rhs)
internal
pure
returns (uint256)
{
return (lhs + rhs - 1) / rhs;
}

Expand Down Expand Up @@ -213,47 +217,51 @@ library PoolLib {
/**
* @dev Computes the exchange rate for converting assets to shares
* @param assets Amount of assets to exchange
* @param sharesTotalSupply Supply of Vault's ERC20 shares
* @param totalAssets Pool total assets
* @param totalAvailableShares Supply of Vault's ERC20 shares (excluding marked for redemption)
* @param totalAvailableAssets Pool total available assets (excluding marked for withdrawal)
* @return shares The amount of shares
*/
function calculateAssetsToShares(
uint256 assets,
uint256 sharesTotalSupply,
uint256 totalAssets
uint256 totalAvailableShares,
uint256 totalAvailableAssets
) external pure returns (uint256 shares) {
if (totalAssets == 0) {
if (totalAvailableAssets == 0) {
return assets;
}

// TODO: add in interest rate.
uint256 rate = (sharesTotalSupply.mul(RAY)).div(totalAssets);
uint256 rate = (totalAvailableShares.mul(RAY)).div(
totalAvailableAssets
);
shares = (rate.mul(assets)).div(RAY);
}

/**
* @dev Computes the exchange rate for converting shares to assets
* @param shares Amount of shares to exchange
* @param sharesTotalSupply Supply of Vault's ERC20 shares
* @param totalAssets Pool NAV
* @param totalAvailableShares Supply of Vault's ERC20 shares (excluding marked for redemption)
* @param totalAvailableAssets Pool total available assets (excluding marked for withdrawal)
* @return assets The amount of shares
*/
function calculateSharesToAssets(
uint256 shares,
uint256 sharesTotalSupply,
uint256 totalAssets
uint256 totalAvailableShares,
uint256 totalAvailableAssets
) external pure returns (uint256 assets) {
if (sharesTotalSupply == 0) {
if (totalAvailableShares == 0) {
return shares;
}

// TODO: add in interest rate.
uint256 rate = (totalAssets.mul(RAY)).div(sharesTotalSupply);
uint256 rate = (totalAvailableAssets.mul(RAY)).div(
totalAvailableShares
);
assets = (rate.mul(shares)).div(RAY);
}

/**
* @dev Calculates total assets held by Vault
* @dev Calculates total assets held by Vault (including those marked for withdrawal)
* @param asset Amount of total assets held by the Vault
* @param vault Address of the ERC4626 vault
* @param outstandingLoanPrincipals Sum of all oustanding loan principals
Expand All @@ -263,27 +271,59 @@ library PoolLib {
address asset,
address vault,
uint256 outstandingLoanPrincipals
) external view returns (uint256 totalAssets) {
) public view returns (uint256 totalAssets) {
totalAssets =
IERC20(asset).balanceOf(vault) +
outstandingLoanPrincipals;
}

/**
* @dev Calculates total assets held by Vault (excluding marked for withdrawal)
* @param asset Amount of total assets held by the Vault
* @param vault Address of the ERC4626 vault
* @param outstandingLoanPrincipals Sum of all oustanding loan principals
* @param withdrawableAssets Sum of all withdrawable assets
* @return totalAvailableAssets Total available assets (excluding marked for withdrawal)
*/
function calculateTotalAvailableAssets(
address asset,
address vault,
uint256 outstandingLoanPrincipals,
uint256 withdrawableAssets
) external view returns (uint256 totalAvailableAssets) {
totalAvailableAssets =
calculateTotalAssets(asset, vault, outstandingLoanPrincipals) -
withdrawableAssets;
}

/**
* @dev Calculates total shares held by Vault (excluding marked for redemption)
* @param vault Address of the ERC4626 vault
* @param redeemableShares Sum of all withdrawable assets
* @return totalAvailableShares Total redeemable shares (excluding marked for redemption)
*/
function calculateTotalAvailableShares(
address vault,
uint256 redeemableShares
) external view returns (uint256 totalAvailableShares) {
totalAvailableShares = IERC20(vault).totalSupply() - redeemableShares;
}

/**
* @dev Calculates the max deposit allowed in the pool
* @param poolLifeCycleState The current pool lifecycle state
* @param poolMaxCapacity Max pool capacity allowed per the pool settings
* @param totalAssets Sum of all pool assets
* @param totalAvailableAssets Sum of all pool assets (excluding marked for withdrawal)
* @return Max deposit allowed
*/
function calculateMaxDeposit(
IPoolLifeCycleState poolLifeCycleState,
uint256 poolMaxCapacity,
uint256 totalAssets
uint256 totalAvailableAssets
) external pure returns (uint256) {
return
poolLifeCycleState == IPoolLifeCycleState.Active
? poolMaxCapacity - totalAssets
? poolMaxCapacity - totalAvailableAssets
: 0;
}

Expand Down Expand Up @@ -425,7 +465,7 @@ library PoolLib {
pure
returns (uint256)
{
return ceil(shares * requestFeeBps, 10_000);
return divideCeil(shares * requestFeeBps, 10_000);
}

/**
Expand Down
26 changes: 24 additions & 2 deletions contracts/mocks/PoolLibTestWrapper.sol
Original file line number Diff line number Diff line change
Expand Up @@ -93,16 +93,38 @@ contract PoolLibTestWrapper is ERC20("PoolLibTest", "PLT") {
);
}

function calculateTotalAvailableAssets(
address asset,
address vault,
uint256 outstandingLoanPrincipals,
uint256 withdrawableAssets
) external view returns (uint256) {
return
PoolLib.calculateTotalAvailableAssets(
asset,
vault,
outstandingLoanPrincipals,
withdrawableAssets
);
}

function calculateTotalAvailableShares(
address vault,
uint256 redeemableShares
) external view returns (uint256) {
return PoolLib.calculateTotalAvailableShares(vault, redeemableShares);
}

function calculateMaxDeposit(
IPoolLifeCycleState poolLifeCycleState,
uint256 poolMaxCapacity,
uint256 totalAssets
uint256 totalAvailableAssets
) external pure returns (uint256) {
return
PoolLib.calculateMaxDeposit(
poolLifeCycleState,
poolMaxCapacity,
totalAssets
totalAvailableAssets
);
}

Expand Down
8 changes: 4 additions & 4 deletions test/Pool.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1020,7 +1020,7 @@ describe("Pool", () => {
});
});

describe("totalRedeemableBalance()", () => {
describe("totalRedeemableShares()", () => {
it("returns the redeemable number of shares in this pool", async () => {
const {
pool,
Expand All @@ -1041,7 +1041,7 @@ describe("Pool", () => {
await time.increase(withdrawRequestPeriodDuration);
await pool.connect(poolManager).crank();

expect(await pool.totalRedeemableBalance()).to.equal(40);
expect(await pool.totalRedeemableShares()).to.equal(40);
});
});

Expand All @@ -1062,7 +1062,7 @@ describe("Pool", () => {
});
});

describe("totalWithdrawableBalance()", () => {
describe("totalWithdrawableAssets()", () => {
it("returns the withdrawable number of shares in this pool", async () => {
const {
pool,
Expand All @@ -1083,7 +1083,7 @@ describe("Pool", () => {
await time.increase(withdrawRequestPeriodDuration);
await pool.connect(poolManager).crank();

expect(await pool.totalWithdrawableBalance()).to.equal(40);
expect(await pool.totalWithdrawableAssets()).to.equal(40);
});
});
});
Expand Down
Loading

0 comments on commit f2555b5

Please sign in to comment.