Description:
Multi-signature wallet contract requiring multiple confirmations for transaction execution.
Blockchain: Ethereum
Source Code: View Code On The Blockchain
Solidity Source Code:
{{
"language": "Solidity",
"sources": {
"src/policies/deposits/DepositManager.sol": {
"content": "// SPDX-License-Identifier: AGPL-3.0
/// forge-lint: disable-start(asm-keccak256, mixed-case-function)
pragma solidity >=0.8.20;
// Interfaces
import {IERC20} from "src/interfaces/IERC20.sol";
import {IDepositManager} from "src/policies/interfaces/deposits/IDepositManager.sol";
import {IReceiptTokenManager} from "src/policies/interfaces/deposits/IReceiptTokenManager.sol";
import {IERC4626} from "src/interfaces/IERC4626.sol";
import {IERC165} from "@openzeppelin-5.3.0/interfaces/IERC165.sol";
// Libraries
import {ERC20} from "@solmate-6.2.0/tokens/ERC20.sol";
import {EnumerableSet} from "@openzeppelin-5.3.0/utils/structs/EnumerableSet.sol";
import {TransferHelper} from "src/libraries/TransferHelper.sol";
// Bophades
import {Kernel, Keycode, Permissions, Policy, toKeycode} from "src/Kernel.sol";
import {ROLESv1} from "src/modules/ROLES/OlympusRoles.sol";
import {PolicyEnabler} from "src/policies/utils/PolicyEnabler.sol";
import {BaseAssetManager} from "src/bases/BaseAssetManager.sol";
import {ReceiptTokenManager} from "src/policies/deposits/ReceiptTokenManager.sol";
/// @title Deposit Manager
/// @notice This policy manages deposits and withdrawals for Olympus protocol contracts
/// @dev Key Features:
/// - ERC6909 receipt tokens with optional ERC20 wrapping, using ReceiptTokenManager
/// - Operator isolation preventing cross-operator fund access
/// - Borrowing functionality
/// - Configurable reclaim rates for risk management
contract DepositManager is Policy, PolicyEnabler, IDepositManager, BaseAssetManager {
using TransferHelper for ERC20;
using EnumerableSet for EnumerableSet.UintSet;
// ========== CONSTANTS ========== //
/// @notice The role that is allowed to deposit and withdraw funds
bytes32 public constant ROLE_DEPOSIT_OPERATOR = "deposit_operator";
/// @notice The receipt token manager for creating receipt tokens
ReceiptTokenManager internal immutable _RECEIPT_TOKEN_MANAGER;
// ========== STATE VARIABLES ========== //
/// @notice Maps asset liabilities key to the number of receipt tokens that have been minted
/// @dev This is used to ensure that the receipt tokens are solvent
/// As with the BaseAssetManager, deposited asset tokens with different deposit periods are co-mingled.
mapping(bytes32 key => uint256 receiptTokenSupply) internal _assetLiabilities;
/// @notice Maps token ID to the asset period
mapping(uint256 tokenId => AssetPeriod) internal _assetPeriods;
/// @notice Set of token IDs that this DepositManager owns
EnumerableSet.UintSet internal _ownedTokenIds;
/// @notice Constant equivalent to 100%
uint16 public constant ONE_HUNDRED_PERCENT = 100e2;
/// @notice Maps operator address to its name
mapping(address operator => bytes3 name) internal _operatorToName;
/// @notice A set of operator names
/// @dev This contains unique values
mapping(bytes3 name => bool isRegistered) internal _operatorNames;
// ========== BORROWING STATE VARIABLES ========== //
/// @notice Maps asset-operator key to current borrowed amounts
/// @dev The key is the keccak256 of the asset address and the operator address
mapping(bytes32 key => uint256 borrowedAmount) internal _borrowedAmounts;
// ========== MODIFIERS ========== //
function _onlyAssetPeriodExists(
IERC20 asset_,
uint8 depositPeriod_,
address operator_
) internal view {
uint256 tokenId = _RECEIPT_TOKEN_MANAGER.getReceiptTokenId(
address(this),
asset_,
depositPeriod_,
operator_
);
if (address(_assetPeriods[tokenId].asset) == address(0)) {
revert DepositManager_InvalidAssetPeriod(address(asset_), depositPeriod_, operator_);
}
}
/// @notice Reverts if the asset period is not configured
modifier onlyAssetPeriodExists(
IERC20 asset_,
uint8 depositPeriod_,
address operator_
) {
_onlyAssetPeriodExists(asset_, depositPeriod_, operator_);
_;
}
function _onlyAssetPeriodEnabled(
IERC20 asset_,
uint8 depositPeriod_,
address operator_
) internal view {
uint256 tokenId = _RECEIPT_TOKEN_MANAGER.getReceiptTokenId(
address(this),
asset_,
depositPeriod_,
operator_
);
AssetPeriod memory assetPeriod = _assetPeriods[tokenId];
if (assetPeriod.asset == address(0)) {
revert DepositManager_InvalidAssetPeriod(address(asset_), depositPeriod_, operator_);
}
if (!assetPeriod.isEnabled) {
revert DepositManager_AssetPeriodDisabled(address(asset_), depositPeriod_, operator_);
}
}
/// @notice Reverts if the asset period is not enabled
modifier onlyAssetPeriodEnabled(
IERC20 asset_,
uint8 depositPeriod_,
address operator_
) {
_onlyAssetPeriodEnabled(asset_, depositPeriod_, operator_);
_;
}
// ========== CONSTRUCTOR ========== //
constructor(address kernel_, address tokenManager_) Policy(Kernel(kernel_)) {
// Validate that the token manager implements IReceiptTokenManager
if (!IERC165(tokenManager_).supportsInterface(type(IReceiptTokenManager).interfaceId)) {
revert DepositManager_InvalidParams("token manager");
}
_RECEIPT_TOKEN_MANAGER = ReceiptTokenManager(tokenManager_);
// Disabled by default by PolicyEnabler
}
// ========== Policy Configuration ========== //
/// @inheritdoc Policy
function configureDependencies() external override returns (Keycode[] memory dependencies) {
dependencies = new Keycode[](1);
dependencies[0] = toKeycode("ROLES");
ROLES = ROLESv1(getModuleAddress(dependencies[0]));
}
/// @inheritdoc Policy
function requestPermissions()
external
view
override
returns (Permissions[] memory permissions)
{}
function VERSION() external pure returns (uint8 major, uint8 minor) {
major = 1;
minor = 0;
return (major, minor);
}
// ========== DEPOSIT/WITHDRAW FUNCTIONS ========== //
/// @inheritdoc IDepositManager
/// @dev This function is only callable by addresses with the deposit operator role
///
/// The actions of the calling deposit operator are restricted to its own namespace, preventing the operator from accessing funds of other operators.
///
/// This function reverts if:
/// - The contract is not enabled
/// - The caller does not have the deposit operator role
/// - The asset/deposit period/operator combination is not enabled
/// - The deposit amount is below the minimum deposit requirement
/// - The deposit would exceed the asset's deposit cap for the operator
/// - The depositor has not approved the DepositManager to spend the asset tokens
/// - The depositor has insufficient asset token balance
/// - The asset is a fee-on-transfer token
/// - Zero shares would be received from the vault
function deposit(
DepositParams calldata params_
)
external
onlyEnabled
onlyRole(ROLE_DEPOSIT_OPERATOR)
onlyAssetPeriodEnabled(params_.asset, params_.depositPeriod, msg.sender)
returns (uint256 receiptTokenId, uint256 actualAmount)
{
// Deposit into vault
// This will revert if the asset is not configured
// This takes place before any state changes to avoid ERC777 re-entrancy
(actualAmount, ) = _depositAsset(
params_.asset,
params_.depositor,
params_.amount,
true // Enforce minimum deposit
);
// Mint the receipt token to the caller
receiptTokenId = _RECEIPT_TOKEN_MANAGER.getReceiptTokenId(
address(this),
params_.asset,
params_.depositPeriod,
msg.sender
);
_RECEIPT_TOKEN_MANAGER.mint(
params_.depositor,
receiptTokenId,
actualAmount,
params_.shouldWrap
);
// Update the asset liabilities for the caller (operator)
_assetLiabilities[_getAssetLiabilitiesKey(params_.asset, msg.sender)] += actualAmount;
return (receiptTokenId, actualAmount);
}
/// @inheritdoc IDepositManager
/// @dev The actions of the calling deposit operator are restricted to its own namespace, preventing the operator from accessing funds of other operators.
///
/// Note that the returned value is a theoretical maximum. The theoretical value may not be accurate or possible due to rounding and other behaviours in an ERC4626 vault.
function maxClaimYield(IERC20 asset_, address operator_) external view returns (uint256) {
(, uint256 depositedSharesInAssets) = getOperatorAssets(asset_, operator_);
bytes32 assetLiabilitiesKey = _getAssetLiabilitiesKey(asset_, operator_);
uint256 operatorLiabilities = _assetLiabilities[assetLiabilitiesKey];
uint256 borrowedAmount = _borrowedAmounts[assetLiabilitiesKey];
// Avoid reverting
// Adjust by 1 to account for the different behaviour in ERC4626.previewRedeem and ERC4626.previewWithdraw, which could leave the receipt token insolvent
if (depositedSharesInAssets + borrowedAmount < operatorLiabilities + 1) return 0;
return depositedSharesInAssets + borrowedAmount - operatorLiabilities - 1;
}
/// @inheritdoc IDepositManager
/// @dev Notes:
/// - This function is only callable by addresses with the deposit operator role
/// - The actions of the calling deposit operator are restricted to its own namespace, preventing the operator from accessing funds of other operators.
/// - Given a low enough amount, the actual amount withdrawn may be 0. This function will not revert in such a case.
///
/// This function reverts if:
/// - The contract is not enabled
/// - The caller does not have the deposit operator role
/// - The asset is not configured in BaseAssetManager
/// - The operator becomes insolvent after the withdrawal (assets + borrowed < liabilities)
function claimYield(
IERC20 asset_,
address recipient_,
uint256 amount_
)
external
onlyEnabled
onlyRole(ROLE_DEPOSIT_OPERATOR)
onlyConfiguredAsset(asset_)
returns (uint256 actualAmount)
{
// Withdraw the funds from the vault
// The value returned can also be zero
(, actualAmount) = _withdrawAsset(asset_, recipient_, amount_);
// The receipt token supply is not adjusted here, as there is no minting/burning of receipt tokens
// Validate operator solvency after withdrawal
_validateOperatorSolvency(asset_, msg.sender);
// Emit an event
emit OperatorYieldClaimed(address(asset_), recipient_, msg.sender, actualAmount);
return actualAmount;
}
/// @inheritdoc IDepositManager
/// @dev Notes:
/// - This function is only callable by addresses with the deposit operator role
/// - The actions of the calling deposit operator are restricted to its own namespace, preventing the operator from accessing funds of other operators.
/// - Given a low enough amount, the actual amount withdrawn may be 0. This function will not revert in such a case.
///
/// This function will revert if:
/// - The contract is not enabled
/// - The caller does not have the deposit operator role
/// - The recipient is the zero address
/// - The asset/deposit period/operator combination is not configured
/// - The depositor has insufficient receipt token balance
/// - For wrapped tokens: depositor has not approved ReceiptTokenManager to spend the wrapped ERC20 token
/// - For unwrapped tokens: depositor has not approved the caller to spend ERC6909 tokens
/// - The operator becomes insolvent after the withdrawal (assets + borrowed < liabilities)
function withdraw(
WithdrawParams calldata params_
) external onlyEnabled onlyRole(ROLE_DEPOSIT_OPERATOR) returns (uint256 actualAmount) {
// Validate that the recipient is not the zero address
if (params_.recipient == address(0)) revert DepositManager_ZeroAddress();
// Burn the receipt token from the depositor
// Will revert if the asset configuration is not valid/invalid receipt token ID
_RECEIPT_TOKEN_MANAGER.burn(
params_.depositor,
_RECEIPT_TOKEN_MANAGER.getReceiptTokenId(
address(this),
params_.asset,
params_.depositPeriod,
msg.sender
),
params_.amount,
params_.isWrapped
);
// Update the asset liabilities for the caller (operator)
_assetLiabilities[_getAssetLiabilitiesKey(params_.asset, msg.sender)] -= params_.amount;
// Withdraw the funds from the vault to the recipient
// This will revert if the asset is not configured
(, actualAmount) = _withdrawAsset(params_.asset, params_.recipient, params_.amount);
// Validate operator solvency after state updates
_validateOperatorSolvency(params_.asset, msg.sender);
return actualAmount;
}
/// @inheritdoc IDepositManager
function getOperatorLiabilities(
IERC20 asset_,
address operator_
) external view returns (uint256) {
return _assetLiabilities[_getAssetLiabilitiesKey(asset_, operator_)];
}
/// @notice Get the key for the asset liabilities mapping
/// @dev The key is the keccak256 of the asset address and the operator address
function _getAssetLiabilitiesKey(
IERC20 asset_,
address operator_
) internal pure returns (bytes32) {
return keccak256(abi.encode(address(asset_), operator_));
}
/// @notice Validates that an operator remains solvent after a withdrawal
/// @dev This function ensures that operator assets + borrowed amount >= operator liabilities
/// This is the core solvency constraint for the DepositManager
///
/// Notes:
/// - The solvency checks assume that the value of each vault share is increasing, and will not reduce.
/// - In a situation where the assets per share reduces below 1 (at the appropriate decimal scale), the solvency check will fail.
///
/// @param asset_ The asset to validate solvency for
/// @param operator_ The operator to validate solvency for
function _validateOperatorSolvency(IERC20 asset_, address operator_) internal view {
(, uint256 depositedSharesInAssets) = getOperatorAssets(asset_, operator_);
bytes32 assetLiabilitiesKey = _getAssetLiabilitiesKey(asset_, operator_);
uint256 operatorLiabilities = _assetLiabilities[assetLiabilitiesKey];
uint256 borrowedAmount = _borrowedAmounts[assetLiabilitiesKey];
if (operatorLiabilities > depositedSharesInAssets + borrowedAmount) {
revert DepositManager_Insolvent(
address(asset_),
operatorLiabilities,
depositedSharesInAssets,
borrowedAmount
);
}
}
// ========== OPERATOR NAMES ========== //
/// @inheritdoc IDepositManager
/// @dev Note that once set, an operator name cannot be changed.
///
/// This function reverts if:
/// - The contract is not enabled
/// - The caller does not have the admin or manager role
/// - The operator's name is already set
/// - The name is already in use by another operator
/// - The operator name is empty
/// - The operator name is not exactly 3 characters long
/// - The operator name contains characters that are not a-z or 0-9
function setOperatorName(
address operator_,
string calldata name_
) external onlyEnabled onlyManagerOrAdminRole {
// Validate that the name is not already set for the operator
if (_operatorToName[operator_] != bytes3(0)) {
revert DepositManager_OperatorNameSet(operator_);
}
// Validate that the name is not empty
if (bytes(name_).length == 0) {
revert DepositManager_OperatorNameInvalid();
}
// Validate that the name contains 3 characters
if (bytes(name_).length != 3) {
revert DepositManager_OperatorNameInvalid();
}
// Validate that the characters are a-z, 0-9
{
bytes memory nameBytes = bytes(name_);
for (uint256 i = 0; i < 3; i++) {
if (bytes1(nameBytes[i]) >= 0x61 && bytes1(nameBytes[i]) <= 0x7A) {
continue; // Lowercase letter
}
if (bytes1(nameBytes[i]) >= 0x30 && bytes1(nameBytes[i]) <= 0x39) {
continue; // Number
}
revert DepositManager_OperatorNameInvalid();
}
}
/// forge-lint: disable-next-line(unsafe-typecast)
bytes3 nameBytes3 = bytes3(bytes(name_));
// Validate that the name isn't in use by another operator
if (_operatorNames[nameBytes3]) revert DepositManager_OperatorNameInUse(name_);
// Set the name
_operatorToName[operator_] = nameBytes3;
// Add to the operator names to prevent re-use
_operatorNames[nameBytes3] = true;
// Emit event
emit OperatorNameSet(operator_, name_);
}
/// @inheritdoc IDepositManager
function getOperatorName(address operator_) public view returns (string memory) {
bytes memory nameBytes = new bytes(3);
bytes3 operatorName = _operatorToName[operator_];
if (operatorName == bytes3(0)) {
return "";
}
nameBytes[0] = bytes1(operatorName[0]);
nameBytes[1] = bytes1(operatorName[1]);
nameBytes[2] = bytes1(operatorName[2]);
// Convert bytes to string
return string(nameBytes);
}
// ========== ASSET PERIOD ========== //
/// @inheritdoc IDepositManager
function isAssetPeriod(
IERC20 asset_,
uint8 depositPeriod_,
address operator_
) public view override returns (AssetPeriodStatus memory status) {
uint256 receiptTokenId = _RECEIPT_TOKEN_MANAGER.getReceiptTokenId(
address(this),
asset_,
depositPeriod_,
operator_
);
status.isConfigured = address(_assetPeriods[receiptTokenId].asset) != address(0);
status.isEnabled = _assetPeriods[receiptTokenId].isEnabled;
return status;
}
/// @inheritdoc IDepositManager
/// @dev This function reverts if:
/// - The contract is not enabled
/// - The caller does not have the admin or manager role
/// - asset_ is the zero address
/// - minimumDeposit_ > depositCap_
///
/// Notes:
/// - A limitation of the current implementation is that the vault is assumed to be monotonically-increasing in value.
/// - The pairing of the asset and vault is immutable, to prevent a governance attack on user deposits.
function addAsset(
IERC20 asset_,
IERC4626 vault_,
uint256 depositCap_,
uint256 minimumDeposit_
) external onlyEnabled onlyManagerOrAdminRole {
_addAsset(asset_, vault_, depositCap_, minimumDeposit_);
}
/// @inheritdoc IDepositManager
/// @dev This function reverts if:
/// - The contract is not enabled
/// - The caller does not have the admin or manager role
/// - asset_ is not configured
/// - The existing minimum deposit > depositCap_
function setAssetDepositCap(
IERC20 asset_,
uint256 depositCap_
) external onlyEnabled onlyManagerOrAdminRole {
_setAssetDepositCap(asset_, depositCap_);
}
/// @inheritdoc IDepositManager
/// @dev This function reverts if:
/// - The contract is not enabled
/// - The caller does not have the admin or manager role
/// - asset_ is not configured
/// - minimumDeposit_ > the existing deposit cap
function setAssetMinimumDeposit(
IERC20 asset_,
uint256 minimumDeposit_
) external onlyEnabled onlyManagerOrAdminRole {
_setAssetMinimumDeposit(asset_, minimumDeposit_);
}
/// @inheritdoc IDepositManager
/// @dev This function is only callable by the manager or admin role
///
/// This function reverts if:
/// - The contract is not enabled
/// - The caller does not have the manager or admin role
/// - The asset has not been added via addAsset()
/// - The operator is the zero address
/// - The deposit period is 0
/// - The asset/deposit period/operator combination is already configured
/// - The operator name has not been set
/// - Receipt token creation fails (invalid parameters in ReceiptTokenManager)
function addAssetPeriod(
IERC20 asset_,
uint8 depositPeriod_,
address operator_
)
external
onlyEnabled
onlyManagerOrAdminRole
onlyConfiguredAsset(asset_)
returns (uint256 receiptTokenId)
{
// Validate that the operator is not the zero address
if (operator_ == address(0)) revert DepositManager_ZeroAddress();
// Validate that the deposit period is not 0
if (depositPeriod_ == 0) revert DepositManager_OutOfBounds();
// Validate that the asset and deposit period combination is not already configured
if (isAssetPeriod(asset_, depositPeriod_, operator_).isConfigured) {
revert DepositManager_AssetPeriodExists(address(asset_), depositPeriod_, operator_);
}
// Configure the ERC6909 receipt token and asset period atomically
receiptTokenId = _setReceiptTokenData(asset_, depositPeriod_, operator_);
// Emit event
emit AssetPeriodConfigured(receiptTokenId, address(asset_), operator_, depositPeriod_);
return receiptTokenId;
}
/// @inheritdoc IDepositManager
/// @dev This function is only callable by the manager or admin role
///
/// This function reverts if:
/// - The contract is not enabled
/// - The caller does not have the manager or admin role
/// - The asset/deposit period/operator combination does not exist
/// - The asset period is already enabled
function enableAssetPeriod(
IERC20 asset_,
uint8 depositPeriod_,
address operator_
)
external
onlyEnabled
onlyManagerOrAdminRole
onlyAssetPeriodExists(asset_, depositPeriod_, operator_)
{
uint256 tokenId = _RECEIPT_TOKEN_MANAGER.getReceiptTokenId(
address(this),
asset_,
depositPeriod_,
operator_
);
if (_assetPeriods[tokenId].isEnabled) {
revert DepositManager_AssetPeriodEnabled(address(asset_), depositPeriod_, operator_);
}
_assetPeriods[tokenId].isEnabled = true;
// Emit event
emit AssetPeriodEnabled(tokenId, address(asset_), operator_, depositPeriod_);
}
/// @inheritdoc IDepositManager
/// @dev This function is only callable by the manager or admin role
///
/// This function reverts if:
/// - The contract is not enabled
/// - The caller does not have the manager or admin role
/// - The asset/deposit period/operator combination does not exist
/// - The asset period is already disabled
function disableAssetPeriod(
IERC20 asset_,
uint8 depositPeriod_,
address operator_
)
external
onlyEnabled
onlyManagerOrAdminRole
onlyAssetPeriodExists(asset_, depositPeriod_, operator_)
{
uint256 tokenId = _RECEIPT_TOKEN_MANAGER.getReceiptTokenId(
address(this),
asset_,
depositPeriod_,
operator_
);
if (!_assetPeriods[tokenId].isEnabled) {
revert DepositManager_AssetPeriodDisabled(address(asset_), depositPeriod_, operator_);
}
_assetPeriods[tokenId].isEnabled = false;
// Emit event
emit AssetPeriodDisabled(tokenId, address(asset_), operator_, depositPeriod_);
}
/// @inheritdoc IDepositManager
function getAssetPeriods() external view override returns (AssetPeriod[] memory assetPeriods) {
// Get all token IDs owned by this contract
uint256[] memory tokenIds = _ownedTokenIds.values();
// Build the array of asset periods (all owned tokens should have valid asset periods)
assetPeriods = new AssetPeriod[](tokenIds.length);
for (uint256 i = 0; i < tokenIds.length; i++) {
assetPeriods[i] = _assetPeriods[tokenIds[i]];
}
return assetPeriods;
}
/// @inheritdoc IDepositManager
function getAssetPeriod(uint256 tokenId_) public view override returns (AssetPeriod memory) {
return _assetPeriods[tokenId_];
}
/// @inheritdoc IDepositManager
function getAssetPeriod(
IERC20 asset_,
uint8 depositPeriod_,
address operator_
) public view override returns (AssetPeriod memory) {
return _assetPeriods[getReceiptTokenId(asset_, depositPeriod_, operator_)];
}
// ========== BORROWING FUNCTIONS ========== //
/// @inheritdoc IDepositManager
/// @dev Notes:
/// - This function is only callable by addresses with the deposit operator role
/// - Given a low enough amount, the actual amount withdrawn may be 0. This function will not revert in such a case.
///
/// This function reverts if:
/// - The contract is not enabled
/// - The caller does not have the deposit operator role
/// - The recipient is the zero address
/// - The asset has not been added via addAsset()
/// - The amount exceeds the operator's available borrowing capacity
/// - The operator becomes insolvent after the withdrawal (assets + borrowed < liabilities)
function borrowingWithdraw(
BorrowingWithdrawParams calldata params_
) external onlyEnabled onlyRole(ROLE_DEPOSIT_OPERATOR) returns (uint256 actualAmount) {
// Validate that the recipient is not the zero address
if (params_.recipient == address(0)) revert DepositManager_ZeroAddress();
// Validate that the asset is configured
if (!_isConfiguredAsset(params_.asset)) revert AssetManager_NotConfigured();
// Check borrowing capacity
uint256 availableCapacity = getBorrowingCapacity(params_.asset, msg.sender);
if (params_.amount > availableCapacity) {
revert DepositManager_BorrowingLimitExceeded(
address(params_.asset),
msg.sender,
params_.amount,
availableCapacity
);
}
// Withdraw the funds from the vault to the recipient
// The value returned can also be zero
(, actualAmount) = _withdrawAsset(params_.asset, params_.recipient, params_.amount);
// Update borrowed amount
// The requested amount is used, in order to avoid issues with insolvency checks
_borrowedAmounts[_getAssetLiabilitiesKey(params_.asset, msg.sender)] += params_.amount;
// Validate operator solvency after state updates
_validateOperatorSolvency(params_.asset, msg.sender);
// Emit event
emit BorrowingWithdrawal(
address(params_.asset),
msg.sender,
params_.recipient,
actualAmount
);
return actualAmount;
}
/// @inheritdoc IDepositManager
/// @dev Notes:
/// - This function is only callable by addresses with the deposit operator role
/// - This function does not check for over-payment. It is expected to be handled by the calling contract.
/// - If the actual amount repaid is greater than the maximum amount provided, updates to the state variables are capped at the maximum amount.
///
/// This function reverts if:
/// - The contract is not enabled
/// - The caller does not have the deposit operator role
/// - The asset has not been added via addAsset()
/// - The payer has not approved DepositManager to spend the asset tokens
/// - The payer has insufficient asset token balance
/// - The asset is a fee-on-transfer token
/// - Zero shares would be deposited into the vault
/// - The operator becomes insolvent after the repayment (assets + borrowed < liabilities)
function borrowingRepay(
BorrowingRepayParams calldata params_
) external onlyEnabled onlyRole(ROLE_DEPOSIT_OPERATOR) returns (uint256 actualAmount) {
// Validate that the asset is configured
if (!_isConfiguredAsset(params_.asset)) revert AssetManager_NotConfigured();
// Get the borrowing key
bytes32 borrowingKey = _getAssetLiabilitiesKey(params_.asset, msg.sender);
// Transfer funds from payer to this contract
// This takes place before any state changes to avoid ERC777 re-entrancy
// This purposefully does not check for over-payment, as it is expected to be handled by the calling contract
(actualAmount, ) = _depositAsset(
params_.asset,
params_.payer,
params_.amount,
false // Do not enforce minimum deposit
);
// Update borrowed amount
// Reduce by the actual amount, to avoid leakage
// But cap at the max amount, to avoid an underflow for other loans
_borrowedAmounts[borrowingKey] -= params_.maxAmount < actualAmount
? params_.maxAmount
: actualAmount;
// Validate operator solvency after borrowed amount change
_validateOperatorSolvency(params_.asset, msg.sender);
// Emit event
emit BorrowingRepayment(address(params_.asset), msg.sender, params_.payer, actualAmount);
return actualAmount;
}
/// @inheritdoc IDepositManager
/// @dev This function is only callable by addresses with the deposit operator role
///
/// This function reverts if:
/// - The contract is not enabled
/// - The caller does not have the deposit operator role
/// - The asset has not been added via addAsset()
/// - The amount exceeds the current borrowed amount for the operator
/// - The payer has insufficient receipt token balance
/// - The payer has not approved the caller to spend ERC6909 tokens
/// - The operator becomes insolvent after the default (assets + borrowed < liabilities)
function borrowingDefault(
BorrowingDefaultParams calldata params_
) external onlyEnabled onlyRole(ROLE_DEPOSIT_OPERATOR) {
// Validate that the asset is configured
if (!_isConfiguredAsset(params_.asset)) revert AssetManager_NotConfigured();
// Get the borrowing key
bytes32 borrowingKey = _getAssetLiabilitiesKey(params_.asset, msg.sender);
// Check that the operator is not over-paying
// This would cause accounting issues
uint256 currentBorrowed = _borrowedAmounts[borrowingKey];
if (currentBorrowed < params_.amount) {
revert DepositManager_BorrowedAmountExceeded(
address(params_.asset),
msg.sender,
params_.amount,
currentBorrowed
);
}
// Burn the receipt tokens from the payer
_RECEIPT_TOKEN_MANAGER.burn(
params_.payer,
_RECEIPT_TOKEN_MANAGER.getReceiptTokenId(
address(this),
params_.asset,
params_.depositPeriod,
msg.sender
),
params_.amount,
false
);
// Update the asset liabilities for the caller (operator)
_assetLiabilities[_getAssetLiabilitiesKey(params_.asset, msg.sender)] -= params_.amount;
// Update the borrowed amount
_borrowedAmounts[borrowingKey] -= params_.amount;
// Validate operator solvency after borrowed amount change
_validateOperatorSolvency(params_.asset, msg.sender);
// No need to update the operator shares, as the balance has already been adjusted upon withdraw/repay
// Emit event
emit BorrowingDefault(address(params_.asset), msg.sender, params_.payer, params_.amount);
}
/// @inheritdoc IDepositManager
function getBorrowedAmount(
IERC20 asset_,
address operator_
) public view returns (uint256 borrowed) {
return _borrowedAmounts[_getAssetLiabilitiesKey(asset_, operator_)];
}
/// @inheritdoc IDepositManager
function getBorrowingCapacity(
IERC20 asset_,
address operator_
) public view returns (uint256 capacity) {
uint256 operatorLiabilities = _assetLiabilities[_getAssetLiabilitiesKey(asset_, operator_)];
uint256 currentBorrowed = getBorrowedAmount(asset_, operator_);
// This is unlikely to happen, but included to avoid a revert
if (currentBorrowed >= operatorLiabilities) {
return 0;
}
return operatorLiabilities - currentBorrowed;
}
// ========== RECEIPT TOKEN FUNCTIONS ========== //
function _setReceiptTokenData(
IERC20 asset_,
uint8 depositPeriod_,
address operator_
) internal returns (uint256 tokenId) {
// Validate that the operator name is set
string memory operatorName = getOperatorName(operator_);
if (bytes(operatorName).length == 0) {
revert DepositManager_OperatorNameNotSet(operator_);
}
// Create the receipt token via the factory
tokenId = _RECEIPT_TOKEN_MANAGER.createToken(
asset_,
depositPeriod_,
operator_,
operatorName
);
// Record this token ID as owned by this contract
_ownedTokenIds.add(tokenId);
// Set the asset period data atomically
_assetPeriods[tokenId] = AssetPeriod({
isEnabled: true,
depositPeriod: depositPeriod_,
asset: address(asset_),
operator: operator_
});
return tokenId;
}
/// @inheritdoc IDepositManager
function getReceiptTokenId(
IERC20 asset_,
uint8 depositPeriod_,
address operator_
) public view override returns (uint256) {
return
_RECEIPT_TOKEN_MANAGER.getReceiptTokenId(
address(this),
asset_,
depositPeriod_,
operator_
);
}
/// @inheritdoc IDepositManager
function getReceiptTokenManager() external view override returns (IReceiptTokenManager) {
return IReceiptTokenManager(address(_RECEIPT_TOKEN_MANAGER));
}
/// @inheritdoc IDepositManager
function getReceiptTokenIds() external view override returns (uint256[] memory) {
return _ownedTokenIds.values();
}
/// @inheritdoc IDepositManager
function getReceiptToken(
IERC20 asset_,
uint8 depositPeriod_,
address operator_
) external view override returns (uint256 tokenId, address wrappedToken) {
tokenId = _RECEIPT_TOKEN_MANAGER.getReceiptTokenId(
address(this),
asset_,
depositPeriod_,
operator_
);
wrappedToken = _RECEIPT_TOKEN_MANAGER.getWrappedToken(tokenId);
return (tokenId, wrappedToken);
}
// ========== ERC165 ========== //
function supportsInterface(
bytes4 interfaceId
) public view virtual override(BaseAssetManager, PolicyEnabler) returns (bool) {
return
interfaceId == type(IDepositManager).interfaceId ||
BaseAssetManager.supportsInterface(interfaceId) ||
PolicyEnabler.supportsInterface(interfaceId);
}
// ========== ADMIN FUNCTIONS ==========
/// @notice Rescue any ERC20 token sent to this contract and send it to the TRSRY
/// @dev This function reverts if:
/// - The caller does not have the admin role
/// - token_ is a managed asset or vault
/// - token_ is the zero address
///
/// @param token_ The address of the ERC20 token to rescue
function rescue(address token_) external onlyEnabled onlyAdminRole {
// Validate that the token is not a managed asset or vault token
uint256 configuredAssetsLength = _configuredAssets.length;
for (uint256 i = 0; i < configuredAssetsLength; ) {
IERC20 asset = _configuredAssets[i];
// Prevent rescue of a configured asset or vault
if (token_ == address(asset) || token_ == _assetConfigurations[asset].vault)
revert DepositManager_CannotRescueAsset(token_);
unchecked {
i++;
}
}
// Transfer the token balance to TRSRY
// This will revert if the token is not a valid ERC20 or the zero address
uint256 balance = ERC20(token_).balanceOf(address(this));
address treasury = getModuleAddress(toKeycode("TRSRY"));
if (balance > 0 && treasury != address(0)) {
ERC20(token_).safeTransfer(treasury, balance);
emit TokenRescued(token_, balance);
}
}
}
/// forge-lint: disable-end(asm-keccak256, mixed-case-function)
"
},
"src/interfaces/IERC20.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;
// Imported from forge-std
/// @dev Interface of the ERC20 standard as defined in the EIP.
/// @dev This includes the optional name, symbol, and decimals metadata.
interface IERC20 {
/// @dev Emitted when `value` tokens are moved from one account (`from`) to another (`to`).
event Transfer(address indexed from, address indexed to, uint256 value);
/// @dev Emitted when the allowance of a `spender` for an `owner` is set, where `value`
/// is the new allowance.
event Approval(address indexed owner, address indexed spender, uint256 value);
/// @notice Returns the amount of tokens in existence.
function totalSupply() external view returns (uint256);
/// @notice Returns the amount of tokens owned by `account`.
function balanceOf(address account) external view returns (uint256);
/// @notice Moves `amount` tokens from the caller's account to `to`.
function transfer(address to, uint256 amount) external returns (bool);
/// @notice Returns the remaining number of tokens that `spender` is allowed
/// to spend on behalf of `owner`
function allowance(address owner, address spender) external view returns (uint256);
/// @notice Sets `amount` as the allowance of `spender` over the caller's tokens.
/// @dev Be aware of front-running risks: https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
function approve(address spender, uint256 amount) external returns (bool);
/// @notice Moves `amount` tokens from `from` to `to` using the allowance mechanism.
/// `amount` is then deducted from the caller's allowance.
function transferFrom(address from, address to, uint256 amount) external returns (bool);
/// @notice Returns the name of the token.
function name() external view returns (string memory);
/// @notice Returns the symbol of the token.
function symbol() external view returns (string memory);
/// @notice Returns the decimals places of the token.
function decimals() external view returns (uint8);
}
"
},
"src/policies/interfaces/deposits/IDepositManager.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;
import {IERC20} from "src/interfaces/IERC20.sol";
import {IERC4626} from "src/interfaces/IERC4626.sol";
import {IAssetManager} from "src/bases/interfaces/IAssetManager.sol";
import {IReceiptTokenManager} from "src/policies/interfaces/deposits/IReceiptTokenManager.sol";
/// @title Deposit Manager
/// @notice Defines an interface for a policy that manages deposits on behalf of other contracts. It is meant to be used by the facilities, and is not an end-user policy.
///
/// Key terms for the contract:
/// - Asset: an ERC20 asset that can be deposited into the contract
/// - Asset vault: an optional ERC4626 vault that assets are deposited into
/// - Asset period: the combination of an asset and deposit period
interface IDepositManager is IAssetManager {
// ========== EVENTS ========== //
event OperatorYieldClaimed(
address indexed asset,
address indexed depositor,
address indexed operator,
uint256 amount
);
// Asset Configuration Events
event OperatorNameSet(address indexed operator, string name);
event AssetPeriodConfigured(
uint256 indexed receiptTokenId,
address indexed asset,
address indexed operator,
uint8 depositPeriod
);
event AssetPeriodEnabled(
uint256 indexed receiptTokenId,
address indexed asset,
address indexed operator,
uint8 depositPeriod
);
event AssetPeriodDisabled(
uint256 indexed receiptTokenId,
address indexed asset,
address indexed operator,
uint8 depositPeriod
);
event TokenRescued(address indexed token, uint256 amount);
// Borrowing Events
event BorrowingWithdrawal(
address indexed asset,
address indexed operator,
address indexed recipient,
uint256 amount
);
event BorrowingRepayment(
address indexed asset,
address indexed operator,
address indexed payer,
uint256 amount
);
event BorrowingDefault(
address indexed asset,
address indexed operator,
address indexed payer,
uint256 amount
);
// ========== ERRORS ========== //
error DepositManager_InvalidParams(string reason);
/// @notice Error if the action would leave the contract insolvent (liabilities > assets + borrowed)
///
/// @param asset The address of the underlying asset
/// @param requiredAssets The quantity of asset liabilities
/// @param depositedSharesInAssets The quantity of assets that the deposited shares represent
/// @param borrowedAmount The quantity of assets that are currently borrowed
error DepositManager_Insolvent(
address asset,
uint256 requiredAssets,
uint256 depositedSharesInAssets,
uint256 borrowedAmount
);
error DepositManager_ZeroAddress();
error DepositManager_OutOfBounds();
error DepositManager_CannotRescueAsset(address token);
// Asset Configuration Errors
error DepositManager_OperatorNameNotSet(address operator);
error DepositManager_OperatorNameSet(address operator);
error DepositManager_OperatorNameInvalid();
error DepositManager_OperatorNameInUse(string name);
error DepositManager_InvalidAssetPeriod(address asset, uint8 depositPeriod, address operator);
error DepositManager_AssetPeriodExists(address asset, uint8 depositPeriod, address operator);
error DepositManager_AssetPeriodEnabled(address asset, uint8 depositPeriod, address operator);
error DepositManager_AssetPeriodDisabled(address asset, uint8 depositPeriod, address operator);
// Borrowing Errors
error DepositManager_BorrowingLimitExceeded(
address asset,
address operator,
uint256 requested,
uint256 available
);
error DepositManager_BorrowedAmountExceeded(
address asset,
address operator,
uint256 amount,
uint256 borrowed
);
// ========== STRUCTS ========== //
/// @notice Parameters for the {deposit} function
///
/// @param asset The underlying ERC20 asset
/// @param depositPeriod The deposit period, in months
/// @param depositor The depositor
/// @param amount The amount to deposit
/// @param shouldWrap Whether the receipt token should be wrapped
struct DepositParams {
IERC20 asset;
uint8 depositPeriod;
address depositor;
uint256 amount;
bool shouldWrap;
}
/// @notice Parameters for the {withdraw} function
///
/// @param asset The underlying ERC20 asset
/// @param depositPeriod The deposit period, in months
/// @param depositor The depositor that is holding the receipt tokens
/// @param recipient The recipient of the withdrawn asset
/// @param amount The amount to withdraw
/// @param isWrapped Whether the receipt token is wrapped
struct WithdrawParams {
IERC20 asset;
uint8 depositPeriod;
address depositor;
address recipient;
uint256 amount;
bool isWrapped;
}
/// @notice An asset period configuration, representing an asset and period combination
///
/// @param isEnabled Whether the asset period is enabled for new deposits
/// @param depositPeriod The deposit period, in months
/// @param asset The underlying ERC20 asset
/// @param operator The operator that can issue this receipt token
struct AssetPeriod {
bool isEnabled;
uint8 depositPeriod;
address asset;
address operator;
}
/// @notice Status of an asset period
///
/// @param isConfigured Whether the asset period is configured
/// @param isEnabled Whether the asset period is enabled for new deposits
struct AssetPeriodStatus {
bool isConfigured;
bool isEnabled;
}
/// @notice Parameters for borrowing withdrawal operations
///
/// @param asset The underlying ERC20 asset
/// @param recipient The recipient of the borrowed funds
/// @param amount The amount to borrow
struct BorrowingWithdrawParams {
IERC20 asset;
address recipient;
uint256 amount;
}
/// @notice Parameters for borrowing repayment operations
///
/// @param asset The underlying ERC20 asset
/// @param payer The address making the repayment
/// @param amount The amount of principal to repay
/// @param maxAmount The maximum amount that can be repaid
struct BorrowingRepayParams {
IERC20 asset;
address payer;
uint256 amount;
uint256 maxAmount;
}
/// @notice Parameters for borrowing default operations
///
/// @param asset The underlying ERC20 asset
/// @param depositPeriod The deposit period, in months
/// @param payer The address making the default
/// @param amount The amount to default
struct BorrowingDefaultParams {
IERC20 asset;
uint8 depositPeriod;
address payer;
uint256 amount;
}
// ========== BORROWING FUNCTIONS ========== //
/// @notice Borrows funds from deposits
/// @dev The implementing contract is expected to handle the following:
/// - Validating that the caller has the correct role
/// - Validating borrowing limits and capacity
/// - Transferring the underlying asset from the contract to the recipient
/// - Updating borrowing state
/// - Checking solvency
///
/// @param params_ The parameters for the borrowing withdrawal
/// @return actualAmount The quantity of underlying assets transferred to the recipient
function borrowingWithdraw(
BorrowingWithdrawParams calldata params_
) external returns (uint256 actualAmount);
/// @notice Repays borrowed funds
/// @dev The implementing contract is expected to handle the following:
/// - Validating that the caller has the correct role
/// - Transferring the underlying asset from the payer to the contract
/// - Updating borrowing state
/// - Checking solvency
///
/// @param params_ The parameters for the borrowing repayment
/// @return actualAmount The quantity of underlying assets received from the payer
function borrowingRepay(
BorrowingRepayParams calldata params_
) external returns (uint256 actualAmount);
/// @notice Defaults on a borrowed amount
/// @dev The implementing contract is expected to handle the following:
/// - Validating that the caller has the correct role
/// - Burning the receipt tokens from the payer for the default amount
/// - Updating borrowing state
/// - Updating liabilities
function borrowingDefault(BorrowingDefaultParams calldata params_) external;
/// @notice Gets the current borrowed amount for an operator
///
/// @param asset_ The address of the underlying asset
/// @param operator_ The address of the operator
/// @return borrowed The current borrowed amount for the operator
function getBorrowedAmount(
IERC20 asset_,
address operator_
) external view returns (uint256 borrowed);
/// @notice Gets the available borrowing capacity for an operator
///
/// @param asset_ The address of the underlying asset
/// @param operator_ The address of the operator
/// @return capacity The available borrowing capacity for the operator
function getBorrowingCapacity(
IERC20 asset_,
address operator_
) external view returns (uint256 capacity);
// ========== DEPOSIT/WITHDRAW FUNCTIONS ========== //
/// @notice Deposits the given amount of the underlying asset in exchange for a receipt token
/// @dev The implementing contract is expected to handle the following:
/// - Validating that the caller has the correct role
/// - Transferring the underlying asset from the depositor to the contract
/// - Minting the receipt token to the depositor
/// - Updating the amount of deposited funds
///
/// @param params_ The parameters for the deposit
/// @return receiptTokenId The ID of the receipt token
/// @return actualAmount The quantity of receipt tokens minted to the depositor
function deposit(
DepositParams calldata params_
) external returns (uint256 receiptTokenId, uint256 actualAmount);
/// @notice Returns the maximum yield that can be claimed for an asset and operator pair
///
/// @param asset_ The address of the underlying asset
/// @param operator_ The address of the operator
/// @return yieldAssets The amount of yield that can be claimed
function maxClaimYield(
IERC20 asset_,
address operator_
) external view returns (uint256 yieldAssets);
/// @notice Claims the yield from the underlying asset
/// This does not burn receipt tokens, but should reduce the amount of shares the caller has in the vault.
/// @dev The implementing contract is expected to handle the following:
/// - Validating that the caller has the correct role
/// - Transferring the underlying asset from the contract to the recipient
/// - Updating the amount of deposited funds
/// - Checking solvency
///
/// @param asset_ The address of the underlying asset
/// @param recipient_ The recipient of the claimed yield
/// @param amount_ The amount to claim yield for
/// @return actualAmount The quantity of underlying assets transferred to the recipient
function claimYield(
IERC20 asset_,
address recipient_,
uint256 amount_
) external returns (uint256 actualAmount);
/// @notice Withdraws the given amount of the underlying asset
/// @dev The implementing contract is expected to handle the following:
/// - Validating that the caller has the correct role
/// - Burning the receipt token
/// - Transferring the underlying asset from the contract to the recipient
/// - Updating the amount of deposited funds
///
/// @param params_ The parameters for the withdrawal
/// @return actualAmount The quantity of underlying assets transferred to the recipient
function withdraw(WithdrawParams calldata params_) external returns (uint256 actualAmount);
/// @notice Returns the liabilities for an asset and operator pair
///
/// @param asset_ The address of the underlying asset
/// @param operator_ The address of the operator
/// @return liabilities The quantity of assets that the contract is custodying for the operator's depositors
function getOperatorLiabilities(
IERC20 asset_,
address operator_
) external view returns (uint256 liabilities);
// ========== OPERATOR NAMES ========== //
/// @notice Sets the name of an operator. This is included in the name and symbol of receipt tokens.
/// @dev The implementing contract is expected to handle the following:
/// - Validating that the caller has the correct role
/// - Setting the operator name
/// - Emitting an event
function setOperatorName(address operator_, string calldata name_) external;
/// @notice Returns the name of an operator
///
/// @param operator_ The address of the operator
/// @return name The name of the operator or an empty string
function getOperatorName(address operator_) external view returns (string memory name);
// ========== DEPOSIT CONFIGURATIONS ========== //
/// @notice Adds a new asset
/// @dev The implementing contract is expected to handle the following:
/// - Validating that the caller has the correct role
/// - Configuring the asset
/// - Emitting an event
///
/// @param asset_ The address of the underlying asset
/// @param vault_ The address of the ERC4626 vault to deposit the asset into (or the zero address)
/// @param depositCap_ The deposit cap of the asset
/// @param minimumDeposit_ The minimum deposit amount for the asset
function addAsset(
IERC20 asset_,
IERC4626 vault_,
uint256 depositCap_,
uint256 minimumDeposit_
) external;
/// @notice Sets the deposit cap for an asset
/// @dev The implementing contract is expected to handle the following:
/// - Validating that the caller has the correct role
/// - Setting the deposit cap for the asset
/// - Emitting an event
///
/// @param asset_ The address of the underlying asset
/// @param depositCap_ The deposit cap to set for the asset
function setAssetDepositCap(IERC20 asset_, uint256 depositCap_) external;
/// @notice Sets the minimum deposit for an asset
/// @dev The implementing contract is expected to handle the following:
/// - Validating that the caller has the correct role
/// - Setting the minimum deposit for the asset
/// - Emitting an event
///
/// @param asset_ The address of the underlying asset
/// @param minimumDeposit_ The minimum deposit to set for the asset
function setAssetMinimumDeposit(IERC20 asset_, uint256 minimumDeposit_) external;
/// @notice Adds a new asset period
/// @dev The implementing contract is expected to handle the following:
/// - Validating that the caller has the correct role
/// - Creating a new receipt token
/// - Emitting an event
///
/// @param asset_ The address of the underlying asset
/// @param depositPeriod_ The deposit period, in months
/// @param operator_ The address of the operator
/// @return receiptTokenId The ID of the new receipt token
function addAssetPeriod(
IERC20 asset_,
uint8 depositPeriod_,
address operator_
) external returns (uint256 receiptTokenId);
/// @notice Disables an asset period, which prevents new deposits
/// @dev The implementing contract is expected to handle the following:
/// - Validating that the caller has the correct role
/// - Disabling the asset period
/// - Emitting an event
///
/// @param asset_ The address of the underlying asset
/// @param depositPeriod_ The deposit period, in months
/// @param operator_ The address of the operator
function disableAssetPeriod(IERC20 asset_, uint8 depositPeriod_, address operator_) external;
/// @notice Enables an asset period, which allows new deposits
/// @dev The implementing contract is expected to handle the following:
/// - Validating that the caller has the correct role
/// - Enabling the asset period
/// - Emitting an event
///
/// @param asset_ The address of the underlying asset
/// @param depositPeriod_ The deposit period, in months
/// @param operator_ The address of the operator
function enableAssetPeriod(IERC20 asset_, uint8 depositPeriod_, address operator_) external;
/// @notice Returns the asset period for an asset, period and operator
///
/// @param asset_ The address of the underlying asset
/// @param depositPeriod_ The deposit period, in months
/// @param operator_ The address of the operator
/// @return configuration The asset period
function getAssetPeriod(
IERC20 asset_,
uint8 depositPeriod_,
address operator_
) external view returns (AssetPeriod memory configuration);
/// @notice Returns the asset period from a receipt token ID
///
/// @param tokenId_ The ID of the receipt token
/// @return configuration The asset period
function getAssetPeriod(
uint256 tokenId_
) external view returns (AssetPeriod memory configuration);
/// @notice Returns whether a deposit asset, period and operator combination are configured
/// @dev A asset period that is disabled will not accept further deposits
///
/// @param asset_ The address of the underlying asset
/// @param depositPeriod_ The deposit period, in months
/// @param operator_ The address of the operator
/// @return status The status of the asset period
function isAssetPeriod(
IERC20 asset_,
uint8 depositPeriod_,
address operator_
) external view returns (AssetPeriodStatus memory status);
/// @notice Gets all configured asset periods
///
/// @return assetPeriods Array of configured asset periods
function getAssetPeriods() external view returns (AssetPeriod[] memory assetPeriods);
// ========== RECEIPT TOKEN FUNCTIONS ========== //
/// @notice Returns the ID of the receipt token for an asset period and operator
/// @dev The ID returned is not a guarantee that the asset period is configured or enabled. {isAssetPeriod} should be used for that purpose.
///
/// @param asset_ The address of the underlying asset
/// @param depositPeriod_ The deposit period, in months
/// @param operator_ The address of the operator
/// @return receiptTokenId The ID of the receipt token
function getReceiptTokenId(
IERC20 asset_,
uint8 depositPeriod_,
address operator_
) external view returns (uint256 receiptTokenId);
/// @notice Convenience function that returns both receipt token ID and wrapped token address
///
/// @param asset_ The asset contract
/// @param depositPeriod_ The deposit period in months
/// @param operator_ The operator address
/// @return tokenId The receipt token ID
/// @return wrappedToken The address of the wrapped ERC20 token (0x0 if not created yet)
function getReceiptToken(
IERC20 asset_,
uint8 depositPeriod_,
address operator_
) external view returns (uint256 tokenId, address wrappedToken);
/// @notice Gets the receipt token manager
///
/// @return manager The receipt token manager contract
function getReceiptTokenManager() external view returns (IReceiptTokenManager manager);
/// @notice Gets all receipt token IDs owned by this contract
///
/// @return tokenIds Array of receipt token IDs
function getReceiptTokenIds() external view returns (uint256[] memory tokenIds);
}
"
},
"src/policies/interfaces/deposits/IReceiptTokenManager.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;
import {IERC20} from "src/interfaces/IERC20.sol";
import {IERC6909} from "@openzeppelin-5.3.0/interfaces/draft-IERC6909.sol";
import {IERC6909Wrappable} from "src/interfaces/IERC6909Wrappable.sol";
/// @title IReceiptTokenManager
/// @notice Interface for the contract that creates and manages receipt tokens
interface IReceiptTokenManager is IERC6909, IERC6909Wrappable {
// ========== EVENTS ========== //
event TokenCreated(
uint256 indexed tokenId,
address indexed owner,
address indexed asset,
uint8 depositPeriod,
address operator
);
// ========== ERRORS ========== //
error ReceiptTokenManager_TokenExists(uint256 tokenId);
error ReceiptTokenManager_NotOwner(address caller, address owner);
error ReceiptTokenManager_InvalidParams(string reason);
// ========== FUNCTIONS ========== //
/// @notice Creates a new receipt token
/// @dev The caller (msg.sender) becomes the owner of the token for security
///
/// @param asset_ The underlying asset
/// @param depositPeriod_ The deposit period
/// @param operator_ T
Submitted on: 2025-11-07 16:14:09
Comments
Log in to comment.
No comments yet.