LogoLogo
  • CrossCurve MetaLayer
    • ⚙️What is CrossCurve MetaLayer
      • CrossCurve Consensus bridge
      • CrossCurve Pools v2
    • 🗺️Roadmap
      • 2024
  • 🏢CrossCurve DAO
    • Overview of CrossCurve DAO
    • Voting
    • Obtaining veEYWA and Calculating the Boost
    • Staking mechanics
    • NFTs
      • CrossCurve DAO NFT
      • EYWA NFT Collection
  • 💼Earn with CrossCurve
    • Staking in CrossCurve
    • Providing Liquidity to CrossCurve Pools
    • Voting for Incentives
  • 📖user documentation
    • 🛸Migration to Sonic
      • Why are we moving to Sonic
      • Sonic Upgrade Stages
      • Liquidity transfer from Fantom to Sonic
      • Sonic Incentives on CrossCurve MetaLayer
    • 🔃Swap interface
      • How to trade
      • Slippage settings
      • Routing
    • 🌊Liquidity Interface
      • Easy mode
      • via Curve (Balanced)
      • Liquidity provision use cases
        • Deposit
          • Easy mode (Imbalanced)
          • via Curve (Balanced)
        • Withdraw
          • Easy mode (Imbalanced)
          • via Curve (Balanced)
        • Curve Knowledge Database
          • Balanced liquidity provision
          • Guide to transferring CRV from Fantom chain to Ethereum mainnet
          • Disclamer
    • 🏢DAO
      • Locker Interface
      • Vote Interface
      • Incentives Interface
      • Working with the EYWA Locker contract in Arbiscan.
    • 🌾Yield
      • Farms Interface
        • Staking liquidity and earning rewards
      • APR Calculator
      • EYWA pool via Convex
    • 💼Vesting
      • Claim portal interface
      • Early farming program interface
    • EYWA NFT
      • Bridge interface in the Aurora chain
      • Merge interface in the Arbitrum chain
      • EYWA NFT Manager interface
      • Dashboard interface
    • Leaderboard
    • ❄️Outdated
      • Early farming program
  • 📖Developer documentation
    • Pools/asset contracts
      • Hubchain Pools and Assets
      • 💱Supported tokens
    • 🔗CrossCurve smart contracts
    • 💻Guide for Developers
      • Technical Documentation for CrossCurve DAO Smart Contracts
        • CalldataHelperV1
        • DelegationManagerV1
        • DelegationConditionValidatorV1
        • EmissionManagerV1
        • EscrowManager
        • EscrowVoteManagerV1
        • GaugeFactoryV1
        • GaugeV1
        • IncentiveRewardsDistributor
        • LockHolderFactoryV1
        • LockHolderV1
        • ProposalManager
        • RebaseRewardsDistributorV1
        • RewardsDistributorFactoryV1
        • Treasury
      • 🔃Make cross-chain swap
      • 🔦Tracking cross-chain swap
      • 📔Pusher API Reference
      • 📝Glossary
      • API Specification
  • 📣About CrossCurve
    • 🛡️Security audits
    • 🧠Team
    • Project History
    • Website
    • Telegram
    • Twitter
    • Medium
    • Discord
    • YouTube
    • LinkedIn
    • GitHub
Powered by GitBook
On this page
  • Overview
  • Inherited Contracts and Interfaces
  • Constants
  • State Variables
  • Constructor
  • External Functions
  • Integration with OpenZeppelin Governor Modules
  • Internal Functions
  • Events
  • Errors
  • Summary
Export as PDF
  1. Developer documentation
  2. Guide for Developers
  3. Technical Documentation for CrossCurve DAO Smart Contracts

ProposalManager

Overview

ProposalManager is a governance contract built on OpenZeppelin’s Governor framework, incorporating additional logic for:

  • Protected Function Selectors: Certain function calls (on specific chain IDs and target addresses) are restricted to the contract owner. This prevents unauthorized calls to critical on-chain functionality within governance proposals.

  • Cross-Chain Awareness: Maintains a set of valid chain IDs for bridging proposals, allowing secure multi-chain governance actions when using the bridge.

  • Custom Quorum Rules: Allows the owner to adjust the quorum fraction within set bounds (25%–80%).

The contract also integrates with a Calldata Helper (CALLDATA_HELPER) to decode function selectors from the bridging calls. By extending Governor, GovernorCountingSimple, GovernorVotes, GovernorVotesQuorumFraction, GovernorTimelockControl, and Ownable, ProposalManager offers a robust on-chain DAO mechanism with time-locked execution, vote counting, adjustable quorum, and protective measures for crucial function selectors.


