blueprint-tsa 1.1.0 → 1.1.2

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.
@@ -89,7 +89,7 @@ function buildCheckResult(checkName, analyzer, passedMessage, failedMessage, con
89
89
  }
90
90
  return result;
91
91
  }
92
- async function runOpcodeInfoCheck(contractName, contractPath, ui, timeout, opcodes, verbose, legacyAnalysisArtifacts, iterationLimit, recursionLimit) {
92
+ async function runOpcodeInfoCheck(contractName, contractPath, ui, timeout, opcodes, verbose, legacyAnalysisArtifacts, iterationLimit, recursionLimit, interactive) {
93
93
  // Calculate timeout per opcode
94
94
  let opcodeTimeout = null;
95
95
  if (timeout !== null && opcodes.length > 0) {
@@ -103,6 +103,7 @@ async function runOpcodeInfoCheck(contractName, contractPath, ui, timeout, opcod
103
103
  contract: contractName,
104
104
  iterationLimit,
105
105
  recursionLimit,
106
+ interactive,
106
107
  legacyAnalysisArtifacts,
107
108
  };
108
109
  const results = [];
@@ -118,13 +119,14 @@ async function runDrainCheck(contractName, contractPath, ui, commonArgs) {
118
119
  const analyzer = await (0, drain_check_js_1.runDrainCheckAnalysis)(ui, contractPath, commonArgs, `${constants_js_1.DRAIN_CHECK_NAME} completed.`);
119
120
  return buildCheckResult(constants_js_1.DRAIN_CHECK_NAME, analyzer, "No drain vulnerabilities detected", "Vulnerability found - contract may be vulnerable to drain attacks", contractName, commonArgs.timeout);
120
121
  }
121
- async function runReplayAttackCheck(contractName, contractPath, ui, timeout, verbose, iterationLimit, recursionLimit, legacyAnalysisArtifacts) {
122
+ async function runReplayAttackCheck(contractName, contractPath, ui, timeout, verbose, iterationLimit, recursionLimit, legacyAnalysisArtifacts, interactive) {
122
123
  const analyzer = await (0, replay_attack_check_js_1.runReplayAttackCheckAnalysis)(ui, contractPath, {
123
124
  timeout,
124
125
  verbose,
125
126
  contract: contractName,
126
127
  iterationLimit,
127
128
  recursionLimit,
129
+ interactive,
128
130
  legacyAnalysisArtifacts,
129
131
  }, null, `${constants_js_1.REPLAY_ATTACK_CHECK_NAME} completed.`);
130
132
  return buildCheckResult(constants_js_1.REPLAY_ATTACK_CHECK_NAME, analyzer, "No replay attack vulnerabilities detected", "Vulnerability found - contract may be vulnerable to replay attacks", contractName, timeout);
@@ -222,7 +224,7 @@ const auditCommand = async (ui, parsedArgs) => {
222
224
  const ownerMethod = parsedArgs["owner-method"];
223
225
  const disableOpcodeExtraction = parsedArgs["disable-opcode-extraction"];
224
226
  const verbose = parsedArgs.verbose;
225
- await (0, build_utils_js_1.buildAllContracts)(ui);
227
+ await (0, build_utils_js_1.buildAllContracts)(ui, parsedArgs.interactive);
226
228
  const contractPath = (0, paths_js_1.findCompiledContract)(contractName);
227
229
  if (!(0, fs_1.existsSync)(contractPath)) {
228
230
  ui.write(`\n${constants_js_1.Sym.ERR} Contract ${contractName} not found`);
@@ -254,6 +256,7 @@ const auditCommand = async (ui, parsedArgs) => {
254
256
  ui,
255
257
  codePath: contractPath,
256
258
  contractName,
259
+ interactive: parsedArgs.interactive,
257
260
  });
258
261
  }
259
262
  // Calculate timeout if not provided
@@ -283,7 +286,7 @@ const auditCommand = async (ui, parsedArgs) => {
283
286
  // Run opcode-info check
284
287
  ui.write("");
285
288
  ui.write(`${constants_js_1.Sym.WAIT} Step 1/${checkCount}: running opcode authorization analysis...`);
286
- summary.opcodeInfo = await runOpcodeInfoCheck(contractName, contractPath, ui, effectiveTimeout, opcodes, verbose ?? false, parsedArgs[common_analyzer_args_js_1.VERBOSE_ANALYSIS_ARTIFACTS_OPTION], parsedArgs[common_analyzer_args_js_1.ITERATION_LIMIT_OPTION], parsedArgs[common_analyzer_args_js_1.RECURSION_LIMIT_OPTION]);
289
+ summary.opcodeInfo = await runOpcodeInfoCheck(contractName, contractPath, ui, effectiveTimeout, opcodes, verbose ?? false, parsedArgs[common_analyzer_args_js_1.VERBOSE_ANALYSIS_ARTIFACTS_OPTION], parsedArgs[common_analyzer_args_js_1.ITERATION_LIMIT_OPTION], parsedArgs[common_analyzer_args_js_1.RECURSION_LIMIT_OPTION], parsedArgs.interactive);
287
290
  // Run drain-check
288
291
  ui.write("");
289
292
  ui.write(`${constants_js_1.Sym.WAIT} Step 2/${checkCount}: running drain check...`);
@@ -302,7 +305,7 @@ const auditCommand = async (ui, parsedArgs) => {
302
305
  // Run replay-attack-check
303
306
  ui.write("");
304
307
  ui.write(`${constants_js_1.Sym.WAIT} Step 3/${checkCount}: running replay attack check...`);
305
- const replayResult = await runReplayAttackCheck(contractName, contractPath, ui, effectiveTimeout, verbose ?? false, commonArgs.iterationLimit, commonArgs.recursionLimit, commonArgs.legacyAnalysisArtifacts ?? false);
308
+ const replayResult = await runReplayAttackCheck(contractName, contractPath, ui, effectiveTimeout, verbose ?? false, commonArgs.iterationLimit, commonArgs.recursionLimit, commonArgs.legacyAnalysisArtifacts ?? false, parsedArgs.interactive);
306
309
  summary.checks.push(replayResult);
307
310
  // Run bounce-check
308
311
  ui.write("");
@@ -63,6 +63,7 @@ const runBounceCheckAnalysis = async (ui, contractPath, commonArgs, completionMe
63
63
  checkerCell,
64
64
  properties,
65
65
  codePath: contractPath,
66
+ interactive: commonArgs.interactive ?? true,
66
67
  legacyAnalysisArtifacts: commonArgs.legacyAnalysisArtifacts,
67
68
  });
68
69
  const reportDir = (0, paths_js_1.getReportDirectory)(analyzer.id);
@@ -94,7 +95,7 @@ const runBounceCheckAnalysis = async (ui, contractPath, commonArgs, completionMe
94
95
  exports.runBounceCheckAnalysis = runBounceCheckAnalysis;
95
96
  const bounceCheckCommand = async (ui, parsedArgs) => {
96
97
  const contractName = parsedArgs.contract;
97
- await (0, build_utils_js_1.buildAllContracts)(ui);
98
+ await (0, build_utils_js_1.buildAllContracts)(ui, parsedArgs.interactive);
98
99
  const contractPath = (0, command_utils_js_1.resolveBuiltContract)(ui, contractName);
99
100
  const { opcodes, timeout } = await (0, command_utils_js_1.resolveOpcodesAndTimeout)(ui, contractName, contractPath, {
100
101
  disableOpcodeExtraction: parsedArgs["disable-opcode-extraction"],
@@ -33,7 +33,9 @@ const resolveBuiltContract = (ui, contractName) => {
33
33
  exports.resolveBuiltContract = resolveBuiltContract;
34
34
  const hasExplicitTimeout = (timeout) => timeout !== null && timeout !== undefined;
35
35
  exports.hasExplicitTimeout = hasExplicitTimeout;
36
- const isInteractiveEnabled = (interactive) => interactive !== false;
36
+ const isInteractiveEnabled = (interactive) => interactive !== false &&
37
+ process.stdin.isTTY === true &&
38
+ process.stdout.isTTY === true;
37
39
  exports.isInteractiveEnabled = isInteractiveEnabled;
38
40
  const confirmOpcodeExtractionWait = async (ui, options) => {
39
41
  if (!(0, exports.isInteractiveEnabled)(options.interactive)) {
@@ -89,6 +91,7 @@ const resolveOpcodesAndTimeout = async (ui, contractName, contractPath, opts) =>
89
91
  ui,
90
92
  codePath: contractPath,
91
93
  contractName,
94
+ interactive: opts.interactive,
92
95
  });
93
96
  }
94
97
  let timeout = opts.explicitTimeout ?? null;
@@ -0,0 +1,11 @@
1
+ import { CommandModule } from "yargs";
2
+ import { CommandContext } from "../cli.js";
3
+ declare const SUPPORTED_LANGUAGES: readonly ["Tolk", "FunC"];
4
+ export type CheckerLanguage = (typeof SUPPORTED_LANGUAGES)[number];
5
+ export interface StoredCheckerDraft {
6
+ checkerLanguage: CheckerLanguage;
7
+ contractName: string;
8
+ }
9
+ export declare function createCreateCheckerCommand(context: CommandContext): CommandModule<object, object>;
10
+ export declare function executeCreateCheckerCommand(context: CommandContext): Promise<void>;
11
+ export {};
@@ -0,0 +1,70 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.createCreateCheckerCommand = createCreateCheckerCommand;
7
+ exports.executeCreateCheckerCommand = executeCreateCheckerCommand;
8
+ const fs_1 = require("fs");
9
+ const path_1 = __importDefault(require("path"));
10
+ const constants_js_1 = require("../common/constants.js");
11
+ const paths_js_1 = require("../common/paths.js");
12
+ const CREATE_CHECKER_COMMAND = "create-checker";
13
+ const CREATE_CHECKER_DESCRIPTION = "Collect information for a user-defined checker template";
14
+ const CHECKERS_DIRECTORY_NAME = "checkers";
15
+ const CHECKER_CONFIG_FILE_EXTENSION = ".json";
16
+ const TOLK_LANGUAGE = "Tolk";
17
+ const FUNC_LANGUAGE = "FunC";
18
+ const SUPPORTED_LANGUAGES = [TOLK_LANGUAGE, FUNC_LANGUAGE];
19
+ const CHOOSE_CHECKER_LANGUAGE_MESSAGE = "Choose checker language";
20
+ const EMPTY_VALUE_MESSAGE = "Value cannot be empty. Please try again.";
21
+ function createCreateCheckerCommand(context) {
22
+ return {
23
+ command: CREATE_CHECKER_COMMAND,
24
+ describe: CREATE_CHECKER_DESCRIPTION,
25
+ builder: {},
26
+ handler: async () => {
27
+ await executeCreateCheckerCommand(context);
28
+ },
29
+ };
30
+ }
31
+ async function executeCreateCheckerCommand(context) {
32
+ const { ui } = context;
33
+ ui.write("Create custom checker draft");
34
+ ui.write("");
35
+ const checkerName = await promptRequiredValue(ui, "Checker name");
36
+ const checkerLanguage = await promptCheckerLanguage(ui);
37
+ const contractName = await promptRequiredValue(ui, "Contract name to check");
38
+ const checkerDraft = {
39
+ checkerLanguage,
40
+ contractName,
41
+ };
42
+ const draftPath = persistCheckerDraft(checkerName, checkerDraft);
43
+ ui.write("");
44
+ ui.write(`${constants_js_1.Sym.OK} Checker draft stored successfully.`);
45
+ ui.write(`Name: ${checkerName}`);
46
+ ui.write(`Language: ${checkerDraft.checkerLanguage}`);
47
+ ui.write(`Contract: ${checkerDraft.contractName}`);
48
+ ui.write(`Saved to: ${draftPath}`);
49
+ }
50
+ async function promptRequiredValue(ui, label) {
51
+ while (true) {
52
+ const value = (await ui.input(label)).trim();
53
+ if (value.length > 0) {
54
+ return value;
55
+ }
56
+ ui.write(`${constants_js_1.Sym.WARN} ${EMPTY_VALUE_MESSAGE}`);
57
+ }
58
+ }
59
+ async function promptCheckerLanguage(ui) {
60
+ const languageChoices = [...SUPPORTED_LANGUAGES];
61
+ return await ui.choose(CHOOSE_CHECKER_LANGUAGE_MESSAGE, languageChoices, (language) => language);
62
+ }
63
+ function persistCheckerDraft(checkerName, draft) {
64
+ const checkersDirectory = path_1.default.join((0, paths_js_1.findTSAProjectDirectory)(), CHECKERS_DIRECTORY_NAME);
65
+ (0, fs_1.mkdirSync)(checkersDirectory, { recursive: true });
66
+ const fileName = `${checkerName}${CHECKER_CONFIG_FILE_EXTENSION}`;
67
+ const filePath = path_1.default.join(checkersDirectory, fileName);
68
+ (0, fs_1.writeFileSync)(filePath, `${JSON.stringify(draft, null, 2)}\n`);
69
+ return filePath;
70
+ }
@@ -50,6 +50,7 @@ const runDrainCheckAnalysis = async (ui, contractPath, commonArgs, completionMes
50
50
  checkerCell: (0, core_1.beginCell)().endCell(),
51
51
  properties,
52
52
  codePath: contractPath,
53
+ interactive: commonArgs.interactive ?? true,
53
54
  legacyAnalysisArtifacts: commonArgs.legacyAnalysisArtifacts,
54
55
  });
55
56
  const reportDir = (0, paths_js_1.getReportDirectory)(analyzer.id);
@@ -82,7 +83,7 @@ const runDrainCheckAnalysis = async (ui, contractPath, commonArgs, completionMes
82
83
  exports.runDrainCheckAnalysis = runDrainCheckAnalysis;
83
84
  const drainCheckCommand = async (ui, parsedArgs) => {
84
85
  const contractName = parsedArgs.contract;
85
- await (0, build_utils_js_1.buildAllContracts)(ui);
86
+ await (0, build_utils_js_1.buildAllContracts)(ui, parsedArgs.interactive);
86
87
  const contractPath = (0, command_utils_js_1.resolveBuiltContract)(ui, contractName);
87
88
  const { opcodes, timeout } = await (0, command_utils_js_1.resolveOpcodesAndTimeout)(ui, contractName, contractPath, {
88
89
  disableOpcodeExtraction: parsedArgs["disable-opcode-extraction"],
@@ -145,6 +146,7 @@ const drainCheckConcrete = async (ui, config, completionMessage = "Analysis comp
145
146
  checkerCell,
146
147
  properties,
147
148
  codePath: config.codePath,
149
+ interactive: true,
148
150
  });
149
151
  await analyzer.run(constants_js_1.DRAIN_CHECK_CONCRETE_FILENAME, (wrapper) => [
150
152
  "custom-checker-compiled",
@@ -64,6 +64,7 @@ async function runOpcodeAuthorizationCheckAnalysis(opcode, contractPath, ui, com
64
64
  checkerCell,
65
65
  properties,
66
66
  codePath: contractPath,
67
+ interactive: commonArgs.interactive ?? true,
67
68
  legacyAnalysisArtifacts: commonArgs.legacyAnalysisArtifacts,
68
69
  });
69
70
  const sarifPath = (0, paths_js_1.getSarifReportPath)(analyzer.id);
@@ -152,7 +153,7 @@ function formatOpcodeInfo(infos) {
152
153
  const opcodeInfoHandler = async (context, args) => {
153
154
  const { ui } = context;
154
155
  const { timeout, contract, verbose } = args;
155
- await (0, build_utils_js_1.buildAllContracts)(ui);
156
+ await (0, build_utils_js_1.buildAllContracts)(ui, args.interactive);
156
157
  const codePath = (0, paths_js_1.findCompiledContract)(contract);
157
158
  if (!(0, fs_1.existsSync)(codePath)) {
158
159
  ui.write(`\n${constants_js_1.Sym.ERR} Contract ${contract} not found`);
@@ -167,6 +168,7 @@ const opcodeInfoHandler = async (context, args) => {
167
168
  ui,
168
169
  codePath,
169
170
  contractName: contract,
171
+ interactive: args.interactive,
170
172
  });
171
173
  if (opcodes.length === 0) {
172
174
  ui.write("");
@@ -61,6 +61,7 @@ const runOwnerHijackCheckAnalysis = async (ui, contractPath, methodId, commonArg
61
61
  checkerCell,
62
62
  properties,
63
63
  codePath: contractPath,
64
+ interactive: commonArgs.interactive ?? true,
64
65
  legacyAnalysisArtifacts: commonArgs.legacyAnalysisArtifacts,
65
66
  });
66
67
  const reportDir = (0, paths_js_1.getReportDirectory)(analyzer.id);
@@ -96,7 +97,7 @@ exports.runOwnerHijackCheckAnalysis = runOwnerHijackCheckAnalysis;
96
97
  const ownerHijackCheckCommand = async (ui, parsedArgs) => {
97
98
  const contractName = parsedArgs.contract;
98
99
  const methodId = BigInt((0, core_1.getMethodId)(parsedArgs["method-name"]));
99
- await (0, build_utils_js_1.buildAllContracts)(ui);
100
+ await (0, build_utils_js_1.buildAllContracts)(ui, parsedArgs.interactive);
100
101
  const contractPath = (0, command_utils_js_1.resolveBuiltContract)(ui, contractName);
101
102
  const { opcodes, timeout } = await (0, command_utils_js_1.resolveOpcodesAndTimeout)(ui, contractName, contractPath, {
102
103
  disableOpcodeExtraction: parsedArgs["disable-opcode-extraction"],
@@ -171,6 +172,7 @@ const ownerHijackCheckConcrete = async (ui, config, concreteCheckerOptions, comp
171
172
  checkerCell,
172
173
  properties,
173
174
  codePath: config.codePath,
175
+ interactive: true,
174
176
  });
175
177
  await analyzer.run(constants_js_1.OWNER_HIJACK_CHECK_CONCRETE_FILENAME, (wrapper) => [
176
178
  "custom-checker-compiled",
@@ -74,6 +74,7 @@ const runReplayAttackCheckAnalysis = async (ui, contractPath, commonArgs, seqnoD
74
74
  checkerCell,
75
75
  properties,
76
76
  codePath: contractPath,
77
+ interactive: commonArgs.interactive ?? true,
77
78
  legacyAnalysisArtifacts: commonArgs.legacyAnalysisArtifacts,
78
79
  });
79
80
  const reportDir = (0, paths_js_1.getReportDirectory)(analyzer.id);
@@ -111,7 +112,7 @@ const resolveSeqnoData = (ui, seqnoMethodName, seqnoRestriction) => {
111
112
  };
112
113
  const replayAttackCheckCommand = async (ui, parsedArgs) => {
113
114
  const contractName = parsedArgs.contract;
114
- await (0, build_utils_js_1.buildAllContracts)(ui);
115
+ await (0, build_utils_js_1.buildAllContracts)(ui, parsedArgs.interactive);
115
116
  const contractPath = (0, command_utils_js_1.resolveBuiltContract)(ui, contractName);
116
117
  const timeout = parsedArgs.timeout ?? null;
117
118
  const seqnoData = resolveSeqnoData(ui, parsedArgs["seqno-method-name"], parsedArgs["seqno-restriction"]);
@@ -10,6 +10,7 @@ export interface AnalyzerWrapperConfig {
10
10
  checkerCell: Cell;
11
11
  properties: TreeProperty[];
12
12
  codePath: string;
13
+ interactive: boolean;
13
14
  legacyAnalysisArtifacts?: boolean;
14
15
  expectsSarifReport?: boolean;
15
16
  }
@@ -27,6 +28,7 @@ export declare class AnalyzerWrapper {
27
28
  private tempCheckerCellPath;
28
29
  private progressTimer;
29
30
  id: string;
31
+ private isInteractiveEnabled;
30
32
  constructor(config: AnalyzerWrapperConfig);
31
33
  /**
32
34
  * Prints analysis information to UI
@@ -15,10 +15,6 @@ const draw_js_1 = require("./draw.js");
15
15
  const format_utils_js_1 = require("./format-utils.js");
16
16
  const result_parsing_js_1 = require("./result-parsing.js");
17
17
  const paths_js_1 = require("./paths.js");
18
- /**
19
- * Generic wrapper for checker-based vulnerability analysis
20
- * Handles common logic for compiling, running, and cleaning up checker analysis
21
- */
22
18
  const ANALYZER_RUNNING_MESSAGE = "Running TSA analysis...";
23
19
  const ANALYZER_SUCCESS_LOG_PREFIX = "TSA run log saved to:";
24
20
  const ANALYZER_NON_EMPTY_LOG_MESSAGE = "TSA produced additional log output. Check the log file for details.";
@@ -38,6 +34,11 @@ const MESSAGE_BODY_INPUT_KEY = "messageBody";
38
34
  const CONTRACT_DATA_INPUT_KEY = "contractData";
39
35
  const INDENT_SIZE = 2;
40
36
  class AnalyzerWrapper {
37
+ isInteractiveEnabled() {
38
+ return (this.config.interactive !== false &&
39
+ process.stdin.isTTY === true &&
40
+ process.stdout.isTTY === true);
41
+ }
41
42
  constructor(config) {
42
43
  this.tempBocPath = null;
43
44
  this.tempCheckerCellPath = null;
@@ -74,7 +75,9 @@ class AnalyzerWrapper {
74
75
  if (this.config.checkerPath == null) {
75
76
  return;
76
77
  }
77
- this.config.ui.setActionPrompt(`${constants_js_1.Sym.WAIT} Compiling checker...`);
78
+ if (this.isInteractiveEnabled()) {
79
+ this.config.ui.setActionPrompt(`${constants_js_1.Sym.WAIT} Compiling checker...`);
80
+ }
78
81
  try {
79
82
  const bocCode = await (0, build_utils_js_1.compileFuncFileToBase64Boc)(this.config.checkerPath, checkerFilename);
80
83
  const bocBuffer = Buffer.from(bocCode, "base64");
@@ -82,7 +85,9 @@ class AnalyzerWrapper {
82
85
  (0, fs_1.writeFileSync)(this.tempBocPath, bocBuffer);
83
86
  }
84
87
  catch (error) {
85
- this.config.ui.clearActionPrompt();
88
+ if (this.isInteractiveEnabled()) {
89
+ this.config.ui.clearActionPrompt();
90
+ }
86
91
  this.config.ui.write(`\n${constants_js_1.Sym.ERR} Compilation failed: ${error.message}`);
87
92
  process.exit(1);
88
93
  }
@@ -140,6 +145,9 @@ class AnalyzerWrapper {
140
145
  }
141
146
  startProgressBar(timeoutSeconds) {
142
147
  this.stopProgressBar();
148
+ if (!this.isInteractiveEnabled()) {
149
+ return;
150
+ }
143
151
  if (timeoutSeconds === null) {
144
152
  this.config.ui.setActionPrompt(`${constants_js_1.Sym.WAIT} ${ANALYZER_RUNNING_MESSAGE}`);
145
153
  return;
@@ -161,7 +169,7 @@ class AnalyzerWrapper {
161
169
  }
162
170
  async showCompletedProgressBar(timeoutSeconds) {
163
171
  this.stopProgressBar();
164
- if (timeoutSeconds === null) {
172
+ if (!this.isInteractiveEnabled() || timeoutSeconds === null) {
165
173
  return;
166
174
  }
167
175
  this.config.ui.setActionPrompt(`${constants_js_1.Sym.WAIT} ${ANALYZER_RUNNING_MESSAGE} ${this.formatProgressBar(1)}`);
@@ -229,6 +237,7 @@ class AnalyzerWrapper {
229
237
  const reportDir = path_1.default.dirname((0, paths_js_1.getSummaryPath)(this.id));
230
238
  const sarifPath = (0, paths_js_1.getSarifReportPath)(this.id);
231
239
  const exploitExecutionIndex = (0, result_parsing_js_1.findExploitExecutionIndex)(sarifPath);
240
+ const shouldRemoveSarifReport = (0, result_parsing_js_1.isSarifResultsEmpty)(sarifPath) || (0, result_parsing_js_1.isSingleNonFailingResult)(sarifPath);
232
241
  const executionDirs = (0, fs_1.readdirSync)(reportDir)
233
242
  .filter((entry) => entry.startsWith(EXECUTION_DIRECTORY_PREFIX))
234
243
  .map((entry) => path_1.default.join(reportDir, entry))
@@ -267,6 +276,10 @@ class AnalyzerWrapper {
267
276
  (0, fs_1.rmSync)(extraContractDataDir, { recursive: true, force: true });
268
277
  }
269
278
  }
279
+ if (shouldRemoveSarifReport && (0, fs_1.existsSync)(sarifPath)) {
280
+ (0, fs_1.unlinkSync)(sarifPath);
281
+ }
282
+ this.removeDirectoryIfEmpty(reportDir);
270
283
  }
271
284
  /**
272
285
  * Runs the checker analysis with custom analyzer arguments
@@ -287,6 +300,7 @@ class AnalyzerWrapper {
287
300
  const timeoutValue = timeoutIndex >= 0 ? (analyzerArgs[timeoutIndex + 1] ?? null) : null;
288
301
  const timeoutSeconds = timeoutValue !== null ? Number.parseInt(timeoutValue, 10) : null;
289
302
  const analyzer = await analyzer_js_1.Analyzer.create();
303
+ (0, paths_js_1.getReportDirectory)(this.id); // report directory should be created here
290
304
  const logPath = (0, paths_js_1.getTsaRunLogPath)(this.id);
291
305
  this.startProgressBar(Number.isFinite(timeoutSeconds) ? timeoutSeconds : null);
292
306
  const result = await analyzer.run(analyzerArgs, logPath);
@@ -302,16 +316,7 @@ class AnalyzerWrapper {
302
316
  if (!(0, fs_1.existsSync)(sarifPath)) {
303
317
  throw new Error(MISSING_SARIF_ERROR_MESSAGE);
304
318
  }
305
- if (!this.usesVerboseAnalysisArtifacts() &&
306
- (0, result_parsing_js_1.isSarifResultsEmpty)(sarifPath)) {
307
- if (!hasLogOutput) {
308
- (0, fs_1.rmSync)((0, paths_js_1.getReportDirectory)(this.id), {
309
- recursive: true,
310
- force: true,
311
- });
312
- }
313
- }
314
- else {
319
+ if (!this.usesVerboseAnalysisArtifacts()) {
315
320
  this.normalizeExportedInputs();
316
321
  }
317
322
  }
@@ -379,12 +384,15 @@ class AnalyzerWrapper {
379
384
  return;
380
385
  }
381
386
  const summaryPath = (0, paths_js_1.getSummaryPath)(this.id);
382
- const typedInputLine = `Typed input: ${this.getResolvedTypedInputPath(vulnerability.executionIndex)}`;
387
+ const relativeSummaryPath = path_1.default.relative(process.cwd(), summaryPath);
388
+ const relativeTypedInputPath = path_1.default.relative(process.cwd(), this.getResolvedTypedInputPath(vulnerability.executionIndex));
389
+ const relativeSarifPath = path_1.default.relative(process.cwd(), sarifPath);
390
+ const typedInputLine = `Typed input: ${relativeTypedInputPath}`;
383
391
  const reportLines = [
384
392
  `${constants_js_1.Sym.WARN} Vulnerability found!`,
385
- `Summary path: ${summaryPath}`,
393
+ `Summary path: ${relativeSummaryPath}`,
386
394
  typedInputLine,
387
- `SARIF with full information: ${sarifPath}`,
395
+ `SARIF with full information: ${relativeSarifPath}`,
388
396
  ];
389
397
  if (descriptionUrl) {
390
398
  reportLines.push("");
@@ -1,3 +1,3 @@
1
1
  import { UIProvider } from "@ton/blueprint";
2
- export declare const buildAllContracts: (ui: UIProvider) => Promise<void>;
2
+ export declare const buildAllContracts: (ui: UIProvider, interactive: boolean) => Promise<void>;
3
3
  export declare const compileFuncFileToBase64Boc: (filePath: string, fileName: string) => Promise<string>;
@@ -9,8 +9,13 @@ const fs_1 = require("fs");
9
9
  const path_1 = __importDefault(require("path"));
10
10
  const func_js_1 = require("@ton-community/func-js");
11
11
  const constants_js_1 = require("./constants.js");
12
- const buildAllContracts = async (ui) => {
13
- ui.setActionPrompt(`${constants_js_1.Sym.WAIT} Compiling contracts...`);
12
+ const buildAllContracts = async (ui, interactive) => {
13
+ const isInteractiveTerminal = interactive &&
14
+ process.stdin.isTTY === true &&
15
+ process.stdout.isTTY === true;
16
+ if (isInteractiveTerminal) {
17
+ ui.setActionPrompt(`${constants_js_1.Sym.WAIT} Compiling contracts...`);
18
+ }
14
19
  try {
15
20
  await (0, blueprint_1.buildAll)(ui);
16
21
  }
@@ -4,9 +4,9 @@ export declare class Sym {
4
4
  static ERR: string;
5
5
  static WAIT: string;
6
6
  }
7
- export declare const TSA_VERSION = "v0.5.1";
8
- export declare const TSA_NAME = "tsa-cli-v0.5.1.jar";
9
- export declare const TSA_URL = "https://github.com/espritoxyz/tsa/releases/download/v0.5.1/tsa-cli.jar";
7
+ export declare const TSA_VERSION = "v0.5.4";
8
+ export declare const TSA_NAME = "tsa-cli-v0.5.4.jar";
9
+ export declare const TSA_URL = "https://github.com/espritoxyz/tsa/releases/download/v0.5.4/tsa-cli.jar";
10
10
  export declare const DRAIN_CHECK_SYMBOLIC_FILENAME = "drain-check-symbolic.fc";
11
11
  export declare const DRAIN_CHECK_CONCRETE_FILENAME = "drain-check-concrete.fc";
12
12
  export declare const DRAIN_CHECK_ID = "drain-check";
@@ -8,7 +8,7 @@ Sym.OK = "✅";
8
8
  Sym.WARN = "⚠️";
9
9
  Sym.ERR = "❌";
10
10
  Sym.WAIT = "⏳";
11
- exports.TSA_VERSION = "v0.5.1";
11
+ exports.TSA_VERSION = "v0.5.4";
12
12
  exports.TSA_NAME = `tsa-cli-${exports.TSA_VERSION}.jar`;
13
13
  exports.TSA_URL = `https://github.com/espritoxyz/tsa/releases/download/${exports.TSA_VERSION}/tsa-cli.jar`;
14
14
  exports.DRAIN_CHECK_SYMBOLIC_FILENAME = "drain-check-symbolic.fc";
@@ -3,5 +3,6 @@ export interface OpcodeExtractorConfig {
3
3
  ui: UIProvider;
4
4
  codePath: string;
5
5
  contractName: string;
6
+ interactive?: boolean;
6
7
  }
7
8
  export declare function extractOpcodes(config: OpcodeExtractorConfig): Promise<number[]>;
@@ -36,6 +36,7 @@ async function extractOpcodes(config) {
36
36
  checkerCell: new core_1.Cell(),
37
37
  properties,
38
38
  codePath: config.codePath,
39
+ interactive: config.interactive ?? true,
39
40
  expectsSarifReport: false,
40
41
  });
41
42
  const args = [
@@ -1,5 +1,6 @@
1
1
  export declare const findExploitExecutionIndex: (sarifPath: string) => number | undefined;
2
2
  export declare const findNonFailingExecution: (sarifPath: string) => number | undefined;
3
+ export declare const isSingleNonFailingResult: (sarifPath: string) => boolean;
3
4
  export declare const isSarifResultsEmpty: (sarifPath: string) => boolean;
4
5
  export declare const getMessageValue: (sarifPath: string, index: number) => bigint | null;
5
6
  export declare const getInitialBalance: (sarifPath: string, index: number) => bigint;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.getInitialBalance = exports.getMessageValue = exports.isSarifResultsEmpty = exports.findNonFailingExecution = exports.findExploitExecutionIndex = void 0;
3
+ exports.getInitialBalance = exports.getMessageValue = exports.isSarifResultsEmpty = exports.isSingleNonFailingResult = exports.findNonFailingExecution = exports.findExploitExecutionIndex = void 0;
4
4
  const fs_1 = require("fs");
5
5
  const constants_js_1 = require("./constants.js");
6
6
  const getSarifResults = (sarifPath) => {
@@ -24,6 +24,12 @@ const findNonFailingExecution = (sarifPath) => {
24
24
  return findExecutionByMessage(sarifPath, constants_js_1.EXPECTED_MESSAGE_NON_FAILING);
25
25
  };
26
26
  exports.findNonFailingExecution = findNonFailingExecution;
27
+ const isSingleNonFailingResult = (sarifPath) => {
28
+ const results = getSarifResults(sarifPath);
29
+ return (results.length === 1 &&
30
+ results[0]?.message?.text === constants_js_1.EXPECTED_MESSAGE_NON_FAILING);
31
+ };
32
+ exports.isSingleNonFailingResult = isSingleNonFailingResult;
27
33
  const isSarifResultsEmpty = (sarifPath) => {
28
34
  return getSarifResults(sarifPath).length === 0;
29
35
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "blueprint-tsa",
3
- "version": "1.1.0",
3
+ "version": "1.1.2",
4
4
  "description": "TSA plugin for TON Blueprint",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",