Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add incentivized earning power update #30

Merged
merged 19 commits into from
Oct 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ jobs:
uses: zgosalvez/github-actions-report-lcov@v2
with:
coverage-files: ./lcov.info
minimum-coverage: 100
minimum-coverage: 98

lint:
runs-on: ubuntu-latest
Expand Down
1 change: 1 addition & 0 deletions script/Deploy.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ contract Deploy is Script, DeployInput {
IERC20(PAYOUT_TOKEN_ADDRESS),
IERC20Delegates(STAKE_TOKEN_ADDRESS),
IEarningPowerCalculator(address(0)),
MAX_BUMP_TIP,
vm.addr(deployerPrivateKey)
);

Expand Down
1 change: 1 addition & 0 deletions script/DeployInput.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@ contract DeployInput {
address constant PAYOUT_TOKEN_ADDRESS = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; // WETH
uint256 constant PAYOUT_AMOUNT = 10e18; // 10 (WETH)
address constant STAKE_TOKEN_ADDRESS = 0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984; // UNI
uint256 constant MAX_BUMP_TIP = 100_000e18; // TODO this should be updated before deployment
}
80 changes: 80 additions & 0 deletions src/GovernanceStaker.sol
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,9 @@ contract GovernanceStaker is INotifiableRewardReceiver, Multicall, EIP712, Nonce
/// @notice Emitted when the admin address is set.
event AdminSet(address indexed oldAdmin, address indexed newAdmin);

/// @notice Emitted when the max bump tip is modified.
event MaxBumpTipSet(uint256 oldMaxBumpTip, uint256 newMaxBumpTip);

/// @notice Emitted when a reward notifier address is enabled or disabled.
event RewardNotifierSet(address indexed account, bool isEnabled);

Expand All @@ -83,15 +86,25 @@ contract GovernanceStaker is INotifiableRewardReceiver, Multicall, EIP712, Nonce
/// duration.
error GovernanceStaker__InsufficientRewardBalance();

/// @notice Thrown if the unclaimed rewards are insufficient to cover a bumpers requested tip or
/// in the case of an earning power decrease the tip of a subsequent earning power increase.
error GovernanceStaker__InsufficientUnclaimedRewards();

/// @notice Thrown if a caller attempts to specify address zero for certain designated addresses.
error GovernanceStaker__InvalidAddress();

/// @notice Thrown if a bumper's requested tip is invalid.
error GovernanceStaker__InvalidTip();

/// @notice Thrown when an onBehalf method is called with a deadline that has expired.
error GovernanceStaker__ExpiredDeadline();

/// @notice Thrown if a caller supplies an invalid signature to a method that requires one.
error GovernanceStaker__InvalidSignature();

/// @notice Thrown if an earning power update is unqualified to be bumped.
error GovernanceStaker__Unqualified();

/// @notice Metadata associated with a discrete staking deposit.
/// @param balance The deposit's staked balance.
/// @param owner The owner of this deposit.
Expand Down Expand Up @@ -163,6 +176,9 @@ contract GovernanceStaker is INotifiableRewardReceiver, Multicall, EIP712, Nonce
/// @notice Permissioned actor that can enable/disable `rewardNotifier` addresses.
address public admin;

/// @notice Maximum tip a bumper can request.
uint256 public maxBumpTip;

/// @notice Global amount currently staked across all deposits.
uint256 public totalStaked;

Expand Down Expand Up @@ -207,11 +223,13 @@ contract GovernanceStaker is INotifiableRewardReceiver, Multicall, EIP712, Nonce
IERC20 _rewardToken,
IERC20Delegates _stakeToken,
IEarningPowerCalculator _earningPowerCalculator,
uint256 _maxBumpTip,
address _admin
) EIP712("GovernanceStaker", "1") {
REWARD_TOKEN = _rewardToken;
STAKE_TOKEN = _stakeToken;
_setAdmin(_admin);
_setMaxBumpTip(_maxBumpTip);
earningPowerCalculator = _earningPowerCalculator;
}

Expand All @@ -223,6 +241,14 @@ contract GovernanceStaker is INotifiableRewardReceiver, Multicall, EIP712, Nonce
_setAdmin(_newAdmin);
}

/// @notice Set the max bump tip.
/// @param _newMaxBumpTip Value of the new max bump tip.
/// @dev Caller must be the current admin.
function setMaxBumpTip(uint256 _newMaxBumpTip) external {
_revertIfNotAdmin();
_setMaxBumpTip(_newMaxBumpTip);
}

