Skip to content

Commit 1685ff2

Browse files
committed
Merge remote-tracking branch 'upstream/main' into security/white-hat-fixed
2 parents 8431c80 + 4262063 commit 1685ff2

21 files changed

Lines changed: 1553 additions & 1020 deletions

.github/pull_request_template.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,6 @@
88

99
## Sector#3 Contribution
1010

11-
<!--- Please add this pull request as a DAO contribution on Sector#3: https://goerli.sector3.xyz/daos -->
11+
<!--- Please add this pull request as a DAO contribution on Sector#3: https://sepolia.sector3.xyz -->
1212

13-
- [ ] Reported contribution at https://goerli.sector3.xyz/v0/priorities/0x6660626F3b51c0A7a9C558Ade45B17B7De6f91c1
13+
- [ ] Reported contribution at https://sepolia.sector3.xyz/v1/daos/0xB1932B5ba9Dfd0a2dCFa08140eb272DC60804699

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,10 +43,10 @@ Deploy a smart contract to the local network:
4343
npx hardhat run --network localhost scripts/deploy-<contract>.ts
4444
```
4545

46-
Deploy a smart contract to the Goerli test network:
46+
Deploy a smart contract to the Sepolia test network:
4747

4848
```shell
49-
npx hardhat run --network goerli scripts/deploy-<contract>.ts
49+
npx hardhat run --network sepolia scripts/deploy-<contract>.ts
5050
```
5151

5252
Verify a contract on Etherscan:

contracts/Lock.sol

Lines changed: 0 additions & 34 deletions
This file was deleted.

contracts/governance/Sector3Governor.sol

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

44
import "@openzeppelin/contracts/governance/Governor.sol";
55
import "@openzeppelin/contracts/governance/extensions/GovernorSettings.sol";

contracts/protocol/Enums.sol

Lines changed: 0 additions & 4 deletions
This file was deleted.

contracts/protocol/IPriority.sol

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,12 @@
11
// SPDX-License-Identifier: MIT
2-
pragma solidity ^0.8.17;
3-
4-
import "./Enums.sol";
5-
import "./Structs.sol";
2+
pragma solidity ^0.8.19;
63

74
interface IPriority {
85

96
/**
107
* Add a contribution for the current epoch.
118
*/
12-
function addContribution(Contribution memory contribution) external;
9+
function addContribution(string memory description, string memory proofURL, uint8 hoursSpent, uint8 alignmentPercentage) external;
1310

1411
/**
1512
* Claim reward for contributions made in a past epoch.

contracts/protocol/Sector3DAO.sol

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

44
import './Sector3DAOPriority.sol';
55

@@ -12,7 +12,7 @@ contract Sector3DAO {
1212
/**
1313
* The protocol version.
1414
*/
15-
uint8 public constant version = 0;
15+
uint8 public constant version = 1;
1616

1717
/**
1818
* The smart contract owner.
@@ -43,7 +43,15 @@ contract Sector3DAO {
4343
name = name_;
4444
purpose = purpose_;
4545
token = token_;
46-
owner = msg.sender;
46+
owner = tx.origin;
47+
}
48+
49+
/**
50+
* Updates the DAO's owner.
51+
*/
52+
function setOwner(address owner_) public {
53+
require(msg.sender == owner, "You aren't the owner");
54+
owner = owner_;
4755
}
4856

4957
modifier onlyOwner() {
@@ -73,22 +81,17 @@ contract Sector3DAO {
7381
token = token_;
7482
}
7583

76-
function deployPriority(string calldata title, address rewardToken, uint16 epochDurationInDays, uint256 epochBudget) public onlyOwner returns (Sector3DAOPriority) {
77-
Sector3DAOPriority priority = new Sector3DAOPriority(address(this), title, rewardToken, epochDurationInDays, epochBudget);
84+
function deployPriority(string calldata title, address rewardToken, uint16 epochDurationInDays, uint256 epochBudget, address gatingNFT) public onlyOwner returns (Sector3DAOPriority) {
85+
Sector3DAOPriority priority = new Sector3DAOPriority(address(this), title, rewardToken, epochDurationInDays, epochBudget, gatingNFT);
7886
priorities.push(priority);
7987
return priority;
8088
}
8189

82-
function getPriorityCount() public view returns (uint16) {
83-
return uint16(priorities.length);
84-
}
85-
8690
function getPriorities() public view returns (Sector3DAOPriority[] memory) {
8791
return priorities;
8892
}
8993

90-
function removePriority(Sector3DAOPriority priority) public onlyOwner {
91-
require(!priority.isInVotingPeriod(), "Cannot remove priority during voting period");
94+
function removePriority(Sector3DAOPriority priority) public onlyOwner {
9295
Sector3DAOPriority[] memory prioritiesAfterRemoval = new Sector3DAOPriority[](priorities.length - 1);
9396
uint16 prioritiesIndex = 0;
9497
for (uint16 i = 0; i < prioritiesAfterRemoval.length; i++) {

contracts/protocol/Sector3DAOFactory.sol

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// SPDX-License-Identifier: MIT
2-
pragma solidity ^0.8.17;
2+
pragma solidity ^0.8.19;
33

44
import './Sector3DAO.sol';
55

@@ -13,11 +13,6 @@ contract Sector3DAOFactory {
1313

1414
constructor() {
1515
owner = msg.sender;
16-
// daos.push(0x5FbDB2315678afecb367f032d93F642f64180aa3); // localhost
17-
daos.push(0xEa98D59e4EF83822393AF87e587713c2674eD4FD); // Sector#3 DAO (v0)
18-
daos.push(0xd87246302AE8f12485BB525f27778106c636166e); // BanklessDAO (v3)
19-
daos.push(0x2D624a0bA38b40B4f7bE2bfeb56B6B0dD81Be6A1); // Nation3 (v0)
20-
daos.push(0x9741B82017485759c9Bcc13FeA10c1105f82d25C); // Bankless Africa (v0)
2116
}
2217

2318

contracts/protocol/Sector3DAOPriority.sol

Lines changed: 80 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
// SPDX-License-Identifier: MIT
2-
pragma solidity ^0.8.17;
2+
pragma solidity ^0.8.19;
33

44
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
5+
import "@openzeppelin/contracts/token/ERC721/IERC721.sol";
56
import "./IPriority.sol";
6-
import "./Enums.sol";
77
import "./Structs.sol";
88

99
contract Sector3DAOPriority is IPriority {
@@ -15,21 +15,29 @@ contract Sector3DAOPriority is IPriority {
1515
uint256 public immutable startTime;
1616
uint16 public immutable epochDuration;
1717
uint256 public immutable epochBudget;
18+
IERC721 public immutable gatingNFT;
1819
Contribution[] contributions;
20+
mapping(uint16 => mapping(address => bool)) claims;
21+
uint256 public claimsBalance;
1922

2023
event ContributionAdded(Contribution contribution);
2124
event RewardClaimed(uint16 epochIndex, address contributor, uint256 amount);
2225

2326
error EpochNotYetEnded();
27+
error EpochNotYetFunded();
2428
error NoRewardForEpoch();
29+
error RewardAlreadyClaimed();
30+
error NoGatingNFTOwnership();
31+
error InvalidInput();
2532

26-
constructor(address dao_, string memory title_, address rewardToken_, uint16 epochDurationInDays, uint256 epochBudget_) {
33+
constructor(address dao_, string memory title_, address rewardToken_, uint16 epochDurationInDays, uint256 epochBudget_, address gatingNFT_) {
2734
dao = dao_;
2835
title = title_;
2936
rewardToken = IERC20(rewardToken_);
3037
startTime = block.timestamp;
3138
epochDuration = epochDurationInDays;
3239
epochBudget = epochBudget_;
40+
gatingNFT = IERC721(gatingNFT_);
3341
}
3442

3543
/**
@@ -41,40 +49,53 @@ contract Sector3DAOPriority is IPriority {
4149
return uint16(timePassedSinceStart / epochDurationInSeconds);
4250
}
4351

44-
function addContribution(Contribution memory contribution) public {
45-
contribution.timestamp = block.timestamp;
46-
contribution.epochIndex = getEpochIndex();
47-
contribution.contributor = msg.sender;
48-
contribution.alignmentPercentage = uint8(contribution.alignment) * 20;
49-
contributions.push(contribution);
50-
emit ContributionAdded(contribution);
51-
}
52-
53-
function addContribution2(string memory description, string memory proofURL, uint8 hoursSpent, Alignment alignment) public {
52+
/**
53+
* @notice Adds a contribution to the current epoch.
54+
*/
55+
function addContribution(string memory description, string memory proofURL, uint8 hoursSpent, uint8 alignmentPercentage) public {
56+
if (address(gatingNFT) != address(0x0)) {
57+
if (gatingNFT.balanceOf(msg.sender) == 0) {
58+
revert NoGatingNFTOwnership();
59+
}
60+
}
61+
if(bytes(description).length == 0 || bytes(proofURL).length == 0){
62+
revert InvalidInput();
63+
}
64+
uint16 epochIndex = getEpochIndex();
5465
Contribution memory contribution = Contribution({
55-
timestamp: block.timestamp,
56-
epochIndex: getEpochIndex(),
57-
contributor: msg.sender,
58-
description: description,
59-
proofURL: proofURL,
60-
hoursSpent: hoursSpent,
61-
alignment: alignment,
62-
alignmentPercentage: uint8(alignment) * 20
66+
timestamp: block.timestamp,
67+
epochIndex: epochIndex,
68+
contributor: msg.sender,
69+
description: description,
70+
proofURL: proofURL,
71+
hoursSpent: hoursSpent,
72+
alignmentPercentage: alignmentPercentage
6373
});
6474
contributions.push(contribution);
6575
emit ContributionAdded(contribution);
66-
}
76+
}
6777

68-
function getContributionCount() public view returns (uint16) {
69-
return uint16(contributions.length);
70-
}
7178

7279
function getContributions() public view returns (Contribution[] memory) {
7380
return contributions;
7481
}
7582

76-
function getContribution(uint16 index) public view returns (Contribution memory) {
77-
return contributions[index];
83+
function getEpochContributions(uint16 epochIndex) public view returns (Contribution[] memory) {
84+
uint16 count = 0;
85+
for (uint16 i = 0; i < contributions.length; i++) {
86+
if (contributions[i].epochIndex == epochIndex) {
87+
count++;
88+
}
89+
}
90+
Contribution[] memory epochContributions = new Contribution[](count);
91+
count = 0;
92+
for (uint16 i = 0; i < contributions.length; i++) {
93+
if (contributions[i].epochIndex == epochIndex) {
94+
epochContributions[count] = contributions[i];
95+
count++;
96+
}
97+
}
98+
return epochContributions;
7899
}
79100

80101
/**
@@ -98,6 +119,7 @@ contract Sector3DAOPriority is IPriority {
98119
if (rewardClaimed) {
99120
revert RewardAlreadyClaimed();
100121
}
122+
101123
claims[epochIndex][msg.sender] = true;
102124
claimsBalance += epochReward;
103125
require(rewardToken.transfer(msg.sender, epochReward), "Reward transfer failed");
@@ -114,28 +136,12 @@ contract Sector3DAOPriority is IPriority {
114136
return epochBudget * allocationPercentage / 100;
115137
}
116138

117-
/**
118-
* @notice Checks if the smart contract has received enough funding to cover claims for a past epoch.
119-
*
120-
* @param epochIndex The index of the epoch to check.
121-
* @return A boolean indicating whether the epoch is funded or not.
122-
*
123-
* @dev This function loops through all past epochs to calculate whether the current epoch is funded or not.
124-
* If the number of past epochs becomes very large, the function may consume an excessive amount of gas and fail to execute,
125-
* thereby preventing other legitimate functions from executing. Epochs without contributions are excluded from funding.
126-
*/
127139

128-
function isEpochFunded(uint16 epochIndex) public view returns (bool) {
129-
if (epochIndex >= getEpochIndex()) {
130-
revert EpochNotYetEnded();
131-
}
132-
if (getEpochContributions(epochIndex).length == 0) {
133-
return false;
134-
}
135-
uint256 totalBudget = epochBudget * (epochIndex + 1);
136-
uint256 totalFundingReceived = rewardToken.balanceOf(address(this)) + claimsBalance;
137-
uint16 numberOfEpochsWithContributions = epochIndex + 1;
138-
return totalFundingReceived >= totalBudget;
140+
/**
141+
* Checks if a contributor's reward has been claimed for a given epoch.
142+
*/
143+
function isRewardClaimed(uint16 epochIndex, address contributor) public view returns (bool) {
144+
return claims[epochIndex][contributor];
139145
}
140146

141147

@@ -162,4 +168,30 @@ contract Sector3DAOPriority is IPriority {
162168
return uint8(hoursSpentContributor * 100 / hoursSpentAllContributors);
163169
}
164170
}
171+
172+
/**
173+
* @notice Checks if the smart contract has received enough funding to cover claims for a past epoch.
174+
* @dev Epochs without contributions are excluded from funding.
175+
*/
176+
function isEpochFunded(uint16 epochIndex) public view returns (bool) {
177+
if (epochIndex >= getEpochIndex()) {
178+
revert EpochNotYetEnded();
179+
}
180+
if (getEpochContributions(epochIndex).length == 0) {
181+
return false;
182+
}
183+
uint16 numberOfEpochsWithContributions = 0;
184+
for (uint16 i = 0; i <= epochIndex; i++) {
185+
if (getEpochContributions(i).length > 0) {
186+
numberOfEpochsWithContributions++;
187+
}
188+
}
189+
if (numberOfEpochsWithContributions == 0) {
190+
return false;
191+
} else {
192+
uint256 totalBudget = epochBudget * numberOfEpochsWithContributions;
193+
uint256 totalFundingReceived = rewardToken.balanceOf(address(this)) + claimsBalance;
194+
return totalFundingReceived >= totalBudget;
195+
}
196+
}
165197
}

contracts/protocol/Structs.sol

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,12 @@
11
// SPDX-License-Identifier: MIT
2-
pragma solidity ^0.8.17;
3-
4-
import "./Enums.sol";
2+
pragma solidity ^0.8.19;
53

64
struct Contribution {
75
uint256 timestamp;
86
uint16 epochIndex;
97
address contributor;
108
string description;
119
string proofURL;
12-
Alignment alignment;
13-
uint8 alignmentPercentage;
1410
uint8 hoursSpent;
11+
uint8 alignmentPercentage;
1512
}

0 commit comments

Comments
 (0)