UNPKG

@nodeset/contracts

Version:

Protocol for accessing NodeSet's Constellation Ethereum staking network

863 lines (715 loc) 35.1 kB
import { ethers } from 'hardhat'; import { BigNumber } from 'ethers'; import { expect } from 'chai'; import { Protocol, SetupData, Signers } from '../test'; import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'; import { RocketPool } from '../test'; import { IMinipool, MockMinipool } from '../../typechain-types'; import { createMinipool, generateDepositData, generateDepositDataForStake, getMinipoolMinimumRPLStake } from '../rocketpool/_helpers/minipool'; import { nodeStakeRPL, registerNode } from '../rocketpool/_helpers/node'; import { mintRPL } from '../rocketpool/_helpers/tokens'; import { userDeposit } from '../rocketpool/_helpers/deposit'; import { ContractTransaction } from '@ethersproject/contracts'; import { Contract, EventFilter, utils } from 'ethers'; import seedrandom from 'seedrandom'; import { deposit } from '../rocketpool/deposit/scenario-deposit'; // optionally include the names of the accounts export const printBalances = async (accounts: string[], opts: any = {}) => { const { names = [] } = opts; for (let i = 0; i < accounts.length; i++) { console.log( `Balance: ${ethers.utils.formatEther(await ethers.provider.getBalance(accounts[i]))} at ${names.length > 0 ? names[i] : accounts[i] }` ); } }; export function computeKeccak256FromBytes32(bytes32String: string): string { // Computing the keccak256 hash directly from the Bytes32 string const hash = ethers.utils.keccak256(bytes32String); return hash; } export const printTokenBalances = async (accounts: string[], token: string, opts: any = {}) => { const { names = [] } = opts; const weth = await ethers.getContractAt('IWETH', token); for (let i = 0; i < accounts.length; i++) { console.log( `Token Balance: ${ethers.utils.formatEther(await weth.balanceOf(accounts[i]))} at ${names.length > 0 ? names[i] : accounts[i] }` ); } }; // given an object containing other objects that have addresses, print the balances of each address and the name of the object the address belongs to export const printObjectBalances = async (obj: any) => { for (const key in obj) { await printBalances([obj[key].address], { names: [key] }); } }; export const printObjectTokenBalances = async (obj: any, tokenAddr: string) => { for (const key in obj) { await printTokenBalances([obj[key].address], tokenAddr, { names: [key] }); } }; // acceptableErrorMargin is a number between 0 and 1 to indicate the percentage of error that is acceptable export const expectNumberE18ToBeApproximately = ( actualBig: BigNumber, expectedBig: BigNumber, accpetableErrorMargin: number ) => { const actual = Number(ethers.utils.formatEther(actualBig)); const expected = Number(ethers.utils.formatEther(expectedBig)); const upperBound = expected * (1 + accpetableErrorMargin); const lowerBound = expected * (1 - accpetableErrorMargin); // handle case where expected is 0 if (expected === 0) { expect(actual).to.be.within(-accpetableErrorMargin, accpetableErrorMargin); return; } expect(actual).to.be.within(lowerBound, upperBound); }; export const evaluateModel = (x: number, k: number, m: number) => { // f(x) = maxValue * (e^(k*(x-1)) - e^-k) / (1 - e^-k) // Wow I forgot how easy math is in any other language than solidity, 1 line lol return (m * (Math.exp(k * (x - 1)) - Math.exp(-k))) / (1 - Math.exp(-k)); }; export const assertMultipleTransfers = async ( tx: ContractTransaction, expectedTransfers: Array<{ from: string; to: string; value: BigNumber }> ) => { // Wait for the transaction to be mined const receipt = await tx.wait(); // Ensure events are defined or default to an empty array const events = receipt.events ?? []; // Filter for all Transfer events const transferEvents = events.filter((event) => event.event === 'Transfer'); // Check if there's at least one Transfer event expect(transferEvents, 'No Transfer events found').to.be.an('array').that.is.not.empty; // Iterate over expected transfers and match them with actual transfers let foundTransfers = []; for (const expectedTransfer of expectedTransfers) { const match = transferEvents.find((event) => { const { from, to, value } = event.args as any; return from === expectedTransfer.from && to === expectedTransfer.to && value.eq(expectedTransfer.value); }); if (match) { foundTransfers.push(match); } } // If the number of found transfers does not match the expected number, print details of all transfers if (foundTransfers.length !== expectedTransfers.length) { console.log('Not all expected Transfers were matched. Actual Transfer events:'); console.table( transferEvents.map((event) => ({ from: event.args!.from, to: event.args!.to, value: event.args!.value.toString(), })) ); expect.fail('Not all expected Transfers did not match'); } }; export const assertSingleTransferExists = async ( tx: ContractTransaction, expectedFrom: string, expectedTo: string, expectedValue: BigNumber ) => { // Wait for the transaction to be mined const receipt = await tx.wait(); // Ensure events are defined or default to an empty array const events = receipt.events ?? []; // Filter for all Transfer events const transferEvents = events.filter((event) => event.event === 'Transfer'); // Check if there's at least one Transfer event expect(transferEvents, 'No Transfer events found').to.be.an('array').that.is.not.empty; // Track if the expected Transfer event is found let isExpectedTransferFound = false; // Store details of all transfers for pretty printing if needed const allTransfers = []; for (const transferEvent of transferEvents) { const { from, to, value } = transferEvent.args as any; allTransfers.push({ from, to, value: value.toString() }); // Check if this event matches the expected values if (from === expectedFrom && to === expectedTo && value.toString() === expectedValue.toString()) { if (isExpectedTransferFound) { // Found more than one matching Transfer event, which is not expected expect.fail('Multiple Transfer events match the expected values'); } isExpectedTransferFound = true; } } // If expected Transfer event is not found, pretty print all transfers if (!isExpectedTransferFound) { console.log('No Transfer event matched the expected values. All Transfer events:'); console.table(allTransfers); expect.fail('Expected Transfer event not found'); } }; export async function deployMinipool(setupData: SetupData, bondValue: BigNumber, subNodeOperator: string) { const salt = 3; const { rawSalt, pepperedSalt } = await approvedSalt(salt, subNodeOperator); const depositData = await generateDepositData(setupData.protocol.superNode.address, pepperedSalt); const config = { timezoneLocation: 'Australia/Brisbane', bondAmount: bondValue, minimumNodeFee: 0, validatorPubkey: depositData.depositData.pubkey, validatorSignature: depositData.depositData.signature, depositDataRoot: depositData.depositDataRoot, salt: pepperedSalt, expectedMinipoolAddress: depositData.minipoolAddress, }; const { sig, timestamp } = await approveHasSignedExitMessageSig( setupData, subNodeOperator, '0x' + config.expectedMinipoolAddress, config.salt, ); await setupData.protocol.superNode.connect(setupData.signers.hyperdriver).createMinipool({ validatorPubkey: config.validatorPubkey, validatorSignature: config.validatorSignature, depositDataRoot: config.depositDataRoot, salt: rawSalt, expectedMinipoolAddress: config.expectedMinipoolAddress, sigGenesisTime: timestamp, sig: sig }, { value: ethers.utils.parseEther('1') }); return config.expectedMinipoolAddress; } export async function deployRPMinipool( signer: SignerWithAddress, rocketPool: RocketPool, signers: Signers, bondValue: BigNumber ) { await registerNode({ from: signer.address }); // Stake RPL to cover minipools let minipoolRplStake = await getMinipoolMinimumRPLStake(); let rplStake = ethers.BigNumber.from(minipoolRplStake.toString()).mul(3); rocketPool.rplContract.connect(signers.rplWhale).transfer(signer.address, rplStake); await nodeStakeRPL(rplStake, { from: signer.address }); const mockMinipool = await createMinipool({ from: signer.address, value: bondValue, }); await userDeposit({ from: signer.address, value: bondValue }); let scrubPeriod = 60 * 60 * 24; // 24 hours return await ethers.getContractAt('RocketMinipoolInterface', mockMinipool.address); } export async function upgradePriceFetcherToMock(signers: Signers, protocol: Protocol, price: BigNumber) { const mockPriceFetcherFactory = await ethers.getContractFactory('MockPriceFetcher'); const mockPriceFetcher = await mockPriceFetcherFactory.deploy(); await mockPriceFetcher.deployed(); const lastPrice = await protocol.priceFetcher.getPrice(); await protocol.priceFetcher.connect(signers.admin).upgradeTo(mockPriceFetcher.address); const priceFetcherV2 = await ethers.getContractAt('MockPriceFetcher', protocol.priceFetcher.address); await priceFetcherV2.setPrice(price); return lastPrice; } export async function printEventDetails(tx: ContractTransaction, contract: Contract): Promise<void> { const receipt = await tx.wait(); if (receipt.events) { for (let i = 0; i < receipt.events.length; i++) { const event = receipt.events[i]; if (event.event && event.args) { // Check if event name and args are available console.log(`Event Name: ${event.event}`); console.log('Arguments:'); // Ensure event.args is defined before accessing its properties if (event.args) { Object.keys(event.args) .filter((key) => isNaN(parseInt(key))) // Filter out numeric keys .forEach((key) => { console.log(` ${key}: ${event.args![key]}`); }); } } else if (event.topics && event.topics.length > 0) { // Decode the raw log try { const eventDescription = contract.interface.getEvent(event.topics[0]); console.log(`Event Name: ${eventDescription.name}`); const decodedData = contract.interface.decodeEventLog(eventDescription, event.data, event.topics); if (decodedData) { console.log('Arguments:'); Object.keys(decodedData).forEach((key) => { console.log(` ${key}: ${decodedData[key]}`); }); } } catch (e) { console.log('Uh oh, error occured printing events due to manual decoding :('); } } } } } export async function predictDeploymentAddress(address: string, factoryNonceOffset: number): Promise<string> { return ethers.utils.getContractAddress({ from: address, nonce: factoryNonceOffset }); } export async function increaseEVMTime(seconds: number) { // Sending the evm_increaseTime request await ethers.provider.send('evm_increaseTime', [seconds]); // Mining a new block to apply the EVM time change await ethers.provider.send('evm_mine', []); } /** * Automatically registers new operators, adds the minimum necessary assets to the operator distributor, * then creates and stakes one minipool for each of them. * @param setupData * @param subNodeOperators */ export const registerNewValidator = async (setupData: SetupData, subNodeOperators: SignerWithAddress[]) => { const requiredEth = ethers.utils.parseEther('8').mul(subNodeOperators.length); const { protocol, signers } = setupData; const bond = await setupData.protocol.superNode.bond(); const minipools = [] for (let i = 0; i < subNodeOperators.length; i++) { console.log('setting up node operator %s of %s', i + 1, subNodeOperators.length); //console.log('ETH balance of OD', await ethers.provider.getBalance(protocol.operatorDistributor.address)); if (!(await protocol.superNode.hasSufficientLiquidity(bond))) { //console.log('not enough liquidity, calling prepareOperatorDistributionContract'); await prepareOperatorDistributionContract(setupData, 1); } //console.log('sufficient liquidity for node operator %s of %s', i + 1, subNodeOperators.length); const nodeOperator = subNodeOperators[i]; const salt = i; if (!(await protocol.whitelist.getIsAddressInWhitelist(nodeOperator.address))) { await assertAddOperator(setupData, nodeOperator); } const { rawSalt, pepperedSalt } = await approvedSalt(salt, nodeOperator.address); const depositData = await generateDepositData(protocol.superNode.address, pepperedSalt); 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, }; minipools.push(depositData.minipoolAddress) await setupData.rocketPool.rocketDepositPoolContract.deposit({ value: ethers.utils.parseEther('32'), }); await setupData.rocketPool.rocketDepositPoolContract.assignDeposits(); const { sig, timestamp } = await approveHasSignedExitMessageSig( setupData, nodeOperator.address, '0x' + config.expectedMinipoolAddress, config.salt, ); await 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') }); // Simulate the passage of a day const oneDayInSeconds = 24 * 60 * 60; const nextBlockTimestamp = (await ethers.provider.getBlock('latest')).timestamp + oneDayInSeconds; await ethers.provider.send('evm_setNextBlockTimestamp', [nextBlockTimestamp]); await ethers.provider.send('evm_mine', []); // Mine a block to apply the new timestamp // enter stake mode const depositDataStake = await generateDepositDataForStake(config.expectedMinipoolAddress); await protocol.superNode.connect(nodeOperator).stake(depositDataStake.depositData.signature, depositDataStake.depositDataRoot, config.expectedMinipoolAddress); } return minipools; }; export const approvedSalt = async ( salt: number, subNodeOperator: string ) => { const subNodeOpSalt = ethers.utils.keccak256(ethers.utils.solidityPack(['uint256', 'address'], [salt, subNodeOperator])); const subNodeOpSaltBigNumber = ethers.BigNumber.from(subNodeOpSalt); return { rawSalt: salt, pepperedSalt: subNodeOpSaltBigNumber }; } export const approveHasSignedExitMessageSig = async ( setupData: SetupData, subNodeOperator: string, expectedMinipoolAddress: string, salt: BigNumber, ) => { const goodSigner = setupData.signers.adminServer; const role = await setupData.protocol.directory.hasRole( ethers.utils.keccak256(ethers.utils.toUtf8Bytes('ADMIN_SERVER_ROLE')), goodSigner.address ); expect(role).equals(true); const timestamp = (await ethers.provider.getBlock(await ethers.provider.getBlockNumber())).timestamp; const network = await ethers.provider.getNetwork(); const chainId = network.chainId; const nonce = await setupData.protocol.superNode.nonces(subNodeOperator) const packedData = ethers.utils.solidityPack( ['address', 'uint256', 'address', 'uint256', 'uint256', 'uint256'], [expectedMinipoolAddress, salt, setupData.protocol.superNode.address, nonce, 0, chainId] ); const messageHash = ethers.utils.keccak256(packedData); const messageHashBytes = ethers.utils.arrayify(messageHash); const sig = await goodSigner.signMessage(messageHashBytes); return { sig, timestamp }; }; export const approveHasSignedExitMessageSigBadTarget = async ( setupData: SetupData, subNodeOperator: string, expectedMinipoolAddress: string, salt: BigNumber, ) => { const goodSigner = setupData.signers.adminServer; const role = await setupData.protocol.directory.hasRole( ethers.utils.keccak256(ethers.utils.toUtf8Bytes('ADMIN_SERVER_ROLE')), goodSigner.address ); expect(role).equals(true); const timestamp = (await ethers.provider.getBlock(await ethers.provider.getBlockNumber())).timestamp; const network = await ethers.provider.getNetwork(); const chainId = network.chainId; const nonce = await setupData.protocol.superNode.nonces(subNodeOperator) const packedData = ethers.utils.solidityPack( ['address', 'uint256', 'address', 'uint256', 'uint256', 'uint256'], [expectedMinipoolAddress, salt, setupData.protocol.operatorDistributor.address, nonce, 0, chainId] ); const messageHash = ethers.utils.keccak256(packedData); const messageHashBytes = ethers.utils.arrayify(messageHash); const sig = await goodSigner.signMessage(messageHashBytes); return { sig, timestamp }; }; export const approveHasSignedExitMessageSigBadNonce = async ( setupData: SetupData, subNodeOperator: string, expectedMinipoolAddress: string, salt: BigNumber, ) => { const goodSigner = setupData.signers.adminServer; const role = await setupData.protocol.directory.hasRole( ethers.utils.keccak256(ethers.utils.toUtf8Bytes('ADMIN_SERVER_ROLE')), goodSigner.address ); expect(role).equals(true); const timestamp = (await ethers.provider.getBlock(await ethers.provider.getBlockNumber())).timestamp; const network = await ethers.provider.getNetwork(); const chainId = network.chainId; const nonce = await setupData.protocol.superNode.nonces(subNodeOperator) const packedData = ethers.utils.solidityPack( ['address', 'uint256', 'address', 'uint256', 'uint256', 'uint256'], [expectedMinipoolAddress, salt, setupData.protocol.superNode.address, nonce.add(1), 0, chainId] ); const messageHash = ethers.utils.keccak256(packedData); const messageHashBytes = ethers.utils.arrayify(messageHash); const sig = await goodSigner.signMessage(messageHashBytes); return { sig, timestamp }; }; export const approveHasSignedExitMessageSigBadChainId = async ( setupData: SetupData, subNodeOperator: string, expectedMinipoolAddress: string, salt: BigNumber, ) => { const goodSigner = setupData.signers.adminServer; const role = await setupData.protocol.directory.hasRole( ethers.utils.keccak256(ethers.utils.toUtf8Bytes('ADMIN_SERVER_ROLE')), goodSigner.address ); expect(role).equals(true); const timestamp = (await ethers.provider.getBlock(await ethers.provider.getBlockNumber())).timestamp; const network = await ethers.provider.getNetwork(); const chainId = network.chainId; const nonce = await setupData.protocol.superNode.nonces(subNodeOperator) const packedData = ethers.utils.solidityPack( ['address', 'uint256', 'address', 'uint256', 'uint256', 'uint256'], [expectedMinipoolAddress, salt, setupData.protocol.superNode.address, nonce, 0, chainId + 1] ); const messageHash = ethers.utils.keccak256(packedData); const messageHashBytes = ethers.utils.arrayify(messageHash); const sig = await goodSigner.signMessage(messageHashBytes); return { sig, timestamp }; }; export const approveHasSignedExitMessageSigBadEncoding = async ( setupData: SetupData, subNodeOperator: string, expectedMinipoolAddress: string, salt: BigNumber, ) => { const goodSigner = setupData.signers.adminServer; const role = await setupData.protocol.directory.hasRole( ethers.utils.keccak256(ethers.utils.toUtf8Bytes('ADMIN_SERVER_ROLE')), goodSigner.address ); expect(role).equals(true); const timestamp = (await ethers.provider.getBlock(await ethers.provider.getBlockNumber())).timestamp; const network = await ethers.provider.getNetwork(); const chainId = network.chainId; const nonce = await setupData.protocol.superNode.nonces(subNodeOperator) const packedData = ethers.utils.defaultAbiCoder.encode( ['address', 'uint256', 'uint256', 'address', 'uint256', 'uint256'], [expectedMinipoolAddress, salt, timestamp, setupData.protocol.superNode.address, nonce, chainId] ); const messageHash = ethers.utils.keccak256(packedData); const messageHashBytes = ethers.utils.arrayify(messageHash); const sig = await goodSigner.signMessage(messageHashBytes); return { sig, timestamp }; }; export const whitelistUserServerSig = async (setupData: SetupData, nodeOperator: SignerWithAddress) => { const timestamp = (await ethers.provider.getBlock(await ethers.provider.getBlockNumber())).timestamp; const network = await ethers.provider.getNetwork(); const chainId = network.chainId; const goodSigner = setupData.signers.adminServer; const role = await setupData.protocol.directory.hasRole( ethers.utils.keccak256(ethers.utils.toUtf8Bytes('ADMIN_SERVER_ROLE')), goodSigner.address ); expect(role).equals(true); const nonce = await setupData.protocol.whitelist.nonces(nodeOperator.address); const packedData = ethers.utils.solidityPack( ['address', 'address', 'uint256', 'uint256', 'uint256'], [nodeOperator.address, setupData.protocol.whitelist.address, nonce, 0, chainId] ); const messageHash = ethers.utils.keccak256(packedData); const messageHashBytes = ethers.utils.arrayify(messageHash); const sig = await goodSigner.signMessage(messageHashBytes); return { sig, timestamp }; }; export const assertAddOperator = async (setupData: SetupData, nodeOperator: SignerWithAddress) => { const { sig, timestamp } = await whitelistUserServerSig(setupData, nodeOperator); expect(await setupData.protocol.whitelist.getIsAddressInWhitelist(nodeOperator.address)).equals(false); await setupData.protocol.whitelist .connect(setupData.signers.adminServer) .addOperator(nodeOperator.address, sig); expect(await setupData.protocol.whitelist.getIsAddressInWhitelist(nodeOperator.address)).equals(true); }; export const deployMockToken = async (amount: BigNumber) => { const Token = await ethers.getContractFactory("MockERC20"); const token = await Token.deploy("Mock Token", "MT", amount) return token; } export const badAutWhitelistUserServerSig = async (setupData: SetupData, nodeOperator: SignerWithAddress) => { const timestamp = (await ethers.provider.getBlock(await ethers.provider.getBlockNumber())).timestamp; const network = await ethers.provider.getNetwork(); const chainId = network.chainId; const badSigner = setupData.signers.random5; const role = await setupData.protocol.directory.hasRole( ethers.utils.keccak256(ethers.utils.toUtf8Bytes('ADMIN_SERVER_ROLE')), badSigner.address ); expect(role).equals(false); const packedData = ethers.utils.solidityPack( ['address', 'address', 'uint256', 'uint256'], [nodeOperator.address, setupData.protocol.whitelist.address, 0, chainId] ); const messageHash = ethers.utils.keccak256(packedData); const messageHashBytes = ethers.utils.arrayify(messageHash); const sig = await badSigner.signMessage(messageHashBytes); return { sig, timestamp }; }; export async function prepareOperatorDistributionContract(setupData: SetupData, numOperators: number) { const vweth = setupData.protocol.vCWETH; let depositTarget = ethers.utils.parseEther('8').mul(BigNumber.from(numOperators)); let depositAmount = depositTarget.sub(await ethers.provider.getBalance(setupData.protocol.operatorDistributor.address)); const vaultMinimum = await vweth.getMissingLiquidityAfterDepositNoFee(depositAmount); let requiredEth; let liquidityReservePercent = await setupData.protocol.vCWETH.liquidityReservePercent(); if (liquidityReservePercent.eq(BigNumber.from(0))) { requiredEth = depositTarget; } else { requiredEth = depositAmount .add(vaultMinimum) .mul((await setupData.protocol.vCWETH.liquidityReservePercent()) .div(ethers.utils.parseUnits('1', 17))) .add(depositAmount.div(ethers.utils.parseUnits("1", 2))) .add(depositAmount.div(ethers.utils.parseUnits("1", 3))) .add(depositAmount.div(ethers.utils.parseUnits("1", 4))) .add(depositAmount.div(ethers.utils.parseUnits("1", 5))) .add(depositAmount.div(ethers.utils.parseUnits("1", 6))) .add(depositAmount.div(ethers.utils.parseUnits("1", 7))) .add(depositAmount.div(ethers.utils.parseUnits("1", 8))) .add(depositAmount.div(ethers.utils.parseUnits("1", 9))) .add(depositAmount.div(ethers.utils.parseUnits("1", 10))) .add(depositAmount.div(ethers.utils.parseUnits("1", 11))) .add(depositAmount.div(ethers.utils.parseUnits("1", 12))) .add(depositAmount.div(ethers.utils.parseUnits("1", 13))) .add(depositAmount.div(ethers.utils.parseUnits("1", 14))) .add(depositAmount.div(ethers.utils.parseUnits("1", 15))) .add(depositAmount.div(ethers.utils.parseUnits("1", 16))) .add(depositAmount.div(ethers.utils.parseUnits("1", 17))) .add(depositAmount.div(ethers.utils.parseUnits("1", 18))) .add(ethers.constants.One); } requiredEth = requiredEth.mul(ethers.utils.parseEther('1')).div(ethers.utils.parseEther('1').sub(await vweth.mintFee())); let error = 0; switch (numOperators) { case 1: error = 1; break; case 2: error = 2; break; case 3: error = (await setupData.protocol.vCWETH.liquidityReservePercent()).eq(0) ? 1 : 3; break; case 4: error = 4; break; case 5: error = 5; break; } // the real math is probably some weird function of the fee amount and liquidity reserve, // but we only use a param value of up to 5 in our tests and 3 is the only weird case requiredEth = requiredEth.add(error); await setupData.protocol.wETH.connect(setupData.signers.ethWhale).deposit({ value: requiredEth }); await setupData.protocol.wETH.connect(setupData.signers.ethWhale).approve(setupData.protocol.vCWETH.address, requiredEth); expect(await setupData.protocol.vCWETH.connect(setupData.signers.ethWhale).deposit(requiredEth, setupData.signers.ethWhale.address)).to.not.be.reverted; expect(await ethers.provider.getBalance(setupData.protocol.operatorDistributor.address)).equals(depositTarget); const rplStaked = await setupData.rocketPool.rocketNodeStakingContract.getNodeRPLStake(setupData.protocol.superNode.address) const ethMatched = await setupData.rocketPool.rocketNodeStakingContract.getNodeETHMatched(setupData.protocol.superNode.address) const rplRequired = (await setupData.protocol.operatorDistributor.calculateRplStakeShortfall( rplStaked, ethMatched.add((ethers.utils.parseEther("32").mul(BigNumber.from(numOperators))).sub(depositTarget)) )); const protocolSigner = setupData.signers.protocolSigner; await setupData.rocketPool.rplContract.connect(setupData.signers.rplWhale).transfer(setupData.protocol.operatorDistributor.address, rplRequired); return requiredEth; } export function createMockDid(rewardee: string) { return ethers.utils.solidityKeccak256(["address"], [rewardee]); } // sig schema keccak256(abi.encodePacked(_amount, _rewardee, nonces[_rewardee], address(this), block.chainid)) export async function createClaimRewardSig(setupData: SetupData, token: string, did: string, rewardee: string, amount: BigNumber) { return createClaimRewardSigWithNonce(setupData, token, did, rewardee, amount, await setupData.protocol.yieldDistributor.nonces(did)); } export async function createClaimRewardSigWithNonce(setupData: SetupData, token: string, did: string, rewardee: string, amount: BigNumber, nonce: BigNumber) { const network = await ethers.provider.getNetwork(); const chainId = network.chainId; /** * Optionally const packedData = ethers.utils.solidityKeccak256( ['address', 'address', 'uint256', 'uint256', 'address', 'uint256'], [token, rewardee, amount, nonce, setupData.protocol.yieldDistributor.address, chainId] ); * does a pack and hash in one call */ const packedData = ethers.utils.solidityPack( ['address', 'bytes32', 'address', 'uint256', 'uint256', 'uint256', 'address', 'uint256'], [token, did, rewardee, amount, nonce, 0, setupData.protocol.yieldDistributor.address, chainId] ); const messageHash = ethers.utils.keccak256(packedData); const messageHashBytes = ethers.utils.arrayify(messageHash); const adminServerHasAdminServerRole = await setupData.protocol.yieldDistributor.hasRole( ethers.utils.keccak256(ethers.utils.arrayify(ethers.utils.toUtf8Bytes('NODESET_ADMIN_SERVER_ROLE'))), setupData.signers.nodesetServerAdmin.address ); expect(adminServerHasAdminServerRole).equals(true); const sig = await setupData.signers.nodesetServerAdmin.signMessage(messageHashBytes); return sig; } export async function createClaimRewardBadTargetSigWithNonce(setupData: SetupData, token: string, did: string, rewardee: string, amount: BigNumber, nonce: BigNumber) { const network = await ethers.provider.getNetwork(); const chainId = network.chainId; const packedData = ethers.utils.solidityPack( ['address', 'bytes32', 'address', 'uint256', 'uint256', 'uint256', 'address', 'uint256'], [token, did, rewardee, amount, nonce, 0, setupData.protocol.superNode.address, chainId] // we are signing super node which is an intended bug ); const messageHash = ethers.utils.keccak256(packedData); const messageHashBytes = ethers.utils.arrayify(messageHash); const adminServerHasAdminServerRole = await setupData.protocol.yieldDistributor.hasRole( ethers.utils.keccak256(ethers.utils.arrayify(ethers.utils.toUtf8Bytes('NODESET_ADMIN_SERVER_ROLE'))), setupData.signers.nodesetServerAdmin.address ); expect(adminServerHasAdminServerRole).equals(true); const sig = await setupData.signers.nodesetServerAdmin.signMessage(messageHashBytes); return sig; } export async function createClaimRewardBadChainIdSigWithNonce(setupData: SetupData, token: string, did: string, rewardee: string, amount: BigNumber, nonce: BigNumber) { const network = await ethers.provider.getNetwork(); const chainId = network.chainId + 1; // this bug is intended const packedData = ethers.utils.solidityPack( ['address', 'bytes32', 'address', 'uint256', 'uint256', 'uint256', 'address', 'uint256'], [token, did, rewardee, amount, nonce, 0, setupData.protocol.yieldDistributor.address, chainId] ); const messageHash = ethers.utils.keccak256(packedData); const messageHashBytes = ethers.utils.arrayify(messageHash); const adminServerHasAdminServerRole = await setupData.protocol.yieldDistributor.hasRole( ethers.utils.keccak256(ethers.utils.arrayify(ethers.utils.toUtf8Bytes('NODESET_ADMIN_SERVER_ROLE'))), setupData.signers.nodesetServerAdmin.address ); expect(adminServerHasAdminServerRole).equals(true); const sig = await setupData.signers.nodesetServerAdmin.signMessage(messageHashBytes); return sig; } export async function createClaimRewardBadSignerSigWithNonce(setupData: SetupData, badSigner: SignerWithAddress, token: string, did: string, rewardee: string, amount: BigNumber, nonce: BigNumber) { const network = await ethers.provider.getNetwork(); const chainId = network.chainId; const packedData = ethers.utils.solidityPack( ['address', 'bytes32', 'address', 'uint256', 'uint256', 'uint256', 'address', 'uint256'], [token, did, rewardee, amount, nonce, 0, setupData.protocol.yieldDistributor.address, chainId] ); const messageHash = ethers.utils.keccak256(packedData); const messageHashBytes = ethers.utils.arrayify(messageHash); const adminServerHasAdminServerRole = await setupData.protocol.yieldDistributor.hasRole( ethers.utils.keccak256(ethers.utils.arrayify(ethers.utils.toUtf8Bytes('NODESET_ADMIN_SERVER_ROLE'))), badSigner.address ); expect(adminServerHasAdminServerRole).equals(false); const sig = await badSigner.signMessage(messageHashBytes); return sig; } export async function createClaimRewardBadEncodedSigWithNonce(setupData: SetupData, token: string, did: string, rewardee: string, amount: BigNumber, nonce: BigNumber) { const network = await ethers.provider.getNetwork(); const chainId = network.chainId; const packedData = ethers.utils.defaultAbiCoder.encode( ['address', 'bytes32', 'address', 'uint256', 'uint256', 'address', 'uint256'], [token, did, rewardee, amount, nonce, setupData.protocol.yieldDistributor.address, chainId] ); const messageHash = ethers.utils.keccak256(packedData); const messageHashBytes = ethers.utils.arrayify(messageHash); const adminServerHasAdminServerRole = await setupData.protocol.yieldDistributor.hasRole( ethers.utils.keccak256(ethers.utils.arrayify(ethers.utils.toUtf8Bytes('NODESET_ADMIN_SERVER_ROLE'))), setupData.signers.nodesetServerAdmin.address ); expect(adminServerHasAdminServerRole).equals(true); const sig = await setupData.signers.nodesetServerAdmin.signMessage(messageHashBytes); return sig; } export async function getNextContractAddress(signer: SignerWithAddress, offset = 0) { // Get current nonce of the signer const nonce = (await signer.getTransactionCount()) + offset; // Prepare the RLP encoded structure of the to-be-deployed contract const rlpEncoded = require('rlp').encode([signer.address, nonce]); // Calculate the hash const contractAddressHash = ethers.utils.keccak256(rlpEncoded); // The last 20 bytes of this hash are the address const contractAddress = '0x' + contractAddressHash.slice(-40); return contractAddress; } export async function getNextFactoryContractAddress(factoryAddress: string, factoryNonce: number) { // RLP encode the factory address and nonce const rlpEncoded = ethers.utils.solidityPack(['address', 'uint256'], [factoryAddress, factoryNonce]); // Calculate the hash const contractAddressHash = ethers.utils.keccak256(rlpEncoded); // The last 20 bytes of this hash are the address const contractAddress = '0x' + contractAddressHash.slice(-40); return contractAddress; } export async function getEventNames(tx: ContractTransaction, contract: Contract): Promise<string[]> { let emittedEvents: string[] = []; let emittedArgs: any[] = []; const receipt = await tx.wait(); if (receipt.events) { for (let i = 0; i < receipt.events.length; i++) { const event = receipt.events[i]; if (event.event) { // Check if event name is available emittedEvents.push(event.event); } else if (event.topics && event.topics.length > 0) { // Decode the raw log const eventDescription = contract.interface.getEvent(event.topics[0]); emittedEvents.push(eventDescription.name); const decodedData = contract.interface.decodeEventLog(eventDescription, event.data, event.topics); emittedArgs.push(decodedData); } } } return emittedEvents; }