Skip to content

Commit 5650a42

Browse files
committed
feat: Smart contracts version 1.0 (first final)
1 parent cf37110 commit 5650a42

File tree

3 files changed

+92
-33
lines changed

3 files changed

+92
-33
lines changed
Lines changed: 47 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,77 +1,91 @@
1-
// SPDX-License-Identifier: GNU
1+
// SPDX-License-Identifier: GPL-3.0-or-later
22
pragma solidity ^0.8.24;
33

4-
import "@openzeppelin/contracts/access/AccessControlEnumerable.sol";
4+
import { AccessControlEnumerable } from "@openzeppelin/contracts/access/extensions/AccessControlEnumerable.sol";
55

66

7-
contract TaskCreatorAccess is AccessControlEnumerable {
7+
contract TaskAccess is AccessControlEnumerable {
88

9-
EnumerableSet.AddressSet private _candidates; // candidates under voting
109
mapping(address => uint256) public voteCount; /// vote count of each task creator
11-
mapping(address => mapping(address => bool)) public hasVoted; // voter => candidate => voted
10+
mapping(address => uint256) public candidateEpoch; /// epoch of each candidate
11+
mapping(address => mapping(address => uint256)) public lastVotedEpoch; /// last epoch a voter voted for a candidate
1212

1313
bytes32 public constant TASK_CREATOR_ROLE = keccak256("TASK_CREATOR_ROLE");
1414

15-
event CandidateProposed(address indexed candidate);
1615
event Voted(address indexed voter, address indexed candidate, uint256 votes);
1716
event Unvoted(address indexed voter, address indexed candidate, uint256 votes);
1817
event TaskCreatorAdded(address indexed newCreator);
1918
event TaskCreatorRemoved(address indexed exCreator);
19+
event AdminAdded(address indexed newAdmin);
20+
event AdminRemoved(address indexed exAdmin);
2021

2122

22-
constructor(address initialCreator) {
23+
constructor() {
2324
_grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
2425
}
2526

26-
/// @dev Check if an address is a TaskCreator
27-
function isTaskCreator(address account) public view returns (bool) {
28-
return hasRole(TASK_CREATOR_ROLE, account);
29-
}
30-
31-
/// @dev Get the number of TaskCreators
32-
function taskCreatorCount() public view returns (uint256) {
33-
return getRoleMemberCount(TASK_CREATOR_ROLE);
34-
}
3527

3628
/// @dev Dynamic voting threshold based on number of TaskCreators
3729
function threshold() public view returns (uint256) {
3830
uint256 n = taskCreatorCount();
3931
if (n <= 20) {
4032
return (n / 2) + 1; // majority
4133
} else if (n < 200) {
42-
return n / 3; // ~1/3 when moderately sized
34+
return (n + 2) / 3; // ~1/3 when moderately sized
4335
} else {
4436
return 100; // capped threshold
4537
}
4638
}
4739

40+
/// TaskCreator FUNCTIONS
41+
4842
/// @dev Vote for a new TaskCreator
4943
function voteForTaskCreator(address candidate) external onlyRole(TASK_CREATOR_ROLE) {
44+
require(candidate != address(0), "Zero address");
45+
require(candidate != msg.sender, "Self-vote not allowed");
5046
require(!hasRole(TASK_CREATOR_ROLE, candidate), "Already TaskCreator");
51-
require(!hasVoted[msg.sender][candidate], "Already voted");
47+
require(lastVotedEpoch[msg.sender][candidate] < candidateEpoch[candidate], "Already voted");
5248

53-
hasVoted[msg.sender][candidate] = true;
49+
lastVotedEpoch[msg.sender][candidate] = candidateEpoch[candidate];
5450
voteCount[candidate]++;
5551

5652
emit Voted(msg.sender, candidate, voteCount[candidate]);
5753

5854
if (voteCount[candidate] >= threshold()) {
55+
candidateEpoch[candidate] += 1;
56+
voteCount[candidate] = 0; // reset votes
5957
_grantRole(TASK_CREATOR_ROLE, candidate);
6058
emit TaskCreatorAdded(candidate);
6159
}
6260
}
6361

6462
/// @dev Remove vote for a TaskCreator
65-
function unvoteTaskCreator(address candidate) external {
66-
require(voteCount[candidate] > 0, "Candidate has no votes");
67-
require(hasVoted[msg.sender][candidate], "Not voted");
63+
function unvoteTaskCreator(address candidate) external onlyRole(TASK_CREATOR_ROLE) {
64+
require(candidate != address(0), "Zero address");
65+
require(lastVotedEpoch[msg.sender][candidate] == candidateEpoch[candidate], "Epoch mismatch");
6866

69-
hasVoted[msg.sender][candidate] = false;
67+
lastVotedEpoch[msg.sender][candidate] = 0; // reset epoch
7068
voteCount[candidate]--;
7169

7270
emit Unvoted(msg.sender, candidate, voteCount[candidate]);
7371
}
7472

73+
/// @dev Check if an address is a TaskCreator
74+
function isTaskCreator(address account) public view returns (bool) {
75+
return hasRole(TASK_CREATOR_ROLE, account);
76+
}
77+
78+
/// @dev Get the number of TaskCreators
79+
function taskCreatorCount() public view returns (uint256) {
80+
return getRoleMemberCount(TASK_CREATOR_ROLE);
81+
}
82+
83+
function getTaskCreators() public view returns (address[] memory) {
84+
return getRoleMembers(TASK_CREATOR_ROLE);
85+
}
86+
87+
/// ADMIN FUNCTIONS
88+
7589
/// @dev Directly grant TaskCreator role
7690
function addTaskCreator(address account) external onlyRole(DEFAULT_ADMIN_ROLE) {
7791
_grantRole(TASK_CREATOR_ROLE, account);
@@ -87,10 +101,20 @@ contract TaskCreatorAccess is AccessControlEnumerable {
87101
/// @dev Add new Admin
88102
function addAdmin(address account) external onlyRole(DEFAULT_ADMIN_ROLE) {
89103
_grantRole(DEFAULT_ADMIN_ROLE, account);
104+
emit AdminAdded(account);
90105
}
91106

92107
/// @dev Remove Admin
93108
function removeAdmin(address account) external onlyRole(DEFAULT_ADMIN_ROLE) {
94109
_revokeRole(DEFAULT_ADMIN_ROLE, account);
110+
emit AdminRemoved(account);
111+
}
112+
113+
function isTaskAdmin(address account) public view returns (bool) {
114+
return hasRole(DEFAULT_ADMIN_ROLE, account);
115+
}
116+
117+
function getAdmins() public view returns (address[] memory) {
118+
return getRoleMembers(DEFAULT_ADMIN_ROLE);
95119
}
96120
}

src/TaskManager.sol

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
1-
// SPDX-License-Identifier: GNU
1+
// SPDX-License-Identifier: GPL-3.0-or-later
22
pragma solidity ^0.8.24;
33

44
import "./TaskToken.sol";
5-
import "./TaskCreatorAccess.sol";
5+
import "./TaskAccess.sol";
66
import "./Verifier.sol";
77

88
contract TaskManager {
99
TaskToken public taskToken; // The ERC-1155 token contract
10-
TaskCreatorAccess public accessControl; // Access control for task creators
10+
TaskAccess public accessControl; // Access control for task creators
1111
Groth16Verifier public verifier; // zkSNARK verifier contract
1212
mapping(uint256 => Task) public tasks; // taskId => Task
1313

@@ -27,11 +27,19 @@ contract TaskManager {
2727
_;
2828
}
2929

30+
/// @dev Restrict to only TaskAdmins
31+
modifier onlyTaskAdmin() {
32+
require(accessControl.isTaskAdmin(msg.sender), "Not TaskAdmin");
33+
_;
34+
}
35+
3036
constructor(
3137
address _taskToken,
38+
address _access,
3239
address _verifier
3340
) {
3441
taskToken = TaskToken(_taskToken);
42+
accessControl = TaskAccess(_access);
3543
verifier = Groth16Verifier(_verifier);
3644
}
3745

@@ -44,7 +52,7 @@ contract TaskManager {
4452
}
4553

4654
/// @dev Deactivate a task to prevent further solving
47-
function deactivateTask(uint256 taskId) onlyTaskCreator external {
55+
function deactivateTask(uint256 taskId) onlyTaskAdmin external {
4856
Task storage task = tasks[taskId];
4957
require(task.active, "Task not active");
5058
task.active = false;
@@ -57,7 +65,7 @@ contract TaskManager {
5765
uint[2] calldata a,
5866
uint[2][2] calldata b,
5967
uint[2] calldata c,
60-
uint[] calldata publicSignals
68+
uint[4] calldata publicSignals
6169
) external {
6270
Task storage task = tasks[taskId];
6371
// Check if task is active

src/TaskToken.sol

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,37 @@
1-
// SPDX-License-Identifier: GNU
1+
// SPDX-License-Identifier: GPL-3.0-or-later
22
pragma solidity ^0.8.24;
33

4-
import {ERC1155} from "@openzeppelin/contracts/token/ERC1155/ERC1155.sol";
4+
import { ERC1155 } from "@openzeppelin/contracts/token/ERC1155/ERC1155.sol";
55
import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";
66

77

88
contract TaskToken is ERC1155, Ownable {
99
mapping(uint256 => string) private _taskURIs; // Mapping: taskId => metadata URI
1010
uint256 public nextTaskId = 1; // Optional: track existing task IDs
11+
address public manager;
1112

12-
constructor(address _accessControl) ERC1155("") Ownable(msg.sender) {}
13+
constructor() ERC1155("") Ownable(msg.sender) {}
14+
15+
/// @dev Restrict to only TaskManager
16+
modifier onlyManager() {
17+
require(msg.sender == manager, "Not TaskManager");
18+
_;
19+
}
20+
21+
/// @dev Restrict to only TaskManager
22+
function setManager(address _manager) onlyOwner external {
23+
manager = _manager;
24+
}
1325

1426
/// @dev Add a new task token with its metadata URI
15-
function addTaskToken(string memory taskURI) onlyOwner external returns (uint256 taskId) {
27+
function addTaskToken(string memory taskURI) onlyManager external returns (uint256 taskId) {
1628
taskId = nextTaskId++;
1729
_taskURIs[taskId] = taskURI;
1830
}
1931

2032
/// @dev Mint token
2133
/// TODO: restrict to only solvers of the task!
22-
function mint(address to, uint256 taskId) onlyOwner external {
34+
function mint(address to, uint256 taskId) onlyManager external {
2335
require(bytes(_taskURIs[taskId]).length > 0, "Invalid taskId");
2436
require(balanceOf(to, taskId) == 0, "Already claimed");
2537
_mint(to, taskId, 1, "");
@@ -47,4 +59,19 @@ contract TaskToken is ERC1155, Ownable {
4759
function exists(uint256 taskId) public view returns (bool) {
4860
return bytes(_taskURIs[taskId]).length > 0;
4961
}
62+
63+
/// Disable approvals and transfers
64+
65+
function setApprovalForAll(address, bool) public virtual override {
66+
revert("Approvals disabled");
67+
}
68+
69+
function safeTransferFrom(address, address, uint256, uint256, bytes memory) public virtual override {
70+
revert("Transfers disabled");
71+
}
72+
73+
function safeBatchTransferFrom(address, address, uint256[] memory, uint256[] memory, bytes memory) public virtual override {
74+
revert("Transfers disabled");
75+
}
76+
5077
}

0 commit comments

Comments
 (0)