diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 000000000..25437d3b4 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,11 @@ +# See: https://EditorConfig.org +# VSCode plugin: https://marketplace.visualstudio.com/items?itemName=EditorConfig.EditorConfig + +root = true + +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +indent_style = space +indent_size = 4 diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 000000000..c2294fc7d --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,35 @@ +module.exports = { + parser: "@typescript-eslint/parser", + parserOptions: { + project: ["tsconfig.json", "tsconfig.tests.json"], + sourceType: "module", + }, + plugins: ["@typescript-eslint/eslint-plugin"], + extends: ["plugin:@typescript-eslint/eslint-recommended", "plugin:@typescript-eslint/recommended", "prettier"], + root: true, + env: { + node: true, + }, + ignorePatterns: [".eslintrc.js", "node_modules", "out", "out-tests", "out-browser", "out-browser-tests"], + rules: { + "@typescript-eslint/interface-name-prefix": "off", + "@typescript-eslint/explicit-function-return-type": "off", + "@typescript-eslint/no-explicit-any": "off", + "@typescript-eslint/no-use-before-define": "off", + "@typescript-eslint/quotes": "off", + "@typescript-eslint/no-empty-interface": "off", + "@typescript-eslint/no-non-null-assertion": "off", + "@typescript-eslint/no-inferrable-types": "off", + "@typescript-eslint/no-namespace": "warn", + "@typescript-eslint/no-var-requires": "warn", + "@typescript-eslint/no-empty-function": "off", + "@typescript-eslint/no-unused-vars": [ + "warn", + { + argsIgnorePattern: "^_", + varsIgnorePattern: "^_", + }, + ], + "prefer-const": "off", + }, +}; diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 987f2a803..b3e02a3d5 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -24,6 +24,7 @@ jobs: with: node-version: ${{ matrix.node-version }} - run: npm ci + - run: npm run lint - run: npm run compile - run: npm run compile-browser - run: npm test diff --git a/.github/workflows/publish-not-main.yml b/.github/workflows/publish-not-main.yml index 9bae589f3..92b3c521a 100644 --- a/.github/workflows/publish-not-main.yml +++ b/.github/workflows/publish-not-main.yml @@ -6,10 +6,10 @@ on: channel: type: choice description: NPM channel - options: - - alpha - - beta - - previous + options: + - alpha + - beta + - previous permissions: contents: write @@ -23,7 +23,7 @@ jobs: with: node-version: 16 registry-url: https://registry.npmjs.org/ - + - name: Create release env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -33,7 +33,7 @@ jobs: - run: npm ci - run: npm test - + - name: Publish to npmjs env: NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 000000000..9e4d56ada --- /dev/null +++ b/.prettierrc @@ -0,0 +1,6 @@ +{ + "singleQuote": false, + "trailingComma": "all", + "tabWidth": 4, + "printWidth": 120 +} diff --git a/.vscode/settings.json b/.vscode/settings.json index 2c63c0851..e5c7eee47 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,2 +1,10 @@ { + "editor.formatOnSave": true, + "editor.formatOnSaveMode": "file", + "[javascript]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + }, + "[typescript]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + } } diff --git a/package-lock.json b/package-lock.json index 13e8e70b0..b4c79ed86 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,42 +1,46 @@ { "name": "@multiversx/sdk-core", - "version": "12.19.2", + "version": "13.0.0-beta.18", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@multiversx/sdk-core", - "version": "12.19.2", + "version": "13.0.0-beta.18", "license": "MIT", "dependencies": { "@multiversx/sdk-transaction-decoder": "1.0.2", "bech32": "1.1.4", "blake2b": "2.1.3", "buffer": "6.0.3", - "json-duplicate-key-handle": "1.0.0", - "keccak": "3.0.2", - "protobufjs": "7.2.4" + "json-bigint": "1.0.0", + "keccak": "3.0.2" }, "devDependencies": { - "@multiversx/sdk-network-providers": "2.2.0", - "@multiversx/sdk-wallet": "3.0.0", - "@multiversx/sdk-wallet-next": "npm:@multiversx/sdk-wallet@4.0.0", + "@multiversx/sdk-network-providers": "2.4.1", + "@multiversx/sdk-wallet": "4.4.0", "@types/assert": "1.4.6", "@types/chai": "4.2.11", "@types/mocha": "9.1.0", "@types/node": "13.13.2", + "@typescript-eslint/eslint-plugin": "5.44.0", + "@typescript-eslint/parser": "5.44.0", "assert": "2.0.0", "axios": "1.6.5", "browserify": "17.0.0", "chai": "4.2.0", + "eslint": "8.28.0", + "eslint-config-prettier": "9.1.0", "esmify": "2.1.1", "mocha": "9.2.2", + "prettier": "3.2.4", + "protobufjs-cli": "1.1.2", "ts-node": "9.1.1", - "tslint": "6.1.3", "typescript": "4.1.2" }, "peerDependencies": { - "bignumber.js": "^9.0.1" + "bignumber.js": "^9.0.1", + "protobufjs": "^7.2.6" } }, "@multiversx/sdk-wallet:4.0.0-beta.3": { @@ -45,6 +49,15 @@ "@multiversx/sdk-wallet@4.0.0-beta.3": { "extraneous": true }, + "node_modules/@aashutoshrathi/word-wrap": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", + "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/@ampproject/remapping": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz", @@ -431,6 +444,77 @@ "node": ">=6.9.0" } }, + "node_modules/@eslint/eslintrc": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.4.1.tgz", + "integrity": "sha512-XXrH9Uarn0stsyldqDYq8r++mROmWRI1xKMXa640Bb//SY1+ECYX6VzT6Lcx5frD0V30XieqJ0oX9I2Xj5aoMA==", + "dev": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.4.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.11.14", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", + "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==", + "dev": true, + "dependencies": { + "@humanwhocodes/object-schema": "^2.0.2", + "debug": "^4.3.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.2.tgz", + "integrity": "sha512-6EwiSjwWYP7pTckG6I5eyFANjPhmPjUX9JRLUSfNPC7FX7zK9gyZAfUEaECL6ALTpGX5AjnBq3C9XmVWPitNpw==", + "dev": true + }, "node_modules/@jridgewell/gen-mapping": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz", @@ -478,6 +562,18 @@ "@jridgewell/sourcemap-codec": "1.4.14" } }, + "node_modules/@jsdoc/salty": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/@jsdoc/salty/-/salty-0.2.7.tgz", + "integrity": "sha512-mh8LbS9d4Jq84KLw8pzho7XC2q2/IJGiJss3xwRoLD1A+EE16SjN4PfaG4jRCzKegTFLlN0Zd8SdUPE6XdoPFg==", + "dev": true, + "dependencies": { + "lodash": "^4.17.21" + }, + "engines": { + "node": ">=v12.0.0" + } + }, "node_modules/@multiversx/sdk-bls-wasm": { "version": "0.3.5", "resolved": "https://registry.npmjs.org/@multiversx/sdk-bls-wasm/-/sdk-bls-wasm-0.3.5.tgz", @@ -488,29 +584,18 @@ } }, "node_modules/@multiversx/sdk-network-providers": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@multiversx/sdk-network-providers/-/sdk-network-providers-2.2.0.tgz", - "integrity": "sha512-2n/+7Ap6S9rJGTiX38GCZ2TmY9zQ1U7o1DwnWpHNRJRxArSN/xzLrbcSKy8InMyc+4A+VHf5pV0Pk8NdPV6++w==", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@multiversx/sdk-network-providers/-/sdk-network-providers-2.4.1.tgz", + "integrity": "sha512-AyKLxt51v4Y94NC3/0witz7XPpZ6+2mOi8CVW+j7HP6RtDl5vJinxCriSKb0Z/PzV6LdmJmwCW5iDRebMIk6fg==", "dev": true, "dependencies": { - "axios": "1.6.1", + "axios": "1.6.5", "bech32": "1.1.4", "bignumber.js": "9.0.1", "buffer": "6.0.3", "json-bigint": "1.0.0" } }, - "node_modules/@multiversx/sdk-network-providers/node_modules/axios": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.1.tgz", - "integrity": "sha512-vfBmhDpKafglh0EldBEbVuoe7DyAavGSLWhuSm5ZSEKQnHhBf0xAAwybbNH1IkrJNGnS/VG4I5yxig1pCEXE4g==", - "dev": true, - "dependencies": { - "follow-redirects": "^1.15.0", - "form-data": "^4.0.0", - "proxy-from-env": "^1.1.0" - } - }, "node_modules/@multiversx/sdk-transaction-decoder": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/@multiversx/sdk-transaction-decoder/-/sdk-transaction-decoder-1.0.2.tgz", @@ -525,29 +610,9 @@ "integrity": "sha512-LcknSilhIGatDAsY1ak2I8VtGaHNhgMSYVxFrGLXv+xLHytaKZKcaUJJUE7qmBr7h33o5YQwP55pMI0xmkpJwg==" }, "node_modules/@multiversx/sdk-wallet": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@multiversx/sdk-wallet/-/sdk-wallet-3.0.0.tgz", - "integrity": "sha512-nDVBtva1mpfutXA8TfUnpdeFqhY9O+deNU3U/Z41yPBcju1trHDC9gcKPyQqcQ3qjG/6LwEXmIm7Dc5fIsvVjg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@multiversx/sdk-bls-wasm": "0.3.5", - "bech32": "1.1.4", - "bip39": "3.0.2", - "blake2b": "2.1.3", - "ed25519-hd-key": "1.1.2", - "ed2curve": "0.3.0", - "keccak": "3.0.1", - "scryptsy": "2.1.0", - "tweetnacl": "1.0.3", - "uuid": "8.3.2" - } - }, - "node_modules/@multiversx/sdk-wallet-next": { - "name": "@multiversx/sdk-wallet", - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@multiversx/sdk-wallet/-/sdk-wallet-4.0.0.tgz", - "integrity": "sha512-Fskqg9AGgqSIAgN+Ag9Y/DIoZRr4qgB0baVZ1nlXhgaRuM30v1UeW0TAIhuAbXkkMiTOJyLaCeebUDYy1VJgWA==", + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@multiversx/sdk-wallet/-/sdk-wallet-4.4.0.tgz", + "integrity": "sha512-wS4P8a2ts3cNaSLUw9VFf4yhWSMTYng+nyHKi3/9QalLP5lxBumUfD/mUkb9sK13UPJ5Xp/zB3j8a4Qdllw2Ag==", "dev": true, "dependencies": { "@multiversx/sdk-bls-wasm": "0.3.5", @@ -564,20 +629,6 @@ "uuid": "8.3.2" } }, - "node_modules/@multiversx/sdk-wallet-next/node_modules/keccak": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/keccak/-/keccak-3.0.1.tgz", - "integrity": "sha512-epq90L9jlFWCW7+pQa6JOnKn2Xgl2mtI664seYR6MHskvI9agt7AnDqmAlp9TqU4/caMYbA08Hi5DMZAl5zdkA==", - "dev": true, - "hasInstallScript": true, - "dependencies": { - "node-addon-api": "^2.0.0", - "node-gyp-build": "^4.2.0" - }, - "engines": { - "node": ">=10.0.0" - } - }, "node_modules/@multiversx/sdk-wallet/node_modules/keccak": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/keccak/-/keccak-3.0.1.tgz", @@ -616,30 +667,70 @@ } ] }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/@protobufjs/aspromise": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", - "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==" + "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==", + "peer": true }, "node_modules/@protobufjs/base64": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", - "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==" + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", + "peer": true }, "node_modules/@protobufjs/codegen": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", - "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==" + "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==", + "peer": true }, "node_modules/@protobufjs/eventemitter": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", - "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==" + "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==", + "peer": true }, "node_modules/@protobufjs/fetch": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", + "peer": true, "dependencies": { "@protobufjs/aspromise": "^1.1.1", "@protobufjs/inquire": "^1.1.0" @@ -648,27 +739,32 @@ "node_modules/@protobufjs/float": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", - "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==" + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==", + "peer": true }, "node_modules/@protobufjs/inquire": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", - "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==" + "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==", + "peer": true }, "node_modules/@protobufjs/path": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", - "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==" + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==", + "peer": true }, "node_modules/@protobufjs/pool": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", - "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==" + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==", + "peer": true }, "node_modules/@protobufjs/utf8": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", - "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==" + "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==", + "peer": true }, "node_modules/@types/assert": { "version": "1.4.6", @@ -682,6 +778,34 @@ "integrity": "sha512-t7uW6eFafjO+qJ3BIV2gGUyZs27egcNRkUdalkud+Qa3+kg//f129iuOFivHDXQ+vnU3fDXuwgv0cqMCbcE8sw==", "dev": true }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true + }, + "node_modules/@types/linkify-it": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-3.0.5.tgz", + "integrity": "sha512-yg6E+u0/+Zjva+buc3EIb+29XEg4wltq7cSmd4Uc2EE/1nUVmxyzpX6gUXD0V8jIrG0r7YeOGVIbYRkxeooCtw==", + "dev": true + }, + "node_modules/@types/markdown-it": { + "version": "12.2.3", + "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-12.2.3.tgz", + "integrity": "sha512-GKMHFfv3458yYy+v/N8gjufHO6MSZKCOXpZc5GXIWWy8uldwfmPn98vp81gZ5f9SVw8YYBctgfJ22a2d7AOMeQ==", + "dev": true, + "dependencies": { + "@types/linkify-it": "*", + "@types/mdurl": "*" + } + }, + "node_modules/@types/mdurl": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-1.0.5.tgz", + "integrity": "sha512-6L6VymKTzYSrEf4Nev4Xa1LCHKrlTlYCBMTlQKFuddo1CvQcE52I0mwfOJayueUC7MJuXOeHTcIU683lzd0cUA==", + "dev": true + }, "node_modules/@types/mocha": { "version": "9.1.0", "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-9.1.0.tgz", @@ -693,125 +817,496 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-13.13.2.tgz", "integrity": "sha512-LB2R1Oyhpg8gu4SON/mfforE525+Hi/M1ineICEDftqNVTyFg1aRIeGuTvXAoWHc4nbrFncWtJgMmoyRvuGh7A==" }, - "node_modules/@ungap/promise-all-settled": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz", - "integrity": "sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==", + "node_modules/@types/semver": { + "version": "7.5.6", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.6.tgz", + "integrity": "sha512-dn1l8LaMea/IjDoHNd9J52uBbInB796CDffS6VdIxvqYCPSG0V0DzHp76GpaWnlhg88uYyPbXCDIowa86ybd5A==", "dev": true }, - "node_modules/acorn": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", - "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "5.44.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.44.0.tgz", + "integrity": "sha512-j5ULd7FmmekcyWeArx+i8x7sdRHzAtXTkmDPthE4amxZOWKFK7bomoJ4r7PJ8K7PoMzD16U8MmuZFAonr1ERvw==", "dev": true, - "bin": { - "acorn": "bin/acorn" + "dependencies": { + "@typescript-eslint/scope-manager": "5.44.0", + "@typescript-eslint/type-utils": "5.44.0", + "@typescript-eslint/utils": "5.44.0", + "debug": "^4.3.4", + "ignore": "^5.2.0", + "natural-compare-lite": "^1.4.0", + "regexpp": "^3.2.0", + "semver": "^7.3.7", + "tsutils": "^3.21.0" }, "engines": { - "node": ">=0.4.0" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^5.0.0", + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, - "node_modules/acorn-node": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/acorn-node/-/acorn-node-1.8.2.tgz", - "integrity": "sha512-8mt+fslDufLYntIoPAaIMUe/lrbrehIiwmR3t2k9LljIzoigEPF27eLk2hy8zSGzmR/ogr7zbRKINMo1u0yh5A==", + "node_modules/@typescript-eslint/eslint-plugin/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", "dev": true, "dependencies": { - "acorn": "^7.0.0", - "acorn-walk": "^7.0.0", - "xtend": "^4.0.2" + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" } }, - "node_modules/acorn-walk": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz", - "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==", + "node_modules/@typescript-eslint/eslint-plugin/node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, "engines": { - "node": ">=0.4.0" + "node": ">=10" } }, - "node_modules/ansi-colors": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", - "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", + "node_modules/@typescript-eslint/eslint-plugin/node_modules/tsutils": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", + "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", "dev": true, + "dependencies": { + "tslib": "^1.8.1" + }, "engines": { - "node": ">=6" + "node": ">= 6" + }, + "peerDependencies": { + "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" } }, - "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "node_modules/@typescript-eslint/eslint-plugin/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/@typescript-eslint/parser": { + "version": "5.44.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.44.0.tgz", + "integrity": "sha512-H7LCqbZnKqkkgQHaKLGC6KUjt3pjJDx8ETDqmwncyb6PuoigYajyAwBGz08VU/l86dZWZgI4zm5k2VaKqayYyA==", "dev": true, + "dependencies": { + "@typescript-eslint/scope-manager": "5.44.0", + "@typescript-eslint/types": "5.44.0", + "@typescript-eslint/typescript-estree": "5.44.0", + "debug": "^4.3.4" + }, "engines": { - "node": ">=8" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, - "node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "node_modules/@typescript-eslint/scope-manager": { + "version": "5.44.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.44.0.tgz", + "integrity": "sha512-2pKml57KusI0LAhgLKae9kwWeITZ7IsZs77YxyNyIVOwQ1kToyXRaJLl+uDEXzMN5hnobKUOo2gKntK9H1YL8g==", "dev": true, "dependencies": { - "color-convert": "^1.9.0" + "@typescript-eslint/types": "5.44.0", + "@typescript-eslint/visitor-keys": "5.44.0" }, "engines": { - "node": ">=4" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "node_modules/@typescript-eslint/type-utils": { + "version": "5.44.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.44.0.tgz", + "integrity": "sha512-A1u0Yo5wZxkXPQ7/noGkRhV4J9opcymcr31XQtOzcc5nO/IHN2E2TPMECKWYpM3e6olWEM63fq/BaL1wEYnt/w==", "dev": true, "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" + "@typescript-eslint/typescript-estree": "5.44.0", + "@typescript-eslint/utils": "5.44.0", + "debug": "^4.3.4", + "tsutils": "^3.21.0" }, "engines": { - "node": ">= 8" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, - "node_modules/arg": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", - "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", - "dev": true - }, - "node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true - }, - "node_modules/asn1.js": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-5.4.1.tgz", - "integrity": "sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==", + "node_modules/@typescript-eslint/type-utils/node_modules/tsutils": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", + "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", "dev": true, "dependencies": { - "bn.js": "^4.0.0", - "inherits": "^2.0.1", - "minimalistic-assert": "^1.0.0", - "safer-buffer": "^2.1.0" + "tslib": "^1.8.1" + }, + "engines": { + "node": ">= 6" + }, + "peerDependencies": { + "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" } }, - "node_modules/asn1.js/node_modules/bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", - "dev": true + "node_modules/@typescript-eslint/types": { + "version": "5.44.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.44.0.tgz", + "integrity": "sha512-Tp+zDnHmGk4qKR1l+Y1rBvpjpm5tGXX339eAlRBDg+kgZkz9Bw+pqi4dyseOZMsGuSH69fYfPJCBKBrbPCxYFQ==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } }, - "node_modules/assert": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/assert/-/assert-2.0.0.tgz", - "integrity": "sha512-se5Cd+js9dXJnu6Ag2JFc00t+HmHOen+8Q+L7O9zI0PqQXr20uk2J0XQqMxZEeo5U50o8Nvmmx7dZrl+Ufr35A==", + "node_modules/@typescript-eslint/typescript-estree": { + "version": "5.44.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.44.0.tgz", + "integrity": "sha512-M6Jr+RM7M5zeRj2maSfsZK2660HKAJawv4Ud0xT+yauyvgrsHu276VtXlKDFnEmhG+nVEd0fYZNXGoAgxwDWJw==", "dev": true, "dependencies": { - "es6-object-assign": "^1.1.0", - "is-nan": "^1.2.1", + "@typescript-eslint/types": "5.44.0", + "@typescript-eslint/visitor-keys": "5.44.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/tsutils": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", + "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", + "dev": true, + "dependencies": { + "tslib": "^1.8.1" + }, + "engines": { + "node": ">= 6" + }, + "peerDependencies": { + "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/@typescript-eslint/utils": { + "version": "5.44.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.44.0.tgz", + "integrity": "sha512-fMzA8LLQ189gaBjS0MZszw5HBdZgVwxVFShCO3QN+ws3GlPkcy9YuS3U4wkT6su0w+Byjq3mS3uamy9HE4Yfjw==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.9", + "@types/semver": "^7.3.12", + "@typescript-eslint/scope-manager": "5.44.0", + "@typescript-eslint/types": "5.44.0", + "@typescript-eslint/typescript-estree": "5.44.0", + "eslint-scope": "^5.1.1", + "eslint-utils": "^3.0.0", + "semver": "^7.3.7" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "5.44.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.44.0.tgz", + "integrity": "sha512-a48tLG8/4m62gPFbJ27FxwCOqPKxsb8KC3HkmYoq2As/4YyjQl1jDbRr1s63+g4FS/iIehjmN3L5UjmKva1HzQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.44.0", + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@ungap/promise-all-settled": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz", + "integrity": "sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==", + "dev": true + }, + "node_modules/acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/acorn-node": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/acorn-node/-/acorn-node-1.8.2.tgz", + "integrity": "sha512-8mt+fslDufLYntIoPAaIMUe/lrbrehIiwmR3t2k9LljIzoigEPF27eLk2hy8zSGzmR/ogr7zbRKINMo1u0yh5A==", + "dev": true, + "dependencies": { + "acorn": "^7.0.0", + "acorn-walk": "^7.0.0", + "xtend": "^4.0.2" + } + }, + "node_modules/acorn-walk": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz", + "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-colors": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", + "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/asn1.js": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-5.4.1.tgz", + "integrity": "sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==", + "dev": true, + "dependencies": { + "bn.js": "^4.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0", + "safer-buffer": "^2.1.0" + } + }, + "node_modules/asn1.js/node_modules/bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", + "dev": true + }, + "node_modules/assert": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/assert/-/assert-2.0.0.tgz", + "integrity": "sha512-se5Cd+js9dXJnu6Ag2JFc00t+HmHOen+8Q+L7O9zI0PqQXr20uk2J0XQqMxZEeo5U50o8Nvmmx7dZrl+Ufr35A==", + "dev": true, + "dependencies": { + "es6-object-assign": "^1.1.0", + "is-nan": "^1.2.1", "object-is": "^1.0.1", "util": "^0.12.0" } @@ -1038,11 +1533,6 @@ "babylon": "bin/babylon.js" } }, - "node_modules/backslash": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/backslash/-/backslash-0.2.0.tgz", - "integrity": "sha512-Avs+8FUZ1HF/VFP4YWwHQZSGzRPm37ukU1JQYQWijuHhtXdOuAzcZ8PcAzfIw898a8PyBzdn+RtnKA6MzW0X2A==" - }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -1125,6 +1615,12 @@ "nanoassert": "^1.0.0" } }, + "node_modules/bluebird": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", + "dev": true + }, "node_modules/bn.js": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz", @@ -1441,15 +1937,6 @@ "integrity": "sha512-571s0T7nZWK6vB67HI5dyUF7wXiNcfaPPPTl6zYCNApANjIvYJTg7hlud/+cJpdAhS7dVzqMLmfhfHR3rAcOjQ==", "dev": true }, - "node_modules/builtin-modules": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", - "integrity": "sha512-wxXCdllwGhI2kCC0MnvTGYTMvnVZTvqgypkiTI8Pa5tcz2i6VqsqwYGgqwXji+4RgCzms6EajE4IxiUH6HH8nQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/builtin-status-codes": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz", @@ -1475,6 +1962,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/camelcase": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", @@ -1503,6 +1999,18 @@ } ] }, + "node_modules/catharsis": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/catharsis/-/catharsis-0.9.0.tgz", + "integrity": "sha512-prMTQVpcns/tzFgFVkVp6ak6RykZyWb3gu8ckUpd6YkTlacOd3DXGJjIpD4Q6zJirizvaiAjSSHlOsA+6sNh2A==", + "dev": true, + "dependencies": { + "lodash": "^4.17.15" + }, + "engines": { + "node": ">= 10" + } + }, "node_modules/chai": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/chai/-/chai-4.2.0.tgz", @@ -1630,12 +2138,6 @@ "node": ">= 0.8" } }, - "node_modules/commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true - }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -1738,6 +2240,20 @@ "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", "dev": true }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/crypto-browserify": { "version": "3.12.0", "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz", @@ -1807,6 +2323,12 @@ "node": ">=0.12" } }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, "node_modules/define-properties": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.4.tgz", @@ -1909,10 +2431,34 @@ "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", "dev": true }, - "node_modules/domain-browser": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.2.0.tgz", - "integrity": "sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==", + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/domain-browser": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.2.0.tgz", + "integrity": "sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==", "dev": true, "engines": { "node": ">=0.4", @@ -1981,6 +2527,15 @@ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "dev": true }, + "node_modules/entities": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.1.0.tgz", + "integrity": "sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==", + "dev": true, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/es6-object-assign": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/es6-object-assign/-/es6-object-assign-1.1.0.tgz", @@ -2005,6 +2560,361 @@ "node": ">=0.8.0" } }, + "node_modules/escodegen": { + "version": "1.14.3", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.14.3.tgz", + "integrity": "sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw==", + "dev": true, + "dependencies": { + "esprima": "^4.0.1", + "estraverse": "^4.2.0", + "esutils": "^2.0.2", + "optionator": "^0.8.1" + }, + "bin": { + "escodegen": "bin/escodegen.js", + "esgenerate": "bin/esgenerate.js" + }, + "engines": { + "node": ">=4.0" + }, + "optionalDependencies": { + "source-map": "~0.6.1" + } + }, + "node_modules/escodegen/node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/escodegen/node_modules/levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==", + "dev": true, + "dependencies": { + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/escodegen/node_modules/optionator": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", + "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", + "dev": true, + "dependencies": { + "deep-is": "~0.1.3", + "fast-levenshtein": "~2.0.6", + "levn": "~0.3.0", + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2", + "word-wrap": "~1.2.3" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/escodegen/node_modules/prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/escodegen/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/escodegen/node_modules/type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==", + "dev": true, + "dependencies": { + "prelude-ls": "~1.1.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/eslint": { + "version": "8.28.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.28.0.tgz", + "integrity": "sha512-S27Di+EVyMxcHiwDrFzk8dJYAaD+/5SoWKxL1ri/71CRHsnJnRDPNt2Kzj24+MT9FDupf4aqqyqPrvI8MvQ4VQ==", + "dev": true, + "dependencies": { + "@eslint/eslintrc": "^1.3.3", + "@humanwhocodes/config-array": "^0.11.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "ajv": "^6.10.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.1.1", + "eslint-utils": "^3.0.0", + "eslint-visitor-keys": "^3.3.0", + "espree": "^9.4.0", + "esquery": "^1.4.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.15.0", + "grapheme-splitter": "^1.0.4", + "ignore": "^5.2.0", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-sdsl": "^4.1.4", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.1", + "regexpp": "^3.2.0", + "strip-ansi": "^6.0.1", + "strip-json-comments": "^3.1.0", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-config-prettier": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.1.0.tgz", + "integrity": "sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==", + "dev": true, + "bin": { + "eslint-config-prettier": "bin/cli.js" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, + "node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/eslint-scope/node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/eslint-utils": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", + "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^2.0.0" + }, + "engines": { + "node": "^10.0.0 || ^12.0.0 || >= 14.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + }, + "peerDependencies": { + "eslint": ">=5" + } + }, + "node_modules/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/eslint/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/eslint/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/eslint/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/eslint/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/eslint-scope": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/eslint/node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/eslint/node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/eslint/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/esmify": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/esmify/-/esmify-2.1.1.tgz", @@ -2023,6 +2933,35 @@ "through2": "^2.0.5" } }, + "node_modules/espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "dev": true, + "dependencies": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree/node_modules/acorn": { + "version": "8.11.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", + "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/esprima": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", @@ -2036,6 +2975,57 @@ "node": ">=4" } }, + "node_modules/esquery": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", + "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", + "dev": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esquery/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, "node_modules/esutils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", @@ -2064,12 +3054,67 @@ "safe-buffer": "^5.1.1" } }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "node_modules/fast-glob": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", + "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, "node_modules/fast-safe-stringify": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==", "dev": true }, + "node_modules/fastq": { + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.0.tgz", + "integrity": "sha512-zGygtijUMT7jnk3h26kUms3BkSDp4IfIKjmnqI2tvx6nuBfiF1UqOxbnLfzdv+apBy+53oaImsKtMw/xYbW+1w==", + "dev": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, "node_modules/fill-range": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", @@ -2107,10 +3152,30 @@ "flat": "cli.js" } }, + "node_modules/flat-cache": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", + "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", + "dev": true, + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.3", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.2.9", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.9.tgz", + "integrity": "sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==", + "dev": true + }, "node_modules/follow-redirects": { - "version": "1.15.4", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.4.tgz", - "integrity": "sha512-Cr4D/5wlrb0z9dgERpUL3LrmPKVDsETIJhaCMeDfuFYcqa5bldGV6wBsAN6X/vxlXQtFBMrXdXxdL8CbDTGniw==", + "version": "1.15.6", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", + "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", "dev": true, "funding": [ { @@ -2264,6 +3329,26 @@ "node": ">=4" } }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/gopd": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", @@ -2276,6 +3361,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true + }, + "node_modules/grapheme-splitter": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", + "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", + "dev": true + }, "node_modules/growl": { "version": "1.10.5", "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", @@ -2458,6 +3555,40 @@ } ] }, + "node_modules/ignore": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.0.tgz", + "integrity": "sha512-g7dmpshy+gD7mh88OC9NwSGTKoc3kyLAZQRU1mt53Aw/vnvfXnbC+F/7F7QoYVKbV+KNvJx8wArewKy1vXMtlg==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "engines": { + "node": ">=0.8.19" + } + }, "node_modules/inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -2640,6 +3771,15 @@ "node": ">=0.12.0" } }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/is-plain-obj": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", @@ -2692,6 +3832,16 @@ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "dev": true }, + "node_modules/js-sdsl": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.4.2.tgz", + "integrity": "sha512-dwXFwByc/ajSV6m5bcKAPwe4yDDF6D614pxmIi5odytzxRlwqF6nwoiCek80Ixc7Cvma5awClxrzFtxCQvcM8w==", + "dev": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/js-sdsl" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -2710,6 +3860,65 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/js2xmlparser": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/js2xmlparser/-/js2xmlparser-4.0.2.tgz", + "integrity": "sha512-6n4D8gLlLf1n5mNLQPRfViYzu9RATblzPEtm1SthMX1Pjao0r9YI9nw7ZIfRxQMERS87mcswrg+r/OYrPRX6jA==", + "dev": true, + "dependencies": { + "xmlcreate": "^2.0.4" + } + }, + "node_modules/jsdoc": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/jsdoc/-/jsdoc-4.0.2.tgz", + "integrity": "sha512-e8cIg2z62InH7azBBi3EsSEqrKx+nUtAS5bBcYTSpZFA+vhNPyhv8PTFZ0WsjOPDj04/dOLlm08EDcQJDqaGQg==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.20.15", + "@jsdoc/salty": "^0.2.1", + "@types/markdown-it": "^12.2.3", + "bluebird": "^3.7.2", + "catharsis": "^0.9.0", + "escape-string-regexp": "^2.0.0", + "js2xmlparser": "^4.0.2", + "klaw": "^3.0.0", + "markdown-it": "^12.3.2", + "markdown-it-anchor": "^8.4.1", + "marked": "^4.0.10", + "mkdirp": "^1.0.4", + "requizzle": "^0.2.3", + "strip-json-comments": "^3.1.0", + "underscore": "~1.13.2" + }, + "bin": { + "jsdoc": "jsdoc.js" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/jsdoc/node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jsdoc/node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true, + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/jsesc": { "version": "2.5.2", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", @@ -2726,18 +3935,27 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", - "dev": true, "dependencies": { "bignumber.js": "^9.0.0" } }, - "node_modules/json-duplicate-key-handle": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-duplicate-key-handle/-/json-duplicate-key-handle-1.0.0.tgz", - "integrity": "sha512-OLIxL+UpfwUsqcLX3i6Z51ChTou/Vje+6bSeGUSubj96dF/SfjObDprLy++ZXYH07KITuEzsXS7PX7e/BGf4jw==", - "dependencies": { - "backslash": "^0.2.0" - } + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true }, "node_modules/json5": { "version": "2.2.3", @@ -2803,6 +4021,24 @@ "node": ">= 6" } }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/klaw": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/klaw/-/klaw-3.0.0.tgz", + "integrity": "sha512-0Fo5oir+O9jnXu5EefYbVK+mHMBeEVEy2cmctR1O1NECcCkPRreJKrS6Qt/j3KC2C148Dfo9i3pCmCMsdqGr0g==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.1.9" + } + }, "node_modules/labeled-stream-splicer": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/labeled-stream-splicer/-/labeled-stream-splicer-2.0.2.tgz", @@ -2813,6 +4049,28 @@ "stream-splicer": "^2.0.0" } }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/linkify-it": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-3.0.3.tgz", + "integrity": "sha512-ynTsyrFSdE5oZ/O9GEf00kPngmOfVwazR5GKDq6EYfhlpFug3J2zybX56a2PRRpc9P+FuSoGNAwjlbDs9jJBPQ==", + "dev": true, + "dependencies": { + "uc.micro": "^1.0.1" + } + }, "node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -2840,6 +4098,12 @@ "integrity": "sha512-eDn9kqrAmVUC1wmZvlQ6Uhde44n+tXpqPrN8olQJbttgh0oKclk+SF54P47VEGE9CEiMeRwAP8BaM7UHvBkz2A==", "dev": true }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, "node_modules/log-symbols": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", @@ -2929,7 +4193,8 @@ "node_modules/long": { "version": "5.2.3", "resolved": "https://registry.npmjs.org/long/-/long-5.2.3.tgz", - "integrity": "sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==" + "integrity": "sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==", + "peer": true }, "node_modules/loose-envify": { "version": "1.4.0", @@ -2958,6 +4223,44 @@ "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", "dev": true }, + "node_modules/markdown-it": { + "version": "12.3.2", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-12.3.2.tgz", + "integrity": "sha512-TchMembfxfNVpHkbtriWltGWc+m3xszaRD0CZup7GFFhzIgQqxIfn3eGj1yZpfuflzPvfkt611B2Q/Bsk1YnGg==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1", + "entities": "~2.1.0", + "linkify-it": "^3.0.1", + "mdurl": "^1.0.1", + "uc.micro": "^1.0.5" + }, + "bin": { + "markdown-it": "bin/markdown-it.js" + } + }, + "node_modules/markdown-it-anchor": { + "version": "8.6.7", + "resolved": "https://registry.npmjs.org/markdown-it-anchor/-/markdown-it-anchor-8.6.7.tgz", + "integrity": "sha512-FlCHFwNnutLgVTflOYHPW2pPcl2AACqVzExlkGQNsi4CJgqOHN7YTgDd4LuhgN1BFO3TS0vLAruV1Td6dwWPJA==", + "dev": true, + "peerDependencies": { + "@types/markdown-it": "*", + "markdown-it": "*" + } + }, + "node_modules/marked": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/marked/-/marked-4.3.0.tgz", + "integrity": "sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==", + "dev": true, + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 12" + } + }, "node_modules/md5.js": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", @@ -2969,6 +4272,34 @@ "safe-buffer": "^5.1.2" } }, + "node_modules/mdurl": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", + "integrity": "sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g==", + "dev": true + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "dependencies": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, "node_modules/miller-rabin": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz", @@ -3042,18 +4373,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/mkdirp": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", - "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", - "dev": true, - "dependencies": { - "minimist": "^1.2.6" - }, - "bin": { - "mkdirp": "bin/cmd.js" - } - }, "node_modules/mkdirp-classic": { "version": "0.5.3", "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", @@ -3264,6 +4583,18 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, + "node_modules/natural-compare-lite": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz", + "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==", + "dev": true + }, "node_modules/node-addon-api": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-2.0.2.tgz", @@ -3337,6 +4668,23 @@ "wrappy": "1" } }, + "node_modules/optionator": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", + "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", + "dev": true, + "dependencies": { + "@aashutoshrathi/word-wrap": "^1.2.3", + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/os-browserify": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.3.0.tgz", @@ -3379,6 +4727,18 @@ "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", "dev": true }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/parents": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parents/-/parents-1.0.1.tgz", @@ -3422,105 +4782,319 @@ "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", "dev": true, "engines": { - "node": ">=0.10.0" + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "node_modules/path-platform": { + "version": "0.11.15", + "resolved": "https://registry.npmjs.org/path-platform/-/path-platform-0.11.15.tgz", + "integrity": "sha512-Y30dB6rab1A/nfEKsZxmr01nUotHX0c/ZiIAsCTatEe1CmS5Pm5He7fZ195bPT7RdquoaL8lLxFCMQi/bS7IJg==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/pathval": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", + "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/pbkdf2": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.2.tgz", + "integrity": "sha512-iuh7L6jA7JEGu2WxDwtQP1ddOpaJNC4KlDEFfdQajSGgGPNi4OyDc2R7QnbY2bR9QjBVGwgvTdNJZoE7RaxUMA==", + "dev": true, + "dependencies": { + "create-hash": "^1.1.2", + "create-hmac": "^1.1.4", + "ripemd160": "^2.0.1", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" + }, + "engines": { + "node": ">=0.12" + } + }, + "node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.4.tgz", + "integrity": "sha512-FWu1oLHKCrtpO1ypU6J0SbK2d9Ckwysq6bHj/uaCP26DxrPpppCLQRGVuqAxSTvhF00AcvDRyYrLNW7ocBhFFQ==", + "dev": true, + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "dev": true, + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true + }, + "node_modules/protobufjs": { + "version": "7.2.6", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.2.6.tgz", + "integrity": "sha512-dgJaEDDL6x8ASUZ1YqWciTRrdOuYNzoOf27oHNfdyvKqHr5i0FV7FSLU+aIeFjyFgVxrpTOtQUi0BLLBymZaBw==", + "hasInstallScript": true, + "peer": true, + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/node": ">=13.7.0", + "long": "^5.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/protobufjs-cli": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/protobufjs-cli/-/protobufjs-cli-1.1.2.tgz", + "integrity": "sha512-8ivXWxT39gZN4mm4ArQyJrRgnIwZqffBWoLDsE21TmMcKI3XwJMV4lEF2WU02C4JAtgYYc2SfJIltelD8to35g==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "escodegen": "^1.13.0", + "espree": "^9.0.0", + "estraverse": "^5.1.0", + "glob": "^8.0.0", + "jsdoc": "^4.0.0", + "minimist": "^1.2.0", + "semver": "^7.1.2", + "tmp": "^0.2.1", + "uglify-js": "^3.7.7" + }, + "bin": { + "pbjs": "bin/pbjs", + "pbts": "bin/pbts" + }, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "protobufjs": "^7.0.0" + } + }, + "node_modules/protobufjs-cli/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/protobufjs-cli/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/protobufjs-cli/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/protobufjs-cli/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" } }, - "node_modules/path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "node_modules/protobufjs-cli/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, - "node_modules/path-platform": { - "version": "0.11.15", - "resolved": "https://registry.npmjs.org/path-platform/-/path-platform-0.11.15.tgz", - "integrity": "sha512-Y30dB6rab1A/nfEKsZxmr01nUotHX0c/ZiIAsCTatEe1CmS5Pm5He7fZ195bPT7RdquoaL8lLxFCMQi/bS7IJg==", + "node_modules/protobufjs-cli/node_modules/glob": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + }, "engines": { - "node": ">= 0.8.0" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/pathval": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", - "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", + "node_modules/protobufjs-cli/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, "engines": { - "node": "*" + "node": ">=8" } }, - "node_modules/pbkdf2": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.2.tgz", - "integrity": "sha512-iuh7L6jA7JEGu2WxDwtQP1ddOpaJNC4KlDEFfdQajSGgGPNi4OyDc2R7QnbY2bR9QjBVGwgvTdNJZoE7RaxUMA==", + "node_modules/protobufjs-cli/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", "dev": true, "dependencies": { - "create-hash": "^1.1.2", - "create-hmac": "^1.1.4", - "ripemd160": "^2.0.1", - "safe-buffer": "^5.0.1", - "sha.js": "^2.4.8" + "yallist": "^4.0.0" }, "engines": { - "node": ">=0.12" + "node": ">=10" } }, - "node_modules/picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", - "dev": true - }, - "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "node_modules/protobufjs-cli/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", "dev": true, - "engines": { - "node": ">=8.6" + "dependencies": { + "brace-expansion": "^2.0.1" }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" + "engines": { + "node": ">=10" } }, - "node_modules/process": { - "version": "0.11.10", - "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", - "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "node_modules/protobufjs-cli/node_modules/semver": { + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, "engines": { - "node": ">= 0.6.0" + "node": ">=10" } }, - "node_modules/process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "dev": true - }, - "node_modules/protobufjs": { - "version": "7.2.4", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.2.4.tgz", - "integrity": "sha512-AT+RJgD2sH8phPmCf7OUZR8xGdcJRga4+1cOaXJ64hvcSkVhNcRHOwIxUatPH15+nj59WAGTDv3LSGZPEQbJaQ==", - "hasInstallScript": true, + "node_modules/protobufjs-cli/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, "dependencies": { - "@protobufjs/aspromise": "^1.1.2", - "@protobufjs/base64": "^1.1.2", - "@protobufjs/codegen": "^2.0.4", - "@protobufjs/eventemitter": "^1.1.0", - "@protobufjs/fetch": "^1.1.0", - "@protobufjs/float": "^1.0.2", - "@protobufjs/inquire": "^1.1.0", - "@protobufjs/path": "^1.1.2", - "@protobufjs/pool": "^1.1.0", - "@protobufjs/utf8": "^1.1.0", - "@types/node": ">=13.7.0", - "long": "^5.0.0" + "has-flag": "^4.0.0" }, "engines": { - "node": ">=12.0.0" + "node": ">=8" } }, + "node_modules/protobufjs-cli/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, "node_modules/proxy-from-env": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", @@ -3572,6 +5146,26 @@ "node": ">=0.4.x" } }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, "node_modules/randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", @@ -3648,6 +5242,18 @@ "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==", "dev": true }, + "node_modules/regexpp": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", + "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + } + }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -3657,6 +5263,15 @@ "node": ">=0.10.0" } }, + "node_modules/requizzle": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/requizzle/-/requizzle-0.2.4.tgz", + "integrity": "sha512-JRrFk1D4OQ4SqovXOgdav+K8EAhSB/LJZqCz8tbX0KObcdeM15Ss59ozWMBWmmINMagCwmqn4ZNryUGpBsl6Jw==", + "dev": true, + "dependencies": { + "lodash": "^4.17.21" + } + }, "node_modules/resolve": { "version": "1.22.1", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", @@ -3674,6 +5289,40 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/ripemd160": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", @@ -3684,6 +5333,29 @@ "inherits": "^2.0.1" } }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -3755,6 +5427,27 @@ "fast-safe-stringify": "^2.0.7" } }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/shell-quote": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.0.tgz", @@ -3784,6 +5477,15 @@ } ] }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/source-map": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", @@ -3812,12 +5514,6 @@ "node": ">=0.10.0" } }, - "node_modules/sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", - "dev": true - }, "node_modules/stream-browserify": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-3.0.0.tgz", @@ -3976,6 +5672,12 @@ "acorn-node": "^1.2.0" } }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true + }, "node_modules/through": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", @@ -4004,6 +5706,15 @@ "node": ">=0.6.0" } }, + "node_modules/tmp": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.3.tgz", + "integrity": "sha512-nZD7m9iCPC5g0pYmcaxogYKggSfLsdxl8of3Q/oIbqCqLLIO9IAF0GWjX1z9NZRHPiXv8Wex4yDCaZsgEw0Y8w==", + "dev": true, + "engines": { + "node": ">=14.14" + } + }, "node_modules/to-fast-properties": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", @@ -4057,98 +5768,15 @@ "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", "dev": true, "engines": { - "node": ">=0.3.1" - } - }, - "node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true - }, - "node_modules/tslint": { - "version": "6.1.3", - "resolved": "https://registry.npmjs.org/tslint/-/tslint-6.1.3.tgz", - "integrity": "sha512-IbR4nkT96EQOvKE2PW/djGz8iGNeJ4rF2mBfiYaR/nvUWYKJhLwimoJKgjIFEIDibBtOevj7BqCRL4oHeWWUCg==", - "deprecated": "TSLint has been deprecated in favor of ESLint. Please see https://github.com/palantir/tslint/issues/4534 for more information.", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.0.0", - "builtin-modules": "^1.1.1", - "chalk": "^2.3.0", - "commander": "^2.12.1", - "diff": "^4.0.1", - "glob": "^7.1.1", - "js-yaml": "^3.13.1", - "minimatch": "^3.0.4", - "mkdirp": "^0.5.3", - "resolve": "^1.3.2", - "semver": "^5.3.0", - "tslib": "^1.13.0", - "tsutils": "^2.29.0" - }, - "bin": { - "tslint": "bin/tslint" - }, - "engines": { - "node": ">=4.8.0" - }, - "peerDependencies": { - "typescript": ">=2.3.0-dev || >=2.4.0-dev || >=2.5.0-dev || >=2.6.0-dev || >=2.7.0-dev || >=2.8.0-dev || >=2.9.0-dev || >=3.0.0-dev || >= 3.1.0-dev || >= 3.2.0-dev || >= 4.0.0-dev" - } - }, - "node_modules/tslint/node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "dependencies": { - "sprintf-js": "~1.0.2" - } - }, - "node_modules/tslint/node_modules/diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", - "dev": true, - "engines": { - "node": ">=0.3.1" - } - }, - "node_modules/tslint/node_modules/js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", - "dev": true, - "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/tslint/node_modules/semver": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", - "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", - "dev": true, - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/tsutils": { - "version": "2.29.0", - "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.29.0.tgz", - "integrity": "sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA==", - "dev": true, - "dependencies": { - "tslib": "^1.8.1" - }, - "peerDependencies": { - "typescript": ">=2.1.0 || >=2.1.0-dev || >=2.2.0-dev || >=2.3.0-dev || >=2.4.0-dev || >=2.5.0-dev || >=2.6.0-dev || >=2.7.0-dev || >=2.8.0-dev || >=2.9.0-dev || >= 3.0.0-dev || >= 3.1.0-dev" + "node": ">=0.3.1" } }, + "node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, "node_modules/tty-browserify": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.1.tgz", @@ -4161,6 +5789,18 @@ "integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==", "dev": true }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/type-detect": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", @@ -4170,6 +5810,18 @@ "node": ">=4" } }, + "node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/typedarray": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", @@ -4189,6 +5841,24 @@ "node": ">=4.2.0" } }, + "node_modules/uc.micro": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz", + "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==", + "dev": true + }, + "node_modules/uglify-js": { + "version": "3.17.4", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.17.4.tgz", + "integrity": "sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g==", + "dev": true, + "bin": { + "uglifyjs": "bin/uglifyjs" + }, + "engines": { + "node": ">=0.8.0" + } + }, "node_modules/umd": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/umd/-/umd-3.0.3.tgz", @@ -4214,6 +5884,12 @@ "undeclared-identifiers": "bin.js" } }, + "node_modules/underscore": { + "version": "1.13.6", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.6.tgz", + "integrity": "sha512-+A5Sja4HP1M08MaXya7p5LvjuM7K6q/2EaC0+iovj/wOcMsTzMvDFbasi/oSapiwOlt252IqsKqPjCl7huKS0A==", + "dev": true + }, "node_modules/update-browserslist-db": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz", @@ -4240,6 +5916,24 @@ "browserslist": ">= 4.21.0" } }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/uri-js/node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/url": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz", @@ -4324,6 +6018,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/workerpool": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.0.tgz", @@ -4386,6 +6089,12 @@ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "dev": true }, + "node_modules/xmlcreate": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/xmlcreate/-/xmlcreate-2.0.4.tgz", + "integrity": "sha512-nquOebG4sngPmGPICTS5EnxqhKbCmz5Ox5hsszI2T6U5qdrJizBc+0ilYSEjTSzU0yZcmvppztXe/5Al5fUwdg==", + "dev": true + }, "node_modules/xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", @@ -4475,6 +6184,12 @@ } }, "dependencies": { + "@aashutoshrathi/word-wrap": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", + "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==", + "dev": true + }, "@ampproject/remapping": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz", @@ -4768,6 +6483,57 @@ "to-fast-properties": "^2.0.0" } }, + "@eslint/eslintrc": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.4.1.tgz", + "integrity": "sha512-XXrH9Uarn0stsyldqDYq8r++mROmWRI1xKMXa640Bb//SY1+ECYX6VzT6Lcx5frD0V30XieqJ0oX9I2Xj5aoMA==", + "dev": true, + "requires": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.4.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "dependencies": { + "globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dev": true, + "requires": { + "type-fest": "^0.20.2" + } + } + } + }, + "@humanwhocodes/config-array": { + "version": "0.11.14", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", + "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==", + "dev": true, + "requires": { + "@humanwhocodes/object-schema": "^2.0.2", + "debug": "^4.3.1", + "minimatch": "^3.0.5" + } + }, + "@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true + }, + "@humanwhocodes/object-schema": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.2.tgz", + "integrity": "sha512-6EwiSjwWYP7pTckG6I5eyFANjPhmPjUX9JRLUSfNPC7FX7zK9gyZAfUEaECL6ALTpGX5AjnBq3C9XmVWPitNpw==", + "dev": true + }, "@jridgewell/gen-mapping": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz", @@ -4806,6 +6572,15 @@ "@jridgewell/sourcemap-codec": "1.4.14" } }, + "@jsdoc/salty": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/@jsdoc/salty/-/salty-0.2.7.tgz", + "integrity": "sha512-mh8LbS9d4Jq84KLw8pzho7XC2q2/IJGiJss3xwRoLD1A+EE16SjN4PfaG4jRCzKegTFLlN0Zd8SdUPE6XdoPFg==", + "dev": true, + "requires": { + "lodash": "^4.17.21" + } + }, "@multiversx/sdk-bls-wasm": { "version": "0.3.5", "resolved": "https://registry.npmjs.org/@multiversx/sdk-bls-wasm/-/sdk-bls-wasm-0.3.5.tgz", @@ -4813,29 +6588,16 @@ "dev": true }, "@multiversx/sdk-network-providers": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@multiversx/sdk-network-providers/-/sdk-network-providers-2.2.0.tgz", - "integrity": "sha512-2n/+7Ap6S9rJGTiX38GCZ2TmY9zQ1U7o1DwnWpHNRJRxArSN/xzLrbcSKy8InMyc+4A+VHf5pV0Pk8NdPV6++w==", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@multiversx/sdk-network-providers/-/sdk-network-providers-2.4.1.tgz", + "integrity": "sha512-AyKLxt51v4Y94NC3/0witz7XPpZ6+2mOi8CVW+j7HP6RtDl5vJinxCriSKb0Z/PzV6LdmJmwCW5iDRebMIk6fg==", "dev": true, "requires": { - "axios": "1.6.1", + "axios": "1.6.5", "bech32": "1.1.4", "bignumber.js": "9.0.1", "buffer": "6.0.3", "json-bigint": "1.0.0" - }, - "dependencies": { - "axios": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.1.tgz", - "integrity": "sha512-vfBmhDpKafglh0EldBEbVuoe7DyAavGSLWhuSm5ZSEKQnHhBf0xAAwybbNH1IkrJNGnS/VG4I5yxig1pCEXE4g==", - "dev": true, - "requires": { - "follow-redirects": "^1.15.0", - "form-data": "^4.0.0", - "proxy-from-env": "^1.1.0" - } - } } }, "@multiversx/sdk-transaction-decoder": { @@ -4854,39 +6616,9 @@ } }, "@multiversx/sdk-wallet": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@multiversx/sdk-wallet/-/sdk-wallet-3.0.0.tgz", - "integrity": "sha512-nDVBtva1mpfutXA8TfUnpdeFqhY9O+deNU3U/Z41yPBcju1trHDC9gcKPyQqcQ3qjG/6LwEXmIm7Dc5fIsvVjg==", - "dev": true, - "requires": { - "@multiversx/sdk-bls-wasm": "0.3.5", - "bech32": "1.1.4", - "bip39": "3.0.2", - "blake2b": "2.1.3", - "ed25519-hd-key": "1.1.2", - "ed2curve": "0.3.0", - "keccak": "3.0.1", - "scryptsy": "2.1.0", - "tweetnacl": "1.0.3", - "uuid": "8.3.2" - }, - "dependencies": { - "keccak": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/keccak/-/keccak-3.0.1.tgz", - "integrity": "sha512-epq90L9jlFWCW7+pQa6JOnKn2Xgl2mtI664seYR6MHskvI9agt7AnDqmAlp9TqU4/caMYbA08Hi5DMZAl5zdkA==", - "dev": true, - "requires": { - "node-addon-api": "^2.0.0", - "node-gyp-build": "^4.2.0" - } - } - } - }, - "@multiversx/sdk-wallet-next": { - "version": "npm:@multiversx/sdk-wallet@4.0.0", - "resolved": "https://registry.npmjs.org/@multiversx/sdk-wallet/-/sdk-wallet-4.0.0.tgz", - "integrity": "sha512-Fskqg9AGgqSIAgN+Ag9Y/DIoZRr4qgB0baVZ1nlXhgaRuM30v1UeW0TAIhuAbXkkMiTOJyLaCeebUDYy1VJgWA==", + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@multiversx/sdk-wallet/-/sdk-wallet-4.4.0.tgz", + "integrity": "sha512-wS4P8a2ts3cNaSLUw9VFf4yhWSMTYng+nyHKi3/9QalLP5lxBumUfD/mUkb9sK13UPJ5Xp/zB3j8a4Qdllw2Ag==", "dev": true, "requires": { "@multiversx/sdk-bls-wasm": "0.3.5", @@ -4927,30 +6659,61 @@ "integrity": "sha512-ilHEACi9DwqJB0pw7kv+Apvh50jiiSyR/cQ3y4W7lOR5mhvn/50FLUfsnfJz0BDZtl/RR16kXvptiv6q1msYZg==", "dev": true }, + "@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "requires": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + } + }, + "@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true + }, + "@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "requires": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + } + }, "@protobufjs/aspromise": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", - "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==" + "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==", + "peer": true }, "@protobufjs/base64": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", - "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==" + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", + "peer": true }, "@protobufjs/codegen": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", - "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==" + "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==", + "peer": true }, "@protobufjs/eventemitter": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", - "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==" + "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==", + "peer": true }, "@protobufjs/fetch": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", + "peer": true, "requires": { "@protobufjs/aspromise": "^1.1.1", "@protobufjs/inquire": "^1.1.0" @@ -4959,27 +6722,32 @@ "@protobufjs/float": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", - "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==" + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==", + "peer": true }, "@protobufjs/inquire": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", - "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==" + "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==", + "peer": true }, "@protobufjs/path": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", - "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==" + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==", + "peer": true }, "@protobufjs/pool": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", - "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==" + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==", + "peer": true }, "@protobufjs/utf8": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", - "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==" + "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==", + "peer": true }, "@types/assert": { "version": "1.4.6", @@ -4993,6 +6761,34 @@ "integrity": "sha512-t7uW6eFafjO+qJ3BIV2gGUyZs27egcNRkUdalkud+Qa3+kg//f129iuOFivHDXQ+vnU3fDXuwgv0cqMCbcE8sw==", "dev": true }, + "@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true + }, + "@types/linkify-it": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-3.0.5.tgz", + "integrity": "sha512-yg6E+u0/+Zjva+buc3EIb+29XEg4wltq7cSmd4Uc2EE/1nUVmxyzpX6gUXD0V8jIrG0r7YeOGVIbYRkxeooCtw==", + "dev": true + }, + "@types/markdown-it": { + "version": "12.2.3", + "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-12.2.3.tgz", + "integrity": "sha512-GKMHFfv3458yYy+v/N8gjufHO6MSZKCOXpZc5GXIWWy8uldwfmPn98vp81gZ5f9SVw8YYBctgfJ22a2d7AOMeQ==", + "dev": true, + "requires": { + "@types/linkify-it": "*", + "@types/mdurl": "*" + } + }, + "@types/mdurl": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-1.0.5.tgz", + "integrity": "sha512-6L6VymKTzYSrEf4Nev4Xa1LCHKrlTlYCBMTlQKFuddo1CvQcE52I0mwfOJayueUC7MJuXOeHTcIU683lzd0cUA==", + "dev": true + }, "@types/mocha": { "version": "9.1.0", "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-9.1.0.tgz", @@ -5004,6 +6800,217 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-13.13.2.tgz", "integrity": "sha512-LB2R1Oyhpg8gu4SON/mfforE525+Hi/M1ineICEDftqNVTyFg1aRIeGuTvXAoWHc4nbrFncWtJgMmoyRvuGh7A==" }, + "@types/semver": { + "version": "7.5.6", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.6.tgz", + "integrity": "sha512-dn1l8LaMea/IjDoHNd9J52uBbInB796CDffS6VdIxvqYCPSG0V0DzHp76GpaWnlhg88uYyPbXCDIowa86ybd5A==", + "dev": true + }, + "@typescript-eslint/eslint-plugin": { + "version": "5.44.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.44.0.tgz", + "integrity": "sha512-j5ULd7FmmekcyWeArx+i8x7sdRHzAtXTkmDPthE4amxZOWKFK7bomoJ4r7PJ8K7PoMzD16U8MmuZFAonr1ERvw==", + "dev": true, + "requires": { + "@typescript-eslint/scope-manager": "5.44.0", + "@typescript-eslint/type-utils": "5.44.0", + "@typescript-eslint/utils": "5.44.0", + "debug": "^4.3.4", + "ignore": "^5.2.0", + "natural-compare-lite": "^1.4.0", + "regexpp": "^3.2.0", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "dependencies": { + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + }, + "tsutils": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", + "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", + "dev": true, + "requires": { + "tslib": "^1.8.1" + } + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + } + } + }, + "@typescript-eslint/parser": { + "version": "5.44.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.44.0.tgz", + "integrity": "sha512-H7LCqbZnKqkkgQHaKLGC6KUjt3pjJDx8ETDqmwncyb6PuoigYajyAwBGz08VU/l86dZWZgI4zm5k2VaKqayYyA==", + "dev": true, + "requires": { + "@typescript-eslint/scope-manager": "5.44.0", + "@typescript-eslint/types": "5.44.0", + "@typescript-eslint/typescript-estree": "5.44.0", + "debug": "^4.3.4" + } + }, + "@typescript-eslint/scope-manager": { + "version": "5.44.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.44.0.tgz", + "integrity": "sha512-2pKml57KusI0LAhgLKae9kwWeITZ7IsZs77YxyNyIVOwQ1kToyXRaJLl+uDEXzMN5hnobKUOo2gKntK9H1YL8g==", + "dev": true, + "requires": { + "@typescript-eslint/types": "5.44.0", + "@typescript-eslint/visitor-keys": "5.44.0" + } + }, + "@typescript-eslint/type-utils": { + "version": "5.44.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.44.0.tgz", + "integrity": "sha512-A1u0Yo5wZxkXPQ7/noGkRhV4J9opcymcr31XQtOzcc5nO/IHN2E2TPMECKWYpM3e6olWEM63fq/BaL1wEYnt/w==", + "dev": true, + "requires": { + "@typescript-eslint/typescript-estree": "5.44.0", + "@typescript-eslint/utils": "5.44.0", + "debug": "^4.3.4", + "tsutils": "^3.21.0" + }, + "dependencies": { + "tsutils": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", + "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", + "dev": true, + "requires": { + "tslib": "^1.8.1" + } + } + } + }, + "@typescript-eslint/types": { + "version": "5.44.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.44.0.tgz", + "integrity": "sha512-Tp+zDnHmGk4qKR1l+Y1rBvpjpm5tGXX339eAlRBDg+kgZkz9Bw+pqi4dyseOZMsGuSH69fYfPJCBKBrbPCxYFQ==", + "dev": true + }, + "@typescript-eslint/typescript-estree": { + "version": "5.44.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.44.0.tgz", + "integrity": "sha512-M6Jr+RM7M5zeRj2maSfsZK2660HKAJawv4Ud0xT+yauyvgrsHu276VtXlKDFnEmhG+nVEd0fYZNXGoAgxwDWJw==", + "dev": true, + "requires": { + "@typescript-eslint/types": "5.44.0", + "@typescript-eslint/visitor-keys": "5.44.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "dependencies": { + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + }, + "tsutils": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", + "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", + "dev": true, + "requires": { + "tslib": "^1.8.1" + } + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + } + } + }, + "@typescript-eslint/utils": { + "version": "5.44.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.44.0.tgz", + "integrity": "sha512-fMzA8LLQ189gaBjS0MZszw5HBdZgVwxVFShCO3QN+ws3GlPkcy9YuS3U4wkT6su0w+Byjq3mS3uamy9HE4Yfjw==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.9", + "@types/semver": "^7.3.12", + "@typescript-eslint/scope-manager": "5.44.0", + "@typescript-eslint/types": "5.44.0", + "@typescript-eslint/typescript-estree": "5.44.0", + "eslint-scope": "^5.1.1", + "eslint-utils": "^3.0.0", + "semver": "^7.3.7" + }, + "dependencies": { + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + } + } + }, + "@typescript-eslint/visitor-keys": { + "version": "5.44.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.44.0.tgz", + "integrity": "sha512-a48tLG8/4m62gPFbJ27FxwCOqPKxsb8KC3HkmYoq2As/4YyjQl1jDbRr1s63+g4FS/iIehjmN3L5UjmKva1HzQ==", + "dev": true, + "requires": { + "@typescript-eslint/types": "5.44.0", + "eslint-visitor-keys": "^3.3.0" + } + }, "@ungap/promise-all-settled": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz", @@ -5016,6 +7023,13 @@ "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", "dev": true }, + "acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "requires": {} + }, "acorn-node": { "version": "1.8.2", "resolved": "https://registry.npmjs.org/acorn-node/-/acorn-node-1.8.2.tgz", @@ -5033,6 +7047,18 @@ "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==", "dev": true }, + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, "ansi-colors": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", @@ -5076,6 +7102,12 @@ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "dev": true }, + "array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true + }, "asn1.js": { "version": "5.4.1", "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-5.4.1.tgz", @@ -5303,11 +7335,6 @@ "integrity": "sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==", "dev": true }, - "backslash": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/backslash/-/backslash-0.2.0.tgz", - "integrity": "sha512-Avs+8FUZ1HF/VFP4YWwHQZSGzRPm37ukU1JQYQWijuHhtXdOuAzcZ8PcAzfIw898a8PyBzdn+RtnKA6MzW0X2A==" - }, "balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -5372,6 +7399,12 @@ "nanoassert": "^1.0.0" } }, + "bluebird": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", + "dev": true + }, "bn.js": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz", @@ -5646,12 +7679,6 @@ "integrity": "sha512-571s0T7nZWK6vB67HI5dyUF7wXiNcfaPPPTl6zYCNApANjIvYJTg7hlud/+cJpdAhS7dVzqMLmfhfHR3rAcOjQ==", "dev": true }, - "builtin-modules": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", - "integrity": "sha512-wxXCdllwGhI2kCC0MnvTGYTMvnVZTvqgypkiTI8Pa5tcz2i6VqsqwYGgqwXji+4RgCzms6EajE4IxiUH6HH8nQ==", - "dev": true - }, "builtin-status-codes": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz", @@ -5674,6 +7701,12 @@ "get-intrinsic": "^1.0.2" } }, + "callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true + }, "camelcase": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", @@ -5686,6 +7719,15 @@ "integrity": "sha512-CPB+UL9XMT/Av+pJxCKGhdx+yg1hzplvFJQlJ2n68PyQGMz9L/E2zCyLdOL8uasbouTUgnPl+y0tccI/se+BEw==", "dev": true }, + "catharsis": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/catharsis/-/catharsis-0.9.0.tgz", + "integrity": "sha512-prMTQVpcns/tzFgFVkVp6ak6RykZyWb3gu8ckUpd6YkTlacOd3DXGJjIpD4Q6zJirizvaiAjSSHlOsA+6sNh2A==", + "dev": true, + "requires": { + "lodash": "^4.17.15" + } + }, "chai": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/chai/-/chai-4.2.0.tgz", @@ -5790,12 +7832,6 @@ "delayed-stream": "~1.0.0" } }, - "commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true - }, "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -5895,6 +7931,17 @@ "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", "dev": true }, + "cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, "crypto-browserify": { "version": "3.12.0", "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz", @@ -5944,6 +7991,12 @@ "type-detect": "^4.0.0" } }, + "deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, "define-properties": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.4.tgz", @@ -6024,6 +8077,24 @@ } } }, + "dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "requires": { + "path-type": "^4.0.0" + } + }, + "doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "requires": { + "esutils": "^2.0.2" + } + }, "domain-browser": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.2.0.tgz", @@ -6094,6 +8165,12 @@ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "dev": true }, + "entities": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.1.0.tgz", + "integrity": "sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==", + "dev": true + }, "es6-object-assign": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/es6-object-assign/-/es6-object-assign-1.1.0.tgz", @@ -6112,6 +8189,259 @@ "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", "dev": true }, + "escodegen": { + "version": "1.14.3", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.14.3.tgz", + "integrity": "sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw==", + "dev": true, + "requires": { + "esprima": "^4.0.1", + "estraverse": "^4.2.0", + "esutils": "^2.0.2", + "optionator": "^0.8.1", + "source-map": "~0.6.1" + }, + "dependencies": { + "estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true + }, + "levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==", + "dev": true, + "requires": { + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" + } + }, + "optionator": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", + "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", + "dev": true, + "requires": { + "deep-is": "~0.1.3", + "fast-levenshtein": "~2.0.6", + "levn": "~0.3.0", + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2", + "word-wrap": "~1.2.3" + } + }, + "prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==", + "dev": true + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "optional": true + }, + "type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==", + "dev": true, + "requires": { + "prelude-ls": "~1.1.2" + } + } + } + }, + "eslint": { + "version": "8.28.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.28.0.tgz", + "integrity": "sha512-S27Di+EVyMxcHiwDrFzk8dJYAaD+/5SoWKxL1ri/71CRHsnJnRDPNt2Kzj24+MT9FDupf4aqqyqPrvI8MvQ4VQ==", + "dev": true, + "requires": { + "@eslint/eslintrc": "^1.3.3", + "@humanwhocodes/config-array": "^0.11.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "ajv": "^6.10.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.1.1", + "eslint-utils": "^3.0.0", + "eslint-visitor-keys": "^3.3.0", + "espree": "^9.4.0", + "esquery": "^1.4.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.15.0", + "grapheme-splitter": "^1.0.4", + "ignore": "^5.2.0", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-sdsl": "^4.1.4", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.1", + "regexpp": "^3.2.0", + "strip-ansi": "^6.0.1", + "strip-json-comments": "^3.1.0", + "text-table": "^0.2.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true + }, + "eslint-scope": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "dev": true, + "requires": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + } + }, + "estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true + }, + "glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "requires": { + "is-glob": "^4.0.3" + } + }, + "globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dev": true, + "requires": { + "type-fest": "^0.20.2" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "eslint-config-prettier": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.1.0.tgz", + "integrity": "sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==", + "dev": true, + "requires": {} + }, + "eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "requires": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "dependencies": { + "estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true + } + } + }, + "eslint-utils": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", + "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", + "dev": true, + "requires": { + "eslint-visitor-keys": "^2.0.0" + }, + "dependencies": { + "eslint-visitor-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "dev": true + } + } + }, + "eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true + }, "esmify": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/esmify/-/esmify-2.1.1.tgz", @@ -6130,12 +8460,71 @@ "through2": "^2.0.5" } }, + "espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "dev": true, + "requires": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "dependencies": { + "acorn": { + "version": "8.11.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", + "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", + "dev": true + } + } + }, "esprima": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", "dev": true }, + "esquery": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", + "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", + "dev": true, + "requires": { + "estraverse": "^5.1.0" + }, + "dependencies": { + "estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true + } + } + }, + "esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "requires": { + "estraverse": "^5.2.0" + }, + "dependencies": { + "estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true + } + } + }, + "estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true + }, "esutils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", @@ -6158,12 +8547,61 @@ "safe-buffer": "^5.1.1" } }, + "fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "fast-glob": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", + "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", + "dev": true, + "requires": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + } + }, + "fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, "fast-safe-stringify": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==", "dev": true }, + "fastq": { + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.0.tgz", + "integrity": "sha512-zGygtijUMT7jnk3h26kUms3BkSDp4IfIKjmnqI2tvx6nuBfiF1UqOxbnLfzdv+apBy+53oaImsKtMw/xYbW+1w==", + "dev": true, + "requires": { + "reusify": "^1.0.4" + } + }, + "file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "requires": { + "flat-cache": "^3.0.4" + } + }, "fill-range": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", @@ -6189,10 +8627,27 @@ "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", "dev": true }, + "flat-cache": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", + "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", + "dev": true, + "requires": { + "flatted": "^3.2.9", + "keyv": "^4.5.3", + "rimraf": "^3.0.2" + } + }, + "flatted": { + "version": "3.2.9", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.9.tgz", + "integrity": "sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==", + "dev": true + }, "follow-redirects": { - "version": "1.15.4", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.4.tgz", - "integrity": "sha512-Cr4D/5wlrb0z9dgERpUL3LrmPKVDsETIJhaCMeDfuFYcqa5bldGV6wBsAN6X/vxlXQtFBMrXdXxdL8CbDTGniw==", + "version": "1.15.6", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", + "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", "dev": true }, "for-each": { @@ -6298,6 +8753,20 @@ "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", "dev": true }, + "globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "requires": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + } + }, "gopd": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", @@ -6307,6 +8776,18 @@ "get-intrinsic": "^1.1.3" } }, + "graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true + }, + "grapheme-splitter": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", + "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", + "dev": true + }, "growl": { "version": "1.10.5", "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", @@ -6437,6 +8918,28 @@ "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==" }, + "ignore": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.0.tgz", + "integrity": "sha512-g7dmpshy+gD7mh88OC9NwSGTKoc3kyLAZQRU1mt53Aw/vnvfXnbC+F/7F7QoYVKbV+KNvJx8wArewKy1vXMtlg==", + "dev": true + }, + "import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "requires": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + } + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true + }, "inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -6574,6 +9077,12 @@ "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "dev": true }, + "is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true + }, "is-plain-obj": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", @@ -6611,6 +9120,12 @@ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "dev": true }, + "js-sdsl": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.4.2.tgz", + "integrity": "sha512-dwXFwByc/ajSV6m5bcKAPwe4yDDF6D614pxmIi5odytzxRlwqF6nwoiCek80Ixc7Cvma5awClxrzFtxCQvcM8w==", + "dev": true + }, "js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -6626,6 +9141,52 @@ "argparse": "^2.0.1" } }, + "js2xmlparser": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/js2xmlparser/-/js2xmlparser-4.0.2.tgz", + "integrity": "sha512-6n4D8gLlLf1n5mNLQPRfViYzu9RATblzPEtm1SthMX1Pjao0r9YI9nw7ZIfRxQMERS87mcswrg+r/OYrPRX6jA==", + "dev": true, + "requires": { + "xmlcreate": "^2.0.4" + } + }, + "jsdoc": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/jsdoc/-/jsdoc-4.0.2.tgz", + "integrity": "sha512-e8cIg2z62InH7azBBi3EsSEqrKx+nUtAS5bBcYTSpZFA+vhNPyhv8PTFZ0WsjOPDj04/dOLlm08EDcQJDqaGQg==", + "dev": true, + "requires": { + "@babel/parser": "^7.20.15", + "@jsdoc/salty": "^0.2.1", + "@types/markdown-it": "^12.2.3", + "bluebird": "^3.7.2", + "catharsis": "^0.9.0", + "escape-string-regexp": "^2.0.0", + "js2xmlparser": "^4.0.2", + "klaw": "^3.0.0", + "markdown-it": "^12.3.2", + "markdown-it-anchor": "^8.4.1", + "marked": "^4.0.10", + "mkdirp": "^1.0.4", + "requizzle": "^0.2.3", + "strip-json-comments": "^3.1.0", + "underscore": "~1.13.2" + }, + "dependencies": { + "escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true + }, + "mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true + } + } + }, "jsesc": { "version": "2.5.2", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", @@ -6636,18 +9197,27 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", - "dev": true, "requires": { "bignumber.js": "^9.0.0" } }, - "json-duplicate-key-handle": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-duplicate-key-handle/-/json-duplicate-key-handle-1.0.0.tgz", - "integrity": "sha512-OLIxL+UpfwUsqcLX3i6Z51ChTou/Vje+6bSeGUSubj96dF/SfjObDprLy++ZXYH07KITuEzsXS7PX7e/BGf4jw==", - "requires": { - "backslash": "^0.2.0" - } + "json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true }, "json5": { "version": "2.2.3", @@ -6693,6 +9263,24 @@ } } }, + "keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "requires": { + "json-buffer": "3.0.1" + } + }, + "klaw": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/klaw/-/klaw-3.0.0.tgz", + "integrity": "sha512-0Fo5oir+O9jnXu5EefYbVK+mHMBeEVEy2cmctR1O1NECcCkPRreJKrS6Qt/j3KC2C148Dfo9i3pCmCMsdqGr0g==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.9" + } + }, "labeled-stream-splicer": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/labeled-stream-splicer/-/labeled-stream-splicer-2.0.2.tgz", @@ -6703,6 +9291,25 @@ "stream-splicer": "^2.0.0" } }, + "levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "requires": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + } + }, + "linkify-it": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-3.0.3.tgz", + "integrity": "sha512-ynTsyrFSdE5oZ/O9GEf00kPngmOfVwazR5GKDq6EYfhlpFug3J2zybX56a2PRRpc9P+FuSoGNAwjlbDs9jJBPQ==", + "dev": true, + "requires": { + "uc.micro": "^1.0.1" + } + }, "locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -6724,6 +9331,12 @@ "integrity": "sha512-eDn9kqrAmVUC1wmZvlQ6Uhde44n+tXpqPrN8olQJbttgh0oKclk+SF54P47VEGE9CEiMeRwAP8BaM7UHvBkz2A==", "dev": true }, + "lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, "log-symbols": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", @@ -6788,7 +9401,8 @@ "long": { "version": "5.2.3", "resolved": "https://registry.npmjs.org/long/-/long-5.2.3.tgz", - "integrity": "sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==" + "integrity": "sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==", + "peer": true }, "loose-envify": { "version": "1.4.0", @@ -6814,6 +9428,32 @@ "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", "dev": true }, + "markdown-it": { + "version": "12.3.2", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-12.3.2.tgz", + "integrity": "sha512-TchMembfxfNVpHkbtriWltGWc+m3xszaRD0CZup7GFFhzIgQqxIfn3eGj1yZpfuflzPvfkt611B2Q/Bsk1YnGg==", + "dev": true, + "requires": { + "argparse": "^2.0.1", + "entities": "~2.1.0", + "linkify-it": "^3.0.1", + "mdurl": "^1.0.1", + "uc.micro": "^1.0.5" + } + }, + "markdown-it-anchor": { + "version": "8.6.7", + "resolved": "https://registry.npmjs.org/markdown-it-anchor/-/markdown-it-anchor-8.6.7.tgz", + "integrity": "sha512-FlCHFwNnutLgVTflOYHPW2pPcl2AACqVzExlkGQNsi4CJgqOHN7YTgDd4LuhgN1BFO3TS0vLAruV1Td6dwWPJA==", + "dev": true, + "requires": {} + }, + "marked": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/marked/-/marked-4.3.0.tgz", + "integrity": "sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==", + "dev": true + }, "md5.js": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", @@ -6825,6 +9465,28 @@ "safe-buffer": "^5.1.2" } }, + "mdurl": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", + "integrity": "sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g==", + "dev": true + }, + "merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true + }, + "micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "requires": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + } + }, "miller-rabin": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz", @@ -6885,15 +9547,6 @@ "integrity": "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==", "dev": true }, - "mkdirp": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", - "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", - "dev": true, - "requires": { - "minimist": "^1.2.6" - } - }, "mkdirp-classic": { "version": "0.5.3", "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", @@ -7052,6 +9705,18 @@ "integrity": "sha512-n6Vs/3KGyxPQd6uO0eH4Bv0ojGSUvuLlIHtC3Y0kEO23YRge8H9x1GCzLn28YX0H66pMkxuaeESFq4tKISKwdw==", "dev": true }, + "natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, + "natural-compare-lite": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz", + "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==", + "dev": true + }, "node-addon-api": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-2.0.2.tgz", @@ -7105,6 +9770,20 @@ "wrappy": "1" } }, + "optionator": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", + "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", + "dev": true, + "requires": { + "@aashutoshrathi/word-wrap": "^1.2.3", + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0" + } + }, "os-browserify": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.3.0.tgz", @@ -7135,6 +9814,15 @@ "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", "dev": true }, + "parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "requires": { + "callsites": "^3.0.0" + } + }, "parents": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parents/-/parents-1.0.1.tgz", @@ -7175,6 +9863,12 @@ "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", "dev": true }, + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true + }, "path-parse": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", @@ -7187,6 +9881,12 @@ "integrity": "sha512-Y30dB6rab1A/nfEKsZxmr01nUotHX0c/ZiIAsCTatEe1CmS5Pm5He7fZ195bPT7RdquoaL8lLxFCMQi/bS7IJg==", "dev": true }, + "path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true + }, "pathval": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", @@ -7218,6 +9918,18 @@ "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", "dev": true }, + "prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true + }, + "prettier": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.4.tgz", + "integrity": "sha512-FWu1oLHKCrtpO1ypU6J0SbK2d9Ckwysq6bHj/uaCP26DxrPpppCLQRGVuqAxSTvhF00AcvDRyYrLNW7ocBhFFQ==", + "dev": true + }, "process": { "version": "0.11.10", "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", @@ -7231,9 +9943,10 @@ "dev": true }, "protobufjs": { - "version": "7.2.4", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.2.4.tgz", - "integrity": "sha512-AT+RJgD2sH8phPmCf7OUZR8xGdcJRga4+1cOaXJ64hvcSkVhNcRHOwIxUatPH15+nj59WAGTDv3LSGZPEQbJaQ==", + "version": "7.2.6", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.2.6.tgz", + "integrity": "sha512-dgJaEDDL6x8ASUZ1YqWciTRrdOuYNzoOf27oHNfdyvKqHr5i0FV7FSLU+aIeFjyFgVxrpTOtQUi0BLLBymZaBw==", + "peer": true, "requires": { "@protobufjs/aspromise": "^1.1.2", "@protobufjs/base64": "^1.1.2", @@ -7249,6 +9962,130 @@ "long": "^5.0.0" } }, + "protobufjs-cli": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/protobufjs-cli/-/protobufjs-cli-1.1.2.tgz", + "integrity": "sha512-8ivXWxT39gZN4mm4ArQyJrRgnIwZqffBWoLDsE21TmMcKI3XwJMV4lEF2WU02C4JAtgYYc2SfJIltelD8to35g==", + "dev": true, + "requires": { + "chalk": "^4.0.0", + "escodegen": "^1.13.0", + "espree": "^9.0.0", + "estraverse": "^5.1.0", + "glob": "^8.0.0", + "jsdoc": "^4.0.0", + "minimist": "^1.2.0", + "semver": "^7.1.2", + "tmp": "^0.2.1", + "uglify-js": "^3.7.7" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "glob": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "requires": { + "brace-expansion": "^2.0.1" + } + }, + "semver": { + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + } + } + }, "proxy-from-env": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", @@ -7295,6 +10132,12 @@ "integrity": "sha512-773xhDQnZBMFobEiztv8LIl70ch5MSF/jUQVlhwFyBILqq96anmoctVIYz+ZRp0qbCKATTn6ev02M3r7Ga5vqA==", "dev": true }, + "queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true + }, "randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", @@ -7370,12 +10213,27 @@ "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==", "dev": true }, + "regexpp": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", + "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", + "dev": true + }, "require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", "dev": true }, + "requizzle": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/requizzle/-/requizzle-0.2.4.tgz", + "integrity": "sha512-JRrFk1D4OQ4SqovXOgdav+K8EAhSB/LJZqCz8tbX0KObcdeM15Ss59ozWMBWmmINMagCwmqn4ZNryUGpBsl6Jw==", + "dev": true, + "requires": { + "lodash": "^4.17.21" + } + }, "resolve": { "version": "1.22.1", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", @@ -7387,6 +10245,27 @@ "supports-preserve-symlinks-flag": "^1.0.0" } }, + "resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true + }, + "reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true + }, + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, "ripemd160": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", @@ -7397,6 +10276,15 @@ "inherits": "^2.0.1" } }, + "run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "requires": { + "queue-microtask": "^1.2.2" + } + }, "safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -7448,6 +10336,21 @@ "fast-safe-stringify": "^2.0.7" } }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true + }, "shell-quote": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.0.tgz", @@ -7460,6 +10363,12 @@ "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", "dev": true }, + "slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true + }, "source-map": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", @@ -7484,12 +10393,6 @@ } } }, - "sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", - "dev": true - }, "stream-browserify": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-3.0.0.tgz", @@ -7625,6 +10528,12 @@ "acorn-node": "^1.2.0" } }, + "text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true + }, "through": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", @@ -7650,6 +10559,12 @@ "process": "~0.11.0" } }, + "tmp": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.3.tgz", + "integrity": "sha512-nZD7m9iCPC5g0pYmcaxogYKggSfLsdxl8of3Q/oIbqCqLLIO9IAF0GWjX1z9NZRHPiXv8Wex4yDCaZsgEw0Y8w==", + "dev": true + }, "to-fast-properties": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", @@ -7693,69 +10608,6 @@ "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", "dev": true }, - "tslint": { - "version": "6.1.3", - "resolved": "https://registry.npmjs.org/tslint/-/tslint-6.1.3.tgz", - "integrity": "sha512-IbR4nkT96EQOvKE2PW/djGz8iGNeJ4rF2mBfiYaR/nvUWYKJhLwimoJKgjIFEIDibBtOevj7BqCRL4oHeWWUCg==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.0.0", - "builtin-modules": "^1.1.1", - "chalk": "^2.3.0", - "commander": "^2.12.1", - "diff": "^4.0.1", - "glob": "^7.1.1", - "js-yaml": "^3.13.1", - "minimatch": "^3.0.4", - "mkdirp": "^0.5.3", - "resolve": "^1.3.2", - "semver": "^5.3.0", - "tslib": "^1.13.0", - "tsutils": "^2.29.0" - }, - "dependencies": { - "argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "requires": { - "sprintf-js": "~1.0.2" - } - }, - "diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", - "dev": true - }, - "js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", - "dev": true, - "requires": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - } - }, - "semver": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", - "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", - "dev": true - } - } - }, - "tsutils": { - "version": "2.29.0", - "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.29.0.tgz", - "integrity": "sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA==", - "dev": true, - "requires": { - "tslib": "^1.8.1" - } - }, "tty-browserify": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.1.tgz", @@ -7768,12 +10620,27 @@ "integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==", "dev": true }, + "type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "requires": { + "prelude-ls": "^1.2.1" + } + }, "type-detect": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", "dev": true }, + "type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true + }, "typedarray": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", @@ -7786,6 +10653,18 @@ "integrity": "sha512-thGloWsGH3SOxv1SoY7QojKi0tc+8FnOmiarEGMbd/lar7QOEd3hvlx3Fp5y6FlDUGl9L+pd4n2e+oToGMmhRQ==", "dev": true }, + "uc.micro": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz", + "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==", + "dev": true + }, + "uglify-js": { + "version": "3.17.4", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.17.4.tgz", + "integrity": "sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g==", + "dev": true + }, "umd": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/umd/-/umd-3.0.3.tgz", @@ -7805,6 +10684,12 @@ "xtend": "^4.0.1" } }, + "underscore": { + "version": "1.13.6", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.6.tgz", + "integrity": "sha512-+A5Sja4HP1M08MaXya7p5LvjuM7K6q/2EaC0+iovj/wOcMsTzMvDFbasi/oSapiwOlt252IqsKqPjCl7huKS0A==", + "dev": true + }, "update-browserslist-db": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz", @@ -7815,6 +10700,23 @@ "picocolors": "^1.0.0" } }, + "uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "requires": { + "punycode": "^2.1.0" + }, + "dependencies": { + "punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true + } + } + }, "url": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz", @@ -7886,6 +10788,12 @@ "is-typed-array": "^1.1.10" } }, + "word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true + }, "workerpool": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.0.tgz", @@ -7935,6 +10843,12 @@ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "dev": true }, + "xmlcreate": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/xmlcreate/-/xmlcreate-2.0.4.tgz", + "integrity": "sha512-nquOebG4sngPmGPICTS5EnxqhKbCmz5Ox5hsszI2T6U5qdrJizBc+0ilYSEjTSzU0yZcmvppztXe/5Al5fUwdg==", + "dev": true + }, "xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", diff --git a/package.json b/package.json index a7aff72c3..24cdd41d8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@multiversx/sdk-core", - "version": "12.19.2", + "version": "13.0.0-beta.18", "description": "MultiversX SDK for JavaScript and TypeScript", "main": "out/index.js", "types": "out/index.d.js", @@ -10,15 +10,16 @@ ], "scripts": { "test": "npm run tests-unit", - "tests-unit": "mocha $(find . -name '*.spec.ts' ! -name '*.net.spec.*')", + "tests-unit": "mocha $(find . -name '*.spec.ts' ! -name '*.local.net.spec.*' ! -name '*.devnet.spec.*' ! -name '*.testnet.spec.*')", "tests-localnet": "mocha $(find . -name '*.local.net.spec.ts')", - "tests-devnet": "mocha $(find . -name '*.dev.net.spec.ts')", - "tests-testnet": "mocha $(find . -name '*.test.net.spec.ts')", + "tests-devnet": "mocha $(find . -name '*.devnet.spec.ts')", + "tests-testnet": "mocha $(find . -name '*.testnet.spec.ts')", "compile-browser": "tsc -p tsconfig.json && browserify out/index.js -o out-browser/sdk-core.js --standalone multiversxSdkCore -p esmify", "compile": "tsc -p tsconfig.json", - "compile-proto": "npx pbjs -t static-module -w commonjs -o src/proto/compiled.js src/proto/transaction.proto && npx pbts -o src/proto/compiled.d.ts src/proto/compiled.js", + "compile-proto": "npx pbjs -t static-module -w default -o src/proto/compiled.js src/proto/transaction.proto", "browser-tests": "make clean && make browser-tests && http-server --port=9876 -o browser-tests/index.html", - "lint": "tslint --project .", + "lint": "eslint .", + "pretty": "prettier --write ./src/**/*.{js,ts}", "pretest": "npm run compile", "prepare": "npm run compile" }, @@ -26,17 +27,15 @@ "license": "MIT", "dependencies": { "@multiversx/sdk-transaction-decoder": "1.0.2", + "json-bigint": "1.0.0", "bech32": "1.1.4", "blake2b": "2.1.3", "buffer": "6.0.3", - "json-duplicate-key-handle": "1.0.0", - "keccak": "3.0.2", - "protobufjs": "7.2.4" + "keccak": "3.0.2" }, "devDependencies": { - "@multiversx/sdk-network-providers": "2.2.0", - "@multiversx/sdk-wallet": "3.0.0", - "@multiversx/sdk-wallet-next": "npm:@multiversx/sdk-wallet@4.0.0", + "@multiversx/sdk-network-providers": "2.4.1", + "@multiversx/sdk-wallet": "4.4.0", "@types/assert": "1.4.6", "@types/chai": "4.2.11", "@types/mocha": "9.1.0", @@ -47,11 +46,17 @@ "chai": "4.2.0", "esmify": "2.1.1", "mocha": "9.2.2", + "protobufjs-cli": "1.1.2", "ts-node": "9.1.1", - "tslint": "6.1.3", - "typescript": "4.1.2" + "typescript": "4.1.2", + "@typescript-eslint/eslint-plugin": "5.44.0", + "@typescript-eslint/parser": "5.44.0", + "eslint": "8.28.0", + "eslint-config-prettier": "9.1.0", + "prettier": "3.2.4" }, "peerDependencies": { - "bignumber.js": "^9.0.1" + "bignumber.js": "^9.0.1", + "protobufjs": "^7.2.6" } } diff --git a/src/abi/typeFormula.ts b/src/abi/typeFormula.ts new file mode 100644 index 000000000..ca24926b8 --- /dev/null +++ b/src/abi/typeFormula.ts @@ -0,0 +1,18 @@ +export class TypeFormula { + name: string; + typeParameters: TypeFormula[]; + + constructor(name: string, typeParameters: TypeFormula[]) { + this.name = name; + this.typeParameters = typeParameters; + } + + toString(): string { + if (this.typeParameters.length > 0) { + const typeParameters = this.typeParameters.map((typeParameter) => typeParameter.toString()).join(", "); + return `${this.name}<${typeParameters}>`; + } else { + return this.name; + } + } +} diff --git a/src/abi/typeFormulaParser.spec.ts b/src/abi/typeFormulaParser.spec.ts new file mode 100644 index 000000000..7271b86b9 --- /dev/null +++ b/src/abi/typeFormulaParser.spec.ts @@ -0,0 +1,25 @@ +import { assert } from "chai"; +import { TypeFormulaParser } from "./typeFormulaParser"; + +describe("test type formula parser", () => { + it("should parse expression", async () => { + const parser = new TypeFormulaParser(); + + const testVectors = [ + ["i64", "i64"], + [" i64 ", "i64"], + ["utf-8 string", "utf-8 string"], + ["MultiResultVec>", "MultiResultVec>"], + ["tuple3>", "tuple3>"], + ["tuple2", "tuple2"], + ["tuple2 ", "tuple2"], + ["tuple, List>", "tuple, List>"], + ]; + + for (const [inputExpression, expectedExpression] of testVectors) { + const typeFormula = parser.parseExpression(inputExpression); + const outputExpression = typeFormula.toString(); + assert.equal(outputExpression, expectedExpression); + } + }); +}); diff --git a/src/abi/typeFormulaParser.ts b/src/abi/typeFormulaParser.ts new file mode 100644 index 000000000..02e7b9612 --- /dev/null +++ b/src/abi/typeFormulaParser.ts @@ -0,0 +1,130 @@ +import { TypeFormula } from "./typeFormula"; + +export class TypeFormulaParser { + static BEGIN_TYPE_PARAMETERS = "<"; + static END_TYPE_PARAMETERS = ">"; + static COMMA = ","; + static PUNCTUATION = [ + TypeFormulaParser.COMMA, + TypeFormulaParser.BEGIN_TYPE_PARAMETERS, + TypeFormulaParser.END_TYPE_PARAMETERS, + ]; + + parseExpression(expression: string): TypeFormula { + expression = expression.trim(); + + const tokens = this.tokenizeExpression(expression).filter((token) => token !== TypeFormulaParser.COMMA); + const stack: any[] = []; + + for (const token of tokens) { + if (this.isPunctuation(token)) { + if (this.isEndOfTypeParameters(token)) { + const typeFormula = this.acquireTypeWithParameters(stack); + stack.push(typeFormula); + } else if (this.isBeginningOfTypeParameters(token)) { + // This symbol is pushed as a simple string. + stack.push(token); + } else { + throw new Error(`Unexpected token (punctuation): ${token}`); + } + } else { + // It's a type name. We push it as a simple string. + stack.push(token); + } + } + + if (stack.length !== 1) { + throw new Error(`Unexpected stack length at end of parsing: ${stack.length}`); + } + if (TypeFormulaParser.PUNCTUATION.includes(stack[0])) { + throw new Error("Unexpected root element."); + } + + const item = stack[0]; + + if (item instanceof TypeFormula) { + return item; + } else if (typeof item === "string") { + // Expression contained a simple, non-generic type. + return new TypeFormula(item, []); + } else { + throw new Error(`Unexpected item on stack: ${item}`); + } + } + + private tokenizeExpression(expression: string): string[] { + const tokens: string[] = []; + let currentToken = ""; + + for (const character of expression) { + if (this.isPunctuation(character)) { + if (currentToken) { + // Retain current token + tokens.push(currentToken.trim()); + // Reset current token + currentToken = ""; + } + + // Punctuation character + tokens.push(character); + } else { + currentToken += character; + } + } + + if (currentToken) { + // Retain the last token (if any). + tokens.push(currentToken.trim()); + } + + return tokens; + } + + private acquireTypeWithParameters(stack: any[]): TypeFormula { + const typeParameters = this.acquireTypeParameters(stack); + const typeName = stack.pop(); + const typeFormula = new TypeFormula(typeName, typeParameters.reverse()); + return typeFormula; + } + + private acquireTypeParameters(stack: any[]): TypeFormula[] { + const typeParameters: TypeFormula[] = []; + + while (true) { + const item = stack.pop(); + + if (item === undefined) { + throw new Error("Badly specified type parameters"); + } + + if (this.isBeginningOfTypeParameters(item)) { + // We've acquired all type parameters. + break; + } + + if (item instanceof TypeFormula) { + // Type parameter is a previously-acquired type. + typeParameters.push(item); + } else if (typeof item === "string") { + // Type parameter is a simple, non-generic type. + typeParameters.push(new TypeFormula(item, [])); + } else { + throw new Error(`Unexpected type parameter object in stack: ${item}`); + } + } + + return typeParameters; + } + + private isPunctuation(token: string): boolean { + return TypeFormulaParser.PUNCTUATION.includes(token); + } + + private isEndOfTypeParameters(token: string): boolean { + return token === TypeFormulaParser.END_TYPE_PARAMETERS; + } + + private isBeginningOfTypeParameters(token: string): boolean { + return token === TypeFormulaParser.BEGIN_TYPE_PARAMETERS; + } +} diff --git a/src/account.ts b/src/account.ts index 8d10b89f7..d371afdc6 100644 --- a/src/account.ts +++ b/src/account.ts @@ -8,7 +8,7 @@ export class Account { /** * The address of the account. */ - readonly address: IAddress = new Address(); + readonly address: IAddress = Address.empty(); /** * The nonce of the account (the account sequence number). @@ -30,7 +30,7 @@ export class Account { /** * Updates account properties (such as nonce, balance). */ - update(obj: { nonce: INonce, balance: IAccountBalance}) { + update(obj: { nonce: INonce; balance: IAccountBalance }) { this.nonce = obj.nonce; this.balance = obj.balance; } diff --git a/src/adapters/index.ts b/src/adapters/index.ts new file mode 100644 index 000000000..e73a1b2d8 --- /dev/null +++ b/src/adapters/index.ts @@ -0,0 +1 @@ +export * from "./queryRunnerAdapter"; diff --git a/src/adapters/queryRunnerAdapter.ts b/src/adapters/queryRunnerAdapter.ts new file mode 100644 index 000000000..db673de4c --- /dev/null +++ b/src/adapters/queryRunnerAdapter.ts @@ -0,0 +1,42 @@ +import { Address } from "../address"; +import { IAddress } from "../interface"; +import { IContractQueryResponse } from "../interfaceOfNetwork"; +import { SmartContractQuery, SmartContractQueryResponse } from "../smartContractQuery"; + +interface INetworkProvider { + queryContract(query: IQuery): Promise; +} + +interface IQuery { + address: IAddress; + caller?: IAddress; + func: { toString(): string }; + value?: { toString(): string }; + getEncodedArguments(): string[]; +} + +export class QueryRunnerAdapter { + private readonly networkProvider: INetworkProvider; + + constructor(options: { networkProvider: INetworkProvider }) { + this.networkProvider = options.networkProvider; + } + + async runQuery(query: SmartContractQuery): Promise { + const adaptedQuery: IQuery = { + address: Address.fromBech32(query.contract), + caller: query.caller ? Address.fromBech32(query.caller) : undefined, + func: query.function, + value: query.value, + getEncodedArguments: () => query.arguments.map((arg) => Buffer.from(arg).toString("hex")), + }; + + const adaptedQueryResponse = await this.networkProvider.queryContract(adaptedQuery); + return new SmartContractQueryResponse({ + function: query.function, + returnCode: adaptedQueryResponse.returnCode.toString(), + returnMessage: adaptedQueryResponse.returnMessage, + returnDataParts: adaptedQueryResponse.getReturnDataParts(), + }); + } +} diff --git a/src/address.spec.ts b/src/address.spec.ts index ca731f8e6..7acbd9994 100644 --- a/src/address.spec.ts +++ b/src/address.spec.ts @@ -1,8 +1,7 @@ import { assert } from "chai"; -import { Address } from "./address"; +import { Address, AddressComputer } from "./address"; import * as errors from "./errors"; - describe("test address", () => { let aliceBech32 = "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"; let bobBech32 = "erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx"; @@ -10,15 +9,18 @@ describe("test address", () => { let bobHex = "8049d639e5a6980d1cd2392abcce41029cda74a1563523a202f09641cc2618f8"; it("should create address", async () => { - let alice = new Address(aliceBech32); - let bob = new Address(bobBech32); + assert.equal(new Address(aliceBech32).toHex(), aliceHex); + assert.equal(new Address(bobBech32).toHex(), bobHex); - assert.equal(alice.hex(), aliceHex); - assert.equal(bob.hex(), bobHex); + assert.equal(new Address(Buffer.from(aliceHex, "hex")).toHex(), aliceHex); + assert.equal(new Address(Buffer.from(bobHex, "hex")).toHex(), bobHex); + + assert.equal(new Address(new Uint8Array(Buffer.from(aliceHex, "hex"))).toHex(), aliceHex); + assert.equal(new Address(new Uint8Array(Buffer.from(bobHex, "hex"))).toHex(), bobHex); }); it("should create empty address", async () => { - let nobody = new Address(); + const nobody = Address.empty(); assert.isEmpty(nobody.hex()); assert.isEmpty(nobody.bech32()); @@ -41,13 +43,60 @@ describe("test address", () => { assert.throw(() => new Address("foo"), errors.ErrAddressCannotCreate); assert.throw(() => new Address("a".repeat(7)), errors.ErrAddressCannotCreate); assert.throw(() => new Address(Buffer.from("aaaa", "hex")), errors.ErrAddressCannotCreate); - assert.throw(() => new Address("erd1l453hd0gt5gzdp7czpuall8ggt2dcv5zwmfdf3sd3lguxseux2"), errors.ErrAddressCannotCreate); - assert.throw(() => new Address("xerd1l453hd0gt5gzdp7czpuall8ggt2dcv5zwmfdf3sd3lguxseux2fsmsgldz"), errors.ErrAddressCannotCreate); + assert.throw( + () => new Address("erd1l453hd0gt5gzdp7czpuall8ggt2dcv5zwmfdf3sd3lguxseux2"), + errors.ErrAddressCannotCreate, + ); + assert.throw( + () => new Address("xerd1l453hd0gt5gzdp7czpuall8ggt2dcv5zwmfdf3sd3lguxseux2fsmsgldz"), + errors.ErrAddressCannotCreate, + ); }); it("should validate the address without throwing the error", () => { assert.isTrue(Address.isValid(aliceBech32)); - assert.isFalse(Address.isValid('xerd1l453hd0gt5gzdp7czpuall8ggt2dcv5zwmfdf3sd3lguxseux2fsmsgldz')); - assert.isFalse(Address.isValid('erd1l453hd0gt5gzdp7czpuall8ggt2dcv5zwmfdf3sd3lguxseux2')) - }) + assert.isFalse(Address.isValid("xerd1l453hd0gt5gzdp7czpuall8ggt2dcv5zwmfdf3sd3lguxseux2fsmsgldz")); + assert.isFalse(Address.isValid("erd1l453hd0gt5gzdp7czpuall8ggt2dcv5zwmfdf3sd3lguxseux2")); + }); + + it("should check whether isSmartContract", () => { + assert.isFalse( + Address.fromBech32("erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th").isSmartContract(), + ); + assert.isTrue( + Address.fromBech32("erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqplllst77y4l").isSmartContract(), + ); + assert.isTrue( + Address.fromBech32("erd1qqqqqqqqqqqqqpgqxwakt2g7u9atsnr03gqcgmhcv38pt7mkd94q6shuwt").isSmartContract(), + ); + }); + + it("should contract address", () => { + const addressComputer = new AddressComputer(); + const deployer = Address.fromBech32("erd1j0hxzs7dcyxw08c4k2nv9tfcaxmqy8rj59meq505w92064x0h40qcxh3ap"); + + let contractAddress = addressComputer.computeContractAddress(deployer, 0n); + assert.equal(contractAddress.toHex(), "00000000000000000500bb652200ed1f994200ab6699462cab4b1af7b11ebd5e"); + assert.equal(contractAddress.toBech32(), "erd1qqqqqqqqqqqqqpgqhdjjyq8dr7v5yq9tv6v5vt9tfvd00vg7h40q6779zn"); + + contractAddress = addressComputer.computeContractAddress(deployer, 1n); + assert.equal(contractAddress.toHex(), "000000000000000005006e4f90488e27342f9a46e1809452c85ee7186566bd5e"); + assert.equal(contractAddress.toBech32(), "erd1qqqqqqqqqqqqqpgqde8eqjywyu6zlxjxuxqfg5kgtmn3setxh40qen8egy"); + }); + + it("should get address shard", () => { + const addressComputer = new AddressComputer(); + + let address = Address.fromBech32("erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"); + let shard = addressComputer.getShardOfAddress(address); + assert.equal(shard, 1); + + address = Address.fromBech32("erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx"); + shard = addressComputer.getShardOfAddress(address); + assert.equal(shard, 0); + + address = Address.fromBech32("erd1k2s324ww2g0yj38qn2ch2jwctdy8mnfxep94q9arncc6xecg3xaq6mjse8"); + shard = addressComputer.getShardOfAddress(address); + assert.equal(shard, 2); + }); }); diff --git a/src/address.ts b/src/address.ts index 48bfdbaa8..e694c001b 100644 --- a/src/address.ts +++ b/src/address.ts @@ -1,5 +1,9 @@ import * as bech32 from "bech32"; +import BigNumber from "bignumber.js"; +import { CURRENT_NUMBER_OF_SHARDS_WITHOUT_META, METACHAIN_ID, WasmVirtualMachine } from "./constants"; import * as errors from "./errors"; +import { bigIntToBuffer } from "./tokenOperations/codec"; +const createKeccakHash = require("keccak"); /** * The human-readable-part of the bech32 addresses. @@ -13,6 +17,11 @@ const PUBKEY_LENGTH = 32; const SMART_CONTRACT_HEX_PUBKEY_PREFIX = "0".repeat(16); +interface IAddress { + getPublicKey(): Buffer; + getHrp(): string; +} + /** * An Address, as an immutable object. */ @@ -23,15 +32,15 @@ export class Address { /** * Creates an address object, given a raw string (whether a hex pubkey or a Bech32 address), a sequence of bytes, or another Address object. */ - public constructor(value?: Address | Buffer | string) { + public constructor(value: Address | Buffer | Uint8Array | string) { if (!value) { return; } if (value instanceof Address) { return Address.fromAddress(value); } - if (value instanceof Buffer) { - return Address.fromBuffer(value); + if (ArrayBuffer.isView(value)) { + return Address.fromBuffer(Buffer.from(value)); } if (typeof value === "string") { return Address.fromString(value); @@ -48,7 +57,7 @@ export class Address { } private static fromValidHex(value: string): Address { - let result = new Address(); + let result = Address.empty(); result.valueHex = value; return result; } @@ -91,10 +100,11 @@ export class Address { } /** - * Creates an empty address object + * Creates an empty address object. + * Generally speaking, this should not be used by client code (internal use only). */ static empty(): Address { - return new Address(); + return new Address(""); } /** @@ -109,12 +119,12 @@ export class Address { throw new errors.ErrAddressCannotCreate(value, err); } - let prefix = decoded.prefix; + const prefix = decoded.prefix; if (prefix != HRP) { throw new errors.ErrAddressBadHrp(HRP, prefix); } - let pubkey = Buffer.from(bech32.fromWords(decoded.words)); + const pubkey = Buffer.from(bech32.fromWords(decoded.words)); if (pubkey.length != PUBKEY_LENGTH) { throw new errors.ErrAddressCannotCreate(value); } @@ -128,21 +138,26 @@ export class Address { static isValid(value: string): boolean { const decoded = bech32.decodeUnsafe(value); const prefix = decoded?.prefix; - const pubkey = decoded - ? Buffer.from(bech32.fromWords(decoded.words)) - : undefined; + const pubkey = decoded ? Buffer.from(bech32.fromWords(decoded.words)) : undefined; if (prefix !== HRP || pubkey?.length !== PUBKEY_LENGTH) { return false; } - + return true; } /** - * Returns the hex representation of the address (pubkey) + * Use {@link toHex} instead. */ hex(): string { + return this.toHex(); + } + + /** + * Returns the hex representation of the address (pubkey) + */ + toHex(): string { if (this.isEmpty()) { return ""; } @@ -151,9 +166,16 @@ export class Address { } /** - * Returns the bech32 representation of the address + * Use {@link toBech32} instead. */ bech32(): string { + return this.toBech32(); + } + + /** + * Returns the bech32 representation of the address + */ + toBech32(): string { if (this.isEmpty()) { return ""; } @@ -164,9 +186,16 @@ export class Address { } /** - * Returns the pubkey as raw bytes (buffer) + * Use {@link getPublicKey} instead. */ pubkey(): Buffer { + return this.getPublicKey(); + } + + /** + * Returns the pubkey as raw bytes (buffer) + */ + getPublicKey(): Buffer { if (this.isEmpty()) { return Buffer.from([]); } @@ -174,6 +203,14 @@ export class Address { return Buffer.from(this.valueHex, "hex"); } + /** + * Returns the human-readable-part of the bech32 addresses. + * The HRP is currently hardcoded to "erd". + */ + getHrp(): string { + return HRP; + } + /** * Returns whether the address is empty. */ @@ -196,7 +233,7 @@ export class Address { * Returns the bech32 representation of the address */ toString(): string { - return this.bech32(); + return this.toBech32(); } /** @@ -204,19 +241,96 @@ export class Address { */ toJSON(): object { return { - bech32: this.bech32(), - pubkey: this.hex() + bech32: this.toBech32(), + pubkey: this.toHex(), }; } /** - * Creates the Zero address (the one that should be used when deploying smart contracts) + * Creates the Zero address (the one that should be used when deploying smart contracts). + * Generally speaking, this should not be used by client code (internal use only). */ static Zero(): Address { return new Address("0".repeat(64)); } + /** + * Use {@link isSmartContract} instead. + */ isContractAddress(): boolean { - return this.hex().startsWith(SMART_CONTRACT_HEX_PUBKEY_PREFIX); + return this.isSmartContract(); + } + + /** + * Returns whether the address is a smart contract address. + */ + isSmartContract(): boolean { + return this.toHex().startsWith(SMART_CONTRACT_HEX_PUBKEY_PREFIX); + } +} + +export class AddressComputer { + private readonly numberOfShardsWithoutMeta: number; + + constructor(numberOfShardsWithoutMeta?: number) { + this.numberOfShardsWithoutMeta = numberOfShardsWithoutMeta || CURRENT_NUMBER_OF_SHARDS_WITHOUT_META; + } + + computeContractAddress(deployer: IAddress, deploymentNonce: bigint): Address { + const initialPadding = Buffer.alloc(8, 0); + const ownerPubkey = deployer.getPublicKey(); + const shardSelector = ownerPubkey.slice(30); + const ownerNonceBytes = Buffer.alloc(8); + + const bigNonce = new BigNumber(deploymentNonce.toString()); + const bigNonceBuffer = bigIntToBuffer(bigNonce); + ownerNonceBytes.write(bigNonceBuffer.reverse().toString("hex"), "hex"); + + const bytesToHash = Buffer.concat([ownerPubkey, ownerNonceBytes]); + const hash = createKeccakHash("keccak256").update(bytesToHash).digest(); + const vmTypeBytes = Buffer.from(WasmVirtualMachine, "hex"); + const addressBytes = Buffer.concat([initialPadding, vmTypeBytes, hash.slice(10, 30), shardSelector]); + + return new Address(addressBytes); + } + + getShardOfAddress(address: IAddress): number { + return this.getShardOfPubkey(address.getPublicKey(), this.numberOfShardsWithoutMeta); + } + + private getShardOfPubkey(pubkey: Uint8Array, numberOfShards: number): number { + const maskHigh: number = parseInt("11", 2); + const maskLow: number = parseInt("01", 2); + + const lastByteOfPubkey: number = pubkey[31]; + + if (this.isPubkeyOfMetachain(pubkey)) { + return METACHAIN_ID; + } + + let shard: number = lastByteOfPubkey & maskHigh; + if (shard > numberOfShards - 1) { + shard = lastByteOfPubkey & maskLow; + } + + return shard; + } + + private isPubkeyOfMetachain(pubkey: Uint8Array): boolean { + const metachainPrefix = Buffer.from([ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + ]); + const pubkeyPrefix = Buffer.from(pubkey).slice(0, metachainPrefix.length); + + if (metachainPrefix.equals(pubkeyPrefix)) { + return true; + } + + const zeroAddress = Buffer.alloc(32); + if (zeroAddress.equals(Buffer.from(pubkey))) { + return true; + } + + return false; } } diff --git a/src/asyncTimer.spec.ts b/src/asyncTimer.spec.ts index 8166ce800..903a4cc9b 100644 --- a/src/asyncTimer.spec.ts +++ b/src/asyncTimer.spec.ts @@ -2,7 +2,6 @@ import { assert } from "chai"; import * as errors from "./errors"; import { AsyncTimer } from "./asyncTimer"; - describe("test asyncTimer", () => { it("should start timer and resolve promise", async () => { let timer = new AsyncTimer("test"); @@ -21,7 +20,7 @@ describe("test asyncTimer", () => { let longPromise = longTimer.start(42000); let shortTimerThenAbortLongTimer = shortPromise.then(() => longTimer.abort()); - let longTimerThenCatchAbort = longPromise.catch(reason => error = reason); + let longTimerThenCatchAbort = longPromise.catch((reason) => (error = reason)); await Promise.all([shortTimerThenAbortLongTimer, longTimerThenCatchAbort]); diff --git a/src/compatibility.ts b/src/compatibility.ts index 274423603..9caf1977a 100644 --- a/src/compatibility.ts +++ b/src/compatibility.ts @@ -5,20 +5,18 @@ import { IAddress } from "./interface"; * For internal use only. */ export class Compatibility { - static areWarningsEnabled: boolean = true; - /** * For internal use only. */ static guardAddressIsSetAndNonZero(address: IAddress | undefined, context: string, resolution: string) { - if (!this.areWarningsEnabled) { - return; - } - if (!address || address.bech32() == "") { - console.warn(`${context}: address should be set; ${resolution}. In the future, this will throw an exception instead of emitting a WARN.`); + console.warn( + `${context}: address should be set; ${resolution}. In the future, this will throw an exception instead of emitting a WARN.`, + ); } else if (address.bech32() == Address.Zero().bech32()) { - console.warn(`${context}: address should not be the 'zero' address (also known as the 'contracts deployment address'); ${resolution}. In the future, this will throw an exception instead of emitting a WARN.`); + console.warn( + `${context}: address should not be the 'zero' address (also known as the 'contracts deployment address'); ${resolution}. In the future, this will throw an exception instead of emitting a WARN.`, + ); } } } diff --git a/src/constants.ts b/src/constants.ts index 66d72198a..ac9a02cae 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -2,11 +2,23 @@ export const TRANSACTION_MIN_GAS_PRICE = 1000000000; export const TRANSACTION_OPTIONS_DEFAULT = 0; export const TRANSACTION_OPTIONS_TX_HASH_SIGN = 0b0001; export const TRANSACTION_OPTIONS_TX_GUARDED = 0b0010; -export const TRANSACTION_VERSION_DEFAULT = 1; -export const TRANSACTION_VERSION_WITH_OPTIONS = 2; +export const TRANSACTION_VERSION_DEFAULT = 2; +export const MIN_TRANSACTION_VERSION_THAT_SUPPORTS_OPTIONS = 2; export const ESDT_TRANSFER_GAS_LIMIT = 500000; export const ESDT_TRANSFER_FUNCTION_NAME = "ESDTTransfer"; export const ESDTNFT_TRANSFER_FUNCTION_NAME = "ESDTNFTTransfer"; export const MULTI_ESDTNFT_TRANSFER_FUNCTION_NAME = "MultiESDTNFTTransfer"; export const ESDT_TRANSFER_VALUE = "0"; export const ARGUMENTS_SEPARATOR = "@"; +export const VM_TYPE_WASM_VM = new Uint8Array([0x05, 0x00]); +export const CONTRACT_DEPLOY_ADDRESS = "erd1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq6gq4hu"; +export const DELEGATION_MANAGER_SC_ADDRESS = "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqylllslmq6y6"; +export const DEFAULT_HRP = "erd"; +export const ESDT_CONTRACT_ADDRESS = "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqzllls8a5w6u"; +export const DEFAULT_MESSAGE_VERSION = 1; +export const MESSAGE_PREFIX = "\x17Elrond Signed Message:\n"; +export const HEX_TRANSACTION_HASH_LENGTH = 64; +export const BECH32_ADDRESS_LENGTH = 62; +export const CURRENT_NUMBER_OF_SHARDS_WITHOUT_META = 3; +export const WasmVirtualMachine = "0500"; +export const METACHAIN_ID = 4294967295; diff --git a/src/converters/index.ts b/src/converters/index.ts new file mode 100644 index 000000000..f43e67fcc --- /dev/null +++ b/src/converters/index.ts @@ -0,0 +1 @@ +export * from "./transactionsConverter"; diff --git a/src/converters/transactionsConverter.ts b/src/converters/transactionsConverter.ts new file mode 100644 index 000000000..2f0df2813 --- /dev/null +++ b/src/converters/transactionsConverter.ts @@ -0,0 +1,136 @@ +import { IPlainTransactionObject, ITransaction } from "../interface"; +import { IContractResultItem, ITransactionEvent, ITransactionOnNetwork } from "../interfaceOfNetwork"; +import { ResultsParser } from "../smartcontracts"; +import { Transaction } from "../transaction"; +import { + SmartContractCallOutcome, + SmartContractResult, + TransactionEvent, + TransactionLogs, + TransactionOutcome, +} from "../transactionsOutcomeParsers/resources"; + +export class TransactionsConverter { + public transactionToPlainObject(transaction: ITransaction): IPlainTransactionObject { + const plainObject = { + nonce: Number(transaction.nonce), + value: transaction.value.toString(), + receiver: transaction.receiver, + sender: transaction.sender, + senderUsername: this.toBase64OrUndefined(transaction.senderUsername), + receiverUsername: this.toBase64OrUndefined(transaction.receiverUsername), + gasPrice: Number(transaction.gasPrice), + gasLimit: Number(transaction.gasLimit), + data: this.toBase64OrUndefined(transaction.data), + chainID: transaction.chainID.valueOf(), + version: transaction.version, + options: transaction.options == 0 ? undefined : transaction.options, + guardian: transaction.guardian ? transaction.guardian : undefined, + signature: this.toHexOrUndefined(transaction.signature), + guardianSignature: this.toHexOrUndefined(transaction.guardianSignature), + }; + + return plainObject; + } + + private toBase64OrUndefined(value?: string | Uint8Array) { + return value && value.length ? Buffer.from(value).toString("base64") : undefined; + } + + private toHexOrUndefined(value?: Uint8Array) { + return value && value.length ? Buffer.from(value).toString("hex") : undefined; + } + + public plainObjectToTransaction(object: IPlainTransactionObject): Transaction { + const transaction = new Transaction({ + nonce: BigInt(object.nonce), + value: BigInt(object.value || ""), + receiver: object.receiver, + receiverUsername: this.bufferFromBase64(object.receiverUsername).toString(), + sender: object.sender, + senderUsername: this.bufferFromBase64(object.senderUsername).toString(), + guardian: object.guardian, + gasPrice: BigInt(object.gasPrice), + gasLimit: BigInt(object.gasLimit), + data: this.bufferFromBase64(object.data), + chainID: String(object.chainID), + version: Number(object.version), + options: Number(object.options), + signature: this.bufferFromHex(object.signature), + guardianSignature: this.bufferFromHex(object.guardianSignature), + }); + + return transaction; + } + + private bufferFromBase64(value?: string) { + return Buffer.from(value || "", "base64"); + } + + private bufferFromHex(value?: string) { + return Buffer.from(value || "", "hex"); + } + + public transactionOnNetworkToOutcome(transactionOnNetwork: ITransactionOnNetwork): TransactionOutcome { + // In the future, this will not be needed because the transaction, as returned from the API, + // will hold the data corresponding to the direct smart contract call outcome (in case of smart contract calls). + const legacyResultsParser = new ResultsParser(); + const callOutcomeBundle = legacyResultsParser.parseUntypedOutcome(transactionOnNetwork); + const callOutcome = new SmartContractCallOutcome({ + function: transactionOnNetwork.function, + returnCode: callOutcomeBundle.returnCode.toString(), + returnMessage: callOutcomeBundle.returnMessage, + returnDataParts: callOutcomeBundle.values, + }); + + const contractResults = transactionOnNetwork.contractResults.items.map((result) => + this.smartContractResultOnNetworkToSmartContractResult(result), + ); + + const logs = new TransactionLogs({ + address: transactionOnNetwork.logs.address.bech32(), + events: transactionOnNetwork.logs.events.map((event) => this.eventOnNetworkToEvent(event)), + }); + + return new TransactionOutcome({ + logs: logs, + smartContractResults: contractResults, + directSmartContractCallOutcome: callOutcome, + }); + } + + private smartContractResultOnNetworkToSmartContractResult( + resultOnNetwork: IContractResultItem, + ): SmartContractResult { + return new SmartContractResult({ + sender: resultOnNetwork.sender.bech32(), + receiver: resultOnNetwork.receiver.bech32(), + data: Buffer.from(resultOnNetwork.data), + logs: new TransactionLogs({ + address: resultOnNetwork.logs.address.bech32(), + events: resultOnNetwork.logs.events.map((event) => this.eventOnNetworkToEvent(event)), + }), + }); + } + + private eventOnNetworkToEvent(eventOnNetwork: ITransactionEvent): TransactionEvent { + // Before Sirius, there was no "additionalData" field on transaction logs. + // After Sirius, the "additionalData" field includes the payload of the legacy "data" field, as well (as its first element): + // https://github.com/multiversx/mx-chain-go/blob/v1.6.18/process/transactionLog/process.go#L159 + const legacyData = eventOnNetwork.dataPayload?.valueOf() || Buffer.from(eventOnNetwork.data || ""); + const dataItems = eventOnNetwork.additionalData?.map((data) => Buffer.from(data.valueOf())) || []; + + if (dataItems.length === 0) { + if (legacyData.length) { + dataItems.push(Buffer.from(legacyData)); + } + } + + return new TransactionEvent({ + address: eventOnNetwork.address.bech32(), + identifier: eventOnNetwork.identifier, + topics: eventOnNetwork.topics.map((topic) => Buffer.from(topic.hex(), "hex")), + dataItems: dataItems, + }); + } +} diff --git a/src/converters/transactionsConverters.spec.ts b/src/converters/transactionsConverters.spec.ts new file mode 100644 index 000000000..dd44b173f --- /dev/null +++ b/src/converters/transactionsConverters.spec.ts @@ -0,0 +1,220 @@ +import { + ContractResultItem, + ContractResults, + TransactionEventData, + TransactionEvent as TransactionEventOnNetwork, + TransactionEventTopic, + TransactionLogs as TransactionLogsOnNetwork, + TransactionOnNetwork, +} from "@multiversx/sdk-network-providers"; +import { assert } from "chai"; +import { Address } from "../address"; +import { Transaction } from "../transaction"; +import { + SmartContractCallOutcome, + SmartContractResult, + TransactionEvent, + TransactionLogs, + TransactionOutcome, +} from "../transactionsOutcomeParsers/resources"; +import { TransactionsConverter } from "./transactionsConverter"; + +describe("test transactions converter", async () => { + it("converts transaction to plain object and back", () => { + const converter = new TransactionsConverter(); + + const transaction = new Transaction({ + nonce: 90, + value: BigInt("123456789000000000000000000000"), + sender: "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th", + receiver: "erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx", + senderUsername: "alice", + receiverUsername: "bob", + gasPrice: 1000000000, + gasLimit: 80000, + data: Buffer.from("hello"), + chainID: "localnet", + version: 2, + }); + + const plainObject = converter.transactionToPlainObject(transaction); + const restoredTransaction = converter.plainObjectToTransaction(plainObject); + + assert.deepEqual(plainObject, transaction.toPlainObject()); + assert.deepEqual(restoredTransaction, Transaction.fromPlainObject(plainObject)); + assert.deepEqual(restoredTransaction, transaction); + assert.deepEqual(plainObject, { + nonce: 90, + value: "123456789000000000000000000000", + sender: "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th", + receiver: "erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx", + senderUsername: "YWxpY2U=", + receiverUsername: "Ym9i", + gasPrice: 1000000000, + gasLimit: 80000, + data: "aGVsbG8=", + chainID: "localnet", + version: 2, + options: undefined, + guardian: undefined, + signature: undefined, + guardianSignature: undefined, + }); + }); + + it("converts transaction on network to transaction outcome", () => { + const converter = new TransactionsConverter(); + + const transactionOnNetwork = new TransactionOnNetwork({ + nonce: 7, + function: "hello", + logs: new TransactionLogsOnNetwork({ + address: Address.fromBech32("erd1k2s324ww2g0yj38qn2ch2jwctdy8mnfxep94q9arncc6xecg3xaq6mjse8"), + events: [ + new TransactionEventOnNetwork({ + identifier: "foobar", + topics: [], + dataPayload: new TransactionEventData(Buffer.from("foo")), + additionalData: [], + }), + ], + }), + contractResults: new ContractResults([ + new ContractResultItem({ + nonce: 8, + data: "@6f6b@2a", + logs: new TransactionLogsOnNetwork({ + address: Address.fromBech32("erd1k2s324ww2g0yj38qn2ch2jwctdy8mnfxep94q9arncc6xecg3xaq6mjse8"), + events: [ + new TransactionEventOnNetwork({ + identifier: "writeLog", + topics: [ + new TransactionEventTopic( + // '@too much gas provided for processing: gas provided = 596384500, gas used = 733010' + "QHRvbyBtdWNoIGdhcyBwcm92aWRlZCBmb3IgcHJvY2Vzc2luZzogZ2FzIHByb3ZpZGVkID0gNTk2Mzg0NTAwLCBnYXMgdXNlZCA9IDczMzAxMA==", + ), + ], + dataPayload: TransactionEventData.fromBase64("QDZmNmI="), + }), + ], + }), + }), + ]), + }); + + const actualTransactionOutcome = converter.transactionOnNetworkToOutcome(transactionOnNetwork); + const expectedTransactionOutcome = new TransactionOutcome({ + directSmartContractCallOutcome: new SmartContractCallOutcome({ + function: "hello", + returnCode: "ok", + returnMessage: "ok", + returnDataParts: [Buffer.from([42])], + }), + smartContractResults: [ + new SmartContractResult({ + sender: "", + receiver: "", + data: Buffer.from("@6f6b@2a"), + logs: { + address: "erd1k2s324ww2g0yj38qn2ch2jwctdy8mnfxep94q9arncc6xecg3xaq6mjse8", + events: [ + new TransactionEvent({ + address: "", + identifier: "writeLog", + topics: [ + Buffer.from( + "@too much gas provided for processing: gas provided = 596384500, gas used = 733010", + ), + ], + dataItems: [Buffer.from("QDZmNmI=", "base64")], + }), + ], + }, + }), + ], + logs: new TransactionLogs({ + address: "erd1k2s324ww2g0yj38qn2ch2jwctdy8mnfxep94q9arncc6xecg3xaq6mjse8", + events: [ + new TransactionEvent({ + address: "", + identifier: "foobar", + topics: [], + dataItems: [Buffer.from("foo")], + }), + ], + }), + }); + + assert.deepEqual(actualTransactionOutcome, expectedTransactionOutcome); + }); + + it("converts transaction on network to transaction outcome (with signal error)", () => { + const converter = new TransactionsConverter(); + + const transactionOnNetwork = new TransactionOnNetwork({ + nonce: 42, + function: "hello", + contractResults: new ContractResults([ + new ContractResultItem({ + nonce: 42, + data: "@657865637574696f6e206661696c6564", + logs: new TransactionLogsOnNetwork({ + address: Address.fromBech32("erd1qqqqqqqqqqqqqpgqj8k976l59n7fyth8ujl4as5uyn3twn0ha0wsge5r5x"), + events: [ + new TransactionEventOnNetwork({ + address: Address.fromBech32( + "erd1qqqqqqqqqqqqqpgqj8k976l59n7fyth8ujl4as5uyn3twn0ha0wsge5r5x", + ), + identifier: "signalError", + topics: [ + new TransactionEventTopic("XmC5/yOF6ie6DD2kaJd5qPc2Ss7h2w7nvuWaxmCiiXQ="), + new TransactionEventTopic("aW5zdWZmaWNpZW50IGZ1bmRz"), + ], + dataPayload: new TransactionEventData(Buffer.from("@657865637574696f6e206661696c6564")), + additionalData: [ + new TransactionEventData(Buffer.from("@657865637574696f6e206661696c6564")), + new TransactionEventData(Buffer.from("foobar")), + ], + }), + ], + }), + }), + ]), + }); + + const actualTransactionOutcome = converter.transactionOnNetworkToOutcome(transactionOnNetwork); + const expectedTransactionOutcome = new TransactionOutcome({ + directSmartContractCallOutcome: new SmartContractCallOutcome({ + function: "hello", + returnCode: "execution failed", + returnMessage: "execution failed", + returnDataParts: [], + }), + smartContractResults: [ + new SmartContractResult({ + sender: "", + receiver: "", + data: Buffer.from("@657865637574696f6e206661696c6564"), + logs: { + address: "erd1qqqqqqqqqqqqqpgqj8k976l59n7fyth8ujl4as5uyn3twn0ha0wsge5r5x", + events: [ + new TransactionEvent({ + address: "erd1qqqqqqqqqqqqqpgqj8k976l59n7fyth8ujl4as5uyn3twn0ha0wsge5r5x", + identifier: "signalError", + topics: [ + Address.fromBech32( + "erd1testnlersh4z0wsv8kjx39me4rmnvjkwu8dsaea7ukdvvc9z396qykv7z7", + ).getPublicKey(), + Buffer.from("insufficient funds"), + ], + dataItems: [Buffer.from("@657865637574696f6e206661696c6564"), Buffer.from("foobar")], + }), + ], + }, + }), + ], + }); + + assert.deepEqual(actualTransactionOutcome, expectedTransactionOutcome); + }); +}); diff --git a/src/errors.ts b/src/errors.ts index 80d052dbe..30310edcd 100644 --- a/src/errors.ts +++ b/src/errors.ts @@ -2,332 +2,373 @@ * The base class for exceptions (errors). */ export class Err extends Error { - inner: Error | undefined = undefined; + inner: Error | undefined = undefined; - public constructor(message: string, inner?: Error) { - super(message); - this.inner = inner; - } + public constructor(message: string, inner?: Error) { + super(message); + this.inner = inner; + } - /** - * Returns a pretty, friendly summary for the error or for the chain of errros (if appropriate). - */ - summary(): any[] { - let result = []; + /** + * Returns a pretty, friendly summary for the error or for the chain of errros (if appropriate). + */ + summary(): any[] { + let result = []; - result.push({ name: this.name, message: this.message }); + result.push({ name: this.name, message: this.message }); - let inner: any = this.inner; - while (inner) { - result.push({ name: inner.name, message: inner.message }); - inner = inner.inner; - } + let inner: any = this.inner; + while (inner) { + result.push({ name: inner.name, message: inner.message }); + inner = inner.inner; + } - return result; - } + return result; + } } /** * Signals invalid arguments for a function, for an operation. */ export class ErrInvalidArgument extends Err { - public constructor(message: string, inner?: Error) { - super(`Invalid argument: ${message}`, inner); - } + public constructor(message: string, inner?: Error) { + super(`Invalid argument: ${message}`, inner); + } } /** * Signals an unsupported operation. */ export class ErrUnsupportedOperation extends Err { - public constructor(operation: string, reason: string = "not specified") { - super(`Operation "${operation}" not supported. Reason: ${reason}`); - } + public constructor(operation: string, reason: string = "not specified") { + super(`Operation "${operation}" not supported. Reason: ${reason}`); + } } /** * Signals the provisioning of objects of unexpected (bad) types. */ export class ErrBadType extends Err { - public constructor(name: string, type: any, value?: any) { - super(`Bad type of "${name}": ${value}. Expected type: ${type}`); - } + public constructor(name: string, type: any, value?: any) { + super(`Bad type of "${name}": ${value}. Expected type: ${type}`); + } } /** * Signals that an invariant failed. */ export class ErrInvariantFailed extends Err { - public constructor(message: string) { - super(`Invariant failed: [${message}]`); - } + public constructor(message: string) { + super(`Invariant failed: [${message}]`); + } } /** * Signals an unexpected condition. */ export class ErrUnexpectedCondition extends Err { - public constructor(message: string) { - super(`Unexpected condition: [${message}]`); - } + public constructor(message: string) { + super(`Unexpected condition: [${message}]`); + } } /** * Signals issues with {@link Address} instantiation. */ export class ErrAddressCannotCreate extends Err { - public constructor(input: any, inner?: Error) { - let message = `Cannot create address from: ${input}`; - super(message, inner); - } + public constructor(input: any, inner?: Error) { + let message = `Cannot create address from: ${input}`; + super(message, inner); + } } /** * Signals issues with the HRP of an {@link Address}. */ export class ErrAddressBadHrp extends Err { - public constructor(expected: string, got: string) { - super(`Wrong address HRP. Expected: ${expected}, got ${got}`); - } + public constructor(expected: string, got: string) { + super(`Wrong address HRP. Expected: ${expected}, got ${got}`); + } } /** * Signals the presence of an empty / invalid address. */ export class ErrAddressEmpty extends Err { - public constructor() { - super(`Address is empty`); - } + public constructor() { + super(`Address is empty`); + } } /** * Signals an invalid value for {@link GasLimit} objects. */ export class ErrNotEnoughGas extends Err { - public constructor(value: number) { - super(`Not enough gas provided: ${value}`); - } + public constructor(value: number) { + super(`Not enough gas provided: ${value}`); + } } /** * Signals an invalid value for {@link Nonce} objects. */ export class ErrNonceInvalid extends Err { - public constructor(value: number) { - super(`Invalid nonce: ${value}`); - } + public constructor(value: number) { + super(`Invalid nonce: ${value}`); + } } /** * Signals an invalid value for {@link TransactionVersion} objects. */ export class ErrTransactionVersionInvalid extends Err { - public constructor(value: number) { - super(`Invalid transaction version: ${value}`); - } + public constructor(value: number) { + super(`Invalid transaction version: ${value}`); + } } /** * Signals an invalid value for {@link TransactionOptions} objects. */ export class ErrTransactionOptionsInvalid extends Err { - public constructor(value: number) { - super(`Invalid transaction options: ${value}`); - } + public constructor(value: number) { + super(`Invalid transaction options: ${value}`); + } } /** * Signals an error related to signing a message (a transaction). */ export class ErrSignatureCannotCreate extends Err { - public constructor(input: any, inner?: Error) { - let message = `Cannot create signature from: ${input}`; - super(message, inner); - } + public constructor(input: any, inner?: Error) { + let message = `Cannot create signature from: ${input}`; + super(message, inner); + } } /** * Signals an invalid value for the name of a {@link ContractFunction}. */ export class ErrInvalidFunctionName extends Err { - public constructor() { - super(`Invalid function name`); - } + public constructor() { + super(`Invalid function name`); + } } /** * Signals a failed operation, since the Timer is already running. */ export class ErrAsyncTimerAlreadyRunning extends Err { - public constructor() { - super("Async timer already running"); - } + public constructor() { + super("Async timer already running"); + } } /** * Signals a failed operation, since the Timer has been aborted. */ export class ErrAsyncTimerAborted extends Err { - public constructor() { - super("Async timer aborted"); - } + public constructor() { + super("Async timer aborted"); + } } /** * Signals a timout for a {@link TransactionWatcher}. */ export class ErrTransactionWatcherTimeout extends Err { - public constructor() { - super(`TransactionWatcher has timed out`); - } + public constructor() { + super(`TransactionWatcher has timed out`); + } } /** * Signals an issue related to waiting for a specific transaction status. */ export class ErrExpectedTransactionStatusNotReached extends Err { - public constructor() { - super(`Expected transaction status not reached`); - } + public constructor() { + super(`Expected transaction status not reached`); + } } /** * Signals an issue related to waiting for specific transaction events. */ export class ErrExpectedTransactionEventsNotFound extends Err { - public constructor() { - super(`Expected transaction events not found`); - } + public constructor() { + super(`Expected transaction events not found`); + } } /** * Signals a generic error in the context of Smart Contracts. */ export class ErrContract extends Err { - public constructor(message: string) { - super(message); - } + public constructor(message: string) { + super(message); + } } export class ErrContractHasNoAddress extends ErrContract { - public constructor() { - super(` + public constructor() { + super(` The smart contract has no address set. Make sure you provide the address in the constructor, or call setAddress() appropriately. If you need to recompute the address of the contract, make use of SmartContract.computeAddress() (static method). `); - } + } } /** * Signals an error thrown by the mock-like test objects. */ export class ErrMock extends Err { - public constructor(message: string) { - super(message); - } + public constructor(message: string) { + super(message); + } } /** * Signals a generic type error. */ export class ErrTypingSystem extends Err { - public constructor(message: string) { - super(message); - } + public constructor(message: string) { + super(message); + } } /** * Signals a missing field on a struct. */ export class ErrMissingFieldOnStruct extends Err { - public constructor(fieldName: string, structName: string) { - super(`field ${fieldName} does not exist on struct ${structName}`); - } + public constructor(fieldName: string, structName: string) { + super(`field ${fieldName} does not exist on struct ${structName}`); + } } /** * Signals a missing field on an enum. */ export class ErrMissingFieldOnEnum extends Err { - public constructor(fieldName: string, enumName: string) { - super(`field ${fieldName} does not exist on enum ${enumName}`); - } + public constructor(fieldName: string, enumName: string) { + super(`field ${fieldName} does not exist on enum ${enumName}`); + } } /** * Signals an error when parsing the contract results. */ export class ErrCannotParseContractResults extends Err { - public constructor(details: string) { - super(`cannot parse contract results: ${details}`); - } + public constructor(details: string) { + super(`cannot parse contract results: ${details}`); + } } /** * Signals an error when parsing the outcome of a transaction (results and logs). */ export class ErrCannotParseTransactionOutcome extends Err { - public constructor(transactionHash: string, message: string) { - super(`cannot parse outcome of transaction ${transactionHash}: ${message}`); - } + public constructor(transactionHash: string, message: string) { + super(`cannot parse outcome of transaction ${transactionHash}: ${message}`); + } } /** * Signals a generic codec (encode / decode) error. */ export class ErrCodec extends Err { - public constructor(message: string) { - super(message); - } + public constructor(message: string) { + super(message); + } } /** * Signals a generic contract interaction error. */ export class ErrContractInteraction extends Err { - public constructor(message: string) { - super(message); - } + public constructor(message: string) { + super(message); + } } /** * Signals that a method is not yet implemented */ export class ErrNotImplemented extends Err { - public constructor() { - super("Method not yet implemented"); - } + public constructor() { + super("Method not yet implemented"); + } } /** * Signals invalid arguments when using the relayed v1 builder */ export class ErrInvalidRelayedV1BuilderArguments extends Err { - public constructor() { - super("invalid arguments for relayed v1 builder"); - } + public constructor() { + super("invalid arguments for relayed v1 builder"); + } } /** * Signals invalid arguments when using the relayed v2 builder */ export class ErrInvalidRelayedV2BuilderArguments extends Err { - public constructor() { - super("invalid arguments for relayed v2 builder"); - } + public constructor() { + super("invalid arguments for relayed v2 builder"); + } } /** * Signals that Gas Limit isn't 0 for an inner tx when using relayed v2 builder */ export class ErrGasLimitShouldBe0ForInnerTransaction extends Err { - public constructor() { - super("gas limit must be 0 for the inner transaction for relayed v2"); - } + public constructor() { + super("gas limit must be 0 for the inner transaction for relayed v2"); + } } +/** + * Signals that the `isCompleted` property is missing on the transaction obect and is needed for the Transaction Watcher + */ export class ErrIsCompletedFieldIsMissingOnTransaction extends Err { - public constructor() { - super("The transaction watcher requires the `isCompleted` property to be defined on the transaction object. Perhaps you've used the sdk-network-provider's `ProxyNetworkProvider.getTransaction()` and in that case you should also pass `withProcessStatus=true`.") - } + public constructor() { + super( + "The transaction watcher requires the `isCompleted` property to be defined on the transaction object. Perhaps you've used the sdk-network-provider's `ProxyNetworkProvider.getTransaction()` and in that case you should also pass `withProcessStatus=true`.", + ); + } +} + +/** + * Signals that the provided token identifier is not valid + */ +export class ErrInvalidTokenIdentifier extends Err { + public constructor(message: string) { + super(message); + } +} + +/** + * Signals a generic bad usage error + */ +export class ErrBadUsage extends Err { + public constructor(message: string) { + super(message); + } +} + +/** + * Signals an invalid inner transaction for relayed transactions + */ +export class ErrInvalidInnerTransaction extends Err { + public constructor(message: string) { + super(message); + } +} + +/** + * Signals an error when parsing the logs of a transaction. + */ +export class ErrParseTransactionOutcome extends Err { + public constructor(message: string) { + super(message); + } } diff --git a/src/gasEstimator.spec.ts b/src/gasEstimator.spec.ts index 740ed4ef2..e1394717f 100644 --- a/src/gasEstimator.spec.ts +++ b/src/gasEstimator.spec.ts @@ -24,7 +24,7 @@ describe("test gas estimator", () => { gasPerDataByte: 3000, gasCostESDTTransfer: 200000, gasCostESDTNFTTransfer: 300000, - gasCostESDTNFTMultiTransfer: 400000 + gasCostESDTNFTMultiTransfer: 400000, }); assert.equal(estimator.forEGLDTransfer(0), 10000); diff --git a/src/gasEstimator.ts b/src/gasEstimator.ts index 6207c1bfa..368be5e53 100644 --- a/src/gasEstimator.ts +++ b/src/gasEstimator.ts @@ -17,13 +17,13 @@ export const DefaultGasConfiguration: IGasConfiguration = { gasPerDataByte: 1500, gasCostESDTTransfer: 200000, gasCostESDTNFTTransfer: 200000, - gasCostESDTNFTMultiTransfer: 200000 + gasCostESDTNFTMultiTransfer: 200000, }; // Additional gas to account for eventual increases in gas requirements (thus avoid fast-breaking changes in clients of the library). const ADDITIONAL_GAS_FOR_ESDT_TRANSFER = 100000; -// Additional gas to account for extra blockchain operations (e.g. data movement (between accounts) for NFTs), +// Additional gas to account for extra blockchain operations (e.g. data movement (between accounts) for NFTs), // and for eventual increases in gas requirements (thus avoid fast-breaking changes in clients of the library). const ADDITIONAL_GAS_FOR_ESDT_NFT_TRANSFER = 800000; @@ -35,9 +35,7 @@ export class GasEstimator { } forEGLDTransfer(dataLength: number) { - const gasLimit = - this.gasConfiguration.minGasLimit + - this.gasConfiguration.gasPerDataByte * dataLength; + const gasLimit = this.gasConfiguration.minGasLimit + this.gasConfiguration.gasPerDataByte * dataLength; return gasLimit; } diff --git a/src/hash.ts b/src/hash.ts index 5f7095a76..40efda328 100644 --- a/src/hash.ts +++ b/src/hash.ts @@ -8,7 +8,7 @@ export class Hash { /** * Creates a new Hash object. - * + * * @param hash The hash, as a Buffer or a hex-encoded string. */ constructor(hash: Buffer | string) { diff --git a/src/index.ts b/src/index.ts index e89b0105e..bfc9a49c7 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,29 +1,34 @@ /** - * A library for interacting with the MultiversX blockchain (in general) and Smart Contracts (in particular). + * A library for interacting with the MultiversX blockchain (in general) and Smart Contracts (in particular). * * @packageDocumentation */ -require('./globals'); +require("./globals"); export * from "./account"; +export * from "./adapters"; export * from "./address"; export * from "./asyncTimer"; +export * from "./converters"; export * from "./errors"; export * from "./gasEstimator"; export * from "./interface"; export * from "./interfaceOfNetwork"; export * from "./logger"; +export * from "./message"; export * from "./networkParams"; export * from "./relayedTransactionV1Builder"; export * from "./relayedTransactionV2Builder"; export * from "./signableMessage"; +export * from "./smartContractQueriesController"; export * from "./smartcontracts"; export * from "./tokenOperations"; -export * from "./tokenTransfer"; +export * from "./tokens"; export * from "./transaction"; +export * from "./transactionComputer"; export * from "./transactionPayload"; export * from "./transactionWatcher"; -export * from "./transferTransactionsFactory"; +export * from "./transactionsFactories"; +export * from "./transactionsOutcomeParsers"; export * from "./utils"; - diff --git a/src/interface.ts b/src/interface.ts index 4a0c10369..97c86a54f 100644 --- a/src/interface.ts +++ b/src/interface.ts @@ -26,16 +26,45 @@ export interface IPlainTransactionObject { guardianSignature?: string; } -export interface ISignature { hex(): string; } -export interface IAddress { bech32(): string; } -export interface ITransactionValue { toString(): string; } -export interface IAccountBalance { toString(): string; } -export interface INonce { valueOf(): number; } -export interface IChainID { valueOf(): string; } -export interface IGasLimit { valueOf(): number; } -export interface IGasPrice { valueOf(): number; } -export interface ITransactionVersion { valueOf(): number; } -export interface ITransactionOptions { valueOf(): number; } +export interface ISignature { + hex(): string; +} + +export interface IAddress { + bech32(): string; +} + +export interface ITransactionValue { + toString(): string; +} + +export interface IAccountBalance { + toString(): string; +} + +export interface INonce { + valueOf(): number; +} + +export interface IChainID { + valueOf(): string; +} + +export interface IGasLimit { + valueOf(): number; +} + +export interface IGasPrice { + valueOf(): number; +} + +export interface ITransactionVersion { + valueOf(): number; +} + +export interface ITransactionOptions { + valueOf(): number; +} export interface ITransactionPayload { length(): number; @@ -44,6 +73,9 @@ export interface ITransactionPayload { valueOf(): Buffer; } +/** + * Legacy interface. The class `TokenTransfer` can be used instead, where necessary. + */ export interface ITokenTransfer { readonly tokenIdentifier: string; readonly nonce: number; @@ -55,3 +87,21 @@ export interface ITokenTransfer { * @deprecated Use {@link ITokenTransfer} instead. */ export type ITokenPayment = ITokenTransfer; + +export interface ITransaction { + sender: string; + receiver: string; + gasLimit: bigint; + chainID: string; + nonce: bigint; + value: bigint; + senderUsername: string; + receiverUsername: string; + gasPrice: bigint; + data: Uint8Array; + version: number; + options: number; + guardian: string; + signature: Uint8Array; + guardianSignature: Uint8Array; +} diff --git a/src/interfaceOfNetwork.ts b/src/interfaceOfNetwork.ts index 07edeb9e6..4fefd39e2 100644 --- a/src/interfaceOfNetwork.ts +++ b/src/interfaceOfNetwork.ts @@ -20,6 +20,7 @@ export interface ITransactionOnNetwork { value: string; receiver: IAddress; sender: IAddress; + function?: string; data: Buffer; status: ITransactionStatus; receipt: ITransactionReceipt; @@ -63,19 +64,13 @@ export interface IContractReturnCode { } export interface ITransactionLogs { + address: IAddress; events: ITransactionEvent[]; - findSingleOrNoneEvent(identifier: string, predicate?: (event: ITransactionEvent) => boolean): ITransactionEvent | undefined; - - /** - * @deprecated Will be removed from the interface (with no replacement). Not used in "sdk-core". - */ - findFirstOrNoneEvent(identifier: string, predicate?: (event: ITransactionEvent) => boolean): ITransactionEvent | undefined; - - /** - * @deprecated Will be removed from the interface (with no replacement). Not used in "sdk-core". - */ - findEvents(identifier: string, predicate?: (event: ITransactionEvent) => boolean): ITransactionEvent[]; + findSingleOrNoneEvent( + identifier: string, + predicate?: (event: ITransactionEvent) => boolean, + ): ITransactionEvent | undefined; } export interface ITransactionEvent { @@ -83,6 +78,9 @@ export interface ITransactionEvent { readonly identifier: string; readonly topics: ITransactionEventTopic[]; readonly data: string; + // See https://github.com/multiversx/mx-sdk-js-network-providers/blob/v2.4.0/src/transactionEvents.ts#L13 + readonly dataPayload?: { valueOf(): Uint8Array }; + readonly additionalData?: { valueOf(): Uint8Array }[]; findFirstOrNoneTopic(predicate: (topic: ITransactionEventTopic) => boolean): ITransactionEventTopic | undefined; getLastTopic(): ITransactionEventTopic; diff --git a/src/logger.ts b/src/logger.ts index 50ff4d254..65910ea6c 100644 --- a/src/logger.ts +++ b/src/logger.ts @@ -4,7 +4,7 @@ export enum LogLevel { Info = 2, Warn = 3, Error = 4, - None = 5 + None = 5, } export class Logger { @@ -54,4 +54,3 @@ export class Logger { console.error(message, optionalParams); } } - diff --git a/src/message.spec.ts b/src/message.spec.ts new file mode 100644 index 000000000..dfa97e4c6 --- /dev/null +++ b/src/message.spec.ts @@ -0,0 +1,87 @@ +import { UserVerifier } from "@multiversx/sdk-wallet"; +import { assert } from "chai"; +import { DEFAULT_MESSAGE_VERSION } from "./constants"; +import { Message, MessageComputer } from "./message"; +import { TestWallet, loadTestWallets } from "./testutils"; + +describe("test message", () => { + let alice: TestWallet; + const messageComputer = new MessageComputer(); + + before(async function () { + ({ alice } = await loadTestWallets()); + }); + + it("should test message compute bytes for signing", async () => { + const data = Buffer.from("test message"); + + const message = new Message({ + data: data, + }); + + const serialized = messageComputer.computeBytesForSigning(message); + + assert.equal( + Buffer.from(serialized).toString("hex"), + "2162d6271208429e6d3e664139e98ba7c5f1870906fb113e8903b1d3f531004d", + ); + }); + + it("should create, sign, pack, unpack and verify message", async () => { + const data = Buffer.from("test"); + + const message = new Message({ + data: data, + address: alice.getAddress(), + }); + + message.signature = await alice.signer.sign(messageComputer.computeBytesForSigning(message)); + + assert.equal( + Buffer.from(message.signature).toString("hex"), + "7aff43cd6e3d880a65033bf0a1b16274854fd7dfa9fe5faa7fa9a665ee851afd4c449310f5f1697d348e42d1819eaef69080e33e7652d7393521ed50d7427a0e", + ); + + const packedMessage = messageComputer.packMessage(message); + assert.deepEqual(packedMessage, { + address: "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th", + message: "74657374", + signature: + "7aff43cd6e3d880a65033bf0a1b16274854fd7dfa9fe5faa7fa9a665ee851afd4c449310f5f1697d348e42d1819eaef69080e33e7652d7393521ed50d7427a0e", + version: 1, + }); + + const unpackedMessage = messageComputer.unpackMessage(packedMessage); + assert.deepEqual(unpackedMessage.address, alice.getAddress()); + assert.deepEqual(unpackedMessage.data, message.data); + assert.deepEqual(unpackedMessage.signature, message.signature); + assert.deepEqual(unpackedMessage.version, message.version); + + const verifier = UserVerifier.fromAddress(alice.getAddress()); + const isValid = verifier.verify( + Buffer.from(messageComputer.computeBytesForVerifying(unpackedMessage)), + Buffer.from(unpackedMessage.signature!), + ); + assert.equal(isValid, true); + }); + + it("should unpack legacy message", async () => { + const legacyMessage = { + address: "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th", + message: "0x7468697320697320612074657374206d657373616765", + signature: + "0xb16847437049986f936dd4a0917c869730cbf29e40a0c0821ca70db33f44758c3d41bcbea446dee70dea13d50942343bb78e74979dc434bbb2b901e0f4fd1809", + version: 1, + signer: "ErdJS", + }; + + const message = messageComputer.unpackMessage(legacyMessage); + assert.deepEqual(message.address, alice.getAddress()); + assert.deepEqual(Buffer.from(message.data).toString(), "this is a test message"); + assert.deepEqual( + Buffer.from(message.signature!).toString("hex"), + "b16847437049986f936dd4a0917c869730cbf29e40a0c0821ca70db33f44758c3d41bcbea446dee70dea13d50942343bb78e74979dc434bbb2b901e0f4fd1809", + ); + assert.deepEqual(message.version, DEFAULT_MESSAGE_VERSION); + }); +}); diff --git a/src/message.ts b/src/message.ts new file mode 100644 index 000000000..d018f26dd --- /dev/null +++ b/src/message.ts @@ -0,0 +1,90 @@ +import { IAddress } from "./interface"; +import { DEFAULT_MESSAGE_VERSION, MESSAGE_PREFIX } from "./constants"; +import { Address } from "./address"; + +const createKeccakHash = require("keccak"); + +export class Message { + /** + * Actual message being signed. + */ + public data: Uint8Array; + /** + * The message signature. + */ + public signature?: Uint8Array; + /** + * Address of the wallet that performed the signing operation. + */ + public address?: IAddress; + /** + * Number representing the message version. + */ + public version: number; + + constructor(options: { data: Uint8Array; signature?: Uint8Array; address?: IAddress; version?: number }) { + this.data = options.data; + this.signature = options.signature; + this.address = options.address; + this.version = options.version || DEFAULT_MESSAGE_VERSION; + } +} + +export class MessageComputer { + constructor() {} + + computeBytesForSigning(message: Message): Uint8Array { + const messageSize = Buffer.from(message.data.length.toString()); + const signableMessage = Buffer.concat([messageSize, message.data]); + let bytesToHash = Buffer.concat([Buffer.from(MESSAGE_PREFIX), signableMessage]); + + return createKeccakHash("keccak256").update(bytesToHash).digest(); + } + + computeBytesForVerifying(message: Message): Uint8Array { + return this.computeBytesForSigning(message); + } + + packMessage(message: Message): { + message: string; + signature: string; + address: string; + version: number; + } { + return { + message: Buffer.from(message.data).toString("hex"), + signature: message.signature ? Buffer.from(message.signature).toString("hex") : "", + address: message.address ? message.address.bech32() : "", + version: message.version ? message.version : DEFAULT_MESSAGE_VERSION, + }; + } + + unpackMessage(packedMessage: { message: string; signature?: string; address?: string; version?: number }): Message { + const dataHex = this.trimHexPrefix(packedMessage.message); + const data = Buffer.from(dataHex, "hex"); + + const signatureHex = this.trimHexPrefix(packedMessage.signature || ""); + const signature = Buffer.from(signatureHex, "hex"); + + let address: Address | undefined = undefined; + if (packedMessage.address) { + address = Address.fromBech32(packedMessage.address); + } + + const version = packedMessage.version || DEFAULT_MESSAGE_VERSION; + + return new Message({ + data: data, + signature: signature, + address: address, + version: version, + }); + } + + private trimHexPrefix(data: string): string { + if (data.startsWith("0x") || data.startsWith("0X")) { + return data.slice(2); + } + return data; + } +} diff --git a/src/networkParams.spec.ts b/src/networkParams.spec.ts index 2932b8167..7f127437b 100644 --- a/src/networkParams.spec.ts +++ b/src/networkParams.spec.ts @@ -1,6 +1,9 @@ import { assert } from "chai"; import { - TRANSACTION_OPTIONS_DEFAULT, TRANSACTION_OPTIONS_TX_GUARDED, TRANSACTION_OPTIONS_TX_HASH_SIGN, TRANSACTION_VERSION_DEFAULT, TRANSACTION_VERSION_WITH_OPTIONS + TRANSACTION_OPTIONS_DEFAULT, + TRANSACTION_OPTIONS_TX_GUARDED, + TRANSACTION_OPTIONS_TX_HASH_SIGN, + TRANSACTION_VERSION_DEFAULT, } from "./constants"; import { TransactionOptions, TransactionVersion } from "./networkParams"; @@ -17,7 +20,7 @@ describe("test transaction version", () => { const versionWithOptions = TransactionVersion.withTxOptions(); assert.equal(TRANSACTION_VERSION_DEFAULT, versionDefault.valueOf()); - assert.equal(TRANSACTION_VERSION_WITH_OPTIONS, versionWithOptions.valueOf()); + assert.equal(TRANSACTION_VERSION_DEFAULT, versionWithOptions.valueOf()); }); }); @@ -61,7 +64,10 @@ describe("test transaction options", () => { assert.isTrue(optionsWithGuardian.isWithGuardian()); const optionsWithHashSignAndGuardian = TransactionOptions.withOptions({ hashSign: true, guarded: true }); - assert.equal(optionsWithHashSignAndGuardian.valueOf(), TRANSACTION_OPTIONS_TX_HASH_SIGN | TRANSACTION_OPTIONS_TX_GUARDED); + assert.equal( + optionsWithHashSignAndGuardian.valueOf(), + TRANSACTION_OPTIONS_TX_HASH_SIGN | TRANSACTION_OPTIONS_TX_GUARDED, + ); assert.isTrue(optionsWithHashSignAndGuardian.isWithHashSign()); assert.isTrue(optionsWithHashSignAndGuardian.isWithGuardian()); }); diff --git a/src/networkParams.ts b/src/networkParams.ts index 5a26bf08c..36f8a23c7 100644 --- a/src/networkParams.ts +++ b/src/networkParams.ts @@ -1,4 +1,9 @@ -import { TRANSACTION_OPTIONS_DEFAULT, TRANSACTION_OPTIONS_TX_GUARDED, TRANSACTION_OPTIONS_TX_HASH_SIGN, TRANSACTION_VERSION_DEFAULT, TRANSACTION_VERSION_WITH_OPTIONS } from "./constants"; +import { + TRANSACTION_OPTIONS_DEFAULT, + TRANSACTION_OPTIONS_TX_GUARDED, + TRANSACTION_OPTIONS_TX_HASH_SIGN, + TRANSACTION_VERSION_DEFAULT, +} from "./constants"; import * as errors from "./errors"; export class TransactionVersion { @@ -31,7 +36,7 @@ export class TransactionVersion { * Creates a TransactionVersion object with the VERSION setting for enabling options */ static withTxOptions(): TransactionVersion { - return new TransactionVersion(TRANSACTION_VERSION_WITH_OPTIONS); + return new TransactionVersion(TRANSACTION_VERSION_DEFAULT); } valueOf(): number { @@ -68,10 +73,7 @@ export class TransactionOptions { /** * Creates a TransactionOptions object from a set of options. */ - public static withOptions(options: { - hashSign?: boolean, - guarded?: boolean - }): TransactionOptions { + public static withOptions(options: { hashSign?: boolean; guarded?: boolean }): TransactionOptions { let value = 0; if (options.hashSign) { diff --git a/src/proto/compiled.d.ts b/src/proto/compiled.d.ts deleted file mode 100644 index ed08acfeb..000000000 --- a/src/proto/compiled.d.ts +++ /dev/null @@ -1,178 +0,0 @@ -import * as $protobuf from "protobufjs"; -/** Namespace proto. */ -export namespace proto { - - /** Properties of a Transaction. */ - interface ITransaction { - - /** Transaction Nonce */ - Nonce?: (number|Long|null); - - /** Transaction Value */ - Value?: (Uint8Array|null); - - /** Transaction RcvAddr */ - RcvAddr?: (Uint8Array|null); - - /** Transaction RcvUserName */ - RcvUserName?: (Uint8Array|null); - - /** Transaction SndAddr */ - SndAddr?: (Uint8Array|null); - - /** Transaction SndUserName */ - SndUserName?: (Uint8Array|null); - - /** Transaction GasPrice */ - GasPrice?: (number|Long|null); - - /** Transaction GasLimit */ - GasLimit?: (number|Long|null); - - /** Transaction Data */ - Data?: (Uint8Array|null); - - /** Transaction ChainID */ - ChainID?: (Uint8Array|null); - - /** Transaction Version */ - Version?: (number|null); - - /** Transaction Signature */ - Signature?: (Uint8Array|null); - - /** Transaction Options */ - Options?: (number|null); - - /** Transaction GuardAddr */ - GuardAddr?: (Uint8Array|null); - - /** Transaction GuardSignature */ - GuardSignature?: (Uint8Array|null); - } - - /** Represents a Transaction. */ - class Transaction implements ITransaction { - - /** - * Constructs a new Transaction. - * @param [properties] Properties to set - */ - constructor(properties?: proto.ITransaction); - - /** Transaction Nonce. */ - public Nonce: (number|Long); - - /** Transaction Value. */ - public Value: Uint8Array; - - /** Transaction RcvAddr. */ - public RcvAddr: Uint8Array; - - /** Transaction RcvUserName. */ - public RcvUserName: Uint8Array; - - /** Transaction SndAddr. */ - public SndAddr: Uint8Array; - - /** Transaction SndUserName. */ - public SndUserName: Uint8Array; - - /** Transaction GasPrice. */ - public GasPrice: (number|Long); - - /** Transaction GasLimit. */ - public GasLimit: (number|Long); - - /** Transaction Data. */ - public Data: Uint8Array; - - /** Transaction ChainID. */ - public ChainID: Uint8Array; - - /** Transaction Version. */ - public Version: number; - - /** Transaction Signature. */ - public Signature: Uint8Array; - - /** Transaction Options. */ - public Options: number; - - /** Transaction GuardAddr. */ - public GuardAddr: Uint8Array; - - /** Transaction GuardSignature. */ - public GuardSignature: Uint8Array; - - /** - * Creates a new Transaction instance using the specified properties. - * @param [properties] Properties to set - * @returns Transaction instance - */ - public static create(properties?: proto.ITransaction): proto.Transaction; - - /** - * Encodes the specified Transaction message. Does not implicitly {@link proto.Transaction.verify|verify} messages. - * @param message Transaction message or plain object to encode - * @param [writer] Writer to encode to - * @returns Writer - */ - public static encode(message: proto.ITransaction, writer?: $protobuf.Writer): $protobuf.Writer; - - /** - * Encodes the specified Transaction message, length delimited. Does not implicitly {@link proto.Transaction.verify|verify} messages. - * @param message Transaction message or plain object to encode - * @param [writer] Writer to encode to - * @returns Writer - */ - public static encodeDelimited(message: proto.ITransaction, writer?: $protobuf.Writer): $protobuf.Writer; - - /** - * Decodes a Transaction message from the specified reader or buffer. - * @param reader Reader or buffer to decode from - * @param [length] Message length if known beforehand - * @returns Transaction - * @throws {Error} If the payload is not a reader or valid buffer - * @throws {$protobuf.util.ProtocolError} If required fields are missing - */ - public static decode(reader: ($protobuf.Reader|Uint8Array), length?: number): proto.Transaction; - - /** - * Decodes a Transaction message from the specified reader or buffer, length delimited. - * @param reader Reader or buffer to decode from - * @returns Transaction - * @throws {Error} If the payload is not a reader or valid buffer - * @throws {$protobuf.util.ProtocolError} If required fields are missing - */ - public static decodeDelimited(reader: ($protobuf.Reader|Uint8Array)): proto.Transaction; - - /** - * Verifies a Transaction message. - * @param message Plain object to verify - * @returns `null` if valid, otherwise the reason why it is not - */ - public static verify(message: { [k: string]: any }): (string|null); - - /** - * Creates a Transaction message from a plain object. Also converts values to their respective internal types. - * @param object Plain object - * @returns Transaction - */ - public static fromObject(object: { [k: string]: any }): proto.Transaction; - - /** - * Creates a plain object from a Transaction message. Also converts values to other types if specified. - * @param message Transaction - * @param [options] Conversion options - * @returns Plain object - */ - public static toObject(message: proto.Transaction, options?: $protobuf.IConversionOptions): { [k: string]: any }; - - /** - * Converts this Transaction to JSON. - * @returns JSON object - */ - public toJSON(): { [k: string]: any }; - } -} diff --git a/src/proto/compiled.js b/src/proto/compiled.js index 7efef837c..feed796f9 100644 --- a/src/proto/compiled.js +++ b/src/proto/compiled.js @@ -1,652 +1,690 @@ /*eslint-disable block-scoped-var, id-length, no-control-regex, no-magic-numbers, no-prototype-builtins, no-redeclare, no-shadow, no-var, sort-vars*/ -"use strict"; +(function(global, factory) { /* global define, require, module */ -var $protobuf = require("protobufjs/minimal"); + /* AMD */ if (typeof define === 'function' && define.amd) + define(["protobufjs/minimal"], factory); -// Common aliases -var $Reader = $protobuf.Reader, $Writer = $protobuf.Writer, $util = $protobuf.util; + /* CommonJS */ else if (typeof require === 'function' && typeof module === 'object' && module && module.exports) + module.exports = factory(require("protobufjs/minimal")); -// Exported root namespace -var $root = $protobuf.roots["default"] || ($protobuf.roots["default"] = {}); - -$root.proto = (function() { - - /** - * Namespace proto. - * @exports proto - * @namespace - */ - var proto = {}; - - proto.Transaction = (function() { - - /** - * Properties of a Transaction. - * @memberof proto - * @interface ITransaction - * @property {number|Long|null} [Nonce] Transaction Nonce - * @property {Uint8Array|null} [Value] Transaction Value - * @property {Uint8Array|null} [RcvAddr] Transaction RcvAddr - * @property {Uint8Array|null} [RcvUserName] Transaction RcvUserName - * @property {Uint8Array|null} [SndAddr] Transaction SndAddr - * @property {Uint8Array|null} [SndUserName] Transaction SndUserName - * @property {number|Long|null} [GasPrice] Transaction GasPrice - * @property {number|Long|null} [GasLimit] Transaction GasLimit - * @property {Uint8Array|null} [Data] Transaction Data - * @property {Uint8Array|null} [ChainID] Transaction ChainID - * @property {number|null} [Version] Transaction Version - * @property {Uint8Array|null} [Signature] Transaction Signature - * @property {number|null} [Options] Transaction Options - * @property {Uint8Array|null} [GuardAddr] Transaction GuardAddr - * @property {Uint8Array|null} [GuardSignature] Transaction GuardSignature - */ - - /** - * Constructs a new Transaction. - * @memberof proto - * @classdesc Represents a Transaction. - * @implements ITransaction - * @constructor - * @param {proto.ITransaction=} [properties] Properties to set - */ - function Transaction(properties) { - if (properties) - for (var keys = Object.keys(properties), i = 0; i < keys.length; ++i) - if (properties[keys[i]] != null) - this[keys[i]] = properties[keys[i]]; - } - - /** - * Transaction Nonce. - * @member {number|Long} Nonce - * @memberof proto.Transaction - * @instance - */ - Transaction.prototype.Nonce = $util.Long ? $util.Long.fromBits(0,0,true) : 0; - - /** - * Transaction Value. - * @member {Uint8Array} Value - * @memberof proto.Transaction - * @instance - */ - Transaction.prototype.Value = $util.newBuffer([]); - - /** - * Transaction RcvAddr. - * @member {Uint8Array} RcvAddr - * @memberof proto.Transaction - * @instance - */ - Transaction.prototype.RcvAddr = $util.newBuffer([]); - - /** - * Transaction RcvUserName. - * @member {Uint8Array} RcvUserName - * @memberof proto.Transaction - * @instance - */ - Transaction.prototype.RcvUserName = $util.newBuffer([]); - - /** - * Transaction SndAddr. - * @member {Uint8Array} SndAddr - * @memberof proto.Transaction - * @instance - */ - Transaction.prototype.SndAddr = $util.newBuffer([]); - - /** - * Transaction SndUserName. - * @member {Uint8Array} SndUserName - * @memberof proto.Transaction - * @instance - */ - Transaction.prototype.SndUserName = $util.newBuffer([]); - - /** - * Transaction GasPrice. - * @member {number|Long} GasPrice - * @memberof proto.Transaction - * @instance - */ - Transaction.prototype.GasPrice = $util.Long ? $util.Long.fromBits(0,0,true) : 0; - - /** - * Transaction GasLimit. - * @member {number|Long} GasLimit - * @memberof proto.Transaction - * @instance - */ - Transaction.prototype.GasLimit = $util.Long ? $util.Long.fromBits(0,0,true) : 0; +})(this, function($protobuf) { + "use strict"; + // Common aliases + var $Reader = $protobuf.Reader, $Writer = $protobuf.Writer, $util = $protobuf.util; + + // Exported root namespace + var $root = $protobuf.roots["default"] || ($protobuf.roots["default"] = {}); + + $root.proto = (function() { + /** - * Transaction Data. - * @member {Uint8Array} Data - * @memberof proto.Transaction - * @instance + * Namespace proto. + * @exports proto + * @namespace */ - Transaction.prototype.Data = $util.newBuffer([]); - - /** - * Transaction ChainID. - * @member {Uint8Array} ChainID - * @memberof proto.Transaction - * @instance - */ - Transaction.prototype.ChainID = $util.newBuffer([]); - - /** - * Transaction Version. - * @member {number} Version - * @memberof proto.Transaction - * @instance - */ - Transaction.prototype.Version = 0; - - /** - * Transaction Signature. - * @member {Uint8Array} Signature - * @memberof proto.Transaction - * @instance - */ - Transaction.prototype.Signature = $util.newBuffer([]); - - /** - * Transaction Options. - * @member {number} Options - * @memberof proto.Transaction - * @instance - */ - Transaction.prototype.Options = 0; - - /** - * Transaction GuardAddr. - * @member {Uint8Array} GuardAddr - * @memberof proto.Transaction - * @instance - */ - Transaction.prototype.GuardAddr = $util.newBuffer([]); - - /** - * Transaction GuardSignature. - * @member {Uint8Array} GuardSignature - * @memberof proto.Transaction - * @instance - */ - Transaction.prototype.GuardSignature = $util.newBuffer([]); - - /** - * Creates a new Transaction instance using the specified properties. - * @function create - * @memberof proto.Transaction - * @static - * @param {proto.ITransaction=} [properties] Properties to set - * @returns {proto.Transaction} Transaction instance - */ - Transaction.create = function create(properties) { - return new Transaction(properties); - }; - - /** - * Encodes the specified Transaction message. Does not implicitly {@link proto.Transaction.verify|verify} messages. - * @function encode - * @memberof proto.Transaction - * @static - * @param {proto.ITransaction} message Transaction message or plain object to encode - * @param {$protobuf.Writer} [writer] Writer to encode to - * @returns {$protobuf.Writer} Writer - */ - Transaction.encode = function encode(message, writer) { - if (!writer) - writer = $Writer.create(); - if (message.Nonce != null && Object.hasOwnProperty.call(message, "Nonce")) - writer.uint32(/* id 1, wireType 0 =*/8).uint64(message.Nonce); - if (message.Value != null && Object.hasOwnProperty.call(message, "Value")) - writer.uint32(/* id 2, wireType 2 =*/18).bytes(message.Value); - if (message.RcvAddr != null && Object.hasOwnProperty.call(message, "RcvAddr")) - writer.uint32(/* id 3, wireType 2 =*/26).bytes(message.RcvAddr); - if (message.RcvUserName != null && Object.hasOwnProperty.call(message, "RcvUserName")) - writer.uint32(/* id 4, wireType 2 =*/34).bytes(message.RcvUserName); - if (message.SndAddr != null && Object.hasOwnProperty.call(message, "SndAddr")) - writer.uint32(/* id 5, wireType 2 =*/42).bytes(message.SndAddr); - if (message.SndUserName != null && Object.hasOwnProperty.call(message, "SndUserName")) - writer.uint32(/* id 6, wireType 2 =*/50).bytes(message.SndUserName); - if (message.GasPrice != null && Object.hasOwnProperty.call(message, "GasPrice")) - writer.uint32(/* id 7, wireType 0 =*/56).uint64(message.GasPrice); - if (message.GasLimit != null && Object.hasOwnProperty.call(message, "GasLimit")) - writer.uint32(/* id 8, wireType 0 =*/64).uint64(message.GasLimit); - if (message.Data != null && Object.hasOwnProperty.call(message, "Data")) - writer.uint32(/* id 9, wireType 2 =*/74).bytes(message.Data); - if (message.ChainID != null && Object.hasOwnProperty.call(message, "ChainID")) - writer.uint32(/* id 10, wireType 2 =*/82).bytes(message.ChainID); - if (message.Version != null && Object.hasOwnProperty.call(message, "Version")) - writer.uint32(/* id 11, wireType 0 =*/88).uint32(message.Version); - if (message.Signature != null && Object.hasOwnProperty.call(message, "Signature")) - writer.uint32(/* id 12, wireType 2 =*/98).bytes(message.Signature); - if (message.Options != null && Object.hasOwnProperty.call(message, "Options")) - writer.uint32(/* id 13, wireType 0 =*/104).uint32(message.Options); - if (message.GuardAddr != null && Object.hasOwnProperty.call(message, "GuardAddr")) - writer.uint32(/* id 14, wireType 2 =*/114).bytes(message.GuardAddr); - if (message.GuardSignature != null && Object.hasOwnProperty.call(message, "GuardSignature")) - writer.uint32(/* id 15, wireType 2 =*/122).bytes(message.GuardSignature); - return writer; - }; - - /** - * Encodes the specified Transaction message, length delimited. Does not implicitly {@link proto.Transaction.verify|verify} messages. - * @function encodeDelimited - * @memberof proto.Transaction - * @static - * @param {proto.ITransaction} message Transaction message or plain object to encode - * @param {$protobuf.Writer} [writer] Writer to encode to - * @returns {$protobuf.Writer} Writer - */ - Transaction.encodeDelimited = function encodeDelimited(message, writer) { - return this.encode(message, writer).ldelim(); - }; - - /** - * Decodes a Transaction message from the specified reader or buffer. - * @function decode - * @memberof proto.Transaction - * @static - * @param {$protobuf.Reader|Uint8Array} reader Reader or buffer to decode from - * @param {number} [length] Message length if known beforehand - * @returns {proto.Transaction} Transaction - * @throws {Error} If the payload is not a reader or valid buffer - * @throws {$protobuf.util.ProtocolError} If required fields are missing - */ - Transaction.decode = function decode(reader, length) { - if (!(reader instanceof $Reader)) - reader = $Reader.create(reader); - var end = length === undefined ? reader.len : reader.pos + length, message = new $root.proto.Transaction(); - while (reader.pos < end) { - var tag = reader.uint32(); - switch (tag >>> 3) { - case 1: - message.Nonce = reader.uint64(); - break; - case 2: - message.Value = reader.bytes(); - break; - case 3: - message.RcvAddr = reader.bytes(); - break; - case 4: - message.RcvUserName = reader.bytes(); - break; - case 5: - message.SndAddr = reader.bytes(); - break; - case 6: - message.SndUserName = reader.bytes(); - break; - case 7: - message.GasPrice = reader.uint64(); - break; - case 8: - message.GasLimit = reader.uint64(); - break; - case 9: - message.Data = reader.bytes(); - break; - case 10: - message.ChainID = reader.bytes(); - break; - case 11: - message.Version = reader.uint32(); - break; - case 12: - message.Signature = reader.bytes(); - break; - case 13: - message.Options = reader.uint32(); - break; - case 14: - message.GuardAddr = reader.bytes(); - break; - case 15: - message.GuardSignature = reader.bytes(); - break; - default: - reader.skipType(tag & 7); - break; - } + var proto = {}; + + proto.Transaction = (function() { + + /** + * Properties of a Transaction. + * @memberof proto + * @interface ITransaction + * @property {number|Long|null} [Nonce] Transaction Nonce + * @property {Uint8Array|null} [Value] Transaction Value + * @property {Uint8Array|null} [RcvAddr] Transaction RcvAddr + * @property {Uint8Array|null} [RcvUserName] Transaction RcvUserName + * @property {Uint8Array|null} [SndAddr] Transaction SndAddr + * @property {Uint8Array|null} [SndUserName] Transaction SndUserName + * @property {number|Long|null} [GasPrice] Transaction GasPrice + * @property {number|Long|null} [GasLimit] Transaction GasLimit + * @property {Uint8Array|null} [Data] Transaction Data + * @property {Uint8Array|null} [ChainID] Transaction ChainID + * @property {number|null} [Version] Transaction Version + * @property {Uint8Array|null} [Signature] Transaction Signature + * @property {number|null} [Options] Transaction Options + * @property {Uint8Array|null} [GuardianAddr] Transaction GuardianAddr + * @property {Uint8Array|null} [GuardianSignature] Transaction GuardianSignature + */ + + /** + * Constructs a new Transaction. + * @memberof proto + * @classdesc Represents a Transaction. + * @implements ITransaction + * @constructor + * @param {proto.ITransaction=} [properties] Properties to set + */ + function Transaction(properties) { + if (properties) + for (var keys = Object.keys(properties), i = 0; i < keys.length; ++i) + if (properties[keys[i]] != null) + this[keys[i]] = properties[keys[i]]; } - return message; - }; - - /** - * Decodes a Transaction message from the specified reader or buffer, length delimited. - * @function decodeDelimited - * @memberof proto.Transaction - * @static - * @param {$protobuf.Reader|Uint8Array} reader Reader or buffer to decode from - * @returns {proto.Transaction} Transaction - * @throws {Error} If the payload is not a reader or valid buffer - * @throws {$protobuf.util.ProtocolError} If required fields are missing - */ - Transaction.decodeDelimited = function decodeDelimited(reader) { - if (!(reader instanceof $Reader)) - reader = new $Reader(reader); - return this.decode(reader, reader.uint32()); - }; - - /** - * Verifies a Transaction message. - * @function verify - * @memberof proto.Transaction - * @static - * @param {Object.} message Plain object to verify - * @returns {string|null} `null` if valid, otherwise the reason why it is not - */ - Transaction.verify = function verify(message) { - if (typeof message !== "object" || message === null) - return "object expected"; - if (message.Nonce != null && message.hasOwnProperty("Nonce")) - if (!$util.isInteger(message.Nonce) && !(message.Nonce && $util.isInteger(message.Nonce.low) && $util.isInteger(message.Nonce.high))) - return "Nonce: integer|Long expected"; - if (message.Value != null && message.hasOwnProperty("Value")) - if (!(message.Value && typeof message.Value.length === "number" || $util.isString(message.Value))) - return "Value: buffer expected"; - if (message.RcvAddr != null && message.hasOwnProperty("RcvAddr")) - if (!(message.RcvAddr && typeof message.RcvAddr.length === "number" || $util.isString(message.RcvAddr))) - return "RcvAddr: buffer expected"; - if (message.RcvUserName != null && message.hasOwnProperty("RcvUserName")) - if (!(message.RcvUserName && typeof message.RcvUserName.length === "number" || $util.isString(message.RcvUserName))) - return "RcvUserName: buffer expected"; - if (message.SndAddr != null && message.hasOwnProperty("SndAddr")) - if (!(message.SndAddr && typeof message.SndAddr.length === "number" || $util.isString(message.SndAddr))) - return "SndAddr: buffer expected"; - if (message.SndUserName != null && message.hasOwnProperty("SndUserName")) - if (!(message.SndUserName && typeof message.SndUserName.length === "number" || $util.isString(message.SndUserName))) - return "SndUserName: buffer expected"; - if (message.GasPrice != null && message.hasOwnProperty("GasPrice")) - if (!$util.isInteger(message.GasPrice) && !(message.GasPrice && $util.isInteger(message.GasPrice.low) && $util.isInteger(message.GasPrice.high))) - return "GasPrice: integer|Long expected"; - if (message.GasLimit != null && message.hasOwnProperty("GasLimit")) - if (!$util.isInteger(message.GasLimit) && !(message.GasLimit && $util.isInteger(message.GasLimit.low) && $util.isInteger(message.GasLimit.high))) - return "GasLimit: integer|Long expected"; - if (message.Data != null && message.hasOwnProperty("Data")) - if (!(message.Data && typeof message.Data.length === "number" || $util.isString(message.Data))) - return "Data: buffer expected"; - if (message.ChainID != null && message.hasOwnProperty("ChainID")) - if (!(message.ChainID && typeof message.ChainID.length === "number" || $util.isString(message.ChainID))) - return "ChainID: buffer expected"; - if (message.Version != null && message.hasOwnProperty("Version")) - if (!$util.isInteger(message.Version)) - return "Version: integer expected"; - if (message.Signature != null && message.hasOwnProperty("Signature")) - if (!(message.Signature && typeof message.Signature.length === "number" || $util.isString(message.Signature))) - return "Signature: buffer expected"; - if (message.Options != null && message.hasOwnProperty("Options")) - if (!$util.isInteger(message.Options)) - return "Options: integer expected"; - if (message.GuardAddr != null && message.hasOwnProperty("GuardAddr")) - if (!(message.GuardAddr && typeof message.GuardAddr.length === "number" || $util.isString(message.GuardAddr))) - return "GuardAddr: buffer expected"; - if (message.GuardSignature != null && message.hasOwnProperty("GuardSignature")) - if (!(message.GuardSignature && typeof message.GuardSignature.length === "number" || $util.isString(message.GuardSignature))) - return "GuardSignature: buffer expected"; - return null; - }; - - /** - * Creates a Transaction message from a plain object. Also converts values to their respective internal types. - * @function fromObject - * @memberof proto.Transaction - * @static - * @param {Object.} object Plain object - * @returns {proto.Transaction} Transaction - */ - Transaction.fromObject = function fromObject(object) { - if (object instanceof $root.proto.Transaction) - return object; - var message = new $root.proto.Transaction(); - if (object.Nonce != null) - if ($util.Long) - (message.Nonce = $util.Long.fromValue(object.Nonce)).unsigned = true; - else if (typeof object.Nonce === "string") - message.Nonce = parseInt(object.Nonce, 10); - else if (typeof object.Nonce === "number") - message.Nonce = object.Nonce; - else if (typeof object.Nonce === "object") - message.Nonce = new $util.LongBits(object.Nonce.low >>> 0, object.Nonce.high >>> 0).toNumber(true); - if (object.Value != null) - if (typeof object.Value === "string") - $util.base64.decode(object.Value, message.Value = $util.newBuffer($util.base64.length(object.Value)), 0); - else if (object.Value.length) - message.Value = object.Value; - if (object.RcvAddr != null) - if (typeof object.RcvAddr === "string") - $util.base64.decode(object.RcvAddr, message.RcvAddr = $util.newBuffer($util.base64.length(object.RcvAddr)), 0); - else if (object.RcvAddr.length) - message.RcvAddr = object.RcvAddr; - if (object.RcvUserName != null) - if (typeof object.RcvUserName === "string") - $util.base64.decode(object.RcvUserName, message.RcvUserName = $util.newBuffer($util.base64.length(object.RcvUserName)), 0); - else if (object.RcvUserName.length) - message.RcvUserName = object.RcvUserName; - if (object.SndAddr != null) - if (typeof object.SndAddr === "string") - $util.base64.decode(object.SndAddr, message.SndAddr = $util.newBuffer($util.base64.length(object.SndAddr)), 0); - else if (object.SndAddr.length) - message.SndAddr = object.SndAddr; - if (object.SndUserName != null) - if (typeof object.SndUserName === "string") - $util.base64.decode(object.SndUserName, message.SndUserName = $util.newBuffer($util.base64.length(object.SndUserName)), 0); - else if (object.SndUserName.length) - message.SndUserName = object.SndUserName; - if (object.GasPrice != null) - if ($util.Long) - (message.GasPrice = $util.Long.fromValue(object.GasPrice)).unsigned = true; - else if (typeof object.GasPrice === "string") - message.GasPrice = parseInt(object.GasPrice, 10); - else if (typeof object.GasPrice === "number") - message.GasPrice = object.GasPrice; - else if (typeof object.GasPrice === "object") - message.GasPrice = new $util.LongBits(object.GasPrice.low >>> 0, object.GasPrice.high >>> 0).toNumber(true); - if (object.GasLimit != null) - if ($util.Long) - (message.GasLimit = $util.Long.fromValue(object.GasLimit)).unsigned = true; - else if (typeof object.GasLimit === "string") - message.GasLimit = parseInt(object.GasLimit, 10); - else if (typeof object.GasLimit === "number") - message.GasLimit = object.GasLimit; - else if (typeof object.GasLimit === "object") - message.GasLimit = new $util.LongBits(object.GasLimit.low >>> 0, object.GasLimit.high >>> 0).toNumber(true); - if (object.Data != null) - if (typeof object.Data === "string") - $util.base64.decode(object.Data, message.Data = $util.newBuffer($util.base64.length(object.Data)), 0); - else if (object.Data.length) - message.Data = object.Data; - if (object.ChainID != null) - if (typeof object.ChainID === "string") - $util.base64.decode(object.ChainID, message.ChainID = $util.newBuffer($util.base64.length(object.ChainID)), 0); - else if (object.ChainID.length) - message.ChainID = object.ChainID; - if (object.Version != null) - message.Version = object.Version >>> 0; - if (object.Signature != null) - if (typeof object.Signature === "string") - $util.base64.decode(object.Signature, message.Signature = $util.newBuffer($util.base64.length(object.Signature)), 0); - else if (object.Signature.length) - message.Signature = object.Signature; - if (object.Options != null) - message.Options = object.Options >>> 0; - if (object.GuardAddr != null) - if (typeof object.GuardAddr === "string") - $util.base64.decode(object.GuardAddr, message.GuardAddr = $util.newBuffer($util.base64.length(object.GuardAddr)), 0); - else if (object.GuardAddr.length) - message.GuardAddr = object.GuardAddr; - if (object.GuardSignature != null) - if (typeof object.GuardSignature === "string") - $util.base64.decode(object.GuardSignature, message.GuardSignature = $util.newBuffer($util.base64.length(object.GuardSignature)), 0); - else if (object.GuardSignature.length) - message.GuardSignature = object.GuardSignature; - return message; - }; - - /** - * Creates a plain object from a Transaction message. Also converts values to other types if specified. - * @function toObject - * @memberof proto.Transaction - * @static - * @param {proto.Transaction} message Transaction - * @param {$protobuf.IConversionOptions} [options] Conversion options - * @returns {Object.} Plain object - */ - Transaction.toObject = function toObject(message, options) { - if (!options) - options = {}; - var object = {}; - if (options.defaults) { - if ($util.Long) { - var long = new $util.Long(0, 0, true); - object.Nonce = options.longs === String ? long.toString() : options.longs === Number ? long.toNumber() : long; - } else - object.Nonce = options.longs === String ? "0" : 0; - if (options.bytes === String) - object.Value = ""; - else { - object.Value = []; - if (options.bytes !== Array) - object.Value = $util.newBuffer(object.Value); - } - if (options.bytes === String) - object.RcvAddr = ""; - else { - object.RcvAddr = []; - if (options.bytes !== Array) - object.RcvAddr = $util.newBuffer(object.RcvAddr); - } - if (options.bytes === String) - object.RcvUserName = ""; - else { - object.RcvUserName = []; - if (options.bytes !== Array) - object.RcvUserName = $util.newBuffer(object.RcvUserName); - } - if (options.bytes === String) - object.SndAddr = ""; - else { - object.SndAddr = []; - if (options.bytes !== Array) - object.SndAddr = $util.newBuffer(object.SndAddr); + + /** + * Transaction Nonce. + * @member {number|Long} Nonce + * @memberof proto.Transaction + * @instance + */ + Transaction.prototype.Nonce = $util.Long ? $util.Long.fromBits(0,0,true) : 0; + + /** + * Transaction Value. + * @member {Uint8Array} Value + * @memberof proto.Transaction + * @instance + */ + Transaction.prototype.Value = $util.newBuffer([]); + + /** + * Transaction RcvAddr. + * @member {Uint8Array} RcvAddr + * @memberof proto.Transaction + * @instance + */ + Transaction.prototype.RcvAddr = $util.newBuffer([]); + + /** + * Transaction RcvUserName. + * @member {Uint8Array} RcvUserName + * @memberof proto.Transaction + * @instance + */ + Transaction.prototype.RcvUserName = $util.newBuffer([]); + + /** + * Transaction SndAddr. + * @member {Uint8Array} SndAddr + * @memberof proto.Transaction + * @instance + */ + Transaction.prototype.SndAddr = $util.newBuffer([]); + + /** + * Transaction SndUserName. + * @member {Uint8Array} SndUserName + * @memberof proto.Transaction + * @instance + */ + Transaction.prototype.SndUserName = $util.newBuffer([]); + + /** + * Transaction GasPrice. + * @member {number|Long} GasPrice + * @memberof proto.Transaction + * @instance + */ + Transaction.prototype.GasPrice = $util.Long ? $util.Long.fromBits(0,0,true) : 0; + + /** + * Transaction GasLimit. + * @member {number|Long} GasLimit + * @memberof proto.Transaction + * @instance + */ + Transaction.prototype.GasLimit = $util.Long ? $util.Long.fromBits(0,0,true) : 0; + + /** + * Transaction Data. + * @member {Uint8Array} Data + * @memberof proto.Transaction + * @instance + */ + Transaction.prototype.Data = $util.newBuffer([]); + + /** + * Transaction ChainID. + * @member {Uint8Array} ChainID + * @memberof proto.Transaction + * @instance + */ + Transaction.prototype.ChainID = $util.newBuffer([]); + + /** + * Transaction Version. + * @member {number} Version + * @memberof proto.Transaction + * @instance + */ + Transaction.prototype.Version = 0; + + /** + * Transaction Signature. + * @member {Uint8Array} Signature + * @memberof proto.Transaction + * @instance + */ + Transaction.prototype.Signature = $util.newBuffer([]); + + /** + * Transaction Options. + * @member {number} Options + * @memberof proto.Transaction + * @instance + */ + Transaction.prototype.Options = 0; + + /** + * Transaction GuardianAddr. + * @member {Uint8Array} GuardianAddr + * @memberof proto.Transaction + * @instance + */ + Transaction.prototype.GuardianAddr = $util.newBuffer([]); + + /** + * Transaction GuardianSignature. + * @member {Uint8Array} GuardianSignature + * @memberof proto.Transaction + * @instance + */ + Transaction.prototype.GuardianSignature = $util.newBuffer([]); + + /** + * Creates a new Transaction instance using the specified properties. + * @function create + * @memberof proto.Transaction + * @static + * @param {proto.ITransaction=} [properties] Properties to set + * @returns {proto.Transaction} Transaction instance + */ + Transaction.create = function create(properties) { + return new Transaction(properties); + }; + + /** + * Encodes the specified Transaction message. Does not implicitly {@link proto.Transaction.verify|verify} messages. + * @function encode + * @memberof proto.Transaction + * @static + * @param {proto.ITransaction} message Transaction message or plain object to encode + * @param {$protobuf.Writer} [writer] Writer to encode to + * @returns {$protobuf.Writer} Writer + */ + Transaction.encode = function encode(message, writer) { + if (!writer) + writer = $Writer.create(); + if (message.Nonce != null && Object.hasOwnProperty.call(message, "Nonce")) + writer.uint32(/* id 1, wireType 0 =*/8).uint64(message.Nonce); + if (message.Value != null && Object.hasOwnProperty.call(message, "Value")) + writer.uint32(/* id 2, wireType 2 =*/18).bytes(message.Value); + if (message.RcvAddr != null && Object.hasOwnProperty.call(message, "RcvAddr")) + writer.uint32(/* id 3, wireType 2 =*/26).bytes(message.RcvAddr); + if (message.RcvUserName != null && Object.hasOwnProperty.call(message, "RcvUserName")) + writer.uint32(/* id 4, wireType 2 =*/34).bytes(message.RcvUserName); + if (message.SndAddr != null && Object.hasOwnProperty.call(message, "SndAddr")) + writer.uint32(/* id 5, wireType 2 =*/42).bytes(message.SndAddr); + if (message.SndUserName != null && Object.hasOwnProperty.call(message, "SndUserName")) + writer.uint32(/* id 6, wireType 2 =*/50).bytes(message.SndUserName); + if (message.GasPrice != null && Object.hasOwnProperty.call(message, "GasPrice")) + writer.uint32(/* id 7, wireType 0 =*/56).uint64(message.GasPrice); + if (message.GasLimit != null && Object.hasOwnProperty.call(message, "GasLimit")) + writer.uint32(/* id 8, wireType 0 =*/64).uint64(message.GasLimit); + if (message.Data != null && Object.hasOwnProperty.call(message, "Data")) + writer.uint32(/* id 9, wireType 2 =*/74).bytes(message.Data); + if (message.ChainID != null && Object.hasOwnProperty.call(message, "ChainID")) + writer.uint32(/* id 10, wireType 2 =*/82).bytes(message.ChainID); + if (message.Version != null && Object.hasOwnProperty.call(message, "Version")) + writer.uint32(/* id 11, wireType 0 =*/88).uint32(message.Version); + if (message.Signature != null && Object.hasOwnProperty.call(message, "Signature")) + writer.uint32(/* id 12, wireType 2 =*/98).bytes(message.Signature); + if (message.Options != null && Object.hasOwnProperty.call(message, "Options")) + writer.uint32(/* id 13, wireType 0 =*/104).uint32(message.Options); + if (message.GuardianAddr != null && Object.hasOwnProperty.call(message, "GuardianAddr")) + writer.uint32(/* id 14, wireType 2 =*/114).bytes(message.GuardianAddr); + if (message.GuardianSignature != null && Object.hasOwnProperty.call(message, "GuardianSignature")) + writer.uint32(/* id 15, wireType 2 =*/122).bytes(message.GuardianSignature); + return writer; + }; + + /** + * Encodes the specified Transaction message, length delimited. Does not implicitly {@link proto.Transaction.verify|verify} messages. + * @function encodeDelimited + * @memberof proto.Transaction + * @static + * @param {proto.ITransaction} message Transaction message or plain object to encode + * @param {$protobuf.Writer} [writer] Writer to encode to + * @returns {$protobuf.Writer} Writer + */ + Transaction.encodeDelimited = function encodeDelimited(message, writer) { + return this.encode(message, writer).ldelim(); + }; + + /** + * Decodes a Transaction message from the specified reader or buffer. + * @function decode + * @memberof proto.Transaction + * @static + * @param {$protobuf.Reader|Uint8Array} reader Reader or buffer to decode from + * @param {number} [length] Message length if known beforehand + * @returns {proto.Transaction} Transaction + * @throws {Error} If the payload is not a reader or valid buffer + * @throws {$protobuf.util.ProtocolError} If required fields are missing + */ + Transaction.decode = function decode(reader, length) { + if (!(reader instanceof $Reader)) + reader = $Reader.create(reader); + var end = length === undefined ? reader.len : reader.pos + length, message = new $root.proto.Transaction(); + while (reader.pos < end) { + var tag = reader.uint32(); + switch (tag >>> 3) { + case 1: { + message.Nonce = reader.uint64(); + break; + } + case 2: { + message.Value = reader.bytes(); + break; + } + case 3: { + message.RcvAddr = reader.bytes(); + break; + } + case 4: { + message.RcvUserName = reader.bytes(); + break; + } + case 5: { + message.SndAddr = reader.bytes(); + break; + } + case 6: { + message.SndUserName = reader.bytes(); + break; + } + case 7: { + message.GasPrice = reader.uint64(); + break; + } + case 8: { + message.GasLimit = reader.uint64(); + break; + } + case 9: { + message.Data = reader.bytes(); + break; + } + case 10: { + message.ChainID = reader.bytes(); + break; + } + case 11: { + message.Version = reader.uint32(); + break; + } + case 12: { + message.Signature = reader.bytes(); + break; + } + case 13: { + message.Options = reader.uint32(); + break; + } + case 14: { + message.GuardianAddr = reader.bytes(); + break; + } + case 15: { + message.GuardianSignature = reader.bytes(); + break; + } + default: + reader.skipType(tag & 7); + break; + } } - if (options.bytes === String) - object.SndUserName = ""; - else { - object.SndUserName = []; - if (options.bytes !== Array) - object.SndUserName = $util.newBuffer(object.SndUserName); + return message; + }; + + /** + * Decodes a Transaction message from the specified reader or buffer, length delimited. + * @function decodeDelimited + * @memberof proto.Transaction + * @static + * @param {$protobuf.Reader|Uint8Array} reader Reader or buffer to decode from + * @returns {proto.Transaction} Transaction + * @throws {Error} If the payload is not a reader or valid buffer + * @throws {$protobuf.util.ProtocolError} If required fields are missing + */ + Transaction.decodeDelimited = function decodeDelimited(reader) { + if (!(reader instanceof $Reader)) + reader = new $Reader(reader); + return this.decode(reader, reader.uint32()); + }; + + /** + * Verifies a Transaction message. + * @function verify + * @memberof proto.Transaction + * @static + * @param {Object.} message Plain object to verify + * @returns {string|null} `null` if valid, otherwise the reason why it is not + */ + Transaction.verify = function verify(message) { + if (typeof message !== "object" || message === null) + return "object expected"; + if (message.Nonce != null && message.hasOwnProperty("Nonce")) + if (!$util.isInteger(message.Nonce) && !(message.Nonce && $util.isInteger(message.Nonce.low) && $util.isInteger(message.Nonce.high))) + return "Nonce: integer|Long expected"; + if (message.Value != null && message.hasOwnProperty("Value")) + if (!(message.Value && typeof message.Value.length === "number" || $util.isString(message.Value))) + return "Value: buffer expected"; + if (message.RcvAddr != null && message.hasOwnProperty("RcvAddr")) + if (!(message.RcvAddr && typeof message.RcvAddr.length === "number" || $util.isString(message.RcvAddr))) + return "RcvAddr: buffer expected"; + if (message.RcvUserName != null && message.hasOwnProperty("RcvUserName")) + if (!(message.RcvUserName && typeof message.RcvUserName.length === "number" || $util.isString(message.RcvUserName))) + return "RcvUserName: buffer expected"; + if (message.SndAddr != null && message.hasOwnProperty("SndAddr")) + if (!(message.SndAddr && typeof message.SndAddr.length === "number" || $util.isString(message.SndAddr))) + return "SndAddr: buffer expected"; + if (message.SndUserName != null && message.hasOwnProperty("SndUserName")) + if (!(message.SndUserName && typeof message.SndUserName.length === "number" || $util.isString(message.SndUserName))) + return "SndUserName: buffer expected"; + if (message.GasPrice != null && message.hasOwnProperty("GasPrice")) + if (!$util.isInteger(message.GasPrice) && !(message.GasPrice && $util.isInteger(message.GasPrice.low) && $util.isInteger(message.GasPrice.high))) + return "GasPrice: integer|Long expected"; + if (message.GasLimit != null && message.hasOwnProperty("GasLimit")) + if (!$util.isInteger(message.GasLimit) && !(message.GasLimit && $util.isInteger(message.GasLimit.low) && $util.isInteger(message.GasLimit.high))) + return "GasLimit: integer|Long expected"; + if (message.Data != null && message.hasOwnProperty("Data")) + if (!(message.Data && typeof message.Data.length === "number" || $util.isString(message.Data))) + return "Data: buffer expected"; + if (message.ChainID != null && message.hasOwnProperty("ChainID")) + if (!(message.ChainID && typeof message.ChainID.length === "number" || $util.isString(message.ChainID))) + return "ChainID: buffer expected"; + if (message.Version != null && message.hasOwnProperty("Version")) + if (!$util.isInteger(message.Version)) + return "Version: integer expected"; + if (message.Signature != null && message.hasOwnProperty("Signature")) + if (!(message.Signature && typeof message.Signature.length === "number" || $util.isString(message.Signature))) + return "Signature: buffer expected"; + if (message.Options != null && message.hasOwnProperty("Options")) + if (!$util.isInteger(message.Options)) + return "Options: integer expected"; + if (message.GuardianAddr != null && message.hasOwnProperty("GuardianAddr")) + if (!(message.GuardianAddr && typeof message.GuardianAddr.length === "number" || $util.isString(message.GuardianAddr))) + return "GuardianAddr: buffer expected"; + if (message.GuardianSignature != null && message.hasOwnProperty("GuardianSignature")) + if (!(message.GuardianSignature && typeof message.GuardianSignature.length === "number" || $util.isString(message.GuardianSignature))) + return "GuardianSignature: buffer expected"; + return null; + }; + + /** + * Creates a Transaction message from a plain object. Also converts values to their respective internal types. + * @function fromObject + * @memberof proto.Transaction + * @static + * @param {Object.} object Plain object + * @returns {proto.Transaction} Transaction + */ + Transaction.fromObject = function fromObject(object) { + if (object instanceof $root.proto.Transaction) + return object; + var message = new $root.proto.Transaction(); + if (object.Nonce != null) + if ($util.Long) + (message.Nonce = $util.Long.fromValue(object.Nonce)).unsigned = true; + else if (typeof object.Nonce === "string") + message.Nonce = parseInt(object.Nonce, 10); + else if (typeof object.Nonce === "number") + message.Nonce = object.Nonce; + else if (typeof object.Nonce === "object") + message.Nonce = new $util.LongBits(object.Nonce.low >>> 0, object.Nonce.high >>> 0).toNumber(true); + if (object.Value != null) + if (typeof object.Value === "string") + $util.base64.decode(object.Value, message.Value = $util.newBuffer($util.base64.length(object.Value)), 0); + else if (object.Value.length >= 0) + message.Value = object.Value; + if (object.RcvAddr != null) + if (typeof object.RcvAddr === "string") + $util.base64.decode(object.RcvAddr, message.RcvAddr = $util.newBuffer($util.base64.length(object.RcvAddr)), 0); + else if (object.RcvAddr.length >= 0) + message.RcvAddr = object.RcvAddr; + if (object.RcvUserName != null) + if (typeof object.RcvUserName === "string") + $util.base64.decode(object.RcvUserName, message.RcvUserName = $util.newBuffer($util.base64.length(object.RcvUserName)), 0); + else if (object.RcvUserName.length >= 0) + message.RcvUserName = object.RcvUserName; + if (object.SndAddr != null) + if (typeof object.SndAddr === "string") + $util.base64.decode(object.SndAddr, message.SndAddr = $util.newBuffer($util.base64.length(object.SndAddr)), 0); + else if (object.SndAddr.length >= 0) + message.SndAddr = object.SndAddr; + if (object.SndUserName != null) + if (typeof object.SndUserName === "string") + $util.base64.decode(object.SndUserName, message.SndUserName = $util.newBuffer($util.base64.length(object.SndUserName)), 0); + else if (object.SndUserName.length >= 0) + message.SndUserName = object.SndUserName; + if (object.GasPrice != null) + if ($util.Long) + (message.GasPrice = $util.Long.fromValue(object.GasPrice)).unsigned = true; + else if (typeof object.GasPrice === "string") + message.GasPrice = parseInt(object.GasPrice, 10); + else if (typeof object.GasPrice === "number") + message.GasPrice = object.GasPrice; + else if (typeof object.GasPrice === "object") + message.GasPrice = new $util.LongBits(object.GasPrice.low >>> 0, object.GasPrice.high >>> 0).toNumber(true); + if (object.GasLimit != null) + if ($util.Long) + (message.GasLimit = $util.Long.fromValue(object.GasLimit)).unsigned = true; + else if (typeof object.GasLimit === "string") + message.GasLimit = parseInt(object.GasLimit, 10); + else if (typeof object.GasLimit === "number") + message.GasLimit = object.GasLimit; + else if (typeof object.GasLimit === "object") + message.GasLimit = new $util.LongBits(object.GasLimit.low >>> 0, object.GasLimit.high >>> 0).toNumber(true); + if (object.Data != null) + if (typeof object.Data === "string") + $util.base64.decode(object.Data, message.Data = $util.newBuffer($util.base64.length(object.Data)), 0); + else if (object.Data.length >= 0) + message.Data = object.Data; + if (object.ChainID != null) + if (typeof object.ChainID === "string") + $util.base64.decode(object.ChainID, message.ChainID = $util.newBuffer($util.base64.length(object.ChainID)), 0); + else if (object.ChainID.length >= 0) + message.ChainID = object.ChainID; + if (object.Version != null) + message.Version = object.Version >>> 0; + if (object.Signature != null) + if (typeof object.Signature === "string") + $util.base64.decode(object.Signature, message.Signature = $util.newBuffer($util.base64.length(object.Signature)), 0); + else if (object.Signature.length >= 0) + message.Signature = object.Signature; + if (object.Options != null) + message.Options = object.Options >>> 0; + if (object.GuardianAddr != null) + if (typeof object.GuardianAddr === "string") + $util.base64.decode(object.GuardianAddr, message.GuardianAddr = $util.newBuffer($util.base64.length(object.GuardianAddr)), 0); + else if (object.GuardianAddr.length >= 0) + message.GuardianAddr = object.GuardianAddr; + if (object.GuardianSignature != null) + if (typeof object.GuardianSignature === "string") + $util.base64.decode(object.GuardianSignature, message.GuardianSignature = $util.newBuffer($util.base64.length(object.GuardianSignature)), 0); + else if (object.GuardianSignature.length >= 0) + message.GuardianSignature = object.GuardianSignature; + return message; + }; + + /** + * Creates a plain object from a Transaction message. Also converts values to other types if specified. + * @function toObject + * @memberof proto.Transaction + * @static + * @param {proto.Transaction} message Transaction + * @param {$protobuf.IConversionOptions} [options] Conversion options + * @returns {Object.} Plain object + */ + Transaction.toObject = function toObject(message, options) { + if (!options) + options = {}; + var object = {}; + if (options.defaults) { + if ($util.Long) { + var long = new $util.Long(0, 0, true); + object.Nonce = options.longs === String ? long.toString() : options.longs === Number ? long.toNumber() : long; + } else + object.Nonce = options.longs === String ? "0" : 0; + if (options.bytes === String) + object.Value = ""; + else { + object.Value = []; + if (options.bytes !== Array) + object.Value = $util.newBuffer(object.Value); + } + if (options.bytes === String) + object.RcvAddr = ""; + else { + object.RcvAddr = []; + if (options.bytes !== Array) + object.RcvAddr = $util.newBuffer(object.RcvAddr); + } + if (options.bytes === String) + object.RcvUserName = ""; + else { + object.RcvUserName = []; + if (options.bytes !== Array) + object.RcvUserName = $util.newBuffer(object.RcvUserName); + } + if (options.bytes === String) + object.SndAddr = ""; + else { + object.SndAddr = []; + if (options.bytes !== Array) + object.SndAddr = $util.newBuffer(object.SndAddr); + } + if (options.bytes === String) + object.SndUserName = ""; + else { + object.SndUserName = []; + if (options.bytes !== Array) + object.SndUserName = $util.newBuffer(object.SndUserName); + } + if ($util.Long) { + var long = new $util.Long(0, 0, true); + object.GasPrice = options.longs === String ? long.toString() : options.longs === Number ? long.toNumber() : long; + } else + object.GasPrice = options.longs === String ? "0" : 0; + if ($util.Long) { + var long = new $util.Long(0, 0, true); + object.GasLimit = options.longs === String ? long.toString() : options.longs === Number ? long.toNumber() : long; + } else + object.GasLimit = options.longs === String ? "0" : 0; + if (options.bytes === String) + object.Data = ""; + else { + object.Data = []; + if (options.bytes !== Array) + object.Data = $util.newBuffer(object.Data); + } + if (options.bytes === String) + object.ChainID = ""; + else { + object.ChainID = []; + if (options.bytes !== Array) + object.ChainID = $util.newBuffer(object.ChainID); + } + object.Version = 0; + if (options.bytes === String) + object.Signature = ""; + else { + object.Signature = []; + if (options.bytes !== Array) + object.Signature = $util.newBuffer(object.Signature); + } + object.Options = 0; + if (options.bytes === String) + object.GuardianAddr = ""; + else { + object.GuardianAddr = []; + if (options.bytes !== Array) + object.GuardianAddr = $util.newBuffer(object.GuardianAddr); + } + if (options.bytes === String) + object.GuardianSignature = ""; + else { + object.GuardianSignature = []; + if (options.bytes !== Array) + object.GuardianSignature = $util.newBuffer(object.GuardianSignature); + } } - if ($util.Long) { - var long = new $util.Long(0, 0, true); - object.GasPrice = options.longs === String ? long.toString() : options.longs === Number ? long.toNumber() : long; - } else - object.GasPrice = options.longs === String ? "0" : 0; - if ($util.Long) { - var long = new $util.Long(0, 0, true); - object.GasLimit = options.longs === String ? long.toString() : options.longs === Number ? long.toNumber() : long; - } else - object.GasLimit = options.longs === String ? "0" : 0; - if (options.bytes === String) - object.Data = ""; - else { - object.Data = []; - if (options.bytes !== Array) - object.Data = $util.newBuffer(object.Data); - } - if (options.bytes === String) - object.ChainID = ""; - else { - object.ChainID = []; - if (options.bytes !== Array) - object.ChainID = $util.newBuffer(object.ChainID); - } - object.Version = 0; - if (options.bytes === String) - object.Signature = ""; - else { - object.Signature = []; - if (options.bytes !== Array) - object.Signature = $util.newBuffer(object.Signature); - } - object.Options = 0; - if (options.bytes === String) - object.GuardAddr = ""; - else { - object.GuardAddr = []; - if (options.bytes !== Array) - object.GuardAddr = $util.newBuffer(object.GuardAddr); - } - if (options.bytes === String) - object.GuardSignature = ""; - else { - object.GuardSignature = []; - if (options.bytes !== Array) - object.GuardSignature = $util.newBuffer(object.GuardSignature); + if (message.Nonce != null && message.hasOwnProperty("Nonce")) + if (typeof message.Nonce === "number") + object.Nonce = options.longs === String ? String(message.Nonce) : message.Nonce; + else + object.Nonce = options.longs === String ? $util.Long.prototype.toString.call(message.Nonce) : options.longs === Number ? new $util.LongBits(message.Nonce.low >>> 0, message.Nonce.high >>> 0).toNumber(true) : message.Nonce; + if (message.Value != null && message.hasOwnProperty("Value")) + object.Value = options.bytes === String ? $util.base64.encode(message.Value, 0, message.Value.length) : options.bytes === Array ? Array.prototype.slice.call(message.Value) : message.Value; + if (message.RcvAddr != null && message.hasOwnProperty("RcvAddr")) + object.RcvAddr = options.bytes === String ? $util.base64.encode(message.RcvAddr, 0, message.RcvAddr.length) : options.bytes === Array ? Array.prototype.slice.call(message.RcvAddr) : message.RcvAddr; + if (message.RcvUserName != null && message.hasOwnProperty("RcvUserName")) + object.RcvUserName = options.bytes === String ? $util.base64.encode(message.RcvUserName, 0, message.RcvUserName.length) : options.bytes === Array ? Array.prototype.slice.call(message.RcvUserName) : message.RcvUserName; + if (message.SndAddr != null && message.hasOwnProperty("SndAddr")) + object.SndAddr = options.bytes === String ? $util.base64.encode(message.SndAddr, 0, message.SndAddr.length) : options.bytes === Array ? Array.prototype.slice.call(message.SndAddr) : message.SndAddr; + if (message.SndUserName != null && message.hasOwnProperty("SndUserName")) + object.SndUserName = options.bytes === String ? $util.base64.encode(message.SndUserName, 0, message.SndUserName.length) : options.bytes === Array ? Array.prototype.slice.call(message.SndUserName) : message.SndUserName; + if (message.GasPrice != null && message.hasOwnProperty("GasPrice")) + if (typeof message.GasPrice === "number") + object.GasPrice = options.longs === String ? String(message.GasPrice) : message.GasPrice; + else + object.GasPrice = options.longs === String ? $util.Long.prototype.toString.call(message.GasPrice) : options.longs === Number ? new $util.LongBits(message.GasPrice.low >>> 0, message.GasPrice.high >>> 0).toNumber(true) : message.GasPrice; + if (message.GasLimit != null && message.hasOwnProperty("GasLimit")) + if (typeof message.GasLimit === "number") + object.GasLimit = options.longs === String ? String(message.GasLimit) : message.GasLimit; + else + object.GasLimit = options.longs === String ? $util.Long.prototype.toString.call(message.GasLimit) : options.longs === Number ? new $util.LongBits(message.GasLimit.low >>> 0, message.GasLimit.high >>> 0).toNumber(true) : message.GasLimit; + if (message.Data != null && message.hasOwnProperty("Data")) + object.Data = options.bytes === String ? $util.base64.encode(message.Data, 0, message.Data.length) : options.bytes === Array ? Array.prototype.slice.call(message.Data) : message.Data; + if (message.ChainID != null && message.hasOwnProperty("ChainID")) + object.ChainID = options.bytes === String ? $util.base64.encode(message.ChainID, 0, message.ChainID.length) : options.bytes === Array ? Array.prototype.slice.call(message.ChainID) : message.ChainID; + if (message.Version != null && message.hasOwnProperty("Version")) + object.Version = message.Version; + if (message.Signature != null && message.hasOwnProperty("Signature")) + object.Signature = options.bytes === String ? $util.base64.encode(message.Signature, 0, message.Signature.length) : options.bytes === Array ? Array.prototype.slice.call(message.Signature) : message.Signature; + if (message.Options != null && message.hasOwnProperty("Options")) + object.Options = message.Options; + if (message.GuardianAddr != null && message.hasOwnProperty("GuardianAddr")) + object.GuardianAddr = options.bytes === String ? $util.base64.encode(message.GuardianAddr, 0, message.GuardianAddr.length) : options.bytes === Array ? Array.prototype.slice.call(message.GuardianAddr) : message.GuardianAddr; + if (message.GuardianSignature != null && message.hasOwnProperty("GuardianSignature")) + object.GuardianSignature = options.bytes === String ? $util.base64.encode(message.GuardianSignature, 0, message.GuardianSignature.length) : options.bytes === Array ? Array.prototype.slice.call(message.GuardianSignature) : message.GuardianSignature; + return object; + }; + + /** + * Converts this Transaction to JSON. + * @function toJSON + * @memberof proto.Transaction + * @instance + * @returns {Object.} JSON object + */ + Transaction.prototype.toJSON = function toJSON() { + return this.constructor.toObject(this, $protobuf.util.toJSONOptions); + }; + + /** + * Gets the default type url for Transaction + * @function getTypeUrl + * @memberof proto.Transaction + * @static + * @param {string} [typeUrlPrefix] your custom typeUrlPrefix(default "type.googleapis.com") + * @returns {string} The default type url + */ + Transaction.getTypeUrl = function getTypeUrl(typeUrlPrefix) { + if (typeUrlPrefix === undefined) { + typeUrlPrefix = "type.googleapis.com"; } - } - if (message.Nonce != null && message.hasOwnProperty("Nonce")) - if (typeof message.Nonce === "number") - object.Nonce = options.longs === String ? String(message.Nonce) : message.Nonce; - else - object.Nonce = options.longs === String ? $util.Long.prototype.toString.call(message.Nonce) : options.longs === Number ? new $util.LongBits(message.Nonce.low >>> 0, message.Nonce.high >>> 0).toNumber(true) : message.Nonce; - if (message.Value != null && message.hasOwnProperty("Value")) - object.Value = options.bytes === String ? $util.base64.encode(message.Value, 0, message.Value.length) : options.bytes === Array ? Array.prototype.slice.call(message.Value) : message.Value; - if (message.RcvAddr != null && message.hasOwnProperty("RcvAddr")) - object.RcvAddr = options.bytes === String ? $util.base64.encode(message.RcvAddr, 0, message.RcvAddr.length) : options.bytes === Array ? Array.prototype.slice.call(message.RcvAddr) : message.RcvAddr; - if (message.RcvUserName != null && message.hasOwnProperty("RcvUserName")) - object.RcvUserName = options.bytes === String ? $util.base64.encode(message.RcvUserName, 0, message.RcvUserName.length) : options.bytes === Array ? Array.prototype.slice.call(message.RcvUserName) : message.RcvUserName; - if (message.SndAddr != null && message.hasOwnProperty("SndAddr")) - object.SndAddr = options.bytes === String ? $util.base64.encode(message.SndAddr, 0, message.SndAddr.length) : options.bytes === Array ? Array.prototype.slice.call(message.SndAddr) : message.SndAddr; - if (message.SndUserName != null && message.hasOwnProperty("SndUserName")) - object.SndUserName = options.bytes === String ? $util.base64.encode(message.SndUserName, 0, message.SndUserName.length) : options.bytes === Array ? Array.prototype.slice.call(message.SndUserName) : message.SndUserName; - if (message.GasPrice != null && message.hasOwnProperty("GasPrice")) - if (typeof message.GasPrice === "number") - object.GasPrice = options.longs === String ? String(message.GasPrice) : message.GasPrice; - else - object.GasPrice = options.longs === String ? $util.Long.prototype.toString.call(message.GasPrice) : options.longs === Number ? new $util.LongBits(message.GasPrice.low >>> 0, message.GasPrice.high >>> 0).toNumber(true) : message.GasPrice; - if (message.GasLimit != null && message.hasOwnProperty("GasLimit")) - if (typeof message.GasLimit === "number") - object.GasLimit = options.longs === String ? String(message.GasLimit) : message.GasLimit; - else - object.GasLimit = options.longs === String ? $util.Long.prototype.toString.call(message.GasLimit) : options.longs === Number ? new $util.LongBits(message.GasLimit.low >>> 0, message.GasLimit.high >>> 0).toNumber(true) : message.GasLimit; - if (message.Data != null && message.hasOwnProperty("Data")) - object.Data = options.bytes === String ? $util.base64.encode(message.Data, 0, message.Data.length) : options.bytes === Array ? Array.prototype.slice.call(message.Data) : message.Data; - if (message.ChainID != null && message.hasOwnProperty("ChainID")) - object.ChainID = options.bytes === String ? $util.base64.encode(message.ChainID, 0, message.ChainID.length) : options.bytes === Array ? Array.prototype.slice.call(message.ChainID) : message.ChainID; - if (message.Version != null && message.hasOwnProperty("Version")) - object.Version = message.Version; - if (message.Signature != null && message.hasOwnProperty("Signature")) - object.Signature = options.bytes === String ? $util.base64.encode(message.Signature, 0, message.Signature.length) : options.bytes === Array ? Array.prototype.slice.call(message.Signature) : message.Signature; - if (message.Options != null && message.hasOwnProperty("Options")) - object.Options = message.Options; - if (message.GuardAddr != null && message.hasOwnProperty("GuardAddr")) - object.GuardAddr = options.bytes === String ? $util.base64.encode(message.GuardAddr, 0, message.GuardAddr.length) : options.bytes === Array ? Array.prototype.slice.call(message.GuardAddr) : message.GuardAddr; - if (message.GuardSignature != null && message.hasOwnProperty("GuardSignature")) - object.GuardSignature = options.bytes === String ? $util.base64.encode(message.GuardSignature, 0, message.GuardSignature.length) : options.bytes === Array ? Array.prototype.slice.call(message.GuardSignature) : message.GuardSignature; - return object; - }; - - /** - * Converts this Transaction to JSON. - * @function toJSON - * @memberof proto.Transaction - * @instance - * @returns {Object.} JSON object - */ - Transaction.prototype.toJSON = function toJSON() { - return this.constructor.toObject(this, $protobuf.util.toJSONOptions); - }; - - return Transaction; + return typeUrlPrefix + "/proto.Transaction"; + }; + + return Transaction; + })(); + + return proto; })(); - return proto; -})(); - -module.exports = $root; + return $root; +}); diff --git a/src/proto/serializer.spec.ts b/src/proto/serializer.spec.ts index f6da42c58..f92acf0ed 100644 --- a/src/proto/serializer.spec.ts +++ b/src/proto/serializer.spec.ts @@ -3,7 +3,7 @@ import { Address } from "../address"; import { TransactionVersion } from "../networkParams"; import { Signature } from "../signature"; import { loadTestWallets, TestWallet } from "../testutils"; -import { TokenTransfer } from "../tokenTransfer"; +import { TokenTransfer } from "../tokens"; import { Transaction } from "../transaction"; import { TransactionPayload } from "../transactionPayload"; import { ProtoSerializer } from "./serializer"; @@ -26,10 +26,11 @@ describe("serialize transactions", () => { chainID: "local-testnet" }); - transaction.applySignature(new Signature("b56769014f2bdc5cf9fc4a05356807d71fcf8775c819b0f1b0964625b679c918ffa64862313bfef86f99b38cb84fcdb16fa33ad6eb565276616723405cd8f109")); + const signer = wallets.alice.signer; + transaction.applySignature(await signer.sign(transaction.serializeForSigning())); let buffer = serializer.serializeTransaction(transaction); - assert.equal(buffer.toString("hex"), "0859120200001a208049d639e5a6980d1cd2392abcce41029cda74a1563523a202f09641cc2618f82a200139472eff6886771a982f3083da5d421f24c29181e63888228dc81ca60d69e1388094ebdc0340d08603520d6c6f63616c2d746573746e657458016240b56769014f2bdc5cf9fc4a05356807d71fcf8775c819b0f1b0964625b679c918ffa64862313bfef86f99b38cb84fcdb16fa33ad6eb565276616723405cd8f109"); + assert.equal(buffer.toString("hex"), "0859120200001a208049d639e5a6980d1cd2392abcce41029cda74a1563523a202f09641cc2618f82a200139472eff6886771a982f3083da5d421f24c29181e63888228dc81ca60d69e1388094ebdc0340d08603520d6c6f63616c2d746573746e6574580262403f08a1dd64fbb627d10b048e0b45b1390f29bb0e457762a2ccb710b029f299022a67a4b8e45cf62f4314afec2e56b5574c71e38df96cc41fae757b7ee5062503"); }); it("with data, no value", async () => { @@ -43,10 +44,11 @@ describe("serialize transactions", () => { chainID: "local-testnet" }); - transaction.applySignature(new Signature("e47fd437fc17ac9a69f7bf5f85bafa9e7628d851c4f69bd9fedc7e36029708b2e6d168d5cd652ea78beedd06d4440974ca46c403b14071a1a148d4188f6f2c0d")); + const signer = wallets.alice.signer; + transaction.applySignature(await signer.sign(transaction.serializeForSigning())); let buffer = serializer.serializeTransaction(transaction); - assert.equal(buffer.toString("hex"), "085a120200001a208049d639e5a6980d1cd2392abcce41029cda74a1563523a202f09641cc2618f82a200139472eff6886771a982f3083da5d421f24c29181e63888228dc81ca60d69e1388094ebdc034080f1044a0568656c6c6f520d6c6f63616c2d746573746e657458016240e47fd437fc17ac9a69f7bf5f85bafa9e7628d851c4f69bd9fedc7e36029708b2e6d168d5cd652ea78beedd06d4440974ca46c403b14071a1a148d4188f6f2c0d"); + assert.equal(buffer.toString("hex"), "085a120200001a208049d639e5a6980d1cd2392abcce41029cda74a1563523a202f09641cc2618f82a200139472eff6886771a982f3083da5d421f24c29181e63888228dc81ca60d69e1388094ebdc034080f1044a0568656c6c6f520d6c6f63616c2d746573746e657458026240f9e8c1caf7f36b99e7e76ee1118bf71b55cde11a2356e2b3adf15f4ad711d2e1982469cbba7eb0afbf74e8a8f78e549b9410cd86eeaa88fcba62611ac9f6e30e"); }); it("with data, with value", async () => { @@ -60,10 +62,11 @@ describe("serialize transactions", () => { chainID: "local-testnet" }); - transaction.applySignature(new Signature("9074789e0b4f9b2ac24b1fd351a4dd840afcfeb427b0f93e2a2d429c28c65ee9f4c288ca4dbde79de0e5bcf8c1a5d26e1b1c86203faea923e0edefb0b5099b0c")); + const signer = wallets.alice.signer; + transaction.applySignature(await signer.sign(transaction.serializeForSigning())); let buffer = serializer.serializeTransaction(transaction); - assert.equal(buffer.toString("hex"), "085b1209008ac7230489e800001a208049d639e5a6980d1cd2392abcce41029cda74a1563523a202f09641cc2618f82a200139472eff6886771a982f3083da5d421f24c29181e63888228dc81ca60d69e1388094ebdc0340a08d064a0c666f722074686520626f6f6b520d6c6f63616c2d746573746e6574580162409074789e0b4f9b2ac24b1fd351a4dd840afcfeb427b0f93e2a2d429c28c65ee9f4c288ca4dbde79de0e5bcf8c1a5d26e1b1c86203faea923e0edefb0b5099b0c"); + assert.equal(buffer.toString("hex"), "085b1209008ac7230489e800001a208049d639e5a6980d1cd2392abcce41029cda74a1563523a202f09641cc2618f82a200139472eff6886771a982f3083da5d421f24c29181e63888228dc81ca60d69e1388094ebdc0340a08d064a0c666f722074686520626f6f6b520d6c6f63616c2d746573746e657458026240b45f22e9f57a6df22670fcc3566723a0711a05ac2547456de59fd222a54940e4a1d99bd414897ccbf5c02a842ad86e638989b7f4d30edd26c99a8cd1eb092304"); }); it("with data, with large value", async () => { @@ -77,10 +80,11 @@ describe("serialize transactions", () => { chainID: "local-testnet" }); - transaction.applySignature(new Signature("39938d15812708475dfc8125b5d41dbcea0b2e3e7aabbbfceb6ce4f070de3033676a218b73facd88b1432d7d4accab89c6130b3abe5cc7bbbb5146e61d355b03")); + const signer = wallets.alice.signer; + transaction.applySignature(await signer.sign(transaction.serializeForSigning())); let buffer = serializer.serializeTransaction(transaction); - assert.equal(buffer.toString("hex"), "085c120e00018ee90ff6181f3761632000001a208049d639e5a6980d1cd2392abcce41029cda74a1563523a202f09641cc2618f82a200139472eff6886771a982f3083da5d421f24c29181e63888228dc81ca60d69e1388094ebdc0340a08d064a11666f722074686520737061636573686970520d6c6f63616c2d746573746e65745801624039938d15812708475dfc8125b5d41dbcea0b2e3e7aabbbfceb6ce4f070de3033676a218b73facd88b1432d7d4accab89c6130b3abe5cc7bbbb5146e61d355b03"); + assert.equal(buffer.toString("hex"), "085c120e00018ee90ff6181f3761632000001a208049d639e5a6980d1cd2392abcce41029cda74a1563523a202f09641cc2618f82a200139472eff6886771a982f3083da5d421f24c29181e63888228dc81ca60d69e1388094ebdc0340a08d064a11666f722074686520737061636573686970520d6c6f63616c2d746573746e65745802624001f05aa8cb0614e12a94ab9dcbde5e78370a4e05d23ef25a1fb9d5fcf1cb3b1f33b919cd8dafb1704efb18fa233a8aa0d3344fb6ee9b613a7d7a403786ffbd0a"); }); it("with nonce = 0", async () => { @@ -113,9 +117,10 @@ describe("serialize transactions", () => { chainID: "T" }); - transaction.applySignature(new Signature("5966dd6b98fc5ecbcd203fa38fac7059ba5c17683099071883b0ad6697386769321d851388a99cb8b81aab625aa2d7e13621432dbd8ab334c5891cd7c7755200")) + const signer = wallets.carol.signer; + transaction.applySignature(await signer.sign(transaction.serializeForSigning())); const buffer = serializer.serializeTransaction(transaction); - assert.equal(buffer.toString("hex"), "08cc011209000de0b6b3a76400001a200139472eff6886771a982f3083da5d421f24c29181e63888228dc81ca60d69e12205616c6963652a20b2a11555ce521e4944e09ab17549d85b487dcd26c84b5017a39e31a3670889ba32056361726f6c388094ebdc0340d08603520154580162405966dd6b98fc5ecbcd203fa38fac7059ba5c17683099071883b0ad6697386769321d851388a99cb8b81aab625aa2d7e13621432dbd8ab334c5891cd7c7755200"); + assert.equal(buffer.toString("hex"), "08cc011209000de0b6b3a76400001a200139472eff6886771a982f3083da5d421f24c29181e63888228dc81ca60d69e12205616c6963652a20b2a11555ce521e4944e09ab17549d85b487dcd26c84b5017a39e31a3670889ba32056361726f6c388094ebdc0340d086035201545802624051e6cd78fb3ab4b53ff7ad6864df27cb4a56d70603332869d47a5cf6ea977c30e696103e41e8dddf2582996ad335229fdf4acb726564dbc1a0bc9e705b511f06"); }); }); diff --git a/src/proto/serializer.ts b/src/proto/serializer.ts index 9868041fc..9e1565b07 100644 --- a/src/proto/serializer.ts +++ b/src/proto/serializer.ts @@ -42,8 +42,8 @@ export class ProtoSerializer { if (transaction.isGuardedTransaction()) { const guardianAddress = transaction.getGuardian(); - protoTransaction.GuardAddr = new Address(guardianAddress.bech32()).pubkey(); - protoTransaction.GuardSignature = transaction.getGuardianSignature(); + protoTransaction.GuardianAddr = new Address(guardianAddress.bech32()).pubkey(); + protoTransaction.GuardianSignature = transaction.getGuardianSignature(); } const encoded = proto.Transaction.encode(protoTransaction).finish(); diff --git a/src/proto/transaction.proto b/src/proto/transaction.proto index ed6674c89..c3e46e993 100644 --- a/src/proto/transaction.proto +++ b/src/proto/transaction.proto @@ -8,26 +8,21 @@ syntax = "proto3"; package proto; -option go_package = "transaction"; -option (gogoproto.stable_marshaler_all) = true; - -import "github.com/gogo/protobuf/gogoproto/gogo.proto"; - // Transaction holds all the data needed for a value transfer or SC call message Transaction { - uint64 Nonce = 1 [(gogoproto.jsontag) = "nonce"]; - bytes Value = 2 [(gogoproto.jsontag) = "value", (gogoproto.casttypewith) = "math/big.Int;github.com/ElrondNetwork/elrond-go/data.BigIntCaster"]; - bytes RcvAddr = 3 [(gogoproto.jsontag) = "receiver"]; - bytes RcvUserName = 4 [(gogoproto.jsontag) = "rcvUserName,omitempty"]; - bytes SndAddr = 5 [(gogoproto.jsontag) = "sender"]; - bytes SndUserName = 6 [(gogoproto.jsontag) = "sndUserName,omitempty"]; - uint64 GasPrice = 7 [(gogoproto.jsontag) = "gasPrice,omitempty"]; - uint64 GasLimit = 8 [(gogoproto.jsontag) = "gasLimit,omitempty"]; - bytes Data = 9 [(gogoproto.jsontag) = "data,omitempty"]; - bytes ChainID = 10 [(gogoproto.jsontag) = "chainID"]; - uint32 Version = 11 [(gogoproto.jsontag) = "version"]; - bytes Signature = 12 [(gogoproto.jsontag) = "signature,omitempty"]; - uint32 Options = 13 [(gogoproto.jsontag) = "options,omitempty"]; - bytes GuardAddr = 14 [(gogoproto.jsontag) = "guardian,omitempty"]; - bytes GuardSignature = 15 [(gogoproto.jsontag) = "guardianSignature,omitempty"]; + uint64 Nonce = 1; + bytes Value = 2; + bytes RcvAddr = 3; + bytes RcvUserName = 4; + bytes SndAddr = 5; + bytes SndUserName = 6; + uint64 GasPrice = 7; + uint64 GasLimit = 8; + bytes Data = 9; + bytes ChainID = 10; + uint32 Version = 11; + bytes Signature = 12; + uint32 Options = 13; + bytes GuardianAddr = 14; + bytes GuardianSignature = 15; } diff --git a/src/reflection.ts b/src/reflection.ts index 836ffdb03..1c020c23c 100644 --- a/src/reflection.ts +++ b/src/reflection.ts @@ -1,5 +1,5 @@ -export function getJavascriptPrototypesInHierarchy(obj: Object, filter: (prototype: any) => boolean): Object[] { - let prototypes: Object[] = []; +export function getJavascriptPrototypesInHierarchy(obj: any, filter: (prototype: any) => boolean): any[] { + let prototypes: any[] = []; let prototype: any = Object.getPrototypeOf(obj); while (prototype && filter(prototype)) { diff --git a/src/relayedTransactionV1Builder.spec.ts b/src/relayedTransactionV1Builder.spec.ts index 3d6cb4768..55bbc9c09 100644 --- a/src/relayedTransactionV1Builder.spec.ts +++ b/src/relayedTransactionV1Builder.spec.ts @@ -3,9 +3,8 @@ import { Address } from "./address"; import * as errors from "./errors"; import { TransactionOptions, TransactionVersion } from "./networkParams"; import { RelayedTransactionV1Builder } from "./relayedTransactionV1Builder"; -import { Signature } from "./signature"; import { TestWallet, loadTestWallets } from "./testutils"; -import { TokenTransfer } from "./tokenTransfer"; +import { TokenTransfer } from "./tokens"; import { Transaction } from "./transaction"; import { TransactionPayload } from "./transactionPayload"; @@ -61,7 +60,7 @@ describe("test relayed v1 transaction builder", function () { data: new TransactionPayload("getContractConfig"), }); - await bob.signer.sign(innerTx); + innerTx.applySignature(await bob.signer.sign(innerTx.serializeForSigning())); const builder = new RelayedTransactionV1Builder(); const relayedTxV1 = builder @@ -71,11 +70,11 @@ describe("test relayed v1 transaction builder", function () { .setRelayerAddress(alice.address) .build(); - await alice.signer.sign(relayedTxV1); + relayedTxV1.applySignature(await alice.signer.sign(relayedTxV1.serializeForSigning())); assert.equal(relayedTxV1.getNonce().valueOf(), 2627); - assert.equal(relayedTxV1.getData().toString(), "relayedTx@7b226e6f6e6365223a3139382c2273656e646572223a2267456e574f65576d6d413063306a6b71764d354241707a61644b46574e534f69417643575163776d4750673d222c227265636569766572223a22414141414141414141414141415141414141414141414141414141414141414141414141414141432f2f383d222c2276616c7565223a302c226761735072696365223a313030303030303030302c226761734c696d6974223a36303030303030302c2264617461223a225a3256305132397564484a68593352446232356d6157633d222c227369676e6174757265223a2239682b6e6742584f5536776674315464437368534d4b3454446a5a32794f74686336564c576e3478724d5a706248427738677a6c6659596d362b766b505258303764634a562b4745635462616a7049692b5a5a5942773d3d222c22636861696e4944223a2256413d3d222c2276657273696f6e223a317d"); - assert.equal(relayedTxV1.getSignature().toString("hex"), "c7d2c3b971f44eca676c10624d3c4319f8898af159f003e1e59f446cb75e5a294c9f0758d800e04d3daff11e67d20c4c1f85fd54aad6deb947ef391e6dd09d07"); + assert.equal(relayedTxV1.getData().toString(), "relayedTx@7b226e6f6e6365223a3139382c2273656e646572223a2267456e574f65576d6d413063306a6b71764d354241707a61644b46574e534f69417643575163776d4750673d222c227265636569766572223a22414141414141414141414141415141414141414141414141414141414141414141414141414141432f2f383d222c2276616c7565223a302c226761735072696365223a313030303030303030302c226761734c696d6974223a36303030303030302c2264617461223a225a3256305132397564484a68593352446232356d6157633d222c227369676e6174757265223a2272525455544858677a4273496e4f6e454b6b7869642b354e66524d486e33534948314673746f577352434c434b3258514c41614f4e704449346531476173624c5150616130566f364144516d4f2b52446b6f364a43413d3d222c22636861696e4944223a2256413d3d222c2276657273696f6e223a327d"); + assert.equal(relayedTxV1.getSignature().toString("hex"), "128e7cdc14c2b9beee2f3ff7a7fa5d1f5ef31a654a0c92e223c90ab28265fa277d306f23a06536248cf9573e828017004fb639617fade4d68a37524aafca710d"); }); it("should compute relayed v1 transaction (with usernames)", async function () { @@ -97,7 +96,7 @@ describe("test relayed v1 transaction builder", function () { chainID: networkConfig.ChainID }); - await carol.signer.sign(innerTx); + innerTx.applySignature(await carol.signer.sign(innerTx.serializeForSigning())); const builder = new RelayedTransactionV1Builder(); const relayedTxV1 = builder @@ -107,11 +106,47 @@ describe("test relayed v1 transaction builder", function () { .setRelayerAddress(frank.address) .build(); - await frank.signer.sign(relayedTxV1); + relayedTxV1.applySignature(await frank.signer.sign(relayedTxV1.serializeForSigning())); assert.equal(relayedTxV1.getNonce().valueOf(), 715); - assert.equal(relayedTxV1.getData().toString(), "relayedTx@7b226e6f6e6365223a3230382c2273656e646572223a227371455656633553486b6c45344a717864556e59573068397a536249533141586f3534786f32634969626f3d222c227265636569766572223a2241546c484c76396f686e63616d433877673970645168386b77704742356a6949496f3349484b594e6165453d222c2276616c7565223a313030303030303030303030303030303030302c226761735072696365223a313030303030303030302c226761734c696d6974223a35303030302c2264617461223a22222c227369676e6174757265223a22744d616d736b6f315a574b526663594e4b5673793463797879643335764b754844576a3548706172344167734c2b4a4e585642545a574c754467384867514254476d724a6b49443133637050614c55322f38626644513d3d222c22636861696e4944223a2256413d3d222c2276657273696f6e223a312c22736e64557365724e616d65223a22593246796232773d222c22726376557365724e616d65223a22595778705932553d227d"); - assert.equal(relayedTxV1.getSignature().toString("hex"), "0fbab023085551b7c497e5c52f64df802cb518ebaac93f8897e5cca25a8aff447565fa96570f7b547f7c0d0fceb2c7d12bcb5f37fa92c79725d9b2c69039f00d"); + assert.equal(relayedTxV1.getData().toString(), "relayedTx@7b226e6f6e6365223a3230382c2273656e646572223a227371455656633553486b6c45344a717864556e59573068397a536249533141586f3534786f32634969626f3d222c227265636569766572223a2241546c484c76396f686e63616d433877673970645168386b77704742356a6949496f3349484b594e6165453d222c2276616c7565223a313030303030303030303030303030303030302c226761735072696365223a313030303030303030302c226761734c696d6974223a35303030302c2264617461223a22222c227369676e6174757265223a226a33427a6469554144325963517473576c65707663664a6f75657a48573063316b735a424a4d6339573167435450512b6870636759457858326f6f367a4b5654347464314b4b6f79783841526a346e336474576c44413d3d222c22636861696e4944223a2256413d3d222c2276657273696f6e223a322c22736e64557365724e616d65223a22593246796232773d222c22726376557365724e616d65223a22595778705932553d227d"); + assert.equal(relayedTxV1.getSignature().toString("hex"), "3787d640e5a579e7977a4a1bcdd435ad11855632fa4a414a06fbf8355692d1a58d76ef0adbdd6ccd6bd3c329f36bd53c180d4873ec1a6c558e659aeb9ab92d00"); + }); + + it("should compute relayed v1 transaction with big value", async function () { + const networkConfig = { + MinGasLimit: 50_000, + GasPerDataByte: 1_500, + GasPriceModifier: 0.01, + ChainID: "T" + }; + + const innerTx = new Transaction({ + nonce: 208, + value: TokenTransfer.egldFromAmount(1999999), + sender: carol.address, + receiver: alice.address, + senderUsername: "carol", + receiverUsername: "alice", + gasLimit: 50000, + chainID: networkConfig.ChainID + }); + + innerTx.applySignature(await carol.signer.sign(innerTx.serializeForSigning())); + + const builder = new RelayedTransactionV1Builder(); + const relayedTxV1 = builder + .setInnerTransaction(innerTx) + .setRelayerNonce(715) + .setNetworkConfig(networkConfig) + .setRelayerAddress(frank.address) + .build(); + + relayedTxV1.applySignature(await frank.signer.sign(relayedTxV1.serializeForSigning())); + + assert.equal(relayedTxV1.getNonce().valueOf(), 715); + assert.equal(relayedTxV1.getData().toString(), "relayedTx@7b226e6f6e6365223a3230382c2273656e646572223a227371455656633553486b6c45344a717864556e59573068397a536249533141586f3534786f32634969626f3d222c227265636569766572223a2241546c484c76396f686e63616d433877673970645168386b77704742356a6949496f3349484b594e6165453d222c2276616c7565223a313939393939393030303030303030303030303030303030302c226761735072696365223a313030303030303030302c226761734c696d6974223a35303030302c2264617461223a22222c227369676e6174757265223a22594661677972512f726d614c7333766e7159307657553858415a7939354b4e31725738347a4f764b62376c7a3773576e2f566a546d68704378774d682b7261314e444832574d6f3965507648304f79427453776a44773d3d222c22636861696e4944223a2256413d3d222c2276657273696f6e223a322c22736e64557365724e616d65223a22593246796232773d222c22726376557365724e616d65223a22595778705932553d227d"); + assert.equal(relayedTxV1.getSignature().toString("hex"), "c0fb5cf8c0a413d6988ba35dc279c63f8849572c5f23b1cab36dcc50952dc3ed9da01068d6ac0cbde7e14167bfc2eca5164d5c2154c89eb313c9c596e3f8b801"); }); it("should compute guarded inner Tx - relayed v1 transaction", async function () { @@ -134,8 +169,8 @@ describe("test relayed v1 transaction builder", function () { options: TransactionOptions.withOptions({ guarded: true }) }); - await bob.signer.sign(innerTx); - innerTx.applyGuardianSignature(new Signature("c72e08622c86d9b6889fb9f33eed75c6a04a940daa012825464c6ccaad71640cfcf5c4d38b36fb0575345bbec90daeb2db7a423bfb5253cef0ddc5c87d1b5f04")); + innerTx.applySignature(await bob.signer.sign(innerTx.serializeForSigning())); + innerTx.applyGuardianSignature(await grace.signer.sign(innerTx.serializeForSigning())); const builder = new RelayedTransactionV1Builder(); const relayedTxV1 = builder @@ -145,11 +180,11 @@ describe("test relayed v1 transaction builder", function () { .setRelayerAddress(alice.address) .build(); - await alice.signer.sign(relayedTxV1); + relayedTxV1.applySignature(await alice.signer.sign(relayedTxV1.serializeForSigning())); assert.equal(relayedTxV1.getNonce().valueOf(), 2627); - assert.equal(relayedTxV1.getData().toString(), "relayedTx@7b226e6f6e6365223a3139382c2273656e646572223a2267456e574f65576d6d413063306a6b71764d354241707a61644b46574e534f69417643575163776d4750673d222c227265636569766572223a22414141414141414141414146414b565841323879704877692f79693741364c64504b704f68464d386958513d222c2276616c7565223a302c226761735072696365223a313030303030303030302c226761734c696d6974223a36303030303030302c2264617461223a225a3256305132397564484a68593352446232356d6157633d222c227369676e6174757265223a224b4b78324f33383655725135416b4f465258307578327933446a384853334b373038487174344668377161557669424550716c45614e746e6158706a6f2f333651476d4a456934784435457a6c6f4f677a634d4442773d3d222c22636861696e4944223a2256413d3d222c2276657273696f6e223a322c226f7074696f6e73223a322c22677561726469616e223a22486f714c61306e655733766843716f56696c70715372744c5673774939535337586d7a563868477450684d3d222c22677561726469616e5369676e6174757265223a227879344959697947326261496e376e7a507531317871424b6c4132714153676c526b7873797131785a417a3839635454697a6237425855305737374a44613679323370434f2f745355383777336358496652746642413d3d227d"); - assert.equal(relayedTxV1.getSignature().toString("hex"), "f3db6318406f01ef807f04039e33563f97c2eabc1c8a59b6e412429725f03242cba679c717b734cb06859ef8e14349edf1db5eb1a9b3d36697b922475ae7ea07"); + assert.equal(relayedTxV1.getData().toString(), "relayedTx@7b226e6f6e6365223a3139382c2273656e646572223a2267456e574f65576d6d413063306a6b71764d354241707a61644b46574e534f69417643575163776d4750673d222c227265636569766572223a22414141414141414141414146414b565841323879704877692f79693741364c64504b704f68464d386958513d222c2276616c7565223a302c226761735072696365223a313030303030303030302c226761734c696d6974223a36303030303030302c2264617461223a225a3256305132397564484a68593352446232356d6157633d222c227369676e6174757265223a224b4b78324f33383655725135416b4f465258307578327933446a384853334b373038487174344668377161557669424550716c45614e746e6158706a6f2f333651476d4a456934784435457a6c6f4f677a634d4442773d3d222c22636861696e4944223a2256413d3d222c2276657273696f6e223a322c226f7074696f6e73223a322c22677561726469616e223a22486f714c61306e655733766843716f56696c70715372744c5673774939535337586d7a563868477450684d3d222c22677561726469616e5369676e6174757265223a222b5431526f4833625a792f54423177342b6a365155477258645637457577553073753948646551626453515269463953757a686d634b705463526d58595252366c534c6652394931624d7134674730436538363741513d3d227d"); + assert.equal(relayedTxV1.getSignature().toString("hex"), "39cff9d5100e290fbc7361cb6e2402261caf864257b4116f150e0c61e7869155dff8361fa5449431eb7a8ed847c01ba9b3b5ebafe5fac1a3d40c64829d827e00"); }); it("should compute guarded inner tx and guarded relayed v1 transaction", async function () { @@ -172,9 +207,8 @@ describe("test relayed v1 transaction builder", function () { options: TransactionOptions.withOptions({ guarded: true }) }); - await bob.signer.sign(innerTx); - innerTx.applyGuardianSignature(new Signature("b12d08732c86d9b6889fb9f33eed65c6a04a960daa012825464c6ccaad71640cfcf5c4d38b36fb0575345bbec90daeb2db7a423bfb5253cef0ddc5c87d1b5f04")); - + innerTx.applySignature(await bob.signer.sign(innerTx.serializeForSigning())); + innerTx.applyGuardianSignature(await grace.signer.sign(innerTx.serializeForSigning())); const builder = new RelayedTransactionV1Builder(); const relayedTxV1 = builder .setInnerTransaction(innerTx) @@ -186,11 +220,11 @@ describe("test relayed v1 transaction builder", function () { .setRelayedTransactionGuardian(frank.address) .build(); - await alice.signer.sign(relayedTxV1); - relayedTxV1.applyGuardianSignature(new Signature("d32c08722c86d9b6889fb9f33eed65c6a04a970daa012825464c6ccaad71640cfcf5c4d38b36fb0575345bbec90daeb2db7a423bfb5253cef0ddc5c87d1b5f04")); + relayedTxV1.applySignature(await alice.signer.sign(relayedTxV1.serializeForSigning())); + relayedTxV1.applyGuardianSignature(await frank.signer.sign(relayedTxV1.serializeForSigning())); assert.equal(relayedTxV1.getNonce().valueOf(), 2627); - assert.equal(relayedTxV1.getData().toString(), "relayedTx@7b226e6f6e6365223a3139382c2273656e646572223a2267456e574f65576d6d413063306a6b71764d354241707a61644b46574e534f69417643575163776d4750673d222c227265636569766572223a22414141414141414141414146414b565841323879704877692f79693741364c64504b704f68464d386958513d222c2276616c7565223a302c226761735072696365223a313030303030303030302c226761734c696d6974223a36303030303030302c2264617461223a225957526b546e5674596d5679222c227369676e6174757265223a223469724d4b4a656d724d375174344e7635487633544c44683775654779487045564c4371674a3677652f7a662b746a4933354975573452633458543451533433475333356158386c6a533834324a38426854645043673d3d222c22636861696e4944223a2256413d3d222c2276657273696f6e223a322c226f7074696f6e73223a322c22677561726469616e223a22486f714c61306e655733766843716f56696c70715372744c5673774939535337586d7a563868477450684d3d222c22677561726469616e5369676e6174757265223a227353304963797947326261496e376e7a5075316c7871424b6c6732714153676c526b7873797131785a417a3839635454697a6237425855305737374a44613679323370434f2f745355383777336358496652746642413d3d227d"); - assert.equal(relayedTxV1.getSignature().toString("hex"), "15fe39045685625d0f1742e34ba7679d225d49fc1f1f2ab363b7e78beddb8d278d11f5658a0d0e996d28ba77c49bc6d614b62a4eb7e74f363546ecaa2a84d905"); + assert.equal(relayedTxV1.getData().toString(), "relayedTx@7b226e6f6e6365223a3139382c2273656e646572223a2267456e574f65576d6d413063306a6b71764d354241707a61644b46574e534f69417643575163776d4750673d222c227265636569766572223a22414141414141414141414146414b565841323879704877692f79693741364c64504b704f68464d386958513d222c2276616c7565223a302c226761735072696365223a313030303030303030302c226761734c696d6974223a36303030303030302c2264617461223a225957526b546e5674596d5679222c227369676e6174757265223a223469724d4b4a656d724d375174344e7635487633544c44683775654779487045564c4371674a3677652f7a662b746a4933354975573452633458543451533433475333356158386c6a533834324a38426854645043673d3d222c22636861696e4944223a2256413d3d222c2276657273696f6e223a322c226f7074696f6e73223a322c22677561726469616e223a22486f714c61306e655733766843716f56696c70715372744c5673774939535337586d7a563868477450684d3d222c22677561726469616e5369676e6174757265223a2270424754394e674a78307539624c56796b654d78786a454865374269696c37764932324a46676f32787a6e2f496e3032463769546563356b44395045324f747065386c475335412b532f4a36417762576834446744673d3d227d"); + assert.equal(relayedTxV1.getSignature().toString("hex"), "8ede1bbeed96b102344dffeac12c2592c62b7313cdeb132e8c8bf11d2b1d3bb8189d257a6dbcc99e222393d9b9ec77656c349dae97a32e68bdebd636066bf706"); }); }); diff --git a/src/relayedTransactionV1Builder.ts b/src/relayedTransactionV1Builder.ts index 2e690e225..c4871b61b 100644 --- a/src/relayedTransactionV1Builder.ts +++ b/src/relayedTransactionV1Builder.ts @@ -1,4 +1,3 @@ -import BigNumber from "bignumber.js"; import { Address } from "./address"; import { ErrInvalidRelayedV1BuilderArguments } from "./errors"; import { IAddress, INonce } from "./interface"; @@ -7,6 +6,11 @@ import { TransactionOptions, TransactionVersion } from "./networkParams"; import { Transaction } from "./transaction"; import { TransactionPayload } from "./transactionPayload"; +const JSONbig = require("json-bigint"); + +/** + * @deprecated Use {@link RelayedTransactionsFactory} instead. + */ export class RelayedTransactionV1Builder { innerTransaction: Transaction | undefined; relayerAddress: IAddress | undefined; @@ -131,7 +135,7 @@ export class RelayedTransactionV1Builder { "nonce": this.innerTransaction.getNonce().valueOf(), "sender": new Address(this.innerTransaction.getSender().bech32()).pubkey().toString("base64"), "receiver": new Address(this.innerTransaction.getReceiver().bech32()).pubkey().toString("base64"), - "value": new BigNumber(this.innerTransaction.getValue().toString(), 10).toNumber(), + "value": BigInt(this.innerTransaction.getValue().toString()), "gasPrice": this.innerTransaction.getGasPrice().valueOf(), "gasLimit": this.innerTransaction.getGasLimit().valueOf(), "data": this.innerTransaction.getData().valueOf().toString("base64"), @@ -145,6 +149,6 @@ export class RelayedTransactionV1Builder { "rcvUserName": this.innerTransaction.getReceiverUsername() ? Buffer.from(this.innerTransaction.getReceiverUsername()).toString("base64") : undefined, }; - return JSON.stringify(txObject); + return JSONbig.stringify(txObject); } } diff --git a/src/relayedTransactionV2Builder.spec.ts b/src/relayedTransactionV2Builder.spec.ts index 434892a68..b72823c2c 100644 --- a/src/relayedTransactionV2Builder.spec.ts +++ b/src/relayedTransactionV2Builder.spec.ts @@ -70,7 +70,7 @@ describe("test relayed v2 transaction builder", function () { version: 2, }); - await bob.signer.sign(innerTx); + innerTx.applySignature(await bob.signer.sign(innerTx.serializeForSigning())); const builder = new RelayedTransactionV2Builder(); const relayedTxV2 = builder @@ -82,7 +82,7 @@ describe("test relayed v2 transaction builder", function () { .build(); relayedTxV2.setSender(alice.address); - await alice.signer.sign(relayedTxV2); + relayedTxV2.applySignature(await alice.signer.sign(relayedTxV2.serializeForSigning())); assert.equal(relayedTxV2.getNonce().valueOf(), 37); assert.equal(relayedTxV2.getVersion().valueOf(), 2); diff --git a/src/relayedTransactionV2Builder.ts b/src/relayedTransactionV2Builder.ts index bd363885c..a213c5413 100644 --- a/src/relayedTransactionV2Builder.ts +++ b/src/relayedTransactionV2Builder.ts @@ -5,6 +5,9 @@ import { AddressValue, ArgSerializer, BytesValue, U64Value } from "./smartcontra import { Transaction } from "./transaction"; import { TransactionPayload } from "./transactionPayload"; +/** + * @deprecated Use {@link RelayedTransactionsFactory} instead. + */ export class RelayedTransactionV2Builder { innerTransaction: Transaction | undefined; innerTransactionGasLimit: IGasLimit | undefined; diff --git a/src/signableMessage.ts b/src/signableMessage.ts index 4fe82b7e0..872407cf0 100644 --- a/src/signableMessage.ts +++ b/src/signableMessage.ts @@ -1,80 +1,81 @@ import { Address } from "./address"; import { ISignature } from "./interface"; import { interpretSignatureAsBuffer } from "./signature"; +import { MESSAGE_PREFIX } from "./constants"; const createKeccakHash = require("keccak"); -export const MESSAGE_PREFIX = "\x17Elrond Signed Message:\n"; - +/** + * @deprecated Use {@link Message} instead. + */ export class SignableMessage { - - /** - * Actual message being signed. - */ - message: Buffer; - /** - * Signature obtained by a signer of type @param signer . - */ - signature: Buffer; - - /** - * Address of the wallet that performed the signing operation - */ - address: Address; - - /** - * Text representing the identifer for the application that signed the message - */ - signer: string; - - /** - * Number representing the signable message version - */ - version: number; - - public constructor(init?: Partial) { - this.message = Buffer.from([]); - this.signature = Buffer.from([]); - this.version = 1; - this.signer = "ErdJS"; - this.address = new Address(); - - Object.assign(this, init); - } - - serializeForSigning(): Buffer { - const messageSize = Buffer.from(this.message.length.toString()); - const signableMessage = Buffer.concat([messageSize, this.message]); - let bytesToHash = Buffer.concat([Buffer.from(MESSAGE_PREFIX), signableMessage]); - - return createKeccakHash("keccak256").update(bytesToHash).digest(); - } - - serializeForSigningRaw(): Buffer { - return Buffer.concat([this.getMessageSize(), this.message]); - } - - getSignature(): Buffer { - return this.signature; - } - - applySignature(signature: ISignature | Uint8Array) { - this.signature = interpretSignatureAsBuffer(signature); - } - - getMessageSize(): Buffer { - const messageSize = Buffer.alloc(4); - messageSize.writeUInt32BE(this.message.length, 0); - - return messageSize; - } - - toJSON(): object { - return { - address: this.address.bech32(), - message: "0x" + this.message.toString("hex"), - signature: "0x" + this.signature.toString("hex"), - version: this.version, - signer: this.signer, - }; - } + /** + * Actual message being signed. + */ + message: Buffer; + /** + * Signature obtained by a signer of type @param signer . + */ + signature: Buffer; + + /** + * Address of the wallet that performed the signing operation + */ + address: Address; + + /** + * Text representing the identifer for the application that signed the message + */ + signer: string; + + /** + * Number representing the signable message version + */ + version: number; + + public constructor(init?: Partial) { + this.message = Buffer.from([]); + this.signature = Buffer.from([]); + this.version = 1; + this.signer = "ErdJS"; + this.address = Address.empty(); + + Object.assign(this, init); + } + + serializeForSigning(): Buffer { + const messageSize = Buffer.from(this.message.length.toString()); + const signableMessage = Buffer.concat([messageSize, this.message]); + let bytesToHash = Buffer.concat([Buffer.from(MESSAGE_PREFIX), signableMessage]); + + return createKeccakHash("keccak256").update(bytesToHash).digest(); + } + + serializeForSigningRaw(): Buffer { + return Buffer.concat([this.getMessageSize(), this.message]); + } + + getSignature(): Buffer { + return this.signature; + } + + applySignature(signature: ISignature | Uint8Array) { + this.signature = interpretSignatureAsBuffer(signature); + } + + getMessageSize(): Buffer { + const messageSize = Buffer.alloc(4); + messageSize.writeUInt32BE(this.message.length, 0); + + return messageSize; + } + + toJSON(): object { + return { + address: this.address.bech32(), + message: "0x" + this.message.toString("hex"), + signature: "0x" + this.signature.toString("hex"), + version: this.version, + signer: this.signer, + }; + } } diff --git a/src/signature.ts b/src/signature.ts index e307a2255..95fe99778 100644 --- a/src/signature.ts +++ b/src/signature.ts @@ -1,6 +1,5 @@ import * as errors from "./errors"; - const SIGNATURE_LENGTH = 64; /** @@ -59,7 +58,7 @@ export class Signature { } } -export function interpretSignatureAsBuffer(signature: { hex(): string; } | Uint8Array): Buffer { +export function interpretSignatureAsBuffer(signature: { hex(): string } | Uint8Array): Buffer { if (ArrayBuffer.isView(signature)) { return Buffer.from(signature); } else if ((signature).hex != null) { diff --git a/src/smartContractQueriesController.spec.ts b/src/smartContractQueriesController.spec.ts new file mode 100644 index 000000000..c65309833 --- /dev/null +++ b/src/smartContractQueriesController.spec.ts @@ -0,0 +1,248 @@ +import { ContractQueryResponse } from "@multiversx/sdk-network-providers"; +import BigNumber from "bignumber.js"; +import { assert } from "chai"; +import { QueryRunnerAdapter } from "./adapters/queryRunnerAdapter"; +import { SmartContractQueriesController } from "./smartContractQueriesController"; +import { SmartContractQueryResponse } from "./smartContractQuery"; +import { AbiRegistry, BigUIntValue, BooleanValue, BytesValue, Tuple, U16Value, U64Value } from "./smartcontracts"; +import { bigIntToBuffer } from "./smartcontracts/codec/utils"; +import { MockNetworkProvider, loadAbiRegistry } from "./testutils"; + +describe("test smart contract queries controller", () => { + describe("createQuery", () => { + it("works without ABI, when arguments are buffers", function () { + const adapter = new QueryRunnerAdapter({ networkProvider: new MockNetworkProvider() }); + const controller = new SmartContractQueriesController({ + queryRunner: adapter, + }); + + const query = controller.createQuery({ + contract: "erd1foo", + function: "bar", + arguments: [bigIntToBuffer(42), Buffer.from("abba")], + }); + + assert.equal(query.contract, "erd1foo"); + assert.equal(query.function, "bar"); + assert.deepEqual(query.arguments, [bigIntToBuffer(42), Buffer.from("abba")]); + }); + + it("works without ABI, when arguments are typed values", function () { + const adapter = new QueryRunnerAdapter({ networkProvider: new MockNetworkProvider() }); + const controller = new SmartContractQueriesController({ + queryRunner: adapter, + }); + + const query = controller.createQuery({ + contract: "erd1foo", + function: "bar", + arguments: [new BigUIntValue(42), BytesValue.fromUTF8("abba")], + }); + + assert.equal(query.contract, "erd1foo"); + assert.equal(query.function, "bar"); + assert.deepEqual(query.arguments, [bigIntToBuffer(42), Buffer.from("abba")]); + }); + + it("fails without ABI, when arguments aren't buffers, nor typed values", function () { + const adapter = new QueryRunnerAdapter({ networkProvider: new MockNetworkProvider() }); + const controller = new SmartContractQueriesController({ + queryRunner: adapter, + }); + + assert.throws(() => { + controller.createQuery({ + contract: "erd1foo", + function: "bar", + arguments: [42, "abba"], + }); + }, "cannot encode arguments"); + }); + + it("works with ABI, when arguments are native JS objects", async function () { + const adapter = new QueryRunnerAdapter({ + networkProvider: new MockNetworkProvider(), + }); + const controller = new SmartContractQueriesController({ + abi: await loadAbiRegistry("src/testdata/lottery-esdt.abi.json"), + queryRunner: adapter, + }); + + const query = controller.createQuery({ + contract: "erd1foo", + function: "getLotteryInfo", + arguments: ["myLottery"], + }); + + assert.equal(query.contract, "erd1foo"); + assert.equal(query.function, "getLotteryInfo"); + assert.deepEqual(query.arguments, [Buffer.from("myLottery")]); + }); + + it("works with ABI, when arguments typed values", async function () { + const adapter = new QueryRunnerAdapter({ + networkProvider: new MockNetworkProvider(), + }); + const controller = new SmartContractQueriesController({ + abi: await loadAbiRegistry("src/testdata/lottery-esdt.abi.json"), + queryRunner: adapter, + }); + + const query = controller.createQuery({ + contract: "erd1foo", + function: "getLotteryInfo", + arguments: [BytesValue.fromUTF8("myLottery")], + }); + + assert.equal(query.contract, "erd1foo"); + assert.equal(query.function, "getLotteryInfo"); + assert.deepEqual(query.arguments, [Buffer.from("myLottery")]); + }); + + it("works with ABI, with mixed arguments", async function () { + const abi = AbiRegistry.create({ + endpoints: [ + { + name: "bar", + inputs: [ + { + type: "tuple", + }, + { + type: "tuple>", + }, + { + type: "List>", + }, + { + type: "u64", + }, + ], + outputs: [], + }, + ], + }); + + const adapter = new QueryRunnerAdapter({ + networkProvider: new MockNetworkProvider(), + }); + const controller = new SmartContractQueriesController({ + abi: abi, + queryRunner: adapter, + }); + + const query = controller.createQuery({ + contract: "erd1foo", + function: "bar", + arguments: [ + // Typed value + Tuple.fromItems([new U16Value(42), new BooleanValue(true)]), + // Native JS objects + [43, false], + [ + [44, false], + [45, true], + ], + // Typed value + new U64Value(46), + ], + }); + + assert.equal(query.contract, "erd1foo"); + assert.equal(query.function, "bar"); + assert.deepEqual(query.arguments, [ + Buffer.from("002a01", "hex"), + Buffer.from("2b0100", "hex"), + Buffer.from("2c002d01", "hex"), + Buffer.from("2e", "hex"), + ]); + }); + }); + + describe("runQuery", () => { + it("calls queryContract on the network provider", async function () { + const networkProvider = new MockNetworkProvider(); + const adapter = new QueryRunnerAdapter({ + networkProvider: networkProvider, + }); + const controller = new SmartContractQueriesController({ + queryRunner: adapter, + }); + + networkProvider.mockQueryContractOnFunction( + "bar", + new ContractQueryResponse({ + returnData: [Buffer.from("abba").toString("base64")], + returnCode: "ok", + }), + ); + + const query = { + contract: "erd1qqqqqqqqqqqqqpgqvc7gdl0p4s97guh498wgz75k8sav6sjfjlwqh679jy", + function: "bar", + arguments: [], + }; + + const response = await controller.runQuery(query); + + assert.equal(response.returnCode, "ok"); + assert.deepEqual(response.returnDataParts, [Buffer.from("abba")]); + }); + }); + + describe("parseQueryResponse", () => { + it("works without ABI", function () { + const adapter = new QueryRunnerAdapter({ + networkProvider: new MockNetworkProvider(), + }); + const controller = new SmartContractQueriesController({ + queryRunner: adapter, + }); + + const response = new SmartContractQueryResponse({ + function: "bar", + returnCode: "ok", + returnMessage: "ok", + returnDataParts: [Buffer.from("abba")], + }); + + const parsed = controller.parseQueryResponse(response); + + assert.deepEqual(parsed, [Buffer.from("abba")]); + }); + + it("works with ABI", async function () { + const adapter = new QueryRunnerAdapter({ + networkProvider: new MockNetworkProvider(), + }); + const controller = new SmartContractQueriesController({ + abi: await loadAbiRegistry("src/testdata/lottery-esdt.abi.json"), + queryRunner: adapter, + }); + + const response = new SmartContractQueryResponse({ + function: "getLotteryInfo", + returnCode: "ok", + returnMessage: "ok", + returnDataParts: [ + Buffer.from( + "0000000b6c75636b792d746f6b656e000000010100000000000000005fc2b9dbffffffff00000001640000000a140ec80fa7ee88000000", + "hex", + ), + ], + }); + + const [parsed] = controller.parseQueryResponse(response); + + assert.deepEqual(parsed, { + token_identifier: "lucky-token", + ticket_price: new BigNumber("1"), + tickets_left: new BigNumber(0), + deadline: new BigNumber("0x000000005fc2b9db", 16), + max_entries_per_user: new BigNumber(0xffffffff), + prize_distribution: Buffer.from([0x64]), + prize_pool: new BigNumber("94720000000000000000000"), + }); + }); + }); +}); diff --git a/src/smartContractQueriesController.ts b/src/smartContractQueriesController.ts new file mode 100644 index 000000000..233d83371 --- /dev/null +++ b/src/smartContractQueriesController.ts @@ -0,0 +1,106 @@ +import { Err } from "./errors"; +import { IContractQueryResponse } from "./interfaceOfNetwork"; +import { SmartContractQuery, SmartContractQueryResponse } from "./smartContractQuery"; +import { ArgSerializer, ContractFunction, EndpointDefinition, NativeSerializer, ResultsParser } from "./smartcontracts"; + +interface IAbi { + getEndpoint(name: string | ContractFunction): EndpointDefinition; +} + +interface IQueryRunner { + runQuery(query: SmartContractQuery): Promise; +} + +export class SmartContractQueriesController { + private readonly abi?: IAbi; + private readonly queryRunner: IQueryRunner; + private readonly legacyResultsParser: ResultsParser; + + constructor(options: { abi?: IAbi; queryRunner: IQueryRunner }) { + this.abi = options.abi; + this.queryRunner = options.queryRunner; + this.legacyResultsParser = new ResultsParser(); + } + + createQuery(options: { + contract: string; + caller?: string; + value?: bigint; + function: string; + arguments: any[]; + }): SmartContractQuery { + const preparedArguments = this.encodeArguments(options.function, options.arguments); + + return new SmartContractQuery({ + contract: options.contract, + caller: options.caller, + function: options.function, + arguments: preparedArguments, + value: options.value, + }); + } + + private encodeArguments(functionName: string, args: any[]): Uint8Array[] { + const endpoint = this.abi?.getEndpoint(functionName); + + if (endpoint) { + const typedArgs = NativeSerializer.nativeToTypedValues(args, endpoint); + return new ArgSerializer().valuesToBuffers(typedArgs); + } + + if (this.areArgsOfTypedValue(args)) { + return new ArgSerializer().valuesToBuffers(args); + } + + if (this.areArgsBuffers(args)) { + return args.map((arg) => Buffer.from(arg)); + } + + throw new Err( + "cannot encode arguments: when ABI is not available, they must be either typed values or buffers", + ); + } + + private areArgsOfTypedValue(args: any[]): boolean { + for (const arg of args) { + if (!arg.belongsToTypesystem) { + return false; + } + } + + return true; + } + + private areArgsBuffers(args: any[]): boolean { + for (const arg of args) { + if (!ArrayBuffer.isView(arg)) { + return false; + } + } + + return true; + } + + async runQuery(query: SmartContractQuery): Promise { + const queryResponse = await this.queryRunner.runQuery(query); + return queryResponse; + } + + parseQueryResponse(response: SmartContractQueryResponse): any[] { + if (!this.abi) { + return response.returnDataParts; + } + + const legacyQueryResponse: IContractQueryResponse = { + returnCode: response.returnCode, + returnMessage: response.returnMessage, + getReturnDataParts: () => response.returnDataParts.map((part) => Buffer.from(part)), + }; + + const functionName = response.function; + const endpoint = this.abi.getEndpoint(functionName); + const legacyBundle = this.legacyResultsParser.parseQueryResponse(legacyQueryResponse, endpoint); + const nativeValues = legacyBundle.values.map((value) => value.valueOf()); + return nativeValues; + } +} diff --git a/src/smartContractQuery.ts b/src/smartContractQuery.ts new file mode 100644 index 000000000..275d16bc8 --- /dev/null +++ b/src/smartContractQuery.ts @@ -0,0 +1,35 @@ +export class SmartContractQuery { + contract: string; + caller?: string; + value?: bigint; + function: string; + arguments: Uint8Array[]; + + constructor(options: { + contract: string; + caller?: string; + value?: bigint; + function: string; + arguments: Uint8Array[]; + }) { + this.contract = options.contract; + this.caller = options.caller; + this.value = options.value; + this.function = options.function; + this.arguments = options.arguments; + } +} + +export class SmartContractQueryResponse { + function: string; + returnCode: string; + returnMessage: string; + returnDataParts: Uint8Array[]; + + constructor(obj: { function: string; returnCode: string; returnMessage: string; returnDataParts: Uint8Array[] }) { + this.function = obj.function; + this.returnCode = obj.returnCode; + this.returnMessage = obj.returnMessage; + this.returnDataParts = obj.returnDataParts; + } +} diff --git a/src/smartcontracts/argSerializer.spec.ts b/src/smartcontracts/argSerializer.spec.ts index 06bcac914..840650820 100644 --- a/src/smartcontracts/argSerializer.spec.ts +++ b/src/smartcontracts/argSerializer.spec.ts @@ -29,7 +29,7 @@ describe("test serializer", () => { serializeThenDeserialize( ["u32", "i64", "bytes"], [new U32Value(100), new I64Value(new BigNumber("-1")), new BytesValue(Buffer.from("abba", "hex"))], - "64@ff@abba" + "64@ff@abba", ); serializeThenDeserialize( @@ -39,7 +39,7 @@ describe("test serializer", () => { OptionValue.newMissing(), CompositeValue.fromItems(new U8Value(3), new BytesValue(Buffer.from("abba", "hex"))), ], - "0100000064@@03@abba" + "0100000064@@03@abba", ); serializeThenDeserialize( @@ -49,19 +49,26 @@ describe("test serializer", () => { VariadicValue.fromItems( new BytesValue(Buffer.from("abba", "hex")), new BytesValue(Buffer.from("abba", "hex")), - new BytesValue(Buffer.from("abba", "hex")) + new BytesValue(Buffer.from("abba", "hex")), ), ], - "00080009@abba@abba@abba" + "00080009@abba@abba@abba", ); serializeThenDeserialize( ["MultiArg, List>", "VarArgs"], [ - CompositeValue.fromItems(OptionValue.newProvided(new U8Value(7)), List.fromItems([new U16Value(8), new U16Value(9)])), - VariadicValue.fromItems(new BytesValue(Buffer.from("abba", "hex")), new BytesValue(Buffer.from("abba", "hex")), new BytesValue(Buffer.from("abba", "hex"))) + CompositeValue.fromItems( + OptionValue.newProvided(new U8Value(7)), + List.fromItems([new U16Value(8), new U16Value(9)]), + ), + VariadicValue.fromItems( + new BytesValue(Buffer.from("abba", "hex")), + new BytesValue(Buffer.from("abba", "hex")), + new BytesValue(Buffer.from("abba", "hex")), + ), ], - "0107@00080009@abba@abba@abba" + "0107@00080009@abba@abba@abba", ); }); @@ -69,7 +76,7 @@ describe("test serializer", () => { serializeThenDeserialize( ["tuple2"], [Tuple.fromItems([new I32Value(100), new I16Value(10)])], - "00000064000a" + "00000064000a", ); }); diff --git a/src/smartcontracts/argSerializer.ts b/src/smartcontracts/argSerializer.ts index d4dbeafb3..4adf35a7f 100644 --- a/src/smartcontracts/argSerializer.ts +++ b/src/smartcontracts/argSerializer.ts @@ -5,7 +5,6 @@ import { OptionalType, OptionalValue } from "./typesystem/algebraic"; import { CompositeType, CompositeValue } from "./typesystem/composite"; import { VariadicType, VariadicValue } from "./typesystem/variadic"; - interface IArgSerializerOptions { codec: ICodec; } @@ -22,7 +21,7 @@ interface IParameterDefinition { // TODO: perhaps move default construction options to a factory (ArgSerializerFactory), instead of referencing them in the constructor // (postpone as much as possible, breaking change) const defaultArgSerializerOptions: IArgSerializerOptions = { - codec: new BinaryCodec() + codec: new BinaryCodec(), }; export class ArgSerializer { @@ -47,7 +46,7 @@ export class ArgSerializer { */ stringToBuffers(joinedString: string): Buffer[] { // We also keep the zero-length buffers (they could encode missing options, Option). - return joinedString.split(ARGUMENTS_SEPARATOR).map(item => Buffer.from(item, "hex")); + return joinedString.split(ARGUMENTS_SEPARATOR).map((item) => Buffer.from(item, "hex")); } /** @@ -55,6 +54,7 @@ export class ArgSerializer { */ buffersToValues(buffers: Buffer[], parameters: IParameterDefinition[]): TypedValue[] { // TODO: Refactor, split (function is quite complex). + // eslint-disable-next-line @typescript-eslint/no-this-alias const self = this; buffers = buffers || []; @@ -94,6 +94,8 @@ export class ArgSerializer { // Non-composite (singular), non-variadic (fixed) type. // The only branching without a recursive call. const typedValue = decodeNextBuffer(type); + + // TODO: Handle the case (maybe throw error) when "typedValue" is, actually, null. return typedValue!; } @@ -136,7 +138,7 @@ export class ArgSerializer { /** * Serializes a set of typed values into an arguments string (e.g. aa@bb@@cc). */ - valuesToString(values: TypedValue[]): { argumentsString: string, count: number } { + valuesToString(values: TypedValue[]): { argumentsString: string; count: number } { let strings = this.valuesToStrings(values); let argumentsString = strings.join(ARGUMENTS_SEPARATOR); let count = strings.length; @@ -148,7 +150,7 @@ export class ArgSerializer { */ valuesToStrings(values: TypedValue[]): string[] { let buffers = this.valuesToBuffers(values); - let strings = buffers.map(buffer => buffer.toString("hex")); + let strings = buffers.map((buffer) => buffer.toString("hex")); return strings; } @@ -158,6 +160,7 @@ export class ArgSerializer { */ valuesToBuffers(values: TypedValue[]): Buffer[] { // TODO: Refactor, split (function is quite complex). + // eslint-disable-next-line @typescript-eslint/no-this-alias const self = this; const buffers: Buffer[] = []; @@ -190,7 +193,7 @@ export class ArgSerializer { handleValue(item); } - return + return; } // Non-composite (singular), non-variadic (fixed) type. diff --git a/src/smartcontracts/argumentErrorContext.ts b/src/smartcontracts/argumentErrorContext.ts index 76543ddf8..661f62b22 100644 --- a/src/smartcontracts/argumentErrorContext.ts +++ b/src/smartcontracts/argumentErrorContext.ts @@ -13,11 +13,15 @@ export class ArgumentErrorContext { } throwError(specificError: string): never { - throw new ErrInvalidArgument(`Error when converting arguments for endpoint (endpoint name: ${this.endpointName}, argument index: ${this.argumentIndex}, name: ${this.parameterDefinition.name}, type: ${this.parameterDefinition.type})\nNested error: ${specificError}`); + throw new ErrInvalidArgument( + `Error when converting arguments for endpoint (endpoint name: ${this.endpointName}, argument index: ${this.argumentIndex}, name: ${this.parameterDefinition.name}, type: ${this.parameterDefinition.type})\nNested error: ${specificError}`, + ); } convertError(native: any, typeName: string): never { - this.throwError(`Can't convert argument (argument: ${native}, type ${typeof native}), wanted type: ${typeName})`); + this.throwError( + `Can't convert argument (argument: ${native}, type ${typeof native}), wanted type: ${typeName})`, + ); } unhandledType(functionName: string, type: Type): never { @@ -27,14 +31,18 @@ export class ArgumentErrorContext { guardSameLength(native: any[], valueTypes: T[]) { native = native || []; if (native.length != valueTypes.length) { - this.throwError(`Incorrect composite type length: have ${native.length}, expected ${valueTypes.length} (argument: ${native})`); + this.throwError( + `Incorrect composite type length: have ${native.length}, expected ${valueTypes.length} (argument: ${native})`, + ); } } guardHasField(native: any, fieldName: string) { native = native || {}; if (!(fieldName in native)) { - this.throwError(`Struct argument does not contain a field named "${fieldName}" (argument: ${JSON.stringify(native)})`); + this.throwError( + `Struct argument does not contain a field named "${fieldName}" (argument: ${JSON.stringify(native)})`, + ); } } } diff --git a/src/smartcontracts/code.spec.ts b/src/smartcontracts/code.spec.ts index 1a5f3d94e..1933cfefe 100644 --- a/src/smartcontracts/code.spec.ts +++ b/src/smartcontracts/code.spec.ts @@ -1,26 +1,25 @@ import { assert } from "chai"; import { Code } from "./code"; -import { Hash } from "../hash"; -describe("Code Class Tests", function() { +describe("Code Class Tests", function () { const sampleHex = "abcdef0123456789"; const sampleBuffer = Buffer.from(sampleHex, "hex"); - it("should create Code from buffer", function() { + it("should create Code from buffer", function () { const code = Code.fromBuffer(sampleBuffer); assert.instanceOf(code, Code); assert.equal(code.toString(), sampleHex); }); - it("should create Code from hex string", function() { + it("should create Code from hex string", function () { const code = Code.fromHex(sampleHex); assert.instanceOf(code, Code); assert.equal(code.toString(), sampleHex); }); - it("should return the correct buffer from valueOf", function() { + it("should return the correct buffer from valueOf", function () { const code = Code.fromHex(sampleHex); const buffer = code.valueOf(); @@ -28,11 +27,11 @@ describe("Code Class Tests", function() { assert.equal(buffer.toString("hex"), sampleHex); }); - it("should compute hash correctly", function() { + it("should compute hash correctly", function () { const code = Code.fromHex(sampleHex); const hash = code.computeHash(); assert.instanceOf(hash, Buffer); - assert.equal(hash.toString('hex'), 'ac86b78afd9bdda3641a47a4aff2a7ee26acd40cc534d63655e9dfbf3f890a02') + assert.equal(hash.toString("hex"), "ac86b78afd9bdda3641a47a4aff2a7ee26acd40cc534d63655e9dfbf3f890a02"); }); }); diff --git a/src/smartcontracts/code.ts b/src/smartcontracts/code.ts index 54c540a35..e7a8b68d2 100644 --- a/src/smartcontracts/code.ts +++ b/src/smartcontracts/code.ts @@ -1,7 +1,5 @@ -import { Hash } from "../hash"; - -const createHasher = require('blake2b') -const CODE_HASH_LENGTH = 32 +const createHasher = require("blake2b"); +const CODE_HASH_LENGTH = 32; /** * Bytecode of a Smart Contract, as an abstraction. @@ -24,7 +22,7 @@ export class Code { * Creates a Code object from a hex-encoded string. */ static fromHex(hex: string): Code { - return new Code(hex) + return new Code(hex); } /** @@ -39,10 +37,8 @@ export class Code { } computeHash(): Buffer { - const hash = createHasher(CODE_HASH_LENGTH) - .update(this.valueOf()) - .digest(); + const hash = createHasher(CODE_HASH_LENGTH).update(this.valueOf()).digest(); - return Buffer.from(hash) + return Buffer.from(hash); } } diff --git a/src/smartcontracts/codeMetadata.spec.ts b/src/smartcontracts/codeMetadata.spec.ts index f5d565083..7b0eab88c 100644 --- a/src/smartcontracts/codeMetadata.spec.ts +++ b/src/smartcontracts/codeMetadata.spec.ts @@ -1,8 +1,8 @@ import { assert } from "chai"; import { CodeMetadata } from "./codeMetadata"; -describe("CodeMetadata Class Tests", function() { - it("should create a default CodeMetadata instance", function() { +describe("CodeMetadata Class Tests", function () { + it("should create a default CodeMetadata instance", function () { const metadata = new CodeMetadata(); assert.isTrue(metadata.upgradeable); assert.isFalse(metadata.readable); @@ -10,7 +10,7 @@ describe("CodeMetadata Class Tests", function() { assert.isFalse(metadata.payableBySc); }); - it("should toggle properties correctly", function() { + it("should toggle properties correctly", function () { const metadata = new CodeMetadata(); metadata.toggleUpgradeable(false); metadata.toggleReadable(true); @@ -23,7 +23,7 @@ describe("CodeMetadata Class Tests", function() { assert.isTrue(metadata.payableBySc); }); - it("should convert to buffer correctly", function() { + it("should convert to buffer correctly", function () { const metadata = new CodeMetadata(true, true, true, true); const buffer = metadata.toBuffer(); @@ -32,7 +32,7 @@ describe("CodeMetadata Class Tests", function() { assert.equal(buffer[1], CodeMetadata.ByteOne.Payable | CodeMetadata.ByteOne.PayableBySc); }); - it("should create from buffer correctly when all flags are set", function() { + it("should create from buffer correctly when all flags are set", function () { const buffer = Buffer.from([ CodeMetadata.ByteZero.Upgradeable | CodeMetadata.ByteZero.Readable, CodeMetadata.ByteOne.Payable | CodeMetadata.ByteOne.PayableBySc, @@ -45,7 +45,7 @@ describe("CodeMetadata Class Tests", function() { assert.isTrue(metadata.payableBySc); }); - it("should create from buffer correctly when some flags are set", function() { + it("should create from buffer correctly when some flags are set", function () { const buffer = Buffer.from([CodeMetadata.ByteZero.Upgradeable, CodeMetadata.ByteOne.PayableBySc]); const metadata = CodeMetadata.fromBuffer(buffer); @@ -55,11 +55,28 @@ describe("CodeMetadata Class Tests", function() { assert.isTrue(metadata.payableBySc); }); - it("should handle buffer too short error", function() { + it("should handle buffer too short error", function () { const buffer = Buffer.from([CodeMetadata.ByteZero.Upgradeable]); - assert.throws(() => { - CodeMetadata.fromBuffer(buffer); - }, Error, "Buffer is too short."); + assert.throws( + () => { + CodeMetadata.fromBuffer(buffer); + }, + Error, + "Buffer is too short.", + ); + }); + + it("should test code metadata from bytes", () => { + const bytes = new Uint8Array([1, 0]); + const codeMetadata = CodeMetadata.fromBytes(bytes); + + assert.equal(codeMetadata.toString(), "0100"); + assert.deepEqual(codeMetadata.toJSON(), { + upgradeable: true, + readable: false, + payable: false, + payableBySc: false, + }); }); }); diff --git a/src/smartcontracts/codeMetadata.ts b/src/smartcontracts/codeMetadata.ts index 03a549921..2133e66e2 100644 --- a/src/smartcontracts/codeMetadata.ts +++ b/src/smartcontracts/codeMetadata.ts @@ -6,6 +6,7 @@ export class CodeMetadata { public readable: boolean; public payable: boolean; public payableBySc: boolean; + private static readonly codeMetadataLength = 2; static ByteZero = { Upgradeable: 1, @@ -34,11 +35,15 @@ export class CodeMetadata { this.payableBySc = payableBySc } + static fromBytes(bytes: Uint8Array): CodeMetadata { + return CodeMetadata.fromBuffer(Buffer.from(bytes)); + } + /** * Creates a metadata object from a buffer. */ static fromBuffer(buffer: Buffer): CodeMetadata { - if (buffer.length < 2) { + if (buffer.length < this.codeMetadataLength) { throw new Error('Buffer is too short.'); } diff --git a/src/smartcontracts/codec/address.ts b/src/smartcontracts/codec/address.ts index 5c8d57fad..8e640d540 100644 --- a/src/smartcontracts/codec/address.ts +++ b/src/smartcontracts/codec/address.ts @@ -4,7 +4,7 @@ import { AddressValue } from "../typesystem"; export class AddressBinaryCodec { /** * Reads and decodes an AddressValue from a given buffer. - * + * * @param buffer the input buffer */ decodeNested(buffer: Buffer): [AddressValue, number] { @@ -17,11 +17,11 @@ export class AddressBinaryCodec { /** * Reads and decodes an AddressValue from a given buffer. - * + * * @param buffer the input buffer */ decodeTopLevel(buffer: Buffer): AddressValue { - let [decoded, length] = this.decodeNested(buffer); + let [decoded, _length] = this.decodeNested(buffer); return decoded; } diff --git a/src/smartcontracts/codec/binary.spec.ts b/src/smartcontracts/codec/binary.spec.ts index 9042ce67c..95d2304da 100644 --- a/src/smartcontracts/codec/binary.spec.ts +++ b/src/smartcontracts/codec/binary.spec.ts @@ -1,7 +1,7 @@ import * as errors from "../../errors"; import { assert } from "chai"; import { BinaryCodec, BinaryCodecConstraints } from "./binary"; -import { AddressType, AddressValue, BigIntType, BigUIntType, BigUIntValue, BooleanType, BooleanValue, I16Type, I32Type, I64Type, I8Type, NumericalType, NumericalValue, Struct, Field, StructType, TypedValue, U16Type, U32Type, U32Value, U64Type, U64Value, U8Type, U8Value, List, ListType, EnumType, EnumVariantDefinition, EnumValue, ArrayVec, ArrayVecType, U16Value, TokenIdentifierType, TokenIdentifierValue, StringValue, StringType } from "../typesystem"; +import { AddressType, AddressValue, BigIntType, BigUIntType, BigUIntValue, BooleanType, BooleanValue, I16Type, I32Type, I64Type, I8Type, NumericalType, NumericalValue, Struct, Field, StructType, TypedValue, U16Type, U32Type, U32Value, U64Type, U64Value, U8Type, U8Value, List, ListType, EnumType, EnumVariantDefinition, EnumValue, ArrayVec, ArrayVecType, U16Value, TokenIdentifierType, TokenIdentifierValue, StringValue, StringType, BigIntValue, I64Value, I32Value, I16Value, I8Value } from "../typesystem"; import { isMsbOne } from "./utils"; import { Address } from "../../address"; import { BytesType, BytesValue } from "../typesystem/bytes"; @@ -85,6 +85,20 @@ describe("test binary codec (basic)", () => { } }); + it("should create numeric values, from both bigint and BigNumber.Value", async () => { + assert.deepEqual(new BigUIntValue("0xabcdefabcdefabcdef"), new BigUIntValue(BigInt("0xabcdefabcdefabcdef"))); + assert.deepEqual(new U64Value("0xabcdef"), new U64Value(BigInt(0xabcdef))); + assert.deepEqual(new U32Value("0xabcdef"), new U32Value(BigInt(0xabcdef))); + assert.deepEqual(new U16Value("0xabcdef"), new U16Value(BigInt(0xabcdef))); + assert.deepEqual(new U8Value("0xabcdef"), new U8Value(BigInt(0xabcdef))); + + assert.deepEqual(new BigIntValue(BigInt("0xabcdefabcdefabcdef")), new BigIntValue(BigInt("0xabcdefabcdefabcdef"))); + assert.deepEqual(new I64Value("0xabcdef"), new I64Value(BigInt(0xabcdef))); + assert.deepEqual(new I32Value("0xabcdef"), new I32Value(BigInt(0xabcdef))); + assert.deepEqual(new I16Value("0xabcdef"), new I16Value(BigInt(0xabcdef))); + assert.deepEqual(new I8Value("0xabcdef"), new I8Value(BigInt(0xabcdef))); + }); + it("should create bytes and strings, encode and decode", async () => { let bytesValue = BytesValue.fromHex("74657374"); let stringValue = StringValue.fromHex("74657374"); diff --git a/src/smartcontracts/codec/binary.ts b/src/smartcontracts/codec/binary.ts index 519ad2a5e..1b9709315 100644 --- a/src/smartcontracts/codec/binary.ts +++ b/src/smartcontracts/codec/binary.ts @@ -80,13 +80,7 @@ export class BinaryCodec { } encodeNested(typedValue: TypedValue): Buffer { - guardTrue( - typedValue - .getType() - .getCardinality() - .isSingular(), - "singular cardinality, thus encodable type" - ); + guardTrue(typedValue.getType().getCardinality().isSingular(), "singular cardinality, thus encodable type"); return onTypedValueSelect(typedValue, { onPrimitive: () => this.primitiveCodec.encodeNested(typedValue), @@ -100,13 +94,7 @@ export class BinaryCodec { } encodeTopLevel(typedValue: TypedValue): Buffer { - guardTrue( - typedValue - .getType() - .getCardinality() - .isSingular(), - "singular cardinality, thus encodable type" - ); + guardTrue(typedValue.getType().getCardinality().isSingular(), "singular cardinality, thus encodable type"); return onTypedValueSelect(typedValue, { onPrimitive: () => this.primitiveCodec.encodeTopLevel(typedValue), diff --git a/src/smartcontracts/codec/enum.ts b/src/smartcontracts/codec/enum.ts index 4d9be4c89..d73e79bd2 100644 --- a/src/smartcontracts/codec/enum.ts +++ b/src/smartcontracts/codec/enum.ts @@ -23,7 +23,7 @@ export class EnumBinaryCodec { let variant = type.getVariantByDiscriminant(discriminant); let fieldDefinitions = variant.getFieldsDefinitions(); - + let [fields, lengthOfFields]: [Field[], number] = this.fieldsCodec.decodeNested(buffer, fieldDefinitions); let enumValue = new EnumValue(type, variant, fields); @@ -33,7 +33,7 @@ export class EnumBinaryCodec { private readDiscriminant(buffer: Buffer): [discriminant: number, length: number] { let [value, length] = this.binaryCodec.decodeNested(buffer, new U8Type()); let discriminant = value.valueOf(); - + return [discriminant, length]; } @@ -51,9 +51,11 @@ export class EnumBinaryCodec { let fields = enumValue.getFields(); let hasFields = fields.length > 0; let fieldsBuffer = this.fieldsCodec.encodeNested(fields); - + let discriminant = new U8Value(enumValue.discriminant); - let discriminantBuffer = hasFields ? this.binaryCodec.encodeNested(discriminant) : this.binaryCodec.encodeTopLevel(discriminant); + let discriminantBuffer = hasFields + ? this.binaryCodec.encodeNested(discriminant) + : this.binaryCodec.encodeTopLevel(discriminant); return Buffer.concat([discriminantBuffer, fieldsBuffer]); } diff --git a/src/smartcontracts/codec/fields.ts b/src/smartcontracts/codec/fields.ts index d5b6dee28..31a88ab58 100644 --- a/src/smartcontracts/codec/fields.ts +++ b/src/smartcontracts/codec/fields.ts @@ -20,13 +20,13 @@ export class FieldsBinaryCodec { let field = new Field(decoded, fieldDefinition.name); fields.push(field); } - + return [fields, totalLength]; } encodeNested(fields: ReadonlyArray): Buffer { let buffers: Buffer[] = []; - + for (const field of fields) { let fieldBuffer = this.binaryCodec.encodeNested(field.value); buffers.push(fieldBuffer); diff --git a/src/smartcontracts/codec/h256.ts b/src/smartcontracts/codec/h256.ts index 7806c2a80..27a043c43 100644 --- a/src/smartcontracts/codec/h256.ts +++ b/src/smartcontracts/codec/h256.ts @@ -3,7 +3,7 @@ import { H256Value } from "../typesystem/h256"; export class H256BinaryCodec { /** * Reads and decodes a H256Value from a given buffer. - * + * * @param buffer the input buffer */ decodeNested(buffer: Buffer): [H256Value, number] { @@ -14,11 +14,11 @@ export class H256BinaryCodec { /** * Reads and decodes a H256Value from a given buffer. - * + * * @param buffer the input buffer */ decodeTopLevel(buffer: Buffer): H256Value { - let [decoded, length] = this.decodeNested(buffer); + let [decoded, _length] = this.decodeNested(buffer); return decoded; } diff --git a/src/smartcontracts/codec/numerical.ts b/src/smartcontracts/codec/numerical.ts index 38016615b..9dd27f6e6 100644 --- a/src/smartcontracts/codec/numerical.ts +++ b/src/smartcontracts/codec/numerical.ts @@ -1,7 +1,15 @@ import BigNumber from "bignumber.js"; import { NumericalType, NumericalValue } from "../typesystem"; import { SizeOfU32 } from "./constants"; -import { bigIntToBuffer, bufferToBigInt, cloneBuffer, flipBufferBitsInPlace, isMsbOne, isMsbZero, prependByteToBuffer } from "./utils"; +import { + bigIntToBuffer, + bufferToBigInt, + cloneBuffer, + flipBufferBitsInPlace, + isMsbOne, + isMsbZero, + prependByteToBuffer, +} from "./utils"; /** * Encodes and decodes "NumericalValue" objects. @@ -91,7 +99,7 @@ export class NumericalBinaryCodec { // Fix ambiguity if any if (isMsbZero(buffer)) { - buffer = prependByteToBuffer(buffer, 0xFF); + buffer = prependByteToBuffer(buffer, 0xff); } const paddingBytes = Buffer.alloc(size - buffer.length, 0xff); @@ -135,7 +143,7 @@ export class NumericalBinaryCodec { // Fix ambiguity if any if (isMsbZero(buffer)) { - buffer = prependByteToBuffer(buffer, 0xFF); + buffer = prependByteToBuffer(buffer, 0xff); } return buffer; diff --git a/src/smartcontracts/codec/option.ts b/src/smartcontracts/codec/option.ts index f10971224..37cdc58ef 100644 --- a/src/smartcontracts/codec/option.ts +++ b/src/smartcontracts/codec/option.ts @@ -34,7 +34,7 @@ export class OptionValueBinaryCodec { throw new errors.ErrCodec("invalid buffer for optional value"); } - let [decoded, decodedLength] = this.binaryCodec.decodeNested(buffer.slice(1), type); + let [decoded, _decodedLength] = this.binaryCodec.decodeNested(buffer.slice(1), type); return new OptionValue(type, decoded); } diff --git a/src/smartcontracts/codec/primitive.ts b/src/smartcontracts/codec/primitive.ts index 833b9e141..f761c0df2 100644 --- a/src/smartcontracts/codec/primitive.ts +++ b/src/smartcontracts/codec/primitive.ts @@ -56,7 +56,7 @@ export class PrimitiveBinaryCodec { onString: () => this.stringCodec.decodeNested(buffer), onH256: () => this.h256Codec.decodeNested(buffer), onTokenIndetifier: () => this.tokenIdentifierCodec.decodeNested(buffer), - onNothing: () => this.nothingCodec.decodeNested() + onNothing: () => this.nothingCodec.decodeNested(), }); } @@ -69,7 +69,7 @@ export class PrimitiveBinaryCodec { onString: () => this.stringCodec.decodeTopLevel(buffer), onH256: () => this.h256Codec.decodeTopLevel(buffer), onTokenIndetifier: () => this.tokenIdentifierCodec.decodeTopLevel(buffer), - onNothing: () => this.nothingCodec.decodeTopLevel() + onNothing: () => this.nothingCodec.decodeTopLevel(), }); } @@ -82,7 +82,7 @@ export class PrimitiveBinaryCodec { onString: () => this.stringCodec.encodeNested(value), onH256: () => this.h256Codec.encodeNested(value), onTypeIdentifier: () => this.tokenIdentifierCodec.encodeNested(value), - onNothing: () => this.nothingCodec.encodeNested() + onNothing: () => this.nothingCodec.encodeNested(), }); } @@ -95,7 +95,7 @@ export class PrimitiveBinaryCodec { onString: () => this.stringCodec.encodeTopLevel(value), onH256: () => this.h256Codec.encodeTopLevel(value), onTypeIdentifier: () => this.tokenIdentifierCodec.encodeTopLevel(value), - onNothing: () => this.nothingCodec.encodeTopLevel() + onNothing: () => this.nothingCodec.encodeTopLevel(), }); } } diff --git a/src/smartcontracts/function.ts b/src/smartcontracts/function.ts index 83d55e3f0..b8547e46a 100644 --- a/src/smartcontracts/function.ts +++ b/src/smartcontracts/function.ts @@ -11,7 +11,7 @@ export class ContractFunction { /** * Creates a ContractFunction object, given its name. - * + * * @param name the name of the function */ constructor(name: string) { diff --git a/src/smartcontracts/interaction.local.net.spec.ts b/src/smartcontracts/interaction.local.net.spec.ts index 802724e35..16247af5f 100644 --- a/src/smartcontracts/interaction.local.net.spec.ts +++ b/src/smartcontracts/interaction.local.net.spec.ts @@ -4,17 +4,26 @@ import { loadAbiRegistry, loadTestWallets, prepareDeployment, TestWallet } from import { ContractController } from "../testutils/contractController"; import { createLocalnetProvider } from "../testutils/networkProviders"; import { Transaction } from "../transaction"; +import { TransactionComputer } from "../transactionComputer"; import { Interaction } from "./interaction"; import { ReturnCode } from "./returnCode"; import { SmartContract } from "./smartContract"; - +import { TransactionsFactoryConfig } from "../transactionsFactories/transactionsFactoryConfig"; +import { SmartContractTransactionsFactory } from "../transactionsFactories/smartContractTransactionsFactory"; +import { promises } from "fs"; +import { ResultsParser } from "./resultsParser"; +import { TransactionWatcher } from "../transactionWatcher"; +import { SmartContractQueriesController } from "../smartContractQueriesController"; +import { QueryRunnerAdapter } from "../adapters/queryRunnerAdapter"; describe("test smart contract interactor", function () { let provider = createLocalnetProvider(); let alice: TestWallet; + let resultsParser: ResultsParser; before(async function () { ({ alice } = await loadTestWallets()); + resultsParser = new ResultsParser(); }); it("should interact with 'answer' (local testnet)", async function () { @@ -34,16 +43,21 @@ describe("test smart contract interactor", function () { codePath: "src/testdata/answer.wasm", gasLimit: 3000000, initArguments: [], - chainID: network.ChainID + chainID: network.ChainID, }); - let { bundle: { returnCode } } = await controller.deploy(deployTransaction); + let { + bundle: { returnCode }, + } = await controller.deploy(deployTransaction); assert.isTrue(returnCode.isSuccess()); - const interaction = contract.methods.getUltimateAnswer() - .withGasLimit(3000000) - .withChainID(network.ChainID) - .withSender(alice.address); + const interaction = ( + contract.methods + .getUltimateAnswer() + .withGasLimit(3000000) + .withChainID(network.ChainID) + .withSender(alice.address) + ); // Query let queryResponseBundle = await controller.query(interaction); @@ -61,10 +75,7 @@ describe("test smart contract interactor", function () { await provider.sendTransaction(transaction); // Execute, and wait for execution - transaction = interaction - .withSender(alice.address) - .useThenIncrementNonceOf(alice.account) - .buildTransaction(); + transaction = interaction.withSender(alice.address).useThenIncrementNonceOf(alice.account).buildTransaction(); await signTransaction({ transaction: transaction, wallet: alice }); let { bundle: executionResultsBundle } = await controller.execute(interaction, transaction); @@ -74,6 +85,105 @@ describe("test smart contract interactor", function () { assert.isTrue(executionResultsBundle.returnCode.equals(ReturnCode.Ok)); }); + it("should interact with 'answer' (local testnet) using the SmartContractTransactionsFactory", async function () { + this.timeout(80000); + + let abiRegistry = await loadAbiRegistry("src/testdata/answer.abi.json"); + + let network = await provider.getNetworkConfig(); + await alice.sync(provider); + + const config = new TransactionsFactoryConfig({ chainID: network.ChainID }); + const factory = new SmartContractTransactionsFactory({ + config: config, + abi: abiRegistry, + }); + + const bytecode = await promises.readFile("src/testdata/answer.wasm"); + + const deployTransaction = factory.createTransactionForDeploy({ + sender: alice.address, + bytecode: bytecode, + gasLimit: 3000000n, + }); + deployTransaction.nonce = BigInt(alice.account.nonce.valueOf()); + + const transactionComputer = new TransactionComputer(); + deployTransaction.signature = await alice.signer.sign( + Buffer.from(transactionComputer.computeBytesForSigning(deployTransaction)), + ); + + const contractAddress = SmartContract.computeAddress(alice.address, alice.account.nonce); + alice.account.incrementNonce(); + + const transactionCompletionAwaiter = new TransactionWatcher({ + getTransaction: async (hash: string) => { + return await provider.getTransaction(hash, true); + }, + }); + + const deployTxHash = await provider.sendTransaction(deployTransaction); + let transactionOnNetwork = await transactionCompletionAwaiter.awaitCompleted(deployTxHash); + const untypedBundle = resultsParser.parseUntypedOutcome(transactionOnNetwork); + assert.isTrue(untypedBundle.returnCode.isSuccess()); + + const queryRunner = new QueryRunnerAdapter({ networkProvider: provider }); + const queryController = new SmartContractQueriesController({ abi: abiRegistry, queryRunner: queryRunner }); + + const query = queryController.createQuery({ + contract: contractAddress.bech32(), + caller: alice.address.bech32(), + function: "getUltimateAnswer", + arguments: [], + }); + + const queryResponse = await queryController.runQuery(query); + const parsed = queryController.parseQueryResponse(queryResponse); + assert.lengthOf(parsed, 1); + assert.deepEqual(parsed[0], new BigNumber(42)); + + // Query + let transaction = factory.createTransactionForExecute({ + sender: alice.address, + contract: contractAddress, + function: "getUltimateAnswer", + gasLimit: 3000000n, + }); + transaction.nonce = BigInt(alice.account.nonce.valueOf()); + transaction.signature = await alice.signer.sign( + Buffer.from(transactionComputer.computeBytesForSigning(transaction)), + ); + + alice.account.incrementNonce(); + + await provider.sendTransaction(transaction); + + // Execute, and wait for execution + transaction = factory.createTransactionForExecute({ + sender: alice.address, + contract: contractAddress, + function: "getUltimateAnswer", + gasLimit: 3000000n, + }); + transaction.nonce = BigInt(alice.account.nonce.valueOf()); + transaction.signature = await alice.signer.sign( + Buffer.from(transactionComputer.computeBytesForSigning(transaction)), + ); + + alice.account.incrementNonce(); + + const executeTxHash = await provider.sendTransaction(transaction); + transactionOnNetwork = await transactionCompletionAwaiter.awaitCompleted(executeTxHash); + const typedBundle = resultsParser.parseOutcome( + transactionOnNetwork, + abiRegistry.getEndpoint("getUltimateAnswer"), + ); + + assert.lengthOf(typedBundle.values, 1); + assert.deepEqual(typedBundle.firstValue!.valueOf(), new BigNumber(42)); + assert.isTrue(typedBundle.returnCode.equals(ReturnCode.Ok)); + }); + it("should interact with 'counter' (local testnet)", async function () { this.timeout(120000); @@ -91,10 +201,12 @@ describe("test smart contract interactor", function () { codePath: "src/testdata/counter.wasm", gasLimit: 3000000, initArguments: [], - chainID: network.ChainID + chainID: network.ChainID, }); - let { bundle: { returnCode } } = await controller.deploy(deployTransaction); + let { + bundle: { returnCode }, + } = await controller.deploy(deployTransaction); assert.isTrue(returnCode.isSuccess()); let getInteraction = contract.methods.get(); @@ -114,7 +226,9 @@ describe("test smart contract interactor", function () { // Increment, wait for execution. let incrementTransaction = incrementInteraction.useThenIncrementNonceOf(alice.account).buildTransaction(); await signTransaction({ transaction: incrementTransaction, wallet: alice }); - let { bundle: { firstValue: valueAfterIncrement } } = await controller.execute(incrementInteraction, incrementTransaction); + let { + bundle: { firstValue: valueAfterIncrement }, + } = await controller.execute(incrementInteraction, incrementTransaction); assert.deepEqual(valueAfterIncrement!.valueOf(), new BigNumber(2)); // Decrement twice. Wait for execution of the second transaction. @@ -124,10 +238,111 @@ describe("test smart contract interactor", function () { decrementTransaction = decrementInteraction.useThenIncrementNonceOf(alice.account).buildTransaction(); await signTransaction({ transaction: decrementTransaction, wallet: alice }); - let { bundle: { firstValue: valueAfterDecrement } } = await controller.execute(decrementInteraction, decrementTransaction); + let { + bundle: { firstValue: valueAfterDecrement }, + } = await controller.execute(decrementInteraction, decrementTransaction); assert.deepEqual(valueAfterDecrement!.valueOf(), new BigNumber(0)); }); + it("should interact with 'counter' (local testnet) using the SmartContractTransactionsFactory", async function () { + this.timeout(120000); + + let abiRegistry = await loadAbiRegistry("src/testdata/counter.abi.json"); + + let network = await provider.getNetworkConfig(); + await alice.sync(provider); + + const config = new TransactionsFactoryConfig({ chainID: network.ChainID }); + const factory = new SmartContractTransactionsFactory({ + config: config, + abi: abiRegistry, + }); + + const bytecode = await promises.readFile("src/testdata/counter.wasm"); + + const deployTransaction = factory.createTransactionForDeploy({ + sender: alice.address, + bytecode: bytecode, + gasLimit: 3000000n, + }); + deployTransaction.nonce = BigInt(alice.account.nonce.valueOf()); + + const transactionComputer = new TransactionComputer(); + deployTransaction.signature = await alice.signer.sign( + Buffer.from(transactionComputer.computeBytesForSigning(deployTransaction)), + ); + + const contractAddress = SmartContract.computeAddress(alice.address, alice.account.nonce); + alice.account.incrementNonce(); + + const transactionCompletionAwaiter = new TransactionWatcher({ + getTransaction: async (hash: string) => { + return await provider.getTransaction(hash, true); + }, + }); + + const deployTxHash = await provider.sendTransaction(deployTransaction); + let transactionOnNetwork = await transactionCompletionAwaiter.awaitCompleted(deployTxHash); + const untypedBundle = resultsParser.parseUntypedOutcome(transactionOnNetwork); + assert.isTrue(untypedBundle.returnCode.isSuccess()); + + const queryRunner = new QueryRunnerAdapter({ networkProvider: provider }); + const queryController = new SmartContractQueriesController({ abi: abiRegistry, queryRunner: queryRunner }); + + let incrementTransaction = factory.createTransactionForExecute({ + sender: alice.address, + contract: contractAddress, + function: "increment", + gasLimit: 3000000n, + }); + incrementTransaction.nonce = BigInt(alice.account.nonce.valueOf()); + + incrementTransaction.signature = await alice.signer.sign( + Buffer.from(transactionComputer.computeBytesForSigning(incrementTransaction)), + ); + + alice.account.incrementNonce(); + + // Query "get()" + const query = queryController.createQuery({ + contract: contractAddress.bech32(), + function: "get", + arguments: [], + }); + const queryResponse = await queryController.runQuery(query); + const parsed = queryController.parseQueryResponse(queryResponse); + assert.deepEqual(parsed[0], new BigNumber(1)); + + const incrementTxHash = await provider.sendTransaction(incrementTransaction); + transactionOnNetwork = await transactionCompletionAwaiter.awaitCompleted(incrementTxHash); + let typedBundle = resultsParser.parseOutcome(transactionOnNetwork, abiRegistry.getEndpoint("increment")); + assert.deepEqual(typedBundle.firstValue!.valueOf(), new BigNumber(2)); + + let decrementTransaction = factory.createTransactionForExecute({ + sender: alice.address, + contract: contractAddress, + function: "decrement", + gasLimit: 3000000n, + }); + decrementTransaction.nonce = BigInt(alice.account.nonce.valueOf()); + decrementTransaction.signature = await alice.signer.sign( + Buffer.from(transactionComputer.computeBytesForSigning(decrementTransaction)), + ); + + alice.account.incrementNonce(); + + await provider.sendTransaction(decrementTransaction); + + decrementTransaction.nonce = BigInt(alice.account.nonce.valueOf()); + decrementTransaction.signature = await alice.signer.sign( + Buffer.from(transactionComputer.computeBytesForSigning(decrementTransaction)), + ); + + const decrementTxHash = await provider.sendTransaction(decrementTransaction); + transactionOnNetwork = await transactionCompletionAwaiter.awaitCompleted(decrementTxHash); + typedBundle = resultsParser.parseOutcome(transactionOnNetwork, abiRegistry.getEndpoint("decrement")); + }); + it("should interact with 'lottery-esdt' (local testnet)", async function () { this.timeout(140000); @@ -145,35 +360,37 @@ describe("test smart contract interactor", function () { codePath: "src/testdata/lottery-esdt.wasm", gasLimit: 100000000, initArguments: [], - chainID: network.ChainID + chainID: network.ChainID, }); - let { bundle: { returnCode } } = await controller.deploy(deployTransaction); + let { + bundle: { returnCode }, + } = await controller.deploy(deployTransaction); assert.isTrue(returnCode.isSuccess()); - let startInteraction = contract.methods.start([ - "lucky", - "EGLD", - 1, - null, - null, - 1, - null, - null - ]) - .withGasLimit(30000000) - .withChainID(network.ChainID) - .withSender(alice.address); - - let lotteryStatusInteraction = contract.methods.status(["lucky"]) - .withGasLimit(5000000) - .withChainID(network.ChainID) - .withSender(alice.address); - - let getLotteryInfoInteraction = contract.methods.getLotteryInfo(["lucky"]) - .withGasLimit(5000000) - .withChainID(network.ChainID) - .withSender(alice.address); + let startInteraction = ( + contract.methods + .start(["lucky", "EGLD", 1, null, null, 1, null, null]) + .withGasLimit(30000000) + .withChainID(network.ChainID) + .withSender(alice.address) + ); + + let lotteryStatusInteraction = ( + contract.methods + .status(["lucky"]) + .withGasLimit(5000000) + .withChainID(network.ChainID) + .withSender(alice.address) + ); + + let getLotteryInfoInteraction = ( + contract.methods + .getLotteryInfo(["lucky"]) + .withGasLimit(5000000) + .withChainID(network.ChainID) + .withSender(alice.address) + ); // start() let startTransaction = startInteraction @@ -219,16 +436,137 @@ describe("test smart contract interactor", function () { tickets_left: new BigNumber(800), max_entries_per_user: new BigNumber(1), prize_distribution: Buffer.from([0x64]), - prize_pool: new BigNumber("0") + prize_pool: new BigNumber("0"), + }); + }); + + it("should interact with 'lottery-esdt' (local testnet) using the SmartContractTransactionsFactory", async function () { + this.timeout(140000); + + let abiRegistry = await loadAbiRegistry("src/testdata/lottery-esdt.abi.json"); + + let network = await provider.getNetworkConfig(); + await alice.sync(provider); + + const config = new TransactionsFactoryConfig({ chainID: network.ChainID }); + const factory = new SmartContractTransactionsFactory({ + config: config, + abi: abiRegistry, + }); + + const bytecode = await promises.readFile("src/testdata/lottery-esdt.wasm"); + + // Deploy the contract + const deployTransaction = factory.createTransactionForDeploy({ + sender: alice.address, + bytecode: bytecode, + gasLimit: 100000000n, + }); + deployTransaction.nonce = BigInt(alice.account.nonce.valueOf()); + + const transactionComputer = new TransactionComputer(); + deployTransaction.signature = await alice.signer.sign( + Buffer.from(transactionComputer.computeBytesForSigning(deployTransaction)), + ); + + const contractAddress = SmartContract.computeAddress(alice.address, alice.account.nonce); + alice.account.incrementNonce(); + + const transactionCompletionAwaiter = new TransactionWatcher({ + getTransaction: async (hash: string) => { + return await provider.getTransaction(hash, true); + }, + }); + + const deployTxHash = await provider.sendTransaction(deployTransaction); + let transactionOnNetwork = await transactionCompletionAwaiter.awaitCompleted(deployTxHash); + const untypedBundle = resultsParser.parseUntypedOutcome(transactionOnNetwork); + assert.isTrue(untypedBundle.returnCode.isSuccess()); + + // start() + let startTransaction = factory.createTransactionForExecute({ + sender: alice.address, + contract: contractAddress, + function: "start", + arguments: ["lucky", "EGLD", 1, null, null, 1, null, null], + gasLimit: 30000000n, + }); + startTransaction.nonce = BigInt(alice.account.nonce.valueOf()); + startTransaction.signature = await alice.signer.sign( + Buffer.from(transactionComputer.computeBytesForSigning(startTransaction)), + ); + + alice.account.incrementNonce(); + + const startTxHash = await provider.sendTransaction(startTransaction); + transactionOnNetwork = await transactionCompletionAwaiter.awaitCompleted(startTxHash); + let typedBundle = resultsParser.parseOutcome(transactionOnNetwork, abiRegistry.getEndpoint("start")); + assert.equal(typedBundle.returnCode.valueOf(), "ok"); + assert.lengthOf(typedBundle.values, 0); + + // status() + let lotteryStatusTransaction = factory.createTransactionForExecute({ + sender: alice.address, + contract: contractAddress, + function: "status", + arguments: ["lucky"], + gasLimit: 5000000n, + }); + lotteryStatusTransaction.nonce = BigInt(alice.account.nonce.valueOf()); + lotteryStatusTransaction.signature = await alice.signer.sign( + Buffer.from(transactionComputer.computeBytesForSigning(lotteryStatusTransaction)), + ); + + alice.account.incrementNonce(); + + const statusTxHash = await provider.sendTransaction(lotteryStatusTransaction); + transactionOnNetwork = await transactionCompletionAwaiter.awaitCompleted(statusTxHash); + typedBundle = resultsParser.parseOutcome(transactionOnNetwork, abiRegistry.getEndpoint("status")); + assert.equal(typedBundle.returnCode.valueOf(), "ok"); + assert.lengthOf(typedBundle.values, 1); + assert.equal(typedBundle.firstValue!.valueOf().name, "Running"); + + // getlotteryInfo() (this is a view function, but for the sake of the test, we'll execute it) + let lotteryInfoTransaction = factory.createTransactionForExecute({ + sender: alice.address, + contract: contractAddress, + function: "getLotteryInfo", + arguments: ["lucky"], + gasLimit: 5000000n, + }); + lotteryInfoTransaction.nonce = BigInt(alice.account.nonce.valueOf()); + lotteryInfoTransaction.signature = await alice.signer.sign( + Buffer.from(transactionComputer.computeBytesForSigning(lotteryInfoTransaction)), + ); + + alice.account.incrementNonce(); + + const infoTxHash = await provider.sendTransaction(lotteryInfoTransaction); + transactionOnNetwork = await transactionCompletionAwaiter.awaitCompleted(infoTxHash); + typedBundle = resultsParser.parseOutcome(transactionOnNetwork, abiRegistry.getEndpoint("getLotteryInfo")); + assert.equal(typedBundle.returnCode.valueOf(), "ok"); + assert.lengthOf(typedBundle.values, 1); + + // Ignore "deadline" field in our test + let info = typedBundle.firstValue!.valueOf(); + delete info.deadline; + + assert.deepEqual(info, { + token_identifier: "EGLD", + ticket_price: new BigNumber("1"), + tickets_left: new BigNumber(800), + max_entries_per_user: new BigNumber(1), + prize_distribution: Buffer.from([0x64]), + prize_pool: new BigNumber("0"), }); }); - async function signTransaction(options: { transaction: Transaction, wallet: TestWallet }) { + async function signTransaction(options: { transaction: Transaction; wallet: TestWallet }) { const transaction = options.transaction; const wallet = options.wallet; const serialized = transaction.serializeForSigning(); - const signature = await wallet.signerNext.sign(serialized); + const signature = await wallet.signer.sign(serialized); transaction.applySignature(signature); } }); diff --git a/src/smartcontracts/interaction.spec.ts b/src/smartcontracts/interaction.spec.ts index 2fb119d16..0d3d73297 100644 --- a/src/smartcontracts/interaction.spec.ts +++ b/src/smartcontracts/interaction.spec.ts @@ -5,12 +5,13 @@ import { Address } from "../address"; import { loadAbiRegistry, loadTestWallets, - MockProvider, + MockNetworkProvider, setupUnitTestWatcherTimeouts, TestWallet } from "../testutils"; import { ContractController } from "../testutils/contractController"; -import { TokenTransfer } from "../tokenTransfer"; +import { Token, TokenTransfer } from "../tokens"; +import { Transaction } from "../transaction"; import { ContractFunction } from "./function"; import { Interaction } from "./interaction"; import { ReturnCode } from "./returnCode"; @@ -20,7 +21,7 @@ import { BytesValue } from "./typesystem/bytes"; describe("test smart contract interactor", function () { let dummyAddress = new Address("erd1qqqqqqqqqqqqqpgqak8zt22wl2ph4tswtyc39namqx6ysa2sd8ss4xmlj3"); - let provider = new MockProvider(); + let provider = new MockNetworkProvider(); let alice: TestWallet; before(async function () { @@ -33,6 +34,7 @@ describe("test smart contract interactor", function () { let interaction = new Interaction(contract, dummyFunction, []); let transaction = interaction + .withSender(alice.address) .withNonce(7) .withValue(TokenTransfer.egldFromAmount(1)) .withGasLimit(20000000) @@ -63,6 +65,7 @@ describe("test smart contract interactor", function () { // ESDT, single let transaction = new Interaction(contract, dummyFunction, []) + .withSender(alice) .withSingleESDTTransfer(TokenFoo(10)) .buildTransaction(); @@ -146,7 +149,36 @@ describe("test smart contract interactor", function () { assert.equal(transaction.getSender().bech32(), alice.bech32()); assert.equal(transaction.getReceiver().bech32(), alice.bech32()); - assert.equal(transaction.getData().toString(), `MultiESDTNFTTransfer@${hexContractAddress}@02@${hexStrămoși}@01@01@${hexStrămoși}@2a@01@${hexDummyFunction}`); + }); + + it("should create transaction, with ABI, with transfer & execute", async function () { + const abiRegistry = await loadAbiRegistry("src/testdata/answer.abi.json"); + const contract = new SmartContract({ address: dummyAddress, abi: abiRegistry }); + const alice = new Address("erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"); + const token = new Token({ identifier: "FOO-abcdef", nonce: 0n }); + + const transaction = contract.methods + .getUltimateAnswer() + .withChainID("T") + .withSender(alice) + .withGasLimit(543210) + .withSingleESDTTransfer(new TokenTransfer({ token, amount: 100n })) + .withNonce(42) + .buildTransaction(); + + assert.deepEqual( + transaction, + new Transaction({ + chainID: "T", + sender: alice.toBech32(), + receiver: dummyAddress.toBech32(), + data: Buffer.from("ESDTTransfer@464f4f2d616263646566@64@676574556c74696d617465416e73776572"), + gasLimit: 543210n, + value: 0n, + version: 2, + nonce: 42n, + }), + ); }); it("should interact with 'answer'", async function () { @@ -156,10 +188,9 @@ describe("test smart contract interactor", function () { let contract = new SmartContract({ address: dummyAddress, abi: abiRegistry }); let controller = new ContractController(provider); - let interaction = contract.methods - .getUltimateAnswer() - .withGasLimit(543210) - .withChainID("T"); + let interaction = ( + contract.methods.getUltimateAnswer().withGasLimit(543210).withChainID("T") + ); assert.equal(contract.getAddress(), dummyAddress); assert.deepEqual(interaction.getFunction(), new ContractFunction("getUltimateAnswer")); @@ -180,31 +211,31 @@ describe("test smart contract interactor", function () { assert.isTrue(queryCode.equals(ReturnCode.Ok)); // Execute, do not wait for execution - let transaction = interaction.withNonce(0).buildTransaction(); + let transaction = interaction.withSender(alice.address).withNonce(0).buildTransaction(); transaction.setSender(alice.address); - await alice.signer.sign(transaction); + transaction.applySignature(await alice.signer.sign(transaction.serializeForSigning())); await provider.sendTransaction(transaction); assert.equal(transaction.getNonce().valueOf(), 0); assert.equal(transaction.getData().toString(), "getUltimateAnswer"); assert.equal( transaction.getHash().toString(), - "60d0956a8902c1179dce92d91bd9670e31b9a9cd07c1d620edb7754a315b4818" + "3579ad09099feb9755c860ddd225251170806d833342e912fccdfe2ed5c3a364" ); transaction = interaction.withNonce(1).buildTransaction(); transaction.setSender(alice.address); - await alice.signer.sign(transaction); + transaction.applySignature(await alice.signer.sign(transaction.serializeForSigning())); await provider.sendTransaction(transaction); assert.equal(transaction.getNonce().valueOf(), 1); assert.equal( transaction.getHash().toString(), - "acd207c38f6c3341b18d8ef331fa07ba49615fa12d7610aad5d8495293049f24" + "ad513ce7c5d371d30e48f073326899766736eac1ac231d847d45bc3facbcb496" ); // Execute, and wait for execution transaction = interaction.withNonce(2).buildTransaction(); transaction.setSender(alice.address); - await alice.signer.sign(transaction); + transaction.applySignature(await alice.signer.sign(transaction.serializeForSigning())); provider.mockGetTransactionWithAnyHashAsNotarizedWithOneResult("@6f6b@2bs"); let { bundle } = await controller.execute(interaction, transaction); @@ -235,25 +266,35 @@ describe("test smart contract interactor", function () { assert.deepEqual(counterValue!.valueOf(), new BigNumber(7)); - let incrementTransaction = incrementInteraction.withNonce(14).buildTransaction(); - await alice.signer.sign(incrementTransaction); + let incrementTransaction = incrementInteraction + .withSender(alice.address) + .withNonce(14) + .withChainID("mock") + .buildTransaction(); + + incrementTransaction.applySignature(await alice.signer.sign(incrementTransaction.serializeForSigning())); provider.mockGetTransactionWithAnyHashAsNotarizedWithOneResult("@6f6b@08"); let { bundle: { firstValue: valueAfterIncrement } } = await controller.execute(incrementInteraction, incrementTransaction); assert.deepEqual(valueAfterIncrement!.valueOf(), new BigNumber(8)); // Decrement three times (simulate three parallel broadcasts). Wait for execution of the latter (third transaction). Return fake "5". // Decrement #1 - let decrementTransaction = decrementInteraction.withNonce(15).buildTransaction(); - await alice.signer.sign(decrementTransaction); + let decrementTransaction = decrementInteraction + .withSender(alice.address) + .withNonce(15) + .withChainID("mock") + .buildTransaction(); + + decrementTransaction.applySignature(await alice.signer.sign(decrementTransaction.serializeForSigning())); await provider.sendTransaction(decrementTransaction); // Decrement #2 decrementTransaction = decrementInteraction.withNonce(16).buildTransaction(); - await alice.signer.sign(decrementTransaction); + decrementTransaction.applySignature(await alice.signer.sign(decrementTransaction.serializeForSigning())); await provider.sendTransaction(decrementTransaction); // Decrement #3 decrementTransaction = decrementInteraction.withNonce(17).buildTransaction(); - await alice.signer.sign(decrementTransaction); + decrementTransaction.applySignature(await alice.signer.sign(decrementTransaction.serializeForSigning())); provider.mockGetTransactionWithAnyHashAsNotarizedWithOneResult("@6f6b@05"); let { bundle: { firstValue: valueAfterDecrement } } = await controller.execute(decrementInteraction, decrementTransaction); assert.deepEqual(valueAfterDecrement!.valueOf(), new BigNumber(5)); @@ -292,8 +333,13 @@ describe("test smart contract interactor", function () { ); // start() - let startTransaction = startInteraction.withNonce(14).buildTransaction(); - await alice.signer.sign(startTransaction); + let startTransaction = startInteraction + .withSender(alice.address) + .withNonce(14) + .withChainID("mock") + .buildTransaction(); + + startTransaction.applySignature(await alice.signer.sign(startTransaction.serializeForSigning())); provider.mockGetTransactionWithAnyHashAsNotarizedWithOneResult("@6f6b"); let { bundle: { returnCode: startReturnCode, values: startReturnValues } } = await controller.execute(startInteraction, startTransaction); @@ -302,8 +348,13 @@ describe("test smart contract interactor", function () { assert.lengthOf(startReturnValues, 0); // status() (this is a view function, but for the sake of the test, we'll execute it) - let statusTransaction = statusInteraction.withNonce(15).buildTransaction(); - await alice.signer.sign(statusTransaction); + let statusTransaction = statusInteraction + .withSender(alice.address) + .withNonce(15) + .withChainID("mock") + .buildTransaction(); + + statusTransaction.applySignature(await alice.signer.sign(statusTransaction.serializeForSigning())); provider.mockGetTransactionWithAnyHashAsNotarizedWithOneResult("@6f6b@01"); let { bundle: { returnCode: statusReturnCode, values: statusReturnValues, firstValue: statusFirstValue } } = await controller.execute(statusInteraction, statusTransaction); @@ -313,8 +364,13 @@ describe("test smart contract interactor", function () { assert.deepEqual(statusFirstValue!.valueOf(), { name: "Running", fields: [] }); // lotteryInfo() (this is a view function, but for the sake of the test, we'll execute it) - let getLotteryInfoTransaction = getLotteryInfoInteraction.withNonce(15).buildTransaction(); - await alice.signer.sign(getLotteryInfoTransaction); + let getLotteryInfoTransaction = getLotteryInfoInteraction + .withSender(alice.address) + .withNonce(15) + .withChainID("mock") + .buildTransaction(); + + getLotteryInfoTransaction.applySignature(await alice.signer.sign(getLotteryInfoTransaction.serializeForSigning())); provider.mockGetTransactionWithAnyHashAsNotarizedWithOneResult("@6f6b@0000000b6c75636b792d746f6b656e000000010100000000000000005fc2b9dbffffffff00000001640000000a140ec80fa7ee88000000"); let { bundle: { returnCode: infoReturnCode, values: infoReturnValues, firstValue: infoFirstValue } } = await controller.execute(getLotteryInfoInteraction, getLotteryInfoTransaction); diff --git a/src/smartcontracts/interaction.ts b/src/smartcontracts/interaction.ts index 243a0974d..2568dc7dc 100644 --- a/src/smartcontracts/interaction.ts +++ b/src/smartcontracts/interaction.ts @@ -1,14 +1,16 @@ import { Account } from "../account"; import { Address } from "../address"; import { Compatibility } from "../compatibility"; -import { ESDTNFT_TRANSFER_FUNCTION_NAME, ESDT_TRANSFER_FUNCTION_NAME, MULTI_ESDTNFT_TRANSFER_FUNCTION_NAME } from "../constants"; +import { TRANSACTION_VERSION_DEFAULT } from "../constants"; import { IAddress, IChainID, IGasLimit, IGasPrice, INonce, ITokenTransfer, ITransactionValue } from "../interface"; +import { TokenTransfer } from "../tokens"; import { Transaction } from "../transaction"; +import { SmartContractTransactionsFactory, TransactionsFactoryConfig } from "../transactionsFactories"; import { ContractFunction } from "./function"; import { InteractionChecker } from "./interactionChecker"; import { CallArguments } from "./interface"; import { Query } from "./query"; -import { AddressValue, BigUIntValue, BytesValue, EndpointDefinition, TypedValue, U64Value, U8Value } from "./typesystem"; +import { EndpointDefinition, TypedValue } from "./typesystem"; /** * Internal interface: the smart contract, as seen from the perspective of an {@link Interaction}. @@ -20,8 +22,10 @@ interface ISmartContractWithinInteraction { } /** + * Legacy component. Use "SmartContractTransactionsFactory" (for transactions) or "SmartContractQueriesController" (for queries), instead. + * * Interactions can be seen as mutable transaction & query builders. - * + * * Aside from building transactions and queries, the interactors are also responsible for interpreting * the execution outcome for the objects they've built. */ @@ -35,24 +39,18 @@ export class Interaction { private gasLimit: IGasLimit = 0; private gasPrice: IGasPrice | undefined = undefined; private chainID: IChainID = ""; - private querent: IAddress = new Address(); + private querent: IAddress = Address.empty(); private explicitReceiver?: IAddress; - private sender: IAddress = new Address(); - - private isWithSingleESDTTransfer: boolean = false; - private isWithSingleESDTNFTTransfer: boolean = false; - private isWithMultiESDTNFTTransfer: boolean = false; - private tokenTransfers: TokenTransfersWithinInteraction; - - constructor( - contract: ISmartContractWithinInteraction, - func: ContractFunction, - args: TypedValue[] - ) { + private sender: IAddress = Address.empty(); + private version: number = TRANSACTION_VERSION_DEFAULT; + + private tokenTransfers: TokenTransfer[]; + + constructor(contract: ISmartContractWithinInteraction, func: ContractFunction, args: TypedValue[]) { this.contract = contract; this.function = func; this.args = args; - this.tokenTransfers = new TokenTransfersWithinInteraction([], this); + this.tokenTransfers = []; } getContractAddress(): IAddress { @@ -76,7 +74,7 @@ export class Interaction { } getTokenTransfers(): ITokenTransfer[] { - return this.tokenTransfers.getTransfers(); + return this.tokenTransfers; } getGasLimit(): IGasLimit { @@ -88,41 +86,34 @@ export class Interaction { } buildTransaction(): Transaction { - Compatibility.guardAddressIsSetAndNonZero(this.sender, "'sender' of interaction", "use interaction.withSender()"); - - let receiver = this.explicitReceiver || this.contract.getAddress(); - let func: ContractFunction = this.function; - let args = this.args; - - if (this.isWithSingleESDTTransfer) { - func = new ContractFunction(ESDT_TRANSFER_FUNCTION_NAME); - args = this.tokenTransfers.buildArgsForSingleESDTTransfer(); - } else if (this.isWithSingleESDTNFTTransfer) { - // For NFT, SFT and MetaESDT, transaction.sender == transaction.receiver. - receiver = this.sender; - func = new ContractFunction(ESDTNFT_TRANSFER_FUNCTION_NAME); - args = this.tokenTransfers.buildArgsForSingleESDTNFTTransfer(); - } else if (this.isWithMultiESDTNFTTransfer) { - // For NFT, SFT and MetaESDT, transaction.sender == transaction.receiver. - receiver = this.sender; - func = new ContractFunction(MULTI_ESDTNFT_TRANSFER_FUNCTION_NAME); - args = this.tokenTransfers.buildArgsForMultiESDTNFTTransfer(); - } + Compatibility.guardAddressIsSetAndNonZero( + this.sender, + "'sender' of interaction", + "use interaction.withSender()", + ); + + const factoryConfig = new TransactionsFactoryConfig({ chainID: this.chainID.valueOf() }); + const factory = new SmartContractTransactionsFactory({ + config: factoryConfig, + }); - let transaction = this.contract.call({ - func: func, - // GasLimit will be set using "withGasLimit()". - gasLimit: this.gasLimit, - gasPrice: this.gasPrice, - args: args, - // Value will be set using "withValue()". - value: this.value, - receiver: receiver, - chainID: this.chainID, - caller: this.sender + const transaction = factory.createTransactionForExecute({ + sender: this.sender, + contract: this.contract.getAddress(), + function: this.function.valueOf(), + gasLimit: BigInt(this.gasLimit.valueOf()), + arguments: this.args, + nativeTransferAmount: BigInt(this.value.toString()), + tokenTransfers: this.tokenTransfers, }); - transaction.setNonce(this.nonce); + transaction.chainID = this.chainID.valueOf(); + transaction.nonce = BigInt(this.nonce.valueOf()); + transaction.version = this.version; + + if (this.gasPrice) { + transaction.gasPrice = BigInt(this.gasPrice.valueOf()); + } return transaction; } @@ -134,7 +125,7 @@ export class Interaction { args: this.args, // Value will be set using "withValue()". value: this.value, - caller: this.querent + caller: this.querent, }); } @@ -144,40 +135,17 @@ export class Interaction { } withSingleESDTTransfer(transfer: ITokenTransfer): Interaction { - this.isWithSingleESDTTransfer = true; - this.tokenTransfers = new TokenTransfersWithinInteraction([transfer], this); + this.tokenTransfers = [transfer].map((transfer) => new TokenTransfer(transfer)); return this; } - withSingleESDTNFTTransfer(transfer: ITokenTransfer): Interaction; - /** - * @deprecated do not pass the "sender" parameter. Make sure to call "withSender()", instead. - */ - withSingleESDTNFTTransfer(transfer: ITokenTransfer, sender?: IAddress): Interaction; - withSingleESDTNFTTransfer(transfer: ITokenTransfer, sender?: IAddress): Interaction { - this.isWithSingleESDTNFTTransfer = true; - this.tokenTransfers = new TokenTransfersWithinInteraction([transfer], this); - - if (sender) { - this.sender = sender; - } - + withSingleESDTNFTTransfer(transfer: ITokenTransfer): Interaction { + this.tokenTransfers = [transfer].map((transfer) => new TokenTransfer(transfer)); return this; } - withMultiESDTNFTTransfer(transfers: ITokenTransfer[]): Interaction; - /** - * @deprecated do not pass the "sender" parameter. Make sure to call "withSender()", instead. - */ - withMultiESDTNFTTransfer(transfers: ITokenTransfer[], sender?: IAddress): Interaction; - withMultiESDTNFTTransfer(transfers: ITokenTransfer[], sender?: IAddress): Interaction { - this.isWithMultiESDTNFTTransfer = true; - this.tokenTransfers = new TokenTransfersWithinInteraction(transfers, this); - - if (sender) { - this.sender = sender; - } - + withMultiESDTNFTTransfer(transfers: ITokenTransfer[]): Interaction { + this.tokenTransfers = transfers.map((transfer) => new TokenTransfer(transfer)); return this; } @@ -210,6 +178,11 @@ export class Interaction { return this; } + withVersion(version: number): Interaction { + this.version = version; + return this; + } + /** * Sets the "caller" field on contract queries. */ @@ -231,92 +204,3 @@ export class Interaction { return this; } } - -class TokenTransfersWithinInteraction { - private readonly transfers: ITokenTransfer[]; - private readonly interaction: Interaction; - - constructor(transfers: ITokenTransfer[], interaction: Interaction) { - this.transfers = transfers; - this.interaction = interaction; - } - - getTransfers() { - return this.transfers; - } - - buildArgsForSingleESDTTransfer(): TypedValue[] { - let singleTransfer = this.transfers[0]; - - return [ - this.getTypedTokenIdentifier(singleTransfer), - this.getTypedTokenQuantity(singleTransfer), - this.getTypedInteractionFunction(), - ...this.getInteractionArguments() - ]; - } - - buildArgsForSingleESDTNFTTransfer(): TypedValue[] { - let singleTransfer = this.transfers[0]; - - return [ - this.getTypedTokenIdentifier(singleTransfer), - this.getTypedTokenNonce(singleTransfer), - this.getTypedTokenQuantity(singleTransfer), - this.getTypedTokensReceiver(), - this.getTypedInteractionFunction(), - ...this.getInteractionArguments() - ]; - } - - buildArgsForMultiESDTNFTTransfer(): TypedValue[] { - let result: TypedValue[] = []; - - result.push(this.getTypedTokensReceiver()); - result.push(this.getTypedNumberOfTransfers()); - - for (const transfer of this.transfers) { - result.push(this.getTypedTokenIdentifier(transfer)); - result.push(this.getTypedTokenNonce(transfer)); - result.push(this.getTypedTokenQuantity(transfer)); - } - - result.push(this.getTypedInteractionFunction()); - result.push(...this.getInteractionArguments()); - - return result; - } - - private getTypedNumberOfTransfers(): TypedValue { - return new U8Value(this.transfers.length); - } - - private getTypedTokenIdentifier(transfer: ITokenTransfer): TypedValue { - // Important: for NFTs, this has to be the "collection" name, actually. - // We will reconsider adding the field "collection" on "Token" upon merging "ApiProvider" and "ProxyProvider". - return BytesValue.fromUTF8(transfer.tokenIdentifier); - } - - private getTypedTokenNonce(transfer: ITokenTransfer): TypedValue { - // The token nonce (creation nonce) - return new U64Value(transfer.nonce); - } - - private getTypedTokenQuantity(transfer: ITokenTransfer): TypedValue { - // For NFTs, this will be 1. - return new BigUIntValue(transfer.amountAsBigInteger); - } - - private getTypedTokensReceiver(): TypedValue { - // The actual receiver of the token(s): the contract - return new AddressValue(this.interaction.getContractAddress()); - } - - private getTypedInteractionFunction(): TypedValue { - return BytesValue.fromUTF8(this.interaction.getFunction().valueOf()) - } - - private getInteractionArguments(): TypedValue[] { - return this.interaction.getArguments(); - } -} diff --git a/src/smartcontracts/interactionChecker.spec.ts b/src/smartcontracts/interactionChecker.spec.ts index 157ba2907..00f511176 100644 --- a/src/smartcontracts/interactionChecker.spec.ts +++ b/src/smartcontracts/interactionChecker.spec.ts @@ -2,7 +2,7 @@ import { assert } from "chai"; import { Address } from "../address"; import * as errors from "../errors"; import { loadAbiRegistry } from "../testutils"; -import { TokenTransfer } from "../tokenTransfer"; +import { TokenTransfer } from "../tokens"; import { Interaction } from "./interaction"; import { InteractionChecker } from "./interactionChecker"; import { SmartContract } from "./smartContract"; diff --git a/src/smartcontracts/interface.ts b/src/smartcontracts/interface.ts index 08e46232a..9b9356437 100644 --- a/src/smartcontracts/interface.ts +++ b/src/smartcontracts/interface.ts @@ -31,7 +31,7 @@ export interface ISmartContract { export interface DeployArguments { code: ICode; codeMetadata?: ICodeMetadata; - initArguments?: TypedValue[]; + initArguments?: any[]; value?: ITransactionValue; gasLimit: IGasLimit; gasPrice?: IGasPrice; @@ -42,7 +42,7 @@ export interface DeployArguments { export interface UpgradeArguments { code: ICode; codeMetadata?: ICodeMetadata; - initArguments?: TypedValue[]; + initArguments?: any[]; value?: ITransactionValue; gasLimit: IGasLimit; gasPrice?: IGasPrice; @@ -52,7 +52,7 @@ export interface UpgradeArguments { export interface CallArguments { func: IContractFunction; - args?: TypedValue[]; + args?: any[]; value?: ITransactionValue; gasLimit: IGasLimit; receiver?: IAddress; diff --git a/src/smartcontracts/nativeSerializer.spec.ts b/src/smartcontracts/nativeSerializer.spec.ts index f24e82389..2a8cb2333 100644 --- a/src/smartcontracts/nativeSerializer.spec.ts +++ b/src/smartcontracts/nativeSerializer.spec.ts @@ -291,7 +291,7 @@ describe("test native serializer", () => { }, { "type": "tuple>", }, { - "type": "List>>", + "type": "List>", }, { "type": "u64" }], diff --git a/src/smartcontracts/query.ts b/src/smartcontracts/query.ts index a298ebf05..3db545197 100644 --- a/src/smartcontracts/query.ts +++ b/src/smartcontracts/query.ts @@ -18,7 +18,7 @@ export class Query { args?: TypedValue[], value?: ITransactionValue }) { - this.caller = obj.caller || new Address(); + this.caller = obj.caller || Address.empty(); this.address = obj.address; this.func = obj.func; this.args = obj.args || []; diff --git a/src/smartcontracts/resultsParser.spec.ts b/src/smartcontracts/resultsParser.spec.ts index e257cc079..2c033a652 100644 --- a/src/smartcontracts/resultsParser.spec.ts +++ b/src/smartcontracts/resultsParser.spec.ts @@ -202,7 +202,7 @@ describe("test smart contract results parser", () => { it("should parse contract outcome, on signal error", async () => { let transaction = new TransactionOnNetwork({ logs: new TransactionLogs({ - address: new Address(), + address: Address.empty(), events: [ new TransactionEvent({ identifier: "signalError", @@ -222,7 +222,7 @@ describe("test smart contract results parser", () => { it("should parse contract outcome, on too much gas warning", async () => { let transaction = new TransactionOnNetwork({ logs: new TransactionLogs({ - address: new Address(), + address: Address.empty(), events: [ new TransactionEvent({ identifier: "writeLog", diff --git a/src/smartcontracts/resultsParser.ts b/src/smartcontracts/resultsParser.ts index 6186e8e6c..b0323c9b6 100644 --- a/src/smartcontracts/resultsParser.ts +++ b/src/smartcontracts/resultsParser.ts @@ -1,8 +1,16 @@ -import { TransactionDecoder, TransactionMetadata } from "@multiversx/sdk-transaction-decoder/lib/src/transaction.decoder"; +import { + TransactionDecoder, + TransactionMetadata, +} from "@multiversx/sdk-transaction-decoder/lib/src/transaction.decoder"; import { Address } from "../address"; import { ErrCannotParseContractResults } from "../errors"; import { IAddress } from "../interface"; -import { IContractQueryResponse, IContractResults, ITransactionLogs, ITransactionOnNetwork } from "../interfaceOfNetwork"; +import { + IContractQueryResponse, + IContractResults, + ITransactionLogs, + ITransactionOnNetwork, +} from "../interfaceOfNetwork"; import { Logger } from "../logger"; import { ArgSerializer } from "./argSerializer"; import { TypedOutcomeBundle, UntypedOutcomeBundle } from "./interface"; @@ -51,6 +59,11 @@ const defaultResultsParserOptions: IResultsParserOptions = { }; /** + * Legacy component. + * For parsing contract query responses, use the "SmartContractQueriesController" instead. + * For parsing smart contract outcome (return data), use the "SmartContractTransactionsOutcomeParser" instead. + * For parding smart contract events, use the "TransactionEventsParser" instead. + * * Parses contract query responses and smart contract results. * The parsing involves some heuristics, in order to handle slight inconsistencies (e.g. some SCRs are present on API, but missing on Gateway). */ @@ -62,6 +75,9 @@ export class ResultsParser { this.argsSerializer = options.argsSerializer; } + /** + * Legacy method, use "SmartContractQueriesController.parseQueryResponse()" instead. + */ parseQueryResponse(queryResponse: IContractQueryResponse, endpoint: { output: IParameterDefinition[] }): TypedOutcomeBundle { let parts = queryResponse.getReturnDataParts(); let values = this.argsSerializer.buffersToValues(parts, endpoint.output); @@ -78,6 +94,9 @@ export class ResultsParser { }; } + /** + * Legacy method, use "SmartContractQueriesController.parseQueryResponse()" instead. + */ parseUntypedQueryResponse(queryResponse: IContractQueryResponse): UntypedOutcomeBundle { let returnCode = new ReturnCode(queryResponse.returnCode.toString()) @@ -88,13 +107,25 @@ export class ResultsParser { }; } + /** + * Legacy method, use "SmartContractTransactionsOutcomeParser.parseExecute()" instead. + */ parseOutcome(transaction: ITransactionOnNetwork, endpoint: { output: IParameterDefinition[] }): TypedOutcomeBundle { - let untypedBundle = this.parseUntypedOutcome(transaction); - let values = this.argsSerializer.buffersToValues(untypedBundle.values, endpoint.output); + const untypedBundle = this.parseUntypedOutcome(transaction); + const typedBundle = this.parseOutcomeFromUntypedBundle(untypedBundle, endpoint); + return typedBundle; + } + + /** + * @internal + * For internal use only. + */ + parseOutcomeFromUntypedBundle(bundle: UntypedOutcomeBundle, endpoint: { output: IParameterDefinition[] }) { + const values = this.argsSerializer.buffersToValues(bundle.values, endpoint.output); return { - returnCode: untypedBundle.returnCode, - returnMessage: untypedBundle.returnMessage, + returnCode: bundle.returnCode, + returnMessage: bundle.returnMessage, values: values, firstValue: values[0], secondValue: values[1], @@ -103,12 +134,15 @@ export class ResultsParser { }; } + /** + * Legacy method, use "SmartContractTransactionsOutcomeParser.parseExecute()" instead. + */ parseUntypedOutcome(transaction: ITransactionOnNetwork): UntypedOutcomeBundle { let bundle: UntypedOutcomeBundle | null; let transactionMetadata = this.parseTransactionMetadata(transaction); - bundle = this.createBundleOnSimpleMoveBalance(transaction) + bundle = this.createBundleOnSimpleMoveBalance(transaction); if (bundle) { Logger.trace("parseUntypedOutcome(): on simple move balance"); return bundle; @@ -249,7 +283,7 @@ export class ResultsParser { return { returnCode: returnCode, returnMessage: returnMessage, - values: returnDataParts + values: returnDataParts, }; } @@ -308,7 +342,7 @@ export class ResultsParser { return null; } - private sliceDataFieldInParts(data: string): { returnCode: ReturnCode, returnDataParts: Buffer[] } { + protected sliceDataFieldInParts(data: string): { returnCode: ReturnCode, returnDataParts: Buffer[] } { // By default, skip the first part, which is usually empty (e.g. "[empty]@6f6b") let startingIndex = 1; @@ -332,34 +366,54 @@ export class ResultsParser { return { returnCode, returnDataParts }; } + /** + * Legacy method, use "TransactionEventsParser.parseEvent()" instead. + */ parseEvent(transactionEvent: ITransactionEvent, eventDefinition: { inputs: IEventInputDefinition[] }): any { - const result: any = {}; - - // We skip the first topic, because that's the event identifier. - const topics = transactionEvent.topics.map(topic => Buffer.from(topic.valueOf())).slice(1); - // < Sirius. + // We skip the first topic, because, for log entries emitted by smart contracts, that's the same as the event identifier. See: + // https://github.com/multiversx/mx-chain-vm-go/blob/v1.5.27/vmhost/contexts/output.go#L283 + const topics = transactionEvent.topics.map((topic) => Buffer.from(topic.valueOf())).slice(1); + + // Before Sirius, there was no "additionalData" field on transaction logs. + // After Sirius, the "additionalData" field includes the "data" field, as well (as the first element): + // https://github.com/multiversx/mx-chain-go/blob/v1.6.18/process/transactionLog/process.go#L159 + // Right now, the logic below is duplicated (see "TransactionsConverter"). However, "ResultsParser" will be deprecated & removed at a later time. const legacyData = transactionEvent.dataPayload?.valueOf() || Buffer.from([]); - // >= Sirius. - const additionalData = transactionEvent.additionalData?.map(data => Buffer.from(data.valueOf())) || []; + const dataItems = transactionEvent.additionalData?.map((data) => Buffer.from(data.valueOf())) || []; - // < Sirius. - if (additionalData.length == 0) { - if (legacyData.length > 0) { - additionalData.push(Buffer.from(legacyData)); + if (dataItems.length === 0) { + if (legacyData.length) { + dataItems.push(Buffer.from(legacyData)); } } + return this.doParseEvent({ topics, dataItems, eventDefinition }); + } + + /** + * @internal + * For internal use only. + * + * Once the legacy "ResultParser" is deprecated & removed, this logic will be absorbed into "TransactionEventsParser". + */ + doParseEvent(options: { + topics: Buffer[]; + dataItems: Buffer[]; + eventDefinition: { inputs: IEventInputDefinition[] }; + }): any { + const result: any = {}; + // "Indexed" ABI "event.inputs" correspond to "event.topics[1:]": - const indexedInputs = eventDefinition.inputs.filter(input => input.indexed); - const decodedTopics = this.argsSerializer.buffersToValues(topics, indexedInputs); + const indexedInputs = options.eventDefinition.inputs.filter((input) => input.indexed); + const decodedTopics = this.argsSerializer.buffersToValues(options.topics, indexedInputs); for (let i = 0; i < indexedInputs.length; i++) { result[indexedInputs[i].name] = decodedTopics[i].valueOf(); } // "Non-indexed" ABI "event.inputs" correspond to "event.data": - const nonIndexedInputs = eventDefinition.inputs.filter(input => !input.indexed); - const decodedDataParts = this.argsSerializer.buffersToValues(additionalData, nonIndexedInputs); + const nonIndexedInputs = options.eventDefinition.inputs.filter((input) => !input.indexed); + const decodedDataParts = this.argsSerializer.buffersToValues(options.dataItems, nonIndexedInputs); for (let i = 0; i < nonIndexedInputs.length; i++) { result[nonIndexedInputs[i].name] = decodedDataParts[i].valueOf(); diff --git a/src/smartcontracts/smartContract.local.net.spec.ts b/src/smartcontracts/smartContract.local.net.spec.ts index 075bc7129..75040dc0f 100644 --- a/src/smartcontracts/smartContract.local.net.spec.ts +++ b/src/smartcontracts/smartContract.local.net.spec.ts @@ -10,6 +10,12 @@ import { ResultsParser } from "./resultsParser"; import { SmartContract } from "./smartContract"; import { AddressValue, BigUIntValue, OptionalValue, OptionValue, TokenIdentifierValue, U32Value } from "./typesystem"; import { BytesValue } from "./typesystem/bytes"; +import { TransactionsFactoryConfig } from "../transactionsFactories/transactionsFactoryConfig"; +import { SmartContractTransactionsFactory } from "../transactionsFactories/smartContractTransactionsFactory"; +import { promises } from "fs"; +import { TransactionComputer } from "../transactionComputer"; +import { QueryRunnerAdapter } from "../adapters/queryRunnerAdapter"; +import { SmartContractQueriesController } from "../smartContractQueriesController"; describe("test on local testnet", function () { let alice: TestWallet, bob: TestWallet, carol: TestWallet; @@ -21,7 +27,9 @@ describe("test on local testnet", function () { ({ alice, bob, carol } = await loadTestWallets()); watcher = new TransactionWatcher({ - getTransaction: async (hash: string) => { return await provider.getTransaction(hash, true) } + getTransaction: async (hash: string) => { + return await provider.getTransaction(hash, true); + }, }); }); @@ -43,7 +51,7 @@ describe("test on local testnet", function () { codePath: "src/testdata/counter.wasm", gasLimit: 3000000, initArguments: [], - chainID: network.ChainID + chainID: network.ChainID, }); // ++ @@ -51,10 +59,10 @@ describe("test on local testnet", function () { func: new ContractFunction("increment"), gasLimit: 3000000, chainID: network.ChainID, - caller: alice.address + caller: alice.address, }); transactionIncrement.setNonce(alice.account.nonce); - await alice.signer.sign(transactionIncrement); + transactionIncrement.applySignature(await alice.signer.sign(transactionIncrement.serializeForSigning())); alice.account.incrementNonce(); @@ -63,7 +71,7 @@ describe("test on local testnet", function () { func: new ContractFunction("increment"), gasLimit: 100000, chainID: network.ChainID, - caller: alice.address + caller: alice.address, }); simulateOne.setSender(alice.address); @@ -71,27 +79,116 @@ describe("test on local testnet", function () { func: new ContractFunction("foobar"), gasLimit: 500000, chainID: network.ChainID, - caller: alice.address + caller: alice.address, }); simulateTwo.setSender(alice.address); simulateOne.setNonce(alice.account.nonce); simulateTwo.setNonce(alice.account.nonce); - await alice.signer.sign(simulateOne); - await alice.signer.sign(simulateTwo); + simulateOne.applySignature(await alice.signer.sign(simulateOne.serializeForSigning())); + simulateTwo.applySignature(await alice.signer.sign(simulateTwo.serializeForSigning())); // Broadcast & execute - await provider.sendTransaction(transactionDeploy); - await provider.sendTransaction(transactionIncrement); + const txHashDeploy = await provider.sendTransaction(transactionDeploy); + const txHashIncrement = await provider.sendTransaction(transactionIncrement); - await watcher.awaitCompleted(transactionDeploy); - let transactionOnNetwork = await provider.getTransaction(transactionDeploy.getHash().hex()); + await watcher.awaitCompleted(txHashDeploy); + let transactionOnNetwork = await provider.getTransaction(txHashDeploy); let bundle = resultsParser.parseUntypedOutcome(transactionOnNetwork); assert.isTrue(bundle.returnCode.isSuccess()); - await watcher.awaitCompleted(transactionIncrement); - transactionOnNetwork = await provider.getTransaction(transactionIncrement.getHash().hex()); + await watcher.awaitCompleted(txHashIncrement); + transactionOnNetwork = await provider.getTransaction(txHashIncrement); + bundle = resultsParser.parseUntypedOutcome(transactionOnNetwork); + assert.isTrue(bundle.returnCode.isSuccess()); + + // Simulate + Logger.trace(JSON.stringify(await provider.simulateTransaction(simulateOne), null, 4)); + Logger.trace(JSON.stringify(await provider.simulateTransaction(simulateTwo), null, 4)); + }); + + it("counter: should deploy, then simulate transactions using SmartContractTransactionsFactory", async function () { + this.timeout(60000); + + TransactionWatcher.DefaultPollingInterval = 5000; + TransactionWatcher.DefaultTimeout = 50000; + + let network = await provider.getNetworkConfig(); + await alice.sync(provider); + + const config = new TransactionsFactoryConfig({ chainID: network.ChainID }); + const factory = new SmartContractTransactionsFactory({ config: config }); + + const bytecode = await promises.readFile("src/testdata/counter.wasm"); + + const deployTransaction = factory.createTransactionForDeploy({ + sender: alice.address, + bytecode: bytecode, + gasLimit: 3000000n, + }); + deployTransaction.nonce = BigInt(alice.account.nonce.valueOf()); + + const transactionComputer = new TransactionComputer(); + deployTransaction.signature = await alice.signer.sign( + Buffer.from(transactionComputer.computeBytesForSigning(deployTransaction)), + ); + + const contractAddress = SmartContract.computeAddress(alice.address, alice.account.nonce); + alice.account.incrementNonce(); + + const smartContractCallTransaction = factory.createTransactionForExecute({ + sender: alice.address, + contract: contractAddress, + function: "increment", + gasLimit: 3000000n, + }); + smartContractCallTransaction.nonce = BigInt(alice.account.nonce.valueOf()); + smartContractCallTransaction.signature = await alice.signer.sign( + Buffer.from(transactionComputer.computeBytesForSigning(smartContractCallTransaction)), + ); + + alice.account.incrementNonce(); + + const simulateOne = factory.createTransactionForExecute({ + sender: alice.address, + function: "increment", + contract: contractAddress, + gasLimit: 100000n, + }); + + simulateOne.nonce = BigInt(alice.account.nonce.valueOf()); + simulateOne.signature = await alice.signer.sign( + Buffer.from(transactionComputer.computeBytesForSigning(simulateOne)), + ); + + alice.account.incrementNonce(); + + const simulateTwo = factory.createTransactionForExecute({ + sender: alice.address, + function: "foobar", + contract: contractAddress, + gasLimit: 500000n, + }); + + simulateTwo.nonce = BigInt(alice.account.nonce.valueOf()); + simulateTwo.signature = await alice.signer.sign( + Buffer.from(transactionComputer.computeBytesForSigning(simulateTwo)), + ); + + alice.account.incrementNonce(); + + // Broadcast & execute + const deployTxHash = await provider.sendTransaction(deployTransaction); + const callTxHash = await provider.sendTransaction(smartContractCallTransaction); + + await watcher.awaitCompleted(deployTxHash); + let transactionOnNetwork = await provider.getTransaction(deployTxHash); + let bundle = resultsParser.parseUntypedOutcome(transactionOnNetwork); + assert.isTrue(bundle.returnCode.isSuccess()); + + await watcher.awaitCompleted(callTxHash); + transactionOnNetwork = await provider.getTransaction(callTxHash); bundle = resultsParser.parseUntypedOutcome(transactionOnNetwork); assert.isTrue(bundle.returnCode.isSuccess()); @@ -118,7 +215,7 @@ describe("test on local testnet", function () { codePath: "src/testdata/counter.wasm", gasLimit: 3000000, initArguments: [], - chainID: network.ChainID + chainID: network.ChainID, }); // ++ @@ -126,10 +223,12 @@ describe("test on local testnet", function () { func: new ContractFunction("increment"), gasLimit: 2000000, chainID: network.ChainID, - caller: alice.address + caller: alice.address, }); transactionIncrementFirst.setNonce(alice.account.nonce); - await alice.signer.sign(transactionIncrementFirst); + transactionIncrementFirst.applySignature( + await alice.signer.sign(transactionIncrementFirst.serializeForSigning()), + ); alice.account.incrementNonce(); @@ -138,10 +237,12 @@ describe("test on local testnet", function () { func: new ContractFunction("increment"), gasLimit: 2000000, chainID: network.ChainID, - caller: alice.address + caller: alice.address, }); transactionIncrementSecond.setNonce(alice.account.nonce); - await alice.signer.sign(transactionIncrementSecond); + transactionIncrementSecond.applySignature( + await alice.signer.sign(transactionIncrementSecond.serializeForSigning()), + ); alice.account.incrementNonce(); @@ -150,9 +251,9 @@ describe("test on local testnet", function () { await provider.sendTransaction(transactionIncrementFirst); await provider.sendTransaction(transactionIncrementSecond); - await watcher.awaitCompleted(transactionDeploy); - await watcher.awaitCompleted(transactionIncrementFirst); - await watcher.awaitCompleted(transactionIncrementSecond); + await watcher.awaitCompleted(transactionDeploy.getHash().hex()); + await watcher.awaitCompleted(transactionIncrementFirst.getHash().hex()); + await watcher.awaitCompleted(transactionIncrementSecond.getHash().hex()); // Check counter let query = contract.createQuery({ func: new ContractFunction("get") }); @@ -160,6 +261,84 @@ describe("test on local testnet", function () { assert.equal(3, decodeUnsignedNumber(queryResponse.getReturnDataParts()[0])); }); + it("counter: should deploy, call and query contract using SmartContractTransactionsFactory", async function () { + this.timeout(80000); + + TransactionWatcher.DefaultPollingInterval = 5000; + TransactionWatcher.DefaultTimeout = 50000; + + let network = await provider.getNetworkConfig(); + await alice.sync(provider); + + const config = new TransactionsFactoryConfig({ chainID: network.ChainID }); + const factory = new SmartContractTransactionsFactory({ config: config }); + + const bytecode = await promises.readFile("src/testdata/counter.wasm"); + + const deployTransaction = factory.createTransactionForDeploy({ + sender: alice.address, + bytecode: bytecode, + gasLimit: 3000000n, + }); + deployTransaction.nonce = BigInt(alice.account.nonce.valueOf()); + + const transactionComputer = new TransactionComputer(); + deployTransaction.signature = await alice.signer.sign( + Buffer.from(transactionComputer.computeBytesForSigning(deployTransaction)), + ); + + const contractAddress = SmartContract.computeAddress(alice.address, alice.account.nonce); + alice.account.incrementNonce(); + + const firstScCallTransaction = factory.createTransactionForExecute({ + sender: alice.address, + contract: contractAddress, + function: "increment", + gasLimit: 3000000n, + }); + firstScCallTransaction.nonce = BigInt(alice.account.nonce.valueOf()); + firstScCallTransaction.signature = await alice.signer.sign( + Buffer.from(transactionComputer.computeBytesForSigning(firstScCallTransaction)), + ); + + alice.account.incrementNonce(); + + const secondScCallTransaction = factory.createTransactionForExecute({ + sender: alice.address, + contract: contractAddress, + function: "increment", + gasLimit: 3000000n, + }); + secondScCallTransaction.nonce = BigInt(alice.account.nonce.valueOf()); + secondScCallTransaction.signature = await alice.signer.sign( + Buffer.from(transactionComputer.computeBytesForSigning(secondScCallTransaction)), + ); + + alice.account.incrementNonce(); + + // Broadcast & execute + const deployTxHash = await provider.sendTransaction(deployTransaction); + const firstScCallHash = await provider.sendTransaction(firstScCallTransaction); + const secondScCallHash = await provider.sendTransaction(secondScCallTransaction); + + await watcher.awaitCompleted(deployTxHash); + await watcher.awaitCompleted(firstScCallHash); + await watcher.awaitCompleted(secondScCallHash); + + // Check counter + const queryRunner = new QueryRunnerAdapter({ networkProvider: provider }); + const smartContractQueriesController = new SmartContractQueriesController({ queryRunner: queryRunner }); + + const query = smartContractQueriesController.createQuery({ + contract: contractAddress.bech32(), + function: "get", + arguments: [], + }); + + const queryResponse = await smartContractQueriesController.runQuery(query); + assert.equal(3, decodeUnsignedNumber(Buffer.from(queryResponse.returnDataParts[0]))); + }); + it("erc20: should deploy, call and query contract", async function () { this.timeout(60000); @@ -178,7 +357,7 @@ describe("test on local testnet", function () { codePath: "src/testdata/erc20.wasm", gasLimit: 50000000, initArguments: [new U32Value(10000)], - chainID: network.ChainID + chainID: network.ChainID, }); // Minting @@ -187,7 +366,7 @@ describe("test on local testnet", function () { gasLimit: 9000000, args: [new AddressValue(bob.address), new U32Value(1000)], chainID: network.ChainID, - caller: alice.address + caller: alice.address, }); let transactionMintCarol = contract.call({ @@ -195,7 +374,7 @@ describe("test on local testnet", function () { gasLimit: 9000000, args: [new AddressValue(carol.address), new U32Value(1500)], chainID: network.ChainID, - caller: alice.address + caller: alice.address, }); // Apply nonces and sign the remaining transactions @@ -204,17 +383,17 @@ describe("test on local testnet", function () { transactionMintCarol.setNonce(alice.account.nonce); alice.account.incrementNonce(); - await alice.signer.sign(transactionMintBob); - await alice.signer.sign(transactionMintCarol); + transactionMintBob.applySignature(await alice.signer.sign(transactionMintBob.serializeForSigning())); + transactionMintCarol.applySignature(await alice.signer.sign(transactionMintCarol.serializeForSigning())); // Broadcast & execute await provider.sendTransaction(transactionDeploy); await provider.sendTransaction(transactionMintBob); await provider.sendTransaction(transactionMintCarol); - await watcher.awaitCompleted(transactionDeploy); - await watcher.awaitCompleted(transactionMintBob); - await watcher.awaitCompleted(transactionMintCarol); + await watcher.awaitCompleted(transactionDeploy.getHash().hex()); + await watcher.awaitCompleted(transactionMintBob.getHash().hex()); + await watcher.awaitCompleted(transactionMintCarol.getHash().hex()); // Query state, do some assertions let query = contract.createQuery({ func: new ContractFunction("totalSupply") }); @@ -223,7 +402,7 @@ describe("test on local testnet", function () { query = contract.createQuery({ func: new ContractFunction("balanceOf"), - args: [new AddressValue(alice.address)] + args: [new AddressValue(alice.address)], }); queryResponse = await provider.queryContract(query); @@ -231,7 +410,7 @@ describe("test on local testnet", function () { query = contract.createQuery({ func: new ContractFunction("balanceOf"), - args: [new AddressValue(bob.address)] + args: [new AddressValue(bob.address)], }); queryResponse = await provider.queryContract(query); @@ -239,13 +418,116 @@ describe("test on local testnet", function () { query = contract.createQuery({ func: new ContractFunction("balanceOf"), - args: [new AddressValue(carol.address)] + args: [new AddressValue(carol.address)], }); queryResponse = await provider.queryContract(query); assert.equal(1500, decodeUnsignedNumber(queryResponse.getReturnDataParts()[0])); }); + it("erc20: should deploy, call and query contract using SmartContractTransactionsFactory", async function () { + this.timeout(60000); + + TransactionWatcher.DefaultPollingInterval = 5000; + TransactionWatcher.DefaultTimeout = 50000; + + let network = await provider.getNetworkConfig(); + await alice.sync(provider); + + const config = new TransactionsFactoryConfig({ chainID: network.ChainID }); + const factory = new SmartContractTransactionsFactory({ config: config }); + + const bytecode = await promises.readFile("src/testdata/erc20.wasm"); + + const deployTransaction = factory.createTransactionForDeploy({ + sender: alice.address, + bytecode: bytecode, + gasLimit: 50000000n, + arguments: [new U32Value(10000)], + }); + deployTransaction.nonce = BigInt(alice.account.nonce.valueOf()); + const transactionComputer = new TransactionComputer(); + deployTransaction.signature = await alice.signer.sign( + Buffer.from(transactionComputer.computeBytesForSigning(deployTransaction)), + ); + + const contractAddress = SmartContract.computeAddress(alice.address, alice.account.nonce); + alice.account.incrementNonce(); + + const transactionMintBob = factory.createTransactionForExecute({ + sender: alice.address, + contract: contractAddress, + function: "transferToken", + gasLimit: 9000000n, + arguments: [new AddressValue(bob.address), new U32Value(1000)], + }); + transactionMintBob.nonce = BigInt(alice.account.nonce.valueOf()); + transactionMintBob.signature = await alice.signer.sign( + Buffer.from(transactionComputer.computeBytesForSigning(transactionMintBob)), + ); + + alice.account.incrementNonce(); + + const transactionMintCarol = factory.createTransactionForExecute({ + sender: alice.address, + contract: contractAddress, + function: "transferToken", + gasLimit: 9000000n, + arguments: [new AddressValue(carol.address), new U32Value(1500)], + }); + transactionMintCarol.nonce = BigInt(alice.account.nonce.valueOf()); + transactionMintCarol.signature = await alice.signer.sign( + Buffer.from(transactionComputer.computeBytesForSigning(transactionMintCarol)), + ); + + alice.account.incrementNonce(); + + // Broadcast & execute + const deployTxHash = await provider.sendTransaction(deployTransaction); + const mintBobTxHash = await provider.sendTransaction(transactionMintBob); + const mintCarolTxHash = await provider.sendTransaction(transactionMintCarol); + + await watcher.awaitCompleted(deployTxHash); + await watcher.awaitCompleted(mintBobTxHash); + await watcher.awaitCompleted(mintCarolTxHash); + + // Query state, do some assertions + const queryRunner = new QueryRunnerAdapter({ networkProvider: provider }); + const smartContractQueriesController = new SmartContractQueriesController({ queryRunner: queryRunner }); + + let query = smartContractQueriesController.createQuery({ + contract: contractAddress.bech32(), + function: "totalSupply", + arguments: [], + }); + let queryResponse = await smartContractQueriesController.runQuery(query); + assert.equal(10000, decodeUnsignedNumber(Buffer.from(queryResponse.returnDataParts[0]))); + + query = smartContractQueriesController.createQuery({ + contract: contractAddress.bech32(), + function: "balanceOf", + arguments: [new AddressValue(alice.address)], + }); + queryResponse = await smartContractQueriesController.runQuery(query); + assert.equal(7500, decodeUnsignedNumber(Buffer.from(queryResponse.returnDataParts[0]))); + + query = smartContractQueriesController.createQuery({ + contract: contractAddress.bech32(), + function: "balanceOf", + arguments: [new AddressValue(bob.address)], + }); + queryResponse = await smartContractQueriesController.runQuery(query); + assert.equal(1000, decodeUnsignedNumber(Buffer.from(queryResponse.returnDataParts[0]))); + + query = smartContractQueriesController.createQuery({ + contract: contractAddress.bech32(), + function: "balanceOf", + arguments: [new AddressValue(carol.address)], + }); + queryResponse = await smartContractQueriesController.runQuery(query); + assert.equal(1500, decodeUnsignedNumber(Buffer.from(queryResponse.returnDataParts[0]))); + }); + it("lottery: should deploy, call and query contract", async function () { this.timeout(60000); @@ -264,7 +546,7 @@ describe("test on local testnet", function () { codePath: "src/testdata/lottery-esdt.wasm", gasLimit: 50000000, initArguments: [], - chainID: network.ChainID + chainID: network.ChainID, }); // Start @@ -280,7 +562,7 @@ describe("test on local testnet", function () { OptionValue.newProvided(new U32Value(1)), OptionValue.newMissing(), OptionValue.newMissing(), - OptionalValue.newMissing() + OptionalValue.newMissing(), ], chainID: network.ChainID, caller: alice.address, @@ -288,14 +570,14 @@ describe("test on local testnet", function () { // Apply nonces and sign the remaining transactions transactionStart.setNonce(alice.account.nonce); - await alice.signer.sign(transactionStart); + transactionStart.applySignature(await alice.signer.sign(transactionStart.serializeForSigning())); // Broadcast & execute await provider.sendTransaction(transactionDeploy); await provider.sendTransaction(transactionStart); - await watcher.awaitAllEvents(transactionDeploy, ["SCDeploy"]); - await watcher.awaitAnyEvent(transactionStart, ["completedTxEvent"]); + await watcher.awaitAllEvents(transactionDeploy.getHash().hex(), ["SCDeploy"]); + await watcher.awaitAnyEvent(transactionStart.getHash().hex(), ["completedTxEvent"]); // Let's check the SCRs let transactionOnNetwork = await provider.getTransaction(transactionDeploy.getHash().hex()); @@ -309,20 +591,106 @@ describe("test on local testnet", function () { // Query state, do some assertions let query = contract.createQuery({ func: new ContractFunction("status"), - args: [ - BytesValue.fromUTF8("lucky") - ] + args: [BytesValue.fromUTF8("lucky")], }); let queryResponse = await provider.queryContract(query); assert.equal(decodeUnsignedNumber(queryResponse.getReturnDataParts()[0]), 1); query = contract.createQuery({ func: new ContractFunction("status"), - args: [ - BytesValue.fromUTF8("missingLottery") - ] + args: [BytesValue.fromUTF8("missingLottery")], }); queryResponse = await provider.queryContract(query); assert.equal(decodeUnsignedNumber(queryResponse.getReturnDataParts()[0]), 0); }); + + it("lottery: should deploy, call and query contract using SmartContractTransactionsFactory", async function () { + this.timeout(60000); + + TransactionWatcher.DefaultPollingInterval = 5000; + TransactionWatcher.DefaultTimeout = 50000; + + let network = await provider.getNetworkConfig(); + await alice.sync(provider); + + const config = new TransactionsFactoryConfig({ chainID: network.ChainID }); + const factory = new SmartContractTransactionsFactory({ config: config }); + + const bytecode = await promises.readFile("src/testdata/lottery-esdt.wasm"); + + const deployTransaction = factory.createTransactionForDeploy({ + sender: alice.address, + bytecode: bytecode, + gasLimit: 50000000n, + }); + deployTransaction.nonce = BigInt(alice.account.nonce.valueOf()); + + const transactionComputer = new TransactionComputer(); + deployTransaction.signature = await alice.signer.sign( + Buffer.from(transactionComputer.computeBytesForSigning(deployTransaction)), + ); + + const contractAddress = SmartContract.computeAddress(alice.address, alice.account.nonce); + alice.account.incrementNonce(); + + const startTransaction = factory.createTransactionForExecute({ + sender: alice.address, + contract: contractAddress, + function: "start", + gasLimit: 10000000n, + arguments: [ + BytesValue.fromUTF8("lucky"), + new TokenIdentifierValue("EGLD"), + new BigUIntValue(1), + OptionValue.newMissing(), + OptionValue.newMissing(), + OptionValue.newProvided(new U32Value(1)), + OptionValue.newMissing(), + OptionValue.newMissing(), + OptionalValue.newMissing(), + ], + }); + startTransaction.nonce = BigInt(alice.account.nonce.valueOf()); + startTransaction.signature = await alice.signer.sign( + Buffer.from(transactionComputer.computeBytesForSigning(startTransaction)), + ); + + alice.account.incrementNonce(); + + // Broadcast & execute + const deployTx = await provider.sendTransaction(deployTransaction); + const startTx = await provider.sendTransaction(startTransaction); + + await watcher.awaitAllEvents(deployTx, ["SCDeploy"]); + await watcher.awaitAnyEvent(startTx, ["completedTxEvent"]); + + // Let's check the SCRs + let transactionOnNetwork = await provider.getTransaction(deployTx); + let bundle = resultsParser.parseUntypedOutcome(transactionOnNetwork); + assert.isTrue(bundle.returnCode.isSuccess()); + + transactionOnNetwork = await provider.getTransaction(startTx); + bundle = resultsParser.parseUntypedOutcome(transactionOnNetwork); + assert.isTrue(bundle.returnCode.isSuccess()); + + // Query state, do some assertions + const queryRunner = new QueryRunnerAdapter({ networkProvider: provider }); + const smartContractQueriesController = new SmartContractQueriesController({ queryRunner: queryRunner }); + + let query = smartContractQueriesController.createQuery({ + contract: contractAddress.bech32(), + function: "status", + arguments: [BytesValue.fromUTF8("lucky")], + }); + let queryResponse = await smartContractQueriesController.runQuery(query); + assert.equal(decodeUnsignedNumber(Buffer.from(queryResponse.returnDataParts[0])), 1); + + query = smartContractQueriesController.createQuery({ + contract: contractAddress.bech32(), + function: "status", + arguments: [BytesValue.fromUTF8("missingLottery")], + }); + queryResponse = await smartContractQueriesController.runQuery(query); + assert.equal(decodeUnsignedNumber(Buffer.from(queryResponse.returnDataParts[0])), 0); + }); }); diff --git a/src/smartcontracts/smartContract.spec.ts b/src/smartcontracts/smartContract.spec.ts index 51233d058..11ba86d75 100644 --- a/src/smartcontracts/smartContract.spec.ts +++ b/src/smartcontracts/smartContract.spec.ts @@ -1,7 +1,7 @@ import { TransactionStatus } from "@multiversx/sdk-network-providers"; import { assert } from "chai"; import { Address } from "../address"; -import { loadTestWallets, MarkCompleted, MockProvider, setupUnitTestWatcherTimeouts, TestWallet, Wait } from "../testutils"; +import { loadTestWallets, MarkCompleted, MockNetworkProvider, setupUnitTestWatcherTimeouts, TestWallet, Wait } from "../testutils"; import { TransactionWatcher } from "../transactionWatcher"; import { Code } from "./code"; import { ContractFunction } from "./function"; @@ -11,7 +11,7 @@ import { BytesValue } from "./typesystem/bytes"; describe("test contract", () => { - let provider = new MockProvider(); + let provider = new MockNetworkProvider(); let chainID = "test"; let alice: TestWallet; @@ -57,14 +57,14 @@ describe("test contract", () => { assert.equal(contract.getAddress().bech32(), "erd1qqqqqqqqqqqqqpgq3ytm9m8dpeud35v3us20vsafp77smqghd8ss4jtm0q"); // Sign the transaction - alice.signer.sign(deployTransaction); + deployTransaction.applySignature(await alice.signer.sign(deployTransaction.serializeForSigning())); // Now let's broadcast the deploy transaction, and wait for its execution. let hash = await provider.sendTransaction(deployTransaction); await Promise.all([ provider.mockTransactionTimeline(deployTransaction, [new Wait(40), new TransactionStatus("pending"), new Wait(40), new TransactionStatus("executed"), new MarkCompleted()]), - watcher.awaitCompleted(deployTransaction) + watcher.awaitCompleted(deployTransaction.getHash().hex()) ]); assert.isTrue((await provider.getTransactionStatus(hash)).isExecuted()); @@ -109,8 +109,8 @@ describe("test contract", () => { assert.equal(callTransactionTwo.getGasLimit().valueOf(), 1500000); // Sign transactions, broadcast them - alice.signer.sign(callTransactionOne); - alice.signer.sign(callTransactionTwo); + callTransactionOne.applySignature(await alice.signer.sign(callTransactionOne.serializeForSigning())); + callTransactionTwo.applySignature(await alice.signer.sign(callTransactionTwo.serializeForSigning())); let hashOne = await provider.sendTransaction(callTransactionOne); let hashTwo = await provider.sendTransaction(callTransactionTwo); @@ -118,11 +118,50 @@ describe("test contract", () => { await Promise.all([ provider.mockTransactionTimeline(callTransactionOne, [new Wait(40), new TransactionStatus("pending"), new Wait(40), new TransactionStatus("executed"), new MarkCompleted()]), provider.mockTransactionTimeline(callTransactionTwo, [new Wait(40), new TransactionStatus("pending"), new Wait(40), new TransactionStatus("executed"), new MarkCompleted()]), - watcher.awaitCompleted(callTransactionOne), - watcher.awaitCompleted(callTransactionTwo) + watcher.awaitCompleted(callTransactionOne.getHash().hex()), + watcher.awaitCompleted(callTransactionTwo.getHash().hex()) ]); assert.isTrue((await provider.getTransactionStatus(hashOne)).isExecuted()); assert.isTrue((await provider.getTransactionStatus(hashTwo)).isExecuted()); }); + + it("should upgrade", async () => { + setupUnitTestWatcherTimeouts(); + let watcher = new TransactionWatcher(provider); + + let contract = new SmartContract(); + contract.setAddress(Address.fromBech32("erd1qqqqqqqqqqqqqpgq3ytm9m8dpeud35v3us20vsafp77smqghd8ss4jtm0q")) + + let deployTransaction = contract.upgrade({ + code: Code.fromBuffer(Buffer.from([1, 2, 3, 4])), + gasLimit: 1000000, + chainID: chainID, + caller: alice.address + }); + + provider.mockUpdateAccount(alice.address, account => { + account.nonce = 42; + }); + + await alice.sync(provider); + deployTransaction.setNonce(alice.account.nonce); + + assert.equal(deployTransaction.getData().valueOf().toString(), "upgradeContract@01020304@0100"); + assert.equal(deployTransaction.getGasLimit().valueOf(), 1000000); + assert.equal(deployTransaction.getNonce().valueOf(), 42); + + // Sign the transaction + deployTransaction.applySignature(await alice.signer.sign(deployTransaction.serializeForSigning())); + + // Now let's broadcast the deploy transaction, and wait for its execution. + let hash = await provider.sendTransaction(deployTransaction); + + await Promise.all([ + provider.mockTransactionTimeline(deployTransaction, [new Wait(40), new TransactionStatus("pending"), new Wait(40), new TransactionStatus("executed"), new MarkCompleted()]), + watcher.awaitCompleted(deployTransaction.getHash().hex()) + ]); + + assert.isTrue((await provider.getTransactionStatus(hash)).isExecuted()); + }); }); diff --git a/src/smartcontracts/smartContract.ts b/src/smartcontracts/smartContract.ts index 0ab5e8e00..de24dde2c 100644 --- a/src/smartcontracts/smartContract.ts +++ b/src/smartcontracts/smartContract.ts @@ -1,22 +1,23 @@ -import BigNumber from "bignumber.js"; -import { Address } from "../address"; +import { Address, AddressComputer } from "../address"; import { Compatibility } from "../compatibility"; +import { TRANSACTION_MIN_GAS_PRICE } from "../constants"; import { ErrContractHasNoAddress } from "../errors"; import { IAddress, INonce } from "../interface"; import { Transaction } from "../transaction"; +import { SmartContractTransactionsFactory } from "../transactionsFactories/smartContractTransactionsFactory"; +import { TransactionsFactoryConfig } from "../transactionsFactories/transactionsFactoryConfig"; import { guardValueIsSet } from "../utils"; -import { bigIntToBuffer } from "./codec/utils"; import { CodeMetadata } from "./codeMetadata"; import { ContractFunction } from "./function"; import { Interaction } from "./interaction"; -import { CallArguments, DeployArguments, ISmartContract, QueryArguments, UpgradeArguments } from "./interface"; +import { CallArguments, DeployArguments, ICodeMetadata, ISmartContract, QueryArguments, UpgradeArguments } from "./interface"; import { NativeSerializer } from "./nativeSerializer"; import { Query } from "./query"; -import { ArwenVirtualMachine, ContractCallPayloadBuilder, ContractDeployPayloadBuilder, ContractUpgradePayloadBuilder } from "./transactionPayloadBuilders"; import { EndpointDefinition, TypedValue } from "./typesystem"; -const createKeccakHash = require("keccak"); interface IAbi { + constructorDefinition: EndpointDefinition; + getEndpoints(): EndpointDefinition[]; getEndpoint(name: string | ContractFunction): EndpointDefinition; } @@ -25,7 +26,7 @@ interface IAbi { * An abstraction for deploying and interacting with Smart Contracts. */ export class SmartContract implements ISmartContract { - private address: IAddress = new Address(); + private address: IAddress = Address.empty(); private abi?: IAbi; /** @@ -47,7 +48,7 @@ export class SmartContract implements ISmartContract { * Create a SmartContract object by providing its address on the Network. */ constructor(options: { address?: IAddress, abi?: IAbi } = {}) { - this.address = options.address || new Address(); + this.address = options.address || Address.empty(); this.abi = options.abi; if (this.abi) { @@ -56,6 +57,7 @@ export class SmartContract implements ISmartContract { } private setupMethods() { + // eslint-disable-next-line @typescript-eslint/no-this-alias let contract = this; let abi = this.getAbi(); @@ -110,29 +112,56 @@ export class SmartContract implements ISmartContract { deploy({ deployer, code, codeMetadata, initArguments, value, gasLimit, gasPrice, chainID }: DeployArguments): Transaction { Compatibility.guardAddressIsSetAndNonZero(deployer, "'deployer' of SmartContract.deploy()", "pass the actual address to deploy()"); - codeMetadata = codeMetadata || new CodeMetadata(); - initArguments = initArguments || []; - value = value || 0; + const config = new TransactionsFactoryConfig({ chainID: chainID.valueOf() }); + const factory = new SmartContractTransactionsFactory({ + config: config, + abi: this.abi, + }); - let payload = new ContractDeployPayloadBuilder() - .setCode(code) - .setCodeMetadata(codeMetadata) - .setInitArgs(initArguments) - .build(); + const bytecode = Buffer.from(code.toString(), "hex"); + const metadataAsJson = this.getMetadataPropertiesAsObject(codeMetadata); - let transaction = new Transaction({ - receiver: Address.Zero(), + const transaction = factory.createTransactionForDeploy({ sender: deployer, - value: value, - gasLimit: gasLimit, - gasPrice: gasPrice, - data: payload, - chainID: chainID + bytecode: bytecode, + gasLimit: BigInt(gasLimit.valueOf()), + arguments: initArguments, + isUpgradeable: metadataAsJson.upgradeable, + isReadable: metadataAsJson.readable, + isPayable: metadataAsJson.payable, + isPayableBySmartContract: metadataAsJson.payableBySc, }); + transaction.setChainID(chainID); + transaction.setValue(value ?? 0); + transaction.setGasPrice(gasPrice ?? TRANSACTION_MIN_GAS_PRICE); + return transaction; } + private getMetadataPropertiesAsObject(codeMetadata?: ICodeMetadata): { + upgradeable: boolean, + readable: boolean, + payable: boolean, + payableBySc: boolean + } { + let metadata: CodeMetadata; + if (codeMetadata) { + metadata = CodeMetadata.fromBytes(Buffer.from(codeMetadata.toString(), "hex")); + } + else { + metadata = new CodeMetadata(); + } + const metadataAsJson = metadata.toJSON() as { + upgradeable: boolean, + readable: boolean, + payable: boolean, + payableBySc: boolean + }; + + return metadataAsJson; + } + /** * Creates a {@link Transaction} for upgrading the Smart Contract on the Network. */ @@ -141,26 +170,31 @@ export class SmartContract implements ISmartContract { this.ensureHasAddress(); - codeMetadata = codeMetadata || new CodeMetadata(); - initArguments = initArguments || []; - value = value || 0; + const config = new TransactionsFactoryConfig({ chainID: chainID.valueOf() }); + const factory = new SmartContractTransactionsFactory({ + config: config, + abi: this.abi, + }); - let payload = new ContractUpgradePayloadBuilder() - .setCode(code) - .setCodeMetadata(codeMetadata) - .setInitArgs(initArguments) - .build(); + const bytecode = Uint8Array.from(Buffer.from(code.toString(), "hex")); + const metadataAsJson = this.getMetadataPropertiesAsObject(codeMetadata); - let transaction = new Transaction({ + const transaction = factory.createTransactionForUpgrade({ sender: caller, - receiver: this.getAddress(), - value: value, - gasLimit: gasLimit, - gasPrice: gasPrice, - data: payload, - chainID: chainID + contract: this.getAddress(), + bytecode: bytecode, + gasLimit: BigInt(gasLimit.valueOf()), + arguments: initArguments, + isUpgradeable: metadataAsJson.upgradeable, + isReadable: metadataAsJson.readable, + isPayable: metadataAsJson.payable, + isPayableBySmartContract: metadataAsJson.payableBySc, }); + transaction.setChainID(chainID); + transaction.setValue(value ?? 0); + transaction.setGasPrice(gasPrice ?? TRANSACTION_MIN_GAS_PRICE); + return transaction; } @@ -172,24 +206,27 @@ export class SmartContract implements ISmartContract { this.ensureHasAddress(); + const config = new TransactionsFactoryConfig({ chainID: chainID.valueOf() }); + const factory = new SmartContractTransactionsFactory({ + config: config, + abi: this.abi, + }); + args = args || []; value = value || 0; - let payload = new ContractCallPayloadBuilder() - .setFunction(func) - .setArgs(args) - .build(); - - let transaction = new Transaction({ + const transaction = factory.createTransactionForExecute({ sender: caller, - receiver: receiver ? receiver : this.getAddress(), - value: value, - gasLimit: gasLimit, - gasPrice: gasPrice, - data: payload, - chainID: chainID, + contract: receiver ? receiver : this.getAddress(), + function: func.toString(), + gasLimit: BigInt(gasLimit.valueOf()), + arguments: args, }); + transaction.setChainID(chainID); + transaction.setValue(value); + transaction.setGasPrice(gasPrice ?? TRANSACTION_MIN_GAS_PRICE); + return transaction; } @@ -219,26 +256,8 @@ export class SmartContract implements ISmartContract { * @param nonce The owner nonce used for the deployment transaction */ static computeAddress(owner: IAddress, nonce: INonce): IAddress { - let initialPadding = Buffer.alloc(8, 0); - let ownerPubkey = new Address(owner.bech32()).pubkey(); - let shardSelector = ownerPubkey.slice(30); - let ownerNonceBytes = Buffer.alloc(8); - - const bigNonce = new BigNumber(nonce.valueOf().toString(10)); - const bigNonceBuffer = bigIntToBuffer(bigNonce); - ownerNonceBytes.write(bigNonceBuffer.reverse().toString('hex'), 'hex'); - - let bytesToHash = Buffer.concat([ownerPubkey, ownerNonceBytes]); - let hash = createKeccakHash("keccak256").update(bytesToHash).digest(); - let vmTypeBytes = Buffer.from(ArwenVirtualMachine, "hex"); - let addressBytes = Buffer.concat([ - initialPadding, - vmTypeBytes, - hash.slice(10, 30), - shardSelector - ]); - - let address = new Address(addressBytes); - return address; + const deployer = Address.fromBech32(owner.bech32()); + const addressComputer = new AddressComputer(); + return addressComputer.computeContractAddress(deployer, BigInt(nonce.valueOf())); } } diff --git a/src/smartcontracts/smartContractResults.local.net.spec.ts b/src/smartcontracts/smartContractResults.local.net.spec.ts index eeadf13d4..1ad837676 100644 --- a/src/smartcontracts/smartContractResults.local.net.spec.ts +++ b/src/smartcontracts/smartContractResults.local.net.spec.ts @@ -5,6 +5,10 @@ import { TransactionWatcher } from "../transactionWatcher"; import { ContractFunction } from "./function"; import { ResultsParser } from "./resultsParser"; import { SmartContract } from "./smartContract"; +import { TransactionsFactoryConfig } from "../transactionsFactories/transactionsFactoryConfig"; +import { SmartContractTransactionsFactory } from "../transactionsFactories/smartContractTransactionsFactory"; +import { promises } from "fs"; +import { TransactionComputer } from "../transactionComputer"; describe("fetch transactions from local testnet", function () { let alice: TestWallet; @@ -16,7 +20,9 @@ describe("fetch transactions from local testnet", function () { before(async function () { ({ alice } = await loadTestWallets()); watcher = new TransactionWatcher({ - getTransaction: async (hash: string) => { return await provider.getTransaction(hash, true) } + getTransaction: async (hash: string) => { + return await provider.getTransaction(hash, true); + }, }); }); @@ -38,7 +44,7 @@ describe("fetch transactions from local testnet", function () { codePath: "src/testdata/counter.wasm", gasLimit: 3000000, initArguments: [], - chainID: network.ChainID + chainID: network.ChainID, }); // ++ @@ -46,23 +52,82 @@ describe("fetch transactions from local testnet", function () { func: new ContractFunction("increment"), gasLimit: 3000000, chainID: network.ChainID, - caller: alice.address + caller: alice.address, }); transactionIncrement.setNonce(alice.account.nonce); - await alice.signer.sign(transactionIncrement); + transactionIncrement.applySignature(await alice.signer.sign(transactionIncrement.serializeForSigning())); + + alice.account.incrementNonce(); + + // Broadcast & execute + const txHashDeploy = await provider.sendTransaction(transactionDeploy); + const txHashIncrement = await provider.sendTransaction(transactionIncrement); + + await watcher.awaitCompleted(txHashDeploy); + await watcher.awaitCompleted(txHashIncrement); + + const transactionOnNetworkDeploy = await provider.getTransaction(txHashDeploy); + const transactionOnNetworkIncrement = await provider.getTransaction(txHashIncrement); + + let bundle = resultsParser.parseUntypedOutcome(transactionOnNetworkDeploy); + assert.isTrue(bundle.returnCode.isSuccess()); + + bundle = resultsParser.parseUntypedOutcome(transactionOnNetworkIncrement); + assert.isTrue(bundle.returnCode.isSuccess()); + }); + + it("interact with counter smart contract using SmartContractTransactionsFactory", async function () { + this.timeout(60000); + + TransactionWatcher.DefaultPollingInterval = 5000; + TransactionWatcher.DefaultTimeout = 50000; + + let network = await provider.getNetworkConfig(); + await alice.sync(provider); + + const config = new TransactionsFactoryConfig({ chainID: network.ChainID }); + const factory = new SmartContractTransactionsFactory({ config: config }); + + const bytecode = await promises.readFile("src/testdata/counter.wasm"); + + const deployTransaction = factory.createTransactionForDeploy({ + sender: alice.address, + bytecode: bytecode, + gasLimit: 3000000n, + }); + deployTransaction.nonce = BigInt(alice.account.nonce.valueOf()); + + const transactionComputer = new TransactionComputer(); + deployTransaction.signature = await alice.signer.sign( + Buffer.from(transactionComputer.computeBytesForSigning(deployTransaction)), + ); + + const contractAddress = SmartContract.computeAddress(alice.address, alice.account.nonce); + alice.account.incrementNonce(); + + const smartContractCallTransaction = factory.createTransactionForExecute({ + sender: alice.address, + contract: contractAddress, + function: "increment", + gasLimit: 3000000n, + }); + smartContractCallTransaction.nonce = BigInt(alice.account.nonce.valueOf()); + smartContractCallTransaction.signature = await alice.signer.sign( + Buffer.from(transactionComputer.computeBytesForSigning(smartContractCallTransaction)), + ); alice.account.incrementNonce(); // Broadcast & execute - await provider.sendTransaction(transactionDeploy); - await provider.sendTransaction(transactionIncrement); + const deployTxHash = await provider.sendTransaction(deployTransaction); + const callTxHash = await provider.sendTransaction(smartContractCallTransaction); - await watcher.awaitCompleted(transactionDeploy); - await watcher.awaitCompleted(transactionIncrement); + await watcher.awaitCompleted(deployTxHash); + await watcher.awaitCompleted(callTxHash); - let transactionOnNetworkDeploy = await provider.getTransaction(transactionDeploy.getHash().hex()); - let transactionOnNetworkIncrement = await provider.getTransaction(transactionIncrement.getHash().hex()); + let transactionOnNetworkDeploy = await provider.getTransaction(deployTxHash); + let transactionOnNetworkIncrement = await provider.getTransaction(callTxHash); let bundle = resultsParser.parseUntypedOutcome(transactionOnNetworkDeploy); assert.isTrue(bundle.returnCode.isSuccess()); diff --git a/src/smartcontracts/transactionPayloadBuilders.ts b/src/smartcontracts/transactionPayloadBuilders.ts index 84c825422..215b9e729 100644 --- a/src/smartcontracts/transactionPayloadBuilders.ts +++ b/src/smartcontracts/transactionPayloadBuilders.ts @@ -1,13 +1,13 @@ - +import { WasmVirtualMachine } from "../constants"; import { TransactionPayload } from "../transactionPayload"; import { guardValueIsSet } from "../utils"; import { ArgSerializer } from "./argSerializer"; import { ICode, ICodeMetadata, IContractFunction } from "./interface"; import { TypedValue } from "./typesystem"; -export const ArwenVirtualMachine = "0500"; - /** + * @deprecated Use {@link SmartContractTransactionsFactory} instead. + * * A builder for {@link TransactionPayload} objects, to be used for Smart Contract deployment transactions. */ export class ContractDeployPayloadBuilder { @@ -55,7 +55,7 @@ export class ContractDeployPayloadBuilder { let code = this.code!.toString(); let codeMetadata = this.codeMetadata.toString(); - let data = `${code}@${ArwenVirtualMachine}@${codeMetadata}`; + let data = `${code}@${WasmVirtualMachine}@${codeMetadata}`; data = appendArgumentsToString(data, this.arguments); return new TransactionPayload(data); @@ -63,6 +63,8 @@ export class ContractDeployPayloadBuilder { } /** + * @deprecated Use {@link SmartContractTransactionsFactory} instead. + * * A builder for {@link TransactionPayload} objects, to be used for Smart Contract upgrade transactions. */ export class ContractUpgradePayloadBuilder { @@ -118,6 +120,8 @@ export class ContractUpgradePayloadBuilder { } /** + * @deprecated Use {@link SmartContractTransactionsFactory} instead. + * * A builder for {@link TransactionPayload} objects, to be used for Smart Contract execution transactions. */ export class ContractCallPayloadBuilder { diff --git a/src/smartcontracts/typesystem/abiRegistry.spec.ts b/src/smartcontracts/typesystem/abiRegistry.spec.ts index 3d151e1d0..d3d966e8e 100644 --- a/src/smartcontracts/typesystem/abiRegistry.spec.ts +++ b/src/smartcontracts/typesystem/abiRegistry.spec.ts @@ -63,20 +63,20 @@ describe("test abi registry", () => { it("binary codec correctly decodes perform action result", async () => { let bc = new BinaryCodec(); let buff = Buffer.from( - "0588c738a5d26c0e3a2b4f9e8110b540ee9c0b71a3be057569a5a7b0fcb482c8f70000000806f05b59d3b200000000000b68656c6c6f20776f726c6400000000", - "hex" + "0500000000000000000500d006f73c4221216fa679bc559005584c4f1160e569e1000000012a0000000003616464000000010000000107", + "hex", ); - let registry = await loadAbiRegistry("src/testdata/multisig.abi.json"); + let registry = await loadAbiRegistry("src/testdata/multisig-full.abi.json"); let performAction = registry.getEndpoint("getActionData"); assert.equal(performAction.output[0].type.getName(), "Action"); let result = bc.decodeTopLevel(buff, performAction.output[0].type); assert.deepEqual( JSON.stringify(result.valueOf()), - `{"name":"SendTransferExecute","fields":[{"to":{"bech32":"erd13rrn3fwjds8r5260n6q3pd2qa6wqkudrhczh26d957c0edyzermshds0k8","pubkey":"88c738a5d26c0e3a2b4f9e8110b540ee9c0b71a3be057569a5a7b0fcb482c8f7"},"egld_amount":"500000000000000000","endpoint_name":{"type":"Buffer","data":[104,101,108,108,111,32,119,111,114,108,100]},"arguments":[]}]}` + `{"name":"SendTransferExecuteEgld","fields":[{"to":{"bech32":"erd1qqqqqqqqqqqqqpgq6qr0w0zzyysklfneh32eqp2cf383zc89d8sstnkl60","pubkey":"00000000000000000500d006f73c4221216fa679bc559005584c4f1160e569e1"},"egld_amount":"42","opt_gas_limit":null,"endpoint_name":{"type":"Buffer","data":[97,100,100]},"arguments":[{"type":"Buffer","data":[7]}]}]}`, ); - assert.equal(result.valueOf().name, "SendTransferExecute"); + assert.equal(result.valueOf().name, "SendTransferExecuteEgld"); }); it("should load ABI containing arrayN and nested structs", async () => { @@ -98,7 +98,12 @@ describe("test abi registry", () => { it("should load ABI when custom types are out of order (a)", async () => { const registry = await loadAbiRegistry("src/testdata/custom-types-out-of-order-a.abi.json"); - assert.deepEqual(registry.getStruct("EsdtTokenTransfer").getNamesOfDependencies(), ["EsdtTokenType", "TokenIdentifier", "u64", "BigUint"]); + assert.deepEqual(registry.getStruct("EsdtTokenTransfer").getNamesOfDependencies(), [ + "EsdtTokenType", + "TokenIdentifier", + "u64", + "BigUint", + ]); assert.deepEqual(registry.getEnum("EsdtTokenType").getNamesOfDependencies(), []); assert.deepEqual(registry.getStruct("TypeA").getNamesOfDependencies(), ["TypeB", "TypeC", "u64"]); assert.deepEqual(registry.getStruct("TypeB").getNamesOfDependencies(), ["TypeC", "u64"]); @@ -108,7 +113,12 @@ describe("test abi registry", () => { it("should load ABI when custom types are out of order (b)", async () => { const registry = await loadAbiRegistry("src/testdata/custom-types-out-of-order-b.abi.json"); - assert.deepEqual(registry.getStruct("EsdtTokenTransfer").getNamesOfDependencies(), ["EsdtTokenType", "TokenIdentifier", "u64", "BigUint"]); + assert.deepEqual(registry.getStruct("EsdtTokenTransfer").getNamesOfDependencies(), [ + "EsdtTokenType", + "TokenIdentifier", + "u64", + "BigUint", + ]); assert.deepEqual(registry.getEnum("EsdtTokenType").getNamesOfDependencies(), []); assert.deepEqual(registry.getStruct("TypeA").getNamesOfDependencies(), ["TypeB", "TypeC", "u64"]); assert.deepEqual(registry.getStruct("TypeB").getNamesOfDependencies(), ["TypeC", "u64"]); @@ -119,15 +129,29 @@ describe("test abi registry", () => { const registry = await loadAbiRegistry("src/testdata/custom-types-out-of-order-c.abi.json"); assert.lengthOf(registry.customTypes, 5); - assert.deepEqual(registry.getStruct("LoanCreateOptions").getNamesOfDependencies(), ["BigUint", "Address", "TokenIdentifier", "Status", "bytes"]); - + assert.deepEqual(registry.getStruct("LoanCreateOptions").getNamesOfDependencies(), [ + "BigUint", + "Address", + "TokenIdentifier", + "Status", + "bytes", + ]); }); it("should load ABI when custom types are out of order (community example: d)", async () => { const registry = await loadAbiRegistry("src/testdata/custom-types-out-of-order-d.abi.json"); assert.lengthOf(registry.customTypes, 12); - assert.deepEqual(registry.getStruct("AuctionItem").getNamesOfDependencies(), ["u64", "Address", "BigUint", "Option", "NftData", "bytes", "TokenIdentifier", "List"]); + assert.deepEqual(registry.getStruct("AuctionItem").getNamesOfDependencies(), [ + "u64", + "Address", + "BigUint", + "Option", + "NftData", + "bytes", + "TokenIdentifier", + "List", + ]); }); it("should load ABI with counted-variadic", async () => { diff --git a/src/smartcontracts/typesystem/abiRegistry.ts b/src/smartcontracts/typesystem/abiRegistry.ts index 27c300eee..601b65be8 100644 --- a/src/smartcontracts/typesystem/abiRegistry.ts +++ b/src/smartcontracts/typesystem/abiRegistry.ts @@ -20,8 +20,8 @@ export class AbiRegistry { name: string; constructorDefinition: EndpointDefinition; endpoints: EndpointDefinition[]; - customTypes: CustomType[], - events?: EventDefinition[] + customTypes: CustomType[]; + events?: EventDefinition[]; }) { this.name = options.name; this.constructorDefinition = options.constructorDefinition; @@ -32,10 +32,10 @@ export class AbiRegistry { static create(options: { name?: string; - constructor?: any, + constructor?: any; endpoints?: any[]; - types?: Record - events?: any[] + types?: Record; + events?: any[]; }): AbiRegistry { const name = options.name || interfaceNamePlaceholder; const constructor = options.constructor || {}; @@ -45,7 +45,7 @@ export class AbiRegistry { // Load arbitrary input parameters into properly-defined objects (e.g. EndpointDefinition and CustomType). const constructorDefinition = EndpointDefinition.fromJSON({ name: "constructor", ...constructor }); - const endpointDefinitions = endpoints.map(item => EndpointDefinition.fromJSON(item)); + const endpointDefinitions = endpoints.map((item) => EndpointDefinition.fromJSON(item)); const customTypes: CustomType[] = []; for (const customTypeName in types) { @@ -60,14 +60,14 @@ export class AbiRegistry { } } - const eventDefinitions = events.map(item => EventDefinition.fromJSON(item)); + const eventDefinitions = events.map((item) => EventDefinition.fromJSON(item)); const registry = new AbiRegistry({ name: name, constructorDefinition: constructorDefinition, endpoints: endpointDefinitions, customTypes: customTypes, - events: eventDefinitions + events: eventDefinitions, }); const remappedRegistry = registry.remapToKnownTypes(); @@ -158,26 +158,31 @@ export class AbiRegistry { constructorDefinition: newConstructor, endpoints: newEndpoints, customTypes: newCustomTypes, - events: newEvents + events: newEvents, }); return newRegistry; } - private mapCustomTypeDepthFirst(typeToMap: CustomType, allTypesToMap: CustomType[], mapper: TypeMapper, mappedTypes: CustomType[]) { - const hasBeenMapped = mappedTypes.findIndex(type => type.getName() == typeToMap.getName()) >= 0; + private mapCustomTypeDepthFirst( + typeToMap: CustomType, + allTypesToMap: CustomType[], + mapper: TypeMapper, + mappedTypes: CustomType[], + ) { + const hasBeenMapped = mappedTypes.findIndex((type) => type.getName() == typeToMap.getName()) >= 0; if (hasBeenMapped) { return; } for (const typeName of typeToMap.getNamesOfDependencies()) { - const dependencyType = allTypesToMap.find(type => type.getName() == typeName); + const dependencyType = allTypesToMap.find((type) => type.getName() == typeName); if (!dependencyType) { // It's a type that we don't have to map (e.g. could be a primitive type). continue; } - this.mapCustomTypeDepthFirst(dependencyType, allTypesToMap, mapper, mappedTypes) + this.mapCustomTypeDepthFirst(dependencyType, allTypesToMap, mapper, mappedTypes); } const mappedType = mapper.mapType(typeToMap); @@ -187,11 +192,11 @@ export class AbiRegistry { function mapEndpoint(endpoint: EndpointDefinition, mapper: TypeMapper): EndpointDefinition { const newInput = endpoint.input.map( - (e) => new EndpointParameterDefinition(e.name, e.description, mapper.mapType(e.type)) + (e) => new EndpointParameterDefinition(e.name, e.description, mapper.mapType(e.type)), ); const newOutput = endpoint.output.map( - (e) => new EndpointParameterDefinition(e.name, e.description, mapper.mapType(e.type)) + (e) => new EndpointParameterDefinition(e.name, e.description, mapper.mapType(e.type)), ); return new EndpointDefinition(endpoint.name, newInput, newOutput, endpoint.modifiers); @@ -199,11 +204,12 @@ function mapEndpoint(endpoint: EndpointDefinition, mapper: TypeMapper): Endpoint function mapEvent(event: EventDefinition, mapper: TypeMapper): EventDefinition { const newInputs = event.inputs.map( - (e) => new EventTopicDefinition({ - name: e.name, - type: mapper.mapType(e.type), - indexed: e.indexed - }) + (e) => + new EventTopicDefinition({ + name: e.name, + type: mapper.mapType(e.type), + indexed: e.indexed, + }), ); return new EventDefinition(event.identifier, newInputs); diff --git a/src/smartcontracts/typesystem/address.ts b/src/smartcontracts/typesystem/address.ts index 25a869840..53391e952 100644 --- a/src/smartcontracts/typesystem/address.ts +++ b/src/smartcontracts/typesystem/address.ts @@ -32,7 +32,7 @@ export class AddressValue extends PrimitiveValue { /** * Returns whether two objects have the same value. - * + * * @param other another AddressValue */ equals(other: AddressValue): boolean { diff --git a/src/smartcontracts/typesystem/algebraic.ts b/src/smartcontracts/typesystem/algebraic.ts index 5d41282c6..91225dee0 100644 --- a/src/smartcontracts/typesystem/algebraic.ts +++ b/src/smartcontracts/typesystem/algebraic.ts @@ -16,7 +16,7 @@ export class OptionalType extends Type { } isAssignableFrom(type: Type): boolean { - if (!(type.hasExactClass(OptionalType.ClassName))) { + if (!type.hasExactClass(OptionalType.ClassName)) { return false; } diff --git a/src/smartcontracts/typesystem/boolean.ts b/src/smartcontracts/typesystem/boolean.ts index 78b68dac1..9eef9423b 100644 --- a/src/smartcontracts/typesystem/boolean.ts +++ b/src/smartcontracts/typesystem/boolean.ts @@ -30,7 +30,7 @@ export class BooleanValue extends PrimitiveValue { /** * Returns whether two objects have the same value. - * + * * @param other another BooleanValue */ equals(other: BooleanValue): boolean { diff --git a/src/smartcontracts/typesystem/composite.spec.ts b/src/smartcontracts/typesystem/composite.spec.ts index f3b4ebf60..e8ee76269 100644 --- a/src/smartcontracts/typesystem/composite.spec.ts +++ b/src/smartcontracts/typesystem/composite.spec.ts @@ -5,7 +5,6 @@ import { CompositeType, CompositeValue } from "./composite"; import { EndpointParameterDefinition } from "./endpoint"; import { U32Type, U32Value } from "./numerical"; - describe("test composite", () => { const serializer = new ArgSerializer(); @@ -21,7 +20,7 @@ describe("test composite", () => { it("should get valueOf() upon decoding", () => { const compositeType = new CompositeType(new U32Type(), new BytesType()); - const endpointDefinition = new EndpointParameterDefinition("", "", compositeType) + const endpointDefinition = new EndpointParameterDefinition("", "", compositeType); const [compositeValue] = serializer.stringToValues("2a@abba", [endpointDefinition]); const values = compositeValue.valueOf(); @@ -43,7 +42,7 @@ describe("test composite", () => { it("should get valueOf() upon decoding, when items are missing", () => { const compositeType = new CompositeType(new U32Type(), new BytesType()); - const endpointDefinition = new EndpointParameterDefinition("", "", compositeType) + const endpointDefinition = new EndpointParameterDefinition("", "", compositeType); const [compositeValue] = serializer.stringToValues("", [endpointDefinition]); const values = compositeValue.valueOf(); diff --git a/src/smartcontracts/typesystem/composite.ts b/src/smartcontracts/typesystem/composite.ts index b34417953..6892a9a1f 100644 --- a/src/smartcontracts/typesystem/composite.ts +++ b/src/smartcontracts/typesystem/composite.ts @@ -32,7 +32,7 @@ export class CompositeValue extends TypedValue { } static fromItems(...items: TypedValue[]): CompositeValue { - let typeParameters = items.map(value => value.getType()); + let typeParameters = items.map((value) => value.getType()); let type = new CompositeType(...typeParameters); return new CompositeValue(type, items); } @@ -42,7 +42,7 @@ export class CompositeValue extends TypedValue { } valueOf(): any[] { - return this.items.map(item => item?.valueOf()); + return this.items.map((item) => item?.valueOf()); } equals(other: CompositeValue): boolean { diff --git a/src/smartcontracts/typesystem/endpoint.spec.ts b/src/smartcontracts/typesystem/endpoint.spec.ts index 41e7f1f8b..b26cdb26f 100644 --- a/src/smartcontracts/typesystem/endpoint.spec.ts +++ b/src/smartcontracts/typesystem/endpoint.spec.ts @@ -1,19 +1,18 @@ import { assert } from "chai"; import { EndpointDefinition } from "./endpoint"; - describe("test endpoint", () => { - it('should handle an only-owner modifier', async () => { - const actual = EndpointDefinition.fromJSON({ - name: 'foo', - onlyOwner: true, - mutability: 'payable', - payableInTokens: [], - inputs: [], - outputs: [], - }) + it("should handle an only-owner modifier", async () => { + const actual = EndpointDefinition.fromJSON({ + name: "foo", + onlyOwner: true, + mutability: "payable", + payableInTokens: [], + inputs: [], + outputs: [], + }); - assert.isTrue(actual.modifiers.onlyOwner) - assert.isTrue(actual.modifiers.isOnlyOwner()) - }) + assert.isTrue(actual.modifiers.onlyOwner); + assert.isTrue(actual.modifiers.isOnlyOwner()); + }); }); diff --git a/src/smartcontracts/typesystem/endpoint.ts b/src/smartcontracts/typesystem/endpoint.ts index 4fa0cbdba..9868b72dd 100644 --- a/src/smartcontracts/typesystem/endpoint.ts +++ b/src/smartcontracts/typesystem/endpoint.ts @@ -10,7 +10,12 @@ export class EndpointDefinition { readonly output: EndpointParameterDefinition[] = []; readonly modifiers: EndpointModifiers; - constructor(name: string, input: EndpointParameterDefinition[], output: EndpointParameterDefinition[], modifiers: EndpointModifiers) { + constructor( + name: string, + input: EndpointParameterDefinition[], + output: EndpointParameterDefinition[], + modifiers: EndpointModifiers, + ) { this.name = name; this.input = input || []; this.output = output || []; @@ -22,12 +27,12 @@ export class EndpointDefinition { } static fromJSON(json: { - name: string, - onlyOwner?: boolean - mutability: string, - payableInTokens: string[], - inputs: any[], - outputs: any[] + name: string; + onlyOwner?: boolean; + mutability: string; + payableInTokens: string[]; + inputs: any[]; + outputs: any[]; }): EndpointDefinition { json.name = json.name == null ? NamePlaceholder : json.name; json.onlyOwner = json.onlyOwner || false; @@ -35,8 +40,8 @@ export class EndpointDefinition { json.inputs = json.inputs || []; json.outputs = json.outputs || []; - let input = json.inputs.map(param => EndpointParameterDefinition.fromJSON(param)); - let output = json.outputs.map(param => EndpointParameterDefinition.fromJSON(param)); + let input = json.inputs.map((param) => EndpointParameterDefinition.fromJSON(param)); + let output = json.outputs.map((param) => EndpointParameterDefinition.fromJSON(param)); let modifiers = new EndpointModifiers(json.mutability, json.payableInTokens, json.onlyOwner); return new EndpointDefinition(json.name, input, output, modifiers); @@ -98,8 +103,12 @@ export class EndpointParameterDefinition { this.type = type; } - static fromJSON(json: { name?: string, description?: string, type: string }): EndpointParameterDefinition { + static fromJSON(json: { name?: string; description?: string; type: string }): EndpointParameterDefinition { let parsedType = new TypeExpressionParser().parse(json.type); - return new EndpointParameterDefinition(json.name || NamePlaceholder, json.description || DescriptionPlaceholder, parsedType); + return new EndpointParameterDefinition( + json.name || NamePlaceholder, + json.description || DescriptionPlaceholder, + parsedType, + ); } } diff --git a/src/smartcontracts/typesystem/enum.spec.ts b/src/smartcontracts/typesystem/enum.spec.ts index 4e98ab650..50ede8e21 100644 --- a/src/smartcontracts/typesystem/enum.spec.ts +++ b/src/smartcontracts/typesystem/enum.spec.ts @@ -15,23 +15,18 @@ describe("test enums", () => { ]); let orangeVariant = new EnumVariantDefinition("Orange", 1, [ - new FieldDefinition("0", "hex code", new StringType()) + new FieldDefinition("0", "hex code", new StringType()), ]); - let enumType = new EnumType("Colour", [ - greenVariant, - orangeVariant - ]); + let enumType = new EnumType("Colour", [greenVariant, orangeVariant]); let green = new EnumValue(enumType, greenVariant, [ new Field(new U8Value(0), "0"), new Field(new U8Value(255), "1"), - new Field(new U8Value(0), "2") + new Field(new U8Value(0), "2"), ]); - let orange = new EnumValue(enumType, orangeVariant, [ - new Field(new StringValue("#FFA500"), "0") - ]); + let orange = new EnumValue(enumType, orangeVariant, [new Field(new StringValue("#FFA500"), "0")]); assert.lengthOf(green.getFields(), 3); assert.lengthOf(orange.getFields(), 1); @@ -52,7 +47,7 @@ describe("test enums", () => { ]); let orangeVariant = new EnumVariantDefinition("Orange", 1, [ - new FieldDefinition("0", "hex code", new StringType()) + new FieldDefinition("0", "hex code", new StringType()), ]); let yellowVariant = new EnumVariantDefinition("Yellow", 2, [ @@ -62,54 +57,37 @@ describe("test enums", () => { ]); // Define enum type - let enumType = new EnumType("Colour", [ - greenVariant, - orangeVariant, - yellowVariant - ]); + let enumType = new EnumType("Colour", [greenVariant, orangeVariant, yellowVariant]); // Create enum values let green = new EnumValue(enumType, greenVariant, [ new Field(new U8Value(0), "0"), new Field(new U8Value(255), "1"), - new Field(new U8Value(0), "2") + new Field(new U8Value(0), "2"), ]); - let orange = new EnumValue(enumType, orangeVariant, [ - new Field(new StringValue("#FFA500"), "0") - ]); + let orange = new EnumValue(enumType, orangeVariant, [new Field(new StringValue("#FFA500"), "0")]); let yellow = new EnumValue(enumType, yellowVariant, [ new Field(new U8Value(255), "red"), new Field(new U8Value(255), "green"), - new Field(new U8Value(0), "blue") + new Field(new U8Value(0), "blue"), ]); // Test valueOf() assert.deepEqual(green.valueOf(), { - "name": "Green", - "fields": [ - new BigNumber(0), - new BigNumber(255), - new BigNumber(0) - ] + name: "Green", + fields: [new BigNumber(0), new BigNumber(255), new BigNumber(0)], }); assert.deepEqual(orange.valueOf(), { - "name": "Orange", - "fields": [ - "#FFA500" - ] + name: "Orange", + fields: ["#FFA500"], }); assert.deepEqual(yellow.valueOf(), { - "name": "Yellow", - "fields": [ - new BigNumber(255), - new BigNumber(255), - new BigNumber(0) - ] + name: "Yellow", + fields: [new BigNumber(255), new BigNumber(255), new BigNumber(0)], }); }); }); - diff --git a/src/smartcontracts/typesystem/enum.ts b/src/smartcontracts/typesystem/enum.ts index 1320a4b41..a0a2b24d6 100644 --- a/src/smartcontracts/typesystem/enum.ts +++ b/src/smartcontracts/typesystem/enum.ts @@ -35,8 +35,8 @@ export class EnumType extends CustomType { return variants.map((variant, index) => { return { ...variant, - discriminant: index - } + discriminant: index, + }; }); } @@ -71,7 +71,7 @@ export class EnumVariantDefinition { constructor(name: string, discriminant: number, fieldsDefinitions: FieldDefinition[] = []) { guardTrue( discriminant < SimpleEnumMaxDiscriminant, - `discriminant for simple enum should be less than ${SimpleEnumMaxDiscriminant}` + `discriminant for simple enum should be less than ${SimpleEnumMaxDiscriminant}`, ); this.name = name; @@ -89,7 +89,7 @@ export class EnumVariantDefinition { } getFieldDefinition(name: string): FieldDefinition | undefined { - return this.fieldsDefinitions.find(item => item.name == name); + return this.fieldsDefinitions.find((item) => item.name == name); } getNamesOfDependencies(): string[] { @@ -109,7 +109,7 @@ export class EnumValue extends TypedValue { this.name = variant.name; this.discriminant = variant.discriminant; this.fields = fields; - this.fieldsByName = new Map(fields.map(field => [field.name, field])); + this.fieldsByName = new Map(fields.map((field) => [field.name, field])); let definitions = variant.getFieldsDefinitions(); Fields.checkTyping(this.fields, definitions); diff --git a/src/smartcontracts/typesystem/event.ts b/src/smartcontracts/typesystem/event.ts index 5b4dcac69..f358b4688 100644 --- a/src/smartcontracts/typesystem/event.ts +++ b/src/smartcontracts/typesystem/event.ts @@ -12,14 +12,11 @@ export class EventDefinition { this.inputs = inputs || []; } - static fromJSON(json: { - identifier: string, - inputs: any[] - }): EventDefinition { + static fromJSON(json: { identifier: string; inputs: any[] }): EventDefinition { json.identifier = json.identifier == null ? NamePlaceholder : json.identifier; json.inputs = json.inputs || []; - const inputs = json.inputs.map(param => EventTopicDefinition.fromJSON(param)); + const inputs = json.inputs.map((param) => EventTopicDefinition.fromJSON(param)); return new EventDefinition(json.identifier, inputs); } } @@ -29,19 +26,19 @@ export class EventTopicDefinition { readonly type: Type; readonly indexed: boolean; - constructor(options: { name: string, type: Type, indexed: boolean }) { + constructor(options: { name: string; type: Type; indexed: boolean }) { this.name = options.name; this.type = options.type; this.indexed = options.indexed; } - static fromJSON(json: { name?: string, type: string, indexed: boolean }): EventTopicDefinition { + static fromJSON(json: { name?: string; type: string; indexed: boolean }): EventTopicDefinition { const parsedType = new TypeExpressionParser().parse(json.type); return new EventTopicDefinition({ name: json.name || NamePlaceholder, type: parsedType, - indexed: json.indexed + indexed: json.indexed, }); } } diff --git a/src/smartcontracts/typesystem/factory.ts b/src/smartcontracts/typesystem/factory.ts index b48708295..651de575e 100644 --- a/src/smartcontracts/typesystem/factory.ts +++ b/src/smartcontracts/typesystem/factory.ts @@ -4,13 +4,13 @@ import { List } from "./generic"; import { TokenIdentifierValue } from "./tokenIdentifier"; export function createListOfAddresses(addresses: Address[]): List { - let addressesTyped = addresses.map(address => new AddressValue(address)); + let addressesTyped = addresses.map((address) => new AddressValue(address)); let list = List.fromItems(addressesTyped); return list; } export function createListOfTokenIdentifiers(identifiers: string[]): List { - let identifiersTyped = identifiers.map(identifier => new TokenIdentifierValue(identifier)); + let identifiersTyped = identifiers.map((identifier) => new TokenIdentifierValue(identifier)); let list = List.fromItems(identifiersTyped); return list; } diff --git a/src/smartcontracts/typesystem/fields.ts b/src/smartcontracts/typesystem/fields.ts index c281df94e..d8953cce4 100644 --- a/src/smartcontracts/typesystem/fields.ts +++ b/src/smartcontracts/typesystem/fields.ts @@ -13,7 +13,7 @@ export class FieldDefinition { this.type = type; } - static fromJSON(json: { name: string, description: string, type: string }): FieldDefinition { + static fromJSON(json: { name: string; description: string; type: string }): FieldDefinition { let parsedType = new TypeExpressionParser().parse(json.type); return new FieldDefinition(json.name, json.description, parsedType); } @@ -32,7 +32,9 @@ export class Field { const actualType: Type = this.value.getType(); if (!actualType.equals(expectedDefinition.type)) { - throw new errors.ErrTypingSystem(`check type of field "${expectedDefinition.name}; expected: ${expectedDefinition.type}, actual: ${actualType}"`); + throw new errors.ErrTypingSystem( + `check type of field "${expectedDefinition.name}; expected: ${expectedDefinition.type}, actual: ${actualType}"`, + ); } if (this.name != expectedDefinition.name) { throw new errors.ErrTypingSystem(`check name of field "${expectedDefinition.name}"`); @@ -53,7 +55,7 @@ export class Fields { for (let i = 0; i < fields.length; i++) { let field = fields[i]; let definition = definitions[i]; - + field.checkTyping(definition); } } diff --git a/src/smartcontracts/typesystem/generic.ts b/src/smartcontracts/typesystem/generic.ts index c8b540ea8..cfce5eb15 100644 --- a/src/smartcontracts/typesystem/generic.ts +++ b/src/smartcontracts/typesystem/generic.ts @@ -15,7 +15,7 @@ export class OptionType extends Type { } isAssignableFrom(type: Type): boolean { - if (!(type.hasExactClass(OptionType.ClassName))) { + if (!type.hasExactClass(OptionType.ClassName)) { return false; } diff --git a/src/smartcontracts/typesystem/index.ts b/src/smartcontracts/typesystem/index.ts index 4e79b5554..f7bc39d45 100644 --- a/src/smartcontracts/typesystem/index.ts +++ b/src/smartcontracts/typesystem/index.ts @@ -27,4 +27,3 @@ export * from "./typeExpressionParser"; export * from "./typeMapper"; export * from "./types"; export * from "./variadic"; - diff --git a/src/smartcontracts/typesystem/matchers.ts b/src/smartcontracts/typesystem/matchers.ts index c34e98e82..43957d535 100644 --- a/src/smartcontracts/typesystem/matchers.ts +++ b/src/smartcontracts/typesystem/matchers.ts @@ -27,7 +27,7 @@ export function onTypeSelect( onTuple: () => TResult; onEnum: () => TResult; onOther?: () => TResult; - } + }, ): TResult { if (type.hasExactClass(OptionType.ClassName)) { return selectors.onOption(); @@ -69,7 +69,7 @@ export function onTypedValueSelect( onTuple: () => TResult; onEnum: () => TResult; onOther?: () => TResult; - } + }, ): TResult { if (value.hasClassOrSuperclass(PrimitiveValue.ClassName)) { return selectors.onPrimitive(); @@ -112,7 +112,7 @@ export function onPrimitiveValueSelect( onTypeIdentifier: () => TResult; onNothing: () => TResult; onOther?: () => TResult; - } + }, ): TResult { if (value.hasExactClass(BooleanValue.ClassName)) { return selectors.onBoolean(); @@ -157,7 +157,7 @@ export function onPrimitiveTypeSelect( onTokenIndetifier: () => TResult; onNothing: () => TResult; onOther?: () => TResult; - } + }, ): TResult { if (type.hasExactClass(BooleanType.ClassName)) { return selectors.onBoolean(); diff --git a/src/smartcontracts/typesystem/numerical.ts b/src/smartcontracts/typesystem/numerical.ts index e7222e167..440600284 100644 --- a/src/smartcontracts/typesystem/numerical.ts +++ b/src/smartcontracts/typesystem/numerical.ts @@ -1,6 +1,6 @@ -import * as errors from "../../errors"; -import { PrimitiveType, PrimitiveValue, Type } from "./types"; import BigNumber from "bignumber.js"; +import * as errors from "../../errors"; +import { PrimitiveType, PrimitiveValue } from "./types"; export class NumericalType extends PrimitiveType { static ClassName = "NumericalType"; @@ -156,9 +156,13 @@ export class NumericalValue extends PrimitiveValue { readonly sizeInBytes: number | undefined; readonly withSign: boolean; - constructor(type: NumericalType, value: BigNumber.Value) { + constructor(type: NumericalType, value: BigNumber.Value | bigint) { super(type); + if (typeof value === "bigint") { + value = value.toString(); + } + this.value = new BigNumber(value); this.sizeInBytes = type.sizeInBytes; this.withSign = type.withSign; @@ -178,7 +182,7 @@ export class NumericalValue extends PrimitiveValue { /** * Returns whether two objects have the same value. - * + * * @param other another NumericalValue */ equals(other: NumericalValue): boolean { @@ -197,8 +201,8 @@ export class NumericalValue extends PrimitiveValue { export class U8Value extends NumericalValue { static ClassName = "U8Value"; - constructor(value: BigNumber.Value) { - super(new U8Type(), new BigNumber(value)); + constructor(value: BigNumber.Value | bigint) { + super(new U8Type(), value); } getClassName(): string { @@ -209,8 +213,8 @@ export class U8Value extends NumericalValue { export class I8Value extends NumericalValue { static ClassName = "I8Value"; - constructor(value: BigNumber.Value) { - super(new I8Type(), new BigNumber(value)); + constructor(value: BigNumber.Value | bigint) { + super(new I8Type(), value); } getClassName(): string { @@ -221,8 +225,8 @@ export class I8Value extends NumericalValue { export class U16Value extends NumericalValue { static ClassName = "U16Value"; - constructor(value: BigNumber.Value) { - super(new U16Type(), new BigNumber(value)); + constructor(value: BigNumber.Value | bigint) { + super(new U16Type(), value); } getClassName(): string { @@ -233,8 +237,8 @@ export class U16Value extends NumericalValue { export class I16Value extends NumericalValue { static ClassName = "I16Value"; - constructor(value: BigNumber.Value) { - super(new I16Type(), new BigNumber(value)); + constructor(value: BigNumber.Value | bigint) { + super(new I16Type(), value); } getClassName(): string { @@ -245,8 +249,8 @@ export class I16Value extends NumericalValue { export class U32Value extends NumericalValue { static ClassName = "U32Value"; - constructor(value: BigNumber.Value) { - super(new U32Type(), new BigNumber(value)); + constructor(value: BigNumber.Value | bigint) { + super(new U32Type(), value); } getClassName(): string { @@ -257,8 +261,8 @@ export class U32Value extends NumericalValue { export class I32Value extends NumericalValue { static ClassName = "I32Value"; - constructor(value: BigNumber.Value) { - super(new I32Type(), new BigNumber(value)); + constructor(value: BigNumber.Value | bigint) { + super(new I32Type(), value); } getClassName(): string { @@ -269,7 +273,7 @@ export class I32Value extends NumericalValue { export class U64Value extends NumericalValue { static ClassName = "U64Value"; - constructor(value: BigNumber.Value) { + constructor(value: BigNumber.Value | bigint) { super(new U64Type(), value); } @@ -281,7 +285,7 @@ export class U64Value extends NumericalValue { export class I64Value extends NumericalValue { static ClassName = "I64Value"; - constructor(value: BigNumber.Value) { + constructor(value: BigNumber.Value | bigint) { super(new I64Type(), value); } @@ -293,7 +297,7 @@ export class I64Value extends NumericalValue { export class BigUIntValue extends NumericalValue { static ClassName = "BigUIntValue"; - constructor(value: BigNumber.Value) { + constructor(value: BigNumber.Value | bigint) { super(new BigUIntType(), value); } @@ -305,7 +309,7 @@ export class BigUIntValue extends NumericalValue { export class BigIntValue extends NumericalValue { static ClassName = "BigIntValue"; - constructor(value: BigNumber.Value) { + constructor(value: BigNumber.Value | bigint) { super(new BigIntType(), value); } diff --git a/src/smartcontracts/typesystem/struct.spec.ts b/src/smartcontracts/typesystem/struct.spec.ts index ae7639968..36209019d 100644 --- a/src/smartcontracts/typesystem/struct.spec.ts +++ b/src/smartcontracts/typesystem/struct.spec.ts @@ -8,15 +8,12 @@ import { TokenIdentifierType, TokenIdentifierValue } from "./tokenIdentifier"; describe("test structs", () => { it("should get fields", () => { - let fooType = new StructType( - "Foo", - [ - new FieldDefinition("a", "", new TokenIdentifierType()), - new FieldDefinition("b", "", new BigUIntType()), - new FieldDefinition("c", "", new U32Type()), - new FieldDefinition("d", "", new BytesType()), - ] - ); + let fooType = new StructType("Foo", [ + new FieldDefinition("a", "", new TokenIdentifierType()), + new FieldDefinition("b", "", new BigUIntType()), + new FieldDefinition("c", "", new U32Type()), + new FieldDefinition("d", "", new BytesType()), + ]); let fooStruct = new Struct(fooType, [ new Field(new TokenIdentifierValue("lucky-token"), "a"), diff --git a/src/smartcontracts/typesystem/struct.ts b/src/smartcontracts/typesystem/struct.ts index e0adaac06..94e367f8f 100644 --- a/src/smartcontracts/typesystem/struct.ts +++ b/src/smartcontracts/typesystem/struct.ts @@ -1,5 +1,5 @@ -import { ErrMissingFieldOnStruct, ErrTypingSystem } from "../../errors"; -import { FieldDefinition, Field, Fields } from "./fields"; +import { ErrMissingFieldOnStruct } from "../../errors"; +import { Field, FieldDefinition, Fields } from "./fields"; import { CustomType, TypedValue } from "./types"; export class StructType extends CustomType { @@ -15,8 +15,8 @@ export class StructType extends CustomType { return StructType.ClassName; } - static fromJSON(json: { name: string, fields: any[] }): StructType { - let definitions = (json.fields || []).map(definition => FieldDefinition.fromJSON(definition)); + static fromJSON(json: { name: string; fields: any[] }): StructType { + let definitions = (json.fields || []).map((definition) => FieldDefinition.fromJSON(definition)); return new StructType(json.name, definitions); } @@ -25,7 +25,7 @@ export class StructType extends CustomType { } getFieldDefinition(name: string): FieldDefinition | undefined { - return this.fieldsDefinitions.find(item => item.name == name); + return this.fieldsDefinitions.find((item) => item.name == name); } getNamesOfDependencies(): string[] { @@ -44,7 +44,7 @@ export class Struct extends TypedValue { constructor(type: StructType, fields: Field[]) { super(type); this.fields = fields; - this.fieldsByName = new Map(fields.map(field => [field.name, field])); + this.fieldsByName = new Map(fields.map((field) => [field.name, field])); this.checkTyping(); } @@ -81,7 +81,7 @@ export class Struct extends TypedValue { return result; } - + equals(other: Struct): boolean { if (!this.getType().equals(other.getType())) { return false; diff --git a/src/smartcontracts/typesystem/tokenIdentifier.ts b/src/smartcontracts/typesystem/tokenIdentifier.ts index 7ea324cfe..5fea9d8b2 100644 --- a/src/smartcontracts/typesystem/tokenIdentifier.ts +++ b/src/smartcontracts/typesystem/tokenIdentifier.ts @@ -24,7 +24,7 @@ export class TokenIdentifierValue extends PrimitiveValue { } static egld(): TokenIdentifierValue { - return new TokenIdentifierValue(EGLDTokenIdentifier) + return new TokenIdentifierValue(EGLDTokenIdentifier); } static esdtTokenIdentifier(identifier: string): TokenIdentifierValue { @@ -46,7 +46,7 @@ export class TokenIdentifierValue extends PrimitiveValue { if (this.getLength() != other.getLength()) { return false; } - + return this.value == other.value; } diff --git a/src/smartcontracts/typesystem/tuple.ts b/src/smartcontracts/typesystem/tuple.ts index caaf7fd4b..f249913db 100644 --- a/src/smartcontracts/typesystem/tuple.ts +++ b/src/smartcontracts/typesystem/tuple.ts @@ -15,13 +15,15 @@ export class TupleType extends StructType { } private static prepareName(typeParameters: Type[]): string { - let fields: string = typeParameters.map(type => type.toString()).join(", "); + let fields: string = typeParameters.map((type) => type.toString()).join(", "); let result = `tuple<${fields}>`; return result; } private static prepareFieldDefinitions(typeParameters: Type[]): FieldDefinition[] { - let result = typeParameters.map((type, i) => new FieldDefinition(prepareFieldName(i), "anonymous tuple field", type)); + let result = typeParameters.map( + (type, i) => new FieldDefinition(prepareFieldName(i), "anonymous tuple field", type), + ); return result; } } @@ -50,7 +52,7 @@ export class Tuple extends Struct { throw new errors.ErrTypingSystem("bad tuple items"); } - let fieldsTypes = items.map(item => item.getType()); + let fieldsTypes = items.map((item) => item.getType()); let tupleType = new TupleType(...fieldsTypes); let fields = items.map((item, i) => new Field(item, prepareFieldName(i))); diff --git a/src/smartcontracts/typesystem/typeExpressionParser.spec.ts b/src/smartcontracts/typesystem/typeExpressionParser.spec.ts index 0465a1b54..0aa09ae2c 100644 --- a/src/smartcontracts/typesystem/typeExpressionParser.spec.ts +++ b/src/smartcontracts/typesystem/typeExpressionParser.spec.ts @@ -1,7 +1,7 @@ -import * as errors from "../../errors"; import { assert } from "chai"; -import { Type } from "./types"; +import { ErrTypingSystem } from "../../errors"; import { TypeExpressionParser } from "./typeExpressionParser"; +import { Type } from "./types"; describe("test parser", () => { let parser = new TypeExpressionParser(); @@ -102,54 +102,53 @@ describe("test parser", () => { ], }); - type = parser.parse("MultiArg, List>"); assert.deepEqual(type.toJSON(), { - "name": "MultiArg", - "typeParameters": [ + name: "MultiArg", + typeParameters: [ { - "name": "Option", - "typeParameters": [ + name: "Option", + typeParameters: [ { - "name": "u8", - "typeParameters": [] - } - ] + name: "u8", + typeParameters: [], + }, + ], }, { - "name": "List", - "typeParameters": [ + name: "List", + typeParameters: [ { - "name": "u16", - "typeParameters": [] - } - ] - } - ] + name: "u16", + typeParameters: [], + }, + ], + }, + ], }); type = parser.parse("variadic>"); assert.deepEqual(type.toJSON(), { - "name": "variadic", - "typeParameters": [ + name: "variadic", + typeParameters: [ { - "name": "multi", - "typeParameters": [ + name: "multi", + typeParameters: [ { - "name": "array32", - "typeParameters": [] + name: "array32", + typeParameters: [], }, { - "name": "u32", - "typeParameters": [] + name: "u32", + typeParameters: [], }, { - "name": "array64", - "typeParameters": [] - } - ] - } - ] + name: "array64", + typeParameters: [], + }, + ], + }, + ], }); }); @@ -195,8 +194,6 @@ describe("test parser", () => { ], }); - // TODO: In a future PR, replace the JSON-based parsing logic with a better one and enable this test. - // This test currently fails because JSON key de-duplication takes place: i32 is incorrectly de-duplicated by the parser. type = parser.parse("tuple2"); assert.deepEqual(type.toJSON(), { name: "tuple2", @@ -222,7 +219,7 @@ describe("test parser", () => { { name: "u64", typeParameters: [], - } + }, ], }, { @@ -231,13 +228,78 @@ describe("test parser", () => { { name: "u64", typeParameters: [], - } + }, + ], + }, + ], + }); + }); + + it("should parse ", () => { + let type: Type; + type = parser.parse("variadic>"); + assert.deepEqual(type.toJSON(), { + name: "variadic", + typeParameters: [ + { + name: "multi", + typeParameters: [ + { + name: "BigUint", + typeParameters: [], + }, + { + name: "BigUint", + typeParameters: [], + }, + { + name: "u64", + typeParameters: [], + }, + { + name: "BigUint", + typeParameters: [], + }, ], }, ], }); }); + it("should parse multi", () => { + const type = parser.parse("multi"); + + assert.deepEqual(type.toJSON(), { + name: "multi", + typeParameters: [ + { + name: "u8", + typeParameters: [], + }, + { + name: "utf-8 string", + typeParameters: [], + }, + { + name: "u8", + typeParameters: [], + }, + { + name: "utf-8 string", + typeParameters: [], + }, + { + name: "u8", + typeParameters: [], + }, + { + name: "utf-8 string", + typeParameters: [], + }, + ], + }); + }); + it("should handle utf-8 string types which contain spaces", () => { let type: Type; @@ -264,14 +326,12 @@ describe("test parser", () => { }, ], }); - }); it("should not parse expression", () => { - assert.throw(() => parser.parse("<>"), errors.ErrTypingSystem); - assert.throw(() => parser.parse("<"), errors.ErrTypingSystem); - // TODO: In a future PR replace Json Parsing logic with a better one and enable this test - //assert.throw(() => parser.parse("MultiResultVec"), errors.ErrTypingSystem); - assert.throw(() => parser.parse("a, b"), errors.ErrTypingSystem); + assert.throw(() => parser.parse("<>"), ErrTypingSystem); + assert.throw(() => parser.parse("<"), ErrTypingSystem); + assert.throw(() => parser.parse("MultiResultVec"), ErrTypingSystem); + assert.throw(() => parser.parse("a, b"), ErrTypingSystem); }); }); diff --git a/src/smartcontracts/typesystem/typeExpressionParser.ts b/src/smartcontracts/typesystem/typeExpressionParser.ts index 97be25865..62d2216c5 100644 --- a/src/smartcontracts/typesystem/typeExpressionParser.ts +++ b/src/smartcontracts/typesystem/typeExpressionParser.ts @@ -1,95 +1,31 @@ -import * as errors from "../../errors"; +import { TypeFormula } from "../../abi/typeFormula"; +import { TypeFormulaParser } from "../../abi/typeFormulaParser"; +import { ErrTypingSystem } from "../../errors"; import { Type } from "./types"; -var jsonHandler = require("json-duplicate-key-handle"); export class TypeExpressionParser { - parse(expression: string): Type { - let root = this.doParse(expression); - let rootKeys = Object.keys(root); - - if (rootKeys.length != 1) { - throw new errors.ErrTypingSystem(`bad type expression: ${expression}`); - } + private readonly backingTypeFormulaParser: TypeFormulaParser; - let name = rootKeys[0]; - let type = this.nodeToType(name, root[name]); - return type; + constructor() { + this.backingTypeFormulaParser = new TypeFormulaParser(); } - private doParse(expression: string): any { - let jsoned = this.getJsonedString(expression); - + parse(expression: string): Type { try { - return jsonHandler.parse(jsoned); - } catch (error) { - throw new errors.ErrTypingSystem(`cannot parse type expression: ${expression}. internal json: ${jsoned}.`); + return this.doParse(expression); + } catch (e) { + throw new ErrTypingSystem(`Failed to parse type expression: ${expression}. Error: ${e}`); } } - /** - * Converts a raw type expression to a JSON, parsing-friendly format. - * This is a workaround, so that the parser implementation is simpler (thus we actually rely on the JSON parser). - * - * @param expression a string such as: - * - * ``` - * - Option> - * - VarArgs> - * - MultiResultVec - * ``` - */ - private getJsonedString(expression: string) { - let jsoned = ""; - - for (var i = 0; i < expression.length; i++) { - let char = expression.charAt(i); - let previousChar = expression.charAt(i - 1); - let nextChar = expression.charAt(i + 1); - - if (char == "<") { - jsoned += ": {"; - } else if (char == ">") { - if (previousChar != ">") { - jsoned += ": {} }"; - } else { - jsoned += "}"; - } - } else if (char == ",") { - if (nextChar == ">") { - // Skip superfluous comma - } else if (previousChar == ">") { - jsoned += ","; - } else { - jsoned += ": {},"; - } - } else { - jsoned += char; - } - } - - // Split by the delimiters, but exclude the spaces that are found in the middle of "utf-8 string" - let symbolsRegex = /(:|\{|\}|,|\s)/; - let tokens = jsoned - // Hack for Safari compatibility, where we can't use negative lookbehind - .replace(/utf\-8\sstring/ig, "utf-8-string") - .split(symbolsRegex) - .filter((token) => token); - - jsoned = tokens.map((token) => (symbolsRegex.test(token) ? token : `"${token}"`)) - .map((token) => token.replace(/utf\-8\-string/ig, "utf-8 string")) - .join(""); - - if (tokens.length == 1) { - // Workaround for simple, non-generic types. - return `{${jsoned}: {}}`; - } - - return `{${jsoned}}`; + private doParse(expression: string): Type { + const typeFormula = this.backingTypeFormulaParser.parseExpression(expression); + const type = this.typeFormulaToType(typeFormula); + return type; } - private nodeToType(name: string, node: any): Type { - if (name.charAt(name.length - 1) === "1") { name = name.slice(0, -1); } - let typeParameters = Object.keys(node).map((key) => this.nodeToType(key, node[key])); - return new Type(name, typeParameters); + private typeFormulaToType(typeFormula: TypeFormula): Type { + const typeParameters = typeFormula.typeParameters.map((typeFormula) => this.typeFormulaToType(typeFormula)); + return new Type(typeFormula.name, typeParameters); } } diff --git a/src/smartcontracts/typesystem/typeMapper.spec.ts b/src/smartcontracts/typesystem/typeMapper.spec.ts index 987f16e34..130d511b6 100644 --- a/src/smartcontracts/typesystem/typeMapper.spec.ts +++ b/src/smartcontracts/typesystem/typeMapper.spec.ts @@ -39,10 +39,14 @@ describe("test mapper", () => { }); it("should map complex generic, composite, variadic types", () => { - testMapping("MultiResultVec>", new VariadicType( - new CompositeType(new I32Type(), new BytesType()), - )); - testMapping("VarArgs>", new VariadicType(new CompositeType(new I32Type(), new BytesType()))); + testMapping( + "MultiResultVec>", + new VariadicType(new CompositeType(new I32Type(), new BytesType())), + ); + testMapping( + "VarArgs>", + new VariadicType(new CompositeType(new I32Type(), new BytesType())), + ); testMapping("OptionalResult
", new OptionalType(new AddressType())); }); diff --git a/src/smartcontracts/typesystem/typeMapper.ts b/src/smartcontracts/typesystem/typeMapper.ts index 110c37fc6..edc40769f 100644 --- a/src/smartcontracts/typesystem/typeMapper.ts +++ b/src/smartcontracts/typesystem/typeMapper.ts @@ -21,7 +21,7 @@ import { U16Type, U32Type, U64Type, - U8Type + U8Type, } from "./numerical"; import { StringType } from "./string"; import { StructType } from "./struct"; @@ -99,7 +99,7 @@ export class TypeMapper { ["EgldOrEsdtTokenIdentifier", new TokenIdentifierType()], ["CodeMetadata", new CodeMetadataType()], ["nothing", new NothingType()], - ["AsyncCall", new NothingType()] + ["AsyncCall", new NothingType()], ]); this.learnedTypesMap = new Map(); @@ -180,8 +180,8 @@ export class TypeMapper { new EnumVariantDefinition( variant.name, variant.discriminant, - this.mappedFields(variant.getFieldsDefinitions()) - ) + this.mappedFields(variant.getFieldsDefinitions()), + ), ); let mappedEnum = new EnumType(type.getName(), variants); return mappedEnum; @@ -189,7 +189,7 @@ export class TypeMapper { private mappedFields(definitions: FieldDefinition[]): FieldDefinition[] { return definitions.map( - (definition) => new FieldDefinition(definition.name, definition.description, this.mapType(definition.type)) + (definition) => new FieldDefinition(definition.name, definition.description, this.mapType(definition.type)), ); } diff --git a/src/smartcontracts/typesystem/types.spec.ts b/src/smartcontracts/typesystem/types.spec.ts index 22f87769d..0639b9615 100644 --- a/src/smartcontracts/typesystem/types.spec.ts +++ b/src/smartcontracts/typesystem/types.spec.ts @@ -19,19 +19,19 @@ describe("test types", () => { }); it("should be assignable from", () => { - assert.isTrue((new Type("Type")).isAssignableFrom(new PrimitiveType("PrimitiveType"))); - assert.isTrue((new Type("Type")).isAssignableFrom(new BooleanType())); - assert.isTrue((new Type("Type")).isAssignableFrom(new AddressType())); - assert.isTrue((new Type("Type")).isAssignableFrom(new U32Type())); + assert.isTrue(new Type("Type").isAssignableFrom(new PrimitiveType("PrimitiveType"))); + assert.isTrue(new Type("Type").isAssignableFrom(new BooleanType())); + assert.isTrue(new Type("Type").isAssignableFrom(new AddressType())); + assert.isTrue(new Type("Type").isAssignableFrom(new U32Type())); - assert.isTrue((new PrimitiveType("PrimitiveType")).isAssignableFrom(new BooleanType())); - assert.isTrue((new PrimitiveType("PrimitiveType")).isAssignableFrom(new AddressType())); - assert.isTrue((new PrimitiveType("PrimitiveType")).isAssignableFrom(new U32Type())); + assert.isTrue(new PrimitiveType("PrimitiveType").isAssignableFrom(new BooleanType())); + assert.isTrue(new PrimitiveType("PrimitiveType").isAssignableFrom(new AddressType())); + assert.isTrue(new PrimitiveType("PrimitiveType").isAssignableFrom(new U32Type())); - assert.isTrue((new AddressType()).isAssignableFrom(new AddressType())); - assert.isFalse((new AddressType()).isAssignableFrom(new BooleanType())); - assert.isFalse((new U32Type()).isAssignableFrom(new BooleanType())); - assert.isFalse((new U32Type()).isAssignableFrom(new PrimitiveType("PrimitiveType"))); + assert.isTrue(new AddressType().isAssignableFrom(new AddressType())); + assert.isFalse(new AddressType().isAssignableFrom(new BooleanType())); + assert.isFalse(new U32Type().isAssignableFrom(new BooleanType())); + assert.isFalse(new U32Type().isAssignableFrom(new PrimitiveType("PrimitiveType"))); assert.isTrue(new BytesType().isAssignableFrom(new BytesType())); assert.isTrue(new U32Type().isAssignableFrom(parser.parse("u32"))); @@ -54,17 +54,32 @@ describe("test types", () => { it("should get fully qualified name", () => { assert.equal(new Type("foo").getFullyQualifiedName(), "multiversx:types:foo"); assert.equal(new U32Type().getFullyQualifiedName(), "multiversx:types:u32"); - assert.equal(parser.parse("MultiResultVec").getFullyQualifiedName(), "multiversx:types:MultiResultVec"); + assert.equal( + parser.parse("MultiResultVec").getFullyQualifiedName(), + "multiversx:types:MultiResultVec", + ); assert.equal(parser.parse("utf-8 string").getFullyQualifiedName(), "multiversx:types:utf-8 string"); - assert.equal(parser.parse("Option").getFullyQualifiedName(), "multiversx:types:Option"); + assert.equal( + parser.parse("Option").getFullyQualifiedName(), + "multiversx:types:Option", + ); }); it("types and values should have correct JavaScript class hierarchy", () => { assert.deepEqual(new U32Type().getClassHierarchy(), ["Type", "PrimitiveType", "NumericalType", "U32Type"]); - assert.deepEqual(new U32Value(42).getClassHierarchy(), ["TypedValue", "PrimitiveValue", "NumericalValue", "U32Value"]); + assert.deepEqual(new U32Value(42).getClassHierarchy(), [ + "TypedValue", + "PrimitiveValue", + "NumericalValue", + "U32Value", + ]); assert.deepEqual(new BytesType().getClassHierarchy(), ["Type", "PrimitiveType", "BytesType"]); - assert.deepEqual(new BytesValue(Buffer.from("foobar")).getClassHierarchy(), ["TypedValue", "PrimitiveValue", "BytesValue"]); + assert.deepEqual(new BytesValue(Buffer.from("foobar")).getClassHierarchy(), [ + "TypedValue", + "PrimitiveValue", + "BytesValue", + ]); }); it("should report type dependencies", () => { diff --git a/src/smartcontracts/typesystem/types.ts b/src/smartcontracts/typesystem/types.ts index 007b780cc..b3e68d5bb 100644 --- a/src/smartcontracts/typesystem/types.ts +++ b/src/smartcontracts/typesystem/types.ts @@ -12,7 +12,11 @@ export class Type { private readonly typeParameters: Type[]; protected readonly cardinality: TypeCardinality; - public constructor(name: string, typeParameters: Type[] = [], cardinality: TypeCardinality = TypeCardinality.fixed(1)) { + public constructor( + name: string, + typeParameters: Type[] = [], + cardinality: TypeCardinality = TypeCardinality.fixed(1), + ) { guardValueIsSet("name", name); this.name = name; @@ -29,8 +33,8 @@ export class Type { } getClassHierarchy(): string[] { - let prototypes = getJavascriptPrototypesInHierarchy(this, prototype => prototype.belongsToTypesystem); - let classNames = prototypes.map(prototype => (prototype).getClassName()).reverse(); + let prototypes = getJavascriptPrototypesInHierarchy(this, (prototype) => prototype.belongsToTypesystem); + let classNames = prototypes.map((prototype) => (prototype).getClassName()).reverse(); return classNames; } @@ -38,11 +42,13 @@ export class Type { * Gets the fully qualified name of the type, to allow for better (efficient and non-ambiguous) type comparison within the custom typesystem. */ getFullyQualifiedName(): string { - let joinedTypeParameters = this.getTypeParameters().map(type => type.getFullyQualifiedName()).join(", "); + let joinedTypeParameters = this.getTypeParameters() + .map((type) => type.getFullyQualifiedName()) + .join(", "); - return this.isGenericType() ? - `multiversx:types:${this.getName()}<${joinedTypeParameters}>` : - `multiversx:types:${this.getName()}`; + return this.isGenericType() + ? `multiversx:types:${this.getName()}<${joinedTypeParameters}>` + : `multiversx:types:${this.getName()}`; } hasExactClass(className: string): boolean { @@ -68,10 +74,12 @@ export class Type { } /** - * Generates type expressions similar to mx-sdk-rs. + * Generates type expressions similar to mx-sdk-rs. */ toString() { - let typeParameters: string = this.getTypeParameters().map(type => type.toString()).join(", "); + let typeParameters: string = this.getTypeParameters() + .map((type) => type.toString()) + .join(", "); let typeParametersExpression = typeParameters ? `<${typeParameters}>` : ""; return `${this.name}${typeParametersExpression}`; } @@ -103,11 +111,11 @@ export class Type { /** * Inspired from: https://docs.microsoft.com/en-us/dotnet/api/system.type.isassignablefrom * For (most) generics, type invariance is expected (assumed) - neither covariance, nor contravariance are supported yet (will be supported in a next release). - * + * * One exception though: for {@link OptionType}, we simulate covariance for missing (not provided) values. * For example, Option is assignable from Option. * For more details, see the implementation of {@link OptionType} and @{@link OptionalType}. - * + * * Also see: * - https://en.wikipedia.org/wiki/Covariance_and_contravariance_(computer_science) * - https://docs.microsoft.com/en-us/dotnet/standard/generics/covariance-and-contravariance @@ -128,8 +136,8 @@ export class Type { } private static getFullyQualifiedNamesInHierarchy(type: Type): string[] { - let prototypes: any[] = getJavascriptPrototypesInHierarchy(type, prototype => prototype.belongsToTypesystem); - let fullyQualifiedNames = prototypes.map(prototype => prototype.getFullyQualifiedName.call(type)); + let prototypes: any[] = getJavascriptPrototypesInHierarchy(type, (prototype) => prototype.belongsToTypesystem); + let fullyQualifiedNames = prototypes.map((prototype) => prototype.getFullyQualifiedName.call(type)); return fullyQualifiedNames; } @@ -150,7 +158,7 @@ export class Type { toJSON(): any { return { name: this.name, - typeParameters: this.typeParameters.map(item => item.toJSON()) + typeParameters: this.typeParameters.map((item) => item.toJSON()), }; } @@ -161,14 +169,14 @@ export class Type { /** * A special marker for types within the custom typesystem. */ - belongsToTypesystem() { } + belongsToTypesystem() {} } /** * TODO: Simplify this class, keep only what is needed. - * + * * An abstraction for defining and operating with the cardinality of a (composite or simple) type. - * + * * Simple types (the ones that are directly encodable) have a fixed cardinality: [lower = 1, upper = 1]. * Composite types (not directly encodable) do not follow this constraint. For example: * - VarArgs: [lower = 0, upper = *] @@ -254,8 +262,8 @@ export abstract class TypedValue { } getClassHierarchy(): string[] { - let prototypes = getJavascriptPrototypesInHierarchy(this, prototype => prototype.belongsToTypesystem); - let classNames = prototypes.map(prototype => (prototype).getClassName()).reverse(); + let prototypes = getJavascriptPrototypesInHierarchy(this, (prototype) => prototype.belongsToTypesystem); + let classNames = prototypes.map((prototype) => (prototype).getClassName()).reverse(); return classNames; } @@ -278,7 +286,7 @@ export abstract class TypedValue { /** * A special marker for values within the custom typesystem. */ - belongsToTypesystem() { } + belongsToTypesystem() {} } export abstract class PrimitiveValue extends TypedValue { @@ -309,7 +317,6 @@ export class TypePlaceholder extends Type { } } - export class NullType extends Type { static ClassName = "NullType"; diff --git a/src/testdata/adder.abi.json b/src/testdata/adder.abi.json new file mode 100644 index 000000000..88d5bf134 --- /dev/null +++ b/src/testdata/adder.abi.json @@ -0,0 +1,62 @@ +{ + "buildInfo": { + "rustc": { + "version": "1.71.0-nightly", + "commitHash": "7f94b314cead7059a71a265a8b64905ef2511796", + "commitDate": "2023-04-23", + "channel": "Nightly", + "short": "rustc 1.71.0-nightly (7f94b314c 2023-04-23)" + }, + "contractCrate": { + "name": "adder", + "version": "0.0.0" + }, + "framework": { + "name": "multiversx-sc", + "version": "0.41.3" + } + }, + "docs": [ + "One of the simplest smart contracts possible,", + "it holds a single variable in storage, which anyone can increment." + ], + "name": "Adder", + "constructor": { + "inputs": [ + { + "name": "initial_value", + "type": "BigUint" + } + ], + "outputs": [] + }, + "endpoints": [ + { + "name": "getSum", + "mutability": "readonly", + "inputs": [], + "outputs": [ + { + "type": "BigUint" + } + ] + }, + { + "docs": [ + "Add desired amount to the storage variable." + ], + "name": "add", + "mutability": "mutable", + "inputs": [ + { + "name": "value", + "type": "BigUint" + } + ], + "outputs": [] + } + ], + "events": [], + "hasCallback": false, + "types": {} +} diff --git a/src/testdata/adder.wasm b/src/testdata/adder.wasm new file mode 100755 index 000000000..77ce7e234 Binary files /dev/null and b/src/testdata/adder.wasm differ diff --git a/src/testdata/multisig.abi.json b/src/testdata/multisig-full.abi.json similarity index 57% rename from src/testdata/multisig.abi.json rename to src/testdata/multisig-full.abi.json index 01a363c5a..401de7cc0 100644 --- a/src/testdata/multisig.abi.json +++ b/src/testdata/multisig-full.abi.json @@ -1,19 +1,20 @@ { "buildInfo": { "rustc": { - "version": "1.59.0-nightly", - "commitHash": "399ba6bb377ce02224b57c4d6e127e160fa76b34", - "commitDate": "2022-01-03", + "version": "1.71.0-nightly", + "commitHash": "a2b1646c597329d0a25efa3889b66650f65de1de", + "commitDate": "2023-05-25", "channel": "Nightly", - "short": "rustc 1.59.0-nightly (399ba6bb3 2022-01-03)" + "short": "rustc 1.71.0-nightly (a2b1646c5 2023-05-25)" }, "contractCrate": { "name": "multisig", - "version": "1.0.0" + "version": "1.0.0", + "gitVersion": "v0.45.2.1-reproducible-169-g37d970c" }, "framework": { - "name": "elrond-wasm", - "version": "0.25.0" + "name": "multiversx-sc", + "version": "0.47.2" } }, "docs": [ @@ -38,127 +39,30 @@ }, "endpoints": [ { - "docs": [ - "Allows the contract to receive funds even if it is marked as unpayable in the protocol." - ], - "name": "deposit", + "name": "upgrade", "mutability": "mutable", - "payableInTokens": [ - "*" - ], "inputs": [], "outputs": [] }, { "docs": [ - "Iterates through all actions and retrieves those that are still pending.", - "Serialized full action data:", - "- the action id", - "- the serialized action data", - "- (number of signers followed by) list of signer addresses." - ], - "name": "getPendingActionFullInfo", - "mutability": "readonly", - "inputs": [], - "outputs": [ - { - "type": "variadic", - "multi_result": true - } - ] - }, - { - "docs": [ - "Returns `true` (`1`) if the user has signed the action.", - "Does not check whether or not the user is still a board member and the signature valid." - ], - "name": "signed", - "mutability": "readonly", - "inputs": [ - { - "name": "user", - "type": "Address" - }, - { - "name": "action_id", - "type": "u32" - } - ], - "outputs": [ - { - "type": "bool" - } - ] - }, - { - "docs": [ - "Indicates user rights.", - "`0` = no rights,", - "`1` = can propose, but not sign,", - "`2` = can propose and sign." - ], - "name": "userRole", - "mutability": "readonly", - "inputs": [ - { - "name": "user", - "type": "Address" - } - ], - "outputs": [ - { - "type": "UserRole" - } - ] - }, - { - "docs": [ - "Lists all users that can sign actions." - ], - "name": "getAllBoardMembers", - "mutability": "readonly", - "inputs": [], - "outputs": [ - { - "type": "variadic
", - "multi_result": true - } - ] - }, - { - "docs": [ - "Lists all proposers that are not board members." - ], - "name": "getAllProposers", - "mutability": "readonly", - "inputs": [], - "outputs": [ - { - "type": "variadic
", - "multi_result": true - } - ] - }, - { - "docs": [ - "Used by board members to sign actions." + "Allows the contract to receive funds even if it is marked as unpayable in the protocol." ], - "name": "sign", + "name": "deposit", "mutability": "mutable", - "inputs": [ - { - "name": "action_id", - "type": "u32" - } + "payableInTokens": [ + "*" ], + "inputs": [], "outputs": [] }, { "docs": [ - "Board members can withdraw their signatures if they no longer desire for the action to be executed.", - "Actions that are left with no valid signatures can be then deleted to free up storage." + "Clears storage pertaining to an action that is no longer supposed to be executed.", + "Any signatures that the action received must first be removed, via `unsign`.", + "Otherwise this endpoint would be prone to abuse." ], - "name": "unsign", + "name": "discardAction", "mutability": "mutable", "inputs": [ { @@ -170,16 +74,15 @@ }, { "docs": [ - "Clears storage pertaining to an action that is no longer supposed to be executed.", - "Any signatures that the action received must first be removed, via `unsign`.", - "Otherwise this endpoint would be prone to abuse." + "Discard all the actions with the given IDs" ], - "name": "discardAction", + "name": "discardBatch", "mutability": "mutable", "inputs": [ { - "name": "action_id", - "type": "u32" + "name": "action_ids", + "type": "variadic", + "multi_arg": true } ], "outputs": [] @@ -212,11 +115,7 @@ ] }, { - "docs": [ - "Denormalized proposer count.", - "It is kept in sync with the user list by the contract." - ], - "name": "getNumProposers", + "name": "getNumGroups", "mutability": "readonly", "inputs": [], "outputs": [ @@ -227,10 +126,10 @@ }, { "docs": [ - "The index of the last proposed action.", - "0 means that no action was ever proposed yet." + "Denormalized proposer count.", + "It is kept in sync with the user list by the contract." ], - "name": "getActionLastIndex", + "name": "getNumProposers", "mutability": "readonly", "inputs": [], "outputs": [ @@ -240,56 +139,25 @@ ] }, { - "docs": [ - "Serialized action data of an action with index." - ], - "name": "getActionData", - "mutability": "readonly", - "inputs": [ - { - "name": "action_id", - "type": "u32" - } - ], - "outputs": [ - { - "type": "Action" - } - ] - }, - { - "docs": [ - "Gets addresses of all users who signed an action.", - "Does not check if those users are still board members or not,", - "so the result may contain invalid signers." - ], - "name": "getActionSigners", + "name": "getActionGroup", "mutability": "readonly", "inputs": [ { - "name": "action_id", + "name": "group_id", "type": "u32" } ], "outputs": [ { - "type": "List
" + "type": "variadic", + "multi_result": true } ] }, { - "docs": [ - "Gets addresses of all users who signed an action and are still board members.", - "All these signatures are currently valid." - ], - "name": "getActionSignerCount", + "name": "getLastGroupActionId", "mutability": "readonly", - "inputs": [ - { - "name": "action_id", - "type": "u32" - } - ], + "inputs": [], "outputs": [ { "type": "u32" @@ -298,20 +166,12 @@ }, { "docs": [ - "It is possible for board members to lose their role.", - "They are not automatically removed from all actions when doing so,", - "therefore the contract needs to re-check every time when actions are performed.", - "This function is used to validate the signers before performing an action.", - "It also makes it easy to check before performing an action." + "The index of the last proposed action.", + "0 means that no action was ever proposed yet." ], - "name": "getActionValidSignerCount", + "name": "getActionLastIndex", "mutability": "readonly", - "inputs": [ - { - "name": "action_id", - "type": "u32" - } - ], + "inputs": [], "outputs": [ { "type": "u32" @@ -408,12 +268,39 @@ "type": "BigUint" }, { - "name": "opt_function", - "type": "optional", + "name": "opt_gas_limit", + "type": "Option" + }, + { + "name": "function_call", + "type": "variadic", "multi_arg": true + } + ], + "outputs": [ + { + "type": "u32" + } + ] + }, + { + "name": "proposeTransferExecuteEsdt", + "mutability": "mutable", + "inputs": [ + { + "name": "to", + "type": "Address" }, { - "name": "arguments", + "name": "tokens", + "type": "List" + }, + { + "name": "opt_gas_limit", + "type": "Option" + }, + { + "name": "function_call", "type": "variadic", "multi_arg": true } @@ -426,7 +313,7 @@ }, { "docs": [ - "Propose a transaction in which the contract will perform a transfer-execute call.", + "Propose a transaction in which the contract will perform an async call call.", "Can call smart contract endpoints directly.", "Can use ESDTTransfer/ESDTNFTTransfer/MultiESDTTransfer to send tokens, while also optionally calling endpoints.", "Works well with builtin functions.", @@ -444,12 +331,11 @@ "type": "BigUint" }, { - "name": "opt_function", - "type": "optional", - "multi_arg": true + "name": "opt_gas_limit", + "type": "Option" }, { - "name": "arguments", + "name": "function_call", "type": "variadic", "multi_arg": true } @@ -521,28 +407,51 @@ ] }, { - "docs": [ - "Returns `true` (`1`) if `getActionValidSignerCount >= getQuorum`." - ], - "name": "quorumReached", - "mutability": "readonly", + "name": "proposeBatch", + "mutability": "mutable", "inputs": [ { - "name": "action_id", - "type": "u32" + "name": "actions", + "type": "variadic", + "multi_arg": true } ], "outputs": [ { - "type": "bool" + "type": "u32" } ] }, { "docs": [ - "Proposers and board members use this to launch signed actions." + "Used by board members to sign actions." ], - "name": "performAction", + "name": "sign", + "mutability": "mutable", + "inputs": [ + { + "name": "action_id", + "type": "u32" + } + ], + "outputs": [] + }, + { + "docs": [ + "Sign all the actions in the given batch" + ], + "name": "signBatch", + "mutability": "mutable", + "inputs": [ + { + "name": "group_id", + "type": "u32" + } + ], + "outputs": [] + }, + { + "name": "signAndPerform", "mutability": "mutable", "inputs": [ { @@ -552,34 +461,597 @@ ], "outputs": [ { - "type": "PerformActionResult" + "type": "optional
", + "multi_result": true } ] - } - ], - "hasCallback": false, - "types": { - "CallActionData": { - "type": "struct", - "fields": [ + }, + { + "name": "signBatchAndPerform", + "mutability": "mutable", + "inputs": [ + { + "name": "group_id", + "type": "u32" + } + ], + "outputs": [] + }, + { + "docs": [ + "Board members can withdraw their signatures if they no longer desire for the action to be executed.", + "Actions that are left with no valid signatures can be then deleted to free up storage." + ], + "name": "unsign", + "mutability": "mutable", + "inputs": [ + { + "name": "action_id", + "type": "u32" + } + ], + "outputs": [] + }, + { + "docs": [ + "Unsign all actions with the given IDs" + ], + "name": "unsignBatch", + "mutability": "mutable", + "inputs": [ + { + "name": "group_id", + "type": "u32" + } + ], + "outputs": [] + }, + { + "docs": [ + "Returns `true` (`1`) if the user has signed the action.", + "Does not check whether or not the user is still a board member and the signature valid." + ], + "name": "signed", + "mutability": "readonly", + "inputs": [ + { + "name": "user", + "type": "Address" + }, + { + "name": "action_id", + "type": "u32" + } + ], + "outputs": [ + { + "type": "bool" + } + ] + }, + { + "name": "unsignForOutdatedBoardMembers", + "mutability": "mutable", + "inputs": [ + { + "name": "action_id", + "type": "u32" + }, + { + "name": "outdated_board_members", + "type": "variadic", + "multi_arg": true + } + ], + "outputs": [] + }, + { + "docs": [ + "Returns `true` (`1`) if `getActionValidSignerCount >= getQuorum`." + ], + "name": "quorumReached", + "mutability": "readonly", + "inputs": [ + { + "name": "action_id", + "type": "u32" + } + ], + "outputs": [ + { + "type": "bool" + } + ] + }, + { + "docs": [ + "Proposers and board members use this to launch signed actions." + ], + "name": "performAction", + "mutability": "mutable", + "inputs": [ + { + "name": "action_id", + "type": "u32" + } + ], + "outputs": [ + { + "type": "optional
", + "multi_result": true + } + ] + }, + { + "docs": [ + "Perform all the actions in the given batch" + ], + "name": "performBatch", + "mutability": "mutable", + "inputs": [ + { + "name": "group_id", + "type": "u32" + } + ], + "outputs": [] + }, + { + "name": "dnsRegister", + "onlyOwner": true, + "mutability": "mutable", + "payableInTokens": [ + "EGLD" + ], + "inputs": [ + { + "name": "dns_address", + "type": "Address" + }, + { + "name": "name", + "type": "bytes" + } + ], + "outputs": [] + }, + { + "docs": [ + "Iterates through all actions and retrieves those that are still pending.", + "Serialized full action data:", + "- the action id", + "- the serialized action data", + "- (number of signers followed by) list of signer addresses." + ], + "name": "getPendingActionFullInfo", + "mutability": "readonly", + "inputs": [ + { + "name": "opt_range", + "type": "optional>", + "multi_arg": true + } + ], + "outputs": [ + { + "type": "variadic", + "multi_result": true + } + ], + "labels": [ + "multisig-external-view" + ], + "allow_multiple_var_args": true + }, + { + "docs": [ + "Indicates user rights.", + "`0` = no rights,", + "`1` = can propose, but not sign,", + "`2` = can propose and sign." + ], + "name": "userRole", + "mutability": "readonly", + "inputs": [ + { + "name": "user", + "type": "Address" + } + ], + "outputs": [ + { + "type": "UserRole" + } + ], + "labels": [ + "multisig-external-view" + ] + }, + { + "docs": [ + "Lists all users that can sign actions." + ], + "name": "getAllBoardMembers", + "mutability": "readonly", + "inputs": [], + "outputs": [ + { + "type": "variadic
", + "multi_result": true + } + ], + "labels": [ + "multisig-external-view" + ] + }, + { + "docs": [ + "Lists all proposers that are not board members." + ], + "name": "getAllProposers", + "mutability": "readonly", + "inputs": [], + "outputs": [ + { + "type": "variadic
", + "multi_result": true + } + ], + "labels": [ + "multisig-external-view" + ] + }, + { + "docs": [ + "Serialized action data of an action with index." + ], + "name": "getActionData", + "mutability": "readonly", + "inputs": [ + { + "name": "action_id", + "type": "u32" + } + ], + "outputs": [ + { + "type": "Action" + } + ], + "labels": [ + "multisig-external-view" + ] + }, + { + "docs": [ + "Gets addresses of all users who signed an action.", + "Does not check if those users are still board members or not,", + "so the result may contain invalid signers." + ], + "name": "getActionSigners", + "mutability": "readonly", + "inputs": [ + { + "name": "action_id", + "type": "u32" + } + ], + "outputs": [ + { + "type": "List
" + } + ], + "labels": [ + "multisig-external-view" + ] + }, + { + "docs": [ + "Gets addresses of all users who signed an action and are still board members.", + "All these signatures are currently valid." + ], + "name": "getActionSignerCount", + "mutability": "readonly", + "inputs": [ + { + "name": "action_id", + "type": "u32" + } + ], + "outputs": [ + { + "type": "u32" + } + ], + "labels": [ + "multisig-external-view" + ] + }, + { + "docs": [ + "It is possible for board members to lose their role.", + "They are not automatically removed from all actions when doing so,", + "therefore the contract needs to re-check every time when actions are performed.", + "This function is used to validate the signers before performing an action.", + "It also makes it easy to check before performing an action." + ], + "name": "getActionValidSignerCount", + "mutability": "readonly", + "inputs": [ + { + "name": "action_id", + "type": "u32" + } + ], + "outputs": [ + { + "type": "u32" + } + ], + "labels": [ + "multisig-external-view" + ] + } + ], + "events": [ + { + "identifier": "asyncCallSuccess", + "inputs": [ + { + "name": "results", + "type": "variadic", + "indexed": true + } + ] + }, + { + "identifier": "asyncCallError", + "inputs": [ + { + "name": "err_code", + "type": "u32", + "indexed": true + }, + { + "name": "err_message", + "type": "bytes", + "indexed": true + } + ] + }, + { + "identifier": "startPerformAction", + "inputs": [ + { + "name": "data", + "type": "ActionFullInfo" + } + ] + }, + { + "identifier": "performChangeUser", + "inputs": [ + { + "name": "action_id", + "type": "u32", + "indexed": true + }, + { + "name": "changed_user", + "type": "Address", + "indexed": true + }, + { + "name": "old_role", + "type": "UserRole", + "indexed": true + }, + { + "name": "new_role", + "type": "UserRole", + "indexed": true + } + ] + }, + { + "identifier": "performChangeQuorum", + "inputs": [ + { + "name": "action_id", + "type": "u32", + "indexed": true + }, + { + "name": "new_quorum", + "type": "u32", + "indexed": true + } + ] + }, + { + "identifier": "performAsyncCall", + "inputs": [ + { + "name": "action_id", + "type": "u32", + "indexed": true + }, { "name": "to", - "type": "Address" + "type": "Address", + "indexed": true }, { - "name": "egld_amount", - "type": "BigUint" + "name": "egld_value", + "type": "BigUint", + "indexed": true }, { - "name": "endpoint_name", - "type": "bytes" + "name": "gas", + "type": "u64", + "indexed": true + }, + { + "name": "endpoint", + "type": "bytes", + "indexed": true }, { "name": "arguments", - "type": "List" + "type": "variadic", + "indexed": true + } + ] + }, + { + "identifier": "performTransferExecuteEgld", + "inputs": [ + { + "name": "action_id", + "type": "u32", + "indexed": true + }, + { + "name": "to", + "type": "Address", + "indexed": true + }, + { + "name": "egld_value", + "type": "BigUint", + "indexed": true + }, + { + "name": "gas", + "type": "u64", + "indexed": true + }, + { + "name": "endpoint", + "type": "bytes", + "indexed": true + }, + { + "name": "arguments", + "type": "variadic", + "indexed": true + } + ] + }, + { + "identifier": "performTransferExecuteEsdt", + "inputs": [ + { + "name": "action_id", + "type": "u32", + "indexed": true + }, + { + "name": "to", + "type": "Address", + "indexed": true + }, + { + "name": "tokens", + "type": "List", + "indexed": true + }, + { + "name": "gas", + "type": "u64", + "indexed": true + }, + { + "name": "endpoint", + "type": "bytes", + "indexed": true + }, + { + "name": "arguments", + "type": "variadic", + "indexed": true + } + ] + }, + { + "identifier": "performDeployFromSource", + "inputs": [ + { + "name": "action_id", + "type": "u32", + "indexed": true + }, + { + "name": "egld_value", + "type": "BigUint", + "indexed": true + }, + { + "name": "source_address", + "type": "Address", + "indexed": true + }, + { + "name": "code_metadata", + "type": "CodeMetadata", + "indexed": true + }, + { + "name": "gas", + "type": "u64", + "indexed": true + }, + { + "name": "arguments", + "type": "variadic", + "indexed": true } ] }, + { + "identifier": "performUpgradeFromSource", + "inputs": [ + { + "name": "action_id", + "type": "u32", + "indexed": true + }, + { + "name": "target_address", + "type": "Address", + "indexed": true + }, + { + "name": "egld_value", + "type": "BigUint", + "indexed": true + }, + { + "name": "source_address", + "type": "Address", + "indexed": true + }, + { + "name": "code_metadata", + "type": "CodeMetadata", + "indexed": true + }, + { + "name": "gas", + "type": "u64", + "indexed": true + }, + { + "name": "arguments", + "type": "variadic", + "indexed": true + } + ] + } + ], + "esdtAttributes": [], + "hasCallback": true, + "types": { "Action": { "type": "enum", "variants": [ @@ -628,7 +1100,7 @@ ] }, { - "name": "SendTransferExecute", + "name": "SendTransferExecuteEgld", "discriminant": 5, "fields": [ { @@ -638,8 +1110,18 @@ ] }, { - "name": "SendAsyncCall", + "name": "SendTransferExecuteEsdt", "discriminant": 6, + "fields": [ + { + "name": "0", + "type": "EsdtTransferExecuteData" + } + ] + }, + { + "name": "SendAsyncCall", + "discriminant": 7, "fields": [ { "name": "0", @@ -649,7 +1131,7 @@ }, { "name": "SCDeployFromSource", - "discriminant": 7, + "discriminant": 8, "fields": [ { "name": "amount", @@ -671,7 +1153,7 @@ }, { "name": "SCUpgradeFromSource", - "discriminant": 8, + "discriminant": 9, "fields": [ { "name": "sc_address", @@ -707,6 +1189,10 @@ "name": "action_id", "type": "u32" }, + { + "name": "group_id", + "type": "u32" + }, { "name": "action_data", "type": "Action" @@ -717,32 +1203,83 @@ } ] }, - "PerformActionResult": { + "ActionStatus": { "type": "enum", "variants": [ { - "name": "Nothing", + "name": "Available", "discriminant": 0 }, { - "name": "DeployResult", - "discriminant": 1, - "fields": [ - { - "name": "0", - "type": "Address" - } - ] + "name": "Aborted", + "discriminant": 1 + } + ] + }, + "CallActionData": { + "type": "struct", + "fields": [ + { + "name": "to", + "type": "Address" }, { - "name": "SendAsyncCall", - "discriminant": 2, - "fields": [ - { - "name": "0", - "type": "AsyncCall" - } - ] + "name": "egld_amount", + "type": "BigUint" + }, + { + "name": "opt_gas_limit", + "type": "Option" + }, + { + "name": "endpoint_name", + "type": "bytes" + }, + { + "name": "arguments", + "type": "List" + } + ] + }, + "EsdtTokenPayment": { + "type": "struct", + "fields": [ + { + "name": "token_identifier", + "type": "TokenIdentifier" + }, + { + "name": "token_nonce", + "type": "u64" + }, + { + "name": "amount", + "type": "BigUint" + } + ] + }, + "EsdtTransferExecuteData": { + "type": "struct", + "fields": [ + { + "name": "to", + "type": "Address" + }, + { + "name": "tokens", + "type": "List" + }, + { + "name": "opt_gas_limit", + "type": "Option" + }, + { + "name": "endpoint_name", + "type": "bytes" + }, + { + "name": "arguments", + "type": "List" } ] }, diff --git a/src/testdata/multisig-full.wasm b/src/testdata/multisig-full.wasm new file mode 100644 index 000000000..acb183e62 Binary files /dev/null and b/src/testdata/multisig-full.wasm differ diff --git a/src/testutils/contractController.ts b/src/testutils/contractController.ts index 3fed66f79..517d8eef6 100644 --- a/src/testutils/contractController.ts +++ b/src/testutils/contractController.ts @@ -21,26 +21,26 @@ export class ContractController { } async deploy(transaction: Transaction): Promise<{ transactionOnNetwork: ITransactionOnNetwork, bundle: UntypedOutcomeBundle }> { - Logger.info(`ContractController.deploy [begin]: transaction = ${transaction.getHash()}`); - - await this.provider.sendTransaction(transaction); - let transactionOnNetwork = await this.transactionCompletionAwaiter.awaitCompleted(transaction); + const txHash = await this.provider.sendTransaction(transaction); + Logger.info(`ContractController.deploy [begin]: transaction = ${txHash}`); + + let transactionOnNetwork = await this.transactionCompletionAwaiter.awaitCompleted(txHash); let bundle = this.parser.parseUntypedOutcome(transactionOnNetwork); - Logger.info(`ContractController.deploy [end]: transaction = ${transaction.getHash()}, return code = ${bundle.returnCode}`); + Logger.info(`ContractController.deploy [end]: transaction = ${txHash}, return code = ${bundle.returnCode}`); return { transactionOnNetwork, bundle }; } async execute(interaction: Interaction, transaction: Transaction): Promise<{ transactionOnNetwork: ITransactionOnNetwork, bundle: TypedOutcomeBundle }> { - Logger.info(`ContractController.execute [begin]: function = ${interaction.getFunction()}, transaction = ${transaction.getHash()}`); + const txHash = await this.provider.sendTransaction(transaction); + Logger.info(`ContractController.execute [begin]: function = ${interaction.getFunction()}, transaction = ${txHash}`); interaction.check(); - await this.provider.sendTransaction(transaction); - let transactionOnNetwork = await this.transactionCompletionAwaiter.awaitCompleted(transaction); + let transactionOnNetwork = await this.transactionCompletionAwaiter.awaitCompleted(txHash); let bundle = this.parser.parseOutcome(transactionOnNetwork, interaction.getEndpoint()); - Logger.info(`ContractController.execute [end]: function = ${interaction.getFunction()}, transaction = ${transaction.getHash()}, return code = ${bundle.returnCode}`); + Logger.info(`ContractController.execute [end]: function = ${interaction.getFunction()}, transaction = ${txHash}, return code = ${bundle.returnCode}`); return { transactionOnNetwork, bundle }; } diff --git a/src/testutils/index.ts b/src/testutils/index.ts index 6a28a8ff0..7e9e84738 100644 --- a/src/testutils/index.ts +++ b/src/testutils/index.ts @@ -1,3 +1,3 @@ -export * from "./mockProvider"; +export * from "./mockNetworkProvider"; export * from "./utils"; export * from "./wallets"; diff --git a/src/testutils/mockProvider.ts b/src/testutils/mockNetworkProvider.ts similarity index 87% rename from src/testutils/mockProvider.ts rename to src/testutils/mockNetworkProvider.ts index 884ca6496..4a842e0cc 100644 --- a/src/testutils/mockProvider.ts +++ b/src/testutils/mockNetworkProvider.ts @@ -1,15 +1,26 @@ -import { ContractResultItem, ContractResults, TransactionOnNetwork, TransactionStatus } from "@multiversx/sdk-network-providers"; +import { + ContractResultItem, + ContractResults, + TransactionOnNetwork, + TransactionStatus, +} from "@multiversx/sdk-network-providers"; import { Address } from "../address"; import { AsyncTimer } from "../asyncTimer"; import * as errors from "../errors"; import { ErrMock } from "../errors"; import { IAddress } from "../interface"; -import { IAccountOnNetwork, IContractQueryResponse, INetworkConfig, ITransactionOnNetwork, ITransactionStatus } from "../interfaceOfNetwork"; +import { + IAccountOnNetwork, + IContractQueryResponse, + INetworkConfig, + ITransactionOnNetwork, + ITransactionStatus, +} from "../interfaceOfNetwork"; import { Query } from "../smartcontracts/query"; import { Transaction, TransactionHash } from "../transaction"; import { createAccountBalance } from "./utils"; -export class MockProvider { +export class MockNetworkProvider { static AddressOfAlice = new Address("erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"); static AddressOfBob = new Address("erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx"); static AddressOfCarol = new Address("erd1k2s324ww2g0yj38qn2ch2jwctdy8mnfxep94q9arncc6xecg3xaq6mjse8"); @@ -24,18 +35,9 @@ export class MockProvider { this.transactions = new Map(); this.accounts = new Map(); - this.accounts.set( - MockProvider.AddressOfAlice.bech32(), - { nonce: 0, balance: createAccountBalance(1000) } - ); - this.accounts.set( - MockProvider.AddressOfBob.bech32(), - { nonce: 5, balance: createAccountBalance(500) } - ); - this.accounts.set( - MockProvider.AddressOfCarol.bech32(), - { nonce: 42, balance: createAccountBalance(300) } - ); + this.accounts.set(MockNetworkProvider.AddressOfAlice.bech32(), { nonce: 0, balance: createAccountBalance(1000) }); + this.accounts.set(MockNetworkProvider.AddressOfBob.bech32(), { nonce: 5, balance: createAccountBalance(500) }); + this.accounts.set(MockNetworkProvider.AddressOfCarol.bech32(), { nonce: 42, balance: createAccountBalance(300) }); } mockUpdateAccount(address: Address, mutate: (item: IAccountOnNetwork) => void) { @@ -69,7 +71,7 @@ export class MockProvider { let response = new TransactionOnNetwork({ status: new TransactionStatus("executed"), contractResults: new ContractResults([contractResult]), - isCompleted: true + isCompleted: true, }); this.getTransactionResponders.unshift(new GetTransactionResponder(predicate, response)); @@ -109,7 +111,7 @@ export class MockProvider { return account; } - throw new ErrMock("Account not found") + throw new ErrMock("Account not found"); } async sendTransaction(transaction: Transaction): Promise { @@ -119,8 +121,8 @@ export class MockProvider { sender: transaction.getSender(), receiver: transaction.getReceiver(), data: transaction.getData().valueOf(), - status: new TransactionStatus("pending") - }) + status: new TransactionStatus("pending"), + }), ); this.mockTransactionTimeline(transaction, this.nextTransactionTimelinePoints); @@ -176,7 +178,7 @@ export class Wait { } } -export class MarkCompleted { } +export class MarkCompleted {} class QueryContractResponder { readonly matches: (query: Query) => boolean; diff --git a/src/testutils/networkProviders.ts b/src/testutils/networkProviders.ts index 475da761c..736a9bedb 100644 --- a/src/testutils/networkProviders.ts +++ b/src/testutils/networkProviders.ts @@ -1,6 +1,12 @@ import { ApiNetworkProvider, ProxyNetworkProvider } from "@multiversx/sdk-network-providers"; import { IAddress } from "../interface"; -import { IAccountOnNetwork, IContractQueryResponse, INetworkConfig, ITransactionOnNetwork, ITransactionStatus } from "../interfaceOfNetwork"; +import { + IAccountOnNetwork, + IContractQueryResponse, + INetworkConfig, + ITransactionOnNetwork, + ITransactionStatus, +} from "../interfaceOfNetwork"; import { Query } from "../smartcontracts/query"; import { Transaction } from "../transaction"; diff --git a/src/testutils/utils.ts b/src/testutils/utils.ts index 876393148..76ef1b9f8 100644 --- a/src/testutils/utils.ts +++ b/src/testutils/utils.ts @@ -11,12 +11,12 @@ import axios, { AxiosResponse } from "axios"; import BigNumber from "bignumber.js"; export async function prepareDeployment(obj: { - deployer: TestWallet, - contract: SmartContract, - codePath: string, - initArguments: TypedValue[], - gasLimit: IGasLimit, - chainID: IChainID + deployer: TestWallet; + contract: SmartContract; + codePath: string; + initArguments: TypedValue[]; + gasLimit: IGasLimit; + chainID: IChainID; }): Promise { let contract = obj.contract; let deployer = obj.deployer; @@ -26,15 +26,15 @@ export async function prepareDeployment(obj: { gasLimit: obj.gasLimit, initArguments: obj.initArguments, chainID: obj.chainID, - deployer: deployer.address + deployer: deployer.address, }); let nonce = deployer.account.getNonceThenIncrement(); let contractAddress = SmartContract.computeAddress(deployer.address, nonce); transaction.setNonce(nonce); - transaction.setSender(deployer.address) + transaction.setSender(deployer.address); contract.setAddress(contractAddress); - await deployer.signer.sign(transaction); + transaction.applySignature(await deployer.signer.sign(transaction.serializeForSigning())); return transaction; } @@ -42,11 +42,11 @@ export async function prepareDeployment(obj: { export async function loadContractCode(path: PathLike): Promise { if (isOnBrowserTests()) { let response: AxiosResponse = await axios.get(path.toString(), { - responseType: 'arraybuffer', + responseType: "arraybuffer", transformResponse: [], headers: { - "Accept": "application/wasm" - } + Accept: "application/wasm", + }, }); let buffer = Buffer.from(response.data); @@ -90,3 +90,7 @@ export function setupUnitTestWatcherTimeouts() { export function createAccountBalance(egld: number): BigNumber { return new BigNumber(egld.toString() + "0".repeat(18)); } + +export function b64TopicsToBytes(topics: string[]): Uint8Array[] { + return topics.map((topic) => Buffer.from(topic, "base64")); +} diff --git a/src/testutils/wallets.ts b/src/testutils/wallets.ts index 4ba8c4a43..e86ce05c0 100644 --- a/src/testutils/wallets.ts +++ b/src/testutils/wallets.ts @@ -1,5 +1,4 @@ import { UserSecretKey, UserSigner } from "@multiversx/sdk-wallet"; -import { UserSigner as UserSignerNext } from "@multiversx/sdk-wallet-next"; import axios from "axios"; import * as fs from "fs"; import * as path from "path"; @@ -24,8 +23,21 @@ export async function syncTestWallets(wallets: Record, provi } export async function loadTestWallets(): Promise> { - let walletNames = ["alice", "bob", "carol", "dan", "eve", "frank", "grace", "heidi", "ivan", "judy", "mallory", "mike"]; - let wallets = await Promise.all(walletNames.map(async name => await loadTestWallet(name))); + let walletNames = [ + "alice", + "bob", + "carol", + "dan", + "eve", + "frank", + "grace", + "heidi", + "ivan", + "judy", + "mallory", + "mike", + ]; + let wallets = await Promise.all(walletNames.map(async (name) => await loadTestWallet(name))); let walletMap: Record = {}; for (let i in walletNames) { walletMap[walletNames[i]] = wallets[i]; @@ -45,11 +57,7 @@ export async function loadTestWallet(name: string): Promise { let jsonContents = JSON.parse(await readTestWalletFileContents(name + ".json")); let pemContents = await readTestWalletFileContents(name + ".pem"); let pemKey = UserSecretKey.fromPem(pemContents); - return new TestWallet( - new Address(jsonContents.address), - pemKey.hex(), - jsonContents, - pemContents); + return new TestWallet(new Address(jsonContents.address), pemKey.hex(), jsonContents, pemContents); } async function readTestWalletFileContents(name: string): Promise { @@ -73,7 +81,6 @@ export class TestWallet { readonly secretKeyHex: string; readonly secretKey: Buffer; readonly signer: UserSigner; - readonly signerNext: UserSignerNext; readonly keyFileObject: any; readonly pemFileText: any; readonly account: Account; @@ -83,7 +90,6 @@ export class TestWallet { this.secretKeyHex = secretKeyHex; this.secretKey = Buffer.from(secretKeyHex, "hex"); this.signer = new UserSigner(UserSecretKey.fromString(secretKeyHex)); - this.signerNext = new UserSignerNext(UserSecretKey.fromString(secretKeyHex)); this.keyFileObject = keyFileObject; this.pemFileText = pemFileText; this.account = new Account(this.address); @@ -95,7 +101,7 @@ export class TestWallet { async sync(provider: IAccountFetcher) { let accountOnNetwork = await provider.getAccount(this.address); - await this.account.update(accountOnNetwork); + this.account.update(accountOnNetwork); return this; } } diff --git a/src/tokenOperations/codec.ts b/src/tokenOperations/codec.ts index 3b33abfd6..64b4c4844 100644 --- a/src/tokenOperations/codec.ts +++ b/src/tokenOperations/codec.ts @@ -1,6 +1,4 @@ import BigNumber from "bignumber.js"; -import { Address } from "../address"; -import { IAddress } from "../interface"; import * as contractsCodecUtils from "../smartcontracts/codec/utils"; import * as codecUtils from "../utils.codec"; @@ -24,25 +22,9 @@ export function bigIntToBuffer(value: BigNumber.Value): Buffer { return contractsCodecUtils.bigIntToBuffer(value); } -export function bigIntToHex(value: BigNumber.Value): string { - if (value == 0) { - return ""; - } - - return contractsCodecUtils.getHexMagnitudeOfBigInt(value); -} - -export function utf8ToHex(value: string) { - const hex = Buffer.from(value).toString("hex"); - return codecUtils.zeroPadStringIfOddLength(hex); -} +export { utf8ToHex, bigIntToHex, addressToHex } from "../utils.codec"; export function bufferToHex(value: Buffer) { const hex = value.toString("hex"); return codecUtils.zeroPadStringIfOddLength(hex); } - -export function addressToHex(address: IAddress): string { - const buffer = Address.fromBech32(address.toString()).pubkey(); - return buffer.toString("hex"); -} diff --git a/src/tokenOperations/tokenOperationsFactory.test.net.spec.ts b/src/tokenOperations/tokenOperationsFactory.testnet.spec.ts similarity index 98% rename from src/tokenOperations/tokenOperationsFactory.test.net.spec.ts rename to src/tokenOperations/tokenOperationsFactory.testnet.spec.ts index e9a4b9681..2ae574fa8 100644 --- a/src/tokenOperations/tokenOperationsFactory.test.net.spec.ts +++ b/src/tokenOperations/tokenOperationsFactory.testnet.spec.ts @@ -3,10 +3,10 @@ import { GasEstimator } from "../gasEstimator"; import { INetworkConfig, ITransactionOnNetwork } from "../interfaceOfNetwork"; import { TestWallet, loadTestWallets } from "../testutils"; import { INetworkProvider, createTestnetProvider } from "../testutils/networkProviders"; -import { TokenTransfer } from "../tokenTransfer"; +import { TokenTransfer } from "../tokens"; import { Transaction } from "../transaction"; import { TransactionWatcher } from "../transactionWatcher"; -import { TransferTransactionsFactory } from "../transferTransactionsFactory"; +import { TransferTransactionsFactory } from "../transactionsFactories/transferTransactionsFactory"; import { TokenOperationsFactory } from "./tokenOperationsFactory"; import { TokenOperationsFactoryConfig } from "./tokenOperationsFactoryConfig"; import { TokenOperationsOutcomeParser } from "./tokenOperationsOutcomeParser"; @@ -675,14 +675,14 @@ describe("test factory on testnet", function () { async function processTransaction( wallet: TestWallet, transaction: Transaction, - tag: string + tag: string, ): Promise { wallet.account.incrementNonce(); - await wallet.signer.sign(transaction); + transaction.applySignature(await wallet.signer.sign(transaction.serializeForSigning())); await provider.sendTransaction(transaction); console.log(`Sent transaction [${tag}]: ${transaction.getHash().hex()}`); - const transactionOnNetwork = await watcher.awaitCompleted(transaction); + const transactionOnNetwork = await watcher.awaitCompleted(transaction.getHash().hex()); return transactionOnNetwork; } }); diff --git a/src/tokenOperations/tokenOperationsFactory.ts b/src/tokenOperations/tokenOperationsFactory.ts index 4f8c8fc8a..a4a4ffc52 100644 --- a/src/tokenOperations/tokenOperationsFactory.ts +++ b/src/tokenOperations/tokenOperationsFactory.ts @@ -63,7 +63,7 @@ interface IIssueSemiFungibleArgs extends IBaseArgs { canAddSpecialRoles: boolean; } -interface IIssueNonFungibleArgs extends IIssueSemiFungibleArgs { } +interface IIssueNonFungibleArgs extends IIssueSemiFungibleArgs {} interface IRegisterMetaESDT extends IIssueSemiFungibleArgs { numDecimals: number; @@ -176,6 +176,9 @@ interface IBurnQuantityArgs extends IBaseArgs { quantityToBurn: BigNumber.Value; } +/** + * @deprecated Use {@link TokenManagementTransactionsFactory} instead. + */ export class TokenOperationsFactory { private readonly config: IConfig; private readonly trueAsHex; @@ -207,7 +210,7 @@ export class TokenOperationsFactory { utf8ToHex("canUpgrade"), args.canUpgrade ? this.trueAsHex : this.falseAsHex, utf8ToHex("canAddSpecialRoles"), - args.canAddSpecialRoles ? this.trueAsHex : this.falseAsHex + args.canAddSpecialRoles ? this.trueAsHex : this.falseAsHex, ]; return this.createTransaction({ @@ -251,7 +254,7 @@ Once the token is registered, you can unset this role by calling "unsetBurnRoleG utf8ToHex("canUpgrade"), args.canUpgrade ? this.trueAsHex : this.falseAsHex, utf8ToHex("canAddSpecialRoles"), - args.canAddSpecialRoles ? this.trueAsHex : this.falseAsHex + args.canAddSpecialRoles ? this.trueAsHex : this.falseAsHex, ]; return this.createTransaction({ @@ -286,7 +289,7 @@ Once the token is registered, you can unset this role by calling "unsetBurnRoleG utf8ToHex("canUpgrade"), args.canUpgrade ? this.trueAsHex : this.falseAsHex, utf8ToHex("canAddSpecialRoles"), - args.canAddSpecialRoles ? this.trueAsHex : this.falseAsHex + args.canAddSpecialRoles ? this.trueAsHex : this.falseAsHex, ]; return this.createTransaction({ @@ -322,7 +325,7 @@ Once the token is registered, you can unset this role by calling "unsetBurnRoleG utf8ToHex("canUpgrade"), args.canUpgrade ? this.trueAsHex : this.falseAsHex, utf8ToHex("canAddSpecialRoles"), - args.canAddSpecialRoles ? this.trueAsHex : this.falseAsHex + args.canAddSpecialRoles ? this.trueAsHex : this.falseAsHex, ]; return this.createTransaction({ diff --git a/src/tokenOperations/tokenOperationsFactoryConfig.ts b/src/tokenOperations/tokenOperationsFactoryConfig.ts index ef9731bf0..e1ebb54dd 100644 --- a/src/tokenOperations/tokenOperationsFactoryConfig.ts +++ b/src/tokenOperations/tokenOperationsFactoryConfig.ts @@ -2,6 +2,9 @@ import BigNumber from "bignumber.js"; import { Address } from "../address"; import { IAddress, IChainID, IGasLimit, IGasPrice } from "../interface"; +/** + * @deprecated Use {@link TransactionsFactoryConfig} instead. + */ export class TokenOperationsFactoryConfig { chainID: IChainID; minGasPrice: IGasPrice = 1000000000; @@ -21,7 +24,9 @@ export class TokenOperationsFactoryConfig { gasLimitESDTNFTBurn: IGasLimit = 1000000; gasLimitStorePerByte: IGasLimit = 50000; issueCost: BigNumber.Value = "50000000000000000"; - esdtContractAddress: IAddress = Address.fromBech32("erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqzllls8a5w6u"); + esdtContractAddress: IAddress = Address.fromBech32( + "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqzllls8a5w6u" + ); constructor(chainID: IChainID) { this.chainID = chainID; diff --git a/src/tokenOperations/tokenOperationsOutcomeParser.ts b/src/tokenOperations/tokenOperationsOutcomeParser.ts index 260ffb3d3..8fc489f3c 100644 --- a/src/tokenOperations/tokenOperationsOutcomeParser.ts +++ b/src/tokenOperations/tokenOperationsOutcomeParser.ts @@ -3,7 +3,6 @@ import { ErrCannotParseTransactionOutcome } from "../errors"; import { IAddress } from "../interface"; import { bufferToBigInt } from "./codec"; - interface ITransactionOnNetwork { hash: string; contractResults: IContractResults; @@ -42,8 +41,7 @@ export interface IRegisterAndSetAllRolesOutcome { roles: string[]; } -export interface IToggleBurnRoleGloballyOutcome { -} +export interface IToggleBurnRoleGloballyOutcome {} export interface ISetSpecialRoleOutcome { userAddress: string; @@ -71,8 +69,7 @@ export interface IBurnOutcome { burntSupply: string; } -export interface IPausingOutcome { -} +export interface IPausingOutcome {} export interface IFreezingOutcome { userAddress: string; @@ -106,6 +103,9 @@ export interface IBurnQuantityOutcome { burntQuantity: string; } +/** + * @deprecated Use {@link TokenManagementTransactionsOutcomeParser} + */ export class TokenOperationsOutcomeParser { parseIssueFungible(transaction: ITransactionOnNetwork): IESDTIssueOutcome { this.ensureNoError(transaction); @@ -146,7 +146,7 @@ export class TokenOperationsOutcomeParser { const tokenIdentifier = this.extractTokenIdentifier(eventRegister); const eventSetRole = this.findSingleEventByIdentifier(transaction, "ESDTSetRole"); - const roles = eventSetRole.topics.slice(3).map(topic => topic.valueOf().toString()); + const roles = eventSetRole.topics.slice(3).map((topic) => topic.valueOf().toString()); return { tokenIdentifier, roles }; } @@ -167,7 +167,7 @@ export class TokenOperationsOutcomeParser { const event = this.findSingleEventByIdentifier(transaction, "ESDTSetRole"); const userAddress = event.address.toString(); const tokenIdentifier = this.extractTokenIdentifier(event); - const roles = event.topics.slice(3).map(topic => topic.valueOf().toString()); + const roles = event.topics.slice(3).map((topic) => topic.valueOf().toString()); return { userAddress, tokenIdentifier, roles }; } @@ -284,13 +284,16 @@ export class TokenOperationsOutcomeParser { const data = Buffer.from(event.data.substring(1), "hex").toString(); const message = event.topics[1]?.valueOf().toString(); - throw new ErrCannotParseTransactionOutcome(transaction.hash, `encountered signalError: ${message} (${data})`); + throw new ErrCannotParseTransactionOutcome( + transaction.hash, + `encountered signalError: ${message} (${data})`, + ); } } } private findSingleEventByIdentifier(transaction: ITransactionOnNetwork, identifier: string): ITransactionEvent { - const events = this.gatherAllEvents(transaction).filter(event => event.identifier == identifier); + const events = this.gatherAllEvents(transaction).filter((event) => event.identifier == identifier); if (events.length == 0) { throw new ErrCannotParseTransactionOutcome(transaction.hash, `cannot find event of type ${identifier}`); @@ -330,4 +333,3 @@ export class TokenOperationsOutcomeParser { return Address.fromBuffer(event.topics[3]?.valueOf()).toString(); } } - diff --git a/src/tokenTransfer.ts b/src/tokenTransfer.ts deleted file mode 100644 index c0dff5aab..000000000 --- a/src/tokenTransfer.ts +++ /dev/null @@ -1,132 +0,0 @@ -import BigNumber from "bignumber.js"; -import { ErrInvalidArgument } from "./errors"; - -const EGLDTokenIdentifier = "EGLD"; -const EGLDNumDecimals = 18; - -// Note: this will actually set the default rounding mode for all BigNumber objects in the environment (in the application / dApp). -BigNumber.set({ ROUNDING_MODE: 1 }); - -interface ITokenTransferOptions { - tokenIdentifier: string; - nonce: number; - amountAsBigInteger: BigNumber.Value; - numDecimals?: number; -} - -export class TokenTransfer { - readonly tokenIdentifier: string; - readonly nonce: number; - readonly amountAsBigInteger: BigNumber; - readonly numDecimals: number; - - public constructor(options: ITokenTransferOptions) { - const amount = new BigNumber(options.amountAsBigInteger); - if (!amount.isInteger() || amount.isNegative()) { - throw new ErrInvalidArgument(`bad amountAsBigInteger: ${options.amountAsBigInteger}`); - } - - this.tokenIdentifier = options.tokenIdentifier; - this.nonce = options.nonce; - this.amountAsBigInteger = amount; - this.numDecimals = options.numDecimals || 0; - } - - static egldFromAmount(amount: BigNumber.Value) { - const amountAsBigInteger = new BigNumber(amount).shiftedBy(EGLDNumDecimals).decimalPlaces(0); - return this.egldFromBigInteger(amountAsBigInteger); - } - - static egldFromBigInteger(amountAsBigInteger: BigNumber.Value) { - return new TokenTransfer({ - tokenIdentifier: EGLDTokenIdentifier, - nonce: 0, - amountAsBigInteger, - numDecimals: EGLDNumDecimals, - }); - } - - static fungibleFromAmount(tokenIdentifier: string, amount: BigNumber.Value, numDecimals: number) { - const amountAsBigInteger = new BigNumber(amount).shiftedBy(numDecimals).decimalPlaces(0); - return this.fungibleFromBigInteger(tokenIdentifier, amountAsBigInteger, numDecimals); - } - - static fungibleFromBigInteger(tokenIdentifier: string, amountAsBigInteger: BigNumber.Value, numDecimals: number = 0) { - return new TokenTransfer({ - tokenIdentifier, - nonce: 0, - amountAsBigInteger, - numDecimals, - }); - } - - static nonFungible(tokenIdentifier: string, nonce: number) { - return new TokenTransfer({ - tokenIdentifier, - nonce, - amountAsBigInteger: 1, - numDecimals: 0, - }); - } - - static semiFungible(tokenIdentifier: string, nonce: number, quantity: number) { - return new TokenTransfer({ - tokenIdentifier, - nonce, - amountAsBigInteger: quantity, - numDecimals: 0, - }); - } - - static metaEsdtFromAmount(tokenIdentifier: string, nonce: number, amount: BigNumber.Value, numDecimals: number) { - const amountAsBigInteger = new BigNumber(amount).shiftedBy(numDecimals).decimalPlaces(0); - return this.metaEsdtFromBigInteger(tokenIdentifier, nonce, amountAsBigInteger, numDecimals); - } - - static metaEsdtFromBigInteger(tokenIdentifier: string, nonce: number, amountAsBigInteger: BigNumber.Value, numDecimals = 0) { - return new TokenTransfer({ - tokenIdentifier, - nonce, - amountAsBigInteger, - numDecimals, - }); - } - - toString() { - return this.amountAsBigInteger.toFixed(0); - } - - valueOf(): BigNumber { - return this.amountAsBigInteger; - } - - toPrettyString(): string { - return `${this.toAmount()} ${this.tokenIdentifier}`; - } - - private toAmount(): string { - return this.amountAsBigInteger.shiftedBy(-this.numDecimals).toFixed(this.numDecimals); - } - - isEgld(): boolean { - return this.tokenIdentifier == EGLDTokenIdentifier; - } - - isFungible(): boolean { - return this.nonce == 0; - } -} - -/** - * @deprecated use {@link TokenTransfer} instead. - */ -export class TokenPayment extends TokenTransfer { - constructor(tokenIdentifier: string, nonce: number, amountAsBigInteger: BigNumber.Value, numDecimals: number) { - super({ - tokenIdentifier, - nonce, - amountAsBigInteger, - numDecimals, - }); - } -}; diff --git a/src/tokenTransferBuilders.spec.ts b/src/tokenTransferBuilders.spec.ts index f9ff0152c..e3fd02add 100644 --- a/src/tokenTransferBuilders.spec.ts +++ b/src/tokenTransferBuilders.spec.ts @@ -1,7 +1,11 @@ import { assert } from "chai"; import { Address } from "./address"; -import { TokenTransfer } from "./tokenTransfer"; -import { ESDTNFTTransferPayloadBuilder, ESDTTransferPayloadBuilder, MultiESDTNFTTransferPayloadBuilder } from "./tokenTransferBuilders"; +import { TokenTransfer } from "./tokens"; +import { + ESDTNFTTransferPayloadBuilder, + ESDTTransferPayloadBuilder, + MultiESDTNFTTransferPayloadBuilder, +} from "./tokenTransferBuilders"; describe("test token transfer builders", () => { it("should work with ESDT transfers", () => { @@ -17,7 +21,10 @@ describe("test token transfer builders", () => { .setDestination(new Address("erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx")) .build(); - assert.equal(payload.toString(), "ESDTNFTTransfer@4552444a532d333866323439@01@01@8049d639e5a6980d1cd2392abcce41029cda74a1563523a202f09641cc2618f8"); + assert.equal( + payload.toString(), + "ESDTNFTTransfer@4552444a532d333866323439@01@01@8049d639e5a6980d1cd2392abcce41029cda74a1563523a202f09641cc2618f8", + ); }); it("should work with ESDTNFT transfers (SFT)", () => { @@ -28,7 +35,10 @@ describe("test token transfer builders", () => { .setDestination(new Address("erd1testnlersh4z0wsv8kjx39me4rmnvjkwu8dsaea7ukdvvc9z396qykv7z7")) .build(); - assert.equal(payload.toString(), "ESDTNFTTransfer@53454d492d396566643066@01@05@5e60b9ff2385ea27ba0c3da4689779a8f7364acee1db0ee7bee59ac660a28974"); + assert.equal( + payload.toString(), + "ESDTNFTTransfer@53454d492d396566643066@01@05@5e60b9ff2385ea27ba0c3da4689779a8f7364acee1db0ee7bee59ac660a28974", + ); }); it("should work with Multi ESDTNFT transfers", () => { @@ -39,6 +49,9 @@ describe("test token transfer builders", () => { .setDestination(new Address("erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th")) .build(); - assert.equal(payload.toString(), "MultiESDTNFTTransfer@0139472eff6886771a982f3083da5d421f24c29181e63888228dc81ca60d69e1@02@4552444a532d333866323439@01@01@4241522d633830643239@@8ac7230489e80000"); + assert.equal( + payload.toString(), + "MultiESDTNFTTransfer@0139472eff6886771a982f3083da5d421f24c29181e63888228dc81ca60d69e1@02@4552444a532d333866323439@01@01@4241522d633830643239@@8ac7230489e80000", + ); }); }); diff --git a/src/tokenTransferBuilders.ts b/src/tokenTransferBuilders.ts index edc476416..1c6f70097 100644 --- a/src/tokenTransferBuilders.ts +++ b/src/tokenTransferBuilders.ts @@ -2,7 +2,7 @@ import { Address } from "./address"; import { IAddress, ITokenTransfer } from "./interface"; import { ArgSerializer } from "./smartcontracts/argSerializer"; import { AddressValue, BigUIntValue, BytesValue, TypedValue, U16Value, U64Value } from "./smartcontracts/typesystem"; -import { TokenTransfer } from "./tokenTransfer"; +import { TokenTransfer } from "./tokens"; import { TransactionPayload } from "./transactionPayload"; /** diff --git a/src/tokenTransfer.spec.ts b/src/tokens.spec.ts similarity index 60% rename from src/tokenTransfer.spec.ts rename to src/tokens.spec.ts index 5a891869c..0228a2f7c 100644 --- a/src/tokenTransfer.spec.ts +++ b/src/tokens.spec.ts @@ -1,7 +1,39 @@ import { assert } from "chai"; -import { TokenTransfer } from "./tokenTransfer"; +import { Token, TokenComputer, TokenTransfer } from "./tokens"; -describe("test token transfer", () => { +describe("test tokens and token computer", async () => { + const tokenComputer = new TokenComputer(); + + it("should test if token is fungible", async () => { + const fungibleToken = new Token({ identifier: "TEST-123456" }); + const nonFungibleToken = new Token({ identifier: "NFT-987654", nonce: 7n }); + + assert.equal(tokenComputer.isFungible(fungibleToken), true); + assert.equal(tokenComputer.isFungible(nonFungibleToken), false); + }); + + it("should extract nonce from extended identifier", async () => { + const extendedIdentifier = "TEST-123456-0a"; + let nonce = tokenComputer.extractNonceFromExtendedIdentifier(extendedIdentifier); + assert.equal(nonce, 10); + + const fungibleTokenIdentifier = "FNG-123456"; + nonce = tokenComputer.extractNonceFromExtendedIdentifier(fungibleTokenIdentifier); + assert.equal(nonce, 0); + }); + + it("should extract identifier from extended identifier", async () => { + const extendedIdentifier = "TEST-123456-0a"; + let identifier = tokenComputer.extractIdentifierFromExtendedIdentifier(extendedIdentifier); + assert.equal(identifier, "TEST-123456"); + + const fungibleTokenIdentifier = "FNG-123456"; + identifier = tokenComputer.extractIdentifierFromExtendedIdentifier(fungibleTokenIdentifier); + assert.equal(identifier, "FNG-123456"); + }); +}); + +describe("test token transfer (legacy)", () => { it("should work with EGLD", () => { assert.equal(TokenTransfer.egldFromAmount("1").toString(), "1000000000000000000"); assert.equal(TokenTransfer.egldFromAmount("10").toString(), "10000000000000000000"); @@ -19,14 +51,17 @@ describe("test token transfer", () => { assert.equal(TokenTransfer.egldFromAmount(100).toPrettyString(), "100.000000000000000000 EGLD"); assert.equal(TokenTransfer.egldFromAmount(1000).toPrettyString(), "1000.000000000000000000 EGLD"); assert.equal(TokenTransfer.egldFromAmount("0.123456789").toPrettyString(), "0.123456789000000000 EGLD"); - assert.equal(TokenTransfer.egldFromAmount("0.123456789123456789777777888888").toPrettyString(), "0.123456789123456789 EGLD"); + assert.equal( + TokenTransfer.egldFromAmount("0.123456789123456789777777888888").toPrettyString(), + "0.123456789123456789 EGLD", + ); assert.equal(TokenTransfer.egldFromBigInteger("1").toString(), "1"); assert.equal(TokenTransfer.egldFromBigInteger("1").toPrettyString(), "0.000000000000000001 EGLD"); assert.isTrue(TokenTransfer.egldFromAmount("1").isEgld()); }); - it("should work with USDC", () => { + it("should work with USDC (legacy)", () => { const identifier = "USDC-c76f1f"; const numDecimals = 6; @@ -34,10 +69,13 @@ describe("test token transfer", () => { assert.equal(TokenTransfer.fungibleFromAmount(identifier, "0.1", numDecimals).toString(), "100000"); assert.equal(TokenTransfer.fungibleFromAmount(identifier, "0.123456789", numDecimals).toString(), "123456"); assert.equal(TokenTransfer.fungibleFromBigInteger(identifier, "1000000", numDecimals).toString(), "1000000"); - assert.equal(TokenTransfer.fungibleFromBigInteger(identifier, "1000000", numDecimals).toPrettyString(), "1.000000 USDC-c76f1f"); + assert.equal( + TokenTransfer.fungibleFromBigInteger(identifier, "1000000", numDecimals).toPrettyString(), + "1.000000 USDC-c76f1f", + ); }); - it("should work with MetaESDT", () => { + it("should work with MetaESDT (legacy)", () => { const identifier = "MEXFARML-28d646"; const numDecimals = 18; const nonce = 12345678; @@ -48,7 +86,7 @@ describe("test token transfer", () => { assert.equal(transfer.toString(), "100000000000000000"); }); - it("should work with NFTs", () => { + it("should work with NFTs (legacy)", () => { const identifier = "TEST-38f249"; const nonce = 1; const transfer = TokenTransfer.nonFungible(identifier, nonce); diff --git a/src/tokens.ts b/src/tokens.ts new file mode 100644 index 000000000..f6b29bc91 --- /dev/null +++ b/src/tokens.ts @@ -0,0 +1,314 @@ +import BigNumber from "bignumber.js"; +import { ErrInvalidArgument, ErrInvalidTokenIdentifier } from "./errors"; + +// Legacy constants: +const EGLDTokenIdentifier = "EGLD"; +const EGLDNumDecimals = 18; + +// Legacy configuration. +// Note: this will actually set the default rounding mode for all BigNumber objects in the environment (in the application / dApp). +BigNumber.set({ ROUNDING_MODE: 1 }); + +interface ILegacyTokenTransferOptions { + tokenIdentifier: string; + nonce: number; + amountAsBigInteger: BigNumber.Value; + numDecimals?: number; +} + +export class Token { + readonly identifier: string; + readonly nonce: bigint; + + constructor(options: { identifier: string; nonce?: bigint }) { + this.identifier = options.identifier; + this.nonce = options.nonce || 0n; + } +} + +export class TokenTransfer { + readonly token: Token; + readonly amount: bigint; + + /** + * Legacy field. Use "token.identifier" instead. + */ + readonly tokenIdentifier: string; + + /** + * Legacy field. Use "token.nonce" instead. + */ + readonly nonce: number; + + /** + * Legacy field. Use "amount" instead. + */ + readonly amountAsBigInteger: BigNumber; + + /** + * Legacy field. The number of decimals is not a concern of "sdk-core". + * For formatting and parsing amounts, use "sdk-dapp" or "bignumber.js" directly. + */ + readonly numDecimals: number; + + constructor(options: { token: Token; amount: bigint } | ILegacyTokenTransferOptions) { + if (this.isLegacyTokenTransferOptions(options)) { + // Handle legacy fields. + const amount = new BigNumber(options.amountAsBigInteger); + if (!amount.isInteger() || amount.isNegative()) { + throw new ErrInvalidArgument(`bad amountAsBigInteger: ${options.amountAsBigInteger}`); + } + + this.tokenIdentifier = options.tokenIdentifier; + this.nonce = options.nonce; + this.amountAsBigInteger = amount; + this.numDecimals = options.numDecimals || 0; + + // Handle new fields. + this.token = new Token({ + identifier: options.tokenIdentifier, + nonce: BigInt(options.nonce), + }); + + this.amount = BigInt(this.amountAsBigInteger.toFixed(0)); + } else { + // Handle new fields. + this.token = options.token; + this.amount = options.amount; + + // Handle legacy fields. + this.tokenIdentifier = options.token.identifier; + this.nonce = Number(options.token.nonce); + this.amountAsBigInteger = new BigNumber(this.amount.toString()); + this.numDecimals = 0; + } + } + + private isLegacyTokenTransferOptions(options: any): options is ILegacyTokenTransferOptions { + return options.tokenIdentifier !== undefined; + } + + /** + * Legacy function. Use the constructor instead: new TokenTransfer({ token, amount }); + */ + static egldFromAmount(amount: BigNumber.Value) { + const amountAsBigInteger = new BigNumber(amount).shiftedBy(EGLDNumDecimals).decimalPlaces(0); + return this.egldFromBigInteger(amountAsBigInteger); + } + + /** + * Legacy function. Use the constructor instead: new TokenTransfer({ token, amount }); + */ + static egldFromBigInteger(amountAsBigInteger: BigNumber.Value) { + return new TokenTransfer({ + tokenIdentifier: EGLDTokenIdentifier, + nonce: 0, + amountAsBigInteger, + numDecimals: EGLDNumDecimals, + }); + } + + /** + * Legacy function. Use the constructor instead: new TokenTransfer({ token, amount }); + */ + static fungibleFromAmount(tokenIdentifier: string, amount: BigNumber.Value, numDecimals: number) { + const amountAsBigInteger = new BigNumber(amount).shiftedBy(numDecimals).decimalPlaces(0); + return this.fungibleFromBigInteger(tokenIdentifier, amountAsBigInteger, numDecimals); + } + + /** + * Legacy function. Use the constructor instead: new TokenTransfer({ token, amount }); + */ + static fungibleFromBigInteger( + tokenIdentifier: string, + amountAsBigInteger: BigNumber.Value, + numDecimals: number = 0, + ) { + return new TokenTransfer({ + tokenIdentifier, + nonce: 0, + amountAsBigInteger, + numDecimals, + }); + } + + /** + * Legacy function. Use the constructor instead: new TokenTransfer({ token, amount }); + */ + static nonFungible(tokenIdentifier: string, nonce: number) { + return new TokenTransfer({ + tokenIdentifier, + nonce, + amountAsBigInteger: 1, + numDecimals: 0, + }); + } + + /** + * Legacy function. Use the constructor instead: new TokenTransfer({ token, amount }); + */ + static semiFungible(tokenIdentifier: string, nonce: number, quantity: number) { + return new TokenTransfer({ + tokenIdentifier, + nonce, + amountAsBigInteger: quantity, + numDecimals: 0, + }); + } + + /** + * Legacy function. Use the constructor instead: new TokenTransfer({ token, amount }); + */ + static metaEsdtFromAmount(tokenIdentifier: string, nonce: number, amount: BigNumber.Value, numDecimals: number) { + const amountAsBigInteger = new BigNumber(amount).shiftedBy(numDecimals).decimalPlaces(0); + return this.metaEsdtFromBigInteger(tokenIdentifier, nonce, amountAsBigInteger, numDecimals); + } + + /** + * Legacy function. Use the constructor instead: new TokenTransfer({ token, amount }); + */ + static metaEsdtFromBigInteger( + tokenIdentifier: string, + nonce: number, + amountAsBigInteger: BigNumber.Value, + numDecimals = 0, + ) { + return new TokenTransfer({ + tokenIdentifier, + nonce, + amountAsBigInteger, + numDecimals, + }); + } + + toString() { + return this.amount.toString(); + } + + /** + * Legacy function. Use the "amount" field instead. + */ + valueOf(): BigNumber { + return new BigNumber(this.amount.toString()); + } + + /** + * Legacy function. For formatting and parsing amounts, use "sdk-dapp" or "bignumber.js" directly. + */ + toPrettyString(): string { + return `${this.toAmount()} ${this.tokenIdentifier}`; + } + + private toAmount(): string { + return this.amountAsBigInteger.shiftedBy(-this.numDecimals).toFixed(this.numDecimals); + } + + /** + * Legacy function. Within your code, don't mix native values (EGLD) and custom (ESDT) tokens. + * See "TransferTransactionsFactory.createTransactionForNativeTokenTransfer()" vs. "TransferTransactionsFactory.createTransactionForESDTTokenTransfer()". + */ + isEgld(): boolean { + return this.token.identifier == EGLDTokenIdentifier; + } + + /** + * Legacy function. Use "TokenComputer.isFungible(token)" instead. + */ + isFungible(): boolean { + return this.token.nonce == 0n; + } +} + +export class TokenComputer { + constructor() {} + + isFungible(token: Token): boolean { + return token.nonce === 0n; + } + + extractNonceFromExtendedIdentifier(identifier: string): number { + const parts = identifier.split("-"); + + this.checkIfExtendedIdentifierWasProvided(parts); + this.checkLengthOfRandomSequence(parts[1]); + + // in case the identifier of a fungible token is provided + if (parts.length == 2) { + return 0; + } + + const hexNonce = Buffer.from(parts[2], "hex"); + return decodeUnsignedNumber(hexNonce); + } + + extractIdentifierFromExtendedIdentifier(identifier: string): string { + const parts = identifier.split("-"); + + this.checkIfExtendedIdentifierWasProvided(parts); + this.ensureTokenTickerValidity(parts[0]); + this.checkLengthOfRandomSequence(parts[1]); + + return parts[0] + "-" + parts[1]; + } + + private checkIfExtendedIdentifierWasProvided(tokenParts: string[]): void { + // this is for the identifiers of fungible tokens + const MIN_EXTENDED_IDENTIFIER_LENGTH_IF_SPLITTED = 2; + // this is for the identifiers of nft, sft and meta-esdt + const MAX_EXTENDED_IDENTIFIER_LENGTH_IF_SPLITTED = 3; + + if ( + tokenParts.length < MIN_EXTENDED_IDENTIFIER_LENGTH_IF_SPLITTED || + tokenParts.length > MAX_EXTENDED_IDENTIFIER_LENGTH_IF_SPLITTED + ) { + throw new ErrInvalidTokenIdentifier("Invalid extended token identifier provided"); + } + } + + private checkLengthOfRandomSequence(randomSequence: string): void { + const TOKEN_RANDOM_SEQUENCE_LENGTH = 6; + + if (randomSequence.length !== TOKEN_RANDOM_SEQUENCE_LENGTH) { + throw new ErrInvalidTokenIdentifier( + "The identifier is not valid. The random sequence does not have the right length", + ); + } + } + + private ensureTokenTickerValidity(ticker: string) { + const MIN_TICKER_LENGTH = 3; + const MAX_TICKER_LENGTH = 10; + + if (ticker.length < MIN_TICKER_LENGTH || ticker.length > MAX_TICKER_LENGTH) { + throw new ErrInvalidTokenIdentifier( + `The token ticker should be between ${MIN_TICKER_LENGTH} and ${MAX_TICKER_LENGTH} characters`, + ); + } + + if (!ticker.match(/^[a-zA-Z0-9]+$/)) { + throw new ErrInvalidTokenIdentifier("The token ticker should only contain alphanumeric characters"); + } + + if (!(ticker == ticker.toUpperCase())) { + throw new ErrInvalidTokenIdentifier("The token ticker should be upper case"); + } + } +} + +function decodeUnsignedNumber(arg: Buffer): number { + return arg.readUIntBE(0, arg.length); +} + +/** + * @deprecated use {@link TokenTransfer} instead. + */ +export class TokenPayment extends TokenTransfer { + constructor(tokenIdentifier: string, nonce: number, amountAsBigInteger: BigNumber.Value, numDecimals: number) { + super({ + tokenIdentifier, + nonce, + amountAsBigInteger, + numDecimals, + }); + } +} diff --git a/src/transaction.local.net.spec.ts b/src/transaction.local.net.spec.ts index 30f674ec3..844091230 100644 --- a/src/transaction.local.net.spec.ts +++ b/src/transaction.local.net.spec.ts @@ -2,11 +2,14 @@ import BigNumber from "bignumber.js"; import { assert } from "chai"; import { Logger } from "./logger"; import { loadTestWallets, TestWallet } from "./testutils"; -import { createLocalnetProvider } from "./testutils/networkProviders"; -import { TokenTransfer } from "./tokenTransfer"; +import { createLocalnetProvider, INetworkProvider } from "./testutils/networkProviders"; +import { TokenTransfer } from "./tokens"; import { Transaction } from "./transaction"; import { TransactionPayload } from "./transactionPayload"; import { TransactionWatcher } from "./transactionWatcher"; +import { TransactionsFactoryConfig } from "./transactionsFactories/transactionsFactoryConfig"; +import { TransferTransactionsFactory } from "./transactionsFactories/transferTransactionsFactory"; +import { TransactionComputer } from "./transactionComputer"; describe("test transaction", function () { let alice: TestWallet, bob: TestWallet; @@ -15,26 +18,30 @@ describe("test transaction", function () { ({ alice, bob } = await loadTestWallets()); }); + function createTransactionWatcher(provider: INetworkProvider) { + return new TransactionWatcher( + { + getTransaction: async (hash: string) => { + return await provider.getTransaction(hash, true); + }, + }, + { timeoutMilliseconds: 100000 }, + ); + } + it("should send transactions and wait for completion", async function () { this.timeout(70000); let provider = createLocalnetProvider(); - let watcher = new TransactionWatcher({ - getTransaction: async (hash: string) => { return await provider.getTransaction(hash, true) } - }); + let watcher = createTransactionWatcher(provider); let network = await provider.getNetworkConfig(); - await alice.sync(provider); - - await bob.sync(provider); - let initialBalanceOfBob = new BigNumber(bob.account.balance.toString()); - let transactionOne = new Transaction({ sender: alice.address, receiver: bob.address, value: TokenTransfer.egldFromAmount(42), gasLimit: network.MinGasLimit, - chainID: network.ChainID + chainID: network.ChainID, }); let transactionTwo = new Transaction({ @@ -42,9 +49,13 @@ describe("test transaction", function () { receiver: bob.address, value: TokenTransfer.egldFromAmount(43), gasLimit: network.MinGasLimit, - chainID: network.ChainID + chainID: network.ChainID, }); + await alice.sync(provider); + await bob.sync(provider); + let initialBalanceOfBob = new BigNumber(bob.account.balance.toString()); + transactionOne.setNonce(alice.account.nonce); alice.account.incrementNonce(); transactionTwo.setNonce(alice.account.nonce); @@ -55,8 +66,8 @@ describe("test transaction", function () { await provider.sendTransaction(transactionOne); await provider.sendTransaction(transactionTwo); - await watcher.awaitCompleted(transactionOne); - await watcher.awaitCompleted(transactionTwo); + await watcher.awaitCompleted(transactionOne.getHash().hex()); + await watcher.awaitCompleted(transactionTwo.getHash().hex()); await bob.sync(provider); let newBalanceOfBob = new BigNumber(bob.account.balance.toString()); @@ -68,28 +79,26 @@ describe("test transaction", function () { this.timeout(70000); let provider = createLocalnetProvider(); - let watcher = new TransactionWatcher({ - getTransaction: async (hash: string) => { return await provider.getTransaction(hash, true) } - }); + let watcher = createTransactionWatcher(provider); let network = await provider.getNetworkConfig(); - await alice.sync(provider); - await bob.sync(provider); - let initialBalanceOfBob = new BigNumber(bob.account.balance.toString()); - let transactionOne = new Transaction({ sender: alice.address, receiver: bob.address, value: TokenTransfer.egldFromAmount(42), gasLimit: network.MinGasLimit, - chainID: network.ChainID + chainID: network.ChainID, }); + await alice.sync(provider); + await bob.sync(provider); + let initialBalanceOfBob = new BigNumber(bob.account.balance.toString()); + transactionOne.setNonce(alice.account.nonce); await signTransaction({ transaction: transactionOne, wallet: alice }); await provider.sendTransaction(transactionOne); - await watcher.awaitCompleted(transactionOne); + await watcher.awaitCompleted(transactionOne.getHash().hex()); await bob.sync(provider); let newBalanceOfBob = new BigNumber(bob.account.balance.toString()); @@ -110,7 +119,7 @@ describe("test transaction", function () { gasLimit: 70000, receiver: alice.address, value: TokenTransfer.egldFromAmount(1000), - chainID: network.ChainID + chainID: network.ChainID, }); let transactionTwo = new Transaction({ @@ -119,7 +128,7 @@ describe("test transaction", function () { gasLimit: 70000, receiver: alice.address, value: TokenTransfer.egldFromAmount(1000000), - chainID: network.ChainID + chainID: network.ChainID, }); transactionOne.setNonce(alice.account.nonce); @@ -132,12 +141,46 @@ describe("test transaction", function () { Logger.trace(JSON.stringify(await provider.simulateTransaction(transactionTwo), null, 4)); }); - async function signTransaction(options: { transaction: Transaction, wallet: TestWallet }) { + it("should create transaction using the TokenTransferFactory", async function () { + this.timeout(70000); + + const provider = createLocalnetProvider(); + const watcher = createTransactionWatcher(provider); + + const network = await provider.getNetworkConfig(); + + const config = new TransactionsFactoryConfig({ chainID: network.ChainID }); + const factory = new TransferTransactionsFactory({ config: config }); + + await alice.sync(provider); + await bob.sync(provider); + const initialBalanceOfBob = new BigNumber(bob.account.balance.toString()); + + const transaction = factory.createTransactionForNativeTokenTransfer({ + sender: alice.address, + receiver: bob.address, + nativeAmount: 42000000000000000000n, + }); + transaction.nonce = BigInt(alice.account.nonce.valueOf()); + + const transactionComputer = new TransactionComputer(); + transaction.signature = await alice.signer.sign(transactionComputer.computeBytesForSigning(transaction)); + + const txHash = await provider.sendTransaction(transaction); + await watcher.awaitCompleted(txHash); + + await bob.sync(provider); + const newBalanceOfBob = new BigNumber(bob.account.balance.toString()); + + assert.deepEqual(TokenTransfer.egldFromAmount(42).valueOf(), newBalanceOfBob.minus(initialBalanceOfBob)); + }); + + async function signTransaction(options: { transaction: Transaction; wallet: TestWallet }) { const transaction = options.transaction; const wallet = options.wallet; const serialized = transaction.serializeForSigning(); - const signature = await wallet.signerNext.sign(serialized); + const signature = await wallet.signer.sign(serialized); transaction.applySignature(signature); } }); diff --git a/src/transaction.spec.ts b/src/transaction.spec.ts index 52dd1f75b..c514a3abd 100644 --- a/src/transaction.spec.ts +++ b/src/transaction.spec.ts @@ -1,40 +1,100 @@ import BigNumber from "bignumber.js"; import { assert } from "chai"; import { Address } from "./address"; +import { MIN_TRANSACTION_VERSION_THAT_SUPPORTS_OPTIONS } from "./constants"; import { TransactionOptions, TransactionVersion } from "./networkParams"; +import { ProtoSerializer } from "./proto"; import { TestWallet, loadTestWallets } from "./testutils"; -import { TokenTransfer } from "./tokenTransfer"; +import { TokenTransfer } from "./tokens"; import { Transaction } from "./transaction"; +import { TransactionComputer } from "./transactionComputer"; import { TransactionPayload } from "./transactionPayload"; +import { UserPublicKey, UserVerifier } from "@multiversx/sdk-wallet/out"; - -describe("test transaction construction", async () => { +describe("test transaction", async () => { let wallets: Record; - let minGasLimit = 50000; - let minGasPrice = 1000000000; + const minGasLimit = 50000; + const minGasPrice = 1000000000; + + const transactionComputer = new TransactionComputer(); + + const networkConfig = { + MinGasLimit: 50000, + GasPerDataByte: 1500, + GasPriceModifier: 0.01, + ChainID: "D", + }; before(async function () { wallets = await loadTestWallets(); }); - it("with no data, no value", async () => { - let transaction = new Transaction({ + it("should serialize transaction for signing (without data)", async () => { + const transaction = new Transaction({ + chainID: networkConfig.ChainID, + sender: wallets.alice.address.bech32(), + receiver: wallets.bob.address.bech32(), + gasLimit: 50000n, + value: 0n, + version: 2, + nonce: 89n, + }); + + const serializedTransactionBytes = transactionComputer.computeBytesForSigning(transaction); + const serializedTransaction = Buffer.from(serializedTransactionBytes).toString(); + + assert.equal( + serializedTransaction, + `{"nonce":89,"value":"0","receiver":"erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx","sender":"erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th","gasPrice":1000000000,"gasLimit":50000,"chainID":"D","version":2}`, + ); + }); + + it("should serialize transaction for signing (with data)", async () => { + const transaction = new Transaction({ + chainID: networkConfig.ChainID, + sender: wallets.alice.address.bech32(), + receiver: wallets.bob.address.bech32(), + gasLimit: 70000n, + value: 1000000000000000000n, + version: 2, + nonce: 90n, + data: new Uint8Array(Buffer.from("hello")), + }); + + const serializedTransactionBytes = transactionComputer.computeBytesForSigning(transaction); + const serializedTransaction = Buffer.from(serializedTransactionBytes).toString(); + + assert.equal( + serializedTransaction, + `{"nonce":90,"value":"1000000000000000000","receiver":"erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx","sender":"erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th","gasPrice":1000000000,"gasLimit":70000,"data":"aGVsbG8=","chainID":"D","version":2}`, + ); + }); + + it("should sign transaction (with no data, no value) (legacy)", async () => { + const transaction = new Transaction({ nonce: 89, value: "0", sender: wallets.alice.address, receiver: wallets.bob.address, gasPrice: minGasPrice, gasLimit: minGasLimit, - chainID: "local-testnet" + chainID: "local-testnet", }); - await wallets.alice.signer.sign(transaction); - assert.equal("b56769014f2bdc5cf9fc4a05356807d71fcf8775c819b0f1b0964625b679c918ffa64862313bfef86f99b38cb84fcdb16fa33ad6eb565276616723405cd8f109", transaction.getSignature().toString("hex")); - assert.equal(transaction.getHash().toString(), "eb30c50c8831885ebcfac986d27e949ec02cf25676e22a009b7a486e5431ec2e"); + transaction.applySignature(await wallets.alice.signer.sign(transaction.serializeForSigning())); + + assert.equal( + transaction.getSignature().toString("hex"), + "3f08a1dd64fbb627d10b048e0b45b1390f29bb0e457762a2ccb710b029f299022a67a4b8e45cf62f4314afec2e56b5574c71e38df96cc41fae757b7ee5062503", + ); + assert.equal( + transaction.getHash().toString(), + "1359fb9d5b0b47ca9f3b4adce6e4a524fa74099dd4732743b9226774a4cb0ad8", + ); }); - it("with data, no value", async () => { - let transaction = new Transaction({ + it("should sign transaction (with data, no value) (legacy)", async () => { + const transaction = new Transaction({ nonce: 90, value: "0", sender: wallets.alice.address, @@ -42,16 +102,98 @@ describe("test transaction construction", async () => { gasPrice: minGasPrice, gasLimit: 80000, data: new TransactionPayload("hello"), - chainID: "local-testnet" + chainID: "local-testnet", }); - await wallets.alice.signer.sign(transaction); - assert.equal("e47fd437fc17ac9a69f7bf5f85bafa9e7628d851c4f69bd9fedc7e36029708b2e6d168d5cd652ea78beedd06d4440974ca46c403b14071a1a148d4188f6f2c0d", transaction.getSignature().toString("hex")); - assert.equal(transaction.getHash().toString(), "95ed9ac933712d7d77721d75eecfc7896873bb0d746417153812132521636872"); + transaction.applySignature(await wallets.alice.signer.sign(transaction.serializeForSigning())); + + assert.equal( + transaction.getSignature().toString("hex"), + "f9e8c1caf7f36b99e7e76ee1118bf71b55cde11a2356e2b3adf15f4ad711d2e1982469cbba7eb0afbf74e8a8f78e549b9410cd86eeaa88fcba62611ac9f6e30e", + ); + assert.equal( + transaction.getHash().toString(), + "10a2bd6f9c358d2c9645368081999efd2a4cc7f24bdfdd75e8f57485fd702001", + ); }); - it("with data, with opaque, unused options (the protocol ignores the options when version == 1)", async () => { - let transaction = new Transaction({ + it("should sign transaction (with usernames)", async () => { + const transaction = new Transaction({ + chainID: "T", + sender: wallets.carol.address.bech32(), + receiver: wallets.alice.address.bech32(), + gasLimit: 50000n, + value: 1000000000000000000n, + version: 2, + nonce: 204n, + senderUsername: "carol", + receiverUsername: "alice", + }); + + transaction.signature = await wallets.carol.signer.sign( + transactionComputer.computeBytesForSigning(transaction), + ); + + assert.equal( + Buffer.from(transaction.signature).toString("hex"), + "51e6cd78fb3ab4b53ff7ad6864df27cb4a56d70603332869d47a5cf6ea977c30e696103e41e8dddf2582996ad335229fdf4acb726564dbc1a0bc9e705b511f06", + ); + }); + + it("should compute hash", async () => { + const transaction = new Transaction({ + chainID: networkConfig.ChainID, + sender: wallets.alice.address.bech32(), + receiver: wallets.alice.address.bech32(), + gasLimit: 100000n, + value: 1000000000000n, + version: 2, + nonce: 17243n, + data: Buffer.from("testtx"), + }); + + transaction.signature = Buffer.from( + "eaa9e4dfbd21695d9511e9754bde13e90c5cfb21748a339a79be11f744c71872e9fe8e73c6035c413f5f08eef09e5458e9ea6fc315ff4da0ab6d000b450b2a07", + "hex", + ); + + const hash = transactionComputer.computeTransactionHash(transaction); + + assert.equal( + Buffer.from(hash).toString("hex"), + "169b76b752b220a76a93aeebc462a1192db1dc2ec9d17e6b4d7b0dcc91792f03", + ); + }); + + it("should compute hash (with usernames)", async () => { + const transaction = new Transaction({ + chainID: networkConfig.ChainID, + sender: wallets.alice.address.bech32(), + receiver: wallets.alice.address.bech32(), + gasLimit: 100000n, + value: 1000000000000n, + version: 2, + nonce: 17244n, + data: Buffer.from("testtx"), + senderUsername: "alice", + receiverUsername: "alice", + }); + + transaction.signature = Buffer.from( + "807bcd7de5553ea6dfc57c0510e84d46813c5963d90fec50991c500091408fcf6216dca48dae16a579a1611ed8b2834bae8bd0027dc17eb557963f7151b82c07", + "hex", + ); + + const hash = transactionComputer.computeTransactionHash(transaction); + + assert.equal( + Buffer.from(hash).toString("hex"), + "41b5acf7ebaf4a9165a64206b6ebc02021b3adda55ffb2a2698aac2e7004dc29", + ); + }); + + it("should sign & compute hash (with data, with opaque, unused options) (legacy)", async () => { + const transaction = new Transaction({ nonce: 89, value: "0", sender: wallets.alice.address, @@ -59,17 +201,18 @@ describe("test transaction construction", async () => { gasPrice: minGasPrice, gasLimit: minGasLimit, chainID: "local-testnet", + // The protocol ignores the options when version == 1 version: new TransactionVersion(1), - options: new TransactionOptions(1) + options: new TransactionOptions(1), }); - await wallets.alice.signer.sign(transaction); - assert.equal("c83e69b853a891bf2130c1839362fe2a7a8db327dcc0c9f130497a4f24b0236140b394801bb2e04ce061a6f873cb432bf1bb1e6072e295610904662ac427a30a", transaction.getSignature().toString("hex")); - assert.equal(transaction.getHash().toString(), "32fb1681bd532b226b5bdeed61ae62ce9416bf5e92e48caf96253ff72d1670ac"); + assert.throws(() => { + transaction.serializeForSigning(); + }, `Non-empty transaction options requires transaction version >= ${MIN_TRANSACTION_VERSION_THAT_SUPPORTS_OPTIONS}`); }); - it("with data, with value", async () => { - let transaction = new Transaction({ + it("should sign & compute hash (with data, with value) (legacy)", async () => { + const transaction = new Transaction({ nonce: 91, value: TokenTransfer.egldFromAmount(10), sender: wallets.alice.address, @@ -77,16 +220,23 @@ describe("test transaction construction", async () => { gasPrice: minGasPrice, gasLimit: 100000, data: new TransactionPayload("for the book"), - chainID: "local-testnet" + chainID: "local-testnet", }); - await wallets.alice.signer.sign(transaction); - assert.equal("9074789e0b4f9b2ac24b1fd351a4dd840afcfeb427b0f93e2a2d429c28c65ee9f4c288ca4dbde79de0e5bcf8c1a5d26e1b1c86203faea923e0edefb0b5099b0c", transaction.getSignature().toString("hex")); - assert.equal(transaction.getHash().toString(), "af53e0fc86612d5068862716b5169effdf554951ecc89849b0e836eb0b63fa3e"); + transaction.applySignature(await wallets.alice.signer.sign(transaction.serializeForSigning())); + + assert.equal( + transaction.getSignature().toString("hex"), + "b45f22e9f57a6df22670fcc3566723a0711a05ac2547456de59fd222a54940e4a1d99bd414897ccbf5c02a842ad86e638989b7f4d30edd26c99a8cd1eb092304", + ); + assert.equal( + transaction.getHash().toString(), + "84125d7154d81a723642100bdf74e6df99f7c069c016d1e6bbeb408fd4e961bf", + ); }); - it("with data, with large value", async () => { - let transaction = new Transaction({ + it("should sign & compute hash (with data, with large value) (legacy)", async () => { + const transaction = new Transaction({ nonce: 92, value: TokenTransfer.egldFromBigInteger("123456789000000000000000000000"), sender: wallets.alice.address, @@ -94,16 +244,23 @@ describe("test transaction construction", async () => { gasPrice: minGasPrice, gasLimit: 100000, data: new TransactionPayload("for the spaceship"), - chainID: "local-testnet" + chainID: "local-testnet", }); - await wallets.alice.signer.sign(transaction); - assert.equal("39938d15812708475dfc8125b5d41dbcea0b2e3e7aabbbfceb6ce4f070de3033676a218b73facd88b1432d7d4accab89c6130b3abe5cc7bbbb5146e61d355b03", transaction.getSignature().toString("hex")); - assert.equal(transaction.getHash().toString(), "e4a6048d92409cfe50f12e81218cb92f39966c618979a693b8d16320a06061c1"); + transaction.applySignature(await wallets.alice.signer.sign(transaction.serializeForSigning())); + + assert.equal( + transaction.getSignature().toString("hex"), + "01f05aa8cb0614e12a94ab9dcbde5e78370a4e05d23ef25a1fb9d5fcf1cb3b1f33b919cd8dafb1704efb18fa233a8aa0d3344fb6ee9b613a7d7a403786ffbd0a", + ); + assert.equal( + transaction.getHash().toString(), + "321e1f1a0e3d06edade34fd0fdf3b4859e4328a73706a442c2439968a074113c", + ); }); - it("with nonce = 0", async () => { - let transaction = new Transaction({ + it("should sign & compute hash (with nonce = 0) (legacy)", async () => { + const transaction = new Transaction({ nonce: 0, value: 0, sender: wallets.alice.address, @@ -112,53 +269,74 @@ describe("test transaction construction", async () => { gasLimit: 80000, data: new TransactionPayload("hello"), chainID: "local-testnet", - version: new TransactionVersion(1) + version: new TransactionVersion(1), }); - await wallets.alice.signer.sign(transaction); - assert.equal("dfa3e9f2fdec60dcb353bac3b3435b4a2ff251e7e98eaf8620f46c731fc70c8ba5615fd4e208b05e75fe0f7dc44b7a99567e29f94fcd91efac7e67b182cd2a04", transaction.getSignature().toString("hex")); - assert.equal(transaction.getHash().toString(), "6ffa1a75f98aaf336bfb87ef13b9b5a477a017158285d34ee2a503668767e69e"); + transaction.applySignature(await wallets.alice.signer.sign(transaction.serializeForSigning())); + + assert.equal( + transaction.getSignature().toString("hex"), + "dfa3e9f2fdec60dcb353bac3b3435b4a2ff251e7e98eaf8620f46c731fc70c8ba5615fd4e208b05e75fe0f7dc44b7a99567e29f94fcd91efac7e67b182cd2a04", + ); + assert.equal( + transaction.getHash().toString(), + "6ffa1a75f98aaf336bfb87ef13b9b5a477a017158285d34ee2a503668767e69e", + ); }); - it("without options field, should be omitted", async () => { - let transaction = new Transaction({ + it("should sign & compute hash (without options field, should be omitted) (legacy)", async () => { + const transaction = new Transaction({ nonce: 89, value: 0, sender: wallets.alice.address, receiver: wallets.bob.address, gasPrice: minGasPrice, gasLimit: minGasLimit, - chainID: "local-testnet" + chainID: "local-testnet", }); - await wallets.alice.signer.sign(transaction); - assert.equal("b56769014f2bdc5cf9fc4a05356807d71fcf8775c819b0f1b0964625b679c918ffa64862313bfef86f99b38cb84fcdb16fa33ad6eb565276616723405cd8f109", transaction.getSignature().toString("hex")); - assert.equal(transaction.getHash().toString(), "eb30c50c8831885ebcfac986d27e949ec02cf25676e22a009b7a486e5431ec2e"); + transaction.applySignature(await wallets.alice.signer.sign(transaction.serializeForSigning())); + + assert.equal( + transaction.getSignature().toString("hex"), + "3f08a1dd64fbb627d10b048e0b45b1390f29bb0e457762a2ccb710b029f299022a67a4b8e45cf62f4314afec2e56b5574c71e38df96cc41fae757b7ee5062503", + ); + assert.equal( + transaction.getHash().toString(), + "1359fb9d5b0b47ca9f3b4adce6e4a524fa74099dd4732743b9226774a4cb0ad8", + ); - let result = transaction.serializeForSigning(); + const result = transaction.serializeForSigning(); assert.isFalse(result.toString().includes("options")); }); - it("with guardian field, should be omitted", async () => { - let transaction = new Transaction({ + it("should sign & compute hash (with guardian field, should be omitted) (legacy)", async () => { + const transaction = new Transaction({ nonce: 89, value: 0, sender: wallets.alice.address, receiver: wallets.bob.address, gasPrice: minGasPrice, gasLimit: minGasLimit, - chainID: "local-testnet" + chainID: "local-testnet", }); - await wallets.alice.signer.sign(transaction); - assert.equal("b56769014f2bdc5cf9fc4a05356807d71fcf8775c819b0f1b0964625b679c918ffa64862313bfef86f99b38cb84fcdb16fa33ad6eb565276616723405cd8f109", transaction.getSignature().toString("hex")); - assert.equal(transaction.getHash().toString(), "eb30c50c8831885ebcfac986d27e949ec02cf25676e22a009b7a486e5431ec2e"); + transaction.applySignature(await wallets.alice.signer.sign(transaction.serializeForSigning())); + + assert.equal( + transaction.getSignature().toString("hex"), + "3f08a1dd64fbb627d10b048e0b45b1390f29bb0e457762a2ccb710b029f299022a67a4b8e45cf62f4314afec2e56b5574c71e38df96cc41fae757b7ee5062503", + ); + assert.equal( + transaction.getHash().toString(), + "1359fb9d5b0b47ca9f3b4adce6e4a524fa74099dd4732743b9226774a4cb0ad8", + ); - let result = transaction.serializeForSigning(); + const result = transaction.serializeForSigning(); assert.isFalse(result.toString().includes("options")); }); - it("with usernames", async () => { + it("should sign & compute hash (with usernames) (legacy)", async () => { const transaction = new Transaction({ nonce: 204, value: "1000000000000000000", @@ -167,57 +345,125 @@ describe("test transaction construction", async () => { senderUsername: "carol", receiverUsername: "alice", gasLimit: 50000, - chainID: "T" + chainID: "T", }); - await wallets.carol.signer.sign(transaction); - assert.equal(transaction.getSignature().toString("hex"), "5966dd6b98fc5ecbcd203fa38fac7059ba5c17683099071883b0ad6697386769321d851388a99cb8b81aab625aa2d7e13621432dbd8ab334c5891cd7c7755200"); - assert.equal(transaction.getHash().toString(), "5728fadbc6c1024c4a0d5552eca44e80c182dc9077e58e31d599cf9496c96d1e"); + transaction.applySignature(await wallets.carol.signer.sign(transaction.serializeForSigning())); + + assert.equal( + transaction.getSignature().toString("hex"), + "51e6cd78fb3ab4b53ff7ad6864df27cb4a56d70603332869d47a5cf6ea977c30e696103e41e8dddf2582996ad335229fdf4acb726564dbc1a0bc9e705b511f06", + ); + assert.equal( + transaction.getHash().toString(), + "edc84d776bfd655ddbd6fce24a83e379496ac47890d00be9c8bb2c6666fa3fd8", + ); }); - it("computes correct fee", () => { - let transaction = new Transaction({ + it("should sign & compute hash (guarded transaction)", async () => { + const alice = wallets.alice; + + const transaction = new Transaction({ + chainID: "local-testnet", + sender: alice.address.bech32(), + receiver: wallets.bob.address.bech32(), + gasLimit: 150000n, + gasPrice: 1000000000n, + data: new Uint8Array(Buffer.from("test data field")), + version: 2, + options: 2, + nonce: 92n, + value: 123456789000000000000000000000n, + guardian: "erd1x23lzn8483xs2su4fak0r0dqx6w38enpmmqf2yrkylwq7mfnvyhsxqw57y", + }); + transaction.guardianSignature = new Uint8Array(64); + transaction.signature = await alice.signer.sign(transactionComputer.computeBytesForSigning(transaction)); + + const serializer = new ProtoSerializer(); + const buffer = serializer.serializeTransaction(transaction); + + assert.equal( + buffer.toString("hex"), + "085c120e00018ee90ff6181f3761632000001a208049d639e5a6980d1cd2392abcce41029cda74a1563523a202f09641cc2618f82a200139472eff6886771a982f3083da5d421f24c29181e63888228dc81ca60d69e1388094ebdc0340f093094a0f746573742064617461206669656c64520d6c6f63616c2d746573746e657458026240e574d78b19e1481a6b9575c162e66f2f906a3178aec537509356385c4f1a5330a9b73a87a456fc6d7041e93b5f8a1231a92fb390174872a104a0929215600c0c6802722032a3f14cf53c4d0543954f6cf1bda0369d13e661dec095107627dc0f6d33612f7a4000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + ); + + const txHash = transactionComputer.computeTransactionHash(transaction); + assert.equal( + Buffer.from(txHash).toString("hex"), + "242022e9dcfa0ee1d8199b0043314dbda8601619f70069ebc441b9f03349a35c", + ); + }); + + it("computes fee (legacy)", () => { + const transaction = new Transaction({ nonce: 92, value: TokenTransfer.egldFromBigInteger("123456789000000000000000000000"), sender: wallets.alice.address, receiver: wallets.bob.address, - gasPrice: 500, - gasLimit: 20, - chainID: "local-testnet" + gasPrice: minGasPrice, + gasLimit: minGasLimit, + chainID: "local-testnet", }); - let networkConfig = { - MinGasLimit: 10, - GasPerDataByte: 1500, - GasPriceModifier: 0.01, - ChainID: "T" - }; + const fee = transaction.computeFee(networkConfig); + assert.equal(fee.toString(), "50000000000000"); + }); - let fee = transaction.computeFee(networkConfig); - assert.equal(fee.toString(), "5050"); + it("computes fee", async () => { + const transaction = new Transaction({ + chainID: "D", + sender: wallets.alice.address.bech32(), + receiver: wallets.alice.address.bech32(), + gasLimit: 50000n, + gasPrice: minGasPrice, + }); + + const gasLimit = transactionComputer.computeTransactionFee(transaction, networkConfig); + assert.equal(gasLimit.toString(), "50000000000000"); }); - it("computes correct fee with data field", () => { + it("computes fee, but should throw `NotEnoughGas` error", async () => { + const transaction = new Transaction({ + chainID: networkConfig.ChainID, + sender: wallets.alice.address.bech32(), + receiver: wallets.alice.address.bech32(), + gasLimit: 50000n, + data: Buffer.from("toolittlegaslimit"), + }); + + assert.throws(() => { + transactionComputer.computeTransactionFee(transaction, networkConfig); + }); + }); + + it("computes fee (with data field) (legacy)", () => { let transaction = new Transaction({ nonce: 92, value: TokenTransfer.egldFromBigInteger("123456789000000000000000000000"), sender: wallets.alice.address, receiver: wallets.bob.address, data: new TransactionPayload("testdata"), - gasPrice: 500, - gasLimit: 12010, - chainID: "local-testnet" + gasPrice: minGasPrice, + gasLimit: minGasLimit + 12010, + chainID: "local-testnet", }); - let networkConfig = { - MinGasLimit: 10, - GasPerDataByte: 1500, - GasPriceModifier: 0.01, - ChainID: "T" - }; - let fee = transaction.computeFee(networkConfig); - assert.equal(fee.toString(), "6005000"); + assert.equal(fee.toString(), "62000100000000"); + }); + + it("computes fee (with data field)", async () => { + const transaction = new Transaction({ + chainID: networkConfig.ChainID, + sender: wallets.alice.address.bech32(), + receiver: wallets.alice.address.bech32(), + gasLimit: 50000n + 12010n, + gasPrice: minGasPrice, + data: Buffer.from("testdata"), + }); + + const gasLimit = transactionComputer.computeTransactionFee(transaction, networkConfig); + assert.equal(gasLimit.toString(), "62000100000000"); }); it("should convert transaction to plain object and back", () => { @@ -232,7 +478,7 @@ describe("test transaction construction", async () => { gasPrice: minGasPrice, gasLimit: 80000, data: new TransactionPayload("hello"), - chainID: "local-testnet" + chainID: "local-testnet", }); const plainObject = transaction.toPlainObject(); @@ -246,7 +492,7 @@ describe("test transaction construction", async () => { sender: wallets.alice.address, receiver: wallets.bob.address, gasLimit: 50000, - chainID: "local-testnet" + chainID: "local-testnet", }); assert.equal(tx1.getValue().toString(), "123456789000000000000000000000"); @@ -255,19 +501,19 @@ describe("test transaction construction", async () => { sender: wallets.alice.address, receiver: wallets.bob.address, gasLimit: 50000, - chainID: "local-testnet" + chainID: "local-testnet", }); assert.equal(tx2.getValue().toString(), "123456789000000000000000000000"); const tx3 = new Transaction({ - // Passing a BigNumber is not recommended. + // Passing a BigNumber is not recommended. // However, ITransactionValue interface is permissive, and developers may mistakenly pass such objects as values. // TokenTransfer objects or simple strings (see above) are preferred, instead. value: new BigNumber("123456789000000000000000000000"), sender: wallets.alice.address, receiver: wallets.bob.address, gasLimit: 50000, - chainID: "local-testnet" + chainID: "local-testnet", }); assert.equal(tx3.getValue().toString(), "123456789000000000000000000000"); }); @@ -297,7 +543,7 @@ describe("test transaction construction", async () => { data: new TransactionPayload("hello"), chainID: "local-testnet", version: new TransactionVersion(1), - options: TransactionOptions.withOptions({ guarded: true }) + options: TransactionOptions.withOptions({ guarded: true }), }); assert.isFalse(transaction.isGuardedTransaction()); @@ -311,7 +557,7 @@ describe("test transaction construction", async () => { data: new TransactionPayload("hello"), chainID: "local-testnet", version: new TransactionVersion(2), - options: TransactionOptions.withOptions({ guarded: true }) + options: TransactionOptions.withOptions({ guarded: true }), }); assert.isFalse(transaction.isGuardedTransaction()); @@ -325,7 +571,7 @@ describe("test transaction construction", async () => { data: new TransactionPayload("hello"), chainID: "local-testnet", version: new TransactionVersion(2), - options: TransactionOptions.withOptions({ guarded: true }) + options: TransactionOptions.withOptions({ guarded: true }), }); assert.isFalse(transaction.isGuardedTransaction()); @@ -340,7 +586,7 @@ describe("test transaction construction", async () => { data: new TransactionPayload("hello"), chainID: "local-testnet", version: new TransactionVersion(2), - options: TransactionOptions.withOptions({ guarded: true }) + options: TransactionOptions.withOptions({ guarded: true }), }); assert.isFalse(transaction.isGuardedTransaction()); @@ -355,10 +601,158 @@ describe("test transaction construction", async () => { data: new TransactionPayload("hello"), chainID: "local-testnet", version: new TransactionVersion(2), - options: TransactionOptions.withOptions({ guarded: true }) + options: TransactionOptions.withOptions({ guarded: true }), }); - await wallets.alice.signer.sign(transaction); + transaction.applySignature(await wallets.alice.signer.sign(transaction.serializeForSigning())); transaction.applyGuardianSignature(transaction.getSignature()); assert.isTrue(transaction.isGuardedTransaction()); }); + + it("test sign using hash", async () => { + let transaction = new Transaction({ + nonce: 89n, + value: 0n, + sender: wallets.alice.address.toBech32(), + receiver: wallets.bob.address.toBech32(), + gasLimit: 50000n, + gasPrice: 1000000000n, + chainID: "integration tests chain ID", + version: 2, + options: 1, + }); + + transaction.signature = await wallets.alice.signer.sign(transactionComputer.computeHashForSigning(transaction)); + + assert.equal( + "f0c81f2393b1ec5972c813f817bae8daa00ade91c6f75ea604ab6a4d2797aca4378d783023ff98f1a02717fe4f24240cdfba0b674ee9abb18042203d713bc70a", + Buffer.from(transaction.signature).toString("hex"), + ); + }); + + it("should apply guardian", async () => { + let transaction = new Transaction({ + nonce: 89n, + value: 0n, + sender: wallets.alice.address.toBech32(), + receiver: wallets.bob.address.toBech32(), + gasLimit: 50000n, + chainID: "localnet", + }); + + transactionComputer.applyGuardian(transaction, wallets.carol.address.toBech32()); + + assert.equal(transaction.version, 2); + assert.equal(transaction.options, 2); + assert.equal(transaction.guardian, wallets.carol.address.toBech32()); + }); + + it("should apply guardian with options set for hash signing", async () => { + let transaction = new Transaction({ + nonce: 89n, + value: 0n, + sender: wallets.alice.address.toBech32(), + receiver: wallets.bob.address.toBech32(), + gasLimit: 50000n, + chainID: "localnet", + version: 1, + }); + + transactionComputer.applyOptionsForHashSigning(transaction); + assert.equal(transaction.version, 2); + assert.equal(transaction.options, 1); + + transactionComputer.applyGuardian(transaction, wallets.carol.address.toBech32()); + assert.equal(transaction.version, 2); + assert.equal(transaction.options, 3); + }); + + it("should ensure transaction is valid", async () => { + let transaction = new Transaction({ + sender: "invalidAddress", + receiver: wallets.bob.address.toBech32(), + gasLimit: 50000n, + chainID: "", + }); + + assert.throws(() => { + transactionComputer.computeBytesForSigning(transaction); + }, "Invalid `sender` field. Should be the bech32 address of the sender."); + + transaction.sender = wallets.alice.address.toBech32(); + + assert.throws(() => { + transactionComputer.computeBytesForSigning(transaction); + }, "The `chainID` field is not set"); + + transaction.chainID = "localnet"; + transaction.version = 1; + transaction.options = 2; + + assert.throws(() => { + transactionComputer.computeBytesForSigning(transaction); + }, `Non-empty transaction options requires transaction version >= ${MIN_TRANSACTION_VERSION_THAT_SUPPORTS_OPTIONS}`); + + transactionComputer.applyOptionsForHashSigning(transaction); + + assert.equal(transaction.version, 2); + assert.equal(transaction.options, 3); + }); + + it("should compute bytes to verify transaction signature", async () => { + let transaction = new Transaction({ + sender: wallets.alice.address.toBech32(), + receiver: wallets.bob.address.toBech32(), + gasLimit: 50000n, + chainID: "D", + nonce: 7n, + }); + + transaction.signature = await wallets.alice.signer.sign( + transactionComputer.computeBytesForSigning(transaction), + ); + + const userVerifier = new UserVerifier(new UserPublicKey(wallets.alice.address.getPublicKey())); + const isSignedByAlice = userVerifier.verify( + transactionComputer.computeBytesForVerifying(transaction), + transaction.signature, + ); + + const wrongVerifier = new UserVerifier(new UserPublicKey(wallets.bob.address.getPublicKey())); + const isSignedByBob = wrongVerifier.verify( + transactionComputer.computeBytesForVerifying(transaction), + transaction.signature, + ); + + assert.equal(isSignedByAlice, true); + assert.equal(isSignedByBob, false); + }); + + it("should compute bytes to verify transaction signature (signed by hash)", async () => { + let transaction = new Transaction({ + sender: wallets.alice.address.toBech32(), + receiver: wallets.bob.address.toBech32(), + gasLimit: 50000n, + chainID: "D", + nonce: 7n, + }); + + transactionComputer.applyOptionsForHashSigning(transaction); + + transaction.signature = await wallets.alice.signer.sign(transactionComputer.computeHashForSigning(transaction)); + + const userVerifier = new UserVerifier(new UserPublicKey(wallets.alice.address.getPublicKey())); + const isSignedByAlice = userVerifier.verify( + transactionComputer.computeBytesForVerifying(transaction), + transaction.signature, + ); + + const wrongVerifier = new UserVerifier(new UserPublicKey(wallets.bob.address.getPublicKey())); + const isSignedByBob = wrongVerifier.verify( + transactionComputer.computeBytesForVerifying(transaction), + transaction.signature, + ); + + assert.equal(isSignedByAlice, true); + assert.equal(isSignedByBob, false); + }); }); diff --git a/src/transaction.ts b/src/transaction.ts index f5f5dff8a..9678fa25f 100644 --- a/src/transaction.ts +++ b/src/transaction.ts @@ -1,445 +1,443 @@ import { BigNumber } from "bignumber.js"; import { Address } from "./address"; -import { Compatibility } from "./compatibility"; -import { TRANSACTION_MIN_GAS_PRICE } from "./constants"; -import * as errors from "./errors"; +import { TRANSACTION_MIN_GAS_PRICE, TRANSACTION_OPTIONS_DEFAULT, TRANSACTION_VERSION_DEFAULT } from "./constants"; +import { TransactionsConverter } from "./converters/transactionsConverter"; import { Hash } from "./hash"; -import { IAddress, IChainID, IGasLimit, IGasPrice, INonce, IPlainTransactionObject, ISignature, ITransactionOptions, ITransactionPayload, ITransactionValue, ITransactionVersion } from "./interface"; +import { + IAddress, + IChainID, + IGasLimit, + IGasPrice, + INonce, + IPlainTransactionObject, + ISignature, + ITransactionOptions, + ITransactionPayload, + ITransactionValue, + ITransactionVersion, +} from "./interface"; import { INetworkConfig } from "./interfaceOfNetwork"; import { TransactionOptions, TransactionVersion } from "./networkParams"; -import { ProtoSerializer } from "./proto"; -import { Signature, interpretSignatureAsBuffer } from "./signature"; +import { interpretSignatureAsBuffer } from "./signature"; import { TransactionPayload } from "./transactionPayload"; -import { guardNotEmpty } from "./utils"; - -const createTransactionHasher = require("blake2b"); -const TRANSACTION_HASH_LENGTH = 32; +import { TransactionComputer } from "./transactionComputer"; /** * An abstraction for creating and signing transactions. */ export class Transaction { - /** - * The nonce of the transaction (the account sequence number of the sender). - */ - private nonce: INonce; - - /** - * The value to transfer. - */ - private value: ITransactionValue; - - /** - * The address of the sender. - */ - private sender: IAddress; - - /** - * The address of the receiver. - */ - private readonly receiver: IAddress; - - /** - * The username of the sender. - */ - private senderUsername: string; - - /** - * The username of the receiver. - */ - private receiverUsername: string; - - /** - * The gas price to be used. - */ - private gasPrice: IGasPrice; - - /** - * The maximum amount of gas to be consumed when processing the transaction. - */ - private gasLimit: IGasLimit; - - /** - * The payload of the transaction. - */ - private readonly data: ITransactionPayload; - - /** - * The chain ID of the Network (e.g. "1" for Mainnet). - */ - private chainID: IChainID; - - /** - * The version, required by the Network in order to correctly interpret the contents of the transaction. - * @deprecated Use getVersion() and setVersion() instead. - */ - version: TransactionVersion; - - /** - * The options field, useful for describing different settings available for transactions - * @deprecated Use getOptions() and setOptions() instead. - */ - options: TransactionOptions; - - /** - * The address of the guardian. - */ - private guardian: IAddress; - - /** - * The signature. - */ - private signature: Buffer; - - /** - * The signature of the guardian. - */ - private guardianSignature: Buffer; - - /** - * The transaction hash, also used as a transaction identifier. - */ - private hash: TransactionHash; - - /** - * Creates a new Transaction object. - */ - public constructor({ - nonce, - value, - sender, - receiver, - senderUsername, - receiverUsername, - gasPrice, - gasLimit, - data, - chainID, - version, - options, - guardian, - }: { - nonce?: INonce; - value?: ITransactionValue; - sender: IAddress; - receiver: IAddress; - senderUsername?: string; - receiverUsername?: string; - gasPrice?: IGasPrice; - gasLimit: IGasLimit; - data?: ITransactionPayload; - chainID: IChainID; - version?: ITransactionVersion; - options?: ITransactionOptions; - guardian?: IAddress; - }) { - this.nonce = nonce || 0; - this.value = value ? new BigNumber(value.toString()).toFixed(0) : 0; - this.sender = sender; - this.receiver = receiver; - this.senderUsername = senderUsername || ""; - this.receiverUsername = receiverUsername || ""; - this.gasPrice = gasPrice || TRANSACTION_MIN_GAS_PRICE; - this.gasLimit = gasLimit; - this.data = data || new TransactionPayload(); - this.chainID = chainID; - this.version = version ? new TransactionVersion(version.valueOf()) : TransactionVersion.withDefaultVersion(); - this.options = options ? new TransactionOptions(options.valueOf()) : TransactionOptions.withDefaultOptions(); - this.guardian = guardian || Address.empty(); - - this.signature = Buffer.from([]); - this.guardianSignature = Buffer.from([]); - this.hash = TransactionHash.empty(); - } - - getNonce(): INonce { - return this.nonce; - } - - /** - * Sets the account sequence number of the sender. Must be done prior signing. - */ - setNonce(nonce: INonce) { - this.nonce = nonce; - } - - getValue(): ITransactionValue { - return this.value; - } - - setValue(value: ITransactionValue) { - this.value = value; - } - - getSender(): IAddress { - return this.sender; - } - - setSender(sender: IAddress) { - this.sender = sender; - } - - getReceiver(): IAddress { - return this.receiver; - } - - getSenderUsername(): string { - return this.senderUsername; - } - - setSenderUsername(senderUsername: string) { - this.senderUsername = senderUsername; - } - - getReceiverUsername(): string { - return this.receiverUsername; - } - - setReceiverUsername(receiverUsername: string) { - this.receiverUsername = receiverUsername; - } - - getGuardian(): IAddress { - return this.guardian; - } - - getGasPrice(): IGasPrice { - return this.gasPrice; - } - - setGasPrice(gasPrice: IGasPrice) { - this.gasPrice = gasPrice; - } - - getGasLimit(): IGasLimit { - return this.gasLimit; - } - - setGasLimit(gasLimit: IGasLimit) { - this.gasLimit = gasLimit; - } - - getData(): ITransactionPayload { - return this.data; - } - - getChainID(): IChainID { - return this.chainID; - } - - setChainID(chainID: IChainID) { - this.chainID = chainID; - } - - getVersion(): TransactionVersion { - return this.version; - } - - setVersion(version: ITransactionVersion) { - this.version = new TransactionVersion(version.valueOf()); - } - - getOptions(): TransactionOptions { - // Make sure that "sdk-core v12" is compatible (for a while) with (older) libraries that were previously setting the (soon to be private) "options" field directly, - // instead of using the "setOptions()" method. - const options = new TransactionOptions(this.options.valueOf()); - return options; - } - - setOptions(options: ITransactionOptions) { - this.options = new TransactionOptions(options.valueOf()); - } - - getSignature(): Buffer { - return this.signature; - } - - getGuardianSignature(): Buffer { - return this.guardianSignature; - } - - setGuardian(guardian: IAddress) { - this.guardian = guardian; - } - - getHash(): TransactionHash { - guardNotEmpty(this.hash, "hash"); - return this.hash; - } - - /** - * Serializes a transaction to a sequence of bytes, ready to be signed. - * This function is called internally by signers. - */ - serializeForSigning(): Buffer { - // TODO: for appropriate tx.version, interpret tx.options accordingly and sign using the content / data hash - let plain = this.toPlainObject(); - // Make sure we never sign the transaction with another signature set up (useful when using the same method for verification) - if (plain.signature) { - delete plain.signature; - } - - if (plain.guardianSignature) { - delete plain.guardianSignature; - } - - if (!plain.guardian) { - delete plain.guardian - } - - let serialized = JSON.stringify(plain); - - return Buffer.from(serialized); - } - - /** - * Checks the integrity of the guarded transaction - */ - isGuardedTransaction(): boolean { - const hasGuardian = this.guardian.bech32().length > 0; - const hasGuardianSignature = this.guardianSignature.length > 0; - return this.getOptions().isWithGuardian() && hasGuardian && hasGuardianSignature; - } - - /** - * Converts the transaction object into a ready-to-serialize, plain JavaScript object. - * This function is called internally within the signing procedure. - */ - toPlainObject(): IPlainTransactionObject { - const plainObject = { - nonce: this.nonce.valueOf(), - value: this.value.toString(), - receiver: this.receiver.bech32(), - sender: this.sender.bech32(), - senderUsername: this.senderUsername ? Buffer.from(this.senderUsername).toString("base64") : undefined, - receiverUsername: this.receiverUsername ? Buffer.from(this.receiverUsername).toString("base64") : undefined, - gasPrice: this.gasPrice.valueOf(), - gasLimit: this.gasLimit.valueOf(), - data: this.data.length() == 0 ? undefined : this.data.encoded(), - chainID: this.chainID.valueOf(), - version: this.getVersion().valueOf(), - options: this.getOptions().valueOf() == 0 ? undefined : this.getOptions().valueOf(), - guardian: this.guardian?.bech32() ? (this.guardian.bech32() == "" ? undefined : this.guardian.bech32()) : undefined, - signature: this.signature.toString("hex") ? this.signature.toString("hex") : undefined, - guardianSignature: this.guardianSignature.toString("hex") ? this.guardianSignature.toString("hex") : undefined, - }; - - Compatibility.guardAddressIsSetAndNonZero(new Address(plainObject.sender), "'sender' of transaction", "pass the actual sender to the Transaction constructor") - - return plainObject; - } - - /** - * Converts a plain object transaction into a Transaction Object. - * - * @param plainObjectTransaction Raw data of a transaction, usually obtained by calling toPlainObject() - */ - static fromPlainObject(plainObjectTransaction: IPlainTransactionObject): Transaction { - const tx = new Transaction({ - nonce: Number(plainObjectTransaction.nonce), - value: new BigNumber(plainObjectTransaction.value).toFixed(0), - receiver: Address.fromString(plainObjectTransaction.receiver), - receiverUsername: plainObjectTransaction.receiverUsername ? Buffer.from(plainObjectTransaction.receiverUsername, "base64").toString() : undefined, - sender: Address.fromString(plainObjectTransaction.sender), - senderUsername: plainObjectTransaction.senderUsername ? Buffer.from(plainObjectTransaction.senderUsername, "base64").toString() : undefined, - guardian: plainObjectTransaction.guardian ? Address.fromString(plainObjectTransaction.guardian) : undefined, - gasPrice: Number(plainObjectTransaction.gasPrice), - gasLimit: Number(plainObjectTransaction.gasLimit), - data: new TransactionPayload(Buffer.from(plainObjectTransaction.data || "", "base64")), - chainID: String(plainObjectTransaction.chainID), - version: new TransactionVersion(plainObjectTransaction.version), - options: plainObjectTransaction.options != null ? new TransactionOptions(plainObjectTransaction.options) : undefined - }); - - if (plainObjectTransaction.signature) { - tx.applySignature( - new Signature(plainObjectTransaction.signature), - ); - } - - if (plainObjectTransaction.guardianSignature) { - tx.applyGuardianSignature( - new Signature(plainObjectTransaction.guardianSignature) - ); - } - - return tx; - } - - /** - * Applies the signature on the transaction. - * - * @param signature The signature, as computed by a signer. - */ - applySignature(signature: ISignature | Uint8Array) { - this.signature = interpretSignatureAsBuffer(signature); - this.hash = TransactionHash.compute(this); - } - - /** - * Applies the guardian signature on the transaction. - * - * @param guardianSignature The signature, as computed by a signer. - */ - applyGuardianSignature(guardianSignature: ISignature | Uint8Array) { - this.guardianSignature = interpretSignatureAsBuffer(guardianSignature); - this.hash = TransactionHash.compute(this); - } - - /** - * Converts a transaction to a ready-to-broadcast object. - * Called internally by the network provider. - */ - toSendable(): any { - return this.toPlainObject(); - } - - /** - * Computes the current transaction fee based on the {@link NetworkConfig} and transaction properties - * @param networkConfig {@link NetworkConfig} - */ - computeFee(networkConfig: INetworkConfig): BigNumber { - let moveBalanceGas = - networkConfig.MinGasLimit.valueOf() + - this.data.length() * networkConfig.GasPerDataByte.valueOf(); - if (moveBalanceGas > this.gasLimit.valueOf()) { - throw new errors.ErrNotEnoughGas(this.gasLimit.valueOf()); - } - - let gasPrice = new BigNumber(this.gasPrice.valueOf()); - let feeForMove = new BigNumber(moveBalanceGas).multipliedBy(gasPrice); - if (moveBalanceGas === this.gasLimit.valueOf()) { - return feeForMove; - } - - let diff = new BigNumber(this.gasLimit.valueOf() - moveBalanceGas); - let modifiedGasPrice = gasPrice.multipliedBy( - new BigNumber(networkConfig.GasPriceModifier.valueOf()) - ); - let processingFee = diff.multipliedBy(modifiedGasPrice); - - return feeForMove.plus(processingFee); - } + /** + * The nonce of the transaction (the account sequence number of the sender). + */ + public nonce: bigint; + + /** + * The value to transfer. + */ + public value: bigint; + + /** + * The address of the sender, in bech32 format. + */ + public sender: string; + + /** + * The address of the receiver, in bech32 format. + */ + public receiver: string; + + /** + * The username of the sender. + */ + public senderUsername: string; + + /** + * The username of the receiver. + */ + public receiverUsername: string; + + /** + * The gas price to be used. + */ + public gasPrice: bigint; + + /** + * The maximum amount of gas to be consumed when processing the transaction. + */ + public gasLimit: bigint; + + /** + * The payload of the transaction. + */ + public data: Uint8Array; + + /** + * The chain ID of the Network (e.g. "1" for Mainnet). + */ + public chainID: string; + + /** + * The version, required by the Network in order to correctly interpret the contents of the transaction. + */ + public version: number; + + /** + * The options field, useful for describing different settings available for transactions. + */ + public options: number; + + /** + * The address of the guardian, in bech32 format. + */ + public guardian: string; + + /** + * The signature. + */ + public signature: Uint8Array; + + /** + * The signature of the guardian. + */ + public guardianSignature: Uint8Array; + + /** + * Creates a new Transaction object. + */ + public constructor(options: { + nonce?: INonce | bigint; + value?: ITransactionValue | bigint; + sender: IAddress | string; + receiver: IAddress | string; + senderUsername?: string; + receiverUsername?: string; + gasPrice?: IGasPrice | bigint; + gasLimit: IGasLimit | bigint; + data?: ITransactionPayload | Uint8Array; + chainID: IChainID | string; + version?: ITransactionVersion | number; + options?: ITransactionOptions | number; + guardian?: IAddress | string; + signature?: Uint8Array; + guardianSignature?: Uint8Array; + }) { + this.nonce = BigInt(options.nonce?.valueOf() || 0n); + // We still rely on "bigNumber" for value, because client code might be passing a BigNumber object as a legacy "ITransactionValue", + // and we want to keep compatibility. + this.value = options.value ? BigInt(new BigNumber(options.value.toString()).toFixed(0)) : 0n; + this.sender = this.addressAsBech32(options.sender); + this.receiver = this.addressAsBech32(options.receiver); + this.senderUsername = options.senderUsername || ""; + this.receiverUsername = options.receiverUsername || ""; + this.gasPrice = BigInt(options.gasPrice?.valueOf() || TRANSACTION_MIN_GAS_PRICE); + this.gasLimit = BigInt(options.gasLimit.valueOf()); + this.data = options.data?.valueOf() || new Uint8Array(); + this.chainID = options.chainID.valueOf(); + this.version = Number(options.version?.valueOf() || TRANSACTION_VERSION_DEFAULT); + this.options = Number(options.options?.valueOf() || TRANSACTION_OPTIONS_DEFAULT); + this.guardian = options.guardian ? this.addressAsBech32(options.guardian) : ""; + + this.signature = options.signature || Buffer.from([]); + this.guardianSignature = options.guardianSignature || Buffer.from([]); + } + + private addressAsBech32(address: IAddress | string): string { + return typeof address === "string" ? address : address.bech32(); + } + + /** + * Legacy method, use the "nonce" property instead. + */ + getNonce(): INonce { + return Number(this.nonce); + } + + /** + * Legacy method, use the "nonce" property instead. + * Sets the account sequence number of the sender. Must be done prior signing. + */ + setNonce(nonce: INonce | bigint) { + this.nonce = BigInt(nonce.valueOf()); + } + + /** + * Legacy method, use the "value" property instead. + */ + getValue(): ITransactionValue { + return this.value; + } + + /** + * Legacy method, use the "value" property instead. + */ + setValue(value: ITransactionValue | bigint) { + this.value = BigInt(value.toString()); + } + + /** + * Legacy method, use the "sender" property instead. + */ + getSender(): IAddress { + return Address.fromBech32(this.sender); + } + + /** + * Legacy method, use the "sender" property instead. + */ + setSender(sender: IAddress | string) { + this.sender = typeof sender === "string" ? sender : sender.bech32(); + } + + /** + * Legacy method, use the "receiver" property instead. + */ + getReceiver(): IAddress { + return Address.fromBech32(this.receiver); + } + + /** + * Legacy method, use the "senderUsername" property instead. + */ + getSenderUsername(): string { + return this.senderUsername; + } + + /** + * Legacy method, use the "senderUsername" property instead. + */ + setSenderUsername(senderUsername: string) { + this.senderUsername = senderUsername; + } + + /** + * Legacy method, use the "receiverUsername" property instead. + */ + getReceiverUsername(): string { + return this.receiverUsername; + } + + /** + * Legacy method, use the "receiverUsername" property instead. + */ + setReceiverUsername(receiverUsername: string) { + this.receiverUsername = receiverUsername; + } + + /** + * Legacy method, use the "guardian" property instead. + */ + getGuardian(): IAddress { + return new Address(this.guardian); + } + + /** + * Legacy method, use the "gasPrice" property instead. + */ + getGasPrice(): IGasPrice { + return Number(this.gasPrice); + } + + /** + * Legacy method, use the "gasPrice" property instead. + */ + setGasPrice(gasPrice: IGasPrice | bigint) { + this.gasPrice = BigInt(gasPrice.valueOf()); + } + + /** + * Legacy method, use the "gasLimit" property instead. + */ + getGasLimit(): IGasLimit { + return Number(this.gasLimit); + } + + /** + * Legacy method, use the "gasLimit" property instead. + */ + setGasLimit(gasLimit: IGasLimit | bigint) { + this.gasLimit = BigInt(gasLimit.valueOf()); + } + + /** + * Legacy method, use the "data" property instead. + */ + getData(): ITransactionPayload { + return new TransactionPayload(Buffer.from(this.data)); + } + + /** + * Legacy method, use the "chainID" property instead. + */ + getChainID(): IChainID { + return this.chainID; + } + + /** + * Legacy method, use the "chainID" property instead. + */ + setChainID(chainID: IChainID | string) { + this.chainID = chainID.valueOf(); + } + + /** + * Legacy method, use the "version" property instead. + */ + getVersion(): TransactionVersion { + return new TransactionVersion(this.version); + } + + /** + * Legacy method, use the "version" property instead. + */ + setVersion(version: ITransactionVersion | number) { + this.version = version.valueOf(); + } + + /** + * Legacy method, use the "options" property instead. + */ + getOptions(): TransactionOptions { + return new TransactionOptions(this.options.valueOf()); + } + + /** + * Legacy method, use the "options" property instead. + * + * Question for review: check how the options are set by sdk-dapp, wallet, ledger, extension. + */ + setOptions(options: ITransactionOptions | number) { + this.options = options.valueOf(); + } + + /** + * Legacy method, use the "signature" property instead. + */ + getSignature(): Buffer { + return Buffer.from(this.signature); + } + + /** + * Legacy method, use the "guardianSignature" property instead. + */ + getGuardianSignature(): Buffer { + return Buffer.from(this.guardianSignature); + } + + /** + * Legacy method, use the "guardian" property instead. + */ + setGuardian(guardian: IAddress | string) { + this.guardian = typeof guardian === "string" ? guardian : guardian.bech32(); + } + + /** + * Legacy method, use "TransactionComputer.computeTransactionHash()" instead. + */ + getHash(): TransactionHash { + return TransactionHash.compute(this); + } + + /** + * Legacy method, use "TransactionComputer.computeBytesForSigning()" instead. + * Serializes a transaction to a sequence of bytes, ready to be signed. + * This function is called internally by signers. + */ + serializeForSigning(): Buffer { + const computer = new TransactionComputer(); + const bytes = computer.computeBytesForSigning(this); + return Buffer.from(bytes); + } + + /** + * Checks the integrity of the guarded transaction + */ + isGuardedTransaction(): boolean { + const hasGuardian = this.guardian.length > 0; + const hasGuardianSignature = this.guardianSignature.length > 0; + return this.getOptions().isWithGuardian() && hasGuardian && hasGuardianSignature; + } + + /** + * Legacy method, use "TransactionsConverter.transactionToPlainObject()" instead. + * + * Converts the transaction object into a ready-to-serialize, plain JavaScript object. + * This function is called internally within the signing procedure. + */ + toPlainObject(): IPlainTransactionObject { + // Ideally, "converters" package should be outside of "core", and not referenced here. + const converter = new TransactionsConverter(); + return converter.transactionToPlainObject(this); + } + + /** + * Legacy method, use "TransactionsConverter.plainObjectToTransaction()" instead. + * Converts a plain object transaction into a Transaction Object. + * + * @param plainObjectTransaction Raw data of a transaction, usually obtained by calling toPlainObject() + */ + static fromPlainObject(plainObjectTransaction: IPlainTransactionObject): Transaction { + // Ideally, "converters" package should be outside of "core", and not referenced here. + const converter = new TransactionsConverter(); + return converter.plainObjectToTransaction(plainObjectTransaction); + } + + /** + * Legacy method, use the "signature" property instead. + * Applies the signature on the transaction. + * + * @param signature The signature, as computed by a signer. + */ + applySignature(signature: ISignature | Uint8Array) { + this.signature = interpretSignatureAsBuffer(signature); + } + + /** + * Legacy method, use the "guardianSignature" property instead. + * Applies the guardian signature on the transaction. + * + * @param guardianSignature The signature, as computed by a signer. + */ + applyGuardianSignature(guardianSignature: ISignature | Uint8Array) { + this.guardianSignature = interpretSignatureAsBuffer(guardianSignature); + } + + /** + * Converts a transaction to a ready-to-broadcast object. + * Called internally by the network provider. + */ + toSendable(): any { + return this.toPlainObject(); + } + + /** + * Legacy method, use "TransactionComputer.computeTransactionFee()" instead. + * + * Computes the current transaction fee based on the {@link NetworkConfig} and transaction properties + * @param networkConfig {@link NetworkConfig} + */ + computeFee(networkConfig: INetworkConfig): BigNumber { + const computer = new TransactionComputer(); + const fee = computer.computeTransactionFee(this, networkConfig); + return new BigNumber(fee.toString()); + } } /** + * Legacy class, use "TransactionComputer.computeTransactionHash()" instead. * An abstraction for handling and computing transaction hashes. */ export class TransactionHash extends Hash { - constructor(hash: string) { - super(hash); - } - - /** - * Computes the hash of a transaction. - */ - static compute(transaction: Transaction): TransactionHash { - let serializer = new ProtoSerializer(); - let buffer = serializer.serializeTransaction(transaction); - let hash = createTransactionHasher(TRANSACTION_HASH_LENGTH) - .update(buffer) - .digest("hex"); - return new TransactionHash(hash); - } -} + constructor(hash: string) { + super(hash); + } + /** + * Legacy method, use "TransactionComputer.computeTransactionHash()" instead. + * Computes the hash of a transaction. + */ + static compute(transaction: Transaction): TransactionHash { + const computer = new TransactionComputer(); + const hash = computer.computeTransactionHash(transaction); + return new TransactionHash(Buffer.from(hash).toString("hex")); + } +} diff --git a/src/transactionComputer.ts b/src/transactionComputer.ts new file mode 100644 index 000000000..d4117a393 --- /dev/null +++ b/src/transactionComputer.ts @@ -0,0 +1,148 @@ +import { INetworkConfig } from "./interfaceOfNetwork"; +import * as errors from "./errors"; +import BigNumber from "bignumber.js"; +import { ITransaction } from "./interface"; +import { ProtoSerializer } from "./proto"; +import { Transaction } from "./transaction"; +import { + BECH32_ADDRESS_LENGTH, + MIN_TRANSACTION_VERSION_THAT_SUPPORTS_OPTIONS, + TRANSACTION_OPTIONS_TX_GUARDED, + TRANSACTION_OPTIONS_TX_HASH_SIGN, +} from "./constants"; + +const createTransactionHasher = require("blake2b"); +const createKeccakHash = require("keccak"); +const TRANSACTION_HASH_LENGTH = 32; + +/** + * An utilitary class meant to work together with the {@link Transaction} class. + */ +export class TransactionComputer { + constructor() {} + + computeTransactionFee( + transaction: { gasPrice: bigint; gasLimit: bigint; data: Uint8Array }, + networkConfig: INetworkConfig, + ): bigint { + const moveBalanceGas = BigInt( + networkConfig.MinGasLimit + transaction.data.length * networkConfig.GasPerDataByte, + ); + if (moveBalanceGas > transaction.gasLimit) { + throw new errors.ErrNotEnoughGas(parseInt(transaction.gasLimit.toString(), 10)); + } + + const gasPrice = transaction.gasPrice; + const feeForMove = moveBalanceGas * gasPrice; + if (moveBalanceGas === transaction.gasLimit) { + return feeForMove; + } + + const diff = transaction.gasLimit - moveBalanceGas; + const modifiedGasPrice = BigInt( + new BigNumber(gasPrice.toString()).multipliedBy(new BigNumber(networkConfig.GasPriceModifier)).toFixed(0), + ); + const processingFee = diff * modifiedGasPrice; + + return feeForMove + processingFee; + } + + computeBytesForSigning(transaction: ITransaction): Uint8Array { + this.ensureValidTransactionFields(transaction); + + const plainTransaction = this.toPlainObjectForSigning(transaction); + const serialized = JSON.stringify(plainTransaction); + return new Uint8Array(Buffer.from(serialized)); + } + + computeBytesForVerifying(transaction: ITransaction): Uint8Array { + const isTxSignedByHash = this.hasOptionsSetForHashSigning(transaction); + + if (isTxSignedByHash) { + return this.computeHashForSigning(transaction); + } + return this.computeBytesForSigning(transaction); + } + + computeHashForSigning(transaction: ITransaction): Uint8Array { + const plainTransaction = this.toPlainObjectForSigning(transaction); + const signable = Buffer.from(JSON.stringify(plainTransaction)); + return createKeccakHash("keccak256").update(signable).digest(); + } + + computeTransactionHash(transaction: ITransaction): Uint8Array { + const serializer = new ProtoSerializer(); + const buffer = serializer.serializeTransaction(new Transaction(transaction)); + const hash = createTransactionHasher(TRANSACTION_HASH_LENGTH).update(buffer).digest("hex"); + + return Buffer.from(hash, "hex"); + } + + hasOptionsSetForGuardedTransaction(transaction: ITransaction): boolean { + return (transaction.options & TRANSACTION_OPTIONS_TX_GUARDED) == TRANSACTION_OPTIONS_TX_GUARDED; + } + + hasOptionsSetForHashSigning(transaction: ITransaction): boolean { + return (transaction.options & TRANSACTION_OPTIONS_TX_HASH_SIGN) == TRANSACTION_OPTIONS_TX_HASH_SIGN; + } + + applyGuardian(transaction: ITransaction, guardian: string) { + if (transaction.version < MIN_TRANSACTION_VERSION_THAT_SUPPORTS_OPTIONS) { + transaction.version = MIN_TRANSACTION_VERSION_THAT_SUPPORTS_OPTIONS; + } + + transaction.options = transaction.options | TRANSACTION_OPTIONS_TX_GUARDED; + transaction.guardian = guardian; + } + + applyOptionsForHashSigning(transaction: ITransaction) { + if (transaction.version < MIN_TRANSACTION_VERSION_THAT_SUPPORTS_OPTIONS) { + transaction.version = MIN_TRANSACTION_VERSION_THAT_SUPPORTS_OPTIONS; + } + transaction.options = transaction.options | TRANSACTION_OPTIONS_TX_HASH_SIGN; + } + + private toPlainObjectForSigning(transaction: ITransaction) { + return { + nonce: Number(transaction.nonce), + value: transaction.value.toString(), + receiver: transaction.receiver, + sender: transaction.sender, + senderUsername: this.toBase64OrUndefined(transaction.senderUsername), + receiverUsername: this.toBase64OrUndefined(transaction.receiverUsername), + gasPrice: Number(transaction.gasPrice), + gasLimit: Number(transaction.gasLimit), + data: this.toBase64OrUndefined(transaction.data), + chainID: transaction.chainID, + version: transaction.version, + options: transaction.options ? transaction.options : undefined, + guardian: transaction.guardian ? transaction.guardian : undefined, + }; + } + + private toBase64OrUndefined(value?: string | Uint8Array) { + return value && value.length ? Buffer.from(value).toString("base64") : undefined; + } + + private ensureValidTransactionFields(transaction: ITransaction) { + if (transaction.sender.length !== BECH32_ADDRESS_LENGTH) { + throw new errors.ErrBadUsage("Invalid `sender` field. Should be the bech32 address of the sender."); + } + + if (transaction.receiver.length !== BECH32_ADDRESS_LENGTH) { + throw new errors.ErrBadUsage("Invalid `receiver` field. Should be the bech32 address of the receiver."); + } + + if (!transaction.chainID.length) { + throw new errors.ErrBadUsage("The `chainID` field is not set"); + } + + if (transaction.version < MIN_TRANSACTION_VERSION_THAT_SUPPORTS_OPTIONS) { + if (this.hasOptionsSetForGuardedTransaction(transaction) || this.hasOptionsSetForHashSigning(transaction)) { + throw new errors.ErrBadUsage( + `Non-empty transaction options requires transaction version >= ${MIN_TRANSACTION_VERSION_THAT_SUPPORTS_OPTIONS}`, + ); + } + } + } +} diff --git a/src/transactionPayload.ts b/src/transactionPayload.ts index f8ef13a71..8cd07ae29 100644 --- a/src/transactionPayload.ts +++ b/src/transactionPayload.ts @@ -1,5 +1,3 @@ -import { ContractCallPayloadBuilder, ContractDeployPayloadBuilder, ContractUpgradePayloadBuilder } from "./smartcontracts/transactionPayloadBuilders"; - /** * The "data" field of a Transaction, as an immutable object. */ diff --git a/src/transactionWatcher.spec.ts b/src/transactionWatcher.spec.ts index 92646cc47..7d9574200 100644 --- a/src/transactionWatcher.spec.ts +++ b/src/transactionWatcher.spec.ts @@ -1,14 +1,37 @@ import { TransactionOnNetwork, TransactionStatus } from "@multiversx/sdk-network-providers"; import { assert } from "chai"; -import { MarkCompleted, MockProvider, Wait } from "./testutils"; +import { MarkCompleted, MockNetworkProvider, Wait } from "./testutils"; import { TransactionHash } from "./transaction"; import { TransactionWatcher } from "./transactionWatcher"; describe("test transactionWatcher", () => { - it("should await status == executed", async () => { - let hash = new TransactionHash("abba"); - let provider = new MockProvider(); + it("should await status == executed using hash", async () => { + let hash = new TransactionHash("abbaabbaabbaabbaabbaabbaabbaabbaabbaabbaabbaabbaabbaabbaabbaabba"); + let provider = new MockNetworkProvider(); + let watcher = new TransactionWatcher(provider, { + pollingIntervalMilliseconds: 42, + timeoutMilliseconds: 42 * 42 + }); + let dummyTransaction = { + getHash: () => hash + } + + provider.mockPutTransaction(hash, new TransactionOnNetwork({ + status: new TransactionStatus("unknown") + })); + + await Promise.all([ + provider.mockTransactionTimelineByHash(hash, [new Wait(40), new TransactionStatus("pending"), new Wait(40), new TransactionStatus("executed"), new MarkCompleted()]), + watcher.awaitCompleted(dummyTransaction.getHash().hex()) + ]); + + assert.isTrue((await provider.getTransactionStatus(hash.hex())).isExecuted()); + }); + + it("should await status == executed using transaction", async () => { + let hash = new TransactionHash("abbaabbaabbaabbaabbaabbaabbaabbaabbaabbaabbaabbaabbaabbaabbaabba"); + let provider = new MockNetworkProvider(); let watcher = new TransactionWatcher(provider, { pollingIntervalMilliseconds: 42, timeoutMilliseconds: 42 * 42 diff --git a/src/transactionWatcher.ts b/src/transactionWatcher.ts index b2fa2ec21..ae077b38b 100644 --- a/src/transactionWatcher.ts +++ b/src/transactionWatcher.ts @@ -1,5 +1,11 @@ import { AsyncTimer } from "./asyncTimer"; -import { Err, ErrExpectedTransactionEventsNotFound, ErrExpectedTransactionStatusNotReached, ErrIsCompletedFieldIsMissingOnTransaction } from "./errors"; +import { HEX_TRANSACTION_HASH_LENGTH } from "./constants"; +import { + Err, + ErrExpectedTransactionEventsNotFound, + ErrExpectedTransactionStatusNotReached, + ErrIsCompletedFieldIsMissingOnTransaction, +} from "./errors"; import { ITransactionFetcher } from "./interface"; import { ITransactionEvent, ITransactionOnNetwork, ITransactionStatus } from "./interfaceOfNetwork"; import { Logger } from "./logger"; @@ -10,7 +16,7 @@ export type PredicateIsAwaitedStatus = (status: ITransactionStatus) => boolean; * Internal interface: a transaction, as seen from the perspective of a {@link TransactionWatcher}. */ interface ITransaction { - getHash(): { hex(): string; } + getHash(): { hex(): string }; } /** @@ -21,7 +27,7 @@ export class TransactionWatcher { static DefaultTimeout: number = TransactionWatcher.DefaultPollingInterval * 15; static DefaultPatience: number = 0; - static NoopOnStatusReceived = (_: ITransactionStatus) => { }; + static NoopOnStatusReceived = (_: ITransactionStatus) => {}; protected readonly fetcher: ITransactionFetcher; protected readonly pollingIntervalMilliseconds: number; @@ -30,7 +36,7 @@ export class TransactionWatcher { /** * A transaction watcher (awaiter). - * + * * @param fetcher The transaction fetcher * @param options The options * @param options.pollingIntervalMilliseconds The polling interval, in milliseconds @@ -40,101 +46,126 @@ export class TransactionWatcher { constructor( fetcher: ITransactionFetcher, options: { - pollingIntervalMilliseconds?: number, - timeoutMilliseconds?: number, - patienceMilliseconds?: number - } = {}) { + pollingIntervalMilliseconds?: number; + timeoutMilliseconds?: number; + patienceMilliseconds?: number; + } = {}, + ) { this.fetcher = new TransactionFetcherWithTracing(fetcher); - this.pollingIntervalMilliseconds = options.pollingIntervalMilliseconds || TransactionWatcher.DefaultPollingInterval; + this.pollingIntervalMilliseconds = + options.pollingIntervalMilliseconds || TransactionWatcher.DefaultPollingInterval; this.timeoutMilliseconds = options.timeoutMilliseconds || TransactionWatcher.DefaultTimeout; this.patienceMilliseconds = options.patienceMilliseconds || TransactionWatcher.DefaultPatience; } /** * Waits until the transaction reaches the "pending" status. + * @param txHash The hex-encoded transaction hash */ - public async awaitPending(transaction: ITransaction): Promise { + public async awaitPending(transactionOrTxHash: ITransaction | string): Promise { const isPending = (transaction: ITransactionOnNetwork) => transaction.status.isPending(); - const doFetch = async () => await this.fetcher.getTransaction(transaction.getHash().hex()); + const doFetch = async () => { + const hash = this.transactionOrTxHashToTxHash(transactionOrTxHash); + return await this.fetcher.getTransaction(hash); + }; const errorProvider = () => new ErrExpectedTransactionStatusNotReached(); - return this.awaitConditionally( - isPending, - doFetch, - errorProvider - ); + return this.awaitConditionally(isPending, doFetch, errorProvider); } /** - * Waits until the transaction is completely processed. - */ - public async awaitCompleted(transaction: ITransaction): Promise { + * Waits until the transaction is completely processed. + * @param txHash The hex-encoded transaction hash + */ + public async awaitCompleted(transactionOrTxHash: ITransaction | string): Promise { const isCompleted = (transactionOnNetwork: ITransactionOnNetwork) => { if (transactionOnNetwork.isCompleted === undefined) { throw new ErrIsCompletedFieldIsMissingOnTransaction(); } - return transactionOnNetwork.isCompleted + return transactionOnNetwork.isCompleted; }; - const doFetch = async () => await this.fetcher.getTransaction(transaction.getHash().hex()); + const doFetch = async () => { + const hash = this.transactionOrTxHashToTxHash(transactionOrTxHash); + return await this.fetcher.getTransaction(hash); + }; const errorProvider = () => new ErrExpectedTransactionStatusNotReached(); - return this.awaitConditionally( - isCompleted, - doFetch, - errorProvider - ); + return this.awaitConditionally(isCompleted, doFetch, errorProvider); } - public async awaitAllEvents(transaction: ITransaction, events: string[]): Promise { + public async awaitAllEvents( + transactionOrTxHash: ITransaction | string, + events: string[], + ): Promise { const foundAllEvents = (transactionOnNetwork: ITransactionOnNetwork) => { - const allEventIdentifiers = this.getAllTransactionEvents(transactionOnNetwork).map(event => event.identifier); - const allAreFound = events.every(event => allEventIdentifiers.includes(event)); + const allEventIdentifiers = this.getAllTransactionEvents(transactionOnNetwork).map( + (event) => event.identifier, + ); + const allAreFound = events.every((event) => allEventIdentifiers.includes(event)); return allAreFound; }; - const doFetch = async () => await this.fetcher.getTransaction(transaction.getHash().hex()); + const doFetch = async () => { + const hash = this.transactionOrTxHashToTxHash(transactionOrTxHash); + return await this.fetcher.getTransaction(hash); + }; const errorProvider = () => new ErrExpectedTransactionEventsNotFound(); - return this.awaitConditionally( - foundAllEvents, - doFetch, - errorProvider - ); + return this.awaitConditionally(foundAllEvents, doFetch, errorProvider); } - public async awaitAnyEvent(transaction: ITransaction, events: string[]): Promise { + public async awaitAnyEvent( + transactionOrTxHash: ITransaction | string, + events: string[], + ): Promise { const foundAnyEvent = (transactionOnNetwork: ITransactionOnNetwork) => { - const allEventIdentifiers = this.getAllTransactionEvents(transactionOnNetwork).map(event => event.identifier); - const anyIsFound = events.find(event => allEventIdentifiers.includes(event)) != undefined; + const allEventIdentifiers = this.getAllTransactionEvents(transactionOnNetwork).map( + (event) => event.identifier, + ); + const anyIsFound = events.find((event) => allEventIdentifiers.includes(event)) != undefined; return anyIsFound; }; - const doFetch = async () => await this.fetcher.getTransaction(transaction.getHash().hex()); + const doFetch = async () => { + const hash = this.transactionOrTxHashToTxHash(transactionOrTxHash); + return await this.fetcher.getTransaction(hash); + }; const errorProvider = () => new ErrExpectedTransactionEventsNotFound(); - return this.awaitConditionally( - foundAnyEvent, - doFetch, - errorProvider - ); + return this.awaitConditionally(foundAnyEvent, doFetch, errorProvider); } - public async awaitOnCondition(transaction: ITransaction, condition: (data: ITransactionOnNetwork) => boolean): Promise { - const doFetch = async () => await this.fetcher.getTransaction(transaction.getHash().hex()); + public async awaitOnCondition( + transactionOrTxHash: ITransaction | string, + condition: (data: ITransactionOnNetwork) => boolean, + ): Promise { + const doFetch = async () => { + const hash = this.transactionOrTxHashToTxHash(transactionOrTxHash); + return await this.fetcher.getTransaction(hash); + }; const errorProvider = () => new ErrExpectedTransactionStatusNotReached(); - return this.awaitConditionally( - condition, - doFetch, - errorProvider - ); + return this.awaitConditionally(condition, doFetch, errorProvider); + } + + private transactionOrTxHashToTxHash(transactionOrTxHash: ITransaction | string): string { + const hash = + typeof transactionOrTxHash === "string" ? transactionOrTxHash : transactionOrTxHash.getHash().hex(); + + if (hash.length !== HEX_TRANSACTION_HASH_LENGTH) { + throw new Err( + `Invalid transaction hash length. The length of a hex encoded hash should be ${HEX_TRANSACTION_HASH_LENGTH}.`, + ); + } + + return hash; } protected async awaitConditionally( isSatisfied: (data: TData) => boolean, doFetch: () => Promise, - createError: () => Err + createError: () => Err, ): Promise { const periodicTimer = new AsyncTimer("watcher:periodic"); const patienceTimer = new AsyncTimer("watcher:patience"); diff --git a/src/transactionsFactories/delegationTransactionsFactory.spec.ts b/src/transactionsFactories/delegationTransactionsFactory.spec.ts new file mode 100644 index 000000000..c76d1917c --- /dev/null +++ b/src/transactionsFactories/delegationTransactionsFactory.spec.ts @@ -0,0 +1,306 @@ +import { ValidatorPublicKey } from "@multiversx/sdk-wallet"; +import { assert } from "chai"; +import { Address } from "../address"; +import { DELEGATION_MANAGER_SC_ADDRESS } from "../constants"; +import { DelegationTransactionsFactory } from "./delegationTransactionsFactory"; +import { TransactionsFactoryConfig } from "./transactionsFactoryConfig"; + +describe("test delegation transactions factory", function () { + const config = new TransactionsFactoryConfig({ chainID: "D" }); + const delegationFactory = new DelegationTransactionsFactory({ config: config }); + + it("should create 'Transaction' for new delegation contract", async function () { + const sender = Address.fromBech32("erd18s6a06ktr2v6fgxv4ffhauxvptssnaqlds45qgsrucemlwc8rawq553rt2"); + const delagationCap = 5000000000000000000000n; + const serviceFee = 10n; + const value = 1250000000000000000000n; + + const transaction = delegationFactory.createTransactionForNewDelegationContract({ + sender: sender, + totalDelegationCap: delagationCap, + serviceFee: serviceFee, + amount: value, + }); + + assert.equal(transaction.sender, "erd18s6a06ktr2v6fgxv4ffhauxvptssnaqlds45qgsrucemlwc8rawq553rt2"); + assert.equal(transaction.receiver, DELEGATION_MANAGER_SC_ADDRESS); + assert.isDefined(transaction.data); + assert.deepEqual(transaction.data, Buffer.from("createNewDelegationContract@010f0cf064dd59200000@0a")); + assert.equal(transaction.gasLimit, 60126500n); + assert.equal(transaction.value, value); + assert.equal(transaction.chainID, config.chainID); + }); + + it("should create 'Transaction' for adding nodes", async function () { + const sender = Address.fromBech32("erd18s6a06ktr2v6fgxv4ffhauxvptssnaqlds45qgsrucemlwc8rawq553rt2"); + const delegationContract = Address.fromBech32("erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqtllllls002zgc"); + const publicKey = new ValidatorPublicKey( + Buffer.from( + "e7beaa95b3877f47348df4dd1cb578a4f7cabf7a20bfeefe5cdd263878ff132b765e04fef6f40c93512b666c47ed7719b8902f6c922c04247989b7137e837cc81a62e54712471c97a2ddab75aa9c2f58f813ed4c0fa722bde0ab718bff382208", + "hex", + ), + ); + + const mockMessage = { + getSignature: () => + Buffer.from( + "81109fa1c8d3dc7b6c2d6e65206cc0bc1a83c9b2d1eb91a601d66ad32def430827d5eb52917bd2b0d04ce195738db216", + "hex", + ), + }; + + const transaction = delegationFactory.createTransactionForAddingNodes({ + sender: sender, + delegationContract: delegationContract, + publicKeys: [publicKey], + signedMessages: [mockMessage.getSignature()], + }); + + assert.equal(transaction.sender, "erd18s6a06ktr2v6fgxv4ffhauxvptssnaqlds45qgsrucemlwc8rawq553rt2"); + assert.equal(transaction.receiver, "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqtllllls002zgc"); + assert.isDefined(transaction.data); + assert.deepEqual( + transaction.data, + Buffer.from( + "addNodes@e7beaa95b3877f47348df4dd1cb578a4f7cabf7a20bfeefe5cdd263878ff132b765e04fef6f40c93512b666c47ed7719b8902f6c922c04247989b7137e837cc81a62e54712471c97a2ddab75aa9c2f58f813ed4c0fa722bde0ab718bff382208@81109fa1c8d3dc7b6c2d6e65206cc0bc1a83c9b2d1eb91a601d66ad32def430827d5eb52917bd2b0d04ce195738db216", + ), + ); + assert.equal(transaction.value, 0n); + }); + + it("should create 'Transaction' for removing nodes", async function () { + const sender = Address.fromBech32("erd18s6a06ktr2v6fgxv4ffhauxvptssnaqlds45qgsrucemlwc8rawq553rt2"); + const delegationContract = Address.fromBech32("erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqtllllls002zgc"); + + const publicKey = { + hex(): string { + return Buffer.from("abba").toString("hex"); + }, + }; + + const transaction = delegationFactory.createTransactionForRemovingNodes({ + sender: sender, + delegationContract: delegationContract, + publicKeys: [publicKey], + }); + + assert.equal(transaction.sender, "erd18s6a06ktr2v6fgxv4ffhauxvptssnaqlds45qgsrucemlwc8rawq553rt2"); + assert.equal(transaction.receiver, "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqtllllls002zgc"); + assert.isDefined(transaction.data); + assert.deepEqual(transaction.data, Buffer.from("removeNodes@61626261")); + assert.equal(transaction.value, 0n); + }); + + it("should create 'Transaction' for staking nodes", async function () { + const sender = Address.fromBech32("erd18s6a06ktr2v6fgxv4ffhauxvptssnaqlds45qgsrucemlwc8rawq553rt2"); + const delegationContract = Address.fromBech32("erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqtllllls002zgc"); + + const publicKey = { + hex(): string { + return Buffer.from("abba").toString("hex"); + }, + }; + + const transaction = delegationFactory.createTransactionForStakingNodes({ + sender: sender, + delegationContract: delegationContract, + publicKeys: [publicKey], + }); + + assert.equal(transaction.sender, "erd18s6a06ktr2v6fgxv4ffhauxvptssnaqlds45qgsrucemlwc8rawq553rt2"); + assert.equal(transaction.receiver, "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqtllllls002zgc"); + assert.isDefined(transaction.data); + assert.deepEqual(transaction.data, Buffer.from("stakeNodes@61626261")); + assert.equal(transaction.value, 0n); + }); + + it("should create 'Transaction' for unbonding nodes", async function () { + const sender = Address.fromBech32("erd18s6a06ktr2v6fgxv4ffhauxvptssnaqlds45qgsrucemlwc8rawq553rt2"); + const delegationContract = Address.fromBech32("erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqtllllls002zgc"); + + const publicKey = { + hex(): string { + return Buffer.from("abba").toString("hex"); + }, + }; + + const transaction = delegationFactory.createTransactionForUnbondingNodes({ + sender: sender, + delegationContract: delegationContract, + publicKeys: [publicKey], + }); + + assert.equal(transaction.sender, "erd18s6a06ktr2v6fgxv4ffhauxvptssnaqlds45qgsrucemlwc8rawq553rt2"); + assert.equal(transaction.receiver, "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqtllllls002zgc"); + assert.isDefined(transaction.data); + assert.deepEqual(transaction.data, Buffer.from("unBondNodes@61626261")); + assert.equal(transaction.value, 0n); + assert.equal(transaction.gasLimit, 12080000n); + }); + + it("should create 'Transaction' for unstaking nodes", async function () { + const sender = Address.fromBech32("erd18s6a06ktr2v6fgxv4ffhauxvptssnaqlds45qgsrucemlwc8rawq553rt2"); + const delegationContract = Address.fromBech32("erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqtllllls002zgc"); + + const publicKey = { + hex(): string { + return Buffer.from("abba").toString("hex"); + }, + }; + + const transaction = delegationFactory.createTransactionForUnstakingNodes({ + sender: sender, + delegationContract: delegationContract, + publicKeys: [publicKey], + }); + + assert.equal(transaction.sender, "erd18s6a06ktr2v6fgxv4ffhauxvptssnaqlds45qgsrucemlwc8rawq553rt2"); + assert.equal(transaction.receiver, "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqtllllls002zgc"); + assert.isDefined(transaction.data); + assert.deepEqual(transaction.data, Buffer.from("unStakeNodes@61626261")); + assert.equal(transaction.value, 0n); + assert.equal(transaction.gasLimit, 12081500n); + }); + + it("should create 'Transaction' for unjailing nodes", async function () { + const sender = Address.fromBech32("erd18s6a06ktr2v6fgxv4ffhauxvptssnaqlds45qgsrucemlwc8rawq553rt2"); + const delegationContract = Address.fromBech32("erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqtllllls002zgc"); + + const publicKey = { + hex(): string { + return Buffer.from("abba").toString("hex"); + }, + }; + + const transaction = delegationFactory.createTransactionForUnjailingNodes({ + sender: sender, + delegationContract: delegationContract, + publicKeys: [publicKey], + }); + + assert.equal(transaction.sender, "erd18s6a06ktr2v6fgxv4ffhauxvptssnaqlds45qgsrucemlwc8rawq553rt2"); + assert.equal(transaction.receiver, "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqtllllls002zgc"); + assert.isDefined(transaction.data); + assert.deepEqual(transaction.data, Buffer.from("unJailNodes@61626261")); + assert.equal(transaction.value, 0n); + }); + + it("should create 'Transaction' for changing service fee", async function () { + const sender = Address.fromBech32("erd18s6a06ktr2v6fgxv4ffhauxvptssnaqlds45qgsrucemlwc8rawq553rt2"); + const delegationContract = Address.fromBech32("erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqtllllls002zgc"); + const serviceFee = 10n; + + const transaction = delegationFactory.createTransactionForChangingServiceFee({ + sender: sender, + delegationContract: delegationContract, + serviceFee: serviceFee, + }); + + assert.equal(transaction.sender, "erd18s6a06ktr2v6fgxv4ffhauxvptssnaqlds45qgsrucemlwc8rawq553rt2"); + assert.equal(transaction.receiver, "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqtllllls002zgc"); + assert.isDefined(transaction.data); + assert.deepEqual(transaction.data, Buffer.from("changeServiceFee@0a")); + assert.equal(transaction.value, 0n); + }); + + it("should create 'Transaction' for changing delegation cap", async function () { + const sender = Address.fromBech32("erd18s6a06ktr2v6fgxv4ffhauxvptssnaqlds45qgsrucemlwc8rawq553rt2"); + const delegationContract = Address.fromBech32("erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqtllllls002zgc"); + const delegationCap = 5000000000000000000000n; + + const transaction = delegationFactory.createTransactionForModifyingDelegationCap({ + sender: sender, + delegationContract: delegationContract, + delegationCap: delegationCap, + }); + + assert.equal(transaction.sender, "erd18s6a06ktr2v6fgxv4ffhauxvptssnaqlds45qgsrucemlwc8rawq553rt2"); + assert.equal(transaction.receiver, "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqtllllls002zgc"); + assert.isDefined(transaction.data); + assert.deepEqual(transaction.data, Buffer.from("modifyTotalDelegationCap@010f0cf064dd59200000")); + assert.equal(transaction.value, 0n); + }); + + it("should create 'Transaction' for setting automatic activation", async function () { + const sender = Address.fromBech32("erd18s6a06ktr2v6fgxv4ffhauxvptssnaqlds45qgsrucemlwc8rawq553rt2"); + const delegationContract = Address.fromBech32("erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqtllllls002zgc"); + + const transaction = delegationFactory.createTransactionForSettingAutomaticActivation({ + sender: sender, + delegationContract: delegationContract, + }); + + assert.equal(transaction.sender, "erd18s6a06ktr2v6fgxv4ffhauxvptssnaqlds45qgsrucemlwc8rawq553rt2"); + assert.equal(transaction.receiver, "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqtllllls002zgc"); + assert.isDefined(transaction.data); + assert.deepEqual(transaction.data, Buffer.from("setAutomaticActivation@74727565")); + assert.equal(transaction.value, 0n); + }); + + it("should create 'Transaction' for unsetting automatic activation", async function () { + const sender = Address.fromBech32("erd18s6a06ktr2v6fgxv4ffhauxvptssnaqlds45qgsrucemlwc8rawq553rt2"); + const delegationContract = Address.fromBech32("erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqtllllls002zgc"); + + const transaction = delegationFactory.createTransactionForUnsettingAutomaticActivation({ + sender: sender, + delegationContract: delegationContract, + }); + + assert.equal(transaction.sender, "erd18s6a06ktr2v6fgxv4ffhauxvptssnaqlds45qgsrucemlwc8rawq553rt2"); + assert.equal(transaction.receiver, "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqtllllls002zgc"); + assert.isDefined(transaction.data); + assert.deepEqual(transaction.data, Buffer.from("setAutomaticActivation@66616c7365")); + assert.equal(transaction.value, 0n); + }); + + it("should create 'Transaction' for setting cap check on redelegate rewards", async function () { + const sender = Address.fromBech32("erd18s6a06ktr2v6fgxv4ffhauxvptssnaqlds45qgsrucemlwc8rawq553rt2"); + const delegationContract = Address.fromBech32("erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqtllllls002zgc"); + + const transaction = delegationFactory.createTransactionForSettingCapCheckOnRedelegateRewards({ + sender: sender, + delegationContract: delegationContract, + }); + + assert.equal(transaction.sender, "erd18s6a06ktr2v6fgxv4ffhauxvptssnaqlds45qgsrucemlwc8rawq553rt2"); + assert.equal(transaction.receiver, "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqtllllls002zgc"); + assert.isDefined(transaction.data); + assert.deepEqual(transaction.data, Buffer.from("setCheckCapOnReDelegateRewards@74727565")); + assert.equal(transaction.value, 0n); + }); + + it("should create 'Transaction' for unsetting cap check on redelegate rewards", async function () { + const sender = Address.fromBech32("erd18s6a06ktr2v6fgxv4ffhauxvptssnaqlds45qgsrucemlwc8rawq553rt2"); + const delegationContract = Address.fromBech32("erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqtllllls002zgc"); + + const transaction = delegationFactory.createTransactionForUnsettingCapCheckOnRedelegateRewards({ + sender: sender, + delegationContract: delegationContract, + }); + + assert.equal(transaction.sender, "erd18s6a06ktr2v6fgxv4ffhauxvptssnaqlds45qgsrucemlwc8rawq553rt2"); + assert.equal(transaction.receiver, "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqtllllls002zgc"); + assert.isDefined(transaction.data); + assert.deepEqual(transaction.data, Buffer.from("setCheckCapOnReDelegateRewards@66616c7365")); + assert.equal(transaction.value, 0n); + }); + + it("should create 'Transaction' for setting metadata", async function () { + const sender = Address.fromBech32("erd18s6a06ktr2v6fgxv4ffhauxvptssnaqlds45qgsrucemlwc8rawq553rt2"); + const delegationContract = Address.fromBech32("erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqtllllls002zgc"); + + const transaction = delegationFactory.createTransactionForSettingMetadata({ + sender: sender, + delegationContract: delegationContract, + name: "name", + website: "website", + identifier: "identifier", + }); + + assert.equal(transaction.sender, "erd18s6a06ktr2v6fgxv4ffhauxvptssnaqlds45qgsrucemlwc8rawq553rt2"); + assert.equal(transaction.receiver, "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqtllllls002zgc"); + assert.isDefined(transaction.data); + assert.deepEqual(transaction.data, Buffer.from("setMetaData@6e616d65@77656273697465@6964656e746966696572")); + assert.equal(transaction.value, 0n); + }); +}); diff --git a/src/transactionsFactories/delegationTransactionsFactory.ts b/src/transactionsFactories/delegationTransactionsFactory.ts new file mode 100644 index 000000000..bc1369b52 --- /dev/null +++ b/src/transactionsFactories/delegationTransactionsFactory.ts @@ -0,0 +1,379 @@ +import { Address } from "../address"; +import { DELEGATION_MANAGER_SC_ADDRESS } from "../constants"; +import { Err } from "../errors"; +import { IAddress } from "../interface"; +import { ArgSerializer, BigUIntValue, BytesValue, StringValue } from "../smartcontracts"; +import { Transaction } from "../transaction"; +import { TransactionBuilder } from "./transactionBuilder"; + +interface Config { + chainID: string; + minGasLimit: bigint; + gasLimitPerByte: bigint; + gasLimitStake: bigint; + gasLimitUnstake: bigint; + gasLimitUnbond: bigint; + gasLimitCreateDelegationContract: bigint; + gasLimitDelegationOperations: bigint; + additionalGasLimitPerValidatorNode: bigint; + additionalGasLimitForDelegationOperations: bigint; +} + +interface IValidatorPublicKey { + hex(): string; +} + +/** + * Use this class to create delegation related transactions like creating a new delegation contract or adding nodes. + */ +export class DelegationTransactionsFactory { + private readonly config: Config; + private readonly argSerializer: ArgSerializer; + + constructor(options: { config: Config }) { + this.config = options.config; + this.argSerializer = new ArgSerializer(); + } + + createTransactionForNewDelegationContract(options: { + sender: IAddress; + totalDelegationCap: bigint; + serviceFee: bigint; + amount: bigint; + }): Transaction { + const dataParts = [ + "createNewDelegationContract", + ...this.argSerializer.valuesToStrings([ + new BigUIntValue(options.totalDelegationCap), + new BigUIntValue(options.serviceFee), + ]), + ]; + + const executionGasLimit = + this.config.gasLimitCreateDelegationContract + this.config.additionalGasLimitForDelegationOperations; + + return new TransactionBuilder({ + config: this.config, + sender: options.sender, + receiver: Address.fromBech32(DELEGATION_MANAGER_SC_ADDRESS), + dataParts: dataParts, + gasLimit: executionGasLimit, + addDataMovementGas: true, + amount: options.amount, + }).build(); + } + + createTransactionForAddingNodes(options: { + sender: IAddress; + delegationContract: IAddress; + publicKeys: IValidatorPublicKey[]; + signedMessages: Uint8Array[]; + }): Transaction { + if (options.publicKeys.length !== options.signedMessages.length) { + throw new Err("The number of public keys should match the number of signed messages"); + } + + const signedMessagesAsTypedValues = options.signedMessages.map( + (message) => new BytesValue(Buffer.from(message)), + ); + const messagesAsStrings = this.argSerializer.valuesToStrings(signedMessagesAsTypedValues); + + const numNodes = options.publicKeys.length; + const dataParts = ["addNodes"]; + + for (let i = 0; i < numNodes; i++) { + dataParts.push(...[options.publicKeys[i].hex(), messagesAsStrings[i]]); + } + + return new TransactionBuilder({ + config: this.config, + sender: options.sender, + receiver: options.delegationContract, + dataParts: dataParts, + gasLimit: this.computeExecutionGasLimitForNodesManagement(numNodes), + addDataMovementGas: true, + }).build(); + } + + createTransactionForRemovingNodes(options: { + sender: IAddress; + delegationContract: IAddress; + publicKeys: IValidatorPublicKey[]; + }): Transaction { + const dataParts = ["removeNodes"]; + + for (const key of options.publicKeys) { + dataParts.push(key.hex()); + } + + const numNodes = options.publicKeys.length; + return new TransactionBuilder({ + config: this.config, + sender: options.sender, + receiver: options.delegationContract, + dataParts: dataParts, + gasLimit: this.computeExecutionGasLimitForNodesManagement(numNodes), + addDataMovementGas: true, + }).build(); + } + + createTransactionForStakingNodes(options: { + sender: IAddress; + delegationContract: IAddress; + publicKeys: IValidatorPublicKey[]; + }): Transaction { + let dataParts = ["stakeNodes"]; + + for (const key of options.publicKeys) { + dataParts = dataParts.concat(key.hex()); + } + + const numNodes = options.publicKeys.length; + const additionalGasForAllNodes = BigInt(numNodes) * this.config.additionalGasLimitPerValidatorNode; + + const executionGasLimit = + additionalGasForAllNodes + this.config.gasLimitStake + this.config.gasLimitDelegationOperations; + + return new TransactionBuilder({ + config: this.config, + sender: options.sender, + receiver: options.delegationContract, + dataParts: dataParts, + gasLimit: executionGasLimit, + addDataMovementGas: true, + }).build(); + } + + createTransactionForUnbondingNodes(options: { + sender: IAddress; + delegationContract: IAddress; + publicKeys: IValidatorPublicKey[]; + }): Transaction { + let dataParts = ["unBondNodes"]; + + for (const key of options.publicKeys) { + dataParts = dataParts.concat(key.hex()); + } + + const numNodes = options.publicKeys.length; + const executionGasLimit = + BigInt(numNodes) * this.config.additionalGasLimitPerValidatorNode + + this.config.gasLimitUnbond + + this.config.gasLimitDelegationOperations; + + return new TransactionBuilder({ + config: this.config, + sender: options.sender, + receiver: options.delegationContract, + dataParts: dataParts, + gasLimit: executionGasLimit, + addDataMovementGas: true, + }).build(); + } + + createTransactionForUnstakingNodes(options: { + sender: IAddress; + delegationContract: IAddress; + publicKeys: IValidatorPublicKey[]; + }): Transaction { + let dataParts = ["unStakeNodes"]; + + for (const key of options.publicKeys) { + dataParts = dataParts.concat(key.hex()); + } + + const numNodes = options.publicKeys.length; + const executionGasLimit = + BigInt(numNodes) * this.config.additionalGasLimitPerValidatorNode + + this.config.gasLimitUnstake + + this.config.gasLimitDelegationOperations; + + return new TransactionBuilder({ + config: this.config, + sender: options.sender, + receiver: options.delegationContract, + dataParts: dataParts, + gasLimit: executionGasLimit, + addDataMovementGas: true, + }).build(); + } + + createTransactionForUnjailingNodes(options: { + sender: IAddress; + delegationContract: IAddress; + publicKeys: IValidatorPublicKey[]; + }): Transaction { + const dataParts = ["unJailNodes"]; + + for (const key of options.publicKeys) { + dataParts.push(key.hex()); + } + + const numNodes = options.publicKeys.length; + return new TransactionBuilder({ + config: this.config, + sender: options.sender, + receiver: options.delegationContract, + dataParts: dataParts, + gasLimit: this.computeExecutionGasLimitForNodesManagement(numNodes), + addDataMovementGas: true, + }).build(); + } + + createTransactionForChangingServiceFee(options: { + sender: IAddress; + delegationContract: IAddress; + serviceFee: bigint; + }): Transaction { + const dataParts = [ + "changeServiceFee", + this.argSerializer.valuesToStrings([new BigUIntValue(options.serviceFee)])[0], + ]; + const gasLimit = + this.config.gasLimitDelegationOperations + this.config.additionalGasLimitForDelegationOperations; + + return new TransactionBuilder({ + config: this.config, + sender: options.sender, + receiver: options.delegationContract, + dataParts: dataParts, + gasLimit: gasLimit, + addDataMovementGas: true, + }).build(); + } + + createTransactionForModifyingDelegationCap(options: { + sender: IAddress; + delegationContract: IAddress; + delegationCap: bigint; + }): Transaction { + const dataParts = [ + "modifyTotalDelegationCap", + this.argSerializer.valuesToStrings([new BigUIntValue(options.delegationCap)])[0], + ]; + const gasLimit = + this.config.gasLimitDelegationOperations + this.config.additionalGasLimitForDelegationOperations; + + return new TransactionBuilder({ + config: this.config, + sender: options.sender, + receiver: options.delegationContract, + dataParts: dataParts, + gasLimit: gasLimit, + addDataMovementGas: true, + }).build(); + } + + createTransactionForSettingAutomaticActivation(options: { + sender: IAddress; + delegationContract: IAddress; + }): Transaction { + const dataParts = ["setAutomaticActivation", this.argSerializer.valuesToStrings([new StringValue("true")])[0]]; + const gasLimit = + this.config.gasLimitDelegationOperations + this.config.additionalGasLimitForDelegationOperations; + + return new TransactionBuilder({ + config: this.config, + sender: options.sender, + receiver: options.delegationContract, + dataParts: dataParts, + gasLimit: gasLimit, + addDataMovementGas: true, + }).build(); + } + + createTransactionForUnsettingAutomaticActivation(options: { + sender: IAddress; + delegationContract: IAddress; + }): Transaction { + const dataParts = ["setAutomaticActivation", this.argSerializer.valuesToStrings([new StringValue("false")])[0]]; + const gasLimit = + this.config.gasLimitDelegationOperations + this.config.additionalGasLimitForDelegationOperations; + + return new TransactionBuilder({ + config: this.config, + sender: options.sender, + receiver: options.delegationContract, + dataParts: dataParts, + gasLimit: gasLimit, + addDataMovementGas: true, + }).build(); + } + + createTransactionForSettingCapCheckOnRedelegateRewards(options: { + sender: IAddress; + delegationContract: IAddress; + }): Transaction { + const dataParts = [ + "setCheckCapOnReDelegateRewards", + this.argSerializer.valuesToStrings([new StringValue("true")])[0], + ]; + const gasLimit = + this.config.gasLimitDelegationOperations + this.config.additionalGasLimitForDelegationOperations; + + return new TransactionBuilder({ + config: this.config, + sender: options.sender, + receiver: options.delegationContract, + dataParts: dataParts, + gasLimit: gasLimit, + addDataMovementGas: true, + }).build(); + } + + createTransactionForUnsettingCapCheckOnRedelegateRewards(options: { + sender: IAddress; + delegationContract: IAddress; + }): Transaction { + const dataParts = [ + "setCheckCapOnReDelegateRewards", + this.argSerializer.valuesToStrings([new StringValue("false")])[0], + ]; + const gasLimit = + this.config.gasLimitDelegationOperations + this.config.additionalGasLimitForDelegationOperations; + + return new TransactionBuilder({ + config: this.config, + sender: options.sender, + receiver: options.delegationContract, + dataParts: dataParts, + gasLimit: gasLimit, + addDataMovementGas: true, + }).build(); + } + + createTransactionForSettingMetadata(options: { + sender: IAddress; + delegationContract: IAddress; + name: string; + website: string; + identifier: string; + }): Transaction { + const dataParts = [ + "setMetaData", + ...this.argSerializer.valuesToStrings([ + new StringValue(options.name), + new StringValue(options.website), + new StringValue(options.identifier), + ]), + ]; + + const gasLimit = + this.config.gasLimitDelegationOperations + this.config.additionalGasLimitForDelegationOperations; + + return new TransactionBuilder({ + config: this.config, + sender: options.sender, + receiver: options.delegationContract, + dataParts: dataParts, + gasLimit: gasLimit, + addDataMovementGas: true, + }).build(); + } + + private computeExecutionGasLimitForNodesManagement(numNodes: number): bigint { + const additionalGasForAllNodes = this.config.additionalGasLimitPerValidatorNode * BigInt(numNodes); + + return this.config.gasLimitDelegationOperations + additionalGasForAllNodes; + } +} diff --git a/src/transactionsFactories/index.ts b/src/transactionsFactories/index.ts new file mode 100644 index 000000000..6aa272608 --- /dev/null +++ b/src/transactionsFactories/index.ts @@ -0,0 +1,6 @@ +export * from "./delegationTransactionsFactory"; +export * from "./relayedTransactionsFactory"; +export * from "./smartContractTransactionsFactory"; +export * from "./tokenManagementTransactionsFactory"; +export * from "./transactionsFactoryConfig"; +export * from "./transferTransactionsFactory"; diff --git a/src/transactionsFactories/relayedTransactionsFactory.spec.ts b/src/transactionsFactories/relayedTransactionsFactory.spec.ts new file mode 100644 index 000000000..e48c995d5 --- /dev/null +++ b/src/transactionsFactories/relayedTransactionsFactory.spec.ts @@ -0,0 +1,278 @@ +import { assert } from "chai"; +import { TestWallet, loadTestWallets } from "../testutils"; +import { Transaction } from "../transaction"; +import { TransactionComputer } from "../transactionComputer"; +import { RelayedTransactionsFactory } from "./relayedTransactionsFactory"; +import { TransactionsFactoryConfig } from "./transactionsFactoryConfig"; + +describe("test relayed v1 transaction builder", function () { + const config = new TransactionsFactoryConfig({ chainID: "T" }); + const factory = new RelayedTransactionsFactory({ config: config }); + const transactionComputer = new TransactionComputer(); + let alice: TestWallet, bob: TestWallet, carol: TestWallet, grace: TestWallet, frank: TestWallet; + + before(async function () { + ({ alice, bob, carol, grace, frank } = await loadTestWallets()); + }); + + it("should throw exception when creating relayed v1 transaction with invalid inner transaction", async function () { + let innerTransaction = new Transaction({ + sender: alice.address.bech32(), + receiver: "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqzllls8a5w6u", + gasLimit: 10000000n, + data: Buffer.from("getContractConfig"), + chainID: config.chainID, + }); + + assert.throws(() => { + factory.createRelayedV1Transaction({ innerTransaction: innerTransaction, relayerAddress: bob.address }), + "The inner transaction is not signed"; + }); + + innerTransaction.gasLimit = 0n; + innerTransaction.signature = Buffer.from("invalidsignature"); + + assert.throws(() => { + factory.createRelayedV1Transaction({ innerTransaction: innerTransaction, relayerAddress: bob.address }), + "The gas limit is not set for the inner transaction"; + }); + }); + + it("should create relayed v1 transaction", async function () { + let innerTransaction = new Transaction({ + sender: bob.address.bech32(), + receiver: "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqzllls8a5w6u", + gasLimit: 60000000n, + data: Buffer.from("getContractConfig"), + chainID: config.chainID, + nonce: 198n, + }); + + const serializedInnerTransaction = transactionComputer.computeBytesForSigning(innerTransaction); + innerTransaction.signature = await bob.signer.sign(serializedInnerTransaction); + + const relayedTransaction = factory.createRelayedV1Transaction({ + innerTransaction: innerTransaction, + relayerAddress: alice.address, + }); + relayedTransaction.nonce = 2627n; + + const serializedRelayedTransaction = transactionComputer.computeBytesForSigning(relayedTransaction); + relayedTransaction.signature = await alice.signer.sign(serializedRelayedTransaction); + + assert.equal( + Buffer.from(relayedTransaction.data).toString(), + "relayedTx@7b226e6f6e6365223a3139382c2273656e646572223a2267456e574f65576d6d413063306a6b71764d354241707a61644b46574e534f69417643575163776d4750673d222c227265636569766572223a22414141414141414141414141415141414141414141414141414141414141414141414141414141432f2f383d222c2276616c7565223a302c226761735072696365223a313030303030303030302c226761734c696d6974223a36303030303030302c2264617461223a225a3256305132397564484a68593352446232356d6157633d222c227369676e6174757265223a2272525455544858677a4273496e4f6e454b6b7869642b354e66524d486e33534948314673746f577352434c434b3258514c41614f4e704449346531476173624c5150616130566f364144516d4f2b52446b6f364a43413d3d222c22636861696e4944223a2256413d3d222c2276657273696f6e223a327d", + ); + assert.equal( + Buffer.from(relayedTransaction.signature).toString("hex"), + "128e7cdc14c2b9beee2f3ff7a7fa5d1f5ef31a654a0c92e223c90ab28265fa277d306f23a06536248cf9573e828017004fb639617fade4d68a37524aafca710d", + ); + }); + + it("should create relayed v1 transaction with usernames", async function () { + let innerTransaction = new Transaction({ + sender: carol.address.bech32(), + receiver: alice.address.bech32(), + gasLimit: 50000n, + chainID: config.chainID, + nonce: 208n, + senderUsername: "carol", + receiverUsername: "alice", + value: 1000000000000000000n, + }); + + const serializedInnerTransaction = transactionComputer.computeBytesForSigning(innerTransaction); + innerTransaction.signature = await carol.signer.sign(serializedInnerTransaction); + + const relayedTransaction = factory.createRelayedV1Transaction({ + innerTransaction: innerTransaction, + relayerAddress: frank.address, + }); + relayedTransaction.nonce = 715n; + + const serializedRelayedTransaction = transactionComputer.computeBytesForSigning(relayedTransaction); + relayedTransaction.signature = await frank.signer.sign(serializedRelayedTransaction); + + assert.equal( + Buffer.from(relayedTransaction.data).toString(), + "relayedTx@7b226e6f6e6365223a3230382c2273656e646572223a227371455656633553486b6c45344a717864556e59573068397a536249533141586f3534786f32634969626f3d222c227265636569766572223a2241546c484c76396f686e63616d433877673970645168386b77704742356a6949496f3349484b594e6165453d222c2276616c7565223a313030303030303030303030303030303030302c226761735072696365223a313030303030303030302c226761734c696d6974223a35303030302c2264617461223a22222c227369676e6174757265223a226a33427a6469554144325963517473576c65707663664a6f75657a48573063316b735a424a4d6339573167435450512b6870636759457858326f6f367a4b5654347464314b4b6f79783841526a346e336474576c44413d3d222c22636861696e4944223a2256413d3d222c2276657273696f6e223a322c22736e64557365724e616d65223a22593246796232773d222c22726376557365724e616d65223a22595778705932553d227d", + ); + assert.equal( + Buffer.from(relayedTransaction.signature).toString("hex"), + "3787d640e5a579e7977a4a1bcdd435ad11855632fa4a414a06fbf8355692d1a58d76ef0adbdd6ccd6bd3c329f36bd53c180d4873ec1a6c558e659aeb9ab92d00", + ); + }); + + it("should create relayed v1 transaction with big value", async function () { + let innerTransaction = new Transaction({ + sender: carol.address.bech32(), + receiver: alice.address.bech32(), + gasLimit: 50000n, + chainID: config.chainID, + nonce: 208n, + senderUsername: "carol", + receiverUsername: "alice", + value: 1999999000000000000000000n, + }); + + const serializedInnerTransaction = transactionComputer.computeBytesForSigning(innerTransaction); + innerTransaction.signature = await carol.signer.sign(serializedInnerTransaction); + + const relayedTransaction = factory.createRelayedV1Transaction({ + innerTransaction: innerTransaction, + relayerAddress: frank.address, + }); + relayedTransaction.nonce = 715n; + + const serializedRelayedTransaction = transactionComputer.computeBytesForSigning(relayedTransaction); + relayedTransaction.signature = await frank.signer.sign(serializedRelayedTransaction); + + assert.equal( + Buffer.from(relayedTransaction.data).toString(), + "relayedTx@7b226e6f6e6365223a3230382c2273656e646572223a227371455656633553486b6c45344a717864556e59573068397a536249533141586f3534786f32634969626f3d222c227265636569766572223a2241546c484c76396f686e63616d433877673970645168386b77704742356a6949496f3349484b594e6165453d222c2276616c7565223a313939393939393030303030303030303030303030303030302c226761735072696365223a313030303030303030302c226761734c696d6974223a35303030302c2264617461223a22222c227369676e6174757265223a22594661677972512f726d614c7333766e7159307657553858415a7939354b4e31725738347a4f764b62376c7a3773576e2f566a546d68704378774d682b7261314e444832574d6f3965507648304f79427453776a44773d3d222c22636861696e4944223a2256413d3d222c2276657273696f6e223a322c22736e64557365724e616d65223a22593246796232773d222c22726376557365724e616d65223a22595778705932553d227d", + ); + assert.equal( + Buffer.from(relayedTransaction.signature).toString("hex"), + "c0fb5cf8c0a413d6988ba35dc279c63f8849572c5f23b1cab36dcc50952dc3ed9da01068d6ac0cbde7e14167bfc2eca5164d5c2154c89eb313c9c596e3f8b801", + ); + }); + + it("should create relayed v1 transaction with guarded inner transaction", async function () { + let innerTransaction = new Transaction({ + sender: bob.address.bech32(), + receiver: "erd1qqqqqqqqqqqqqpgq54tsxmej537z9leghvp69hfu4f8gg5eu396q83gnnz", + gasLimit: 60000000n, + chainID: config.chainID, + data: Buffer.from("getContractConfig"), + nonce: 198n, + version: 2, + options: 2, + guardian: grace.address.bech32(), + }); + + const serializedInnerTransaction = transactionComputer.computeBytesForSigning(innerTransaction); + innerTransaction.signature = await bob.signer.sign(serializedInnerTransaction); + innerTransaction.guardianSignature = await grace.signer.sign(serializedInnerTransaction); + + const relayedTransaction = factory.createRelayedV1Transaction({ + innerTransaction: innerTransaction, + relayerAddress: alice.address, + }); + relayedTransaction.nonce = 2627n; + + const serializedRelayedTransaction = transactionComputer.computeBytesForSigning(relayedTransaction); + relayedTransaction.signature = await alice.signer.sign(serializedRelayedTransaction); + + assert.equal( + Buffer.from(relayedTransaction.data).toString(), + "relayedTx@7b226e6f6e6365223a3139382c2273656e646572223a2267456e574f65576d6d413063306a6b71764d354241707a61644b46574e534f69417643575163776d4750673d222c227265636569766572223a22414141414141414141414146414b565841323879704877692f79693741364c64504b704f68464d386958513d222c2276616c7565223a302c226761735072696365223a313030303030303030302c226761734c696d6974223a36303030303030302c2264617461223a225a3256305132397564484a68593352446232356d6157633d222c227369676e6174757265223a224b4b78324f33383655725135416b4f465258307578327933446a384853334b373038487174344668377161557669424550716c45614e746e6158706a6f2f333651476d4a456934784435457a6c6f4f677a634d4442773d3d222c22636861696e4944223a2256413d3d222c2276657273696f6e223a322c226f7074696f6e73223a322c22677561726469616e223a22486f714c61306e655733766843716f56696c70715372744c5673774939535337586d7a563868477450684d3d222c22677561726469616e5369676e6174757265223a222b5431526f4833625a792f54423177342b6a365155477258645637457577553073753948646551626453515269463953757a686d634b705463526d58595252366c534c6652394931624d7134674730436538363741513d3d227d", + ); + assert.equal( + Buffer.from(relayedTransaction.signature).toString("hex"), + "39cff9d5100e290fbc7361cb6e2402261caf864257b4116f150e0c61e7869155dff8361fa5449431eb7a8ed847c01ba9b3b5ebafe5fac1a3d40c64829d827e00", + ); + }); + + it("should create guarded relayed v1 transaction with guarded inner transaction", async function () { + let innerTransaction = new Transaction({ + sender: bob.address.bech32(), + receiver: "erd1qqqqqqqqqqqqqpgq54tsxmej537z9leghvp69hfu4f8gg5eu396q83gnnz", + gasLimit: 60000000n, + chainID: config.chainID, + data: Buffer.from("addNumber"), + nonce: 198n, + version: 2, + options: 2, + guardian: grace.address.bech32(), + }); + + const serializedInnerTransaction = transactionComputer.computeBytesForSigning(innerTransaction); + innerTransaction.signature = await bob.signer.sign(serializedInnerTransaction); + innerTransaction.guardianSignature = await grace.signer.sign(serializedInnerTransaction); + + const relayedTransaction = factory.createRelayedV1Transaction({ + innerTransaction: innerTransaction, + relayerAddress: alice.address, + }); + relayedTransaction.nonce = 2627n; + relayedTransaction.options = 2; + relayedTransaction.guardian = frank.address.bech32(); + + const serializedRelayedTransaction = transactionComputer.computeBytesForSigning(relayedTransaction); + relayedTransaction.signature = await alice.signer.sign(serializedRelayedTransaction); + relayedTransaction.guardianSignature = await frank.signer.sign(serializedRelayedTransaction); + + assert.equal( + Buffer.from(relayedTransaction.data).toString(), + "relayedTx@7b226e6f6e6365223a3139382c2273656e646572223a2267456e574f65576d6d413063306a6b71764d354241707a61644b46574e534f69417643575163776d4750673d222c227265636569766572223a22414141414141414141414146414b565841323879704877692f79693741364c64504b704f68464d386958513d222c2276616c7565223a302c226761735072696365223a313030303030303030302c226761734c696d6974223a36303030303030302c2264617461223a225957526b546e5674596d5679222c227369676e6174757265223a223469724d4b4a656d724d375174344e7635487633544c44683775654779487045564c4371674a3677652f7a662b746a4933354975573452633458543451533433475333356158386c6a533834324a38426854645043673d3d222c22636861696e4944223a2256413d3d222c2276657273696f6e223a322c226f7074696f6e73223a322c22677561726469616e223a22486f714c61306e655733766843716f56696c70715372744c5673774939535337586d7a563868477450684d3d222c22677561726469616e5369676e6174757265223a2270424754394e674a78307539624c56796b654d78786a454865374269696c37764932324a46676f32787a6e2f496e3032463769546563356b44395045324f747065386c475335412b532f4a36417762576834446744673d3d227d", + ); + assert.equal( + Buffer.from(relayedTransaction.signature).toString("hex"), + "8ede1bbeed96b102344dffeac12c2592c62b7313cdeb132e8c8bf11d2b1d3bb8189d257a6dbcc99e222393d9b9ec77656c349dae97a32e68bdebd636066bf706", + ); + }); + + it("should throw exception when creating relayed v2 transaction with invalid inner transaction", async function () { + let innerTransaction = new Transaction({ + sender: bob.address.bech32(), + receiver: bob.address.bech32(), + gasLimit: 50000n, + chainID: config.chainID, + }); + + assert.throws(() => { + factory.createRelayedV2Transaction({ + innerTransaction: innerTransaction, + innerTransactionGasLimit: 50000n, + relayerAddress: carol.address, + }), + "The gas limit should not be set for the inner transaction"; + }); + + innerTransaction.gasLimit = 0n; + + assert.throws(() => { + factory.createRelayedV2Transaction({ + innerTransaction: innerTransaction, + innerTransactionGasLimit: 50000n, + relayerAddress: carol.address, + }), + "The inner transaction is not signed"; + }); + }); + + it("should create relayed v2 transaction", async function () { + let innerTransaction = new Transaction({ + sender: bob.address.bech32(), + receiver: "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqzllls8a5w6u", + gasLimit: 0n, + chainID: config.chainID, + data: Buffer.from("getContractConfig"), + nonce: 15n, + version: 2, + options: 0, + }); + + const serializedInnerTransaction = transactionComputer.computeBytesForSigning(innerTransaction); + innerTransaction.signature = await bob.signer.sign(serializedInnerTransaction); + + const relayedTransaction = factory.createRelayedV2Transaction({ + innerTransaction: innerTransaction, + innerTransactionGasLimit: 60000000n, + relayerAddress: alice.address, + }); + relayedTransaction.nonce = 37n; + + const serializedRelayedTransaction = transactionComputer.computeBytesForSigning(relayedTransaction); + relayedTransaction.signature = await alice.signer.sign(serializedRelayedTransaction); + + assert.equal(relayedTransaction.version, 2); + assert.equal(relayedTransaction.options, 0); + assert.equal(relayedTransaction.gasLimit.toString(), "60414500"); + assert.equal( + Buffer.from(relayedTransaction.data).toString(), + "relayedTxV2@000000000000000000010000000000000000000000000000000000000002ffff@0f@676574436f6e7472616374436f6e666967@fc3ed87a51ee659f937c1a1ed11c1ae677e99629fae9cc289461f033e6514d1a8cfad1144ae9c1b70f28554d196bd6ba1604240c1c1dc19c959e96c1c3b62d0c", + ); + }); +}); diff --git a/src/transactionsFactories/relayedTransactionsFactory.ts b/src/transactionsFactories/relayedTransactionsFactory.ts new file mode 100644 index 000000000..fdf12be61 --- /dev/null +++ b/src/transactionsFactories/relayedTransactionsFactory.ts @@ -0,0 +1,116 @@ +import BigNumber from "bignumber.js"; +import { Address } from "../address"; +import { ErrInvalidInnerTransaction } from "../errors"; +import { IAddress, ITransaction } from "../interface"; +import { AddressValue, ArgSerializer, BytesValue, U64Value } from "../smartcontracts"; +import { Transaction } from "../transaction"; + +const JSONbig = require("json-bigint"); + +interface IConfig { + chainID: string; + minGasLimit: bigint; + gasLimitPerByte: bigint; +} + +/** + * Use this class to create both RelayedV1 and RelayedV2 transactions. + */ +export class RelayedTransactionsFactory { + private readonly config: IConfig; + + constructor(options: { config: IConfig }) { + this.config = options.config; + } + + createRelayedV1Transaction(options: { innerTransaction: ITransaction; relayerAddress: IAddress }): Transaction { + if (!options.innerTransaction.gasLimit) { + throw new ErrInvalidInnerTransaction("The gas limit is not set for the inner transaction"); + } + + if (!options.innerTransaction.signature.length) { + throw new ErrInvalidInnerTransaction("The inner transaction is not signed"); + } + + const serializedTransaction = this.prepareInnerTransactionForRelayedV1(options.innerTransaction); + const data = `relayedTx@${Buffer.from(serializedTransaction).toString("hex")}`; + + const additionalGasForDataLength = this.config.gasLimitPerByte * BigInt(data.length); + const gasLimit = this.config.minGasLimit + additionalGasForDataLength + options.innerTransaction.gasLimit; + + return new Transaction({ + chainID: this.config.chainID, + sender: options.relayerAddress.bech32(), + receiver: options.innerTransaction.sender, + gasLimit: gasLimit, + data: Buffer.from(data), + }); + } + + createRelayedV2Transaction(options: { + innerTransaction: ITransaction; + innerTransactionGasLimit: bigint; + relayerAddress: IAddress; + }): Transaction { + if (options.innerTransaction.gasLimit) { + throw new ErrInvalidInnerTransaction("The gas limit should not be set for the inner transaction"); + } + + if (!options.innerTransaction.signature.length) { + throw new ErrInvalidInnerTransaction("The inner transaction is not signed"); + } + + const { argumentsString } = new ArgSerializer().valuesToString([ + new AddressValue(Address.fromBech32(options.innerTransaction.receiver)), + new U64Value(new BigNumber(options.innerTransaction.nonce.toString())), + new BytesValue(Buffer.from(options.innerTransaction.data)), + new BytesValue(Buffer.from(options.innerTransaction.signature)), + ]); + + const data = `relayedTxV2@${argumentsString}`; + + const additionalGasForDataLength = this.config.gasLimitPerByte * BigInt(data.length); + const gasLimit = options.innerTransactionGasLimit + this.config.minGasLimit + additionalGasForDataLength; + + return new Transaction({ + sender: options.relayerAddress.bech32(), + receiver: options.innerTransaction.sender, + value: 0n, + gasLimit: gasLimit, + chainID: this.config.chainID, + data: Buffer.from(data), + version: options.innerTransaction.version, + options: options.innerTransaction.options, + }); + } + + private prepareInnerTransactionForRelayedV1(innerTransaction: ITransaction): string { + const txObject = { + nonce: innerTransaction.nonce, + sender: Address.fromBech32(innerTransaction.sender).pubkey().toString("base64"), + receiver: Address.fromBech32(innerTransaction.receiver).pubkey().toString("base64"), + value: innerTransaction.value, + gasPrice: innerTransaction.gasPrice, + gasLimit: innerTransaction.gasLimit, + data: Buffer.from(innerTransaction.data).toString("base64"), + signature: Buffer.from(innerTransaction.signature).toString("base64"), + chainID: Buffer.from(innerTransaction.chainID).toString("base64"), + version: innerTransaction.version, + options: innerTransaction.options.valueOf() == 0 ? undefined : innerTransaction.options, + guardian: innerTransaction.guardian + ? Address.fromBech32(innerTransaction.guardian).pubkey().toString("base64") + : undefined, + guardianSignature: innerTransaction.guardianSignature.length + ? Buffer.from(innerTransaction.guardianSignature).toString("base64") + : undefined, + sndUserName: innerTransaction.senderUsername + ? Buffer.from(innerTransaction.senderUsername).toString("base64") + : undefined, + rcvUserName: innerTransaction.receiverUsername + ? Buffer.from(innerTransaction.receiverUsername).toString("base64") + : undefined, + }; + + return JSONbig.stringify(txObject); + } +} diff --git a/src/transactionsFactories/smartContractTransactionsFactory.spec.ts b/src/transactionsFactories/smartContractTransactionsFactory.spec.ts new file mode 100644 index 000000000..5b1f771bf --- /dev/null +++ b/src/transactionsFactories/smartContractTransactionsFactory.spec.ts @@ -0,0 +1,350 @@ +import { assert, expect } from "chai"; +import { Address } from "../address"; +import { CONTRACT_DEPLOY_ADDRESS } from "../constants"; +import { Err } from "../errors"; +import { U32Value } from "../smartcontracts"; +import { Code } from "../smartcontracts/code"; +import { AbiRegistry } from "../smartcontracts/typesystem/abiRegistry"; +import { loadAbiRegistry, loadContractCode } from "../testutils/utils"; +import { Token, TokenTransfer } from "../tokens"; +import { SmartContractTransactionsFactory } from "./smartContractTransactionsFactory"; +import { TransactionsFactoryConfig } from "./transactionsFactoryConfig"; + +describe("test smart contract transactions factory", function () { + const config = new TransactionsFactoryConfig({ chainID: "D" }); + let smartContractFactory: SmartContractTransactionsFactory; + let abiAwareFactory: SmartContractTransactionsFactory; + let adderByteCode: Code; + let abiRegistry: AbiRegistry; + + before(async function () { + smartContractFactory = new SmartContractTransactionsFactory({ + config: config, + }); + + adderByteCode = await loadContractCode("src/testdata/adder.wasm"); + abiRegistry = await loadAbiRegistry("src/testdata/adder.abi.json"); + + abiAwareFactory = new SmartContractTransactionsFactory({ + config: config, + abi: abiRegistry, + }); + }); + + it("should throw error when args are not of type 'TypedValue'", async function () { + const sender = Address.fromBech32("erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"); + const gasLimit = 6000000n; + const args = [0]; + + assert.throws( + () => + smartContractFactory.createTransactionForDeploy({ + sender: sender, + bytecode: adderByteCode.valueOf(), + gasLimit: gasLimit, + arguments: args, + }), + Err, + "Can't convert args to TypedValues", + ); + }); + + it("should create 'Transaction' for deploy", async function () { + const sender = Address.fromBech32("erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"); + const gasLimit = 6000000n; + const args = [new U32Value(0)]; + + const transaction = smartContractFactory.createTransactionForDeploy({ + sender: sender, + bytecode: adderByteCode.valueOf(), + gasLimit: gasLimit, + arguments: args, + }); + + const transactionAbiAware = abiAwareFactory.createTransactionForDeploy({ + sender: sender, + bytecode: adderByteCode.valueOf(), + gasLimit: gasLimit, + arguments: args, + }); + + assert.equal(transaction.sender, "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"); + assert.equal(transaction.receiver, CONTRACT_DEPLOY_ADDRESS); + expect(transaction.data.length).to.be.greaterThan(0); + assert.equal(transaction.gasLimit.valueOf(), gasLimit); + assert.equal(transaction.value, 0n); + + assert.deepEqual(transaction, transactionAbiAware); + }); + + it("should create 'Transaction' for execute without transfer", async function () { + const sender = Address.fromBech32("erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"); + const contract = Address.fromBech32("erd1qqqqqqqqqqqqqpgqhy6nl6zq07rnzry8uyh6rtyq0uzgtk3e69fqgtz9l4"); + const func = "add"; + const gasLimit = 6000000n; + const args = [new U32Value(7)]; + + const transaction = smartContractFactory.createTransactionForExecute({ + sender: sender, + contract: contract, + function: func, + gasLimit: gasLimit, + arguments: args, + }); + + const transactionAbiAware = abiAwareFactory.createTransactionForExecute({ + sender: sender, + contract: contract, + function: func, + gasLimit: gasLimit, + arguments: args, + }); + + assert.equal(transaction.sender, "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"); + assert.equal(transaction.receiver, "erd1qqqqqqqqqqqqqpgqhy6nl6zq07rnzry8uyh6rtyq0uzgtk3e69fqgtz9l4"); + assert.deepEqual(transaction.data, Buffer.from("add@07")); + assert.equal(transaction.gasLimit, gasLimit); + assert.equal(transaction.value, 0n); + + assert.deepEqual(transaction, transactionAbiAware); + }); + + it("should create 'Transaction' for execute and transfer native token", async function () { + const sender = Address.fromBech32("erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"); + const contract = Address.fromBech32("erd1qqqqqqqqqqqqqpgqhy6nl6zq07rnzry8uyh6rtyq0uzgtk3e69fqgtz9l4"); + const func = "add"; + const gasLimit = 6000000n; + const egldAmount = 1000000000000000000n; + + const transaction = smartContractFactory.createTransactionForExecute({ + sender: sender, + contract: contract, + function: func, + gasLimit: gasLimit, + arguments: [new U32Value(7)], + nativeTransferAmount: egldAmount, + }); + + const transactionAbiAware = abiAwareFactory.createTransactionForExecute({ + sender: sender, + contract: contract, + function: func, + gasLimit: gasLimit, + arguments: [7], + nativeTransferAmount: egldAmount, + }); + + assert.equal(transaction.sender, "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"); + assert.equal(transaction.receiver, "erd1qqqqqqqqqqqqqpgqhy6nl6zq07rnzry8uyh6rtyq0uzgtk3e69fqgtz9l4"); + assert.deepEqual(transaction.data, Buffer.from("add@07")); + assert.equal(transaction.gasLimit, gasLimit); + assert.equal(transaction.value, 1000000000000000000n); + + assert.deepEqual(transaction, transactionAbiAware); + }); + + it("should create 'Transaction' for execute and transfer single esdt", async function () { + const sender = Address.fromBech32("erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"); + const contract = Address.fromBech32("erd1qqqqqqqqqqqqqpgqhy6nl6zq07rnzry8uyh6rtyq0uzgtk3e69fqgtz9l4"); + const func = "add"; + const gasLimit = 6000000n; + const args = [new U32Value(7)]; + const token = new Token({ identifier: "FOO-6ce17b", nonce: 0n }); + const transfer = new TokenTransfer({ token, amount: 10n }); + + const transaction = smartContractFactory.createTransactionForExecute({ + sender: sender, + contract: contract, + function: func, + gasLimit: gasLimit, + arguments: args, + tokenTransfers: [transfer], + }); + + const transactionAbiAware = abiAwareFactory.createTransactionForExecute({ + sender: sender, + contract: contract, + function: func, + gasLimit: gasLimit, + arguments: args, + tokenTransfers: [transfer], + }); + + assert.equal(transaction.sender, "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"); + assert.equal(transaction.receiver, "erd1qqqqqqqqqqqqqpgqhy6nl6zq07rnzry8uyh6rtyq0uzgtk3e69fqgtz9l4"); + assert.deepEqual(transaction.data, Buffer.from("ESDTTransfer@464f4f2d366365313762@0a@616464@07")); + assert.equal(transaction.gasLimit, gasLimit); + assert.equal(transaction.value, 0n); + + assert.deepEqual(transaction, transactionAbiAware); + }); + + it("should create 'Transaction' for execute and transfer multiple esdts", async function () { + const sender = Address.fromBech32("erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"); + const contract = Address.fromBech32("erd1qqqqqqqqqqqqqpgqak8zt22wl2ph4tswtyc39namqx6ysa2sd8ss4xmlj3"); + const func = "add"; + const gasLimit = 6000000n; + const args = [new U32Value(7)]; + + const fooToken = new Token({ identifier: "FOO-6ce17b", nonce: 0n }); + const fooTransfer = new TokenTransfer({ token: fooToken, amount: 10n }); + const barToken = new Token({ identifier: "BAR-5bc08f", nonce: 0n }); + const barTransfer = new TokenTransfer({ token: barToken, amount: 3140n }); + + const transaction = smartContractFactory.createTransactionForExecute({ + sender: sender, + contract: contract, + function: func, + gasLimit: gasLimit, + arguments: args, + tokenTransfers: [fooTransfer, barTransfer], + }); + + const transactionAbiAware = abiAwareFactory.createTransactionForExecute({ + sender: sender, + contract: contract, + function: func, + gasLimit: gasLimit, + arguments: args, + tokenTransfers: [fooTransfer, barTransfer], + }); + + assert.equal(transaction.sender, "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"); + assert.equal(transaction.receiver, "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"); + + assert.deepEqual( + transaction.data, + Buffer.from( + "MultiESDTNFTTransfer@00000000000000000500ed8e25a94efa837aae0e593112cfbb01b448755069e1@02@464f4f2d366365313762@@0a@4241522d356263303866@@0c44@616464@07", + ), + ); + + assert.equal(transaction.gasLimit, gasLimit); + assert.equal(transaction.value, 0n); + + assert.deepEqual(transaction, transactionAbiAware); + }); + + it("should create 'Transaction' for execute and transfer single nft", async function () { + const sender = Address.fromBech32("erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"); + const contract = Address.fromBech32("erd1qqqqqqqqqqqqqpgqhy6nl6zq07rnzry8uyh6rtyq0uzgtk3e69fqgtz9l4"); + const func = "add"; + const gasLimit = 6000000n; + const args = [new U32Value(7)]; + + const token = new Token({ identifier: "NFT-123456", nonce: 1n }); + const transfer = new TokenTransfer({ token, amount: 1n }); + + const transaction = smartContractFactory.createTransactionForExecute({ + sender: sender, + contract: contract, + function: func, + gasLimit: gasLimit, + arguments: args, + tokenTransfers: [transfer], + }); + + const transactionAbiAware = abiAwareFactory.createTransactionForExecute({ + sender: sender, + contract: contract, + function: func, + gasLimit: gasLimit, + arguments: args, + tokenTransfers: [transfer], + }); + + assert.equal(transaction.sender, "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"); + assert.equal(transaction.receiver, "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"); + + assert.isDefined(transaction.data); + assert.deepEqual( + transaction.data, + Buffer.from( + "ESDTNFTTransfer@4e46542d313233343536@01@01@00000000000000000500b9353fe8407f87310c87e12fa1ac807f0485da39d152@616464@07", + ), + ); + + assert.equal(transaction.gasLimit, gasLimit); + assert.equal(transaction.value, 0n); + + assert.deepEqual(transaction, transactionAbiAware); + }); + + it("should create 'Transaction' for execute and transfer multiple nfts", async function () { + const sender = Address.fromBech32("erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"); + const contract = Address.fromBech32("erd1qqqqqqqqqqqqqpgqhy6nl6zq07rnzry8uyh6rtyq0uzgtk3e69fqgtz9l4"); + const func = "add"; + const gasLimit = 6000000n; + const args = [new U32Value(7)]; + + const firstToken = new Token({ identifier: "NFT-123456", nonce: 1n }); + const firstTransfer = new TokenTransfer({ token: firstToken, amount: 1n }); + const secondToken = new Token({ identifier: "NFT-123456", nonce: 42n }); + const secondTransfer = new TokenTransfer({ token: secondToken, amount: 1n }); + + const transaction = smartContractFactory.createTransactionForExecute({ + sender: sender, + contract: contract, + function: func, + gasLimit: gasLimit, + arguments: args, + tokenTransfers: [firstTransfer, secondTransfer], + }); + + const transactionAbiAware = abiAwareFactory.createTransactionForExecute({ + sender: sender, + contract: contract, + function: func, + gasLimit: gasLimit, + arguments: args, + tokenTransfers: [firstTransfer, secondTransfer], + }); + + assert.equal(transaction.sender, "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"); + assert.equal(transaction.receiver, "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"); + + assert.isDefined(transaction.data); + assert.deepEqual( + transaction.data, + Buffer.from( + "MultiESDTNFTTransfer@00000000000000000500b9353fe8407f87310c87e12fa1ac807f0485da39d152@02@4e46542d313233343536@01@01@4e46542d313233343536@2a@01@616464@07", + ), + ); + + assert.equal(transaction.gasLimit, gasLimit); + assert.equal(transaction.value, 0n); + + assert.deepEqual(transaction, transactionAbiAware); + }); + + it("should create 'Transaction' for upgrade", async function () { + const sender = Address.fromBech32("erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"); + const contract = Address.fromBech32("erd1qqqqqqqqqqqqqpgqhy6nl6zq07rnzry8uyh6rtyq0uzgtk3e69fqgtz9l4"); + const gasLimit = 6000000n; + const args = [new U32Value(0)]; + + const transaction = smartContractFactory.createTransactionForUpgrade({ + sender: sender, + contract: contract, + bytecode: adderByteCode.valueOf(), + gasLimit: gasLimit, + arguments: args, + }); + + const transactionAbiAware = abiAwareFactory.createTransactionForUpgrade({ + sender: sender, + contract: contract, + bytecode: adderByteCode.valueOf(), + gasLimit: gasLimit, + arguments: args, + }); + + assert.equal(transaction.sender, "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"); + assert.equal(transaction.receiver, "erd1qqqqqqqqqqqqqpgqhy6nl6zq07rnzry8uyh6rtyq0uzgtk3e69fqgtz9l4"); + assert.isTrue(Buffer.from(transaction.data!).toString().startsWith("upgradeContract@")); + assert.equal(transaction.gasLimit, gasLimit); + assert.equal(transaction.value, 0n); + + assert.deepEqual(transaction, transactionAbiAware); + }); +}); diff --git a/src/transactionsFactories/smartContractTransactionsFactory.ts b/src/transactionsFactories/smartContractTransactionsFactory.ts new file mode 100644 index 000000000..832d8ae3a --- /dev/null +++ b/src/transactionsFactories/smartContractTransactionsFactory.ts @@ -0,0 +1,180 @@ +import { Address } from "../address"; +import { CONTRACT_DEPLOY_ADDRESS, VM_TYPE_WASM_VM } from "../constants"; +import { Err, ErrBadUsage } from "../errors"; +import { IAddress } from "../interface"; +import { ArgSerializer, CodeMetadata, ContractFunction, EndpointDefinition } from "../smartcontracts"; +import { NativeSerializer } from "../smartcontracts/nativeSerializer"; +import { TokenComputer, TokenTransfer } from "../tokens"; +import { Transaction } from "../transaction"; +import { byteArrayToHex, utf8ToHex } from "../utils.codec"; +import { TokenTransfersDataBuilder } from "./tokenTransfersDataBuilder"; +import { TransactionBuilder } from "./transactionBuilder"; + +interface Config { + chainID: string; + minGasLimit: bigint; + gasLimitPerByte: bigint; +} + +interface IAbi { + constructorDefinition: EndpointDefinition; + + getEndpoint(name: string | ContractFunction): EndpointDefinition; +} + +/** + * Use this class to create transactions to deploy, call or upgrade a smart contract. + */ +export class SmartContractTransactionsFactory { + private readonly config: Config; + private readonly abi?: IAbi; + private readonly tokenComputer: TokenComputer; + private readonly dataArgsBuilder: TokenTransfersDataBuilder; + + constructor(options: { config: Config; abi?: IAbi }) { + this.config = options.config; + this.abi = options.abi; + this.tokenComputer = new TokenComputer(); + this.dataArgsBuilder = new TokenTransfersDataBuilder(); + } + + createTransactionForDeploy(options: { + sender: IAddress; + bytecode: Uint8Array; + gasLimit: bigint; + arguments?: any[]; + nativeTransferAmount?: bigint; + isUpgradeable?: boolean; + isReadable?: boolean; + isPayable?: boolean; + isPayableBySmartContract?: boolean; + }): Transaction { + const nativeTransferAmount = options.nativeTransferAmount ?? 0n; + const isUpgradeable = options.isUpgradeable ?? true; + const isReadable = options.isReadable ?? true; + const isPayable = options.isPayable ?? false; + const isPayableBySmartContract = options.isPayableBySmartContract ?? true; + const args = options.arguments || []; + const metadata = new CodeMetadata(isUpgradeable, isReadable, isPayable, isPayableBySmartContract); + let parts = [byteArrayToHex(options.bytecode), byteArrayToHex(VM_TYPE_WASM_VM), metadata.toString()]; + const preparedArgs = this.argsToDataParts(args, this.abi?.constructorDefinition); + parts = parts.concat(preparedArgs); + + return new TransactionBuilder({ + config: this.config, + sender: options.sender, + receiver: Address.fromBech32(CONTRACT_DEPLOY_ADDRESS), + dataParts: parts, + gasLimit: options.gasLimit, + addDataMovementGas: false, + amount: nativeTransferAmount, + }).build(); + } + + createTransactionForExecute(options: { + sender: IAddress; + contract: IAddress; + function: string; + gasLimit: bigint; + arguments?: any[]; + nativeTransferAmount?: bigint; + tokenTransfers?: TokenTransfer[]; + }): Transaction { + const args = options.arguments || []; + const tokenTransfer = options.tokenTransfers || []; + const nativeTransferAmount = options.nativeTransferAmount ?? 0n; + const numberOfTokens = tokenTransfer.length; + + if (nativeTransferAmount && numberOfTokens) { + throw new ErrBadUsage("Can't send both native tokens and custom tokens(ESDT/NFT)"); + } + + let receiver = options.contract; + let dataParts: string[] = []; + + if (numberOfTokens === 1) { + const transfer = tokenTransfer[0]; + + if (this.tokenComputer.isFungible(transfer.token)) { + dataParts = this.dataArgsBuilder.buildDataPartsForESDTTransfer(transfer); + } else { + dataParts = this.dataArgsBuilder.buildDataPartsForSingleESDTNFTTransfer(transfer, receiver); + receiver = options.sender; + } + } else if (numberOfTokens > 1) { + dataParts = this.dataArgsBuilder.buildDataPartsForMultiESDTNFTTransfer(receiver, tokenTransfer); + receiver = options.sender; + } + + dataParts.push(dataParts.length ? utf8ToHex(options.function) : options.function); + dataParts = dataParts.concat(this.argsToDataParts(args, this.abi?.getEndpoint(options.function))); + + return new TransactionBuilder({ + config: this.config, + sender: options.sender, + receiver: receiver, + dataParts: dataParts, + gasLimit: options.gasLimit, + addDataMovementGas: false, + amount: nativeTransferAmount, + }).build(); + } + + createTransactionForUpgrade(options: { + sender: IAddress; + contract: IAddress; + bytecode: Uint8Array; + gasLimit: bigint; + arguments?: any[]; + nativeTransferAmount?: bigint; + isUpgradeable?: boolean; + isReadable?: boolean; + isPayable?: boolean; + isPayableBySmartContract?: boolean; + }): Transaction { + const nativeTransferAmount = options.nativeTransferAmount ?? 0n; + + const isUpgradeable = options.isUpgradeable ?? true; + const isReadable = options.isReadable ?? true; + const isPayable = options.isPayable ?? false; + const isPayableBySmartContract = options.isPayableBySmartContract ?? true; + + const args = options.arguments || []; + const metadata = new CodeMetadata(isUpgradeable, isReadable, isPayable, isPayableBySmartContract); + + let parts = ["upgradeContract", byteArrayToHex(options.bytecode), metadata.toString()]; + const preparedArgs = this.argsToDataParts(args, this.abi?.constructorDefinition); + parts = parts.concat(preparedArgs); + + return new TransactionBuilder({ + config: this.config, + sender: options.sender, + receiver: options.contract, + dataParts: parts, + gasLimit: options.gasLimit, + addDataMovementGas: false, + amount: nativeTransferAmount, + }).build(); + } + + private argsToDataParts(args: any[], endpoint?: EndpointDefinition): string[] { + if (endpoint) { + const typedArgs = NativeSerializer.nativeToTypedValues(args, endpoint); + return new ArgSerializer().valuesToStrings(typedArgs); + } + + if (this.areArgsOfTypedValue(args)) { + return new ArgSerializer().valuesToStrings(args); + } + throw new Err("Can't convert args to TypedValues"); + } + + private areArgsOfTypedValue(args: any[]): boolean { + for (const arg of args) { + if (!arg.belongsToTypesystem) { + return false; + } + } + return true; + } +} diff --git a/src/transactionsFactories/tokenManagementTransactionIntentsFactory.spec.ts b/src/transactionsFactories/tokenManagementTransactionIntentsFactory.spec.ts new file mode 100644 index 000000000..aa5b72636 --- /dev/null +++ b/src/transactionsFactories/tokenManagementTransactionIntentsFactory.spec.ts @@ -0,0 +1,179 @@ +import { assert } from "chai"; +import { ESDT_CONTRACT_ADDRESS } from "../constants"; +import { loadTestWallets, TestWallet } from "../testutils"; +import { TokenManagementTransactionsFactory } from "./tokenManagementTransactionsFactory"; +import { TransactionsFactoryConfig } from "./transactionsFactoryConfig"; + +describe("test token management transactions factory", () => { + let frank: TestWallet, grace: TestWallet; + let tokenManagementFactory: TokenManagementTransactionsFactory; + let config: TransactionsFactoryConfig; + + before(async function () { + ({ frank, grace } = await loadTestWallets()); + config = new TransactionsFactoryConfig({ chainID: "T" }); + tokenManagementFactory = new TokenManagementTransactionsFactory({ config: config }); + }); + + it("should create 'Transaction' for registering and setting roles", () => { + const transaction = tokenManagementFactory.createTransactionForRegisteringAndSettingRoles({ + sender: frank.address, + tokenName: "TEST", + tokenTicker: "TEST", + tokenType: "FNG", + numDecimals: 2n, + }); + + assert.deepEqual(transaction.data, Buffer.from("registerAndSetAllRoles@54455354@54455354@464e47@02")); + assert.equal(transaction.sender, frank.address.toString()); + assert.equal(transaction.receiver, "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqzllls8a5w6u"); + assert.deepEqual(transaction.value, config.issueCost); + assert.deepEqual(transaction.gasLimit, 60125000n); + }); + + it("should create 'Transaction' for issuing fungible token", () => { + const transaction = tokenManagementFactory.createTransactionForIssuingFungible({ + sender: frank.address, + tokenName: "FRANK", + tokenTicker: "FRANK", + initialSupply: 100n, + numDecimals: 0n, + canFreeze: true, + canWipe: true, + canPause: true, + canChangeOwner: true, + canUpgrade: false, + canAddSpecialRoles: false, + }); + + assert.deepEqual( + transaction.data, + Buffer.from( + "issue@4652414e4b@4652414e4b@64@@63616e467265657a65@74727565@63616e57697065@74727565@63616e5061757365@74727565@63616e4368616e67654f776e6572@74727565@63616e55706772616465@66616c7365@63616e4164645370656369616c526f6c6573@66616c7365", + ), + ); + assert.equal(transaction.sender, frank.address.toString()); + assert.equal(transaction.receiver, ESDT_CONTRACT_ADDRESS); + assert.deepEqual(transaction.value, config.issueCost); + }); + + it("should create 'Transaction' for issuing semi-fungible token", () => { + const transaction = tokenManagementFactory.createTransactionForIssuingSemiFungible({ + sender: frank.address, + tokenName: "FRANK", + tokenTicker: "FRANK", + canFreeze: true, + canWipe: true, + canPause: true, + canTransferNFTCreateRole: true, + canChangeOwner: true, + canUpgrade: false, + canAddSpecialRoles: false, + }); + + assert.deepEqual( + transaction.data, + Buffer.from( + "issueSemiFungible@4652414e4b@4652414e4b@63616e467265657a65@74727565@63616e57697065@74727565@63616e5061757365@74727565@63616e5472616e736665724e4654437265617465526f6c65@74727565@63616e4368616e67654f776e6572@74727565@63616e55706772616465@66616c7365@63616e4164645370656369616c526f6c6573@66616c7365", + ), + ); + assert.equal(transaction.sender, frank.address.toString()); + assert.equal(transaction.receiver, ESDT_CONTRACT_ADDRESS); + assert.deepEqual(transaction.value, config.issueCost); + }); + + it("should create 'Transaction' for issuing non-fungible token", () => { + const transaction = tokenManagementFactory.createTransactionForIssuingNonFungible({ + sender: frank.address, + tokenName: "FRANK", + tokenTicker: "FRANK", + canFreeze: true, + canWipe: true, + canPause: true, + canTransferNFTCreateRole: true, + canChangeOwner: true, + canUpgrade: false, + canAddSpecialRoles: false, + }); + + assert.deepEqual( + transaction.data, + Buffer.from( + "issueNonFungible@4652414e4b@4652414e4b@63616e467265657a65@74727565@63616e57697065@74727565@63616e5061757365@74727565@63616e5472616e736665724e4654437265617465526f6c65@74727565@63616e4368616e67654f776e6572@74727565@63616e55706772616465@66616c7365@63616e4164645370656369616c526f6c6573@66616c7365", + ), + ); + assert.equal(transaction.sender, frank.address.toString()); + assert.equal(transaction.receiver, ESDT_CONTRACT_ADDRESS); + assert.deepEqual(transaction.value, config.issueCost); + }); + + it("should create 'Transaction' for registering metaEsdt", () => { + const transaction = tokenManagementFactory.createTransactionForRegisteringMetaESDT({ + sender: frank.address, + tokenName: "FRANK", + tokenTicker: "FRANK", + numDecimals: 10n, + canFreeze: true, + canWipe: true, + canPause: true, + canTransferNFTCreateRole: true, + canChangeOwner: true, + canUpgrade: false, + canAddSpecialRoles: false, + }); + + assert.deepEqual( + transaction.data, + Buffer.from( + "registerMetaESDT@4652414e4b@4652414e4b@0a@63616e467265657a65@74727565@63616e57697065@74727565@63616e5061757365@74727565@63616e5472616e736665724e4654437265617465526f6c65@74727565@63616e4368616e67654f776e6572@74727565@63616e55706772616465@66616c7365@63616e4164645370656369616c526f6c6573@66616c7365", + ), + ); + assert.equal(transaction.sender, frank.address.toString()); + assert.equal(transaction.receiver, ESDT_CONTRACT_ADDRESS); + assert.deepEqual(transaction.value, config.issueCost); + }); + + it("should create 'Transaction' for setting spcial role on non-fungible token", () => { + const transaction = tokenManagementFactory.createTransactionForSettingSpecialRoleOnNonFungibleToken({ + sender: frank.address, + user: grace.address, + tokenIdentifier: "FRANK-11ce3e", + addRoleNFTCreate: true, + addRoleNFTBurn: false, + addRoleNFTUpdateAttributes: true, + addRoleNFTAddURI: true, + addRoleESDTTransferRole: false, + }); + + assert.deepEqual( + transaction.data, + Buffer.from( + "setSpecialRole@4652414e4b2d313163653365@1e8a8b6b49de5b7be10aaa158a5a6a4abb4b56cc08f524bb5e6cd5f211ad3e13@45534454526f6c654e4654437265617465@45534454526f6c654e465455706461746541747472696275746573@45534454526f6c654e4654416464555249", + ), + ); + assert.equal(transaction.sender, frank.address.toString()); + assert.equal(transaction.receiver, ESDT_CONTRACT_ADDRESS); + assert.equal(transaction.value, 0n); + }); + + it("should create 'Transaction' for creating nft", () => { + const transaction = tokenManagementFactory.createTransactionForCreatingNFT({ + sender: grace.address, + tokenIdentifier: "FRANK-aa9e8d", + initialQuantity: 1n, + name: "test", + royalties: 1000, + hash: "abba", + attributes: Buffer.from("test"), + uris: ["a", "b"], + }); + + assert.deepEqual( + transaction.data, + Buffer.from("ESDTNFTCreate@4652414e4b2d616139653864@01@74657374@03e8@61626261@74657374@61@62"), + ); + assert.equal(transaction.sender, grace.address.toString()); + assert.equal(transaction.receiver, grace.address.toString()); + assert.equal(transaction.value, 0n); + }); +}); diff --git a/src/transactionsFactories/tokenManagementTransactionsFactory.ts b/src/transactionsFactories/tokenManagementTransactionsFactory.ts new file mode 100644 index 000000000..86662c00e --- /dev/null +++ b/src/transactionsFactories/tokenManagementTransactionsFactory.ts @@ -0,0 +1,654 @@ +import { Address } from "../address"; +import { ESDT_CONTRACT_ADDRESS } from "../constants"; +import { IAddress } from "../interface"; +import { Logger } from "../logger"; +import { AddressValue, ArgSerializer, BigUIntValue, BytesValue, StringValue } from "../smartcontracts"; +import { Transaction } from "../transaction"; +import { TransactionBuilder } from "./transactionBuilder"; + +interface Config { + chainID: string; + minGasLimit: bigint; + gasLimitPerByte: bigint; + gasLimitIssue: bigint; + gasLimitToggleBurnRoleGlobally: bigint; + gasLimitEsdtLocalMint: bigint; + gasLimitEsdtLocalBurn: bigint; + gasLimitSetSpecialRole: bigint; + gasLimitPausing: bigint; + gasLimitFreezing: bigint; + gasLimitWiping: bigint; + gasLimitEsdtNftCreate: bigint; + gasLimitEsdtNftUpdateAttributes: bigint; + gasLimitEsdtNftAddQuantity: bigint; + gasLimitEsdtNftBurn: bigint; + gasLimitStorePerByte: bigint; + issueCost: bigint; +} + +type RegisterAndSetAllRolesTokenType = "NFT" | "SFT" | "META" | "FNG"; + +/** + * Use this class to create token management transactions like issuing ESDTs, creating NFTs, setting roles, etc. + */ +export class TokenManagementTransactionsFactory { + private readonly config: Config; + private readonly argSerializer: ArgSerializer; + private readonly trueAsString: string; + private readonly falseAsString: string; + + constructor(options: { config: Config }) { + this.config = options.config; + this.argSerializer = new ArgSerializer(); + this.trueAsString = "true"; + this.falseAsString = "false"; + } + + createTransactionForIssuingFungible(options: { + sender: IAddress; + tokenName: string; + tokenTicker: string; + initialSupply: bigint; + numDecimals: bigint; + canFreeze: boolean; + canWipe: boolean; + canPause: boolean; + canChangeOwner: boolean; + canUpgrade: boolean; + canAddSpecialRoles: boolean; + }): Transaction { + this.notifyAboutUnsettingBurnRoleGlobally(); + + const args = [ + new StringValue(options.tokenName), + new StringValue(options.tokenTicker), + new BigUIntValue(options.initialSupply), + new BigUIntValue(options.numDecimals), + new StringValue("canFreeze"), + new StringValue(this.boolToString(options.canFreeze)), + new StringValue("canWipe"), + new StringValue(this.boolToString(options.canWipe)), + new StringValue("canPause"), + new StringValue(this.boolToString(options.canPause)), + new StringValue("canChangeOwner"), + new StringValue(this.boolToString(options.canChangeOwner)), + new StringValue("canUpgrade"), + new StringValue(this.boolToString(options.canUpgrade)), + new StringValue("canAddSpecialRoles"), + new StringValue(this.boolToString(options.canAddSpecialRoles)), + ]; + + const dataParts = ["issue", ...this.argSerializer.valuesToStrings(args)]; + + return new TransactionBuilder({ + config: this.config, + sender: options.sender, + receiver: Address.fromBech32(ESDT_CONTRACT_ADDRESS), + dataParts: dataParts, + gasLimit: this.config.gasLimitIssue, + addDataMovementGas: true, + amount: this.config.issueCost, + }).build(); + } + + createTransactionForIssuingSemiFungible(options: { + sender: IAddress; + tokenName: string; + tokenTicker: string; + canFreeze: boolean; + canWipe: boolean; + canPause: boolean; + canTransferNFTCreateRole: boolean; + canChangeOwner: boolean; + canUpgrade: boolean; + canAddSpecialRoles: boolean; + }): Transaction { + this.notifyAboutUnsettingBurnRoleGlobally(); + + const args = [ + new StringValue(options.tokenName), + new StringValue(options.tokenTicker), + new StringValue("canFreeze"), + new StringValue(this.boolToString(options.canFreeze)), + new StringValue("canWipe"), + new StringValue(this.boolToString(options.canWipe)), + new StringValue("canPause"), + new StringValue(this.boolToString(options.canPause)), + new StringValue("canTransferNFTCreateRole"), + new StringValue(this.boolToString(options.canTransferNFTCreateRole)), + new StringValue("canChangeOwner"), + new StringValue(this.boolToString(options.canChangeOwner)), + new StringValue("canUpgrade"), + new StringValue(this.boolToString(options.canUpgrade)), + new StringValue("canAddSpecialRoles"), + new StringValue(this.boolToString(options.canAddSpecialRoles)), + ]; + + const dataParts = ["issueSemiFungible", ...this.argSerializer.valuesToStrings(args)]; + + return new TransactionBuilder({ + config: this.config, + sender: options.sender, + receiver: Address.fromBech32(ESDT_CONTRACT_ADDRESS), + dataParts: dataParts, + gasLimit: this.config.gasLimitIssue, + addDataMovementGas: true, + amount: this.config.issueCost, + }).build(); + } + + createTransactionForIssuingNonFungible(options: { + sender: IAddress; + tokenName: string; + tokenTicker: string; + canFreeze: boolean; + canWipe: boolean; + canPause: boolean; + canTransferNFTCreateRole: boolean; + canChangeOwner: boolean; + canUpgrade: boolean; + canAddSpecialRoles: boolean; + }): Transaction { + this.notifyAboutUnsettingBurnRoleGlobally(); + + const args = [ + new StringValue(options.tokenName), + new StringValue(options.tokenTicker), + new StringValue("canFreeze"), + new StringValue(this.boolToString(options.canFreeze)), + new StringValue("canWipe"), + new StringValue(this.boolToString(options.canWipe)), + new StringValue("canPause"), + new StringValue(this.boolToString(options.canPause)), + new StringValue("canTransferNFTCreateRole"), + new StringValue(this.boolToString(options.canTransferNFTCreateRole)), + new StringValue("canChangeOwner"), + new StringValue(this.boolToString(options.canChangeOwner)), + new StringValue("canUpgrade"), + new StringValue(this.boolToString(options.canUpgrade)), + new StringValue("canAddSpecialRoles"), + new StringValue(this.boolToString(options.canAddSpecialRoles)), + ]; + + const dataParts = ["issueNonFungible", ...this.argSerializer.valuesToStrings(args)]; + + return new TransactionBuilder({ + config: this.config, + sender: options.sender, + receiver: Address.fromBech32(ESDT_CONTRACT_ADDRESS), + dataParts: dataParts, + gasLimit: this.config.gasLimitIssue, + addDataMovementGas: true, + amount: this.config.issueCost, + }).build(); + } + + createTransactionForRegisteringMetaESDT(options: { + sender: IAddress; + tokenName: string; + tokenTicker: string; + numDecimals: bigint; + canFreeze: boolean; + canWipe: boolean; + canPause: boolean; + canTransferNFTCreateRole: boolean; + canChangeOwner: boolean; + canUpgrade: boolean; + canAddSpecialRoles: boolean; + }): Transaction { + this.notifyAboutUnsettingBurnRoleGlobally(); + + const args = [ + new StringValue(options.tokenName), + new StringValue(options.tokenTicker), + new BigUIntValue(options.numDecimals), + new StringValue("canFreeze"), + new StringValue(this.boolToString(options.canFreeze)), + new StringValue("canWipe"), + new StringValue(this.boolToString(options.canWipe)), + new StringValue("canPause"), + new StringValue(this.boolToString(options.canPause)), + new StringValue("canTransferNFTCreateRole"), + new StringValue(this.boolToString(options.canTransferNFTCreateRole)), + new StringValue("canChangeOwner"), + new StringValue(this.boolToString(options.canChangeOwner)), + new StringValue("canUpgrade"), + new StringValue(this.boolToString(options.canUpgrade)), + new StringValue("canAddSpecialRoles"), + new StringValue(this.boolToString(options.canAddSpecialRoles)), + ]; + + const dataParts = ["registerMetaESDT", ...this.argSerializer.valuesToStrings(args)]; + + return new TransactionBuilder({ + config: this.config, + sender: options.sender, + receiver: Address.fromBech32(ESDT_CONTRACT_ADDRESS), + dataParts: dataParts, + gasLimit: this.config.gasLimitIssue, + addDataMovementGas: true, + amount: this.config.issueCost, + }).build(); + } + + createTransactionForRegisteringAndSettingRoles(options: { + sender: IAddress; + tokenName: string; + tokenTicker: string; + tokenType: RegisterAndSetAllRolesTokenType; + numDecimals: bigint; + }): Transaction { + this.notifyAboutUnsettingBurnRoleGlobally(); + + const dataParts = [ + "registerAndSetAllRoles", + ...this.argSerializer.valuesToStrings([ + new StringValue(options.tokenName), + new StringValue(options.tokenTicker), + new StringValue(options.tokenType), + new BigUIntValue(options.numDecimals), + ]), + ]; + + return new TransactionBuilder({ + config: this.config, + sender: options.sender, + receiver: Address.fromBech32(ESDT_CONTRACT_ADDRESS), + dataParts: dataParts, + gasLimit: this.config.gasLimitIssue, + addDataMovementGas: true, + amount: this.config.issueCost, + }).build(); + } + + createTransactionForSettingBurnRoleGlobally(options: { sender: IAddress; tokenIdentifier: string }): Transaction { + const dataParts = [ + "setBurnRoleGlobally", + ...this.argSerializer.valuesToStrings([new StringValue(options.tokenIdentifier)]), + ]; + + return new TransactionBuilder({ + config: this.config, + sender: options.sender, + receiver: Address.fromBech32(ESDT_CONTRACT_ADDRESS), + dataParts: dataParts, + gasLimit: this.config.gasLimitToggleBurnRoleGlobally, + addDataMovementGas: true, + }).build(); + } + + createTransactionForUnsettingBurnRoleGlobally(options: { sender: IAddress; tokenIdentifier: string }): Transaction { + const dataParts = [ + "unsetBurnRoleGlobally", + ...this.argSerializer.valuesToStrings([new StringValue(options.tokenIdentifier)]), + ]; + + return new TransactionBuilder({ + config: this.config, + sender: options.sender, + receiver: Address.fromBech32(ESDT_CONTRACT_ADDRESS), + dataParts: dataParts, + gasLimit: this.config.gasLimitToggleBurnRoleGlobally, + addDataMovementGas: true, + }).build(); + } + + createTransactionForSettingSpecialRoleOnFungibleToken(options: { + sender: IAddress; + user: IAddress; + tokenIdentifier: string; + addRoleLocalMint: boolean; + addRoleLocalBurn: boolean; + }): Transaction { + const args = [new StringValue(options.tokenIdentifier), new AddressValue(options.user)]; + + options.addRoleLocalMint ? args.push(new StringValue("ESDTRoleLocalMint")) : 0; + options.addRoleLocalBurn ? args.push(new StringValue("ESDTRoleLocalBurn")) : 0; + + const dataParts = ["setSpecialRole", ...this.argSerializer.valuesToStrings(args)]; + + return new TransactionBuilder({ + config: this.config, + sender: options.sender, + receiver: Address.fromBech32(ESDT_CONTRACT_ADDRESS), + dataParts: dataParts, + gasLimit: this.config.gasLimitSetSpecialRole, + addDataMovementGas: true, + }).build(); + } + + createTransactionForSettingSpecialRoleOnSemiFungibleToken(options: { + sender: IAddress; + user: IAddress; + tokenIdentifier: string; + addRoleNFTCreate: boolean; + addRoleNFTBurn: boolean; + addRoleNFTAddQuantity: boolean; + addRoleESDTTransferRole: boolean; + }): Transaction { + const args = [new StringValue(options.tokenIdentifier), new AddressValue(options.user)]; + + options.addRoleNFTCreate ? args.push(new StringValue("ESDTRoleNFTCreate")) : 0; + options.addRoleNFTBurn ? args.push(new StringValue("ESDTRoleNFTBurn")) : 0; + options.addRoleNFTAddQuantity ? args.push(new StringValue("ESDTRoleNFTAddQuantity")) : 0; + options.addRoleESDTTransferRole ? args.push(new StringValue("ESDTTransferRole")) : 0; + + const dataParts = ["setSpecialRole", ...this.argSerializer.valuesToStrings(args)]; + + return new TransactionBuilder({ + config: this.config, + sender: options.sender, + receiver: Address.fromBech32(ESDT_CONTRACT_ADDRESS), + dataParts: dataParts, + gasLimit: this.config.gasLimitSetSpecialRole, + addDataMovementGas: true, + }).build(); + } + + createTransactionForSettingSpecialRoleOnMetaESDT(options: { + sender: IAddress; + user: IAddress; + tokenIdentifier: string; + addRoleNFTCreate: boolean; + addRoleNFTBurn: boolean; + addRoleNFTAddQuantity: boolean; + addRoleESDTTransferRole: boolean; + }): Transaction { + return this.createTransactionForSettingSpecialRoleOnSemiFungibleToken(options); + } + + createTransactionForSettingSpecialRoleOnNonFungibleToken(options: { + sender: IAddress; + user: IAddress; + tokenIdentifier: string; + addRoleNFTCreate: boolean; + addRoleNFTBurn: boolean; + addRoleNFTUpdateAttributes: boolean; + addRoleNFTAddURI: boolean; + addRoleESDTTransferRole: boolean; + }): Transaction { + const args = [new StringValue(options.tokenIdentifier), new AddressValue(options.user)]; + + options.addRoleNFTCreate ? args.push(new StringValue("ESDTRoleNFTCreate")) : 0; + options.addRoleNFTBurn ? args.push(new StringValue("ESDTRoleNFTBurn")) : 0; + options.addRoleNFTUpdateAttributes ? args.push(new StringValue("ESDTRoleNFTUpdateAttributes")) : 0; + options.addRoleNFTAddURI ? args.push(new StringValue("ESDTRoleNFTAddURI")) : 0; + options.addRoleESDTTransferRole ? args.push(new StringValue("ESDTTransferRole")) : 0; + + const dataParts = ["setSpecialRole", ...this.argSerializer.valuesToStrings(args)]; + + return new TransactionBuilder({ + config: this.config, + sender: options.sender, + receiver: Address.fromBech32(ESDT_CONTRACT_ADDRESS), + dataParts: dataParts, + gasLimit: this.config.gasLimitSetSpecialRole, + addDataMovementGas: true, + }).build(); + } + + createTransactionForCreatingNFT(options: { + sender: IAddress; + tokenIdentifier: string; + initialQuantity: bigint; + name: string; + royalties: number; + hash: string; + attributes: Uint8Array; + uris: string[]; + }): Transaction { + const dataParts = [ + "ESDTNFTCreate", + ...this.argSerializer.valuesToStrings([ + new StringValue(options.tokenIdentifier), + new BigUIntValue(options.initialQuantity), + new StringValue(options.name), + new BigUIntValue(options.royalties), + new StringValue(options.hash), + new BytesValue(Buffer.from(options.attributes)), + ...options.uris.map((uri) => new StringValue(uri)), + ]), + ]; + + // Note that the following is an approximation (a reasonable one): + const nftData = options.name + options.hash + options.attributes + options.uris.join(""); + const storageGasLimit = this.config.gasLimitPerByte + BigInt(nftData.length); + + return new TransactionBuilder({ + config: this.config, + sender: options.sender, + receiver: options.sender, + dataParts: dataParts, + gasLimit: this.config.gasLimitEsdtNftCreate + storageGasLimit, + addDataMovementGas: true, + }).build(); + } + + createTransactionForPausing(options: { sender: IAddress; tokenIdentifier: string }): Transaction { + const dataParts = ["pause", ...this.argSerializer.valuesToStrings([new StringValue(options.tokenIdentifier)])]; + + return new TransactionBuilder({ + config: this.config, + sender: options.sender, + receiver: options.sender, + dataParts: dataParts, + gasLimit: this.config.gasLimitPausing, + addDataMovementGas: true, + }).build(); + } + + createTransactionForUnpausing(options: { sender: IAddress; tokenIdentifier: string }): Transaction { + const dataParts = [ + "unPause", + ...this.argSerializer.valuesToStrings([new StringValue(options.tokenIdentifier)]), + ]; + + return new TransactionBuilder({ + config: this.config, + sender: options.sender, + receiver: options.sender, + dataParts: dataParts, + gasLimit: this.config.gasLimitPausing, + addDataMovementGas: true, + }).build(); + } + + createTransactionForFreezing(options: { sender: IAddress; user: IAddress; tokenIdentifier: string }): Transaction { + const dataParts = [ + "freeze", + ...this.argSerializer.valuesToStrings([ + new StringValue(options.tokenIdentifier), + new AddressValue(options.user), + ]), + ]; + + return new TransactionBuilder({ + config: this.config, + sender: options.sender, + receiver: options.sender, + dataParts: dataParts, + gasLimit: this.config.gasLimitFreezing, + addDataMovementGas: true, + }).build(); + } + + createTransactionForUnfreezing(options: { + sender: IAddress; + user: IAddress; + tokenIdentifier: string; + }): Transaction { + const dataParts = [ + "UnFreeze", + ...this.argSerializer.valuesToStrings([ + new StringValue(options.tokenIdentifier), + new AddressValue(options.user), + ]), + ]; + + return new TransactionBuilder({ + config: this.config, + sender: options.sender, + receiver: options.sender, + dataParts: dataParts, + gasLimit: this.config.gasLimitFreezing, + addDataMovementGas: true, + }).build(); + } + + createTransactionForWiping(options: { sender: IAddress; user: IAddress; tokenIdentifier: string }): Transaction { + const dataParts = [ + "wipe", + ...this.argSerializer.valuesToStrings([ + new StringValue(options.tokenIdentifier), + new AddressValue(options.user), + ]), + ]; + + return new TransactionBuilder({ + config: this.config, + sender: options.sender, + receiver: options.sender, + dataParts: dataParts, + gasLimit: this.config.gasLimitWiping, + addDataMovementGas: true, + }).build(); + } + + createTransactionForLocalMint(options: { + sender: IAddress; + tokenIdentifier: string; + supplyToMint: bigint; + }): Transaction { + const dataParts = [ + "ESDTLocalMint", + ...this.argSerializer.valuesToStrings([ + new StringValue(options.tokenIdentifier), + new BigUIntValue(options.supplyToMint), + ]), + ]; + + return new TransactionBuilder({ + config: this.config, + sender: options.sender, + receiver: options.sender, + dataParts: dataParts, + gasLimit: this.config.gasLimitEsdtLocalMint, + addDataMovementGas: true, + }).build(); + } + + createTransactionForLocalBurning(options: { + sender: IAddress; + tokenIdentifier: string; + supplyToBurn: bigint; + }): Transaction { + const dataParts = [ + "ESDTLocalBurn", + ...this.argSerializer.valuesToStrings([ + new StringValue(options.tokenIdentifier), + new BigUIntValue(options.supplyToBurn), + ]), + ]; + + return new TransactionBuilder({ + config: this.config, + sender: options.sender, + receiver: options.sender, + dataParts: dataParts, + gasLimit: this.config.gasLimitEsdtLocalBurn, + addDataMovementGas: true, + }).build(); + } + + createTransactionForUpdatingAttributes(options: { + sender: IAddress; + tokenIdentifier: string; + tokenNonce: bigint; + attributes: Uint8Array; + }): Transaction { + const dataParts = [ + "ESDTNFTUpdateAttributes", + ...this.argSerializer.valuesToStrings([ + new StringValue(options.tokenIdentifier), + new BigUIntValue(options.tokenNonce), + new BytesValue(Buffer.from(options.attributes)), + ]), + ]; + + return new TransactionBuilder({ + config: this.config, + sender: options.sender, + receiver: options.sender, + dataParts: dataParts, + gasLimit: this.config.gasLimitEsdtNftUpdateAttributes, + addDataMovementGas: true, + }).build(); + } + + createTransactionForAddingQuantity(options: { + sender: IAddress; + tokenIdentifier: string; + tokenNonce: bigint; + quantityToAdd: bigint; + }): Transaction { + const dataParts = [ + "ESDTNFTAddQuantity", + ...this.argSerializer.valuesToStrings([ + new StringValue(options.tokenIdentifier), + new BigUIntValue(options.tokenNonce), + new BigUIntValue(options.quantityToAdd), + ]), + ]; + + return new TransactionBuilder({ + config: this.config, + sender: options.sender, + receiver: options.sender, + dataParts: dataParts, + gasLimit: this.config.gasLimitEsdtNftAddQuantity, + addDataMovementGas: true, + }).build(); + } + + createTransactionForBurningQuantity(options: { + sender: IAddress; + tokenIdentifier: string; + tokenNonce: bigint; + quantityToBurn: bigint; + }): Transaction { + const dataParts = [ + "ESDTNFTBurn", + ...this.argSerializer.valuesToStrings([ + new StringValue(options.tokenIdentifier), + new BigUIntValue(options.tokenNonce), + new BigUIntValue(options.quantityToBurn), + ]), + ]; + + return new TransactionBuilder({ + config: this.config, + sender: options.sender, + receiver: options.sender, + dataParts: dataParts, + gasLimit: this.config.gasLimitEsdtNftBurn, + addDataMovementGas: true, + }).build(); + } + + private notifyAboutUnsettingBurnRoleGlobally() { + Logger.info(` +========== +IMPORTANT! +========== +You are about to issue (register) a new token. This will set the role "ESDTRoleBurnForAll" (globally). +Once the token is registered, you can unset this role by calling "unsetBurnRoleGlobally" (in a separate transaction).`); + } + + private boolToString(value: boolean): string { + if (value) { + return this.trueAsString; + } + + return this.falseAsString; + } +} diff --git a/src/transactionsFactories/tokenTransfersDataBuilder.ts b/src/transactionsFactories/tokenTransfersDataBuilder.ts new file mode 100644 index 000000000..45cd92162 --- /dev/null +++ b/src/transactionsFactories/tokenTransfersDataBuilder.ts @@ -0,0 +1,56 @@ +import { IAddress } from "../interface"; +import { ArgSerializer } from "../smartcontracts/argSerializer"; +import { AddressValue, BigUIntValue, TokenIdentifierValue, TypedValue, U32Value } from "../smartcontracts/typesystem"; +import { TokenComputer, TokenTransfer } from "../tokens"; + +export class TokenTransfersDataBuilder { + private tokenComputer: TokenComputer; + private argsSerializer: ArgSerializer; + + constructor() { + this.tokenComputer = new TokenComputer(); + this.argsSerializer = new ArgSerializer(); + } + + buildDataPartsForESDTTransfer(transfer: TokenTransfer): string[] { + const args = this.argsSerializer.valuesToStrings([ + new TokenIdentifierValue(transfer.token.identifier), + new BigUIntValue(transfer.amount), + ]); + + return ["ESDTTransfer", ...args]; + } + + buildDataPartsForSingleESDTNFTTransfer(transfer: TokenTransfer, receiver: IAddress) { + const token = transfer.token; + const identifier = this.tokenComputer.extractIdentifierFromExtendedIdentifier(token.identifier); + + const args = this.argsSerializer.valuesToStrings([ + new TokenIdentifierValue(identifier), + new BigUIntValue(token.nonce), + new BigUIntValue(transfer.amount), + new AddressValue(receiver), + ]); + + return ["ESDTNFTTransfer", ...args]; + } + + buildDataPartsForMultiESDTNFTTransfer(receiver: IAddress, transfers: TokenTransfer[]) { + const argsTyped: TypedValue[] = [new AddressValue(receiver), new U32Value(transfers.length)]; + + for (const transfer of transfers) { + const identifier = this.tokenComputer.extractIdentifierFromExtendedIdentifier(transfer.token.identifier); + + argsTyped.push( + ...[ + new TokenIdentifierValue(identifier), + new BigUIntValue(transfer.token.nonce), + new BigUIntValue(transfer.amount), + ], + ); + } + + const args = this.argsSerializer.valuesToStrings(argsTyped); + return ["MultiESDTNFTTransfer", ...args]; + } +} diff --git a/src/transactionsFactories/transactionBuilder.ts b/src/transactionsFactories/transactionBuilder.ts new file mode 100644 index 000000000..6c163e816 --- /dev/null +++ b/src/transactionsFactories/transactionBuilder.ts @@ -0,0 +1,70 @@ +import { ARGUMENTS_SEPARATOR } from "../constants"; +import { IAddress, ITransactionPayload } from "../interface"; +import { Transaction } from "../transaction"; +import { TransactionPayload } from "../transactionPayload"; + +interface Config { + chainID: string; + minGasLimit: bigint; + gasLimitPerByte: bigint; +} + +/** + * @internal + */ +export class TransactionBuilder { + private config: Config; + private sender: IAddress; + private receiver: IAddress; + private dataParts: string[]; + private providedGasLimit: bigint; + private addDataMovementGas: boolean; + private amount?: bigint; + + constructor(options: { + config: Config; + sender: IAddress; + receiver: IAddress; + dataParts: string[]; + gasLimit: bigint; + addDataMovementGas: boolean; + amount?: bigint; + }) { + this.config = options.config; + this.sender = options.sender; + this.receiver = options.receiver; + this.dataParts = options.dataParts; + this.providedGasLimit = options.gasLimit; + this.addDataMovementGas = options.addDataMovementGas; + this.amount = options.amount; + } + + private computeGasLimit(payload: ITransactionPayload): bigint { + if (!this.addDataMovementGas) { + return this.providedGasLimit; + } + + const dataMovementGas = this.config.minGasLimit + this.config.gasLimitPerByte * BigInt(payload.length()); + const gasLimit = dataMovementGas + this.providedGasLimit; + return gasLimit; + } + + private buildTransactionPayload(): TransactionPayload { + const data = this.dataParts.join(ARGUMENTS_SEPARATOR); + return new TransactionPayload(data); + } + + build(): Transaction { + const data = this.buildTransactionPayload(); + const gasLimit = this.computeGasLimit(data); + + return new Transaction({ + sender: this.sender.bech32(), + receiver: this.receiver.bech32(), + gasLimit: gasLimit, + value: this.amount || 0n, + data: data.valueOf(), + chainID: this.config.chainID, + }); + } +} diff --git a/src/transactionsFactories/transactionsFactoryConfig.ts b/src/transactionsFactories/transactionsFactoryConfig.ts new file mode 100644 index 000000000..4de6201c3 --- /dev/null +++ b/src/transactionsFactories/transactionsFactoryConfig.ts @@ -0,0 +1,70 @@ +import { DEFAULT_HRP } from "../constants"; + +export class TransactionsFactoryConfig { + chainID: string; + addressHrp: string; + minGasLimit: bigint; + gasLimitPerByte: bigint; + gasLimitIssue: bigint; + gasLimitToggleBurnRoleGlobally: bigint; + gasLimitEsdtLocalMint: bigint; + gasLimitEsdtLocalBurn: bigint; + gasLimitSetSpecialRole: bigint; + gasLimitPausing: bigint; + gasLimitFreezing: bigint; + gasLimitWiping: bigint; + gasLimitEsdtNftCreate: bigint; + gasLimitEsdtNftUpdateAttributes: bigint; + gasLimitEsdtNftAddQuantity: bigint; + gasLimitEsdtNftBurn: bigint; + gasLimitStorePerByte: bigint; + issueCost: bigint; + gasLimitStake: bigint; + gasLimitUnstake: bigint; + gasLimitUnbond: bigint; + gasLimitCreateDelegationContract: bigint; + gasLimitDelegationOperations: bigint; + additionalGasLimitPerValidatorNode: bigint; + additionalGasLimitForDelegationOperations: bigint; + gasLimitESDTTransfer: bigint; + gasLimitESDTNFTTransfer: bigint; + gasLimitMultiESDTNFTTransfer: bigint; + + constructor(options: { chainID: string }) { + // General-purpose configuration + this.chainID = options.chainID; + this.addressHrp = DEFAULT_HRP; + this.minGasLimit = 50000n; + this.gasLimitPerByte = 1500n; + + // Configuration for token operations + this.gasLimitIssue = 60000000n; + this.gasLimitToggleBurnRoleGlobally = 60000000n; + this.gasLimitEsdtLocalMint = 300000n; + this.gasLimitEsdtLocalBurn = 300000n; + this.gasLimitSetSpecialRole = 60000000n; + this.gasLimitPausing = 60000000n; + this.gasLimitFreezing = 60000000n; + this.gasLimitWiping = 60000000n; + this.gasLimitEsdtNftCreate = 3000000n; + this.gasLimitEsdtNftUpdateAttributes = 1000000n; + this.gasLimitEsdtNftAddQuantity = 1000000n; + this.gasLimitEsdtNftBurn = 1000000n; + this.gasLimitStorePerByte = 50000n; + this.issueCost = 50000000000000000n; + + // Configuration for delegation operations + this.gasLimitStake = 5000000n; + this.gasLimitUnstake = 5000000n; + this.gasLimitUnbond = 5000000n; + this.gasLimitCreateDelegationContract = 50000000n; + this.gasLimitDelegationOperations = 1000000n; + this.additionalGasLimitPerValidatorNode = 6000000n; + this.additionalGasLimitForDelegationOperations = 10000000n; + + // Configuration for token transfers + this.gasLimitESDTTransfer = 200000n; + this.gasLimitESDTNFTTransfer = 200000n; + this.gasLimitMultiESDTNFTTransfer = 200000n; + } +} diff --git a/src/transactionsFactories/transferTransactionsFactory.spec.ts b/src/transactionsFactories/transferTransactionsFactory.spec.ts new file mode 100644 index 000000000..7251b9c42 --- /dev/null +++ b/src/transactionsFactories/transferTransactionsFactory.spec.ts @@ -0,0 +1,121 @@ +import { assert } from "chai"; +import { Address } from "../address"; +import { ErrBadUsage } from "../errors"; +import { Token, TokenTransfer } from "../tokens"; +import { TransactionsFactoryConfig } from "./transactionsFactoryConfig"; +import { TransferTransactionsFactory } from "./transferTransactionsFactory"; + +describe("test transfer transcations factory", function () { + const config = new TransactionsFactoryConfig({ chainID: "D" }); + const transferFactory = new TransferTransactionsFactory({ + config: config, + }); + + const alice = Address.fromBech32("erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"); + const bob = Address.fromBech32("erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx"); + + it("should throw error, no token transfer provided", async () => { + let transfers: any = []; + + assert.throw( + () => { + transferFactory.createTransactionForESDTTokenTransfer({ + sender: alice, + receiver: bob, + tokenTransfers: transfers, + }); + }, + ErrBadUsage, + "No token transfer has been provided", + ); + }); + + it("should create 'Transaction' for native token transfer without data", async () => { + const transaction = transferFactory.createTransactionForNativeTokenTransfer({ + sender: alice, + receiver: bob, + nativeAmount: 1000000000000000000n, + }); + + assert.equal(transaction.sender, alice.bech32()); + assert.equal(transaction.receiver, bob.bech32()); + assert.equal(transaction.value.valueOf(), 1000000000000000000n); + assert.equal(transaction.gasLimit.valueOf(), 50000n); + assert.deepEqual(transaction.data, new Uint8Array()); + }); + + it("should create 'Transaction' for native token transfer with data", async () => { + const transaction = transferFactory.createTransactionForNativeTokenTransfer({ + sender: alice, + receiver: bob, + nativeAmount: 1000000000000000000n, + data: Buffer.from("test data"), + }); + + assert.equal(transaction.sender, alice.bech32()); + assert.equal(transaction.receiver, bob.bech32()); + assert.equal(transaction.value.valueOf(), 1000000000000000000n); + assert.equal(transaction.gasLimit.valueOf(), 63500n); + assert.deepEqual(transaction.data, Buffer.from("test data")); + }); + + it("should create 'Transaction' for esdt transfer", async () => { + const fooToken = new Token({ identifier: "FOO-123456", nonce: 0n }); + const transfer = new TokenTransfer({ token: fooToken, amount: 1000000n }); + + const transaction = transferFactory.createTransactionForESDTTokenTransfer({ + sender: alice, + receiver: bob, + tokenTransfers: [transfer], + }); + + assert.equal(transaction.sender, alice.bech32()); + assert.equal(transaction.receiver, bob.bech32()); + assert.equal(transaction.value.valueOf(), 0n); + assert.equal(transaction.gasLimit.valueOf(), 410000n); + assert.deepEqual(transaction.data.toString(), "ESDTTransfer@464f4f2d313233343536@0f4240"); + }); + + it("should create 'Transaction' for nft transfer", async () => { + const nft = new Token({ identifier: "NFT-123456", nonce: 10n }); + const transfer = new TokenTransfer({ token: nft, amount: 1n }); + + const transaction = transferFactory.createTransactionForESDTTokenTransfer({ + sender: alice, + receiver: bob, + tokenTransfers: [transfer], + }); + + assert.equal(transaction.sender, alice.bech32()); + assert.equal(transaction.receiver, alice.bech32()); + assert.equal(transaction.value.valueOf(), 0n); + assert.equal(transaction.gasLimit.valueOf(), 1210500n); + assert.deepEqual( + transaction.data.toString(), + "ESDTNFTTransfer@4e46542d313233343536@0a@01@8049d639e5a6980d1cd2392abcce41029cda74a1563523a202f09641cc2618f8", + ); + }); + + it("should create 'Transaction' for multiple nft transfers", async () => { + const firstNft = new Token({ identifier: "NFT-123456", nonce: 10n }); + const firstTransfer = new TokenTransfer({ token: firstNft, amount: 1n }); + + const secondNft = new Token({ identifier: "TEST-987654", nonce: 1n }); + const secondTransfer = new TokenTransfer({ token: secondNft, amount: 1n }); + + const transaction = transferFactory.createTransactionForESDTTokenTransfer({ + sender: alice, + receiver: bob, + tokenTransfers: [firstTransfer, secondTransfer], + }); + + assert.equal(transaction.sender, alice.bech32()); + assert.equal(transaction.receiver, alice.bech32()); + assert.equal(transaction.value.valueOf(), 0n); + assert.equal(transaction.gasLimit.valueOf(), 1466000n); + assert.deepEqual( + transaction.data.toString(), + "MultiESDTNFTTransfer@8049d639e5a6980d1cd2392abcce41029cda74a1563523a202f09641cc2618f8@02@4e46542d313233343536@0a@01@544553542d393837363534@01@01", + ); + }); +}); diff --git a/src/transactionsFactories/transferTransactionsFactory.ts b/src/transactionsFactories/transferTransactionsFactory.ts new file mode 100644 index 000000000..4a431bea7 --- /dev/null +++ b/src/transactionsFactories/transferTransactionsFactory.ts @@ -0,0 +1,368 @@ +import { Err, ErrBadUsage } from "../errors"; +import { + IAddress, + IChainID, + IGasLimit, + IGasPrice, + INonce, + ITokenTransfer, + ITransactionPayload, + ITransactionValue, +} from "../interface"; +import { + AddressValue, + ArgSerializer, + BigUIntValue, + BytesValue, + TypedValue, + U16Value, + U64Value, +} from "../smartcontracts"; +import { TokenComputer, TokenTransfer } from "../tokens"; +import { Transaction } from "../transaction"; +import { TransactionPayload } from "../transactionPayload"; +import { TokenTransfersDataBuilder } from "./tokenTransfersDataBuilder"; +import { TransactionBuilder } from "./transactionBuilder"; + +const ADDITIONAL_GAS_FOR_ESDT_TRANSFER = 100000; +const ADDITIONAL_GAS_FOR_ESDT_NFT_TRANSFER = 800000; + +interface IConfig { + chainID: string; + minGasLimit: bigint; + gasLimitPerByte: bigint; + gasLimitESDTTransfer: bigint; + gasLimitESDTNFTTransfer: bigint; + gasLimitMultiESDTNFTTransfer: bigint; +} + +interface IGasEstimator { + forEGLDTransfer(dataLength: number): number; + forESDTTransfer(dataLength: number): number; + forESDTNFTTransfer(dataLength: number): number; + forMultiESDTNFTTransfer(dataLength: number, numTransfers: number): number; +} + +/** + * Use this class to create transactions for native token transfers (EGLD) or custom tokens transfers (ESDT/NTF/MetaESDT). + */ +export class TransferTransactionsFactory { + private readonly config?: IConfig; + private readonly tokenTransfersDataBuilder?: TokenTransfersDataBuilder; + private readonly tokenComputer?: TokenComputer; + private readonly gasEstimator?: IGasEstimator; + + /** + * Should be instantiated using `Config` and `TokenComputer`. + * Instantiating this class using GasEstimator represents the legacy version of this class. + * The legacy version contains methods like `createEGLDTransfer`, `createESDTTransfer`, `createESDTNFTTransfer` and `createMultiESDTNFTTransfer`. + * This was done in order to minimize breaking changes in client code. + */ + constructor(options: IGasEstimator | { config: IConfig }) { + if (this.isGasEstimator(options)) { + this.gasEstimator = options; + } else { + this.config = options.config; + this.tokenComputer = new TokenComputer(); + this.tokenTransfersDataBuilder = new TokenTransfersDataBuilder(); + } + } + + private isGasEstimator(options: any): options is IGasEstimator { + return ( + typeof options === "object" && + typeof options.forEGLDTransfer === "function" && + typeof options.forESDTTransfer === "function" && + typeof options.forESDTNFTTransfer === "function" && + typeof options.forMultiESDTNFTTransfer === "function" + ); + } + + private isGasEstimatorDefined(): boolean { + return this.gasEstimator !== undefined; + } + + private ensureMembersAreDefined() { + if (this.config === undefined) { + throw new Err("'config' is not defined"); + } + + if (this.tokenTransfersDataBuilder === undefined) { + throw new Err("`dataArgsBuilder is not defined`"); + } + + if (this.tokenComputer === undefined) { + throw new Err("`tokenComputer is not defined`"); + } + } + + createTransactionForNativeTokenTransfer(options: { + sender: IAddress; + receiver: IAddress; + nativeAmount: bigint; + data?: Uint8Array; + }): Transaction { + this.ensureMembersAreDefined(); + + const data = options.data || new Uint8Array(); + + return new Transaction({ + sender: options.sender.bech32(), + receiver: options.receiver.bech32(), + chainID: this.config!.chainID, + gasLimit: this.computeGasForMoveBalance(this.config!, data), + data: data, + value: options.nativeAmount, + }); + } + + createTransactionForESDTTokenTransfer(options: { + sender: IAddress; + receiver: IAddress; + tokenTransfers: TokenTransfer[]; + }): Transaction { + this.ensureMembersAreDefined(); + + const numberOfTransfers = options.tokenTransfers.length; + + if (numberOfTransfers === 0) { + throw new ErrBadUsage("No token transfer has been provided"); + } + + if (numberOfTransfers === 1) { + return this.createSingleESDTTransferTransaction(options); + } + + const dataParts = this.tokenTransfersDataBuilder!.buildDataPartsForMultiESDTNFTTransfer( + options.receiver, + options.tokenTransfers, + ); + + const extraGasForTransfer = + this.config!.gasLimitMultiESDTNFTTransfer * BigInt(numberOfTransfers) + + BigInt(ADDITIONAL_GAS_FOR_ESDT_NFT_TRANSFER); + + return new TransactionBuilder({ + config: this.config!, + sender: options.sender, + receiver: options.sender, + dataParts: dataParts, + gasLimit: extraGasForTransfer, + addDataMovementGas: true, + }).build(); + } + + /** + * This is a legacy method. Can only be used if the class was instantiated using `GasEstimator`. + * Use {@link createTransactionForNativeTokenTransfer} instead. + */ + createEGLDTransfer(args: { + nonce?: INonce; + value: ITransactionValue; + receiver: IAddress; + sender: IAddress; + gasPrice?: IGasPrice; + gasLimit?: IGasLimit; + data?: ITransactionPayload; + chainID: IChainID; + }) { + if (!this.isGasEstimatorDefined()) { + throw new Err( + "You are calling a legacy function to create an EGLD transfer transaction. If this is your intent, then instantiate the class using a `GasEstimator`. Or, instead, use the new, recommended `createTransactionForNativeTokenTransfer` method.", + ); + } + + const dataLength = args.data?.length() || 0; + const estimatedGasLimit = this.gasEstimator!.forEGLDTransfer(dataLength); + + return new Transaction({ + nonce: args.nonce, + value: args.value, + receiver: args.receiver, + sender: args.sender, + gasPrice: args.gasPrice, + gasLimit: args.gasLimit || estimatedGasLimit, + data: args.data, + chainID: args.chainID, + }); + } + + /** + * This is a legacy method. Can only be used if the class was instantiated using `GasEstimator`. + * Use {@link createTransactionForESDTTokenTransfer} instead. + */ + createESDTTransfer(args: { + tokenTransfer: ITokenTransfer; + nonce?: INonce; + receiver: IAddress; + sender: IAddress; + gasPrice?: IGasPrice; + gasLimit?: IGasLimit; + chainID: IChainID; + }) { + if (!this.isGasEstimatorDefined()) { + throw new Err( + "You are calling a legacy function to create an ESDT transfer transaction. If this is your intent, then instantiate the class using a `GasEstimator`. Or, instead, use the new, recommended `createTransactionForESDTTokenTransfer` method.", + ); + } + + const { argumentsString } = new ArgSerializer().valuesToString([ + // The token identifier + BytesValue.fromUTF8(args.tokenTransfer.tokenIdentifier), + // The transfered amount + new BigUIntValue(args.tokenTransfer.valueOf()), + ]); + + const data = `ESDTTransfer@${argumentsString}`; + const transactionPayload = new TransactionPayload(data); + const dataLength = transactionPayload.length() || 0; + const estimatedGasLimit = this.gasEstimator!.forESDTTransfer(dataLength); + + return new Transaction({ + nonce: args.nonce, + receiver: args.receiver, + sender: args.sender, + gasPrice: args.gasPrice, + gasLimit: args.gasLimit || estimatedGasLimit, + data: transactionPayload, + chainID: args.chainID, + }); + } + + /** + * This is a legacy method. Can only be used if the class was instantiated using `GasEstimator`. + * Use {@link createTransactionForESDTTokenTransfer} instead. + */ + createESDTNFTTransfer(args: { + tokenTransfer: ITokenTransfer; + nonce?: INonce; + destination: IAddress; + sender: IAddress; + gasPrice?: IGasPrice; + gasLimit?: IGasLimit; + chainID: IChainID; + }) { + if (!this.isGasEstimatorDefined()) { + throw new Err( + "You are calling a legacy function to create an ESDTNFT transfer transaction. If this is your intent, then instantiate the class using a `GasEstimator`. Or, instead, use the new, recommended `createTransactionForESDTTokenTransfer` method.", + ); + } + + const { argumentsString } = new ArgSerializer().valuesToString([ + // The token identifier + BytesValue.fromUTF8(args.tokenTransfer.tokenIdentifier), + // The nonce of the token + new U64Value(args.tokenTransfer.nonce), + // The transferred quantity + new BigUIntValue(args.tokenTransfer.valueOf()), + // The destination address + new AddressValue(args.destination), + ]); + + const data = `ESDTNFTTransfer@${argumentsString}`; + const transactionPayload = new TransactionPayload(data); + const dataLength = transactionPayload.length() || 0; + const estimatedGasLimit = this.gasEstimator!.forESDTNFTTransfer(dataLength); + + return new Transaction({ + nonce: args.nonce, + receiver: args.sender, + sender: args.sender, + gasPrice: args.gasPrice, + gasLimit: args.gasLimit || estimatedGasLimit, + data: transactionPayload, + chainID: args.chainID, + }); + } + + /** + * This is a legacy method. Can only be used if the class was instantiated using `GasEstimator`. + * Use {@link createTransactionForESDTTokenTransfer} instead. + */ + createMultiESDTNFTTransfer(args: { + tokenTransfers: ITokenTransfer[]; + nonce?: INonce; + destination: IAddress; + sender: IAddress; + gasPrice?: IGasPrice; + gasLimit?: IGasLimit; + chainID: IChainID; + }) { + if (!this.isGasEstimatorDefined()) { + throw new Err( + "You are calling a legacy function to create a MultiESDTNFT transfer transaction. If this is your intent, then instantiate the class using a `GasEstimator`. Or, instead, use the new, recommended `createTransactionForESDTTokenTransfer` method.", + ); + } + + const parts: TypedValue[] = [ + // The destination address + new AddressValue(args.destination), + // Number of tokens + new U16Value(args.tokenTransfers.length), + ]; + + for (const payment of args.tokenTransfers) { + parts.push( + ...[ + // The token identifier + BytesValue.fromUTF8(payment.tokenIdentifier), + // The nonce of the token + new U64Value(payment.nonce), + // The transfered quantity + new BigUIntValue(payment.valueOf()), + ], + ); + } + + const { argumentsString } = new ArgSerializer().valuesToString(parts); + const data = `MultiESDTNFTTransfer@${argumentsString}`; + const transactionPayload = new TransactionPayload(data); + const dataLength = transactionPayload.length() || 0; + const estimatedGasLimit = this.gasEstimator!.forMultiESDTNFTTransfer(dataLength, args.tokenTransfers.length); + + return new Transaction({ + nonce: args.nonce, + receiver: args.sender, + sender: args.sender, + gasPrice: args.gasPrice, + gasLimit: args.gasLimit || estimatedGasLimit, + data: transactionPayload, + chainID: args.chainID, + }); + } + + private createSingleESDTTransferTransaction(options: { + sender: IAddress; + receiver: IAddress; + tokenTransfers: TokenTransfer[]; + }): Transaction { + this.ensureMembersAreDefined(); + + let dataParts: string[] = []; + const transfer = options.tokenTransfers[0]; + let extraGasForTransfer = 0n; + let receiver = options.receiver; + + if (this.tokenComputer!.isFungible(transfer.token)) { + dataParts = this.tokenTransfersDataBuilder!.buildDataPartsForESDTTransfer(transfer); + extraGasForTransfer = this.config!.gasLimitESDTTransfer + BigInt(ADDITIONAL_GAS_FOR_ESDT_TRANSFER); + } else { + dataParts = this.tokenTransfersDataBuilder!.buildDataPartsForSingleESDTNFTTransfer(transfer, receiver); + extraGasForTransfer = this.config!.gasLimitESDTNFTTransfer + BigInt(ADDITIONAL_GAS_FOR_ESDT_NFT_TRANSFER); + receiver = options.sender; + } + + return new TransactionBuilder({ + config: this.config!, + sender: options.sender, + receiver: receiver, + dataParts: dataParts, + gasLimit: extraGasForTransfer, + addDataMovementGas: true, + }).build(); + } + + private computeGasForMoveBalance(config: IConfig, data: Uint8Array): bigint { + return config.minGasLimit + config.gasLimitPerByte * BigInt(data.length); + } +} diff --git a/src/transactionsOutcomeParsers/delegationTransactionsOutcomeParser.spec.ts b/src/transactionsOutcomeParsers/delegationTransactionsOutcomeParser.spec.ts new file mode 100644 index 000000000..c57ac28c4 --- /dev/null +++ b/src/transactionsOutcomeParsers/delegationTransactionsOutcomeParser.spec.ts @@ -0,0 +1,67 @@ +import { assert } from "chai"; +import { Address } from "../address"; +import { b64TopicsToBytes } from "../testutils"; +import { DelegationTransactionsOutcomeParser } from "./delegationTransactionsOutcomeParser"; +import { SmartContractResult, TransactionEvent, TransactionLogs, TransactionOutcome } from "./resources"; + +describe("test delegation transactions outcome parser", () => { + const parser = new DelegationTransactionsOutcomeParser(); + + it("should test parseCreateNewDelegationContract ", () => { + const contractAddress = Address.fromBech32("erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqy8lllls62y8s5"); + let encodedTopics = [ + "Q8M8GTdWSAAA", + "Q8M8GTdWSAAA", + "AQ==", + "Q8M8GTdWSAAA", + "AAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAABD///8=", + ]; + + const delegateEvent = new TransactionEvent({ + address: "erd18s6a06ktr2v6fgxv4ffhauxvptssnaqlds45qgsrucemlwc8rawq553rt2", + identifier: "delegate", + topics: b64TopicsToBytes(encodedTopics), + }); + + encodedTopics = [ + "AAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAABD///8=", + "PDXX6ssamaSgzKpTfvDMCuEJ9B9sK0AiA+Yzv7sHH1w=", + ]; + const scDeployEvent = new TransactionEvent({ + address: "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqy8lllls62y8s5", + identifier: "SCDeploy", + topics: b64TopicsToBytes(encodedTopics), + }); + + const logs = new TransactionLogs({ events: [delegateEvent, scDeployEvent] }); + + encodedTopics = ["b2g6sUl6beG17FCUIkFwCOTGJjoJJi5SjkP2077e6xA="]; + const scResultEvent = new TransactionEvent({ + address: "erd18s6a06ktr2v6fgxv4ffhauxvptssnaqlds45qgsrucemlwc8rawq553rt2", + identifier: "completedTxEvent", + topics: b64TopicsToBytes(encodedTopics), + }); + + const scResultLog = new TransactionLogs({ + address: "erd18s6a06ktr2v6fgxv4ffhauxvptssnaqlds45qgsrucemlwc8rawq553rt2", + events: [scResultEvent], + }); + + const scResult = new SmartContractResult({ + sender: "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqylllslmq6y6", + receiver: "erd18s6a06ktr2v6fgxv4ffhauxvptssnaqlds45qgsrucemlwc8rawq553rt2", + data: Buffer.from( + "QDZmNmJAMDAwMDAwMDAwMDAwMDAwMDAwMDEwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAxMGZmZmZmZg==", + "base64", + ), + logs: scResultLog, + }); + + const txOutcome = new TransactionOutcome({ smartContractResults: [scResult], logs: logs }); + + const outcome = parser.parseCreateNewDelegationContract(txOutcome); + + assert.lengthOf(outcome, 1); + assert.equal(outcome[0].contractAddress, contractAddress.toBech32()); + }); +}); diff --git a/src/transactionsOutcomeParsers/delegationTransactionsOutcomeParser.ts b/src/transactionsOutcomeParsers/delegationTransactionsOutcomeParser.ts new file mode 100644 index 000000000..305949058 --- /dev/null +++ b/src/transactionsOutcomeParsers/delegationTransactionsOutcomeParser.ts @@ -0,0 +1,40 @@ +import { Address } from "../address"; +import { ErrParseTransactionOutcome } from "../errors"; +import { TransactionEvent, TransactionOutcome, findEventsByIdentifier } from "./resources"; + +export class DelegationTransactionsOutcomeParser { + constructor() {} + + parseCreateNewDelegationContract(transactionOutcome: TransactionOutcome): { contractAddress: string }[] { + this.ensureNoError(transactionOutcome.logs.events); + + const events = findEventsByIdentifier(transactionOutcome, "SCDeploy"); + + return events.map((event) => ({ contractAddress: this.extractContractAddress(event) })); + } + + private ensureNoError(transactionEvents: TransactionEvent[]) { + for (const event of transactionEvents) { + if (event.identifier == "signalError") { + const data = Buffer.from(event.dataItems[0]?.toString().slice(1)).toString() || ""; + const message = this.decodeTopicAsString(event.topics[1]); + + throw new ErrParseTransactionOutcome( + `encountered signalError: ${message} (${Buffer.from(data, "hex").toString()})`, + ); + } + } + } + + private extractContractAddress(event: TransactionEvent): string { + if (!event.topics[0]?.length) { + return ""; + } + const address = Buffer.from(event.topics[0]); + return Address.fromBuffer(address).bech32(); + } + + private decodeTopicAsString(topic: Uint8Array): string { + return Buffer.from(topic).toString(); + } +} diff --git a/src/transactionsOutcomeParsers/index.ts b/src/transactionsOutcomeParsers/index.ts new file mode 100644 index 000000000..b50f8ba9c --- /dev/null +++ b/src/transactionsOutcomeParsers/index.ts @@ -0,0 +1,5 @@ +export * from "./delegationTransactionsOutcomeParser"; +export * from "./resources"; +export * from "./smartContractTransactionsOutcomeParser"; +export * from "./tokenManagementTransactionsOutcomeParser"; +export * from "./transactionEventsParser"; diff --git a/src/transactionsOutcomeParsers/resources.spec.ts b/src/transactionsOutcomeParsers/resources.spec.ts new file mode 100644 index 000000000..1d8fbd8ea --- /dev/null +++ b/src/transactionsOutcomeParsers/resources.spec.ts @@ -0,0 +1,69 @@ +import { assert } from "chai"; +import { + SmartContractResult, + TransactionEvent, + TransactionLogs, + TransactionOutcome, + findEventsByFirstTopic, + findEventsByIdentifier, +} from "./resources"; + +describe("test resources", () => { + it("finds events by identifier, by first topic", async function () { + const outcome = new TransactionOutcome({ + logs: new TransactionLogs({ + events: [ + new TransactionEvent({ + identifier: "foo", + topics: [Buffer.from("a")], + }), + ], + }), + smartContractResults: [ + new SmartContractResult({ + logs: new TransactionLogs({ + events: [ + new TransactionEvent({ + identifier: "foo", + topics: [Buffer.from("b")], + }), + new TransactionEvent({ + identifier: "bar", + topics: [Buffer.from("c")], + }), + ], + }), + }), + ], + }); + + const foundByIdentifierFoo = findEventsByIdentifier(outcome, "foo"); + const foundByIdentifierBar = findEventsByIdentifier(outcome, "bar"); + const foundByTopic = findEventsByFirstTopic(outcome, "b"); + + assert.deepEqual(foundByIdentifierFoo, [ + new TransactionEvent({ + identifier: "foo", + topics: [Buffer.from("a")], + }), + new TransactionEvent({ + identifier: "foo", + topics: [Buffer.from("b")], + }), + ]); + + assert.deepEqual(foundByIdentifierBar, [ + new TransactionEvent({ + identifier: "bar", + topics: [Buffer.from("c")], + }), + ]); + + assert.deepEqual(foundByTopic, [ + new TransactionEvent({ + identifier: "foo", + topics: [Buffer.from("b")], + }), + ]); + }); +}); diff --git a/src/transactionsOutcomeParsers/resources.ts b/src/transactionsOutcomeParsers/resources.ts new file mode 100644 index 000000000..b30bd2f1c --- /dev/null +++ b/src/transactionsOutcomeParsers/resources.ts @@ -0,0 +1,100 @@ +export class TransactionEvent { + address: string; + identifier: string; + topics: Uint8Array[]; + dataItems: Uint8Array[]; + + constructor(init: Partial) { + this.address = ""; + this.identifier = ""; + this.topics = []; + this.dataItems = []; + + Object.assign(this, init); + } +} + +export class TransactionLogs { + address: string; + events: TransactionEvent[]; + + constructor(init: Partial) { + this.address = ""; + this.events = []; + + Object.assign(this, init); + } +} + +export class SmartContractResult { + sender: string; + receiver: string; + data: Uint8Array; + logs: TransactionLogs; + + constructor(init: Partial) { + this.sender = ""; + this.receiver = ""; + this.data = new Uint8Array(); + this.logs = new TransactionLogs({}); + + Object.assign(this, init); + } +} + +export class TransactionOutcome { + directSmartContractCallOutcome: SmartContractCallOutcome; + smartContractResults: SmartContractResult[]; + logs: TransactionLogs; + + constructor(init: Partial) { + this.directSmartContractCallOutcome = new SmartContractCallOutcome({}); + this.smartContractResults = []; + this.logs = new TransactionLogs({}); + + Object.assign(this, init); + } +} + +export class SmartContractCallOutcome { + function: string; + returnDataParts: Uint8Array[]; + returnMessage: string; + returnCode: string; + + constructor(init: Partial) { + this.function = ""; + this.returnDataParts = []; + this.returnMessage = ""; + this.returnCode = ""; + + Object.assign(this, init); + } +} + +export function findEventsByPredicate( + transactionOutcome: TransactionOutcome, + predicate: (event: TransactionEvent) => boolean, +): TransactionEvent[] { + return gatherAllEvents(transactionOutcome).filter(predicate); +} + +export function findEventsByIdentifier(transactionOutcome: TransactionOutcome, identifier: string): TransactionEvent[] { + return findEventsByPredicate(transactionOutcome, (event) => event.identifier == identifier); +} + +export function findEventsByFirstTopic(transactionOutcome: TransactionOutcome, topic: string): TransactionEvent[] { + return findEventsByPredicate(transactionOutcome, (event) => event.topics[0]?.toString() == topic); +} + +export function gatherAllEvents(transactionOutcome: TransactionOutcome): TransactionEvent[] { + const allEvents = []; + + allEvents.push(...transactionOutcome.logs.events); + + for (const item of transactionOutcome.smartContractResults) { + allEvents.push(...item.logs.events); + } + + return allEvents; +} diff --git a/src/transactionsOutcomeParsers/smartContractTransactionsOutcomeParser.spec.ts b/src/transactionsOutcomeParsers/smartContractTransactionsOutcomeParser.spec.ts new file mode 100644 index 000000000..5bbcbdd52 --- /dev/null +++ b/src/transactionsOutcomeParsers/smartContractTransactionsOutcomeParser.spec.ts @@ -0,0 +1,239 @@ +import { + ContractResultItem, + ContractResults, + TransactionEventTopic, + TransactionOnNetwork, + TransactionEvent as TransactionOnNetworkEvent, + TransactionLogs as TransactionOnNetworkLogs, +} from "@multiversx/sdk-network-providers"; +import BigNumber from "bignumber.js"; +import { assert } from "chai"; +import { Address } from "../address"; +import { TransactionsConverter } from "../converters/transactionsConverter"; +import { loadAbiRegistry } from "../testutils"; +import { SmartContractCallOutcome, TransactionEvent, TransactionLogs, TransactionOutcome } from "./resources"; +import { SmartContractTransactionsOutcomeParser } from "./smartContractTransactionsOutcomeParser"; + +describe("test smart contract transactions outcome parser", () => { + it("parses deploy outcome (minimalistic)", async function () { + const contract = Address.fromBech32("erd1qqqqqqqqqqqqqpgqqacl85rd0gl2q8wggl8pwcyzcr4fflc5d8ssve45cj"); + const deployer = Address.fromBech32("erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"); + const codeHash = Buffer.from("abba", "hex"); + + const parser = new SmartContractTransactionsOutcomeParser(); + + const parsed = parser.parseDeploy({ + transactionOutcome: new TransactionOutcome({ + directSmartContractCallOutcome: new SmartContractCallOutcome({ + returnCode: "ok", + returnMessage: "ok", + }), + logs: new TransactionLogs({ + events: [ + new TransactionEvent({ + identifier: "SCDeploy", + topics: [contract.getPublicKey(), deployer.getPublicKey(), codeHash], + }), + ], + }), + }), + }); + + assert.equal(parsed.returnCode, "ok"); + assert.equal(parsed.returnMessage, "ok"); + assert.deepEqual(parsed.contracts, [ + { + address: contract.toBech32(), + ownerAddress: deployer.toBech32(), + codeHash: codeHash, + }, + ]); + }); + + it("parses deploy outcome", async function () { + const contract = Address.fromBech32("erd1qqqqqqqqqqqqqpgqqacl85rd0gl2q8wggl8pwcyzcr4fflc5d8ssve45cj"); + const deployer = Address.fromBech32("erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"); + const codeHash = Buffer.from("abba", "hex"); + + const parser = new SmartContractTransactionsOutcomeParser(); + const transactionsConverter = new TransactionsConverter(); + + const transactionOnNetwork = new TransactionOnNetwork({ + nonce: 7, + logs: new TransactionOnNetworkLogs({ + events: [ + new TransactionOnNetworkEvent({ + identifier: "SCDeploy", + topics: [ + new TransactionEventTopic(contract.getPublicKey().toString("base64")), + new TransactionEventTopic(deployer.getPublicKey().toString("base64")), + new TransactionEventTopic(codeHash.toString("base64")), + ], + }), + ], + }), + contractResults: new ContractResults([ + new ContractResultItem({ + nonce: 8, + data: "@6f6b", + }), + ]), + }); + + const transactionOutcome = transactionsConverter.transactionOnNetworkToOutcome(transactionOnNetwork); + const parsed = parser.parseDeploy({ transactionOutcome }); + + assert.equal(parsed.returnCode, "ok"); + assert.equal(parsed.returnMessage, "ok"); + assert.deepEqual(parsed.contracts, [ + { + address: contract.toBech32(), + ownerAddress: deployer.toBech32(), + codeHash: codeHash, + }, + ]); + }); + + it("parses deploy outcome (with error)", async function () { + const deployer = Address.fromBech32("erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"); + + const parser = new SmartContractTransactionsOutcomeParser(); + const transactionsConverter = new TransactionsConverter(); + + const transactionOnNetwork = new TransactionOnNetwork({ + nonce: 7, + logs: new TransactionOnNetworkLogs({ + events: [ + new TransactionOnNetworkEvent({ + identifier: "signalError", + topics: [ + new TransactionEventTopic(deployer.getPublicKey().toString("base64")), + new TransactionEventTopic(Buffer.from("wrong number of arguments").toString("base64")), + ], + data: "@75736572206572726f72", + }), + ], + }), + }); + + const transactionOutcome = transactionsConverter.transactionOnNetworkToOutcome(transactionOnNetwork); + const parsed = parser.parseDeploy({ transactionOutcome }); + + assert.equal(parsed.returnCode, "user error"); + assert.equal(parsed.returnMessage, "wrong number of arguments"); + assert.deepEqual(parsed.contracts, []); + }); + + it("parses execute outcome, without ABI (minimalistic)", function () { + const parser = new SmartContractTransactionsOutcomeParser(); + + const parsed = parser.parseExecute({ + transactionOutcome: new TransactionOutcome({ + directSmartContractCallOutcome: new SmartContractCallOutcome({ + function: "hello", + returnCode: "ok", + returnMessage: "ok", + returnDataParts: [Buffer.from([42])], + }), + }), + }); + + assert.deepEqual(parsed.values, [Buffer.from([42])]); + assert.equal(parsed.returnCode, "ok"); + assert.equal(parsed.returnMessage, "ok"); + }); + + it("parses execute outcome, without ABI", function () { + const parser = new SmartContractTransactionsOutcomeParser(); + const transactionsConverter = new TransactionsConverter(); + const transactionOnNetwork = new TransactionOnNetwork({ + nonce: 7, + contractResults: new ContractResults([ + new ContractResultItem({ + nonce: 8, + data: "@6f6b@2a", + }), + ]), + }); + + const transactionOutcome = transactionsConverter.transactionOnNetworkToOutcome(transactionOnNetwork); + + const parsed = parser.parseExecute({ transactionOutcome }); + + assert.deepEqual(parsed.values, [Buffer.from([42])]); + assert.equal(parsed.returnCode, "ok"); + assert.equal(parsed.returnMessage, "ok"); + }); + + it("parses execute outcome, with ABI (minimalistic)", async function () { + const parser = new SmartContractTransactionsOutcomeParser({ + abi: await loadAbiRegistry("src/testdata/answer.abi.json"), + }); + + const parsed = parser.parseExecute({ + transactionOutcome: new TransactionOutcome({ + directSmartContractCallOutcome: new SmartContractCallOutcome({ + // For the sake of the test, let's say that we've called this function as a transaction, not as a query. + function: "getUltimateAnswer", + returnCode: "ok", + returnMessage: "ok", + returnDataParts: [Buffer.from([42])], + }), + }), + }); + + // At this moment, U64Value.valueOf() returns a BigNumber. This might change in the future. + assert.deepEqual(parsed.values, [new BigNumber("42")]); + assert.equal(parsed.returnCode, "ok"); + assert.equal(parsed.returnMessage, "ok"); + }); + + it("parses execute outcome, with ABI", async function () { + const parser = new SmartContractTransactionsOutcomeParser({ + abi: await loadAbiRegistry("src/testdata/answer.abi.json"), + }); + + const transactionsConverter = new TransactionsConverter(); + const transactionOnNetwork = new TransactionOnNetwork({ + nonce: 7, + function: "getUltimateAnswer", + contractResults: new ContractResults([ + new ContractResultItem({ + nonce: 8, + data: "@6f6b@2a", + }), + ]), + }); + + const transactionOutcome = transactionsConverter.transactionOnNetworkToOutcome(transactionOnNetwork); + const parsed = parser.parseExecute({ transactionOutcome }); + + // At this moment, U64Value.valueOf() returns a BigNumber. This might change in the future. + assert.deepEqual(parsed.values, [new BigNumber("42")]); + assert.equal(parsed.returnCode, "ok"); + assert.equal(parsed.returnMessage, "ok"); + }); + + it("cannot parse execute outcome, with ABI, when function name is missing", async function () { + const parser = new SmartContractTransactionsOutcomeParser({ + abi: await loadAbiRegistry("src/testdata/answer.abi.json"), + }); + + const transactionsConverter = new TransactionsConverter(); + const transactionOnNetwork = new TransactionOnNetwork({ + nonce: 7, + contractResults: new ContractResults([ + new ContractResultItem({ + nonce: 8, + data: "@6f6b@2a", + }), + ]), + }); + + const transactionOutcome = transactionsConverter.transactionOnNetworkToOutcome(transactionOnNetwork); + + assert.throws(() => { + parser.parseExecute({ transactionOutcome }); + }, 'Function name is not available in the transaction outcome, thus endpoint definition (ABI) cannot be picked (for parsing). Maybe provide the "function" parameter explicitly?'); + }); +}); diff --git a/src/transactionsOutcomeParsers/smartContractTransactionsOutcomeParser.ts b/src/transactionsOutcomeParsers/smartContractTransactionsOutcomeParser.ts new file mode 100644 index 000000000..7c240504c --- /dev/null +++ b/src/transactionsOutcomeParsers/smartContractTransactionsOutcomeParser.ts @@ -0,0 +1,118 @@ +import { Address } from "../address"; +import { Err } from "../errors"; +import { EndpointDefinition, ResultsParser, ReturnCode, Type, UntypedOutcomeBundle } from "../smartcontracts"; +import { TransactionEvent, TransactionOutcome, findEventsByIdentifier } from "./resources"; + +interface IAbi { + getEndpoint(name: string): EndpointDefinition; +} + +interface IParameterDefinition { + type: Type; +} + +interface ILegacyResultsParser { + parseOutcomeFromUntypedBundle( + bundle: UntypedOutcomeBundle, + endpoint: { output: IParameterDefinition[] }, + ): { + values: any[]; + returnCode: { valueOf(): string }; + returnMessage: string; + }; +} + +export class SmartContractTransactionsOutcomeParser { + private readonly abi?: IAbi; + private readonly legacyResultsParser: ILegacyResultsParser; + + constructor(options?: { abi?: IAbi; legacyResultsParser?: ILegacyResultsParser }) { + this.abi = options?.abi; + + // Prior v13, we've advertised that people can override the "ResultsParser" to alter it's behavior in case of exotic flows. + // Now, since the new "SmartContractTransactionsOutcomeParser" (still) depends on the legacy "ResultsParser", + // at least until "return data parts of direct outcome of contract call" are included on API & Proxy responses (on GET transaction), + // we have to allow the same level of customization (for exotic flows). + this.legacyResultsParser = options?.legacyResultsParser || new ResultsParser(); + } + + parseDeploy(options: { transactionOutcome: TransactionOutcome }): { + returnCode: string; + returnMessage: string; + contracts: { + address: string; + ownerAddress: string; + codeHash: Uint8Array; + }[]; + } { + const directCallOutcome = options.transactionOutcome.directSmartContractCallOutcome; + const events = findEventsByIdentifier(options.transactionOutcome, "SCDeploy"); + const contracts = events.map((event) => this.parseScDeployEvent(event)); + + return { + returnCode: directCallOutcome.returnCode, + returnMessage: directCallOutcome.returnMessage, + contracts: contracts, + }; + } + + private parseScDeployEvent(event: TransactionEvent): { + address: string; + ownerAddress: string; + codeHash: Uint8Array; + } { + const topicForAddress = event.topics[0]; + const topicForOwnerAddress = event.topics[1]; + const topicForCodeHash = event.topics[2]; + + const address = topicForAddress?.length ? new Address(topicForAddress).toBech32() : ""; + const ownerAddress = topicForOwnerAddress?.length ? new Address(topicForOwnerAddress).toBech32() : ""; + const codeHash = topicForCodeHash; + + return { + address, + ownerAddress, + codeHash, + }; + } + + parseExecute(options: { transactionOutcome: TransactionOutcome; function?: string }): { + values: any[]; + returnCode: string; + returnMessage: string; + } { + const directCallOutcome = options.transactionOutcome.directSmartContractCallOutcome; + + if (!this.abi) { + return { + values: directCallOutcome.returnDataParts, + returnCode: directCallOutcome.returnCode, + returnMessage: directCallOutcome.returnMessage, + }; + } + + const functionName = options.function || directCallOutcome.function; + + if (!functionName) { + throw new Err( + `Function name is not available in the transaction outcome, thus endpoint definition (ABI) cannot be picked (for parsing). Maybe provide the "function" parameter explicitly?`, + ); + } + + const endpoint = this.abi.getEndpoint(functionName); + + const legacyUntypedBundle = { + returnCode: new ReturnCode(directCallOutcome.returnCode), + returnMessage: directCallOutcome.returnMessage, + values: directCallOutcome.returnDataParts.map((part) => Buffer.from(part)), + }; + + const legacyTypedBundle = this.legacyResultsParser.parseOutcomeFromUntypedBundle(legacyUntypedBundle, endpoint); + + return { + values: legacyTypedBundle.values.map((value) => value.valueOf()), + returnCode: legacyTypedBundle.returnCode.toString(), + returnMessage: legacyTypedBundle.returnMessage, + }; + } +} diff --git a/src/transactionsOutcomeParsers/tokenManagementTransactionsOutcomeParser.spec.ts b/src/transactionsOutcomeParsers/tokenManagementTransactionsOutcomeParser.spec.ts new file mode 100644 index 000000000..136fe6d5b --- /dev/null +++ b/src/transactionsOutcomeParsers/tokenManagementTransactionsOutcomeParser.spec.ts @@ -0,0 +1,596 @@ +import { assert } from "chai"; +import { ErrParseTransactionOutcome } from "../errors"; +import { b64TopicsToBytes } from "../testutils"; +import { SmartContractResult, TransactionEvent, TransactionLogs, TransactionOutcome } from "./resources"; +import { TokenManagementTransactionsOutcomeParser } from "./tokenManagementTransactionsOutcomeParser"; + +describe("test token management transactions outcome parser", () => { + const parser = new TokenManagementTransactionsOutcomeParser(); + + it("should test ensure error", () => { + const encodedTopics = ["Avk0jZ1kR+l9c76wQQoYcu4hvXPz+jxxTdqQeaCrbX8=", "dGlja2VyIG5hbWUgaXMgbm90IHZhbGlk"]; + const event = new TransactionEvent({ + address: "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqzllls8a5w6u", + identifier: "signalError", + topics: b64TopicsToBytes(encodedTopics), + dataItems: [Buffer.from("QDc1NzM2NTcyMjA2NTcyNzI2Zjcy", "base64")], + }); + + const logs = new TransactionLogs({ events: [event] }); + const txOutcome = new TransactionOutcome({ logs: logs }); + + assert.throws( + () => { + parser.parseIssueFungible(txOutcome); + }, + ErrParseTransactionOutcome, + "encountered signalError: ticker name is not valid (user error)", + ); + }); + + it("should test parse issue fungible", () => { + const identifier = "ZZZ-9ee87d"; + const base64Identifier = Buffer.from(identifier).toString("base64"); + + const encodedTopics = [base64Identifier, "U0VDT05E", "Wlpa", "RnVuZ2libGVFU0RU", "Ag=="]; + const event = new TransactionEvent({ + address: "erd18s6a06ktr2v6fgxv4ffhauxvptssnaqlds45qgsrucemlwc8rawq553rt2", + identifier: "issue", + topics: b64TopicsToBytes(encodedTopics), + }); + + const logs = new TransactionLogs({ + address: "erd18s6a06ktr2v6fgxv4ffhauxvptssnaqlds45qgsrucemlwc8rawq553rt2", + events: [event], + }); + + const txOutcome = new TransactionOutcome({ logs: logs }); + + const outcome = parser.parseIssueFungible(txOutcome); + assert.lengthOf(outcome, 1); + assert.equal(outcome[0].tokenIdentifier, identifier); + }); + + it("should test parse issue non fungible", () => { + const identifier = "NFT-f01d1e"; + const base64Identifier = Buffer.from(identifier).toString("base64"); + + let encodedTopics = [ + "TkZULWYwMWQxZQ==", + "", + "Y2FuVXBncmFkZQ==", + "dHJ1ZQ==", + "Y2FuQWRkU3BlY2lhbFJvbGVz", + "dHJ1ZQ==", + ]; + const firstEvent = new TransactionEvent({ + address: "erd18s6a06ktr2v6fgxv4ffhauxvptssnaqlds45qgsrucemlwc8rawq553rt2", + identifier: "upgradeProperties", + topics: b64TopicsToBytes(encodedTopics), + }); + + encodedTopics = ["TkZULWYwMWQxZQ==", "", "", "RVNEVFJvbGVCdXJuRm9yQWxs"]; + const secondEvent = new TransactionEvent({ + address: "erd18s6a06ktr2v6fgxv4ffhauxvptssnaqlds45qgsrucemlwc8rawq553rt2", + identifier: "ESDTSetBurnRoleForAll", + topics: b64TopicsToBytes(encodedTopics), + }); + + encodedTopics = [base64Identifier, "TkZURVNU", "TkZU", "Tm9uRnVuZ2libGVFU0RU"]; + const thirdEvent = new TransactionEvent({ + address: "erd18s6a06ktr2v6fgxv4ffhauxvptssnaqlds45qgsrucemlwc8rawq553rt2", + identifier: "issueNonFungible", + topics: b64TopicsToBytes(encodedTopics), + }); + + const logs = new TransactionLogs({ + address: "erd18s6a06ktr2v6fgxv4ffhauxvptssnaqlds45qgsrucemlwc8rawq553rt2", + events: [firstEvent, secondEvent, thirdEvent], + }); + + const txOutcome = new TransactionOutcome({ logs: logs }); + + const outcome = parser.parseIssueNonFungible(txOutcome); + assert.lengthOf(outcome, 1); + assert.equal(outcome[0].tokenIdentifier, identifier); + }); + + it("should test parse issue semi fungible", () => { + const identifier = "SEMIFNG-2c6d9f"; + const base64Identifier = Buffer.from(identifier).toString("base64"); + + const encodedTopics = [base64Identifier, "U0VNSQ==", "U0VNSUZORw==", "U2VtaUZ1bmdpYmxlRVNEVA=="]; + const event = new TransactionEvent({ + address: "erd18s6a06ktr2v6fgxv4ffhauxvptssnaqlds45qgsrucemlwc8rawq553rt2", + identifier: "issueSemiFungible", + topics: b64TopicsToBytes(encodedTopics), + }); + + const logs = new TransactionLogs({ + address: "erd18s6a06ktr2v6fgxv4ffhauxvptssnaqlds45qgsrucemlwc8rawq553rt2", + events: [event], + }); + + const txOutcome = new TransactionOutcome({ logs: logs }); + + const outcome = parser.parseIssueSemiFungible(txOutcome); + assert.lengthOf(outcome, 1); + assert.equal(outcome[0].tokenIdentifier, identifier); + }); + + it("should test parse register meta esdt", () => { + const identifier = "METATEST-e05d11"; + const base64Identifier = Buffer.from(identifier).toString("base64"); + + const encodedTopics = [base64Identifier, "TUVURVNU", "TUVUQVRFU1Q=", "TWV0YUVTRFQ="]; + const event = new TransactionEvent({ + address: "erd18s6a06ktr2v6fgxv4ffhauxvptssnaqlds45qgsrucemlwc8rawq553rt2", + identifier: "registerMetaESDT", + topics: b64TopicsToBytes(encodedTopics), + }); + + const logs = new TransactionLogs({ + address: "erd18s6a06ktr2v6fgxv4ffhauxvptssnaqlds45qgsrucemlwc8rawq553rt2", + events: [event], + }); + + const txOutcome = new TransactionOutcome({ logs: logs }); + + const outcome = parser.parseRegisterMetaEsdt(txOutcome); + assert.lengthOf(outcome, 1); + assert.equal(outcome[0].tokenIdentifier, identifier); + }); + + it("should test parse register and set all roles", () => { + const firstIdentifier = "LMAO-d9f892"; + const firstBase64Identifier = Buffer.from(firstIdentifier).toString("base64"); + + const secondIdentifier = "TST-123456"; + const secondBase64Identifier = Buffer.from(secondIdentifier).toString("base64"); + + const roles = ["ESDTRoleLocalMint", "ESDTRoleLocalBurn"]; + + let encodedTopics = [firstBase64Identifier, "TE1BTw==", "TE1BTw==", "RnVuZ2libGVFU0RU", "Ag=="]; + const firstEvent = new TransactionEvent({ + address: "erd18s6a06ktr2v6fgxv4ffhauxvptssnaqlds45qgsrucemlwc8rawq553rt2", + identifier: "registerAndSetAllRoles", + topics: b64TopicsToBytes(encodedTopics), + }); + + encodedTopics = [secondBase64Identifier, "TE1BTw==", "TE1BTw==", "RnVuZ2libGVFU0RU", "Ag=="]; + const secondEvent = new TransactionEvent({ + address: "erd18s6a06ktr2v6fgxv4ffhauxvptssnaqlds45qgsrucemlwc8rawq553rt2", + identifier: "registerAndSetAllRoles", + topics: b64TopicsToBytes(encodedTopics), + }); + + const transactionLogs = new TransactionLogs({ + address: "erd18s6a06ktr2v6fgxv4ffhauxvptssnaqlds45qgsrucemlwc8rawq553rt2", + events: [firstEvent, secondEvent], + }); + + encodedTopics = ["TE1BTy1kOWY4OTI=", "", "", "RVNEVFJvbGVMb2NhbE1pbnQ=", "RVNEVFJvbGVMb2NhbEJ1cm4="]; + const firstResultEvent = new TransactionEvent({ + address: "erd18s6a06ktr2v6fgxv4ffhauxvptssnaqlds45qgsrucemlwc8rawq553rt2", + identifier: "ESDTSetRole", + topics: b64TopicsToBytes(encodedTopics), + }); + + encodedTopics = ["VFNULTEyMzQ1Ng==", "", "", "RVNEVFJvbGVMb2NhbE1pbnQ=", "RVNEVFJvbGVMb2NhbEJ1cm4="]; + const secondResultEvent = new TransactionEvent({ + address: "erd18s6a06ktr2v6fgxv4ffhauxvptssnaqlds45qgsrucemlwc8rawq553rt2", + identifier: "ESDTSetRole", + topics: b64TopicsToBytes(encodedTopics), + }); + + const resultLogs = new TransactionLogs({ + address: "erd18s6a06ktr2v6fgxv4ffhauxvptssnaqlds45qgsrucemlwc8rawq553rt2", + events: [firstResultEvent, secondResultEvent], + }); + + const scResult = new SmartContractResult({ + sender: "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqzllls8a5w6u", + receiver: "erd18s6a06ktr2v6fgxv4ffhauxvptssnaqlds45qgsrucemlwc8rawq553rt2", + data: Buffer.from( + "RVNEVFNldFJvbGVANGM0ZDQxNGYyZDY0Mzk2NjM4MzkzMkA0NTUzNDQ1NDUyNmY2YzY1NGM2ZjYzNjE2YzRkNjk2ZTc0QDQ1NTM0NDU0NTI2ZjZjNjU0YzZmNjM2MTZjNDI3NTcyNmU=", + "base64", + ), + logs: resultLogs, + }); + + const txOutcome = new TransactionOutcome({ + smartContractResults: [scResult], + logs: transactionLogs, + }); + + const outcome = parser.parseRegisterAndSetAllRoles(txOutcome); + assert.lengthOf(outcome, 2); + + assert.equal(outcome[0].tokenIdentifier, firstIdentifier); + assert.deepEqual(outcome[0].roles, roles); + + assert.equal(outcome[1].tokenIdentifier, secondIdentifier); + assert.deepEqual(outcome[1].roles, roles); + }); + + it("should test parse register set special role", () => { + const identifier = "METATEST-e05d11"; + const base64Identifier = Buffer.from(identifier).toString("base64"); + const roles = ["ESDTRoleNFTCreate", "ESDTRoleNFTAddQuantity", "ESDTRoleNFTBurn"]; + + const encodedTopics = [ + base64Identifier, + "", + "", + "RVNEVFJvbGVORlRDcmVhdGU=", + "RVNEVFJvbGVORlRBZGRRdWFudGl0eQ==", + "RVNEVFJvbGVORlRCdXJu", + ]; + const event = new TransactionEvent({ + address: "erd18s6a06ktr2v6fgxv4ffhauxvptssnaqlds45qgsrucemlwc8rawq553rt2", + identifier: "ESDTSetRole", + topics: b64TopicsToBytes(encodedTopics), + }); + + const transactionLogs = new TransactionLogs({ + address: "erd18s6a06ktr2v6fgxv4ffhauxvptssnaqlds45qgsrucemlwc8rawq553rt2", + events: [event], + }); + + const txOutcome = new TransactionOutcome({ + logs: transactionLogs, + }); + + const outcome = parser.parseSetSpecialRole(txOutcome); + assert.lengthOf(outcome, 1); + assert.equal(outcome[0].userAddress, "erd18s6a06ktr2v6fgxv4ffhauxvptssnaqlds45qgsrucemlwc8rawq553rt2"); + assert.equal(outcome[0].tokenIdentifier, identifier); + assert.deepEqual(outcome[0].roles, roles); + }); + + it("should test parse nft create", () => { + const identifier = "NFT-f01d1e"; + const base64Identifier = Buffer.from(identifier).toString("base64"); + const nonce = BigInt(1); + const initialQuantity = BigInt(1); + + const encodedTopics = [ + base64Identifier, + "AQ==", + "AQ==", + "CAESAgABIuUBCAESCE5GVEZJUlNUGiA8NdfqyxqZpKDMqlN+8MwK4Qn0H2wrQCID5jO/uwcfXCDEEyouUW1ZM3ZKQ3NVcWpNM3hxeGR3VWczemJoVFNMUWZoN0szbW5aWXhyaGNRRFl4RzJDaHR0cHM6Ly9pcGZzLmlvL2lwZnMvUW1ZM3ZKQ3NVcWpNM3hxeGR3VWczemJoVFNMUWZoN0szbW5aWXhyaGNRRFl4Rzo9dGFnczo7bWV0YWRhdGE6UW1SY1A5NGtYcjV6WmpSR3ZpN21KNnVuN0xweFVoWVZSNFI0UnBpY3h6Z1lrdA==", + ]; + const event = new TransactionEvent({ + address: "erd18s6a06ktr2v6fgxv4ffhauxvptssnaqlds45qgsrucemlwc8rawq553rt2", + identifier: "ESDTNFTCreate", + topics: b64TopicsToBytes(encodedTopics), + }); + + const transactionLogs = new TransactionLogs({ + address: "erd18s6a06ktr2v6fgxv4ffhauxvptssnaqlds45qgsrucemlwc8rawq553rt2", + events: [event], + }); + + const txOutcome = new TransactionOutcome({ + logs: transactionLogs, + }); + + const outcome = parser.parseNftCreate(txOutcome); + assert.lengthOf(outcome, 1); + assert.equal(outcome[0].tokenIdentifier, identifier); + assert.equal(outcome[0].nonce, nonce); + assert.equal(outcome[0].initialQuantity, initialQuantity); + }); + + it("should test parse local mint", () => { + const identifier = "AAA-29c4c9"; + const base64Identifier = Buffer.from(identifier).toString("base64"); + const nonce = BigInt(0); + const mintedSupply = BigInt(100000); + + const encodedTopics = [base64Identifier, "", "AYag"]; + const event = new TransactionEvent({ + address: "erd18s6a06ktr2v6fgxv4ffhauxvptssnaqlds45qgsrucemlwc8rawq553rt2", + identifier: "ESDTLocalMint", + topics: b64TopicsToBytes(encodedTopics), + }); + + const transactionLogs = new TransactionLogs({ + address: "erd18s6a06ktr2v6fgxv4ffhauxvptssnaqlds45qgsrucemlwc8rawq553rt2", + events: [event], + }); + + const txOutcome = new TransactionOutcome({ + logs: transactionLogs, + }); + + const outcome = parser.parseLocalMint(txOutcome); + assert.lengthOf(outcome, 1); + assert.equal(outcome[0].userAddress, event.address); + assert.equal(outcome[0].tokenIdentifier, identifier); + assert.equal(outcome[0].nonce, nonce); + assert.equal(outcome[0].mintedSupply, mintedSupply); + }); + + it("should test parse local burn", () => { + const identifier = "AAA-29c4c9"; + const base64Identifier = Buffer.from(identifier).toString("base64"); + const nonce = BigInt(0); + const burntSupply = BigInt(100000); + + const encodedTopics = [base64Identifier, "", "AYag"]; + const event = new TransactionEvent({ + address: "erd18s6a06ktr2v6fgxv4ffhauxvptssnaqlds45qgsrucemlwc8rawq553rt2", + identifier: "ESDTLocalBurn", + topics: b64TopicsToBytes(encodedTopics), + }); + + const transactionLogs = new TransactionLogs({ + address: "erd18s6a06ktr2v6fgxv4ffhauxvptssnaqlds45qgsrucemlwc8rawq553rt2", + events: [event], + }); + + const txOutcome = new TransactionOutcome({ + logs: transactionLogs, + }); + + const outcome = parser.parseLocalBurn(txOutcome); + assert.lengthOf(outcome, 1); + assert.equal(outcome[0].userAddress, event.address); + assert.equal(outcome[0].tokenIdentifier, identifier); + assert.equal(outcome[0].nonce, nonce); + assert.equal(outcome[0].burntSupply, burntSupply); + }); + + it("should test parse pause", () => { + const identifier = "AAA-29c4c9"; + const base64Identifier = Buffer.from(identifier).toString("base64"); + + const encodedTopics = [base64Identifier]; + const event = new TransactionEvent({ + address: "erd18s6a06ktr2v6fgxv4ffhauxvptssnaqlds45qgsrucemlwc8rawq553rt2", + identifier: "ESDTPause", + topics: b64TopicsToBytes(encodedTopics), + }); + + const transactionLogs = new TransactionLogs({ + address: "erd18s6a06ktr2v6fgxv4ffhauxvptssnaqlds45qgsrucemlwc8rawq553rt2", + events: [event], + }); + + const txOutcome = new TransactionOutcome({ + logs: transactionLogs, + }); + + const outcome = parser.parsePause(txOutcome); + assert.lengthOf(outcome, 1); + assert.equal(outcome[0].tokenIdentifier, identifier); + }); + + it("should test parse unpause", () => { + const identifier = "AAA-29c4c9"; + const base64Identifier = Buffer.from(identifier).toString("base64"); + + const encodedTopics = [base64Identifier]; + const event = new TransactionEvent({ + address: "erd18s6a06ktr2v6fgxv4ffhauxvptssnaqlds45qgsrucemlwc8rawq553rt2", + identifier: "ESDTUnPause", + topics: b64TopicsToBytes(encodedTopics), + }); + + const transactionLogs = new TransactionLogs({ + address: "erd18s6a06ktr2v6fgxv4ffhauxvptssnaqlds45qgsrucemlwc8rawq553rt2", + events: [event], + }); + + const txOutcome = new TransactionOutcome({ + logs: transactionLogs, + }); + + const outcome = parser.parseUnpause(txOutcome); + assert.lengthOf(outcome, 1); + assert.equal(outcome[0].tokenIdentifier, identifier); + }); + + it("should test parse freeze", () => { + const identifier = "AAA-29c4c9"; + const base64Identifier = Buffer.from(identifier).toString("base64"); + const nonce = BigInt(0); + const balance = BigInt(10000000); + const address = "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"; + + const encodedTopics = [base64Identifier, "", "mJaA", "ATlHLv9ohncamC8wg9pdQh8kwpGB5jiIIo3IHKYNaeE="]; + const event = new TransactionEvent({ + address: "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqzllls8a5w6u", + identifier: "ESDTFreeze", + topics: b64TopicsToBytes(encodedTopics), + }); + + const transactionLogs = new TransactionLogs({ + address: "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th", + events: [event], + }); + + const scResult = new SmartContractResult({ + sender: "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqzllls8a5w6u", + receiver: "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th", + data: Buffer.from("RVNEVEZyZWV6ZUA0MTQxNDEyZDMyMzk2MzM0NjMzOQ==", "base64"), + logs: transactionLogs, + }); + + const txOutcome = new TransactionOutcome({ + smartContractResults: [scResult], + }); + + const outcome = parser.parseFreeze(txOutcome); + assert.lengthOf(outcome, 1); + assert.equal(outcome[0].userAddress, address); + assert.equal(outcome[0].tokenIdentifier, identifier); + assert.equal(outcome[0].nonce, nonce); + assert.equal(outcome[0].balance, balance); + }); + + it("should test parse unfreeze", () => { + const identifier = "AAA-29c4c9"; + const base64Identifier = Buffer.from(identifier).toString("base64"); + const nonce = BigInt(0); + const balance = BigInt(10000000); + const address = "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"; + + const encodedTopics = [base64Identifier, "", "mJaA", "ATlHLv9ohncamC8wg9pdQh8kwpGB5jiIIo3IHKYNaeE="]; + const event = new TransactionEvent({ + address: "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqzllls8a5w6u", + identifier: "ESDTUnFreeze", + topics: b64TopicsToBytes(encodedTopics), + }); + + const transactionLogs = new TransactionLogs({ + address: "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th", + events: [event], + }); + + const scResult = new SmartContractResult({ + sender: "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqzllls8a5w6u", + receiver: "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th", + data: Buffer.from("RVNEVEZyZWV6ZUA0MTQxNDEyZDMyMzk2MzM0NjMzOQ==", "base64"), + logs: transactionLogs, + }); + + const txOutcome = new TransactionOutcome({ + smartContractResults: [scResult], + }); + + const outcome = parser.parseUnfreeze(txOutcome); + assert.lengthOf(outcome, 1); + assert.equal(outcome[0].userAddress, address); + assert.equal(outcome[0].tokenIdentifier, identifier); + assert.equal(outcome[0].nonce, nonce); + assert.equal(outcome[0].balance, balance); + }); + + it("should test parse wipe", () => { + const identifier = "AAA-29c4c9"; + const base64Identifier = Buffer.from(identifier).toString("base64"); + const nonce = BigInt(0); + const balance = BigInt(10000000); + const address = "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"; + + const encodedTopics = [base64Identifier, "", "mJaA", "ATlHLv9ohncamC8wg9pdQh8kwpGB5jiIIo3IHKYNaeE="]; + const event = new TransactionEvent({ + address: "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqzllls8a5w6u", + identifier: "ESDTWipe", + topics: b64TopicsToBytes(encodedTopics), + }); + + const transactionLogs = new TransactionLogs({ + address: "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th", + events: [event], + }); + + const scResult = new SmartContractResult({ + sender: "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqzllls8a5w6u", + receiver: "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th", + data: Buffer.from("RVNEVEZyZWV6ZUA0MTQxNDEyZDMyMzk2MzM0NjMzOQ==", "base64"), + logs: transactionLogs, + }); + + const txOutcome = new TransactionOutcome({ + smartContractResults: [scResult], + }); + + const outcome = parser.parseWipe(txOutcome); + assert.lengthOf(outcome, 1); + assert.equal(outcome[0].userAddress, address); + assert.equal(outcome[0].tokenIdentifier, identifier); + assert.equal(outcome[0].nonce, nonce); + assert.equal(outcome[0].balance, balance); + }); + + it("should test parse update attributes", () => { + const identifier = "NFT-f01d1e"; + const base64Identifier = Buffer.from(identifier).toString("base64"); + const nonce = BigInt(1); + const attributes = "metadata:ipfsCID/test.json;tags:tag1,tag2"; + const base64Attributes = Buffer.from(attributes).toString("base64"); + + const encodedTopics = [base64Identifier, "AQ==", "", base64Attributes]; + const event = new TransactionEvent({ + address: "erd18s6a06ktr2v6fgxv4ffhauxvptssnaqlds45qgsrucemlwc8rawq553rt2", + identifier: "ESDTNFTUpdateAttributes", + topics: b64TopicsToBytes(encodedTopics), + }); + + const transactionLogs = new TransactionLogs({ + address: "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th", + events: [event], + }); + + const txOutcome = new TransactionOutcome({ + logs: transactionLogs, + }); + + const outcome = parser.parseUpdateAttributes(txOutcome); + assert.lengthOf(outcome, 1); + assert.equal(outcome[0].tokenIdentifier, identifier); + assert.equal(outcome[0].nonce, nonce); + assert.equal(Buffer.from(outcome[0].attributes).toString(), attributes); + }); + + it("should test parse add quantity", () => { + const identifier = "NFT-f01d1e"; + const base64Identifier = Buffer.from(identifier).toString("base64"); + const nonce = BigInt(1); + const addedQuantity = BigInt(10); + + const encodedTopics = [base64Identifier, "AQ==", "Cg=="]; + const event = new TransactionEvent({ + address: "erd18s6a06ktr2v6fgxv4ffhauxvptssnaqlds45qgsrucemlwc8rawq553rt2", + identifier: "ESDTNFTAddQuantity", + topics: b64TopicsToBytes(encodedTopics), + }); + + const transactionLogs = new TransactionLogs({ + address: "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th", + events: [event], + }); + + const txOutcome = new TransactionOutcome({ + logs: transactionLogs, + }); + + const outcome = parser.parseAddQuantity(txOutcome); + assert.lengthOf(outcome, 1); + assert.equal(outcome[0].tokenIdentifier, identifier); + assert.equal(outcome[0].nonce, nonce); + assert.equal(outcome[0].addedQuantity, addedQuantity); + }); + + it("should test parse burn quantity", () => { + const identifier = "NFT-f01d1e"; + const base64Identifier = Buffer.from(identifier).toString("base64"); + const nonce = BigInt(1); + const burntQuantity = BigInt(16); + + const encodedTopics = [base64Identifier, "AQ==", "EA=="]; + const event = new TransactionEvent({ + address: "erd18s6a06ktr2v6fgxv4ffhauxvptssnaqlds45qgsrucemlwc8rawq553rt2", + identifier: "ESDTNFTBurn", + topics: b64TopicsToBytes(encodedTopics), + }); + + const transactionLogs = new TransactionLogs({ + address: "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th", + events: [event], + }); + + const txOutcome = new TransactionOutcome({ + logs: transactionLogs, + }); + + const outcome = parser.parseBurnQuantity(txOutcome); + assert.lengthOf(outcome, 1); + assert.equal(outcome[0].tokenIdentifier, identifier); + assert.equal(outcome[0].nonce, nonce); + assert.equal(outcome[0].burntQuantity, burntQuantity); + }); +}); diff --git a/src/transactionsOutcomeParsers/tokenManagementTransactionsOutcomeParser.ts b/src/transactionsOutcomeParsers/tokenManagementTransactionsOutcomeParser.ts new file mode 100644 index 000000000..0702cba6c --- /dev/null +++ b/src/transactionsOutcomeParsers/tokenManagementTransactionsOutcomeParser.ts @@ -0,0 +1,410 @@ +import { Address } from "../address"; +import { ErrParseTransactionOutcome } from "../errors"; +import { bufferToBigInt } from "../smartcontracts/codec/utils"; +import { TransactionEvent, TransactionOutcome, findEventsByIdentifier } from "./resources"; + +export class TokenManagementTransactionsOutcomeParser { + constructor() {} + + parseIssueFungible(transactionOutcome: TransactionOutcome): { tokenIdentifier: string }[] { + this.ensureNoError(transactionOutcome.logs.events); + + const events = findEventsByIdentifier(transactionOutcome, "issue"); + return events.map((event) => ({ tokenIdentifier: this.extractTokenIdentifier(event) })); + } + + parseIssueNonFungible(transactionOutcome: TransactionOutcome): { tokenIdentifier: string }[] { + this.ensureNoError(transactionOutcome.logs.events); + + const events = findEventsByIdentifier(transactionOutcome, "issueNonFungible"); + return events.map((event) => ({ tokenIdentifier: this.extractTokenIdentifier(event) })); + } + + parseIssueSemiFungible(transactionOutcome: TransactionOutcome): { tokenIdentifier: string }[] { + this.ensureNoError(transactionOutcome.logs.events); + + const events = findEventsByIdentifier(transactionOutcome, "issueSemiFungible"); + return events.map((event) => ({ tokenIdentifier: this.extractTokenIdentifier(event) })); + } + + parseRegisterMetaEsdt(transactionOutcome: TransactionOutcome): { tokenIdentifier: string }[] { + this.ensureNoError(transactionOutcome.logs.events); + + const events = findEventsByIdentifier(transactionOutcome, "registerMetaESDT"); + return events.map((event) => ({ tokenIdentifier: this.extractTokenIdentifier(event) })); + } + + parseRegisterAndSetAllRoles( + transactionOutcome: TransactionOutcome, + ): { tokenIdentifier: string; roles: string[] }[] { + this.ensureNoError(transactionOutcome.logs.events); + const registerEvents = findEventsByIdentifier(transactionOutcome, "registerAndSetAllRoles"); + const setRoleEvents = findEventsByIdentifier(transactionOutcome, "ESDTSetRole"); + + if (registerEvents.length !== setRoleEvents.length) { + throw new ErrParseTransactionOutcome( + "Register Events and Set Role events mismatch. Should have the same number of events.", + ); + } + + return registerEvents.map((registerEvent, index) => { + const tokenIdentifier = this.extractTokenIdentifier(registerEvent); + const encodedRoles = setRoleEvents[index].topics.slice(3); + const roles = encodedRoles.map((role) => this.decodeTopicAsString(role)); + return { tokenIdentifier, roles }; + }); + } + + parseSetBurnRoleGlobally(transactionOutcome: TransactionOutcome) { + this.ensureNoError(transactionOutcome.logs.events); + } + + parseUnsetBurnRoleGlobally(transactionOutcome: TransactionOutcome) { + this.ensureNoError(transactionOutcome.logs.events); + } + + parseSetSpecialRole(transactionOutcome: TransactionOutcome): { + userAddress: string; + tokenIdentifier: string; + roles: string[]; + }[] { + this.ensureNoError(transactionOutcome.logs.events); + + const events = findEventsByIdentifier(transactionOutcome, "ESDTSetRole"); + return events.map((event) => this.getOutputForSetSpecialRoleEvent(event)); + } + + private getOutputForSetSpecialRoleEvent(event: TransactionEvent): { + userAddress: string; + tokenIdentifier: string; + roles: string[]; + } { + const userAddress = event.address; + const tokenIdentifier = this.extractTokenIdentifier(event); + const encodedRoles = event.topics.slice(3); + const roles = encodedRoles.map((role) => this.decodeTopicAsString(role)); + + return { userAddress: userAddress, tokenIdentifier: tokenIdentifier, roles: roles }; + } + + parseNftCreate(transactionOutcome: TransactionOutcome): { + tokenIdentifier: string; + nonce: bigint; + initialQuantity: bigint; + }[] { + this.ensureNoError(transactionOutcome.logs.events); + + const events = findEventsByIdentifier(transactionOutcome, "ESDTNFTCreate"); + return events.map((event) => this.getOutputForNftCreateEvent(event)); + } + + private getOutputForNftCreateEvent(event: TransactionEvent): { + tokenIdentifier: string; + nonce: bigint; + initialQuantity: bigint; + } { + const tokenIdentifier = this.extractTokenIdentifier(event); + const nonce = this.extractNonce(event); + const amount = this.extractAmount(event); + + return { tokenIdentifier: tokenIdentifier, nonce: nonce, initialQuantity: amount }; + } + + parseLocalMint(transactionOutcome: TransactionOutcome): { + userAddress: string; + tokenIdentifier: string; + nonce: bigint; + mintedSupply: bigint; + }[] { + this.ensureNoError(transactionOutcome.logs.events); + + const events = findEventsByIdentifier(transactionOutcome, "ESDTLocalMint"); + return events.map((event) => this.getOutputForLocalMintEvent(event)); + } + + private getOutputForLocalMintEvent(event: TransactionEvent): { + userAddress: string; + tokenIdentifier: string; + nonce: bigint; + mintedSupply: bigint; + } { + const userAddress = event.address; + const tokenIdentifier = this.extractTokenIdentifier(event); + const nonce = this.extractNonce(event); + const mintedSupply = this.extractAmount(event); + + return { + userAddress: userAddress, + tokenIdentifier: tokenIdentifier, + nonce: nonce, + mintedSupply: mintedSupply, + }; + } + + parseLocalBurn(transactionOutcome: TransactionOutcome): { + userAddress: string; + tokenIdentifier: string; + nonce: bigint; + burntSupply: bigint; + }[] { + this.ensureNoError(transactionOutcome.logs.events); + + const events = findEventsByIdentifier(transactionOutcome, "ESDTLocalBurn"); + return events.map((event) => this.getOutputForLocalBurnEvent(event)); + } + + private getOutputForLocalBurnEvent(event: TransactionEvent): { + userAddress: string; + tokenIdentifier: string; + nonce: bigint; + burntSupply: bigint; + } { + const userAddress = event.address; + const tokenIdentifier = this.extractTokenIdentifier(event); + const nonce = this.extractNonce(event); + const burntSupply = this.extractAmount(event); + + return { + userAddress: userAddress, + tokenIdentifier: tokenIdentifier, + nonce: nonce, + burntSupply: burntSupply, + }; + } + + parsePause(transactionOutcome: TransactionOutcome): { tokenIdentifier: string }[] { + this.ensureNoError(transactionOutcome.logs.events); + + const events = findEventsByIdentifier(transactionOutcome, "ESDTPause"); + return events.map((event) => ({ tokenIdentifier: this.extractTokenIdentifier(event) })); + } + + parseUnpause(transactionOutcome: TransactionOutcome): { tokenIdentifier: string }[] { + this.ensureNoError(transactionOutcome.logs.events); + + const events = findEventsByIdentifier(transactionOutcome, "ESDTUnPause"); + return events.map((event) => ({ tokenIdentifier: this.extractTokenIdentifier(event) })); + } + + parseFreeze(transactionOutcome: TransactionOutcome): { + userAddress: string; + tokenIdentifier: string; + nonce: bigint; + balance: bigint; + }[] { + this.ensureNoError(transactionOutcome.logs.events); + + const events = findEventsByIdentifier(transactionOutcome, "ESDTFreeze"); + return events.map((event) => this.getOutputForFreezeEvent(event)); + } + + private getOutputForFreezeEvent(event: TransactionEvent): { + userAddress: string; + tokenIdentifier: string; + nonce: bigint; + balance: bigint; + } { + const userAddress = this.extractAddress(event); + const tokenIdentifier = this.extractTokenIdentifier(event); + const nonce = this.extractNonce(event); + const balance = this.extractAmount(event); + + return { + userAddress: userAddress, + tokenIdentifier: tokenIdentifier, + nonce: nonce, + balance: balance, + }; + } + + parseUnfreeze(transactionOutcome: TransactionOutcome): { + userAddress: string; + tokenIdentifier: string; + nonce: bigint; + balance: bigint; + }[] { + this.ensureNoError(transactionOutcome.logs.events); + + const events = findEventsByIdentifier(transactionOutcome, "ESDTUnFreeze"); + return events.map((event) => this.getOutputForUnfreezeEvent(event)); + } + + private getOutputForUnfreezeEvent(event: TransactionEvent): { + userAddress: string; + tokenIdentifier: string; + nonce: bigint; + balance: bigint; + } { + const userAddress = this.extractAddress(event); + const tokenIdentifier = this.extractTokenIdentifier(event); + const nonce = this.extractNonce(event); + const balance = this.extractAmount(event); + + return { + userAddress: userAddress, + tokenIdentifier: tokenIdentifier, + nonce: nonce, + balance: balance, + }; + } + + parseWipe(transactionOutcome: TransactionOutcome): { + userAddress: string; + tokenIdentifier: string; + nonce: bigint; + balance: bigint; + }[] { + this.ensureNoError(transactionOutcome.logs.events); + + const events = findEventsByIdentifier(transactionOutcome, "ESDTWipe"); + return events.map((event) => this.getOutputForWipeEvent(event)); + } + + private getOutputForWipeEvent(event: TransactionEvent): { + userAddress: string; + tokenIdentifier: string; + nonce: bigint; + balance: bigint; + } { + const userAddress = this.extractAddress(event); + const tokenIdentifier = this.extractTokenIdentifier(event); + const nonce = this.extractNonce(event); + const balance = this.extractAmount(event); + + return { + userAddress: userAddress, + tokenIdentifier: tokenIdentifier, + nonce: nonce, + balance: balance, + }; + } + + parseUpdateAttributes(transactionOutcome: TransactionOutcome): { + tokenIdentifier: string; + nonce: bigint; + attributes: Uint8Array; + }[] { + this.ensureNoError(transactionOutcome.logs.events); + + const events = findEventsByIdentifier(transactionOutcome, "ESDTNFTUpdateAttributes"); + return events.map((event) => this.getOutputForUpdateAttributesEvent(event)); + } + + private getOutputForUpdateAttributesEvent(event: TransactionEvent): { + tokenIdentifier: string; + nonce: bigint; + attributes: Uint8Array; + } { + const tokenIdentifier = this.extractTokenIdentifier(event); + const nonce = this.extractNonce(event); + const attributes = event.topics[3] ? event.topics[3] : new Uint8Array(); + + return { + tokenIdentifier: tokenIdentifier, + nonce: nonce, + attributes: attributes, + }; + } + + parseAddQuantity(transactionOutcome: TransactionOutcome): { + tokenIdentifier: string; + nonce: bigint; + addedQuantity: bigint; + }[] { + this.ensureNoError(transactionOutcome.logs.events); + + const events = findEventsByIdentifier(transactionOutcome, "ESDTNFTAddQuantity"); + return events.map((event) => this.getOutputForAddQuantityEvent(event)); + } + + private getOutputForAddQuantityEvent(event: TransactionEvent): { + tokenIdentifier: string; + nonce: bigint; + addedQuantity: bigint; + } { + const tokenIdentifier = this.extractTokenIdentifier(event); + const nonce = this.extractNonce(event); + const addedQuantity = this.extractAmount(event); + + return { + tokenIdentifier: tokenIdentifier, + nonce: nonce, + addedQuantity: addedQuantity, + }; + } + + parseBurnQuantity(transactionOutcome: TransactionOutcome): { + tokenIdentifier: string; + nonce: bigint; + burntQuantity: bigint; + }[] { + this.ensureNoError(transactionOutcome.logs.events); + + const events = findEventsByIdentifier(transactionOutcome, "ESDTNFTBurn"); + return events.map((event) => this.getOutputForBurnQuantityEvent(event)); + } + + private getOutputForBurnQuantityEvent(event: TransactionEvent): { + tokenIdentifier: string; + nonce: bigint; + burntQuantity: bigint; + } { + const tokenIdentifier = this.extractTokenIdentifier(event); + const nonce = this.extractNonce(event); + const burntQuantity = this.extractAmount(event); + + return { + tokenIdentifier: tokenIdentifier, + nonce: nonce, + burntQuantity: burntQuantity, + }; + } + + private ensureNoError(transactionEvents: TransactionEvent[]) { + for (const event of transactionEvents) { + if (event.identifier == "signalError") { + const data = Buffer.from(event.dataItems[0]?.toString().slice(1)).toString() || ""; + const message = this.decodeTopicAsString(event.topics[1]); + + throw new ErrParseTransactionOutcome( + `encountered signalError: ${message} (${Buffer.from(data, "hex").toString()})`, + ); + } + } + } + + private extractTokenIdentifier(event: TransactionEvent): string { + if (!event.topics[0]?.length) { + return ""; + } + return this.decodeTopicAsString(event.topics[0]); + } + + private extractNonce(event: TransactionEvent): bigint { + if (!event.topics[1]?.length) { + return BigInt(0); + } + const nonce = Buffer.from(event.topics[1]); + return BigInt(bufferToBigInt(nonce).toFixed(0)); + } + + private extractAmount(event: TransactionEvent): bigint { + if (!event.topics[2]?.length) { + return BigInt(0); + } + const amount = Buffer.from(event.topics[2]); + return BigInt(bufferToBigInt(amount).toFixed(0)); + } + + private extractAddress(event: TransactionEvent): string { + if (!event.topics[3]?.length) { + return ""; + } + const address = Buffer.from(event.topics[3]); + return Address.fromBuffer(address).bech32(); + } + + private decodeTopicAsString(topic: Uint8Array): string { + return Buffer.from(topic).toString(); + } +} diff --git a/src/transactionsOutcomeParsers/transactionEventsParser.spec.ts b/src/transactionsOutcomeParsers/transactionEventsParser.spec.ts new file mode 100644 index 000000000..9ed01c91c --- /dev/null +++ b/src/transactionsOutcomeParsers/transactionEventsParser.spec.ts @@ -0,0 +1,220 @@ +import { + ContractResultItem, + ContractResults, + TransactionEventData, + TransactionEvent as TransactionEventOnNetwork, + TransactionEventTopic, + TransactionLogs as TransactionLogsOnNetwork, + TransactionOnNetwork, +} from "@multiversx/sdk-network-providers"; +import BigNumber from "bignumber.js"; +import { assert } from "chai"; +import { Address } from "../address"; +import { TransactionsConverter } from "../converters/transactionsConverter"; +import { AbiRegistry } from "../smartcontracts"; +import { loadAbiRegistry } from "../testutils"; +import { TransactionEvent, findEventsByFirstTopic } from "./resources"; +import { TransactionEventsParser } from "./transactionEventsParser"; + +describe("test transaction events parser", () => { + it("parses events (minimalistic)", async function () { + const parser = new TransactionEventsParser({ + abi: await loadAbiRegistry("src/testdata/esdt-safe.abi.json"), + }); + + const values = parser.parseEvents({ + events: [ + new TransactionEvent({ + identifier: "transferOverMaxAmount", + topics: [Buffer.from("transferOverMaxAmount"), Buffer.from([0x2a]), Buffer.from([0x2b])], + }), + ], + }); + + assert.deepEqual(values, [ + { + batch_id: new BigNumber(42), + tx_id: new BigNumber(43), + }, + ]); + }); + + it("parses events (esdt-safe, deposit)", async function () { + const parser = new TransactionEventsParser({ + abi: await loadAbiRegistry("src/testdata/esdt-safe.abi.json"), + }); + + const transactionsConverter = new TransactionsConverter(); + const transactionOnNetwork = new TransactionOnNetwork({ + nonce: 7, + contractResults: new ContractResults([ + new ContractResultItem({ + nonce: 8, + data: "@6f6b", + logs: new TransactionLogsOnNetwork({ + events: [ + new TransactionEventOnNetwork({ + identifier: "deposit", + topics: [ + new TransactionEventTopic("ZGVwb3NpdA=="), + new TransactionEventTopic("cmzC1LRt1r10pMhNAnFb+FyudjGMq4G8CefCYdQUmmc="), + new TransactionEventTopic("AAAADFdFR0xELTAxZTQ5ZAAAAAAAAAAAAAAAAWQ="), + ], + dataPayload: new TransactionEventData(Buffer.from("AAAAAAAAA9sAAAA=", "base64")), + }), + ], + }), + }), + ]), + }); + + const transactionOutcome = transactionsConverter.transactionOnNetworkToOutcome(transactionOnNetwork); + const events = findEventsByFirstTopic(transactionOutcome, "deposit"); + const parsed = parser.parseEvents({ events }); + + assert.deepEqual(parsed, [ + { + dest_address: Address.fromBech32("erd1wfkv9495dhtt6a9yepxsyu2mlpw2ua333j4cr0qfulpxr4q5nfnshgyqun"), + tokens: [ + { + token_identifier: "WEGLD-01e49d", + token_nonce: new BigNumber(0), + amount: new BigNumber(100), + }, + ], + event_data: { + tx_nonce: new BigNumber(987), + opt_function: null, + opt_arguments: null, + opt_gas_limit: null, + }, + }, + ]); + }); + + it("parses events (multisig, startPerformAction)", async function () { + const parser = new TransactionEventsParser({ + abi: await loadAbiRegistry("src/testdata/multisig-full.abi.json"), + }); + + const transactionsConverter = new TransactionsConverter(); + const transactionOnNetwork = new TransactionOnNetwork({ + nonce: 7, + contractResults: new ContractResults([ + new ContractResultItem({ + nonce: 8, + data: "@6f6b", + }), + ]), + logs: new TransactionLogsOnNetwork({ + events: [ + new TransactionEventOnNetwork({ + identifier: "performAction", + topics: [new TransactionEventTopic("c3RhcnRQZXJmb3JtQWN0aW9u")], + dataPayload: new TransactionEventData( + Buffer.from( + "00000001000000000500000000000000000500d006f73c4221216fa679bc559005584c4f1160e569e1000000000000000003616464000000010000000107000000010139472eff6886771a982f3083da5d421f24c29181e63888228dc81ca60d69e1", + "hex", + ), + ), + }), + ], + }), + }); + + const transactionOutcome = transactionsConverter.transactionOnNetworkToOutcome(transactionOnNetwork); + const events = findEventsByFirstTopic(transactionOutcome, "startPerformAction"); + const parsed = parser.parseEvents({ events }); + + assert.deepEqual(parsed, [ + { + data: { + action_id: new BigNumber("1"), + group_id: new BigNumber("0"), + action_data: { + name: "SendTransferExecuteEgld", + fields: [ + { + to: Address.fromBech32( + "erd1qqqqqqqqqqqqqpgq6qr0w0zzyysklfneh32eqp2cf383zc89d8sstnkl60", + ), + egld_amount: new BigNumber("0"), + opt_gas_limit: null, + endpoint_name: Buffer.from("add"), + arguments: [Buffer.from("07", "hex")], + }, + ], + }, + signers: [Address.fromBech32("erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th")], + }, + }, + ]); + }); + + it("cannot parse events, when definition is missing", async function () { + const parser = new TransactionEventsParser({ + abi: await loadAbiRegistry("src/testdata/esdt-safe.abi.json"), + }); + + assert.throws(() => { + parser.parseEvents({ + events: [ + new TransactionEvent({ + identifier: "foobar", + topics: [Buffer.from("doFoobar")], + }), + ], + }); + }, "Invariant failed: [event [doFoobar] not found]"); + }); + + it("parses event (with multi-values)", async function () { + const abi = AbiRegistry.create({ + events: [ + { + identifier: "doFoobar", + inputs: [ + { + name: "a", + type: "multi", + indexed: true, + }, + { + name: "b", + type: "multi", + indexed: true, + }, + { + name: "c", + type: "u8", + indexed: false, + }, + ], + }, + ], + }); + + const parser = new TransactionEventsParser({ abi }); + const parsed = parser.parseEvent({ + event: new TransactionEvent({ + identifier: "foobar", + topics: [ + Buffer.from("doFoobar"), + Buffer.from([42]), + Buffer.from("test"), + Buffer.from([43]), + Buffer.from("test"), + Buffer.from("test"), + Buffer.from([44]), + ], + dataItems: [Buffer.from([42])], + }), + }); + + assert.deepEqual(parsed, { + a: [new BigNumber(42), "test", new BigNumber(43), "test"], + b: ["test", new BigNumber(44)], + c: new BigNumber(42), + }); + }); +}); diff --git a/src/transactionsOutcomeParsers/transactionEventsParser.ts b/src/transactionsOutcomeParsers/transactionEventsParser.ts new file mode 100644 index 000000000..4d568e077 --- /dev/null +++ b/src/transactionsOutcomeParsers/transactionEventsParser.ts @@ -0,0 +1,55 @@ +import { ResultsParser } from "../smartcontracts"; +import { EventDefinition } from "../smartcontracts/typesystem/event"; +import { TransactionEvent } from "./resources"; + +interface IAbi { + getEvent(name: string): EventDefinition; +} + +export class TransactionEventsParser { + private readonly legacyResultsParser: ResultsParser; + private readonly abi: IAbi; + private readonly firstTopicIsIdentifier: boolean; + + constructor(options: { abi: IAbi; firstTopicIsIdentifier?: boolean }) { + this.legacyResultsParser = new ResultsParser(); + this.abi = options.abi; + + // By default, we consider that the first topic is the event identifier. + // This is true for log entries emitted by smart contracts: + // https://github.com/multiversx/mx-chain-vm-go/blob/v1.5.27/vmhost/contexts/output.go#L270 + // https://github.com/multiversx/mx-chain-vm-go/blob/v1.5.27/vmhost/contexts/output.go#L283 + this.firstTopicIsIdentifier = options.firstTopicIsIdentifier ?? true; + } + + parseEvents(options: { events: TransactionEvent[] }): any[] { + const results = []; + + for (const event of options.events) { + const parsedEvent = this.parseEvent({ event }); + results.push(parsedEvent); + } + + return results; + } + + parseEvent(options: { event: TransactionEvent }): any { + const topics = options.event.topics.map((topic) => Buffer.from(topic)); + const abiIdentifier = this.firstTopicIsIdentifier ? topics[0]?.toString() : options.event.identifier; + + if (this.firstTopicIsIdentifier) { + topics.shift(); + } + + const dataItems = options.event.dataItems.map((dataItem) => Buffer.from(dataItem)); + const eventDefinition = this.abi.getEvent(abiIdentifier); + + const parsedEvent = this.legacyResultsParser.doParseEvent({ + topics: topics, + dataItems: dataItems, + eventDefinition: eventDefinition, + }); + + return parsedEvent; + } +} diff --git a/src/transferTransactionsFactory.spec.ts b/src/transferTransactionsFactory.spec.ts index 93638a384..66b91fe16 100644 --- a/src/transferTransactionsFactory.spec.ts +++ b/src/transferTransactionsFactory.spec.ts @@ -1,9 +1,9 @@ import { assert } from "chai"; import { Address } from "./address"; import { GasEstimator } from "./gasEstimator"; -import { TokenTransfer } from "./tokenTransfer"; +import { TokenTransfer } from "./tokens"; import { TransactionPayload } from "./transactionPayload"; -import { TransferTransactionsFactory } from "./transferTransactionsFactory"; +import { TransferTransactionsFactory } from "./transactionsFactories/transferTransactionsFactory"; describe("test transaction factory", () => { const factory = new TransferTransactionsFactory(new GasEstimator()); diff --git a/src/transferTransactionsFactory.ts b/src/transferTransactionsFactory.ts deleted file mode 100644 index 7444bfde5..000000000 --- a/src/transferTransactionsFactory.ts +++ /dev/null @@ -1,157 +0,0 @@ -import { IAddress, IChainID, IGasLimit, IGasPrice, INonce, ITokenTransfer, ITransactionPayload, ITransactionValue } from "./interface"; -import { ArgSerializer } from "./smartcontracts/argSerializer"; -import { AddressValue, BigUIntValue, BytesValue, TypedValue, U16Value, U64Value } from "./smartcontracts/typesystem"; -import { Transaction } from "./transaction"; -import { TransactionPayload } from "./transactionPayload"; - -interface IGasEstimator { - forEGLDTransfer(dataLength: number): number; - forESDTTransfer(dataLength: number): number; - forESDTNFTTransfer(dataLength: number): number; - forMultiESDTNFTTransfer(dataLength: number, numTransfers: number): number; -} - -export class TransferTransactionsFactory { - private readonly gasEstimator; - - constructor(gasEstimator: IGasEstimator) { - this.gasEstimator = gasEstimator; - } - - createEGLDTransfer(args: { - nonce?: INonce; - value: ITransactionValue; - receiver: IAddress; - sender: IAddress; - gasPrice?: IGasPrice; - gasLimit?: IGasLimit; - data?: ITransactionPayload; - chainID: IChainID; - }) { - const dataLength = args.data?.length() || 0; - const estimatedGasLimit = this.gasEstimator.forEGLDTransfer(dataLength); - - return new Transaction({ - nonce: args.nonce, - value: args.value, - receiver: args.receiver, - sender: args.sender, - gasPrice: args.gasPrice, - gasLimit: args.gasLimit || estimatedGasLimit, - data: args.data, - chainID: args.chainID - }); - } - - createESDTTransfer(args: { - tokenTransfer: ITokenTransfer, - nonce?: INonce; - receiver: IAddress; - sender: IAddress; - gasPrice?: IGasPrice; - gasLimit?: IGasLimit; - chainID: IChainID; - }) { - const { argumentsString } = new ArgSerializer().valuesToString([ - // The token identifier - BytesValue.fromUTF8(args.tokenTransfer.tokenIdentifier), - // The transfered amount - new BigUIntValue(args.tokenTransfer.valueOf()), - ]); - - const data = `ESDTTransfer@${argumentsString}`; - const transactionPayload = new TransactionPayload(data); - const dataLength = transactionPayload.length() || 0; - const estimatedGasLimit = this.gasEstimator.forESDTTransfer(dataLength); - - return new Transaction({ - nonce: args.nonce, - receiver: args.receiver, - sender: args.sender, - gasPrice: args.gasPrice, - gasLimit: args.gasLimit || estimatedGasLimit, - data: transactionPayload, - chainID: args.chainID - }); - } - - createESDTNFTTransfer(args: { - tokenTransfer: ITokenTransfer, - nonce?: INonce; - destination: IAddress; - sender: IAddress; - gasPrice?: IGasPrice; - gasLimit?: IGasLimit; - chainID: IChainID; - }) { - const { argumentsString } = new ArgSerializer().valuesToString([ - // The token identifier - BytesValue.fromUTF8(args.tokenTransfer.tokenIdentifier), - // The nonce of the token - new U64Value(args.tokenTransfer.nonce), - // The transferred quantity - new BigUIntValue(args.tokenTransfer.valueOf()), - // The destination address - new AddressValue(args.destination) - ]); - - const data = `ESDTNFTTransfer@${argumentsString}`; - const transactionPayload = new TransactionPayload(data); - const dataLength = transactionPayload.length() || 0; - const estimatedGasLimit = this.gasEstimator.forESDTNFTTransfer(dataLength); - - return new Transaction({ - nonce: args.nonce, - receiver: args.sender, - sender: args.sender, - gasPrice: args.gasPrice, - gasLimit: args.gasLimit || estimatedGasLimit, - data: transactionPayload, - chainID: args.chainID - }); - } - - createMultiESDTNFTTransfer(args: { - tokenTransfers: ITokenTransfer[], - nonce?: INonce; - destination: IAddress; - sender: IAddress; - gasPrice?: IGasPrice; - gasLimit?: IGasLimit; - chainID: IChainID; - }) { - const parts: TypedValue[] = [ - // The destination address - new AddressValue(args.destination), - // Number of tokens - new U16Value(args.tokenTransfers.length) - ]; - - for (const payment of args.tokenTransfers) { - parts.push(...[ - // The token identifier - BytesValue.fromUTF8(payment.tokenIdentifier), - // The nonce of the token - new U64Value(payment.nonce), - // The transfered quantity - new BigUIntValue(payment.valueOf()) - ]); - } - - const { argumentsString } = new ArgSerializer().valuesToString(parts); - const data = `MultiESDTNFTTransfer@${argumentsString}`; - const transactionPayload = new TransactionPayload(data); - const dataLength = transactionPayload.length() || 0; - const estimatedGasLimit = this.gasEstimator.forMultiESDTNFTTransfer(dataLength, args.tokenTransfers.length); - - return new Transaction({ - nonce: args.nonce, - receiver: args.sender, - sender: args.sender, - gasPrice: args.gasPrice, - gasLimit: args.gasLimit || estimatedGasLimit, - data: transactionPayload, - chainID: args.chainID - }); - } -} diff --git a/src/utils.codec.spec.ts b/src/utils.codec.spec.ts index 1ab1660ba..0da21c14d 100644 --- a/src/utils.codec.spec.ts +++ b/src/utils.codec.spec.ts @@ -1,11 +1,22 @@ import { assert } from "chai"; -import { isPaddedHex, numberToPaddedHex, zeroPadStringIfOddLength } from "./utils.codec"; +import { byteArrayToHex, isPaddedHex, numberToPaddedHex, utf8ToHex, zeroPadStringIfOddLength } from "./utils.codec"; describe("test codec utils", () => { it("should convert numberToPaddedHex", () => { + assert.equal(numberToPaddedHex(0), "00"); assert.equal(numberToPaddedHex(1), "01"); assert.equal(numberToPaddedHex(10), "0a"); assert.equal(numberToPaddedHex(256), "0100"); + + assert.equal(numberToPaddedHex(0n), "00"); + assert.equal(numberToPaddedHex(1n), "01"); + assert.equal(numberToPaddedHex(10n), "0a"); + assert.equal(numberToPaddedHex(256n), "0100"); + + assert.equal(numberToPaddedHex("0"), "00"); + assert.equal(numberToPaddedHex("1"), "01"); + assert.equal(numberToPaddedHex("10"), "0a"); + assert.equal(numberToPaddedHex("256"), "0100"); }); it("should check if isPaddedHex", () => { @@ -21,4 +32,18 @@ describe("test codec utils", () => { assert.equal(zeroPadStringIfOddLength("1"), "01"); assert.equal(zeroPadStringIfOddLength("01"), "01"); }); + + it("should convert byteArrayToHex", () => { + const firstArray = new Uint8Array([0x05, 0x00]); + const secondArray = new Uint8Array([0x7]); + + assert.equal(byteArrayToHex(firstArray), "0500"); + assert.equal(byteArrayToHex(secondArray), "07"); + }); + + it("should convert utf8ToHex", () => { + assert.equal(utf8ToHex("stringandnumber7"), "737472696e67616e646e756d62657237"); + assert.equal(utf8ToHex("somestring"), "736f6d65737472696e67"); + assert.equal(utf8ToHex("aaa"), "616161"); + }); }); diff --git a/src/utils.codec.ts b/src/utils.codec.ts index 8867c889f..0e03b658b 100644 --- a/src/utils.codec.ts +++ b/src/utils.codec.ts @@ -1,7 +1,18 @@ import BigNumber from "bignumber.js"; +import { Address } from "./address"; +import { IAddress } from "./interface"; +import * as contractsCodecUtils from "./smartcontracts/codec/utils"; -export function numberToPaddedHex(value: BigNumber.Value) { - let hex = new BigNumber(value).toString(16); +export function numberToPaddedHex(value: bigint | number | BigNumber.Value) { + let hexableNumber: { toString(radix?: number): string }; + + if (typeof value === "bigint" || typeof value === "number") { + hexableNumber = value; + } else { + hexableNumber = new BigNumber(value); + } + + const hex = hexableNumber.toString(16); return zeroPadStringIfOddLength(hex); } @@ -20,3 +31,30 @@ export function zeroPadStringIfOddLength(input: string): string { return input; } + +export function utf8ToHex(value: string) { + const hex = Buffer.from(value).toString("hex"); + return zeroPadStringIfOddLength(hex); +} + +export function boolToHex(value: boolean) { + return utf8ToHex(value.toString()); +} + +export function byteArrayToHex(byteArray: Uint8Array): string { + const hexString = Buffer.from(byteArray).toString("hex"); + return zeroPadStringIfOddLength(hexString); +} + +export function bigIntToHex(value: BigNumber.Value): string { + if (value == 0) { + return ""; + } + + return contractsCodecUtils.getHexMagnitudeOfBigInt(value); +} + +export function addressToHex(address: IAddress): string { + const buffer = Address.fromBech32(address.toString()).pubkey(); + return buffer.toString("hex"); +} diff --git a/src/utils.ts b/src/utils.ts index 7b1a78b52..dd72af659 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -37,19 +37,19 @@ export function guardLength(withLength: { length?: number }, expectedLength: num } } -export function guardNotEmpty(value: { isEmpty?: () => boolean, length?: number }, what: string) { +export function guardNotEmpty(value: { isEmpty?: () => boolean; length?: number }, what: string) { if (isEmpty(value)) { throw new errors.ErrInvariantFailed(`${what} is empty`); } } -export function guardEmpty(value: { isEmpty?: () => boolean, length?: number }, what: string) { +export function guardEmpty(value: { isEmpty?: () => boolean; length?: number }, what: string) { if (!isEmpty(value)) { throw new errors.ErrInvariantFailed(`${what} is not empty`); } } -export function isEmpty(value: { isEmpty?: () => boolean, length?: number }): boolean { +export function isEmpty(value: { isEmpty?: () => boolean; length?: number }): boolean { if (value.isEmpty) { return value.isEmpty(); } diff --git a/tsconfig.json b/tsconfig.json index 716261e67..089ef3062 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,10 +1,10 @@ { "compilerOptions": { "module": "CommonJS", - "target": "es2015", + "target": "ES2020", "outDir": "out", "lib": [ - "ES2015", + "ES2020", "DOM" ], "sourceMap": true, diff --git a/tsconfig.tests.json b/tsconfig.tests.json index 150c202de..52a987db1 100644 --- a/tsconfig.tests.json +++ b/tsconfig.tests.json @@ -1,10 +1,10 @@ { "compilerOptions": { "module": "CommonJS", - "target": "ES2015", + "target": "ES2020", "outDir": "out-tests", "lib": [ - "ES2015", + "ES2020", "DOM" ], "sourceMap": true, @@ -23,12 +23,10 @@ "src/**/*", ], "exclude": [ - "node_modules", + "node_modules", "out", "out-tests", "out-browser", - "out-browser-tests", - "src/examples", - "src/_docs" + "out-browser-tests" ] } diff --git a/tslint.json b/tslint.json deleted file mode 100644 index 9e0c39729..000000000 --- a/tslint.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "rules": { - "no-string-throw": true, - "no-unused-expression": true, - "no-duplicate-variable": true, - "no-floating-promises": true, - "curly": true, - "class-name": true, - "semicolon": [ - true, - "always" - ], - "triple-equals": false - }, - "defaultSeverity": "warning" -}