Skip to content

Commit

Permalink
Added Governance enhancements.
Browse files Browse the repository at this point in the history
  • Loading branch information
Admin committed Nov 29, 2024
1 parent 0212231 commit 4876c09
Show file tree
Hide file tree
Showing 9 changed files with 882 additions and 15 deletions.
8 changes: 4 additions & 4 deletions TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,10 +88,10 @@
- [x] Develop badge-based governance weight

### Governance
- [ ] Add delegation capabilities
- [ ] Implement gasless voting
- [ ] Create specialized committees
- [ ] Add proposal templates
- [x] Add delegation capabilities
- [x] Implement gasless voting
- [x] Create specialized committees
- [x] Add proposal templates

### Integration & Expansion
- [ ] Add API endpoints
Expand Down
130 changes: 130 additions & 0 deletions src/CommitteeGovernance.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;

import "@openzeppelin/contracts/access/AccessControl.sol";
import "./Governance.sol";

/**
* @title CommitteeGovernance
* @dev Manages specialized committees with their own voting domains
*/
contract CommitteeGovernance is AccessControl {
bytes32 public constant COMMITTEE_ADMIN_ROLE = keccak256("COMMITTEE_ADMIN_ROLE");

struct Committee {
string name;
string description;
uint256 votingPowerMultiplier; // Multiplier for committee members' voting power (in basis points)
bool active;
mapping(address => bool) members;
mapping(bytes4 => bool) allowedFunctions; // Function selectors this committee can propose
}

// Mapping of committee ID to Committee
mapping(uint256 => Committee) public committees;
uint256 public committeeCount;

// Governance contract reference
Governance public governance;

event CommitteeCreated(uint256 indexed committeeId, string name);
event MemberAdded(uint256 indexed committeeId, address indexed member);
event MemberRemoved(uint256 indexed committeeId, address indexed member);
event FunctionAllowed(uint256 indexed committeeId, bytes4 indexed functionSelector);
event FunctionDisallowed(uint256 indexed committeeId, bytes4 indexed functionSelector);

constructor(address _governanceAddress) {
governance = Governance(_governanceAddress);
_setupRole(DEFAULT_ADMIN_ROLE, msg.sender);
_setupRole(COMMITTEE_ADMIN_ROLE, msg.sender);
}

/**
* @dev Create a new committee
* @param name Name of the committee
* @param description Description of the committee's purpose
* @param votingPowerMultiplier Voting power multiplier for committee members
*/
function createCommittee(string memory name, string memory description, uint256 votingPowerMultiplier)
external
onlyRole(COMMITTEE_ADMIN_ROLE)
{
require(votingPowerMultiplier <= 10000, "Multiplier cannot exceed 100x");

uint256 committeeId = committeeCount++;
Committee storage committee = committees[committeeId];
committee.name = name;
committee.description = description;
committee.votingPowerMultiplier = votingPowerMultiplier;
committee.active = true;

emit CommitteeCreated(committeeId, name);
}

/**
* @dev Add a member to a committee
* @param committeeId ID of the committee
* @param member Address of the new member
*/
function addMember(uint256 committeeId, address member) external onlyRole(COMMITTEE_ADMIN_ROLE) {
require(committeeId < committeeCount, "Committee does not exist");
require(!committees[committeeId].members[member], "Already a member");

committees[committeeId].members[member] = true;
emit MemberAdded(committeeId, member);
}

/**
* @dev Remove a member from a committee
* @param committeeId ID of the committee
* @param member Address of the member to remove
*/
function removeMember(uint256 committeeId, address member) external onlyRole(COMMITTEE_ADMIN_ROLE) {
require(committeeId < committeeCount, "Committee does not exist");
require(committees[committeeId].members[member], "Not a member");

committees[committeeId].members[member] = false;
emit MemberRemoved(committeeId, member);
}

/**
* @dev Allow a function to be proposed by a committee
* @param committeeId ID of the committee
* @param functionSelector Function selector to allow
*/
function allowFunction(uint256 committeeId, bytes4 functionSelector) external onlyRole(COMMITTEE_ADMIN_ROLE) {
require(committeeId < committeeCount, "Committee does not exist");
committees[committeeId].allowedFunctions[functionSelector] = true;
emit FunctionAllowed(committeeId, functionSelector);
}

/**
* @dev Check if an address is a member of a committee
* @param committeeId ID of the committee
* @param member Address to check
*/
function isMember(uint256 committeeId, address member) public view returns (bool) {
return committees[committeeId].members[member];
}

/**
* @dev Get the voting power multiplier for a member in a specific committee
* @param committeeId ID of the committee
* @param member Address of the member
*/
function getVotingPowerMultiplier(uint256 committeeId, address member) public view returns (uint256) {
if (!isMember(committeeId, member)) {
return 0;
}
return committees[committeeId].votingPowerMultiplier;
}

/**
* @dev Check if a function can be proposed by a committee
* @param committeeId ID of the committee
* @param functionSelector Function selector to check
*/
function isFunctionAllowed(uint256 committeeId, bytes4 functionSelector) public view returns (bool) {
return committees[committeeId].allowedFunctions[functionSelector];
}
}
78 changes: 78 additions & 0 deletions src/GaslessVoting.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;

import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
import "@openzeppelin/contracts/utils/cryptography/EIP712.sol";

