Skip to content

Commit 6d2994b

Browse files
committed
🆙 Update Carbon contracts in controllers (#669)
1 parent 00cfb14 commit 6d2994b

19 files changed

+986
-199
lines changed

‎contracts/carbon/ProtocolConfig.sol‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,8 @@ contract ProtocolConfig is Upgradeable, IProtocolConfig {
3232
address _protocolAdmin,
3333
address _protocolTreasury,
3434
address _pauserAddress
35-
) external initializer {
36-
__Upgradeable_init(msg.sender, msg.sender);
35+
) public initializer {
36+
__Upgradeable_init(msg.sender, _pauserAddress);
3737
defaultProtocolFeeRate = _defaultProtocolFeeRate;
3838
protocolAdmin = _protocolAdmin;
3939
protocolTreasury = _protocolTreasury;

‎contracts/carbon/StructuredPortfolio.sol‎

Lines changed: 117 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
pragma solidity ^0.8.16;
1313

14+
import {Math} from "@openzeppelin/contracts/utils/math/Math.sol";
1415
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
1516
import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol";
1617
import {Upgradeable} from "./proxy/Upgradeable.sol";
@@ -52,8 +53,8 @@ contract StructuredPortfolio is IStructuredPortfolio, LoansManager, Upgradeable
5253
IProtocolConfig _protocolConfig,
5354
PortfolioParams memory portfolioParams,
5455
TrancheInitData[] memory tranchesInitData,
55-
ExpectedEquityRate calldata _expectedEquityRate
56-
) external initializer {
56+
ExpectedEquityRate memory _expectedEquityRate
57+
) public initializer {
5758
_initialize(_fixedInterestOnlyLoans, underlyingToken);
5859
__Upgradeable_init(_protocolConfig.protocolAdmin(), _protocolConfig.pauserAddress());
5960
_grantRole(MANAGER_ROLE, manager);
@@ -101,48 +102,37 @@ contract StructuredPortfolio is IStructuredPortfolio, LoansManager, Upgradeable
101102
}
102103

103104
function updateCheckpoints() public whenNotPaused {
104-
require(status == Status.Live, "SP: Portfolio is not live");
105-
105+
require(status != Status.CapitalFormation, "SP: No checkpoints before start");
106+
(uint256[] memory _totalAssetsAfter, uint256[] memory pendingFees) = _calculateWaterfall(virtualTokenBalance + loansValue());
107+
LoansDeficitCheckpoint[] memory deficits = _calculateLoansDeficit(_totalAssetsAfter, pendingFees);
108+
for (uint256 i = 0; i < _totalAssetsAfter.length; i++) {
109+
tranches[i].updateCheckpointFromPortfolio(_totalAssetsAfter[i], deficits[i].deficit);
110+
}
106111
if (someLoansDefaulted) {
107-
_updateCheckpointsAndLoansDeficit();
108-
} else {
109-
_updateCheckpoints();
112+
for (uint256 i = 1; i < deficits.length; i++) {
113+
tranchesData[i].loansDeficitCheckpoint = deficits[i];
114+
}
110115
}
111116
}
112117

113-
function _updateCheckpointsAndLoansDeficit() internal {
114-
uint256[] memory _totalAssetsBefore = new uint256[](tranches.length);
115-
for (uint256 i = 0; i < tranches.length; i++) {
116-
_totalAssetsBefore[i] = tranches[i].getCheckpoint().totalAssets;
118+
function _calculateLoansDeficit(uint256[] memory realTotalAssets, uint256[] memory pendingFees)
119+
internal
120+
view
121+
returns (LoansDeficitCheckpoint[] memory)
122+
{
123+
LoansDeficitCheckpoint[] memory deficits = new LoansDeficitCheckpoint[](realTotalAssets.length);
124+
if (!someLoansDefaulted) {
125+
return deficits;
117126
}
118-
119-
uint256[] memory _totalAssetsAfter = _updateCheckpoints();
120-
121127
uint256 timestamp = _limitedBlockTimestamp();
122-
for (uint256 i = 1; i < _totalAssetsAfter.length; i++) {
123-
uint256 currentDeficit = _defaultedLoansDeficit(tranchesData[i], timestamp);
124-
125-
int256 deficitDelta = SafeCast.toInt256(_totalAssetsBefore[i]) - SafeCast.toInt256(_totalAssetsAfter[i]);
126-
uint256 newDeficit = _getNewLoansDeficit(currentDeficit, deficitDelta);
127-
128-
tranchesData[i].loansDeficitCheckpoint = LoansDeficitCheckpoint({deficit: newDeficit, timestamp: timestamp});
129-
}
130-
}
131-
132-
function _updateCheckpoints() internal returns (uint256[] memory) {
133-
uint256[] memory _totalAssetsAfter = calculateWaterfall();
134-
for (uint256 i = 0; i < _totalAssetsAfter.length; i++) {
135-
tranches[i].updateCheckpointFromPortfolio(_totalAssetsAfter[i]);
136-
}
137-
return _totalAssetsAfter;
138-
}
139-
140-
function _getNewLoansDeficit(uint256 currentDeficit, int256 delta) internal pure returns (uint256) {
141-
require(delta > type(int256).min, "SP: Delta out of range");
142-
if (delta < 0 && currentDeficit < uint256(-delta)) {
143-
return 0;
128+
for (uint256 i = 1; i < realTotalAssets.length; i++) {
129+
Checkpoint memory checkpoint = tranches[i].getCheckpoint();
130+
uint256 assumedTotalAssets = _assumedTrancheValue(i, timestamp);
131+
uint256 assumedTotalAssetsAfterFees = _saturatingSub(assumedTotalAssets, Math.max(pendingFees[i], checkpoint.unpaidFees));
132+
uint256 newDeficit = _saturatingSub(assumedTotalAssetsAfterFees, realTotalAssets[i]);
133+
deficits[i] = LoansDeficitCheckpoint({deficit: newDeficit, timestamp: timestamp});
144134
}
145-
return _addSigned(currentDeficit, delta);
135+
return deficits;
146136
}
147137

148138
function increaseVirtualTokenBalance(uint256 increment) external {
@@ -157,16 +147,18 @@ contract StructuredPortfolio is IStructuredPortfolio, LoansManager, Upgradeable
157147
uint256 tranchesCount = tranches.length;
158148
for (uint256 i = 0; i < tranchesCount; i++) {
159149
if (msg.sender == address(tranches[i])) {
160-
virtualTokenBalance = _addSigned(virtualTokenBalance, delta);
150+
virtualTokenBalance = delta < 0 ? virtualTokenBalance - uint256(-delta) : virtualTokenBalance + uint256(delta);
161151
return;
162152
}
163153
}
164154
revert("SP: Not a tranche");
165155
}
166156

167-
function totalAssets() public view returns (uint256) {
157+
function totalAssets() external view returns (uint256) {
168158
if (status == Status.Live) {
169-
return liquidAssets() + loansValue();
159+
uint256 _totalPendingFees = totalPendingFees();
160+
uint256 totalAssetsBeforeFees = virtualTokenBalance + loansValue();
161+
return _saturatingSub(totalAssetsBeforeFees, _totalPendingFees);
170162
}
171163
return _sum(_tranchesTotalAssets());
172164
}
@@ -244,6 +236,53 @@ contract StructuredPortfolio is IStructuredPortfolio, LoansManager, Upgradeable
244236
_checkTranchesRatios(_tranchesTotalAssets());
245237
}
246238

239+
function maxTrancheValueComplyingWithRatio(uint256 trancheIdx) external view returns (uint256) {
240+
if (status != Status.Live || trancheIdx == 0) {
241+
return type(uint256).max;
242+
}
243+
244+
uint256[] memory waterfallValues = calculateWaterfall();
245+
246+
uint256 subordinateValue = 0;
247+
for (uint256 i = 0; i < trancheIdx; i++) {
248+
subordinateValue += waterfallValues[i];
249+
}
250+
251+
uint256 minSubordinateRatio = tranchesData[trancheIdx].minSubordinateRatio;
252+
if (minSubordinateRatio == 0) {
253+
return type(uint256).max;
254+
}
255+
256+
return (subordinateValue * BASIS_PRECISION) / minSubordinateRatio;
257+
}
258+
259+
function minTrancheValueComplyingWithRatio(uint256 trancheIdx) external view returns (uint256) {
260+
if (status != Status.Live) {
261+
return 0;
262+
}
263+
264+
uint256[] memory trancheValues = calculateWaterfall();
265+
uint256 tranchesCount = trancheValues.length;
266+
if (trancheIdx == tranchesCount - 1) {
267+
return 0;
268+
}
269+
270+
uint256 subordinateValueWithoutTranche = 0;
271+
uint256 maxThreshold = 0;
272+
for (uint256 i = 0; i < tranchesCount - 1; i++) {
273+
uint256 trancheValue = trancheValues[i];
274+
if (i != trancheIdx) {
275+
subordinateValueWithoutTranche += trancheValue;
276+
}
277+
if (i >= trancheIdx) {
278+
uint256 lowerBound = (trancheValues[i + 1] * tranchesData[i + 1].minSubordinateRatio) / BASIS_PRECISION;
279+
uint256 minTrancheValue = _saturatingSub(lowerBound, subordinateValueWithoutTranche);
280+
maxThreshold = Math.max(minTrancheValue, maxThreshold);
281+
}
282+
}
283+
return maxThreshold;
284+
}
285+
247286
function _tranchesTotalAssets() internal view returns (uint256[] memory) {
248287
if (status == Status.Live) {
249288
return calculateWaterfall();
@@ -290,36 +329,24 @@ contract StructuredPortfolio is IStructuredPortfolio, LoansManager, Upgradeable
290329
}
291330

292331
_changePortfolioStatus(Status.Closed);
332+
updateCheckpoints();
293333

294334
if (!isAfterEndDate) {
295335
endDate = block.timestamp;
296336
}
297337
}
298338

299339
function _closeTranches() internal {
340+
updateCheckpoints();
300341
uint256 limitedBlockTimestamp = _limitedBlockTimestamp();
342+
(uint256[] memory waterfall, ) = _calculateWaterfall(virtualTokenBalance);
301343

302-
uint256[] memory waterfallTranchesAssets = calculateWaterfall();
303-
uint256[] memory distributedAssets = new uint256[](waterfallTranchesAssets.length);
304-
uint256 assetsLeft = virtualTokenBalance;
305-
306-
for (uint256 i = waterfallTranchesAssets.length - 1; i > 0; i--) {
307-
tranchesData[i].maxValueOnClose = _assumedTrancheValue(i, limitedBlockTimestamp);
308-
309-
uint256 trancheDistributedAssets = _min(waterfallTranchesAssets[i], assetsLeft);
310-
distributedAssets[i] = trancheDistributedAssets;
311-
tranches[i].updateCheckpointFromPortfolio(trancheDistributedAssets);
312-
313-
assetsLeft -= trancheDistributedAssets;
314-
}
315-
316-
distributedAssets[0] = _min(waterfallTranchesAssets[0], assetsLeft);
317-
tranches[0].updateCheckpointFromPortfolio(distributedAssets[0]);
318-
319-
for (uint256 i = 0; i < distributedAssets.length; i++) {
320-
uint256 trancheDistributedAssets = distributedAssets[i];
321-
tranchesData[i].distributedAssets = trancheDistributedAssets;
322-
_transfer(tranches[i], trancheDistributedAssets);
344+
for (uint256 i = 0; i < waterfall.length; i++) {
345+
if (i != 0) {
346+
tranchesData[i].maxValueOnClose = _assumedTrancheValue(i, limitedBlockTimestamp);
347+
}
348+
tranchesData[i].distributedAssets = waterfall[i];
349+
_transfer(tranches[i], waterfall[i]);
323350
}
324351
}
325352

@@ -340,21 +367,34 @@ contract StructuredPortfolio is IStructuredPortfolio, LoansManager, Upgradeable
340367
}
341368

342369
function calculateWaterfall() public view returns (uint256[] memory) {
343-
uint256[] memory waterfall = calculateWaterfallWithoutFees();
370+
(uint256[] memory waterfall, ) = _calculateWaterfall(virtualTokenBalance + loansValue());
371+
return waterfall;
372+
}
373+
374+
function _calculateWaterfall(uint256 assetsLeft) internal view returns (uint256[] memory, uint256[] memory) {
375+
uint256[] memory waterfall = _calculateWaterfallWithoutFees(assetsLeft);
376+
uint256[] memory fees = new uint256[](tranches.length);
344377
for (uint256 i = 0; i < waterfall.length; i++) {
345378
uint256 pendingFees = tranches[i].totalPendingFeesForAssets(waterfall[i]);
346379
waterfall[i] = _saturatingSub(waterfall[i], pendingFees);
380+
fees[i] = pendingFees;
347381
}
348-
return waterfall;
382+
return (waterfall, fees);
349383
}
350384

351385
function calculateWaterfallWithoutFees() public view returns (uint256[] memory) {
386+
return _calculateWaterfallWithoutFees(virtualTokenBalance + loansValue());
387+
}
388+
389+
function _calculateWaterfallWithoutFees(uint256 assetsLeft) internal view returns (uint256[] memory) {
352390
uint256[] memory waterfall = new uint256[](tranches.length);
353391
if (status != Status.Live) {
392+
for (uint256 i = 0; i < waterfall.length; i++) {
393+
waterfall[i] = tranches[i].totalAssetsBeforeFees();
394+
}
354395
return waterfall;
355396
}
356397

357-
uint256 assetsLeft = virtualTokenBalance + loansValue();
358398
uint256 limitedBlockTimestamp = _limitedBlockTimestamp();
359399

360400
for (uint256 i = waterfall.length - 1; i > 0; i--) {
@@ -377,28 +417,20 @@ contract StructuredPortfolio is IStructuredPortfolio, LoansManager, Upgradeable
377417
function _assumedTrancheValue(uint256 trancheIdx, uint256 timestamp) internal view returns (uint256) {
378418
Checkpoint memory checkpoint = tranches[trancheIdx].getCheckpoint();
379419
TrancheData memory trancheData = tranchesData[trancheIdx];
420+
uint256 targetApy = trancheData.targetApy;
380421

381-
uint256 assumedTotalAssets = _assumedTotalAssets(checkpoint, trancheData, timestamp);
382-
uint256 defaultedLoansDeficit = _defaultedLoansDeficit(trancheData, timestamp);
422+
uint256 timePassedSinceCheckpoint = _saturatingSub(timestamp, checkpoint.timestamp);
423+
uint256 assumedTotalAssets = _withInterest(checkpoint.totalAssets, targetApy, timePassedSinceCheckpoint) +
424+
checkpoint.unpaidFees;
383425

384-
return assumedTotalAssets + defaultedLoansDeficit;
385-
}
386-
387-
function _assumedTotalAssets(
388-
Checkpoint memory checkpoint,
389-
TrancheData memory trancheData,
390-
uint256 timestamp
391-
) internal pure returns (uint256) {
392-
uint256 timePassed = _saturatingSub(timestamp, checkpoint.timestamp);
393-
return _withInterest(checkpoint.totalAssets, trancheData.targetApy, timePassed);
394-
}
395-
396-
function _defaultedLoansDeficit(TrancheData memory trancheData, uint256 timestamp) internal pure returns (uint256) {
397-
if (trancheData.loansDeficitCheckpoint.deficit == 0) {
398-
return 0;
426+
uint256 defaultedLoansDeficit;
427+
uint256 checkpointDeficit = trancheData.loansDeficitCheckpoint.deficit;
428+
if (checkpointDeficit != 0) {
429+
uint256 timePassed = _saturatingSub(timestamp, trancheData.loansDeficitCheckpoint.timestamp);
430+
defaultedLoansDeficit = _withInterest(checkpointDeficit, targetApy, timePassed);
399431
}
400-
uint256 timePassed = _saturatingSub(timestamp, trancheData.loansDeficitCheckpoint.timestamp);
401-
return _withInterest(trancheData.loansDeficitCheckpoint.deficit, trancheData.targetApy, timePassed);
432+
433+
return assumedTotalAssets + defaultedLoansDeficit;
402434
}
403435

404436
function _withInterest(
@@ -456,7 +488,7 @@ contract StructuredPortfolio is IStructuredPortfolio, LoansManager, Upgradeable
456488
continue;
457489
}
458490

459-
uint256 trancheShare = _min(trancheFreeCapacity, undistributedAssets);
491+
uint256 trancheShare = Math.min(trancheFreeCapacity, undistributedAssets);
460492
undistributedAssets -= trancheShare;
461493
_repayInClosed(i, trancheShare);
462494
}
@@ -504,18 +536,10 @@ contract StructuredPortfolio is IStructuredPortfolio, LoansManager, Upgradeable
504536
emit PortfolioStatusChanged(newStatus);
505537
}
506538

507-
function _min(uint256 x, uint256 y) internal pure returns (uint256) {
508-
return x > y ? y : x;
509-
}
510-
511539
function _saturatingSub(uint256 x, uint256 y) internal pure returns (uint256) {
512540
return x > y ? x - y : 0;
513541
}
514542

515-
function _addSigned(uint256 x, int256 y) internal pure returns (uint256) {
516-
return y < 0 ? x - uint256(-y) : x + uint256(y);
517-
}
518-
519543
function _sum(uint256[] memory components) internal pure returns (uint256) {
520544
uint256 sum;
521545
for (uint256 i = 0; i < components.length; i++) {
@@ -525,7 +549,7 @@ contract StructuredPortfolio is IStructuredPortfolio, LoansManager, Upgradeable
525549
}
526550

527551
function _limitedBlockTimestamp() internal view returns (uint256) {
528-
return _min(block.timestamp, endDate);
552+
return Math.min(block.timestamp, endDate);
529553
}
530554

531555
function _requireManagerRole() internal view {

0 commit comments

Comments
 (0)