Skip to content

Commit

Permalink
test: unit tests for changeEnabledTokens
Browse files Browse the repository at this point in the history
  • Loading branch information
lekhovitsky committed Feb 28, 2023
1 parent 93d994c commit ec40fcb
Show file tree
Hide file tree
Showing 3 changed files with 155 additions and 30 deletions.
38 changes: 19 additions & 19 deletions contracts/credit/CreditManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,7 @@ contract CreditManager is ICreditManagerV2, ACLNonReentrantTrait {
// Initializes the enabled token mask for Credit Account to 1 (only the underlying is enabled)
enabledTokensMap[creditAccount] = 1; // F:[CM-8]

if (supportsQuotas) cumulativeQuotaInterest[creditAccount] = 1; // F: [CMQ-01]
if (supportsQuotas) cumulativeQuotaInterest[creditAccount] = 1; // F: [CMQ-1]

// Returns the address of the opened Credit Account
return creditAccount; // F:[CM-8]
Expand Down Expand Up @@ -338,7 +338,7 @@ contract CreditManager is ICreditManagerV2, ACLNonReentrantTrait {
quotaInterest = cumulativeQuotaInterest[creditAccount];

if (tokens.length > 0) {
quotaInterest += poolQuotaKeeper().closeCreditAccount(creditAccount, tokens); // F: [CMQ-06]
quotaInterest += poolQuotaKeeper().closeCreditAccount(creditAccount, tokens); // F: [CMQ-6]
}
}

Expand Down Expand Up @@ -515,23 +515,23 @@ contract CreditManager is ICreditManagerV2, ACLNonReentrantTrait {

TokenLT[] memory tokens = getLimitedTokens(creditAccount);
if (tokens.length > 0) {
quotaInterestAccrued += poolQuotaKeeper().accrueQuotaInterest(creditAccount, tokens); // F: [CMQ-04, 05]
quotaInterestAccrued += poolQuotaKeeper().accrueQuotaInterest(creditAccount, tokens); // F: [CMQ-4,5]
}

if (quotaInterestAccrued > 2) {
uint256 quotaProfit = (quotaInterestAccrued * fee) / PERCENTAGE_FACTOR;

if (amountRepaid >= quotaInterestAccrued + quotaProfit) {
amountRepaid -= quotaInterestAccrued + quotaProfit; // F: [CMQ-05]
amountProfit += quotaProfit; // F: [CMQ-05]
cumulativeQuotaInterest[creditAccount] = 1; // F: [CMQ-05]
amountRepaid -= quotaInterestAccrued + quotaProfit; // F: [CMQ-5]
amountProfit += quotaProfit; // F: [CMQ-5]
cumulativeQuotaInterest[creditAccount] = 1; // F: [CMQ-5]
} else {
uint256 amountToPool = (amountRepaid * PERCENTAGE_FACTOR) / (PERCENTAGE_FACTOR + fee);

amountProfit += amountRepaid - amountToPool; // F: [CMQ-04]
amountRepaid = 0; // F: [CMQ-04]
amountProfit += amountRepaid - amountToPool; // F: [CMQ-4]
amountRepaid = 0; // F: [CMQ-4]

cumulativeQuotaInterest[creditAccount] = quotaInterestAccrued - amountToPool + 1; // F: [CMQ-04]
cumulativeQuotaInterest[creditAccount] = quotaInterestAccrued - amountToPool + 1; // F: [CMQ-4]
}
}
}
Expand Down Expand Up @@ -917,7 +917,7 @@ contract CreditManager is ICreditManagerV2, ACLNonReentrantTrait {
external
view
override
adaptersOrCreditFacadeOnly // F: [CM-2]
adaptersOrCreditFacadeOnly // F:[CM-3]
{
uint256 enabledTokenMask = enabledTokensMap[creditAccount];
uint256 totalTokensEnabled = _calcEnabledTokens(enabledTokenMask);
Expand Down Expand Up @@ -955,7 +955,7 @@ contract CreditManager is ICreditManagerV2, ACLNonReentrantTrait {
function disableToken(address token)
external
override
whenNotPausedOrEmergency // F:[CM-5]
whenNotPausedOrEmergency
adaptersOrCreditFacadeOnly // F:[CM-3]
nonReentrant
returns (bool)
Expand All @@ -980,7 +980,7 @@ contract CreditManager is ICreditManagerV2, ACLNonReentrantTrait {
external
override
whenNotPausedOrEmergency
adaptersOrCreditFacadeOnly
adaptersOrCreditFacadeOnly // F:[CM-3]
nonReentrant
returns (bool wasEnabled, bool wasDisabled)
{
Expand All @@ -1001,24 +1001,24 @@ contract CreditManager is ICreditManagerV2, ACLNonReentrantTrait {

// remove tokens on the intersection as they will cancel each other
uint256 intersection = tokensToEnable & tokensToDisable;
tokensToEnable &= ~intersection;
tokensToDisable &= ~intersection;
tokensToEnable &= ~intersection; // F:[CM-33]
tokensToDisable &= ~intersection; // F:[CM-33]

// check that operation doesn't try to enable one of forbidden tokens
if (forbiddenTokenMask & tokensToEnable != 0) {
revert TokenNotAllowedException(); // F:[CM-30]
revert TokenNotAllowedException(); // F:[CM-30,32]
}

uint256 enabledTokens = enabledTokensMap[creditAccount];

wasEnabled = tokensToEnable & ~enabledTokens != 0;
if (wasEnabled) {
enabledTokens |= tokensToEnable; // F:[CM-31]
enabledTokens |= tokensToEnable; // F:[CM-31,34]
}

wasDisabled = tokensToDisable & enabledTokens != 0;
if (wasDisabled) {
enabledTokens &= ~tokensToDisable; // F:[CM-46]
enabledTokens &= ~tokensToDisable; // F:[CM-34,46]
}

if (wasEnabled || wasDisabled) enabledTokensMap[creditAccount] = enabledTokens;
Expand Down Expand Up @@ -1628,9 +1628,9 @@ contract CreditManager is ICreditManagerV2, ACLNonReentrantTrait {
/// Tokens in the mask also incur additional interest based on their quotas
function setLimitedMask(uint256 _limitedTokenMask)
external
creditConfiguratorOnly // F: [CMQ-02]
creditConfiguratorOnly // F: [CMQ-2]
{
limitedTokenMask = _limitedTokenMask; // F: [CMQ-02]
limitedTokenMask = _limitedTokenMask; // F: [CMQ-2]
}

/// @dev Sets the maximal number of enabled tokens on a single Credit Account.
Expand Down
123 changes: 121 additions & 2 deletions contracts/test/credit/CreditManager.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -383,7 +383,9 @@ contract CreditManagerTest is DSTest, ICreditManagerV2Events, ICreditManagerV2Ex
/// - executeOrder
/// - checkAndEnableToken
/// - fullCollateralCheck
function test_CM_03_credit_account_execution_functions_revert_if_not_called_by_creditFacade() public {
/// - disableToken
/// - changeEnabledTokens
function test_CM_03_credit_account_execution_functions_revert_if_not_called_by_creditFacade_or_adapters() public {
assertEq(creditManager.creditFacade(), address(this));

evm.startPrank(USER);
Expand All @@ -403,6 +405,12 @@ contract CreditManagerTest is DSTest, ICreditManagerV2Events, ICreditManagerV2Ex
evm.expectRevert(AdaptersOrCreditFacadeOnlyException.selector);
creditManager.checkEnabledTokensLength(DUMB_ADDRESS);

evm.expectRevert(AdaptersOrCreditFacadeOnlyException.selector);
creditManager.disableToken(DUMB_ADDRESS);

evm.expectRevert(AdaptersOrCreditFacadeOnlyException.selector);
creditManager.changeEnabledTokens(0, 0);

evm.stopPrank();
}

Expand Down Expand Up @@ -1337,7 +1345,7 @@ contract CreditManagerTest is DSTest, ICreditManagerV2Events, ICreditManagerV2Ex
evm.expectRevert(TokenNotAllowedException.selector);
creditManager.checkAndEnableToken(DUMB_ADDRESS);

// Case: token is frobidden
// Case: token is forbidden
address token = tokenTestSuite.addressOf(Tokens.USDC);
uint256 tokenMask = creditManager.tokenMasksMap(token);

Expand Down Expand Up @@ -1365,6 +1373,117 @@ contract CreditManagerTest is DSTest, ICreditManagerV2Events, ICreditManagerV2Ex
expectTokenIsEnabled(creditAccount, Tokens.USDC, true);
}

//
// CHANGE ENABLED TOKENS
//

/// @dev [CM-32]: changeEnabledTokens reverts on enabling forbidden tokens
function test_CM_32_changeEnabledTokens_reverts_on_enabling_forbidden_tokens() public {
_openAccountAndTransferToCF();

address token = tokenTestSuite.addressOf(Tokens.USDC);
uint256 tokenMask = creditManager.tokenMasksMap(token);

evm.prank(CONFIGURATOR);
creditManager.setForbidMask(tokenMask);

evm.expectRevert(TokenNotAllowedException.selector);
creditManager.changeEnabledTokens(tokenMask, 0);
}

/// @dev [CM-33]: changeEnabledTokens ignores tokens on intersection of toEnable and toDisable
function test_CM_33_changeEnabledTokens_ignores_tokens_on_intersection() public {
address creditAccount = _openAccountAndTransferToCF();

address token = tokenTestSuite.addressOf(Tokens.USDC);
uint256 tokenMask = creditManager.tokenMasksMap(token);

(bool wasEnabled, bool wasDisabled) = creditManager.changeEnabledTokens(tokenMask, tokenMask);
expectTokenIsEnabled(creditAccount, Tokens.USDC, false);
assertTrue(!wasEnabled);
assertTrue(!wasDisabled);

creditManager.checkAndEnableToken(token);
expectTokenIsEnabled(creditAccount, Tokens.USDC, true);

(wasEnabled, wasDisabled) = creditManager.changeEnabledTokens(tokenMask, tokenMask);
expectTokenIsEnabled(creditAccount, Tokens.USDC, true);
assertTrue(!wasEnabled);
assertTrue(!wasDisabled);
}

/// @dev [CM-34] changeEnabledTokens enables and disables tokens for credit account
function test_CM_34_changeEnabledTokens_enables_and_disables_tokens_for_credit_account() public {
address creditAccount = _openAccountAndTransferToCF();

address token = tokenTestSuite.addressOf(Tokens.USDC);
uint256 tokenMask = creditManager.tokenMasksMap(token);

(bool wasEnabled, bool wasDisabled) = creditManager.changeEnabledTokens(tokenMask, 0);
expectTokenIsEnabled(creditAccount, Tokens.USDC, true);
assertTrue(wasEnabled);
assertTrue(!wasDisabled);

(wasEnabled, wasDisabled) = creditManager.changeEnabledTokens(0, tokenMask);
expectTokenIsEnabled(creditAccount, Tokens.USDC, false);
assertTrue(!wasEnabled);
assertTrue(wasDisabled);
}

/// @dev [CM-35] changeEnabledTokens fuzzing test
function test_CM_35_changeEnabledTokens_fuzzing_test(
bool enableUsdc,
bool disableUsdc,
bool enableWeth,
bool disableWeth,
bool enableLink,
bool disableLink,
bool forbidLink
) public {
address creditAccount = _openAccountAndTransferToCF();

if (disableUsdc) creditManager.checkAndEnableToken(tokenTestSuite.addressOf(Tokens.USDC));
if (disableWeth) creditManager.checkAndEnableToken(tokenTestSuite.addressOf(Tokens.WETH));
if (disableLink) creditManager.checkAndEnableToken(tokenTestSuite.addressOf(Tokens.LINK));

uint256 tokensToEnable;
uint256 tokensToDisable;
bool mustRevert;
{
uint256 usdcMask = creditManager.tokenMasksMap(tokenTestSuite.addressOf(Tokens.USDC));
uint256 wethMask = creditManager.tokenMasksMap(tokenTestSuite.addressOf(Tokens.WETH));
uint256 linkMask = creditManager.tokenMasksMap(tokenTestSuite.addressOf(Tokens.LINK));

tokensToEnable = 0;
if (enableUsdc) tokensToEnable |= usdcMask;
if (enableWeth) tokensToEnable |= wethMask;
if (enableLink) tokensToEnable |= linkMask;

tokensToDisable = 0;
if (disableUsdc) tokensToDisable |= usdcMask;
if (disableWeth) tokensToDisable |= wethMask;
if (disableLink) tokensToDisable |= linkMask;

if (forbidLink) {
evm.prank(CONFIGURATOR);
creditManager.setForbidMask(linkMask);
}

mustRevert = forbidLink && enableLink && !disableLink;
}

if (mustRevert) evm.expectRevert(TokenNotAllowedException.selector);
(bool enabled, bool disabled) = creditManager.changeEnabledTokens(tokensToEnable, tokensToDisable);
if (mustRevert) return;

expectTokenIsEnabled(creditAccount, Tokens.USDC, enableUsdc);
expectTokenIsEnabled(creditAccount, Tokens.WETH, enableWeth);
expectTokenIsEnabled(creditAccount, Tokens.LINK, enableLink);

assertTrue(enabled == (enableUsdc && !disableUsdc || enableWeth && !disableWeth || enableLink && !disableLink));
assertTrue(disabled == (!enableUsdc && disableUsdc || !enableWeth && disableWeth || !enableLink && disableLink));
}

//
// FULL COLLATERAL CHECK
//
Expand Down
24 changes: 15 additions & 9 deletions contracts/test/credit/CreditManager_Quotas.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -182,12 +182,12 @@ contract CreditManagerQuotasTest is
///
///

/// @dev [CMQ-01]: constructor correctly sets supportsQuotas based on pool
/// @dev [CMQ-1]: constructor correctly sets supportsQuotas based on pool
function test_CMQ_01_constructor_correctly_sets_quota_related_params() public {
assertTrue(creditManager.supportsQuotas(), "Credit Manager does not support quotas");
}

/// @dev [CMQ-02]: setLimitedMask works correctly
/// @dev [CMQ-2]: setLimitedMask works correctly
function test_CMQ_02_setLimitedMask_works_correctly() public {
uint256 usdcMask = creditManager.tokenMasksMap(tokenTestSuite.addressOf(Tokens.USDC));
uint256 linkMask = creditManager.tokenMasksMap(tokenTestSuite.addressOf(Tokens.LINK));
Expand All @@ -203,7 +203,7 @@ contract CreditManagerQuotasTest is
assertEq(creditManager.limitedTokenMask(), usdcMask | linkMask, "New limited mask is incorrect");
}

/// @dev [CMQ-03]: updateQuotas works correctly
/// @dev [CMQ-3]: updateQuotas works correctly
function test_CMQ_03_updateQuotas_works_correctly() public {
_makeTokenLimited(tokenTestSuite.addressOf(Tokens.USDT), 500, uint96(1_000_000 * WAD));

Expand Down Expand Up @@ -248,7 +248,7 @@ contract CreditManagerQuotasTest is
creditManager.updateQuotas(creditAccount, quotaUpdates);
}

/// @dev [CMQ-04]: Quotas are handled correctly on debt decrease: amount < quota interest case
/// @dev [CMQ-4]: Quotas are handled correctly on debt decrease: amount < quota interest case
function test_CMQ_04_quotas_are_handled_correctly_at_repayment_partial_case() public {
_makeTokenLimited(tokenTestSuite.addressOf(Tokens.USDT), 500, uint96(1_000_000 * WAD));

Expand Down Expand Up @@ -289,7 +289,7 @@ contract CreditManagerQuotasTest is
assertEq(totalDebtAfter, totalDebtBefore - amountRepaid + 1, "Debt updated incorrectly");
}

/// @dev [CMQ-05]: Quotas are handled correctly on debt decrease: amount >= quota interest case
/// @dev [CMQ-5]: Quotas are handled correctly on debt decrease: amount >= quota interest case
function test_CMQ_05_quotas_are_handled_correctly_at_repayment_full_case() public {
_makeTokenLimited(tokenTestSuite.addressOf(Tokens.USDT), 500, uint96(1_000_000 * WAD));

Expand Down Expand Up @@ -331,7 +331,7 @@ contract CreditManagerQuotasTest is
assertLe(diff, 1, "Debt updated incorrectly");
}

/// @dev [CMQ-06]: Quotas are disabled on closing an account
/// @dev [CMQ-6]: Quotas are disabled on closing an account
function test_CMQ_06_quotas_are_disabled_on_close_account_and_all_quota_fees_are_repaid() public {
_makeTokenLimited(tokenTestSuite.addressOf(Tokens.USDT), 500, uint96(1_000_000 * WAD));

Expand Down Expand Up @@ -374,12 +374,15 @@ contract CreditManagerQuotasTest is
assertEq(uint256(quota.cumulativeIndexLU), 0, "Cumulative index was not updated");
}

function test_CMQ_07_enableToken_disableToken_do_nothing_for_limited_tokens() public {
/// @dev [CMQ-7] enableToken, disableToken and changeEnabledTokens do nothing for limited tokens
function test_CMQ_07_enable_disable_changeEnabled_do_nothing_for_limited_tokens() public {
(,,, address creditAccount) = _openCreditAccount();
creditManager.transferAccountOwnership(USER, address(this));

creditManager.checkAndEnableToken(tokenTestSuite.addressOf(Tokens.LINK));
expectTokenIsEnabled(creditAccount, Tokens.LINK, false);

creditManager.changeEnabledTokens(creditManager.tokenMasksMap(tokenTestSuite.addressOf(Tokens.LINK)), 0);
expectTokenIsEnabled(creditAccount, Tokens.LINK, false);

QuotaUpdate[] memory quotaUpdates = new QuotaUpdate[](1);
Expand All @@ -389,11 +392,13 @@ contract CreditManagerQuotasTest is
creditManager.updateQuotas(creditAccount, quotaUpdates);

creditManager.disableToken(tokenTestSuite.addressOf(Tokens.LINK));
expectTokenIsEnabled(creditAccount, Tokens.LINK, true);

creditManager.changeEnabledTokens(0, creditManager.tokenMasksMap(tokenTestSuite.addressOf(Tokens.LINK)));
expectTokenIsEnabled(creditAccount, Tokens.LINK, true);
}

/// @dev [CMQ-08]: fullCollateralCheck fuzzing test with quotas
/// @dev [CMQ-8]: fullCollateralCheck fuzzing test with quotas
function test_CMQ_08_fullCollateralCheck_fuzzing_test_quotas(
uint128 borrowedAmount,
uint128 daiBalance,
Expand Down Expand Up @@ -492,7 +497,7 @@ contract CreditManagerQuotasTest is
creditManager.fullCollateralCheck(creditAccount, new uint256[](0), minHealthFactor);
}

/// @dev [CMQ-09]: fullCollateralCheck does not check non-limited tokens if limited are enough to cover debt
/// @dev [CMQ-9]: fullCollateralCheck does not check non-limited tokens if limited are enough to cover debt
function test_CMQ_09_fullCollateralCheck_skips_normal_tokens_if_limited_tokens_cover_debt() public {
_makeTokenLimited(tokenTestSuite.addressOf(Tokens.USDC), 500, uint96(1_000_000 * WAD));

Expand Down Expand Up @@ -571,6 +576,7 @@ contract CreditManagerQuotasTest is
assertLe(diff, 2, "Total debt not equal");
}

/// @dev [CMQ-11] updateQuotas reverts on too many enabled tokens
function test_CMQ_11_updateQuotas_reverts_on_too_many_tokens_enabled() public {
(,,, address creditAccount) = _openCreditAccount();

Expand Down

0 comments on commit ec40fcb

Please sign in to comment.