Skip to content
This repository has been archived by the owner on Oct 23, 2020. It is now read-only.

Plugin with task for verifying contracts on etherscan #2

Merged
merged 31 commits into from
Apr 6, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
a2ae651
Initial version
alcuadrado Feb 3, 2019
256bf99
Add recommended README structure
fzeoli Feb 7, 2019
a1cfe9b
Update buidler version
alcuadrado Feb 7, 2019
1f50a98
Update Buidler version
alcuadrado Feb 8, 2019
ddaf11b
Update Buidler version
alcuadrado Mar 13, 2019
a91c6fc
fix test
mpetrunic Mar 18, 2019
14437d9
ignore IDE settings files
mpetrunic Mar 18, 2019
cbcc4fc
add etherscan configuration
mpetrunic Mar 18, 2019
405044f
get flatten contract source
mpetrunic Mar 18, 2019
96eb7ce
verification params
mpetrunic Mar 18, 2019
79aa06b
convert solv short version to full version
mpetrunic Mar 19, 2019
4744eed
etherscan service
mpetrunic Mar 19, 2019
95f7f1b
removes ganache because not used
mpetrunic Mar 19, 2019
d81a8c8
reorg tests
mpetrunic Mar 19, 2019
34c943a
working etherscan verification submission
mpetrunic Mar 19, 2019
afb9a53
add verification status checking
mpetrunic Mar 19, 2019
cdada15
lint fixes
mpetrunic Mar 19, 2019
ee17270
implements checking for contract name
mpetrunic Mar 21, 2019
126bd37
constructor argument encoding
mpetrunic Mar 21, 2019
da28676
lint fixes
mpetrunic Mar 21, 2019
515bdbd
adds tests for abi encoder
mpetrunic Mar 22, 2019
1910979
contract compiler tests
mpetrunic Mar 22, 2019
a821247
SolcVersions unit tests
mpetrunic Mar 22, 2019
9562c7e
add integration test for solc versions
mpetrunic Mar 22, 2019
2b4b16f
lint fixes
mpetrunic Mar 22, 2019
5b163d8
add integration tests
mpetrunic Mar 25, 2019
7f7bc2b
updates readme
mpetrunic Mar 25, 2019
f1c0b77
lint fixes
mpetrunic Mar 25, 2019
5e2020c
address PR comments
mpetrunic Mar 26, 2019
aee96d3
lint fixes
mpetrunic Mar 26, 2019
147b68d
fix end config class redeclaration
mpetrunic Mar 26, 2019
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .env
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
TS_NODE_FILES=true

WALLET_PRIVATE_KEY=0xB60B1D45848F32C21EB483EE1F83EFCBE33B896E3F16044821A0DB4BF209FA8B

ETHERSCAN_API_KEY=QJBGHU5IH52MIB2HNYP51ZWIBFR6MYGNY6
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
/dist
/test/buidler-project/cache/
# Logs
logs
*.log
Expand Down Expand Up @@ -59,3 +61,7 @@ typings/

# next.js build output
.next

#IDE settings
.idea/
sample
21 changes: 21 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
language: node_js

node_js:
- "8"
- "10"

install:
- yarn global add codecov
- yarn
- yarn add --peer @nomiclabs/buidler@^1.0.0-beta.2

script:
- yarn test:unit
- codecov
- yarn lint

cache: yarn

branches:
only:
- master
40 changes: 40 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,42 @@
# buidler-etherscan
Buidler plugin for verifying contracts on Etherscan

## Installation

We recommend developing Buidler plugins using yarn. To start working on your project, just run

- `yarn`
- `yarn add --peer @nomiclabs/buidler@^1.0.0-beta.2`

## Updating Buidler or other peer dependencies

When updating/adding Buidler or other peer dependencies, you should update the `.travis.yml` file's install section. The right version of all of them has to be installed in a single line, after `yarn`.

## Testing

