Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/reserve-protocol/reserve-index-dtf/llms.txt

Use this file to discover all available pages before exploring further.

Overview

The FolioGovernor contract provides on-chain governance for Folio instances. It extends OpenZeppelin’s Governor framework with dynamic proposal thresholds based on token supply.

Key Features

  • Dynamic Proposal Threshold: Percentage-based threshold that scales with supply
  • Timelock Integration: All proposals execute through a timelock
  • Quorum Control: Configurable quorum as percentage of supply
  • Vote Delegation: Users can delegate voting power
  • Simple Counting: For/Against/Abstain voting

Architecture

FolioGovernor extends multiple OpenZeppelin governor modules:
FolioGovernor.sol
contract FolioGovernor is
    GovernorUpgradeable,
    GovernorSettingsUpgradeable,
    GovernorCountingSimpleUpgradeable,
    GovernorVotesUpgradeable,
    GovernorVotesQuorumFractionUpgradeable,
    GovernorTimelockControlUpgradeable

Initialization

_token
IVotes
Voting token (typically StakingVault or Folio with voting enabled)
_timelock
TimelockControllerUpgradeable
Timelock contract for proposal execution
_votingDelay
uint48
Delay in seconds before voting starts after proposal
_votingPeriod
uint32
Duration in seconds that voting remains open
_proposalThreshold
uint256
Percentage of supply required to propose (e.g., 0.01e18 = 1%)
_quorumFraction
uint256
Percentage of supply required for quorum (e.g., 0.04e18 = 4%)
FolioGovernor.sol
function initialize(
    IVotes _token,
    TimelockControllerUpgradeable _timelock,
    uint48 _votingDelay,
    uint32 _votingPeriod,
    uint256 _proposalThreshold,
    uint256 _quorumFraction
) external initializer

Governance Parameters

Proposal Threshold

The number of tokens required to create a proposal is dynamically calculated:
FolioGovernor.sol
function proposalThreshold() public view returns (uint256) {
    uint256 threshold = super.proposalThreshold(); // D18{1}
    uint256 pastSupply = Math.max(1, token().getPastTotalSupply(clock() - 1));

    // CEIL to ensure thresholds near 0% don't get rounded down to 0
    return (threshold * pastSupply + (1e18 - 1)) / 1e18;
}
The threshold is calculated as a percentage of the previous block’s supply, preventing manipulation through same-block minting.

Quorum

Quorum is calculated using the same percentage-based approach:
FolioGovernor.sol
function quorumDenominator() public pure override returns (uint256) {
    return 1e18; // Use 18 decimals for percentage precision
}
Quorum Numerator
uint256
E.g., 0.04e18 for 4% quorum requirement
Quorum Denominator
uint256
default:"1e18"
Fixed at 1e18 for 18-decimal precision

Proposal Lifecycle

1. Create Proposal

Example Proposal
address[] memory targets = new address[](1);
targets[0] = address(folio);

uint256[] memory values = new uint256[](1);
values[0] = 0;

bytes[] memory calldatas = new bytes[](1);
calldatas[0] = abi.encodeWithSignature(
    "setMintFee(uint256)",
    0.01e18 // 1% mint fee
);

string memory description = "Proposal: Set mint fee to 1%";

uint256 proposalId = governor.propose(
    targets,
    values,
    calldatas,
    description
);

2. Voting Delay

After creation, there’s a delay before voting begins:
function votingDelay() public view returns (uint256) {
    // Returns delay in blocks/seconds
}
This delay allows users to acquire tokens and delegate voting power before the vote starts.

3. Active Voting

During the voting period, token holders can vote:
Cast Vote
// Vote options: Against (0), For (1), Abstain (2)
governor.castVote(proposalId, 1); // Vote For

// Or vote with reason
governor.castVoteWithReason(
    proposalId,
    1,
    "This improves protocol sustainability"
);

4. Queue in Timelock

Successful proposals must be queued:
Queue Proposal
governor.queue(
    targets,
    values,
    calldatas,
    keccak256(bytes(description))
);

5. Execute After Timelock

Once the timelock delay passes:
Execute Proposal
governor.execute(
    targets,
    values,
    calldatas,
    keccak256(bytes(description))
);

