Skip to content

Commit

Permalink
fix: vebo access control and deploy tests
Browse files Browse the repository at this point in the history
  • Loading branch information
Amuhar committed Jan 9, 2025
1 parent aa5c7c7 commit cf98121
Show file tree
Hide file tree
Showing 4 changed files with 407 additions and 0 deletions.
205 changes: 205 additions & 0 deletions test/0.8.9/oracle/validator-exit-bus-oracle.accessControl.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
import { expect } from "chai";
import { ContractTransactionResponse, ZeroAddress } from "ethers";
import { ethers } from "hardhat";

import { HardhatEthersSigner } from "@nomicfoundation/hardhat-ethers/signers";

import { HashConsensus__Harness, ValidatorsExitBus__Harness, ValidatorsExitBusOracle } from "typechain-types";

Check warning on line 7 in test/0.8.9/oracle/validator-exit-bus-oracle.accessControl.test.ts

View workflow job for this annotation

GitHub Actions / ESLint

'ValidatorsExitBusOracle' is defined but never used

import { CONSENSUS_VERSION, de0x, numberToHex, SECONDS_PER_SLOT } from "lib";

Check warning on line 9 in test/0.8.9/oracle/validator-exit-bus-oracle.accessControl.test.ts

View workflow job for this annotation

GitHub Actions / ESLint

'SECONDS_PER_SLOT' is defined but never used

import { DATA_FORMAT_LIST, deployVEBO, initVEBO } from "test/deploy";
import { Snapshot } from "test/suite";

const PUBKEYS = [
"0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
"0xcccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc",
];

