Skip to content

Commit

Permalink
fix: comutation for DEBT_ONLY covered
Browse files Browse the repository at this point in the history
  • Loading branch information
0xmikko committed May 19, 2023
1 parent 5535e9e commit 0a8a3c8
Show file tree
Hide file tree
Showing 10 changed files with 478 additions and 99 deletions.
119 changes: 63 additions & 56 deletions contracts/credit/CreditManagerV3.sol
Original file line number Diff line number Diff line change
Expand Up @@ -656,34 +656,38 @@ contract CreditManagerV3 is ICreditManagerV3, SanityCheckTrait, ReentrancyGuardT
if (task == CollateralCalcTask.GENERIC_PARAMS) return collateralDebtData; // U:[CM-20]
///
/// COMPUTE DEBT PARAMS
collateralDebtData.enabledTokensMask = enabledTokensMask;
collateralDebtData.enabledTokensMask = enabledTokensMask; // U:[CM-21]

if (supportsQuotas) {
collateralDebtData.quotedTokenMask = quotedTokenMask & collateralDebtData.enabledTokensMask;
collateralDebtData._poolQuotaKeeper = poolQuotaKeeper();
collateralDebtData._poolQuotaKeeper = poolQuotaKeeper(); // U:[CM-21]

(
collateralDebtData.quotedTokens,
collateralDebtData.cumulativeQuotaInterest,
collateralDebtData.quotas,
collateralDebtData.quotedLts,
collateralDebtData.quotedTokenMask
) = _getQuotaTokenData({
collateralDebtData.enabledQuotedTokenMask
) = _getQuotedTokensData({
creditAccount: creditAccount,
enabledTokensMask: enabledTokensMask,
_poolQuotaKeeper: collateralDebtData._poolQuotaKeeper
});
}); // U:[CM-21]

collateralDebtData.cumulativeQuotaInterest += creditAccountInfo[creditAccount].cumulativeQuotaInterest - 1;
collateralDebtData.cumulativeQuotaInterest += creditAccountInfo[creditAccount].cumulativeQuotaInterest - 1; // U:[CM-21]
}

(collateralDebtData.accruedInterest, collateralDebtData.accruedFees) =
collateralDebtData.calcAccruedInterestAndFees({feeInterest: feeInterest});
collateralDebtData.accruedInterest = CreditLogic.calcAccruedInterest({
amount: collateralDebtData.debt,
cumulativeIndexLastUpdate: collateralDebtData.cumulativeIndexLastUpdate,
cumulativeIndexNow: collateralDebtData.cumulativeIndexNow
}) + collateralDebtData.cumulativeQuotaInterest; // U:[CM-21]

collateralDebtData.accruedFees = collateralDebtData.accruedInterest * feeInterest / PERCENTAGE_FACTOR; // U:[CM-21]

if (task == CollateralCalcTask.DEBT_ONLY) return collateralDebtData;
if (task == CollateralCalcTask.DEBT_ONLY) return collateralDebtData; // U:[CM-21]
///
/// COMPUTES COLLATERAL
/// If task == FULL_COLLATERAL_CHECK_LAZY, until it finds enough collateral
/// If task == FULL_COLLATERAL_CHECK_LAZY, collateral is computed until it less enough
address _priceOracle = priceOracle;

collateralDebtData.totalDebtUSD = _convertToUSD({
Expand Down Expand Up @@ -726,6 +730,53 @@ contract CreditManagerV3 is ICreditManagerV3, SanityCheckTrait, ReentrancyGuardT
}
}

