11// SPDX-License-Identifier: MIT
2- pragma solidity ^ 0.8.17 ;
2+ pragma solidity ^ 0.8.19 ;
33
44import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol " ;
5+ import "@openzeppelin/contracts/token/ERC721/IERC721.sol " ;
56import "./IPriority.sol " ;
6- import "./Enums.sol " ;
77import "./Structs.sol " ;
88
99contract Sector3DAOPriority is IPriority {
@@ -15,21 +15,28 @@ 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 ();
2531
26- constructor (address dao_ , string memory title_ , address rewardToken_ , uint16 epochDurationInDays , uint256 epochBudget_ ) {
32+ constructor (address dao_ , string memory title_ , address rewardToken_ , uint16 epochDurationInDays , uint256 epochBudget_ , address gatingNFT_ ) {
2733 dao = dao_;
2834 title = title_;
2935 rewardToken = IERC20 (rewardToken_);
3036 startTime = block .timestamp ;
3137 epochDuration = epochDurationInDays;
3238 epochBudget = epochBudget_;
39+ gatingNFT = IERC721 (gatingNFT_);
3340 }
3441
3542 /**
@@ -41,40 +48,48 @@ contract Sector3DAOPriority is IPriority {
4148 return uint16 (timePassedSinceStart / epochDurationInSeconds);
4249 }
4350
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 {
51+ /**
52+ * @notice Adds a contribution to the current epoch.
53+ */
54+ function addContribution (string memory description , string memory proofURL , uint8 hoursSpent , uint8 alignmentPercentage ) public {
55+ if (address (gatingNFT) != address (0x0 )) {
56+ if (gatingNFT.balanceOf (msg .sender ) == 0 ) {
57+ revert NoGatingNFTOwnership ();
58+ }
59+ }
5460 Contribution memory contribution = Contribution ({
5561 timestamp: block .timestamp ,
5662 epochIndex: getEpochIndex (),
5763 contributor: msg .sender ,
5864 description: description,
5965 proofURL: proofURL,
6066 hoursSpent: hoursSpent,
61- alignment: alignment,
62- alignmentPercentage: uint8 (alignment) * 20
67+ alignmentPercentage: alignmentPercentage
6368 });
6469 contributions.push (contribution);
6570 emit ContributionAdded (contribution);
6671 }
6772
68- function getContributionCount () public view returns (uint16 ) {
69- return uint16 (contributions.length );
70- }
71-
7273 function getContributions () public view returns (Contribution[] memory ) {
7374 return contributions;
7475 }
7576
76- function getContribution (uint16 index ) public view returns (Contribution memory ) {
77- return contributions[index];
77+ function getEpochContributions (uint16 epochIndex ) public view returns (Contribution[] memory ) {
78+ uint16 count = 0 ;
79+ for (uint16 i = 0 ; i < contributions.length ; i++ ) {
80+ if (contributions[i].epochIndex == epochIndex) {
81+ count++ ;
82+ }
83+ }
84+ Contribution[] memory epochContributions = new Contribution [](count);
85+ count = 0 ;
86+ for (uint16 i = 0 ; i < contributions.length ; i++ ) {
87+ if (contributions[i].epochIndex == epochIndex) {
88+ epochContributions[count] = contributions[i];
89+ count++ ;
90+ }
91+ }
92+ return epochContributions;
7893 }
7994
8095 /**
@@ -115,30 +130,12 @@ contract Sector3DAOPriority is IPriority {
115130 }
116131
117132 /**
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- */
127-
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;
133+ * Checks if a contributor's reward has been claimed for a given epoch.
134+ */
135+ function isRewardClaimed (uint16 epochIndex , address contributor ) public view returns (bool ) {
136+ return claims[epochIndex][contributor];
139137 }
140138
141-
142139 /**
143140 * Calculates a contributor's percentage allocation of the budget for a given epoch.
144141 *
@@ -162,4 +159,30 @@ contract Sector3DAOPriority is IPriority {
162159 return uint8 (hoursSpentContributor * 100 / hoursSpentAllContributors);
163160 }
164161 }
162+
163+ /**
164+ * @notice Checks if the smart contract has received enough funding to cover claims for a past epoch.
165+ * @dev Epochs without contributions are excluded from funding.
166+ */
167+ function isEpochFunded (uint16 epochIndex ) public view returns (bool ) {
168+ if (epochIndex >= getEpochIndex ()) {
169+ revert EpochNotYetEnded ();
170+ }
171+ if (getEpochContributions (epochIndex).length == 0 ) {
172+ return false ;
173+ }
174+ uint16 numberOfEpochsWithContributions = 0 ;
175+ for (uint16 i = 0 ; i <= epochIndex; i++ ) {
176+ if (getEpochContributions (i).length > 0 ) {
177+ numberOfEpochsWithContributions++ ;
178+ }
179+ }
180+ if (numberOfEpochsWithContributions == 0 ) {
181+ return false ;
182+ } else {
183+ uint256 totalBudget = epochBudget * numberOfEpochsWithContributions;
184+ uint256 totalFundingReceived = rewardToken.balanceOf (address (this )) + claimsBalance;
185+ return totalFundingReceived >= totalBudget;
186+ }
187+ }
165188}
0 commit comments