Inherited Contracts and Interfaces

  1. Governor (OpenZeppelin): Base contract for on-chain governance, offering functionalities such as proposal creation, voting, proposal states, etc.

  2. GovernorCountingSimple (OpenZeppelin): Implements a simple vote counting mechanism: For, Against, and Abstain.

  3. GovernorVotes (OpenZeppelin): Integrates an IVotes token (in this case, an escrow manager providing voting power) for calculating a user’s votes at proposal snapshot blocks.

  4. GovernorVotesQuorumFraction (OpenZeppelin): Defines quorum as a fraction of the total supply of the governance token. The fraction is adjustable by the contract’s owner within defined limits.

  5. GovernorTimelockControl (OpenZeppelin): Integrates a TimelockController for executing proposals after a governance-defined delay.

  6. Ownable (OpenZeppelin): Provides basic ownership functionality, allowing the owner to perform restricted actions (e.g., adjusting quorum fraction, managing chain IDs/selectors).

  7. IProposalManager (Custom): Declares additional functions, errors, and events specifically for managing cross-chain or bridging-based proposals, including protected selectors.

Additional External References:

  • ICalldataHelperV1 (CALLDATA_HELPER): A contract that decodes selectors from proposal call data, used for identifying protected function selectors in bridging calls.

  • EnumerableSet (OpenZeppelin): Used to store a set of valid chain IDs, ensuring efficient addition, removal, and existence checks.


Constants

uint256 public constant MINIMUM_QUORUM_NUMERATOR = 25;
uint256 public constant MAXIMUM_QUORUM_NUMERATOR = 80;
  • MINIMUM_QUORUM_NUMERATOR (25): Minimum permissible quorum fraction (25%).

  • MAXIMUM_QUORUM_NUMERATOR (80): Maximum permissible quorum fraction (80%).


State Variables

  • CALLDATA_HELPER (ICalldataHelperV1 immutable) The address of a helper contract used to decode function selectors for bridging calls. Set in the constructor and cannot be changed.

  • s_bridge (address) The address of the bridge contract used for cross-chain proposals or calls. Initially set in the constructor, can be updated by setBridge.

  • _chainIds (EnumerableSet.UintSet) A private set of valid chain IDs recognized by the contract. The owner can add or remove chain IDs via addChainId / removeChainId.

  • s_protectedSelectorsByChainIdAndTarget (mapping(uint256 => mapping(address => bytes4[]))) Stores arrays of protected function selectors for each (chainId, target) pair.

  • s_isProtectedSelector (mapping(uint256 => mapping(address => mapping(bytes4 => bool)))) A quick boolean lookup indicating if a particular function selector is protected on a given chainId and target address.


Constructor

constructor(
    IVotes escrowManager_,
    TimelockController timelock_,
    ICalldataHelperV1 calldataHelper_,
    address owner_,
    address bridge_
)
    Governor("EYWA DAO")
    GovernorVotes(escrowManager_)
    GovernorVotesQuorumFraction(50)
    GovernorTimelockControl(timelock_)
    Ownable(owner_)
{
    CALLDATA_HELPER = calldataHelper_;
    s_bridge = bridge_;
}
  • Parameters:

    • escrowManager_: An IVotes-compliant contract (e.g., an escrow manager) used for vote calculations.

    • timelock_: The TimelockController contract address used to queue and execute proposals after a time delay.

    • calldataHelper_: The helper contract for decoding function selectors in bridging calls.

    • owner_: Address designated as the owner of this contract (can set quorum fraction, manage chain IDs, etc.).

    • bridge_: The initial address of the cross-chain bridge.

  • Logic and Effects:

    1. Initializes the underlying Governor modules:

      • Governor("EYWA DAO") sets the governor name.

      • GovernorVotes(escrowManager_) specifies the votes token source.

      • GovernorVotesQuorumFraction(50) sets an initial quorum fraction of 50%.

      • GovernorTimelockControl(timelock_) ties this governor to the specified timelock.

      • Ownable(owner_) sets the contract owner.

    2. Stores CALLDATA_HELPER and s_bridge.


External Functions

1. setBridge(address bridge_)

function setBridge(address bridge_) external onlyOwner
  • Description: Updates the s_bridge address used for bridging calls. Only callable by the contract owner.

  • Parameters:

    • bridge_: The new bridge contract address.

  • Events:

    • BridgeUpdated(oldBridge, newBridge) logs the address change.


