diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7ea2ef7..35aff3b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -41,7 +41,7 @@ jobs: needs: [lint] runs-on: ubuntu-latest container: - image: mcr.microsoft.com/playwright:v1.41.0-jammy + image: mcr.microsoft.com/playwright:v1.45.0-jammy strategy: fail-fast: false matrix: @@ -135,7 +135,11 @@ jobs: run: yarn cov:lcov - name: Send coverage to Codecov if: always() - uses: codecov/codecov-action@v3 + uses: codecov/codecov-action@v4 + with: + fail_ci_if_error: true + token: ${{ secrets.CODECOV_TOKEN }} + verbose: true merge-reports: # Merge reports after playwright-tests, even if some shards have failed diff --git a/.pnp.cjs b/.pnp.cjs index 11ba626..7446a76 100755 --- a/.pnp.cjs +++ b/.pnp.cjs @@ -31,7 +31,7 @@ const RAW_RUNTIME_STATE = ["@blueprintjs/icons", "virtual:57e49a228e4f69875db681dddaec2e006cf6676c311b4ad30fe336086d7e10f88b1f726b5ceb9ca085d4e12f644bdf4827fd576f77963ccae6ba92cde2ed0dd9#npm:5.7.0"],\ ["@blueprintjs/select", "virtual:57e49a228e4f69875db681dddaec2e006cf6676c311b4ad30fe336086d7e10f88b1f726b5ceb9ca085d4e12f644bdf4827fd576f77963ccae6ba92cde2ed0dd9#npm:5.0.23"],\ ["@monaco-editor/react", "virtual:57e49a228e4f69875db681dddaec2e006cf6676c311b4ad30fe336086d7e10f88b1f726b5ceb9ca085d4e12f644bdf4827fd576f77963ccae6ba92cde2ed0dd9#npm:4.6.0"],\ - ["@playwright/test", "npm:1.41.1"],\ + ["@playwright/test", "npm:1.45.0"],\ ["@types/node", "npm:20.11.5"],\ ["@types/pako", "npm:2.0.3"],\ ["@types/react", "npm:18.2.48"],\ @@ -3731,11 +3731,11 @@ const RAW_RUNTIME_STATE = }]\ ]],\ ["@playwright/test", [\ - ["npm:1.41.1", {\ - "packageLocation": "./.yarn/cache/@playwright-test-npm-1.41.1-1287dca42b-72bd5bb67c.zip/node_modules/@playwright/test/",\ + ["npm:1.45.0", {\ + "packageLocation": "./.yarn/cache/@playwright-test-npm-1.45.0-56c1d32405-bfb3cdcca2.zip/node_modules/@playwright/test/",\ "packageDependencies": [\ - ["@playwright/test", "npm:1.41.1"],\ - ["playwright", "npm:1.41.1"]\ + ["@playwright/test", "npm:1.45.0"],\ + ["playwright", "npm:1.45.0"]\ ],\ "linkType": "HARD"\ }]\ @@ -5035,7 +5035,7 @@ const RAW_RUNTIME_STATE = ["@blueprintjs/icons", "virtual:57e49a228e4f69875db681dddaec2e006cf6676c311b4ad30fe336086d7e10f88b1f726b5ceb9ca085d4e12f644bdf4827fd576f77963ccae6ba92cde2ed0dd9#npm:5.7.0"],\ ["@blueprintjs/select", "virtual:57e49a228e4f69875db681dddaec2e006cf6676c311b4ad30fe336086d7e10f88b1f726b5ceb9ca085d4e12f644bdf4827fd576f77963ccae6ba92cde2ed0dd9#npm:5.0.23"],\ ["@monaco-editor/react", "virtual:57e49a228e4f69875db681dddaec2e006cf6676c311b4ad30fe336086d7e10f88b1f726b5ceb9ca085d4e12f644bdf4827fd576f77963ccae6ba92cde2ed0dd9#npm:4.6.0"],\ - ["@playwright/test", "npm:1.41.1"],\ + ["@playwright/test", "npm:1.45.0"],\ ["@types/node", "npm:20.11.5"],\ ["@types/pako", "npm:2.0.3"],\ ["@types/react", "npm:18.2.48"],\ @@ -9483,21 +9483,21 @@ const RAW_RUNTIME_STATE = }]\ ]],\ ["playwright", [\ - ["npm:1.41.1", {\ - "packageLocation": "./.yarn/cache/playwright-npm-1.41.1-e18f42705a-32d48c1f8f.zip/node_modules/playwright/",\ + ["npm:1.45.0", {\ + "packageLocation": "./.yarn/cache/playwright-npm-1.45.0-8ae9f06534-dbb1c3fe12.zip/node_modules/playwright/",\ "packageDependencies": [\ - ["playwright", "npm:1.41.1"],\ + ["playwright", "npm:1.45.0"],\ ["fsevents", "patch:fsevents@npm%3A2.3.2#optional!builtin::version=2.3.2&hash=df0bf1"],\ - ["playwright-core", "npm:1.41.1"]\ + ["playwright-core", "npm:1.45.0"]\ ],\ "linkType": "HARD"\ }]\ ]],\ ["playwright-core", [\ - ["npm:1.41.1", {\ - "packageLocation": "./.yarn/unplugged/playwright-core-npm-1.41.1-f0504e52ed/node_modules/playwright-core/",\ + ["npm:1.45.0", {\ + "packageLocation": "./.yarn/unplugged/playwright-core-npm-1.45.0-84fc2ff2f7/node_modules/playwright-core/",\ "packageDependencies": [\ - ["playwright-core", "npm:1.41.1"]\ + ["playwright-core", "npm:1.45.0"]\ ],\ "linkType": "HARD"\ }]\ diff --git a/.yarn b/.yarn index 8fd4397..c190860 160000 --- a/.yarn +++ b/.yarn @@ -1 +1 @@ -Subproject commit 8fd4397ecbf85ede0ab8637cf901166f6173b5fc +Subproject commit c190860787ca8bff08036aa6c182ef4dd8148e46 diff --git a/README.md b/README.md index 373d155..f16e7d9 100644 --- a/README.md +++ b/README.md @@ -58,7 +58,7 @@ This will open Bitauth IDE in development mode. Bitauth IDE uses [Yarn's Zero-Installs strategy](https://yarnpkg.com/features/zero-installs) – all of [Bitauth IDE's dependencies are tracked in an independent git repository](https://github.com/bitauth/bitauth-ide-dependencies), and the dependency repo is automatically shallow-cloned into the `.yarn` directory. -To run all tests: +To run all tests, install the playwright browsers with `yarn test:install`, then: ```sh yarn test diff --git a/libauth b/libauth index 7c7e74d..61abaca 160000 --- a/libauth +++ b/libauth @@ -1 +1 @@ -Subproject commit 7c7e74d459bac5448c7d1b4eb222128f4e5191ed +Subproject commit 61abaca37bc86cccce5fa99c86a48f0c8793643f diff --git a/package.json b/package.json index b640773..0c65278 100644 --- a/package.json +++ b/package.json @@ -3,12 +3,12 @@ "version": "1.0.0", "private": true, "devDependencies": { - "@bitauth/libauth": "2.0.0", + "@bitauth/libauth": "3.1.0-next.8", "@blueprintjs/core": "^5.8.2", "@blueprintjs/icons": "^5.7.0", "@blueprintjs/select": "^5.0.23", "@monaco-editor/react": "^4.6.0", - "@playwright/test": "^1.41.1", + "@playwright/test": "^1.45.0", "@types/node": "^20.11.5", "@types/pako": "^2.0.3", "@types/react": "^18.2.48", @@ -59,6 +59,7 @@ "fix:eslint": "yarn test:eslint --fix", "gen:assets:install": "yarn_enable_scripts=true yarn add -D @vite-pwa/assets-generator", "gen:assets": "pwa-assets-generator --preset minimal public/favicon.svg", + "test:install": "yarn playwright install", "test": "yarn test:lint && yarn test:e2e", "test:lint": "yarn test:prettier && yarn test:eslint && yarn test:spelling", "test:eslint": "eslint . -c .eslintrc.cjs --max-warnings 0", @@ -70,7 +71,7 @@ "e2e:report": "playwright show-report", "e2e:report:keep": "[ ! -f playwright-report-keep ] || mv playwright-report-keep playwright-report-keep-$(date +%Y%m%d_%H%MZ) && mv playwright-report playwright-report-keep && yarn e2e:report:keep:view", "e2e:report:keep:view": "playwright show-report playwright-report-keep", - "e2e:docker:bash": "docker run --rm -p 9323:9323 -v $(pwd):/work/ -w /work/ -it mcr.microsoft.com/playwright:v1.41.0-jammy /bin/bash", + "e2e:docker:bash": "docker run --rm -p 9323:9323 -v $(pwd):/work/ -w /work/ -it mcr.microsoft.com/playwright:v1.45.0-jammy /bin/bash", "e2e:docker": "node -e \"fetch('http://localhost:3000').then(res => res.status === 200 ? console.log('Running e2e tests in Docker using base URL: http://localhost:3000') : Promise.reject()).catch(() => { console.error(`No server is listening at: http://localhost:3000. Run 'yarn start' or 'yarn preview' in another tab and try again.`); process.exit(1); } );\" && yarn e2e:docker:bash -c 'DOCKER=1 yarn test:e2e'", "e2e:docker:prod": "node -e \"fetch('http://localhost:31313').then(res => res.status === 200 ? console.log('Running e2e tests in Docker using base URL: http://localhost:31313') : Promise.reject()).catch(() => {console.error(`No server is listening at: http://localhost:31313. Run 'yarn preview:prod' in another tab and try again.`); process.exit(1); });\" && yarn e2e:docker:bash -c 'DOCKER=1 yarn test:e2e:prod'", "e2e:update-har": "UPDATE_HAR=1 yarn test:e2e --project=chromium tests/routing.spec.ts", diff --git a/src/cash-assembly/editor-tooling.ts b/src/cash-assembly/editor-tooling.ts index 7a2ebff..cb079d5 100644 --- a/src/cash-assembly/editor-tooling.ts +++ b/src/cash-assembly/editor-tooling.ts @@ -2,13 +2,17 @@ import { EvaluationViewerLine, EvaluationViewerSpacer, } from '../editor/editor-types'; -import { IDESupportedProgramState } from '../state/types'; +import { + EvaluationViewerSettings, + IDESupportedProgramState, +} from '../state/types'; import { containsRange, EvaluationSample, - range, Range, + range, + rangesAreEqual, } from '@bitauth/libauth'; import * as monacoEditor from 'monaco-editor/esm/vs/editor/editor.api'; @@ -21,14 +25,23 @@ export type MonacoMarkerDataRequired = { endColumn: number; }; +const isNotUndefined = (value: T | undefined): value is T => { + return value !== undefined; +}; const getExecutionSpacers = ( controlStack: IDESupportedProgramState['controlStack'], ) => - controlStack.map((isExecuting) => - isExecuting === true - ? EvaluationViewerSpacer.executedConditional - : EvaluationViewerSpacer.skippedConditional, - ); + controlStack + .map((item) => + typeof item === 'object' + ? undefined + : typeof item === 'number' + ? EvaluationViewerSpacer.loop + : item + ? EvaluationViewerSpacer.executedConditional + : EvaluationViewerSpacer.skippedConditional, + ) + .filter(isNotUndefined); /** * @param samples a list of samples ordered by their ending position @@ -40,6 +53,7 @@ export const samplesToEvaluationLines = < >( samples: EvaluationSample[], totalLines: number, + loopViewingIndexes: EvaluationViewerSettings['loopViewingIndexes'], ): EvaluationViewerLine[] => { if (samples.length === 0) { return []; @@ -48,16 +62,18 @@ export const samplesToEvaluationLines = < const rootIdentifier = 'root'; const evaluationIdentifier = (evaluationRange: Range) => `${evaluationRange.startLineNumber},${evaluationRange.startColumn}`; + type UniqueEvaluation = { + latestSample: EvaluationSample; + /** + * either 'root' or the key of the parent evaluation in + * `uniqueEvaluations` + */ + parentEvaluation: string; + evaluationRange: Range; + spacers: EvaluationViewerSpacer[]; + }; const uniqueEvaluations = samples.reduce<{ - [beginLineAndColumn: string]: { - /** - * either 'root' or the key of the parent evaluation in - * `uniqueEvaluations` - */ - parentEvaluation: string; - evaluationRange: Range; - spacers: EvaluationViewerSpacer[]; - }; + [beginLineAndColumn: string]: UniqueEvaluation; }>((evaluations, sample) => { const parentEvaluations = Object.entries(evaluations).filter(([, value]) => containsRange(value.evaluationRange, sample.evaluationRange), @@ -78,16 +94,27 @@ export const samplesToEvaluationLines = < const parentSpacers = parentEvaluation === rootIdentifier ? [] - : evaluations[parentEvaluation]!.spacers; + : [ + ...evaluations[parentEvaluation]!.spacers, + ...getExecutionSpacers( + evaluations[parentEvaluation]!.latestSample.state.controlStack, + ), + ]; const evaluationStartId = evaluationIdentifier(sample.evaluationRange); const evaluationIsAlreadyIdentified = evaluationStartId in evaluations; return { ...evaluations, ...(evaluationIsAlreadyIdentified - ? {} + ? { + [evaluationStartId]: { + ...evaluations[evaluationStartId], + latestSample: sample, + } as UniqueEvaluation, + } : { - [evaluationIdentifier(sample.evaluationRange)]: { + [evaluationStartId]: { + latestSample: sample, parentEvaluation, evaluationRange: sample.evaluationRange, spacers: [ @@ -101,23 +128,117 @@ export const samplesToEvaluationLines = < }), }; }, {}); + + /** + * A mapping of `ip` indexes at which visible loops begin (`OP_BEGIN`) to + * information about that loop. + */ + const loopMap = samples.reduce<{ + [loopId: string]: { + beginsAtSample: number; + endsAtSample: number; + evaluationRange: Range; + iterationStateCount: number; + }; + }>((map, sample, sampleIndex) => { + const [loopStartIp] = sample.state.controlStack.slice(-1); + if (typeof loopStartIp !== 'number') return map; + const loopId = `${sample.evaluationRange.startLineNumber}-${sample.evaluationRange.startColumn}-${loopStartIp}`; + if (map[loopId] === undefined) { + map[loopId] = { + beginsAtSample: sampleIndex, + endsAtSample: sampleIndex + 1, + evaluationRange: sample.evaluationRange, + /** + * `sample.state` is the first state; `iterations` contains only the + * remaining states following each repeated execution. Count is + * incremented by one to account for the initial state. + */ + + iterationStateCount: sample.iterations!.length + 1, + }; + } else { + map[loopId]!.endsAtSample = sampleIndex; + } + return map; + }, {}); + const loopViewingInfo = Object.keys(loopMap) + .sort((a, b) => Number(a) - Number(b)) + .map((loopId, loopIndex) => ({ + loopId, + ...loopMap[loopId]!, + viewingIteration: loopViewingIndexes[loopIndex] ?? 0, + maxIterationIndex: 0, + })); + const samplesWithLoopViewingInfo = range(samples.length).map((index) => ({ + sample: samples[index], + stateOffset: 0, + offsetMultiple: 1, + activeLoops: [] as number[], + })); + loopViewingInfo.forEach((loopInfo, loopIndex) => { + samplesWithLoopViewingInfo.forEach((item, index) => { + if ( + item.sample !== undefined && + rangesAreEqual(loopInfo.evaluationRange, item.sample.evaluationRange) && + loopInfo.beginsAtSample <= index && + loopInfo.endsAtSample >= index + ) { + item.activeLoops.push(loopIndex); + const requestedStateOffset = + item.stateOffset + item.offsetMultiple * loopInfo.viewingIteration; + if ( + index === loopInfo.beginsAtSample && + item.sample.iterations?.[requestedStateOffset - 1] === undefined + ) { + loopInfo.viewingIteration = 0; + loopViewingInfo[loopIndex]!.viewingIteration = 0; + } + item.stateOffset += item.offsetMultiple * loopInfo.viewingIteration; + item.offsetMultiple += loopInfo.viewingIteration; + } + }); + const parentActiveLoops = samplesWithLoopViewingInfo[ + loopInfo.beginsAtSample + ]!.activeLoops.slice(0, -1); + const parentIterations = + parentActiveLoops.length === 0 + ? 0 + : loopViewingInfo[parentActiveLoops[parentActiveLoops.length - 1]!]! + .iterationStateCount; + const maxIterations = + parentIterations === 0 + ? loopInfo.iterationStateCount + : loopInfo.iterationStateCount / parentIterations; + loopInfo.maxIterationIndex = maxIterations - 1; + }); + const initialStateIndex = 0; - const finalLineContents = samples.reduce<{ + const finalLineContents = samplesWithLoopViewingInfo.reduce<{ hasError: boolean; lines: { [line: number]: { + activeLoops: number[]; sample: EvaluationSample; spacers: EvaluationViewerSpacer[] | undefined; + stateOffset: number; }; }; }>( - (contents, sample) => { + (contents, sampleWrapper) => { + const sample = sampleWrapper.sample!; + const { stateOffset, activeLoops } = sampleWrapper; if (contents.hasError) { return { hasError: true, lines: { ...contents.lines, - [sample.range.endLineNumber]: { spacers: [], sample }, + [sample.range.endLineNumber]: { + activeLoops, + sample, + spacers: [], + stateOffset, + }, }, }; } @@ -131,7 +252,12 @@ export const samplesToEvaluationLines = < hasError: sample.state.error !== undefined, lines: { ...contents.lines, - [sample.range.endLineNumber]: { sample, spacers }, + [sample.range.endLineNumber]: { + activeLoops, + sample, + spacers, + stateOffset, + }, }, }; }, @@ -142,31 +268,60 @@ export const samplesToEvaluationLines = < * The zero-th line for each frame must be the initial state of that frame. */ [initialStateIndex]: { + activeLoops: [], sample: samples[initialStateIndex]!, spacers: undefined, + stateOffset: 0, }, }, }, ); const definedLinesReversed = Object.keys(finalLineContents.lines).reverse(); - const lines = range(totalLines).map>( - (lineNumber) => { - const mostRecentDefinedLineNumber = Number( - definedLinesReversed.find( - (definedLineNumber) => Number(definedLineNumber) <= lineNumber, - ) ?? initialStateIndex, - ); - const lineHasNewSample = mostRecentDefinedLineNumber === lineNumber; - const { sample, spacers } = - finalLineContents.lines[mostRecentDefinedLineNumber]!; - const line: EvaluationViewerLine = { - spacers, - ...(lineHasNewSample ? { state: sample.state } : {}), - }; - return line; - }, - ); + const linesWithLoopInfo = range(totalLines).map<{ + activeLoops: number[]; + line: EvaluationViewerLine; + }>((lineNumber) => { + const mostRecentDefinedLineNumber = Number( + definedLinesReversed.find( + (definedLineNumber) => Number(definedLineNumber) <= lineNumber, + ) ?? initialStateIndex, + ); + const lineHasNewSample = mostRecentDefinedLineNumber === lineNumber; + const { activeLoops, sample, spacers, stateOffset } = + finalLineContents.lines[mostRecentDefinedLineNumber]!; + const line: EvaluationViewerLine = { + spacers: spacers?.slice(), + ...(lineHasNewSample + ? { + state: + stateOffset === 0 + ? sample.state + : sample.iterations![stateOffset - 1]!, + } + : {}), + }; + return { activeLoops, line }; + }); + + const markedBeginning = range(loopViewingInfo.length).map(() => false); + const lines = linesWithLoopInfo.map(({ activeLoops, line }) => { + activeLoops.forEach((activeLoop) => { + if (markedBeginning[activeLoop] === false) { + markedBeginning[activeLoop] = true; + const index = line.spacers!.lastIndexOf(EvaluationViewerSpacer.loop); + const maximumIterationIndex = + loopViewingInfo[activeLoop]!.maxIterationIndex; + const iterationIndex = loopViewingInfo[activeLoop]!.viewingIteration; + line.spacers!.splice(index, 1, { + iterationIndex, + maximumIterationIndex, + loopIndex: activeLoop, + }); + } + }); + return line; + }); if (finalLineContents.hasError) { /** diff --git a/src/editor/Editor.tsx b/src/editor/Editor.tsx index 1153a8d..1e0cdd3 100644 --- a/src/editor/Editor.tsx +++ b/src/editor/Editor.tsx @@ -6,10 +6,14 @@ import { CurrentScripts, EvaluationViewerSettings, IDESupportedProgramState, + IDESupportedVM, } from '../state/types'; import { unknownValue } from '../utils'; import { getCurrentScripts, getUsedIds } from './common'; +import { DebugWorkerResult } from './debug-worker'; +// eslint-disable-next-line import/no-unresolved +import DebugWorker from './debug-worker.ts?worker'; import { ImportExportDialog } from './dialogs/import-export-dialog/ImportExportDialog'; import { ImportScriptDialog } from './dialogs/import-script-dialog/ImportScriptDialog'; import { NewEntityDialog } from './dialogs/new-entity-dialog/NewEntityDialog'; @@ -33,7 +37,8 @@ import { WalletEditor } from './wallet/wallet-editor/WalletEditor'; import { WalletHistoryExplorer } from './wallet/wallet-history-explorer/WalletHistoryExplorer'; import { WelcomePane } from './welcome-pane/WelcomePane'; -import { useCallback, useEffect, useState } from 'react'; +import { stringify } from '@bitauth/libauth'; +import { useCallback, useEffect, useRef, useState } from 'react'; import { Mosaic } from 'react-mosaic-component'; import { connect } from 'react-redux'; @@ -60,6 +65,8 @@ type EditorDispatch = { changeEvaluationViewerSettings: typeof ActionCreators.changeEvaluationViewerSettings; importExport: typeof ActionCreators.importExport; switchScenario: typeof ActionCreators.switchScenario; + startDebugging: typeof ActionCreators.startDebugging; + finishDebugging: typeof ActionCreators.finishDebugging; }; type EditorProps = { @@ -67,6 +74,8 @@ type EditorProps = { currentlyEditingInternalId: string | undefined; currentScripts: CurrentScripts; activeDialog: ActiveDialog; + currentVmId: IDESupportedVM; + debug: AppState['debug']; evaluationViewerSettings: EvaluationViewerSettings; usedIds: string[]; } & EditorDispatch; @@ -78,6 +87,8 @@ export const Editor = connect( currentScripts: getCurrentScripts(state), activeDialog: state.activeDialog, evaluationViewerSettings: state.evaluationViewerSettings, + currentVmId: state.currentVmId, + debug: state.debug, usedIds: getUsedIds(state), }), { @@ -92,6 +103,8 @@ export const Editor = connect( ActionCreators.changeEvaluationViewerSettings, importExport: ActionCreators.importExport, switchScenario: ActionCreators.switchScenario, + startDebugging: ActionCreators.startDebugging, + finishDebugging: ActionCreators.finishDebugging, }, )((props: EditorProps) => { const [projectExplorerWidth, setProjectExplorerWidth] = useState(21); @@ -151,6 +164,53 @@ export const Editor = connect( viewerRefCallbackFrame2, viewerRefCallbackFrame3, ]; + const workerRef = useRef(); + const lastKey = useRef(); + + const { finishDebugging } = props; + useEffect(() => { + workerRef.current = new DebugWorker(); + workerRef.current.onmessage = (e: MessageEvent) => { + finishDebugging(e.data); + }; + return () => { + workerRef.current?.terminate(); + }; + }, [finishDebugging]); + + const { computed, currentVmId, startDebugging, debug: debugState } = props; + useEffect(() => { + if ( + computed.editorMode === ProjectEditorMode.isolatedScriptEditor || + computed.editorMode === ProjectEditorMode.scriptPairEditor || + computed.editorMode === ProjectEditorMode.testedScriptEditor + ) { + const scriptMode = computed; + const key = stringify( + { + vmId: currentVmId, + config: scriptMode.workerDetails.compilerConfiguration, + lockingScriptId: scriptMode.workerDetails.lockingScriptId, + unlockingScriptId: scriptMode.workerDetails.unlockingScriptId, + scenarioId: scriptMode.workerDetails.scenarioId, + }, + 0, + ); + if (key !== lastKey.current) { + lastKey.current = key; + const compilationId = debugState.compilationId + 1; + startDebugging(); + workerRef.current?.postMessage({ + compilerConfiguration: scriptMode.workerDetails.compilerConfiguration, + lockingScriptId: scriptMode.workerDetails.lockingScriptId, + unlockingScriptId: scriptMode.workerDetails.unlockingScriptId, + scenarioId: scriptMode.workerDetails.scenarioId, + vmId: currentVmId, + compilationId, + }); + } + } + }, [computed, currentVmId, startDebugging, debugState.compilationId]); useEffect(() => { const setKey = (value: unknown) => { @@ -190,7 +250,7 @@ export const Editor = connect( deleteScript={props.deleteScript} editScript={props.editScript} frame={computed.scriptEditorFrames[indexFromTop]!} - isP2SH={computed.lockingType === 'p2sh20'} + lockingType={computed.lockingType} isPushed={computed.isPushed} scriptDetails={computed.scriptDetails} setCursorLine={setCursorLine[indexFromTop]!} @@ -217,6 +277,7 @@ export const Editor = connect( debugTrace={computed.debugTrace} evaluationViewerSettings={props.evaluationViewerSettings} importExport={props.importExport} + isProcessing={computed.isProcessing} scenarioDetails={computed.scenarioDetails} showControls={indexFromTop === 0} switchScenario={props.switchScenario} diff --git a/src/editor/constants.ts b/src/editor/constants.ts index 28c0a0a..9e90251 100644 --- a/src/editor/constants.ts +++ b/src/editor/constants.ts @@ -1,7 +1,7 @@ export const ideURI = window.location.origin; export const bitauthWalletTemplateSchema = - 'https://ide.bitauth.com/authentication-template-v0.schema.json'; + 'https://libauth.org/schemas/wallet-template-v0.schema.json'; export const localStorageBackupPrefix = 'BITAUTH_IDE_BACKUP_'; diff --git a/src/editor/debug-worker.ts b/src/editor/debug-worker.ts new file mode 100644 index 0000000..b80cf99 --- /dev/null +++ b/src/editor/debug-worker.ts @@ -0,0 +1,130 @@ +/// +import { + DebugDetails, + IDESupportedProgramState, + IDESupportedVM, +} from '../state/types'; + +import { IDESupportedAuthenticationProgram } from './editor-types'; + +import { + createCompiler, + createVirtualMachineBch2023, + createVirtualMachineBch2025, + createVirtualMachineBch2026, + createVirtualMachineBchSpec, + ScenarioGenerationDebuggingResult, + stringify, +} from '@bitauth/libauth'; + +export type DebugWorkerJob = { + compilerConfiguration: unknown; + lockingScriptId?: string; + unlockingScriptId?: string; + scenarioId?: string; + vmId: IDESupportedVM; + compilationId: number; +}; + +export type DebugWorkerResult = { + compilationId: number; + result: DebugDetails['result']; +}; + +const debugCache = new Map< + string, + { debugTrace: IDESupportedProgramState[]; verifyResult: string | true } +>(); +const scenarioCache = new Map< + string, + string | ScenarioGenerationDebuggingResult +>(); +const debugKey = ( + vmId: IDESupportedVM, + program: IDESupportedAuthenticationProgram, +) => `${vmId}:${stringify(program, 0)}`; +const scenarioKey = ( + configuration: unknown, + lockingScriptId: string | undefined, + unlockingScriptId: string | undefined, + scenarioId: string | undefined, +) => + `${lockingScriptId ?? ''}:${unlockingScriptId ?? ''}:${scenarioId ?? ''}:${stringify(configuration, 0)}`; + +self.onmessage = (event: MessageEvent) => { + const { + compilerConfiguration, + lockingScriptId, + unlockingScriptId, + scenarioId, + vmId, + compilationId, + } = event.data; + + const sKey = scenarioKey( + compilerConfiguration, + lockingScriptId, + unlockingScriptId, + scenarioId, + ); + let scenarioGeneration = scenarioCache.get(sKey); + if (scenarioGeneration === undefined) { + const compiler = createCompiler( + compilerConfiguration as ReturnType< + typeof createCompiler + >['configuration'], + ); + scenarioGeneration = compiler.generateScenario({ + debug: true, + lockingScriptId, + unlockingScriptId, + scenarioId, + }) as string | ScenarioGenerationDebuggingResult; + scenarioCache.set(sKey, scenarioGeneration); + if (scenarioCache.size > 20) { + const first = scenarioCache.keys().next().value as string; + scenarioCache.delete(first); + } + } + + const program = + typeof scenarioGeneration !== 'string' && + typeof scenarioGeneration.scenario !== 'string' + ? scenarioGeneration.scenario.program + : undefined; + + let debugTrace: IDESupportedProgramState[] | undefined; + let verifyResult: string | true | undefined; + if (program !== undefined) { + const dKey = debugKey(vmId, program); + const cached = debugCache.get(dKey); + if (cached !== undefined) { + ({ debugTrace, verifyResult } = cached); + } else { + const vm = + vmId === 'BCH_2023_05' + ? createVirtualMachineBch2023() + : vmId === 'BCH_2025_05' + ? createVirtualMachineBch2025() + : vmId === 'BCH_2026_05' + ? createVirtualMachineBch2026() + : createVirtualMachineBchSpec(); + debugTrace = vm.debug(program) as IDESupportedProgramState[]; + verifyResult = vm.verify(program); + debugCache.set(dKey, { debugTrace, verifyResult }); + if (debugCache.size > 20) { + const first = debugCache.keys().next().value as string; + debugCache.delete(first); + } + } + } + const workResult: DebugWorkerResult = { + compilationId, + result: { + debugTrace: debugTrace!, + scenarioGeneration, + verifyResult: verifyResult!, + }, + }; + self.postMessage(workResult); +}; diff --git a/src/editor/dialogs/edit-script-dialog/EditScriptDialog.tsx b/src/editor/dialogs/edit-script-dialog/EditScriptDialog.tsx index 95f229f..a0911d5 100644 --- a/src/editor/dialogs/edit-script-dialog/EditScriptDialog.tsx +++ b/src/editor/dialogs/edit-script-dialog/EditScriptDialog.tsx @@ -1,6 +1,11 @@ import '../editor-dialog.css'; import { ActionCreators } from '../../../state/reducer'; -import { ScriptType } from '../../../state/types'; +import { + LockingType, + lockingTypeDescriptions, + lockingTypes, + ScriptType, +} from '../../../state/types'; import { toConventionalId } from '../../common'; import { @@ -9,6 +14,7 @@ import { Classes, Dialog, FormGroup, + HTMLSelect, InputGroup, Intent, Switch, @@ -21,7 +27,7 @@ export const EditScriptDialog = ({ name, internalId, id, - isP2SH, + lockingType, isPushed, isOpen, closeDialog, @@ -33,8 +39,8 @@ export const EditScriptDialog = ({ name: string; internalId: string; id: string; - isP2SH?: boolean; - isPushed?: boolean; + lockingType: LockingType; + isPushed: boolean; usedIds: string[]; editScript: typeof ActionCreators.editScript; deleteScript: typeof ActionCreators.deleteScript; @@ -43,7 +49,7 @@ export const EditScriptDialog = ({ }) => { const [scriptName, setScriptName] = useState(name); const [scriptId, setScriptId] = useState(id); - const [scriptIsP2SH, setScriptIsP2SH] = useState(isP2SH); + const [scriptLockingType, setScriptLockingType] = useState(lockingType); const [scriptIsPushed, setScriptIsPushed] = useState(isPushed); const [nonUniqueId, setNonUniqueId] = useState(''); const [promptDelete, setPromptDelete] = useState(false); @@ -55,7 +61,7 @@ export const EditScriptDialog = ({ onOpening={() => { setScriptName(name); setScriptId(id); - setScriptIsP2SH(isP2SH); + setScriptLockingType(lockingType); setNonUniqueId(''); }} onClose={() => { @@ -117,22 +123,17 @@ export const EditScriptDialog = ({ )} {scriptType === 'locking' && ( - If enabled, this script will be nested in the standard P2SH - template. - - } + helperText={lockingTypeDescriptions[scriptLockingType]} label="Script Mode" labelFor="script-p2sh" inline={true} > - { - setScriptIsP2SH(!scriptIsP2SH); + value={scriptLockingType} + options={lockingTypes} + onChange={(e) => { + setScriptLockingType(e.currentTarget.value as LockingType); }} /> @@ -212,7 +213,7 @@ export const EditScriptDialog = ({ scriptName === '' || (scriptName === name && scriptId === id && - scriptIsP2SH === isP2SH && + scriptLockingType === lockingType && scriptIsPushed === isPushed) || (!isTest && scriptId === '') } @@ -224,7 +225,7 @@ export const EditScriptDialog = ({ internalId, name: scriptName, id: scriptId, - lockingType: scriptIsP2SH ? 'p2sh20' : 'standard', + lockingType: scriptLockingType, isPushed: scriptIsPushed, }); closeDialog(); diff --git a/src/editor/dialogs/import-export-dialog/ImportExportDialog.tsx b/src/editor/dialogs/import-export-dialog/ImportExportDialog.tsx index e0e10ba..c55c75d 100644 --- a/src/editor/dialogs/import-export-dialog/ImportExportDialog.tsx +++ b/src/editor/dialogs/import-export-dialog/ImportExportDialog.tsx @@ -250,7 +250,9 @@ export const ImportExportDialog = connect( return; } const checkMarkers = () => { - const markers = monaco.editor.getModelMarkers({}); + const markers = monaco.editor.getModelMarkers({ + resource: model.uri, + }); setErrorCount(markers.length); }; /** @@ -305,7 +307,7 @@ export const ImportExportDialog = connect( )} - ) : evaluationViewerSettings.scriptNumbersDisplayFormat === 'hex' ? ( + ) : evaluationViewerSettings.vmNumbersDisplayFormat === 'bigint' ? ( + + + + ) : evaluationViewerSettings.vmNumbersDisplayFormat === 'hex' ? ( @@ -598,7 +830,7 @@ export const ViewerControls = ({ onClick={() => { changeEvaluationViewerSettings({ ...evaluationViewerSettings, - scriptNumbersDisplayFormat: 'binary', + vmNumbersDisplayFormat: 'binary', }); }} > @@ -607,7 +839,7 @@ export const ViewerControls = ({ ) : ( @@ -615,7 +847,7 @@ export const ViewerControls = ({ onClick={() => { changeEvaluationViewerSettings({ ...evaluationViewerSettings, - scriptNumbersDisplayFormat: 'integer', + vmNumbersDisplayFormat: 'integer', }); }} > @@ -796,6 +1028,7 @@ export const EvaluationViewer = (props: { viewerRef: (viewer: HTMLDivElement | null) => void; showControls: boolean; scenarioDetails: ScenarioDetails; + isProcessing: boolean; }) => { const { evaluationSource, evaluationTrace, frame, lookup } = props.computedState; @@ -806,6 +1039,8 @@ export const EvaluationViewer = (props: { const [cachedLookup, setCachedLookup] = useState<{ lookup: StackItemIdentifyFunction | undefined; }>(emptyLookup); + const [stackItemDifferState, setStackItemDifferState] = + useState(initialStackItemDifferState); if (evaluationTrace.join() !== cachedEvaluationTrace.join()) { setCachedEvaluation(emptyEvaluation); @@ -825,6 +1060,7 @@ export const EvaluationViewer = (props: { const cacheIsAvailable = cachedEvaluation.length !== 0; const showCached = hasError && cacheIsAvailable; + const fadeCached = showCached; // && props.isProcessing; // TODO: props.isProcessing should be false for compilation errors in tested scripts const evaluation = showCached ? cachedEvaluation : evaluationLines; const activeLookup = showCached ? cachedLookup.lookup : lookup; @@ -833,7 +1069,7 @@ export const EvaluationViewer = (props: { className={`EvaluationViewer EvaluationViewer-${frame.scriptType}`} ref={props.viewerRef} > -
+
{evaluation && evaluation.length > 0 ? (
@@ -860,6 +1096,11 @@ export const EvaluationViewer = (props: { lineNumber={0} lookup={activeLookup} settings={props.evaluationViewerSettings} + changeEvaluationViewerSettings={ + props.changeEvaluationViewerSettings + } + stackItemDifferState={stackItemDifferState} + setStackItemDifferState={setStackItemDifferState} /> )}
@@ -875,6 +1116,11 @@ export const EvaluationViewer = (props: { lineNumber={lineIndex + 1} lookup={activeLookup} settings={props.evaluationViewerSettings} + changeEvaluationViewerSettings={ + props.changeEvaluationViewerSettings + } + stackItemDifferState={stackItemDifferState} + setStackItemDifferState={setStackItemDifferState} /> ))}
diff --git a/src/editor/script-editor/ScriptEditor.css b/src/editor/script-editor/ScriptEditor.css index 6ab656a..a1cfe47 100644 --- a/src/editor/script-editor/ScriptEditor.css +++ b/src/editor/script-editor/ScriptEditor.css @@ -47,16 +47,15 @@ .script-tag { font-size: 0.7em; - background-color: #2e7105; border-radius: 3px; display: inline-block; padding: 0.1em 0.5em; margin-left: 1em; } - .p2sh-tag { + .locking-type-tag { text-transform: uppercase; - background-color: #2e7105; + background-color: var(--editor-background-color); } .pushed-tag { diff --git a/src/editor/script-editor/ScriptEditor.tsx b/src/editor/script-editor/ScriptEditor.tsx index be8e853..ce02a83 100644 --- a/src/editor/script-editor/ScriptEditor.tsx +++ b/src/editor/script-editor/ScriptEditor.tsx @@ -3,6 +3,8 @@ import { MonacoMarkerDataRequired } from '../../cash-assembly/editor-tooling'; import { ActionCreators } from '../../state/reducer'; import { IDESupportedProgramState, + LockingType, + lockingTypeDescriptions, ScriptDetails, VariableDetails, } from '../../state/types'; @@ -17,8 +19,8 @@ import { getSigningSerializationOperationDetails, isCorrectScript, keyOperationsWhichRequireAParameter, - opcodeCompletionItemProviderBCH, opcodeHoverProviderBCH, + opcodeSuggestions, signatureOperationParameterDescriptions, signingSerializationOperationDetails, } from './bch-language'; @@ -202,7 +204,7 @@ const updateMarkers = export const ScriptEditor = (props: { frame: ScriptEditorFrame; - isP2SH: boolean; + lockingType: LockingType; isPushed: boolean; scriptDetails: ScriptDetails; variableDetails: VariableDetails; @@ -494,7 +496,30 @@ export const ScriptEditor = (props: { const opcodeCompletionProvider = monaco.languages.registerCompletionItemProvider( cashAssemblyLanguageId, - opcodeCompletionItemProviderBCH, + { + triggerCharacters: [''], + provideCompletionItems: (model, position) => { + if (!isCorrectScript(model, script)) { + return; + } + const query = model.getWordAtPosition(position); + const columns = model.getWordUntilPosition(position); + const range: Range = { + startColumn: columns.startColumn, + endColumn: columns.endColumn, + startLineNumber: position.lineNumber, + endLineNumber: position.lineNumber, + }; + const suggestions = + query !== null && + (query.word === 'O' || + query.word === 'OP' || + query.word.startsWith('OP_')) + ? opcodeSuggestions(range) + : []; + return { suggestions }; + }, + }, ); const variableCompletionProvider = @@ -803,14 +828,29 @@ export const ScriptEditor = (props: { {name} {scriptType === 'test-setup' &&  (Setup)} {scriptType === 'test-check' &&  (Check)} - {props.isP2SH && ( - - P2SH - - )} + {(scriptType === 'unlocking' || scriptType === 'locking') && + (props.lockingType === 'p2sh20' ? ( + + P2SH20 + + ) : props.lockingType === 'p2sh32' ? ( + + P2SH32 + + ) : ( + + P2S + + ))} {props.isPushed && scriptType === 'tested' && ( { setEditScriptDialogIsOpen(false); diff --git a/src/editor/script-editor/bch-language.ts b/src/editor/script-editor/bch-language.ts index 6400c69..e9ab9fe 100644 --- a/src/editor/script-editor/bch-language.ts +++ b/src/editor/script-editor/bch-language.ts @@ -1,6 +1,6 @@ import { BuiltInVariables, - CompilerOperationsKeyBCH, + CompilerOperationsKeyBch, CompilerOperationsSigningSerializationComponent, CompilerOperationsSigningSerializationFull, OpcodeDescriptionsBCH, @@ -23,12 +23,10 @@ const disabledOpcodes = [ 'OP_VER', 'OP_VERIF', 'OP_VERNOTIF', - 'OP_INVERT', 'OP_RESERVED1', 'OP_RESERVED2', 'OP_2MUL', 'OP_2DIV', - 'OP_MUL', 'OP_LSHIFT', 'OP_RSHIFT', ]; @@ -202,7 +200,7 @@ const completableOpcodes = [ ...otherOpcodes, ]; -const opcodeSuggestions = (range: Range) => +export const opcodeSuggestions = (range: Range) => completableOpcodes.map((opcode) => ({ label: opcode, detail: descriptions[opcode] === undefined ? '' : descriptions[opcode]![1], @@ -213,32 +211,9 @@ const opcodeSuggestions = (range: Range) => range, })); -export const opcodeCompletionItemProviderBCH: Monaco.languages.CompletionItemProvider = - { - triggerCharacters: [''], - provideCompletionItems: (model, position) => { - const query = model.getWordAtPosition(position); - const columns = model.getWordUntilPosition(position); - const range: Range = { - startColumn: columns.startColumn, - endColumn: columns.endColumn, - startLineNumber: position.lineNumber, - endLineNumber: position.lineNumber, - }; - const suggestions = - query !== null && - (query.word === 'O' || - query.word === 'OP' || - query.word.startsWith('OP_')) - ? opcodeSuggestions(range) - : []; - return { suggestions }; - }, - }; - export const getKeyOperationDescriptions = (parameter?: string) => { - const map: { [op in CompilerOperationsKeyBCH]: [string, string] } = { - data_signature: [ + const map: { [op in CompilerOperationsKeyBch]: [string, string] } = { + ecdsa_data_signature: [ 'Data Signature (ECDSA)', `An ECDSA signature covering the sha256 hash of the compiled bytecode ${ parameter ? `from script ID "${parameter}"` : 'of another script' @@ -262,7 +237,7 @@ export const getKeyOperationDescriptions = (parameter?: string) => { : '' }.`, ], - signature: [ + ecdsa_signature: [ 'Signature (ECDSA)', `An ECDSA signature covering the double sha256 hash of the serialized transaction${ parameter @@ -338,12 +313,14 @@ export const signatureOperationParameterDescriptions: { ], }; -const keyOperationPartsToDetails = (operation: string, parameter: string) => { +const keyOperationPartsToDetails = ( + operation: string, + parameter: string, +): [string, string] => { return ( - // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition - getKeyOperationDescriptions(parameter)[ - operation as CompilerOperationsKeyBCH - ] || [ + (getKeyOperationDescriptions(parameter)[operation] as + | [string, string] + | undefined) ?? [ 'Unknown Operation', `The compiler knows about the "${operation}${ parameter ? `.${parameter}` : '' diff --git a/src/editor/script-editor/error-assistance.tsx b/src/editor/script-editor/error-assistance.tsx index 867f101..29d6c9e 100644 --- a/src/editor/script-editor/error-assistance.tsx +++ b/src/editor/script-editor/error-assistance.tsx @@ -3,7 +3,9 @@ import { abbreviateStackItem } from '../common'; import { ScriptEditorFrame } from '../editor-types'; import { - AuthenticationErrorBCH2022, + AuthenticationErrorBch2025Additions, + AuthenticationErrorBchSpec, + AuthenticationErrorBchSpecAdditions, AuthenticationErrorCommon, binToHex, } from '@bitauth/libauth'; @@ -11,19 +13,22 @@ import { Popover } from '@blueprintjs/core'; export type PossibleErrors = | AuthenticationErrorCommon - | AuthenticationErrorBCH2022; + | AuthenticationErrorBch2025Additions + | AuthenticationErrorBchSpecAdditions; /** * This predates Libauth v2, where VM error messages can include contextual * information. It would probably be better to improve those messages instead of * expanding this. + * + * TODO: update for new Libauth error match strategy (`error.includes(type)`) */ export const vmErrorAssistanceBCH: { [error in PossibleErrors]?: ( state: IDESupportedProgramState, ) => string | JSX.Element; } = { - [AuthenticationErrorCommon.unsatisfiedLocktime]: (state) => ( + [AuthenticationErrorBchSpec.unsatisfiedLocktime]: (state) => ( This error occurs when the transaction's locktime has not reached the locktime required by this operation. In this scenario, the @@ -31,7 +36,7 @@ export const vmErrorAssistanceBCH: { {state.program.transaction.locktime}. ), - [AuthenticationErrorCommon.nonNullSignatureFailure]: (state) => { + [AuthenticationErrorBchSpec.nonNullSignatureFailure]: (state) => { return (

diff --git a/src/editor/script-editor/wallet-template.schema.json b/src/editor/script-editor/wallet-template.schema.json index a0d488a..8171dc7 100644 --- a/src/editor/script-editor/wallet-template.schema.json +++ b/src/editor/script-editor/wallet-template.schema.json @@ -2,16 +2,40 @@ "$ref": "#/definitions/WalletTemplate", "$schema": "http://json-schema.org/draft-07/schema#", "definitions": { + "AuthenticationVirtualMachineIdentifier": { + "description": "Allowable identifiers for authentication virtual machine versions. The `BCH` prefix identifies the Bitcoin Cash network, the `XEC` prefix identifies the eCash network, the `BSV` prefix identifies the Bitcoin SV network, and the `BTC` prefix identifies the Bitcoin Core network. VM versions are named according to the date they were deployed on the indicated network.\n\nFor each network prefix, a `_SPEC` VM version is reserved to indicate that the template requires a custom, not-yet-deployed VM version (e.g. one or more CHIPs). By convention, templates marked for `_SPEC` VMs should indicate their requirements in the template description. After deployment of the `_SPEC` VM, when template compatibility is verified, the template's `supported` array should be updated to indicate compatibility with the live VM version.", + "enum": [ + "BCH_2020_05", + "BCH_2021_05", + "BCH_2022_05", + "BCH_2023_05", + "BCH_2024_05", + "BCH_2025_05", + "BCH_2026_05", + "BCH_2027_05", + "BCH_2028_05", + "BCH_2029_05", + "BCH_2030_05", + "BCH_SPEC", + "BSV_2020_02", + "BSV_SPEC", + "BTC_2017_08", + "BTC_SPEC", + "XEC_2020_05", + "XEC_SPEC" + ], + "type": "string" + }, "WalletTemplate": { "additionalProperties": false, - "description": "A `WalletTemplate` (A.K.A. `CashAssembly Template`) specifies a set of locking scripts, unlocking scripts, and other information required to use a certain authentication scheme. Templates fully describe wallets and protocols in a way that can be shared between software clients.", + "description": "A `WalletTemplate` specifies a set of locking scripts, unlocking scripts, and other information required to use a certain wallet protocol. Templates fully describe wallet protocols in a way that can be shared between software clients.", "properties": { "$schema": { - "description": "The URI that identifies the JSON Schema used by this template. Try: `https://libauth.org/schemas/authentication-template-v0.schema.json` to enable documentation, autocompletion, and validation in JSON documents.", + "description": "The URI that identifies the JSON Schema used by this template. Try: `https://libauth.org/schemas/wallet-template-v0.schema.json` to enable documentation, autocompletion, and validation in JSON documents.", "type": "string" }, "description": { - "description": "An optionally multi-line, free-form, human-readable description for this wallet template (for use in user interfaces). If displayed, this description should use a monospace font to properly render ASCII diagrams.", + "description": "An optionally multi-line, free-form, human-readable description for this wallet template (for use in user interfaces). If displayed, this description should use a monospace font to properly render ASCII diagrams.\n\nDescriptions have no length limit, but in user interfaces with limited space, they should be hidden beyond the first newline character or `140` characters until revealed by the user (e.g. by hiding the remaining description until the user activates a \"show more\" link).", "type": "string" }, "entities": { @@ -22,7 +46,7 @@ "type": "object" }, "name": { - "description": "A single-line, Title Case, human-readable name for this wallet template (for use in user interfaces).", + "description": "A single-line, Title Case, human-readable name for this authentication template (for use in user interfaces).", "type": "string" }, "scenarios": { @@ -61,27 +85,27 @@ }, "version": { "const": 0, - "description": "A number identifying the format of this WalletTemplate. Currently, this implementation requires `version` be set to `0`.", + "deprecated": "template versions are now specified via `$schema`", + "description": "A number identifying the format of this WalletTemplate. Currently, this implementation allows `version` to be set to `0`.", "type": "number" } }, - "required": ["entities", "scripts", "supported", "version"], + "required": ["entities", "scripts", "supported"], "type": "object" }, "WalletTemplateAddressData": { "additionalProperties": false, "properties": { "description": { - "description": "A single-line, human readable description for this address data.", + "description": "A single-line, human readable description for this variable (for use in user interfaces).", "type": "string" }, "name": { - "description": "A single-line, Title Case, human-readable name for this address data.", + "description": "A single-line, Title Case, human-readable name for this variable (for use in user interfaces).", "type": "string" }, "type": { "const": "AddressData", - "description": "`AddressData` is the most low-level variable type. It must be collected and stored each time a script is generated (usually, a locking script). `AddressData` can include any type of data, and can be used in any way.\n\nFor more persistent data, use `WalletData`.", "type": "string" } }, @@ -121,32 +145,35 @@ "additionalProperties": false, "properties": { "addressOffset": { - "description": "The offset by which to increment the `addressIndex` provided in the compilation data when deriving this `HdKey`. (Default: 0)\n\nThis is useful for deriving the \"next\" (`1`) or \"previous\" (`-1`) address to be used in the current compiler configuration.", + "description": "The offset by which to increment the `addressIndex` provided in the compilation data when deriving this `HdKey`. (Default: 0)\n\nThis is useful for deriving the \"next\" (`1`) or \"previous\" (`-1`) address for use in the current compilation.", "type": "number" }, "description": { - "description": "A single-line, human readable description for this HD key.", + "description": "A single-line, human readable description for this variable (for use in user interfaces).", "type": "string" }, "hdPublicKeyDerivationPath": { - "description": "The path to derive the entity's HD public key from the entity's master HD private key. By default, `m` (i.e. the entity's HD public key represents the same node in the HD tree as its HD private key).\n\nThis can be used to specify another derivation path from which the `publicDerivationPath` begins, e.g. `m/0'/1'/2'`. See `publicDerivationPath` for details.\n\nThis path must begin with an `m` (private derivation) and be fixed – it cannot contain an `i` character to represent the address index, as a dynamic hardened path would require a new HD public key for each address.", + "description": "The path to derive the entity's HD public key from the entity's provided HD private key. By default, an empty string (`\"\"`), i.e. the entity's HD public key represents the same node in the HD tree as the provided HD private key.\n\nThis can be used to specify another relative or absolute derivation path from which the `publicDerivationPath` begins, e.g. `m/0'/1'/2'`. See `publicDerivationPath` for details.\n\nThis path may optionally begin with an `m` (for relative private derivation) and must be fixed – it cannot contain any `i` characters to represent the address index, as a dynamic hardened path would require a new HD public key for each address.\n\nNote, `hdPublicKeyDerivationPath` could be automatically determined in some cases, but it's always defined independently to improve validation and auditability.", "type": "string" }, "name": { - "description": "A single-line, Title Case, human-readable name for this HD key.", + "description": "A single-line, Title Case, human-readable name for this variable (for use in user interfaces).", "type": "string" }, + "neverSignTwice": { + "description": "If set to `true`, indicates that this key should never be used to sign two different messages.\n\nThis is useful for contracts that use zero-confirmation escrow systems to guarantee against double-spend attempts. By indicating that the user could be subjected to losses if a key were used in multiple signatures, templates can ensure that wallet implementations apply appropriate safeguards around use of the key.\n\nDefaults to `false`.", + "type": "boolean" + }, "privateDerivationPath": { - "description": "The derivation path used to derive this `HdKey` from the owning entity's HD private key. By default, `m/i`.\n\nThis path uses the notation specified in BIP32 and the `i` character to represent the location of the `addressIndex`:\n\nThe first character must be `m` (private derivation), followed by sets of `/` and a number representing the child index used in the derivation at that depth. Hardened derivation is represented by a trailing `'`, and hardened child indexes are represented with the hardened index offset (`2147483648`) subtracted. The `i` character is replaced with the value of `addressIndex` plus this `HdKey`'s `addressOffset`. If the `i` character is followed by `'`, the hardened index offset is added (`2147483648`) and hardened derivation is used.\n\nFor example, `m/0/1'/i'` uses 3 levels of derivation, with child indexes in the following order:\n\n`derive(derive(derive(node, 0), 2147483648 + 1), 2147483648 + addressIndex + addressOffset)`\n\nBecause hardened derivation requires knowledge of the private key, `HdKey` variables with `derivationPath`s that include hardened derivation cannot use HD public derivation (the `hdPublicKeys` property in `CompilationData`). Instead, compilation requires the respective HD private key (`CompilationData.hdKeys.hdPrivateKeys`) or the fully-derived public key (`CompilationData.hdKeys.derivedPublicKeys`).", + "description": "The relative or absolute derivation path used to derive this `HdKey` from the owning entity's HD private key. By default, `i`.\n\nIf the first character is `m`, the path is an absolute path, otherwise, the path is a relative path. For absolute paths, the compiler will verify that the relevant entity's HD private key is a master private key (encoded with a depth of zero); `HdKey`s with relative `privateDerivationPath` may be resolved using non-master private keys (e.g. hardened accounts that have been previously derived and delegated to a sub-entity prior to compilation).\n\nThis path uses the notation specified in BIP32 and the `i` character to represent the `addressIndex`:\n\nAn optional `m` character (indicating an absolute, private derivation path), followed by sets of `/` and a number representing the child index used in the derivation at that depth. Hardened derivation is represented by a trailing `'`, and hardened child indexes are represented with the hardened index offset (`2147483648`) subtracted. All `i` characters are replaced with the value of `addressIndex` plus this `HdKey`'s `addressOffset`. If the `i` character is followed by `'`, the hardened index offset is added (`2147483648`) and hardened derivation is used.\n\nFor example, `m/0/1'/i'` has 3 levels of derivation, with child indexes in the following order:\n\n`derive(derive(derive(masterKey, 0), 2147483648 + 1), 2147483648 + addressIndex + addressOffset)`\n\nAs the path is absolute (begins with `m`), the compiler will also verify that a zero-depth (\"master\") HD private key is provided for the entity owning this `HdKey`.\n\nNote, because hardened derivation requires knowledge of the private key, `HdKey` variables with `privateDerivationPath`s that include hardened derivation must configure `hdPublicKeyDerivationPath` to support HD public derivation.", "type": "string" }, "publicDerivationPath": { - "description": "The derivation path used to derive this `HdKey`'s public key from the owning entity's HD public key. If not set, the public equivalent of `privateDerivationPath` is used. For the `privateDerivationPath` default of `m/i`, this is `M/i`.\n\nIf `privateDerivationPath` uses hardened derivation for some levels, but later derivation levels use non-hardened derivation, `publicDerivationPath` can be used to specify a public derivation path beginning from `hdPublicKeyDerivationPath` (i.e. `publicDerivationPath` should always be a non-hardened segment of `privateDerivationPath` that follows `hdPublicKeyDerivationPath`).\n\nThe first character must be `M` (public derivation), followed by sets of `/` and a number representing the child index used in the non-hardened derivation at that depth.\n\nFor example, if `privateDerivationPath` is `m/0'/i`, it is not possible to derive the equivalent public key with only the HD public key `M`. (The path \"`M/0'/i`\" is impossible.) However, given the HD public key for `m/0'`, it is possible to derive the public key of `m/0'/i` for any `i`. In this case, `hdPublicKeyDerivationPath` would be `m/0'` and `publicDerivationPath` would be the remaining `M/i`.", + "description": "The relative derivation path used to derive this `HdKey`'s public key from the owning entity's HD public key (configured via `hdPublicKeyDerivationPath`). If not set, the relative path (following the `m/` of `privateDerivationPath`) is used. For the `privateDerivationPath` default of `i`, this is `i`.\n\nIf `privateDerivationPath` uses hardened derivation for some levels, but later derivation levels use non-hardened derivation, `publicDerivationPath` can be used to specify a public derivation path beginning from `hdPublicKeyDerivationPath` (i.e. `publicDerivationPath` should always be a non-hardened segment of `privateDerivationPath` that follows `hdPublicKeyDerivationPath`).\n\nThe `publicDerivationPath` must be a relative HD derivation path: non-hardened positive integer child indexes (between `0` and `2147483647`, without any trailing `'`s) separated by `/`s.\n\nFor example, if `privateDerivationPath` is `m/0'/i`, it is not possible to derive the equivalent public key with only the HD public key `M`. (The path `M/0'/i` is impossible.) However, given the HD public key for `m/0'`, it is possible to derive the public key of `m/0'/i` for any `i`. In this case, `hdPublicKeyDerivationPath` would be `m/0'` and `publicDerivationPath` would be the remaining `i`.", "type": "string" }, "type": { "const": "HdKey", - "description": "The `HdKey` (Hierarchical-Deterministic Key) type automatically manages key generation and mapping in a standard way. For greater control, use `Key`.", "type": "string" } }, @@ -157,16 +184,19 @@ "additionalProperties": false, "properties": { "description": { - "description": "A single-line, human readable description for this key.", + "description": "A single-line, human readable description for this variable (for use in user interfaces).", "type": "string" }, "name": { - "description": "A single-line, Title Case, human-readable name for this key.", + "description": "A single-line, Title Case, human-readable name for this variable (for use in user interfaces).", "type": "string" }, + "neverSignTwice": { + "description": "If set to `true`, indicates that this key should never be used to sign two different messages.\n\nThis is useful for contracts that use zero-confirmation escrow systems to guarantee against double-spend attempts. By indicating that the user could be subjected to losses if a key were used in multiple signatures, templates can ensure that wallet implementations apply appropriate safeguards around use of the key.\n\nDefaults to `false`.", + "type": "boolean" + }, "type": { "const": "Key", - "description": "The `Key` type provides fine-grained control over key generation and mapping. Most templates should instead use `HdKey`.\n\nAny HD (Hierarchical-Deterministic) derivation must be completed outside of the templating system and provided at the time of use.", "type": "string" } }, @@ -265,7 +295,7 @@ "type": "object" } ], - "description": "A type that describes the configuration for a particular locking or unlocking bytecode within an wallet template scenario.\n\nBytecode may be specified as either a hexadecimal-encoded string or an object describing the required compilation.\n\nFor `sourceOutputs` and `transaction.inputs`, defaults to `{ script: [\"copy\"], overrides: {} }`. For `transaction.outputs`, defaults to `{ script: [\"copy\"], overrides: { \"hdKeys\": { \"addressIndex\": 1 } } }`." + "description": "A type that describes the configuration for a particular locking or unlocking bytecode within a wallet template scenario.\n\nBytecode may be specified as either a hexadecimal-encoded string or an object describing the required compilation.\n\nFor `sourceOutputs` and `transaction.inputs`, defaults to `{ script: [\"copy\"], overrides: {} }`. For `transaction.outputs`, defaults to `{ script: [\"copy\"], overrides: { \"hdKeys\": { \"addressIndex\": 1 } } }`." }, "WalletTemplateScenarioData": { "additionalProperties": false, @@ -275,7 +305,7 @@ "additionalProperties": { "type": "string" }, - "description": "A map of full identifiers to CashAssembly scripts that compile to each identifier's value for this scenario. Allowing `bytecode` to be specified as scripts (rather than e.g. hex) offers greater power and flexibility.\n\nBytecode scripts have access to each other and all other template scripts and defined variables, however, cyclical references will produce an error at compile time. Also, because the results of these compilations will be used to generate the compilation context for this scenario, these scripts may not use compiler operations that themselves require access to compilation context (e.g. signatures).\n\nThe provided `fullIdentifier` should match the complete identifier for each item, e.g. `some_wallet_data`, `variable_id.public_key`, or `variable_id.signature.all_outputs`.\n\nAll `AddressData` and `WalletData` variables must be provided via `bytecode` (though the default scenario automatically includes reasonable values), and pre-computed results for operations of other variable types (e.g. `key.public_key`) may also be provided via this property.\n\nBecause each bytecode identifier may precisely match the identifier of the variable it defines for this scenario, references between these scripts must refer to the target script with a `_scenario.` prefix. E.g. to reference a sibling script `my_foo` from `my_bar`, the `my_bar` script must use the identifier `_scenario.my_foo`.", + "description": "A map of full identifiers to CashAssembly scripts that compile to each identifier's value for this scenario. Allowing `bytecode` to be specified as scripts (rather than e.g. hex) offers greater power and flexibility.\n\nBytecode scripts have access to each other and all other template scripts and defined variables, however, cyclical references will produce an error at compile time. Also, because the results of these compilations will be used to generate the compilation context for this scenario, these scripts may not use compiler operations that themselves require access to compilation context (e.g. signatures).\n\nThe provided `fullIdentifier` should match the complete identifier for each item, e.g. `some_wallet_data`, `variable_id.public_key`, or `variable_id.schnorr_signature.all_outputs`.\n\nAll `AddressData` and `WalletData` variables must be provided via `bytecode` (though the default scenario automatically includes reasonable values), and pre-computed results for operations of other variable types (e.g. `key.public_key`) may also be provided via this property.\n\nBecause each bytecode identifier may precisely match the identifier of the variable it defines for this scenario, references between these scripts must refer to the target script with a `_scenario.` prefix. E.g. to reference a sibling script `my_foo` from `my_bar`, the `my_bar` script must use the identifier `_scenario.my_foo`.", "type": "object" }, "currentBlockHeight": { @@ -330,7 +360,7 @@ }, "WalletTemplateScenarioInput": { "additionalProperties": false, - "description": "An example input used to define a scenario for an wallet template.", + "description": "An example input used to define a scenario for a wallet template.", "properties": { "outpointIndex": { "description": "The index of the output in the transaction from which this input is spent.\n\nIf undefined, this defaults to the same index as the input itself (so that by default, every outpoint in the produced transaction is different, even if an empty `outpointTransactionHash` is used for each transaction).", @@ -366,7 +396,7 @@ }, "WalletTemplateScenarioOutput": { "additionalProperties": false, - "description": "An example output used to define a scenario for an wallet template.", + "description": "An example output used to define a scenario for a wallet template.", "properties": { "lockingBytecode": { "$ref": "#/definitions/WalletTemplateScenarioBytecode", @@ -394,7 +424,7 @@ "type": "string" }, "commitment": { - "description": "The commitment message included in the non-fungible token (of `category`) held in this output.\n\nIf undefined, this defaults to: `\"\"` (a zero-length commitment).", + "description": "The hex-encoded commitment contents included in the non-fungible token held in this output.\n\nIf undefined, this defaults to: `\"\"` (a zero-length commitment).", "type": "string" } }, @@ -412,7 +442,7 @@ }, "WalletTemplateScenarioOutput": { "additionalProperties": false, - "description": "An example output used to define a scenario for an wallet template.", + "description": "An example output used to define a scenario for a wallet template.", "properties": { "lockingBytecode": { "anyOf": [ @@ -453,7 +483,7 @@ "type": "string" }, "commitment": { - "description": "The commitment message included in the non-fungible token (of `category`) held in this output.\n\nIf undefined, this defaults to: `\"\"` (a zero-length commitment).", + "description": "The hex-encoded commitment contents included in the non-fungible token held in this output.\n\nIf undefined, this defaults to: `\"\"` (a zero-length commitment).", "type": "string" } }, @@ -471,11 +501,11 @@ }, "WalletTemplateScenarioSourceOutput": { "$ref": "#/definitions/WalletTemplateScenarioOutput", - "description": "A source output used by an wallet template scenario." + "description": "A source output used by a wallet template scenario." }, "WalletTemplateScenarioTransactionOutput": { "$ref": "#/definitions/WalletTemplateScenarioOutput", - "description": "A transaction output used to define an wallet template scenario transaction." + "description": "A transaction output used to define a wallet template scenario transaction." }, "WalletTemplateScript": { "additionalProperties": false, @@ -652,38 +682,20 @@ "additionalProperties": false, "properties": { "description": { - "description": "A single-line, human readable description for this wallet data.", + "description": "A single-line, human readable description for this variable (for use in user interfaces).", "type": "string" }, "name": { - "description": "A single-line, Title Case, human-readable name for this wallet data.", + "description": "A single-line, Title Case, human-readable name for this variable (for use in user interfaces).", "type": "string" }, "type": { "const": "WalletData", - "description": "The `WalletData` type provides a static piece of data that should be collected once and stored at the time of wallet creation. `WalletData` should be persistent for the life of the wallet, rather than changing from locking script to locking script.\n\nFor address-specific data, use `AddressData`.", "type": "string" } }, "required": ["type"], "type": "object" - }, - "AuthenticationVirtualMachineIdentifier": { - "description": "Allowable identifiers for authentication virtual machine versions. The `BCH` prefix identifies the Bitcoin Cash network, the `XEC` prefix identifies the eCash network, the `BSV` prefix identifies the Bitcoin SV network, and the `BTC` prefix identifies the Bitcoin Core network. VM versions are named according to the date they were deployed on the indicated network.\n\nFor each network prefix, a `_SPEC` VM version is reserved to indicate that the template requires a custom, not-yet-deployed VM version (e.g. one or more CHIPs). By convention, templates marked for `_SPEC` VMs should indicate their requirements in the template description. After deployment of the `_SPEC` VM, when template compatibility is verified, the template's `supported` array should be updated to indicate compatibility with the live VM version.", - "enum": [ - "BCH_2020_05", - "BCH_2021_05", - "BCH_2022_05", - "BCH_2023_05", - "BCH_SPEC", - "BSV_2020_02", - "BSV_SPEC", - "BTC_2017_08", - "BTC_SPEC", - "XEC_2020_05", - "XEC_SPEC" - ], - "type": "string" } } } diff --git a/src/editor/template-settings/TemplateSettings.tsx b/src/editor/template-settings/TemplateSettings.tsx index f63d1b7..816492c 100644 --- a/src/editor/template-settings/TemplateSettings.tsx +++ b/src/editor/template-settings/TemplateSettings.tsx @@ -20,10 +20,26 @@ const availableVms: { [key in IDESupportedVM]: React.ReactNode } = { BCH_2023_05 Bitcoin Cash - 2022 May Upgrade + 2023 May Upgrade live ), + BCH_2025_05: ( + + BCH_2025_05 + Bitcoin Cash + 2025 May Upgrade + Spec + + ), + BCH_2026_05: ( + + BCH_2026_05 + Bitcoin Cash + 2026 May Upgrade + Spec + + ), BCH_SPEC: ( BCH_SPEC diff --git a/src/header/HeaderBar.tsx b/src/header/HeaderBar.tsx index d2bd4ea..4a83b68 100644 --- a/src/header/HeaderBar.tsx +++ b/src/header/HeaderBar.tsx @@ -47,7 +47,9 @@ const ideModes: IDESupportedModes[] = [ const vms: IDESupportedVirtualMachine[] = [ { id: 'BCH_2023_05', name: 'BCH 2023 VM', disabled: false }, - // { id: 'BCH_SPEC', name: 'BCH CHIPs VM', disabled: false }, + { id: 'BCH_2025_05', name: 'BCH 2025 VM', disabled: false }, + { id: 'BCH_2026_05', name: 'BCH 2026 VM', disabled: false }, + { id: 'BCH_SPEC', name: 'BCH SPEC VM', disabled: false }, { id: 'BTC_2017_08', name: 'BTC 2017 VM', disabled: true }, { id: 'BSV_2020_02', name: 'BSV 2020 VM', disabled: true }, { id: 'XEC_2020_05', name: 'XEC 2020 VM', disabled: true }, diff --git a/src/state/defaults.ts b/src/state/defaults.ts index 16fd66b..c859c5e 100644 --- a/src/state/defaults.ts +++ b/src/state/defaults.ts @@ -15,8 +15,12 @@ export const emptyTemplate: WalletTemplate = { name: 'Untitled', entities: {}, scripts: {}, - supported: ['BCH_2023_05'] as IDESupportedVM[], - version: 0 as const, + supported: [ + 'BCH_2023_05', + 'BCH_2025_05', + 'BCH_2026_05', + 'BCH_SPEC', + ] as IDESupportedVM[], }; const defaultTemplate = ideImportWalletTemplate(emptyTemplate); @@ -34,18 +38,25 @@ export const defaultState: AppState = { currentScenarioInternalId: undefined, lastSelectedScenarioInternalId: undefined, currentTemplate: defaultTemplate, - currentVmId: 'BCH_2023_05', + currentVmId: 'BCH_2025_05', evaluationViewerSettings: { abbreviateLongStackItems: true, groupStackItemsDeeperThan: 3, - scriptNumbersDisplayFormat: 'integer', + supportBigInt: true, + vmNumbersDisplayFormat: 'integer', reverseStack: false, showAlternateStack: false, identifyStackItems: true, + loopViewingIndexes: [], }, activeDialog: ActiveDialog.none, templateLoadTime: undefined, pendingTemplateImport: undefined, + debug: { + isProcessing: true, + compilationId: 0, + result: undefined, + }, wallets: { // cspell: disable walletsByInternalId: { diff --git a/src/state/import-export.ts b/src/state/import-export.ts index 2733c56..1ef4a57 100644 --- a/src/state/import-export.ts +++ b/src/state/import-export.ts @@ -566,6 +566,5 @@ export const exportWalletTemplate = ( supported: [...currentTemplate.supportedVirtualMachines].sort((a, b) => a.localeCompare(b), ), - version: 0, }; }; diff --git a/src/state/reducer.ts b/src/state/reducer.ts index 35a4409..0b20c5e 100644 --- a/src/state/reducer.ts +++ b/src/state/reducer.ts @@ -1,5 +1,6 @@ /* eslint-disable no-case-declarations, @typescript-eslint/no-unsafe-member-access */ +import { DebugWorkerResult } from '../editor/debug-worker'; import { createInsecureUuidV4, unknownValue } from '../utils'; import { defaultState, emptyTemplate } from './defaults'; @@ -526,6 +527,8 @@ class App extends ImmerReducer { firstSupportedVm !== undefined ) { this.draftState.currentVmId = firstSupportedVm; + this.draftState.evaluationViewerSettings.supportBigInt = + firstSupportedVm !== 'BCH_2023_05'; } this.draftState.templateLoadTime = new Date(); this.draftState.currentTemplate = template; @@ -557,6 +560,19 @@ class App extends ImmerReducer { } activateVm(vm: IDESupportedVM) { this.draftState.currentVmId = vm; + this.draftState.evaluationViewerSettings.supportBigInt = + vm !== 'BCH_2023_05'; + } + startDebugging() { + this.draftState.debug.isProcessing = true; + this.draftState.debug.compilationId += 1; + this.draftState.debug.result = undefined; + } + finishDebugging(payload: DebugWorkerResult) { + if (payload.compilationId === this.draftState.debug.compilationId) { + this.draftState.debug.result = payload.result; + this.draftState.debug.isProcessing = false; + } } } diff --git a/src/state/types.ts b/src/state/types.ts index e9f8e31..f40e70f 100644 --- a/src/state/types.ts +++ b/src/state/types.ts @@ -92,10 +92,12 @@ export type ScenarioDetails = { }; /** * The generated scenario or scenario generation error for this evaluation. + * Undefined before generation has completed. */ generatedScenario: | ScenarioGenerationDebuggingResult - | string; + | string + | undefined; /** * A listing of all available scenarios, including the currently active one. * If `currentScenario` is undefined, this should be an empty list (since @@ -115,6 +117,47 @@ export type ScenarioDetails = { export type ScriptType = BaseScriptType | 'tested' | 'test-check'; +export const scriptTypes: { label: string; value: ScriptType }[] = [ + { label: 'Locking Script', value: 'locking' }, + { label: 'Unlocking Script', value: 'unlocking' }, + { label: 'Isolated Script', value: 'isolated' }, + { label: 'Script Test', value: 'test-setup' }, +]; + +export const typeDescriptions: { [key in ScriptType]: string } = { + locking: + 'Locking scripts hold funds. A locking script is the “challenge” which must be unlocked to spend a transaction output. An “Address” is simply an abstraction for a specific locking script.', + unlocking: + 'An unlocking script spends from a locking script. To create a transaction, the spender must provide a valid unlocking script for each input being spent. (A locking script can be unlocked by multiple unlocking scripts.)', + isolated: + 'An isolated script is useful for constructions like checksums or re-usable utility scripts (which can be used inside other scripts). Isolated scripts can have script tests, e.g. utility scripts can be tested to ensure they perform a series of operations properly.', + 'test-setup': + 'A script test is applied to an isolated script. Each script test has a “setup” phase which is evaluated before the tested script, and a “check” phase which is evaluated after. The test passes if the “check” script leaves a single Script Number 1 on the stack.', + tested: + 'Something is broken: tested scripts should be created by assigning a test-setup script to an isolated script.', + 'test-check': + 'Something is broken: script tests should use the `test-setup` type in this dialog.', +}; + +export type LockingType = WalletTemplateScriptLocking['lockingType']; + +export const lockingTypes: { label: string; value: LockingType }[] = [ + { label: 'P2SH20', value: 'p2sh20' }, + { label: 'P2SH32', value: 'p2sh32' }, + { label: 'P2S', value: 'standard' }, +]; + +export const lockingTypeDescriptions: { + [key in LockingType]: string; +} = { + p2sh20: + 'This is a P2SH20 script: P2SH20-wrapping bytecode is automatically included during compilation.', + p2sh32: + 'This is a P2SH32 script. The P2SH32-wrapping bytecode is automatically included during compilation.', + standard: + 'This is a P2S script: it is compiled directly into the transaction output without P2SH-wrapping bytecode.', +}; + export type BaseScriptType = | 'locking' | 'unlocking' @@ -241,6 +284,8 @@ export type DisableId = true; */ export const IDEVms = [ 'BCH_2023_05', + 'BCH_2025_05', + 'BCH_2026_05', 'BCH_SPEC', 'BSV_2020_02', 'BTC_2017_08', @@ -328,6 +373,24 @@ export type IDEWallets = { utxosByChainPath: { [internalId: string]: IDEUTXOs }; }; +export type DebugDetails = + | { + isProcessing: true; + compilationId: number; + result: undefined; + } + | { + isProcessing: false; + compilationId: number; + result: { + debugTrace: IDESupportedProgramState[]; + verifyResult: string | true; + scenarioGeneration: + | string + | ScenarioGenerationDebuggingResult; + }; + }; + export type AppState = { ideMode: IDEMode; /** @@ -392,6 +455,7 @@ export type AppState = { * display the pending import rather than the default empty template. */ pendingTemplateImport: string | undefined; + debug: DebugDetails; }; export type CurrentScripts = { @@ -422,7 +486,7 @@ export type CurrentVariables = { export type IDESupportedProgramState = AuthenticationProgramStateMinimum & AuthenticationProgramStateStack & AuthenticationProgramStateAlternateStack & - AuthenticationProgramStateControlStack & + AuthenticationProgramStateControlStack & AuthenticationProgramStateError & AuthenticationProgramStateCodeSeparator & AuthenticationProgramStateSignatureAnalysis & @@ -433,10 +497,11 @@ export type IDESupportedProgramState = AuthenticationProgramStateMinimum & */ export type EvaluationViewerSettings = { /** - * If `true`, the EvaluationViewer should aggressively attempt to replace - * valid Script Numbers on the stack with their numerical representation. + * The EvaluationViewer will aggressively attempt to replace valid Script + * Numbers on the stack with the chosen representation. */ - scriptNumbersDisplayFormat: 'hex' | 'integer' | 'binary'; + vmNumbersDisplayFormat: 'hex' | 'integer' | 'binary' | 'bigint'; + supportBigInt: boolean; /** * If `true`, the EvaluationViewer should show the AlternativeStack rather * than the normal stack. @@ -469,4 +534,29 @@ export type EvaluationViewerSettings = { * follow the origin of specific byte sequences). */ identifyStackItems: boolean; + + /** + * An array of numbers indicating the iteration index to display of each + * loop rendered in the EvaluationViewer. Each array item corresponds to a + * visible loop in source order. + * + * Note that this setting is global: when switching between active scripts, + * the editor attempts to continue displaying the same iteration index as a + * convenience; this is often useful when reviewing different code paths of + * the same contract or when switching rapidly between contracts. + * + * By design, if any index of the current `loopViewingIndexes` is set to an + * index which is not valid in the current evaluation, the viewer will + * automatically display iteration `0` of that particular loop without + * modifying `loopViewingIndexes`, retaining the user's configured + * loop-viewing state until the user manually changes the respective loop + * viewing index. This allows the viewer to maintain the users configured + * state through program changes which shorten and then revert the number of + * iterations performed by a particular loop. + * + * Note also that tested scripts therefore share loop viewing indexes between + * the tested script and script test (a useful feature, as the tested script + * will often iterate in the same way as the tested script). + */ + loopViewingIndexes: number[]; }; diff --git a/src/templates/2-of-2-recoverable.json b/src/templates/2-of-2-recoverable.json index 78ca148..f5e2ecf 100644 --- a/src/templates/2-of-2-recoverable.json +++ b/src/templates/2-of-2-recoverable.json @@ -74,7 +74,7 @@ "fails": ["before_recovery_time"], "name": "Recover – Signer 1", "passes": ["after_recovery_time"], - "script": "<0>\n\n\n<1>", + "script": "<0>\n\n\n<1>", "timeLockType": "timestamp", "unlocks": "lock" }, @@ -83,7 +83,7 @@ "fails": ["before_recovery_time"], "name": "Recover – Signer 2", "passes": ["after_recovery_time"], - "script": "<0>\n\n\n<1>", + "script": "<0>\n\n\n<1>", "timeLockType": "timestamp", "unlocks": "lock" }, @@ -91,10 +91,20 @@ "estimate": "before_recovery_time", "name": "Standard Spend", "passes": ["after_recovery_time", "before_recovery_time"], - "script": "<0>\n\n\n<0>", + "script": "<0>\n\n\n<0>", "unlocks": "lock" } }, - "supported": ["BCH_2023_05", "BCH_SPEC", "BSV_2020_02", "BTC_2017_08"], + "supported": [ + "BCH_2020_05", + "BCH_2021_05", + "BCH_2022_05", + "BCH_2023_05", + "BCH_2024_05", + "BCH_2025_05", + "BCH_2026_05", + "BCH_SPEC", + "BTC_2017_08" + ], "version": 0 } diff --git a/src/templates/2-of-3.json b/src/templates/2-of-3.json index 989d55a..182a959 100644 --- a/src/templates/2-of-3.json +++ b/src/templates/2-of-3.json @@ -1,4 +1,5 @@ { + "$schema": "https://libauth.org/schemas/wallet-template-v0.schema.json", "name": "2-of-3 Multisig", "entities": { "signer_1": { @@ -32,17 +33,17 @@ "scripts": { "1_and_2": { "name": "Cosigner 1 & 2", - "script": "OP_0\n\n", + "script": "<0b011>\n\n", "unlocks": "lock" }, "1_and_3": { "name": "Cosigner 1 & 3", - "script": "OP_0\n\n", + "script": "<0b101>\n\n", "unlocks": "lock" }, "2_and_3": { "name": "Cosigner 2 & 3", - "script": "OP_0\n\n", + "script": "<0b110>\n\n", "unlocks": "lock" }, "lock": { @@ -51,6 +52,15 @@ "script": "OP_2\n\n\n\nOP_3\nOP_CHECKMULTISIG" } }, - "supported": ["BCH_2021_05", "BCH_2022_05"], - "version": 0 + "supported": [ + "BCH_2020_05", + "BCH_2021_05", + "BCH_2022_05", + "BCH_2023_05", + "BCH_2024_05", + "BCH_2025_05", + "BCH_2026_05", + "BCH_SPEC", + "BTC_2017_08" + ] } diff --git a/tests/editor.spec.ts b/tests/editor.spec.ts index bb9bbdf..e314962 100644 --- a/tests/editor.spec.ts +++ b/tests/editor.spec.ts @@ -73,3 +73,19 @@ test('ignores misleading unicode characters, shows compilation on hover', async await expect(page.getByText('Compiled: 0x03666974')).toBeVisible(); await expect(page).toHaveScreenshot(); }); + +test('renders loops as expected', async ({ page }) => { + await loadTemplate(page, 'tests/fixtures/loops.json'); + await page.getByRole('button', { name: 'Nested Loops', exact: true }).click(); + await expect( + page.getByRole('heading', { name: 'Nested Loops P2SH', exact: true }), + ).toBeVisible(); + await expect(page).toHaveScreenshot(); + await page.locator('.loop-start').first().hover(); + await page.locator('.loop-start').first().click(); + await page.locator('.bp5-slider-label').nth(1).click(); + await expect(page).toHaveScreenshot(); + await page.locator('.loop-start').nth(2).hover(); + await page.locator('.loop-start').nth(2).click(); + await expect(page).toHaveScreenshot(); +}); diff --git a/tests/editor.spec.ts-snapshots/renders-loops-as-expected-1-chromium-linux.png b/tests/editor.spec.ts-snapshots/renders-loops-as-expected-1-chromium-linux.png new file mode 100644 index 0000000..c058237 Binary files /dev/null and b/tests/editor.spec.ts-snapshots/renders-loops-as-expected-1-chromium-linux.png differ diff --git a/tests/editor.spec.ts-snapshots/renders-loops-as-expected-1-firefox-linux.png b/tests/editor.spec.ts-snapshots/renders-loops-as-expected-1-firefox-linux.png new file mode 100644 index 0000000..b2d7c53 Binary files /dev/null and b/tests/editor.spec.ts-snapshots/renders-loops-as-expected-1-firefox-linux.png differ diff --git a/tests/editor.spec.ts-snapshots/renders-loops-as-expected-1-mobile-webkit-linux.png b/tests/editor.spec.ts-snapshots/renders-loops-as-expected-1-mobile-webkit-linux.png new file mode 100644 index 0000000..faa5d3d Binary files /dev/null and b/tests/editor.spec.ts-snapshots/renders-loops-as-expected-1-mobile-webkit-linux.png differ diff --git a/tests/editor.spec.ts-snapshots/renders-loops-as-expected-1-webkit-linux.png b/tests/editor.spec.ts-snapshots/renders-loops-as-expected-1-webkit-linux.png new file mode 100644 index 0000000..c0a8f19 Binary files /dev/null and b/tests/editor.spec.ts-snapshots/renders-loops-as-expected-1-webkit-linux.png differ diff --git a/tests/editor.spec.ts-snapshots/renders-loops-as-expected-2-chromium-linux.png b/tests/editor.spec.ts-snapshots/renders-loops-as-expected-2-chromium-linux.png new file mode 100644 index 0000000..827d278 Binary files /dev/null and b/tests/editor.spec.ts-snapshots/renders-loops-as-expected-2-chromium-linux.png differ diff --git a/tests/editor.spec.ts-snapshots/renders-loops-as-expected-2-firefox-linux.png b/tests/editor.spec.ts-snapshots/renders-loops-as-expected-2-firefox-linux.png new file mode 100644 index 0000000..e31ff94 Binary files /dev/null and b/tests/editor.spec.ts-snapshots/renders-loops-as-expected-2-firefox-linux.png differ diff --git a/tests/editor.spec.ts-snapshots/renders-loops-as-expected-2-mobile-webkit-linux.png b/tests/editor.spec.ts-snapshots/renders-loops-as-expected-2-mobile-webkit-linux.png new file mode 100644 index 0000000..92f8c86 Binary files /dev/null and b/tests/editor.spec.ts-snapshots/renders-loops-as-expected-2-mobile-webkit-linux.png differ diff --git a/tests/editor.spec.ts-snapshots/renders-loops-as-expected-2-webkit-linux.png b/tests/editor.spec.ts-snapshots/renders-loops-as-expected-2-webkit-linux.png new file mode 100644 index 0000000..cd43e23 Binary files /dev/null and b/tests/editor.spec.ts-snapshots/renders-loops-as-expected-2-webkit-linux.png differ diff --git a/tests/editor.spec.ts-snapshots/renders-loops-as-expected-3-chromium-linux.png b/tests/editor.spec.ts-snapshots/renders-loops-as-expected-3-chromium-linux.png new file mode 100644 index 0000000..48c5d94 Binary files /dev/null and b/tests/editor.spec.ts-snapshots/renders-loops-as-expected-3-chromium-linux.png differ diff --git a/tests/editor.spec.ts-snapshots/renders-loops-as-expected-3-firefox-linux.png b/tests/editor.spec.ts-snapshots/renders-loops-as-expected-3-firefox-linux.png new file mode 100644 index 0000000..636e8a1 Binary files /dev/null and b/tests/editor.spec.ts-snapshots/renders-loops-as-expected-3-firefox-linux.png differ diff --git a/tests/editor.spec.ts-snapshots/renders-loops-as-expected-3-mobile-webkit-linux.png b/tests/editor.spec.ts-snapshots/renders-loops-as-expected-3-mobile-webkit-linux.png new file mode 100644 index 0000000..5f431c0 Binary files /dev/null and b/tests/editor.spec.ts-snapshots/renders-loops-as-expected-3-mobile-webkit-linux.png differ diff --git a/tests/editor.spec.ts-snapshots/renders-loops-as-expected-3-webkit-linux.png b/tests/editor.spec.ts-snapshots/renders-loops-as-expected-3-webkit-linux.png new file mode 100644 index 0000000..ddf0a55 Binary files /dev/null and b/tests/editor.spec.ts-snapshots/renders-loops-as-expected-3-webkit-linux.png differ diff --git a/tests/editor.spec.ts-snapshots/renders-spacers-as-expected-1-chromium-linux.png b/tests/editor.spec.ts-snapshots/renders-spacers-as-expected-1-chromium-linux.png index 55c3af9..37c2d33 100644 Binary files a/tests/editor.spec.ts-snapshots/renders-spacers-as-expected-1-chromium-linux.png and b/tests/editor.spec.ts-snapshots/renders-spacers-as-expected-1-chromium-linux.png differ diff --git a/tests/editor.spec.ts-snapshots/renders-spacers-as-expected-1-firefox-linux.png b/tests/editor.spec.ts-snapshots/renders-spacers-as-expected-1-firefox-linux.png index 9d4515e..b22e97a 100644 Binary files a/tests/editor.spec.ts-snapshots/renders-spacers-as-expected-1-firefox-linux.png and b/tests/editor.spec.ts-snapshots/renders-spacers-as-expected-1-firefox-linux.png differ diff --git a/tests/editor.spec.ts-snapshots/renders-spacers-as-expected-1-mobile-webkit-linux.png b/tests/editor.spec.ts-snapshots/renders-spacers-as-expected-1-mobile-webkit-linux.png index 367022c..17c9b09 100644 Binary files a/tests/editor.spec.ts-snapshots/renders-spacers-as-expected-1-mobile-webkit-linux.png and b/tests/editor.spec.ts-snapshots/renders-spacers-as-expected-1-mobile-webkit-linux.png differ diff --git a/tests/editor.spec.ts-snapshots/renders-spacers-as-expected-1-webkit-linux.png b/tests/editor.spec.ts-snapshots/renders-spacers-as-expected-1-webkit-linux.png index b9a065c..cc640c4 100644 Binary files a/tests/editor.spec.ts-snapshots/renders-spacers-as-expected-1-webkit-linux.png and b/tests/editor.spec.ts-snapshots/renders-spacers-as-expected-1-webkit-linux.png differ diff --git a/tests/editor.spec.ts-snapshots/renders-spacers-as-expected-2-chromium-linux.png b/tests/editor.spec.ts-snapshots/renders-spacers-as-expected-2-chromium-linux.png index f85a9d5..f16149f 100644 Binary files a/tests/editor.spec.ts-snapshots/renders-spacers-as-expected-2-chromium-linux.png and b/tests/editor.spec.ts-snapshots/renders-spacers-as-expected-2-chromium-linux.png differ diff --git a/tests/editor.spec.ts-snapshots/renders-spacers-as-expected-2-firefox-linux.png b/tests/editor.spec.ts-snapshots/renders-spacers-as-expected-2-firefox-linux.png index 6633a91..4269c67 100644 Binary files a/tests/editor.spec.ts-snapshots/renders-spacers-as-expected-2-firefox-linux.png and b/tests/editor.spec.ts-snapshots/renders-spacers-as-expected-2-firefox-linux.png differ diff --git a/tests/editor.spec.ts-snapshots/renders-spacers-as-expected-2-mobile-webkit-linux.png b/tests/editor.spec.ts-snapshots/renders-spacers-as-expected-2-mobile-webkit-linux.png index 3dc63e7..0ccaf64 100644 Binary files a/tests/editor.spec.ts-snapshots/renders-spacers-as-expected-2-mobile-webkit-linux.png and b/tests/editor.spec.ts-snapshots/renders-spacers-as-expected-2-mobile-webkit-linux.png differ diff --git a/tests/editor.spec.ts-snapshots/renders-spacers-as-expected-2-webkit-linux.png b/tests/editor.spec.ts-snapshots/renders-spacers-as-expected-2-webkit-linux.png index 61bf34a..e8b3b80 100644 Binary files a/tests/editor.spec.ts-snapshots/renders-spacers-as-expected-2-webkit-linux.png and b/tests/editor.spec.ts-snapshots/renders-spacers-as-expected-2-webkit-linux.png differ diff --git a/tests/entity-settings.spec.ts-snapshots/warns-about-duplicate-entity-IDs-1-chromium-linux.png b/tests/entity-settings.spec.ts-snapshots/warns-about-duplicate-entity-IDs-1-chromium-linux.png index c1a0b12..43d9b2e 100644 Binary files a/tests/entity-settings.spec.ts-snapshots/warns-about-duplicate-entity-IDs-1-chromium-linux.png and b/tests/entity-settings.spec.ts-snapshots/warns-about-duplicate-entity-IDs-1-chromium-linux.png differ diff --git a/tests/entity-settings.spec.ts-snapshots/warns-about-duplicate-entity-IDs-1-firefox-linux.png b/tests/entity-settings.spec.ts-snapshots/warns-about-duplicate-entity-IDs-1-firefox-linux.png index 92cb079..3e39f93 100644 Binary files a/tests/entity-settings.spec.ts-snapshots/warns-about-duplicate-entity-IDs-1-firefox-linux.png and b/tests/entity-settings.spec.ts-snapshots/warns-about-duplicate-entity-IDs-1-firefox-linux.png differ diff --git a/tests/entity-settings.spec.ts-snapshots/warns-about-duplicate-entity-IDs-1-mobile-webkit-linux.png b/tests/entity-settings.spec.ts-snapshots/warns-about-duplicate-entity-IDs-1-mobile-webkit-linux.png index 8c3c32d..77e9681 100644 Binary files a/tests/entity-settings.spec.ts-snapshots/warns-about-duplicate-entity-IDs-1-mobile-webkit-linux.png and b/tests/entity-settings.spec.ts-snapshots/warns-about-duplicate-entity-IDs-1-mobile-webkit-linux.png differ diff --git a/tests/entity-settings.spec.ts-snapshots/warns-about-duplicate-entity-IDs-1-webkit-linux.png b/tests/entity-settings.spec.ts-snapshots/warns-about-duplicate-entity-IDs-1-webkit-linux.png index c0eec6d..76c21e8 100644 Binary files a/tests/entity-settings.spec.ts-snapshots/warns-about-duplicate-entity-IDs-1-webkit-linux.png and b/tests/entity-settings.spec.ts-snapshots/warns-about-duplicate-entity-IDs-1-webkit-linux.png differ diff --git a/tests/fixtures/empty.json b/tests/fixtures/empty.json index 1513f07..ea19c14 100644 --- a/tests/fixtures/empty.json +++ b/tests/fixtures/empty.json @@ -1,5 +1,5 @@ { - "$schema": "https://ide.bitauth.com/authentication-template-v0.schema.json", + "$schema": "https://libauth.org/schemas/wallet-template-v0.schema.json", "description": "An empty wallet template.\n", "name": "empty.json", "entities": {}, @@ -15,6 +15,5 @@ "script": "" } }, - "supported": ["BCH_2023_05"], - "version": 0 + "supported": ["BCH_2023_05", "BCH_2025_05"] } diff --git a/tests/fixtures/loops.json b/tests/fixtures/loops.json new file mode 100644 index 0000000..7656eae --- /dev/null +++ b/tests/fixtures/loops.json @@ -0,0 +1,13 @@ +{ + "$schema": "https://libauth.org/schemas/wallet-template-v0.schema.json", + "description": "Render loops and loop controls in the evaluation viewer.\n", + "name": "loops.json", + "entities": {}, + "scripts": { + "nested_loops": { + "name": "Nested Loops", + "script": "\n<0> <0>\nOP_BEGIN\n\n <1> OP_IF \n <0> OP_BEGIN OP_1ADD OP_DUP <2> OP_EQUAL OP_UNTIL\n <1> <1> OP_BEGIN \n OP_UNTIL OP_BEGIN\n OP_UNTIL\n OP_ENDIF\n OP_ADD\n OP_SWAP OP_1ADD OP_SWAP\n OP_OVER\n <1> OP_IF \n <0> OP_BEGIN OP_BEGIN\n OP_1ADD\n OP_DUP\n <$(\n <0> OP_BEGIN \n OP_1ADD \n OP_DUP <5>\n OP_EQUAL\n OP_UNTIL\n )>\n OP_EQUAL\n OP_UNTIL <1> OP_UNTIL\n OP_ENDIF\n OP_EQUAL\nOP_UNTIL\nOP_NIP\n" + } + }, + "supported": ["BCH_SPEC"] +} diff --git a/tests/fixtures/network/success/bf273322db1709581003eb1f05a66be408864eed.json b/tests/fixtures/network/success/bf273322db1709581003eb1f05a66be408864eed.json index 3bd50fa..452baa9 100644 --- a/tests/fixtures/network/success/bf273322db1709581003eb1f05a66be408864eed.json +++ b/tests/fixtures/network/success/bf273322db1709581003eb1f05a66be408864eed.json @@ -24,7 +24,7 @@ "raw_url": "https://gist.githubusercontent.com/bitjson/a055ad6ba863a4472767bb5e441a3437/raw/9c014d7e356f33c1b981d07538a936434a08fc27/single-sig.json", "size": 1254, "truncated": false, - "content": "{\n \"$schema\": \"https://ide.bitauth.com/authentication-template-v0.schema.json\",\n \"description\": \"A standard single-factor authentication template that uses Pay-to-Public-Key-Hash (P2PKH), the most common authentication scheme in use on the network.\\n\\nThis P2PKH template uses BCH Schnorr signatures, reducing the size of transactions. Because the template uses a Hierarchical Deterministic (HD) key, it also supports watch-only clients.\",\n \"name\": \"Single Signature (P2PKH)\",\n \"entities\": {\n \"owner\": {\n \"description\": \"The individual who can spend from this wallet.\",\n \"name\": \"Owner\",\n \"scripts\": [\"lock\", \"unlock\"],\n \"variables\": {\n \"key\": {\n \"description\": \"The private key that controls this wallet.\",\n \"name\": \"Key\",\n \"type\": \"HdKey\"\n }\n }\n }\n },\n \"scripts\": {\n \"unlock\": {\n \"name\": \"Unlock\",\n \"script\": \"\\n\",\n \"unlocks\": \"lock\"\n },\n \"lock\": {\n \"lockingType\": \"standard\",\n \"name\": \"P2PKH Lock\",\n \"script\": \"OP_DUP\\nOP_HASH160 <$( OP_HASH160\\n)> OP_EQUALVERIFY\\nOP_CHECKSIG\"\n }\n },\n \"supported\": [\"BCH_2020_05\", \"BCH_2021_05\", \"BCH_2022_05\"],\n \"version\": 0\n}" + "content": "{\n \"$schema\": \"https://libauth.org/schemas/wallet-template-v0.schema.json\",\n \"description\": \"A standard single-factor authentication template that uses Pay-to-Public-Key-Hash (P2PKH), the most common authentication scheme in use on the network.\\n\\nThis P2PKH template uses BCH Schnorr signatures, reducing the size of transactions. Because the template uses a Hierarchical Deterministic (HD) key, it also supports watch-only clients.\",\n \"name\": \"Single Signature (P2PKH)\",\n \"entities\": {\n \"owner\": {\n \"description\": \"The individual who can spend from this wallet.\",\n \"name\": \"Owner\",\n \"scripts\": [\"lock\", \"unlock\"],\n \"variables\": {\n \"key\": {\n \"description\": \"The private key that controls this wallet.\",\n \"name\": \"Key\",\n \"type\": \"HdKey\"\n }\n }\n }\n },\n \"scripts\": {\n \"unlock\": {\n \"name\": \"Unlock\",\n \"script\": \"\\n\",\n \"unlocks\": \"lock\"\n },\n \"lock\": {\n \"lockingType\": \"standard\",\n \"name\": \"P2PKH Lock\",\n \"script\": \"OP_DUP\\nOP_HASH160 <$( OP_HASH160\\n)> OP_EQUALVERIFY\\nOP_CHECKSIG\"\n }\n },\n \"supported\": [\"BCH_2020_05\", \"BCH_2021_05\", \"BCH_2022_05\"],\n \"version\": 0\n}" } }, "public": true, diff --git a/tests/fixtures/non-push-unlocking-opcode.json b/tests/fixtures/non-push-unlocking-opcode.json index 0680312..bceae41 100644 --- a/tests/fixtures/non-push-unlocking-opcode.json +++ b/tests/fixtures/non-push-unlocking-opcode.json @@ -1,5 +1,5 @@ { - "$schema": "https://ide.bitauth.com/authentication-template-v0.schema.json", + "$schema": "https://libauth.org/schemas/wallet-template-v0.schema.json", "description": "Render the error for a non-push opcode located in unlocking bytecode.\n", "name": "non-push-unlocking-opcode.json", "entities": {}, @@ -15,6 +15,5 @@ "script": "OP_ADD\n" } }, - "supported": ["BCH_2023_05"], - "version": 0 + "supported": ["BCH_2023_05", "BCH_2025_05"] } diff --git a/tests/fixtures/single_signature_p2pkh.wallet-template.json b/tests/fixtures/single_signature_p2pkh.wallet-template.json index bea1d20..7ff0777 100644 --- a/tests/fixtures/single_signature_p2pkh.wallet-template.json +++ b/tests/fixtures/single_signature_p2pkh.wallet-template.json @@ -1,5 +1,5 @@ { - "$schema": "https://ide.bitauth.com/authentication-template-v0.schema.json", + "$schema": "https://libauth.org/schemas/wallet-template-v0.schema.json", "description": "A standard single-factor wallet template that uses Pay-to-Public-Key-Hash (P2PKH), the most common authentication scheme in use on the network.\n\nThis P2PKH template uses BCH Schnorr signatures, reducing the size of transactions. Because the template uses a Hierarchical Deterministic (HD) key, it also supports watch-only clients.", "name": "Single Signature (P2PKH)", "entities": { @@ -28,6 +28,14 @@ "script": "OP_DUP\nOP_HASH160 <$( OP_HASH160\n)> OP_EQUALVERIFY\nOP_CHECKSIG" } }, - "supported": ["BCH_2020_05", "BCH_2021_05", "BCH_2022_05"], - "version": 0 + "supported": [ + "BCH_2020_05", + "BCH_2021_05", + "BCH_2022_05", + "BCH_2023_05", + "BCH_2024_05", + "BCH_2025_05", + "BCH_2026_05", + "BCH_SPEC" + ] } diff --git a/tests/fixtures/spacers.json b/tests/fixtures/spacers.json index f1c11b0..a2bf92c 100644 --- a/tests/fixtures/spacers.json +++ b/tests/fixtures/spacers.json @@ -1,5 +1,5 @@ { - "$schema": "https://ide.bitauth.com/authentication-template-v0.schema.json", + "$schema": "https://libauth.org/schemas/wallet-template-v0.schema.json", "description": "Render an example of deeply-nested spacers in the evaluation viewer.", "name": "spacers.json", "entities": {}, @@ -20,6 +20,5 @@ "script": "OP_IF\n <1> OP_IF\n <\n $(<'eval_1'> \n <$(\n <'eval_2'>\n <1> OP_IF\n OP_DROP\n <'inner_if'>\n OP_ENDIF\n )>\n OP_2DROP\n <1> OP_IF\n <'if_1'>\n <1> OP_IF\n <'if_2'>\n OP_ENDIF \n OP_DROP\n OP_ENDIF\n )\n > // push happens after this line\n OP_ELSE <'never'>\n OP_ENDIF\nOP_ELSE\n <0>\nOP_ELSE <'Multiple OP_ELSE quirk'> OP_DROP\nOP_ELSE OP_NOT\nOP_ENDIF \nOP_0NOTEQUAL" } }, - "supported": ["BCH_2023_05"], - "version": 0 + "supported": ["BCH_2023_05", "BCH_2025_05"] } diff --git a/tests/fixtures/state-merkle-trees.json b/tests/fixtures/state-merkle-trees.json index ca3e11c..7ae47ba 100644 --- a/tests/fixtures/state-merkle-trees.json +++ b/tests/fixtures/state-merkle-trees.json @@ -1,5 +1,5 @@ { - "$schema": "https://ide.bitauth.com/authentication-template-v0.schema.json", + "$schema": "https://libauth.org/schemas/wallet-template-v0.schema.json", "description": "This contract demonstrates a simple Merkle tree leaf replacement within a covenant. Leaves can be used to maintain any kind of state that a covenant must manage internally like sealed ballots (e.g. shareholder votes), deposit receipts (e.g. for issuing refunds), an order book, etc.\n\nThis demonstration uses a 3-level Merkle tree for which each leaf has been initialized to OP_0:\n \n rt\n / \\\n z0 z1\n / \\ / \\\n y0 y1 y2 y3\n /\\ /\\ /\\ /\\\n x0 x1 x2 x3 x4 x5 x6 x7\n | | | | | | | |\n a0 a1 a2 a3 a4 a5 a6 a7\n\nIn this demo, both \"before\" and \"after\" Merkle trees are simultaneously built/validated for each tree level, optimizing contract size.\n\nYou can explore scripts on the left sidebar:\n\n- See the \"Replace Empty Leaf\" script for a full demo.\n- See the \"Left Sibling\" and \"Right Sibling\" scripts for unit tests of a single tree level.\n\nNote: Merkle trees are typically less efficient for state management than token commitments. Before choosing a Merkle tree, verify that the intended use case cannot be served by placing state in token commitments issued either to certain participants or to covenants designed for this purpose.", "name": "Experimenting with Contract State Merkle Trees", "entities": {}, @@ -103,6 +103,5 @@ } } }, - "supported": ["BCH_2022_05"], - "version": 0 + "supported": ["BCH_2022_05"] } diff --git a/tests/screenshots.spec.ts-snapshots/template-settings-chromium-hd-linux.png b/tests/screenshots.spec.ts-snapshots/template-settings-chromium-hd-linux.png index 344c47b..2a9142e 100644 Binary files a/tests/screenshots.spec.ts-snapshots/template-settings-chromium-hd-linux.png and b/tests/screenshots.spec.ts-snapshots/template-settings-chromium-hd-linux.png differ diff --git a/tests/welcome.spec.ts-snapshots/can-import-a-template-1-chromium-linux.png b/tests/welcome.spec.ts-snapshots/can-import-a-template-1-chromium-linux.png index 734e795..bfad261 100644 Binary files a/tests/welcome.spec.ts-snapshots/can-import-a-template-1-chromium-linux.png and b/tests/welcome.spec.ts-snapshots/can-import-a-template-1-chromium-linux.png differ diff --git a/tests/welcome.spec.ts-snapshots/can-import-a-template-1-firefox-linux.png b/tests/welcome.spec.ts-snapshots/can-import-a-template-1-firefox-linux.png index 3cfd088..f919f16 100644 Binary files a/tests/welcome.spec.ts-snapshots/can-import-a-template-1-firefox-linux.png and b/tests/welcome.spec.ts-snapshots/can-import-a-template-1-firefox-linux.png differ diff --git a/tests/welcome.spec.ts-snapshots/can-import-a-template-1-mobile-webkit-linux.png b/tests/welcome.spec.ts-snapshots/can-import-a-template-1-mobile-webkit-linux.png index 78b532f..3d25be7 100644 Binary files a/tests/welcome.spec.ts-snapshots/can-import-a-template-1-mobile-webkit-linux.png and b/tests/welcome.spec.ts-snapshots/can-import-a-template-1-mobile-webkit-linux.png differ diff --git a/tests/welcome.spec.ts-snapshots/can-import-a-template-1-webkit-linux.png b/tests/welcome.spec.ts-snapshots/can-import-a-template-1-webkit-linux.png index 7846b9a..074e22e 100644 Binary files a/tests/welcome.spec.ts-snapshots/can-import-a-template-1-webkit-linux.png and b/tests/welcome.spec.ts-snapshots/can-import-a-template-1-webkit-linux.png differ diff --git a/tests/welcome.spec.ts-snapshots/can-import-a-template-2-chromium-linux.png b/tests/welcome.spec.ts-snapshots/can-import-a-template-2-chromium-linux.png index 1010e8d..ae21539 100644 Binary files a/tests/welcome.spec.ts-snapshots/can-import-a-template-2-chromium-linux.png and b/tests/welcome.spec.ts-snapshots/can-import-a-template-2-chromium-linux.png differ diff --git a/tests/welcome.spec.ts-snapshots/can-import-a-template-2-firefox-linux.png b/tests/welcome.spec.ts-snapshots/can-import-a-template-2-firefox-linux.png index 9eee7a8..2b61bba 100644 Binary files a/tests/welcome.spec.ts-snapshots/can-import-a-template-2-firefox-linux.png and b/tests/welcome.spec.ts-snapshots/can-import-a-template-2-firefox-linux.png differ diff --git a/tests/welcome.spec.ts-snapshots/can-import-a-template-2-mobile-webkit-linux.png b/tests/welcome.spec.ts-snapshots/can-import-a-template-2-mobile-webkit-linux.png index 1ae2600..f204803 100644 Binary files a/tests/welcome.spec.ts-snapshots/can-import-a-template-2-mobile-webkit-linux.png and b/tests/welcome.spec.ts-snapshots/can-import-a-template-2-mobile-webkit-linux.png differ diff --git a/tests/welcome.spec.ts-snapshots/can-import-a-template-2-webkit-linux.png b/tests/welcome.spec.ts-snapshots/can-import-a-template-2-webkit-linux.png index d948e4d..61d5f29 100644 Binary files a/tests/welcome.spec.ts-snapshots/can-import-a-template-2-webkit-linux.png and b/tests/welcome.spec.ts-snapshots/can-import-a-template-2-webkit-linux.png differ diff --git a/tests/welcome.spec.ts-snapshots/can-import-a-template-3-chromium-linux.png b/tests/welcome.spec.ts-snapshots/can-import-a-template-3-chromium-linux.png index c5572e7..1edc58c 100644 Binary files a/tests/welcome.spec.ts-snapshots/can-import-a-template-3-chromium-linux.png and b/tests/welcome.spec.ts-snapshots/can-import-a-template-3-chromium-linux.png differ diff --git a/tests/welcome.spec.ts-snapshots/can-import-a-template-3-firefox-linux.png b/tests/welcome.spec.ts-snapshots/can-import-a-template-3-firefox-linux.png index 822d776..5e4812c 100644 Binary files a/tests/welcome.spec.ts-snapshots/can-import-a-template-3-firefox-linux.png and b/tests/welcome.spec.ts-snapshots/can-import-a-template-3-firefox-linux.png differ diff --git a/tests/welcome.spec.ts-snapshots/can-import-a-template-3-mobile-webkit-linux.png b/tests/welcome.spec.ts-snapshots/can-import-a-template-3-mobile-webkit-linux.png index e8c277b..bb83995 100644 Binary files a/tests/welcome.spec.ts-snapshots/can-import-a-template-3-mobile-webkit-linux.png and b/tests/welcome.spec.ts-snapshots/can-import-a-template-3-mobile-webkit-linux.png differ diff --git a/tests/welcome.spec.ts-snapshots/can-import-a-template-3-webkit-linux.png b/tests/welcome.spec.ts-snapshots/can-import-a-template-3-webkit-linux.png index ee9d750..e4d9bec 100644 Binary files a/tests/welcome.spec.ts-snapshots/can-import-a-template-3-webkit-linux.png and b/tests/welcome.spec.ts-snapshots/can-import-a-template-3-webkit-linux.png differ diff --git a/tests/welcome.spec.ts-snapshots/loads-the-multisig-template-1-chromium-linux.png b/tests/welcome.spec.ts-snapshots/loads-the-multisig-template-1-chromium-linux.png index f0d7e24..7cae150 100644 Binary files a/tests/welcome.spec.ts-snapshots/loads-the-multisig-template-1-chromium-linux.png and b/tests/welcome.spec.ts-snapshots/loads-the-multisig-template-1-chromium-linux.png differ diff --git a/tests/welcome.spec.ts-snapshots/loads-the-multisig-template-1-firefox-linux.png b/tests/welcome.spec.ts-snapshots/loads-the-multisig-template-1-firefox-linux.png index 203a51a..68a1c7d 100644 Binary files a/tests/welcome.spec.ts-snapshots/loads-the-multisig-template-1-firefox-linux.png and b/tests/welcome.spec.ts-snapshots/loads-the-multisig-template-1-firefox-linux.png differ diff --git a/tests/welcome.spec.ts-snapshots/loads-the-multisig-template-1-mobile-webkit-linux.png b/tests/welcome.spec.ts-snapshots/loads-the-multisig-template-1-mobile-webkit-linux.png index b9dde41..a9f8326 100644 Binary files a/tests/welcome.spec.ts-snapshots/loads-the-multisig-template-1-mobile-webkit-linux.png and b/tests/welcome.spec.ts-snapshots/loads-the-multisig-template-1-mobile-webkit-linux.png differ diff --git a/tests/welcome.spec.ts-snapshots/loads-the-multisig-template-1-webkit-linux.png b/tests/welcome.spec.ts-snapshots/loads-the-multisig-template-1-webkit-linux.png index ca3999d..0f67431 100644 Binary files a/tests/welcome.spec.ts-snapshots/loads-the-multisig-template-1-webkit-linux.png and b/tests/welcome.spec.ts-snapshots/loads-the-multisig-template-1-webkit-linux.png differ diff --git a/tests/welcome.spec.ts-snapshots/loads-the-scratch-pad-template-1-chromium-linux.png b/tests/welcome.spec.ts-snapshots/loads-the-scratch-pad-template-1-chromium-linux.png index 0eeffa0..ea9d1de 100644 Binary files a/tests/welcome.spec.ts-snapshots/loads-the-scratch-pad-template-1-chromium-linux.png and b/tests/welcome.spec.ts-snapshots/loads-the-scratch-pad-template-1-chromium-linux.png differ diff --git a/tests/welcome.spec.ts-snapshots/loads-the-scratch-pad-template-1-firefox-linux.png b/tests/welcome.spec.ts-snapshots/loads-the-scratch-pad-template-1-firefox-linux.png index b80a34b..50b816c 100644 Binary files a/tests/welcome.spec.ts-snapshots/loads-the-scratch-pad-template-1-firefox-linux.png and b/tests/welcome.spec.ts-snapshots/loads-the-scratch-pad-template-1-firefox-linux.png differ diff --git a/tests/welcome.spec.ts-snapshots/loads-the-scratch-pad-template-1-mobile-webkit-linux.png b/tests/welcome.spec.ts-snapshots/loads-the-scratch-pad-template-1-mobile-webkit-linux.png index 4012fe4..13ccd4d 100644 Binary files a/tests/welcome.spec.ts-snapshots/loads-the-scratch-pad-template-1-mobile-webkit-linux.png and b/tests/welcome.spec.ts-snapshots/loads-the-scratch-pad-template-1-mobile-webkit-linux.png differ diff --git a/tests/welcome.spec.ts-snapshots/loads-the-scratch-pad-template-1-webkit-linux.png b/tests/welcome.spec.ts-snapshots/loads-the-scratch-pad-template-1-webkit-linux.png index 39cd406..16752cf 100644 Binary files a/tests/welcome.spec.ts-snapshots/loads-the-scratch-pad-template-1-webkit-linux.png and b/tests/welcome.spec.ts-snapshots/loads-the-scratch-pad-template-1-webkit-linux.png differ diff --git a/tests/welcome.spec.ts-snapshots/loads-the-scratch-pad-template-2-chromium-linux.png b/tests/welcome.spec.ts-snapshots/loads-the-scratch-pad-template-2-chromium-linux.png index 0481753..c770ab6 100644 Binary files a/tests/welcome.spec.ts-snapshots/loads-the-scratch-pad-template-2-chromium-linux.png and b/tests/welcome.spec.ts-snapshots/loads-the-scratch-pad-template-2-chromium-linux.png differ diff --git a/tests/welcome.spec.ts-snapshots/loads-the-scratch-pad-template-2-firefox-linux.png b/tests/welcome.spec.ts-snapshots/loads-the-scratch-pad-template-2-firefox-linux.png index 9c815b9..5fb0621 100644 Binary files a/tests/welcome.spec.ts-snapshots/loads-the-scratch-pad-template-2-firefox-linux.png and b/tests/welcome.spec.ts-snapshots/loads-the-scratch-pad-template-2-firefox-linux.png differ diff --git a/tests/welcome.spec.ts-snapshots/loads-the-scratch-pad-template-2-mobile-webkit-linux.png b/tests/welcome.spec.ts-snapshots/loads-the-scratch-pad-template-2-mobile-webkit-linux.png index 0423b44..1675f32 100644 Binary files a/tests/welcome.spec.ts-snapshots/loads-the-scratch-pad-template-2-mobile-webkit-linux.png and b/tests/welcome.spec.ts-snapshots/loads-the-scratch-pad-template-2-mobile-webkit-linux.png differ diff --git a/tests/welcome.spec.ts-snapshots/loads-the-scratch-pad-template-2-webkit-linux.png b/tests/welcome.spec.ts-snapshots/loads-the-scratch-pad-template-2-webkit-linux.png index 582f2e7..c8de9ac 100644 Binary files a/tests/welcome.spec.ts-snapshots/loads-the-scratch-pad-template-2-webkit-linux.png and b/tests/welcome.spec.ts-snapshots/loads-the-scratch-pad-template-2-webkit-linux.png differ diff --git a/tests/welcome.spec.ts-snapshots/loads-the-single-signature-template-1-chromium-linux.png b/tests/welcome.spec.ts-snapshots/loads-the-single-signature-template-1-chromium-linux.png index bad9d9b..8068f33 100644 Binary files a/tests/welcome.spec.ts-snapshots/loads-the-single-signature-template-1-chromium-linux.png and b/tests/welcome.spec.ts-snapshots/loads-the-single-signature-template-1-chromium-linux.png differ diff --git a/tests/welcome.spec.ts-snapshots/loads-the-single-signature-template-1-firefox-linux.png b/tests/welcome.spec.ts-snapshots/loads-the-single-signature-template-1-firefox-linux.png index f87a9a2..e5182e0 100644 Binary files a/tests/welcome.spec.ts-snapshots/loads-the-single-signature-template-1-firefox-linux.png and b/tests/welcome.spec.ts-snapshots/loads-the-single-signature-template-1-firefox-linux.png differ diff --git a/tests/welcome.spec.ts-snapshots/loads-the-single-signature-template-1-mobile-webkit-linux.png b/tests/welcome.spec.ts-snapshots/loads-the-single-signature-template-1-mobile-webkit-linux.png index c56bb9b..56e1561 100644 Binary files a/tests/welcome.spec.ts-snapshots/loads-the-single-signature-template-1-mobile-webkit-linux.png and b/tests/welcome.spec.ts-snapshots/loads-the-single-signature-template-1-mobile-webkit-linux.png differ diff --git a/tests/welcome.spec.ts-snapshots/loads-the-single-signature-template-1-webkit-linux.png b/tests/welcome.spec.ts-snapshots/loads-the-single-signature-template-1-webkit-linux.png index 36f37b9..ab57d1d 100644 Binary files a/tests/welcome.spec.ts-snapshots/loads-the-single-signature-template-1-webkit-linux.png and b/tests/welcome.spec.ts-snapshots/loads-the-single-signature-template-1-webkit-linux.png differ diff --git a/vite.config.mts b/vite.config.mts index 36e3ceb..b566e9f 100644 --- a/vite.config.mts +++ b/vite.config.mts @@ -72,6 +72,10 @@ export default defineConfig({ cssMinify: 'lightningcss', chunkSizeWarningLimit: 1000000, }, + worker: { + format: 'es', + plugins: () => [], + }, optimizeDeps: { esbuildOptions: { supported: { diff --git a/yarn.lock b/yarn.lock index fc97953..67f4c5e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2413,14 +2413,14 @@ __metadata: languageName: node linkType: hard -"@playwright/test@npm:^1.41.1": - version: 1.41.1 - resolution: "@playwright/test@npm:1.41.1" +"@playwright/test@npm:^1.45.0": + version: 1.45.0 + resolution: "@playwright/test@npm:1.45.0" dependencies: - playwright: "npm:1.41.1" + playwright: "npm:1.45.0" bin: playwright: cli.js - checksum: 72bd5bb67c512027d214b9c54c2a22a469bd19d7809771e53a5bfdcc11330591e01579bb22f807d1ebbcdcea35d625e0fc9eb9791cebcc63bf55b82dd1cdefdd + checksum: bfb3cdcca2df5ef7a2a380cc189a5ed905bef9551a2b84071c42b620b77e32a2cffdb74841957bb5e519559ed26b42a7777d09dc897a6aee67258cc041e705f9 languageName: node linkType: hard @@ -3407,12 +3407,12 @@ __metadata: version: 0.0.0-use.local resolution: "bitauth-ide@workspace:." dependencies: - "@bitauth/libauth": "npm:2.0.0" + "@bitauth/libauth": "npm:3.1.0-next.8" "@blueprintjs/core": "npm:^5.8.2" "@blueprintjs/icons": "npm:^5.7.0" "@blueprintjs/select": "npm:^5.0.23" "@monaco-editor/react": "npm:^4.6.0" - "@playwright/test": "npm:^1.41.1" + "@playwright/test": "npm:^1.45.0" "@types/node": "npm:^20.11.5" "@types/pako": "npm:^2.0.3" "@types/react": "npm:^18.2.48" @@ -7389,27 +7389,27 @@ __metadata: languageName: node linkType: hard -"playwright-core@npm:1.41.1": - version: 1.41.1 - resolution: "playwright-core@npm:1.41.1" +"playwright-core@npm:1.45.0": + version: 1.45.0 + resolution: "playwright-core@npm:1.45.0" bin: playwright-core: cli.js - checksum: cdd91267ca23e3f65d519100e956859c70e3e9ca29e3fe00e700b457903129e41dfa17752f1ea37ad0a8a7c6330baf9f3be503e4cbfa3e8833e80a037f899aee + checksum: 5c3c205ad6d52c674fb3a91981a2068b17e7d02350c868cb3bc51e0c236df1b5da7c123ebdb6c22c66c8182965d2bba0831fa272a8a388c4e545eac5b3efa501 languageName: node linkType: hard -"playwright@npm:1.41.1": - version: 1.41.1 - resolution: "playwright@npm:1.41.1" +"playwright@npm:1.45.0": + version: 1.45.0 + resolution: "playwright@npm:1.45.0" dependencies: fsevents: "npm:2.3.2" - playwright-core: "npm:1.41.1" + playwright-core: "npm:1.45.0" dependenciesMeta: fsevents: optional: true bin: playwright: cli.js - checksum: 32d48c1f8ff881770a19c9245fb4191fc36b5e97ab5f48effa0b1cf5e83fa958f6fdd7e4268dd984aa306ac5fe9e4324510211910751fb52cebb9bae819d13ca + checksum: dbb1c3fe127650bd4b96a84b5271fbb56eea298bc87fa43a16b5054a4f011e00edd9a1a5e769efe4776a7c60d2a87c280543930a19cd12846d3d1e266198d4df languageName: node linkType: hard