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

@ -7,7 +7,7 @@
"isProxy": false
},
{
"name": "connectionManager",
"name": "AbacusConnectionManager",
"address": "0xc41169650335Ad274157Ea5116Cdf227430A68a3",
"constructorArguments": [],
"isProxy": false
@ -215,7 +215,7 @@
"isProxy": false
},
{
"name": "connectionManager",
"name": "AbacusConnectionManager",
"address": "0xfA1fBF362144ae1bEf2E33409948dA1FB812bb41",
"constructorArguments": [],
"isProxy": false
@ -387,7 +387,7 @@
"isProxy": false
},
{
"name": "connectionManager",
"name": "AbacusConnectionManager",
"address": "0x33AbaF6708be03Bdf0595DA0745A7111b01dB8c7",
"constructorArguments": [],
"isProxy": false
@ -595,7 +595,7 @@
"isProxy": false
},
{
"name": "connectionManager",
"name": "AbacusConnectionManager",
"address": "0xb636B2c65A75d41F0dBe98fB33eb563d245a241a",
"constructorArguments": [],
"isProxy": false
@ -803,7 +803,7 @@
"isProxy": false
},
{
"name": "connectionManager",
"name": "AbacusConnectionManager",
"address": "0xe403E16db1f5997bC62Dc611A8d42836364A7f01",
"constructorArguments": [],
"isProxy": false
@ -1011,7 +1011,7 @@
"isProxy": false
},
{
"name": "connectionManager",
"name": "AbacusConnectionManager",
"address": "0xFb55597F07417b08195Ba674f4dd58aeC9B89FBB",
"constructorArguments": [],
"isProxy": false
@ -1183,7 +1183,7 @@
"isProxy": false
},
{
"name": "connectionManager",
"name": "AbacusConnectionManager",
"address": "0x740bEd6E4eEc7c57a2818177Fba3f9E896D5DE1c",
"constructorArguments": [],
"isProxy": false
@ -1373,7 +1373,7 @@
"isProxy": true
},
{
"name": "abacusConnectionManager",
"name": "AbacusConnectionManager",
"address": "0xC2E36cd6e32e194EE11f15D9273B64461A4D49A2",
"constructorArguments": "",
"isProxy": false
@ -1511,7 +1511,7 @@
"isProxy": true
},
{
"name": "connectionManager",
"name": "AbacusConnectionManager",
"address": "0x01812D60958798695391dacF092BAc4a715B1718",
"constructorArguments": "",
"isProxy": false
@ -1579,7 +1579,7 @@
"isProxy": true
},
{
"name": "abacusConnectionManager",
"name": "AbacusConnectionManager",
"address": "0x16B710b86CAd07E6F1C531861a16F5feC29dba37",
"constructorArguments": "",
"isProxy": false
@ -1717,7 +1717,7 @@
"isProxy": true
},
{
"name": "connectionManager",
"name": "AbacusConnectionManager",
"address": "0xD356C996277eFb7f75Ee8bd61b31cC781A12F54f",
"constructorArguments": "",
"isProxy": false
@ -1785,7 +1785,7 @@
"isProxy": true
},
{
"name": "connectionManager",
"name": "AbacusConnectionManager",
"address": "0x16B710b86CAd07E6F1C531861a16F5feC29dba37",
"constructorArguments": "",
"isProxy": false
@ -1949,7 +1949,7 @@
"isProxy": true
},
{
"name": "connectionManager",
"name": "AbacusConnectionManager",
"address": "0x16B710b86CAd07E6F1C531861a16F5feC29dba37",
"constructorArguments": "",
"isProxy": false
@ -2093,7 +2093,7 @@
"isProxy": true
},
{
"name": "connectionManager",
"name": "AbacusConnectionManager",
"address": "0x4926a10788306D84202A2aDbd290b7743146Cc17",
"constructorArguments": "",
"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 { ConnectionType } from '../../src/config/agent';
import { startMetricsServer } from '../../src/utils/metrics';
import {
assertChain,
assertContext,
diagonalize,
sleep,
} from '../../src/utils/utils';
import { assertEnvironment, getArgs, getCoreEnvironmentConfig } from '../utils';
import { assertChain, diagonalize, sleep } from '../../src/utils/utils';
import { getArgs, getCoreEnvironmentConfig } from '../utils';
import { getApp } from './utils';
@ -69,12 +64,6 @@ const MAX_MESSAGES_ALLOWED_TO_SEND = 5;
function getKathyArgs() {
const args = getArgs()
.coerce('e', assertEnvironment)
.demandOption('e')
.coerce('context', assertContext)
.demandOption('context')
.boolean('cycle-once')
.describe(
'cycle-once',
@ -135,7 +124,7 @@ function getKathyArgs() {
// Returns whether an error occurred
async function main(): Promise<boolean> {
const {
e: environment,
environment,
context,
chainsToSkip,
cycleOnce,

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

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

@ -27,6 +27,16 @@ export enum Chains { // must be string type to be used with Object.keys
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 = [
Chains.arbitrum,
Chains.avalanche,

@ -84,7 +84,7 @@ export abstract class HyperlaneDeployer<
for (const chain of targetChains) {
const chainConnection = this.multiProvider.getChainConnection(chain);
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(
chain,
this.configMap[chain],

@ -23,6 +23,7 @@ enum ExplorerApiActions {
enum ExplorerApiErrors {
ALREADY_VERIFIED = 'Contract source code already verified',
ALREADY_VERIFIED_ALT = 'Already Verified',
VERIFICATION_PENDING = 'Pending in queue',
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');
}
verify(): Promise<PromiseSettledResult<void>[]> {
verify(targets = this.chains()): Promise<PromiseSettledResult<void>[]> {
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> {
this.logger(`Verifying ${chain}...`);
const chainLogger = this.logger.extend(chain);
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());
if (result.message === 'NOTOK') {
switch (result.result) {
case ExplorerApiErrors.VERIFICATION_PENDING:
await utils.sleep(5000);
await utils.sleep(5000); // wait 5 seconds
return this.submitForm(chain, action, options);
case ExplorerApiErrors.ALREADY_VERIFIED:
case ExplorerApiErrors.ALREADY_VERIFIED_ALT:
return;
case ExplorerApiErrors.PROXY_FAILED:
this.logger(`Proxy verification failed, try manually?`);
return;
default:
throw new Error(`Verification failed: ${result.result}`);
}
@ -112,13 +112,15 @@ export class ContractVerifier<Chain extends ChainName> extends MultiGeneric<
async verifyContract(
chain: Chain,
input: ContractVerificationInput,
logger = this.logger,
): Promise<void> {
if (input.address === ethers.constants.AddressZero) {
return;
}
logger(`Checking ${input.address} (${input.name})...`);
if (Array.isArray(input.constructorArguments)) {
this.logger('Constructor arguments in legacy format, skipping');
return;
}
const data = {
sourceCode: this.flattenedSource,
@ -142,11 +144,14 @@ export class ContractVerifier<Chain extends ChainName> extends MultiGeneric<
// poll for verified status
if (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)
if (input.isProxy) {
this.logger('Skipping proxy verification');
return;
const proxyGuid = await this.submitForm(
chain,
ExplorerApiActions.MARK_PROXY,
@ -159,8 +164,10 @@ export class ContractVerifier<Chain extends ChainName> extends MultiGeneric<
await this.submitForm(chain, ExplorerApiActions.CHECK_PROXY_STATUS, {
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 {
chainConnectionConfigs,

@ -1,4 +1,4 @@
import { AllChains } from '../consts/chains';
import { AllChains, AllDeprecatedChains } from '../consts/chains';
import { ChainMap, ChainName, Remotes } from '../types';
export class MultiGeneric<Chain extends ChainName, Value> {
@ -40,7 +40,9 @@ export class MultiGeneric<Chain extends ChainName, Value> {
}
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 {

Loading…
Cancel
Save