Overview
S Balance
S Value
$0.00More Info
Private Name Tags
ContractCreator
Loading...
Loading
This contract may be a proxy contract. Click on More Options and select Is this a proxy? to confirm and enable the "Read as Proxy" & "Write as Proxy" tabs.
Similar Match Source Code This contract matches the deployed Bytecode of the Source Code for Contract 0xdCC73745...8a9c5a2C2 The constructor portion of the code might be different and could alter the actual behaviour of the contract
Contract Name:
Protocol
Compiler Version
v0.8.28+commit.7893614a
Optimization Enabled:
Yes with 200 runs
Other Settings:
shanghai EvmVersion
Contract Source Code (Solidity Standard Json-Input format)
// SPDX-License-Identifier: MIT pragma solidity 0.8.28; // OpenZeppelin import { AccessControlUpgradeable } from "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol"; import { ReentrancyGuardUpgradeable } from "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol"; // libraries import { ProtocolConfigState } from "./libs/ProtocolConfigState.sol"; import { FeeManagementLibrary } from "./libs/FeeManagementLibrary.sol"; import { CacheLibrary } from "./libs/CacheLibrary.sol"; import { HookLibrary } from "./libs/HookLibrary.sol"; import { ProtocolUtils } from "./libs/ProtocolUtils.sol"; import { OperationContextLibrary } from "./libs/OperationContextLibrary.sol"; import { TimeManagementLibrary } from "./libs/TimeManagementLibrary.sol"; import { ValidationLibrary } from "./libs/ValidationLibrary.sol"; import { CustomRevert } from "./libs/CustomRevert.sol"; // types import { GroupId, GroupIdLibrary } from "./types/GroupId.sol"; import { GroupStateHelper, GroupSettings } from "./types/GroupStateHelper.sol"; import { GroupState, CollateralInfo, OperationTypes } from "./types/CommonTypes.sol"; import { GroupKey } from "./types/GroupKey.sol"; import { Currency, CurrencyLibrary } from "./types/Currency.sol"; // declarations import { DProtocol } from "./declarations/DProtocol.sol"; import { DOperationContext } from "./declarations/DOperationContext.sol"; // interfaces import { IProtocol } from "./interfaces/IProtocol.sol"; import { ITreasuryMinimum as ITreasury } from "./interfaces/ITreasuryMinimum.sol"; import { IHooks } from "./interfaces/IHooks.sol"; contract Protocol is AccessControlUpgradeable, ReentrancyGuardUpgradeable, IProtocol { using CurrencyLibrary for Currency; using GroupIdLibrary for GroupKey; using OperationContextLibrary for DOperationContext.OperationContext; using CacheLibrary for CacheLibrary.Storage; using FeeManagementLibrary for FeeManagementLibrary.FeeStorage; using TimeManagementLibrary for TimeManagementLibrary.TimeStorage; using HookLibrary for IHooks; using CustomRevert for bytes4; using ValidationLibrary for *; /// @notice Cache storage for group states CacheLibrary.Storage private cacheStorage; /// @notice Fee storage for owed fees FeeManagementLibrary.FeeStorage private feeStorage; /// @notice Time storage for operation time tracking TimeManagementLibrary.TimeStorage private timeStorage; /// @notice Configuration storage for protocol-wide flags ProtocolConfigState.ConfigStorage private configStorage; /// @notice Admin address address public adminAddress; /// @custom:oz-upgrades-unsafe-allow constructor constructor() initializer {} /** * @notice Allow direct ETH transfers */ receive() external payable {} /** * @notice Fallback function to prevent direct calls */ fallback() external payable { IProtocol.NotPermitted.selector.revertWith(); } /** * @notice Initializes the Protocol contract. * @param _admin The address of the admin. */ function initialize(address _admin) external initializer { if (_admin == address(0)) { IProtocol.ZeroAddress.selector.revertWith(); } __AccessControl_init(); __ReentrancyGuard_init(); _grantRole(DEFAULT_ADMIN_ROLE, _admin); adminAddress = _admin; } /** * @notice Forces an update of the cache for a specific group. * @param groupId The identifier of the group. */ function forceUpdateGroupCache(address tokenRegistry, GroupId groupId) external nonReentrant onlyRole(DEFAULT_ADMIN_ROLE) { cacheStorage.forceUpdate(tokenRegistry, groupId); address groupTreasury = cacheStorage.getGroupTreasury(groupId); if (groupTreasury != address(0)) { try ITreasury(groupTreasury).forceUpdateGroupCache(tokenRegistry, groupId) { /// @dev Treasury cache update succeeded } catch { /// @dev Bubble up the revert reason if Treasury fails CustomRevert.bubbleUpAndRevertWith(IProtocol.TreasuryGroupUpdateFailed.selector, address(groupTreasury)); } } else { IProtocol.TreasuryGroupUpdateFailed.selector.revertWith(groupId); } } /** * @notice Allows the hook contract to settle owed fees. * @param groupId The key identifying the group. * @param token The token to settle fees in. * @dev Only the hook contract can call this function. */ function settleOwedFees(GroupId groupId, address token) external nonReentrant { address hookContract = cacheStorage.getGroupHookContract(groupId); // Validate caller is the hook contract if (_msgSender() != hookContract) { IProtocol.NotPermitted.selector.revertWith(); } feeStorage.settleOwedFees(groupId, hookContract, token); } /** * @notice Mints tokens based on the provided parameters. * @param groupKey The key identifying the group. * @param params The mint parameters. * @return result The result of the mint operation. */ function mintToken( GroupKey calldata groupKey, DProtocol.MintParams calldata params ) external payable override nonReentrant returns (DProtocol.MintResult memory result) { GroupId groupId = groupKey.toId(); GroupState memory groupState = cacheStorage.getGroupState(groupId); (bool isSupported, CollateralInfo memory collateralInfo) = ValidationLibrary.isPaymentTokenSupported( groupState.acceptableCollaterals, params.baseIn, Currency.wrap(params.paymentToken), true, msg.value ); if (!isSupported) { IProtocol.UnsupportedCollateralType.selector.revertWith(); } uint256 baseIn = params.baseIn; // If user sets baseIn == max, and payment token is non-native, // then we override baseIn with their entire balance. if (params.baseIn == type(uint256).max && !Currency.wrap(params.paymentToken).isNative()) { baseIn = Currency.wrap(params.paymentToken).balanceOf(_msgSender()); } // If baseIn > 0, proceed with the minting logic if (baseIn > 0) { DOperationContext.Context memory opParams = DOperationContext.Context({ groupId: groupId, groupState: groupState, /// @dev amount0 is the payment token amount /// @dev amount1 reserved for further use for the hooks /// @dev context updates the amount1 to the minted token amount after the conversion is done amounts: DOperationContext.ContextAmounts({ amount0: baseIn, amount1: 0 }), operationType: params.operationType, paymentToken: collateralInfo, slippage: params.slippage, recipient: _msgSender(), hookData: params.hookData }); DOperationContext.OperationContext memory context = OperationContextLibrary.createContext(opParams); ValidationLibrary.validateOperation(configStorage, groupId, context); result = _processMint(groupId, context); return result; } // If baseIn == 0, returning an empty MintResult return DProtocol.MintResult({ ytMinted: 0, vtMinted: 0 }); } /** * @notice Redeems tokens based on the provided parameters. * @param groupKey The key identifying the group. * @param params The redeem parameters. * @return baseOut The amount of base tokens received. */ function redeemToken( GroupKey calldata groupKey, DProtocol.RedeemParams calldata params ) external override nonReentrant returns (uint256 baseOut) { GroupId groupId = groupKey.toId(); GroupState memory groupState = cacheStorage.getGroupState(groupId); (bool isSupported, CollateralInfo memory collateralInfo) = ValidationLibrary.isPaymentTokenSupported( groupState.acceptableCollaterals, params.baseOut, Currency.wrap(params.desiredCollateral), false, 0 ); if (!isSupported) IProtocol.UnsupportedCollateralType.selector.revertWith(); baseOut = params.baseOut; if (params.baseOut == type(uint256).max) { if (params.operationType == uint8(OperationTypes.OP_TYPE_REDEEM_VT)) { baseOut = groupState.core.xToken.balanceOf(_msgSender()); } else { baseOut = groupState.extended.rebalancePool.balanceOf(_msgSender()); } } if (baseOut > 0) { // Creating operation context without hookData DOperationContext.Context memory opParams = DOperationContext.Context({ groupId: groupId, groupState: groupState, /// @dev amount0 is the redeeming token amount /// @dev amount1 reserved for further use for the hooks /// @dev context updates the amount1 to the desired token amount after the conversion is done amounts: DOperationContext.ContextAmounts({ amount0: baseOut, amount1: 0 }), operationType: params.operationType, paymentToken: collateralInfo, slippage: params.slippage, recipient: _msgSender(), hookData: params.hookData }); DOperationContext.OperationContext memory context = OperationContextLibrary.createContext(opParams); ValidationLibrary.validateOperation(configStorage, groupId, context); baseOut = _processRedeem(groupId, context); return baseOut; } } /** * @notice Processes the mint operation. * @param groupId The group ID. * @param context The operation context. * @return result The result of the mint operation. */ function _processMint( GroupId groupId, DOperationContext.OperationContext memory context ) private returns (DProtocol.MintResult memory result) { address recipient = context.getRecipient(); address hookAddress = context.getHookContract(); // Handle any necessary before-mint hooks using HookLibrary if (hookAddress != address(0)) { IHooks hook = IHooks(hookAddress); HookLibrary.beforeMint(hook, groupId, context); } // Prepare the token amount for the treasury & distribute fees (Currency preparedToken, uint256 amountWithoutFee, ProtocolUtils.FeeSplit memory feeSplit) = feeStorage.prepareTokenForTreasury( timeStorage, context, context.getAmount0(), true ); // Mint the tokens to the recipient result = ProtocolUtils.mintTokens(context, amountWithoutFee, context.getRecipient()); // Distribute fees based on the split feeStorage.distributeFees(context, context.getYieldBearingToken(), feeSplit); emit MintToken(groupId, preparedToken.toAddress(), recipient, _msgSender(), result.ytMinted, result.vtMinted); // Handle any necessary after-mint hooks if (hookAddress != address(0)) { IHooks hook = IHooks(hookAddress); HookLibrary.afterMint(hook, groupId, context); } return result; } /** * @notice Processes the redeem operation. * @param groupId The group ID. * @param context The operation context. * @return baseOut The amount of base tokens received. */ function _processRedeem(GroupId groupId, DOperationContext.OperationContext memory context) private returns (uint256 baseOut) { address from = context.getRecipient(); address hookAddress = context.getHookContract(); // Handle any necessary before-redeem hooks if (hookAddress != address(0)) { IHooks hook = IHooks(hookAddress); HookLibrary.beforeRedeem(hook, groupId, context); } // Prepare tokens and distribute fees (Currency preparedToken, uint256 amountWithoutFee, ProtocolUtils.FeeSplit memory feeSplit) = feeStorage.prepareTokenForTreasury( timeStorage, context, context.getAmount0(), false ); // Distribute fees based on the split feeStorage.distributeFees(context, preparedToken, feeSplit); // Convert tokens to desired collateral and apply slippage check baseOut = ProtocolUtils.convertToDesiredCollateralAndSend(context, amountWithoutFee, from); emit RedeemToken(groupId, preparedToken.toAddress(), from, _msgSender(), baseOut); // Handle any necessary after-redeem hooks if (hookAddress != address(0)) { IHooks hook = IHooks(hookAddress); HookLibrary.afterRedeem(hook, groupId, context); } return baseOut; } /** Get user operation times for a group. * @return lastVTMintTime The last VT mint time for the user. * @return lastYTMintTime The last YT mint time for the user. * @return lastVTRedeemTime The last VT redeem time for the user. * @return lastYTRedeemTime The last YT redeem time for the user. */ function getUserOperationTime( GroupId groupId, address user ) external view returns (uint256 lastVTMintTime, uint256 lastYTMintTime, uint256 lastVTRedeemTime, uint256 lastYTRedeemTime) { (lastVTMintTime, lastYTMintTime, lastVTRedeemTime, lastYTRedeemTime) = timeStorage.getUserOperationTime(groupId, user); return (lastVTMintTime, lastYTMintTime, lastVTRedeemTime, lastYTRedeemTime); } /** * @notice Returns the group mint flag. * @param groupId The group identifier. * @return True if minting is enabled for the group, false otherwise. */ function getGroupMintFlag(GroupId groupId) external view returns (bool) { return ProtocolConfigState.getMintFlag(configStorage, groupId); } /** * @notice Returns the group redeem flag. * @param groupId The group identifier. * @return True if redeeming is enabled for the group, false otherwise. */ function getGroupRedeemFlag(GroupId groupId) external view returns (bool) { return ProtocolConfigState.getRedeemFlag(configStorage, groupId); } /** * @notice Returns the group stability flag. * @param groupId The group identifier. * @return True if the group is in stability mode, false otherwise. */ function getGroupStabilityFlag(GroupId groupId) external view returns (bool) { return ProtocolConfigState.getStabilityFlag(configStorage, groupId); } /** * @notice Returns the global mint flag. * @return True if minting is globally enabled, false otherwise. */ function getGlobalMintFlag() external view returns (bool) { return ProtocolConfigState.getGlobalMintFlag(configStorage); } /** * @notice Returns the global redeem flag. * @return True if redeeming is globally enabled, false otherwise. */ function getGlobalRedeemFlag() external view returns (bool) { return ProtocolConfigState.getGlobalRedeemFlag(configStorage); } /** * @notice Returns the stability ratio for a group. * @param groupId The group identifier. * @return The stability ratio. */ function stabilityRatio(GroupId groupId) external view returns (uint96) { GroupState memory groupState = cacheStorage.getGroupState(groupId); return GroupStateHelper.getStabilityRatio(GroupSettings.wrap(groupState.groupSettings)); } /** * @notice Sets the global mint flag. * @param value The new value for the global mint flag. */ function setGlobalMintFlag(bool value) external onlyRole(DEFAULT_ADMIN_ROLE) { ProtocolConfigState.setGlobalMintFlag(configStorage, value); } /** * @notice Sets the global redeem flag. * @param value The new value for the global redeem flag. */ function setGlobalRedeemFlag(bool value) external onlyRole(DEFAULT_ADMIN_ROLE) { ProtocolConfigState.setGlobalRedeemFlag(configStorage, value); } /** * @notice Sets the mint flag for a specific group. * @param groupId The group identifier. * @param value The new value for the group's mint flag. */ function setGroupMintFlag(GroupId groupId, bool value) external onlyRole(DEFAULT_ADMIN_ROLE) { ProtocolConfigState.setMintFlag(configStorage, groupId, value); } /** * @notice Sets the redeem flag for a specific group. * @param groupId The group identifier. * @param value The new value for the group's redeem flag. */ function setGroupRedeemFlag(GroupId groupId, bool value) external onlyRole(DEFAULT_ADMIN_ROLE) { ProtocolConfigState.setRedeemFlag(configStorage, groupId, value); } /** * @notice Sets the stability flag for a specific group. * @param groupId The group identifier. * @param value The new value for the group's stability flag. */ function setGroupStabilityFlag(GroupId groupId, bool value) external onlyRole(DEFAULT_ADMIN_ROLE) { ProtocolConfigState.setStabilityFlag(configStorage, groupId, value); } /** * @notice Gets the amount of owed fees for a hook contract. * @param groupId The group ID. * @param hookContract The address of the hook contract. * @param token The token to settle fees in. * @return The amount of owed fees. */ function getOwedFees(GroupId groupId, address hookContract, address token) external view returns (uint256) { return feeStorage.getOwedFees(groupId, hookContract, token); } /** * @notice Allows the admin to recover ERC20 tokens mistakenly sent to the contract. * @param tokenAddress The address of the token to recover. * @param tokenAmount The amount of tokens to recover. * @dev Only the admin can call this function. And normally protocol not keeps erc20 tokens apart from hook owed fees. */ function recoverERC20(address tokenAddress, uint256 tokenAmount) external nonReentrant onlyRole(DEFAULT_ADMIN_ROLE) { if (tokenAddress == address(0)) { IProtocol.ZeroAddress.selector.revertWith(); } Currency token = Currency.wrap(tokenAddress); uint256 currentBalance = token.balanceOf(address(this)); if (tokenAmount > currentBalance) { IProtocol.InsufficientBalance.selector.revertWith(tokenAddress); } /// @dev Transfer the amount to the admin- whoever calls this function token.safeTransfer(adminAddress, tokenAmount); emit ERC20Recovered(token, tokenAmount); } /** * @notice Allows the admin to recover ETH mistakenly sent to the contract. * @param amount The amount of ETH to recover. * @dev Only the admin can call this function. * @dev Normally protocol not keeps ETH. * @notice Slither false-positive. The function is only accessible by the admin. */ //slither-disable-next-line low-level-calls function recoversEther(uint256 amount) external nonReentrant onlyRole(DEFAULT_ADMIN_ROLE) { if (amount > address(this).balance) { IProtocol.InsufficientBalance.selector.revertWith(address(0)); } emit NativeTokenWithdrawn(amount); /// @dev Transfer the amount to the admin- whoever calls this function (bool success, ) = payable(adminAddress).call{ value: amount }(""); if (!success) revert IProtocol.NativeTokenWithdrawnFailed(amount); } /** * @notice Prevents renouncing roles for security * @dev Overrides the default renounceRole function to prevent accidental role removal */ function renounceRole(bytes32, address) public virtual override { revert("Roles can't be renounced"); } /** * @notice Checks if the contract supports a specific interface. * @param interfaceId The interface identifier. * @return True if the interface is supported, false otherwise. */ function supportsInterface(bytes4 interfaceId) public view override(AccessControlUpgradeable) returns (bool) { return interfaceId == type(IProtocol).interfaceId || super.supportsInterface(interfaceId); } // slither-disable-next-line unused-state uint256[50] private __gap; // Storage gap for upgradeability }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (access/AccessControl.sol) pragma solidity ^0.8.0; import "./IAccessControlUpgradeable.sol"; import "../utils/ContextUpgradeable.sol"; import "../utils/StringsUpgradeable.sol"; import "../utils/introspection/ERC165Upgradeable.sol"; import {Initializable} from "../proxy/utils/Initializable.sol"; /** * @dev Contract module that allows children to implement role-based access * control mechanisms. This is a lightweight version that doesn't allow enumerating role * members except through off-chain means by accessing the contract event logs. Some * applications may benefit from on-chain enumerability, for those cases see * {AccessControlEnumerable}. * * Roles are referred to by their `bytes32` identifier. These should be exposed * in the external API and be unique. The best way to achieve this is by * using `public constant` hash digests: * * ```solidity * bytes32 public constant MY_ROLE = keccak256("MY_ROLE"); * ``` * * Roles can be used to represent a set of permissions. To restrict access to a * function call, use {hasRole}: * * ```solidity * function foo() public { * require(hasRole(MY_ROLE, msg.sender)); * ... * } * ``` * * Roles can be granted and revoked dynamically via the {grantRole} and * {revokeRole} functions. Each role has an associated admin role, and only * accounts that have a role's admin role can call {grantRole} and {revokeRole}. * * By default, the admin role for all roles is `DEFAULT_ADMIN_ROLE`, which means * that only accounts with this role will be able to grant or revoke other * roles. More complex role relationships can be created by using * {_setRoleAdmin}. * * WARNING: The `DEFAULT_ADMIN_ROLE` is also its own admin: it has permission to * grant and revoke this role. Extra precautions should be taken to secure * accounts that have been granted it. We recommend using {AccessControlDefaultAdminRules} * to enforce additional security measures for this role. */ abstract contract AccessControlUpgradeable is Initializable, ContextUpgradeable, IAccessControlUpgradeable, ERC165Upgradeable { struct RoleData { mapping(address => bool) members; bytes32 adminRole; } mapping(bytes32 => RoleData) private _roles; bytes32 public constant DEFAULT_ADMIN_ROLE = 0x00; /** * @dev Modifier that checks that an account has a specific role. Reverts * with a standardized message including the required role. * * The format of the revert reason is given by the following regular expression: * * /^AccessControl: account (0x[0-9a-f]{40}) is missing role (0x[0-9a-f]{64})$/ * * _Available since v4.1._ */ modifier onlyRole(bytes32 role) { _checkRole(role); _; } function __AccessControl_init() internal onlyInitializing { } function __AccessControl_init_unchained() internal onlyInitializing { } /** * @dev See {IERC165-supportsInterface}. */ function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) { return interfaceId == type(IAccessControlUpgradeable).interfaceId || super.supportsInterface(interfaceId); } /** * @dev Returns `true` if `account` has been granted `role`. */ function hasRole(bytes32 role, address account) public view virtual override returns (bool) { return _roles[role].members[account]; } /** * @dev Revert with a standard message if `_msgSender()` is missing `role`. * Overriding this function changes the behavior of the {onlyRole} modifier. * * Format of the revert message is described in {_checkRole}. * * _Available since v4.6._ */ function _checkRole(bytes32 role) internal view virtual { _checkRole(role, _msgSender()); } /** * @dev Revert with a standard message if `account` is missing `role`. * * The format of the revert reason is given by the following regular expression: * * /^AccessControl: account (0x[0-9a-f]{40}) is missing role (0x[0-9a-f]{64})$/ */ function _checkRole(bytes32 role, address account) internal view virtual { if (!hasRole(role, account)) { revert( string( abi.encodePacked( "AccessControl: account ", StringsUpgradeable.toHexString(account), " is missing role ", StringsUpgradeable.toHexString(uint256(role), 32) ) ) ); } } /** * @dev Returns the admin role that controls `role`. See {grantRole} and * {revokeRole}. * * To change a role's admin, use {_setRoleAdmin}. */ function getRoleAdmin(bytes32 role) public view virtual override returns (bytes32) { return _roles[role].adminRole; } /** * @dev Grants `role` to `account`. * * If `account` had not been already granted `role`, emits a {RoleGranted} * event. * * Requirements: * * - the caller must have ``role``'s admin role. * * May emit a {RoleGranted} event. */ function grantRole(bytes32 role, address account) public virtual override onlyRole(getRoleAdmin(role)) { _grantRole(role, account); } /** * @dev Revokes `role` from `account`. * * If `account` had been granted `role`, emits a {RoleRevoked} event. * * Requirements: * * - the caller must have ``role``'s admin role. * * May emit a {RoleRevoked} event. */ function revokeRole(bytes32 role, address account) public virtual override onlyRole(getRoleAdmin(role)) { _revokeRole(role, account); } /** * @dev Revokes `role` from the calling account. * * Roles are often managed via {grantRole} and {revokeRole}: this function's * purpose is to provide a mechanism for accounts to lose their privileges * if they are compromised (such as when a trusted device is misplaced). * * If the calling account had been revoked `role`, emits a {RoleRevoked} * event. * * Requirements: * * - the caller must be `account`. * * May emit a {RoleRevoked} event. */ function renounceRole(bytes32 role, address account) public virtual override { require(account == _msgSender(), "AccessControl: can only renounce roles for self"); _revokeRole(role, account); } /** * @dev Grants `role` to `account`. * * If `account` had not been already granted `role`, emits a {RoleGranted} * event. Note that unlike {grantRole}, this function doesn't perform any * checks on the calling account. * * May emit a {RoleGranted} event. * * [WARNING] * ==== * This function should only be called from the constructor when setting * up the initial roles for the system. * * Using this function in any other way is effectively circumventing the admin * system imposed by {AccessControl}. * ==== * * NOTE: This function is deprecated in favor of {_grantRole}. */ function _setupRole(bytes32 role, address account) internal virtual { _grantRole(role, account); } /** * @dev Sets `adminRole` as ``role``'s admin role. * * Emits a {RoleAdminChanged} event. */ function _setRoleAdmin(bytes32 role, bytes32 adminRole) internal virtual { bytes32 previousAdminRole = getRoleAdmin(role); _roles[role].adminRole = adminRole; emit RoleAdminChanged(role, previousAdminRole, adminRole); } /** * @dev Grants `role` to `account`. * * Internal function without access restriction. * * May emit a {RoleGranted} event. */ function _grantRole(bytes32 role, address account) internal virtual { if (!hasRole(role, account)) { _roles[role].members[account] = true; emit RoleGranted(role, account, _msgSender()); } } /** * @dev Revokes `role` from `account`. * * Internal function without access restriction. * * May emit a {RoleRevoked} event. */ function _revokeRole(bytes32 role, address account) internal virtual { if (hasRole(role, account)) { _roles[role].members[account] = false; emit RoleRevoked(role, account, _msgSender()); } } /** * @dev This empty reserved space is put in place to allow future versions to add new * variables without shifting down storage in the inheritance chain. * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps */ uint256[49] private __gap; }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (security/ReentrancyGuard.sol) pragma solidity ^0.8.0; import {Initializable} from "../proxy/utils/Initializable.sol"; /** * @dev Contract module that helps prevent reentrant calls to a function. * * Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier * available, which can be applied to functions to make sure there are no nested * (reentrant) calls to them. * * Note that because there is a single `nonReentrant` guard, functions marked as * `nonReentrant` may not call one another. This can be worked around by making * those functions `private`, and then adding `external` `nonReentrant` entry * points to them. * * TIP: If you would like to learn more about reentrancy and alternative ways * to protect against it, check out our blog post * https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul]. */ abstract contract ReentrancyGuardUpgradeable is Initializable { // Booleans are more expensive than uint256 or any type that takes up a full // word because each write operation emits an extra SLOAD to first read the // slot's contents, replace the bits taken up by the boolean, and then write // back. This is the compiler's defense against contract upgrades and // pointer aliasing, and it cannot be disabled. // The values being non-zero value makes deployment a bit more expensive, // but in exchange the refund on every call to nonReentrant will be lower in // amount. Since refunds are capped to a percentage of the total // transaction's gas, it is best to keep them low in cases like this one, to // increase the likelihood of the full refund coming into effect. uint256 private constant _NOT_ENTERED = 1; uint256 private constant _ENTERED = 2; uint256 private _status; function __ReentrancyGuard_init() internal onlyInitializing { __ReentrancyGuard_init_unchained(); } function __ReentrancyGuard_init_unchained() internal onlyInitializing { _status = _NOT_ENTERED; } /** * @dev Prevents a contract from calling itself, directly or indirectly. * Calling a `nonReentrant` function from another `nonReentrant` * function is not supported. It is possible to prevent this from happening * by making the `nonReentrant` function external, and making it call a * `private` function that does the actual work. */ modifier nonReentrant() { _nonReentrantBefore(); _; _nonReentrantAfter(); } function _nonReentrantBefore() private { // On the first call to nonReentrant, _status will be _NOT_ENTERED require(_status != _ENTERED, "ReentrancyGuard: reentrant call"); // Any calls to nonReentrant after this point will fail _status = _ENTERED; } function _nonReentrantAfter() private { // By storing the original value once again, a refund is triggered (see // https://eips.ethereum.org/EIPS/eip-2200) _status = _NOT_ENTERED; } /** * @dev Returns true if the reentrancy guard is currently set to "entered", which indicates there is a * `nonReentrant` function in the call stack. */ function _reentrancyGuardEntered() internal view returns (bool) { return _status == _ENTERED; } /** * @dev This empty reserved space is put in place to allow future versions to add new * variables without shifting down storage in the inheritance chain. * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps */ uint256[49] private __gap; }
// SPDX-License-Identifier: MIT pragma solidity 0.8.28; import { WordCodec } from "./WordCodec.sol"; import { GroupId } from "../types/GroupId.sol"; import { IProtocol } from "../interfaces/IProtocol.sol"; import { CustomRevert } from "./CustomRevert.sol"; /// @title Protocol Config State Library /// @notice Provides functions to manage global and group-specific configuration flags. library ProtocolConfigState { using WordCodec for bytes32; using CustomRevert for bytes4; uint256 internal constant GLOBAL_MINT_FLAG_OFFSET = 0; // Offset for the global mint flag (boolean) uint256 internal constant GLOBAL_REDEEM_FLAG_OFFSET = 1; // Offset for the global redeem flag (boolean) uint256 internal constant GLOBAL_STABILITY_FLAG_OFFSET = 2; // Offset for the global stability flag (boolean) // Offsets for per-group configuration flags (bits 0 to 2) uint256 internal constant GROUP_MINT_FLAG_OFFSET = 0; // Offset for the group mint flag (boolean) uint256 internal constant GROUP_REDEEM_FLAG_OFFSET = 1; // Offset for the group redeem flag (boolean) uint256 internal constant GROUP_STABILITY_FLAG_OFFSET = 2; // Offset for the group stability flag (boolean) error ZeroGroupKey(); // Bits 3 to 255 are reserved for future use /// @notice Configuration storage structure. struct ConfigStorage { /// @notice Encoded protocol-wide configuration data. bytes32 protocolConfigData; /// @notice Mapping of group IDs to their encoded configuration data. mapping(bytes32 => bytes32) groupConfigData; } /// @notice Retrieves the global mint flag. /// @param self The configuration storage. /// @return True if global minting is enabled, false otherwise. function getGlobalMintFlag(ConfigStorage storage self) internal view returns (bool) { return self.protocolConfigData.decodeBool(GLOBAL_MINT_FLAG_OFFSET); } /// @notice Sets the global mint flag. /// @param self The configuration storage. /// @param value The new value for the global mint flag. function setGlobalMintFlag(ConfigStorage storage self, bool value) internal { self.protocolConfigData = self.protocolConfigData.insertBool(value, GLOBAL_MINT_FLAG_OFFSET); } /// @notice Retrieves the global redeem flag. /// @param self The configuration storage. /// @return True if global redeeming is enabled, false otherwise. function getGlobalRedeemFlag(ConfigStorage storage self) internal view returns (bool) { return self.protocolConfigData.decodeBool(GLOBAL_REDEEM_FLAG_OFFSET); } /// @notice Sets the global redeem flag. /// @param self The configuration storage. /// @param value The new value for the global redeem flag. function setGlobalRedeemFlag(ConfigStorage storage self, bool value) internal { self.protocolConfigData = self.protocolConfigData.insertBool(value, GLOBAL_REDEEM_FLAG_OFFSET); } /// @notice Retrieves the global stability flag. /// @param self The configuration storage. /// @return True if global stability is enabled, false otherwise. function getGlobalStabilityFlag(ConfigStorage storage self) internal view returns (bool) { return self.protocolConfigData.decodeBool(GLOBAL_STABILITY_FLAG_OFFSET); } /// @notice Sets the global stability flag. /// @param self The configuration storage. /// @param value The new value for the global stability flag. function setGlobalStabilityFlag(ConfigStorage storage self, bool value) internal { self.protocolConfigData = self.protocolConfigData.insertBool(value, GLOBAL_STABILITY_FLAG_OFFSET); } /// @notice Retrieves the mint flag for a specific group. /// @param self The configuration storage. /// @param groupId The group ID. /// @return True if minting is enabled for the group, false otherwise. function getMintFlag(ConfigStorage storage self, GroupId groupId) internal view returns (bool) { bytes32 groupKey = _validateAndGetGroupKey(groupId); return self.groupConfigData[groupKey].decodeBool(GROUP_MINT_FLAG_OFFSET); } /// @notice Sets the mint flag for a specific group. /// @param self The configuration storage. /// @param groupId The group ID. /// @param value The new value for the group's mint flag. function setMintFlag(ConfigStorage storage self, GroupId groupId, bool value) internal { bytes32 groupKey = _validateAndGetGroupKey(groupId); self.groupConfigData[groupKey] = self.groupConfigData[groupKey].insertBool(value, GROUP_MINT_FLAG_OFFSET); } /// @notice Retrieves the redeem flag for a specific group. /// @param self The configuration storage. /// @param groupId The group ID. /// @return True if redeeming is enabled for the group, false otherwise. function getRedeemFlag(ConfigStorage storage self, GroupId groupId) internal view returns (bool) { bytes32 groupKey = _validateAndGetGroupKey(groupId); return self.groupConfigData[groupKey].decodeBool(GROUP_REDEEM_FLAG_OFFSET); } /// @notice Sets the redeem flag for a specific group. /// @param self The configuration storage. /// @param groupId The group ID. /// @param value The new value for the group's redeem flag. function setRedeemFlag(ConfigStorage storage self, GroupId groupId, bool value) internal { bytes32 groupKey = _validateAndGetGroupKey(groupId); self.groupConfigData[groupKey] = self.groupConfigData[groupKey].insertBool(value, GROUP_REDEEM_FLAG_OFFSET); } /// @notice Retrieves the stability flag for a specific group. /// @param self The configuration storage. /// @param groupId The group ID. /// @return True if stability is enabled for the group, false otherwise. function getStabilityFlag(ConfigStorage storage self, GroupId groupId) internal view returns (bool) { bytes32 groupKey = _validateAndGetGroupKey(groupId); return self.groupConfigData[groupKey].decodeBool(GROUP_STABILITY_FLAG_OFFSET); } /// @notice Sets the stability flag for a specific group. /// @param self The configuration storage. /// @param groupId The group ID. /// @param value The new value for the group's stability flag. function setStabilityFlag(ConfigStorage storage self, GroupId groupId, bool value) internal { bytes32 groupKey = _validateAndGetGroupKey(groupId); self.groupConfigData[groupKey] = self.groupConfigData[groupKey].insertBool(value, GROUP_STABILITY_FLAG_OFFSET); } /// @notice Validates the group ID and returns the corresponding group key. /// @param groupId The group ID to validate. /// @return The group key derived from the group ID. function _validateAndGetGroupKey(GroupId groupId) private pure returns (bytes32) { bytes32 groupKey = GroupId.unwrap(groupId); if (groupKey == bytes32(0)) { ZeroGroupKey.selector.revertWith(); } return groupKey; } }
// SPDX-License-Identifier: MIT pragma solidity 0.8.28; import { Currency, CurrencyLibrary } from "../types/Currency.sol"; import { GroupId } from "../types/GroupId.sol"; import { DOperationContext } from "../declarations/DOperationContext.sol"; import { OperationContextLibrary } from "./OperationContextLibrary.sol"; import { ProtocolUtils } from "./ProtocolUtils.sol"; import { CustomRevert } from "./CustomRevert.sol"; import { IProtocol } from "../interfaces/IProtocol.sol"; import { Address, AddressLibrary } from "../types/Address.sol"; import { TimeManagementLibrary } from "./TimeManagementLibrary.sol"; /** * @title FeeManagementLibrary * @notice Library for managing protocol fees and treasury-related operations */ library FeeManagementLibrary { using CurrencyLibrary for Currency; using OperationContextLibrary for DOperationContext.OperationContext; using TimeManagementLibrary for TimeManagementLibrary.TimeStorage; using AddressLibrary for Address; using CustomRevert for bytes4; /// @dev The storage struct for fee management struct FeeStorage { mapping(GroupId => mapping(address => mapping(Currency => uint256))) owedFees; } // Events event FeeDelegated(GroupId indexed groupId, address indexed hookContract, uint256 directFeeAmount, uint256 hookFeeAmount); event FeeCollected(GroupId indexed groupId, address indexed feeCollector, address indexed token, uint256 amount); event FeesSettled(GroupId indexed groupId, address indexed hookContract, address indexed token, uint256 amount); // Errors error ZeroAmount(GroupId groupId); error ZeroAddress(GroupId groupId); error NoFeesOwed(GroupId groupId); error InsufficientBalance(GroupId groupId, address token); /** * @notice Distributes fees between the fee collector and hook contract * @param self The fee storage * @param context The operation context * @param feeToken The token to use for fees * @param feeSplit The fee split information */ function distributeFees( FeeStorage storage self, DOperationContext.OperationContext memory context, Currency feeToken, ProtocolUtils.FeeSplit memory feeSplit ) internal { if (feeSplit.directFeeAmount > 0 || feeSplit.protocolFeeAmount > 0) { address hookContract = context.getHookContract(); // Handle hook share if (feeSplit.hookFeeAmount > 0 && hookContract != address(0)) { // Accumulate owed fees to hook self.owedFees[context.groupId][hookContract][feeToken] += feeSplit.hookFeeAmount; emit FeeDelegated(context.groupId, hookContract, feeSplit.directFeeAmount, feeSplit.hookFeeAmount); } } // Handle direct fee emit if (feeSplit.directFeeAmount > 0) { emit FeeCollected(context.groupId, context.getFeeCollector().toAddress(), feeToken.toAddress(), feeSplit.directFeeAmount); } } /** * @notice Prepares tokens for treasury operations * @param /self The fee storage * @param context The operation context * @param baseIn The amount of base tokens * @param isMinting Whether this is a minting operation * @return preparedToken The token prepared for treasury * @return amountWithoutFee The amount after fees * @return feeSplit The fee split details */ function prepareTokenForTreasury( FeeStorage storage /*self*/, TimeManagementLibrary.TimeStorage storage timeStorage, DOperationContext.OperationContext memory context, uint256 baseIn, bool isMinting ) internal returns (Currency preparedToken, uint256 amountWithoutFee, ProtocolUtils.FeeSplit memory feeSplit) { if (baseIn == 0) { ZeroAmount.selector.revertWith(context.groupId); } // Update operation time timeStorage.updateOperationTime(context.groupId, context.getOperationType(), context.getRecipient(), isMinting); // Check for early exit fee if redeeming if (!isMinting) { (bool applyEarlyFee, uint24 additionalFeeBps) = timeStorage.checkEarlyExitFee(context, context.getRecipient()); if (applyEarlyFee) { uint24 currentFee = context.getContextFee(); uint24 newFee = currentFee + additionalFeeBps; context.setContextFee(newFee); } } // Prepare token for treasury if (isMinting) { Currency paymentToken = context.getPaymentToken(); if (paymentToken.isNative()) baseIn = msg.value; return ProtocolUtils.prepareTokenMintForTreasury(context, baseIn); } else { return ProtocolUtils.redeemTokens(context, baseIn, context.getRecipient()); } } /** * @notice Allows hook contracts to settle owed fees * @param self The fee storage * @param groupId The group ID * @param hookContract The hook contract address * @param token The token to settle fees in */ function settleOwedFees(FeeStorage storage self, GroupId groupId, address hookContract, address token) internal { if (token == address(0)) { ZeroAddress.selector.revertWith(groupId); } Currency tokenCurrency = Currency.wrap(token); uint256 owedAmount = self.owedFees[groupId][hookContract][tokenCurrency]; if (owedAmount == 0) { NoFeesOwed.selector.revertWith(groupId); } self.owedFees[groupId][hookContract][tokenCurrency] = 0; // Check if protocol has enough balance if (tokenCurrency.isNative()) IProtocol.NotPermitted.selector.revertWith(); if (tokenCurrency.balanceOf(address(this)) < owedAmount) { InsufficientBalance.selector.revertWith(groupId, token); } tokenCurrency.safeTransfer(hookContract, owedAmount); emit FeesSettled(groupId, hookContract, token, owedAmount); } /** * @notice Gets the amount of owed fees for a hook contract * @param self The fee storage * @param groupId The group ID * @param hookContract The hook contract address * @param token The token to check fees in * @return The amount of owed fees */ function getOwedFees(FeeStorage storage self, GroupId groupId, address hookContract, address token) internal view returns (uint256) { return self.owedFees[groupId][hookContract][Currency.wrap(token)]; } }
// SPDX-License-Identifier: MIT pragma solidity 0.8.28; import { ITokenRegistryMinimum as ITokenRegistry } from "../interfaces/ITokenRegistryMinimum.sol"; import { DTokenRegistry } from "../declarations/DTokenRegistry.sol"; import { GroupId } from "../types/GroupId.sol"; import { IProtocol } from "../interfaces/IProtocol.sol"; import { Address, AddressLibrary } from "../types/Address.sol"; import { Currency, CurrencyLibrary } from "../types/Currency.sol"; import { CustomRevert } from "./CustomRevert.sol"; import { GroupStateHelper, GroupSettings } from "../types/GroupStateHelper.sol"; import { GroupState, CollateralInfo } from "../types/CommonTypes.sol"; library CacheLibrary { using CurrencyLibrary for Currency; using AddressLibrary for Address; using CustomRevert for bytes4; struct CachedGroupState { GroupState data; uint256 lastUpdateBlock; bool exists; } struct Storage { mapping(GroupId => CachedGroupState) cachedStates; } event CacheUpdated(GroupId indexed groupId); error ConfigNotReady(GroupId groupId); error InvalidTokenRegistry(address tokenRegistry); error InvalidGroupConfiguration(GroupId groupId); error EmptyCollateralListOnUpdate(GroupId groupId); /** * @notice Retrieves the cached group state, updating the cache if necessary. * @param self The storage containing cached group states. * @param groupId The ID of the group. * @return The group state. */ function getGroupState(Storage storage self, GroupId groupId) internal view returns (GroupState memory) { CachedGroupState storage cachedState = self.cachedStates[groupId]; if (!cachedState.exists) { ConfigNotReady.selector.revertWith(groupId); } return cachedState.data; } /** * @notice Updates the cache for a specific group ID. * @param self The storage containing cached group states. * @param tokenRegistry The token registry to fetch group state from. * @param groupId The ID of the group. */ function updateCache(Storage storage self, ITokenRegistry tokenRegistry, GroupId groupId) internal { CachedGroupState storage cachedState = self.cachedStates[groupId]; GroupState memory groupState = tokenRegistry.getGroup(groupId); if (groupState.core.aToken.isZero()) { InvalidGroupConfiguration.selector.revertWith(groupId); } uint256 len = groupState.acceptableCollaterals.length; if (len > 0) { delete cachedState.data.acceptableCollaterals; for (uint256 i = 0; i < len; ) { cachedState.data.acceptableCollaterals.push(groupState.acceptableCollaterals[i]); unchecked { ++i; } } } else { EmptyCollateralListOnUpdate.selector.revertWith(groupId); } /// @dev Validate core tokens with simple zero checks. /// @dev Token registry should have already validated these but this is another layer of fundamental validation. _simpleValidateGroupCoreTokens(groupState.core, groupId); _simpleValidateGroupExtendedTokens(groupState.extended, groupId); cachedState.data.core = groupState.core; cachedState.data.extended = groupState.extended; cachedState.data.feesPacked = groupState.feesPacked; cachedState.data.groupSettings = groupState.groupSettings; cachedState.data.hookContract = groupState.hookContract; cachedState.lastUpdateBlock = block.number; cachedState.exists = true; emit CacheUpdated(groupId); } /** * @notice Validates the core tokens of a group. * @param core The core tokens of the group. * @param groupId The ID of the group. * @dev This is a simple validation that checks for zero addresses and cartesian checks. * @dev Token registry should have already validated these but this is another layer of fundamental validation. */ function _simpleValidateGroupCoreTokens(DTokenRegistry.GroupCore memory core, GroupId groupId) private pure { if (core.aToken.isZero() || core.xToken.isZero() || core.baseToken.isZero() || core.yieldBearingToken.isZero()) { InvalidGroupConfiguration.selector.revertWith(groupId); } /// @dev cartesian check if ( core.aToken.equals(core.xToken) || core.aToken.equals(core.baseToken) || core.aToken.equals(core.yieldBearingToken) || core.xToken.equals(core.baseToken) || core.xToken.equals(core.yieldBearingToken) || core.baseToken.equals(core.yieldBearingToken) ) { InvalidGroupConfiguration.selector.revertWith(groupId); } } /** * @notice Validates the extended tokens of a group. * @param extended The extended tokens of the group. * @param groupId The ID of the group. * @dev This is a simple validation that checks for zero addresses. * @dev Token registry should have already validated these but this is another layer of fundamental validation. */ function _simpleValidateGroupExtendedTokens(DTokenRegistry.GroupExtended memory extended, GroupId groupId) private pure { if ( extended.priceOracle.isZero() || extended.rateProvider.isZero() || extended.swapRouter.isZero() || extended.treasury.isZero() || extended.feeCollector.isZero() || extended.strategy.isZero() || extended.rebalancePool.isZero() ) InvalidGroupConfiguration.selector.revertWith(groupId); } /** * @notice Forces the cache to update for a specific group ID. * @param self The storage containing cached group states. * @param tokenRegistry The token registry to fetch group state from. * @param groupId The ID of the group. */ function forceUpdate(Storage storage self, address tokenRegistry, GroupId groupId) internal { if (tokenRegistry == address(0)) InvalidTokenRegistry.selector.revertWith(groupId); updateCache(self, ITokenRegistry(tokenRegistry), groupId); } function getGroupTreasury(Storage storage self, GroupId groupId) internal view returns (address) { if (!self.cachedStates[groupId].exists) { ConfigNotReady.selector.revertWith(groupId); } return self.cachedStates[groupId].data.extended.treasury.toAddress(); } function getGroupHookContract(Storage storage self, GroupId groupId) internal view returns (address) { if (!self.cachedStates[groupId].exists) { ConfigNotReady.selector.revertWith(groupId); } return self.cachedStates[groupId].data.hookContract.toAddress(); } }
// SPDX-License-Identifier: MIT pragma solidity 0.8.28; import { CustomRevert } from "./CustomRevert.sol"; import { GroupId } from "../types/GroupId.sol"; import { IHooks } from "../interfaces/IHooks.sol"; import { DOperationContext } from "../declarations/DOperationContext.sol"; import { OperationContextLibrary } from "./OperationContextLibrary.sol"; import { GroupFeeLibrary } from "./GroupFeeLibrary.sol"; /** * @title HookLibrary * @notice Provides utilities to manage hooks related to minting, redeeming. * It handles the invocation of hook functions and manages `hookData` only when necessary. * @dev Hooks are for internal purpose so its generally safer */ library HookLibrary { using GroupFeeLibrary for uint24; using OperationContextLibrary for DOperationContext.OperationContext; using CustomRevert for bytes4; uint256 internal constant MAX_HOOK_DATA_LENGTH = 1024; // 1 KB limit for hookData uint256 internal constant EMPTY_BYTES_LENGTH = 32; // bytes length when empty uint256 internal constant MIN_HOOK_RESPONSE_LENGTH = 32 + 32; // bytes4 selector + empty bytes updatedHookData uint256 internal constant MAX_HOOK_RESPONSE_LENGTH = MIN_HOOK_RESPONSE_LENGTH + MAX_HOOK_DATA_LENGTH; // bytes4 selector + bytes hookData + uint24 lpFeeOverride /// @dev Thrown when the hook response is invalid. error InvalidHookResponse(GroupId groupId); /// @dev Thrown when the provided hook contract address is invalid. /// @param hookContract The address of the invalid hook contract. error InvalidHookContract(address hookContract); /// @dev Thrown when a hook call fails, providing the hook address and revert reason. /// @param hookContract The address of the hook contract that failed. /// @param revertReason The raw revert reason returned by the hook contract. error Wrap__FailedHookCall(address hookContract, bytes revertReason); /// @dev Emitted when a hook call fails. /// @param hookContract The address of the hook contract. /// @param hookType The type of hook attempted. /// @param sender The address initiating the hook. /// @param groupId The identifier of the group. /// @param reason The reason for the hook failure. event HookFailed(GroupId indexed groupId, address indexed hookContract, address indexed sender, string hookType, string reason); /** * @dev Defines the structure of permissions for various hook operations. */ struct Permissions { bool beforeMint; bool afterMint; bool beforeRedeem; bool afterRedeem; } // Permission Flags uint16 internal constant BEFORE_MINT_FLAG = 1 << 0; uint16 internal constant AFTER_MINT_FLAG = 1 << 1; uint16 internal constant BEFORE_REDEEM_FLAG = 1 << 2; uint16 internal constant AFTER_REDEEM_FLAG = 1 << 3; // Mask of all possible flags uint16 internal constant ALL_HOOK_MASK = BEFORE_MINT_FLAG | AFTER_MINT_FLAG | BEFORE_REDEEM_FLAG | AFTER_REDEEM_FLAG; // ========================= // ==== Permission Functions ===== // ========================= /** * @dev Packs the permissions into a uint16. * @param flags The Permissions struct containing individual permission flags. * @return permissions The packed permissions as a uint16. */ function packPermissions(Permissions memory flags) internal pure returns (uint16 permissions) { if (flags.beforeMint) permissions |= BEFORE_MINT_FLAG; if (flags.afterMint) permissions |= AFTER_MINT_FLAG; if (flags.beforeRedeem) permissions |= BEFORE_REDEEM_FLAG; if (flags.afterRedeem) permissions |= AFTER_REDEEM_FLAG; } // ========================= // ==== Hook Contract Validation ===== // ========================= /** * @dev Validates the permissions of a hook contract during initialization. * @param self The IHooks contract. * @param expectedPermissions The expected uint16 packed permissions */ function validateHookPermissions(IHooks self, uint16 expectedPermissions) internal view { uint16 actualPermissions = getHookContractPermissions(self); if (actualPermissions != expectedPermissions) { InvalidHookContract.selector.revertWith(address(self)); } } /** * @dev Checks if a hook contract is valid during initialization. * @param self The IHooks contract. * @param fee The fee associated with the hook. * @return True if the hook contract is valid. */ function isValidHookContract(IHooks self, uint24 fee) internal view returns (bool) { if (address(self) == address(0)) { // If there is no hook contract set, then fee cannot be dynamic return !fee.isDynamicFee(); } else { // If a hook contract is set, it must have at least 1 flag set, or have a dynamic fee uint16 permissions = getHookContractPermissions(self); return (permissions & ALL_HOOK_MASK != 0) || fee.isDynamicFee(); } } /** * @dev Retrieves the permissions from the hook contract. * @param self The IHooks contract. * @return The permissions as a uint16. */ function getHookContractPermissions(IHooks self) internal view returns (uint16) { (bool success, bytes memory data) = address(self).staticcall(abi.encodeWithSelector(IHooks.getHookPermissions.selector)); if (!success || data.length == 0) { InvalidHookContract.selector.bubbleUpAndRevertWith(address(self)); } uint16 permissions = abi.decode(data, (uint16)); return permissions; } /** * @dev Checks if the given permissions include the specified flag. * @param actualPermissions The actual permissions represented as a uint256. * @param flag The permission flag to check. * @return True if the flag is set in the actualPermissions. */ function hasPermission(uint256 actualPermissions, uint16 flag) internal pure returns (bool) { return uint16(actualPermissions) & flag != 0; } // ========================= // ==== Hook Invocation ===== // ========================= /** * @dev Calls the hook contract with the provided data. * @param self The IHooks contract. * @param data The calldata to be sent to the hook contract. * @return result The result returned by the hook contract. */ function callHook(IHooks self, bytes memory data) internal returns (bytes memory result) { bool success; assembly ("memory-safe") { success := call(gas(), self, 0, add(data, 32), mload(data), 0, 0) let size := returndatasize() result := mload(0x40) // Load the free memory pointer mstore(0x40, add(result, and(add(size, 63), not(31)))) // Update free memory pointer mstore(result, size) // Set the length of result returndatacopy(add(result, 32), 0, size) // Copy return data } if (!success) { if (!success) Wrap__FailedHookCall.selector.bubbleUpAndRevertWith(address(self)); } } // ========================= // ==== Hook Operations ===== // ========================= /** * @dev Modifier to prevent self-calls to the hook contract. * @param self The IHooks contract. */ modifier noSelfCall(IHooks self) { if (msg.sender != address(self)) { _; } } /** * @dev Invokes the beforeMint hook if the permission is set. * @param self The IHooks contract. * @param groupId The GroupId for the operation. * @param context The operation context. */ function beforeMint(IHooks self, GroupId groupId, DOperationContext.OperationContext memory context) internal noSelfCall(self) { if (!hasPermission(context.getHookPermissions(), BEFORE_MINT_FLAG)) { return; } bytes memory hookData = context.getHookData(); uint24 contextFee = context.getContextFee(); bool isDynamic = contextFee & GroupFeeLibrary.DYNAMIC_FEE_FLAG != 0; bool isDelegate = contextFee & GroupFeeLibrary.DELEGATE_FEE_FLAG != 0; bytes memory data = abi.encodeWithSelector(IHooks.beforeMint.selector, msg.sender, groupId, context, hookData); bytes memory result = callHook(self, data); if (result.length == 0) { // Hook call failed or returned empty result, proceed without changing context return; } // Expected result is (bytes4 selector, bytes updatedHookData, 24 lpFeeOverride) /// @notice Each primitive type gets padded to 32 bytes + bytes if (result.length < MIN_HOOK_RESPONSE_LENGTH + MIN_HOOK_RESPONSE_LENGTH || result.length > MAX_HOOK_RESPONSE_LENGTH) { InvalidHookResponse.selector.revertWith(context.groupId); } (bytes4 returnedSelector, bytes memory updatedHookData, uint24 lpFeeOverrideUint) = abi.decode(result, (bytes4, bytes, uint24)); if (returnedSelector != IHooks.beforeMint.selector) { InvalidHookResponse.selector.revertWith(context.groupId); } (bool isValid, uint24 lpFeeOverride) = _sanitizeAndProcessFeeOverride(lpFeeOverrideUint); if (isValid && lpFeeOverride > 0 && (isDynamic || isDelegate)) { // Update the fee in the context context.setOverriddenFee(lpFeeOverride); } // Update hookData in context setHookData(context, updatedHookData); } /** * @dev Sanitizes and processes the fee override value. * @param _lpFeeOverrideUint The fee override value to sanitize. * @return isValid True if the fee override is valid, false otherwise. * @return fee The sanitized fee override value. */ function _sanitizeAndProcessFeeOverride(uint24 _lpFeeOverrideUint) internal pure returns (bool, uint24) { uint24 sanitizedFee = _lpFeeOverrideUint & GroupFeeLibrary.REMOVE_ALL_FLAGS_MASK; // If ZERO_FEE_FLAG is set, REQUIRE fee amount to be 0 if (_lpFeeOverrideUint & GroupFeeLibrary.ZERO_FEE_FLAG != 0) { // If hook claims zero fee but sends non-zero amount, reject it if (sanitizedFee != 0) { return (false, 0); } return (true, GroupFeeLibrary.ZERO_FEE_FLAG); } // For non-zero fee cases return (sanitizedFee > 0, sanitizedFee); } /** * @dev Invokes the afterMint hook if the permission is set. * @param self The IHooks contract. * @param groupId The GroupId for the operation. * @param context The operation context. */ function afterMint(IHooks self, GroupId groupId, DOperationContext.OperationContext memory context) internal noSelfCall(self) { if (!hasPermission(context.getHookPermissions(), AFTER_MINT_FLAG)) { return; } bytes memory hookData = context.getHookData(); bytes memory data = abi.encodeWithSelector(IHooks.afterMint.selector, msg.sender, groupId, context, hookData); bytes memory result = callHook(self, data); if (result.length == 0) { // Hook call failed or returned empty result, proceed without changing context return; } // Expected result is (bytes4 selector, bytes updatedHookData) /// @notice Each primitive type gets padded to 32 bytes + bytes if (result.length < MIN_HOOK_RESPONSE_LENGTH || result.length > MAX_HOOK_RESPONSE_LENGTH) { InvalidHookResponse.selector.revertWith(context.groupId); } (bytes4 returnedSelector, bytes memory updatedHookData) = abi.decode(result, (bytes4, bytes)); if (returnedSelector != IHooks.afterMint.selector) { InvalidHookResponse.selector.revertWith(context.groupId); } // Update hookData in context setHookData(context, updatedHookData); } /** * @dev Invokes the beforeRedeem hook if the permission is set. * @param self The IHooks contract. * @param groupId The GroupId for the operation. * @param context The operation context. */ function beforeRedeem(IHooks self, GroupId groupId, DOperationContext.OperationContext memory context) internal noSelfCall(self) { if (!hasPermission(context.getHookPermissions(), BEFORE_REDEEM_FLAG)) { return; } bytes memory hookData = context.getHookData(); uint24 contextFee = context.getContextFee(); bool isDynamic = contextFee & GroupFeeLibrary.DYNAMIC_FEE_FLAG != 0; bool isDelegate = contextFee & GroupFeeLibrary.DELEGATE_FEE_FLAG != 0; bytes memory data = abi.encodeWithSelector(IHooks.beforeRedeem.selector, msg.sender, groupId, context, hookData); bytes memory result = callHook(self, data); if (result.length == 0) { // Hook call failed or returned empty result, proceed without changing context return; } // Expected result is (bytes4 selector, bytes updatedHookData, uint24 lpFeeOverride) /// @notice Each primitive type gets padded to 32 bytes if (result.length < MIN_HOOK_RESPONSE_LENGTH + MIN_HOOK_RESPONSE_LENGTH || result.length > MAX_HOOK_RESPONSE_LENGTH) { InvalidHookResponse.selector.revertWith(context.groupId); } (bytes4 returnedSelector, bytes memory updatedHookData, uint24 lpFeeOverrideUint) = abi.decode(result, (bytes4, bytes, uint24)); if (returnedSelector != IHooks.beforeRedeem.selector) { InvalidHookResponse.selector.revertWith(context.groupId); } (bool isValid, uint24 lpFeeOverride) = _sanitizeAndProcessFeeOverride(lpFeeOverrideUint); if (isValid && lpFeeOverride > 0 && lpFeeOverride != context.getContextFee() && (isDynamic || isDelegate)) { // Update the fee in the context context.setOverriddenFee(lpFeeOverride); } // Update hookData in context setHookData(context, updatedHookData); } /** * @dev Invokes the afterRedeem hook if the permission is set. * @param self The IHooks contract. * @param groupId The GroupId for the operation. * @param context The operation context. */ function afterRedeem(IHooks self, GroupId groupId, DOperationContext.OperationContext memory context) internal noSelfCall(self) { if (!hasPermission(context.getHookPermissions(), AFTER_REDEEM_FLAG)) { return; } bytes memory hookData = context.getHookData(); bytes memory data = abi.encodeWithSelector(IHooks.afterRedeem.selector, msg.sender, groupId, context, hookData); bytes memory result = callHook(self, data); if (result.length == 0) { // Hook call failed or returned empty result, proceed without changing context return; } // Expected result is (bytes4 selector, bytes updatedHookData) /// @notice Each primitive type gets padded to 32 bytes + bytes if (result.length < MIN_HOOK_RESPONSE_LENGTH || result.length > MAX_HOOK_RESPONSE_LENGTH) { InvalidHookResponse.selector.revertWith(context.groupId); } (bytes4 returnedSelector, bytes memory updatedHookData) = abi.decode(result, (bytes4, bytes)); if (returnedSelector != IHooks.afterRedeem.selector) { InvalidHookResponse.selector.revertWith(context.groupId); } // Update hookData in context setHookData(context, updatedHookData); } // ========================= // ==== Helper Functions ===== // ========================= /** * @dev Sets the hookData in the operation context. * @param context The operation context. * @param hookData The hook data to set. */ function setHookData(DOperationContext.OperationContext memory context, bytes memory hookData) internal pure { context.setHookData(hookData); } }
// SPDX-License-Identifier: MIT pragma solidity 0.8.28; import { Currency } from "../types/Currency.sol"; import { Address, AddressLibrary } from "../types/Address.sol"; import { ITreasury } from "../interfaces/ITreasury.sol"; import { IWToken } from "../interfaces/IWToken.sol"; import { IDEXRouter } from "../interfaces/IDEXRouter.sol"; import { IRebalancePool } from "../interfaces/IRebalancePool.sol"; import { OperationContextLibrary } from "./OperationContextLibrary.sol"; import { StabilityFeeOperations } from "./StabilityFeeOperations.sol"; import { DOperationContext } from "../declarations/DOperationContext.sol"; import { DProtocol } from "../declarations/DProtocol.sol"; import { CurrencyLibrary } from "../types/Currency.sol"; import { GroupFeeLibrary } from "./GroupFeeLibrary.sol"; import { GroupId } from "../types/GroupId.sol"; import { CustomRevert } from "./CustomRevert.sol"; import { IPriceOracle } from "../interfaces/IPriceOracle.sol"; import { FullMath } from "./math/FullMath.sol"; import { OperationTypes } from "../types/CommonTypes.sol"; /** * @title ProtocolUtils * @notice Library containing utility functions for protocol operations with integrated stability and fee management. * @dev The logic here: * - baseIn represents the amount of payment tokens the user is willing to pay (not the requested baseToken amount). * - We first convert this payment token amount (baseIn) to an equivalent "base token" amount we could potentially mint. * - Then we apply treasury limits (boundedAmount). * - If the treasury can mint less than what baseIn implies, we scale down payment token usage accordingly. * - Then we calculate fees on the final "effective base token" amount. * - For ERC20 payment: we only now do a `transferFrom` for the exact required payment amount after adjusting for treasury limits and fees. * - For native token payment: we received `msg.value`. After final calculations and swaps, we refund the unused portion at the end as yieldBearingToken. * - We handle swaps, wrapping/unwrapping, and fee distributions accordingly. */ library ProtocolUtils { using CurrencyLibrary for Currency; using OperationContextLibrary for DOperationContext.OperationContext; using AddressLibrary for Address; using CustomRevert for bytes4; using StabilityFeeOperations for DOperationContext.OperationContext; /** * @dev Custom errors for better debugging and revert reasons. */ error ZeroNativeAmount(GroupId groupId); error ZeroAmount(GroupId groupId); error ZeroAddress(GroupId groupId); error NotPermitted(GroupId groupId); error InsufficientPreparedAmount(GroupId groupId); error InsufficientAllowance(GroupId groupId); error SwapOpFailed(GroupId groupId); error BaseTokenCannotBeUsedAsCollateral(GroupId groupId); error WrappedAmountIsZero(GroupId groupId); error NativeTokenWrappingNotAllowed(GroupId groupId); error UnwrappedAmountIsZero(GroupId groupId); error InvalidSwapRouter(GroupId groupId); error InvalidOperationType(GroupId groupId); error InvalidRate(GroupId groupId); error InvalidMinimumAmount(GroupId groupId, uint256 minimumAmount, uint256 amountReceived); error InvalidSlippageTolerance(GroupId groupId, uint256 minOutAmount, uint256 amountReceived); error TransferFailed(GroupId groupId, address recipient, uint256 amount); error ExcessiveSwapAmount(GroupId groupId); struct FeeSplit { uint256 directFeeAmount; // immediate fee (in yield bearing token terms) uint256 hookFeeAmount; // hook fee amount (if applicable) uint256 protocolFeeAmount; // protocol fee amount (if applicable) uint24 appliedFeeRate; // fee rate applied } uint256 private constant PRECISION = 1e18; uint256 private constant HUNDRED = 100; uint256 private constant BASIS_POINTS = 10_000; uint24 private constant MAX_SLIPPAGE_TOLERANCE = 1_000; // 10% bps uint256 private constant REFUND_SAFETY_MARGIN = HUNDRED; // 1% in bps for refunds /** * @dev Emitted when a refund happens (in yieldBearingToken). */ event Refund(address indexed account, uint256 indexed safeRefundAmount); // New event declarations /** * @dev Emitted when fees are collected. */ event FeeCollected(address indexed feeCollector, uint256 protocolFeeAmount, uint256 hookFeeAmount); /** * @dev Emitted when tokens are swapped. */ event TokensSwapped(address indexed fromToken, address indexed toToken, uint256 amountIn, uint256 amountOut); /** * @dev Emitted when `aTokens` are auto-compounded into the `RebalancePool`. * @param amountIn The amount of `aTokens` deposited. * @param amountOut The amount of tokens minted from the `RebalancePool`. * @param recipient The address receiving the tokens from the `RebalancePool`. */ event AutoCompounded(uint256 indexed amountIn, uint256 indexed amountOut, address indexed recipient); /** * @notice Prepares tokens for treasury operations when user provides `baseIn` as the payment token amount they are willing to spend. * @dev Sequence of operations: * 1. Convert payment token to base token equivalent and check bounds * 2. Scale down payment if needed based on bounds * 3. Calculate fees on effective base amount * 4. Handle payment (transfer for ERC20 or verify msg.value) * 5. Swap to yieldBearingToken * 6. Take protocol fees from received yieldBearingToken * 7. Wrap remaining yieldBearingToken to baseToken * 8. Handle excess for native payments (as yieldBearingToken) * * @param context The operation context * @param baseIn The amount of payment token user is willing to pay (msg.value if native) * @return preparedToken The base token prepared for treasury * @return finalBaseAmount The final amount after fees * @return feeSplit The fee split details */ function prepareTokenMintForTreasury( DOperationContext.OperationContext memory context, uint256 baseIn ) internal returns (Currency preparedToken, uint256 finalBaseAmount, FeeSplit memory feeSplit) { GroupId groupId = context.groupId; Currency baseToken = context.getBaseToken(); Currency yieldBearingToken = context.getYieldBearingToken(); Currency paymentToken = context.getPaymentToken(); if (baseToken.isZero() || yieldBearingToken.isZero()) { ZeroAddress.selector.revertWith(groupId); } // 1. Calculate conversions and bounds first (uint256 paymentTokenPrice, uint256 yieldBearingTokenPrice) = _getPrices(context); uint256 paymentTokenDecimals = uint256(context.getPaymentTokenDecimals()); uint256 adjustedBaseIn = (baseIn * PRECISION) / (10 ** paymentTokenDecimals); uint256 expectedBaseFromPayment = (adjustedBaseIn * paymentTokenPrice) / yieldBearingTokenPrice; // 2. Check treasury bounds StabilityFeeOperations.StabilityState memory stabilityState = StabilityFeeOperations.getStabilityState( context, expectedBaseFromPayment ); uint256 boundedAmount = stabilityState.boundedAmount; if (boundedAmount == 0) ZeroAmount.selector.revertWith(groupId); // 3. Scale payment if needed based on bounds uint256 effectiveBaseAmount; uint256 effectivePaymentAmount; if (expectedBaseFromPayment > boundedAmount) { uint256 ratio = (boundedAmount * PRECISION) / expectedBaseFromPayment; effectiveBaseAmount = boundedAmount; effectivePaymentAmount = (baseIn * ratio) / PRECISION; } else { effectiveBaseAmount = expectedBaseFromPayment; effectivePaymentAmount = baseIn; } if (effectiveBaseAmount == 0) ZeroAmount.selector.revertWith(groupId); // 4. Calculate fees based on effective base amount (uint256 amountWithoutFee, FeeSplit memory fs) = _calculateAndSplitFees(context, effectiveBaseAmount); if (amountWithoutFee == 0 && fs.directFeeAmount == 0) ZeroAmount.selector.revertWith(groupId); uint256 totalBaseRequired = amountWithoutFee + fs.directFeeAmount; uint256 requiredPaymentWithFee = _baseToPayment(totalBaseRequired, paymentTokenDecimals, paymentTokenPrice, yieldBearingTokenPrice); // 5. Handle payment transfer/verification uint256 excessNativeAmount = 0; if (!paymentToken.isNative()) { paymentToken.safeTransferFrom(context.getRecipient(), address(this), requiredPaymentWithFee); } else { if (msg.value < requiredPaymentWithFee) { InsufficientPreparedAmount.selector.revertWith(groupId); } // Calculate excess native amount excessNativeAmount = msg.value - requiredPaymentWithFee; } // 6. Swap payment to yieldBearingToken (sAVAX) uint256 yieldBearingTokenAmount = _handleTokenSwap( context, requiredPaymentWithFee, totalBaseRequired, paymentTokenPrice, yieldBearingTokenPrice, paymentTokenDecimals ); // New: Swap excess native amount to yieldBearingToken for refund uint256 excessYieldBearingTokenAmount = 0; if (excessNativeAmount > 1e2) { excessYieldBearingTokenAmount = _handleExcessNativeTokenSwap( context, excessNativeAmount, paymentTokenPrice, yieldBearingTokenPrice, paymentTokenDecimals ); } // Check if we got significantly more than needed if (yieldBearingTokenAmount > (totalBaseRequired * (HUNDRED + 1)) / HUNDRED) { // 1% threshold ExcessiveSwapAmount.selector.revertWith(groupId); } // 7. Take protocol fees from received yieldBearingToken if (fs.protocolFeeAmount > 0) { yieldBearingToken.safeTransfer(context.getFeeCollector().toAddress(), fs.protocolFeeAmount); } // Emit FeeCollected event after fees are distributed if (fs.protocolFeeAmount > 0 || fs.hookFeeAmount > 0) { emit FeeCollected(context.getFeeCollector().toAddress(), fs.protocolFeeAmount, fs.hookFeeAmount); } // 8. Wrap remaining yieldBearingToken to baseToken uint256 remainingToWrap = yieldBearingTokenAmount - fs.directFeeAmount; uint256 finalAmount = _wrapTokens(context, remainingToWrap); if (finalAmount == 0) revert InsufficientPreparedAmount(groupId); // 9. Handle excess yieldBearingToken refund uint256 remainingYieldBearing = yieldBearingTokenAmount - fs.protocolFeeAmount - remainingToWrap; uint256 totalExcessYieldBearing = remainingYieldBearing + excessYieldBearingTokenAmount; if (totalExcessYieldBearing > 1e2) { uint256 safeExcess = (totalExcessYieldBearing * REFUND_SAFETY_MARGIN) / BASIS_POINTS; yieldBearingToken.safeTransfer(context.getRecipient(), safeExcess); emit Refund(context.getRecipient(), safeExcess); } preparedToken = baseToken; finalBaseAmount = finalAmount; feeSplit = fs; return (preparedToken, finalBaseAmount, feeSplit); } /** * @notice Calculates and splits the fees according to the configured fee rates. * @dev Uses high-precision arithmetic to minimize rounding errors. * @param context The operation context. * @param boundedAmount The amount after applying treasury bounds. * @return amountWithoutFee The amount after subtracting total fees. * @return feeSplit The detailed fee split. */ function _calculateAndSplitFees( DOperationContext.OperationContext memory context, uint256 boundedAmount ) private pure returns (uint256 amountWithoutFee, FeeSplit memory feeSplit) { uint24 contextFee = context.getContextFee(); uint24 overriddenFee = context.getOverriddenFee(); // First check for zero fee flag if ((overriddenFee & GroupFeeLibrary.ZERO_FEE_FLAG) != 0) { return (boundedAmount, FeeSplit({ directFeeAmount: 0, hookFeeAmount: 0, protocolFeeAmount: 0, appliedFeeRate: 0 })); } // Get clean fees without flags uint24 cleanContextFee = contextFee & GroupFeeLibrary.REMOVE_ALL_FLAGS_MASK; uint24 cleanOverriddenFee = overriddenFee & GroupFeeLibrary.REMOVE_ALL_FLAGS_MASK; // Determine which fee to apply based on context flags uint24 appliedFeeRate; bool hasHookShare = (contextFee & GroupFeeLibrary.DELEGATE_FEE_FLAG) != 0; bool isDynamic = (contextFee & GroupFeeLibrary.DYNAMIC_FEE_FLAG) != 0; if (cleanOverriddenFee > 0 && (isDynamic || hasHookShare)) { appliedFeeRate = cleanOverriddenFee; } else { appliedFeeRate = cleanContextFee; } // Calculate total fee amount uint256 totalFeeAmount = FullMath.mulDiv(boundedAmount, appliedFeeRate, BASIS_POINTS); // Split fees based on context flags if (hasHookShare) { uint24 protocolFeeRate = context.getProtocolFee(); uint256 protocolShare = protocolFeeRate > 0 ? FullMath.mulDiv(totalFeeAmount, protocolFeeRate, BASIS_POINTS) : 0; feeSplit.protocolFeeAmount = protocolShare; feeSplit.directFeeAmount = totalFeeAmount; feeSplit.hookFeeAmount = totalFeeAmount - protocolShare; } else { feeSplit.directFeeAmount = totalFeeAmount; feeSplit.protocolFeeAmount = totalFeeAmount; feeSplit.hookFeeAmount = 0; } amountWithoutFee = boundedAmount - totalFeeAmount; feeSplit.appliedFeeRate = appliedFeeRate; return (amountWithoutFee, feeSplit); } /** * @notice Mints VT or YT tokens after we have prepared the base tokens. * @dev This function handles the final step of minting after all preparations are complete. * @param context The operation context * @param underlyingValue The underlying base token amount to use for minting * @param recipient Recipient of the minted tokens */ function mintTokens( DOperationContext.OperationContext memory context, uint256 underlyingValue, address recipient ) internal returns (DProtocol.MintResult memory result) { ITreasury treasury = ITreasury(context.getTreasury().toAddress()); IRebalancePool rebalancePool = IRebalancePool(context.getRebalancePool().toAddress()); if (address(treasury) == address(0) || address(rebalancePool) == address(0)) { revert ZeroAddress(context.groupId); } StabilityFeeOperations.StabilityState memory stabilityState = StabilityFeeOperations.getStabilityState(context, underlyingValue); uint8 operationType = context.getOperationType(); Currency baseToken = context.getBaseToken(); uint256 currentAllowance = baseToken.allowance(address(this), address(treasury)); if (currentAllowance < stabilityState.boundedAmount) { baseToken.safeIncreaseAllowance(address(treasury), stabilityState.boundedAmount - currentAllowance); } if (operationType == uint8(OperationTypes.OP_TYPE_MINT_VT)) { result.vtMinted = _handleVTMinting(context, stabilityState.boundedAmount, recipient, treasury); } else if (operationType == uint8(OperationTypes.OP_TYPE_MINT_YT)) { result.ytMinted = _handleYTMinting(context, stabilityState.boundedAmount, recipient, treasury, rebalancePool); } else { InvalidOperationType.selector.revertWith(context.groupId); } context.setAmount1(result.vtMinted > 0 ? result.vtMinted : result.ytMinted); return result; } /** * @notice Handles the minting of VT tokens. * @param context The operation context. * @param amount The amount of base tokens to mint. * @param recipient The recipient of the minted VT tokens. * @param treasury The treasury contract. * @return mintedAmount The amount of VT tokens minted. */ function _handleVTMinting( DOperationContext.OperationContext memory context, uint256 amount, address recipient, ITreasury treasury ) private returns (uint256 mintedAmount) { mintedAmount = treasury.mintXToken(context.groupId, amount, recipient); if (mintedAmount == 0) { revert InvalidMinimumAmount(context.groupId, amount, mintedAmount); } return mintedAmount; } /** * @notice Mints YT tokens by depositing `aTokens` into the `RebalancePool`. * @param context The operation context. * @param amount The amount of base tokens to mint. * @param recipient The recipient of the minted YT tokens. * @param treasury The treasury contract. * @param rebalancePool The rebalance pool contract. * @return mintedAmount The amount of YT tokens minted. */ function _handleYTMinting( DOperationContext.OperationContext memory context, uint256 amount, address recipient, ITreasury treasury, IRebalancePool rebalancePool ) private returns (uint256 mintedAmount) { uint256 aTokensMinted = treasury.mintAToken(context.groupId, amount, address(this)); if (aTokensMinted == 0) { revert InvalidMinimumAmount(context.groupId, amount, aTokensMinted); } Currency aToken = context.getAToken(); uint256 currentAllowance = aToken.allowance(address(this), address(rebalancePool)); if (currentAllowance < aTokensMinted) { aToken.safeIncreaseAllowance(address(rebalancePool), aTokensMinted - currentAllowance); } mintedAmount = rebalancePool.deposit(aTokensMinted, recipient); // Emit AutoCompounded event after depositing into the RebalancePool emit AutoCompounded(aTokensMinted, mintedAmount, recipient); return mintedAmount; } /** * @notice Redeems VT or YT tokens, applying stability checks, fees, and unwrapping. * @dev The sequence here is: * 1. Redeem tokens from treasury/pool * 2. Unwrap to yieldBearingToken if needed * 3. Calculate and take fees */ function redeemTokens( DOperationContext.OperationContext memory context, uint256 receivedAmount, address from ) internal returns (Currency preparedToken, uint256 preparedAmount, FeeSplit memory feeSplit) { Currency baseToken = context.getBaseToken(); if (baseToken.isZero()) { ZeroAddress.selector.revertWith(context.groupId); } StabilityFeeOperations.StabilityState memory stabilityState = StabilityFeeOperations.getStabilityState(context, receivedAmount); // First handle the redemption uint256 redeemedAmount = _handleTokenRedemption(context, stabilityState.boundedAmount, from); // Then unwrap the redeemed amount uint256 unwrappedAmount = _unwrapTokens(context, redeemedAmount); // Calculate fees on the unwrapped amount (preparedAmount, feeSplit) = _calculateAndSplitFees(context, unwrappedAmount); // Double check that we have enough balance after fees uint256 actualBalance = context.getYieldBearingToken().balanceOf(address(this)); if (actualBalance < feeSplit.protocolFeeAmount + preparedAmount) { InsufficientPreparedAmount.selector.revertWith(context.groupId); } // Take protocol fees if (feeSplit.protocolFeeAmount > 0) { context.getYieldBearingToken().safeTransfer(context.getFeeCollector().toAddress(), feeSplit.protocolFeeAmount); } preparedToken = context.getYieldBearingToken(); return (preparedToken, preparedAmount, feeSplit); } function _handleTokenRedemption( DOperationContext.OperationContext memory context, uint256 amount, address from ) private returns (uint256 redeemedAmount) { uint8 operationType = context.getOperationType(); ITreasury treasury = ITreasury(context.getTreasury().toAddress()); if (operationType == uint8(OperationTypes.OP_TYPE_REDEEM_VT)) { redeemedAmount = _handleVTRedemption(context, amount, from, treasury); } else if (operationType == uint8(OperationTypes.OP_TYPE_REDEEM_YT)) { redeemedAmount = _handleYTRedemption(context, amount, from); } else { InvalidOperationType.selector.revertWith(context.groupId); } return redeemedAmount; } function _handleVTRedemption( DOperationContext.OperationContext memory context, uint256 amount, address from, ITreasury treasury ) private returns (uint256 redeemedAmount) { Currency vtToken = context.getXToken(); if (vtToken.isZero()) revert ZeroAddress(context.groupId); vtToken.safeTransferFrom(from, address(this), amount); uint256 currentAllowance = vtToken.allowance(address(this), address(treasury)); if (currentAllowance < amount) { vtToken.safeIncreaseAllowance(address(treasury), amount - currentAllowance); } redeemedAmount = treasury.redeem(context.groupId, 0, amount, address(this)); return redeemedAmount; } function _handleYTRedemption( DOperationContext.OperationContext memory context, uint256 amount, address from ) private returns (uint256 redeemedAmount) { IRebalancePool rebalancePool = IRebalancePool(context.getRebalancePool().toAddress()); if (address(rebalancePool) == address(0)) revert ZeroAddress(context.groupId); uint256 aTokensRedeemed = rebalancePool.redeem(amount, address(this), from); if (aTokensRedeemed == 0) { InsufficientPreparedAmount.selector.revertWith(context.groupId); } ITreasury treasury = ITreasury(context.getTreasury().toAddress()); Currency aToken = context.getAToken(); uint256 currentAllowance = aToken.allowance(address(this), address(treasury)); if (currentAllowance < aTokensRedeemed) { aToken.safeIncreaseAllowance(address(treasury), aTokensRedeemed - currentAllowance); } redeemedAmount = treasury.redeem(context.groupId, aTokensRedeemed, 0, address(this)); return redeemedAmount; } function _wrapTokens(DOperationContext.OperationContext memory context, uint256 amountToWrap) private returns (uint256 wrappedAmount) { Currency baseToken = context.getBaseToken(); Currency yieldBearingToken = context.getYieldBearingToken(); IWToken wToken = IWToken(baseToken.toAddress()); uint256 currentAllowance = yieldBearingToken.allowance(address(this), address(wToken)); if (currentAllowance < amountToWrap) { yieldBearingToken.safeIncreaseAllowance(address(wToken), amountToWrap - currentAllowance); } try wToken.wrap(amountToWrap) returns (uint256 actualWrappedAmount) { wrappedAmount = actualWrappedAmount; } catch { SwapOpFailed.selector.revertWith(context.groupId); } if (wrappedAmount == 0) { WrappedAmountIsZero.selector.revertWith(context.groupId); } return wrappedAmount; } function _unwrapTokens( DOperationContext.OperationContext memory context, uint256 amountToUnwrap ) private returns (uint256 unwrappedAmount) { if (!context.isWrappingRequired()) { return amountToUnwrap; } Currency baseToken = context.getBaseToken(); IWToken wToken = IWToken(baseToken.toAddress()); try wToken.unwrap(amountToUnwrap) returns (uint256 actualUnwrappedAmount) { unwrappedAmount = actualUnwrappedAmount; } catch { SwapOpFailed.selector.revertWith(context.groupId); } if (unwrappedAmount == 0) { revert UnwrappedAmountIsZero(context.groupId); } return unwrappedAmount; } /** * @notice Convert base token amount (yieldBearing scale) to payment token amount using prices. */ function _baseToPayment( uint256 baseAmount, // in yieldBearing scale (1e18) uint256 paymentTokenDecimals, uint256 paymentTokenPrice, uint256 yieldBearingTokenPrice ) private pure returns (uint256) { uint256 rawPayment = (baseAmount * yieldBearingTokenPrice) / paymentTokenPrice; uint256 factor = 10 ** paymentTokenDecimals; return (rawPayment * factor) / PRECISION; } /** * @notice Retrieves token prices from oracle. */ function _getPrices( DOperationContext.OperationContext memory context ) private view returns (uint256 paymentTokenPrice, uint256 yieldBearingTokenPrice) { IPriceOracle priceOracle = IPriceOracle(context.getPriceOracle().toAddress()); Currency paymentToken = context.getPaymentToken(); Currency yieldBearingToken = context.getYieldBearingToken(); bool validPaymentToken; bool validYieldBearingToken; (validPaymentToken, paymentTokenPrice) = priceOracle.getPrice(paymentToken.toAddress()); (validYieldBearingToken, yieldBearingTokenPrice) = priceOracle.getPrice(yieldBearingToken.toAddress()); if (paymentTokenPrice == 0 || yieldBearingTokenPrice == 0 || !validPaymentToken || !validYieldBearingToken) { InvalidRate.selector.revertWith(context.groupId); } } /** * @notice Performs token swap from paymentToken to yieldBearingToken. * @param context The operation context * @param paymentIn Payment token amount we have after calculations * @param totalBaseRequired The total yieldBearingToken needed * @return yieldBearingTokenAmount The yieldBearingToken obtained after swap */ function _handleTokenSwap( DOperationContext.OperationContext memory context, uint256 paymentIn, uint256 totalBaseRequired, uint256 paymentTokenPrice, uint256 yieldBearingTokenPrice, uint256 paymentTokenDecimals ) private returns (uint256 yieldBearingTokenAmount) { Currency paymentToken = context.getPaymentToken(); Currency yieldBearingToken = context.getYieldBearingToken(); if (paymentToken.equals(yieldBearingToken)) { // No swap needed return totalBaseRequired; } // Slippage check uint256 slippageRate = uint256(context.getSlippageRate()); if (slippageRate > MAX_SLIPPAGE_TOLERANCE) { revert InvalidSlippageTolerance(context.groupId, MAX_SLIPPAGE_TOLERANCE, slippageRate); } // Calculate expected output and minimum acceptable amount uint256 adjustedPaymentIn = (paymentIn * PRECISION) / (10 ** paymentTokenDecimals); uint256 expectedOut = (adjustedPaymentIn * paymentTokenPrice) / yieldBearingTokenPrice; uint256 minOut = (expectedOut * (BASIS_POINTS - slippageRate)) / BASIS_POINTS; // Ensure minOut doesn't exceed what we actually need if (minOut > totalBaseRequired) { minOut = totalBaseRequired; } // Perform swap yieldBearingTokenAmount = swapTokens(context, paymentToken, yieldBearingToken, paymentIn, minOut, paymentToken.isNative()); return yieldBearingTokenAmount; } function _handleExcessNativeTokenSwap( DOperationContext.OperationContext memory context, uint256 excessNativeAmount, uint256 paymentTokenPrice, uint256 yieldBearingTokenPrice, uint256 paymentTokenDecimals ) private returns (uint256 yieldBearingTokenAmount) { Currency paymentToken = context.getPaymentToken(); Currency yieldBearingToken = context.getYieldBearingToken(); // Calculate expected output and minimum acceptable amount for the excess uint256 adjustedPaymentIn = (excessNativeAmount * PRECISION) / (10 ** paymentTokenDecimals); uint256 expectedOut = (adjustedPaymentIn * paymentTokenPrice) / yieldBearingTokenPrice; uint256 minOut = (expectedOut * (BASIS_POINTS - MAX_SLIPPAGE_TOLERANCE)) / BASIS_POINTS; // Perform swap for excess native token to yieldBearingToken yieldBearingTokenAmount = swapTokens(context, paymentToken, yieldBearingToken, excessNativeAmount, minOut, paymentToken.isNative()); return yieldBearingTokenAmount; } /** * @notice Executes token swaps through DEX router with optimal path selection. * @dev Handles native token wrapping/unwrapping automatically. */ function swapTokens( DOperationContext.OperationContext memory context, Currency fromToken, Currency toToken, uint256 amount, uint256 minAmountOut, bool isNative ) private returns (uint256 swappedAmount) { address swapRouter = context.getSwapRouter().toAddress(); if (swapRouter == address(0)) InvalidSwapRouter.selector.revertWith(context.groupId); IDEXRouter router = IDEXRouter(swapRouter); uint256 deadline = block.timestamp + 5; address wethAddress = context.getGroupWethAddress(); address[] memory path; if (fromToken.isNative()) { path = new address[](2); path[0] = wethAddress; path[1] = toToken.toAddress(); require(toToken.equals(context.getYieldBearingToken()), "Only native to yieldBearingToken swaps supported"); uint256[] memory amounts = router.swapExactETHForTokens{ value: amount }(minAmountOut, path, address(this), deadline); swappedAmount = amounts[amounts.length - 1]; } else if (isNative) { path = new address[](2); path[0] = fromToken.toAddress(); path[1] = wethAddress; _approveRouter(fromToken, swapRouter, amount); uint256[] memory amounts = router.swapExactTokensForETH(amount, minAmountOut, path, address(this), deadline); swappedAmount = amounts[amounts.length - 1]; } else { if (fromToken.toAddress() == wethAddress || toToken.toAddress() == wethAddress) { path = new address[](2); path[0] = fromToken.toAddress(); path[1] = toToken.toAddress(); } else { path = new address[](3); path[0] = fromToken.toAddress(); path[1] = wethAddress; path[2] = toToken.toAddress(); } _approveRouter(fromToken, swapRouter, amount); uint256[] memory amounts = router.swapExactTokensForTokens(amount, minAmountOut, path, address(this), deadline); swappedAmount = amounts[amounts.length - 1]; } if (swappedAmount < minAmountOut) { revert InvalidMinimumAmount(context.groupId, minAmountOut, swappedAmount); } // Emit TokensSwapped event after a successful swap emit TokensSwapped(fromToken.toAddress(), toToken.toAddress(), amount, swappedAmount); return swappedAmount; } function _approveRouter(Currency tokenContract, address router, uint256 amount) private { uint256 currentAllowance = tokenContract.allowance(address(this), router); if (currentAllowance < amount) { tokenContract.safeIncreaseAllowance(router, amount - currentAllowance); } } /** * @notice Converts and sends tokens to the desired collateral type if needed. * @dev Important: Base token cannot be used as collateral. */ function convertToDesiredCollateralAndSend( DOperationContext.OperationContext memory context, uint256 baseAmountWithoutFee, address recipient ) internal returns (uint256 swappedAmount) { if (baseAmountWithoutFee == 0) { UnwrappedAmountIsZero.selector.revertWith(context.groupId); } Currency baseToken = context.getBaseToken(); Currency yieldBearingToken = context.getYieldBearingToken(); Currency desiredCollateral = context.getPaymentToken(); if (desiredCollateral.equals(baseToken)) { BaseTokenCannotBeUsedAsCollateral.selector.revertWith(context.groupId); } // If user wants yieldBearingToken back, just transfer it if (desiredCollateral.equals(yieldBearingToken)) { yieldBearingToken.safeTransfer(recipient, baseAmountWithoutFee); return baseAmountWithoutFee; } bool isNative = desiredCollateral.isNative(); uint256 slippageRate = uint256(context.getSlippageRate()); if (slippageRate > MAX_SLIPPAGE_TOLERANCE) { revert InvalidSlippageTolerance(context.groupId, MAX_SLIPPAGE_TOLERANCE, slippageRate); } // Get prices for conversion IPriceOracle priceOracle = IPriceOracle(context.getPriceOracle().toAddress()); (bool isValidYieldBearingTokenPrice, uint256 yieldBearingTokenPrice) = priceOracle.getPrice(yieldBearingToken.toAddress()); (bool isValidCollateralPrice, uint256 desiredCollateralPrice) = priceOracle.getPrice(desiredCollateral.toAddress()); if (!isValidYieldBearingTokenPrice || !isValidCollateralPrice || yieldBearingTokenPrice == 0 || desiredCollateralPrice == 0) { InvalidRate.selector.revertWith(context.groupId); } uint8 baseTokenDecimals = context.getBaseTokenDecimals(); uint256 desiredCollateralDecimals = uint256(context.getPaymentTokenDecimals()); // --------------------------------------------------------------------- // 1) Normalize baseAmountWithoutFee from baseTokenDecimals up to 1e18 // --------------------------------------------------------------------- // If baseTokenDecimals == 18, this is effectively just `baseAmountWithoutFee`. // Otherwise we scale it so the math against yieldBearingTokenPrice and // desiredCollateralPrice (both typically at 1e18) stays consistent. // --------------------------------------------------------------------- uint256 normalizedBaseAmount = (baseAmountWithoutFee * PRECISION) / (10 ** baseTokenDecimals); // --------------------------------------------------------------------- // 2) Calculate expectedOutAmount with prices at 1e18 // --------------------------------------------------------------------- // Now normalizedBaseAmount is in 1e18. Multiplying by yieldBearingTokenPrice // and dividing by desiredCollateralPrice keeps the math in 1e18 as well. // --------------------------------------------------------------------- uint256 expectedOutAmount = (normalizedBaseAmount * yieldBearingTokenPrice) / desiredCollateralPrice; // --------------------------------------------------------------------- // 3) Apply slippage to get minOutAmount still in 1e18 // --------------------------------------------------------------------- uint256 minOutAmount = (expectedOutAmount * (BASIS_POINTS - slippageRate)) / BASIS_POINTS; // --------------------------------------------------------------------- // 4) Convert minOutAmount from 1e18 down to desiredCollateralDecimals // --------------------------------------------------------------------- uint256 minAmountOutInDesiredDecimals = (minOutAmount * (10 ** desiredCollateralDecimals)) / PRECISION; swappedAmount = swapTokens( context, yieldBearingToken, desiredCollateral, baseAmountWithoutFee, minAmountOutInDesiredDecimals, isNative ); if (swappedAmount == 0) { TransferFailed.selector.revertWith(context.groupId, recipient, swappedAmount); } // If paying out a native token (e.g. ETH), we have to forward it // and adjust swappedAmount in yieldBearingToken terms for accounting. if (isNative) { uint256 yieldBearingEquivalent = (((swappedAmount * PRECISION) / (10 ** desiredCollateralDecimals)) * desiredCollateralPrice) / yieldBearingTokenPrice; (bool success, ) = recipient.call{ value: swappedAmount }(""); require(success, "ETH transfer failed"); swappedAmount = yieldBearingEquivalent; // Return the yieldBearingToken equivalent } else { // For standard ERC20 tokens, just transfer the swapped tokens desiredCollateral.safeTransfer(recipient, swappedAmount); } context.setAmount1(swappedAmount); return swappedAmount; } }
// SPDX-License-Identifier: MIT pragma solidity 0.8.28; import { DOperationContext } from "../declarations/DOperationContext.sol"; import { Slot0, Slot0Library } from "../types/Slot0.sol"; import { Currency, CurrencyLibrary } from "../types/Currency.sol"; import { GroupId } from "../types/GroupId.sol"; import { CustomRevert } from "./CustomRevert.sol"; // import { DTokenRegistry } from "../declarations/DTokenRegistry.sol"; import { Address, AddressLibrary } from "../types/Address.sol"; import { GroupState, FeePermissions, DefaultFeeParams, OperationTypes } from "../types/CommonTypes.sol"; import { GroupStateHelper, GroupSettings } from "../types/GroupStateHelper.sol"; import { GroupFeeLibrary } from "../libs/GroupFeeLibrary.sol"; import { IRateProvider } from "../interfaces/IRateProvider.sol"; import { ITreasury } from "../interfaces/ITreasury.sol"; /** * @title OperationContextLibrary * @notice Library for creating and managing operation contexts within the protocol. */ library OperationContextLibrary { using Slot0Library for Slot0; using GroupStateHelper for GroupState; using GroupStateHelper for GroupSettings; using AddressLibrary for Address; using CurrencyLibrary for Currency; using CustomRevert for bytes4; // Custom Errors error ZeroAddress(GroupId groupId); error ZeroAmount(GroupId groupId); error CycleOperationsAreNotPermitted(GroupId groupId); error InvalidCollateralRatio(GroupId groupId); error ErrorMintingPausedInStabilityMode(GroupId groupId); error InvalidOperationType(GroupId groupId); error ErrorGettingRate(GroupId groupId, uint256 rate); error InvalidSlippageTolerance(GroupId groupId, uint256 minAmount1, uint256 amount0); error ArbitraryCallsNotAllowed(GroupId groupId, address recipient, address sender); // Constants uint256 private constant PRECISION = 1e18; uint24 private constant MAX_SLIPPAGE_TOLERANCE = 1_000; // 10% bps uint24 private constant BASIS_POINTS = 10_000; /** * @notice Creates an operation context. * @param params Struct containing parameters needed to create the context. * @return context The created operation context. */ function createContext( DOperationContext.Context memory params ) internal view returns (DOperationContext.OperationContext memory context) { // Validate recipient and amounts _validateParams(params); // Initialize context without applying slippage checks context = _initializeContext(params); // Retrieve operation type uint8 operationType = params.operationType; // Get stability ratios uint96 stabilityRatio = _getStabilityRatio(params); uint96 stabilityConditionsTriggeringRate = _getstabilityConditionsTriggeringRate(params); // Retrieve collateral ratio and validate context.collateralRatio = _getCollateralRatio(params, stabilityRatio); // Calculate context fee & override fee depending on the group's permissions uint24 contextFee = _calculateContextFee(params, operationType, context.collateralRatio, stabilityConditionsTriggeringRate); // Get initial protocol fee uint24 initialProtocolFee = GroupStateHelper.getProtocolFee(params.groupState.feesPacked); // Pack Slot0 and Slot1 context.params = _createParams(params, operationType, initialProtocolFee, contextFee, stabilityConditionsTriggeringRate); } // ------ Helper Functions ------ // /** * @notice Validates the parameters for creating an operation context. * @param params Struct containing parameters needed to create the context. */ function _validateParams(DOperationContext.Context memory params) internal pure { if (params.recipient == address(0)) { ZeroAddress.selector.revertWith(params.groupId); } if (params.amounts.amount0 == 0 && params.amounts.amount1 == 0) { ZeroAmount.selector.revertWith(params.groupId); } if (params.slippage > MAX_SLIPPAGE_TOLERANCE) { revert InvalidSlippageTolerance(params.groupId, params.slippage, MAX_SLIPPAGE_TOLERANCE); } } /** * @notice Initializes the operation context without applying slippage checks. * @param params Struct containing parameters needed to create the context. * @return context The initialized operation context. */ function _initializeContext( DOperationContext.Context memory params ) internal pure returns (DOperationContext.OperationContext memory context) { context.groupId = params.groupId; context.amount0 = params.amounts.amount0; context.amount1 = params.amounts.amount1; context.recipient = params.recipient; context.paymentToken = params.paymentToken; context.slippage = _validateSlippage(params.groupId, params.slippage); context.groupState = DOperationContext.TightGroupState({ core: params.groupState.core, extended: params.groupState.extended, groupSettings: params.groupState.groupSettings, feesPacked: params.groupState.feesPacked, hookContract: params.groupState.hookContract }); } /** * @notice Validates the slippage value. * @param groupId The group ID. * @param slippage The slippage value to validate. * @return The validated slippage value. */ function _validateSlippage(GroupId groupId, uint24 slippage) internal pure returns (uint24) { if (slippage > MAX_SLIPPAGE_TOLERANCE) { revert InvalidSlippageTolerance(groupId, slippage, MAX_SLIPPAGE_TOLERANCE); } return slippage; } /** * @notice Retrieves the collateral ratio and validates it. * @param params Struct containing parameters needed to create the context. * @param stabilityRatio The stability ratio. * @return collateralRatio The validated collateral ratio. */ function _getCollateralRatio( DOperationContext.Context memory params, uint96 stabilityRatio ) internal view returns (uint256 collateralRatio) { ITreasury treasury = ITreasury(params.groupState.extended.treasury.toAddress()); try treasury.collateralRatio(params.groupId) returns (uint256 _collateralRatio) { collateralRatio = _collateralRatio; if (collateralRatio <= uint256(stabilityRatio)) { ErrorMintingPausedInStabilityMode.selector.revertWith(params.groupId); } } catch { InvalidCollateralRatio.selector.revertWith(params.groupId); } } /** * @notice Calculates the context fee. * @param params Struct containing parameters needed to create the context. * @param operationType The operation type. * @param collateralRatio The collateral ratio. * @param stabilityConditionsTriggeringRate The stability conditions triggering rate. * @return contextFee The calculated context fee. */ function _calculateContextFee( DOperationContext.Context memory params, uint8 operationType, uint256 collateralRatio, uint96 stabilityConditionsTriggeringRate ) internal pure returns (uint24 contextFee) { bool isStabilityMode = collateralRatio < uint256(stabilityConditionsTriggeringRate); if (isStabilityMode) { contextFee = _getStabilityFee(params.groupId, params.groupState.feesPacked, operationType); } else { contextFee = _getRegularFee(params.groupId, params.groupState.feesPacked, operationType); } contextFee = _applyFeePermissions(contextFee, params.groupState.groupSettings); return contextFee; } /** * @notice Retrieves the stability fee. * @param groupId The group ID. * @param feesPacked The packed fees. * @param operationType The operation type. * @return The stability fee. */ function _getStabilityFee(GroupId groupId, bytes32 feesPacked, uint8 operationType) internal pure returns (uint24) { if (operationType == uint8(OperationTypes.OP_TYPE_MINT_VT)) { return GroupStateHelper.getStabilityMintFeeVT(feesPacked); } else if (operationType == uint8(OperationTypes.OP_TYPE_MINT_YT)) { return GroupStateHelper.getStabilityMintFeeYT(feesPacked); } else if (operationType == uint8(OperationTypes.OP_TYPE_REDEEM_VT)) { return GroupStateHelper.getStabilityRedeemFeeVT(feesPacked); } else if (operationType == uint8(OperationTypes.OP_TYPE_REDEEM_YT)) { return GroupStateHelper.getStabilityRedeemFeeYT(feesPacked); } else { InvalidOperationType.selector.revertWith(groupId); } } /** * @notice Retrieves the regular fee. * @param groupId The group ID. * @param feesPacked The packed fees. * @param operationType The operation type. * @return The regular fee. */ function _getRegularFee(GroupId groupId, bytes32 feesPacked, uint8 operationType) internal pure returns (uint24) { if (operationType == uint8(OperationTypes.OP_TYPE_MINT_VT)) { return GroupStateHelper.getMintFeeVT(feesPacked); } else if (operationType == uint8(OperationTypes.OP_TYPE_MINT_YT)) { return GroupStateHelper.getMintFeeYT(feesPacked); } else if (operationType == uint8(OperationTypes.OP_TYPE_REDEEM_VT)) { return GroupStateHelper.getRedeemFeeVT(feesPacked); } else if (operationType == uint8(OperationTypes.OP_TYPE_REDEEM_YT)) { return GroupStateHelper.getRedeemFeeYT(feesPacked); } else { InvalidOperationType.selector.revertWith(groupId); } } /** * @notice Applies fee permissions to the context fee. * @param fee The context fee. * @param groupSettings The group settings. * @return The context fee with applied permissions. */ function _applyFeePermissions(uint24 fee, bytes32 groupSettings) internal pure returns (uint24) { FeePermissions memory feePermissions = GroupStateHelper.getFeePermissions(GroupSettings.wrap(groupSettings)); if (feePermissions.isDynamic && !feePermissions.allowDelegation) { fee |= GroupFeeLibrary.DYNAMIC_FEE_FLAG; } if (feePermissions.allowDelegation) { fee |= GroupFeeLibrary.DELEGATE_FEE_FLAG; } return fee; } /** * @notice Creates the parameters for the operation context. * @param params Struct containing parameters needed to create the context. * @param operationType The operation type. * @param initialProtocolFee The initial protocol fee. * @param contextFee The context fee. * @param stabilityConditionsTriggeringRate The stability conditions triggering rate. * @return operationParams The created parameters. */ function _createParams( DOperationContext.Context memory params, uint8 operationType, uint24 initialProtocolFee, uint24 contextFee, uint96 stabilityConditionsTriggeringRate ) internal pure returns (DOperationContext.Params memory operationParams) { Slot0 slot0 = Slot0Library.pack( operationType, initialProtocolFee, contextFee, 0, // overriddenFee stabilityConditionsTriggeringRate ); operationParams = DOperationContext.Params({ slot0: slot0, slot1: params.hookData }); } /** * @notice Retrieves the stability ratio. * @param params Struct containing parameters needed to create the context. * @return The stability ratio. */ function _getStabilityRatio(DOperationContext.Context memory params) internal pure returns (uint96) { return GroupStateHelper.getStabilityRatio(GroupSettings.wrap(params.groupState.groupSettings)); } /** * @notice Retrieves the stability conditions triggering rate. * @param params Struct containing parameters needed to create the context. * @return The stability conditions triggering rate. */ function _getstabilityConditionsTriggeringRate(DOperationContext.Context memory params) internal pure returns (uint96) { return GroupStateHelper.getStabilityTriggeringRatio(GroupSettings.wrap(params.groupState.groupSettings)); } // ------ Getter Functions ------ // /** * @notice Retrieves the operation type from the operation context. * @param self The operation context. * @return The operation type. */ function getOperationType(DOperationContext.OperationContext memory self) internal pure returns (uint8) { return self.params.slot0.operationType(); } /** * @notice Retrieves the first amount from the operation context. * @param self The operation context. * @return The first amount. */ function getAmount0(DOperationContext.OperationContext memory self) internal pure returns (uint256) { return self.amount0; } /** * @notice Retrieves the slippage rate from the operation context. * @param self The operation context. * @return The slippage rate. */ function getSlippageRate(DOperationContext.OperationContext memory self) internal pure returns (uint24) { return self.slippage; } /** * @notice Retrieves the context fee from the operation context. * @param self The operation context. * @return The context fee. */ function getContextFee(DOperationContext.OperationContext memory self) internal pure returns (uint24) { return self.params.slot0.contextFee(); } /** * @notice Retrieves the protocol fee from the operation context. * @param self The operation context. * @return The protocol fee. */ function getProtocolFee(DOperationContext.OperationContext memory self) internal pure returns (uint24) { return self.params.slot0.contextProtocolFee(); } /** * @notice Retrieves the overridden fee from the operation context. * @param self The operation context. * @return The overridden fee. */ function getOverriddenFee(DOperationContext.OperationContext memory self) internal pure returns (uint24) { return self.params.slot0.overriddenFee(); } /** * @notice Retrieves the hook data from the operation context. * @param self The operation context. * @return The hook data. */ function getHookData(DOperationContext.OperationContext memory self) internal pure returns (bytes memory) { return self.params.slot1; } /** * @notice Retrieves the recipient from the operation context. * @param self The operation context. * @return The recipient. */ function getRecipient(DOperationContext.OperationContext memory self) internal view returns (address) { address recipient = self.recipient; if (recipient != msg.sender) revert ArbitraryCallsNotAllowed(self.groupId, recipient, msg.sender); return self.recipient; } /** * @notice Retrieves the payment token from the operation context. * @param self The operation context. * @return The payment token. */ function getPaymentToken(DOperationContext.OperationContext memory self) internal pure returns (Currency) { return self.paymentToken.token; } /** * @notice Retrieves the payment token decimals from the operation context. * @param self The operation context. * @return The payment token decimals. */ function getPaymentTokenDecimals(DOperationContext.OperationContext memory self) internal pure returns (uint8) { return self.paymentToken.decimals; } /** * @notice Retrieves the base token decimals from the operation context. * @param self The operation context. * @return The base token decimals. */ function getBaseTokenDecimals(DOperationContext.OperationContext memory self) internal pure returns (uint8) { return GroupStateHelper.getBaseTokenDecimals(GroupSettings.wrap(self.groupState.groupSettings)); } /** * @notice Retrieves the hook contract from the operation context. * @param self The operation context. * @return The hook contract. */ function getHookContract(DOperationContext.OperationContext memory self) internal pure returns (address) { return self.groupState.hookContract.toAddress(); } function getGroupWethAddress(DOperationContext.OperationContext memory self) internal pure returns (address) { return self.groupState.core.wethToken.toAddress(); } /** * @notice Retrieves the stability ratio from the operation context. * @param self The operation context. * @return The stability ratio. */ function getStabilityRatio(DOperationContext.OperationContext memory self) internal pure returns (uint96) { return GroupStateHelper.getStabilityRatio(GroupSettings.wrap(self.groupState.groupSettings)); } // ------ Setter Functions ------ // /** * @notice Sets the overridden fee in the operation context. * @param self The operation context. * @param overriddenFee The overridden fee. */ function setOverriddenFee(DOperationContext.OperationContext memory self, uint24 overriddenFee) internal pure { FeePermissions memory feePermissions = GroupStateHelper.getFeePermissions(GroupSettings.wrap(self.groupState.groupSettings)); DefaultFeeParams memory defaultFees = GroupStateHelper.getDefaultFeeParams(self.groupState.feesPacked); uint24 validatedFee = GroupFeeLibrary.processGroupFee(defaultFees, feePermissions, overriddenFee); self.params.slot0 = self.params.slot0.setOverriddenFee(validatedFee); } /** * @notice Sets the hook data in the operation context. * @param self The operation context. * @param hookData The hook data. */ function setHookData(DOperationContext.OperationContext memory self, bytes memory hookData) internal pure { /// @dev Only set hookData if hookContract exists if (!self.groupState.hookContract.isZero()) { self.params.slot1 = hookData; } } /** * @notice Sets the context fee in the operation context. * @param self The operation context. * @param contextFee The context fee. */ function setContextFee(DOperationContext.OperationContext memory self, uint24 contextFee) internal pure { self.params.slot0 = self.params.slot0.setContextFee(contextFee); } // ------ Utility Functions ------ // /** * @notice Validates the recipient in the operation context. * @param self The operation context. */ function validateRecipient(DOperationContext.OperationContext memory self) internal pure { if (self.recipient == address(0)) { ZeroAddress.selector.revertWith(self.groupId); } /// @dev guarding for hooks to cycle minting/redeeming if (self.recipient == self.groupState.hookContract.toAddress()) { CycleOperationsAreNotPermitted.selector.revertWith(self.groupId); } } /** * @notice Retrieves the fee model from the operation context. * @param self The operation context. * @return The fee model. */ function getFeeModel(DOperationContext.OperationContext memory self) internal pure returns (uint8) { return GroupStateHelper.getFeeModel(GroupSettings.wrap(self.groupState.groupSettings)); } /** * @notice Retrieves the context collateral ratio from the operation context. * @param self The operation context. * @return The context collateral ratio. */ function getContextCollateralRatio(DOperationContext.OperationContext memory self) internal pure returns (uint256) { return self.collateralRatio; } /** * @notice Checks if wrapping is required in the operation context. * @param self The operation context. * @return True if wrapping is required, false otherwise. */ function isWrappingRequired(DOperationContext.OperationContext memory self) internal pure returns (bool) { return GroupStateHelper.isWrappingRequired(GroupSettings.wrap(self.groupState.groupSettings)); } /** * @notice Retrieves the hook permissions from the operation context. * @param self The operation context. * @return The hook permissions. */ function getHookPermissions(DOperationContext.OperationContext memory self) internal pure returns (uint256) { return GroupStateHelper.getHookPermissions(GroupSettings.wrap(self.groupState.groupSettings)); } // ------ Interaction with Slot0 and Slot1 ------ // /** * @notice Sets the second amount in the operation context. * @param self The operation context. * @param amount1 The second amount. */ function setAmount1(DOperationContext.OperationContext memory self, uint256 amount1) internal pure { self.amount1 = amount1; } /** * @notice Retrieves the AToken from the operation context. * @param self The operation context. * @return The AToken. */ function getAToken(DOperationContext.OperationContext memory self) internal pure returns (Currency) { return self.groupState.core.aToken; } /** * @notice Retrieves the XToken from the operation context. * @param self The operation context. * @return The XToken. */ function getXToken(DOperationContext.OperationContext memory self) internal pure returns (Currency) { return self.groupState.core.xToken; } /** * @notice Retrieves the base token from the operation context. * @param self The operation context. * @return The base token. */ function getBaseToken(DOperationContext.OperationContext memory self) internal pure returns (Currency) { return self.groupState.core.baseToken; } /** * @notice Retrieves the yield bearing token from the operation context. * @param self The operation context. * @return The yield bearing token. */ function getYieldBearingToken(DOperationContext.OperationContext memory self) internal pure returns (Currency) { return self.groupState.core.yieldBearingToken; } /** * @notice Retrieves the swap router from the operation context. * @param self The operation context. * @return The swap router. */ function getSwapRouter(DOperationContext.OperationContext memory self) internal pure returns (Address) { return self.groupState.extended.swapRouter; } /** * @notice Retrieves the price oracle from the operation context. * @param self The operation context. * @return The price oracle. */ function getPriceOracle(DOperationContext.OperationContext memory self) internal pure returns (Address) { return self.groupState.extended.priceOracle; } /** * @notice Retrieves the treasury from the operation context. * @param self The operation context. * @return The treasury. */ function getTreasury(DOperationContext.OperationContext memory self) internal pure returns (Address) { return self.groupState.extended.treasury; } /** * @notice Retrieves the fee collector from the operation context. * @param self The operation context. * @return The fee collector. */ function getFeeCollector(DOperationContext.OperationContext memory self) internal pure returns (Address) { return self.groupState.extended.feeCollector; } /** * @notice Retrieves the rebalance pool from the operation context. * @param self The operation context. * @return The rebalance pool. */ function getRebalancePool(DOperationContext.OperationContext memory self) internal pure returns (Currency) { return self.groupState.extended.rebalancePool; } function isMintOperation(DOperationContext.OperationContext memory self) internal pure returns (bool) { uint8 operationType = self.params.slot0.operationType(); return operationType == uint8(OperationTypes.OP_TYPE_MINT_VT) || operationType == uint8(OperationTypes.OP_TYPE_MINT_YT); } function isRedeemOperation(DOperationContext.OperationContext memory self) internal pure returns (bool) { uint8 operationType = self.params.slot0.operationType(); return operationType == uint8(OperationTypes.OP_TYPE_REDEEM_VT) || operationType == uint8(OperationTypes.OP_TYPE_REDEEM_YT); } }
// SPDX-License-Identifier: MIT pragma solidity 0.8.28; import { DOperationContext } from "../declarations/DOperationContext.sol"; import { OperationContextLibrary } from "./OperationContextLibrary.sol"; import { GroupId } from "../types/GroupId.sol"; import { IProtocol } from "../interfaces/IProtocol.sol"; import { CustomRevert } from "./CustomRevert.sol"; import { OperationTypes } from "../types/CommonTypes.sol"; /** * @title TimeManagementLibrary * @notice Library for managing time-based operations and restrictions */ library TimeManagementLibrary { using OperationContextLibrary for DOperationContext.OperationContext; using CustomRevert for bytes4; /// @notice Early exit and time lock constants uint256 private constant EARLY_EXIT_WINDOW = 24 hours; uint24 private constant EARLY_EXIT_FEE_BPS = 25; // 0.25% struct TimeStorage { /// @notice Track last operation times per user per group mapping(GroupId => mapping(address => UserOperationTime)) userOperationTimes; // /// @notice Track which fee models require time locks // mapping(GroupId => bool) hasRedeemLock; } /// @notice Track last operation times per user per group struct UserOperationTime { uint256 lastVTMintTime; // Last VT mint time uint256 lastYTMintTime; // Last YT mint time uint256 lastVTRedeemTime; // Last VT redeem time uint256 lastYTRedeemTime; // Last YT redeem time } /** * @notice Checks if an early exit fee should be applied * @param self The time storage * @param context The operation context * @param user The user address * @return feeApplied Whether early exit fee should be applied * @return additionalFeeBps The additional fee in basis points */ function checkEarlyExitFee( TimeStorage storage self, DOperationContext.OperationContext memory context, address user ) internal view returns (bool feeApplied, uint24 additionalFeeBps) { GroupId groupId = context.groupId; UserOperationTime storage userTime = self.userOperationTimes[groupId][user]; uint8 operationType = context.getOperationType(); if (context.isRedeemOperation()) { uint256 relevantMintTime = operationType == uint8(OperationTypes.OP_TYPE_REDEEM_VT) ? userTime.lastVTMintTime : userTime.lastYTMintTime; if (relevantMintTime != 0 && block.timestamp - relevantMintTime < EARLY_EXIT_WINDOW) { return (true, EARLY_EXIT_FEE_BPS); } } return (false, 0); } /** * @notice Updates the operation timestamp for a user * @param self The time storage * @param groupId The group ID * @param operationType The operation type * @param user The user address * @param isMinting Whether the operation is minting */ function updateOperationTime(TimeStorage storage self, GroupId groupId, uint8 operationType, address user, bool isMinting) internal { if (isMinting) { if (operationType == uint8(OperationTypes.OP_TYPE_MINT_VT)) { self.userOperationTimes[groupId][user].lastVTMintTime = block.timestamp; } else if (operationType == uint8(OperationTypes.OP_TYPE_MINT_YT)) { self.userOperationTimes[groupId][user].lastYTMintTime = block.timestamp; } } else { if (operationType == uint8(OperationTypes.OP_TYPE_REDEEM_VT)) { self.userOperationTimes[groupId][user].lastVTRedeemTime = block.timestamp; } else if (operationType == uint8(OperationTypes.OP_TYPE_REDEEM_YT)) { self.userOperationTimes[groupId][user].lastYTRedeemTime = block.timestamp; } } } // /** // * @notice Validates time-based restrictions for a user // * @param self The time storage // * @param context The operation context // * @param user The user address // */ // function validateTimeRestrictions( // TimeStorage storage self, // DOperationContext.OperationContext memory context, // address user // ) internal view { // GroupId groupId = context.groupId; // if (!self.hasRedeemLock[groupId]) { // return; // } // UserOperationTime storage userTime = self.userOperationTimes[groupId][user]; // uint8 operationType = context.getOperationType(); // uint256 relevantMintTime = operationType == uint8(OperationTypes.OP_TYPE_REDEEM_VT) ? userTime.lastVTMintTime : userTime.lastYTMintTime; // if (relevantMintTime != 0 && block.timestamp - relevantMintTime < EARLY_EXIT_WINDOW) { // IProtocol.RedeemLocked.selector.revertWith(groupId); // } // } /** * @notice Gets the operation times for a user * @param self The time storage * @param groupId The group ID * @param user The user address * @return lastVTMintTime The last VT mint time * @return lastYTMintTime The last YT mint time * @return lastVTRedeemTime The last VT redeem time * @return lastYTRedeemTime The last YT redeem time */ function getUserOperationTime( TimeStorage storage self, GroupId groupId, address user ) internal view returns (uint256 lastVTMintTime, uint256 lastYTMintTime, uint256 lastVTRedeemTime, uint256 lastYTRedeemTime) { UserOperationTime storage userTime = self.userOperationTimes[groupId][user]; return (userTime.lastVTMintTime, userTime.lastYTMintTime, userTime.lastVTRedeemTime, userTime.lastYTRedeemTime); } }
// SPDX-License-Identifier: MIT pragma solidity 0.8.28; import { IProtocol } from "../interfaces/IProtocol.sol"; import { CustomRevert } from "./CustomRevert.sol"; import { ProtocolConfigState } from "./ProtocolConfigState.sol"; import { TimeManagementLibrary } from "./TimeManagementLibrary.sol"; import { DOperationContext } from "../declarations/DOperationContext.sol"; import { OperationContextLibrary } from "./OperationContextLibrary.sol"; import { GroupState } from "../types/CommonTypes.sol"; import { GroupId } from "../types/GroupId.sol"; import { Currency, CurrencyLibrary } from "../types/Currency.sol"; import { CollateralInfo } from "../types/CommonTypes.sol"; import { OperationTypes } from "../types/CommonTypes.sol"; library ValidationLibrary { using CurrencyLibrary for Currency; using OperationContextLibrary for DOperationContext.OperationContext; using TimeManagementLibrary for TimeManagementLibrary.TimeStorage; using CustomRevert for bytes4; error ErrorMintPaused(GroupId groupId); error ErrorRedeemPaused(GroupId groupId); error InvalidOperationType(GroupId groupId); error InsufficientAllowance(GroupId groupId); error InsufficientBalance(GroupId groupId); error InvalidPaymentAmount(); error InvalidMinMaxCollateralAmount(); /** * @notice Validates the operation based on the context. * @param configStorage The configuration storage. * param timeStorage The time storage. * @param groupId The group ID. * @param context The operation context. */ function validateOperation( ProtocolConfigState.ConfigStorage storage configStorage, // TimeManagementLibrary.TimeStorage storage timeStorage, GroupId groupId, DOperationContext.OperationContext memory context ) public view { if (context.isMintOperation()) { if (!ProtocolConfigState.getGlobalMintFlag(configStorage) || !ProtocolConfigState.getMintFlag(configStorage, groupId)) { ErrorMintPaused.selector.revertWith(groupId); } } else if (context.isRedeemOperation()) { if (!ProtocolConfigState.getGlobalRedeemFlag(configStorage) || !ProtocolConfigState.getRedeemFlag(configStorage, groupId)) { ErrorRedeemPaused.selector.revertWith(groupId); } context.validateRecipient(); validateUserBalance(context); // timeStorage.validateTimeRestrictions(context, msgSender); } else { InvalidOperationType.selector.revertWith(groupId); } } /** * @notice Validates the user balance for redeem operations. * @param context The operation context. */ function validateUserBalance(DOperationContext.OperationContext memory context) public view { uint8 operationType = context.getOperationType(); uint256 tokenIn = context.getAmount0(); address msgSender = context.getRecipient(); if (operationType == uint8(OperationTypes.OP_TYPE_REDEEM_VT)) { if (context.getXToken().balanceOf(msgSender) < tokenIn) InsufficientBalance.selector.revertWith(context.groupId); if (context.getXToken().allowance(msgSender, address(this)) < tokenIn) InsufficientAllowance.selector.revertWith(context.groupId); } else if (operationType == uint8(OperationTypes.OP_TYPE_REDEEM_YT)) { if (context.getRebalancePool().balanceOf(msgSender) < tokenIn) InsufficientBalance.selector.revertWith(context.groupId); if (context.getRebalancePool().allowance(msgSender, address(this)) < tokenIn) InsufficientAllowance.selector.revertWith(context.groupId); } else { InvalidOperationType.selector.revertWith(context.groupId); } } /** * @notice Checks if the payment token is supported by the group. * @param acceptableCollaterals The acceptable collaterals for the group. * @param baseIn The base amount for the operation. * @param paymentToken The payment token to check. * @param isMintOperation True if the operation is a mint operation, false otherwise. * @param msgValue The amount of ETH sent with the transaction. * @return True if the payment token is supported, false otherwise. */ function isPaymentTokenSupported( CollateralInfo[] memory acceptableCollaterals, uint256 baseIn, Currency paymentToken, bool isMintOperation, uint256 msgValue ) public pure returns (bool, CollateralInfo memory) { CollateralInfo memory collateralInfo; if (paymentToken.isNative() && (isMintOperation && msgValue != baseIn)) { InvalidPaymentAmount.selector.revertWith(); } for (uint256 i = 0; i < acceptableCollaterals.length; ) { if (acceptableCollaterals[i].token.equals(paymentToken)) { if (isMintOperation) { if (baseIn <= acceptableCollaterals[i].minAmount || baseIn >= acceptableCollaterals[i].maxAmount) { InvalidMinMaxCollateralAmount.selector.revertWith(); } } collateralInfo = acceptableCollaterals[i]; return (true, collateralInfo); } unchecked { ++i; } } return (false, collateralInfo); } }
// SPDX-License-Identifier: MIT pragma solidity 0.8.28; import { GroupId } from "../types/GroupId.sol"; // solhint-disable /// @title Library for reverting with custom errors efficiently /// @notice Contains functions for reverting with custom errors with different argument types efficiently /// @dev To use this library, declare `using CustomRevert for bytes4;` and replace `revert CustomError()` with /// `CustomError.selector.revertWith()` /// @dev The functions may tamper with the free memory pointer but it is fine since the call context is exited immediately library CustomRevert { /// @dev Reverts with the selector of a custom error in the scratch space function revertWith(bytes4 selector) internal pure { assembly("memory-safe") { mstore(0, selector) revert(0, 0x04) } } /// @dev Reverts with a custom error with an address argument in the scratch space function revertWith(bytes4 selector, address addr) internal pure { assembly("memory-safe") { mstore(0, selector) mstore(0x04, and(addr, 0xffffffffffffffffffffffffffffffffffffffff)) revert(0, 0x24) } } /// @dev Reverts with a custom error with an int24 argument in the scratch space function revertWith(bytes4 selector, int24 value) internal pure { assembly("memory-safe") { mstore(0, selector) mstore(0x04, signextend(2, value)) revert(0, 0x24) } } /// @dev Reverts with a custom error with a uint160 argument in the scratch space function revertWith(bytes4 selector, uint160 value) internal pure { assembly("memory-safe") { mstore(0, selector) mstore(0x04, and(value, 0xffffffffffffffffffffffffffffffffffffffff)) revert(0, 0x24) } } /// @dev Reverts with a custom error with two int24 arguments function revertWith(bytes4 selector, int24 value1, int24 value2) internal pure { assembly("memory-safe") { let fmp := mload(0x40) mstore(fmp, selector) mstore(add(fmp, 0x04), signextend(2, value1)) mstore(add(fmp, 0x24), signextend(2, value2)) revert(fmp, 0x44) } } /// @dev Reverts with a custom error with two uint160 arguments function revertWith(bytes4 selector, uint160 value1, uint160 value2) internal pure { assembly("memory-safe") { let fmp := mload(0x40) mstore(fmp, selector) mstore(add(fmp, 0x04), and(value1, 0xffffffffffffffffffffffffffffffffffffffff)) mstore(add(fmp, 0x24), and(value2, 0xffffffffffffffffffffffffffffffffffffffff)) revert(fmp, 0x44) } } /// @dev Reverts with a custom error with two address arguments function revertWith(bytes4 selector, address value1, address value2) internal pure { assembly("memory-safe") { mstore(0, selector) mstore(0x04, and(value1, 0xffffffffffffffffffffffffffffffffffffffff)) mstore(0x24, and(value2, 0xffffffffffffffffffffffffffffffffffffffff)) revert(0, 0x44) } } /// @dev Reverts with a custom error with a bytes32 argument in the scratch space function revertWith(bytes4 selector, bytes32 value) internal pure { assembly("memory-safe") { mstore(0, selector) mstore(0x04, value) revert(0, 0x24) } } /// @dev Reverts with a custom error with a bytes32 argument in the scratch space function revertWith(bytes4 selector, GroupId value) internal pure { bytes32 valueBytes = GroupId.unwrap(value); assembly("memory-safe") { mstore(0, selector) mstore(0x04, valueBytes) revert(0, 0x24) } } /// @dev Reverts with a custom error with a bytes32 and an address argument function revertWith(bytes4 selector, GroupId value, address addr) internal pure { bytes32 valueBytes = GroupId.unwrap(value); assembly("memory-safe") { mstore(0x00, selector) mstore(0x04, valueBytes) mstore(0x24, and(addr, 0xffffffffffffffffffffffffffffffffffffffff)) revert(0x00, 0x44) } } /// @dev Reverts with a custom error with a bytes32, address, and uint256 arguments function revertWith(bytes4 selector, GroupId value, address addr, uint256 amount) internal pure { bytes32 valueBytes = GroupId.unwrap(value); assembly("memory-safe") { mstore(0x00, selector) mstore(0x04, valueBytes) mstore(0x24, and(addr, 0xffffffffffffffffffffffffffffffffffffffff)) mstore(0x44, amount) revert(0x00, 0x64) } } /// @notice bubble up the revert message returned by a call and revert with the selector provided /// @dev this function should only be used with custom errors of the type `CustomError(address target, bytes revertReason)` function bubbleUpAndRevertWith(bytes4 selector, address addr) internal pure { assembly("memory-safe") { let size := returndatasize() let fmp := mload(0x40) // Encode selector, address, offset, size, data mstore(fmp, selector) mstore(add(fmp, 0x04), addr) mstore(add(fmp, 0x24), 0x40) mstore(add(fmp, 0x44), size) returndatacopy(add(fmp, 0x64), 0, size) // Ensure the size is a multiple of 32 bytes let encodedSize := add(0x64, mul(div(add(size, 31), 32), 32)) revert(fmp, encodedSize) } } }
// SPDX-License-Identifier: MIT pragma solidity 0.8.28; import { GroupKey } from "./GroupKey.sol"; type GroupId is bytes32; // @notice library for computing the ID of a group library GroupIdLibrary { using GroupIdLibrary for GroupId; function toId(GroupKey memory groupKey) internal pure returns (GroupId groupId) { groupId = GroupId.wrap(keccak256(abi.encode(groupKey))); } }
// SPDX-License-Identifier: MIT pragma solidity 0.8.28; import { DTokenRegistry } from "../declarations/DTokenRegistry.sol"; import { WordCodec } from "../libs/WordCodec.sol"; import { Address, AddressLibrary } from "../types/Address.sol"; import { Currency, CurrencyLibrary } from "../types/Currency.sol"; import { DefaultFeeParams, FeeParams, FeePermissions, CollateralInfo, GroupState } from "../types/CommonTypes.sol"; import { ITokenRegistry } from "../interfaces/ITokenRegistry.sol"; /** * @title GroupStateHelper * @notice Library for managing group settings and fee parameters in a space-efficient manner using bit packing * @dev Uses WordCodec for bit manipulation operations to store and retrieve various parameters */ type GroupSettings is bytes32; library GroupStateHelper { using WordCodec for bytes32; using CurrencyLibrary for Currency; using AddressLibrary for address; // Group Settings bit layout (total 256 bits): uint256 private constant A_TOKEN_DECIMALS_OFFSET = 0; // [0-7]: aToken decimals (8 bits) uint256 private constant X_TOKEN_DECIMALS_OFFSET = 8; // [8-15]: xToken decimals (8 bits) uint256 private constant BASE_TOKEN_DECIMALS_OFFSET = 16; // [16-23]: baseToken decimals (8 bits) uint256 private constant YIELD_BEARING_TOKEN_DECIMALS_OFFSET = 24; // [24-31]: yieldBearingToken decimals (8 bits) uint256 private constant HOOK_PERMISSIONS_OFFSET = 32; // [32-47]: hook permissions (16 bits) uint256 private constant FEE_PERMISSIONS_OFFSET = 48; // [48-49]: fee permissions (2 bits) uint256 private constant FEE_MODEL_OFFSET = 50; // [50-57]: fee model (8 bits) uint256 private constant WRAPPING_REQUIRED_OFFSET = 58; // [58]: wrapping required (1 bit) uint256 private constant STABILITY_RATIO_OFFSET = 59; // [59-154]: stability ratio (96 bits) uint256 private constant STABILITY_TRIGGER_RATIO_OFFSET = 155; // [155-250]: stability triggering ratio (96 bits) // Fee Parameters bit layout (total 256 bits): uint256 private constant MAX_FEE_OFFSET = 0; // [0-15]: max fee (16 bits) uint256 private constant MIN_FEE_OFFSET = 16; // [16-31]: min fee (16 bits) uint256 private constant BASE_FEE_OFFSET = 32; // [32-47]: base fee (16 bits) uint256 private constant YIELD_FEE_VT_OFFSET = 48; // [48-63]: yield fee VT (16 bits) uint256 private constant YIELD_FEE_YT_OFFSET = 64; // [64-79]: yield fee YT (16 bits) uint256 private constant REDEEM_FEE_YT_OFFSET = 80; // [80-103]: redeem fee YT (24 bits) uint256 private constant MINT_FEE_YT_OFFSET = 104; // [104-127]: mint fee YT (24 bits) uint256 private constant REDEEM_FEE_VT_OFFSET = 128; // [128-151]: redeem fee VT (24 bits) uint256 private constant MINT_FEE_VT_OFFSET = 152; // [152-175]: mint fee VT (24 bits) uint256 private constant STABILITY_MINT_FEE_VT_OFFSET = 176; // [176-191]: stability mint fee VT (16 bits) uint256 private constant STABILITY_MINT_FEE_YT_OFFSET = 192; // [192-207]: stability mint fee YT (16 bits) uint256 private constant STABILITY_REDEEM_FEE_VT_OFFSET = 208; // [208-223]: stability redeem fee VT (16 bits) uint256 private constant STABILITY_REDEEM_FEE_YT_OFFSET = 224; // [224-239]: stability redeem fee YT (16 bits) uint256 private constant PROTOCOL_FEE_OFFSET = 240; // [240-255]: protocol fee (16 bits) // Common bit lengths uint256 private constant LENGTH_1BIT = 1; uint256 private constant LENGTH_2BITS = 2; uint256 private constant LENGTH_8BITS = 8; uint256 private constant LENGTH_16BITS = 16; uint256 private constant LENGTH_24BITS = 24; uint256 private constant LENGTH_96BITS = 96; /** * @notice Retrieves the aToken decimals from group settings * @param groupSettings The packed group settings * @return The aToken decimals */ function getATokenDecimals(GroupSettings groupSettings) internal pure returns (uint8) { return uint8(GroupSettings.unwrap(groupSettings).decodeUint(A_TOKEN_DECIMALS_OFFSET, LENGTH_8BITS)); } /** * @notice Retrieves the xToken decimals from group settings * @param groupSettings The packed group settings * @return The xToken decimals */ function getXTokenDecimals(GroupSettings groupSettings) internal pure returns (uint8) { return uint8(GroupSettings.unwrap(groupSettings).decodeUint(X_TOKEN_DECIMALS_OFFSET, LENGTH_8BITS)); } /** * @notice Retrieves the baseToken decimals from group settings * @param groupSettings The packed group settings * @return The baseToken decimals */ function getBaseTokenDecimals(GroupSettings groupSettings) internal pure returns (uint8) { return uint8(GroupSettings.unwrap(groupSettings).decodeUint(BASE_TOKEN_DECIMALS_OFFSET, LENGTH_8BITS)); } /** * @notice Retrieves the yieldBearingToken decimals from group settings * @param groupSettings The packed group settings * @return The yieldBearingToken decimals */ function getYieldBearingTokenDecimals(GroupSettings groupSettings) internal pure returns (uint8) { return uint8(GroupSettings.unwrap(groupSettings).decodeUint(YIELD_BEARING_TOKEN_DECIMALS_OFFSET, LENGTH_8BITS)); } /** * @notice Retrieves the hook permissions from group settings * @param groupSettings The packed group settings * @return The hook permissions */ function getHookPermissions(GroupSettings groupSettings) internal pure returns (uint16) { return uint16(GroupSettings.unwrap(groupSettings).decodeUint(HOOK_PERMISSIONS_OFFSET, LENGTH_16BITS)); } /** * @notice Retrieves the fee permissions from group settings * @param groupSettings The packed group settings * @return FeePermissions struct containing permission flags */ function getFeePermissions(GroupSettings groupSettings) internal pure returns (FeePermissions memory) { bytes32 raw = GroupSettings.unwrap(groupSettings); return FeePermissions({ isDynamic: raw.decodeBool(FEE_PERMISSIONS_OFFSET + 1), allowDelegation: raw.decodeBool(FEE_PERMISSIONS_OFFSET) }); } /** * @notice Retrieves the fee model from group settings * @param groupSettings The packed group settings * @return The fee model */ function getFeeModel(GroupSettings groupSettings) internal pure returns (uint8) { return uint8(GroupSettings.unwrap(groupSettings).decodeUint(FEE_MODEL_OFFSET, LENGTH_8BITS)); } /** * @notice Checks if wrapping is required from group settings * @param groupSettings The packed group settings * @return True if wrapping is required, false otherwise */ function isWrappingRequired(GroupSettings groupSettings) internal pure returns (bool) { return GroupSettings.unwrap(groupSettings).decodeBool(WRAPPING_REQUIRED_OFFSET); } /** * @notice Retrieves the stability ratio from group settings * @param groupSettings The packed group settings * @return The stability ratio (scaled by 1e18) */ function getStabilityRatio(GroupSettings groupSettings) internal pure returns (uint96) { return uint96(GroupSettings.unwrap(groupSettings).decodeUint(STABILITY_RATIO_OFFSET, LENGTH_96BITS)); } /** * @notice Retrieves the stability triggering ratio from group settings * @param groupSettings The packed group settings * @return The stability triggering ratio (scaled by 1e18) */ function getStabilityTriggeringRatio(GroupSettings groupSettings) internal pure returns (uint96) { return uint96(GroupSettings.unwrap(groupSettings).decodeUint(STABILITY_TRIGGER_RATIO_OFFSET, LENGTH_96BITS)); } /** * @notice Sets the aToken decimals in group settings * @param groupSettings Current group settings * @param decimals The new aToken decimals * @return Updated group settings */ function setATokenDecimals(GroupSettings groupSettings, uint8 decimals) internal pure returns (GroupSettings) { bytes32 updated = GroupSettings.unwrap(groupSettings).insertUint(uint256(decimals), A_TOKEN_DECIMALS_OFFSET, LENGTH_8BITS); return GroupSettings.wrap(updated); } /** * @notice Sets the xToken decimals in group settings * @param groupSettings Current group settings * @param decimals The new xToken decimals * @return Updated group settings */ function setXTokenDecimals(GroupSettings groupSettings, uint8 decimals) internal pure returns (GroupSettings) { bytes32 updated = GroupSettings.unwrap(groupSettings).insertUint(uint256(decimals), X_TOKEN_DECIMALS_OFFSET, LENGTH_8BITS); return GroupSettings.wrap(updated); } /** * @notice Sets the baseToken decimals in group settings * @param groupSettings Current group settings * @param decimals The new baseToken decimals * @return Updated group settings */ function setBaseTokenDecimals(GroupSettings groupSettings, uint8 decimals) internal pure returns (GroupSettings) { bytes32 updated = GroupSettings.unwrap(groupSettings).insertUint(uint256(decimals), BASE_TOKEN_DECIMALS_OFFSET, LENGTH_8BITS); return GroupSettings.wrap(updated); } /** * @notice Sets the yieldBearingToken decimals in group settings * @param groupSettings Current group settings * @param decimals The new yieldBearingToken decimals * @return Updated group settings */ function setYieldBearingTokenDecimals(GroupSettings groupSettings, uint8 decimals) internal pure returns (GroupSettings) { bytes32 updated = GroupSettings.unwrap(groupSettings).insertUint(uint256(decimals), YIELD_BEARING_TOKEN_DECIMALS_OFFSET, LENGTH_8BITS); return GroupSettings.wrap(updated); } /** * @notice Sets the hook permissions in group settings * @param groupSettings Current group settings * @param hookPermissions The new hook permissions * @return Updated group settings */ function setHookPermissions(GroupSettings groupSettings, uint16 hookPermissions) internal pure returns (GroupSettings) { bytes32 updated = GroupSettings.unwrap(groupSettings).insertUint(uint256(hookPermissions), HOOK_PERMISSIONS_OFFSET, LENGTH_16BITS); return GroupSettings.wrap(updated); } /** * @notice Sets the fee permissions in group settings * @param groupSettings Current group settings * @param permissions The new fee permissions * @return Updated group settings */ function setFeePermissions(GroupSettings groupSettings, FeePermissions memory permissions) internal pure returns (GroupSettings) { bytes32 raw = GroupSettings.unwrap(groupSettings); raw = raw.insertBool(permissions.allowDelegation, FEE_PERMISSIONS_OFFSET); raw = raw.insertBool(permissions.isDynamic, FEE_PERMISSIONS_OFFSET + 1); return GroupSettings.wrap(raw); } /** * @notice Sets the fee model in group settings * @param groupSettings Current group settings * @param feeModel The new fee model * @return Updated group settings */ function setFeeModel(GroupSettings groupSettings, uint8 feeModel) internal pure returns (GroupSettings) { bytes32 updated = GroupSettings.unwrap(groupSettings).insertUint(uint256(feeModel), FEE_MODEL_OFFSET, LENGTH_8BITS); return GroupSettings.wrap(updated); } /** * @notice Sets the wrapping required flag in group settings * @param groupSettings Current group settings * @param wrappingRequired The new wrapping required flag * @return Updated group settings */ function setWrappingRequired(GroupSettings groupSettings, bool wrappingRequired) internal pure returns (GroupSettings) { bytes32 updated = GroupSettings.unwrap(groupSettings).insertBool(wrappingRequired, WRAPPING_REQUIRED_OFFSET); return GroupSettings.wrap(updated); } /** * @notice Sets the stability ratio in group settings * @param groupSettings Current group settings * @param stabilityRatio The new stability ratio (scaled by 1e18) * @return Updated group settings */ function setStabilityRatio(GroupSettings groupSettings, uint96 stabilityRatio) internal pure returns (GroupSettings) { bytes32 updated = GroupSettings.unwrap(groupSettings).insertUint(uint256(stabilityRatio), STABILITY_RATIO_OFFSET, LENGTH_96BITS); return GroupSettings.wrap(updated); } /** * @notice Sets the stability triggering ratio in group settings * @param groupSettings Current group settings * @param stabilityTriggeringRatio The new stability triggering ratio (scaled by 1e18) * @return Updated group settings */ function setStabilityTriggeringRatio(GroupSettings groupSettings, uint96 stabilityTriggeringRatio) internal pure returns (GroupSettings) { bytes32 updated = GroupSettings.unwrap(groupSettings).insertUint( uint256(stabilityTriggeringRatio), STABILITY_TRIGGER_RATIO_OFFSET, LENGTH_96BITS ); return GroupSettings.wrap(updated); } /** * @notice Retrieves all fee parameters from packed fees * @param feesPacked The packed fees * @return FeeParams struct containing all fee parameters */ function getFeeParams(bytes32 feesPacked) internal pure returns (FeeParams memory) { return FeeParams({ mintFeeVT: uint24(feesPacked.decodeUint(MINT_FEE_VT_OFFSET, LENGTH_24BITS)), redeemFeeVT: uint24(feesPacked.decodeUint(REDEEM_FEE_VT_OFFSET, LENGTH_24BITS)), mintFeeYT: uint24(feesPacked.decodeUint(MINT_FEE_YT_OFFSET, LENGTH_24BITS)), redeemFeeYT: uint24(feesPacked.decodeUint(REDEEM_FEE_YT_OFFSET, LENGTH_24BITS)), // Upcast 16-bit stability fees to 24-bit stabilityMintFeeVT: uint24(uint16(feesPacked.decodeUint(STABILITY_MINT_FEE_VT_OFFSET, LENGTH_16BITS))), stabilityMintFeeYT: uint24(uint16(feesPacked.decodeUint(STABILITY_MINT_FEE_YT_OFFSET, LENGTH_16BITS))), stabilityRedeemFeeVT: uint24(uint16(feesPacked.decodeUint(STABILITY_REDEEM_FEE_VT_OFFSET, LENGTH_16BITS))), stabilityRedeemFeeYT: uint24(uint16(feesPacked.decodeUint(STABILITY_REDEEM_FEE_YT_OFFSET, LENGTH_16BITS))), // Upcast 16-bit yield fees to 24-bit yieldFeeVT: uint24(uint16(feesPacked.decodeUint(YIELD_FEE_VT_OFFSET, LENGTH_16BITS))), yieldFeeYT: uint24(uint16(feesPacked.decodeUint(YIELD_FEE_YT_OFFSET, LENGTH_16BITS))), protocolFee: uint16(feesPacked.decodeUint(PROTOCOL_FEE_OFFSET, LENGTH_16BITS)) }); } /** * @notice Retrieves the default fee parameters from packed fees * @param feesPacked The packed fees * @return DefaultFeeParams struct containing min, max and base fees */ function getDefaultFeeParams(bytes32 feesPacked) internal pure returns (DefaultFeeParams memory) { return DefaultFeeParams({ baseFee: uint24(feesPacked.decodeUint(BASE_FEE_OFFSET, LENGTH_16BITS)), maxFee: uint16(feesPacked.decodeUint(MAX_FEE_OFFSET, LENGTH_16BITS)), minFee: uint16(feesPacked.decodeUint(MIN_FEE_OFFSET, LENGTH_16BITS)) }); } /** * @notice Retrieves the max fee from packed fees * @param feesPacked The packed fees * @return The max fee value (24-bit) */ function getMaxFee(bytes32 feesPacked) internal pure returns (uint24) { return uint24(uint16(feesPacked.decodeUint(MAX_FEE_OFFSET, LENGTH_16BITS))); } /** * @notice Retrieves the min fee from packed fees * @param feesPacked The packed fees * @return The min fee value (24-bit) */ function getMinFee(bytes32 feesPacked) internal pure returns (uint24) { return uint24(uint16(feesPacked.decodeUint(MIN_FEE_OFFSET, LENGTH_16BITS))); } /** * @notice Retrieves the base fee from packed fees * @param feesPacked The packed fees * @return The base fee value (24-bit) */ function getBaseFee(bytes32 feesPacked) internal pure returns (uint24) { return uint24(uint16(feesPacked.decodeUint(BASE_FEE_OFFSET, LENGTH_16BITS))); } /** * @notice Retrieves the yield fee VT from packed fees * @param feesPacked The packed fees * @return The yield fee VT value (24-bit) */ function getYieldFeeVT(bytes32 feesPacked) internal pure returns (uint24) { return uint24(uint16(feesPacked.decodeUint(YIELD_FEE_VT_OFFSET, LENGTH_16BITS))); } /** * @notice Retrieves the yield fee YT from packed fees * @param feesPacked The packed fees * @return The yield fee YT value (24-bit) */ function getYieldFeeYT(bytes32 feesPacked) internal pure returns (uint24) { return uint24(uint16(feesPacked.decodeUint(YIELD_FEE_YT_OFFSET, LENGTH_16BITS))); } /** * @notice Retrieves the mint fee VT from packed fees * @param feesPacked The packed fees * @return The mint fee VT value (24-bit) */ function getMintFeeVT(bytes32 feesPacked) internal pure returns (uint24) { return uint24(feesPacked.decodeUint(MINT_FEE_VT_OFFSET, LENGTH_24BITS)); } /** * @notice Retrieves the redeem fee VT from packed fees * @param feesPacked The packed fees * @return The redeem fee VT value (24-bit) */ function getRedeemFeeVT(bytes32 feesPacked) internal pure returns (uint24) { return uint24(feesPacked.decodeUint(REDEEM_FEE_VT_OFFSET, LENGTH_24BITS)); } /** * @notice Retrieves the mint fee YT from packed fees * @param feesPacked The packed fees * @return The mint fee YT value (24-bit) */ function getMintFeeYT(bytes32 feesPacked) internal pure returns (uint24) { return uint24(feesPacked.decodeUint(MINT_FEE_YT_OFFSET, LENGTH_24BITS)); } /** * @notice Retrieves the redeem fee YT from packed fees * @param feesPacked The packed fees * @return The redeem fee YT value (24-bit) */ function getRedeemFeeYT(bytes32 feesPacked) internal pure returns (uint24) { return uint24(feesPacked.decodeUint(REDEEM_FEE_YT_OFFSET, LENGTH_24BITS)); } /** * @notice Retrieves the stability mint fee VT from packed fees * @param feesPacked The packed fees * @return The stability mint fee VT value (24-bit) */ function getStabilityMintFeeVT(bytes32 feesPacked) internal pure returns (uint24) { return uint24(uint16(feesPacked.decodeUint(STABILITY_MINT_FEE_VT_OFFSET, LENGTH_16BITS))); } /** * @notice Retrieves the stability mint fee YT from packed fees * @param feesPacked The packed fees * @return The stability mint fee YT value (24-bit) */ function getStabilityMintFeeYT(bytes32 feesPacked) internal pure returns (uint24) { return uint24(uint16(feesPacked.decodeUint(STABILITY_MINT_FEE_YT_OFFSET, LENGTH_16BITS))); } /** * @notice Retrieves the stability redeem fee VT from packed fees * @param feesPacked The packed fees * @return The stability redeem fee VT value (24-bit) */ function getStabilityRedeemFeeVT(bytes32 feesPacked) internal pure returns (uint24) { return uint24(uint16(feesPacked.decodeUint(STABILITY_REDEEM_FEE_VT_OFFSET, LENGTH_16BITS))); } /** * @notice Retrieves the stability redeem fee YT from packed fees * @param feesPacked The packed fees * @return The stability redeem fee YT value (24-bit) */ function getStabilityRedeemFeeYT(bytes32 feesPacked) internal pure returns (uint24) { return uint24(uint16(feesPacked.decodeUint(STABILITY_REDEEM_FEE_YT_OFFSET, LENGTH_16BITS))); } /** * @notice Retrieves the protocol fee from packed fees * @param feesPacked The packed fees * @return The protocol fee value (24-bit) */ function getProtocolFee(bytes32 feesPacked) internal pure returns (uint24) { return uint24(uint16(feesPacked.decodeUint(PROTOCOL_FEE_OFFSET, LENGTH_16BITS))); } /** * @notice Sets the max fee in packed fees * @param feesPacked Current packed fees * @param maxFee The new max fee value (truncated to 16-bit) * @return Updated packed fees */ function setMaxFee(bytes32 feesPacked, uint24 maxFee) internal pure returns (bytes32) { return feesPacked.insertUint(uint256(uint16(maxFee)), MAX_FEE_OFFSET, LENGTH_16BITS); } /** * @notice Sets the min fee in packed fees * @param feesPacked Current packed fees * @param minFee The new min fee value (truncated to 16-bit) * @return Updated packed fees */ function setMinFee(bytes32 feesPacked, uint24 minFee) internal pure returns (bytes32) { return feesPacked.insertUint(uint256(uint16(minFee)), MIN_FEE_OFFSET, LENGTH_16BITS); } /** * @notice Sets the base fee in packed fees * @param feesPacked Current packed fees * @param baseFee The new base fee value (truncated to 16-bit) * @return Updated packed fees */ function setBaseFee(bytes32 feesPacked, uint24 baseFee) internal pure returns (bytes32) { return feesPacked.insertUint(uint256(uint16(baseFee)), BASE_FEE_OFFSET, LENGTH_16BITS); } /** * @notice Sets the yield fee VT in packed fees * @param feesPacked Current packed fees * @param yieldFeeVT The new yield fee VT value (truncated to 16-bit) * @return Updated packed fees */ function setYieldFeeVT(bytes32 feesPacked, uint24 yieldFeeVT) internal pure returns (bytes32) { return feesPacked.insertUint(uint256(uint16(yieldFeeVT)), YIELD_FEE_VT_OFFSET, LENGTH_16BITS); } /** * @notice Sets the yield fee YT in packed fees * @param feesPacked Current packed fees * @param yieldFeeYT The new yield fee YT value (truncated to 16-bit) * @return Updated packed fees */ function setYieldFeeYT(bytes32 feesPacked, uint24 yieldFeeYT) internal pure returns (bytes32) { return feesPacked.insertUint(uint256(uint16(yieldFeeYT)), YIELD_FEE_YT_OFFSET, LENGTH_16BITS); } /** * @notice Sets the mint fee VT in packed fees * @param feesPacked Current packed fees * @param mintFeeVT The new mint fee VT value * @return Updated packed fees */ function setMintFeeVT(bytes32 feesPacked, uint24 mintFeeVT) internal pure returns (bytes32) { return feesPacked.insertUint(uint256(mintFeeVT), MINT_FEE_VT_OFFSET, LENGTH_24BITS); } /** * @notice Sets the redeem fee VT in packed fees * @param feesPacked Current packed fees * @param redeemFeeVT The new redeem fee VT value * @return Updated packed fees */ function setRedeemFeeVT(bytes32 feesPacked, uint24 redeemFeeVT) internal pure returns (bytes32) { return feesPacked.insertUint(uint256(redeemFeeVT), REDEEM_FEE_VT_OFFSET, LENGTH_24BITS); } /** * @notice Sets the mint fee YT in packed fees * @param feesPacked Current packed fees * @param mintFeeYT The new mint fee YT value * @return Updated packed fees */ function setMintFeeYT(bytes32 feesPacked, uint24 mintFeeYT) internal pure returns (bytes32) { return feesPacked.insertUint(uint256(mintFeeYT), MINT_FEE_YT_OFFSET, LENGTH_24BITS); } /** * @notice Sets the redeem fee YT in packed fees * @param feesPacked Current packed fees * @param redeemFeeYT The new redeem fee YT value * @return Updated packed fees */ function setRedeemFeeYT(bytes32 feesPacked, uint24 redeemFeeYT) internal pure returns (bytes32) { return feesPacked.insertUint(uint256(redeemFeeYT), REDEEM_FEE_YT_OFFSET, LENGTH_24BITS); } /** * @notice Sets the stability mint fee VT in packed fees * @param feesPacked Current packed fees * @param stabilityMintFeeVT The new stability mint fee VT value (truncated to 16-bit) * @return Updated packed fees */ function setStabilityMintFeeVT(bytes32 feesPacked, uint24 stabilityMintFeeVT) internal pure returns (bytes32) { return feesPacked.insertUint(uint256(uint16(stabilityMintFeeVT)), STABILITY_MINT_FEE_VT_OFFSET, LENGTH_16BITS); } /** * @notice Sets the stability mint fee YT in packed fees * @param feesPacked Current packed fees * @param stabilityMintFeeYT The new stability mint fee YT value (truncated to 16-bit) * @return Updated packed fees */ function setStabilityMintFeeYT(bytes32 feesPacked, uint24 stabilityMintFeeYT) internal pure returns (bytes32) { return feesPacked.insertUint(uint256(uint16(stabilityMintFeeYT)), STABILITY_MINT_FEE_YT_OFFSET, LENGTH_16BITS); } /** * @notice Sets the stability redeem fee VT in packed fees * @param feesPacked Current packed fees * @param stabilityRedeemFeeVT The new stability redeem fee VT value (truncated to 16-bit) * @return Updated packed fees */ function setStabilityRedeemFeeVT(bytes32 feesPacked, uint24 stabilityRedeemFeeVT) internal pure returns (bytes32) { return feesPacked.insertUint(uint256(uint16(stabilityRedeemFeeVT)), STABILITY_REDEEM_FEE_VT_OFFSET, LENGTH_16BITS); } /** * @notice Sets the stability redeem fee YT in packed fees * @param feesPacked Current packed fees * @param stabilityRedeemFeeYT The new stability redeem fee YT value (truncated to 16-bit) * @return Updated packed fees */ function setStabilityRedeemFeeYT(bytes32 feesPacked, uint24 stabilityRedeemFeeYT) internal pure returns (bytes32) { return feesPacked.insertUint(uint256(uint16(stabilityRedeemFeeYT)), STABILITY_REDEEM_FEE_YT_OFFSET, LENGTH_16BITS); } /** * @notice Sets the protocol fee in packed fees * @param feesPacked Current packed fees * @param protocolFee The new protocol fee value (truncated to 16-bit) * @return Updated packed fees */ function setProtocolFee(bytes32 feesPacked, uint24 protocolFee) internal pure returns (bytes32) { return feesPacked.insertUint(uint256(uint16(protocolFee)), PROTOCOL_FEE_OFFSET, LENGTH_16BITS); } // Group Core Components Getters /** * @notice Retrieves the hook contract from group state * @param groupState The group state * @return The hook contract address wrapper */ function getHookContract(GroupState memory groupState) internal pure returns (Address) { return groupState.hookContract; } }
// SPDX-License-Identifier: MIT pragma solidity 0.8.28; import { Currency } from "./Currency.sol"; import { DTokenRegistry } from "../declarations/DTokenRegistry.sol"; import { Address } from "../types/Address.sol"; // Enums representing different fee models that can be applied. enum OperationTypes { OP_TYPE_MINT_VT, OP_TYPE_MINT_YT, OP_TYPE_REDEEM_VT, OP_TYPE_REDEEM_YT } enum FeeModel { NONE, // No fees. MANAGEMENT_FEE, // Management fee model. VARIABLE_FUNDING_FEE, // Fee that varies based on funding. FIXED_FUNDING_FEE, // Fixed fee for funding. CURATED_PAIRS_FEE, // Curated pairs fee model. BLANK_FEE // Blank fee model. } // Information about acceptable collaterals struct CollateralInfo { Currency token; // Token address for collateral uint8 decimals; // Decimals for the collateral token uint256 minAmount; // Minimum amount for both usage minting and redeeming (as desired token) uint256 maxAmount; // Maximum amount for both usage minting and redeeming (as desired token) } // DefaultFeeParams defines the basic fee structure with base, min, and max fees. struct DefaultFeeParams { uint24 baseFee; // The base fee applied when no flags are present. uint24 minFee; // The minimum fee allowed for dynamic fee models. uint24 maxFee; // The maximum fee allowed. } // FeeParams defines specific fees for different token types and operations. struct FeeParams { uint24 mintFeeVT; // Minting fee for volatile tokens (VT). uint24 redeemFeeVT; // Redemption fee for volatile tokens (VT). uint24 mintFeeYT; // Minting fee for yield tokens (YT). uint24 redeemFeeYT; // Redemption fee for yield tokens (YT). uint24 stabilityMintFeeVT; // Stability minting fee for volatile tokens (VT). uint24 stabilityMintFeeYT; // Stability minting fee for yield tokens (YT). uint24 stabilityRedeemFeeVT; // Stability redemption fee for volatile tokens (VT). uint24 stabilityRedeemFeeYT; // Stability redemption fee for yield tokens (YT). uint24 yieldFeeVT; // Yield fee for volatile tokens (VT). uint24 yieldFeeYT; // Yield fee for yield tokens (YT). uint24 protocolFee; // Protocol fee. } // FeePermissions define the flexibility of the fee model (dynamic fees, delegation). struct FeePermissions { bool isDynamic; // Whether the fee model is dynamic. bool allowDelegation; // Whether fee delegation is allowed. } struct GroupState { DTokenRegistry.GroupCore core; DTokenRegistry.GroupExtended extended; bytes32 feesPacked; CollateralInfo[] acceptableCollaterals; Address hookContract; bytes32 groupSettings; }
// SPDX-License-Identifier: MIT pragma solidity 0.8.28; import { DTokenRegistry } from "../declarations/DTokenRegistry.sol"; /** * @title GroupKey * @dev Struct representing the core group key information. */ struct GroupKey { /** * @dev The core group information from the DTokenRegistry. */ DTokenRegistry.GroupCore core; }
// SPDX-License-Identifier: MIT pragma solidity 0.8.28; /// @notice https://forum.openzeppelin.com/t/safeerc20-vs-safeerc20upgradeable/17326 import { SafeERC20Upgradeable, IERC20Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol"; import { CustomRevert } from "../libs/CustomRevert.sol"; // Custom type `Currency` to represent either an ERC20 token or the native currency (e.g., ETH) type Currency is address; /** * @title CurrencyLibrary * @dev A library for handling operations related to currencies, supporting both ERC20 tokens and native currency. * Provides utility functions for balance checks, transfers, approvals, and comparisons. */ library CurrencyLibrary { // Use SafeERC20Upgradeable for IERC20Upgradeable token operations to ensure safety using SafeERC20Upgradeable for IERC20Upgradeable; // Use CustomRevert for standardized error handling via selectors using CustomRevert for bytes4; // Define a constant representing the native currency (address(0)) Currency public constant NATIVE = Currency.wrap(address(0)); // Custom error definitions for various invalid operations involving native currency error NativeCurrencyTransfersNotAllowed(); error NativeCurrencyTransferFromNotAllowed(); error NativeCurrencyApprovalNotAllowed(); error NativeCurrencyDoesNotHaveTotalSupply(); error NativeCurrencyIncreaseAllowanceNotAllowed(); error NativeCurrencyDecreaseAllowanceNotAllowed(); error ArbitraryTransfersNotAllowed(); /** * @notice Checks if two currencies are equal. * @param currency The first currency to compare. * @param other The second currency to compare. * @return True if both currencies are the same, false otherwise. */ function equals(Currency currency, Currency other) internal pure returns (bool) { return Currency.unwrap(currency) == Currency.unwrap(other); } /** * @notice Retrieves the balance of the specified owner for the given currency. * @param currency The currency to check (ERC20 token or native). * @param owner The address of the owner whose balance is queried. * @return The balance of the owner in the specified currency. */ function balanceOf(Currency currency, address owner) internal view returns (uint256) { if (isNative(currency)) { return owner.balance; // For native currency, return the ETH balance } else { return IERC20Upgradeable(Currency.unwrap(currency)).balanceOf(owner); // For ERC20 tokens, use balanceOf } } /** * @notice Safely transfers a specified amount of the currency to a recipient. * @param currency The currency to transfer (must be an ERC20 token). * @param to The recipient address. * @param amount The amount to transfer. * @dev Native currency transfers are not allowed and will revert. */ function safeTransfer(Currency currency, address to, uint256 amount) internal { if (isNative(currency)) { // Revert if attempting to transfer native currency using ERC20 methods NativeCurrencyTransfersNotAllowed.selector.revertWith(); } else { IERC20Upgradeable(Currency.unwrap(currency)).safeTransfer(to, amount); } } /** * @notice Safely transfers a specified amount of the currency from one address to another. * @param currency The currency to transfer (must be an ERC20 token). * @param safeFrom The address to transfer from. * @param to The recipient address. * @param amount The amount to transfer. * @dev Native currency transfers are not allowed and will revert. * @dev Arbitrary transfers (i.e., not initiated by the sender) are also not allowed and will revert. * @notice Slither false positive. The function is internal and only used within the library */ //slither-disable-next-line arbitrary-send-erc20 function safeTransferFrom(Currency currency, address safeFrom, address to, uint256 amount) internal { if (isNative(currency)) { // Revert if attempting to transfer ERC20 tokens from an address other than the sender // This is to prevent arbitrary transfers, which are not allowed in the context of this library // This logic has the priority, so overrides any other inhereted logic NativeCurrencyTransferFromNotAllowed.selector.revertWith(); } else { if (safeFrom != msg.sender) ArbitraryTransfersNotAllowed.selector.revertWith(); IERC20Upgradeable(Currency.unwrap(currency)).safeTransferFrom(safeFrom, to, amount); } } /** * @notice Retrieves the allowance of a spender for the given owner's currency. * @param currency The currency to check (must be an ERC20 token). * @param owner The address of the owner of the currency. * @param spender The address of the spender. * @return The allowance of the spender for the owner's currency. */ function allowance(Currency currency, address owner, address spender) internal view returns (uint256) { if (isNative(currency)) { return 0; // For native currency, return 0 as allowance is not applicable } else { return IERC20Upgradeable(Currency.unwrap(currency)).allowance(owner, spender); // For ERC20 tokens, use allowance } } /** * @notice Safely approves a spender to spend a specified amount of the currency. * @param currency The currency to approve (must be an ERC20 token). * @param spender The address authorized to spend the tokens. * @param amount The amount to approve. * @dev Approving native currency is not allowed and will revert. */ function safeApprove(Currency currency, address spender, uint256 amount) internal { if (!isNative(currency)) { IERC20Upgradeable(Currency.unwrap(currency)).safeApprove(spender, amount); } else { // Revert if attempting to approve native currency NativeCurrencyApprovalNotAllowed.selector.revertWith(); } } /** * @notice Safely increases the allowance of a spender for the currency. * @param currency The currency to modify allowance for (must be an ERC20 token). * @param spender The address authorized to spend the tokens. * @param addedValue The amount to increase the allowance by. * @dev Increasing allowance for native currency is not allowed and will revert. */ function safeIncreaseAllowance(Currency currency, address spender, uint256 addedValue) internal { if (!isNative(currency)) { IERC20Upgradeable(Currency.unwrap(currency)).safeIncreaseAllowance(spender, addedValue); } else { // Revert if attempting to increase allowance for native currency NativeCurrencyIncreaseAllowanceNotAllowed.selector.revertWith(); } } /** * @notice Checks if the given currency is the native currency. * @param currency The currency to check. * @return True if `currency` is the native currency, false otherwise. */ function isNative(Currency currency) internal pure returns (bool) { return Currency.unwrap(currency) == Currency.unwrap(NATIVE); } /** * @notice Checks if the given currency is the zero address. * @param currency The currency to check. * @return True if `currency` is the zero address, false otherwise. */ function isZero(Currency currency) internal pure returns (bool) { return Currency.unwrap(currency) == address(0); } /** * @notice Converts the currency address to a unique identifier. * @param currency The currency to convert. * @return The uint256 representation of the currency's address. */ function toId(Currency currency) internal pure returns (uint256) { return uint160(Currency.unwrap(currency)); } /** * @notice Unwraps the `Currency` type to retrieve the underlying address. * @param currency The currency to unwrap. * @return The underlying address of the currency. */ function toAddress(Currency currency) internal pure returns (address) { return Currency.unwrap(currency); } }
// SPDX-License-Identifier: MIT pragma solidity 0.8.28; library DProtocol { struct MintParams { uint8 operationType; // Type of mint operation uint256 baseIn; // Amount of base token input uint24 slippage; // The slippage tolerance address paymentToken; // Token used for payment bytes hookData; // Hook data if any } struct RedeemParams { uint8 operationType; // Type of redeem operation uint256 baseOut; // Amount of tokens to redeem uint24 slippage; // The slippage tolerance address desiredCollateral; // Desired collateral to receive bytes hookData; // Hook data if any } struct MintResult { uint256 ytMinted; // Amount of YT Tokens minted uint256 vtMinted; // Amount of VT Tokens minted } }
// SPDX-License-Identifier: MIT pragma solidity 0.8.28; import { Currency } from "../types/Currency.sol"; import { GroupId } from "../types/GroupId.sol"; import { Slot0 } from "../types/Slot0.sol"; import { DTokenRegistry } from "../declarations/DTokenRegistry.sol"; import { Address } from "../types/Address.sol"; import { GroupState, CollateralInfo } from "../types/CommonTypes.sol"; /** * @title DOperationContext * @notice Library containing the data structures used for operation contexts. */ library DOperationContext { /** * @notice Struct representing amounts used in the context. */ struct ContextAmounts { uint256 amount0; uint256 amount1; } /** * @notice Struct representing a tight group state. */ struct TightGroupState { DTokenRegistry.GroupCore core; DTokenRegistry.GroupExtended extended; bytes32 groupSettings; bytes32 feesPacked; Address hookContract; } /** * @notice Struct containing parameters needed to create an operation context. */ struct Context { uint8 operationType; ContextAmounts amounts; uint24 slippage; address recipient; CollateralInfo paymentToken; GroupId groupId; GroupState groupState; bytes hookData; } /** * @notice Struct representing parameters packed into Slot0 and Slot1 (memory bytes). */ struct Params { Slot0 slot0; bytes slot1; } /** * @notice Struct representing the full operation context. */ struct OperationContext { GroupId groupId; Params params; TightGroupState groupState; uint256 amount0; uint256 amount1; uint24 slippage; address recipient; CollateralInfo paymentToken; uint256 collateralRatio; } }
// SPDX-License-Identifier: MIT pragma solidity 0.8.28; import { Currency } from "../types/Currency.sol"; import { GroupKey } from "../types/GroupKey.sol"; import { GroupId } from "../types/GroupId.sol"; import { DProtocol } from "../declarations/DProtocol.sol"; interface IProtocol { /** * @notice Emitted when ERC20 tokens are recovered. * @param tokenAddress The address of the recovered token. * @param amount The amount of tokens recovered. */ event ERC20Recovered(Currency indexed tokenAddress, uint256 indexed amount); /** * @notice Emitted when native tokens are withdrawn. * @param amount The amount of native tokens withdrawn. */ event NativeTokenWithdrawn(uint256 indexed amount); /** * @notice Emitted when tokens are minted. * @param groupId The group ID. * @param token The token address. * @param recipient The recipient address. * @param sender The sender address. * @param ytMinted The amount of aTokens minted. * @param vtMinted The amount of vTokens minted. */ event MintToken( GroupId indexed groupId, address indexed token, address indexed recipient, address sender, uint256 ytMinted, uint256 vtMinted ); /** * @notice Emitted when tokens are redeemed. * @param groupId The group ID. * @param token The token address. * @param recipient The recipient address. * @param sender The sender address. * @param baseOut The amount of base tokens received. */ event RedeemToken( GroupId indexed groupId, address indexed token, address indexed recipient, address sender, uint256 baseOut ); /** * @notice Emitted when the minting is paused. * @param groupId The group ID. */ event RedeemLockUpdated(GroupId indexed groupId, bool indexed enabled); event FeeDelegated(address indexed hookContract, uint256 indexed protocolFeeAmount, uint256 indexed hookRemainingFee); event FeeCollected(address indexed feeCollector, address indexed token, uint256 indexed amount); /** * @notice Emitted when fees are settled. * @param hookContract The hook contract address. * @param token The token address. * @param amount The amount of fees settled. */ event FeesSettled(address indexed hookContract, address indexed token, uint256 indexed amount); // Custom Errors // todo: bytes32 groupId error ZeroAddress(); error ZeroAmount(); error ErrorMinTokenAmount(); error NotPermitted(); error ZeroGroupId(); error ConfigNotReady(); error NoFeesOwed(); error InvalidOperationType(); error InvalidRatios(); error ErrorMintPaused(); error ErrorRedeemPaused(); error UnsupportedCollateralType(); error InvalidMinMaxCollateralAmount(); error InsufficientAllowance(); error InsufficientBalance(address token); error ErrorInsufficientTokenOutput(); error ErrorInsufficientBaseOutput(); error InvalidSlippageRequested(); error TreasuryGroupUpdateFailed(); error InvalidMinimumAmount(uint256 minimumAmount, uint256 amountReceived); error MaximumAmountExceeded(GroupId groupId, uint256 preparedAmount, uint256 maxAmount); error InvalidPaymentAmount(); error TransferFailed(GroupId groupId, address recipient, uint256 amount); error ErrorATokenMintingPausedInStabilityMode(GroupId groupId); error RedeemLocked(GroupId groupId); error EarlyExit(GroupId groupId); error EmptyCollateralListOnUpdate(GroupId groupId); error InvalidGroupConfiguration(GroupId groupId); error NativeTokenWithdrawnFailed(uint256 amount); /** * @notice Mints tokens based on the provided parameters. * @param groupKey The key identifying the group. * @param params The mint parameters. * @return result The result of the mint operation. */ function mintToken( GroupKey calldata groupKey, DProtocol.MintParams calldata params ) external payable returns (DProtocol.MintResult memory result); /** * @notice Redeems tokens based on the provided parameters. * @param groupKey The key identifying the group. * @param params The redeem parameters. * @return baseOut The amount of base tokens received. */ function redeemToken(GroupKey calldata groupKey, DProtocol.RedeemParams calldata params) external returns (uint256 baseOut); /** * @notice Returns the stability ratio for a group. * @param groupId The group identifier. * @return The stability ratio. */ function stabilityRatio(GroupId groupId) external view returns (uint96); }
// SPDX-License-Identifier: MIT pragma solidity 0.8.28; import { GroupId } from "../types/GroupId.sol"; interface ITreasuryMinimum { function isUnderCollateral(GroupId groupId) external view returns (bool); function currentBaseTokenPrice(GroupId groupId) external view returns (uint256); function totalBaseToken(GroupId groupId) external view returns (uint256); function forceUpdateGroupCache(address tokenRegistry, GroupId groupId) external; }
// SPDX-License-Identifier: MIT pragma solidity 0.8.28; import { GroupId } from "../types/GroupId.sol"; import { DOperationContext } from "../declarations/DOperationContext.sol"; /** * @title IHooks * @notice Interface defining hooks that can be called during protocol operations * @dev This interface must be implemented by any contract that wants to act as a hook */ interface IHooks { /** * @notice Returns the permissions of the hook contract * @return A uint16 representing the permission flags */ function getHookPermissions() external view returns (uint16); /** * @notice Hook called before a mint operation * @param sender The address initiating the operation * @param groupId The group identifier * @param context The operation context * @param hookData The current hook data * @return selector The function selector (should be `beforeMint.selector`) * @return updatedHookData The updated hook data * @return lpFeeOverride An optional LP fee override */ function beforeMint( address sender, GroupId groupId, DOperationContext.OperationContext calldata context, bytes calldata hookData ) external returns (bytes4 selector, bytes memory updatedHookData, uint24 lpFeeOverride); /** * @notice Hook called after a mint operation * @param sender The address initiating the operation * @param groupId The group identifier * @param context The operation context * @param hookData The current hook data * @return selector The function selector (should be `afterMint.selector`) * @return updatedHookData The updated hook data */ function afterMint( address sender, GroupId groupId, DOperationContext.OperationContext calldata context, bytes calldata hookData ) external returns (bytes4 selector, bytes memory updatedHookData); /** * @notice Hook called before a redeem operation * @param sender The address initiating the operation * @param groupId The group identifier * @param context The operation context * @param hookData The current hook data * @return selector The function selector (should be `beforeRedeem.selector`) * @return updatedHookData The updated hook data * @return lpFeeOverride An optional LP fee override */ function beforeRedeem( address sender, GroupId groupId, DOperationContext.OperationContext calldata context, bytes calldata hookData ) external returns (bytes4 selector, bytes memory updatedHookData, uint24 lpFeeOverride); /** * @notice Hook called after a redeem operation * @param sender The address initiating the operation * @param groupId The group identifier * @param context The operation context * @param hookData The current hook data * @return selector The function selector (should be `afterRedeem.selector`) * @return updatedHookData The updated hook data */ function afterRedeem( address sender, GroupId groupId, DOperationContext.OperationContext calldata context, bytes calldata hookData ) external returns (bytes4 selector, bytes memory updatedHookData); }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts v4.4.1 (access/IAccessControl.sol) pragma solidity ^0.8.0; /** * @dev External interface of AccessControl declared to support ERC165 detection. */ interface IAccessControlUpgradeable { /** * @dev Emitted when `newAdminRole` is set as ``role``'s admin role, replacing `previousAdminRole` * * `DEFAULT_ADMIN_ROLE` is the starting admin for all roles, despite * {RoleAdminChanged} not being emitted signaling this. * * _Available since v3.1._ */ event RoleAdminChanged(bytes32 indexed role, bytes32 indexed previousAdminRole, bytes32 indexed newAdminRole); /** * @dev Emitted when `account` is granted `role`. * * `sender` is the account that originated the contract call, an admin role * bearer except when using {AccessControl-_setupRole}. */ event RoleGranted(bytes32 indexed role, address indexed account, address indexed sender); /** * @dev Emitted when `account` is revoked `role`. * * `sender` is the account that originated the contract call: * - if using `revokeRole`, it is the admin role bearer * - if using `renounceRole`, it is the role bearer (i.e. `account`) */ event RoleRevoked(bytes32 indexed role, address indexed account, address indexed sender); /** * @dev Returns `true` if `account` has been granted `role`. */ function hasRole(bytes32 role, address account) external view returns (bool); /** * @dev Returns the admin role that controls `role`. See {grantRole} and * {revokeRole}. * * To change a role's admin, use {AccessControl-_setRoleAdmin}. */ function getRoleAdmin(bytes32 role) external view returns (bytes32); /** * @dev Grants `role` to `account`. * * If `account` had not been already granted `role`, emits a {RoleGranted} * event. * * Requirements: * * - the caller must have ``role``'s admin role. */ function grantRole(bytes32 role, address account) external; /** * @dev Revokes `role` from `account`. * * If `account` had been granted `role`, emits a {RoleRevoked} event. * * Requirements: * * - the caller must have ``role``'s admin role. */ function revokeRole(bytes32 role, address account) external; /** * @dev Revokes `role` from the calling account. * * Roles are often managed via {grantRole} and {revokeRole}: this function's * purpose is to provide a mechanism for accounts to lose their privileges * if they are compromised (such as when a trusted device is misplaced). * * If the calling account had been granted `role`, emits a {RoleRevoked} * event. * * Requirements: * * - the caller must be `account`. */ function renounceRole(bytes32 role, address account) external; }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.4) (utils/Context.sol) pragma solidity ^0.8.0; import {Initializable} from "../proxy/utils/Initializable.sol"; /** * @dev Provides information about the current execution context, including the * sender of the transaction and its data. While these are generally available * via msg.sender and msg.data, they should not be accessed in such a direct * manner, since when dealing with meta-transactions the account sending and * paying for execution may not be the actual sender (as far as an application * is concerned). * * This contract is only required for intermediate, library-like contracts. */ abstract contract ContextUpgradeable is Initializable { function __Context_init() internal onlyInitializing { } function __Context_init_unchained() internal onlyInitializing { } function _msgSender() internal view virtual returns (address) { return msg.sender; } function _msgData() internal view virtual returns (bytes calldata) { return msg.data; } function _contextSuffixLength() internal view virtual returns (uint256) { return 0; } /** * @dev This empty reserved space is put in place to allow future versions to add new * variables without shifting down storage in the inheritance chain. * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps */ uint256[50] private __gap; }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (utils/Strings.sol) pragma solidity ^0.8.0; import "./math/MathUpgradeable.sol"; import "./math/SignedMathUpgradeable.sol"; /** * @dev String operations. */ library StringsUpgradeable { bytes16 private constant _SYMBOLS = "0123456789abcdef"; uint8 private constant _ADDRESS_LENGTH = 20; /** * @dev Converts a `uint256` to its ASCII `string` decimal representation. */ function toString(uint256 value) internal pure returns (string memory) { unchecked { uint256 length = MathUpgradeable.log10(value) + 1; string memory buffer = new string(length); uint256 ptr; /// @solidity memory-safe-assembly assembly { ptr := add(buffer, add(32, length)) } while (true) { ptr--; /// @solidity memory-safe-assembly assembly { mstore8(ptr, byte(mod(value, 10), _SYMBOLS)) } value /= 10; if (value == 0) break; } return buffer; } } /** * @dev Converts a `int256` to its ASCII `string` decimal representation. */ function toString(int256 value) internal pure returns (string memory) { return string(abi.encodePacked(value < 0 ? "-" : "", toString(SignedMathUpgradeable.abs(value)))); } /** * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation. */ function toHexString(uint256 value) internal pure returns (string memory) { unchecked { return toHexString(value, MathUpgradeable.log256(value) + 1); } } /** * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length. */ function toHexString(uint256 value, uint256 length) internal pure returns (string memory) { bytes memory buffer = new bytes(2 * length + 2); buffer[0] = "0"; buffer[1] = "x"; for (uint256 i = 2 * length + 1; i > 1; --i) { buffer[i] = _SYMBOLS[value & 0xf]; value >>= 4; } require(value == 0, "Strings: hex length insufficient"); return string(buffer); } /** * @dev Converts an `address` with fixed length of 20 bytes to its not checksummed ASCII `string` hexadecimal representation. */ function toHexString(address addr) internal pure returns (string memory) { return toHexString(uint256(uint160(addr)), _ADDRESS_LENGTH); } /** * @dev Returns true if the two strings are equal. */ function equal(string memory a, string memory b) internal pure returns (bool) { return keccak256(bytes(a)) == keccak256(bytes(b)); } }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts v4.4.1 (utils/introspection/ERC165.sol) pragma solidity ^0.8.0; import "./IERC165Upgradeable.sol"; import {Initializable} from "../../proxy/utils/Initializable.sol"; /** * @dev Implementation of the {IERC165} interface. * * Contracts that want to implement ERC165 should inherit from this contract and override {supportsInterface} to check * for the additional interface id that will be supported. For example: * * ```solidity * function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) { * return interfaceId == type(MyInterface).interfaceId || super.supportsInterface(interfaceId); * } * ``` * * Alternatively, {ERC165Storage} provides an easier to use but more expensive implementation. */ abstract contract ERC165Upgradeable is Initializable, IERC165Upgradeable { function __ERC165_init() internal onlyInitializing { } function __ERC165_init_unchained() internal onlyInitializing { } /** * @dev See {IERC165-supportsInterface}. */ function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) { return interfaceId == type(IERC165Upgradeable).interfaceId; } /** * @dev This empty reserved space is put in place to allow future versions to add new * variables without shifting down storage in the inheritance chain. * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps */ uint256[50] private __gap; }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (proxy/utils/Initializable.sol) pragma solidity ^0.8.2; import "../../utils/AddressUpgradeable.sol"; /** * @dev This is a base contract to aid in writing upgradeable contracts, or any kind of contract that will be deployed * behind a proxy. Since proxied contracts do not make use of a constructor, it's common to move constructor logic to an * external initializer function, usually called `initialize`. It then becomes necessary to protect this initializer * function so it can only be called once. The {initializer} modifier provided by this contract will have this effect. * * The initialization functions use a version number. Once a version number is used, it is consumed and cannot be * reused. This mechanism prevents re-execution of each "step" but allows the creation of new initialization steps in * case an upgrade adds a module that needs to be initialized. * * For example: * * [.hljs-theme-light.nopadding] * ```solidity * contract MyToken is ERC20Upgradeable { * function initialize() initializer public { * __ERC20_init("MyToken", "MTK"); * } * } * * contract MyTokenV2 is MyToken, ERC20PermitUpgradeable { * function initializeV2() reinitializer(2) public { * __ERC20Permit_init("MyToken"); * } * } * ``` * * TIP: To avoid leaving the proxy in an uninitialized state, the initializer function should be called as early as * possible by providing the encoded function call as the `_data` argument to {ERC1967Proxy-constructor}. * * CAUTION: When used with inheritance, manual care must be taken to not invoke a parent initializer twice, or to ensure * that all initializers are idempotent. This is not verified automatically as constructors are by Solidity. * * [CAUTION] * ==== * Avoid leaving a contract uninitialized. * * An uninitialized contract can be taken over by an attacker. This applies to both a proxy and its implementation * contract, which may impact the proxy. To prevent the implementation contract from being used, you should invoke * the {_disableInitializers} function in the constructor to automatically lock it when it is deployed: * * [.hljs-theme-light.nopadding] * ``` * /// @custom:oz-upgrades-unsafe-allow constructor * constructor() { * _disableInitializers(); * } * ``` * ==== */ abstract contract Initializable { /** * @dev Indicates that the contract has been initialized. * @custom:oz-retyped-from bool */ uint8 private _initialized; /** * @dev Indicates that the contract is in the process of being initialized. */ bool private _initializing; /** * @dev Triggered when the contract has been initialized or reinitialized. */ event Initialized(uint8 version); /** * @dev A modifier that defines a protected initializer function that can be invoked at most once. In its scope, * `onlyInitializing` functions can be used to initialize parent contracts. * * Similar to `reinitializer(1)`, except that functions marked with `initializer` can be nested in the context of a * constructor. * * Emits an {Initialized} event. */ modifier initializer() { bool isTopLevelCall = !_initializing; require( (isTopLevelCall && _initialized < 1) || (!AddressUpgradeable.isContract(address(this)) && _initialized == 1), "Initializable: contract is already initialized" ); _initialized = 1; if (isTopLevelCall) { _initializing = true; } _; if (isTopLevelCall) { _initializing = false; emit Initialized(1); } } /** * @dev A modifier that defines a protected reinitializer function that can be invoked at most once, and only if the * contract hasn't been initialized to a greater version before. In its scope, `onlyInitializing` functions can be * used to initialize parent contracts. * * A reinitializer may be used after the original initialization step. This is essential to configure modules that * are added through upgrades and that require initialization. * * When `version` is 1, this modifier is similar to `initializer`, except that functions marked with `reinitializer` * cannot be nested. If one is invoked in the context of another, execution will revert. * * Note that versions can jump in increments greater than 1; this implies that if multiple reinitializers coexist in * a contract, executing them in the right order is up to the developer or operator. * * WARNING: setting the version to 255 will prevent any future reinitialization. * * Emits an {Initialized} event. */ modifier reinitializer(uint8 version) { require(!_initializing && _initialized < version, "Initializable: contract is already initialized"); _initialized = version; _initializing = true; _; _initializing = false; emit Initialized(version); } /** * @dev Modifier to protect an initialization function so that it can only be invoked by functions with the * {initializer} and {reinitializer} modifiers, directly or indirectly. */ modifier onlyInitializing() { require(_initializing, "Initializable: contract is not initializing"); _; } /** * @dev Locks the contract, preventing any future reinitialization. This cannot be part of an initializer call. * Calling this in the constructor of a contract will prevent that contract from being initialized or reinitialized * to any version. It is recommended to use this to lock implementation contracts that are designed to be called * through proxies. * * Emits an {Initialized} event the first time it is successfully executed. */ function _disableInitializers() internal virtual { require(!_initializing, "Initializable: contract is initializing"); if (_initialized != type(uint8).max) { _initialized = type(uint8).max; emit Initialized(type(uint8).max); } } /** * @dev Returns the highest version that has been initialized. See {reinitializer}. */ function _getInitializedVersion() internal view returns (uint8) { return _initialized; } /** * @dev Returns `true` if the contract is currently initializing. See {onlyInitializing}. */ function _isInitializing() internal view returns (bool) { return _initializing; } }
// SPDX-License-Identifier: MIT pragma solidity 0.8.28; // solhint-disable no-inline-assembly /// @dev A subset copied from the following contracts: /// /// + `balancer-labs/v2-solidity-utils/contracts/helpers/WordCodec.sol` /// + `balancer-labs/v2-solidity-utils/contracts/helpers/WordCodecHelpers.sol` library WordCodec { /// @dev Inserts an unsigned integer of bitLength, shifted by an offset, into a 256 bit word, /// replacing the old value. Returns the new word. function insertUint(bytes32 word, uint256 value, uint256 offset, uint256 bitLength) internal pure returns (bytes32 result) { // Equivalent to: // uint256 mask = (1 << bitLength) - 1; // bytes32 clearedWord = bytes32(uint256(word) & ~(mask << offset)); // result = clearedWord | bytes32(value << offset); assembly("memory-safe") { let mask := sub(shl(bitLength, 1), 1) let clearedWord := and(word, not(shl(offset, mask))) result := or(clearedWord, shl(offset, value)) } } /// @dev Decodes and returns an unsigned integer with `bitLength` bits, shifted by an offset, from a 256 bit word. function decodeUint(bytes32 word, uint256 offset, uint256 bitLength) internal pure returns (uint256 result) { // Equivalent to: // result = uint256(word >> offset) & ((1 << bitLength) - 1); assembly("memory-safe") { result := and(shr(offset, word), sub(shl(bitLength, 1), 1)) } } /// @dev Inserts a signed integer shifted by an offset into a 256 bit word, replacing the old value. Returns /// the new word. /// function insertInt(bytes32 word, int256 value, uint256 offset, uint256 bitLength) internal pure returns (bytes32) { unchecked { uint256 mask = (1 << bitLength) - 1; bytes32 clearedWord = bytes32(uint256(word) & ~(mask << offset)); // Integer values need masking to remove the upper bits of negative values. return clearedWord | bytes32((uint256(value) & mask) << offset); } } /// @dev Decodes and returns a signed integer with `bitLength` bits, shifted by an offset, from a 256 bit word. function decodeInt(bytes32 word, uint256 offset, uint256 bitLength) internal pure returns (int256 result) { unchecked { int256 maxInt = int256((1 << (bitLength - 1)) - 1); uint256 mask = (1 << bitLength) - 1; int256 value = int256(uint256(word >> offset) & mask); // In case the decoded value is greater than the max positive integer that can be represented with bitLength // bits, we know it was originally a negative integer. Therefore, we mask it to restore the sign in the 256 bit // representation. // // Equivalent to: // result = value > maxInt ? (value | int256(~mask)) : value; assembly { result := or(mul(gt(value, maxInt), not(mask)), value) } } } /// @dev Decodes and returns a boolean shifted by an offset from a 256 bit word. function decodeBool(bytes32 word, uint256 offset) internal pure returns (bool result) { // Equivalent to: // result = (uint256(word >> offset) & 1) == 1; assembly { result := and(shr(offset, word), 1) } } /// @dev Inserts a boolean value shifted by an offset into a 256 bit word, replacing the old value. Returns the new /// word. function insertBool(bytes32 word, bool value, uint256 offset) internal pure returns (bytes32 result) { // Equivalent to: // bytes32 clearedWord = bytes32(uint256(word) & ~(1 << offset)); // bytes32 referenceInsertBool = clearedWord | bytes32(uint256(value ? 1 : 0) << offset); assembly("memory-safe") { let clearedWord := and(word, not(shl(offset, 1))) result := or(clearedWord, shl(offset, value)) } } function clearWordAtPosition(bytes32 word, uint256 offset, uint256 bitLength) internal pure returns (bytes32 clearedWord) { unchecked { uint256 mask = (1 << bitLength) - 1; clearedWord = bytes32(uint256(word) & ~(mask << offset)); } } /// @dev Encodes an address into a 256 bit word at a given offset. function insertAddress(bytes32 word, address value, uint256 offset) internal pure returns (bytes32 result) { assembly("memory-safe") { let clearedWord := and(word, not(shl(offset, 0xffffffffffffffffffffffffffffffffffffffff))) result := or(clearedWord, shl(offset, value)) } } /// @dev Decodes an address from a 256 bit word at a given offset. function decodeAddress(bytes32 word, uint256 offset) internal pure returns (address result) { assembly("memory-safe") { result := and(shr(offset, word), 0xffffffffffffffffffffffffffffffffffffffff) } } /// @dev Encodes an enum value into a 256 bit word at a given offset. function insertEnum(bytes32 word, uint8 value, uint256 offset) internal pure returns (bytes32 result) { assembly("memory-safe") { let clearedWord := and(word, not(shl(offset, 0xff))) result := or(clearedWord, shl(offset, value)) } } /// @dev Decodes an enum value from a 256 bit word at a given offset. function decodeEnum(bytes32 word, uint256 offset) internal pure returns (uint8 result) { assembly("memory-safe") { result := and(shr(offset, word), 0xff) } } }
// SPDX-License-Identifier: MIT pragma solidity 0.8.28; // Import the CustomRevert library for standardized error handling import { CustomRevert } from "../libs/CustomRevert.sol"; /** * @title Address * @dev Defines a user-defined value type `Address` that wraps the built-in `address` type. */ type Address is address; /** * @title AddressLibrary * @dev A library for performing various operations on the `Address` type. */ library AddressLibrary { // Apply the library functions to the `Address` type using AddressLibrary for Address; // Use the CustomRevert library for standardized error handling via selectors using CustomRevert for bytes4; // Custom error definitions for more descriptive revert reasons error ZeroAddress(); error FailedCall(string reason); error NonContractAddress(); error UnableToSendValue(); /** * @notice Checks if the given `Address` is the zero address. * @param addr The `Address` to check. * @return True if `addr` is the zero address, false otherwise. */ function isZero(Address addr) internal pure returns (bool) { return Address.unwrap(addr) == address(0); } /** * @notice Determines if the given `Address` is a contract. * @param addr The `Address` to check. * @return True if `addr` is a contract, false otherwise. * * @dev This method relies on the fact that contracts have non-zero code size. * It returns false for contracts in construction, since the code is only stored at the end of the constructor execution. */ function isContract(Address addr) internal view returns (bool) { return Address.unwrap(addr).code.length > 0; } /** * @notice Compares two `Address` instances for equality. * @param a The first `Address`. * @param b The second `Address`. * @return True if both addresses are equal, false otherwise. */ function equals(Address a, Address b) internal pure returns (bool) { address addrA = Address.unwrap(a); address addrB = Address.unwrap(b); return addrA == addrB; } /** * @notice Converts an `Address` to a `uint160`. * @param addr The `Address` to convert. * @return The `uint160` representation of the address. */ function toUint160(Address addr) internal pure returns (uint160) { return uint160(Address.unwrap(addr)); } /** * @notice Creates an `Address` from a `uint160`. * @param addr The `uint160` value to convert. * @return A new `Address` instance. */ function fromUint160(uint160 addr) internal pure returns (Address) { return Address.wrap(address(addr)); } /** * @notice Unwraps the `Address` type to retrieve the underlying address. * @param addr The `Address` to unwrap. * @return The underlying `address`. */ function toAddress(Address addr) internal pure returns (address) { return Address.unwrap(addr); } }
// SPDX-License-Identifier: MIT pragma solidity 0.8.28; import { GroupId } from "../types/GroupId.sol"; import { GroupState } from "../types/CommonTypes.sol"; interface ITokenRegistryMinimum { // Getter functions for group state function getGroup(GroupId groupId) external view returns (GroupState memory); }
// SPDX-License-Identifier: MIT pragma solidity 0.8.28; import { Currency } from "../types/Currency.sol"; import { Address } from "../types/Address.sol"; import { DefaultFeeParams, FeeParams, FeePermissions, CollateralInfo } from "../types/CommonTypes.sol"; library DTokenRegistry { // Core tokens of the group (immutable) struct GroupCore { Currency aToken; // Yield-bearing token Currency xToken; // Leverage token Currency baseToken; // Base token for the group - wrapped Avax Currency yieldBearingToken; // Example: staked AVAX (immutable) Currency wethToken; // WETH token address for the router } // Decimals for each core token struct GroupDecimals { uint8 aTokenDecimals; uint8 xTokenDecimals; uint8 baseTokenDecimals; uint8 yieldBearingTokenDecimals; } // Extended group settings (mutable) struct GroupExtended { Address priceOracle; // Price Oracle address Address rateProvider; // Rate provider address Address swapRouter; // Swap Router Address treasury; // Treasury address Address feeCollector; // Fee collector address Address strategy; // Strategy contract address Currency rebalancePool; // Rebalance pool address } // Metadata for the group (partially mutable) struct GroupMeta { uint96 stabilityRatio; // Mutable stability ratio (this is 2^96-1 and we have max 5e18) uint96 stabilityConditionsTriggeringRate; // Mutable stability fee trigger (this is 2^96-1 and we have max 5e18) uint8 feeModel; // Immutable fee model used for this group bool isWrappingRequired; // Immutable wrapping requirement flag } // Struct representing full group setup during creation struct GroupSetup { GroupCore core; GroupDecimals decimals; GroupExtended extended; GroupMeta meta; FeeParams fees; DefaultFeeParams defaultFees; FeePermissions feePermissions; CollateralInfo[] acceptableCollaterals; } // Data used for updating mutable parts of the group struct GroupUpdate { GroupExtended extended; GroupMeta meta; CollateralInfo[] acceptableCollaterals; FeeParams feeParams; DefaultFeeParams defaultFees; } }
// SPDX-License-Identifier: MI pragma solidity 0.8.28; import { CustomRevert } from "./CustomRevert.sol"; import { FeePermissions, DefaultFeeParams } from "../types/CommonTypes.sol"; /** * @title GroupFeeLibrary * @dev Provides utilities to manage fees for groups with their own fee structures and permissions. * This library handles fee validation, processing, and manipulation based on defined flags and permissions. */ library GroupFeeLibrary { // Utilize the CustomRevert library for standardized error handling via selectors. using CustomRevert for bytes4; /** * @dev Thrown when a fee exceeds the allowed maximum. * @param fee The fee value that is too large. */ error InvalidGroupFee(uint24 fee); /** * @dev Thrown when fee delegation is attempted without permission. */ error FeeDelegationNotAllowed(); error BaseFeeExceedslimit(); error MinFeeExceedsBaseFee(); error MaxFeeIsLessThanBaseFee(); error MaxFeeExceedsLimit(); /** * @dev Thrown when a dynamic fee operation is called but not allowed. */ error NotDynamicFeeGroup(); // ==================== // ==== Constants ==== // ==================== /** * @dev Flag indicating dynamic fees. Embedded in the fee's uint24 value. * Example: fee | 0x800000 */ uint24 public constant DYNAMIC_FEE_FLAG = 0x800000; /** * @dev Flag for overriding fees. Embedded in the fee's uint24 value. * Example: fee |0x400000 */ uint24 public constant OVERRIDE_FEE_FLAG = 0x400000; /** * @dev Flag allowing fee delegation. Embedded in the fee's uint24 value. * Example: fee | 0x200000 */ uint24 public constant DELEGATE_FEE_FLAG = 0x200000; /** * @dev Flag for explicit zero fee. Embedded in the fee's uint24 value. * Example: fee |0x100000 * @notice Group min Fee settings has the priority over this flag, so it's impossible where minFee not equal to zero */ uint24 public constant ZERO_FEE_FLAG = 0x100000; /** * @dev Mask to remove all flags from the fee's uint24 value. * Example: fee |0x1FFFFF * All flags (0x800000, 0x400000, 0x200000, 0x100000) are in the upper 4 bits * limiting the mask to 0x0FFFFF, all these flags are cleared. */ uint24 public constant REMOVE_ALL_FLAGS_MASK = 0x0FFFFF; /** * @dev Maximum allowed group fee (10,000 BPS). 50% expressed in basis points. */ uint24 public constant MAX_GROUP_FEE = 5_000; // ==================== // ==== Internal Functions ==== // ==================== /** * @notice Validates whether the given fee is within acceptable boundaries. * @param self The DefaultFeeParams struct containing fee parameters. * @param fee The fee value to validate. * @return True if the fee is valid, false otherwise. * * @dev The cleaned fee (without flags) must be within the min/max fee limits and not exceed MAX_GROUP_FEE. */ function isValid(DefaultFeeParams memory self, uint24 fee) internal pure returns (bool) { // Clean the fee by removing any flags (since flags do not affect fee validation). uint24 cleanFee = fee & REMOVE_ALL_FLAGS_MASK; // The cleaned fee must be within the min/max fee limits and not exceed MAX_GROUP_FEE. /// @dev The cleaned fee must be within the min/max fee limits and not exceed MAX_GROUP_FEE, eventhough hook sent with ZERO_FEE_FLAG return cleanFee >= self.minFee && cleanFee <= self.maxFee && cleanFee <= MAX_GROUP_FEE; } /** * @notice Checks if the fee has the dynamic fee flag set. * @param fee The fee value to check. * @return True if the dynamic fee flag is set, false otherwise. */ function isDynamicFee(uint24 fee) internal pure returns (bool) { return fee & DYNAMIC_FEE_FLAG != 0; } /** * @notice Checks if the fee has an override flag. * @param fee The fee value to check. * @return True if the override flag is set, false otherwise. */ function isOverride(uint24 fee) internal pure returns (bool) { return fee & OVERRIDE_FEE_FLAG != 0; } /** * @notice Checks if the fee has the delegation flag set. * @param fee The fee value to check. * @return True if the delegation flag is set, false otherwise. */ function isDelegate(uint24 fee) internal pure returns (bool) { return fee & DELEGATE_FEE_FLAG != 0; } /** * @notice Removes all flags from the fee (i.e., cleans it to its base value). * @param fee The fee value to clean. * @return cleanedFee The fee value without any flags. */ function _removeAllFlags(uint24 fee) internal pure returns (uint24 cleanedFee) { return fee & REMOVE_ALL_FLAGS_MASK; } /** * @notice Validates the group fee based on default fee parameters and permissions. * @param self The DefaultFeeParams struct containing fee parameters. * @param permissions The FeePermissions struct containing permission flags. * @param checkingFee The fee value to validate. * @return The validated fee. * * @dev Ensures the fee is within allowed bounds and adheres to permissions. */ function _validateGroupFee( DefaultFeeParams memory self, FeePermissions memory permissions, uint24 checkingFee ) internal pure returns (uint24) { // Ensure fee delegation is allowed. if (!permissions.allowDelegation && isDelegate(checkingFee)) { FeeDelegationNotAllowed.selector.revertWith(); } if (!permissions.isDynamic && isDynamicFee(checkingFee)) { NotDynamicFeeGroup.selector.revertWith(); } // Validate fee bounds bool validated = isValid(self, checkingFee); if (!validated) { InvalidGroupFee.selector.revertWith(checkingFee); } return checkingFee; } /** * @notice Processes the group fee, applying validations and possible overrides. * @param self The DefaultFeeParams struct containing fee parameters. * @param permissions The FeePermissions struct containing permission flags. * @param overrideFee The fee value that may override the default base fee. * @return cleanFee The processed and validated fee. */ function processGroupFee( DefaultFeeParams memory self, FeePermissions memory permissions, uint24 overrideFee ) internal pure returns (uint24 cleanFee) { cleanFee = _validateGroupFee(self, permissions, overrideFee); } /** * @notice Validates the fee parameters to ensure they are logically correct. * @param params The DefaultFeeParams struct containing fee parameters. * * @dev Ensures that: * - Base fee does not exceed MAX_GROUP_FEE. * - Minimum fee is not greater than the base fee. * - Maximum fee is not less than the base fee. * - Maximum fee does not exceed MAX_GROUP_FEE. */ function _validateFeeParams(DefaultFeeParams memory params) internal pure { // Base fee should not exceed the maximum group fee. if (params.baseFee > MAX_GROUP_FEE) { BaseFeeExceedslimit.selector.revertWith(); } // Min fee must be less than or equal to base fee. if (params.minFee > params.baseFee) { MinFeeExceedsBaseFee.selector.revertWith(); } // Max fee must be greater than or equal to base fee. if (params.maxFee < params.baseFee) { MaxFeeIsLessThanBaseFee.selector.revertWith(); } // Max fee should not exceed the maximum allowed group fee. if (params.maxFee > MAX_GROUP_FEE) { MaxFeeExceedsLimit.selector.revertWith(); } } }
// SPDX-License-Identifier: MIT pragma solidity 0.8.28; import { GroupId } from "../types/GroupId.sol"; import { DTokenRegistry } from "../declarations/DTokenRegistry.sol"; import { DTreasury } from "../declarations/DTreasury.sol"; import { TreasuryHarvestLibrary } from "../libs/TreasuryHarvestLibrary.sol"; interface ITreasury { // Events event HarvestYield(GroupId indexed groupId, uint256 indexed yieldBaseTokens); event HarvestFees(GroupId indexed groupId, uint256 indexed xTokenAmount, uint256 indexed aTokenAmount); event FeesDistributed(GroupId indexed groupId, uint256 indexed xTokenAmount, uint256 indexed baseTokenAmount); event UpdateBaseTokenCap(GroupId indexed groupId, uint256 indexed oldBaseTokenCap, uint256 indexed newBaseTokenCap); event Settle(GroupId indexed groupId, uint256 indexed oldPrice, uint256 indexed newPrice); event UpdateFeeCollector(address indexed oldFeeCollector, address indexed newFeeCollector); event GroupInitialized(GroupId indexed groupId, uint256 indexed baseTokenPrice); event RebalanceUpPerformed(GroupId indexed groupId, uint256 indexed newCollateralRatio); event RebalanceDownPerformed(GroupId indexed groupId, uint256 indexed newCollateralRatio); event TransferToStrategy(GroupId indexed groupId, uint256 indexed amount); // Custom Errors error ZeroAddress(); error ZeroAmount(); error OnlyStrategy(); error GroupAlreadyInitialized(GroupId groupId); error ErrorUnderCollateral(GroupId groupId); error ErrorExceedTotalCap(GroupId groupId); error ErrorInvalidTwapPrice(GroupId groupId); error NotPermitted(); error InvalidGroupConfiguration(GroupId groupId); error InvalidRate(GroupId groupId, address rateProvider); error InvalidRatio(GroupId groupId); error InvalidPrice(GroupId groupId); error InvalidBaseTokenCap(GroupId groupId); error ErrorInsufficientBalance(GroupId groupId, address token); error ErrorSwapFailed(); error ErrorDistributingYield(GroupId groupId); error ErrorWithdrawFromStrategy(); error ErrorCollateralRatioTooSmall(GroupId groupId); error MaximumAmountExceeded(GroupId groupId, uint256 maxAmount); error StrategyUnderflow(GroupId groupId); error InvalidTokenType(); error RenounceRoleProhibited(); // View Functions function collateralRatio(GroupId groupId) external view returns (uint256); function isUnderCollateral(GroupId groupId) external view returns (bool); function leverageRatio(GroupId groupId) external view returns (uint256); function currentBaseTokenPrice(GroupId groupId) external view returns (uint256); // function isBaseTokenPriceValid(GroupId groupId) external view returns (bool _isValid); function totalBaseToken(GroupId groupId) external view returns (uint256); function maxMintableAToken( GroupId groupId, uint256 _newCollateralRatio ) external view returns (uint256 _maxBaseIn, uint256 _maxATokenMintable); function maxRedeemableAToken( GroupId groupId, uint256 _newCollateralRatio ) external view returns (uint256 _maxBaseOut, uint256 _maxATokenRedeemable); // State-Changing Functions function mintAToken(GroupId groupId, uint256 _baseIn, address _recipient) external returns (uint256 _aTokenOut); function mintXToken(GroupId groupId, uint256 _baseIn, address _recipient) external returns (uint256 _xTokenOut); function redeem(GroupId groupId, uint256 _aTokenIn, uint256 _xTokenIn, address _owner) external returns (uint256 _baseOut); function transferToStrategy(GroupId groupId, uint256 _amount) external; // Harvest Functions function harvestYield(GroupId groupId, TreasuryHarvestLibrary.HarvestParams memory params) external; function harvestFees(GroupId groupId, TreasuryHarvestLibrary.HarvestParams memory params) external; // Management Functions function updateBaseTokenCap(GroupId groupId, uint256 _baseTokenCap) external; function updateFeeCollector(address _newFeeCollector) external; function forceUpdateGroupCache(address tokenRegistry, GroupId groupId) external; function getTreasuryState(GroupId groupId) external view returns (DTreasury.TreasuryState memory); }
// SPDX-License-Identifier: MIT pragma solidity 0.8.28; import { IERC20Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol"; import { GroupId } from "../types/GroupId.sol"; /** * @title IWToken * @notice Interface for the WToken contract, enabling wrapping and unwrapping of an underlying token. */ interface IWToken is IERC20Upgradeable { /** * @dev Emitted when tokens are wrapped into WTokens. * @param user The address of the user wrapping tokens. * @param underlyingAmount The amount of underlying tokens wrapped. * @param amount The amount of tokens wrapped. */ event TokenWrapped(address indexed user, uint256 indexed underlyingAmount, uint256 indexed amount); /** * @dev Emitted when WTokens are unwrapped into underlying tokens. * @param user The address of the user unwrapping tokens. * @param underlyingAmount The amount of underlying tokens unwrapped. * @param amount The amount of WTokens unwrapped. */ event TokenUnwrapped(address indexed user, uint256 indexed underlyingAmount, uint256 indexed amount); /** * @dev Emitted when the treasury address is updated. * @param oldTreasury The address of the previous treasury contract. * @param newTreasury The address of the new treasury contract. */ event UpdateTreasuryAddress(address indexed oldTreasury, address indexed newTreasury); /** * @dev Emitted when the associated group ID is updated. * @param groupId New group identifier. */ event UpdateGroup(GroupId indexed groupId); event Rebase(uint256 indexed epoch, uint256 indexed newScalar); event RateProviderUpdated(address indexed rateProvider); // Custom Errors error ErrorZeroAddress(); error InvalidDecimals(); error ErrorZeroAmount(); error ErrorNotPermitted(); error ErrorCannotRecoverToken(); error ErrorMaxUnderlyingExceeded(); /** * @notice Wraps underlying tokens into WTokens. * @param underlyingAmount The amount of underlying tokens to wrap. * @return wrappedAmount amount of WTokens minted. */ function wrap(uint256 underlyingAmount) external returns (uint256 wrappedAmount); /** * @notice Unwraps WTokens back into underlying tokens. * @param burnAmount The amount of WTokens to burn. * @return amount of underlying tokens returned. */ function unwrap(uint256 burnAmount) external returns (uint256 amount); /** * @notice rebase the WToken */ function rebase() external; function updateRateProvider(address _rateProvider) external; }
// SPDX-License-Identifier: MIT pragma solidity 0.8.28; interface IDEXRouter { function swapExactTokensForTokens( uint256 amountIn, uint256 amountOutMin, address[] calldata path, address to, uint256 deadline ) external returns (uint256[] memory amounts); function swapExactETHForTokens( uint256 amountOutMin, address[] calldata path, address to, uint256 deadline ) external payable returns (uint256[] memory amounts); function swapExactTokensForETH( uint256 amountIn, uint256 amountOutMin, address[] calldata path, address to, uint256 deadline ) external returns (uint256[] memory amounts); // function getAmountsOut( // uint256 amountIn, // address[] calldata path // ) external view returns (uint256[] memory amounts); }
// SPDX-License-Identifier: MIT pragma solidity 0.8.28; import { IERC4626Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC4626Upgradeable.sol"; /** * @title IRebalancePool * @notice Interface for the RebalancePool contract implementing ERC4626 */ interface IRebalancePool is IERC4626Upgradeable { /// @notice Errors error ZeroAddress(); error ZeroDeposit(); error InvalidDepositAmount(); error ZeroRedeem(); error TokenAlreadyAdded(address token); error ZeroShares(); error InvalidPriceFromOracle(); error NotPermitted(); error InvalidYieldFee(); error RedeemingNotAvailableYet(); error CannotRecoverToken(); error UpdatingNotAvailableYet(); error InvalidCoolingPeriod(); error CoolingPeriodTriggered(); /// @notice Events event YieldDistributed(uint256 indexed assets, uint256 indexed shares); event NAVUpdated(address indexed caller, uint256 indexed totalAssets, uint256 indexed totalSupply); event CoolingOffPeriodUpdated(uint256 indexed oldPeriod, uint256 indexed newPeriod); event FeeCollectorUpdated(address indexed oldCollector, address indexed newCollector); event PriceOracleUpdated(address indexed oldOracle, address indexed newOracle); event OtherERC20Withdrawn(address indexed receiver, address indexed token, uint256 indexed amount); event DepositMade(address indexed depositor, address indexed receiver, uint256 indexed amount); event YieldFeeUpdated(uint256 indexed newFee); /// @notice Function to update NAV externally function updateNAV() external; /// @notice Get NAV per share function getNavPerShare() external view returns (uint256); /// @notice Override decimals function decimals() external view override returns (uint8); /// @notice Get cooling off period function coolingOffPeriod() external view returns (uint256); /// @notice Transfer tokens to treasury function transferTokenToTreasury(address token, uint256 amount) external; /// @notice Get yieldFeePercentage function yieldFeePercentage() external view returns (uint256); /// @notice Get base points function BASE_POINTS() external view returns (uint256); }
// SPDX-License-Identifier: MIT pragma solidity 0.8.28; import { ITreasury } from "../interfaces/ITreasury.sol"; import { OperationContextLibrary } from "./OperationContextLibrary.sol"; import { DOperationContext } from "../declarations/DOperationContext.sol"; import { GroupId } from "../types/GroupId.sol"; import { Address, AddressLibrary } from "../types/Address.sol"; import { FeeModel, OperationTypes } from "../types/CommonTypes.sol"; library StabilityFeeOperations { using OperationContextLibrary for DOperationContext.OperationContext; using AddressLibrary for Address; uint256 private constant PRECISION = 1e18; uint256 private constant BASIS_POINTS = 10_000; error ErrorStabilityModeMintNotAllowed(GroupId groupId, uint8 operationType, uint8 feeModel, bool isInStabilityMode); error ErrorStabilityModeRedeemNotAllowed(GroupId groupId, uint8 operationType, uint8 feeModel, bool isInStabilityMode); struct StabilityState { uint256 maxBaseInOrOut; // Maximum base token input/output before stability mode uint256 maxTokenAmount; // Maximum token amount for mint/redeem bool isInStabilityMode; // Whether system is in stability mode uint256 boundedAmount; // Amount bounded by stability restrictions uint24 appliedFeeRate; // Fee rate to apply based on stability state } /** * @notice Gets stability state information for the current operation. * @param context The operation context. * @param amount The requested amount for the operation. * @return state The stability state containing relevant information. */ function getStabilityState( DOperationContext.OperationContext memory context, uint256 amount ) internal view returns (StabilityState memory state) { ITreasury treasury = ITreasury(context.getTreasury().toAddress()); uint256 stabilityRatio = context.getStabilityRatio(); uint256 collateralRatio = context.getContextCollateralRatio(); uint8 operationType = context.getOperationType(); // Step 1: Determine if we're in stability mode state.isInStabilityMode = collateralRatio < stabilityRatio; // Step 2: Validate stability mode restrictions if (context.isMintOperation()) { if (operationType == uint8(OperationTypes.OP_TYPE_MINT_YT)) { validateMintStabilityMode(context, state); // Only get maxMintable amounts if we pass stability validation (state.maxBaseInOrOut, state.maxTokenAmount) = treasury.maxMintableAToken(context.groupId, stabilityRatio); state.boundedAmount = boundAmount(amount, state.maxBaseInOrOut); } else { // For VT minting, no bounds needed state.maxBaseInOrOut = type(uint256).max; state.maxTokenAmount = type(uint256).max; state.boundedAmount = amount; } } else { // Redemption operations // Step 3: Check stability mode restrictions for redemptions validateRedeemStabilityMode(context, state); // Set boundedAmount directly to the requested amount state.boundedAmount = amount; } return state; } /** * @notice Validates mint operations in stability mode. * @param context The operation context. * @param state The current stability state. */ function validateMintStabilityMode(DOperationContext.OperationContext memory context, StabilityState memory state) private pure { if (!state.isInStabilityMode) return; // Only check YT minting restrictions if (context.getOperationType() == uint8(OperationTypes.OP_TYPE_MINT_YT)) { uint8 feeModel = context.getFeeModel(); // Allow minting only for specific fee models in stability mode if (feeModel != uint8(FeeModel.CURATED_PAIRS_FEE) && feeModel != uint8(FeeModel.BLANK_FEE)) { revert ErrorStabilityModeMintNotAllowed(context.groupId, context.getOperationType(), feeModel, state.isInStabilityMode); } } } /** * @notice Validates redeem operations in stability mode. * @param context The operation context. * @param state The current stability state. */ function validateRedeemStabilityMode(DOperationContext.OperationContext memory context, StabilityState memory state) private pure { if (!state.isInStabilityMode) return; // Restrict VT redemption in stability mode if (context.getOperationType() == uint8(OperationTypes.OP_TYPE_REDEEM_VT)) { uint8 feeModel = context.getFeeModel(); revert ErrorStabilityModeRedeemNotAllowed(context.groupId, feeModel, context.getFeeModel(), state.isInStabilityMode); } } /** * @notice Bounds mint amount based on stability limits. * @param amount The requested amount. * @param maxAmount The maximum allowed amount. * @return The bounded amount. */ function boundAmount(uint256 amount, uint256 maxAmount) private pure returns (uint256) { return amount > maxAmount ? maxAmount : amount; } }
// SPDX-License-Identifier: MIT pragma solidity 0.8.28; /** * @title IPriceOracle * @notice Interface for the PriceOracle contract, managing and updating token price feeds. */ interface IPriceOracle { /** * @dev Emitted when a price feed is added for a token. * @param token The address of the token. * @param feed The address of the Chainlink price feed. */ event FeedAdded(address indexed token, address indexed feed); /** * @dev Emitted when a price feed is removed for a token. * @param token The address of the token. */ event FeedRemoved(address indexed token); /** * @dev Emitted when a price feed is updated for a token. * @param token The address of the token. * @param oldFeed The address of the old price feed. * @param newFeed The address of the new price feed. */ event FeedUpdated(address indexed token, address indexed oldFeed, address indexed newFeed); // Custom Errors error ZeroAddress(); error InvalidAddress(); error InvalidOperation(); error InvalidAmount(); error StalePrice(); error InvalidInput(); error DuplicateEntry(); /** * @notice Gets the latest price data for a specified token. * @param token The address of the token. * @return isValid Whether the price is valid. * @return price The last updated price (18 decimals). */ function getPrice(address token) external view returns (bool isValid, uint256 price); /** * @notice gets the token decimals from the feed */ function getFeedDecimals(address token) external view returns (uint8 decimals); }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; // solhint-disable /// @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 = a * b; // Least significant 256 bits of the product uint256 prod1; // Most significant 256 bits of the product assembly ("memory-safe") { let mm := mulmod(a, b, not(0)) prod1 := sub(sub(mm, prod0), lt(mm, prod0)) } // Make sure the result is less than 2**256. // Also prevents denominator == 0 require(denominator > prod1, "FullMath: denominator too small"); // Handle non-overflow cases, 256 by 256 division if (prod1 == 0) { assembly ("memory-safe") { result := div(prod0, denominator) } return result; } /////////////////////////////////////////////// // 512 by 256 division. /////////////////////////////////////////////// // Make division exact by subtracting the remainder from [prod1 prod0] // Compute remainder using mulmod uint256 remainder; assembly ("memory-safe") { remainder := mulmod(a, b, denominator) } // Subtract 256 bit number from 512 bit number assembly ("memory-safe") { prod1 := sub(prod1, gt(remainder, prod0)) prod0 := sub(prod0, remainder) } // Factor powers of two out of denominator // Compute largest power of two divisor of denominator. // Always >= 1. uint256 twos = (0 - denominator) & denominator; // Divide denominator by power of two assembly ("memory-safe") { denominator := div(denominator, twos) } // Divide [prod1 prod0] by the factors of two assembly ("memory-safe") { prod0 := div(prod0, twos) } // Shift in bits from prod1 into prod0. For this we need // to flip `twos` such that it is 2**256 / twos. // If twos is zero, then it becomes one assembly ("memory-safe") { twos := add(div(sub(0, twos), twos), 1) } prod0 |= prod1 * twos; // Invert denominator mod 2**256 // Now that denominator is an odd number, it has an inverse // modulo 2**256 such that denominator * inv = 1 mod 2**256. // Compute the inverse by starting with a seed that is correct // correct for four bits. That is, denominator * inv = 1 mod 2**4 uint256 inv = (3 * denominator) ^ 2; // Now use Newton-Raphson iteration to improve the precision. // Thanks to Hensel's lifting lemma, this also works in modular // arithmetic, doubling the correct bits in each step. inv *= 2 - denominator * inv; // inverse mod 2**8 inv *= 2 - denominator * inv; // inverse mod 2**16 inv *= 2 - denominator * inv; // inverse mod 2**32 inv *= 2 - denominator * inv; // inverse mod 2**64 inv *= 2 - denominator * inv; // inverse mod 2**128 inv *= 2 - denominator * inv; // inverse mod 2**256 // Because the division is now exact we can divide by multiplying // with the modular inverse of denominator. This will give us the // correct result modulo 2**256. Since the preconditions guarantee // that the outcome is less than 2**256, this is the final result. // We don't need to compute the high bits of the result and prod1 // is no longer required. result = prod0 * inv; return result; } } /// @notice Calculates ceil(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 function mulDivRoundingUp(uint256 a, uint256 b, uint256 denominator) internal pure returns (uint256 result) { unchecked { result = mulDiv(a, b, denominator); if (mulmod(a, b, denominator) != 0) { require(++result > 0, "FullMath: addition overflow"); } } } }
// SPDX-License-Identifier: MIT pragma solidity 0.8.28; import { WordCodec } from "../libs/WordCodec.sol"; import { CustomRevert } from "../libs/CustomRevert.sol"; import { GroupFeeLibrary } from "../libs/GroupFeeLibrary.sol"; import { OperationTypes } from "./CommonTypes.sol"; type Slot0 is bytes32; /** * @title Slot0Library * @notice Library for managing operation types, fees, and rates in a space-efficient manner * @dev Uses bit packing to store multiple values in a single bytes32 slot */ library Slot0Library { using WordCodec for bytes32; using CustomRevert for bytes4; // Operation Type Constants uint8 internal constant MAX_OPERATION_TYPE = 3; // Fee Range Constants /// @dev Same as GroupFeeLibrary uint24 internal constant MAX_FEE = 10_000; /// 100% in basis points (1e4) uint24 internal constant MAX_OVERRIDING_FEE = 5_000; /// 50% in basis points (5e3) uint64 internal constant MAX_RATE = type(uint64).max; // ~18.44e18 uint96 internal constant MAX_STABILITY_TRIGGER = type(uint96).max; // Offsets uint256 internal constant OPERATION_TYPE_OFFSET = 0; // 8 bits uint256 internal constant CONTEXT_PROTOCOL_FEE_OFFSET = 8; // 24 bits uint256 internal constant CONTEXT_FEE_OFFSET = 32; // 24 bits uint256 internal constant OVERRIDDEN_FEE_OFFSET = 56; // 24 bits uint256 internal constant STABILITY_FEE_TRIGGER_OFFSET = 80; // 96 bits for stability fee trigger // Bit lengths for the values uint256 internal constant OPERATION_TYPE_LENGTH = 8; uint256 internal constant CONTEXT_PROTOCOL_FEE_LENGTH = 24; uint256 internal constant CONTEXT_FEE_LENGTH = 24; uint256 internal constant OVERRIDDEN_FEE_LENGTH = 24; uint256 internal constant RATE_LENGTH = 64; /// @dev ~18.44e18 is sufficient for rate uint256 internal constant STABILITY_FEE_TRIGGER_LENGTH = 96; // Custom errors error InvalidOperationType(uint8 operationType); error InvalidFee(uint24 fee); error InvalidOverridenFee(uint24 fee); error InvalidRate(uint64 rate); error InvalidStabilityTrigger(uint96 trigger); error InvalidPackedData(); error OperationTypeMismatch(); // Validation modifiers /** * @dev Validates that a protocol fees is within acceptable bounds */ modifier validateProtocolFee(uint24 fee) { if (fee & GroupFeeLibrary.REMOVE_ALL_FLAGS_MASK > MAX_FEE) revert InvalidFee(fee); _; } /** * @dev Validates that hook fee is within acceptable bounds */ modifier validateOverridenFee(uint24 fee) { if ((fee & GroupFeeLibrary.REMOVE_ALL_FLAGS_MASK) > MAX_OVERRIDING_FEE) revert InvalidFee(fee); _; } /** * @dev Validates that an operation type is valid */ modifier validateOperationType(uint8 opType) { if (opType > MAX_OPERATION_TYPE) revert InvalidOperationType(opType); _; } // Getter Functions /// @notice Retrieves the operation type from Slot0. /// @param _packed The packed Slot0 data /// @return The operation type function operationType(Slot0 _packed) internal pure returns (uint8) { return uint8(Slot0.unwrap(_packed).decodeUint(OPERATION_TYPE_OFFSET, OPERATION_TYPE_LENGTH)); } /// @notice Retrieves the context protocol fee from Slot0. /// @param _packed The packed Slot0 data /// @return The context protocol fee function contextProtocolFee(Slot0 _packed) internal pure returns (uint24) { uint24 fee = uint24(Slot0.unwrap(_packed).decodeUint(CONTEXT_PROTOCOL_FEE_OFFSET, CONTEXT_PROTOCOL_FEE_LENGTH)); if ((fee & GroupFeeLibrary.REMOVE_ALL_FLAGS_MASK) > MAX_FEE) revert InvalidFee(fee); return fee; } /// @notice Retrieves the context LP fee from Slot0. /// @param _packed The packed Slot0 data /// @return The context LP fee function contextFee(Slot0 _packed) internal pure returns (uint24) { uint24 fee = uint24(Slot0.unwrap(_packed).decodeUint(CONTEXT_FEE_OFFSET, CONTEXT_FEE_LENGTH)); if ((fee & GroupFeeLibrary.REMOVE_ALL_FLAGS_MASK) > MAX_FEE) revert InvalidFee(fee); return fee; } /// @notice Retrieves the overridden fee from Slot0. /// @param _packed The packed Slot0 data /// @return The overridden fee function overriddenFee(Slot0 _packed) internal pure returns (uint24) { uint24 fee = uint24(Slot0.unwrap(_packed).decodeUint(OVERRIDDEN_FEE_OFFSET, OVERRIDDEN_FEE_LENGTH)); if ((fee & GroupFeeLibrary.REMOVE_ALL_FLAGS_MASK) > MAX_OVERRIDING_FEE) revert InvalidFee(fee); return fee; } /// @notice Retrieves the stability fee trigger ratio from Slot0. /// @param _packed The packed Slot0 data /// @return The stability fee trigger ratio function stabilityConditionsTriggeringRate(Slot0 _packed) internal pure returns (uint256) { return uint256(Slot0.unwrap(_packed).decodeUint(STABILITY_FEE_TRIGGER_OFFSET, STABILITY_FEE_TRIGGER_LENGTH)); } // Pack Function /// @notice Packs all Slot0 data into a single bytes32. /// @param _operationType The operation type to pack /// @param _contextProtocolFee The context protocol fee to pack /// @param _contextFee The context fee to pack /// @param _overriddenFee The overridden fee to pack /// @param _stabilityFeeTrigger The stability fee trigger to pack /// @return _packed The packed Slot0 data function pack( uint8 _operationType, uint24 _contextProtocolFee, uint24 _contextFee, uint24 _overriddenFee, uint96 _stabilityFeeTrigger ) internal pure validateOperationType(_operationType) validateProtocolFee(_contextProtocolFee) validateProtocolFee(_contextFee) validateOverridenFee(_overriddenFee) returns (Slot0 _packed) { bytes32 packed = 0; packed = packed.insertUint(_operationType, OPERATION_TYPE_OFFSET, OPERATION_TYPE_LENGTH); packed = packed.insertUint(_contextProtocolFee, CONTEXT_PROTOCOL_FEE_OFFSET, CONTEXT_PROTOCOL_FEE_LENGTH); packed = packed.insertUint(_contextFee, CONTEXT_FEE_OFFSET, CONTEXT_FEE_LENGTH); packed = packed.insertUint(_overriddenFee, OVERRIDDEN_FEE_OFFSET, OVERRIDDEN_FEE_LENGTH); packed = packed.insertUint(_stabilityFeeTrigger, STABILITY_FEE_TRIGGER_OFFSET, STABILITY_FEE_TRIGGER_LENGTH); if (!isValidPacked(packed)) revert InvalidPackedData(); return Slot0.wrap(packed); } // Setter Functions /// @notice Sets the overridden fee in Slot0. /// @param _packed Current Slot0 data /// @param _overriddenFee New overridden fee value /// @return Updated Slot0 data function setOverriddenFee(Slot0 _packed, uint24 _overriddenFee) internal pure validateOverridenFee(_overriddenFee) returns (Slot0) { bytes32 packed = Slot0.unwrap(_packed).insertUint(_overriddenFee, OVERRIDDEN_FEE_OFFSET, OVERRIDDEN_FEE_LENGTH); if (!isValidPacked(packed)) revert InvalidPackedData(); return Slot0.wrap(packed); } /// @notice Sets the context fee in Slot0. /// @param _packed Current Slot0 data /// @param _contextFee New context fee value /// @return Updated Slot0 data function setContextFee(Slot0 _packed, uint24 _contextFee) internal pure validateProtocolFee(_contextFee) returns (Slot0) { bytes32 packed = Slot0.unwrap(_packed).insertUint(_contextFee, CONTEXT_FEE_OFFSET, CONTEXT_FEE_LENGTH); if (!isValidPacked(packed)) revert InvalidPackedData(); return Slot0.wrap(packed); } /// @notice Sets the context protocol fee in Slot0. /// @param _packed Current Slot0 data /// @param _contextProtocolFee New context protocol fee value /// @return Updated Slot0 data function setContextProtocolFee( Slot0 _packed, uint24 _contextProtocolFee ) internal pure validateProtocolFee(_contextProtocolFee) returns (Slot0) { bytes32 packed = Slot0.unwrap(_packed).insertUint(_contextProtocolFee, CONTEXT_PROTOCOL_FEE_OFFSET, CONTEXT_PROTOCOL_FEE_LENGTH); if (!isValidPacked(packed)) revert InvalidPackedData(); return Slot0.wrap(packed); } // Internal Helper Functions /// @dev Validates that packed data contains valid values /// @param packed The packed data to validate /// @return True if the packed data is valid function isValidPacked(bytes32 packed) internal pure returns (bool) { uint8 _opType = uint8(packed.decodeUint(OPERATION_TYPE_OFFSET, OPERATION_TYPE_LENGTH)); uint24 _protocolFee = uint24(packed.decodeUint(CONTEXT_PROTOCOL_FEE_OFFSET, CONTEXT_PROTOCOL_FEE_LENGTH)); uint24 _contextFee = uint24(packed.decodeUint(CONTEXT_FEE_OFFSET, CONTEXT_FEE_LENGTH)); uint24 _overriddenFee = uint24(packed.decodeUint(OVERRIDDEN_FEE_OFFSET, OVERRIDDEN_FEE_LENGTH)); return _opType <= MAX_OPERATION_TYPE && (_protocolFee & GroupFeeLibrary.REMOVE_ALL_FLAGS_MASK) <= MAX_FEE && (_contextFee & GroupFeeLibrary.REMOVE_ALL_FLAGS_MASK) <= MAX_FEE && (_overriddenFee & GroupFeeLibrary.REMOVE_ALL_FLAGS_MASK) <= MAX_OVERRIDING_FEE; } }
// SPDX-License-Identifier: MIT pragma solidity 0.8.28; interface IRateProvider { function getRate() external view returns (uint256); function rateDecimals() external view returns (uint256); }
// SPDX-License-Identifier: MIT pragma solidity 0.8.28; import { Currency } from "../types/Currency.sol"; import { GroupId } from "../types/GroupId.sol"; import { GroupKey } from "../types/GroupKey.sol"; import { DTokenRegistry } from "../declarations/DTokenRegistry.sol"; import { GroupState, FeePermissions, FeeParams, DefaultFeeParams, CollateralInfo } from "../types/CommonTypes.sol"; interface ITokenRegistry { // Events event GroupAdded(GroupId indexed groupId); event GroupUpdated(GroupId indexed groupId); event CollateralAdded(GroupId indexed groupId, Currency indexed token, uint8 indexed decimals); event CollateralRemoved(GroupId indexed groupId, Currency indexed token); event HookContractSet(GroupId indexed groupId, address indexed newHookContract, bool indexed isDynamic, bool isDelegated); event HookPermissionsSet(GroupId indexed groupId, uint256 indexed newPermissions); event UpdateFeePermissions(GroupId indexed groupId, bool indexed isDynamic, bool indexed allowDelegation); // Custom Errors error AddressNotContract(address addr); error NotPermitted(); error GroupNotFound(GroupId groupId); error GroupAlreadyExists(GroupId groupId); error InvalidDecimals(); error InvalidMinMaxCollateralAmount(); error CollateralAlreadyExists(); error MultipleNativeTokensNotAllowed(); error CollateralNotFound(); error InvalidGroupSetup(); error ZeroAddress(); error EmptyCollateralList(); error MaxCollateralsLimitReached(); error StabilityRatioTooLarge(); error stabilityConditionsTriggeringRateTooLarge(); error InvalidStabilityTriggeringRatio(GroupId groupId); error FeesTooHigh(); error InvalidMaxFee(); error InvalidMinFee(); error InvalidBaseFee(); error InvalidYieldFee(); error InvalidStabilityFee(); error InvalidProtocolFee(); error InvalidFeeFlags(); error FeesAreNotCompatibleWithDefaultFees(); error CannotRecoverToken(); // Group management functions (Manager Role) function addGroup( DTokenRegistry.GroupSetup calldata setup, // Full group setup address hookContract, // Hook contract for the group uint256 hookPermissions // Hook permissions ) external; // Collateral management functions (Manager Role) function addCollateral(GroupKey calldata key, CollateralInfo calldata collateral) external; function removeCollateral(GroupKey calldata key, Currency token) external; // Getter functions for group state function getGroup(GroupId groupId) external view returns (GroupState memory); function getStabilityRatio(GroupId groupId) external view returns (uint96); function getStabilityTriggeringRatio(GroupId groupId) external view returns (uint96); function getProtocolFee(GroupId groupId) external view returns (uint24); function getFeePermissions(GroupId groupId) external view returns (FeePermissions memory); function getFeeParams(GroupId groupId) external view returns (FeeParams memory); function getMintFeeVT(GroupId groupId) external view returns (uint24); function getRedeemFeeVT(GroupId groupId) external view returns (uint24); function getMintFeeYT(GroupId groupId) external view returns (uint24); function getRedeemFeeYT(GroupId groupId) external view returns (uint24); function getYieldFeeYT(GroupId groupId) external view returns (uint24); function getStabilityMintFeeVT(GroupId groupId) external view returns (uint24); function getStabilityMintFeeYT(GroupId groupId) external view returns (uint24); function getStabilityRedeemFeeVT(GroupId groupId) external view returns (uint24); function getStabilityRedeemFeeYT(GroupId groupId) external view returns (uint24); function getHookContract(GroupId groupId) external view returns (address); function getHookPermissions(GroupId groupId) external view returns (uint256); function validateCollateral(GroupId groupId, Currency currency) external view returns (bool); function isWrappingRequired(GroupId groupId) external view returns (bool); function getFeeModel(GroupId groupId) external view returns (uint8); }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.3) (token/ERC20/utils/SafeERC20.sol) pragma solidity ^0.8.0; import "../IERC20Upgradeable.sol"; import "../extensions/IERC20PermitUpgradeable.sol"; import "../../../utils/AddressUpgradeable.sol"; /** * @title SafeERC20 * @dev Wrappers around ERC20 operations that throw on failure (when the token * contract returns false). Tokens that return no value (and instead revert or * throw on failure) are also supported, non-reverting calls are assumed to be * successful. * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract, * which allows you to call the safe operations as `token.safeTransfer(...)`, etc. */ library SafeERC20Upgradeable { using AddressUpgradeable for address; /** * @dev Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value, * non-reverting calls are assumed to be successful. */ function safeTransfer(IERC20Upgradeable token, address to, uint256 value) internal { _callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value)); } /** * @dev Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the * calling contract. If `token` returns no value, non-reverting calls are assumed to be successful. */ function safeTransferFrom(IERC20Upgradeable token, address from, address to, uint256 value) internal { _callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value)); } /** * @dev Deprecated. This function has issues similar to the ones found in * {IERC20-approve}, and its usage is discouraged. * * Whenever possible, use {safeIncreaseAllowance} and * {safeDecreaseAllowance} instead. */ function safeApprove(IERC20Upgradeable token, address spender, uint256 value) internal { // safeApprove should only be called when setting an initial allowance, // or when resetting it to zero. To increase and decrease it, use // 'safeIncreaseAllowance' and 'safeDecreaseAllowance' require( (value == 0) || (token.allowance(address(this), spender) == 0), "SafeERC20: approve from non-zero to non-zero allowance" ); _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value)); } /** * @dev Increase the calling contract's allowance toward `spender` by `value`. If `token` returns no value, * non-reverting calls are assumed to be successful. */ function safeIncreaseAllowance(IERC20Upgradeable token, address spender, uint256 value) internal { uint256 oldAllowance = token.allowance(address(this), spender); _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, oldAllowance + value)); } /** * @dev Decrease the calling contract's allowance toward `spender` by `value`. If `token` returns no value, * non-reverting calls are assumed to be successful. */ function safeDecreaseAllowance(IERC20Upgradeable token, address spender, uint256 value) internal { unchecked { uint256 oldAllowance = token.allowance(address(this), spender); require(oldAllowance >= value, "SafeERC20: decreased allowance below zero"); _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, oldAllowance - value)); } } /** * @dev Set the calling contract's allowance toward `spender` to `value`. If `token` returns no value, * non-reverting calls are assumed to be successful. Meant to be used with tokens that require the approval * to be set to zero before setting it to a non-zero value, such as USDT. */ function forceApprove(IERC20Upgradeable token, address spender, uint256 value) internal { bytes memory approvalCall = abi.encodeWithSelector(token.approve.selector, spender, value); if (!_callOptionalReturnBool(token, approvalCall)) { _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, 0)); _callOptionalReturn(token, approvalCall); } } /** * @dev Use a ERC-2612 signature to set the `owner` approval toward `spender` on `token`. * Revert on invalid signature. */ function safePermit( IERC20PermitUpgradeable token, address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s ) internal { uint256 nonceBefore = token.nonces(owner); token.permit(owner, spender, value, deadline, v, r, s); uint256 nonceAfter = token.nonces(owner); require(nonceAfter == nonceBefore + 1, "SafeERC20: permit did not succeed"); } /** * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement * on the return value: the return value is optional (but if data is returned, it must not be false). * @param token The token targeted by the call. * @param data The call data (encoded using abi.encode or one of its variants). */ function _callOptionalReturn(IERC20Upgradeable token, bytes memory data) private { // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since // we're implementing it ourselves. We use {Address-functionCall} to perform this call, which verifies that // the target address contains contract code and also asserts for success in the low-level call. bytes memory returndata = address(token).functionCall(data, "SafeERC20: low-level call failed"); require(returndata.length == 0 || abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed"); } /** * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement * on the return value: the return value is optional (but if data is returned, it must not be false). * @param token The token targeted by the call. * @param data The call data (encoded using abi.encode or one of its variants). * * This is a variant of {_callOptionalReturn} that silents catches all reverts and returns a bool instead. */ function _callOptionalReturnBool(IERC20Upgradeable token, bytes memory data) private returns (bool) { // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since // we're implementing it ourselves. We cannot use {Address-functionCall} here since this should return false // and not revert is the subcall reverts. (bool success, bytes memory returndata) = address(token).call(data); return success && (returndata.length == 0 || abi.decode(returndata, (bool))) && AddressUpgradeable.isContract(address(token)); } }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (utils/math/Math.sol) pragma solidity ^0.8.0; /** * @dev Standard math utilities missing in the Solidity language. */ library MathUpgradeable { enum Rounding { Down, // Toward negative infinity Up, // Toward infinity Zero // Toward zero } /** * @dev Returns the largest of two numbers. */ function max(uint256 a, uint256 b) internal pure returns (uint256) { return a > b ? a : b; } /** * @dev Returns the smallest of two numbers. */ function min(uint256 a, uint256 b) internal pure returns (uint256) { return a < b ? a : b; } /** * @dev Returns the average of two numbers. The result is rounded towards * zero. */ function average(uint256 a, uint256 b) internal pure returns (uint256) { // (a + b) / 2 can overflow. return (a & b) + (a ^ b) / 2; } /** * @dev Returns the ceiling of the division of two numbers. * * This differs from standard division with `/` in that it rounds up instead * of rounding down. */ function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) { // (a + b - 1) / b can overflow on addition, so we distribute. return a == 0 ? 0 : (a - 1) / b + 1; } /** * @notice Calculates floor(x * y / denominator) with full precision. Throws if result overflows a uint256 or denominator == 0 * @dev Original credit to Remco Bloemen under MIT license (https://xn--2-umb.com/21/muldiv) * with further edits by Uniswap Labs also under MIT license. */ function mulDiv(uint256 x, uint256 y, uint256 denominator) internal pure returns (uint256 result) { unchecked { // 512-bit multiply [prod1 prod0] = x * y. Compute the product mod 2^256 and mod 2^256 - 1, then use // 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(x, y, not(0)) prod0 := mul(x, y) prod1 := sub(sub(mm, prod0), lt(mm, prod0)) } // Handle non-overflow cases, 256 by 256 division. if (prod1 == 0) { // Solidity will revert if denominator == 0, unlike the div opcode on its own. // The surrounding unchecked block does not change this fact. // See https://docs.soliditylang.org/en/latest/control-structures.html#checked-or-unchecked-arithmetic. return prod0 / denominator; } // Make sure the result is less than 2^256. Also prevents denominator == 0. require(denominator > prod1, "Math: mulDiv overflow"); /////////////////////////////////////////////// // 512 by 256 division. /////////////////////////////////////////////// // Make division exact by subtracting the remainder from [prod1 prod0]. uint256 remainder; assembly { // Compute remainder using mulmod. remainder := mulmod(x, y, denominator) // Subtract 256 bit number from 512 bit number. prod1 := sub(prod1, gt(remainder, prod0)) prod0 := sub(prod0, remainder) } // Factor powers of two out of denominator and compute largest power of two divisor of denominator. Always >= 1. // See https://cs.stackexchange.com/q/138556/92363. // Does not overflow because the denominator cannot be zero at this stage in the function. uint256 twos = denominator & (~denominator + 1); assembly { // Divide denominator by twos. denominator := div(denominator, twos) // Divide [prod1 prod0] by twos. prod0 := div(prod0, twos) // Flip twos such that it is 2^256 / twos. If twos is zero, then it becomes one. twos := add(div(sub(0, twos), twos), 1) } // Shift in bits from prod1 into prod0. prod0 |= prod1 * twos; // Invert denominator mod 2^256. Now that denominator is an odd number, it has an inverse modulo 2^256 such // that denominator * inv = 1 mod 2^256. Compute the inverse by starting with a seed that is correct for // four bits. That is, denominator * inv = 1 mod 2^4. uint256 inverse = (3 * denominator) ^ 2; // Use the Newton-Raphson iteration to improve the precision. Thanks to Hensel's lifting lemma, this also works // in modular arithmetic, doubling the correct bits in each step. inverse *= 2 - denominator * inverse; // inverse mod 2^8 inverse *= 2 - denominator * inverse; // inverse mod 2^16 inverse *= 2 - denominator * inverse; // inverse mod 2^32 inverse *= 2 - denominator * inverse; // inverse mod 2^64 inverse *= 2 - denominator * inverse; // inverse mod 2^128 inverse *= 2 - denominator * inverse; // inverse mod 2^256 // Because the division is now exact we can divide by multiplying with the modular inverse of denominator. // This will give us the correct result modulo 2^256. Since the preconditions guarantee that the outcome is // less than 2^256, this is the final result. We don't need to compute the high bits of the result and prod1 // is no longer required. result = prod0 * inverse; return result; } } /** * @notice Calculates x * y / denominator with full precision, following the selected rounding direction. */ function mulDiv(uint256 x, uint256 y, uint256 denominator, Rounding rounding) internal pure returns (uint256) { uint256 result = mulDiv(x, y, denominator); if (rounding == Rounding.Up && mulmod(x, y, denominator) > 0) { result += 1; } return result; } /** * @dev Returns the square root of a number. If the number is not a perfect square, the value is rounded down. * * Inspired by Henry S. Warren, Jr.'s "Hacker's Delight" (Chapter 11). */ function sqrt(uint256 a) internal pure returns (uint256) { if (a == 0) { return 0; } // For our first guess, we get the biggest power of 2 which is smaller than the square root of the target. // // We know that the "msb" (most significant bit) of our target number `a` is a power of 2 such that we have // `msb(a) <= a < 2*msb(a)`. This value can be written `msb(a)=2**k` with `k=log2(a)`. // // This can be rewritten `2**log2(a) <= a < 2**(log2(a) + 1)` // → `sqrt(2**k) <= sqrt(a) < sqrt(2**(k+1))` // → `2**(k/2) <= sqrt(a) < 2**((k+1)/2) <= 2**(k/2 + 1)` // // Consequently, `2**(log2(a) / 2)` is a good first approximation of `sqrt(a)` with at least 1 correct bit. uint256 result = 1 << (log2(a) >> 1); // At this point `result` is an estimation with one bit of precision. We know the true value is a uint128, // since it is the square root of a uint256. Newton's method converges quadratically (precision doubles at // every iteration). We thus need at most 7 iteration to turn our partial result with one bit of precision // into the expected uint128 result. unchecked { result = (result + a / result) >> 1; result = (result + a / result) >> 1; result = (result + a / result) >> 1; result = (result + a / result) >> 1; result = (result + a / result) >> 1; result = (result + a / result) >> 1; result = (result + a / result) >> 1; return min(result, a / result); } } /** * @notice Calculates sqrt(a), following the selected rounding direction. */ function sqrt(uint256 a, Rounding rounding) internal pure returns (uint256) { unchecked { uint256 result = sqrt(a); return result + (rounding == Rounding.Up && result * result < a ? 1 : 0); } } /** * @dev Return the log in base 2, rounded down, of a positive value. * Returns 0 if given 0. */ function log2(uint256 value) internal pure returns (uint256) { uint256 result = 0; unchecked { if (value >> 128 > 0) { value >>= 128; result += 128; } if (value >> 64 > 0) { value >>= 64; result += 64; } if (value >> 32 > 0) { value >>= 32; result += 32; } if (value >> 16 > 0) { value >>= 16; result += 16; } if (value >> 8 > 0) { value >>= 8; result += 8; } if (value >> 4 > 0) { value >>= 4; result += 4; } if (value >> 2 > 0) { value >>= 2; result += 2; } if (value >> 1 > 0) { result += 1; } } return result; } /** * @dev Return the log in base 2, following the selected rounding direction, of a positive value. * Returns 0 if given 0. */ function log2(uint256 value, Rounding rounding) internal pure returns (uint256) { unchecked { uint256 result = log2(value); return result + (rounding == Rounding.Up && 1 << result < value ? 1 : 0); } } /** * @dev Return the log in base 10, rounded down, of a positive value. * Returns 0 if given 0. */ function log10(uint256 value) internal pure returns (uint256) { uint256 result = 0; unchecked { if (value >= 10 ** 64) { value /= 10 ** 64; result += 64; } if (value >= 10 ** 32) { value /= 10 ** 32; result += 32; } if (value >= 10 ** 16) { value /= 10 ** 16; result += 16; } if (value >= 10 ** 8) { value /= 10 ** 8; result += 8; } if (value >= 10 ** 4) { value /= 10 ** 4; result += 4; } if (value >= 10 ** 2) { value /= 10 ** 2; result += 2; } if (value >= 10 ** 1) { result += 1; } } return result; } /** * @dev Return the log in base 10, following the selected rounding direction, of a positive value. * Returns 0 if given 0. */ function log10(uint256 value, Rounding rounding) internal pure returns (uint256) { unchecked { uint256 result = log10(value); return result + (rounding == Rounding.Up && 10 ** result < value ? 1 : 0); } } /** * @dev Return the log in base 256, rounded down, of a positive value. * Returns 0 if given 0. * * Adding one to the result gives the number of pairs of hex symbols needed to represent `value` as a hex string. */ function log256(uint256 value) internal pure returns (uint256) { uint256 result = 0; unchecked { if (value >> 128 > 0) { value >>= 128; result += 16; } if (value >> 64 > 0) { value >>= 64; result += 8; } if (value >> 32 > 0) { value >>= 32; result += 4; } if (value >> 16 > 0) { value >>= 16; result += 2; } if (value >> 8 > 0) { result += 1; } } return result; } /** * @dev Return the log in base 256, following the selected rounding direction, of a positive value. * Returns 0 if given 0. */ function log256(uint256 value, Rounding rounding) internal pure returns (uint256) { unchecked { uint256 result = log256(value); return result + (rounding == Rounding.Up && 1 << (result << 3) < value ? 1 : 0); } } }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.8.0) (utils/math/SignedMath.sol) pragma solidity ^0.8.0; /** * @dev Standard signed math utilities missing in the Solidity language. */ library SignedMathUpgradeable { /** * @dev Returns the largest of two signed numbers. */ function max(int256 a, int256 b) internal pure returns (int256) { return a > b ? a : b; } /** * @dev Returns the smallest of two signed numbers. */ function min(int256 a, int256 b) internal pure returns (int256) { return a < b ? a : b; } /** * @dev Returns the average of two signed numbers without overflow. * The result is rounded towards zero. */ function average(int256 a, int256 b) internal pure returns (int256) { // Formula from the book "Hacker's Delight" int256 x = (a & b) + ((a ^ b) >> 1); return x + (int256(uint256(x) >> 255) & (a ^ b)); } /** * @dev Returns the absolute unsigned value of a signed value. */ function abs(int256 n) internal pure returns (uint256) { unchecked { // must be unchecked in order to support `n = type(int256).min` return uint256(n >= 0 ? n : -n); } } }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts v4.4.1 (utils/introspection/IERC165.sol) pragma solidity ^0.8.0; /** * @dev Interface of the ERC165 standard, as defined in the * https://eips.ethereum.org/EIPS/eip-165[EIP]. * * Implementers can declare support of contract interfaces, which can then be * queried by others ({ERC165Checker}). * * For an implementation, see {ERC165}. */ interface IERC165Upgradeable { /** * @dev Returns true if this contract implements the interface defined by * `interfaceId`. See the corresponding * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section] * to learn more about how these ids are created. * * This function call must use less than 30 000 gas. */ function supportsInterface(bytes4 interfaceId) external view returns (bool); }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (utils/Address.sol) pragma solidity ^0.8.1; /** * @dev Collection of functions related to the address type */ library AddressUpgradeable { /** * @dev Returns true if `account` is a contract. * * [IMPORTANT] * ==== * It is unsafe to assume that an address for which this function returns * false is an externally-owned account (EOA) and not a contract. * * Among others, `isContract` will return false for the following * types of addresses: * * - an externally-owned account * - a contract in construction * - an address where a contract will be created * - an address where a contract lived, but was destroyed * * Furthermore, `isContract` will also return true if the target contract within * the same transaction is already scheduled for destruction by `SELFDESTRUCT`, * which only has an effect at the end of a transaction. * ==== * * [IMPORTANT] * ==== * You shouldn't rely on `isContract` to protect against flash loan attacks! * * Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets * like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract * constructor. * ==== */ function isContract(address account) internal view returns (bool) { // This method relies on extcodesize/address.code.length, which returns 0 // for contracts in construction, since the code is only stored at the end // of the constructor execution. return account.code.length > 0; } /** * @dev Replacement for Solidity's `transfer`: sends `amount` wei to * `recipient`, forwarding all available gas and reverting on errors. * * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost * of certain opcodes, possibly making contracts go over the 2300 gas limit * imposed by `transfer`, making them unable to receive funds via * `transfer`. {sendValue} removes this limitation. * * https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/[Learn more]. * * IMPORTANT: because control is transferred to `recipient`, care must be * taken to not create reentrancy vulnerabilities. Consider using * {ReentrancyGuard} or the * https://solidity.readthedocs.io/en/v0.8.0/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern]. */ function sendValue(address payable recipient, uint256 amount) internal { require(address(this).balance >= amount, "Address: insufficient balance"); (bool success, ) = recipient.call{value: amount}(""); require(success, "Address: unable to send value, recipient may have reverted"); } /** * @dev Performs a Solidity function call using a low level `call`. A * plain `call` is an unsafe replacement for a function call: use this * function instead. * * If `target` reverts with a revert reason, it is bubbled up by this * function (like regular Solidity function calls). * * Returns the raw returned data. To convert to the expected return value, * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`]. * * Requirements: * * - `target` must be a contract. * - calling `target` with `data` must not revert. * * _Available since v3.1._ */ function functionCall(address target, bytes memory data) internal returns (bytes memory) { return functionCallWithValue(target, data, 0, "Address: low-level call failed"); } /** * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with * `errorMessage` as a fallback revert reason when `target` reverts. * * _Available since v3.1._ */ function functionCall( address target, bytes memory data, string memory errorMessage ) internal returns (bytes memory) { return functionCallWithValue(target, data, 0, errorMessage); } /** * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], * but also transferring `value` wei to `target`. * * Requirements: * * - the calling contract must have an ETH balance of at least `value`. * - the called Solidity function must be `payable`. * * _Available since v3.1._ */ function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) { return functionCallWithValue(target, data, value, "Address: low-level call with value failed"); } /** * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but * with `errorMessage` as a fallback revert reason when `target` reverts. * * _Available since v3.1._ */ function functionCallWithValue( address target, bytes memory data, uint256 value, string memory errorMessage ) internal returns (bytes memory) { require(address(this).balance >= value, "Address: insufficient balance for call"); (bool success, bytes memory returndata) = target.call{value: value}(data); return verifyCallResultFromTarget(target, success, returndata, errorMessage); } /** * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], * but performing a static call. * * _Available since v3.3._ */ function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) { return functionStaticCall(target, data, "Address: low-level static call failed"); } /** * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`], * but performing a static call. * * _Available since v3.3._ */ function functionStaticCall( address target, bytes memory data, string memory errorMessage ) internal view returns (bytes memory) { (bool success, bytes memory returndata) = target.staticcall(data); return verifyCallResultFromTarget(target, success, returndata, errorMessage); } /** * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], * but performing a delegate call. * * _Available since v3.4._ */ function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) { return functionDelegateCall(target, data, "Address: low-level delegate call failed"); } /** * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`], * but performing a delegate call. * * _Available since v3.4._ */ function functionDelegateCall( address target, bytes memory data, string memory errorMessage ) internal returns (bytes memory) { (bool success, bytes memory returndata) = target.delegatecall(data); return verifyCallResultFromTarget(target, success, returndata, errorMessage); } /** * @dev Tool to verify that a low level call to smart-contract was successful, and revert (either by bubbling * the revert reason or using the provided one) in case of unsuccessful call or if target was not a contract. * * _Available since v4.8._ */ function verifyCallResultFromTarget( address target, bool success, bytes memory returndata, string memory errorMessage ) internal view returns (bytes memory) { if (success) { if (returndata.length == 0) { // only check isContract if the call was successful and the return data is empty // otherwise we already know that it was a contract require(isContract(target), "Address: call to non-contract"); } return returndata; } else { _revert(returndata, errorMessage); } } /** * @dev Tool to verify that a low level call was successful, and revert if it wasn't, either by bubbling the * revert reason or using the provided one. * * _Available since v4.3._ */ function verifyCallResult( bool success, bytes memory returndata, string memory errorMessage ) internal pure returns (bytes memory) { if (success) { return returndata; } else { _revert(returndata, errorMessage); } } function _revert(bytes memory returndata, string memory errorMessage) private pure { // Look for revert reason and bubble it up if present if (returndata.length > 0) { // The easiest way to bubble the revert reason is using memory via assembly /// @solidity memory-safe-assembly assembly { let returndata_size := mload(returndata) revert(add(32, returndata), returndata_size) } } else { revert(errorMessage); } } }
// SPDX-License-Identifier: MIT pragma solidity 0.8.28; import { ExponentialMovingAverageV8 } from "../libs/math/ExponentialMovingAverageV8.sol"; library DTreasury { using ExponentialMovingAverageV8 for ExponentialMovingAverageV8.EMAStorage; struct GroupUpdateParams { uint256 baseTokenCaps; uint256 baseIn; } struct TreasuryState { ExponentialMovingAverageV8.EMAStorage emaLeverageRatio; uint256 totalBaseTokens; uint256 baseTokenCaps; uint256 lastSettlementTimestamp; uint256 baseTokenPrice; bool inited; uint256 strategyUnderlying; } }
// SPDX-License-Identifier: MIT pragma solidity 0.8.28; import { Currency, CurrencyLibrary } from "../types/Currency.sol"; import { GroupStateHelper, GroupSettings } from "../types/GroupStateHelper.sol"; import { GroupState, FeeModel } from "../types/CommonTypes.sol"; import { GroupId } from "../types/GroupId.sol"; import { DTreasury } from "../declarations/DTreasury.sol"; import { TreasuryStateLibrary } from "./TreasuryStateLibrary.sol"; import { IDXToken } from "../interfaces/IDXToken.sol"; import { IAToken } from "../interfaces/IAToken.sol"; import { IWToken } from "../interfaces/IWToken.sol"; import { IRebalancePool } from "../interfaces/IRebalancePool.sol"; import { CustomRevert } from "../libs/CustomRevert.sol"; import { FullMath } from "../libs/math/FullMath.sol"; import { IProtocolMinimum as IProtocol } from "../interfaces/IProtocolMinimum.sol"; /** * @title TreasuryHarvestLibrary * @notice This library orchestrates fee and yield harvesting for a specific group. * @dev It handles collecting fees, distributing base tokens, minting aTokens, and managing * yield fees. The library relies on external calls to tokens and Rebalance Pools, and * carefully handles potential revert scenarios via `try/catch` and bubble-up reverts. */ library TreasuryHarvestLibrary { using CurrencyLibrary for Currency; using GroupStateHelper for GroupSettings; using TreasuryStateLibrary for *; using CustomRevert for bytes4; // ========================= // ======== Errors ========= // ========================= error ZeroAmount(); error WrongFeeModel(GroupId groupId); error ErrorDistributingYield(); error ErrorFeeCollectionFailed(GroupId groupId); error ErrorDistributingTokens(GroupId groupId); // ========================= // ======== Events ========= // ========================= event FeesCollected(GroupId indexed groupId, uint256 amount); event FeesHarvested(GroupId indexed groupId, uint256 dxTokenBalance, address feeCollector); event YieldHarvested(GroupId indexed groupId, uint256 yieldAmount, address feeCollector); event ATokensMintedToPool(GroupId indexed groupId, uint256 aTokenAmount, address recipient); event TokensSwappedToPool(GroupId indexed groupId, address fromToken, address toToken, uint256 amountIn, uint256 amountOut); // ========================= // ======== Structs ======== // ========================= /** * @notice Parameters used to harvest yield or fees. * @param sendTokens If true, will swap base tokens into stablecoins * instead of attempting to mint aTokens (when mint capacity is exceeded). * @param swapRouter Address of the DEX/router used to swap base tokens for stablecoins. * @param stablecoin The address of the stablecoin into which base tokens may be swapped. * @param minAmountOut The minimum amount of stablecoins that must be received from the swap * for the transaction not to revert. */ struct HarvestParams { bool sendTokens; address swapRouter; address stablecoin; uint256 minAmountOut; } /** * @notice Collects any accrued fees from dxToken. * @dev Calls `collectFees` on the dxToken. Reverts if no fees were collected. * @param groupState The current group state, which includes the dxToken address. * @return The amount of dxToken fees collected. */ function collectFees(GroupState memory groupState, GroupId groupId) internal returns (uint256) { IDXToken dxToken = IDXToken(groupState.core.xToken.toAddress()); uint256 dxTokenBalanceBefore = dxToken.balanceOf(address(this)); // Attempt to collect fees from dxToken try dxToken.collectFees() { // Get the balance of dxToken after fees have been collected uint256 dxTokenBalanceAfter = dxToken.balanceOf(address(this)); uint256 collectedAmount = dxTokenBalanceAfter - dxTokenBalanceBefore; // Revert if no fees were actually collected if (collectedAmount == 0) ZeroAmount.selector.revertWith(); // Emit an event to log the fees collected emit FeesCollected(groupId, collectedAmount); return collectedAmount; } catch { CustomRevert.bubbleUpAndRevertWith(dxToken.collectFees.selector, address(dxToken)); } } /** * @notice Harvests fees (not yield) based on the group's fee model. * @dev For management fees, the dxTokens are simply transferred to the feeCollector. * For funding fees, dxTokens are burned and then aTokens are minted (or stablecoins are swapped). * @param groupId The ID of the group for which fees are harvested. * @param groupState The current group state data. * @param treasuryState The TreasuryState storage reference for updating internal state. * @param harvestParams Parameters controlling how the harvested tokens might be swapped or minted. * @param dxTokenBalance The amount of dxTokens available for fee harvesting. * @param feeCollector The address to which management fees should be sent. * @param protocol The Protocol contract address (for calculating stability ratio, etc). */ function harvestFees( GroupId groupId, GroupState memory groupState, DTreasury.TreasuryState storage treasuryState, HarvestParams memory harvestParams, uint256 dxTokenBalance, address feeCollector, address protocol ) internal { IDXToken dxToken = IDXToken(groupState.core.xToken.toAddress()); // Determine the fee model for this group FeeModel feeModel = dxToken.getFeeModel(); if (feeModel == FeeModel.MANAGEMENT_FEE) { // Simply transfer dxTokens to fee collector groupState.core.xToken.safeTransfer(feeCollector, dxTokenBalance); return; } if (feeModel == FeeModel.VARIABLE_FUNDING_FEE || feeModel == FeeModel.FIXED_FUNDING_FEE) { // For funding fees, burn dxTokens try dxToken.burn(address(this), dxTokenBalance) {} catch { CustomRevert.bubbleUpAndRevertWith(dxToken.burn.selector, address(dxToken)); } // Calculate baseTokenAmount from dxTokens uint256 baseTokenAmount = dxToken.dxTokenToBaseToken(dxTokenBalance); uint8 baseTokenDecimals = GroupSettings.wrap(groupState.groupSettings).getBaseTokenDecimals(); // apply yield and get net harvestable amount (baseTokenAmount, ) = _applyYield(groupState, baseTokenAmount, feeCollector); // Normalize baseTokenAmount to an 18-decimal scale (for internal accounting) uint256 baseTokenAmountNormalized = TreasuryStateLibrary.normalizeDecimals( baseTokenAmount, baseTokenDecimals ); // Distribute base tokens either as aToken or stablecoin, depending on mint capacity _distributeBaseTokens( groupState, groupId, treasuryState, baseTokenAmount, baseTokenAmountNormalized, harvestParams, protocol ); emit FeesHarvested(groupId, dxTokenBalance, feeCollector); return; } // If none of the recognized fee models applies, revert. WrongFeeModel.selector.revertWith(groupId); } /** * @notice Harvests yield for a group, applies any yield fees, and distributes the net amount. * @dev A portion of the yield is sent to the feeCollector as a yield fee (if applicable). * @param groupId The ID of the group for which yield is being harvested. * @param groupState The current group state data. * @param treasuryState The TreasuryState storage reference for updating internal state. * @param harvestParams Parameters controlling how the base tokens might be swapped or minted. * @param harvestableAmount The total base token amount of yield harvested. * @param feeCollector The address to which the yield fee is sent. * @param protocol The Protocol contract address (for calculating stability ratio, etc). */ function harvestYield( GroupId groupId, GroupState memory groupState, DTreasury.TreasuryState storage treasuryState, HarvestParams memory harvestParams, uint256 harvestableAmount, address feeCollector, address protocol ) internal { // Apply yield fees and get net harvestable amount (harvestableAmount, ) = _applyYield(groupState, harvestableAmount, feeCollector); // Normalize the base token amount for internal accounting uint8 baseTokenDecimals = GroupSettings.wrap(groupState.groupSettings).getBaseTokenDecimals(); uint256 baseTokenAmountNormalized = TreasuryStateLibrary.normalizeDecimals( harvestableAmount, baseTokenDecimals ); // Distribute the net base tokens (either as stablecoin or aTokens) _distributeBaseTokens( groupState, groupId, treasuryState, harvestableAmount, baseTokenAmountNormalized, harvestParams, protocol ); emit YieldHarvested(groupId, harvestableAmount, feeCollector); } /** * @notice Calculates how many aTokens should be minted given a certain amount of base tokens, * and also how many aTokens can be minted at maximum without breaching collateral targets. * @param groupState The current group state data. * @param groupId The ID of the group for which aTokens will be minted. * @param baseTokenAmountNormalized The normalized amount of base tokens to convert into aTokens. * @param aToken The IAToken contract reference (whose NAV is used to compute minted supply). * @param protocol The Protocol contract address (for stability ratio checks). * @return aTokenToMint How many aTokens should be minted given the current NAV. * @return aTokenMintableMax The maximum number of aTokens that can be minted without breaching collateral. */ function calculateMintingParams( DTreasury.TreasuryState storage treasuryState, GroupState memory groupState, GroupId groupId, uint256 baseTokenAmountNormalized, IAToken aToken, address protocol ) internal view returns (uint256 aTokenToMint, uint256 aTokenMintableMax) { // Fetch the current base token price from an oracle or similar feed (, uint256 newPrice) = TreasuryStateLibrary.fetchBaseTokenPrice(treasuryState, groupState); // NAV of the aToken is used to determine how many aTokens 1 base token is worth uint256 aTokenNav = aToken.nav(); // aTokenToMint = (baseTokenAmountNormalized * newPrice) / aTokenNav aTokenToMint = FullMath.mulDiv(baseTokenAmountNormalized, newPrice, aTokenNav); // Check how many aTokens we can mint before breaching collateral/stability constraints (, aTokenMintableMax) = TreasuryStateLibrary.maxMintableAToken( treasuryState, groupState, uint256(IProtocol(protocol).stabilityRatio(groupId)) ); } /** * @notice Distributes base tokens to the Rebalance Pool either by minting aTokens or swapping * them into stablecoins, depending on the mint capacity and user preference. * @dev If the aToken mint capacity is insufficient and `sendTokens == true`, base tokens * are swapped into stablecoins. Otherwise, the function reverts. * @param groupState The current group state data. * @param groupId The ID of the group for which distribution is happening. * @param treasuryState The TreasuryState storage reference for updating internal state. * @param baseTokenAmount The actual (denormalized) amount of base tokens. * @param baseTokenAmountNormalized The normalized base token amount (scaled to 18 decimals internally). * @param harvestParams Struct containing swap and stablecoin parameters. * @param protocol The Protocol contract address (for stability ratio checks). */ function _distributeBaseTokens( GroupState memory groupState, GroupId groupId, DTreasury.TreasuryState storage treasuryState, uint256 baseTokenAmount, uint256 baseTokenAmountNormalized, HarvestParams memory harvestParams, address protocol ) private { IAToken aToken = IAToken(groupState.core.aToken.toAddress()); Currency stableCoinCurrency = Currency.wrap(harvestParams.stablecoin); // Compute how many aTokens we *need* to mint and how many are *allowed* to mint (uint256 aTokenToMint, uint256 aTokenMintableMax) = calculateMintingParams( treasuryState, groupState, groupId, baseTokenAmountNormalized, aToken, protocol ); // If we cannot mint the entire required amount of aTokens if (aTokenMintableMax < aTokenToMint) { // If we are NOT allowed to send stablecoins, revert if (!harvestParams.sendTokens) ErrorDistributingTokens.selector.revertWith(groupId); // Decrease the treasury's totalBaseTokens because we'll unwrap and swap them treasuryState.totalBaseTokens -= baseTokenAmountNormalized; // 1. Unwrap the wrapped base tokens into their underlying (e.g., WETH -> ETH or WMATIC -> MATIC). uint256 unwrappedBaseTokens = IWToken(groupState.core.baseToken.toAddress()).unwrap(baseTokenAmount); // 2. Swap from base tokens -> stablecoins uint256 stablecoinAmount = TreasuryStateLibrary.swapTokens( address(this), harvestParams.swapRouter, // We should swap from the *yield-bearing token* (not the base token) groupState.core.yieldBearingToken.toAddress(), stableCoinCurrency.toAddress(), unwrappedBaseTokens, harvestParams.minAmountOut ); // 3. Transfer the stablecoins to the Rebalance Pool stableCoinCurrency.safeTransfer( groupState.extended.rebalancePool.toAddress(), stablecoinAmount ); emit TokensSwappedToPool(groupId, groupState.core.baseToken.toAddress(), harvestParams.stablecoin, unwrappedBaseTokens, stablecoinAmount); } else { // We can mint the full aToken amount from the base tokens // Increase the treasury's totalBaseTokens accordingly treasuryState.totalBaseTokens += baseTokenAmountNormalized; // Convert aTokenToMint from normalized (18 decimals) to the actual aToken decimals uint8 aTokenDecimals = GroupSettings.wrap(groupState.groupSettings).getATokenDecimals(); uint256 aTokenToMintDenorm = TreasuryStateLibrary.denormalizeDecimals(aTokenToMint, aTokenDecimals); // Mint aTokens directly to the Rebalance Pool aToken.mint(groupState.extended.rebalancePool.toAddress(), aTokenToMintDenorm); // Update the Rebalance Pool’s NAV to reflect the newly minted aTokens IRebalancePool(groupState.extended.rebalancePool.toAddress()).updateNAV(); emit ATokensMintedToPool(groupId, aTokenToMintDenorm, groupState.extended.rebalancePool.toAddress()); } } /** * @notice Applies a yield fee to the harvestable amount (if any) and transfers that fee * portion to the feeCollector. Returns the net amount after fee. * @param groupState The current group state data (for accessing baseToken). * @param harvestableAmount The total yield (in base tokens) to be distributed. * @param feeCollector The address to which yield fees should be sent. * @return _harvestableAmount The net (post-fee) amount of base tokens. * @return _feeAmount The fee amount that was deducted and sent to the feeCollector. */ function _applyYield( GroupState memory groupState, uint256 harvestableAmount, address feeCollector ) private returns (uint256 _harvestableAmount, uint256 _feeAmount) { IRebalancePool rebalancePool = IRebalancePool(groupState.extended.rebalancePool.toAddress()); uint256 yieldFeePercentage = rebalancePool.yieldFeePercentage(); uint256 BASE_POINTS = 10_000; // If there's a yield fee, calculate and transfer it if (yieldFeePercentage > 0) { _feeAmount = (harvestableAmount * yieldFeePercentage) / BASE_POINTS; _harvestableAmount = harvestableAmount - _feeAmount; if (_feeAmount > 0) { groupState.core.baseToken.safeTransfer(feeCollector, _feeAmount); } } else { // If no yield fee, the entire amount is harvestable _harvestableAmount = harvestableAmount; } return (_harvestableAmount, _feeAmount); } }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/extensions/ERC4626.sol) pragma solidity ^0.8.0; import "../ERC20Upgradeable.sol"; import "../utils/SafeERC20Upgradeable.sol"; import "../../../interfaces/IERC4626Upgradeable.sol"; import "../../../utils/math/MathUpgradeable.sol"; import {Initializable} from "../../../proxy/utils/Initializable.sol"; /** * @dev Implementation of the ERC4626 "Tokenized Vault Standard" as defined in * https://eips.ethereum.org/EIPS/eip-4626[EIP-4626]. * * This extension allows the minting and burning of "shares" (represented using the ERC20 inheritance) in exchange for * underlying "assets" through standardized {deposit}, {mint}, {redeem} and {burn} workflows. This contract extends * the ERC20 standard. Any additional extensions included along it would affect the "shares" token represented by this * contract and not the "assets" token which is an independent contract. * * [CAUTION] * ==== * In empty (or nearly empty) ERC-4626 vaults, deposits are at high risk of being stolen through frontrunning * with a "donation" to the vault that inflates the price of a share. This is variously known as a donation or inflation * attack and is essentially a problem of slippage. Vault deployers can protect against this attack by making an initial * deposit of a non-trivial amount of the asset, such that price manipulation becomes infeasible. Withdrawals may * similarly be affected by slippage. Users can protect against this attack as well as unexpected slippage in general by * verifying the amount received is as expected, using a wrapper that performs these checks such as * https://github.com/fei-protocol/ERC4626#erc4626router-and-base[ERC4626Router]. * * Since v4.9, this implementation uses virtual assets and shares to mitigate that risk. The `_decimalsOffset()` * corresponds to an offset in the decimal representation between the underlying asset's decimals and the vault * decimals. This offset also determines the rate of virtual shares to virtual assets in the vault, which itself * determines the initial exchange rate. While not fully preventing the attack, analysis shows that the default offset * (0) makes it non-profitable, as a result of the value being captured by the virtual shares (out of the attacker's * donation) matching the attacker's expected gains. With a larger offset, the attack becomes orders of magnitude more * expensive than it is profitable. More details about the underlying math can be found * xref:erc4626.adoc#inflation-attack[here]. * * The drawback of this approach is that the virtual shares do capture (a very small) part of the value being accrued * to the vault. Also, if the vault experiences losses, the users try to exit the vault, the virtual shares and assets * will cause the first user to exit to experience reduced losses in detriment to the last users that will experience * bigger losses. Developers willing to revert back to the pre-v4.9 behavior just need to override the * `_convertToShares` and `_convertToAssets` functions. * * To learn more, check out our xref:ROOT:erc4626.adoc[ERC-4626 guide]. * ==== * * _Available since v4.7._ */ abstract contract ERC4626Upgradeable is Initializable, ERC20Upgradeable, IERC4626Upgradeable { using MathUpgradeable for uint256; IERC20Upgradeable private _asset; uint8 private _underlyingDecimals; /** * @dev Set the underlying asset contract. This must be an ERC20-compatible contract (ERC20 or ERC777). */ function __ERC4626_init(IERC20Upgradeable asset_) internal onlyInitializing { __ERC4626_init_unchained(asset_); } function __ERC4626_init_unchained(IERC20Upgradeable asset_) internal onlyInitializing { (bool success, uint8 assetDecimals) = _tryGetAssetDecimals(asset_); _underlyingDecimals = success ? assetDecimals : 18; _asset = asset_; } /** * @dev Attempts to fetch the asset decimals. A return value of false indicates that the attempt failed in some way. */ function _tryGetAssetDecimals(IERC20Upgradeable asset_) private view returns (bool, uint8) { (bool success, bytes memory encodedDecimals) = address(asset_).staticcall( abi.encodeWithSelector(IERC20MetadataUpgradeable.decimals.selector) ); if (success && encodedDecimals.length >= 32) { uint256 returnedDecimals = abi.decode(encodedDecimals, (uint256)); if (returnedDecimals <= type(uint8).max) { return (true, uint8(returnedDecimals)); } } return (false, 0); } /** * @dev Decimals are computed by adding the decimal offset on top of the underlying asset's decimals. This * "original" value is cached during construction of the vault contract. If this read operation fails (e.g., the * asset has not been created yet), a default of 18 is used to represent the underlying asset's decimals. * * See {IERC20Metadata-decimals}. */ function decimals() public view virtual override(IERC20MetadataUpgradeable, ERC20Upgradeable) returns (uint8) { return _underlyingDecimals + _decimalsOffset(); } /** @dev See {IERC4626-asset}. */ function asset() public view virtual override returns (address) { return address(_asset); } /** @dev See {IERC4626-totalAssets}. */ function totalAssets() public view virtual override returns (uint256) { return _asset.balanceOf(address(this)); } /** @dev See {IERC4626-convertToShares}. */ function convertToShares(uint256 assets) public view virtual override returns (uint256) { return _convertToShares(assets, MathUpgradeable.Rounding.Down); } /** @dev See {IERC4626-convertToAssets}. */ function convertToAssets(uint256 shares) public view virtual override returns (uint256) { return _convertToAssets(shares, MathUpgradeable.Rounding.Down); } /** @dev See {IERC4626-maxDeposit}. */ function maxDeposit(address) public view virtual override returns (uint256) { return type(uint256).max; } /** @dev See {IERC4626-maxMint}. */ function maxMint(address) public view virtual override returns (uint256) { return type(uint256).max; } /** @dev See {IERC4626-maxWithdraw}. */ function maxWithdraw(address owner) public view virtual override returns (uint256) { return _convertToAssets(balanceOf(owner), MathUpgradeable.Rounding.Down); } /** @dev See {IERC4626-maxRedeem}. */ function maxRedeem(address owner) public view virtual override returns (uint256) { return balanceOf(owner); } /** @dev See {IERC4626-previewDeposit}. */ function previewDeposit(uint256 assets) public view virtual override returns (uint256) { return _convertToShares(assets, MathUpgradeable.Rounding.Down); } /** @dev See {IERC4626-previewMint}. */ function previewMint(uint256 shares) public view virtual override returns (uint256) { return _convertToAssets(shares, MathUpgradeable.Rounding.Up); } /** @dev See {IERC4626-previewWithdraw}. */ function previewWithdraw(uint256 assets) public view virtual override returns (uint256) { return _convertToShares(assets, MathUpgradeable.Rounding.Up); } /** @dev See {IERC4626-previewRedeem}. */ function previewRedeem(uint256 shares) public view virtual override returns (uint256) { return _convertToAssets(shares, MathUpgradeable.Rounding.Down); } /** @dev See {IERC4626-deposit}. */ function deposit(uint256 assets, address receiver) public virtual override returns (uint256) { require(assets <= maxDeposit(receiver), "ERC4626: deposit more than max"); uint256 shares = previewDeposit(assets); _deposit(_msgSender(), receiver, assets, shares); return shares; } /** @dev See {IERC4626-mint}. * * As opposed to {deposit}, minting is allowed even if the vault is in a state where the price of a share is zero. * In this case, the shares will be minted without requiring any assets to be deposited. */ function mint(uint256 shares, address receiver) public virtual override returns (uint256) { require(shares <= maxMint(receiver), "ERC4626: mint more than max"); uint256 assets = previewMint(shares); _deposit(_msgSender(), receiver, assets, shares); return assets; } /** @dev See {IERC4626-withdraw}. */ function withdraw(uint256 assets, address receiver, address owner) public virtual override returns (uint256) { require(assets <= maxWithdraw(owner), "ERC4626: withdraw more than max"); uint256 shares = previewWithdraw(assets); _withdraw(_msgSender(), receiver, owner, assets, shares); return shares; } /** @dev See {IERC4626-redeem}. */ function redeem(uint256 shares, address receiver, address owner) public virtual override returns (uint256) { require(shares <= maxRedeem(owner), "ERC4626: redeem more than max"); uint256 assets = previewRedeem(shares); _withdraw(_msgSender(), receiver, owner, assets, shares); return assets; } /** * @dev Internal conversion function (from assets to shares) with support for rounding direction. */ function _convertToShares(uint256 assets, MathUpgradeable.Rounding rounding) internal view virtual returns (uint256) { return assets.mulDiv(totalSupply() + 10 ** _decimalsOffset(), totalAssets() + 1, rounding); } /** * @dev Internal conversion function (from shares to assets) with support for rounding direction. */ function _convertToAssets(uint256 shares, MathUpgradeable.Rounding rounding) internal view virtual returns (uint256) { return shares.mulDiv(totalAssets() + 1, totalSupply() + 10 ** _decimalsOffset(), rounding); } /** * @dev Deposit/mint common workflow. */ function _deposit(address caller, address receiver, uint256 assets, uint256 shares) internal virtual { // If _asset is ERC777, `transferFrom` can trigger a reentrancy BEFORE the transfer happens through the // `tokensToSend` hook. On the other hand, the `tokenReceived` hook, that is triggered after the transfer, // calls the vault, which is assumed not malicious. // // Conclusion: we need to do the transfer before we mint so that any reentrancy would happen before the // assets are transferred and before the shares are minted, which is a valid state. // slither-disable-next-line reentrancy-no-eth SafeERC20Upgradeable.safeTransferFrom(_asset, caller, address(this), assets); _mint(receiver, shares); emit Deposit(caller, receiver, assets, shares); } /** * @dev Withdraw/redeem common workflow. */ function _withdraw( address caller, address receiver, address owner, uint256 assets, uint256 shares ) internal virtual { if (caller != owner) { _spendAllowance(owner, caller, shares); } // If _asset is ERC777, `transfer` can trigger a reentrancy AFTER the transfer happens through the // `tokensReceived` hook. On the other hand, the `tokensToSend` hook, that is triggered before the transfer, // calls the vault, which is assumed not malicious. // // Conclusion: we need to do the transfer after the burn so that any reentrancy would happen after the // shares are burned and after the assets are transferred, which is a valid state. _burn(owner, shares); SafeERC20Upgradeable.safeTransfer(_asset, receiver, assets); emit Withdraw(caller, receiver, owner, assets, shares); } function _decimalsOffset() internal view virtual returns (uint8) { return 0; } /** * @dev This empty reserved space is put in place to allow future versions to add new * variables without shifting down storage in the inheritance chain. * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps */ uint256[49] private __gap; }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/IERC20.sol) pragma solidity ^0.8.0; /** * @dev Interface of the ERC20 standard as defined in the EIP. */ interface IERC20Upgradeable { /** * @dev Emitted when `value` tokens are moved from one account (`from`) to * another (`to`). * * Note that `value` may be zero. */ event Transfer(address indexed from, address indexed to, uint256 value); /** * @dev Emitted when the allowance of a `spender` for an `owner` is set by * a call to {approve}. `value` is the new allowance. */ event Approval(address indexed owner, address indexed spender, uint256 value); /** * @dev Returns the amount of tokens in existence. */ function totalSupply() external view returns (uint256); /** * @dev Returns the amount of tokens owned by `account`. */ function balanceOf(address account) external view returns (uint256); /** * @dev Moves `amount` tokens from the caller's account to `to`. * * Returns a boolean value indicating whether the operation succeeded. * * Emits a {Transfer} event. */ function transfer(address to, uint256 amount) external returns (bool); /** * @dev Returns the remaining number of tokens that `spender` will be * allowed to spend on behalf of `owner` through {transferFrom}. This is * zero by default. * * This value changes when {approve} or {transferFrom} are called. */ function allowance(address owner, address spender) external view returns (uint256); /** * @dev Sets `amount` as the allowance of `spender` over the caller's tokens. * * Returns a boolean value indicating whether the operation succeeded. * * IMPORTANT: Beware that changing an allowance with this method brings the risk * that someone may use both the old and the new allowance by unfortunate * transaction ordering. One possible solution to mitigate this race * condition is to first reduce the spender's allowance to 0 and set the * desired value afterwards: * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 * * Emits an {Approval} event. */ function approve(address spender, uint256 amount) external returns (bool); /** * @dev Moves `amount` tokens from `from` to `to` using the * allowance mechanism. `amount` is then deducted from the caller's * allowance. * * Returns a boolean value indicating whether the operation succeeded. * * Emits a {Transfer} event. */ function transferFrom(address from, address to, uint256 amount) external returns (bool); }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.4) (token/ERC20/extensions/IERC20Permit.sol) pragma solidity ^0.8.0; /** * @dev Interface of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in * https://eips.ethereum.org/EIPS/eip-2612[EIP-2612]. * * Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by * presenting a message signed by the account. By not relying on {IERC20-approve}, the token holder account doesn't * need to send a transaction, and thus is not required to hold Ether at all. * * ==== Security Considerations * * There are two important considerations concerning the use of `permit`. The first is that a valid permit signature * expresses an allowance, and it should not be assumed to convey additional meaning. In particular, it should not be * considered as an intention to spend the allowance in any specific way. The second is that because permits have * built-in replay protection and can be submitted by anyone, they can be frontrun. A protocol that uses permits should * take this into consideration and allow a `permit` call to fail. Combining these two aspects, a pattern that may be * generally recommended is: * * ```solidity * function doThingWithPermit(..., uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) public { * try token.permit(msg.sender, address(this), value, deadline, v, r, s) {} catch {} * doThing(..., value); * } * * function doThing(..., uint256 value) public { * token.safeTransferFrom(msg.sender, address(this), value); * ... * } * ``` * * Observe that: 1) `msg.sender` is used as the owner, leaving no ambiguity as to the signer intent, and 2) the use of * `try/catch` allows the permit to fail and makes the code tolerant to frontrunning. (See also * {SafeERC20-safeTransferFrom}). * * Additionally, note that smart contract wallets (such as Argent or Safe) are not able to produce permit signatures, so * contracts should have entry points that don't rely on permit. */ interface IERC20PermitUpgradeable { /** * @dev Sets `value` as the allowance of `spender` over ``owner``'s tokens, * given ``owner``'s signed approval. * * IMPORTANT: The same issues {IERC20-approve} has related to transaction * ordering also apply here. * * Emits an {Approval} event. * * Requirements: * * - `spender` cannot be the zero address. * - `deadline` must be a timestamp in the future. * - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner` * over the EIP712-formatted function arguments. * - the signature must use ``owner``'s current nonce (see {nonces}). * * For more information on the signature format, see the * https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP * section]. * * CAUTION: See Security Considerations above. */ function permit( address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s ) external; /** * @dev Returns the current nonce for `owner`. This value must be * included whenever a signature is generated for {permit}. * * Every successful call to {permit} increases ``owner``'s nonce by one. This * prevents a signature from being used multiple times. */ function nonces(address owner) external view returns (uint256); /** * @dev Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}. */ // solhint-disable-next-line func-name-mixedcase function DOMAIN_SEPARATOR() external view returns (bytes32); }
// SPDX-License-Identifier: MIT pragma solidity 0.8.28; import { LogExpMathV8 } from "./LogExpMathV8.sol"; // solhint-disable not-rely-on-time /// @dev See https://en.wikipedia.org/wiki/Exponential_smoothing /// It is the same as `ExponentialMovingAverageV7` with `unchecked` scope. library ExponentialMovingAverageV8 { /************* * Constants * *************/ /// @dev The precision used to compute EMA. uint256 private constant PRECISION = 1e18; /*********** * Structs * ***********/ /// @dev Compiler will pack this into single `uint256`. /// @param lastTime The last timestamp when the storage is updated. /// @param sampleInterval The sampling time interval used in the EMA. /// @param lastValue The last value in the data sequence, with precision 1e18. /// @param lastEmaValue The last EMA value computed, with precision 1e18. struct EMAStorage { uint40 lastTime; uint24 sampleInterval; uint96 lastValue; uint96 lastEmaValue; } /// @dev Save value of EMA storage. /// @param s The EMA storage. /// @param value The new value, with precision 1e18. function saveValue(EMAStorage storage s, uint96 value) internal { s.lastEmaValue = uint96(emaValue(s)); s.lastValue = value; s.lastTime = uint40(block.timestamp); } /// @dev Return the current ema value. /// @param s The EMA storage. function emaValue(EMAStorage storage s) internal view returns (uint256) { unchecked { if (uint256(s.lastTime) < block.timestamp) { uint256 dt = block.timestamp - uint256(s.lastTime); uint256 e = (dt * PRECISION) / s.sampleInterval; if (e > 41e18) { return s.lastValue; } else { uint256 alpha = uint256(LogExpMathV8.exp(-int256(e))); return (s.lastValue * (PRECISION - alpha) + s.lastEmaValue * alpha) / PRECISION; } } else { return s.lastEmaValue; } } } }
// SPDX-License-Identifier: MIT pragma solidity 0.8.28; import { Currency, CurrencyLibrary } from "../types/Currency.sol"; import { Address, AddressLibrary } from "../types/Address.sol"; import { FxStableMath } from "../libs/math/FxStableMath.sol"; import { ExponentialMovingAverageV8 } from "../libs/math/ExponentialMovingAverageV8.sol"; import { IERC20Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol"; import { IPriceOracle } from "../interfaces/IPriceOracle.sol"; import { DTreasury } from "../declarations/DTreasury.sol"; import { GroupStateHelper, GroupSettings } from "../types/GroupStateHelper.sol"; import { IAToken } from "../interfaces/IAToken.sol"; import { CustomRevert } from "../libs/CustomRevert.sol"; import { IDEXRouter } from "../interfaces/IDEXRouter.sol"; import { IStrategy } from "../interfaces/IStrategy.sol"; import { GroupState } from "../types/CommonTypes.sol"; import { FullMath } from "../libs/math/FullMath.sol"; /** * @title TreasuryStateLibrary * @notice Library for managing treasury state and calculations. */ library TreasuryStateLibrary { using CurrencyLibrary for Currency; using AddressLibrary for Address; using FxStableMath for FxStableMath.SwapState; using ExponentialMovingAverageV8 for ExponentialMovingAverageV8.EMAStorage; using GroupStateHelper for GroupSettings; using CustomRevert for bytes4; error ErrorInvalidTwapPrice(); error ErrorSwapFailed(); error ErrorWithdrawFromStrategy(); error StrategyUnderflow(); event BaseTokenCapsUpdated(uint256 newCaps); event BaseTokenPriceUpdated(uint256 newPrice); event SwapExecuted(address indexed fromToken, address indexed toToken, uint256 amountIn, uint256 amountOut); event BaseTokenTransferred(address indexed recipient, uint256 amount); event StrategyWithdrawal(address indexed strategy, uint256 amount); event EMALeverageRatioUpdated(uint256 newEMAValue); event GroupEMALeverageRatioInitialized(uint256 initialValue); uint256 private constant PRECISION = 1e18; /** * @notice Updates the base token caps in the treasury state. * @param self The treasury state storage. * @param newCaps The new base token caps. */ function updateBaseTokenCaps(DTreasury.TreasuryState storage self, uint256 newCaps) internal { self.baseTokenCaps = newCaps; emit BaseTokenCapsUpdated(newCaps); } /** * @notice Updates the base token price in the treasury state. * @param self The treasury state storage. * @param newPrice The new base token price. */ function updateBaseTokenPrice(DTreasury.TreasuryState storage self, uint256 newPrice) internal { self.baseTokenPrice = newPrice; emit BaseTokenPriceUpdated(newPrice); } /** * @notice Loads the swap state with proper precision handling. * @param self The treasury state storage. * @param group The group state. * @return _state The loaded swap state. */ function loadSwapState( DTreasury.TreasuryState storage self, GroupState memory group ) internal view returns (FxStableMath.SwapState memory _state) { // Fetch decimals uint8 baseTokenDecimals = GroupSettings.wrap(group.groupSettings).getBaseTokenDecimals(); uint8 aTokenDecimals = GroupSettings.wrap(group.groupSettings).getATokenDecimals(); uint8 xTokenDecimals = GroupSettings.wrap(group.groupSettings).getXTokenDecimals(); // Normalize baseSupply to 18 decimals _state.baseSupply = normalizeDecimals(self.totalBaseTokens, baseTokenDecimals); // Fetch base token price (already in 18 decimals) (_state.baseTwapNav, _state.baseNav) = fetchBaseTokenPrice(self, group); if (_state.baseSupply == 0) { _state.aNav = PRECISION; _state.xNav = PRECISION; } else { // Fetch token supplies and normalize to 18 decimals uint256 aSupplyRaw = IERC20Upgradeable(group.core.aToken.toAddress()).totalSupply(); uint256 xSupplyRaw = IERC20Upgradeable(group.core.xToken.toAddress()).totalSupply(); _state.aSupply = normalizeDecimals(aSupplyRaw, aTokenDecimals); _state.xSupply = normalizeDecimals(xSupplyRaw, xTokenDecimals); // Fetch aNav (assuming it's already in 18 decimals) _state.beta = IAToken(group.core.aToken.toAddress()).beta(); if (_state.beta) { _state.aNav = IAToken(group.core.aToken.toAddress()).nav(); } else { _state.aNav = PRECISION; } if (_state.xSupply == 0) { // No xToken, treat the nav of xToken as 1.0 _state.xNav = PRECISION; } else { uint256 _baseVal = _state.baseSupply * _state.baseNav; uint256 _aVal = _state.aSupply * _state.aNav; if (_baseVal >= _aVal) { _state.xNav = (_baseVal - _aVal) / _state.xSupply; } else { // Under-collateralized _state.xNav = 0; } } } } /** * @notice Fetches the base token price from the price oracle. * @param group The group state. * @return _twap The time-weighted average price. * @return _price The selected price */ function fetchBaseTokenPrice( DTreasury.TreasuryState storage /*self*/, GroupState memory group ) internal view returns (uint256 _twap, uint256 _price) { bool isValid; address priceOracle = group.extended.priceOracle.toAddress(); (isValid, _price) = IPriceOracle(priceOracle).getPrice(group.core.baseToken.toAddress()); if (!isValid || _price == 0) ErrorInvalidTwapPrice.selector.revertWith(); // Prices are already in 18 decimals return (_price, _price); } /** * @notice Checks if the treasury is under-collateralized. * @param self The treasury state storage. * @param group The group state. * @return True if under-collateralized, false otherwise. */ function isUnderCollateral(DTreasury.TreasuryState storage self, GroupState memory group) internal view returns (bool) { FxStableMath.SwapState memory _state = loadSwapState(self, group); return _state.xNav == 0; } /** * @notice Gets the collateral ratio of the treasury. * @param self The treasury state storage. * @param group The group state. * @return The collateral ratio. */ function getCollateralRatio(DTreasury.TreasuryState storage self, GroupState memory group) internal view returns (uint256) { FxStableMath.SwapState memory _state = loadSwapState(self, group); if (_state.baseSupply == 0) return PRECISION; if (_state.aSupply == 0 || _state.aNav == 0) return PRECISION * PRECISION; return FullMath.mulDiv(_state.baseSupply * _state.baseNav, PRECISION, _state.aSupply * _state.aNav); } /** * @notice Calculates the maximum mintable AToken based on the new collateral ratio. * @param self The treasury state storage. * @param group The group state. * @param _newCollateralRatio The new desired collateral ratio. * @return _maxBaseIn The maximum base tokens that can be input. * @return _maxATokenMintable The maximum AToken that can be minted. */ function maxMintableAToken( DTreasury.TreasuryState storage self, GroupState memory group, uint256 _newCollateralRatio ) internal view returns (uint256 _maxBaseIn, uint256 _maxATokenMintable) { FxStableMath.SwapState memory _state = loadSwapState(self, group); (_maxBaseIn, _maxATokenMintable) = _state.maxMintableAToken(_newCollateralRatio); } /** * @notice Calculates the maximum redeemable AToken based on the new collateral ratio. * @param self The treasury state storage. * @param group The group state. * @param _newCollateralRatio The new desired collateral ratio. * @return _maxBaseOut The maximum base tokens that can be output. * @return _maxATokenRedeemable The maximum AToken that can be redeemed. */ function maxRedeemableAToken( DTreasury.TreasuryState storage self, GroupState memory group, uint256 _newCollateralRatio ) internal view returns (uint256 _maxBaseOut, uint256 _maxATokenRedeemable) { FxStableMath.SwapState memory _state = loadSwapState(self, group); (_maxBaseOut, _maxATokenRedeemable) = _state.maxRedeemableAToken(_newCollateralRatio); } /** * @notice Updates the Exponential Moving Average (EMA) of the leverage ratio. * @param self The treasury state storage. * @param _state The current swap state. */ function updateEMALeverageRatio(DTreasury.TreasuryState storage self, FxStableMath.SwapState memory _state) internal { uint256 _ratio = _state.leverageRatio(); self.emaLeverageRatio.saveValue(uint96(_ratio)); emit EMALeverageRatioUpdated(_ratio); } function getEMAValue(DTreasury.TreasuryState storage self) internal view returns (uint256) { return self.emaLeverageRatio.emaValue(); } function initializeGroupEMALeverageRatio(DTreasury.TreasuryState storage self) internal { self.emaLeverageRatio.lastTime = uint40(block.timestamp); self.emaLeverageRatio.lastValue = uint96(PRECISION * 2); self.emaLeverageRatio.lastEmaValue = uint96(PRECISION * 2); self.emaLeverageRatio.sampleInterval = 1 days; emit GroupEMALeverageRatioInitialized(PRECISION * 2); } /** * @dev Normalizes a token amount to 18 decimals. * @param amount The amount to normalize. * @param tokenDecimals The decimals of the token. * @return The normalized amount. */ function normalizeDecimals(uint256 amount, uint8 tokenDecimals) internal pure returns (uint256) { if (tokenDecimals == 18) { return amount; } uint256 factor; if (tokenDecimals < 18) { factor = 10 ** (18 - tokenDecimals); return amount * factor; } else { factor = 10 ** (tokenDecimals - 18); return amount / factor; } } /** * @dev Denormalizes a token amount from 18 decimals to the token's decimals. * @param amount The amount to denormalize. * @param tokenDecimals The decimals of the token. * @return The denormalized amount. */ function denormalizeDecimals(uint256 amount, uint8 tokenDecimals) internal pure returns (uint256) { if (tokenDecimals == 18) { return amount; } uint256 factor; if (tokenDecimals < 18) { factor = 10 ** (18 - tokenDecimals); return amount / factor; } else { factor = 10 ** (tokenDecimals - 18); return amount * factor; } } /** * @notice Transfers base tokens to the recipient, withdrawing from strategy if necessary. * @param baseToken The base token. * @param strategyAddress The strategy address. * @param treasuryState The treasury state. * @param amount The amount of base tokens to transfer. * @param recipient The recipient address. */ function transferBaseToken( address self, uint8 baseTokenDecimals, Currency baseToken, address strategyAddress, DTreasury.TreasuryState storage treasuryState, uint256 amount, address recipient ) internal { uint256 amountNormalized = normalizeDecimals(amount, baseTokenDecimals); uint256 balance = baseToken.balanceOf(self); uint256 balanceNormalized = normalizeDecimals(balance, baseTokenDecimals); if (balanceNormalized < amountNormalized) { uint256 diff = amountNormalized - balanceNormalized; if (diff == 0) return; IStrategy strategy = IStrategy(strategyAddress); bool success = strategy.withdrawToTreasury(diff); if (!success) ErrorWithdrawFromStrategy.selector.revertWith(); if (treasuryState.strategyUnderlying < diff) StrategyUnderflow.selector.revertWith(); treasuryState.strategyUnderlying -= diff; balance = baseToken.balanceOf(self); balanceNormalized = normalizeDecimals(balance, baseTokenDecimals); if (amountNormalized > balanceNormalized) { amountNormalized = balanceNormalized; amount = denormalizeDecimals(amountNormalized, baseTokenDecimals); } } treasuryState.totalBaseTokens -= amountNormalized; baseToken.safeTransfer(recipient, amount); emit BaseTokenTransferred(recipient, amount); } /** * @notice Swaps tokens using the specified router. * @param self The address of the contract using this library * @param swapRouter The address of the swap router. * @param from The address of the token to swap from. * @param to The address of the token to swap to. * @param amount The amount of tokens to swap. * @param minOutAmount The minimum acceptable amount of tokens to receive. * @return swappedAmount The amount of tokens received after the swap. */ function swapTokens( address self, address swapRouter, address from, address to, uint256 amount, uint256 minOutAmount ) internal returns (uint256 swappedAmount) { IDEXRouter router = IDEXRouter(swapRouter); uint256 deadline = block.timestamp + 30; address[] memory path = new address[](2); path[0] = from; path[1] = to; Currency fromToken = Currency.wrap(from); uint256 currentAllowance = fromToken.allowance(self, swapRouter); if (currentAllowance < amount) { fromToken.safeIncreaseAllowance(swapRouter, amount - currentAllowance); } swappedAmount = _executeSwap(router, amount, minOutAmount, path, self, deadline); if (swappedAmount < minOutAmount) { ErrorSwapFailed.selector.revertWith(); } emit SwapExecuted(from, to, amount, swappedAmount); return swappedAmount; } function _executeSwap( IDEXRouter router, uint256 amount, uint256 minOutAmount, address[] memory path, address to, uint256 deadline ) private returns (uint256) { try router.swapExactTokensForTokens(amount, minOutAmount, path, to, deadline) returns (uint256[] memory amounts) { return amounts[amounts.length - 1]; } catch { ErrorSwapFailed.selector.revertWith(); } } }
// SPDX-License-Identifier: MIT pragma solidity 0.8.28; import { IERC20Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol"; import { FeeModel } from "../types/CommonTypes.sol"; interface IDXToken is IERC20Upgradeable { /********** * Events * **********/ /// @notice Emitted when the cooling-off period is updated. /// @param oldValue The value of the previous cooling-off period. /// @param newValue The value of the current cooling-off period. event UpdateCoolingOffPeriod(uint256 indexed oldValue, uint256 indexed newValue); /// @notice Emitted when the management fee rate is updated. /// @param oldRate The previous management fee rate. /// @param newRate The new management fee rate. event UpdateManagementFeeRate(uint256 indexed oldRate, uint256 indexed newRate); /// @notice Emitted when the funding fee rate is updated. /// @param oldRate The previous funding fee rate. /// @param newRate The new funding fee rate. event UpdateFundingFeeRate(uint256 indexed oldRate, uint256 indexed newRate); /// @notice Emitted when the fixed yield amount is updated. /// @param oldRate The previous fixed yield amount. /// @param newRate The new fixed yield amount. event UpdateVariableFundingFeeRate(uint256 indexed oldRate, uint256 indexed newRate); /// @notice Emitted when fees are collected. /// @param amount The amount of fees collected. event FeesCollected(uint256 indexed amount); /// @notice Emitted when tokens are minted. /// @param to The address receiving the minted tokens. /// @param amount The amount of tokens minted. event Minted(address indexed to, uint256 indexed amount); /// @notice Emitted when tokens are burned. /// @param from The address whose tokens are burned. /// @param amount The amount of tokens burned. event Burned(address indexed from, uint256 indexed amount); /// @notice Emitted when the cooling-off period is triggered. /// @param account The account triggering the cooling-off period. /// @param timestamp The timestamp when triggered. event CoolingOffPeriodTriggered(address indexed account, uint256 indexed timestamp); /// @notice Emitted when the associated group is updated. /// @param groupId The new associated group ID. event UpdateGroup(bytes32 indexed groupId); /// @notice Emitted when the treasury address is updated. /// @param oldTreasury The old treasury address. /// @param newTreasury The new treasury address. event UpdateTreasuryAddress(address indexed oldTreasury, address indexed newTreasury); /// @notice Emitted when the max supply is updated. /// @param oldMaxSupply The old max supply. /// @param newMaxSupply The new max supply. event UpdateMaxSupply(uint256 indexed oldMaxSupply, uint256 indexed newMaxSupply); /********** * Errors * **********/ /// @dev Thrown when an address is zero. error ZeroAddress(); /// @dev Thrown when decimals are invalid. error InvalidDecimals(); /// @dev Thrown when the cooling-off period is too large. error CoolingOffPeriodTooLarge(); /// @dev Thrown when trying to mint or burn an amount below the minimum. error ErrorMinimumMintingAmount(); /// @dev Thrown when trying to burn an amount below the minimum. error ErrorMinimumBurningAmount(); /// @dev Thrown when minting exceeds the max supply. error ExceedsMaxSupply(); /// @dev Thrown when cooling-off period is active. error CoolingOffPeriodActive(); /// @dev Thrown when parameter is unchanged. error ParameterUnchanged(); /// @dev Thrown when management fee is invalid. error InvalidManagementFee(); /// @dev Thrown when funding fee rate is invalid. error InvalidFundingFeeRate(); /// @dev Thrown when fixed yield amount is invalid. error InvalidFixedYieldAmount(); /// @dev Thrown when fee model is invalid. error InvalidFeeModel(); /// @dev Thrown when base supply is zero. error BaseSupplyZero(); /// @dev Thrown when transfer amount exceeds balance. error TransferAmountExceedsBalance(); /// @dev Thrown when burn amount exceeds balance. error BurnAmountExceedsBalance(); /// @dev Thrown cannot recover token. error CannotRecoverToken(); /// @dev Thrown when invalid base token price. error InvalidBaseTokenPrice(); /// @dev Thrown when an operation is not permitted. error NotPermitted(); /************************* * Public View Functions * *************************/ /// @notice Returns the NAV of the token. function nav() external view returns (uint256); /// @notice Converts DXToken amount to base token amount. function dxTokenToBaseToken(uint256 dxTokenAmount) external view returns (uint256 baseTokenAmount); /// @notice Returns the fee model. function getFeeModel() external view returns (FeeModel); /**************************** * Public Mutated Functions * ****************************/ /// @notice Mints tokens to a specified address. function mint(address _to, uint256 _amount) external; /// @notice Burns tokens from a specified address. function burn(address _from, uint256 _amount) external; /// @notice Collects accumulated fees. function collectFees() external; }
// SPDX-License-Identifier: MIT pragma solidity 0.8.28; import { IERC20Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol"; import { GroupId } from "../types/GroupId.sol"; /** * @title IAToken * @notice Interface for AToken, an ERC20-compatible token with minting, burning, and * additional functionality for treasury and rebalance management. */ interface IAToken is IERC20Upgradeable { /** * @dev Emitted when the treasury address is updated. * @param oldTreasury The previous treasury address. * @param newTreasury The new treasury address. */ event UpdateTreasuryAddress(address indexed oldTreasury, address indexed newTreasury); /** * @dev Emitted when the rebalance pool address is updated. * @param oldRebalancePool The previous rebalance pool address. * @param newRebalancePool The new rebalance pool address. */ event UpdateRebalancePool(address indexed oldRebalancePool, address indexed newRebalancePool); /** * @dev Emitted when tokens are minted to an address. * @param to The address receiving the minted tokens. * @param amount The amount of tokens minted. */ event Minted(address indexed to, uint256 indexed amount); /** * @dev Emitted when tokens are burned from an address. * @param from The address from which tokens are burned. * @param amount The amount of tokens burned. */ event Burned(address indexed from, uint256 indexed amount); /** * @dev Emitted when a group is updated. * @param groupId The identifier of the updated group. */ event UpdateGroup(GroupId indexed groupId); /** * @dev Emitted when the max supply is updated. * @param oldMaxSupply The previous max supply. * @param newMaxSupply The new max supply. */ event UpdateMaxSupply(uint256 indexed oldMaxSupply, uint256 indexed newMaxSupply); // Custom Errors error InvalidDecimals(); error ErrorNotPermitted(); error ErrorZeroAddress(); error ErrorZeroAmount(); error ErrorExceedsMaxSupply(); error ErrorCannotRecoverToken(); error ErrorParameterUnchanged(); /** * @notice Returns the beta status of the token. */ function beta() external view returns (bool); /** * @notice Returns the Net Asset Value (NAV) of the token. * @return The current NAV as an unsigned integer. */ function nav() external view returns (uint256); /** * @notice Returns the number of decimals used by the token. * @return The number of decimals. */ function decimals() external view returns (uint8); /** * @notice Mints tokens to a specified address. * @dev Emits a {Minted} event. * @param _to The address to receive the minted tokens. * @param _amount The amount of tokens to mint. */ function mint(address _to, uint256 _amount) external; /** * @notice Burns tokens from a specified address. * @dev Emits a {Burned} event. * @param _from The address from which tokens are burned. * @param _amount The amount of tokens to burn. */ function burn(address _from, uint256 _amount) external; }
// SPDX-License-Identifier: MIT pragma solidity 0.8.28; import { GroupId } from "../types/GroupId.sol"; interface IProtocolMinimum { function stabilityRatio(GroupId groupId) external view returns (uint96); }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/ERC20.sol) pragma solidity ^0.8.0; import "./IERC20Upgradeable.sol"; import "./extensions/IERC20MetadataUpgradeable.sol"; import "../../utils/ContextUpgradeable.sol"; import {Initializable} from "../../proxy/utils/Initializable.sol"; /** * @dev Implementation of the {IERC20} interface. * * This implementation is agnostic to the way tokens are created. This means * that a supply mechanism has to be added in a derived contract using {_mint}. * For a generic mechanism see {ERC20PresetMinterPauser}. * * TIP: For a detailed writeup see our guide * https://forum.openzeppelin.com/t/how-to-implement-erc20-supply-mechanisms/226[How * to implement supply mechanisms]. * * The default value of {decimals} is 18. To change this, you should override * this function so it returns a different value. * * We have followed general OpenZeppelin Contracts guidelines: functions revert * instead returning `false` on failure. This behavior is nonetheless * conventional and does not conflict with the expectations of ERC20 * applications. * * Additionally, an {Approval} event is emitted on calls to {transferFrom}. * This allows applications to reconstruct the allowance for all accounts just * by listening to said events. Other implementations of the EIP may not emit * these events, as it isn't required by the specification. * * Finally, the non-standard {decreaseAllowance} and {increaseAllowance} * functions have been added to mitigate the well-known issues around setting * allowances. See {IERC20-approve}. */ contract ERC20Upgradeable is Initializable, ContextUpgradeable, IERC20Upgradeable, IERC20MetadataUpgradeable { mapping(address => uint256) private _balances; mapping(address => mapping(address => uint256)) private _allowances; uint256 private _totalSupply; string private _name; string private _symbol; /** * @dev Sets the values for {name} and {symbol}. * * All two of these values are immutable: they can only be set once during * construction. */ function __ERC20_init(string memory name_, string memory symbol_) internal onlyInitializing { __ERC20_init_unchained(name_, symbol_); } function __ERC20_init_unchained(string memory name_, string memory symbol_) internal onlyInitializing { _name = name_; _symbol = symbol_; } /** * @dev Returns the name of the token. */ function name() public view virtual override returns (string memory) { return _name; } /** * @dev Returns the symbol of the token, usually a shorter version of the * name. */ function symbol() public view virtual override returns (string memory) { return _symbol; } /** * @dev Returns the number of decimals used to get its user representation. * For example, if `decimals` equals `2`, a balance of `505` tokens should * be displayed to a user as `5.05` (`505 / 10 ** 2`). * * Tokens usually opt for a value of 18, imitating the relationship between * Ether and Wei. This is the default value returned by this function, unless * it's overridden. * * NOTE: This information is only used for _display_ purposes: it in * no way affects any of the arithmetic of the contract, including * {IERC20-balanceOf} and {IERC20-transfer}. */ function decimals() public view virtual override returns (uint8) { return 18; } /** * @dev See {IERC20-totalSupply}. */ function totalSupply() public view virtual override returns (uint256) { return _totalSupply; } /** * @dev See {IERC20-balanceOf}. */ function balanceOf(address account) public view virtual override returns (uint256) { return _balances[account]; } /** * @dev See {IERC20-transfer}. * * Requirements: * * - `to` cannot be the zero address. * - the caller must have a balance of at least `amount`. */ function transfer(address to, uint256 amount) public virtual override returns (bool) { address owner = _msgSender(); _transfer(owner, to, amount); return true; } /** * @dev See {IERC20-allowance}. */ function allowance(address owner, address spender) public view virtual override returns (uint256) { return _allowances[owner][spender]; } /** * @dev See {IERC20-approve}. * * NOTE: If `amount` is the maximum `uint256`, the allowance is not updated on * `transferFrom`. This is semantically equivalent to an infinite approval. * * Requirements: * * - `spender` cannot be the zero address. */ function approve(address spender, uint256 amount) public virtual override returns (bool) { address owner = _msgSender(); _approve(owner, spender, amount); return true; } /** * @dev See {IERC20-transferFrom}. * * Emits an {Approval} event indicating the updated allowance. This is not * required by the EIP. See the note at the beginning of {ERC20}. * * NOTE: Does not update the allowance if the current allowance * is the maximum `uint256`. * * Requirements: * * - `from` and `to` cannot be the zero address. * - `from` must have a balance of at least `amount`. * - the caller must have allowance for ``from``'s tokens of at least * `amount`. */ function transferFrom(address from, address to, uint256 amount) public virtual override returns (bool) { address spender = _msgSender(); _spendAllowance(from, spender, amount); _transfer(from, to, amount); return true; } /** * @dev Atomically increases the allowance granted to `spender` by the caller. * * This is an alternative to {approve} that can be used as a mitigation for * problems described in {IERC20-approve}. * * Emits an {Approval} event indicating the updated allowance. * * Requirements: * * - `spender` cannot be the zero address. */ function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) { address owner = _msgSender(); _approve(owner, spender, allowance(owner, spender) + addedValue); return true; } /** * @dev Atomically decreases the allowance granted to `spender` by the caller. * * This is an alternative to {approve} that can be used as a mitigation for * problems described in {IERC20-approve}. * * Emits an {Approval} event indicating the updated allowance. * * Requirements: * * - `spender` cannot be the zero address. * - `spender` must have allowance for the caller of at least * `subtractedValue`. */ function decreaseAllowance(address spender, uint256 subtractedValue) public virtual returns (bool) { address owner = _msgSender(); uint256 currentAllowance = allowance(owner, spender); require(currentAllowance >= subtractedValue, "ERC20: decreased allowance below zero"); unchecked { _approve(owner, spender, currentAllowance - subtractedValue); } return true; } /** * @dev Moves `amount` of tokens from `from` to `to`. * * This internal function is equivalent to {transfer}, and can be used to * e.g. implement automatic token fees, slashing mechanisms, etc. * * Emits a {Transfer} event. * * Requirements: * * - `from` cannot be the zero address. * - `to` cannot be the zero address. * - `from` must have a balance of at least `amount`. */ function _transfer(address from, address to, uint256 amount) internal virtual { require(from != address(0), "ERC20: transfer from the zero address"); require(to != address(0), "ERC20: transfer to the zero address"); _beforeTokenTransfer(from, to, amount); uint256 fromBalance = _balances[from]; require(fromBalance >= amount, "ERC20: transfer amount exceeds balance"); unchecked { _balances[from] = fromBalance - amount; // Overflow not possible: the sum of all balances is capped by totalSupply, and the sum is preserved by // decrementing then incrementing. _balances[to] += amount; } emit Transfer(from, to, amount); _afterTokenTransfer(from, to, amount); } /** @dev Creates `amount` tokens and assigns them to `account`, increasing * the total supply. * * Emits a {Transfer} event with `from` set to the zero address. * * Requirements: * * - `account` cannot be the zero address. */ function _mint(address account, uint256 amount) internal virtual { require(account != address(0), "ERC20: mint to the zero address"); _beforeTokenTransfer(address(0), account, amount); _totalSupply += amount; unchecked { // Overflow not possible: balance + amount is at most totalSupply + amount, which is checked above. _balances[account] += amount; } emit Transfer(address(0), account, amount); _afterTokenTransfer(address(0), account, amount); } /** * @dev Destroys `amount` tokens from `account`, reducing the * total supply. * * Emits a {Transfer} event with `to` set to the zero address. * * Requirements: * * - `account` cannot be the zero address. * - `account` must have at least `amount` tokens. */ function _burn(address account, uint256 amount) internal virtual { require(account != address(0), "ERC20: burn from the zero address"); _beforeTokenTransfer(account, address(0), amount); uint256 accountBalance = _balances[account]; require(accountBalance >= amount, "ERC20: burn amount exceeds balance"); unchecked { _balances[account] = accountBalance - amount; // Overflow not possible: amount <= accountBalance <= totalSupply. _totalSupply -= amount; } emit Transfer(account, address(0), amount); _afterTokenTransfer(account, address(0), amount); } /** * @dev Sets `amount` as the allowance of `spender` over the `owner` s tokens. * * This internal function is equivalent to `approve`, and can be used to * e.g. set automatic allowances for certain subsystems, etc. * * Emits an {Approval} event. * * Requirements: * * - `owner` cannot be the zero address. * - `spender` cannot be the zero address. */ function _approve(address owner, address spender, uint256 amount) internal virtual { require(owner != address(0), "ERC20: approve from the zero address"); require(spender != address(0), "ERC20: approve to the zero address"); _allowances[owner][spender] = amount; emit Approval(owner, spender, amount); } /** * @dev Updates `owner` s allowance for `spender` based on spent `amount`. * * Does not update the allowance amount in case of infinite allowance. * Revert if not enough allowance is available. * * Might emit an {Approval} event. */ function _spendAllowance(address owner, address spender, uint256 amount) internal virtual { uint256 currentAllowance = allowance(owner, spender); if (currentAllowance != type(uint256).max) { require(currentAllowance >= amount, "ERC20: insufficient allowance"); unchecked { _approve(owner, spender, currentAllowance - amount); } } } /** * @dev Hook that is called before any transfer of tokens. This includes * minting and burning. * * Calling conditions: * * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens * will be transferred to `to`. * - when `from` is zero, `amount` tokens will be minted for `to`. * - when `to` is zero, `amount` of ``from``'s tokens will be burned. * - `from` and `to` are never both zero. * * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. */ function _beforeTokenTransfer(address from, address to, uint256 amount) internal virtual {} /** * @dev Hook that is called after any transfer of tokens. This includes * minting and burning. * * Calling conditions: * * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens * has been transferred to `to`. * - when `from` is zero, `amount` tokens have been minted for `to`. * - when `to` is zero, `amount` of ``from``'s tokens have been burned. * - `from` and `to` are never both zero. * * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. */ function _afterTokenTransfer(address from, address to, uint256 amount) internal virtual {} /** * @dev This empty reserved space is put in place to allow future versions to add new * variables without shifting down storage in the inheritance chain. * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps */ uint256[45] private __gap; }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (interfaces/IERC4626.sol) pragma solidity ^0.8.0; import "../token/ERC20/IERC20Upgradeable.sol"; import "../token/ERC20/extensions/IERC20MetadataUpgradeable.sol"; /** * @dev Interface of the ERC4626 "Tokenized Vault Standard", as defined in * https://eips.ethereum.org/EIPS/eip-4626[ERC-4626]. * * _Available since v4.7._ */ interface IERC4626Upgradeable is IERC20Upgradeable, IERC20MetadataUpgradeable { event Deposit(address indexed sender, address indexed owner, uint256 assets, uint256 shares); event Withdraw( address indexed sender, address indexed receiver, address indexed owner, uint256 assets, uint256 shares ); /** * @dev Returns the address of the underlying token used for the Vault for accounting, depositing, and withdrawing. * * - MUST be an ERC-20 token contract. * - MUST NOT revert. */ function asset() external view returns (address assetTokenAddress); /** * @dev Returns the total amount of the underlying asset that is “managed” by Vault. * * - SHOULD include any compounding that occurs from yield. * - MUST be inclusive of any fees that are charged against assets in the Vault. * - MUST NOT revert. */ function totalAssets() external view returns (uint256 totalManagedAssets); /** * @dev Returns the amount of shares that the Vault would exchange for the amount of assets provided, in an ideal * scenario where all the conditions are met. * * - MUST NOT be inclusive of any fees that are charged against assets in the Vault. * - MUST NOT show any variations depending on the caller. * - MUST NOT reflect slippage or other on-chain conditions, when performing the actual exchange. * - MUST NOT revert. * * NOTE: This calculation MAY NOT reflect the “per-user” price-per-share, and instead should reflect the * “average-user’s” price-per-share, meaning what the average user should expect to see when exchanging to and * from. */ function convertToShares(uint256 assets) external view returns (uint256 shares); /** * @dev Returns the amount of assets that the Vault would exchange for the amount of shares provided, in an ideal * scenario where all the conditions are met. * * - MUST NOT be inclusive of any fees that are charged against assets in the Vault. * - MUST NOT show any variations depending on the caller. * - MUST NOT reflect slippage or other on-chain conditions, when performing the actual exchange. * - MUST NOT revert. * * NOTE: This calculation MAY NOT reflect the “per-user” price-per-share, and instead should reflect the * “average-user’s” price-per-share, meaning what the average user should expect to see when exchanging to and * from. */ function convertToAssets(uint256 shares) external view returns (uint256 assets); /** * @dev Returns the maximum amount of the underlying asset that can be deposited into the Vault for the receiver, * through a deposit call. * * - MUST return a limited value if receiver is subject to some deposit limit. * - MUST return 2 ** 256 - 1 if there is no limit on the maximum amount of assets that may be deposited. * - MUST NOT revert. */ function maxDeposit(address receiver) external view returns (uint256 maxAssets); /** * @dev Allows an on-chain or off-chain user to simulate the effects of their deposit at the current block, given * current on-chain conditions. * * - MUST return as close to and no more than the exact amount of Vault shares that would be minted in a deposit * call in the same transaction. I.e. deposit should return the same or more shares as previewDeposit if called * in the same transaction. * - MUST NOT account for deposit limits like those returned from maxDeposit and should always act as though the * deposit would be accepted, regardless if the user has enough tokens approved, etc. * - MUST be inclusive of deposit fees. Integrators should be aware of the existence of deposit fees. * - MUST NOT revert. * * NOTE: any unfavorable discrepancy between convertToShares and previewDeposit SHOULD be considered slippage in * share price or some other type of condition, meaning the depositor will lose assets by depositing. */ function previewDeposit(uint256 assets) external view returns (uint256 shares); /** * @dev Mints shares Vault shares to receiver by depositing exactly amount of underlying tokens. * * - MUST emit the Deposit event. * - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the * deposit execution, and are accounted for during deposit. * - MUST revert if all of assets cannot be deposited (due to deposit limit being reached, slippage, the user not * approving enough underlying tokens to the Vault contract, etc). * * NOTE: most implementations will require pre-approval of the Vault with the Vault’s underlying asset token. */ function deposit(uint256 assets, address receiver) external returns (uint256 shares); /** * @dev Returns the maximum amount of the Vault shares that can be minted for the receiver, through a mint call. * - MUST return a limited value if receiver is subject to some mint limit. * - MUST return 2 ** 256 - 1 if there is no limit on the maximum amount of shares that may be minted. * - MUST NOT revert. */ function maxMint(address receiver) external view returns (uint256 maxShares); /** * @dev Allows an on-chain or off-chain user to simulate the effects of their mint at the current block, given * current on-chain conditions. * * - MUST return as close to and no fewer than the exact amount of assets that would be deposited in a mint call * in the same transaction. I.e. mint should return the same or fewer assets as previewMint if called in the * same transaction. * - MUST NOT account for mint limits like those returned from maxMint and should always act as though the mint * would be accepted, regardless if the user has enough tokens approved, etc. * - MUST be inclusive of deposit fees. Integrators should be aware of the existence of deposit fees. * - MUST NOT revert. * * NOTE: any unfavorable discrepancy between convertToAssets and previewMint SHOULD be considered slippage in * share price or some other type of condition, meaning the depositor will lose assets by minting. */ function previewMint(uint256 shares) external view returns (uint256 assets); /** * @dev Mints exactly shares Vault shares to receiver by depositing amount of underlying tokens. * * - MUST emit the Deposit event. * - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the mint * execution, and are accounted for during mint. * - MUST revert if all of shares cannot be minted (due to deposit limit being reached, slippage, the user not * approving enough underlying tokens to the Vault contract, etc). * * NOTE: most implementations will require pre-approval of the Vault with the Vault’s underlying asset token. */ function mint(uint256 shares, address receiver) external returns (uint256 assets); /** * @dev Returns the maximum amount of the underlying asset that can be withdrawn from the owner balance in the * Vault, through a withdraw call. * * - MUST return a limited value if owner is subject to some withdrawal limit or timelock. * - MUST NOT revert. */ function maxWithdraw(address owner) external view returns (uint256 maxAssets); /** * @dev Allows an on-chain or off-chain user to simulate the effects of their withdrawal at the current block, * given current on-chain conditions. * * - MUST return as close to and no fewer than the exact amount of Vault shares that would be burned in a withdraw * call in the same transaction. I.e. withdraw should return the same or fewer shares as previewWithdraw if * called * in the same transaction. * - MUST NOT account for withdrawal limits like those returned from maxWithdraw and should always act as though * the withdrawal would be accepted, regardless if the user has enough shares, etc. * - MUST be inclusive of withdrawal fees. Integrators should be aware of the existence of withdrawal fees. * - MUST NOT revert. * * NOTE: any unfavorable discrepancy between convertToShares and previewWithdraw SHOULD be considered slippage in * share price or some other type of condition, meaning the depositor will lose assets by depositing. */ function previewWithdraw(uint256 assets) external view returns (uint256 shares); /** * @dev Burns shares from owner and sends exactly assets of underlying tokens to receiver. * * - MUST emit the Withdraw event. * - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the * withdraw execution, and are accounted for during withdraw. * - MUST revert if all of assets cannot be withdrawn (due to withdrawal limit being reached, slippage, the owner * not having enough shares, etc). * * Note that some implementations will require pre-requesting to the Vault before a withdrawal may be performed. * Those methods should be performed separately. */ function withdraw(uint256 assets, address receiver, address owner) external returns (uint256 shares); /** * @dev Returns the maximum amount of Vault shares that can be redeemed from the owner balance in the Vault, * through a redeem call. * * - MUST return a limited value if owner is subject to some withdrawal limit or timelock. * - MUST return balanceOf(owner) if owner is not subject to any withdrawal limit or timelock. * - MUST NOT revert. */ function maxRedeem(address owner) external view returns (uint256 maxShares); /** * @dev Allows an on-chain or off-chain user to simulate the effects of their redeemption at the current block, * given current on-chain conditions. * * - MUST return as close to and no more than the exact amount of assets that would be withdrawn in a redeem call * in the same transaction. I.e. redeem should return the same or more assets as previewRedeem if called in the * same transaction. * - MUST NOT account for redemption limits like those returned from maxRedeem and should always act as though the * redemption would be accepted, regardless if the user has enough shares, etc. * - MUST be inclusive of withdrawal fees. Integrators should be aware of the existence of withdrawal fees. * - MUST NOT revert. * * NOTE: any unfavorable discrepancy between convertToAssets and previewRedeem SHOULD be considered slippage in * share price or some other type of condition, meaning the depositor will lose assets by redeeming. */ function previewRedeem(uint256 shares) external view returns (uint256 assets); /** * @dev Burns exactly shares from owner and sends assets of underlying tokens to receiver. * * - MUST emit the Withdraw event. * - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the * redeem execution, and are accounted for during redeem. * - MUST revert if all of shares cannot be redeemed (due to withdrawal limit being reached, slippage, the owner * not having enough shares, etc). * * NOTE: some implementations will require pre-requesting to the Vault before a withdrawal may be performed. * Those methods should be performed separately. */ function redeem(uint256 shares, address receiver, address owner) external returns (uint256 assets); }
// SPDX-License-Identifier: MIT // THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE // WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. pragma solidity 0.8.28; /* solhint-disable */ /** * @dev Copied from https://github.com/balancer/balancer-v2-monorepo/blob/master/pkg/solidity-utils/contracts/math/LogExpMath.sol * * Some modifications are made due to compile error. * * It is the same as `LogExpMathV8` with `unchecked` scope. * * @dev Exponentiation and logarithm functions for 18 decimal fixed point numbers (both base and exponent/argument). * * Exponentiation and logarithm with arbitrary bases (x^y and log_x(y)) are implemented by conversion to natural * exponentiation and logarithm (where the base is Euler's number). * * @author Fernando Martinelli - @fernandomartinelli * @author Sergio Yuhjtman - @sergioyuhjtman * @author Daniel Fernandez - @dmf7z */ library LogExpMathV8 { // All fixed point multiplications and divisions are inlined. This means we need to divide by ONE when multiplying // two numbers, and multiply by ONE when dividing them. // All arguments and return values are 18 decimal fixed point numbers. int256 constant ONE_18 = 1e18; // Internally, intermediate values are computed with higher precision as 20 decimal fixed point numbers, and in the // case of ln36, 36 decimals. int256 constant ONE_20 = 1e20; int256 constant ONE_36 = 1e36; // The domain of natural exponentiation is bound by the word size and number of decimals used. // // Because internally the result will be stored using 20 decimals, the largest possible result is // (2^255 - 1) / 10^20, which makes the largest exponent ln((2^255 - 1) / 10^20) = 130.700829182905140221. // The smallest possible result is 10^(-18), which makes largest negative argument // ln(10^(-18)) = -41.446531673892822312. // We use 130.0 and -41.0 to have some safety margin. int256 constant MAX_NATURAL_EXPONENT = 130e18; int256 constant MIN_NATURAL_EXPONENT = -41e18; // Bounds for ln_36's argument. Both ln(0.9) and ln(1.1) can be represented with 36 decimal places in a fixed point // 256 bit integer. int256 constant LN_36_LOWER_BOUND = ONE_18 - 1e17; int256 constant LN_36_UPPER_BOUND = ONE_18 + 1e17; uint256 constant MILD_EXPONENT_BOUND = 2 ** 254 / uint256(ONE_20); // 18 decimal constants int256 constant x0 = 128000000000000000000; // 2ˆ7 int256 constant a0 = 38877084059945950922200000000000000000000000000000000000; // eˆ(x0) (no decimals) int256 constant x1 = 64000000000000000000; // 2ˆ6 int256 constant a1 = 6235149080811616882910000000; // eˆ(x1) (no decimals) // 20 decimal constants int256 constant x2 = 3200000000000000000000; // 2ˆ5 int256 constant a2 = 7896296018268069516100000000000000; // eˆ(x2) int256 constant x3 = 1600000000000000000000; // 2ˆ4 int256 constant a3 = 888611052050787263676000000; // eˆ(x3) int256 constant x4 = 800000000000000000000; // 2ˆ3 int256 constant a4 = 298095798704172827474000; // eˆ(x4) int256 constant x5 = 400000000000000000000; // 2ˆ2 int256 constant a5 = 5459815003314423907810; // eˆ(x5) int256 constant x6 = 200000000000000000000; // 2ˆ1 int256 constant a6 = 738905609893065022723; // eˆ(x6) int256 constant x7 = 100000000000000000000; // 2ˆ0 int256 constant a7 = 271828182845904523536; // eˆ(x7) int256 constant x8 = 50000000000000000000; // 2ˆ-1 int256 constant a8 = 164872127070012814685; // eˆ(x8) int256 constant x9 = 25000000000000000000; // 2ˆ-2 int256 constant a9 = 128402541668774148407; // eˆ(x9) int256 constant x10 = 12500000000000000000; // 2ˆ-3 int256 constant a10 = 113314845306682631683; // eˆ(x10) int256 constant x11 = 6250000000000000000; // 2ˆ-4 int256 constant a11 = 106449445891785942956; // eˆ(x11) /** * @dev Exponentiation (x^y) with unsigned 18 decimal fixed point base and exponent. * * Reverts if ln(x) * y is smaller than `MIN_NATURAL_EXPONENT`, or larger than `MAX_NATURAL_EXPONENT`. */ function pow(uint256 x, uint256 y) internal pure returns (uint256) { unchecked { if (y == 0) { // We solve the 0^0 indetermination by making it equal one. return uint256(ONE_18); } if (x == 0) { return 0; } // Instead of computing x^y directly, we instead rely on the properties of logarithms and exponentiation to // arrive at that result. In particular, exp(ln(x)) = x, and ln(x^y) = y * ln(x). This means // x^y = exp(y * ln(x)). // The ln function takes a signed value, so we need to make sure x fits in the signed 256 bit range. require(x >> 255 == 0, "X_OUT_OF_BOUNDS"); int256 x_int256 = int256(x); // We will compute y * ln(x) in a single step. Depending on the value of x, we can either use ln or ln_36. In // both cases, we leave the division by ONE_18 (due to fixed point multiplication) to the end. // This prevents y * ln(x) from overflowing, and at the same time guarantees y fits in the signed 256 bit range. require(y < MILD_EXPONENT_BOUND, "Y_OUT_OF_BOUNDS"); int256 y_int256 = int256(y); int256 logx_times_y; if (LN_36_LOWER_BOUND < x_int256 && x_int256 < LN_36_UPPER_BOUND) { int256 ln_36_x = _ln_36(x_int256); // ln_36_x has 36 decimal places, so multiplying by y_int256 isn't as straightforward, since we can't just // bring y_int256 to 36 decimal places, as it might overflow. Instead, we perform two 18 decimal // multiplications and add the results: one with the first 18 decimals of ln_36_x, and one with the // (downscaled) last 18 decimals. logx_times_y = ((ln_36_x / ONE_18) * y_int256 + ((ln_36_x % ONE_18) * y_int256) / ONE_18); } else { logx_times_y = _ln(x_int256) * y_int256; } logx_times_y /= ONE_18; // Finally, we compute exp(y * ln(x)) to arrive at x^y require(MIN_NATURAL_EXPONENT <= logx_times_y && logx_times_y <= MAX_NATURAL_EXPONENT, "PRODUCT_OUT_OF_BOUNDS"); return uint256(exp(logx_times_y)); } } /** * @dev Natural exponentiation (e^x) with signed 18 decimal fixed point exponent. * * Reverts if `x` is smaller than MIN_NATURAL_EXPONENT, or larger than `MAX_NATURAL_EXPONENT`. */ function exp(int256 x) internal pure returns (int256) { unchecked { require(x >= MIN_NATURAL_EXPONENT && x <= MAX_NATURAL_EXPONENT, "INVALID_EXPONENT"); if (x < 0) { // We only handle positive exponents: e^(-x) is computed as 1 / e^x. We can safely make x positive since it // fits in the signed 256 bit range (as it is larger than MIN_NATURAL_EXPONENT). // Fixed point division requires multiplying by ONE_18. return ((ONE_18 * ONE_18) / exp(-x)); } // First, we use the fact that e^(x+y) = e^x * e^y to decompose x into a sum of powers of two, which we call x_n, // where x_n == 2^(7 - n), and e^x_n = a_n has been precomputed. We choose the first x_n, x0, to equal 2^7 // because all larger powers are larger than MAX_NATURAL_EXPONENT, and therefore not present in the // decomposition. // At the end of this process we will have the product of all e^x_n = a_n that apply, and the remainder of this // decomposition, which will be lower than the smallest x_n. // exp(x) = k_0 * a_0 * k_1 * a_1 * ... + k_n * a_n * exp(remainder), where each k_n equals either 0 or 1. // We mutate x by subtracting x_n, making it the remainder of the decomposition. // The first two a_n (e^(2^7) and e^(2^6)) are too large if stored as 18 decimal numbers, and could cause // intermediate overflows. Instead we store them as plain integers, with 0 decimals. // Additionally, x0 + x1 is larger than MAX_NATURAL_EXPONENT, which means they will not both be present in the // decomposition. // For each x_n, we test if that term is present in the decomposition (if x is larger than it), and if so deduct // it and compute the accumulated product. int256 firstAN; if (x >= x0) { x -= x0; firstAN = a0; } else if (x >= x1) { x -= x1; firstAN = a1; } else { firstAN = 1; // One with no decimal places } // We now transform x into a 20 decimal fixed point number, to have enhanced precision when computing the // smaller terms. x *= 100; // `product` is the accumulated product of all a_n (except a0 and a1), which starts at 20 decimal fixed point // one. Recall that fixed point multiplication requires dividing by ONE_20. int256 product = ONE_20; if (x >= x2) { x -= x2; product = (product * a2) / ONE_20; } if (x >= x3) { x -= x3; product = (product * a3) / ONE_20; } if (x >= x4) { x -= x4; product = (product * a4) / ONE_20; } if (x >= x5) { x -= x5; product = (product * a5) / ONE_20; } if (x >= x6) { x -= x6; product = (product * a6) / ONE_20; } if (x >= x7) { x -= x7; product = (product * a7) / ONE_20; } if (x >= x8) { x -= x8; product = (product * a8) / ONE_20; } if (x >= x9) { x -= x9; product = (product * a9) / ONE_20; } // x10 and x11 are unnecessary here since we have high enough precision already. // Now we need to compute e^x, where x is small (in particular, it is smaller than x9). We use the Taylor series // expansion for e^x: 1 + x + (x^2 / 2!) + (x^3 / 3!) + ... + (x^n / n!). int256 seriesSum = ONE_20; // The initial one in the sum, with 20 decimal places. int256 term; // Each term in the sum, where the nth term is (x^n / n!). // The first term is simply x. term = x; seriesSum += term; // Each term (x^n / n!) equals the previous one times x, divided by n. Since x is a fixed point number, // multiplying by it requires dividing by ONE_20, but dividing by the non-fixed point n values does not. term = ((term * x) / ONE_20) / 2; seriesSum += term; term = ((term * x) / ONE_20) / 3; seriesSum += term; term = ((term * x) / ONE_20) / 4; seriesSum += term; term = ((term * x) / ONE_20) / 5; seriesSum += term; term = ((term * x) / ONE_20) / 6; seriesSum += term; term = ((term * x) / ONE_20) / 7; seriesSum += term; term = ((term * x) / ONE_20) / 8; seriesSum += term; term = ((term * x) / ONE_20) / 9; seriesSum += term; term = ((term * x) / ONE_20) / 10; seriesSum += term; term = ((term * x) / ONE_20) / 11; seriesSum += term; term = ((term * x) / ONE_20) / 12; seriesSum += term; // 12 Taylor terms are sufficient for 18 decimal precision. // We now have the first a_n (with no decimals), and the product of all other a_n present, and the Taylor // approximation of the exponentiation of the remainder (both with 20 decimals). All that remains is to multiply // all three (one 20 decimal fixed point multiplication, dividing by ONE_20, and one integer multiplication), // and then drop two digits to return an 18 decimal value. return (((product * seriesSum) / ONE_20) * firstAN) / 100; } } /** * @dev Logarithm (log(arg, base), with signed 18 decimal fixed point base and argument. */ function log(int256 arg, int256 base) internal pure returns (int256) { unchecked { // This performs a simple base change: log(arg, base) = ln(arg) / ln(base). // Both logBase and logArg are computed as 36 decimal fixed point numbers, either by using ln_36, or by // upscaling. int256 logBase; if (LN_36_LOWER_BOUND < base && base < LN_36_UPPER_BOUND) { logBase = _ln_36(base); } else { logBase = _ln(base) * ONE_18; } int256 logArg; if (LN_36_LOWER_BOUND < arg && arg < LN_36_UPPER_BOUND) { logArg = _ln_36(arg); } else { logArg = _ln(arg) * ONE_18; } // When dividing, we multiply by ONE_18 to arrive at a result with 18 decimal places return (logArg * ONE_18) / logBase; } } /** * @dev Natural logarithm (ln(a)) with signed 18 decimal fixed point argument. */ function ln(int256 a) internal pure returns (int256) { unchecked { // The real natural logarithm is not defined for negative numbers or zero. require(a > 0, "OUT_OF_BOUNDS"); if (LN_36_LOWER_BOUND < a && a < LN_36_UPPER_BOUND) { return _ln_36(a) / ONE_18; } else { return _ln(a); } } } /** * @dev Internal natural logarithm (ln(a)) with signed 18 decimal fixed point argument. */ function _ln(int256 a) private pure returns (int256) { unchecked { if (a < ONE_18) { // Since ln(a^k) = k * ln(a), we can compute ln(a) as ln(a) = ln((1/a)^(-1)) = - ln((1/a)). If a is less // than one, 1/a will be greater than one, and this if statement will not be entered in the recursive call. // Fixed point division requires multiplying by ONE_18. return (-_ln((ONE_18 * ONE_18) / a)); } // First, we use the fact that ln^(a * b) = ln(a) + ln(b) to decompose ln(a) into a sum of powers of two, which // we call x_n, where x_n == 2^(7 - n), which are the natural logarithm of precomputed quantities a_n (that is, // ln(a_n) = x_n). We choose the first x_n, x0, to equal 2^7 because the exponential of all larger powers cannot // be represented as 18 fixed point decimal numbers in 256 bits, and are therefore larger than a. // At the end of this process we will have the sum of all x_n = ln(a_n) that apply, and the remainder of this // decomposition, which will be lower than the smallest a_n. // ln(a) = k_0 * x_0 + k_1 * x_1 + ... + k_n * x_n + ln(remainder), where each k_n equals either 0 or 1. // We mutate a by subtracting a_n, making it the remainder of the decomposition. // For reasons related to how `exp` works, the first two a_n (e^(2^7) and e^(2^6)) are not stored as fixed point // numbers with 18 decimals, but instead as plain integers with 0 decimals, so we need to multiply them by // ONE_18 to convert them to fixed point. // For each a_n, we test if that term is present in the decomposition (if a is larger than it), and if so divide // by it and compute the accumulated sum. int256 sum = 0; if (a >= a0 * ONE_18) { a /= a0; // Integer, not fixed point division sum += x0; } if (a >= a1 * ONE_18) { a /= a1; // Integer, not fixed point division sum += x1; } // All other a_n and x_n are stored as 20 digit fixed point numbers, so we convert the sum and a to this format. sum *= 100; a *= 100; // Because further a_n are 20 digit fixed point numbers, we multiply by ONE_20 when dividing by them. if (a >= a2) { a = (a * ONE_20) / a2; sum += x2; } if (a >= a3) { a = (a * ONE_20) / a3; sum += x3; } if (a >= a4) { a = (a * ONE_20) / a4; sum += x4; } if (a >= a5) { a = (a * ONE_20) / a5; sum += x5; } if (a >= a6) { a = (a * ONE_20) / a6; sum += x6; } if (a >= a7) { a = (a * ONE_20) / a7; sum += x7; } if (a >= a8) { a = (a * ONE_20) / a8; sum += x8; } if (a >= a9) { a = (a * ONE_20) / a9; sum += x9; } if (a >= a10) { a = (a * ONE_20) / a10; sum += x10; } if (a >= a11) { a = (a * ONE_20) / a11; sum += x11; } // a is now a small number (smaller than a_11, which roughly equals 1.06). This means we can use a Taylor series // that converges rapidly for values of `a` close to one - the same one used in ln_36. // Let z = (a - 1) / (a + 1). // ln(a) = 2 * (z + z^3 / 3 + z^5 / 5 + z^7 / 7 + ... + z^(2 * n + 1) / (2 * n + 1)) // Recall that 20 digit fixed point division requires multiplying by ONE_20, and multiplication requires // division by ONE_20. int256 z = ((a - ONE_20) * ONE_20) / (a + ONE_20); int256 z_squared = (z * z) / ONE_20; // num is the numerator of the series: the z^(2 * n + 1) term int256 num = z; // seriesSum holds the accumulated sum of each term in the series, starting with the initial z int256 seriesSum = num; // In each step, the numerator is multiplied by z^2 num = (num * z_squared) / ONE_20; seriesSum += num / 3; num = (num * z_squared) / ONE_20; seriesSum += num / 5; num = (num * z_squared) / ONE_20; seriesSum += num / 7; num = (num * z_squared) / ONE_20; seriesSum += num / 9; num = (num * z_squared) / ONE_20; seriesSum += num / 11; // 6 Taylor terms are sufficient for 36 decimal precision. // Finally, we multiply by 2 (non fixed point) to compute ln(remainder) seriesSum *= 2; // We now have the sum of all x_n present, and the Taylor approximation of the logarithm of the remainder (both // with 20 decimals). All that remains is to sum these two, and then drop two digits to return a 18 decimal // value. return (sum + seriesSum) / 100; } } /** * @dev Intrnal high precision (36 decimal places) natural logarithm (ln(x)) with signed 18 decimal fixed point argument, * for x close to one. * * Should only be used if x is between LN_36_LOWER_BOUND and LN_36_UPPER_BOUND. */ function _ln_36(int256 x) private pure returns (int256) { unchecked { // Since ln(1) = 0, a value of x close to one will yield a very small result, which makes using 36 digits // worthwhile. // First, we transform x to a 36 digit fixed point value. x *= ONE_18; // We will use the following Taylor expansion, which converges very rapidly. Let z = (x - 1) / (x + 1). // ln(x) = 2 * (z + z^3 / 3 + z^5 / 5 + z^7 / 7 + ... + z^(2 * n + 1) / (2 * n + 1)) // Recall that 36 digit fixed point division requires multiplying by ONE_36, and multiplication requires // division by ONE_36. int256 z = ((x - ONE_36) * ONE_36) / (x + ONE_36); int256 z_squared = (z * z) / ONE_36; // num is the numerator of the series: the z^(2 * n + 1) term int256 num = z; // seriesSum holds the accumulated sum of each term in the series, starting with the initial z int256 seriesSum = num; // In each step, the numerator is multiplied by z^2 num = (num * z_squared) / ONE_36; seriesSum += num / 3; num = (num * z_squared) / ONE_36; seriesSum += num / 5; num = (num * z_squared) / ONE_36; seriesSum += num / 7; num = (num * z_squared) / ONE_36; seriesSum += num / 9; num = (num * z_squared) / ONE_36; seriesSum += num / 11; num = (num * z_squared) / ONE_36; seriesSum += num / 13; num = (num * z_squared) / ONE_36; seriesSum += num / 15; // 8 Taylor terms are sufficient for 36 decimal precision. // All that remains is multiplying by 2 (non fixed point). return seriesSum * 2; } } }
// SPDX-License-Identifier: MIT pragma solidity 0.8.28; /// @dev This library implements formulas for minting and redeeming stable coin token (aToken) and leveraged (xToken) /// tokens in a system where: /// - There is a base token with a certain Net Asset Value (NAV). /// - A stable coin token (aToken) with its own NAV. /// - A leveraged token (xToken) with its own NAV. /// All values are scaled by 1e18 before being passed to these functions. /// /// The core equation governing the system is: /// n * v = nf * vf + nx * vx /// /// Where: /// n = current total base supply /// v = baseNav (NAV of base token) /// nf = aSupply (supply of stable coin token) /// vf = aNav (NAV of stable coin token) /// nx = xSupply (supply of leveraged token) /// vx = xNav (NAV of leveraged token) /// /// The "collateral ratio" (cr) or "target collateral ratio" (ncr) is defined as: /// cr = (total base value) / (stable coin token value) = (n * v) / (nf * vf) /// /// Operations involve adjusting supplies (minting or redeeming tokens) to reach a new collateral ratio (ncr). /// We define: /// dn = change in base supply (how many base tokens are added or removed) /// df = change in stable coin token supply (how many stable coin tokens are added or removed) /// /// By setting: /// ((n + dn) * v) / ((nf + df)*vf) = ncr /// /// and using the base equation n * v = nf * vf + nx * vx, we can derive formulas for dn and df /// depending on whether we are minting or redeeming under a desired collateral ratio. library FxStableMath { /************* * Constants * *************/ /// @dev The precision (scaling factor) used for calculations. uint256 internal constant PRECISION = 1e18; /// @dev The maximum leverage ratio allowed. uint256 internal constant MAX_LEVERAGE_RATIO = 100e18; /*********** * Structs * ***********/ struct SwapState { // Current supply of the base token uint256 baseSupply; // Current NAV of the base token (scaled by 1e18) uint256 baseNav; // Current TWAP NAV of the base token (scaled by 1e18), used for stable calculations uint256 baseTwapNav; // Current supply of the stable coin token uint256 aSupply; // Current NAV of the stable coin token (scaled by 1e18) uint256 aNav; // Current supply of the leveraged token uint256 xSupply; // Current NAV of the leveraged token (scaled by 1e18) uint256 xNav; // A boolean parameter `beta` that may adjust behavior in some calculations bool beta; } /** * @notice Compute how much base token (dn) and stable coin tokens (df) can be minted to achieve a new collateral ratio, * if the current collateral ratio is less than the new target. * * Variables (for reference): * n: current total base supply * v: baseNav * nf: aSupply * vf: aNav * nx: xSupply * vx: xNav * ncr: newCollateralRatio * * Core equations: * Initially: * n * v = nf * vf + nx * vx * * After adding some amount of base tokens (dn) and stable coin tokens (df): * (n + dn) * v = (nf + df) * vf + nx * vx * * Define collateral ratio as: * ((n + dn) * v) / ((nf + df) * vf) = ncr * * From the above, solving for df and dn: * df = ((n * v) - (ncr * nf * vf)) / ((ncr - 1) * vf) * dn = ((n * v) - (ncr * nf * vf)) / ((ncr - 1) * v) * * Here, df and dn tell us how much stable coin token and base token must be added to achieve ncr. * If dn > 0, we need to add that many base tokens; if df > 0, we can mint that many stable coin tokens. * * @dev If the current collateral ratio is already >= ncr, then we return 0 because no minting is needed. * * @param state Current state of the system. * @param _newCollateralRatio The desired new collateral ratio (scaled by 1e18). * @return _maxBaseIn The amount of base token required (corresponds to dn). * @return _maxATokenMintable The amount of stable coin token (aToken) that can be minted (corresponds to df). */ function maxMintableAToken( SwapState memory state, uint256 _newCollateralRatio ) internal pure returns (uint256 _maxBaseIn, uint256 _maxATokenMintable) { // Calculate scaled values uint256 _baseVal = state.baseSupply * (state.baseNav) * (PRECISION); uint256 _aVal = _newCollateralRatio * (state.aSupply) * state.aNav; // If baseVal > aVal, we can mint if (_baseVal > _aVal) { // Adjust ncr to (ncr - 1)*PRECISION _newCollateralRatio = _newCollateralRatio - (PRECISION); uint256 _delta = _baseVal - _aVal; // dn = delta / ((ncr - 1)*v) _maxBaseIn = _delta / (state.baseNav * (_newCollateralRatio)); // df = delta / ((ncr - 1)*vf) _maxATokenMintable = _delta / (state.aNav * (_newCollateralRatio)); } } /** * @notice Compute how much base token (dn) and leveraged tokens (xToken) can be minted to achieve a new collateral ratio, * if the current collateral ratio is less than the new target. * * Equations: * n * v = nf * vf + nx * vx * * After adding dn base tokens and dx leveraged tokens: * (n + dn)*v = nf*vf + (nx + dx)*vx * * The new collateral ratio condition: * ((n + dn)*v) / (nf*vf) = ncr * * From this, solving for dn and dx: * dn = (ncr * nf * vf - n * v) / v * dx = (ncr * nf * vf - n * v) / vx * * @dev If the current collateral ratio >= ncr, we return 0 because no minting is needed. * * @param state The current state. * @param _newCollateralRatio The desired new collateral ratio (scaled by 1e18). * @return _maxBaseIn The amount of base token needed (dn). * @return _maxXTokenMintable The amount of leveraged token (xToken) that can be minted (dx). */ function maxMintableXToken( SwapState memory state, uint256 _newCollateralRatio ) internal pure returns (uint256 _maxBaseIn, uint256 _maxXTokenMintable) { uint256 _baseVal = state.baseSupply * state.baseNav * PRECISION; uint256 _aVal = _newCollateralRatio * state.aSupply * state.aNav; if (_aVal > _baseVal) { uint256 _delta = _aVal - _baseVal; // dn = delta / (v * PRECISION) _maxBaseIn = _delta / (state.baseNav * (PRECISION)); // dx = delta / (vx * PRECISION) _maxXTokenMintable = _delta / (state.xNav * (PRECISION)); } } /** * @notice Compute how many stable coin tokens (aToken) can be redeemed and how much base token (dn) is released * to reach the new collateral ratio if the current ratio is greater than the target. * * Equations: * Initially: * n * v = nf * vf + nx * vx * * After removing dn base tokens and df stable coin tokens: * (n - dn)*v = (nf - df)*vf + nx*vx * * The new collateral ratio: * ((n - dn)*v) / ((nf - df)*vf) = ncr * * Solve these for df and dn: * df = (ncr * nf * vf - n * v) / ((ncr - 1)*vf) * dn = (ncr * nf * vf - n * v) / ((ncr - 1)*v) * * Here, df and dn now represent how many stable coin tokens and base tokens must be removed (redeemed) * to achieve ncr. If df > 0, it means we should redeem that many aTokens; if dn > 0, that many base tokens * can be released. * * @dev If the current collateral ratio <= ncr, no redemption is needed, so return 0. * * @param state Current state. * @param _newCollateralRatio Desired collateral ratio (scaled by 1e18). * @return _maxBaseOut The amount of base token released (dn). * @return _maxATokenRedeemable The amount of stable coin token that can be redeemed (df). */ function maxRedeemableAToken( SwapState memory state, uint256 _newCollateralRatio ) internal pure returns (uint256 _maxBaseOut, uint256 _maxATokenRedeemable) { uint256 _baseVal = state.baseSupply * (state.baseNav) * (PRECISION); uint256 _aVal = _newCollateralRatio * (state.aSupply) * (PRECISION); if (_aVal > _baseVal) { uint256 _delta = _aVal - _baseVal; // Adjust ncr to (ncr - 1)*PRECISION _newCollateralRatio = _newCollateralRatio - (PRECISION); // df = delta / ((ncr - 1)*vf) _maxATokenRedeemable = _delta / (_newCollateralRatio * (state.aNav)); // dn = delta / ((ncr - 1)*v) _maxBaseOut = _delta / (_newCollateralRatio * (state.baseNav)); } else { _maxBaseOut = 0; _maxATokenRedeemable = 0; } } /** * @notice Compute how many leveraged tokens (xToken) can be redeemed and how much base token is released * if the current collateral ratio is greater than the target. * * Equations: * n * v = nf * vf + nx * vx * * After removing dn base tokens and dx leveraged tokens: * (n - dn)*v = nf*vf + (nx - dx)*vx * * The new collateral ratio: * ((n - dn)*v) / (nf*vf) = ncr * * Solve for dn and dx: * dn = (n * v - ncr * nf * vf) / v * dx = (n * v - ncr * nf * vf) / vx * * If dn > 0, that means base tokens can be redeemed; if dx > 0, that many xTokens can be redeemed. * * @dev If current collateral ratio <= ncr, return 0. * * @param state Current state. * @param _newCollateralRatio Desired collateral ratio (scaled by 1e18). * @return _maxBaseOut The base token released (dn). * @return _maxXTokenRedeemable The leveraged tokens (xToken) redeemable (dx). */ function maxRedeemableXToken( SwapState memory state, uint256 _newCollateralRatio ) internal pure returns (uint256 _maxBaseOut, uint256 _maxXTokenRedeemable) { uint256 _baseVal = state.baseSupply * (state.baseNav) * (PRECISION); uint256 _aVal = _newCollateralRatio * (state.aSupply) * (state.aNav); if (_baseVal > _aVal) { uint256 _delta = _baseVal - _aVal; // dx = delta / (vx * PRECISION) _maxXTokenRedeemable = _delta / (state.xNav * (PRECISION)); // dn = delta / (v * PRECISION) _maxBaseOut = _delta / (state.baseNav * (PRECISION)); } else { _maxBaseOut = 0; _maxXTokenRedeemable = 0; } } /** * @notice Mint stable coin tokens (aToken) given a certain amount of base tokens (dn). * * Equations: * n * v = nf * vf + nx * vx * After adding dn base and df fraction tokens: * (n + dn)*v = (nf + df)*vf + nx*vx * * Solve for df given dn: * df = (dn * v) / vf * * @param state Current state. * @param _baseIn Amount of base token supplied. * @return _aTokenOut Amount of stable coin token minted (df). */ function mintAToken(SwapState memory state, uint256 _baseIn) internal pure returns (uint256 _aTokenOut) { // df = (dn * v) / vf _aTokenOut = (_baseIn * state.baseNav) / state.aNav; } /** * @notice Mint leveraged tokens (xToken) given a certain amount of base tokens (dn). * * Equations: * n * v = nf * vf + nx * vx * After adding dn base tokens and dx leveraged tokens: * (n + dn)*v = nf*vf + (nx + dx)*vx * * Solve for dx: * dx = (dn * v * nx) / (n * v - nf * vf) * * @param state Current state. * @param _baseIn Amount of base token supplied. * @return _xTokenOut Amount of leveraged token minted (dx). */ function mintXToken(SwapState memory state, uint256 _baseIn) internal pure returns (uint256 _xTokenOut) { // dx = (dn * v * nx) / (n * v - nf * vf) _xTokenOut = _baseIn * state.baseNav * state.xSupply; _xTokenOut = _xTokenOut / (state.baseSupply * state.baseNav - state.aSupply * state.aNav); } /** * @notice Redeem base tokens by supplying stable coin tokens (aToken) and/or leveraged tokens (xToken). * * Equations: * n * v = nf * vf + nx * vx * After removing df fraction tokens and dx leveraged tokens: * (n - dn)*v = (nf - df)*vf + (nx - dx)*vx * * The amount of baseOut (dn*v) depends on how many fraction and leveraged tokens are provided. * * If xSupply = 0 (no leveraged tokens): * baseOut = (aTokenIn * vf) / v * * If xSupply > 0: * baseOut = [ (aTokenIn * vf) + (xTokenIn * (n*v - nf*vf) / nx ) ] / v * * @param state Current state. * @param _aTokenIn stable coin tokens supplied. * @param _xTokenIn Leveraged tokens supplied. * @return _baseOut Amount of base token redeemed. */ function redeem(SwapState memory state, uint256 _aTokenIn, uint256 _xTokenIn) internal pure returns (uint256 _baseOut) { uint256 _xVal = state.baseSupply * state.baseNav - state.aSupply * state.aNav; if (state.xSupply == 0) { _baseOut = (_aTokenIn * state.aNav) / state.baseNav; } else { _baseOut = _aTokenIn * state.aNav; _baseOut += (_xTokenIn * _xVal) / state.xSupply; _baseOut /= state.baseNav; } } /** * @notice Compute the current leverage ratio for the xToken. * * Define: * rho = (aSupply * aNav) / (baseSupply * baseTwapNav) * * When beta = false, leverage ratio = 1 / (1 - rho) * If under-collateralized (rho >= 1), leverage ratio = MAX_LEVERAGE_RATIO * * @param state Current state. * @return ratio Current leverage ratio. */ function leverageRatio(SwapState memory state) internal pure returns (uint256 ratio) { if (state.beta) return PRECISION; if(state.baseSupply == 0 || state.baseNav == 0 || state.baseTwapNav == 0) return 0; // rho = (aSupply * aNav * PRECISION) / (baseSupply * baseTwapNav) uint256 rho = (state.aSupply * state.aNav * PRECISION) / (state.baseSupply * state.baseTwapNav); if (rho >= PRECISION) { // Under-collateralized ratio = MAX_LEVERAGE_RATIO; } else { // ratio = 1 / (1 - rho) ratio = (PRECISION * PRECISION) / (PRECISION - rho); if (ratio > MAX_LEVERAGE_RATIO) ratio = MAX_LEVERAGE_RATIO; } } }
// SPDX-License-Identifier: MIT pragma solidity 0.8.28; interface IStrategy { error ErrorNotPermitted(); error ErrorZeroAmount(); error ErrorZeroAddress(); error ErrorCannotRecoverToken(); error ZeroGroupId(); function withdrawToTreasury(uint256 _diff) external returns (bool); function emergencyWithdraw() external returns (bool); }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/IERC20Metadata.sol) pragma solidity ^0.8.0; import "../IERC20Upgradeable.sol"; /** * @dev Interface for the optional metadata functions from the ERC20 standard. * * _Available since v4.1._ */ interface IERC20MetadataUpgradeable is IERC20Upgradeable { /** * @dev Returns the name of the token. */ function name() external view returns (string memory); /** * @dev Returns the symbol of the token. */ function symbol() external view returns (string memory); /** * @dev Returns the decimals places of the token. */ function decimals() external view returns (uint8); }
{ "remappings": [ "@aave/=node_modules/@aave/", "@account-abstraction/=node_modules/@account-abstraction/", "@chainlink/=node_modules/@chainlink/", "@eth-optimism/=node_modules/@chainlink/contracts/node_modules/@eth-optimism/", "@openzeppelin/=node_modules/@openzeppelin/", "@uniswap/=node_modules/@uniswap/", "base64-sol/=node_modules/base64-sol/", "ds-test/=lib/ds-test/", "eth-gas-reporter/=node_modules/eth-gas-reporter/", "forge-std/=lib/forge-std/src/", "hardhat/=node_modules/hardhat/", "solidity-bytes-utils/=node_modules/solidity-bytes-utils/", "erc4626-tests/=lib/openzeppelin-contracts-upgradeable/lib/erc4626-tests/", "openzeppelin-contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/", "openzeppelin/=lib/openzeppelin-contracts-upgradeable/contracts/", "solmate/=lib/solmate/src/", "abdk-libraries-solidity/=node_modules/abdk-libraries-solidity/" ], "optimizer": { "enabled": true, "runs": 200 }, "metadata": { "useLiteralContent": false, "bytecodeHash": "ipfs", "appendCBOR": true }, "outputSelection": { "*": { "*": [ "evm.bytecode", "evm.deployedBytecode", "devdoc", "userdoc", "metadata", "abi" ] } }, "evmVersion": "shanghai", "viaIR": true, "libraries": { "src/libs/ValidationLibrary.sol": { "ValidationLibrary": "0x51Ce2EFdEB2d8Bea8F060C6a870B6744e37b5C0F" } } }
Contract Security Audit
- No Contract Security Audit Submitted- Submit Audit Here
[{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[{"internalType":"GroupId","name":"groupId","type":"bytes32"},{"internalType":"address","name":"recipient","type":"address"},{"internalType":"address","name":"sender","type":"address"}],"name":"ArbitraryCallsNotAllowed","type":"error"},{"inputs":[],"name":"ConfigNotReady","type":"error"},{"inputs":[{"internalType":"GroupId","name":"groupId","type":"bytes32"}],"name":"EarlyExit","type":"error"},{"inputs":[{"internalType":"GroupId","name":"groupId","type":"bytes32"}],"name":"EmptyCollateralListOnUpdate","type":"error"},{"inputs":[{"internalType":"GroupId","name":"groupId","type":"bytes32"}],"name":"ErrorATokenMintingPausedInStabilityMode","type":"error"},{"inputs":[],"name":"ErrorInsufficientBaseOutput","type":"error"},{"inputs":[],"name":"ErrorInsufficientTokenOutput","type":"error"},{"inputs":[],"name":"ErrorMinTokenAmount","type":"error"},{"inputs":[],"name":"ErrorMintPaused","type":"error"},{"inputs":[],"name":"ErrorRedeemPaused","type":"error"},{"inputs":[{"internalType":"GroupId","name":"groupId","type":"bytes32"},{"internalType":"uint8","name":"operationType","type":"uint8"},{"internalType":"uint8","name":"feeModel","type":"uint8"},{"internalType":"bool","name":"isInStabilityMode","type":"bool"}],"name":"ErrorStabilityModeMintNotAllowed","type":"error"},{"inputs":[{"internalType":"GroupId","name":"groupId","type":"bytes32"},{"internalType":"uint8","name":"operationType","type":"uint8"},{"internalType":"uint8","name":"feeModel","type":"uint8"},{"internalType":"bool","name":"isInStabilityMode","type":"bool"}],"name":"ErrorStabilityModeRedeemNotAllowed","type":"error"},{"inputs":[],"name":"InsufficientAllowance","type":"error"},{"inputs":[{"internalType":"address","name":"token","type":"address"}],"name":"InsufficientBalance","type":"error"},{"inputs":[{"internalType":"GroupId","name":"groupId","type":"bytes32"}],"name":"InsufficientPreparedAmount","type":"error"},{"inputs":[{"internalType":"uint24","name":"fee","type":"uint24"}],"name":"InvalidFee","type":"error"},{"inputs":[{"internalType":"GroupId","name":"groupId","type":"bytes32"}],"name":"InvalidGroupConfiguration","type":"error"},{"inputs":[],"name":"InvalidMinMaxCollateralAmount","type":"error"},{"inputs":[{"internalType":"uint256","name":"minimumAmount","type":"uint256"},{"internalType":"uint256","name":"amountReceived","type":"uint256"}],"name":"InvalidMinimumAmount","type":"error"},{"inputs":[{"internalType":"GroupId","name":"groupId","type":"bytes32"},{"internalType":"uint256","name":"minimumAmount","type":"uint256"},{"internalType":"uint256","name":"amountReceived","type":"uint256"}],"name":"InvalidMinimumAmount","type":"error"},{"inputs":[],"name":"InvalidOperationType","type":"error"},{"inputs":[{"internalType":"uint8","name":"operationType","type":"uint8"}],"name":"InvalidOperationType","type":"error"},{"inputs":[],"name":"InvalidPackedData","type":"error"},{"inputs":[],"name":"InvalidPaymentAmount","type":"error"},{"inputs":[],"name":"InvalidRatios","type":"error"},{"inputs":[],"name":"InvalidSlippageRequested","type":"error"},{"inputs":[{"internalType":"GroupId","name":"groupId","type":"bytes32"},{"internalType":"uint256","name":"minAmount1","type":"uint256"},{"internalType":"uint256","name":"amount0","type":"uint256"}],"name":"InvalidSlippageTolerance","type":"error"},{"inputs":[{"internalType":"GroupId","name":"groupId","type":"bytes32"},{"internalType":"uint256","name":"minOutAmount","type":"uint256"},{"internalType":"uint256","name":"amountReceived","type":"uint256"}],"name":"InvalidSlippageTolerance","type":"error"},{"inputs":[{"internalType":"GroupId","name":"groupId","type":"bytes32"},{"internalType":"uint256","name":"preparedAmount","type":"uint256"},{"internalType":"uint256","name":"maxAmount","type":"uint256"}],"name":"MaximumAmountExceeded","type":"error"},{"inputs":[{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"NativeTokenWithdrawnFailed","type":"error"},{"inputs":[],"name":"NoFeesOwed","type":"error"},{"inputs":[],"name":"NotPermitted","type":"error"},{"inputs":[{"internalType":"GroupId","name":"groupId","type":"bytes32"}],"name":"RedeemLocked","type":"error"},{"inputs":[{"internalType":"GroupId","name":"groupId","type":"bytes32"},{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"TransferFailed","type":"error"},{"inputs":[],"name":"TreasuryGroupUpdateFailed","type":"error"},{"inputs":[],"name":"UnsupportedCollateralType","type":"error"},{"inputs":[{"internalType":"GroupId","name":"groupId","type":"bytes32"}],"name":"UnwrappedAmountIsZero","type":"error"},{"inputs":[],"name":"ZeroAddress","type":"error"},{"inputs":[{"internalType":"GroupId","name":"groupId","type":"bytes32"}],"name":"ZeroAddress","type":"error"},{"inputs":[],"name":"ZeroAmount","type":"error"},{"inputs":[],"name":"ZeroGroupId","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"amountIn","type":"uint256"},{"indexed":true,"internalType":"uint256","name":"amountOut","type":"uint256"},{"indexed":true,"internalType":"address","name":"recipient","type":"address"}],"name":"AutoCompounded","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"GroupId","name":"groupId","type":"bytes32"}],"name":"CacheUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"Currency","name":"tokenAddress","type":"address"},{"indexed":true,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"ERC20Recovered","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"feeCollector","type":"address"},{"indexed":true,"internalType":"address","name":"token","type":"address"},{"indexed":true,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"FeeCollected","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"GroupId","name":"groupId","type":"bytes32"},{"indexed":true,"internalType":"address","name":"feeCollector","type":"address"},{"indexed":true,"internalType":"address","name":"token","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"FeeCollected","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"feeCollector","type":"address"},{"indexed":false,"internalType":"uint256","name":"protocolFeeAmount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"hookFeeAmount","type":"uint256"}],"name":"FeeCollected","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"hookContract","type":"address"},{"indexed":true,"internalType":"uint256","name":"protocolFeeAmount","type":"uint256"},{"indexed":true,"internalType":"uint256","name":"hookRemainingFee","type":"uint256"}],"name":"FeeDelegated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"GroupId","name":"groupId","type":"bytes32"},{"indexed":true,"internalType":"address","name":"hookContract","type":"address"},{"indexed":false,"internalType":"uint256","name":"directFeeAmount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"hookFeeAmount","type":"uint256"}],"name":"FeeDelegated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"hookContract","type":"address"},{"indexed":true,"internalType":"address","name":"token","type":"address"},{"indexed":true,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"FeesSettled","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"GroupId","name":"groupId","type":"bytes32"},{"indexed":true,"internalType":"address","name":"hookContract","type":"address"},{"indexed":true,"internalType":"address","name":"token","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"FeesSettled","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint8","name":"version","type":"uint8"}],"name":"Initialized","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"GroupId","name":"groupId","type":"bytes32"},{"indexed":true,"internalType":"address","name":"token","type":"address"},{"indexed":true,"internalType":"address","name":"recipient","type":"address"},{"indexed":false,"internalType":"address","name":"sender","type":"address"},{"indexed":false,"internalType":"uint256","name":"ytMinted","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"vtMinted","type":"uint256"}],"name":"MintToken","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"NativeTokenWithdrawn","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"GroupId","name":"groupId","type":"bytes32"},{"indexed":true,"internalType":"bool","name":"enabled","type":"bool"}],"name":"RedeemLockUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"GroupId","name":"groupId","type":"bytes32"},{"indexed":true,"internalType":"address","name":"token","type":"address"},{"indexed":true,"internalType":"address","name":"recipient","type":"address"},{"indexed":false,"internalType":"address","name":"sender","type":"address"},{"indexed":false,"internalType":"uint256","name":"baseOut","type":"uint256"}],"name":"RedeemToken","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"uint256","name":"safeRefundAmount","type":"uint256"}],"name":"Refund","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"previousAdminRole","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"newAdminRole","type":"bytes32"}],"name":"RoleAdminChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleGranted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleRevoked","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"fromToken","type":"address"},{"indexed":true,"internalType":"address","name":"toToken","type":"address"},{"indexed":false,"internalType":"uint256","name":"amountIn","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amountOut","type":"uint256"}],"name":"TokensSwapped","type":"event"},{"stateMutability":"payable","type":"fallback"},{"inputs":[],"name":"DEFAULT_ADMIN_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"adminAddress","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"tokenRegistry","type":"address"},{"internalType":"GroupId","name":"groupId","type":"bytes32"}],"name":"forceUpdateGroupCache","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"getGlobalMintFlag","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getGlobalRedeemFlag","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"GroupId","name":"groupId","type":"bytes32"}],"name":"getGroupMintFlag","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"GroupId","name":"groupId","type":"bytes32"}],"name":"getGroupRedeemFlag","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"GroupId","name":"groupId","type":"bytes32"}],"name":"getGroupStabilityFlag","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"GroupId","name":"groupId","type":"bytes32"},{"internalType":"address","name":"hookContract","type":"address"},{"internalType":"address","name":"token","type":"address"}],"name":"getOwedFees","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleAdmin","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"GroupId","name":"groupId","type":"bytes32"},{"internalType":"address","name":"user","type":"address"}],"name":"getUserOperationTime","outputs":[{"internalType":"uint256","name":"lastVTMintTime","type":"uint256"},{"internalType":"uint256","name":"lastYTMintTime","type":"uint256"},{"internalType":"uint256","name":"lastVTRedeemTime","type":"uint256"},{"internalType":"uint256","name":"lastYTRedeemTime","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"grantRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"hasRole","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_admin","type":"address"}],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"components":[{"internalType":"Currency","name":"aToken","type":"address"},{"internalType":"Currency","name":"xToken","type":"address"},{"internalType":"Currency","name":"baseToken","type":"address"},{"internalType":"Currency","name":"yieldBearingToken","type":"address"},{"internalType":"Currency","name":"wethToken","type":"address"}],"internalType":"struct DTokenRegistry.GroupCore","name":"core","type":"tuple"}],"internalType":"struct GroupKey","name":"groupKey","type":"tuple"},{"components":[{"internalType":"uint8","name":"operationType","type":"uint8"},{"internalType":"uint256","name":"baseIn","type":"uint256"},{"internalType":"uint24","name":"slippage","type":"uint24"},{"internalType":"address","name":"paymentToken","type":"address"},{"internalType":"bytes","name":"hookData","type":"bytes"}],"internalType":"struct DProtocol.MintParams","name":"params","type":"tuple"}],"name":"mintToken","outputs":[{"components":[{"internalType":"uint256","name":"ytMinted","type":"uint256"},{"internalType":"uint256","name":"vtMinted","type":"uint256"}],"internalType":"struct DProtocol.MintResult","name":"result","type":"tuple"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"tokenAddress","type":"address"},{"internalType":"uint256","name":"tokenAmount","type":"uint256"}],"name":"recoverERC20","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"recoversEther","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"components":[{"internalType":"Currency","name":"aToken","type":"address"},{"internalType":"Currency","name":"xToken","type":"address"},{"internalType":"Currency","name":"baseToken","type":"address"},{"internalType":"Currency","name":"yieldBearingToken","type":"address"},{"internalType":"Currency","name":"wethToken","type":"address"}],"internalType":"struct DTokenRegistry.GroupCore","name":"core","type":"tuple"}],"internalType":"struct GroupKey","name":"groupKey","type":"tuple"},{"components":[{"internalType":"uint8","name":"operationType","type":"uint8"},{"internalType":"uint256","name":"baseOut","type":"uint256"},{"internalType":"uint24","name":"slippage","type":"uint24"},{"internalType":"address","name":"desiredCollateral","type":"address"},{"internalType":"bytes","name":"hookData","type":"bytes"}],"internalType":"struct DProtocol.RedeemParams","name":"params","type":"tuple"}],"name":"redeemToken","outputs":[{"internalType":"uint256","name":"baseOut","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"},{"internalType":"address","name":"","type":"address"}],"name":"renounceRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"revokeRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bool","name":"value","type":"bool"}],"name":"setGlobalMintFlag","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bool","name":"value","type":"bool"}],"name":"setGlobalRedeemFlag","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"GroupId","name":"groupId","type":"bytes32"},{"internalType":"bool","name":"value","type":"bool"}],"name":"setGroupMintFlag","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"GroupId","name":"groupId","type":"bytes32"},{"internalType":"bool","name":"value","type":"bool"}],"name":"setGroupRedeemFlag","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"GroupId","name":"groupId","type":"bytes32"},{"internalType":"bool","name":"value","type":"bool"}],"name":"setGroupStabilityFlag","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"GroupId","name":"groupId","type":"bytes32"},{"internalType":"address","name":"token","type":"address"}],"name":"settleOwedFees","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"GroupId","name":"groupId","type":"bytes32"}],"name":"stabilityRatio","outputs":[{"internalType":"uint96","name":"","type":"uint96"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"stateMutability":"payable","type":"receive"}]
Deployed Bytecode
0x6080806040526004361015610027575b503615610025576339218f3b60e01b5f5260045ffd5b005b5f905f3560e01c90816301ffc9a7146114df575080630587bcf3146114a35780630795a7ba1461140757806309ae48411461128b578063248a9ca3146112585780632f2ff15d1461119f57806336568abe14611140578063386d81fa146110d8578063487523f414611088578063795c1af9146110385780638980f11f14610f975780638d390d3114610f6057806391d1485414610f17578063942a13ef14610ee2578063a217fddf14610ec8578063a935508714610e81578063b06576e814610e51578063bb5e558c14610e1d578063c4d66de814610c14578063cccc6f4c14610bf0578063d1d67e1814610bba578063d547741f14610b00578063e3dab550146102a3578063eb9915a714610280578063f0e8cb1614610219578063f9de6313146101bd578063fa7f0c4e1461018e5763fc6f94680361000f573461018b578060031936011261018b5760ce546040516001600160a01b039091168152602090f35b80fd5b503461018b5760206101b06101a23661154c565b906101ab6124aa565b6121ed565b6001609755604051908152f35b503461018b57604036600319011261018b576004356101da6115cb565b6101e2612c81565b811561020a5781835260cd6020526001196040842054161790825260cd602052604082205580f35b633960608d60e11b8352600483fd5b503461018b57604036600319011261018b57604061023561158b565b91600435815260cb602052209060018060a01b03165f52602052608060405f208054906001810154906003600282015491015491604051938452602084015260408301526060820152f35b503461018b578060031936011261018b576020600160cc54166040519015158152f35b503461063a57604036600319011261063a576102bd6115a1565b90602435916102ca6124aa565b6102d2612c81565b6001600160a01b03168015610aed57825f5260c960205260405f2060405190635ab3ea5d60e11b82528460048301525f82602481865afa918215610ae2575f926108ee575b508151516001600160a01b0316156106fa576060820180515180156108db57600d83019182545f845580610888575b505f5b8281106107c9575050835180519092506001600160a01b031615905080156107b4575b801561079f575b801561078a575b61066357805160208201516001600160a01b03918216911681811492918315610773575b831561075c575b508215610745575b821561072e575b50811561070d575b506106fa5760208201805180519193916001600160a01b0316159081156106e4575b81156106ce575b81156106b8575b81156106a2575b811561068c575b8115610676575b50610663578051805183546001600160a01b03199081166001600160a01b0392831617855560208084015160018781018054851692861692909217909155604080860151600289018054861691871691909117905560608087015160038a018054871691881691909117905560809687015160048a01805487169188169190911790559851805160058a018054871691881691909117905592830151600689018054861691871691909117905582810151600789018054861691871691909117905597820151600888018054851691861691909117905581850151600988018054851691861691909117905560a082810151600a89018054861691871691909117905560c090920151600b88018054851691861691909117905596850151600c870155840151600f8601559190920151600e840180549092169216919091179055436010820155601101805460ff19169091179055827f67795b7df97de6699e0f17226d6111be2ddc0ebe76fb110c2e8ab6df9312579f5f80a2825f5260c960205260ff601160405f2001541615610650575f83815260c960205260409020600801546001600160a01b031692831561063e57833b1561063a5760405191630e3dab5560e41b8352600483015260248201525f8160448183875af19081610625575b5061061d573d8091604051936308c99ae160e21b8552600485015260406024850152816044850152606484013e601f801991011660640190fd5b600160975580f35b6106329192505f906116a7565b5f905f6105e3565b5f80fd5b6308c99ae160e21b5f5260045260245ffd5b826337e1342d60e21b5f5260045260245ffd5b85635dbad7a160e01b5f5260045260245ffd5b60c001516001600160a01b03161590505f610401565b60a08101516001600160a01b03161591506103fa565b60808101516001600160a01b03161591506103f3565b60608101516001600160a01b03161591506103ec565b60408101516001600160a01b03161591506103e5565b60208101516001600160a01b03161591506103de565b84635dbad7a160e01b5f5260045260245ffd5b60408101516060909101516001600160a01b0390811691161490505f6103bc565b60608201516001600160a01b03161491505f6103b4565b60408201516001600160a01b0316811492506103ad565b60608301516001600160a01b03161492505f6103a5565b60408301516001600160a01b03168114935061039e565b5060608101516001600160a01b03161561037a565b5060408101516001600160a01b031615610373565b5060208101516001600160a01b03161561036c565b6107d481835161403a565b5190845491680100000000000000008310156108745760018301808755831015610860575f86815260209081902082516003909502018054918301516001600160a81b03199092166001600160a01b03959095169490941760a09190911b60ff60a01b161783556040810151600184810191909155606091909101516002939093019290925501610349565b634e487b7160e01b5f52603260045260245ffd5b634e487b7160e01b5f52604160045260245ffd5b806003029060038204036108c757835f5260205f20908101905b8181106108af5750610346565b805f600392555f60018201555f6002820155016108a2565b634e487b7160e01b5f52601160045260245ffd5b8663f4ac45b160e01b5f5260045260245ffd5b9091503d805f833e61090081836116a7565b81019060208183031261063a578051906001600160401b03821161063a570180820391610200831261063a5760405192610939846115e9565b60a0811261063a5760e09060405161095081611604565b61095985611789565b815261096760208601611789565b602082015261097860408601611789565b604082015261098960608601611789565b606082015261099a60808601611789565b60808201528552609f19011261063a576040516109b68161161f565b6109c260a08401611789565b81526109d060c08401611789565b60208201526109e160e08401611789565b60408201526109f36101008401611789565b6060820152610a056101208401611789565b6080820152610a176101408401611789565b60a0820152610a296101608401611789565b60c0820152602084015261018082015160408401526101a08201516001600160401b03811161063a57820181601f8201121561063a578051610a6a81612587565b92610a7860405194856116a7565b81845260208085019260071b8401019281841161063a57602001915b838310610ac8575050505060608301526101e090610ab56101c08201611789565b6080840152015160a0820152905f610317565b6020608091610ad7848661179d565b815201920191610a94565b6040513d5f823e3d90fd5b826324df413360e21b5f5260045260245ffd5b3461063a57604036600319011261063a57600435610b1c61158b565b90610b3b610b36825f526065602052600160405f20015490565b612d79565b805f52606560205260405f2060018060a01b0383165f5260205260ff60405f205416610b6357005b5f8181526065602090815260408083206001600160a01b0395909516808452949091528120805460ff19169055339291907ff6391f5c32d9c69d2a47ea670b442974b53935d1edc7fd64eb21e047a839171b9080a4005b3461063a57602036600319011261063a5760206001600160601b0360a0610be260043561259e565b0151603b1c16604051908152f35b3461063a575f36600319011261063a576020600160cc54811c166040519015158152f35b3461063a57602036600319011261063a57610c2d6115a1565b5f549060ff8260081c161591828093610e10575b8015610df9575b15610d9d5760ff1981166001175f5582610d8c575b506001600160a01b03168015610d7d57610c8f60ff5f5460081c16610c8181612e23565b610c8a81612e23565b612e23565b60016097555f8181527fffdfc1249c027f9191656349feb0761381bb32c9f557e01f419fd08754bf5a1b602052604090205460ff1615610d19575b6001600160601b0360a01b60ce54161760ce55610ce357005b61ff00195f54165f557f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb3847402498602060405160018152a1005b5f8181527fffdfc1249c027f9191656349feb0761381bb32c9f557e01f419fd08754bf5a1b60205260408120805460ff19166001179055339082907f2f8788117e7eff1d82e926ec794901d17c78024a50270940304540a733656f0d8180a4610cca565b63d92e233d60e01b5f5260045ffd5b61ffff1916610101175f5582610c5d565b60405162461bcd60e51b815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201526d191e481a5b9a5d1a585b1a5e995960921b6064820152608490fd5b50303b158015610c485750600160ff821614610c48565b50600160ff821610610c41565b3461063a57602036600319011261063a57610e366115da565b610e3e612c81565b60021960cc54169060011b1760cc555f80f35b3461063a57602036600319011261063a57610e6a6115da565b610e72612c81565b60011960cc54161760cc555f80f35b3461063a57602036600319011261063a576004358015610eb9575f5260cd6020526020600160405f205460021c166040519015158152f35b633960608d60e11b5f5260045ffd5b3461063a575f36600319011261063a5760206040515f8152f35b3461063a57602036600319011261063a576004358015610eb9575f5260cd6020526020600160405f2054166040519015158152f35b3461063a57604036600319011261063a57610f3061158b565b6004355f52606560205260405f209060018060a01b03165f52602052602060ff60405f2054166040519015158152f35b3461063a57602036600319011261063a576004358015610eb9575f5260cd6020526020600160405f2054811c166040519015158152f35b3461063a57604036600319011261063a57610fb06115a1565b60243590610fbc6124aa565b610fc4612c81565b6001600160a01b03168015610d7d57610fdd308261278c565b82116110265760ce54610ffb9083906001600160a01b031683612db8565b7f505b28e6941631badc363841ecbf8e1214b9379c643936458e87be718e1579995f80a36001609755005b63112fed8b60e31b5f5260045260245ffd5b3461063a57604036600319011261063a576004356110546115cb565b61105c612c81565b8115610eb957815f5260cd60205260041960405f205416915f5260cd60205260021b1760405f20555f80f35b3461063a57604036600319011261063a576004356110a46115cb565b6110ac612c81565b8115610eb957815f5260cd60205260021960405f205416915f5260cd60205260011b1760405f20555f80f35b3461063a57606036600319011261063a576110f161158b565b6044356001600160a01b038116919082900361063a576004355f90815260ca602090815260408083206001600160a01b0394851684528252808320949093168252928352819020549051908152f35b3461063a57604036600319011261063a5761115961158b565b5060405162461bcd60e51b815260206004820152601860248201527f526f6c65732063616e27742062652072656e6f756e63656400000000000000006044820152606490fd5b3461063a57604036600319011261063a576004356111bb61158b565b906111d5610b36825f526065602052600160405f20015490565b805f52606560205260405f2060018060a01b0383165f5260205260ff60405f205416156111fe57005b5f8181526065602090815260408083206001600160a01b0395909516808452949091528120805460ff19166001179055339291907f2f8788117e7eff1d82e926ec794901d17c78024a50270940304540a733656f0d9080a4005b3461063a57602036600319011261063a5760206112836004355f526065602052600160405f20015490565b604051908152f35b3461063a57604036600319011261063a576004356112a761158b565b6112af6124aa565b815f5260c960205260ff601160405f20015416156113f4575f82815260c960205260409020600e01546001600160a01b031690338290036113e5576001600160a01b03169182156113d3575f81815260ca602090815260408083206001600160a01b038681168552908352818420908716845290915290205480156113c0575f82815260ca602090815260408083206001600160a01b038781168552908352818420908816845290915281205580611367308661278c565b106113a95760208161139b7fd3093803bade32f00a1623cb5300e4caed82036accaa6aafadc9f55827a83c08938688612db8565b604051908152a46001609755005b8382630c29241b60e21b5f5260045260245260445ffd5b506393aea85b60e01b5f5260045260245ffd5b6383cf4a2b60e01b5f5260045260245ffd5b6339218f3b60e01b5f5260045ffd5b506337e1342d60e21b5f5260045260245ffd5b3461063a57602036600319011261063a576004356114236124aa565b61142b612c81565b47811161149057807f7bcc4dd1d0cf712ca981d90a999e6ea7d3da3e11844d63b289bd1c15c2ec5e8d5f80a25f8080808460018060a01b0360ce54165af16114716121be565b501561147e576001609755005b631b1eb84f60e31b5f5260045260245ffd5b63112fed8b60e31b5f525f60045260245ffd5b60406114c86114b13661154c565b906114ba6116c8565b506114c36124aa565b611aff565b600160975560208251918051835201516020820152f35b3461063a57602036600319011261063a576004359063ffffffff60e01b821680920361063a57602091632e2ecea560e01b8114908115611521575b5015158152f35b637965db0b60e01b81149150811561153b575b508361151a565b6301ffc9a760e01b14905083611534565b90600319820160c0811261063a5760a01361063a5760049160a435906001600160401b03821161063a5760a090829003600319011261063a5760040190565b602435906001600160a01b038216820361063a57565b600435906001600160a01b038216820361063a57565b35906001600160a01b038216820361063a57565b60243590811515820361063a57565b60043590811515820361063a57565b60c081019081106001600160401b0382111761087457604052565b60a081019081106001600160401b0382111761087457604052565b60e081019081106001600160401b0382111761087457604052565b604081019081106001600160401b0382111761087457604052565b608081019081106001600160401b0382111761087457604052565b61010081019081106001600160401b0382111761087457604052565b606081019081106001600160401b0382111761087457604052565b90601f801991011681019081106001600160401b0382111761087457604052565b604051906116d58261163a565b5f6020838281520152565b91908260a091031261063a5760405191602083018381106001600160401b03821117610874576040528261176060806040519361171c85611604565b611725816115b7565b8552611733602082016115b7565b6020860152611744604082016115b7565b6040860152611755606082016115b7565b6060860152016115b7565b608083015252565b356001600160a01b038116810361063a5790565b5190811515820361063a57565b51906001600160a01b038216820361063a57565b919082608091031261063a576040516117b581611655565b80926117c081611789565b8252602081015160ff8116810361063a5760609182916020850152604081015160408501520151910152565b919060a08382031261063a5761180e9060206118078561177c565b940161179d565b90565b90602080835192838152019201905f5b81811061182e5750505090565b909192602060808261186c60019488516060809160018060a01b03815116845260ff6020820151166020850152604081015160408501520151910152565b019401929101611821565b3560ff8116810361063a5790565b3562ffffff8116810361063a5790565b903590601e198136030182121561063a57018035906001600160401b03821161063a5760200191813603831361063a57565b6001600160401b03811161087457601f01601f191660200190565b9291926118ee826118c7565b916118fc60405193846116a7565b82948184528183011161063a578281602093845f960137010152565b5f5b8381106119295750505f910152565b818101518382015260200161191a565b9060209161195281518092818552858086019101611918565b601f01601f1916010190565b80516001600160a01b03908116835260208083015182169084015260408083015182169084015260608083015182169084015260809182015116910152565b80516001600160a01b03908116835260208083015182169084015260408083015182169084015260608083015182169084015260808083015182169084015260a08083015182169084015260c09182015116910152565b9060cc825260208201526060604082015281516060820152610380610100611a40602080860151610340608087015280516103a0870152015160406103c08601526103e0850190611939565b936040810151611a5460a08601825161195e565b611a67602082015161014087019061199d565b6040818101516102208701526060808301516102408801526080928301516001600160a01b0390811661026089015284820151610280890152928401516102a088015260a084015162ffffff166102c088015260c084015183166102e088015260e08401518051909316610300880152602083015160ff16610320880152908201516103408701520151610360850152015191015290565b611b0d611b129136906116e0565b612500565b611b1b8161259e565b6060808201515f947351ce2efdeb2d8bea8f060c6a870b6744e37b5c0f939092602084013590840160a0611b7d6001600160a01b03611b5984611768565b1695604051968792839263196896eb60e11b845285600485015260a4840190611811565b908660248401526044830152600160648301523460848301520381895af4938415610ae2575f905f9561218a575b501561217b575f19821480612161575b61213c575b5080611be45750505050505060405190611bd98261163a565b808252602082015290565b611c7093611c669260405192611bf98461163a565b83525f6020840152611c0a82611877565b9462ffffff611c29611c1e60408601611885565b946080810190611895565b94909560ff60405199611c3b8b611670565b168952602089015216604087015233606087015260808601528760a086015260c085015236916118e2565b60e08201526128db565b90803b1561063a575f604051809263469c699160e11b82528180611c988789600484016119f4565b03915af48015610ae257612127575b50611cb06116c8565b50611cba81612e83565b6040820151608001516001600160a01b031692908315159081612110575b611ce6606085015185613201565b611cf4879993949299612e83565b9860405199611d028b61163a565b5f808c526020808d019190915260408a01510151606081015160c0909101516001600160a01b03908116939116919082158015612108575b6120f457611d48908b6148d5565b60208b0151516040808d015151015160ff909116906001600160a01b0316916060611d74863086614a0a565b9101928584518084106120de575b5050505080155f14611eee5750518a5160405163095a7a1b60e21b81526004810191909152602481018290526001600160a01b03929092166044830152909250906020908390606490829089905af1918215611ee3578592611eaf575b508115611e9557508692611e457fac903ad8fe2e7a6d229683df0f465af874089964b0ea74e096f95d106cd333b79360609360208e01525b60208d0180519097508015611e8d575b60808c015260408b015151606001516001600160a01b03168b613dcf565b8a5194516040805133815260208101979097528601526001600160a01b03908116951693a4611e75575b50505090565b338314611e6f57611e8592613f41565b5f8080611e6f565b508c51611e27565b88516323dc081560e01b8652600452602452604452606483fd5b9091506020813d602011611edb575b81611ecb602093836116a7565b8101031261063a5751905f611ddf565b3d9150611ebe565b6040513d87823e3d90fd5b9192916001036120ca57518a51604051637ef0584160e11b81526004810191909152602481018290523060448201529291602090849060649082908b905af19283156120bf57879361208b575b50821561206f575060408a01515151869392916020916001600160a01b0316611f65853083614a0a565b9085858310612054575b505050604460405180968193636e553f6560e01b835286600484015260018060a01b0316968760248401525af192831561204957908995949392918793612009575b509282611e45927fac903ad8fe2e7a6d229683df0f465af874089964b0ea74e096f95d106cd333b796946060967f491ac6e8661dcfb6555d94e6fd4b55961faf240fb9fad5774ba6a93532f330e38b80a48d52611e17565b919395509391506020813d602011612041575b81612029602093836116a7565b8101031261063a575188949193919290916060611fb1565b3d915061201c565b6040513d88823e3d90fd5b6120616120679387613dc2565b91614a62565b5f8085611f6f565b8a516323dc081560e01b88526004526024526044829052606486fd5b9092506020813d6020116120b7575b816120a7602093836116a7565b8101031261063a5751915f611f3b565b3d915061209a565b6040513d89823e3d90fd5b8a5163a78a5d1160e01b8852600452602487fd5b6120eb9361206191613dc2565b5f808581611d82565b8a516383cf4a2b60e01b8852600452602487fd5b508315611d3a565b338514611cd857612122848487613091565b611cd8565b6121349193505f906116a7565b5f915f611ca7565b61215b91506001600160a01b039061215390611768565b16339061278c565b5f611bc0565b506001600160a01b0361217382611768565b161515611bbb565b63436cb04160e01b5f5260045ffd5b90506121af91945060a03d60a0116121b7575b6121a781836116a7565b8101906117ec565b93905f611bab565b503d61219d565b3d156121e8573d906121cf826118c7565b916121dd60405193846116a7565b82523d5f602084013e565b606090565b611b0d6121fe9193929336906116e0565b916122088361259e565b927351ce2efdeb2d8bea8f060c6a870b6744e37b5c0f9160608501519060208101359560a0612266600180831b0361224260608601611768565b1694604051958692839263196896eb60e11b845285600485015260a4840190611811565b908b602484015260448301525f60648301525f60848301520381885af4928315610ae2575f905f94612486575b501561217b575f198714612434575b866122ae575050505050565b95611c669161232994959697604051926122c78461163a565b83525f60208401526122d882611877565b9462ffffff6122ec611c1e60408601611885565b94909560ff604051996122fe8b611670565b168952602089015216604087015233606087015260808601528660a086015260c085015236916118e2565b91803b1561063a575f604051809263469c699160e11b825281806123518888600484016119f4565b03915af48015610ae257612424575b5061236a82612e83565b60408301516080015190926001600160a01b03909116918215158061240d575b6123aa6123b08661239f6060870151876138fc565b828996929396613dcf565b866141b8565b604080513381526020810183905291976001600160a01b0390811693169185917fdd8b8dbb53fec7033579b7466dc8fd28b088f5b31bca605ca8fa24a570e366ca91a46123fd5750505090565b338314611e6f57611e85926144e7565b33841461238a5761241f83838661404e565b61238a565b5f61242e916116a7565b5f612360565b9550600260ff61244383611877565b1603612469578551602001516124639033906001600160a01b031661278c565b956122a2565b602086015160c001516124639033906001600160a01b031661278c565b90506124a291935060a03d60a0116121b7576121a781836116a7565b92905f612293565b6002609754146124bb576002609755565b60405162461bcd60e51b815260206004820152601f60248201527f5265656e7472616e637947756172643a207265656e7472616e742063616c6c006044820152606490fd5b6040516125126020820180935161195e565b60a0815261252160c0826116a7565b51902090565b6040519061253482611604565b5f6080838281528260208201528260408201528260608201520152565b6040519061255e8261161f565b5f60c0838281528260208201528260408201528260608201528260808201528260a08201520152565b6001600160401b0381116108745760051b60200190565b905f60a06040516125ae816115e9565b6125b6612527565b81526125c0612551565b60208201528260408201526060808201528260808201520152815f5260c960205260405f209160ff6011840154161561277a5750604051612600816115e9565b60405161260c81611604565b83546001600160a01b03908116825260018501548116602083015260028501548116604080840191909152600386015482166060840152600486015490911660808301529082525161265d8161161f565b60058401546001600160a01b0390811682526006850154811660208084019190915260078601548216604080850191909152600887015483166060850152600987015483166080850152600a870154831660a0850152600b87015490921660c0840152830191909152600c84015490820152600d830180549093906126e181612587565b906126ef60405192836116a7565b80825260208201955f5260205f20955f905b828210612730575050506060830152600e8101546001600160a01b03166080830152600f015460a08201529150565b6003602060019260405161274381611655565b60ff8c54868060a01b038116835260a01c1683820152848c0154604082015260028c01546060820152815201980191019096612701565b6337e1342d60e21b5f5260045260245ffd5b6001600160a01b03168061279f57503190565b6040516370a0823160e01b81526001600160a01b039092166004830152602090829060249082905afa908115610ae2575f916127d9575090565b90506020813d602011612800575b816127f4602093836116a7565b8101031261063a575190565b3d91506127e7565b604051906128158261163a565b60606020835f81520152565b6040519061282e82611655565b5f6060838281528260208201528260408201520152565b6040519061012082018281106001600160401b03821117610874576040525f61010083828152612873612808565b602082015260405161288481611604565b61288c612527565b8152612896612551565b602082015283604082015283606082015283608082015260408201528260608201528260808201528260a08201528260c08201526128d2612821565b60e08201520152565b6128e3612845565b506060810180519092906001600160a01b031615612c6a57602082019283518051159081612c5d575b50612c46576040830162ffffff8151166103e88111612c26575061292e612845565b60a0850180518252865151606083015295516020015160808083019190915292516001600160a01b031660c08201529184015160e08301528451905162ffffff16906103e88211612c0b575060a082015260c0830192835180519060208101519060a0810151604082015191608060018060a01b039101511692604051946129b586611604565b855260208501526040840152606083015260808201526040830152819360ff8251169080519160a083016001600160601b038151603b1c169051936060602060018060a01b039201510151169060208a5160246040518095819363f3c4799760e01b835260048301525afa5f9281612bd7575b50612a41578951637055368360e11b5f5260045260245ffd5b81999293949596979899911115612bc3578290806101008901526001600160601b0386609b1c16115f14612bac57612a809151604085510151906145fd565b915b60a08151015192612a938194615292565b908151151580612ba0575b612b92575b5060200151612b85575b604090510151928360f01c612ac0612808565b5060038311612b72576127108111612b605750612710620fffff841611612b48579062ffff0066ffffff00000000926001600160601b0360501b90604b1c169460e81c16179160201b16171791612b16836152c2565b15612b395760e0602092015160405193612b2f8561163a565b8452828401520152565b6338bfed7360e11b5f5260045ffd5b62ffffff83634d72eb2f60e11b5f521660045260245ffd5b634d72eb2f60e11b5f5260045260245ffd5b82631ac6d2b760e21b5f5260045260245ffd5b6220000090921791612aad565b628000001793506020612aa3565b50602082015115612a9e565b612bbd915160408551015190614597565b91612a82565b505163ef6f777560e01b5f5260045260245ffd5b9092506020813d602011612c03575b81612bf3602093836116a7565b8101031261063a5751915f612a28565b3d9150612be6565b63751419d760e11b5f526004526024526103e860445260645ffd5b60a085015163751419d760e11b5f526004526024526103e860445260645ffd5b60a0830151630180267160e01b5f5260045260245ffd5b602091500151155f61290c565b60a08201516383cf4a2b60e01b5f5260045260245ffd5b335f9081527fffdfc1249c027f9191656349feb0761381bb32c9f557e01f419fd08754bf5a1b602052604090205460ff1615612cb957565b612d75612cc533614b41565b612d556011612cd35f614c27565b9260376040519485927f416363657373436f6e74726f6c3a206163636f756e74200000000000000000006020850152612d158151809260208688019101611918565b83017001034b99036b4b9b9b4b733903937b6329607d1b83820152612d44825180936020604885019101611918565b01010301601f1981018352826116a7565b60405162461bcd60e51b8152602060048201529182916024830190611939565b0390fd5b5f81815260656020908152604080832033845290915290205460ff1615612d9d5750565b612d7590612d556011612cd3612db233614b41565b93614c27565b9091906001600160a01b031680612dd857630c5d6fad60e21b5f5260045ffd5b60405163a9059cbb60e01b60208201526001600160a01b0390931660248401526044830191909152612e219190612e1c82606481015b03601f1981018452836116a7565b614cc9565b565b15612e2a57565b60405162461bcd60e51b815260206004820152602b60248201527f496e697469616c697a61626c653a20636f6e7472616374206973206e6f74206960448201526a6e697469616c697a696e6760a81b6064820152608490fd5b60c081019060018060a01b0382511690338203612ea9575050516001600160a01b031690565b516364e9666360e11b5f526004526024523360445260645ffd5b909161180e949260018060a01b0316825260208201526080604082015282516080820152610100612f1860208086015161034060a086015280516103c0860152015160406103e0850152610400840190611939565b936040810151612f2c60c08501825161195e565b612f3f602082015161016086019061199d565b6040818101516102408601526060808301516102608701526080928301516001600160a01b03908116610280880152848201516102a0880152928401516102c087015260a084015162ffffff166102e087015260c0840151831661030087015260e08401518051909316610320870152602083015160ff1661034087015290820151610360860152015161038084015201516103a08201526060818403910152611939565b919082018092116108c757565b51906001600160e01b03198216820361063a57565b81601f8201121561063a57805161301c816118c7565b9261302a60405194856116a7565b8184526020828401011161063a5761180e9160208085019101611918565b909160608284031261063a5761305d82612ff1565b9260208301516001600160401b03811161063a5760409161307f918501613006565b92015162ffffff8116810361063a5790565b909160016130ab8260408061ffff920151015160201c1690565b16156131fc5760208101916130fd828451926130f86130cf6020860151955161531f565b97612e0e628000008a16159660405195869363e3b7f38160e01b60208601523360248601612ec3565b61464d565b805180156131f45760801180156131e8575b6131d4578060208061312693518301019101613048565b9095916001600160e01b031916631c480c7f60e01b016131c057613149906146c9565b929091826131b1575b82613190575b5050613180575b5060400151608001516001600160a01b0316613179575050565b5160200152565b61318a90826146f6565b5f61315f565b81159250906131a2575b505f80613158565b6220000091501615155f61319a565b62ffffff841615159250613152565b8351632014ceb360e21b5f5260045260245ffd5b8251632014ceb360e21b5f5260045260245ffd5b5080516104401061310f565b505050505050565b505050565b9161320a612821565b5081156138e857825160ff602085015151169061322685612e83565b91806138af57505f5260cb60205260405f209060018060a01b03165f5260205260405f204290555b60e083018051519091906001600160a01b0316156138a7575b61326f612821565b50835160408086015151908101516060909101518451516001600160a01b0390811695928116939116919083158015613896575b6113d35760408881015160208101515184515191516060015183516341976e0960e01b81526001600160a01b03938416600482015291831699921692816024818c5afa928315610ae2575f915f94613872575b50604080516341976e0960e01b81526001600160a01b03909216600483015290998a9060249082905afa988915610ae2575f905f9a61383e575b508315918215613835575b821561382c575b508115613823575b5061380f5760e08a01516020015160ff9081161698670de0b6b3a76400008102818104670de0b6b3a764000014821517156108c75760606133b36133ac8c8f948f6133a26133a79261339c8c936141aa565b9061418c565b61400a565b61418c565b80936148d5565b01519081156137fc57818111156137f457670de0b6b3a7640000820291808304670de0b6b3a7640000036108c7576133f76133fe92670de0b6b3a76400009461418c565b909361400a565b50505b80156137e157613411908b6153fa565b98908015806137d8575b6137c557908a846134318e9695948d5190612fe4565b8096670de0b6b3a764000061345c61344d856133a7898761400a565b613456876141aa565b9061400a565b04905f968015155f146137885791816134878261348c9897956134808b9896612e83565b3091615533565b6155af565b9a5f96606484116136fd575b50505050506065810290808204606514901517156108c7576064900487116136eb575060408501958651806136c4575b50865180158015906136b7575b613663575b506134e6865182613dc2565b60408981015151908101516060909101519192915f916020916001600160a01b039182169116613517823083614a0a565b908287831061364e575b505050602460405180948193630ea598cb60e41b83528860048401525af15f918161361a575b5061356057895163381f209760e21b5f5260045260245ffd5b989394959697891561360657916135806135859261358a95945190613dc2565b613dc2565b612fe4565b6064811161359b575b505050929190565b6064810290808204606414901517156108c7576127106135c7910480936135c184612e83565b90612db8565b6001600160a01b03906135d990612e83565b167fbb28353e4598c3b9199101a66e0989549b659a59a54d2c27fbb183f1932c8e6d5f80a35f8080613593565b8451637fbbb20d60e11b5f5260045260245ffd5b9091506020813d602011613646575b81613636602093836116a7565b8101031261063a5751905f613547565b3d9150613629565b61206161365b9389613dc2565b5f8082613521565b60408981015160200151608001517f108516ddcf5ba43cea6bb2cd5ff6d59ac196c1c86ccb9178332b9dd72d1ca56191906001600160a01b03908116169260208a015182519182526020820152a25f6134da565b50602087015115156134d5565b604089015160200151608001516136e591906001600160a01b031685612db8565b5f6134c8565b6319b811a560e31b5f5260045260245ffd5b515160408e01515160600151959750939492936001600160a01b03908116939192911694670de0b6b3a7640000850290858204670de0b6b3a764000014861517156108c7576133a26133a79261339c613755966141aa565b9161232883029280840461232814901517156108c75761377d9361271083159404928d614ea7565b915f80808080613498565b50509394505050508034106137b2579061348c8b82858f95806137ac8a9234613dc2565b976155af565b8463ea9dce7560e01b5f5260045260245ffd5b84630180267160e01b5f5260045260245ffd5b5089511561341b565b83630180267160e01b5f5260045260245ffd5b915050613401565b85630180267160e01b5f5260045260245ffd5b8951633203e4dd60e21b5f5260045260245ffd5b9050155f61334a565b1591505f613342565b8a15925061333b565b905061386391995060403d60401161386b575b61385b81836116a7565b81019061416f565b98905f613330565b503d613851565b61388e91945060408093503d841161386b5761385b81836116a7565b9390916132f6565b506001600160a01b038316156132a3565b349250613267565b6001146138be575b505061324e565b5f5260cb60205260405f209060018060a01b03165f5260205260405f206001429101555f806138b7565b8251630180267160e01b5f5260045260245ffd5b9190613906612821565b5080156138e857825190602084019160ff835151169061392586612e83565b9160028103613d8957505f5260cb60205260405f209060018060a01b03165f5260205260405f206002429101555b61396761395f85612e83565b8560cb61483e565b90613d11575b5061397784612e83565b90613980612821565b506040858101515101516001600160a01b031615613ce95760606139a660ff92876148d5565b0151925151604086015160200151606001516001600160a01b03169391166001198101613b625750604085015151602001516001600160a01b03168015613b4e576139f682602094309084615533565b613a01843083614a0a565b9084838310613b39575b505050608485515f6040519586948593635435d29560e11b8552600485015282602485015260448401523060648401525af1908115610ae2575f91613b05575b50613a5a613a60915b84615339565b836153fa565b604084015151606001519193909291613a83906001600160a01b0316309061278c565b6040840190613a93868351612fe4565b11613af15790613aba915180613abf575b506040015151606001516001600160a01b031690565b929190565b604082015180516060015160209091015160800151613aeb92916001600160a01b039182169116612db8565b5f613aa4565b505163ea9dce7560e01b5f5260045260245ffd5b90506020813d602011613b31575b81613b20602093836116a7565b8101031261063a5751613a5a613a4b565b3d9150613b13565b612061613b469385613dc2565b5f8084613a0b565b85516383cf4a2b60e01b5f5260045260245ffd5b91925090600303613cfd5760408401516020015160c001516001600160a01b0316918215613ce957604051635d043b2960e11b815260048101929092523060248301526001600160a01b0316604482015290602090829060649082905f905af1908115610ae2575f91613cb7575b508015613ca3576040830151602080820151606001519151516001600160a01b0392831693919216613c03843083614a0a565b9084838310613c8e575b505050608485515f6040519586948593635435d29560e11b8552600485015260248401528160448401523060648401525af1908115610ae2575f91613c5a575b50613a5a613a6091613a54565b90506020813d602011613c86575b81613c75602093836116a7565b8101031261063a5751613a5a613c4d565b3d9150613c68565b612061613c9b9385613dc2565b5f8084613c0d565b825163ea9dce7560e01b5f5260045260245ffd5b90506020813d602011613ce1575b81613cd2602093836116a7565b8101031261063a57515f613bd0565b3d9150613cc5565b84516383cf4a2b60e01b5f5260045260245ffd5b835163a78a5d1160e01b5f5260045260245ffd5b62ffffff80613d2185515161531f565b1691160162ffffff81116108c75782515190612710620fffff821611613d715766ffffff000000009060201b169066ffffff00000000191617613d63816152c2565b15612b39578251525f61396d565b62ffffff90634d72eb2f60e11b5f521660045260245ffd5b600314613d98575b5050613953565b5f5260cb60205260405f209060018060a01b03165f5260205260405f206003429101555f80613d91565b919082039182116108c757565b91805115801590613f00575b613e3b575b519182613dec57505050565b80516040918201516020908101516080015192519485526001600160a01b0393841694939092169290917fad7560be13f82820274d285adf47dbc099a4c411d68af52bfd62ef13f0a2ba2391a4565b6040830151608001516001600160a01b03166020820190815180151580613eee575b613e6a575b505050613de0565b85515f5260ca60205260405f2060018060a01b0383165f5260205260405f2060018060a01b0386165f52602052613ea660405f20918254612fe4565b90557f785f14a7745852cfc4c745b3453427cb39e6d606d6431e8d8f5dcebe459d6e286040865192855194518251958652602086015260018060a01b031693a35f8080613e62565b506001600160a01b0382161515613e5d565b5060408101511515613ddb565b91909160408184031261063a57613f2381612ff1565b9260208201516001600160401b03811161063a5761180e9201613006565b604083810151015160201c600216156131fc5782613f87916130f86020830194612e0e602087510151604051958693632c7df23760e01b60208601523360248601612ec3565b805180156140045760408110908115613ff8575b506131d45780602080613fb393518301019101613f0d565b92906001600160e01b03191663d3820dc960e01b01613fe55760400151608001516001600160a01b0316613179575050565b51632014ceb360e21b5f5260045260245ffd5b9050610440105f613f9b565b50505050565b818102929181159184041417156108c757565b8051156108605760200190565b8051600110156108605760400190565b80518210156108605760209160051b010190565b909160046140688260408061ffff920151015160201c1690565b16156131fc5760208101916140b5828451926130f861408c6020860151955161531f565b97612e0e628000008a161596604051958693637d7f232760e01b60208601523360248601612ec3565b805180156131f4576080118015614163575b6131d457806020806140de93518301019101613048565b9095916001600160e01b031916638280dcd960e01b016131c057614101906146c9565b92909182614154575b826141345782613190575050613180575060400151608001516001600160a01b0316613179575050565b915062ffffff61414586515161531f565b1662ffffff8416141591613152565b62ffffff84161515925061410a565b508051610440106140c7565b919082604091031261063a5760206141868361177c565b92015190565b8115614196570490565b634e487b7160e01b5f52601260045260245ffd5b604d81116108c757600a0a90565b9080156144d357604082810151519081015160609091015160e0840151516001600160a01b039182169392908216911681146144bf576001600160a01b0383168181146144b05781159162ffffff60a0870151166103e8811161449357604087810151602001515181516341976e0960e01b815260048101959095526001600160a01b0316959084602481895afa958615610ae2575f945f97614469575b5060406024918151928380926341976e0960e01b82528860048301525afa948515610ae2575f915f96614446575b501590811561443d575b508015614435575b801561442d575b6144195760ff6040808a0151015160101c169660ff6142c58a602060e060ff92015101511690565b1697670de0b6b3a76400008302838104670de0b6b3a7640000036108c757604d82116108c7576133a7896133a2899361430195600a0a9061418c565b92612710039161271083116108c75784670de0b6b3a764000061433861271061432e8b976143409961400a565b046134568d6141aa565b04928b614ea7565b9283156143f557156143da5750670de0b6b3a7640000820291808304670de0b6b3a7640000036108c7576143855f95946133a78796946133a2889761339c89986141aa565b965af16143906121be565b501561439f5760808291015290565b60405162461bcd60e51b8152602060048201526013602482015272115512081d1c985b9cd9995c8819985a5b1959606a1b6044820152606490fd5b60809450829690839694506143f0939250612db8565b015290565b838888516348cd2abf60e11b5f5260045260018060a01b031660245260445260645ffd5b8751633203e4dd60e21b5f5260045260245ffd5b50831561429d565b508515614296565b9050155f61428e565b909550614462915060403d60401161386b5761385b81836116a7565b945f614284565b60249197506040955061448890863d881161386b5761385b81836116a7565b959095979150614256565b865163751419d760e11b5f526004526103e860245260445260645ffd5b50509261180e92508391612db8565b8351633500335960e21b5f5260045260245ffd5b5051632976bf1560e21b5f5260045260245ffd5b604083810151015160201c600816156131fc578261452d916130f86020830194612e0e602087510151604051958693634bdd816560e01b60208601523360248601612ec3565b80518015614004576040811090811561458b575b506131d4578060208061455993518301019101613f0d565b92906001600160e01b03191663b4227e9b60e01b01613fe55760400151608001516001600160a01b0316613179575050565b9050610440105f614541565b909160ff16806145ae57505060981c62ffffff1690565b600181036145c357505060681c62ffffff1690565b600281036145d857505060801c62ffffff1690565b6003036145eb575060501c62ffffff1690565b63a78a5d1160e01b5f5260045260245ffd5b909160ff168061461357505060b01c61ffff1690565b6001810361462757505060c01c61ffff1690565b6002810361463b57505060d01c61ffff1690565b6003036145eb575060e01c61ffff1690565b91905f809160208151910182865af1913d9260405193601f19603f82011685016040528085525f602086013e1580614683575050565b61468a5750565b6040805163319d54c360e01b81526001600160a01b03909216600483015260248201523d60448201819052805f606484013e601f801991011660640190fd5b9062100000620fffff831692166146e1578115159190565b906146f0576001906210000090565b5f905f90565b6040810190606061470b604084510151615292565b925101515f6040805161471d8161168c565b8281528260208201520152604051926147358461168c565b61ffff8260201c16845261ffff60406020860195828560101c168752019216825260208101511580614830575b61482157511580614813575b6148045762ffffff620fffff851693511683101590816147f3575b50806147e7575b156147cf57602001916113888351519211613d715762ffffff60381b9060381b169062ffffff60381b191617906147c6826152c2565b15612b39575152565b62ffffff8363071015a560e21b5f521660045260245ffd5b50611388821115614790565b62ffffff915051168211155f614789565b633989c69960e21b5f5260045ffd5b50628000008416151561476e565b6325ecfb7560e11b5f5260045ffd5b506220000085161515614762565b81515f908152602091825260408082206001600160a01b039095168252938252929092209101515160ff16600281148181156148ca575b50614883575b50505f905f90565b6002036148c157545b80151590816148aa575b506148a2575f8061487b565b600190601990565b6201518091506148ba9042613dc2565b105f614896565b6001015461488c565b60039150145f614875565b6148dd612527565b604082015160200151606001519092906001600160a01b0390811616916001600160601b036040808301510151603b1c16906101008101518560ff602084018582825151169410604084015251511680159081156149ff575b50156149f157506001036149dc5790604482614954876040956156ef565b5183519586938492637ac6f8ab60e01b8452600484015260248301525afa8015610ae2575f925f916149a3575b5060208401528183528181111561499c57505b606082015290565b9050614994565b9250506040823d6040116149d4575b816149bf604093836116a7565b8101031261063a57602082519201515f614981565b3d91506149b2565b50505f19808452602084015260608301525090565b925050614994929350615689565b60019150145f614936565b6001600160a01b03169182614a20575050505f90565b604051636eb1769f60e11b81526001600160a01b0392831660048201529116602482015290602090829060449082905afa908115610ae2575f916127d9575090565b6001600160a01b03168015614b2157604051636eb1769f60e11b81523060048201526001600160a01b038316602482015292602084604481855afa938415610ae2575f94614aeb575b50614abc612e1c91612e2195612fe4565b60405163095ea7b360e01b60208201526001600160a01b03909416602485015260448401528260648101612e0e565b93506020843d602011614b19575b81614b06602093836116a7565b8101031261063a57925192614abc614aab565b3d9150614af9565b63220abce160e01b5f5260045ffd5b908151811015610860570160200190565b614b4b602a6118c7565b90614b5960405192836116a7565b602a8252614b67602a6118c7565b6020830190601f19013682378251156108605760309053815160011015610860576078602183015360295b60018111614be65750614ba25790565b606460405162461bcd60e51b815260206004820152602060248201527f537472696e67733a20686578206c656e67746820696e73756666696369656e746044820152fd5b90600f81166010811015610860576f181899199a1a9b1b9c1cb0b131b232b360811b901a614c148385614b30565b5360041c9080156108c7575f1901614b92565b614c3160426118c7565b90614c3f60405192836116a7565b60428252614c4d60426118c7565b6020830190601f19013682378251156108605760309053815160011015610860576078602183015360415b60018111614c885750614ba25790565b90600f81166010811015610860576f181899199a1a9b1b9c1cb0b131b232b360811b901a614cb68385614b30565b5360041c9080156108c7575f1901614c78565b90614d299160018060a01b03165f8060405193614ce76040866116a7565b602085527f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c6564602086015260208151910182855af1614d236121be565b9161582f565b8051908115918215614d97575b505015614d3f57565b60405162461bcd60e51b815260206004820152602a60248201527f5361666545524332303a204552433230206f7065726174696f6e20646964206e6044820152691bdd081cdd58d8d9595960b21b6064820152608490fd5b819250906020918101031261063a576020614db2910161177c565b5f80614d36565b60208183031261063a578051906001600160401b03821161063a57019080601f8301121561063a578151614dec81612587565b92614dfa60405194856116a7565b81845260208085019260051b82010192831161063a57602001905b828210614e225750505090565b8151815260209182019101614e15565b90602080835192838152019201905f5b818110614e4f5750505090565b82516001600160a01b0316845260209384019390920191600101614e42565b9192608093614e9392979695978452602084015260a0604084015260a0830190614e32565b6001600160a01b0390951660608201520152565b94929390604086019060018060a01b03604060208451015101511690811561527e5760054201928342116108c7575151608001516001600160a01b03828116979116918891886150905750505060405190614f036060836116a7565b600282526040366020840137614f188261401d565b526001600160a01b03851680614f2d8361402a565b52604089015151606001516001600160a01b031603615032575f91614f779188604051809681958294637ff36ab560e01b84528a6004850152608060248501526084840190614e32565b90306044840152606483015203925af1908115610ae2575f91615010575b5080515f1981019081116108c757614fac9161403a565b51945b818610614ff457505060407f25f1d03755df23c30e25db2dbd3891e31ce084bdfbfc46f9fe5e446ee5f9b2d491815194855285602086015260018060a01b031693a390565b859250516323dc081560e01b5f5260045260245260445260645ffd5b61502c91503d805f833e61502481836116a7565b810190614db9565b5f614f95565b60405162461bcd60e51b815260206004820152603060248201527f4f6e6c79206e617469766520746f207969656c6442656172696e67546f6b656e60448201526f081cddd85c1cc81cdd5c1c1bdc9d195960821b6064820152608490fd5b9394939192911561516c57915f61510d9592819594604051916150b46060846116a7565b6002835260403660208501378b6150ca8461401d565b526150d48361402a565b526150e0853083614a0a565b9085858310615157575b505050604051968795869485936318cbafe560e01b85528b309260048701614e6e565b03925af1908115610ae2575f9161513d575b5080515f1981019081116108c7576151369161403a565b5194614faf565b61515191503d805f833e61502481836116a7565b5f61511f565b6120616151649387613dc2565b5f80856150ea565b90808814801561526c575b156152105750925f92918361510d95604051906151956060836116a7565b600282526040366020840137818b6151ac8261401d565b526001600160a01b038b16906151c19061402a565b525b6151ce853083614a0a565b90858583106151fb575b505050604051968795869485936338ed173960e01b85528b309260048701614e6e565b6120616152089387613dc2565b5f80856151d8565b92915092604051936152236080866116a7565b60038552606036602087013784938861523b8761401d565b526152458661402a565b52845160021015610860575f80948a92606061510d980160018060a01b038b1690526151c3565b506001600160a01b0387168114615177565b8751636438763b60e11b5f5260045260245ffd5b61529a6116c8565b506001604051916152aa8361163a565b818160311c161515835260301c161515602082015290565b600360ff821611159081615309575b816152f3575b816152e0575090565b6113889150620fffff9060381c16111590565b9050612710620fffff8260201c161115906152d7565b9050612710620fffff8260081c161115906152d1565b60201c612710620fffff62ffffff8316921611612b605790565b9060016040808401510151603a1c16156153f557604082810151518101519051636f074d1f60e11b81526004810192909252602090829060249082905f906001600160a01b03165af15f91816153c1575b506153a357505163381f209760e21b5f5260045260245ffd5b9081156153ae575090565b51632976bf1560e21b5f5260045260245ffd5b9091506020813d6020116153ed575b816153dd602093836116a7565b8101031261063a5751905f61538a565b3d91506153d0565b905090565b91906020615406612821565b93019061541482515161531f565b9082515160381c620fffff8116906113888211613d715762100000166155085762ffffff906220000084161515936280000081161582151590816154f2575b50156154e657505b16926154678483615781565b92156154cc57515160081c612710620fffff62ffffff8316921611612b60576154b892919080156154c25761549f6154ae9184615781565b80604089015283885283613dc2565b6020870152613dc2565b9060608401529190565b506154ae5f61549f565b50906154b8918186528160408701525f6020870152613dc2565b620fffff91501661545b565b80159150615501575b5f615453565b50846154fb565b50935050506040519161551a83611655565b5f83525f60208401525f60408401525f60608401529190565b9192916001600160a01b0316908161555457631954782960e01b5f5260045ffd5b6001600160a01b0316913383036155a057612e2193604051936323b872dd60e01b6020860152602485015260018060a01b03166044840152606483015260648252612e1c6084836116a7565b63f8fd233360e01b5f5260045ffd5b60e0810151516040820151516060015194969195939492936001600160a01b0393841693909116919083831461567e5762ffffff60a088015116976103e8891161566057670de0b6b3a7640000860290868204670de0b6b3a764000014871517156108c7576133a26133a79261339c615627966141aa565b956127100361271081116108c7576156456127109161180e9861400a565b0493808511615658575b50801594614ea7565b93505f61564f565b88885163751419d760e11b5f526004526103e860245260445260645ffd5b505050505091505090565b90604001908151156156eb57600260ff60208301515116146156a9575050565b604081810151810151915192519051633583df0360e11b8152600481019390935260329190911c60ff1660248301819052604483015215156064820152608490fd5b5050565b90604001908151156156eb576020810190600160ff835151161461571257505050565b604080820151015160321c60ff169060ff821660048114159081615775575b5061573c5750505050565b51915151925160405163aaf4f52d60e01b8152600481019390935260ff9384166024840152921660448201529015156064820152608490fd5b6005915014155f615731565b808202905f198382099082808310920391808303928361271011156157ea57146157df577fbc01a36e2eb1c432ca57a786c226809d495182a9930be0ded288ce703afb7e9193612710910990828211900360fc1b910360041c170290565b505061271091500490565b60405162461bcd60e51b815260206004820152601f60248201527f46756c6c4d6174683a2064656e6f6d696e61746f7220746f6f20736d616c6c006044820152606490fd5b919290156158915750815115615843575090565b3b1561584c5790565b60405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e74726163740000006044820152606490fd5b8251909150156158a45750805190602001fd5b60405162461bcd60e51b815260206004820152908190612d7590602483019061193956fea2646970667358221220b4fab022237eefc14f58a95b389557ad2a9b5930de37542a95d9760c19fa3e7b64736f6c634300081c0033
Loading...
Loading
Loading...
Loading
Multichain Portfolio | 31 Chains
Chain | Token | Portfolio % | Price | Amount | Value |
---|
A contract address hosts a smart contract, which is a set of code stored on the blockchain that runs when predetermined conditions are met. Learn more about addresses in our Knowledge Base.