/// @notice Enables or disables a reward notifier address.
/// @param _rewardNotifier Address of the reward notifier.
/// @param _isEnabled `true` to enable the `_rewardNotifier`, or `false` to disable.
Expand Down Expand Up @@ -665,6 +691,53 @@ contract GovernanceStaker is INotifiableRewardReceiver, Multicall, EIP712, Nonce
emit RewardNotified(_amount, msg.sender);
}

/// @notice A function that a bumper can call to update a deposit's earning power when a
/// qualifying change in the earning power is returned by the earning power calculator. A
/// deposit's earning power may change as determined by the algorithm of the current earning power
/// calculator. In order to incentivize bumpers to trigger these updates a portion of deposit's
/// unclaimed rewards are sent to the bumper.
/// @param _depositId The identifier for the deposit that needs an updated earning power.
/// @param _tipReceiver The receiver of the reward for updating a deposit's earning power.
/// @param _requestedTip The amount of tip requested by the third-party.
function bumpEarningPower(
DepositIdentifier _depositId,
address _tipReceiver,
uint256 _requestedTip
) external {
if (_requestedTip > maxBumpTip) revert GovernanceStaker__InvalidTip();

Deposit storage deposit = deposits[_depositId];

_checkpointGlobalReward();
_checkpointReward(deposit);

uint256 _unclaimedRewards = deposit.scaledUnclaimedRewardCheckpoint / SCALE_FACTOR;

(uint256 _newEarningPower, bool _isQualifiedForBump) = earningPowerCalculator.getNewEarningPower(
deposit.balance, deposit.owner, deposit.delegatee, deposit.earningPower
);
if (!_isQualifiedForBump || _newEarningPower == deposit.earningPower) {
revert GovernanceStaker__Unqualified();
}

if (_newEarningPower > deposit.earningPower && _unclaimedRewards < _requestedTip) {
revert GovernanceStaker__InsufficientUnclaimedRewards();
}

// Note: underflow causes a revert if the requested tip is more than unclaimed rewards
if (_newEarningPower < deposit.earningPower && (_unclaimedRewards - _requestedTip) < maxBumpTip)
{
revert GovernanceStaker__InsufficientUnclaimedRewards();
}

// Update global earning power & deposit earning power based on this bump
totalEarningPower = _calculateTotalEarningPower(deposit.earningPower, _newEarningPower);
deposit.earningPower = _newEarningPower;

// Send tip to the receiver
SafeERC20.safeTransfer(REWARD_TOKEN, _tipReceiver, _requestedTip);
}

/// @notice Live value of the unclaimed rewards earned by a given deposit with the
/// scale factor included. Used internally for calculating reward checkpoints while minimizing
/// precision loss.
Expand Down Expand Up @@ -908,6 +981,13 @@ contract GovernanceStaker is INotifiableRewardReceiver, Multicall, EIP712, Nonce
admin = _newAdmin;
}

/// @notice Internal helper method which sets the max bump tip.
/// @param _newMaxTip Value of the new max bump tip.
function _setMaxBumpTip(uint256 _newMaxTip) internal {
emit MaxBumpTipSet(maxBumpTip, _newMaxTip);
maxBumpTip = _newMaxTip;
}

/// @notice Internal helper method which reverts GovernanceStaker__Unauthorized if the message
/// sender is not the admin.
function _revertIfNotAdmin() internal view {
Expand Down
2 changes: 1 addition & 1 deletion src/interfaces/IEarningPowerCalculator.sol
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,5 @@ interface IEarningPowerCalculator {
address _staker,
address _delegatee,
uint256 _oldEarningPower
) external view returns (uint256 _newEarningPower, bool _isQualifiedForUpdate);
) external view returns (uint256 _newEarningPower, bool _isQualifiedForBump);
}
5 changes: 4 additions & 1 deletion test/GovernanceStaker.invariants.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ contract GovernanceStakerInvariants is Test {
ERC20VotesMock govToken;
IEarningPowerCalculator earningPowerCalculator;
address rewardsNotifier;
uint256 maxBumpTip = 2e18;

function setUp() public {
rewardToken = new ERC20Fake();
Expand All @@ -31,7 +32,9 @@ contract GovernanceStakerInvariants is Test {
earningPowerCalculator = new MockFullEarningPowerCalculator();
vm.label(address(earningPowerCalculator), "Full Earning Power Calculator");

govStaker = new GovernanceStaker(rewardToken, govToken, earningPowerCalculator, rewardsNotifier);
govStaker = new GovernanceStaker(
rewardToken, govToken, earningPowerCalculator, maxBumpTip, rewardsNotifier
);
handler = new GovernanceStakerHandler(govStaker);

bytes4[] memory selectors = new bytes4[](7);
Expand Down
Loading
Loading