describe("ValidatorsExitBusOracle.sol:accessControl", () => {
let consensus: HashConsensus__Harness;
let oracle: ValidatorsExitBus__Harness;
let admin: HardhatEthersSigner;
let originalState: string;

let initTx: ContractTransactionResponse;
let oracleVersion: bigint;
let exitRequests: ExitRequest[];
let reportFields: ReportFields;
let reportItems: ReturnType<typeof getValidatorsExitBusReportDataItems>;
let reportHash: string;

let member1: HardhatEthersSigner;
let member2: HardhatEthersSigner;
let member3: HardhatEthersSigner;
let stranger: HardhatEthersSigner;
let account1: HardhatEthersSigner;

interface ExitRequest {
moduleId: number;
nodeOpId: number;
valIndex: number;
valPubkey: string;
}

interface ReportFields {
consensusVersion: bigint;
refSlot: bigint;
requestsCount: number;
dataFormat: number;
data: string;
}

const calcValidatorsExitBusReportDataHash = (items: ReturnType<typeof getValidatorsExitBusReportDataItems>) => {
const data = ethers.AbiCoder.defaultAbiCoder().encode(["(uint256,uint256,uint256,uint256,bytes)"], [items]);
return ethers.keccak256(data);
};

const getValidatorsExitBusReportDataItems = (r: ReportFields) => {
return [r.consensusVersion, r.refSlot, r.requestsCount, r.dataFormat, r.data];
};

const encodeExitRequestHex = ({ moduleId, nodeOpId, valIndex, valPubkey }: ExitRequest) => {
const pubkeyHex = de0x(valPubkey);
expect(pubkeyHex.length).to.equal(48 * 2);
return numberToHex(moduleId, 3) + numberToHex(nodeOpId, 5) + numberToHex(valIndex, 8) + pubkeyHex;
};

const encodeExitRequestsDataList = (requests: ExitRequest[]) => {
return "0x" + requests.map(encodeExitRequestHex).join("");
};

const deploy = async () => {
const deployed = await deployVEBO(admin.address);
oracle = deployed.oracle;
consensus = deployed.consensus;

initTx = await initVEBO({ admin: admin.address, oracle, consensus, resumeAfterDeploy: true });

oracleVersion = await oracle.getContractVersion();

await consensus.addMember(member1, 1);
await consensus.addMember(member2, 2);
await consensus.addMember(member3, 2);

const { refSlot } = await consensus.getCurrentFrame();
exitRequests = [
{ moduleId: 1, nodeOpId: 0, valIndex: 0, valPubkey: PUBKEYS[0] },
{ moduleId: 1, nodeOpId: 0, valIndex: 2, valPubkey: PUBKEYS[1] },
{ moduleId: 2, nodeOpId: 0, valIndex: 1, valPubkey: PUBKEYS[2] },
];

reportFields = {
consensusVersion: CONSENSUS_VERSION,
dataFormat: DATA_FORMAT_LIST,
refSlot: refSlot,
requestsCount: exitRequests.length,
data: encodeExitRequestsDataList(exitRequests),
};

reportItems = getValidatorsExitBusReportDataItems(reportFields);
reportHash = calcValidatorsExitBusReportDataHash(reportItems);

await consensus.connect(member1).submitReport(refSlot, reportHash, CONSENSUS_VERSION);
await consensus.connect(member3).submitReport(refSlot, reportHash, CONSENSUS_VERSION);
};

before(async () => {
[admin, member1, member2, member3, stranger, account1] = await ethers.getSigners();

await deploy();
});

beforeEach(async () => (originalState = await Snapshot.take()));

afterEach(async () => await Snapshot.restore(originalState));

context("deploying", () => {
it("deploying accounting oracle", async () => {
expect(oracle).to.be.not.null;
expect(consensus).to.be.not.null;
expect(initTx).to.be.not.null;
expect(oracleVersion).to.be.not.null;
expect(exitRequests).to.be.not.null;
expect(reportFields).to.be.not.null;
expect(reportItems).to.be.not.null;
expect(reportHash).to.be.not.null;
});
});

context("DEFAULT_ADMIN_ROLE", () => {
context("Admin is set at initialize", () => {
it("should set admin at initialize", async () => {
const DEFAULT_ADMIN_ROLE = await oracle.DEFAULT_ADMIN_ROLE();
await expect(initTx).to.emit(oracle, "RoleGranted").withArgs(DEFAULT_ADMIN_ROLE, admin, admin);
});
it("should revert without admin address", async () => {
await expect(
oracle.initialize(ZeroAddress, await consensus.getAddress(), CONSENSUS_VERSION, 0),
).to.be.revertedWithCustomError(oracle, "AdminCannotBeZero");
});
});
});

context("PAUSE_ROLE", () => {
it("should revert without PAUSE_ROLE role", async () => {
await expect(oracle.connect(stranger).pauseFor(0)).to.be.revertedWithOZAccessControlError(
await stranger.getAddress(),
await oracle.PAUSE_ROLE(),
);
});

it("should allow calling from a possessor of PAUSE_ROLE role", async () => {
await oracle.grantRole(await oracle.PAUSE_ROLE(), account1);

const tx = await oracle.connect(account1).pauseFor(9999);
await expect(tx).to.emit(oracle, "Paused").withArgs(9999);
});
});

context("RESUME_ROLE", () => {
it("should revert without RESUME_ROLE role", async () => {
await oracle.connect(admin).pauseFor(9999);

await expect(oracle.connect(stranger).resume()).to.be.revertedWithOZAccessControlError(
await stranger.getAddress(),
await oracle.RESUME_ROLE(),
);
});

it("should allow calling from a possessor of RESUME_ROLE role", async () => {
await oracle.pauseFor(9999, { from: admin });
await oracle.grantRole(await oracle.RESUME_ROLE(), account1);

const tx = await oracle.connect(account1).resume();
await expect(tx).to.emit(oracle, "Resumed").withArgs();
});
});

context("SUBMIT_DATA_ROLE", () => {
context("_checkMsgSenderIsAllowedToSubmitData", () => {
it("should revert from not consensus member without SUBMIT_DATA_ROLE role", async () => {
await expect(
oracle.connect(stranger).submitReportData(reportFields, oracleVersion),
).to.be.revertedWithCustomError(oracle, "SenderNotAllowed");
});

it("should allow calling from a possessor of SUBMIT_DATA_ROLE role", async () => {
await oracle.grantRole(await oracle.SUBMIT_DATA_ROLE(), account1);
const deadline = (await oracle.getConsensusReport()).processingDeadlineTime;
await consensus.setTime(deadline);

const tx = await oracle.connect(account1).submitReportData(reportFields, oracleVersion);

await expect(tx).to.emit(oracle, "ProcessingStarted").withArgs(reportFields.refSlot, reportHash);
});

it("should allow calling from a member", async () => {
const tx = await oracle.connect(member2).submitReportData(reportFields, oracleVersion);

await expect(tx).to.emit(oracle, "ProcessingStarted").withArgs(reportFields.refSlot, reportHash);
});
});
});
});
81 changes: 81 additions & 0 deletions test/0.8.9/oracle/validator-exit-bus-oracle.deploy.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { expect } from "chai";
import { ZeroAddress } from "ethers";
import { ethers } from "hardhat";