/**
* @title GaslessVoting
* @dev Implements meta-transactions for gasless voting in governance
*/
contract GaslessVoting is EIP712 {
using ECDSA for bytes32;

// Typed struct for EIP-712
struct VotePermit {
address voter;
uint256 proposalId;
bool support;
uint256 nonce;
uint256 deadline;
}

// Mapping of voter nonces for replay protection
mapping(address => uint256) public nonces;

// EIP-712 type hash
bytes32 public constant VOTE_PERMIT_TYPEHASH =
keccak256("VotePermit(address voter,uint256 proposalId,bool support,uint256 nonce,uint256 deadline)");

constructor() EIP712("Backr Governance", "1") {}

/**
* @dev Returns the current nonce for an address
* @param voter Address to get nonce for
*/
function getNonce(address voter) public view returns (uint256) {
return nonces[voter];
}

/**
* @dev Returns the domain separator used in the encoding of the signature for permits, as defined by EIP712
*/
function DOMAIN_SEPARATOR() external view returns (bytes32) {
return _domainSeparatorV4();
}

/**
* @dev Verifies vote permit signature and returns the signer
* @param permit Vote permit struct
* @param signature Signature bytes
*/
function verifyVotePermit(VotePermit memory permit, bytes memory signature) public view returns (address) {
bytes32 structHash = keccak256(
abi.encode(
VOTE_PERMIT_TYPEHASH, permit.voter, permit.proposalId, permit.support, permit.nonce, permit.deadline
)
);

bytes32 hash = _hashTypedDataV4(structHash);
address signer = hash.recover(signature);

require(signer == permit.voter, "GaslessVoting: invalid signature");
require(block.timestamp <= permit.deadline, "GaslessVoting: expired deadline");
require(nonces[permit.voter] == permit.nonce, "GaslessVoting: invalid nonce");

return signer;
}

/**
* @dev Processes a vote permit, incrementing nonce if valid
* @param permit Vote permit struct
* @param signature Signature bytes
*/
function _processVotePermit(VotePermit memory permit, bytes memory signature) internal {
verifyVotePermit(permit, signature);
nonces[permit.voter]++;
}
}
41 changes: 31 additions & 10 deletions src/Governance.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,13 @@ pragma solidity ^0.8.13;
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import "./GaslessVoting.sol";

/**
* @title Governance
* @dev Contract for managing platform governance through a DAO structure
*/
contract Governance is Ownable, ReentrancyGuard {
contract Governance is Ownable, ReentrancyGuard, GaslessVoting {
struct Proposal {
uint256 id;
address proposer;
Expand Down Expand Up @@ -172,32 +173,52 @@ contract Governance is Ownable, ReentrancyGuard {
}

/**
* @dev Cast a vote on a proposal
* @dev Cast a vote through meta-transaction
* @param permit Vote permit struct
* @param signature Signature bytes
*/
function castVoteWithPermit(VotePermit memory permit, bytes memory signature) external {
_processVotePermit(permit, signature);
_castVote(permit.voter, permit.proposalId, permit.support);
}

/**
* @dev Internal function to cast a vote
* @param voter Address of the voter
* @param proposalId ID of the proposal
* @param support True for support, false for against
* @param support True for support, false against
*/
function castVote(uint256 proposalId, bool support) external nonReentrant {
function _castVote(address voter, uint256 proposalId, bool support) internal {
Proposal storage proposal = proposals[proposalId];
require(block.timestamp <= proposal.endTime, "Voting period ended");
require(!proposal.hasVoted[msg.sender], "Already voted");
require(!proposal.hasVoted[voter], "Already voted");

// Take snapshot of voting power if not already taken
if (proposal.votingPowerSnapshot[msg.sender] == 0) {
proposal.votingPowerSnapshot[msg.sender] = getVotingPower(msg.sender);
if (proposal.votingPowerSnapshot[voter] == 0) {
proposal.votingPowerSnapshot[voter] = getVotingPower(voter);
}

uint256 votes = proposal.votingPowerSnapshot[msg.sender];
uint256 votes = proposal.votingPowerSnapshot[voter];
require(votes > 0, "No voting power");

proposal.hasVoted[msg.sender] = true;
proposal.hasVoted[voter] = true;

if (support) {
proposal.forVotes += votes;
} else {
proposal.againstVotes += votes;
}

emit VoteCast(msg.sender, proposalId, support, votes);
emit VoteCast(voter, proposalId, support, votes);
}

/**
* @dev Cast a vote on a proposal
* @param proposalId ID of the proposal
* @param support True for support, false for against
*/
function castVote(uint256 proposalId, bool support) external nonReentrant {
_castVote(msg.sender, proposalId, support);
}

/**
Expand Down
1 change: 0 additions & 1 deletion src/PlatformToken.sol
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,6 @@ contract PlatformToken is ERC20, Ownable {
require(block.timestamp >= stakeTimestamp[msg.sender] + MIN_STAKE_DURATION, "Minimum stake duration not met");

uint256 reward = calculateReward(msg.sender);
uint256 total = stakedAmount + reward;

stakedBalance[msg.sender] = 0;
stakeTimestamp[msg.sender] = 0;
Expand Down
Loading

0 comments on commit 4876c09

Please sign in to comment.