Skip to content

Commit

Permalink
Merge pull request #36 from Gearbox-protocol/multicall-optimisation
Browse files Browse the repository at this point in the history
Multicall optimisation
  • Loading branch information
0xmikko authored May 21, 2023
2 parents 28fabb2 + c9d84b2 commit 85a581b
Show file tree
Hide file tree
Showing 133 changed files with 11,935 additions and 14,821 deletions.
89 changes: 45 additions & 44 deletions contracts/adapters/AbstractAdapter.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,75 +3,77 @@
// (c) Gearbox Holdings, 2023
pragma solidity ^0.8.17;

import {IAddressProvider} from "@gearbox-protocol/core-v2/contracts/interfaces/IAddressProvider.sol";

import {IAdapter} from "../interfaces/IAdapter.sol";
import {ICreditManagerV3} from "../interfaces/ICreditManagerV3.sol";
import {CallerNotCreditFacadeException} from "../interfaces/IExceptions.sol";
import {IPool4626} from "../interfaces/IPool4626.sol";
import {ACLNonReentrantTrait} from "../traits/ACLNonReentrantTrait.sol";
import {ACLTrait} from "../traits/ACLTrait.sol";

/// @title Abstract adapter
/// @dev Inheriting adapters MUST use provided internal functions to perform all operations with credit accounts
abstract contract AbstractAdapter is IAdapter, ACLNonReentrantTrait {
/// @notice Credit manager the adapter is connected to
ICreditManagerV3 public immutable override creditManager;
abstract contract AbstractAdapter is IAdapter, ACLTrait {
/// @inheritdoc IAdapter
address public immutable override creditManager;

/// @notice Address provider
IAddressProvider public immutable override addressProvider;
/// @inheritdoc IAdapter
address public immutable override addressProvider;

/// @notice Address of the adapted contract
/// @inheritdoc IAdapter
address public immutable override targetContract;

/// @notice Constructor
/// @param _creditManager Credit manager to connect this adapter to
/// @param _creditManager Credit manager to connect the adapter to
/// @param _targetContract Address of the adapted contract
constructor(address _creditManager, address _targetContract)
ACLNonReentrantTrait(address(IPool4626(ICreditManagerV3(_creditManager).pool()).addressProvider())) // F: [AA-1]
nonZeroAddress(_targetContract) // F: [AA-1]
ACLTrait(ICreditManagerV3(_creditManager).addressProvider()) // U:[AA-1A]
nonZeroAddress(_targetContract) // U:[AA-1A]
{
creditManager = ICreditManagerV3(_creditManager); // F: [AA-2]
addressProvider = IAddressProvider(IPool4626(creditManager.pool()).addressProvider()); // F: [AA-2]
targetContract = _targetContract; // F: [AA-2]
creditManager = _creditManager; // U:[AA-1B]
addressProvider = ICreditManagerV3(_creditManager).addressProvider(); // U:[AA-1B]
targetContract = _targetContract; // U:[AA-1B]
}

/// @dev Ensures that function is called by the credit facade
/// @dev Inheriting adapters MUST use this modifier in all external functions that operate
/// on credit accounts to ensure they are called as part of the multicall
/// @dev Ensures that caller of the function is credit facade connected to the credit manager
/// @dev Inheriting adapters MUST use this modifier in all external functions that operate on credit accounts
modifier creditFacadeOnly() {
if (msg.sender != creditManager.creditFacade()) {
revert CallerNotCreditFacadeException(); // F: [AA-3]
}
_revertIfCallerNotCreditFacade();
_;
}

/// @dev Returns the credit account that will execute an external call to the target contract
/// @dev Inheriting adapters MUST use this function to find the address of the account they operate on
/// @dev Ensures that caller is credit facade connected to the credit manager
function _revertIfCallerNotCreditFacade() internal view {
if (msg.sender != ICreditManagerV3(creditManager).creditFacade()) {
revert CallerNotCreditFacadeException(); // U:[AA-2]
}
}

/// @dev Ensures that external call credit account is set and returns its address
function _creditAccount() internal view returns (address) {
return creditManager.externalCallCreditAccountOrRevert(); // F: [AA-4]
return ICreditManagerV3(creditManager).getActiveCreditAccountOrRevert(); // U:[AA-3]
}

/// @dev Checks that token is registered as collateral in the credit manager and returns its mask
/// @dev Ensures that token is registered as collateral in the credit manager and returns its mask
function _getMaskOrRevert(address token) internal view returns (uint256 tokenMask) {
tokenMask = creditManager.getTokenMaskOrRevert(token); // F: [AA-5]
tokenMask = ICreditManagerV3(creditManager).getTokenMaskOrRevert(token); // U:[AA-4]
}

/// @dev Approves target contract to spend given token from the credit account
/// Reverts if external call credit account is not set or token is not registered as collateral
/// @param token Token to approve
/// @param amount Amount to approve
/// @dev Reverts if token is not registered as collateral in the credit manager
function _approveToken(address token, uint256 amount) internal {
creditManager.approveCreditAccount(token, amount); // F: [AA-6]
ICreditManagerV3(creditManager).approveCreditAccount(token, amount); // U:[AA-5]
}

/// @dev Executes an external call from the credit account to the target contract
/// Reverts if external call credit account is not set
/// @param callData Data to call the target contract with
/// @return result Call result
function _execute(bytes memory callData) internal returns (bytes memory result) {
return creditManager.executeOrder(callData); // F: [AA-7]
return ICreditManagerV3(creditManager).executeOrder(callData); // U:[AA-6]
}

/// @dev Executes a swap operation on the target contract without input token approval
/// @dev Executes a swap operation without input token approval
/// Reverts if external call credit account is not set or any of passed tokens is not registered as collateral
/// @param tokenIn Input token that credit account spends in the call
/// @param tokenOut Output token that credit account receives after the call
/// @param callData Data to call the target contract with
Expand All @@ -80,19 +82,18 @@ abstract contract AbstractAdapter is IAdapter, ACLNonReentrantTrait {
/// @return tokensToEnable Bit mask of tokens that should be enabled after the call
/// @return tokensToDisable Bit mask of tokens that should be disabled after the call
/// @return result Call result
/// @dev Reverts if `tokenIn` or `tokenOut` are not registered as collateral in the credit manager
function _executeSwapNoApprove(address tokenIn, address tokenOut, bytes memory callData, bool disableTokenIn)
internal
returns (uint256 tokensToEnable, uint256 tokensToDisable, bytes memory result)
{
tokensToEnable = _getMaskOrRevert(tokenOut); // F: [AA-8, AA-10]
uint256 tokenInMask = _getMaskOrRevert(tokenIn); // F: [AA-10]
if (disableTokenIn) tokensToDisable = tokenInMask; // F: [AA-8]
result = _execute(callData); // F: [AA-8]
tokensToEnable = _getMaskOrRevert(tokenOut); // U:[AA-7]
uint256 tokenInMask = _getMaskOrRevert(tokenIn);
if (disableTokenIn) tokensToDisable = tokenInMask; // U:[AA-7]
result = _execute(callData); // U:[AA-7]
}

/// @dev Executes a swap operation on the target contract with maximum input token approval,
/// and resets this approval to 1 after the call
/// @dev Executes a swap operation with maximum input token approval, and revokes approval after the call
/// Reverts if external call credit account is not set or any of passed tokens is not registered as collateral
/// @param tokenIn Input token that credit account spends in the call
/// @param tokenOut Output token that credit account receives after the call
/// @param callData Data to call the target contract with
Expand All @@ -101,15 +102,15 @@ abstract contract AbstractAdapter is IAdapter, ACLNonReentrantTrait {
/// @return tokensToEnable Bit mask of tokens that should be enabled after the call
/// @return tokensToDisable Bit mask of tokens that should be disabled after the call
/// @return result Call result
/// @dev Reverts if `tokenIn` or `tokenOut` are not registered as collateral in the credit manager
/// @custom:expects Credit manager reverts when trying to approve non-collateral token
function _executeSwapSafeApprove(address tokenIn, address tokenOut, bytes memory callData, bool disableTokenIn)
internal
returns (uint256 tokensToEnable, uint256 tokensToDisable, bytes memory result)
{
tokensToEnable = _getMaskOrRevert(tokenOut); // F: [AA-9, AA-10]
if (disableTokenIn) tokensToDisable = _getMaskOrRevert(tokenIn); // F: [AA-9, AA-10]
_approveToken(tokenIn, type(uint256).max); // F: [AA-9, AA-10]
result = _execute(callData); // F: [AA-9]
_approveToken(tokenIn, 1); // F: [AA-9]
tokensToEnable = _getMaskOrRevert(tokenOut); // U:[AA-8]
if (disableTokenIn) tokensToDisable = _getMaskOrRevert(tokenIn); // U:[AA-8]
_approveToken(tokenIn, type(uint256).max); // U:[AA-8]
result = _execute(callData); // U:[AA-8]
_approveToken(tokenIn, 1); // U:[AA-8]
}
}
162 changes: 105 additions & 57 deletions contracts/core/AccountFactoryV3.sol
Original file line number Diff line number Diff line change
@@ -1,97 +1,145 @@
// SPDX-License-Identifier: BUSL-1.1
// Gearbox Protocol. Generalized leverage for DeFi protocols
// (c) Gearbox Holdings, 2022
// (c) Gearbox Holdings, 2023
pragma solidity ^0.8.17;
pragma abicoder v1;

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

import {IVersion} from "@gearbox-protocol/core-v2/contracts/interfaces/IVersion.sol";

import {CreditAccountV3} from "../credit/CreditAccountV3.sol";
import {CreditManagerV3} from "../credit/CreditManagerV3.sol";
import {IAccountFactoryV3} from "../interfaces/IAccountFactoryV3.sol";
import {
CallerNotCreditManagerException,
CreditAccountIsInUseException,
MasterCreditAccountAlreadyDeployedException
} from "../interfaces/IExceptions.sol";
import {ACLTrait} from "../traits/ACLTrait.sol";
import {ContractsRegisterTrait} from "../traits/ContractsRegisterTrait.sol";

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

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

import "forge-std/console.sol";

struct CreditManagerFactory {
/// @dev Struct storing per-CreditManager data on account usage queue
struct FactoryParams {
/// @dev Address of the contract being cloned to create new Credit Accounts
address masterCreditAccount;
uint32 head;
uint32 tail;
uint16 minUsedInQueue;
/// @dev Id of the next reused Credit Account in the used account queue, i.e.
/// the front of the reused CA queue
uint40 head;
/// @dev Id of the last returned Credit Account in the used account queue, i.e.
/// the back of the reused CA queue
uint40 tail;
}

/// @dev Struct compressing the credit account address and the
/// timestamp at which it is reusable
struct QueuedAccount {
address creditAccount;
uint40 reusableAfter;
}

/// @title Disposable credit accounts factory
contract AccountFactoryV3 is IAccountFactory, ACLTrait, ContractsRegisterTrait {
/// @dev Address of master credit account for cloning
mapping(address => CreditManagerFactory) public masterCreditAccounts;
/// @title Account factory V3
/// @notice Reusable credit accounts factory.
/// - Account deployment is cheap thanks to the clones proxy pattern
/// - Accounts are reusable: new accounts are only deployed when the queue of reusable accounts is empty
/// (a separate queue is maintained for each credit manager)
/// - When account is returned to the factory, it is only added to the queue after a certain delay, which
/// allows DAO to rescue funds that might have been accidentally left upon account closure
contract AccountFactoryV3 is IAccountFactoryV3, ACLTrait, ContractsRegisterTrait {
/// @inheritdoc IVersion
uint256 public constant override version = 3_00;

mapping(address => address[]) public usedCreditAccounts;
/// @inheritdoc IAccountFactoryV3
uint40 public constant override delay = 3 days;

/// @dev Contract version
uint256 public constant version = 3_00;
/// @dev Mapping credit manager => factory params
mapping(address => FactoryParams) internal _factoryParams;

error MasterCreditAccountAlreadyDeployed();
/// @dev Mapping credit manager => queued accounts
mapping(address => QueuedAccount[]) internal _queuedAccounts;

/// @param addressProvider Address of address repository
/// @notice Constructor
/// @param addressProvider Address provider contract address
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 deployAction, uint256) external override returns (address creditAccount) {
CreditManagerFactory storage cmf = masterCreditAccounts[msg.sender];
address masterCreditAccount = cmf.masterCreditAccount;
/// @inheritdoc IAccountFactoryV3
function takeCreditAccount(uint256, uint256) external override returns (address creditAccount) {
FactoryParams storage fp = _factoryParams[msg.sender];

address masterCreditAccount = fp.masterCreditAccount;
if (masterCreditAccount == address(0)) {
revert CallerNotCreditManagerException();
revert CallerNotCreditManagerException(); // U:[AF-1]
}
uint256 totalUsed = cmf.tail - cmf.head;
if (totalUsed < cmf.minUsedInQueue || deployAction == uint256(TakeAccountAction.DEPLOY_NEW_ONE)) {
// Create a new credit account if there are none in stock
creditAccount = Clones.clone(masterCreditAccount); // T:[AF-2]
emit DeployCreditAccount(creditAccount);

/// A used Credit Account is only given to a user if sufficiently long
/// time has passed since it was last taken out.
/// This is done to prevent a user from intentionally reopening an account
/// that they closed shortly prior, as this can potentially be used as an element
/// in an attack.
uint256 head = fp.head;
if (head == fp.tail || block.timestamp < _queuedAccounts[msg.sender][head].reusableAfter) {
creditAccount = Clones.clone(masterCreditAccount); // U:[AF-2A]
emit DeployCreditAccount({creditAccount: creditAccount, creditManager: msg.sender}); // U:[AF-2A]
} else {
creditAccount = usedCreditAccounts[msg.sender][cmf.head];
++cmf.head;
emit ReuseCreditAccount(creditAccount);
creditAccount = _queuedAccounts[msg.sender][head].creditAccount; // U:[AF-2B]
delete _queuedAccounts[msg.sender][head]; // U:[AF-2B]
unchecked {
++fp.head; // U:[AF-2B]
}
}

// emit InitializeCreditAccount(result, msg.sender); // T:[AF-5]
emit TakeCreditAccount({creditAccount: creditAccount, creditManager: msg.sender}); // U:[AF-2A,2B]
}

function returnCreditAccount(address usedAccount) external override {
CreditManagerFactory storage cmf = masterCreditAccounts[msg.sender];
/// @inheritdoc IAccountFactoryV3
function returnCreditAccount(address creditAccount) external override {
FactoryParams storage fp = _factoryParams[msg.sender];

if (cmf.masterCreditAccount == address(0)) {
revert CallerNotCreditManagerException();
if (fp.masterCreditAccount == address(0)) {
revert CallerNotCreditManagerException(); // U:[AF-1]
}

usedCreditAccounts[msg.sender][cmf.tail] = usedAccount;
++cmf.tail;
emit ReturnCreditAccount(usedAccount);
_queuedAccounts[msg.sender].push(
QueuedAccount({creditAccount: creditAccount, reusableAfter: uint40(block.timestamp) + delay})
); // U:[AF-3]
unchecked {
++fp.tail; // U:[AF-3]
}
emit ReturnCreditAccount({creditAccount: creditAccount, creditManager: msg.sender}); // U:[AF-3]
}

// CONFIGURATION
/// ------------- ///
/// CONFIGURATION ///
/// ------------- ///

function addCreditManager(address creditManager, uint16 minUsedInQueue)
/// @inheritdoc IAccountFactoryV3
function addCreditManager(address creditManager)
external
configuratorOnly
registeredCreditManagerOnly(creditManager)
override
configuratorOnly // U:[AF-1]
registeredCreditManagerOnly(creditManager) // U:[AF-4A]
{
if (masterCreditAccounts[creditManager].masterCreditAccount != address(0)) {
revert MasterCreditAccountAlreadyDeployed();
if (_factoryParams[creditManager].masterCreditAccount != address(0)) {
revert MasterCreditAccountAlreadyDeployedException(); // U:[AF-4B]
}
address masterCreditAccount = address(new CreditAccountV3(creditManager)); // U:[AF-4C]
_factoryParams[creditManager].masterCreditAccount = masterCreditAccount; // U:[AF-4C]
emit AddCreditManager(creditManager, masterCreditAccount); // U:[AF-4C]
}

masterCreditAccounts[creditManager] = CreditManagerFactory({
masterCreditAccount: address(new CreditAccountV3(creditManager)),
head: 0,
tail: 0,
minUsedInQueue: minUsedInQueue
});
/// @inheritdoc IAccountFactoryV3
function rescue(address creditAccount, address target, bytes calldata data)
external
configuratorOnly // U:[AF-1]
{
address creditManager = CreditAccountV3(creditAccount).creditManager();
_checkRegisteredCreditManagerOnly(creditManager); // U:[AF-5A]

(,,,,, address borrower) = CreditManagerV3(creditManager).creditAccountInfo(creditAccount);
if (borrower != address(0)) {
revert CreditAccountIsInUseException(); // U:[AF-5B]
}

emit AddCreditManager(creditManager);
CreditAccountV3(creditAccount).rescue(target, data); // U:[AF-5C]
}
}
Loading

0 comments on commit 85a581b

Please sign in to comment.