Running `yarn test` will run every test located in the `test/` folder. They use [mocha](https://mochajs.org) and [chai](https://www.chaijs.com/), but you can customize them.

You can run only integration tests with `yarn run test:integration` and unit with `yarn run test:unit`.

For integration tests, it is required to set env (.env file) variable `WALLET_PRIVATE_KEY` with ropsten wallet private key.

We recommend creating unit tests for your own modules, and integration tests for the interaction of the plugin with Buidler and its dependencies.

## Linting and autoformat

All all of Buidler projects use [prettier](https://prettier.io/) and [tslint](https://palantir.github.io/tslint/).

You can check if your code style is correct by running `yarn lint`, and fix it with `yarn lint:fix`.

## Building the project

Just run `yarn buidl` ️👷‍

## README file

We recommend writing a README that contains the following information:

* What is it
* How to install it
* New tasks
* Environment extensions
62 changes: 62 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
{
"name": "@nomiclabs/buidler-etherscan",
"version": "0.0.1",
"description": "Buidler TypeScript plugin for verifying contracts on etherscan",
"repository": "github:nomiclabs/buidler-etherscan",
"contributors": [
"Nomic Labs SRL",
"Marin Petrunić <[email protected]>"
],
"license": "MIT",
"main": "dist/src/index.js",
"types": "dist/src/index.d.ts",
"scripts": {
"lint:fix": "prettier --write 'src/**/*.{js,ts}' 'test/**/*.{ts}' && tslint --fix --config tslint.json --project tsconfig.json",
"lint": "tslint --config tslint.json --project tsconfig.json",
"test": "yarn run test:unit && yarn run test:integration",
"test:unit": "mocha --recursive \"test/unit/**/*.ts\"",
"test:integration": "mocha --recursive \"test/integration/**/*.ts\"",
"build": "tsc",
"buidl": "tsc",
"watch": "tsc -w"
},
"files": [
"dist/src/",
"src/",
"LICENSE",
"README.md"
],
"devDependencies": {
"@types/chai": "^4.1.7",
"@types/download": "^6.2.4",
"@types/ethereumjs-abi": "^0.6.3",
"@types/find-up": "^2.1.1",
"@types/fs-extra": "^5.0.4",
"@types/mocha": "^5.2.5",
"@types/nock": "^9.3.1",
"@types/node": "^8.10.38",
"@types/request": "^2.48.1",
"@types/request-promise": "^4.1.42",
"chai": "^4.2.0",
"dotenv": "^6.2.0",
"ethers": "^4.0.27",
"mocha": "^5.2.0",
"nock": "^10.0.6",
"prettier": "^1.15.3",
"solc": "^0.5.6",
"source-map-support": "^0.5.9",
"ts-node": "^8.0.2",
"tslint": "^5.12.0",
"tslint-config-prettier": "^1.17.0",
"tslint-plugin-prettier": "^2.0.1",
"typescript": "^3.2.2"
},
"peerDependencies": {
"@nomiclabs/buidler": "1.0.0-beta.2"
},
"dependencies": {
"ethereumjs-abi": "^0.6.7",
"request": "^2.88.0",
"request-promise": "^4.2.4"
}
}
26 changes: 26 additions & 0 deletions src/AbiEncoder.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { BuidlerPluginError } from "@nomiclabs/buidler/plugins";
import abi from "ethereumjs-abi";

export default class AbiEncoder {
public static encodeConstructor(
contractAbi: any[],
constructorArguments: string[]
): string {
let constructorAbis: any[] = contractAbi.filter(
value => value.type === "constructor"
);
if (constructorAbis.length === 0) {
return "";
}
constructorAbis = constructorAbis.filter(
value => value.inputs.length === constructorArguments.length
);
if (constructorAbis.length === 0) {
throw new BuidlerPluginError("Invalid number of constructor arguments");
}
const types = constructorAbis[0].inputs.map(
(value: { type: string }) => value.type
);
return abi.rawEncode(types, constructorArguments).toString("hex");
}
}
56 changes: 56 additions & 0 deletions src/ContractCompiler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { TASK_COMPILE_RUN_COMPILER } from "@nomiclabs/buidler/builtin-tasks/task-names";
import { BuidlerPluginError } from "@nomiclabs/buidler/plugins";
import { RunTaskFunction } from "@nomiclabs/buidler/types";

export default class ContractCompiler {
constructor(private readonly runTask: RunTaskFunction) {}

public async getAbi(source: string, contractName: string): Promise<any> {
return (await this.compile(source, contractName)).abi;
}

public async compile(
source: string,
contractName: string
): Promise<{ abi: any[]; bytecode: string }> {
const compilationResult = await this.runTask(TASK_COMPILE_RUN_COMPILER, {
input: this.getSolcInput(source)
});
if (compilationResult.errors) {
throw new BuidlerPluginError(
"Failed to compile: " + JSON.stringify(compilationResult.errors)
);
}
const contracts: any = compilationResult.contracts.contracts;
for (const contract in contracts) {
if (contracts.hasOwnProperty(contract) && contract === contractName) {
return {
abi: contracts[contract].abi,
bytecode: contracts[contract].evm.bytecode.object
};
}
}
throw new BuidlerPluginError(
`Given contract name (${contractName}) doesn't exist in sources`
);
}

private getSolcInput(source: string) {
return {
language: "Solidity",
sources: {
contracts: {
content: source
}
},

settings: {
outputSelection: {
"*": {
"*": ["*"]
}
}
}
};
}
}
18 changes: 18 additions & 0 deletions src/etherscan/EtherscanResponse.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
export default class EtherscanResponse {
public readonly status: number;

public readonly message: string;

public constructor(response: any) {
mpetrunic marked this conversation as resolved.
Show resolved Hide resolved
this.status = parseInt(response.status, 10);
this.message = response.result;
}

public isPending() {
return this.message === "Pending in queue";
}

public isOk() {
return this.status === 1;
}
}
62 changes: 62 additions & 0 deletions src/etherscan/EtherscanService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { BuidlerPluginError } from "@nomiclabs/buidler/plugins";
import request from "request-promise";

import EtherscanResponse from "./EtherscanResponse";
import EtherscanVerifyContractRequest from "./EtherscanVerifyContractRequest";

export default class EtherscanService {
constructor(private readonly url: string) {}

public async verifyContract(
req: EtherscanVerifyContractRequest
): Promise<EtherscanResponse> {
try {
const response = new EtherscanResponse(
await request.post(this.url, { form: req, json: true })
);

if (!response.isOk()) {
throw new BuidlerPluginError(response.message);
}
return response;
} catch (e) {
throw new BuidlerPluginError(
"Failed to send contract verification request. Reason: " + e.message,
e
);
}
}

public async getVerificationStatus(guid: string): Promise<EtherscanResponse> {
try {
const response = new EtherscanResponse(
await request.get(this.url, {
json: true,
qs: {
module: "contract",
action: "checkverifystatus",
guid
}
})
);
if (response.isPending()) {
await this.delay(2000);
return this.getVerificationStatus(guid);
}
if (!response.isOk()) {
throw new BuidlerPluginError(response.message);
}
return response;
} catch (e) {
throw new BuidlerPluginError(
"Failed to verify contract. Reason: " + e.message
);
}
}

private delay(ms: number): Promise<void> {
return new Promise(function(resolve) {
setTimeout(resolve, ms);
});
}
}
67 changes: 67 additions & 0 deletions src/etherscan/EtherscanVerifyContractRequest.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { BuidlerPluginError } from "@nomiclabs/buidler/plugins";
import { SolcConfig } from "@nomiclabs/buidler/types";

import { EtherscanBuidlerEnvironment } from "../index";

export default class EtherscanVerifyContractRequest {
// for that weird etherscan library props
[key: string]: any;
public readonly apikey: string;
public readonly module: string = "contract";
public readonly action: string = "verifysourcecode";
public readonly contractaddress: string;
public readonly sourceCode: string;
public readonly contractname: string;
public readonly compilerversion: string;
public readonly optimizationUsed: number;
public readonly runs: number;
public readonly constructorArguements: string;

constructor(
etherscanConfig: EtherscanBuidlerEnvironment,
solcConfig: SolcConfig,
contractName: string,
address: string,
libraries: string,
source: string,
constructorArguments: string
) {
this.apikey = etherscanConfig.apiKey;
this.contractaddress = address;
this.sourceCode = source;
this.contractname = contractName;
this.compilerversion = solcConfig.fullVersion;
this.optimizationUsed = solcConfig.optimizer.enabled ? 1 : 0;
this.runs = solcConfig.optimizer.runs;
this.constructorArguements = constructorArguments;
this.setLibraries(libraries);
}

public serialize(): string {
return JSON.stringify(this);
}

private setLibraries(libraries: string) {
let i: number = 1;
let parsedLibraries: { [key: string]: string } = {};
try {
if (libraries) {
parsedLibraries = JSON.parse(libraries);
}
} catch (e) {
throw new BuidlerPluginError(
"Failed to parse libraries. Reason: " + e.message
);
}
for (const libraryName in parsedLibraries) {
if (parsedLibraries.hasOwnProperty(libraryName)) {
this["libraryname" + i] = libraryName;
this["libraryaddress" + i] = parsedLibraries[libraryName];
i++;
if (i >= 10) {
mpetrunic marked this conversation as resolved.
Show resolved Hide resolved
break;
}
}
}
}
}
Loading