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 calldata
to 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