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,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}
0 commit comments