@@ -325,5 +325,140 @@ contract AaveYieldBackendIntegrationTest is Test {
325325 // 2 operations: enable deposits + withdraw surplus
326326 _verifyInvariants (false , 2 );
327327 }
328+
329+ /*//////////////////////////////////////////////////////////////////////////
330+ Random Sequence Fuzz Tests
331+ //////////////////////////////////////////////////////////////////////////*/
332+
333+ struct YieldBackendStep {
334+ uint8 a; // action type: 0 enable, 1 disable, 2 switch, 3 upgrade, 4 downgrade, 5 withdraw surplus
335+ uint32 v; // action param (amount for upgrade/downgrade, unused for others)
336+ uint16 dt; // time delta (for yield accrual simulation)
337+ }
338+
339+ /// @notice Test random sequence of yield backend operations
340+ /// @dev Simulates real-world usage patterns with appropriate frequency distribution
341+ function testRandomYieldBackendSequence (YieldBackendStep[20 ] memory steps ) external {
342+ // Track state
343+ bool backendEnabled = false ;
344+ bool initialExcessPreserved = true ; // Track if initial excess has been withdrawn via surplus
345+ uint256 numAaveOperations = 0 ;
346+ AaveYieldBackend currentBackend = aaveBackend;
347+
348+ for (uint256 i = 0 ; i < steps.length ; ++ i) {
349+ YieldBackendStep memory s = steps[i];
350+ uint256 action = s.a % 20 ; // Use modulo 20 for frequency distribution
351+
352+ // Action frequency distribution:
353+ // 0: Enable (5%)
354+ // 1: Disable (5%)
355+ // 2: Switch (5%)
356+ // 3-16: Upgrade/Downgrade (70%, split evenly: 3-9 upgrade, 10-16 downgrade)
357+ // 17-19: Withdraw surplus (15%)
358+
359+ if (action == 0 ) {
360+ // Enable yield backend (5% frequency)
361+ if (! backendEnabled) {
362+ vm.startPrank (ADMIN);
363+ superToken.enableYieldBackend (currentBackend);
364+ vm.stopPrank ();
365+ backendEnabled = true ;
366+ numAaveOperations += 1 ; // enable deposits all existing underlying
367+ }
368+ } else if (action == 1 ) {
369+ // Disable yield backend (5% frequency)
370+ if (backendEnabled) {
371+ vm.startPrank (ADMIN);
372+ superToken.disableYieldBackend ();
373+ vm.stopPrank ();
374+ backendEnabled = false ;
375+ numAaveOperations += 1 ; // disable withdraws max
376+ }
377+ } else if (action == 2 ) {
378+ // Switch yield backend: disable current, enable new (5% frequency)
379+ if (backendEnabled) {
380+ // Disable current
381+ vm.startPrank (ADMIN);
382+ superToken.disableYieldBackend ();
383+ vm.stopPrank ();
384+ numAaveOperations += 1 ; // disable withdraws
385+
386+ // Deploy and enable new backend
387+ AaveYieldBackend newBackend = new AaveYieldBackend (
388+ IERC20 (USDC),
389+ IPool (AAVE_POOL),
390+ SURPLUS_RECEIVER
391+ );
392+ vm.startPrank (ADMIN);
393+ superToken.enableYieldBackend (newBackend);
394+ vm.stopPrank ();
395+ currentBackend = newBackend;
396+ numAaveOperations += 1 ; // enable deposits
397+ }
398+ } else if (action >= 3 && action <= 9 ) {
399+ // Upgrade (35% frequency)
400+ if (backendEnabled) {
401+ // Bound upgrade amount to reasonable range
402+ uint256 upgradeAmount = bound (uint256 (s.v), 1e18 , 1_000_000 * 1e18 );
403+ vm.startPrank (ALICE);
404+ superToken.upgrade (upgradeAmount);
405+ vm.stopPrank ();
406+ numAaveOperations += 1 ; // upgrade deposits
407+ }
408+ } else if (action >= 10 && action <= 16 ) {
409+ // Downgrade (35% frequency)
410+ if (backendEnabled) {
411+ uint256 aliceBalance = superToken.balanceOf (ALICE);
412+ if (aliceBalance > 0 ) {
413+ // Bound downgrade amount to available balance
414+ uint256 downgradeAmount = bound (uint256 (s.v), 1e18 , aliceBalance);
415+ // Don't downgrade more than available
416+ if (downgradeAmount > aliceBalance) {
417+ downgradeAmount = aliceBalance;
418+ }
419+ vm.startPrank (ALICE);
420+ superToken.downgrade (downgradeAmount);
421+ vm.stopPrank ();
422+ numAaveOperations += 1 ; // downgrade withdraws
423+ }
424+ }
425+ } else if (action >= 17 && action <= 19 ) {
426+ // Withdraw surplus (15% frequency)
427+ if (backendEnabled) {
428+ // Check if there's surplus to withdraw
429+ uint256 underlyingBalance = IERC20 (USDC).balanceOf (address (superToken));
430+ uint256 aTokenBalance = IERC20 (A_USDC).balanceOf (address (superToken));
431+ (uint256 normalizedTotalSupply ,) = superToken.toUnderlyingAmount (superToken.totalSupply ());
432+ uint256 totalAssets = underlyingBalance + aTokenBalance;
433+
434+ // Only withdraw if there's actual surplus (after 100 wei margin)
435+ if (totalAssets > normalizedTotalSupply + 100 ) {
436+ vm.startPrank (ADMIN);
437+ superToken.withdrawSurplusFromYieldBackend ();
438+ vm.stopPrank ();
439+ numAaveOperations += 1 ; // withdraw surplus
440+ // After withdrawing surplus, initial excess is also withdrawn
441+ initialExcessPreserved = false ;
442+ }
443+ }
444+ }
445+
446+ // Warp time to simulate yield accrual (if dt > 0)
447+ if (s.dt > 0 ) {
448+ // Bound time warp to reasonable range (1 hour to 30 days)
449+ uint256 timeWarp = bound (uint256 (s.dt), 1 hours, 30 days);
450+ vm.warp (block .timestamp + timeWarp);
451+ }
452+
453+ // Verify invariants after each step
454+ // Initial excess should be preserved only if backend is enabled AND surplus hasn't been withdrawn
455+ bool preserveInitialExcess = backendEnabled && initialExcessPreserved;
456+ _verifyInvariants (preserveInitialExcess, numAaveOperations);
457+ }
458+
459+ // Final invariant check
460+ bool finalPreserveInitialExcess = backendEnabled && initialExcessPreserved;
461+ _verifyInvariants (finalPreserveInitialExcess, numAaveOperations);
462+ }
328463}
329464
0 commit comments