@@ -137,6 +137,170 @@ contract EmissionsControllerUnitTests_pressButton is EmissionsControllerUnitTest
137137 cheats.expectRevert (IEmissionsControllerErrors.EmissionsNotStarted.selector );
138138 emissionsController.pressButton (1 );
139139 }
140+
141+ function test_revert_pressButton_AfterOneEpoch_AllDistributionsProcessed () public {
142+ // Add a distribution that should run for exactly 1 epoch (epoch 0 only)
143+ cheats.prank (incentiveCouncil);
144+ emissionsController.addDistribution (
145+ Distribution ({
146+ weight: 10_000 ,
147+ startEpoch: 0 ,
148+ totalEpochs: 1 ,
149+ distributionType: DistributionType.RewardsForAllEarners,
150+ operatorSet: emptyOperatorSet (),
151+ strategiesAndMultipliers: defaultStrategiesAndMultipliers ()
152+ })
153+ );
154+
155+ // Epoch 0: should process
156+ cheats.warp (EMISSIONS_START_TIME);
157+ assertEq (emissionsController.getCurrentEpoch (), 0 );
158+ assertTrue (emissionsController.isButtonPressable ());
159+ emissionsController.pressButton (1 );
160+
161+ // Epoch 1: should NOT process (distribution ended at epoch 0+1=1)
162+ cheats.warp (EMISSIONS_START_TIME + EMISSIONS_EPOCH_LENGTH);
163+ assertEq (emissionsController.getCurrentEpoch (), 1 );
164+ assertFalse (emissionsController.isButtonPressable ());
165+ cheats.expectRevert (IEmissionsControllerErrors.AllDistributionsProcessed.selector );
166+ emissionsController.pressButton (1 );
167+ }
168+
169+ function test_revert_pressButton_AfterTwoEpochs_AllDistributionsProcessed () public {
170+ // Add a distribution that should run for exactly 2 epochs (epochs 0 and 1)
171+ cheats.prank (incentiveCouncil);
172+ emissionsController.addDistribution (
173+ Distribution ({
174+ weight: 10_000 ,
175+ startEpoch: 0 ,
176+ totalEpochs: 2 ,
177+ distributionType: DistributionType.RewardsForAllEarners,
178+ operatorSet: emptyOperatorSet (),
179+ strategiesAndMultipliers: defaultStrategiesAndMultipliers ()
180+ })
181+ );
182+
183+ // Epoch 0: should process
184+ cheats.warp (EMISSIONS_START_TIME);
185+ assertEq (emissionsController.getCurrentEpoch (), 0 );
186+ emissionsController.pressButton (1 );
187+ // Verify button is not pressable again in same epoch
188+ assertFalse (emissionsController.isButtonPressable ());
189+
190+ // Epoch 1: should process
191+ cheats.warp (EMISSIONS_START_TIME + EMISSIONS_EPOCH_LENGTH);
192+ assertEq (emissionsController.getCurrentEpoch (), 1 );
193+ assertTrue (emissionsController.isButtonPressable ());
194+ emissionsController.pressButton (1 );
195+ assertFalse (emissionsController.isButtonPressable ());
196+
197+ // Epoch 2: should NOT process (distribution ended at epoch 0+2=2)
198+ cheats.warp (EMISSIONS_START_TIME + 2 * EMISSIONS_EPOCH_LENGTH);
199+ assertEq (emissionsController.getCurrentEpoch (), 2 );
200+ // Button should not be pressable because all distributions have ended
201+ assertFalse (emissionsController.isButtonPressable ());
202+ cheats.expectRevert (IEmissionsControllerErrors.AllDistributionsProcessed.selector );
203+ emissionsController.pressButton (1 );
204+ }
205+
206+ function test_revert_pressButton_AfterThreeFutureEpochs_AllDistributionsProcessed () public {
207+ // Add a distribution that starts at epoch 2 and runs for 3 epochs (epochs 2, 3, 4)
208+ cheats.prank (incentiveCouncil);
209+ emissionsController.addDistribution (
210+ Distribution ({
211+ weight: 10_000 ,
212+ startEpoch: 2 ,
213+ totalEpochs: 3 ,
214+ distributionType: DistributionType.RewardsForAllEarners,
215+ operatorSet: emptyOperatorSet (),
216+ strategiesAndMultipliers: defaultStrategiesAndMultipliers ()
217+ })
218+ );
219+
220+ // Epoch 0: should not process (hasn't started)
221+ cheats.warp (EMISSIONS_START_TIME);
222+ assertEq (emissionsController.getCurrentEpoch (), 0 );
223+ assertFalse (emissionsController.isButtonPressable ());
224+
225+ // Epoch 1: should not process (hasn't started)
226+ cheats.warp (EMISSIONS_START_TIME + EMISSIONS_EPOCH_LENGTH);
227+ assertEq (emissionsController.getCurrentEpoch (), 1 );
228+ assertFalse (emissionsController.isButtonPressable ());
229+
230+ // Epoch 2: should process (first epoch)
231+ cheats.warp (EMISSIONS_START_TIME + 2 * EMISSIONS_EPOCH_LENGTH);
232+ assertEq (emissionsController.getCurrentEpoch (), 2 );
233+ assertTrue (emissionsController.isButtonPressable ());
234+ emissionsController.pressButton (1 );
235+
236+ // Epoch 3: should process
237+ cheats.warp (EMISSIONS_START_TIME + 3 * EMISSIONS_EPOCH_LENGTH);
238+ assertEq (emissionsController.getCurrentEpoch (), 3 );
239+ assertTrue (emissionsController.isButtonPressable ());
240+ emissionsController.pressButton (1 );
241+
242+ // Epoch 4: should process (last epoch)
243+ cheats.warp (EMISSIONS_START_TIME + 4 * EMISSIONS_EPOCH_LENGTH);
244+ assertEq (emissionsController.getCurrentEpoch (), 4 );
245+ assertTrue (emissionsController.isButtonPressable ());
246+ emissionsController.pressButton (1 );
247+
248+ // Epoch 5: should NOT process (ended at epoch 2+3=5)
249+ cheats.warp (EMISSIONS_START_TIME + 5 * EMISSIONS_EPOCH_LENGTH);
250+ assertEq (emissionsController.getCurrentEpoch (), 5 );
251+ assertFalse (emissionsController.isButtonPressable ());
252+ cheats.expectRevert (IEmissionsControllerErrors.AllDistributionsProcessed.selector );
253+ emissionsController.pressButton (1 );
254+ }
255+
256+ function testFuzz_revert_pressButton_AfterDistributionEnds_AllDistributionsProcessed (uint8 _startEpoch , uint8 _totalEpochs ) public {
257+ // Bound inputs to reasonable values
258+ uint64 startEpoch = uint64 (bound (_startEpoch, 0 , 10 ));
259+ uint64 totalEpochs = uint64 (bound (_totalEpochs, 1 , 10 ));
260+
261+ // Add a distribution with fuzzed parameters
262+ cheats.prank (incentiveCouncil);
263+ emissionsController.addDistribution (
264+ Distribution ({
265+ weight: 10_000 ,
266+ startEpoch: startEpoch,
267+ totalEpochs: totalEpochs,
268+ distributionType: DistributionType.RewardsForAllEarners,
269+ operatorSet: emptyOperatorSet (),
270+ strategiesAndMultipliers: defaultStrategiesAndMultipliers ()
271+ })
272+ );
273+
274+ uint endEpoch = startEpoch + totalEpochs;
275+ uint maxTestEpoch = endEpoch + 2 ; // Test a couple epochs after the end
276+
277+ // Test each epoch from 0 to maxTestEpoch
278+ for (uint epoch = 0 ; epoch <= maxTestEpoch; epoch++ ) {
279+ // Warp to the beginning of this epoch
280+ cheats.warp (EMISSIONS_START_TIME + epoch * EMISSIONS_EPOCH_LENGTH);
281+ assertEq (emissionsController.getCurrentEpoch (), epoch);
282+
283+ // Determine if the button should be pressable in this epoch
284+ bool shouldBeActive = (epoch >= startEpoch && epoch < endEpoch);
285+
286+ if (shouldBeActive) {
287+ // Button should be pressable if we haven't pressed it in this epoch yet
288+ assertTrue (emissionsController.isButtonPressable (), "Button should be pressable during active epochs " );
289+ emissionsController.pressButton (1 );
290+ // After pressing, button should not be pressable again in the same epoch
291+ assertFalse (emissionsController.isButtonPressable (), "Button should not be pressable after pressing in same epoch " );
292+ } else {
293+ // Button should not be pressable before start or after end
294+ assertFalse (emissionsController.isButtonPressable (), "Button should not be pressable outside active epochs " );
295+
296+ // If we're at or past the end epoch, verify the correct revert
297+ if (epoch >= endEpoch) {
298+ cheats.expectRevert (IEmissionsControllerErrors.AllDistributionsProcessed.selector );
299+ emissionsController.pressButton (1 );
300+ }
301+ }
302+ }
303+ }
140304}
141305
142306contract EmissionsControllerUnitTests_sweep is EmissionsControllerUnitTests {
0 commit comments