From 403aa83d8004e868cba9ae4edb9a835c874485e3 Mon Sep 17 00:00:00 2001 From: Raoul Schaffranek Date: Tue, 14 May 2024 13:46:08 +0200 Subject: [PATCH 1/8] Api key and build-info parsing. --- package.json | 5 +++ src/DebugAdapter.ts | 5 ++- src/startDebugging.ts | 90 ++++++++++++++++++++++++++++++++----------- 3 files changed, 76 insertions(+), 24 deletions(-) diff --git a/package.json b/package.json index 5a50b6f..7dee4c6 100644 --- a/package.json +++ b/package.json @@ -48,6 +48,11 @@ "default": "ws://localhost:6789", "description": "The websocket URL where the simbolik server is listening." }, + "simbolik.api-key": { + "type": "string", + "default": "valid-api-key", + "description": "The API key to authenticate with the simbolik server." + }, "simbolik.bmc-depth": { "type": "integer", "description": "Signals the backend that it should only unroll loops up to this depth. This parameter is only relevant for symbolic execution backends.", diff --git a/src/DebugAdapter.ts b/src/DebugAdapter.ts index dc393d5..80e7b97 100644 --- a/src/DebugAdapter.ts +++ b/src/DebugAdapter.ts @@ -60,6 +60,8 @@ function retry( }); } +type WithApiKey = { apiKey: string }; + class WebsocketDebugAdapter implements vscode.DebugAdapter { _onDidSendMessage = new vscode.EventEmitter(); @@ -74,7 +76,8 @@ class WebsocketDebugAdapter implements vscode.DebugAdapter { onDidSendMessage = this._onDidSendMessage.event; handleMessage(message: vscode.DebugProtocolMessage): void { - this.websocket.send(JSON.stringify(message)); + const messageWithApiKey : vscode.DebugProtocolMessage & WithApiKey = Object.assign({}, message, {apiKey: "valid-api-key"}); + this.websocket.send(JSON.stringify(messageWithApiKey)); } dispose() { diff --git a/src/startDebugging.ts b/src/startDebugging.ts index a3a089e..da89765 100644 --- a/src/startDebugging.ts +++ b/src/startDebugging.ts @@ -49,42 +49,38 @@ export async function startDebugging( const file = activeTextEditor.document.uri.toString(); const contractName = contract['name']; const methodSignature = `${method['name']}(${parameters.join(',')})`; - const stopAtFirstOpcode = getConfigValue('stop-at-first-opcode', false); const showSourcemaps = getConfigValue('show-sourcemaps', false); const debugConfigName = `${contractName}.${methodSignature}`; const anvilPort = getConfigValue('anvil-port', '8545'); const rpcUrl = `http://localhost:${anvilPort}`; - const myDebugConfig = debugConfig( - debugConfigName, - file, - contractName, - methodSignature, - stopAtFirstOpcode, - showSourcemaps, - rpcUrl - ); - const autobuild = getConfigValue('autobuild', true); - if (autobuild) { const build = forgeBuildTask(activeTextEditor.document.uri.fsPath); const buildExecution = await vscode.tasks.executeTask(build); try { await completed(buildExecution); - const session = await vscode.debug.startDebugging( - workspaceFolder, - myDebugConfig - ); } catch (e) { vscode.window.showErrorMessage('Failed to build project.'); } - } else { - const session = await vscode.debug.startDebugging( - workspaceFolder, - myDebugConfig - ); } + const myFoundryRoot = await foundryRoot(activeTextEditor.document.uri.fsPath); + const buildInfo = await loadBuildInfo(activeTextEditor.document.uri.fsPath); + const myDebugConfig = debugConfig( + debugConfigName, + file, + contractName, + methodSignature, + stopAtFirstOpcode, + showSourcemaps, + rpcUrl, + buildInfo, + myFoundryRoot + ); + const session = await vscode.debug.startDebugging( + workspaceFolder, + myDebugConfig + ); } function completed(tastkExecution: vscode.TaskExecution): Promise { @@ -108,7 +104,9 @@ function debugConfig( methodSignature: string, stopAtFirstOpcode: boolean, showSourcemaps: boolean, - rpcUrl: string + rpcUrl: string, + buildInfo: string, + clientMount: string ) { return { name: name, @@ -119,7 +117,9 @@ function debugConfig( methodSignature: methodSignature, stopAtFirstOpcode: stopAtFirstOpcode, showSourcemaps: showSourcemaps, - rpcUrl: rpcUrl + rpcUrl: rpcUrl, + buildInfo: buildInfo, + clientMount: clientMount, }; } @@ -151,3 +151,47 @@ function forgeBuildTask(file: string) { task.presentationOptions.reveal = vscode.TaskRevealKind.Always; return task; } + + +async function loadBuildInfo(file: string) : Promise { + const root = await foundryRoot(file); + const buildInfo = await forgeBuildInfo(root); + return buildInfo[0]; +} + +async function foundryRoot(file: string) { + // Find the root of the project, which is the directory containing the foundry.toml file + let root = file; + let stat; + try { + stat = await vscode.workspace.fs.stat(vscode.Uri.file(`${root}/foundry.toml`)); + } catch (e) { + stat = false; + } + while (!stat) { + const lastSlash = root.lastIndexOf('/'); + if (lastSlash === -1) { + throw new Error('Could not find foundry.toml'); + } + root = root.substring(0, lastSlash); + try { + stat = await vscode.workspace.fs.stat(vscode.Uri.file(`${root}/foundry.toml`)); + } catch (e) { + stat = false; + } + } + return root; +} + +async function forgeBuildInfo(root: string) : Promise { + // Return the string contents of all build-info files. The files are stored under out/build-info/$hash.json + // TODO: Ordering of files. Should we sort by timestamp? + const buildInfoDir = `${root}/out/build-info`; + const buildInfoFiles = (await vscode.workspace.fs.readDirectory(vscode.Uri.file(buildInfoDir))).filter(([file, type]) => { + return type === vscode.FileType.File && file.endsWith('.json'); + }); + return Promise.all(buildInfoFiles.flatMap(async ([file, _type]) => { + const buildInfo = await vscode.workspace.fs.readFile(vscode.Uri.file(`${buildInfoDir}/${file}`)); + return buildInfo.toString(); + })); +} From 67618a4b549743cd6b952376f6fc8dc3bdf3ad7d Mon Sep 17 00:00:00 2001 From: Raoul Schaffranek Date: Wed, 22 May 2024 13:53:04 +0200 Subject: [PATCH 2/8] Reordered extension settings. --- package.json | 86 ++++++++++++++++++++++++++++++---------------------- 1 file changed, 50 insertions(+), 36 deletions(-) diff --git a/package.json b/package.json index 7dee4c6..fedcc7f 100644 --- a/package.json +++ b/package.json @@ -43,75 +43,89 @@ "configuration": { "title": "Simbolik: Solidity Debugger", "properties": { - "simbolik.server": { - "type": "string", - "default": "ws://localhost:6789", - "description": "The websocket URL where the simbolik server is listening." - }, "simbolik.api-key": { "type": "string", "default": "valid-api-key", - "description": "The API key to authenticate with the simbolik server." + "description": "The API key to authenticate with the simbolik server.", + "order": 0 }, - "simbolik.bmc-depth": { - "type": "integer", - "description": "Signals the backend that it should only unroll loops up to this depth. This parameter is only relevant for symbolic execution backends.", - "default": 3 + "simbolik.server": { + "type": "string", + "default": "ws://beta.simbolik.runtimeverification.com:3000", + "description": "The websocket URL where the simbolik server is listening. Do not change this unless you are running your own simbolik server.", + "order": 1 }, - "simbolik.stop-at-first-opcode": { + "simbolik.forge-path": { + "type": "string", + "default": "forge", + "description": "The path to the forge executable. Simbolik uses forge to compile the smart contracts. If forge is not in the PATH, you can set the path here.", + "order": 2 + }, + "simbolik.autobuild": { "type": "boolean", - "description": "If set to true, the debugger will stop at the first opcode. Otherwise it will stop at the function entry.", - "default": true + "default": true, + "description": "If set to true, the debugger will automatically build the project before starting the debugger.", + "order": 3 }, - "simbolik.show-sourcemaps": { + "simbolik.incremental-build": { "type": "boolean", "default": false, - "description": "If set to true, the debugger will include sourcemaps in the disassembly view. This is useful when debugging sourcemaps." + "description": "If autobuild is eanbled and incremental-build is set to true, the debugger will use incremental builds. Notice, that the support for incremental builds is experimental and sometimes leads to unexpected behavior.", + "order": 4 + }, + "simbolik.stop-at-first-opcode": { + "type": "boolean", + "description": "If set to true, the debugger will stop at the first opcode. Otherwise it will stop at the function entry. Disabling this option is experimental and may lead to unexpected behavior.", + "default": true, + "order": 5 }, "simbolik.anvil-port": { "type": "integer", "default": 8545, - "description": "The port where the Anvil server is listening. If anvil-autostart is set to true, the debugger passes this port to the anvil server." + "description": "The port where the Anvil server is listening. If anvil-autostart is set to true, the debugger passes this port to the anvil server. This options is only used when you're running your own simbolik server.", + "order": 6 }, "simbolik.simbolik-autostart": { "type": "boolean", - "default": true, - "description": "If set to true, the debugger will start the simbolik server automatically with VSCode." + "default": false, + "description": "If set to true, the debugger will start the simbolik server automatically with VSCode. This option can only be used if you have the simbolik server installed on your machine.", + "order": 7 }, "simbolik.simbolik-path": { "type": "string", "default": "simbolik", - "description": "The path to the simbolik executable. If simbolik-autostart is set to true, the debugger will start the simbolik server with this executable. If simbolik is not in the PATH, you can set the path here." + "description": "The path to the simbolik executable. If simbolik-autostart is set to true, the debugger will start the simbolik server with this executable. If simbolik is not in the PATH, you can set the path here.", + "order": 8 }, "simbolik.anvil-autostart": { "type": "boolean", - "default": true, - "description": "If set to true, the debugger will start the anvil server automatically with VSCode." + "default": false, + "description": "If set to true, the debugger will start the anvil server for every debugging session. Only set this to true if you are running your own simbolik server.", + "order": 9 }, "simbolik.anvil-path": { "type": "string", "default": "anvil", - "description": "The path to the anvil executable. If anvil-autostart is set to true, the debugger will start the anvil server with this executable. If anvil is not in the PATH, you can set the path here." - }, - "simbolik.forge-path": { - "type": "string", - "default": "forge", - "description": "The path to the forge executable. Simbolik uses forge to compile the smart contracts. If forge is not in the PATH, you can set the path here." - }, - "simbolik.autobuild": { - "type": "boolean", - "default": true, - "description": "If set to true, the debugger will automatically build the project before starting the debugger." + "description": "The path to the anvil executable. If anvil-autostart is set to true, the debugger will start the anvil server with this executable. If anvil is not in the PATH, you can set the path here.", + "order": 10 }, - "simbolik.incremental-build": { + "simbolik.enable-parameters": { "type": "boolean", "default": false, - "description": "If autobuild is eanbled and incremental-build is set to true, the debugger will use incremental builds. Notice, that the support for incremental builds is experimental and sometimes leads to unexpected sourcemaps." + "description": "If set to true, the debugger will show a debug button above functions with parameters. Notice, that this requires a backend that supports parameter debugging. The default Foundry backend does not support parameter debugging.", + "order": 11 }, - "simbolik.enable-parameters": { + "simbolik.show-sourcemaps": { "type": "boolean", "default": false, - "description": "If set to true, the debugger will show a debug button above functions with parameters. Notice, that this requires a backend that supports parameter debugging. The default Foundry backend does not support parameter debugging." + "description": "If set to true, the debugger will include sourcemaps in the disassembly view. This is useful when debugging sourcemaps.", + "order": 12 + }, + "simbolik.bmc-depth": { + "type": "integer", + "description": "Signals the backend that it should only unroll loops up to this depth. This parameter is only relevant for symbolic execution backends.", + "default": 3, + "order": 13 } } }, From 46f23a6e25c91e9fa603ddd6b72f2bfeec1ce836 Mon Sep 17 00:00:00 2001 From: Raoul Schaffranek Date: Thu, 23 May 2024 13:15:32 +0200 Subject: [PATCH 3/8] Use API key from extension settings --- src/DebugAdapter.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/DebugAdapter.ts b/src/DebugAdapter.ts index 80e7b97..00acc04 100644 --- a/src/DebugAdapter.ts +++ b/src/DebugAdapter.ts @@ -76,7 +76,8 @@ class WebsocketDebugAdapter implements vscode.DebugAdapter { onDidSendMessage = this._onDidSendMessage.event; handleMessage(message: vscode.DebugProtocolMessage): void { - const messageWithApiKey : vscode.DebugProtocolMessage & WithApiKey = Object.assign({}, message, {apiKey: "valid-api-key"}); + const apiKey = getConfigValue('apiKey', ''); + const messageWithApiKey : vscode.DebugProtocolMessage & WithApiKey = Object.assign({}, message, {apiKey}); this.websocket.send(JSON.stringify(messageWithApiKey)); } From 5f423a581da8745579b35a3490260951f5dc4ab4 Mon Sep 17 00:00:00 2001 From: Raoul Schaffranek Date: Thu, 23 May 2024 13:16:02 +0200 Subject: [PATCH 4/8] Error message for failed API key validation --- src/extension.ts | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/extension.ts b/src/extension.ts index ccad50a..d4deb56 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -66,6 +66,28 @@ export function activate(context: vscode.ExtensionContext) { supervisor.anvilTerminate(); outputChannel.info(`Debug session ended: ${session.id}`); }); + + vscode.debug.onDidReceiveDebugSessionCustomEvent(async event => { + if (event.event === 'api-key-validation-failed') { + + const action = await vscode.window.showErrorMessage( + 'API key validation failed', + 'Open Settings', + 'Learn More' + ); + if (action === 'Open Settings') { + vscode.commands.executeCommand( + 'workbench.action.openSettings', + 'simbolik.apiKey' + ); + } + if (action === 'Learn More') { + vscode.env.openExternal(vscode.Uri.parse('https://simbolik.runtimeverification.com')); + } + + } + console.log(event); + }); } // This method is called when your extension is deactivated From d03b2afd283ab4ac54afa76a72c5f76178d43b63 Mon Sep 17 00:00:00 2001 From: lisandrasilva Date: Thu, 23 May 2024 14:50:28 +0100 Subject: [PATCH 5/8] Fixed api-key config value --- src/DebugAdapter.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/DebugAdapter.ts b/src/DebugAdapter.ts index 00acc04..3920a11 100644 --- a/src/DebugAdapter.ts +++ b/src/DebugAdapter.ts @@ -76,7 +76,7 @@ class WebsocketDebugAdapter implements vscode.DebugAdapter { onDidSendMessage = this._onDidSendMessage.event; handleMessage(message: vscode.DebugProtocolMessage): void { - const apiKey = getConfigValue('apiKey', ''); + const apiKey = getConfigValue('api-key', ''); const messageWithApiKey : vscode.DebugProtocolMessage & WithApiKey = Object.assign({}, message, {apiKey}); this.websocket.send(JSON.stringify(messageWithApiKey)); } From 3e38817e4d2587b873b523a154ecce7d409fbf53 Mon Sep 17 00:00:00 2001 From: lisandrasilva Date: Thu, 23 May 2024 16:03:09 +0100 Subject: [PATCH 6/8] Added parallel debugging sessions error message --- src/extension.ts | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/extension.ts b/src/extension.ts index d4deb56..628e10b 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -69,7 +69,6 @@ export function activate(context: vscode.ExtensionContext) { vscode.debug.onDidReceiveDebugSessionCustomEvent(async event => { if (event.event === 'api-key-validation-failed') { - const action = await vscode.window.showErrorMessage( 'API key validation failed', 'Open Settings', @@ -82,9 +81,15 @@ export function activate(context: vscode.ExtensionContext) { ); } if (action === 'Learn More') { - vscode.env.openExternal(vscode.Uri.parse('https://simbolik.runtimeverification.com')); + vscode.env.openExternal( + vscode.Uri.parse('https://simbolik.runtimeverification.com') + ); } - + } + if (event.event === 'api-key-sessions-limit-exceeded') { + const action = await vscode.window.showErrorMessage( + 'Too many debugging sessions running in parallel' + ); } console.log(event); }); From 02eeee25df8f6d4c992bfb209eba03059f9a5ebe Mon Sep 17 00:00:00 2001 From: Raoul Schaffranek Date: Sat, 25 May 2024 12:01:18 +0200 Subject: [PATCH 7/8] Add clientVersion to DAP messages --- src/DebugAdapter.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/DebugAdapter.ts b/src/DebugAdapter.ts index 00acc04..56ece50 100644 --- a/src/DebugAdapter.ts +++ b/src/DebugAdapter.ts @@ -61,6 +61,9 @@ function retry( } type WithApiKey = { apiKey: string }; +type WithClientVersion = { clientVersion: string }; + +type DebugProtocolMessage = vscode.DebugProtocolMessage & WithApiKey & WithClientVersion; class WebsocketDebugAdapter implements vscode.DebugAdapter { _onDidSendMessage = new vscode.EventEmitter(); @@ -77,7 +80,8 @@ class WebsocketDebugAdapter implements vscode.DebugAdapter { handleMessage(message: vscode.DebugProtocolMessage): void { const apiKey = getConfigValue('apiKey', ''); - const messageWithApiKey : vscode.DebugProtocolMessage & WithApiKey = Object.assign({}, message, {apiKey}); + const clientVersion = vscode.extensions.getExtension('simbolik.simbolik')?.packageJSON.version; + const messageWithApiKey : DebugProtocolMessage = Object.assign({}, message, {apiKey, clientVersion}); this.websocket.send(JSON.stringify(messageWithApiKey)); } From 32216ee044a1a08adf91b54eee079d2e40013588 Mon Sep 17 00:00:00 2001 From: Raoul Schaffranek Date: Sat, 25 May 2024 17:12:32 +0200 Subject: [PATCH 8/8] Fix buildinfo parsing --- .github/workflows/release.yml | 59 ++++++++++++++++ LICENSE.md | 29 ++++++++ package-lock.json | 128 ++++++++++++++++++---------------- package.json | 5 +- src/foundry.ts | 113 ++++++++++++++++++++++++++++++ src/startDebugging.ts | 78 +-------------------- tsconfig.json | 1 + 7 files changed, 274 insertions(+), 139 deletions(-) create mode 100644 .github/workflows/release.yml create mode 100644 LICENSE.md create mode 100644 src/foundry.ts diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..fd1650c --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,59 @@ +on: workflow_dispatch + +name: Publish VS Code Extension to Microsoft Marketplace and Open VSX +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + - name: 'Install nodejs 18' + uses: actions/setup-node@v2 + with: + node-version: '18' + # Run install dependencies + - name: Install dependencies + run: | + corepack enable + yarn install + # Run tests + - name: Build + run: yarn run build + - name: Get current package version + id: package_version + uses: martinbeentjes/npm-get-version-action@v1.1.0 + - name: Check version is mentioned in Changelog + uses: mindsers/changelog-reader-action@v2.0.0 + with: + version: ${{ steps.package_version.outputs.current-version }} + path: 'CHANGELOG.md' + - name: Create a Release + id: create_release + uses: actions/create-release@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + tag_name : ${{ steps.package_version.outputs.current-version}} + release_name: ${{ steps.package_version.outputs.current-version}} + body: Publish ${{ steps.package_version.outputs.current-version}} + - name: Publish extension to Visual Studio Marketplace + id: create_vsix + uses: HaaLeo/publish-vscode-extension@v1 + with: + pat: ${{ secrets.VS_MARKETPLACE_TOKEN }} + registryUrl: https://marketplace.visualstudio.com + - name: Publish extension to Open VSX + uses: HaaLeo/publish-vscode-extension@v1 + with: + pat: ${{ secrets.OPEN_VSX_TOKEN }} + extensionFile: ${{ steps.create_vsix.outputs.vsixPath }} + packagePath: '' + - name: Attach vsix to release + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ steps.create_release.outputs.upload_url }} + asset_path: ${{ steps.create_vsix.outputs.vsixPath}} + asset_name: ${{ steps.create_vsix.outputs.vsixPath}} + asset_content_type: application/vsix \ No newline at end of file diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..19363d0 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,29 @@ +BSD 3-Clause License + +Copyright (c) 2023-2024, Runtime Verification Inc. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 1d85a90..be64ba0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,15 +1,16 @@ { "name": "simbolik", - "version": "1.1.0-beta", + "version": "2.0.0-beta", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "simbolik", - "version": "1.1.0-beta", + "version": "2.0.0-beta", "dependencies": { "@solidity-parser/parser": "^0.18.0", "@types/ws": "^8.5.10", + "toml": "^3.0.0", "ws": "^8.16.0" }, "devDependencies": { @@ -34,12 +35,12 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.24.2", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.2.tgz", - "integrity": "sha512-y5+tLQyV8pg3fsiln67BVLD1P13Eg4lh5RW9mF0zUuvLrv9uIQ4MCL+CRT+FTsBlBjcIan6PGsLcBN0m3ClUyQ==", + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.6.tgz", + "integrity": "sha512-ZJhac6FkEd1yhG2AHOmfcXG4ceoLltoCVJjN5XsWN9BifBQr+cHJbWi0h68HZuSORq+3WtJ2z0hwF2NG1b5kcA==", "dev": true, "dependencies": { - "@babel/highlight": "^7.24.2", + "@babel/highlight": "^7.24.6", "picocolors": "^1.0.0" }, "engines": { @@ -47,21 +48,21 @@ } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", - "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.6.tgz", + "integrity": "sha512-4yA7s865JHaqUdRbnaxarZREuPTHrjpDT+pXoAZ1yhyo6uFnIEpS8VMu16siFOHDpZNKYv5BObhsB//ycbICyw==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/highlight": { - "version": "7.24.2", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.2.tgz", - "integrity": "sha512-Yac1ao4flkTxTteCDZLEvdxg2fZfz1v8M4QpaGypq/WPDqg3ijHYbDfs+LG5hvzSoqaSZ9/Z9lKSP3CjZjv+pA==", + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.6.tgz", + "integrity": "sha512-2YnuOp4HAk2BsBrJJvYCbItHx0zWscI1C3zgWkz+wDyD9I7GIVrfnLyrR4Y1VR+7p+chAEcrgRQYZAGIKMV7vQ==", "dev": true, "dependencies": { - "@babel/helper-validator-identifier": "^7.22.20", + "@babel/helper-validator-identifier": "^7.24.6", "chalk": "^2.4.2", "js-tokens": "^4.0.0", "picocolors": "^1.0.0" @@ -807,9 +808,9 @@ "dev": true }, "node_modules/@types/vscode": { - "version": "1.88.0", - "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.88.0.tgz", - "integrity": "sha512-rWY+Bs6j/f1lvr8jqZTyp5arRMfovdxolcqGi+//+cPDOh8SBvzXH90e7BiSXct5HJ9HGW6jATchbRTpTJpEkw==", + "version": "1.89.0", + "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.89.0.tgz", + "integrity": "sha512-TMfGKLSVxfGfoO8JfIE/neZqv7QLwS4nwPwL/NwMvxtAY2230H2I4Z5xx6836pmJvMAzqooRQ4pmLm7RUicP3A==", "dev": true }, "node_modules/@types/ws": { @@ -1015,9 +1016,9 @@ "dev": true }, "node_modules/@vscode/test-electron": { - "version": "2.3.9", - "resolved": "https://registry.npmjs.org/@vscode/test-electron/-/test-electron-2.3.9.tgz", - "integrity": "sha512-z3eiChaCQXMqBnk2aHHSEkobmC2VRalFQN0ApOAtydL172zXGxTwGrRtviT5HnUB+Q+G3vtEYFtuQkYqBzYgMA==", + "version": "2.3.10", + "resolved": "https://registry.npmjs.org/@vscode/test-electron/-/test-electron-2.3.10.tgz", + "integrity": "sha512-FxMqrvUm6a8S5tP4CymNJ40e6kD+wUTWTc6K32U629yrCCa+kl/rmpkC2gKpN4F4zjg1r+0Hnk9sl0+N2atsYA==", "dev": true, "dependencies": { "http-proxy-agent": "^4.0.1", @@ -1399,12 +1400,12 @@ } }, "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, "dependencies": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" }, "engines": { "node": ">=8" @@ -1492,9 +1493,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001613", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001613.tgz", - "integrity": "sha512-BNjJULJfOONQERivfxte7alLfeLW4QnwHvNW4wEcLEbXfV6VSCYvr+REbf2Sojv8tC1THpjPXBxWgDbq4NtLWg==", + "version": "1.0.30001621", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001621.tgz", + "integrity": "sha512-+NLXZiviFFKX0fk8Piwv3PfLPGtRqJeq2TiNoUff/qB5KJgwecJTvCXDpmlyP/eCI/GUEmp/h/y5j0yckiiZrA==", "dev": true, "funding": [ { @@ -1757,9 +1758,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.4.750", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.750.tgz", - "integrity": "sha512-9ItEpeu15hW5m8jKdriL+BQrgwDTXEL9pn4SkillWFu73ZNNNQ2BKKLS+ZHv2vC9UkNhosAeyfxOf/5OSeTCPA==", + "version": "1.4.783", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.783.tgz", + "integrity": "sha512-bT0jEz/Xz1fahQpbZ1D7LgmPYZ3iHVY39NcWWro1+hA2IvjiPeaXtfSqrQ+nXjApMvQRE2ASt1itSLRrebHMRQ==", "dev": true, "peer": true }, @@ -1770,9 +1771,9 @@ "dev": true }, "node_modules/enhanced-resolve": { - "version": "5.16.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.16.0.tgz", - "integrity": "sha512-O+QWCviPNSSLAD9Ucn8Awv+poAkqn3T1XY5/N7kR7rQO9yfSGWkYZDwpJ+iKF7B8rxaQKWngSqACpgzeapSyoA==", + "version": "5.16.1", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.16.1.tgz", + "integrity": "sha512-4U5pNsuDl0EhuZpq46M5xPslstkviJuhrdobaRDBk2Jy2KO37FDAJl4lb2KlNabxT0m4MTK2UHNrsAcphE8nyw==", "dev": true, "dependencies": { "graceful-fs": "^4.2.4", @@ -1792,9 +1793,9 @@ } }, "node_modules/es-module-lexer": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.5.2.tgz", - "integrity": "sha512-l60ETUTmLqbVbVHv1J4/qj+M8nq7AwMzEcg3kmJDt9dCNrTk+yHcYFf/Kw75pMDwd9mPcIGCG5LcS20SxYRzFA==", + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.5.3.tgz", + "integrity": "sha512-i1gCgmR9dCl6Vil6UKPI/trA69s08g/syhiDK9TG0Nf1RJjjFI+AzoWW7sPufzkgYAn861skuCwJa0pIIHYxvg==", "dev": true, "peer": true }, @@ -2298,9 +2299,9 @@ } }, "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, "dependencies": { "to-regex-range": "^5.0.1" @@ -2783,6 +2784,7 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", "dev": true, "dependencies": { "once": "^1.3.0", @@ -3216,12 +3218,12 @@ } }, "node_modules/micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.7.tgz", + "integrity": "sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==", "dev": true, "dependencies": { - "braces": "^3.0.2", + "braces": "^3.0.3", "picomatch": "^2.3.1" }, "engines": { @@ -3632,9 +3634,9 @@ } }, "node_modules/picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz", + "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==", "dev": true }, "node_modules/picomatch": { @@ -4097,13 +4099,10 @@ } }, "node_modules/semver": { - "version": "7.6.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", - "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz", + "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==", "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, "bin": { "semver": "bin/semver.js" }, @@ -4219,9 +4218,9 @@ } }, "node_modules/spdx-license-ids": { - "version": "3.0.17", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.17.tgz", - "integrity": "sha512-sh8PWc/ftMqAAdFiBu6Fy6JUOYjqDJBJvIhpfDMyHrr0Rbp5liZqd4TjtQ/RgfLjKFZb+LMx5hpml5qOWy0qvg==", + "version": "3.0.18", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.18.tgz", + "integrity": "sha512-xxRs31BqRYHwiMzudOrpSiHtZ8i/GeionCBDSilhYRj+9gIcI8wCZTlXZKu9vZIVqViP3dcp9qE5G6AlIaD+TQ==", "dev": true }, "node_modules/string_decoder": { @@ -4348,9 +4347,9 @@ } }, "node_modules/terser": { - "version": "5.30.4", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.30.4.tgz", - "integrity": "sha512-xRdd0v64a8mFK9bnsKVdoNP9GQIKUAaJPTaqEQDL4w/J8WaW4sWXXoMZ+6SimPkfT5bElreXf8m9HnmPc3E1BQ==", + "version": "5.31.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.31.0.tgz", + "integrity": "sha512-Q1JFAoUKE5IMfI4Z/lkE/E6+SwgzO+x4tq4v1AyBLRj8VSYvRO6A/rQrPg1yud4g0En9EKI1TvFRF2tQFcoUkg==", "dev": true, "peer": true, "dependencies": { @@ -4447,6 +4446,11 @@ "node": ">=8.0" } }, + "node_modules/toml": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/toml/-/toml-3.0.0.tgz", + "integrity": "sha512-y/mWCZinnvxjTKYhJ+pYxwD0mRLVvOtdS2Awbgxln6iEnt4rk0yBxeSBHkGJcPucRiG0e55mwWp+g/05rsrd6w==" + }, "node_modules/trim-newlines": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-3.0.1.tgz", @@ -4540,9 +4544,9 @@ "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" }, "node_modules/update-browserslist-db": { - "version": "1.0.13", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", - "integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==", + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.16.tgz", + "integrity": "sha512-KVbTxlBYlckhF5wgfyZXTWnMn7MMZjMu9XG8bPlliUOP9ThaF4QnhP8qrjrH7DRzHfSk0oQv1wToW+iA5GajEQ==", "dev": true, "funding": [ { @@ -4560,8 +4564,8 @@ ], "peer": true, "dependencies": { - "escalade": "^3.1.1", - "picocolors": "^1.0.0" + "escalade": "^3.1.2", + "picocolors": "^1.0.1" }, "bin": { "update-browserslist-db": "cli.js" diff --git a/package.json b/package.json index fedcc7f..203123d 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ }, "publisher": "runtimeverification", "description": "Advanced Solidity and EVM Debugger", - "version": "1.1.0-beta", + "version": "2.0.0-beta", "engines": { "vscode": "^1.79.0" }, @@ -209,6 +209,7 @@ "dependencies": { "@solidity-parser/parser": "^0.18.0", "@types/ws": "^8.5.10", - "ws": "^8.16.0" + "ws": "^8.16.0", + "toml": "^3.0.0" } } diff --git a/src/foundry.ts b/src/foundry.ts new file mode 100644 index 0000000..58128f6 --- /dev/null +++ b/src/foundry.ts @@ -0,0 +1,113 @@ +import * as vscode from 'vscode'; +import {getConfigValue} from './utils'; +import {parse as parseToml} from 'toml'; + + +export +function forgeBuildTask(file: string) { + const incrementalBuild = getConfigValue('incremental-build', false); + const forgePath = getConfigValue('forge-path', 'forge'); + const cwd = file.substring(0, file.lastIndexOf('/')); + const task = new vscode.Task( + { + label: 'forge build', + type: 'shell', + }, + vscode.TaskScope.Workspace, + 'forge', + 'simbolik', + new vscode.ShellExecution(forgePath, ['build'], { + cwd, + env: { + 'FOUNDRY_OPTIMIZER': 'false', + 'FOUNDRY_BUILD_INFO': 'true', + 'FOUNDRY_EXTRA_OUTPUT': '["storageLayout", "evm.bytecode.generatedSources"]', + 'FOUNDRY_BYTECODE_HASH': 'ipfs', + 'FOUNDRY_CBOR_METADATA': 'true', + 'FOUNDRY_CACHE': incrementalBuild ? 'true' : 'false', + } + }) + ); + task.isBackground = true; + task.presentationOptions.reveal = vscode.TaskRevealKind.Always; + return task; +} + +export +async function loadBuildInfo(file: string): Promise { + const root = await foundryRoot(file); + const buildInfo = await forgeBuildInfo(root); + return buildInfo; +} + +export +async function foundryRoot(file: string) { + // Find the root of the project, which is the directory containing the foundry.toml file + let root = file; + let stat; + try { + stat = await vscode.workspace.fs.stat(vscode.Uri.file(`${root}/foundry.toml`)); + } catch (e) { + stat = false; + } + while (!stat) { + const lastSlash = root.lastIndexOf('/'); + if (lastSlash === -1) { + throw new Error('Could not find foundry.toml'); + } + root = root.substring(0, lastSlash); + try { + stat = await vscode.workspace.fs.stat(vscode.Uri.file(`${root}/foundry.toml`)); + } catch (e) { + stat = false; + } + } + return root; +} + +export +type FoundryConfig = { 'profile'?: { [profile: string]: { [key: string]: string } } }; + +export +async function foundryConfig(root: string): Promise { + const configPath = `${root}/foundry.toml`; + const config = await vscode.workspace.fs.readFile(vscode.Uri.file(configPath)); + return parseToml(config.toString()); +} + +async function forgeBuildInfo(root: string): Promise { + 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`; + + // Get list of build-info files + const files = await vscode.workspace.fs.readDirectory(vscode.Uri.file(buildInfoDir)); + const buildInfoFiles = files.filter(([file, type]) => type === vscode.FileType.File && file.endsWith('.json')); + + if (buildInfoFiles.length === 0) { + vscode.window.showErrorMessage('No build-info files found'); + return ''; + } + + // Retrieve file stats and sort by creation timestamp + const sortedFiles = await getSortedFilesByCreationTime(buildInfoDir, buildInfoFiles); + + // Read the youngest build-info file + const youngestBuildInfo = await vscode.workspace.fs.readFile(sortedFiles[0].uri); + return youngestBuildInfo.toString(); +} + +async function getSortedFilesByCreationTime(buildInfoDir: string, 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 fileStat = await vscode.workspace.fs.stat(fileUri); + return { file, uri: fileUri, ctime: fileStat.ctime }; + }) + ); + + // Sort files by creation time (ctime) + return filesWithStats.sort((a, b) => b.ctime - a.ctime); +} \ No newline at end of file diff --git a/src/startDebugging.ts b/src/startDebugging.ts index 745e8c7..f8589fd 100644 --- a/src/startDebugging.ts +++ b/src/startDebugging.ts @@ -5,8 +5,9 @@ import { VariableDeclaration, } from '@solidity-parser/parser/dist/src/ast-types'; import * as vscode from 'vscode'; -import {getConfigValue} from './utils'; -import {Supervisor} from './supevervisor'; +import { getConfigValue } from './utils'; +import { Supervisor } from './supevervisor'; +import { forgeBuildTask, foundryRoot, loadBuildInfo } from './foundry'; export async function startDebugging( this: Supervisor, @@ -128,76 +129,3 @@ function debugConfig( clientMount: clientMount, }; } - -function forgeBuildTask(file: string) { - const incrementalBuild = getConfigValue('incremental-build', false); - const forgePath = getConfigValue('forge-path', 'forge'); - const cwd = file.substring(0, file.lastIndexOf('/')); - const task = new vscode.Task( - { - label: 'forge build', - type: 'shell', - }, - vscode.TaskScope.Workspace, - 'forge', - 'simbolik', - new vscode.ShellExecution(forgePath, ['build'], { - cwd, - env: { - 'FOUNDRY_OPTIMIZER': 'false', - 'FOUNDRY_BUILD_INFO': 'true', - 'FOUNDRY_EXTRA_OUTPUT': '["storageLayout", "evm.bytecode.generatedSources"]', - 'FOUNDRY_BYTECODE_HASH': 'ipfs', - 'FOUNDRY_CBOR_METADATA': 'true', - 'FOUNDRY_CACHE': incrementalBuild ? 'true' : 'false', - } - }) - ); - task.isBackground = true; - task.presentationOptions.reveal = vscode.TaskRevealKind.Always; - return task; -} - - -async function loadBuildInfo(file: string) : Promise { - const root = await foundryRoot(file); - const buildInfo = await forgeBuildInfo(root); - return buildInfo[0]; -} - -async function foundryRoot(file: string) { - // Find the root of the project, which is the directory containing the foundry.toml file - let root = file; - let stat; - try { - stat = await vscode.workspace.fs.stat(vscode.Uri.file(`${root}/foundry.toml`)); - } catch (e) { - stat = false; - } - while (!stat) { - const lastSlash = root.lastIndexOf('/'); - if (lastSlash === -1) { - throw new Error('Could not find foundry.toml'); - } - root = root.substring(0, lastSlash); - try { - stat = await vscode.workspace.fs.stat(vscode.Uri.file(`${root}/foundry.toml`)); - } catch (e) { - stat = false; - } - } - return root; -} - -async function forgeBuildInfo(root: string) : Promise { - // Return the string contents of all build-info files. The files are stored under out/build-info/$hash.json - // TODO: Ordering of files. Should we sort by timestamp? - const buildInfoDir = `${root}/out/build-info`; - const buildInfoFiles = (await vscode.workspace.fs.readDirectory(vscode.Uri.file(buildInfoDir))).filter(([file, type]) => { - return type === vscode.FileType.File && file.endsWith('.json'); - }); - return Promise.all(buildInfoFiles.flatMap(async ([file, _type]) => { - const buildInfo = await vscode.workspace.fs.readFile(vscode.Uri.file(`${buildInfoDir}/${file}`)); - return buildInfo.toString(); - })); -} diff --git a/tsconfig.json b/tsconfig.json index 262a284..bceb86a 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -15,5 +15,6 @@ "src/extension.web.ts", "src/DebugAdapter.web.ts", "sampleWorkspace", + "node_modules", ] }