UNPKG

@nodeset/contracts

Version:

Protocol for accessing NodeSet's Constellation Ethereum staking network

364 lines (292 loc) 22.2 kB
import { expect } from "chai"; import { ethers, upgrades, hardhatArguments } from "hardhat"; import { loadFixture } from "@nomicfoundation/hardhat-network-helpers"; import { protocolFixture } from "./test"; import { prepareOperatorDistributionContract, registerNewValidator } from "./utils/utils"; describe("XRETHOracle", function () { describe("Initialization", function () { it("Should initialize with correct directory address", async function () { const { protocol, signers } = await loadFixture(protocolFixture); const { oracle, directory } = protocol; expect(await oracle.getDirectory()).to.equal(directory.address); }); }); describe("Upgradability", function () { it("Admin can upgrade contract", async function () { const { protocol, signers } = await loadFixture(protocolFixture); const { oracle, directory } = protocol; const { admin } = signers; const initialAddress = oracle.address; const initialImpl = await upgrades.erc1967.getImplementationAddress(initialAddress); const MockXRETHOracleV2 = await ethers.getContractFactory("MockRETHOracle", admin); const newXRETHOracle = await upgrades.upgradeProxy(oracle.address, MockXRETHOracleV2, { kind: 'uups', unsafeAllow: [ 'state-variable-assignment', 'missing-public-upgradeto', 'state-variable-immutable', 'constructor', 'delegatecall', 'selfdestruct', 'external-library-linking', 'enum-definition', 'struct-definition' ], unsafeAllowCustomTypes: true, unsafeSkipStorageCheck: true }); expect(newXRETHOracle.address).to.equal(initialAddress); expect(await upgrades.erc1967.getImplementationAddress(initialAddress)).to.not.equal(initialImpl); }); }); describe("Yield Accrual", function () { it("Should return the correct total yield accrued", async function () { const { protocol, signers } = await loadFixture(protocolFixture); const { oracle, directory } = protocol; const { admin } = signers; expect(await oracle.getTotalYieldAccrued()).to.equal(0); }); it("Should set total yield accrued with valid signature", async function () { const { protocol, signers } = await loadFixture(protocolFixture); const { oracle, directory } = protocol; const { admin, random } = signers; const adminOracleRole = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("ADMIN_ORACLE_ROLE")); await directory.connect(admin).grantRole(adminOracleRole, random.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("100"); 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, oracle.address, chainId]); const signature = await random.signMessage(ethers.utils.arrayify(messageHash)); await oracle.connect(admin).setTotalYieldAccrued(signature, sigData); expect(await oracle.getTotalYieldAccrued()).to.equal(newTotalYield); }); it("Should set total yield accrued with valid signature adjusting for 0 fees (expect no impact)", async function () { const { protocol, signers } = await loadFixture(protocolFixture); const { oracle, directory } = protocol; const { admin, random } = signers; const adminOracleRole = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("ADMIN_ORACLE_ROLE")); await directory.connect(admin).grantRole(adminOracleRole, random.address); const timestamp = (await ethers.provider.getBlock(await ethers.provider.getBlockNumber())).timestamp const network = await ethers.provider.getNetwork(); const chainId = network.chainId; await protocol.vCWETH.connect(signers.admin).setTreasuryFee(0); await protocol.vCWETH.connect(signers.admin).setNodeOperatorFee(0); const newTotalYield = ethers.utils.parseEther("100"); 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, oracle.address, chainId]); const signature = await random.signMessage(ethers.utils.arrayify(messageHash)); await oracle.connect(admin).setTotalYieldAccrued(signature, sigData); expect(await oracle.getTotalYieldAccrued()).to.equal(newTotalYield); }); it("Should set total yield accrued with valid signature adjusting for 100% fees (expect no impact)", async function () { const { protocol, signers } = await loadFixture(protocolFixture); const { oracle, directory } = protocol; const { admin, random } = signers; const adminOracleRole = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("ADMIN_ORACLE_ROLE")); await directory.connect(admin).grantRole(adminOracleRole, random.address); const timestamp = (await ethers.provider.getBlock(await ethers.provider.getBlockNumber())).timestamp const network = await ethers.provider.getNetwork(); const chainId = network.chainId; await protocol.vCWETH.connect(signers.admin).setNodeOperatorFee(0); await protocol.vCWETH.connect(signers.admin).setTreasuryFee(ethers.utils.parseEther("1")); const newTotalYield = ethers.utils.parseEther("100"); 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, oracle.address, chainId]); const signature = await random.signMessage(ethers.utils.arrayify(messageHash)); await oracle.connect(admin).setTotalYieldAccrued(signature, sigData); expect(await oracle.getTotalYieldAccrued()).to.equal(newTotalYield); }); it("Should fail to set total yield accrued with invalid signature", async function () { const { protocol, signers } = await loadFixture(protocolFixture); const { oracle, directory } = protocol; const { admin, random } = signers; 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("100"); 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, oracle.address, chainId]); const signature = await random.signMessage(ethers.utils.arrayify(messageHash)); await expect( oracle.connect(admin).setTotalYieldAccrued(signature, sigData) ).to.be.revertedWith("signer must have permission from admin oracle role"); }); it("Should fail to set total yield accrued with incorrect message hash", async function () { const { protocol, signers } = await loadFixture(protocolFixture); const { oracle, directory } = protocol; const { admin, random, random2 } = signers; const timestamp = (await ethers.provider.getBlock(await ethers.provider.getBlockNumber())).timestamp const network = await ethers.provider.getNetwork(); const chainId = network.chainId; const adminOracleRole = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("ADMIN_ORACLE_ROLE")); await directory.connect(admin).grantRole(adminOracleRole, random.address); const newTotalYield = ethers.utils.parseEther("100"); const currentOracleError = await protocol.operatorDistributor.oracleError(); const sigData = { newTotalYieldAccrued: newTotalYield, expectedOracleError: currentOracleError, timeStamp: timestamp }; const incorrectMessageHash = ethers.utils.solidityKeccak256(["int256"], [newTotalYield]); const signature = await random.signMessage(ethers.utils.arrayify(incorrectMessageHash)); await expect( oracle.connect(random).setTotalYieldAccrued(signature, sigData) ).to.be.revertedWith("signer must have permission from admin oracle role"); }); it("Should fail to set total yield accrued with older signature", async function () { const { protocol, signers } = await loadFixture(protocolFixture); const { oracle, directory } = protocol; const { admin, random } = signers; const adminOracleRole = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("ADMIN_ORACLE_ROLE")); await directory.connect(admin).grantRole(adminOracleRole, random.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("100"); 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, oracle.address, chainId]); const signature = await random.signMessage(ethers.utils.arrayify(messageHash)); await oracle.connect(admin).setTotalYieldAccrued(signature, sigData) await expect( oracle.connect(admin).setTotalYieldAccrued(signature, sigData) ).to.be.revertedWith("cannot update oracle using old data"); }) describe("When expected oracle error is less than the actual error", function () { describe("When new yield is greater than 0", function () { it("Adjusts appropriately", async function () { const setupData = await loadFixture(protocolFixture); const { protocol, signers } = setupData; const { oracle, directory } = protocol; const { admin, random } = signers; const adminOracleRole = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("ADMIN_ORACLE_ROLE")); await directory.connect(admin).grantRole(adminOracleRole, random.address); const timestamp = (await ethers.provider.getBlock(await ethers.provider.getBlockNumber())).timestamp const network = await ethers.provider.getNetwork(); const chainId = network.chainId; let newTotalYield = ethers.utils.parseEther("100"); let currentOracleError = await protocol.operatorDistributor.oracleError(); const expectedOracleError = currentOracleError; const sigData = { newTotalYieldAccrued: newTotalYield, expectedOracleError: expectedOracleError, timeStamp: timestamp }; const messageHash = ethers.utils.solidityKeccak256(["int256", "uint256", "uint256", "address", "uint256"], [newTotalYield, currentOracleError, timestamp, oracle.address, chainId]); const signature = await random.signMessage(ethers.utils.arrayify(messageHash)); const minipools = await registerNewValidator(setupData, [random]); const newRewards = ethers.utils.parseEther("1"); const priorBalance = await protocol.vCWETH.totalAssets(); await signers.ethWhale.sendTransaction({ to: minipools[0], value: newRewards }) await protocol.operatorDistributor.processMinipool(minipools[0]); const actualYieldIncrease = (await protocol.vCWETH.totalAssets()).sub(priorBalance); await oracle.connect(admin).setTotalYieldAccrued(signature, sigData) newTotalYield = newTotalYield.sub(actualYieldIncrease); expect(expectedOracleError < currentOracleError); expect(await oracle.getTotalYieldAccrued()).to.equal(newTotalYield); }); }); describe("When new yield is less than 0", function () { it("Adjusts appropriately", async function () { const setupData = await loadFixture(protocolFixture); const { protocol, signers } = setupData; const { oracle, directory } = protocol; const { admin, random } = signers; const adminOracleRole = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("ADMIN_ORACLE_ROLE")); await directory.connect(admin).grantRole(adminOracleRole, random.address); const timestamp = (await ethers.provider.getBlock(await ethers.provider.getBlockNumber())).timestamp const network = await ethers.provider.getNetwork(); const chainId = network.chainId; let newTotalYield = ethers.utils.parseEther("-100"); let currentOracleError = await protocol.operatorDistributor.oracleError(); const expectedOracleError = currentOracleError; const sigData = { newTotalYieldAccrued: newTotalYield, expectedOracleError: expectedOracleError, timeStamp: timestamp }; const messageHash = ethers.utils.solidityKeccak256(["int256", "uint256", "uint256", "address", "uint256"], [newTotalYield, currentOracleError, timestamp, oracle.address, chainId]); const signature = await random.signMessage(ethers.utils.arrayify(messageHash)); const minipools = await registerNewValidator(setupData, [random]); const newRewards = ethers.utils.parseEther("1"); const priorBalance = await protocol.vCWETH.totalAssets(); await signers.ethWhale.sendTransaction({ to: minipools[0], value: newRewards }) await protocol.operatorDistributor.processMinipool(minipools[0]); const actualYieldIncrease = (await protocol.vCWETH.totalAssets()).sub(priorBalance); await oracle.connect(admin).setTotalYieldAccrued(signature, sigData) newTotalYield = newTotalYield.add(actualYieldIncrease); expect(expectedOracleError < currentOracleError); expect(await oracle.getTotalYieldAccrued()).to.equal(newTotalYield); }); }); describe("When new yield is equal to 0", function () { it("New yield remains 0", async function () { const setupData = await loadFixture(protocolFixture); const { protocol, signers } = setupData; const { oracle, directory } = protocol; const { admin, random } = signers; const adminOracleRole = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("ADMIN_ORACLE_ROLE")); await directory.connect(admin).grantRole(adminOracleRole, random.address); const timestamp = (await ethers.provider.getBlock(await ethers.provider.getBlockNumber())).timestamp const network = await ethers.provider.getNetwork(); const chainId = network.chainId; let newTotalYield = ethers.utils.parseEther("0"); let currentOracleError = await protocol.operatorDistributor.oracleError(); const expectedOracleError = currentOracleError; const sigData = { newTotalYieldAccrued: newTotalYield, expectedOracleError: expectedOracleError, timeStamp: timestamp }; const messageHash = ethers.utils.solidityKeccak256(["int256", "uint256", "uint256", "address", "uint256"], [newTotalYield, currentOracleError, timestamp, oracle.address, chainId]); const signature = await random.signMessage(ethers.utils.arrayify(messageHash)); const minipools = await registerNewValidator(setupData, [random]); const newRewards = ethers.utils.parseEther("1"); await signers.ethWhale.sendTransaction({ to: minipools[0], value: newRewards }) await protocol.operatorDistributor.processMinipool(minipools[0]); expect(expectedOracleError < currentOracleError); await oracle.connect(admin).setTotalYieldAccrued(signature, sigData) expect(await oracle.getTotalYieldAccrued()).to.equal(0); }); }); }); describe("When expected oracle error is equal to the actual error", function () { it("Total yield is not changed", async function () { const setupData = await loadFixture(protocolFixture); const { protocol, signers } = setupData; const { oracle, directory } = protocol; const { admin, random } = signers; const adminOracleRole = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("ADMIN_ORACLE_ROLE")); await directory.connect(admin).grantRole(adminOracleRole, random.address); const timestamp = (await ethers.provider.getBlock(await ethers.provider.getBlockNumber())).timestamp const network = await ethers.provider.getNetwork(); const chainId = network.chainId; let newTotalYield = ethers.utils.parseEther("100"); let currentOracleError = await protocol.operatorDistributor.oracleError(); const expectedOracleError = currentOracleError; const sigData = { newTotalYieldAccrued: newTotalYield, expectedOracleError: expectedOracleError, timeStamp: timestamp }; const messageHash = ethers.utils.solidityKeccak256(["int256", "uint256", "uint256", "address", "uint256"], [newTotalYield, currentOracleError, timestamp, oracle.address, chainId]); const signature = await random.signMessage(ethers.utils.arrayify(messageHash)); await oracle.connect(admin).setTotalYieldAccrued(signature, sigData) expect(expectedOracleError).equals(currentOracleError); expect(await oracle.getTotalYieldAccrued()).to.equal(newTotalYield); }); }); describe("When expected oracle error is greater than the actual error", function () { it("Reverts", async function () { const setupData = await loadFixture(protocolFixture); const { protocol, signers } = setupData; const { oracle, directory } = protocol; const { admin, random } = signers; const adminOracleRole = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("ADMIN_ORACLE_ROLE")); await directory.connect(admin).grantRole(adminOracleRole, random.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("100"); let currentOracleError = (await protocol.operatorDistributor.oracleError()).add(1); // add a bit to simulate a broken oracle const sigData = { newTotalYieldAccrued: newTotalYield, expectedOracleError: currentOracleError, timeStamp: timestamp }; const messageHash = ethers.utils.solidityKeccak256(["int256", "uint256", "uint256", "address", "uint256"], [newTotalYield, currentOracleError, timestamp, oracle.address, chainId]); const signature = await random.signMessage(ethers.utils.arrayify(messageHash)); await expect(oracle.connect(admin).setTotalYieldAccrued(signature, sigData)) .to.be.revertedWith('actual oracleError was less than expectedOracleError'); }); }); }); });