Skip to main content

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 recipient
  • vestingManager_: 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

NameTypeDescription
amountuint256The amount of tokens to release

Returns

NameTypeDescription
releaseduint256The amount of tokens released

revoke

Revokes the vested tokens and transfers them to the beneficiary

function revoke(uint256 amount) external override;

Parameters

NameTypeDescription
amountuint256The 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

NameTypeDescription
newBeneficiaryaddressThe 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 manager
  • VaultAlreadyInitialized(): Attempting to initialize already initialized Vault
  • VaultZeroAddressDelegate(): Attempting to delegate to zero address
  • InvalidAddress(): 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 and getPastVotes 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