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)
The official deployment repository handles role management efficiently: https://github.com/morpho-org/vault-v2-deployment
Scenario 1: Initial Vault Setup (Zero Timelock)
The DeployVaultV2.s.sol script handles the complete initial setup. Here's how it manages roles:
// The script sets the broadcaster as temporary owner and curator
VaultV2 vaultV2 = new VaultV2(broadcaster, vaultV1.asset());
vaultV2.setCurator(broadcaster);
// Submit allocator role assignments
vaultV2.submit(abi.encodeCall(vaultV2.setIsAllocator, (broadcaster, true)));
vaultV2.submit(abi.encodeCall(vaultV2.setIsAllocator, (allocator, true)));
// Execute immediately (timelock is 0 by default)
vaultV2.setIsAllocator(broadcaster, true);
vaultV2.setIsAllocator(allocator, true);
// After configuration, transfer roles to final addresses
vaultV2.setCurator(curator);
if (sentinel != address(0)) {
vaultV2.setIsSentinel(sentinel, true);
}
vaultV2.setOwner(owner);Scenario 2: Production Management (Non-Zero Timelock)
Once a vault has non-zero timelocks, role changes require a two-step process:
Step 1: Submitting a Role Change
Create a script to submit the proposal:
script/SubmitRoleChange.s.sol
pragma solidity 0.8.28;
import {Script} from "forge-std/Script.sol";
import {IVaultV2} from "vault-v2/interfaces/IVaultV2.sol";
contract SubmitRoleChange is Script {
function run() external {
address vaultAddress = vm.envAddress("VAULT_V2_ADDRESS");
address newAllocator = vm.envAddress("NEW_ALLOCATOR_ADDRESS");
IVaultV2 vault = IVaultV2(vaultAddress);
vm.startBroadcast();
// Submit the proposal
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. Execute after", timelock, "seconds");
}
}Step 2: Executing the Role Change
After the timelock expires, execute the change:
script/ExecuteRoleChange.s.sol
pragma solidity 0.8.28;
import {Script} from "forge-std/Script.sol";
import {IVaultV2} from "vault-v2/interfaces/IVaultV2.sol";
contract ExecuteRoleChange is Script {
function run() external {
address vaultAddress = vm.envAddress("VAULT_V2_ADDRESS");
address newAllocator = vm.envAddress("NEW_ALLOCATOR_ADDRESS");
IVaultV2 vault = IVaultV2(vaultAddress);
vm.startBroadcast();
// Execute the timelocked action
vault.setIsAllocator(newAllocator, true);
vm.stopBroadcast();
console.log("Allocator role assigned successfully");
}
}Revoking Proposals
Curators and Sentinels can revoke pending proposals:
// To revoke a pending proposal
bytes memory dataToRevoke = abi.encodeCall(IVaultV2.setIsAllocator, (address, bool));
vault.revoke(dataToRevoke);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. - Execute
setOwner(address newOwner)to transfer ownership.
Curator Actions (Timelocked)
As the Curator, connect your wallet to:
- Submit the Proposal:
- Use a tool like Foundry's
cast calldatato ABI-encode your intended function call (e.g.,setIsAllocator(0x..., true)). - Call the
submit(bytes)function with the encoded data.
- Use a tool like Foundry's
- Wait for the Timelock: Check
executableAt(bytes data)to see when execution is allowed. - Execute the Proposal: After the timelock, anyone can call the original function to finalize the change.
Checking Role Status
Use the Read Contract tab to verify current roles:
owner(): Returns the current ownercurator(): Returns the current curatorisAllocator(address): Check if an address is an allocatorisSentinel(address): Check if an address is a sentinel
