Skip to content

Commit

Permalink
Merge branch 'dev/macro-audit-12' of https://github.com/PeggyJV/cella…
Browse files Browse the repository at this point in the history
…r-contracts into feat/s-frax-support
  • Loading branch information
crispymangoes committed Oct 24, 2023
2 parents 0c5d7ee + b3fea67 commit 9393ad5
Show file tree
Hide file tree
Showing 13 changed files with 1,191 additions and 154 deletions.
8 changes: 8 additions & 0 deletions src/interfaces/external/Aura/IBaseRewardPool.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.21;

interface IBaseRewardPool {
// Aura Pool Example: https://etherscan.deth.net/address/0x032B676d5D55e8ECbAe88ebEE0AA10fB5f72F6CB

function getReward(address _account, bool _claimExtras) external returns (bool);
}
8 changes: 8 additions & 0 deletions src/interfaces/external/Frax/IFToken.sol
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,14 @@ interface IFToken {

function toBorrowAmount(uint256 _shares, bool _roundUp) external view returns (uint256 _amount);

function toBorrowShares(
uint256 _amount,
bool _roundUp,
bool _previewInterest
) external view returns (uint256 _shares);

function toBorrowShares(uint256 _amount, bool _roundUp) external view returns (uint256);

function userBorrowShares(address) external view returns (uint256);

function userCollateralBalance(address) external view returns (uint256);
Expand Down
78 changes: 78 additions & 0 deletions src/modules/adaptors/Aura/AuraERC4626Adaptor.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity 0.8.21;

import { BaseAdaptor, ERC20, SafeTransferLib, Cellar, PriceRouter, Math } from "src/modules/adaptors/BaseAdaptor.sol";
import { IBaseRewardPool } from "src/interfaces/external/Aura/IBaseRewardPool.sol";
import { ERC4626 } from "@solmate/mixins/ERC4626.sol";
import { ERC4626Adaptor } from "src/modules/adaptors/ERC4626Adaptor.sol";

/**
* @title Aura ERC4626 Adaptor
* @dev This adaptor is specifically for Aura contracts.
* @notice Carries out typical ERC4626Adaptor functionality and allows Cellars to claim rewards from AURA pools
* @author crispymangoes, 0xEinCodes
* NOTE: Transferrance of aura-wrapped BPT is not alowed as per their contracts: ref - https://etherscan.io/address/0xdd1fe5ad401d4777ce89959b7fa587e569bf125d#code#F1#L254
*/
contract AuraERC4626Adaptor is ERC4626Adaptor {
using SafeTransferLib for ERC20;
using Math for uint256;

//==================== Adaptor Data Specification ====================
// adaptorData = abi.encode(address auraPool)
// Where:
// `auraPool` is the AURA pool address position this adaptor is working with.
//================= Configuration Data Specification =================
// NA
//====================================================================

/**
* @notice Attempted to interact with an auraPool the Cellar is not using.
*/
error AuraExtrasAdaptor__AuraPoolPositionsMustBeTracked(address auraPool);

//============================================ Global Functions ===========================================
/**
* @dev Identifier unique to this adaptor for a shared registry.
* Normally the identifier would just be the address of this contract, but this
* Identifier is needed during Cellar Delegate Call Operations, so getting the address
* of the adaptor is more difficult.
*/
function identifier() public pure virtual override returns (bytes32) {
return keccak256(abi.encode("Aura ERC4626 Adaptor V 0.1"));
}

//============================================ Strategist Functions ===========================================

/**
* @notice Allows strategists to get rewards for an AuraPool.
* @param _auraPool the specified AuraPool
* @param _claimExtras Whether or not to claim extra rewards associated to the AuraPool (outside of rewardToken for AuraPool)
*/
function getRewards(IBaseRewardPool _auraPool, bool _claimExtras) public {
_validateAuraPool(address(_auraPool));
_getRewards(_auraPool, _claimExtras);
}

/**
* @notice Validates that a given auraPool is set up as a position in the calling Cellar.
* @dev This function uses `address(this)` as the address of the calling Cellar.
*/
function _validateAuraPool(address _auraPool) internal view {
bytes32 positionHash = keccak256(abi.encode(identifier(), false, abi.encode(_auraPool)));
uint32 positionId = Cellar(address(this)).registry().getPositionHashToPositionId(positionHash);
if (!Cellar(address(this)).isPositionUsed(positionId))
revert AuraExtrasAdaptor__AuraPoolPositionsMustBeTracked(_auraPool);
}

//============================================ Interface Helper Functions ===========================================

//============================== Interface Details ==============================
// It is unlikely, but AURA pool interfaces can change between versions.
// To account for this, internal functions will be used in case it is needed to
// implement new functionality.
//===============================================================================

function _getRewards(IBaseRewardPool _auraPool, bool _claimExtras) internal virtual {
_auraPool.getReward(address(this), _claimExtras);
}
}
174 changes: 174 additions & 0 deletions src/modules/adaptors/ERC4626Adaptor.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity 0.8.21;

import { BaseAdaptor, ERC20, SafeTransferLib, Cellar } from "src/modules/adaptors/BaseAdaptor.sol";
import { ERC4626 } from "@solmate/mixins/ERC4626.sol";

/**
* @title Generic ERC4626 Vault Adaptor (basically a copy of Cellar Adaptor w/ virtual function sigs to enable inheritance and overriding).
* @notice Allows Cellars to interact with other ERC4626 contracts.
* @author crispymangoes, 0xEinCodes
* NOTE: `CellarAdaptor.sol` was used, and is still used for nested Cellar positions for Sommelier Cellars (ERC4626 Vaults). Integrations into Aura, among other ERC4626 contracts outside of Sommelier have led to using a separate ERC4626Vault.sol file that allows more flexibility via inheritance.
* NOTE: `Cellar` is still used throughout this contract at times because it is a contract made for the Sommelier protocol (has extra pricing and accounting mechanics, etc.)
*/
contract ERC4626Adaptor is BaseAdaptor {
using SafeTransferLib for ERC20;

//==================== Adaptor Data Specification ====================
// adaptorData = abi.encode(ERC4626 erc4626Vault)
// Where:
// `erc4626Vault` is the underling ERC4626 this adaptor is working with
//================= Configuration Data Specification =================
// configurationData = abi.encode(bool isLiquid)
// Where:
// `isLiquid` dictates whether the position is liquid or not
// If true:
// position can support use withdraws
// else:
// position can not support user withdraws
//
//====================================================================

/**
* @notice Strategist attempted to interact with a erc4626Vault with no position setup for it.
*/
error ERC4626Adaptor__ERC4626PositionNotUsed(address erc4626Vault);

//============================================ Global Functions ===========================================
/**
* @dev Identifier unique to this adaptor for a shared registry.
* Normally the identifier would just be the address of this contract, but this
* Identifier is needed during Cellar Delegate Call Operations, so getting the address
* of the adaptor is more difficult.
*/
function identifier() public pure virtual override returns (bytes32) {
return keccak256(abi.encode("Sommelier General ERC4626 Adaptor V 0.0"));
}

//============================================ Implement Base Functions ===========================================
/**
* @notice Cellar must approve ERC4626 position to spend its assets, then deposit into the ERC4626 position.
* @param assets the amount of assets to deposit into the ERC4626 position
* @param adaptorData adaptor data containining the abi encoded ERC4626
* @dev configurationData is NOT used
*/
function deposit(uint256 assets, bytes memory adaptorData, bytes memory) public virtual override {
// Deposit assets to `vault`.
ERC4626 erc4626Vault = abi.decode(adaptorData, (ERC4626));
_verifyERC4626PositionIsUsed(address(erc4626Vault));
ERC20 asset = erc4626Vault.asset();
asset.safeApprove(address(erc4626Vault), assets);
erc4626Vault.deposit(assets, address(this));

// Zero out approvals if necessary.
_revokeExternalApproval(asset, address(erc4626Vault));
}

/**
* @notice Cellar needs to call withdraw on ERC4626 position.
* @dev Important to verify that external receivers are allowed if receiver is not Cellar address.
* @param assets the amount of assets to withdraw from the ERC4626 position
* @param receiver address to send assets to'
* @param adaptorData data needed to withdraw from the ERC4626 position
* @param configurationData abi encoded bool indicating whether the position is liquid or not
*/
function withdraw(
uint256 assets,
address receiver,
bytes memory adaptorData,
bytes memory configurationData
) public virtual override {
// Check that position is setup to be liquid.
bool isLiquid = abi.decode(configurationData, (bool));
if (!isLiquid) revert BaseAdaptor__UserWithdrawsNotAllowed();

// Run external receiver check.
_externalReceiverCheck(receiver);

// Withdraw assets from `Vault`.
ERC4626 erc4626Vault = abi.decode(adaptorData, (ERC4626));
_verifyERC4626PositionIsUsed(address(erc4626Vault));
erc4626Vault.withdraw(assets, receiver, address(this));
}

/**
* @notice Cellar needs to call `maxWithdraw` to see if its assets are locked.
*/
function withdrawableFrom(
bytes memory adaptorData,
bytes memory configurationData
) public view virtual override returns (uint256) {
bool isLiquid = abi.decode(configurationData, (bool));
if (isLiquid) {
ERC4626 erc4626Vault = abi.decode(adaptorData, (ERC4626));
return erc4626Vault.maxWithdraw(msg.sender);
} else return 0;
}

/**
* @notice Uses ERC4626 `previewRedeem` to determine Cellars balance in ERC4626 position.
*/
function balanceOf(bytes memory adaptorData) public view virtual override returns (uint256) {
ERC4626 erc4626Vault = abi.decode(adaptorData, (ERC4626));
return erc4626Vault.previewRedeem(erc4626Vault.balanceOf(msg.sender));
}

/**
* @notice Returns the asset the ERC4626 position uses.
*/
function assetOf(bytes memory adaptorData) public view virtual override returns (ERC20) {
ERC4626 erc4626Vault = abi.decode(adaptorData, (ERC4626));
return ERC20(erc4626Vault.asset());
}

/**
* @notice This adaptor returns collateral, and not debt.
*/
function isDebt() public pure virtual override returns (bool) {
return false;
}

//============================================ Strategist Functions ===========================================
/**
* @notice Allows strategists to deposit into ERC4626 positions.
* @dev Uses `_maxAvailable` helper function, see BaseAdaptor.sol
* @param erc4626Vault the ERC4626 to deposit `assets` into
* @param assets the amount of assets to deposit into `Vault`
*/
function depositToVault(ERC4626 erc4626Vault, uint256 assets) public {
_verifyERC4626PositionIsUsed(address(erc4626Vault));
ERC20 asset = erc4626Vault.asset();
assets = _maxAvailable(asset, assets);
asset.safeApprove(address(erc4626Vault), assets);
erc4626Vault.deposit(assets, address(this));

// Zero out approvals if necessary.
_revokeExternalApproval(asset, address(erc4626Vault));
}

/**
* @notice Allows strategists to withdraw from ERC4626 positions.
* @param erc4626Vault the ERC4626 to withdraw `assets` from
* @param assets the amount of assets to withdraw from `Vault`
*/
function withdrawFromVault(ERC4626 erc4626Vault, uint256 assets) public {
_verifyERC4626PositionIsUsed(address(erc4626Vault));
if (assets == type(uint256).max) assets = erc4626Vault.maxWithdraw(address(this));
erc4626Vault.withdraw(assets, address(this), address(this));
}

//============================================ Helper Functions ===========================================

/**
* @notice Reverts if a given `erc4626Vault` is not set up as a position in the calling Cellar.
* @dev This function is only used in a delegate call context, hence why address(this) is used
* to get the calling Cellar.
*/
function _verifyERC4626PositionIsUsed(address erc4626Vault) internal view {
// Check that erc4626Vault position is setup to be used in the calling cellar.
bytes32 positionHash = keccak256(abi.encode(identifier(), false, abi.encode(erc4626Vault)));
uint32 positionId = Cellar(address(this)).registry().getPositionHashToPositionId(positionHash);
if (!Cellar(address(this)).isPositionUsed(positionId))
revert ERC4626Adaptor__ERC4626PositionNotUsed(erc4626Vault);
}
}
5 changes: 5 additions & 0 deletions src/modules/adaptors/Frax/CollateralFTokenAdaptor.sol
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,11 @@ contract CollateralFTokenAdaptor is BaseAdaptor, FraxlendHealthFactorLogic {
*/
function removeCollateral(uint256 _collateralAmount, IFToken _fraxlendPair) public {
_validateFToken(_fraxlendPair);

if (_collateralAmount == type(uint256).max) {
_collateralAmount = _userCollateralBalance(_fraxlendPair, address(this));
}

// remove collateral
_removeCollateral(_collateralAmount, _fraxlendPair);
uint256 _exchangeRate = _getExchangeRateInfo(_fraxlendPair); // needed to calculate LTV
Expand Down
12 changes: 6 additions & 6 deletions src/modules/adaptors/Frax/DebtFTokenAdaptor.sol
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ contract DebtFTokenAdaptor is BaseAdaptor, FraxlendHealthFactorLogic {
_validateFToken(_fraxlendPair);
ERC20 tokenToRepay = ERC20(_fraxlendPairAsset(_fraxlendPair));
uint256 debtTokenToRepay = _maxAvailable(tokenToRepay, _debtTokenRepayAmount);
uint256 sharesToRepay = _toAssetShares(_fraxlendPair, debtTokenToRepay, false, true);
uint256 sharesToRepay = _toBorrowShares(_fraxlendPair, debtTokenToRepay, false, true);
uint256 sharesAccToFraxlend = _userBorrowShares(_fraxlendPair, address(this)); // get fraxlendPair's record of borrowShares atm
if (sharesAccToFraxlend == 0) revert DebtFTokenAdaptor__CannotRepayNoDebt(address(_fraxlendPair)); // NOTE: from checking it out, unless `userBorrowShares[_borrower] -= _shares;` reverts, then fraxlendCore lets users repay FRAX w/ no limiters.

Expand Down Expand Up @@ -250,21 +250,21 @@ contract DebtFTokenAdaptor is BaseAdaptor, FraxlendHealthFactorLogic {
}

/**
* @notice Converts a given asset amount to a number of asset shares (fTokens) from specified 'v2' Fraxlend Pair
* @dev This is one of the adjusted functions from v1 to v2. ftoken.toAssetShares() calls into the respective version (v2 by default) of Fraxlend Pair
* @notice Converts a given asset amount to a number of borrow shares from specified 'v2' Fraxlend Pair
* @dev This is one of the adjusted functions from v1 to v2. ftoken.toBorrowAmount() calls into the respective version (v2 by default) of Fraxlend Pair
* @param fToken The specified Fraxlend Pair
* @param amount The amount of asset
* @param roundUp Whether to round up after division
* @param previewInterest Whether to preview interest accrual before calculation
* @return number of asset shares
* @return number of borrow shares
*/
function _toAssetShares(
function _toBorrowShares(
IFToken fToken,
uint256 amount,
bool roundUp,
bool previewInterest
) internal view virtual returns (uint256) {
return fToken.toAssetShares(amount, roundUp, previewInterest);
return fToken.toBorrowShares(amount, roundUp, previewInterest);
}

/**
Expand Down
8 changes: 4 additions & 4 deletions src/modules/adaptors/Frax/DebtFTokenAdaptorV1.sol
Original file line number Diff line number Diff line change
Expand Up @@ -88,20 +88,20 @@ contract DebtFTokenAdaptorV1 is DebtFTokenAdaptor {
}

/**
* @notice Converts a given asset amount to a number of asset shares (fTokens) from specified FraxlendV1 Pair
* @notice Converts a given asset amount to a number of borrow shares from specified FraxlendV1 Pair
* @dev versions of FraxLendPair do not have a fourth param whereas v2 does
* @param fToken The specified FraxLendPair
* @param amount The amount of asset
* @param roundUp Whether to round up after division
* @return number of asset shares
* @return number of borrow shares
*/
function _toAssetShares(
function _toBorrowShares(
IFToken fToken,
uint256 amount,
bool roundUp,
bool
) internal view override returns (uint256) {
return fToken.toAssetShares(amount, roundUp);
return fToken.toBorrowShares(amount, roundUp);
}

/**
Expand Down
Loading

0 comments on commit 9393ad5

Please sign in to comment.