diff --git a/contracts-v1/env.ts b/contracts-v1/env.ts index a1aa2661a4..b9066c182a 100644 --- a/contracts-v1/env.ts +++ b/contracts-v1/env.ts @@ -30,7 +30,7 @@ const IS_GAS_REPORTING_ENABLED = Boolean(process.env.IS_GAS_REPORTING_ENABLED) | // CONTRACTS ADDRESSES // ---------------- const GNT_ADDRESS = process.env.GNT_ADDRESS || '0xE6de13D64F6036E4E3f5fC84B5EB620C5C7c1050'; -const GLM_ADDRESS = process.env.GLM_ADDRESS || '0x71432DD1ae7DB41706ee6a22148446087BdD0906'; +const GLM_ADDRESS = process.env.GLM_ADDRESS || '0x7DD9c5Cba05E151C895FDe1CF355C9A1D5DA6429'; const GLM_FAUCET_ADDRESS = process.env.GLM_FAUCET_ADDRESS || '0xD380d54df4993FC2Cae84F3ADB77fB97694933A8'; diff --git a/contracts-v1/hardhat.config.ts b/contracts-v1/hardhat.config.ts index 18ee1a8e16..8b2b5faef7 100644 --- a/contracts-v1/hardhat.config.ts +++ b/contracts-v1/hardhat.config.ts @@ -23,10 +23,14 @@ import './tasks/mine'; import './tasks/prepare-local-test-env'; import './tasks/send-glm'; import './tasks/target-check'; +import './tasks/auth-check'; +import './tasks/deposits-check'; +import './tasks/proposals-check'; import './tasks/target-deploy'; import './tasks/target-upgrade'; import './tasks/target-withdraw'; import './tasks/status'; +import './tasks/verify-deployment'; const config: HardhatUserConfig = { docgen: { diff --git a/contracts-v1/tasks/auth-check.ts b/contracts-v1/tasks/auth-check.ts new file mode 100644 index 0000000000..c8262a2d58 --- /dev/null +++ b/contracts-v1/tasks/auth-check.ts @@ -0,0 +1,61 @@ +import { subtask, task } from 'hardhat/config'; +import { exit } from 'process'; + +import { verify } from './verification/verifiable'; + +/* eslint no-console: 0 */ +import { AUTH, ZERO_ADDRESS } from '../helpers/constants'; +import { Auth } from '../typechain'; + +task('auth-check', 'Check auth at particular addres') + .addParam('address', 'Auth contract address') + .addParam('contractName', 'Name of the contract', AUTH) + .addFlag('verify', 'Verifies contract storage against expected values') + .addOptionalParam('multisig', 'Expected multisig address') + .addOptionalParam('pendingowner', 'Expected pending owner', ZERO_ADDRESS) + .setAction(async (taskArgs, { ethers, getNamedAccounts, run }) => { + console.log('Querying contract deployed at:', taskArgs.address); + const { deployer } = await getNamedAccounts(); + const target: Auth = await ethers.getContractAt( + taskArgs.contractName, + taskArgs.address, + deployer, + ); + + const multisig = await target.multisig(); + const pendingOwner = await target.pendingOwner(); + + console.log('multisig: ', multisig); + console.log('pendingOwner: ', pendingOwner); + + if (taskArgs.verify) { + const result = await run('auth-check:verify'); + exit(result); + } + }); + +subtask('auth-check:verify', 'Verify auth contract') + .addParam('address', 'Auth contract address') + .addParam('multisig', 'Expected multisig address') + .addParam('pendingowner', 'Expected pending owner', ZERO_ADDRESS) + .setAction(async (taskArgs, hre) => { + console.log('Veryfing Auth contract'); + + const res = await verify( + { + address: taskArgs.address, + contractName: AUTH, + properties: [ + ['multisig', taskArgs.multisig], + ['pendingOwner', taskArgs.pendingowner], + ], + }, + hre, + ); + + if (res === 0) { + console.log('Auth contract successfully verified! 👍'); + } + + return res; + }); diff --git a/contracts-v1/tasks/deposits-check.ts b/contracts-v1/tasks/deposits-check.ts new file mode 100644 index 0000000000..746c806fd8 --- /dev/null +++ b/contracts-v1/tasks/deposits-check.ts @@ -0,0 +1,62 @@ +import { subtask, task } from 'hardhat/config'; +import { exit } from 'process'; + +/* eslint no-console: 0 */ +import { verify } from './verification/verifiable'; + +import { GLM_ADDRESS } from '../env'; +import { DEPOSITS } from '../helpers/constants'; +import { Deposits } from '../typechain'; + +task('deposits-check', 'Check deposits at particular addres') + .addParam('address', 'Deposits contract address') + .addParam('contractName', 'Name of the contract', DEPOSITS) + .addFlag('verify', 'Verifies contract storage against expected values') + .addOptionalParam('auth', 'Expected Auth contract address') + .addOptionalParam('glm', 'Expected GLM contract address') + .setAction(async (taskArgs, { ethers, getNamedAccounts, run }) => { + console.log('Querying contract deployed at:', taskArgs.address); + const { deployer } = await getNamedAccounts(); + const target: Deposits = await ethers.getContractAt( + taskArgs.contractName, + taskArgs.address, + deployer, + ); + + const auth = await target.auth(); + const glm = await target.glm(); + + console.log('auth: ', auth); + console.log('glm: ', glm); + + if (taskArgs.verify) { + const result = await run('deposits-check:verify', taskArgs); + exit(result); + } + }); + +subtask('deposits-check:verify', 'Verify deposits contract') + .addParam('address', 'Deposits contract address') + .addParam('auth', 'Expected Auth contract address') + .addParam('glm', 'Expected GLM contract address', GLM_ADDRESS) + .setAction(async (taskArgs, hre) => { + console.log('Veryfing Deposits contract'); + + const res = await verify( + { + address: taskArgs.address, + contractName: DEPOSITS, + properties: [ + ['auth', taskArgs.auth], + ['glm', taskArgs.glm], + ], + }, + hre, + ); + + if (res === 0) { + console.log('Deposits contract successfully verified! 👍'); + } + + return res; + }); diff --git a/contracts-v1/tasks/proposals-check.ts b/contracts-v1/tasks/proposals-check.ts new file mode 100644 index 0000000000..62726f9fb4 --- /dev/null +++ b/contracts-v1/tasks/proposals-check.ts @@ -0,0 +1,87 @@ +/* eslint no-console: 0 */ +import { subtask, task, types } from 'hardhat/config'; +import { exit } from 'process'; + +import { verify, arraysEqualPredicate } from './verification/verifiable'; + +import { PROPOSALS_CID } from '../env'; +import { PROPOSALS, ZERO_ADDRESS } from '../helpers/constants'; +import { Proposals } from '../typechain'; + +task('proposals-check', 'Check proposals at particular addres') + .addParam('address', 'Proposals contracts address') + .addParam('contractName', 'Name of the contract', PROPOSALS) + .addFlag('verify', 'Verifies contract storage against expected values') + .addOptionalParam('auth', 'Expected Auth contract address') + .addOptionalParam('cid', 'Expected cid', PROPOSALS_CID) + .addOptionalParam('epochs', 'Expected epochs contract address', ZERO_ADDRESS) + .addOptionalParam( + 'epoch', + 'Epoch for which to query for proposal addresses', + undefined, + types.int, + ) + .addParam('proposals', 'Comma separated list of expected proposals') + .setAction(async (taskArgs, { ethers, getNamedAccounts, run }) => { + console.log('Querying contract deployed at:', taskArgs.address); + const { deployer } = await getNamedAccounts(); + const target: Proposals = await ethers.getContractAt( + taskArgs.contractName, + taskArgs.address, + deployer, + ); + + const auth = await target.auth(); + const cid = await target.cid(); + const epochs = await target.epochs(); + + console.log('auth: ', auth); + console.log('cid: ', cid); + console.log('epochs: ', epochs); + + if (taskArgs.epoch !== undefined) { + console.log( + `proposals at epoch ${taskArgs.epoch}: `, + await target.getProposalAddresses(taskArgs.epoch), + ); + } + + if (taskArgs.verify) { + const result = await run('proposals-check:verify', taskArgs); + exit(result); + } + }); + +subtask('proposals-check:verify', 'Verify Proposals contract') + .addParam('address', 'proposals contract address') + .addParam('auth', 'Expected Auth contract address') + .addParam('epochs', 'Expected Epochs contract address') + .addParam('cid', 'Expected cid') + .addParam('epoch', 'Epoch at which to query for proposals', 100, types.int) + .addParam('proposals', 'Comma separated list of expected proposals') + .setAction(async (taskArgs, hre) => { + console.log('Veryfing Proposals contract'); + + const res = await verify( + { + address: taskArgs.address, + contractName: PROPOSALS, + properties: [ + ['auth', taskArgs.auth], + ['cid', taskArgs.cid], + ['epochs', taskArgs.epochs], + [ + ['getProposalAddresses', [taskArgs.epoch]], + arraysEqualPredicate(taskArgs.proposals.split(',')), + ], + ], + }, + hre, + ); + + if (res === 0) { + console.log('Proposals contract successfully verified! 👍'); + } + + return res; + }); diff --git a/contracts-v1/tasks/verification/verifiable.ts b/contracts-v1/tasks/verification/verifiable.ts new file mode 100644 index 0000000000..b51e16c1ce --- /dev/null +++ b/contracts-v1/tasks/verification/verifiable.ts @@ -0,0 +1,96 @@ +/* eslint no-console: 0 */ +import { Contract } from 'ethers'; +import { HardhatRuntimeEnvironment } from 'hardhat/types'; + +type PropertyPredicateT = (value: any) => boolean; +type VerifiablePropertyT = [string | [string, [any]], PropertyPredicateT | any]; + +interface Verifiable { + address: string; + contractName: string; + properties: Array; +} + +async function getContract( + verifiable: Verifiable, + hre: HardhatRuntimeEnvironment, +): Promise { + return hre.ethers.getContractAt(verifiable.contractName, verifiable.address); +} + +function propertyOf(property: keyof ContractT) { + return property; +} + +function defaultPredicate(expected: any) { + return (actual: any) => { + return actual === expected; + }; +} + +export function arraysEqualPredicate(expected: [any], shouldCheckOrder = false) { + return (a: [any]): boolean => { + const actual = [...a]; // make a copy + + if (expected.length !== actual.length) return false; + + if (!shouldCheckOrder) { + expected.sort(); + actual.sort(); + } + + return expected.every((elem, index) => { + console.debug('Expected: %s, actual: %s', elem, actual[index]); + return elem === actual[index]; + }); + }; +} + +function predicateToStr(p: PropertyPredicateT | any): string { + if (p instanceof Function) { + return ''; + } + + return p.toString(); +} + +/* eslint-disable no-console */ +export async function verify( + verifiable: Verifiable, + hre: HardhatRuntimeEnvironment, +): Promise { + const contract: ContractT = await getContract(verifiable, hre); + + let errors = 0; + + for await (const [property, predicateOrVal] of verifiable.properties) { + let propertyFunc; + let propertyName: string; + if (typeof property === 'string') { + propertyName = property as string; + propertyFunc = async () => contract[propertyOf(propertyName)](); + } else { + let args: [any]; + [propertyName, args] = property as [string, [any]]; + propertyFunc = async () => contract[propertyOf(propertyName)](...args); + } + + const propertyValue = await propertyFunc(); + const predicate = + predicateOrVal instanceof Function ? predicateOrVal : defaultPredicate(predicateOrVal); + + console.debug( + 'Checking %s. Expected: %s. Value: %s', + propertyName, + predicateToStr(predicateOrVal), + propertyValue, + ); + + if (!predicate(propertyValue)) { + console.error('Verification failed for %s.', propertyName); + errors += 1; + } + } + + return errors; +} diff --git a/contracts-v1/tasks/verify-deployment.ts b/contracts-v1/tasks/verify-deployment.ts new file mode 100644 index 0000000000..83679b3b62 --- /dev/null +++ b/contracts-v1/tasks/verify-deployment.ts @@ -0,0 +1,66 @@ +import { task, types } from 'hardhat/config'; +import { exit } from 'process'; + +import { MULTISIG_ADDRESS, PROPOSALS_CID, PROPOSALS_ADDRESSES, GLM_ADDRESS } from '../env'; +import { ZERO_ADDRESS } from '../helpers/constants'; + +task('verify-deployment', 'Verify deployed contracts') + .addFlag('auth', 'Verifies Auth contaract storage against expected values') + .addFlag('deposits', 'Verifies Deposists contract storage against expected values') + .addFlag('proposals', 'Verifies Proposals contract storage against expected values') + .addOptionalParam( + 'file', + 'File with expected values in json format. Explicitly defined arguments take precedence.', + ) + // Auth + .addParam('authaddress', 'Auth contract address') + .addOptionalParam('multisig', 'Expected multisig', MULTISIG_ADDRESS) + .addOptionalParam('pendingowner', 'Expected Auth pendingOwner', ZERO_ADDRESS) + // Deposits + .addParam('depositsaddress', 'Deposits contract address') + .addOptionalParam('glm', 'Expected GLM contract address', GLM_ADDRESS) + // Proposals + .addOptionalParam('proposalsaddress', 'Proposals contract address') + .addOptionalParam('epochs', 'Expected Epochs contract address', ZERO_ADDRESS) + .addOptionalParam('cid', 'Expected cid', PROPOSALS_CID) + .addOptionalParam('epoch', 'Epoch at which to query for proposals', 100, types.int) + .addOptionalParam( + 'proposalslist', + 'Comma separated list of expected proposals', + PROPOSALS_ADDRESSES, + ) + .setAction(async (taskArgs, hre) => { + let errors = 0; + + if (taskArgs.auth) { + const result = await hre.run('auth-check:verify', { + address: taskArgs.authaddress, + multisig: taskArgs.multisig, + pendingowner: taskArgs.pendingowner, + }); + errors += result; + } + + if (taskArgs.deposits) { + const result = await hre.run('deposits-check:verify', { + address: taskArgs.depositsaddress, + auth: taskArgs.authaddress, + glm: taskArgs.glm, + }); + errors += result; + } + + if (taskArgs.proposals) { + const result = await hre.run('proposals-check:verify', { + address: taskArgs.proposalsaddress, + auth: taskArgs.authaddress, + cid: taskArgs.cid, + epoch: taskArgs.epoch, + epochs: taskArgs.epochs, + proposals: taskArgs.proposalslist, + }); + errors += result; + } + + exit(errors); + });