WARNING: This smart contract has not undergone a formal security audit. Using this code in production carries significant risks. Always conduct professional security audits before deploying any smart contract to a live environment. The code is presented for educational purposes only.
In our previous articles, we explored the architecture, financial mechanisms, environmental impact tracking, and tranche structuring of the GreenBonds contract. This article focuses on the governance system, which enables bondholders to participate in decision-making.
Governance Framework
The governance system in the contract allows for decentralized decision-making regarding bond parameters, fund allocation, and project direction. This increases transparency and gives bondholders a voice in how their capital is managed.
Governance Parameters
The contract defines key governance parameters:
// Governance parameters
uint256 public quorum;
uint256 public votingPeriod;
These parameters determine:
The minimum percentage of bonds required to participate for a vote to be valid (quorum)The duration of the voting period in seconds
These parameters are initialized during contract deployment:
// Initialize governance parameters
quorum = _totalSupply * 30 / 100; // 30% quorum
votingPeriod = 7 days;
Proposal Structure
Proposals are stored in a dedicated struct:
struct Proposal {
address proposer;
string description;
bytes callData;
address target;
uint256 forVotes;
uint256 againstVotes;
uint256 startTime;
uint256 endTime;
bool executed;
mapping(address => bool) hasVoted;
}
mapping(uint256 => Proposal) public proposals;
uint256 public proposalCount;
Each proposal contains:
The proposer’s addressA description of the proposalThe call data to be executed if the proposal passesThe target contract for the callVote tracking (for votes, against votes)Time parameters (start and end times)Execution statusA mapping to track voter participation
Proposal Creation
Proposals can be created by addresses with the ISSUER_ROLE:
function createProposal(string memory description, address target, bytes memory callData)
external
onlyRole(ISSUER_ROLE)
whenNotPaused
returns (uint256)
{
uint256 proposalId = proposalCount++;
Proposal storage proposal = proposals[proposalId];
proposal.proposer = msg.sender;
proposal.description = description;
proposal.target = target;
proposal.callData = callData;
proposal.startTime = block.timestamp;
proposal.endTime = block.timestamp + votingPeriod;
proposal.executed = false;
emit ProposalCreated(proposalId, msg.sender, description);
return proposalId;
}
This function:
Increments the proposal counterInitializes a new proposal with the provided parametersSets the voting period based on the current governance parametersEmits an event to notify stakeholdersReturns the proposal ID for reference
Voting Mechanism
Bondholders can vote on proposals, with voting power proportional to their bond holdings:
function castVote(uint256 proposalId, bool support) external whenNotPaused {
if (proposalId >= proposalCount) revert ProposalDoesNotExist();
Proposal storage proposal = proposals[proposalId];
if (block.timestamp > proposal.endTime) revert VotingPeriodEnded();
if (proposal.executed) revert ProposalAlreadyExecuted();
if (proposal.hasVoted[msg.sender]) revert AlreadyVoted();
uint256 votingPower = balanceOf(msg.sender);
if (votingPower == 0) revert NoVotingPower();
proposal.hasVoted[msg.sender] = true;
if (support) {
proposal.forVotes += votingPower;
} else {
proposal.againstVotes += votingPower;
}
emit VoteCast(msg.sender, proposalId, support, votingPower);
}
This function:
Validates that the proposal exists and is activeChecks that the voter hasn’t already votedDetermines voting power based on bond holdingsRecords the vote based on support or oppositionEmits an event to track voting activity
Proposal Execution
After the voting period ends, successful proposals can be executed:
function executeProposal(uint256 proposalId) external nonReentrant whenNotPaused {
if (proposalId >= proposalCount) revert ProposalDoesNotExist();
Proposal storage proposal = proposals[proposalId];
if (block.timestamp <= proposal.endTime) revert VotingPeriodEnded();
if (proposal.executed) revert ProposalAlreadyExecuted();
// Check if quorum is met and proposal passed
if (proposal.forVotes + proposal.againstVotes < quorum) revert QuorumNotReached();
if (proposal.forVotes <= proposal.againstVotes) revert ProposalRejected();
proposal.executed = true;
// Execute the proposal
(bool success, ) = proposal.target.call(proposal.callData);
if (!success) revert FailedExecution();
emit ProposalExecuted(proposalId);
}
This function enforces several conditions:
The voting period must have endedThe proposal must not have been executed alreadyThe quorum requirement must be metThe “for” votes must exceed the “against” votes
If these conditions are satisfied, the function:
Marks the proposal as executedCalls the target contract with the specified call dataReverts if the execution failsEmits an event to notify stakeholders
Updating Governance Parameters
The governance parameters can be updated by the admin through a time-locked operation:
function updateGovernanceParams(uint256 newQuorum, uint256 newVotingPeriod)
external
onlyRole(DEFAULT_ADMIN_ROLE)
whenNotPaused
{
if (newQuorum == 0) revert InvalidValue();
if (newVotingPeriod == 0) revert InvalidValue();
bytes32 operationId = keccak256(abi.encodePacked(“updateGovernance”, newQuorum, newVotingPeriod));
if (checkAndScheduleOperation(operationId)) {
uint256 oldQuorum = quorum;
uint256 oldVotingPeriod = votingPeriod;
// Update storage
quorum = newQuorum;
votingPeriod = newVotingPeriod;
emit GovernanceParamsUpdated(oldQuorum, newQuorum, oldVotingPeriod, newVotingPeriod);
emit OperationExecuted(operationId);
}
}
This function:
Validates the new parametersCreates a unique operation IDChecks if the operation is already scheduled, and if not, schedules itIf the timelock has expired, updates the parametersEmits events to track the changes
Timelock Mechanism
The contract includes a timelock mechanism with tracking of executed operations:
mapping(bytes32 => uint256) public operationTimestamps;
uint256 public constant TIMELOCK_PERIOD = 2 days;
mapping(bytes32 => bool) public isOperationExecuted;
The checkAndScheduleOperation function includes protection against re-execution:
function checkAndScheduleOperation(bytes32 operationId) internal returns (bool) {
if (isOperationExecuted[operationId]) {
revert OperationAlreadyExecuted();
}
if (operationTimestamps[operationId] == 0) {
scheduleOperation(operationId);
return false;
}
if (block.timestamp < operationTimestamps[operationId]) {
revert TimelockNotExpired();
}
isOperationExecuted[operationId] = true;
return true;
}
This mechanism:
Checks if an operation has already been executed to prevent re-executionSchedules the operation if it hasn’t been scheduled yetValidates that the timelock period has expiredMarks the operation as executed when it runs
Operations that use the timelock include:
Updating coupon periodIssuer emergency withdrawalsEmergency recoveryUpdating governance parameters
Governance Use Cases
The governance system enables several key use cases:
Parameter Adjustments
Governance proposals can adjust bond parameters such as:
Coupon ratesEarly redemption penaltiesVerification requirements for impact reports
Fund Allocation
Proposals can direct project funds to specific initiatives:
Allocating funds to new green projectsAdjusting the distribution of existing fundsApproving exceptional expenditures
Emergency Actions
In case of market disruption or project issues, proposals can authorize emergency measures:
Pausing coupon paymentsAccelerating or delaying project timelinesImplementing contingency plans
Upgrading the Contract
While the upgrade functionality is restricted to addresses with the UPGRADER_ROLE, governance could be used to authorize who receives this role, creating a more decentralized upgrade process.
Bondholders’ Rights and Responsibilities
The governance system grants bondholders both rights and responsibilities:
Rights
Voting on proposals with weight proportional to bond holdingsProposing changes (if granted the ISSUER_ROLE)Accessing transparent information about votes and execution
Responsibilities
Participating in votes to ensure quorum is reachedReviewing proposals to make informed decisionsMonitoring the execution of approved proposals
Conclusion
The governance system in the GreenBonds contract adds an important layer of decentralization and transparency to the green bond framework. By enabling bondholders to participate in decision-making, it aligns the interests of issuers and investors while providing mechanisms for adapting to changing circumstances.
In the final article of this series, we’ll examine the contract’s security features, fund management, and emergency mechanisms.
Part 5: Governance and On-Chain Decision Making was originally published in Coinmonks on Medium, where people are continuing the conversation by highlighting and responding to this story.