Skip to content

Commit 81f4bb2

Browse files
authored
Merge pull request #2 from smartnodes-lab/dao-payments
- Reduced base interval to 8 hours for testnet-v2 - Bumped solidity version to 0.8.24
2 parents 08eaab7 + 7022e2a commit 81f4bb2

13 files changed

+2628
-1427
lines changed

foundry.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
src = "src"
33
out = "out"
44
libs = ["lib"]
5-
solc_version = "0.8.22"
5+
solc_version = "0.8.24"
66
remappings = [
77
"@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/",
88
"forge-std/=lib/forge-std/src/",

script/Deploy.s.sol

Lines changed: 31 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,12 @@ import {SmartnodesCore} from "../src/SmartnodesCore.sol";
66
import {SmartnodesERC20} from "../src/SmartnodesERC20.sol";
77
import {SmartnodesCoordinator} from "../src/SmartnodesCoordinator.sol";
88
import {SmartnodesDAO} from "../src/SmartnodesDAO.sol";
9+
import {TimelockController} from "@openzeppelin/contracts/governance/TimelockController.sol";
910

10-
uint256 constant DAO_VOTING_PERIOD = 7 days;
11-
uint256 constant DEPLOYMENT_MULTIPLIER = 1;
12-
uint256 constant INTERVAL_SECONDS = 1 hours;
11+
// DAO Configuration
12+
uint256 constant TIMELOCK_DELAY = 2 days;
13+
uint128 constant BASE_UPDATE_TIME = uint128(8 hours);
14+
uint8 constant PROPOSAL_THRESHOLD_PERCENTAGE = 66;
1315

1416
contract Deploy is Script {
1517
address[] genesis;
@@ -18,41 +20,53 @@ contract Deploy is Script {
1820
function run() external {
1921
genesis.push(msg.sender);
2022
genesis.push(0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266);
23+
address[] memory proposers = new address[](0);
24+
address[] memory executors = new address[](0);
2125

2226
vm.startBroadcast();
2327

24-
SmartnodesERC20 token = new SmartnodesERC20(
25-
DEPLOYMENT_MULTIPLIER,
26-
genesis
27-
);
28-
SmartnodesDAO dao = new SmartnodesDAO(
29-
address(token),
30-
DAO_VOTING_PERIOD,
31-
1000
28+
SmartnodesERC20 token = new SmartnodesERC20(genesis);
29+
TimelockController timelock = new TimelockController(
30+
TIMELOCK_DELAY,
31+
proposers,
32+
executors,
33+
msg.sender // Temporary admin to set up roles
3234
);
35+
SmartnodesDAO dao = new SmartnodesDAO(token, timelock);
3336
SmartnodesCore core = new SmartnodesCore(address(token));
3437
SmartnodesCoordinator coordinator = new SmartnodesCoordinator(
35-
uint128(INTERVAL_SECONDS * DEPLOYMENT_MULTIPLIER),
36-
66,
38+
BASE_UPDATE_TIME,
39+
PROPOSAL_THRESHOLD_PERCENTAGE,
3740
address(core),
3841
address(token),
3942
initialActiveNodes
4043
);
4144

4245
token.setSmartnodes(address(core), address(coordinator));
43-
44-
token.setDAO(address(dao));
46+
token.setDAO(address(timelock));
4547
core.setCoordinator(address(coordinator));
4648

47-
bytes32 publicKeyHash = vm.envBytes32("PUBLIC_KEY_HASH");
49+
// Configure timelock roles
50+
bytes32 PROPOSER_ROLE = timelock.PROPOSER_ROLE();
51+
bytes32 EXECUTOR_ROLE = timelock.EXECUTOR_ROLE();
52+
bytes32 CANCELLER_ROLE = timelock.CANCELLER_ROLE();
53+
bytes32 DEFAULT_ADMIN_ROLE = timelock.DEFAULT_ADMIN_ROLE();
4854

55+
// Grant DAO the proposer and canceller roles
56+
timelock.grantRole(PROPOSER_ROLE, address(dao));
57+
timelock.grantRole(CANCELLER_ROLE, address(dao));
58+
timelock.grantRole(EXECUTOR_ROLE, address(0));
59+
timelock.renounceRole(DEFAULT_ADMIN_ROLE, msg.sender);
60+
61+
bytes32 publicKeyHash = vm.envBytes32("PUBLIC_KEY_HASH");
4962
core.createValidator(publicKeyHash);
5063
coordinator.addValidator();
5164

5265
console.log("Token:", address(token));
66+
console.log("Timelock:", address(timelock));
67+
console.log("DAO:", address(dao));
5368
console.log("Core:", address(core));
5469
console.log("Coordinator:", address(coordinator));
55-
console.log("DAO:", address(dao));
5670

5771
vm.stopBroadcast();
5872
}

src/SmartnodesCoordinator.sol

Lines changed: 13 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// SPDX-License-Identifier: MIT
2-
pragma solidity ^0.8.22;
2+
pragma solidity ^0.8.24;
33

44
import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
55
import {ISmartnodesCore} from "./interfaces/ISmartnodesCore.sol";
@@ -59,10 +59,7 @@ contract SmartnodesCoordinator is ReentrancyGuard {
5959
mapping(address => uint256) public validatorVote;
6060
mapping(address => uint8) public hasSubmittedProposal;
6161
mapping(address => uint256) public validatorLastActiveRound; // Track validator activity
62-
63-
// Enhanced round management
64-
uint256 public currentRoundNumber;
65-
uint256 private roundSeed; // Used for deterministic but unpredictable validator selection
62+
uint256 private roundSeed;
6663

6764
// ============= Events ==============
6865
event ProposalCreated(
@@ -150,7 +147,6 @@ contract SmartnodesCoordinator is ReentrancyGuard {
150147
}
151148

152149
// Initialize round management
153-
currentRoundNumber = 1;
154150
roundSeed = uint256(
155151
keccak256(
156152
abi.encode(block.timestamp, block.prevrandao, _genesisNodes)
@@ -206,7 +202,7 @@ contract SmartnodesCoordinator is ReentrancyGuard {
206202

207203
// mark as active
208204
hasSubmittedProposal[sender] = proposalNum;
209-
validatorLastActiveRound[sender] = currentRoundNumber;
205+
validatorLastActiveRound[sender] = nextProposalId;
210206

211207
emit ProposalCreated(proposalNum, proposalHash, sender);
212208
}
@@ -228,7 +224,7 @@ contract SmartnodesCoordinator is ReentrancyGuard {
228224

229225
Proposal storage proposal = currentProposals[proposalId - 1];
230226
validatorVote[msg.sender] = proposalId;
231-
validatorLastActiveRound[msg.sender] = currentRoundNumber; // Mark as active
227+
validatorLastActiveRound[msg.sender] = nextProposalId; // Mark as active
232228
proposal.votes++;
233229
}
234230

@@ -281,7 +277,7 @@ contract SmartnodesCoordinator is ReentrancyGuard {
281277
}
282278

283279
// Mark executor as active
284-
validatorLastActiveRound[msg.sender] = currentRoundNumber;
280+
validatorLastActiveRound[msg.sender] = nextProposalId;
285281

286282
// Optimized batch validator removal
287283
if (validatorsToRemove.length > 0) {
@@ -341,7 +337,7 @@ contract SmartnodesCoordinator is ReentrancyGuard {
341337

342338
validators.push(validator);
343339
isValidator[validator] = true;
344-
validatorLastActiveRound[validator] = currentRoundNumber; // Mark as active from start
340+
validatorLastActiveRound[validator] = nextProposalId; // Mark as active from start
345341
emit ValidatorAdded(validator);
346342
}
347343

@@ -438,7 +434,6 @@ contract SmartnodesCoordinator is ReentrancyGuard {
438434

439435
function _updateRound() internal {
440436
_resetValidatorStates();
441-
currentRoundNumber++;
442437
_selectNewRoundValidators();
443438
delete currentProposals; // Clear proposals for new round
444439
timeConfig.lastExecutionTime = uint128(block.timestamp);
@@ -492,7 +487,7 @@ contract SmartnodesCoordinator is ReentrancyGuard {
492487
roundSeed,
493488
block.timestamp,
494489
block.prevrandao,
495-
currentRoundNumber,
490+
nextProposalId,
496491
blockhash(block.number - 1)
497492
)
498493
)
@@ -511,8 +506,8 @@ contract SmartnodesCoordinator is ReentrancyGuard {
511506

512507
// Prioritize active validators (those who participated in recent rounds)
513508
uint256 selectedCount = 0;
514-
uint256 inactivityThreshold = currentRoundNumber > 3
515-
? currentRoundNumber - 3
509+
uint256 inactivityThreshold = nextProposalId > 3
510+
? nextProposalId - 3
516511
: 0;
517512

518513
// First, try to select active validators
@@ -552,7 +547,7 @@ contract SmartnodesCoordinator is ReentrancyGuard {
552547
}
553548
}
554549

555-
emit NewRoundStarted(currentRoundNumber, currentRoundValidators);
550+
emit NewRoundStarted(nextProposalId, currentRoundValidators);
556551
}
557552

558553
function _computeProposalHash(
@@ -628,7 +623,8 @@ contract SmartnodesCoordinator is ReentrancyGuard {
628623

629624
function _isCurrentRoundExpired() internal view returns (bool) {
630625
TimeConfig memory tc = timeConfig;
631-
return block.timestamp > tc.lastExecutionTime + (tc.updateTime << 1);
626+
return
627+
block.timestamp > tc.lastExecutionTime + ((tc.updateTime * 5) / 4);
632628
}
633629

634630
// ============= View Functions =============
@@ -726,7 +722,7 @@ contract SmartnodesCoordinator is ReentrancyGuard {
726722
)
727723
{
728724
return (
729-
currentRoundNumber,
725+
nextProposalId,
730726
currentRoundValidators.length,
731727
_calculateRoundValidatorCount(),
732728
_calculateRequiredVotes()

src/SmartnodesCore.sol

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// SPDX-License-Identifier: MIT
2-
pragma solidity ^0.8.22;
2+
pragma solidity ^0.8.24;
33

44
import {ISmartnodesCoordinator} from "./interfaces/ISmartnodesCoordinator.sol";
55
import {ISmartnodesERC20, PaymentAmounts} from "./interfaces/ISmartnodesERC20.sol";
@@ -19,6 +19,7 @@ contract SmartnodesCore {
1919
error Core__NotValidatorMultisig();
2020
error Core__NotToken();
2121
error Core__NodeExists();
22+
error Core__NodeDoesNotExist();
2223

2324
// ============= Events ==============
2425
enum JobState {
@@ -127,6 +128,29 @@ contract SmartnodesCore {
127128
i_tokenContract.lockTokens(userAddress, false);
128129
}
129130

131+
function unlockValidator() external {
132+
address nodeAddress = msg.sender;
133+
Node storage validator = validators[nodeAddress];
134+
135+
if (!validator.exists || !validator.locked)
136+
revert Core__NodeDoesNotExist();
137+
138+
validator.locked = false;
139+
140+
i_tokenContract.unlockTokens(nodeAddress, true);
141+
}
142+
143+
function unlockUser() external {
144+
address nodeAddress = msg.sender;
145+
Node storage user = users[nodeAddress];
146+
147+
if (!user.exists || !user.locked) revert Core__NodeDoesNotExist();
148+
149+
user.locked = false;
150+
151+
i_tokenContract.unlockTokens(nodeAddress, true);
152+
}
153+
130154
/**
131155
* @notice Requests a new job to be created with a form of payment
132156
* @param _userId Unique identifier associated with P2P node for the requesting user

0 commit comments

Comments
 (0)