Skip to content

Commit

Permalink
🗞️ Add verify-contract package (#390)
Browse files Browse the repository at this point in the history
  • Loading branch information
janjakubnanista authored Feb 12, 2024
1 parent 3fb1788 commit 1ea1aae
Show file tree
Hide file tree
Showing 40 changed files with 13,316 additions and 2 deletions.
3 changes: 3 additions & 0 deletions packages/verify-contract/.eslintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.turbo
dist
node_modules
3 changes: 3 additions & 0 deletions packages/verify-contract/.eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"extends": "../../.eslintrc.json"
}
144 changes: 144 additions & 0 deletions packages/verify-contract/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
<p align="center">
<a href="https://layerzero.network">
<img alt="LayerZero" style="max-width: 500px" src="https://d3a2dpnnrypp5h.cloudfront.net/bridge-app/lz.png"/>
</a>
</p>

<h1 align="center">@layerzerolabs/verify-contract</h1>

<!-- The badges section -->
<p align="center">
<!-- Shields.io NPM published package version -->
<a href="https://www.npmjs.com/package/@layerzerolabs/verify-contract"><img alt="NPM Version" src="https://img.shields.io/npm/v/@layerzerolabs/verify-contract"/></a>
<!-- Shields.io NPM downloads -->
<a href="https://www.npmjs.com/package/@layerzerolabs/verify-contract"><img alt="Downloads" src="https://img.shields.io/npm/dm/@layerzerolabs/verify-contract"/></a>
<!-- Shields.io license badge -->
<a href="https://www.npmjs.com/package/@layerzerolabs/verify-contract"><img alt="NPM License" src="https://img.shields.io/npm/l/@layerzerolabs/verify-contract"/></a>
</p>

## Installation

```bash
yarn add @layerzerolabs/verify-contract

pnpm add @layerzerolabs/verify-contract

npm install @layerzerolabs/verify-contract
```

## Usage

### CLI

This package comes with a CLI interface:

```bash
npx @layerzerolabs/verify-contract --help
```

Using the CLI, contracts can be verified one network at a time.

### Programatic usage

The package provides two types of verification for hardhat deploy: _target_ and _non-target_.

#### Target verification

This is suitable for verifying contracts that have been the compilation targets for a deployment, i.e. they have their own deployment file.
This is the default and easiest case for which we know all the information we need from the deployment file.

```typescript
import { verifyHardhatDeployTarget } from "@layerzerolabs/verify-contract";

// Programmatic usage allows for more fine-grained and multi-network verification
verifyHardhatDeployTarget({
paths: {
deployments: "./my/little/deployments/folder",
},
networks: {
whatachain: {
apiUrl: "https://api.whatachain.io/api",
apiKey: "david.hasselhoff.1234",
},
},
// The filter option allows you to limit the scope of verification to
// specific contracts
//
// It supports several ways of scoping the verification:
//
// A list of case-sensitive contract names
filter: ["Factory", "Router"],
// A single contract name
filter: "ONFT1155",
// Boolean to toggle the verification as a whole
filter: false,
// A function that gets passed the contract name and an relative contract path and returns a boolean to signify the contract needs to be verified
filter: (name, path) => name.startsWith("Potato721"),
});
```

#### Non-target verification

This is suitable for verifying contracts that have been e.g. deployed dynamically from other contracts within the deployment.

In this case we need to know more information - the specific deployment file to use, the address of the contract and also its constructor arguments.

```typescript
import { verifyHardhatDeployNonTarget } from "@layerzerolabs/verify-contract";

// Programmatic usage allows for more fine-grained and multi-network verification
verifyHardhatDeployNonTarget({
paths: {
deployments: "./my/little/deployments/folder",
},
networks: {
whatachain: {
apiUrl: "https://api.whatachain.io/api",
apiKey: "david.hasselhoff.1234",
},
},
// The contracts array is used to pass the contract details
contracts: [
{
address: "0x0",
network: "whatachain",
// We'll need to pass the name of the deployment file to use (relative to the deployments path)
deployment: "OtherContract.json",
constructorArguments: [1000, "0x0"],
// In this case we'll need to pass a fully-qualified contract name
contractName: "contracts/examples/Pool.sol",
},
],
});
```

### Default configuration

The package is preconfigured for scan API URLs for several well-known networks:

