Free 30-min Web3 Consultation
Book Consultation
Smart Contract Security Audits
View Audit Services
Custom DeFi Protocol Development
Explore DeFi
Full-Stack Web3 dApp Development
View App Services
Free 30-min Web3 Consultation
Book Consultation
Smart Contract Security Audits
View Audit Services
Custom DeFi Protocol Development
Explore DeFi
Full-Stack Web3 dApp Development
View App Services
Free 30-min Web3 Consultation
Book Consultation
Smart Contract Security Audits
View Audit Services
Custom DeFi Protocol Development
Explore DeFi
Full-Stack Web3 dApp Development
View App Services
Free 30-min Web3 Consultation
Book Consultation
Smart Contract Security Audits
View Audit Services
Custom DeFi Protocol Development
Explore DeFi
Full-Stack Web3 dApp Development
View App Services
LABS
Guides

Launching a Governance Token-Based Upgrade Approval

A technical guide to implementing a decentralized upgrade system. Token holders vote on proposals, and a designated executor deploys the approved changes. Covers proposal lifecycle, security patterns, and OpenZeppelin integration.
Chainscore © 2026
introduction
GOVERNANCE

Introduction to Token-Governed Upgrades

A guide to implementing on-chain upgrade mechanisms controlled by a protocol's native token holders.

Token-governed upgrades are a core mechanism for decentralized protocol evolution. Instead of relying on a centralized development team or multi-signature wallet to deploy new contract logic, this model empowers the community of token holders to propose, debate, and approve changes. This process is typically managed by a Governor contract, such as OpenZeppelin's Governor, which coordinates proposals, voting, and the final execution of the upgrade via a Timelock contract. This architecture ensures that no single entity can unilaterally alter the protocol, aligning control with the stakeholders who have a financial interest in its success.

The upgrade flow follows a structured lifecycle. First, a proposal is created, specifying the target contract (like a proxy admin or a UUPSUpgradeable implementation) and the new bytecode to be executed. Token holders then vote on the proposal, with their voting power proportional to their token balance, often using a snapshot of balances from a specific block. If the proposal achieves the required quorum and passes the vote threshold (e.g., a majority or supermajority), it moves to a Timelock period. This mandatory delay allows users to review the passed changes and, if necessary, exit the system before the upgrade is executed, providing a critical security safeguard.

Implementing this requires specific smart contract patterns. The most common is the Transparent Proxy Pattern or the UUPS (EIP-1822) pattern, where the core logic contract is separate from the storage contract. The Governor controls the proxy's upgrade function. A basic proposal execution call would look like this, targeting a TransparentUpgradeableProxy:

solidity
// Calldata to execute on the proxy admin to upgrade the implementation
proxyAdmin.upgrade(proxyAddress, newImplementationAddress);

The Timelock contract acts as the owner of the ProxyAdmin, so the Governor's successful proposal ultimately schedules this transaction in the Timelock.

Key parameters must be carefully configured for security and effectiveness. These include the voting delay (time between proposal and voting start), voting period (duration of the vote), proposal threshold (minimum tokens needed to submit a proposal), quorum (minimum voting power required for a valid outcome), and Timelock delay. Protocols like Uniswap and Compound use delays ranging from 2 to 7 days. Setting these values requires balancing agility against security; a very low quorum could allow a small group to pass proposals, while a very high delay might hinder necessary rapid responses to critical issues.

This governance model introduces unique considerations. Voter apathy can lead to low participation, making the protocol vulnerable to attacks by dedicated minority blocs. Snapshot is often used for gas-free off-chain signaling before an on-chain vote. Furthermore, the technical complexity of upgrade proposals necessitates clear communication and often a delegate system, where users can trust technical experts to vote on their behalf. Despite these challenges, token-governed upgrades represent a fundamental shift toward credible neutrality and long-term sustainability for decentralized applications.

prerequisites
TUTORIAL

Prerequisites and Setup

This guide details the prerequisites for implementing a governance token-based upgrade approval system for a smart contract, covering required tools, foundational knowledge, and initial project configuration.

Before writing any code, you must establish your development environment and understand the core components. You will need Node.js (v18 or later) and npm or yarn installed. The primary tool is a development framework like Hardhat or Foundry, which provides testing, deployment, and scripting capabilities. You'll also need access to an Ethereum node for testing; you can use a local Hardhat network, a testnet RPC provider like Alchemy or Infura, or a local node client. Basic familiarity with Solidity, JavaScript/TypeScript, and the ERC-20 token standard is essential.

