@nodeset/contracts
Version:
Protocol for accessing NodeSet's Constellation Ethereum staking network
200 lines (162 loc) • 11.3 kB
text/typescript
import { expect } from "chai";
import { ethers, network } from "hardhat";
import { loadFixture } from "@nomicfoundation/hardhat-network-helpers"
import { getAllAddresses, protocolFixture, SetupData } from "../test";
import { BigNumber as BN } from "ethers";
import { Protocol } from "../test";
import { Signers } from "../test";
import { RocketPool } from "../test";
import { IERC20, IMinipool__factory, MockMinipool, MockMinipool__factory, MockRocketNodeManager, WETHVault, RPLVault, IWETH, RocketMinipoolInterface } from "../../typechain-types";
import { OperatorStruct } from "../protocol-types/types";
import { deployRPMinipool, registerNewValidator, expectNumberE18ToBeApproximately, prepareOperatorDistributionContract, printBalances, printObjectBalances, printObjectTokenBalances, printTokenBalances, assertAddOperator, deployMinipool, increaseEVMTime } from "../utils/utils";
import { generateDepositDataForStake } from "../rocketpool/_helpers/minipool";
describe("Node Operator Onboarding", function () {
let setupData: SetupData;
let protocol: Protocol;
let signers: Signers;
let rocketPool: RocketPool;
let minipoolAddress: string;
let xrETH: WETHVault;
let xRPL: RPLVault;
let rpl: IERC20;
let weth: IWETH;
const bondValue = ethers.utils.parseEther("8");
before(async function () {
setupData = await protocolFixture();
protocol = setupData.protocol;
signers = setupData.signers;
rocketPool = setupData.rocketPool;
await rocketPool.rplContract.connect(signers.rplWhale).transfer(signers.hyperdriver.address, ethers.utils.parseEther("100"));
xrETH = protocol.vCWETH;
xRPL = protocol.vCRPL;
weth = protocol.wETH;
rpl = await ethers.getContractAt("@openzeppelin/contracts/token/ERC20/IERC20.sol:IERC20", await protocol.directory.getRPLAddress()) as IERC20;
});
it("admin needs to kyc the node operator and register them in the whitelist", async function () {
await assertAddOperator(setupData, signers.hyperdriver);
});
it("node operator creates minipool via creating validator account", async function () {
//await assertAddOperator(setupData, signers.hyperdriver);
// now we must create a minipool via super node
expect(await protocol.superNode.hasSufficientLiquidity(bondValue)).equals(false);
await prepareOperatorDistributionContract(setupData, 1);
expect(await protocol.superNode.hasSufficientLiquidity(bondValue)).equals(true);
expect(await protocol.superNode.getTotalEthStaked()).equals(BigInt(0));
expect(await protocol.superNode.getTotalEthMatched()).equals(BigInt(0));
minipoolAddress = await deployMinipool(setupData, bondValue, signers.hyperdriver.address);
// Assuming signers.hyperdriver.address and minipoolAddress are defined
const hyperdriverAddress = await ethers.utils.getAddress(signers.hyperdriver.address);
const minipoolFormatAddress = await ethers.utils.getAddress(minipoolAddress);
// Check if the subNodeOperator has the minipool
expect((await protocol.superNode.minipoolData(minipoolFormatAddress)).subNodeOperator).to.equal(hyperdriverAddress);
});
// continue debugging staking ops, why would staking fail here?
it("sub node operator 'hyperdriver' decides to begin staking process", async () => {
await setupData.rocketPool.rocketDepositPoolContract.deposit({
value: ethers.utils.parseEther("32")
})
await setupData.rocketPool.rocketDepositPoolContract.assignDeposits();
await increaseEVMTime(60 * 60 * 24 * 7 * 32);
const depositDataStake = await generateDepositDataForStake(minipoolAddress);
await protocol.superNode.connect(signers.hyperdriver).stake(depositDataStake.depositData.signature, depositDataStake.depositDataRoot, minipoolAddress);
})
it("eth whale supplies Constellation vaults with eth and rpl", async function () {
// ethWhale gets shares of xrETH
const initialBalance = await weth.balanceOf(protocol.operatorDistributor.address);
await protocol.wETH.connect(signers.ethWhale).deposit({ value: ethers.utils.parseEther("100") });
await protocol.wETH.connect(signers.ethWhale).approve(protocol.vCWETH.address, ethers.utils.parseEther("100"));
await protocol.vCWETH.connect(signers.ethWhale).deposit(ethers.utils.parseEther("100"), signers.ethWhale.address);
const expectedAmountInDP = ethers.utils.parseEther("0"); // should be zero bc funds always get swept and rebalanced during deposit
const actualAmountInDP = (await weth.balanceOf(protocol.operatorDistributor.address)).sub(initialBalance);
expectNumberE18ToBeApproximately(actualAmountInDP, expectedAmountInDP, 0.05);
const intialBalanceRpl = await protocol.vCRPL.totalAssets();
await rocketPool.rplContract.connect(signers.rplWhale).approve(protocol.vCRPL.address, ethers.utils.parseEther("100"));
await protocol.vCRPL.connect(signers.rplWhale).deposit(ethers.utils.parseEther("100"), signers.rplWhale.address);
const expectedRplInDP = ethers.utils.parseEther("100");
const actualRplInDP = (await protocol.vCRPL.totalAssets()).sub(intialBalanceRpl);
//expectNumberE18ToBeApproximately(actualRplInDP, expectedRplInDP, 0.1); // ooof, lets get this estimate down to 0.001%
});
it("oracle update increases yield appropriately", async function () {
// push down coverage ratio
await rocketPool.rplContract.connect(signers.rplWhale).transfer(signers.hyperdriver.address, ethers.utils.parseEther("200"));
await rocketPool.rplContract.connect(signers.hyperdriver).approve(protocol.vCRPL.address, ethers.utils.parseEther("200"));
await protocol.vCRPL.connect(signers.hyperdriver).deposit(ethers.utils.parseEther("200"), signers.hyperdriver.address);
// deposit into protocol
await protocol.wETH.connect(signers.ethWhale).deposit({ value: ethers.utils.parseEther("100") });
await protocol.wETH.connect(signers.ethWhale).approve(protocol.vCWETH.address, ethers.utils.parseEther("100"));
await protocol.vCWETH.connect(signers.ethWhale).deposit(ethers.utils.parseEther("100"), signers.ethWhale.address);
const timestamp = (await ethers.provider.getBlock(await ethers.provider.getBlockNumber())).timestamp
const network = await ethers.provider.getNetwork();
const chainId = network.chainId;
const newTotalYield = ethers.utils.parseEther("3");
const currentOracleError = await protocol.operatorDistributor.oracleError();
const sigData = { newTotalYieldAccrued: newTotalYield, expectedOracleError: currentOracleError, timeStamp: timestamp };
const messageHash = ethers.utils.solidityKeccak256(["int256", "uint256", "uint256", "address", "uint256"], [newTotalYield, currentOracleError, timestamp, protocol.oracle.address, chainId]);
const signature = await signers.admin.signMessage(ethers.utils.arrayify(messageHash));
// accrue yield via oracle
await protocol.oracle.connect(signers.admin).setTotalYieldAccrued(signature, sigData)
const tx = await protocol.vCWETH.connect(signers.ethWhale).redeem(ethers.utils.parseEther("1"), signers.ethWhale.address, signers.ethWhale.address);
const receipt = await tx.wait();
const { events } = receipt;
if (events) {
for (let i = 0; i < events.length; i++) {
if (events[i].event?.includes("Capital")) {
expectNumberE18ToBeApproximately(events[i].args?.amount, ethers.utils.parseEther("0.7246"), 0.01);
}
}
}
});
it("someone calls for yield distribution", async function () {
const xrETHBalancesBefore = [];
const xRPLBalancesBefore = [];
const ETHbalancesBefore = [];
const RPLbalancesBefore = [];
const allAddresses = getAllAddresses(signers, protocol, rocketPool);
for (let i = 0; i < allAddresses.length; i++) {
xrETHBalancesBefore.push(allAddresses[i].name + " - " + await protocol.vCWETH.balanceOf(allAddresses[i].address));
xRPLBalancesBefore.push(allAddresses[i].name + " - " + await protocol.vCRPL.balanceOf(allAddresses[i].address));
ETHbalancesBefore.push(allAddresses[i].name + " - " + await ethers.provider.getBalance(allAddresses[i].address));
RPLbalancesBefore.push(allAddresses[i].name + " - " + await rocketPool.rplContract.balanceOf(allAddresses[i].address));
}
registerNewValidator(setupData, [signers.hyperdriver]);
const xrETHBalancesAfter = [];
const xRPLBalancesAfter = [];
const ETHbalancesAfter = [];
const RPLbalancesAfter = [];
for (let i = 0; i < allAddresses.length; i++) {
xrETHBalancesAfter.push(allAddresses[i].name + " - " + await protocol.vCWETH.balanceOf(allAddresses[i].address));
xRPLBalancesAfter.push(allAddresses[i].name + " - " + await protocol.vCRPL.balanceOf(allAddresses[i].address));
ETHbalancesAfter.push(allAddresses[i].name + " - " + await ethers.provider.getBalance(allAddresses[i].address));
RPLbalancesAfter.push(allAddresses[i].name + " - " + await rocketPool.rplContract.balanceOf(allAddresses[i].address));
}
// print before - after balances if delta is not 0
console.log("printing delta balances")
for (let i = 0; i < allAddresses.length; i++) {
if (xrETHBalancesBefore[i] != xrETHBalancesAfter[i]) {
console.log("xrETH balance before: ", xrETHBalancesBefore[i]);
console.log("xrETH balance after: ", xrETHBalancesAfter[i]);
}
if (xRPLBalancesBefore[i] != xRPLBalancesAfter[i]) {
console.log("xRPL balance before: ", xRPLBalancesBefore[i]);
console.log("xRPL balance after: ", xRPLBalancesAfter[i]);
}
if (ETHbalancesBefore[i] != ETHbalancesAfter[i]) {
console.log("ETH balance before: ", ETHbalancesBefore[i]);
console.log("ETH balance after: ", ETHbalancesAfter[i]);
}
if (RPLbalancesBefore[i] != RPLbalancesAfter[i]) {
console.log("RPL balance before: ", RPLbalancesBefore[i]);
console.log("RPL balance after: ", RPLbalancesAfter[i]);
}
}
});
it("sub node operator 'hyperdriver' decides to begin staking process again...", async () => {
await setupData.rocketPool.rocketDepositPoolContract.deposit({
value: ethers.utils.parseEther("32")
})
await setupData.rocketPool.rocketDepositPoolContract.assignDeposits();
await increaseEVMTime(60 * 60 * 24 * 7 * 32);
const depositDataStake = await generateDepositDataForStake(minipoolAddress);
await expect(protocol.superNode.connect(signers.hyperdriver).stake(depositDataStake.depositData.signature, depositDataStake.depositDataRoot, minipoolAddress)).to.be.revertedWith("The minipool can only begin staking while in prelaunch");
})
});