Skip to content

Commit

Permalink
Merge pull request #20 from runtimeverification/raoul/web-extension
Browse files Browse the repository at this point in the history
Fix web extension
  • Loading branch information
RaoulSchaffranek authored Nov 9, 2024
2 parents 01d99f3 + 04a2d02 commit 1e8d1a8
Show file tree
Hide file tree
Showing 9 changed files with 297 additions and 166 deletions.
246 changes: 129 additions & 117 deletions package-lock.json

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
},
"publisher": "runtimeverification",
"description": "Advanced Solidity and EVM Debugger",
"version": "3.1.1",
"version": "4.0.0",
"engines": {
"vscode": "^1.79.0"
},
Expand Down Expand Up @@ -94,7 +94,7 @@
"simbolik.auto-open-disassembly-view": {
"type": "boolean",
"description": "If set to true, the debugger will automatically open the disassembly view when starting a debugging session.",
"default": true,
"default": false,
"order": 8
},
"simbolik.json-rpc-url": {
Expand Down Expand Up @@ -210,7 +210,7 @@
"build": "npm run vscode:prepublish",
"vscode:prepublish": "npm run esbuild-base -- --minify && npm run esbuild-base-web -- --minify",
"esbuild-base": "esbuild ./src/extension.ts --bundle --outfile=build/extension.js --external:vscode --format=cjs --platform=node",
"esbuild-base-web": "esbuild ./src/extension.web.ts --bundle --outfile=build/extension.web.js --external:vscode --format=esm --platform=browser",
"esbuild-base-web": "esbuild ./src/extension.web.ts --bundle --outfile=build/extension.web.js --external:vscode --format=cjs --platform=browser --target=es2020 --minify",
"esbuild": "npm run esbuild-base -- --sourcemap",
"compile": "tsc -p ./tsconfig.json",
"compile-web": "tsc -p ./tsconfig.web.json",
Expand Down
28 changes: 21 additions & 7 deletions src/DebugAdapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,8 @@ class WebsocketDebugAdapter implements vscode.DebugAdapter {
websocket.onmessage = (message: MessageEvent) => {
const payload = message.data.toString();
const data = JSON.parse(payload);
const dataWithAbsolutePaht = this.prependPaths(data);
this._onDidSendMessage.fire(dataWithAbsolutePaht);
const dataWithAbsolutePaths = this.prependPaths(data);
this._onDidSendMessage.fire(dataWithAbsolutePaths);
};
}

Expand All @@ -75,8 +75,12 @@ class WebsocketDebugAdapter implements vscode.DebugAdapter {
this.websocket.close();
}

foundryRoot() : string {
return this.configuration['clientMount']
foundryRoot() : vscode.Uri {
if (!this.configuration['clientMount']) {
return vscode.Uri.parse('file:///');
}
const uri = vscode.Uri.from(this.configuration['clientMount']);
return uri;
}

/**
Expand All @@ -91,9 +95,11 @@ class WebsocketDebugAdapter implements vscode.DebugAdapter {
const result = Object.assign({}, message);
for (const key in message) {
if (['path', 'symbolFilePath'].includes(key) && typeof message[key] === 'string') {
result[key] = stripPrefix(message[key], `${this.foundryRoot()}/`);
const uri = vscode.Uri.parse(message[key]);
result[key] = relativeTo(uri, this.foundryRoot());
} else if (key == 'file' && typeof message[key] === 'string') {
result[key] = stripPrefix(message[key], `file://${this.foundryRoot()}/`);
const uri = vscode.Uri.parse(message[key]);
result[key] = relativeTo(uri, this.foundryRoot());
} else if (typeof message[key] === 'object') {
result[key] = this.trimPaths(message[key]);
}
Expand All @@ -117,7 +123,7 @@ class WebsocketDebugAdapter implements vscode.DebugAdapter {
if (['path', 'symbolFilePath', 'file'].includes(key) && typeof message[key] === 'string') {
result[key] = `${this.foundryRoot()}/${message[key]}`;
} else if (key == 'file' && typeof message[key] === 'string') {
result[key] = `file://${this.foundryRoot()}/${message[key]}`;
result[key] = `${this.foundryRoot()}/${message[key]}`;
} else if (typeof message[key] === 'object') {
result[key] = this.prependPaths(message[key]);
}
Expand All @@ -129,6 +135,14 @@ class WebsocketDebugAdapter implements vscode.DebugAdapter {

}

function relativeTo(uri: vscode.Uri, prefixUri: vscode.Uri): string {
const s = uri.path;
const prefix = prefixUri.path + '/';
const relative = stripPrefix(s, prefix);
const result = stripPrefix(relative, '/');
return result;
}

function stripPrefix(s: string, prefix: string): string {
if (s.startsWith(prefix)) {
return s.slice(prefix.length);
Expand Down
98 changes: 91 additions & 7 deletions src/DebugAdapter.web.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export class SolidityDebugAdapterDescriptorFactory
const server = getConfigValue('server', 'wss://beta.simbolik.runtimeverification.com');
const websocket = new WebSocket(server);
websocket.onopen = () => {
const websocketAdapter = new WebsocketDebugAdapter(websocket);
const websocketAdapter = new WebsocketDebugAdapter(websocket, session.configuration);
const implementation = new vscode.DebugAdapterInlineImplementation(websocketAdapter);
resolve(implementation);
};
Expand All @@ -42,25 +42,109 @@ export class SolidityDebugAdapterDescriptorFactory
}
}

type WithApiKey = { apiKey: string };
type WithClientVersion = { clientVersion: string };

type DebugProtocolMessage = vscode.DebugProtocolMessage & WithApiKey & WithClientVersion;

class WebsocketDebugAdapter implements vscode.DebugAdapter {
_onDidSendMessage = new vscode.EventEmitter<vscode.DebugProtocolMessage>();

constructor(private websocket: WebSocket) {
constructor(private websocket: WebSocket, private configuration: vscode.DebugConfiguration) {
websocket.onmessage = (message: MessageEvent) => {
message.data.text().then((payload: string) => {
const json = JSON.parse(payload);
this._onDidSendMessage.fire(json);
});
const data = JSON.parse(message.data);
const dataWithAbsolutePaths = this.prependPaths(data);
this._onDidSendMessage.fire(dataWithAbsolutePaths);
};
}

onDidSendMessage = this._onDidSendMessage.event;

handleMessage(message: vscode.DebugProtocolMessage): void {
this.websocket.send(JSON.stringify(message));
const apiKey = getConfigValue('api-key', '');
const clientVersion = vscode.extensions.getExtension('simbolik.simbolik')?.packageJSON.version;
const messageWithApiKey : DebugProtocolMessage = Object.assign({}, message, {apiKey, clientVersion});
const messageWithRelativePaths = this.trimPaths(messageWithApiKey);
this.websocket.send(JSON.stringify(messageWithRelativePaths));
}

dispose() {
this.websocket.close();
}

foundryRoot() : vscode.Uri {
if (!this.configuration['clientMount']) {
return vscode.Uri.parse('file:///');
}
const uri = vscode.Uri.from(this.configuration['clientMount']);
return uri;
}

/**
* Recursively walk over all object properties and for each property
* named `path` and type `string`, remove the foundry root from the path.
* @param message
*/
trimPaths(message: {[key: string]: any} | any[] ) : {[key: string]: any} | any[] {
if (Array.isArray(message)) {
return message.map((item) => this.trimPaths(item));
} else if (message instanceof Object) {
const result = Object.assign({}, message);
for (const key in message) {
if (['path', 'symbolFilePath'].includes(key) && typeof message[key] === 'string') {
const uri = vscode.Uri.parse(message[key]);
result[key] = relativeTo(uri, this.foundryRoot());
} else if (key == 'file' && typeof message[key] === 'string') {
const uri = vscode.Uri.parse(message[key]);
result[key] = relativeTo(uri, this.foundryRoot());
} else if (typeof message[key] === 'object') {
result[key] = this.trimPaths(message[key]);
}
}
return result;
}
return message;
}

/**
* Recursively walk over all object properties and for each property
* named `path` and type `string`, prepend the foundry root to the path.
* @param message
*/
prependPaths(message: {[key: string]: any} | any[]) : {[key: string]: any} | any[] {
if (Array.isArray(message)) {
return message.map((item) => this.prependPaths(item));
} else if (message instanceof Object) {
const result = Object.assign({}, message);
for (const key in message) {
if (['path', 'symbolFilePath', 'file'].includes(key) && typeof message[key] === 'string') {
result[key] = `${this.foundryRoot()}/${message[key]}`;
} else if (key == 'file' && typeof message[key] === 'string') {
result[key] = `${this.foundryRoot()}/${message[key]}`;
} else if (typeof message[key] === 'object') {
result[key] = this.prependPaths(message[key]);
}
}
return result;
}
return message;
}
}

function relativeTo(uri: vscode.Uri, prefixUri: vscode.Uri): string {
const s = uri.path;
const prefix = prefixUri.path;
const relative = stripPrefix(s, prefix);
const result = stripPrefix(relative, '/');
console.log('uri', uri);
console.log('prefixUri', prefixUri);
console.log('result', result);
return result;
}

function stripPrefix(s: string, prefix: string): string {
if (s.startsWith(prefix)) {
return s.slice(prefix.length);
}
return s;
}
5 changes: 3 additions & 2 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,12 +83,13 @@ export function activate(context: vscode.ExtensionContext) {
kastProvider
);


vscode.debug.onDidStartDebugSession(session => {
outputChannel.info(`Debug session started: ${session.id}`);
if (session.type === 'solidity') {
if (getConfigValue('auto-open-disassembly-view', true)) {
if (getConfigValue('auto-open-disassembly-view', false)) {
vscode.commands.executeCommand('debug.action.openDisassemblyView');
}
}
}
});

Expand Down
14 changes: 13 additions & 1 deletion src/extension.web.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ import {CodelensProvider} from './CodelensProvider';
import {SolidityDebugAdapterDescriptorFactory} from './DebugAdapter.web';
import {startDebugging} from './startDebugging';
import {KastProvider, viewKast} from './KastProvider';
import {getConfigValue} from './utils';

console.log("Hello from Simbolik!");

const outputChannel = vscode.window.createOutputChannel(
'Simbolik Solidity Debugger',
Expand Down Expand Up @@ -36,7 +39,7 @@ export function activate(context: vscode.ExtensionContext) {

disposable = vscode.commands.registerCommand(
'simbolik.startDebugging',
startDebugging
(contract, method) => startDebugging(contract, method),
);
context.subscriptions.push(disposable);

Expand All @@ -51,6 +54,15 @@ export function activate(context: vscode.ExtensionContext) {

vscode.debug.onDidStartDebugSession(session => {
outputChannel.info(`Debug session started: ${session.id}`);
if (session.type === 'solidity') {
if (getConfigValue('auto-open-disassembly-view', false)) {
vscode.commands.executeCommand('debug.action.openDisassemblyView');
}
}
});

vscode.debug.onDidTerminateDebugSession(session => {
outputChannel.info(`Debug session ended: ${session.id}`);
});
}

Expand Down
50 changes: 27 additions & 23 deletions src/foundry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ import {getConfigValue} from './utils';
import {parse as parseToml} from 'smol-toml';

export
function forgeBuildTask(file: string) {
function forgeBuildTask(file: vscode.Uri) {
const incrementalBuild = getConfigValue('incremental-build', false);
const forgePath = getConfigValue('forge-path', 'forge');
const cwd = file.substring(0, file.lastIndexOf('/'));
const cwd = file.with({path: file.path.split('/').slice(0, -1).join('/')}).fsPath;
const task = new vscode.Task(
{
label: 'forge build',
Expand All @@ -20,7 +20,7 @@ function forgeBuildTask(file: string) {
env: {
'FOUNDRY_OPTIMIZER': 'false',
'FOUNDRY_BUILD_INFO': 'true',
'FOUNDRY_EXTRA_OUTPUT': '["storageLayout", "evm.bytecode.generatedSources", "evm.legacyAssembly"]',
'FOUNDRY_EXTRA_OUTPUT': '["storageLayout", "evm.bytecode.generatedSources", "evm.legacyAssembly", "evm.deployedBytecode.immutableReferences"]',
'FOUNDRY_BYTECODE_HASH': 'ipfs',
'FOUNDRY_CBOR_METADATA': 'true',
'FOUNDRY_FORCE': incrementalBuild ? 'false' : 'true',
Expand All @@ -33,56 +33,59 @@ function forgeBuildTask(file: string) {
}

export
async function loadBuildInfo(file: string): Promise<string> {
async function loadBuildInfo(file: vscode.Uri): Promise<string> {
const root = await foundryRoot(file);
const buildInfo = await forgeBuildInfo(root);
return buildInfo;
}

export
async function foundryRoot(file: string) {
async function foundryRoot(file: vscode.Uri): Promise<vscode.Uri> {
// Find the root of the project, which is the directory containing the foundry.toml file
let root = file;
let base = file.with({'path': '/'})
let pathSegments = file.path.split('/');
let stat;
try {
stat = await vscode.workspace.fs.stat(vscode.Uri.file(`${root}/foundry.toml`));
const uri = vscode.Uri.joinPath(base, ...pathSegments, 'foundry.toml');
stat = await vscode.workspace.fs.stat(uri);
} catch (e) {
stat = false;
}
while (!stat) {
const lastSlash = root.lastIndexOf('/');
if (lastSlash === -1) {
throw new Error('Could not find foundry.toml');
if (pathSegments.length === 0) {
throw new Error('No foundry.toml found');
}
root = root.substring(0, lastSlash);
pathSegments.pop();
try {
stat = await vscode.workspace.fs.stat(vscode.Uri.file(`${root}/foundry.toml`));
const uri = vscode.Uri.joinPath(base, ...pathSegments, 'foundry.toml');
stat = await vscode.workspace.fs.stat(uri);
} catch (e) {
stat = false;
}
}
return root;
return vscode.Uri.joinPath(base, ...pathSegments);
}

export
type FoundryConfig = { 'profile'?: { [profile: string]: { [key: string]: string } } };

export
async function foundryConfig(root: string): Promise<FoundryConfig> {
const configPath = `${root}/foundry.toml`;
const config = await vscode.workspace.fs.readFile(vscode.Uri.file(configPath));
return parseToml(config.toString());
async function foundryConfig(root: vscode.Uri): Promise<FoundryConfig> {
const configPath = vscode.Uri.joinPath(root, 'foundry.toml');
const config = await vscode.workspace.fs.readFile(configPath);
const text = new TextDecoder().decode(config);
return parseToml(text);
}

async function forgeBuildInfo(root: string): Promise<string> {
async function forgeBuildInfo(root: vscode.Uri): Promise<string> {
const config = await foundryConfig(root);
const out = config?.profile?.default?.out ?? 'out';

// Get the contents of the youngest build-info file
const buildInfoDir = `${root}/${out}/build-info`;
const buildInfoDir = vscode.Uri.joinPath(root, out, 'build-info');

// Get list of build-info files
const files = await vscode.workspace.fs.readDirectory(vscode.Uri.file(buildInfoDir));
const files = await vscode.workspace.fs.readDirectory(buildInfoDir);
const buildInfoFiles = files.filter(([file, type]) => type === vscode.FileType.File && file.endsWith('.json'));

if (buildInfoFiles.length === 0) {
Expand All @@ -95,13 +98,14 @@ async function forgeBuildInfo(root: string): Promise<string> {

// Read the youngest build-info file
const youngestBuildInfo = await vscode.workspace.fs.readFile(sortedFiles[0].uri);
return youngestBuildInfo.toString();
const text = new TextDecoder().decode(youngestBuildInfo);
return text;
}

async function getSortedFilesByCreationTime(buildInfoDir: string, buildInfoFiles: [string, vscode.FileType][]): Promise<{ file: string, uri: vscode.Uri, ctime: number }[]> {
async function getSortedFilesByCreationTime(buildInfoDir: vscode.Uri, buildInfoFiles: [string, vscode.FileType][]): Promise<{ file: string, uri: vscode.Uri, ctime: number }[]> {
const filesWithStats = await Promise.all(
buildInfoFiles.map(async ([file]) => {
const fileUri = vscode.Uri.file(`${buildInfoDir}/${file}`);
const fileUri = vscode.Uri.joinPath(buildInfoDir, file);
const fileStat = await vscode.workspace.fs.stat(fileUri);
return { file, uri: fileUri, ctime: fileStat.ctime };
})
Expand Down
Loading

0 comments on commit 1e8d1a8

Please sign in to comment.