The system architecture relies on two main smart contracts: a Governance Token and an Upgradeable Contract. The token, typically an ERC-20 with voting extensions (like OpenZeppelin's ERC20Votes), grants voting power. The upgradeable contract must use a proxy pattern, such as the Transparent Proxy or UUPS (EIP-1822), to separate logic from storage, allowing the logic to be replaced. You will use libraries like OpenZeppelin Contracts for secure, audited implementations of these standards, which significantly reduces development risk.

Initialize your project using your chosen framework. For a Hardhat project, run npx hardhat init and select the TypeScript template for better type safety. Install the necessary dependencies: npm install @openzeppelin/contracts @openzeppelin/hardhat-upgrades. Configure your hardhat.config.ts to include the upgrade plugin and set up network connections for deployment. For Foundry, use forge init and manage OpenZeppelin contracts via forge install. This setup creates the scaffold for writing, testing, and deploying your contracts.

system-architecture
GOVERNANCE

System Architecture Overview

A technical breakdown of implementing a secure, on-chain governance system for protocol upgrades using a native token.

A governance token-based upgrade approval system decentralizes control over a protocol's core logic. Instead of a single admin key, a multisig wallet or a DAO smart contract holds upgrade authority. To execute an upgrade, a formal proposal must be submitted on-chain, followed by a voting period where token holders cast votes weighted by their stake. This architecture shifts the security model from centralized trust to decentralized, economically-aligned consensus, making protocol changes transparent and community-driven.

The core components of this system are: the Governance Token (e.g., an ERC-20 or ERC-1155), the Governor Contract (manages proposals and voting, like OpenZeppelin's Governor), and the Upgradeable Proxy Contract (holds the protocol's state and logic, following patterns like Transparent or UUPS). The Governor contract is configured with parameters like votingDelay, votingPeriod, and quorumThreshold. A typical proposal lifecycle involves: Pending -> Active -> Succeeded/Defeated -> Queued -> Executed.

Here is a simplified code snippet for a proposal submission using OpenZeppelin's Governor framework:

solidity
function proposeUpgrade(address newImplementation, bytes memory upgradeCalldata) public returns (uint256 proposalId) {
    address[] memory targets = new address[](1);
    targets[0] = address(proxyAdmin);
    uint256[] memory values = new uint256[](1);
    bytes[] memory calldatas = new bytes[](1);
    calldatas[0] = abi.encodeWithSelector(
        proxyAdmin.upgrade.selector,
        proxyAddress,
        newImplementation
    );
    proposalId = governor.propose(targets, values, calldatas, "Upgrade to V2");
}

This bundles the call to upgrade the proxy into a governance proposal.

Security is paramount in this architecture. Key considerations include: protecting against vote manipulation through snapshotting mechanisms, ensuring the timelock between a proposal's success and its execution to allow for user exit, and rigorously auditing the upgrade logic to prevent malicious implementations. The Governor contract should be the sole proposer and executor for the proxy's admin, eliminating any centralized backdoor. Using established libraries like OpenZeppelin Contracts for the Governor, TimelockController, and upgradeable proxies significantly reduces audit surface area.

Real-world implementations vary. Compound's Governor Bravo and Uniswap's Governor are seminal on-chain governance models. Many protocols use a security council multisig as a fallback mechanism to fast-track critical bug fixes, creating a hybrid model. When designing parameters, align the votingPeriod (e.g., 3-7 days) and quorum (e.g., 4% of supply) with your token distribution to ensure legitimate participation without stagnation. The final executed upgrade call must be performed by the Timelock contract, not an EOA, guaranteeing the enforced delay.

core-contracts
GOVERNANCE TOKEN UPGRADES

Core Contract Components

Key smart contract modules and patterns required to implement a secure, decentralized upgrade process for a protocol using its native governance token.

05

Proposal Creation & Calldata

The process of crafting and encoding the transaction(s) that will upgrade the system. This is the most technical step for proposers.

  • Target & Calldata: The proposal specifies the target contract (e.g., the ProxyAdmin or Timelock) and the encoded function call (e.g., upgradeTo(address newImplementation)).
  • Multicall Proposals: Complex upgrades often bundle multiple calls into a single proposal using a Multicall contract for atomic execution.
  • Simulation: Proposals must be thoroughly simulated on a testnet or fork before mainnet submission to verify the upgrade's effects.
06

Security & Emergency Safeguards

Contingency mechanisms to handle bugs or malicious proposals. A robust system includes layers of defense.

  • Guardian/Pause Mechanism: A trusted, timelocked multisig that can pause the system in an emergency, independent of the standard governance cycle.
  • Veto Power: In some systems (e.g., Compound), a designated address can veto a malicious proposal that has passed.
  • Grace Period: A final delay after the timelock expires before execution, allowing for a last-minute community veto or intervention.
step-deploy-token
FOUNDATION

Step 1: Deploy the Governance Token

This step involves creating and deploying the on-chain asset that will represent voting power in your decentralized governance system.

A governance token is a fungible digital asset, typically an ERC-20 on Ethereum or a similar standard on other EVM chains, that grants its holder the right to participate in a protocol's decision-making process. Each token typically equals one vote. The initial distribution of these tokens is a critical design choice that defines the project's initial power structure. Common methods include a fair launch via liquidity mining, an airdrop to early users, a sale to fund development, or allocation to a core team and treasury. The token contract itself is often a standard implementation, like OpenZeppelin's ERC20Votes, which includes snapshotting capabilities to prevent vote manipulation.

For this guide, we'll use a simplified Solidity example based on OpenZeppelin contracts. The key feature is the inclusion of the ERC20Votes extension, which keeps a historical record of voting checkpoints, allowing users to delegate voting power without transferring tokens. First, ensure you have the OpenZeppelin library installed: npm install @openzeppelin/contracts. Then, you can write a basic governance token contract.

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

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Votes.sol";

contract GovernanceToken is ERC20, ERC20Votes {
    constructor() ERC20("ProjectGov", "PGOV") ERC20Permit("ProjectGov") {
        _mint(msg.sender, 1000000 * 10 ** decimals()); // Mint initial supply to deployer
    }

    // The following functions are overrides required by Solidity for ERC20Votes.
    function _afterTokenTransfer(address from, address to, uint256 amount) internal override(ERC20, ERC20Votes) {
        super._afterTokenTransfer(from, to, amount);
    }

    function _mint(address to, uint256 amount) internal override(ERC20, ERC20Votes) {
        super._mint(to, amount);
    }

    function _burn(address account, uint256 amount) internal override(ERC20, ERC20Votes) {
        super._burn(account, amount);
    }
}

Deploy this contract to your chosen network (e.g., Ethereum Sepolia, Arbitrum Sepolia) using a tool like Foundry or Hardhat. The deployer address will receive the entire initial supply, which you must then distribute according to your chosen model. Critical security considerations at this stage include: ensuring the token contract has no hidden minting functions (unless explicitly desired), verifying that the ERC20Votes snapshotting works correctly to prevent double-voting, and considering whether to add features like a timelock on team tokens. Once deployed, the contract address becomes the immutable identifier for your governance token and must be used in all subsequent steps for the governor contract and any user interfaces.

step-configure-governor
GOVERNANCE SETUP

Step 2: Configure and Deploy the Governor

This step details the deployment of the on-chain governance contract that will manage proposals and voting for your protocol's upgrades.

With your MyToken contract deployed, you now need to create its governing body. The Governor contract is the core on-chain component that will manage the proposal lifecycle—from creation and voting to execution. For this guide, we'll use OpenZeppelin's Governor contracts, which provide modular, battle-tested implementations. We will deploy a Governor that uses the ERC20Votes token for voting weight and a TimelockController for secure, delayed execution of successful proposals. This architecture separates the voting power (the token) from the execution authority (the timelock), a critical security best practice.

First, you must write and deploy the Governor contract itself. Below is a basic example inheriting from OpenZeppelin's Governor, GovernorSettings, GovernorCountingSimple, GovernorVotes, and GovernorTimelockControl. The constructor arguments configure key parameters: the voting delay (blocks before voting starts), voting period (duration of the vote), proposal threshold (minimum tokens needed to propose), the ERC20Votes token address, and the TimelockController address.

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

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 MyGovernor is Governor, GovernorSettings, GovernorCountingSimple, GovernorVotes, GovernorTimelockControl {
    constructor(IVotes _token, TimelockController _timelock)
        Governor("MyGovernor")
        GovernorSettings(1 /* 1 block voting delay */, 50400 /* 1 week voting period */, 0 /* 0 token proposal threshold */)
        GovernorVotes(_token)
        GovernorTimelockControl(_timelock)
    {}
    // ... required overrides for quorum, etc.
}

Before deploying MyGovernor, you must deploy a TimelockController. This contract acts as the executor, holding the protocol's upgrade authority. It introduces a mandatory delay between a proposal's approval and its execution, allowing token holders time to react if a malicious proposal slips through. Deploy it with your intended multi-signature wallet or EOA as the initial admin. The timelock delay is a critical security parameter; a common setting is 2-3 days (e.g., 172800 seconds). Use the OpenZeppelin TimelockController factory or deploy it directly via script.

Once both contracts are deployed, you must transfer control of the protocol's core components to the TimelockController. This is a crucial and potentially dangerous step. For a typical upgradeable proxy pattern using OpenZeppelin's TransparentUpgradeableProxy, you would change the proxy admin from your deployer wallet to the timelock address. After this transfer, only proposals that pass through the governance process can execute upgrades. Ensure you thoroughly test this flow on a testnet before mainnet deployment, as regaining control requires a successful governance proposal.

Finally, verify that the governance system is wired correctly. Key checks include: confirming the MyGovernor contract points to the correct token and timelock addresses, ensuring the timelock has the intended delay and admin, and verifying that the proxy admin role has been successfully transferred. At this point, the on-chain governance framework is live. Token holders can now delegate their votes, and proposers can create new governance proposals to manage the protocol's future, with execution gated by the timelock's safety delay.

step-build-upgrade-module
IMPLEMENTING GOVERNANCE LOGIC

Step 3: Build the Upgrade Execution Module

This step implements the core logic that executes the upgrade after receiving approval from the token-based governance vote.

The Upgrade Execution Module is a smart contract that acts as the final, permissioned executor for a protocol upgrade. Its primary function is to call the upgradeToAndCall function on a TransparentUpgradeableProxy contract, but only after verifying that a successful governance vote has authorized the action. This separation of concerns—voting on approval in one contract and executing in another—is a security best practice, often called the timelock pattern. It prevents a single transaction from both approving and immediately executing a potentially malicious upgrade.

The module's core logic involves two key checks. First, it must validate that the caller is the authorized Governor contract (like OpenZeppelin Governor). Second, it must confirm that the specific proposal ID for this upgrade has been successfully queued and its execution delay has passed. You implement this by storing a mapping, such as mapping(uint256 => bool) public executedProposals, to prevent re-execution. The execution function should be protected with a modifier like onlyGovernance that checks msg.sender against the stored governor address.

Here is a simplified code skeleton for the module's main function:

solidity
function executeUpgrade(address proxy, address newImplementation, bytes memory data) external onlyGovernance {
    require(!executedProposals[proposalId], "Proposal already executed");
    // Additional check: ensure proposal is in Queued state and timelock elapsed
    TransparentUpgradeableProxy(payable(proxy)).upgradeToAndCall(newImplementation, data);
    executedProposals[proposalId] = true;
}

The upgradeToAndCall function is used to atomically upgrade the proxy's logic contract and initialize the new implementation in a single transaction, which is crucial for complex upgrades that require setup.

For production use, integrate with a TimelockController contract. The standard pattern is: Governor proposes → Timelock queues after vote succeeds → Timelock executes after delay → Timelock calls your Execution Module. Your module would then use the TimelockController as its owner, adding another layer of security and a mandatory waiting period. This delay gives the community a final window to audit the upgrade transaction before it executes on-chain.

Finally, thoroughly test the execution module with forked mainnet simulations using tools like Foundry or Hardhat. Key test scenarios include: verifying the onlyGovernance modifier rejects unauthorized calls, confirming the proposal ID cannot be re-executed, and ensuring the upgradeToAndCall parameters correctly point to the verified new implementation contract. This module is the final gatekeeper for your protocol's upgradeability, so its security is paramount.

KEY DESIGN DECISIONS

Governance Parameter Trade-offs

Comparison of common parameter configurations for on-chain governance, balancing security, efficiency, and participation.

ParameterHigh SecurityBalancedHigh Agility

Quorum Threshold

50%

20-40%

< 10%

Voting Period

7-14 days

3-7 days

1-2 days

Proposal Threshold

1-5% of supply

0.1-1% of supply

0.01-0.1% of supply

Timelock Delay

72-168 hours

24-72 hours

0-24 hours

Vote Type

Weighted by stake

Weighted by stake

1 Token = 1 Vote

Delegation

Emergency Cancel

Execution Gas Limit

Unlimited

5-10M gas

< 5M gas

step-integrate-workflow
GOVERNANCE EXECUTION

Step 4: Integrate the Full Workflow

This final step combines the token, governor, and timelock into a complete system for proposing, voting on, and executing protocol upgrades.

With the governance token minted, a governor contract deployed, and a timelock executor configured, you now have all the components for a decentralized upgrade process. The full workflow follows a standard sequence: 1) A token holder creates a proposal, 2) The community votes during a defined period, 3) The proposal queues in the timelock if it succeeds, and 4) After the delay, anyone can execute the approved action. This structure separates the power to propose and vote from the power to execute, a critical security feature that prevents rushed or malicious upgrades.