| Network | API URL |
| -------------------------------------------------------------- | ------------------------------------------------ |
| `avalanche`, `avalanche-mainnet` | `https://api.snowtrace.io/api` |
| `fuji`, `avalanche-testnet` | `https://api-testnet.snowtrace.io/api` |
| `bsc` | `https://api.bscscan.com/api` |
| `bsc-testnet` | `https://api-testnet.bscscan.com/api` |
| `ethereum` | `https://api.etherscan.io/api` |
| `ethereum-goerli` | `https://api-goerli.etherscan.io/api` |
| `goerli` | `https://api-goerli.etherscan.io/api` |
| `fantom` | `https://api.ftmscan.com/api` |
| `fantom-testnet` | `https://api-testnet.ftmscan.com/api` |
| `arbitrum` | `https://api.arbiscan.io/api` |
| `arbitrum-goerli` | `https://api-goerli.arbiscan.io/api` |
| `polygon` | `https://api.polygonscan.com/api` |
| `mumbai` | `https://api-testnet.polygonscan.com/api` |
| `optimism` | `https://api-optimistic.etherscan.io/api` |
| `optimism-goerli` | `https://api-goerli-optimistic.etherscan.io/api` |
| `gnosis` | `https://api.gnosisscan.io/api` |
| `zkpolygon`, `zkpolygon-mainnet` | `https://api-zkevm.polygonscan.com/api` |
| `base`, `base-mainnet` | `https://api.basescan.org/api` |
| `base-goerli` | `https://api-goerli.basescan.org/api` |
| `zkconsensys`, `zkconsensys-mainnet`, `linea`, `linea-mainnet` | `https://api.lineascan.build/api` |
| `moonbeam` | `https://api-moonbeam.moonscan.io/api` |
| `moonbeam-testnet` | `https://api-moonbase.moonscan.io/api` |
| `kava`, `kava-mainnet` | `https://kavascan.com/api` |
| `kava-testnet` | `https://testnet.kavascan.com/api` |
3 changes: 3 additions & 0 deletions packages/verify-contract/cli.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#!/usr/bin/env node

import('./dist/cli.js');
11 changes: 11 additions & 0 deletions packages/verify-contract/jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/** @type {import('ts-jest').JestConfigWithTsJest} */
module.exports = {
cache: false,
testEnvironment: 'node',
moduleNameMapper: {
'^@/(.*)$': '<rootDir>/src/$1',
},
transform: {
'^.+\\.(t|j)sx?$': '@swc/jest',
},
};
48 changes: 48 additions & 0 deletions packages/verify-contract/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
{
"name": "@layerzerolabs/verify-contract",
"version": "1.1.9",
"description": "Verify Solidity contracts on supported block explorers",
"bugs": {
"url": "https://github.com/LayerZero-Labs/devtools/issues"
},
"repository": {
"type": "git",
"url": "git+https://github.com/LayerZero-Labs/devtools.git",
"directory": "packages/verify-contract"
},
"license": "MIT",
"main": "./dist/index.js",
"module": "./dist/index.mjs",
"types": "./dist.index.d.ts",
"bin": "./cli.js",
"files": [
"./dist/*",
"./cli.js"
],
"scripts": {
"build": "$npm_execpath tsup",
"clean": "rm -rf dist",
"dev": "$npm_execpath tsup --watch",
"lint": "$npm_execpath eslint '**/*.{js,ts,json}'",
"lint:fix": "eslint --fix '**/*.{js,ts,json}'",
"start": "node ./cli.js",
"test": "jest"
},
"devDependencies": {
"@ethersproject/abi": "^5.7.0",
"@layerzerolabs/io-devtools": "~0.1.3",
"@solidity-parser/parser": "^0.16.1",
"@swc/core": "^1.4.0",
"@swc/jest": "^0.2.36",
"@types/jest": "^29.5.12",
"@types/node": "^18.18.14",
"chalk": "^4.1.2",
"commander": "^11.1.0",
"esbuild-plugin-copy": "~2.1.1",
"got": "12.6.1",
"jest": "^29.7.0",
"tsup": "^8.0.1",
"typescript": "^5.3.3",
"zod": "^3.22.4"
}
}
132 changes: 132 additions & 0 deletions packages/verify-contract/src/cli.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
import { Command, InvalidOptionArgumentError, Option } from 'commander'
import { verifyTarget, verifyNonTarget } from './hardhat-deploy/verify'
import { COLORS } from './common/logger'
import { createLogger } from '@layerzerolabs/io-devtools'
import { VerifyHardhatNonTargetConfig, type VerifyHardhatTargetConfig } from './hardhat-deploy/types'
import { version } from '../package.json'