2. addChainId(uint256 chainId_)

function addChainId(uint256 chainId_) external onlyOwner
  • Description: Adds a new chain ID to _chainIds. If the chain ID already exists, it reverts.

  • Checks:

    • _chainIds.add(chainId_) must return true, otherwise reverts with ChainIdAlreadyExists(chainId_).


3. removeChainId(uint256 chainId_)

function removeChainId(uint256 chainId_) external onlyOwner
  • Description: Removes an existing chain ID from _chainIds. If the chain ID does not exist, it reverts.

  • Checks:

    • _chainIds.remove(chainId_) must return true, otherwise reverts with ChainIdDoesNotExist(chainId_).


4. getChainIdsLength()

function getChainIdsLength() external view returns (uint256)
  • Description: Returns the number of chain IDs stored in _chainIds.

  • Return:

    • uint256: The length of _chainIds.


5. getChainIdAtIndex(uint256 index_)

function getChainIdAtIndex(uint256 index_) external view returns (uint256)
  • Description: Retrieves a chain ID from _chainIds by index_.

  • Return:

    • uint256: The chain ID at the specified index.


6. addProtectedSelector(uint256 chainId_, address target_, bytes4 selector_)

function addProtectedSelector(uint256 chainId_, address target_, bytes4 selector_) external onlyOwner
  • Description: Marks a function selector (selector_) as protected on a particular chainId_ and target_. If the chain ID doesn’t exist or the selector is already protected, it reverts.

  • Events & Checks:

    • Must have _chainIds.contains(chainId_), otherwise InvalidChainId(...).

    • If s_isProtectedSelector[chainId_][target_][selector_] is true, reverts with SelectorAlreadyExists(...).

    • Adds the selector to s_protectedSelectorsByChainIdAndTarget[chainId_][target_] and sets s_isProtectedSelector[chainId_][target_][selector_] = true.


7. removeProtectedSelector(uint256 chainId_, address target_, bytes4 selector_)

function removeProtectedSelector(uint256 chainId_, address target_, bytes4 selector_) external onlyOwner
  • Description: Removes a protected function selector from s_protectedSelectorsByChainIdAndTarget. If the chain ID isn’t recognized or the selector isn’t actually protected, it reverts.

  • Logic:

    1. Ensures chainId_ is valid in _chainIds.

    2. Checks if s_isProtectedSelector[chainId_][target_][selector_] == true; otherwise reverts with SelectorDoesNotExist(...).

    3. Searches in the array s_protectedSelectorsByChainIdAndTarget[chainId_][target_], removes the entry by swapping the last element, then popping the array.

    4. Deletes s_isProtectedSelector[chainId_][target_][selector_].


8. updateQuorumNumerator(uint256 quorumNumerator_)

function updateQuorumNumerator(uint256 quorumNumerator_) external override onlyOwner
  • Description: An override from GovernorVotesQuorumFraction that checks if the new quorum fraction is within [MINIMUM_QUORUM_NUMERATOR, MAXIMUM_QUORUM_NUMERATOR]. Otherwise reverts with GovernorInvalidQuorumFraction(...).


9. updateTimelock(TimelockController)

function updateTimelock(TimelockController) external override
  • Description: An override from GovernorTimelockControl. This function does nothing here (the timelock is set in the constructor).


10. propose(...) (Overridden from Governor)

function propose(
    address[] memory targets_,
    uint256[] memory values_,
    bytes[] memory calldatas_,
    string memory description_
)
    public
    override (Governor, IGovernor)
    returns (uint256)
  • Description:

    • Creates a new proposal. Before creation, the contract calls _checkSelectors(...) to see if any protected calls are included in calldatas_. If protected calls are found but the proposer is not the owner, it reverts with ProtectedFunctionSelectorUsed().

    • After checking, it calls super.propose(...) to proceed with the standard Governor proposal workflow.

  • Parameters:

    • targets_, values_, calldatas_: Arrays specifying which contracts and functions to call, with how much ETH, plus the function data.

    • description_: A string describing the proposal.

  • Return:

    • uint256: The ID of the newly created proposal.


Integration with OpenZeppelin Governor Modules

clock() (IERC6372)

function clock() public view override (Governor, GovernorVotes, IERC6372) returns (uint48)
  • Description: Returns the current timestamp as a uint48. Used by the governance system for time-related logic.

