ichaingov-contract-generator 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +98 -0
- package/bin/cli.js +182 -0
- package/core/encodeParameter.js +10 -0
- package/core/loadConfig.js +21 -0
- package/core/validateConfig.js +83 -0
- package/index/base-contracts/GatewayRegistry.sol +346 -0
- package/index/base-contracts/Governance.sol +152 -0
- package/index/base-contracts/GovernanceWithTimelock.sol +198 -0
- package/index/base-contracts/PolicyRegistry.sol +112 -0
- package/index/base-contracts/Timelock.sol +19 -0
- package/index/base-contracts/Token.sol +49 -0
- package/index/generateDeploy.js +59 -0
- package/index/generator.js +37 -0
- package/index/mappers.js +19 -0
- package/index/templates/deploy.js.hbs +136 -0
- package/index/templates/template-config.json +63 -0
- package/package.json +38 -0
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
pragma solidity ^0.8.20;
|
|
3
|
+
|
|
4
|
+
import "@openzeppelin/contracts/access/Ownable.sol";
|
|
5
|
+
|
|
6
|
+
contract GatewayRegistry is Ownable {
|
|
7
|
+
uint256 public constant MAX_REPUTATION = 10_000;
|
|
8
|
+
enum OrgStatus {
|
|
9
|
+
Active,
|
|
10
|
+
Suspended,
|
|
11
|
+
Probation,
|
|
12
|
+
Inactive
|
|
13
|
+
}
|
|
14
|
+
enum GatewayStatus {
|
|
15
|
+
Active,
|
|
16
|
+
Suspended,
|
|
17
|
+
Revoked
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
struct Organization {
|
|
21
|
+
string name;
|
|
22
|
+
address wallet;
|
|
23
|
+
OrgStatus status;
|
|
24
|
+
uint256 registeredAt;
|
|
25
|
+
uint256 updatedAt;
|
|
26
|
+
bool exists;
|
|
27
|
+
uint256 reputation;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
struct Gateway {
|
|
31
|
+
bytes publicKey;
|
|
32
|
+
address orgWallet;
|
|
33
|
+
string name;
|
|
34
|
+
GatewayStatus status;
|
|
35
|
+
uint256 registeredAt;
|
|
36
|
+
uint256 updatedAt;
|
|
37
|
+
bool exists;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
mapping(address => Organization) public organizations;
|
|
41
|
+
mapping(bytes => Gateway) public gateways;
|
|
42
|
+
mapping(address => bytes[]) public orgGateways;
|
|
43
|
+
|
|
44
|
+
address[] public orgList;
|
|
45
|
+
bytes[] public gatewayList;
|
|
46
|
+
|
|
47
|
+
mapping(address => mapping(uint256 => uint256))
|
|
48
|
+
private _reputationSnapshots;
|
|
49
|
+
mapping(address => uint256[]) private _snapshotBlocks;
|
|
50
|
+
|
|
51
|
+
event OrganizationRegistered(
|
|
52
|
+
address indexed wallet,
|
|
53
|
+
string name,
|
|
54
|
+
uint256 timestamp
|
|
55
|
+
);
|
|
56
|
+
event OrganizationStatusChanged(
|
|
57
|
+
address indexed wallet,
|
|
58
|
+
OrgStatus oldStatus,
|
|
59
|
+
OrgStatus newStatus,
|
|
60
|
+
string reason
|
|
61
|
+
);
|
|
62
|
+
event OrganizationRemoved(address indexed wallet, string reason);
|
|
63
|
+
event GatewayRegistered(
|
|
64
|
+
bytes publicKey,
|
|
65
|
+
address indexed orgWallet,
|
|
66
|
+
uint256 timestamp
|
|
67
|
+
);
|
|
68
|
+
event GatewayStatusChanged(
|
|
69
|
+
bytes publicKey,
|
|
70
|
+
GatewayStatus oldStatus,
|
|
71
|
+
GatewayStatus newStatus,
|
|
72
|
+
string reason
|
|
73
|
+
);
|
|
74
|
+
event GatewayRemoved(bytes publicKey, string reason);
|
|
75
|
+
|
|
76
|
+
error OrgAlreadyExists(address wallet);
|
|
77
|
+
error OrgNotFound(address wallet);
|
|
78
|
+
error GatewayAlreadyExists(bytes publicKey);
|
|
79
|
+
error GatewayNotFound(bytes publicKey);
|
|
80
|
+
error InvalidAddress();
|
|
81
|
+
error InvalidPublicKey();
|
|
82
|
+
error ReputationOutOfRange(uint256 value, uint256 max);
|
|
83
|
+
|
|
84
|
+
constructor(address initialOwner) Ownable(initialOwner) {}
|
|
85
|
+
|
|
86
|
+
function registerOrganization(
|
|
87
|
+
address wallet,
|
|
88
|
+
string calldata name,
|
|
89
|
+
uint256 initialReputation
|
|
90
|
+
) external onlyOwner {
|
|
91
|
+
if (wallet == address(0)) revert InvalidAddress();
|
|
92
|
+
if (organizations[wallet].exists) revert OrgAlreadyExists(wallet);
|
|
93
|
+
if (initialReputation > MAX_REPUTATION)
|
|
94
|
+
revert ReputationOutOfRange(initialReputation, MAX_REPUTATION);
|
|
95
|
+
|
|
96
|
+
organizations[wallet] = Organization({
|
|
97
|
+
name: name,
|
|
98
|
+
wallet: wallet,
|
|
99
|
+
status: OrgStatus.Active,
|
|
100
|
+
registeredAt: block.number,
|
|
101
|
+
updatedAt: block.number,
|
|
102
|
+
exists: true,
|
|
103
|
+
reputation: initialReputation
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
_takeSnapshot(wallet);
|
|
107
|
+
orgList.push(wallet);
|
|
108
|
+
emit OrganizationRegistered(wallet, name, block.number);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function setOrganizationStatus(
|
|
112
|
+
address wallet,
|
|
113
|
+
OrgStatus newStatus,
|
|
114
|
+
string calldata reason
|
|
115
|
+
) external onlyOwner {
|
|
116
|
+
if (!organizations[wallet].exists) revert OrgNotFound(wallet);
|
|
117
|
+
OrgStatus old = organizations[wallet].status;
|
|
118
|
+
organizations[wallet].status = newStatus;
|
|
119
|
+
organizations[wallet].updatedAt = block.number;
|
|
120
|
+
emit OrganizationStatusChanged(wallet, old, newStatus, reason);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function updateOrganizationName(
|
|
124
|
+
address wallet,
|
|
125
|
+
string calldata newName
|
|
126
|
+
) external onlyOwner {
|
|
127
|
+
if (!organizations[wallet].exists) revert OrgNotFound(wallet);
|
|
128
|
+
organizations[wallet].name = newName;
|
|
129
|
+
organizations[wallet].updatedAt = block.number;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function removeOrganization(
|
|
133
|
+
address wallet,
|
|
134
|
+
string calldata reason
|
|
135
|
+
) external onlyOwner {
|
|
136
|
+
if (!organizations[wallet].exists) revert OrgNotFound(wallet);
|
|
137
|
+
for (uint256 i = 0; i < orgList.length; i++) {
|
|
138
|
+
if (orgList[i] == wallet) {
|
|
139
|
+
orgList[i] = orgList[orgList.length - 1];
|
|
140
|
+
orgList.pop();
|
|
141
|
+
break;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
delete organizations[wallet];
|
|
145
|
+
emit OrganizationRemoved(wallet, reason);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function registerGateway(
|
|
149
|
+
bytes calldata publicKey,
|
|
150
|
+
address orgWallet,
|
|
151
|
+
string calldata name
|
|
152
|
+
) external onlyOwner {
|
|
153
|
+
if (publicKey.length == 0) revert InvalidPublicKey();
|
|
154
|
+
if (!organizations[orgWallet].exists) revert OrgNotFound(orgWallet);
|
|
155
|
+
if (gateways[publicKey].exists) revert GatewayAlreadyExists(publicKey);
|
|
156
|
+
|
|
157
|
+
gateways[publicKey] = Gateway({
|
|
158
|
+
publicKey: publicKey,
|
|
159
|
+
orgWallet: orgWallet,
|
|
160
|
+
name: name,
|
|
161
|
+
status: GatewayStatus.Active,
|
|
162
|
+
registeredAt: block.number,
|
|
163
|
+
updatedAt: block.number,
|
|
164
|
+
exists: true
|
|
165
|
+
});
|
|
166
|
+
orgGateways[orgWallet].push(publicKey);
|
|
167
|
+
gatewayList.push(publicKey);
|
|
168
|
+
emit GatewayRegistered(publicKey, orgWallet, block.number);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function setGatewayStatus(
|
|
172
|
+
bytes calldata publicKey,
|
|
173
|
+
GatewayStatus newStatus,
|
|
174
|
+
string calldata reason
|
|
175
|
+
) external onlyOwner {
|
|
176
|
+
if (!gateways[publicKey].exists) revert GatewayNotFound(publicKey);
|
|
177
|
+
GatewayStatus old = gateways[publicKey].status;
|
|
178
|
+
gateways[publicKey].status = newStatus;
|
|
179
|
+
gateways[publicKey].updatedAt = block.number;
|
|
180
|
+
emit GatewayStatusChanged(publicKey, old, newStatus, reason);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function removeGateway(
|
|
184
|
+
bytes calldata publicKey,
|
|
185
|
+
string calldata reason
|
|
186
|
+
) external onlyOwner {
|
|
187
|
+
if (!gateways[publicKey].exists) revert GatewayNotFound(publicKey);
|
|
188
|
+
address orgWallet = gateways[publicKey].orgWallet;
|
|
189
|
+
bytes[] storage og = orgGateways[orgWallet];
|
|
190
|
+
for (uint256 i = 0; i < og.length; i++) {
|
|
191
|
+
if (keccak256(og[i]) == keccak256(publicKey)) {
|
|
192
|
+
og[i] = og[og.length - 1];
|
|
193
|
+
og.pop();
|
|
194
|
+
break;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
for (uint256 i = 0; i < gatewayList.length; i++) {
|
|
198
|
+
if (keccak256(gatewayList[i]) == keccak256(publicKey)) {
|
|
199
|
+
gatewayList[i] = gatewayList[gatewayList.length - 1];
|
|
200
|
+
gatewayList.pop();
|
|
201
|
+
break;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
delete gateways[publicKey];
|
|
205
|
+
emit GatewayRemoved(publicKey, reason);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function setReputation(address wallet, uint256 newRep) external onlyOwner {
|
|
209
|
+
if (!organizations[wallet].exists) revert OrgNotFound(wallet);
|
|
210
|
+
if (newRep > MAX_REPUTATION)
|
|
211
|
+
revert ReputationOutOfRange(newRep, MAX_REPUTATION);
|
|
212
|
+
organizations[wallet].reputation = newRep;
|
|
213
|
+
organizations[wallet].updatedAt = block.number;
|
|
214
|
+
_takeSnapshot(wallet);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function increaseReputation(
|
|
218
|
+
address wallet,
|
|
219
|
+
uint256 amount
|
|
220
|
+
) external onlyOwner {
|
|
221
|
+
if (!organizations[wallet].exists) revert OrgNotFound(wallet);
|
|
222
|
+
uint256 old = organizations[wallet].reputation;
|
|
223
|
+
uint256 next = old + amount > MAX_REPUTATION
|
|
224
|
+
? MAX_REPUTATION
|
|
225
|
+
: old + amount;
|
|
226
|
+
organizations[wallet].reputation = next;
|
|
227
|
+
organizations[wallet].updatedAt = block.number;
|
|
228
|
+
_takeSnapshot(wallet);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function decreaseReputation(
|
|
232
|
+
address wallet,
|
|
233
|
+
uint256 amount
|
|
234
|
+
) external onlyOwner {
|
|
235
|
+
if (!organizations[wallet].exists) revert OrgNotFound(wallet);
|
|
236
|
+
uint256 old = organizations[wallet].reputation;
|
|
237
|
+
uint256 next = old < amount ? 0 : old - amount;
|
|
238
|
+
organizations[wallet].reputation = next;
|
|
239
|
+
organizations[wallet].updatedAt = block.number;
|
|
240
|
+
_takeSnapshot(wallet);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function _takeSnapshot(address wallet) internal {
|
|
244
|
+
uint256[] storage blocks = _snapshotBlocks[wallet];
|
|
245
|
+
if (blocks.length == 0 || blocks[blocks.length - 1] < block.number) {
|
|
246
|
+
blocks.push(block.number);
|
|
247
|
+
_reputationSnapshots[wallet][block.number] = organizations[wallet]
|
|
248
|
+
.reputation;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function getReputationAt(
|
|
253
|
+
address wallet,
|
|
254
|
+
uint256 blockNumber
|
|
255
|
+
) external view returns (uint256) {
|
|
256
|
+
uint256[] storage blocks = _snapshotBlocks[wallet];
|
|
257
|
+
|
|
258
|
+
if (blocks.length == 0) return organizations[wallet].reputation;
|
|
259
|
+
if (blockNumber < blocks[0]) return 0;
|
|
260
|
+
|
|
261
|
+
uint256 low = 0;
|
|
262
|
+
uint256 high = blocks.length - 1;
|
|
263
|
+
while (low < high) {
|
|
264
|
+
uint256 mid = (low + high + 1) / 2;
|
|
265
|
+
if (blocks[mid] <= blockNumber) low = mid;
|
|
266
|
+
else high = mid - 1;
|
|
267
|
+
}
|
|
268
|
+
return _reputationSnapshots[wallet][blocks[low]];
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
function getOrganization(
|
|
272
|
+
address wallet
|
|
273
|
+
) external view returns (Organization memory) {
|
|
274
|
+
if (!organizations[wallet].exists) revert OrgNotFound(wallet);
|
|
275
|
+
return organizations[wallet];
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function getGateway(
|
|
279
|
+
bytes calldata publicKey
|
|
280
|
+
) external view returns (Gateway memory) {
|
|
281
|
+
if (!gateways[publicKey].exists) revert GatewayNotFound(publicKey);
|
|
282
|
+
return gateways[publicKey];
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
function getOrgGateways(
|
|
286
|
+
address orgWallet
|
|
287
|
+
) external view returns (bytes[] memory) {
|
|
288
|
+
return orgGateways[orgWallet];
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
function getOrgCount() external view returns (uint256) {
|
|
292
|
+
return orgList.length;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
function getGatewayCount() external view returns (uint256) {
|
|
296
|
+
return gatewayList.length;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
function isOrgActive(address wallet) external view returns (bool) {
|
|
300
|
+
return
|
|
301
|
+
organizations[wallet].exists &&
|
|
302
|
+
organizations[wallet].status == OrgStatus.Active;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
function isGatewayActive(
|
|
306
|
+
bytes calldata publicKey
|
|
307
|
+
) external view returns (bool) {
|
|
308
|
+
return
|
|
309
|
+
gateways[publicKey].exists &&
|
|
310
|
+
gateways[publicKey].status == GatewayStatus.Active;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
function getOrgsByStatus(
|
|
314
|
+
OrgStatus status
|
|
315
|
+
) external view returns (address[] memory) {
|
|
316
|
+
uint256 count = 0;
|
|
317
|
+
for (uint256 i = 0; i < orgList.length; i++)
|
|
318
|
+
if (organizations[orgList[i]].status == status) count++;
|
|
319
|
+
address[] memory result = new address[](count);
|
|
320
|
+
uint256 idx = 0;
|
|
321
|
+
for (uint256 i = 0; i < orgList.length; i++)
|
|
322
|
+
if (organizations[orgList[i]].status == status)
|
|
323
|
+
result[idx++] = orgList[i];
|
|
324
|
+
return result;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
function getAllOrganizations()
|
|
328
|
+
external
|
|
329
|
+
view
|
|
330
|
+
returns (Organization[] memory)
|
|
331
|
+
{
|
|
332
|
+
Organization[] memory all = new Organization[](orgList.length);
|
|
333
|
+
for (uint256 i = 0; i < orgList.length; i++) {
|
|
334
|
+
all[i] = organizations[orgList[i]];
|
|
335
|
+
}
|
|
336
|
+
return all;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
function getAllGateways() external view returns (Gateway[] memory) {
|
|
340
|
+
Gateway[] memory all = new Gateway[](gatewayList.length);
|
|
341
|
+
for (uint256 i = 0; i < gatewayList.length; i++) {
|
|
342
|
+
all[i] = gateways[gatewayList[i]];
|
|
343
|
+
}
|
|
344
|
+
return all;
|
|
345
|
+
}
|
|
346
|
+
}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
pragma solidity ^0.8.27;
|
|
3
|
+
|
|
4
|
+
import {Governor} from "@openzeppelin/contracts/governance/Governor.sol";
|
|
5
|
+
import {GovernorCountingSimple} from "@openzeppelin/contracts/governance/extensions/GovernorCountingSimple.sol";
|
|
6
|
+
import {GovernorSettings} from "@openzeppelin/contracts/governance/extensions/GovernorSettings.sol";
|
|
7
|
+
import {GovernorVotes} from "@openzeppelin/contracts/governance/extensions/GovernorVotes.sol";
|
|
8
|
+
import {GovernorVotesQuorumFraction} from "@openzeppelin/contracts/governance/extensions/GovernorVotesQuorumFraction.sol";
|
|
9
|
+
import {IVotes} from "@openzeppelin/contracts/governance/utils/IVotes.sol";
|
|
10
|
+
import {Math} from "@openzeppelin/contracts/utils/math/Math.sol";
|
|
11
|
+
import {GatewayRegistry} from "./GatewayRegistry.sol";
|
|
12
|
+
|
|
13
|
+
contract Governance is
|
|
14
|
+
Governor,
|
|
15
|
+
GovernorSettings,
|
|
16
|
+
GovernorCountingSimple,
|
|
17
|
+
GovernorVotes,
|
|
18
|
+
GovernorVotesQuorumFraction
|
|
19
|
+
{
|
|
20
|
+
enum VotingSystem {
|
|
21
|
+
TokenBased, // 0: ERC20Votes weight (default)
|
|
22
|
+
Quadratic, // 1: sqrt of ERC20Votes weight
|
|
23
|
+
WeightedReputation // 2: tokenVotes boosted by GatewayRegistry reputation
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/// only needed for WeightedReputation, address(0) otherwise
|
|
27
|
+
GatewayRegistry public gatewayRegistry;
|
|
28
|
+
VotingSystem public defaultVotingSystem;
|
|
29
|
+
|
|
30
|
+
mapping(uint256 => VotingSystem) public proposalVotingSystem;
|
|
31
|
+
|
|
32
|
+
/// @notice Transient context: set at start of _castVote, read by _getVotes.
|
|
33
|
+
/// Safe because _castVote → super._castVote → _getVotes all run in one call stack.
|
|
34
|
+
/// Reset after each vote to keep storage clean between transactions.
|
|
35
|
+
VotingSystem private _activeVotingSystem;
|
|
36
|
+
|
|
37
|
+
error RegistryNotSet();
|
|
38
|
+
|
|
39
|
+
constructor(
|
|
40
|
+
string memory governorName,
|
|
41
|
+
IVotes _token,
|
|
42
|
+
uint32 _votingDelay,
|
|
43
|
+
uint32 _votingPeriod,
|
|
44
|
+
uint256 _proposalThreshold,
|
|
45
|
+
uint256 _quorumFraction,
|
|
46
|
+
VotingSystem _defaultVotingSystem,
|
|
47
|
+
GatewayRegistry _gatewayRegistry
|
|
48
|
+
)
|
|
49
|
+
Governor(governorName)
|
|
50
|
+
GovernorSettings(_votingDelay, _votingPeriod, _proposalThreshold)
|
|
51
|
+
GovernorVotes(_token)
|
|
52
|
+
GovernorVotesQuorumFraction(_quorumFraction)
|
|
53
|
+
{
|
|
54
|
+
if (
|
|
55
|
+
_defaultVotingSystem == VotingSystem.WeightedReputation &&
|
|
56
|
+
address(_gatewayRegistry) == address(0)
|
|
57
|
+
) revert RegistryNotSet();
|
|
58
|
+
|
|
59
|
+
defaultVotingSystem = _defaultVotingSystem;
|
|
60
|
+
gatewayRegistry = _gatewayRegistry;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function setDefaultVotingSystem(VotingSystem newSystem) external onlyGovernance {
|
|
64
|
+
if (
|
|
65
|
+
newSystem == VotingSystem.WeightedReputation &&
|
|
66
|
+
address(gatewayRegistry) == address(0)
|
|
67
|
+
) revert RegistryNotSet();
|
|
68
|
+
|
|
69
|
+
defaultVotingSystem = newSystem;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function setGatewayRegistry(GatewayRegistry newRegistry) external onlyGovernance {
|
|
73
|
+
gatewayRegistry = newRegistry;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
function propose(
|
|
78
|
+
address[] memory targets,
|
|
79
|
+
uint256[] memory values,
|
|
80
|
+
bytes[] memory calldatas,
|
|
81
|
+
string memory description
|
|
82
|
+
) public override returns (uint256) {
|
|
83
|
+
uint256 proposalId = super.propose(targets, values, calldatas, description);
|
|
84
|
+
_assignVotingSystem(proposalId, defaultVotingSystem);
|
|
85
|
+
return proposalId;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function _assignVotingSystem(uint256 proposalId, VotingSystem vs) internal {
|
|
89
|
+
proposalVotingSystem[proposalId] = vs;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function _castVote(
|
|
93
|
+
uint256 proposalId,
|
|
94
|
+
address account,
|
|
95
|
+
uint8 support,
|
|
96
|
+
string memory reason,
|
|
97
|
+
bytes memory params
|
|
98
|
+
) internal override returns (uint256) {
|
|
99
|
+
_activeVotingSystem = proposalVotingSystem[proposalId];
|
|
100
|
+
uint256 weight = super._castVote(proposalId, account, support, reason, params);
|
|
101
|
+
delete _activeVotingSystem;
|
|
102
|
+
return weight;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
function _getVotes(
|
|
107
|
+
address account,
|
|
108
|
+
uint256 timepoint,
|
|
109
|
+
bytes memory params
|
|
110
|
+
)
|
|
111
|
+
internal view
|
|
112
|
+
override(Governor, GovernorVotes)
|
|
113
|
+
returns (uint256)
|
|
114
|
+
{
|
|
115
|
+
uint256 tokenVotes = super._getVotes(account, timepoint, params);
|
|
116
|
+
VotingSystem vs = _activeVotingSystem;
|
|
117
|
+
|
|
118
|
+
if (vs == VotingSystem.TokenBased) {
|
|
119
|
+
return tokenVotes;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (vs == VotingSystem.Quadratic) {
|
|
123
|
+
return Math.sqrt(tokenVotes);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// WeightedReputation: using 10_000 base avoids floating point
|
|
127
|
+
if (vs == VotingSystem.WeightedReputation) {
|
|
128
|
+
if (address(gatewayRegistry) == address(0)) return tokenVotes;
|
|
129
|
+
uint256 reputation = gatewayRegistry.getReputationAt(account, timepoint);
|
|
130
|
+
return tokenVotes * (10_000 + reputation) / 10_000;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return tokenVotes;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function votingDelay()
|
|
137
|
+
public view override(Governor, GovernorSettings) returns (uint256)
|
|
138
|
+
{ return super.votingDelay(); }
|
|
139
|
+
|
|
140
|
+
function votingPeriod()
|
|
141
|
+
public view override(Governor, GovernorSettings) returns (uint256)
|
|
142
|
+
{ return super.votingPeriod(); }
|
|
143
|
+
|
|
144
|
+
function proposalThreshold()
|
|
145
|
+
public view override(Governor, GovernorSettings) returns (uint256)
|
|
146
|
+
{ return super.proposalThreshold(); }
|
|
147
|
+
|
|
148
|
+
function quorum(uint256 blockNumber)
|
|
149
|
+
public view override(Governor, GovernorVotesQuorumFraction) returns (uint256)
|
|
150
|
+
{ return super.quorum(blockNumber); }
|
|
151
|
+
|
|
152
|
+
}
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
pragma solidity ^0.8.27;
|
|
3
|
+
|
|
4
|
+
import {Governor} from "@openzeppelin/contracts/governance/Governor.sol";
|
|
5
|
+
import {TimelockController} from "@openzeppelin/contracts/governance/TimelockController.sol";
|
|
6
|
+
import {GovernorCountingSimple} from "@openzeppelin/contracts/governance/extensions/GovernorCountingSimple.sol";
|
|
7
|
+
import {GovernorSettings} from "@openzeppelin/contracts/governance/extensions/GovernorSettings.sol";
|
|
8
|
+
import {GovernorTimelockControl} from "@openzeppelin/contracts/governance/extensions/GovernorTimelockControl.sol";
|
|
9
|
+
import {GovernorVotes} from "@openzeppelin/contracts/governance/extensions/GovernorVotes.sol";
|
|
10
|
+
import {GovernorVotesQuorumFraction} from "@openzeppelin/contracts/governance/extensions/GovernorVotesQuorumFraction.sol";
|
|
11
|
+
import {IVotes} from "@openzeppelin/contracts/governance/utils/IVotes.sol";
|
|
12
|
+
import {Math} from "@openzeppelin/contracts/utils/math/Math.sol";
|
|
13
|
+
import {GatewayRegistry} from "./GatewayRegistry.sol";
|
|
14
|
+
|
|
15
|
+
contract GovernanceWithTimelock is
|
|
16
|
+
Governor,
|
|
17
|
+
GovernorSettings,
|
|
18
|
+
GovernorCountingSimple,
|
|
19
|
+
GovernorVotes,
|
|
20
|
+
GovernorVotesQuorumFraction,
|
|
21
|
+
GovernorTimelockControl
|
|
22
|
+
{
|
|
23
|
+
|
|
24
|
+
enum VotingSystem {
|
|
25
|
+
TokenBased, // 0: standard ERC20Votes weight (default)
|
|
26
|
+
Quadratic, // 1: sqrt(tokenVotes) — reduces whale dominance
|
|
27
|
+
WeightedReputation // 2: tokenVotes boosted by GatewayRegistry reputation
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
/// @notice Optional — only needed for WeightedReputation, address(0) otherwise
|
|
32
|
+
GatewayRegistry public gatewayRegistry;
|
|
33
|
+
|
|
34
|
+
/// @notice Voting system applied to proposals created via propose()
|
|
35
|
+
VotingSystem public defaultVotingSystem;
|
|
36
|
+
|
|
37
|
+
/// @notice voting System of a given proposal
|
|
38
|
+
mapping(uint256 => VotingSystem) public proposalVotingSystem;
|
|
39
|
+
|
|
40
|
+
/// @notice Transient context: set at start of _castVote, read by _getVotes.
|
|
41
|
+
/// Safe because _castVote → super._castVote → _getVotes all run in one call stack.
|
|
42
|
+
/// Reset after each vote to keep storage clean between transactions.
|
|
43
|
+
VotingSystem private _activeVotingSystem;
|
|
44
|
+
|
|
45
|
+
error RegistryNotSet();
|
|
46
|
+
|
|
47
|
+
constructor(
|
|
48
|
+
string memory governorName,
|
|
49
|
+
IVotes _token,
|
|
50
|
+
TimelockController _timelock,
|
|
51
|
+
uint32 _votingDelay,
|
|
52
|
+
uint32 _votingPeriod,
|
|
53
|
+
uint256 _proposalThreshold,
|
|
54
|
+
uint256 _quorumFraction,
|
|
55
|
+
VotingSystem _defaultVotingSystem,
|
|
56
|
+
GatewayRegistry _gatewayRegistry // pass address(0) for TokenBased / Quadratic
|
|
57
|
+
)
|
|
58
|
+
Governor(governorName)
|
|
59
|
+
GovernorSettings(_votingDelay, _votingPeriod, _proposalThreshold)
|
|
60
|
+
GovernorVotes(_token)
|
|
61
|
+
GovernorVotesQuorumFraction(_quorumFraction)
|
|
62
|
+
GovernorTimelockControl(_timelock)
|
|
63
|
+
{
|
|
64
|
+
if (
|
|
65
|
+
_defaultVotingSystem == VotingSystem.WeightedReputation &&
|
|
66
|
+
address(_gatewayRegistry) == address(0)
|
|
67
|
+
) revert RegistryNotSet();
|
|
68
|
+
|
|
69
|
+
defaultVotingSystem = _defaultVotingSystem;
|
|
70
|
+
gatewayRegistry = _gatewayRegistry;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function setDefaultVotingSystem(VotingSystem newSystem) external onlyGovernance {
|
|
74
|
+
if (
|
|
75
|
+
newSystem == VotingSystem.WeightedReputation &&
|
|
76
|
+
address(gatewayRegistry) == address(0)
|
|
77
|
+
) revert RegistryNotSet();
|
|
78
|
+
|
|
79
|
+
defaultVotingSystem = newSystem;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function setGatewayRegistry(GatewayRegistry newRegistry) external onlyGovernance {
|
|
83
|
+
gatewayRegistry = newRegistry;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function propose(
|
|
87
|
+
address[] memory targets,
|
|
88
|
+
uint256[] memory values,
|
|
89
|
+
bytes[] memory calldatas,
|
|
90
|
+
string memory description
|
|
91
|
+
) public override returns (uint256) {
|
|
92
|
+
uint256 proposalId = super.propose(targets, values, calldatas, description);
|
|
93
|
+
_assignVotingSystem(proposalId, defaultVotingSystem);
|
|
94
|
+
return proposalId;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function _assignVotingSystem(uint256 proposalId, VotingSystem vs) internal {
|
|
98
|
+
proposalVotingSystem[proposalId] = vs;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function _castVote(
|
|
102
|
+
uint256 proposalId,
|
|
103
|
+
address account,
|
|
104
|
+
uint8 support,
|
|
105
|
+
string memory reason,
|
|
106
|
+
bytes memory params
|
|
107
|
+
) internal override returns (uint256) {
|
|
108
|
+
_activeVotingSystem = proposalVotingSystem[proposalId];
|
|
109
|
+
|
|
110
|
+
uint256 weight = super._castVote(proposalId, account, support, reason, params);
|
|
111
|
+
delete _activeVotingSystem;
|
|
112
|
+
|
|
113
|
+
return weight;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/// @notice Core weight calculation — routes to the correct formula
|
|
117
|
+
/// based on _activeVotingSystem set by _castVote above.
|
|
118
|
+
function _getVotes(
|
|
119
|
+
address account,
|
|
120
|
+
uint256 timepoint,
|
|
121
|
+
bytes memory params
|
|
122
|
+
)
|
|
123
|
+
internal view
|
|
124
|
+
override(Governor, GovernorVotes)
|
|
125
|
+
returns (uint256)
|
|
126
|
+
{
|
|
127
|
+
uint256 tokenVotes = super._getVotes(account, timepoint, params);
|
|
128
|
+
|
|
129
|
+
VotingSystem vs = _activeVotingSystem;
|
|
130
|
+
|
|
131
|
+
if (vs == VotingSystem.TokenBased) {
|
|
132
|
+
return tokenVotes;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (vs == VotingSystem.Quadratic) {
|
|
136
|
+
return Math.sqrt(tokenVotes);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Using 10_000 avoids floating point arithmetic
|
|
140
|
+
if (vs == VotingSystem.WeightedReputation) {
|
|
141
|
+
if (address(gatewayRegistry) == address(0)) return tokenVotes;
|
|
142
|
+
uint256 reputation = gatewayRegistry.getReputationAt(account, timepoint);
|
|
143
|
+
return tokenVotes * (10_000 + reputation) / 10_000;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return tokenVotes;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function state(uint256 proposalId)
|
|
150
|
+
public view override(Governor, GovernorTimelockControl) returns (ProposalState)
|
|
151
|
+
{ return super.state(proposalId); }
|
|
152
|
+
|
|
153
|
+
function proposalNeedsQueuing(uint256 proposalId)
|
|
154
|
+
public view override(Governor, GovernorTimelockControl) returns (bool)
|
|
155
|
+
{ return super.proposalNeedsQueuing(proposalId); }
|
|
156
|
+
|
|
157
|
+
function _queueOperations(
|
|
158
|
+
uint256 proposalId, address[] memory targets,
|
|
159
|
+
uint256[] memory values, bytes[] memory calldatas, bytes32 descriptionHash
|
|
160
|
+
)
|
|
161
|
+
internal override(Governor, GovernorTimelockControl) returns (uint48)
|
|
162
|
+
{ return super._queueOperations(proposalId, targets, values, calldatas, descriptionHash); }
|
|
163
|
+
|
|
164
|
+
function _executeOperations(
|
|
165
|
+
uint256 proposalId, address[] memory targets,
|
|
166
|
+
uint256[] memory values, bytes[] memory calldatas, bytes32 descriptionHash
|
|
167
|
+
)
|
|
168
|
+
internal override(Governor, GovernorTimelockControl)
|
|
169
|
+
{ super._executeOperations(proposalId, targets, values, calldatas, descriptionHash); }
|
|
170
|
+
|
|
171
|
+
function _cancel(
|
|
172
|
+
address[] memory targets, uint256[] memory values,
|
|
173
|
+
bytes[] memory calldatas, bytes32 descriptionHash
|
|
174
|
+
)
|
|
175
|
+
internal override(Governor, GovernorTimelockControl) returns (uint256)
|
|
176
|
+
{ return super._cancel(targets, values, calldatas, descriptionHash); }
|
|
177
|
+
|
|
178
|
+
function _executor()
|
|
179
|
+
internal view override(Governor, GovernorTimelockControl) returns (address)
|
|
180
|
+
{ return super._executor(); }
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
function votingDelay()
|
|
184
|
+
public view override(Governor, GovernorSettings) returns (uint256)
|
|
185
|
+
{ return super.votingDelay(); }
|
|
186
|
+
|
|
187
|
+
function votingPeriod()
|
|
188
|
+
public view override(Governor, GovernorSettings) returns (uint256)
|
|
189
|
+
{ return super.votingPeriod(); }
|
|
190
|
+
|
|
191
|
+
function proposalThreshold()
|
|
192
|
+
public view override(Governor, GovernorSettings) returns (uint256)
|
|
193
|
+
{ return super.proposalThreshold(); }
|
|
194
|
+
|
|
195
|
+
function quorum(uint256 blockNumber)
|
|
196
|
+
public view override(Governor, GovernorVotesQuorumFraction) returns (uint256)
|
|
197
|
+
{ return super.quorum(blockNumber); }
|
|
198
|
+
}
|