@@ -16,9 +16,8 @@ import "@openzeppelin4/contracts/utils/cryptography/MerkleProof.sol";
1616/// @dev On mainnet, the relay and the distributor are the same contract as there is no need for an intermediate contract to
1717/// handle cross-chain messaging.
1818contract RocketMerkleDistributorMainnet is RocketBase , RocketMerkleDistributorMainnetInterface , RocketVaultWithdrawerInterface {
19-
2019 // Events
21- event RewardsClaimed (address indexed claimer , uint256 [] rewardIndex , uint256 [] amountRPL , uint256 [] amountETH );
20+ event RewardsClaimed (address indexed claimer , Claim [] claims );
2221
2322 // Constants
2423 uint256 constant network = 0 ;
@@ -33,15 +32,25 @@ contract RocketMerkleDistributorMainnet is RocketBase, RocketMerkleDistributorMa
3332 // Construct
3433 constructor (RocketStorageInterface _rocketStorageAddress ) RocketBase (_rocketStorageAddress) {
3534 // Version
36- version = 2 ;
35+ version = 3 ;
3736 // Precompute keys
3837 rocketVaultKey = keccak256 (abi.encodePacked ("contract.address " , "rocketVault " ));
3938 rocketTokenRPLKey = keccak256 (abi.encodePacked ("contract.address " , "rocketTokenRPL " ));
39+ }
40+
41+ /// @notice Used following an upgrade or new deployment to initialise the delegate list
42+ function initialise () external override {
43+ // On new deploy, allow guardian to initialise, otherwise, only a network contract
44+ if (rocketStorage.getDeployedStatus ()) {
45+ require (getBool (keccak256 (abi.encodePacked ("contract.exists " , msg .sender ))), "Invalid or outdated network contract " );
46+ } else {
47+ require (msg .sender == rocketStorage.getGuardian (), "Not guardian " );
48+ }
4049 // Set this contract as the relay for network 0
4150 setAddress (keccak256 (abi.encodePacked ("rewards.relay.address " , uint256 (0 ))), address (this ));
4251 }
4352
44- // Called by RocketRewardsPool to include a snapshot into this distributor
53+ /// @notice Called by RocketRewardsPool to include a snapshot into this distributor
4554 function relayRewards (uint256 _rewardIndex , bytes32 _root , uint256 _rewardsRPL , uint256 _rewardsETH ) external override onlyLatestContract ("rocketMerkleDistributorMainnet " , address (this )) onlyLatestContract ("rocketRewardsPool " , msg .sender ) {
4655 bytes32 key = keccak256 (abi.encodePacked ('rewards.merkle.root ' , _rewardIndex));
4756 require (getBytes32 (key) == bytes32 (0 ));
@@ -58,18 +67,19 @@ contract RocketMerkleDistributorMainnet is RocketBase, RocketMerkleDistributorMa
5867 }
5968 }
6069
61- // Reward recipients can call this method with a merkle proof to claim rewards for one or more reward intervals
62- function claim (address _nodeAddress , uint256 [] calldata _rewardIndex , uint256 [] calldata _amountRPL , uint256 [] calldata _amountETH , bytes32 [][] calldata _merkleProof ) external override {
63- claimAndStake (_nodeAddress, _rewardIndex, _amountRPL, _amountETH, _merkleProof , 0 );
70+ /// @notice Reward recipients can call this method with a merkle proof to claim rewards for one or more reward intervals
71+ function claim (address _nodeAddress , Claim [] calldata _claims ) external override {
72+ claimAndStake (_nodeAddress, _claims , 0 );
6473 }
6574
66- function claimAndStake (address _nodeAddress , uint256 [] calldata _rewardIndex , uint256 [] calldata _amountRPL , uint256 [] calldata _amountETH , bytes32 [][] calldata _merkleProof , uint256 _stakeAmount ) public override {
67- _verifyClaim (_rewardIndex, _nodeAddress, _amountRPL, _amountETH, _merkleProof);
68- _claimAndStake (_nodeAddress, _rewardIndex, _amountRPL, _amountETH, _stakeAmount);
75+ /// @notice Reward recipients can call this method to claim rewards for one or more reward intervals and immediately stake some or all of the claimed RPL
76+ function claimAndStake (address _nodeAddress , Claim[] calldata _claims , uint256 _stakeAmount ) public override {
77+ _verifyClaim (_nodeAddress, _claims);
78+ _claimAndStake (_nodeAddress, _claims, _stakeAmount);
6979 }
7080
71- // Node operators can call this method to claim rewards for one or more reward intervals and specify an amount of RPL to stake at the same time
72- function _claimAndStake (address _nodeAddress , uint256 [] calldata _rewardIndex , uint256 [] calldata _amountRPL , uint256 [] calldata _amountETH , uint256 _stakeAmount ) internal {
81+ /// @notice Node operators can call this method to claim rewards for one or more reward intervals and specify an amount of RPL to stake at the same time
82+ function _claimAndStake (address _nodeAddress , Claim [] calldata _claims , uint256 _stakeAmount ) internal {
7383 // Get contracts
7484 RocketVaultInterface rocketVault = RocketVaultInterface (getAddress (rocketVaultKey));
7585
@@ -100,10 +110,12 @@ contract RocketMerkleDistributorMainnet is RocketBase, RocketMerkleDistributorMa
100110 // Calculate totals
101111 {
102112 uint256 totalAmountRPL = 0 ;
103- uint256 totalAmountETH = 0 ;
104- for (uint256 i = 0 ; i < _rewardIndex.length ; ++ i) {
105- totalAmountRPL = totalAmountRPL + _amountRPL[i];
106- totalAmountETH = totalAmountETH + _amountETH[i];
113+ uint256 totalAmountSmoothingPoolETH = 0 ;
114+ uint256 totalAmountVoterETH = 0 ;
115+ for (uint256 i = 0 ; i < _claims.length ; ++ i) {
116+ totalAmountRPL = totalAmountRPL + _claims[i].amountRPL;
117+ totalAmountSmoothingPoolETH = totalAmountSmoothingPoolETH + _claims[i].amountSmoothingPoolETH;
118+ totalAmountVoterETH = totalAmountVoterETH + _claims[i].amountVoterETH;
107119 }
108120 // Validate input
109121 require (_stakeAmount <= totalAmountRPL, "Invalid stake amount " );
@@ -115,16 +127,29 @@ contract RocketMerkleDistributorMainnet is RocketBase, RocketMerkleDistributorMa
115127 }
116128 }
117129 // Distribute ETH
118- if (totalAmountETH > 0 ) {
119- rocketVault.withdrawEther (totalAmountETH);
120- // Allow up to 10000 gas to send ETH to the withdrawal address
121- (bool result ,) = withdrawalAddress.call {value: totalAmountETH, gas: 10000 }("" );
122- if (! result) {
123- // If the withdrawal address cannot accept the ETH with 10000 gas, add it to their balance to be claimed later at their own expense
124- bytes32 balanceKey = keccak256 (abi.encodePacked ('rewards.eth.balance ' , withdrawalAddress));
125- addUint (balanceKey, totalAmountETH);
126- // Return the ETH to the vault
127- rocketVault.depositEther {value: totalAmountETH}();
130+ if (totalAmountSmoothingPoolETH + totalAmountVoterETH > 0 ) {
131+ rocketVault.withdrawEther (totalAmountSmoothingPoolETH + totalAmountVoterETH);
132+ if (totalAmountSmoothingPoolETH > 0 ) {
133+ // Allow up to 10000 gas to send ETH to the withdrawal address
134+ (bool result ,) = withdrawalAddress.call {value: totalAmountSmoothingPoolETH, gas: 10000 }("" );
135+ if (! result) {
136+ // If the withdrawal address cannot accept the ETH with 10000 gas, add it to their balance to be claimed later at their own expense
137+ bytes32 balanceKey = keccak256 (abi.encodePacked ('rewards.eth.balance ' , withdrawalAddress));
138+ addUint (balanceKey, totalAmountSmoothingPoolETH);
139+ // Return the ETH to the vault
140+ rocketVault.depositEther {value: totalAmountSmoothingPoolETH}();
141+ }
142+ }
143+ if (totalAmountVoterETH > 0 ) {
144+ // Allow up to 10000 gas to send ETH to the RPL withdrawal address
145+ (bool result ,) = rplWithdrawalAddress.call {value: totalAmountVoterETH, gas: 10000 }("" );
146+ if (! result) {
147+ // If the RPL withdrawal address cannot accept the ETH with 10000 gas, add it to their balance to be claimed later at their own expense
148+ bytes32 balanceKey = keccak256 (abi.encodePacked ('rewards.eth.balance ' , rplWithdrawalAddress));
149+ addUint (balanceKey, totalAmountVoterETH);
150+ // Return the ETH to the vault
151+ rocketVault.depositEther {value: totalAmountVoterETH}();
152+ }
128153 }
129154 }
130155 }
@@ -139,10 +164,10 @@ contract RocketMerkleDistributorMainnet is RocketBase, RocketMerkleDistributorMa
139164 }
140165
141166 // Emit event
142- emit RewardsClaimed (_nodeAddress, _rewardIndex, _amountRPL, _amountETH );
167+ emit RewardsClaimed (_nodeAddress, _claims );
143168 }
144169
145- // If ETH was claimed but was unable to be sent to the withdrawal address, it can be claimed via this function
170+ /// @notice If ETH was claimed but was unable to be sent to the withdrawal address, it can be claimed via this function
146171 function claimOutstandingEth () external override {
147172 // Get contracts
148173 RocketVaultInterface rocketVault = RocketVaultInterface (getAddress (rocketVaultKey));
@@ -157,55 +182,55 @@ contract RocketMerkleDistributorMainnet is RocketBase, RocketMerkleDistributorMa
157182 require (result, 'Transfer failed ' );
158183 }
159184
160- // Returns the amount of ETH that can be claimed by a withdrawal address
185+ /// @notice Returns the amount of ETH that can be claimed by a withdrawal address
161186 function getOutstandingEth (address _address ) external override view returns (uint256 ) {
162187 bytes32 balanceKey = keccak256 (abi.encodePacked ('rewards.eth.balance ' , _address));
163188 return getUint (balanceKey);
164189 }
165190
166- // Verifies the given data exists as a leaf nodes for the specified reward interval and marks them as claimed if they are valid
167- // Note: this function is optimised for gas when _rewardIndex is ordered numerically
168- function _verifyClaim (uint256 [] calldata _rewardIndex , address _nodeAddress , uint256 [] calldata _amountRPL , uint256 [] calldata _amountETH , bytes32 [][] calldata _merkleProof ) internal {
191+ /// @notice Verifies the given data exists as a leaf nodes for the specified reward interval and marks them as claimed if they are valid
192+ /// @dev This function is optimised for gas when _rewardIndex is ordered numerically
193+ function _verifyClaim (address _nodeAddress , Claim [] calldata _claim ) internal {
169194 // Set initial parameters to the first reward index in the array
170- uint256 indexWordIndex = _rewardIndex [0 ] / 256 ;
195+ uint256 indexWordIndex = _claim [0 ].rewardIndex / 256 ;
171196 bytes32 claimedWordKey = keccak256 (abi.encodePacked ('rewards.interval.claimed ' , _nodeAddress, indexWordIndex));
172197 uint256 claimedWord = getUint (claimedWordKey);
173198 // Loop over every entry
174- for (uint256 i = 0 ; i < _rewardIndex .length ; ++ i) {
199+ for (uint256 i = 0 ; i < _claim .length ; ++ i) {
175200 // Prevent accidental claim of 0
176- require (_amountRPL [i] > 0 || _amountETH [i] > 0 , "Invalid amount " );
201+ require (_claim [i].amountRPL > 0 || _claim [i].amountSmoothingPoolETH > 0 || _claim[i].amountVoterETH > 0 , "Invalid amount " );
177202 // Check if this entry has a different word index than the previous
178- if (indexWordIndex != _rewardIndex [i] / 256 ) {
203+ if (indexWordIndex != _claim [i].rewardIndex / 256 ) {
179204 // Store the previous word
180205 setUint (claimedWordKey, claimedWord);
181206 // Load the word for this entry
182- indexWordIndex = _rewardIndex [i] / 256 ;
207+ indexWordIndex = _claim [i].rewardIndex / 256 ;
183208 claimedWordKey = keccak256 (abi.encodePacked ('rewards.interval.claimed ' , _nodeAddress, indexWordIndex));
184209 claimedWord = getUint (claimedWordKey);
185210 }
186211 // Calculate the bit index for this entry
187- uint256 indexBitIndex = _rewardIndex [i] % 256 ;
212+ uint256 indexBitIndex = _claim [i].rewardIndex % 256 ;
188213 // Ensure the bit is not yet set on this word
189214 uint256 mask = (1 << indexBitIndex);
190215 require (claimedWord & mask != mask, "Already claimed " );
191216 // Verify the merkle proof
192- require (_verifyProof (_rewardIndex[i], _nodeAddress, _amountRPL[i], _amountETH[i], _merkleProof [i]), "Invalid proof " );
217+ require (_verifyProof (_nodeAddress, _claim [i]), "Invalid proof " );
193218 // Set the bit for the current reward index
194219 claimedWord = claimedWord | (1 << indexBitIndex);
195220 }
196221 // Store the word
197222 setUint (claimedWordKey, claimedWord);
198223 }
199224
200- // Verifies that the given proof is valid
201- function _verifyProof (uint256 _rewardIndex , address _nodeAddress , uint256 _amountRPL , uint256 _amountETH , bytes32 [] calldata _merkleProof ) internal view returns (bool ) {
202- bytes32 node = keccak256 (abi.encodePacked (_nodeAddress, network, _amountRPL, _amountETH ));
203- bytes32 key = keccak256 (abi.encodePacked ('rewards.merkle.root ' , _rewardIndex ));
225+ /// @notice Verifies that the given proof is valid
226+ function _verifyProof (address _nodeAddress , Claim calldata _claim ) internal view returns (bool ) {
227+ bytes32 node = keccak256 (abi.encodePacked (_nodeAddress, network, _claim.amountRPL, _claim.amountSmoothingPoolETH, _claim.amountVoterETH ));
228+ bytes32 key = keccak256 (abi.encodePacked ('rewards.merkle.root ' , _claim.rewardIndex ));
204229 bytes32 merkleRoot = getBytes32 (key);
205- return MerkleProof.verify (_merkleProof , merkleRoot, node);
230+ return MerkleProof.verify (_claim.merkleProof , merkleRoot, node);
206231 }
207232
208- // Returns true if the given claimer has claimed for the given reward interval
233+ /// @notice Returns true if the given claimer has claimed for the given reward interval
209234 function isClaimed (uint256 _rewardIndex , address _claimer ) public override view returns (bool ) {
210235 uint256 indexWordIndex = _rewardIndex / 256 ;
211236 uint256 indexBitIndex = _rewardIndex % 256 ;
@@ -214,6 +239,6 @@ contract RocketMerkleDistributorMainnet is RocketBase, RocketMerkleDistributorMa
214239 return claimedWord & mask == mask;
215240 }
216241
217- // Allow receiving ETH from RocketVault, no action required
242+ /// @notice Allow receiving ETH from RocketVault, no action required
218243 function receiveVaultWithdrawalETH () external override payable {}
219244}
0 commit comments