UNPKG

@nodeset/contracts

Version:

Protocol for accessing NodeSet's Constellation Ethereum staking network

188 lines (146 loc) 9.04 kB
import { expect } from "chai"; import { ethers, upgrades } from "hardhat"; import { loadFixture } from "@nomicfoundation/hardhat-network-helpers" import { protocolFixture, SetupData } from "./test"; import { BigNumber as BN } from "ethers"; import { computeKeccak256FromBytes32, prepareOperatorDistributionContract, printEventDetails, registerNewValidator, upgradePriceFetcherToMock } from "./utils/utils"; import { IMinipool, MockMinipool } from "../typechain-types"; import { RocketDepositPool } from "./rocketpool/_utils/artifacts"; describe("Operator Distributor", function () { it("Processes minipool rewards correctly even when nodeRefundBalance > 0 (no exit)", async function (){ const setupData = await loadFixture(protocolFixture); const { protocol, signers, rocketPool } = setupData; const { operatorDistributor } = protocol; // create 1 minipool await prepareOperatorDistributionContract(setupData, 1); const minipools = await registerNewValidator(setupData, [signers.random]); const priorAssets = await protocol.vCWETH.totalAssets(); // simulate rewards to minipool contract from beacon const baconReward = ethers.utils.parseEther("1"); // assume a 15% rETH fee and LEB8 (36.25% of all rewards), which is default settings for RP const constellationPortion = baconReward.mul(ethers.utils.parseEther(".3625")).div(ethers.utils.parseEther("1")); const xrETHPortion = await protocol.vCWETH.getIncomeAfterFees(constellationPortion); await signers.ethWhale.sendTransaction({ to: minipools[0], value: baconReward }) // random person distributes the balance to increase nodeRefundBalance const minipool = await ethers.getContractAt("IMinipool", minipools[0]); await minipool.connect(signers.random).distributeBalance(true); expect(await minipool.getNodeRefundBalance()).equals(constellationPortion); // protocol sweeps in rewards await protocol.operatorDistributor.connect(signers.random).processMinipool(minipools[0]); expect(await protocol.vCWETH.totalAssets()).to.equal(priorAssets.add(xrETHPortion)); }); it("Processes minipool rewards correctly even when nodeRefundBalance > 0 (exit)", async function (){ const setupData = await loadFixture(protocolFixture); const { protocol, signers, rocketPool } = setupData; const { operatorDistributor } = protocol; // create 1 minipool await prepareOperatorDistributionContract(setupData, 1); const minipools = await registerNewValidator(setupData, [signers.random]); const priorAssets = await protocol.vCWETH.totalAssets(); // simulate rewards to minipool contract from beacon const baconReward = ethers.utils.parseEther("1"); // assume a 15% rETH fee and LEB8 (36.25% of all rewards), which is default settings for RP const constellationPortion = baconReward.mul(ethers.utils.parseEther(".3625")).div(ethers.utils.parseEther("1")); const xrETHPortion = await protocol.vCWETH.getIncomeAfterFees(constellationPortion); await signers.ethWhale.sendTransaction({ to: minipools[0], value: baconReward }) // random person distributes the balance to increase nodeRefundBalance const minipool = await ethers.getContractAt("IMinipool", minipools[0]); await minipool.connect(signers.random).distributeBalance(true); expect(await minipool.getNodeRefundBalance()).equals(constellationPortion); // exit the minipool const beaconBalance = ethers.utils.parseEther("32"); await signers.ethWhale.sendTransaction({ to: minipools[0], value: beaconBalance }) // protocol sweeps in rewards await protocol.operatorDistributor.connect(signers.random).processMinipool(minipools[0]); expect(await protocol.vCWETH.totalAssets()).to.equal(priorAssets.add(xrETHPortion)); }); it("Tops up the RPL stake if it is below the minimum", async function () { // load fixture const setupData = await loadFixture(protocolFixture); const { protocol, signers, rocketPool } = setupData; const { operatorDistributor } = protocol; const { rocketNodeStakingContract, rocketDepositPoolContract } = rocketPool; const rocketNodeStaking = await ethers.getContractAt("RocketNodeStaking", await protocol.directory.getRocketNodeStakingAddress()); const rocketDepositPool = await ethers.getContractAt("RocketDepositPool", rocketDepositPoolContract.address); // set expectations for params //expect(setupData.protocol.operatorDistributor.targetStakeRatio >= (await setupData.protocol.operatorDistributor.minimumStakeRatio()).mul(2)); // add minimum assets for 2 minipools await prepareOperatorDistributionContract(setupData, 2); // create 2 minipools await registerNewValidator(setupData, [signers.random, signers.random2]); // send rpl to the operator distributor const rplAmount = ethers.utils.parseEther("1000"); await rocketPool.rplContract.connect(signers.rplWhale).transfer(operatorDistributor.address, rplAmount); const lastPrice = await upgradePriceFetcherToMock(signers, protocol, ethers.utils.parseEther("55")); const initialRplStake = await rocketNodeStaking.getNodeRPLStake(protocol.superNode.address); const tx = await operatorDistributor.connect(signers.protocolSigner).processNextMinipool(); await operatorDistributor.connect(signers.protocolSigner).processNextMinipool(); await operatorDistributor.connect(signers.protocolSigner).processNextMinipool(); await operatorDistributor.connect(signers.protocolSigner).processNextMinipool(); const finalRplStake = await rocketNodeStaking.getNodeRPLStake(protocol.superNode.address); expect(finalRplStake.sub(initialRplStake)).eq(ethers.BigNumber.from(0)); }); // test distribute yield and show how it increases and adjusts for oracle error it("Rewards distribution distributes correct amounts of ETH to all NOs", async function () { // record all node balances // capture pending rewards // distribute rewards // verify rewards were distributed correctly }); // test target stake ratio logic it("success - target stake ratio may be set above 100%", async function () { // load fixture const setupData = await loadFixture(protocolFixture); const { protocol, signers, rocketPool } = setupData; const { admin } = signers; const { operatorDistributor } = protocol; const rocketNodeStaking = await ethers.getContractAt("RocketNodeStaking", await protocol.directory.getRocketNodeStakingAddress()); await operatorDistributor.connect(admin).setTargetStakeRatio(ethers.utils.parseEther("1.0001")); const initialRplStake = await rocketNodeStaking.getNodeRPLStake(protocol.superNode.address); expect(initialRplStake).equals(0) await prepareOperatorDistributionContract(setupData, 1); await registerNewValidator(setupData, [signers.random]); const price = await protocol.priceFetcher.getPrice(); }); it("success - target stake ratio may be set equal 100%", async function () { // load fixture const setupData = await loadFixture(protocolFixture); const { protocol, signers, rocketPool } = setupData; const { admin, rplWhale } = signers; const { operatorDistributor } = protocol; const rocketNodeStaking = await ethers.getContractAt("RocketNodeStaking", await protocol.directory.getRocketNodeStakingAddress()); // getSettingUint('node.per.minipool.stake.minimum'); //await rocketPool.rockStorageContract.setUint("0x2667306bf1c3fdbd6985406babb7b6f4af682212c96c7461d13f2c6e46339fe5", ethers.utils.parseEther(".3")); await operatorDistributor.connect(admin).setTargetStakeRatio(ethers.utils.parseEther("1")); await rocketPool.rplContract.connect(signers.rplWhale).transfer(protocol.operatorDistributor.address, ethers.utils.parseEther("5000")); let initialRplStake = await rocketNodeStaking.getNodeRPLStake(protocol.superNode.address); expect(initialRplStake).equals(0) await prepareOperatorDistributionContract(setupData, 1); await registerNewValidator(setupData, [signers.random]); }); it("success - target stake ratio may be set less than 100%", async function () { // load fixture const setupData = await loadFixture(protocolFixture); const { protocol, signers, rocketPool } = setupData; const { admin, rplWhale } = signers; const { operatorDistributor } = protocol; const rocketNodeStaking = await ethers.getContractAt("RocketNodeStaking", await protocol.directory.getRocketNodeStakingAddress()); // getSettingUint('node.per.minipool.stake.minimum'); //await rocketPool.rockStorageContract.setUint("0x2667306bf1c3fdbd6985406babb7b6f4af682212c96c7461d13f2c6e46339fe5", ethers.utils.parseEther(".3")); await operatorDistributor.connect(admin).setTargetStakeRatio(ethers.utils.parseEther(".5")); await rocketPool.rplContract.connect(signers.rplWhale).transfer(protocol.operatorDistributor.address, ethers.utils.parseEther("5000")); let initialRplStake = await rocketNodeStaking.getNodeRPLStake(protocol.superNode.address); expect(initialRplStake).equals(0) await prepareOperatorDistributionContract(setupData, 2); await registerNewValidator(setupData, [signers.random]); }); });