Skip to content

Commit

Permalink
Add verify command
Browse files Browse the repository at this point in the history
  • Loading branch information
k1rill-fedoseev committed Jun 4, 2021
1 parent d5bf6f6 commit b2ce7c1
Show file tree
Hide file tree
Showing 11 changed files with 361 additions and 52 deletions.
4 changes: 4 additions & 0 deletions .eslintrc
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
{
"rules": {
"no-constant-condition": "off",
"no-await-in-loop": "off"
},
"extends": [
"oclif",
"oclif-typescript"
Expand Down
46 changes: 26 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,43 +28,49 @@ USAGE
<!-- usagestop -->
# Commands
<!-- commands -->
* [`sourcify-to-etherscan hello [FILE]`](#sourcify-to-etherscan-hello-file)
* [`sourcify-to-etherscan help [COMMAND]`](#sourcify-to-etherscan-help-command)
* [`sourcify-to-etherscan verify CONTRACT`](#sourcify-to-etherscan-verify-contract)

## `sourcify-to-etherscan hello [FILE]`
## `sourcify-to-etherscan help [COMMAND]`

describe the command here
display help for sourcify-to-etherscan

```
USAGE
$ sourcify-to-etherscan hello [FILE]
$ sourcify-to-etherscan help [COMMAND]
OPTIONS
-f, --force
-h, --help show CLI help
-n, --name=name name to print
ARGUMENTS
COMMAND command to show help for
EXAMPLE
$ sourcify-to-etherscan hello
hello world from ./src/hello.ts!
OPTIONS
--all see all commands in CLI
```

_See code: [src/commands/hello.ts](https://github.com/k1rill-fedoseev/sourcify-to-etherscan/blob/v0.0.1/src/commands/hello.ts)_
_See code: [@oclif/plugin-help](https://github.com/oclif/plugin-help/blob/v3.2.2/src/commands/help.ts)_

## `sourcify-to-etherscan help [COMMAND]`
## `sourcify-to-etherscan verify CONTRACT`

display help for sourcify-to-etherscan
check contract verification status in Sourcify and Etherscan

```
USAGE
$ sourcify-to-etherscan help [COMMAND]
ARGUMENTS
COMMAND command to show help for
$ sourcify-to-etherscan verify CONTRACT
OPTIONS
--all see all commands in CLI
-a, --args=args abi-encoded constructor
arguments
-h, --help show CLI help
-k, --apikey=apikey etherscan api key
-n, --network=(mainnet|ropsten|rinkeby|goerli|kovan|bsc|bsc_testnet|1|3|4|5|42|56|97) [default: mainnet] network name
or chain id to use
EXAMPLES
$ sourcify-to-etherscan verify --apikey <...> --network rinkeby 0x94263a20b1Eea751d6C3B207A7A0ba8fF8Db9E90
$ sourcify-to-etherscan verify -k <...> -n 4 -a <...> 0x94263a20b1Eea751d6C3B207A7A0ba8fF8Db9E90
```

_See code: [@oclif/plugin-help](https://github.com/oclif/plugin-help/blob/v3.2.2/src/commands/help.ts)_
_See code: [src/commands/verify.ts](https://github.com/k1rill-fedoseev/sourcify-to-etherscan/blob/v0.0.1/src/commands/verify.ts)_
<!-- commandsstop -->
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"@oclif/command": "^1",
"@oclif/config": "^1",
"@oclif/plugin-help": "^3",
"axios": "^0.21.1",
"tslib": "^1"
},
"devDependencies": {
Expand All @@ -24,7 +25,7 @@
"typescript": "^3.3"
},
"engines": {
"node": ">=8.0.0"
"node": ">=10.0.0"
},
"files": [
"/bin",
Expand Down
31 changes: 0 additions & 31 deletions src/commands/hello.ts

This file was deleted.

81 changes: 81 additions & 0 deletions src/commands/verify.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import {Command, flags} from '@oclif/command'
import {Network, NETWORKS, VerificationStatus} from '../constants'
import * as sourcify from '../sourcify'
import * as etherscan from '../etherscan'
import {makeStandardJson} from '../utils'

export default class Verify extends Command {
static description = 'check contract verification status in Sourcify and Etherscan'

static examples = [
'$ sourcify-to-etherscan verify --apikey <...> --network rinkeby 0x94263a20b1Eea751d6C3B207A7A0ba8fF8Db9E90',
'$ sourcify-to-etherscan verify -k <...> -n 4 -a <...> 0x94263a20b1Eea751d6C3B207A7A0ba8fF8Db9E90',
]

static flags = {
help: flags.help({char: 'h'}),
network: flags.enum({
char: 'n',
description: 'network name or chain id to use',
options: [...NETWORKS.map(n => n.name), ...NETWORKS.map(n => n.chainId.toString())],
default: 'mainnet',
}),
args: flags.string({char: 'a', description: 'abi-encoded constructor arguments'}),
apikey: flags.string({char: 'k', description: 'etherscan api key', env: 'ETHERSCAN_API_KEY'}),
}

static args = [{name: 'contract', required: true}]

async run() {
const {args, flags} = this.parse(Verify)

const network: Network = NETWORKS.find(n => n.name === flags.network || n.chainId.toString() === flags.network)!

this.log('Checking verification status on Sourcify')

const status = await sourcify.check(network.chainId, args.contract)

if (status !== 'perfect') {
this.error(`contract ${args.contract} is not yet verified on Sourcify for ${network.name}`)
}

this.log('Fetching source code and metadata files from Sourcify')

const files = await sourcify.files(network.chainId, args.contract)

this.log('Parsing source files')

const {target, version, metadata, sources, constructorArgs} = sourcify.parseFiles(files)

this.log('Constructing standard JSON input from obtained source files')

const standardJson = makeStandardJson(metadata, sources)

this.log('Submitting standard JSON to etherscan')

const guid = await etherscan.verify({
api: network.etherscanApiURL,
apiKey: flags.apikey,
contract: args.contract,
source: standardJson,
target,
version,
args: flags.args || constructorArgs,
})

if (guid === VerificationStatus.ALREADY_VERIFIED) {
this.log('Contract is already verified on Etherscan')
return
}

this.log(`Waiting for the verification job ${guid} to complete`)

const result = await etherscan.waitFor(network.etherscanApiURL, flags.apikey, guid)

if (result === VerificationStatus.SUCCESS) {
this.log('Successfully verified contract on Etherscan :)')
} else {
this.error('Failed to verify :(')
}
}
}
24 changes: 24 additions & 0 deletions src/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
export enum VerificationStatus {
FAILED = 'Fail - Unable to verify',
SUCCESS = 'Pass - Verified',
PENDING = 'Pending in queue',
ALREADY_VERIFIED = 'Contract source code already verified',
}

export type Network = {
name: string;
chainId: number;
etherscanApiURL: string;
}

export const NETWORKS: Network[] = [
{name: 'mainnet', chainId: 1, etherscanApiURL: 'https://api.etherscan.io/api'},
{name: 'ropsten', chainId: 3, etherscanApiURL: 'https://api-ropsten.etherscan.io/api'},
{name: 'rinkeby', chainId: 4, etherscanApiURL: 'https://api-rinkeby.etherscan.io/api'},
{name: 'goerli', chainId: 5, etherscanApiURL: 'https://api-goerli.etherscan.io/api'},
{name: 'kovan', chainId: 42, etherscanApiURL: 'https://api-kovan.etherscan.io/api'},
{name: 'bsc', chainId: 56, etherscanApiURL: 'https://api.bscscan.com/api'},
{name: 'bsc_testnet', chainId: 97, etherscanApiURL: 'https://api-testnet.bscscan.com/api'},
]

export const SOURCIFY_API = 'https://sourcify.dev/server/'
45 changes: 45 additions & 0 deletions src/etherscan.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import axios from 'axios'
import {VerificationStatus} from './constants'
import {delay} from './utils'

type VerifyParams = {
api: string; apiKey: string | undefined; contract: string; source: any; target: string; version: string; args: string;
}
export async function verify({api, apiKey, contract, source, target, version, args}: VerifyParams): Promise<string> {
const params = new URLSearchParams()
if (apiKey) {
params.append('apikey', apiKey)
}
params.append('module', 'contract')
params.append('action', 'verifysourcecode')
params.append('contractaddress', contract)
params.append('sourceCode', JSON.stringify(source))
params.append('codeformat', 'solidity-standard-json-input')
params.append('contractname', target)
params.append('compilerversion', version)
params.append('constructorArguements', args)

const {data} = await axios.post(api, params, {
headers: {'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8'},
})

return data.result
}

export async function waitFor(api: string, apiKey: string | undefined, guid: string): Promise<string> {
while (true) {
await delay(3000)

const {data} = await axios.get(api, {
params: {
apiKey,
module: 'contract',
action: 'checkverifystatus',
guid,
},
})
if (data.result !== VerificationStatus.PENDING) {
return data.result
}
}
}
63 changes: 63 additions & 0 deletions src/sourcify.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import axios from 'axios'
import {SOURCIFY_API} from './constants'

export async function check(chainId: number, contract: string): Promise<string> {
const {data} = await axios.get(`${SOURCIFY_API}check-by-addresses`, {
params: {
addresses: contract,
chainIds: chainId,
},
})
if (!data || !data[0]) {
return 'false'
}
return data[0].status
}

export async function files(chainId: number, contract: string): Promise<any> {
const {data} = await axios.get(`${SOURCIFY_API}files/${chainId}/${contract}`)

return data
}

export type Metadata = {
language: string;
settings: any;
}
type SourcifyOutput = {
metadata: Metadata;
version: string;
sources: any;
target: string;
constructorArgs: string;
}
type FileContent = {
content: string;
}
type File = FileContent & {
name: string;
path: string;
}

export function parseFiles(files: any): SourcifyOutput {
const metadata = JSON.parse(files.find((file: File) => file.name === 'metadata.json').content)
const constructorArgs = files.find((file: File) => file.name === 'constructor-args.txt')
const sourcesArray = files.filter((file: File) => file.name !== 'metadata.json' && file.name !== 'constructor-args.txt')
const version = `v${metadata.compiler.version}`
const target = Object.entries(metadata.settings.compilationTarget)[0].join(':')
const sources: {[key: string]: FileContent} = {}

const prefix = sourcesArray[0].path.match(/^.*\/sources\//)[0]
for (const file of sourcesArray) {
const path = file.path.replace('sources/_', 'sources/@').slice(prefix.length)
sources[path] = {content: file.content}
}

return {
metadata,
version,
target,
sources,
constructorArgs: constructorArgs ? constructorArgs.content.slice(2) : '',
}
}
16 changes: 16 additions & 0 deletions src/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import {Metadata} from './sourcify'

export const delay = (ms: number) => new Promise(res => setTimeout(res, ms))

export function makeStandardJson(metadata: Metadata, sources: any) {
return {
language: metadata.language,
sources,
settings: {
optimizer: metadata.settings.optimizer,
evmVersion: metadata.settings.evmVersion,
remappings: metadata.settings.remappings,
libraries: metadata.settings.libraries,
},
}
}
Loading

0 comments on commit b2ce7c1

Please sign in to comment.