function _getQuotedTokensData(address creditAccount, uint256 enabledTokensMask, address _poolQuotaKeeper)
internal
view
returns (
address[] memory quotaTokens,
uint256 outstandingQuotaInterest,
uint256[] memory quotas,
uint16[] memory lts,
uint256 quotedMask
)
{
uint256 _maxEnabledTokens = maxEnabledTokens;

quotedMask = enabledTokensMask & quotedTokenMask;

if (quotedMask != 0) {
quotaTokens = new address[](_maxEnabledTokens);
quotas = new uint256[](_maxEnabledTokens);
lts = new uint16[](_maxEnabledTokens);

uint256 j;
unchecked {
for (uint256 tokenMask = 2; tokenMask <= quotedMask; tokenMask <<= 1) {
if (j == _maxEnabledTokens) {
revert TooManyEnabledTokensException();
}

if (quotedMask & tokenMask != 0) {
address token;
(token, lts[j]) = _collateralTokensByMask({tokenMask: tokenMask, calcLT: true});

quotaTokens[j] = token;

uint256 outstandingInterestDelta;
(quotas[j], outstandingInterestDelta) =
_getQuotaAndOutstandingInterest(_poolQuotaKeeper, creditAccount, token);

// Safe because quotaInterest = (quota is uint96) * APY * time, so even with 1000% APY, it will take 10**10 years for overflow
outstandingQuotaInterest += outstandingInterestDelta;

++j;
}
}
}
}
}

//
// QUOTAS MANAGEMENT
//
Expand Down Expand Up @@ -1287,7 +1338,7 @@ contract CreditManagerV3 is ICreditManagerV3, SanityCheckTrait, ReentrancyGuardT
view
returns (uint256 quoted, uint256 outstandingInterest)
{
return IPoolQuotaKeeper(_poolQuotaKeeper).getQuotaAndInterest(creditAccount, token);
return IPoolQuotaKeeper(_poolQuotaKeeper).getQuotaAndOutstandingInterest(creditAccount, token);
}

function _convertToUSD(address _priceOracle, uint256 amountInToken, address token)
Expand All @@ -1305,48 +1356,4 @@ contract CreditManagerV3 is ICreditManagerV3, SanityCheckTrait, ReentrancyGuardT
{
amountInToken = IPriceOracleV2(_priceOracle).convertFromUSD(amountInUSD, token);
}

