Skip to content

Manage Vault V2 Roles

This tutorial guides you through setting up and managing roles for a Morpho Vault V2. A clear understanding and proper assignment of roles are critical for the vault's security, governance, and operational success.

The Role Setup Workflow

Setting up roles for a new Vault V2 follows a specific sequence. The Owner first assigns the Curator and Sentinels (actions which are immediate). Then, the Curator proposes strategic changes, such as appointing an Allocator.

Crucially, most of the Curator's actions are timelocked. The process for these actions depends on whether a timelock duration is set.

Managing Roles via Scripts (Recommended)

Using scripts is the most reliable way to manage vault roles, as it minimizes manual error. A reference deployment repository is available to simplify this process: https://github.com/morpho-org/vault-v2-deployment

Below are conceptual examples of how to perform these actions using Foundry scripts.

Scenario 1: Initial Vault Setup (Zero Timelock)

When a Vault V2 is first deployed, all timelocks are set to zero. This is intentional, allowing for a rapid, one-time initial configuration. In this scenario, submit and execute can be done in immediate succession.

Example Script: Appointing an Allocator at Setup

This single script submits the proposal and executes it right away, as the waiting period is zero.

// This is a simplified example for initial setup.
import {IVaultV2} from "vault-v2/src/interfaces/IVaultV2.sol";
import {Script, console} from "forge-std/Script.sol";
 
contract InitialRoleSetup is Script {
    function run() external {
        address vaultAddress = vm.envAddress("VAULT_V2_ADDRESS");
        address newAllocator = vm.envAddress("NEW_ALLOCATOR_ADDRESS");
        uint256 curatorPrivateKey = vm.envUint("CURATOR_PRIVATE_KEY");
 
        IVaultV2 vault = IVaultV2(vaultAddress);
 
        vm.startBroadcast(curatorPrivateKey);
 
        // Step 1: Submit the proposal as Curator
        bytes memory data = abi.encodeCall(IVaultV2.setIsAllocator, (newAllocator, true));
        vault.submit(data);
 
        // Step 2: Execute immediately.
        // Since the default timelock is 0, this can be executed right after submission.
        // In a real script, this would be a separate transaction in the same broadcast.
        vault.setIsAllocator(newAllocator, true);
 
        vm.stopBroadcast();
 
        console.log("Allocator role assigned successfully during initial setup.");
    }
}

Scenario 2: Production Management (Non-Zero Timelock)

Once a vault is live and "hardened" with non-zero timelocks, role management becomes a true multi-step process that requires waiting for a real-world time delay. You cannot use vm.warp on a live network.

The process must be split into two separate actions: one to submit, and another to execute after the delay.

Step 1: Submitting the Proposal

This script is run by the Curator to propose appointing a new Allocator.

script/SubmitAllocator.s.sol
import {IVaultV2} from "vault-v2/src/interfaces/IVaultV2.sol";
import {Script, console} from "forge-std/Script.sol";
 
contract SubmitAllocator is Script {
    function run() external {
        address vaultAddress = vm.envAddress("VAULT_V2_ADDRESS");
        address newAllocator = vm.envAddress("NEW_ALLOCATOR_ADDRESS");
        uint256 curatorPrivateKey = vm.envUint("CURATOR_PRIVATE_KEY");
 
        IVaultV2 vault = IVaultV2(vaultAddress);
 
        console.log("Submitting proposal to appoint", newAllocator, "as an Allocator...");
        
        vm.startBroadcast(curatorPrivateKey);
 
        bytes memory data = abi.encodeCall(IVaultV2.setIsAllocator, (newAllocator, true));
        vault.submit(data);
 
        vm.stopBroadcast();
        
        uint256 timelock = vault.timelock(IVaultV2.setIsAllocator.selector);
        console.log("Proposal submitted. You must wait at least", timelock, "seconds before executing.");
    }
}

After running this script, the Curator must wait for the timelock period to pass.

Step 2: Executing the Proposal

After the waiting period, anyone can call the setIsAllocator function to finalize the proposal. This script does not require the Curator's key.

script/ExecuteAllocator.s.sol
import {IVaultV2} from "vault-v2/src/interfaces/IVaultV2.sol";
import {Script, console} from "forge-std/Script.sol";
 
contract ExecuteAllocator is Script {
    function run() external {
        address vaultAddress = vm.envAddress("VAULT_V2_ADDRESS");
        address newAllocator = vm.envAddress("NEW_ALLOCATOR_ADDRESS");
        // Note: This can be run by any account, so a generic broadcaster key is fine.
        uint256 broadcasterPrivateKey = vm.envUint("BROADCASTER_PRIVATE_KEY"); 
 
        IVaultV2 vault = IVaultV2(vaultAddress);
 
        console.log("Executing proposal to appoint", newAllocator, "as an Allocator...");
        
        vm.startBroadcast(broadcasterPrivateKey);
 
        vault.setIsAllocator(newAllocator, true);
 
        vm.stopBroadcast();
        
        console.log("Allocator role assigned successfully.");
    }
}

Managing Roles via Etherscan

For manual management, the process mirrors the scripted approach.

Owner Actions (Immediate)

As the Owner, connect your wallet to the Vault V2 contract on Etherscan and use the Write Contract tab to:

  • Execute setCurator(address newCurator) to appoint the Curator.
  • Execute setIsSentinel(address account, bool newIsSentinel) to add or remove Sentinels.

Curator Actions (Timelocked)

As the Curator, connect your wallet to:

  1. Submit the Proposal:
    • Use a tool like Foundry's cast calldata to ABI-encode your intended function call (e.g., setIsAllocator(0x..., true)).
    • Call the submit(bytes) function with the encoded data.
  2. Wait for the Timelock: Wait for the required duration.
  3. Execute the Proposal: After the timelock, anyone can connect their wallet and call the original function (e.g., setIsAllocator(0x..., true)) to finalize the change.