Advanced Concepts

Delegations

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() .

veFXS Voting Weight Decay

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.

Smart Contract Design

Create an Alpha proposal

  1. Click Propose

  2. Fill out your transaction data

  3. Submit

Fractional Voting

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.

Governance Configuration

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

Additional Frax Governance Specific Configuration:

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.

Full Proposal Flow

Alpha

Identical to OpenZeppelin Governor with GovernorTimelockControl.

  • Create proposal - propose()

  • Vote - castVote()

  • If fails do nothing

  • If succeeds

    • queue()

    • Wait Timelock's minDelay

    • execute()

Omega

  1. 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.

  2. ⅗ EOA sign the transaction through the UI

  3. 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.

  4. veFXS voters have a 2-day window of time to vote on the proposal.

  5. 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.

    1. 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.

Smart Contract Signature Support (EIP1271 / EIP712)

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().

Attacks and Mitigations

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.

Technical Discussions

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.

Codebase and Contract Addresses

Codebase

https://github.com/FraxFinance/frax-governance

Contract Addresses

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

Last updated