function _getQuotaTokenData(address creditAccount, uint256 enabledTokensMask, address _poolQuotaKeeper)
internal
view
returns (
address[] memory quotaTokens,
uint256 outstandingQuotaInterest,
uint256[] memory quotas,
uint16[] memory lts,
uint256 quotedMask
)
{
uint256 _maxEnabledTokens = maxEnabledTokens;

quotedMask = enabledTokensMask & quotedTokenMask;
quotaTokens = new address[](_maxEnabledTokens);
quotas = new uint256[](_maxEnabledTokens);
lts = new uint16[](_maxEnabledTokens);

uint256 j;
unchecked {
for (uint256 tokenMask = 2; tokenMask <= quotedMask; tokenMask <<= 1) {
if (quotedMask & tokenMask != 0) {
address token;
(token, lts[j]) = _collateralTokensByMask(tokenMask, true);

quotaTokens[j] = token;

uint256 outstandingInterestDelta;
(quotas[j], outstandingInterestDelta) =
_getQuotaAndOutstandingInterest(_poolQuotaKeeper, creditAccount, token);

// Safe because quotaInterest = (quota is uint96) * APY * time, so even with 1000% APY, it will take 10**10 years for overflow
outstandingQuotaInterest += outstandingInterestDelta;

++j;

if (j >= _maxEnabledTokens) {
revert TooManyEnabledTokensException();
}
}
}
}
}
}
2 changes: 1 addition & 1 deletion contracts/interfaces/ICreditManagerV3.sol
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ struct CollateralDebtData {
uint256 totalValueUSD;
uint256 twvUSD;
uint256 enabledTokensMask;
uint256 quotedTokenMask;
uint256 enabledQuotedTokenMask;
address[] quotedTokens;
uint16[] quotedLts;
uint256[] quotas;
Expand Down
2 changes: 1 addition & 1 deletion contracts/interfaces/IPoolQuotaKeeper.sol
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ interface IPoolQuotaKeeper is IPoolQuotaKeeperEvents, IVersion {
returns (uint96 quota, uint192 cumulativeIndexLU);

/// @dev Computes collateral value for quoted tokens on the account, as well as accrued quota interest
function getQuotaAndInterest(address creditAccount, address token)
function getQuotaAndOutstandingInterest(address creditAccount, address token)
external
view
returns (uint256 quoted, uint256 outstandingInterest);
Expand Down
26 changes: 2 additions & 24 deletions contracts/libraries/CreditLogic.sol
Original file line number Diff line number Diff line change
Expand Up @@ -269,29 +269,6 @@ library CreditLogic {
//
// COLLATERAL & DEBT COMPUTATION
//

/// @dev IMPLEMENTATION: calcAccruedInterestAndFees
// / @param creditAccount Address of the Credit Account
// / @param quotaInterest Total quota premiums accrued, computed elsewhere
// / @return debt The debt principal
// / @return accruedInterest Accrued interest
// / @return accruedFees Accrued interest and protocol fees
function calcAccruedInterestAndFees(CollateralDebtData memory collateralDebtData, uint16 feeInterest)
internal
pure
returns (uint256 accruedInterest, uint256 accruedFees)
{
// Interest is never stored and is always computed dynamically
// as the difference between the current cumulative index of the pool
// and the cumulative index recorded in the Credit Account
accruedInterest = calcAccruedInterest(
collateralDebtData.debt, collateralDebtData.cumulativeIndexLastUpdate, collateralDebtData.cumulativeIndexNow
) + collateralDebtData.cumulativeQuotaInterest; // F:[CM-49]

// Fees are computed as a percentage of interest
accruedFees = collateralDebtData.accruedInterest * feeInterest / PERCENTAGE_FACTOR; // F: [CM-49]
}

function calcCollateral(
CollateralDebtData memory collateralDebtData,
address creditAccount,
Expand Down Expand Up @@ -326,7 +303,8 @@ library CreditLogic {
}
}
{
uint256 tokensToCheckMask = collateralDebtData.enabledTokensMask.disable(collateralDebtData.quotedTokenMask);
uint256 tokensToCheckMask =
collateralDebtData.enabledTokensMask.disable(collateralDebtData.enabledQuotedTokenMask);

uint256 tvDelta;
uint256 twvDelta;
Expand Down
2 changes: 1 addition & 1 deletion contracts/pool/PoolQuotaKeeper.sol
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ contract PoolQuotaKeeper is IPoolQuotaKeeper, ACLNonReentrantTrait, ContractsReg
//
// GETTERS
//
function getQuotaAndInterest(address creditAccount, address token)
function getQuotaAndOutstandingInterest(address creditAccount, address token)
external
view
override
Expand Down
69 changes: 69 additions & 0 deletions contracts/test/lib/helper.sol
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,73 @@ contract TestHelper is Test {
function _testCaseErr(string memory caseName, string memory err) internal pure returns (string memory) {
return string.concat("\nCase: ", caseName, "\nError: ", err);
}

function arrayOf(uint256 v1) internal pure returns (uint256[] memory array) {
array = new uint256[](1);
array[0] = v1;
}

function arrayOf(uint256 v1, uint256 v2) internal pure returns (uint256[] memory array) {
array = new uint256[](2);
array[0] = v1;
array[1] = v2;
}

function arrayOf(uint256 v1, uint256 v2, uint256 v3) internal pure returns (uint256[] memory array) {
array = new uint256[](3);
array[0] = v1;
array[1] = v2;
array[2] = v3;
}

function arrayOf(uint256 v1, uint256 v2, uint256 v3, uint256 v4) internal pure returns (uint256[] memory array) {
array = new uint256[](4);
array[0] = v1;
array[1] = v2;
array[2] = v3;
array[3] = v4;
}

function arrayOfU16(uint16 v1) internal pure returns (uint16[] memory array) {
array = new uint16[](1);
array[0] = v1;
}

function arrayOfU16(uint16 v1, uint16 v2) internal pure returns (uint16[] memory array) {
array = new uint16[](2);
array[0] = v1;
array[1] = v2;
}

function arrayOfU16(uint16 v1, uint16 v2, uint16 v3) internal pure returns (uint16[] memory array) {
array = new uint16[](3);
array[0] = v1;
array[1] = v2;
array[2] = v3;
}

function arrayOfU16(uint16 v1, uint16 v2, uint16 v3, uint16 v4) internal pure returns (uint16[] memory array) {
array = new uint16[](4);
array[0] = v1;
array[1] = v2;
array[2] = v3;
array[3] = v4;
}

function _copyU16toU256(uint16[] memory a16) internal pure returns (uint256[] memory a256) {
uint256 len = a16.length;
uint256[] memory a256 = new uint256[](len);

unchecked {
for (uint256 i; i < len; ++i) {
a256[i] = a16[i];
}
}
}

function assertEq(uint16[] memory a1, uint16[] memory a2, string memory reason) internal {
assertEq(a1.length, a2.length, string.concat(reason, "Arrays has different length"));

assertEq(_copyU16toU256(a1), _copyU16toU256(a2), reason);
}
}
14 changes: 11 additions & 3 deletions contracts/test/mocks/pool/PoolQuotaKeeperMock.sol
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ contract PoolQuotaKeeperMock is IPoolQuotaKeeper {
uint256 internal return_interest;
bool internal return_isQuotedToken;

mapping(address => uint96) internal _quoted;
mapping(address => uint256) internal _outstandingInterest;

constructor(address _pool, address _underlying) {
pool = _pool;
underlying = _underlying;
Expand Down Expand Up @@ -80,15 +83,20 @@ contract PoolQuotaKeeperMock is IPoolQuotaKeeper {
/// @dev Batch updates the quota rates and changes the combined quota revenue
function updateRates() external {}

function setQuotaAndOutstandingInterest(address token, uint96 quoted, uint256 outstandingInterest) external {
_quoted[token] = quoted;
_outstandingInterest[token] = outstandingInterest;
}

/// GETTERS
function getQuotaAndInterest(address creditAccount, address token)
function getQuotaAndOutstandingInterest(address creditAccount, address token)
external
view
override
returns (uint256 quoted, uint256 interest)
{
quoted = return_quoted;
interest = return_interest;
quoted = _quoted[token];
interest = _outstandingInterest[token];
}

/// @dev Returns cumulative index in RAY for a quoted token. Returns 0 for non-quoted tokens.
Expand Down
26 changes: 26 additions & 0 deletions contracts/test/suites/TokensTestSuite.sol
Original file line number Diff line number Diff line change
Expand Up @@ -107,4 +107,30 @@ contract TokensTestSuite is Test, TokensData, TokensTestSuiteHelper {
function burn(Tokens t, address from, uint256 amount) external {
burn(addressOf[t], from, amount);
}

function listOf(Tokens t1) external returns (address[] memory tokensList) {
tokensList = new address[](1);
tokensList[0] = addressOf[t1];
}

function listOf(Tokens t1, Tokens t2) external returns (address[] memory tokensList) {
tokensList = new address[](2);
tokensList[0] = addressOf[t1];
tokensList[1] = addressOf[t2];
}

function listOf(Tokens t1, Tokens t2, Tokens t3) external returns (address[] memory tokensList) {
tokensList = new address[](3);
tokensList[0] = addressOf[t1];
tokensList[1] = addressOf[t2];
tokensList[2] = addressOf[t3];
}

function listOf(Tokens t1, Tokens t2, Tokens t3, Tokens t4) external returns (address[] memory tokensList) {
tokensList = new address[](4);
tokensList[0] = addressOf[t1];
tokensList[1] = addressOf[t2];
tokensList[2] = addressOf[t3];
tokensList[3] = addressOf[t4];
}
}
Loading

0 comments on commit 0a8a3c8

Please sign in to comment.