Generalize verification (#1230)

* Generalize verify-core to verify

* Add deprecated chains filtering

* Add network filter flag

* Improve logging and make verifier more defensive
pull/1273/head
Yorke Rhodes 2 years ago committed by GitHub
parent 0c9979e80a
commit 625a09e39a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 18
      typescript/infra/config/environments/mainnet/core/verification/verification.json
  2. 28
      typescript/infra/config/environments/testnet2/core/verification/verification.json
  3. 17
      typescript/infra/scripts/helloworld/kathy.ts
  4. 15
      typescript/infra/scripts/utils.ts
  5. 46
      typescript/infra/scripts/verify.ts
  6. 17
      typescript/infra/src/utils/utils.ts
  7. 10
      typescript/sdk/src/consts/chains.ts
  8. 2
      typescript/sdk/src/deploy/HyperlaneDeployer.ts
  9. 31
      typescript/sdk/src/deploy/verify/ContractVerifier.ts
  10. 7
      typescript/sdk/src/index.ts
  11. 6
      typescript/sdk/src/utils/MultiGeneric.ts

@ -23,8 +23,8 @@
"isProxy": true "isProxy": true
}, },
{ {
"name": "ConnectionManager", "name": "AbacusConnectionManager",
"address": "0x12582c7B0f43c6A667CBaA7fA8b112F7fb1E69F0", "address": "0x12582c7B0f43c6A667CBaA7fA8b112lF7fb1E69F0",
"isProxy": false "isProxy": false
}, },
{ {
@ -190,7 +190,7 @@
"isProxy": true "isProxy": true
}, },
{ {
"name": "ConnectionManager", "name": "AbacusConnectionManager",
"address": "0xeA87ae93Fa0019a82A727bfd3eBd1cFCa8f64f1D", "address": "0xeA87ae93Fa0019a82A727bfd3eBd1cFCa8f64f1D",
"isProxy": false "isProxy": false
}, },
@ -357,7 +357,7 @@
"isProxy": true "isProxy": true
}, },
{ {
"name": "ConnectionManager", "name": "AbacusConnectionManager",
"address": "0x8b83fefd896fAa52057798f6426E9f0B080FCCcE", "address": "0x8b83fefd896fAa52057798f6426E9f0B080FCCcE",
"isProxy": false "isProxy": false
}, },
@ -524,7 +524,7 @@
"isProxy": true "isProxy": true
}, },
{ {
"name": "ConnectionManager", "name": "AbacusConnectionManager",
"address": "0x1Ab68dC4f7b6cfcd00218D4b761b7F3b5a724555", "address": "0x1Ab68dC4f7b6cfcd00218D4b761b7F3b5a724555",
"isProxy": false "isProxy": false
}, },
@ -691,7 +691,7 @@
"isProxy": true "isProxy": true
}, },
{ {
"name": "ConnectionManager", "name": "AbacusConnectionManager",
"address": "0x19dc38aeae620380430C200a6E990D5Af5480117", "address": "0x19dc38aeae620380430C200a6E990D5Af5480117",
"isProxy": false "isProxy": false
}, },
@ -858,7 +858,7 @@
"isProxy": true "isProxy": true
}, },
{ {
"name": "ConnectionManager", "name": "AbacusConnectionManager",
"address": "0x8b83fefd896fAa52057798f6426E9f0B080FCCcE", "address": "0x8b83fefd896fAa52057798f6426E9f0B080FCCcE",
"isProxy": false "isProxy": false
}, },
@ -1025,7 +1025,7 @@
"isProxy": true "isProxy": true
}, },
{ {
"name": "ConnectionManager", "name": "AbacusConnectionManager",
"address": "0xd83A4F747fE80Ed98839e05079B1B7Fe037b1638", "address": "0xd83A4F747fE80Ed98839e05079B1B7Fe037b1638",
"isProxy": false "isProxy": false
}, },
@ -1194,7 +1194,7 @@
"isProxy": true "isProxy": true
}, },
{ {
"name": "ConnectionManager", "name": "AbacusConnectionManager",
"address": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", "address": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908",
"constructorArguments": "", "constructorArguments": "",
"isProxy": false "isProxy": false

@ -7,7 +7,7 @@
"isProxy": false "isProxy": false
}, },
{ {
"name": "connectionManager", "name": "AbacusConnectionManager",
"address": "0xc41169650335Ad274157Ea5116Cdf227430A68a3", "address": "0xc41169650335Ad274157Ea5116Cdf227430A68a3",
"constructorArguments": [], "constructorArguments": [],
"isProxy": false "isProxy": false
@ -215,7 +215,7 @@
"isProxy": false "isProxy": false
}, },
{ {
"name": "connectionManager", "name": "AbacusConnectionManager",
"address": "0xfA1fBF362144ae1bEf2E33409948dA1FB812bb41", "address": "0xfA1fBF362144ae1bEf2E33409948dA1FB812bb41",
"constructorArguments": [], "constructorArguments": [],
"isProxy": false "isProxy": false
@ -387,7 +387,7 @@
"isProxy": false "isProxy": false
}, },
{ {
"name": "connectionManager", "name": "AbacusConnectionManager",
"address": "0x33AbaF6708be03Bdf0595DA0745A7111b01dB8c7", "address": "0x33AbaF6708be03Bdf0595DA0745A7111b01dB8c7",
"constructorArguments": [], "constructorArguments": [],
"isProxy": false "isProxy": false
@ -595,7 +595,7 @@
"isProxy": false "isProxy": false
}, },
{ {
"name": "connectionManager", "name": "AbacusConnectionManager",
"address": "0xb636B2c65A75d41F0dBe98fB33eb563d245a241a", "address": "0xb636B2c65A75d41F0dBe98fB33eb563d245a241a",
"constructorArguments": [], "constructorArguments": [],
"isProxy": false "isProxy": false
@ -803,7 +803,7 @@
"isProxy": false "isProxy": false
}, },
{ {
"name": "connectionManager", "name": "AbacusConnectionManager",
"address": "0xe403E16db1f5997bC62Dc611A8d42836364A7f01", "address": "0xe403E16db1f5997bC62Dc611A8d42836364A7f01",
"constructorArguments": [], "constructorArguments": [],
"isProxy": false "isProxy": false
@ -1011,7 +1011,7 @@
"isProxy": false "isProxy": false
}, },
{ {
"name": "connectionManager", "name": "AbacusConnectionManager",
"address": "0xFb55597F07417b08195Ba674f4dd58aeC9B89FBB", "address": "0xFb55597F07417b08195Ba674f4dd58aeC9B89FBB",
"constructorArguments": [], "constructorArguments": [],
"isProxy": false "isProxy": false
@ -1183,7 +1183,7 @@
"isProxy": false "isProxy": false
}, },
{ {
"name": "connectionManager", "name": "AbacusConnectionManager",
"address": "0x740bEd6E4eEc7c57a2818177Fba3f9E896D5DE1c", "address": "0x740bEd6E4eEc7c57a2818177Fba3f9E896D5DE1c",
"constructorArguments": [], "constructorArguments": [],
"isProxy": false "isProxy": false
@ -1373,7 +1373,7 @@
"isProxy": true "isProxy": true
}, },
{ {
"name": "abacusConnectionManager", "name": "AbacusConnectionManager",
"address": "0xC2E36cd6e32e194EE11f15D9273B64461A4D49A2", "address": "0xC2E36cd6e32e194EE11f15D9273B64461A4D49A2",
"constructorArguments": "", "constructorArguments": "",
"isProxy": false "isProxy": false
@ -1511,7 +1511,7 @@
"isProxy": true "isProxy": true
}, },
{ {
"name": "connectionManager", "name": "AbacusConnectionManager",
"address": "0x01812D60958798695391dacF092BAc4a715B1718", "address": "0x01812D60958798695391dacF092BAc4a715B1718",
"constructorArguments": "", "constructorArguments": "",
"isProxy": false "isProxy": false
@ -1579,7 +1579,7 @@
"isProxy": true "isProxy": true
}, },
{ {
"name": "abacusConnectionManager", "name": "AbacusConnectionManager",
"address": "0x16B710b86CAd07E6F1C531861a16F5feC29dba37", "address": "0x16B710b86CAd07E6F1C531861a16F5feC29dba37",
"constructorArguments": "", "constructorArguments": "",
"isProxy": false "isProxy": false
@ -1717,7 +1717,7 @@
"isProxy": true "isProxy": true
}, },
{ {
"name": "connectionManager", "name": "AbacusConnectionManager",
"address": "0xD356C996277eFb7f75Ee8bd61b31cC781A12F54f", "address": "0xD356C996277eFb7f75Ee8bd61b31cC781A12F54f",
"constructorArguments": "", "constructorArguments": "",
"isProxy": false "isProxy": false
@ -1785,7 +1785,7 @@
"isProxy": true "isProxy": true
}, },
{ {
"name": "connectionManager", "name": "AbacusConnectionManager",
"address": "0x16B710b86CAd07E6F1C531861a16F5feC29dba37", "address": "0x16B710b86CAd07E6F1C531861a16F5feC29dba37",
"constructorArguments": "", "constructorArguments": "",
"isProxy": false "isProxy": false
@ -1949,7 +1949,7 @@
"isProxy": true "isProxy": true
}, },
{ {
"name": "connectionManager", "name": "AbacusConnectionManager",
"address": "0x16B710b86CAd07E6F1C531861a16F5feC29dba37", "address": "0x16B710b86CAd07E6F1C531861a16F5feC29dba37",
"constructorArguments": "", "constructorArguments": "",
"isProxy": false "isProxy": false
@ -2093,7 +2093,7 @@
"isProxy": true "isProxy": true
}, },
{ {
"name": "connectionManager", "name": "AbacusConnectionManager",
"address": "0x4926a10788306D84202A2aDbd290b7743146Cc17", "address": "0x4926a10788306D84202A2aDbd290b7743146Cc17",
"constructorArguments": "", "constructorArguments": "",
"isProxy": false "isProxy": false

@ -14,13 +14,8 @@ import { debug, error, log, utils, warn } from '@hyperlane-xyz/utils';
import { KEY_ROLE_ENUM } from '../../src/agents/roles'; import { KEY_ROLE_ENUM } from '../../src/agents/roles';
import { ConnectionType } from '../../src/config/agent'; import { ConnectionType } from '../../src/config/agent';
import { startMetricsServer } from '../../src/utils/metrics'; import { startMetricsServer } from '../../src/utils/metrics';
import { import { assertChain, diagonalize, sleep } from '../../src/utils/utils';
assertChain, import { getArgs, getCoreEnvironmentConfig } from '../utils';
assertContext,
diagonalize,
sleep,
} from '../../src/utils/utils';
import { assertEnvironment, getArgs, getCoreEnvironmentConfig } from '../utils';
import { getApp } from './utils'; import { getApp } from './utils';
@ -69,12 +64,6 @@ const MAX_MESSAGES_ALLOWED_TO_SEND = 5;
function getKathyArgs() { function getKathyArgs() {
const args = getArgs() const args = getArgs()
.coerce('e', assertEnvironment)
.demandOption('e')
.coerce('context', assertContext)
.demandOption('context')
.boolean('cycle-once') .boolean('cycle-once')
.describe( .describe(
'cycle-once', 'cycle-once',
@ -135,7 +124,7 @@ function getKathyArgs() {
// Returns whether an error occurred // Returns whether an error occurred
async function main(): Promise<boolean> { async function main(): Promise<boolean> {
const { const {
e: environment, environment,
context, context,
chainsToSkip, chainsToSkip,
cycleOnce, cycleOnce,

@ -25,18 +25,19 @@ import { assertContext } from '../src/utils/utils';
export function getArgs() { export function getArgs() {
return yargs(process.argv.slice(2)) return yargs(process.argv.slice(2))
.alias('e', 'env') .describe('environment', 'deploy environment')
.describe('e', 'deploy environment') .coerce('environment', assertEnvironment)
.string('e') .demandOption('environment')
.alias('e', 'environment')
.describe('context', 'deploy context') .describe('context', 'deploy context')
.string('context') .coerce('context', assertContext)
.help('h') .demandOption('context')
.alias('h', 'help'); .alias('c', 'context');
} }
export async function getEnvironmentFromArgs(): Promise<string> { export async function getEnvironmentFromArgs(): Promise<string> {
const argv = await getArgs().argv; const argv = await getArgs().argv;
return argv.e!; return argv.environment!;
} }
export function assertEnvironment(env: string): DeployEnvironment { export function assertEnvironment(env: string): DeployEnvironment {

@ -1,6 +1,3 @@
import { existsSync, readFileSync } from 'fs';
import path from 'path';
import { import {
CompilerOptions, CompilerOptions,
CompleteChainMap, CompleteChainMap,
@ -8,38 +5,34 @@ import {
} from '@hyperlane-xyz/sdk'; } from '@hyperlane-xyz/sdk';
import { fetchGCPSecret } from '../src/utils/gcloud'; import { fetchGCPSecret } from '../src/utils/gcloud';
import { execCmd, readJSON } from '../src/utils/utils'; import { execCmd, readFileAtPath, readJSONAtPath } from '../src/utils/utils';
import { import { assertEnvironment, getArgs, getCoreEnvironmentConfig } from './utils';
getCoreEnvironmentConfig,
getCoreVerificationDirectory,
getEnvironment,
} from './utils';
async function main() { async function main() {
const environment = await getEnvironment(); const argv = await getArgs()
const config = getCoreEnvironmentConfig(environment) as any; .string('source')
.describe('source', 'flattened solidity source file')
.demandOption('source')
.string('artifacts')
.describe('artifacts', 'verification artifacts JSON file')
.demandOption('artifacts')
.string('network')
.describe('network', 'optional target network').argv;
const environment = assertEnvironment(argv.e!);
const config = getCoreEnvironmentConfig(environment);
const multiProvider = await config.getMultiProvider(); const multiProvider = await config.getMultiProvider();
const verification = readJSON( const verification = readJSONAtPath(argv.artifacts!);
getCoreVerificationDirectory(environment),
'verification.json',
);
const sourcePath = path.join( const sourcePath = argv.source!;
getCoreVerificationDirectory(environment), const flattenedSource = readFileAtPath(sourcePath);
'flattened.sol',
);
if (!existsSync(sourcePath)) {
throw new Error(
`Could not find flattened source at ${sourcePath}, run 'yarn hardhat flatten' in 'solidity/core'`,
);
}
// from solidity/core/hardhat.config.ts // from solidity/core/hardhat.config.ts
const compilerOptions: CompilerOptions = { const compilerOptions: CompilerOptions = {
codeformat: 'solidity-single-file', codeformat: 'solidity-single-file',
compilerversion: 'v0.8.16+commit.07a7930e', compilerversion: 'v0.8.13+commit.07a7930e',
optimizationUsed: '1', optimizationUsed: '1',
runs: '999999', runs: '999999',
}; };
@ -56,7 +49,6 @@ async function main() {
await execCmd(`solc-select use ${matches[1]}`); await execCmd(`solc-select use ${matches[1]}`);
await execCmd(`solc ${sourcePath}`); await execCmd(`solc ${sourcePath}`);
const flattenedSource = readFileSync(sourcePath, { encoding: 'utf8' });
const apiKeys: CompleteChainMap<string> = await fetchGCPSecret( const apiKeys: CompleteChainMap<string> = await fetchGCPSecret(
'explorer-api-keys', 'explorer-api-keys',
true, true,
@ -70,7 +62,7 @@ async function main() {
compilerOptions, compilerOptions,
); );
return verifier.verify(); return verifier.verify(argv.network ? [argv.network] : undefined);
} }
main().then(console.log).catch(console.error); main().then(console.log).catch(console.error);

@ -162,18 +162,19 @@ export function writeJSON(directory: string, filename: string, obj: any) {
); );
} }
export function readJSON(directory: string, filename: string) { export function readFileAtPath(filepath: string) {
if (!fs.existsSync(directory)) { if (!fs.existsSync(filepath)) {
throw Error("directory doesn't exist"); throw Error(`file doesn't exist at ${filepath}`);
} }
return readJSONAtPath(path.join(directory, filename)); return fs.readFileSync(filepath, 'utf8');
} }
export function readJSONAtPath(filepath: string) { export function readJSONAtPath(filepath: string) {
if (!fs.existsSync(filepath)) { return JSON.parse(readFileAtPath(filepath));
throw Error("file doesn't exist"); }
}
return JSON.parse(fs.readFileSync(filepath, 'utf8')); export function readJSON(directory: string, filename: string) {
return readJSONAtPath(path.join(directory, filename));
} }
export function assertRole(roleStr: string) { export function assertRole(roleStr: string) {

@ -27,6 +27,16 @@ export enum Chains { // must be string type to be used with Object.keys
test3 = 'test3', test3 = 'test3',
} }
export enum DeprecatedChains {
rinkeby = 'rinkeby',
optimismrinkeby = 'optimismrinkeby',
arbitrumrinkeby = 'arbitrumrinkeby',
kovan = 'kovan',
optimismkovan = 'optimismkovan',
arbitrumkovan = 'arbitrumkovan',
}
export const AllDeprecatedChains = Object.keys(DeprecatedChains) as string[];
export const Mainnets = [ export const Mainnets = [
Chains.arbitrum, Chains.arbitrum,
Chains.avalanche, Chains.avalanche,

@ -84,7 +84,7 @@ export abstract class HyperlaneDeployer<
for (const chain of targetChains) { for (const chain of targetChains) {
const chainConnection = this.multiProvider.getChainConnection(chain); const chainConnection = this.multiProvider.getChainConnection(chain);
const signerUrl = await chainConnection.getAddressUrl(); const signerUrl = await chainConnection.getAddressUrl();
this.logger(`Deploying to ${chain} from ${signerUrl}...`); this.logger(`Deploying to ${chain} from ${signerUrl} ...`);
this.deployedContracts[chain] = await this.deployContracts( this.deployedContracts[chain] = await this.deployContracts(
chain, chain,
this.configMap[chain], this.configMap[chain],

@ -23,6 +23,7 @@ enum ExplorerApiActions {
enum ExplorerApiErrors { enum ExplorerApiErrors {
ALREADY_VERIFIED = 'Contract source code already verified', ALREADY_VERIFIED = 'Contract source code already verified',
ALREADY_VERIFIED_ALT = 'Already Verified',
VERIFICATION_PENDING = 'Pending in queue', VERIFICATION_PENDING = 'Pending in queue',
PROXY_FAILED = 'A corresponding implementation contract was unfortunately not detected for the proxy address.', PROXY_FAILED = 'A corresponding implementation contract was unfortunately not detected for the proxy address.',
} }
@ -44,17 +45,16 @@ export class ContractVerifier<Chain extends ChainName> extends MultiGeneric<
this.logger = debug('hyperlane:ContractVerifier'); this.logger = debug('hyperlane:ContractVerifier');
} }
verify(): Promise<PromiseSettledResult<void>[]> { verify(targets = this.chains()): Promise<PromiseSettledResult<void>[]> {
return Promise.allSettled( return Promise.allSettled(
this.chains().map((chain) => this.verifyChain(chain, this.get(chain))), targets.map((chain) => this.verifyChain(chain, this.get(chain))),
); );
} }
async verifyChain(chain: Chain, inputs: VerificationInput): Promise<void> { async verifyChain(chain: Chain, inputs: VerificationInput): Promise<void> {
this.logger(`Verifying ${chain}...`); this.logger(`Verifying ${chain}...`);
const chainLogger = this.logger.extend(chain);
for (const input of inputs) { for (const input of inputs) {
await this.verifyContract(chain, input, chainLogger); await this.verifyContract(chain, input);
} }
} }
@ -89,18 +89,18 @@ export class ContractVerifier<Chain extends ChainName> extends MultiGeneric<
}); });
} }
// avoid rate limiting (5 requests per second)
await utils.sleep(1000 / 5);
const result = JSON.parse(await response.text()); const result = JSON.parse(await response.text());
if (result.message === 'NOTOK') { if (result.message === 'NOTOK') {
switch (result.result) { switch (result.result) {
case ExplorerApiErrors.VERIFICATION_PENDING: case ExplorerApiErrors.VERIFICATION_PENDING:
await utils.sleep(5000); await utils.sleep(5000); // wait 5 seconds
return this.submitForm(chain, action, options); return this.submitForm(chain, action, options);
case ExplorerApiErrors.ALREADY_VERIFIED: case ExplorerApiErrors.ALREADY_VERIFIED:
case ExplorerApiErrors.ALREADY_VERIFIED_ALT:
return; return;
case ExplorerApiErrors.PROXY_FAILED: case ExplorerApiErrors.PROXY_FAILED:
this.logger(`Proxy verification failed, try manually?`);
return;
default: default:
throw new Error(`Verification failed: ${result.result}`); throw new Error(`Verification failed: ${result.result}`);
} }
@ -112,13 +112,15 @@ export class ContractVerifier<Chain extends ChainName> extends MultiGeneric<
async verifyContract( async verifyContract(
chain: Chain, chain: Chain,
input: ContractVerificationInput, input: ContractVerificationInput,
logger = this.logger,
): Promise<void> { ): Promise<void> {
if (input.address === ethers.constants.AddressZero) { if (input.address === ethers.constants.AddressZero) {
return; return;
} }
logger(`Checking ${input.address} (${input.name})...`); if (Array.isArray(input.constructorArguments)) {
this.logger('Constructor arguments in legacy format, skipping');
return;
}
const data = { const data = {
sourceCode: this.flattenedSource, sourceCode: this.flattenedSource,
@ -142,11 +144,14 @@ export class ContractVerifier<Chain extends ChainName> extends MultiGeneric<
// poll for verified status // poll for verified status
if (guid) { if (guid) {
await this.submitForm(chain, ExplorerApiActions.CHECK_STATUS, { guid }); await this.submitForm(chain, ExplorerApiActions.CHECK_STATUS, { guid });
this.logger(`Successfully verified ${addressUrl}#code`);
} }
logger(`Already verified at ${addressUrl}#code`);
// mark as proxy (if applicable) // mark as proxy (if applicable)
if (input.isProxy) { if (input.isProxy) {
this.logger('Skipping proxy verification');
return;
const proxyGuid = await this.submitForm( const proxyGuid = await this.submitForm(
chain, chain,
ExplorerApiActions.MARK_PROXY, ExplorerApiActions.MARK_PROXY,
@ -159,8 +164,10 @@ export class ContractVerifier<Chain extends ChainName> extends MultiGeneric<
await this.submitForm(chain, ExplorerApiActions.CHECK_PROXY_STATUS, { await this.submitForm(chain, ExplorerApiActions.CHECK_PROXY_STATUS, {
guid: proxyGuid, guid: proxyGuid,
}); });
this.logger(
`Successfully verified proxy ${addressUrl}#readProxyContract`,
);
} }
logger(`Already verified at ${addressUrl}#readProxyContract`);
} }
} }
} }

@ -1,4 +1,9 @@
export { AllChains, Chains, Mainnets } from './consts/chains'; export {
AllChains,
Chains,
Mainnets,
AllDeprecatedChains,
} from './consts/chains';
export { chainMetadata } from './consts/chainMetadata'; export { chainMetadata } from './consts/chainMetadata';
export { export {
chainConnectionConfigs, chainConnectionConfigs,

@ -1,4 +1,4 @@
import { AllChains } from '../consts/chains'; import { AllChains, AllDeprecatedChains } from '../consts/chains';
import { ChainMap, ChainName, Remotes } from '../types'; import { ChainMap, ChainName, Remotes } from '../types';
export class MultiGeneric<Chain extends ChainName, Value> { export class MultiGeneric<Chain extends ChainName, Value> {
@ -40,7 +40,9 @@ export class MultiGeneric<Chain extends ChainName, Value> {
} }
chains(): Chain[] { chains(): Chain[] {
return Object.keys(this.chainMap) as Chain[]; return Object.keys(this.chainMap).filter(
(chain) => !AllDeprecatedChains.includes(chain),
) as Chain[];
} }
forEach(fn: (n: Chain, dc: Value) => void): void { forEach(fn: (n: Chain, dc: Value) => void): void {

Loading…
Cancel
Save