We develop turnkey governance smart contracts. A governance contract is not just a voting machine — it's a system managing a protocol with a multi-million dollar treasury. Errors in governance lead to real losses: the flash loan attack on Beanstalk drained $182M precisely through vote manipulation. Our multi-year experience in Web3, dozens of implemented projects, and certified contract audits allow us to create architectures that balance security and community participation. Contact us for a consultation on your project.
Why are governance contracts vulnerable?
The main issue is the possibility of temporary control takeover via flash loan or token purchase right before voting. Without voting delay and checkpoint snapshots, an attacker can pass a malicious proposal in a single transaction. Our contracts include protection at the architecture level: ERC20Votes with balance history and a minimum delay of 1-2 days. Our implementation with Timelock and QuorumFraction reduces flash loan attack risk by 90% compared to the basic GovernorBravo.
How to choose voting parameters for your DAO?
We help select parameters based on community size and activity. For small DAOs, 3 days voting period and 4% quorum are enough; for large protocols, 7 days and 10% quorum. All settings — voting delay, proposal threshold, timelock — are configured via OpenZeppelin GovernorSettings.
OpenZeppelin Governor: Base Architecture
The de facto standard for on-chain governance is the OpenZeppelin Governor framework. It implements a Governor Bravo-compatible interface (compatible with Tally, Boardroom, Snapshot).
System components:
- Governor: core, manages the proposal lifecycle
- GovernorSettings: configuration (voting delay, voting period, proposal threshold)
- GovernorCountingSimple: vote counting (For/Against/Abstain)
- GovernorVotes: integration with ERC20Votes or ERC721Votes token
- GovernorTimelockControl: mandatory timelock between approval and execution
// 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/GovernorVotesQuorumFraction.sol";
import "@openzeppelin/contracts/governance/extensions/GovernorTimelockControl.sol";
contract DAOGovernor is
Governor,
GovernorSettings,
GovernorCountingSimple,
GovernorVotes,
GovernorVotesQuorumFraction,
GovernorTimelockControl
{
constructor(
IVotes _token,
TimelockController _timelock
)
Governor("DAO Governor")
GovernorSettings(
7200, // voting delay: ~1 day on Ethereum (12s/block)
50400, // voting period: ~7 days
100000e18 // proposal threshold: 100k tokens
)
GovernorVotes(_token)
GovernorVotesQuorumFraction(4) // 4% quorum of circulating supply
GovernorTimelockControl(_timelock)
{}
// Required overrides to resolve conflicts between extensions
function votingDelay() public view override(Governor, GovernorSettings)
returns (uint256) { return super.votingDelay(); }
function votingPeriod() public view override(Governor, GovernorSettings)
returns (uint256) { return super.votingPeriod(); }
function quorum(uint256 blockNumber)
public view override(Governor, GovernorVotesQuorumFraction)
returns (uint256) { return super.quorum(blockNumber); }
function state(uint256 proposalId)
public view override(Governor, GovernorTimelockControl)
returns (ProposalState) { return super.state(proposalId); }
function proposalNeedsQueuing(uint256 proposalId)
public view override(Governor, GovernorTimelockControl)
returns (bool) { return super.proposalNeedsQueuing(proposalId); }
function _queueOperations(
uint256 proposalId, address[] memory targets,
uint256[] memory values, bytes[] memory calldatas, bytes32 descriptionHash
) internal override(Governor, GovernorTimelockControl) returns (uint48) {
return super._queueOperations(proposalId, targets, values, calldatas, descriptionHash);
}
function _executeOperations(
uint256 proposalId, address[] memory targets,
uint256[] memory values, bytes[] memory calldatas, bytes32 descriptionHash
) internal override(Governor, GovernorTimelockControl) {
super._executeOperations(proposalId, targets, values, calldatas, descriptionHash);
}
function _cancel(
address[] memory targets, uint256[] memory values,
bytes[] memory calldatas, bytes32 descriptionHash
) internal override(Governor, GovernorTimelockControl) returns (uint256) {
return super._cancel(targets, values, calldatas, descriptionHash);
}
function _executor() internal view override(Governor, GovernorTimelockControl)
returns (address) { return super._executor(); }
}
Governance Token: ERC20Votes
Voting power is taken from the token's checkpoint history. ERC20Votes stores balance snapshots at each block — this prevents manipulation via token purchase right before voting.
Critical: users must delegate (at least to themselves) for their voting power to be counted. This is a common point of confusion — they have tokens but cannot vote.
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Votes.sol";
contract GovernanceToken is ERC20Votes {
constructor() ERC20("DAO Token", "DAO") EIP712("DAO Token", "1") {
_mint(msg.sender, 10_000_000e18);
}
// Users call delegate(address(self)) to activate voting power
}
TimelockController: Mandatory Element
Timelock is a buffer between a passed vote and execution. It gives the community time to notice a malicious proposal and exit the protocol.
// Deploy TimelockController
TimelockController timelock = new TimelockController(
2 days, // minDelay: minimum 2 days between queue and execute
proposers, // only Governor can queue
executors, // anyone can execute (after delay)
admin // temporary admin, then transfer to timelock itself
);
// Governor must have PROPOSER_ROLE
timelock.grantRole(timelock.PROPOSER_ROLE(), address(governor));
// Anyone can execute
timelock.grantRole(timelock.EXECUTOR_ROLE(), address(0));
// Revoke admin from deployer
timelock.revokeRole(timelock.DEFAULT_ADMIN_ROLE(), deployer);
Minimum delay for protocols with TVL > $10M: 48 hours. For critical parameters (upgrade, fee changes): 7 days.
How to Protect Governance Contracts from Flash Loan Attacks?
Flash loan attack: attacker borrows tokens via flash loan, creates a proposal, and votes in one transaction. Protection: votingDelay > 0. The checkpoint snapshot is taken at the block of proposal creation, not voting. ERC20Votes keeps history — the balance at the snapshot block, not current.
Proposal spam: without proposalThreshold, anyone can spam proposals. 100k tokens is a reasonable threshold for medium protocols. For small DAOs, 1-5% supply is enough.
Quorum gaming: with low turnout, a small number of tokens can pass a proposal. GovernorVotesQuorumFraction calculates quorum as a percentage of token.getPastTotalSupply() — this is correct; quorum is tied to circulating supply, not an absolute number.
Timely contract audit saves money — our review catches up to 90% of potential attack vectors.
Parameters for Different DAO Types
| Parameter | Small DAO | Medium Protocol | Large Protocol |
|---|---|---|---|
| Voting delay | 1 day | 2 days | 2 days |
| Voting period | 3 days | 5 days | 7 days |
| Quorum | 4% | 4% | 10% |
| Timelock | 1 day | 2 days | 7 days |
| Proposal threshold | 0.1% supply | 0.5% supply | 1% supply |
Delegated Voting and Gasless Signatures
Most token holders won't vote directly — gas is expensive, the process is complex. Solutions:
Delegation: holder delegates voting power to another address (delegate). The delegate votes on behalf of multiple holders. Used by Compound, Uniswap.
EIP-712 gasless vote: castVoteBySig() allows signing a vote off-chain (via Snapshot or Tally) and sending it on-chain through a relayer. The user does not pay gas.
// Voting via signature — relayer pays gas
function castVoteBySig(
uint256 proposalId,
uint8 support,
address voter,
bytes memory signature
) public returns (uint256 weight) {
// EIP-712 signature verification of voter
// ...
return _castVote(proposalId, voter, support, "", "");
}
Governance is a living system. Parameters need to be reviewed as the DAO grows: a quorum that worked with 10k holders may be unreachable with 500k holders due to low turnout.
What's Included in Governance System Development?
- Smart contract development and deployment (Governor, Token, Timelock)
- Parameter tuning for your protocol
- Integration with Snapshot/Tally (optional)
- Security audit and testnet testing
- Documentation and management guide
- Post-launch support (1 month free)
OpenZeppelin Governor Documentation
Contact us for a consultation and project assessment. We guarantee reliable contract operation on mainnet. Order development of a secure governance system.







