Skip to content

Commit

Permalink
Add Unit Tests for Staker-Calculator Interactions
Browse files Browse the repository at this point in the history
This commit introduces a mock calculator with adjustable earning power to expand test coverage, validating staker responses to varied earning scenarios such as scaled and boosted staking amounts.
  • Loading branch information
mikeghen committed Nov 11, 2024
1 parent a90fb14 commit 415b390
Show file tree
Hide file tree
Showing 2 changed files with 176 additions and 0 deletions.
133 changes: 133 additions & 0 deletions test/GovernanceStaker.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,15 @@ import {ERC20VotesMock, ERC20Permit} from "test/mocks/MockERC20Votes.sol";
import {IERC20Errors} from "openzeppelin/interfaces/draft-IERC6093.sol";
import {ERC20Fake} from "test/fakes/ERC20Fake.sol";
import {MockFullEarningPowerCalculator} from "test/mocks/MockFullEarningPowerCalculator.sol";
import {MockConfigurableEarningPowerCalculator} from
"test/mocks/MockConfigurableEarningPowerCalculator.sol";
import {PercentAssertions} from "test/helpers/PercentAssertions.sol";

contract GovernanceStakerTest is Test, PercentAssertions {
ERC20Fake rewardToken;
ERC20VotesMock govToken;
MockFullEarningPowerCalculator earningPowerCalculator;
MockConfigurableEarningPowerCalculator configCalculator;

address admin;
address rewardNotifier;
Expand Down Expand Up @@ -60,6 +63,9 @@ contract GovernanceStakerTest is Test, PercentAssertions {
earningPowerCalculator = new MockFullEarningPowerCalculator();
vm.label(address(earningPowerCalculator), "Full Earning Power Calculator");

configCalculator = new MockConfigurableEarningPowerCalculator();
vm.label(address(configCalculator), "Configurable Earning Power Calculator");

admin = makeAddr("admin");

govStaker = new GovernanceStakerHarness(
Expand Down Expand Up @@ -789,6 +795,95 @@ contract Stake is GovernanceStakerTest {
vm.expectRevert(GovernanceStaker.GovernanceStaker__InvalidAddress.selector);
govStaker.stake(_amount, _delegatee, address(0));
}

function testFuzz_StakeWithReducedEarningPower(
address _depositor,
uint256 _stakeAmount,
uint256 _multiplierBips
) public {
vm.assume(_depositor != address(0));
_stakeAmount = _boundMintAmount(_stakeAmount);
_multiplierBips = bound(_multiplierBips, 1, 20_000);

configCalculator.__setMultiplierBips(_multiplierBips);
govStaker = new GovernanceStakerHarness(
rewardToken, govToken, configCalculator, maxBumpTip, admin, "GovernanceStaker"
);

vm.prank(admin);
govStaker.setRewardNotifier(rewardNotifier, true);

_mintGovToken(_depositor, _stakeAmount);
vm.prank(_depositor);
govToken.approve(address(govStaker), _stakeAmount);

vm.prank(_depositor);
GovernanceStaker.DepositIdentifier depositId =
govStaker.stake(_stakeAmount, _depositor, _depositor);

(,, uint96 actualEarningPower,,,,) = govStaker.deposits(depositId);
uint256 expectedEarningPower = (_stakeAmount * _multiplierBips) / 10_000;
assertEq(actualEarningPower, expectedEarningPower, "Earning power should be reduced");
}

function testFuzz_StakeWithBoostedEarningPower(
address _depositor,
uint256 _stakeAmount,
uint256 _multiplierBips
) public {
vm.assume(_depositor != address(0));
_stakeAmount = _boundMintAmount(_stakeAmount);
_multiplierBips = bound(_multiplierBips, 1, 20_000);

configCalculator.__setMultiplierBips(_multiplierBips);
govStaker = new GovernanceStakerHarness(
rewardToken, govToken, configCalculator, maxBumpTip, admin, "GovernanceStaker"
);

vm.prank(admin);
govStaker.setRewardNotifier(rewardNotifier, true);

_mintGovToken(_depositor, _stakeAmount);
vm.prank(_depositor);
govToken.approve(address(govStaker), _stakeAmount);

vm.prank(_depositor);
GovernanceStaker.DepositIdentifier depositId =
govStaker.stake(_stakeAmount, _depositor, _depositor);

(,, uint96 actualEarningPower,,,,) = govStaker.deposits(depositId);
uint256 expectedEarningPower = (_stakeAmount * _multiplierBips) / 10_000;
assertEq(actualEarningPower, expectedEarningPower, "Earning power should be boosted");
}

function testFuzz_StakeWithFixedEarningPower(
address _depositor,
uint256 _stakeAmount,
uint256 _fixedPower
) public {
vm.assume(_depositor != address(0));
_stakeAmount = _boundMintAmount(_stakeAmount);
_fixedPower = _boundMintAmount(_fixedPower);

configCalculator.__setFixedReturn(_fixedPower);
govStaker = new GovernanceStakerHarness(
rewardToken, govToken, configCalculator, maxBumpTip, admin, "GovernanceStaker"
);

vm.prank(admin);
govStaker.setRewardNotifier(rewardNotifier, true);

_mintGovToken(_depositor, _stakeAmount);
vm.prank(_depositor);
govToken.approve(address(govStaker), _stakeAmount);

vm.prank(_depositor);
GovernanceStaker.DepositIdentifier depositId =
govStaker.stake(_stakeAmount, _depositor, _depositor);

(,, uint96 actualEarningPower,,,,) = govStaker.deposits(depositId);
assertEq(actualEarningPower, _fixedPower, "Earning power should match fixed value");
}
}

contract PermitAndStake is GovernanceStakerTest {
Expand Down Expand Up @@ -2894,6 +2989,44 @@ contract BumpEarningPower is GovernanceStakerRewardsTest {
vm.expectRevert(stdError.arithmeticError);
govStaker.bumpEarningPower(_depositId, _tipReceiver, _requestedTip);
}

function testFuzz_EarningPowerChangesAfterMultiplierUpdate(
address _depositor,
uint256 _stakeAmount,
uint256 _multiplierBips,
uint256 _rewardAmount
) public {
vm.assume(_depositor != address(0));
_stakeAmount = _boundToRealisticStake(_stakeAmount);
_multiplierBips = bound(_multiplierBips, 1, 20_000);
_rewardAmount = bound(_rewardAmount, 1000e18, 10_000_000e18);

// Set up the governance staker with a default multiplier
configCalculator.__setMultiplierBips(10_000);
govStaker = new GovernanceStakerHarness(
rewardToken, govToken, configCalculator, maxBumpTip, admin, "GovernanceStaker"
);

vm.prank(admin);
govStaker.setRewardNotifier(rewardNotifier, true);

(, GovernanceStaker.DepositIdentifier depositId) =
_boundMintAndStake(_depositor, _stakeAmount, _depositor);

_mintTransferAndNotifyReward(_rewardAmount);

_jumpAhead(1 days);

// Update multiplier and bump earning power
configCalculator.__setMultiplierBips(_multiplierBips);
vm.prank(_depositor);
govStaker.bumpEarningPower(depositId, _depositor, 0);

// Verify the earning power update
(,, uint96 actualEarningPower,,,,) = govStaker.deposits(depositId);
uint256 expectedEarningPower = (_stakeAmount * _multiplierBips) / 10_000;
assertEq(actualEarningPower, expectedEarningPower, "Earning power should be updated");
}
}

contract LastTimeRewardDistributed is GovernanceStakerRewardsTest {
Expand Down
43 changes: 43 additions & 0 deletions test/mocks/MockConfigurableEarningPowerCalculator.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity ^0.8.23;

import {IEarningPowerCalculator} from "src/GovernanceStaker.sol";

contract MockConfigurableEarningPowerCalculator is IEarningPowerCalculator {
// multiplier applied to all earning power calculations (in basis points)
// 10000 = 100% (no change), 5000 = 50%, 20000 = 200% etc
uint256 public multiplierBips = 10_000;

// Optional fixed return value that overrides multiplier if set
uint256 public fixedReturnValue;
bool public useFixedReturn;

function __setMultiplierBips(uint256 _multiplierBips) external {
multiplierBips = _multiplierBips;
useFixedReturn = false;
}

function __setFixedReturn(uint256 _value) external {
fixedReturnValue = _value;
useFixedReturn = true;
}

function getEarningPower(
uint256 _amountStaked,
address, // _staker,
address // _delegatee
) external view returns (uint256) {
if (useFixedReturn) return fixedReturnValue;
return (_amountStaked * multiplierBips) / 10_000;
}

function getNewEarningPower(
uint256 _amountStaked,
address, // _staker,
address, // _delegatee,
uint256 // _oldEarningPower
) external view returns (uint256, bool) {
if (useFixedReturn) return (fixedReturnValue, true);
return ((_amountStaked * multiplierBips) / 10_000, true);
}
}

0 comments on commit 415b390

Please sign in to comment.