diff --git a/contracts/adapters/AbstractAdapter.sol b/contracts/adapters/AbstractAdapter.sol index 6affa1b..c91bb38 100644 --- a/contracts/adapters/AbstractAdapter.sol +++ b/contracts/adapters/AbstractAdapter.sol @@ -58,25 +58,35 @@ abstract contract AbstractAdapter is IAdapter { return creditManager.executeOrder(targetContract, callData); // F: [AA-6,9] } - /// @dev Approves a token from the Credit Account to the target contract + /// @dev Approves the target contract to spend given token from the Credit Account /// @param token Token to be approved /// @param amount Amount to be approved function _approveToken(address token, uint256 amount) internal { creditManager.approveCreditAccount(targetContract, token, amount); // F: [AA-6,10] } - /// @dev Enable a token in the Credit Account + /// @dev Enables a token in the Credit Account /// @param token Address of the token to enable function _enableToken(address token) internal { creditManager.checkAndEnableToken(token); // F: [AA-6,11] } - /// @dev Disable a token in the Credit Account + /// @dev Disables a token in the Credit Account /// @param token Address of the token to disable function _disableToken(address token) internal { creditManager.disableToken(token); // F: [AA-6,12] } + /// @dev Changes enabled tokens in the Credit Account + /// @param tokensToEnable Bitmask of tokens that should be enabled + /// @param tokensToDisable Bitmask of tokens that should be disabled + /// @dev This function might be useful for adapters that work with limited set of tokens, whose masks can be + /// determined in the adapter constructor, thus saving gas by avoiding querying them during execution + /// and combining multiple enable/disable operations into a single one + function _changeEnabledTokens(uint256 tokensToEnable, uint256 tokensToDisable) internal { + creditManager.changeEnabledTokens(tokensToEnable, tokensToDisable); // F: [AA-6,13] + } + /// @dev Executes a swap operation on the target contract from the Credit Account /// without explicit approval to spend `tokenIn` /// @param tokenIn The token that the call is expected to spend diff --git a/contracts/credit/CreditManager.sol b/contracts/credit/CreditManager.sol index f6398e1..371b980 100644 --- a/contracts/credit/CreditManager.sol +++ b/contracts/credit/CreditManager.sol @@ -731,7 +731,7 @@ contract CreditManager is ICreditManagerV2, ACLNonReentrantTrait { } /// @dev IMPLEMENTATION: checkAndEnableToken - function _checkAndEnableToken(address creditAccount, address token) internal { + function _checkAndEnableToken(address creditAccount, address token) internal virtual { uint256 tokenMask = tokenMasksMap(token); if (tokenMask == 0) { revert TokenNotAllowedException(); // F:[CM-30] @@ -965,7 +965,7 @@ contract CreditManager is ICreditManagerV2, ACLNonReentrantTrait { } /// @dev IMPLEMENTATION: disableToken - function _disableToken(address creditAccount, address token) internal returns (bool wasChanged) { + function _disableToken(address creditAccount, address token) internal virtual returns (bool wasChanged) { uint256 tokenMask = tokenMasksMap(token); (, wasChanged) = _changeEnabledTokens(creditAccount, 0, tokenMask); } @@ -999,7 +999,7 @@ contract CreditManager is ICreditManagerV2, ACLNonReentrantTrait { tokensToEnable &= ~limitedTokens; // F:[CMQ-7] tokensToDisable &= ~limitedTokens; // F:[CMQ-7] - // remove tokens on the intersection as they will cancel each other + // remove tokens on the intersection (otherwise return variables might be incorrect) uint256 intersection = tokensToEnable & tokensToDisable; tokensToEnable &= ~intersection; // F:[CM-33] tokensToDisable &= ~intersection; // F:[CM-33] diff --git a/contracts/test/adapters/AbstractAdapter.t.sol b/contracts/test/adapters/AbstractAdapter.t.sol index 966f3ef..448b69f 100644 --- a/contracts/test/adapters/AbstractAdapter.t.sol +++ b/contracts/test/adapters/AbstractAdapter.t.sol @@ -170,6 +170,17 @@ contract AbstractAdapterTest is ) ); + evm.prank(USER); + evm.expectRevert(HasNoOpenedAccountException.selector); + creditFacade.multicall( + multicallBuilder( + MultiCall({ + target: address(adapterMock), + callData: abi.encodeCall(AdapterMock.changeEnabledTokens, (0, 0)) + }) + ) + ); + for (uint256 dt; dt < 2; ++dt) { evm.prank(USER); evm.expectRevert(HasNoOpenedAccountException.selector); @@ -329,4 +340,21 @@ contract AbstractAdapterTest is ) ); } + + /// @dev [AA-13]: _changeEnabledTokens correctly passes parameters to CreditManager + function test_AA_13_changeEnabledTokens_correctly_passes_to_credit_manager() public { + _openTestCreditAccount(); + + evm.expectCall(address(creditManager), abi.encodeCall(ICreditManagerV2.changeEnabledTokens, (1, 2))); + + evm.prank(USER); + creditFacade.multicall( + multicallBuilder( + MultiCall({ + target: address(adapterMock), + callData: abi.encodeCall(adapterMock.changeEnabledTokens, (1, 2)) + }) + ) + ); + } } diff --git a/contracts/test/mocks/adapters/AdapterMock.sol b/contracts/test/mocks/adapters/AdapterMock.sol index a0819a4..7b778bd 100644 --- a/contracts/test/mocks/adapters/AdapterMock.sol +++ b/contracts/test/mocks/adapters/AdapterMock.sol @@ -56,6 +56,10 @@ contract AdapterMock is AbstractAdapter { _disableToken(token); } + function changeEnabledTokens(uint256 tokensToEnable, uint256 tokensToDisable) external creditFacadeOnly { + _changeEnabledTokens(tokensToEnable, tokensToDisable); + } + fallback() external creditFacadeOnly { _execute(msg.data); }