Skip to content

Commit ed97c3e

Browse files
committed
wip: contract size
1 parent 95da520 commit ed97c3e

File tree

4 files changed

+289
-32
lines changed

4 files changed

+289
-32
lines changed

package-lock.json

Lines changed: 127 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,8 @@
186186
"typescript": "^4.8.4"
187187
},
188188
"dependencies": {
189-
"cli-table3": "^0.6.3"
189+
"cli-table3": "^0.6.3",
190+
"path": "^0.12.7",
191+
"solc": "^0.8.26"
190192
}
191-
}
193+
}

src/commands/contract-size.js

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
const vscode = require('vscode');
2+
const solc = require('solc');
3+
const path = require('path');
4+
const fs = require('fs');
5+
6+
let decorationType;
7+
let workspaceRoot;
8+
let remappings = [];
9+
10+
function activate(context) {
11+
console.log('contract-size: Activating extension');
12+
workspaceRoot = vscode.workspace.workspaceFolders?.[0]?.uri.fsPath || '';
13+
console.log('contract-size: Workspace root:', workspaceRoot);
14+
15+
loadRemappings();
16+
17+
decorationType = vscode.window.createTextEditorDecorationType({
18+
after: {
19+
margin: '0 0 0 1em',
20+
textDecoration: 'none; opacity: 0.7;'
21+
}
22+
});
23+
24+
context.subscriptions.push(
25+
vscode.workspace.onDidChangeTextDocument(event => {
26+
if (event.document.languageId === 'solidity') {
27+
updateDecorations(event.document);
28+
}
29+
}),
30+
vscode.window.onDidChangeActiveTextEditor(editor => {
31+
if (editor?.document.languageId === 'solidity') {
32+
updateDecorations(editor.document);
33+
}
34+
})
35+
);
36+
37+
if (vscode.window.activeTextEditor?.document.languageId === 'solidity') {
38+
updateDecorations(vscode.window.activeTextEditor.document);
39+
}
40+
41+
console.log('contract-size: Activation complete');
42+
}
43+
44+
function loadRemappings() {
45+
const remappingsPath = path.join(workspaceRoot, 'remappings.txt');
46+
console.log('contract-size: Looking for remappings at:', remappingsPath);
47+
if (fs.existsSync(remappingsPath)) {
48+
const content = fs.readFileSync(remappingsPath, 'utf8');
49+
remappings = content.split('\n')
50+
.filter(line => line.trim() !== '')
51+
.map(line => {
52+
const [from, to] = line.split('=');
53+
return { from: from.trim(), to: to.trim() };
54+
});
55+
console.log('contract-size: Loaded remappings:', remappings);
56+
} else {
57+
console.log('contract-size: No remappings.txt found');
58+
}
59+
}
60+
61+
function updateDecorations(document) {
62+
const text = document.getText();
63+
const contractSizes = compileAndGetSizes(document.fileName, text);
64+
const decorations = [];
65+
66+
const contractRegex = /contract\s+(\w+)(?:\s+is\s+[^{]+)?\s*{/g;
67+
let match;
68+
while ((match = contractRegex.exec(text)) !== null) {
69+
const contractName = match[1];
70+
const size = contractSizes[contractName];
71+
if (size) {
72+
const pos = document.positionAt(match.index);
73+
const range = document.lineAt(pos.line).range;
74+
decorations.push({
75+
range,
76+
renderOptions: {
77+
after: {
78+
contentText: ` (${formatSize(size)})`,
79+
color: getSizeColor(size),
80+
}
81+
}
82+
});
83+
}
84+
}
85+
86+
vscode.window.activeTextEditor?.setDecorations(decorationType, decorations);
87+
}
88+
89+
function compileAndGetSizes(fileName, source) {
90+
const input = {
91+
language: 'Solidity',
92+
sources: { [fileName]: { content: source } },
93+
settings: { outputSelection: { '*': { '*': ['evm.bytecode'] } } }
94+
};
95+
96+
try {
97+
const output = JSON.parse(solc.compile(JSON.stringify(input), { import: findImports }));
98+
if (output.errors?.some(error => error.severity === 'error')) {
99+
console.error('contract-size: Compilation errors:', output.errors);
100+
return {};
101+
}
102+
103+
const sizes = {};
104+
for (const [, contracts] of Object.entries(output.contracts)) {
105+
for (const [name, contract] of Object.entries(contracts)) {
106+
sizes[name] = contract.evm.bytecode.object.length / 2;
107+
}
108+
}
109+
return sizes;
110+
} catch (error) {
111+
console.error('contract-size: Compilation failed:', error);
112+
return {};
113+
}
114+
}
115+
116+
function findImports(importPath) {
117+
console.log('contract-size: Resolving import:', importPath);
118+
for (const remap of remappings) {
119+
if (importPath.startsWith(remap.from)) {
120+
const remappedPath = importPath.replace(remap.from, remap.to);
121+
const fullPath = path.resolve(workspaceRoot, remappedPath);
122+
if (fs.existsSync(fullPath)) {
123+
console.log('contract-size: Resolved import via remappings:', fullPath);
124+
return { contents: fs.readFileSync(fullPath, 'utf8') };
125+
}
126+
}
127+
else {
128+
console.log('contract-size: Resolved import without remappings:', importPath);
129+
return { contents: fs.readFileSync(importPath, 'utf8') };
130+
}
131+
}
132+
133+
}
134+
135+
function formatSize(bytes) {
136+
return `${(bytes / 1024).toFixed(2) / 2} KB`;
137+
}
138+
139+
function getSizeColor(bytes) {
140+
const kb = bytes / 1024;
141+
if (kb > 22) return '#FF4136';
142+
if (kb > 20) return '#FFDC00';
143+
return '#2ECC40';
144+
}
145+
146+
module.exports = {
147+
activate
148+
};

0 commit comments

Comments
 (0)