Description:
Multi-signature wallet contract requiring multiple confirmations for transaction execution.
Blockchain: Ethereum
Source Code: View Code On The Blockchain
Solidity Source Code:
{{
"language": "Solidity",
"sources": {
"src/collectors/defi/StrETHOracle.sol": {
"content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.25;
import "../../../src/vaults/Vault.sol";
import "./ICustomOracle.sol";
import "./external/IAaveOracleV3.sol";
import "./external/IAavePoolV3.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
contract StrETHOracle is ICustomOracle {
address public constant ETH = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;
address public constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;
address public constant WSTETH = 0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0;
address public constant USDC = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48;
address public constant USDT = 0xdAC17F958D2ee523a2206206994597C13D831ec7;
address public constant USDS = 0xdC035D45d973E3EC169d2276DDab16f1e407384F;
address public constant USDE = 0x4c9EDD5852cd905f086C759E8383e09bff1E68B3;
address public constant SUSDE = 0x9D39A5DE30e57443BfF2A8307A4256c8797A3497;
IAavePoolV3 public constant AAVE_CORE = IAavePoolV3(0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2);
IAavePoolV3 public constant AAVE_PRIME = IAavePoolV3(0x4e033931ad43597d96D6bcc25c280717730B58B1);
IAaveOracleV3 public constant AAVE_ORACLE = IAaveOracleV3(0x54586bE62E3c3580375aE3723C145253060Ca0C2);
struct Balance {
address asset;
int256 balance;
string metadata;
}
function getAaveBalances(address token, address vault, IAavePoolV3 instance)
public
view
returns (uint256 collateral, uint256 debt)
{
if (token == ETH) {
return (0, 0);
}
IAavePoolV3.ReserveDataLegacy memory data;
data = instance.getReserveData(token);
if (data.aTokenAddress != address(0)) {
collateral = IERC20(data.aTokenAddress).balanceOf(vault);
}
if (data.variableDebtTokenAddress != address(0)) {
debt = IERC20(data.variableDebtTokenAddress).balanceOf(vault);
}
}
function allTokens() public pure returns (address[] memory tokens) {
address[8] memory tokens_ = [ETH, WETH, WSTETH, USDC, USDT, USDS, USDE, SUSDE];
tokens = new address[](tokens_.length);
for (uint256 i = 0; i < tokens_.length; i++) {
tokens[i] = tokens_[i];
}
}
function evaluate(address asset, address denominator, uint256 amount) public view returns (uint256) {
if (asset == denominator) {
return amount;
}
uint256 assetPriceD8 = AAVE_ORACLE.getAssetPrice(asset);
uint8 assetDecimals = IERC20Metadata(asset).decimals();
uint256 denominatorPriceD8 = AAVE_ORACLE.getAssetPrice(denominator);
uint8 denominatorDecimals = IERC20Metadata(asset).decimals();
return Math.mulDiv(amount, assetPriceD8 * 10 ** denominatorDecimals, denominatorPriceD8 * 10 ** assetDecimals);
}
function evaluateSigned(address asset, address denominator, int256 amount) public view returns (int256) {
if (amount == 0) {
return 0;
}
if (asset == ETH) {
return evaluateSigned(WETH, denominator, amount);
}
if (amount > 0) {
return int256(evaluate(asset, denominator, uint256(amount)));
}
return -int256(evaluate(asset, denominator, uint256(-amount)));
}
function tvl(address vault, Data calldata data) public view returns (uint256 value) {
Balance[] memory response = getDistributions(Vault(payable(vault)));
address[] memory tokens = allTokens();
int256[] memory balances = new int256[](tokens.length);
for (uint256 i = 0; i < response.length; i++) {
uint256 index;
for (uint256 j = 0; j < tokens.length; j++) {
if (tokens[j] == response[i].asset) {
index = j;
break;
}
}
balances[index] += response[i].balance;
}
int256 signedValue = 0;
for (uint256 i = 0; i < tokens.length; i++) {
signedValue += evaluateSigned(tokens[i], data.denominator, balances[i]);
}
if (signedValue < 0) {
return 0;
}
value = uint256(signedValue);
}
function getDistributions(Vault vault) public view returns (Balance[] memory response) {
uint256 subvaults = vault.subvaults();
address[] memory vaults = new address[](subvaults + 1);
for (uint256 i = 0; i < subvaults; i++) {
vaults[i] = vault.subvaultAt(i);
}
vaults[subvaults] = address(vault);
address[] memory tokens = allTokens();
response = new Balance[](tokens.length * 5);
uint256 iterator = 0;
for (uint256 i = 0; i < tokens.length; i++) {
Balance memory token = Balance({asset: tokens[i], balance: 0, metadata: "ERC20"});
Balance memory coreDebt = Balance({asset: tokens[i], balance: 0, metadata: "AaveCoreDebt"});
Balance memory coreCollateral = Balance({asset: tokens[i], balance: 0, metadata: "AaveCoreCollateral"});
Balance memory primeDebt = Balance({asset: tokens[i], balance: 0, metadata: "AavePrimeDebt"});
Balance memory primeCollateral = Balance({asset: tokens[i], balance: 0, metadata: "AavePrimeCollateral"});
for (uint256 j = 0; j < vaults.length; j++) {
token.balance += int256(TransferLibrary.balanceOf(tokens[i], vaults[j]));
{
(uint256 collateral, uint256 debt) = getAaveBalances(tokens[i], vaults[j], AAVE_CORE);
coreCollateral.balance += int256(collateral);
coreDebt.balance -= int256(debt);
}
{
(uint256 collateral, uint256 debt) = getAaveBalances(tokens[i], vaults[j], AAVE_PRIME);
primeCollateral.balance += int256(collateral);
primeDebt.balance -= int256(debt);
}
}
if (token.balance != 0) {
response[iterator++] = token;
}
if (coreCollateral.balance != 0) {
response[iterator++] = coreCollateral;
}
if (coreDebt.balance != 0) {
response[iterator++] = coreDebt;
}
if (primeCollateral.balance != 0) {
response[iterator++] = primeCollateral;
}
if (primeDebt.balance != 0) {
response[iterator++] = primeDebt;
}
}
assembly {
mstore(response, iterator)
}
}
}
"
},
"src/vaults/Vault.sol": {
"content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.25;
import "../interfaces/factories/IFactoryEntity.sol";
import "../modules/ACLModule.sol";
import "../modules/ShareModule.sol";
import "../modules/VaultModule.sol";
contract Vault is IFactoryEntity, VaultModule, ShareModule {
struct RoleHolder {
bytes32 role;
address holder;
}
constructor(
string memory name_,
uint256 version_,
address depositQueueFactory_,
address redeemQueueFactory_,
address subvaultFactory_,
address verifierFactory_
)
ACLModule(name_, version_)
ShareModule(name_, version_, depositQueueFactory_, redeemQueueFactory_)
VaultModule(name_, version_, subvaultFactory_, verifierFactory_)
{}
function initialize(bytes calldata initParams) external initializer {
{
(
address admin_,
address shareManager_,
address feeManager_,
address riskManager_,
address oracle_,
address defaultDepositHook_,
address defaultRedeemHook_,
uint256 queueLimit_,
RoleHolder[] memory roleHolders
) = abi.decode(
initParams, (address, address, address, address, address, address, address, uint256, RoleHolder[])
);
__BaseModule_init();
__ACLModule_init(admin_);
__ShareModule_init(
shareManager_, feeManager_, oracle_, defaultDepositHook_, defaultRedeemHook_, queueLimit_
);
__VaultModule_init(riskManager_);
for (uint256 i = 0; i < roleHolders.length; i++) {
_grantRole(roleHolders[i].role, roleHolders[i].holder);
}
}
emit Initialized(initParams);
}
}
"
},
"src/collectors/defi/ICustomOracle.sol": {
"content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.25;
interface ICustomOracle {
struct Data {
address oracle;
uint256 timestamp;
address denominator;
string metadata;
}
function tvl(address vault, Data calldata data) external view returns (uint256);
}
"
},
"src/collectors/defi/external/IAaveOracleV3.sol": {
"content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.25;
interface IAaveOracleV3 {
function getAssetPrice(address) external view returns (uint256);
}
"
},
"src/collectors/defi/external/IAavePoolV3.sol": {
"content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.25;
interface IAavePoolV3 {
struct ReserveConfigurationMap {
uint256 data;
}
struct ReserveDataLegacy {
ReserveConfigurationMap configuration;
uint128 liquidityIndex;
uint128 currentLiquidityRate;
uint128 variableBorrowIndex;
uint128 currentVariableBorrowRate;
uint128 currentStableBorrowRate;
uint40 lastUpdateTimestamp;
uint16 id;
// aToken address
address aTokenAddress;
address stableDebtTokenAddress;
// variableDebtToken address
address variableDebtTokenAddress;
address interestRateStrategyAddress;
uint128 accruedToTreasury;
uint128 unbacked;
uint128 isolationModeTotalDebt;
}
function getReserveData(address asset) external view returns (ReserveDataLegacy memory res);
}
"
},
"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/token/ERC20/extensions/IERC20Metadata.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC20/extensions/IERC20Metadata.sol)
pragma solidity ^0.8.20;
import {IERC20} from "../IERC20.sol";
/**
* @dev Interface for the optional metadata functions from the ERC-20 standard.
*/
interface IERC20Metadata is IERC20 {
/**
* @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);
}
"
},
"src/interfaces/factories/IFactoryEntity.sol": {
"content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.25;
/// @title IFactoryEntity
interface IFactoryEntity {
/// @notice Initializes the factory-created entity with arbitrary initialization data.
/// @param initParams The initialization parameters.
function initialize(bytes calldata initParams) external;
/// @notice Emitted once the entity has been initialized.
/// @param initParams The initialization parameters.
event Initialized(bytes initParams);
}
"
},
"src/modules/ACLModule.sol": {
"content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.25;
import "../interfaces/modules/IACLModule.sol";
import "../libraries/SlotLibrary.sol";
import "../permissions/MellowACL.sol";
import "./BaseModule.sol";
abstract contract ACLModule is IACLModule, BaseModule, MellowACL {
constructor(string memory name_, uint256 version_) MellowACL(name_, version_) {}
// Internal functions
function __ACLModule_init(address admin_) internal onlyInitializing {
if (admin_ == address(0)) {
revert ZeroAddress();
}
_grantRole(DEFAULT_ADMIN_ROLE, admin_);
}
}
"
},
"src/modules/ShareModule.sol": {
"content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.25;
import "../interfaces/modules/IShareModule.sol";
import "../libraries/SlotLibrary.sol";
import "../libraries/TransferLibrary.sol";
import "./ACLModule.sol";
abstract contract ShareModule is IShareModule, ACLModule {
using EnumerableSet for EnumerableSet.AddressSet;
/// @inheritdoc IShareModule
bytes32 public constant SET_HOOK_ROLE = keccak256("modules.ShareModule.SET_HOOK_ROLE");
/// @inheritdoc IShareModule
bytes32 public constant CREATE_QUEUE_ROLE = keccak256("modules.ShareModule.CREATE_QUEUE_ROLE");
/// @inheritdoc IShareModule
bytes32 public constant SET_QUEUE_STATUS_ROLE = keccak256("modules.ShareModule.SET_QUEUE_STATUS_ROLE");
/// @inheritdoc IShareModule
bytes32 public constant SET_QUEUE_LIMIT_ROLE = keccak256("modules.ShareModule.SET_QUEUE_LIMIT_ROLE");
/// @inheritdoc IShareModule
bytes32 public constant REMOVE_QUEUE_ROLE = keccak256("modules.ShareModule.REMOVE_QUEUE_ROLE");
/// @inheritdoc IShareModule
IFactory public immutable depositQueueFactory;
/// @inheritdoc IShareModule
IFactory public immutable redeemQueueFactory;
bytes32 private immutable _shareModuleStorageSlot;
constructor(string memory name_, uint256 version_, address depositQueueFactory_, address redeemQueueFactory_) {
_shareModuleStorageSlot = SlotLibrary.getSlot("ShareModule", name_, version_);
depositQueueFactory = IFactory(depositQueueFactory_);
redeemQueueFactory = IFactory(redeemQueueFactory_);
}
// View functions
/// @inheritdoc IShareModule
function shareManager() public view returns (IShareManager) {
return IShareManager(_shareModuleStorage().shareManager);
}
/// @inheritdoc IShareModule
function feeManager() public view returns (IFeeManager) {
return IFeeManager(_shareModuleStorage().feeManager);
}
/// @inheritdoc IShareModule
function oracle() public view returns (IOracle) {
return IOracle(_shareModuleStorage().oracle);
}
/// @inheritdoc IShareModule
function hasQueue(address queue) public view returns (bool) {
return _shareModuleStorage().queues[IQueue(queue).asset()].contains(queue);
}
/// @inheritdoc IShareModule
function getAssetCount() public view returns (uint256) {
return _shareModuleStorage().assets.length();
}
/// @inheritdoc IShareModule
function assetAt(uint256 index) public view returns (address) {
return _shareModuleStorage().assets.at(index);
}
/// @inheritdoc IShareModule
function hasAsset(address asset) public view returns (bool) {
return _shareModuleStorage().assets.contains(asset);
}
/// @inheritdoc IShareModule
function queueAt(address asset, uint256 index) public view returns (address) {
return _shareModuleStorage().queues[asset].at(index);
}
/// @inheritdoc IShareModule
function getQueueCount() public view returns (uint256) {
return _shareModuleStorage().queueCount;
}
/// @inheritdoc IShareModule
function getQueueCount(address asset) public view returns (uint256) {
return _shareModuleStorage().queues[asset].length();
}
/// @inheritdoc IShareModule
function queueLimit() public view returns (uint256) {
return _shareModuleStorage().queueLimit;
}
/// @inheritdoc IShareModule
function isDepositQueue(address queue) public view returns (bool) {
return _shareModuleStorage().isDepositQueue[queue];
}
/// @inheritdoc IShareModule
function isPausedQueue(address queue) public view returns (bool) {
return _shareModuleStorage().isPausedQueue[queue];
}
/// @inheritdoc IShareModule
function defaultDepositHook() public view returns (address) {
return _shareModuleStorage().defaultDepositHook;
}
/// @inheritdoc IShareModule
function defaultRedeemHook() public view returns (address) {
return _shareModuleStorage().defaultRedeemHook;
}
/// @inheritdoc IShareModule
function claimableSharesOf(address account) public view returns (uint256 shares) {
ShareModuleStorage storage $ = _shareModuleStorage();
EnumerableSet.AddressSet storage assets = $.assets;
uint256 assetsCount = assets.length();
for (uint256 i = 0; i < assetsCount; i++) {
address asset = assets.at(i);
EnumerableSet.AddressSet storage queues = $.queues[asset];
uint256 queuesCount = queues.length();
for (uint256 j = 0; j < queuesCount; j++) {
address queue = queues.at(j);
if ($.isDepositQueue[queue]) {
shares += IDepositQueue(queue).claimableOf(account);
}
}
}
return shares;
}
/// @inheritdoc IShareModule
function getHook(address queue) public view returns (address) {
ShareModuleStorage storage $ = _shareModuleStorage();
address hook = $.customHooks[queue];
return hook != address(0) ? hook : $.isDepositQueue[queue] ? $.defaultDepositHook : $.defaultRedeemHook;
}
/// @inheritdoc IShareModule
function getLiquidAssets() public view returns (uint256) {
address queue = _msgSender();
address asset = IQueue(queue).asset();
ShareModuleStorage storage $ = _shareModuleStorage();
if (!$.queues[asset].contains(queue) || $.isDepositQueue[queue]) {
revert Forbidden();
}
address hook = getHook(queue);
if (hook == address(0)) {
return TransferLibrary.balanceOf(asset, address(this));
}
return IRedeemHook(hook).getLiquidAssets(asset);
}
// Mutable functions
/// @inheritdoc IShareModule
function setCustomHook(address queue, address hook) external onlyRole(SET_HOOK_ROLE) {
if (queue == address(0)) {
revert ZeroAddress();
}
_shareModuleStorage().customHooks[queue] = hook;
emit CustomHookSet(queue, hook);
}
/// @inheritdoc IShareModule
function setDefaultDepositHook(address hook) external onlyRole(SET_HOOK_ROLE) {
_shareModuleStorage().defaultDepositHook = hook;
emit DefaultHookSet(hook, true);
}
/// @inheritdoc IShareModule
function setDefaultRedeemHook(address hook) external onlyRole(SET_HOOK_ROLE) {
_shareModuleStorage().defaultRedeemHook = hook;
emit DefaultHookSet(hook, false);
}
/// @inheritdoc IShareModule
function setQueueLimit(uint256 limit) external onlyRole(SET_QUEUE_LIMIT_ROLE) {
_shareModuleStorage().queueLimit = limit;
emit QueueLimitSet(limit);
}
/// @inheritdoc IShareModule
function setQueueStatus(address queue, bool isPaused) external onlyRole(SET_QUEUE_STATUS_ROLE) {
if (!hasQueue(queue)) {
revert Forbidden();
}
_shareModuleStorage().isPausedQueue[queue] = isPaused;
emit SetQueueStatus(queue, isPaused);
}
/// @inheritdoc IShareModule
function createQueue(uint256 version, bool isDeposit, address owner, address asset, bytes calldata data)
external
nonReentrant
onlyRole(CREATE_QUEUE_ROLE)
{
ShareModuleStorage storage $ = _shareModuleStorage();
if (!IOracle($.oracle).isSupportedAsset(asset)) {
revert UnsupportedAsset(asset);
}
uint256 count = $.queueCount + 1;
if (count > $.queueLimit) {
revert QueueLimitReached();
}
address queue = (isDeposit ? depositQueueFactory : redeemQueueFactory).create(
version, owner, abi.encode(asset, address(this), data)
);
$.queueCount = count;
$.queues[asset].add(queue);
$.assets.add(asset);
$.isDepositQueue[queue] = isDeposit;
emit QueueCreated(queue, asset, isDeposit);
}
/// @inheritdoc IShareModule
function removeQueue(address queue) external onlyRole(REMOVE_QUEUE_ROLE) {
if (!IQueue(queue).canBeRemoved()) {
revert Forbidden();
}
address asset = IQueue(queue).asset();
ShareModuleStorage storage $ = _shareModuleStorage();
if (!$.queues[asset].remove(queue)) {
revert Forbidden();
}
delete $.isDepositQueue[queue];
if ($.queues[asset].length() == 0) {
$.assets.remove(asset);
}
delete $.customHooks[queue];
--$.queueCount;
emit QueueRemoved(queue, asset);
}
/// @inheritdoc IShareModule
function claimShares(address account) external {
ShareModuleStorage storage $ = _shareModuleStorage();
EnumerableSet.AddressSet storage assets = $.assets;
uint256 assetsCount = assets.length();
for (uint256 i = 0; i < assetsCount; i++) {
address asset = assets.at(i);
EnumerableSet.AddressSet storage queues = $.queues[asset];
uint256 queuesCount = queues.length();
for (uint256 j = 0; j < queuesCount; j++) {
address queue = queues.at(j);
if ($.isDepositQueue[queue]) {
IDepositQueue(queue).claim(account);
}
}
}
emit SharesClaimed(account);
}
/// @inheritdoc IShareModule
function callHook(uint256 assets) external {
address queue = _msgSender();
address asset = IQueue(queue).asset();
ShareModuleStorage storage $ = _shareModuleStorage();
if (!_shareModuleStorage().queues[asset].contains(queue)) {
revert Forbidden();
}
address hook = getHook(queue);
if (hook != address(0)) {
Address.functionDelegateCall(hook, abi.encodeCall(IHook.callHook, (asset, assets)));
}
if (!$.isDepositQueue[queue]) {
TransferLibrary.sendAssets(asset, queue, assets);
}
emit HookCalled(queue, asset, assets, hook);
}
/// @inheritdoc IShareModule
function handleReport(address asset, uint224 priceD18, uint32 depositTimestamp, uint32 redeemTimestamp)
external
nonReentrant
{
ShareModuleStorage storage $ = _shareModuleStorage();
if (_msgSender() != $.oracle) {
revert Forbidden();
}
IShareManager shareManager_ = IShareManager($.shareManager);
IFeeManager feeManager_ = IFeeManager($.feeManager);
uint256 fees;
if (asset == feeManager_.baseAsset(address(this))) {
address feeRecipient_ = feeManager_.feeRecipient();
fees = feeManager_.calculateFee(
address(this),
asset,
priceD18,
shareManager_.totalShares() - shareManager_.activeSharesOf(feeRecipient_)
);
if (fees != 0) {
shareManager_.mint(feeRecipient_, fees);
}
feeManager_.updateState(asset, priceD18);
}
EnumerableSet.AddressSet storage queues = _shareModuleStorage().queues[asset];
uint256 length = queues.length();
for (uint256 i = 0; i < length; i++) {
address queue = queues.at(i);
IQueue(queue).handleReport(priceD18, $.isDepositQueue[queue] ? depositTimestamp : redeemTimestamp);
}
emit ReportHandled(asset, priceD18, depositTimestamp, redeemTimestamp, fees);
}
// Internal functions
function __ShareModule_init(
address shareManager_,
address feeManager_,
address oracle_,
address defaultDepositHook_,
address defaultRedeemHook_,
uint256 queueLimit_
) internal onlyInitializing {
if (shareManager_ == address(0) || feeManager_ == address(0) || oracle_ == address(0)) {
revert ZeroAddress();
}
ShareModuleStorage storage $ = _shareModuleStorage();
$.shareManager = shareManager_;
$.feeManager = feeManager_;
$.oracle = oracle_;
$.defaultDepositHook = defaultDepositHook_;
$.defaultRedeemHook = defaultRedeemHook_;
$.queueLimit = queueLimit_;
}
function _shareModuleStorage() internal view returns (ShareModuleStorage storage $) {
bytes32 slot = _shareModuleStorageSlot;
assembly {
$.slot := slot
}
}
}
"
},
"src/modules/VaultModule.sol": {
"content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.25;
import "../interfaces/modules/IVaultModule.sol";
import "../libraries/SlotLibrary.sol";
import "../libraries/TransferLibrary.sol";
import "./ACLModule.sol";
abstract contract VaultModule is IVaultModule, ACLModule {
using EnumerableSet for EnumerableSet.AddressSet;
/// @inheritdoc IVaultModule
bytes32 public constant CREATE_SUBVAULT_ROLE = keccak256("modules.VaultModule.CREATE_SUBVAULT_ROLE");
/// @inheritdoc IVaultModule
bytes32 public constant DISCONNECT_SUBVAULT_ROLE = keccak256("modules.VaultModule.DISCONNECT_SUBVAULT_ROLE");
/// @inheritdoc IVaultModule
bytes32 public constant RECONNECT_SUBVAULT_ROLE = keccak256("modules.VaultModule.RECONNECT_SUBVAULT_ROLE");
/// @inheritdoc IVaultModule
bytes32 public constant PULL_LIQUIDITY_ROLE = keccak256("modules.VaultModule.PULL_LIQUIDITY_ROLE");
/// @inheritdoc IVaultModule
bytes32 public constant PUSH_LIQUIDITY_ROLE = keccak256("modules.VaultModule.PUSH_LIQUIDITY_ROLE");
/// @inheritdoc IVaultModule
IFactory public immutable subvaultFactory;
/// @inheritdoc IVaultModule
IFactory public immutable verifierFactory;
bytes32 private immutable _subvaultModuleStorageSlot;
constructor(string memory name_, uint256 version_, address subvaultFactory_, address verifierFactory_) {
_subvaultModuleStorageSlot = SlotLibrary.getSlot("VaultModule", name_, version_);
subvaultFactory = IFactory(subvaultFactory_);
verifierFactory = IFactory(verifierFactory_);
}
// View functionss
/// @inheritdoc IVaultModule
function subvaults() public view returns (uint256) {
return _vaultStorage().subvaults.length();
}
/// @inheritdoc IVaultModule
function subvaultAt(uint256 index) public view returns (address) {
return _vaultStorage().subvaults.at(index);
}
/// @inheritdoc IVaultModule
function hasSubvault(address subvault) public view returns (bool) {
return _vaultStorage().subvaults.contains(subvault);
}
/// @inheritdoc IVaultModule
function riskManager() public view returns (IRiskManager) {
return IRiskManager(_vaultStorage().riskManager);
}
// Mutable functions
/// @inheritdoc IVaultModule
function createSubvault(uint256 version, address owner, address verifier)
external
onlyRole(CREATE_SUBVAULT_ROLE)
nonReentrant
returns (address subvault)
{
if (!verifierFactory.isEntity(verifier)) {
revert NotEntity(verifier);
}
if (address(IVerifier(verifier).vault()) != address(this)) {
revert Forbidden();
}
subvault = subvaultFactory.create(version, owner, abi.encode(verifier, address(this)));
_vaultStorage().subvaults.add(subvault);
emit SubvaultCreated(subvault, version, owner, verifier);
}
/// @inheritdoc IVaultModule
function disconnectSubvault(address subvault) external onlyRole(DISCONNECT_SUBVAULT_ROLE) {
VaultModuleStorage storage $ = _vaultStorage();
if (!$.subvaults.remove(subvault)) {
revert NotConnected(subvault);
}
emit SubvaultDisconnected(subvault);
}
/// @inheritdoc IVaultModule
function reconnectSubvault(address subvault) external onlyRole(RECONNECT_SUBVAULT_ROLE) {
VaultModuleStorage storage $ = _vaultStorage();
if (!subvaultFactory.isEntity(subvault)) {
revert NotEntity(subvault);
}
if (ISubvaultModule(subvault).vault() != address(this)) {
revert InvalidSubvault(subvault);
}
IVerifier verifier = IVerifierModule(subvault).verifier();
if (!verifierFactory.isEntity(address(verifier))) {
revert NotEntity(address(verifier));
}
if (address(verifier.vault()) != address(this)) {
revert Forbidden();
}
if (!$.subvaults.add(subvault)) {
revert AlreadyConnected(subvault);
}
emit SubvaultReconnected(subvault, address(verifier));
}
/// @inheritdoc IVaultModule
function pullAssets(address subvault, address asset, uint256 value)
external
onlyRole(PULL_LIQUIDITY_ROLE)
nonReentrant
{
_pullAssets(subvault, asset, value);
}
/// @inheritdoc IVaultModule
function pushAssets(address subvault, address asset, uint256 value)
external
onlyRole(PUSH_LIQUIDITY_ROLE)
nonReentrant
{
_pushAssets(subvault, asset, value);
}
/// @inheritdoc IVaultModule
function hookPullAssets(address subvault, address asset, uint256 value) external {
if (_msgSender() != address(this)) {
revert Forbidden();
}
_pullAssets(subvault, asset, value);
}
/// @inheritdoc IVaultModule
function hookPushAssets(address subvault, address asset, uint256 value) external {
if (_msgSender() != address(this)) {
revert Forbidden();
}
_pushAssets(subvault, asset, value);
}
// Internal functions
function _pullAssets(address subvault, address asset, uint256 value) internal {
riskManager().modifySubvaultBalance(subvault, asset, -int256(value));
ISubvaultModule(subvault).pullAssets(asset, value);
emit AssetsPulled(asset, subvault, value);
}
function _pushAssets(address subvault, address asset, uint256 value) internal {
riskManager().modifySubvaultBalance(subvault, asset, int256(value));
TransferLibrary.sendAssets(asset, subvault, value);
emit AssetsPushed(asset, subvault, value);
}
function __VaultModule_init(address riskManager_) internal onlyInitializing {
if (riskManager_ == address(0)) {
revert ZeroAddress();
}
_vaultStorage().riskManager = riskManager_;
}
function _vaultStorage() private view returns (VaultModuleStorage storage $) {
bytes32 slot = _subvaultModuleStorageSlot;
assembly {
$.slot := slot
}
}
}
"
},
"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC20/IERC20.sol)
pragma solidity ^0.8.20;
/**
* @dev Interface of the ERC-20 standard as defined in the ERC.
*/
interface IERC20 {
/**
* @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 value of tokens in existence.
*/
function totalSupply() external view returns (uint256);
/**
* @dev Returns the value of tokens owned by `account`.
*/
function balanceOf(address account) external view returns (uint256);
/**
* @dev Moves a `value` amount of 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 value) 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 a `value` amount of tokens 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 value) external returns (bool);
/**
* @dev Moves a `value` amount of tokens from `from` to `to` using the
* allowance mechanism. `value` 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 value) external returns (bool);
}
"
},
"src/interfaces/modules/IACLModule.sol": {
"content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.25;
import "../permissions/IMellowACL.sol";
import "./IBaseModule.sol";
/// @notice Interface for the ACLModule, implements IMellowACL
interface IACLModule is IMellowACL {
/// @notice Thrown when a zero address is provided
error ZeroAddress();
/// @notice Thrown when an unauthorized caller attempts a restricted operation
error Forbidden();
}
"
},
"src/libraries/SlotLibrary.sol": {
"content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.25;
/// @title SlotLibrary
/// @notice Library for computing deterministic and collision-resistant storage slots
/// @dev Used to generate unique storage slots for upgradeable modules using string identifiers
library SlotLibrary {
/// @notice Computes a unique storage slot based on the module's identifiers
/// @param contractName Logical contract/module name (e.g., "ShareModule")
/// @param name Human-readable instance name (e.g., "Mellow")
/// @param version Version number for the module configuration
/// @return A bytes32 value representing the derived storage slot
function getSlot(string memory contractName, string memory name, uint256 version) internal pure returns (bytes32) {
return keccak256(
abi.encode(
uint256(keccak256(abi.encodePacked("mellow.flexible-vaults.storage.", contractName, name, version))) - 1
)
) & ~bytes32(uint256(0xff));
}
}
"
},
"src/permissions/MellowACL.sol": {
"content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.25;
import "../interfaces/permissions/IMellowACL.sol";
import "../libraries/SlotLibrary.sol";
abstract contract MellowACL is IMellowACL, AccessControlEnumerableUpgradeable {
using EnumerableSet for EnumerableSet.Bytes32Set;
bytes32 private immutable _mellowACLStorageSlot;
constructor(string memory name_, uint256 version_) {
_mellowACLStorageSlot = SlotLibrary.getSlot("MellowACL", name_, version_);
_disableInitializers();
}
// View functions
/// @inheritdoc IMellowACL
function supportedRoles() external view returns (uint256) {
return _mellowACLStorage().supportedRoles.length();
}
/// @inheritdoc IMellowACL
function supportedRoleAt(uint256 index) external view returns (bytes32) {
return _mellowACLStorage().supportedRoles.at(index);
}
/// @inheritdoc IMellowACL
function hasSupportedRole(bytes32 role) external view returns (bool) {
return _mellowACLStorage().supportedRoles.contains(role);
}
// Internal functions
function _grantRole(bytes32 role, address account) internal virtual override returns (bool) {
if (super._grantRole(role, account)) {
if (_mellowACLStorage().supportedRoles.add(role)) {
emit RoleAdded(role);
}
return true;
}
return false;
}
function _revokeRole(bytes32 role, address account) internal virtual override returns (bool) {
if (super._revokeRole(role, account)) {
if (getRoleMemberCount(role) == 0) {
_mellowACLStorage().supportedRoles.remove(role);
emit RoleRemoved(role);
}
return true;
}
return false;
}
function _mellowACLStorage() private view returns (MellowACLStorage storage $) {
bytes32 slot = _mellowACLStorageSlot;
assembly {
$.slot := slot
}
}
}
"
},
"src/modules/BaseModule.sol": {
"content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.25;
import "../interfaces/modules/IBaseModule.sol";
abstract contract BaseModule is IBaseModule, ContextUpgradeable, ReentrancyGuardUpgradeable {
constructor() {
_disableInitializers();
}
// View functions
/// @inheritdoc IBaseModule
function getStorageAt(bytes32 slot) external pure returns (StorageSlot.Bytes32Slot memory) {
return StorageSlot.getBytes32Slot(slot);
}
/// @inheritdoc IERC721Receiver
function onERC721Received(address, address, uint256, bytes calldata) external pure returns (bytes4) {
return IERC721Receiver.onERC721Received.selector;
}
// Mutable functions
receive() external payable {}
// Internal functions
function __BaseModule_init() internal onlyInitializing {
__ReentrancyGuard_init();
}
}
"
},
"src/interfaces/modules/IShareModule.sol": {
"content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.25;
import "../factories/IFactory.sol";
import "../hooks/IRedeemHook.sol";
import "../managers/IFeeManager.sol";
import "../managers/IShareManager.sol";
import "../oracles/IOracle.sol";
import "../queues/IDepositQueue.sol";
import "../queues/IQueue.sol";
import "../queues/IRedeemQueue.sol";
import "./IBaseModule.sol";
/// @title IShareModule
/// @notice Manages user-facing interactions with the vault via deposit/redeem queues, hooks, and share accounting.
/// @dev Coordinates oracle report handling, hook invocation, fee calculation, and queue lifecycle.
interface IShareModule is IBaseModule {
/// @notice Thrown when an unsupported asset is used for queue creation.
error UnsupportedAsset(address asset);
/// @notice Thrown when the number of queues exceeds the allowed system-wide maximum.
error QueueLimitReached();
/// @notice Thrown when an operation is attempted with a zero-value parameter.
error ZeroValue();
/// @dev Storage structure for the ShareModule.
struct ShareModuleStorage {
address shareManager; // Address of the ShareManager responsible for minting/burning shares
address feeManager; // Address of the FeeManager that calculates and collects protocol fees
address oracle; // Address of the Oracle
address defaultDepositHook; // Optional hook that is called by default after DepositQueue requests are processed
address defaultRedeemHook; // Optional hook that is called by default before RedeemQueue requests are processed
uint256 queueCount; // Total number of queues across all assets
uint256 queueLimit; // Maximum number of queues allowed in the system
mapping(address => address) customHooks; // Optional queue-specific hooks
mapping(address => bool) isDepositQueue; // Whether the queue is a deposit queue
mapping(address => bool) isPausedQueue; // Whether queue operations are currently paused
mapping(address => EnumerableSet.AddressSet) queues; // Mapping of asset to its associated queues
EnumerableSet.AddressSet assets; // Set of all supported assets with queues
}
/// @notice Role identifier for managing per-queue and default hooks
function SET_HOOK_ROLE() external view returns (bytes32);
/// @notice Role identifier for creating new queues
function CREATE_QUEUE_ROLE() external view returns (bytes32);
/// @notice Role identifier for changing the active/paused status of queues
function SET_QUEUE_STATUS_ROLE() external view returns (bytes32);
/// @notice Role identifier for modifying the global queue limit
function SET_QUEUE_LIMIT_ROLE() external view returns (bytes32);
/// @notice Role identifier for removing existing queues
function REMOVE_QUEUE_ROLE() external view returns (bytes32);
/// @notice Returns the ShareManager used for minting and burning shares
function shareManager() external view returns (IShareManager);
/// @notice Returns the FeeManager contract used for fee calculations
function feeManager() external view returns (IFeeManager);
/// @notice Returns the Oracle contract used for handling reports and managing supported assets.
function oracle() external view returns (IOracle);
/// @notice Returns the factory used for deploying deposit queues
function depositQueueFactory() external view returns (IFactory);
/// @notice Returns the factory used for deploying redeem queues
function redeemQueueFactory() external view returns (IFactory);
/// @notice Returns total number of distinct assets with queues
function getAssetCount() external view returns (uint256);
/// @notice Returns the address of the asset at the given index
function assetAt(uint256 index) external view returns (address);
/// @notice Returns whether the given asset is associated with any queues
function hasAsset(address asset) external view returns (bool);
/// @notice Returns whether the given queue is registered
function hasQueue(address queue) external view returns (bool);
/// @notice Returns whether the given queue is a deposit queue
function isDepositQueue(address queue) external view returns (bool);
/// @notice Returns whether the given queue is currently paused
function isPausedQueue(address queue) external view returns (bool);
/// @notice Returns number of queues associated with a given asset
function getQueueCount(address asset) external view returns (uint256);
/// @notice Returns the total number of queues across all assets
function getQueueCount() external view returns (uint256);
/// @notice Returns the queue at the given index for the specified asset
function queueAt(address asset, uint256 index) external view returns (address);
/// @notice Returns the hook assigned to a queue (customHook or defaultHook as a fallback)
function getHook(address queue) external view returns (address hook);
/// @notice Returns the default hook for deposit queues
function defaultDepositHook() external view returns (address);
/// @notice Returns the default hook for redeem queues
function defaultRedeemHook() external view returns (address);
/// @notice Returns the current global queue limit
function queueLimit() external view returns (uint256);
/// @notice Returns the total number of claimable shares for a given user
function claimableSharesOf(address account) external view returns (uint256 shares);
/// @notice Called by redeem queues to check the amount of assets available for instant withdrawal
function getLiquidAssets() external view returns (uint256);
/// @notice Claims all claimable shares from deposit queues for the specified account
function claimShares(address account) external;
/// @notice Assigns a custom hook contract to a specific queue
function setCustomHook(address queue, address hook) external;
/// @notice Sets the global default deposit hook
function setDefaultDepositHook(address hook) external;
/// @notice Sets the global default redeem hook
function setDefaultRedeemHook(address hook) external;
/// @notice Creates a new deposit or redeem queue for a given asset
function createQueue(uint256 version, bool isDepositQueue, address owner, address asset, bytes calldata data)
external;
/// @notice Removes a queue from the system if its `canBeRemoved()` function returns true
function removeQueue(address queue) external;
/// @notice Sets the maximum number of allowed queues across the module
function setQueueLimit(uint256 limit) external;
/// @notice Pauses or resumes a queue's operation
function setQueueStatus(address queue, bool isPaused) external;
/// @notice Invokes a queue's hook (also transfers assets to the queue for redeem queues)
function callHook(uint256 assets) external;
/// @notice Handles an oracle price report, distributes fees and calls internal hooks
function handleReport(address asset, uint224 priceD18, uint32 depositTimestamp, uint32 redeemTimestamp) external;
/// @notice Emitted when a user successfully claims shares from deposit queues
event SharesClaimed(address indexed account);
/// @notice Emitted when a queue-specific custom hook is updated
event CustomHookSet(address indexed queue, address indexed hook);
/// @notice Emitted when a new queue is created
event QueueCreated(address indexed queue, address indexed asset, bool isDepositQueue);
/// @notice Emitted when a queue is removed
event QueueRemoved(address indexed queue, address indexed asset);
/// @notice Emitted after a queue hook is successfully called
event HookCalled(address indexed queue, address indexed asset, uint256 assets, address hook);
/// @notice Emitted when the global queue limit is updated
event QueueLimitSet(uint256 limit);
/// @notice Emitted when a queue's paused status changes
event SetQueueStatus(address indexed queue, bool indexed isPaused);
/// @notice Emitted when a new default hook is configured
event DefaultHookSet(address indexed hook, bool isDepositHook);
/// @notice Emitted after processing a price report and fee distribution
event ReportHandled(
address indexed asset, uint224 indexed priceD18, uint32 depositTimestamp, uint32 redeemTimestamp, uint256 fees
);
}
"
},
"src/libraries/TransferLibrary.sol": {
"content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.25;
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/utils/Address.sol";
/// @title TransferLibrary
/// @notice Library for unified handling of native ETH and ERC20 asset transfers.
/// @dev Provides safe and abstracted methods for sending and receiving both ETH and ERC20 tokens.
///
/// # ETH Convention
/// Uses the constant `ETH = 0xEeee...EeE` to distinguish native ETH from ERC20 tokens.
library TransferLibrary {
using SafeERC20 for IERC20;
/// @notice Error thrown when `msg.value` does not match expected ETH amount
error InvalidValue();
/// @dev Placeholder address used to represent native ETH transfers
address public constant ETH = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;
/// @notice Safely sends assets (ETH or ERC20) to a recipient
/// @param asset Address of the asset to send (use `ETH` constant for native ETH)
/// @param to Recipient address
/// @param assets Amount of assets to send
/// @dev Uses `Address.sendValue` for ETH and `safeTransfer` for ERC20
function sendAssets(address asset, address to, uint256 assets) internal {
if (asset == ETH) {
Address.sendValue(payable(to), assets);
} else {
IERC20(asset).safeTransfer(to, assets);
}
}
/// @notice Safely receives assets (ETH or ERC20) from a sender
/// @param asset Address of the asset to receive (use `ETH` constant for native ETH)
/// @param from Sender address (only used for ERC20)
/// @param assets Amount of assets expected to receive
/// @dev Reverts if `msg.value` is incorrect for ETH or uses `safeTransferFrom` for ERC20
function receiveAssets(address asset, address from, uint256 assets) internal {
if (asset == ETH) {
if (msg.value != assets) {
revert InvalidValue();
}
} else {
IERC20(asset).safeTransferFrom(from, address(this), assets);
}
}
/// @notice Returns the balance of an account for a given asset
/// @param asset Address of the asset to check the balance of (use `ETH` constant for native ETH)
/// @param account Address of the account to check the balance of
/// @return Balance of the account for the given asset
function balanceOf(address asset, address account) internal view returns (uint256) {
if (asset == ETH) {
return account.balance;
} else {
return IERC20(asset).balanceOf(account);
}
}
}
"
},
"src/interfaces/modules/IVaultModule.sol": {
"content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.25;
import "../factories/IFactory.sol";
import "../managers/IRiskManager.sol";
import "./IACLModule.sol";
import "./IShareModule.sol";
import "./ISubvaultModule.sol";
import "./IVerifierModule.sol";
/// @title IVaultModule
/// @notice Interface for a VaultModule that manages and coordinates asset flows
/// and sub-vault connections within a modular vault architecture.
interface IVaultModule is IACLModule {
/// @dev Thrown when trying to reconnect a subvault that is already connected.
error AlreadyConnected(address subvault);
/// @dev Thrown when trying to disconnect a subvault that is not currently connected.
error NotConnected(address subvault);
/// @dev Thrown when the provided address is not a valid factory-deployed entity.
error NotEntity(address subvault);
/// @dev Thrown when a given subvault is not correctly configured.
error InvalidSubvault(address subvault);
/// @notice Storage structure used to track vault state and subvaults.
struct VaultModuleStorage {
address riskManager;
EnumerableSet.AddressSet subvaults;
}
/// @notice Role that allows the creation of new subvaults.
function CREATE_SUBVAULT_ROLE() external view returns (bytes32);
/// @notice Role that allows disconnecting existing subvaults.
function DISCONNECT_SUBVAULT_ROLE() external view returns (bytes32);
/// @notice Role identifier for reconnecting subvaults.
/// @dev Grants permission to reattach a subvault to the vault system.
/// This includes both re-connecting a previously disconnected subvault
/// and connecting a new, properly configured subvault for the first time.
/// Used to maintain modularity and support hot-swapping of subvaults.
function RECONNECT_SUBVAULT_ROLE() external view returns (bytes32);
/// @notice Role that allows pulling assets from subvaults.
function PULL_LIQUIDITY_ROLE() external view returns (bytes32);
/// @notice Role that allows pushing assets into subvaults.
function PUSH_LIQUIDITY_ROLE() external view returns (bytes32);
/// @notice Returns the factory used to deploy new subvaults.
function subvaultFactory() external view returns (IFactory);
/// @notice Returns the factory used to deploy verifiers.
function verifierFactory() external view returns (IFactory);
/// @notice Returns the total number of connected subvaults.
function subvaults() external view returns (uint256);
/// @notice Returns the address of the subvault at a specific index.
/// @param index Index in the set of subvaults.
function subvaultAt(uint256 index) external view returns (address);
/// @notice Checks whether a given address is currently an active subvault.
/// @param subvault Address to check.
function hasSubvault(address subvault) external view returns (bool);
/// @notice Returns the address of the risk manager module.
function riskManager() external view returns (IRiskManager);
/// @notice Creates and connects a new subvault.
/// @param version Version of the subvault contract to deploy.
/// @param owner Owner of the newly created subvault.
/// @param verifier Verifier contract used for permissions within the subvault.
/// @return subvault Address of the newly created subvault.
function createSubvault(uint256 version, address owner, address verifier) external returns (address subvault);
/// @notice Disconnects a subvault from the vault.
/// @param subvault Address of the subvault to disconnect.
function disconnectSubvault(address subvault) external;
/// @notice Reconnects a subvault to the main vault system.
/// @dev Can be used to reattach either:
/// - A previously disconnected subvault, or
/// - A newly created and properly configured subvault.
/// Requires the caller to have the `RECONNECT_SUBVAULT_ROLE`.
/// @param subvault The address of the subvault to reconnect.
function reconnectSubvault(address subvault) external;
/// @notice Sends a specified amount of assets from the vault to a connected subvault.
/// @param subvault Address of the destination subvault.
/// @param asset Address of the asset to transfer.
/// @param value Amount of the asset to send.
function pushAssets(address subvault, address asset, uint256 value) external;
/// @notice Pulls a specified amount of assets from a connected subvault into the vault.
/// @param subvault Address of the source subvault.
/// @param asset Address of the asset to transfer.
/// @param value Amount of the asset to receive.
function pullAssets(address subvault, address asset, uint256 value) external;
/// @notice Internally used function that transfers assets from the vault to a connected subvault.
/// @dev Must be invoked by the vault itself via hook execution logic.
/// @param subvault Address of the destination subvault.
/// @param asset Address of the asset being transferred.
/// @param value Amount of the asset being transferred.
function hookPushAssets(address subvault, address asset, uint256 value) external;
/// @notice Internally used function that pulls assets from a connected subvault into the vault.
/// @dev Must be invoked by the vault itself via hook execution logic.
/// @param subvault Address of the source subvault.
/// @param asset Address of the asset being pulled.
/// @param value Amount of the asset being pulled.
function hookPullAssets(address subvault, address asset, uint256 value) external;
/// @notice Emitted when a new subvault is created.
event SubvaultCreated(address indexed subvault, uint256 version, address indexed owner, address indexed verifier);
/// @notice Emitted when a subvault is disconnected.
event SubvaultDisconnected(address indexed subvault);
/// @notice Emitted when a subvault is reconnected.
event SubvaultReconnected(address indexed subvault, address indexed verifier);
/// @notice Emitted when assets are pulled from a subvault into the vault.
event AssetsPulled(address indexed asset, address indexed subvault, uint256 value);
/// @notice Emitted when assets are pushed from the vault into a subvault.
event AssetsPushed(address indexed asset, address indexed subvault, uint256 value);
}
"
},
"src/interfaces/permissions/IMellowACL.sol": {
"content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.25;
import "@openzeppelin/contracts-upgradeable/access/extensions/AccessControlEnumerableUpgradeable.sol";
/// @notice Interface for the MellowACL contract, which extends OpenZeppelin's AccessControlEnumerable
/// @dev Adds tracking of which roles are actively in use (i.e., assigned to at least one address)
interface IMellowACL is IAccessControlEnumerable {
/// @notice Storage layout used to track actively assigned roles
struct MellowACLStorage {
EnumerableSet.Bytes32Set supportedRoles; // Set of roles that have at least one assigned member
}
/// @notice Returns the total number of unique roles that are currently assigned
function supportedRoles() external view returns (uint256);
/// @notice Returns the role at the specified index in the set of active roles
/// @param index Index within the supported role set
/// @return role The bytes32 identifier of the role
function supportedRoleAt(uint256 index) external view returns (bytes32);
/// @notice Checks whether a given role is currently active (i.e., has at least one member)
/// @param role The bytes32 identifier of the role to check
/// @return isActive True if the role has any members assigned
function hasSupportedRole(bytes32 role) external view returns (bool);
/// @notice Emitted when a new role is granted for the first time
event RoleAdded(bytes32 indexed role);
/// @notice Emitted when a role loses its last member
event RoleRemoved(bytes32 indexed role);
}
"
},
"src/interfaces/modules/IBaseModule.sol": {
"content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.25;
import "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/utils/ReentrancyGuardUpgradeable.sol";
import "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol";
import "@openzeppelin/contracts/utils/StorageSlot.sol";
/// @notice Interface for base module functionality shared across all modules
/// @dev Provides basic utilities such as raw storage access, ERC721 receiver support and `receive()` callback
interface IBaseModule is IERC721Receiver {
/// @notice Returns a reference to a storage slot as a `StorageSlot.Bytes32Slot` struct
/// @param slot The keccak256-derived storage slot identifier
/// @return A struct exposing the `.value` field stored at the given slot
function getStorageAt(bytes32 slot) external pure returns (StorageSlot.Bytes32Slot memory);
}
"
},
"src/interfaces/factories/IFactory.sol": {
"content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.25;
import "./IFactoryEntity.sol";
import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol";
import "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
/// @title IFactory
/// @notice Interface for a factory that manages deployable upgradeable proxies and implementation governance.
interface IFactory is IFactoryEntity {
/// @notice Thrown when attempting to access an index outside the valid range.
error OutOfBounds(uint256 index);
/// @notice Thrown when trying to use an implementation version that is blacklisted.
error BlacklistedVersion(uint256 version);
/// @notice Thrown when an implementation is already in the accepted list.
error ImplementationAlreadyAccepted(address implementation);
/// @notice Thrown when an implementation has already been proposed.
error ImplementationAlreadyProposed(address implementation);
/// @notice Thrown when attempting to accept an implementation that was never proposed.
error ImplementationNotProposed(address implementation);
/// @dev Internal storage structure for tracking factory state.
struct FactoryStorage {
EnumerableSet.AddressSet entities; // Set of deployed upgradeable proxies
EnumerableSet.AddressSet implementations; // Set of accepted implementation addresses
EnumerableSet.AddressSet proposals; // Set of currently proposed (but not yet accepted) implementations
mapping(uint256 version => bool) isBlacklisted; // Tracks whether a given version is blacklisted
}
/// @notice Returns the total number of deployed entities (proxies).
function entities() external view returns (uint256);
/// @notice Returns the address of the deployed entity at a given index.
function entityAt(uint256 index) external view returns (address);
/// @notice Returns whether the given address is a deployed entity.
function isEntity(address entity) external view returns (bool);
/// @notice Returns the total number of accepted implementation contracts.
function implementations() external view returns (uint256);
/// @notice Returns the implementation address at the given index.
function implementationAt(uint256 index) external view returns (address);
/// @notice Returns the number of currently proposed (pending) implementations.
function proposals() external view returns (uint256);
/// @notice Returns the address of a proposed implementation at a given index.
function proposalAt(uint256 index) external view returns (address);
/// @notice Returns whether the given implementation version is blacklisted.
function isBlacklisted(uint256 version) external view returns (bool);
/// @notice Updates the blacklist status for a specific implementation version.
/// @param version The version index to update.
/// @param flag True to blacklist, false to unblacklist.
function setBlacklistStatus(uint256 version, bool flag) external;
/// @notice Proposes a new implementation for future deployment.
/// @param implementation The address of the proposed implementation contract.
function proposeImplementation(address implementation) external;
/// @notice Approves a previously proposed implementation, allowing it to be used for deployments.
/// @param implementation The address of the proposed implementation to approve.
function acceptProposedImplementation(address implementation) external;
/// @notice Deploys a new TransparentUpgradeableProxy using an accepted implementation.
/// @param version The version index of the implementation to use.
/// @param owner The address that will become the owner of the proxy.
/// @param initParams Calldata to be passed for initialization of the new proxy instance.
/// @return instance The address of the newly deployed proxy contract.
function create(uint256 version, address owner, bytes calldata initParams) external returns (address instance);
/// @notice Emitted when the blacklist status of a version is updated.
event SetBlacklistStatus(uint256 version, bool flag);
/// @notice Emitted when a new implementation is proposed.
event ProposeImplementation(address implementation);
/// @notice Emitted when a proposed implementation is accepted.
event AcceptProposedImplementation(address implementation);
/// @notice Emitted when a new proxy instance is successfully deployed.
event Created(address indexed instance, uint256 indexed version, address indexed owner, bytes initParams);
}
"
},
"src/interfaces/hooks/IRedeemHook.sol": {
"content": "// SPDX-License-Identifier: BUSL-1.1
pragma
Submitted on: 2025-09-22 17:25:17
Comments
Log in to comment.
No comments yet.