blueprint-tsa 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/README.md +0 -0
  2. package/dist/cli.d.ts +13 -0
  3. package/dist/cli.js +54 -0
  4. package/dist/commands/audit.d.ts +19 -0
  5. package/dist/commands/audit.js +296 -0
  6. package/dist/commands/bounce-check.d.ts +15 -0
  7. package/dist/commands/bounce-check.js +152 -0
  8. package/dist/commands/clean.d.ts +1 -0
  9. package/dist/commands/clean.js +20 -0
  10. package/dist/commands/drain-check.d.ts +32 -0
  11. package/dist/commands/drain-check.js +218 -0
  12. package/dist/commands/opcode-info.d.ts +23 -0
  13. package/dist/commands/opcode-info.js +176 -0
  14. package/dist/commands/owner-hijack-check.d.ts +20 -0
  15. package/dist/commands/owner-hijack-check.js +290 -0
  16. package/dist/commands/replay-attack-check.d.ts +20 -0
  17. package/dist/commands/replay-attack-check.js +149 -0
  18. package/dist/commands/reproduce.d.ts +3 -0
  19. package/dist/commands/reproduce.js +102 -0
  20. package/dist/common/analyzer-wrapper.d.ts +69 -0
  21. package/dist/common/analyzer-wrapper.js +198 -0
  22. package/dist/common/analyzer.d.ts +10 -0
  23. package/dist/common/analyzer.js +49 -0
  24. package/dist/common/build-utils.d.ts +3 -0
  25. package/dist/common/build-utils.js +68 -0
  26. package/dist/common/constants.d.ts +41 -0
  27. package/dist/common/constants.js +45 -0
  28. package/dist/common/draw.d.ts +7 -0
  29. package/dist/common/draw.js +33 -0
  30. package/dist/common/file-utils.d.ts +7 -0
  31. package/dist/common/file-utils.js +20 -0
  32. package/dist/common/format-utils.d.ts +13 -0
  33. package/dist/common/format-utils.js +30 -0
  34. package/dist/common/opcode-extractor.d.ts +7 -0
  35. package/dist/common/opcode-extractor.js +60 -0
  36. package/dist/common/paths.d.ts +19 -0
  37. package/dist/common/paths.js +139 -0
  38. package/dist/common/result-parsing.d.ts +4 -0
  39. package/dist/common/result-parsing.js +40 -0
  40. package/dist/index.d.ts +4 -0
  41. package/dist/index.js +16 -0
  42. package/dist/install/architecture.d.ts +5 -0
  43. package/dist/install/architecture.js +51 -0
  44. package/dist/install/downloading.d.ts +1 -0
  45. package/dist/install/downloading.js +44 -0
  46. package/dist/install/java.d.ts +1 -0
  47. package/dist/install/java.js +89 -0
  48. package/dist/install/postinstall.d.ts +1 -0
  49. package/dist/install/postinstall.js +12 -0
  50. package/dist/install/tsa-jar.d.ts +1 -0
  51. package/dist/install/tsa-jar.js +23 -0
  52. package/dist/install/unzip.d.ts +1 -0
  53. package/dist/install/unzip.js +14 -0
  54. package/dist/reproduce/build-config.d.ts +3 -0
  55. package/dist/reproduce/build-config.js +24 -0
  56. package/dist/reproduce/concrete-analysis.d.ts +15 -0
  57. package/dist/reproduce/concrete-analysis.js +21 -0
  58. package/dist/reproduce/network.d.ts +19 -0
  59. package/dist/reproduce/network.js +70 -0
  60. package/dist/reproduce/reproduce-config.d.ts +30 -0
  61. package/dist/reproduce/reproduce-config.js +59 -0
  62. package/dist/reproduce/utils.d.ts +4 -0
  63. package/dist/reproduce/utils.js +34 -0
  64. package/dist/tsa.d.ts +2 -0
  65. package/dist/tsa.js +22 -0
  66. package/package.json +45 -0
