Governance Libraries
The governance libraries provide 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 Types: Abstract contract (Admin) and Struct library (ProposalStructs) License: EVVM-NONCOMMERCIAL-1.0 Import Paths:
@evvm/testnet-contracts/library/utils/governance/Admin.sol@evvm/testnet-contracts/library/utils/governance/ProposalStructs.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
ProposalStructs
ProposalStructs exposes simple structs used by governance-enabled contracts:
struct AddressTypeProposal {
address current;
address proposal;
uint256 timeToAccept;
}
struct UintTypeProposal {
uint256 current;
uint256 proposal;
uint256 timeToAccept;
}
struct 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.
Admin Contract
Admin 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
function proposeAdmin(address newAdmin, uint256 delay) external onlyAdmin
Proposes a new admin; sets admin.proposal and admin.timeToAccept = block.timestamp + delay, emits AdminProposed
acceptAdmin
function acceptAdmin() external
Accepts the pending proposal; requires caller to be the proposed admin and block.timestamp >= admin.timeToAccept; sets admin.current = admin.proposal and clears the proposal fields; emits AdminAccepted
Modifier
onlyAdmin()— restricts execution to the currently active admin address
Usage Example
import {Admin} from "@evvm/testnet-contracts/library/utils/governance/Admin.sol";
contract MyService is Admin {
constructor(address initialAdmin) Admin(initialAdmin) {}
function proposeNewAdmin(address candidate, uint256 delay) external onlyAdmin {
proposeAdmin(candidate, delay);
}
function acceptNewAdmin() external {
acceptAdmin();
}
// Other admin-controlled functions
function updateServiceParameter(uint256 value) external onlyAdmin {
// Only current admin can call this
}
}
Usage in EVVM Contracts
Many EVVM contracts use the Admin base contract for governance:
- TreasuryHostChainStation: Uses
Adminfor admin management - TreasuryExternalChainStation: Uses
Adminfor admin management - NameService: Uses
Adminpatterns - Staking: Uses
Adminfor governance - P2PSwap: Uses
Adminpatterns
Typical Pattern:
contract EVVMContract is Admin {
ProposalStructs.AddressTypeProposal public someAddress;
function proposeSomeAddress(address newAddress, uint256 delay) external onlyAdmin {
someAddress.proposal = newAddress;
someAddress.timeToAccept = block.timestamp + delay;
}
function acceptSomeAddress() external onlyAdmin {
require(block.timestamp >= someAddress.timeToAccept, "Too early");
someAddress.current = someAddress.proposal;
someAddress.proposal = address(0);
someAddress.timeToAccept = 0;
}
}
Best Practices
- Delay Duration: Use 1 day (86400 seconds) minimum for production contracts
- Proposal Validation: Check
newAdmin != address(0)andnewAdmin != admin.current - Event Monitoring: Listen for
AdminProposedandAdminAcceptedevents - Multi-sig Recommended: Use multi-sig wallets for admin addresses
Recommendation: Inherit from Admin for any contract requiring admin governance with time-delayed changes.
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