@nodeset/contracts
Version:
Protocol for accessing NodeSet's Constellation Ethereum staking network
468 lines (419 loc) • 22.2 kB
text/typescript
import { expect } from "chai";
import { loadFixture } from "@nomicfoundation/hardhat-network-helpers";
import { Protocol, protocolFixture, RocketPool, SetupData, Signers } from "../test";
import { approvedSalt, approveHasSignedExitMessageSig, assertAddOperator } from ".././utils/utils";
import { generateDepositData } from ".././rocketpool/_helpers/minipool";
import { prepareOperatorDistributionContract } from "../utils/utils";
const ethMintAmount = ethers.utils.parseEther("8");
const rplMintAmount = ethers.utils.parseEther("360");
const salt = 3;
describe("SuperNodeAccount creation under validator limits", function () {
let setupData: SetupData;
let protocol: Protocol;
let signers: Signers;
let rocketPool: RocketPool;
beforeEach(async function () {
setupData = await loadFixture(protocolFixture);
protocol = setupData.protocol;
signers = setupData.signers;
rocketPool = setupData.rocketPool;
// Set liquidity reserve to 0%
await protocol.vCRPL.connect(signers.admin).setLiquidityReservePercent(0);
await protocol.vCWETH.connect(signers.admin).setLiquidityReservePercent(0);
await protocol.superNode.connect(signers.admin).setMaxValidators(10);
});
describe("when there is not enough assets deposited", async function () {
it("should revert", async function () {
// Deposit ETH
const ethBalance = await ethers.provider.getBalance(signers.ethWhale.address)
await protocol.wETH.connect(signers.ethWhale).deposit({ value: ethers.utils.parseEther("1000") });
await protocol.wETH.connect(signers.ethWhale).approve(protocol.vCWETH.address, ethBalance);
// Subtract 1 from mint amount to ensure it is not enough
await protocol.vCWETH.connect(signers.ethWhale).deposit(ethers.utils.parseEther("7"), signers.ethWhale.address);
// Deposit RPL
await rocketPool.rplContract.connect(signers.rplWhale).transfer(signers.ethWhale.address, rplMintAmount);
await rocketPool.rplContract.connect(signers.ethWhale).approve(protocol.vCRPL.address, rplMintAmount);
// Subtract 1 from mint amount to ensure it is not enough
await protocol.vCRPL.connect(signers.ethWhale).deposit(ethers.utils.parseEther("359"), signers.ethWhale.address);
const nodeOperator = signers.hyperdriver;
await assertAddOperator(setupData, nodeOperator);
const { rawSalt, pepperedSalt } = await approvedSalt(salt, nodeOperator.address);
const depositData = await generateDepositData(protocol.superNode.address, pepperedSalt)
const bond = await setupData.protocol.superNode.bond();
const config = {
timezoneLocation: 'Australia/Brisbane',
bondAmount: bond,
minimumNodeFee: 0,
validatorPubkey: depositData.depositData.pubkey,
validatorSignature: depositData.depositData.signature,
depositDataRoot: depositData.depositDataRoot,
salt: pepperedSalt,
expectedMinipoolAddress: depositData.minipoolAddress,
};
const { sig, timestamp } = await approveHasSignedExitMessageSig(
setupData,
nodeOperator.address,
'0x' + config.expectedMinipoolAddress,
config.salt,
);
await expect(
protocol.superNode
.connect(nodeOperator)
.createMinipool({
validatorPubkey: config.validatorPubkey,
validatorSignature: config.validatorSignature,
depositDataRoot: config.depositDataRoot,
salt: rawSalt,
expectedMinipoolAddress: config.expectedMinipoolAddress,
sigGenesisTime: timestamp,
sig: sig
}, { value: ethers.utils.parseEther('1') }))
.to.be.revertedWith('NodeAccount: protocol must have enough rpl and eth');
});
});
describe("when there is just enough assets deposited for one", async function () {
it("should create a minipool", async function () {
await prepareOperatorDistributionContract(setupData, 1);
const nodeOperator = signers.hyperdriver;
await assertAddOperator(setupData, nodeOperator);
const { rawSalt, pepperedSalt } = await approvedSalt(salt, nodeOperator.address);
const depositData = await generateDepositData(protocol.superNode.address, pepperedSalt)
const bond = await setupData.protocol.superNode.bond();
const config = {
timezoneLocation: 'Australia/Brisbane',
bondAmount: bond,
minimumNodeFee: 0,
validatorPubkey: depositData.depositData.pubkey,
validatorSignature: depositData.depositData.signature,
depositDataRoot: depositData.depositDataRoot,
salt: pepperedSalt,
expectedMinipoolAddress: depositData.minipoolAddress,
};
const { sig, timestamp } = await approveHasSignedExitMessageSig(
setupData,
nodeOperator.address,
'0x' + config.expectedMinipoolAddress,
config.salt,
);
await expect(
protocol.superNode
.connect(nodeOperator)
.createMinipool({
validatorPubkey: config.validatorPubkey,
validatorSignature: config.validatorSignature,
depositDataRoot: config.depositDataRoot,
salt: rawSalt,
expectedMinipoolAddress: config.expectedMinipoolAddress,
sigGenesisTime: timestamp,
sig: sig
}, { value: ethers.utils.parseEther('1') }))
.to.not.be.reverted;
});
});
describe("when there is enough assets for many", async function () {
describe("when there is a single node operator", async function () {
it("should create many minipools", async function () {
// Deposit ETH (for 3 minipools)
await prepareOperatorDistributionContract(setupData, 3);
const nodeOperator = signers.hyperdriver;
await assertAddOperator(setupData, nodeOperator);
const bond = await setupData.protocol.superNode.bond();
// Create first minipool
let salts = await approvedSalt(salt, nodeOperator.address);
let depositData = await generateDepositData(protocol.superNode.address, salts.pepperedSalt)
let config = {
timezoneLocation: 'Australia/Brisbane',
bondAmount: bond,
minimumNodeFee: 0,
validatorPubkey: depositData.depositData.pubkey,
validatorSignature: depositData.depositData.signature,
depositDataRoot: depositData.depositDataRoot,
salt: salts.pepperedSalt,
expectedMinipoolAddress: depositData.minipoolAddress,
};
let exitMessage = await approveHasSignedExitMessageSig(
setupData,
nodeOperator.address,
'0x' + config.expectedMinipoolAddress,
config.salt,
);
await expect(
protocol.superNode
.connect(nodeOperator)
.createMinipool({
validatorPubkey: config.validatorPubkey,
validatorSignature: config.validatorSignature,
depositDataRoot: config.depositDataRoot,
salt: salts.rawSalt,
expectedMinipoolAddress: config.expectedMinipoolAddress,
sigGenesisTime: exitMessage.timestamp,
sig: exitMessage.sig
}, { value: ethers.utils.parseEther('1') }))
.to.not.be.reverted;
// Create second minipool
salts = await approvedSalt(4, nodeOperator.address);
depositData = await generateDepositData(protocol.superNode.address, salts.pepperedSalt)
config = {
timezoneLocation: 'Australia/Brisbane',
bondAmount: bond,
minimumNodeFee: 0,
validatorPubkey: depositData.depositData.pubkey,
validatorSignature: depositData.depositData.signature,
depositDataRoot: depositData.depositDataRoot,
salt: salts.pepperedSalt,
expectedMinipoolAddress: depositData.minipoolAddress,
};
exitMessage = await approveHasSignedExitMessageSig(
setupData,
nodeOperator.address,
'0x' + config.expectedMinipoolAddress,
config.salt,
);
await expect(
protocol.superNode
.connect(nodeOperator)
.createMinipool({
validatorPubkey: config.validatorPubkey,
validatorSignature: config.validatorSignature,
depositDataRoot: config.depositDataRoot,
salt: salts.rawSalt,
expectedMinipoolAddress: config.expectedMinipoolAddress,
sigGenesisTime: exitMessage.timestamp,
sig: exitMessage.sig
}, { value: ethers.utils.parseEther('1') }))
.to.not.be.reverted;
// Create third minipool
salts = await approvedSalt(5, nodeOperator.address);
depositData = await generateDepositData(protocol.superNode.address, salts.pepperedSalt)
config = {
timezoneLocation: 'Australia/Brisbane',
bondAmount: bond,
minimumNodeFee: 0,
validatorPubkey: depositData.depositData.pubkey,
validatorSignature: depositData.depositData.signature,
depositDataRoot: depositData.depositDataRoot,
salt: salts.pepperedSalt,
expectedMinipoolAddress: depositData.minipoolAddress,
};
exitMessage = await approveHasSignedExitMessageSig(
setupData,
nodeOperator.address,
'0x' + config.expectedMinipoolAddress,
config.salt,
);
await expect(
protocol.superNode
.connect(nodeOperator)
.createMinipool({
validatorPubkey: config.validatorPubkey,
validatorSignature: config.validatorSignature,
depositDataRoot: config.depositDataRoot,
salt: salts.rawSalt,
expectedMinipoolAddress: config.expectedMinipoolAddress,
sigGenesisTime: exitMessage.timestamp,
sig: exitMessage.sig
}, { value: ethers.utils.parseEther('1') }))
.to.not.be.reverted;
// Create fourth minipool
salts = await approvedSalt(6, nodeOperator.address);
depositData = await generateDepositData(protocol.superNode.address, salts.pepperedSalt)
config = {
timezoneLocation: 'Australia/Brisbane',
bondAmount: bond,
minimumNodeFee: 0,
validatorPubkey: depositData.depositData.pubkey,
validatorSignature: depositData.depositData.signature,
depositDataRoot: depositData.depositDataRoot,
salt: salts.pepperedSalt,
expectedMinipoolAddress: depositData.minipoolAddress,
};
exitMessage = await approveHasSignedExitMessageSig(
setupData,
nodeOperator.address,
'0x' + config.expectedMinipoolAddress,
config.salt,
);
await expect(
protocol.superNode
.connect(nodeOperator)
.createMinipool({
validatorPubkey: config.validatorPubkey,
validatorSignature: config.validatorSignature,
depositDataRoot: config.depositDataRoot,
salt: salts.rawSalt,
expectedMinipoolAddress: config.expectedMinipoolAddress,
sigGenesisTime: exitMessage.timestamp,
sig: exitMessage.sig
}, { value: ethers.utils.parseEther('1') }))
.to.be.revertedWith('NodeAccount: protocol must have enough rpl and eth');
await protocol.superNode.connect(signers.admin).setMaxValidators(3);
await expect(
protocol.superNode
.connect(nodeOperator)
.createMinipool({
validatorPubkey: config.validatorPubkey,
validatorSignature: config.validatorSignature,
depositDataRoot: config.depositDataRoot,
salt: salts.rawSalt,
expectedMinipoolAddress: config.expectedMinipoolAddress,
sigGenesisTime: exitMessage.timestamp,
sig: exitMessage.sig
}, { value: ethers.utils.parseEther('1') }))
.to.be.revertedWith('Sub node operator has created too many minipools already');
});
});
describe("when there are multiple node operators", async function () {
it("should create many minipools", async function () {
await protocol.superNode.connect(signers.admin).setMaxValidators(1);
// Deposit ETH (for 3 minipools)
await prepareOperatorDistributionContract(setupData, 3);
const nodeOperator1 = signers.hyperdriver;
await assertAddOperator(setupData, nodeOperator1);
const nodeOperator2 = signers.random;
await assertAddOperator(setupData, nodeOperator2);
// create minipool for nodeOperator1
let salts = await approvedSalt(salt, nodeOperator1.address);
let depositData = await generateDepositData(protocol.superNode.address, salts.pepperedSalt)
let config = {
timezoneLocation: 'Australia/Brisbane',
bondAmount: await protocol.superNode.bond(),
minimumNodeFee: 0,
validatorPubkey: depositData.depositData.pubkey,
validatorSignature: depositData.depositData.signature,
depositDataRoot: depositData.depositDataRoot,
salt: salts.pepperedSalt,
expectedMinipoolAddress: depositData.minipoolAddress,
};
let exitMessage = await approveHasSignedExitMessageSig(
setupData,
nodeOperator1.address,
'0x' + config.expectedMinipoolAddress,
config.salt,
);
await expect(
protocol.superNode
.connect(nodeOperator1)
.createMinipool({
validatorPubkey: config.validatorPubkey,
validatorSignature: config.validatorSignature,
depositDataRoot: config.depositDataRoot,
salt: salts.rawSalt,
expectedMinipoolAddress: config.expectedMinipoolAddress,
sigGenesisTime: exitMessage.timestamp,
sig: exitMessage.sig
}, { value: ethers.utils.parseEther('1') }))
.to.not.be.reverted;
// create minipool for nodeOperator2
salts = await approvedSalt(4, nodeOperator2.address);
depositData = await generateDepositData(protocol.superNode.address, salts.pepperedSalt)
config = {
timezoneLocation: 'Australia/Brisbane',
bondAmount: await protocol.superNode.bond(),
minimumNodeFee: 0,
validatorPubkey: depositData.depositData.pubkey,
validatorSignature: depositData.depositData.signature,
depositDataRoot: depositData.depositDataRoot,
salt: salts.pepperedSalt,
expectedMinipoolAddress: depositData.minipoolAddress,
};
exitMessage = await approveHasSignedExitMessageSig(
setupData,
nodeOperator2.address,
'0x' + config.expectedMinipoolAddress,
config.salt,
);
await expect(
protocol.superNode
.connect(nodeOperator2)
.createMinipool({
validatorPubkey: config.validatorPubkey,
validatorSignature: config.validatorSignature,
depositDataRoot: config.depositDataRoot,
salt: salts.rawSalt,
expectedMinipoolAddress: config.expectedMinipoolAddress,
sigGenesisTime: exitMessage.timestamp,
sig: exitMessage.sig
}, { value: ethers.utils.parseEther('1') }))
.to.not.be.reverted;
// create minipool for nodeOperator1
salts = await approvedSalt(5, nodeOperator1.address);
depositData = await generateDepositData(protocol.superNode.address, salts.pepperedSalt)
config = {
timezoneLocation: 'Australia/Brisbane',
bondAmount: await protocol.superNode.bond(),
minimumNodeFee: 0,
validatorPubkey: depositData.depositData.pubkey,
validatorSignature: depositData.depositData.signature,
depositDataRoot: depositData.depositDataRoot,
salt: salts.pepperedSalt,
expectedMinipoolAddress: depositData.minipoolAddress,
};
exitMessage = await approveHasSignedExitMessageSig(
setupData,
nodeOperator1.address,
'0x' + config.expectedMinipoolAddress,
config.salt,
);
await expect(
protocol.superNode
.connect(nodeOperator1)
.createMinipool({
validatorPubkey: config.validatorPubkey,
validatorSignature: config.validatorSignature,
depositDataRoot: config.depositDataRoot,
salt: salts.rawSalt,
expectedMinipoolAddress: config.expectedMinipoolAddress,
sigGenesisTime: exitMessage.timestamp,
sig: exitMessage.sig
}, { value: ethers.utils.parseEther('1') }))
.to.be.revertedWith('Sub node operator has created too many minipools already');
await protocol.superNode.connect(signers.admin).setMaxValidators(2);
await expect(
protocol.superNode
.connect(nodeOperator1)
.createMinipool({
validatorPubkey: config.validatorPubkey,
validatorSignature: config.validatorSignature,
depositDataRoot: config.depositDataRoot,
salt: salts.rawSalt,
expectedMinipoolAddress: config.expectedMinipoolAddress,
sigGenesisTime: exitMessage.timestamp,
sig: exitMessage.sig
}, { value: ethers.utils.parseEther('1') }))
.to.not.be.reverted;
// create minipool for nodeOperator2
salts = await approvedSalt(6, nodeOperator2.address);
depositData = await generateDepositData(protocol.superNode.address, salts.pepperedSalt)
config = {
timezoneLocation: 'Australia/Brisbane',
bondAmount: await protocol.superNode.bond(),
minimumNodeFee: 0,
validatorPubkey: depositData.depositData.pubkey,
validatorSignature: depositData.depositData.signature,
depositDataRoot: depositData.depositDataRoot,
salt: salts.pepperedSalt,
expectedMinipoolAddress: depositData.minipoolAddress,
};
exitMessage = await approveHasSignedExitMessageSig(
setupData,
nodeOperator2.address,
'0x' + config.expectedMinipoolAddress,
config.salt,
);
await expect(
protocol.superNode
.connect(nodeOperator2)
.createMinipool({
validatorPubkey: config.validatorPubkey,
validatorSignature: config.validatorSignature,
depositDataRoot: config.depositDataRoot,
salt: salts.rawSalt,
expectedMinipoolAddress: config.expectedMinipoolAddress,
sigGenesisTime: exitMessage.timestamp,
sig: exitMessage.sig
}, { value: ethers.utils.parseEther('1') }))
.to.be.revertedWith('NodeAccount: protocol must have enough rpl and eth');
});
});
});
});