@@ -0,0 +1,290 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ownerHijackCheckConcrete = exports.runOwnerHijackCheckAnalysis = exports.configureOwnerHijackCommand = void 0;
4
+ const fs_1 = require("fs");
5
+ const core_1 = require("@ton/core");
6
+ const analyzer_wrapper_js_1 = require("../common/analyzer-wrapper.js");
7
+ const build_config_js_1 = require("../reproduce/build-config.js");
8
+ const constants_js_1 = require("../common/constants.js");
9
+ const build_utils_js_1 = require("../common/build-utils.js");
10
+ const utils_js_1 = require("../reproduce/utils.js");
11
+ const paths_js_1 = require("../common/paths.js");
12
+ const opcode_extractor_js_1 = require("../common/opcode-extractor.js");
13
+ const ONE_MINUTE_SECONDS = 60;
14
+ const configureOwnerHijackCommand = (context) => ({
15
+ command: constants_js_1.OWNER_HIJACK_CHECK_ID,
16
+ description: "Analyze contract for the possibility of owner hijack",
17
+ builder: (yargs) => yargs
18
+ .option("timeout", {
19
+ alias: "t",
20
+ type: "number",
21
+ description: "Analysis timeout in seconds",
22
+ })
23
+ .option("contract", {
24
+ alias: "c",
25
+ type: "string",
26
+ description: "Contract name",
27
+ demandOption: true,
28
+ })
29
+ .option("method-name", {
30
+ alias: "m",
31
+ type: "string",
32
+ description: "The method name of get_owner getter",
33
+ demandOption: true,
34
+ })
35
+ .option("verbose", {
36
+ alias: "v",
37
+ type: "boolean",
38
+ description: "Use debug output in TSA log",
39
+ })
40
+ .option("disable-opcode-extraction", {
41
+ type: "boolean",
42
+ description: "Disable opcode extraction. This affects path selection strategy and default timeout.",
43
+ }),
44
+ handler: async (argv) => await ownerHijackCommand(context, argv),
45
+ });
46
+ exports.configureOwnerHijackCommand = configureOwnerHijackCommand;
47
+ const extractOptions = (ui, parsedArgs) => {
48
+ const contract = parsedArgs.contract;
49
+ if (typeof contract !== "string") {
50
+ throw new Error("Contract name or path is required");
51
+ }
52
+ const timeout = parsedArgs.timeout ?? null;
53
+ const methodid = (0, core_1.getMethodId)(parsedArgs.methodName);
54
+ if (!Number.isInteger(methodid)) {
55
+ throw new Error("MethodId is not an integer");
56
+ }
57
+ const methodId = BigInt(methodid);
58
+ const options = {
59
+ contract,
60
+ timeout,
61
+ methodId,
62
+ };
63
+ const properties = [
64
+ { key: "Contract", value: options.contract },
65
+ { key: "Mode", value: "TON owner hijack" },
66
+ {
67
+ key: "Options",
68
+ separator: true,
69
+ children: [
70
+ {
71
+ key: "Timeout",
72
+ value: options.timeout !== null ? `${options.timeout} seconds` : "not set",
73
+ },
74
+ { key: "Method id", value: options.methodId.toString() },
75
+ ],
76
+ },
77
+ ];
78
+ return { options, properties };
79
+ };
80
+ /**
81
+ * Runs owner hijack check analysis and returns the analyzer wrapper
82
+ * @param contractName - Name of the contract
83
+ * @param contractPath - Path to the compiled contract
84
+ * @param ui - UI provider
85
+ * @param timeout - Analysis timeout in seconds
86
+ * @param methodId - Method ID of the owner getter
87
+ * @param opcodes - List of opcodes to analyze
88
+ * @param verbose - Enable verbose output
89
+ * @returns AnalyzerWrapper instance
90
+ */
91
+ const runOwnerHijackCheckAnalysis = async (contractName, contractPath, ui, timeout, methodId, opcodes, verbose = false, completionMessage = "Analysis complete.") => {
92
+ const properties = [
93
+ { key: "Contract", value: contractName },
94
+ { key: "Mode", value: "TON owner hijack" },
95
+ {
96
+ key: "Options",
97
+ separator: true,
98
+ children: [
99
+ {
100
+ key: "Timeout",
101
+ value: timeout !== null ? `${timeout} seconds` : "not set",
102
+ },
103
+ { key: "Method id", value: methodId.toString() },
104
+ ],
105
+ },
106
+ ];
107
+ const checkerPath = (0, paths_js_1.getCheckerPath)(constants_js_1.OWNER_HIJACK_CHECK_SYMBOLIC_FILENAME);
108
+ const checkerCell = (0, core_1.beginCell)().storeUint(methodId, 32).endCell();
109
+ const analyzer = new analyzer_wrapper_js_1.AnalyzerWrapper({
110
+ ui,
111
+ checkerPath,
112
+ checkerCell,
113
+ properties,
114
+ codePath: contractPath,
115
+ });
116
+ const reportDir = (0, paths_js_1.getReportDirectory)(analyzer.id);
117
+ const sarifPath = (0, paths_js_1.getSarifReportPath)(analyzer.id);
118
+ await analyzer.run(constants_js_1.OWNER_HIJACK_CHECK_SYMBOLIC_FILENAME, (wrapper) => [
119
+ "custom-checker-compiled",
120
+ "--checker",
121
+ wrapper.getTempBocPath(),
122
+ "--contract",
123
+ contractPath,
124
+ "--stop-when-exit-codes-found",
125
+ constants_js_1.ERROR_EXIT_CODE.toString(),
126
+ "--checker-data",
127
+ wrapper.getTempCheckerCellPath(),
128
+ "--output",
129
+ sarifPath,
130
+ "--exported-inputs",
131
+ reportDir,
132
+ ...(verbose ? ["-v"] : []),
133
+ ...opcodes.flatMap((opcode) => ["--opcode", opcode.toString()]),
134
+ "--disable-out-message-analysis",
135
+ ...(timeout != null ? ["--timeout", timeout.toString()] : []),
136
+ ], completionMessage);
137
+ // Write reproduction config if vulnerability is found
138
+ const vulnerability = analyzer.getVulnerability();
139
+ if (vulnerability) {
140
+ (0, build_config_js_1.writeReproduceConfig)(vulnerability, constants_js_1.OWNER_HIJACK_CHECK_ID, timeout, analyzer.id, {
141
+ kind: "owner-hijack-check",
142
+ methodId: methodId.toString(),
143
+ });
144
+ }
145
+ return analyzer;
146
+ };
147
+ exports.runOwnerHijackCheckAnalysis = runOwnerHijackCheckAnalysis;
148
+ const ownerHijackCommand = async (context, parsedArgs) => {
149
+ const { ui } = context;
150
+ await (0, build_utils_js_1.buildContracts)(ui);
151
+ const { options, properties } = extractOptions(ui, parsedArgs);
152
+ const contractPath = (0, paths_js_1.findCompiledContract)(options.contract);
153
+ if (!(0, fs_1.existsSync)(contractPath)) {
154
+ ui.write(`\n${constants_js_1.Sym.ERR} Contract ${options.contract} not found`);
155
+ process.exit(1);
156
+ }
157
+ let opcodes = [];
158
+ if (!parsedArgs["disable-opcode-extraction"]) {
159
+ opcodes = await (0, opcode_extractor_js_1.extractOpcodes)({
160
+ ui,
161
+ codePath: contractPath,
162
+ contractName: options.contract,
163
+ });
164
+ }
165
+ // If timeout wasn't provided, set it to 1 minute * (number_of_opcodes + 1)
166
+ if (options.timeout === null && opcodes.length > 0) {
167
+ options.timeout = ONE_MINUTE_SECONDS * (opcodes.length + 1);
168
+ ui.write("");
169
+ ui.write("The timeout was calculated automatically based on the number of opcodes.");
170
+ }
171
+ // Update properties to reflect the calculated timeout
172
+ const timeoutProperty = properties[2].children?.find((p) => p.key === "Timeout");
173
+ if (timeoutProperty) {
174
+ timeoutProperty.value =
175
+ options.timeout !== null ? `${options.timeout} seconds` : "not set";
176
+ }
177
+ const analyzer = await (0, exports.runOwnerHijackCheckAnalysis)(options.contract, contractPath, ui, options.timeout, options.methodId, opcodes, parsedArgs.verbose);
178
+ const vulnerability = analyzer.getVulnerability();
179
+ analyzer.reportVulnerability(vulnerability, constants_js_1.OWNER_HIJACK_DESCRIPTION_URL);
180
+ (0, utils_js_1.printCleanupInstructions)(ui);
181
+ if (vulnerability != null) {
182
+ (0, utils_js_1.printReproductionInstructions)(ui, analyzer.id);
183
+ process.exit(2);
184
+ }
185
+ };
186
+ const readNanotons = async (request, ui) => {
187
+ while (true) {
188
+ const userInput = await ui.input(request);
189
+ try {
190
+ return (0, core_1.toNano)(userInput);
191
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
192
+ }
193
+ catch (e) {
194
+ ui.write(`Your input (${userInput}) was of not correct nanoton format. Please try again.`);
195
+ }
196
+ }
197
+ };
198
+ const ownerHijackCheckConcrete = async (config, concreteCheckerOptions, completionMessage = "Analysis complete.") => {
199
+ const { ui } = config;
200
+ if (!(0, fs_1.existsSync)(config.codePath)) {
201
+ ui.write(`\n${constants_js_1.Sym.ERR} Code at ${config.codePath} not found`);
202
+ process.exit(1);
203
+ }
204
+ const timeout = config.timeout;
205
+ const properties = [
206
+ { key: "Contract", value: config.contractAddress.toRawString() },
207
+ { key: "Mode", value: "TON owner hijack reproduction" },
208
+ {
209
+ key: "Options",
210
+ separator: true,
211
+ children: [
212
+ {
213
+ key: "Timeout",
214
+ value: timeout !== null ? `${timeout} seconds` : "not set",
215
+ },
216
+ {
217
+ key: "Method id",
218
+ value: timeout !== null ? `${timeout} seconds` : "not set",
219
+ },
220
+ { key: "Sender", value: config.senderAddress.toRawString() },
221
+ ],
222
+ },
223
+ ];
224
+ const maxTons = await readNanotons("Enter maximum amount of TONs for reproduction message:", ui);
225
+ const checkerPath = (0, paths_js_1.getCheckerPath)(constants_js_1.OWNER_HIJACK_CHECK_CONCRETE_FILENAME);
226
+ const getMethodId = () => {
227
+ const stringedMethodId = concreteCheckerOptions.methodId;
228
+ try {
229
+ return BigInt(stringedMethodId);
230
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
231
+ }
232
+ catch (error) {
233
+ throw new Error(`Invalid BigInt string format (${stringedMethodId}) stored as methodId`);
234
+ }
235
+ };
236
+ const methodId = getMethodId();
237
+ console.log(`methodId=${methodId} maxTons=${maxTons} address=${config.senderAddress.toRawString()}`);
238
+ const checkerCell = (0, core_1.beginCell)()
239
+ .storeInt(methodId, 32)
240
+ .storeAddress(config.senderAddress)
241
+ .storeCoins(maxTons)
242
+ .endCell();
243
+ const analyzer = new analyzer_wrapper_js_1.AnalyzerWrapper({
244
+ ui,
245
+ checkerPath,
246
+ checkerCell,
247
+ properties,
248
+ codePath: config.codePath,
249
+ });
250
+ await analyzer.run(constants_js_1.OWNER_HIJACK_CHECK_CONCRETE_FILENAME, (wrapper) => [
251
+ "custom-checker-compiled",
252
+ "--checker",
253
+ wrapper.getTempBocPath(),
254
+ "--contract",
255
+ config.codePath,
256
+ "--data",
257
+ config.dataPath,
258
+ "--balance",
259
+ config.balance.toString(),
260
+ "--address",
261
+ config.contractAddress.toRawString(),
262
+ "--stop-when-exit-codes-found",
263
+ constants_js_1.ERROR_EXIT_CODE.toString(),
264
+ "--checker-data",
265
+ wrapper.getTempCheckerCellPath(),
266
+ "--output",
267
+ (0, paths_js_1.getSarifReportPath)(wrapper.id),
268
+ "--disable-out-message-analysis",
269
+ "--exported-inputs",
270
+ (0, paths_js_1.getReportDirectory)(wrapper.id),
271
+ ...(config.timeout != null
272
+ ? ["--timeout", config.timeout.toString()]
273
+ : []),
274
+ "-v",
275
+ ], completionMessage);
276
+ const vulnerability = analyzer.getVulnerability();
277
+ if (vulnerability == null) {
278
+ ui.write(`${constants_js_1.Sym.WARN} Vulnerability couldn't be reproduced with concrete data.`);
279
+ return null;
280
+ }
281
+ if (vulnerability.value == null) {
282
+ throw new Error("Unexpected external message");
283
+ }
284
+ return {
285
+ address: config.contractAddress,
286
+ msgBody: vulnerability.msgBody,
287
+ suggestedValue: vulnerability.value,
288
+ };
289
+ };
290
+ exports.ownerHijackCheckConcrete = ownerHijackCheckConcrete;
@@ -0,0 +1,20 @@
1
+ import { UIProvider } from "@ton/blueprint";
2
+ import { CommandContext } from "../cli.js";
3
+ import { AnalyzerWrapper } from "../common/analyzer-wrapper.js";
4
+ export declare const configureReplayAttackCheckCommand: (context: CommandContext) => any;
5
+ interface SeqnoData {
6
+ getterName: string;
7
+ upperBound: number;
8
+ }
9
+ /**
10
+ * Runs replay attack check analysis and returns the analyzer wrapper
11
+ * @param contractName - Name of the contract
12
+ * @param contractPath - Path to the compiled contract
13
+ * @param ui - UI provider
14
+ * @param timeout - Analysis timeout in seconds
15
+ * @param verbose - Enable verbose output
16
+ * @param seqnoData - Add the seqno constraints on the checker
17
+ * @returns AnalyzerWrapper instance
18
+ */
19
+ export declare const runReplayAttackCheckAnalysis: (contractName: string, contractPath: string, ui: UIProvider, timeout: number | null, verbose?: boolean, completionMessage?: string, seqnoData?: SeqnoData | null) => Promise<AnalyzerWrapper>;
20
+ export {};
@@ -0,0 +1,149 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.runReplayAttackCheckAnalysis = exports.configureReplayAttackCheckCommand = void 0;
4
+ const fs_1 = require("fs");
5
+ const core_1 = require("@ton/core");
6
+ const analyzer_wrapper_js_1 = require("../common/analyzer-wrapper.js");
7
+ const constants_js_1 = require("../common/constants.js");
8
+ const build_utils_js_1 = require("../common/build-utils.js");
9
+ const utils_js_1 = require("../reproduce/utils.js");
10
+ const paths_js_1 = require("../common/paths.js");
11
+ const configureReplayAttackCheckCommand = (context) => {
12
+ return {
13
+ command: constants_js_1.REPLAY_ATTACK_CHECK_ID,
14
+ description: "Analyze contract for replay attack vulnerabilities",
15
+ builder: (yargs) => yargs
16
+ .option("timeout", {
17
+ alias: "t",
18
+ type: "number",
19
+ description: "Analysis timeout in seconds",
20
+ })
21
+ .option("contract", {
22
+ alias: "c",
23
+ type: "string",
24
+ description: "Contract name",
25
+ demandOption: true,
26
+ })
27
+ .option("seqno-method-name", {
28
+ alias: "s",
29
+ type: "string",
30
+ description: "Method name of a seqno getter method",
31
+ demandOption: false,
32
+ })
33
+ .option("seqno-restriction", {
34
+ alias: "r",
35
+ type: "number",
36
+ description: "The upper bound of a seqno. Only the seq numbers that satisfy the restrictions are considered in executions",
37
+ demandOption: false,
38
+ })
39
+ .option("verbose", {
40
+ alias: "v",
41
+ type: "boolean",
42
+ description: "Use debug output in TSA log",
43
+ }),
44
+ handler: async (argv) => {
45
+ await replayAttackCheckCommand(context, argv);
46
+ },
47
+ };
48
+ };
49
+ exports.configureReplayAttackCheckCommand = configureReplayAttackCheckCommand;
50
+ /**
51
+ * Runs replay attack check analysis and returns the analyzer wrapper
52
+ * @param contractName - Name of the contract
53
+ * @param contractPath - Path to the compiled contract
54
+ * @param ui - UI provider
55
+ * @param timeout - Analysis timeout in seconds
56
+ * @param verbose - Enable verbose output
57
+ * @param seqnoData - Add the seqno constraints on the checker
58
+ * @returns AnalyzerWrapper instance
59
+ */
60
+ const runReplayAttackCheckAnalysis = async (contractName, contractPath, ui, timeout, verbose = false, completionMessage = "Analysis complete.", seqnoData = null) => {
61
+ const checkerPath = (0, paths_js_1.getCheckerPath)(constants_js_1.REPLAY_ATTACK_CHECK_SYMBOLIC_FILENAME);
62
+ const properties = [
63
+ { key: "Contract", value: contractName },
64
+ { key: "Mode", value: "Replay attack check" },
65
+ {
66
+ key: "Options",
67
+ separator: true,
68
+ children: [
69
+ {
70
+ key: "Timeout",
71
+ value: timeout !== null ? `${timeout} seconds` : "not set",
72
+ },
73
+ {
74
+ key: "SeqnoData",
75
+ value: seqnoData !== null ? JSON.stringify(seqnoData) : "not set",
76
+ },
77
+ ],
78
+ },
79
+ ];
80
+ const checkerCell = seqnoData !== null
81
+ ? (0, core_1.beginCell)()
82
+ .storeUint(1, 1)
83
+ .storeUint((0, core_1.getMethodId)(seqnoData.getterName), 32)
84
+ .storeUint(seqnoData.upperBound, 256)
85
+ .endCell()
86
+ : (0, core_1.beginCell)().storeUint(0, 1).endCell();
87
+ const analyzer = new analyzer_wrapper_js_1.AnalyzerWrapper({
88
+ ui,
89
+ checkerPath,
90
+ checkerCell,
91
+ properties,
92
+ codePath: contractPath,
93
+ });
94
+ const reportDir = (0, paths_js_1.getReportDirectory)(analyzer.id);
95
+ const sarifPath = (0, paths_js_1.getSarifReportPath)(analyzer.id);
96
+ await analyzer.run(constants_js_1.REPLAY_ATTACK_CHECK_SYMBOLIC_FILENAME, (wrapper) => [
97
+ "custom-checker-compiled",
98
+ "--checker",
99
+ wrapper.getTempBocPath(),
100
+ "--contract",
101
+ contractPath,
102
+ "--stop-when-exit-codes-found",
103
+ constants_js_1.ERROR_EXIT_CODE.toString(),
104
+ "--checker-data",
105
+ wrapper.getTempCheckerCellPath(),
106
+ "--output",
107
+ sarifPath,
108
+ ...(timeout != null ? ["--timeout", timeout.toString()] : []),
109
+ "--exported-inputs",
110
+ reportDir,
111
+ ...(verbose ? ["-v"] : []),
112
+ "--continue-on-contract-exception",
113
+ "--disable-out-message-analysis",
114
+ ], completionMessage);
115
+ return analyzer;
116
+ };
117
+ exports.runReplayAttackCheckAnalysis = runReplayAttackCheckAnalysis;
118
+ const replayAttackCheckCommand = async (context, parsedArgs) => {
119
+ const { ui } = context;
120
+ await (0, build_utils_js_1.buildContracts)(ui);
121
+ if (!parsedArgs.contract) {
122
+ throw new Error("Contract name or path is required");
123
+ }
124
+ const contract = parsedArgs.contract;
125
+ const contractPath = (0, paths_js_1.findCompiledContract)(contract);
126
+ if (!(0, fs_1.existsSync)(contractPath)) {
127
+ ui.write(`\n${constants_js_1.Sym.ERR} Contract ${contract} not found`);
128
+ process.exit(1);
129
+ }
130
+ const timeout = parsedArgs.timeout ?? null;
131
+ const getSeqnoMethodName = parsedArgs.seqnoMethodName;
132
+ const getSeqnoRestriction = parsedArgs.seqnoRestriction;
133
+ var seqnoData = null;
134
+ if (getSeqnoMethodName !== undefined && getSeqnoRestriction !== undefined) {
135
+ seqnoData = {
136
+ getterName: getSeqnoMethodName,
137
+ upperBound: getSeqnoRestriction,
138
+ };
139
+ }
140
+ else if (getSeqnoRestriction !== undefined ||
141
+ getSeqnoMethodName !== undefined) {
142
+ ui.write(`\n${constants_js_1.Sym.ERR} you should specify either both the seqno getter method and seqno restriction or neither`);
143
+ process.exit(1);
144
+ }
145
+ const analyzer = await (0, exports.runReplayAttackCheckAnalysis)(contract, contractPath, ui, timeout, parsedArgs.verbose, "Analysis complete.", seqnoData);
146
+ const vulnerability = analyzer.getVulnerability();
147
+ analyzer.reportVulnerability(vulnerability, constants_js_1.REPLAY_DESCRIPTION_URL);
148
+ (0, utils_js_1.printCleanupInstructions)(ui);
149
+ };
@@ -0,0 +1,3 @@
1
+ import { CommandContext } from "../cli.js";
2
+ export declare const configureReproduceCommand: (context: CommandContext) => any;
3
+ export declare const executeReproduceCommand: (context: CommandContext, parsedArgs: any) => Promise<void>;
@@ -0,0 +1,102 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.executeReproduceCommand = exports.configureReproduceCommand = void 0;
4
+ const fs_1 = require("fs");
5
+ const core_1 = require("@ton/core");
6
+ const blueprint_1 = require("@ton/blueprint");
7
+ const network_js_1 = require("../reproduce/network.js");
8
+ const concrete_analysis_js_1 = require("../reproduce/concrete-analysis.js");
9
+ const constants_js_1 = require("../common/constants.js");
10
+ const utils_js_1 = require("../reproduce/utils.js");
11
+ const reproduce_config_js_1 = require("../reproduce/reproduce-config.js");
12
+ const format_utils_js_1 = require("../common/format-utils.js");
13
+ const configureReproduceCommand = (context) => ({
14
+ command: constants_js_1.REPRODUCE_ID,
15
+ description: "Reproduce found vulnerability",
16
+ builder: (yargs) => yargs.option("config", {
17
+ alias: "c",
18
+ type: "string",
19
+ description: "Path to the reproduction config",
20
+ demandOption: true,
21
+ }),
22
+ handler: async (argv) => await (0, exports.executeReproduceCommand)(context, argv),
23
+ });
24
+ exports.configureReproduceCommand = configureReproduceCommand;
25
+ async function checkAddressContainsExpectedData(network, queriedAddress, config, ui) {
26
+ const contractState = await network.getContractState(queriedAddress);
27
+ if (contractState.state.type !== "active") {
28
+ throw new Error(`Contract at ${queriedAddress.toString()} is not active`);
29
+ }
30
+ let dataMatches = false;
31
+ if (contractState.state.data) {
32
+ const actualData = core_1.Cell.fromBoc(contractState.state.data)[0];
33
+ dataMatches = actualData.equals(config.data);
34
+ }
35
+ if (dataMatches) {
36
+ ui.write(`${constants_js_1.Sym.OK} The data stored at contract matches the expected data.`);
37
+ ui.write(`${constants_js_1.Sym.OK} Balance: ${(0, format_utils_js_1.nanotonToTon)(contractState.balance)}.`);
38
+ }
39
+ else {
40
+ ui.write(`${constants_js_1.Sym.ERR} Contract data on the contract does not match data on the config`);
41
+ process.exit(1);
42
+ }
43
+ return contractState;
44
+ }
45
+ const executeReproduceCommand = async (context, parsedArgs) => {
46
+ const { ui } = context;
47
+ const reproduceConfigPath = parsedArgs.config;
48
+ if (!reproduceConfigPath) {
49
+ throw new Error("Please specify the reproduction config file");
50
+ }
51
+ const configJsonResult = reproduce_config_js_1.TsaVulnerabilityConfigSchema.safeParse(JSON.parse((0, fs_1.readFileSync)(reproduceConfigPath, "utf-8")));
52
+ if (!configJsonResult.success) {
53
+ ui.write("Failed to parse reproduce config file");
54
+ process.exit(1);
55
+ }
56
+ const configJson = configJsonResult.data;
57
+ const network = await (0, blueprint_1.createNetworkProvider)(ui, { _: [] });
58
+ if (configJson.mode === constants_js_1.DEPLOY_AND_REPRODUCE_COMMAND) {
59
+ const codeHex = JSON.parse((0, fs_1.readFileSync)(configJson.codePath, "utf-8")).hex;
60
+ const dataBinary = (0, fs_1.readFileSync)(configJson.dataPath);
61
+ const config = {
62
+ code: core_1.Cell.fromBoc(Buffer.from(codeHex, "hex"))[0],
63
+ data: core_1.Cell.fromBoc(dataBinary)[0],
64
+ suggestedBalance: BigInt(configJson.suggestedBalance),
65
+ suggestedValue: BigInt(configJson.suggestedValue),
66
+ };
67
+ const useExistingContract = await ui.prompt("Do you want to reuse an already deployed contract?");
68
+ const getUserInputAddress = async () => {
69
+ return await ui.inputAddress("Input the address to deploy contract to");
70
+ };
71
+ const deployChameleon = async () => {
72
+ const nonces = Array.from(Array(8), () => BigInt(Math.floor(Math.random() * (1 << 29))));
73
+ const deployResult = await (0, network_js_1.deployViaChameleon)(network, config, nonces);
74
+ return deployResult.address;
75
+ };
76
+ const address = useExistingContract
77
+ ? await getUserInputAddress()
78
+ : await deployChameleon();
79
+ const contractState = await checkAddressContainsExpectedData(network, address, config, ui);
80
+ const senderAddress = network.sender().address;
81
+ if (!senderAddress) {
82
+ throw new Error("Sender address is not available");
83
+ }
84
+ const concreteAnalysisConfig = {
85
+ codePath: configJson.codePath,
86
+ dataPath: configJson.dataPath,
87
+ balance: contractState.balance,
88
+ contractAddress: address,
89
+ senderAddress,
90
+ ui,
91
+ timeout: configJson.timeout,
92
+ concreteCheckerOptions: configJson.concreteCheckerOptions,
93
+ };
94
+ const vulnerability = await (0, concrete_analysis_js_1.runConcreteAnalysis)(configJson.command, concreteAnalysisConfig);
95
+ if (vulnerability == null) {
96
+ return;
97
+ }
98
+ (0, utils_js_1.printCleanupInstructions)(ui);
99
+ await (0, network_js_1.reproduce)(network, vulnerability);
100
+ }
101
+ };
102
+ exports.executeReproduceCommand = executeReproduceCommand;
@@ -0,0 +1,69 @@
1
+ import { UIProvider } from "@ton/blueprint";
2
+ import { Cell } from "@ton/core";
3
+ import { TreeProperty } from "./draw.js";
4
+ /**
5
+ * Configuration for analyzer wrapper
6
+ */
7
+ export interface AnalyzerWrapperConfig {
8
+ ui: UIProvider;
9
+ checkerPath: string | null;
10
+ checkerCell: Cell;
11
+ properties: TreeProperty[];
12
+ codePath: string;
13
+ }
14
+ export interface VulnerabilityDescription {
15
+ value: bigint | null;
16
+ balance: bigint;
17
+ dataPath: string;
18
+ codePath: string;
19
+ executionIndex: number;
20
+ msgBody: Cell;
21
+ }
22
+ /**
23
+ * Generic wrapper for checker-based vulnerability analysis
24
+ * Handles common logic for compiling, running, and cleaning up checker analysis
25
+ */
26
+ export declare class AnalyzerWrapper {
27
+ private config;
28
+ private tempBocPath;
29
+ private tempCheckerCellPath;
30
+ id: string;
31
+ constructor(config: AnalyzerWrapperConfig);
32
+ /**
33
+ * Prints analysis information to UI
34
+ */
35
+ private printAnalysisInfo;
36
+ /**
37
+ * Validates that checker file exists
38
+ */
39
+ private validateCheckerFile;
40
+ /**
41
+ * Compiles FunC checker to BoC and writes to temporary file
42
+ */
43
+ private compileChecker;
44
+ /**
45
+ * Writes checker cell to temporary BoC file
46
+ */
47
+ private writeCheckerCell;
48
+ /**
49
+ * Cleans up temporary files
50
+ */
51
+ private cleanup;
52
+ /**
53
+ * Gets the temporary BoC file path (for use in analyzer arguments)
54
+ */
55
+ getTempBocPath(): string;
56
+ /**
57
+ * Gets the temporary checker cell file path (for use in analyzer arguments)
58
+ */
59
+ getTempCheckerCellPath(): string;
60
+ /**
61
+ * Runs the checker analysis with custom analyzer arguments
62
+ * @param checkerFilename - Name of the checker file to compile
63
+ * @param buildArgs - Callback function to build analyzer arguments after compilation
64
+ */
65
+ run(checkerFilename: string | null, buildArgs: (wrapper: this) => string[], completionMessage?: string): Promise<void>;
66
+ getVulnerability(): VulnerabilityDescription | null;
67
+ vulnerabilityIsPresent(): boolean;
68
+ reportVulnerability(vulnerability: VulnerabilityDescription | null, descriptionUrl?: string): void;
69
+ }