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
Binary file removed .DS_Store
Binary file not shown.
2 changes: 1 addition & 1 deletion foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
src = "src"
out = "out"
libs = ["lib"]
solc_version = "0.8.22"
solc_version = "0.8.24"
remappings = [
"@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/",
"forge-std/=lib/forge-std/src/",
Expand Down
48 changes: 31 additions & 17 deletions script/Deploy.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@ import {SmartnodesCore} from "../src/SmartnodesCore.sol";
import {SmartnodesERC20} from "../src/SmartnodesERC20.sol";
import {SmartnodesCoordinator} from "../src/SmartnodesCoordinator.sol";
import {SmartnodesDAO} from "../src/SmartnodesDAO.sol";
import {TimelockController} from "@openzeppelin/contracts/governance/TimelockController.sol";

uint256 constant DAO_VOTING_PERIOD = 7 days;
uint256 constant DEPLOYMENT_MULTIPLIER = 1;
uint256 constant INTERVAL_SECONDS = 1 hours;
// DAO Configuration
uint256 constant TIMELOCK_DELAY = 2 days;
uint128 constant BASE_UPDATE_TIME = uint128(8 hours);
uint8 constant PROPOSAL_THRESHOLD_PERCENTAGE = 66;

contract Deploy is Script {
address[] genesis;
Expand All @@ -18,41 +20,53 @@ contract Deploy is Script {
function run() external {
genesis.push(msg.sender);
genesis.push(0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266);
address[] memory proposers = new address[](0);
address[] memory executors = new address[](0);

vm.startBroadcast();

SmartnodesERC20 token = new SmartnodesERC20(
DEPLOYMENT_MULTIPLIER,
genesis
);
SmartnodesDAO dao = new SmartnodesDAO(
address(token),
DAO_VOTING_PERIOD,
1000
SmartnodesERC20 token = new SmartnodesERC20(genesis);
TimelockController timelock = new TimelockController(
TIMELOCK_DELAY,
proposers,
executors,
msg.sender // Temporary admin to set up roles
);
SmartnodesDAO dao = new SmartnodesDAO(token, timelock);
SmartnodesCore core = new SmartnodesCore(address(token));
SmartnodesCoordinator coordinator = new SmartnodesCoordinator(
uint128(INTERVAL_SECONDS * DEPLOYMENT_MULTIPLIER),
66,
BASE_UPDATE_TIME,
PROPOSAL_THRESHOLD_PERCENTAGE,
address(core),
address(token),
initialActiveNodes
);

token.setSmartnodes(address(core), address(coordinator));

token.setDAO(address(dao));
token.setDAO(address(timelock));
core.setCoordinator(address(coordinator));

bytes32 publicKeyHash = vm.envBytes32("PUBLIC_KEY_HASH");
// Configure timelock roles
bytes32 PROPOSER_ROLE = timelock.PROPOSER_ROLE();
bytes32 EXECUTOR_ROLE = timelock.EXECUTOR_ROLE();
bytes32 CANCELLER_ROLE = timelock.CANCELLER_ROLE();
bytes32 DEFAULT_ADMIN_ROLE = timelock.DEFAULT_ADMIN_ROLE();

// Grant DAO the proposer and canceller roles
timelock.grantRole(PROPOSER_ROLE, address(dao));
timelock.grantRole(CANCELLER_ROLE, address(dao));
timelock.grantRole(EXECUTOR_ROLE, address(0));
timelock.renounceRole(DEFAULT_ADMIN_ROLE, msg.sender);

bytes32 publicKeyHash = vm.envBytes32("PUBLIC_KEY_HASH");
core.createValidator(publicKeyHash);
coordinator.addValidator();

console.log("Token:", address(token));
console.log("Timelock:", address(timelock));
console.log("DAO:", address(dao));
console.log("Core:", address(core));
console.log("Coordinator:", address(coordinator));
console.log("DAO:", address(dao));

vm.stopBroadcast();
}
Expand Down
30 changes: 13 additions & 17 deletions src/SmartnodesCoordinator.sol
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.22;
pragma solidity ^0.8.24;

import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import {ISmartnodesCore} from "./interfaces/ISmartnodesCore.sol";
Expand Down Expand Up @@ -59,10 +59,7 @@ contract SmartnodesCoordinator is ReentrancyGuard {
mapping(address => uint256) public validatorVote;
mapping(address => uint8) public hasSubmittedProposal;
mapping(address => uint256) public validatorLastActiveRound; // Track validator activity

// Enhanced round management
uint256 public currentRoundNumber;
uint256 private roundSeed; // Used for deterministic but unpredictable validator selection
uint256 private roundSeed;

// ============= Events ==============
event ProposalCreated(
Expand Down Expand Up @@ -150,7 +147,6 @@ contract SmartnodesCoordinator is ReentrancyGuard {
}

// Initialize round management
currentRoundNumber = 1;
roundSeed = uint256(
keccak256(
abi.encode(block.timestamp, block.prevrandao, _genesisNodes)
Expand Down Expand Up @@ -206,7 +202,7 @@ contract SmartnodesCoordinator is ReentrancyGuard {

// mark as active
hasSubmittedProposal[sender] = proposalNum;
validatorLastActiveRound[sender] = currentRoundNumber;
validatorLastActiveRound[sender] = nextProposalId;

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

Proposal storage proposal = currentProposals[proposalId - 1];
validatorVote[msg.sender] = proposalId;
validatorLastActiveRound[msg.sender] = currentRoundNumber; // Mark as active
validatorLastActiveRound[msg.sender] = nextProposalId; // Mark as active
proposal.votes++;
}

Expand Down Expand Up @@ -281,7 +277,7 @@ contract SmartnodesCoordinator is ReentrancyGuard {
}

// Mark executor as active
validatorLastActiveRound[msg.sender] = currentRoundNumber;
validatorLastActiveRound[msg.sender] = nextProposalId;

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

validators.push(validator);
isValidator[validator] = true;
validatorLastActiveRound[validator] = currentRoundNumber; // Mark as active from start
validatorLastActiveRound[validator] = nextProposalId; // Mark as active from start
emit ValidatorAdded(validator);
}

Expand Down Expand Up @@ -438,7 +434,6 @@ contract SmartnodesCoordinator is ReentrancyGuard {

function _updateRound() internal {
_resetValidatorStates();
currentRoundNumber++;
_selectNewRoundValidators();
delete currentProposals; // Clear proposals for new round
timeConfig.lastExecutionTime = uint128(block.timestamp);
Expand Down Expand Up @@ -492,7 +487,7 @@ contract SmartnodesCoordinator is ReentrancyGuard {
roundSeed,
block.timestamp,
block.prevrandao,
currentRoundNumber,
nextProposalId,
blockhash(block.number - 1)
)
)
Expand All @@ -511,8 +506,8 @@ contract SmartnodesCoordinator is ReentrancyGuard {

// Prioritize active validators (those who participated in recent rounds)
uint256 selectedCount = 0;
uint256 inactivityThreshold = currentRoundNumber > 3
? currentRoundNumber - 3
uint256 inactivityThreshold = nextProposalId > 3
? nextProposalId - 3
: 0;

// First, try to select active validators
Expand Down Expand Up @@ -552,7 +547,7 @@ contract SmartnodesCoordinator is ReentrancyGuard {
}
}

emit NewRoundStarted(currentRoundNumber, currentRoundValidators);
emit NewRoundStarted(nextProposalId, currentRoundValidators);
}

function _computeProposalHash(
Expand Down Expand Up @@ -628,7 +623,8 @@ contract SmartnodesCoordinator is ReentrancyGuard {

function _isCurrentRoundExpired() internal view returns (bool) {
TimeConfig memory tc = timeConfig;
return block.timestamp > tc.lastExecutionTime + (tc.updateTime << 1);
return
block.timestamp > tc.lastExecutionTime + ((tc.updateTime * 5) / 4);
}

// ============= View Functions =============
Expand Down Expand Up @@ -726,7 +722,7 @@ contract SmartnodesCoordinator is ReentrancyGuard {
)
{
return (
currentRoundNumber,
nextProposalId,
currentRoundValidators.length,
_calculateRoundValidatorCount(),
_calculateRequiredVotes()
Expand Down
26 changes: 25 additions & 1 deletion src/SmartnodesCore.sol
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.22;
pragma solidity ^0.8.24;

import {ISmartnodesCoordinator} from "./interfaces/ISmartnodesCoordinator.sol";
import {ISmartnodesERC20, PaymentAmounts} from "./interfaces/ISmartnodesERC20.sol";
Expand All @@ -19,6 +19,7 @@ contract SmartnodesCore {
error Core__NotValidatorMultisig();
error Core__NotToken();
error Core__NodeExists();
error Core__NodeDoesNotExist();

// ============= Events ==============
enum JobState {
Expand Down Expand Up @@ -127,6 +128,29 @@ contract SmartnodesCore {
i_tokenContract.lockTokens(userAddress, false);
}

function unlockValidator() external {
address nodeAddress = msg.sender;
Node storage validator = validators[nodeAddress];

if (!validator.exists || !validator.locked)
revert Core__NodeDoesNotExist();

validator.locked = false;

i_tokenContract.unlockTokens(nodeAddress, true);
}

function unlockUser() external {
address nodeAddress = msg.sender;
Node storage user = users[nodeAddress];

if (!user.exists || !user.locked) revert Core__NodeDoesNotExist();

user.locked = false;

i_tokenContract.unlockTokens(nodeAddress, true);
}

/**
* @notice Requests a new job to be created with a form of payment
* @param _userId Unique identifier associated with P2P node for the requesting user
Expand Down
Loading