state(uint256 proposalId_)

function state(uint256 proposalId_) public view override(Governor, GovernorTimelockControl, IGovernor) returns (ProposalState)
  • Description: Returns the current state of a proposal (Pending, Active, Canceled, Defeated, Succeeded, Queued, Expired, or Executed), integrating timelock considerations.

quorum(uint256 timepoint_)

function quorum(uint256 timepoint_) public view override (Governor, GovernorVotesQuorumFraction, IGovernor) returns (uint256)
  • Description: Calculates the number of votes required for quorum at a given timepoint_, i.e., (totalSupplyAt(timepoint_) * quorumNumerator(timepoint_)) / quorumDenominator().

CLOCK_MODE()

function CLOCK_MODE() public pure override (Governor, GovernorVotes, IERC6372) returns (string memory)
  • Description: Returns "mode=timestamp", indicating that block timestamps (instead of block numbers) are used for governance timing.

proposalThreshold(), votingDelay(), votingPeriod(), proposalNeedsQueuing(...)

These standard overrides define:

  • proposalThreshold(): 2_500e18 — minimum voting power needed to create a proposal.

  • votingDelay(): 2 days — time between proposal creation and the start of voting.

  • votingPeriod(): 5 days — how long votes are accepted.

  • proposalNeedsQueuing(...): returns true, indicating proposals must be queued in the timelock after passing, before execution.


Internal Functions

_checkSelectors(...)

function _checkSelectors(
    address proposalCreator_,
    address[] memory targets_,
    bytes[] memory calldatas_
)
    private
    view
  • Description:

    • Iterates over targets_ and associated calldatas_, extracting the function selector for each. Then checks if it is among the protected selectors for the given chain ID (block.chainid) or if the target is the s_bridge address.

    • If any call uses a protected selector and proposalCreator_ is not the contract owner, reverts with ProtectedFunctionSelectorUsed().

  • Parameters:

    • proposalCreator_: The address that created the proposal.

    • targets_: The array of target contract addresses.

    • calldatas_: The array of encoded function calls corresponding to each target.

  • Logic:

    1. If targets_[i] equals s_bridge, decodes further with CALLDATA_HELPER.decode(...) to find (m_calldata, m_target, m_chainId). Then extracts the first 4 bytes (selector) from m_calldata.

    2. Checks s_protectedSelectorsByChainIdAndTarget[m_chainId][m_target].

    3. If matched, marks m_isProtected = true.

    4. Otherwise, if targets_[i] != s_bridge, reads the first 4 bytes from calldatas_[i] as the selector and checks s_protectedSelectorsByChainIdAndTarget[block.chainid][targets_[i]].

    5. If m_isProtected == true and proposalCreator_ != owner(), revert with ProtectedFunctionSelectorUsed().


_queueOperations(...), _executeOperations(...), _cancel(...), _executor()

These are standard overrides from GovernorTimelockControl that handle queueing, executing, and canceling proposals in the timelock. The _executeOperations override also calls _checkSelectors(...) again before executing the proposal.


Events

  1. BridgeUpdated(address indexed oldBridge, address indexed newBridge)

    • Emitted when the bridge address changes via setBridge.

No additional custom events are defined besides those in the IProposalManager interface and standard Governor events.


Errors

From IProposalManager, we have:

  • ChainIdAlreadyExists(uint256 chainId)

  • ChainIdDoesNotExist(uint256 chainId)

  • SelectorAlreadyExists(bytes4 selector)

  • SelectorDoesNotExist(bytes4 selector)

  • InvalidChainId(uint256 chainId)

  • ProtectedFunctionSelectorUsed()

  • GovernorInvalidQuorumFraction(uint256 newQuorumNumerator, uint256 quorumDenominator) (part of GovernorVotesQuorumFraction logic)


Summary

ProposalManager extends and customizes OpenZeppelin’s Governor to cater to CrossCurve DAO’s multi-chain governance needs. Through protective measures (_checkSelectors), it guards certain function calls on specific chain IDs and target addresses, ensuring only the contract owner can propose them. Additionally, it manages a dynamic range of acceptable quorum fractions and integrates with a TimelockController for secure, time-delayed proposal execution. By combining these features with ICalldataHelperV1 for decoding bridging calls, ProposalManager offers a flexible yet secure governance solution for cross-chain proposals in the CrossCurve ecosystem.

PreviousLockHolderV1NextRebaseRewardsDistributorV1

Last updated 1 month ago

📖
💻