The core integration point is the governor contract's relationship with the timelock. When deploying your governor (e.g., using OpenZeppelin's Governor contracts), you must set the timelock address as the executor. This means the governor does not directly call the target contract; instead, it schedules calls on the timelock. A proposal's calldata—such as a function signature and arguments for upgrading a proxy—is packaged into a transaction that the governor submits to the timelock's queue. Here is a simplified view of the proposal lifecycle in code: propose(targets, values, calldatas, description) → castVote(proposalId, support) → queue(proposalId) → execute(proposalId).

For developers, the key integration task is ensuring your governor contract has the proper permissions. The timelock should be the admin or owner of the upgradeable contracts you wish to govern (like a Transparent or UUPS proxy). Consequently, the only address authorized to perform an upgrade is the timelock itself. The governor, having the PROPOSER role on the timelock, is the only entity that can queue actions. This creates a permission flow: Token Holders → Governor (Proposer) → Timelock (Executor) → Target Contract. You can visualize this using a tool like OpenZeppelin Defender to monitor proposal states across contracts.

Testing the integrated workflow is non-negotiable. Use a forked mainnet or a local development chain to simulate the entire process end-to-end. Key tests should verify that: a proposal with sufficient votes moves from Active to Succeeded state, the queue function can only be called after the vote ends, the timelock enforces the correct delay before execution, and finally, the execute call successfully updates the target contract's logic. Consider edge cases like proposal cancellation, quorum failures, and the timelock's grace period which defines how long a queued proposal remains executable.

