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/ConvertibleDepositFacility.sol": {
"content": "// SPDX-License-Identifier: AGPL-3.0
/// forge-lint: disable-start(mixed-case-function, mixed-case-variable)
pragma solidity >=0.8.20;
// Interfaces
import {IERC20} from "src/interfaces/IERC20.sol";
import {IERC165} from "@openzeppelin-5.3.0/interfaces/IERC165.sol";
import {IConvertibleDepositFacility} from "src/policies/interfaces/deposits/IConvertibleDepositFacility.sol";
import {IDepositManager} from "src/policies/interfaces/deposits/IDepositManager.sol";
import {IDepositPositionManager} from "src/modules/DEPOS/IDepositPositionManager.sol";
import {IPeriodicTask} from "src/interfaces/IPeriodicTask.sol";
// Libraries
import {FullMath} from "src/libraries/FullMath.sol";
// Bophades
import {Keycode, Permissions, Policy, toKeycode} from "src/Kernel.sol";
import {ROLESv1} from "src/modules/ROLES/ROLES.v1.sol";
import {MINTRv1} from "src/modules/MINTR/MINTR.v1.sol";
import {TRSRYv1} from "src/modules/TRSRY/TRSRY.v1.sol";
import {DEPOSv1} from "src/modules/DEPOS/DEPOS.v1.sol";
import {HEART_ROLE} from "src/policies/utils/RoleDefinitions.sol";
import {BaseDepositFacility} from "src/policies/deposits/BaseDepositFacility.sol";
/// @title Convertible Deposit Facility
/// @notice Implementation of the {IConvertibleDepositFacility} interface
/// It is a general-purpose contract that can be used to create, mint, convert, redeem, and reclaim receipt tokens
contract ConvertibleDepositFacility is
BaseDepositFacility,
IConvertibleDepositFacility,
IPeriodicTask
{
// ========== CONSTANTS ========== //
bytes32 public constant ROLE_AUCTIONEER = "cd_auctioneer";
// ========== STATE VARIABLES ========== //
/// @notice The MINTR module.
MINTRv1 public MINTR;
uint256 internal constant _OHM_SCALE = 1e9;
// ========== SETUP ========== //
constructor(
address kernel_,
address depositManager_
) BaseDepositFacility(kernel_, depositManager_) {
// Disabled by default by PolicyEnabler
}
/// @inheritdoc Policy
function configureDependencies() external override returns (Keycode[] memory dependencies) {
dependencies = new Keycode[](4);
dependencies[0] = toKeycode("TRSRY");
dependencies[1] = toKeycode("MINTR");
dependencies[2] = toKeycode("ROLES");
dependencies[3] = toKeycode("DEPOS");
TRSRY = TRSRYv1(getModuleAddress(dependencies[0]));
MINTR = MINTRv1(getModuleAddress(dependencies[1]));
ROLES = ROLESv1(getModuleAddress(dependencies[2]));
DEPOS = DEPOSv1(getModuleAddress(dependencies[3]));
// Validate that the OHM scale is the same
uint256 ohmScale = 10 ** uint256(MINTR.ohm().decimals());
if (ohmScale != _OHM_SCALE) revert CDF_InvalidArgs("OHM decimals");
}
/// @inheritdoc Policy
function requestPermissions()
external
view
override
returns (Permissions[] memory permissions)
{
Keycode mintrKeycode = toKeycode("MINTR");
Keycode deposKeycode = toKeycode("DEPOS");
permissions = new Permissions[](5);
permissions[0] = Permissions({
keycode: mintrKeycode,
funcSelector: MINTR.increaseMintApproval.selector
});
permissions[1] = Permissions({keycode: mintrKeycode, funcSelector: MINTR.mintOhm.selector});
permissions[2] = Permissions({keycode: deposKeycode, funcSelector: DEPOS.mint.selector});
permissions[3] = Permissions({
keycode: deposKeycode,
funcSelector: DEPOS.setRemainingDeposit.selector
});
permissions[4] = Permissions({keycode: deposKeycode, funcSelector: DEPOS.split.selector});
}
function VERSION() external pure returns (uint8 major, uint8 minor) {
major = 1;
minor = 0;
return (major, minor);
}
// ========== MINT ========== //
/// @inheritdoc IConvertibleDepositFacility
/// @dev This function reverts if:
/// - The caller does not have the ROLE_AUCTIONEER role
/// - The contract is not enabled
/// - The asset and period are not supported
function createPosition(
CreatePositionParams calldata params_
)
external
onlyRole(ROLE_AUCTIONEER)
nonReentrant
onlyEnabled
returns (uint256 positionId, uint256 receiptTokenId, uint256 actualAmount)
{
// Deposit the asset into the deposit manager
// This will validate that the asset is supported, and mint the receipt token
(receiptTokenId, actualAmount) = DEPOSIT_MANAGER.deposit(
IDepositManager.DepositParams({
asset: params_.asset,
depositPeriod: params_.periodMonths,
depositor: params_.depositor,
amount: params_.amount,
shouldWrap: params_.wrapReceipt
})
);
// Create a new position in the DEPOS module
positionId = DEPOS.mint(
IDepositPositionManager.MintParams({
owner: params_.depositor,
asset: address(params_.asset),
periodMonths: params_.periodMonths,
remainingDeposit: actualAmount,
conversionPrice: params_.conversionPrice,
expiry: uint48(block.timestamp + uint48(params_.periodMonths) * 30 days),
wrapPosition: params_.wrapPosition,
additionalData: ""
})
);
// Emit an event
emit CreatedDeposit(
address(params_.asset),
params_.depositor,
positionId,
params_.periodMonths,
actualAmount
);
return (positionId, receiptTokenId, actualAmount);
}
/// @inheritdoc IConvertibleDepositFacility
function deposit(
IERC20 asset_,
uint8 periodMonths_,
uint256 amount_,
bool wrapReceipt_
) external nonReentrant onlyEnabled returns (uint256 receiptTokenId, uint256 actualAmount) {
// Deposit the asset into the deposit manager and get the receipt token back
// This will revert if the asset is not supported
(receiptTokenId, actualAmount) = DEPOSIT_MANAGER.deposit(
IDepositManager.DepositParams({
asset: asset_,
depositPeriod: periodMonths_,
depositor: msg.sender,
amount: amount_,
shouldWrap: wrapReceipt_
})
);
return (receiptTokenId, actualAmount);
}
// ========== CONVERTIBLE DEPOSIT ACTIONS ========== //
/// @notice Determines the conversion output
///
/// @param depositor_ The depositor of the position
/// @param positionId_ The ID of the position
/// @param amount_ The amount of receipt tokens to convert
/// @param previousAsset_ Used to validate that the asset is the same across positions (zero if the first position)
/// @param previousPeriodMonths_ Used to validate that the period is the same across positions (0 if the first position)
/// @return convertedTokenOut The amount of converted tokens
/// @return currentAsset The asset of the current position
/// @return currentPeriodMonths The period of the current position
function _previewConvert(
address depositor_,
uint256 positionId_,
uint256 amount_,
address previousAsset_,
uint8 previousPeriodMonths_
)
internal
view
returns (uint256 convertedTokenOut, address currentAsset, uint8 currentPeriodMonths)
{
// Validate that the position is valid
// This will revert if the position is not valid
DEPOSv1.Position memory position = DEPOS.getPosition(positionId_);
// Validate that the depositor is the owner of the position
if (position.owner != depositor_) revert CDF_NotOwner(positionId_);
// Validate that the position has not expired
if (block.timestamp >= position.expiry) revert CDF_PositionExpired(positionId_);
// Validate that the deposit amount is not greater than the remaining deposit
if (amount_ > position.remainingDeposit) revert CDF_InvalidAmount(positionId_, amount_);
// Validate that the position supports conversion
if (position.operator != address(this)) revert CDF_Unsupported(positionId_);
// Set the asset, or validate
currentAsset = position.asset;
currentPeriodMonths = position.periodMonths;
if (previousAsset_ == address(0)) {
// Validate that the asset is supported
if (
!DEPOSIT_MANAGER
.isAssetPeriod(IERC20(currentAsset), currentPeriodMonths, address(this))
.isConfigured
) revert CDF_InvalidToken(positionId_, currentAsset, currentPeriodMonths);
} else if (previousAsset_ != currentAsset || previousPeriodMonths_ != currentPeriodMonths) {
revert CDF_InvalidArgs("multiple assets");
}
// The deposit and receipt token have the same decimals, so either can be used
convertedTokenOut = FullMath.mulDiv(
amount_, // Scale: deposit token
_OHM_SCALE,
position.conversionPrice // Scale: deposit token
);
return (convertedTokenOut, currentAsset, currentPeriodMonths);
}
/// @inheritdoc IConvertibleDepositFacility
/// @dev This function reverts if:
/// - The contract is not enabled
/// - The length of the positionIds_ array does not match the length of the amounts_ array
/// - depositor_ is not the owner of all of the positions
/// - Any position is not valid
/// - Any position is not a supported asset
/// - Any position has a different asset or deposit period
/// - Any position has reached the conversion expiry
/// - Any conversion amount is greater than the remaining deposit
/// - The amount of deposits to convert is 0
/// - The converted amount is 0
function previewConvert(
address depositor_,
uint256[] memory positionIds_,
uint256[] memory amounts_
) external view onlyEnabled returns (uint256 receiptTokenIn, uint256 convertedTokenOut) {
// Make sure the lengths of the arrays are the same
if (positionIds_.length != amounts_.length) revert CDF_InvalidArgs("array length");
address asset;
uint8 periodMonths;
for (uint256 i; i < positionIds_.length; ++i) {
uint256 positionId = positionIds_[i];
uint256 amount = amounts_[i];
receiptTokenIn += amount;
(
uint256 previewConvertOut,
address currentAsset,
uint8 currentPeriodMonths
) = _previewConvert(depositor_, positionId, amount, asset, periodMonths);
convertedTokenOut += previewConvertOut;
asset = currentAsset;
periodMonths = currentPeriodMonths;
}
// If the amount is 0, revert
if (receiptTokenIn == 0) revert CDF_InvalidArgs("amount");
// If the converted amount is 0, revert
if (convertedTokenOut == 0) revert CDF_InvalidArgs("converted amount");
return (receiptTokenIn, convertedTokenOut);
}
/// @inheritdoc IConvertibleDepositFacility
/// @dev This function reverts if:
/// - The contract is not enabled
/// - No positions are provided
/// - The length of the positionIds_ array does not match the length of the amounts_ array
/// - The caller is not the owner of all of the positions
/// - Any position is not valid
/// - Any position is not a supported asset
/// - Any position has a different asset or deposit period
/// - Any position has reached the conversion expiry
/// - Any position has a conversion amount greater than the remaining deposit
/// - The amount of deposits to convert is 0
/// - The converted amount is 0
function convert(
uint256[] memory positionIds_,
uint256[] memory amounts_,
bool wrappedReceipt_
)
external
nonReentrant
onlyEnabled
returns (uint256 receiptTokenIn, uint256 convertedTokenOut)
{
if (positionIds_.length == 0) revert CDF_InvalidArgs("no positions");
// Make sure the lengths of the arrays are the same
if (positionIds_.length != amounts_.length) revert CDF_InvalidArgs("array length");
address asset;
uint8 periodMonths;
for (uint256 i; i < positionIds_.length; ++i) {
uint256 positionId = positionIds_[i];
uint256 depositAmount = amounts_[i];
receiptTokenIn += depositAmount;
(
uint256 previewConvertOut,
address currentAsset,
uint8 currentPeriodMonths
) = _previewConvert(msg.sender, positionId, depositAmount, asset, periodMonths);
convertedTokenOut += previewConvertOut;
asset = currentAsset;
periodMonths = currentPeriodMonths;
// Update the position
DEPOS.setRemainingDeposit(
positionId,
DEPOS.getPosition(positionId).remainingDeposit - depositAmount
);
}
// Withdraw the underlying asset and deposit into the treasury
// The actual amount withdrawn may differ from `receiptTokenIn` by a few wei,
// but will not materially affect the amount of OHM that is minted when converting.
// Additionally, given that the amount is composed of multiple positions
// (each with potentially different conversion prices), it is not trivial to
// re-calculate `convertedTokenOut` with the actual amount.
DEPOSIT_MANAGER.withdraw(
IDepositManager.WithdrawParams({
asset: IERC20(asset),
depositPeriod: periodMonths,
depositor: msg.sender,
recipient: address(TRSRY),
amount: receiptTokenIn,
isWrapped: wrappedReceipt_
})
);
// Mint OHM to the owner/caller
// No need to check if `convertedTokenOut` is 0, as MINTR will revert
MINTR.increaseMintApproval(address(this), convertedTokenOut);
MINTR.mintOhm(msg.sender, convertedTokenOut);
// Emit event
emit ConvertedDeposit(asset, msg.sender, periodMonths, receiptTokenIn, convertedTokenOut);
return (receiptTokenIn, convertedTokenOut);
}
// ========== YIELD ========== //
/// @inheritdoc IConvertibleDepositFacility
/// @dev This returns the value from DepositManager.maxClaimYield(), which is a theoretical value.
function previewClaimYield(IERC20 asset_) public view returns (uint256 yieldAssets) {
yieldAssets = DEPOSIT_MANAGER.maxClaimYield(asset_, address(this));
return yieldAssets;
}
/// @inheritdoc IConvertibleDepositFacility
function claimYield(IERC20 asset_) public returns (uint256) {
// Determine the yield
uint256 previewedYield = previewClaimYield(asset_);
return claimYield(asset_, previewedYield);
}
/// @inheritdoc IConvertibleDepositFacility
/// @dev This function mainly serves as a backup for claiming protocol yield, in case the max yield cannot be claimed.
function claimYield(IERC20 asset_, uint256 amount_) public returns (uint256) {
// If disabled, don't do anything
if (!isEnabled) return 0;
// Skip if there is no yield to claim
if (amount_ == 0) return 0;
// Claim the yield
// This will revert if the asset is not supported, or the receipt token becomes insolvent
// The value returned can also be zero
uint256 actualYield = DEPOSIT_MANAGER.claimYield(asset_, address(TRSRY), amount_);
// Emit the event
emit ClaimedYield(address(asset_), actualYield);
return actualYield;
}
/// @inheritdoc IConvertibleDepositFacility
function claimAllYield() external {
// Get the assets
IERC20[] memory assets = DEPOSIT_MANAGER.getConfiguredAssets();
// Iterate over the deposit assets
for (uint256 i; i < assets.length; ++i) {
// Claim the yield
claimYield(assets[i]);
}
}
// ========== VIEW FUNCTIONS ========== //
/// @inheritdoc IConvertibleDepositFacility
function convertedToken() external view returns (address) {
return address(MINTR.ohm());
}
// ========== PERIODIC TASKS ========== //
/// @inheritdoc IPeriodicTask
/// @dev This function reverts if:
/// - The caller is not authorized
///
/// Notes:
/// - If disabled, nothing is done
/// - This will attempt to claim yield for all configured assets
/// - If the claimAllYield function fails for any asset, an event will be emitted instead of reverting
function execute() external onlyRole(HEART_ROLE) {
// Don't do anything if disabled
if (!isEnabled) return;
try this.claimAllYield() {
// Do nothing
} catch {
// This avoids the periodic task from failing loudly, as the claimAllYield function is not critical to the system
emit ClaimAllYieldFailed();
}
}
// ========== ERC165 ========== //
function supportsInterface(
bytes4 interfaceId
) public view virtual override(BaseDepositFacility, IPeriodicTask) returns (bool) {
return
interfaceId == type(IERC165).interfaceId ||
interfaceId == type(IConvertibleDepositFacility).interfaceId ||
interfaceId == type(IPeriodicTask).interfaceId ||
super.supportsInterface(interfaceId);
}
}
/// forge-lint: disable-end(mixed-case-function, mixed-case-variable)
"
},
"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);
}
"
},
"dependencies/openzeppelin-new-5.3.0/contracts/interfaces/IERC165.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC165.sol)
pragma solidity ^0.8.20;
import {IERC165} from "../utils/introspection/IERC165.sol";
"
},
"src/policies/interfaces/deposits/IConvertibleDepositFacility.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
// Interfaces
import {IERC20} from "src/interfaces/IERC20.sol";
/// @title IConvertibleDepositFacility
/// @notice Interface for a contract that can perform functions related to convertible deposit (CD) tokens
interface IConvertibleDepositFacility {
// ========== EVENTS ========== //
event CreatedDeposit(
address indexed asset,
address indexed depositor,
uint256 indexed positionId,
uint8 periodMonths,
uint256 depositAmount
);
event ConvertedDeposit(
address indexed asset,
address indexed depositor,
uint8 periodMonths,
uint256 depositAmount,
uint256 convertedAmount
);
event ClaimedYield(address indexed asset, uint256 amount);
event ClaimAllYieldFailed();
// ========== ERRORS ========== //
error CDF_InvalidArgs(string reason_);
error CDF_NotOwner(uint256 positionId_);
error CDF_PositionExpired(uint256 positionId_);
error CDF_InvalidAmount(uint256 positionId_, uint256 amount_);
error CDF_InvalidToken(uint256 positionId_, address token_, uint8 periodMonths_);
error CDF_Unsupported(uint256 positionId_);
// ========== DATA STRUCTURES ========== //
/// @notice Parameters for the {createPosition} function
///
/// @param asset The address of the asset
/// @param periodMonths The period of the deposit
/// @param depositor The address to create the position for
/// @param amount The amount of asset to deposit
/// @param conversionPrice The amount of asset tokens required to receive 1 OHM (scale: asset token decimals)
/// @param wrapPosition Whether the position should be wrapped
/// @param wrapReceipt Whether the receipt token should be wrapped
struct CreatePositionParams {
IERC20 asset;
uint8 periodMonths;
address depositor;
uint256 amount;
uint256 conversionPrice;
bool wrapPosition;
bool wrapReceipt;
}
// ========== CONVERTIBLE DEPOSIT ACTIONS ========== //
/// @notice Creates a convertible deposit position
/// @dev The implementing contract is expected to handle the following:
/// - Validating that the asset is supported
/// - Validating that the caller has the correct role
/// - Depositing the asset
/// - Minting the receipt token
/// - Creating a new position in the DEPOS module
/// - Emitting an event
///
/// @param params_ The parameters for the position creation
/// @return positionId The ID of the new position
function createPosition(
CreatePositionParams calldata params_
) external returns (uint256 positionId, uint256 receiptTokenId, uint256 actualAmount);
/// @notice Deposits the given amount of the underlying asset in exchange for a receipt token. This function can be used to mint additional receipt tokens on a 1:1 basis, without creating a new position.
///
/// @param asset_ The address of the asset
/// @param periodMonths_ The period of the deposit
/// @param amount_ The amount of asset to deposit
/// @param wrapReceipt_ Whether the receipt token should be wrapped
/// @return receiptTokenId The ID of the receipt token
/// @return actualAmount The quantity of receipt tokens minted to the depositor
function deposit(
IERC20 asset_,
uint8 periodMonths_,
uint256 amount_,
bool wrapReceipt_
) external returns (uint256 receiptTokenId, uint256 actualAmount);
/// @notice Converts receipt tokens to OHM before conversion expiry
/// @dev The implementing contract is expected to handle the following:
/// - Validating that the caller is the owner of all of the positions
/// - Validating that the token in the position is a supported receipt token
/// - Validating that all of the positions are valid
/// - Validating that the conversion expiry for all of the positions has not passed
/// - Burning the receipt tokens
/// - Minting OHM to `account_`
/// - Transferring the deposit token to the treasury
/// - Emitting an event
///
/// @param positionIds_ An array of position ids that will be converted
/// @param amounts_ An array of amounts of receipt tokens to convert
/// @param wrappedReceipt_ Whether the receipt tokens to use are wrapped as ERC20s
/// @return receiptTokenIn The total amount of receipt tokens converted
/// @return convertedTokenOut The amount of OHM minted during conversion
function convert(
uint256[] memory positionIds_,
uint256[] memory amounts_,
bool wrappedReceipt_
) external returns (uint256 receiptTokenIn, uint256 convertedTokenOut);
/// @notice Preview the amount of receipt tokens and OHM that would be converted
/// @dev The implementing contract is expected to handle the following:
/// - Validating that `account_` is the owner of all of the positions
/// - Validating that token in the position is a supported receipt token
/// - Validating that all of the positions are valid
/// - Validating that the conversion expiry for all of the positions has not passed
/// - Returning the total amount of receipt tokens and OHM that would be converted
///
/// @param account_ The address to preview the conversion for
/// @param positionIds_ An array of position ids that will be converted
/// @param amounts_ An array of amounts of receipt tokens to convert
/// @return receiptTokenIn The total amount of receipt tokens converted
/// @return convertedTokenOut The amount of OHM minted during conversion
function previewConvert(
address account_,
uint256[] memory positionIds_,
uint256[] memory amounts_
) external view returns (uint256 receiptTokenIn, uint256 convertedTokenOut);
// ========== YIELD ========== //
/// @notice Preview the amount of yield that would be claimed for the given asset
///
/// @param asset_ The address of the asset
/// @return assets The amount of assets that would be claimed
function previewClaimYield(IERC20 asset_) external view returns (uint256 assets);
/// @notice Claim the yield accrued for the given asset
///
/// @param asset_ The address of the asset
/// @return assets The amount of assets that were claimed
function claimYield(IERC20 asset_) external returns (uint256 assets);
/// @notice Claim the yield accrued for the given asset
///
/// @param asset_ The address of the asset
/// @param amount_ The amount to claim
/// @return assets The amount of assets that were claimed
function claimYield(IERC20 asset_, uint256 amount_) external returns (uint256 assets);
/// @notice Claim the yield accrued for all assets and deposit periods
function claimAllYield() external;
// ========== VIEW FUNCTIONS ========== //
/// @notice The address of the token that is converted to by the facility
function convertedToken() external view returns (address);
}
"
},
"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/modules/DEPOS/IDepositPositionManager.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity >=0.8.15;
/// @title IDepositPositionManager
/// @notice This interface defines the functions for the DEPOS module.
/// The objective of this module is to track the terms of a deposit position.
interface IDepositPositionManager {
// ========== DATA STRUCTURES ========== //
/// @notice Data structure for the terms of a deposit position
///
/// @param operator Address of the operator/creator of the position
/// @param owner Address of the owner of the position
/// @param asset Address of the asset
/// @param periodMonths The period of the deposit
/// @param remainingDeposit Amount of reserve tokens remaining to be converted
/// @param conversionPrice The amount of asset tokens required to receive 1 OHM (scale: asset token decimals)
/// @param expiry Timestamp of the position expiry
/// @param wrapped Whether the term is wrapped
/// @param additionalData Additional data for the position
struct Position {
address operator;
address owner;
address asset;
uint8 periodMonths;
uint256 remainingDeposit;
uint256 conversionPrice;
uint48 expiry;
bool wrapped;
bytes additionalData;
}
/// @notice Parameters for the {mint} function
///
/// @param owner Address of the owner of the position
/// @param asset Address of the asset
/// @param periodMonths The period of the deposit
/// @param remainingDeposit Amount of reserve tokens remaining to be converted
/// @param conversionPrice The amount of asset tokens required to receive 1 OHM (scale: asset token decimals)
/// @param expiry Timestamp of the position expiry
/// @param wrapPosition Whether the position should be wrapped
/// @param additionalData Additional data for the position
struct MintParams {
address owner;
address asset;
uint8 periodMonths;
uint256 remainingDeposit;
uint256 conversionPrice;
uint48 expiry;
bool wrapPosition;
bytes additionalData;
}
// ========== EVENTS ========== //
/// @notice Emitted when a position is created
event PositionCreated(
uint256 indexed positionId,
address indexed owner,
address indexed asset,
uint8 periodMonths,
uint256 remainingDeposit,
uint256 conversionPrice,
uint48 expiry,
bool wrapped
);
/// @notice Emitted when a position's remaining deposit is updated
event PositionRemainingDepositUpdated(uint256 indexed positionId, uint256 remainingDeposit);
/// @notice Emitted when a position's additional data is updated
event PositionAdditionalDataUpdated(uint256 indexed positionId, bytes additionalData);
/// @notice Emitted when a position is split
event PositionSplit(
uint256 indexed positionId,
uint256 indexed newPositionId,
address indexed asset,
uint8 periodMonths,
uint256 amount,
address to,
bool wrap
);
/// @notice Emitted when a position is wrapped
event PositionWrapped(uint256 indexed positionId);
/// @notice Emitted when a position is unwrapped
event PositionUnwrapped(uint256 indexed positionId);
/// @notice Emitted when the token renderer is set
event TokenRendererSet(address indexed renderer);
// ========== ERRORS ========== //
/// @notice Error thrown when the caller is not the owner of the position
error DEPOS_NotOwner(uint256 positionId_);
/// @notice Error thrown when the caller is not the operator of the position
error DEPOS_NotOperator(uint256 positionId_);
/// @notice Error thrown when an invalid position ID is provided
error DEPOS_InvalidPositionId(uint256 id_);
/// @notice Error thrown when a position has already been wrapped
error DEPOS_AlreadyWrapped(uint256 positionId_);
/// @notice Error thrown when a position has not been wrapped
error DEPOS_NotWrapped(uint256 positionId_);
/// @notice Error thrown when an invalid parameter is provided
error DEPOS_InvalidParams(string reason_);
/// @notice Error thrown when a position does not support conversion
error DEPOS_NotConvertible(uint256 positionId_);
/// @notice Error thrown when the renderer contract does not implement the required interface
error DEPOS_InvalidRenderer(address renderer_);
// ========== WRAPPING ========== //
/// @notice Wraps a position into an ERC721 token
/// This is useful if the position owner wants a tokenized representation of their position. It is functionally equivalent to the position itself.
///
/// @dev The implementing function should do the following:
/// - Validate that the caller is the owner of the position
/// - Validate that the position is not already wrapped
/// - Mint an ERC721 token to the position owner
///
/// @param positionId_ The ID of the position to wrap
function wrap(uint256 positionId_) external;
/// @notice Unwraps/burns an ERC721 position token
/// This is useful if the position owner wants to unwrap their token back into the position.
///
/// @dev The implementing function should do the following:
/// - Validate that the caller is the owner of the position
/// - Validate that the position is already wrapped
/// - Burn the ERC721 token
///
/// @param positionId_ The ID of the position to unwrap
function unwrap(uint256 positionId_) external;
// ========== POSITION MANAGEMENT =========== //
/// @notice Creates a new deposit position
/// @dev The implementing function should do the following:
/// - Validate that the caller is permissioned
/// - Validate that the owner is not the zero address
/// - Validate that the convertible deposit token is not the zero address
/// - Validate that the remaining deposit is greater than 0
/// - Validate that the conversion price is greater than 0
/// - Validate that the expiry is in the future
/// - Create the position record
/// - Wrap the position if requested
///
/// @param params_ The parameters for the position creation
/// @return _positionId The ID of the new position
function mint(MintParams calldata params_) external returns (uint256 _positionId);
/// @notice Updates the remaining deposit of a position
/// @dev The implementing function should do the following:
/// - Validate that the caller is permissioned
/// - Validate that the position ID is valid
/// - Update the remaining deposit of the position
///
/// @param positionId_ The ID of the position to update
/// @param amount_ The new amount of the position
function setRemainingDeposit(uint256 positionId_, uint256 amount_) external;
/// @notice Updates the additional data of a position
/// @dev The implementing function should do the following:
/// - Validate that the caller is permissioned
/// - Validate that the position ID is valid
/// - Update the additional data of the position
///
/// @param positionId_ The ID of the position to update
/// @param additionalData_ The new additional data of the position
function setAdditionalData(uint256 positionId_, bytes calldata additionalData_) external;
/// @notice Splits the specified amount of the position into a new position
/// This is useful if the position owner wants to split their position into multiple smaller positions.
/// @dev The implementing function should do the following:
/// - Validate that the caller is the owner of the position
/// - Validate that the amount is greater than 0
/// - Validate that the amount is less than or equal to the remaining deposit
/// - Validate that `to_` is not the zero address
/// - Update the remaining deposit of the original position
/// - Create the new position record
/// - Wrap the new position if requested
///
/// @param positionId_ The ID of the position to split
/// @param amount_ The amount of the position to split
/// @param to_ The address to split the position to
/// @param wrap_ Whether the new position should be wrapped
/// @return newPositionId The ID of the new position
function split(
uint256 positionId_,
uint256 amount_,
address to_,
bool wrap_
) external returns (uint256 newPositionId);
// ========== POSITION INFORMATION ========== //
/// @notice Get the total number of positions
///
/// @return _count The total number of positions
function getPositionCount() external view returns (uint256 _count);
/// @notice Get the IDs of all positions for a given user
///
/// @param user_ The address of the user
/// @return _positionIds An array of position IDs
function getUserPositionIds(
address user_
) external view returns (uint256[] memory _positionIds);
/// @notice Get the position for a given ID
///
/// @param positionId_ The ID of the position
/// @return _position The position for the given ID
function getPosition(uint256 positionId_) external view returns (Position memory _position);
/// @notice Check if a position is expired
///
/// @param positionId_ The ID of the position
/// @return _expired Whether the position is expired
function isExpired(uint256 positionId_) external view returns (bool _expired);
/// @notice Check if a position is convertible
///
/// @param positionId_ The ID of the position
/// @return _convertible Whether the position is convertible
function isConvertible(uint256 positionId_) external view returns (bool _convertible);
/// @notice Preview the amount of OHM that would be received for a given amount of convertible deposit tokens
///
/// @param positionId_ The ID of the position
/// @param amount_ The amount of convertible deposit tokens to convert
/// @return _ohmOut The amount of OHM that would be received
function previewConvert(
uint256 positionId_,
uint256 amount_
) external view returns (uint256 _ohmOut);
// ========== TOKEN URI RENDERER ========== //
/// @notice Set the token renderer contract
/// @dev The implementing function should do the following:
/// - Validate that the caller is permissioned
/// - Validate that the renderer contract implements the required interface
/// - Set the renderer contract
/// - Emit an event
///
/// @param renderer_ The address of the renderer contract
function setTokenRenderer(address renderer_) external;
/// @notice Get the current token renderer contract
///
/// @return _renderer The address of the current renderer contract (or zero address if not set)
function getTokenRenderer() external view returns (address _renderer);
}
"
},
"src/interfaces/IPeriodicTask.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;
/// @title IPeriodicTask
/// @notice Interface for a contract that can perform a task at a specified interval
interface IPeriodicTask {
// ========== FUNCTIONS ========== //
/// @notice Executes the periodic task
/// @dev Guidelines for implementing functions:
/// @dev - The implementing function is responsible for checking if the task is due to be executed.
/// @dev - The implementing function should avoid reverting, as that would cause the calling contract to revert.
/// @dev - The implementing function should be protected by a role check for the "heart" role.
function execute() external;
/// @notice ERC165 interface support
function supportsInterface(bytes4 interfaceId) external view returns (bool);
}
"
},
"src/libraries/FullMath.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;
/// @title Contains 512-bit math functions
/// @notice Facilitates multiplication and division that can have overflow of an intermediate value without any loss of precision
/// @dev Handles "phantom overflow" i.e., allows multiplication and division where an intermediate value overflows 256 bits
library FullMath {
/// @notice Calculates floor(a×b÷denominator) with full precision. Throws if result overflows a uint256 or denominator == 0
/// @param a The multiplicand
/// @param b The multiplier
/// @param denominator The divisor
/// @return result The 256-bit result
/// @dev Credit to Remco Bloemen under MIT license https://xn--2-umb.com/21/muldiv
function mulDiv(
uint256 a,
uint256 b,
uint256 denominator
) internal pure returns (uint256 result) {
unchecked {
// 512-bit multiply [prod1 prod0] = a * b
// Compute the product mod 2**256 and mod 2**256 - 1
// then use the Chinese Remainder Theorem to reconstruct
// the 512 bit result. The result is stored in two 256
// variables such that product = prod1 * 2**256 + prod0
uint256 prod0; // Least significant 256 bits of the product
uint256 prod1; // Most significant 256 bits of the product
assembly {
let mm := mulmod
Submitted on: 2025-11-07 16:13:56
Comments
Log in to comment.
No comments yet.