Skip to content

Commit

Permalink
Fix for incorrect calculation of ETH matched for legacy minipools and…
Browse files Browse the repository at this point in the history
… other minor corrections
  • Loading branch information
kanewallmann committed Aug 1, 2024
1 parent fb53ec9 commit 7ddbbec
Show file tree
Hide file tree
Showing 12 changed files with 231 additions and 40 deletions.
4 changes: 2 additions & 2 deletions contracts/contract/dao/RocketDAOProposal.sol
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ contract RocketDAOProposal is RocketBase, RocketDAOProposalInterface {
// Events
event ProposalAdded(address indexed proposer, string indexed proposalDAO, uint256 indexed proposalID, bytes payload, uint256 time);
event ProposalVoted(uint256 indexed proposalID, address indexed voter, bool indexed supported, uint256 time);
event ProposalExecuted(uint256 indexed proposalID, address indexed executer, uint256 time);
event ProposalExecuted(uint256 indexed proposalID, address indexed executor, uint256 time);
event ProposalCancelled(uint256 indexed proposalID, address indexed canceller, uint256 time);

// The namespace for any data stored in the trusted node DAO (do not change)
Expand All @@ -34,7 +34,7 @@ contract RocketDAOProposal is RocketBase, RocketDAOProposalInterface {
// Construct
constructor(RocketStorageInterface _rocketStorageAddress) RocketBase(_rocketStorageAddress) {
// Version
version = 1;
version = 2;
}


Expand Down
6 changes: 3 additions & 3 deletions contracts/contract/dao/protocol/RocketDAOProtocolProposal.sol
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,15 @@ contract RocketDAOProtocolProposal is RocketBase, RocketDAOProtocolProposalInter
event ProposalAdded(address indexed proposer, uint256 indexed proposalID, bytes payload, uint256 time);
event ProposalVoted(uint256 indexed proposalID, address indexed voter, VoteDirection direction, uint256 votingPower, uint256 time);
event ProposalVoteOverridden(uint256 indexed proposalID, address indexed delegate, address indexed voter, uint256 votingPower, uint256 time);
event ProposalExecuted(uint256 indexed proposalID, address indexed executer, uint256 time);
event ProposalFinalised(uint256 indexed proposalID, address indexed executer, uint256 time);
event ProposalExecuted(uint256 indexed proposalID, address indexed executor, uint256 time);
event ProposalFinalised(uint256 indexed proposalID, address indexed executor, uint256 time);
event ProposalDestroyed(uint256 indexed proposalID, uint256 time);

// The namespace for any data stored in the protocol DAO (do not change)
string constant internal daoProposalNameSpace = "dao.protocol.proposal.";

constructor(RocketStorageInterface _rocketStorageAddress) RocketBase(_rocketStorageAddress) {
version = 1;
version = 2;
}

/*** Proposals **********************/
Expand Down
19 changes: 12 additions & 7 deletions contracts/contract/dao/protocol/RocketDAOProtocolVerifier.sol
Original file line number Diff line number Diff line change
Expand Up @@ -39,17 +39,17 @@ contract RocketDAOProtocolVerifier is RocketBase, RocketDAOProtocolVerifierInter
uint256 constant internal hashOffset = 2;

// Burn address

This comment has been minimized.

Copy link
@0xfornax

0xfornax Aug 18, 2024

Member

Comment needs an update

address constant internal burnAddress = address(0x0000000000000000000000000000000000000000);
uint256 constant internal bondBurnPercent = 0.2 ether;

// Events
event RootSubmitted(uint256 indexed proposalId, address indexed proposer, uint32 blockNumber, uint256 index, Types.Node root, Types.Node[] treeNodes, uint256 timestamp);
event RootSubmitted(uint256 indexed proposalID, address indexed proposer, uint32 blockNumber, uint256 index, Types.Node root, Types.Node[] treeNodes, uint256 timestamp);
event ChallengeSubmitted(uint256 indexed proposalID, address indexed challenger, uint256 index, uint256 timestamp);
event ProposalBondBurned(uint256 indexed proposalID, address indexed proposer, uint256 amount, uint256 timestamp);

// Construct
constructor(RocketStorageInterface _rocketStorageAddress) RocketBase(_rocketStorageAddress) {
// Version
version = 1;
version = 2;
}

/// @notice Returns the depth per round
Expand Down Expand Up @@ -157,7 +157,7 @@ contract RocketDAOProtocolVerifier is RocketBase, RocketDAOProtocolVerifierInter
// Unlock and burn
RocketNodeStakingInterface rocketNodeStaking = RocketNodeStakingInterface(getContractAddress("rocketNodeStaking"));
rocketNodeStaking.unlockRPL(proposer, proposalBond);
rocketNodeStaking.transferRPL(proposer, burnAddress, proposalBond);
rocketNodeStaking.burnRPL(proposer, proposalBond);
// Log it
emit ProposalBondBurned(_proposalID, proposer, proposalBond, block.timestamp);
}
Expand Down Expand Up @@ -328,16 +328,18 @@ contract RocketDAOProtocolVerifier is RocketBase, RocketDAOProtocolVerifierInter
uint256 nodeCount = getUint(bytes32(proposalKey + nodeCountOffset));
uint256 totalDefeatingIndices = getRoundsFromIndex(defeatIndex, nodeCount);
uint256 totalReward = proposalBond * rewardedIndices / totalDefeatingIndices;
uint256 burnAmount = totalReward * bondBurnPercent / calcBase;
// Unlock the reward amount from the proposer and transfer it to the challenger
address proposer = getAddress(bytes32(proposalKey + proposerOffset));
rocketNodeStaking.unlockRPL(proposer, totalReward);
rocketNodeStaking.transferRPL(proposer, msg.sender, totalReward);
rocketNodeStaking.burnRPL(proposer, burnAmount);
rocketNodeStaking.transferRPL(proposer, msg.sender, totalReward - burnAmount);
}
}

