Skip to content

Commit 556ed43

Browse files
authored
chore: fix review comments + add testnet configs (#592)
1 parent e2e9cdc commit 556ed43

File tree

1 file changed

+123
-4
lines changed

1 file changed

+123
-4
lines changed

scripts/contractDiff/contractDiffTool.ts

Lines changed: 123 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,42 @@ const NETWORK_CONFIGS: Record<string, NetworkConfig> = {
5151
apiUrl: "https://zetachain.blockscout.com/api",
5252
explorerType: "blockscout",
5353
},
54+
sepolia_testnet: {
55+
name: "Ethereum Sepolia",
56+
apiUrl: "https://api.etherscan.io/v2/api?chainid=11155111",
57+
apiKeyEnv: process.env.ETHERSCAN_API_KEY!,
58+
explorerType: "etherscan",
59+
},
60+
bsc_testnet: {
61+
name: "BNB Smart Chain Testnet",
62+
apiUrl: "https://api.etherscan.io/v2/api?chainid=97",
63+
apiKeyEnv: process.env.ETHERSCAN_API_KEY!,
64+
explorerType: "etherscan",
65+
},
66+
amoy_testnet: {
67+
name: "Polygon Amoy",
68+
apiUrl: "https://api.etherscan.io/v2/api?chainid=80002",
69+
apiKeyEnv: process.env.ETHERSCAN_API_KEY!,
70+
explorerType: "etherscan",
71+
},
72+
base_sepolia: {
73+
name: "Base Sepolia",
74+
apiUrl: "https://api.etherscan.io/v2/api?chainid=84532",
75+
apiKeyEnv: process.env.ETHERSCAN_API_KEY!,
76+
explorerType: "etherscan",
77+
},
78+
arbitrum_sepolia: {
79+
name: "Arbitrum Sepolia",
80+
apiUrl: "https://api.etherscan.io/v2/api?chainid=421614",
81+
apiKeyEnv: process.env.ETHERSCAN_API_KEY!,
82+
explorerType: "etherscan",
83+
},
84+
avalanche_testnet: {
85+
name: "Avalanche Fuji",
86+
apiUrl: "https://api.etherscan.io/v2/api?chainid=43113",
87+
apiKeyEnv: process.env.ETHERSCAN_API_KEY!,
88+
explorerType: "etherscan",
89+
}
5490
}
5591

