diff --git a/protocol-contracts/staking/contracts/OperatorRewarder.sol b/protocol-contracts/staking/contracts/OperatorRewarder.sol index 7fc9d1df19..b2fabc27d7 100644 --- a/protocol-contracts/staking/contracts/OperatorRewarder.sol +++ b/protocol-contracts/staking/contracts/OperatorRewarder.sol @@ -35,19 +35,35 @@ contract OperatorRewarder { mapping(address => int256) private _rewardsPaid; mapping(address => address) private _authorizedClaimers; - /// @notice Emitted when the beneficiary is transferred. + /** + * @notice Emitted when the beneficiary is transferred. + * @param oldBeneficiary The previous beneficiary address. + * @param newBeneficiary The new beneficiary address. + */ event BeneficiaryTransferred(address oldBeneficiary, address newBeneficiary); /// @notice Emitted when the contract is shut down. event Shutdown(); - /// @notice Emitted when the maximum fee is updated. + /** + * @notice Emitted when the maximum fee is updated. + * @param oldFee The previous maximum fee in basis points. + * @param newFee The new maximum fee in basis points. + */ event MaxFeeUpdated(uint16 oldFee, uint16 newFee); - /// @notice Emitted when the fee is updated. + /** + * @notice Emitted when the fee is updated. + * @param oldFee The previous fee in basis points. + * @param newFee The new fee in basis points. + */ event FeeUpdated(uint16 oldFee, uint16 newFee); - /// @notice Emitted when an address is authorized to claim rewards on behalf of the receiver address. + /** + * @notice Emitted when an address is authorized to claim rewards on behalf of the receiver address. + * @param receiver The address that will receive the rewards. + * @param claimer The address authorized to claim rewards on behalf of the receiver. + */ event ClaimerAuthorized(address receiver, address claimer); /// @notice Error for invalid claimer address. @@ -326,19 +342,34 @@ contract OperatorRewarder { return SafeCast.toUint256(SignedMath.max(0, allocation - _rewardsPaid[account])); } + /** + * @notice Returns the total historical rewards distributed. + * @dev This amount is computed as the sum of: + * - the total rewards accumulated in the contract (see `_totalAssetsPlusPaidRewards()`) from + * the start of the contract + * - minus the fees not yet claimed by the beneficiary (see `_unpaidFee()`) + * @return The total historical rewards amount. + */ function historicalReward() public view virtual returns (uint256) { uint256 totalAssetsPlusPaidRewards = _totalAssetsPlusPaidRewards(); return totalAssetsPlusPaidRewards - _unpaidFee(totalAssetsPlusPaidRewards); } /** - * @notice Returns unpaid fee. + * @notice Returns unpaid fee (not yet claimed by the beneficiary). * @return Amount of unpaid fee. */ function unpaidFee() public view virtual returns (uint256) { return _unpaidFee(_totalAssetsPlusPaidRewards()); } + /** + * @notice Transfers the specified amount of tokens to the specified address. + * @dev If the amount to transfer is greater than the balance of the rewarder, it will first + * claim rewards from the ProtocolStaking contract. + * @param to The address to transfer the tokens to. + * @param amount The amount of tokens to transfer. + */ function _doTransferOut(address to, uint256 amount) internal { IERC20 token_ = token(); if (amount > token_.balanceOf(address(this))) { @@ -370,7 +401,12 @@ contract OperatorRewarder { function _claimFee() internal virtual { uint256 totalAssetsPlusPaidRewards = _totalAssetsPlusPaidRewards(); uint256 unpaidFee_ = _unpaidFee(totalAssetsPlusPaidRewards); + + // Update the last claim value used to define the next unpaid fee (see `_unpaidFee()`). + // This amount is exactly the same as `historicalReward()`, but we need to get the unpaid + // fee separately in order to send the fee to the beneficiary below. _lastClaimTotalAssetsPlusPaidRewards = totalAssetsPlusPaidRewards - unpaidFee_; + if (unpaidFee_ > 0) { _doTransferOut(beneficiary(), unpaidFee_); } @@ -410,6 +446,14 @@ contract OperatorRewarder { _feeBasisPoints = basisPoints; } + /** + * @notice Returns the total assets plus earned rewards plus paid rewards. + * @dev This amount is computed as the sum of: + * - the balance of the rewarder contract (includes: total claimed but unpaid rewards + total donation + unpaid fees) + * - the earned rewards by the operator staking contract (total earned but unpaid rewards) + * - the total rewards paid to the delegators (total paid rewards) + * @return Total assets plus earned rewards plus paid rewards. + */ function _totalAssetsPlusPaidRewards() internal view returns (uint256) { return token().balanceOf(address(this)) + @@ -417,12 +461,37 @@ contract OperatorRewarder { _totalRewardsPaid; } + /** + * @notice Returns the unpaid fee. + * @dev This amount is computed as a percentage (defined in basis points) of the amount of rewards + * accumulated since the last time fees were claimed. This works because: + * - claiming fees snapshots the historical rewards amount in the `_lastClaimTotalAssetsPlusPaidRewards` + * value, which monitors all rewards accumulated since the start of the contract by excluding fees. + * - the total rewards amount (`_totalAssetsPlusPaidRewards()`) is computed such as it is always + * increasing, except when fees are claimed (where fees are transferred to the beneficiary). + * This makes sure that the difference between both above values does not take claimed fees + * into account when computing the next unpaid fees. + * @param totalAssetsPlusPaidRewards The total assets plus earned rewards plus paid rewards. + * @return Amount of unpaid fee. + */ function _unpaidFee(uint256 totalAssetsPlusPaidRewards) internal view returns (uint256) { uint256 totalAssetsPlusPaidRewardsDelta = totalAssetsPlusPaidRewards - _lastClaimTotalAssetsPlusPaidRewards; return (totalAssetsPlusPaidRewardsDelta * feeBasisPoints()) / 10_000; } - /// @dev Compute total allocation based on number of shares and total shares. Must take paid rewards into account after. + /** + * @notice Compute total allocation based on number of shares and total shares. + * @param share The number of shares. + * @param total The total number of shares. + * @return The total allocation. + * The allocation corresponds to the rewards (both real and virtual) a user would receive if + * the current weight distribution, with their updated stake, was constant since the deployment + * of the protocol. + * Total reward amount is computed as the sum of: + * - historical rewards: total of all rewards generated up to that point + * - paid virtual rewards: a pool of "virtual" rewards that account for changes in the weight distribution + * Note: the `mulDiv` rounds down: floor(totalRewards * share / total) + */ function _allocation(uint256 share, uint256 total) private view returns (uint256) { return SafeCast.toUint256(SafeCast.toInt256(historicalReward()) + _totalVirtualRewardsPaid).mulDiv(share, total); diff --git a/protocol-contracts/staking/contracts/OperatorStaking.sol b/protocol-contracts/staking/contracts/OperatorStaking.sol index 2f047261d3..b02c292472 100644 --- a/protocol-contracts/staking/contracts/OperatorStaking.sol +++ b/protocol-contracts/staking/contracts/OperatorStaking.sol @@ -47,10 +47,23 @@ contract OperatorStaking is ERC1363Upgradeable, ReentrancyGuardTransient, UUPSUp bytes32 private constant OPERATOR_STAKING_STORAGE_LOCATION = 0x7fc851282090a0d8832502c48739eac98a0856539351f17cb5d5950c860fd200; - /// @dev Emitted when an operator is set or unset for a controller. + /** + * @dev Emitted when an operator is set or unset for a controller. + * @param controller The controller address. + * @param operator The operator address. + * @param approved True if the operator is approved, false if revoked. + */ event OperatorSet(address indexed controller, address indexed operator, bool approved); - /// @dev Emitted when a redeem request is made. + /** + * @dev Emitted when a redeem request is made. + * @param controller The controller address for the redeem request. + * @param owner The owner of the shares being redeemed. + * @param requestId The unique identifier for the redeem request. + * @param sender The address that initiated the redeem request. + * @param shares The number of shares requested to redeem. + * @param releaseTime The timestamp when the shares can be released. + */ event RedeemRequest( address indexed controller, address indexed owner, @@ -60,7 +73,11 @@ contract OperatorStaking is ERC1363Upgradeable, ReentrancyGuardTransient, UUPSUp uint48 releaseTime ); - /// @dev Emitted when the rewarder contract is set. + /** + * @dev Emitted when the rewarder contract is set. + * @param oldRewarder The previous rewarder contract address. + * @param newRewarder The new rewarder contract address. + */ event RewarderSet(address oldRewarder, address newRewarder); /// @dev Thrown when the caller is not the ProtocolStaking's owner. diff --git a/protocol-contracts/staking/contracts/ProtocolStaking.sol b/protocol-contracts/staking/contracts/ProtocolStaking.sol index 5e51d77866..150d288811 100644 --- a/protocol-contracts/staking/contracts/ProtocolStaking.sol +++ b/protocol-contracts/staking/contracts/ProtocolStaking.sol @@ -54,19 +54,53 @@ contract ProtocolStaking is AccessControlDefaultAdminRulesUpgradeable, ERC20Vote bytes32 private constant MANAGER_ROLE = keccak256("MANAGER_ROLE"); bytes32 private constant ELIGIBLE_ACCOUNT_ROLE = keccak256("ELIGIBLE_ACCOUNT_ROLE"); - /// @dev Emitted when tokens are staked by an account. + /** + * @dev Emitted when tokens are staked by an account. + * @param account The address of the account staking tokens. + * @param amount The amount of tokens staked. + */ event TokensStaked(address indexed account, uint256 amount); - /// @dev Emitted when tokens are unstaked by an account. + + /** + * @dev Emitted when tokens are unstaked by an account. + * @param account The address of the account unstaking tokens. + * @param amount The amount of tokens unstaked. + * @param releaseTime The timestamp when the tokens can be released. + */ event TokensUnstaked(address indexed account, uint256 amount, uint48 releaseTime); - /// @dev Emitted when tokens are released to a recipient after the unstaking cooldown period. + + /** + * @dev Emitted when tokens are released to a recipient after the unstaking cooldown period. + * @param recipient The address receiving the released tokens. + * @param amount The amount of tokens released. + */ event TokensReleased(address indexed recipient, uint256 amount); - /// @dev Emitted when rewards of an account are claimed. + + /** + * @dev Emitted when rewards of an account are claimed. + * @param account The address of the account whose rewards are claimed. + * @param recipient The address receiving the claimed rewards. + * @param amount The amount of rewards claimed. + */ event RewardsClaimed(address indexed account, address indexed recipient, uint256 amount); - /// @dev Emitted when the reward rate is updated. + + /** + * @dev Emitted when the reward rate is updated. + * @param rewardRate The new reward rate in tokens per second. + */ event RewardRateSet(uint256 rewardRate); - /// @dev Emitted when the unstake cooldown is updated. + + /** + * @dev Emitted when the unstake cooldown is updated. + * @param unstakeCooldownPeriod The new unstake cooldown period in seconds. + */ event UnstakeCooldownPeriodSet(uint256 unstakeCooldownPeriod); - /// @dev Emitted when the reward recipient of an account is updated. + + /** + * @dev Emitted when the reward recipient of an account is updated. + * @param account The address of the account whose reward recipient is updated. + * @param recipient The new reward recipient address. + */ event RewardsRecipientSet(address indexed account, address indexed recipient); /// @dev The account cannot be made eligible. @@ -81,7 +115,17 @@ contract ProtocolStaking is AccessControlDefaultAdminRulesUpgradeable, ERC20Vote _disableInitializers(); } - /// @dev Initializes this upgradeable protocol staking contract. + /** + * @dev Initializes this upgradeable protocol staking contract. + * @param name The name of the ERC20 token representing staked tokens. + * @param symbol The symbol of the ERC20 token representing staked tokens. + * @param version The version string for EIP712 domain separator. + * @param stakingToken_ The address of the token used for staking and rewards. + * @param governor The address granted the default admin role. + * @param manager The address granted the manager role. + * @param initialUnstakeCooldownPeriod The initial unstake cooldown period in seconds. + * @param initialRewardRate The initial reward rate in tokens per second. + */ function initialize( string memory name, string memory symbol, @@ -230,7 +274,10 @@ contract ProtocolStaking is AccessControlDefaultAdminRulesUpgradeable, ERC20Vote return SafeCast.toUint256(SignedMath.max(0, SafeCast.toInt256(allocation) - $._paid[account])); } - /// @dev Returns the staking token which is used for staking and rewards. + /** + * @dev Returns the staking token which is used for staking and rewards. + * @return The address of the staking token. + */ function stakingToken() public view returns (address) { return _getProtocolStakingStorage()._stakingToken; } @@ -244,12 +291,18 @@ contract ProtocolStaking is AccessControlDefaultAdminRulesUpgradeable, ERC20Vote return Math.sqrt(amount); } - /// @dev Returns the current total staked weight. + /** + * @dev Returns the current total staked weight. + * @return The total staked weight of all eligible accounts. + */ function totalStakedWeight() public view returns (uint256) { return _getProtocolStakingStorage()._totalEligibleStakedWeight; } - /// @dev Returns the current unstake cooldown period in seconds. + /** + * @dev Returns the current unstake cooldown period in seconds. + * @return The unstake cooldown period in seconds. + */ function unstakeCooldownPeriod() public view returns (uint256) { return _getProtocolStakingStorage()._unstakeCooldownPeriod; }