Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ optimizer_runs = 200
via_ir = true
evm_version = "shanghai"
fs_permissions = [
{ access = "read", path = "./" }
{ access = "read", path = "./" },
{ access = "read-write", path = "./broadcast" }
]
allow_paths = ["../src"]

Expand Down
14 changes: 5 additions & 9 deletions script/DeployBufferManager.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,15 @@ import { console } from "forge-std/console.sol";

import { BufferManager } from "@multyr-core/core/modules/BufferManager.sol";
import { IBufferManager } from "@multyr-core/interfaces/IBufferManager.sol";
import { ChainConfig } from "./config/ChainConfig.sol";

/// @title DeployBufferManager -- standalone BufferManager redeploy
/// @notice Deploys a new BufferManager for an existing CoreVault.
/// Use for incident response or initial standalone deploy.
/// After deploy, caller must call vault.setEcosystem() to wire the new BM.
/// @dev Idempotent: safe to deploy multiple times; only the one set in ecosystem is active.
/// Fails fast if vault is address(0) -- never deploy against zero vault.
/// @custom:chain-id 42161 (Arbitrum One -- enforced at runtime)
/// @custom:chain-id Arbitrum One (42161), Base (8453), Ethereum Mainnet (1) -- see script/config/ChainConfig.sol
/// @custom:env-vars DEPLOYER_PRIVATE_KEY, CORE_VAULT_ADDRESS,
/// TARGET_HOT_BPS (opt, default 400), MIN_HOT_BPS (opt, default 200),
/// TARGET_WARM_BPS (opt, default 600), MAX_WARM_BPS (opt, default 800),
Expand All @@ -23,14 +24,8 @@ import { IBufferManager } from "@multyr-core/interfaces/IBufferManager.sol";
/// 3) If warm adapters: bufferManager.addWarmAdapter(...) + vault.approveWarmAdapters(...)
contract DeployBufferManager is Script {

uint256 constant ARBITRUM_ONE_CHAIN_ID = 42161;
address constant USDC = 0xaf88d065e77c8cC2239327C5EDb3A432268e5831;

function run() external returns (BufferManager bm) {
require(
block.chainid == ARBITRUM_ONE_CHAIN_ID,
"WRONG_CHAIN: DeployBufferManager is Arbitrum-only (chainId 42161)"
);
ChainConfig.Config memory chain = ChainConfig.current();

uint256 deployerPk = vm.envUint("DEPLOYER_PRIVATE_KEY");
address deployer = vm.addr(deployerPk);
Expand All @@ -51,6 +46,7 @@ contract DeployBufferManager is Script {
console.log("================================================================");
console.log(" DEPLOY BUFFER MANAGER (standalone)");
console.log("================================================================");
console.log("Chain: ", chain.chainName);
console.log("Deployer: ", deployer);
console.log("CoreVault: ", coreVault);
console.log("targetHotBps: ", targetHotBps);
Expand All @@ -67,7 +63,7 @@ contract DeployBufferManager is Script {
maxWarmBps: uint16(maxWarmBps),
opsReserveTargetBps: uint16(opsReserveBps),
maxWarmSlippageBps: uint16(maxWarmSlippageBps),
asset: USDC,
asset: chain.usdc,
warmAdapter: address(0), // deprecated field
twapWindowSec: 0,
paused: false
Expand Down
9 changes: 6 additions & 3 deletions script/DeployFixedMaturityVault.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ pragma solidity ^0.8.28;

import { Script } from "forge-std/Script.sol";
import { console } from "forge-std/console.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { IERC20Metadata } from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";

// Core
Expand Down Expand Up @@ -130,7 +131,7 @@ contract DeployFixedMaturityVault is Script {
_activateSelectorRegistry(result);

console.log("=== PHASE 8: SEED DEAD DEPOSIT (MANDATORY) ===");
_seedDeadDeposit(result);
_seedDeadDeposit(cfg, result);

console.log("=== PHASE 9: FINAL ASSERTIONS ===");
_assertFinalState(cfg, result);
Expand Down Expand Up @@ -413,8 +414,10 @@ contract DeployFixedMaturityVault is Script {
// PHASE 8: SEED DEAD DEPOSIT (MANDATORY -- inflation attack hardening)
// =========================================================================

function _seedDeadDeposit(FMDeploymentResult memory result) internal {
IAdminModule(address(result.vault)).seedDeadDeposit(1e6); // 1 USDC
function _seedDeadDeposit(FMConfig memory cfg, FMDeploymentResult memory result) internal {
uint256 deadDepositAmount = 1e6; // 1 USDC
IERC20(cfg.chain.usdc).approve(address(result.vault), deadDepositAmount);
IAdminModule(address(result.vault)).seedDeadDeposit(deadDepositAmount);
require(IAdminModule(address(result.vault)).isDeadDepositDone(), "SEED: dead deposit failed");
console.log(" [OK] Dead deposit seeded (1 USDC)");
}
Expand Down
11 changes: 4 additions & 7 deletions script/DeployFixedMaturityVaultUpkeep.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { Script } from "forge-std/Script.sol";
import { console } from "forge-std/console.sol";

import { FixedMaturityVaultUpkeep } from "@multyr-core/automation/FixedMaturityVaultUpkeep.sol";
import { ChainConfig } from "./config/ChainConfig.sol";

/// @title DeployFixedMaturityVaultUpkeep -- FM FixedMaturityVaultUpkeep standalone deploy
/// @notice Deploys a FixedMaturityVaultUpkeep keeper for an existing FM CoreVault.
Expand All @@ -13,20 +14,15 @@ import { FixedMaturityVaultUpkeep } from "@multyr-core/automation/FixedMaturityV
/// activate, matured, recall, settle, close) via Chainlink Automation.
/// @dev Stateless: takes vault address + configuration params only.
/// Does not require any special permissions to deploy -- Chainlink registers as forwarder.
/// @custom:chain-id 42161 (Arbitrum One -- enforced at runtime)
/// @custom:chain-id Arbitrum One (42161), Base (8453), Ethereum Mainnet (1) -- see script/config/ChainConfig.sol
/// @custom:env-vars DEPLOYER_PRIVATE_KEY, FM_VAULT_ADDRESS,
/// FM_UPKEEP_STRICT_MODE (opt, default true)
/// @custom:post-deploy 1) Register on Chainlink Automation
/// 2) No additional vault grants needed -- upkeep reads public FM state
contract DeployFixedMaturityVaultUpkeep is Script {

uint256 constant ARBITRUM_ONE_CHAIN_ID = 42161;

function run() external returns (FixedMaturityVaultUpkeep fmUpkeep) {
require(
block.chainid == ARBITRUM_ONE_CHAIN_ID,
"WRONG_CHAIN: DeployFixedMaturityVaultUpkeep is Arbitrum-only (chainId 42161)"
);
ChainConfig.Config memory chain = ChainConfig.current();

uint256 deployerPk = vm.envUint("DEPLOYER_PRIVATE_KEY");
address deployer = vm.addr(deployerPk);
Expand All @@ -39,6 +35,7 @@ contract DeployFixedMaturityVaultUpkeep is Script {
console.log("================================================================");
console.log(" DEPLOY FIXED MATURITY VAULT UPKEEP");
console.log("================================================================");
console.log("Chain: ", chain.chainName);
console.log("Deployer: ", deployer);
console.log("FM Vault: ", fmVault);
console.log("Strict Mode: ", strictMode);
Expand Down
11 changes: 4 additions & 7 deletions script/DeployQueueModule.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { console } from "forge-std/console.sol";
import { EpochedQueueModule } from "@multyr-core/core/modules/EpochedQueueModule.sol";
import { SelectorLib } from "@multyr-core/core/libraries/SelectorLib.sol";
import { CoreVault } from "@multyr-core/core/CoreVault.sol";
import { ChainConfig } from "./config/ChainConfig.sol";

/// @title DeployQueueModule -- EpochedQueueModule standalone redeploy (incident response)
/// @notice Deploys a new EpochedQueueModule delegatecall target and re-wires it to an
Expand All @@ -18,7 +19,7 @@ import { CoreVault } from "@multyr-core/core/CoreVault.sol";
/// module instance is wired to it).
/// Re-wiring requires vault owner (pre-seal) or timelock (post-seal routing freeze lifted).
/// CRITICAL: Do NOT re-wire after routing is frozen unless a timelock tx is submitted first.
/// @custom:chain-id 42161 (Arbitrum One -- enforced at runtime)
/// @custom:chain-id Arbitrum One (42161), Base (8453), Ethereum Mainnet (1) -- see script/config/ChainConfig.sol
/// @custom:env-vars DEPLOYER_PRIVATE_KEY, CORE_VAULT_ADDRESS, REWIRE (opt, default false)
/// REWIRE=true -- also calls setModulesBatch to update selector routing on vault
/// REWIRE=false (default) -- deploys only, caller wires manually
Expand All @@ -28,13 +29,8 @@ import { CoreVault } from "@multyr-core/core/CoreVault.sol";
/// 3) Verify routing: vault.moduleOf(requestEpochWithdrawal.selector) == newModule
contract DeployQueueModule is Script {

uint256 constant ARBITRUM_ONE_CHAIN_ID = 42161;

function run() external returns (EpochedQueueModule queueModule) {
require(
block.chainid == ARBITRUM_ONE_CHAIN_ID,
"WRONG_CHAIN: DeployQueueModule is Arbitrum-only (chainId 42161)"
);
ChainConfig.Config memory chain = ChainConfig.current();

uint256 deployerPk = vm.envUint("DEPLOYER_PRIVATE_KEY");
address deployer = vm.addr(deployerPk);
Expand All @@ -49,6 +45,7 @@ contract DeployQueueModule is Script {
console.log("================================================================");
console.log(" DEPLOY QUEUE MODULE (standalone -- incident response)");
console.log("================================================================");
console.log("Chain: ", chain.chainName);
console.log("Deployer: ", deployer);
console.log("Rewire: ", rewire);
if (rewire) { console.log("CoreVault: ", coreVault); }
Expand Down
11 changes: 4 additions & 7 deletions script/DeployStrategyRouter.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,28 +5,24 @@ import { Script } from "forge-std/Script.sol";
import { console } from "forge-std/console.sol";

import { StrategyRouter } from "@multyr-core/core/modules/StrategyRouter.sol";
import { ChainConfig } from "./config/ChainConfig.sol";

/// @title DeployStrategyRouter -- standalone StrategyRouter redeploy
/// @notice Deploys a new StrategyRouter for an existing CoreVault + GlobalConfig.
/// Use for incident response or initial standalone deploy (separate from DeployCoreSystem).
/// After deploy, caller must re-register strategies and update ecosystem config.
/// @dev WARNING: Redeploying StrategyRouter clears strategy registry -- all strategies must be
/// re-registered via registerStrategy() after wiring. No state is migrated automatically.
/// @custom:chain-id 42161 (Arbitrum One -- enforced at runtime)
/// @custom:chain-id Arbitrum One (42161), Base (8453), Ethereum Mainnet (1) -- see script/config/ChainConfig.sol
/// @custom:env-vars DEPLOYER_PRIVATE_KEY, CORE_VAULT_ADDRESS, GLOBAL_CONFIG_ADDRESS
/// @custom:post-deploy 1) strategyRouter.setHealthRegistry(healthRegistry) -- requires SR owner
/// 2) vault.setEcosystem() with new SR address -- requires vault owner/timelock
/// 3) Re-register all strategies: strategyRouter.registerStrategy(...)
/// 4) strategyRouter.transferOwnership(timelock)
contract DeployStrategyRouter is Script {

uint256 constant ARBITRUM_ONE_CHAIN_ID = 42161;

function run() external returns (StrategyRouter router) {
require(
block.chainid == ARBITRUM_ONE_CHAIN_ID,
"WRONG_CHAIN: DeployStrategyRouter is Arbitrum-only (chainId 42161)"
);
ChainConfig.Config memory chain = ChainConfig.current();

uint256 deployerPk = vm.envUint("DEPLOYER_PRIVATE_KEY");
address deployer = vm.addr(deployerPk);
Expand All @@ -39,6 +35,7 @@ contract DeployStrategyRouter is Script {
console.log("================================================================");
console.log(" DEPLOY STRATEGY ROUTER (standalone)");
console.log("================================================================");
console.log("Chain: ", chain.chainName);
console.log("Deployer: ", deployer);
console.log("CoreVault: ", coreVault);
console.log("GlobalConfig: ", globalConfig);
Expand Down
11 changes: 4 additions & 7 deletions script/DeployVaultUpkeep.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,15 @@ import { console } from "forge-std/console.sol";

import { VaultUpkeep } from "@multyr-core/automation/VaultUpkeep.sol";
import { BufferManager } from "@multyr-core/core/modules/BufferManager.sol";
import { ChainConfig } from "./config/ChainConfig.sol";

/// @title DeployVaultUpkeep -- OE VaultUpkeep standalone deploy
/// @notice Deploys a VaultUpkeep keeper for an existing OE CoreVault.
/// Run after DeployCoreSystem or DeployCoreIntegrated when upkeep was not included.
/// Grants no special permissions automatically -- caller must set BM keeper after deploy.
/// @dev Idempotent in the sense that deploying twice creates a second keeper; only one should
/// be registered with Chainlink at a time. Stateless: no storage, just constructor args.
/// @custom:chain-id 42161 (Arbitrum One -- enforced at runtime)
/// @custom:chain-id Arbitrum One (42161), Base (8453), Ethereum Mainnet (1) -- see script/config/ChainConfig.sol
/// @custom:env-vars DEPLOYER_PRIVATE_KEY, VAULT_ADDRESS, BUFFER_MANAGER_ADDRESS,
/// STRATEGY_ROUTER_ADDRESS, GLOBAL_CONFIG_ADDRESS,
/// DEFAULT_MAX_REALIZE (opt, default 1000000e6), DEFAULT_MAX_DEPLOY (opt, default 1000000e6),
Expand All @@ -23,13 +24,8 @@ import { BufferManager } from "@multyr-core/core/modules/BufferManager.sol";
/// 3) Register on Chainlink Automation (forwarder address from registration)
contract DeployVaultUpkeep is Script {

uint256 constant ARBITRUM_ONE_CHAIN_ID = 42161;

function run() external returns (VaultUpkeep upkeep) {
require(
block.chainid == ARBITRUM_ONE_CHAIN_ID,
"WRONG_CHAIN: DeployVaultUpkeep is Arbitrum-only (chainId 42161)"
);
ChainConfig.Config memory chain = ChainConfig.current();

uint256 deployerPk = vm.envUint("DEPLOYER_PRIVATE_KEY");
address deployer = vm.addr(deployerPk);
Expand All @@ -52,6 +48,7 @@ contract DeployVaultUpkeep is Script {
console.log("================================================================");
console.log(" DEPLOY VAULT UPKEEP (OE standalone)");
console.log("================================================================");
console.log("Chain: ", chain.chainName);
console.log("Deployer: ", deployer);
console.log("Vault: ", vault);
console.log("BufferManager: ", bufferManager);
Expand Down
12 changes: 11 additions & 1 deletion src/core/libraries/SelectorLib.sol
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ library SelectorLib {
uint256 internal constant ADMIN_MODULE_VIEW_SELECTORS = 15; // +1: getForceExitPenalty, +1: isPerfInitialized
uint256 internal constant ERC4626_MODULE_SELECTORS = 11; // +1: forceWithdraw, +1: forceWithdrawAll
uint256 internal constant LIQUIDITY_OPS_MODULE_SELECTORS = 7; // canDeploy, deployToStrategies, deployToStrategiesWithPlan, realizeForQueue, realizeForReserveAndOps, canRebalanceStrategies, rebalanceStrategies
uint256 internal constant FIXED_MATURITY_MODULE_SELECTORS = 14; // 13 plan selectors + autoCloseFunding
uint256 internal constant FIXED_MATURITY_MODULE_SELECTORS = 23; // 13 plan selectors + autoCloseFunding + 9 previously-unrouted views

uint256 internal constant TOTAL_SELECTORS = QUEUE_MODULE_SELECTORS + QUEUE_MODULE_VIEW_SELECTORS
+ ADMIN_MODULE_OWNER_SELECTORS + ADMIN_MODULE_VIEW_SELECTORS + ERC4626_MODULE_SELECTORS
Expand Down Expand Up @@ -203,6 +203,16 @@ library SelectorLib {
selectors[11] = FixedMaturityModule.isSettlementOpen.selector;
selectors[12] = FixedMaturityModule.currentVaultModeAndState.selector;
selectors[13] = FixedMaturityModule.fundingProgressBps.selector;
selectors[14] = FixedMaturityModule.isInstantExitOpen.selector;
selectors[15] = FixedMaturityModule.netFundedAssets.selector;
selectors[16] = FixedMaturityModule.isFundingSuccessful.selector;
selectors[17] = FixedMaturityModule.isFundingTargetReached.selector;
selectors[18] = FixedMaturityModule.finalPerformanceFeeStatus.selector;
// Config getters (used by upkeep and off-chain tooling)
selectors[19] = FixedMaturityModule.fundingDeadlineTs.selector;
selectors[20] = FixedMaturityModule.maturityTs.selector;
selectors[21] = FixedMaturityModule.minFundingAssets.selector;
selectors[22] = FixedMaturityModule.fixedTermStrategy.selector;
}

// ═══════════════════════════════════════════════════════════════════════════════
Expand Down
13 changes: 11 additions & 2 deletions src/core/libraries/SelectorRegistry.sol
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,15 @@ contract SelectorRegistry {
if (selector == FixedMaturityModule.isSettlementOpen.selector) return ROLE_PUBLIC;
if (selector == FixedMaturityModule.currentVaultModeAndState.selector) return ROLE_PUBLIC;
if (selector == FixedMaturityModule.fundingProgressBps.selector) return ROLE_PUBLIC;
if (selector == FixedMaturityModule.isInstantExitOpen.selector) return ROLE_PUBLIC;
if (selector == FixedMaturityModule.netFundedAssets.selector) return ROLE_PUBLIC;
if (selector == FixedMaturityModule.isFundingSuccessful.selector) return ROLE_PUBLIC;
if (selector == FixedMaturityModule.isFundingTargetReached.selector) return ROLE_PUBLIC;
if (selector == FixedMaturityModule.finalPerformanceFeeStatus.selector) return ROLE_PUBLIC;
if (selector == FixedMaturityModule.fundingDeadlineTs.selector) return ROLE_PUBLIC;
if (selector == FixedMaturityModule.maturityTs.selector) return ROLE_PUBLIC;
if (selector == FixedMaturityModule.minFundingAssets.selector) return ROLE_PUBLIC;
if (selector == FixedMaturityModule.fixedTermStrategy.selector) return ROLE_PUBLIC;

// Not registered - return special value
return ROLE_UNREGISTERED;
Expand Down Expand Up @@ -402,7 +411,7 @@ contract SelectorRegistry {
// @dev If we keep this, we need to maintain it manually as selectors are added/removed -
// consider if it's worth the maintenance burden.
function totalRegisteredSelectors() external pure returns (uint256) {
// 35 owner + 15 admin view + 6 queue write + 5 queue view + 11 ERC4626 + 6 LiquidityOps + 14 FM = 92
return 92;
// 35 owner + 15 admin view + 6 queue write + 5 queue view + 11 ERC4626 + 6 LiquidityOps + 23 FM = 101
return 101;
}
}
Loading