Skip to content

Commit f7826dd

Browse files
committed
DAO Implemented, Test Suite Fixed
1 parent 54b296e commit f7826dd

File tree

12 files changed

+944
-502
lines changed

12 files changed

+944
-502
lines changed

src/SmartnodesCoordinator.sol

Lines changed: 119 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import {ISmartnodesCore} from "./interfaces/ISmartnodesCore.sol";
88
* @title SmartnodesCoordinator
99
* @notice Manages job and user participation updates to SmartnodesCore contract. Updates are
1010
* @notice controlled by a rotating set of validators that vote on these state updates periodically.
11-
* @dev Optimized multi-signature coordinator for managing SmartnodesCore contract
1211
*/
1312
contract SmartnodesCoordinator is ReentrancyGuard {
1413
// ============= Errors ==============
@@ -27,6 +26,7 @@ contract SmartnodesCoordinator is ReentrancyGuard {
2726
error Coordinator__NotEnoughActiveValidators();
2827
error Coordinator__InvalidApprovalPercentage();
2928
error Coordinator__InvalidAddress();
29+
error Coordinator__ProposalExpired();
3030

3131
// ============= Structs ==============
3232
struct Proposal {
@@ -57,6 +57,11 @@ contract SmartnodesCoordinator is ReentrancyGuard {
5757

5858
uint256 public requiredValidators;
5959

60+
// Proposal cleanup configuration
61+
uint256 public constant MAX_PROPOSAL_AGE = 7 days; // Proposals older than 7 days can be cleaned up
62+
uint256 public constant CLEANUP_BATCH_SIZE = 50; // Max proposals to clean in one call
63+
uint256 public oldestProposalId = 1; // Track oldest proposal for efficient cleanup
64+
6065
// Validator management
6166
address[] public validators;
6267
address[] public currentRoundValidators;
@@ -78,6 +83,7 @@ contract SmartnodesCoordinator is ReentrancyGuard {
7883
bytes32 indexed proposalHash
7984
);
8085
event ProposalExpired(uint256 indexed proposalId);
86+
event ProposalsCleanedUp(uint256 fromId, uint256 toId, uint256 count);
8187
event ValidatorAdded(address indexed validator);
8288
event ValidatorRemoved(address indexed validator);
8389
event RoundStarted(uint256 indexed roundId, address[] selectedValidators);
@@ -113,6 +119,16 @@ contract SmartnodesCoordinator is ReentrancyGuard {
113119
_;
114120
}
115121

122+
modifier validProposal(uint256 proposalId) {
123+
Proposal storage proposal = proposals[proposalId];
124+
if (proposal.creator == address(0))
125+
revert Coordinator__InvalidProposalNumber();
126+
if (proposal.executed) revert Coordinator__InvalidProposalNumber();
127+
if (_isProposalExpired(proposalId))
128+
revert Coordinator__ProposalExpired();
129+
_;
130+
}
131+
116132
constructor(
117133
uint128 _updateTime,
118134
uint8 _requiredApprovalsPercentage,
@@ -177,13 +193,7 @@ contract SmartnodesCoordinator is ReentrancyGuard {
177193
*/
178194
function voteForProposal(
179195
uint256 proposalId
180-
) external onlyValidator nonReentrant {
181-
Proposal storage proposal = proposals[proposalId];
182-
183-
if (proposal.creator == address(0) || proposal.executed) {
184-
revert Coordinator__InvalidProposalNumber();
185-
}
186-
196+
) external onlyValidator validProposal(proposalId) nonReentrant {
187197
if (!_isCurrentRoundExpired() && validatorVote[msg.sender] != 0) {
188198
revert Coordinator__AlreadyVoted();
189199
}
@@ -196,6 +206,7 @@ contract SmartnodesCoordinator is ReentrancyGuard {
196206
validatorVote[msg.sender] = proposalId;
197207

198208
// Increment votes and check threshold in one go
209+
Proposal storage proposal = proposals[proposalId];
199210
uint16 newVotes = ++proposal.votes;
200211
uint256 requiredVotes = _calculateRequiredVotes();
201212

@@ -218,13 +229,12 @@ contract SmartnodesCoordinator is ReentrancyGuard {
218229
bytes32[] calldata jobHashes,
219230
address[] calldata jobWorkers,
220231
uint256[] calldata jobCapacities
221-
) external onlyValidator nonReentrant {
232+
) external onlyValidator validProposal(proposalId) nonReentrant {
222233
Proposal storage proposal = proposals[proposalId];
223234

224235
// Batch validation
225236
if (proposal.creator != msg.sender)
226237
revert Coordinator__MustBeProposalCreator();
227-
if (proposal.executed) revert Coordinator__InvalidProposalNumber();
228238
if (proposal.votes < _calculateRequiredVotes())
229239
revert Coordinator__NotEnoughVotes();
230240

@@ -264,6 +274,33 @@ contract SmartnodesCoordinator is ReentrancyGuard {
264274
_updateRound();
265275
}
266276

277+
// ============= Cleanup Functions =============
278+
/**
279+
* @notice Clean up old proposals to save storage costs
280+
* @dev Can be called by anyone to incentivize cleanup
281+
* @return cleanedCount Number of proposals cleaned up
282+
*/
283+
function cleanupOldProposals() external returns (uint256 cleanedCount) {
284+
return _cleanupProposals(CLEANUP_BATCH_SIZE);
285+
}
286+
287+
/**
288+
* @notice Force expire a specific proposal that's older than MAX_PROPOSAL_AGE
289+
* @param proposalId The proposal to expire
290+
*/
291+
function expireProposal(uint256 proposalId) external {
292+
if (!_isProposalExpired(proposalId))
293+
revert Coordinator__ProposalTooEarly();
294+
295+
Proposal storage proposal = proposals[proposalId];
296+
if (proposal.creator == address(0))
297+
revert Coordinator__InvalidProposalNumber();
298+
if (proposal.executed) return; // Already handled
299+
300+
delete proposals[proposalId];
301+
emit ProposalExpired(proposalId);
302+
}
303+
267304
// ============= Validator Management =============
268305
/**
269306
* @notice Add validator with stake verification
@@ -292,6 +329,59 @@ contract SmartnodesCoordinator is ReentrancyGuard {
292329
}
293330

294331
// ============= INTERNAL FUNCTIONS =============
332+
function _cleanupProposals(
333+
uint256 batchSize
334+
) internal returns (uint256 cleanedCount) {
335+
uint256 currentProposalId = roundData.nextProposalId;
336+
uint256 startId = oldestProposalId;
337+
uint256 endId = startId + batchSize;
338+
339+
if (endId > currentProposalId) {
340+
endId = currentProposalId;
341+
}
342+
343+
if (startId >= endId) return 0;
344+
345+
uint256 cutoffTime = block.timestamp - MAX_PROPOSAL_AGE;
346+
347+
unchecked {
348+
for (uint256 i = startId; i < endId; ++i) {
349+
Proposal storage proposal = proposals[i];
350+
351+
// Skip if proposal doesn't exist
352+
if (proposal.creator == address(0)) {
353+
continue;
354+
} else if (proposal.createdAt > cutoffTime) {
355+
break;
356+
}
357+
358+
// Clean up executed or expired proposals
359+
if (proposal.executed || _isProposalExpired(i)) {
360+
delete proposals[i];
361+
++cleanedCount;
362+
}
363+
}
364+
}
365+
366+
// Update oldest proposal pointer
367+
oldestProposalId = endId;
368+
369+
if (cleanedCount > 0) {
370+
emit ProposalsCleanedUp(startId, endId - 1, cleanedCount);
371+
}
372+
373+
return cleanedCount;
374+
}
375+
376+
function _isProposalExpired(
377+
uint256 proposalId
378+
) internal view returns (bool) {
379+
Proposal storage proposal = proposals[proposalId];
380+
if (proposal.creator == address(0)) return false;
381+
382+
return block.timestamp > proposal.createdAt + MAX_PROPOSAL_AGE;
383+
}
384+
295385
function _addValidator(address validator) internal {
296386
if (!i_smartnodesCore.isLockedValidator(validator)) {
297387
revert Coordinator__NotValidator();
@@ -355,18 +445,11 @@ contract SmartnodesCoordinator is ReentrancyGuard {
355445
}
356446

357447
function _cleanupExpiredRound() internal {
358-
address[] memory vals = validators;
359-
uint256 validatorCount = vals.length;
448+
_resetValidatorStates();
360449

361-
unchecked {
362-
for (uint256 i = 0; i < validatorCount; ++i) {
363-
address validator = vals[i];
364-
validatorVote[validator] = 0;
365-
validatorToProposal[validator] = 0;
366-
}
367-
}
450+
// Enhanced cleanup: also clean up old proposals during round expiry
451+
_cleanupProposals(CLEANUP_BATCH_SIZE / 2); // Use smaller batch during round transitions
368452

369-
delete currentRoundValidators;
370453
emit ProposalExpired(roundData.currentRoundId);
371454
}
372455

@@ -389,10 +472,10 @@ contract SmartnodesCoordinator is ReentrancyGuard {
389472

390473
unchecked {
391474
for (uint256 i = 0; i < validatorCount; ++i) {
392-
validatorVote[vals[i]] = 0;
475+
delete validatorVote[vals[i]];
393476
}
394477
for (uint256 i = 0; i < selectedCount; ++i) {
395-
validatorToProposal[currentVals[i]] = 0;
478+
delete validatorToProposal[currentVals[i]];
396479
}
397480
}
398481
}
@@ -515,6 +598,12 @@ contract SmartnodesCoordinator is ReentrancyGuard {
515598
return _isCurrentRoundExpired();
516599
}
517600

601+
function isProposalExpired(
602+
uint256 proposalId
603+
) external view returns (bool) {
604+
return _isProposalExpired(proposalId);
605+
}
606+
518607
function getRequiredApprovals() external view returns (uint256) {
519608
return _calculateRequiredVotes();
520609
}
@@ -547,6 +636,14 @@ contract SmartnodesCoordinator is ReentrancyGuard {
547636
return proposals[proposalId];
548637
}
549638

639+
function getCleanupInfo()
640+
external
641+
view
642+
returns (uint256 oldestId, uint256 nextId, uint256 maxAge)
643+
{
644+
return (oldestProposalId, roundData.nextProposalId, MAX_PROPOSAL_AGE);
645+
}
646+
550647
function getState()
551648
external
552649
view

0 commit comments

Comments
 (0)