Skip to content

Commit

Permalink
feat: multiple creditAccounts per user
Browse files Browse the repository at this point in the history
  • Loading branch information
0xmikko committed May 1, 2023
1 parent 2d9f1f9 commit 7c6d7b8
Show file tree
Hide file tree
Showing 24 changed files with 1,228 additions and 1,300 deletions.
64 changes: 64 additions & 0 deletions contracts/core/AccountFactory.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
// SPDX-License-Identifier: BUSL-1.1
// Gearbox Protocol. Generalized leverage for DeFi protocols
// (c) Gearbox Holdings, 2022
pragma solidity ^0.8.17;

import {ContractsRegisterTrait} from "../traits/ContractsRegisterTrait.sol";
import {Clones} from "@openzeppelin/contracts/proxy/Clones.sol";

import {CreditAccount} from "../credit/CreditAccount.sol";
import {ACLTrait} from "../traits/ACLTrait.sol";

import {IAccountFactory} from "../interfaces/IAccountFactory.sol";

// EXCEPTIONS
import "../interfaces/IExceptions.sol";

/// @title Disposable credit accounts factory
contract AccountFactoryV2 is IAccountFactory, ACLTrait, ContractsRegisterTrait {
/// @dev Address of master credit account for cloning
mapping(address => address) public masterCreditAccounts;

/// @dev Contract version
uint256 public constant version = 3_00;

error MasterCreditAccountAlreadyDeployed();

/// @param addressProvider Address of address repository
constructor(address addressProvider) ACLTrait(addressProvider) ContractsRegisterTrait(addressProvider) {}

/// @dev Provides a new credit account to a Credit Manager
/// @return creditAccount Address of credit account
function takeCreditAccount(uint256, uint256) external override returns (address creditAccount) {
address masterCreditAccount = _getMasterCreditAccountOrRevert();
// Create a new credit account if there are none in stock
creditAccount = Clones.clone(masterCreditAccount); // T:[AF-2]

// emit InitializeCreditAccount(result, msg.sender); // T:[AF-5]
}

function returnCreditAccount(address usedAccount) external override {
// Do nothing for disposable CA
}

// CONFIGURATION

function addCreditManager(address creditManager)
external
configuratorOnly
registeredCreditManagerOnly(creditManager)
{
if (masterCreditAccounts[msg.sender] != address(0)) {
revert MasterCreditAccountAlreadyDeployed();
}

masterCreditAccounts[msg.sender] = address(new CreditAccount(creditManager));
}

function _getMasterCreditAccountOrRevert() internal view returns (address masterCA) {
masterCA = masterCreditAccounts[msg.sender];
if (masterCA == address(0)) {
revert CallerNotCreditManagerException();
}
}
}
60 changes: 60 additions & 0 deletions contracts/credit/CreditAccount.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
// SPDX-License-Identifier: BUSL-1.1
// Gearbox Protocol. Generalized leverage for DeFi protocols
// (c) Gearbox Holdings, 2022
pragma solidity ^0.8.10;

import {Address} from "@openzeppelin/contracts/utils/Address.sol";

import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";

import "../interfaces/IExceptions.sol";

/// @title Credit Account
/// @notice Implements generic credit account logic:
/// - Holds collateral assets
/// - Stores general parameters: borrowed amount, cumulative index at open and block when it was initialized
/// - Transfers assets
/// - Executes financial orders by calling connected protocols on its behalf
///
/// More: https://dev.gearbox.fi/developers/credit/credit_account
contract CreditAccount {
using SafeERC20 for IERC20;
using Address for address;
/// @dev Address of the currently connected Credit Manager

address public immutable creditManager;

// Contract version
uint256 public constant version = 3_00;

/// @dev Restricts operations to the connected Credit Manager only
modifier creditManagerOnly() {
if (msg.sender != creditManager) {
revert CallerNotCreditManagerException();
}
_;
}

constructor(address _creditManager) {
creditManager = _creditManager;
}

/// @dev Transfers tokens from the credit account to a provided address. Restricted to the current Credit Manager only.
/// @param token Token to be transferred from the Credit Account.
/// @param to Address of the recipient.
/// @param amount Amount to be transferred.
function safeTransfer(address token, address to, uint256 amount)
external
creditManagerOnly // T:[CA-2]
{
IERC20(token).safeTransfer(to, amount); // T:[CA-6]
}

/// @dev Executes a call to a 3rd party contract with provided data. Restricted to the current Credit Manager only.
/// @param destination Contract address to be called.
/// @param data Data to call the contract with.
function execute(address destination, bytes memory data) external creditManagerOnly returns (bytes memory) {
return destination.functionCall(data); // T: [CM-48]
}
}
102 changes: 41 additions & 61 deletions contracts/credit/CreditConfigurator.sol
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import {
DEFAULT_FEE_LIQUIDATION_EXPIRED,
DEFAULT_LIQUIDATION_PREMIUM_EXPIRED,
DEFAULT_LIMIT_PER_BLOCK_MULTIPLIER,
UNIVERSAL_CONTRACT,
WAD
} from "@gearbox-protocol/core-v2/contracts/libraries/Constants.sol";

