diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000..9930459a --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "old"] + path = old + url = git@github.com:rocket-pool/rocketpool.git diff --git a/contracts/contract/dao/protocol/settings/RocketDAOProtocolSettingsDeposit.sol b/contracts/contract/dao/protocol/settings/RocketDAOProtocolSettingsDeposit.sol index bf826420..26c55f7f 100644 --- a/contracts/contract/dao/protocol/settings/RocketDAOProtocolSettingsDeposit.sol +++ b/contracts/contract/dao/protocol/settings/RocketDAOProtocolSettingsDeposit.sol @@ -3,7 +3,7 @@ pragma solidity 0.8.18; import "./RocketDAOProtocolSettings.sol"; import "../../../../interface/dao/protocol/settings/RocketDAOProtocolSettingsDepositInterface.sol"; - + /// @notice Network deposit settings contract RocketDAOProtocolSettingsDeposit is RocketDAOProtocolSettings, RocketDAOProtocolSettingsDepositInterface { diff --git a/contracts/contract/deposit/RocketDepositPool.sol b/contracts/contract/deposit/RocketDepositPool.sol index 361d64c7..7bcb48da 100644 --- a/contracts/contract/deposit/RocketDepositPool.sol +++ b/contracts/contract/deposit/RocketDepositPool.sol @@ -16,16 +16,14 @@ import "../../interface/util/AddressQueueStorageInterface.sol"; import "../../interface/util/LinkedListStorageInterface.sol"; import "../../types/MinipoolDeposit.sol"; import "../RocketBase.sol"; -import {RocketNodeStakingInterface} from "../../interface/node/RocketNodeStakingInterface.sol"; - -import "hardhat/console.sol"; -import {RocketMegapoolFactoryInterface} from "../../interface/megapool/RocketMegapoolFactoryInterface.sol"; +import "../../interface/node/RocketNodeStakingInterface.sol"; +import "../../interface/megapool/RocketMegapoolFactoryInterface.sol"; /// @notice Accepts user deposits and mints rETH; handles assignment of deposited ETH to megapools contract RocketDepositPool is RocketBase, RocketDepositPoolInterface, RocketVaultWithdrawerInterface { // Constants - uint256 private constant milliToWei = 10**15; + uint256 private constant milliToWei = 10 ** 15; bytes32 private constant queueKeyVariable = keccak256("minipools.available.variable"); bytes32 private constant expressQueueNamespace = keccak256("deposit.queue.express"); bytes32 private constant standardQueueNamespace = keccak256("deposit.queue.standard"); @@ -83,8 +81,8 @@ contract RocketDepositPool is RocketBase, RocketDepositPoolInterface, RocketVaul uint256 minipoolCapacity = rocketMinipoolQueue.getEffectiveCapacity(); uint256 balance = getBalance(); // Calculate and return - if (minipoolCapacity >= balance) { return 0; } - else { return balance - minipoolCapacity; } + if (minipoolCapacity >= balance) {return 0;} + else {return balance - minipoolCapacity;} } /// @dev Callback required to receive ETH withdrawal from the vault @@ -113,8 +111,9 @@ contract RocketDepositPool is RocketBase, RocketDepositPoolInterface, RocketVaul // case where capacityNeeded fits in the deposit pool without looking at the queue if (rocketDAOProtocolSettingsDeposit.getAssignDepositsEnabled()) { RocketMinipoolQueueInterface rocketMinipoolQueue = RocketMinipoolQueueInterface(getContractAddress("rocketMinipoolQueue")); - require(capacityNeeded <= maxDepositPoolSize + rocketMinipoolQueue.getEffectiveCapacity(), - "The deposit pool size after depositing (and matching with minipools) exceeds the maximum size"); + uint256 capacity = rocketMinipoolQueue.getEffectiveCapacity(); + capacity += getUint("deposit.pool.requested.total"); + require(capacityNeeded <= maxDepositPoolSize + capacity, "The deposit pool size after depositing exceeds the maximum size"); } else { revert("The deposit pool size after depositing exceeds the maximum size"); } @@ -142,7 +141,8 @@ contract RocketDepositPool is RocketBase, RocketDepositPoolInterface, RocketVaul // When assignments are enabled, we can accept the max amount plus whatever space is available in the minipool queue if (rocketDAOProtocolSettingsDeposit.getAssignDepositsEnabled()) { RocketMinipoolQueueInterface rocketMinipoolQueue = RocketMinipoolQueueInterface(getContractAddress("rocketMinipoolQueue")); - maxCapacity = maxCapacity + rocketMinipoolQueue.getEffectiveCapacity(); + maxCapacity += rocketMinipoolQueue.getEffectiveCapacity(); + maxCapacity += getUint("deposit.pool.requested.total"); } // Check we aren't already over if (depositPoolBalance >= maxCapacity) { @@ -168,7 +168,7 @@ contract RocketDepositPool is RocketBase, RocketDepositPoolInterface, RocketVaul // Withdraw ETH from the vault rocketVault.withdrawEther(_amount); // Send it to msg.sender (function modifier verifies msg.sender is RocketNodeDeposit) - (bool success, ) = address(msg.sender).call{value: _amount}(""); + (bool success,) = address(msg.sender).call{value: _amount}(""); require(success, "Failed to send ETH"); } @@ -223,6 +223,16 @@ contract RocketDepositPool is RocketBase, RocketDepositPoolInterface, RocketVaul return _assignDeposits(rocketDAOProtocolSettingsDeposit); } + /// @dev If deposit assignments are enabled, assigns a single deposit + function maybeAssignOneDeposit() override external onlyThisLatestContract { + RocketDAOProtocolSettingsDepositInterface rocketDAOProtocolSettingsDeposit = RocketDAOProtocolSettingsDepositInterface(getContractAddress("rocketDAOProtocolSettingsDeposit")); + if (!rocketDAOProtocolSettingsDeposit.getAssignDepositsEnabled()) { + return; + } + // TODO: Clarify in RPIP "If possible, deposit SHALL assign one validator as described below" if this means node deposit should assign to legacy minipools too + assignMegapools(1); + } + /// @dev Assigns deposits to available minipools, returns false if assignment is currently disabled function _assignDeposits(RocketDAOProtocolSettingsDepositInterface _rocketDAOProtocolSettingsDeposit) private returns (bool) { // Check if assigning deposits is enabled @@ -234,13 +244,27 @@ contract RocketDepositPool is RocketBase, RocketDepositPoolInterface, RocketVaul if (addressQueueStorage.getLength(queueKeyVariable) > 0) { RocketMinipoolQueueInterface rocketMinipoolQueue = RocketMinipoolQueueInterface(getContractAddress("rocketMinipoolQueue")); _assignDepositsLegacy(rocketMinipoolQueue, _rocketDAOProtocolSettingsDeposit); - return true; + } else { + // Then assign megapools + _assignMegapools(_rocketDAOProtocolSettingsDeposit); } - // Assign megapools - assignMegapools(msg.value / 32 ether); return true; } + function _assignMegapools(RocketDAOProtocolSettingsDepositInterface _rocketDAOProtocolSettingsDeposit) private { + // Load contracts + RocketDAOProtocolSettingsMinipoolInterface rocketDAOProtocolSettingsMinipool = RocketDAOProtocolSettingsMinipoolInterface(getContractAddress("rocketDAOProtocolSettingsMinipool")); + // Calculate the number of minipools to assign + // TODO: Confirm whether we still want to support socialised assignments or whether the RPIP intends for them to be entirely removed (improve gas if removed) + uint256 maxAssignments = _rocketDAOProtocolSettingsDeposit.getMaximumDepositAssignments(); + uint256 scalingCount = msg.value / 32 ether; + uint256 assignments = _rocketDAOProtocolSettingsDeposit.getMaximumDepositSocialisedAssignments() + scalingCount; + if (assignments > maxAssignments) { + assignments = maxAssignments; + } + assignMegapools(assignments); + } + /// @dev Assigns deposits using the legacy minipool queue function _assignDepositsLegacy(RocketMinipoolQueueInterface _rocketMinipoolQueue, RocketDAOProtocolSettingsDepositInterface _rocketDAOProtocolSettingsDeposit) private { // Load contracts @@ -258,7 +282,7 @@ contract RocketDepositPool is RocketBase, RocketDepositPoolInterface, RocketVaul assignments = maxAssignments; } address[] memory minipools = _rocketMinipoolQueue.dequeueMinipools(assignments); - if (minipools.length > 0){ + if (minipools.length > 0) { // Withdraw ETH from vault uint256 totalEther = minipools.length / variableDepositAmount; rocketVault.withdrawEther(totalEther); @@ -307,13 +331,15 @@ contract RocketDepositPool is RocketBase, RocketDepositPoolInterface, RocketVaul // Enqueue megapool bytes32 namespace = getQueueNamespace(_expressQueue); DepositQueueValue memory value = DepositQueueValue({ - receiver: msg.sender, // Megapool address + receiver: msg.sender, // Megapool address validatorId: uint32(_validatorId), // Incrementing id per validator in a megapool suppliedValue: uint32(_bondAmount / milliToWei), // NO bond amount requestedValue: uint32(_amount / milliToWei) // Amount being requested }); LinkedListStorageInterface linkedListStorage = LinkedListStorageInterface(getContractAddress("linkedListStorage")); linkedListStorage.enqueueItem(namespace, value); + // Increase requested balance + addUint("deposit.pool.requested.total", _amount); } function exitQueue(uint256 _validatorId, bool _expressQueue) external onlyRegisteredMegapool(msg.sender) { @@ -330,6 +356,7 @@ contract RocketDepositPool is RocketBase, RocketDepositPoolInterface, RocketVaul /// @notice Assigns funds to megapools at the front of the queue if enough ETH is available /// @param _count The maximum number of megapools to assign in this call + // TODO: Make this only callable internally or via RocketNodeDeposit function assignMegapools(uint256 _count) override public { if (_count == 0) { // Nothing to do @@ -346,13 +373,14 @@ contract RocketDepositPool is RocketBase, RocketDepositPoolInterface, RocketVaul // TODO: Parameterise express_queue_rate uint256 expressQueueRate = 2; + uint256 totalSent = 0; for (uint256 i = 0; i < _count; i++) { if (expressQueueLength == 0 && standardQueueLength == 0) { break; } - bool express = queueIndex % (expressQueueRate+1) != 0; + bool express = queueIndex % (expressQueueRate + 1) != 0; if (express && expressQueueLength == 0) { express = false; @@ -379,6 +407,7 @@ contract RocketDepositPool is RocketBase, RocketDepositPoolInterface, RocketVaul // Account for node balance nodeBalanceUsed += head.suppliedValue; + totalSent += ethRequired; // Update counts for next iteration queueIndex ++; @@ -392,6 +421,7 @@ contract RocketDepositPool is RocketBase, RocketDepositPoolInterface, RocketVaul // Store state changes subUint("deposit.pool.node.balance", nodeBalanceUsed); setUint("megapool.queue.index", queueIndex); + subUint("deposit.pool.requested.total", totalSent); } /// @notice diff --git a/contracts/contract/node/RocketNodeDeposit.sol b/contracts/contract/node/RocketNodeDeposit.sol index ab4326e4..5f5580d2 100644 --- a/contracts/contract/node/RocketNodeDeposit.sol +++ b/contracts/contract/node/RocketNodeDeposit.sol @@ -1,25 +1,25 @@ // SPDX-License-Identifier: GPL-3.0-only pragma solidity 0.8.18; -import {RocketStorageInterface} from "../../interface/RocketStorageInterface.sol"; -import {RocketVaultInterface} from "../../interface/RocketVaultInterface.sol"; -import {RocketDAOProtocolSettingsMinipoolInterface} from "../../interface/dao/protocol/settings/RocketDAOProtocolSettingsMinipoolInterface.sol"; -import {RocketDAOProtocolSettingsNodeInterface} from "../../interface/dao/protocol/settings/RocketDAOProtocolSettingsNodeInterface.sol"; -import {RocketDepositPoolInterface} from "../../interface/deposit/RocketDepositPoolInterface.sol"; -import {RocketMegapoolFactoryInterface} from "../../interface/megapool/RocketMegapoolFactoryInterface.sol"; -import {RocketMegapoolInterface} from "../../interface/megapool/RocketMegapoolInterface.sol"; -import {RocketMinipoolInterface} from "../../interface/minipool/RocketMinipoolInterface.sol"; -import {RocketMinipoolManagerInterface} from "../../interface/minipool/RocketMinipoolManagerInterface.sol"; -import {RocketMinipoolQueueInterface} from "../../interface/minipool/RocketMinipoolQueueInterface.sol"; -import {RocketNetworkFeesInterface} from "../../interface/network/RocketNetworkFeesInterface.sol"; -import {RocketNetworkVotingInterface} from "../../interface/network/RocketNetworkVotingInterface.sol"; -import {RocketNodeDepositInterface} from "../../interface/node/RocketNodeDepositInterface.sol"; -import {RocketNodeManagerInterface} from "../../interface/node/RocketNodeManagerInterface.sol"; -import {RocketNodeStakingInterface} from "../../interface/node/RocketNodeStakingInterface.sol"; -import {RocketBase} from "../RocketBase.sol"; -import {RocketNetworkSnapshots} from "../network/RocketNetworkSnapshots.sol"; - -/// @notice +import "../../interface/RocketStorageInterface.sol"; +import "../../interface/RocketVaultInterface.sol"; +import "../../interface/dao/protocol/settings/RocketDAOProtocolSettingsMinipoolInterface.sol"; +import "../../interface/dao/protocol/settings/RocketDAOProtocolSettingsNodeInterface.sol"; +import "../../interface/deposit/RocketDepositPoolInterface.sol"; +import "../../interface/megapool/RocketMegapoolFactoryInterface.sol"; +import "../../interface/megapool/RocketMegapoolInterface.sol"; +import "../../interface/minipool/RocketMinipoolInterface.sol"; +import "../../interface/minipool/RocketMinipoolManagerInterface.sol"; +import "../../interface/minipool/RocketMinipoolQueueInterface.sol"; +import "../../interface/network/RocketNetworkFeesInterface.sol"; +import "../../interface/network/RocketNetworkVotingInterface.sol"; +import "../../interface/network/RocketNetworkSnapshotsInterface.sol"; +import "../../interface/node/RocketNodeDepositInterface.sol"; +import "../../interface/node/RocketNodeManagerInterface.sol"; +import "../../interface/node/RocketNodeStakingInterface.sol"; +import "../RocketBase.sol"; + +/// @notice Entry point for node operators to perform deposits for the creation of new validators on the network contract RocketNodeDeposit is RocketBase, RocketNodeDepositInterface { // Constants uint256 constant internal pubKeyLength = 48; @@ -207,7 +207,7 @@ contract RocketNodeDeposit is RocketBase, RocketNodeDepositInterface { RocketDepositPoolInterface rocketDepositPool = RocketDepositPoolInterface(getContractAddress("rocketDepositPool")); rocketDepositPool.nodeDeposit{value: msg.value}(_bondAmount); // Attempt to assign 1 megapool - rocketDepositPool.assignMegapools(1); + rocketDepositPool.maybeAssignOneDeposit(); } /// @notice Called by minipools during bond reduction to increase the amount of ETH the node operator has @@ -223,7 +223,7 @@ contract RocketNodeDeposit is RocketBase, RocketNodeDepositInterface { function _increaseEthMatched(address _nodeAddress, uint256 _amount) private { // Check amount doesn't exceed limits RocketNodeStakingInterface rocketNodeStaking = RocketNodeStakingInterface(getContractAddress("rocketNodeStaking")); - RocketNetworkSnapshots rocketNetworkSnapshots = RocketNetworkSnapshots(getContractAddress("rocketNetworkSnapshots")); + RocketNetworkSnapshotsInterface rocketNetworkSnapshots = RocketNetworkSnapshotsInterface(getContractAddress("rocketNetworkSnapshots")); uint256 ethMatched = rocketNodeStaking.getNodeETHMatched(_nodeAddress) + _amount; require( ethMatched <= rocketNodeStaking.getNodeETHMatchedLimit(_nodeAddress), @@ -237,7 +237,7 @@ contract RocketNodeDeposit is RocketBase, RocketNodeDepositInterface { /// @dev Increases the amount of ETH supplied by a node operator as bond function _increaseEthProvided(address _nodeAddress, uint256 _amount) private { RocketNodeStakingInterface rocketNodeStaking = RocketNodeStakingInterface(getContractAddress("rocketNodeStaking")); - RocketNetworkSnapshots rocketNetworkSnapshots = RocketNetworkSnapshots(getContractAddress("rocketNetworkSnapshots")); + RocketNetworkSnapshotsInterface rocketNetworkSnapshots = RocketNetworkSnapshotsInterface(getContractAddress("rocketNetworkSnapshots")); uint256 ethProvided = rocketNodeStaking.getNodeETHProvided(_nodeAddress) + _amount; bytes32 key = keccak256(abi.encodePacked("eth.provided.node.amount", _nodeAddress)); rocketNetworkSnapshots.push(key, uint224(ethProvided)); diff --git a/contracts/contract/upgrade/RocketUpgradeOneDotFour.sol b/contracts/contract/upgrade/RocketUpgradeOneDotFour.sol index b76a72cb..df621acc 100644 --- a/contracts/contract/upgrade/RocketUpgradeOneDotFour.sol +++ b/contracts/contract/upgrade/RocketUpgradeOneDotFour.sol @@ -7,6 +7,7 @@ import "../../interface/network/RocketNetworkPricesInterface.sol"; import "../../interface/dao/protocol/settings/RocketDAOProtocolSettingsNodeInterface.sol"; import "../../interface/util/AddressSetStorageInterface.sol"; import "../../interface/minipool/RocketMinipoolManagerInterface.sol"; +import "../../interface/megapool/RocketMegapoolFactoryInterface.sol"; /// @notice v1.4 Saturn 1 upgrade contract contract RocketUpgradeOneDotFour is RocketBase { @@ -18,28 +19,22 @@ contract RocketUpgradeOneDotFour is RocketBase { bool public locked; // Upgrade contracts - address public newRocketDAOProposal; - address public newRocketDAOProtocolProposal; - address public newRocketDAOProtocolVerifier; - address public newRocketDAOProtocolSettingsProposals; - address public newRocketDAOProtocolSettingsAuction; - address public newRocketMinipoolManager; - address public newRocketNodeStaking; - address public newRocketMinipoolDelegate; - address public newRocketNodeDeposit; - address public newRocketNetworkVoting; + address public rocketMegapoolDelegate; + address public rocketMegapoolFactory; + address public rocketMegapoolProxy; + address public rocketNodeManager; + address public rocketNodeDeposit; + address public rocketDepositPool; + address public linkedListStorage; // Upgrade ABIs - string public newRocketDAOProposalAbi; - string public newRocketDAOProtocolProposalAbi; - string public newRocketDAOProtocolVerifierAbi; - string public newRocketDAOProtocolSettingsProposalsAbi; - string public newRocketDAOProtocolSettingsAuctionAbi; - string public newRocketMinipoolManagerAbi; - string public newRocketNodeStakingAbi; - string public newRocketMinipoolDelegateAbi; - string public newRocketNodeDepositAbi; - string public newRocketNetworkVotingAbi; + string public rocketMegapoolDelegateAbi; + string public rocketMegapoolFactoryAbi; + string public rocketMegapoolProxyAbi; + string public rocketNodeManagerAbi; + string public rocketNodeDepositAbi; + string public rocketDepositPoolAbi; + string public linkedListStorageAbi; // Save deployer to limit access to set functions address immutable deployer; @@ -63,30 +58,22 @@ contract RocketUpgradeOneDotFour is RocketBase { require(!locked, "Contract locked"); // Set contract addresses - newRocketDAOProposal = _addresses[0]; - newRocketDAOProtocolProposal = _addresses[1]; - newRocketDAOProtocolVerifier = _addresses[2]; - newRocketDAOProtocolSettingsProposals = _addresses[3]; - newRocketDAOProtocolSettingsAuction = _addresses[4]; - newRocketMinipoolManager = _addresses[5]; - newRocketNodeStaking = _addresses[6]; - newRocketMinipoolDelegate = _addresses[7]; - newRocketNodeDeposit = _addresses[8]; - newRocketNetworkVoting = _addresses[9]; + rocketMegapoolDelegate = _addresses[0]; + rocketMegapoolFactory = _addresses[1]; + rocketMegapoolProxy = _addresses[2]; + rocketNodeManager = _addresses[3]; + rocketNodeDeposit = _addresses[4]; + rocketDepositPool = _addresses[5]; + linkedListStorage = _addresses[6]; // Set ABIs - newRocketDAOProposalAbi = _abis[0]; - newRocketDAOProtocolProposalAbi = _abis[1]; - newRocketDAOProtocolVerifierAbi = _abis[2]; - newRocketDAOProtocolSettingsProposalsAbi = _abis[3]; - newRocketDAOProtocolSettingsAuctionAbi = _abis[4]; - newRocketMinipoolManagerAbi = _abis[5]; - newRocketNodeStakingAbi = _abis[6]; - newRocketMinipoolDelegateAbi = _abis[7]; - newRocketNodeDepositAbi = _abis[8]; - newRocketNetworkVotingAbi = _abis[9]; - - // Note: rocketMinipool abi has not changed so does not require updating + rocketMegapoolDelegateAbi = _abis[0]; + rocketMegapoolFactoryAbi = _abis[1]; + rocketMegapoolProxyAbi = _abis[2]; + rocketNodeManagerAbi = _abis[3]; + rocketNodeDepositAbi = _abis[4]; + rocketDepositPoolAbi = _abis[5]; + linkedListStorageAbi = _abis[6]; } /// @notice Prevents further changes from being applied @@ -100,17 +87,19 @@ contract RocketUpgradeOneDotFour is RocketBase { require(!executed, "Already executed"); executed = true; - // Upgrade contracts - _upgradeContract("rocketDAOProposal", newRocketDAOProposal, newRocketDAOProposalAbi); - _upgradeContract("rocketDAOProtocolProposal", newRocketDAOProtocolProposal, newRocketDAOProtocolProposalAbi); - _upgradeContract("rocketDAOProtocolVerifier", newRocketDAOProtocolVerifier, newRocketDAOProtocolVerifierAbi); - _upgradeContract("rocketDAOProtocolSettingsProposals", newRocketDAOProtocolSettingsProposals, newRocketDAOProtocolSettingsProposalsAbi); - _upgradeContract("rocketDAOProtocolSettingsAuction", newRocketDAOProtocolSettingsAuction, newRocketDAOProtocolSettingsAuctionAbi); - _upgradeContract("rocketMinipoolManager", newRocketMinipoolManager, newRocketMinipoolManagerAbi); - _upgradeContract("rocketNodeStaking", newRocketNodeStaking, newRocketNodeStakingAbi); - _upgradeContract("rocketMinipoolDelegate", newRocketMinipoolDelegate, newRocketMinipoolDelegateAbi); - _upgradeContract("rocketNodeDeposit", newRocketNodeDeposit, newRocketNodeDepositAbi); - _upgradeContract("rocketNetworkVoting", newRocketNetworkVoting, newRocketNetworkVotingAbi); + // Add new contracts + _addContract("rocketMegapoolDelegate", rocketMegapoolDelegate, rocketMegapoolDelegateAbi); + _addContract("rocketMegapoolFactory", rocketMegapoolFactory, rocketMegapoolFactoryAbi); + _addContract("rocketMegapoolProxy", rocketMegapoolProxy, rocketMegapoolProxyAbi); + _addContract("linkedListStorage", linkedListStorage, linkedListStorageAbi); + + // Upgrade existing contracts + _upgradeContract("rocketNodeManager", rocketNodeManager, rocketNodeManagerAbi); + _upgradeContract("rocketNodeDeposit", rocketNodeDeposit, rocketNodeDepositAbi); + _upgradeContract("rocketDepositPool", rocketDepositPool, rocketDepositPoolAbi); + + // Init the megapool factory + RocketMegapoolFactoryInterface(rocketMegapoolFactory).initialise(); // Set a protocol version value in storage for convenience with bindings setString(keccak256(abi.encodePacked("protocol.version")), "1.4"); @@ -136,4 +125,26 @@ contract RocketUpgradeOneDotFour is RocketBase { deleteString(keccak256(abi.encodePacked("contract.name", oldContractAddress))); deleteBool(keccak256(abi.encodePacked("contract.exists", oldContractAddress))); } + + /// @dev Add a new network contract + function _addContract(string memory _name, address _contractAddress, string memory _contractAbi) internal { + // Check contract name + bytes32 nameHash = keccak256(abi.encodePacked(_name)); + require(bytes(_name).length > 0, "Invalid contract name"); + // Cannot add contract if it already exists (use upgradeContract instead) + require(getAddress(keccak256(abi.encodePacked("contract.address", _name))) == address(0x0), "Contract name is already in use"); + // Cannot add contract if already in use as ABI only + string memory existingAbi = getString(keccak256(abi.encodePacked("contract.abi", _name))); + require(bytes(existingAbi).length == 0, "Contract name is already in use"); + // Check contract address + require(_contractAddress != address(0x0), "Invalid contract address"); + require(!getBool(keccak256(abi.encodePacked("contract.exists", _contractAddress))), "Contract address is already in use"); + // Check ABI isn't empty + require(bytes(_contractAbi).length > 0, "Empty ABI is invalid"); + // Register contract + setBool(keccak256(abi.encodePacked("contract.exists", _contractAddress)), true); + setString(keccak256(abi.encodePacked("contract.name", _contractAddress)), _name); + setAddress(keccak256(abi.encodePacked("contract.address", _name)), _contractAddress); + setString(keccak256(abi.encodePacked("contract.abi", _name)), _contractAbi); + } } diff --git a/contracts/contract/util/LinkedListStorage.sol b/contracts/contract/util/LinkedListStorage.sol index c57957ca..0cb556a3 100644 --- a/contracts/contract/util/LinkedListStorage.sol +++ b/contracts/contract/util/LinkedListStorage.sol @@ -72,7 +72,7 @@ contract LinkedListStorage is RocketBase, LinkedListStorageInterface { /// @notice Add an item to the end of the list. Requires that the item does not exist in the list /// @param _namespace defines the queue to be used /// @param _item the deposit queue item to be added - function enqueueItem(bytes32 _namespace, DepositQueueValue memory _item) virtual override external onlyLatestContract("addressLinkedListStorage", address(this)) onlyLatestNetworkContract { + function enqueueItem(bytes32 _namespace, DepositQueueValue memory _item) virtual override external onlyLatestContract("linkedListStorage", address(this)) onlyLatestNetworkContract { _enqueueItem(_namespace, _item); } @@ -110,7 +110,7 @@ contract LinkedListStorage is RocketBase, LinkedListStorageInterface { /// @notice Remove an item from the start of a queue and return it. Requires that the queue is not empty /// @param _namespace defines the queue to be used - function dequeueItem(bytes32 _namespace) public virtual override onlyLatestContract("addressLinkedListStorage", address(this)) onlyLatestNetworkContract returns (DepositQueueValue memory item) { + function dequeueItem(bytes32 _namespace) public virtual override onlyLatestContract("linkedListStorage", address(this)) onlyLatestNetworkContract returns (DepositQueueValue memory item) { return _dequeueItem(_namespace); } @@ -159,7 +159,7 @@ contract LinkedListStorage is RocketBase, LinkedListStorageInterface { /// @notice Removes an item from a queue. Requires that the item exists in the queue /// @param _namespace defines the queue to be used /// @param _item to be removed from the queue - function removeItem(bytes32 _namespace, DepositQueueValue memory _item) public virtual override onlyLatestContract("addressLinkedListStorage", address(this)) onlyLatestNetworkContract { + function removeItem(bytes32 _namespace, DepositQueueValue memory _item) public virtual override onlyLatestContract("linkedListStorage", address(this)) onlyLatestNetworkContract { _removeItem(_namespace, _item); } diff --git a/contracts/interface/deposit/RocketDepositPoolInterface.sol b/contracts/interface/deposit/RocketDepositPoolInterface.sol index 4cc76c52..227bfdcf 100644 --- a/contracts/interface/deposit/RocketDepositPoolInterface.sol +++ b/contracts/interface/deposit/RocketDepositPoolInterface.sol @@ -15,6 +15,7 @@ interface RocketDepositPoolInterface { function recycleExcessCollateral() external payable; function recycleLiquidatedStake() external payable; function assignDeposits() external; + function maybeAssignOneDeposit() external; function maybeAssignDeposits() external returns (bool); function withdrawExcessBalance(uint256 _amount) external; function requestFunds(uint256 _bondAmount, uint256 _validatorIndex, uint256 _amount, bool _useExpressTicket) external payable; diff --git a/hardhat-upgrade.config.js b/hardhat-upgrade.config.js new file mode 100644 index 00000000..3bf1d707 --- /dev/null +++ b/hardhat-upgrade.config.js @@ -0,0 +1,25 @@ +let common = require('./hardhat-common.config.js'); + +// Config from environment +const mnemonicPhrase = process.env.MNEMONIC || 'test test test test test test test test test test test junk'; +const mnemonicPassword = process.env.MNEMONIC_PASSWORD; +const providerUrl = process.env.PROVIDER_URL || 'http://localhost:8545'; + +module.exports = Object.assign(common, { + networks: { + testnet: { + url: `${providerUrl}`, + accounts: { + mnemonic: mnemonicPhrase, + path: 'm/44\'/60\'/0\'/0', + initialIndex: 0, + count: 10, + passphrase: mnemonicPassword, + }, + network_id: '*', + }, + }, + paths: { + tests: './test-upgrade', + } +}); diff --git a/old b/old new file mode 160000 index 00000000..8d4d5c0f --- /dev/null +++ b/old @@ -0,0 +1 @@ +Subproject commit 8d4d5c0f1b810f97ca42cd1ceece0e7c81eb81ee diff --git a/package.json b/package.json index 8d715c8e..91a64237 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ "test": "hardhat test --bail", "test-gas": "REPORT_GAS=1 hardhat test --bail", "test-fork": "hardhat test --config hardhat-fork.config.js --bail", + "test-upgrade": "./scripts/upgrade-test.sh", "coverage": "SOLIDITY_COVERAGE=true hardhat coverage", "slither": "slither --filter-paths node_modules,old --exclude conformance-to-solidity-naming-conventions ." }, diff --git a/scripts/upgrade-test.sh b/scripts/upgrade-test.sh new file mode 100755 index 00000000..d2778507 --- /dev/null +++ b/scripts/upgrade-test.sh @@ -0,0 +1,29 @@ +#!/bin/bash + +# Point hardhat at local node +export PROVIDER_URL=http://localhost:8545 +export MNEMONIC="test test test test test test test test test test test junk" +export MNEMONIC_PASSWORD= +export ROCKET_STORAGE=0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512 + +# Start local hardhat node +trap 'kill $(lsof -t -i:8545)' EXIT +npx hardhat node &>/dev/null & +sleep 10 + +# Init submodule +#git submodule update --init + +# Install deps and build +cd old +#npm install +npx hardhat compile + +# Deploy the old version +npx hardhat run scripts/deploy.js --network testnet + +# Move to project root +cd .. + +# Run upgrade test suite +npx hardhat test --network testnet --config hardhat-upgrade.config.js --bail \ No newline at end of file diff --git a/test-upgrade/_helpers/upgrade.js b/test-upgrade/_helpers/upgrade.js new file mode 100644 index 00000000..ffbe1154 --- /dev/null +++ b/test-upgrade/_helpers/upgrade.js @@ -0,0 +1,80 @@ +import { artifacts, RocketStorage } from '../../test/_utils/artifacts'; +import pako from 'pako'; + +const networkContracts = { + rocketMegapoolDelegate: artifacts.require('RocketMegapoolDelegate'), + rocketMegapoolFactory: artifacts.require('RocketMegapoolFactory'), + rocketMegapoolProxy: artifacts.require('RocketMegapoolProxy'), + rocketNodeManager: artifacts.require('RocketNodeManager'), + rocketNodeDeposit: artifacts.require('RocketNodeDeposit'), + rocketDepositPool: artifacts.require('RocketDepositPool'), + linkedListStorage: artifacts.require('LinkedListStorage'), + + rocketUpgradeOneDotFour: artifacts.require('RocketUpgradeOneDotFour'), +}; + +function compressABI(abi) { + return Buffer.from(pako.deflate(JSON.stringify(abi))).toString('base64'); +} + +export async function deployUpgrade(rocketStorageAddress) { + let contracts = {}; + let addresses = {}; + let upgradeContract; + + // Deploy other contracts + for (let contract in networkContracts) { + // Only deploy if it hasn't been deployed already like a precompiled + let instance; + const abi = networkContracts[contract].abi; + + switch (contract) { + // Contracts with no constructor args + case 'rocketMinipoolDelegate': + instance = await networkContracts[contract].clone(); + addresses[contract] = instance.target; + break; + + // Upgrade contract + case 'rocketUpgradeOneDotFour': + instance = await networkContracts[contract].new(rocketStorageAddress); + const args = [ + [ + addresses.rocketMegapoolDelegate, + addresses.rocketMegapoolFactory, + addresses.rocketMegapoolProxy, + addresses.rocketNodeManager, + addresses.rocketNodeDeposit, + addresses.rocketDepositPool, + addresses.linkedListStorage, + ], + [ + compressABI(networkContracts.rocketMegapoolDelegate.abi), + compressABI(networkContracts.rocketMegapoolFactory.abi), + compressABI(networkContracts.rocketMegapoolProxy.abi), + compressABI(networkContracts.rocketNodeManager.abi), + compressABI(networkContracts.rocketNodeDeposit.abi), + compressABI(networkContracts.rocketDepositPool.abi), + compressABI(networkContracts.linkedListStorage.abi), + ], + ]; + await instance.set(...args); + upgradeContract = instance; + break; + + // All other contracts - pass storage address + default: + instance = await networkContracts[contract].clone(rocketStorageAddress); + addresses[contract] = instance.target; + break; + } + + contracts[contract] = { + instance: instance, + address: instance.target, + abi: abi, + }; + } + + return upgradeContract; +} diff --git a/test-upgrade/rocket-upgrade-tests.js b/test-upgrade/rocket-upgrade-tests.js new file mode 100644 index 00000000..6ba1f950 --- /dev/null +++ b/test-upgrade/rocket-upgrade-tests.js @@ -0,0 +1,69 @@ +import { afterEach, before, beforeEach, describe, it } from 'mocha'; +import { + Artifacts, + artifacts, RocketNetworkVoting, + RocketNodeManager, + RocketStorage, + RocketUpgradeOneDotFour, +} from '../test/_utils/artifacts'; +import { injectBNHelpers } from '../test/_helpers/bn'; +import { endSnapShot, globalSnapShot, startSnapShot } from '../test/_utils/snapshotting'; +import { registerNode } from '../test/_helpers/node'; +import { printTitle } from '../test/_utils/formatting'; +import { setDefaultParameters } from '../test/_helpers/defaults'; +import { deployMegapool, nodeDeposit } from '../test/_helpers/megapool'; +import { deployUpgrade } from './_helpers/upgrade'; +import { setDaoNodeTrustedBootstrapUpgrade } from '../test/dao/scenario-dao-node-trusted-bootstrap'; + +const helpers = require('@nomicfoundation/hardhat-network-helpers'); +const hre = require('hardhat'); +const ethers = hre.ethers; + +injectBNHelpers(); +beforeEach(startSnapShot); +afterEach(endSnapShot); + +const rocketStorageAddress = process.env.ROCKET_STORAGE || '0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512'; + +describe('Test Upgrade', () => { + let owner, + node, + nodeWithdrawalAddress, + random; + + let upgradeContract; + + before(async () => { + await globalSnapShot(); + + [ + owner, + node, + nodeWithdrawalAddress, + random, + ] = await ethers.getSigners(); + + // Deploy upgrade while global artifacts are still latest version + upgradeContract = await deployUpgrade(rocketStorageAddress); + // Load artifacts from old deployment and initialise default parameters + await artifacts.loadFromDeployment(rocketStorageAddress); + await setDefaultParameters(); + }); + + async function executeUpgrade() { + // Bootstrap add the upgrade contract and execute + await setDaoNodeTrustedBootstrapUpgrade('addContract', 'rocketUpgradeOneDotFour', RocketUpgradeOneDotFour.abi, upgradeContract.target, {from: owner}); + await upgradeContract.connect(owner).execute(); + // Reload contracts from deployment as some were upgraded + await artifacts.loadFromDeployment(rocketStorageAddress); + } + + it(printTitle('node', 'can create megapool and deposit'), async () => { + await registerNode({ from: node }); + + await executeUpgrade(); + + await deployMegapool({ from: node }); + await nodeDeposit(false, false, { value: '4'.ether, from: node }); + }); +}); \ No newline at end of file diff --git a/test/_helpers/megapool.js b/test/_helpers/megapool.js index 19fd2d39..2fe4eacd 100644 --- a/test/_helpers/megapool.js +++ b/test/_helpers/megapool.js @@ -1,7 +1,7 @@ import { assertBN } from './bn'; import * as assert from 'assert'; import { - artifacts, RocketDepositPool, + artifacts, RocketDAOProtocolSettingsDeposit, RocketDepositPool, RocketMegapoolDelegate, RocketMegapoolFactory, RocketNodeDeposit, RocketNodeManager, @@ -47,16 +47,16 @@ export async function deployMegapool(txOptions) { export async function nodeDeposit(useExpressTicket, useCredit, txOptions) { const [ rocketNodeDeposit, - rocketNodeStaking, rocketNodeManager, rocketMegapoolFactory, rocketDepositPool, + rocketDAOProtocolSettingsDeposit, ] = await Promise.all([ RocketNodeDeposit.deployed(), - RocketNodeStaking.deployed(), RocketNodeManager.deployed(), RocketMegapoolFactory.deployed(), RocketDepositPool.deployed(), + RocketDAOProtocolSettingsDeposit.deployed(), ]); // Construct deposit data for prestake @@ -91,9 +91,10 @@ export async function nodeDeposit(useExpressTicket, useCredit, txOptions) { const data1 = await getData(); + const assignmentsEnabled = await rocketDAOProtocolSettingsDeposit.getAssignDepositsEnabled(); const depositPoolCapacity = await rocketDepositPool.getBalance(); const amountRequired = '32'.ether - txOptions.value; - const expectAssignment = depositPoolCapacity >= amountRequired; + const expectAssignment = assignmentsEnabled && depositPoolCapacity >= amountRequired; const tx = await rocketNodeDeposit.connect(txOptions.from).deposit(txOptions.value, useExpressTicket, depositData.pubkey, depositData.signature, depositDataRoot, txOptions); await tx.wait(); diff --git a/test/_utils/artifacts.js b/test/_utils/artifacts.js index 6cae0fd8..b3536a53 100644 --- a/test/_utils/artifacts.js +++ b/test/_utils/artifacts.js @@ -1,3 +1,5 @@ +import { decompressABI } from './contract'; + const hre = require('hardhat'); const ethers = hre.ethers; @@ -25,12 +27,30 @@ class Artifact { return (await ethers.getContractFactory(this.name)).deploy(...args); } - at (address) { + at(address) { return new ethers.Contract(address, this.abi, hre.ethers.provider); } + + async fromDeployment(rocketStorage) { + let contractName = this.name.charAt(0).toLowerCase() + this.name.slice(1); + + const addressKey = ethers.solidityPackedKeccak256(['string', 'string'], ['contract.address', contractName]); + const abiKey = ethers.solidityPackedKeccak256(['string', 'string'], ['contract.abi', contractName]); + + const address = await rocketStorage['getAddress(bytes32)'](addressKey); + const abiRaw = await rocketStorage['getString(bytes32)'](abiKey); + + if (address === '0x0000000000000000000000000000000000000000' || abiRaw === '') { + return; + } + + this.abi = decompressABI(abiRaw); + + this.instance = new ethers.Contract(address, this.abi, hre.ethers.provider); + } } -class Artifacts { +export class Artifacts { constructor() { this.artifacts = {}; } @@ -41,6 +61,19 @@ class Artifacts { } return this.artifacts[name]; } + + async loadFromDeployment(rocketStorageAddress) { + RocketStorage.instance = this.artifacts['RocketStorage'].at(rocketStorageAddress); + + for (const name in this.artifacts) { + switch (name) { + case 'RocketStorage': + break; + default: + await this.artifacts[name].fromDeployment(RocketStorage.instance); + } + } + } } export const artifacts = new Artifacts(); @@ -108,3 +141,4 @@ export const RocketNetworkVoting = artifacts.require('RocketNetworkVoting'); export const MegapoolUpgradeHelper = artifacts.require('MegapoolUpgradeHelper'); export const BeaconStateVerifier = artifacts.require('BeaconStateVerifierMock'); export const BlockRootsMock = artifacts.require('BlockRootsMock'); +export const RocketUpgradeOneDotFour = artifacts.require('RocketUpgradeOneDotFour'); diff --git a/test/auction/auction-tests.js b/test/auction/auction-tests.js index 5d34134a..7c7b246c 100644 --- a/test/auction/auction-tests.js +++ b/test/auction/auction-tests.js @@ -1,22 +1,23 @@ import { before, describe, it } from 'mocha'; import { printTitle } from '../_utils/formatting'; import { shouldRevert } from '../_utils/testing'; -import { RocketDAONodeTrustedSettingsMinipool, RocketDAOProtocolSettingsAuction } from '../_utils/artifacts'; +import { + RocketAuctionManager, + RocketDAOProtocolSettingsAuction, + RocketTokenRPL, + RocketVault, +} from '../_utils/artifacts'; import { auctionCreateLot, auctionPlaceBid, getLotPriceAtBlock, getLotStartBlock } from '../_helpers/auction'; -import { userDeposit } from '../_helpers/deposit'; -import { createMinipool, stakeMinipool } from '../_helpers/minipool'; import { submitPrices } from '../_helpers/network'; -import { nodeStakeRPL, registerNode, setNodeTrusted } from '../_helpers/node'; import { setDAOProtocolBootstrapSetting } from '../dao/scenario-dao-protocol-bootstrap'; -import { mintRPL } from '../_helpers/tokens'; +import { approveRPL, mintRPL } from '../_helpers/tokens'; import { createLot } from './scenario-create-lot'; import { placeBid } from './scenario-place-bid'; import { claimBid } from './scenario-claim-bid'; import { recoverUnclaimedRPL } from './scenario-recover-rpl'; -import { withdrawValidatorBalance } from '../minipool/scenario-withdraw-validator-balance'; -import { setDAONodeTrustedBootstrapSetting } from '../dao/scenario-dao-node-trusted-bootstrap'; import { assertBN } from '../_helpers/bn'; import { globalSnapShot } from '../_utils/snapshotting'; +import { registerNode, setNodeTrusted } from '../_helpers/node'; const helpers = require('@nomicfoundation/hardhat-network-helpers'); const hre = require('hardhat'); @@ -31,9 +32,6 @@ export default function() { random2; const auctionDuration = 7200; - const scrubPeriod = (60 * 60 * 24); // 24 hours - - let minipool; before(async () => { await globalSnapShot(); @@ -47,58 +45,10 @@ export default function() { ] = await ethers.getSigners(); // Set settings - await setDAONodeTrustedBootstrapSetting(RocketDAONodeTrustedSettingsMinipool, 'minipool.scrub.period', scrubPeriod, { from: owner }); await setDAOProtocolBootstrapSetting(RocketDAOProtocolSettingsAuction, 'auction.lot.duration', auctionDuration, { from: owner }); - - // Register node - await registerNode({ from: node }); - // Register trusted node await registerNode({ from: trustedNode }); await setNodeTrusted(trustedNode, 'saas_1', 'node@home.com', owner); - - // Mint RPL to node & stake; create & stake minipool - const rplAmount = '10000'.ether; - await mintRPL(owner, node, rplAmount); - await nodeStakeRPL(rplAmount, { from: node }); - minipool = await createMinipool({ from: node, value: '8'.ether }); - await userDeposit({ from: random1, value: '24'.ether }); - await helpers.time.increase(scrubPeriod + 1); - await stakeMinipool(minipool, { from: node }); - - // Send 8 ETH to the minipool so a slash will occur on distribute - await owner.sendTransaction({ - to: minipool.target, - value: '8'.ether, - }); - }); - - it(printTitle('random address', 'can create a lot'), async () => { - // Slash RPL assigned to minipool to fill auction contract - await withdrawValidatorBalance(minipool, '0'.ether, node, true); - - // Create first lot - await createLot({ - from: random1, - }); - - // Create second lot - await createLot({ - from: random1, - }); - }); - - it(printTitle('random address', 'cannot create a lot while lot creation is disabled'), async () => { - // Slash RPL assigned to minipool to fill auction contract - await withdrawValidatorBalance(minipool, '0'.ether, node, true); - - // Disable lot creation - await setDAOProtocolBootstrapSetting(RocketDAOProtocolSettingsAuction, 'auction.lot.create.enabled', false, { from: owner }); - - // Attempt to create lot - await shouldRevert(createLot({ - from: random1, - }), 'Created a lot while lot creation was disabled'); }); it(printTitle('random address', 'cannot create a lot with an insufficient RPL balance'), async () => { @@ -108,267 +58,269 @@ export default function() { }), 'Created a lot with an insufficient RPL balance'); }); - it(printTitle('auction lot', 'has correct price at block'), async () => { - // Set lot settings - await setDAOProtocolBootstrapSetting(RocketDAOProtocolSettingsAuction, 'auction.lot.duration', 100000, { from: owner }); - await setDAOProtocolBootstrapSetting(RocketDAOProtocolSettingsAuction, 'auction.price.start', '1'.ether, { from: owner }); - await setDAOProtocolBootstrapSetting(RocketDAOProtocolSettingsAuction, 'auction.price.reserve', '0.5'.ether, { from: owner }); - - // Set RPL price - let block = await ethers.provider.getBlockNumber(); - let slotTimestamp = '1600000000'; - await submitPrices(block, slotTimestamp, '1'.ether, { from: trustedNode }); - - // Create lot - await withdrawValidatorBalance(minipool, '0'.ether, node, true); - await auctionCreateLot({ from: random1 }); - - // Get lot start block - const startBlock = parseInt(await getLotStartBlock(0)); - - // Set expected prices at blocks - const values = [ - { block: startBlock + 0, expectedPrice: '1.00000'.ether }, - { block: startBlock + 12000, expectedPrice: '0.99280'.ether }, - { block: startBlock + 25000, expectedPrice: '0.96875'.ether }, - { block: startBlock + 37000, expectedPrice: '0.93155'.ether }, - { block: startBlock + 50000, expectedPrice: '0.87500'.ether }, - { block: startBlock + 63000, expectedPrice: '0.80155'.ether }, - { block: startBlock + 75000, expectedPrice: '0.71875'.ether }, - { block: startBlock + 88000, expectedPrice: '0.61280'.ether }, - { block: startBlock + 100000, expectedPrice: '0.50000'.ether }, - ]; - - // Check fees - for (let vi = 0; vi < values.length; ++vi) { - let v = values[vi]; - let price = await getLotPriceAtBlock(0, v.block); - assertBN.equal(price, v.expectedPrice, 'Lot price does not match expected price at block'); - } - }); - - it(printTitle('random address', 'can place a bid on a lot'), async () => { - // Create lots - await withdrawValidatorBalance(minipool, '0'.ether, node, true); - await auctionCreateLot({ from: random1 }); - await auctionCreateLot({ from: random1 }); - - // Place bid on first lot from first address - await placeBid(0, { - from: random1, - value: '4'.ether, - }); - - // Increase bid on first lot from first address - await placeBid(0, { - from: random1, - value: '4'.ether, - }); - - // Place bid on first lot from second address - await placeBid(0, { - from: random2, - value: '4'.ether, - }); + describe('With RPL to auction', () => { + const rplAmount = '1600'.ether; - // Place bid on second lot from first address - await placeBid(1, { - from: random1, - value: '2'.ether, - }); - - // Increase bid on second lot from first address - await placeBid(1, { - from: random1, - value: '2'.ether, - }); - - // Place bid on second lot from second address - await placeBid(1, { - from: random2, - value: '2'.ether, - }); - }); - - it(printTitle('random address', 'can claim RPL from a lot'), async () => { - // Create lots & place bids to clear - await withdrawValidatorBalance(minipool, '0'.ether, node, true); - await auctionCreateLot({ from: random1 }); - await auctionCreateLot({ from: random1 }); - await auctionPlaceBid(0, { from: random1, value: '5'.ether }); - await auctionPlaceBid(0, { from: random2, value: '5'.ether }); - await auctionPlaceBid(1, { from: random1, value: '3'.ether }); - await auctionPlaceBid(1, { from: random2, value: '3'.ether }); - - // Claim RPL on first lot from first address - await claimBid(0, { - from: random1, - }); - - // Claim RPL on first lot from second address - await claimBid(0, { - from: random2, - }); - - // Claim RPL on second lot from first address - await claimBid(1, { - from: random1, - }); - - // Claim RPL on second lot from second address - await claimBid(1, { - from: random2, + before(async () => { + // Get contracts + const rocketTokenRpl = await RocketTokenRPL.deployed(); + const rocketVault = await RocketVault.deployed(); + const rocketAuctionManager = await RocketAuctionManager.deployed(); + // Mint and send RPL to the auction manager + await mintRPL(owner, owner, rplAmount); + await approveRPL(rocketVault.target, rplAmount, { from: owner }); + await rocketVault.depositToken('rocketAuctionManager', rocketTokenRpl.target, rplAmount, { from: owner }); + // Verify RPL balance + const rplBalance = await rocketAuctionManager.getTotalRPLBalance(); + assertBN.equal(rplAmount, rplBalance, 'Incorrect RPL balance'); }); - }); - - it(printTitle('random address', 'can recover unclaimed RPL from a lot'), async () => { - // Create closed lots - await withdrawValidatorBalance(minipool, '0'.ether, node, true); - await auctionCreateLot({ from: random1 }); - await auctionCreateLot({ from: random1 }); - // Wait for duration to end - await helpers.mine(auctionDuration); - - // Recover RPL from first lot - await recoverUnclaimedRPL(0, { - from: random1, + it(printTitle('random address', 'can create a lot'), async () => { + // Create first lot + await createLot({ + from: random1, + }); + // Create second lot + await createLot({ + from: random1, + }); }); - // Recover RPL from second lot - await recoverUnclaimedRPL(1, { - from: random1, + it(printTitle('random address', 'cannot create a lot while lot creation is disabled'), async () => { + // Disable lot creation + await setDAOProtocolBootstrapSetting(RocketDAOProtocolSettingsAuction, 'auction.lot.create.enabled', false, { from: owner }); + // Attempt to create lot + await shouldRevert(createLot({ + from: random1, + }), 'Created a lot while lot creation was disabled'); }); - }); - describe('With Lot', () => { - before(async () => { + it(printTitle('auction lot', 'has correct price at block'), async () => { + // Set lot settings + await setDAOProtocolBootstrapSetting(RocketDAOProtocolSettingsAuction, 'auction.lot.duration', 100000, { from: owner }); + await setDAOProtocolBootstrapSetting(RocketDAOProtocolSettingsAuction, 'auction.price.start', '1'.ether, { from: owner }); + await setDAOProtocolBootstrapSetting(RocketDAOProtocolSettingsAuction, 'auction.price.reserve', '0.5'.ether, { from: owner }); + // Set RPL price + let block = await ethers.provider.getBlockNumber(); + let slotTimestamp = '1600000000'; + await submitPrices(block, slotTimestamp, '1'.ether, { from: trustedNode }); // Create lot - await withdrawValidatorBalance(minipool, '0'.ether, node); await auctionCreateLot({ from: random1 }); + // Get lot start block + const startBlock = parseInt(await getLotStartBlock(0)); + // Set expected prices at blocks + const values = [ + { block: startBlock + 0, expectedPrice: '1.00000'.ether }, + { block: startBlock + 12000, expectedPrice: '0.99280'.ether }, + { block: startBlock + 25000, expectedPrice: '0.96875'.ether }, + { block: startBlock + 37000, expectedPrice: '0.93155'.ether }, + { block: startBlock + 50000, expectedPrice: '0.87500'.ether }, + { block: startBlock + 63000, expectedPrice: '0.80155'.ether }, + { block: startBlock + 75000, expectedPrice: '0.71875'.ether }, + { block: startBlock + 88000, expectedPrice: '0.61280'.ether }, + { block: startBlock + 100000, expectedPrice: '0.50000'.ether }, + ]; + // Check fees + for (let vi = 0; vi < values.length; ++vi) { + let v = values[vi]; + let price = await getLotPriceAtBlock(0, v.block); + assertBN.equal(price, v.expectedPrice, 'Lot price does not match expected price at block'); + } }); - it(printTitle('random address', 'cannot recover unclaimed RPL from a lot which has no RPL to recover'), async () => { - await auctionPlaceBid(0, { from: random1, value: '1000'.ether }); - - // Wait for duration to end - await helpers.mine(auctionDuration); - - // Attempt to recover RPL again - await shouldRevert(recoverUnclaimedRPL(0, { - from: random1, - }), 'Recovered unclaimed RPL from a lot which had no RPL to recover'); - }); - - - it(printTitle('random address', 'cannot bid on a lot which doesn\'t exist'), async () => { - // Attempt to place bid - await shouldRevert(placeBid(1, { + it(printTitle('random address', 'can place a bid on a lot'), async () => { + // Create lots + await auctionCreateLot({ from: random1 }); + await auctionCreateLot({ from: random1 }); + // Place bid on first lot from first address + await placeBid(0, { from: random1, value: '4'.ether, - }), 'Bid on a lot which doesn\'t exist'); - }); - - it(printTitle('random address', 'cannot bid on a lot while bidding is disabled'), async () => { - // Disable bidding - await setDAOProtocolBootstrapSetting(RocketDAOProtocolSettingsAuction, 'auction.lot.bidding.enabled', false, { from: owner }); - - // Attempt to place bid - await shouldRevert(placeBid(0, { + }); + // Increase bid on first lot from first address + await placeBid(0, { from: random1, value: '4'.ether, - }), 'Bid on a lot while bidding was disabled'); + }); + // Place bid on first lot from second address + await placeBid(0, { + from: random2, + value: '4'.ether, + }); + // Place bid on second lot from first address + await placeBid(1, { + from: random1, + value: '2'.ether, + }); + // Increase bid on second lot from first address + await placeBid(1, { + from: random1, + value: '2'.ether, + }); + // Place bid on second lot from second address + await placeBid(1, { + from: random2, + value: '2'.ether, + }); }); - it(printTitle('random address', 'cannot bid an invalid amount on a lot'), async () => { - // Attempt to place bid - await shouldRevert(placeBid(0, { + it(printTitle('random address', 'can claim RPL from a lot'), async () => { + // Create lots & place bids to clear + await auctionCreateLot({ from: random1 }); + await auctionCreateLot({ from: random1 }); + await auctionPlaceBid(0, { from: random1, value: '5'.ether }); + await auctionPlaceBid(0, { from: random2, value: '5'.ether }); + await auctionPlaceBid(1, { from: random1, value: '3'.ether }); + await auctionPlaceBid(1, { from: random2, value: '3'.ether }); + // Claim RPL on first lot from first address + await claimBid(0, { + from: random1, + }); + // Claim RPL on first lot from second address + await claimBid(0, { + from: random2, + }); + // Claim RPL on second lot from first address + await claimBid(1, { from: random1, - value: '0'.ether, - }), 'Bid an invalid amount on a lot'); + }); + // Claim RPL on second lot from second address + await claimBid(1, { + from: random2, + }); }); - it(printTitle('random address', 'cannot bid on a lot after the lot bidding period has concluded'), async () => { + it(printTitle('random address', 'can recover unclaimed RPL from a lot'), async () => { + // Create closed lots + await auctionCreateLot({ from: random1 }); + await auctionCreateLot({ from: random1 }); // Wait for duration to end await helpers.mine(auctionDuration); - - // Attempt to place bid - await shouldRevert(placeBid(0, { + // Recover RPL from first lot + await recoverUnclaimedRPL(0, { from: random1, - value: '4'.ether, - }), 'Bid on a lot after the bidding period concluded'); - }); - - it(printTitle('random address', 'cannot bid on a lot after the RPL allocation has been exhausted'), async () => { - // Place bid & claim all RPL - await placeBid(0, { + }); + // Recover RPL from second lot + await recoverUnclaimedRPL(1, { from: random1, - value: '1000'.ether, }); - - // Attempt to place bid - await shouldRevert(placeBid(0, { - from: random2, - value: '4'.ether, - }), 'Bid on a lot after the RPL allocation was exhausted'); }); - it(printTitle('random address', 'cannot claim RPL from a lot which doesn\'t exist'), async () => { - await auctionPlaceBid(0, { from: random1, value: '1000'.ether }); + describe('With Lot', () => { + before(async () => { + // Create lot + await auctionCreateLot({ from: random1 }); + }); - // Attempt to claim RPL - await shouldRevert(claimBid(1, { - from: random1, - }), 'Claimed RPL from a lot which doesn\'t exist'); - }); + it(printTitle('random address', 'cannot recover unclaimed RPL from a lot which has no RPL to recover'), async () => { + await auctionPlaceBid(0, { from: random1, value: '1000'.ether }); + // Wait for duration to end + await helpers.mine(auctionDuration); + // Attempt to recover RPL again + await shouldRevert(recoverUnclaimedRPL(0, { + from: random1, + }), 'Recovered unclaimed RPL from a lot which had no RPL to recover'); + }); - it(printTitle('random address', 'cannot claim RPL from a lot before it has cleared'), async () => { - await auctionPlaceBid(0, { from: random1, value: '4'.ether }); + it(printTitle('random address', 'cannot bid on a lot which doesn\'t exist'), async () => { + // Attempt to place bid + await shouldRevert(placeBid(1, { + from: random1, + value: '4'.ether, + }), 'Bid on a lot which doesn\'t exist'); + }); - // Attempt to claim RPL - await shouldRevert(claimBid(0, { - from: random1, - }), 'Claimed RPL from a lot before it has cleared'); - }); + it(printTitle('random address', 'cannot bid on a lot while bidding is disabled'), async () => { + // Disable bidding + await setDAOProtocolBootstrapSetting(RocketDAOProtocolSettingsAuction, 'auction.lot.bidding.enabled', false, { from: owner }); + // Attempt to place bid + await shouldRevert(placeBid(0, { + from: random1, + value: '4'.ether, + }), 'Bid on a lot while bidding was disabled'); + }); - it(printTitle('random address', 'cannot claim RPL from a lot it has not bid on'), async () => { - await auctionPlaceBid(0, { from: random1, value: '1000'.ether }); + it(printTitle('random address', 'cannot bid an invalid amount on a lot'), async () => { + // Attempt to place bid + await shouldRevert(placeBid(0, { + from: random1, + value: '0'.ether, + }), 'Bid an invalid amount on a lot'); + }); - // Attempt to claim RPL - await shouldRevert(claimBid(0, { - from: random2, - }), 'Address claimed RPL from a lot it has not bid on'); - }); + it(printTitle('random address', 'cannot bid on a lot after the lot bidding period has concluded'), async () => { + // Wait for duration to end + await helpers.mine(auctionDuration); + // Attempt to place bid + await shouldRevert(placeBid(0, { + from: random1, + value: '4'.ether, + }), 'Bid on a lot after the bidding period concluded'); + }); - it(printTitle('random address', 'cannot recover unclaimed RPL from a lot which doesn\'t exist'), async () => { - // Wait for duration to end - await helpers.mine(auctionDuration); + it(printTitle('random address', 'cannot bid on a lot after the RPL allocation has been exhausted'), async () => { + // Place bid & claim all RPL + await placeBid(0, { + from: random1, + value: '1000'.ether, + }); + // Attempt to place bid + await shouldRevert(placeBid(0, { + from: random2, + value: '4'.ether, + }), 'Bid on a lot after the RPL allocation was exhausted'); + }); - // Attempt to recover RPL - await shouldRevert(recoverUnclaimedRPL(1, { - from: random1, - }), 'Recovered unclaimed RPL from a lot which doesn\'t exist'); - }); + it(printTitle('random address', 'cannot claim RPL from a lot which doesn\'t exist'), async () => { + await auctionPlaceBid(0, { from: random1, value: '1000'.ether }); + // Attempt to claim RPL + await shouldRevert(claimBid(1, { + from: random1, + }), 'Claimed RPL from a lot which doesn\'t exist'); + }); - it(printTitle('random address', 'cannot recover unclaimed RPL from a lot before the lot bidding period has concluded'), async () => { - // Attempt to recover RPL - await shouldRevert(recoverUnclaimedRPL(0, { - from: random1, - }), 'Recovered unclaimed RPL from a lot before its bidding period had concluded'); + it(printTitle('random address', 'cannot claim RPL from a lot before it has cleared'), async () => { + await auctionPlaceBid(0, { from: random1, value: '4'.ether }); + // Attempt to claim RPL + await shouldRevert(claimBid(0, { + from: random1, + }), 'Claimed RPL from a lot before it has cleared'); + }); - }); + it(printTitle('random address', 'cannot claim RPL from a lot it has not bid on'), async () => { + await auctionPlaceBid(0, { from: random1, value: '1000'.ether }); + // Attempt to claim RPL + await shouldRevert(claimBid(0, { + from: random2, + }), 'Address claimed RPL from a lot it has not bid on'); + }); - it(printTitle('random address', 'cannot recover unclaimed RPL from a lot twice'), async () => { - // Wait for duration to end - await helpers.mine(auctionDuration); + it(printTitle('random address', 'cannot recover unclaimed RPL from a lot which doesn\'t exist'), async () => { + // Wait for duration to end + await helpers.mine(auctionDuration); + // Attempt to recover RPL + await shouldRevert(recoverUnclaimedRPL(1, { + from: random1, + }), 'Recovered unclaimed RPL from a lot which doesn\'t exist'); + }); - // Recover RPL - await recoverUnclaimedRPL(0, { from: random1 }); + it(printTitle('random address', 'cannot recover unclaimed RPL from a lot before the lot bidding period has concluded'), async () => { + // Attempt to recover RPL + await shouldRevert(recoverUnclaimedRPL(0, { + from: random1, + }), 'Recovered unclaimed RPL from a lot before its bidding period had concluded'); + }); - // Attempt to recover RPL again - await shouldRevert(recoverUnclaimedRPL(0, { - from: random1, - }), 'Recovered unclaimed RPL from a lot twice'); + it(printTitle('random address', 'cannot recover unclaimed RPL from a lot twice'), async () => { + // Wait for duration to end + await helpers.mine(auctionDuration); + // Recover RPL + await recoverUnclaimedRPL(0, { from: random1 }); + // Attempt to recover RPL again + await shouldRevert(recoverUnclaimedRPL(0, { + from: random1, + }), 'Recovered unclaimed RPL from a lot twice'); + }); }); }); }); diff --git a/test/dao/dao-protocol-tests.js b/test/dao/dao-protocol-tests.js index a3b7bcc9..1965453f 100644 --- a/test/dao/dao-protocol-tests.js +++ b/test/dao/dao-protocol-tests.js @@ -405,7 +405,8 @@ export default function() { * Proposer */ - describe('With Node Operators', () => { + // TODO: Re-enable and once we have RPL megapool staking implemented + describe.skip('With Node Operators', () => { let nodes; before(async () => { diff --git a/test/deposit/deposit-pool-tests.js b/test/deposit/deposit-pool-tests.js index ba7ac595..a6304f67 100644 --- a/test/deposit/deposit-pool-tests.js +++ b/test/deposit/deposit-pool-tests.js @@ -2,22 +2,18 @@ import { before, describe, it } from 'mocha'; import { printTitle } from '../_utils/formatting'; import { shouldRevert } from '../_utils/testing'; import { userDeposit } from '../_helpers/deposit'; -import { createMinipool, getMinipoolMinimumRPLStake } from '../_helpers/minipool'; +import { getMinipoolMinimumRPLStake } from '../_helpers/minipool'; import { submitBalances } from '../_helpers/network'; -import { nodeDeposit, nodeStakeRPL, registerNode, setNodeTrusted } from '../_helpers/node'; +import { nodeStakeRPL, registerNode, setNodeTrusted } from '../_helpers/node'; import { getRethExchangeRate, getRethTotalSupply, mintRPL } from '../_helpers/tokens'; import { getDepositSetting } from '../_helpers/settings'; import { deposit } from './scenario-deposit'; -import { - RocketDAONodeTrustedSettingsMembers, - RocketDAOProtocolSettingsDeposit, - RocketDepositPool, -} from '../_utils/artifacts'; +import { RocketDAOProtocolSettingsDeposit, RocketDepositPool } from '../_utils/artifacts'; import { setDAOProtocolBootstrapSetting } from '../dao/scenario-dao-protocol-bootstrap'; -import { setDAONodeTrustedBootstrapSetting } from '../dao/scenario-dao-node-trusted-bootstrap'; -import { assignDepositsV2 } from './scenario-assign-deposits-v2'; +import { assignDeposits } from './scenario-assign-deposits'; import { assertBN } from '../_helpers/bn'; import { globalSnapShot } from '../_utils/snapshotting'; +import { nodeDeposit } from '../_helpers/megapool'; const hre = require('hardhat'); const ethers = hre.ethers; @@ -32,7 +28,6 @@ export default function() { before(async () => { await globalSnapShot(); - [ owner, node, @@ -40,10 +35,8 @@ export default function() { staker, random, ] = await ethers.getSigners(); - // Register node await registerNode({ from: node }); - // Register trusted node await registerNode({ from: trustedNode }); await setNodeTrusted(trustedNode, 'saas_1', 'node@home.com', owner); @@ -68,11 +61,9 @@ export default function() { let rethSupply = await getRethTotalSupply(); let slotTimestamp = '1600000000'; await submitBalances(1, slotTimestamp, totalBalance, 0, rethSupply, { from: trustedNode }); - // Get & check updated rETH exchange rate let exchangeRate2 = await getRethExchangeRate(); assertBN.notEqual(exchangeRate1, exchangeRate2, 'rETH exchange rate has not changed'); - // Deposit again with updated rETH exchange rate await deposit({ from: staker, @@ -83,7 +74,6 @@ export default function() { it(printTitle('staker', 'cannot make a deposit while deposits are disabled'), async () => { // Disable deposits await setDAOProtocolBootstrapSetting(RocketDAOProtocolSettingsDeposit, 'deposit.enabled', false, { from: owner }); - // Attempt deposit await shouldRevert(deposit({ from: staker, @@ -96,7 +86,6 @@ export default function() { let minimumDeposit = await getDepositSetting('MinimumDeposit'); let depositAmount = minimumDeposit / 2n; assertBN.isBelow(depositAmount, minimumDeposit, 'Deposit amount is not less than the minimum deposit'); - // Attempt deposit await shouldRevert(deposit({ from: staker, @@ -107,7 +96,6 @@ export default function() { it(printTitle('staker', 'cannot make a deposit which would exceed the maximum deposit pool size'), async () => { // Set max deposit pool size await setDAOProtocolBootstrapSetting(RocketDAOProtocolSettingsDeposit, 'deposit.pool.maximum', '100'.ether, { from: owner }); - // Attempt deposit await shouldRevert(deposit({ from: staker, @@ -120,19 +108,15 @@ export default function() { await setDAOProtocolBootstrapSetting(RocketDAOProtocolSettingsDeposit, 'deposit.pool.maximum', '1'.ether, { from: owner }); // Disable socialised assignments so the deposit pool balance check succeeds await setDAOProtocolBootstrapSetting(RocketDAOProtocolSettingsDeposit, 'deposit.assign.socialised.maximum', 0, { from: owner }); - // Attempt deposit greater than maximum (fails) await shouldRevert(deposit({ from: staker, value: '16'.ether, }), 'Made a deposit which exceeds the maximum deposit pool size'); - - // Create a minipool to add to the queue - let minipoolRplStake = await getMinipoolMinimumRPLStake(); - await mintRPL(owner, node, minipoolRplStake); - await nodeStakeRPL(minipoolRplStake, { from: node }); - await createMinipool({ from: node, value: '16'.ether }); - + // Perform 4 node deposits so there is 16 ETH space available for user deposits + for (let i = 0; i < 4; ++i) { + await nodeDeposit(false, false, { value: '4'.ether, from: node }); + } // Attempt deposit await deposit({ from: staker, @@ -146,35 +130,27 @@ export default function() { it(printTitle('random address', 'can assign deposits'), async () => { // Assign deposits with no assignable deposits - await assignDepositsV2({ + await assignDeposits({ from: staker, }); - // Disable deposit assignment await setDAOProtocolBootstrapSetting(RocketDAOProtocolSettingsDeposit, 'deposit.assign.enabled', false, { from: owner }); - - // Disable minimum unbonded commission threshold - await setDAONodeTrustedBootstrapSetting(RocketDAONodeTrustedSettingsMembers, 'members.minipool.unbonded.min.fee', '0', { from: owner }); - // Stake RPL to cover minipools let minipoolRplStake = await getMinipoolMinimumRPLStake(); let rplStake = minipoolRplStake * 3n; await mintRPL(owner, trustedNode, rplStake); await nodeStakeRPL(rplStake, { from: trustedNode }); - - // Make user & node deposits + // Deposit and queue up some validators await userDeposit({ from: staker, value: '100'.ether }); - await nodeDeposit({ from: trustedNode, value: '16'.ether }); - await nodeDeposit({ from: trustedNode, value: '16'.ether }); - await nodeDeposit({ from: trustedNode, value: '16'.ether }); - + for (let i = 0; i < 3; ++i) { + await nodeDeposit(false, false, { value: '4'.ether, from: node }); + } // Re-enable deposit assignment & set limit await setDAOProtocolBootstrapSetting(RocketDAOProtocolSettingsDeposit, 'deposit.assign.enabled', true, { from: owner }); await setDAOProtocolBootstrapSetting(RocketDAOProtocolSettingsDeposit, 'deposit.assign.maximum', 3, { from: owner }); await setDAOProtocolBootstrapSetting(RocketDAOProtocolSettingsDeposit, 'deposit.assign.socialised.maximum', 3, { from: owner }); - // Assign deposits with assignable deposits - await assignDepositsV2({ + await assignDeposits({ from: staker, }); }); @@ -184,7 +160,7 @@ export default function() { await setDAOProtocolBootstrapSetting(RocketDAOProtocolSettingsDeposit, 'deposit.assign.enabled', false, { from: owner }); // Attempt to assign deposits - await shouldRevert(assignDepositsV2({ + await shouldRevert(assignDeposits({ from: staker, }), 'Assigned deposits while deposit assignment is disabled'); }); @@ -195,25 +171,24 @@ export default function() { it(printTitle('random address', 'can check maximum deposit amount'), async () => { const rocketDepositPool = await RocketDepositPool.deployed(); - // Disable deposits await setDAOProtocolBootstrapSetting(RocketDAOProtocolSettingsDeposit, 'deposit.enabled', false, { from: owner }); assertBN.equal(await rocketDepositPool.getMaximumDepositAmount(), 0, 'Invalid maximum deposit amount'); - // Enable deposits await setDAOProtocolBootstrapSetting(RocketDAOProtocolSettingsDeposit, 'deposit.enabled', true, { from: owner }); const depositPoolMaximum = '100'.ether; await setDAOProtocolBootstrapSetting(RocketDAOProtocolSettingsDeposit, 'deposit.pool.maximum', depositPoolMaximum, { from: owner }); assertBN.equal(await rocketDepositPool.getMaximumDepositAmount(), depositPoolMaximum, 'Invalid maximum deposit amount'); - - // Create a 16 ETH minipool - let minipoolRplStake = await getMinipoolMinimumRPLStake(); - let rplStake = minipoolRplStake * 1n; - await mintRPL(owner, node, rplStake); - await nodeStakeRPL(rplStake, { from: node }); - const minipoolBondAmount = '16'.ether; - await createMinipool({ from: node, value: minipoolBondAmount }); - assertBN.equal(await rocketDepositPool.getMaximumDepositAmount(), depositPoolMaximum + minipoolBondAmount, 'Invalid maximum deposit amount'); + // Disable assignments + await setDAOProtocolBootstrapSetting(RocketDAOProtocolSettingsDeposit, 'deposit.assign.enabled', false, { from: owner }); + // Create 4 validators totally 112 ETH extra capacity + for (let i = 0; i < 4; ++i) { + await nodeDeposit(false, false, { value: '4'.ether, from: node }); + } + // Enable assignments to make that extra capacity usable + await setDAOProtocolBootstrapSetting(RocketDAOProtocolSettingsDeposit, 'deposit.assign.enabled', true, { from: owner }); + // Check that maximum is correct + assertBN.equal(await rocketDepositPool.getMaximumDepositAmount(), depositPoolMaximum + '112'.ether, 'Invalid maximum deposit amount'); }); }); } diff --git a/test/deposit/scenario-assign-deposits-v2.js b/test/deposit/scenario-assign-deposits-v2.js deleted file mode 100644 index f3581f5b..00000000 --- a/test/deposit/scenario-assign-deposits-v2.js +++ /dev/null @@ -1,130 +0,0 @@ -import { - RocketDepositPool, - RocketDAOProtocolSettingsDeposit, - RocketMinipoolQueue, - RocketDAOProtocolSettingsMinipool, - RocketVault, - RocketMinipoolDelegate, -} from '../_utils/artifacts'; -import { assertBN } from '../_helpers/bn'; -import { BigMin } from '../_helpers/bigmath'; - -const hre = require('hardhat'); -const ethers = hre.ethers; - -// Assign deposits to minipools -export async function assignDepositsV2(txOptions) { - // Load contracts - const [ - rocketDepositPool, - rocketDAOProtocolSettingsDeposit, - rocketMinipoolQueue, - rocketDAOProtocolSettingsMinipool, - rocketVault, - ] = await Promise.all([ - RocketDepositPool.deployed(), - RocketDAOProtocolSettingsDeposit.deployed(), - RocketMinipoolQueue.deployed(), - RocketDAOProtocolSettingsMinipool.deployed(), - RocketVault.deployed(), - ]); - - // Get parameters - let [ - depositPoolBalance, - maxDepositAssignments, - maxSocialisedAssignments, - minipoolQueueLength, - fullMinipoolQueueLength, halfMinipoolQueueLength, emptyMinipoolQueueLength, - fullDepositUserAmount, halfDepositUserAmount, emptyDepositUserAmount, - ] = await Promise.all([ - rocketVault.balanceOf("rocketDepositPool"), - rocketDAOProtocolSettingsDeposit.getMaximumDepositAssignments(), - rocketDAOProtocolSettingsDeposit.getMaximumDepositSocialisedAssignments(), - rocketMinipoolQueue.getLength(), - rocketMinipoolQueue.getLengthLegacy(1), rocketMinipoolQueue.getLengthLegacy(2), rocketMinipoolQueue.getLengthLegacy(3), - rocketDAOProtocolSettingsMinipool.getDepositUserAmount(1), rocketDAOProtocolSettingsMinipool.getDepositUserAmount(2), rocketDAOProtocolSettingsMinipool.getDepositUserAmount(3), - ]); - - // Get queued minipool capacities - let minipoolCapacities = []; - for (let i = 0; i < halfMinipoolQueueLength; ++i) minipoolCapacities.push(halfDepositUserAmount); - for (let i = 0; i < fullMinipoolQueueLength; ++i) minipoolCapacities.push(fullDepositUserAmount); - for (let i = 0; i < emptyMinipoolQueueLength; ++i) minipoolCapacities.push(emptyDepositUserAmount); - - // Get expected deposit assignment parameters - let expectedDepositAssignments = 0n; - let expectedEthAssigned = '0'.ether; - let expectedNodeBalanceUsed = '0'.ether; - let depositBalanceRemaining = depositPoolBalance; - let depositAssignmentsRemaining = maxDepositAssignments; - - while (minipoolCapacities.length > 0 && depositBalanceRemaining >= minipoolCapacities[0] && depositAssignmentsRemaining > 0) { - let capacity = minipoolCapacities.shift(); - ++expectedDepositAssignments; - expectedEthAssigned = expectedEthAssigned.add(capacity); - depositBalanceRemaining = depositBalanceRemaining.sub(capacity); - --depositAssignmentsRemaining; - } - - // No legacy deposits - if (expectedDepositAssignments === 0n) { - let scalingCount = maxSocialisedAssignments; - let totalEthCount = depositPoolBalance / '31'.ether; - expectedDepositAssignments = BigMin(scalingCount, totalEthCount, maxDepositAssignments, minipoolQueueLength); - expectedEthAssigned = '31'.ether * expectedDepositAssignments; - - let indices = [...Array(Number(expectedDepositAssignments)).keys()]; - let addressesInQueue = await Promise.all(indices.map(i => rocketMinipoolQueue.getMinipoolAt(i))); - let minipoolsInQueue = await Promise.all(addressesInQueue.map(a => RocketMinipoolDelegate.at(a))); - let topUpValues = await Promise.all(minipoolsInQueue.map(m => m.getNodeTopUpValue())) - expectedNodeBalanceUsed = topUpValues.reduce((p, c) => p + c, expectedNodeBalanceUsed); - } - - // Get balances - function getBalances() { - return Promise.all([ - rocketDepositPool.getBalance(), - rocketDepositPool.getNodeBalance(), - ethers.provider.getBalance(rocketVault.target), - ]).then( - ([depositPoolEth, depositPoolNodeEth, vaultEth]) => - ({depositPoolEth, depositPoolNodeEth, vaultEth}) - ); - } - - // Get minipool queue details - function getMinipoolQueueDetails() { - return Promise.all([ - rocketMinipoolQueue.getTotalLength(), - rocketMinipoolQueue.getTotalCapacity(), - ]).then( - ([totalLength, totalCapacity]) => - ({totalLength, totalCapacity}) - ); - } - - // Get initial balances & minipool queue details - let [balances1, queue1] = await Promise.all([ - getBalances(), - getMinipoolQueueDetails(), - ]); - - // Assign deposits - await rocketDepositPool.connect(txOptions.from).assignDeposits(txOptions); - - // Get updated balances & minipool queue details - let [balances2, queue2] = await Promise.all([ - getBalances(), - getMinipoolQueueDetails(), - ]); - - // Check balances - assertBN.equal(balances2.depositPoolEth, balances1.depositPoolEth - expectedEthAssigned, 'Incorrect updated deposit pool ETH balance'); - assertBN.equal(balances2.depositPoolNodeEth, balances1.depositPoolNodeEth - expectedNodeBalanceUsed, 'Incorrect updated deposit pool node ETH balance'); - assertBN.equal(balances2.vaultEth, balances1.vaultEth - expectedEthAssigned, 'Incorrect updated vault ETH balance'); - - // Check minipool queues - assertBN.equal(queue2.totalLength, queue1.totalLength - BigInt(expectedDepositAssignments), 'Incorrect updated minipool queue length'); - assertBN.equal(queue2.totalCapacity, queue1.totalCapacity - expectedEthAssigned, 'Incorrect updated minipool queue capacity'); -} diff --git a/test/deposit/scenario-assign-deposits.js b/test/deposit/scenario-assign-deposits.js index 3b907448..d081f84c 100644 --- a/test/deposit/scenario-assign-deposits.js +++ b/test/deposit/scenario-assign-deposits.js @@ -1,103 +1,14 @@ -import { - RocketDAOProtocolSettingsDeposit, - RocketDAOProtocolSettingsMinipool, - RocketDepositPool, - RocketMinipoolQueue, - RocketVault, -} from '../_utils/artifacts'; -import { assertBN } from '../_helpers/bn'; +import { RocketDepositPool } from '../_utils/artifacts'; // Assign deposits to minipools export async function assignDeposits(txOptions) { - // Load contracts const [ rocketDepositPool, - rocketDAOProtocolSettingsDeposit, - rocketMinipoolQueue, - rocketDAOProtocolSettingsMinipool, - rocketVault, ] = await Promise.all([ RocketDepositPool.deployed(), - RocketDAOProtocolSettingsDeposit.deployed(), - RocketMinipoolQueue.deployed(), - RocketDAOProtocolSettingsMinipool.deployed(), - RocketVault.deployed(), ]); - // Get parameters - let [ - depositPoolBalance, - maxDepositAssignments, - fullMinipoolQueueLength, halfMinipoolQueueLength, emptyMinipoolQueueLength, - fullDepositUserAmount, halfDepositUserAmount, emptyDepositUserAmount, - ] = await Promise.all([ - rocketDepositPool.getBalance.call(), - rocketDAOProtocolSettingsDeposit.getMaximumDepositAssignments.call(), - rocketMinipoolQueue.getLength.call(1), rocketMinipoolQueue.getLength.call(2), rocketMinipoolQueue.getLength.call(3), - rocketDAOProtocolSettingsMinipool.getDepositUserAmount(1), rocketDAOProtocolSettingsMinipool.getDepositUserAmount(2), rocketDAOProtocolSettingsMinipool.getDepositUserAmount(3), - ]); - - // Get queued minipool capacities - let minipoolCapacities = []; - for (let i = 0; i < halfMinipoolQueueLength; ++i) minipoolCapacities.push(halfDepositUserAmount); - for (let i = 0; i < fullMinipoolQueueLength; ++i) minipoolCapacities.push(fullDepositUserAmount); - for (let i = 0; i < emptyMinipoolQueueLength; ++i) minipoolCapacities.push(emptyDepositUserAmount); - - // Get expected deposit assignment parameters - let expectedDepositAssignments = 0; - let expectedEthAssigned = 0n; - let depositBalanceRemaining = depositPoolBalance; - let depositAssignmentsRemaining = maxDepositAssignments; - while (minipoolCapacities.length > 0 && depositBalanceRemaining.gte(minipoolCapacities[0]) && depositAssignmentsRemaining > 0) { - let capacity = minipoolCapacities.shift(); - ++expectedDepositAssignments; - expectedEthAssigned = expectedEthAssigned.add(capacity); - depositBalanceRemaining = depositBalanceRemaining.sub(capacity); - --depositAssignmentsRemaining; - } - - // Get balances - function getBalances() { - return Promise.all([ - rocketDepositPool.getBalance.call(), - web3.eth.getBalance(rocketVault.address).then(value => value.BN), - ]).then( - ([depositPoolEth, vaultEth]) => - ({ depositPoolEth, vaultEth }), - ); - } - - // Get minipool queue details - function getMinipoolQueueDetails() { - return Promise.all([ - rocketMinipoolQueue.getTotalLength.call(), - rocketMinipoolQueue.getTotalCapacity.call(), - ]).then( - ([totalLength, totalCapacity]) => - ({ totalLength, totalCapacity }), - ); - } - - // Get initial balances & minipool queue details - let [balances1, queue1] = await Promise.all([ - getBalances(), - getMinipoolQueueDetails(), - ]); - - // Assign deposits - await rocketDepositPool.assignDeposits(txOptions); - - // Get updated balances & minipool queue details - let [balances2, queue2] = await Promise.all([ - getBalances(), - getMinipoolQueueDetails(), - ]); - - // Check balances - assertBN.equal(balances2.depositPoolEth, balances1.depositPoolEth.sub(expectedEthAssigned), 'Incorrect updated deposit pool ETH balance'); - assertBN.equal(balances2.vaultEth, balances1.vaultEth.sub(expectedEthAssigned), 'Incorrect updated vault ETH balance'); + await rocketDepositPool.connect(txOptions.from).assignDeposits(); - // Check minipool queues - assertBN.equal(queue2.totalLength, queue1.totalLength.sub(expectedDepositAssignments.BN), 'Incorrect updated minipool queue length'); - assertBN.equal(queue2.totalCapacity, queue1.totalCapacity.sub(expectedEthAssigned), 'Incorrect updated minipool queue capacity'); + // TODO: Check pre and post conditions on assigning deposits } diff --git a/test/megapool/megapool-tests.js b/test/megapool/megapool-tests.js index 0120090a..2d12b094 100644 --- a/test/megapool/megapool-tests.js +++ b/test/megapool/megapool-tests.js @@ -21,7 +21,7 @@ const hre = require('hardhat'); const ethers = hre.ethers; export default function() { - describe.only('Megapools', () => { + describe('Megapools', () => { let owner, node, nodeWithdrawalAddress, diff --git a/test/network/network-penalties-tests.js b/test/network/network-penalties-tests.js index 0c9fbb0f..f49d3720 100644 --- a/test/network/network-penalties-tests.js +++ b/test/network/network-penalties-tests.js @@ -15,7 +15,8 @@ const hre = require('hardhat'); const ethers = hre.ethers; export default function() { - describe('RocketNetworkPenalties', () => { + // TODO: Update penalties to Saturn once implemented + describe.skip('RocketNetworkPenalties', () => { let owner, node, trustedNode1, diff --git a/test/network/network-voting-tests.js b/test/network/network-voting-tests.js index 5851bcd2..7bf1541d 100644 --- a/test/network/network-voting-tests.js +++ b/test/network/network-voting-tests.js @@ -14,7 +14,8 @@ const hre = require('hardhat'); const ethers = hre.ethers; export default function() { - describe('RocketNetworkVoting', () => { + // TODO: Update to Saturn once implemented + describe.skip('RocketNetworkVoting', () => { let owner, node, random; diff --git a/test/node/node-deposit-tests.js b/test/node/node-deposit-tests.js index 19440dcd..a743a8bc 100644 --- a/test/node/node-deposit-tests.js +++ b/test/node/node-deposit-tests.js @@ -25,7 +25,8 @@ const hre = require('hardhat'); const ethers = hre.ethers; export default function() { - describe('RocketNodeDeposit', () => { + // TODO: Remove irrelevant tests for Saturn + describe.skip('RocketNodeDeposit', () => { let owner, node, trustedNode, diff --git a/test/node/node-distributor-tests.js b/test/node/node-distributor-tests.js index abe9afac..18987dba 100644 --- a/test/node/node-distributor-tests.js +++ b/test/node/node-distributor-tests.js @@ -19,7 +19,8 @@ const hre = require('hardhat'); const ethers = hre.ethers; export default function() { - describe('RocketNodeDistributor', () => { + // TODO: Migrate to upgrade tests once implemented for Saturn + describe.skip('RocketNodeDistributor', () => { let owner, node1, node2, diff --git a/test/node/node-staking-tests.js b/test/node/node-staking-tests.js index 159954b6..7a21fbe6 100644 --- a/test/node/node-staking-tests.js +++ b/test/node/node-staking-tests.js @@ -47,7 +47,6 @@ export default function() { let rocketNodeStaking; before(async () => { await globalSnapShot(); - [ owner, node, @@ -56,42 +55,31 @@ export default function() { rplWithdrawalAddress, withdrawalAddress, ] = await ethers.getSigners(); - // Load contracts rocketNodeStaking = await RocketNodeStaking.deployed(); - // Set settings await setDAONodeTrustedBootstrapSetting(RocketDAONodeTrustedSettingsMinipool, 'minipool.scrub.period', scrubPeriod, { from: owner }); - // Register node await registerNode({ from: node }); - // Register trusted node await registerNode({ from: trustedNode }); await setNodeTrusted(trustedNode, 'saas_1', 'node1@home.com', owner); - // Mint RPL to accounts const rplAmount = '10000'.ether; await mintRPL(owner, node, rplAmount); await mintRPL(owner, random, rplAmount); await mintRPL(owner, rplWithdrawalAddress, rplAmount); await mintRPL(owner, withdrawalAddress, rplAmount); - }); it(printTitle('node operator', 'can stake RPL'), async () => { // Set parameters const rplAmount = '5000'.ether; - // Approve transfer & stake RPL once await approveRPL(rocketNodeStaking.target, rplAmount, { from: node }); await stakeRpl(rplAmount, { from: node, }); - - // Make node deposit / create minipool - await nodeDeposit({ from: node, value: '16'.ether }); - // Approve transfer & stake RPL twice await approveRPL(rocketNodeStaking.target, rplAmount, { from: node }); await stakeRpl(rplAmount, { @@ -102,7 +90,6 @@ export default function() { it(printTitle('random address', 'cannot stake RPL'), async () => { // Set parameters const rplAmount = '10000'.ether; - // Approve transfer & attempt to stake RPL await approveRPL(rocketNodeStaking.target, rplAmount, { from: node }); await shouldRevert(stakeRpl(rplAmount, { @@ -113,13 +100,10 @@ export default function() { it(printTitle('node operator', 'can withdraw staked RPL'), async () => { // Set parameters const rplAmount = '10000'.ether; - // Remove withdrawal cooldown period await setDAOProtocolBootstrapSetting(RocketDAOProtocolSettingsRewards, 'rewards.claimsperiods', 0, { from: owner }); - // Stake RPL await nodeStakeRPL(rplAmount, { from: node }); - // Withdraw staked RPL await withdrawRpl(rplAmount, { from: node, @@ -143,110 +127,88 @@ export default function() { // Set parameters const stakeAmount = '10000'.ether; const withdrawAmount = '20000'.ether; - // Remove withdrawal cooldown period await setDAOProtocolBootstrapSetting(RocketDAOProtocolSettingsRewards, 'rewards.claimsperiods', 0, { from: owner }); - // Stake RPL await nodeStakeRPL(stakeAmount, { from: node }); - // Withdraw staked RPL await shouldRevert(withdrawRpl(withdrawAmount, { from: node, }), 'Withdrew more RPL than was staked'); }); - it(printTitle('node operator', 'cannot withdraw RPL leaving the node undercollateralised'), async () => { + it.skip(printTitle('node operator', 'cannot withdraw RPL leaving the node undercollateralised'), async () => { // Set parameters const rplAmount = '10000'.ether; - // Remove withdrawal cooldown period await setDAOProtocolBootstrapSetting(RocketDAOProtocolSettingsRewards, 'rewards.claimsperiods', 0, { from: owner }); - // Stake RPL await nodeStakeRPL(rplAmount, { from: node }); - // Make node deposit / create minipool await nodeDeposit({ from: node, value: '16'.ether }); - // Withdraw staked RPL await shouldRevert(withdrawRpl(rplAmount, { from: node, }), 'Withdrew RPL leaving the node undercollateralised'); }); - it(printTitle('node operator', 'can withdraw RPL after finalising their minipool'), async () => { + it.skip(printTitle('node operator', 'can withdraw RPL after finalising their minipool'), async () => { // Set parameters const rplAmount = '10000'.ether; - // Remove withdrawal cooldown period await setDAOProtocolBootstrapSetting(RocketDAOProtocolSettingsRewards, 'rewards.claimsperiods', 0, { from: owner }); - // Stake RPL await nodeStakeRPL(rplAmount, { from: node }); - // Create a staking minipool await userDeposit({ from: random, value: '16'.ether }); const minipool = await createMinipool({ from: node, value: '16'.ether }); await helpers.time.increase(scrubPeriod + 1); await stakeMinipool(minipool, { from: node }); - // Cannot withdraw RPL yet await shouldRevert(withdrawRpl(rplAmount, { from: node, }), 'Withdrew RPL leaving the node undercollateralised'); - // Still cannot withdraw RPL yet await shouldRevert(withdrawRpl(rplAmount, { from: node, }), 'Withdrew RPL leaving the node undercollateralised'); - // Withdraw and finalise await withdrawValidatorBalance(minipool, '32'.ether, node, true); - // Should be able to withdraw now await withdrawRpl(rplAmount, { from: node, }); }); - it(printTitle('node operator', 'cannot withdraw RPL if random distributes balance on their minipool until they finalise'), async () => { + it.skip(printTitle('node operator', 'cannot withdraw RPL if random distributes balance on their minipool until they finalise'), async () => { // Set parameters const rplAmount = '10000'.ether; - // Remove withdrawal cooldown period await setDAOProtocolBootstrapSetting(RocketDAOProtocolSettingsRewards, 'rewards.claimsperiods', 0, { from: owner }); - // Stake RPL await nodeStakeRPL(rplAmount, { from: node }); - // Create a staking minipool await userDeposit({ from: random, value: '16'.ether }); const minipool = await createMinipool({ from: node, value: '16'.ether }); await helpers.time.increase(scrubPeriod + 1); await stakeMinipool(minipool, { from: node }); - // Send ETH to the minipool to simulate receving from SWC await trustedNode.sendTransaction({ to: minipool.target, value: '32'.ether, }); - // Begin user distribution process await beginUserDistribute(minipool, { from: random }); // Wait await helpers.time.increase(userDistributeStartTime + 1); // Withdraw without finalising await withdrawValidatorBalance(minipool, '0'.ether, random); - // Cannot withdraw RPL yet await shouldRevert(withdrawRpl(rplAmount, { from: node, }), 'Withdrew RPL leaving the node undercollateralised'); - // Finalise the pool await minipool.connect(node).finalise(); - // Should be able to withdraw now await withdrawRpl(rplAmount, { from: node, @@ -256,13 +218,10 @@ export default function() { it(printTitle('random address', 'cannot withdraw staked RPL'), async () => { // Set parameters const rplAmount = '10000'.ether; - // Remove withdrawal cooldown period await setDAOProtocolBootstrapSetting(RocketDAOProtocolSettingsRewards, 'rewards.claimsperiods', 0, { from: owner }); - // Stake RPL await nodeStakeRPL(rplAmount, { from: node }); - // Withdraw staked RPL await shouldRevert(withdrawRpl(rplAmount, { from: random, @@ -272,7 +231,6 @@ export default function() { it(printTitle('random address', 'cannot stake on behalf of a node without allowance'), async () => { // Set parameters const rplAmount = '10000'.ether; - // Stake RPL await shouldRevert(nodeStakeRPLFor(node, rplAmount, { from: random }), 'Was able to stake', 'Not allowed to stake for'); }); @@ -280,10 +238,8 @@ export default function() { it(printTitle('random address', 'can stake on behalf of a node with allowance'), async () => { // Set parameters const rplAmount = '10000'.ether; - // Allow await setStakeRPLForAllowed(random, true, { from: node }); - // Stake RPL await nodeStakeRPLFor(node, rplAmount, { from: random }); }); @@ -291,16 +247,12 @@ export default function() { it(printTitle('random address', 'can stake on behalf of a node with allowance from withdrawal address'), async () => { // Set parameters const rplAmount = '10000'.ether; - // Set RPL withdrawal address await setNodeRPLWithdrawalAddress(node, rplWithdrawalAddress, { from: node }); - // Not allowed to set from node address any more await shouldRevert(setStakeRPLForAllowed(random, true, { from: node }), 'Was able to allow', 'Must be called from RPL withdrawal address'); - // Allow from RPL withdrawal address await setStakeRPLForAllowedWithNodeAddress(node, random, true, { from: rplWithdrawalAddress }); - // Stake RPL await nodeStakeRPLFor(node, rplAmount, { from: random }); }); @@ -308,10 +260,8 @@ export default function() { it(printTitle('node operator', 'cannot stake from node address once RPL withdrawal address is set'), async () => { // Set parameters const rplAmount = '10000'.ether; - // Set RPL withdrawal address await setNodeRPLWithdrawalAddress(node, rplWithdrawalAddress, { from: node }); - // Stake RPL await shouldRevert(nodeStakeRPL(rplAmount, { from: node }), 'Was able to stake', 'Not allowed to stake for'); }); @@ -319,10 +269,8 @@ export default function() { it(printTitle('node operator', 'can stake from primary withdrawal address'), async () => { // Set parameters const rplAmount = '10000'.ether; - // Set RPL withdrawal address await setNodeWithdrawalAddress(node, withdrawalAddress, { from: node }); - // Stake RPL await nodeStakeRPLFor(node, rplAmount, { from: withdrawalAddress }); }); @@ -330,10 +278,8 @@ export default function() { it(printTitle('node operator', 'can stake from RPL withdrawal address'), async () => { // Set parameters const rplAmount = '10000'.ether; - // Set RPL withdrawal address await setNodeRPLWithdrawalAddress(node, rplWithdrawalAddress, { from: node }); - // Stake RPL await nodeStakeRPLFor(node, rplAmount, { from: rplWithdrawalAddress }); }); diff --git a/test/rewards/rewards-tests.js b/test/rewards/rewards-tests.js index cca329b8..a253f291 100644 --- a/test/rewards/rewards-tests.js +++ b/test/rewards/rewards-tests.js @@ -3,7 +3,6 @@ import { printTitle } from '../_utils/formatting'; import { shouldRevert } from '../_utils/testing'; import { submitPrices } from '../_helpers/network'; import { - getNodeEffectiveRPLStake, nodeStakeRPL, registerNode, setNodeRPLWithdrawalAddress, @@ -12,7 +11,6 @@ import { } from '../_helpers/node'; import { RevertOnTransfer, RocketDAONodeTrustedProposals, - RocketDAONodeTrustedSettingsMinipool, RocketDAOProtocolSettingsNode, RocketMerkleDistributorMainnet, RocketRewardsPool, @@ -27,9 +25,6 @@ import { setRPLInflationStartTime, } from '../dao/scenario-dao-protocol-bootstrap'; import { mintRPL } from '../_helpers/tokens'; -import { createMinipool, stakeMinipool } from '../_helpers/minipool'; -import { userDeposit } from '../_helpers/deposit'; -import { setDAONodeTrustedBootstrapSetting } from '../dao/scenario-dao-node-trusted-bootstrap'; import { executeRewards, submitRewards } from './scenario-submit-rewards'; import { claimRewards } from './scenario-claim-rewards'; import { claimAndStakeRewards } from './scenario-claim-and-stake-rewards'; @@ -129,9 +124,6 @@ export default function() { let slotTimestamp = '1600000000'; - // Set settings - await setDAONodeTrustedBootstrapSetting(RocketDAONodeTrustedSettingsMinipool, 'minipool.scrub.period', scrubPeriod, { from: owner }); - // Register nodes await registerNode({ from: registeredNode1 }); await registerNode({ from: registeredNode2 }); @@ -158,28 +150,6 @@ export default function() { await mintRPL(owner, registeredNode2, '32'.ether); await nodeStakeRPL('32'.ether, { from: registeredNode1 }); await nodeStakeRPL('32'.ether, { from: registeredNode2 }); - - // User deposits - await userDeposit({ from: userOne, value: '48'.ether }); - - // Create minipools - let minipool1 = await createMinipool({ from: registeredNode1, value: '16'.ether }); - let minipool2 = await createMinipool({ from: registeredNode2, value: '16'.ether }); - let minipool3 = await createMinipool({ from: registeredNode2, value: '16'.ether }); - - // Wait required scrub period - await helpers.time.increase(scrubPeriod + 1); - - // Stake minipools - await stakeMinipool(minipool1, { from: registeredNode1 }); - await stakeMinipool(minipool2, { from: registeredNode2 }); - await stakeMinipool(minipool3, { from: registeredNode2 }); - - // Check node effective stakes - let node1EffectiveStake = await getNodeEffectiveRPLStake(registeredNode1); - let node2EffectiveStake = await getNodeEffectiveRPLStake(registeredNode2); - assertBN.equal(node1EffectiveStake, '16'.ether, 'Incorrect node 1 effective stake'); - assertBN.equal(node2EffectiveStake, '32'.ether, 'Incorrect node 2 effective stake'); }); /*** Setting Claimers *************************/ diff --git a/test/rocket-pool-tests.js b/test/rocket-pool-tests.js index 5a2eac33..10108e07 100644 --- a/test/rocket-pool-tests.js +++ b/test/rocket-pool-tests.js @@ -59,13 +59,14 @@ before(async function() { }); // Run tests -// auctionTests(); +auctionTests(); daoProtocolTests(); daoProtocolTreasuryTests(); daoNodeTrustedTests(); daoSecurityTests(); depositPoolTests(); megapoolTests(); +// TODO: Move minipool tests into upgrade test suite and strip them back to just the available Saturn functionality // minipoolScrubTests(); // minipoolTests(); // minipoolVacantTests(); diff --git a/test/token/reth-tests.js b/test/token/reth-tests.js index 94f76b10..7c69611e 100644 --- a/test/token/reth-tests.js +++ b/test/token/reth-tests.js @@ -1,32 +1,20 @@ -import { describe, it, before } from 'mocha'; +import { before, describe, it } from 'mocha'; import { printTitle } from '../_utils/formatting'; import { shouldRevert } from '../_utils/testing'; import { getDepositExcessBalance, userDeposit } from '../_helpers/deposit'; -import { createMinipool, getMinipoolMinimumRPLStake, stakeMinipool } from '../_helpers/minipool'; -import { submitBalances } from '../_helpers/network'; -import { nodeStakeRPL, registerNode, setNodeTrusted, setNodeWithdrawalAddress } from '../_helpers/node'; -import { - depositExcessCollateral, - getRethBalance, - getRethCollateralRate, - getRethExchangeRate, - getRethTotalSupply, - mintRPL, -} from '../_helpers/tokens'; +import { registerNode, setNodeTrusted } from '../_helpers/node'; +import { depositExcessCollateral, getRethBalance, getRethCollateralRate } from '../_helpers/tokens'; import { burnReth } from './scenario-reth-burn'; import { transferReth } from './scenario-reth-transfer'; import { - RocketDAONodeTrustedSettingsMinipool, - RocketDAOProtocolSettingsMinipool, + RocketDAOProtocolSettingsDeposit, RocketDAOProtocolSettingsNetwork, RocketTokenRETH, } from '../_utils/artifacts'; import { setDAOProtocolBootstrapSetting } from '../dao/scenario-dao-protocol-bootstrap'; -import { beginUserDistribute, withdrawValidatorBalance } from '../minipool/scenario-withdraw-validator-balance'; -import { setDAONodeTrustedBootstrapSetting } from '../dao/scenario-dao-node-trusted-bootstrap'; import { assertBN } from '../_helpers/bn'; -import * as assert from 'assert'; import { globalSnapShot } from '../_utils/snapshotting'; +import { getMegapoolForNode, nodeDeposit } from '../_helpers/megapool'; const helpers = require('@nomicfoundation/hardhat-network-helpers'); const hre = require('hardhat'); @@ -36,21 +24,15 @@ export default function() { describe('RocketTokenRETH', () => { let owner, node, - nodeWithdrawalAddress, trustedNode, staker1, staker2, random; - let scrubPeriod = (60 * 60 * 24); // 24 hours - // Setup - let minipool; - let withdrawalBalance = '36'.ether; - let rethBalance; - let submitPricesFrequency = 500; - let depositDeplay = 100; - const userDistributeStartTime = 60 * 60 * 24 * 90; // 90 days + const submitPricesFrequency = 500; + const depositFeePerc = '0.005'.ether; // 0.5% deposit fee + const rethCollateralRate = '1'.ether; before(async () => { await globalSnapShot(); @@ -58,93 +40,38 @@ export default function() { [ owner, node, - nodeWithdrawalAddress, trustedNode, staker1, staker2, random, ] = await ethers.getSigners(); - let slotTimestamp = '1600000000'; - - // Get current rETH exchange rate - let exchangeRate1 = await getRethExchangeRate(); - - // Make deposit - await userDeposit({ from: staker1, value: '16'.ether }); - - // Register node & set withdrawal address - await registerNode({ from: node }); - await setNodeWithdrawalAddress(node, nodeWithdrawalAddress, { from: node }); - - // Register trusted node - await registerNode({ from: trustedNode }); - await setNodeTrusted(trustedNode, 'saas_1', 'node@home.com', owner); - // Set settings - await setDAOProtocolBootstrapSetting(RocketDAOProtocolSettingsNetwork, 'network.reth.collateral.target', '1'.ether, { from: owner }); + await setDAOProtocolBootstrapSetting(RocketDAOProtocolSettingsNetwork, 'network.reth.collateral.target', rethCollateralRate, { from: owner }); await setDAOProtocolBootstrapSetting(RocketDAOProtocolSettingsNetwork, 'network.submit.prices.frequency', submitPricesFrequency, { from: owner }); - await setDAOProtocolBootstrapSetting(RocketDAOProtocolSettingsNetwork, 'network.reth.deposit.delay', depositDeplay, { from: owner }); - await setDAOProtocolBootstrapSetting(RocketDAOProtocolSettingsMinipool, 'minipool.user.distribute.window.start', userDistributeStartTime, { from: owner }); - await setDAONodeTrustedBootstrapSetting(RocketDAONodeTrustedSettingsMinipool, 'minipool.scrub.period', scrubPeriod, { from: owner }); - - // Stake RPL to cover minipools - let rplStake = await getMinipoolMinimumRPLStake(); - await mintRPL(owner, node, rplStake); - await nodeStakeRPL(rplStake, { from: node }); - - // Create withdrawable minipool - minipool = await createMinipool({ from: node, value: '16'.ether }); - await helpers.time.increase(scrubPeriod + 1); - await stakeMinipool(minipool, { from: node }); - - // Update network ETH total to alter rETH exchange rate - let rethSupply = await getRethTotalSupply(); - let nodeFee = await minipool.getNodeFee.call(); - let depositBalance = '32'.ether; - let userAmount = '16'.ether; - let rewards = withdrawalBalance - depositBalance; - let halfRewards = rewards / 2n; - let nodeCommissionFee = halfRewards * nodeFee / '1'.ether; - let ethBalance = userAmount + (halfRewards - nodeCommissionFee); - await submitBalances(1, slotTimestamp, ethBalance, 0, rethSupply, { from: trustedNode }); - - // Get & check staker rETH balance - rethBalance = await getRethBalance(staker1); - assertBN.isAbove(rethBalance, 0, 'Incorrect staker rETH balance'); - - // Get & check updated rETH exchange rate - let exchangeRate2 = await getRethExchangeRate(); - assert.notEqual(exchangeRate1, exchangeRate2, 'rETH exchange rate has not changed'); + await setDAOProtocolBootstrapSetting(RocketDAOProtocolSettingsDeposit, 'deposit.fee', depositFeePerc, { from: owner }); }); - it(printTitle('rETH holder', 'can transfer rETH after enough time has passed'), async () => { + it(printTitle('rETH holder', 'can transfer rETH after deposit'), async () => { // Make user deposit const depositAmount = '20'.ether; await userDeposit({ from: staker2, value: depositAmount }); - - // Wait "network.reth.deposit.delay" blocks - await helpers.mine(depositDeplay); - + const rethBalance = await getRethBalance(staker1.address); // Transfer rETH await transferReth(random, rethBalance, { from: staker1, }); }); - it(printTitle('rETH holder', 'can transfer rETH without waiting if received via transfer'), async () => { + it(printTitle('rETH holder', 'can transfer rETH if received via transfer'), async () => { // Make user deposit const depositAmount = '20'.ether; await userDeposit({ from: staker2, value: depositAmount }); - - // Wait "network.reth.deposit.delay" blocks - await helpers.mine(depositDeplay); - + const rethBalance = await getRethBalance(staker1.address); // Transfer rETH await transferReth(random, rethBalance, { from: staker1, }); - // Transfer rETH again await transferReth(staker1, rethBalance, { from: random, @@ -152,40 +79,10 @@ export default function() { }); it(printTitle('rETH holder', 'can burn rETH for ETH collateral'), async () => { - // Wait "network.reth.deposit.delay" blocks - await helpers.mine(depositDeplay); - - // Send ETH to the minipool to simulate receiving from SWC - await trustedNode.sendTransaction({ - to: minipool.target, - value: withdrawalBalance, - }); - - // Begin user distribution process - await beginUserDistribute(minipool, { from: random }); - // Wait - await helpers.time.increase(userDistributeStartTime + 1); - // Withdraw without finalising - await withdrawValidatorBalance(minipool, '0'.ether, random); - - // Burn rETH - await burnReth(rethBalance, { - from: staker1, - }); - }); - - it(printTitle('rETH holder', 'can burn rETH for excess deposit pool ETH'), async () => { // Make user deposit const depositAmount = '20'.ether; - await userDeposit({ from: staker2, value: depositAmount }); - - // Check deposit pool excess balance - let excessBalance = await getDepositExcessBalance(); - assertBN.equal(excessBalance, depositAmount, 'Incorrect deposit pool excess balance'); - - // Wait "network.reth.deposit.delay" blocks - await helpers.mine(depositDeplay); - + await userDeposit({ from: staker1, value: depositAmount }); + const rethBalance = await getRethBalance(staker1.address); // Burn rETH await burnReth(rethBalance, { from: staker1, @@ -193,65 +90,27 @@ export default function() { }); it(printTitle('rETH holder', 'cannot burn an invalid amount of rETH'), async () => { - // Wait "network.reth.deposit.delay" blocks - await helpers.mine(depositDeplay); - - // Send ETH to the minipool to simulate receving from SWC - await trustedNode.sendTransaction({ - to: minipool.target, - value: withdrawalBalance, - }); - - // Begin user distribution process - await beginUserDistribute(minipool, { from: random }); - // Wait - await helpers.time.increase(userDistributeStartTime + 1); - // Withdraw without finalising - await withdrawValidatorBalance(minipool, '0'.ether, random); - + // Make user deposit + const depositAmount = '20'.ether; + await userDeposit({ from: staker1, value: depositAmount }); + const rethBalance = await getRethBalance(staker1.address); // Get burn amounts let burnZero = '0'.ether; let burnExcess = '100'.ether; assertBN.isAbove(burnExcess, rethBalance, 'Burn amount does not exceed rETH balance'); - // Attempt to burn 0 rETH await shouldRevert(burnReth(burnZero, { from: staker1, }), 'Burned an invalid amount of rETH'); - // Attempt to burn too much rETH await shouldRevert(burnReth(burnExcess, { from: staker1, }), 'Burned an amount of rETH greater than the token balance'); }); - it(printTitle('rETH holder', 'cannot burn rETH with insufficient collateral'), async () => { - // Wait "network.reth.deposit.delay" blocks - await helpers.mine(depositDeplay); - - // Attempt to burn rETH for contract collateral - await shouldRevert(burnReth(rethBalance, { - from: staker1, - }), 'Burned rETH with an insufficient contract ETH balance'); - - // Make user deposit - const depositAmount = '10'.ether; - await userDeposit({ from: staker2, value: depositAmount }); - - // Check deposit pool excess balance - let excessBalance = await getDepositExcessBalance(); - assertBN.equal(excessBalance, depositAmount, 'Incorrect deposit pool excess balance'); - - // Attempt to burn rETH for excess deposit pool ETH - await shouldRevert(burnReth(rethBalance, { - from: staker1, - }), 'Burned rETH with an insufficient deposit pool excess ETH balance'); - }); - it(printTitle('random', 'can deposit excess collateral into the deposit pool'), async () => { - // Get rETH contract - const rocketTokenRETH = await RocketTokenRETH.deployed(); // Send enough ETH to rETH contract to exceed target collateralisation rate + const rocketTokenRETH = await RocketTokenRETH.deployed(); await random.sendTransaction({ to: rocketTokenRETH.target, value: '32'.ether, @@ -263,5 +122,55 @@ export default function() { // Collateral rate should now be 1 (the target rate) assertBN.equal(collateralRate, '1'.ether); }); + + describe('With node deposit', () => { + let rethBalance; + + const depositAmount = '28'.ether; + + before(async () => { + // Make a user deposit enough to create a 4 ETH bonded validator + await userDeposit({ from: staker1, value: depositAmount }); + rethBalance = await getRethBalance(staker1.address); + // Register trusted node + await registerNode({ from: trustedNode }); + await setNodeTrusted(trustedNode, 'saas_1', 'node@home.com', owner); + // Register node and create a validator + await registerNode({ from: node }); + await nodeDeposit(false, false, { value: '4'.ether, from: node }); + await getMegapoolForNode(node); + }); + + it(printTitle('rETH holder', 'can burn rETH for excess deposit pool ETH'), async () => { + // Make a user deposit from another stake large enough to burn staker1's rETH balance + await userDeposit({ from: staker2, value: depositAmount }); + // Check deposit pool excess balance + let excessBalance = await getDepositExcessBalance(); + assertBN.equal(excessBalance, depositAmount, 'Incorrect deposit pool excess balance'); + // Burn rETH + await burnReth(rethBalance, { + from: staker1, + }); + }); + + it(printTitle('rETH holder', 'cannot burn rETH with insufficient collateral'), async () => { + // Attempt to burn rETH for contract collateral + await shouldRevert(burnReth(rethBalance, { + from: staker1, + }), 'Burned rETH with an insufficient contract ETH balance'); + // Make user deposit + const depositAmount = '10'.ether; + await userDeposit({ from: staker2, value: depositAmount }); + // Check deposit pool excess balance + let excessBalance = await getDepositExcessBalance(); + assertBN.equal(excessBalance, depositAmount, 'Incorrect deposit pool excess balance'); + // Attempt to burn rETH for excess deposit pool ETH + await shouldRevert(burnReth(rethBalance, { + from: staker1, + }), 'Burned rETH with an insufficient deposit pool excess ETH balance'); + }); + }); + + // TODO: Add tests for burning rETH and receiving validator rewards once megapool distributes are implemented }); } diff --git a/test/util/util-tests.js b/test/util/util-tests.js index 9edd8cb5..b85ef45e 100644 --- a/test/util/util-tests.js +++ b/test/util/util-tests.js @@ -30,7 +30,7 @@ export default function() { it(printTitle('random', 'pack/unpack shouldnt change values'), async () => { const linkedListStorage = await LinkedListStorage.deployed(); let item = { - receiver: random, + receiver: random.address, validatorId: 1, suppliedValue: 8000, requestedValue: 32000, @@ -46,7 +46,7 @@ export default function() { it(printTitle('random', 'can enqueue/dequeue items'), async () => { const linkedListStorage = await LinkedListStorage.deployed(); let itemIn = { - receiver: random, + receiver: random.address, validatorId: 1, suppliedValue: 8000, requestedValue: 32000, @@ -72,22 +72,22 @@ export default function() { // remove the second item await linkedListStorage.removeItem(regularQueue, itemIn) - let first = await linkedListStorage.getItem.call(regularQueue, 1); + let first = await linkedListStorage.getItem(regularQueue, 1); assert.equal(first.validatorId, 1) - let last = await linkedListStorage.getItem.call(regularQueue, 3); + let last = await linkedListStorage.getItem(regularQueue, 3); assert.equal(last.validatorId, 3) await linkedListStorage.dequeueItem(regularQueue) - listLength = await linkedListStorage.getLength.call(regularQueue); + listLength = await linkedListStorage.getLength(regularQueue); assert.equal(listLength, 1) await linkedListStorage.dequeueItem(regularQueue) - listLength = await linkedListStorage.getLength.call(regularQueue); + listLength = await linkedListStorage.getLength(regularQueue); assert.equal(listLength, 0) }); it(printTitle('random', 'can remove the only queue item'), async () => { const linkedListStorage = await LinkedListStorage.deployed(); let itemIn = { - receiver: random, + receiver: random.address, validatorId: 1, suppliedValue: 8000, requestedValue: 32000, @@ -101,7 +101,7 @@ export default function() { it(printTitle('random', 'cannot add the same item twice'), async () => { const linkedListStorage = await LinkedListStorage.deployed(); let itemIn = { - receiver: random, + receiver: random.address, validatorId: 1, suppliedValue: 8000, requestedValue: 32000, @@ -115,7 +115,7 @@ export default function() { it(printTitle('random', 'indexOf for non existing item returns 0'), async () => { const linkedListStorage = await LinkedListStorage.deployed(); let itemIn = { - receiver: random, + receiver: random.address, validatorId: 1, suppliedValue: 8000, requestedValue: 32000, @@ -127,7 +127,7 @@ export default function() { it(printTitle('random', 'reverts when trying to remove non existent item'), async () => { const linkedListStorage = await LinkedListStorage.deployed(); let itemIn = { - receiver: random, + receiver: random.address, validatorId: 1, suppliedValue: 8000, requestedValue: 32000, diff --git a/test/util/verifier-tests.js b/test/util/verifier-tests.js index 568321a8..a35baf34 100644 --- a/test/util/verifier-tests.js +++ b/test/util/verifier-tests.js @@ -8,7 +8,7 @@ const hre = require('hardhat'); const ethers = hre.ethers; export default function() { - describe.only('BeaconStateVerifier', () => { + describe('BeaconStateVerifier', () => { let owner, node, random;