From 59d5f3094eddcd9ca5869377c28469752c130009 Mon Sep 17 00:00:00 2001 From: bin Date: Sat, 30 Dec 2023 11:33:25 +0800 Subject: [PATCH] Move distLossToTranches and distLossRecoveryToTranches from policy to pool --- contracts/BaseTranchesPolicy.sol | 43 ---- contracts/Pool.sol | 116 ++++++---- contracts/interfaces/ITranchesPolicy.sol | 36 --- test/BaseTranchesPolicyTest.ts | 271 ----------------------- 4 files changed, 73 insertions(+), 393 deletions(-) delete mode 100644 test/BaseTranchesPolicyTest.ts diff --git a/contracts/BaseTranchesPolicy.sol b/contracts/BaseTranchesPolicy.sol index d7c4ac7d..13684467 100644 --- a/contracts/BaseTranchesPolicy.sol +++ b/contracts/BaseTranchesPolicy.sol @@ -64,49 +64,6 @@ abstract contract BaseTranchesPolicy is PoolConfigCache, ITranchesPolicy { return (profitsForTrancheVault, profitsForFirstLossCover); } - /// @inheritdoc ITranchesPolicy - function distLossToTranches( - uint256 loss, - uint96[2] memory assets - ) external pure returns (uint96[2] memory updatedAssets, uint96[2] memory losses) { - uint256 juniorTotalAssets = assets[JUNIOR_TRANCHE]; - // Distribute losses to junior tranche up to the total junior asset - losses[JUNIOR_TRANCHE] = uint96(juniorTotalAssets >= loss ? loss : juniorTotalAssets); - losses[SENIOR_TRANCHE] = uint96(loss - losses[JUNIOR_TRANCHE]); - updatedAssets[JUNIOR_TRANCHE] = uint96(assets[JUNIOR_TRANCHE] - losses[JUNIOR_TRANCHE]); - updatedAssets[SENIOR_TRANCHE] = uint96(assets[SENIOR_TRANCHE] - losses[SENIOR_TRANCHE]); - - return (updatedAssets, losses); - } - - /// @inheritdoc ITranchesPolicy - function distLossRecoveryToTranches( - uint256 lossRecovery, - uint96[2] memory assets, - uint96[2] memory losses - ) external pure returns (uint256 remainingLossRecovery, uint96[2] memory, uint96[2] memory) { - uint96 seniorLoss = losses[SENIOR_TRANCHE]; - // Allocates recovery to senior first, up to the total senior losses - uint256 seniorLossRecovery = lossRecovery >= seniorLoss ? seniorLoss : lossRecovery; - if (seniorLossRecovery > 0) { - assets[SENIOR_TRANCHE] += uint96(seniorLossRecovery); - losses[SENIOR_TRANCHE] -= uint96(seniorLossRecovery); - } - - remainingLossRecovery = lossRecovery - seniorLossRecovery; - if (remainingLossRecovery > 0) { - uint96 juniorLoss = losses[JUNIOR_TRANCHE]; - uint256 juniorLossRecovery = remainingLossRecovery >= juniorLoss - ? juniorLoss - : remainingLossRecovery; - assets[JUNIOR_TRANCHE] += uint96(juniorLossRecovery); - losses[JUNIOR_TRANCHE] -= uint96(juniorLossRecovery); - remainingLossRecovery = remainingLossRecovery - juniorLossRecovery; - } - - return (remainingLossRecovery, assets, losses); - } - /// @inheritdoc ITranchesPolicy function refreshYieldTracker(uint96[2] memory assets) public virtual { // Empty function for RiskAdjustedTranchePolicy diff --git a/contracts/Pool.sol b/contracts/Pool.sol index af24a88e..07b16f0e 100644 --- a/contracts/Pool.sol +++ b/contracts/Pool.sol @@ -274,30 +274,39 @@ contract Pool is PoolConfigCache, IPool { if (loss > 0) { // If there are losses remaining, let the junior and senior tranches cover the losses. - TranchesAssets memory assets = tranchesAssets; - (uint96[2] memory newAssets, uint96[2] memory lossesDelta) = tranchesPolicy - .distLossToTranches( - loss, - [assets.seniorTotalAssets, assets.juniorTotalAssets] - ); - _updateTranchesAssets(newAssets); - - TranchesLosses memory losses = tranchesLosses; - losses.seniorLoss += lossesDelta[SENIOR_TRANCHE]; - losses.juniorLoss += lossesDelta[JUNIOR_TRANCHE]; - tranchesLosses = losses; - - emit LossDistributed( - loss, - newAssets[SENIOR_TRANCHE], - newAssets[JUNIOR_TRANCHE], - losses.seniorLoss, - losses.juniorLoss - ); + _distLossToTranches(loss); } } } + /** + * @notice Distributes loss to tranches + * @param loss the loss amount + */ + function _distLossToTranches(uint256 loss) internal { + TranchesAssets memory assets = tranchesAssets; + uint256 juniorTotalAssets = assets.juniorTotalAssets; + // Distribute losses to junior tranche up to the total junior asset + uint256 juniorLoss = juniorTotalAssets >= loss ? loss : juniorTotalAssets; + uint256 seniorLoss = loss - juniorLoss; + + assets.seniorTotalAssets -= uint96(seniorLoss); + assets.juniorTotalAssets -= uint96(juniorLoss); + _updateTranchesAssets([assets.seniorTotalAssets, assets.juniorTotalAssets]); + TranchesLosses memory losses = tranchesLosses; + losses.seniorLoss += uint96(seniorLoss); + losses.juniorLoss += uint96(juniorLoss); + tranchesLosses = losses; + + emit LossDistributed( + loss, + assets.seniorTotalAssets, + assets.juniorTotalAssets, + losses.seniorLoss, + losses.juniorLoss + ); + } + /** * @notice Utility function that distributes loss recovery to different tranches and * First Loss Covers (FLCs). The distribution sequence is: senior tranche, junior tranche, @@ -307,29 +316,7 @@ contract Pool is PoolConfigCache, IPool { */ function _distributeLossRecovery(uint256 lossRecovery) internal { if (lossRecovery > 0) { - TranchesAssets memory assets = tranchesAssets; - TranchesLosses memory losses = tranchesLosses; - ( - uint256 remainingLossRecovery, - uint96[2] memory newAssets, - uint96[2] memory newLosses - ) = tranchesPolicy.distLossRecoveryToTranches( - lossRecovery, - [assets.seniorTotalAssets, assets.juniorTotalAssets], - [losses.seniorLoss, losses.juniorLoss] - ); - _updateTranchesAssets(newAssets); - tranchesLosses = TranchesLosses({ - seniorLoss: newLosses[SENIOR_TRANCHE], - juniorLoss: newLosses[JUNIOR_TRANCHE] - }); - emit LossRecoveryDistributed( - lossRecovery - remainingLossRecovery, - newAssets[SENIOR_TRANCHE], - newAssets[JUNIOR_TRANCHE], - newLosses[SENIOR_TRANCHE], - newLosses[JUNIOR_TRANCHE] - ); + uint256 remainingLossRecovery = _distLossRecoveryToTranches(lossRecovery); // Distributes the remainder to First Loss Covers. uint256 numFirstLossCovers = _firstLossCovers.length; @@ -340,6 +327,49 @@ contract Pool is PoolConfigCache, IPool { } } + /** + * @notice Distributes loss recovery to tranches + * @param lossRecovery the loss recovery amount + * @return remainingLossRecovery the remaining loss recovery after distributing among tranches + */ + function _distLossRecoveryToTranches( + uint256 lossRecovery + ) internal returns (uint256 remainingLossRecovery) { + TranchesAssets memory assets = tranchesAssets; + TranchesLosses memory losses = tranchesLosses; + uint96 seniorLoss = losses.seniorLoss; + // Allocates recovery to senior first, up to the total senior losses + uint256 seniorLossRecovery = lossRecovery >= seniorLoss ? seniorLoss : lossRecovery; + if (seniorLossRecovery > 0) { + assets.seniorTotalAssets += uint96(seniorLossRecovery); + losses.seniorLoss -= uint96(seniorLossRecovery); + } + + remainingLossRecovery = lossRecovery - seniorLossRecovery; + if (remainingLossRecovery > 0) { + uint96 juniorLoss = losses.juniorLoss; + uint256 juniorLossRecovery = remainingLossRecovery >= juniorLoss + ? juniorLoss + : remainingLossRecovery; + assets.juniorTotalAssets += uint96(juniorLossRecovery); + losses.juniorLoss -= uint96(juniorLossRecovery); + remainingLossRecovery = remainingLossRecovery - juniorLossRecovery; + } + + _updateTranchesAssets([assets.seniorTotalAssets, assets.juniorTotalAssets]); + tranchesLosses = losses; + + emit LossRecoveryDistributed( + lossRecovery - remainingLossRecovery, + assets.seniorTotalAssets, + assets.juniorTotalAssets, + losses.seniorLoss, + losses.juniorLoss + ); + + return remainingLossRecovery; + } + /** * @notice Gets the total asset of a tranche * @param index the tranche index. diff --git a/contracts/interfaces/ITranchesPolicy.sol b/contracts/interfaces/ITranchesPolicy.sol index f1f2619d..039c2754 100644 --- a/contracts/interfaces/ITranchesPolicy.sol +++ b/contracts/interfaces/ITranchesPolicy.sol @@ -6,42 +6,6 @@ pragma solidity ^0.8.0; */ interface ITranchesPolicy { - /** - * @notice Distributes loss to tranches - * @dev Passing asset value for the tranches as a parameter to make the function stateless - * @param loss the loss amount - * @param assets assets for each tranche, index 0 for senior, 1 for junior - * @return updatedAssets updated total assets for each tranche - * @return losses losses for each tranche - */ - function distLossToTranches( - uint256 loss, - uint96[2] memory assets - ) external pure returns (uint96[2] memory updatedAssets, uint96[2] memory losses); - - /** - * @notice Distributes loss recovery to tranches - * @dev Passing asset value for the tranches as a parameter to make the function stateless - * @param lossRecovery the loss recovery amount - * @param assets assets for each tranche, index 0 for senior, 1 for junior - * @param losses losses for each tranche, index 0 for senior, 1 for junior - * @return remainingLossRecovery the remaining loss recovery after distributing among tranches - * @return newAssets updated total assets for each tranche, index 0 for senior, 1 for junior - * @return newLosses updated total losses for each tranche, index 0 for senior, 1 for junior - */ - function distLossRecoveryToTranches( - uint256 lossRecovery, - uint96[2] memory assets, - uint96[2] memory losses - ) - external - pure - returns ( - uint256 remainingLossRecovery, - uint96[2] memory newAssets, - uint96[2] memory newLosses - ); - /** * @notice Distributes profit to tranches * @dev Passing asset value for the tranches as a parameter to make the function stateless diff --git a/test/BaseTranchesPolicyTest.ts b/test/BaseTranchesPolicyTest.ts deleted file mode 100644 index e4a70903..00000000 --- a/test/BaseTranchesPolicyTest.ts +++ /dev/null @@ -1,271 +0,0 @@ -import { loadFixture } from "@nomicfoundation/hardhat-network-helpers"; -import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; -import { expect } from "chai"; -import { BigNumber as BN } from "ethers"; -import { ethers } from "hardhat"; -import { - Calendar, - CreditDueManager, - EpochManager, - EvaluationAgentNFT, - FirstLossCover, - HumaConfig, - MockPoolCredit, - MockToken, - Pool, - PoolConfig, - PoolFeeManager, - PoolSafe, - RiskAdjustedTranchesPolicy, - TrancheVault, -} from "../typechain-types"; -import { - CONSTANTS, - PnLCalculator, - deployAndSetupPoolContracts, - deployProtocolContracts, -} from "./BaseTest"; -import { getFirstLossCoverInfo, toToken } from "./TestUtils"; - -let defaultDeployer: SignerWithAddress, - protocolOwner: SignerWithAddress, - treasury: SignerWithAddress, - eaServiceAccount: SignerWithAddress, - pdsServiceAccount: SignerWithAddress; -let poolOwner: SignerWithAddress, - poolOwnerTreasury: SignerWithAddress, - evaluationAgent: SignerWithAddress, - poolOperator: SignerWithAddress; -let lender: SignerWithAddress; - -let eaNFTContract: EvaluationAgentNFT, - humaConfigContract: HumaConfig, - mockTokenContract: MockToken; -let poolConfigContract: PoolConfig, - poolFeeManagerContract: PoolFeeManager, - poolSafeContract: PoolSafe, - calendarContract: Calendar, - borrowerFirstLossCoverContract: FirstLossCover, - affiliateFirstLossCoverContract: FirstLossCover, - tranchesPolicyContract: RiskAdjustedTranchesPolicy, - poolContract: Pool, - epochManagerContract: EpochManager, - seniorTrancheVaultContract: TrancheVault, - juniorTrancheVaultContract: TrancheVault, - creditContract: MockPoolCredit, - creditDueManagerContract: CreditDueManager; - -describe("BaseTranchesPolicy Tests", function () { - before(async function () { - [ - defaultDeployer, - protocolOwner, - treasury, - eaServiceAccount, - pdsServiceAccount, - poolOwner, - poolOwnerTreasury, - evaluationAgent, - poolOperator, - lender, - ] = await ethers.getSigners(); - }); - - async function prepare() { - [eaNFTContract, humaConfigContract, mockTokenContract] = await deployProtocolContracts( - protocolOwner, - treasury, - eaServiceAccount, - pdsServiceAccount, - poolOwner, - ); - - [ - poolConfigContract, - poolFeeManagerContract, - poolSafeContract, - calendarContract, - borrowerFirstLossCoverContract, - affiliateFirstLossCoverContract, - tranchesPolicyContract, - poolContract, - epochManagerContract, - seniorTrancheVaultContract, - juniorTrancheVaultContract, - creditContract as unknown, - creditDueManagerContract, - ] = await deployAndSetupPoolContracts( - humaConfigContract, - mockTokenContract, - eaNFTContract, - "FixedSeniorYieldTranchePolicy", - defaultDeployer, - poolOwner, - "MockPoolCredit", - "BorrowerLevelCreditManager", - evaluationAgent, - poolOwnerTreasury, - poolOperator, - [lender], - ); - - const juniorDepositAmount = toToken(100_000); - await juniorTrancheVaultContract - .connect(lender) - .deposit(juniorDepositAmount, lender.address); - const seniorDepositAmount = toToken(300_000); - await seniorTrancheVaultContract - .connect(lender) - .deposit(seniorDepositAmount, lender.address); - } - - beforeEach(async function () { - await loadFixture(prepare); - }); - - describe("distLossToTranches", function () { - it("Calculates the correct loss when the junior tranche loss can be covered by assets", async function () { - const assets = await poolContract.currentTranchesAssets(); - const loss = assets[CONSTANTS.JUNIOR_TRANCHE]; - - const firstLossCoverInfos = await Promise.all( - [borrowerFirstLossCoverContract, affiliateFirstLossCoverContract].map( - async (contract) => await getFirstLossCoverInfo(contract, poolConfigContract), - ), - ); - const [newAssets, newLosses] = await PnLCalculator.calcLoss( - loss, - assets, - firstLossCoverInfos, - ); - const result = await tranchesPolicyContract.callStatic.distLossToTranches( - loss, - assets, - ); - - expect(result[0][CONSTANTS.SENIOR_TRANCHE]).to.equal( - newAssets[CONSTANTS.SENIOR_TRANCHE], - ); - expect(result[0][CONSTANTS.JUNIOR_TRANCHE]).to.equal( - newAssets[CONSTANTS.JUNIOR_TRANCHE], - ); - expect(result[1][CONSTANTS.SENIOR_TRANCHE]).to.equal( - newLosses[CONSTANTS.SENIOR_TRANCHE], - ); - expect(result[1][CONSTANTS.JUNIOR_TRANCHE]).to.equal( - newLosses[CONSTANTS.JUNIOR_TRANCHE], - ); - }); - - it("Calculates the correct loss when junior tranche loss cannot be covered", async function () { - const assets = await poolContract.currentTranchesAssets(); - const loss = assets[CONSTANTS.JUNIOR_TRANCHE].add(1); - - const firstLossCoverInfos = await Promise.all( - [borrowerFirstLossCoverContract, affiliateFirstLossCoverContract].map( - async (contract) => await getFirstLossCoverInfo(contract, poolConfigContract), - ), - ); - const [newAssets, newLosses] = await PnLCalculator.calcLoss( - loss, - assets, - firstLossCoverInfos, - ); - const result = await tranchesPolicyContract.callStatic.distLossToTranches( - loss, - assets, - ); - - expect(result[0][CONSTANTS.SENIOR_TRANCHE]).to.equal( - newAssets[CONSTANTS.SENIOR_TRANCHE], - ); - expect(result[0][CONSTANTS.JUNIOR_TRANCHE]).to.equal( - newAssets[CONSTANTS.JUNIOR_TRANCHE], - ); - expect(result[0][CONSTANTS.JUNIOR_TRANCHE]).to.equal(0); - expect(result[1][CONSTANTS.SENIOR_TRANCHE]).to.equal( - newLosses[CONSTANTS.SENIOR_TRANCHE], - ); - expect(result[1][CONSTANTS.JUNIOR_TRANCHE]).to.equal( - newLosses[CONSTANTS.JUNIOR_TRANCHE], - ); - expect(result[1][CONSTANTS.JUNIOR_TRANCHE]).to.equal(assets[CONSTANTS.JUNIOR_TRANCHE]); - }); - }); - - describe("distLossRecoveryToTranches", function () { - it("Calculates the correct loss when only the senior loss can be recovered", async function () { - const assets = await poolContract.currentTranchesAssets(); - const loss = assets[CONSTANTS.SENIOR_TRANCHE].add(assets[CONSTANTS.JUNIOR_TRANCHE]); - const recovery = assets[CONSTANTS.SENIOR_TRANCHE]; - - const [assetsAfterLosses, losses] = - await tranchesPolicyContract.callStatic.distLossToTranches(loss, assets); - const [, newAssetsWithLossRecovery, newLossesWithLossRecovery] = - await PnLCalculator.calcLossRecovery(recovery, assetsAfterLosses, losses, [ - BN.from(0), - BN.from(0), - ]); - const resultWithLossRecovery = - await tranchesPolicyContract.callStatic.distLossRecoveryToTranches( - recovery, - assetsAfterLosses, - losses, - ); - expect(resultWithLossRecovery[0]).to.equal(0); - expect(resultWithLossRecovery[1][CONSTANTS.SENIOR_TRANCHE]).to.equal( - newAssetsWithLossRecovery[CONSTANTS.SENIOR_TRANCHE], - ); - expect(resultWithLossRecovery[1][CONSTANTS.JUNIOR_TRANCHE]).to.equal( - newAssetsWithLossRecovery[CONSTANTS.JUNIOR_TRANCHE], - ); - expect(resultWithLossRecovery[1][CONSTANTS.JUNIOR_TRANCHE]).to.equal(0); - expect(resultWithLossRecovery[2][CONSTANTS.SENIOR_TRANCHE]).to.equal( - newLossesWithLossRecovery[CONSTANTS.SENIOR_TRANCHE], - ); - expect(resultWithLossRecovery[2][CONSTANTS.JUNIOR_TRANCHE]).to.equal( - newLossesWithLossRecovery[CONSTANTS.JUNIOR_TRANCHE], - ); - expect(resultWithLossRecovery[2][CONSTANTS.JUNIOR_TRANCHE]).to.equal( - assets[CONSTANTS.JUNIOR_TRANCHE], - ); - }); - - it("Calculates the correct loss when both the senior and junior loss can be recovered", async function () { - const assets = await poolContract.currentTranchesAssets(); - const loss = assets[CONSTANTS.SENIOR_TRANCHE].add(assets[CONSTANTS.JUNIOR_TRANCHE]); - const recovery = loss; - - const [assetsAfterLosses, losses] = - await tranchesPolicyContract.callStatic.distLossToTranches(loss, assets); - const [, newAssetsWithLossRecovery, newLossesWithLossRecovery] = - await PnLCalculator.calcLossRecovery(recovery, assetsAfterLosses, losses, [ - BN.from(0), - BN.from(0), - ]); - const resultWithLossRecovery = - await tranchesPolicyContract.callStatic.distLossRecoveryToTranches( - recovery, - assetsAfterLosses, - losses, - ); - expect(resultWithLossRecovery[0]).to.equal(0); - expect(resultWithLossRecovery[1][CONSTANTS.SENIOR_TRANCHE]).to.equal( - newAssetsWithLossRecovery[CONSTANTS.SENIOR_TRANCHE], - ); - expect(resultWithLossRecovery[1][CONSTANTS.JUNIOR_TRANCHE]).to.equal( - newAssetsWithLossRecovery[CONSTANTS.JUNIOR_TRANCHE], - ); - expect(resultWithLossRecovery[1][CONSTANTS.SENIOR_TRANCHE]).to.equal( - assets[CONSTANTS.SENIOR_TRANCHE], - ); - expect(resultWithLossRecovery[2][CONSTANTS.SENIOR_TRANCHE]).to.equal( - newLossesWithLossRecovery[CONSTANTS.SENIOR_TRANCHE], - ); - expect(resultWithLossRecovery[2][CONSTANTS.JUNIOR_TRANCHE]).to.equal( - newLossesWithLossRecovery[CONSTANTS.JUNIOR_TRANCHE], - ); - expect(resultWithLossRecovery[2][CONSTANTS.SENIOR_TRANCHE]).to.equal(0); - }); - }); -});