Once live, community education is crucial. Document the specific steps for token holders: how to delegate voting power, where to view active proposals (e.g., on Tally or Snapshot), how to connect their wallet to vote, and the meaning of voting options (For, Against, Abstain). Clearly communicate the governance parameters: voting delay, voting period, proposal threshold, quorum, and timelock delay. Transparency about these values builds trust and ensures participants understand the rules of the system they are governing.

GOVERNANCE UPGRADES

Security Considerations and Best Practices

Launching a governance token-based upgrade introduces unique security risks. This guide addresses common developer questions and pitfalls, focusing on smart contract implementation and operational security.

A timelock is a smart contract that enforces a mandatory delay between when a governance proposal is approved and when its actions can be executed. This is a critical security mechanism for governance token-based upgrades.

Key reasons for using a timelock:

  • Security Buffer: It provides token holders time to react to a malicious or faulty proposal. If a bad proposal passes, users can exit the system (e.g., withdraw funds) before the harmful code executes.
  • Transparency: All pending actions are visible on-chain during the delay period, allowing for public scrutiny.
  • Prevents Flashloan Attacks: It mitigates the risk of an attacker using a flashloan to acquire a majority of tokens, pass a proposal, and execute it instantly to drain funds.

Implementation Note: The timelock should be the owner or admin of the upgradeable contract (like a proxy). Proposals must queue actions in the timelock, which then executes them after the delay. A common standard is OpenZeppelin's TimelockController.

GOVERNANCE TOKEN UPGRADES

Frequently Asked Questions

Common technical questions and solutions for developers implementing on-chain governance for protocol upgrades.

A timelock is a smart contract that enforces a mandatory delay between a governance proposal's approval and its execution. It is a critical security mechanism for governance token-based upgrades.

Key reasons for using a timelock:

  • Security Buffer: Provides a "grace period" (e.g., 48-72 hours) for users and the community to review the finalized, executable code before it takes effect.
  • Exit Opportunity: Allows token holders to withdraw funds or exit the protocol if they disagree with a passed proposal.
  • Guard Against Malicious Proposals: Mitigates the risk of a rushed, harmful upgrade by a compromised wallet or a flash-loan attack on governance voting power.

Protocols like Uniswap, Compound, and Aave use timelocks (e.g., 2-day delays) as a standard security practice. The timelock contract address is typically set as the admin or owner of the upgradeable contract.

How to Build a Governance Token Upgrade Approval System | ChainScore Guides