@nodeset/contracts
Version:
Protocol for accessing NodeSet's Constellation Ethereum staking network
100 lines (89 loc) • 4.19 kB
JavaScript
const { RocketNodeManager, RocketMinipoolManager, RocketMinipoolDelegate } = require('../_utils/artifacts');
const { assertBN } = require('./bn');
async function checkInvariants() {
const nodeAddresses = await getNodeAddresses();
for (const nodeAddress of nodeAddresses) {
const minipools = await getMinipoolsByNode(nodeAddress);
await checkNodeInvariants(nodeAddress, minipools);
}
}
async function getNodeAddresses() {
const rocketNodeManager = await RocketNodeManager.deployed();
return await rocketNodeManager.getNodeAddresses(0, 1000);
}
async function getMinipoolDetails(address) {
const minipool = await RocketMinipoolDelegate.at(address);
const [status, finalised, nodeFee, userDepositBalance, nodeDepositBalance] = await Promise.all([
minipool.getStatus(),
minipool.getFinalised(),
minipool.getNodeFee(),
minipool.getUserDepositBalance(),
minipool.getNodeDepositBalance(),
]);
return {
status: status.toString(),
finalised,
nodeFee,
userDepositBalance,
nodeDepositBalance
};
}
async function getMinipoolsByNode(nodeAddress) {
const rocketMinipoolManager = await RocketMinipoolManager.deployed();
const count = await rocketMinipoolManager.getNodeMinipoolCount(nodeAddress);
const minipools = [];
for (let i = 0; i < count; i++) {
const address = await rocketMinipoolManager.getNodeMinipoolAt(nodeAddress, i);
minipools.push(await getMinipoolDetails(address));
}
return minipools;
}
async function checkNodeInvariants(nodeAddress, minipools) {
const rocketMinipoolManager = await RocketMinipoolManager.deployed();
const rocketNodeManager = await RocketNodeManager.deployed();
const depositSizes = ['8'.ether, '16'.ether];
// Filter "staking" minipools
const stakingMinipools = minipools.filter(minipool => minipool.status === '2' && minipool.finalised === false);
// Check overall counts
const [expectedActive, expectedFinalised, expectedStaking] = await Promise.all([
rocketMinipoolManager.getNodeActiveMinipoolCount(nodeAddress),
rocketMinipoolManager.getNodeFinalisedMinipoolCount(nodeAddress),
rocketMinipoolManager.getNodeStakingMinipoolCount(nodeAddress),
]);
const actualActive = minipools.filter(minipool => minipool.finalised !== true).length;
const actualFinalised = minipools.length - actualActive;
const actualStaking = stakingMinipools.length;
assert.equal(actualActive, expectedActive.toNumber(), 'Active minipool count invariant broken');
assert.equal(actualFinalised, expectedFinalised.toNumber(), 'Finalised minipool count invariant broken');
assert.equal(actualStaking, expectedStaking.toNumber(), 'Staking minipool count invariant broken');
// Check deposit size counts
const countBySize = await Promise.all(depositSizes.map(depositSize => rocketMinipoolManager.getNodeStakingMinipoolCountBySize(nodeAddress, depositSize)));
for (let i = 0; i < depositSizes.length; i++) {
const depositSize = depositSizes[i];
const actualCount = countBySize[i].toNumber();
const expectedCount = stakingMinipools.filter(minipool => minipool.nodeDepositBalance.eq(depositSize)).length;
assert.equal(actualCount, expectedCount, 'Deposit size specific staking minipool count invariant broken');
}
// Check weighted average node fee
const expectedFee = weightedAverage(
stakingMinipools.map(minipool => minipool.nodeFee),
stakingMinipools.map(minipool => minipool.userDepositBalance),
);
const actualFee = await rocketNodeManager.getAverageNodeFee(nodeAddress);
assertBN.equal(actualFee, expectedFee, 'Average node fee invariant broken');
}
function weightedAverage(nums, weights) {
if (nums.length === 0) {
return '0'.BN;
}
const [sum, weightSum] = weights.reduce(
(acc, w, i) => {
acc[0] = acc[0].add(nums[i].mul(w));
acc[1] = acc[1].add(w);
return acc;
},
['0'.BN, '0'.BN],
);
return sum.div(weightSum);
}
module.exports = { checkInvariants };