Expand All @@ -27,7 +26,12 @@ import {CreditFacadeV3} from "./CreditFacadeV3.sol";
import {CreditManagerV3} from "./CreditManagerV3.sol";

// INTERFACES
import {ICreditConfigurator, CollateralToken, CreditManagerOpts} from "../interfaces/ICreditConfigurator.sol";
import {
ICreditConfigurator,
CollateralToken,
CreditManagerOpts,
AllowanceAction
} from "../interfaces/ICreditConfigurator.sol";
import {IPriceOracleV2} from "@gearbox-protocol/core-v2/contracts/interfaces/IPriceOracle.sol";
import {IPoolService} from "@gearbox-protocol/core-v2/contracts/interfaces/IPoolService.sol";
import {IAddressProvider} from "@gearbox-protocol/core-v2/contracts/interfaces/IAddressProvider.sol";
Expand Down Expand Up @@ -131,7 +135,7 @@ contract CreditConfigurator is ICreditConfigurator, ACLNonReentrantTrait {
emit SetCreditFacade(address(_creditFacade)); // F: [CC-1A]
emit SetPriceOracle(address(creditManager.priceOracle())); // F: [CC-1A]

_setLimitPerBlock(uint128(DEFAULT_LIMIT_PER_BLOCK_MULTIPLIER * opts.maxBorrowedAmount)); // F:[CC-1]
_setMaxDebtPerBlockMultiplier(uint8(DEFAULT_LIMIT_PER_BLOCK_MULTIPLIER)); // F:[CC-1]

_setLimits(opts.minBorrowedAmount, opts.maxBorrowedAmount); // F:[CC-1]
}
Expand Down Expand Up @@ -254,8 +258,7 @@ contract CreditConfigurator is ICreditConfigurator, ACLNonReentrantTrait {
// otherwise no actions done.
// Skipping case: F:[CC-8]
if (forbiddenTokenMask & tokenMask != 0) {
forbiddenTokenMask ^= tokenMask; // F:[CC-9]
creditFacade().allowToken(token); // TODO: CHECK
creditFacade().setTokenAllowance(token, AllowanceAction.ALLOW); // TODO: CHECK
emit AllowToken(token); // F:[CC-9]
}
}
Expand All @@ -267,7 +270,7 @@ contract CreditConfigurator is ICreditConfigurator, ACLNonReentrantTrait {
/// @param token Address of collateral token to forbid
function forbidToken(address token)
external
controllerOnly // F:[CC-2B]
pausableAdminsOnly // F:[CC-2B]
{
// Gets token masks. Reverts if the token was not added as collateral or is the underlying
uint256 tokenMask = _getAndCheckTokenMaskForSettingLT(token); // F:[CC-7]
Expand All @@ -280,15 +283,15 @@ contract CreditConfigurator is ICreditConfigurator, ACLNonReentrantTrait {
// Skipping case: F:[CC-10]
if (forbiddenTokenMask & tokenMask == 0) {
forbiddenTokenMask |= tokenMask; // F:[CC-11]
creditFacade().forbidToken(token); // TODO: CHECK
creditFacade().setTokenAllowance(token, AllowanceAction.FORBID); // TODO: CHECK
emit ForbidToken(token); // F:[CC-11]
}
}

/// @dev Marks the token as limited, which enables quota logic and additional interest for it
/// @param token Token to make limited
/// @notice This action is irreversible!
function makeTokenLimited(address token) external configuratorOnly {
function makeTokenQuoted(address token) external configuratorOnly {
// Verifies whether the quota keeper has a token registered as quotable
IPoolQuotaKeeper quotaKeeper = creditManager.poolQuotaKeeper();

Expand All @@ -307,7 +310,7 @@ contract CreditConfigurator is ICreditConfigurator, ACLNonReentrantTrait {
if (quotedTokenMask & tokenMask == 0) {
quotedTokenMask |= tokenMask;
creditManager.setQuotedMask(quotedTokenMask);
emit LimitToken(token);
emit QuoteToken(token);
}
}

Expand Down Expand Up @@ -345,7 +348,7 @@ contract CreditConfigurator is ICreditConfigurator, ACLNonReentrantTrait {
function _allowContract(address targetContract, address adapter) internal nonZeroAddress(targetContract) {
// Checks that targetContract or adapter != address(0)

if (!targetContract.isContract() && (targetContract != UNIVERSAL_CONTRACT)) {
if (!targetContract.isContract()) {
revert AddressIsNotContractException(targetContract);
} // F:[CC-12A]

Expand All @@ -369,11 +372,11 @@ contract CreditConfigurator is ICreditConfigurator, ACLNonReentrantTrait {
// If there is an existing adapter for the target contract, it has to be removed
address currentAdapter = creditManager.contractToAdapter(targetContract);
if (currentAdapter != address(0)) {
creditManager.changeContractAllowance(currentAdapter, address(0)); // F: [CC-15A]
creditManager.setContractAllowance(currentAdapter, address(0)); // F: [CC-15A]
}

// Sets a link between adapter and targetContract in creditFacade and creditManager
creditManager.changeContractAllowance(adapter, targetContract); // F:[CC-15]
creditManager.setContractAllowance(adapter, targetContract); // F:[CC-15]

// adds contract to the list of allowed contracts
allowedContractsSet.add(targetContract); // F:[CC-15]
Expand All @@ -400,8 +403,8 @@ contract CreditConfigurator is ICreditConfigurator, ACLNonReentrantTrait {
// Sets both contractToAdapter[targetContract] and adapterToContract[adapter]
// To address(0), which would make Credit Manager revert on attempts to
// call the respective targetContract using the adapter
creditManager.changeContractAllowance(adapter, address(0)); // F:[CC-17]
creditManager.changeContractAllowance(address(0), targetContract); // F:[CC-17]
creditManager.setContractAllowance(adapter, address(0)); // F:[CC-17]
creditManager.setContractAllowance(address(0), targetContract); // F:[CC-17]

// removes contract from the list of allowed contracts
allowedContractsSet.remove(targetContract); // F:[CC-17]
Expand All @@ -426,7 +429,7 @@ contract CreditConfigurator is ICreditConfigurator, ACLNonReentrantTrait {
}

/// Removes the adapter => target contract link only
creditManager.changeContractAllowance(adapter, address(0)); // F: [CC-40]
creditManager.setContractAllowance(adapter, address(0)); // F: [CC-40]

emit ForbidAdapter(adapter); // F: [CC-40]
}
Expand All @@ -450,13 +453,13 @@ contract CreditConfigurator is ICreditConfigurator, ACLNonReentrantTrait {
// Performs sanity checks on limits:
// maxBorrowedAmount must not be less than minBorrowedAmount
// maxBorrowedAmount must not be larger than maximal borrowed amount per block
(uint128 blockLimit,,) = creditFacade().params();
if (_minBorrowedAmount > _maxBorrowedAmount || _maxBorrowedAmount > blockLimit) {
uint8 maxDebtPerBlockMultiplier = creditFacade().maxDebtPerBlockMultiplier();
if (_minBorrowedAmount > _maxBorrowedAmount) {
revert IncorrectLimitsException();
} // F:[CC-18]

// Sets limits in Credit Facade
creditFacade().setCreditAccountLimits(_minBorrowedAmount, _maxBorrowedAmount); // F:[CC-19]
creditFacade().setDebtLimits(_minBorrowedAmount, _maxBorrowedAmount, maxDebtPerBlockMultiplier); // F:[CC-19]
emit SetBorrowingLimits(_minBorrowedAmount, _maxBorrowedAmount); // F:[CC-1A,19]
}

Expand Down Expand Up @@ -598,9 +601,12 @@ contract CreditConfigurator is ICreditConfigurator, ACLNonReentrantTrait {

// Retrieves all parameters in case they need
// to be migrated
(uint128 limitPerBlock, bool isIncreaseDebtFobidden, uint40 expirationDate) = creditFacade().params();

(uint128 minBorrowedAmount, uint128 maxBorrowedAmount) = creditFacade().limits();
uint40 expirationDate = creditFacade().expirationDate();

uint8 _maxDebtPerBlockMultiplier = creditFacade().maxDebtPerBlockMultiplier();

(uint128 minBorrowedAmount, uint128 maxBorrowedAmount) = creditFacade().debtLimits();

bool expirable = creditFacade().expirable();

Expand All @@ -611,9 +617,8 @@ contract CreditConfigurator is ICreditConfigurator, ACLNonReentrantTrait {

if (migrateParams) {
// Copies all limits and restrictions on borrowing
_setLimitPerBlock(limitPerBlock); // F:[CC-30]
_setMaxDebtPerBlockMultiplier(_maxDebtPerBlockMultiplier); // F:[CC-30]
_setLimits(minBorrowedAmount, maxBorrowedAmount); // F:[CC-30]
_setIncreaseDebtForbidden(isIncreaseDebtFobidden); // F:[CC-30]

// Copies the expiration date if the contract is expirable
if (expirable) _setExpirationDate(expirationDate); // F: [CC-30]
Expand Down Expand Up @@ -662,25 +667,9 @@ contract CreditConfigurator is ICreditConfigurator, ACLNonReentrantTrait {
}
}

/// @dev Enables or disables borrowing
/// In Credit Facade (and, consequently, the Credit Manager)
/// @param _mode Prohibits borrowing if true, and allows borrowing otherwise
function setIncreaseDebtForbidden(bool _mode) external {
if (!_acl.isPausableAdmin(msg.sender)) {
revert CallerNotPausableAdminException();
}
_setIncreaseDebtForbidden(_mode);
}

/// @dev IMPLEMENTATION: setIncreaseDebtForbidden
function _setIncreaseDebtForbidden(bool _mode) internal {
(, bool isIncreaseDebtForbidden,) = creditFacade().params(); // F:[CC-32]

// Checks that the mode is actually changed to avoid redundant events
if (_mode != isIncreaseDebtForbidden) {
creditFacade().setIncreaseDebtForbidden(_mode); // F:[CC-32]
emit SetIncreaseDebtForbiddenMode(_mode); // F:[CC-32]
}
/// @dev Disables borrowing in Credit Facade (and, consequently, the Credit Manager)
function forbidBorrowing() external pausableAdminsOnly {
_setMaxDebtPerBlockMultiplier(0);
}

/// @dev Sets the max cumulative loss, which is a threshold of total loss that triggers a system pause
Expand All @@ -706,27 +695,23 @@ contract CreditConfigurator is ICreditConfigurator, ACLNonReentrantTrait {
}

/// @dev Sets the maximal borrowed amount per block
/// @param newLimit The new max borrowed amount per block
function setLimitPerBlock(uint128 newLimit)
/// @param newMaxDebtLimitPerBlockMultiplier The new max borrowed amount per block
function setMaxDebtPerBlockMultiplier(uint8 newMaxDebtLimitPerBlockMultiplier)
external
controllerOnly // F:[CC-2B]
{
_setLimitPerBlock(newLimit); // F:[CC-33]
_setMaxDebtPerBlockMultiplier(newMaxDebtLimitPerBlockMultiplier); // F:[CC-33]
}

/// @dev IMPLEMENTATION: _setLimitPerBlock
function _setLimitPerBlock(uint128 newLimit) internal {
(uint128 maxBorrowedAmountPerBlock,,) = creditFacade().params();
(, uint128 maxBorrowedAmount) = creditFacade().limits();

// Performs a sanity check that the new limit is not less
// than the max borrowed amount per account
if (newLimit < maxBorrowedAmount) revert IncorrectLimitsException(); // F:[CC-33]
/// @dev IMPLEMENTATION: _setMaxDebtPerBlockMultiplier
function _setMaxDebtPerBlockMultiplier(uint8 newMaxDebtLimitPerBlockMultiplier) internal {
uint8 _maxDebtPerBlockMultiplier = creditFacade().maxDebtPerBlockMultiplier();
(uint128 minBorrowedAmount, uint128 maxBorrowedAmount) = creditFacade().debtLimits();

// Checks that the limit was actually changed to avoid redundant events
if (maxBorrowedAmountPerBlock != newLimit) {
creditFacade().setLimitPerBlock(newLimit); // F:[CC-33]
emit SetBorrowingLimitPerBlock(newLimit); // F:[CC-1A,33]
if (newMaxDebtLimitPerBlockMultiplier != _maxDebtPerBlockMultiplier) {
creditFacade().setDebtLimits(minBorrowedAmount, maxBorrowedAmount, newMaxDebtLimitPerBlockMultiplier); // F:[CC-33]
emit SetMaxDebtPerBlockMultiplier(newMaxDebtLimitPerBlockMultiplier); // F:[CC-1A,33]
}
}

Expand All @@ -743,7 +728,7 @@ contract CreditConfigurator is ICreditConfigurator, ACLNonReentrantTrait {

/// @dev IMPLEMENTATION: setExpirationDate
function _setExpirationDate(uint40 newExpirationDate) internal {
(,, uint40 expirationDate) = creditFacade().params();
uint40 expirationDate = creditFacade().expirationDate();

// Sanity checks on the new expiration date
// The new expiration date must be later than the previous one
Expand All @@ -765,11 +750,6 @@ contract CreditConfigurator is ICreditConfigurator, ACLNonReentrantTrait {
external
controllerOnly // F:[CC-2B]
{
_setMaxEnabledTokens(maxEnabledTokens); // F: [CC-37]
}

/// @dev IMPLEMENTATION: setMaxEnabledTokens
function _setMaxEnabledTokens(uint8 maxEnabledTokens) internal {
uint256 maxEnabledTokensCurrent = creditManager.maxAllowedEnabledTokenLength();

// Checks that value is actually changed, to avoid redundant checks
Expand Down
Loading

0 comments on commit 7c6d7b8

Please sign in to comment.