@layerzerolabs/verify-contract 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/constants/blockExplorerApi.json +27 -0
- package/index.js +185 -0
- package/package.json +17 -0
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"avalanche": "api.snowtrace.io",
|
|
3
|
+
"fuji": "api-testnet.snowtrace.io",
|
|
4
|
+
"bsc": "api.bscscan.com",
|
|
5
|
+
"bsc-testnet": "api-testnet.bscscan.com",
|
|
6
|
+
"ethereum": "api.etherscan.io",
|
|
7
|
+
"ethereum-goerli": "api-goerli.etherscan.io",
|
|
8
|
+
"fantom": "api.ftmscan.com",
|
|
9
|
+
"fantom-testnet": "api-testnet.ftmscan.com",
|
|
10
|
+
"arbitrum": "api.arbiscan.io",
|
|
11
|
+
"arbitrum-goerli": "api-goerli.arbiscan.io",
|
|
12
|
+
"polygon": "api.polygonscan.com",
|
|
13
|
+
"mumbai": "api-testnet.polygonscan.com",
|
|
14
|
+
"optimism": "api-optimistic.etherscan.io",
|
|
15
|
+
"optimism-goerli": "api-goerli-optimistic.etherscan.io",
|
|
16
|
+
|
|
17
|
+
"aptos": "",
|
|
18
|
+
"celo": "",
|
|
19
|
+
"dexalot": "",
|
|
20
|
+
"dfk": "",
|
|
21
|
+
"harmony": "",
|
|
22
|
+
"intain": "",
|
|
23
|
+
"klaytn": "",
|
|
24
|
+
"metis": "",
|
|
25
|
+
"moonbeam": "api-moonbeam.moonscan.io",
|
|
26
|
+
"swimmer": ""
|
|
27
|
+
}
|
package/index.js
ADDED
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
const FileSystem = require("fs");
|
|
2
|
+
const https = require('https');
|
|
3
|
+
const querystring = require('querystring');
|
|
4
|
+
const BLOCK_EXPLORER_API_URL = require("./constants/blockExplorerApi.json")
|
|
5
|
+
|
|
6
|
+
const licenseTypes = {
|
|
7
|
+
"None": 1,
|
|
8
|
+
"Unlicense": 2,
|
|
9
|
+
"MIT": 3,
|
|
10
|
+
"GNU-GPLv2": 4,
|
|
11
|
+
'GNU-GPLv3': 5,
|
|
12
|
+
'GNU-LGPLv2.1': 6,
|
|
13
|
+
'GNU-LGPLv3': 7,
|
|
14
|
+
'BSD-2-Clause': 8,
|
|
15
|
+
"BSD-3-Clause": 9,
|
|
16
|
+
"MPL-2.0": 10,
|
|
17
|
+
"OSL-3.0": 11,
|
|
18
|
+
"Apache-2.0": 12,
|
|
19
|
+
"GNU-AGPLv3": 13,
|
|
20
|
+
"BUSL-1.1": 14
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function getContractInheritance(baseContractString, remainingContracts, finalObj) {
|
|
24
|
+
let contractNamesInherited = baseContractString.match(/(import).*(;)/g)
|
|
25
|
+
// we have reached the end of the inheritance as the base contract contains no more imports
|
|
26
|
+
if (!contractNamesInherited) { return finalObj }
|
|
27
|
+
|
|
28
|
+
// extract import names
|
|
29
|
+
contractNamesInherited = contractNamesInherited.map(
|
|
30
|
+
x => {
|
|
31
|
+
if (x.includes('"')) {
|
|
32
|
+
return (x.split(`"`)[1])
|
|
33
|
+
.split(`/`).pop()
|
|
34
|
+
} else {
|
|
35
|
+
return (x.split(`'`)[1])
|
|
36
|
+
.split(`/`).pop()
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
// there are more parent contracts to check, push them into final object
|
|
42
|
+
let parentContracts = []
|
|
43
|
+
for (const contractName of contractNamesInherited) {
|
|
44
|
+
for (const contract of remainingContracts) {
|
|
45
|
+
if (contract[0].includes("/" + contractName)) {
|
|
46
|
+
parentContracts.push(contract)
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// filter out contracts that we haven't added to the finalObj yet
|
|
52
|
+
let remainingContractsNew = remainingContracts.filter(([k, v]) => !Object.keys(Object.fromEntries(parentContracts)).includes(k))
|
|
53
|
+
|
|
54
|
+
// take existing contracts and the new verified parent contracts and merge into object
|
|
55
|
+
let resp = {...finalObj, ...Object.fromEntries(parentContracts)}
|
|
56
|
+
|
|
57
|
+
// go through each of the parent contracts and get inheritance
|
|
58
|
+
for (const [k, v] of parentContracts) {
|
|
59
|
+
resp = {...getContractInheritance(v["content"], remainingContractsNew, finalObj), ...resp}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return resp
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function formatPutObj(baseContract, contractBuildInfo, contractDeployment, contract, network) {
|
|
66
|
+
let putObj= {
|
|
67
|
+
apikey: process.env[`SCAN_API_KEY_${network}`],
|
|
68
|
+
module: "contract",
|
|
69
|
+
action: "verifysourcecode",
|
|
70
|
+
sourceCode: JSON.stringify(contractBuildInfo["input"]),
|
|
71
|
+
contractaddress: contractDeployment["address"],
|
|
72
|
+
codeformat: "solidity-standard-json-input",
|
|
73
|
+
contractname: `${baseContract[0]}:${contract}`,
|
|
74
|
+
compilerversion: "v" + contractBuildInfo["solcLongVersion"],
|
|
75
|
+
licenseType: licenseTypes["None"] // default to none
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// specify license type if one is found in the base contract
|
|
79
|
+
for (const [license, type] of Object.entries(licenseTypes)) {
|
|
80
|
+
if (baseContract[1]["content"].includes(`SPDX-License-Identifier: ${license}`)) {
|
|
81
|
+
putObj["licenseType"] = type
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (contractBuildInfo["input"]["settings"]["optimizer"]["enabled"]) {
|
|
86
|
+
putObj["optimizationUsed"] = 1
|
|
87
|
+
putObj["runs"] = contractBuildInfo["input"]["settings"]["optimizer"]["runs"]
|
|
88
|
+
} else {
|
|
89
|
+
putObj["optimizationUsed"] = 0
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
let constructorAbiEncoded
|
|
93
|
+
if (baseContract[1]["content"].includes("constructor(")) {
|
|
94
|
+
let constructorTypes = (contractDeployment["abi"].filter(x => x["type"] && x["type"] == "constructor")[0]["inputs"]).map(x => x["type"])
|
|
95
|
+
constructorAbiEncoded = ethers.utils.defaultAbiCoder.encode(constructorTypes, contractDeployment["args"])
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (constructorAbiEncoded) {
|
|
99
|
+
putObj["constructorArguements"] = constructorAbiEncoded.substring(2) // misspelled in etherscans api
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return putObj
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function getBaseAndRemainingContract(contractName, contractBuildInfo) {
|
|
106
|
+
const baseContract = Object.entries(contractBuildInfo["input"]["sources"]).filter(([k, v]) => k.includes(contractName))[0]
|
|
107
|
+
const remainingContracts = Object.entries(contractBuildInfo["input"]["sources"]).filter(([k, v]) => !k.includes(contractName))
|
|
108
|
+
return [baseContract, remainingContracts]
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
module.exports = async function (network, contract) {
|
|
113
|
+
if(!BLOCK_EXPLORER_API_URL[network]) {
|
|
114
|
+
throw `Unsupported block explorer network: ${network}`
|
|
115
|
+
}
|
|
116
|
+
if (!process.env[`SCAN_API_KEY_${network}`]) {
|
|
117
|
+
throw `Missing API key for network: ${network}`
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const contractName = `/${contract}.sol`
|
|
121
|
+
|
|
122
|
+
// get the build files/artifacts
|
|
123
|
+
const contractDeployment = JSON.parse(FileSystem.readFileSync(`./deployments/${network}/${contract}.json`, "utf8"))
|
|
124
|
+
// iterate the build-info to find the correct build file
|
|
125
|
+
let contractBuildInfo
|
|
126
|
+
FileSystem.readdirSync(`./artifacts/build-info/`).forEach(fileName => {
|
|
127
|
+
const f = JSON.parse(FileSystem.readFileSync(`./artifacts/build-info/${fileName}`, "utf8"))
|
|
128
|
+
|
|
129
|
+
let test = Object.entries(f["input"]["sources"]).filter(([k, v]) => k.includes(contractName))
|
|
130
|
+
if (test[0] && test[0][0]) {
|
|
131
|
+
if (test[0][0].includes(contractName)) {
|
|
132
|
+
contractBuildInfo = f
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
})
|
|
136
|
+
if (!contractBuildInfo) throw `Could not find contract: ${contractName} inside of build-info!`
|
|
137
|
+
|
|
138
|
+
console.log(`\n\nVerifying... Network: ${network}, contractName: ${contractName}, address: ${contractDeployment["address"]}`)
|
|
139
|
+
|
|
140
|
+
// parse and filter out the extra build files, because the verifier freaks out if too many contracts to check
|
|
141
|
+
const [baseContract, remainingContracts] = getBaseAndRemainingContract(contractName, contractBuildInfo)
|
|
142
|
+
contractBuildInfo["input"]["sources"] = getContractInheritance(baseContract[1]["content"], remainingContracts, Object.fromEntries([baseContract]))
|
|
143
|
+
|
|
144
|
+
// format the put request
|
|
145
|
+
const putObj = formatPutObj(baseContract, contractBuildInfo, contractDeployment, contract, network)
|
|
146
|
+
|
|
147
|
+
const response = await makeRequest(`${BLOCK_EXPLORER_API_URL[network]}`, putObj)
|
|
148
|
+
console.log(JSON.parse(response))
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
async function makeRequest(url, data) {
|
|
152
|
+
const postData = querystring.stringify(data);
|
|
153
|
+
|
|
154
|
+
const options = {
|
|
155
|
+
hostname: url,
|
|
156
|
+
path: '/api',
|
|
157
|
+
method: 'POST',
|
|
158
|
+
headers: {
|
|
159
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
160
|
+
'Content-Length': Buffer.byteLength(postData)
|
|
161
|
+
}
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
return new Promise((resolve, reject) => {
|
|
165
|
+
const req = https.request(options, (res) => {
|
|
166
|
+
let data = '';
|
|
167
|
+
res.on('data', (chunk) => {
|
|
168
|
+
data += chunk;
|
|
169
|
+
});
|
|
170
|
+
res.on('end', () => {
|
|
171
|
+
resolve(data);
|
|
172
|
+
});
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
req.on('error', (error) => {
|
|
176
|
+
reject(error);
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
req.write(postData);
|
|
180
|
+
req.end();
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
|
package/package.json
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@layerzerolabs/verify-contract",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Verify Solidity contracts on supported block explorers",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"scripts": {},
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/LayerZero-Labs/verify-contract.git"
|
|
10
|
+
},
|
|
11
|
+
"author": "Lz.Warlock",
|
|
12
|
+
"license": "MIT",
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/LayerZero-Labs/verify-contract/issues"
|
|
15
|
+
},
|
|
16
|
+
"homepage": "https://github.com/LayerZero-Labs/verify-contract#readme"
|
|
17
|
+
}
|