diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 46b7fb8..175336d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,4 +20,5 @@ jobs: - run: npm run lint - run: npm run check-format - run: npm run compile + - run: npm run build - run: npm test diff --git a/.gitignore b/.gitignore index 483f85f..62460d6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ out dist +extension/dist node_modules .vscode-test/ npm-debug.log diff --git a/.vscode/launch.json b/.vscode/launch.json index 0585be0..7a4e2d0 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -6,7 +6,7 @@ "type": "extensionHost", "request": "launch", "args": [ - "--extensionDevelopmentPath=${workspaceFolder}", + "--extensionDevelopmentPath=${workspaceFolder}/extension", "${workspaceFolder}/sampleWorkspace" ], "outFiles": ["${workspaceFolder}/dist/**/*.js"], diff --git a/.vscodeignore b/.vscodeignore index 2d8c798..e1cdc20 100644 --- a/.vscodeignore +++ b/.vscodeignore @@ -3,6 +3,7 @@ .travis.yml appveyor.yml src/**/* +extension/src/**/* out/tests/**/* **/*.js.map build @@ -11,4 +12,5 @@ node_modules tsconfig.json out sampleWebWorkerWorkspace -sampleWorkspace \ No newline at end of file +sampleWorkspace +images \ No newline at end of file diff --git a/FEATURES.md b/FEATURES.md new file mode 100644 index 0000000..51036e2 --- /dev/null +++ b/FEATURES.md @@ -0,0 +1,75 @@ +# Features + +This document describes features that the AVM debugger supports. + +Screenshots and the exact features are based on the VS Code client. + +## View transaction groups being debugged + +Every execution starts with a top level transaction group. + +![A transaction group being debugged](images/transaction%20group.png) + +## Step into programs executions + +Both LogicSig and application programs associated with transactions can be stepped into. Source maps +are used to show the original source code. + +![A program being debugged](images/app%20call.png) + +## Step into inner transactions + +If an application spawns an inner transaction, the debugger can step into it as well. + +![An inner transaction group being debugged](images/inner%20transaction%20group.png) + +Additionally, the entire call stack can be seen, showing the depth of the inner transaction or inner +application being executed. Each frame of the call stack can be inspected to view the higher level +state. + +![Call stack](images/call%20stack.png) + +## Step-by-step debugging + +The debugger supports step into, over, out, and back. Yes, you can step back! + +## Breakpoint support + +Breakpoints can be set in program source files. The debugger will pause when code corresponding to a +breakpoint is about to be executed. Since multiple opcodes can be in a single line of source code, +breakpoints can be set on specific columns. + +![Breakpoints in program code](images/breakpoints.png) + +## Error reporting + +Execution errors will be reported by the debugger. Since any error stops the execution of a +transaction group, the debugger will not allow you to advance after an error. You can however step +backwards to inspect what happened prior to the error. + +![An error in the debugger](images/error.png) + +## Inspect program state + +The debugger allows you to inspect the state of the program being debugged. This includes the PC +(program counter), stack, and scratch space. + +![Inspecting program state](images/program%20state%20variables.png) + +Byte arrays can be displayed in a variety of formats, including base64, hex, and UTF-8. + +![Inspecting byte arrays in program state](images/program%20state%20variables%20bytes%20expanded.png) + +Additionally, specific values can be added to the watch list. + +Since values relative to the top of the stack are often important, negative indexing is supported to +look up values relative to the top of the stack. + +![Watched values](images/watch%20values.png) + +## Inspect application state + +The debugger also allows you to inspect and watch any available application state from the +execution. Such state includes application boxes, global, and local state. + +![Inspecting application state variables](images/app%20state%20variables%20expanded.png) diff --git a/README.md b/README.md new file mode 100644 index 0000000..4796364 --- /dev/null +++ b/README.md @@ -0,0 +1,29 @@ +# AVM Debugger + +## Summary + +This repo contains an AVM debugger which adheres to the [Debug Adapter Protocol](https://microsoft.github.io/debug-adapter-protocol/). +This protocol is used by a variety of clients, and this repo additionally +implements a basic client for VS Code. + +Unlike traditional debuggers, which typically execute code as it is being +debugged, this debugger operates on an execution trace. The trace is created by +the [algod simulate API](https://developer.algorand.org/docs/rest-apis/algod/#post-v2transactionssimulate). +This debugger is not responsible for compiling programs, assembling transaction groups, or executing +transactions/programs. It is only responsible for replaying the execution trace, which must already +exist. + +This code is based on the [`vscode-mock-debug`](https://github.com/microsoft/vscode-mock-debug) repo. + +## Features + +See [FEATURES.md](FEATURES.md) for a list of features this debugger supports. + +## Build and Run + +1. Clone the repo. +2. `npm i` to install dependencies. +3. Open the project folder in VS Code. +4. From the Run and Debug menu, run the `Extension` configuration to open the AVM Debug extension in another VS Code window. +5. In the new window, go to its Run and Debug menu to select and launch one of the existing configurations. +6. You are now in a debugging session of a transaction group. You can step through the transaction group, inspect variables, set breakpoints and more. See [FEATURES.md](FEATURES.md) for more details. diff --git a/extension/package.json b/extension/package.json new file mode 100644 index 0000000..5bde997 --- /dev/null +++ b/extension/package.json @@ -0,0 +1,118 @@ +{ + "name": "avm-debug-vscode-extension", + "displayName": "AVM Debug", + "version": "1.0.0", + "publisher": "algorand-vscode", + "description": "Debug extension for developing AVM transactions and smart contracts.", + "author": { + "name": "Algorand, llc" + }, + "license": "MIT", + "keywords": [ + "multi-root ready" + ], + "engines": { + "vscode": "^1.80.0" + }, + "categories": [ + "Debuggers" + ], + "private": true, + "repository": { + "type": "git", + "url": "https://github.com/algorand/avm-debugger.git" + }, + "bugs": { + "url": "https://github.com/algorand/avm-debugger/issues" + }, + "main": "dist/extension.js", + "browser": "dist/web-extension.js", + "activationEvents": [ + "workspaceContains:**/*.teal", + "onDebug", + "onDebugResolve:avm", + "onDebugDynamicConfigurations:avm" + ], + "workspaceTrust": { + "request": "never" + }, + "contributes": { + "languages": [ + { + "id": "teal", + "extensions": [ + ".teal" + ], + "configuration": "teal-language-configuration.json" + } + ], + "grammars": [ + { + "language": "teal", + "scopeName": "source.teal", + "path": "syntaxes/teal.tmLanguage.json" + } + ], + "breakpoints": [ + { + "language": "teal" + } + ], + "debuggers": [ + { + "type": "avm", + "languages": [ + "teal" + ], + "label": "AVM Debug", + "program": "../out/src/cli.js", + "runtime": "node", + "configurationAttributes": { + "launch": { + "properties": { + "simulateTraceFile": { + "type": "string", + "description": "Transaction group simulation response with execution trace.", + "default": "${workspaceFolder}/path/to/simulateTraceFile.json" + }, + "programSourcesDescriptionFile": { + "type": "string", + "description": "Description file for sources of programs appearing in transaction group.", + "default": "${workspaceFolder}/path/to/programSourcesDescriptionFile.json" + }, + "stopOnEntry": { + "type": "boolean", + "description": "Automatically stop after launch.", + "default": true + } + } + } + }, + "initialConfigurations": [ + { + "type": "avm", + "request": "launch", + "name": "Debug AVM Transactions", + "simulateTraceFile": "${workspaceFolder}/path/to/simulateTraceFile.json", + "programSourcesDescriptionFile": "${workspaceFolder}/path/to/programSourcesDescriptionFile.json", + "stopOnEntry": true + } + ], + "configurationSnippets": [ + { + "label": "AVM Debug", + "description": "A new configuration for replaying and debugging a Algorand transactions.", + "body": { + "type": "avm", + "request": "launch", + "name": "Debug AVM Transactions", + "simulateTraceFile": "^\"\\${workspaceFolder}/path/to/simulateTraceFile.json\"", + "programSourcesDescriptionFile": "^\"\\${workspaceFolder}/path/to/programSourcesDescriptionFile.json\"", + "stopOnEntry": true + } + } + ] + } + ] + } +} diff --git a/extension/src/activateAvmDebug.ts b/extension/src/activateAvmDebug.ts new file mode 100644 index 0000000..fc206b4 --- /dev/null +++ b/extension/src/activateAvmDebug.ts @@ -0,0 +1,29 @@ +'use strict'; + +import * as vscode from 'vscode'; +import { AvmDebugConfigProvider } from './configuration'; + +export function activateAvmDebug( + context: vscode.ExtensionContext, + factory: vscode.DebugAdapterDescriptorFactory, +) { + // register a configuration provider for 'avm' debug type + const provider = new AvmDebugConfigProvider(); + context.subscriptions.push( + vscode.debug.registerDebugConfigurationProvider('avm', provider), + ); + + context.subscriptions.push( + vscode.debug.registerDebugAdapterDescriptorFactory('avm', factory), + ); + + if (isDisposable(factory)) { + // https://vscode-docs.readthedocs.io/en/stable/extensions/patterns-and-principles/#events + // see events, by the end of subscription, call `dispose()` to release resource. + context.subscriptions.push(factory); + } +} + +function isDisposable(value: object): value is { dispose(): unknown } { + return 'dispose' in value; +} diff --git a/src/configuration.ts b/extension/src/configuration.ts similarity index 96% rename from src/configuration.ts rename to extension/src/configuration.ts index 9b7a6f7..415b606 100644 --- a/src/configuration.ts +++ b/extension/src/configuration.ts @@ -8,7 +8,7 @@ import { CancellationToken, } from 'vscode'; -export class TealDebugConfigProvider +export class AvmDebugConfigProvider implements vscode.DebugConfigurationProvider { /** diff --git a/extension/src/extension.ts b/extension/src/extension.ts new file mode 100644 index 0000000..3ba50b3 --- /dev/null +++ b/extension/src/extension.ts @@ -0,0 +1,25 @@ +'use strict'; + +import * as vscode from 'vscode'; +import { activateAvmDebug } from './activateAvmDebug'; +import { ServerDebugAdapterFactory } from './serverDescriptorFactory'; +import { InlineDebugAdapterFactory } from './internalDescriptorFactory'; + +const runMode: 'server' | 'inline' = 'inline'; + +export function activate(context: vscode.ExtensionContext) { + switch (runMode) { + case 'server': + // run the debug adapter as a server inside the extension and communicate via a socket + activateAvmDebug(context, new ServerDebugAdapterFactory()); + break; + + case 'inline': + // run the debug adapter inside the extension and directly talk to it + activateAvmDebug(context, new InlineDebugAdapterFactory()); + break; + } +} + +// eslint-disable-next-line @typescript-eslint/no-empty-function +export function deactivate() {} diff --git a/extension/src/fileAccessor.ts b/extension/src/fileAccessor.ts new file mode 100644 index 0000000..26e0998 --- /dev/null +++ b/extension/src/fileAccessor.ts @@ -0,0 +1,38 @@ +'use strict'; + +import * as vscode from 'vscode'; +import { FileAccessor } from '../../src'; + +export const workspaceFileAccessor: FileAccessor = { + isWindows: typeof process !== 'undefined' && process.platform === 'win32', + readFile(path: string): Promise { + const uri = pathToUri(path); + return thenableToPromise(vscode.workspace.fs.readFile(uri)); + }, + writeFile(path: string, contents: Uint8Array): Promise { + const uri = pathToUri(path); + return thenableToPromise(vscode.workspace.fs.writeFile(uri, contents)); + }, + basename(path: string): string { + const uri = pathToUri(path); + const lastSlash = uri.path.lastIndexOf('/'); + if (lastSlash === -1) { + return path; + } + return uri.path.substring(lastSlash + 1); + }, +}; + +function pathToUri(path: string) { + try { + return vscode.Uri.file(path); + } catch (e) { + return vscode.Uri.parse(path, true); + } +} + +function thenableToPromise(t: Thenable): Promise { + return new Promise((resolve, reject) => { + t.then(resolve, reject); + }); +} diff --git a/extension/src/internalDescriptorFactory.ts b/extension/src/internalDescriptorFactory.ts new file mode 100644 index 0000000..68e8640 --- /dev/null +++ b/extension/src/internalDescriptorFactory.ts @@ -0,0 +1,18 @@ +'use strict'; + +import * as vscode from 'vscode'; +import { ProviderResult } from 'vscode'; +import { AvmDebugSession } from '../../src'; +import { workspaceFileAccessor } from './fileAccessor'; + +export class InlineDebugAdapterFactory + implements vscode.DebugAdapterDescriptorFactory +{ + createDebugAdapterDescriptor( + _session: vscode.DebugSession, + ): ProviderResult { + return new vscode.DebugAdapterInlineImplementation( + new AvmDebugSession(workspaceFileAccessor), + ); + } +} diff --git a/extension/src/serverDescriptorFactory.ts b/extension/src/serverDescriptorFactory.ts new file mode 100644 index 0000000..4db1fde --- /dev/null +++ b/extension/src/serverDescriptorFactory.ts @@ -0,0 +1,36 @@ +'use strict'; + +import * as Net from 'net'; +import * as vscode from 'vscode'; +import { ProviderResult } from 'vscode'; +import { AvmDebugSession } from '../../src'; +import { workspaceFileAccessor } from './fileAccessor'; + +export class ServerDebugAdapterFactory + implements vscode.DebugAdapterDescriptorFactory +{ + private server?: Net.Server; + + createDebugAdapterDescriptor( + _session: vscode.DebugSession, + _executable: vscode.DebugAdapterExecutable | undefined, + ): ProviderResult { + if (!this.server) { + this.server = Net.createServer((socket) => { + const session = new AvmDebugSession(workspaceFileAccessor); + session.setRunAsServer(true); + session.start(socket as NodeJS.ReadableStream, socket); + }).listen(0); + } + + return new vscode.DebugAdapterServer( + (this.server.address() as Net.AddressInfo).port, + ); + } + + dispose() { + if (this.server) { + this.server.close(); + } + } +} diff --git a/extension/src/web-extension.ts b/extension/src/web-extension.ts new file mode 100644 index 0000000..3fddbef --- /dev/null +++ b/extension/src/web-extension.ts @@ -0,0 +1,12 @@ +import * as vscode from 'vscode'; +import { activateAvmDebug } from './activateAvmDebug'; +import { InlineDebugAdapterFactory } from './internalDescriptorFactory'; + +export function activate(context: vscode.ExtensionContext) { + // Inline is the only supported mode for running the debug adapter in the browser. + activateAvmDebug(context, new InlineDebugAdapterFactory()); +} + +export function deactivate() { + // nothing to do +} diff --git a/extension/syntaxes/teal.tmLanguage.json b/extension/syntaxes/teal.tmLanguage.json new file mode 100644 index 0000000..459df83 --- /dev/null +++ b/extension/syntaxes/teal.tmLanguage.json @@ -0,0 +1,128 @@ +{ + "$schema": "https://raw.githubusercontent.com/martinring/tmlanguage/master/tmlanguage.json", + "name": "Algorand TEAL", + "patterns": [ + { + "include": "#invalid" + }, + { + "include": "#comments" + }, + { + "include": "#strings" + }, + { + "include": "#literals" + }, + { + "include": "#labels" + }, + { + "include": "#keywords" + }, + { + "include": "#pragmas" + } + ], + "repository": { + "comments": { + "name": "comment.line.double-slash.teal", + "begin": "//", + "end": "$" + }, + "keywords": { + "patterns": [ + { + "match": "\\b(base64|b64|base32|b32)(?:\\(|\\s+)([a-zA-Z0-9\\+\\/\\=]+)(?:\\)|\\s?|$)", + "captures": { + "1": { + "name": "support.class.teal" + }, + "2": { + "name": "string.quoted.triple.teal" + } + } + }, + { + "match": "^(addr)\\s+([A-Z2-7\\=]+)", + "captures": { + "1": { + "name": "keyword.other.teal" + }, + "2": { + "name": "string.unquoted.teal" + } + } + }, + { + "name": "keyword.control.teal", + "match": "\\b(assert|b|bnz|bury|bz|callsub|cover|dig|dup|dup2|dupn|err|frame_bury|frame_dig|match|pop|popn|proto|retsub|return|select|swap|switch|uncover)\\b" + }, + { + "name": "keyword.other.teal", + "match": "\\b(int|byte|addr|arg|arg_0|arg_1|arg_2|arg_3|args|bytec|bytec_0|bytec_1|bytec_2|bytec_3|bytecblock|bzero|gaid|gaids|gload|gloads|gloadss|global|gtxn|gtxna|gtxnas|gtxns|gtxnsa|gtxnsas|intc|intc_0|intc_1|intc_2|intc_3|intcblock|load|loads|pushbytes|pushbytess|pushint|pushints|store|stores|txn|txna|txnas)\\b" + }, + { + "name": "keyword.other.unit.teal", + "match": "\\b(box_create|box_del|box_extract|box_get|box_len|box_put|box_replace|acct_params_get|app_global_del|app_global_get|app_global_get_ex|app_global_put|app_local_del|app_local_get|app_local_get_ex|app_local_put|app_opted_in|app_params_get|asset_holding_get|asset_params_get|balance|block|log|min_balance)\\b" + }, + { + "name": "keyword.operator.teal", + "match": "\\b(\\!|\\!\\=|%|\u0026|\u0026\u0026|\\*|\\+|\\-|/|\\\u003c|\\\u003c\\=|\\=\\=|\\\u003e|\\\u003e\\=|\\^|addw|bitlen|btoi|divmodw|divw|exp|expw|itob|mulw|shl|shr|sqrt|\\||\\|\\||\\~|b\\!\\=|b%|b\\*|b\\+|b\\-|b/|b\\\u003c|b\\\u003c\\=|b\\=\\=|b\\\u003e|b\\\u003e\\=|bsqrt|b\u0026|b\\^|b\\||b\\~|base64_decode|concat|extract|extract3|extract_uint16|extract_uint32|extract_uint64|getbit|getbyte|json_ref|len|replace2|replace3|setbit|setbyte|substring|substring3|ec_add|ec_map_to|ec_multi_scalar_mul|ec_pairing_check|ec_scalar_mul|ec_subgroup_check|ecdsa_pk_decompress|ecdsa_pk_recover|ecdsa_verify|ed25519verify|ed25519verify_bare|keccak256|sha256|sha3_256|sha512_256|vrf_verify|gitxn|gitxna|gitxnas|itxn|itxn_begin|itxn_field|itxn_next|itxn_submit|itxna|itxnas)\\b" + } + ] + }, + "labels": { + "patterns": [ + { + "name": "support.variable.teal", + "match": "^\\w+:.*$" + }, + { + "match": "\\b(?\u003c=b|bz|bnz)\\s+(\\w+)\\b", + "captures": { + "1": { + "name": "support.variable.teal" + } + } + } + ] + }, + "literals": { + "patterns": [ + { + "name": "constant.numeric.teal", + "match": "\\b([0-9]+)\\b" + }, + { + "name": "constant.numeric.teal", + "match": "\\b(?\u003c=int\\s+)(0x[0-9]+)\\b" + }, + { + "name": "string.quoted.double.teal", + "match": "\\b(?\u003c=byte\\s+)(0x[0-9]+)\\b" + }, + { + "name": "variable.parameter.teal", + "match": "\\b(unknown|pay|keyreg|acfg|axfer|afrz|appl|NoOp|OptIn|CloseOut|ClearState|UpdateApplication|DeleteApplication|Secp256k1|Secp256r1|Sender|Fee|FirstValid|FirstValidTime|LastValid|Note|Lease|Receiver|Amount|CloseRemainderTo|VotePK|SelectionPK|VoteFirst|VoteLast|VoteKeyDilution|Type|TypeEnum|XferAsset|AssetAmount|AssetSender|AssetReceiver|AssetCloseTo|GroupIndex|TxID|ApplicationID|OnCompletion|NumAppArgs|NumAccounts|ApprovalProgram|ClearStateProgram|RekeyTo|ConfigAsset|ConfigAssetTotal|ConfigAssetDecimals|ConfigAssetDefaultFrozen|ConfigAssetUnitName|ConfigAssetName|ConfigAssetURL|ConfigAssetMetadataHash|ConfigAssetManager|ConfigAssetReserve|ConfigAssetFreeze|ConfigAssetClawback|FreezeAsset|FreezeAssetAccount|FreezeAssetFrozen|NumAssets|NumApplications|GlobalNumUint|GlobalNumByteSlice|LocalNumUint|LocalNumByteSlice|ExtraProgramPages|Nonparticipation|NumLogs|CreatedAssetID|CreatedApplicationID|LastLog|StateProofPK|NumApprovalProgramPages|NumClearStateProgramPages|MinTxnFee|MinBalance|MaxTxnLife|ZeroAddress|GroupSize|LogicSigVersion|Round|LatestTimestamp|CurrentApplicationID|CreatorAddress|CurrentApplicationAddress|GroupID|OpcodeBudget|CallerApplicationID|CallerApplicationAddress|AssetCreateMinBalance|AssetOptInMinBalance|ApplicationArgs|Accounts|Assets|Applications|Logs|ApprovalProgramPages|ClearStateProgramPages|URLEncoding|StdEncoding|JSONString|JSONUint64|JSONObject|AssetBalance|AssetFrozen|AssetTotal|AssetDecimals|AssetDefaultFrozen|AssetUnitName|AssetName|AssetURL|AssetMetadataHash|AssetManager|AssetReserve|AssetFreeze|AssetClawback|AssetCreator|AppApprovalProgram|AppClearStateProgram|AppGlobalNumUint|AppGlobalNumByteSlice|AppLocalNumUint|AppLocalNumByteSlice|AppExtraProgramPages|AppCreator|AppAddress|AcctBalance|AcctMinBalance|AcctAuthAddr|AcctTotalNumUint|AcctTotalNumByteSlice|AcctTotalExtraAppPages|AcctTotalAppsCreated|AcctTotalAppsOptedIn|AcctTotalAssetsCreated|AcctTotalAssets|AcctTotalBoxes|AcctTotalBoxBytes|VrfAlgorand|BlkSeed|BlkTimestamp|BN254g1|BN254g2|BLS12_381g1|BLS12_381g2)\\b" + } + ] + }, + "pragmas": { + "name": "support.function.teal", + "match": "^#pragma\\b.*$" + }, + "strings": { + "name": "string.quoted.double.teal", + "begin": "\"", + "end": "\"", + "patterns": [ + { + "name": "constant.character.escape.teal", + "match": "\\\\(x[0-9A-Fa-f]{2}|.|$)" + } + ] + } + }, + "scopeName": "source.teal" +} diff --git a/extension/teal-language-configuration.json b/extension/teal-language-configuration.json new file mode 100644 index 0000000..84a3316 --- /dev/null +++ b/extension/teal-language-configuration.json @@ -0,0 +1,18 @@ +{ + "comments": { + // symbol used for single line comment. Remove this entry if your language does not support line comments + "lineComment": "//" + }, + // symbols used as brackets + "brackets": [["(", ")"]], + // symbols that are auto closed when typing + "autoClosingPairs": [ + ["(", ")"], + ["\"", "\""] + ], + // symbols that can be used to surround a selection + "surroundingPairs": [ + ["(", ")"], + ["\"", "\""] + ] +} diff --git a/images/app call.png b/images/app call.png new file mode 100644 index 0000000..1dc085a Binary files /dev/null and b/images/app call.png differ diff --git a/images/app state variables expanded.png b/images/app state variables expanded.png new file mode 100644 index 0000000..bba6837 Binary files /dev/null and b/images/app state variables expanded.png differ diff --git a/images/app state variables.png b/images/app state variables.png new file mode 100644 index 0000000..b6a5dc2 Binary files /dev/null and b/images/app state variables.png differ diff --git a/images/breakpoints.png b/images/breakpoints.png new file mode 100644 index 0000000..e72fe49 Binary files /dev/null and b/images/breakpoints.png differ diff --git a/images/call stack.png b/images/call stack.png new file mode 100644 index 0000000..3946edf Binary files /dev/null and b/images/call stack.png differ diff --git a/images/error.png b/images/error.png new file mode 100644 index 0000000..afd66a3 Binary files /dev/null and b/images/error.png differ diff --git a/images/inner transaction group.png b/images/inner transaction group.png new file mode 100644 index 0000000..22778dc Binary files /dev/null and b/images/inner transaction group.png differ diff --git a/images/program state variables bytes expanded.png b/images/program state variables bytes expanded.png new file mode 100644 index 0000000..baf918b Binary files /dev/null and b/images/program state variables bytes expanded.png differ diff --git a/images/program state variables.png b/images/program state variables.png new file mode 100644 index 0000000..e5d2135 Binary files /dev/null and b/images/program state variables.png differ diff --git a/images/transaction group app selected.png b/images/transaction group app selected.png new file mode 100644 index 0000000..9eef34a Binary files /dev/null and b/images/transaction group app selected.png differ diff --git a/images/transaction group.png b/images/transaction group.png new file mode 100644 index 0000000..107ffed Binary files /dev/null and b/images/transaction group.png differ diff --git a/images/watch values.png b/images/watch values.png new file mode 100644 index 0000000..7203fac Binary files /dev/null and b/images/watch values.png differ diff --git a/package-lock.json b/package-lock.json index 3b3b8f8..894460e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,15 +1,17 @@ { - "name": "teal-debug", - "version": "0.52.0", + "name": "avm-debug", + "version": "1.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "teal-debug", - "version": "0.52.0", + "name": "avm-debug", + "version": "1.0.0", "license": "MIT", "dependencies": { + "@vscode/debugadapter": "^1.64.0", "algosdk": "github:jasonpaulos/js-algorand-sdk#teal-source-map-improvements", + "await-notify": "^1.0.1", "json-bigint": "^1.0.0" }, "devDependencies": { @@ -21,10 +23,8 @@ "@types/vscode": "^1.66.0", "@typescript-eslint/eslint-plugin": "^5.62.0", "@typescript-eslint/parser": "^5.62.0", - "@vscode/debugadapter": "^1.56.0", - "@vscode/debugadapter-testsupport": "^1.56.0", - "await-notify": "^1.0.1", - "base64-js": "^1.5.1", + "@vscode/debugadapter-testsupport": "^1.64.0", + "@vscode/vsce": "^2.22.0", "esbuild": "^0.14.29", "eslint": "^8.52.0", "eslint-config-prettier": "^9.0.0", @@ -32,16 +32,14 @@ "glob": "^7.2.0", "mocha": "^10.2.0", "nyc": "^15.1.0", - "path-browserify": "^1.0.1", "prettier": "^3.0.3", "rimraf": "^3.0.2", "ts-mocha": "^10.0.0", "typescript": "^4.6.3", - "url": "^0.11.0", - "vsce": "^2.7.0" + "url": "^0.11.3" }, "engines": { - "vscode": "^1.80.0" + "node": ">=18.0.0" } }, "node_modules/@aashutoshrathi/word-wrap": { @@ -1114,35 +1112,154 @@ "dev": true }, "node_modules/@vscode/debugadapter": { - "version": "1.61.0", - "resolved": "https://registry.npmjs.org/@vscode/debugadapter/-/debugadapter-1.61.0.tgz", - "integrity": "sha512-VDGLUFDVAdnftUebZe4uQCIFUbJ7rTc2Grps4D/CXl+qyzTZSQLv5VADEOZ6kBYG4SvlnMLql5vPQ0G6XvUCvQ==", - "dev": true, + "version": "1.64.0", + "resolved": "https://registry.npmjs.org/@vscode/debugadapter/-/debugadapter-1.64.0.tgz", + "integrity": "sha512-XygE985qmNCzJExDnam4bErK6FG9Ck8S5TRPDNESwkt7i3OXqw5a3vYb7Dteyhz9YMEf7hwhFoT46Mjc45nJUg==", "dependencies": { - "@vscode/debugprotocol": "1.61.0" + "@vscode/debugprotocol": "1.64.0" }, "engines": { "node": ">=14" } }, "node_modules/@vscode/debugadapter-testsupport": { - "version": "1.61.0", - "resolved": "https://registry.npmjs.org/@vscode/debugadapter-testsupport/-/debugadapter-testsupport-1.61.0.tgz", - "integrity": "sha512-M/8aNX1aFvupd+SP0NLEVLKUK9y52BuCK5vKO2gzdpSoRUR2fR8oFbGkTie+/p2Yrcswnuf7hFx0xWkV9avRdg==", + "version": "1.64.0", + "resolved": "https://registry.npmjs.org/@vscode/debugadapter-testsupport/-/debugadapter-testsupport-1.64.0.tgz", + "integrity": "sha512-0XSaJ7y7gPWyaoDJlMBaQTAuP3BiNVhG0FiZBxC8msLWMPq73mOU0k4RsEtmbPAu10BxPJjFnoPco4rb1NzXeA==", "dev": true, "dependencies": { - "@vscode/debugprotocol": "1.61.0" + "@vscode/debugprotocol": "1.64.0" }, "engines": { "node": ">=14" } }, "node_modules/@vscode/debugprotocol": { - "version": "1.61.0", - "resolved": "https://registry.npmjs.org/@vscode/debugprotocol/-/debugprotocol-1.61.0.tgz", - "integrity": "sha512-K/kF27jIStVFqlmUaGc2u+Dj8IR7YdEiSqShWr7MWhDudqpAW7uu7XMwoFwjpuC9LSaVwJMIX7EFC5OJ/RmnDQ==", + "version": "1.64.0", + "resolved": "https://registry.npmjs.org/@vscode/debugprotocol/-/debugprotocol-1.64.0.tgz", + "integrity": "sha512-Zhf3KvB+J04M4HPE2yCvEILGVtPixXUQMLBvx4QcAtjhc5lnwlZbbt80LCsZO2B+2BH8RMgVXk3QQ5DEzEne2Q==" + }, + "node_modules/@vscode/vsce": { + "version": "2.22.0", + "resolved": "https://registry.npmjs.org/@vscode/vsce/-/vsce-2.22.0.tgz", + "integrity": "sha512-8df4uJiM3C6GZ2Sx/KilSKVxsetrTBBIUb3c0W4B1EWHcddioVs5mkyDKtMNP0khP/xBILVSzlXxhV+nm2rC9A==", + "dev": true, + "dependencies": { + "azure-devops-node-api": "^11.0.1", + "chalk": "^2.4.2", + "cheerio": "^1.0.0-rc.9", + "commander": "^6.2.1", + "glob": "^7.0.6", + "hosted-git-info": "^4.0.2", + "jsonc-parser": "^3.2.0", + "leven": "^3.1.0", + "markdown-it": "^12.3.2", + "mime": "^1.3.4", + "minimatch": "^3.0.3", + "parse-semver": "^1.1.1", + "read": "^1.0.7", + "semver": "^7.5.2", + "tmp": "^0.2.1", + "typed-rest-client": "^1.8.4", + "url-join": "^4.0.1", + "xml2js": "^0.5.0", + "yauzl": "^2.3.1", + "yazl": "^2.2.2" + }, + "bin": { + "vsce": "vsce" + }, + "engines": { + "node": ">= 14" + }, + "optionalDependencies": { + "keytar": "^7.7.0" + } + }, + "node_modules/@vscode/vsce/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@vscode/vsce/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@vscode/vsce/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/@vscode/vsce/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", "dev": true }, + "node_modules/@vscode/vsce/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@vscode/vsce/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/@vscode/vsce/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@vscode/vsce/node_modules/xml2js": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.5.0.tgz", + "integrity": "sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA==", + "dev": true, + "dependencies": { + "sax": ">=0.6.0", + "xmlbuilder": "~11.0.0" + }, + "engines": { + "node": ">=4.0.0" + } + }, "node_modules/acorn": { "version": "8.11.2", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.2.tgz", @@ -1310,8 +1427,7 @@ "node_modules/await-notify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/await-notify/-/await-notify-1.0.1.tgz", - "integrity": "sha512-eT6XN2ycPKvuiffzUNmU0dnGmmLw+TexMW7UKOyf5utdVrWx14PR2acRIfy6ZfFWRAv8twt1X74VUgd9RnDmfQ==", - "dev": true + "integrity": "sha512-eT6XN2ycPKvuiffzUNmU0dnGmmLw+TexMW7UKOyf5utdVrWx14PR2acRIfy6ZfFWRAv8twt1X74VUgd9RnDmfQ==" }, "node_modules/azure-devops-node-api": { "version": "11.2.0", @@ -1347,7 +1463,8 @@ "type": "consulting", "url": "https://feross.org/support" } - ] + ], + "optional": true }, "node_modules/bignumber.js": { "version": "9.1.1", @@ -1371,6 +1488,7 @@ "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", "dev": true, + "optional": true, "dependencies": { "buffer": "^5.5.0", "inherits": "^2.0.4", @@ -1396,6 +1514,7 @@ "url": "https://feross.org/support" } ], + "optional": true, "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" @@ -1406,6 +1525,7 @@ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", "dev": true, + "optional": true, "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", @@ -1662,7 +1782,8 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", - "dev": true + "dev": true, + "optional": true }, "node_modules/clean-stack": { "version": "2.2.0", @@ -1805,6 +1926,7 @@ "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", "dev": true, + "optional": true, "dependencies": { "mimic-response": "^3.1.0" }, @@ -1820,6 +1942,7 @@ "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", "dev": true, + "optional": true, "engines": { "node": ">=4.0.0" } @@ -1859,6 +1982,7 @@ "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.2.tgz", "integrity": "sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw==", "dev": true, + "optional": true, "engines": { "node": ">=8" } @@ -1968,6 +2092,7 @@ "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", "dev": true, + "optional": true, "dependencies": { "once": "^1.4.0" } @@ -2284,6 +2409,7 @@ "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", "dev": true, + "optional": true, "engines": { "node": ">=6" } @@ -2474,7 +2600,8 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", - "dev": true + "dev": true, + "optional": true }, "node_modules/fs.realpath": { "version": "1.0.0", @@ -2534,7 +2661,8 @@ "version": "0.0.0", "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==", - "dev": true + "dev": true, + "optional": true }, "node_modules/glob": { "version": "7.2.3", @@ -2754,7 +2882,8 @@ "type": "consulting", "url": "https://feross.org/support" } - ] + ], + "optional": true }, "node_modules/ignore": { "version": "5.2.4", @@ -2819,7 +2948,8 @@ "version": "1.3.8", "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", - "dev": true + "dev": true, + "optional": true }, "node_modules/is-binary-path": { "version": "2.1.0", @@ -3131,12 +3261,19 @@ "json5": "lib/cli.js" } }, + "node_modules/jsonc-parser": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz", + "integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==", + "dev": true + }, "node_modules/keytar": { "version": "7.9.0", "resolved": "https://registry.npmjs.org/keytar/-/keytar-7.9.0.tgz", "integrity": "sha512-VPD8mtVtm5JNtA2AErl6Chp06JBfy7diFQ7TQQhdpWOl6MrCRB+eRbvAZUsbGQS9kiMq0coJsy0W0vHpDCkWsQ==", "dev": true, "hasInstallScript": true, + "optional": true, "dependencies": { "node-addon-api": "^4.3.0", "prebuild-install": "^7.0.1" @@ -3328,6 +3465,7 @@ "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", "dev": true, + "optional": true, "engines": { "node": ">=10" }, @@ -3372,7 +3510,8 @@ "version": "0.5.3", "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", - "dev": true + "dev": true, + "optional": true }, "node_modules/mocha": { "version": "10.2.0", @@ -3516,7 +3655,8 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-1.0.2.tgz", "integrity": "sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==", - "dev": true + "dev": true, + "optional": true }, "node_modules/natural-compare": { "version": "1.4.0", @@ -3535,6 +3675,7 @@ "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.45.0.tgz", "integrity": "sha512-iwXuFrMAcFVi/ZoZiqq8BzAdsLw9kxDfTC0HMyjXfSL/6CSDAGD5UmR7azrAgWV1zKYq7dUUMj4owusBWKLsiQ==", "dev": true, + "optional": true, "dependencies": { "semver": "^7.3.5" }, @@ -3546,7 +3687,8 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-4.3.0.tgz", "integrity": "sha512-73sE9+3UaLYYFmDsFZnqCInzPyh3MqIwZO9cw58yIqAZhONrrabrYyYe3TuIqtIiOuTXVhsGau8hcrhhwSsDIQ==", - "dev": true + "dev": true, + "optional": true }, "node_modules/node-preload": { "version": "0.2.1", @@ -3929,12 +4071,6 @@ "url": "https://github.com/inikulin/parse5?sponsor=1" } }, - "node_modules/path-browserify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", - "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", - "dev": true - }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -4064,6 +4200,7 @@ "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.1.tgz", "integrity": "sha512-jAXscXWMcCK8GgCoHOfIr0ODh5ai8mj63L2nWrjuAgXE6tDyYGnx4/8o/rCgU+B4JSyZBKbeZqzhtwtC3ovxjw==", "dev": true, + "optional": true, "dependencies": { "detect-libc": "^2.0.0", "expand-template": "^2.0.3", @@ -4126,6 +4263,7 @@ "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", "dev": true, + "optional": true, "dependencies": { "end-of-stream": "^1.1.0", "once": "^1.3.1" @@ -4189,6 +4327,7 @@ "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", "dev": true, + "optional": true, "dependencies": { "deep-extend": "^0.6.0", "ini": "~1.3.0", @@ -4204,6 +4343,7 @@ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", "dev": true, + "optional": true, "engines": { "node": ">=0.10.0" } @@ -4417,7 +4557,8 @@ "type": "consulting", "url": "https://feross.org/support" } - ] + ], + "optional": true }, "node_modules/simple-get": { "version": "4.0.1", @@ -4438,6 +4579,7 @@ "url": "https://feross.org/support" } ], + "optional": true, "dependencies": { "decompress-response": "^6.0.0", "once": "^1.3.1", @@ -4500,6 +4642,7 @@ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, + "optional": true, "dependencies": { "safe-buffer": "~5.1.0" } @@ -4569,6 +4712,7 @@ "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", "dev": true, + "optional": true, "dependencies": { "chownr": "^1.1.1", "mkdirp-classic": "^0.5.2", @@ -4581,6 +4725,7 @@ "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", "dev": true, + "optional": true, "dependencies": { "bl": "^4.0.3", "end-of-stream": "^1.4.1", @@ -4597,6 +4742,7 @@ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", "dev": true, + "optional": true, "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", @@ -4759,6 +4905,7 @@ "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", "dev": true, + "optional": true, "dependencies": { "safe-buffer": "^5.0.1" }, @@ -4880,13 +5027,13 @@ } }, "node_modules/url": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/url/-/url-0.11.1.tgz", - "integrity": "sha512-rWS3H04/+mzzJkv0eZ7vEDGiQbgquI1fGfOad6zKvgYQi1SzMmhl7c/DdRGxhaWrVH6z0qWITo8rpnxK/RfEhA==", + "version": "0.11.3", + "resolved": "https://registry.npmjs.org/url/-/url-0.11.3.tgz", + "integrity": "sha512-6hxOLGfZASQK/cijlZnZJTq8OXAkt/3YGfQX45vvMYXpZoo8NdWZcY73K108Jf759lS1Bv/8wXnHDTSz17dSRw==", "dev": true, "dependencies": { "punycode": "^1.4.1", - "qs": "^6.11.0" + "qs": "^6.11.2" } }, "node_modules/url-join": { @@ -4905,7 +5052,8 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "dev": true + "dev": true, + "optional": true }, "node_modules/uuid": { "version": "8.3.2", @@ -4921,121 +5069,6 @@ "resolved": "https://registry.npmjs.org/vlq/-/vlq-2.0.4.tgz", "integrity": "sha512-aodjPa2wPQFkra1G8CzJBTHXhgk3EVSwxSWXNPr1fgdFLUb8kvLV1iEb6rFgasIsjP82HWI6dsb5Io26DDnasA==" }, - "node_modules/vsce": { - "version": "2.15.0", - "resolved": "https://registry.npmjs.org/vsce/-/vsce-2.15.0.tgz", - "integrity": "sha512-P8E9LAZvBCQnoGoizw65JfGvyMqNGlHdlUXD1VAuxtvYAaHBKLBdKPnpy60XKVDAkQCfmMu53g+gq9FM+ydepw==", - "deprecated": "vsce has been renamed to @vscode/vsce. Install using @vscode/vsce instead.", - "dev": true, - "dependencies": { - "azure-devops-node-api": "^11.0.1", - "chalk": "^2.4.2", - "cheerio": "^1.0.0-rc.9", - "commander": "^6.1.0", - "glob": "^7.0.6", - "hosted-git-info": "^4.0.2", - "keytar": "^7.7.0", - "leven": "^3.1.0", - "markdown-it": "^12.3.2", - "mime": "^1.3.4", - "minimatch": "^3.0.3", - "parse-semver": "^1.1.1", - "read": "^1.0.7", - "semver": "^5.1.0", - "tmp": "^0.2.1", - "typed-rest-client": "^1.8.4", - "url-join": "^4.0.1", - "xml2js": "^0.4.23", - "yauzl": "^2.3.1", - "yazl": "^2.2.2" - }, - "bin": { - "vsce": "vsce" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/vsce/node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/vsce/node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/vsce/node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/vsce/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true - }, - "node_modules/vsce/node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/vsce/node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/vsce/node_modules/semver": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", - "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", - "dev": true, - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/vsce/node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -5098,19 +5131,6 @@ "typedarray-to-buffer": "^3.1.5" } }, - "node_modules/xml2js": { - "version": "0.4.23", - "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz", - "integrity": "sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==", - "dev": true, - "dependencies": { - "sax": ">=0.6.0", - "xmlbuilder": "~11.0.0" - }, - "engines": { - "node": ">=4.0.0" - } - }, "node_modules/xmlbuilder": { "version": "11.0.1", "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", diff --git a/package.json b/package.json index e75cb28..d06af43 100644 --- a/package.json +++ b/package.json @@ -1,29 +1,20 @@ { - "name": "teal-debug", - "displayName": "TEAL Debug", - "version": "0.52.0", - "publisher": "algorand-vscode", - "description": "Debug extension for developing TEAL smart contracts in VS Code.", + "name": "avm-debug", + "version": "1.0.0", + "description": "Algorand AVM transaction and smart contract debugger using the Debugger Adapter Protocol", "author": { - "name": "Scytalians" + "name": "Algorand, llc" }, "license": "MIT", - "keywords": [ - "multi-root ready" - ], "engines": { - "vscode": "^1.80.0" + "node": ">=18.0.0" }, - "categories": [ - "Debuggers" - ], - "private": true, "repository": { "type": "git", - "url": "https://github.com/algorand/teal-debugger.git" + "url": "https://github.com/algorand/avm-debugger.git" }, "bugs": { - "url": "https://github.com/algorand/teal-debugger/issues" + "url": "https://github.com/algorand/avm-debugger/issues" }, "scripts": { "compile": "tsc -p ./", @@ -31,33 +22,24 @@ "typecheck": "tsc -p tsconfig.json --noEmit", "check-format": "prettier . --check", "format": "prettier . --write", - "esbuild-base": "esbuild ./src/extension.ts --bundle --tsconfig=./tsconfig.json --external:vscode --format=cjs --platform=node --outfile=dist/extension.js", + "esbuild-base": "esbuild ./extension/src/extension.ts --bundle --tsconfig=./tsconfig.json --external:vscode --format=cjs --platform=node --outfile=extension/dist/extension.js", "watch": "npm run -S esbuild-base -- --sourcemap --sources-content=false --watch", - "build": "npm run -S esbuild-base -- --sourcemap --sources-content=false", + "esbuild-web": "esbuild ./extension/src/web-extension.ts --bundle --tsconfig=./tsconfig.json --external:vscode --format=cjs --platform=browser --outfile=extension/dist/web-extension.js", + "watch-web": "npm run -S esbuild-web -- --sourcemap --sources-content=false --watch", + "build": "npm run -S esbuild-base -- --sourcemap --sources-content=false && npm run -S esbuild-web -- --sourcemap --sources-content=false", "package": "vsce package", "publish": "vsce publish", "publish-pre-release": "vsce publish --pre-release", - "vscode:prepublish": "rimraf dist && npm run -S esbuild-base -- --minify", + "vscode:prepublish": "rimraf dist && npm run -S esbuild-base -- --minify && npm run -S esbuild-web -- --minify", "test": "ts-mocha -p tsconfig.json tests/*test.ts --timeout 30s --diff false", "test:coverage": "nyc npm run test" }, - "nyc": { - "extends": "@istanbuljs/nyc-config-typescript", - "check-coverage": true, - "all": true, - "include": [ - "src/**/!(*.test.*).[tj]s?(x)" - ], - "exclude": [ - "src/_tests_/**/*.*" - ], - "reporter": [ - "html", - "lcov", - "text", - "text-summary" - ], - "report-dir": "coverage" + "main": "./dist/debugAdapter/index.js", + "dependencies": { + "@vscode/debugadapter": "^1.64.0", + "algosdk": "github:jasonpaulos/js-algorand-sdk#teal-source-map-improvements", + "await-notify": "^1.0.1", + "json-bigint": "^1.0.0" }, "devDependencies": { "@istanbuljs/nyc-config-typescript": "^1.0.2", @@ -68,10 +50,8 @@ "@types/vscode": "^1.66.0", "@typescript-eslint/eslint-plugin": "^5.62.0", "@typescript-eslint/parser": "^5.62.0", - "@vscode/debugadapter": "^1.56.0", - "@vscode/debugadapter-testsupport": "^1.56.0", - "await-notify": "^1.0.1", - "base64-js": "^1.5.1", + "@vscode/debugadapter-testsupport": "^1.64.0", + "@vscode/vsce": "^2.22.0", "esbuild": "^0.14.29", "eslint": "^8.52.0", "eslint-config-prettier": "^9.0.0", @@ -79,99 +59,28 @@ "glob": "^7.2.0", "mocha": "^10.2.0", "nyc": "^15.1.0", - "path-browserify": "^1.0.1", "prettier": "^3.0.3", "rimraf": "^3.0.2", "ts-mocha": "^10.0.0", "typescript": "^4.6.3", - "url": "^0.11.0", - "vsce": "^2.7.0" - }, - "main": "./dist/extension.js", - "browser": "./dist/web-extension.js", - "activationEvents": [ - "workspaceContains:**/*.teal", - "onDebug", - "onDebugResolve:teal", - "onDebugDynamicConfigurations:teal" - ], - "workspaceTrust": { - "request": "never" + "url": "^0.11.3" }, - "contributes": { - "languages": [ - { - "id": "teal", - "extensions": [ - ".teal" - ], - "configuration": "./teal-language-configuration.json" - } + "nyc": { + "extends": "@istanbuljs/nyc-config-typescript", + "check-coverage": true, + "all": true, + "include": [ + "src/**/!(*.test.*).[tj]s?(x)" ], - "breakpoints": [ - { - "language": "teal" - } + "exclude": [ + "src/_tests_/**/*.*" ], - "debuggers": [ - { - "type": "teal", - "languages": [ - "teal" - ], - "label": "TEAL Debug", - "program": "./out/src/debugAdapter/debugAdapter.js", - "runtime": "node", - "configurationAttributes": { - "launch": { - "properties": { - "simulateTraceFile": { - "type": "string", - "description": "Transaction group simulation response with execution trace.", - "default": "${workspaceFolder}/path/to/simulateTraceFile.json" - }, - "programSourcesDescriptionFile": { - "type": "string", - "description": "Description file for sources of programs appearing in transaction group.", - "default": "${workspaceFolder}/path/to/programSourcesDescriptionFile.json" - }, - "stopOnEntry": { - "type": "boolean", - "description": "Automatically stop after launch.", - "default": true - } - } - } - }, - "initialConfigurations": [ - { - "type": "teal", - "request": "launch", - "name": "Debug AVM Transactions", - "simulateTraceFile": "${workspaceFolder}/path/to/simulateTraceFile.json", - "programSourcesDescriptionFile": "${workspaceFolder}/path/to/programSourcesDescriptionFile.json", - "stopOnEntry": true - } - ], - "configurationSnippets": [ - { - "label": "AVM Debug", - "description": "A new configuration for replaying and debugging a Algorand transactions.", - "body": { - "type": "teal", - "request": "launch", - "name": "Debug AVM Transactions", - "simulateTraceFile": "^\"\\${workspaceFolder}/path/to/simulateTraceFile.json\"", - "programSourcesDescriptionFile": "^\"\\${workspaceFolder}/path/to/programSourcesDescriptionFile.json\"", - "stopOnEntry": true - } - } - ] - } - ] - }, - "dependencies": { - "algosdk": "github:jasonpaulos/js-algorand-sdk#teal-source-map-improvements", - "json-bigint": "^1.0.0" + "reporter": [ + "html", + "lcov", + "text", + "text-summary" + ], + "report-dir": "coverage" } } diff --git a/readme.md b/readme.md deleted file mode 100644 index c80dcd5..0000000 --- a/readme.md +++ /dev/null @@ -1,36 +0,0 @@ -# TEAL Debug Adapter for VS Code - -## Summary - -This repo contains a TEAL debugger which adheres to the [Debug Adapter Protocol](https://microsoft.github.io/debug-adapter-protocol/). -This protocol is used by a variety of clients, and this repo specifically -implements a client for VS Code. - -Unlike traditional debuggers, which typically execute code as it is being -debugged, this debugger operates on an execution trace. The trace is created by -the [algod simulate API](https://developer.algorand.org/docs/rest-apis/algod/#post-v2transactionssimulate). -This debugger is not responsible for compiling programs, assembling transaction groups, or executing -transactions/programs. It is only responsible for debugging the execution trace, which must already -exist. - -This code is based on the [`vscode-mock-debug`](https://github.com/microsoft/vscode-mock-debug) repo. - - - -## Build and Run - -1. Clone the repo. -2. Open the project folder in VS Code. -3. Press `F5` to build and launch TEAL Debug in another VS Code window. -4. In the explorer view of the new window open the file `stack-scratch.teal` -5. Set some breakpoints -6. From the editor's "Run and Debug" toolbar dropdown menu select "Debug File" diff --git a/sampleWorkspace/.vscode/launch.json b/sampleWorkspace/.vscode/launch.json index db09f95..9f373eb 100644 --- a/sampleWorkspace/.vscode/launch.json +++ b/sampleWorkspace/.vscode/launch.json @@ -5,7 +5,47 @@ "version": "0.2.0", "configurations": [ { - "type": "teal", + "type": "avm", + "request": "launch", + "name": "Debug App Box State Changes", + + "simulateTraceFile": "${workspaceFolder}/app-state-changes/box-simulate-response.json", + "programSourcesDescriptionFile": "${workspaceFolder}/app-state-changes/sources.json", + + "stopOnEntry": true + }, + { + "type": "avm", + "request": "launch", + "name": "Debug App Global State Changes", + + "simulateTraceFile": "${workspaceFolder}/app-state-changes/global-simulate-response.json", + "programSourcesDescriptionFile": "${workspaceFolder}/app-state-changes/sources.json", + + "stopOnEntry": true + }, + { + "type": "avm", + "request": "launch", + "name": "Debug App Local State Changes", + + "simulateTraceFile": "${workspaceFolder}/app-state-changes/local-simulate-response.json", + "programSourcesDescriptionFile": "${workspaceFolder}/app-state-changes/sources.json", + + "stopOnEntry": true + }, + { + "type": "avm", + "request": "launch", + "name": "Debug Recursive App", + + "simulateTraceFile": "${workspaceFolder}/recursive-app/simulate-response.json", + "programSourcesDescriptionFile": "${workspaceFolder}/recursive-app/sources.json", + + "stopOnEntry": true + }, + { + "type": "avm", "request": "launch", "name": "Debug Slot Machine", @@ -15,7 +55,27 @@ "stopOnEntry": true }, { - "type": "teal", + "type": "avm", + "request": "launch", + "name": "Debug Sourcemap Test", + + "simulateTraceFile": "${workspaceFolder}/sourcemap-test/simulate-response.json", + "programSourcesDescriptionFile": "${workspaceFolder}/sourcemap-test/sources.json", + + "stopOnEntry": true + }, + { + "type": "avm", + "request": "launch", + "name": "Debug Stack Scratch", + + "simulateTraceFile": "${workspaceFolder}/stack-scratch/simulate-response.json", + "programSourcesDescriptionFile": "${workspaceFolder}/stack-scratch/sources.json", + + "stopOnEntry": true + }, + { + "type": "avm", "request": "launch", "name": "Debug Stepping Test", @@ -25,23 +85,63 @@ "stopOnEntry": true }, { - "type": "teal", + "type": "avm", "request": "launch", - "name": "Debug Inner App Error", + "name": "Debug App Error", - "simulateTraceFile": "${workspaceFolder}/errors/inner-app/app-reject-simulate-response.json", - "programSourcesDescriptionFile": "${workspaceFolder}/errors/inner-app/sources.json", + "simulateTraceFile": "${workspaceFolder}/errors/app/simulate-response.json", + "programSourcesDescriptionFile": "${workspaceFolder}/errors/app/sources.json", "stopOnEntry": true }, { - "type": "teal", + "type": "avm", "request": "launch", "name": "Debug App from LogicSig Error", "simulateTraceFile": "${workspaceFolder}/errors/app-from-logicsig/simulate-response.json", "programSourcesDescriptionFile": "${workspaceFolder}/errors/app-from-logicsig/sources.json", + "stopOnEntry": true + }, + { + "type": "avm", + "request": "launch", + "name": "Debug Inner App Error (App Reject)", + + "simulateTraceFile": "${workspaceFolder}/errors/inner-app/app-reject-simulate-response.json", + "programSourcesDescriptionFile": "${workspaceFolder}/errors/inner-app/sources.json", + + "stopOnEntry": true + }, + { + "type": "avm", + "request": "launch", + "name": "Debug Inner App Error (Overspend)", + + "simulateTraceFile": "${workspaceFolder}/errors/inner-app/overspend-simulate-response.json", + "programSourcesDescriptionFile": "${workspaceFolder}/errors/inner-app/sources.json", + + "stopOnEntry": true + }, + { + "type": "avm", + "request": "launch", + "name": "Debug LogicSig Error", + + "simulateTraceFile": "${workspaceFolder}/errors/logicsig/simulate-response.json", + "programSourcesDescriptionFile": "${workspaceFolder}/errors/logicsig/sources.json", + + "stopOnEntry": true + }, + { + "type": "avm", + "request": "launch", + "name": "Debug Error Before LogicSig", + + "simulateTraceFile": "${workspaceFolder}/errors/logicsig-after-error/simulate-response.json", + "programSourcesDescriptionFile": "${workspaceFolder}/errors/logicsig-after-error/sources.json", + "stopOnEntry": true } ] diff --git a/src/activateMockDebug.ts b/src/activateMockDebug.ts deleted file mode 100644 index 6f578d4..0000000 --- a/src/activateMockDebug.ts +++ /dev/null @@ -1,23 +0,0 @@ -'use strict'; - -import * as vscode from 'vscode'; -import { TEALDebugAdapterDescriptorFactory } from './descriptorFactory'; -import { TealDebugConfigProvider } from './configuration'; - -export function activateTealDebug( - context: vscode.ExtensionContext, - factory: TEALDebugAdapterDescriptorFactory, -) { - // register a configuration provider for 'teal' debug type - const provider = new TealDebugConfigProvider(); - context.subscriptions.push( - vscode.debug.registerDebugConfigurationProvider('teal', provider), - ); - - context.subscriptions.push( - vscode.debug.registerDebugAdapterDescriptorFactory('teal', factory), - ); - // https://vscode-docs.readthedocs.io/en/stable/extensions/patterns-and-principles/#events - // see events, by the end of subscription, call `dispose()` to release resource. - context.subscriptions.push(factory); -} diff --git a/src/debugAdapter/appState.ts b/src/appState.ts similarity index 98% rename from src/debugAdapter/appState.ts rename to src/appState.ts index 114cd8f..6ea8242 100644 --- a/src/debugAdapter/appState.ts +++ b/src/appState.ts @@ -81,7 +81,7 @@ function createAvmKvArray( .map( ([key, value]) => new algosdk.modelsv2.AvmKeyValue({ - key: Buffer.from(key, 'hex'), + key: algosdk.hexToBytes(key), value, }), ); diff --git a/src/debugAdapter/basicServer.ts b/src/basicServer.ts similarity index 86% rename from src/debugAdapter/basicServer.ts rename to src/basicServer.ts index 016bd40..076a2d4 100644 --- a/src/debugAdapter/basicServer.ts +++ b/src/basicServer.ts @@ -1,6 +1,6 @@ import * as Net from 'net'; -import { AvmDebugSession } from './debugRequestHandlers'; -import { FileAccessor } from './utils'; +import { AvmDebugSession } from './debugSession'; +import { FileAccessor } from './fileAccessor'; export class BasicServer { private server: Net.Server; diff --git a/src/debugAdapter/debugAdapter.ts b/src/cli.ts similarity index 87% rename from src/debugAdapter/debugAdapter.ts rename to src/cli.ts index bd1c5e6..4c0c59f 100644 --- a/src/debugAdapter/debugAdapter.ts +++ b/src/cli.ts @@ -1,10 +1,11 @@ -import { promises as fs } from 'fs'; +import * as fs from 'fs/promises'; +import { basename } from 'path'; import * as Net from 'net'; -import { FileAccessor } from './utils'; -import { AvmDebugSession } from './debugRequestHandlers'; +import { FileAccessor } from './fileAccessor'; +import { AvmDebugSession } from './debugSession'; /* - * debugAdapter.js is the entrypoint of the debug adapter when it runs as a separate process. + * cli.js is the entrypoint of the debug adapter when it runs as a separate process. */ /* @@ -19,6 +20,9 @@ const fsAccessor: FileAccessor = { writeFile(path: string, contents: Uint8Array): Promise { return fs.writeFile(path, contents); }, + basename(path: string): string { + return basename(path); + }, }; async function run() { diff --git a/src/debugAdapter/debugRequestHandlers.ts b/src/debugSession.ts similarity index 90% rename from src/debugAdapter/debugRequestHandlers.ts rename to src/debugSession.ts index 742e1ea..4f58f52 100644 --- a/src/debugAdapter/debugRequestHandlers.ts +++ b/src/debugSession.ts @@ -13,17 +13,12 @@ import { Breakpoint, } from '@vscode/debugadapter'; import { DebugProtocol } from '@vscode/debugprotocol'; -import { basename } from 'path-browserify'; -import { AvmRuntime, IRuntimeBreakpoint } from './avmRuntime'; +import { AvmRuntime, IRuntimeBreakpoint } from './runtime'; import { ProgramStackFrame } from './traceReplayEngine'; import { Subject } from 'await-notify'; import * as algosdk from 'algosdk'; -import { - FileAccessor, - TEALDebuggingAssets, - isValidUtf8, - limitArray, -} from './utils'; +import { FileAccessor } from './fileAccessor'; +import { AvmDebuggingAssets, utf8Decode, limitArray } from './utils'; const GENERIC_ERROR_ID = 9999; @@ -38,9 +33,9 @@ export enum RuntimeEvents { } /** - * This interface describes the teal-debug specific launch attributes + * This interface describes the avm-debug specific launch attributes * (which are not part of the Debug Adapter Protocol). - * The schema for these attributes lives in the package.json of the teal-debug extension. + * The schema for these attributes lives in the package.json of the avm-debug extension. * The interface should always match this schema. */ export interface ILaunchRequestArguments @@ -140,75 +135,15 @@ export class AvmDebugSession extends DebugSession { // the adapter implements the configurationDone request. response.body.supportsConfigurationDoneRequest = true; - // make VS Code use 'evaluate' when hovering over source - response.body.supportsEvaluateForHovers = false; - // make VS Code show a 'step back' button response.body.supportsStepBack = true; - // make VS Code send cancel request - response.body.supportsCancelRequest = false; - // make VS Code send the breakpointLocations request response.body.supportsBreakpointLocationsRequest = true; - // make VS Code provide "Step in Target" functionality - response.body.supportsStepInTargetsRequest = true; - - // TEAL is not so thready. - response.body.supportsSingleThreadExecutionRequests = false; - response.body.supportsTerminateThreadsRequest = false; - - // the adapter defines two exceptions filters, one with support for conditions. - response.body.supportsExceptionFilterOptions = true; - response.body.exceptionBreakpointFilters = [ - // TODO: make filter inner txn only - { - filter: 'namedException', - label: 'Named Exception', - description: `Break on named exceptions. Enter the exception's name as the Condition.`, - default: false, - supportsCondition: true, - conditionDescription: `Enter the exception's name`, - }, - { - filter: 'otherExceptions', - label: 'Other Exceptions', - description: 'This is a other exception', - default: true, - supportsCondition: false, - }, - ]; - - // make VS Code send exceptionInfo request - // response.body.supportsExceptionInfoRequest = true; - - // make VS Code send setVariable request - // response.body.supportsSetVariable = true; - - // make VS Code send setExpression request - // response.body.supportsSetExpression = true; - - // make VS Code send disassemble request - // response.body.supportsDisassembleRequest = true; - // response.body.supportsSteppingGranularity = true; - // response.body.supportsInstructionBreakpoints = true; - - // make VS Code able to read and write variable memory - // response.body.supportsReadMemoryRequest = true; - // response.body.supportsWriteMemoryRequest = true; - - // response.body.supportSuspendDebuggee = true; - // response.body.supportTerminateDebuggee = true; - // response.body.supportsFunctionBreakpoints = true; response.body.supportsDelayedStackTraceLoading = true; this.sendResponse(response); - - // since this debug adapter can accept configuration requests like 'setBreakpoint' at any time, - // we request them early by sending an 'initializeRequest' to the frontend. - // The frontend will end the configuration sequence by calling 'configurationDone' request. - this.sendEvent(new InitializedEvent()); } /** @@ -250,7 +185,7 @@ export class AvmDebugSession extends DebugSession { args: ILaunchRequestArguments, ) { try { - const debugAssets = await TEALDebuggingAssets.loadFromFiles( + const debugAssets = await AvmDebuggingAssets.loadFromFiles( this.fileAccessor, args.simulateTraceFile, args.programSourcesDescriptionFile, @@ -258,8 +193,11 @@ export class AvmDebugSession extends DebugSession { await this._runtime.onLaunch(debugAssets); - // wait 1 second until configuration has finished (and configurationDoneRequest has been called) - await this._configurationDone.wait(1000); + // This indicates that we can now accept configuration requests like 'setBreakpoint' + this.sendEvent(new InitializedEvent()); + + // Wait until configuration has finished (and configurationDoneRequest has been called) + await this._configurationDone.wait(0); // start the program in the runtime this._runtime.start(!!args.stopOnEntry, !args.noDebug); @@ -675,9 +613,8 @@ export class AvmDebugSession extends DebugSession { if (v.scope.scope === 'global') { const value = state.globalState.getHex(keyHex); if (value) { - const keyBytes = Buffer.from(keyHex, 'hex'); toExpand = new algosdk.modelsv2.AvmKeyValue({ - key: keyBytes, + key: algosdk.hexToBytes(keyHex), value, }); } else { @@ -700,16 +637,15 @@ export class AvmDebugSession extends DebugSession { ); } toExpand = new algosdk.modelsv2.AvmKeyValue({ - key: Buffer.from(keyHex, 'hex'), + key: algosdk.hexToBytes(keyHex), value, }); } } else if (v.scope.scope === 'box') { const value = state.boxState.getHex(keyHex); if (value) { - const keyBytes = Buffer.from(keyHex, 'hex'); toExpand = new algosdk.modelsv2.AvmKeyValue({ - key: keyBytes, + key: algosdk.hexToBytes(keyHex), value, }); } else { @@ -823,9 +759,8 @@ export class AvmDebugSession extends DebugSession { const keyHex = key.slice(2); const value = state.globalState.getHex(keyHex); if (value) { - const keyBytes = Buffer.from(keyHex, 'hex'); const kv = new algosdk.modelsv2.AvmKeyValue({ - key: keyBytes, + key: algosdk.hexToBytes(keyHex), value, }); rv = this.convertAvmKeyValue(scope, kv); @@ -856,9 +791,8 @@ export class AvmDebugSession extends DebugSession { const keyHex = key.slice(2); const value = accountState.getHex(keyHex); if (value) { - const keyBytes = Buffer.from(keyHex, 'hex'); const kv = new algosdk.modelsv2.AvmKeyValue({ - key: keyBytes, + key: algosdk.hexToBytes(keyHex), value, }); rv = this.convertAvmKeyValue(scope, kv); @@ -873,9 +807,8 @@ export class AvmDebugSession extends DebugSession { const keyHex = key.slice(2); const value = state.boxState.getHex(keyHex); if (value) { - const keyBytes = Buffer.from(keyHex, 'hex'); const kv = new algosdk.modelsv2.AvmKeyValue({ - key: keyBytes, + key: algosdk.hexToBytes(keyHex), value, }); rv = this.convertAvmKeyValue(scope, kv); @@ -981,23 +914,6 @@ export class AvmDebugSession extends DebugSession { } } - protected stepInTargetsRequest( - response: DebugProtocol.StepInTargetsResponse, - args: DebugProtocol.StepInTargetsArguments, - ) { - try { - const targets = this._runtime.getStepInTargets(args.frameId); - response.body = { - targets: targets.map((t) => { - return { id: t.id, label: t.label }; - }), - }; - this.sendResponse(response); - } catch (e) { - this.sendErrorResponse(response, GENERIC_ERROR_ID, (e as Error).message); - } - } - protected stepInRequest( response: DebugProtocol.StepInResponse, args: DebugProtocol.StepInArguments, @@ -1046,7 +962,7 @@ export class AvmDebugSession extends DebugSession { // byte array const bytes = avmValue.bytes || new Uint8Array(); namedVariables = 2; - if (isValidUtf8(bytes)) { + if (typeof utf8Decode(bytes) !== 'undefined') { namedVariables++; } indexedVariables = bytes.length; @@ -1086,19 +1002,26 @@ export class AvmDebugSession extends DebugSession { const values: DebugProtocol.Variable[] = []; if (filter !== 'indexed') { - let formats: BufferEncoding[] = ['hex', 'base64']; - if (isValidUtf8(bytes)) { - formats.push('utf-8'); - } - if (bytes.length === 0) { - formats = []; - } + values.push({ + name: 'hex', + type: 'string', + value: algosdk.bytesToHex(bytes), + variablesReference: 0, + }); + + values.push({ + name: 'base64', + type: 'string', + value: algosdk.bytesToBase64(bytes), + variablesReference: 0, + }); - for (const format of formats) { + const utf8Value = utf8Decode(bytes); + if (typeof utf8Value !== 'undefined') { values.push({ - name: format, + name: 'utf-8', type: 'string', - value: Buffer.from(bytes).toString(format), + value: utf8Value, variablesReference: 0, }); } @@ -1139,7 +1062,7 @@ export class AvmDebugSession extends DebugSession { avmKeyValue: algosdk.modelsv2.AvmKeyValue, ): DebugProtocol.Variable { const keyString = - '0x' + Buffer.from(avmKeyValue.key || new Uint8Array()).toString('hex'); + '0x' + algosdk.bytesToHex(avmKeyValue.key || new Uint8Array()); const value = this.convertAvmValue( scope, avmKeyValue.value, @@ -1161,7 +1084,7 @@ export class AvmDebugSession extends DebugSession { return []; } const keyString = - '0x' + Buffer.from(avmKeyValue.key || new Uint8Array()).toString('hex'); + '0x' + algosdk.bytesToHex(avmKeyValue.key || new Uint8Array()); const keyScope = new AppSpecificStateScope({ scope: scope.scope, appID: scope.appID, @@ -1233,7 +1156,7 @@ export class AvmDebugSession extends DebugSession { if (avmValue.type === 1) { // byte array const bytes = avmValue.bytes || new Uint8Array(); - return '0x' + Buffer.from(bytes).toString('hex'); + return '0x' + algosdk.bytesToHex(bytes); } // uint64 const uint = avmValue.uint || 0; @@ -1242,11 +1165,8 @@ export class AvmDebugSession extends DebugSession { private createSource(filePath: string): Source { return new Source( - basename(filePath), + this.fileAccessor.basename(filePath), this.convertDebuggerPathToClient(filePath), - undefined, - undefined, - 'teal-txn-group-adapter-data', ); } diff --git a/src/descriptorFactory.ts b/src/descriptorFactory.ts deleted file mode 100644 index 1a5950b..0000000 --- a/src/descriptorFactory.ts +++ /dev/null @@ -1,71 +0,0 @@ -'use strict'; - -import * as Net from 'net'; -import * as vscode from 'vscode'; -import { ProviderResult } from 'vscode'; -import { AvmDebugSession } from './debugAdapter/debugRequestHandlers'; -import { workspaceFileAccessor } from './fileAccessor'; - -export interface TEALDebugAdapterDescriptorFactory - extends vscode.DebugAdapterDescriptorFactory { - dispose(); -} - -export class TEALDebugAdapterExecutableFactory - implements TEALDebugAdapterDescriptorFactory -{ - createDebugAdapterDescriptor( - _session: vscode.DebugSession, - executable: vscode.DebugAdapterExecutable | undefined, - ): ProviderResult { - // param "executable" contains the executable optionally specified in the package.json (if any) - - // use the executable specified in the package.json if it exists or determine it based on some other information (e.g. the session) - - // TODO: IMPLEMENT HERE - if (!executable) { - const command = 'absolute path to my DA executable'; - const args = ['some args', 'another arg']; - const options = { - cwd: 'working directory for executable', - env: { envVariable: 'some value' }, - }; - executable = new vscode.DebugAdapterExecutable(command, args, options); - } - - // make VS Code launch the DA executable - return executable; - } - - // eslint-disable-next-line @typescript-eslint/no-empty-function - dispose() {} -} - -export class TEALDebugAdapterServerDescriptorFactory - implements TEALDebugAdapterDescriptorFactory -{ - private server?: Net.Server; - - async createDebugAdapterDescriptor( - _session: vscode.DebugSession, - _executable: vscode.DebugAdapterExecutable | undefined, - ): Promise { - if (!this.server) { - this.server = Net.createServer((socket) => { - const session = new AvmDebugSession(workspaceFileAccessor); - session.setRunAsServer(true); - session.start(socket as NodeJS.ReadableStream, socket); - }).listen(0); - } - - return new vscode.DebugAdapterServer( - (this.server.address() as Net.AddressInfo).port, - ); - } - - dispose() { - if (this.server) { - this.server.close(); - } - } -} diff --git a/src/extension.ts b/src/extension.ts deleted file mode 100644 index b427e60..0000000 --- a/src/extension.ts +++ /dev/null @@ -1,26 +0,0 @@ -'use strict'; - -import * as vscode from 'vscode'; -import { activateTealDebug } from './activateMockDebug'; -import { - TEALDebugAdapterServerDescriptorFactory, - TEALDebugAdapterExecutableFactory, -} from './descriptorFactory'; - -const runMode: 'external' | 'server' = 'server'; - -export function activate(context: vscode.ExtensionContext) { - switch (runMode) { - case 'server': - activateTealDebug(context, new TEALDebugAdapterServerDescriptorFactory()); - break; - - case 'external': - default: - activateTealDebug(context, new TEALDebugAdapterExecutableFactory()); - break; - } -} - -// eslint-disable-next-line @typescript-eslint/no-empty-function -export function deactivate() {} diff --git a/src/fileAccessor.ts b/src/fileAccessor.ts index 57c33e1..bc51ba8 100644 --- a/src/fileAccessor.ts +++ b/src/fileAccessor.ts @@ -1,30 +1,6 @@ -'use strict'; - -import * as vscode from 'vscode'; -import { FileAccessor } from './debugAdapter/utils'; - -export const workspaceFileAccessor: FileAccessor = { - isWindows: typeof process !== 'undefined' && process.platform === 'win32', - readFile(path: string): Promise { - const uri = pathToUri(path); - return thenableToPromise(vscode.workspace.fs.readFile(uri)); - }, - writeFile(path: string, contents: Uint8Array): Promise { - const uri = pathToUri(path); - return thenableToPromise(vscode.workspace.fs.writeFile(uri, contents)); - }, -}; - -function pathToUri(path: string) { - try { - return vscode.Uri.file(path); - } catch (e) { - return vscode.Uri.parse(path, true); - } -} - -function thenableToPromise(t: Thenable): Promise { - return new Promise((resolve, reject) => { - t.then(resolve, reject); - }); +export interface FileAccessor { + isWindows: boolean; + readFile(path: string): Promise; + writeFile(path: string, contents: Uint8Array): Promise; + basename(path: string): string; } diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..79315d9 --- /dev/null +++ b/src/index.ts @@ -0,0 +1,2 @@ +export { AvmDebugSession } from './debugSession'; +export { FileAccessor } from './fileAccessor'; diff --git a/src/debugAdapter/avmRuntime.ts b/src/runtime.ts similarity index 96% rename from src/debugAdapter/avmRuntime.ts rename to src/runtime.ts index 27b4ff9..d6e6f90 100644 --- a/src/debugAdapter/avmRuntime.ts +++ b/src/runtime.ts @@ -1,5 +1,5 @@ import { EventEmitter } from 'events'; -import { RuntimeEvents } from './debugRequestHandlers'; +import { RuntimeEvents } from './debugSession'; import { AppState } from './appState'; import { FrameSourceLocation, @@ -7,11 +7,8 @@ import { TraceReplayEngine, TraceReplayStackFrame, } from './traceReplayEngine'; -import { - FileAccessor, - TEALDebuggingAssets, - ProgramSourceDescriptor, -} from './utils'; +import { FileAccessor } from './fileAccessor'; +import { AvmDebuggingAssets, ProgramSourceDescriptor } from './utils'; export interface IRuntimeBreakpoint { id: number; @@ -24,11 +21,6 @@ export interface IRuntimeBreakpointLocation { column?: number; } -interface IRuntimeStepInTargets { - id: number; - label: string; -} - interface IRuntimeStack { count: number; frames: TraceReplayStackFrame[]; @@ -48,7 +40,7 @@ export class AvmRuntime extends EventEmitter { super(); } - public onLaunch(debugAssets: TEALDebuggingAssets): Promise { + public onLaunch(debugAssets: AvmDebuggingAssets): Promise { return this.engine.loadResources(debugAssets); } @@ -199,10 +191,6 @@ export class AvmRuntime extends EventEmitter { }); } - public getStepInTargets(frameId: number): IRuntimeStepInTargets[] { - return []; - } - public stackLength(): number { return this.engine.stack.length; } diff --git a/src/debugAdapter/traceReplayEngine.ts b/src/traceReplayEngine.ts similarity index 99% rename from src/debugAdapter/traceReplayEngine.ts rename to src/traceReplayEngine.ts index a85daf3..64d8de0 100644 --- a/src/debugAdapter/traceReplayEngine.ts +++ b/src/traceReplayEngine.ts @@ -2,7 +2,7 @@ import * as algosdk from 'algosdk'; import { AppState } from './appState'; import { ByteArrayMap, - TEALDebuggingAssets, + AvmDebuggingAssets, ProgramSourceDescriptor, ProgramSourceDescriptorRegistry, } from './utils'; @@ -58,7 +58,7 @@ export class TraceReplayEngine { this.stack = []; } - public async loadResources(debugAssets: TEALDebuggingAssets) { + public async loadResources(debugAssets: AvmDebuggingAssets) { const { simulateResponse, programSourceDescriptorRegistry } = debugAssets; this.simulateResponse = simulateResponse; @@ -739,7 +739,7 @@ export class ProgramStackFrame extends TraceReplayStackFrame { ) { let lsigBytes = this.txnInfo.txn.lsig.l; if (typeof lsigBytes === 'string') { - lsigBytes = Buffer.from(lsigBytes, 'base64'); + lsigBytes = algosdk.base64ToBytes(lsigBytes); } const lsigAccount = new algosdk.LogicSigAccount(lsigBytes); this.logicSigAddress = lsigAccount.address(); @@ -780,9 +780,7 @@ export class ProgramStackFrame extends TraceReplayStackFrame { } else if (typeof this.logicSigAddress !== 'undefined') { name = `logic sig ${this.logicSigAddress}.teal`; } else { - name = `program ${Buffer.from(this.programHash).toString( - 'base64url', - )}.teal`; + name = `program ${algosdk.bytesToHex(this.programHash)}.teal`; } return { name, diff --git a/src/debugAdapter/utils.ts b/src/utils.ts similarity index 80% rename from src/debugAdapter/utils.ts rename to src/utils.ts index 427b420..7ebe5b6 100644 --- a/src/debugAdapter/utils.ts +++ b/src/utils.ts @@ -1,17 +1,18 @@ -import * as path from 'path'; import * as JSONbigWithoutConfig from 'json-bigint'; import * as algosdk from 'algosdk'; - -export interface FileAccessor { - isWindows: boolean; - readFile(path: string): Promise; - writeFile(path: string, contents: Uint8Array): Promise; -} - -export function isValidUtf8(data: Uint8Array): boolean { - const dataBuffer = Buffer.from(data); - const decoded = dataBuffer.toString('utf-8'); - return Buffer.from(decoded).equals(dataBuffer); +import { FileAccessor } from './fileAccessor'; + +/** + * Attempt to decode the given data as UTF-8 and return the result if it is + * valid UTF-8. Otherwise, return undefined. + */ +export function utf8Decode(data: Uint8Array): string | undefined { + const decoder = new TextDecoder('utf-8', { fatal: true }); + try { + return decoder.decode(data); + } catch { + return undefined; + } } export function limitArray( @@ -68,7 +69,7 @@ export class ByteArrayMap { } public set(key: Uint8Array, value: T): void { - this.map.set(Buffer.from(key).toString('hex'), value); + this.map.set(algosdk.bytesToHex(key), value); } public setHex(key: string, value: T): void { @@ -76,7 +77,7 @@ export class ByteArrayMap { } public get(key: Uint8Array): T | undefined { - return this.map.get(Buffer.from(key).toString('hex')); + return this.map.get(algosdk.bytesToHex(key)); } public getHex(key: string): T | undefined { @@ -88,11 +89,11 @@ export class ByteArrayMap { } public has(key: Uint8Array): boolean { - return this.map.has(Buffer.from(key).toString('hex')); + return this.map.has(algosdk.bytesToHex(key)); } public delete(key: Uint8Array): boolean { - return this.map.delete(Buffer.from(key).toString('hex')); + return this.map.delete(algosdk.bytesToHex(key)); } public deleteHex(key: string): boolean { @@ -105,7 +106,7 @@ export class ByteArrayMap { public *entries(): IterableIterator<[Uint8Array, T]> { for (const [key, value] of this.map.entries()) { - yield [Buffer.from(key, 'hex'), value]; + yield [algosdk.hexToBytes(key), value]; } } @@ -121,10 +122,7 @@ export class ByteArrayMap { } function filePathRelativeTo(base: string, filePath: string): string { - if (path.isAbsolute(filePath)) { - return filePath; - } - return path.join(path.dirname(base), filePath); + return new URL(filePath, new URL(base, 'file://')).pathname; } interface ProgramSourceEntryFile { @@ -177,20 +175,18 @@ export class ProgramSourceDescriptor { originFile, data['sourcemap-location'], ); - const rawSourcemap = Buffer.from( - await prefixPotentialError( - fileAccessor.readFile(sourcemapFileLocation), - 'Could not read source map file', - ), + const rawSourcemap = await prefixPotentialError( + fileAccessor.readFile(sourcemapFileLocation), + 'Could not read source map file', ); const sourcemap = new algosdk.SourceMap( - JSON.parse(rawSourcemap.toString('utf-8')), + JSON.parse(new TextDecoder().decode(rawSourcemap)), ); return new ProgramSourceDescriptor({ sourcemapFileLocation, sourcemap, - hash: new Uint8Array(Buffer.from(data.hash, 'base64')), + hash: algosdk.base64ToBytes(data.hash), }); } } @@ -216,16 +212,14 @@ export class ProgramSourceDescriptorRegistry { fileAccessor: FileAccessor, programSourcesDescriptionFilePath: string, ): Promise { - const rawSourcesDescription = Buffer.from( - await prefixPotentialError( - fileAccessor.readFile(programSourcesDescriptionFilePath), - 'Could not read program sources description file', - ), + const rawSourcesDescription = await prefixPotentialError( + fileAccessor.readFile(programSourcesDescriptionFilePath), + 'Could not read program sources description file', ); let jsonSourcesDescription: ProgramSourceEntryFile; try { jsonSourcesDescription = JSON.parse( - rawSourcesDescription.toString('utf-8'), + new TextDecoder().decode(rawSourcesDescription), ) as ProgramSourceEntryFile; if ( !Array.isArray(jsonSourcesDescription['txn-group-sources']) || @@ -257,7 +251,7 @@ export class ProgramSourceDescriptorRegistry { } } -export class TEALDebuggingAssets { +export class AvmDebuggingAssets { constructor( public readonly simulateResponse: algosdk.modelsv2.SimulateResponse, public readonly programSourceDescriptorRegistry: ProgramSourceDescriptorRegistry, @@ -267,17 +261,15 @@ export class TEALDebuggingAssets { fileAccessor: FileAccessor, simulateTraceFilePath: string, programSourcesDescriptionFilePath: string, - ): Promise { - const rawSimulateTrace = Buffer.from( - await prefixPotentialError( - fileAccessor.readFile(simulateTraceFilePath), - 'Could not read simulate trace file', - ), + ): Promise { + const rawSimulateTrace = await prefixPotentialError( + fileAccessor.readFile(simulateTraceFilePath), + 'Could not read simulate trace file', ); let simulateResponse: algosdk.modelsv2.SimulateResponse; try { const jsonPased = parseJsonWithBigints( - rawSimulateTrace.toString('utf-8'), + new TextDecoder().decode(rawSimulateTrace), ); if (jsonPased.version !== 2) { throw new Error( @@ -306,7 +298,7 @@ export class TEALDebuggingAssets { programSourcesDescriptionFilePath, ); - return new TEALDebuggingAssets(simulateResponse, txnGroupDescriptorList); + return new AvmDebuggingAssets(simulateResponse, txnGroupDescriptorList); } } diff --git a/teal-language-configuration.json b/teal-language-configuration.json deleted file mode 100644 index c5e4f74..0000000 --- a/teal-language-configuration.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "comments": { - "lineComment": "//" - } -} diff --git a/tests/adapter.test.ts b/tests/adapter.test.ts index f73fe79..2e2a352 100644 --- a/tests/adapter.test.ts +++ b/tests/adapter.test.ts @@ -2,7 +2,7 @@ import * as assert from 'assert'; import * as path from 'path'; import * as algosdk from 'algosdk'; import { DebugProtocol } from '@vscode/debugprotocol'; -import { ByteArrayMap } from '../src/debugAdapter/utils'; +import { ByteArrayMap } from '../src/utils'; import { TestFixture, assertVariables, advanceTo, DATA_ROOT } from './testing'; describe('Debug Adapter Tests', () => { @@ -47,7 +47,7 @@ describe('Debug Adapter Tests', () => { let success: boolean; try { await fixture.client.initializeRequest({ - adapterID: 'teal', + adapterID: 'avm', linesStartAt1: true, columnsStartAt1: true, pathFormat: 'url', diff --git a/tests/client.ts b/tests/client.ts index 899d5d4..e884f8b 100644 --- a/tests/client.ts +++ b/tests/client.ts @@ -2,7 +2,7 @@ import * as assert from 'assert'; import { SpawnOptions } from 'child_process'; import { DebugClient as DebugClientBase } from '@vscode/debugadapter-testsupport'; import { DebugProtocol } from '@vscode/debugprotocol'; -import { ILaunchRequestArguments } from '../src/debugAdapter/debugRequestHandlers'; +import { ILaunchRequestArguments } from '../src/debugSession'; import { ILocation, IPartialLocation, @@ -159,7 +159,7 @@ export class DebugClient extends DebugClientBase { ) as Promise; } - async hitBreakpoint( + hitBreakpoint( launchArgs: ILaunchRequestArguments, location: ILocation, expectedStopLocation?: IPartialLocation | undefined, @@ -168,67 +168,11 @@ export class DebugClient extends DebugClientBase { if (launchArgs.stopOnEntry) { throw new Error("Can't hit breakpoint when stopOnEntry is true"); } - // Can't call into super.hitBreakpoint because there is a race between setting the breakpoint - // and sending the launch request. Any breakpoints set before launch will be marked 'unverified', - // which will cause super.hitBreakpoint to fail. - await Promise.all([ - this.configurationSequence(), - this.launch({ - ...launchArgs, - stopOnEntry: true, - }), - this.assertStoppedLocation('entry', {}), - ]); - - const setBreakpointsResponse = await this.setBreakpointsRequest({ - breakpoints: [{ line: location.line, column: location.column }], - source: { path: location.path }, - }); - - const bp = setBreakpointsResponse.body.breakpoints[0]; - const verified = - typeof location.verified === 'boolean' ? location.verified : true; - assert.strictEqual( - bp.verified, - verified, - 'breakpoint verification mismatch: verified', - ); - const actualLocation = { - column: bp.column, - line: bp.line, - path: bp.source && bp.source.path, - }; - // assertPartialLocationsEqual(actualLocation, expectedBPLocation || location); - const expectedLocation = expectedBPLocation || location; - if (actualLocation.path) { - this.assertPath( - actualLocation.path, - expectedLocation.path!, - 'breakpoint verification mismatch: path', - ); - } - if (typeof actualLocation.line === 'number') { - assert.strictEqual( - actualLocation.line, - expectedLocation.line, - 'breakpoint verification mismatch: line', - ); - } - if ( - typeof expectedLocation.column === 'number' && - typeof actualLocation.column === 'number' - ) { - assert.strictEqual( - actualLocation.column, - expectedLocation.column, - 'breakpoint verification mismatch: column', - ); - } - - await this.continueRequest({ threadId: 1 }); - await this.assertStoppedLocation( - 'breakpoint', - expectedStopLocation || location, + return super.hitBreakpoint( + launchArgs, + location, + expectedStopLocation, + expectedBPLocation, ); } } diff --git a/tests/testing.ts b/tests/testing.ts index 514896b..9a73b71 100644 --- a/tests/testing.ts +++ b/tests/testing.ts @@ -2,13 +2,14 @@ import * as assert from 'assert'; import * as path from 'path'; import * as fs from 'fs/promises'; import { DebugClient } from './client'; -import { BasicServer } from '../src/debugAdapter/basicServer'; -import { FileAccessor, ByteArrayMap } from '../src/debugAdapter/utils'; +import { BasicServer } from '../src/basicServer'; +import { FileAccessor } from '../src/fileAccessor'; +import { ByteArrayMap } from '../src/utils'; export const PROJECT_ROOT = path.join(__dirname, '../'); const DEBUG_CLIENT_PATH = path.join( PROJECT_ROOT, - 'out/src/debugAdapter/debugAdapter.js', + 'out/src/debugAdapter/cli.js', ); export const DATA_ROOT = path.join(PROJECT_ROOT, 'sampleWorkspace/'); @@ -20,6 +21,9 @@ export const testFileAccessor: FileAccessor = { async writeFile(path: string, contents: Uint8Array) { return await fs.writeFile(path, contents); }, + basename(p: string): string { + return path.basename(p); + }, }; export class TestFixture { @@ -43,7 +47,7 @@ export class TestFixture { public async init() { this._server = new BasicServer(testFileAccessor); - this._client = new DebugClient('node', DEBUG_CLIENT_PATH, 'teal'); + this._client = new DebugClient('node', DEBUG_CLIENT_PATH, 'avm'); await this._client.start(this._server.port()); // If you want to invoke the debug adapter separately in a child process and @@ -51,7 +55,7 @@ export class TestFixture { // this._client = new DebugClient( // 'node', // DEBUG_CLIENT_PATH, - // 'teal', + // 'avm', // undefined, // true, // ); diff --git a/tsconfig.json b/tsconfig.json index fc27dad..c67ee81 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -21,5 +21,5 @@ "strictNullChecks": true, "noUnusedParameters": false }, - "include": ["src/**/*", "test/**/*"] + "include": ["src/**/*", "extension/**/*", "tests/**/*"] }