import { HardhatEthersSigner } from "@nomicfoundation/hardhat-ethers/signers";

import { HashConsensus__Harness, ValidatorsExitBus__Harness, ValidatorsExitBusOracle } from "typechain-types";

import { CONSENSUS_VERSION, SECONDS_PER_SLOT } from "lib";

import { deployVEBO, initVEBO } from "test/deploy";

describe("ValidatorsExitBusOracle.sol:deploy", () => {
context("Deployment and initial configuration", () => {
let admin: HardhatEthersSigner;
let defaultOracle: ValidatorsExitBusOracle;

before(async () => {
[admin] = await ethers.getSigners();
defaultOracle = (await deployVEBO(admin.address)).oracle;
});

it("initialize reverts if admin address is zero", async () => {
const deployed = await deployVEBO(admin.address);

await expect(
deployed.oracle.initialize(ZeroAddress, await deployed.consensus.getAddress(), CONSENSUS_VERSION, 0),
).to.be.revertedWithCustomError(defaultOracle, "AdminCannotBeZero");
});

it("reverts when slotsPerSecond is zero", async () => {
await expect(deployVEBO(admin.address, { secondsPerSlot: 0n })).to.be.revertedWithCustomError(
defaultOracle,
"SecondsPerSlotCannotBeZero",
);
});

context("deployment and init finishes successfully (default setup)", async () => {
let consensus: HashConsensus__Harness;
let oracle: ValidatorsExitBus__Harness;

before(async () => {
const deployed = await deployVEBO(admin.address);
await initVEBO({
admin: admin.address,
oracle: deployed.oracle,
consensus: deployed.consensus,
});

consensus = deployed.consensus;
oracle = deployed.oracle;
});

it("mock time-travellable setup is correct", async () => {
const time1 = await consensus.getTime();
expect(await oracle.getTime()).to.equal(time1);

await consensus.advanceTimeBy(SECONDS_PER_SLOT);

const time2 = await consensus.getTime();
expect(time2).to.equal(time1 + SECONDS_PER_SLOT);
expect(await oracle.getTime()).to.equal(time2);
});

it("initial configuration is correct", async () => {
expect(await oracle.getConsensusContract()).to.equal(await consensus.getAddress());
expect(await oracle.getConsensusVersion()).to.equal(CONSENSUS_VERSION);
expect(await oracle.SECONDS_PER_SLOT()).to.equal(SECONDS_PER_SLOT);
expect(await oracle.isPaused()).to.equal(true);
});

it("pause/resume operations work", async () => {
expect(await oracle.isPaused()).to.equal(true);
await oracle.resume();
expect(await oracle.isPaused()).to.equal(false);
await oracle.pauseFor(123);
expect(await oracle.isPaused()).to.equal(true);
});
});
});
});
1 change: 1 addition & 0 deletions test/deploy/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ export * from "./locator";
export * from "./dao";
export * from "./hashConsensus";
export * from "./withdrawalQueue";
export * from "./validatorExitBusOracle";
Loading

0 comments on commit cf98121

Please sign in to comment.