diff --git a/src/managers/MinSubordinationRatio.sol b/src/managers/MinSubordinationRatio.sol new file mode 100644 index 000000000..6c5a30980 --- /dev/null +++ b/src/managers/MinSubordinationRatio.sol @@ -0,0 +1,90 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.28; + +import {Auth} from "src/misc/Auth.sol"; +import {D18, d18} from "src/misc/types/D18.sol"; + +import {PoolId} from "src/common/types/PoolId.sol"; +import {AssetId} from "src/common/types/AssetId.sol"; +import {ShareClassId} from "src/common/types/ShareClassId.sol"; + +import {IHub} from "src/hub/interfaces/IHub.sol"; +import {IHoldings} from "src/hub/interfaces/IHoldings.sol"; +import {IShareClassManager} from "src/hub/interfaces/IShareClassManager.sol"; + +contract MinSubordination is Auth { + error InvalidJuniorRatio(D18 newRatio, D18 minRatio); + + IHub public immutable hub; + IHoldings public immutable holdings; + IShareClassManager public immutable scm; + + PoolId public immutable poolId; + ShareClassId public immutable seniorScId; + ShareClassId public immutable juniorScId; + + D18 public minJuniorRatio; + + constructor( + IHub hub_, + IHoldings holdings_, + IShareClassManager scm_, + PoolId poolId_, + ShareClassId seniorScId_, + ShareClassId juniorScId_ + ) Auth(msg.sender) { + hub = hub_; + holdings = holdings_; + scm = scm_; + + poolId = poolId_; + seniorScId = seniorScId_; + juniorScId = juniorScId_; + } + + // --- Administration --- + function setMinJuniorRatio(D18 newRatio) external auth { + minJuniorRatio = newRatio; + _checkRatio(); + } + + // --- Pool management --- + function fulfill( + AssetId assetId, + uint128 seniorDeposit, + uint128 seniorRedeem, + D18 seniorNavPerShare, + uint128 juniorDeposit, + uint128 juniorRedeem, + D18 juniorNavPerShare + ) external auth { + hub.updateSharePrice(poolId, seniorScId, seniorNavPerShare); + hub.updateSharePrice(poolId, juniorScId, juniorNavPerShare); + + hub.approveDeposits(poolId, seniorScId, assetId, scm.nowDepositEpoch(seniorScId, assetId), seniorDeposit); + hub.issueShares(poolId, seniorScId, assetId, scm.nowIssueEpoch(seniorScId, assetId), seniorNavPerShare, 0); + + hub.approveRedeems(poolId, seniorScId, assetId, scm.nowRedeemEpoch(seniorScId, assetId), seniorRedeem); + hub.revokeShares(poolId, seniorScId, assetId, scm.nowRevokeEpoch(seniorScId, assetId), seniorNavPerShare, 0); + + hub.approveDeposits(poolId, juniorScId, assetId, scm.nowDepositEpoch(juniorScId, assetId), juniorDeposit); + hub.issueShares(poolId, juniorScId, assetId, scm.nowIssueEpoch(juniorScId, assetId), juniorNavPerShare, 0); + + hub.approveRedeems(poolId, juniorScId, assetId, scm.nowRedeemEpoch(juniorScId, assetId), juniorRedeem); + hub.revokeShares(poolId, juniorScId, assetId, scm.nowRevokeEpoch(juniorScId, assetId), juniorNavPerShare, 0); + + _checkRatio(); + } + + // --- Validation --- + function _checkRatio() internal view { + (uint128 seniorIssuance, D18 seniorPrice) = scm.metrics(seniorScId); + (uint128 juniorIssuance, D18 juniorPrice) = scm.metrics(juniorScId); + + D18 seniorValue = d18(seniorIssuance) * seniorPrice; + D18 juniorValue = d18(juniorIssuance) * juniorPrice; + + D18 juniorRatio = juniorValue / (seniorValue + juniorValue); + require(juniorRatio >= minJuniorRatio, InvalidJuniorRatio(juniorRatio, minJuniorRatio)); + } +} diff --git a/src/misc/types/D18.sol b/src/misc/types/D18.sol index b510596a9..7e41cbd6f 100644 --- a/src/misc/types/D18.sol +++ b/src/misc/types/D18.sol @@ -91,6 +91,10 @@ function eq(D18 a, D18 b) pure returns (bool) { return D18.unwrap(a) == D18.unwrap(b); } +function geq(D18 a, D18 b) pure returns (bool) { + return D18.unwrap(a) >= D18.unwrap(b); +} + function isZero(D18 a) pure returns (bool) { return D18.unwrap(a) == 0; } @@ -108,6 +112,7 @@ using { sub as -, divD18 as /, eq, + geq as >=, mulD18 as *, mulUint128, mulUint256,