Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 7 additions & 7 deletions script/DeployOrg.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ contract DeployOrg is Script {
string orgId;
string orgName;
bool autoUpgrade;
QuorumConfig quorum;
ThresholdConfig threshold;
RoleConfig[] roles;
VotingClassConfig[] votingClasses;
RoleAssignmentsConfig roleAssignments;
Expand All @@ -43,7 +43,7 @@ contract DeployOrg is Script {
BootstrapConfigJson bootstrap; // Optional: initial projects and tasks
}

struct QuorumConfig {
struct ThresholdConfig {
uint8 hybrid;
uint8 directDemocracy;
}
Expand Down Expand Up @@ -212,9 +212,9 @@ contract DeployOrg is Script {
config.withEducationHub = true; // Default to enabled for backward compatibility
}

// Parse quorum
config.quorum.hybrid = uint8(vm.parseJsonUint(configJson, ".quorum.hybrid"));
config.quorum.directDemocracy = uint8(vm.parseJsonUint(configJson, ".quorum.directDemocracy"));
// Parse threshold
config.threshold.hybrid = uint8(vm.parseJsonUint(configJson, ".threshold.hybrid"));
config.threshold.directDemocracy = uint8(vm.parseJsonUint(configJson, ".threshold.directDemocracy"));

// Parse roles array - use serializeJson to work around struct array limitations
// First, manually count by trying to parse until we fail (reasonable max: 100)
Expand Down Expand Up @@ -478,8 +478,8 @@ contract DeployOrg is Script {
// Note: regDeadline/regNonce/regSignature left as default (0/"") = skip sig-based registration
// The frontend should provide these when deployerUsername is non-empty
params.autoUpgrade = config.autoUpgrade;
params.hybridQuorumPct = config.quorum.hybrid;
params.ddQuorumPct = config.quorum.directDemocracy;
params.hybridThresholdPct = config.threshold.hybrid;
params.ddThresholdPct = config.threshold.directDemocracy;
params.ddInitialTargets = config.ddInitialTargets;

// Build role configs
Expand Down
4 changes: 2 additions & 2 deletions script/MainDeploy.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -290,8 +290,8 @@ contract DeployHomeChain is DeployHelper {
params.deployerUsername = "";
// regDeadline/regNonce/regSignature left as default (0/"") = skip registration
params.autoUpgrade = true;
params.hybridQuorumPct = 50;
params.ddQuorumPct = 50;
params.hybridThresholdPct = 50;
params.ddThresholdPct = 50;

// --- Roles ---
params.roles = new RoleConfigStructs.RoleConfig[](2);
Expand Down
2 changes: 1 addition & 1 deletion script/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ Create a JSON configuration file for your organization. See example configs:
"orgId": "unique-org-identifier",
"orgName": "Organization Display Name",
"autoUpgrade": true,
"quorum": {
"threshold": {
"hybrid": 50,
"directDemocracy": 50
},
Expand Down
4 changes: 2 additions & 2 deletions script/README_RunOrgActions.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ The `org-config-governance-demo.json` defines a 4-role organization:

- **Hybrid Voting**: 60% Direct Democracy / 40% Token-Weighted
- **Quadratic Voting**: Enabled for token class
- **Quorum**: 50% for HybridVoting, 60% for DirectDemocracy
- **Threshold**: 50% for HybridVoting, 60% for DirectDemocracy

## Prerequisites

Expand Down Expand Up @@ -175,7 +175,7 @@ The script uses Foundry's JSON parsing capabilities:

```solidity
vm.parseJsonString(configJson, ".orgId")
vm.parseJsonUint(configJson, ".quorum.hybrid")
vm.parseJsonUint(configJson, ".threshold.hybrid")
vm.parseJson(configJson, ".roleAssignments.quickJoinRoles")
```

Expand Down
16 changes: 8 additions & 8 deletions script/RunOrgActions.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ contract RunOrgActions is Script {
string orgId;
string orgName;
bool autoUpgrade;
QuorumConfig quorum;
ThresholdConfig threshold;
RoleConfig[] roles;
VotingClassConfig[] votingClasses;
RoleAssignmentsConfig roleAssignments;
Expand All @@ -67,7 +67,7 @@ contract RunOrgActions is Script {
BootstrapConfigJson bootstrap; // Optional: initial projects and tasks
}

struct QuorumConfig {
struct ThresholdConfig {
uint8 hybrid;
uint8 directDemocracy;
}
Expand Down Expand Up @@ -600,7 +600,7 @@ contract RunOrgActions is Script {

console.log(" [OK] Winner Announced");
console.log(" Winning Option:", winningOption);
console.log(" Is Valid (quorum met):", isValid);
console.log(" Is Valid (threshold met):", isValid);

console.log("\n[OK] Governance Demonstration Complete - Full Cycle!");
console.log(" Proposal Created: 1");
Expand Down Expand Up @@ -628,9 +628,9 @@ contract RunOrgActions is Script {
config.withEducationHub = true; // Default to enabled for backward compatibility
}

// Parse quorum
config.quorum.hybrid = uint8(vm.parseJsonUint(configJson, ".quorum.hybrid"));
config.quorum.directDemocracy = uint8(vm.parseJsonUint(configJson, ".quorum.directDemocracy"));
// Parse threshold
config.threshold.hybrid = uint8(vm.parseJsonUint(configJson, ".threshold.hybrid"));
config.threshold.directDemocracy = uint8(vm.parseJsonUint(configJson, ".threshold.directDemocracy"));

// Parse roles array
uint256 rolesLength = 0;
Expand Down Expand Up @@ -887,8 +887,8 @@ contract RunOrgActions is Script {
params.deployerAddress = deployerAddress; // Address to receive ADMIN hat
params.deployerUsername = ""; // Registration requires EIP-712 signature (regSignature) from frontend
params.autoUpgrade = config.autoUpgrade;
params.hybridQuorumPct = config.quorum.hybrid;
params.ddQuorumPct = config.quorum.directDemocracy;
params.hybridThresholdPct = config.threshold.hybrid;
params.ddThresholdPct = config.threshold.directDemocracy;
params.ddInitialTargets = config.ddInitialTargets;

// Build role configs
Expand Down
16 changes: 8 additions & 8 deletions script/RunOrgActionsAdvanced.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ contract RunOrgActionsAdvanced is Script {
string orgId;
string orgName;
bool autoUpgrade;
QuorumConfig quorum;
ThresholdConfig threshold;
RoleConfig[] roles;
VotingClassConfig[] votingClasses;
RoleAssignmentsConfig roleAssignments;
Expand All @@ -71,7 +71,7 @@ contract RunOrgActionsAdvanced is Script {
BootstrapConfigJson bootstrap; // Optional: initial projects and tasks
}

struct QuorumConfig {
struct ThresholdConfig {
uint8 hybrid;
uint8 directDemocracy;
}
Expand Down Expand Up @@ -717,7 +717,7 @@ contract RunOrgActionsAdvanced is Script {

console.log(" [OK] Winner Announced");
console.log(" Winning Option:", winningOption);
console.log(" Is Valid (quorum met):", isValid);
console.log(" Is Valid (threshold met):", isValid);

console.log("\n[OK] Governance Demonstration Complete - Full Cycle!");
console.log(" Proposal Created: 1");
Expand Down Expand Up @@ -745,9 +745,9 @@ contract RunOrgActionsAdvanced is Script {
config.withEducationHub = true; // Default to enabled for backward compatibility
}

// Parse quorum
config.quorum.hybrid = uint8(vm.parseJsonUint(configJson, ".quorum.hybrid"));
config.quorum.directDemocracy = uint8(vm.parseJsonUint(configJson, ".quorum.directDemocracy"));
// Parse threshold
config.threshold.hybrid = uint8(vm.parseJsonUint(configJson, ".threshold.hybrid"));
config.threshold.directDemocracy = uint8(vm.parseJsonUint(configJson, ".threshold.directDemocracy"));

// Parse roles array
uint256 rolesLength = 0;
Expand Down Expand Up @@ -1004,8 +1004,8 @@ contract RunOrgActionsAdvanced is Script {
params.deployerAddress = deployerAddress; // Address to receive ADMIN hat
params.deployerUsername = ""; // Registration requires EIP-712 signature (regSignature) from frontend
params.autoUpgrade = config.autoUpgrade;
params.hybridQuorumPct = config.quorum.hybrid;
params.ddQuorumPct = config.quorum.directDemocracy;
params.hybridThresholdPct = config.threshold.hybrid;
params.ddThresholdPct = config.threshold.directDemocracy;
params.ddInitialTargets = config.ddInitialTargets;

// Build role configs
Expand Down
2 changes: 1 addition & 1 deletion script/org-config-advanced-demo.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"orgId": "advanced-demo-cooperative-with-vouching",
"orgName": "Advanced Demo Cooperative",
"autoUpgrade": true,
"quorum": {
"threshold": {
"hybrid": 50,
"directDemocracy": 60
},
Expand Down
2 changes: 1 addition & 1 deletion script/org-config-direct-democracy.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"orgId": "direct-democracy-collective",
"orgName": "Direct Democracy Collective",
"autoUpgrade": true,
"quorum": {
"threshold": {
"hybrid": 60,
"directDemocracy": 60
},
Expand Down
2 changes: 1 addition & 1 deletion script/org-config-example.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"orgId": "worker-cooperative-alpha",
"orgName": "Worker Cooperative Alpha",
"autoUpgrade": true,
"quorum": {
"threshold": {
"hybrid": 50,
"directDemocracy": 50
},
Expand Down
2 changes: 1 addition & 1 deletion script/org-config-governance-demo.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"orgId": "governance-demo-cooperative",
"orgName": "Governance Demo Cooperative",
"autoUpgrade": true,
"quorum": {
"threshold": {
"hybrid": 50,
"directDemocracy": 60
},
Expand Down
68 changes: 31 additions & 37 deletions src/DirectDemocracyVoting.sol
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,11 @@ contract DirectDemocracyVoting is Initializable {
}

enum ConfigKey {
QUORUM,
THRESHOLD,
EXECUTOR,
TARGET_ALLOWED,
HAT_ALLOWED
HAT_ALLOWED,
QUORUM
}

/* ─────────── Data Structures ─────────── */
Expand Down Expand Up @@ -57,10 +58,11 @@ contract DirectDemocracyVoting is Initializable {
mapping(address => bool) allowedTarget; // execution allow‑list
uint256[] votingHatIds; // Array of voting hat IDs
uint256[] creatorHatIds; // Array of creator hat IDs
uint8 quorumPercentage; // 1‑100
uint8 thresholdPct; // 1‑100 (min % of support for winning option)
Proposal[] _proposals;
bool _paused; // Inline pausable state
uint256 _lock; // Inline reentrancy guard state
uint32 quorum; // minimum number of voters required (0 = disabled)
}

bytes32 private constant _STORAGE_SLOT = keccak256("poa.directdemocracy.storage");
Expand Down Expand Up @@ -123,7 +125,8 @@ contract DirectDemocracyVoting is Initializable {
event ExecutorUpdated(address newExecutor);
event TargetAllowed(address target, bool allowed);
event ProposalCleaned(uint256 id, uint256 cleaned);
event QuorumPercentageSet(uint8 pct);
event ThresholdPctSet(uint8 pct);
event QuorumSet(uint32 quorum);

/* ─────────── Initialiser ─────────── */
/// @custom:oz-upgrades-unsafe-allow constructor
Expand All @@ -137,20 +140,20 @@ contract DirectDemocracyVoting is Initializable {
uint256[] calldata initialHats,
uint256[] calldata initialCreatorHats,
address[] calldata initialTargets,
uint8 quorumPct
uint8 thresholdPct_
) external initializer {
if (hats_ == address(0) || executor_ == address(0)) {
revert VotingErrors.ZeroAddress();
}
VotingMath.validateQuorum(quorumPct);
VotingMath.validateThreshold(thresholdPct_);

Layout storage l = _layout();
l.hats = IHats(hats_);
l.executor = IExecutor(executor_);
l.quorumPercentage = quorumPct;
l.thresholdPct = thresholdPct_;
l._paused = false; // Initialize paused state
l._lock = 0; // Initialize reentrancy guard state
emit QuorumPercentageSet(quorumPct);
emit ThresholdPctSet(thresholdPct_);

uint256 len = initialHats.length;
for (uint256 i; i < len;) {
Expand Down Expand Up @@ -192,11 +195,11 @@ contract DirectDemocracyVoting is Initializable {

function setConfig(ConfigKey key, bytes calldata value) external onlyExecutor {
Layout storage l = _layout();
if (key == ConfigKey.QUORUM) {
if (key == ConfigKey.THRESHOLD) {
uint8 q = abi.decode(value, (uint8));
VotingMath.validateQuorum(q);
l.quorumPercentage = q;
emit QuorumPercentageSet(q);
VotingMath.validateThreshold(q);
l.thresholdPct = q;
emit ThresholdPctSet(q);
} else if (key == ConfigKey.EXECUTOR) {
address newExecutor = abi.decode(value, (address));
if (newExecutor == address(0)) revert VotingErrors.ZeroAddress();
Expand All @@ -214,6 +217,10 @@ contract DirectDemocracyVoting is Initializable {
HatManager.setHatInArray(l.creatorHatIds, hat, allowed);
}
emit HatSet(hatType, hat, allowed);
} else if (key == ConfigKey.QUORUM) {
uint32 q = abi.decode(value, (uint32));
l.quorum = q;
emit QuorumSet(q);
}
}

Expand Down Expand Up @@ -434,33 +441,16 @@ contract DirectDemocracyVoting is Initializable {
emit Winner(id, winner, valid);
}

/* ─────────── Cleanup ─────────── */
// function cleanupProposal(uint256 id, address[] calldata voters) external exists(id) isExpired(id) {
// Layout storage l = _layout();
// Proposal storage p = l._proposals[id];
// require(p.batches.length > 0 || voters.length > 0, "nothing");
// uint256 cleaned;
// uint256 len = voters.length;
// for (uint256 i; i < len && i < 4_000;) {
// if (p.hasVoted[voters[i]]) {
// delete p.hasVoted[voters[i]];
// unchecked {
// ++cleaned;
// }
// }
// unchecked {
// ++i;
// }
// }
// if (cleaned == 0 && p.batches.length > 0) delete p.batches;
// emit ProposalCleaned(id, cleaned);
// }

/* ─────────── View helpers ─────────── */
function _calcWinner(uint256 id) internal view returns (uint256 win, bool ok) {
Layout storage l = _layout();
Proposal storage p = l._proposals[id];

// Check quorum: minimum number of voters required
if (l.quorum > 0 && p.totalWeight / 100 < l.quorum) {
return (0, false);
}

// Build option scores array for VoteCalc
uint256 len = p.options.length;
uint256[] memory optionScores = new uint256[](len);
Expand All @@ -475,7 +465,7 @@ contract DirectDemocracyVoting is Initializable {
(win, ok,,) = VotingMath.pickWinnerMajority(
optionScores,
p.totalWeight,
l.quorumPercentage,
l.thresholdPct,
true // requireStrictMajority
);
}
Expand All @@ -485,8 +475,12 @@ contract DirectDemocracyVoting is Initializable {
return _layout()._proposals.length;
}

function quorumPercentage() external view returns (uint8) {
return _layout().quorumPercentage;
function thresholdPct() external view returns (uint8) {
return _layout().thresholdPct;
}

function quorum() external view returns (uint32) {
return _layout().quorum;
}

function isTargetAllowed(address target) external view returns (bool) {
Expand Down
Loading