GovernanceUtils
GovernanceUtils provides small, reusable primitives for time-delayed governance and safe admin handover. The package surface is intentionally minimal and focuses on the common pattern of proposing a new value, waiting a configured delay, and then accepting the proposal.
Overview
Contract Type: Library and base abstract contract
License: EVVM-NONCOMMERCIAL-1.0
Import Path: @evvm/testnet-contracts/library/utils/GovernanceUtils.sol
Key Features
- Time-delayed proposals for addresses and uint values
- Simple boolean-flag proposals with delay
- Built-in events to track proposals and acceptances
- Small, audit-friendly surface
Structures
ProposalStructs exposes a few simple structs used by governance-enabled contracts:
AddressTypeProposal { address current; address proposal; uint256 timeToAccept; }UintTypeProposal { uint256 current; uint256 proposal; uint256 timeToAccept; }BoolTypeProposal { bool flag; uint256 timeToAcceptChange; }
They are designed to store the current value, the proposed new value and the earliest timestamp when the proposal can be accepted.
AdminControlled
AdminControlled is an abstract contract that uses the AddressTypeProposal pattern to manage admin handovers with a time delay.
Events
event AdminProposed(address indexed newAdmin, uint256 timeToAccept);event AdminAccepted(address indexed newAdmin);
Errors
error SenderIsNotAdmin();— thrown when a non-admin attempts to perform admin-only actionserror ProposalNotReady();— thrown when attempting to accept a proposal beforetimeToAccept
Functions
proposeAdmin(address newAdmin, uint256 delay)— proposes a new admin; setsadmin.proposalandadmin.timeToAccept = block.timestamp + delay, emitsAdminProposedacceptAdminProposal()— accepts the pending proposal; requires calleronlyAdminandblock.timestamp >= admin.timeToAccept; setsadmin.current = admin.proposaland clears the proposal fields; emitsAdminAccepted
Modifier
onlyAdmin()— restricts execution to the currently active admin address
Usage Example
contract MyService is AdminControlled {
constructor(address initialAdmin) {
admin.current = initialAdmin;
}
function proposeNewAdmin(address candidate, uint256 delay) external onlyAdmin {
proposeAdmin(candidate, delay);
}
function acceptAdmin() external onlyAdmin {
acceptAdminProposal();
}
}
Note: delay should be chosen according to governance risk model. For development and testing, short delays (minutes) are convenient. For production deployments consider multi-day delays.
Best Practices
- Require
onlyAdminfor functions that change critical configuration - Use events to record proposal and acceptance timestamps for off-chain monitoring
- Keep proposals minimal (one field at a time) to simplify reasoning and audits
Tests
Add unit tests that:
- Verify
proposeAdmin()sets the proposal and emitsAdminProposed - Verify
acceptAdminProposal()reverts beforetimeToAcceptand succeeds after - Verify
onlyAdminprevents unauthorized callers
See Also
- EvvmService — services typically protect admin functions using
AdminControlledor similar patterns - Governance processes — consider adding documentation of expected delay values for testnets vs production