From fb56324ff1c0c67b075b57958698035c57d448df Mon Sep 17 00:00:00 2001 From: EvmSerhii Date: Wed, 11 Feb 2026 10:58:26 +0100 Subject: [PATCH 01/11] add scripts to deploy vaults --- foundry.toml | 9 +- script/01_DeployDeployer.s.sol | 51 ++++ script/02_DeployMaxiYieldVault.s.sol | 196 ++++++++++++++ script/03_DeployPointsVault.s.sol | 199 ++++++++++++++ script/04_DeployArcticLens.s.sol | 35 +++ test/MantraVaultsIntegration.t.sol | 382 +++++++++++++++++++++++++++ 6 files changed, 867 insertions(+), 5 deletions(-) create mode 100644 script/01_DeployDeployer.s.sol create mode 100644 script/02_DeployMaxiYieldVault.s.sol create mode 100644 script/03_DeployPointsVault.s.sol create mode 100644 script/04_DeployArcticLens.s.sol create mode 100644 test/MantraVaultsIntegration.t.sol diff --git a/foundry.toml b/foundry.toml index 15b98d56c..d1d35b7d5 100644 --- a/foundry.toml +++ b/foundry.toml @@ -3,7 +3,8 @@ # This overrides the `auto_detect_solc` value solc_version = '0.8.21' auto_detect_solc = false -evm_version = 'cancun' +evm_version = 'london' +viaIR = true optimizer = true optimizer_runs = 200 fs_permissions = [{ access = "read-write", path = "./" }] @@ -31,8 +32,7 @@ base = "${BASE_RPC_URL}" zircuit = "${ZIRCUIT_RPC_URL}" scroll = "${SCROLL_RPC_URL}" linea = "${LINEA_RPC_URL}" -mantra_dukong = "${MANTRA_DUKONG_RPC_URL}" -# mantra = "${MANTRA_RPC_URL}" +mantra = "${MANTRA_DUKONG_RPC_URL}" [etherscan] # mainnet = { key = "${ETHERSCAN_KEY}", url = "https://api.etherscan.io/api" } @@ -42,8 +42,7 @@ mantra_dukong = "${MANTRA_DUKONG_RPC_URL}" # arbitrum = { key = "${ARBISCAN_KEY}" } # optimism = { key = "${OPTIMISMSCAN_KEY}" } # base = { key = "${BASESCAN_KEY}" } -mantra_dukong = { key = "empty", url = "https://explorer.dukong.io/api" } -5887 = { key = "empty", url = "https://explorer.dukong.io/api" } +mantra = { key = "any_random_string", chain = 5887, url = "https://explorer.dukong.io/api" } [fmt] FOUNDRY_FMT_LINE_LENGTH = 120 diff --git a/script/01_DeployDeployer.s.sol b/script/01_DeployDeployer.s.sol new file mode 100644 index 000000000..b379381f9 --- /dev/null +++ b/script/01_DeployDeployer.s.sol @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.21; + +import {Deployer} from "src/helper/Deployer.sol"; +import { + RolesAuthority, + Authority +} from "@solmate/auth/authorities/RolesAuthority.sol"; +import "forge-std/Script.sol"; + +/** + * @notice Script to deploy the CREATE3 Deployer and its RolesAuthority. + * @dev Run with: forge script script/01_DeployDeployer.s.sol --rpc-url --broadcast --verify + */ +contract DeployDeployer is Script { + uint8 public constant DEPLOYER_ROLE = 1; + + function run() external { + uint256 deployerKey = vm.envUint("ETHERFI_LIQUID_DEPLOYER"); + address deployerAddr = vm.addr(deployerKey); + + vm.startBroadcast(deployerKey); + + // 1. Deploy Deployer (Owned by deployerAddr, no initial authority) + Deployer deployer = new Deployer(deployerAddr, Authority(address(0))); + + // 2. Deploy RolesAuthority for the Deployer (Owned by deployerAddr) + RolesAuthority auth = new RolesAuthority( + deployerAddr, + Authority(address(0)) + ); + + // 3. Link them + deployer.setAuthority(auth); + + // 4. Grant deployerAddr permission to deploy through the factory + // We use Role 1 for Deployer Role to match the project's standard + auth.setRoleCapability( + DEPLOYER_ROLE, + address(deployer), + Deployer.deployContract.selector, + true + ); + auth.setUserRole(deployerAddr, DEPLOYER_ROLE, true); + + vm.stopBroadcast(); + + console.log("Deployer deployed at:", address(deployer)); + console.log("RolesAuthority deployed at:", address(auth)); + } +} diff --git a/script/02_DeployMaxiYieldVault.s.sol b/script/02_DeployMaxiYieldVault.s.sol new file mode 100644 index 000000000..21026fa53 --- /dev/null +++ b/script/02_DeployMaxiYieldVault.s.sol @@ -0,0 +1,196 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.21; + +import {BoringVault} from "src/base/BoringVault.sol"; +import { + AccountantWithRateProviders +} from "src/base/Roles/AccountantWithRateProviders.sol"; +import { + TellerWithMultiAssetSupport +} from "src/base/Roles/TellerWithMultiAssetSupport.sol"; +import {DelayedWithdraw} from "src/base/Roles/DelayedWithdraw.sol"; +import { + RolesAuthority, + Authority +} from "@solmate/auth/authorities/RolesAuthority.sol"; +import {Deployer} from "src/helper/Deployer.sol"; +import {ERC20} from "@solmate/tokens/ERC20.sol"; +import "forge-std/Script.sol"; + +/** + * @notice Script to deploy "Maxi Yield" Vault through the Deployer. + * @dev Run with: forge script script/02_DeployMaxiYieldVault.s.sol --rpc-url --broadcast --verify + */ +contract DeployMaxiYieldVault is Script { + // Config for Mantra Testnet + address public constant mUSD = 0x4B545d0758eda6601B051259bD977125fbdA7ba2; + address public constant WETH = address(0); + + // Deployer and Auth addresses (deployed in step 01) + address public deployerAddr = vm.envAddress("DEPLOYER_CONTRACT_ADDRESS"); + address public rolesAuthAddr = vm.envAddress("ROLES_AUTH_CONTRACT_ADDRESS"); + + // Standard Project Roles + uint8 public constant MANAGER_ROLE = 1; + uint8 public constant MINTER_ROLE = 2; + uint8 public constant BURNER_ROLE = 3; + uint8 public constant OWNER_ROLE = 8; + uint8 public constant MULTISIG_ROLE = 9; + uint8 public constant UPDATE_EXCHANGE_RATE_ROLE = 11; + + function run() external { + vm.createSelectFork("mantra"); + uint256 deployerKey = vm.envUint("ETHERFI_LIQUID_DEPLOYER"); + address owner = vm.addr(deployerKey); + Deployer deployer = Deployer(deployerAddr); + RolesAuthority auth = RolesAuthority(rolesAuthAddr); + + vm.startBroadcast(deployerKey); + + // --- 1. Deploy Core Components via Deployer --- + address vault = deployer.deployContract( + "Maxi Yield Vault V1.0", + type(BoringVault).creationCode, + abi.encode(owner, "Maxi Yield mUSD", "my-mUSD", 6), + 0 + ); + + address accountant = deployer.deployContract( + "Maxi Yield Accountant V1.0", + type(AccountantWithRateProviders).creationCode, + abi.encode( + owner, + vault, + owner, + 1e6, + mUSD, + 1.5e4, + 0.5e4, + 20 hours, + 0, + 0 + ), + 0 + ); + + address teller = deployer.deployContract( + "Maxi Yield Teller V1.0", + type(TellerWithMultiAssetSupport).creationCode, + abi.encode(owner, vault, accountant, WETH), + 0 + ); + + address delayedWithdraw = deployer.deployContract( + "Maxi Yield DelayedWithdraw V1.0", + type(DelayedWithdraw).creationCode, + abi.encode(owner, vault, accountant, owner), + 0 + ); + + // --- 2. Post-Deployment Setup --- + + // Roles Permissions + BoringVault(payable(vault)).setAuthority(auth); + AccountantWithRateProviders(accountant).setAuthority(auth); + TellerWithMultiAssetSupport(payable(teller)).setAuthority(auth); + DelayedWithdraw(delayedWithdraw).setAuthority(auth); + + // Minter Role (2) -> Teller can mint shares + auth.setRoleCapability( + MINTER_ROLE, + vault, + BoringVault.enter.selector, + true + ); + auth.setUserRole(teller, MINTER_ROLE, true); + + // Burner Role (3) -> DelayedWithdraw can burn shares + auth.setRoleCapability( + BURNER_ROLE, + vault, + BoringVault.exit.selector, + true + ); + auth.setUserRole(delayedWithdraw, BURNER_ROLE, true); + + // Update Rate Role (11) -> Owner can update accountant exchange rate + auth.setRoleCapability( + UPDATE_EXCHANGE_RATE_ROLE, + accountant, + AccountantWithRateProviders.updateExchangeRate.selector, + true + ); + auth.setUserRole(owner, UPDATE_EXCHANGE_RATE_ROLE, true); + + // Owner Role (8) -> Broad administrative rights + auth.setRoleCapability( + OWNER_ROLE, + teller, + TellerWithMultiAssetSupport.setShareLockPeriod.selector, + true + ); + auth.setRoleCapability( + OWNER_ROLE, + teller, + TellerWithMultiAssetSupport.updateAssetData.selector, + true + ); + auth.setRoleCapability( + OWNER_ROLE, + delayedWithdraw, + DelayedWithdraw.setupWithdrawAsset.selector, + true + ); + auth.setRoleCapability( + OWNER_ROLE, + delayedWithdraw, + DelayedWithdraw.setPullFundsFromVault.selector, + true + ); + auth.setUserRole(owner, OWNER_ROLE, true); + + // Public Roles + auth.setPublicCapability( + teller, + TellerWithMultiAssetSupport.deposit.selector, + true + ); + auth.setPublicCapability( + delayedWithdraw, + DelayedWithdraw.requestWithdraw.selector, + true + ); + auth.setPublicCapability( + delayedWithdraw, + DelayedWithdraw.completeWithdraw.selector, + true + ); + + // Logic Config + TellerWithMultiAssetSupport(payable(teller)).setShareLockPeriod(86400); // 24h + TellerWithMultiAssetSupport(payable(teller)).updateAssetData( + ERC20(mUSD), + true, + true, + 0 + ); + + DelayedWithdraw(delayedWithdraw).setupWithdrawAsset( + ERC20(mUSD), + 0, + 7 days, + 0, + 100 + ); + DelayedWithdraw(delayedWithdraw).setPullFundsFromVault(true); + + BoringVault(payable(vault)).setBeforeTransferHook(teller); + + vm.stopBroadcast(); + + console.log("Maxi Yield Vault:", vault); + console.log("Maxi Yield Accountant:", accountant); + console.log("Maxi Yield Teller:", teller); + console.log("Maxi Yield DelayedWithdraw:", delayedWithdraw); + } +} diff --git a/script/03_DeployPointsVault.s.sol b/script/03_DeployPointsVault.s.sol new file mode 100644 index 000000000..b196e51d3 --- /dev/null +++ b/script/03_DeployPointsVault.s.sol @@ -0,0 +1,199 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.21; + +import {BoringVault} from "src/base/BoringVault.sol"; +import { + AccountantWithRateProviders +} from "src/base/Roles/AccountantWithRateProviders.sol"; +import { + AccountantWithFixedRate +} from "src/base/Roles/AccountantWithFixedRate.sol"; +import { + TellerWithMultiAssetSupport +} from "src/base/Roles/TellerWithMultiAssetSupport.sol"; +import {DelayedWithdraw} from "src/base/Roles/DelayedWithdraw.sol"; +import { + RolesAuthority, + Authority +} from "@solmate/auth/authorities/RolesAuthority.sol"; +import {Deployer} from "src/helper/Deployer.sol"; +import {ERC20} from "@solmate/tokens/ERC20.sol"; +import "forge-std/Script.sol"; + +/** + * @notice Script to deploy "Points" Vault through the Deployer. + * @dev Run with: forge script script/03_DeployPointsVault.s.sol --rpc-url --broadcast --verify + */ +contract DeployPointsVault is Script { + // Config for Mantra Testnet + address public constant mUSD = 0x4B545d0758eda6601B051259bD977125fbdA7ba2; + address public constant WETH = address(0); + + // Deployer and Auth addresses (deployed in step 01) + address public deployerAddr = vm.envAddress("DEPLOYER_CONTRACT_ADDRESS"); + address public rolesAuthAddr = vm.envAddress("ROLES_AUTH_CONTRACT_ADDRESS"); + + // Standard Project Roles + uint8 public constant MANAGER_ROLE = 1; + uint8 public constant MINTER_ROLE = 2; + uint8 public constant BURNER_ROLE = 3; + uint8 public constant OWNER_ROLE = 8; + uint8 public constant MULTISIG_ROLE = 9; + uint8 public constant UPDATE_EXCHANGE_RATE_ROLE = 11; + + function run() external { + vm.createSelectFork("mantra"); + uint256 deployerKey = vm.envUint("ETHERFI_LIQUID_DEPLOYER"); + address owner = vm.addr(deployerKey); + Deployer deployer = Deployer(deployerAddr); + RolesAuthority auth = RolesAuthority(rolesAuthAddr); + + vm.startBroadcast(deployerKey); + + // --- 1. Deploy Core Components via Deployer --- + address vault = deployer.deployContract( + "Points Vault V1.0", + type(BoringVault).creationCode, + abi.encode(owner, "Points mUSD", "pts-mUSD", 6), + 0 + ); + + address accountant = deployer.deployContract( + "Points Accountant V1.0", + type(AccountantWithFixedRate).creationCode, + abi.encode( + owner, + vault, + owner, + 1e6, + mUSD, + 1.5e4, + 0.5e4, + 20 hours, + 0, + 0 + ), + 0 + ); + + address teller = deployer.deployContract( + "Points Teller V1.0", + type(TellerWithMultiAssetSupport).creationCode, + abi.encode(owner, vault, accountant, WETH), + 0 + ); + + address delayedWithdraw = deployer.deployContract( + "Points DelayedWithdraw V1.0", + type(DelayedWithdraw).creationCode, + abi.encode(owner, vault, accountant, owner), + 0 + ); + + // --- 2. Post-Deployment Setup --- + + // Roles Permissions + BoringVault(payable(vault)).setAuthority(auth); + AccountantWithFixedRate(accountant).setAuthority(auth); + TellerWithMultiAssetSupport(payable(teller)).setAuthority(auth); + DelayedWithdraw(delayedWithdraw).setAuthority(auth); + + // Minter Role (2) -> Teller can mint shares + auth.setRoleCapability( + MINTER_ROLE, + vault, + BoringVault.enter.selector, + true + ); + auth.setUserRole(teller, MINTER_ROLE, true); + + // Burner Role (3) -> DelayedWithdraw can burn shares + auth.setRoleCapability( + BURNER_ROLE, + vault, + BoringVault.exit.selector, + true + ); + auth.setUserRole(delayedWithdraw, BURNER_ROLE, true); + + // Update Rate Role (11) -> Owner can update accountant exchange rate + auth.setRoleCapability( + UPDATE_EXCHANGE_RATE_ROLE, + accountant, + AccountantWithRateProviders.updateExchangeRate.selector, + true + ); + auth.setUserRole(owner, UPDATE_EXCHANGE_RATE_ROLE, true); + + // Owner Role (8) -> Broad administrative rights + auth.setRoleCapability( + OWNER_ROLE, + teller, + TellerWithMultiAssetSupport.setShareLockPeriod.selector, + true + ); + auth.setRoleCapability( + OWNER_ROLE, + teller, + TellerWithMultiAssetSupport.updateAssetData.selector, + true + ); + auth.setRoleCapability( + OWNER_ROLE, + delayedWithdraw, + DelayedWithdraw.setupWithdrawAsset.selector, + true + ); + auth.setRoleCapability( + OWNER_ROLE, + delayedWithdraw, + DelayedWithdraw.setPullFundsFromVault.selector, + true + ); + auth.setUserRole(owner, OWNER_ROLE, true); + + // Public Roles + auth.setPublicCapability( + teller, + TellerWithMultiAssetSupport.deposit.selector, + true + ); + auth.setPublicCapability( + delayedWithdraw, + DelayedWithdraw.requestWithdraw.selector, + true + ); + auth.setPublicCapability( + delayedWithdraw, + DelayedWithdraw.completeWithdraw.selector, + true + ); + + // Logic Config + TellerWithMultiAssetSupport(payable(teller)).setShareLockPeriod(86400); // 24h + TellerWithMultiAssetSupport(payable(teller)).updateAssetData( + ERC20(mUSD), + true, + true, + 0 + ); + + DelayedWithdraw(delayedWithdraw).setupWithdrawAsset( + ERC20(mUSD), + 0, + 7 days, + 0, + 100 + ); + DelayedWithdraw(delayedWithdraw).setPullFundsFromVault(true); + + BoringVault(payable(vault)).setBeforeTransferHook(teller); + + vm.stopBroadcast(); + + console.log("Points Vault:", vault); + console.log("Points Accountant (Fixed):", accountant); + console.log("Points Teller:", teller); + console.log("Points DelayedWithdraw:", delayedWithdraw); + } +} diff --git a/script/04_DeployArcticLens.s.sol b/script/04_DeployArcticLens.s.sol new file mode 100644 index 000000000..cb02aba77 --- /dev/null +++ b/script/04_DeployArcticLens.s.sol @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.21; + +import {ArcticArchitectureLens} from "src/helper/ArcticArchitectureLens.sol"; +import {Deployer} from "src/helper/Deployer.sol"; +import "forge-std/Script.sol"; + +/** + * @notice Script to deploy ArcticArchitectureLens through the Deployer. + * @dev Run with: forge script script/04_DeployArcticLens.s.sol --rpc-url --broadcast --verify + */ +contract DeployArcticLens is Script { + // Deployer address (deployed in step 01) + address public deployerAddr = vm.envAddress("DEPLOYER_CONTRACT_ADDRESS"); + + function run() external { + vm.createSelectFork("mantra"); + uint256 deployerKey = vm.envUint("ETHERFI_LIQUID_DEPLOYER"); + Deployer deployer = Deployer(deployerAddr); + + vm.startBroadcast(deployerKey); + + // Deploy Lens via Deployer + address lens = deployer.deployContract( + "Arctic Architecture Lens V1.0", + type(ArcticArchitectureLens).creationCode, + hex"", + 0 + ); + + vm.stopBroadcast(); + + console.log("ArcticArchitectureLens deployed at:", lens); + } +} diff --git a/test/MantraVaultsIntegration.t.sol b/test/MantraVaultsIntegration.t.sol new file mode 100644 index 000000000..7057a9e78 --- /dev/null +++ b/test/MantraVaultsIntegration.t.sol @@ -0,0 +1,382 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.21; + +import {Test, console} from "forge-std/Test.sol"; +import {BoringVault} from "src/base/BoringVault.sol"; +import { + AccountantWithRateProviders +} from "src/base/Roles/AccountantWithRateProviders.sol"; +import { + AccountantWithFixedRate +} from "src/base/Roles/AccountantWithFixedRate.sol"; +import { + TellerWithMultiAssetSupport +} from "src/base/Roles/TellerWithMultiAssetSupport.sol"; +import {DelayedWithdraw} from "src/base/Roles/DelayedWithdraw.sol"; +import { + RolesAuthority, + Authority +} from "@solmate/auth/authorities/RolesAuthority.sol"; +import {Deployer} from "src/helper/Deployer.sol"; +import {ArcticArchitectureLens} from "src/helper/ArcticArchitectureLens.sol"; +import {ERC20} from "@solmate/tokens/ERC20.sol"; + +contract MockERC20 is ERC20 { + constructor( + string memory name, + string memory symbol, + uint8 decimals + ) ERC20(name, symbol, decimals) {} + function mint(address to, uint256 amount) public { + _mint(to, amount); + } +} + +contract MantraVaultsIntegrationTest is Test { + // --- State Variables --- + MockERC20 public mUSD; + Deployer public deployer; + RolesAuthority public auth; + ArcticArchitectureLens public lens; + + // Yield Vault components + BoringVault public yieldVault; + AccountantWithRateProviders public yieldAccountant; + TellerWithMultiAssetSupport public yieldTeller; + DelayedWithdraw public yieldDelayedWithdraw; + + // Points Vault components + BoringVault public pointsVault; + AccountantWithFixedRate public pointsAccountant; + TellerWithMultiAssetSupport public pointsTeller; + DelayedWithdraw public pointsDelayedWithdraw; + + address public owner = address(0xDE1); + address public userA = address(0xA11ce); + + // Standard Project Roles + uint8 public constant MANAGER_ROLE = 1; + uint8 public constant MINTER_ROLE = 2; + uint8 public constant BURNER_ROLE = 3; + uint8 public constant OWNER_ROLE = 8; + uint8 public constant MULTISIG_ROLE = 9; + uint8 public constant UPDATE_EXCHANGE_RATE_ROLE = 11; + + // --- Setup --- + function setUp() public { + vm.startPrank(owner); + + // 1. Setup Mock Token + mUSD = new MockERC20("Mock USD", "mUSD", 6); + deal(address(mUSD), userA, 1000e6); + + // 2. Deploy Infrastructure + deployer = new Deployer(owner, Authority(address(0))); + auth = new RolesAuthority(owner, Authority(address(0))); + deployer.setAuthority(auth); + + // Grant owner Deployer role (Role 1) + auth.setRoleCapability( + 1, + address(deployer), + Deployer.deployContract.selector, + true + ); + auth.setUserRole(owner, 1, true); + + // 3. Deploy Yield Vault via Deployer + yieldVault = BoringVault( + payable( + deployer.deployContract( + "Maxi Yield Vault V1.0", + type(BoringVault).creationCode, + abi.encode(owner, "Maxi Yield mUSD", "my-mUSD", 6), + 0 + ) + ) + ); + + yieldAccountant = AccountantWithRateProviders( + deployer.deployContract( + "Maxi Yield Accountant V1.0", + type(AccountantWithRateProviders).creationCode, + abi.encode( + owner, + address(yieldVault), + owner, + 1e6, + address(mUSD), + 1.5e4, + 0.5e4, + 20 hours, + 0, + 0 + ), + 0 + ) + ); + + yieldTeller = TellerWithMultiAssetSupport( + payable( + deployer.deployContract( + "Maxi Yield Teller V1.0", + type(TellerWithMultiAssetSupport).creationCode, + abi.encode( + owner, + address(yieldVault), + address(yieldAccountant), + address(0) + ), + 0 + ) + ) + ); + + yieldDelayedWithdraw = DelayedWithdraw( + deployer.deployContract( + "Maxi Yield DelayedWithdraw V1.0", + type(DelayedWithdraw).creationCode, + abi.encode( + owner, + address(yieldVault), + address(yieldAccountant), + owner + ), + 0 + ) + ); + + // 4. Deploy Points Vault via Deployer + pointsVault = BoringVault( + payable( + deployer.deployContract( + "Points Vault V1.0", + type(BoringVault).creationCode, + abi.encode(owner, "Points mUSD", "pts-mUSD", 6), + 0 + ) + ) + ); + + pointsAccountant = AccountantWithFixedRate( + deployer.deployContract( + "Points Accountant V1.0", + type(AccountantWithFixedRate).creationCode, + abi.encode( + owner, + address(pointsVault), + owner, + 1e6, + address(mUSD), + 1.5e4, + 0.5e4, + 20 hours, + 0, + 0 + ), + 0 + ) + ); + + pointsTeller = TellerWithMultiAssetSupport( + payable( + deployer.deployContract( + "Points Teller V1.0", + type(TellerWithMultiAssetSupport).creationCode, + abi.encode( + owner, + address(pointsVault), + address(pointsAccountant), + address(0) + ), + 0 + ) + ) + ); + + pointsDelayedWithdraw = DelayedWithdraw( + deployer.deployContract( + "Points DelayedWithdraw V1.0", + type(DelayedWithdraw).creationCode, + abi.encode( + owner, + address(pointsVault), + address(pointsAccountant), + owner + ), + 0 + ) + ); + + // 5. Deploy Lens + lens = ArcticArchitectureLens( + deployer.deployContract( + "Arctic Architecture Lens V1.0", + type(ArcticArchitectureLens).creationCode, + hex"", + 0 + ) + ); + + // 6. Permissions & Config + _setupVault( + yieldVault, + yieldAccountant, + yieldTeller, + yieldDelayedWithdraw + ); + _setupVault( + pointsVault, + pointsAccountant, + pointsTeller, + pointsDelayedWithdraw + ); + + vm.stopPrank(); + } + + function _setupVault( + BoringVault v, + AccountantWithRateProviders a, + TellerWithMultiAssetSupport t, + DelayedWithdraw d + ) internal { + v.setAuthority(auth); + a.setAuthority(auth); + t.setAuthority(auth); + d.setAuthority(auth); + + // Minter Role (2) + auth.setRoleCapability( + MINTER_ROLE, + address(v), + BoringVault.enter.selector, + true + ); + auth.setUserRole(address(t), MINTER_ROLE, true); + + // Burner Role (3) + auth.setRoleCapability( + BURNER_ROLE, + address(v), + BoringVault.exit.selector, + true + ); + auth.setUserRole(address(d), BURNER_ROLE, true); + + // Update Rate Role (11) + auth.setRoleCapability( + UPDATE_EXCHANGE_RATE_ROLE, + address(a), + AccountantWithRateProviders.updateExchangeRate.selector, + true + ); + auth.setUserRole(owner, UPDATE_EXCHANGE_RATE_ROLE, true); + + // Owner Role (8) + auth.setRoleCapability( + OWNER_ROLE, + address(t), + TellerWithMultiAssetSupport.setShareLockPeriod.selector, + true + ); + auth.setUserRole(owner, OWNER_ROLE, true); + + // Public capabilities for UI interaction + auth.setPublicCapability( + address(t), + TellerWithMultiAssetSupport.deposit.selector, + true + ); + auth.setPublicCapability( + address(d), + DelayedWithdraw.requestWithdraw.selector, + true + ); + auth.setPublicCapability( + address(d), + DelayedWithdraw.completeWithdraw.selector, + true + ); + + t.setShareLockPeriod(24 hours); + t.updateAssetData(mUSD, true, true, 0); + d.setupWithdrawAsset(mUSD, 0, 7 days, 0, 100); + d.setPullFundsFromVault(true); + v.setBeforeTransferHook(address(t)); + } + + // --- Tests --- + + function testDeterministicDeployment() public { + assertEq( + address(yieldVault), + deployer.getAddress("Maxi Yield Vault V1.0") + ); + assertEq( + address(pointsVault), + deployer.getAddress("Points Vault V1.0") + ); + assertEq( + address(lens), + deployer.getAddress("Arctic Architecture Lens V1.0") + ); + } + + function testYieldVaultMechanism() public { + uint256 amount = 100e6; + vm.startPrank(userA); + mUSD.approve(address(yieldVault), amount); + yieldTeller.deposit(mUSD, amount, 0); + vm.stopPrank(); + + assertEq(lens.exchangeRate(yieldAccountant), 1e6); + + deal( + address(mUSD), + address(yieldVault), + mUSD.balanceOf(address(yieldVault)) + 10e6 + ); + vm.prank(owner); + yieldAccountant.updateExchangeRate(1_100_000); + + assertEq(lens.exchangeRate(yieldAccountant), 1_100_000); + assertEq( + lens.balanceOfInAssets(userA, yieldVault, yieldAccountant), + 110e6 + ); + } + + function testPointsVaultMechanism() public { + uint256 amount = 100e6; + vm.startPrank(userA); + mUSD.approve(address(pointsVault), amount); + pointsTeller.deposit(mUSD, amount, 0); + vm.stopPrank(); + + assertEq(lens.exchangeRate(pointsAccountant), 1e6); + + deal( + address(mUSD), + address(pointsVault), + mUSD.balanceOf(address(pointsVault)) + 10e6 + ); + vm.prank(owner); + pointsAccountant.updateExchangeRate(1_200_000); + + assertEq(lens.exchangeRate(pointsAccountant), 1_000_000); // Fixed at 1.0 + } + + function testShareLockEnforcement() public { + vm.startPrank(userA); + mUSD.approve(address(yieldVault), 10e6); + yieldTeller.deposit(mUSD, 10e6, 0); + + vm.expectRevert(); + yieldVault.transfer(address(0x1), 1e6); + + skip(24 hours + 1); + yieldVault.transfer(address(0x1), 1e6); + vm.stopPrank(); + } +} From 3957046fec7186b548768d3a95f38540ef857da5 Mon Sep 17 00:00:00 2001 From: EvmSerhii Date: Thu, 12 Feb 2026 08:17:02 +0100 Subject: [PATCH 02/11] create config for Mantra deployment --- script/00_MantraMainnetConstants.sol | 82 +++++++++++++++++++++ script/01_DeployDeployer.s.sol | 22 +++--- script/02_DeployMaxiYieldVault.s.sol | 101 ++++++++++++-------------- script/03_DeployPointsVault.s.sol | 102 ++++++++++++--------------- script/04_DeployArcticLens.s.sol | 2 +- 5 files changed, 185 insertions(+), 124 deletions(-) create mode 100644 script/00_MantraMainnetConstants.sol diff --git a/script/00_MantraMainnetConstants.sol b/script/00_MantraMainnetConstants.sol new file mode 100644 index 000000000..332c912ed --- /dev/null +++ b/script/00_MantraMainnetConstants.sol @@ -0,0 +1,82 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.21; + +library MantraMainnetConstants { + // ========================================== + // External Addresses (Mantra Testnet/Mainnet) + // ========================================== + address internal constant mUSD = 0x4B545d0758eda6601B051259bD977125fbdA7ba2; + address internal constant WETH = address(0); + + // ========================================== + // Roles Authority Configuration + // ========================================== + uint8 internal constant MANAGER_ROLE = 1; + uint8 internal constant DEPLOYER_ROLE = 1; + uint8 internal constant MINTER_ROLE = 2; + uint8 internal constant BURNER_ROLE = 3; + uint8 internal constant OWNER_ROLE = 8; + uint8 internal constant MULTISIG_ROLE = 9; + uint8 internal constant UPDATE_EXCHANGE_RATE_ROLE = 11; + + // ========================================== + // Maxi Yield Vault Configuration + // ========================================== + string internal constant MAXI_NAME = "Maxi Yield Vault V1.0"; + string internal constant MAXI_SYMBOL = "my-mUSD"; + uint8 internal constant MAXI_DECIMALS = 6; + string internal constant MAXI_TOKEN_NAME = "Maxi Yield mUSD"; + + // ========================================== + // Points Vault Configuration + // ========================================== + string internal constant POINTS_NAME = "Points Vault V1.0"; + string internal constant POINTS_SYMBOL = "pts-mUSD"; + uint8 internal constant POINTS_DECIMALS = 6; + string internal constant POINTS_TOKEN_NAME = "Points mUSD"; + + // ========================================== + // Accountant Configuration + // ========================================== + // @notice Starting exchange rate (decimals match base asset, so 1e6 = 1.0) + uint96 internal constant ACCOUNTANT_STARTING_EXCHANGE_RATE = 1e6; + + // @notice The allowed upper bound multiplier. 1.5e4 = 15000 bps = 1.5x. + // NewRate must be <= CurrentRate * 1.5 + uint16 internal constant ACCOUNTANT_ALLOWED_EXCHANGE_RATE_CHANGE_UPPER = + 1.5e4; + + // @notice The allowed lower bound multiplier. 0.5e4 = 5000 bps = 0.5x. + // NewRate must be >= CurrentRate * 0.5 + uint16 internal constant ACCOUNTANT_ALLOWED_EXCHANGE_RATE_CHANGE_LOWER = + 0.5e4; + + // @notice Minimum time between exchange rate updates + uint64 internal constant ACCOUNTANT_MINIMUM_UPDATE_DELAY = 20 hours; + + // @notice Annual platform fee in basis points (e.g., 0 = 0%) + uint16 internal constant ACCOUNTANT_PLATFORM_FEE = 0; + + // @notice Performance fee on yield in basis points (e.g., 0 = 0%) + uint16 internal constant ACCOUNTANT_PERFORMANCE_FEE = 0; + + // ========================================== + // Teller Configuration + // ========================================== + uint64 internal constant TELLER_SHARE_LOCK_PERIOD = 86400; // 24 hours + + // ========================================== + // Delayed Withdraw Configuration + // ========================================== + // @notice Seconds before a requested withdrawal can be completed + uint32 internal constant DW_WITHDRAW_DELAY = 0; + + // @notice Window in seconds that a withdrawal can be completed after maturity + uint32 internal constant DW_COMPLETION_WINDOW = 7 days; + + // @notice Fee charged when withdrawal is completed (in basis points) + uint16 internal constant DW_WITHDRAW_FEE = 0; + + // @notice Maximum loss allowed when evaluating exchange rate diff (in basis points, 100 = 1%) + uint16 internal constant DW_MAX_LOSS = 100; +} diff --git a/script/01_DeployDeployer.s.sol b/script/01_DeployDeployer.s.sol index b379381f9..0e2ddd4a8 100644 --- a/script/01_DeployDeployer.s.sol +++ b/script/01_DeployDeployer.s.sol @@ -7,24 +7,21 @@ import { Authority } from "@solmate/auth/authorities/RolesAuthority.sol"; import "forge-std/Script.sol"; +import { + MantraMainnetConstants as Constants +} from "./MantraMainnetConstants.sol"; -/** - * @notice Script to deploy the CREATE3 Deployer and its RolesAuthority. - * @dev Run with: forge script script/01_DeployDeployer.s.sol --rpc-url --broadcast --verify - */ contract DeployDeployer is Script { - uint8 public constant DEPLOYER_ROLE = 1; - function run() external { - uint256 deployerKey = vm.envUint("ETHERFI_LIQUID_DEPLOYER"); + uint256 deployerKey = vm.envUint("MANTRA_DEPLOYER"); address deployerAddr = vm.addr(deployerKey); vm.startBroadcast(deployerKey); - // 1. Deploy Deployer (Owned by deployerAddr, no initial authority) + // 1. Deploy Deployer Deployer deployer = new Deployer(deployerAddr, Authority(address(0))); - // 2. Deploy RolesAuthority for the Deployer (Owned by deployerAddr) + // 2. Deploy RolesAuthority RolesAuthority auth = new RolesAuthority( deployerAddr, Authority(address(0)) @@ -33,15 +30,14 @@ contract DeployDeployer is Script { // 3. Link them deployer.setAuthority(auth); - // 4. Grant deployerAddr permission to deploy through the factory - // We use Role 1 for Deployer Role to match the project's standard + // 4. Grant permissions auth.setRoleCapability( - DEPLOYER_ROLE, + Constants.DEPLOYER_ROLE, address(deployer), Deployer.deployContract.selector, true ); - auth.setUserRole(deployerAddr, DEPLOYER_ROLE, true); + auth.setUserRole(deployerAddr, Constants.DEPLOYER_ROLE, true); vm.stopBroadcast(); diff --git a/script/02_DeployMaxiYieldVault.s.sol b/script/02_DeployMaxiYieldVault.s.sol index 21026fa53..a6c6a7a8b 100644 --- a/script/02_DeployMaxiYieldVault.s.sol +++ b/script/02_DeployMaxiYieldVault.s.sol @@ -16,42 +16,33 @@ import { import {Deployer} from "src/helper/Deployer.sol"; import {ERC20} from "@solmate/tokens/ERC20.sol"; import "forge-std/Script.sol"; +import { + MantraMainnetConstants as Constants +} from "./MantraMainnetConstants.sol"; -/** - * @notice Script to deploy "Maxi Yield" Vault through the Deployer. - * @dev Run with: forge script script/02_DeployMaxiYieldVault.s.sol --rpc-url --broadcast --verify - */ contract DeployMaxiYieldVault is Script { - // Config for Mantra Testnet - address public constant mUSD = 0x4B545d0758eda6601B051259bD977125fbdA7ba2; - address public constant WETH = address(0); - - // Deployer and Auth addresses (deployed in step 01) address public deployerAddr = vm.envAddress("DEPLOYER_CONTRACT_ADDRESS"); address public rolesAuthAddr = vm.envAddress("ROLES_AUTH_CONTRACT_ADDRESS"); - // Standard Project Roles - uint8 public constant MANAGER_ROLE = 1; - uint8 public constant MINTER_ROLE = 2; - uint8 public constant BURNER_ROLE = 3; - uint8 public constant OWNER_ROLE = 8; - uint8 public constant MULTISIG_ROLE = 9; - uint8 public constant UPDATE_EXCHANGE_RATE_ROLE = 11; - function run() external { vm.createSelectFork("mantra"); - uint256 deployerKey = vm.envUint("ETHERFI_LIQUID_DEPLOYER"); + uint256 deployerKey = vm.envUint("MANTRA_DEPLOYER"); address owner = vm.addr(deployerKey); Deployer deployer = Deployer(deployerAddr); RolesAuthority auth = RolesAuthority(rolesAuthAddr); vm.startBroadcast(deployerKey); - // --- 1. Deploy Core Components via Deployer --- + // --- 1. Deploy Core Components --- address vault = deployer.deployContract( - "Maxi Yield Vault V1.0", + Constants.MAXI_NAME, type(BoringVault).creationCode, - abi.encode(owner, "Maxi Yield mUSD", "my-mUSD", 6), + abi.encode( + owner, + Constants.MAXI_TOKEN_NAME, + Constants.MAXI_SYMBOL, + Constants.MAXI_DECIMALS + ), 0 ); @@ -59,16 +50,16 @@ contract DeployMaxiYieldVault is Script { "Maxi Yield Accountant V1.0", type(AccountantWithRateProviders).creationCode, abi.encode( - owner, - vault, - owner, - 1e6, - mUSD, - 1.5e4, - 0.5e4, - 20 hours, - 0, - 0 + owner, // owner + vault, // vault + owner, // payoutAddress + Constants.ACCOUNTANT_STARTING_EXCHANGE_RATE, // startingExchangeRate + Constants.mUSD, // base + Constants.ACCOUNTANT_ALLOWED_EXCHANGE_RATE_CHANGE_UPPER, + Constants.ACCOUNTANT_ALLOWED_EXCHANGE_RATE_CHANGE_LOWER, + Constants.ACCOUNTANT_MINIMUM_UPDATE_DELAY, + Constants.ACCOUNTANT_PLATFORM_FEE, + Constants.ACCOUNTANT_PERFORMANCE_FEE ), 0 ); @@ -76,7 +67,7 @@ contract DeployMaxiYieldVault is Script { address teller = deployer.deployContract( "Maxi Yield Teller V1.0", type(TellerWithMultiAssetSupport).creationCode, - abi.encode(owner, vault, accountant, WETH), + abi.encode(owner, vault, accountant, Constants.WETH), 0 ); @@ -95,61 +86,59 @@ contract DeployMaxiYieldVault is Script { TellerWithMultiAssetSupport(payable(teller)).setAuthority(auth); DelayedWithdraw(delayedWithdraw).setAuthority(auth); - // Minter Role (2) -> Teller can mint shares + // Grant Roles auth.setRoleCapability( - MINTER_ROLE, + Constants.MINTER_ROLE, vault, BoringVault.enter.selector, true ); - auth.setUserRole(teller, MINTER_ROLE, true); + auth.setUserRole(teller, Constants.MINTER_ROLE, true); - // Burner Role (3) -> DelayedWithdraw can burn shares auth.setRoleCapability( - BURNER_ROLE, + Constants.BURNER_ROLE, vault, BoringVault.exit.selector, true ); - auth.setUserRole(delayedWithdraw, BURNER_ROLE, true); + auth.setUserRole(delayedWithdraw, Constants.BURNER_ROLE, true); - // Update Rate Role (11) -> Owner can update accountant exchange rate auth.setRoleCapability( - UPDATE_EXCHANGE_RATE_ROLE, + Constants.UPDATE_EXCHANGE_RATE_ROLE, accountant, AccountantWithRateProviders.updateExchangeRate.selector, true ); - auth.setUserRole(owner, UPDATE_EXCHANGE_RATE_ROLE, true); + auth.setUserRole(owner, Constants.UPDATE_EXCHANGE_RATE_ROLE, true); - // Owner Role (8) -> Broad administrative rights + // Owner Roles auth.setRoleCapability( - OWNER_ROLE, + Constants.OWNER_ROLE, teller, TellerWithMultiAssetSupport.setShareLockPeriod.selector, true ); auth.setRoleCapability( - OWNER_ROLE, + Constants.OWNER_ROLE, teller, TellerWithMultiAssetSupport.updateAssetData.selector, true ); auth.setRoleCapability( - OWNER_ROLE, + Constants.OWNER_ROLE, delayedWithdraw, DelayedWithdraw.setupWithdrawAsset.selector, true ); auth.setRoleCapability( - OWNER_ROLE, + Constants.OWNER_ROLE, delayedWithdraw, DelayedWithdraw.setPullFundsFromVault.selector, true ); - auth.setUserRole(owner, OWNER_ROLE, true); + auth.setUserRole(owner, Constants.OWNER_ROLE, true); - // Public Roles + // Public Capabilities auth.setPublicCapability( teller, TellerWithMultiAssetSupport.deposit.selector, @@ -167,20 +156,22 @@ contract DeployMaxiYieldVault is Script { ); // Logic Config - TellerWithMultiAssetSupport(payable(teller)).setShareLockPeriod(86400); // 24h + TellerWithMultiAssetSupport(payable(teller)).setShareLockPeriod( + Constants.TELLER_SHARE_LOCK_PERIOD + ); TellerWithMultiAssetSupport(payable(teller)).updateAssetData( - ERC20(mUSD), + ERC20(Constants.mUSD), true, true, 0 ); DelayedWithdraw(delayedWithdraw).setupWithdrawAsset( - ERC20(mUSD), - 0, - 7 days, - 0, - 100 + ERC20(Constants.mUSD), + Constants.DW_WITHDRAW_DELAY, + Constants.DW_COMPLETION_WINDOW, + Constants.DW_WITHDRAW_FEE, + Constants.DW_MAX_LOSS ); DelayedWithdraw(delayedWithdraw).setPullFundsFromVault(true); diff --git a/script/03_DeployPointsVault.s.sol b/script/03_DeployPointsVault.s.sol index b196e51d3..640e81397 100644 --- a/script/03_DeployPointsVault.s.sol +++ b/script/03_DeployPointsVault.s.sol @@ -19,42 +19,33 @@ import { import {Deployer} from "src/helper/Deployer.sol"; import {ERC20} from "@solmate/tokens/ERC20.sol"; import "forge-std/Script.sol"; +import { + MantraMainnetConstants as Constants +} from "./MantraMainnetConstants.sol"; -/** - * @notice Script to deploy "Points" Vault through the Deployer. - * @dev Run with: forge script script/03_DeployPointsVault.s.sol --rpc-url --broadcast --verify - */ contract DeployPointsVault is Script { - // Config for Mantra Testnet - address public constant mUSD = 0x4B545d0758eda6601B051259bD977125fbdA7ba2; - address public constant WETH = address(0); - - // Deployer and Auth addresses (deployed in step 01) address public deployerAddr = vm.envAddress("DEPLOYER_CONTRACT_ADDRESS"); address public rolesAuthAddr = vm.envAddress("ROLES_AUTH_CONTRACT_ADDRESS"); - // Standard Project Roles - uint8 public constant MANAGER_ROLE = 1; - uint8 public constant MINTER_ROLE = 2; - uint8 public constant BURNER_ROLE = 3; - uint8 public constant OWNER_ROLE = 8; - uint8 public constant MULTISIG_ROLE = 9; - uint8 public constant UPDATE_EXCHANGE_RATE_ROLE = 11; - function run() external { vm.createSelectFork("mantra"); - uint256 deployerKey = vm.envUint("ETHERFI_LIQUID_DEPLOYER"); + uint256 deployerKey = vm.envUint("MANTRA_DEPLOYER"); address owner = vm.addr(deployerKey); Deployer deployer = Deployer(deployerAddr); RolesAuthority auth = RolesAuthority(rolesAuthAddr); vm.startBroadcast(deployerKey); - // --- 1. Deploy Core Components via Deployer --- + // --- 1. Deploy Core Components --- address vault = deployer.deployContract( - "Points Vault V1.0", + Constants.POINTS_NAME, type(BoringVault).creationCode, - abi.encode(owner, "Points mUSD", "pts-mUSD", 6), + abi.encode( + owner, + Constants.POINTS_TOKEN_NAME, + Constants.POINTS_SYMBOL, + Constants.POINTS_DECIMALS + ), 0 ); @@ -62,16 +53,16 @@ contract DeployPointsVault is Script { "Points Accountant V1.0", type(AccountantWithFixedRate).creationCode, abi.encode( - owner, - vault, - owner, - 1e6, - mUSD, - 1.5e4, - 0.5e4, - 20 hours, - 0, - 0 + owner, // owner + vault, // vault + owner, // payoutAddress + Constants.ACCOUNTANT_STARTING_EXCHANGE_RATE, // startingExchangeRate + Constants.mUSD, // base + Constants.ACCOUNTANT_ALLOWED_EXCHANGE_RATE_CHANGE_UPPER, + Constants.ACCOUNTANT_ALLOWED_EXCHANGE_RATE_CHANGE_LOWER, + Constants.ACCOUNTANT_MINIMUM_UPDATE_DELAY, + Constants.ACCOUNTANT_PLATFORM_FEE, + Constants.ACCOUNTANT_PERFORMANCE_FEE ), 0 ); @@ -79,7 +70,7 @@ contract DeployPointsVault is Script { address teller = deployer.deployContract( "Points Teller V1.0", type(TellerWithMultiAssetSupport).creationCode, - abi.encode(owner, vault, accountant, WETH), + abi.encode(owner, vault, accountant, Constants.WETH), 0 ); @@ -98,61 +89,60 @@ contract DeployPointsVault is Script { TellerWithMultiAssetSupport(payable(teller)).setAuthority(auth); DelayedWithdraw(delayedWithdraw).setAuthority(auth); - // Minter Role (2) -> Teller can mint shares + // Grant Roles auth.setRoleCapability( - MINTER_ROLE, + Constants.MINTER_ROLE, vault, BoringVault.enter.selector, true ); - auth.setUserRole(teller, MINTER_ROLE, true); + auth.setUserRole(teller, Constants.MINTER_ROLE, true); - // Burner Role (3) -> DelayedWithdraw can burn shares auth.setRoleCapability( - BURNER_ROLE, + Constants.BURNER_ROLE, vault, BoringVault.exit.selector, true ); - auth.setUserRole(delayedWithdraw, BURNER_ROLE, true); + auth.setUserRole(delayedWithdraw, Constants.BURNER_ROLE, true); - // Update Rate Role (11) -> Owner can update accountant exchange rate + // Note: Even for FixedRate, we keep the capability in case we swap to a RateProvider later or manual updates auth.setRoleCapability( - UPDATE_EXCHANGE_RATE_ROLE, + Constants.UPDATE_EXCHANGE_RATE_ROLE, accountant, AccountantWithRateProviders.updateExchangeRate.selector, true ); - auth.setUserRole(owner, UPDATE_EXCHANGE_RATE_ROLE, true); + auth.setUserRole(owner, Constants.UPDATE_EXCHANGE_RATE_ROLE, true); - // Owner Role (8) -> Broad administrative rights + // Owner Roles auth.setRoleCapability( - OWNER_ROLE, + Constants.OWNER_ROLE, teller, TellerWithMultiAssetSupport.setShareLockPeriod.selector, true ); auth.setRoleCapability( - OWNER_ROLE, + Constants.OWNER_ROLE, teller, TellerWithMultiAssetSupport.updateAssetData.selector, true ); auth.setRoleCapability( - OWNER_ROLE, + Constants.OWNER_ROLE, delayedWithdraw, DelayedWithdraw.setupWithdrawAsset.selector, true ); auth.setRoleCapability( - OWNER_ROLE, + Constants.OWNER_ROLE, delayedWithdraw, DelayedWithdraw.setPullFundsFromVault.selector, true ); - auth.setUserRole(owner, OWNER_ROLE, true); + auth.setUserRole(owner, Constants.OWNER_ROLE, true); - // Public Roles + // Public Capabilities auth.setPublicCapability( teller, TellerWithMultiAssetSupport.deposit.selector, @@ -170,20 +160,22 @@ contract DeployPointsVault is Script { ); // Logic Config - TellerWithMultiAssetSupport(payable(teller)).setShareLockPeriod(86400); // 24h + TellerWithMultiAssetSupport(payable(teller)).setShareLockPeriod( + Constants.TELLER_SHARE_LOCK_PERIOD + ); TellerWithMultiAssetSupport(payable(teller)).updateAssetData( - ERC20(mUSD), + ERC20(Constants.mUSD), true, true, 0 ); DelayedWithdraw(delayedWithdraw).setupWithdrawAsset( - ERC20(mUSD), - 0, - 7 days, - 0, - 100 + ERC20(Constants.mUSD), + Constants.DW_WITHDRAW_DELAY, + Constants.DW_COMPLETION_WINDOW, + Constants.DW_WITHDRAW_FEE, + Constants.DW_MAX_LOSS ); DelayedWithdraw(delayedWithdraw).setPullFundsFromVault(true); diff --git a/script/04_DeployArcticLens.s.sol b/script/04_DeployArcticLens.s.sol index cb02aba77..55b78d337 100644 --- a/script/04_DeployArcticLens.s.sol +++ b/script/04_DeployArcticLens.s.sol @@ -15,7 +15,7 @@ contract DeployArcticLens is Script { function run() external { vm.createSelectFork("mantra"); - uint256 deployerKey = vm.envUint("ETHERFI_LIQUID_DEPLOYER"); + uint256 deployerKey = vm.envUint("MANTRA_DEPLOYER"); Deployer deployer = Deployer(deployerAddr); vm.startBroadcast(deployerKey); From 2351747db565e4b2723ac1b4a7eb5a64ea7d01c0 Mon Sep 17 00:00:00 2001 From: EvmSerhii Date: Thu, 12 Feb 2026 08:22:36 +0100 Subject: [PATCH 03/11] add mantra mainnet and testnet to foundry --- foundry.toml | 7 +++++-- mantra.example.env | 28 ++++++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 2 deletions(-) create mode 100644 mantra.example.env diff --git a/foundry.toml b/foundry.toml index d1d35b7d5..e030e32fe 100644 --- a/foundry.toml +++ b/foundry.toml @@ -32,7 +32,8 @@ base = "${BASE_RPC_URL}" zircuit = "${ZIRCUIT_RPC_URL}" scroll = "${SCROLL_RPC_URL}" linea = "${LINEA_RPC_URL}" -mantra = "${MANTRA_DUKONG_RPC_URL}" +mantra = "${MANTRA_RPC_URL}" +mantra_dukong = "${MANTRA_DUKONG_RPC_URL}" [etherscan] # mainnet = { key = "${ETHERSCAN_KEY}", url = "https://api.etherscan.io/api" } @@ -42,7 +43,9 @@ mantra = "${MANTRA_DUKONG_RPC_URL}" # arbitrum = { key = "${ARBISCAN_KEY}" } # optimism = { key = "${OPTIMISMSCAN_KEY}" } # base = { key = "${BASESCAN_KEY}" } -mantra = { key = "any_random_string", chain = 5887, url = "https://explorer.dukong.io/api" } +mantra = { key = "any_random_string", chain = 5888, url = "https://blockscout.mantrascan.io/api" } +mantra_dukong = { key = "any_random_string", chain = 5887, url = "https://explorer.dukong.io/api" } + [fmt] FOUNDRY_FMT_LINE_LENGTH = 120 diff --git a/mantra.example.env b/mantra.example.env new file mode 100644 index 000000000..31da2037a --- /dev/null +++ b/mantra.example.env @@ -0,0 +1,28 @@ +# ===================================================================== +# 1. REQUIRED INPUTS (Fill these before running anything) +# ===================================================================== + +# The Private Key of the wallet deploying the contracts. +# Found in all scripts via: vm.envUint("ETHERFI_LIQUID_DEPLOYER") +MANTRA_DEPLOYER=0xYourPrivateKeyHere... + +# RPC URL for Mantra Chain. +# Needed for: vm.createSelectFork("mantra") and the --rpc-url command +MANTRA_RPC_URL=https://rpc.mantrachain.io + +# For testnet deployments +MANTRA_DUKONG_RPC_URL=https://rpc.dukong.io + +# ===================================================================== +# 2. GENERATED ADDRESSES (Fill these AFTER running Script 01) +# ===================================================================== +# Script 01 prints these addresses to the console. +# You must copy/paste them here before running Scripts 02, 03, or 04. + +# The address of the deployed 'Deployer' contract +# Found in Scripts 02, 03, 04 via: vm.envAddress("DEPLOYER_CONTRACT_ADDRESS") +DEPLOYER_CONTRACT_ADDRESS= + +# The address of the deployed 'RolesAuthority' contract +# Found in Scripts 02, 03 via: vm.envAddress("ROLES_AUTH_CONTRACT_ADDRESS") +ROLES_AUTH_CONTRACT_ADDRESS= \ No newline at end of file From d0e9ffa72b0046cba61254bcc3dbcc5d85b1dc54 Mon Sep 17 00:00:00 2001 From: EvmSerhii Date: Thu, 12 Feb 2026 08:32:08 +0100 Subject: [PATCH 04/11] fix config path --- script/01_DeployDeployer.s.sol | 2 +- script/02_DeployMaxiYieldVault.s.sol | 2 +- script/03_DeployPointsVault.s.sol | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/script/01_DeployDeployer.s.sol b/script/01_DeployDeployer.s.sol index 0e2ddd4a8..2ba411d35 100644 --- a/script/01_DeployDeployer.s.sol +++ b/script/01_DeployDeployer.s.sol @@ -9,7 +9,7 @@ import { import "forge-std/Script.sol"; import { MantraMainnetConstants as Constants -} from "./MantraMainnetConstants.sol"; +} from "./00_MantraMainnetConstants.sol"; contract DeployDeployer is Script { function run() external { diff --git a/script/02_DeployMaxiYieldVault.s.sol b/script/02_DeployMaxiYieldVault.s.sol index a6c6a7a8b..6a78e57c7 100644 --- a/script/02_DeployMaxiYieldVault.s.sol +++ b/script/02_DeployMaxiYieldVault.s.sol @@ -18,7 +18,7 @@ import {ERC20} from "@solmate/tokens/ERC20.sol"; import "forge-std/Script.sol"; import { MantraMainnetConstants as Constants -} from "./MantraMainnetConstants.sol"; +} from "./00_MantraMainnetConstants.sol"; contract DeployMaxiYieldVault is Script { address public deployerAddr = vm.envAddress("DEPLOYER_CONTRACT_ADDRESS"); diff --git a/script/03_DeployPointsVault.s.sol b/script/03_DeployPointsVault.s.sol index 640e81397..cfe1fcfb1 100644 --- a/script/03_DeployPointsVault.s.sol +++ b/script/03_DeployPointsVault.s.sol @@ -21,7 +21,7 @@ import {ERC20} from "@solmate/tokens/ERC20.sol"; import "forge-std/Script.sol"; import { MantraMainnetConstants as Constants -} from "./MantraMainnetConstants.sol"; +} from "./00_MantraMainnetConstants.sol"; contract DeployPointsVault is Script { address public deployerAddr = vm.envAddress("DEPLOYER_CONTRACT_ADDRESS"); From a3e32f8a314bd3d4fdc7529a31b13131452a7f12 Mon Sep 17 00:00:00 2001 From: EvmSerhii Date: Thu, 12 Feb 2026 10:10:51 +0100 Subject: [PATCH 05/11] Update config with mainnet toggle --- mantra.example.env | 11 +++- ...etConstants.sol => 00_MantraConstants.sol} | 60 +++++++++---------- script/01_DeployDeployer.s.sol | 4 +- script/02_DeployMaxiYieldVault.s.sol | 39 +++++++----- script/03_DeployPointsVault.s.sol | 33 ++++++---- script/04_DeployArcticLens.s.sol | 10 +--- 6 files changed, 88 insertions(+), 69 deletions(-) rename script/{00_MantraMainnetConstants.sol => 00_MantraConstants.sol} (60%) diff --git a/mantra.example.env b/mantra.example.env index 31da2037a..65158df0e 100644 --- a/mantra.example.env +++ b/mantra.example.env @@ -14,7 +14,16 @@ MANTRA_RPC_URL=https://rpc.mantrachain.io MANTRA_DUKONG_RPC_URL=https://rpc.dukong.io # ===================================================================== -# 2. GENERATED ADDRESSES (Fill these AFTER running Script 01) +# 2. NETWORK TOGGLE +# ===================================================================== + +# Set to 'true' to use Mainnet addresses (mUSD_MAINNET). +# Set to 'false' to use Testnet addresses (mUSD_TESTNET). +# Used as: vm.envOr("MANTRA_MAINNET", false) +MANTRA_MAINNET=false + +# ===================================================================== +# 3. GENERATED ADDRESSES (Fill these AFTER running Script 01) # ===================================================================== # Script 01 prints these addresses to the console. # You must copy/paste them here before running Scripts 02, 03, or 04. diff --git a/script/00_MantraMainnetConstants.sol b/script/00_MantraConstants.sol similarity index 60% rename from script/00_MantraMainnetConstants.sol rename to script/00_MantraConstants.sol index 332c912ed..557835205 100644 --- a/script/00_MantraMainnetConstants.sol +++ b/script/00_MantraConstants.sol @@ -1,12 +1,20 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity 0.8.21; -library MantraMainnetConstants { +library MantraConstants { // ========================================== - // External Addresses (Mantra Testnet/Mainnet) + // External Addresses // ========================================== - address internal constant mUSD = 0x4B545d0758eda6601B051259bD977125fbdA7ba2; - address internal constant WETH = address(0); + + // --- Testnet (Dukia) --- + address internal constant mUSD_TESTNET = + 0x4B545d0758eda6601B051259bD977125fbdA7ba2; + address internal constant WETH_TESTNET = address(0); + + // --- Mainnet (Placeholder) --- + // TODO: Update these before Mainnet deployment + address internal constant mUSD_MAINNET = address(0); + address internal constant WETH_MAINNET = address(0); // ========================================== // Roles Authority Configuration @@ -20,44 +28,41 @@ library MantraMainnetConstants { uint8 internal constant UPDATE_EXCHANGE_RATE_ROLE = 11; // ========================================== - // Maxi Yield Vault Configuration + // Contract Names // ========================================== - string internal constant MAXI_NAME = "Maxi Yield Vault V1.0"; + + // Arctic Lens + string internal constant ARCTIC_LENS_NAME = "Arctic Architecture Lens V1.0"; + + // Maxi Yield Vault + string internal constant MAXI_VAULT_NAME = "Maxi Yield Vault V1.0"; string internal constant MAXI_SYMBOL = "my-mUSD"; uint8 internal constant MAXI_DECIMALS = 6; string internal constant MAXI_TOKEN_NAME = "Maxi Yield mUSD"; + string internal constant MAXI_ACCOUNTANT_NAME = + "Maxi Yield Accountant V1.0"; + string internal constant MAXI_TELLER_NAME = "Maxi Yield Teller V1.0"; + string internal constant MAXI_DW_NAME = "Maxi Yield DelayedWithdraw V1.0"; - // ========================================== - // Points Vault Configuration - // ========================================== - string internal constant POINTS_NAME = "Points Vault V1.0"; + // Points Vault + string internal constant POINTS_VAULT_NAME = "Points Vault V1.0"; string internal constant POINTS_SYMBOL = "pts-mUSD"; uint8 internal constant POINTS_DECIMALS = 6; string internal constant POINTS_TOKEN_NAME = "Points mUSD"; + string internal constant POINTS_ACCOUNTANT_NAME = "Points Accountant V1.0"; + string internal constant POINTS_TELLER_NAME = "Points Teller V1.0"; + string internal constant POINTS_DW_NAME = "Points DelayedWithdraw V1.0"; // ========================================== // Accountant Configuration // ========================================== - // @notice Starting exchange rate (decimals match base asset, so 1e6 = 1.0) uint96 internal constant ACCOUNTANT_STARTING_EXCHANGE_RATE = 1e6; - - // @notice The allowed upper bound multiplier. 1.5e4 = 15000 bps = 1.5x. - // NewRate must be <= CurrentRate * 1.5 uint16 internal constant ACCOUNTANT_ALLOWED_EXCHANGE_RATE_CHANGE_UPPER = 1.5e4; - - // @notice The allowed lower bound multiplier. 0.5e4 = 5000 bps = 0.5x. - // NewRate must be >= CurrentRate * 0.5 uint16 internal constant ACCOUNTANT_ALLOWED_EXCHANGE_RATE_CHANGE_LOWER = 0.5e4; - - // @notice Minimum time between exchange rate updates uint64 internal constant ACCOUNTANT_MINIMUM_UPDATE_DELAY = 20 hours; - - // @notice Annual platform fee in basis points (e.g., 0 = 0%) uint16 internal constant ACCOUNTANT_PLATFORM_FEE = 0; - - // @notice Performance fee on yield in basis points (e.g., 0 = 0%) uint16 internal constant ACCOUNTANT_PERFORMANCE_FEE = 0; // ========================================== @@ -68,15 +73,8 @@ library MantraMainnetConstants { // ========================================== // Delayed Withdraw Configuration // ========================================== - // @notice Seconds before a requested withdrawal can be completed uint32 internal constant DW_WITHDRAW_DELAY = 0; - - // @notice Window in seconds that a withdrawal can be completed after maturity uint32 internal constant DW_COMPLETION_WINDOW = 7 days; - - // @notice Fee charged when withdrawal is completed (in basis points) uint16 internal constant DW_WITHDRAW_FEE = 0; - - // @notice Maximum loss allowed when evaluating exchange rate diff (in basis points, 100 = 1%) - uint16 internal constant DW_MAX_LOSS = 100; + uint16 internal constant DW_MAX_LOSS = 100; // 1% } diff --git a/script/01_DeployDeployer.s.sol b/script/01_DeployDeployer.s.sol index 2ba411d35..110a7d63b 100644 --- a/script/01_DeployDeployer.s.sol +++ b/script/01_DeployDeployer.s.sol @@ -7,9 +7,7 @@ import { Authority } from "@solmate/auth/authorities/RolesAuthority.sol"; import "forge-std/Script.sol"; -import { - MantraMainnetConstants as Constants -} from "./00_MantraMainnetConstants.sol"; +import {MantraConstants as Constants} from "./00_MantraConstants.sol"; contract DeployDeployer is Script { function run() external { diff --git a/script/02_DeployMaxiYieldVault.s.sol b/script/02_DeployMaxiYieldVault.s.sol index 6a78e57c7..686860145 100644 --- a/script/02_DeployMaxiYieldVault.s.sol +++ b/script/02_DeployMaxiYieldVault.s.sol @@ -16,9 +16,7 @@ import { import {Deployer} from "src/helper/Deployer.sol"; import {ERC20} from "@solmate/tokens/ERC20.sol"; import "forge-std/Script.sol"; -import { - MantraMainnetConstants as Constants -} from "./00_MantraMainnetConstants.sol"; +import {MantraConstants as Constants} from "./00_MantraConstants.sol"; contract DeployMaxiYieldVault is Script { address public deployerAddr = vm.envAddress("DEPLOYER_CONTRACT_ADDRESS"); @@ -28,6 +26,16 @@ contract DeployMaxiYieldVault is Script { vm.createSelectFork("mantra"); uint256 deployerKey = vm.envUint("MANTRA_DEPLOYER"); address owner = vm.addr(deployerKey); + + // Environment Toggle + bool isMainnet = vm.envOr("MANTRA_MAINNET", false); + address mUSD = isMainnet + ? Constants.mUSD_MAINNET + : Constants.mUSD_TESTNET; + address WETH = isMainnet + ? Constants.WETH_MAINNET + : Constants.WETH_TESTNET; + Deployer deployer = Deployer(deployerAddr); RolesAuthority auth = RolesAuthority(rolesAuthAddr); @@ -35,7 +43,7 @@ contract DeployMaxiYieldVault is Script { // --- 1. Deploy Core Components --- address vault = deployer.deployContract( - Constants.MAXI_NAME, + Constants.MAXI_VAULT_NAME, type(BoringVault).creationCode, abi.encode( owner, @@ -47,14 +55,14 @@ contract DeployMaxiYieldVault is Script { ); address accountant = deployer.deployContract( - "Maxi Yield Accountant V1.0", + Constants.MAXI_ACCOUNTANT_NAME, type(AccountantWithRateProviders).creationCode, abi.encode( owner, // owner vault, // vault owner, // payoutAddress Constants.ACCOUNTANT_STARTING_EXCHANGE_RATE, // startingExchangeRate - Constants.mUSD, // base + mUSD, // base (Dynamic) Constants.ACCOUNTANT_ALLOWED_EXCHANGE_RATE_CHANGE_UPPER, Constants.ACCOUNTANT_ALLOWED_EXCHANGE_RATE_CHANGE_LOWER, Constants.ACCOUNTANT_MINIMUM_UPDATE_DELAY, @@ -65,14 +73,14 @@ contract DeployMaxiYieldVault is Script { ); address teller = deployer.deployContract( - "Maxi Yield Teller V1.0", + Constants.MAXI_TELLER_NAME, type(TellerWithMultiAssetSupport).creationCode, - abi.encode(owner, vault, accountant, Constants.WETH), + abi.encode(owner, vault, accountant, WETH), // WETH (Dynamic) 0 ); address delayedWithdraw = deployer.deployContract( - "Maxi Yield DelayedWithdraw V1.0", + Constants.MAXI_DW_NAME, type(DelayedWithdraw).creationCode, abi.encode(owner, vault, accountant, owner), 0 @@ -160,14 +168,14 @@ contract DeployMaxiYieldVault is Script { Constants.TELLER_SHARE_LOCK_PERIOD ); TellerWithMultiAssetSupport(payable(teller)).updateAssetData( - ERC20(Constants.mUSD), + ERC20(mUSD), true, true, 0 ); DelayedWithdraw(delayedWithdraw).setupWithdrawAsset( - ERC20(Constants.mUSD), + ERC20(mUSD), Constants.DW_WITHDRAW_DELAY, Constants.DW_COMPLETION_WINDOW, Constants.DW_WITHDRAW_FEE, @@ -179,9 +187,10 @@ contract DeployMaxiYieldVault is Script { vm.stopBroadcast(); - console.log("Maxi Yield Vault:", vault); - console.log("Maxi Yield Accountant:", accountant); - console.log("Maxi Yield Teller:", teller); - console.log("Maxi Yield DelayedWithdraw:", delayedWithdraw); + console.log("Environment:", isMainnet ? "Mainnet" : "Testnet"); + console.log("Yield Vault:", vault); + console.log("Yield Accountant:", accountant); + console.log("Yield Teller:", teller); + console.log("Yield DelayedWithdraw:", delayedWithdraw); } } diff --git a/script/03_DeployPointsVault.s.sol b/script/03_DeployPointsVault.s.sol index cfe1fcfb1..ea38226c9 100644 --- a/script/03_DeployPointsVault.s.sol +++ b/script/03_DeployPointsVault.s.sol @@ -19,9 +19,7 @@ import { import {Deployer} from "src/helper/Deployer.sol"; import {ERC20} from "@solmate/tokens/ERC20.sol"; import "forge-std/Script.sol"; -import { - MantraMainnetConstants as Constants -} from "./00_MantraMainnetConstants.sol"; +import {MantraConstants as Constants} from "./00_MantraConstants.sol"; contract DeployPointsVault is Script { address public deployerAddr = vm.envAddress("DEPLOYER_CONTRACT_ADDRESS"); @@ -31,6 +29,16 @@ contract DeployPointsVault is Script { vm.createSelectFork("mantra"); uint256 deployerKey = vm.envUint("MANTRA_DEPLOYER"); address owner = vm.addr(deployerKey); + + // Environment Toggle + bool isMainnet = vm.envOr("MANTRA_MAINNET", false); + address mUSD = isMainnet + ? Constants.mUSD_MAINNET + : Constants.mUSD_TESTNET; + address WETH = isMainnet + ? Constants.WETH_MAINNET + : Constants.WETH_TESTNET; + Deployer deployer = Deployer(deployerAddr); RolesAuthority auth = RolesAuthority(rolesAuthAddr); @@ -38,7 +46,7 @@ contract DeployPointsVault is Script { // --- 1. Deploy Core Components --- address vault = deployer.deployContract( - Constants.POINTS_NAME, + Constants.POINTS_VAULT_NAME, type(BoringVault).creationCode, abi.encode( owner, @@ -50,14 +58,14 @@ contract DeployPointsVault is Script { ); address accountant = deployer.deployContract( - "Points Accountant V1.0", + Constants.POINTS_ACCOUNTANT_NAME, type(AccountantWithFixedRate).creationCode, abi.encode( owner, // owner vault, // vault owner, // payoutAddress Constants.ACCOUNTANT_STARTING_EXCHANGE_RATE, // startingExchangeRate - Constants.mUSD, // base + mUSD, // base (Dynamic) Constants.ACCOUNTANT_ALLOWED_EXCHANGE_RATE_CHANGE_UPPER, Constants.ACCOUNTANT_ALLOWED_EXCHANGE_RATE_CHANGE_LOWER, Constants.ACCOUNTANT_MINIMUM_UPDATE_DELAY, @@ -68,14 +76,14 @@ contract DeployPointsVault is Script { ); address teller = deployer.deployContract( - "Points Teller V1.0", + Constants.POINTS_TELLER_NAME, type(TellerWithMultiAssetSupport).creationCode, - abi.encode(owner, vault, accountant, Constants.WETH), + abi.encode(owner, vault, accountant, WETH), // WETH (Dynamic) 0 ); address delayedWithdraw = deployer.deployContract( - "Points DelayedWithdraw V1.0", + Constants.POINTS_DW_NAME, type(DelayedWithdraw).creationCode, abi.encode(owner, vault, accountant, owner), 0 @@ -106,7 +114,7 @@ contract DeployPointsVault is Script { ); auth.setUserRole(delayedWithdraw, Constants.BURNER_ROLE, true); - // Note: Even for FixedRate, we keep the capability in case we swap to a RateProvider later or manual updates + // Update Rate Role auth.setRoleCapability( Constants.UPDATE_EXCHANGE_RATE_ROLE, accountant, @@ -164,14 +172,14 @@ contract DeployPointsVault is Script { Constants.TELLER_SHARE_LOCK_PERIOD ); TellerWithMultiAssetSupport(payable(teller)).updateAssetData( - ERC20(Constants.mUSD), + ERC20(mUSD), true, true, 0 ); DelayedWithdraw(delayedWithdraw).setupWithdrawAsset( - ERC20(Constants.mUSD), + ERC20(mUSD), Constants.DW_WITHDRAW_DELAY, Constants.DW_COMPLETION_WINDOW, Constants.DW_WITHDRAW_FEE, @@ -183,6 +191,7 @@ contract DeployPointsVault is Script { vm.stopBroadcast(); + console.log("Environment:", isMainnet ? "Mainnet" : "Testnet"); console.log("Points Vault:", vault); console.log("Points Accountant (Fixed):", accountant); console.log("Points Teller:", teller); diff --git a/script/04_DeployArcticLens.s.sol b/script/04_DeployArcticLens.s.sol index 55b78d337..bee17bea6 100644 --- a/script/04_DeployArcticLens.s.sol +++ b/script/04_DeployArcticLens.s.sol @@ -4,13 +4,9 @@ pragma solidity 0.8.21; import {ArcticArchitectureLens} from "src/helper/ArcticArchitectureLens.sol"; import {Deployer} from "src/helper/Deployer.sol"; import "forge-std/Script.sol"; +import {MantraConstants as Constants} from "./00_MantraConstants.sol"; -/** - * @notice Script to deploy ArcticArchitectureLens through the Deployer. - * @dev Run with: forge script script/04_DeployArcticLens.s.sol --rpc-url --broadcast --verify - */ contract DeployArcticLens is Script { - // Deployer address (deployed in step 01) address public deployerAddr = vm.envAddress("DEPLOYER_CONTRACT_ADDRESS"); function run() external { @@ -20,9 +16,9 @@ contract DeployArcticLens is Script { vm.startBroadcast(deployerKey); - // Deploy Lens via Deployer + // Deploy Lens via Deployer using Constants name address lens = deployer.deployContract( - "Arctic Architecture Lens V1.0", + Constants.ARCTIC_LENS_NAME, type(ArcticArchitectureLens).creationCode, hex"", 0 From 7a4a00ea809385e0a95f64c2941e2038bdbeaa63 Mon Sep 17 00:00:00 2001 From: maxKMyt Date: Thu, 12 Feb 2026 12:45:34 +0300 Subject: [PATCH 06/11] Minor naming updates --- script/00_MantraConstants.sol | 21 ++++++++++----------- script/04_DeployArcticLens.s.sol | 2 +- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/script/00_MantraConstants.sol b/script/00_MantraConstants.sol index 557835205..5c3532e5e 100644 --- a/script/00_MantraConstants.sol +++ b/script/00_MantraConstants.sol @@ -6,15 +6,14 @@ library MantraConstants { // External Addresses // ========================================== - // --- Testnet (Dukia) --- + // --- Testnet (Dukong) --- address internal constant mUSD_TESTNET = 0x4B545d0758eda6601B051259bD977125fbdA7ba2; - address internal constant WETH_TESTNET = address(0); + address internal constant WETH_TESTNET = address(0); // not used // --- Mainnet (Placeholder) --- - // TODO: Update these before Mainnet deployment - address internal constant mUSD_MAINNET = address(0); - address internal constant WETH_MAINNET = address(0); + address internal constant mUSD_MAINNET = 0xd2b95283011E47257917770D28Bb3EE44c849f6F; + address internal constant WETH_MAINNET = address(0); // not used // ========================================== // Roles Authority Configuration @@ -32,13 +31,13 @@ library MantraConstants { // ========================================== // Arctic Lens - string internal constant ARCTIC_LENS_NAME = "Arctic Architecture Lens V1.0"; + string internal constant ARCTIC_LENS_NAME = "Lens V1.0"; // Maxi Yield Vault string internal constant MAXI_VAULT_NAME = "Maxi Yield Vault V1.0"; - string internal constant MAXI_SYMBOL = "my-mUSD"; + string internal constant MAXI_SYMBOL = "MY-mantraUSD"; uint8 internal constant MAXI_DECIMALS = 6; - string internal constant MAXI_TOKEN_NAME = "Maxi Yield mUSD"; + string internal constant MAXI_TOKEN_NAME = "Maxi Yield mantraUSD"; string internal constant MAXI_ACCOUNTANT_NAME = "Maxi Yield Accountant V1.0"; string internal constant MAXI_TELLER_NAME = "Maxi Yield Teller V1.0"; @@ -46,9 +45,9 @@ library MantraConstants { // Points Vault string internal constant POINTS_VAULT_NAME = "Points Vault V1.0"; - string internal constant POINTS_SYMBOL = "pts-mUSD"; + string internal constant POINTS_SYMBOL = "PTS-mantraUSD"; uint8 internal constant POINTS_DECIMALS = 6; - string internal constant POINTS_TOKEN_NAME = "Points mUSD"; + string internal constant POINTS_TOKEN_NAME = "Points mantraUSD"; string internal constant POINTS_ACCOUNTANT_NAME = "Points Accountant V1.0"; string internal constant POINTS_TELLER_NAME = "Points Teller V1.0"; string internal constant POINTS_DW_NAME = "Points DelayedWithdraw V1.0"; @@ -73,7 +72,7 @@ library MantraConstants { // ========================================== // Delayed Withdraw Configuration // ========================================== - uint32 internal constant DW_WITHDRAW_DELAY = 0; + uint32 internal constant DW_WITHDRAW_DELAY = 0; // zero delay on withdraw uint32 internal constant DW_COMPLETION_WINDOW = 7 days; uint16 internal constant DW_WITHDRAW_FEE = 0; uint16 internal constant DW_MAX_LOSS = 100; // 1% diff --git a/script/04_DeployArcticLens.s.sol b/script/04_DeployArcticLens.s.sol index bee17bea6..6cafa56be 100644 --- a/script/04_DeployArcticLens.s.sol +++ b/script/04_DeployArcticLens.s.sol @@ -26,6 +26,6 @@ contract DeployArcticLens is Script { vm.stopBroadcast(); - console.log("ArcticArchitectureLens deployed at:", lens); + console.log("Lens deployed at:", lens); } } From f8eff2bce7c5520f0252b0e01a1b611fd96a0865 Mon Sep 17 00:00:00 2001 From: EvmSerhii Date: Thu, 12 Feb 2026 15:29:53 +0100 Subject: [PATCH 07/11] Add description on roles in config --- script/00_MantraConstants.sol | 44 +++++++++++++++++++++++++++++++---- 1 file changed, 40 insertions(+), 4 deletions(-) diff --git a/script/00_MantraConstants.sol b/script/00_MantraConstants.sol index 5c3532e5e..b87bc2044 100644 --- a/script/00_MantraConstants.sol +++ b/script/00_MantraConstants.sol @@ -9,21 +9,57 @@ library MantraConstants { // --- Testnet (Dukong) --- address internal constant mUSD_TESTNET = 0x4B545d0758eda6601B051259bD977125fbdA7ba2; - address internal constant WETH_TESTNET = address(0); // not used + address internal constant WETH_TESTNET = address(0); // not used // --- Mainnet (Placeholder) --- - address internal constant mUSD_MAINNET = 0xd2b95283011E47257917770D28Bb3EE44c849f6F; - address internal constant WETH_MAINNET = address(0); // not used + address internal constant mUSD_MAINNET = + 0xd2b95283011E47257917770D28Bb3EE44c849f6F; + address internal constant WETH_MAINNET = address(0); // not used // ========================================== // Roles Authority Configuration // ========================================== + + /* + Purpose: Operational permissions + (configure vault/teller/strategies, pause/unpause, etc.) + Should be: Multisig or admin EOA + */ uint8 internal constant MANAGER_ROLE = 1; uint8 internal constant DEPLOYER_ROLE = 1; + + /* + Purpose: Minting shares/tokens + (usually Teller or Vault) + Should be: Teller contract + (or vault contract depending on design) + */ uint8 internal constant MINTER_ROLE = 2; + + /* + Purpose: Burning shares/tokens on withdraw + Should be: DelayedWithdraw contract + */ uint8 internal constant BURNER_ROLE = 3; + + /* + Purpose: Owner role + (usually the deployer or multisig) + Should be: Multisig or admin EOA + */ uint8 internal constant OWNER_ROLE = 8; + + /* + Purpose: Separate privileged operations + (emergency, config, upgrade hooks) + Should be: Multisig + */ uint8 internal constant MULTISIG_ROLE = 9; + + /* + Purpose: Update exchange rate role + Should be: Updater bot account (EOA with key) or updater contract + */ uint8 internal constant UPDATE_EXCHANGE_RATE_ROLE = 11; // ========================================== @@ -72,7 +108,7 @@ library MantraConstants { // ========================================== // Delayed Withdraw Configuration // ========================================== - uint32 internal constant DW_WITHDRAW_DELAY = 0; // zero delay on withdraw + uint32 internal constant DW_WITHDRAW_DELAY = 0; // zero delay on withdraw uint32 internal constant DW_COMPLETION_WINDOW = 7 days; uint16 internal constant DW_WITHDRAW_FEE = 0; uint16 internal constant DW_MAX_LOSS = 100; // 1% From e910cdfa165ae5bec023bc659e9967a5eae7cead Mon Sep 17 00:00:00 2001 From: maxKMyt Date: Tue, 17 Feb 2026 11:34:21 +0300 Subject: [PATCH 08/11] Update role description --- script/00_MantraConstants.sol | 90 ++++++++++++++++++++++++----------- 1 file changed, 63 insertions(+), 27 deletions(-) diff --git a/script/00_MantraConstants.sol b/script/00_MantraConstants.sol index b87bc2044..f35331419 100644 --- a/script/00_MantraConstants.sol +++ b/script/00_MantraConstants.sol @@ -20,45 +20,81 @@ library MantraConstants { // Roles Authority Configuration // ========================================== - /* - Purpose: Operational permissions - (configure vault/teller/strategies, pause/unpause, etc.) - Should be: Multisig or admin EOA + /* + DEPLOYER_ROLE (id=1) + Purpose: Permission to deploy contracts via helper Deployer. + Applied in current deploy scripts: + - script/01_DeployDeployer.s.sol: + capability: Deployer.deployContract() + contract: Deployer + grantee: deployer EOA (MANTRA_DEPLOYER) + Role holder: dedicated deployer key or deployment multisig. */ - uint8 internal constant MANAGER_ROLE = 1; uint8 internal constant DEPLOYER_ROLE = 1; - /* - Purpose: Minting shares/tokens - (usually Teller or Vault) - Should be: Teller contract - (or vault contract depending on design) + /* + MANAGER_ROLE (id=1) + Purpose: Generic operational/admin permissions (manage/pause/config/update). + Applied in current deploy scripts: + - Not explicitly granted in 01-04 scripts. + - Same id as DEPLOYER_ROLE, so any capability attached to role id=1 + is effectively shared between MANAGER_ROLE and DEPLOYER_ROLE. + Typical holder: Multisig or admin EOA. + */ + uint8 internal constant MANAGER_ROLE = DEPLOYER_ROLE; + + /* + MINTER_ROLE (id=2) + Purpose: Mint BoringVault shares through vault.enter(). + Applied in current deploy scripts: + - script/02_DeployMaxiYieldVault.s.sol: + - script/03_DeployPointsVault.s.sol: + capability: BoringVault.enter() + contract: Maxi Yield BoringVault, Points BoringVault + grantee: Maxi Yield TellerWithMultiAssetSupport, Points TellerWithMultiAssetSupport + Role holder: Teller contract that processes deposits. */ uint8 internal constant MINTER_ROLE = 2; - /* - Purpose: Burning shares/tokens on withdraw - Should be: DelayedWithdraw contract + /* + BURNER_ROLE (id=3) + Purpose: Burn BoringVault shares through vault.exit() during withdrawals/refunds. + Applied in current deploy scripts: + - script/02_DeployMaxiYieldVault.s.sol: + - script/03_DeployPointsVault.s.sol: + capability: BoringVault.exit() + contract: Maxi Yield BoringVault, Points BoringVault + grantee: Maxi Yield DelayedWithdraw, Points DelayedWithdraw + Role holder: withdrawal coordinator contract (DelayedWithdraw). */ uint8 internal constant BURNER_ROLE = 3; - /* - Purpose: Owner role - (usually the deployer or multisig) - Should be: Multisig or admin EOA + /* + OWNER_ROLE (id=8) + Purpose: Day-to-day configuration for Teller/Withdraw flows. + Applied in current deploy scripts: + - script/02_DeployMaxiYieldVault.s.sol: + - script/03_DeployPointsVault.s.sol: + TellerWithMultiAssetSupport.setShareLockPeriod() + TellerWithMultiAssetSupport.updateAssetData() + DelayedWithdraw.setupWithdrawAsset() + DelayedWithdraw.setPullFundsFromVault() + contract: Maxi Yield | Points TellerWithMultiAssetSupport, Maxi Yield | Points DelayedWithdraw + grantee: owner EOA (MANTRA_DEPLOYER) + Role holder: operations multisig (preferable) or trusted admin EOA. */ uint8 internal constant OWNER_ROLE = 8; - /* - Purpose: Separate privileged operations - (emergency, config, upgrade hooks) - Should be: Multisig - */ - uint8 internal constant MULTISIG_ROLE = 9; - - /* - Purpose: Update exchange rate role - Should be: Updater bot account (EOA with key) or updater contract + /* + UPDATE_EXCHANGE_RATE_ROLE (id=11) + Purpose: Call accountant.updateExchangeRate() to move vault share price. + Applied in current deploy scripts: + - script/02_DeployMaxiYieldVault.s.sol: + - script/03_DeployPointsVault.s.sol: + contract: Maxi Yield AccountantWithRateProviders, Points AccountantWithFixedRate + grantee: owner EOA (MANTRA_DEPLOYER) + capability: AccountantWithRateProviders.updateExchangeRate() + Role holder: dedicated updater bot/service with strict monitoring. */ uint8 internal constant UPDATE_EXCHANGE_RATE_ROLE = 11; From 9b4f095a9cec8241852829ed4e222c8517eab2db Mon Sep 17 00:00:00 2001 From: EvmSerhii Date: Tue, 17 Feb 2026 12:28:16 +0100 Subject: [PATCH 09/11] edit config and add mainnet toggle --- script/00_MantraConstants.sol | 36 ++++++++++++-- script/02_DeployMaxiYieldVault.s.sol | 73 +++++++++++++++++++++++++++- script/03_DeployPointsVault.s.sol | 70 ++++++++++++++++++++++++-- 3 files changed, 170 insertions(+), 9 deletions(-) diff --git a/script/00_MantraConstants.sol b/script/00_MantraConstants.sol index f35331419..46f23df44 100644 --- a/script/00_MantraConstants.sol +++ b/script/00_MantraConstants.sol @@ -3,18 +3,46 @@ pragma solidity 0.8.21; library MantraConstants { // ========================================== - // External Addresses + // USER CONFIGURATION // ========================================== + /* + EXAMPLE: + 1. RATE_UPDATER: The address (e.g. Cron Job Bot) that updates exchange rates. + 2. MANAGER: The address (e.g. Multisig) for pausing/emergency actions. + 3. OWNER: The address (e.g. Multisig) that will OWN the system after deployment. + */ + // --- Testnet (Dukong) --- + address internal constant RATE_UPDATER_TESTNET = + 0x0000000000000000000000000000000000000000; + address internal constant MANAGER_TESTNET = + 0x0000000000000000000000000000000000000000; + address internal constant OWNER_TESTNET = + 0x0000000000000000000000000000000000000000; + + // --- Mainnet --- + address internal constant RATE_UPDATER_MAINNET = + 0x0000000000000000000000000000000000000000; + address internal constant MANAGER_MAINNET = + 0x0000000000000000000000000000000000000000; + address internal constant OWNER_MAINNET = + 0x0000000000000000000000000000000000000000; + + // ========================================== + // INTERNAL CONSTANTS + // ========================================== + + // --- External Tokens --- + // Testnet (Dukong) address internal constant mUSD_TESTNET = 0x4B545d0758eda6601B051259bD977125fbdA7ba2; - address internal constant WETH_TESTNET = address(0); // not used + address internal constant WETH_TESTNET = address(0); - // --- Mainnet (Placeholder) --- + // Mainnet (Placeholder) address internal constant mUSD_MAINNET = 0xd2b95283011E47257917770D28Bb3EE44c849f6F; - address internal constant WETH_MAINNET = address(0); // not used + address internal constant WETH_MAINNET = address(0); // ========================================== // Roles Authority Configuration diff --git a/script/02_DeployMaxiYieldVault.s.sol b/script/02_DeployMaxiYieldVault.s.sol index 686860145..99bcc6037 100644 --- a/script/02_DeployMaxiYieldVault.s.sol +++ b/script/02_DeployMaxiYieldVault.s.sol @@ -94,7 +94,21 @@ contract DeployMaxiYieldVault is Script { TellerWithMultiAssetSupport(payable(teller)).setAuthority(auth); DelayedWithdraw(delayedWithdraw).setAuthority(auth); + // --- Role Configuration --- + address rateUpdater = isMainnet + ? Constants.RATE_UPDATER_MAINNET + : Constants.RATE_UPDATER_TESTNET; + address manager = isMainnet + ? Constants.MANAGER_MAINNET + : Constants.MANAGER_TESTNET; + + // Fallback to Owner if not set (for safety/testing) + if (rateUpdater == address(0)) rateUpdater = owner; + if (manager == address(0)) manager = owner; + // Grant Roles + + // MINTER_ROLE (Teller) auth.setRoleCapability( Constants.MINTER_ROLE, vault, @@ -103,6 +117,7 @@ contract DeployMaxiYieldVault is Script { ); auth.setUserRole(teller, Constants.MINTER_ROLE, true); + // BURNER_ROLE (DelayedWithdraw, BoringVault) auth.setRoleCapability( Constants.BURNER_ROLE, vault, @@ -111,15 +126,27 @@ contract DeployMaxiYieldVault is Script { ); auth.setUserRole(delayedWithdraw, Constants.BURNER_ROLE, true); + // UPDATE_EXCHANGE_RATE_ROLE (Rate Updater) auth.setRoleCapability( Constants.UPDATE_EXCHANGE_RATE_ROLE, accountant, AccountantWithRateProviders.updateExchangeRate.selector, true ); - auth.setUserRole(owner, Constants.UPDATE_EXCHANGE_RATE_ROLE, true); + auth.setUserRole( + rateUpdater, + Constants.UPDATE_EXCHANGE_RATE_ROLE, + true + ); - // Owner Roles + // MANAGER_ROLE (Manager) + // Since MANAGER_ROLE = DEPLOYER_ROLE = 1, and Deployer already has it, + // we explicitly grant it to the manager address if different. + // Capabilities for Manager (e.g. manage on Vault if needed) + // For now, we just ensure the user has the role. + auth.setUserRole(manager, Constants.MANAGER_ROLE, true); + + // OWNER_ROLE (Owner - kept as deployer/owner for now) auth.setRoleCapability( Constants.OWNER_ROLE, teller, @@ -185,6 +212,48 @@ contract DeployMaxiYieldVault is Script { BoringVault(payable(vault)).setBeforeTransferHook(teller); + // --- Ownership Transfer --- + // 1. Identify Target Owner + address mantraOwner = vm.envOr("MANTRA_OWNER", address(0)); + if (mantraOwner == address(0)) { + mantraOwner = isMainnet + ? Constants.OWNER_MAINNET + : Constants.OWNER_TESTNET; + } + + // 2. Perform Transfer if valid and different from deployer + if (mantraOwner != address(0) && mantraOwner != owner) { + console.log("Transferring ownership to:", mantraOwner); + + // Grant OWNER_ROLE to new owner first + if (!auth.doesUserHaveRole(mantraOwner, Constants.OWNER_ROLE)) { + auth.setUserRole(mantraOwner, Constants.OWNER_ROLE, true); + } + + // Transfer Auth Ownership of components + if (BoringVault(payable(vault)).owner() != mantraOwner) + BoringVault(payable(vault)).transferOwnership(mantraOwner); + if (AccountantWithRateProviders(accountant).owner() != mantraOwner) + AccountantWithRateProviders(accountant).transferOwnership( + mantraOwner + ); + if ( + TellerWithMultiAssetSupport(payable(teller)).owner() != + mantraOwner + ) + TellerWithMultiAssetSupport(payable(teller)).transferOwnership( + mantraOwner + ); + if (DelayedWithdraw(delayedWithdraw).owner() != mantraOwner) + DelayedWithdraw(delayedWithdraw).transferOwnership(mantraOwner); + + // Revoke Roles from Deployer + if (auth.doesUserHaveRole(owner, Constants.OWNER_ROLE)) { + auth.setUserRole(owner, Constants.OWNER_ROLE, false); + console.log("Revoked OWNER_ROLE from deployer"); + } + } + vm.stopBroadcast(); console.log("Environment:", isMainnet ? "Mainnet" : "Testnet"); diff --git a/script/03_DeployPointsVault.s.sol b/script/03_DeployPointsVault.s.sol index ea38226c9..c1b3a1add 100644 --- a/script/03_DeployPointsVault.s.sol +++ b/script/03_DeployPointsVault.s.sol @@ -97,7 +97,21 @@ contract DeployPointsVault is Script { TellerWithMultiAssetSupport(payable(teller)).setAuthority(auth); DelayedWithdraw(delayedWithdraw).setAuthority(auth); + // --- Role Configuration --- + address rateUpdater = isMainnet + ? Constants.RATE_UPDATER_MAINNET + : Constants.RATE_UPDATER_TESTNET; + address manager = isMainnet + ? Constants.MANAGER_MAINNET + : Constants.MANAGER_TESTNET; + + // Fallback to Owner if not set (for safety/testing) + if (rateUpdater == address(0)) rateUpdater = owner; + if (manager == address(0)) manager = owner; + // Grant Roles + + // MINTER_ROLE (Teller) auth.setRoleCapability( Constants.MINTER_ROLE, vault, @@ -106,6 +120,7 @@ contract DeployPointsVault is Script { ); auth.setUserRole(teller, Constants.MINTER_ROLE, true); + // BURNER_ROLE (DelayedWithdraw, BoringVault) auth.setRoleCapability( Constants.BURNER_ROLE, vault, @@ -114,16 +129,23 @@ contract DeployPointsVault is Script { ); auth.setUserRole(delayedWithdraw, Constants.BURNER_ROLE, true); - // Update Rate Role + // UPDATE_EXCHANGE_RATE_ROLE (Rate Updater) auth.setRoleCapability( Constants.UPDATE_EXCHANGE_RATE_ROLE, accountant, AccountantWithRateProviders.updateExchangeRate.selector, true ); - auth.setUserRole(owner, Constants.UPDATE_EXCHANGE_RATE_ROLE, true); + auth.setUserRole( + rateUpdater, + Constants.UPDATE_EXCHANGE_RATE_ROLE, + true + ); - // Owner Roles + // MANAGER_ROLE (Manager) + auth.setUserRole(manager, Constants.MANAGER_ROLE, true); + + // OWNER_ROLE (Owner - kept as deployer/owner for now) auth.setRoleCapability( Constants.OWNER_ROLE, teller, @@ -189,6 +211,48 @@ contract DeployPointsVault is Script { BoringVault(payable(vault)).setBeforeTransferHook(teller); + // --- Ownership Transfer --- + // 1. Identify Target Owner + address mantraOwner = vm.envOr("MANTRA_OWNER", address(0)); + if (mantraOwner == address(0)) { + mantraOwner = isMainnet + ? Constants.OWNER_MAINNET + : Constants.OWNER_TESTNET; + } + + // 2. Perform Transfer if valid and different from deployer + if (mantraOwner != address(0) && mantraOwner != owner) { + console.log("Transferring ownership to:", mantraOwner); + + // Grant OWNER_ROLE to new owner first + if (!auth.doesUserHaveRole(mantraOwner, Constants.OWNER_ROLE)) { + auth.setUserRole(mantraOwner, Constants.OWNER_ROLE, true); + } + + // Transfer Auth Ownership of components + if (BoringVault(payable(vault)).owner() != mantraOwner) + BoringVault(payable(vault)).transferOwnership(mantraOwner); + if (AccountantWithFixedRate(accountant).owner() != mantraOwner) + AccountantWithFixedRate(accountant).transferOwnership( + mantraOwner + ); + if ( + TellerWithMultiAssetSupport(payable(teller)).owner() != + mantraOwner + ) + TellerWithMultiAssetSupport(payable(teller)).transferOwnership( + mantraOwner + ); + if (DelayedWithdraw(delayedWithdraw).owner() != mantraOwner) + DelayedWithdraw(delayedWithdraw).transferOwnership(mantraOwner); + + // Revoke Roles from Deployer + if (auth.doesUserHaveRole(owner, Constants.OWNER_ROLE)) { + auth.setUserRole(owner, Constants.OWNER_ROLE, false); + console.log("Revoked OWNER_ROLE from deployer"); + } + } + vm.stopBroadcast(); console.log("Environment:", isMainnet ? "Mainnet" : "Testnet"); From 1d16bf2685b488d36acebbb1746eca0388664959 Mon Sep 17 00:00:00 2001 From: EvmSerhii Date: Tue, 24 Feb 2026 16:26:19 +0100 Subject: [PATCH 10/11] latest deployment --- script/00_MantraConstants.sol | 55 ++- script/01_DeployDeployer.s.sol | 2 + script/02_DeployMaxiYieldVault.s.sol | 167 +++++++- script/03_DeployPointsVault.s.sol | 93 ++++- script/04_DeployArcticLens.s.sol | 5 +- test/MantraPermissionsVerification.t.sol | 490 +++++++++++++++++++++++ verify_dukong.sh | 34 ++ 7 files changed, 797 insertions(+), 49 deletions(-) create mode 100644 test/MantraPermissionsVerification.t.sol create mode 100755 verify_dukong.sh diff --git a/script/00_MantraConstants.sol b/script/00_MantraConstants.sol index 46f23df44..2a9eaa518 100644 --- a/script/00_MantraConstants.sol +++ b/script/00_MantraConstants.sol @@ -15,19 +15,19 @@ library MantraConstants { // --- Testnet (Dukong) --- address internal constant RATE_UPDATER_TESTNET = - 0x0000000000000000000000000000000000000000; + 0x37723e376FdF70854665B5f1a5C49cB30E1691AC; address internal constant MANAGER_TESTNET = - 0x0000000000000000000000000000000000000000; + 0x09676Ee4685B618d0DCc85E221019c9Ce3810211; address internal constant OWNER_TESTNET = - 0x0000000000000000000000000000000000000000; + 0x09676Ee4685B618d0DCc85E221019c9Ce3810211; // --- Mainnet --- address internal constant RATE_UPDATER_MAINNET = - 0x0000000000000000000000000000000000000000; + 0x37723e376FdF70854665B5f1a5C49cB30E1691AC; address internal constant MANAGER_MAINNET = - 0x0000000000000000000000000000000000000000; + 0x9bbf09dEC8B93CE548eA6fE50e9786964e47Ee5e; address internal constant OWNER_MAINNET = - 0x0000000000000000000000000000000000000000; + 0x9bbf09dEC8B93CE548eA6fE50e9786964e47Ee5e; // ========================================== // INTERNAL CONSTANTS @@ -75,11 +75,11 @@ library MantraConstants { MINTER_ROLE (id=2) Purpose: Mint BoringVault shares through vault.enter(). Applied in current deploy scripts: - - script/02_DeployMaxiYieldVault.s.sol: + - script/02_DeployRFRYieldVault.s.sol: - script/03_DeployPointsVault.s.sol: capability: BoringVault.enter() - contract: Maxi Yield BoringVault, Points BoringVault - grantee: Maxi Yield TellerWithMultiAssetSupport, Points TellerWithMultiAssetSupport + contract: RFR Yield BoringVault, Points BoringVault + grantee: RFR Yield TellerWithMultiAssetSupport, Points TellerWithMultiAssetSupport Role holder: Teller contract that processes deposits. */ uint8 internal constant MINTER_ROLE = 2; @@ -88,11 +88,11 @@ library MantraConstants { BURNER_ROLE (id=3) Purpose: Burn BoringVault shares through vault.exit() during withdrawals/refunds. Applied in current deploy scripts: - - script/02_DeployMaxiYieldVault.s.sol: + - script/02_DeployRFRYieldVault.s.sol: - script/03_DeployPointsVault.s.sol: capability: BoringVault.exit() - contract: Maxi Yield BoringVault, Points BoringVault - grantee: Maxi Yield DelayedWithdraw, Points DelayedWithdraw + contract: RFR Yield BoringVault, Points BoringVault + grantee: RFR Yield DelayedWithdraw, Points DelayedWithdraw Role holder: withdrawal coordinator contract (DelayedWithdraw). */ uint8 internal constant BURNER_ROLE = 3; @@ -101,13 +101,13 @@ library MantraConstants { OWNER_ROLE (id=8) Purpose: Day-to-day configuration for Teller/Withdraw flows. Applied in current deploy scripts: - - script/02_DeployMaxiYieldVault.s.sol: + - script/02_DeployRFRYieldVault.s.sol: - script/03_DeployPointsVault.s.sol: TellerWithMultiAssetSupport.setShareLockPeriod() TellerWithMultiAssetSupport.updateAssetData() DelayedWithdraw.setupWithdrawAsset() DelayedWithdraw.setPullFundsFromVault() - contract: Maxi Yield | Points TellerWithMultiAssetSupport, Maxi Yield | Points DelayedWithdraw + contract: RFR Yield | Points TellerWithMultiAssetSupport, RFR Yield | Points DelayedWithdraw grantee: owner EOA (MANTRA_DEPLOYER) Role holder: operations multisig (preferable) or trusted admin EOA. */ @@ -117,9 +117,9 @@ library MantraConstants { UPDATE_EXCHANGE_RATE_ROLE (id=11) Purpose: Call accountant.updateExchangeRate() to move vault share price. Applied in current deploy scripts: - - script/02_DeployMaxiYieldVault.s.sol: - - script/03_DeployPointsVault.s.sol: - contract: Maxi Yield AccountantWithRateProviders, Points AccountantWithFixedRate + - script/02_DeployRFRYieldVault.s.sol: + - script/02_DeployRFRYieldVault.s.sol: + contract: RFR Yield AccountantWithRateProviders, Points AccountantWithFixedRate grantee: owner EOA (MANTRA_DEPLOYER) capability: AccountantWithRateProviders.updateExchangeRate() Role holder: dedicated updater bot/service with strict monitoring. @@ -133,21 +133,20 @@ library MantraConstants { // Arctic Lens string internal constant ARCTIC_LENS_NAME = "Lens V1.0"; - // Maxi Yield Vault - string internal constant MAXI_VAULT_NAME = "Maxi Yield Vault V1.0"; - string internal constant MAXI_SYMBOL = "MY-mantraUSD"; - uint8 internal constant MAXI_DECIMALS = 6; - string internal constant MAXI_TOKEN_NAME = "Maxi Yield mantraUSD"; - string internal constant MAXI_ACCOUNTANT_NAME = - "Maxi Yield Accountant V1.0"; - string internal constant MAXI_TELLER_NAME = "Maxi Yield Teller V1.0"; - string internal constant MAXI_DW_NAME = "Maxi Yield DelayedWithdraw V1.0"; + // RFR Yield Vault + string internal constant RFR_VAULT_NAME = "RFR Yield Vault V1.0"; + string internal constant RFR_SYMBOL = "RFR-mUSD"; + uint8 internal constant RFR_DECIMALS = 6; + string internal constant RFR_TOKEN_NAME = "RFR Yield mUSD"; + string internal constant RFR_ACCOUNTANT_NAME = "RFR Yield Accountant V1.0"; + string internal constant RFR_TELLER_NAME = "RFR Yield Teller V1.0"; + string internal constant RFR_DW_NAME = "RFR Yield DelayedWithdraw V1.0"; // Points Vault string internal constant POINTS_VAULT_NAME = "Points Vault V1.0"; - string internal constant POINTS_SYMBOL = "PTS-mantraUSD"; + string internal constant POINTS_SYMBOL = "PTS-mUSD"; uint8 internal constant POINTS_DECIMALS = 6; - string internal constant POINTS_TOKEN_NAME = "Points mantraUSD"; + string internal constant POINTS_TOKEN_NAME = "Points mUSD"; string internal constant POINTS_ACCOUNTANT_NAME = "Points Accountant V1.0"; string internal constant POINTS_TELLER_NAME = "Points Teller V1.0"; string internal constant POINTS_DW_NAME = "Points DelayedWithdraw V1.0"; diff --git a/script/01_DeployDeployer.s.sol b/script/01_DeployDeployer.s.sol index 110a7d63b..e0c920755 100644 --- a/script/01_DeployDeployer.s.sol +++ b/script/01_DeployDeployer.s.sol @@ -9,6 +9,8 @@ import { import "forge-std/Script.sol"; import {MantraConstants as Constants} from "./00_MantraConstants.sol"; +// forge script script/01_DeployDeployer.s.sol --rpc-url mantra_dukong --broadcast --slow + contract DeployDeployer is Script { function run() external { uint256 deployerKey = vm.envUint("MANTRA_DEPLOYER"); diff --git a/script/02_DeployMaxiYieldVault.s.sol b/script/02_DeployMaxiYieldVault.s.sol index 99bcc6037..b01706cd5 100644 --- a/script/02_DeployMaxiYieldVault.s.sol +++ b/script/02_DeployMaxiYieldVault.s.sol @@ -22,8 +22,19 @@ contract DeployMaxiYieldVault is Script { address public deployerAddr = vm.envAddress("DEPLOYER_CONTRACT_ADDRESS"); address public rolesAuthAddr = vm.envAddress("ROLES_AUTH_CONTRACT_ADDRESS"); + // forge script script/02_DeployMaxiYieldVault.s.sol \ + // --rpc-url mantra_dukong \ + // --broadcast \ + // --chain-id 5887 \ + // --legacy \ + // --skip-simulation \ + // -vvvv + function run() external { - vm.createSelectFork("mantra"); + string memory forkName = vm.envOr("MANTRA_MAINNET", false) + ? "mantra" + : "mantra_dukong"; + vm.createSelectFork(forkName); uint256 deployerKey = vm.envUint("MANTRA_DEPLOYER"); address owner = vm.addr(deployerKey); @@ -43,19 +54,19 @@ contract DeployMaxiYieldVault is Script { // --- 1. Deploy Core Components --- address vault = deployer.deployContract( - Constants.MAXI_VAULT_NAME, + Constants.RFR_VAULT_NAME, type(BoringVault).creationCode, abi.encode( owner, - Constants.MAXI_TOKEN_NAME, - Constants.MAXI_SYMBOL, - Constants.MAXI_DECIMALS + Constants.RFR_TOKEN_NAME, + Constants.RFR_SYMBOL, + Constants.RFR_DECIMALS ), 0 ); address accountant = deployer.deployContract( - Constants.MAXI_ACCOUNTANT_NAME, + Constants.RFR_ACCOUNTANT_NAME, type(AccountantWithRateProviders).creationCode, abi.encode( owner, // owner @@ -73,14 +84,14 @@ contract DeployMaxiYieldVault is Script { ); address teller = deployer.deployContract( - Constants.MAXI_TELLER_NAME, + Constants.RFR_TELLER_NAME, type(TellerWithMultiAssetSupport).creationCode, abi.encode(owner, vault, accountant, WETH), // WETH (Dynamic) 0 ); address delayedWithdraw = deployer.deployContract( - Constants.MAXI_DW_NAME, + Constants.RFR_DW_NAME, type(DelayedWithdraw).creationCode, abi.encode(owner, vault, accountant, owner), 0 @@ -118,6 +129,7 @@ contract DeployMaxiYieldVault is Script { auth.setUserRole(teller, Constants.MINTER_ROLE, true); // BURNER_ROLE (DelayedWithdraw, BoringVault) + // exit.selector allows DW to burn shares and trigger the Vault to send underlying assets directly to the user auth.setRoleCapability( Constants.BURNER_ROLE, vault, @@ -140,10 +152,6 @@ contract DeployMaxiYieldVault is Script { ); // MANAGER_ROLE (Manager) - // Since MANAGER_ROLE = DEPLOYER_ROLE = 1, and Deployer already has it, - // we explicitly grant it to the manager address if different. - // Capabilities for Manager (e.g. manage on Vault if needed) - // For now, we just ensure the user has the role. auth.setUserRole(manager, Constants.MANAGER_ROLE, true); // OWNER_ROLE (Owner - kept as deployer/owner for now) @@ -171,6 +179,122 @@ contract DeployMaxiYieldVault is Script { DelayedWithdraw.setPullFundsFromVault.selector, true ); + + // --- OWNER_ROLE Maintenance & Fees --- + auth.setRoleCapability( + Constants.OWNER_ROLE, + delayedWithdraw, + DelayedWithdraw.changeWithdrawFee.selector, + true + ); + auth.setRoleCapability( + Constants.OWNER_ROLE, + delayedWithdraw, + DelayedWithdraw.changeWithdrawDelay.selector, + true + ); + auth.setRoleCapability( + Constants.OWNER_ROLE, + delayedWithdraw, + DelayedWithdraw.changeCompletionWindow.selector, + true + ); + auth.setRoleCapability( + Constants.OWNER_ROLE, + delayedWithdraw, + DelayedWithdraw.changeMaxLoss.selector, + true + ); + auth.setRoleCapability( + Constants.OWNER_ROLE, + delayedWithdraw, + DelayedWithdraw.stopWithdrawalsInAsset.selector, + true + ); + auth.setRoleCapability( + Constants.OWNER_ROLE, + delayedWithdraw, + DelayedWithdraw.setFeeAddress.selector, + true + ); + auth.setRoleCapability( + Constants.OWNER_ROLE, + accountant, + AccountantWithRateProviders.updatePlatformFee.selector, + true + ); + auth.setRoleCapability( + Constants.OWNER_ROLE, + accountant, + AccountantWithRateProviders.updatePerformanceFee.selector, + true + ); + auth.setRoleCapability( + Constants.OWNER_ROLE, + accountant, + AccountantWithRateProviders.updatePayoutAddress.selector, + true + ); + auth.setRoleCapability( + Constants.OWNER_ROLE, + accountant, + AccountantWithRateProviders.updateDelay.selector, + true + ); + auth.setRoleCapability( + Constants.OWNER_ROLE, + accountant, + AccountantWithRateProviders.updateUpper.selector, + true + ); + auth.setRoleCapability( + Constants.OWNER_ROLE, + accountant, + AccountantWithRateProviders.updateLower.selector, + true + ); + auth.setRoleCapability( + Constants.OWNER_ROLE, + accountant, + AccountantWithRateProviders.pause.selector, + true + ); + auth.setRoleCapability( + Constants.OWNER_ROLE, + accountant, + AccountantWithRateProviders.unpause.selector, + true + ); + auth.setRoleCapability( + Constants.OWNER_ROLE, + accountant, + AccountantWithRateProviders.resetHighwaterMark.selector, + true + ); + auth.setRoleCapability( + Constants.OWNER_ROLE, + teller, + TellerWithMultiAssetSupport.pause.selector, + true + ); + auth.setRoleCapability( + Constants.OWNER_ROLE, + teller, + TellerWithMultiAssetSupport.unpause.selector, + true + ); + auth.setRoleCapability( + Constants.OWNER_ROLE, + teller, + TellerWithMultiAssetSupport.denyAll.selector, + true + ); + auth.setRoleCapability( + Constants.OWNER_ROLE, + teller, + TellerWithMultiAssetSupport.allowAll.selector, + true + ); auth.setUserRole(owner, Constants.OWNER_ROLE, true); // Public Capabilities @@ -179,6 +303,11 @@ contract DeployMaxiYieldVault is Script { TellerWithMultiAssetSupport.deposit.selector, true ); + auth.setPublicCapability( + teller, + TellerWithMultiAssetSupport.depositWithPermit.selector, + true + ); auth.setPublicCapability( delayedWithdraw, DelayedWithdraw.requestWithdraw.selector, @@ -196,9 +325,9 @@ contract DeployMaxiYieldVault is Script { ); TellerWithMultiAssetSupport(payable(teller)).updateAssetData( ERC20(mUSD), - true, - true, - 0 + true, // isSupported + true, // isDepositAsset + 0 // sharePremium ); DelayedWithdraw(delayedWithdraw).setupWithdrawAsset( @@ -248,10 +377,10 @@ contract DeployMaxiYieldVault is Script { DelayedWithdraw(delayedWithdraw).transferOwnership(mantraOwner); // Revoke Roles from Deployer - if (auth.doesUserHaveRole(owner, Constants.OWNER_ROLE)) { - auth.setUserRole(owner, Constants.OWNER_ROLE, false); - console.log("Revoked OWNER_ROLE from deployer"); - } + // if (auth.doesUserHaveRole(owner, Constants.OWNER_ROLE)) { + // auth.setUserRole(owner, Constants.OWNER_ROLE, false); + // console.log("Revoked OWNER_ROLE from deployer"); + // } } vm.stopBroadcast(); diff --git a/script/03_DeployPointsVault.s.sol b/script/03_DeployPointsVault.s.sol index c1b3a1add..98fa1d337 100644 --- a/script/03_DeployPointsVault.s.sol +++ b/script/03_DeployPointsVault.s.sol @@ -21,12 +21,23 @@ import {ERC20} from "@solmate/tokens/ERC20.sol"; import "forge-std/Script.sol"; import {MantraConstants as Constants} from "./00_MantraConstants.sol"; +// forge script script/03_DeployPointsVault.s.sol \ +// --rpc-url mantra_dukong \ +// --broadcast \ +// --chain-id 5887 \ +// --legacy \ +// --skip-simulation \ +// -vvvv + contract DeployPointsVault is Script { address public deployerAddr = vm.envAddress("DEPLOYER_CONTRACT_ADDRESS"); address public rolesAuthAddr = vm.envAddress("ROLES_AUTH_CONTRACT_ADDRESS"); function run() external { - vm.createSelectFork("mantra"); + string memory forkName = vm.envOr("MANTRA_MAINNET", false) + ? "mantra" + : "mantra_dukong"; + vm.createSelectFork(forkName); uint256 deployerKey = vm.envUint("MANTRA_DEPLOYER"); address owner = vm.addr(deployerKey); @@ -170,6 +181,86 @@ contract DeployPointsVault is Script { DelayedWithdraw.setPullFundsFromVault.selector, true ); + + // --- OWNER_ROLE Maintenance & Fees --- + auth.setRoleCapability( + Constants.OWNER_ROLE, + delayedWithdraw, + DelayedWithdraw.changeWithdrawFee.selector, + true + ); + auth.setRoleCapability( + Constants.OWNER_ROLE, + delayedWithdraw, + DelayedWithdraw.changeWithdrawDelay.selector, + true + ); + auth.setRoleCapability( + Constants.OWNER_ROLE, + delayedWithdraw, + DelayedWithdraw.changeCompletionWindow.selector, + true + ); + auth.setRoleCapability( + Constants.OWNER_ROLE, + delayedWithdraw, + DelayedWithdraw.changeMaxLoss.selector, + true + ); + auth.setRoleCapability( + Constants.OWNER_ROLE, + delayedWithdraw, + DelayedWithdraw.setFeeAddress.selector, + true + ); + auth.setRoleCapability( + Constants.OWNER_ROLE, + accountant, + AccountantWithRateProviders.updatePayoutAddress.selector, + true + ); + auth.setRoleCapability( + Constants.OWNER_ROLE, + accountant, + AccountantWithRateProviders.pause.selector, + true + ); + auth.setRoleCapability( + Constants.OWNER_ROLE, + accountant, + AccountantWithRateProviders.unpause.selector, + true + ); + auth.setRoleCapability( + Constants.OWNER_ROLE, + accountant, + AccountantWithFixedRate.setYieldDistributor.selector, + true + ); + auth.setRoleCapability( + Constants.OWNER_ROLE, + teller, + TellerWithMultiAssetSupport.pause.selector, + true + ); + auth.setRoleCapability( + Constants.OWNER_ROLE, + teller, + TellerWithMultiAssetSupport.unpause.selector, + true + ); + auth.setRoleCapability( + Constants.OWNER_ROLE, + teller, + TellerWithMultiAssetSupport.denyAll.selector, + true + ); + auth.setRoleCapability( + Constants.OWNER_ROLE, + teller, + TellerWithMultiAssetSupport.allowAll.selector, + true + ); auth.setUserRole(owner, Constants.OWNER_ROLE, true); // Public Capabilities diff --git a/script/04_DeployArcticLens.s.sol b/script/04_DeployArcticLens.s.sol index 6cafa56be..0f160c8cd 100644 --- a/script/04_DeployArcticLens.s.sol +++ b/script/04_DeployArcticLens.s.sol @@ -10,7 +10,10 @@ contract DeployArcticLens is Script { address public deployerAddr = vm.envAddress("DEPLOYER_CONTRACT_ADDRESS"); function run() external { - vm.createSelectFork("mantra"); + string memory forkName = vm.envOr("MANTRA_MAINNET", false) + ? "mantra" + : "mantra_dukong"; + vm.createSelectFork(forkName); uint256 deployerKey = vm.envUint("MANTRA_DEPLOYER"); Deployer deployer = Deployer(deployerAddr); diff --git a/test/MantraPermissionsVerification.t.sol b/test/MantraPermissionsVerification.t.sol new file mode 100644 index 000000000..51a336d42 --- /dev/null +++ b/test/MantraPermissionsVerification.t.sol @@ -0,0 +1,490 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.21; + +import {Test, console} from "forge-std/Test.sol"; +import {BoringVault} from "src/base/BoringVault.sol"; +import { + AccountantWithRateProviders +} from "src/base/Roles/AccountantWithRateProviders.sol"; +import { + AccountantWithFixedRate +} from "src/base/Roles/AccountantWithFixedRate.sol"; +import { + TellerWithMultiAssetSupport +} from "src/base/Roles/TellerWithMultiAssetSupport.sol"; +import {DelayedWithdraw} from "src/base/Roles/DelayedWithdraw.sol"; +import { + RolesAuthority, + Authority +} from "@solmate/auth/authorities/RolesAuthority.sol"; +import {Deployer} from "src/helper/Deployer.sol"; +import {ERC20} from "@solmate/tokens/ERC20.sol"; +import {MantraConstants as Constants} from "script/00_MantraConstants.sol"; + +contract MockERC20 is ERC20 { + constructor( + string memory name, + string memory symbol, + uint8 decimals + ) ERC20(name, symbol, decimals) {} + function mint(address to, uint256 amount) public { + _mint(to, amount); + } +} + +contract MantraPermissionsVerificationTest is Test { + RolesAuthority public auth; + Deployer public deployer; + + BoringVault public maxiVault; + AccountantWithRateProviders public maxiAccountant; + TellerWithMultiAssetSupport public maxiTeller; + DelayedWithdraw public maxiDelayedWithdraw; + + BoringVault public pointsVault; + AccountantWithFixedRate public pointsAccountant; + TellerWithMultiAssetSupport public pointsTeller; + DelayedWithdraw public pointsDelayedWithdraw; + + address public contractOwner = address(0xDE1); + address public roleHolder = address(0xDE2); + address public manager = address(0xDE3); + address public rateUpdater = address(0xDE4); + address public payoutAddress = address(0xDE5); + + MockERC20 public mUSD; + + function setUp() public { + // Deploy mock token first + mUSD = new MockERC20("Mock USD", "mUSD", 6); + + // We mock a deployment state that mirrors what the scripts do + vm.startPrank(contractOwner); + + auth = new RolesAuthority(contractOwner, Authority(address(0))); + deployer = new Deployer(contractOwner, auth); + + // Grant owner permission to deploy + auth.setRoleCapability( + 1, + address(deployer), + Deployer.deployContract.selector, + true + ); + auth.setUserRole(contractOwner, 1, true); + + // Deploy Maxi + maxiVault = BoringVault( + payable( + deployer.deployContract( + "Maxi", + type(BoringVault).creationCode, + abi.encode(contractOwner, "M", "S", 6), + 0 + ) + ) + ); + maxiAccountant = AccountantWithRateProviders( + deployer.deployContract( + "MaxiAcc", + type(AccountantWithRateProviders).creationCode, + abi.encode( + contractOwner, + address(maxiVault), + payoutAddress, + 1e6, + address(mUSD), + 1.5e4, + 0.5e4, + 20 hours, + 0, + 0 + ), + 0 + ) + ); + maxiTeller = TellerWithMultiAssetSupport( + payable( + deployer.deployContract( + "MaxiTel", + type(TellerWithMultiAssetSupport).creationCode, + abi.encode( + contractOwner, + address(maxiVault), + address(maxiAccountant), + address(0) + ), + 0 + ) + ) + ); + maxiDelayedWithdraw = DelayedWithdraw( + deployer.deployContract( + "MaxiDW", + type(DelayedWithdraw).creationCode, + abi.encode( + contractOwner, + address(maxiVault), + address(maxiAccountant), + contractOwner + ), + 0 + ) + ); + + // Deploy Points + pointsVault = BoringVault( + payable( + deployer.deployContract( + "Points", + type(BoringVault).creationCode, + abi.encode(contractOwner, "P", "S", 6), + 0 + ) + ) + ); + pointsAccountant = AccountantWithFixedRate( + deployer.deployContract( + "PointsAcc", + type(AccountantWithFixedRate).creationCode, + abi.encode( + contractOwner, + address(pointsVault), + contractOwner, + 1e6, + address(mUSD), + 1.5e4, + 0.5e4, + 20 hours, + 0, + 0 + ), + 0 + ) + ); + pointsTeller = TellerWithMultiAssetSupport( + payable( + deployer.deployContract( + "PointsTel", + type(TellerWithMultiAssetSupport).creationCode, + abi.encode( + contractOwner, + address(pointsVault), + address(pointsAccountant), + address(0) + ), + 0 + ) + ) + ); + pointsDelayedWithdraw = DelayedWithdraw( + deployer.deployContract( + "PointsDW", + type(DelayedWithdraw).creationCode, + abi.encode( + contractOwner, + address(pointsVault), + address(pointsAccountant), + contractOwner + ), + 0 + ) + ); + + // Apply same authority to all + maxiVault.setAuthority(auth); + maxiAccountant.setAuthority(auth); + maxiTeller.setAuthority(auth); + maxiDelayedWithdraw.setAuthority(auth); + + pointsVault.setAuthority(auth); + pointsAccountant.setAuthority(auth); + pointsTeller.setAuthority(auth); + pointsDelayedWithdraw.setAuthority(auth); + + // Apply Role Setup (Mirrored from scripts) + _applyRoleSetup(); + + vm.stopPrank(); + } + + function _applyRoleSetup() internal { + // MINTER_ROLE (2) + auth.setRoleCapability( + Constants.MINTER_ROLE, + address(maxiVault), + BoringVault.enter.selector, + true + ); + auth.setUserRole(address(maxiTeller), Constants.MINTER_ROLE, true); + + // BURNER_ROLE (3) + auth.setRoleCapability( + Constants.BURNER_ROLE, + address(maxiVault), + BoringVault.exit.selector, + true + ); + auth.setUserRole( + address(maxiDelayedWithdraw), + Constants.BURNER_ROLE, + true + ); + + // OWNER_ROLE (8) - The new capabilities we added + // Maxi Accountant + auth.setRoleCapability( + Constants.OWNER_ROLE, + address(maxiAccountant), + AccountantWithRateProviders.updatePlatformFee.selector, + true + ); + auth.setRoleCapability( + Constants.OWNER_ROLE, + address(maxiAccountant), + AccountantWithRateProviders.updatePerformanceFee.selector, + true + ); + auth.setRoleCapability( + Constants.OWNER_ROLE, + address(maxiAccountant), + AccountantWithRateProviders.updatePayoutAddress.selector, + true + ); + auth.setRoleCapability( + Constants.OWNER_ROLE, + address(maxiAccountant), + AccountantWithRateProviders.unpause.selector, + true + ); + + // Points Accountant + auth.setRoleCapability( + Constants.OWNER_ROLE, + address(pointsAccountant), + AccountantWithRateProviders.updatePayoutAddress.selector, + true + ); + auth.setRoleCapability( + Constants.OWNER_ROLE, + address(pointsAccountant), + AccountantWithFixedRate.setYieldDistributor.selector, + true + ); + + // DelayedWithdraw + auth.setRoleCapability( + Constants.OWNER_ROLE, + address(maxiDelayedWithdraw), + DelayedWithdraw.setupWithdrawAsset.selector, + true + ); + auth.setRoleCapability( + Constants.OWNER_ROLE, + address(maxiDelayedWithdraw), + DelayedWithdraw.changeWithdrawFee.selector, + true + ); + auth.setRoleCapability( + Constants.OWNER_ROLE, + address(pointsDelayedWithdraw), + DelayedWithdraw.setupWithdrawAsset.selector, + true + ); + auth.setRoleCapability( + Constants.OWNER_ROLE, + address(pointsDelayedWithdraw), + DelayedWithdraw.changeWithdrawFee.selector, + true + ); + + // Teller + auth.setRoleCapability( + Constants.OWNER_ROLE, + address(maxiTeller), + TellerWithMultiAssetSupport.setShareLockPeriod.selector, + true + ); + auth.setRoleCapability( + Constants.OWNER_ROLE, + address(maxiTeller), + TellerWithMultiAssetSupport.updateAssetData.selector, + true + ); + auth.setRoleCapability( + Constants.OWNER_ROLE, + address(maxiTeller), + TellerWithMultiAssetSupport.pause.selector, + true + ); + auth.setRoleCapability( + Constants.OWNER_ROLE, + address(maxiTeller), + TellerWithMultiAssetSupport.denyAll.selector, + true + ); + + auth.setUserRole(roleHolder, Constants.OWNER_ROLE, true); + + // MANAGER_ROLE (1) + auth.setUserRole(manager, Constants.MANAGER_ROLE, true); + + // Grant MANAGER_ROLE manage capability on Vault + auth.setRoleCapability( + Constants.MANAGER_ROLE, + address(maxiVault), + bytes4(keccak256("manage(address,bytes,uint256)")), + true + ); + auth.setRoleCapability( + Constants.MANAGER_ROLE, + address(maxiVault), + bytes4(keccak256("manage(address[],bytes[],uint256[])")), + true + ); + + // UPDATE_EXCHANGE_RATE_ROLE (11) + auth.setRoleCapability( + Constants.UPDATE_EXCHANGE_RATE_ROLE, + address(maxiAccountant), + AccountantWithRateProviders.updateExchangeRate.selector, + true + ); + auth.setUserRole( + rateUpdater, + Constants.UPDATE_EXCHANGE_RATE_ROLE, + true + ); + } + + // --- Tests --- + + function testOwnerCanChangeFees() public { + vm.startPrank(roleHolder); + + // 1. Verify Maxi Performance Fee change + maxiAccountant.updatePerformanceFee(25); // 25 bps + (, , , , , , , , , , , uint16 performanceFee) = maxiAccountant + .accountantState(); + assertEq(performanceFee, 25, "Maxi Performance fee should be 25 bps"); + + // 2. Verify Points Performance Fee change (SHOULD REVERT now) + vm.expectRevert(); + pointsAccountant.updatePerformanceFee(25); + + // 3. Verify Withdraw Fee change + // First setup asset + maxiDelayedWithdraw.setupWithdrawAsset( + ERC20(address(mUSD)), + 0, + 7 days, + 0, + 100 + ); + + maxiDelayedWithdraw.changeWithdrawFee(ERC20(address(mUSD)), 50); // 50 bps + (, , , , uint16 withdrawFee, ) = maxiDelayedWithdraw.withdrawAssets( + ERC20(address(mUSD)) + ); + assertEq(withdrawFee, 50, "Withdraw fee should be 50 bps"); + + vm.stopPrank(); + } + + function testManagerPermissions() public { + vm.startPrank(manager); + + // Manager has Role ID 1, which in this system is also the DEPLOYER_ROLE. + // We should verify that manager CANNOT change fees (unless explicitly granted). + + vm.expectRevert(); + maxiAccountant.updatePerformanceFee(100); + + vm.stopPrank(); + } + + function testUnprivilegedUserCannotChangeFees() public { + address hacker = address(0xBAD); + vm.startPrank(hacker); + + vm.expectRevert(); + maxiAccountant.updatePerformanceFee(100); + + vm.stopPrank(); + } + + function testFeeWithdrawalFlow() public { + // 1. Setup Performance Fee (25 bps) + vm.prank(roleHolder); + maxiAccountant.updatePerformanceFee(25); + + // 2. Accumulate Fees (Simulate logic: rate goes from 1.0 to 1.1) + // Give Vault some mUSD to pay fees + mUSD.mint(address(maxiVault), 1000e6); + + // Mint some shares so total supply > 0 + mUSD.mint(address(this), 1000e6); + mUSD.approve(address(maxiVault), 1000e6); + vm.prank(address(maxiTeller)); + maxiVault.enter(address(this), mUSD, 1000e6, address(this), 1000e6); + + // Warp to avoid update delay pause + vm.warp(block.timestamp + 1 days); + + // Checkpoint shares by calling updateExchangeRate once with current rate + vm.prank(rateUpdater); + maxiAccountant.updateExchangeRate(1.0e6); + + // Update exchange rate to 1.1e6 after another 1 day + vm.warp(block.timestamp + 1 days); + vm.prank(rateUpdater); + maxiAccountant.updateExchangeRate(1.1e6); + + // If the accountant paused (auto-pause), unpause it (Owner has unpause rights) + (, , , , , bool isPaused, , , , , , , , ) = maxiAccountant + .accountantState(); + if (isPaused) { + vm.prank(roleHolder); + maxiAccountant.unpause(); + } + + // Check fees owed + (, , uint128 feesOwedInBase, , , , , , , , , ) = maxiAccountant + .accountantState(); + assertTrue(feesOwedInBase > 0, "Fees should have accumulated"); + + // 3. Manager Withdrawal Flow + vm.startPrank(manager); + + // A. Approve Accountant to take fees from Vault + // Data for: mUSD.approve(accountant, type(uint256).max) + bytes memory approveData = abi.encodeWithSelector( + ERC20.approve.selector, + address(maxiAccountant), + type(uint256).max + ); + maxiVault.manage(address(mUSD), approveData, 0); + + // B. Claim Fees + // Data for: accountant.claimFees(mUSD) + bytes memory claimData = abi.encodeWithSelector( + AccountantWithRateProviders.claimFees.selector, + address(mUSD) + ); + maxiVault.manage(address(maxiAccountant), claimData, 0); + + vm.stopPrank(); + + // 4. Verify Payout + uint256 payoutBalance = mUSD.balanceOf(payoutAddress); + assertTrue( + payoutBalance > 0, + "Payout address should have received fees" + ); + assertEq( + payoutBalance, + uint256(feesOwedInBase), + "Payout should match fees owed" + ); + } +} diff --git a/verify_dukong.sh b/verify_dukong.sh new file mode 100755 index 000000000..7eec93220 --- /dev/null +++ b/verify_dukong.sh @@ -0,0 +1,34 @@ +#!/bin/bash + +# Configuration +RPC_URL="mantra_dukong" +VERIFIER="blockscout" +VERIFIER_URL="https://explorer.dukong.io/api" + +# Deployed Addresses +DEPLOYER="0xa54DD6f938EB7C6394BF10B06E267f10aA3fE2eF" +ROLES_AUTHORITY="0x921b20e49ec45B3441CF45e2BeccBEF33620AeE6" +LENS="0x0FDCf61ed820a7e323f74D83B0A2302beD5e29E0" + +# Owner (retrieved from broadcast logs) +OWNER="0x37723e376FdF70854665B5f1a5C49cB30E1691AC" + +echo "Verifying Deployer..." +forge verify-contract $DEPLOYER src/helper/Deployer.sol:Deployer \ + --constructor-args $(cast abi-encode "constructor(address,address)" $OWNER 0x0000000000000000000000000000000000000000) \ + --rpc-url $RPC_URL \ + --verifier $VERIFIER \ + --verifier-url $VERIFIER_URL + +echo "Verifying RolesAuthority..." +forge verify-contract $ROLES_AUTHORITY lib/solmate/src/auth/authorities/RolesAuthority.sol:RolesAuthority \ + --constructor-args $(cast abi-encode "constructor(address,address)" $OWNER 0x0000000000000000000000000000000000000000) \ + --rpc-url $RPC_URL \ + --verifier $VERIFIER \ + --verifier-url $VERIFIER_URL + +echo "Verifying ArcticArchitectureLens..." +forge verify-contract $LENS src/helper/ArcticArchitectureLens.sol:ArcticArchitectureLens \ + --rpc-url $RPC_URL \ + --verifier $VERIFIER \ + --verifier-url $VERIFIER_URL From b8f03de928b30825a482d03680bc6da4420d6c66 Mon Sep 17 00:00:00 2001 From: EvmSerhii Date: Thu, 26 Feb 2026 17:30:58 +0100 Subject: [PATCH 11/11] update with latest deployment --- script/00_MantraConstants.sol | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/script/00_MantraConstants.sol b/script/00_MantraConstants.sol index 2a9eaa518..964e0cc70 100644 --- a/script/00_MantraConstants.sol +++ b/script/00_MantraConstants.sol @@ -134,22 +134,27 @@ library MantraConstants { string internal constant ARCTIC_LENS_NAME = "Lens V1.0"; // RFR Yield Vault - string internal constant RFR_VAULT_NAME = "RFR Yield Vault V1.0"; - string internal constant RFR_SYMBOL = "RFR-mUSD"; + string internal constant RFR_VAULT_NAME = "wmantraUSD (Yield)"; + string internal constant RFR_SYMBOL = "wmantraUSD-Yld"; uint8 internal constant RFR_DECIMALS = 6; - string internal constant RFR_TOKEN_NAME = "RFR Yield mUSD"; - string internal constant RFR_ACCOUNTANT_NAME = "RFR Yield Accountant V1.0"; - string internal constant RFR_TELLER_NAME = "RFR Yield Teller V1.0"; - string internal constant RFR_DW_NAME = "RFR Yield DelayedWithdraw V1.0"; + string internal constant RFR_TOKEN_NAME = "wmantraUSD (Yield)"; + string internal constant RFR_ACCOUNTANT_NAME = + "wmantraUSD (Yield) Accountant v1.0"; + string internal constant RFR_TELLER_NAME = "wmantraUSD (Yield) Teller v1.0"; + string internal constant RFR_DW_NAME = + "wmantraUSD (Yield) DelayedWithdraw v1.0"; // Points Vault - string internal constant POINTS_VAULT_NAME = "Points Vault V1.0"; - string internal constant POINTS_SYMBOL = "PTS-mUSD"; + string internal constant POINTS_VAULT_NAME = "wmantraUSD (Points)"; + string internal constant POINTS_SYMBOL = "wmantraUSD-Pts"; uint8 internal constant POINTS_DECIMALS = 6; - string internal constant POINTS_TOKEN_NAME = "Points mUSD"; - string internal constant POINTS_ACCOUNTANT_NAME = "Points Accountant V1.0"; - string internal constant POINTS_TELLER_NAME = "Points Teller V1.0"; - string internal constant POINTS_DW_NAME = "Points DelayedWithdraw V1.0"; + string internal constant POINTS_TOKEN_NAME = "wmantraUSD (Points)"; + string internal constant POINTS_ACCOUNTANT_NAME = + "wmantraUSD (Points) Accountant v1.0"; + string internal constant POINTS_TELLER_NAME = + "wmantraUSD (Points) Teller v1.0"; + string internal constant POINTS_DW_NAME = + "wmantraUSD (Points) DelayedWithdraw v1.0"; // ========================================== // Accountant Configuration @@ -161,7 +166,7 @@ library MantraConstants { 0.5e4; uint64 internal constant ACCOUNTANT_MINIMUM_UPDATE_DELAY = 20 hours; uint16 internal constant ACCOUNTANT_PLATFORM_FEE = 0; - uint16 internal constant ACCOUNTANT_PERFORMANCE_FEE = 0; + uint16 internal constant ACCOUNTANT_PERFORMANCE_FEE = 500; // ========================================== // Teller Configuration