Proposal States

Proposal States
enum ProposalState {
    Pending,      // Waiting for voting delay to pass
    Active,       // Currently accepting votes
    Canceled,     // Canceled by proposer or guardian
    Defeated,     // Failed to reach quorum or majority
    Succeeded,    // Passed, ready to queue
    Queued,       // Queued in timelock
    Expired,      // Timelock expired without execution
    Executed      // Successfully executed
}
Pending
state
Proposal created, waiting for voting delay
Active
state
Voting is open
Succeeded
state
Vote passed, ready to be queued
Queued
state
In timelock, waiting for execution delay
Executed
state
Successfully executed

Voting Power

Token-Based Voting

Voting power comes from the voting token (IVotes):
Check Voting Power
uint256 votingPower = token.getPastVotes(voter, proposalSnapshot);
Voting power is snapshotted at the proposal creation block to prevent double-voting.

Delegation

Users can delegate their voting power:
Delegate Votes
// Delegate to another address
token.delegate(delegateAddress);

// Self-delegate to activate own voting power
token.delegate(msg.sender);
Tokens do not have voting power until delegated (even to yourself).

Timelock Integration

Execution Delay

All proposals execute through a timelock:
Timelock Flow
1. Proposal succeeds → Queue in timelock
2. Wait for timelock delay (e.g., 2 days)
3. Execute proposal

Cancellation Rights

Timelock guardians can cancel malicious proposals:
Guardian Cancel
// Guardians have CANCELLER_ROLE on timelock
timelock.cancel(operationId);

Admin Functions

Governor settings can be updated via governance:

Set Voting Delay

FolioGovernor.sol
function setVotingDelay(uint256 newVotingDelay) external onlyGovernance

Set Voting Period

FolioGovernor.sol
function setVotingPeriod(uint256 newVotingPeriod) external onlyGovernance

Set Proposal Threshold

FolioGovernor.sol
function setProposalThreshold(uint256 newProposalThreshold) external onlyGovernance
Proposal threshold cannot exceed 100% (1e18). This prevents locking governance.

Update Quorum

FolioGovernor.sol
function updateQuorumNumerator(uint256 newQuorumNumerator) external onlyGovernance

View Functions

Get Proposal State

FolioGovernor.sol
function state(uint256 proposalId) public view returns (ProposalState)

Check Voting

FolioGovernor.sol
function hasVoted(uint256 proposalId, address account) public view returns (bool)

Get Votes

FolioGovernor.sol
function proposalVotes(uint256 proposalId) public view returns (
    uint256 againstVotes,
    uint256 forVotes,
    uint256 abstainVotes
)

Events

ProposalCreated
event
Emitted when a new proposal is createdParameters:
  • proposalId - Unique proposal identifier
  • proposer - Address that created the proposal
  • targets - Target contract addresses
  • values - ETH values for calls
  • signatures - Function signatures
  • calldatas - Encoded function calls
  • startBlock - Voting start block
  • endBlock - Voting end block
  • description - Proposal description
VoteCast
event
Emitted when a vote is castParameters:
  • voter - Address that voted
  • proposalId - Proposal ID
  • support - Vote type (0=Against, 1=For, 2=Abstain)
  • weight - Voting power used
  • reason - Vote reason (if provided)
ProposalQueued
event
Emitted when proposal is queued in timelockParameters:
  • proposalId - Proposal ID
  • eta - Earliest execution time
ProposalExecuted
event
Emitted when proposal is executedParameters:
  • proposalId - Proposal ID

Error Handling

Governor__InvalidProposalThreshold
error
Thrown when trying to set proposal threshold above 100%

Best Practices

Proposal Creation
  • Provide clear, detailed descriptions
  • Test proposal calldata on testnet first
  • Consider timelock delay in planning
  • Communicate with community before proposing
Voting
  • Delegate your tokens to activate voting power
  • Vote early to signal direction
  • Provide reasoning for transparency
  • Monitor proposals actively
Security
  • Use multisig or DAO for guardian role
  • Set reasonable timelock delays (2-7 days typical)
  • Keep proposal threshold accessible but meaningful (0.1-1%)
  • Monitor for malicious proposals

Build docs developers (and LLMs) love