Skip to content

Commit

Permalink
Feat/separate deposit and multi asset deposit (#179)
Browse files Browse the repository at this point in the history
* Separate normal deposits and multi asset deposits, and add preview function.

* Add missing test and remove old commented code

* Copy over code to more advanced cellar permutations

* Add natspec clarifying who can call multi asset only owner functions.

* Add missing natspec

* Address warning

* Implement missing advanced permutations

* Separate deposit events into 2 separate events

* Add better natspec to the multi asset deposit event
  • Loading branch information
crispymangoes authored Jan 18, 2024
1 parent 59e723a commit 7754e2c
Show file tree
Hide file tree
Showing 6 changed files with 519 additions and 127 deletions.
7 changes: 1 addition & 6 deletions src/base/Cellar.sol
Original file line number Diff line number Diff line change
Expand Up @@ -707,11 +707,6 @@ contract Cellar is ERC4626, Owned, ERC721Holder {

// =========================================== CORE LOGIC ===========================================

/**
* @notice Emitted during deposits.
*/
event Deposit(address indexed caller, address indexed owner, address depositAsset, uint256 assets, uint256 shares);

/**
* @notice Attempted an action with zero shares.
*/
Expand Down Expand Up @@ -775,7 +770,7 @@ contract Cellar is ERC4626, Owned, ERC721Holder {

_mint(receiver, shares);

emit Deposit(msg.sender, receiver, address(asset), assets, shares);
emit Deposit(msg.sender, receiver, assets, shares);

afterDeposit(position, assets, shares, receiver);
}
Expand Down
137 changes: 94 additions & 43 deletions src/base/permutations/CellarWithMultiAssetDeposit.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@ pragma solidity 0.8.21;

import { Cellar, Registry, ERC20, Math, SafeTransferLib, Address } from "src/base/Cellar.sol";

// TODO once audited, make a permutation for oracle, aave flashloans, multi-asset deposit
// TODO once audited, make a permutation for oracle, aave flashloans, multi-asset deposit, native support
contract CellarWithMultiAssetDeposit is Cellar {
using Math for uint256;
using SafeTransferLib for ERC20;
Expand Down Expand Up @@ -56,6 +54,20 @@ contract CellarWithMultiAssetDeposit is Cellar {
*/
event AlternativeAssetDropped(address asset);

/**
* @notice Emitted during multi asset deposits.
* @dev Multi asset deposits will emit 2 events, the ERC4626 compliant Deposit event
* and this event. These events were intentionally separated out so we can
* keep the compliant event, but also have an event that emits the depositAsset.
*/
event MultiAssetDeposit(
address indexed caller,
address indexed owner,
address depositAsset,
uint256 assets,
uint256 shares
);

//============================== IMMUTABLES ===============================

constructor(
Expand Down Expand Up @@ -88,6 +100,7 @@ contract CellarWithMultiAssetDeposit is Cellar {

/**
* @notice Allows the owner to add, or update an existing alternative asset deposit.
* @dev Callable by Sommelier Strategists.
* @param _alternativeAsset the ERC20 alternative asset that can be deposited
* @param _alternativeHoldingPosition the holding position to direct alternative asset deposits to
* @param _alternativeAssetFee the fee to charge for depositing this alternative asset
Expand Down Expand Up @@ -117,34 +130,91 @@ contract CellarWithMultiAssetDeposit is Cellar {

/**
* @notice Allows the owner to stop an alternative asset from being deposited.
* @dev Callable by Sommelier Strategists.
* @param _alternativeAsset the asset to not allow for alternative asset deposits anymore
*/
function dropAlternativeAssetData(ERC20 _alternativeAsset) external {
_onlyOwner();
delete alternativeAssetData[_alternativeAsset];
// alternativeAssetData[_alternativeAsset] = AlternativeAssetData(false, 0, 0);

emit AlternativeAssetDropped(address(_alternativeAsset));
}

/**
* @notice Deposits assets into the cellar, and returns shares to receiver.
* @dev Compliant with ERC4626 standard, but additionally allows for multi-asset deposits
* by encoding the asset to deposit at the end of the normal deposit params.
* @param assets amount of assets deposited by user.
* @param receiver address to receive the shares.
* @return shares amount of shares given for deposit.
*/
function deposit(uint256 assets, address receiver) public override nonReentrant returns (uint256 shares) {
// Use `_calculateTotalAssetsOrTotalAssetsWithdrawable` instead of totalAssets bc re-entrancy is already checked in this function.
(uint256 _totalAssets, uint256 _totalSupply) = _getTotalAssetsAndTotalSupply(true);
shares = _deposit(asset, assets, assets, assets, holdingPosition, receiver);
}

/**
* @notice Allows users to deposit into cellar using alternative assets.
* @param depositAsset the asset to deposit
* @param assets amount of depositAsset to deposit
* @param receiver address to receive the shares
*/
function multiAssetDeposit(
ERC20 depositAsset,
uint256 assets,
address receiver
) public nonReentrant returns (uint256 shares) {
// Convert assets from depositAsset to asset.
(
ERC20 depositAsset,
uint256 assetsConvertedToAsset,
uint256 assetsConvertedToAssetWithFeeRemoved,
uint32 position
) = _getDepositAssetAndAdjustedAssetsAndPosition(assets);
) = _getMultiAssetDepositData(depositAsset, assets);

shares = _deposit(
depositAsset,
assets,
assetsConvertedToAsset,
assetsConvertedToAssetWithFeeRemoved,
position,
receiver
);

emit MultiAssetDeposit(msg.sender, receiver, address(depositAsset), assets, shares);
}

//============================== PREVIEW FUNCTIONS ===============================

/**
* @notice Preview function to see how many shares a multi asset deposit will give user.
*/
function previewMultiAssetDeposit(ERC20 depositAsset, uint256 assets) external view returns (uint256 shares) {
// Convert assets from depositAsset to asset.
(uint256 assetsConvertedToAsset, uint256 assetsConvertedToAssetWithFeeRemoved, ) = _getMultiAssetDepositData(
depositAsset,
assets
);

(uint256 _totalAssets, uint256 _totalSupply) = _getTotalAssetsAndTotalSupply(true);
shares = _convertToShares(
assetsConvertedToAssetWithFeeRemoved,
_totalAssets + (assetsConvertedToAsset - assetsConvertedToAssetWithFeeRemoved),
_totalSupply
);
}

//============================== HELPER FUNCTIONS ===============================

/**
* @notice Helper function to fulfill normal deposits and multi asset deposits.
*/
function _deposit(
ERC20 depositAsset,
uint256 assets,
uint256 assetsConvertedToAsset,
uint256 assetsConvertedToAssetWithFeeRemoved,
uint32 position,
address receiver
) internal returns (uint256 shares) {
// Use `_calculateTotalAssetsOrTotalAssetsWithdrawable` instead of totalAssets bc re-entrancy is already checked in this function.
(uint256 _totalAssets, uint256 _totalSupply) = _getTotalAssetsAndTotalSupply(true);

// Perform share calculation using assetsConvertedToAssetWithFeeRemoved.
// Check for rounding error since we round down in previewDeposit.
Expand All @@ -164,46 +234,27 @@ contract CellarWithMultiAssetDeposit is Cellar {
_enter(depositAsset, position, assets, shares, receiver);
}

//============================== HELPER FUNCTION ===============================

/**
* @notice Reads message data to determine if user is trying to deposit with an alternative asset or wants to do a normal deposit.
* @notice Helper function to verify asset is supported for multi asset deposit,
* convert assets from depositAsset to asset, and account for alternative asset fee.
*/
function _getDepositAssetAndAdjustedAssetsAndPosition(
function _getMultiAssetDepositData(
ERC20 depositAsset,
uint256 assets
)
internal
view
returns (
ERC20 depositAsset,
uint256 assetsConvertedToAsset,
uint256 assetsConvertedToAssetWithFeeRemoved,
uint32 position
)
returns (uint256 assetsConvertedToAsset, uint256 assetsConvertedToAssetWithFeeRemoved, uint32 position)
{
uint256 msgDataLength = msg.data.length;
if (msgDataLength == 68) {
// Caller has not encoded an alternative asset, so return address(0).
depositAsset = asset;
assetsConvertedToAssetWithFeeRemoved = assets;
assetsConvertedToAsset = assets;
position = holdingPosition;
} else if (msgDataLength == 100) {
// Caller has encoded an extra arguments, try to decode it as an address.
(, , depositAsset) = abi.decode(msg.data[4:], (uint256, address, ERC20));

AlternativeAssetData memory assetData = alternativeAssetData[depositAsset];
if (!assetData.isSupported) revert CellarWithMultiAssetDeposit__AlternativeAssetNotSupported();

// Convert assets from depositAsset to asset.
assetsConvertedToAsset = priceRouter.getValue(depositAsset, assets, asset);

// Collect alternative asset fee.
assetsConvertedToAssetWithFeeRemoved = assetsConvertedToAsset.mulDivDown(1e8 - assetData.depositFee, 1e8);

position = assetData.holdingPosition;
} else {
revert CellarWithMultiAssetDeposit__CallDataLengthNotSupported();
}
AlternativeAssetData memory assetData = alternativeAssetData[depositAsset];
if (!assetData.isSupported) revert CellarWithMultiAssetDeposit__AlternativeAssetNotSupported();

// Convert assets from depositAsset to asset.
assetsConvertedToAsset = priceRouter.getValue(depositAsset, assets, asset);

// Collect alternative asset fee.
assetsConvertedToAssetWithFeeRemoved = assetsConvertedToAsset.mulDivDown(1e8 - assetData.depositFee, 1e8);

position = assetData.holdingPosition;
}
}
Loading

0 comments on commit 7754e2c

Please sign in to comment.