Vault
The Vault
contract is a minimal proxy implementation that holds vested tokens and enables voting power delegation for governance-enabled vesting schedules. Each vesting in TokenVestingManagerVotes
deploys its own Vault instance using the EIP-1167 clone pattern.
Overview
contract Vault is IVault
Key Features:
- Individual token custody for each vesting schedule
- ERC5805 voting integration for governance participation
- Minimal proxy deployment using EIP-1167 for gas efficiency
- Secure delegation of voting power while tokens remain vested
- Direct integration with governance frameworks
Architecture:
- Deployed as minimal proxy clones from a single implementation
- Initialized with specific token, beneficiary, and vesting manager
- Holds tokens until they are claimed or revoked
- Enables voting power delegation independent of token ownership
Deployment Pattern
// Vault Implementation (deployed once)
address vaultImplementation = new Vault();
// Clone deployment (for each vesting)
address vaultProxy = Clones.clone(vaultImplementation);
IVault(vaultProxy).initialize(tokenAddress, beneficiary, vestingManager);
State Variables
address public token; // ERC20 token address (must implement IERC5805)
address public beneficiary; // Current vesting recipient
address public vestingManager; // TokenVestingManagerVotes contract
address public delegates; // Current voting power delegate
bool private _initialized; // Initialization status
Initialization
initialize
function initialize(
address token_,
address beneficiary_,
address vestingManager_
) external onlyIfNotInitialized
Initializes a cloned Vault contract with specific parameters.
Parameters:
token_
: ERC20 token contract address (must implement IERC5805)beneficiary_
: Address of the vesting recipientvestingManager_
: Address of the TokenVestingManagerVotes contract
Requirements:
- Can only be called once per Vault instance
- All addresses must be non-zero
- Token must implement IERC5805 interface for voting
Access: Anyone (but can only be called once)
Note: This function is called automatically during vesting creation.
Token Management Functions
release
Releases the vested tokens to the beneficiary
function release(uint256 amount) external override returns (uint256 released);
Parameters
Name | Type | Description |
---|---|---|
amount | uint256 | The amount of tokens to release |
Returns
Name | Type | Description |
---|---|---|
released | uint256 | The amount of tokens released |
revoke
Revokes the vested tokens and transfers them to the beneficiary
function revoke(uint256 amount) external override;
Parameters
Name | Type | Description |
---|---|---|
amount | uint256 | The amount of tokens to revoke |
withdrawFee
function withdrawFee(uint256 amount) external override returns (uint256 withdrawn);
changeBeneficiary
Change the beneficiary of the vault
function changeBeneficiary(address newBeneficiary) external override;
Parameters
Name | Type | Description |
---|---|---|
newBeneficiary | address | The address of the new beneficiary |
delegate
function delegate(address delegatee) external override;
getVotes
function getVotes(address account) external view override returns (uint256);
getPastVotes
function getPastVotes(address account, uint256 timepoint) external view override returns (uint256);
getClockMode
function getClockMode() external view override returns (string memory);
getClock
function getClock() external view override returns (uint48);
balance
function balance() external view override returns (uint256);
Error Handling
Vault-Specific Errors:
VaultUnauthorized()
: Caller is not the vesting managerVaultAlreadyInitialized()
: Attempting to initialize already initialized VaultVaultZeroAddressDelegate()
: Attempting to delegate to zero addressInvalidAddress()
: Zero address provided during initialization
Security Considerations
Access Control
- Only vesting manager can call management functions
- Initialization can only happen once
- Beneficiary changes require manager authorization
Token Safety
- Uses SafeERC20 for all token transfers
- Validates all address parameters during initialization
- Prevents reentrancy through single-use initialization
Delegation Security
- Delegation doesn't transfer token ownership
- Voting power can be reclaimed by changing delegation
- Claimed tokens automatically lose Vault voting power
Governance Framework Compatibility
OpenZeppelin Governor
// Vaults automatically work with OpenZeppelin Governor
contract MyGovernor is Governor {
// Vault voting power is automatically included in governance
function getVotes(address account, uint256 blockNumber)
public view override returns (uint256) {
return token.getPastVotes(account, blockNumber);
}
}
Compound Governor
- Implements required
getVotes
andgetPastVotes
functions - Compatible with historical voting power queries
- Supports both block number and timestamp-based voting
Custom Governance Systems
- Flexible query interface for current and historical voting power
- Direct access to delegation state
- Compatible with snapshot-based voting mechanisms
⚠️ Important Notes
- Each Vault is a separate contract with isolated risk
- Delegation affects all tokens in the Vault
- Only the vesting manager can call management functions
- Claimed tokens lose Vault-based voting power
- Token must implement IERC5805 for governance functionality
🗳️ Governance Advantages
- Immediate voting power upon token funding
- Isolated governance participation per vesting
- Compatible with standard governance frameworks
- Flexible delegation without token transfers
- Historical voting power tracking for governance decisions