/// @notice Called by a proposer to claim bonds (both refunded bond and any rewards paid)
/// @param _proposalID The ID of the proposal
/// @param _indices An array of indices which the challenger has a claim against
/// @param _indices An array of indices which the proposer has a claim against
function claimBondProposer(uint256 _proposalID, uint256[] calldata _indices) external onlyLatestContract("rocketDAOProtocolVerifier", address(this)) onlyRegisteredNode(msg.sender) {
uint256 defeatIndex = getUint(bytes32(uint256(keccak256(abi.encodePacked("dao.protocol.proposal", _proposalID)))+defeatIndexOffset));

Expand Down Expand Up @@ -367,6 +369,8 @@ contract RocketDAOProtocolVerifier is RocketBase, RocketDAOProtocolVerifierInter
// Get staking contract
RocketNodeStakingInterface rocketNodeStaking = RocketNodeStakingInterface(getContractAddress("rocketNodeStaking"));

uint256 burnPerChallenge = challengeBond * bondBurnPercent / calcBase;

for (uint256 i = 0; i < _indices.length; ++i) {
// Check the challenge of the given index was responded to
bytes32 challengeKey = keccak256(abi.encodePacked("dao.protocol.proposal.challenge", _proposalID, _indices[i]));
Expand All @@ -386,7 +390,8 @@ contract RocketDAOProtocolVerifier is RocketBase, RocketDAOProtocolVerifierInter
// Unlock the challenger bond and pay to proposer
address challenger = getChallengeAddress(state);
rocketNodeStaking.unlockRPL(challenger, challengeBond);
rocketNodeStaking.transferRPL(challenger, proposer, challengeBond);
rocketNodeStaking.transferRPL(challenger, proposer, challengeBond - burnPerChallenge);
rocketNodeStaking.burnRPL(challenger, burnPerChallenge);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ import "../../../../interface/dao/protocol/settings/RocketDAOProtocolSettingsAuc
contract RocketDAOProtocolSettingsAuction is RocketDAOProtocolSettings, RocketDAOProtocolSettingsAuctionInterface {

constructor(RocketStorageInterface _rocketStorageAddress) RocketDAOProtocolSettings(_rocketStorageAddress, "auction") {
version = 2;
// Initialize settings on deployment
version = 3;
// Initialise settings on deployment
if(!getBool(keccak256(abi.encodePacked(settingNameSpace, "deployed")))) {
// Apply settings
setSettingBool("auction.lot.create.enabled", true);
Expand All @@ -36,8 +36,8 @@ contract RocketDAOProtocolSettingsAuction is RocketDAOProtocolSettings, RocketDA
// >= 1 RPL (RPIP-33)
require(_value >= 1 ether, "Value must be >= 1 RPL");
} else if(settingKey == keccak256(abi.encodePacked("auction.lot.duration"))) {
// >= 1 day (RPIP-33)
require(_value >= 1 days, "Value must be >= 1 day");
// >= 1 day (RPIP-33) (approximated by blocks)
require(_value >= 7200, "Value must be >= 7200");
} else if(settingKey == keccak256(abi.encodePacked("auction.price.start"))) {
// >= 10% (RPIP-33)
require(_value >= 0.1 ether, "Value must be >= 10%");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import "../../../../interface/dao/protocol/settings/RocketDAOProtocolSettingsPro
contract RocketDAOProtocolSettingsProposals is RocketDAOProtocolSettings, RocketDAOProtocolSettingsProposalsInterface {

constructor(RocketStorageInterface _rocketStorageAddress) RocketDAOProtocolSettings(_rocketStorageAddress, "proposals") {
version = 1;
version = 2;
// Initialize settings on deployment
if(!getBool(keccak256(abi.encodePacked(settingNameSpace, "deployed")))) {
// Init settings
Expand Down Expand Up @@ -53,11 +53,11 @@ contract RocketDAOProtocolSettingsProposals is RocketDAOProtocolSettings, Rocket
// Must be at least 30 minutes (RPIP-33)
require(_value >= 30 minutes, "Value must be at least 30 minutes");
} else if(settingKey == keccak256(bytes("proposal.quorum"))) {
// Must be >= 51% & < 75% (RPIP-33)
require(_value >= 0.51 ether && _value < 0.75 ether, "Value must be >= 51% & < 75%");
// Must be >= 15% & < 75%
require(_value >= 0.15 ether && _value < 0.75 ether, "Value must be >= 51% & < 75%");
} else if(settingKey == keccak256(bytes("proposal.veto.quorum"))) {
// Must be >= 51% & < 75% (RPIP-33)
require(_value >= 0.51 ether && _value < 0.75 ether, "Value must be >= 51% & < 75%");
// Must be >= 15% & < 75%
require(_value >= 0.15 ether && _value < 0.75 ether, "Value must be >= 51% & < 75%");
} else if(settingKey == keccak256(bytes("proposal.max.block.age"))) {
// Must be > 128 blocks & < 7200 blocks (RPIP-33)
require(_value > 128 && _value < 7200, "Value must be > 128 blocks & < 7200 blocks");
Expand Down
7 changes: 4 additions & 3 deletions contracts/contract/minipool/RocketMinipoolManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ contract RocketMinipoolManager is RocketBase, RocketMinipoolManagerInterface {
event ReductionCancelled(address indexed minipool, uint256 time);

constructor(RocketStorageInterface _rocketStorageAddress) RocketBase(_rocketStorageAddress) {
version = 4;
version = 5;
}

/// @notice Get the number of minipools in the network
Expand Down Expand Up @@ -380,14 +380,15 @@ contract RocketMinipoolManager is RocketBase, RocketMinipoolManagerInterface {
bytes32 finalisedKey = keccak256(abi.encodePacked("node.minipools.finalised", msg.sender));
require(!getBool(finalisedKey), "Minipool has already been finalised");
setBool(finalisedKey, true);
// Get ETH matched (before adding to finalised count in case of fallback calculation)
RocketNodeStakingInterface rocketNodeStaking = RocketNodeStakingInterface(getContractAddress("rocketNodeStaking"));
uint256 ethMatched = rocketNodeStaking.getNodeETHMatched(_nodeAddress);
// Update the node specific count
addUint(keccak256(abi.encodePacked("node.minipools.finalised.count", _nodeAddress)), 1);
// Update the total count
addUint(keccak256(bytes("minipools.finalised.count")), 1);
// Update ETH matched
RocketNodeStakingInterface rocketNodeStaking = RocketNodeStakingInterface(getContractAddress("rocketNodeStaking"));
RocketNetworkSnapshots rocketNetworkSnapshots = RocketNetworkSnapshots(getContractAddress("rocketNetworkSnapshots"));
uint256 ethMatched = rocketNodeStaking.getNodeETHMatched(_nodeAddress);
ethMatched -= RocketMinipoolInterface(msg.sender).getUserDepositBalance();
bytes32 key = keccak256(abi.encodePacked("eth.matched.node.amount", _nodeAddress));
rocketNetworkSnapshots.push(key, uint224(ethMatched));
Expand Down
31 changes: 28 additions & 3 deletions contracts/contract/node/RocketNodeStaking.sol
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ contract RocketNodeStaking is RocketBase, RocketNodeStakingInterface {
event RPLLocked(address indexed from, uint256 amount, uint256 time);
event RPLUnlocked(address indexed from, uint256 amount, uint256 time);
event RPLTransferred(address indexed from, address indexed to, uint256 amount, uint256 time);
event RPLBurned(address indexed from, uint256 amount, uint256 time);

modifier onlyRPLWithdrawalAddressOrNode(address _nodeAddress) {
// Check that the call is coming from RPL withdrawal address (or node if unset)
Expand All @@ -45,7 +46,7 @@ contract RocketNodeStaking is RocketBase, RocketNodeStakingInterface {
}

constructor(RocketStorageInterface _rocketStorageAddress) RocketBase(_rocketStorageAddress) {
version = 5;
version = 6;

// Precompute keys
totalKey = keccak256(abi.encodePacked("rpl.staked.total.amount"));
Expand Down Expand Up @@ -234,10 +235,15 @@ contract RocketNodeStaking is RocketBase, RocketNodeStakingInterface {
/// @param _nodeAddress The address of the node operator to calculate for
function getNodeETHMatchedLimit(address _nodeAddress) override external view returns (uint256) {
// Load contracts
RocketNetworkPricesInterface rocketNetworkPrices = RocketNetworkPricesInterface(getContractAddress("rocketNetworkPrices"));
RocketDAOProtocolSettingsNodeInterface rocketDAOProtocolSettingsNode = RocketDAOProtocolSettingsNodeInterface(getContractAddress("rocketDAOProtocolSettingsNode"));
// Calculate & return limit
// Retrieve minimum stake parameter
uint256 minimumStakePercent = rocketDAOProtocolSettingsNode.getMinimumPerMinipoolStake();
// When minimum stake is zero, allow unlimited amount of matched ETH
if (minimumStakePercent == 0) {
return type(uint256).max;
}
// Calculate and return limit
RocketNetworkPricesInterface rocketNetworkPrices = RocketNetworkPricesInterface(getContractAddress("rocketNetworkPrices"));
return getNodeRPLStake(_nodeAddress) *rocketNetworkPrices.getRPLPrice() / minimumStakePercent;
}

Expand Down Expand Up @@ -380,6 +386,25 @@ contract RocketNodeStaking is RocketBase, RocketNodeStakingInterface {
emit RPLTransferred(_from, _to, _amount, block.timestamp);
}

/// @notice Burns an amount of RPL staked by a given node operator
/// @param _from The node to burn from
/// @param _amount The amount of RPL to burn
function burnRPL(address _from, uint256 _amount) override external onlyLatestContract("rocketNodeStaking", address(this)) onlyLatestNetworkContract() onlyRegisteredNode(_from) {
// Check sender has enough RPL
require(getNodeRPLStake(_from) >= _amount, "Node has insufficient RPL");
// Decrease the stake amount
decreaseTotalRPLStake(_amount);
decreaseNodeRPLStake(_from, _amount);
// Withdraw the RPL to this contract
IERC20Burnable rplToken = IERC20Burnable(getContractAddress("rocketTokenRPL"));
RocketVaultInterface rocketVault = RocketVaultInterface(getContractAddress("rocketVault"));
rocketVault.withdrawToken(address(this), rplToken, _amount);
// Execute the token burn
rplToken.burn(_amount);
// Emit event
emit RPLBurned(_from, _amount, block.timestamp);
}

/// @notice Withdraw staked RPL back to the node account or withdraw RPL address
/// Can only be called by a node if they have not set their RPL withdrawal address
/// @param _amount The amount of RPL to withdraw
Expand Down
Loading

0 comments on commit 7ddbbec

Please sign in to comment.