Skip to content

Commit 56ea897

Browse files
committed
Separate smoothing pool ETH and voter share ETH from rewards
1 parent 399c7ba commit 56ea897

File tree

10 files changed

+185
-115
lines changed

10 files changed

+185
-115
lines changed

contracts/contract/rewards/RocketMerkleDistributorMainnet.sol

Lines changed: 71 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -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.
1818
contract 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
}

contracts/contract/upgrade/RocketUpgradeOneDotFour.sol

Lines changed: 22 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,8 @@ interface InitialiseInterface {
1111

1212
/// @notice v1.4 Saturn 1 upgrade contract
1313
contract RocketUpgradeOneDotFour is RocketBase {
14-
1514
// Whether the upgrade has been performed or not
16-
bool public executed;
15+
bool internal executed;
1716

1817
// Upgrade contracts
1918
address public immutable rocketMegapoolDelegate;
@@ -47,22 +46,19 @@ contract RocketUpgradeOneDotFour is RocketBase {
4746
address public immutable rocketClaimDAO;
4847
address public immutable rocketMinipoolBonderReducer;
4948
address public immutable rocketNetworkVoting;
49+
address public immutable rocketMerkleDistributorMainnet;
5050

5151
// Upgrade ABIs
52-
string[31] public abis;
53-
54-
// Save deployer to limit access to set functions
55-
address immutable internal deployer;
52+
string[32] public abis;
5653

5754
// Construct
5855
constructor(
5956
RocketStorageInterface _rocketStorageAddress,
60-
address[31] memory _addresses,
61-
string[31] memory _abis
57+
address[32] memory _addresses,
58+
string[32] memory _abis
6259
) RocketBase(_rocketStorageAddress) {
6360
// Version
6461
version = 1;
65-
deployer = msg.sender;
6662

6763
// Set contract addresses
6864
rocketMegapoolDelegate = _addresses[0];
@@ -96,6 +92,7 @@ contract RocketUpgradeOneDotFour is RocketBase {
9692
rocketClaimDAO = _addresses[28];
9793
rocketMinipoolBonderReducer = _addresses[29];
9894
rocketNetworkVoting = _addresses[30];
95+
rocketMerkleDistributorMainnet = _addresses[31];
9996

10097
// Set ABIs
10198
abis = _abis;
@@ -108,7 +105,7 @@ contract RocketUpgradeOneDotFour is RocketBase {
108105

109106
/// @notice Once this contract has been voted in by oDAO, guardian can perform the upgrade
110107
function execute() external onlyGuardian {
111-
require(!executed, "Already executed");
108+
require(!executed);
112109
executed = true;
113110

114111
// Add new contracts
@@ -145,6 +142,10 @@ contract RocketUpgradeOneDotFour is RocketBase {
145142
_upgradeContract("rocketClaimDAO", rocketClaimDAO, abis[28]);
146143
_upgradeContract("rocketMinipoolBondReducer", rocketMinipoolBonderReducer, abis[29]);
147144
_upgradeContract("rocketNetworkVoting", rocketNetworkVoting, abis[30]);
145+
_upgradeContract("rocketMerkleDistributorMainnet", rocketMerkleDistributorMainnet, abis[31]);
146+
147+
// Initialise the rewards relay address
148+
InitialiseInterface(rocketMerkleDistributorMainnet).initialise();
148149

149150
// Initialise the megapool factory
150151
InitialiseInterface(rocketMegapoolFactory).initialise();
@@ -213,13 +214,13 @@ contract RocketUpgradeOneDotFour is RocketBase {
213214
function _upgradeContract(string memory _name, address _contractAddress, string memory _contractAbi) internal {
214215
// Get old contract address & check contract exists
215216
address oldContractAddress = getAddress(keccak256(abi.encodePacked("contract.address", _name)));
216-
require(oldContractAddress != address(0x0), "Contract does not exist");
217+
require(oldContractAddress != address(0x0));
217218
// Check new contract address
218-
require(_contractAddress != address(0x0), "Invalid contract address");
219-
require(_contractAddress != oldContractAddress, "The contract address cannot be set to its current address");
220-
require(!getBool(keccak256(abi.encodePacked("contract.exists", _contractAddress))), "Contract address is already in use");
219+
require(_contractAddress != address(0x0));
220+
require(_contractAddress != oldContractAddress);
221+
require(!getBool(keccak256(abi.encodePacked("contract.exists", _contractAddress))));
221222
// Check ABI isn't empty
222-
require(bytes(_contractAbi).length > 0, "Empty ABI is invalid");
223+
require(bytes(_contractAbi).length > 0);
223224
// Register new contract
224225
setBool(keccak256(abi.encodePacked("contract.exists", _contractAddress)), true);
225226
setString(keccak256(abi.encodePacked("contract.name", _contractAddress)), _name);
@@ -233,17 +234,17 @@ contract RocketUpgradeOneDotFour is RocketBase {
233234
/// @dev Add a new network contract
234235
function _addContract(string memory _name, address _contractAddress, string memory _contractAbi) internal {
235236
// Check contract name
236-
require(bytes(_name).length > 0, "Invalid contract name");
237+
require(bytes(_name).length > 0);
237238
// Cannot add contract if it already exists (use upgradeContract instead)
238-
require(getAddress(keccak256(abi.encodePacked("contract.address", _name))) == address(0x0), "Contract name is already in use");
239+
require(getAddress(keccak256(abi.encodePacked("contract.address", _name))) == address(0x0));
239240
// Cannot add contract if already in use as ABI only
240241
string memory existingAbi = getString(keccak256(abi.encodePacked("contract.abi", _name)));
241-
require(bytes(existingAbi).length == 0, "Contract name is already in use");
242+
require(bytes(existingAbi).length == 0);
242243
// Check contract address
243-
require(_contractAddress != address(0x0), "Invalid contract address");
244-
require(!getBool(keccak256(abi.encodePacked("contract.exists", _contractAddress))), "Contract address is already in use");
244+
require(_contractAddress != address(0x0));
245+
require(!getBool(keccak256(abi.encodePacked("contract.exists", _contractAddress))));
245246
// Check ABI isn't empty
246-
require(bytes(_contractAbi).length > 0, "Empty ABI is invalid");
247+
require(bytes(_contractAbi).length > 0);
247248
// Register contract
248249
setBool(keccak256(abi.encodePacked("contract.exists", _contractAddress)), true);
249250
setString(keccak256(abi.encodePacked("contract.name", _contractAddress)), _name);

0 commit comments

Comments
 (0)