Skip to content

Commit e02db2e

Browse files
committed
Add modular testing utilites
--------- Co-authored-by: Alexander Keating <keating.dev@protonmail.com> Add modular testing natspec (#150) * Add modular testing natspec
1 parent f9efed7 commit e02db2e

20 files changed

Lines changed: 1659 additions & 5 deletions
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
// SPDX-License-Identifier: AGPL-3.0-only
2+
3+
pragma solidity ^0.8.23;
4+
5+
import {BinaryEligibilityOracleEarningPowerCalculator} from
6+
"src/calculators/BinaryEligibilityOracleEarningPowerCalculator.sol";
7+
import {MintRewardNotifier} from "../notifiers/MintRewardNotifier.sol";
8+
import {StakerTestBase} from "./StakerTestBase.sol";
9+
import {Staker} from "../Staker.sol";
10+
11+
/// @title BinaryEligibilityOracleEarningPowerCalculatorTestBase
12+
/// @author [ScopeLift](https://scopelift.co)
13+
/// @notice The base contract for testing BinaryEligibilityOracleEarningPowerCalculator. Contains
14+
/// test setup and helper functions for testing the calculator's behavior when delegatees meet
15+
/// the eligibility threshold. This includes stake management and eligibility threshold testing
16+
/// functionality.
17+
/// @dev This contract requires an initialized instance of
18+
/// `BinaryEligibilityOracleEarningPowerCalculator`. Initialization is typically handled by a
19+
/// deployment script such as
20+
/// `src/script/calculators/DeployBinaryEligibilityOracleEarningPowerCalculator.sol`.
21+
abstract contract BinaryEligibilityOracleEarningPowerCalculatorTestBase is StakerTestBase {
22+
BinaryEligibilityOracleEarningPowerCalculator calculator;
23+
MintRewardNotifier mintRewardNotifier;
24+
25+
/// @notice A helper function that updates the delegatee score for a given deposit to a random
26+
/// value between 0 and twice the eligibility threshold, facilitating tests for both eligible and
27+
/// ineligible delegatee scenarios.
28+
/// @param _depositId The identifier of the deposit whose delegatee's score is to be updated.
29+
function _updateEarningPower(Staker.DepositIdentifier _depositId) internal virtual override {
30+
uint256 _delegateeeScore = vm.randomUint(0, calculator.delegateeEligibilityThresholdScore() * 2);
31+
Staker.Deposit memory _deposit = _fetchDeposit(_depositId);
32+
vm.startPrank(calculator.scoreOracle());
33+
calculator.updateDelegateeScore(_deposit.delegatee, _delegateeeScore);
34+
vm.stopPrank();
35+
}
36+
37+
/// @notice Bound the mint amount to a realistic value.
38+
/// @dev Override of the base contract's function to set appropriate bounds for this calculator.
39+
/// @param _amount The unbounded mint amount.
40+
/// @return The bounded mint amount.
41+
function _boundMintAmount(uint256 _amount) internal pure virtual override returns (uint256) {
42+
return bound(_amount, 1, 100_000_000e18);
43+
}
44+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// SPDX-License-Identifier: AGPL-3.0-only
2+
3+
pragma solidity ^0.8.23;
4+
5+
import {MintRewardNotifier} from "../notifiers/MintRewardNotifier.sol";
6+
import {StakerTestBase} from "./StakerTestBase.sol";
7+
8+
/// @title MintRewardNotifierTestBase
9+
/// @author [ScopeLift](https://scopelift.co)
10+
/// @notice Base contract for testing a staker contract with a single `MintRewardNotifier`. Extends
11+
/// `StakerTestBase` and implements the reward notification logic.
12+
/// @dev This contract requires an initialized instance of `MintRewardNotifier`. Initialization is
13+
/// typically handled by a deployment script such as
14+
/// `src/script/notifiers/DeployMintRewardNotifier.sol`
15+
abstract contract MintRewardNotifierTestBase is StakerTestBase {
16+
MintRewardNotifier mintRewardNotifier;
17+
18+
/// @notice Sets the reward amount, then calls the `notify` function that triggers token minting
19+
/// and reward distribution.
20+
function _notifyRewardAmount(uint256 _amount) public override {
21+
address _owner = mintRewardNotifier.owner();
22+
23+
vm.prank(_owner);
24+
mintRewardNotifier.setRewardAmount(_amount);
25+
26+
mintRewardNotifier.notify();
27+
}
28+
}
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
// SPDX-License-Identifier: AGPL-3.0-only
2+
// slither-disable-start reentrancy-benign
3+
4+
pragma solidity ^0.8.23;
5+
6+
import {BinaryEligibilityOracleEarningPowerCalculator} from
7+
"../calculators/BinaryEligibilityOracleEarningPowerCalculator.sol";
8+
import {StakerTestBase} from "./StakerTestBase.sol";
9+
import {Staker} from "../Staker.sol";
10+
import {BinaryEligibilityOracleEarningPowerCalculatorTestBase} from
11+
"./BinaryEligibilityOracleEarningPowerCalculatorTestBase.sol";
12+
13+
/// @title StakedBinaryEligibilityOracleEarningPowerCalculatorTestSuite
14+
/// @author [ScopeLift](https://scopelift.co)
15+
/// @notice The base contract for testing BinaryEligibilityOracleEarningPowerCalculator. Contains
16+
/// test setup and helper functions for testing the calculator's behavior when delegatees are below
17+
/// the eligibility threshold. This contract is designed to be used in conjunction with the
18+
/// deployment scripts in
19+
/// `src/script/calculators/DeployBinaryEligibilityOracleEarningPowerCalculator.sol`.
20+
abstract contract StakedBinaryEligibilityOracleEarningPowerCalculatorTestBase is
21+
BinaryEligibilityOracleEarningPowerCalculatorTestBase
22+
{
23+
/// @notice Helper to set a delegatee's score below threshold.
24+
/// @param delegatee The address of the delegatee whose score will be set below threshold.
25+
function _setDelegateeScoreBelowThreshold(address delegatee) internal {
26+
vm.startPrank(calculator.scoreOracle());
27+
calculator.updateDelegateeScore(delegatee, calculator.delegateeEligibilityThresholdScore() - 1);
28+
vm.stopPrank();
29+
}
30+
31+
/// @notice A test helper that wraps calling the `stake` function and ensures proper earning power
32+
/// adjustment when delegatee scores are below threshold.
33+
/// @param _depositor The address of the depositor.
34+
/// @param _amount The amount to stake.
35+
/// @param _delegatee The address to which the delegation surrogate is delegating voting power.
36+
/// @return _depositId The id of the created deposit.
37+
function _stakeBelowThreshold(address _depositor, uint256 _amount, address _delegatee)
38+
internal
39+
virtual
40+
returns (Staker.DepositIdentifier _depositId)
41+
{
42+
_depositId = StakerTestBase._stake(_depositor, _amount, _delegatee);
43+
_setDelegateeScoreBelowThreshold(_delegatee);
44+
}
45+
46+
/// @notice Helper to set a delegatee's score above the eligibility threshold.
47+
/// @dev This should be called after a delegatee is known but before checking their earning power.
48+
/// @param delegatee The address of the delegatee whose score will be set above threshold.
49+
function _setDelegateeScoreAboveThreshold(address delegatee) internal {
50+
vm.startPrank(calculator.scoreOracle());
51+
calculator.updateDelegateeScore(delegatee, calculator.delegateeEligibilityThresholdScore() + 1);
52+
vm.stopPrank();
53+
}
54+
55+
/// @notice A test helper that wraps calling the `stake` function and ensures proper earning power
56+
/// adjustment when delegatee scores are above threshold.
57+
/// @param _depositor The address of the depositor.
58+
/// @param _amount The amount to stake.
59+
/// @param _delegatee The address to which the delegation surrogate is delegating voting power.
60+
/// @return _depositId The id of the created deposit.
61+
function _stakeAboveThreshold(address _depositor, uint256 _amount, address _delegatee)
62+
internal
63+
virtual
64+
returns (Staker.DepositIdentifier _depositId)
65+
{
66+
_depositId = StakerTestBase._stake(_depositor, _amount, _delegatee);
67+
68+
_setDelegateeScoreAboveThreshold(_delegatee);
69+
vm.startPrank(_depositor);
70+
staker.bumpEarningPower(_depositId, _depositor, 0);
71+
vm.stopPrank();
72+
}
73+
}
74+
75+
abstract contract StakeBase is StakedBinaryEligibilityOracleEarningPowerCalculatorTestBase {
76+
function testFuzz_StakerEarnsZeroRewardsWhenDelegateeScoreIsBelowThreshold(
77+
address _depositor,
78+
uint96 _amount,
79+
address _delegatee,
80+
uint256 _rewardAmount,
81+
uint256 _percentDuration
82+
) public {
83+
_assumeNotZeroAddressOrStaker(_depositor);
84+
vm.assume(_delegatee != address(0) && _amount != 0);
85+
86+
_mintStakeToken(_depositor, _amount);
87+
_rewardAmount = _boundToRealisticReward(_rewardAmount);
88+
_percentDuration = bound(_percentDuration, 1, 100);
89+
90+
Staker.DepositIdentifier _depositId = _stakeBelowThreshold(_depositor, _amount, _delegatee);
91+
92+
_notifyRewardAmount(_rewardAmount);
93+
_jumpAheadByPercentOfRewardDuration(_percentDuration);
94+
95+
uint256 unclaimedRewards = staker.unclaimedReward(_depositId);
96+
97+
assertEq(unclaimedRewards, 0);
98+
}
99+
}
100+
101+
abstract contract WithdrawBase is StakedBinaryEligibilityOracleEarningPowerCalculatorTestBase {
102+
function testFuzz_OnlyStakeWithEligibleEarningPowerClaimsRewardAfterDuration(
103+
address _depositor1,
104+
address _depositor2,
105+
uint96 _amount1,
106+
uint96 _amount2,
107+
address _delegatee1,
108+
address _delegatee2,
109+
uint256 _rewardAmount,
110+
uint256 _percentDuration
111+
) public {
112+
_assumeNotZeroAddressOrStaker(_depositor1);
113+
_assumeNotZeroAddressOrStaker(_depositor2);
114+
vm.assume(_depositor1 != _depositor2);
115+
vm.assume(_delegatee1 != address(0) && _delegatee2 != address(0) && _delegatee1 != _delegatee2);
116+
117+
_amount1 = uint96(_boundMintAmount(_amount1));
118+
_amount2 = uint96(_boundMintAmount(_amount2));
119+
vm.assume(_amount1 != 0 && _amount2 != 0);
120+
121+
_mintStakeToken(_depositor1, _amount1);
122+
_mintStakeToken(_depositor2, _amount2);
123+
124+
Staker.DepositIdentifier _depositId1 = _stakeBelowThreshold(_depositor1, _amount1, _delegatee1);
125+
Staker.DepositIdentifier _depositId2 = _stakeAboveThreshold(_depositor2, _amount2, _delegatee2);
126+
127+
_rewardAmount = _boundToRealisticReward(_rewardAmount);
128+
_notifyRewardAmount(_rewardAmount);
129+
_percentDuration = bound(_percentDuration, 1, 100);
130+
_jumpAheadByPercentOfRewardDuration(_percentDuration);
131+
132+
Staker.Deposit memory _deposit2 = _fetchDeposit(_depositId2);
133+
uint256 _calculatedRewards2 =
134+
_calculateEarnedRewards(_deposit2.earningPower, _rewardAmount, _percentDuration);
135+
136+
_withdraw(_depositor1, _depositId1, _amount1);
137+
_withdraw(_depositor2, _depositId2, _amount2);
138+
139+
vm.prank(_depositor1);
140+
uint256 _actualReward1 = staker.claimReward(_depositId1);
141+
142+
vm.prank(_depositor2);
143+
uint256 _actualReward2 = staker.claimReward(_depositId2);
144+
145+
assertEq(0, _actualReward1);
146+
assertApproxEqAbs(_calculatedRewards2, _actualReward2, 1);
147+
}
148+
}

0 commit comments

Comments
 (0)