├── CNAME ├── .prettierignore ├── CODEOWNERS ├── borsh-ts ├── test │ ├── .eslintrc.yml │ ├── fuzz │ │ ├── corpus │ │ │ ├── 004b705c22403d1c22ceab37181f6340b5b269b7faee05d97fe65b1ab91cd171 │ │ │ ├── 25b2da453a8cb0d574d31a06f0a57fc7b5f2cb5c08ba00ff900a79ebd0637da7 │ │ │ ├── 2f6f8117189cb82e111a13b3ffb19fcaf35deef41f8c084726c5aac5a4551696 │ │ │ ├── 4eb3f263ac0d9bac83d67e28d3381c64c61654579dc455a9e5ccaa54bc093721 │ │ │ ├── 5a0ac0f8936af102c2ea974ba237896a8ee4dffebd27b65c8101a1d8897d6409 │ │ │ ├── 67fc403d488908e0a27d74a2de056c34c40850bbe61b9ebc82cf48365484f4fc │ │ │ ├── 7fa19d2ffc291390ebe8a238baae9bddbd620d018f803540e09ac1bc82ec3a6c │ │ │ ├── 824d229c23c200a937bcd558a5c55d25830a8efffb9e8661a81e2db3584ca512 │ │ │ ├── 9d6a787b7d95c13bb67d02137cf3e506f550fcfa19363050d1e4917250d83d32 │ │ │ ├── a330e2fd8d25278e3d28205107e67373646fe7c5370030819ccc13990375ca73 │ │ │ ├── a53d5be4493c7cccab1d70bdaf44b17f2cd52501f1b5567ed05406163cf96fef │ │ │ ├── b7361e727623b1cdafda1411e27c119009d0fcccbdddd5fe669c8c02bf04f7dd │ │ │ ├── b98f80e8a618038000215158136c1f2dbd9ed3cad450556faf7b18db4184b42e │ │ │ ├── bec080831059d96afd82176d6e8ee36819bb22b7304f4e23f61d189a6c221f92 │ │ │ ├── cf94b13d93b7accf950d3d2a1be308f7d25d21f81622a7ffc7da3733c03eb572 │ │ │ ├── d2897d4dc13f861fb5e7caf8e5b7e1c9962a36c8489ada9cb0f798c1ec967eb7 │ │ │ ├── dfbc571f6a049f8f66f2fcb510fffc51f5c78078db2a2fb5cf19b0bd840291ee │ │ │ ├── e08ccfae0c2fd6432c8e1bf841050f0b2d78fa519ac76f178db0757d1bd3e96e │ │ │ └── ecd71b60d1820dbbcb06d1f981ab86d16876888bb7ba665637c5717c0a8916b2 │ │ ├── transaction-example │ │ │ ├── enums.d.ts │ │ │ ├── serialize.js │ │ │ ├── enums.js │ │ │ ├── serialize.d.ts │ │ │ ├── key_pair.d.ts │ │ │ ├── signer.d.ts │ │ │ ├── signer.js │ │ │ ├── transaction.d.ts │ │ │ ├── key_pair.js │ │ │ └── transaction.js │ │ └── borsh-roundtrip.js │ └── serialize.test.js ├── .eslintrc.yml └── index.ts ├── .gitignore ├── .travis.yml ├── .eslintrc.yml ├── .github └── workflows │ └── borsh.yml ├── tsconfig.json ├── LICENSE-MIT.txt ├── package.json ├── lib ├── index.d.ts └── index.js ├── README.md └── LICENSE-APACHE /CNAME: -------------------------------------------------------------------------------- 1 | borsh.io -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | **/*.d.ts -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @volovyk-s @ailisp 2 | -------------------------------------------------------------------------------- /borsh-ts/test/.eslintrc.yml: -------------------------------------------------------------------------------- 1 | extends: '../../.eslintrc.yml' 2 | env: 3 | jest: true 4 | -------------------------------------------------------------------------------- /borsh-ts/test/fuzz/corpus/004b705c22403d1c22ceab37181f6340b5b269b7faee05d97fe65b1ab91cd171: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailisp/borsh-js/master/borsh-ts/test/fuzz/corpus/004b705c22403d1c22ceab37181f6340b5b269b7faee05d97fe65b1ab91cd171 -------------------------------------------------------------------------------- /borsh-ts/test/fuzz/corpus/25b2da453a8cb0d574d31a06f0a57fc7b5f2cb5c08ba00ff900a79ebd0637da7: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailisp/borsh-js/master/borsh-ts/test/fuzz/corpus/25b2da453a8cb0d574d31a06f0a57fc7b5f2cb5c08ba00ff900a79ebd0637da7 -------------------------------------------------------------------------------- /borsh-ts/test/fuzz/corpus/2f6f8117189cb82e111a13b3ffb19fcaf35deef41f8c084726c5aac5a4551696: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailisp/borsh-js/master/borsh-ts/test/fuzz/corpus/2f6f8117189cb82e111a13b3ffb19fcaf35deef41f8c084726c5aac5a4551696 -------------------------------------------------------------------------------- /borsh-ts/test/fuzz/corpus/4eb3f263ac0d9bac83d67e28d3381c64c61654579dc455a9e5ccaa54bc093721: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailisp/borsh-js/master/borsh-ts/test/fuzz/corpus/4eb3f263ac0d9bac83d67e28d3381c64c61654579dc455a9e5ccaa54bc093721 -------------------------------------------------------------------------------- /borsh-ts/test/fuzz/corpus/5a0ac0f8936af102c2ea974ba237896a8ee4dffebd27b65c8101a1d8897d6409: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailisp/borsh-js/master/borsh-ts/test/fuzz/corpus/5a0ac0f8936af102c2ea974ba237896a8ee4dffebd27b65c8101a1d8897d6409 -------------------------------------------------------------------------------- /borsh-ts/test/fuzz/corpus/67fc403d488908e0a27d74a2de056c34c40850bbe61b9ebc82cf48365484f4fc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailisp/borsh-js/master/borsh-ts/test/fuzz/corpus/67fc403d488908e0a27d74a2de056c34c40850bbe61b9ebc82cf48365484f4fc -------------------------------------------------------------------------------- /borsh-ts/test/fuzz/corpus/7fa19d2ffc291390ebe8a238baae9bddbd620d018f803540e09ac1bc82ec3a6c: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailisp/borsh-js/master/borsh-ts/test/fuzz/corpus/7fa19d2ffc291390ebe8a238baae9bddbd620d018f803540e09ac1bc82ec3a6c -------------------------------------------------------------------------------- /borsh-ts/test/fuzz/corpus/824d229c23c200a937bcd558a5c55d25830a8efffb9e8661a81e2db3584ca512: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailisp/borsh-js/master/borsh-ts/test/fuzz/corpus/824d229c23c200a937bcd558a5c55d25830a8efffb9e8661a81e2db3584ca512 -------------------------------------------------------------------------------- /borsh-ts/test/fuzz/corpus/9d6a787b7d95c13bb67d02137cf3e506f550fcfa19363050d1e4917250d83d32: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailisp/borsh-js/master/borsh-ts/test/fuzz/corpus/9d6a787b7d95c13bb67d02137cf3e506f550fcfa19363050d1e4917250d83d32 -------------------------------------------------------------------------------- /borsh-ts/test/fuzz/corpus/a330e2fd8d25278e3d28205107e67373646fe7c5370030819ccc13990375ca73: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailisp/borsh-js/master/borsh-ts/test/fuzz/corpus/a330e2fd8d25278e3d28205107e67373646fe7c5370030819ccc13990375ca73 -------------------------------------------------------------------------------- /borsh-ts/test/fuzz/corpus/a53d5be4493c7cccab1d70bdaf44b17f2cd52501f1b5567ed05406163cf96fef: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailisp/borsh-js/master/borsh-ts/test/fuzz/corpus/a53d5be4493c7cccab1d70bdaf44b17f2cd52501f1b5567ed05406163cf96fef -------------------------------------------------------------------------------- /borsh-ts/test/fuzz/corpus/b7361e727623b1cdafda1411e27c119009d0fcccbdddd5fe669c8c02bf04f7dd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailisp/borsh-js/master/borsh-ts/test/fuzz/corpus/b7361e727623b1cdafda1411e27c119009d0fcccbdddd5fe669c8c02bf04f7dd -------------------------------------------------------------------------------- /borsh-ts/test/fuzz/corpus/b98f80e8a618038000215158136c1f2dbd9ed3cad450556faf7b18db4184b42e: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailisp/borsh-js/master/borsh-ts/test/fuzz/corpus/b98f80e8a618038000215158136c1f2dbd9ed3cad450556faf7b18db4184b42e -------------------------------------------------------------------------------- /borsh-ts/test/fuzz/corpus/bec080831059d96afd82176d6e8ee36819bb22b7304f4e23f61d189a6c221f92: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailisp/borsh-js/master/borsh-ts/test/fuzz/corpus/bec080831059d96afd82176d6e8ee36819bb22b7304f4e23f61d189a6c221f92 -------------------------------------------------------------------------------- /borsh-ts/test/fuzz/corpus/cf94b13d93b7accf950d3d2a1be308f7d25d21f81622a7ffc7da3733c03eb572: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailisp/borsh-js/master/borsh-ts/test/fuzz/corpus/cf94b13d93b7accf950d3d2a1be308f7d25d21f81622a7ffc7da3733c03eb572 -------------------------------------------------------------------------------- /borsh-ts/test/fuzz/corpus/d2897d4dc13f861fb5e7caf8e5b7e1c9962a36c8489ada9cb0f798c1ec967eb7: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailisp/borsh-js/master/borsh-ts/test/fuzz/corpus/d2897d4dc13f861fb5e7caf8e5b7e1c9962a36c8489ada9cb0f798c1ec967eb7 -------------------------------------------------------------------------------- /borsh-ts/test/fuzz/corpus/dfbc571f6a049f8f66f2fcb510fffc51f5c78078db2a2fb5cf19b0bd840291ee: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailisp/borsh-js/master/borsh-ts/test/fuzz/corpus/dfbc571f6a049f8f66f2fcb510fffc51f5c78078db2a2fb5cf19b0bd840291ee -------------------------------------------------------------------------------- /borsh-ts/test/fuzz/corpus/e08ccfae0c2fd6432c8e1bf841050f0b2d78fa519ac76f178db0757d1bd3e96e: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailisp/borsh-js/master/borsh-ts/test/fuzz/corpus/e08ccfae0c2fd6432c8e1bf841050f0b2d78fa519ac76f178db0757d1bd3e96e -------------------------------------------------------------------------------- /borsh-ts/test/fuzz/corpus/ecd71b60d1820dbbcb06d1f981ab86d16876888bb7ba665637c5717c0a8916b2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailisp/borsh-js/master/borsh-ts/test/fuzz/corpus/ecd71b60d1820dbbcb06d1f981ab86d16876888bb7ba665637c5717c0a8916b2 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Idea IDE files. 2 | **/.idea/ 3 | 4 | # VSCode IDE files. 5 | **/.vscode/ 6 | 7 | # OS X 8 | **/.DS_Store/ 9 | 10 | # JS modules 11 | **/node_modules/ 12 | package-lock.json 13 | yarn.lock 14 | .nyc_output 15 | 16 | -------------------------------------------------------------------------------- /borsh-ts/test/fuzz/transaction-example/enums.d.ts: -------------------------------------------------------------------------------- 1 | export declare abstract class Enum { 2 | enum: string; 3 | constructor(properties: any); 4 | } 5 | export declare abstract class Assignable { 6 | constructor(properties: any); 7 | } 8 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 12 4 | 5 | before_script: 6 | - "curl -H 'Cache-Control: no-cache' https://raw.githubusercontent.com/fossas/fossa-cli/master/install.sh | sudo bash" 7 | 8 | script: 9 | - fossa init 10 | - fossa analyze --server-scan 11 | - fossa test 12 | -------------------------------------------------------------------------------- /.eslintrc.yml: -------------------------------------------------------------------------------- 1 | env: 2 | es6: true 3 | node: true 4 | extends: 5 | - 'eslint:recommended' 6 | parserOptions: 7 | ecmaVersion: 2018 8 | rules: 9 | indent: 10 | - error 11 | - 4 12 | linebreak-style: 13 | - error 14 | - unix 15 | quotes: 16 | - error 17 | - single 18 | semi: 19 | - error 20 | - always 21 | no-console: 0 22 | -------------------------------------------------------------------------------- /borsh-ts/.eslintrc.yml: -------------------------------------------------------------------------------- 1 | env: 2 | es6: true 3 | node: true 4 | extends: 5 | - 'eslint:recommended' 6 | - 'plugin:@typescript-eslint/eslint-recommended' 7 | - 'plugin:@typescript-eslint/recommended' 8 | parser: '@typescript-eslint/parser' 9 | rules: 10 | no-inner-declarations: 1 11 | '@typescript-eslint/no-explicit-any': 1 12 | '@typescript-eslint/camelcase': 1 13 | '@typescript-eslint/explicit-function-return-type': 1 14 | '@typescript-eslint/no-use-before-define': 1 15 | 16 | parserOptions: 17 | ecmaVersion: 2018 18 | sourceType: module -------------------------------------------------------------------------------- /borsh-ts/test/fuzz/transaction-example/serialize.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __importDefault = (this && this.__importDefault) || function (mod) { 3 | return (mod && mod.__esModule) ? mod : { "default": mod }; 4 | }; 5 | const bs58_1 = __importDefault(require("bs58")); 6 | function base_encode(value) { 7 | if (typeof (value) === 'string') { 8 | value = Buffer.from(value, 'utf8'); 9 | } 10 | return bs58_1.default.encode(Buffer.from(value)); 11 | } 12 | exports.base_encode = base_encode; 13 | function base_decode(value) { 14 | return Buffer.from(bs58_1.default.decode(value)); 15 | } 16 | exports.base_decode = base_decode; 17 | -------------------------------------------------------------------------------- /.github/workflows/borsh.yml: -------------------------------------------------------------------------------- 1 | name: Borsh Tests 2 | on: [push] 3 | jobs: 4 | unit-tests: 5 | runs-on: ubuntu-latest 6 | steps: 7 | - uses: actions/checkout@v2 8 | - uses: actions/setup-node@v2 9 | with: 10 | node-version: '14' 11 | - run: yarn install 12 | - run: yarn test 13 | prettier: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v2 17 | - uses: actions/setup-node@v2 18 | with: 19 | node-version: '14' 20 | - run: yarn install 21 | - run: yarn pretty:check 22 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "esModuleInterop": true, 4 | "lib": [ 5 | "es2015", 6 | "esnext", 7 | "dom" 8 | ], 9 | "module": "commonjs", 10 | "target": "esnext", 11 | "moduleResolution": "node", 12 | "outDir": "./lib", 13 | "declaration": true, 14 | "preserveSymlinks": true, 15 | "preserveWatchOutput": true, 16 | "pretty": true, 17 | "forceConsistentCasingInFileNames": true, 18 | "noFallthroughCasesInSwitch": true, 19 | "noImplicitAny": false, 20 | "noImplicitReturns": true, 21 | "experimentalDecorators": true, 22 | "noUnusedLocals": true 23 | }, 24 | "files": ["./borsh-ts/index.ts"] 25 | } 26 | 27 | -------------------------------------------------------------------------------- /borsh-ts/test/fuzz/transaction-example/enums.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | class Enum { 4 | constructor(properties) { 5 | if (Object.keys(properties).length !== 1) { 6 | throw new Error('Enum can only take single value'); 7 | } 8 | Object.keys(properties).map((key) => { 9 | this[key] = properties[key]; 10 | this.enum = key; 11 | }); 12 | } 13 | } 14 | exports.Enum = Enum; 15 | class Assignable { 16 | constructor(properties) { 17 | Object.keys(properties).map((key) => { 18 | this[key] = properties[key]; 19 | }); 20 | } 21 | } 22 | exports.Assignable = Assignable; 23 | -------------------------------------------------------------------------------- /borsh-ts/test/fuzz/borsh-roundtrip.js: -------------------------------------------------------------------------------- 1 | const borsh = require('../../../lib/index.js'); 2 | const transaction = require('./transaction-example/transaction'); 3 | 4 | exports.fuzz = input => { 5 | try { 6 | const deserialized = borsh.deserialize(transaction.SCHEMA, transaction.Transaction, input); 7 | const serialized = borsh.serialize(transaction.SCHEMA, deserialized); 8 | if (!serialized.equals(input)) { 9 | console.log(`Mismatching output:\n${serialized.toString('hex')}\nand input:\n${input.toString('hex')}`); 10 | throw new Error('Mismatching input and output'); 11 | } 12 | } catch (e) { 13 | if (e instanceof borsh.BorshError) { 14 | // Do nothing 15 | } else { 16 | throw e; 17 | } 18 | } 19 | }; -------------------------------------------------------------------------------- /LICENSE-MIT.txt: -------------------------------------------------------------------------------- 1 | Permission is hereby granted, free of charge, to any 2 | person obtaining a copy of this software and associated 3 | documentation files (the "Software"), to deal in the 4 | Software without restriction, including without 5 | limitation the rights to use, copy, modify, merge, 6 | publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software 8 | is furnished to do so, subject to the following 9 | conditions: 10 | 11 | The above copyright notice and this permission notice 12 | shall be included in all copies or substantial portions 13 | of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 16 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 17 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 18 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 19 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 22 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /borsh-ts/test/fuzz/transaction-example/serialize.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | import BN from 'bn.js'; 3 | export declare function base_encode(value: Uint8Array | string): string; 4 | export declare function base_decode(value: string): Uint8Array; 5 | export declare type Schema = Map; 6 | export declare class BorshError extends Error { 7 | originalMessage: string; 8 | fieldPath: string[]; 9 | constructor(message: string); 10 | addToFieldPath(fieldName: string): void; 11 | } 12 | export declare class BinaryWriter { 13 | buf: Buffer; 14 | length: number; 15 | constructor(); 16 | maybe_resize(): void; 17 | write_u8(value: number): void; 18 | write_u32(value: number): void; 19 | write_u64(value: BN): void; 20 | write_u128(value: BN): void; 21 | private write_buffer; 22 | write_string(str: string): void; 23 | write_fixed_array(array: Uint8Array): void; 24 | write_array(array: any[], fn: any): void; 25 | toArray(): Uint8Array; 26 | } 27 | export declare class BinaryReader { 28 | buf: Buffer; 29 | offset: number; 30 | constructor(buf: Buffer); 31 | read_u8(): number; 32 | read_u32(): number; 33 | read_u64(): BN; 34 | read_u128(): BN; 35 | private read_buffer; 36 | read_string(): string; 37 | read_fixed_array(len: number): Uint8Array; 38 | read_array(fn: any): any[]; 39 | } 40 | export declare function serialize(schema: Schema, obj: any): Uint8Array; 41 | export declare function deserialize(schema: Schema, classType: any, buffer: Buffer): any; 42 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "borsh", 3 | "version": "0.7.0", 4 | "description": "Binary Object Representation Serializer for Hashing", 5 | "main": "lib/index.js", 6 | "types": "lib/index.d.ts", 7 | "files": [ 8 | "lib", 9 | "LICENSE-APACHE", 10 | "LICENSE-MIT.txt" 11 | ], 12 | "scripts": { 13 | "build": "tsc -p ./tsconfig.json", 14 | "test": "jest test --runInBand", 15 | "fuzz": "jsfuzz borsh-ts/test/fuzz/borsh-roundtrip.js borsh-ts/test/fuzz/corpus/", 16 | "dev": "yarn build -w", 17 | "pretest": "yarn build", 18 | "lint": "eslint borsh-ts/**/*.ts", 19 | "pretty": "prettier --write borsh-ts/**/*.ts package.json", 20 | "pretty:check": "yarn prettier --loglevel error --check borsh-ts/**/*.ts package.json", 21 | "fix": "eslint borsh-ts/**/*.ts --fix" 22 | }, 23 | "repository": { 24 | "type": "git", 25 | "url": "git+https://github.com/near/borsh-js.git" 26 | }, 27 | "keywords": [ 28 | "serializer", 29 | "binary", 30 | "serializer", 31 | "deserializer", 32 | "consistency", 33 | "deterministic" 34 | ], 35 | "author": "Near Inc", 36 | "license": "Apache-2.0", 37 | "bugs": { 38 | "url": "https://github.com/near/borsh-js/issues" 39 | }, 40 | "homepage": "https://github.com/near/borsh-js#readme", 41 | "devDependencies": { 42 | "@types/babel__core": "^7.1.2", 43 | "@types/babel__template": "^7.0.2", 44 | "@types/bn.js": "^5.1.0", 45 | "@types/node": "^12.7.3", 46 | "@typescript-eslint/eslint-plugin": "^2.18.0", 47 | "@typescript-eslint/parser": "^2.18.0", 48 | "bs58": "^4.0.0", 49 | "eslint": "^6.5.1", 50 | "jest": "^26.0.1", 51 | "js-sha256": "^0.9.0", 52 | "jsfuzz": "^1.0.14", 53 | "prettier": "^2.4.1", 54 | "typescript": "^3.6.2" 55 | }, 56 | "dependencies": { 57 | "bn.js": "^5.2.0", 58 | "bs58": "^4.0.0", 59 | "buffer": "^6.0.3", 60 | "text-encoding-utf-8": "^1.0.2" 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /lib/index.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | import BN from "bn.js"; 3 | export declare function baseEncode(value: Uint8Array | string): string; 4 | export declare function baseDecode(value: string): Buffer; 5 | export declare type Schema = Map; 6 | export declare class BorshError extends Error { 7 | originalMessage: string; 8 | fieldPath: string[]; 9 | constructor(message: string); 10 | addToFieldPath(fieldName: string): void; 11 | } 12 | export declare class BinaryWriter { 13 | buf: Buffer; 14 | length: number; 15 | constructor(); 16 | maybeResize(): void; 17 | writeU8(value: number): void; 18 | writeU16(value: number): void; 19 | writeU32(value: number): void; 20 | writeU64(value: number | BN): void; 21 | writeU128(value: number | BN): void; 22 | writeU256(value: number | BN): void; 23 | writeU512(value: number | BN): void; 24 | private writeBuffer; 25 | writeString(str: string): void; 26 | writeFixedArray(array: Uint8Array): void; 27 | writeArray(array: any[], fn: any): void; 28 | toArray(): Uint8Array; 29 | } 30 | export declare class BinaryReader { 31 | buf: Buffer; 32 | offset: number; 33 | constructor(buf: Buffer); 34 | readU8(): number; 35 | readU16(): number; 36 | readU32(): number; 37 | readU64(): BN; 38 | readU128(): BN; 39 | readU256(): BN; 40 | readU512(): BN; 41 | private readBuffer; 42 | readString(): string; 43 | readFixedArray(len: number): Uint8Array; 44 | readArray(fn: any): any[]; 45 | } 46 | export declare function serialize(schema: Schema, obj: any, Writer?: typeof BinaryWriter): Uint8Array; 47 | export declare function deserialize(schema: Schema, classType: { 48 | new (args: any): T; 49 | }, buffer: Buffer, Reader?: typeof BinaryReader): T; 50 | export declare function deserializeUnchecked(schema: Schema, classType: { 51 | new (args: any): T; 52 | }, buffer: Buffer, Reader?: typeof BinaryReader): T; 53 | -------------------------------------------------------------------------------- /borsh-ts/test/fuzz/transaction-example/key_pair.d.ts: -------------------------------------------------------------------------------- 1 | import { Assignable } from './enums'; 2 | export declare type Arrayish = string | ArrayLike; 3 | export interface Signature { 4 | signature: Uint8Array; 5 | publicKey: PublicKey; 6 | } 7 | /** All supported key types */ 8 | export declare enum KeyType { 9 | ED25519 = 0 10 | } 11 | /** 12 | * PublicKey representation that has type and bytes of the key. 13 | */ 14 | export declare class PublicKey extends Assignable { 15 | keyType: KeyType; 16 | data: Uint8Array; 17 | static from(value: string | PublicKey): PublicKey; 18 | static fromString(encodedKey: string): PublicKey; 19 | toString(): string; 20 | } 21 | export declare abstract class KeyPair { 22 | abstract sign(message: Uint8Array): Signature; 23 | abstract verify(message: Uint8Array, signature: Uint8Array): boolean; 24 | abstract toString(): string; 25 | abstract getPublicKey(): PublicKey; 26 | /** 27 | * @param curve Name of elliptical curve, case-insensitive 28 | * @returns Random KeyPair based on the curve 29 | */ 30 | static fromRandom(curve: string): KeyPair; 31 | static fromString(encodedKey: string): KeyPair; 32 | } 33 | /** 34 | * This class provides key pair functionality for Ed25519 curve: 35 | * generating key pairs, encoding key pairs, signing and verifying. 36 | */ 37 | export declare class KeyPairEd25519 extends KeyPair { 38 | readonly publicKey: PublicKey; 39 | readonly secretKey: string; 40 | /** 41 | * Construct an instance of key pair given a secret key. 42 | * It's generally assumed that these are encoded in base58. 43 | * @param {string} secretKey 44 | */ 45 | constructor(secretKey: string); 46 | /** 47 | * Generate a new random keypair. 48 | * @example 49 | * const keyRandom = KeyPair.fromRandom(); 50 | * keyRandom.publicKey 51 | * // returns [PUBLIC_KEY] 52 | * 53 | * keyRandom.secretKey 54 | * // returns [SECRET_KEY] 55 | */ 56 | static fromRandom(): KeyPairEd25519; 57 | sign(message: Uint8Array): Signature; 58 | verify(message: Uint8Array, signature: Uint8Array): boolean; 59 | toString(): string; 60 | getPublicKey(): PublicKey; 61 | } 62 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Borsh JS 2 | 3 | [![Project license](https://img.shields.io/badge/license-Apache2.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) 4 | [![Project license](https://img.shields.io/badge/license-MIT-blue.svg)](https://opensource.org/licenses/MIT) 5 | [![Discord](https://img.shields.io/discord/490367152054992913?label=discord)](https://discord.gg/Vyp7ETM) 6 | [![Travis status](https://travis-ci.com/near/borsh.svg?branch=master)](https://travis-ci.com/near/borsh-js) 7 | [![NPM version](https://img.shields.io/npm/v/borsh.svg?style=flat-square)](https://npmjs.com/borsh) 8 | [![Size on NPM](https://img.shields.io/bundlephobia/minzip/borsh.svg?style=flat-square)](https://npmjs.com/borsh) 9 | 10 | **Borsh JS** is an implementation of the [Borsh] binary serialization format for 11 | JavaScript and TypeScript projects. 12 | 13 | Borsh stands for _Binary Object Representation Serializer for Hashing_. It is meant to be used in security-critical projects as it prioritizes consistency, 14 | safety, speed, and comes with a strict specification. 15 | 16 | ## Examples 17 | ### Serializing an object 18 | ```javascript 19 | const value = new Test({ x: 255, y: 20, z: '123', q: [1, 2, 3] }); 20 | const schema = new Map([[Test, { kind: 'struct', fields: [['x', 'u8'], ['y', 'u64'], ['z', 'string'], ['q', [3]]] }]]); 21 | const buffer = borsh.serialize(schema, value); 22 | ``` 23 | 24 | ### Deserializing an object 25 | ```javascript 26 | const newValue = borsh.deserialize(schema, Test, buffer); 27 | ``` 28 | 29 | ## Type Mappings 30 | 31 | | Borsh | TypeScript | 32 | |-----------------------|----------------| 33 | | `u8` integer | `number` | 34 | | `u16` integer | `number` | 35 | | `u32` integer | `number` | 36 | | `u64` integer | `BN` | 37 | | `u128` integer | `BN` | 38 | | `u256` integer | `BN` | 39 | | `u512` integer | `BN` | 40 | | `f32` float | N/A | 41 | | `f64` float | N/A | 42 | | fixed-size byte array | `Uint8Array` | 43 | | UTF-8 string | `string` | 44 | | option | `null` or type | 45 | | map | N/A | 46 | | set | N/A | 47 | | structs | `any` | 48 | 49 | ## Contributing 50 | 51 | Install dependencies: 52 | ```bash 53 | yarn install 54 | ``` 55 | 56 | Continuously build with: 57 | ```bash 58 | yarn dev 59 | ``` 60 | 61 | Run tests: 62 | ```bash 63 | yarn test 64 | ``` 65 | 66 | Run linter 67 | ```bash 68 | yarn lint 69 | ``` 70 | ## Publish 71 | 72 | Prepare `dist` version by running: 73 | ```bash 74 | yarn build 75 | ``` 76 | 77 | When publishing to npm use [np](https://github.com/sindresorhus/np). 78 | 79 | # License 80 | This repository is distributed under the terms of both the MIT license and the Apache License (Version 2.0). 81 | See [LICENSE-MIT](LICENSE-MIT.txt) and [LICENSE-APACHE](LICENSE-APACHE) for details. 82 | 83 | [Borsh]: https://borsh.io 84 | -------------------------------------------------------------------------------- /borsh-ts/test/fuzz/transaction-example/signer.d.ts: -------------------------------------------------------------------------------- 1 | import { Signature, KeyPair, PublicKey } from './key_pair'; 2 | import { KeyStore } from './key_stores'; 3 | /** 4 | * General signing interface, can be used for in memory signing, RPC singing, external wallet, HSM, etc. 5 | */ 6 | export declare abstract class Signer { 7 | /** 8 | * Creates new key and returns public key. 9 | */ 10 | abstract createKey(accountId: string, networkId?: string): Promise; 11 | /** 12 | * Returns public key for given account / network. 13 | * @param accountId accountId to retrieve from. 14 | * @param networkId The targeted network. (ex. default, betanet, etc…) 15 | */ 16 | abstract getPublicKey(accountId?: string, networkId?: string): Promise; 17 | /** 18 | * Signs given message, by first hashing with sha256. 19 | * @param message message to sign. 20 | * @param accountId accountId to use for signing. 21 | * @param networkId The targeted network. (ex. default, betanet, etc…) 22 | */ 23 | abstract signMessage(message: Uint8Array, accountId?: string, networkId?: string): Promise; 24 | } 25 | /** 26 | * Signs using in memory key store. 27 | */ 28 | export declare class InMemorySigner extends Signer { 29 | readonly keyStore: KeyStore; 30 | constructor(keyStore: KeyStore); 31 | /** 32 | * Creates a single account Signer instance with account, network and keyPair provided. 33 | * 34 | * Intended to be useful for temporary keys (e.g. claiming a Linkdrop). 35 | * 36 | * @param networkId The targeted network. (ex. default, betanet, etc…) 37 | * @param accountId The NEAR account to assign the key pair to 38 | * @param keyPair The keyPair to use for signing 39 | */ 40 | static fromKeyPair(networkId: string, accountId: string, keyPair: KeyPair): Promise; 41 | /** 42 | * Creates a public key for the account given 43 | * @param accountId The NEAR account to assign a public key to 44 | * @param networkId The targeted network. (ex. default, betanet, etc…) 45 | * @returns {Promise} 46 | */ 47 | createKey(accountId: string, networkId: string): Promise; 48 | /** 49 | * Gets the existing public key for a given account 50 | * @param accountId The NEAR account to assign a public key to 51 | * @param networkId The targeted network. (ex. default, betanet, etc…) 52 | * @returns {Promise} Returns the public key or null if not found 53 | */ 54 | getPublicKey(accountId?: string, networkId?: string): Promise; 55 | /** 56 | * @param message A message to be signed, typically a serialized transaction 57 | * @param accountId the NEAR account signing the message 58 | * @param networkId The targeted network. (ex. default, betanet, etc…) 59 | * @returns {Promise} 60 | */ 61 | signMessage(message: Uint8Array, accountId?: string, networkId?: string): Promise; 62 | toString(): string; 63 | } 64 | -------------------------------------------------------------------------------- /borsh-ts/test/fuzz/transaction-example/signer.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __importDefault = (this && this.__importDefault) || function (mod) { 3 | return (mod && mod.__esModule) ? mod : { "default": mod }; 4 | }; 5 | Object.defineProperty(exports, "__esModule", { value: true }); 6 | const js_sha256_1 = __importDefault(require("js-sha256")); 7 | const key_pair_1 = require("./key_pair"); 8 | const key_stores_1 = require("./key_stores"); 9 | /** 10 | * General signing interface, can be used for in memory signing, RPC singing, external wallet, HSM, etc. 11 | */ 12 | class Signer { 13 | } 14 | exports.Signer = Signer; 15 | /** 16 | * Signs using in memory key store. 17 | */ 18 | class InMemorySigner extends Signer { 19 | constructor(keyStore) { 20 | super(); 21 | this.keyStore = keyStore; 22 | } 23 | /** 24 | * Creates a single account Signer instance with account, network and keyPair provided. 25 | * 26 | * Intended to be useful for temporary keys (e.g. claiming a Linkdrop). 27 | * 28 | * @param networkId The targeted network. (ex. default, betanet, etc…) 29 | * @param accountId The NEAR account to assign the key pair to 30 | * @param keyPair The keyPair to use for signing 31 | */ 32 | static async fromKeyPair(networkId, accountId, keyPair) { 33 | const keyStore = new key_stores_1.InMemoryKeyStore(); 34 | await keyStore.setKey(networkId, accountId, keyPair); 35 | return new InMemorySigner(keyStore); 36 | } 37 | /** 38 | * Creates a public key for the account given 39 | * @param accountId The NEAR account to assign a public key to 40 | * @param networkId The targeted network. (ex. default, betanet, etc…) 41 | * @returns {Promise} 42 | */ 43 | async createKey(accountId, networkId) { 44 | const keyPair = key_pair_1.KeyPair.fromRandom('ed25519'); 45 | await this.keyStore.setKey(networkId, accountId, keyPair); 46 | return keyPair.getPublicKey(); 47 | } 48 | /** 49 | * Gets the existing public key for a given account 50 | * @param accountId The NEAR account to assign a public key to 51 | * @param networkId The targeted network. (ex. default, betanet, etc…) 52 | * @returns {Promise} Returns the public key or null if not found 53 | */ 54 | async getPublicKey(accountId, networkId) { 55 | const keyPair = await this.keyStore.getKey(networkId, accountId); 56 | if (keyPair === null) { 57 | return null; 58 | } 59 | return keyPair.getPublicKey(); 60 | } 61 | /** 62 | * @param message A message to be signed, typically a serialized transaction 63 | * @param accountId the NEAR account signing the message 64 | * @param networkId The targeted network. (ex. default, betanet, etc…) 65 | * @returns {Promise} 66 | */ 67 | async signMessage(message, accountId, networkId) { 68 | const hash = new Uint8Array(js_sha256_1.default.sha256.array(message)); 69 | if (!accountId) { 70 | throw new Error('InMemorySigner requires provided account id'); 71 | } 72 | const keyPair = await this.keyStore.getKey(networkId, accountId); 73 | if (keyPair === null) { 74 | throw new Error(`Key for ${accountId} not found in ${networkId}`); 75 | } 76 | return keyPair.sign(hash); 77 | } 78 | toString() { 79 | return `InMemorySigner(${this.keyStore})`; 80 | } 81 | } 82 | exports.InMemorySigner = InMemorySigner; 83 | -------------------------------------------------------------------------------- /borsh-ts/test/fuzz/transaction-example/transaction.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | import BN from 'bn.js'; 3 | import { Enum, Assignable } from './enums'; 4 | import { KeyType, PublicKey } from './key_pair'; 5 | import { Signer } from './signer'; 6 | export declare class FunctionCallPermission extends Assignable { 7 | allowance?: BN; 8 | receiverId: string; 9 | methodNames: string[]; 10 | } 11 | export declare class FullAccessPermission extends Assignable { 12 | } 13 | export declare class AccessKeyPermission extends Enum { 14 | functionCall: FunctionCallPermission; 15 | fullAccess: FullAccessPermission; 16 | } 17 | export declare class AccessKey extends Assignable { 18 | nonce: number; 19 | permission: AccessKeyPermission; 20 | } 21 | export declare function fullAccessKey(): AccessKey; 22 | export declare function functionCallAccessKey(receiverId: string, methodNames: string[], allowance?: BN): AccessKey; 23 | export declare class IAction extends Assignable { 24 | } 25 | export declare class CreateAccount extends IAction { 26 | } 27 | export declare class DeployContract extends IAction { 28 | code: Uint8Array; 29 | } 30 | export declare class FunctionCall extends IAction { 31 | methodName: string; 32 | args: Uint8Array; 33 | gas: BN; 34 | deposit: BN; 35 | } 36 | export declare class Transfer extends IAction { 37 | deposit: BN; 38 | } 39 | export declare class Stake extends IAction { 40 | stake: BN; 41 | publicKey: PublicKey; 42 | } 43 | export declare class AddKey extends IAction { 44 | publicKey: PublicKey; 45 | accessKey: AccessKey; 46 | } 47 | export declare class DeleteKey extends IAction { 48 | publicKey: PublicKey; 49 | } 50 | export declare class DeleteAccount extends IAction { 51 | beneficiaryId: string; 52 | } 53 | export declare function createAccount(): Action; 54 | export declare function deployContract(code: Uint8Array): Action; 55 | /** 56 | * Constructs {@link Action} instance representing contract method call. 57 | * 58 | * @param methodName the name of the method to call 59 | * @param args arguments to pass to method. Can be either plain JS object which gets serialized as JSON automatically 60 | * or `Uint8Array` instance which represents bytes passed as is. 61 | * @param gas max amount of gas that method call can use 62 | * @param deposit amount of NEAR (in yoctoNEAR) to send together with the call 63 | */ 64 | export declare function functionCall(methodName: string, args: Uint8Array | object, gas: BN, deposit: BN): Action; 65 | export declare function transfer(deposit: BN): Action; 66 | export declare function stake(stake: BN, publicKey: PublicKey): Action; 67 | export declare function addKey(publicKey: PublicKey, accessKey: AccessKey): Action; 68 | export declare function deleteKey(publicKey: PublicKey): Action; 69 | export declare function deleteAccount(beneficiaryId: string): Action; 70 | export declare class Signature extends Assignable { 71 | keyType: KeyType; 72 | data: Uint8Array; 73 | } 74 | export declare class Transaction extends Assignable { 75 | signerId: string; 76 | publicKey: PublicKey; 77 | nonce: number; 78 | receiverId: string; 79 | actions: Action[]; 80 | blockHash: Uint8Array; 81 | encode(): Uint8Array; 82 | static decode(bytes: Buffer): Transaction; 83 | } 84 | export declare class SignedTransaction extends Assignable { 85 | transaction: Transaction; 86 | signature: Signature; 87 | encode(): Uint8Array; 88 | static decode(bytes: Buffer): SignedTransaction; 89 | } 90 | /** 91 | * Contains a list of the valid transaction Actions available with this API 92 | */ 93 | export declare class Action extends Enum { 94 | createAccount: CreateAccount; 95 | deployContract: DeployContract; 96 | functionCall: FunctionCall; 97 | transfer: Transfer; 98 | stake: Stake; 99 | addKey: AddKey; 100 | deleteKey: DeleteKey; 101 | deleteAccount: DeleteAccount; 102 | } 103 | export declare const SCHEMA: Map; 104 | export declare function createTransaction(signerId: string, publicKey: PublicKey, receiverId: string, nonce: number, actions: Action[], blockHash: Uint8Array): Transaction; 105 | export declare function signTransaction(transaction: Transaction, signer: Signer, accountId?: string, networkId?: string): Promise<[Uint8Array, SignedTransaction]>; 106 | export declare function signTransaction(receiverId: string, nonce: number, actions: Action[], blockHash: Uint8Array, signer: Signer, accountId?: string, networkId?: string): Promise<[Uint8Array, SignedTransaction]>; 107 | -------------------------------------------------------------------------------- /borsh-ts/test/fuzz/transaction-example/key_pair.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __importDefault = (this && this.__importDefault) || function (mod) { 3 | return (mod && mod.__esModule) ? mod : { "default": mod }; 4 | }; 5 | Object.defineProperty(exports, "__esModule", { value: true }); 6 | const tweetnacl_1 = __importDefault(require("tweetnacl")); 7 | const serialize_1 = require("./serialize"); 8 | const enums_1 = require("./enums"); 9 | /** All supported key types */ 10 | var KeyType; 11 | (function (KeyType) { 12 | KeyType[KeyType["ED25519"] = 0] = "ED25519"; 13 | })(KeyType = exports.KeyType || (exports.KeyType = {})); 14 | function key_type_to_str(keyType) { 15 | switch (keyType) { 16 | case KeyType.ED25519: return 'ed25519'; 17 | default: throw new Error(`Unknown key type ${keyType}`); 18 | } 19 | } 20 | function str_to_key_type(keyType) { 21 | switch (keyType.toLowerCase()) { 22 | case 'ed25519': return KeyType.ED25519; 23 | default: throw new Error(`Unknown key type ${keyType}`); 24 | } 25 | } 26 | /** 27 | * PublicKey representation that has type and bytes of the key. 28 | */ 29 | class PublicKey extends enums_1.Assignable { 30 | static from(value) { 31 | if (typeof value === 'string') { 32 | return PublicKey.fromString(value); 33 | } 34 | return value; 35 | } 36 | static fromString(encodedKey) { 37 | const parts = encodedKey.split(':'); 38 | if (parts.length === 1) { 39 | return new PublicKey({ keyType: KeyType.ED25519, data: serialize_1.base_decode(parts[0]) }); 40 | } 41 | else if (parts.length === 2) { 42 | return new PublicKey({ keyType: str_to_key_type(parts[0]), data: serialize_1.base_decode(parts[1]) }); 43 | } 44 | else { 45 | throw new Error('Invalid encoded key format, must be :'); 46 | } 47 | } 48 | toString() { 49 | return `${key_type_to_str(this.keyType)}:${serialize_1.base_encode(this.data)}`; 50 | } 51 | } 52 | exports.PublicKey = PublicKey; 53 | class KeyPair { 54 | /** 55 | * @param curve Name of elliptical curve, case-insensitive 56 | * @returns Random KeyPair based on the curve 57 | */ 58 | static fromRandom(curve) { 59 | switch (curve.toUpperCase()) { 60 | case 'ED25519': return KeyPairEd25519.fromRandom(); 61 | default: throw new Error(`Unknown curve ${curve}`); 62 | } 63 | } 64 | static fromString(encodedKey) { 65 | const parts = encodedKey.split(':'); 66 | if (parts.length === 1) { 67 | return new KeyPairEd25519(parts[0]); 68 | } 69 | else if (parts.length === 2) { 70 | switch (parts[0].toUpperCase()) { 71 | case 'ED25519': return new KeyPairEd25519(parts[1]); 72 | default: throw new Error(`Unknown curve: ${parts[0]}`); 73 | } 74 | } 75 | else { 76 | throw new Error('Invalid encoded key format, must be :'); 77 | } 78 | } 79 | } 80 | exports.KeyPair = KeyPair; 81 | /** 82 | * This class provides key pair functionality for Ed25519 curve: 83 | * generating key pairs, encoding key pairs, signing and verifying. 84 | */ 85 | class KeyPairEd25519 extends KeyPair { 86 | /** 87 | * Construct an instance of key pair given a secret key. 88 | * It's generally assumed that these are encoded in base58. 89 | * @param {string} secretKey 90 | */ 91 | constructor(secretKey) { 92 | super(); 93 | const keyPair = tweetnacl_1.default.sign.keyPair.fromSecretKey(serialize_1.base_decode(secretKey)); 94 | this.publicKey = new PublicKey({ keyType: KeyType.ED25519, data: keyPair.publicKey }); 95 | this.secretKey = secretKey; 96 | } 97 | /** 98 | * Generate a new random keypair. 99 | * @example 100 | * const keyRandom = KeyPair.fromRandom(); 101 | * keyRandom.publicKey 102 | * // returns [PUBLIC_KEY] 103 | * 104 | * keyRandom.secretKey 105 | * // returns [SECRET_KEY] 106 | */ 107 | static fromRandom() { 108 | const newKeyPair = tweetnacl_1.default.sign.keyPair(); 109 | return new KeyPairEd25519(serialize_1.base_encode(newKeyPair.secretKey)); 110 | } 111 | sign(message) { 112 | const signature = tweetnacl_1.default.sign.detached(message, serialize_1.base_decode(this.secretKey)); 113 | return { signature, publicKey: this.publicKey }; 114 | } 115 | verify(message, signature) { 116 | return tweetnacl_1.default.sign.detached.verify(message, signature, this.publicKey.data); 117 | } 118 | toString() { 119 | return `ed25519:${this.secretKey}`; 120 | } 121 | getPublicKey() { 122 | return this.publicKey; 123 | } 124 | } 125 | exports.KeyPairEd25519 = KeyPairEd25519; 126 | -------------------------------------------------------------------------------- /borsh-ts/test/serialize.test.js: -------------------------------------------------------------------------------- 1 | const borsh = require('../../lib/index'); 2 | const BN = require('bn.js'); 3 | 4 | class Assignable { 5 | constructor(properties) { 6 | Object.keys(properties).map((key) => { 7 | this[key] = properties[key]; 8 | }); 9 | } 10 | } 11 | 12 | class Test extends Assignable { } 13 | 14 | class Serializable { 15 | constructor(data) { 16 | this.data = data; 17 | } 18 | 19 | static borshDeserialize(reader) { 20 | return new Serializable(reader.readU8()); 21 | } 22 | 23 | borshSerialize(writer) { 24 | writer.writeU8(this.data); 25 | } 26 | } 27 | 28 | test('serialize object', async () => { 29 | const value = new Test({ x: 255, y: 20, z: '123', q: [1, 2, 3] }); 30 | const schema = new Map([[Test, { kind: 'struct', fields: [['x', 'u8'], ['y', 'u64'], ['z', 'string'], ['q', [3]]] }]]); 31 | const buf = borsh.serialize(schema, value); 32 | const newValue = borsh.deserialize(schema, Test, buf); 33 | expect(newValue.x).toEqual(255); 34 | expect(newValue.y.toString()).toEqual('20'); 35 | expect(newValue.z).toEqual('123'); 36 | expect(newValue.q).toEqual(new Uint8Array([1, 2, 3])); 37 | }); 38 | 39 | test('serialize optional field', async () => { 40 | const schema = new Map([[Test, { kind: 'struct', fields: [['x', { kind: 'option', type: 'string' }]]}]]); 41 | 42 | let buf = borsh.serialize(schema, new Test({ x: '123', })); 43 | let newValue = borsh.deserialize(schema, Test, buf); 44 | expect(newValue.x).toEqual('123'); 45 | 46 | buf = borsh.serialize(schema, new Test({ })); 47 | newValue = borsh.deserialize(schema, Test, buf); 48 | expect(newValue.x).toEqual(undefined); 49 | }); 50 | 51 | test('serialize max uint', async () => { 52 | const u64MaxHex = 'ffffffffffffffff'; 53 | const value = new Test({ 54 | x: 255, 55 | y: 65535, 56 | z: 4294967295, 57 | q: new BN(u64MaxHex, 16), 58 | r: new BN(u64MaxHex.repeat(2), 16), 59 | s: new BN(u64MaxHex.repeat(4), 16), 60 | t: new BN(u64MaxHex.repeat(8), 16) 61 | }); 62 | const schema = new Map([[Test, { 63 | kind: 'struct', 64 | fields: [ 65 | ['x', 'u8'], 66 | ['y', 'u16'], 67 | ['z', 'u32'], 68 | ['q', 'u64'], 69 | ['r', 'u128'], 70 | ['s', 'u256'], 71 | ['t', 'u512'] 72 | ] 73 | }]]); 74 | const buf = borsh.serialize(schema, value); 75 | const newValue = borsh.deserialize(schema, Test, buf); 76 | expect(newValue.x).toEqual(255); 77 | expect(newValue.y).toEqual(65535); 78 | expect(newValue.z).toEqual(4294967295); 79 | expect(newValue.q.toString()).toEqual('18446744073709551615'); 80 | expect(newValue.r.toString()).toEqual('340282366920938463463374607431768211455'); 81 | expect(newValue.s.toString()).toEqual('115792089237316195423570985008687907853269984665640564039457584007913129639935'); 82 | expect(newValue.t.toString()).toEqual('13407807929942597099574024998205846127479365820592393377723561443721764030073546976801874298166903427690031858186486050853753882811946569946433649006084095'); 83 | }); 84 | 85 | test('serialize/deserialize with class methods', () => { 86 | const item = new Serializable(10); 87 | 88 | const buf = borsh.serialize(null, item); 89 | const newValue = borsh.deserialize(null, Serializable, buf); 90 | 91 | expect(newValue).toEqual(item); 92 | }); 93 | 94 | test('serialize/deserialize fixed array', () => { 95 | const value = new Test({ 96 | a: ['hello', 'world'] 97 | }); 98 | const schema = new Map([[Test, { 99 | kind: 'struct', 100 | fields: [ 101 | ['a', ['string', 2]] 102 | ] 103 | }]]); 104 | 105 | const buf = borsh.serialize(schema, value); 106 | const deserializedValue = borsh.deserialize(schema, Test, buf); 107 | 108 | expect(buf).toEqual(Buffer.from([5, 0, 0, 0, 104, 101, 108, 108, 111, 5, 0, 0, 0, 119, 111, 114, 108, 100])); 109 | expect(deserializedValue.a).toEqual(['hello', 'world']); 110 | }); 111 | 112 | test('errors serializing fixed array of wrong size', () => { 113 | const value = new Test({ 114 | a: ['hello', 'world', 'you'] 115 | }); 116 | const schema = new Map([[Test, { 117 | kind: 'struct', 118 | fields: [ 119 | ['a', ['string', 2]] 120 | ] 121 | }]]); 122 | 123 | expect(() => borsh.serialize(schema, value)).toThrow('Expecting byte array of length 2, but got 3 bytes'); 124 | }); 125 | 126 | test('errors serializing fixed array of wrong type', () => { 127 | const value = new Test({ 128 | a: [244, 34] 129 | }); 130 | const schema = new Map([[Test, { 131 | kind: 'struct', 132 | fields: [ 133 | ['a', ['string', 2]] 134 | ] 135 | }]]); 136 | 137 | expect(() => borsh.serialize(schema, value)).toThrow('The first argument must be of type string'); 138 | }); 139 | 140 | test('baseEncode string test', async () => { 141 | const encodedValue = borsh.baseEncode('244ZQ9cgj3CQ6bWBdytfrJMuMQ1jdXLFGnr4HhvtCTnM'); 142 | const expectedValue = 'HKk9gqNj4xb4rLdJuzT5zzJbLa4vHBdYCxQT9H99csQh6nz3Hfpqn4jtWA92'; 143 | expect(encodedValue).toEqual(expectedValue); 144 | }); 145 | 146 | test('baseEncode array test', async () => { 147 | expect(borsh.baseEncode([1, 2, 3, 4, 5])).toEqual('7bWpTW'); 148 | }); 149 | 150 | test('baseDecode test', async () => { 151 | const value = 'HKk9gqNj4xb4rLdJu'; 152 | const expectedDecodedArray = [3, 96, 254, 84, 10, 240, 93, 199, 52, 244, 164, 240, 6]; 153 | const expectedBuffer = Buffer.from(expectedDecodedArray); 154 | expect(borsh.baseDecode(value)).toEqual(expectedBuffer); 155 | }); 156 | 157 | test('base encode and decode test', async () => { 158 | const value = '244ZQ9cgj3CQ6bWBdytfrJMuMQ1jdXLFGnr4HhvtCTnM'; 159 | expect(borsh.baseEncode(borsh.baseDecode(value))).toEqual(value); 160 | }); 161 | 162 | test('serialize with custom writer/reader', async () => { 163 | class ExtendedWriter extends borsh.BinaryWriter { 164 | writeDate(value) { 165 | this.writeU64(value.getTime()); 166 | } 167 | } 168 | 169 | class ExtendedReader extends borsh.BinaryReader { 170 | readDate() { 171 | const value = this.readU64(); 172 | return new Date(value.toNumber()); 173 | } 174 | } 175 | 176 | const time = 'Aug 12, 2021 12:00:00 UTC+00:00'; 177 | const value = new Test({ x: new Date(time) }); 178 | const schema = new Map([[Test, {kind: 'struct', fields: [['x', 'date']]}]]); 179 | 180 | const buf = borsh.serialize(schema, value, ExtendedWriter); 181 | const newValue = borsh.deserialize(schema, Test, buf, ExtendedReader); 182 | expect(newValue.x).toEqual(new Date(time)); 183 | }); 184 | 185 | test('serialize map', async () => { 186 | let map = new Map(); 187 | for (let i = 0; i < 10; i++) { 188 | map.set(new BN(i * 10), "some string " + i.toString()); 189 | } 190 | const value = new Test({ x: map }); 191 | const schema = new Map([[ Test, { 192 | kind: 'struct', 193 | fields: [ 194 | ['x', { kind: 'map', key: 'u64', value: 'string' }], 195 | ], 196 | }]]); 197 | 198 | const buf = borsh.serialize(schema, value); 199 | const deserialized = borsh.deserialize(schema, Test, buf); 200 | expect(deserialized.x.size).toEqual(10); 201 | deserialized.x.forEach((value, key) => { 202 | expect(value).toEqual("some string " + (key.toNumber() / 10).toString()); 203 | }); 204 | }); 205 | -------------------------------------------------------------------------------- /borsh-ts/test/fuzz/transaction-example/transaction.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __importDefault = (this && this.__importDefault) || function (mod) { 3 | return (mod && mod.__esModule) ? mod : { "default": mod }; 4 | }; 5 | Object.defineProperty(exports, "__esModule", { value: true }); 6 | const js_sha256_1 = __importDefault(require("js-sha256")); 7 | const enums_1 = require("./enums"); 8 | const serialize_1 = require("./serialize"); 9 | const key_pair_1 = require("./key_pair"); 10 | class FunctionCallPermission extends enums_1.Assignable { 11 | } 12 | exports.FunctionCallPermission = FunctionCallPermission; 13 | class FullAccessPermission extends enums_1.Assignable { 14 | } 15 | exports.FullAccessPermission = FullAccessPermission; 16 | class AccessKeyPermission extends enums_1.Enum { 17 | } 18 | exports.AccessKeyPermission = AccessKeyPermission; 19 | class AccessKey extends enums_1.Assignable { 20 | } 21 | exports.AccessKey = AccessKey; 22 | function fullAccessKey() { 23 | return new AccessKey({ nonce: 0, permission: new AccessKeyPermission({ fullAccess: new FullAccessPermission({}) }) }); 24 | } 25 | exports.fullAccessKey = fullAccessKey; 26 | function functionCallAccessKey(receiverId, methodNames, allowance) { 27 | return new AccessKey({ nonce: 0, permission: new AccessKeyPermission({ functionCall: new FunctionCallPermission({ receiverId, allowance, methodNames }) }) }); 28 | } 29 | exports.functionCallAccessKey = functionCallAccessKey; 30 | class IAction extends enums_1.Assignable { 31 | } 32 | exports.IAction = IAction; 33 | class CreateAccount extends IAction { 34 | } 35 | exports.CreateAccount = CreateAccount; 36 | class DeployContract extends IAction { 37 | } 38 | exports.DeployContract = DeployContract; 39 | class FunctionCall extends IAction { 40 | } 41 | exports.FunctionCall = FunctionCall; 42 | class Transfer extends IAction { 43 | } 44 | exports.Transfer = Transfer; 45 | class Stake extends IAction { 46 | } 47 | exports.Stake = Stake; 48 | class AddKey extends IAction { 49 | } 50 | exports.AddKey = AddKey; 51 | class DeleteKey extends IAction { 52 | } 53 | exports.DeleteKey = DeleteKey; 54 | class DeleteAccount extends IAction { 55 | } 56 | exports.DeleteAccount = DeleteAccount; 57 | function createAccount() { 58 | return new Action({ createAccount: new CreateAccount({}) }); 59 | } 60 | exports.createAccount = createAccount; 61 | function deployContract(code) { 62 | return new Action({ deployContract: new DeployContract({ code }) }); 63 | } 64 | exports.deployContract = deployContract; 65 | /** 66 | * Constructs {@link Action} instance representing contract method call. 67 | * 68 | * @param methodName the name of the method to call 69 | * @param args arguments to pass to method. Can be either plain JS object which gets serialized as JSON automatically 70 | * or `Uint8Array` instance which represents bytes passed as is. 71 | * @param gas max amount of gas that method call can use 72 | * @param deposit amount of NEAR (in yoctoNEAR) to send together with the call 73 | */ 74 | function functionCall(methodName, args, gas, deposit) { 75 | const anyArgs = args; 76 | const isUint8Array = anyArgs.byteLength !== undefined && anyArgs.byteLength === anyArgs.length; 77 | const serializedArgs = isUint8Array ? args : Buffer.from(JSON.stringify(args)); 78 | return new Action({ functionCall: new FunctionCall({ methodName, args: serializedArgs, gas, deposit }) }); 79 | } 80 | exports.functionCall = functionCall; 81 | function transfer(deposit) { 82 | return new Action({ transfer: new Transfer({ deposit }) }); 83 | } 84 | exports.transfer = transfer; 85 | function stake(stake, publicKey) { 86 | return new Action({ stake: new Stake({ stake, publicKey }) }); 87 | } 88 | exports.stake = stake; 89 | function addKey(publicKey, accessKey) { 90 | return new Action({ addKey: new AddKey({ publicKey, accessKey }) }); 91 | } 92 | exports.addKey = addKey; 93 | function deleteKey(publicKey) { 94 | return new Action({ deleteKey: new DeleteKey({ publicKey }) }); 95 | } 96 | exports.deleteKey = deleteKey; 97 | function deleteAccount(beneficiaryId) { 98 | return new Action({ deleteAccount: new DeleteAccount({ beneficiaryId }) }); 99 | } 100 | exports.deleteAccount = deleteAccount; 101 | class Signature extends enums_1.Assignable { 102 | } 103 | exports.Signature = Signature; 104 | class Transaction extends enums_1.Assignable { 105 | encode() { 106 | return serialize_1.serialize(exports.SCHEMA, this); 107 | } 108 | static decode(bytes) { 109 | return serialize_1.deserialize(exports.SCHEMA, Transaction, bytes); 110 | } 111 | } 112 | exports.Transaction = Transaction; 113 | class SignedTransaction extends enums_1.Assignable { 114 | encode() { 115 | return serialize_1.serialize(exports.SCHEMA, this); 116 | } 117 | static decode(bytes) { 118 | return serialize_1.deserialize(exports.SCHEMA, SignedTransaction, bytes); 119 | } 120 | } 121 | exports.SignedTransaction = SignedTransaction; 122 | /** 123 | * Contains a list of the valid transaction Actions available with this API 124 | */ 125 | class Action extends enums_1.Enum { 126 | } 127 | exports.Action = Action; 128 | exports.SCHEMA = new Map([ 129 | [Signature, { kind: 'struct', fields: [ 130 | ['keyType', 'u8'], 131 | ['data', [64]] 132 | ] }], 133 | [SignedTransaction, { kind: 'struct', fields: [ 134 | ['transaction', Transaction], 135 | ['signature', Signature] 136 | ] }], 137 | [Transaction, { kind: 'struct', fields: [ 138 | ['signerId', 'string'], 139 | ['publicKey', key_pair_1.PublicKey], 140 | ['nonce', 'u64'], 141 | ['receiverId', 'string'], 142 | ['blockHash', [32]], 143 | ['actions', [Action]] 144 | ] }], 145 | [key_pair_1.PublicKey, { kind: 'struct', fields: [ 146 | ['keyType', 'u8'], 147 | ['data', [32]] 148 | ] }], 149 | [AccessKey, { kind: 'struct', fields: [ 150 | ['nonce', 'u64'], 151 | ['permission', AccessKeyPermission], 152 | ] }], 153 | [AccessKeyPermission, { kind: 'enum', field: 'enum', values: [ 154 | ['functionCall', FunctionCallPermission], 155 | ['fullAccess', FullAccessPermission], 156 | ] }], 157 | [FunctionCallPermission, { kind: 'struct', fields: [ 158 | ['allowance', { kind: 'option', type: 'u128' }], 159 | ['receiverId', 'string'], 160 | ['methodNames', ['string']], 161 | ] }], 162 | [FullAccessPermission, { kind: 'struct', fields: [] }], 163 | [Action, { kind: 'enum', field: 'enum', values: [ 164 | ['createAccount', CreateAccount], 165 | ['deployContract', DeployContract], 166 | ['functionCall', FunctionCall], 167 | ['transfer', Transfer], 168 | ['stake', Stake], 169 | ['addKey', AddKey], 170 | ['deleteKey', DeleteKey], 171 | ['deleteAccount', DeleteAccount], 172 | ] }], 173 | [CreateAccount, { kind: 'struct', fields: [] }], 174 | [DeployContract, { kind: 'struct', fields: [ 175 | ['code', ['u8']] 176 | ] }], 177 | [FunctionCall, { kind: 'struct', fields: [ 178 | ['methodName', 'string'], 179 | ['args', ['u8']], 180 | ['gas', 'u64'], 181 | ['deposit', 'u128'] 182 | ] }], 183 | [Transfer, { kind: 'struct', fields: [ 184 | ['deposit', 'u128'] 185 | ] }], 186 | [Stake, { kind: 'struct', fields: [ 187 | ['stake', 'u128'], 188 | ['publicKey', key_pair_1.PublicKey] 189 | ] }], 190 | [AddKey, { kind: 'struct', fields: [ 191 | ['publicKey', key_pair_1.PublicKey], 192 | ['accessKey', AccessKey] 193 | ] }], 194 | [DeleteKey, { kind: 'struct', fields: [ 195 | ['publicKey', key_pair_1.PublicKey] 196 | ] }], 197 | [DeleteAccount, { kind: 'struct', fields: [ 198 | ['beneficiaryId', 'string'] 199 | ] }], 200 | ]); 201 | function createTransaction(signerId, publicKey, receiverId, nonce, actions, blockHash) { 202 | return new Transaction({ signerId, publicKey, nonce, receiverId, actions, blockHash }); 203 | } 204 | exports.createTransaction = createTransaction; 205 | /** 206 | * Signs a given transaction from an account with given keys, applied to the given network 207 | * @param transaction The Transaction object to sign 208 | * @param signer The {Signer} object that assists with signing keys 209 | * @param accountId The human-readable NEAR account name 210 | * @param networkId The targeted network. (ex. default, betanet, etc…) 211 | */ 212 | async function signTransactionObject(transaction, signer, accountId, networkId) { 213 | const message = serialize_1.serialize(exports.SCHEMA, transaction); 214 | const hash = new Uint8Array(js_sha256_1.default.sha256.array(message)); 215 | const signature = await signer.signMessage(message, accountId, networkId); 216 | const signedTx = new SignedTransaction({ 217 | transaction, 218 | signature: new Signature({ keyType: transaction.publicKey.keyType, data: signature.signature }) 219 | }); 220 | return [hash, signedTx]; 221 | } 222 | async function signTransaction(...args) { 223 | if (args[0].constructor === Transaction) { 224 | const [transaction, signer, accountId, networkId] = args; 225 | return signTransactionObject(transaction, signer, accountId, networkId); 226 | } 227 | else { 228 | const [receiverId, nonce, actions, blockHash, signer, accountId, networkId] = args; 229 | const publicKey = await signer.getPublicKey(accountId, networkId); 230 | const transaction = createTransaction(accountId, publicKey, receiverId, nonce, actions, blockHash); 231 | return signTransactionObject(transaction, signer, accountId, networkId); 232 | } 233 | } 234 | exports.signTransaction = signTransaction; 235 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2019 Near 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /borsh-ts/index.ts: -------------------------------------------------------------------------------- 1 | import BN from "bn.js"; 2 | import bs58 from "bs58"; 3 | 4 | // TODO: Make sure this polyfill not included when not required 5 | import * as encoding from "text-encoding-utf-8"; 6 | const ResolvedTextDecoder = 7 | typeof TextDecoder !== "function" ? encoding.TextDecoder : TextDecoder; 8 | const textDecoder = new ResolvedTextDecoder("utf-8", { fatal: true }); 9 | 10 | export function baseEncode(value: Uint8Array | string): string { 11 | if (typeof value === "string") { 12 | value = Buffer.from(value, "utf8"); 13 | } 14 | return bs58.encode(Buffer.from(value)); 15 | } 16 | 17 | export function baseDecode(value: string): Buffer { 18 | return Buffer.from(bs58.decode(value)); 19 | } 20 | 21 | const INITIAL_LENGTH = 1024; 22 | 23 | export type Schema = Map; 24 | 25 | export class BorshError extends Error { 26 | originalMessage: string; 27 | fieldPath: string[] = []; 28 | 29 | constructor(message: string) { 30 | super(message); 31 | this.originalMessage = message; 32 | } 33 | 34 | addToFieldPath(fieldName: string) { 35 | this.fieldPath.splice(0, 0, fieldName); 36 | // NOTE: Modifying message directly as jest doesn't use .toString() 37 | this.message = this.originalMessage + ": " + this.fieldPath.join("."); 38 | } 39 | } 40 | 41 | /// Binary encoder. 42 | export class BinaryWriter { 43 | buf: Buffer; 44 | length: number; 45 | 46 | public constructor() { 47 | this.buf = Buffer.alloc(INITIAL_LENGTH); 48 | this.length = 0; 49 | } 50 | 51 | maybeResize() { 52 | if (this.buf.length < 16 + this.length) { 53 | this.buf = Buffer.concat([this.buf, Buffer.alloc(INITIAL_LENGTH)]); 54 | } 55 | } 56 | 57 | public writeU8(value: number) { 58 | this.maybeResize(); 59 | this.buf.writeUInt8(value, this.length); 60 | this.length += 1; 61 | } 62 | 63 | public writeU16(value: number) { 64 | this.maybeResize(); 65 | this.buf.writeUInt16LE(value, this.length); 66 | this.length += 2; 67 | } 68 | 69 | public writeU32(value: number) { 70 | this.maybeResize(); 71 | this.buf.writeUInt32LE(value, this.length); 72 | this.length += 4; 73 | } 74 | 75 | public writeU64(value: number | BN) { 76 | this.maybeResize(); 77 | this.writeBuffer(Buffer.from(new BN(value).toArray("le", 8))); 78 | } 79 | 80 | public writeU128(value: number | BN) { 81 | this.maybeResize(); 82 | this.writeBuffer(Buffer.from(new BN(value).toArray("le", 16))); 83 | } 84 | 85 | public writeU256(value: number | BN) { 86 | this.maybeResize(); 87 | this.writeBuffer(Buffer.from(new BN(value).toArray("le", 32))); 88 | } 89 | 90 | public writeU512(value: number | BN) { 91 | this.maybeResize(); 92 | this.writeBuffer(Buffer.from(new BN(value).toArray("le", 64))); 93 | } 94 | 95 | private writeBuffer(buffer: Buffer) { 96 | // Buffer.from is needed as this.buf.subarray can return plain Uint8Array in browser 97 | this.buf = Buffer.concat([ 98 | Buffer.from(this.buf.subarray(0, this.length)), 99 | buffer, 100 | Buffer.alloc(INITIAL_LENGTH), 101 | ]); 102 | this.length += buffer.length; 103 | } 104 | 105 | public writeString(str: string) { 106 | this.maybeResize(); 107 | const b = Buffer.from(str, "utf8"); 108 | this.writeU32(b.length); 109 | this.writeBuffer(b); 110 | } 111 | 112 | public writeFixedArray(array: Uint8Array) { 113 | this.writeBuffer(Buffer.from(array)); 114 | } 115 | 116 | public writeArray(array: any[], fn: any) { 117 | this.maybeResize(); 118 | this.writeU32(array.length); 119 | for (const elem of array) { 120 | this.maybeResize(); 121 | fn(elem); 122 | } 123 | } 124 | 125 | public toArray(): Uint8Array { 126 | return this.buf.subarray(0, this.length); 127 | } 128 | } 129 | 130 | function handlingRangeError( 131 | target: any, 132 | propertyKey: string, 133 | propertyDescriptor: PropertyDescriptor 134 | ) { 135 | const originalMethod = propertyDescriptor.value; 136 | propertyDescriptor.value = function (...args: any[]) { 137 | try { 138 | return originalMethod.apply(this, args); 139 | } catch (e) { 140 | if (e instanceof RangeError) { 141 | const code = (e as any).code; 142 | if ( 143 | ["ERR_BUFFER_OUT_OF_BOUNDS", "ERR_OUT_OF_RANGE"].indexOf(code) >= 0 144 | ) { 145 | throw new BorshError("Reached the end of buffer when deserializing"); 146 | } 147 | } 148 | throw e; 149 | } 150 | }; 151 | } 152 | 153 | export class BinaryReader { 154 | buf: Buffer; 155 | offset: number; 156 | 157 | public constructor(buf: Buffer) { 158 | this.buf = buf; 159 | this.offset = 0; 160 | } 161 | 162 | @handlingRangeError 163 | readU8(): number { 164 | const value = this.buf.readUInt8(this.offset); 165 | this.offset += 1; 166 | return value; 167 | } 168 | 169 | @handlingRangeError 170 | readU16(): number { 171 | const value = this.buf.readUInt16LE(this.offset); 172 | this.offset += 2; 173 | return value; 174 | } 175 | 176 | @handlingRangeError 177 | readU32(): number { 178 | const value = this.buf.readUInt32LE(this.offset); 179 | this.offset += 4; 180 | return value; 181 | } 182 | 183 | @handlingRangeError 184 | readU64(): BN { 185 | const buf = this.readBuffer(8); 186 | return new BN(buf, "le"); 187 | } 188 | 189 | @handlingRangeError 190 | readU128(): BN { 191 | const buf = this.readBuffer(16); 192 | return new BN(buf, "le"); 193 | } 194 | 195 | @handlingRangeError 196 | readU256(): BN { 197 | const buf = this.readBuffer(32); 198 | return new BN(buf, "le"); 199 | } 200 | 201 | @handlingRangeError 202 | readU512(): BN { 203 | const buf = this.readBuffer(64); 204 | return new BN(buf, "le"); 205 | } 206 | 207 | private readBuffer(len: number): Buffer { 208 | if (this.offset + len > this.buf.length) { 209 | throw new BorshError(`Expected buffer length ${len} isn't within bounds`); 210 | } 211 | const result = this.buf.slice(this.offset, this.offset + len); 212 | this.offset += len; 213 | return result; 214 | } 215 | 216 | @handlingRangeError 217 | readString(): string { 218 | const len = this.readU32(); 219 | const buf = this.readBuffer(len); 220 | try { 221 | // NOTE: Using TextDecoder to fail on invalid UTF-8 222 | return textDecoder.decode(buf); 223 | } catch (e) { 224 | throw new BorshError(`Error decoding UTF-8 string: ${e}`); 225 | } 226 | } 227 | 228 | @handlingRangeError 229 | readFixedArray(len: number): Uint8Array { 230 | return new Uint8Array(this.readBuffer(len)); 231 | } 232 | 233 | @handlingRangeError 234 | readArray(fn: any): any[] { 235 | const len = this.readU32(); 236 | const result = Array(); 237 | for (let i = 0; i < len; ++i) { 238 | result.push(fn()); 239 | } 240 | return result; 241 | } 242 | } 243 | 244 | function capitalizeFirstLetter(string) { 245 | return string.charAt(0).toUpperCase() + string.slice(1); 246 | } 247 | 248 | function serializeField( 249 | schema: Schema, 250 | fieldName: string, 251 | value: any, 252 | fieldType: any, 253 | writer: any 254 | ) { 255 | try { 256 | // TODO: Handle missing values properly (make sure they never result in just skipped write) 257 | if (typeof fieldType === "string") { 258 | writer[`write${capitalizeFirstLetter(fieldType)}`](value); 259 | } else if (fieldType instanceof Array) { 260 | if (typeof fieldType[0] === "number") { 261 | if (value.length !== fieldType[0]) { 262 | throw new BorshError( 263 | `Expecting byte array of length ${fieldType[0]}, but got ${value.length} bytes` 264 | ); 265 | } 266 | writer.writeFixedArray(value); 267 | } else if (fieldType.length === 2 && typeof fieldType[1] === "number") { 268 | if (value.length !== fieldType[1]) { 269 | throw new BorshError( 270 | `Expecting byte array of length ${fieldType[1]}, but got ${value.length} bytes` 271 | ); 272 | } 273 | for (let i = 0; i < fieldType[1]; i++) { 274 | serializeField(schema, null, value[i], fieldType[0], writer); 275 | } 276 | } else { 277 | writer.writeArray(value, (item: any) => { 278 | serializeField(schema, fieldName, item, fieldType[0], writer); 279 | }); 280 | } 281 | } else if (fieldType.kind !== undefined) { 282 | switch (fieldType.kind) { 283 | case "option": { 284 | if (value === null || value === undefined) { 285 | writer.writeU8(0); 286 | } else { 287 | writer.writeU8(1); 288 | serializeField(schema, fieldName, value, fieldType.type, writer); 289 | } 290 | break; 291 | } 292 | case "map": { 293 | writer.writeU32(value.size); 294 | value.forEach((val, key) => { 295 | serializeField(schema, fieldName, key, fieldType.key, writer); 296 | serializeField(schema, fieldName, val, fieldType.value, writer); 297 | }); 298 | break; 299 | } 300 | default: 301 | throw new BorshError(`FieldType ${fieldType} unrecognized`); 302 | } 303 | } else { 304 | serializeStruct(schema, value, writer); 305 | } 306 | } catch (error) { 307 | if (error instanceof BorshError) { 308 | error.addToFieldPath(fieldName); 309 | } 310 | throw error; 311 | } 312 | } 313 | 314 | function serializeStruct(schema: Schema, obj: any, writer: BinaryWriter) { 315 | if (typeof obj.borshSerialize === "function") { 316 | obj.borshSerialize(writer); 317 | return; 318 | } 319 | 320 | const structSchema = schema.get(obj.constructor); 321 | if (!structSchema) { 322 | throw new BorshError(`Class ${obj.constructor.name} is missing in schema`); 323 | } 324 | 325 | if (structSchema.kind === "struct") { 326 | structSchema.fields.map(([fieldName, fieldType]: [any, any]) => { 327 | serializeField(schema, fieldName, obj[fieldName], fieldType, writer); 328 | }); 329 | } else if (structSchema.kind === "enum") { 330 | const name = obj[structSchema.field]; 331 | for (let idx = 0; idx < structSchema.values.length; ++idx) { 332 | const [fieldName, fieldType]: [any, any] = structSchema.values[idx]; 333 | if (fieldName === name) { 334 | writer.writeU8(idx); 335 | serializeField(schema, fieldName, obj[fieldName], fieldType, writer); 336 | break; 337 | } 338 | } 339 | } else { 340 | throw new BorshError( 341 | `Unexpected schema kind: ${structSchema.kind} for ${obj.constructor.name}` 342 | ); 343 | } 344 | } 345 | 346 | /// Serialize given object using schema of the form: 347 | /// { class_name -> [ [field_name, field_type], .. ], .. } 348 | export function serialize( 349 | schema: Schema, 350 | obj: any, 351 | Writer = BinaryWriter 352 | ): Uint8Array { 353 | const writer = new Writer(); 354 | serializeStruct(schema, obj, writer); 355 | return writer.toArray(); 356 | } 357 | 358 | function deserializeField( 359 | schema: Schema, 360 | fieldName: string, 361 | fieldType: any, 362 | reader: BinaryReader 363 | ): any { 364 | try { 365 | if (typeof fieldType === "string") { 366 | return reader[`read${capitalizeFirstLetter(fieldType)}`](); 367 | } 368 | 369 | if (fieldType instanceof Array) { 370 | if (typeof fieldType[0] === "number") { 371 | return reader.readFixedArray(fieldType[0]); 372 | } else if (typeof fieldType[1] === "number") { 373 | const arr = []; 374 | for (let i = 0; i < fieldType[1]; i++) { 375 | arr.push(deserializeField(schema, null, fieldType[0], reader)); 376 | } 377 | return arr; 378 | } else { 379 | return reader.readArray(() => 380 | deserializeField(schema, fieldName, fieldType[0], reader) 381 | ); 382 | } 383 | } 384 | 385 | if (fieldType.kind === "option") { 386 | const option = reader.readU8(); 387 | if (option) { 388 | return deserializeField(schema, fieldName, fieldType.type, reader); 389 | } 390 | 391 | return undefined; 392 | } 393 | if (fieldType.kind === "map") { 394 | let map = new Map(); 395 | const length = reader.readU32(); 396 | for (let i = 0; i < length; i++) { 397 | const key = deserializeField(schema, fieldName, fieldType.key, reader); 398 | const val = deserializeField( 399 | schema, 400 | fieldName, 401 | fieldType.value, 402 | reader 403 | ); 404 | map.set(key, val); 405 | } 406 | return map; 407 | } 408 | 409 | return deserializeStruct(schema, fieldType, reader); 410 | } catch (error) { 411 | if (error instanceof BorshError) { 412 | error.addToFieldPath(fieldName); 413 | } 414 | throw error; 415 | } 416 | } 417 | 418 | function deserializeStruct( 419 | schema: Schema, 420 | classType: any, 421 | reader: BinaryReader 422 | ) { 423 | if (typeof classType.borshDeserialize === "function") { 424 | return classType.borshDeserialize(reader); 425 | } 426 | 427 | const structSchema = schema.get(classType); 428 | if (!structSchema) { 429 | throw new BorshError(`Class ${classType.name} is missing in schema`); 430 | } 431 | 432 | if (structSchema.kind === "struct") { 433 | const result = {}; 434 | for (const [fieldName, fieldType] of schema.get(classType).fields) { 435 | result[fieldName] = deserializeField( 436 | schema, 437 | fieldName, 438 | fieldType, 439 | reader 440 | ); 441 | } 442 | return new classType(result); 443 | } 444 | 445 | if (structSchema.kind === "enum") { 446 | const idx = reader.readU8(); 447 | if (idx >= structSchema.values.length) { 448 | throw new BorshError(`Enum index: ${idx} is out of range`); 449 | } 450 | const [fieldName, fieldType] = structSchema.values[idx]; 451 | const fieldValue = deserializeField(schema, fieldName, fieldType, reader); 452 | return new classType({ [fieldName]: fieldValue }); 453 | } 454 | 455 | throw new BorshError( 456 | `Unexpected schema kind: ${structSchema.kind} for ${classType.constructor.name}` 457 | ); 458 | } 459 | 460 | /// Deserializes object from bytes using schema. 461 | export function deserialize( 462 | schema: Schema, 463 | classType: { new (args: any): T }, 464 | buffer: Buffer, 465 | Reader = BinaryReader 466 | ): T { 467 | const reader = new Reader(buffer); 468 | const result = deserializeStruct(schema, classType, reader); 469 | if (reader.offset < buffer.length) { 470 | throw new BorshError( 471 | `Unexpected ${ 472 | buffer.length - reader.offset 473 | } bytes after deserialized data` 474 | ); 475 | } 476 | return result; 477 | } 478 | 479 | /// Deserializes object from bytes using schema, without checking the length read 480 | export function deserializeUnchecked( 481 | schema: Schema, 482 | classType: { new (args: any): T }, 483 | buffer: Buffer, 484 | Reader = BinaryReader 485 | ): T { 486 | const reader = new Reader(buffer); 487 | return deserializeStruct(schema, classType, reader); 488 | } 489 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { 3 | if (k2 === undefined) k2 = k; 4 | Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); 5 | }) : (function(o, m, k, k2) { 6 | if (k2 === undefined) k2 = k; 7 | o[k2] = m[k]; 8 | })); 9 | var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { 10 | Object.defineProperty(o, "default", { enumerable: true, value: v }); 11 | }) : function(o, v) { 12 | o["default"] = v; 13 | }); 14 | var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { 15 | var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; 16 | if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); 17 | else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; 18 | return c > 3 && r && Object.defineProperty(target, key, r), r; 19 | }; 20 | var __importStar = (this && this.__importStar) || function (mod) { 21 | if (mod && mod.__esModule) return mod; 22 | var result = {}; 23 | if (mod != null) for (var k in mod) if (k !== "default" && Object.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); 24 | __setModuleDefault(result, mod); 25 | return result; 26 | }; 27 | var __importDefault = (this && this.__importDefault) || function (mod) { 28 | return (mod && mod.__esModule) ? mod : { "default": mod }; 29 | }; 30 | Object.defineProperty(exports, "__esModule", { value: true }); 31 | exports.deserializeUnchecked = exports.deserialize = exports.serialize = exports.BinaryReader = exports.BinaryWriter = exports.BorshError = exports.baseDecode = exports.baseEncode = void 0; 32 | const bn_js_1 = __importDefault(require("bn.js")); 33 | const bs58_1 = __importDefault(require("bs58")); 34 | // TODO: Make sure this polyfill not included when not required 35 | const encoding = __importStar(require("text-encoding-utf-8")); 36 | const ResolvedTextDecoder = typeof TextDecoder !== "function" ? encoding.TextDecoder : TextDecoder; 37 | const textDecoder = new ResolvedTextDecoder("utf-8", { fatal: true }); 38 | function baseEncode(value) { 39 | if (typeof value === "string") { 40 | value = Buffer.from(value, "utf8"); 41 | } 42 | return bs58_1.default.encode(Buffer.from(value)); 43 | } 44 | exports.baseEncode = baseEncode; 45 | function baseDecode(value) { 46 | return Buffer.from(bs58_1.default.decode(value)); 47 | } 48 | exports.baseDecode = baseDecode; 49 | const INITIAL_LENGTH = 1024; 50 | class BorshError extends Error { 51 | constructor(message) { 52 | super(message); 53 | this.fieldPath = []; 54 | this.originalMessage = message; 55 | } 56 | addToFieldPath(fieldName) { 57 | this.fieldPath.splice(0, 0, fieldName); 58 | // NOTE: Modifying message directly as jest doesn't use .toString() 59 | this.message = this.originalMessage + ": " + this.fieldPath.join("."); 60 | } 61 | } 62 | exports.BorshError = BorshError; 63 | /// Binary encoder. 64 | class BinaryWriter { 65 | constructor() { 66 | this.buf = Buffer.alloc(INITIAL_LENGTH); 67 | this.length = 0; 68 | } 69 | maybeResize() { 70 | if (this.buf.length < 16 + this.length) { 71 | this.buf = Buffer.concat([this.buf, Buffer.alloc(INITIAL_LENGTH)]); 72 | } 73 | } 74 | writeU8(value) { 75 | this.maybeResize(); 76 | this.buf.writeUInt8(value, this.length); 77 | this.length += 1; 78 | } 79 | writeU16(value) { 80 | this.maybeResize(); 81 | this.buf.writeUInt16LE(value, this.length); 82 | this.length += 2; 83 | } 84 | writeU32(value) { 85 | this.maybeResize(); 86 | this.buf.writeUInt32LE(value, this.length); 87 | this.length += 4; 88 | } 89 | writeU64(value) { 90 | this.maybeResize(); 91 | this.writeBuffer(Buffer.from(new bn_js_1.default(value).toArray("le", 8))); 92 | } 93 | writeU128(value) { 94 | this.maybeResize(); 95 | this.writeBuffer(Buffer.from(new bn_js_1.default(value).toArray("le", 16))); 96 | } 97 | writeU256(value) { 98 | this.maybeResize(); 99 | this.writeBuffer(Buffer.from(new bn_js_1.default(value).toArray("le", 32))); 100 | } 101 | writeU512(value) { 102 | this.maybeResize(); 103 | this.writeBuffer(Buffer.from(new bn_js_1.default(value).toArray("le", 64))); 104 | } 105 | writeBuffer(buffer) { 106 | // Buffer.from is needed as this.buf.subarray can return plain Uint8Array in browser 107 | this.buf = Buffer.concat([ 108 | Buffer.from(this.buf.subarray(0, this.length)), 109 | buffer, 110 | Buffer.alloc(INITIAL_LENGTH), 111 | ]); 112 | this.length += buffer.length; 113 | } 114 | writeString(str) { 115 | this.maybeResize(); 116 | const b = Buffer.from(str, "utf8"); 117 | this.writeU32(b.length); 118 | this.writeBuffer(b); 119 | } 120 | writeFixedArray(array) { 121 | this.writeBuffer(Buffer.from(array)); 122 | } 123 | writeArray(array, fn) { 124 | this.maybeResize(); 125 | this.writeU32(array.length); 126 | for (const elem of array) { 127 | this.maybeResize(); 128 | fn(elem); 129 | } 130 | } 131 | toArray() { 132 | return this.buf.subarray(0, this.length); 133 | } 134 | } 135 | exports.BinaryWriter = BinaryWriter; 136 | function handlingRangeError(target, propertyKey, propertyDescriptor) { 137 | const originalMethod = propertyDescriptor.value; 138 | propertyDescriptor.value = function (...args) { 139 | try { 140 | return originalMethod.apply(this, args); 141 | } 142 | catch (e) { 143 | if (e instanceof RangeError) { 144 | const code = e.code; 145 | if (["ERR_BUFFER_OUT_OF_BOUNDS", "ERR_OUT_OF_RANGE"].indexOf(code) >= 0) { 146 | throw new BorshError("Reached the end of buffer when deserializing"); 147 | } 148 | } 149 | throw e; 150 | } 151 | }; 152 | } 153 | class BinaryReader { 154 | constructor(buf) { 155 | this.buf = buf; 156 | this.offset = 0; 157 | } 158 | readU8() { 159 | const value = this.buf.readUInt8(this.offset); 160 | this.offset += 1; 161 | return value; 162 | } 163 | readU16() { 164 | const value = this.buf.readUInt16LE(this.offset); 165 | this.offset += 2; 166 | return value; 167 | } 168 | readU32() { 169 | const value = this.buf.readUInt32LE(this.offset); 170 | this.offset += 4; 171 | return value; 172 | } 173 | readU64() { 174 | const buf = this.readBuffer(8); 175 | return new bn_js_1.default(buf, "le"); 176 | } 177 | readU128() { 178 | const buf = this.readBuffer(16); 179 | return new bn_js_1.default(buf, "le"); 180 | } 181 | readU256() { 182 | const buf = this.readBuffer(32); 183 | return new bn_js_1.default(buf, "le"); 184 | } 185 | readU512() { 186 | const buf = this.readBuffer(64); 187 | return new bn_js_1.default(buf, "le"); 188 | } 189 | readBuffer(len) { 190 | if (this.offset + len > this.buf.length) { 191 | throw new BorshError(`Expected buffer length ${len} isn't within bounds`); 192 | } 193 | const result = this.buf.slice(this.offset, this.offset + len); 194 | this.offset += len; 195 | return result; 196 | } 197 | readString() { 198 | const len = this.readU32(); 199 | const buf = this.readBuffer(len); 200 | try { 201 | // NOTE: Using TextDecoder to fail on invalid UTF-8 202 | return textDecoder.decode(buf); 203 | } 204 | catch (e) { 205 | throw new BorshError(`Error decoding UTF-8 string: ${e}`); 206 | } 207 | } 208 | readFixedArray(len) { 209 | return new Uint8Array(this.readBuffer(len)); 210 | } 211 | readArray(fn) { 212 | const len = this.readU32(); 213 | const result = Array(); 214 | for (let i = 0; i < len; ++i) { 215 | result.push(fn()); 216 | } 217 | return result; 218 | } 219 | } 220 | __decorate([ 221 | handlingRangeError 222 | ], BinaryReader.prototype, "readU8", null); 223 | __decorate([ 224 | handlingRangeError 225 | ], BinaryReader.prototype, "readU16", null); 226 | __decorate([ 227 | handlingRangeError 228 | ], BinaryReader.prototype, "readU32", null); 229 | __decorate([ 230 | handlingRangeError 231 | ], BinaryReader.prototype, "readU64", null); 232 | __decorate([ 233 | handlingRangeError 234 | ], BinaryReader.prototype, "readU128", null); 235 | __decorate([ 236 | handlingRangeError 237 | ], BinaryReader.prototype, "readU256", null); 238 | __decorate([ 239 | handlingRangeError 240 | ], BinaryReader.prototype, "readU512", null); 241 | __decorate([ 242 | handlingRangeError 243 | ], BinaryReader.prototype, "readString", null); 244 | __decorate([ 245 | handlingRangeError 246 | ], BinaryReader.prototype, "readFixedArray", null); 247 | __decorate([ 248 | handlingRangeError 249 | ], BinaryReader.prototype, "readArray", null); 250 | exports.BinaryReader = BinaryReader; 251 | function capitalizeFirstLetter(string) { 252 | return string.charAt(0).toUpperCase() + string.slice(1); 253 | } 254 | function serializeField(schema, fieldName, value, fieldType, writer) { 255 | try { 256 | // TODO: Handle missing values properly (make sure they never result in just skipped write) 257 | if (typeof fieldType === "string") { 258 | writer[`write${capitalizeFirstLetter(fieldType)}`](value); 259 | } 260 | else if (fieldType instanceof Array) { 261 | if (typeof fieldType[0] === "number") { 262 | if (value.length !== fieldType[0]) { 263 | throw new BorshError(`Expecting byte array of length ${fieldType[0]}, but got ${value.length} bytes`); 264 | } 265 | writer.writeFixedArray(value); 266 | } 267 | else if (fieldType.length === 2 && typeof fieldType[1] === "number") { 268 | if (value.length !== fieldType[1]) { 269 | throw new BorshError(`Expecting byte array of length ${fieldType[1]}, but got ${value.length} bytes`); 270 | } 271 | for (let i = 0; i < fieldType[1]; i++) { 272 | serializeField(schema, null, value[i], fieldType[0], writer); 273 | } 274 | } 275 | else { 276 | writer.writeArray(value, (item) => { 277 | serializeField(schema, fieldName, item, fieldType[0], writer); 278 | }); 279 | } 280 | } 281 | else if (fieldType.kind !== undefined) { 282 | switch (fieldType.kind) { 283 | case "option": { 284 | if (value === null || value === undefined) { 285 | writer.writeU8(0); 286 | } 287 | else { 288 | writer.writeU8(1); 289 | serializeField(schema, fieldName, value, fieldType.type, writer); 290 | } 291 | break; 292 | } 293 | case "map": { 294 | writer.writeU32(value.size); 295 | value.forEach((val, key) => { 296 | serializeField(schema, fieldName, key, fieldType.key, writer); 297 | serializeField(schema, fieldName, val, fieldType.value, writer); 298 | }); 299 | break; 300 | } 301 | default: 302 | throw new BorshError(`FieldType ${fieldType} unrecognized`); 303 | } 304 | } 305 | else { 306 | serializeStruct(schema, value, writer); 307 | } 308 | } 309 | catch (error) { 310 | if (error instanceof BorshError) { 311 | error.addToFieldPath(fieldName); 312 | } 313 | throw error; 314 | } 315 | } 316 | function serializeStruct(schema, obj, writer) { 317 | if (typeof obj.borshSerialize === "function") { 318 | obj.borshSerialize(writer); 319 | return; 320 | } 321 | const structSchema = schema.get(obj.constructor); 322 | if (!structSchema) { 323 | throw new BorshError(`Class ${obj.constructor.name} is missing in schema`); 324 | } 325 | if (structSchema.kind === "struct") { 326 | structSchema.fields.map(([fieldName, fieldType]) => { 327 | serializeField(schema, fieldName, obj[fieldName], fieldType, writer); 328 | }); 329 | } 330 | else if (structSchema.kind === "enum") { 331 | const name = obj[structSchema.field]; 332 | for (let idx = 0; idx < structSchema.values.length; ++idx) { 333 | const [fieldName, fieldType] = structSchema.values[idx]; 334 | if (fieldName === name) { 335 | writer.writeU8(idx); 336 | serializeField(schema, fieldName, obj[fieldName], fieldType, writer); 337 | break; 338 | } 339 | } 340 | } 341 | else { 342 | throw new BorshError(`Unexpected schema kind: ${structSchema.kind} for ${obj.constructor.name}`); 343 | } 344 | } 345 | /// Serialize given object using schema of the form: 346 | /// { class_name -> [ [field_name, field_type], .. ], .. } 347 | function serialize(schema, obj, Writer = BinaryWriter) { 348 | const writer = new Writer(); 349 | serializeStruct(schema, obj, writer); 350 | return writer.toArray(); 351 | } 352 | exports.serialize = serialize; 353 | function deserializeField(schema, fieldName, fieldType, reader) { 354 | try { 355 | if (typeof fieldType === "string") { 356 | return reader[`read${capitalizeFirstLetter(fieldType)}`](); 357 | } 358 | if (fieldType instanceof Array) { 359 | if (typeof fieldType[0] === "number") { 360 | return reader.readFixedArray(fieldType[0]); 361 | } 362 | else if (typeof fieldType[1] === "number") { 363 | const arr = []; 364 | for (let i = 0; i < fieldType[1]; i++) { 365 | arr.push(deserializeField(schema, null, fieldType[0], reader)); 366 | } 367 | return arr; 368 | } 369 | else { 370 | return reader.readArray(() => deserializeField(schema, fieldName, fieldType[0], reader)); 371 | } 372 | } 373 | if (fieldType.kind === "option") { 374 | const option = reader.readU8(); 375 | if (option) { 376 | return deserializeField(schema, fieldName, fieldType.type, reader); 377 | } 378 | return undefined; 379 | } 380 | if (fieldType.kind === "map") { 381 | let map = new Map(); 382 | const length = reader.readU32(); 383 | for (let i = 0; i < length; i++) { 384 | const key = deserializeField(schema, fieldName, fieldType.key, reader); 385 | const val = deserializeField(schema, fieldName, fieldType.value, reader); 386 | map.set(key, val); 387 | } 388 | return map; 389 | } 390 | return deserializeStruct(schema, fieldType, reader); 391 | } 392 | catch (error) { 393 | if (error instanceof BorshError) { 394 | error.addToFieldPath(fieldName); 395 | } 396 | throw error; 397 | } 398 | } 399 | function deserializeStruct(schema, classType, reader) { 400 | if (typeof classType.borshDeserialize === "function") { 401 | return classType.borshDeserialize(reader); 402 | } 403 | const structSchema = schema.get(classType); 404 | if (!structSchema) { 405 | throw new BorshError(`Class ${classType.name} is missing in schema`); 406 | } 407 | if (structSchema.kind === "struct") { 408 | const result = {}; 409 | for (const [fieldName, fieldType] of schema.get(classType).fields) { 410 | result[fieldName] = deserializeField(schema, fieldName, fieldType, reader); 411 | } 412 | return new classType(result); 413 | } 414 | if (structSchema.kind === "enum") { 415 | const idx = reader.readU8(); 416 | if (idx >= structSchema.values.length) { 417 | throw new BorshError(`Enum index: ${idx} is out of range`); 418 | } 419 | const [fieldName, fieldType] = structSchema.values[idx]; 420 | const fieldValue = deserializeField(schema, fieldName, fieldType, reader); 421 | return new classType({ [fieldName]: fieldValue }); 422 | } 423 | throw new BorshError(`Unexpected schema kind: ${structSchema.kind} for ${classType.constructor.name}`); 424 | } 425 | /// Deserializes object from bytes using schema. 426 | function deserialize(schema, classType, buffer, Reader = BinaryReader) { 427 | const reader = new Reader(buffer); 428 | const result = deserializeStruct(schema, classType, reader); 429 | if (reader.offset < buffer.length) { 430 | throw new BorshError(`Unexpected ${buffer.length - reader.offset} bytes after deserialized data`); 431 | } 432 | return result; 433 | } 434 | exports.deserialize = deserialize; 435 | /// Deserializes object from bytes using schema, without checking the length read 436 | function deserializeUnchecked(schema, classType, buffer, Reader = BinaryReader) { 437 | const reader = new Reader(buffer); 438 | return deserializeStruct(schema, classType, reader); 439 | } 440 | exports.deserializeUnchecked = deserializeUnchecked; 441 | --------------------------------------------------------------------------------