Skip to main content

Governance Contract

The Governance contract enables token holders to propose and vote on protocol changes.

Contract Overview

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

import "@openzeppelin/contracts/governance/Governor.sol";
import "@openzeppelin/contracts/governance/extensions/GovernorSettings.sol";
import "@openzeppelin/contracts/governance/extensions/GovernorCountingSimple.sol";
import "@openzeppelin/contracts/governance/extensions/GovernorVotes.sol";
import "@openzeppelin/contracts/governance/extensions/GovernorTimelockControl.sol";

contract UsmeweGovernor is
    Governor,
    GovernorSettings,
    GovernorCountingSimple,
    GovernorVotes,
    GovernorTimelockControl
{
    // Voting power multipliers for levels
    mapping(address => uint256) public levelMultipliers;

    // Proposal types
    enum ProposalType {
        ParameterChange,
        ContractUpgrade,
        TreasurySpend,
        EmergencyAction
    }

    struct ProposalMetadata {
        ProposalType proposalType;
        string ipfsHash;
        address proposer;
    }

    mapping(uint256 => ProposalMetadata) public proposalMetadata;
}

Key Functions

Create Proposal

function propose(
    address[] memory targets,
    uint256[] memory values,
    bytes[] memory calldatas,
    string memory description,
    ProposalType proposalType,
    string memory ipfsHash
) public returns (uint256 proposalId) {
    require(
        getVotes(msg.sender, block.number - 1) >= proposalThreshold(),
        "Below proposal threshold"
    );

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

    proposalMetadata[proposalId] = ProposalMetadata({
        proposalType: proposalType,
        ipfsHash: ipfsHash,
        proposer: msg.sender
    });

    emit ProposalCreatedWithMetadata(proposalId, proposalType, ipfsHash);

    return proposalId;
}

Cast Vote

function castVote(
    uint256 proposalId,
    uint8 support
) public override returns (uint256 weight) {
    address voter = msg.sender;

    // Get base voting power
    uint256 baseWeight = getVotes(voter, proposalSnapshot(proposalId));

    // Apply level multiplier (Platinum: 1.5x, Diamond: 2x)
    uint256 multiplier = levelMultipliers[voter];
    if (multiplier == 0) multiplier = 100; // 1x default

    weight = (baseWeight * multiplier) / 100;

    _countVote(proposalId, voter, support, weight, "");

    emit VoteCastWithWeight(voter, proposalId, support, weight, baseWeight);

    return weight;
}

Execute Proposal

function execute(
    address[] memory targets,
    uint256[] memory values,
    bytes[] memory calldatas,
    bytes32 descriptionHash
) public payable override returns (uint256) {
    uint256 proposalId = hashProposal(targets, values, calldatas, descriptionHash);

    require(
        state(proposalId) == ProposalState.Succeeded,
        "Proposal not successful"
    );

    // Emergency proposals skip timelock
    if (proposalMetadata[proposalId].proposalType == ProposalType.EmergencyAction) {
        _executeOperations(proposalId, targets, values, calldatas, descriptionHash);
    } else {
        super.execute(targets, values, calldatas, descriptionHash);
    }

    emit ProposalExecuted(proposalId);

    return proposalId;
}

Update Level Multiplier

function updateLevelMultiplier(
    address user,
    uint256 multiplier
) external onlyRole(LEVEL_MANAGER_ROLE) {
    require(multiplier >= 100 && multiplier <= 200, "Invalid multiplier");

    levelMultipliers[user] = multiplier;

    emit LevelMultiplierUpdated(user, multiplier);
}

View Functions

Get Proposal Details

function getProposalDetails(uint256 proposalId) external view returns (
    ProposalState currentState,
    uint256 forVotes,
    uint256 againstVotes,
    uint256 abstainVotes,
    uint256 startBlock,
    uint256 endBlock,
    ProposalType proposalType,
    string memory ipfsHash
) {
    (forVotes, againstVotes, abstainVotes) = proposalVotes(proposalId);

    return (
        state(proposalId),
        forVotes,
        againstVotes,
        abstainVotes,
        proposalSnapshot(proposalId),
        proposalDeadline(proposalId),
        proposalMetadata[proposalId].proposalType,
        proposalMetadata[proposalId].ipfsHash
    );
}

Get Voting Power

function getVotingPower(address account) external view returns (
    uint256 baseVotes,
    uint256 multiplier,
    uint256 effectiveVotes
) {
    baseVotes = getVotes(account, block.number - 1);
    multiplier = levelMultipliers[account];
    if (multiplier == 0) multiplier = 100;
    effectiveVotes = (baseVotes * multiplier) / 100;

    return (baseVotes, multiplier, effectiveVotes);
}

Events

event ProposalCreatedWithMetadata(
    uint256 indexed proposalId,
    ProposalType proposalType,
    string ipfsHash
);

event VoteCastWithWeight(
    address indexed voter,
    uint256 indexed proposalId,
    uint8 support,
    uint256 effectiveWeight,
    uint256 baseWeight
);

event ProposalExecuted(uint256 indexed proposalId);

event LevelMultiplierUpdated(
    address indexed user,
    uint256 multiplier
);

Governance Parameters

ParameterValueDescription
votingDelay1 dayTime before voting starts
votingPeriod7 daysDuration of voting
proposalThreshold1000 tmUSDCMinimum to propose
quorumNumerator4%Minimum participation

Proposal Types

Parameter Change

Modify protocol parameters (interest rates, limits, fees)

Contract Upgrade

Upgrade proxy implementations

Treasury Spend

Allocate treasury funds

Emergency Action

Critical fixes (bypasses timelock)

Voting Power by Level

LevelBase MultiplierEffective Vote
Bronze1.0x100 tmUSDC = 100 votes
Silver1.0x100 tmUSDC = 100 votes
Gold1.0x100 tmUSDC = 100 votes
Platinum1.5x100 tmUSDC = 150 votes
Diamond2.0x100 tmUSDC = 200 votes

Proposal Lifecycle

┌─────────────────────────────────────────────────────────────────┐
│  PROPOSAL LIFECYCLE                                             │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  Created → Pending → Active → Succeeded → Queued → Executed    │
│              │          │         │                             │
│              │          │         └──→ Defeated                 │
│              │          │                                       │
│              │          └──→ Defeated (quorum not met)          │
│              │                                                  │
│              └──→ Cancelled (by proposer)                       │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘
StateDescription
PendingWaiting for voting delay
ActiveVoting in progress
SucceededPassed, awaiting execution
DefeatedFailed (votes or quorum)
QueuedIn timelock queue
ExecutedSuccessfully executed
CancelledCancelled by proposer

Integration Example

import { ethers } from 'ethers';

// Check voting power
const power = await governor.getVotingPower(myAddress);
console.log(`Effective votes: ${power.effectiveVotes}`);

// Create proposal
const targets = [trustVaultAddress];
const values = [0];
const calldatas = [
  trustVault.interface.encodeFunctionData('updateInterestRate', [500])
];
const description = "Proposal #1: Reduce base interest rate to 5%";

const proposalId = await governor.propose(
  targets,
  values,
  calldatas,
  description,
  0, // ParameterChange
  "QmXxx..." // IPFS hash with details
);

// Vote
await governor.castVote(proposalId, 1); // 1 = For

// Execute after voting period
await governor.execute(targets, values, calldatas, ethers.id(description));

Security Measures

Critical governance security features:
  • Timelock: 48-hour delay on non-emergency actions
  • Quorum: 4% minimum participation required
  • Proposal Threshold: Prevents spam proposals
  • Emergency Guardian: Multi-sig can pause in emergencies