const logLevelOption = new Option('-l,--log-level <level>', 'Log level')
.choices(['error', 'warn', 'info', 'http', 'verbose', 'debug', 'silly'])
.default('info')

const deploymentsPathOption = new Option('-d,--deployments <path>', 'Path to the deployments folder')

const dryRunOption = new Option(
'--dry-run',
'Do not verify anything, just output the verifications that would be performed'
)

const networkOption = new Option('-n,--network <network name>', 'Network to verify').makeOptionMandatory()

const apiUrlOption = new Option('-u,--api-url <url>', 'Scan API URL (fully qualified, with protocol and path)')

const apiKeyOption = new Option('-k,--api-key <key>', 'Scan API Key')

const verifyNonTargetCommand = new Command('non-target')
.description(
'Verifies a contract that does not have its own deployment file, i.e. has not been a target of a deployment'
)
.addOption(logLevelOption)
.addOption(dryRunOption)
.addOption(deploymentsPathOption)
.addOption(networkOption)
.addOption(apiUrlOption)
.addOption(apiKeyOption)
.requiredOption('--address <address>', 'Contract address to verify')
.requiredOption('--name <contract name>', 'Fully qualified contract name to verify, e.g. contracts/MyToken.sol')
.requiredOption('--deployment <deployment file name>', 'Deployment file name, e.g. MyOtherToken.json')
.option(
'--arguments <constructor arguments>',
'JSON encoded array of constructor arguments, e.g. [1234, "0x0"]',
(value: string) => {
try {
const decoded = JSON.parse(value)
if (!Array.isArray(decoded)) {
throw new Error(`Constructor arguments must be an array, got ${decoded}`)
}

return decoded
} catch (error) {
throw new InvalidOptionArgumentError(`Malformed constructor arguments specified: ${error}`)
}
}
)
.action(async (args: any) => {

Check warning on line 54 in packages/verify-contract/src/cli.ts

View workflow job for this annotation

GitHub Actions / Check code / Build & Lint

Unexpected any. Specify a different type
const logger = createLogger(args.logLevel)
const config: VerifyHardhatNonTargetConfig = {
dryRun: args.dryRun,
paths: {
deployments: args.deployments,
},
networks: {
[args.network]: {
apiUrl: args.apiUrl,
apiKey: args.apiKey,
},
},
contracts: [
{
network: args.network,
address: args.address,
contractName: args.name,
deployment: args.deployment,
constructorArguments: args.arguments,
},
],
}

try {
await verifyNonTarget(config, logger)
} catch (error) {
logger.error(COLORS.error`The verification script exited with an error: ${error}`)

process.exit(1)
}
})

const verifyTargetCommand = new Command('target')
.description('Verifies contracts that have been a part of a deployment, i.e. have their own deployment files')
.addOption(logLevelOption)
.addOption(dryRunOption)
.addOption(deploymentsPathOption)
.addOption(networkOption)
.addOption(apiUrlOption)
.addOption(apiKeyOption)
.option(
'-c,--contracts <contract names>',
'Comma-separated list of case-sensitive contract names to verify',
(value: string | null | undefined) => {
return value?.trim() ? value.split(',').map((c: string) => c.trim()) : undefined
}
)
.action(async (args: any) => {

Check warning on line 102 in packages/verify-contract/src/cli.ts

View workflow job for this annotation

GitHub Actions / Check code / Build & Lint

Unexpected any. Specify a different type
const logger = createLogger(args.logLevel)
const config: VerifyHardhatTargetConfig = {
dryRun: args.dryRun,
paths: {
deployments: args.deployments,
},
networks: {
[args.network]: {
apiUrl: args.apiUrl,
apiKey: args.apiKey,
},
},
filter: args.contracts,
}

try {
await verifyTarget(config, logger)
} catch (error) {
logger.error(COLORS.error`The verification script exited with an error: ${error}`)

process.exit(1)
}
})

new Command('@layerzerolabs/verify-contract')
.version(version)
.description('Verify a set of contracts based on hardhat-deploy outputs')
.addCommand(verifyNonTargetCommand)
.addCommand(verifyTargetCommand, { isDefault: true })
.parseAsync()
Loading

0 comments on commit 1ea1aae

Please sign in to comment.