Core Contracts & Epoch System Deep Dive
Component Deep Dive
Registrar (Registrar.sol
)
The authoritative, onchain configuration registry for the M0 protocol. It acts as the single source of truth for parameters.
-
Functionality: Implements
IRegistrar
. Acts as a secure key-value store (setKey(key, value)
,get(key)
) and manages critical address lists (addToList(list, account)
,removeFromList(list, account)
,listContains(list, account)
). -
Control: Write access (
setKey
,addToList
,removeFromList
) is strictly limited to the currently activeStandardGovernor
andEmergencyGovernor
contracts, enforced by theonlyStandardOr
EmergencyGovernor`` modifier. Read access is public. -
Usage: Essential for protocol operation. Core contracts (like those managing MToken minting/burning, rates) read configurations from the Registrar.
listContains
is fundamental for permission checks (e.g., verifying if an address is an approved Minter). -
Key Parameters Stored (Examples):
Parameter Key String (Used to derive bytes32
)Description Modified By Read By (Examples) "minters"
Whitelist of accounts allowed to mint M StandardGov/EmergencyGov (via lists) MinterGateway "validators"
Whitelist of offchain collateral validators StandardGov/EmergencyGov (via lists) MinterGateway "earners"
Whitelist for enabling M earning status StandardGov/EmergencyGov (via lists) MToken, WrappedMToken (direct & via EarnerMgr) "earners_list_ignored"
If set (non-zero), ignores the "earners" list StandardGov/EmergencyGov (via setKey
)MToken, WrappedMToken (direct & via EarnerMgr) "minter_rate_model"
Address of contract calculating minter interest StandardGov/EmergencyGov (via setKey
)MinterGateway "earner_rate_model"
Address of contract calculating earner interest StandardGov/EmergencyGov (via setKey
)MToken N/A (Read via vault()
func / constructor)Address of the DistributionVault
ZeroGovernor
(during deployment/reset)MinterGateway, Power token, Registrar "mint_ratio"
Maximum permitted mint-to-collateral ratio StandardGov/EmergencyGov (via setKey
)MinterGateway "update_collateral_interval"
Required frequency for collateral updates StandardGov/EmergencyGov (via setKey
)MinterGateway "minter_freeze_time"
Duration a minter is frozen upon penalty StandardGov/EmergencyGov (via setKey
)MinterGateway "penalty_rate"
Penalty rate for infractions (e.g., missed updates) StandardGov/EmergencyGov (via setKey
)MinterGateway "update_collateral_threshold"
Number of validator signatures needed StandardGov/EmergencyGov (via setKey
)MinterGateway "mint_delay"
Delay before a mint proposal is executable StandardGov/EmergencyGov (via setKey
)MinterGateway "mint_ttl"
Time-to-live for a mint proposal after delay StandardGov/EmergencyGov (via setKey
)MinterGateway "base_minter_rate"
Base rate for minter interest StandardGov/EmergencyGov (via setKey
)MinterRateModel "max_earner_rate"
Max rate for earner interest StandardGov/EmergencyGov (via setKey
)EarnerRateModel "em_admins"
List of admins allowed to manage earners via EarnerManager StandardGov/EmergencyGov (via lists) EarnerManager
DistributionVault (DistributionVault.sol
)
Aggregates and distributes various protocol revenues to Zero token
holders.
- Revenue Sources: Designed to receive arbitrary ERC20 tokens. Key expected sources include:
- Excess M yield generated by the protocol (must be transferred externally to the Vault's address).
CashToken
proceeds fromPower token
Dutch auctions (transferred automatically byPower token.buy
).- Unrefunded
CashToken
proposal fees from failed/expiredStandardGovernor
proposals (transferred by ``StandardGovernor.sendProposalFeeToVault
).
- Distribution Accounting (Critical Process):
- Trigger: The
distribute(token)
function must be called (permissionlessly, by any external account) for the Vault to recognize newly received balances of a specifictoken
. - Calculation: It measures the increase in the Vault's balance of
token
since the last call (IERC20(token).balanceOf(address(this)) - _lastTokenBalances[token]
). - Recording: This difference (the newly distributable amount) is added to the mapping
distributionOfAt[token][currentEpoch]
, earmarking it forZERO
holders of the current epoch. - State Update:
_lastTokenBalances[token]
is updated to the current balance.
- Trigger: The
- Claiming Mechanism:
Zero token
holders callclaim(token, startEpoch, endEpoch, destination)
or its gasless signature variantclaimBySig(...)
.- The function processes epochs strictly in the past (
endEpoch < currentEpoch
). It reverts if the range includes the current epoch or future epochs. - It iterates from
startEpoch
toendEpoch
. For each epochi
:- It retrieves the claimant's historical
ZERO
balance (claimantBalance
) and the totalZERO
supply (epochTotalSupply
) for epochi
usingIZero token(zeroToken).pastBalancesOf(account, i, i)
andIZero token(zeroToken).pastTotalSupplies(i, i)
. - It calculates the pro-rata share for that epoch:
share = (distributionOfAt[token][i] * claimantBalance * _GRANULARITY) / epochTotalSupply
. The_GRANULARITY
constant (1e9) is used during accumulation to minimize precision loss from integer division. - It checks the
hasClaimed[token][i][account]
mapping and skips the epoch if already claimed.
- It retrieves the claimant's historical
- After iterating, it marks all processed epochs (
startEpoch
toendEpoch
) as claimed in thehasClaimed
mapping for thattoken
andaccount
. - The final accumulated
claimed_
amount (scaled by_GRANULARITY
) is divided by_GRANULARITY
to get the true token amount,_lastTokenBalances[token]
is decremented, and the tokens are transferred to thedestination
.
- State: Maintains
_lastTokenBalances
(internal balance tracking),distributionOfAt
(per-token, per-epoch distributable amounts), andhasClaimed
(per-token, per-epoch, per-user claim status) mappings. More about the overall place of the Distribution Vault in the protocol in its dedicated section
StandardGovernor (StandardGovernor.sol
)
Manages the regular, day-to-day governance process with economic incentives.
- Allowed Proposal Actions: Limited to calling specific functions on itself which then interact with the
Registrar
or its own settings:addToList
,removeFromList
,removeFromAndAddToList
,setKey
, andsetProposalFee
. This prevents proposals from executing arbitrary code. Enforced byonlySelf
modifier and internal_revertIfInvalidCalldata
check. - Proposal Fees: Requires a
proposalFee
(set via governance, stored inproposalFee
state variable, initially set in constructor) paid in the currently activeCashToken
(cashToken
state variable). The fee is transferred from the proposer usingtransferFrom
whenpropose
is called. It is refunded viatransfer
upon successful execution (execute
), or can be optionally sent to theDistributionVault
viasendProposalFeeToVault
if the proposal is Defeated or Expired. ThecashToken
andproposalFee
can be changed by theZeroGovernor
viasetCashToken
. - Voting Mechanism: Uses
POWER
tokens. Success requires a simple majority (Yes votes > No votes), checked within thestate()
function logic (specificallyproposal_.yesWeight > proposal_.noWeight
when epoch > voteEnd). Voting is strictly confined to the designated Voting Epoch (odd-numbered epoch N+1 following the proposal epoch N). - Proposal Lifecycle:
- Propose: During a Transfer Epoch (N). Pays fee. Records proposal details. Increments
numberOfProposalsAt[N+1]
. If it's the first proposal for epoch N+1, triggersPower token.markNextVotingEpochAsActive
. - Vote: During the subsequent Voting Epoch (N+1).
state()
isActive
.POWER
transfers/delegations disabled. Voters callcastVote
orcastVotes
. - Tally & Outcome: At the start of Epoch N+2 (Transfer Epoch), the outcome is determined based on votes tallied at the end of Epoch N+1.
state()
becomesSucceeded
orDefeated
. - Execute: If
Succeeded
, can be executed by anyone callingexecute()
during Epoch N+2. Executes the proposed action (e.g.,setKey
onRegistrar
). Refunds fee to proposer. - Expire/Send Fee: If
Defeated
, orSucceeded
but not executed during Epoch N+2, becomesExpired
. Fee can then be sent toDistributionVault
viasendProposalFeeToVault
.
- Propose: During a Transfer Epoch (N). Pays fee. Records proposal details. Increments
- Reward Distribution: Tracks participation per voter per epoch (
numberOfProposalsVotedOnAt
,numberOfProposalsAt
). IfhasVotedOnAllProposals
returns true for a voter (voter_
) after they cast their final vote in an epoch (checked inside_castVote
), the contract callsPower token.markParticipation(voter_)
(to enablePOWER
inflation for the delegatee) andZero token.mint(voter_, reward)
(to distributeZERO
reward to the delegatee). Thereward
calculation usesmaxTotalZeroRewardPerActiveEpoch
.
EmergencyGovernor (EmergencyGovernor.sol
)
Provides an expedited path for critical governance actions using threshold consensus.
- Allowed Proposal Actions: Similar restricted scope as
StandardGovernor
, targeting itself to interact with theRegistrar
orStandardGovernor
:addToList
,removeFromList
,removeFromAndAddToList
,setKey
,setStandardProposalFee
. Enforced byonlySelf
and_revertIfInvalidCalldata
. - Proposal Fees: None required.
- Voting Mechanism: Uses
POWER
tokens. Requires a high threshold (thresholdRatio
, e.g., 6500 basis points = 65%) of the totalPOWER
supply at the snapshot block (voteStart - 1
). The required number of votes is calculated byproposalQuorum()
. Success determined instate()
by checkingyesWeight >= quorum()
. Faster voting window:voteEnd = voteStart + 1
, meaning voting spans the proposal epoch and the subsequent one. - Proposal Lifecycle:
- Propose: Can be proposed in any epoch (N). Records proposal details including the current
thresholdRatio
. - Vote: Voting is active during Epoch N and Epoch N+1. Voters call
castVote
orcastVotes
. - Execute: Can be executed by anyone calling
execute()
as soon as the state becomesSucceeded
(i.e., threshold is met), up until the end of Epoch N+1.proposalDeadline
is end of Epoch N+1. Executes the proposed action. - Outcome: If not executed by end of Epoch N+1, becomes
Expired
. If threshold never met, remainsDefeated
.
- Propose: Can be proposed in any epoch (N). Records proposal details including the current
- No Direct Incentives: Does not trigger
Power token
inflation orZero token
reward minting. Its use is reserved for genuine emergencies. The threshold ratio can be changed by theZeroGovernor
.
ZeroGovernor (ZeroGovernor.sol
)
The apex governor, controlling the structure of the governance system itself via threshold consensus.
- Allowed Proposal Actions: Limited to highly impactful, predefined functions targeting itself or the governors it controls:
resetToPowerHolders()
: RedeploysPower token
,StandardGovernor
,EmergencyGovernor
via their respective Deployer contracts, bootstrapping the newPower token
from the oldPower token
balances.resetToZeroHolders()
: Redeploys the same contracts, but bootstraps the newPower token
from the currentZero token
balances.setCashToken(address newCashToken, uint256 newProposalFee)
: Changes the designatedCashToken
used forStandardGovernor
fees andPower token
auctions (must be pre-approved in_allowedCashTokens
mapping, set at deployment). Also sets theStandardGovernor
fee simultaneously via an internal call.setEmergencyProposalThresholdRatio(uint16 newThresholdRatio)
: Adjusts the voting threshold for theEmergencyGovernor
via an internal call.setZeroProposalThresholdRatio(uint16 newThresholdRatio)
: Adjusts the voting threshold for theZeroGovernor
itself via_setThresholdRatio
.
- Proposal Fees: None required.
- Voting Mechanism: Uses
ZERO
tokens. Requires a threshold (thresholdRatio
) percentage of the totalZERO
supply at the snapshot block (voteStart - 1
).proposalQuorum()
calculates required votes. Success determined instate()
by checkingyesWeight >= quorum()
. Faster voting window:voteEnd = voteStart + 1
. - Proposal Lifecycle: Same faster lifecycle as
EmergencyGovernor
: Propose (N) -> Vote (N & N+1) -> Execute (anytime during N or N+1, once threshold met).proposalDeadline
is end of Epoch N+1. - Reset Functionality: The
resetTo*
functions call thedeploy()
method on the relevant deployer contracts (Power tokenDeployer
,StandardGovernorDeployer
,EmergencyGovernorDeployer
). These deployers useCREATE
to deterministically deploy new contract instances. TheZeroGovernor
emits aResetExecuted
event logging the addresses of the newly deployed contracts and the bootstrap token used. This is the designated mechanism for major governance upgrades or system recovery.
Epoch System (PureEpochs.sol
)
Overview
The entire governance system operates on a strict, time-based epoch system defined in the PureEpochs.sol
library. This structures governance activities and enhances security by preventing certain actions during sensitive periods.
- Epoch Length: Fixed at 15 days (
EPOCH_PERIOD = 15 days
). - Starting Timestamp: Epoch 1 commenced on September 15, 2022, 06:42:42 AM UTC (
STARTING_TIMESTAMP = 1_663_224_162
). The current epoch can be calculated based onblock.timestamp
. - Clock Mode: Conforms to ERC-6372 standard, defined by
PureEpochs.clockMode()
(which encodes the starting timestamp and period). - Epoch Types: Epochs alternate between two types:
- Transfer Epochs (Even-numbered Epochs):
- Activity:
Power token
s can be transferred and delegated.Power token
Dutch auctions are active (if supply available).StandardGovernor
proposals can be created targeting the next Voting Epoch.
- Activity:
- Voting Epochs (Odd-numbered Epochs):
- Restrictions:
Power token
transfers and delegations are disabled by thenotDuringVoteEpoch
modifier inPower token
to ensure stable voting power for the duration.Power token
Dutch auctions are inactive. - Activity:
StandardGovernor
proposals created in the previous Transfer Epoch can be voted on.Power token
inflation andZero token
rewards are calculated and distributed by the respective contracts based on voting participation within this specific epoch.
- Restrictions:
- Note on Emergency/Zero Governors: Their faster proposal lifecycle (
voteEnd = voteStart + 1
) means voting can span across the boundary between any two consecutive epochs (one Transfer, one Voting, or vice-versa), regardless of type restrictions onPower token
transfers. The system uses simple functions inPureEpochs.sol
likecurrentEpoch()
andtimeRemainingInCurrentEpoch()
to manage time. This alternating Transfer/Voting epoch structure is crucial for the integrity ofStandardGovernor
voting, preventing vote manipulation through last-minutePower token
movements.
- Transfer Epochs (Even-numbered Epochs):
Epoch-Based Token Tracking
Both Power token
and Zero token
inherit from EpochBasedVoteToken
, which uses a sophisticated snapshot system to track balances, total supply, voting power, and delegations historically across epochs. Key structures include:
// Stores an amount valid from a specific epoch onwards
struct AmountSnap {
uint16 startingEpoch;
uint240 amount;
}
// Stores an address valid from a specific epoch onwards
struct AccountSnap {
uint16 startingEpoch;
address account;
}
// Storage patterns (simplified representation)
mapping(address => AmountSnap[]) internal _balances; // Balance snaps per account
mapping(address => AccountSnap[]) internal _delegatees; // Delegatee snaps per delegator
mapping(address => AmountSnap[]) internal _votingPowers; // Voting power snaps per delegatee
AmountSnap[] internal _totalSupplies; // Total supply snaps
This allows the system to efficiently query any past state using functions like getPastVotes
, pastBalanceOf
, and pastTotalSupplies
, which are essential for:
- Determining voting weight for proposals based on past snapshots.
- Calculating fair distribution in the
DistributionVault
. - Enabling the inflation (
_getUnrealizedInflation
) and auction (_getTotalSupply(epoch - 1)
) mechanisms inPower token
. - Calculating
ZERO
rewards inStandardGovernor
(_getTotalSupply(epoch - 1)
).
Vote Batching
All three governors (StandardGovernor
, EmergencyGovernor
, ZeroGovernor
) inherit from BatchGovernor
and support vote batching to enable efficient multi-proposal voting in a single transaction:
// Cast votes for multiple proposals
function castVotes(
uint256[] calldata proposalIds,
uint8[] calldata supportList
) external returns (uint256 weight);
// Cast votes with reasons for multiple proposals
function castVotesWithReason(
uint256[] calldata proposalIds,
uint8[] calldata supportList,
string[] calldata reasonList_
) external returns (uint256 weight);
// Gasless signature variants also exist (e.g., castVotesBySig)
This reduces gas costs and improves the user experience for active participants.