diff --git a/Makefile b/Makefile index bf31b7975..c0dbe8307 100644 --- a/Makefile +++ b/Makefile @@ -1,8 +1,8 @@ unit: - node_modules/.bin/cucumber-js --tags "@unit.offline or @unit.algod or @unit.indexer or @unit.rekey or @unit.tealsign or @unit.dryrun or @unit.applications or @unit.responses or @unit.transactions or @unit.transactions.keyreg or @unit.transactions.payment or @unit.responses.231 or @unit.feetest or @unit.indexer.logs or @unit.abijson or @unit.abijson.byname or @unit.atomic_transaction_composer or @unit.responses.unlimited_assets or @unit.indexer.ledger_refactoring or @unit.algod.ledger_refactoring or @unit.dryrun.trace.application" tests/cucumber/features --require-module ts-node/register --require tests/cucumber/steps/index.js + node_modules/.bin/cucumber-js --tags "@unit.offline or @unit.algod or @unit.indexer or @unit.rekey or @unit.tealsign or @unit.dryrun or @unit.applications or @unit.responses or @unit.transactions or @unit.transactions.keyreg or @unit.transactions.payment or @unit.responses.231 or @unit.feetest or @unit.indexer.logs or @unit.abijson or @unit.abijson.byname or @unit.atomic_transaction_composer or @unit.responses.unlimited_assets or @unit.indexer.ledger_refactoring or @unit.algod.ledger_refactoring or @unit.dryrun.trace.application or @unit.sourcemap" tests/cucumber/features --require-module ts-node/register --require tests/cucumber/steps/index.js integration: - node_modules/.bin/cucumber-js --tags "@algod or @assets or @auction or @kmd or @send or @indexer or @rekey or @send.keyregtxn or @dryrun or @compile or @applications or @indexer.applications or @applications.verified or @indexer.231 or @abi or @c2c" tests/cucumber/features --require-module ts-node/register --require tests/cucumber/steps/index.js + node_modules/.bin/cucumber-js --tags "@algod or @assets or @auction or @kmd or @send or @indexer or @rekey or @send.keyregtxn or @dryrun or @compile or @applications or @indexer.applications or @applications.verified or @indexer.231 or @abi or @c2c or @compile.sourcemap" tests/cucumber/features --require-module ts-node/register --require tests/cucumber/steps/index.js docker-test: ./tests/cucumber/docker/run_docker.sh diff --git a/package-lock.json b/package-lock.json index c965dafaa..81e6147a1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,7 +17,8 @@ "js-sha512": "^0.8.0", "json-bigint": "^1.0.0", "superagent": "^6.1.0", - "tweetnacl": "^1.0.3" + "tweetnacl": "^1.0.3", + "vlq": "^2.0.4" }, "devDependencies": { "@types/json-bigint": "^1.0.0", @@ -55,6 +56,9 @@ "typescript": "^4.2.3", "webpack": "^5.38.1", "webpack-cli": "^4.7.2" + }, + "optionalDependencies": { + "fsevents": "2.1.2" } }, "node_modules/@babel/code-frame": { @@ -1493,6 +1497,20 @@ "fsevents": "~2.3.1" } }, + "node_modules/chokidar/node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/chownr": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", @@ -1825,7 +1843,7 @@ "version": "2.6.12", "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.12.tgz", "integrity": "sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==", - "deprecated": "core-js@<3.3 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Please, upgrade your dependencies to the actual version of core-js.", + "deprecated": "core-js@<3.23.3 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Some versions have web compatibility issues. Please, upgrade your dependencies to the actual version of core-js.", "dev": true, "hasInstallScript": true }, @@ -1905,7 +1923,7 @@ "version": "5.1.0", "resolved": "https://registry.npmjs.org/cucumber/-/cucumber-5.1.0.tgz", "integrity": "sha512-zrl2VYTBRgvxucwV2GKAvLqcfA1Naeax8plPvWgPEzl3SCJiuPPv3WxBHIRHtPYcEdbHDR6oqLpZP4bJ8UIdmA==", - "deprecated": "The npm package has moved to @cucumber/cucumber", + "deprecated": "Cucumber is publishing new releases under @cucumber/cucumber", "dev": true, "dependencies": { "@babel/polyfill": "^7.2.3", @@ -1949,6 +1967,7 @@ "version": "6.6.2", "resolved": "https://registry.npmjs.org/cucumber-expressions/-/cucumber-expressions-6.6.2.tgz", "integrity": "sha512-WcFSVBiWNLJbIcAAC3t/ACU46vaOKfe1UIF5H3qveoq+Y4XQm9j3YwHurQNufRKBBg8nCnpU7Ttsx7egjS3hwA==", + "deprecated": "This package is now published under @cucumber/cucumber-expressions", "dev": true, "dependencies": { "becke-ch--regex--s0-0-v1--base--pl--lib": "^1.2.0" @@ -3295,6 +3314,7 @@ "version": "1.2.2", "resolved": "https://registry.npmjs.org/formidable/-/formidable-1.2.2.tgz", "integrity": "sha512-V8gLm+41I/8kguQ4/o1D3RIHRmhYFG4pnNyonvua+40rqcEmT4+V71yaZ3B457xbbgCsCfjSPi65u/W6vK1U5Q==", + "deprecated": "Please upgrade to latest, formidable@v2 or formidable@v3! Check these notes: https://bit.ly/2ZEqIau", "funding": { "url": "https://ko-fi.com/tunnckoCore/commissions" } @@ -3336,10 +3356,10 @@ "dev": true }, "node_modules/fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.2.tgz", + "integrity": "sha512-R4wDiBwZ0KzpgOWetKDug1FZcYhqYnUYKtfZYt4mD5SBz76q0KR4Q9o7GIPamsVPGmW3EYPPJ0dOOjvx32ldZA==", + "deprecated": "\"Please update to latest v2.3 or v2.2\"", "hasInstallScript": true, "optional": true, "os": [ @@ -3436,6 +3456,7 @@ "version": "5.1.0", "resolved": "https://registry.npmjs.org/gherkin/-/gherkin-5.1.0.tgz", "integrity": "sha1-aEu7A63STq9731RPWAM+so+zxtU=", + "deprecated": "This package is now published under @cucumber/gherkin", "dev": true, "bin": { "gherkin-javascript": "bin/gherkin" @@ -6766,6 +6787,11 @@ "safer-buffer": "^2.0.2", "tweetnacl": "~0.14.0" }, + "bin": { + "sshpk-conv": "bin/sshpk-conv", + "sshpk-sign": "bin/sshpk-sign", + "sshpk-verify": "bin/sshpk-verify" + }, "engines": { "node": ">=0.10.0" } @@ -6973,6 +6999,7 @@ "version": "6.1.0", "resolved": "https://registry.npmjs.org/superagent/-/superagent-6.1.0.tgz", "integrity": "sha512-OUDHEssirmplo3F+1HWKUrUjvnQuA+nZI6i/JJBdXb5eq9IyEQwPyPpqND+SSsxf6TygpBEkUjISVRN4/VOpeg==", + "deprecated": "Please upgrade to v7.0.2+ of superagent. We have fixed numerous issues with streams, form-data, attach(), filesystem errors not bubbling up (ENOENT on attach()), and all tests are now passing. See the releases tab for more information at .", "dependencies": { "component-emitter": "^1.3.0", "cookiejar": "^2.1.2", @@ -7756,6 +7783,11 @@ "extsprintf": "^1.2.0" } }, + "node_modules/vlq": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/vlq/-/vlq-2.0.4.tgz", + "integrity": "sha512-aodjPa2wPQFkra1G8CzJBTHXhgk3EVSwxSWXNPr1fgdFLUb8kvLV1iEb6rFgasIsjP82HWI6dsb5Io26DDnasA==" + }, "node_modules/vscode-oniguruma": { "version": "1.6.2", "resolved": "https://registry.npmjs.org/vscode-oniguruma/-/vscode-oniguruma-1.6.2.tgz", @@ -9468,6 +9500,15 @@ "is-glob": "~4.0.1", "normalize-path": "~3.0.0", "readdirp": "~3.5.0" + }, + "dependencies": { + "fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "optional": true + } } }, "chownr": { @@ -10934,10 +10975,9 @@ "dev": true }, "fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.2.tgz", + "integrity": "sha512-R4wDiBwZ0KzpgOWetKDug1FZcYhqYnUYKtfZYt4mD5SBz76q0KR4Q9o7GIPamsVPGmW3EYPPJ0dOOjvx32ldZA==", "optional": true }, "function-bind": { @@ -14259,6 +14299,11 @@ "extsprintf": "^1.2.0" } }, + "vlq": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/vlq/-/vlq-2.0.4.tgz", + "integrity": "sha512-aodjPa2wPQFkra1G8CzJBTHXhgk3EVSwxSWXNPr1fgdFLUb8kvLV1iEb6rFgasIsjP82HWI6dsb5Io26DDnasA==" + }, "vscode-oniguruma": { "version": "1.6.2", "resolved": "https://registry.npmjs.org/vscode-oniguruma/-/vscode-oniguruma-1.6.2.tgz", diff --git a/package.json b/package.json index 95aca5374..aaff3a56f 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,11 @@ "js-sha512": "^0.8.0", "json-bigint": "^1.0.0", "superagent": "^6.1.0", - "tweetnacl": "^1.0.3" + "tweetnacl": "^1.0.3", + "vlq": "^2.0.4" + }, + "optionalDependencies": { + "fsevents": "2.1.2" }, "devDependencies": { "@types/json-bigint": "^1.0.0", diff --git a/src/client/client.ts b/src/client/client.ts index 2fc35b085..8f7ece2e8 100644 --- a/src/client/client.ts +++ b/src/client/client.ts @@ -250,7 +250,8 @@ export default class HTTPClient { async post( relativePath: string, data: any, - requestHeaders: Record = {} + requestHeaders: Record = {}, + query?: Query ): Promise { const fullHeaders = { 'content-type': 'application/json', @@ -261,7 +262,7 @@ export default class HTTPClient { const res = await this.bc.post( relativePath, HTTPClient.serializeData(data, fullHeaders), - undefined, + query, fullHeaders ); diff --git a/src/client/v2/algod/compile.ts b/src/client/v2/algod/compile.ts index f24a94f42..85f02f182 100644 --- a/src/client/v2/algod/compile.ts +++ b/src/client/v2/algod/compile.ts @@ -28,6 +28,11 @@ export default class Compile extends JSONRequest { return `/v2/teal/compile`; } + sourcemap(map: boolean = true) { + this.query.sourcemap = map; + return this; + } + /** * Executes compile * @param headers - A headers object @@ -37,7 +42,8 @@ export default class Compile extends JSONRequest { const res = await this.c.post( this.path(), Buffer.from(this.source), - txHeaders + txHeaders, + this.query ); return res.body; } diff --git a/src/logic/sourcemap.ts b/src/logic/sourcemap.ts new file mode 100644 index 000000000..b00564911 --- /dev/null +++ b/src/logic/sourcemap.ts @@ -0,0 +1,67 @@ +import * as vlq from 'vlq'; + +export class SourceMap { + version: number; + sources: string[]; + names: string[]; + mappings: string; + + pcToLine: { [key: number]: number }; + lineToPc: { [key: number]: number[] }; + + constructor({ + version, + sources, + names, + mappings, + }: { + version: number; + sources: string[]; + names: string[]; + mappings: string; + }) { + this.version = version; + this.sources = sources; + this.names = names; + this.mappings = mappings; + + if (this.version !== 3) + throw new Error(`Only version 3 is supported, got ${this.version}`); + + if (this.mappings === undefined) + throw new Error( + 'mapping undefined, cannot build source map without `mapping`' + ); + + const pcList = this.mappings.split(';').map((m) => { + const decoded = vlq.decode(m); + if (decoded.length > 2) return decoded[2]; + return undefined; + }); + + this.pcToLine = {}; + this.lineToPc = {}; + + let lastLine = 0; + for (const [pc, lineDelta] of pcList.entries()) { + // If the delta is not undefined, the lastLine should be updated with + // lastLine + the delta + if (lineDelta !== undefined) { + lastLine += lineDelta; + } + + if (!(lastLine in this.lineToPc)) this.lineToPc[lastLine] = []; + + this.lineToPc[lastLine].push(pc); + this.pcToLine[pc] = lastLine; + } + } + + getLineForPc(pc: number): number | undefined { + return this.pcToLine[pc]; + } + + getPcsForLine(line: number): number[] | undefined { + return this.lineToPc[line]; + } +} diff --git a/src/main.ts b/src/main.ts index 4d1c80611..12d853485 100644 --- a/src/main.ts +++ b/src/main.ts @@ -168,6 +168,8 @@ export { appendSignMultisigTransaction, multisigAddress, } from './multisig'; +export { SourceMap } from './logic/sourcemap'; + export const LogicTemplates = LogicTemplatesCommonJSExport.default; export * from './dryrun'; diff --git a/tests/cucumber/steps/steps.js b/tests/cucumber/steps/steps.js index 8f03544f6..5f0607ee5 100644 --- a/tests/cucumber/steps/steps.js +++ b/tests/cucumber/steps/steps.js @@ -5341,6 +5341,57 @@ module.exports = function getSteps(options) { } ); + Given('a source map json file {string}', async function (srcmap) { + const js = parseJSON(await loadResource(srcmap)); + this.sourcemap = new algosdk.SourceMap(js); + }); + + Then( + 'the string composed of pc:line number equals {string}', + function (mapping) { + const buff = Object.entries(this.sourcemap.pcToLine).map( + ([pc, line]) => `${pc}:${line}` + ); + assert.equal(buff.join(';'), mapping); + } + ); + + Then( + 'getting the line associated with a pc {string} equals {string}', + function (pc, expectedLine) { + const actualLine = this.sourcemap.getLineForPc(parseInt(pc)); + assert.equal(actualLine, parseInt(expectedLine)); + } + ); + + Then( + 'getting the last pc associated with a line {string} equals {string}', + function (line, expectedPc) { + const actualPcs = this.sourcemap.getPcsForLine(parseInt(line)); + assert.equal(actualPcs.pop(), parseInt(expectedPc)); + } + ); + + When( + 'I compile a teal program {string} with mapping enabled', + async function (teal) { + const tealSrc = await loadResource(teal); + const compiledResponse = await this.v2Client + .compile(tealSrc) + .sourcemap(true) + .do(); + this.rawSourceMap = JSON.stringify(compiledResponse.sourcemap); + } + ); + + Then( + 'the resulting source map is the same as the json {string}', + async function (expectedJsonPath) { + const expected = await loadResource(expectedJsonPath); + assert.equal(this.rawSourceMap, expected.toString().trim()); + } + ); + if (!options.ignoreReturn) { return steps; }