@@ -9,6 +9,8 @@ import {Hoku} from "./Hoku.sol";
99
1010import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/contracts/access/OwnableUpgradeable.sol " ;
1111import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/contracts/proxy/utils/UUPSUpgradeable.sol " ;
12+
13+ import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol " ;
1214import {UD60x18, ud} from "@prb/math/UD60x18.sol " ;
1315
1416/// @title ValidatorRewarder
@@ -17,6 +19,7 @@ import {UD60x18, ud} from "@prb/math/UD60x18.sol";
1719/// @dev The rewarder is called by the subnet actor when a validator claims rewards.
1820contract ValidatorRewarder is IValidatorRewarder , UUPSUpgradeable , OwnableUpgradeable {
1921 using SubnetIDHelper for SubnetID;
22+ using SafeERC20 for Hoku;
2023
2124 // ========== STATE VARIABLES ==========
2225
@@ -30,35 +33,44 @@ contract ValidatorRewarder is IValidatorRewarder, UUPSUpgradeable, OwnableUpgrad
3033 Hoku public token;
3134
3235 /// @notice The latest checkpoint height that rewards can be claimed for
36+ /// @dev Using uint64 to match Filecoin's epoch height type and save gas when interacting with the network
3337 uint64 public latestClaimedCheckpoint;
3438
35- /// @notice The inflation rate for the subnet
36- /// @dev The rate is expressed as a decimal*1e18.
37- /// @dev For example 5% APY is 0.0000928276004952% yield per checkpoint period.
38- /// @dev This is expressed as 928_276_004_952 or 0.000000928276004952*1e18.
39- uint256 public inflationRate;
40-
4139 /// @notice The bottomup checkpoint period for the subnet.
4240 /// @dev The checkpoint period is set when the subnet is created.
4341 uint256 public checkpointPeriod;
4442
4543 /// @notice The supply of HOKU tokens at each checkpoint
46- mapping (uint64 => uint256 ) public checkpointToSupply;
44+ mapping (uint64 checkpointHeight = > uint256 totalSupply ) public checkpointToSupply;
45+
46+ /// @notice The inflation rate for the subnet
47+ /// @dev The rate is expressed as a decimal*1e18.
48+ /// @dev For example 5% APY is 0.0000928276004952% yield per checkpoint period.
49+ /// @dev This is expressed as 928_276_004_952 or 0.000000928276004952*1e18.
50+ uint256 public constant INFLATION_RATE = 928_276_004_952 ;
4751
4852 // ========== EVENTS & ERRORS ==========
4953
5054 event ActiveStateChange (bool active , address account );
55+ event SubnetUpdated (SubnetID subnet , uint256 checkpointPeriod );
56+ event CheckpointClaimed (uint64 indexed checkpointHeight , address indexed validator , uint256 amount );
5157
5258 error SubnetMismatch (SubnetID id );
5359 error InvalidClaimNotifier (address notifier );
5460 error InvalidCheckpointHeight (uint64 claimedCheckpointHeight );
5561 error InvalidCheckpointPeriod (uint256 period );
62+ error InvalidTokenAddress (address token );
63+ error InvalidValidatorAddress (address validator );
64+ error ContractNotActive ();
5665
5766 // ========== INITIALIZER ==========
5867
5968 /// @notice Initializes the rewarder
6069 /// @param hokuToken The address of the HOKU token contract
6170 function initialize (address hokuToken ) public initializer {
71+ if (hokuToken == address (0 )) {
72+ revert InvalidTokenAddress (hokuToken);
73+ }
6274 __Ownable_init (msg .sender );
6375 __UUPSUpgradeable_init ();
6476 _active = true ;
@@ -75,16 +87,17 @@ contract ValidatorRewarder is IValidatorRewarder, UUPSUpgradeable, OwnableUpgrad
7587 }
7688 subnet = subnetId;
7789 checkpointPeriod = period;
90+ emit SubnetUpdated (subnetId, period);
7891 }
7992
8093 // ========== PUBLIC FUNCTIONS ==========
8194
8295 /// @notice Modifier to ensure the contract is active
8396 modifier whenActive () {
8497 if (! _active) {
85- return ; // Skip execution if not active
98+ revert ContractNotActive ();
8699 }
87- _; // Continue with function execution if active
100+ _;
88101 }
89102
90103 /// @notice Indicates whether the gate is active or not
@@ -100,13 +113,6 @@ contract ValidatorRewarder is IValidatorRewarder, UUPSUpgradeable, OwnableUpgrad
100113 emit ActiveStateChange (active, msg .sender );
101114 }
102115
103- /// @notice Sets the inflation rate for the subnet.
104- /// @dev Only the owner can set the inflation rate, and only when the contract is active
105- /// @param rate The new inflation rate
106- function setInflationRate (uint256 rate ) external onlyOwner whenActive {
107- inflationRate = rate;
108- }
109-
110116 /// @notice Notifies the rewarder that a validator has claimed a reward.
111117 /// @dev Only the subnet actor can notify the rewarder, and only when the contract is active.
112118 /// @param id The subnet that the validator belongs to
@@ -117,6 +123,9 @@ contract ValidatorRewarder is IValidatorRewarder, UUPSUpgradeable, OwnableUpgrad
117123 uint64 claimedCheckpointHeight ,
118124 Consensus.ValidatorData calldata data
119125 ) external override whenActive {
126+ if (data.validator == address (0 )) {
127+ revert InvalidValidatorAddress (data.validator);
128+ }
120129 // Check that the rewarder is responsible for the subnet that the validator is claiming rewards for
121130 if (keccak256 (abi.encode (id)) != keccak256 (abi.encode (subnet))) {
122131 revert SubnetMismatch (id);
@@ -143,25 +152,26 @@ contract ValidatorRewarder is IValidatorRewarder, UUPSUpgradeable, OwnableUpgrad
143152 // Get the current supply of HOKU tokens
144153 uint256 currentSupply = token.totalSupply ();
145154
146- // Set the supply for the checkpoint
155+ // Set the supply for the checkpoint and update latest claimed checkpoint
147156 checkpointToSupply[claimedCheckpointHeight] = currentSupply;
148- // Calculate the inflation amount for __this__ checkpoint
157+ latestClaimedCheckpoint = claimedCheckpointHeight;
158+
159+ // Calculate rewards
149160 uint256 supplyDelta = calculateInflationForCheckpoint (currentSupply);
150- // Calculate the validator's share of the inflation for __this__ checkpoint
151161 uint256 validatorShare = calculateValidatorShare (data.blocksCommitted, supplyDelta);
152- // Mint the supply delta minus current validator's share to the Rewarder
162+
163+ // Perform external interactions after state updates
153164 token.mint (address (this ), supplyDelta - validatorShare);
154- // Mint the validator's share to the validator
155165 token.mint (data.validator, validatorShare);
156- // Update the latest claimable checkpoint.
157- latestClaimedCheckpoint = claimedCheckpointHeight;
166+ emit CheckpointClaimed (claimedCheckpointHeight, data.validator, validatorShare);
158167 } else {
159168 // Calculate the supply delta for the checkpoint
160169 uint256 supplyDelta = calculateInflationForCheckpoint (supplyAtCheckpoint);
161170 // Calculate the validator's share of the supply delta
162171 uint256 validatorShare = calculateValidatorShare (data.blocksCommitted, supplyDelta);
163172 // Transfer the validator's share of the supply delta to the validator
164- token.transfer (data.validator, validatorShare);
173+ token.safeTransfer (data.validator, validatorShare);
174+ emit CheckpointClaimed (claimedCheckpointHeight, data.validator, validatorShare);
165175 }
166176 }
167177
@@ -170,9 +180,9 @@ contract ValidatorRewarder is IValidatorRewarder, UUPSUpgradeable, OwnableUpgrad
170180 /// @notice The internal method to calculate the supply delta for a checkpoint
171181 /// @param supply The token supply at the checkpoint
172182 /// @return The supply delta, i.e. the amount of new tokens minted for the checkpoint
173- function calculateInflationForCheckpoint (uint256 supply ) internal view returns (uint256 ) {
183+ function calculateInflationForCheckpoint (uint256 supply ) internal pure returns (uint256 ) {
174184 UD60x18 supplyFixed = ud (supply);
175- UD60x18 inflationRateFixed = ud (inflationRate );
185+ UD60x18 inflationRateFixed = ud (INFLATION_RATE );
176186 UD60x18 result = supplyFixed.mul (inflationRateFixed);
177187 return result.unwrap ();
178188 }
0 commit comments