5692
interface ContractSource {
@@ -94,6 +130,15 @@ interface BlockscoutResponse {
94130
abi?: string;
95131
}
96132

133+
/**
134+
* Fetches contract source code from Blockscout explorer API.
135+
* Converts the Blockscout response format to match Etherscan's format for consistency.
136+
*
137+
* @param address - The contract address to fetch
138+
* @param apiUrl - The Blockscout API base URL
139+
* @returns ContractSource object with source code and metadata
140+
* @throws Error if contract is not verified or fetch fails
141+
*/
97142
async function fetchFromBlockscout(
98143
address: string,
99144
apiUrl: string
@@ -145,18 +190,27 @@ async function fetchFromBlockscout(
145190
} catch (error) {
146191
if (axios.isAxiosError(error)) {
147192
if (error.response?.status === 404) {
148-
throw new Error("Contract not found or not verified on Blockscout");
193+
throw new Error(`Contract not found or not verified on Blockscout: ${error.message}`);
149194
}
150195
throw new Error(`Failed to fetch contract from Blockscout: ${error.message}`);
151196
}
152197
throw error;
153198
}
154199
}
155200

201+
/**
202+
* Fetches contract source code from Etherscan-compatible API.
203+
*
204+
* @param address - The contract address to fetch
205+
* @param apiUrl - The Etherscan API URL with chainid
206+
* @param apiKey - The Etherscan API key for authentication
207+
* @returns ContractSource object with source code and metadata
208+
* @throws Error if contract is not verified or fetch fails
209+
*/
156210
async function fetchFromEtherscan(
157-
address: string,
158-
apiUrl: string,
159-
apiKey: string
211+
address: string,
212+
apiUrl: string,
213+
apiKey: string
160214
): Promise<ContractSource> {
161215
try {
162216
const response = await axios.get<EtherscanResponse>(apiUrl, {
@@ -194,6 +248,15 @@ async function fetchFromEtherscan(
194248
}
195249
}
196250

251+
/**
252+
* Fetches contract source code from the appropriate explorer based on network configuration.
253+
* Routes to either Blockscout or Etherscan API based on the explorer type.
254+
*
255+
* @param address - The contract address to fetch
256+
* @param network - The network identifier
257+
* @returns ContractSource object with source code and metadata
258+
* @throws Error if network is unknown or fetch fails
259+
*/
197260
async function fetchContractSource(
198261
address: string,
199262
network: string
@@ -214,6 +277,13 @@ async function fetchContractSource(
214277
}
215278
}
216279

280+
/**
281+
* Extracts the declaration order of contracts, interfaces, and libraries from Solidity source code.
282+
* Used to maintain consistent ordering when flattening multi-file contracts.
283+
*
284+
* @param sourceCode - The Solidity source code to analyze
285+
* @returns Array of contract/interface/library names in order of declaration
286+
*/
217287
function extractDeclarationOrder(sourceCode: string): string[] {
218288
const order: string[] = [];
219289

@@ -228,6 +298,13 @@ function extractDeclarationOrder(sourceCode: string): string[] {
228298
return order;
229299
}
230300

301+
/**
302+
* Finds the file content that contains a specific contract, interface, or library declaration.
303+
*
304+
* @param sources - Object containing file paths/names as keys and file content as values
305+
* @param contractName - The name of the contract/interface/library to find
306+
* @returns The file content containing the contract, or null if not found
307+
*/
231308
function findFileByContractName(sources: any, contractName: string): string | null {
232309
for (const [, fileData] of Object.entries(sources)) {
233310
const content = typeof fileData === "object" && fileData !== null
@@ -243,6 +320,13 @@ function findFileByContractName(sources: any, contractName: string): string | nu
243320
return null;
244321
}
245322

323+
/**
324+
* Removes duplicate SPDX license identifiers and pragma statements from flattened code.
325+
* Keeps only the first occurrence of each to avoid compilation errors.
326+
*
327+
* @param code - The Solidity source code to clean
328+
* @returns Cleaned source code with duplicate headers removed
329+
*/
246330
function cleanDuplicateHeaders(code: string): string {
247331
const lines = code.split('\n');
248332
const result: string[] = [];
@@ -276,6 +360,14 @@ function cleanDuplicateHeaders(code: string): string {
276360
return result.join('\n');
277361
}
278362

363+
/**
364+
* Flattens multi-file contract source code into a single file.
365+
* Handles both standard JSON format and double-braced format from explorers.
366+
* Files are concatenated in alphabetical order and duplicate headers are removed.
367+
*
368+
* @param sourceCode - The source code
369+
* @returns Flattened source code as a single string
370+
*/
279371
function flattenSourceCode(sourceCode: string): string {
280372
if (sourceCode.startsWith("{{") || sourceCode.startsWith("{")) {
281373
try {
@@ -308,6 +400,14 @@ function flattenSourceCode(sourceCode: string): string {
308400
return sourceCode;
309401
}
310402

403+
/**
404+
* Reorders multi-file contract source to match the declaration order of a single-file version.
405+
* This ensures consistent ordering when comparing contracts that have different file structures.
406+
*
407+
* @param singleFileCode - The single-file version used as reference for ordering
408+
* @param multiFileCode - The multi-file version source code to reorder
409+
* @returns Reordered and flattened source code matching the single-file order
410+
*/
311411
function reorderMultiFileToMatchSingleFile(
312412
singleFileCode: string,
313413
multiFileCode: string
@@ -366,6 +466,15 @@ function reorderMultiFileToMatchSingleFile(
366466
}
367467
}
368468

469+
/**
470+
* Saves contract source code to a file in the specified output directory.
471+
* Creates the directory if it doesn't exist.
472+
*
473+
* @param content - The contract source code to save
474+
* @param fileName - The name of the file to create
475+
* @param outputDir - The directory where the file should be saved
476+
* @returns The full path to the saved file
477+
*/
369478
function saveContractToFile(
370479
content: string,
371480
fileName: string,
@@ -381,6 +490,16 @@ function saveContractToFile(
381490
return filePath;
382491
}
383492

493+
/**
494+
* Main function to fetch, flatten, and compare two contract implementations.
495+
* Fetches both contracts, handles single vs multi-file structures,
496+
* flattens them for comparison, and saves the results to disk.
497+
*
498+
* @param oldAddress - The address of the old/previous contract implementation
499+
* @param newAddress - The address of the new/updated contract implementation
500+
* @param network - The network identifier where the contracts are deployed
501+
* @throws Error if fetching or processing fails
502+
*/
384503
export async function fetchAndFlattenContract(
385504
oldAddress: string,
386505
newAddress: string,

0 commit comments

Comments
 (0)