// // Copyright 2021 Vulcanize, Inc. // /* eslint-disable no-unused-expressions */ /* eslint-disable @typescript-eslint/no-explicit-any */ import { ContractInterface } from 'ethers'; import '@nomiclabs/hardhat-ethers'; import { artifacts, ethers } from 'hardhat'; import { CompilerOutput, CompilerOutputBytecode } from 'hardhat/types'; import { expect } from 'chai'; import isArray from 'lodash/isArray'; import { StorageLayout, GetStorageAt } from '../src'; // storageLayout doesnt exist in type CompilerOutput doesnt. // Extending CompilerOutput type to include storageLayout property. interface StorageCompilerOutput extends CompilerOutput { contracts: { [sourceName: string]: { [contractName: string]: { abi: ContractInterface; evm: { bytecode: CompilerOutputBytecode; deployedBytecode: CompilerOutputBytecode; methodIdentifiers: { [methodSignature: string]: string; }; }; storageLayout?: StorageLayout; } }; }; } interface ProofData { blockHash: string; account: { address: string; storage: { ipldBlock: string; cid: string; } } } /** * Get storage layout of specified contract. * @param contractName */ export const getStorageLayout = async (contractName: string): Promise => { const artifact = await artifacts.readArtifact(contractName); const buildInfo = await artifacts.getBuildInfo(`${artifact.sourceName}:${artifact.contractName}`); if (!buildInfo) { throw new Error('storageLayout not present in compiler output.'); } const output: StorageCompilerOutput = buildInfo.output; const { storageLayout } = output.contracts[artifact.sourceName][artifact.contractName]; if (!storageLayout) { throw new Error('Contract hasn\'t been compiled.'); } return storageLayout; }; /** * Get storage value in hardhat environment using ethers. * @param address * @param position */ export const getStorageAt: GetStorageAt = async ({ blockHash, contract, slot }) => { // TODO: Fix use of blockHash as hex string in getStorageAt. // Using blockHash in getStorageAt throws error. // https://github.com/ethers-io/ethers.js/pull/1550#issuecomment-841746994 // Using latest tag for temporary fix in test scenario. blockHash = 'latest'; const value = await ethers.provider.getStorageAt(contract, slot, blockHash); return { value, proof: { // Returning null value as proof, since ethers library getStorageAt method doesnt return proof. // This function is used in tests to mock the getStorageAt method of ipld-eth-client which returns proof along with value. data: JSON.stringify(null) } }; }; /** * Generate array of dummy addresses of specified length. * @param length */ export const generateDummyAddresses = (length: number): Array => { return Array.from({ length }, () => { return ethers.utils.getAddress(ethers.utils.hexlify(ethers.utils.randomBytes(20))); }); }; /** * Get latest blockHash. */ export const getBlockHash = async (): Promise => { const blockNumber = await ethers.provider.getBlockNumber(); const { hash } = await ethers.provider.getBlock(blockNumber); return hash; }; /** * Assert proof data returned from ipld graphql server. * @param blockHash * @param address * @param proofData */ export const assertProofData = (blockHash: string, address: string, proofData: ProofData): void => { const { blockHash: proofBlockHash, account: { address: proofAddress, storage } } = proofData; expect(proofBlockHash).to.equal(blockHash); expect(proofAddress).to.equal(address); expect(storage.cid).to.not.be.empty; expect(storage.ipldBlock).to.not.be.empty; }; /** * Assert array of proof data. * @param blockHash * @param address * @param proofArray */ export const assertProofArray = (blockHash: string, address: string, proofArray: Array): void => { proofArray.forEach(proofData => { if (isArray(proofData)) { assertProofArray(blockHash, address, proofData); return; } if (['blockHash', 'account'].every(key => key in proofData)) { assertProofData(blockHash, address, proofData); return; } assertProofStruct(blockHash, address, proofData); }); }; /** * Assert array of proof data from structure type. * @param blockHash * @param address * @param proofStruct */ export const assertProofStruct = (blockHash: string, address: string, proofStruct: {[key: string]: any}): void => { Object.values(proofStruct).forEach(proofData => { if (isArray(proofData)) { assertProofArray(blockHash, address, proofData); return; } if (['blockHash', 'account'].every(key => key in proofData)) { assertProofData(blockHash, address, proofData); return; } assertProofStruct(blockHash, address, proofData); }); };