├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── jest.config.js ├── package.json ├── src ├── bindings │ ├── EmulatorBindings.spec.ts │ ├── EmulatorBindings.ts │ ├── __snapshots__ │ │ └── EmulatorBindings.spec.ts.snap │ └── wasm │ │ ├── emulator-emscripten.js │ │ ├── emulator-emscripten.wasm │ │ └── emulator-emscripten.wasm.js ├── coverage │ └── coverage.ts ├── emulator │ ├── ContractExecutor.spec.ts │ ├── ContractExecutor.ts │ ├── ContractSystem.spec.ts │ ├── ContractSystem.ts │ ├── Verbosity.ts │ └── __snapshots__ │ │ └── ContractSystem.spec.ts.snap ├── events │ ├── events.ts │ ├── message.ts │ └── tracker.ts ├── index.ts ├── logger │ └── logger.ts ├── treasure │ └── Treasure.ts └── utils │ ├── addressSource.ts │ ├── createAccount.ts │ ├── createEmptyAccount.ts │ ├── defaultConfig.ts │ ├── getMethodId.ts │ ├── maybe.ts │ ├── randomAddress.ts │ └── testKey.ts ├── tsconfig.json └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | ## [Unreleased] 9 | 10 | ## [4.3.0] - 2024-06-09 11 | 12 | ### Changed 13 | - Updated ton-abi version to @tact-lang npm organization package 14 | - Updated wasm emulation library to release TON v2024.01 15 | 16 | ## [4.2.2] - 2023-08-07 17 | 18 | ## Changed 19 | - Migrated to @ton org libraries for ton-crypto and ton-core 20 | 21 | ## [4.2.1] - 2023-04-27 22 | 23 | ## Fixed 24 | - Trying to parse message using both sender and receiver abi 25 | 26 | ## [4.2.0] - 2023-04-27 27 | 28 | ## Added 29 | - Add experimental ABI parser 30 | 31 | ## [4.1.0] - 2023-04-14 32 | 33 | ## Change 34 | - Upgtade wasm emulation libraries 35 | - Improving performance 36 | - Increase logs buffer limit 37 | 38 | ## [4.0.0] - 2023-04-13 39 | 40 | ## Changed 41 | - Convert values to a string representation for easier to read snapshots 42 | - Advance time by 6 seconds after each run 43 | 44 | ## Added 45 | - Deleted, frozen and storage charge events 46 | 47 | ## [3.4.1] - 2023-03-24 48 | 49 | ## Fixed 50 | - Browser environment support 51 | 52 | ## [3.4.0] - 2023-03-24 53 | 54 | ## Added 55 | - Codecoverage intergration 56 | 57 | ## [3.3.1] - 2023-03-22 58 | 59 | ## Fixed 60 | - Update WASM binares to the one with debug opcodes enabled 61 | 62 | ## [3.3.0] - 2023-03-22 63 | 64 | ## Added 65 | - Parse body of outgoing external message 66 | 67 | ## Changed 68 | - Parse external incoming message' body the same way as internal messages 69 | 70 | ## [3.2.3] - 2023-03-10 71 | 72 | ## Fixed 73 | - Fix typo in `ton-core` and require it to be `>=0.48.0` (by @dvlkv) 74 | 75 | ## [3.2.2] - 2023-03-09 76 | 77 | ## Fixed 78 | - Fix named contract in incoming external messages 79 | - Fix contract address handling in `contract` method 80 | 81 | ## [3.2.1] - 2023-03-09 82 | 83 | ## Fixed 84 | - Missing WASM files in npm package 85 | 86 | ## [3.2.0] - 2023-03-09 87 | 88 | ## Added 89 | - Named addresses for easier snapshot testing 90 | - Now you can use contract instance as an argument instead of it's address 91 | 92 | ## [3.1.0] - 2023-03-09 93 | 94 | ## Changed 95 | - Expose `verbosity` in `ContractSystem` and `ContractExecutor` 96 | 97 | ## [3.0.0] - 2023-03-09 98 | 99 | ## Changed 100 | - Rename `testAddress` to `randomAddress` 101 | - Update emulator binaries to the latest build 102 | - Make it browser friendly 103 | 104 | ## [2.1.1] - 2023-02-02 105 | 106 | ## Changed 107 | - Fixed `Logger.collect()` types 108 | - Print out VM Logs in `Logger.collect()` 109 | 110 | ## [2.1.0] - 2023-02-02 111 | 112 | ## Changed 113 | - Upgraded emulator binary to latest version 114 | - Combine all available logs into a single variable 115 | 116 | ## [2.0.0] - 2023-02-02 117 | 118 | ## Added 119 | - `Logger` to collect VM Logs from transactions 120 | 121 | ## Changed 122 | - `Tracker` now collects transactions instead of plain events for easier debugging 123 | - `events()` renamed to `collect()` 124 | - Changed `type` field in events to `$type` for better readability of jest snapshots 125 | 126 | ## [1.6.1] - 2023-01-26 127 | 128 | ## Changed 129 | - Repository name 130 | 131 | ## [1.6.0] - 2023-01-11 132 | 133 | ## Added 134 | - `reset` to `Tracker` for better readability in some tests 135 | 136 | ## [1.5.0] - 2023-01-11 137 | 138 | ## Added 139 | - `update` to `ContractSystem` to change config, current date or block lt. 140 | - `overwrite` to `ContractSystem` and `ContractExecutor` to replace contract state by address. Useful for mocking. 141 | 142 | ## [1.4.0] - 2023-01-08 143 | 144 | ## Added 145 | - Resolving error messages from error codes 146 | 147 | ## [1.3.0] - 2023-01-04 148 | 149 | ## Added 150 | - Expose vm logs in get method exceptions 151 | 152 | ## [1.2.0] - 2023-01-03 153 | 154 | ## Added 155 | - `Tracker` for tracking contract transactions 156 | 157 | ## Fixed 158 | - Allow sending multiple treasure messages in a single block 159 | 160 | ## [1.1.1] - 2023-01-03 161 | 162 | ## Fixed 163 | - Treasure balance is now `1m` instead of `1bn` to avoid overflow 164 | - Double message delivery 165 | - Case where multiple messages were sent to non-existent contract 166 | 167 | ## [1.1.0] - 2023-01-02 168 | 169 | ## Changed 170 | - Better typings for `Treasure` to include address 171 | 172 | ## [1.0.1] 173 | 174 | 🚀 Initial release 175 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Tact Software Foundation 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TON Emulator 2 | 3 | Emulation toolkit for TON Smart Contracts 4 | 5 | ## Installation 6 | 7 | ```bash 8 | yarn add @tact-lang/emulator @ton/core @ton/crypto ton-abi 9 | ``` 10 | 11 | ## Usage 12 | 13 | ```typescript 14 | import { ContractSystem, randomAddress } from '@tact-lang/emulator'; 15 | import { WalletContractV4 } from '@ton/ton'; 16 | 17 | // Contract System is a virtual environment that emulates the TON blockchain 18 | const system = await ContractSystem.create(); 19 | 20 | // Treasure is a contract that has 1m of TONs and is a handy entry point for your smart contracts 21 | let treasure = await system.treasure('my-treasure'); 22 | system.name(treasure, 'my treasure'); // Set a name for the contract. Useful for snapshot testing. 23 | 24 | // Update verbosity 25 | system.verbosity(treasure, Verbosity.ERROR); // Overwrite for a specific contract 26 | 27 | // Track contract transactions and events 28 | let tracker = system.track(treasure); 29 | 30 | // Logger to collect VM logs from a contract 31 | let logger = system.log(treasure); 32 | 33 | // Create a random unknown address that would be treated as uninitialized contract 34 | let unknownAddress = randomAddress('some-unknown-seed'); // This seed is used to generate deterministic address 35 | 36 | // Send an empty message to the unknown address 37 | await treasure.send({ 38 | to: unknownAddress, 39 | bounce: true, 40 | }); 41 | 42 | // Run a block 43 | let transactions = await system.run(); 44 | console.warn(inspect(transactions, false, 10000)); 45 | 46 | // Open a contract 47 | let wallet = system.open(WalletContractV4.create({ workchain: 0, publicKey: })); 48 | 49 | // Show contract logs 50 | console.warn(logger.collect()); 51 | 52 | // Test events and transactions 53 | expect(tracker.collect()).toMatchSnapshot(); 54 | 55 | ``` 56 | 57 | ## License 58 | 59 | MIT 60 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */ 2 | module.exports = { 3 | preset: 'ts-jest', 4 | testEnvironment: 'node', 5 | testPathIgnorePatterns: ["/node_modules/","/dist/"] 6 | }; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@tact-lang/emulator", 3 | "version": "4.3.0", 4 | "main": "dist/index.js", 5 | "repository": "https://github.com/tact-lang/tact-emulator.git", 6 | "author": "Steve Korshakov ", 7 | "license": "MIT", 8 | "files": [ 9 | "dist", 10 | "src", 11 | "wasm" 12 | ], 13 | "scripts": { 14 | "build": "rm -fr dist && tsc --declaration && cp -r src/bindings/wasm dist/bindings/wasm", 15 | "test": "jest --verbose --runInBand", 16 | "release": "yarn build && yarn test && yarn release-it --npm.yarn1" 17 | }, 18 | "devDependencies": { 19 | "@release-it/keep-a-changelog": "^3.1.0", 20 | "@types/jest": "^29.2.5", 21 | "@types/node": "^18.11.18", 22 | "jest": "^29.3.1", 23 | "release-it": "^15.6.0", 24 | "@ton/core": ">=0.49.2", 25 | "@ton/crypto": "^3.2.0", 26 | "ts-jest": "^29.0.3", 27 | "ts-node": "^10.9.1", 28 | "typescript": "^4.9.4", 29 | "@tact-lang/ton-abi": "^0.0.3" 30 | }, 31 | "peerDependencies": { 32 | "@ton/core": ">=0.49.2", 33 | "@ton/crypto": "^3.2.0", 34 | "ton-abi": "^0.0.1" 35 | }, 36 | "publishConfig": { 37 | "access": "public", 38 | "registry": "https://registry.npmjs.org/" 39 | }, 40 | "release-it": { 41 | "github": { 42 | "release": true 43 | }, 44 | "plugins": { 45 | "@release-it/keep-a-changelog": { 46 | "filename": "CHANGELOG.md" 47 | } 48 | } 49 | }, 50 | "dependencies": { 51 | "prando": "^6.0.1", 52 | "teslabot": "^1.5.0", 53 | "zod": "^3.20.2" 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/bindings/EmulatorBindings.spec.ts: -------------------------------------------------------------------------------- 1 | import { Address, beginCell, Cell, contractAddress } from "@ton/core"; 2 | import { defaultConfig } from "../utils/defaultConfig"; 3 | import { EmulatorBindings } from "./EmulatorBindings"; 4 | 5 | const echoCode = 'te6ccgECIgEAAoYAART/APSkE/S88sgLAQIBYgIDAgLLBAUCAVgeHwIBIAYHAgFIFBUCASAICQIBIA0OAgFICgsAI/N5EA5MmQt1nLALeRLOZk9BjAHfO37cCHXScIflTAg1wsf3gLQ0wMBcbDAAZF/kXDiAfpAIlBmbwT4YQKRW+DAAI4uINdJwh+OJu1E0NQB+GKBAQHXAAExAYAg1yHwFMj4QgHMAQGBAQHPAMntVNsx4N7tRNDUAfhigQEB1wABMQHwFYAwACwgbvLQgIAAeyPhCAcwBAYEBAc8Aye1UALvRBrpRDrpMuQYQARYQBYxyUBt5FAP5FnmNWBUILVgSiq2wQQYQBOEFUBCuuMKBnniyAKbyy3gSmg0OEATOQAt4EoIlDVAUcJGJnhAEzqGGgQa6UQ66TJOBBxcXQvgcAgEgDxAAFVlH8BygDgcAHKAIAgEgERIB9zIcQHKAVAH8A1wAcoCUAXPFlAD+gJwAcpoI26zJW6zsY49f/ANyHDwDXDwDSRus5l/8A0E8AFQBMyVNANw8A3iJG6zmX/wDQTwAVAEzJU0A3DwDeJw8A0Cf/ANAslYzJYzMwFw8A3iIW6zmH/wDQHwAQHMlDFw8A3iyQGATACU+EFvJBAjXwN/AnCAQlhtbfAOgAAT7AAIBIBYXAgFIHB0CASAYGQIBIBobAAsyAHPFsmAALR/yAGUcAHLH95vAAFvjG1vjAHwCPAHgABkcAHIzAEBgQEBzwDJgAAUMaSAACTwEfAPgAAk8BDwD4AIBICAhACe4Ni7UTQ1AH4YoEBAdcAATEB8BOAAJtaseAlAATbd6ME4LnYerpZXPY9CdhzrJUKNs0E4TusalpWyPlmRadeW/vixHMA=='; 6 | 7 | describe('EmulatorBindings', () => { 8 | it('should create bindings', async () => { 9 | let code = Cell.fromBoc(Buffer.from(echoCode, 'base64'))[0]; 10 | let data = beginCell() 11 | .storeRef(Cell.EMPTY) 12 | .storeInt(0, 257) 13 | .endCell(); 14 | let bindings = new EmulatorBindings(); 15 | let res = await bindings.runGetMethod({ 16 | verbosity: 4, 17 | code, 18 | data, 19 | address: contractAddress(0, { code, data }), 20 | config: defaultConfig, 21 | methodId: 115554, args: [{ type: 'int', value: 1n }], 22 | balance: 0n, 23 | gasLimit: 0n, 24 | unixtime: 0, 25 | randomSeed: Buffer.alloc(32) 26 | }); 27 | expect(res.output.success).toBe(true); 28 | if (res.output.success) { 29 | expect(res.output.stack).toMatchSnapshot(); 30 | } 31 | }); 32 | it('should execute emulator case', async () => { 33 | 34 | // verbosity: number, 35 | // address: Address, 36 | // code: Cell, 37 | // data: Cell, 38 | // balance: bigint, 39 | // unixtime: number, 40 | // randomSeed: Buffer, 41 | // gasLimit: bigint, 42 | // methodId: number, 43 | // args: TupleItem[], 44 | // config: Cell, 45 | // let ppp = { 46 | // code: 'te6cckEBCAEAlwABFP8A9KQT9LzyyAsBAgEgAwIAuPKDCNcYINMf0x/THwL4I7vyY+1E0NMf0x/T/9FRMrryoVFEuvKiBPkBVBBV+RDyo/QE0fgAf44WIYAQ9HhvpSCYAtMH1DAB+wCRMuIBs+ZbAaTIyx/LH8v/ye1UAgFIBwQCAUgGBQARuMl+1E0NcLH4ABe7Oc7UTQ0z8x1wv/gABNAwE3l/rQ==', 47 | // data: 'te6cckEBAQEAKgAAUAAAAAEAAAAAjbQby9p37RwFQv8kx3blcbZ6gO1mIWLvoMN5n47Ka2Mw7lLr', 48 | // verbosity: 0, 49 | // libs: '', 50 | // address: '0:26f3f75674dac80f4bb469fb3bbf21860c4c7be7bf7597aa29fca2aae5c5130b', 51 | // unixtime: 0, 52 | // balance: '9999999999999', 53 | // rand_seed: '0000000000000000000000000000000000000000000000000000000000000000', 54 | // gas_limit: '1000000000', 55 | // method_id: '85143' 56 | // }; 57 | // let args = [ 58 | // JSON.stringify(ppp), 59 | // 'te6cckEBAQEABQAABgAAANAJX0U=', 60 | // 'te6cckECcQEABW4AAgPNQAgBAgHOBQIBAUgDASsSY2CBqmNgiLIAAQABEAAAAAAAAADABACc0HOOgSeKR9NK7QvAShWEclTKIiT7KeN5YCq4i54HKwIneLCELpUQAAAAAAAAABUmyLDUSVB+cjV8cixVpAvtnckT5vUz9dfl85TM5VefAQFIBgErEmNgeqJjYIGqAAEAARAAAAAAAAAAwAcAnNBzjoEninFgk152x5e4P2obeffAtRXXbENLO/wHPAXk6o16jdMSEAAAAAAAAADXRh/1XRFatoIzSUoCBNvmoi70eMphk2KbS9TlOhR0JwIBIC8JAgEgHQoCASAYCwIBIBMMAQFYDQEBwA4CAWIQDwBBv2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZnAgFiEhEAQb6PZMavv/PdENi6Zwd5CslnDVQPN6lEiwM3uqalqSrKyAAD31ACASAWFAEBIBUAPtcBAwAAA+gAAD6AAAAAAwAAAAgAAAAEACAAAAAgAAABASAXACTCAQAAAPoAAAD6AAAVfAAAAAcCAUgbGQEBIBoAQuoAAAAAAA9CQAAAAAAD6AAAAAAAAYagAAAAAYAAVVVVVQEBIBwAQuoAAAAAAJiWgAAAAAAnEAAAAAAAD0JAAAAAAYAAVVVVVQIBICceAgEgIh8CASAgIAEBICEAUF3DAAIAAAAIAAAAEAAAwwABhqAAB6EgAA9CQMMAAAPoAAATiAAAJxACASAlIwEBICQAlNEAAAAAAAAD6AAAAAAAD0JA3gAAAAAD6AAAAAAAAAAPQkAAAAAAAA9CQAAAAAAAACcQAAAAAACYloAAAAAABfXhAAAAAAA7msoAAQEgJgCU0QAAAAAAAAPoAAAAAACYloDeAAAAACcQAAAAAAAAAA9CQAAAAAAAmJaAAAAAAAAAJxAAAAAAAJiWgAAAAAAF9eEAAAAAADuaygACASAqKAEBSCkATdBmAAAAAAAAAAAAAAAAgAAAAAAAAPoAAAAAAAAB9AAAAAAAA9CQQAIBIC0rAQEgLAAxYJGE5yoAByOG8m/BAABgkYTnKgAAADAACAEBIC4ADAPoAGQAAQIBIGMwAgEgPTECASA3MgIBIDUzAQEgNAAgAAAHCAAABdwAAAJYAAABLAEBIDYAFGtGVT8QBDuaygACASA6OAEBIDkAFRpRdIdugAEBIB9IAQEgOwEBwDwAt9BTMattNoAAkHAAQx3ZAu+VowKbp+ICJmhD5GEorXlHxC3H6wE6MfljTuTBTbWk4gs+NaYDgOTAD+ixkmRV+9aOm8QUXzkDPYIEZQAAAAAP////+AAAAAAAAAAEAgEgTD4CASBDPwEBIEACApFCQQAqNgQHBAIATEtAATEtAAAAAAIAAAPoACo2AgMCAgAPQkAAmJaAAAAAAQAAAfQBASBEAgEgR0UCCbf///BgRl8AAfwCAtlKSAIBYklTAgEgXV0CASBYSwIBzmBgAgEgYU0BASBOAgPNQFBPAAOooAIBIFhRAgEgVVICASBUUwAB1AIBSGBgAgEgV1YCASBbWwIBIFtdAgEgX1kCASBcWgIBIF1bAgEgYGACASBeXQABSAABWAIB1GBgAAEgAQEgYgAaxAAAAAEAAAAAAAAALgIBIGlkAQH0ZQEBwGYCASBoZwAVv////7y9GpSiABAAFb4AAAO8s2cNwVVQAgEgbGoBAUhrAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIBIG9tAQEgbgBAMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMBASBwAEBVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVaNaat0=' 61 | // ]; 62 | let bindings = new EmulatorBindings(); 63 | let res = await bindings.runGetMethod({ 64 | verbosity: 3, 65 | address: Address.parseRaw('0:26f3f75674dac80f4bb469fb3bbf21860c4c7be7bf7597aa29fca2aae5c5130b'), 66 | code: Cell.fromBase64('te6cckECDgEAAjwAART/APSkE/S88sgLAQIBYgYCAgEgBAMATb3ejBOC52Hq6WVz2PQnYc6yVCjbNBOE7rGpaVsj5ZkWnXlv74sRzAElv11faiaGoA/DFAgIDrgACY7Z5AUARjD+AIvEhlbGxvIHdvcmxkIY/hQwgHv+IDB/8AJw8AJt/iAwAgLOCAcALUmYtHRydWWP4UMJqLVmYWxzZY/hQw4oA+tO2i7ftwIddJwh+VMCDXCx/eAtDTAwFxsMABkX+RcOIB+kAiUGZvBPhhApFb4CCCEJRqmLa6jzQw7UTQ1AH4YoEBAdcAATEB0x8BghCUapi2uvLggdM/ATHbPNs8yPhCAcwBAYEBAc8Aye1U4MAAkTDjDfLAgoDQoJANL5AYLwriSRJjCP72Y5wHANIQPkRNizNc3oL9whyHw7e8G531e6jkHtRNDUAfhigQEB1wABMf4Ai8SGVsbG8gd29ybGQhj+FDCAe/4gMH/wAnDwAm3+IDDI+EIBzAEBgQEBzwDJ7VTbMeABJPhBbyQQI18DfwJwgEJYbW3bPAsB9shxAcoBUAcBygBwAcoCUAXPFlAD+gJwAcpoI26zJW6zsY5MfwHKAMhwAcoAcAHKACRus51/AcoABCBu8tCAUATMljQDcAHKAOIkbrOdfwHKAAQgbvLQgFAEzJY0A3ABygDicAHKAAJ/AcoAAslYzJczMwFwAcoA4iFuswwAMJx/AcoAASBu8tCAAcyVMXABygDiyQH7AAAcyAGCEK/5D1dYyx/LP8lVcC6X'), 67 | data: Cell.fromBase64('te6cckECEQEAAmoAAUEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEABAQHAAgEFoMY/AwEU/wD0pBP0vPLICwQCAWIJBQIBIAcGAE293owTgudh6ullc9j0J2HOslQo2zQThO6xqWlbI+WZFp15b++LEcwBJb9dX2omhqAPwxQICA64AAmO2eQIAEYw/gCLxIZWxsbyB3b3JsZCGP4UMIB7/iAwf/ACcPACbf4gMAICzgsKAC1JmLR0cnVlj+FDCai1ZmFsc2WP4UMOKAPrTtou37cCHXScIflTAg1wsf3gLQ0wMBcbDAAZF/kXDiAfpAIlBmbwT4YQKRW+AgghCUapi2uo80MO1E0NQB+GKBAQHXAAExAdMfAYIQlGqYtrry4IHTPwEx2zzbPMj4QgHMAQGBAQHPAMntVODAAJEw4w3ywIKBANDADS+QGC8K4kkSYwj+9mOcBwDSED5ETYszXN6C/cIch8O3vBud9Xuo5B7UTQ1AH4YoEBAdcAATH+AIvEhlbGxvIHdvcmxkIY/hQwgHv+IDB/8AJw8AJt/iAwyPhCAcwBAYEBAc8Aye1U2zHgAST4QW8kECNfA38CcIBCWG1t2zwOAfbIcQHKAVAHAcoAcAHKAlAFzxZQA/oCcAHKaCNusyVus7GOTH8BygDIcAHKAHABygAkbrOdfwHKAAQgbvLQgFAEzJY0A3ABygDiJG6znX8BygAEIG7y0IBQBMyWNANwAcoA4nABygACfwHKAALJWMyXMzMBcAHKAOIhbrMPADCcfwHKAAEgbvLQgAHMlTFwAcoA4skB+wAAHMgBghCv+Q9XWMsfyz/JhKJ/gQ=='), 68 | balance: 9999999999999n, 69 | unixtime: 0, 70 | randomSeed: Buffer.from('0000000000000000000000000000000000000000000000000000000000000000', 'hex'), 71 | gasLimit: 1000000000n, 72 | methodId: 93099, 73 | args: [], 74 | config: Cell.fromBase64('te6cckECcQEABW4AAgPNQAgBAgHOBQIBAUgDASsSY2CBqmNgiLIAAQABEAAAAAAAAADABACc0HOOgSeKR9NK7QvAShWEclTKIiT7KeN5YCq4i54HKwIneLCELpUQAAAAAAAAABUmyLDUSVB+cjV8cixVpAvtnckT5vUz9dfl85TM5VefAQFIBgErEmNgeqJjYIGqAAEAARAAAAAAAAAAwAcAnNBzjoEninFgk152x5e4P2obeffAtRXXbENLO/wHPAXk6o16jdMSEAAAAAAAAADXRh/1XRFatoIzSUoCBNvmoi70eMphk2KbS9TlOhR0JwIBIC8JAgEgHQoCASAYCwIBIBMMAQFYDQEBwA4CAWIQDwBBv2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZnAgFiEhEAQb6PZMavv/PdENi6Zwd5CslnDVQPN6lEiwM3uqalqSrKyAAD31ACASAWFAEBIBUAPtcBAwAAA+gAAD6AAAAAAwAAAAgAAAAEACAAAAAgAAABASAXACTCAQAAAPoAAAD6AAAVfAAAAAcCAUgbGQEBIBoAQuoAAAAAAA9CQAAAAAAD6AAAAAAAAYagAAAAAYAAVVVVVQEBIBwAQuoAAAAAAJiWgAAAAAAnEAAAAAAAD0JAAAAAAYAAVVVVVQIBICceAgEgIh8CASAgIAEBICEAUF3DAAIAAAAIAAAAEAAAwwABhqAAB6EgAA9CQMMAAAPoAAATiAAAJxACASAlIwEBICQAlNEAAAAAAAAD6AAAAAAAD0JA3gAAAAAD6AAAAAAAAAAPQkAAAAAAAA9CQAAAAAAAACcQAAAAAACYloAAAAAABfXhAAAAAAA7msoAAQEgJgCU0QAAAAAAAAPoAAAAAACYloDeAAAAACcQAAAAAAAAAA9CQAAAAAAAmJaAAAAAAAAAJxAAAAAAAJiWgAAAAAAF9eEAAAAAADuaygACASAqKAEBSCkATdBmAAAAAAAAAAAAAAAAgAAAAAAAAPoAAAAAAAAB9AAAAAAAA9CQQAIBIC0rAQEgLAAxYJGE5yoAByOG8m/BAABgkYTnKgAAADAACAEBIC4ADAPoAGQAAQIBIGMwAgEgPTECASA3MgIBIDUzAQEgNAAgAAAHCAAABdwAAAJYAAABLAEBIDYAFGtGVT8QBDuaygACASA6OAEBIDkAFRpRdIdugAEBIB9IAQEgOwEBwDwAt9BTMattNoAAkHAAQx3ZAu+VowKbp+ICJmhD5GEorXlHxC3H6wE6MfljTuTBTbWk4gs+NaYDgOTAD+ixkmRV+9aOm8QUXzkDPYIEZQAAAAAP////+AAAAAAAAAAEAgEgTD4CASBDPwEBIEACApFCQQAqNgQHBAIATEtAATEtAAAAAAIAAAPoACo2AgMCAgAPQkAAmJaAAAAAAQAAAfQBASBEAgEgR0UCCbf///BgRl8AAfwCAtlKSAIBYklTAgEgXV0CASBYSwIBzmBgAgEgYU0BASBOAgPNQFBPAAOooAIBIFhRAgEgVVICASBUUwAB1AIBSGBgAgEgV1YCASBbWwIBIFtdAgEgX1kCASBcWgIBIF1bAgEgYGACASBeXQABSAABWAIB1GBgAAEgAQEgYgAaxAAAAAEAAAAAAAAALgIBIGlkAQH0ZQEBwGYCASBoZwAVv////7y9GpSiABAAFb4AAAO8s2cNwVVQAgEgbGoBAUhrAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIBIG9tAQEgbgBAMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMBASBwAEBVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVaNaat0=') 75 | }); 76 | console.warn(res.logs); 77 | }) 78 | }); -------------------------------------------------------------------------------- /src/bindings/EmulatorBindings.ts: -------------------------------------------------------------------------------- 1 | import { AsyncLock } from "teslabot"; 2 | import { Address, Cell, serializeTuple, TupleItem } from "@ton/core"; 3 | 4 | // WASM bindings 5 | const createModule: (options: any) => Promise = require('./wasm/emulator-emscripten'); 6 | const wasmBinaryPack = require('./wasm/emulator-emscripten.wasm.js'); 7 | const wasmBinary = Buffer.from(wasmBinaryPack.wasmBinary, 'base64'); 8 | 9 | // Helper functions 10 | type Pointer = unknown; 11 | const writeToCString = (mod: any, data: string): Pointer => { 12 | const len = mod.lengthBytesUTF8(data) + 1; 13 | const ptr = mod._malloc(len); 14 | mod.stringToUTF8(data, ptr, len); 15 | return ptr; 16 | }; 17 | const readFromCString = (mod: any, pointer: Pointer): string => mod.UTF8ToString(pointer); 18 | 19 | // const defaultConfig = Cell.fromBoc(Buffer.from('"te6cckECcQEABW4AAgPNQAgBAgHOBQIBAUgDASsSY2CBqmNgiLIAAQABEAAAAAAAAADABACc0HOOgSeKR9NK7QvAShWEclTKIiT7KeN5YCq4i54HKwIneLCELpUQAAAAAAAAABUmyLDUSVB+cjV8cixVpAvtnckT5vUz9dfl85TM5VefAQFIBgErEmNgeqJjYIGqAAEAARAAAAAAAAAAwAcAnNBzjoEninFgk152x5e4P2obeffAtRXXbENLO/wHPAXk6o16jdMSEAAAAAAAAADXRh/1XRFatoIzSUoCBNvmoi70eMphk2KbS9TlOhR0JwIBIC8JAgEgHQoCASAYCwIBIBMMAQFYDQEBwA4CAWIQDwBBv2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZnAgFiEhEAQb6PZMavv/PdENi6Zwd5CslnDVQPN6lEiwM3uqalqSrKyAAD31ACASAWFAEBIBUAPtcBAwAAA+gAAD6AAAAAAwAAAAgAAAAEACAAAAAgAAABASAXACTCAQAAAPoAAAD6AAAVfAAAAAcCAUgbGQEBIBoAQuoAAAAAAA9CQAAAAAAD6AAAAAAAAYagAAAAAYAAVVVVVQEBIBwAQuoAAAAAAJiWgAAAAAAnEAAAAAAAD0JAAAAAAYAAVVVVVQIBICceAgEgIh8CASAgIAEBICEAUF3DAAIAAAAIAAAAEAAAwwABhqAAB6EgAA9CQMMAAAPoAAATiAAAJxACASAlIwEBICQAlNEAAAAAAAAD6AAAAAAAD0JA3gAAAAAD6AAAAAAAAAAPQkAAAAAAAA9CQAAAAAAAACcQAAAAAACYloAAAAAABfXhAAAAAAA7msoAAQEgJgCU0QAAAAAAAAPoAAAAAACYloDeAAAAACcQAAAAAAAAAA9CQAAAAAAAmJaAAAAAAAAAJxAAAAAAAJiWgAAAAAAF9eEAAAAAADuaygACASAqKAEBSCkATdBmAAAAAAAAAAAAAAAAgAAAAAAAAPoAAAAAAAAB9AAAAAAAA9CQQAIBIC0rAQEgLAAxYJGE5yoAByOG8m/BAABgkYTnKgAAADAACAEBIC4ADAPoAGQAAQIBIGMwAgEgPTECASA3MgIBIDUzAQEgNAAgAAAHCAAABdwAAAJYAAABLAEBIDYAFGtGVT8QBDuaygACASA6OAEBIDkAFRpRdIdugAEBIB9IAQEgOwEBwDwAt9BTMattNoAAkHAAQx3ZAu+VowKbp+ICJmhD5GEorXlHxC3H6wE6MfljTuTBTbWk4gs+NaYDgOTAD+ixkmRV+9aOm8QUXzkDPYIEZQAAAAAP////+AAAAAAAAAAEAgEgTD4CASBDPwEBIEACApFCQQAqNgQHBAIATEtAATEtAAAAAAIAAAPoACo2AgMCAgAPQkAAmJaAAAAAAQAAAfQBASBEAgEgR0UCCbf///BgRl8AAfwCAtlKSAIBYklTAgEgXV0CASBYSwIBzmBgAgEgYU0BASBOAgPNQFBPAAOooAIBIFhRAgEgVVICASBUUwAB1AIBSGBgAgEgV1YCASBbWwIBIFtdAgEgX1kCASBcWgIBIF1bAgEgYGACASBeXQABSAABWAIB1GBgAAEgAQEgYgAaxAAAAAEAAAAAAAAALgIBIGlkAQH0ZQEBwGYCASBoZwAVv////7y9GpSiABAAFb4AAAO8s2cNwVVQAgEgbGoBAUhrAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIBIG9tAQEgbgBAMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMBASBwAEBVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVaNaat0="', 'base64'))[0]; 20 | 21 | export type GetMethodArgs = { 22 | verbosity: number, 23 | address: Address, 24 | code: Cell, 25 | data: Cell, 26 | balance: bigint, 27 | unixtime: number, 28 | randomSeed: Buffer, 29 | gasLimit: bigint, 30 | methodId: number, 31 | args: TupleItem[], 32 | config: Cell, 33 | }; 34 | 35 | export type GetMethodResult = { 36 | logs: string; 37 | vmLogs: string; 38 | output: 39 | | { 40 | success: true; 41 | stack: string; 42 | gas_used: string; 43 | vm_exit_code: number; 44 | missing_library: string | null; 45 | } 46 | | { 47 | success: false; 48 | error: string; 49 | }; 50 | }; 51 | 52 | export type GetMethodResultInternal = { 53 | logs: string; 54 | output: 55 | | { 56 | success: true; 57 | stack: string; 58 | gas_used: string; 59 | vm_exit_code: number; 60 | vm_log: string; 61 | missing_library: string | null; 62 | } 63 | | { 64 | success: false; 65 | error: string; 66 | }; 67 | }; 68 | 69 | export type TransactionArgs = { 70 | config: Cell, 71 | libs: Cell | null, 72 | verbosity: number, 73 | shardAccount: Cell, 74 | message: Cell, 75 | now: number; 76 | lt: bigint; 77 | randomSeed: Buffer; 78 | }; 79 | 80 | export type TransactionResult = { 81 | output: 82 | | { 83 | success: true; 84 | transaction: string; 85 | shard_account: string; 86 | actions: string | null; 87 | } 88 | | { 89 | success: false; 90 | error: string; 91 | }; 92 | vmLogs: string; 93 | logs: string; 94 | } 95 | 96 | type TransactionResultInternal = { 97 | output: 98 | | { 99 | success: true; 100 | transaction: string; 101 | shard_account: string; 102 | vm_log: string; 103 | actions: string | null; 104 | } 105 | | { 106 | success: false; 107 | error: string; 108 | vm_log: string; 109 | }; 110 | logs: string; 111 | } 112 | 113 | export class EmulatorBindings { 114 | 115 | #lock = new AsyncLock(); 116 | #module: any = null; 117 | #errLogs: string[] = []; 118 | 119 | async runGetMethod(args: GetMethodArgs): Promise { 120 | 121 | return await this.#lock.inLock(async () => { 122 | try { 123 | 124 | // Get module 125 | let module = await this.getModule(); 126 | 127 | // Serialize args 128 | let stack = serializeTuple(args.args); 129 | 130 | // Prepare params 131 | const params /*: GetMethodInternalParams */ = { 132 | code: args.code.toBoc().toString('base64'), 133 | data: args.data.toBoc().toString('base64'), 134 | verbosity: args.verbosity, 135 | libs: '', 136 | address: args.address.toRawString(), 137 | unixtime: args.unixtime, 138 | balance: args.balance.toString(), 139 | rand_seed: args.randomSeed.toString('hex'), 140 | gas_limit: args.gasLimit.toString(), 141 | method_id: args.methodId, 142 | debug_enabled: true 143 | }; 144 | 145 | // Execute 146 | let res = await this.invoke(module, 147 | '_run_get_method', 148 | [ 149 | JSON.stringify(params), 150 | stack.toBoc().toString('base64'), 151 | args.config.toBoc().toString('base64'), 152 | ] 153 | ); 154 | let resStr: string; 155 | try { 156 | resStr = readFromCString(module, res); 157 | } finally { 158 | module._free(res); 159 | } 160 | 161 | let txres = JSON.parse(resStr) as GetMethodResultInternal; 162 | let logs: string = prepareLogs(this.#errLogs, txres.logs, txres.output.success ? txres.output.vm_log : ''); 163 | 164 | if (txres.output.success) { 165 | return { 166 | logs, 167 | vmLogs: txres.output.vm_log, 168 | output: { 169 | success: true, 170 | stack: txres.output.stack, 171 | gas_used: txres.output.gas_used, 172 | vm_exit_code: txres.output.vm_exit_code, 173 | missing_library: txres.output.missing_library, 174 | } 175 | }; 176 | } else { 177 | return { 178 | logs, 179 | vmLogs: '', 180 | output: { 181 | success: false, 182 | error: txres.output.error, 183 | } 184 | }; 185 | } 186 | } finally { 187 | this.#errLogs = []; 188 | } 189 | }); 190 | } 191 | 192 | async transaction(args: TransactionArgs): Promise { 193 | return await this.#lock.inLock(async () => { 194 | 195 | try { 196 | // Get module 197 | let module = await this.getModule(); 198 | // let instance = this.getInstance(module, args.config, args.verbosity); 199 | 200 | // Params 201 | const params = { 202 | utime: args.now, 203 | lt: args.lt.toString(), 204 | rand_seed: args.randomSeed.toString('hex'), 205 | ignore_chksig: false, 206 | debug_enabled: true 207 | }; 208 | 209 | // Execute 210 | let res = this.invoke(module, '_emulate', [ 211 | args.config.toBoc().toString('base64'), 212 | args.libs ? args.libs.toBoc().toString('base64') : 0, 213 | args.verbosity, 214 | args.shardAccount.toBoc().toString('base64'), 215 | args.message.toBoc().toString('base64'), 216 | JSON.stringify(params), 217 | ]); 218 | let resStr: string; 219 | try { 220 | resStr = readFromCString(module, res); 221 | } finally { 222 | module._free(res); 223 | } 224 | 225 | // Preprocess result 226 | let txres = JSON.parse(resStr) as TransactionResultInternal; 227 | let logs: string = prepareLogs(this.#errLogs, txres.logs, txres.output.vm_log); 228 | 229 | // Convert output 230 | if (txres.output.success) { 231 | return { 232 | logs, 233 | vmLogs: txres.output.vm_log, 234 | output: { 235 | success: true, 236 | transaction: txres.output.transaction, 237 | shard_account: txres.output.shard_account, 238 | actions: txres.output.actions, 239 | } 240 | }; 241 | } else { 242 | return { 243 | logs, 244 | vmLogs: txres.output.vm_log, 245 | output: { 246 | success: false, 247 | error: txres.output.error 248 | } 249 | }; 250 | } 251 | } finally { 252 | this.#errLogs = []; 253 | } 254 | }); 255 | } 256 | 257 | private invoke = (module: any, name: string, args: (string | number | Pointer)[]): Pointer => { 258 | 259 | // Pointer tracking 260 | const allocatedPointers: Pointer[] = []; 261 | const trackPointer = (pointer: Pointer): Pointer => { 262 | allocatedPointers.push(pointer); 263 | return pointer; 264 | }; 265 | 266 | // Execute 267 | try { 268 | let mappedArgs = args.map((arg) => { 269 | if (typeof arg === 'string') { 270 | return trackPointer(writeToCString(module, arg)) 271 | } else { 272 | return arg; 273 | } 274 | }); 275 | return module[name](...mappedArgs); 276 | } catch (e) { 277 | throw Error((e as any).message); 278 | } finally { 279 | allocatedPointers.forEach((pointer) => module._free(pointer)); 280 | } 281 | } 282 | 283 | private async getModule() { 284 | if (!this.#module) { 285 | this.#module = await createModule({ 286 | wasmBinary, 287 | printErr: (text: string) => this.#errLogs.push(text), 288 | }); 289 | } 290 | let module = this.#module; 291 | return module; 292 | } 293 | } 294 | 295 | function prepareLogs(errors: string[], stdout: string, vmLogs: string) { 296 | let logs = ''; 297 | 298 | let debug = errors.filter((v) => v.startsWith('#DEBUG#')); 299 | let nonDebug = errors.filter((v) => !v.startsWith('#DEBUG#')); 300 | 301 | // Debug Logs 302 | if (debug.length > 0) { 303 | logs += '=== DEBUG LOGS ===\n' 304 | logs += debug.map((v) => v.slice('#DEBUG#: '.length)).join('\n'); 305 | logs += '\n' 306 | logs += '\n' 307 | } 308 | 309 | // VM log 310 | if (vmLogs.length > 0) { 311 | logs += '=== VM LOGS ===\n' 312 | logs += vmLogs; 313 | logs += '\n' 314 | logs += '\n' 315 | } 316 | 317 | // Stdout 318 | if (stdout.length > 0) { 319 | logs += '=== STDOUT ===\n' 320 | logs += stdout; 321 | logs += '\n' 322 | logs += '\n' 323 | } 324 | 325 | // Stderr 326 | if (nonDebug.length > 0) { 327 | logs += '=== STDERR ===\n' 328 | logs += nonDebug.join('\n'); 329 | logs += '\n' 330 | logs += '\n' 331 | } 332 | 333 | return logs; 334 | } -------------------------------------------------------------------------------- /src/bindings/__snapshots__/EmulatorBindings.spec.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`EmulatorBindings should create bindings 1`] = `"te6cckEBAgEAEQABGAAAAQEAAAAAAAAAAgEAALo9fZc="`; 4 | -------------------------------------------------------------------------------- /src/bindings/wasm/emulator-emscripten.js: -------------------------------------------------------------------------------- 1 | 2 | var EmulatorModule = (() => { 3 | var _scriptDir = typeof document !== 'undefined' && document.currentScript ? document.currentScript.src : undefined; 4 | 5 | return ( 6 | function(EmulatorModule) { 7 | EmulatorModule = EmulatorModule || {}; 8 | 9 | var Module=typeof EmulatorModule!="undefined"?EmulatorModule:{};var readyPromiseResolve,readyPromiseReject;Module["ready"]=new Promise(function(resolve,reject){readyPromiseResolve=resolve;readyPromiseReject=reject});var moduleOverrides=Object.assign({},Module);var arguments_=[];var thisProgram="./this.program";var quit_=(status,toThrow)=>{throw toThrow};var ENVIRONMENT_IS_WEB=true;var ENVIRONMENT_IS_WORKER=false;var scriptDirectory="";function locateFile(path){if(Module["locateFile"]){return Module["locateFile"](path,scriptDirectory)}return scriptDirectory+path}var read_,readAsync,readBinary,setWindowTitle;if(ENVIRONMENT_IS_WEB||ENVIRONMENT_IS_WORKER){if(ENVIRONMENT_IS_WORKER){scriptDirectory=self.location.href}else if(typeof document!="undefined"&&document.currentScript){scriptDirectory=document.currentScript.src}if(_scriptDir){scriptDirectory=_scriptDir}if(scriptDirectory.indexOf("blob:")!==0){scriptDirectory=scriptDirectory.substr(0,scriptDirectory.replace(/[?#].*/,"").lastIndexOf("/")+1)}else{scriptDirectory=""}{read_=url=>{var xhr=new XMLHttpRequest;xhr.open("GET",url,false);xhr.send(null);return xhr.responseText};if(ENVIRONMENT_IS_WORKER){readBinary=url=>{var xhr=new XMLHttpRequest;xhr.open("GET",url,false);xhr.responseType="arraybuffer";xhr.send(null);return new Uint8Array(xhr.response)}}readAsync=(url,onload,onerror)=>{var xhr=new XMLHttpRequest;xhr.open("GET",url,true);xhr.responseType="arraybuffer";xhr.onload=()=>{if(xhr.status==200||xhr.status==0&&xhr.response){onload(xhr.response);return}onerror()};xhr.onerror=onerror;xhr.send(null)}}setWindowTitle=title=>document.title=title}else{}var out=Module["print"]||console.log.bind(console);var err=Module["printErr"]||console.warn.bind(console);Object.assign(Module,moduleOverrides);moduleOverrides=null;if(Module["arguments"])arguments_=Module["arguments"];if(Module["thisProgram"])thisProgram=Module["thisProgram"];if(Module["quit"])quit_=Module["quit"];var wasmBinary;if(Module["wasmBinary"])wasmBinary=Module["wasmBinary"];var noExitRuntime=Module["noExitRuntime"]||true;if(typeof WebAssembly!="object"){abort("no native wasm support detected")}var wasmMemory;var ABORT=false;var EXITSTATUS;var UTF8Decoder=typeof TextDecoder!="undefined"?new TextDecoder("utf8"):undefined;function UTF8ArrayToString(heapOrArray,idx,maxBytesToRead){var endIdx=idx+maxBytesToRead;var endPtr=idx;while(heapOrArray[endPtr]&&!(endPtr>=endIdx))++endPtr;if(endPtr-idx>16&&heapOrArray.buffer&&UTF8Decoder){return UTF8Decoder.decode(heapOrArray.subarray(idx,endPtr))}var str="";while(idx>10,56320|ch&1023)}}return str}function UTF8ToString(ptr,maxBytesToRead){return ptr?UTF8ArrayToString(HEAPU8,ptr,maxBytesToRead):""}function stringToUTF8Array(str,heap,outIdx,maxBytesToWrite){if(!(maxBytesToWrite>0))return 0;var startIdx=outIdx;var endIdx=outIdx+maxBytesToWrite-1;for(var i=0;i=55296&&u<=57343){var u1=str.charCodeAt(++i);u=65536+((u&1023)<<10)|u1&1023}if(u<=127){if(outIdx>=endIdx)break;heap[outIdx++]=u}else if(u<=2047){if(outIdx+1>=endIdx)break;heap[outIdx++]=192|u>>6;heap[outIdx++]=128|u&63}else if(u<=65535){if(outIdx+2>=endIdx)break;heap[outIdx++]=224|u>>12;heap[outIdx++]=128|u>>6&63;heap[outIdx++]=128|u&63}else{if(outIdx+3>=endIdx)break;heap[outIdx++]=240|u>>18;heap[outIdx++]=128|u>>12&63;heap[outIdx++]=128|u>>6&63;heap[outIdx++]=128|u&63}}heap[outIdx]=0;return outIdx-startIdx}function stringToUTF8(str,outPtr,maxBytesToWrite){return stringToUTF8Array(str,HEAPU8,outPtr,maxBytesToWrite)}function lengthBytesUTF8(str){var len=0;for(var i=0;i=55296&&c<=57343){len+=4;++i}else{len+=3}}return len}var buffer,HEAP8,HEAPU8,HEAP16,HEAPU16,HEAP32,HEAPU32,HEAPF32,HEAPF64;function updateGlobalBufferAndViews(buf){buffer=buf;Module["HEAP8"]=HEAP8=new Int8Array(buf);Module["HEAP16"]=HEAP16=new Int16Array(buf);Module["HEAP32"]=HEAP32=new Int32Array(buf);Module["HEAPU8"]=HEAPU8=new Uint8Array(buf);Module["HEAPU16"]=HEAPU16=new Uint16Array(buf);Module["HEAPU32"]=HEAPU32=new Uint32Array(buf);Module["HEAPF32"]=HEAPF32=new Float32Array(buf);Module["HEAPF64"]=HEAPF64=new Float64Array(buf)}var INITIAL_MEMORY=Module["INITIAL_MEMORY"]||16777216;var wasmTable;var __ATPRERUN__=[];var __ATINIT__=[];var __ATPOSTRUN__=[];var runtimeInitialized=false;function preRun(){if(Module["preRun"]){if(typeof Module["preRun"]=="function")Module["preRun"]=[Module["preRun"]];while(Module["preRun"].length){addOnPreRun(Module["preRun"].shift())}}callRuntimeCallbacks(__ATPRERUN__)}function initRuntime(){runtimeInitialized=true;callRuntimeCallbacks(__ATINIT__)}function postRun(){if(Module["postRun"]){if(typeof Module["postRun"]=="function")Module["postRun"]=[Module["postRun"]];while(Module["postRun"].length){addOnPostRun(Module["postRun"].shift())}}callRuntimeCallbacks(__ATPOSTRUN__)}function addOnPreRun(cb){__ATPRERUN__.unshift(cb)}function addOnInit(cb){__ATINIT__.unshift(cb)}function addOnPostRun(cb){__ATPOSTRUN__.unshift(cb)}var runDependencies=0;var runDependencyWatcher=null;var dependenciesFulfilled=null;function addRunDependency(id){runDependencies++;if(Module["monitorRunDependencies"]){Module["monitorRunDependencies"](runDependencies)}}function removeRunDependency(id){runDependencies--;if(Module["monitorRunDependencies"]){Module["monitorRunDependencies"](runDependencies)}if(runDependencies==0){if(runDependencyWatcher!==null){clearInterval(runDependencyWatcher);runDependencyWatcher=null}if(dependenciesFulfilled){var callback=dependenciesFulfilled;dependenciesFulfilled=null;callback()}}}function abort(what){{if(Module["onAbort"]){Module["onAbort"](what)}}what="Aborted("+what+")";err(what);ABORT=true;EXITSTATUS=1;what+=". Build with -sASSERTIONS for more info.";var e=new WebAssembly.RuntimeError(what);readyPromiseReject(e);throw e}var dataURIPrefix="data:application/octet-stream;base64,";function isDataURI(filename){return filename.startsWith(dataURIPrefix)}var wasmBinaryFile;wasmBinaryFile="emulator-emscripten.wasm";if(!isDataURI(wasmBinaryFile)){wasmBinaryFile=locateFile(wasmBinaryFile)}function getBinary(file){try{if(file==wasmBinaryFile&&wasmBinary){return new Uint8Array(wasmBinary)}if(readBinary){return readBinary(file)}throw"both async and sync fetching of the wasm failed"}catch(err){abort(err)}}function getBinaryPromise(){if(!wasmBinary&&(ENVIRONMENT_IS_WEB||ENVIRONMENT_IS_WORKER)){if(typeof fetch=="function"){return fetch(wasmBinaryFile,{credentials:"same-origin"}).then(function(response){if(!response["ok"]){throw"failed to load wasm binary file at '"+wasmBinaryFile+"'"}return response["arrayBuffer"]()}).catch(function(){return getBinary(wasmBinaryFile)})}}return Promise.resolve().then(function(){return getBinary(wasmBinaryFile)})}function createWasm(){var info={"a":asmLibraryArg};function receiveInstance(instance,module){var exports=instance.exports;Module["asm"]=exports;wasmMemory=Module["asm"]["za"];updateGlobalBufferAndViews(wasmMemory.buffer);wasmTable=Module["asm"]["Ea"];addOnInit(Module["asm"]["Aa"]);removeRunDependency("wasm-instantiate")}addRunDependency("wasm-instantiate");function receiveInstantiationResult(result){receiveInstance(result["instance"])}function instantiateArrayBuffer(receiver){return getBinaryPromise().then(function(binary){return WebAssembly.instantiate(binary,info)}).then(function(instance){return instance}).then(receiver,function(reason){err("failed to asynchronously prepare wasm: "+reason);abort(reason)})}function instantiateAsync(){if(!wasmBinary&&typeof WebAssembly.instantiateStreaming=="function"&&!isDataURI(wasmBinaryFile)&&typeof fetch=="function"){return fetch(wasmBinaryFile,{credentials:"same-origin"}).then(function(response){var result=WebAssembly.instantiateStreaming(response,info);return result.then(receiveInstantiationResult,function(reason){err("wasm streaming compile failed: "+reason);err("falling back to ArrayBuffer instantiation");return instantiateArrayBuffer(receiveInstantiationResult)})})}else{return instantiateArrayBuffer(receiveInstantiationResult)}}if(Module["instantiateWasm"]){try{var exports=Module["instantiateWasm"](info,receiveInstance);return exports}catch(e){err("Module.instantiateWasm callback failed with error: "+e);return false}}instantiateAsync().catch(readyPromiseReject);return{}}var ASM_CONSTS={1727512:()=>{return Module.getRandomValue()},1727548:()=>{if(Module.getRandomValue===undefined){try{var window_="object"===typeof window?window:self;var crypto_=typeof window_.crypto!=="undefined"?window_.crypto:window_.msCrypto;var randomValuesStandard=function(){var buf=new Uint32Array(1);crypto_.getRandomValues(buf);return buf[0]>>>0};randomValuesStandard();Module.getRandomValue=randomValuesStandard}catch(e){try{var crypto=require("crypto");var randomValueNodeJS=function(){var buf=crypto["randomBytes"](4);return(buf[0]<<24|buf[1]<<16|buf[2]<<8|buf[3])>>>0};randomValueNodeJS();Module.getRandomValue=randomValueNodeJS}catch(e){throw"No secure random number generator found"}}}},1728270:$0=>{throw UTF8ToString($0)}};function callRuntimeCallbacks(callbacks){while(callbacks.length>0){callbacks.shift()(Module)}}function jsStackTrace(){var error=new Error;if(!error.stack){try{throw new Error}catch(e){error=e}if(!error.stack){return"(no stack trace available)"}}return error.stack.toString()}function writeArrayToMemory(array,buffer){HEAP8.set(array,buffer)}function ___cxa_allocate_exception(size){return _malloc(size+24)+24}var exceptionCaught=[];function exception_addRef(info){info.add_ref()}var uncaughtExceptionCount=0;function ___cxa_begin_catch(ptr){var info=new ExceptionInfo(ptr);if(!info.get_caught()){info.set_caught(true);uncaughtExceptionCount--}info.set_rethrown(false);exceptionCaught.push(info);exception_addRef(info);return info.get_exception_ptr()}var exceptionLast=0;function ExceptionInfo(excPtr){this.excPtr=excPtr;this.ptr=excPtr-24;this.set_type=function(type){HEAPU32[this.ptr+4>>2]=type};this.get_type=function(){return HEAPU32[this.ptr+4>>2]};this.set_destructor=function(destructor){HEAPU32[this.ptr+8>>2]=destructor};this.get_destructor=function(){return HEAPU32[this.ptr+8>>2]};this.set_refcount=function(refcount){HEAP32[this.ptr>>2]=refcount};this.set_caught=function(caught){caught=caught?1:0;HEAP8[this.ptr+12>>0]=caught};this.get_caught=function(){return HEAP8[this.ptr+12>>0]!=0};this.set_rethrown=function(rethrown){rethrown=rethrown?1:0;HEAP8[this.ptr+13>>0]=rethrown};this.get_rethrown=function(){return HEAP8[this.ptr+13>>0]!=0};this.init=function(type,destructor){this.set_adjusted_ptr(0);this.set_type(type);this.set_destructor(destructor);this.set_refcount(0);this.set_caught(false);this.set_rethrown(false)};this.add_ref=function(){var value=HEAP32[this.ptr>>2];HEAP32[this.ptr>>2]=value+1};this.release_ref=function(){var prev=HEAP32[this.ptr>>2];HEAP32[this.ptr>>2]=prev-1;return prev===1};this.set_adjusted_ptr=function(adjustedPtr){HEAPU32[this.ptr+16>>2]=adjustedPtr};this.get_adjusted_ptr=function(){return HEAPU32[this.ptr+16>>2]};this.get_exception_ptr=function(){var isPointer=___cxa_is_pointer_type(this.get_type());if(isPointer){return HEAPU32[this.excPtr>>2]}var adjusted=this.get_adjusted_ptr();if(adjusted!==0)return adjusted;return this.excPtr}}function ___cxa_free_exception(ptr){try{return _free(new ExceptionInfo(ptr).ptr)}catch(e){}}function getWasmTableEntry(funcPtr){return wasmTable.get(funcPtr)}function exception_decRef(info){if(info.release_ref()&&!info.get_rethrown()){var destructor=info.get_destructor();if(destructor){getWasmTableEntry(destructor)(info.excPtr)}___cxa_free_exception(info.excPtr)}}function ___cxa_end_catch(){_setThrew(0);var info=exceptionCaught.pop();exception_decRef(info);exceptionLast=0}function ___resumeException(ptr){if(!exceptionLast){exceptionLast=ptr}throw ptr}function ___cxa_find_matching_catch_2(){var thrown=exceptionLast;if(!thrown){setTempRet0(0);return 0}var info=new ExceptionInfo(thrown);info.set_adjusted_ptr(thrown);var thrownType=info.get_type();if(!thrownType){setTempRet0(0);return thrown}var typeArray=Array.prototype.slice.call(arguments);for(var i=0;i>2];return ret},getStr:function(ptr){var ret=UTF8ToString(ptr);return ret}};function ___syscall_fcntl64(fd,cmd,varargs){SYSCALLS.varargs=varargs;return 0}function ___syscall_fstat64(fd,buf){}function ___syscall_getdents64(fd,dirp,count){}function ___syscall_ioctl(fd,op,varargs){SYSCALLS.varargs=varargs;return 0}function ___syscall_lstat64(path,buf){}function ___syscall_newfstatat(dirfd,path,buf,flags){}function ___syscall_openat(dirfd,path,flags,varargs){SYSCALLS.varargs=varargs}function ___syscall_stat64(path,buf){}function __emscripten_date_now(){return Date.now()}var nowIsMonotonic=true;function __emscripten_get_now_is_monotonic(){return nowIsMonotonic}function readI53FromI64(ptr){return HEAPU32[ptr>>2]+HEAP32[ptr+4>>2]*4294967296}function __gmtime_js(time,tmPtr){var date=new Date(readI53FromI64(time)*1e3);HEAP32[tmPtr>>2]=date.getUTCSeconds();HEAP32[tmPtr+4>>2]=date.getUTCMinutes();HEAP32[tmPtr+8>>2]=date.getUTCHours();HEAP32[tmPtr+12>>2]=date.getUTCDate();HEAP32[tmPtr+16>>2]=date.getUTCMonth();HEAP32[tmPtr+20>>2]=date.getUTCFullYear()-1900;HEAP32[tmPtr+24>>2]=date.getUTCDay();var start=Date.UTC(date.getUTCFullYear(),0,1,0,0,0,0);var yday=(date.getTime()-start)/(1e3*60*60*24)|0;HEAP32[tmPtr+28>>2]=yday}function __localtime_js(time,tmPtr){var date=new Date(readI53FromI64(time)*1e3);HEAP32[tmPtr>>2]=date.getSeconds();HEAP32[tmPtr+4>>2]=date.getMinutes();HEAP32[tmPtr+8>>2]=date.getHours();HEAP32[tmPtr+12>>2]=date.getDate();HEAP32[tmPtr+16>>2]=date.getMonth();HEAP32[tmPtr+20>>2]=date.getFullYear()-1900;HEAP32[tmPtr+24>>2]=date.getDay();var start=new Date(date.getFullYear(),0,1);var yday=(date.getTime()-start.getTime())/(1e3*60*60*24)|0;HEAP32[tmPtr+28>>2]=yday;HEAP32[tmPtr+36>>2]=-(date.getTimezoneOffset()*60);var summerOffset=new Date(date.getFullYear(),6,1).getTimezoneOffset();var winterOffset=start.getTimezoneOffset();var dst=(summerOffset!=winterOffset&&date.getTimezoneOffset()==Math.min(winterOffset,summerOffset))|0;HEAP32[tmPtr+32>>2]=dst}function __munmap_js(addr,len,prot,flags,fd,offset){}function allocateUTF8(str){var size=lengthBytesUTF8(str)+1;var ret=_malloc(size);if(ret)stringToUTF8Array(str,HEAP8,ret,size);return ret}function _tzset_impl(timezone,daylight,tzname){var currentYear=(new Date).getFullYear();var winter=new Date(currentYear,0,1);var summer=new Date(currentYear,6,1);var winterOffset=winter.getTimezoneOffset();var summerOffset=summer.getTimezoneOffset();var stdTimezoneOffset=Math.max(winterOffset,summerOffset);HEAP32[timezone>>2]=stdTimezoneOffset*60;HEAP32[daylight>>2]=Number(winterOffset!=summerOffset);function extractZone(date){var match=date.toTimeString().match(/\(([A-Za-z ]+)\)$/);return match?match[1]:"GMT"}var winterName=extractZone(winter);var summerName=extractZone(summer);var winterNamePtr=allocateUTF8(winterName);var summerNamePtr=allocateUTF8(summerName);if(summerOffset>2]=winterNamePtr;HEAPU32[tzname+4>>2]=summerNamePtr}else{HEAPU32[tzname>>2]=summerNamePtr;HEAPU32[tzname+4>>2]=winterNamePtr}}function __tzset_js(timezone,daylight,tzname){if(__tzset_js.called)return;__tzset_js.called=true;_tzset_impl(timezone,daylight,tzname)}function _abort(){abort("")}var readAsmConstArgsArray=[];function readAsmConstArgs(sigPtr,buf){readAsmConstArgsArray.length=0;var ch;buf>>=2;while(ch=HEAPU8[sigPtr++]){buf+=ch!=105&buf;readAsmConstArgsArray.push(ch==105?HEAP32[buf]:HEAPF64[buf++>>1]);++buf}return readAsmConstArgsArray}function _emscripten_asm_const_int(code,sigPtr,argbuf){var args=readAsmConstArgs(sigPtr,argbuf);return ASM_CONSTS[code].apply(null,args)}var _emscripten_get_now;_emscripten_get_now=()=>performance.now();function reallyNegative(x){return x<0||x===0&&1/x===-Infinity}function convertI32PairToI53(lo,hi){return(lo>>>0)+hi*4294967296}function convertU32PairToI53(lo,hi){return(lo>>>0)+(hi>>>0)*4294967296}function reSign(value,bits){if(value<=0){return value}var half=bits<=32?Math.abs(1<=half&&(bits<=32||value>half)){value=-2*half+value}return value}function unSign(value,bits){if(value>=0){return value}return bits<=32?2*Math.abs(1<0?length:lengthBytesUTF8(stringy)+1;var u8array=new Array(len);var numBytesWritten=stringToUTF8Array(stringy,u8array,0,u8array.length);if(dontAddNull)u8array.length=numBytesWritten;return u8array}function formatString(format,varargs){var textIndex=format;var argIndex=varargs;function prepVararg(ptr,type){if(type==="double"||type==="i64"){if(ptr&7){ptr+=4}}else{}return ptr}function getNextArg(type){var ret;argIndex=prepVararg(argIndex,type);if(type==="double"){ret=HEAPF64[argIndex>>3];argIndex+=8}else if(type=="i64"){ret=[HEAP32[argIndex>>2],HEAP32[argIndex+4>>2]];argIndex+=8}else{type="i32";ret=HEAP32[argIndex>>2];argIndex+=4}return ret}var ret=[];var curr,next,currArg;while(1){var startTextIndex=textIndex;curr=HEAP8[textIndex>>0];if(curr===0)break;next=HEAP8[textIndex+1>>0];if(curr==37){var flagAlwaysSigned=false;var flagLeftAlign=false;var flagAlternative=false;var flagZeroPad=false;var flagPadSign=false;flagsLoop:while(1){switch(next){case 43:flagAlwaysSigned=true;break;case 45:flagLeftAlign=true;break;case 35:flagAlternative=true;break;case 48:if(flagZeroPad){break flagsLoop}else{flagZeroPad=true;break}case 32:flagPadSign=true;break;default:break flagsLoop}textIndex++;next=HEAP8[textIndex+1>>0]}var width=0;if(next==42){width=getNextArg("i32");textIndex++;next=HEAP8[textIndex+1>>0]}else{while(next>=48&&next<=57){width=width*10+(next-48);textIndex++;next=HEAP8[textIndex+1>>0]}}var precisionSet=false,precision=-1;if(next==46){precision=0;precisionSet=true;textIndex++;next=HEAP8[textIndex+1>>0];if(next==42){precision=getNextArg("i32");textIndex++}else{while(1){var precisionChr=HEAP8[textIndex+1>>0];if(precisionChr<48||precisionChr>57)break;precision=precision*10+(precisionChr-48);textIndex++}}next=HEAP8[textIndex+1>>0]}if(precision<0){precision=6;precisionSet=false}var argSize;switch(String.fromCharCode(next)){case"h":var nextNext=HEAP8[textIndex+2>>0];if(nextNext==104){textIndex++;argSize=1}else{argSize=2}break;case"l":var nextNext=HEAP8[textIndex+2>>0];if(nextNext==108){textIndex++;argSize=8}else{argSize=4}break;case"L":case"q":case"j":argSize=8;break;case"z":case"t":case"I":argSize=4;break;default:argSize=null}if(argSize)textIndex++;next=HEAP8[textIndex+1>>0];switch(String.fromCharCode(next)){case"d":case"i":case"u":case"o":case"x":case"X":case"p":{var signed=next==100||next==105;argSize=argSize||4;currArg=getNextArg("i"+argSize*8);var argText;if(argSize==8){currArg=next==117?convertU32PairToI53(currArg[0],currArg[1]):convertI32PairToI53(currArg[0],currArg[1])}if(argSize<=4){var limit=Math.pow(256,argSize)-1;currArg=(signed?reSign:unSign)(currArg&limit,argSize*8)}var currAbsArg=Math.abs(currArg);var prefix="";if(next==100||next==105){argText=reSign(currArg,8*argSize).toString(10)}else if(next==117){argText=unSign(currArg,8*argSize).toString(10);currArg=Math.abs(currArg)}else if(next==111){argText=(flagAlternative?"0":"")+currAbsArg.toString(8)}else if(next==120||next==88){prefix=flagAlternative&&currArg!=0?"0x":"";if(currArg<0){currArg=-currArg;argText=(currAbsArg-1).toString(16);var buffer=[];for(var i=0;i=0){if(flagAlwaysSigned){prefix="+"+prefix}else if(flagPadSign){prefix=" "+prefix}}if(argText.charAt(0)=="-"){prefix="-"+prefix;argText=argText.substr(1)}while(prefix.length+argText.lengthexponent&&exponent>=-4){next=(next==103?"f":"F").charCodeAt(0);precision-=exponent+1}else{next=(next==103?"e":"E").charCodeAt(0);precision--}effectivePrecision=Math.min(precision,20)}if(next==101||next==69){argText=currArg.toExponential(effectivePrecision);if(/[eE][-+]\d$/.test(argText)){argText=argText.slice(0,-1)+"0"+argText.slice(-1)}}else if(next==102||next==70){argText=currArg.toFixed(effectivePrecision);if(currArg===0&&reallyNegative(currArg)){argText="-"+argText}}var parts=argText.split("e");if(isGeneral&&!flagAlternative){while(parts[0].length>1&&parts[0].includes(".")&&(parts[0].slice(-1)=="0"||parts[0].slice(-1)==".")){parts[0]=parts[0].slice(0,-1)}}else{if(flagAlternative&&argText.indexOf(".")==-1)parts[0]+=".";while(precision>effectivePrecision++)parts[0]+="0"}argText=parts[0]+(parts.length>1?"e"+parts[1]:"");if(next==69)argText=argText.toUpperCase();if(currArg>=0){if(flagAlwaysSigned){argText="+"+argText}else if(flagPadSign){argText=" "+argText}}}while(argText.length>0])}}else{ret=ret.concat(intArrayFromString("(null)".substr(0,argLength),true))}if(flagLeftAlign){while(argLength0){ret.push(32)}if(!flagLeftAlign)ret.push(getNextArg("i8"));break}case"n":{var ptr=getNextArg("i32*");HEAP32[ptr>>2]=ret.length;break}case"%":{ret.push(curr);break}default:{for(var i=startTextIndex;i>0])}}}textIndex+=2}else{ret.push(curr);textIndex+=1}}return ret}function traverseStack(args){if(!args||!args.callee||!args.callee.name){return[null,"",""]}var funstr=args.callee.toString();var funcname=args.callee.name;var str="(";var first=true;for(var i in args){var a=args[i];if(!first){str+=", "}first=false;if(typeof a=="number"||typeof a=="string"){str+=a}else{str+="("+typeof a+")"}}str+=")";var caller=args.callee.caller;args=caller?caller.arguments:[];if(first)str="";return[args,funcname,str]}function warnOnce(text){if(!warnOnce.shown)warnOnce.shown={};if(!warnOnce.shown[text]){warnOnce.shown[text]=1;err(text)}}function _emscripten_get_callstack_js(flags){var callstack=jsStackTrace();var iThisFunc=callstack.lastIndexOf("_emscripten_log");var iThisFunc2=callstack.lastIndexOf("_emscripten_get_callstack");var iNextLine=callstack.indexOf("\n",Math.max(iThisFunc,iThisFunc2))+1;callstack=callstack.slice(iNextLine);if(flags&32){warnOnce("EM_LOG_DEMANGLE is deprecated; ignoring")}if(flags&8&&typeof emscripten_source_map=="undefined"){warnOnce('Source map information is not available, emscripten_log with EM_LOG_C_STACK will be ignored. Build with "--pre-js $EMSCRIPTEN/src/emscripten-source-map.min.js" linker flag to add source map loading to code.');flags^=8;flags|=16}var stack_args=null;if(flags&128){stack_args=traverseStack(arguments);while(stack_args[1].includes("_emscripten_"))stack_args=traverseStack(stack_args[0])}var lines=callstack.split("\n");callstack="";var newFirefoxRe=new RegExp("\\s*(.*?)@(.*?):([0-9]+):([0-9]+)");var firefoxRe=new RegExp("\\s*(.*?)@(.*):(.*)(:(.*))?");var chromeRe=new RegExp("\\s*at (.*?) \\((.*):(.*):(.*)\\)");for(var l in lines){var line=lines[l];var symbolName="";var file="";var lineno=0;var column=0;var parts=chromeRe.exec(line);if(parts&&parts.length==5){symbolName=parts[1];file=parts[2];lineno=parts[3];column=parts[4]}else{parts=newFirefoxRe.exec(line);if(!parts)parts=firefoxRe.exec(line);if(parts&&parts.length>=4){symbolName=parts[1];file=parts[2];lineno=parts[3];column=parts[4]|0}else{callstack+=line+"\n";continue}}var haveSourceMap=false;if(flags&8){var orig=emscripten_source_map.originalPositionFor({line:lineno,column:column});haveSourceMap=orig&&orig.source;if(haveSourceMap){if(flags&64){orig.source=orig.source.substring(orig.source.replace(/\\/g,"/").lastIndexOf("/")+1)}callstack+=" at "+symbolName+" ("+orig.source+":"+orig.line+":"+orig.column+")\n"}}if(flags&16||!haveSourceMap){if(flags&64){file=file.substring(file.replace(/\\/g,"/").lastIndexOf("/")+1)}callstack+=(haveSourceMap?" = "+symbolName:" at "+symbolName)+" ("+file+":"+lineno+":"+column+")\n"}if(flags&128&&stack_args[0]){if(stack_args[1]==symbolName&&stack_args[2].length>0){callstack=callstack.replace(/\s+$/,"");callstack+=" with values: "+stack_args[1]+stack_args[2]+"\n"}stack_args=traverseStack(stack_args[0])}}callstack=callstack.replace(/\s+$/,"");return callstack}function _emscripten_log_js(flags,str){if(flags&24){str=str.replace(/\s+$/,"");str+=(str.length>0?"\n":"")+_emscripten_get_callstack_js(flags)}if(flags&1){if(flags&4){console.error(str)}else if(flags&2){console.warn(str)}else if(flags&512){console.info(str)}else if(flags&256){console.debug(str)}else{console.log(str)}}else if(flags&6){err(str)}else{out(str)}}function _emscripten_log(flags,format,varargs){var result=formatString(format,varargs);var str=UTF8ArrayToString(result,0);_emscripten_log_js(flags,str)}function abortOnCannotGrowMemory(requestedSize){abort("OOM")}function _emscripten_resize_heap(requestedSize){var oldSize=HEAPU8.length;requestedSize=requestedSize>>>0;abortOnCannotGrowMemory(requestedSize)}var ENV={};function getExecutableName(){return thisProgram||"./this.program"}function getEnvStrings(){if(!getEnvStrings.strings){var lang=(typeof navigator=="object"&&navigator.languages&&navigator.languages[0]||"C").replace("-","_")+".UTF-8";var env={"USER":"web_user","LOGNAME":"web_user","PATH":"/","PWD":"/","HOME":"/home/web_user","LANG":lang,"_":getExecutableName()};for(var x in ENV){if(ENV[x]===undefined)delete env[x];else env[x]=ENV[x]}var strings=[];for(var x in env){strings.push(x+"="+env[x])}getEnvStrings.strings=strings}return getEnvStrings.strings}function writeAsciiToMemory(str,buffer,dontAddNull){for(var i=0;i>0]=str.charCodeAt(i)}if(!dontAddNull)HEAP8[buffer>>0]=0}function _environ_get(__environ,environ_buf){var bufSize=0;getEnvStrings().forEach(function(string,i){var ptr=environ_buf+bufSize;HEAPU32[__environ+i*4>>2]=ptr;writeAsciiToMemory(string,ptr);bufSize+=string.length+1});return 0}function _environ_sizes_get(penviron_count,penviron_buf_size){var strings=getEnvStrings();HEAPU32[penviron_count>>2]=strings.length;var bufSize=0;strings.forEach(function(string){bufSize+=string.length+1});HEAPU32[penviron_buf_size>>2]=bufSize;return 0}function _fd_close(fd){return 52}function _fd_read(fd,iov,iovcnt,pnum){return 52}function _fd_seek(fd,offset_low,offset_high,whence,newOffset){return 70}var printCharBuffers=[null,[],[]];function printChar(stream,curr){var buffer=printCharBuffers[stream];if(curr===0||curr===10){(stream===1?out:err)(UTF8ArrayToString(buffer,0));buffer.length=0}else{buffer.push(curr)}}function _fd_write(fd,iov,iovcnt,pnum){var num=0;for(var i=0;i>2];var len=HEAPU32[iov+4>>2];iov+=8;for(var j=0;j>2]=num;return 0}var tempRet0=0;function getTempRet0(){return tempRet0}var _getTempRet0=getTempRet0;function getRandomDevice(){if(typeof crypto=="object"&&typeof crypto["getRandomValues"]=="function"){var randomBuffer=new Uint8Array(1);return()=>{crypto.getRandomValues(randomBuffer);return randomBuffer[0]}}else return()=>abort("randomDevice")}function _getentropy(buffer,size){if(!_getentropy.randomDevice){_getentropy.randomDevice=getRandomDevice()}for(var i=0;i>0]=_getentropy.randomDevice()}return 0}function _llvm_eh_typeid_for(type){return type}function setTempRet0(val){tempRet0=val}var _setTempRet0=setTempRet0;function __isLeapYear(year){return year%4===0&&(year%100!==0||year%400===0)}function __arraySum(array,index){var sum=0;for(var i=0;i<=index;sum+=array[i++]){}return sum}var __MONTH_DAYS_LEAP=[31,29,31,30,31,30,31,31,30,31,30,31];var __MONTH_DAYS_REGULAR=[31,28,31,30,31,30,31,31,30,31,30,31];function __addDays(date,days){var newDate=new Date(date.getTime());while(days>0){var leap=__isLeapYear(newDate.getFullYear());var currentMonth=newDate.getMonth();var daysInCurrentMonth=(leap?__MONTH_DAYS_LEAP:__MONTH_DAYS_REGULAR)[currentMonth];if(days>daysInCurrentMonth-newDate.getDate()){days-=daysInCurrentMonth-newDate.getDate()+1;newDate.setDate(1);if(currentMonth<11){newDate.setMonth(currentMonth+1)}else{newDate.setMonth(0);newDate.setFullYear(newDate.getFullYear()+1)}}else{newDate.setDate(newDate.getDate()+days);return newDate}}return newDate}function _strftime(s,maxsize,format,tm){var tm_zone=HEAP32[tm+40>>2];var date={tm_sec:HEAP32[tm>>2],tm_min:HEAP32[tm+4>>2],tm_hour:HEAP32[tm+8>>2],tm_mday:HEAP32[tm+12>>2],tm_mon:HEAP32[tm+16>>2],tm_year:HEAP32[tm+20>>2],tm_wday:HEAP32[tm+24>>2],tm_yday:HEAP32[tm+28>>2],tm_isdst:HEAP32[tm+32>>2],tm_gmtoff:HEAP32[tm+36>>2],tm_zone:tm_zone?UTF8ToString(tm_zone):""};var pattern=UTF8ToString(format);var EXPANSION_RULES_1={"%c":"%a %b %d %H:%M:%S %Y","%D":"%m/%d/%y","%F":"%Y-%m-%d","%h":"%b","%r":"%I:%M:%S %p","%R":"%H:%M","%T":"%H:%M:%S","%x":"%m/%d/%y","%X":"%H:%M:%S","%Ec":"%c","%EC":"%C","%Ex":"%m/%d/%y","%EX":"%H:%M:%S","%Ey":"%y","%EY":"%Y","%Od":"%d","%Oe":"%e","%OH":"%H","%OI":"%I","%Om":"%m","%OM":"%M","%OS":"%S","%Ou":"%u","%OU":"%U","%OV":"%V","%Ow":"%w","%OW":"%W","%Oy":"%y"};for(var rule in EXPANSION_RULES_1){pattern=pattern.replace(new RegExp(rule,"g"),EXPANSION_RULES_1[rule])}var WEEKDAYS=["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"];var MONTHS=["January","February","March","April","May","June","July","August","September","October","November","December"];function leadingSomething(value,digits,character){var str=typeof value=="number"?value.toString():value||"";while(str.length0?1:0}var compare;if((compare=sgn(date1.getFullYear()-date2.getFullYear()))===0){if((compare=sgn(date1.getMonth()-date2.getMonth()))===0){compare=sgn(date1.getDate()-date2.getDate())}}return compare}function getFirstWeekStartDate(janFourth){switch(janFourth.getDay()){case 0:return new Date(janFourth.getFullYear()-1,11,29);case 1:return janFourth;case 2:return new Date(janFourth.getFullYear(),0,3);case 3:return new Date(janFourth.getFullYear(),0,2);case 4:return new Date(janFourth.getFullYear(),0,1);case 5:return new Date(janFourth.getFullYear()-1,11,31);case 6:return new Date(janFourth.getFullYear()-1,11,30)}}function getWeekBasedYear(date){var thisDate=__addDays(new Date(date.tm_year+1900,0,1),date.tm_yday);var janFourthThisYear=new Date(thisDate.getFullYear(),0,4);var janFourthNextYear=new Date(thisDate.getFullYear()+1,0,4);var firstWeekStartThisYear=getFirstWeekStartDate(janFourthThisYear);var firstWeekStartNextYear=getFirstWeekStartDate(janFourthNextYear);if(compareByDay(firstWeekStartThisYear,thisDate)<=0){if(compareByDay(firstWeekStartNextYear,thisDate)<=0){return thisDate.getFullYear()+1}return thisDate.getFullYear()}return thisDate.getFullYear()-1}var EXPANSION_RULES_2={"%a":function(date){return WEEKDAYS[date.tm_wday].substring(0,3)},"%A":function(date){return WEEKDAYS[date.tm_wday]},"%b":function(date){return MONTHS[date.tm_mon].substring(0,3)},"%B":function(date){return MONTHS[date.tm_mon]},"%C":function(date){var year=date.tm_year+1900;return leadingNulls(year/100|0,2)},"%d":function(date){return leadingNulls(date.tm_mday,2)},"%e":function(date){return leadingSomething(date.tm_mday,2," ")},"%g":function(date){return getWeekBasedYear(date).toString().substring(2)},"%G":function(date){return getWeekBasedYear(date)},"%H":function(date){return leadingNulls(date.tm_hour,2)},"%I":function(date){var twelveHour=date.tm_hour;if(twelveHour==0)twelveHour=12;else if(twelveHour>12)twelveHour-=12;return leadingNulls(twelveHour,2)},"%j":function(date){return leadingNulls(date.tm_mday+__arraySum(__isLeapYear(date.tm_year+1900)?__MONTH_DAYS_LEAP:__MONTH_DAYS_REGULAR,date.tm_mon-1),3)},"%m":function(date){return leadingNulls(date.tm_mon+1,2)},"%M":function(date){return leadingNulls(date.tm_min,2)},"%n":function(){return"\n"},"%p":function(date){if(date.tm_hour>=0&&date.tm_hour<12){return"AM"}return"PM"},"%S":function(date){return leadingNulls(date.tm_sec,2)},"%t":function(){return"\t"},"%u":function(date){return date.tm_wday||7},"%U":function(date){var days=date.tm_yday+7-date.tm_wday;return leadingNulls(Math.floor(days/7),2)},"%V":function(date){var val=Math.floor((date.tm_yday+7-(date.tm_wday+6)%7)/7);if((date.tm_wday+371-date.tm_yday-2)%7<=2){val++}if(!val){val=52;var dec31=(date.tm_wday+7-date.tm_yday-1)%7;if(dec31==4||dec31==5&&__isLeapYear(date.tm_year%400-1)){val++}}else if(val==53){var jan1=(date.tm_wday+371-date.tm_yday)%7;if(jan1!=4&&(jan1!=3||!__isLeapYear(date.tm_year)))val=1}return leadingNulls(val,2)},"%w":function(date){return date.tm_wday},"%W":function(date){var days=date.tm_yday+7-(date.tm_wday+6)%7;return leadingNulls(Math.floor(days/7),2)},"%y":function(date){return(date.tm_year+1900).toString().substring(2)},"%Y":function(date){return date.tm_year+1900},"%z":function(date){var off=date.tm_gmtoff;var ahead=off>=0;off=Math.abs(off)/60;off=off/60*100+off%60;return(ahead?"+":"-")+String("0000"+off).slice(-4)},"%Z":function(date){return date.tm_zone},"%%":function(){return"%"}};pattern=pattern.replace(/%%/g,"\0\0");for(var rule in EXPANSION_RULES_2){if(pattern.includes(rule)){pattern=pattern.replace(new RegExp(rule,"g"),EXPANSION_RULES_2[rule](date))}}pattern=pattern.replace(/\0\0/g,"%");var bytes=intArrayFromString(pattern,false);if(bytes.length>maxsize){return 0}writeArrayToMemory(bytes,s);return bytes.length-1}function _strftime_l(s,maxsize,format,tm){return _strftime(s,maxsize,format,tm)}var ALLOC_NORMAL=0;var ALLOC_STACK=1;function allocate(slab,allocator){var ret;if(allocator==ALLOC_STACK){ret=stackAlloc(slab.length)}else{ret=_malloc(slab.length)}if(!slab.subarray&&!slab.slice){slab=new Uint8Array(slab)}HEAPU8.set(slab,ret);return ret}var asmLibraryArg={"i":___cxa_allocate_exception,"s":___cxa_begin_catch,"v":___cxa_end_catch,"b":___cxa_find_matching_catch_2,"j":___cxa_find_matching_catch_3,"D":___cxa_find_matching_catch_4,"K":___cxa_find_matching_catch_6,"x":___cxa_free_exception,"N":___cxa_rethrow,"m":___cxa_throw,"ja":___cxa_uncaught_exceptions,"e":___resumeException,"S":___syscall_fcntl64,"sa":___syscall_fstat64,"la":___syscall_getdents64,"Q":___syscall_ioctl,"pa":___syscall_lstat64,"qa":___syscall_newfstatat,"R":___syscall_openat,"ra":___syscall_stat64,"H":__emscripten_date_now,"ua":__emscripten_get_now_is_monotonic,"va":__gmtime_js,"wa":__localtime_js,"ma":__munmap_js,"xa":__tzset_js,"t":_abort,"I":_emscripten_asm_const_int,"ta":_emscripten_get_now,"T":_emscripten_log,"ka":_emscripten_resize_heap,"na":_environ_get,"oa":_environ_sizes_get,"F":_fd_close,"O":_fd_read,"X":_fd_seek,"P":_fd_write,"a":_getTempRet0,"ga":_getentropy,"L":invoke_diii,"M":invoke_fiii,"r":invoke_i,"h":invoke_ii,"c":invoke_iii,"l":invoke_iiii,"k":invoke_iiiii,"ia":invoke_iiiiid,"o":invoke_iiiiii,"q":invoke_iiiiiii,"u":invoke_iiiiiiii,"ya":invoke_iiiiiiiii,"J":invoke_iiiiiiiiii,"G":invoke_iiiiiiiiiiii,"V":invoke_iiiiij,"Z":invoke_iiiij,"da":invoke_iiiijii,"fa":invoke_iij,"ca":invoke_iiji,"W":invoke_j,"ea":invoke_ji,"ba":invoke_jii,"U":invoke_jiiii,"Y":invoke_jijj,"p":invoke_v,"d":invoke_vi,"f":invoke_vii,"g":invoke_viii,"n":invoke_viiii,"w":invoke_viiiii,"z":invoke_viiiiii,"A":invoke_viiiiiii,"B":invoke_viiiiiiiiii,"E":invoke_viiiiiiiiiiiiiii,"_":invoke_viij,"$":invoke_vij,"aa":invoke_viji,"y":_llvm_eh_typeid_for,"C":_setTempRet0,"ha":_strftime_l};var asm=createWasm();var ___wasm_call_ctors=Module["___wasm_call_ctors"]=function(){return(___wasm_call_ctors=Module["___wasm_call_ctors"]=Module["asm"]["Aa"]).apply(null,arguments)};var _emulate=Module["_emulate"]=function(){return(_emulate=Module["_emulate"]=Module["asm"]["Ba"]).apply(null,arguments)};var _free=Module["_free"]=function(){return(_free=Module["_free"]=Module["asm"]["Ca"]).apply(null,arguments)};var _run_get_method=Module["_run_get_method"]=function(){return(_run_get_method=Module["_run_get_method"]=Module["asm"]["Da"]).apply(null,arguments)};var _malloc=Module["_malloc"]=function(){return(_malloc=Module["_malloc"]=Module["asm"]["Fa"]).apply(null,arguments)};var _setThrew=Module["_setThrew"]=function(){return(_setThrew=Module["_setThrew"]=Module["asm"]["Ga"]).apply(null,arguments)};var stackSave=Module["stackSave"]=function(){return(stackSave=Module["stackSave"]=Module["asm"]["Ha"]).apply(null,arguments)};var stackRestore=Module["stackRestore"]=function(){return(stackRestore=Module["stackRestore"]=Module["asm"]["Ia"]).apply(null,arguments)};var stackAlloc=Module["stackAlloc"]=function(){return(stackAlloc=Module["stackAlloc"]=Module["asm"]["Ja"]).apply(null,arguments)};var ___cxa_can_catch=Module["___cxa_can_catch"]=function(){return(___cxa_can_catch=Module["___cxa_can_catch"]=Module["asm"]["Ka"]).apply(null,arguments)};var ___cxa_is_pointer_type=Module["___cxa_is_pointer_type"]=function(){return(___cxa_is_pointer_type=Module["___cxa_is_pointer_type"]=Module["asm"]["La"]).apply(null,arguments)};var dynCall_iij=Module["dynCall_iij"]=function(){return(dynCall_iij=Module["dynCall_iij"]=Module["asm"]["Ma"]).apply(null,arguments)};var dynCall_ji=Module["dynCall_ji"]=function(){return(dynCall_ji=Module["dynCall_ji"]=Module["asm"]["Na"]).apply(null,arguments)};var dynCall_iiiijii=Module["dynCall_iiiijii"]=function(){return(dynCall_iiiijii=Module["dynCall_iiiijii"]=Module["asm"]["Oa"]).apply(null,arguments)};var dynCall_iiji=Module["dynCall_iiji"]=function(){return(dynCall_iiji=Module["dynCall_iiji"]=Module["asm"]["Pa"]).apply(null,arguments)};var dynCall_jii=Module["dynCall_jii"]=function(){return(dynCall_jii=Module["dynCall_jii"]=Module["asm"]["Qa"]).apply(null,arguments)};var dynCall_viji=Module["dynCall_viji"]=function(){return(dynCall_viji=Module["dynCall_viji"]=Module["asm"]["Ra"]).apply(null,arguments)};var dynCall_vij=Module["dynCall_vij"]=function(){return(dynCall_vij=Module["dynCall_vij"]=Module["asm"]["Sa"]).apply(null,arguments)};var dynCall_viij=Module["dynCall_viij"]=function(){return(dynCall_viij=Module["dynCall_viij"]=Module["asm"]["Ta"]).apply(null,arguments)};var dynCall_iiiij=Module["dynCall_iiiij"]=function(){return(dynCall_iiiij=Module["dynCall_iiiij"]=Module["asm"]["Ua"]).apply(null,arguments)};var dynCall_jijj=Module["dynCall_jijj"]=function(){return(dynCall_jijj=Module["dynCall_jijj"]=Module["asm"]["Va"]).apply(null,arguments)};var dynCall_j=Module["dynCall_j"]=function(){return(dynCall_j=Module["dynCall_j"]=Module["asm"]["Wa"]).apply(null,arguments)};var dynCall_iiiiij=Module["dynCall_iiiiij"]=function(){return(dynCall_iiiiij=Module["dynCall_iiiiij"]=Module["asm"]["Xa"]).apply(null,arguments)};var dynCall_jiiii=Module["dynCall_jiiii"]=function(){return(dynCall_jiiii=Module["dynCall_jiiii"]=Module["asm"]["Ya"]).apply(null,arguments)};function invoke_ii(index,a1){var sp=stackSave();try{return getWasmTableEntry(index)(a1)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_vi(index,a1){var sp=stackSave();try{getWasmTableEntry(index)(a1)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_vii(index,a1,a2){var sp=stackSave();try{getWasmTableEntry(index)(a1,a2)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_viii(index,a1,a2,a3){var sp=stackSave();try{getWasmTableEntry(index)(a1,a2,a3)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_viiiii(index,a1,a2,a3,a4,a5){var sp=stackSave();try{getWasmTableEntry(index)(a1,a2,a3,a4,a5)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_iii(index,a1,a2){var sp=stackSave();try{return getWasmTableEntry(index)(a1,a2)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_i(index){var sp=stackSave();try{return getWasmTableEntry(index)()}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_v(index){var sp=stackSave();try{getWasmTableEntry(index)()}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_iiii(index,a1,a2,a3){var sp=stackSave();try{return getWasmTableEntry(index)(a1,a2,a3)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_iiiiii(index,a1,a2,a3,a4,a5){var sp=stackSave();try{return getWasmTableEntry(index)(a1,a2,a3,a4,a5)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_viiii(index,a1,a2,a3,a4){var sp=stackSave();try{getWasmTableEntry(index)(a1,a2,a3,a4)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_iiiii(index,a1,a2,a3,a4){var sp=stackSave();try{return getWasmTableEntry(index)(a1,a2,a3,a4)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_iiiiiii(index,a1,a2,a3,a4,a5,a6){var sp=stackSave();try{return getWasmTableEntry(index)(a1,a2,a3,a4,a5,a6)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_iiiiiiii(index,a1,a2,a3,a4,a5,a6,a7){var sp=stackSave();try{return getWasmTableEntry(index)(a1,a2,a3,a4,a5,a6,a7)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_iiiiiiiiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9){var sp=stackSave();try{return getWasmTableEntry(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_iiiiiiiiiiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11){var sp=stackSave();try{return getWasmTableEntry(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_iiiiiiiii(index,a1,a2,a3,a4,a5,a6,a7,a8){var sp=stackSave();try{return getWasmTableEntry(index)(a1,a2,a3,a4,a5,a6,a7,a8)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_viiiiiii(index,a1,a2,a3,a4,a5,a6,a7){var sp=stackSave();try{getWasmTableEntry(index)(a1,a2,a3,a4,a5,a6,a7)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_viiiiii(index,a1,a2,a3,a4,a5,a6){var sp=stackSave();try{getWasmTableEntry(index)(a1,a2,a3,a4,a5,a6)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_iiiiid(index,a1,a2,a3,a4,a5){var sp=stackSave();try{return getWasmTableEntry(index)(a1,a2,a3,a4,a5)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_fiii(index,a1,a2,a3){var sp=stackSave();try{return getWasmTableEntry(index)(a1,a2,a3)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_diii(index,a1,a2,a3){var sp=stackSave();try{return getWasmTableEntry(index)(a1,a2,a3)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_viiiiiiiiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10){var sp=stackSave();try{getWasmTableEntry(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_viiiiiiiiiiiiiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15){var sp=stackSave();try{getWasmTableEntry(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_iij(index,a1,a2,a3){var sp=stackSave();try{return dynCall_iij(index,a1,a2,a3)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_ji(index,a1){var sp=stackSave();try{return dynCall_ji(index,a1)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_iiiijii(index,a1,a2,a3,a4,a5,a6,a7){var sp=stackSave();try{return dynCall_iiiijii(index,a1,a2,a3,a4,a5,a6,a7)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_iiji(index,a1,a2,a3,a4){var sp=stackSave();try{return dynCall_iiji(index,a1,a2,a3,a4)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_jii(index,a1,a2){var sp=stackSave();try{return dynCall_jii(index,a1,a2)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_viji(index,a1,a2,a3,a4){var sp=stackSave();try{dynCall_viji(index,a1,a2,a3,a4)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_vij(index,a1,a2,a3){var sp=stackSave();try{dynCall_vij(index,a1,a2,a3)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_viij(index,a1,a2,a3,a4){var sp=stackSave();try{dynCall_viij(index,a1,a2,a3,a4)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_iiiij(index,a1,a2,a3,a4,a5){var sp=stackSave();try{return dynCall_iiiij(index,a1,a2,a3,a4,a5)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_jijj(index,a1,a2,a3,a4,a5){var sp=stackSave();try{return dynCall_jijj(index,a1,a2,a3,a4,a5)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_j(index){var sp=stackSave();try{return dynCall_j(index)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_iiiiij(index,a1,a2,a3,a4,a5,a6){var sp=stackSave();try{return dynCall_iiiiij(index,a1,a2,a3,a4,a5,a6)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}function invoke_jiiii(index,a1,a2,a3,a4){var sp=stackSave();try{return dynCall_jiiii(index,a1,a2,a3,a4)}catch(e){stackRestore(sp);if(e!==e+0)throw e;_setThrew(1,0)}}Module["UTF8ToString"]=UTF8ToString;Module["stringToUTF8"]=stringToUTF8;Module["lengthBytesUTF8"]=lengthBytesUTF8;Module["ALLOC_NORMAL"]=ALLOC_NORMAL;Module["allocate"]=allocate;var calledRun;dependenciesFulfilled=function runCaller(){if(!calledRun)run();if(!calledRun)dependenciesFulfilled=runCaller};function run(args){args=args||arguments_;if(runDependencies>0){return}preRun();if(runDependencies>0){return}function doRun(){if(calledRun)return;calledRun=true;Module["calledRun"]=true;if(ABORT)return;initRuntime();readyPromiseResolve(Module);if(Module["onRuntimeInitialized"])Module["onRuntimeInitialized"]();postRun()}if(Module["setStatus"]){Module["setStatus"]("Running...");setTimeout(function(){setTimeout(function(){Module["setStatus"]("")},1);doRun()},1)}else{doRun()}}if(Module["preInit"]){if(typeof Module["preInit"]=="function")Module["preInit"]=[Module["preInit"]];while(Module["preInit"].length>0){Module["preInit"].pop()()}}run(); 10 | 11 | 12 | return EmulatorModule.ready 13 | } 14 | ); 15 | })(); 16 | if (typeof exports === 'object' && typeof module === 'object') 17 | module.exports = EmulatorModule; 18 | else if (typeof define === 'function' && define['amd']) 19 | define([], function() { return EmulatorModule; }); 20 | else if (typeof exports === 'object') 21 | exports["EmulatorModule"] = EmulatorModule; 22 | -------------------------------------------------------------------------------- /src/bindings/wasm/emulator-emscripten.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tact-lang/tact-emulator/8168e68e410543f0a60b99aaffb6e7344c758d6f/src/bindings/wasm/emulator-emscripten.wasm -------------------------------------------------------------------------------- /src/coverage/coverage.ts: -------------------------------------------------------------------------------- 1 | export function isCoverageEnabled() { 2 | let cov = typeof globalThis !== 'undefined' && (globalThis as any).__ton_coverage__; 3 | return !!cov; 4 | } 5 | 6 | export function reportCoverage(logs: string) { 7 | let cov = typeof globalThis !== 'undefined' && (globalThis as any).__ton_coverage__; 8 | if (cov) { 9 | cov(logs); 10 | } 11 | } -------------------------------------------------------------------------------- /src/emulator/ContractExecutor.spec.ts: -------------------------------------------------------------------------------- 1 | import { beginCell, Cell, toNano } from "@ton/core"; 2 | import { randomAddress } from "../utils/randomAddress"; 3 | import { ContractExecutor } from "./ContractExecutor"; 4 | import { ContractSystem } from "./ContractSystem"; 5 | 6 | describe('ContractExecutor', () => { 7 | it('should execute simple get methods', async () => { 8 | const echoCode = 'te6ccgECIgEAAoYAART/APSkE/S88sgLAQIBYgIDAgLLBAUCAVgeHwIBIAYHAgFIFBUCASAICQIBIA0OAgFICgsAI/N5EA5MmQt1nLALeRLOZk9BjAHfO37cCHXScIflTAg1wsf3gLQ0wMBcbDAAZF/kXDiAfpAIlBmbwT4YQKRW+DAAI4uINdJwh+OJu1E0NQB+GKBAQHXAAExAYAg1yHwFMj4QgHMAQGBAQHPAMntVNsx4N7tRNDUAfhigQEB1wABMQHwFYAwACwgbvLQgIAAeyPhCAcwBAYEBAc8Aye1UALvRBrpRDrpMuQYQARYQBYxyUBt5FAP5FnmNWBUILVgSiq2wQQYQBOEFUBCuuMKBnniyAKbyy3gSmg0OEATOQAt4EoIlDVAUcJGJnhAEzqGGgQa6UQ66TJOBBxcXQvgcAgEgDxAAFVlH8BygDgcAHKAIAgEgERIB9zIcQHKAVAH8A1wAcoCUAXPFlAD+gJwAcpoI26zJW6zsY49f/ANyHDwDXDwDSRus5l/8A0E8AFQBMyVNANw8A3iJG6zmX/wDQTwAVAEzJU0A3DwDeJw8A0Cf/ANAslYzJYzMwFw8A3iIW6zmH/wDQHwAQHMlDFw8A3iyQGATACU+EFvJBAjXwN/AnCAQlhtbfAOgAAT7AAIBIBYXAgFIHB0CASAYGQIBIBobAAsyAHPFsmAALR/yAGUcAHLH95vAAFvjG1vjAHwCPAHgABkcAHIzAEBgQEBzwDJgAAUMaSAACTwEfAPgAAk8BDwD4AIBICAhACe4Ni7UTQ1AH4YoEBAdcAATEB8BOAAJtaseAlAATbd6ME4LnYerpZXPY9CdhzrJUKNs0E4TusalpWyPlmRadeW/vixHMA=='; 9 | let system = await ContractSystem.create(); 10 | let contract = await ContractExecutor.create({ 11 | code: Cell.fromBoc(Buffer.from(echoCode, 'base64'))[0], 12 | data: beginCell() 13 | .storeRef(Cell.EMPTY) 14 | .storeInt(0, 257) 15 | .endCell() 16 | }, system); 17 | 18 | // Get method 19 | let res = await contract.get('hello', [{ type: 'int', value: 12312312n }]); 20 | if (res.success !== true) { 21 | throw new Error('get failed'); 22 | } 23 | expect(res.stack.readNumber()).toBe(12312313); 24 | 25 | // Receive method 26 | let tx = await contract.receive({ 27 | info: { 28 | type: 'internal', 29 | src: randomAddress('address-1'), 30 | dest: contract.address, 31 | value: { coins: toNano(1) }, 32 | bounce: false, 33 | ihrDisabled: true, 34 | createdLt: 0n, 35 | createdAt: 0, 36 | bounced: false, 37 | ihrFee: 0n, 38 | forwardFee: 0n, 39 | }, 40 | body: beginCell() 41 | .endCell() 42 | }); 43 | console.warn(tx); 44 | }); 45 | }); -------------------------------------------------------------------------------- /src/emulator/ContractExecutor.ts: -------------------------------------------------------------------------------- 1 | import { AsyncLock } from "teslabot"; 2 | import { Account, Address, beginCell, Cell, ContractState, loadShardAccount, loadTransaction, Message, parseTuple, storeMessage, storeShardAccount, TupleItem, TupleReader } from "@ton/core"; 3 | import { isCoverageEnabled, reportCoverage } from "../coverage/coverage"; 4 | import { createAccount } from "../utils/createAccount"; 5 | import { createEmptyAccount } from "../utils/createEmptyAccount"; 6 | import { getMethodId } from "../utils/getMethodId"; 7 | import { Maybe } from "../utils/maybe"; 8 | import { ContractSystem } from "./ContractSystem"; 9 | import { Verbosity } from "./Verbosity"; 10 | 11 | function bigIntToBuffer(v: bigint, bytes: number) { 12 | return beginCell().storeUint(v, bytes * 8).endCell().beginParse().loadBuffer(bytes); 13 | } 14 | 15 | export type GetMethodResult = { 16 | success: true 17 | gasUsed: bigint, 18 | stack: TupleReader, 19 | exitCode: number, 20 | logs: string, 21 | } | { 22 | success: false, 23 | error: string 24 | } 25 | 26 | /** 27 | * Contract executor is wrapper around contract state 28 | */ 29 | export class ContractExecutor { 30 | 31 | /** 32 | * Create contract empty contract executor 33 | * @param address contract address 34 | * @param system contract system 35 | * @returns executor instance 36 | */ 37 | static createEmpty(address: Address, system: ContractSystem) { 38 | return new ContractExecutor(createEmptyAccount(address), system); 39 | } 40 | 41 | /** 42 | * Create contract executor from account state 43 | * @param args code, data, address and balance 44 | * @param system contract system 45 | * @returns executor instance 46 | */ 47 | static async create(args: { code: Cell, data: Cell, address?: Address, balance?: bigint }, system: ContractSystem) { 48 | return new ContractExecutor(createAccount(args), system); 49 | } 50 | 51 | readonly system: ContractSystem; 52 | readonly address: Address; 53 | #state: Account; 54 | #balance: bigint; 55 | #last: { lt: bigint, hash: bigint } = { lt: 0n, hash: 0n }; 56 | #lock = new AsyncLock(); 57 | #index = 0; 58 | #verbosity: Verbosity | null = null; 59 | #name: string | null = null; 60 | 61 | private constructor(state: Account, system: ContractSystem) { 62 | this.system = system; 63 | this.address = state.addr; 64 | this.#state = state; 65 | this.#balance = this.#state.storage.balance.coins; 66 | } 67 | 68 | /** 69 | * Returns optional address name 70 | */ 71 | get name() { 72 | return this.#name; 73 | } 74 | 75 | /** 76 | * Sets optional address name 77 | */ 78 | set name(v: string | null) { 79 | this.#name = v; 80 | } 81 | 82 | /** 83 | * Returns contract verbosity level 84 | */ 85 | get verbosity() { 86 | return this.#verbosity; 87 | } 88 | 89 | /** 90 | * Sets contract verbosity level 91 | */ 92 | set verbosity(v: Verbosity | null) { 93 | this.#verbosity = v; 94 | } 95 | 96 | /** 97 | * Returns current state of a contract 98 | */ 99 | get state(): ContractState { 100 | let balance = this.#balance; 101 | let last: { 102 | lt: bigint; 103 | hash: Buffer; 104 | } | null = null; 105 | if (this.#last) { 106 | last = { 107 | lt: this.#last.lt, 108 | hash: bigIntToBuffer(this.#last.hash, 32), 109 | } 110 | } 111 | if (this.#state.storage.state.type === 'active') { 112 | return { 113 | balance, 114 | last, 115 | state: { 116 | type: 'active', 117 | code: this.#state.storage.state.state.code ? this.#state.storage.state.state.code.toBoc() : null, 118 | data: this.#state.storage.state.state.data ? this.#state.storage.state.state.data.toBoc() : null, 119 | } 120 | } 121 | } else if (this.#state.storage.state.type === 'uninit') { 122 | return { 123 | balance, 124 | last, 125 | state: { 126 | type: 'uninit', 127 | } 128 | } 129 | } else if (this.#state.storage.state.type === 'frozen') { 130 | return { 131 | balance, 132 | last, 133 | state: { 134 | type: 'frozen', 135 | stateHash: bigIntToBuffer(this.#state.storage.state.stateHash, 32), 136 | } 137 | } 138 | } else { 139 | throw new Error('Unknown contract state'); 140 | } 141 | } 142 | 143 | /** 144 | * Returns current balance of a contract 145 | */ 146 | get balance() { 147 | return this.#balance; 148 | } 149 | 150 | /** 151 | * Sets current balance of a contract 152 | */ 153 | set balance(v: bigint) { 154 | this.#balance = v; 155 | this.#state = { 156 | ...this.#state, 157 | storage: { 158 | ...this.#state.storage, 159 | balance: { ...this.#state.storage.balance, coins: v } 160 | } 161 | } 162 | } 163 | 164 | /** 165 | * Replaces current contract state with new one 166 | * @param code contract code 167 | * @param data contract data 168 | * @param balance contract balance 169 | */ 170 | override = (code: Cell, data: Cell, balance: bigint) => { 171 | this.#balance = balance; 172 | this.#state = createAccount({ code, data, address: this.address, balance }); 173 | this.#last = { lt: 0n, hash: 0n }; 174 | } 175 | 176 | get = async (method: string | number, stack?: Maybe): Promise => { 177 | return await this.#lock.inLock(async () => { 178 | 179 | // Check contract state 180 | if (this.#state.storage.state.type !== 'active') { 181 | throw new Error('Contract is not active'); 182 | } 183 | if (!this.#state.storage.state.state.code) { 184 | throw new Error('Contract has no code'); 185 | } 186 | if (!this.#state.storage.state.state.data) { 187 | throw new Error('Contract has no data'); 188 | } 189 | 190 | // Resolve method id 191 | let methodId: number; 192 | if (typeof method === 'string') { 193 | methodId = getMethodId(method); 194 | } else { 195 | methodId = method; 196 | } 197 | 198 | // Resolve verbosity 199 | let verbosity = this.verbosity !== null ? this.verbosity : this.system.globalVerbosity; 200 | if (isCoverageEnabled()) { 201 | verbosity = Math.max(Verbosity.INFO, verbosity); // Force warning verbosity for coverage 202 | } 203 | 204 | let result = await this.system.bindings.runGetMethod({ 205 | verbosity: verbosity, 206 | address: this.address, 207 | code: this.#state.storage.state.state.code, 208 | data: this.#state.storage.state.state.data, 209 | balance: this.#balance, 210 | unixtime: this.system.now, 211 | randomSeed: Buffer.alloc(32), 212 | gasLimit: 1000000000n, 213 | methodId: methodId, 214 | args: stack || [], 215 | config: this.system.config 216 | }); 217 | if ((result as any).fail) { 218 | return { 219 | success: false, 220 | error: (result as any).message 221 | }; 222 | } 223 | 224 | // Check result 225 | if (!result.output.success) { 226 | return { 227 | success: false, 228 | error: result.output.error 229 | }; 230 | } 231 | 232 | // Report coverage 233 | if (isCoverageEnabled()) { 234 | reportCoverage(result.vmLogs); 235 | } 236 | 237 | // Parse result 238 | let resultStack = parseTuple(Cell.fromBoc(Buffer.from(result.output.stack, 'base64'))[0]); 239 | return { 240 | success: true, 241 | gasUsed: BigInt(result.output.gas_used), 242 | stack: new TupleReader(resultStack), 243 | exitCode: result.output.vm_exit_code, 244 | logs: result.logs 245 | }; 246 | }); 247 | } 248 | 249 | async receive(msg: Message) { 250 | return await this.#lock.inLock(async () => { 251 | if (msg.info.type !== 'internal' && msg.info.type !== 'external-in') { 252 | throw new Error(`Unsupported message type: ${msg.info.type}`); 253 | } 254 | 255 | // Resolve verbosity 256 | let verbosity = this.verbosity !== null ? this.verbosity : this.system.globalVerbosity; 257 | if (isCoverageEnabled()) { 258 | verbosity = Math.max(Verbosity.INFO, verbosity); // Force debug verbosity for coverage 259 | } 260 | 261 | // Execute transaction 262 | let res = await this.system.bindings.transaction({ 263 | config: this.system.config, 264 | libs: null, 265 | verbosity: verbosity, 266 | shardAccount: beginCell().store(storeShardAccount({ account: this.#state, lastTransactionHash: this.#last.hash, lastTransactionLt: this.#last.lt })).endCell(), 267 | message: beginCell().store(storeMessage(msg)).endCell(), 268 | now: this.system.now, 269 | lt: this.system.lt, 270 | randomSeed: Buffer.alloc(32), 271 | }); 272 | if ((res as any).fail) { 273 | throw new Error((res as any).message); 274 | } 275 | 276 | // Report coverage 277 | if (isCoverageEnabled()) { 278 | reportCoverage(res.vmLogs); 279 | } 280 | 281 | // Apply changes 282 | if (res.output.success) { 283 | let src = Cell.fromBoc(Buffer.from(res.output.shard_account, 'base64'))[0]; 284 | let shard = loadShardAccount(src.beginParse()); 285 | if (shard.account) { 286 | this.#state = shard.account!; 287 | this.#balance = shard.account!.storage.balance.coins; 288 | this.#last = { lt: shard.lastTransactionLt, hash: shard.lastTransactionHash }; 289 | } 290 | 291 | // Load transaction 292 | let t = loadTransaction(Cell.fromBoc(Buffer.from(res.output.transaction, 'base64'))[0].beginParse()); 293 | return { 294 | seq: this.#index++, 295 | tx: t, 296 | logs: res.logs 297 | } 298 | } else { 299 | console.warn(res.logs); 300 | throw Error(res.output.error); 301 | } 302 | }); 303 | } 304 | } -------------------------------------------------------------------------------- /src/emulator/ContractSystem.spec.ts: -------------------------------------------------------------------------------- 1 | import { ContractSystem } from "./ContractSystem"; 2 | import { randomAddress } from "../utils/randomAddress"; 3 | import { beginCell, toNano } from "@ton/core"; 4 | 5 | describe('ContractSystem', () => { 6 | it('should send messages', async () => { 7 | 8 | // Create contract system and a wallet 9 | let system = await ContractSystem.create(); 10 | let treasure = system.treasure('treasure'); 11 | let unknownAddress = randomAddress('unknown'); 12 | system.name({ address: unknownAddress }, 'unknown'); 13 | let tracker = system.track(treasure.address); 14 | let logs = system.log(treasure.address); 15 | 16 | // Send a message 17 | await treasure.send({ 18 | to: unknownAddress, 19 | bounce: true, 20 | value: toNano(1), 21 | }); 22 | await system.run(); 23 | 24 | // Send second time 25 | await treasure.send({ 26 | to: unknownAddress, 27 | bounce: true, 28 | value: toNano(1), 29 | body: beginCell().storeUint(0, 32).storeStringTail('Hello world!').endCell() 30 | }); 31 | await system.run(); 32 | 33 | // Events 34 | expect(tracker.collect()).toMatchSnapshot(); 35 | console.warn(logs.collect()); 36 | }); 37 | }); -------------------------------------------------------------------------------- /src/emulator/ContractSystem.ts: -------------------------------------------------------------------------------- 1 | import { Address, Cell, comment, ComputeError, Contract, ContractABI, ContractProvider, external, Message, openContract, toNano, Transaction } from "@ton/core"; 2 | import { EmulatorBindings } from "../bindings/EmulatorBindings"; 3 | import { Tracker } from "../events/tracker"; 4 | import { Logger } from '../logger/logger'; 5 | import { TreasureContract } from "../treasure/Treasure"; 6 | import { AddressSource, resolveAddress } from "../utils/addressSource"; 7 | import { defaultConfig } from "../utils/defaultConfig"; 8 | import { Maybe } from "../utils/maybe"; 9 | import { testKey } from "../utils/testKey"; 10 | import { ContractExecutor } from "./ContractExecutor"; 11 | import { Verbosity } from "./Verbosity"; 12 | import { TypeRegistry } from "@tact-lang/ton-abi"; 13 | 14 | /** 15 | * Contract system is a container for contracts that interact with each other 16 | */ 17 | export class ContractSystem { 18 | 19 | static async create(args?: { config?: Cell, now?: number }) { 20 | return new ContractSystem(args); 21 | } 22 | 23 | #config: Cell; 24 | #now: number; 25 | #lt: bigint; 26 | #bindings: EmulatorBindings; 27 | #contracts: Map; 28 | #abis: Map; 29 | #abiTypes: Map; 30 | #pending: Message[] = []; 31 | #trackers = new Map(); 32 | #loggers = new Map(); 33 | #verbosity: Verbosity = Verbosity.ERROR; 34 | 35 | /** 36 | * Get current network config 37 | */ 38 | get config() { 39 | return this.#config; 40 | } 41 | 42 | /** 43 | * Current contract system time 44 | */ 45 | get now() { 46 | return this.#now; 47 | } 48 | 49 | /** 50 | * VM bindings for contract system 51 | */ 52 | get bindings() { 53 | return this.#bindings; 54 | } 55 | 56 | /** 57 | * LT of a contract system 58 | */ 59 | get lt() { 60 | return this.#lt; 61 | } 62 | 63 | /** 64 | * Returns current verbosity level 65 | */ 66 | get globalVerbosity() { 67 | return this.#verbosity; 68 | } 69 | 70 | private constructor(args?: { config?: Cell, now?: number, lt?: bigint }) { 71 | if (args && args.config) { 72 | this.#config = args.config; 73 | } else { 74 | this.#config = defaultConfig; 75 | } 76 | if (args && args.now) { 77 | this.#now = args.now; 78 | } else { 79 | this.#now = Math.floor(Date.now() / 1000); 80 | } 81 | if (args && args.lt) { 82 | this.#lt = args.lt; 83 | } else { 84 | this.#lt = 0n; 85 | } 86 | this.#bindings = new EmulatorBindings(); 87 | this.#contracts = new Map(); 88 | this.#abis = new Map(); 89 | this.#abiTypes = new Map(); 90 | } 91 | 92 | /** 93 | * Open a treasure with a 1bn of TONs 94 | * @param seed random string that identifies a treasure 95 | * @param workchain optional workchain id 96 | * @returns treasure wallet 97 | */ 98 | treasure(seed: string, workchain: number = 0) { 99 | 100 | // Create a treasure wallet 101 | let key = testKey(seed); 102 | let treasure = TreasureContract.create(workchain, key); 103 | let wallet = this.open(treasure); 104 | 105 | // Update wallet balance and name 106 | let executor = this.contract(treasure.address); 107 | executor.balance = toNano(1000000); 108 | executor.name = `treasure(${seed})`; 109 | 110 | // Return sender 111 | return wallet.sender(treasure.address); 112 | } 113 | 114 | /** 115 | * Override contract state 116 | * @param address 117 | * @param code 118 | * @param data 119 | * @param balance 120 | */ 121 | override(address: Address, code: Cell, data: Cell, balance: bigint) { 122 | this.contract(address).override(code, data, balance); 123 | } 124 | 125 | /** 126 | * Update system state 127 | */ 128 | update(updates: { now?: Maybe, lt?: Maybe, config?: Maybe }) { 129 | if (updates.now !== null && updates.now !== undefined) { 130 | this.#now = updates.now; 131 | } 132 | if (updates.config !== null && updates.config !== undefined) { 133 | this.#config = updates.config; 134 | } 135 | if (updates.lt !== null && updates.lt !== undefined) { 136 | this.#lt = updates.lt; 137 | } 138 | } 139 | 140 | /** 141 | * Get empty Contract Executor for a contract 142 | * @param contract contract address or contract instance 143 | * @returns contract executor 144 | */ 145 | contract(contract: AddressSource) { 146 | let address = resolveAddress(contract); 147 | let key = address.toString({ testOnly: true }); 148 | let executor = this.#contracts.get(key); 149 | if (!executor) { 150 | executor = ContractExecutor.createEmpty(address, this); 151 | this.#contracts.set(key, executor); 152 | } 153 | return executor; 154 | } 155 | 156 | /** 157 | * Creates a provider for contract 158 | * @param contract contract address 159 | * @returns contract provider 160 | */ 161 | provider(contract: Contract) { 162 | return this.#provider(contract.address, contract.init && contract.init.code && contract.init.data ? contract.init : null); 163 | } 164 | 165 | /** 166 | * Open a contract 167 | * @param src contract 168 | * @returns opened contract 169 | */ 170 | open(src: T) { 171 | 172 | // Register ABI 173 | if (src.abi) { 174 | this.#abis.set(src.address.toString({ testOnly: true }), src.abi); 175 | let registry = new TypeRegistry(); 176 | try { 177 | if (src.abi.types) { 178 | for (let i of src.abi.types) { 179 | try { 180 | registry.register(i); 181 | } catch (e) { 182 | // Ignore 183 | } 184 | } 185 | } 186 | } catch (e) { 187 | // Ignore 188 | } 189 | this.#abiTypes.set(src.address.toString({ testOnly: true }), registry); 190 | } 191 | 192 | // Open contract 193 | return openContract(src, (params) => this.#provider(params.address, params.init)); 194 | } 195 | 196 | /** 197 | * Run until stop 198 | */ 199 | async run() { 200 | let result: Transaction[] = []; 201 | while (this.#pending.length > 0) { 202 | let p = this.#pending.shift()!; // TODO: Better (random?) way to select pending message 203 | 204 | if (p.info.type === 'internal' || p.info.type === 'external-in') { 205 | 206 | // Execute 207 | let tx = await this.contract(p.info.dest).receive(p); 208 | let key = p.info.dest.toString({ testOnly: true }); 209 | 210 | // Track 211 | let t = this.#trackers.get(key); 212 | if (t) { 213 | for (let tr of t) { 214 | tr.track(tx.seq, tx.tx, this); 215 | } 216 | } 217 | 218 | // Logs 219 | let l = this.#loggers.get(key); 220 | if (l) { 221 | for (let tr of l) { 222 | tr.track(tx.seq, tx.logs, this); 223 | } 224 | } 225 | 226 | // Add to result 227 | result.push(tx.tx); 228 | 229 | // Add to pending 230 | for (let m of tx.tx.outMessages.values()) { 231 | this.#send(m); 232 | } 233 | } 234 | } 235 | this.update({ now: this.now + 16 }); 236 | return result; 237 | } 238 | 239 | /** 240 | * Create a tracker for a contract 241 | * @param address contract or it's address 242 | */ 243 | track(contract: AddressSource) { 244 | let address = resolveAddress(contract); 245 | let tracker = new Tracker(address); 246 | let key = address.toString({ testOnly: true }); 247 | let trackers = this.#trackers.get(key); 248 | if (!trackers) { 249 | trackers = []; 250 | this.#trackers.set(key, trackers); 251 | } 252 | trackers.push(tracker); 253 | return tracker; 254 | } 255 | 256 | /** 257 | * Create a logger for a contract 258 | * @param address contract or it's address 259 | */ 260 | log(contract: AddressSource) { 261 | let address = resolveAddress(contract); 262 | let logger = new Logger(address); 263 | let key = address.toString({ testOnly: true }); 264 | let loggers = this.#loggers.get(key); 265 | if (!loggers) { 266 | loggers = []; 267 | this.#loggers.set(key, loggers); 268 | } 269 | loggers.push(logger); 270 | return logger; 271 | } 272 | 273 | /** 274 | * Sets contract name 275 | * @param contract contract or it's address 276 | * @param name optional name 277 | */ 278 | name(contract: AddressSource, name: string | null) { 279 | this.contract(contract).name = name; 280 | } 281 | 282 | /** 283 | * Verbose level for a contract 284 | * @param contract contract or it's address 285 | * @param level verbosity level 286 | */ 287 | verbosity(contract: AddressSource, level: Verbosity) { 288 | this.contract(contract).verbosity = level; 289 | } 290 | 291 | /** 292 | * Send external message 293 | * @param message 294 | */ 295 | send(message: Message) { 296 | if (message.info.type !== 'external-in') { 297 | throw Error('Message is not external-in'); 298 | } 299 | this.#send(message); 300 | } 301 | 302 | #send = (src: Message) => { 303 | if (src.info.type !== 'external-out') { 304 | this.#pending.push(src); 305 | } 306 | } 307 | 308 | #provider(address: Address, init: { code: Cell, data: Cell } | null): ContractProvider { 309 | let executor = this.contract(address); 310 | return { 311 | getState: async () => { 312 | return executor.state; 313 | }, 314 | get: async (name, args) => { 315 | let res = await executor.get(name, args); 316 | if (!res.success) { 317 | throw Error(res.error); 318 | } 319 | if (res.exitCode !== 0 && res.exitCode !== 1) { 320 | let abi = this.#abis.get(address.toString({ testOnly: true })); 321 | if (abi && abi.errors && abi.errors[res.exitCode]) { 322 | throw new ComputeError(abi.errors[res.exitCode].message, res.exitCode, { logs: res.logs }); 323 | } else { 324 | throw new ComputeError('Exit code: ' + res.exitCode, res.exitCode, { logs: res.logs }); 325 | } 326 | } 327 | return { stack: res.stack!, gasUsed: res.gasUsed, logs: res.logs }; 328 | }, 329 | internal: async (via, message) => { 330 | 331 | // Resolve if init needed 332 | let state = executor.state; 333 | let neededInit: { code?: Maybe, data?: Maybe } | undefined = undefined; 334 | if (state.state.type !== 'active' && init) { 335 | neededInit = init; 336 | } 337 | 338 | // Resolve bounce 339 | let bounce = true; 340 | if (message.bounce !== null && message.bounce !== undefined) { 341 | bounce = message.bounce; 342 | } 343 | 344 | // Resolve value 345 | let value: bigint; 346 | if (typeof message.value === 'string') { 347 | value = toNano(message.value); 348 | } else { 349 | value = message.value; 350 | } 351 | 352 | // Resolve body 353 | let body: Cell | null = null; 354 | if (typeof message.body === 'string') { 355 | body = comment(message.body); 356 | } else if (message.body) { 357 | body = message.body; 358 | } 359 | 360 | // Send internal message 361 | await via.send({ 362 | to: address, 363 | value, 364 | bounce, 365 | sendMode: message.sendMode, 366 | init: neededInit, 367 | body 368 | }); 369 | }, 370 | external: async (msg) => { 371 | 372 | // Resolve if init needed 373 | let state = executor.state; 374 | let neededInit: { code?: Maybe, data?: Maybe } | undefined = undefined; 375 | if (state.state.type !== 'active' && init) { 376 | neededInit = init; 377 | } 378 | 379 | // Send message 380 | this.#send(external({ 381 | to: address, 382 | init: neededInit, 383 | body: msg 384 | })); 385 | } 386 | } 387 | } 388 | 389 | // 390 | // Utility 391 | // 392 | 393 | tryParseContractMessage(address: Address, src: Cell) { 394 | try { 395 | let b = this.#abiTypes.get(address.toString({ testOnly: true })); 396 | if (!b) { 397 | return undefined; 398 | } 399 | let sc = src.beginParse(); 400 | return b.parse(sc); 401 | } catch (e) { 402 | return undefined; 403 | } 404 | } 405 | 406 | getContractError(address: Address, code: number) { 407 | let b = this.#abis.get(address.toString({ testOnly: true })); 408 | if (!b) { 409 | return null; 410 | } 411 | if (b.errors && b.errors[code]) { 412 | return b.errors[code].message; 413 | } else { 414 | return null; 415 | } 416 | } 417 | 418 | getContractName(address: Address): string | null { 419 | let ex = this.#contracts.get(address.toString({ testOnly: true })); 420 | if (!ex) { 421 | return null; 422 | } 423 | return ex.name; 424 | } 425 | } -------------------------------------------------------------------------------- /src/emulator/Verbosity.ts: -------------------------------------------------------------------------------- 1 | export const enum Verbosity { 2 | NONE = 0, 3 | ERROR = 1, 4 | WARNING = 2, 5 | INFO = 3, 6 | DEBUG = 4 7 | } -------------------------------------------------------------------------------- /src/emulator/__snapshots__/ContractSystem.spec.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`ContractSystem should send messages 1`] = ` 4 | [ 5 | { 6 | "$seq": 0, 7 | "events": [ 8 | { 9 | "$type": "deploy", 10 | }, 11 | { 12 | "$type": "received", 13 | "message": { 14 | "body": { 15 | "cell": "x{CC78E031CD53F4684B97F4BB2FF4B0034FB709E4CC50398F7F9BFD205667F49D25F4B6E23FBD8A22025689813998C3D3A0EA5219F11C7907A5F125512AAB8F0B29A9A317FFFFFFFF00000000C_} 16 | x{D001} 17 | x{62005267EAC3643620B785BD578E117C4FFD17313A14F76F28642C14133842650C1DA1DCD6500000000000000000000000000000}", 18 | "type": "cell", 19 | }, 20 | "to": "@treasure(treasure)", 21 | "type": "external-in", 22 | }, 23 | }, 24 | { 25 | "$type": "processed", 26 | "gasUsed": 3583n, 27 | }, 28 | { 29 | "$type": "sent", 30 | "messages": [ 31 | { 32 | "body": { 33 | "type": "empty", 34 | }, 35 | "bounce": true, 36 | "from": "@treasure(treasure)", 37 | "to": "@unknown", 38 | "type": "internal", 39 | "value": "1", 40 | }, 41 | ], 42 | }, 43 | ], 44 | }, 45 | { 46 | "$seq": 1, 47 | "events": [ 48 | { 49 | "$type": "received-bounced", 50 | "message": { 51 | "body": { 52 | "cell": "x{FFFFFFFF}", 53 | "type": "cell", 54 | }, 55 | "bounce": false, 56 | "from": "@unknown", 57 | "to": "@treasure(treasure)", 58 | "type": "internal", 59 | "value": "0.999", 60 | }, 61 | }, 62 | { 63 | "$type": "processed", 64 | "gasUsed": 409n, 65 | }, 66 | ], 67 | }, 68 | { 69 | "$seq": 2, 70 | "events": [ 71 | { 72 | "$type": "storage-charged", 73 | "amount": "0.000000002", 74 | }, 75 | { 76 | "$type": "received", 77 | "message": { 78 | "body": { 79 | "cell": "x{7044FC22273A55D4E116F23A95F51F01E92BFE98EEEE67D299582CF99A8D6E4D2543CBE38D05758B3CE3ED63518A96829E48274172638A73C613E0A4E96FBF0D29A9A317FFFFFFFF00000001C_} 80 | x{D001} 81 | x{62005267EAC3643620B785BD578E117C4FFD17313A14F76F28642C14133842650C1DA1DCD65000000000000000000000000000000000000048656C6C6F20776F726C6421}", 82 | "type": "cell", 83 | }, 84 | "to": "@treasure(treasure)", 85 | "type": "external-in", 86 | }, 87 | }, 88 | { 89 | "$type": "processed", 90 | "gasUsed": 3583n, 91 | }, 92 | { 93 | "$type": "sent", 94 | "messages": [ 95 | { 96 | "body": { 97 | "text": "Hello world!", 98 | "type": "text", 99 | }, 100 | "bounce": true, 101 | "from": "@treasure(treasure)", 102 | "to": "@unknown", 103 | "type": "internal", 104 | "value": "1", 105 | }, 106 | ], 107 | }, 108 | ], 109 | }, 110 | { 111 | "$seq": 3, 112 | "events": [ 113 | { 114 | "$type": "received-bounced", 115 | "message": { 116 | "body": { 117 | "cell": "x{FFFFFFFF0000000048656C6C6F20776F726C6421}", 118 | "type": "cell", 119 | }, 120 | "bounce": false, 121 | "from": "@unknown", 122 | "to": "@treasure(treasure)", 123 | "type": "internal", 124 | "value": "0.999", 125 | }, 126 | }, 127 | { 128 | "$type": "processed", 129 | "gasUsed": 409n, 130 | }, 131 | ], 132 | }, 133 | ] 134 | `; 135 | -------------------------------------------------------------------------------- /src/events/events.ts: -------------------------------------------------------------------------------- 1 | import { ComputeSkipReason } from "@ton/core"; 2 | import { TrackedMessage } from "./message"; 3 | 4 | export type TrackedTransaction = { 5 | $seq: number, 6 | events: TrackedEvent[] 7 | } 8 | 9 | export type TrackedEvent = { 10 | $type: 'deploy' 11 | } | { 12 | $type: 'frozen' 13 | } | { 14 | $type: 'deleted' 15 | } | { 16 | $type: 'received', 17 | message: TrackedMessage 18 | } | { 19 | $type: 'received-bounced', 20 | message: TrackedMessage 21 | } | { 22 | $type: 'failed', 23 | errorCode: number, 24 | errorMessage?: string 25 | } | { 26 | $type: 'processed', 27 | gasUsed: bigint 28 | } | { 29 | $type: 'skipped', 30 | reason: ComputeSkipReason 31 | } | { 32 | $type: 'sent', 33 | messages: TrackedMessage[] 34 | } | { 35 | $type: 'sent-bounced', 36 | message: TrackedMessage 37 | } | { 38 | $type: 'sent-bounced-failed' 39 | } | { 40 | $type: 'storage-charged', 41 | amount: string 42 | }; -------------------------------------------------------------------------------- /src/events/message.ts: -------------------------------------------------------------------------------- 1 | export type TrackedBody = { 2 | type: 'empty' 3 | } | { 4 | type: 'cell', 5 | cell: string 6 | } | { 7 | type: 'text', 8 | text: string 9 | } | { 10 | type: 'known', 11 | value: any 12 | } 13 | 14 | export type TrackedMessage = { 15 | type: 'external-in', 16 | to: string, 17 | body: TrackedBody 18 | } | { 19 | type: 'external-out', 20 | to: string | null, 21 | body: TrackedBody 22 | } | { 23 | type: 'internal', 24 | from: string, 25 | to: string, 26 | value: string, 27 | bounce: boolean, 28 | body: TrackedBody 29 | }; -------------------------------------------------------------------------------- /src/events/tracker.ts: -------------------------------------------------------------------------------- 1 | import { Address, Cell, Message, Transaction, fromNano } from "@ton/core"; 2 | import { ContractSystem } from "../emulator/ContractSystem"; 3 | import { TrackedEvent, TrackedTransaction } from "./events"; 4 | import { TrackedBody, TrackedMessage } from './message'; 5 | 6 | export class Tracker { 7 | readonly address: Address; 8 | private _transactions: TrackedTransaction[] = []; 9 | 10 | constructor(address: Address) { 11 | this.address = address; 12 | } 13 | 14 | collect() { 15 | if (this._transactions.length > 0) { 16 | let r = this._transactions; 17 | this._transactions = []; 18 | return r; 19 | } else { 20 | return []; 21 | } 22 | } 23 | 24 | reset() { 25 | this._transactions = []; 26 | } 27 | 28 | track(seq: number, tx: Transaction, system: ContractSystem) { 29 | 30 | // Some sanity checks 31 | if (!tx.inMessage) { 32 | throw Error('Tick-tock transaction is not supported'); 33 | } 34 | if (tx.description.type !== 'generic') { 35 | throw Error('Non-generic transaction is not supported'); 36 | } 37 | 38 | let events: TrackedEvent[] = []; 39 | 40 | // Check if deployed 41 | if ((tx.oldStatus === 'non-existing' || tx.oldStatus === 'uninitialized') && tx.endStatus === 'active') { 42 | events.push({ $type: 'deploy' }); 43 | } 44 | 45 | if (tx.description.storagePhase) { 46 | if (tx.description.storagePhase.storageFeesCollected > 0n) { 47 | events.push({ $type: 'storage-charged', amount: fromNano(tx.description.storagePhase.storageFeesCollected) }); 48 | } 49 | if (tx.description.storagePhase.statusChange === 'frozen') { 50 | events.push({ $type: 'frozen' }); 51 | } 52 | if (tx.description.storagePhase.statusChange === 'deleted') { 53 | events.push({ $type: 'deleted' }); 54 | } 55 | } 56 | 57 | // Incoming message 58 | let msg = convertMessage(tx.inMessage, system); 59 | if (tx.inMessage.info.type === 'internal' && tx.inMessage.info.bounced) { 60 | events.push({ $type: 'received-bounced', message: msg }); 61 | } else { 62 | events.push({ $type: 'received', message: msg }); 63 | } 64 | 65 | // Processing 66 | if (tx.description.computePhase.type === 'vm') { 67 | if (tx.description.computePhase.success) { 68 | events.push({ $type: 'processed', gasUsed: tx.description.computePhase.gasUsed }); 69 | } else { 70 | let error = system.getContractError(this.address, tx.description.computePhase.exitCode); 71 | events.push({ $type: 'failed', errorCode: tx.description.computePhase.exitCode, ...(error ? { errorMessage: error } : {}) }); 72 | } 73 | } else { 74 | events.push({ $type: 'skipped', reason: tx.description.computePhase.reason }); 75 | } 76 | 77 | // Outgoing messages 78 | for (let outgoingMessage of tx.outMessages.values()) { 79 | let msg = convertMessage(outgoingMessage, system); 80 | if (outgoingMessage.info.type === 'internal' && outgoingMessage.info.bounced) { 81 | events.push({ $type: 'sent-bounced', message: msg }); 82 | } else { 83 | events.push({ $type: 'sent', messages: [msg] }); 84 | } 85 | } 86 | 87 | // Persist events 88 | this._transactions.push({ $seq: seq, events }); 89 | } 90 | } 91 | 92 | function convertMessage(src: Message, system: ContractSystem): TrackedMessage { 93 | 94 | // Internal message 95 | if (src.info.type === 'internal') { 96 | 97 | let fromRaw = src.info.src.toString({ testOnly: true }); 98 | let knownFrom = system.getContractName(src.info.src); 99 | let toRaw = src.info.dest.toString({ testOnly: true }); 100 | let knownTo = system.getContractName(src.info.dest); 101 | let to = knownTo ? '@' + knownTo : toRaw; 102 | let from = knownFrom ? '@' + knownFrom : fromRaw; 103 | 104 | return { 105 | type: 'internal', 106 | from, 107 | to, 108 | value: fromNano(src.info.value.coins), 109 | bounce: src.info.bounce, 110 | body: convertBody(src.body, [src.info.src, src.info.dest], system) 111 | } 112 | } 113 | 114 | // External in 115 | if (src.info.type === 'external-in') { 116 | let toRaw = src.info.dest.toString({ testOnly: true }); 117 | let knownTo = system.getContractName(src.info.dest); 118 | let to = knownTo ? '@' + knownTo : toRaw; 119 | return { 120 | type: 'external-in', 121 | to: to, 122 | body: convertBody(src.body, [src.info.dest], system) 123 | }; 124 | } 125 | 126 | // External out 127 | if (src.info.type === 'external-out') { 128 | return { 129 | type: 'external-out', 130 | to: src.info.dest ? src.info.dest.toString() : null, 131 | body: convertBody(src.body, [src.info.src], system) 132 | }; 133 | } 134 | 135 | throw Error('Invalid message object'); 136 | } 137 | 138 | function convertBody(src: Cell, address: Address[], system: ContractSystem): TrackedBody { 139 | let sc = src.beginParse(); 140 | 141 | // Empty case 142 | if (sc.remainingBits === 0 && sc.remainingRefs === 0) { 143 | return { type: 'empty' }; 144 | } 145 | 146 | // Too short for op 147 | if (sc.remainingBits <= 32) { 148 | return { type: 'cell', cell: src.toString() }; 149 | } 150 | 151 | // If text 152 | let op = sc.loadUint(32); 153 | if (op === 0) { 154 | return { type: 'text', text: sc.loadStringTail() }; 155 | } 156 | 157 | // If known 158 | for (let a of address) { 159 | let parsed = system.tryParseContractMessage(a, src); 160 | if (parsed !== undefined) { 161 | return { 162 | type: 'known', 163 | value: parsed 164 | }; 165 | } 166 | } 167 | 168 | // Fallback 169 | return { type: 'cell', cell: src.toString() }; 170 | } -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | // Contract system 2 | export { ContractSystem } from './emulator/ContractSystem'; 3 | export { ContractExecutor } from './emulator/ContractExecutor'; 4 | export { randomAddress, randomExternalAddress } from './utils/randomAddress' 5 | export { testKey } from './utils/testKey'; 6 | export { Treasure } from './treasure/Treasure'; 7 | export { Verbosity } from './emulator/Verbosity'; 8 | 9 | // Tracker 10 | export { Tracker } from './events/tracker'; 11 | export { TrackedEvent, TrackedTransaction } from './events/events'; 12 | export { TrackedBody, TrackedMessage } from './events/message'; 13 | 14 | // Logger 15 | export { Logger } from './logger/logger'; -------------------------------------------------------------------------------- /src/logger/logger.ts: -------------------------------------------------------------------------------- 1 | import { Address } from "@ton/core"; 2 | import { ContractSystem } from "../emulator/ContractSystem"; 3 | 4 | export class Logger { 5 | readonly address: Address 6 | private _logs: { $seq: number, logs: string }[] = []; 7 | 8 | constructor(address: Address) { 9 | this.address = address; 10 | } 11 | 12 | collect() { 13 | let r = this._logs.map((v) => '===================\n' + 'TX: ' + v.$seq + '\n===================\n' + v.logs).join('\n\n'); 14 | this._logs = []; 15 | return r; 16 | } 17 | 18 | reset() { 19 | this._logs = []; 20 | } 21 | 22 | track(seq: number, logs: string, system: ContractSystem) { 23 | this._logs.push({ $seq: seq, logs }); 24 | } 25 | } -------------------------------------------------------------------------------- /src/treasure/Treasure.ts: -------------------------------------------------------------------------------- 1 | import { Address, beginCell, Cell, Contract, contractAddress, ContractProvider, Dictionary, DictionaryValue, internal, loadMessageRelaxed, MessageRelaxed, Sender, SendMode, storeMessageRelaxed } from "@ton/core"; 2 | import { KeyPair, sign } from "@ton/crypto"; 3 | import { Maybe } from "../utils/maybe"; 4 | 5 | // Highload Wallet code 6 | const walletCode = 'te6ccgEBCAEAlwABFP8A9KQT9LzyyAsBAgEgAgMCAUgEBQC48oMI1xgg0x/TH9MfAvgju/Jj7UTQ0x/TH9P/0VEyuvKhUUS68qIE+QFUEFX5EPKj9ATR+AB/jhYhgBD0eG+lIJgC0wfUMAH7AJEy4gGz5lsBpMjLH8sfy//J7VQABNAwAgFIBgcAF7s5ztRNDTPzHXC/+AARuMl+1E0NcLH4'; 7 | 8 | const DictionaryMessageValue: DictionaryValue<{ sendMode: SendMode, message: MessageRelaxed }> = { 9 | serialize(src, builder) { 10 | builder.storeUint(src.sendMode, 8); 11 | builder.storeRef(beginCell().store(storeMessageRelaxed(src.message))); 12 | }, 13 | parse(src) { 14 | let sendMode = src.loadUint(8); 15 | let message = loadMessageRelaxed(src.loadRef().beginParse()); 16 | return { sendMode, message }; 17 | }, 18 | } 19 | 20 | export type Treasure = Sender & { 21 | address: Address 22 | } 23 | 24 | export class TreasureContract implements Contract { 25 | static create(workchain: number, keypair: KeyPair) { 26 | return new TreasureContract(workchain, keypair); 27 | } 28 | 29 | readonly address: Address; 30 | readonly init: { code: Cell, data: Cell }; 31 | readonly keypair: KeyPair; 32 | private seqno: number = 0; 33 | 34 | constructor(workchain: number, keypair: KeyPair) { 35 | let code = Cell.fromBoc(Buffer.from(walletCode, 'base64'))[0]; 36 | let data = beginCell() 37 | .storeUint(0, 32) // Seqno 38 | .storeUint(698983191, 32) // Wallet Id 39 | .storeBuffer(keypair.publicKey) 40 | .endCell(); 41 | this.address = contractAddress(workchain, { code, data }); 42 | this.init = { code, data }; 43 | this.keypair = keypair; 44 | } 45 | 46 | async send(provider: ContractProvider, src: MessageRelaxed[], sendMode?: SendMode) { 47 | let transfer = this.createTransfer({ 48 | seqno: this.seqno++, 49 | sendMode: sendMode, 50 | messages: src 51 | }); 52 | provider.external(transfer); 53 | } 54 | 55 | sender(provider: ContractProvider, address: Address): Treasure { 56 | return { 57 | address, 58 | send: async (args) => { 59 | let transfer = this.createTransfer({ 60 | seqno: this.seqno++, 61 | sendMode: args.sendMode, 62 | messages: [internal({ 63 | to: args.to, 64 | value: args.value, 65 | init: args.init, 66 | body: args.body, 67 | bounce: args.bounce 68 | })] 69 | }); 70 | provider.external(transfer); 71 | } 72 | }; 73 | } 74 | 75 | /** 76 | * Create signed transfer 77 | */ 78 | createTransfer(args: { 79 | seqno: number, 80 | messages: MessageRelaxed[] 81 | sendMode?: Maybe, 82 | }) { 83 | 84 | // Resolve send mode 85 | let sendMode = SendMode.PAY_GAS_SEPARATELY; 86 | if (args.sendMode !== null && args.sendMode !== undefined) { 87 | sendMode = args.sendMode; 88 | } 89 | 90 | // Resolve messages 91 | if (args.messages.length > 255) { 92 | throw new Error('Maximum number of messages is 255'); 93 | } 94 | let messages = Dictionary.empty(Dictionary.Keys.Int(16), DictionaryMessageValue); 95 | let index = 0; 96 | for (let m of args.messages) { 97 | messages.set(index++, { sendMode, message: m }); 98 | } 99 | 100 | // Create message 101 | let signingMessage = beginCell() 102 | .storeUint(698983191, 32) // Wallet Id 103 | .storeUint(4294967295, 32) // Timeout 104 | .storeUint(args.seqno, 32) // Seqno 105 | .storeDict(messages); 106 | 107 | // Sign message 108 | let signature = sign(signingMessage.endCell().hash(), this.keypair.secretKey); 109 | 110 | // Body 111 | const body = beginCell() 112 | .storeBuffer(signature) 113 | .storeBuilder(signingMessage) 114 | .endCell(); 115 | 116 | return body; 117 | } 118 | } -------------------------------------------------------------------------------- /src/utils/addressSource.ts: -------------------------------------------------------------------------------- 1 | import { Address, Contract } from "@ton/core"; 2 | 3 | export type AddressSource = Address | Contract; 4 | 5 | export function resolveAddress(source: AddressSource): Address { 6 | if (Address.isAddress(source)) { 7 | return source; 8 | } 9 | if (Address.isAddress(source.address)) { 10 | return source.address; 11 | } 12 | 13 | throw new Error("Invalid address source"); 14 | } -------------------------------------------------------------------------------- /src/utils/createAccount.ts: -------------------------------------------------------------------------------- 1 | import { Account, Address, Cell, contractAddress } from "@ton/core"; 2 | 3 | export function createAccount(args: { code: Cell, data: Cell, workchain?: number, address?: Address, balance?: bigint }): Account { 4 | 5 | let workchain: number; 6 | if (args.workchain !== undefined && args.workchain !== null) { 7 | workchain = args.workchain; 8 | } else { 9 | workchain = 0; 10 | } 11 | 12 | let address: Address; 13 | if (args.address) { 14 | address = args.address; 15 | } else { 16 | address = contractAddress(workchain, { code: args.code, data: args.data }); 17 | } 18 | 19 | let balance: bigint; 20 | if (args.balance) { 21 | balance = args.balance; 22 | } else { 23 | balance = 0n; 24 | } 25 | 26 | return { 27 | addr: address, 28 | storageStats: { 29 | used: { cells: 0n, bits: 0n, publicCells: 0n }, 30 | lastPaid: 0 31 | }, 32 | storage: { 33 | lastTransLt: 0n, 34 | balance: { coins: balance }, 35 | state: { 36 | type: 'active', 37 | state: { 38 | code: args.code, 39 | data: args.data 40 | } 41 | } 42 | } 43 | }; 44 | } -------------------------------------------------------------------------------- /src/utils/createEmptyAccount.ts: -------------------------------------------------------------------------------- 1 | import { Account, Address } from "@ton/core"; 2 | 3 | export function createEmptyAccount(address: Address): Account { 4 | return { 5 | addr: address, 6 | storageStats: { 7 | used: { cells: 0n, bits: 0n, publicCells: 0n }, 8 | lastPaid: 0 9 | }, 10 | storage: { 11 | lastTransLt: 0n, 12 | balance: { coins: 0n }, 13 | state: { 14 | type: 'uninit' 15 | } 16 | } 17 | }; 18 | } -------------------------------------------------------------------------------- /src/utils/defaultConfig.ts: -------------------------------------------------------------------------------- 1 | import { Cell } from "@ton/core"; 2 | 3 | export const defaultConfig = Cell.fromBoc(Buffer.from('te6cckECcQEABW4AAgPNQAgBAgHOBQIBAUgDASsSY2CBqmNgiLIAAQABEAAAAAAAAADABACc0HOOgSeKR9NK7QvAShWEclTKIiT7KeN5YCq4i54HKwIneLCELpUQAAAAAAAAABUmyLDUSVB+cjV8cixVpAvtnckT5vUz9dfl85TM5VefAQFIBgErEmNgeqJjYIGqAAEAARAAAAAAAAAAwAcAnNBzjoEninFgk152x5e4P2obeffAtRXXbENLO/wHPAXk6o16jdMSEAAAAAAAAADXRh/1XRFatoIzSUoCBNvmoi70eMphk2KbS9TlOhR0JwIBIC8JAgEgHQoCASAYCwIBIBMMAQFYDQEBwA4CAWIQDwBBv2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZnAgFiEhEAQb6PZMavv/PdENi6Zwd5CslnDVQPN6lEiwM3uqalqSrKyAAD31ACASAWFAEBIBUAPtcBAwAAA+gAAD6AAAAAAwAAAAgAAAAEACAAAAAgAAABASAXACTCAQAAAPoAAAD6AAAVfAAAAAcCAUgbGQEBIBoAQuoAAAAAAA9CQAAAAAAD6AAAAAAAAYagAAAAAYAAVVVVVQEBIBwAQuoAAAAAAJiWgAAAAAAnEAAAAAAAD0JAAAAAAYAAVVVVVQIBICceAgEgIh8CASAgIAEBICEAUF3DAAIAAAAIAAAAEAAAwwABhqAAB6EgAA9CQMMAAAPoAAATiAAAJxACASAlIwEBICQAlNEAAAAAAAAD6AAAAAAAD0JA3gAAAAAD6AAAAAAAAAAPQkAAAAAAAA9CQAAAAAAAACcQAAAAAACYloAAAAAABfXhAAAAAAA7msoAAQEgJgCU0QAAAAAAAAPoAAAAAACYloDeAAAAACcQAAAAAAAAAA9CQAAAAAAAmJaAAAAAAAAAJxAAAAAAAJiWgAAAAAAF9eEAAAAAADuaygACASAqKAEBSCkATdBmAAAAAAAAAAAAAAAAgAAAAAAAAPoAAAAAAAAB9AAAAAAAA9CQQAIBIC0rAQEgLAAxYJGE5yoAByOG8m/BAABgkYTnKgAAADAACAEBIC4ADAPoAGQAAQIBIGMwAgEgPTECASA3MgIBIDUzAQEgNAAgAAAHCAAABdwAAAJYAAABLAEBIDYAFGtGVT8QBDuaygACASA6OAEBIDkAFRpRdIdugAEBIB9IAQEgOwEBwDwAt9BTMattNoAAkHAAQx3ZAu+VowKbp+ICJmhD5GEorXlHxC3H6wE6MfljTuTBTbWk4gs+NaYDgOTAD+ixkmRV+9aOm8QUXzkDPYIEZQAAAAAP////+AAAAAAAAAAEAgEgTD4CASBDPwEBIEACApFCQQAqNgQHBAIATEtAATEtAAAAAAIAAAPoACo2AgMCAgAPQkAAmJaAAAAAAQAAAfQBASBEAgEgR0UCCbf///BgRl8AAfwCAtlKSAIBYklTAgEgXV0CASBYSwIBzmBgAgEgYU0BASBOAgPNQFBPAAOooAIBIFhRAgEgVVICASBUUwAB1AIBSGBgAgEgV1YCASBbWwIBIFtdAgEgX1kCASBcWgIBIF1bAgEgYGACASBeXQABSAABWAIB1GBgAAEgAQEgYgAaxAAAAAEAAAAAAAAALgIBIGlkAQH0ZQEBwGYCASBoZwAVv////7y9GpSiABAAFb4AAAO8s2cNwVVQAgEgbGoBAUhrAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIBIG9tAQEgbgBAMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMBASBwAEBVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVaNaat0=', 'base64'))[0]; -------------------------------------------------------------------------------- /src/utils/getMethodId.ts: -------------------------------------------------------------------------------- 1 | const TABLE = new Int16Array([ 2 | 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7, 3 | 0x8108, 0x9129, 0xa14a, 0xb16b, 0xc18c, 0xd1ad, 0xe1ce, 0xf1ef, 4 | 0x1231, 0x0210, 0x3273, 0x2252, 0x52b5, 0x4294, 0x72f7, 0x62d6, 5 | 0x9339, 0x8318, 0xb37b, 0xa35a, 0xd3bd, 0xc39c, 0xf3ff, 0xe3de, 6 | 0x2462, 0x3443, 0x0420, 0x1401, 0x64e6, 0x74c7, 0x44a4, 0x5485, 7 | 0xa56a, 0xb54b, 0x8528, 0x9509, 0xe5ee, 0xf5cf, 0xc5ac, 0xd58d, 8 | 0x3653, 0x2672, 0x1611, 0x0630, 0x76d7, 0x66f6, 0x5695, 0x46b4, 9 | 0xb75b, 0xa77a, 0x9719, 0x8738, 0xf7df, 0xe7fe, 0xd79d, 0xc7bc, 10 | 0x48c4, 0x58e5, 0x6886, 0x78a7, 0x0840, 0x1861, 0x2802, 0x3823, 11 | 0xc9cc, 0xd9ed, 0xe98e, 0xf9af, 0x8948, 0x9969, 0xa90a, 0xb92b, 12 | 0x5af5, 0x4ad4, 0x7ab7, 0x6a96, 0x1a71, 0x0a50, 0x3a33, 0x2a12, 13 | 0xdbfd, 0xcbdc, 0xfbbf, 0xeb9e, 0x9b79, 0x8b58, 0xbb3b, 0xab1a, 14 | 0x6ca6, 0x7c87, 0x4ce4, 0x5cc5, 0x2c22, 0x3c03, 0x0c60, 0x1c41, 15 | 0xedae, 0xfd8f, 0xcdec, 0xddcd, 0xad2a, 0xbd0b, 0x8d68, 0x9d49, 16 | 0x7e97, 0x6eb6, 0x5ed5, 0x4ef4, 0x3e13, 0x2e32, 0x1e51, 0x0e70, 17 | 0xff9f, 0xefbe, 0xdfdd, 0xcffc, 0xbf1b, 0xaf3a, 0x9f59, 0x8f78, 18 | 0x9188, 0x81a9, 0xb1ca, 0xa1eb, 0xd10c, 0xc12d, 0xf14e, 0xe16f, 19 | 0x1080, 0x00a1, 0x30c2, 0x20e3, 0x5004, 0x4025, 0x7046, 0x6067, 20 | 0x83b9, 0x9398, 0xa3fb, 0xb3da, 0xc33d, 0xd31c, 0xe37f, 0xf35e, 21 | 0x02b1, 0x1290, 0x22f3, 0x32d2, 0x4235, 0x5214, 0x6277, 0x7256, 22 | 0xb5ea, 0xa5cb, 0x95a8, 0x8589, 0xf56e, 0xe54f, 0xd52c, 0xc50d, 23 | 0x34e2, 0x24c3, 0x14a0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, 24 | 0xa7db, 0xb7fa, 0x8799, 0x97b8, 0xe75f, 0xf77e, 0xc71d, 0xd73c, 25 | 0x26d3, 0x36f2, 0x0691, 0x16b0, 0x6657, 0x7676, 0x4615, 0x5634, 26 | 0xd94c, 0xc96d, 0xf90e, 0xe92f, 0x99c8, 0x89e9, 0xb98a, 0xa9ab, 27 | 0x5844, 0x4865, 0x7806, 0x6827, 0x18c0, 0x08e1, 0x3882, 0x28a3, 28 | 0xcb7d, 0xdb5c, 0xeb3f, 0xfb1e, 0x8bf9, 0x9bd8, 0xabbb, 0xbb9a, 29 | 0x4a75, 0x5a54, 0x6a37, 0x7a16, 0x0af1, 0x1ad0, 0x2ab3, 0x3a92, 30 | 0xfd2e, 0xed0f, 0xdd6c, 0xcd4d, 0xbdaa, 0xad8b, 0x9de8, 0x8dc9, 31 | 0x7c26, 0x6c07, 0x5c64, 0x4c45, 0x3ca2, 0x2c83, 0x1ce0, 0x0cc1, 32 | 0xef1f, 0xff3e, 0xcf5d, 0xdf7c, 0xaf9b, 0xbfba, 0x8fd9, 0x9ff8, 33 | 0x6e17, 0x7e36, 0x4e55, 0x5e74, 0x2e93, 0x3eb2, 0x0ed1, 0x1ef0 34 | ]) 35 | 36 | function crc16(data: string | Buffer) { 37 | if (!(data instanceof Buffer)) { 38 | data = Buffer.from(data) 39 | } 40 | 41 | let crc = 0 42 | 43 | for (let index = 0; index < data.length; index++) { 44 | const byte = data[index] 45 | crc = (TABLE[((crc >> 8) ^ byte) & 0xff] ^ (crc << 8)) & 0xffff 46 | } 47 | 48 | return crc 49 | } 50 | 51 | export function getMethodId(name: string) { 52 | return (crc16(name) & 0xffff) | 0x10000; 53 | } -------------------------------------------------------------------------------- /src/utils/maybe.ts: -------------------------------------------------------------------------------- 1 | export type Maybe = T | null | undefined; -------------------------------------------------------------------------------- /src/utils/randomAddress.ts: -------------------------------------------------------------------------------- 1 | import Prando from "prando"; 2 | import { Address, ExternalAddress } from "@ton/core"; 3 | 4 | export function randomAddress(seed: string, workchain: number = 0) { 5 | const random = new Prando(seed); 6 | const hash = Buffer.alloc(32); 7 | for (let i = 0; i < hash.length; i++) { 8 | hash[i] = random.nextInt(0, 255); 9 | } 10 | return new Address(workchain, hash); 11 | } 12 | 13 | 14 | export function randomExternalAddress(seed: string) { 15 | const random = new Prando(seed); 16 | const hash = Buffer.alloc(32); 17 | for (let i = 0; i < hash.length; i++) { 18 | hash[i] = random.nextInt(0, 255); 19 | } 20 | let v = BigInt('0x' + hash.toString('hex')); 21 | return new ExternalAddress(v, 32 * 8); 22 | } 23 | -------------------------------------------------------------------------------- /src/utils/testKey.ts: -------------------------------------------------------------------------------- 1 | import Prando from "prando"; 2 | import { keyPairFromSeed } from "@ton/crypto"; 3 | 4 | export function testKey(seed: string) { 5 | let random = new Prando(seed); 6 | let res = Buffer.alloc(32); 7 | for (let i = 0; i < res.length; i++) { 8 | res[i] = random.nextInt(0, 256); 9 | } 10 | return keyPairFromSeed(res); 11 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Visit https://aka.ms/tsconfig.json to read more about this file */ 4 | /* Basic Options */ 5 | // "incremental": true, /* Enable incremental compilation */ 6 | "target": "ES2020", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', 'ES2021', or 'ESNEXT'. */ 7 | "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */ 8 | // "lib": [], /* Specify library files to be included in the compilation. */ 9 | // "allowJs": true, /* Allow javascript files to be compiled. */ 10 | // "checkJs": true, /* Report errors in .js files. */ 11 | // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', 'react', 'react-jsx' or 'react-jsxdev'. */ 12 | // "declaration": true, /* Generates corresponding '.d.ts' file. */ 13 | // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ 14 | // "sourceMap": true, /* Generates corresponding '.map' file. */ 15 | // "outFile": "./", /* Concatenate and emit output to single file. */ 16 | "outDir": "./dist", /* Redirect output structure to the directory. */ 17 | // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ 18 | // "composite": true, /* Enable project compilation */ 19 | // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ 20 | // "removeComments": true, /* Do not emit comments to output. */ 21 | // "noEmit": false, /* Do not emit outputs. */ 22 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */ 23 | "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ 24 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ 25 | /* Strict Type-Checking Options */ 26 | "strict": true, /* Enable all strict type-checking options. */ 27 | // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ 28 | // "strictNullChecks": true, /* Enable strict null checks. */ 29 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */ 30 | // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ 31 | // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ 32 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ 33 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ 34 | /* Additional Checks */ 35 | // "noUnusedLocals": true, /* Report errors on unused locals. */ 36 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 37 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 38 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 39 | // "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */ 40 | // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an 'override' modifier. */ 41 | // "noPropertyAccessFromIndexSignature": true, /* Require undeclared properties from index signatures to use element accesses. */ 42 | /* Module Resolution Options */ 43 | // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ 44 | // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ 45 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ 46 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ 47 | // "typeRoots": [], /* List of folders to include type definitions from. */ 48 | // "types": [], /* Type declaration files to be included in compilation. */ 49 | "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ 50 | "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ 51 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ 52 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 53 | /* Source Map Options */ 54 | // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ 55 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 56 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ 57 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ 58 | /* Experimental Options */ 59 | // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ 60 | // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ 61 | /* Advanced Options */ 62 | "skipLibCheck": true, /* Skip type checking of declaration files. */ 63 | "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ 64 | }, 65 | "include": [ 66 | "src/**/*" 67 | ], 68 | "exclude": [ 69 | "node_modules" 70 | ] 71 | } --------------------------------------------------------------------------------