forked from compound-finance/etherscan
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathimport.ts
145 lines (128 loc) · 4.18 KB
/
import.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
import * as util from 'util';
import * as fs from 'fs';
import * as path from 'path';
import WebUtils from 'web3-utils';
import {
Result,
get,
getEtherscanApiUrl,
getEtherscanUrl
} from './api';
import { BuildFile, Metadata } from './contract';
interface EtherscanSource {
SourceCode: string,
ABI: string,
ContractName: string,
CompilerVersion: string,
OptimizationUsed: string,
Runs: string,
ConstructorArguments: string,
Library: string,
LicenseType: string,
SwarmSource: string
}
export async function getEtherscanApiData(network: string, address: string, apikey: string) {
let apiUrl = await getEtherscanApiUrl(network);
let result: Result = <Result>await get(apiUrl, { module: 'contract', action: 'getsourcecode', address, apikey });
if (result.status !== '1') {
throw new Error(`Etherscan Error: ${result.message} ${result.result}`);
}
let s = <EtherscanSource><unknown>result.result[0];
if (s.ABI === "Contract source code not verified") {
throw new Error("Contract source code not verified");
}
return {
source: s.SourceCode,
abi: JSON.parse(s.ABI),
contract: s.ContractName,
compiler: s.CompilerVersion,
optimized: s.OptimizationUsed !== '0',
optimzationRuns: Number(s.Runs),
constructorArgs: s.ConstructorArguments
};
}
async function getContractCreationCode(network: string, address: string, constructorArgs: string) {
let url = `${await getEtherscanUrl(network)}/address/${address}#code`;
let result = <string>await get(url, {}, null);
let verifiedBytecodeRegex = /<div id='verifiedbytecode2'>[\s\r\n]*([0-9a-fA-F]*)[\s\r\n]*<\/div>/g;
let verifiedByteCodeMatches = [...result.matchAll(verifiedBytecodeRegex)];
if (verifiedByteCodeMatches.length === 0) {
throw new Error('Failed to pull deployed contract code from Etherscan');
}
let verifiedBytecode = verifiedByteCodeMatches[0][1];
if (!verifiedBytecode.toLowerCase().endsWith(constructorArgs.toLowerCase())) {
throw new Error("Expected verified bytecode to end with constructor args, but did not: ${JSON.stringify({verifiedBytecode, constructorArgs})}");
}
return verifiedBytecode.slice(0, verifiedBytecode.length - constructorArgs.length);
}
export async function importContract(network: string, address: string | string[], outfile: string, opts_={}) {
let opts = {
apikey: "",
verbose: 0,
...opts_
};
let addresses = Array.isArray(address) ? address : [address];
// Okay, this is where the fun begins, let's gather as much information as we can
let contractBuild = await addresses.reduce(async (acc_, address) => {
let acc = await acc_;
let {
source,
abi,
contract,
compiler,
optimized,
optimzationRuns,
constructorArgs
} = await getEtherscanApiData(network, address, opts.apikey);
let contractCreationCode = await getContractCreationCode(network, address, constructorArgs);
let contractSource = `contracts/${contract}.sol:${contract}`;
let metadata: Metadata = {
version: "1",
language: "Solidity",
compiler: {
version: compiler
},
sources: {
[contractSource]: {
content: source,
keccak256: WebUtils.keccak256(source)
}
},
settings: {
remappings: [],
optimizer: {
enabled: optimized,
runs: optimzationRuns
}, // TODO: Add optimizer `details` section
metadata: {
useLiteralContent: false
},
compilationTarget: {
[contractSource]: contract
},
libraries: {}
},
output: {
abi,
userdoc: [],
devdoc: []
}
};
if (acc.version && compiler !== acc.version) {
console.warn(`WARN: Contracts differ in compiler version ${acc.version} vs ${compiler}. This makes the build file lightly invalid.`);
}
return {
...acc,
contracts: {
...acc.contracts,
[contractSource]: {
abi: abi,
bin: contractCreationCode,
metadata
}
},
version: compiler
};
}, Promise.resolve(<BuildFile>{contracts: {}}));
await util.promisify(fs.writeFile)(outfile, JSON.stringify(contractBuild, null, 2));
}