This repository was archived by the owner on Aug 21, 2025. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 123
Expand file tree
/
Copy pathprecompute-solidity-hashes.ts
More file actions
124 lines (98 loc) · 4.79 KB
/
precompute-solidity-hashes.ts
File metadata and controls
124 lines (98 loc) · 4.79 KB
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
import { promises as fs } from 'fs';
import * as path from 'path';
const Web3 = require('web3');
const web3 = new Web3();
const removeX = (input: string): string => (input.startsWith('0x') ? input.substring(2) : input);
const hexify = (input: string, prepend: boolean = true): string => {
input = input.toLowerCase().trim();
input = removeX(input);
input = input.replace(/[^0-9a-f]/g, '');
return prepend ? '0x' + input : input;
};
// Define all regexes
const regexes = [
{ regex: /precomputeslot\("([^"]+)"\)/i, process: (match: string) => computeSlot(match) }, // Used for slot calculations
{ regex: /precomputeslothex\("([^"]+)"\)/i, process: (match: string) => computeSlotHex(match) }, // Not used currently
{
regex: /precomputekeccak256\([\s\S]*?"([^"]*)"[\s\S]*?\)/gi,
process: (match: string) => computeKeccak256(match),
}, // Used for event topic calculations
{ regex: /functionsig\("([^"]+)"\)/i, process: (match: string) => computeFunctionSig(match) }, // Not used currently
{ regex: /asciihex\("([^"]+)"\)/i, process: (match: string) => computeAsciiHex(match) }, // Used for encoding contract type as bytes32
];
const computeSlot = (input: string): string => {
// Directly compute the hash
const hash = web3.utils.soliditySha3({ type: 'string', value: input }) || '';
// Convert hash to BN, subtract 1, and ensure correct hex formatting
let slot = web3.utils.toHex(web3.utils.toBN(hash).sub(web3.utils.toBN(1)));
// Pad the hex string to 64 characters, ensuring it starts with '0x'
slot = '0x' + slot.substring(2).padStart(64, '0');
return slot;
};
const computeSlotHex = (input: string): string => {
const hash = web3.utils.soliditySha3({ type: 'string', value: input }) || '';
return 'hex"' + hexify(hash.substring(2), false) + '"';
};
const computeKeccak256 = (input: string): string => {
const keccak = web3.utils.keccak256(input);
return keccak === null ? '0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470' : keccak;
};
const computeFunctionSig = (input: string): string => {
const hash = web3.utils.keccak256(web3.eth.abi.encodeFunctionSignature(input));
return hash.substring(0, 10);
};
const computeAsciiHex = (input: string): string => '0x' + web3.utils.asciiToHex(input).substring(2).padStart(64, '0');
// Recursively read directory for .sol files
const readDirRecursively = async (dir: string, fileList: string[] = []): Promise<string[]> => {
const files = await fs.readdir(dir, { withFileTypes: true });
for (const file of files) {
const filePath = path.join(dir, file.name);
if (file.isDirectory()) {
await readDirRecursively(filePath, fileList);
} else if (file.name.endsWith('.sol')) {
fileList.push(filePath);
}
}
return fileList;
};
const processFile = async (filePath: string): Promise<void> => {
let content = await fs.readFile(filePath, { encoding: 'utf8' });
let offset = 0; // Tracks the current offset in content for accurate line number calculation
for (let { regex, process } of regexes) {
let modifiedContent = ''; // Holds the new content as we build it
let lastMatchEnd = 0; // Tracks the end of the last match to slice correctly
regex = new RegExp(regex.source, 'gi'); // Ensure regex is global
let match;
while ((match = regex.exec(content))) {
const beforeMatch = content.slice(lastMatchEnd, match.index);
modifiedContent += beforeMatch; // Add content before current match
const lineNumber = beforeMatch.length > 0 ? beforeMatch.split(/\r?\n/).length + offset : 1 + offset;
const originalText = match[0];
const replacement = process(match[1]);
console.log(`File: ${filePath}\nLine: ${lineNumber}\nOriginal: ${originalText}\nReplacement: ${replacement}\n`);
modifiedContent += content.substring(match.index, regex.lastIndex).replace(originalText, replacement); // Add modified match
lastMatchEnd = regex.lastIndex; // Update lastMatchEnd to the end of the current match
// Update offset to current line number for next iteration
offset += beforeMatch.split(/\r?\n/).length;
}
// Add any remaining content after the last match
modifiedContent += content.slice(lastMatchEnd);
// Update content with the modified content for the next regex
content = modifiedContent;
}
await fs.writeFile(filePath, content, { encoding: 'utf8' });
};
/**
* This script precomputes hashes for the smart contracts anywhere the special precompute keywords are used.
* Usage: `npx ts-node scripts/precompute-solidity-hashes.ts`
*/
const main = async () => {
console.log('Finding .sol files...');
const srcDir = path.join(__dirname, '../src');
const files = await readDirRecursively(srcDir);
console.log(`Found ${files.length} .sol files`);
for (const file of files) {
await processFile(file);
}
};
main().catch(console.error);