Advanced Concepts
Last updated
Last updated
Due to how the veFXS contract works, if you lock more FXS or increase the duration of your lock, you have to delegate your voting weight again using the VeFxsVotingDelegation
contract.
To delegate back to yourself, call the delegate()
function with your address as the delegatee.
Delegations can also be done by signature. See delegateBySig()
.
When your veFXS lock expires, your balance is equal to the amount of FXS you locked. In Frax Governance, as soon as your lock expires your voting weight goes to 0. You need to have an active lock to have voting weight in Frax Governance.
Click Propose
Make sure your voting weight is above the proposal threshold
Check proposal threshold here: https://etherscan.io/address/0xe8ab863e629a05c73d6a23b99d37027e3763156e#readContract#F25
Check your weight here: https://etherscan.io/address/0x6b83c4f3a6729fb7d5e19b720092162df439f567#readContract#F18
Fill out your transaction data
Submit
Frax Governance makes use of ScopeLift’s fractional voting: https://github.com/ScopeLift/flexible-voting/blob/master/src/GovernorCountingFractional.sol
This allows for users to split their voting power between for/against/abstain. It also allows smart contracts/custodians to pass through the voting power to liquidity providers based on their relative LP stake.
For a Safe to be properly configured for frxGov, it must have the FraxGuard installed, have FraxGovernorOmega as a signer, have FraxGovernorAlpha’s TimelockController installed as a module, and have FraxCompatibilityFallbackHandler set as the fallbackHandler. Safes can be sunset similarly through Alpha proposals by removing the configuration and removing them from the allowlist.
FraxGovernorOmega and FraxGovernorAlpha uses OpenZeppelin Governor and the following extensions:
GovernorSettings
GovernorVotesQuorumFraction
GovernorCountingFractional
In addition, Alpha uses these Governor extensions:
GovernorTimelockControl
GovernorPreventLateQuorum
See their configuration here:
https://docs.openzeppelin.com/contracts/4.x/api/governance#GovernorVotesQuorumFraction
https://docs.openzeppelin.com/contracts/4.x/api/governance#GovernorTimelockControl
https://docs.openzeppelin.com/contracts/4.x/api/governance#GovernorSettings
https://docs.openzeppelin.com/contracts/4.x/api/governance#GovernorPreventLateQuorum
All of the following functions must be called from an Alpha proposal.
Alpha and Omega:
setVotingDelayBlocks()
setVeFxsVotingDelegation()
updateShortCircuitNumerator()
Omega only:
addToSafeAllowlist()
removeFromSafeAllowlist()
addToDelegateCallAllowlist()
removeFromDelegateCallAllowlist()
setSafeVotingPeriod()
See the frxGov codebase for explanation of each of these functions.
See the Gnosis Safe codebase for Safe configuration that Alpha controls.
Identical to OpenZeppelin Governor with GovernorTimelockControl.
Create proposal - propose()
Vote - castVote()
If fails do nothing
If succeeds
queue()
Wait Timelock's minDelay
execute()
An owner uses the Gnosis Safe to initiate a DeFi transaction. This produces a transaction hash that identifies the action to be approved or rejected by the other Safe owners.
⅗ EOA sign the transaction through the UI
After 3 signatures are collected, anyone can call fraxGovernorOmega.addTransaction(address teamSafe, TxHashArgs calldata args, bytes calldata signatures)
to begin onchain governance. The team is incentivized to do so because they cannot execute any Gnosis transaction without FraxGovernorOmega’s approval.
veFXS voters have a 2-day window of time to vote on the proposal.
If no quorum is met during the voting window, or there are more for than against votes on the proposal, anyone can call execute()
. This calls safe.approveHash()
from Omega under the hood. It provides the needed approval from FraxGovernorOmega, which will allow the gnosis transaction to be executed through the Gnosis UI.
If quorum is met and there are more against than for votes. The proposal gets vetoed by anyone calling fraxGovernorOmega.rejectTransaction (address teamSafe, uint256 nonce)
. This will cause FraxGovernorOmega to sign a zero ETH transfer Safe transaction with the same nonce. Safe owners can then sign the same transaction in the UI using the “replace transaction” functionality. Safe owners can then execute the zero ETH transfer, incrementing the nonce. The original transaction can never be executed, because there is no approval from FraxGovernorOmega and the nonce has moved on, invalidating the original transaction.
Background: https://help.safe.global/en/articles/40783-what-are-signed-messages
Gnosis Safes can provide smart contract signatures. The typical mechanism is contained in Safe’s CompatibilityFallbackHandler, which checks if the threshold number of signatures is met from the owners, and then the Safe provides its approval. This mechanism was not sufficient for frxGov because we want Omega + owners to provide approval OR Alpha alone to provide approval.
To solve this we wrote FraxCompatibilityFallbackHandler, which only checks if safe.signedMessages(messageHash)
is set. Alpha and Omega can both call the setter of this state using SignMessageLib.signMessage()
.
FraxGovernorAlpha could be used to create a proposal that essentially shuts down the entire Frax protocol and returns all protocol owned liquidity to FXS holders. We have the Timelock delay on Alpha for situations like this. It will take a day from when this proposal passes to when it can be executed, giving Frax users time to exit the protocol via various liquidity pools. We also added in the OpenZeppelin Governor extension GovernorPreventLateQuorum. If a proposal reaches quorum late in the voting period, the voting period will get extended by 2 days. This stops malicious user(s) from voting for a malicious proposal at the last second. It gives time for the community to react in such a scenario.
We also created the frxEth redemption queue to allow users to exit frxEth if a malicious actor gets 51% of veFXS voting power and passes a proposal to infinite mint frxEth in an attempt to steal the treasury.
To conform to the ERC5805 standard, an implementing contract must implement several functions including function getPastTotalSupply(uint256 timepoint) external view returns (uint256)
. We use timestamps throughout our Governor contracts, however this function is the one exception and it must accept a block.number
. Unfortunately, veFXS.totalSupply(timestamp)
doesn't work for historical values, so we must use veFXS.totalSupplyAt(block.number)
.
This also impacts quorum values. Typically quorum is calculated from voting start timestamp (snapshot). We allow similar functionality by making the $votingDelayBlocks
value configurable by governance, so it mirrors the timestamp functionality.
https://github.com/FraxFinance/frax-governance
VeFxsVotingDelegation: https://etherscan.io/address/0x6b83c4f3a6729fb7d5e19b720092162df439f567
FraxGovernorAlpha: https://etherscan.io/address/0xe8Ab863E629a05c73D6a23b99d37027E3763156e
FraxGovernorAlphaTimelock: https://etherscan.io/address/0x821794E69d2831975a11f80E8092c682D5Ec8F83
FraxGovernorOmega: https://etherscan.io/address/0x953791D7C5ac8Ce5fb23BBBF88963DA37a95FE7a
FraxGuard: https://etherscan.io/address/0xed53eb15b2011395A7353e076024CBC9F19481D0
FraxCompatibilityFallbackHandler: https://etherscan.io/address/0x3FeFB779d737aCEa272686eA6E174ebF4273F973