Skip to content

Commit 7dd6878

Browse files
Add support for ContractFileName for "Import from Etherscan" (#2312)
* Add support for `ContractFileName` for Vyper "Import from Etherscan" * Make `ContractFileName` optional in EtherscanResult type * Remove solidity parser from lib-sourcify, move it back to server. * Update the etherscanMocks file to include the `ContractFileName` parameter * Remove `getContractPathFromSourcesOrThrow` in lib-sourcify tests * Remove unused "etherscan_missing_contract_definition" error * Fix contractName generation in etherscan vyper jsonInput
1 parent 4ffa14f commit 7dd6878

File tree

13 files changed

+115
-143
lines changed

13 files changed

+115
-143
lines changed

package-lock.json

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/lib-sourcify/package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,6 @@
4444
"@ethersproject/bignumber": "5.8.0",
4545
"@ethersproject/bytes": "5.8.0",
4646
"@fairdatasociety/bmt-js": "2.1.0",
47-
"@solidity-parser/parser": "0.20.2",
4847
"bs58": "5.0.0",
4948
"ethers": "6.15.0",
5049
"jszip": "3.10.1",

packages/lib-sourcify/src/utils/etherscan/EtherscanTypes.ts

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ export type EtherscanImportErrorCode =
99
| 'etherscan_rate_limit'
1010
| 'etherscan_api_error'
1111
| 'etherscan_not_verified'
12-
| 'etherscan_missing_contract_definition'
1312
| 'etherscan_vyper_version_mapping_failed'
1413
| 'etherscan_missing_contract_in_json'
1514
| 'etherscan_missing_vyper_settings';
@@ -35,9 +34,7 @@ export type EtherscanImportErrorParameters =
3534
code: 'etherscan_api_error';
3635
} & Pick<EtherscanImportErrorDataRequired, 'apiErrorMessage'>)
3736
| ({
38-
code:
39-
| 'etherscan_missing_contract_definition'
40-
| 'etherscan_missing_contract_in_json';
37+
code: 'etherscan_missing_contract_in_json';
4138
} & Pick<EtherscanImportErrorDataRequired, 'contractName'>)
4239
| ({
4340
code: 'etherscan_vyper_version_mapping_failed';
@@ -58,8 +55,6 @@ function getErrorMessageFromCode(params: EtherscanImportErrorParameters) {
5855
return `Error in Etherscan API response. Result message: Invalid API Key`;
5956
case 'etherscan_not_verified':
6057
return `This contract is not verified on Etherscan.`;
61-
case 'etherscan_missing_contract_definition':
62-
return `Contract definition for "${params.contractName}" not found in Etherscan response sources.`;
6358
case 'etherscan_vyper_version_mapping_failed':
6459
return `Failed to map Vyper version "${params.compilerVersion}" from Etherscan to valid compiler version.`;
6560
case 'etherscan_missing_contract_in_json':
@@ -86,6 +81,7 @@ export type EtherscanResult = {
8681
SourceCode: string;
8782
ABI: string;
8883
ContractName: string;
84+
ContractFileName?: string;
8985
CompilerVersion: string;
9086
OptimizationUsed: string;
9187
Runs: string;

packages/lib-sourcify/src/utils/etherscan/etherscan-util.ts

Lines changed: 26 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import {
77
SolidityCompilation,
88
Sources,
99
} from '../..';
10-
import { getContractPathFromSources } from '../..';
1110
import { logInfo, logDebug, logWarn, logError } from '../../logger';
1211
import {
1312
EtherscanImportError,
@@ -138,20 +137,6 @@ export const getSolcJsonInputFromEtherscanResult = (
138137
};
139138
};
140139

141-
export const getContractPathFromSourcesOrThrow = (
142-
contractName: string,
143-
sources: Sources,
144-
): string => {
145-
const contractPath = getContractPathFromSources(contractName, sources);
146-
if (contractPath === undefined) {
147-
throw new EtherscanImportError({
148-
code: 'etherscan_missing_contract_definition',
149-
contractName,
150-
});
151-
}
152-
return contractPath;
153-
};
154-
155140
export const getVyperJsonInputFromSingleFileResult = (
156141
etherscanResult: EtherscanResult,
157142
sources: VyperJsonInput['sources'],
@@ -263,6 +248,19 @@ export const fetchFromEtherscan = async (
263248
return contractResultJson;
264249
};
265250

251+
// We use the new Etherscan API field `ContractFileName`, see https://github.com/ethereum/sourcify/issues/2239
252+
export const getContractFileNameFromEtherscanResultOrThrow = (
253+
contractResultJson: EtherscanResult,
254+
): string => {
255+
if (!contractResultJson.ContractFileName) {
256+
throw new EtherscanImportError({
257+
code: 'etherscan_missing_contract_in_json',
258+
contractName: contractResultJson.ContractName,
259+
});
260+
}
261+
return contractResultJson.ContractFileName;
262+
};
263+
266264
export const processSolidityResultFromEtherscan = (
267265
contractResultJson: EtherscanResult,
268266
): ProcessedEtherscanResult => {
@@ -279,18 +277,17 @@ export const processSolidityResultFromEtherscan = (
279277
if (isEtherscanJsonInput(sourceCodeObject)) {
280278
logDebug('Etherscan solcJsonInput contract found');
281279
solcJsonInput = parseEtherscanJsonInput(sourceCodeObject);
282-
contractPath = getContractPathFromSourcesOrThrow(
283-
contractName,
284-
solcJsonInput.sources,
285-
);
280+
contractPath =
281+
getContractFileNameFromEtherscanResultOrThrow(contractResultJson);
286282
} else if (isEtherscanMultipleFilesObject(sourceCodeObject)) {
287283
logDebug('Etherscan Solidity multiple file contract found');
288284
const sources = JSON.parse(sourceCodeObject) as Sources;
289285
solcJsonInput = getSolcJsonInputFromEtherscanResult(
290286
contractResultJson,
291287
sources,
292288
);
293-
contractPath = getContractPathFromSourcesOrThrow(contractName, sources);
289+
contractPath =
290+
getContractFileNameFromEtherscanResultOrThrow(contractResultJson);
294291
} else {
295292
logDebug('Etherscan Solidity single file contract found');
296293
contractPath = contractResultJson.ContractName + '.sol';
@@ -333,19 +330,15 @@ export const processVyperResultFromEtherscan = async (
333330
if (isJsonInput) {
334331
logDebug('Etherscan vyperJsonInput contract found');
335332
const parsedJsonInput = parseEtherscanJsonInput(sourceCodeProperty);
336-
contractPath = Object.keys(parsedJsonInput.settings.outputSelection)[0];
337-
if (contractPath === '*') {
338-
contractPath = Object.keys(parsedJsonInput.sources).find((source) =>
339-
source.includes(contractResultJson.ContractName),
340-
)!;
341-
if (!contractPath) {
342-
throw new EtherscanImportError({
343-
code: 'etherscan_missing_contract_in_json',
344-
contractName: contractResultJson.ContractName,
345-
});
346-
}
347-
}
348-
contractName = contractPath.split('/').pop()!.split('.')[0];
333+
contractPath =
334+
getContractFileNameFromEtherscanResultOrThrow(contractResultJson);
335+
// contractName: path/to/my.weird.contract.vy should be my.weird.contract
336+
contractName = contractPath
337+
.split('/')
338+
.pop()!
339+
.split('.')
340+
.slice(0, -1)
341+
.join('.');
349342
vyperJsonInput = {
350343
language: 'Vyper',
351344
sources: parsedJsonInput.sources,
Lines changed: 0 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,3 @@
1-
import SolidityParser from '@solidity-parser/parser';
2-
import { Sources } from '..';
3-
import { logDebug, logWarn } from '../logger';
4-
51
/**
62
* Checks whether the provided object contains any keys or not.
73
* @param obj The object whose emptiness is tested.
@@ -25,49 +21,3 @@ export function splitFullyQualifiedName(fullyQualifiedName: string): {
2521
const contractPath = splitIdentifier.slice(0, -1).join(':');
2622
return { contractPath, contractName };
2723
}
28-
29-
/**
30-
* Returns undefined if the contract is not found in the sources
31-
*/
32-
export const getContractPathFromSources = (
33-
contractName: string,
34-
sources: Sources,
35-
): string | undefined => {
36-
logDebug('parsing-util: Parsing sources for finding the contract path', {
37-
contractName,
38-
});
39-
const startTime = Date.now();
40-
let contractPath: string | undefined;
41-
for (const [path, { content }] of Object.entries(sources)) {
42-
try {
43-
const ast = SolidityParser.parse(content);
44-
SolidityParser.visit(ast, {
45-
ContractDefinition: (node: any): any => {
46-
if (node.name === contractName) {
47-
contractPath = path;
48-
return false; // Stop visiting
49-
}
50-
return undefined;
51-
},
52-
});
53-
if (contractPath) break;
54-
} catch (error) {
55-
// Just continue, because the relevant contract might be in a different source file.
56-
logWarn(
57-
'parsing-util: Error parsing source code. Ignoring this source.',
58-
{
59-
path,
60-
error,
61-
},
62-
);
63-
}
64-
}
65-
const endTime = Date.now();
66-
logDebug('parsing-util: Parsing for all sources done', {
67-
contractName,
68-
contractPath,
69-
timeInMs: endTime - startTime,
70-
});
71-
72-
return contractPath;
73-
};

packages/lib-sourcify/test/utils/etherscan-util.spec.ts

Lines changed: 4 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -193,10 +193,8 @@ describe('etherscan util (lib)', function () {
193193
},
194194
contractName: (MULTIPLE_CONTRACT_RESPONSE.result[0] as any)
195195
.ContractName,
196-
contractPath: EtherscanUtils.getContractPathFromSourcesOrThrow(
197-
(MULTIPLE_CONTRACT_RESPONSE.result[0] as any).ContractName,
198-
expectedSources,
199-
),
196+
contractPath: (MULTIPLE_CONTRACT_RESPONSE.result[0] as any)
197+
.ContractFileName,
200198
});
201199
});
202200

@@ -223,10 +221,8 @@ describe('etherscan util (lib)', function () {
223221
jsonInput: expectedJsonInput,
224222
contractName: (STANDARD_JSON_CONTRACT_RESPONSE.result[0] as any)
225223
.ContractName,
226-
contractPath: EtherscanUtils.getContractPathFromSourcesOrThrow(
227-
(STANDARD_JSON_CONTRACT_RESPONSE.result[0] as any).ContractName,
228-
expectedJsonInput.sources,
229-
),
224+
contractPath: (STANDARD_JSON_CONTRACT_RESPONSE.result[0] as any)
225+
.ContractFileName,
230226
});
231227
});
232228
});
@@ -323,22 +319,6 @@ describe('etherscan util (lib)', function () {
323319
});
324320
});
325321

326-
describe('getContractPathFromSourcesOrThrow', () => {
327-
it('should throw when the contract path is not found in the provided sources', () => {
328-
const sources = {
329-
'path/file.sol': { content: 'contract SolidityContract {}' },
330-
};
331-
expect(() =>
332-
EtherscanUtils.getContractPathFromSourcesOrThrow(
333-
'AnotherSolidityContract',
334-
sources,
335-
),
336-
)
337-
.to.throw(EtherscanImportError)
338-
.with.property('code', 'etherscan_missing_contract_definition');
339-
});
340-
});
341-
342322
describe('getCompilationFromEtherscanResult', () => {
343323
it('should return a SolidityCompilation', async () => {
344324
const solidityResult = EtherscanUtils.processSolidityResultFromEtherscan(

packages/lib-sourcify/test/utils/parsing-util.spec.ts

Lines changed: 0 additions & 26 deletions
This file was deleted.

services/server/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@
5555
"@ethereum-sourcify/lib-sourcify": "^2.2.2",
5656
"@google-cloud/cloud-sql-connector": "1.8.3",
5757
"@shazow/whatsabi": "0.22.2",
58+
"@solidity-parser/parser": "^0.20.2",
5859
"bunyan": "1.8.15",
5960
"chalk": "4.1.2",
6061
"config": "3.3.12",

services/server/src/server/apiv1/verification/solc-json/stateless/solc-json.stateless.handlers.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import { Services } from "../../../../services/services";
1010
import { ChainRepository } from "../../../../../sourcify-chain-repository";
1111
import { getApiV1ResponseFromVerification } from "../../../controllers.common";
1212
import logger from "../../../../../common/logger";
13-
import { getContractPathFromSources } from "@ethereum-sourcify/lib-sourcify";
13+
import { getContractPathFromSources } from "../../../../services/utils/parsing-util";
1414

1515
export async function verifySolcJsonEndpoint(req: Request, res: Response) {
1616
const services = req.app.get("services") as Services;

services/server/src/server/services/utils/etherscan-util.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,6 @@ function mapLibError(err: any, throwV2Errors: boolean): never {
4040
? new EtherscanRequestFailedError(message)
4141
: new BadGatewayError(message);
4242

43-
case "etherscan_missing_contract_definition":
4443
case "etherscan_vyper_version_mapping_failed":
4544
case "etherscan_missing_contract_in_json":
4645
case "etherscan_missing_vyper_settings":

0 commit comments

Comments
 (0)