@snovon/solast 0.2.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 (738) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +190 -0
  3. package/dist/api.d.ts +89 -0
  4. package/dist/api.js +33 -0
  5. package/dist/ast/resolve-return-names.d.ts +2 -0
  6. package/dist/ast/resolve-return-names.js +199 -0
  7. package/dist/ast/solc-walker.d.ts +17 -0
  8. package/dist/ast/solc-walker.js +497 -0
  9. package/dist/ast/storage-layout.d.ts +21 -0
  10. package/dist/ast/storage-layout.js +64 -0
  11. package/dist/cli.d.ts +65 -0
  12. package/dist/cli.js +755 -0
  13. package/dist/config.d.ts +9 -0
  14. package/dist/config.js +284 -0
  15. package/dist/dedup/files.d.ts +1 -0
  16. package/dist/dedup/files.js +74 -0
  17. package/dist/dedup/findings.d.ts +41 -0
  18. package/dist/dedup/findings.js +211 -0
  19. package/dist/detectors/_common/access-control.d.ts +204 -0
  20. package/dist/detectors/_common/access-control.js +377 -0
  21. package/dist/detectors/_common/ast.d.ts +139 -0
  22. package/dist/detectors/_common/ast.js +239 -0
  23. package/dist/detectors/_common/compiler-profile.d.ts +14 -0
  24. package/dist/detectors/_common/compiler-profile.js +66 -0
  25. package/dist/detectors/_common/dataflow.d.ts +75 -0
  26. package/dist/detectors/_common/dataflow.js +57 -0
  27. package/dist/detectors/_common/fhe.d.ts +7 -0
  28. package/dist/detectors/_common/fhe.js +40 -0
  29. package/dist/detectors/_common/integer-overflow-helpers.d.ts +58 -0
  30. package/dist/detectors/_common/integer-overflow-helpers.js +422 -0
  31. package/dist/detectors/_common/loop-call-stack.d.ts +9 -0
  32. package/dist/detectors/_common/loop-call-stack.js +132 -0
  33. package/dist/detectors/_common/oracle.d.ts +5 -0
  34. package/dist/detectors/_common/oracle.js +64 -0
  35. package/dist/detectors/_common/price-rate.d.ts +116 -0
  36. package/dist/detectors/_common/price-rate.js +446 -0
  37. package/dist/detectors/_common/source-text.d.ts +11 -0
  38. package/dist/detectors/_common/source-text.js +82 -0
  39. package/dist/detectors/_common/weighted-pool-invariant.d.ts +21 -0
  40. package/dist/detectors/_common/weighted-pool-invariant.js +105 -0
  41. package/dist/detectors/aave-v2-reentrancy.d.ts +7 -0
  42. package/dist/detectors/aave-v2-reentrancy.js +286 -0
  43. package/dist/detectors/access-control.d.ts +103 -0
  44. package/dist/detectors/access-control.js +983 -0
  45. package/dist/detectors/add-reentrancy-on-weth-contract.d.ts +7 -0
  46. package/dist/detectors/add-reentrancy-on-weth-contract.js +536 -0
  47. package/dist/detectors/ai-generated-randomness.d.ts +32 -0
  48. package/dist/detectors/ai-generated-randomness.js +239 -0
  49. package/dist/detectors/amm-spot-oracle-manipulation.d.ts +52 -0
  50. package/dist/detectors/amm-spot-oracle-manipulation.js +420 -0
  51. package/dist/detectors/analyzing-the-uniswap-v3-exploit.d.ts +26 -0
  52. package/dist/detectors/analyzing-the-uniswap-v3-exploit.js +279 -0
  53. package/dist/detectors/any-token-is-destroyed.d.ts +34 -0
  54. package/dist/detectors/any-token-is-destroyed.js +527 -0
  55. package/dist/detectors/anyswap-anytoken-permit-allowance-drain.d.ts +7 -0
  56. package/dist/detectors/anyswap-anytoken-permit-allowance-drain.js +524 -0
  57. package/dist/detectors/anyswap-insufficient-token-validation.d.ts +24 -0
  58. package/dist/detectors/anyswap-insufficient-token-validation.js +342 -0
  59. package/dist/detectors/approval-based-drain.d.ts +7 -0
  60. package/dist/detectors/approval-based-drain.js +772 -0
  61. package/dist/detectors/arbitrary-account-balance-transfer.d.ts +7 -0
  62. package/dist/detectors/arbitrary-account-balance-transfer.js +485 -0
  63. package/dist/detectors/arbitrary-address-spoofing-attack.d.ts +7 -0
  64. package/dist/detectors/arbitrary-address-spoofing-attack.js +444 -0
  65. package/dist/detectors/arbitrary-address-spoofing.d.ts +9 -0
  66. package/dist/detectors/arbitrary-address-spoofing.js +657 -0
  67. package/dist/detectors/arbitrary-call-error.d.ts +127 -0
  68. package/dist/detectors/arbitrary-call-error.js +1163 -0
  69. package/dist/detectors/arbitrary-call.d.ts +4 -0
  70. package/dist/detectors/arbitrary-call.js +11 -0
  71. package/dist/detectors/arbitrary-delegatecall-target.d.ts +35 -0
  72. package/dist/detectors/arbitrary-delegatecall-target.js +554 -0
  73. package/dist/detectors/arbitrary-recipient-no-access-control.d.ts +7 -0
  74. package/dist/detectors/arbitrary-recipient-no-access-control.js +638 -0
  75. package/dist/detectors/arbitrary-storage-proof-forgery.d.ts +35 -0
  76. package/dist/detectors/arbitrary-storage-proof-forgery.js +340 -0
  77. package/dist/detectors/arbitrary-transfer-from.d.ts +38 -0
  78. package/dist/detectors/arbitrary-transfer-from.js +339 -0
  79. package/dist/detectors/arbitrum-cross-chain-message-replay.d.ts +22 -0
  80. package/dist/detectors/arbitrum-cross-chain-message-replay.js +477 -0
  81. package/dist/detectors/avs-slashing-without-quorum-check.d.ts +50 -0
  82. package/dist/detectors/avs-slashing-without-quorum-check.js +386 -0
  83. package/dist/detectors/bad-debt-propagation.d.ts +13 -0
  84. package/dist/detectors/bad-debt-propagation.js +480 -0
  85. package/dist/detectors/bad-k-value-verification.d.ts +7 -0
  86. package/dist/detectors/bad-k-value-verification.js +512 -0
  87. package/dist/detectors/bad-randomness-zero-blockhash.d.ts +29 -0
  88. package/dist/detectors/bad-randomness-zero-blockhash.js +115 -0
  89. package/dist/detectors/balancer-flash-loan-manipulation.d.ts +33 -0
  90. package/dist/detectors/balancer-flash-loan-manipulation.js +178 -0
  91. package/dist/detectors/balancer-pause-guard.d.ts +33 -0
  92. package/dist/detectors/balancer-pause-guard.js +307 -0
  93. package/dist/detectors/balancer-weighted-pool-flash-loan.d.ts +42 -0
  94. package/dist/detectors/balancer-weighted-pool-flash-loan.js +275 -0
  95. package/dist/detectors/batch-transfer-overflow.d.ts +7 -0
  96. package/dist/detectors/batch-transfer-overflow.js +465 -0
  97. package/dist/detectors/beneficiary-validation.d.ts +7 -0
  98. package/dist/detectors/beneficiary-validation.js +696 -0
  99. package/dist/detectors/borrow-behalf-consent.d.ts +7 -0
  100. package/dist/detectors/borrow-behalf-consent.js +400 -0
  101. package/dist/detectors/break-continue-scope.d.ts +7 -0
  102. package/dist/detectors/break-continue-scope.js +194 -0
  103. package/dist/detectors/bridge-accounting-bypass.d.ts +65 -0
  104. package/dist/detectors/bridge-accounting-bypass.js +449 -0
  105. package/dist/detectors/bridge-business-logic-flaw-incorrect-acc.d.ts +43 -0
  106. package/dist/detectors/bridge-business-logic-flaw-incorrect-acc.js +394 -0
  107. package/dist/detectors/bridge-collateral-drain.d.ts +7 -0
  108. package/dist/detectors/bridge-collateral-drain.js +630 -0
  109. package/dist/detectors/bridge-forged-proof.d.ts +7 -0
  110. package/dist/detectors/bridge-forged-proof.js +754 -0
  111. package/dist/detectors/bridge-missing-message-nonce.d.ts +57 -0
  112. package/dist/detectors/bridge-missing-message-nonce.js +638 -0
  113. package/dist/detectors/bridge-swap-metapool-attack.d.ts +20 -0
  114. package/dist/detectors/bridge-swap-metapool-attack.js +230 -0
  115. package/dist/detectors/business-logic-flaw-flashloan-price-mani.d.ts +7 -0
  116. package/dist/detectors/business-logic-flaw-flashloan-price-mani.js +353 -0
  117. package/dist/detectors/business-logic-flaw-incorrect-recipient-balance.d.ts +7 -0
  118. package/dist/detectors/business-logic-flaw-incorrect-recipient-balance.js +403 -0
  119. package/dist/detectors/business-logic-flaw.d.ts +21 -0
  120. package/dist/detectors/business-logic-flaw.js +339 -0
  121. package/dist/detectors/business-logic.d.ts +17 -0
  122. package/dist/detectors/business-logic.js +22 -0
  123. package/dist/detectors/bypassed-insolvency-check.d.ts +30 -0
  124. package/dist/detectors/bypassed-insolvency-check.js +232 -0
  125. package/dist/detectors/bytecode-divergence-risk.d.ts +32 -0
  126. package/dist/detectors/bytecode-divergence-risk.js +150 -0
  127. package/dist/detectors/cache-array-length.d.ts +30 -0
  128. package/dist/detectors/cache-array-length.js +177 -0
  129. package/dist/detectors/cache-storage-reads.d.ts +46 -0
  130. package/dist/detectors/cache-storage-reads.js +323 -0
  131. package/dist/detectors/calldata-secret-access-control.d.ts +36 -0
  132. package/dist/detectors/calldata-secret-access-control.js +446 -0
  133. package/dist/detectors/capital-cross-contract-reentrancy.d.ts +34 -0
  134. package/dist/detectors/capital-cross-contract-reentrancy.js +481 -0
  135. package/dist/detectors/cartel-custom-approval-logic.d.ts +7 -0
  136. package/dist/detectors/cartel-custom-approval-logic.js +407 -0
  137. package/dist/detectors/ccip-receiver-missing-replay-guard.d.ts +22 -0
  138. package/dist/detectors/ccip-receiver-missing-replay-guard.js +413 -0
  139. package/dist/detectors/chain-coupling-risk.d.ts +8 -0
  140. package/dist/detectors/chain-coupling-risk.js +203 -0
  141. package/dist/detectors/chainlink-deprecated-function.d.ts +7 -0
  142. package/dist/detectors/chainlink-deprecated-function.js +205 -0
  143. package/dist/detectors/chainlink-tx-origin.d.ts +7 -0
  144. package/dist/detectors/chainlink-tx-origin.js +363 -0
  145. package/dist/detectors/check-effects-interactions.d.ts +39 -0
  146. package/dist/detectors/check-effects-interactions.js +783 -0
  147. package/dist/detectors/check-permit-missing-chainid.d.ts +27 -0
  148. package/dist/detectors/check-permit-missing-chainid.js +456 -0
  149. package/dist/detectors/classic-reentrancy.d.ts +93 -0
  150. package/dist/detectors/classic-reentrancy.js +645 -0
  151. package/dist/detectors/coinbase-morpho-wethloan-policy.d.ts +29 -0
  152. package/dist/detectors/coinbase-morpho-wethloan-policy.js +368 -0
  153. package/dist/detectors/compoundv2-inflation-attack.d.ts +7 -0
  154. package/dist/detectors/compoundv2-inflation-attack.js +675 -0
  155. package/dist/detectors/constructor-address-validation.d.ts +24 -0
  156. package/dist/detectors/constructor-address-validation.js +335 -0
  157. package/dist/detectors/constructor-interface-no-address-validation.d.ts +32 -0
  158. package/dist/detectors/constructor-interface-no-address-validation.js +283 -0
  159. package/dist/detectors/cross-chain-arbitrary-call.d.ts +7 -0
  160. package/dist/detectors/cross-chain-arbitrary-call.js +601 -0
  161. package/dist/detectors/cross-chain-input-validation.d.ts +31 -0
  162. package/dist/detectors/cross-chain-input-validation.js +347 -0
  163. package/dist/detectors/cross-chain-intent-replay.d.ts +38 -0
  164. package/dist/detectors/cross-chain-intent-replay.js +453 -0
  165. package/dist/detectors/cross-chain-intent-stale-resolution.d.ts +7 -0
  166. package/dist/detectors/cross-chain-intent-stale-resolution.js +463 -0
  167. package/dist/detectors/cross-chain-message-order-dependency.d.ts +8 -0
  168. package/dist/detectors/cross-chain-message-order-dependency.js +472 -0
  169. package/dist/detectors/cross-chain-message-replay.d.ts +8 -0
  170. package/dist/detectors/cross-chain-message-replay.js +568 -0
  171. package/dist/detectors/cross-chain-messaging.d.ts +7 -0
  172. package/dist/detectors/cross-chain-messaging.js +663 -0
  173. package/dist/detectors/cross-chain-msg-truncation.d.ts +7 -0
  174. package/dist/detectors/cross-chain-msg-truncation.js +453 -0
  175. package/dist/detectors/cross-chain-truncation.d.ts +7 -0
  176. package/dist/detectors/cross-chain-truncation.js +422 -0
  177. package/dist/detectors/cross-contract-integer-overflow.d.ts +76 -0
  178. package/dist/detectors/cross-contract-integer-overflow.js +554 -0
  179. package/dist/detectors/cross-contract-reentrancy-trusted-callee.d.ts +39 -0
  180. package/dist/detectors/cross-contract-reentrancy-trusted-callee.js +385 -0
  181. package/dist/detectors/cross-contract-reentrancy.d.ts +63 -0
  182. package/dist/detectors/cross-contract-reentrancy.js +631 -0
  183. package/dist/detectors/cross-function-reentrancy.d.ts +37 -0
  184. package/dist/detectors/cross-function-reentrancy.js +648 -0
  185. package/dist/detectors/cross-protocol-contagion.d.ts +20 -0
  186. package/dist/detectors/cross-protocol-contagion.js +445 -0
  187. package/dist/detectors/cross-protocol-oracle-collateral.d.ts +38 -0
  188. package/dist/detectors/cross-protocol-oracle-collateral.js +487 -0
  189. package/dist/detectors/cross-vm-reentrancy.d.ts +7 -0
  190. package/dist/detectors/cross-vm-reentrancy.js +484 -0
  191. package/dist/detectors/decimals-mismatch.d.ts +89 -0
  192. package/dist/detectors/decimals-mismatch.js +451 -0
  193. package/dist/detectors/deferred-state-update.d.ts +16 -0
  194. package/dist/detectors/deferred-state-update.js +35 -0
  195. package/dist/detectors/deflationary-token.d.ts +27 -0
  196. package/dist/detectors/deflationary-token.js +751 -0
  197. package/dist/detectors/delegate-transfer-unrestricted-caller.d.ts +44 -0
  198. package/dist/detectors/delegate-transfer-unrestricted-caller.js +410 -0
  199. package/dist/detectors/delegatecall-fallback-reentrancy-bypass.d.ts +14 -0
  200. package/dist/detectors/delegatecall-fallback-reentrancy-bypass.js +241 -0
  201. package/dist/detectors/delegatecall-in-loops.d.ts +7 -0
  202. package/dist/detectors/delegatecall-in-loops.js +129 -0
  203. package/dist/detectors/delegatecall-init-owner-mutator.d.ts +8 -0
  204. package/dist/detectors/delegatecall-init-owner-mutator.js +655 -0
  205. package/dist/detectors/delegatecall-init.d.ts +7 -0
  206. package/dist/detectors/delegatecall-init.js +769 -0
  207. package/dist/detectors/delegatecall-untrusted-implementation.d.ts +41 -0
  208. package/dist/detectors/delegatecall-untrusted-implementation.js +888 -0
  209. package/dist/detectors/delegated-authorization-bypass.d.ts +7 -0
  210. package/dist/detectors/delegated-authorization-bypass.js +370 -0
  211. package/dist/detectors/denial-of-service.d.ts +117 -0
  212. package/dist/detectors/denial-of-service.js +947 -0
  213. package/dist/detectors/division-before-multiplication.d.ts +7 -0
  214. package/dist/detectors/division-before-multiplication.js +303 -0
  215. package/dist/detectors/dn404-mirror-access-control.d.ts +26 -0
  216. package/dist/detectors/dn404-mirror-access-control.js +315 -0
  217. package/dist/detectors/doge-flashloan.d.ts +29 -0
  218. package/dist/detectors/doge-flashloan.js +329 -0
  219. package/dist/detectors/donate-inflation-exchangerate-roundin.d.ts +7 -0
  220. package/dist/detectors/donate-inflation-exchangerate-roundin.js +621 -0
  221. package/dist/detectors/donation-share-inflation.d.ts +24 -0
  222. package/dist/detectors/donation-share-inflation.js +466 -0
  223. package/dist/detectors/dont-let-eth-get-rekt.d.ts +84 -0
  224. package/dist/detectors/dont-let-eth-get-rekt.js +1151 -0
  225. package/dist/detectors/dos-unbounded-loop-external-call-revert.d.ts +37 -0
  226. package/dist/detectors/dos-unbounded-loop-external-call-revert.js +541 -0
  227. package/dist/detectors/eip1167-proxy-reentrancy.d.ts +7 -0
  228. package/dist/detectors/eip1167-proxy-reentrancy.js +508 -0
  229. package/dist/detectors/eip4626-vault-reentrancy.d.ts +32 -0
  230. package/dist/detectors/eip4626-vault-reentrancy.js +312 -0
  231. package/dist/detectors/eip5792-auth-replay.d.ts +45 -0
  232. package/dist/detectors/eip5792-auth-replay.js +519 -0
  233. package/dist/detectors/eip712-domain-separator.d.ts +42 -0
  234. package/dist/detectors/eip712-domain-separator.js +524 -0
  235. package/dist/detectors/eip712-signature-verification.d.ts +49 -0
  236. package/dist/detectors/eip712-signature-verification.js +689 -0
  237. package/dist/detectors/eip7702-auth-replay.d.ts +7 -0
  238. package/dist/detectors/eip7702-auth-replay.js +768 -0
  239. package/dist/detectors/eip7702-cross-chain-replay.d.ts +27 -0
  240. package/dist/detectors/eip7702-cross-chain-replay.js +307 -0
  241. package/dist/detectors/eip7702-delegated-eoa-approval-race.d.ts +39 -0
  242. package/dist/detectors/eip7702-delegated-eoa-approval-race.js +413 -0
  243. package/dist/detectors/eip7702-delegation-reentrancy.d.ts +21 -0
  244. package/dist/detectors/eip7702-delegation-reentrancy.js +705 -0
  245. package/dist/detectors/eip7702-delegation-risk.d.ts +7 -0
  246. package/dist/detectors/eip7702-delegation-risk.js +745 -0
  247. package/dist/detectors/eip7702-eoa-assumption.d.ts +57 -0
  248. package/dist/detectors/eip7702-eoa-assumption.js +461 -0
  249. package/dist/detectors/erc1155-batch-missing-per-id-approval.d.ts +23 -0
  250. package/dist/detectors/erc1155-batch-missing-per-id-approval.js +343 -0
  251. package/dist/detectors/erc1155-reentrancy.d.ts +31 -0
  252. package/dist/detectors/erc1155-reentrancy.js +217 -0
  253. package/dist/detectors/erc1271-stub-implementation.d.ts +21 -0
  254. package/dist/detectors/erc1271-stub-implementation.js +268 -0
  255. package/dist/detectors/erc20-safe-wrapper-return-unchecked.d.ts +43 -0
  256. package/dist/detectors/erc20-safe-wrapper-return-unchecked.js +368 -0
  257. package/dist/detectors/erc20-unchecked-non-standard-return.d.ts +55 -0
  258. package/dist/detectors/erc20-unchecked-non-standard-return.js +454 -0
  259. package/dist/detectors/erc2612-permit-frontrunning.d.ts +23 -0
  260. package/dist/detectors/erc2612-permit-frontrunning.js +246 -0
  261. package/dist/detectors/erc2771-context-spoofing.d.ts +41 -0
  262. package/dist/detectors/erc2771-context-spoofing.js +510 -0
  263. package/dist/detectors/erc4337-validation-storage-access.d.ts +35 -0
  264. package/dist/detectors/erc4337-validation-storage-access.js +232 -0
  265. package/dist/detectors/erc4626-totalassets-stub.d.ts +17 -0
  266. package/dist/detectors/erc4626-totalassets-stub.js +216 -0
  267. package/dist/detectors/erc6909-balance-overflow.d.ts +7 -0
  268. package/dist/detectors/erc6909-balance-overflow.js +688 -0
  269. package/dist/detectors/erc6909-operator-scope.d.ts +49 -0
  270. package/dist/detectors/erc6909-operator-scope.js +494 -0
  271. package/dist/detectors/erc721-unchecked-transfer.d.ts +38 -0
  272. package/dist/detectors/erc721-unchecked-transfer.js +364 -0
  273. package/dist/detectors/erc7579-module-install-without-threshold.d.ts +40 -0
  274. package/dist/detectors/erc7579-module-install-without-threshold.js +338 -0
  275. package/dist/detectors/erc7683-fill-validation.d.ts +53 -0
  276. package/dist/detectors/erc7683-fill-validation.js +758 -0
  277. package/dist/detectors/erc7683-intent-resolution.d.ts +7 -0
  278. package/dist/detectors/erc7683-intent-resolution.js +457 -0
  279. package/dist/detectors/erc777-callback-reentrancy.d.ts +8 -0
  280. package/dist/detectors/erc777-callback-reentrancy.js +439 -0
  281. package/dist/detectors/erc777-reentrancy.d.ts +7 -0
  282. package/dist/detectors/erc777-reentrancy.js +488 -0
  283. package/dist/detectors/erc777-tokens-to-send-reentrancy.d.ts +47 -0
  284. package/dist/detectors/erc777-tokens-to-send-reentrancy.js +674 -0
  285. package/dist/detectors/estuary-token-flaw.d.ts +16 -0
  286. package/dist/detectors/estuary-token-flaw.js +547 -0
  287. package/dist/detectors/euler-debt-token-manipulation.d.ts +32 -0
  288. package/dist/detectors/euler-debt-token-manipulation.js +347 -0
  289. package/dist/detectors/exploiting-a-vulnerability-in-curve-fina.d.ts +29 -0
  290. package/dist/detectors/exploiting-a-vulnerability-in-curve-fina.js +210 -0
  291. package/dist/detectors/fallback-delegatecall-reentrancy.d.ts +14 -0
  292. package/dist/detectors/fallback-delegatecall-reentrancy.js +236 -0
  293. package/dist/detectors/farm-business-logic-flaw-lack-of-access.d.ts +7 -0
  294. package/dist/detectors/farm-business-logic-flaw-lack-of-access.js +665 -0
  295. package/dist/detectors/fee-mechanism-exploitation.d.ts +20 -0
  296. package/dist/detectors/fee-mechanism-exploitation.js +400 -0
  297. package/dist/detectors/fee-on-transfer-balance-mismatch.d.ts +49 -0
  298. package/dist/detectors/fee-on-transfer-balance-mismatch.js +394 -0
  299. package/dist/detectors/fhe-encrypted-input-validation.d.ts +29 -0
  300. package/dist/detectors/fhe-encrypted-input-validation.js +210 -0
  301. package/dist/detectors/fhe-handle-leakage.d.ts +44 -0
  302. package/dist/detectors/fhe-handle-leakage.js +315 -0
  303. package/dist/detectors/fhe-oz-pattern-misuse.d.ts +26 -0
  304. package/dist/detectors/fhe-oz-pattern-misuse.js +311 -0
  305. package/dist/detectors/fhe-state-leakage.d.ts +8 -0
  306. package/dist/detectors/fhe-state-leakage.js +400 -0
  307. package/dist/detectors/fi-bridges.d.ts +33 -0
  308. package/dist/detectors/fi-bridges.js +428 -0
  309. package/dist/detectors/finance-access-control-price-oracle-man.d.ts +9 -0
  310. package/dist/detectors/finance-access-control-price-oracle-man.js +640 -0
  311. package/dist/detectors/finance-bridge-address0safetransferfrom.d.ts +8 -0
  312. package/dist/detectors/finance-bridge-address0safetransferfrom.js +574 -0
  313. package/dist/detectors/finance-business-logic-in-mint.d.ts +54 -0
  314. package/dist/detectors/finance-business-logic-in-mint.js +687 -0
  315. package/dist/detectors/finance-erc667-reentrancy.d.ts +7 -0
  316. package/dist/detectors/finance-erc667-reentrancy.js +509 -0
  317. package/dist/detectors/finance-flashloan-price-oracle-manipul.d.ts +7 -0
  318. package/dist/detectors/finance-flashloan-price-oracle-manipul.js +546 -0
  319. package/dist/detectors/finance-flashloan-reentrancy.d.ts +7 -0
  320. package/dist/detectors/finance-flashloan-reentrancy.js +547 -0
  321. package/dist/detectors/finance-swap-metapool-attack.d.ts +19 -0
  322. package/dist/detectors/finance-swap-metapool-attack.js +321 -0
  323. package/dist/detectors/flashloan-price-manipulation.d.ts +7 -0
  324. package/dist/detectors/flashloan-price-manipulation.js +950 -0
  325. package/dist/detectors/flashloan-reentrancy-rari.d.ts +28 -0
  326. package/dist/detectors/flashloan-reentrancy-rari.js +577 -0
  327. package/dist/detectors/flashloan-reentrancy.d.ts +7 -0
  328. package/dist/detectors/flashloan-reentrancy.js +383 -0
  329. package/dist/detectors/flashloan-token-migrate.d.ts +7 -0
  330. package/dist/detectors/flashloan-token-migrate.js +274 -0
  331. package/dist/detectors/force-fed-eth-state-corruption.d.ts +32 -0
  332. package/dist/detectors/force-fed-eth-state-corruption.js +293 -0
  333. package/dist/detectors/free-mint-bug.d.ts +41 -0
  334. package/dist/detectors/free-mint-bug.js +483 -0
  335. package/dist/detectors/front-running-orderbook-state-update.d.ts +37 -0
  336. package/dist/detectors/front-running-orderbook-state-update.js +471 -0
  337. package/dist/detectors/front-running-shared-collateral-write.d.ts +41 -0
  338. package/dist/detectors/front-running-shared-collateral-write.js +508 -0
  339. package/dist/detectors/fusion-v1-settlement-arbitrary-yul-calld.d.ts +30 -0
  340. package/dist/detectors/fusion-v1-settlement-arbitrary-yul-calld.js +354 -0
  341. package/dist/detectors/generalized-frontrunning.d.ts +7 -0
  342. package/dist/detectors/generalized-frontrunning.js +836 -0
  343. package/dist/detectors/governance-flash-loan.d.ts +62 -0
  344. package/dist/detectors/governance-flash-loan.js +452 -0
  345. package/dist/detectors/governance-flashloan-vote.d.ts +41 -0
  346. package/dist/detectors/governance-flashloan-vote.js +272 -0
  347. package/dist/detectors/halborn-security-report-aave-v3.d.ts +6 -0
  348. package/dist/detectors/halborn-security-report-aave-v3.js +357 -0
  349. package/dist/detectors/incorrect-access-control.d.ts +26 -0
  350. package/dist/detectors/incorrect-access-control.js +328 -0
  351. package/dist/detectors/incorrect-burn-accounting.d.ts +10 -0
  352. package/dist/detectors/incorrect-burn-accounting.js +387 -0
  353. package/dist/detectors/incorrect-dividends-calculation.d.ts +27 -0
  354. package/dist/detectors/incorrect-dividends-calculation.js +524 -0
  355. package/dist/detectors/incorrect-dividends.d.ts +27 -0
  356. package/dist/detectors/incorrect-dividends.js +485 -0
  357. package/dist/detectors/incorrect-input-validation.d.ts +23 -0
  358. package/dist/detectors/incorrect-input-validation.js +312 -0
  359. package/dist/detectors/incorrect-signature-verification.d.ts +26 -0
  360. package/dist/detectors/incorrect-signature-verification.js +530 -0
  361. package/dist/detectors/infinite-loop.d.ts +7 -0
  362. package/dist/detectors/infinite-loop.js +440 -0
  363. package/dist/detectors/infinite-number-of-loans.d.ts +13 -0
  364. package/dist/detectors/infinite-number-of-loans.js +565 -0
  365. package/dist/detectors/inheritance-override.d.ts +26 -0
  366. package/dist/detectors/inheritance-override.js +320 -0
  367. package/dist/detectors/initialization-access-control.d.ts +8 -0
  368. package/dist/detectors/initialization-access-control.js +659 -0
  369. package/dist/detectors/insecure-randomness.d.ts +73 -0
  370. package/dist/detectors/insecure-randomness.js +610 -0
  371. package/dist/detectors/insufficient-access-control-trusted-param.d.ts +39 -0
  372. package/dist/detectors/insufficient-access-control-trusted-param.js +356 -0
  373. package/dist/detectors/insufficient-dvn-threshold.d.ts +32 -0
  374. package/dist/detectors/insufficient-dvn-threshold.js +585 -0
  375. package/dist/detectors/integer-overflow-detector.d.ts +45 -0
  376. package/dist/detectors/integer-overflow-detector.js +284 -0
  377. package/dist/detectors/integer-overflow.d.ts +95 -0
  378. package/dist/detectors/integer-overflow.js +344 -0
  379. package/dist/detectors/integer-underflow.d.ts +7 -0
  380. package/dist/detectors/integer-underflow.js +422 -0
  381. package/dist/detectors/intent-settlement-balance-manipulation.d.ts +22 -0
  382. package/dist/detectors/intent-settlement-balance-manipulation.js +548 -0
  383. package/dist/detectors/l1-to-l2-message-reentrancy.d.ts +7 -0
  384. package/dist/detectors/l1-to-l2-message-reentrancy.js +545 -0
  385. package/dist/detectors/l2-withdrawal-validation.d.ts +8 -0
  386. package/dist/detectors/l2-withdrawal-validation.js +303 -0
  387. package/dist/detectors/lack-of-access-control.d.ts +7 -0
  388. package/dist/detectors/lack-of-access-control.js +425 -0
  389. package/dist/detectors/lack-of-calldata-validation.d.ts +16 -0
  390. package/dist/detectors/lack-of-calldata-validation.js +914 -0
  391. package/dist/detectors/lack-of-input-validation-reentrancy.d.ts +7 -0
  392. package/dist/detectors/lack-of-input-validation-reentrancy.js +637 -0
  393. package/dist/detectors/lack-of-slippage-control.d.ts +7 -0
  394. package/dist/detectors/lack-of-slippage-control.js +513 -0
  395. package/dist/detectors/lack-of-slippage-protection.d.ts +7 -0
  396. package/dist/detectors/lack-of-slippage-protection.js +474 -0
  397. package/dist/detectors/lack-of-validation-data.d.ts +23 -0
  398. package/dist/detectors/lack-of-validation-data.js +391 -0
  399. package/dist/detectors/lack-of-validation-pool.d.ts +7 -0
  400. package/dist/detectors/lack-of-validation-pool.js +492 -0
  401. package/dist/detectors/lack-of-validation-userdata.d.ts +7 -0
  402. package/dist/detectors/lack-of-validation-userdata.js +583 -0
  403. package/dist/detectors/lack-of-validation.d.ts +27 -0
  404. package/dist/detectors/lack-of-validation.js +609 -0
  405. package/dist/detectors/layerzero-dvn-quorum-missing.d.ts +22 -0
  406. package/dist/detectors/layerzero-dvn-quorum-missing.js +464 -0
  407. package/dist/detectors/layerzero-v2-unverified-origin.d.ts +40 -0
  408. package/dist/detectors/layerzero-v2-unverified-origin.js +368 -0
  409. package/dist/detectors/liquidation-accounting-desync.d.ts +14 -0
  410. package/dist/detectors/liquidation-accounting-desync.js +145 -0
  411. package/dist/detectors/liquidation-gain-manipulation.d.ts +42 -0
  412. package/dist/detectors/liquidation-gain-manipulation.js +606 -0
  413. package/dist/detectors/liquidation-price-rounding-advantage.d.ts +26 -0
  414. package/dist/detectors/liquidation-price-rounding-advantage.js +283 -0
  415. package/dist/detectors/liquidity-poisoning.d.ts +25 -0
  416. package/dist/detectors/liquidity-poisoning.js +339 -0
  417. package/dist/detectors/loans-malicious-proposal-price-oracle.d.ts +44 -0
  418. package/dist/detectors/loans-malicious-proposal-price-oracle.js +813 -0
  419. package/dist/detectors/logic-flaw.d.ts +186 -0
  420. package/dist/detectors/logic-flaw.js +3356 -0
  421. package/dist/detectors/manipulation-of-funds.d.ts +31 -0
  422. package/dist/detectors/manipulation-of-funds.js +304 -0
  423. package/dist/detectors/merkl-unsafe-claim-callback.d.ts +22 -0
  424. package/dist/detectors/merkl-unsafe-claim-callback.js +94 -0
  425. package/dist/detectors/mev-boost-timestamp.d.ts +7 -0
  426. package/dist/detectors/mev-boost-timestamp.js +318 -0
  427. package/dist/detectors/mev-merge-exploit.d.ts +29 -0
  428. package/dist/detectors/mev-merge-exploit.js +397 -0
  429. package/dist/detectors/mev-sandwich-vulnerability.d.ts +24 -0
  430. package/dist/detectors/mev-sandwich-vulnerability.js +648 -0
  431. package/dist/detectors/mev-slot-manipulation.d.ts +36 -0
  432. package/dist/detectors/mev-slot-manipulation.js +691 -0
  433. package/dist/detectors/mevbot-insufficient-validation.d.ts +48 -0
  434. package/dist/detectors/mevbot-insufficient-validation.js +574 -0
  435. package/dist/detectors/migration-rebalance-without-bound.d.ts +7 -0
  436. package/dist/detectors/migration-rebalance-without-bound.js +514 -0
  437. package/dist/detectors/mint-hardcoded-asset-parity.d.ts +31 -0
  438. package/dist/detectors/mint-hardcoded-asset-parity.js +356 -0
  439. package/dist/detectors/miscalculation-on-spendallowance.d.ts +7 -0
  440. package/dist/detectors/miscalculation-on-spendallowance.js +188 -0
  441. package/dist/detectors/misconfiguration.d.ts +27 -0
  442. package/dist/detectors/misconfiguration.js +410 -0
  443. package/dist/detectors/missing-access-control-caller-supplied-auth.d.ts +7 -0
  444. package/dist/detectors/missing-access-control-caller-supplied-auth.js +550 -0
  445. package/dist/detectors/missing-access-control-receiver-payout.d.ts +7 -0
  446. package/dist/detectors/missing-access-control-receiver-payout.js +460 -0
  447. package/dist/detectors/missing-access-control-role-or-transferfrom.d.ts +7 -0
  448. package/dist/detectors/missing-access-control-role-or-transferfrom.js +663 -0
  449. package/dist/detectors/missing-access-control.d.ts +19 -0
  450. package/dist/detectors/missing-access-control.js +781 -0
  451. package/dist/detectors/missing-sequencer-uptime-check.d.ts +30 -0
  452. package/dist/detectors/missing-sequencer-uptime-check.js +348 -0
  453. package/dist/detectors/missing-storage-gap.d.ts +19 -0
  454. package/dist/detectors/missing-storage-gap.js +193 -0
  455. package/dist/detectors/missing-swap-deadline-slippage.d.ts +31 -0
  456. package/dist/detectors/missing-swap-deadline-slippage.js +231 -0
  457. package/dist/detectors/missing-zk-proof-verification.d.ts +60 -0
  458. package/dist/detectors/missing-zk-proof-verification.js +547 -0
  459. package/dist/detectors/my-experience-with-yearn-finance.d.ts +7 -0
  460. package/dist/detectors/my-experience-with-yearn-finance.js +552 -0
  461. package/dist/detectors/network-bridge-ronin.d.ts +7 -0
  462. package/dist/detectors/network-bridge-ronin.js +408 -0
  463. package/dist/detectors/network-bridge.d.ts +7 -0
  464. package/dist/detectors/network-bridge.js +444 -0
  465. package/dist/detectors/network-underflow.d.ts +7 -0
  466. package/dist/detectors/network-underflow.js +517 -0
  467. package/dist/detectors/nft-denial-of-service.d.ts +7 -0
  468. package/dist/detectors/nft-denial-of-service.js +223 -0
  469. package/dist/detectors/nft-marketplace-order-reentrancy.d.ts +7 -0
  470. package/dist/detectors/nft-marketplace-order-reentrancy.js +427 -0
  471. package/dist/detectors/nft-token-standard-access-control.d.ts +7 -0
  472. package/dist/detectors/nft-token-standard-access-control.js +455 -0
  473. package/dist/detectors/oracle-manipulation-amm-spot-price.d.ts +42 -0
  474. package/dist/detectors/oracle-manipulation-amm-spot-price.js +321 -0
  475. package/dist/detectors/oracle-manipulation-liquidity-withdrawal.d.ts +27 -0
  476. package/dist/detectors/oracle-manipulation-liquidity-withdrawal.js +192 -0
  477. package/dist/detectors/oracle-manipulation.d.ts +90 -0
  478. package/dist/detectors/oracle-manipulation.js +1023 -0
  479. package/dist/detectors/oracle-vortex-manipulation.d.ts +30 -0
  480. package/dist/detectors/oracle-vortex-manipulation.js +473 -0
  481. package/dist/detectors/overpriced-asset-in-oracle.d.ts +41 -0
  482. package/dist/detectors/overpriced-asset-in-oracle.js +420 -0
  483. package/dist/detectors/oz-access-control-roles.d.ts +33 -0
  484. package/dist/detectors/oz-access-control-roles.js +359 -0
  485. package/dist/detectors/pair-manipulation-transfer-hook.d.ts +38 -0
  486. package/dist/detectors/pair-manipulation-transfer-hook.js +366 -0
  487. package/dist/detectors/parameter-access-control.d.ts +47 -0
  488. package/dist/detectors/parameter-access-control.js +511 -0
  489. package/dist/detectors/parameter-manipulation.d.ts +7 -0
  490. package/dist/detectors/parameter-manipulation.js +505 -0
  491. package/dist/detectors/parity-multisig-delegatecall.d.ts +7 -0
  492. package/dist/detectors/parity-multisig-delegatecall.js +707 -0
  493. package/dist/detectors/permissionless-claim-amm-spot-pricing.d.ts +7 -0
  494. package/dist/detectors/permissionless-claim-amm-spot-pricing.js +351 -0
  495. package/dist/detectors/permit-future-dated-deadline.d.ts +31 -0
  496. package/dist/detectors/permit-future-dated-deadline.js +339 -0
  497. package/dist/detectors/phishing-attack-bybit.d.ts +37 -0
  498. package/dist/detectors/phishing-attack-bybit.js +513 -0
  499. package/dist/detectors/post-insolvency-check.d.ts +7 -0
  500. package/dist/detectors/post-insolvency-check.js +277 -0
  501. package/dist/detectors/precision-loss-vulnerability.d.ts +7 -0
  502. package/dist/detectors/precision-loss-vulnerability.js +472 -0
  503. package/dist/detectors/precision-truncation.d.ts +8 -0
  504. package/dist/detectors/precision-truncation.js +425 -0
  505. package/dist/detectors/price-dependency-veth.d.ts +41 -0
  506. package/dist/detectors/price-dependency-veth.js +588 -0
  507. package/dist/detectors/price-feed-verification.d.ts +7 -0
  508. package/dist/detectors/price-feed-verification.js +557 -0
  509. package/dist/detectors/price-manipulation-reentrancy.d.ts +32 -0
  510. package/dist/detectors/price-manipulation-reentrancy.js +445 -0
  511. package/dist/detectors/price-manipulation-via-reentranc.d.ts +7 -0
  512. package/dist/detectors/price-manipulation-via-reentranc.js +569 -0
  513. package/dist/detectors/price-oracle-manipulation.d.ts +25 -0
  514. package/dist/detectors/price-oracle-manipulation.js +530 -0
  515. package/dist/detectors/project-instant-rewards-unlocked.d.ts +6 -0
  516. package/dist/detectors/project-instant-rewards-unlocked.js +462 -0
  517. package/dist/detectors/protocol-reentrancy.d.ts +7 -0
  518. package/dist/detectors/protocol-reentrancy.js +457 -0
  519. package/dist/detectors/proxy-init-race.d.ts +11 -0
  520. package/dist/detectors/proxy-init-race.js +634 -0
  521. package/dist/detectors/proxy-storage-slot-collision.d.ts +7 -0
  522. package/dist/detectors/proxy-storage-slot-collision.js +135 -0
  523. package/dist/detectors/public-internal-function.d.ts +39 -0
  524. package/dist/detectors/public-internal-function.js +233 -0
  525. package/dist/detectors/quote-silent-zero.d.ts +25 -0
  526. package/dist/detectors/quote-silent-zero.js +156 -0
  527. package/dist/detectors/readonly-reentrancy.d.ts +9 -0
  528. package/dist/detectors/readonly-reentrancy.js +108 -0
  529. package/dist/detectors/receipt-redemption-missing-validation.d.ts +31 -0
  530. package/dist/detectors/receipt-redemption-missing-validation.js +453 -0
  531. package/dist/detectors/reentrancy-balance.d.ts +36 -0
  532. package/dist/detectors/reentrancy-balance.js +577 -0
  533. package/dist/detectors/reentrancy-business-logic-game.d.ts +36 -0
  534. package/dist/detectors/reentrancy-business-logic-game.js +616 -0
  535. package/dist/detectors/reentrancy-on-sell-nft.d.ts +23 -0
  536. package/dist/detectors/reentrancy-on-sell-nft.js +510 -0
  537. package/dist/detectors/reflection-token-balance-desync.d.ts +28 -0
  538. package/dist/detectors/reflection-token-balance-desync.js +246 -0
  539. package/dist/detectors/registry-engine.d.ts +34 -0
  540. package/dist/detectors/registry-engine.js +388 -0
  541. package/dist/detectors/rollup-unvalidated-state-update.d.ts +35 -0
  542. package/dist/detectors/rollup-unvalidated-state-update.js +286 -0
  543. package/dist/detectors/s-horizon-bridge-private-key-compromis.d.ts +8 -0
  544. package/dist/detectors/s-horizon-bridge-private-key-compromis.js +615 -0
  545. package/dist/detectors/share-price-manipulation.d.ts +7 -0
  546. package/dist/detectors/share-price-manipulation.js +653 -0
  547. package/dist/detectors/signature-replay.d.ts +30 -0
  548. package/dist/detectors/signature-replay.js +367 -0
  549. package/dist/detectors/simpleswap-unverified-approval.d.ts +27 -0
  550. package/dist/detectors/simpleswap-unverified-approval.js +198 -0
  551. package/dist/detectors/single-spot-oracle-collateral-valuation.d.ts +22 -0
  552. package/dist/detectors/single-spot-oracle-collateral-valuation.js +419 -0
  553. package/dist/detectors/skim-token-balance.d.ts +7 -0
  554. package/dist/detectors/skim-token-balance.js +788 -0
  555. package/dist/detectors/sky-oft-governance-payload.d.ts +7 -0
  556. package/dist/detectors/sky-oft-governance-payload.js +515 -0
  557. package/dist/detectors/sky-oft-governance-truncation.d.ts +32 -0
  558. package/dist/detectors/sky-oft-governance-truncation.js +377 -0
  559. package/dist/detectors/solana-evm-bridge-truncation.d.ts +7 -0
  560. package/dist/detectors/solana-evm-bridge-truncation.js +638 -0
  561. package/dist/detectors/solhint-unchecked-low-level-call.d.ts +74 -0
  562. package/dist/detectors/solhint-unchecked-low-level-call.js +463 -0
  563. package/dist/detectors/stablecoin-pair-spot-oracle.d.ts +7 -0
  564. package/dist/detectors/stablecoin-pair-spot-oracle.js +364 -0
  565. package/dist/detectors/staked-rate-as-oracle.d.ts +44 -0
  566. package/dist/detectors/staked-rate-as-oracle.js +497 -0
  567. package/dist/detectors/stale-oracle.d.ts +63 -0
  568. package/dist/detectors/stale-oracle.js +649 -0
  569. package/dist/detectors/starkware-proof-validation-gap.d.ts +18 -0
  570. package/dist/detectors/starkware-proof-validation-gap.js +629 -0
  571. package/dist/detectors/steth-transfer-reentrancy.d.ts +8 -0
  572. package/dist/detectors/steth-transfer-reentrancy.js +317 -0
  573. package/dist/detectors/storage-collision-malicious-proposal.d.ts +27 -0
  574. package/dist/detectors/storage-collision-malicious-proposal.js +386 -0
  575. package/dist/detectors/timestamp-manipulation.d.ts +49 -0
  576. package/dist/detectors/timestamp-manipulation.js +383 -0
  577. package/dist/detectors/token-access-control.d.ts +7 -0
  578. package/dist/detectors/token-access-control.js +544 -0
  579. package/dist/detectors/token-incorrect-signature-verification.d.ts +23 -0
  580. package/dist/detectors/token-incorrect-signature-verification.js +434 -0
  581. package/dist/detectors/token-transfer-logic-flaw.d.ts +33 -0
  582. package/dist/detectors/token-transfer-logic-flaw.js +267 -0
  583. package/dist/detectors/transfer-double-debit-pool-recipient.d.ts +7 -0
  584. package/dist/detectors/transfer-double-debit-pool-recipient.js +542 -0
  585. package/dist/detectors/treasury-reentrancy.d.ts +7 -0
  586. package/dist/detectors/treasury-reentrancy.js +442 -0
  587. package/dist/detectors/tstore-poison.d.ts +32 -0
  588. package/dist/detectors/tstore-poison.js +417 -0
  589. package/dist/detectors/tstore-race-condition.d.ts +7 -0
  590. package/dist/detectors/tstore-race-condition.js +632 -0
  591. package/dist/detectors/types.d.ts +85 -0
  592. package/dist/detectors/types.js +20 -0
  593. package/dist/detectors/unauthorized-payer-transferfrom.d.ts +66 -0
  594. package/dist/detectors/unauthorized-payer-transferfrom.js +339 -0
  595. package/dist/detectors/unauthorized-transferfrom-shell.d.ts +7 -0
  596. package/dist/detectors/unauthorized-transferfrom-shell.js +504 -0
  597. package/dist/detectors/unauthorized-transferfrom.d.ts +16 -0
  598. package/dist/detectors/unauthorized-transferfrom.js +838 -0
  599. package/dist/detectors/unbound-zk-verifier-input.d.ts +7 -0
  600. package/dist/detectors/unbound-zk-verifier-input.js +445 -0
  601. package/dist/detectors/unbounded-share-price-collateral-oracle.d.ts +48 -0
  602. package/dist/detectors/unbounded-share-price-collateral-oracle.js +566 -0
  603. package/dist/detectors/uncapped-reward-emission.d.ts +7 -0
  604. package/dist/detectors/uncapped-reward-emission.js +493 -0
  605. package/dist/detectors/unchecked-call-forwarding.d.ts +31 -0
  606. package/dist/detectors/unchecked-call-forwarding.js +330 -0
  607. package/dist/detectors/unchecked-external-call-unconditional-state-mutation.d.ts +18 -0
  608. package/dist/detectors/unchecked-external-call-unconditional-state-mutation.js +311 -0
  609. package/dist/detectors/unchecked-external-call.d.ts +66 -0
  610. package/dist/detectors/unchecked-external-call.js +389 -0
  611. package/dist/detectors/unchecked-oft-return.d.ts +13 -0
  612. package/dist/detectors/unchecked-oft-return.js +118 -0
  613. package/dist/detectors/unguarded-governance-execution.d.ts +35 -0
  614. package/dist/detectors/unguarded-governance-execution.js +422 -0
  615. package/dist/detectors/unguarded-governance-executor.d.ts +35 -0
  616. package/dist/detectors/unguarded-governance-executor.js +349 -0
  617. package/dist/detectors/unindexed-event-address.d.ts +7 -0
  618. package/dist/detectors/unindexed-event-address.js +268 -0
  619. package/dist/detectors/uninitialized-implementation.d.ts +27 -0
  620. package/dist/detectors/uninitialized-implementation.js +333 -0
  621. package/dist/detectors/uninitialized-storage-pointer.d.ts +7 -0
  622. package/dist/detectors/uninitialized-storage-pointer.js +110 -0
  623. package/dist/detectors/uniswap-skim-token-balance-attack.d.ts +8 -0
  624. package/dist/detectors/uniswap-skim-token-balance-attack.js +331 -0
  625. package/dist/detectors/uniswap-v4-hook-state-manipulation.d.ts +7 -0
  626. package/dist/detectors/uniswap-v4-hook-state-manipulation.js +296 -0
  627. package/dist/detectors/unprotected-admin-or-fund-sink.d.ts +7 -0
  628. package/dist/detectors/unprotected-admin-or-fund-sink.js +643 -0
  629. package/dist/detectors/unprotected-dex-swap.d.ts +43 -0
  630. package/dist/detectors/unprotected-dex-swap.js +334 -0
  631. package/dist/detectors/unprotected-initializer.d.ts +7 -0
  632. package/dist/detectors/unprotected-initializer.js +707 -0
  633. package/dist/detectors/unprotected-pair-initializer.d.ts +22 -0
  634. package/dist/detectors/unprotected-pair-initializer.js +359 -0
  635. package/dist/detectors/unprotected-upgrade-function.d.ts +7 -0
  636. package/dist/detectors/unprotected-upgrade-function.js +180 -0
  637. package/dist/detectors/unreachable-code-0.8.28.d.ts +19 -0
  638. package/dist/detectors/unreachable-code-0.8.28.js +206 -0
  639. package/dist/detectors/unsafe-proxy-storage.d.ts +7 -0
  640. package/dist/detectors/unsafe-proxy-storage.js +436 -0
  641. package/dist/detectors/unsafe-transient-storage.d.ts +7 -0
  642. package/dist/detectors/unsafe-transient-storage.js +1052 -0
  643. package/dist/detectors/unsafe-tx-origin.d.ts +9 -0
  644. package/dist/detectors/unsafe-tx-origin.js +179 -0
  645. package/dist/detectors/unsigned-validity-window.d.ts +20 -0
  646. package/dist/detectors/unsigned-validity-window.js +220 -0
  647. package/dist/detectors/unvalidated-interface-address.d.ts +25 -0
  648. package/dist/detectors/unvalidated-interface-address.js +377 -0
  649. package/dist/detectors/uups-uninitialized-storage.d.ts +9 -0
  650. package/dist/detectors/uups-uninitialized-storage.js +366 -0
  651. package/dist/detectors/v2-error-k-value-attack.d.ts +33 -0
  652. package/dist/detectors/v2-error-k-value-attack.js +276 -0
  653. package/dist/detectors/v2-k-invariant-bypass.d.ts +33 -0
  654. package/dist/detectors/v2-k-invariant-bypass.js +283 -0
  655. package/dist/detectors/v4-hook-reentrancy.d.ts +9 -0
  656. package/dist/detectors/v4-hook-reentrancy.js +488 -0
  657. package/dist/detectors/vault-inflation-rounding.d.ts +23 -0
  658. package/dist/detectors/vault-inflation-rounding.js +477 -0
  659. package/dist/detectors/vault-share-price-manipulation.d.ts +7 -0
  660. package/dist/detectors/vault-share-price-manipulation.js +332 -0
  661. package/dist/detectors/vortex-interaction-guard.d.ts +45 -0
  662. package/dist/detectors/vortex-interaction-guard.js +275 -0
  663. package/dist/detectors/vortex-protocol-reentrancy-guard.d.ts +27 -0
  664. package/dist/detectors/vortex-protocol-reentrancy-guard.js +408 -0
  665. package/dist/detectors/vulnerable-price-dependency.d.ts +41 -0
  666. package/dist/detectors/vulnerable-price-dependency.js +473 -0
  667. package/dist/detectors/weak-random-mint.d.ts +37 -0
  668. package/dist/detectors/weak-random-mint.js +271 -0
  669. package/dist/detectors/withdraw-be-to-withdraw.d.ts +26 -0
  670. package/dist/detectors/withdraw-be-to-withdraw.js +329 -0
  671. package/dist/detectors/wrong-function-visibility.d.ts +29 -0
  672. package/dist/detectors/wrong-function-visibility.js +147 -0
  673. package/dist/detectors/wrong-price-calculation.d.ts +42 -0
  674. package/dist/detectors/wrong-price-calculation.js +387 -0
  675. package/dist/detectors/yearn-vault-v2-share-price-manipulation.d.ts +32 -0
  676. package/dist/detectors/yearn-vault-v2-share-price-manipulation.js +248 -0
  677. package/dist/detectors/zero-fee.d.ts +7 -0
  678. package/dist/detectors/zero-fee.js +596 -0
  679. package/dist/detectors/zetachain-gateway-hack-analysis.d.ts +7 -0
  680. package/dist/detectors/zetachain-gateway-hack-analysis.js +629 -0
  681. package/dist/detectors/zk-rollup-da-gap.d.ts +8 -0
  682. package/dist/detectors/zk-rollup-da-gap.js +322 -0
  683. package/dist/detectors/zksync-batch-validation.d.ts +8 -0
  684. package/dist/detectors/zksync-batch-validation.js +461 -0
  685. package/dist/detectors/zksync-era-rollup-state-update.d.ts +60 -0
  686. package/dist/detectors/zksync-era-rollup-state-update.js +360 -0
  687. package/dist/detectors/zksync-simulation-drift.d.ts +35 -0
  688. package/dist/detectors/zksync-simulation-drift.js +309 -0
  689. package/dist/exit-codes.d.ts +15 -0
  690. package/dist/exit-codes.js +18 -0
  691. package/dist/formatters/github-actions.d.ts +2 -0
  692. package/dist/formatters/github-actions.js +61 -0
  693. package/dist/formatters/sarif.d.ts +24 -0
  694. package/dist/formatters/sarif.js +670 -0
  695. package/dist/formatters/text.d.ts +14 -0
  696. package/dist/formatters/text.js +152 -0
  697. package/dist/fp-rates.json +70 -0
  698. package/dist/identity/diff-baseline.d.ts +16 -0
  699. package/dist/identity/diff-baseline.js +152 -0
  700. package/dist/identity/hashing.d.ts +39 -0
  701. package/dist/identity/hashing.js +96 -0
  702. package/dist/index.d.ts +174 -0
  703. package/dist/index.js +358 -0
  704. package/dist/parallel-scan.d.ts +66 -0
  705. package/dist/parallel-scan.js +227 -0
  706. package/dist/registry.d.ts +17 -0
  707. package/dist/registry.js +118 -0
  708. package/dist/rules/glob.d.ts +5 -0
  709. package/dist/rules/glob.js +76 -0
  710. package/dist/rules/suppressions.d.ts +23 -0
  711. package/dist/rules/suppressions.js +136 -0
  712. package/dist/rules/tiers.d.ts +23 -0
  713. package/dist/rules/tiers.js +341 -0
  714. package/dist/scan-worker.d.ts +1 -0
  715. package/dist/scan-worker.js +61 -0
  716. package/dist/scan.d.ts +24 -0
  717. package/dist/scan.js +558 -0
  718. package/dist/semantic/contracts.d.ts +10 -0
  719. package/dist/semantic/contracts.js +141 -0
  720. package/dist/semantic/diagnostics.d.ts +29 -0
  721. package/dist/semantic/diagnostics.js +25 -0
  722. package/dist/semantic/eog.d.ts +56 -0
  723. package/dist/semantic/eog.js +545 -0
  724. package/dist/semantic/imports.d.ts +88 -0
  725. package/dist/semantic/imports.js +246 -0
  726. package/dist/semantic/index.d.ts +2 -0
  727. package/dist/semantic/index.js +8 -0
  728. package/dist/semantic/inheritance.d.ts +33 -0
  729. package/dist/semantic/inheritance.js +137 -0
  730. package/dist/semantic/model.d.ts +95 -0
  731. package/dist/semantic/model.js +232 -0
  732. package/dist/semantic/taint-tracker.d.ts +49 -0
  733. package/dist/semantic/taint-tracker.js +410 -0
  734. package/dist/semantic/types.d.ts +119 -0
  735. package/dist/semantic/types.js +18 -0
  736. package/dist/severity.d.ts +10 -0
  737. package/dist/severity.js +78 -0
  738. package/package.json +52 -0
@@ -0,0 +1,3356 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.LogicFlawDetector = void 0;
4
+ const access_control_1 = require("./_common/access-control");
5
+ const ast_1 = require("./_common/ast");
6
+ const price_rate_1 = require("./_common/price-rate");
7
+ const RULE_ID = 'logic-flaw';
8
+ const PATTERN = 'fix-withdrawbe-to-withdraw';
9
+ const PRICE_INFLATE_PATTERN = 'price-manipulation-inflate-attack';
10
+ const COIN_CUT_PATTERN = 'coin-cut-price-manipulation';
11
+ const ZKSYNC_DONATION_PATTERN = 'zksync-donation-attack';
12
+ const ZENTEREST_PATTERN = 'zenterest-price-out-of-date';
13
+ const PENPIE_REWARD_REENTRANCY_PATTERN = 'penpie-io-reentrancy-reward-manipulation';
14
+ const DAO_DAO_BUILD_FINANCE_PATTERN = 'dao-dao-buildfinance-governance-execution';
15
+ const V3_MIGRATOR_PATTERN = 'v3migrator-biswap-unvalidated-pair-token-migration';
16
+ const CELLFRAMENET_PATTERN = 'cellframenet-liquidity-migration-calculation';
17
+ const MISO_UNSPECIFIED_PATTERN = 'miso-unspecified-msg-value-reuse';
18
+ const CS_OUTDATED_GLOBAL_PATTERN = 'cs-outdated-global-variable';
19
+ const LOW_LEVEL_GOVERNANCE_CALLS = new Set(['call', 'delegatecall']);
20
+ const TRANSFER_METHODS = new Set(['transfer', 'send', 'transferFrom', 'safeTransfer', 'safeTransferFrom']);
21
+ const REENTRANCY_GUARD_MODIFIERS = new Set(['nonreentrant', '_nonreentrant', 'noreentrant', 'noreentrancy', 'lock', 'locked']);
22
+ const INTERNAL_VISIBILITIES = new Set(['internal', 'private']);
23
+ const COIN_CUT_VALUE_METHODS = new Set(['transfer', 'transferfrom', 'safetransfer', 'safetransferfrom']);
24
+ const COIN_CUT_ACCOUNTING_NAMES = /balance|balances|share|shares|supply|mint|burn|debt|reserve|asset|total/i;
25
+ const PENPIE_ACCOUNTING_NAMES = /reward|rewards|rewarddebt|pending|share|shares|stake|staked|accreward|rewardpershare|perShare|totalRewards/i;
26
+ const PENPIE_IGNORED_EXTERNAL_METHODS = new Set(['transfer', 'send', 'transferfrom', 'safetransfer', 'safetransferfrom']);
27
+ const PENPIE_LIBRARY_STYLE_METHODS = /^(to[A-Z]|mulDiv|wMul|wDiv|zeroFloorSub|exactlyOneZero|min|max)/;
28
+ const DAO_DAO_EXECUTE_NAMES = /execute|exec|relay|govern/i;
29
+ const DAO_DAO_GOVERNANCE_NAMES = /vote|proposal|propose|passed|queued|state|yes|forvotes/i;
30
+ const DAO_DAO_EXECUTION_CONSUMED_NAMES = /executed|locked|processed|completed|consumed|done/i;
31
+ const DAO_DAO_QUEUE_FLAG_NAMES = /queued/i;
32
+ const MISO_VALUE_ACCOUNTING_NAMES = /commit|committed|commitment|paid|payment|deposit|contribution|credit|balance|share|amount|total/i;
33
+ // Independent reference-price signals: a TWAP, oracle, stored snapshot, or
34
+ // deviation/bound check. Generic cap names (`limit`, `maxPayout`, slippage
35
+ // caps) are deliberately excluded — a max-payout cap is not a price
36
+ // reference, so it must not suppress a Coin Cut finding.
37
+ const COIN_CUT_REFERENCE_NAMES = /twap|oracle|reference|median|trusted|stored|lastrecorded|maxdeviation|deviation|bound/i;
38
+ const OUTDATED_GLOBAL_SOURCE_NAMES = /balance|balances|reserve|reserves|supply|share|shares|liquidity|debt/i;
39
+ const OUTDATED_GLOBAL_SINK_NAMES = /balance|balances|share|shares|supply|mint|burn|debt|reserve|asset|liquidity|reward|claim|withdraw|deposit|amount|total/i;
40
+ // Upper bound on the number of distinct execution paths the price-inflate
41
+ // walk will carry. Forking on every `if` is 2^n in the worst case; the cap
42
+ // keeps deeply nested branch chains from blowing up. Real `transfer` bodies
43
+ // stay far below this.
44
+ const MAX_INFLATE_PATHS = 256;
45
+ // Strict BE-tag: identifier starts with `be` followed by an upper-case
46
+ // boundary, or is one of the canonical BE token-standard names. The
47
+ // stale-renamed-withdraw bug is signalled by a function whose generic
48
+ // (non-BE) accounting differs from a BE-tagged sibling.
49
+ function isBeTaggedName(name) {
50
+ if (!name)
51
+ return false;
52
+ if (/^be(?:[A-Z]|20|721)/.test(name))
53
+ return true;
54
+ const lower = name.toLowerCase();
55
+ return lower === 'be' || lower === 'be20' || lower === 'be721';
56
+ }
57
+ // Names that look like a balance/share/accounting slot, regardless of
58
+ // BE tag. Used to recognise the lvalue/rvalue shapes we care about; the
59
+ // BE-vs-generic split is a separate concern handled by `isBeTaggedName`.
60
+ function isBalanceLikeName(name) {
61
+ if (!name)
62
+ return false;
63
+ const lower = name.toLowerCase();
64
+ return /balance|balances|share|shares|deposit|deposits|stake|staked|locked/.test(lower);
65
+ }
66
+ // A Zenterest-style cached price is a *stored oracle price* — distinguished
67
+ // from a governance-set config constant by the presence of a companion
68
+ // freshness/update timestamp the contract is meant to consult before use.
69
+ // Without such a state variable, a price/rate-like name alone is too weak a
70
+ // signal: ordinary config rates (`feeRate`, `mintPrice`, ...) would all match.
71
+ function looksLikeFreshnessTrackingName(name) {
72
+ if (!name)
73
+ return false;
74
+ if ((0, price_rate_1.isFreshnessFieldName)(name))
75
+ return true;
76
+ const tokens = (0, price_rate_1.splitCamelTokens)(name);
77
+ if (tokens.includes('timestamp'))
78
+ return true;
79
+ const hasAnchor = tokens.some(t => t === 'last' || t === 'prev' || t === 'previous');
80
+ const hasEvent = tokens.some(t => t === 'update' || t === 'updated' || t === 'sync' || t === 'synced' ||
81
+ t === 'refresh' || t === 'refreshed' || t === 'time');
82
+ return hasAnchor && hasEvent;
83
+ }
84
+ function looksLikeCachedOraclePriceName(name) {
85
+ const tokens = (0, price_rate_1.splitCamelTokens)(name);
86
+ if (tokens.length === 0)
87
+ return false;
88
+ const hasPriceTerm = tokens.some(t => t === 'price' || t === 'rate' || t === 'exchange');
89
+ if (!hasPriceTerm)
90
+ return false;
91
+ // Avoid treating ordinary economic/config knobs as cached oracle prices
92
+ // merely because the contract also has an unrelated freshness timestamp.
93
+ if (tokens.some(t => t === 'fee' || t === 'tax' || t === 'mint' || t === 'borrow' || t === 'interest')) {
94
+ return false;
95
+ }
96
+ return tokens.some(t => t === 'cached' || t === 'cache' || t === 'oracle' || t === 'stored' ||
97
+ t === 'snapshot' || t === 'last' || t === 'recorded');
98
+ }
99
+ class LogicFlawDetector {
100
+ id = RULE_ID;
101
+ patternKey = RULE_ID;
102
+ supportedAstKinds = ['parser'];
103
+ currentFile = '';
104
+ currentContract = '';
105
+ contractKindIsAnalysable = false;
106
+ stateVars = [];
107
+ contractHasFreshnessTrackingVar = false;
108
+ contractFunctions = new Map();
109
+ findings = [];
110
+ emittedKey = new Set();
111
+ setFile(file) {
112
+ this.currentFile = file;
113
+ this.findings = [];
114
+ this.currentContract = '';
115
+ this.contractKindIsAnalysable = false;
116
+ this.stateVars = [];
117
+ this.contractHasFreshnessTrackingVar = false;
118
+ this.contractFunctions = new Map();
119
+ this.emittedKey = new Set();
120
+ }
121
+ getFindings() {
122
+ return this.findings;
123
+ }
124
+ ContractDefinition(node) {
125
+ if (!(0, ast_1.isNode)(node, 'ContractDefinition'))
126
+ return;
127
+ const kind = String(node.kind || '').toLowerCase();
128
+ if (kind === 'interface' || kind === 'library') {
129
+ this.currentContract = '';
130
+ this.contractKindIsAnalysable = false;
131
+ this.stateVars = [];
132
+ this.contractHasFreshnessTrackingVar = false;
133
+ this.contractFunctions = new Map();
134
+ return;
135
+ }
136
+ this.currentContract = node.name || '';
137
+ this.contractKindIsAnalysable = true;
138
+ this.stateVars = [];
139
+ this.contractFunctions = new Map();
140
+ for (const sub of node.subNodes || []) {
141
+ if ((0, ast_1.isNode)(sub, 'StateVariableDeclaration')) {
142
+ for (const v of sub.variables || []) {
143
+ if (v?.name)
144
+ this.stateVars.push(v.name);
145
+ }
146
+ }
147
+ else if ((0, ast_1.isNode)(sub, 'FunctionDefinition') && sub?.name) {
148
+ this.contractFunctions.set(sub.name, sub);
149
+ }
150
+ }
151
+ this.contractHasFreshnessTrackingVar = this.stateVars.some(looksLikeFreshnessTrackingName);
152
+ }
153
+ ContractDefinition_post() {
154
+ this.currentContract = '';
155
+ this.contractKindIsAnalysable = false;
156
+ this.stateVars = [];
157
+ this.contractHasFreshnessTrackingVar = false;
158
+ this.contractFunctions = new Map();
159
+ }
160
+ FunctionDefinition(node) {
161
+ if (!this.contractKindIsAnalysable)
162
+ return;
163
+ if (!this.currentContract)
164
+ return;
165
+ if (!node.body)
166
+ return;
167
+ if (node.isConstructor || node.kind === 'constructor')
168
+ return;
169
+ const visibility = String(node.visibility || '').toLowerCase();
170
+ if (visibility === 'private' || visibility === 'internal')
171
+ return;
172
+ const fnName = node.name || '';
173
+ if (!fnName)
174
+ return;
175
+ const zksyncDonation = this.findZksyncDonationAttack(node);
176
+ if (zksyncDonation) {
177
+ const key = `${this.currentContract}.${fnName}.${ZKSYNC_DONATION_PATTERN}`;
178
+ if (!this.emittedKey.has(key)) {
179
+ this.emittedKey.add(key);
180
+ this.findings.push({
181
+ file: this.currentFile,
182
+ contract: this.currentContract,
183
+ 'function': fnName,
184
+ line: zksyncDonation.loc.line,
185
+ endLine: zksyncDonation.loc.endLine,
186
+ column: zksyncDonation.loc.column,
187
+ pattern: ZKSYNC_DONATION_PATTERN,
188
+ confidence: 'high',
189
+ ruleId: RULE_ID,
190
+ severity: 'high',
191
+ message: `Function '${fnName}' converts assets to shares using a donation-inflatable live vault balance.`,
192
+ rationale: 'Mirrors the Venus zkSync/ERC4626 donation pattern: share conversion multiplies deposited assets by share supply and divides by token.balanceOf(address(this)), directly or through a helper, so unsolicited token transfers can inflate the denominator and round victim shares down.',
193
+ suggestedFix: 'Use internal asset accounting, ERC4626 virtual asset/share offsets, or permanent dead-share seeding before exposing share conversion based on vault balances.',
194
+ contractName: this.currentContract,
195
+ functionName: fnName,
196
+ sourceLocation: { line: zksyncDonation.loc.line, column: zksyncDonation.loc.column },
197
+ findingId: '',
198
+ contractHash: '',
199
+ });
200
+ }
201
+ }
202
+ const cellframe = this.findCellframeLiquidityMigration(node, fnName);
203
+ if (cellframe) {
204
+ const key = `${this.currentContract}.${fnName}.${CELLFRAMENET_PATTERN}`;
205
+ if (!this.emittedKey.has(key)) {
206
+ this.emittedKey.add(key);
207
+ this.findings.push({
208
+ file: this.currentFile,
209
+ contract: this.currentContract,
210
+ 'function': fnName,
211
+ line: cellframe.sourceLoc.line,
212
+ endLine: cellframe.sourceLoc.endLine,
213
+ column: cellframe.sourceLoc.column,
214
+ pattern: CELLFRAMENET_PATTERN,
215
+ confidence: 'high',
216
+ ruleId: RULE_ID,
217
+ severity: 'high',
218
+ message: `Function '${fnName}' derives liquidity-migration output from live ${cellframe.sourceLabel} state and uses it in ${cellframe.sinkLabel} without an ordered access guard, reentrancy guard, min-output check, or independent bound.`,
219
+ rationale: 'Mirrors the CellframeNet liquidity-migration calculation issue: a permissionless migration path prices user output from manipulable live pair balances or reserves and moves value or adds liquidity without bounding the computed output against an independent entitlement or slippage constraint.',
220
+ suggestedFix: 'Use recorded LP entitlements or fixed-rate snapshot accounting, restrict administrative migrations with recognized access control, and enforce non-zero min-output/deviation bounds before moving value or adding liquidity.',
221
+ contractName: this.currentContract,
222
+ functionName: fnName,
223
+ sourceLocation: { line: cellframe.sourceLoc.line, column: cellframe.sourceLoc.column },
224
+ findingId: '',
225
+ contractHash: '',
226
+ });
227
+ }
228
+ }
229
+ if ((0, access_control_1.hasRecognisedAccessControlModifier)(node))
230
+ return;
231
+ if (this.hasInlineAccessControl(node.body))
232
+ return;
233
+ const stateMutability = String(node.stateMutability || '').toLowerCase();
234
+ if (stateMutability === 'view' || stateMutability === 'pure')
235
+ return;
236
+ const penpieRewardReentrancy = this.findPenpieRewardReentrancy(node);
237
+ if (penpieRewardReentrancy) {
238
+ const key = `${this.currentContract}.${fnName}.${PENPIE_REWARD_REENTRANCY_PATTERN}`;
239
+ if (!this.emittedKey.has(key)) {
240
+ this.emittedKey.add(key);
241
+ const loc = (0, ast_1.tryLoc)(penpieRewardReentrancy.callNode) || (0, ast_1.tryLoc)(penpieRewardReentrancy.writeNode) || (0, ast_1.tryLoc)(node);
242
+ const line = loc?.line ?? 1;
243
+ this.findings.push({
244
+ file: this.currentFile,
245
+ contract: this.currentContract,
246
+ 'function': fnName,
247
+ line,
248
+ endLine: loc?.endLine ?? line,
249
+ column: loc?.column ?? 0,
250
+ pattern: PENPIE_REWARD_REENTRANCY_PATTERN,
251
+ confidence: 'high',
252
+ ruleId: RULE_ID,
253
+ severity: 'high',
254
+ message: `Function '${fnName}' performs an external interaction before reward/share accounting is finalized.`,
255
+ rationale: 'Mirrors the Penpie io reentrancy/reward-manipulation shape: a permissionless reward harvest or callback-capable interaction runs before rewardDebt, shares, pending reward, or per-share accounting is updated, allowing a reentrant path to observe or manipulate stale accounting.',
256
+ suggestedFix: 'Apply checks-effects-interactions by finalizing reward/share/debt accounting before the external interaction, or protect the entrypoint with a shared reentrancy guard. Keep admin-only synchronization paths behind recognized access control.',
257
+ contractName: this.currentContract,
258
+ functionName: fnName,
259
+ sourceLocation: { line, column: loc?.column ?? 0 },
260
+ findingId: '',
261
+ contractHash: '',
262
+ });
263
+ }
264
+ }
265
+ const zenterest = this.findZenterestPriceOutOfDate(node);
266
+ if (zenterest) {
267
+ const key = `${this.currentContract}.${fnName}.${ZENTEREST_PATTERN}`;
268
+ if (!this.emittedKey.has(key)) {
269
+ this.emittedKey.add(key);
270
+ this.findings.push({
271
+ file: this.currentFile,
272
+ contract: this.currentContract,
273
+ 'function': fnName,
274
+ line: zenterest.sourceLoc.line,
275
+ endLine: zenterest.sourceLoc.endLine,
276
+ column: zenterest.sourceLoc.column,
277
+ pattern: ZENTEREST_PATTERN,
278
+ confidence: 'high',
279
+ ruleId: RULE_ID,
280
+ severity: 'high',
281
+ message: `Function '${fnName}' consumes ${zenterest.sourceLabel} in ${zenterest.sinkLabel} without a same-function timestamp freshness guard.`,
282
+ rationale: 'Mirrors the Zenterest price-out-of-date pattern: a permissionless value-moving path prices payouts or share accounting from a cached price or latestRoundData answer without proving the price timestamp is recent.',
283
+ suggestedFix: 'Check the oracle/cache timestamp against block.timestamp before using the price, or refresh the cached price in the same flow and reject stale values before value movement or share accounting.',
284
+ contractName: this.currentContract,
285
+ functionName: fnName,
286
+ sourceLocation: { line: zenterest.sourceLoc.line, column: zenterest.sourceLoc.column },
287
+ findingId: '',
288
+ contractHash: '',
289
+ });
290
+ }
291
+ }
292
+ if (this.hasRecognisedReentrancyGuardModifier(node))
293
+ return;
294
+ const misoUnspecified = this.findMisoUnspecifiedMsgValueReuse(node);
295
+ if (misoUnspecified) {
296
+ const key = `${this.currentContract}.${fnName}.${MISO_UNSPECIFIED_PATTERN}`;
297
+ if (!this.emittedKey.has(key)) {
298
+ this.emittedKey.add(key);
299
+ const loc = (0, ast_1.tryLoc)(node) ?? { line: 1, endLine: 1, column: 0 };
300
+ this.findings.push({
301
+ file: this.currentFile,
302
+ contract: this.currentContract,
303
+ 'function': fnName,
304
+ line: loc.line || 1,
305
+ endLine: loc.endLine || loc.line || 1,
306
+ column: loc.column || 0,
307
+ pattern: MISO_UNSPECIFIED_PATTERN,
308
+ confidence: 'high',
309
+ ruleId: RULE_ID,
310
+ severity: 'high',
311
+ message: `Payable batch function '${fnName}' loops over self-delegatecall while delegated value-accounting code can repeatedly observe the same msg.value.`,
312
+ rationale: 'Mirrors the SushiSwap MISO unspecified exploit shape: a payable batching entrypoint fans out user-supplied calldata through delegatecall to address(this), so each delegated commit/buy path reuses the original outer msg.value and can credit it multiple times.',
313
+ suggestedFix: 'Avoid payable self-delegatecall batching for value-accounting flows. Pass explicit per-call values and verify their sum against msg.value, or protect administrative batching with recognized access control/reentrancy guards.',
314
+ contractName: this.currentContract,
315
+ functionName: fnName,
316
+ sourceLocation: { line: loc.line || 1, column: loc.column || 0 },
317
+ findingId: '',
318
+ contractHash: '',
319
+ });
320
+ }
321
+ }
322
+ const outdatedGlobal = this.findCsOutdatedGlobalVariable(node);
323
+ if (outdatedGlobal) {
324
+ const key = `${this.currentContract}.${fnName}.${CS_OUTDATED_GLOBAL_PATTERN}`;
325
+ if (!this.emittedKey.has(key)) {
326
+ this.emittedKey.add(key);
327
+ const loc = (0, ast_1.tryLoc)(outdatedGlobal.sinkNode) ?? (0, ast_1.tryLoc)(outdatedGlobal.sourceNode) ?? (0, ast_1.tryLoc)(node);
328
+ const line = loc?.line ?? 1;
329
+ this.findings.push({
330
+ file: this.currentFile,
331
+ contract: this.currentContract,
332
+ 'function': fnName,
333
+ line,
334
+ endLine: loc?.endLine ?? line,
335
+ column: loc?.column ?? 0,
336
+ pattern: CS_OUTDATED_GLOBAL_PATTERN,
337
+ confidence: 'medium',
338
+ ruleId: RULE_ID,
339
+ severity: 'high',
340
+ message: `Function '${fnName}' caches ${outdatedGlobal.sourceName} before an external interaction and reuses the stale value in post-call accounting.`,
341
+ rationale: 'Mirrors the CS outdated-global-variable shape: a mutable reserve/balance/supply value is snapshotted before a token or low-level interaction that can change the underlying global state, then the old snapshot drives accounting or branch decisions after the interaction.',
342
+ suggestedFix: 'Apply checks-effects-interactions, protect the entrypoint with a shared reentrancy guard, or explicitly re-read the reserve/balance/supply value after the external interaction before using it in accounting or decisions.',
343
+ contractName: this.currentContract,
344
+ functionName: fnName,
345
+ sourceLocation: { line, column: loc?.column ?? 0 },
346
+ findingId: '',
347
+ contractHash: '',
348
+ });
349
+ }
350
+ }
351
+ const v3migrator = this.findV3MigratorBiswapPattern(node);
352
+ if (v3migrator) {
353
+ const key = `${this.currentContract}.${fnName}.${V3_MIGRATOR_PATTERN}`;
354
+ if (!this.emittedKey.has(key)) {
355
+ this.emittedKey.add(key);
356
+ const loc = (0, ast_1.tryLoc)(node);
357
+ const line = loc?.line ?? 1;
358
+ this.findings.push({
359
+ file: this.currentFile,
360
+ contract: this.currentContract,
361
+ 'function': fnName,
362
+ line,
363
+ endLine: loc?.endLine ?? line,
364
+ column: loc?.column ?? 0,
365
+ pattern: V3_MIGRATOR_PATTERN,
366
+ confidence: 'high',
367
+ ruleId: RULE_ID,
368
+ severity: 'high',
369
+ message: `Function '${fnName}' migrates tokens via an unvalidated pair and mints without slippage or access control.`,
370
+ rationale: 'Mirrors the Biswap V3Migrator exploit shape: user-controlled pair and tokens are used to drain assets without adequate validation or access control.',
371
+ suggestedFix: 'Validate the pair address against a trusted registry, verify token addresses match the pair, and enforce minimum slippage bounds.',
372
+ contractName: this.currentContract,
373
+ functionName: fnName,
374
+ sourceLocation: { line, column: loc?.column ?? 0 },
375
+ findingId: '',
376
+ contractHash: '',
377
+ });
378
+ }
379
+ }
380
+ const daoDao = this.findDaoDaoBuildFinanceExecutor(node, fnName);
381
+ if (daoDao) {
382
+ const key = `${this.currentContract}.${fnName}.${DAO_DAO_BUILD_FINANCE_PATTERN}`;
383
+ if (!this.emittedKey.has(key)) {
384
+ this.emittedKey.add(key);
385
+ const loc = (0, ast_1.tryLoc)(node) ?? (0, ast_1.tryLoc)(daoDao.node);
386
+ const line = loc?.line ?? 1;
387
+ this.findings.push({
388
+ file: this.currentFile,
389
+ contract: this.currentContract,
390
+ 'function': fnName,
391
+ line,
392
+ endLine: loc?.endLine ?? line,
393
+ column: loc?.column ?? 0,
394
+ pattern: DAO_DAO_BUILD_FINANCE_PATTERN,
395
+ confidence: 'high',
396
+ ruleId: RULE_ID,
397
+ severity: 'critical',
398
+ message: `Function '${fnName}' executes proposal- or caller-controlled target and calldata after only a weak governance gate.`,
399
+ rationale: 'Matches the Build Finance Dao Dao exploit shape: public proposal execution can route arbitrary calldata to an attacker-selected target after a weak vote/proposal gate and before a proposal execution lock is consumed.',
400
+ suggestedFix: 'Restrict execution to a recognized governance or timelock authority, validate snapshot/quorum state, and mark proposals executed or consume queued state before the low-level call.',
401
+ contractName: this.currentContract,
402
+ functionName: fnName,
403
+ sourceLocation: { line, column: loc?.column ?? 0 },
404
+ findingId: '',
405
+ contractHash: '',
406
+ });
407
+ }
408
+ }
409
+ this.detectPriceManipulationInflate(node, fnName);
410
+ const priceInflateKey = `${PRICE_INFLATE_PATTERN}:${this.currentContract}.${fnName}`;
411
+ const cellframeKey = `${this.currentContract}.${fnName}.${CELLFRAMENET_PATTERN}`;
412
+ const coinCut = (this.emittedKey.has(priceInflateKey) || this.emittedKey.has(cellframeKey))
413
+ ? null
414
+ : this.findCoinCutPriceManipulation(node);
415
+ if (coinCut) {
416
+ const key = `${this.currentContract}.${fnName}.${COIN_CUT_PATTERN}`;
417
+ if (!this.emittedKey.has(key)) {
418
+ this.emittedKey.add(key);
419
+ this.findings.push({
420
+ file: this.currentFile,
421
+ contract: this.currentContract,
422
+ 'function': fnName,
423
+ line: coinCut.sourceLoc.line,
424
+ endLine: coinCut.sourceLoc.endLine,
425
+ column: coinCut.sourceLoc.column,
426
+ pattern: COIN_CUT_PATTERN,
427
+ confidence: 'high',
428
+ ruleId: RULE_ID,
429
+ severity: 'high',
430
+ message: `Function '${fnName}' derives value-moving accounting from live ${coinCut.sourceLabel} state and uses it in ${coinCut.sinkLabel} without a TWAP, stored snapshot, or deviation bound.`,
431
+ rationale: 'Mirrors the Caterpillar Coin CUT price-manipulation pattern: a permissionless mint, redeem, claim, or swap path prices output or share accounting from a same-block balanceOf/getReserves read that an attacker can skew before the value-sensitive calculation.',
432
+ suggestedFix: 'Use a TWAP/reference oracle, a stored price snapshot with freshness checks, or bound the live spot value against an independent reference before minting shares, redeeming assets, or transferring the computed output.',
433
+ contractName: this.currentContract,
434
+ functionName: fnName,
435
+ sourceLocation: { line: coinCut.sourceLoc.line, column: coinCut.sourceLoc.column },
436
+ findingId: '',
437
+ contractHash: '',
438
+ });
439
+ }
440
+ }
441
+ const localStructVars = this.collectLocalStructVars(node.body);
442
+ const checked = this.findCheckedBalance(node.body, localStructVars);
443
+ if (!checked)
444
+ return;
445
+ const transferredBase = this.findTransferredBaseName(node.body);
446
+ if (!transferredBase)
447
+ return;
448
+ const debited = this.findDebitedBalance(node.body, localStructVars);
449
+ if (!debited)
450
+ return;
451
+ if (checked.isBe)
452
+ return;
453
+ if (checked.name === debited.name)
454
+ return;
455
+ const otherSideIsBe = debited.isBe || isBeTaggedName(transferredBase) || this.aliasesBeStateVar(transferredBase, node.body);
456
+ if (!otherSideIsBe)
457
+ return;
458
+ if (this.hasReentrancyGuardBody(node.body))
459
+ return;
460
+ const key = `${this.currentContract}.${fnName}`;
461
+ if (this.emittedKey.has(key))
462
+ return;
463
+ this.emittedKey.add(key);
464
+ const primaryLoc = (0, ast_1.tryLoc)(node);
465
+ const line = primaryLoc?.line ?? 1;
466
+ this.findings.push({
467
+ file: this.currentFile,
468
+ contract: this.currentContract,
469
+ 'function': fnName,
470
+ line,
471
+ endLine: primaryLoc?.endLine ?? line,
472
+ column: primaryLoc?.column ?? 0,
473
+ pattern: PATTERN,
474
+ confidence: 'medium',
475
+ ruleId: RULE_ID,
476
+ severity: 'medium',
477
+ message: `Function '${fnName}' validates balance of '${checked.name}' but transfers '${transferredBase}' and debits '${debited.name}' — a stale renamed-withdraw path.`,
478
+ contractName: this.currentContract,
479
+ functionName: fnName,
480
+ sourceLocation: { line, column: primaryLoc?.column ?? 0 },
481
+ findingId: '',
482
+ contractHash: '',
483
+ });
484
+ }
485
+ collectLocalStructVars(body) {
486
+ const out = new Set();
487
+ this.walk(body, node => {
488
+ if ((0, ast_1.isNode)(node, 'VariableDeclarationStatement')) {
489
+ for (const v of node.variables || []) {
490
+ if (!v?.name)
491
+ continue;
492
+ if ((0, ast_1.isNode)(v.typeName, 'UserDefinedTypeName')) {
493
+ out.add(v.name);
494
+ }
495
+ }
496
+ }
497
+ });
498
+ return out;
499
+ }
500
+ findCheckedBalance(body, localStructVars) {
501
+ let found = null;
502
+ this.walk(body, node => {
503
+ if (found)
504
+ return;
505
+ if (!(0, ast_1.isNode)(node, 'BinaryOperation'))
506
+ return;
507
+ if (!/^(>=|>|<=|<|==)$/.test(node.operator || ''))
508
+ return;
509
+ const left = node.left || node.leftExpression;
510
+ const right = node.right || node.rightExpression;
511
+ for (const expr of [left, right]) {
512
+ const ref = this.refFromExpr(expr, localStructVars);
513
+ if (ref) {
514
+ found = ref;
515
+ return;
516
+ }
517
+ }
518
+ });
519
+ return found;
520
+ }
521
+ findDebitedBalance(body, localStructVars) {
522
+ let found = null;
523
+ this.walk(body, node => {
524
+ if (found)
525
+ return;
526
+ if (!(0, ast_1.isNode)(node, 'BinaryOperation'))
527
+ return;
528
+ if (!/^[+\-*/]?=$/.test(node.operator || ''))
529
+ return;
530
+ const left = node.left || node.leftExpression;
531
+ const ref = this.refFromExpr(left, localStructVars);
532
+ if (ref)
533
+ found = ref;
534
+ });
535
+ return found;
536
+ }
537
+ refFromExpr(expr, localStructVars) {
538
+ if (!expr)
539
+ return null;
540
+ if ((0, ast_1.isNode)(expr, 'IndexAccess')) {
541
+ const base = expr.base || expr.baseExpression;
542
+ const baseName = this.simpleName(base);
543
+ if (baseName && isBalanceLikeName(baseName)) {
544
+ return { name: baseName, isBe: isBeTaggedName(baseName) };
545
+ }
546
+ return null;
547
+ }
548
+ if ((0, ast_1.isNode)(expr, 'MemberAccess')) {
549
+ const fieldName = expr.memberName || '';
550
+ if (!fieldName || !isBalanceLikeName(fieldName))
551
+ return null;
552
+ const inner = expr.expression;
553
+ const innerName = this.simpleName(inner);
554
+ if (!innerName || !localStructVars.has(innerName))
555
+ return null;
556
+ return { name: `${innerName}.${fieldName}`, isBe: isBeTaggedName(fieldName) };
557
+ }
558
+ if ((0, ast_1.isNode)(expr, 'Identifier')) {
559
+ const name = expr.name || '';
560
+ if (name && isBalanceLikeName(name) && this.stateVars.includes(name)) {
561
+ return { name, isBe: isBeTaggedName(name) };
562
+ }
563
+ }
564
+ return null;
565
+ }
566
+ findTransferredBaseName(body) {
567
+ let found = null;
568
+ this.walk(body, node => {
569
+ if (found)
570
+ return;
571
+ if (!(0, ast_1.isNode)(node, 'FunctionCall'))
572
+ return;
573
+ const callee = node.expression;
574
+ if (!(0, ast_1.isNode)(callee, 'MemberAccess'))
575
+ return;
576
+ const member = String(callee.memberName || '');
577
+ if (!TRANSFER_METHODS.has(member))
578
+ return;
579
+ const baseName = this.memberAccessRoot(callee.expression);
580
+ if (baseName)
581
+ found = baseName;
582
+ });
583
+ return found;
584
+ }
585
+ findMisoUnspecifiedMsgValueReuse(fn) {
586
+ if (!fn?.body)
587
+ return false;
588
+ if (!this.isPayableFunction(fn))
589
+ return false;
590
+ if (this.hasReentrancyGuardBody(fn.body))
591
+ return false;
592
+ if (!this.hasLoopedSelfDelegatecall(fn.body))
593
+ return false;
594
+ return this.contractHasMisoValueAccountingSink(fn);
595
+ }
596
+ isPayableFunction(fn) {
597
+ return String(fn?.stateMutability || '').toLowerCase() === 'payable';
598
+ }
599
+ isExternallyCallableFunction(fn) {
600
+ const visibility = String(fn?.visibility || '').toLowerCase();
601
+ return visibility === '' || visibility === 'public' || visibility === 'external' || visibility === 'default';
602
+ }
603
+ hasLoopedSelfDelegatecall(body) {
604
+ let found = false;
605
+ this.walk(body, node => {
606
+ if (found || !this.isLoopStatement(node))
607
+ return;
608
+ this.walk(node.body, child => {
609
+ if (found || !(0, ast_1.isNode)(child, 'FunctionCall'))
610
+ return;
611
+ if (this.isSelfDelegatecall(child))
612
+ found = true;
613
+ });
614
+ });
615
+ return found;
616
+ }
617
+ isLoopStatement(node) {
618
+ return (0, ast_1.isNode)(node, 'ForStatement') || (0, ast_1.isNode)(node, 'WhileStatement') || (0, ast_1.isNode)(node, 'DoWhileStatement');
619
+ }
620
+ isSelfDelegatecall(call) {
621
+ if (!(0, ast_1.isNode)(call, 'FunctionCall'))
622
+ return false;
623
+ const callee = this.unwrapCallOptions(call.expression);
624
+ if (!(0, ast_1.isNode)(callee, 'MemberAccess'))
625
+ return false;
626
+ if (String(callee.memberName || '').toLowerCase() !== 'delegatecall')
627
+ return false;
628
+ return this.isAddressThis(callee.expression);
629
+ }
630
+ contractHasMisoValueAccountingSink(batchFn) {
631
+ for (const fn of this.contractFunctions.values()) {
632
+ if (fn === batchFn)
633
+ continue;
634
+ if (!fn?.body)
635
+ continue;
636
+ if (!this.isExternallyCallableFunction(fn))
637
+ continue;
638
+ if (!this.isPayableFunction(fn))
639
+ continue;
640
+ if (this.hasRecognisedReentrancyGuardModifier(fn))
641
+ continue;
642
+ if (this.hasReentrancyGuardBody(fn.body))
643
+ continue;
644
+ if (this.bodyHasMisoMsgValueAccountingWrite(fn.body))
645
+ return true;
646
+ if (this.callsMisoMsgValueAccountingHelper(fn.body))
647
+ return true;
648
+ }
649
+ return false;
650
+ }
651
+ callsMisoMsgValueAccountingHelper(body) {
652
+ const internalNames = this.internalCallNames(body);
653
+ for (const internalName of internalNames) {
654
+ const helper = this.contractFunctions.get(internalName);
655
+ if (!helper?.body)
656
+ continue;
657
+ if (!INTERNAL_VISIBILITIES.has(String(helper.visibility || '').toLowerCase()))
658
+ continue;
659
+ if (this.bodyHasMisoMsgValueAccountingWrite(helper.body))
660
+ return true;
661
+ }
662
+ return false;
663
+ }
664
+ bodyHasMisoMsgValueAccountingWrite(body) {
665
+ let found = false;
666
+ this.walk(body, node => {
667
+ if (found || !(0, ast_1.isNode)(node, 'BinaryOperation'))
668
+ return;
669
+ const operator = String(node.operator || '');
670
+ if (!/^[+\-]?=$/.test(operator))
671
+ return;
672
+ const left = node.left || node.leftExpression;
673
+ if (!this.isMisoAccountingWriteTarget(left))
674
+ return;
675
+ const right = node.right || node.rightExpression;
676
+ if (this.exprContainsMsgValue(right))
677
+ found = true;
678
+ });
679
+ return found;
680
+ }
681
+ isMisoAccountingWriteTarget(expr) {
682
+ if (!expr)
683
+ return false;
684
+ if ((0, ast_1.isNode)(expr, 'IndexAccess'))
685
+ return MISO_VALUE_ACCOUNTING_NAMES.test(this.flattenNames(expr.base || expr.baseExpression, true));
686
+ if ((0, ast_1.isNode)(expr, 'MemberAccess'))
687
+ return MISO_VALUE_ACCOUNTING_NAMES.test(String(expr.memberName || ''));
688
+ if ((0, ast_1.isNode)(expr, 'Identifier'))
689
+ return MISO_VALUE_ACCOUNTING_NAMES.test(String(expr.name || ''));
690
+ return false;
691
+ }
692
+ exprContainsMsgValue(expr) {
693
+ let found = false;
694
+ this.walk(expr, node => {
695
+ if (found)
696
+ return;
697
+ if (!(0, ast_1.isNode)(node, 'MemberAccess'))
698
+ return;
699
+ if (String(node.memberName || '') !== 'value')
700
+ return;
701
+ const base = node.expression;
702
+ if ((0, ast_1.isNode)(base, 'Identifier') && String(base.name || '') === 'msg')
703
+ found = true;
704
+ });
705
+ return found;
706
+ }
707
+ findDaoDaoBuildFinanceExecutor(fn, fnName) {
708
+ const body = fn?.body;
709
+ if (!body)
710
+ return null;
711
+ if (this.hasRecognisedReentrancyGuardModifier(fn))
712
+ return null;
713
+ if (this.hasReentrancyGuardBody(body))
714
+ return null;
715
+ if (!DAO_DAO_EXECUTE_NAMES.test(fnName || '') && !this.hasDaoDaoGovernanceSurface())
716
+ return null;
717
+ const params = this.collectFunctionParameters(fn);
718
+ const storageAliases = this.collectDaoDaoStorageAliases(body);
719
+ const calls = this.collectDaoDaoLowLevelCalls(body);
720
+ for (const call of calls) {
721
+ if (!this.isDangerousDaoDaoCall(call, params, storageAliases))
722
+ continue;
723
+ if (!this.hasDaoDaoGovernanceGateBefore(body, call.node))
724
+ continue;
725
+ if (this.hasDaoDaoExecutionLockBefore(body, call))
726
+ continue;
727
+ return call;
728
+ }
729
+ return null;
730
+ }
731
+ hasDaoDaoGovernanceSurface() {
732
+ if (this.stateVars.some(name => DAO_DAO_GOVERNANCE_NAMES.test(name)))
733
+ return true;
734
+ for (const name of this.contractFunctions.keys()) {
735
+ if (DAO_DAO_GOVERNANCE_NAMES.test(name))
736
+ return true;
737
+ }
738
+ return false;
739
+ }
740
+ collectFunctionParameters(fn) {
741
+ const params = new Set();
742
+ for (const param of fn?.parameters || []) {
743
+ if (param?.name)
744
+ params.add(String(param.name));
745
+ }
746
+ return params;
747
+ }
748
+ collectDaoDaoStorageAliases(body) {
749
+ const aliases = new Set();
750
+ this.walk(body, node => {
751
+ if (!(0, ast_1.isNode)(node, 'VariableDeclarationStatement'))
752
+ return;
753
+ if (!this.isDaoDaoProposalLikeStorageRead(node.initialValue))
754
+ return;
755
+ for (const variable of node.variables || []) {
756
+ if (variable?.name && String(variable.storageLocation || '').toLowerCase() === 'storage') {
757
+ aliases.add(String(variable.name));
758
+ }
759
+ }
760
+ });
761
+ return aliases;
762
+ }
763
+ collectDaoDaoLowLevelCalls(body) {
764
+ const calls = [];
765
+ this.walk(body, node => {
766
+ const call = this.daoDaoLowLevelCall(node);
767
+ if (call)
768
+ calls.push(call);
769
+ });
770
+ return calls.sort((a, b) => this.nodeStart(a.node) - this.nodeStart(b.node));
771
+ }
772
+ daoDaoLowLevelCall(node) {
773
+ if (!(0, ast_1.isNode)(node, 'FunctionCall'))
774
+ return null;
775
+ const callee = this.unwrapCallOptions(node.expression);
776
+ if (!(0, ast_1.isNode)(callee, 'MemberAccess'))
777
+ return null;
778
+ const member = String(callee.memberName || '').toLowerCase();
779
+ if (!LOW_LEVEL_GOVERNANCE_CALLS.has(member))
780
+ return null;
781
+ return {
782
+ node,
783
+ member,
784
+ target: callee.expression,
785
+ args: Array.isArray(node.arguments) ? node.arguments : [],
786
+ };
787
+ }
788
+ isDangerousDaoDaoCall(call, params, storageAliases) {
789
+ if (call.args.length === 0)
790
+ return false;
791
+ const targetControlled = this.isDaoDaoControlledTarget(call.target, params, storageAliases);
792
+ const dataControlled = call.args.some(arg => this.isDaoDaoControlledData(arg, params, storageAliases));
793
+ return targetControlled && dataControlled;
794
+ }
795
+ isDaoDaoControlledTarget(expr, params, storageAliases) {
796
+ if (!expr)
797
+ return false;
798
+ if (this.exprUsesNames(expr, params))
799
+ return true;
800
+ return this.isDaoDaoProposalMember(expr, storageAliases, /target|implementation|token|to|destination|callee/i);
801
+ }
802
+ isDaoDaoControlledData(expr, params, storageAliases) {
803
+ if (!expr)
804
+ return false;
805
+ if (this.exprUsesNames(expr, params))
806
+ return true;
807
+ return this.isDaoDaoProposalMember(expr, storageAliases, /data|calldata|payload|callData|selector/i);
808
+ }
809
+ isDaoDaoProposalMember(expr, storageAliases, memberPattern) {
810
+ if (!(0, ast_1.isNode)(expr, 'MemberAccess'))
811
+ return false;
812
+ if (!memberPattern.test(String(expr.memberName || '')))
813
+ return false;
814
+ const root = this.rootIdentifier(expr.expression);
815
+ return !!root && storageAliases.has(root);
816
+ }
817
+ isDaoDaoProposalLikeStorageRead(expr) {
818
+ const root = this.rootIdentifier(expr);
819
+ return !!root && /proposal|queued|passed/i.test(root);
820
+ }
821
+ hasDaoDaoGovernanceGateBefore(body, callNode) {
822
+ let found = false;
823
+ this.walkBefore(body, callNode, node => {
824
+ if (found || !(0, ast_1.isNode)(node, 'FunctionCall'))
825
+ return;
826
+ const callee = node.expression;
827
+ if (!(0, ast_1.isNode)(callee, 'Identifier'))
828
+ return;
829
+ if (!['require', 'assert'].includes(String(callee.name || '')))
830
+ return;
831
+ const condition = node.arguments?.[0];
832
+ if (this.containsNameMatching(condition, DAO_DAO_GOVERNANCE_NAMES))
833
+ found = true;
834
+ });
835
+ return found;
836
+ }
837
+ hasDaoDaoExecutionLockBefore(body, call) {
838
+ let found = false;
839
+ const callStorageRoots = this.daoDaoCallStorageRoots(call);
840
+ this.walkBefore(body, call.node, node => {
841
+ if (found || !(0, ast_1.isNode)(node, 'BinaryOperation'))
842
+ return;
843
+ if (String(node.operator || '') !== '=')
844
+ return;
845
+ const left = node.left || node.leftExpression;
846
+ const right = node.right || node.rightExpression;
847
+ if (this.isDaoDaoConsumingExecutionWrite(left, right, callStorageRoots))
848
+ found = true;
849
+ });
850
+ return found;
851
+ }
852
+ daoDaoCallStorageRoots(call) {
853
+ const roots = new Set();
854
+ const targetRoot = this.rootIdentifier(call.target);
855
+ if (targetRoot)
856
+ roots.add(targetRoot);
857
+ for (const arg of call.args) {
858
+ const argRoot = this.rootIdentifier(arg);
859
+ if (argRoot)
860
+ roots.add(argRoot);
861
+ }
862
+ return roots;
863
+ }
864
+ isDaoDaoConsumingExecutionWrite(left, right, callStorageRoots) {
865
+ const value = this.booleanLiteralValue(right);
866
+ if (value === null)
867
+ return false;
868
+ if ((0, ast_1.isNode)(left, 'MemberAccess')) {
869
+ const member = String(left.memberName || '');
870
+ const root = this.rootIdentifier(left.expression);
871
+ if (root && callStorageRoots.size > 0 && !callStorageRoots.has(root))
872
+ return false;
873
+ return this.isDaoDaoProtectiveLockValue(member, value);
874
+ }
875
+ if ((0, ast_1.isNode)(left, 'IndexAccess')) {
876
+ const root = this.rootIdentifier(left);
877
+ return this.isDaoDaoProtectiveLockValue(root, value);
878
+ }
879
+ return false;
880
+ }
881
+ isDaoDaoProtectiveLockValue(name, value) {
882
+ if (DAO_DAO_EXECUTION_CONSUMED_NAMES.test(name))
883
+ return value === true;
884
+ if (DAO_DAO_QUEUE_FLAG_NAMES.test(name))
885
+ return value === false;
886
+ return false;
887
+ }
888
+ booleanLiteralValue(expr) {
889
+ if ((0, ast_1.isNode)(expr, 'BooleanLiteral'))
890
+ return expr.value === true;
891
+ if ((0, ast_1.isNode)(expr, 'Identifier')) {
892
+ if (String(expr.name || '') === 'true')
893
+ return true;
894
+ if (String(expr.name || '') === 'false')
895
+ return false;
896
+ }
897
+ return null;
898
+ }
899
+ walkBefore(root, before, visit) {
900
+ const stop = this.nodeStart(before);
901
+ this.walk(root, node => {
902
+ if (this.nodeStart(node) < stop)
903
+ visit(node);
904
+ });
905
+ }
906
+ nodeStart(node) {
907
+ return Array.isArray(node?.range) ? node.range[0] : Number.MAX_SAFE_INTEGER;
908
+ }
909
+ rootIdentifier(expr) {
910
+ let current = expr;
911
+ while (current) {
912
+ if ((0, ast_1.isNode)(current, 'Identifier'))
913
+ return String(current.name || '');
914
+ if ((0, ast_1.isNode)(current, 'MemberAccess')) {
915
+ current = current.expression;
916
+ continue;
917
+ }
918
+ if ((0, ast_1.isNode)(current, 'IndexAccess')) {
919
+ current = current.base || current.baseExpression;
920
+ continue;
921
+ }
922
+ return '';
923
+ }
924
+ return '';
925
+ }
926
+ containsNameMatching(expr, pattern) {
927
+ let found = false;
928
+ this.walk(expr, node => {
929
+ if (found)
930
+ return;
931
+ if ((0, ast_1.isNode)(node, 'Identifier') && pattern.test(String(node.name || '')))
932
+ found = true;
933
+ if ((0, ast_1.isNode)(node, 'MemberAccess') && pattern.test(String(node.memberName || '')))
934
+ found = true;
935
+ });
936
+ return found;
937
+ }
938
+ // For the alias case: `IERC20 out = beToken;` then `out.transfer(...)`.
939
+ // If the transferred base is a local alias for a BE-tagged state var,
940
+ // treat it as BE-tagged.
941
+ aliasesBeStateVar(baseName, body) {
942
+ if (!baseName)
943
+ return false;
944
+ let aliased = false;
945
+ this.walk(body, node => {
946
+ if (aliased)
947
+ return;
948
+ if (!(0, ast_1.isNode)(node, 'VariableDeclarationStatement'))
949
+ return;
950
+ const variables = node.variables || [];
951
+ const initialValue = node.initialValue;
952
+ for (const v of variables) {
953
+ if (!v || v.name !== baseName)
954
+ continue;
955
+ const initName = this.simpleName(initialValue);
956
+ if (initName && isBeTaggedName(initName)) {
957
+ aliased = true;
958
+ return;
959
+ }
960
+ }
961
+ });
962
+ return aliased;
963
+ }
964
+ detectPriceManipulationInflate(fn, fnName) {
965
+ if (fnName !== 'transfer')
966
+ return;
967
+ const paths = this.analyzePriceInflateStatements(fn.body?.statements || [], new Set(), 0);
968
+ // Emit only when a *single* execution path derives a quote, performs the
969
+ // quoted payout, and self-mutates afterwards — and that same path is not
970
+ // bounded. Signals from unrelated `if` arms never satisfy this check.
971
+ const vulnerablePath = paths.some(p => p.hasQuote && p.hasQuotedPayout && p.hasPostPayoutSelfMutation && !p.hasBoundBeforePayout);
972
+ if (!vulnerablePath)
973
+ return;
974
+ const key = `${PRICE_INFLATE_PATTERN}:${this.currentContract}.${fnName}`;
975
+ if (this.emittedKey.has(key))
976
+ return;
977
+ this.emittedKey.add(key);
978
+ const primaryLoc = (0, ast_1.tryLoc)(fn);
979
+ const line = primaryLoc?.line ?? 1;
980
+ this.findings.push({
981
+ file: this.currentFile,
982
+ contract: this.currentContract,
983
+ 'function': fnName,
984
+ line,
985
+ endLine: primaryLoc?.endLine ?? line,
986
+ column: primaryLoc?.column ?? 0,
987
+ pattern: PRICE_INFLATE_PATTERN,
988
+ confidence: 'high',
989
+ ruleId: RULE_ID,
990
+ severity: 'high',
991
+ message: `Transfer sell path derives a payout from manipulable live price state, sends value to the seller, then burns or fees self-held balance, enabling price inflation.`,
992
+ contractName: this.currentContract,
993
+ functionName: fnName,
994
+ sourceLocation: { line, column: primaryLoc?.column ?? 0 },
995
+ findingId: '',
996
+ contractHash: '',
997
+ });
998
+ }
999
+ // Path-scoped walk: produces one `InflatePathState` per distinct execution
1000
+ // path through `statements`. `initialHasQuotedPayout` seeds the carried
1001
+ // payout state so an internal helper expanded *after* a quoted payout in
1002
+ // the caller can still recognise its own self-balance burn as post-payout.
1003
+ analyzePriceInflateStatements(statements, expandedHelpers, depth, initialState) {
1004
+ const initial = initialState ? this.cloneInflatePathState(initialState) : {
1005
+ quoteVars: new Set(),
1006
+ hasQuote: false,
1007
+ hasQuotedPayout: false,
1008
+ hasPostPayoutSelfMutation: false,
1009
+ hasBoundBeforePayout: false,
1010
+ };
1011
+ return this.walkInflateSequence(statements || [], [initial], expandedHelpers, depth);
1012
+ }
1013
+ // Threads every carried path-state through `statements` in source order.
1014
+ // `IfStatement` forks the paths; all other statement kinds mutate each
1015
+ // carried path in place so signals propagate across loops and blocks too.
1016
+ walkInflateSequence(statements, startStates, expandedHelpers, depth) {
1017
+ let paths = startStates;
1018
+ for (const stmt of statements || []) {
1019
+ paths = this.applyInflateStatement(stmt, paths, expandedHelpers, depth);
1020
+ if (paths.length > MAX_INFLATE_PATHS)
1021
+ paths = paths.slice(0, MAX_INFLATE_PATHS);
1022
+ }
1023
+ return paths;
1024
+ }
1025
+ applyInflateStatement(stmt, paths, expandedHelpers, depth) {
1026
+ if (!stmt)
1027
+ return paths;
1028
+ if ((0, ast_1.isNode)(stmt, 'Block')) {
1029
+ return this.walkInflateSequence(stmt.statements || [], paths, expandedHelpers, depth);
1030
+ }
1031
+ if ((0, ast_1.isNode)(stmt, 'IfStatement')) {
1032
+ const trueStmts = this.inflateBranchStatements(stmt.trueBody);
1033
+ const falseStmts = stmt.falseBody ? this.inflateBranchStatements(stmt.falseBody) : null;
1034
+ const out = [];
1035
+ for (const p of paths) {
1036
+ // Each arm gets an independent copy of the path-state and of the
1037
+ // helper-expansion ledger, so evidence cannot leak between branches.
1038
+ out.push(...this.walkInflateSequence(trueStmts, [this.cloneInflatePathState(p)], new Set(expandedHelpers), depth));
1039
+ if (falseStmts) {
1040
+ out.push(...this.walkInflateSequence(falseStmts, [this.cloneInflatePathState(p)], new Set(expandedHelpers), depth));
1041
+ }
1042
+ else {
1043
+ // No `else`: the path that skips the `if` continues unchanged.
1044
+ out.push(this.cloneInflatePathState(p));
1045
+ }
1046
+ }
1047
+ return out;
1048
+ }
1049
+ if ((0, ast_1.isNode)(stmt, 'WhileStatement') || (0, ast_1.isNode)(stmt, 'ForStatement') || (0, ast_1.isNode)(stmt, 'DoWhileStatement')) {
1050
+ return this.walkInflateSequence(this.inflateBranchStatements(stmt.body), paths, expandedHelpers, depth);
1051
+ }
1052
+ const out = [];
1053
+ for (const p of paths) {
1054
+ out.push(...this.applyInflateLeaf(stmt, p, expandedHelpers, depth));
1055
+ }
1056
+ return out;
1057
+ }
1058
+ inflateBranchStatements(node) {
1059
+ if (!node)
1060
+ return [];
1061
+ if ((0, ast_1.isNode)(node, 'Block'))
1062
+ return node.statements || [];
1063
+ return [node];
1064
+ }
1065
+ cloneInflatePathState(s) {
1066
+ return {
1067
+ quoteVars: new Set(s.quoteVars),
1068
+ hasQuote: s.hasQuote,
1069
+ hasQuotedPayout: s.hasQuotedPayout,
1070
+ hasPostPayoutSelfMutation: s.hasPostPayoutSelfMutation,
1071
+ hasBoundBeforePayout: s.hasBoundBeforePayout,
1072
+ };
1073
+ }
1074
+ // Folds the signals of a single non-branching statement into one path-state.
1075
+ applyInflateLeaf(stmt, state, expandedHelpers, depth) {
1076
+ const observeQuote = (name) => {
1077
+ if (!name)
1078
+ return;
1079
+ state.quoteVars.add(name);
1080
+ state.hasQuote = true;
1081
+ };
1082
+ if ((0, ast_1.isNode)(stmt, 'VariableDeclarationStatement')) {
1083
+ const initialValue = stmt.initialValue;
1084
+ if (this.isGetReservesCall(initialValue)) {
1085
+ for (const v of stmt.variables || []) {
1086
+ if (v?.name)
1087
+ observeQuote(v.name);
1088
+ }
1089
+ }
1090
+ else if (this.exprHasManipulablePriceSource(initialValue, state.quoteVars) || this.internalReturnsManipulablePrice(initialValue)) {
1091
+ for (const v of stmt.variables || []) {
1092
+ if (v?.name)
1093
+ observeQuote(v.name);
1094
+ }
1095
+ }
1096
+ if (this.isQuotedPayoutCall(initialValue, state.quoteVars)) {
1097
+ state.hasQuotedPayout = true;
1098
+ }
1099
+ return this.expandInflateHelpersFromExpr(initialValue, state, expandedHelpers, depth);
1100
+ }
1101
+ if ((0, ast_1.isNode)(stmt, 'ExpressionStatement')) {
1102
+ const expr = stmt.expression;
1103
+ if ((0, ast_1.isNode)(expr, 'BinaryOperation')) {
1104
+ const left = expr.left || expr.leftExpression;
1105
+ const right = expr.right || expr.rightExpression;
1106
+ const op = String(expr.operator || '');
1107
+ if (op === '=' && (this.exprHasManipulablePriceSource(right, state.quoteVars) || this.internalReturnsManipulablePrice(right))) {
1108
+ observeQuote(this.simpleName(left));
1109
+ }
1110
+ if (this.isQuotedPayoutCall(right, state.quoteVars)) {
1111
+ state.hasQuotedPayout = true;
1112
+ }
1113
+ let expanded = this.expandInflateHelpersFromExpr(right, state, expandedHelpers, depth);
1114
+ if (state.hasQuotedPayout) {
1115
+ expanded = expanded.map(p => {
1116
+ if (this.isSelfBalanceDecrease(expr)) {
1117
+ return { ...p, quoteVars: new Set(p.quoteVars), hasPostPayoutSelfMutation: true };
1118
+ }
1119
+ return p;
1120
+ });
1121
+ }
1122
+ return expanded;
1123
+ }
1124
+ // A quote-bound `require` only suppresses the path it lives on, and
1125
+ // only when it precedes the quoted payout on that same path.
1126
+ if (this.isQuotedBoundRequire(expr, state.quoteVars) && !state.hasQuotedPayout) {
1127
+ state.hasBoundBeforePayout = true;
1128
+ }
1129
+ if (this.isQuotedPayoutCall(expr, state.quoteVars)) {
1130
+ state.hasQuotedPayout = true;
1131
+ }
1132
+ if (state.hasQuotedPayout && this.isSelfBalanceDecrease(expr)) {
1133
+ state.hasPostPayoutSelfMutation = true;
1134
+ }
1135
+ return this.expandInflateHelpersFromExpr(expr, state, expandedHelpers, depth);
1136
+ }
1137
+ return [state];
1138
+ }
1139
+ expandInflateHelpersFromExpr(expr, state, expandedHelpers, depth) {
1140
+ const internalNames = this.internalCallNames(expr);
1141
+ let paths = [state];
1142
+ for (const internalName of internalNames) {
1143
+ if (depth >= 2 || expandedHelpers.has(internalName))
1144
+ continue;
1145
+ const helper = this.contractFunctions.get(internalName);
1146
+ if (!helper?.body || !INTERNAL_VISIBILITIES.has(String(helper.visibility || '').toLowerCase()))
1147
+ continue;
1148
+ const nextPaths = [];
1149
+ for (const p of paths) {
1150
+ const helperExpanded = new Set(expandedHelpers);
1151
+ helperExpanded.add(internalName);
1152
+ nextPaths.push(...this.analyzePriceInflateStatements(helper.body.statements || [], helperExpanded, depth + 1, p));
1153
+ }
1154
+ paths = nextPaths.length > MAX_INFLATE_PATHS ? nextPaths.slice(0, MAX_INFLATE_PATHS) : nextPaths;
1155
+ }
1156
+ return paths;
1157
+ }
1158
+ internalCallName(expr) {
1159
+ if (!(0, ast_1.isNode)(expr, 'FunctionCall'))
1160
+ return '';
1161
+ const callee = expr.expression;
1162
+ if (!(0, ast_1.isNode)(callee, 'Identifier'))
1163
+ return '';
1164
+ return callee.name || '';
1165
+ }
1166
+ internalCallNames(expr) {
1167
+ const names = [];
1168
+ const seen = new Set();
1169
+ this.walk(expr, node => {
1170
+ const name = this.internalCallName(node);
1171
+ if (!name || seen.has(name))
1172
+ return;
1173
+ seen.add(name);
1174
+ names.push(name);
1175
+ });
1176
+ return names;
1177
+ }
1178
+ internalReturnsManipulablePrice(expr, returningHelpers = new Set()) {
1179
+ const name = this.internalCallName(expr);
1180
+ if (!name)
1181
+ return false;
1182
+ if (returningHelpers.has(name))
1183
+ return false;
1184
+ const helper = this.contractFunctions.get(name);
1185
+ if (!helper?.body || !INTERNAL_VISIBILITIES.has(String(helper.visibility || '').toLowerCase()))
1186
+ return false;
1187
+ const nestedReturningHelpers = new Set(returningHelpers);
1188
+ nestedReturningHelpers.add(name);
1189
+ const quoteVars = new Set();
1190
+ this.walk(helper.body, node => {
1191
+ if ((0, ast_1.isNode)(node, 'VariableDeclarationStatement')) {
1192
+ const initialValue = node.initialValue;
1193
+ if (this.isGetReservesCall(initialValue) || this.exprHasManipulablePriceSource(initialValue, quoteVars) || this.internalReturnsManipulablePrice(initialValue, nestedReturningHelpers)) {
1194
+ for (const v of node.variables || []) {
1195
+ if (v?.name)
1196
+ quoteVars.add(v.name);
1197
+ }
1198
+ }
1199
+ }
1200
+ else if ((0, ast_1.isNode)(node, 'BinaryOperation')) {
1201
+ const op = String(node.operator || '');
1202
+ if (op !== '=')
1203
+ return;
1204
+ const right = node.right || node.rightExpression;
1205
+ if (!this.exprHasManipulablePriceSource(right, quoteVars) && !this.internalReturnsManipulablePrice(right, nestedReturningHelpers))
1206
+ return;
1207
+ const left = node.left || node.leftExpression;
1208
+ const name = this.simpleName(left);
1209
+ if (name)
1210
+ quoteVars.add(name);
1211
+ }
1212
+ });
1213
+ let found = false;
1214
+ this.walk(helper.body, node => {
1215
+ if (found)
1216
+ return;
1217
+ if (!(0, ast_1.isNode)(node, 'ReturnStatement'))
1218
+ return;
1219
+ if (this.exprHasManipulablePriceSource(node.expression, quoteVars))
1220
+ found = true;
1221
+ });
1222
+ return found;
1223
+ }
1224
+ isQuotedBoundRequire(expr, quoteVars) {
1225
+ if (!(0, ast_1.isNode)(expr, 'FunctionCall'))
1226
+ return false;
1227
+ const callee = expr.expression;
1228
+ if (!(0, ast_1.isNode)(callee, 'Identifier') || callee.name !== 'require')
1229
+ return false;
1230
+ const condition = expr.arguments?.[0];
1231
+ if (!(0, ast_1.isNode)(condition, 'BinaryOperation'))
1232
+ return false;
1233
+ if (!/^(<=|<|>=|>)$/.test(String(condition.operator || '')))
1234
+ return false;
1235
+ return this.exprReferencesAny(condition, quoteVars);
1236
+ }
1237
+ isQuotedPayoutCall(expr, quoteVars) {
1238
+ if (!(0, ast_1.isNode)(expr, 'FunctionCall'))
1239
+ return false;
1240
+ const callee = expr.expression;
1241
+ if ((0, ast_1.isNode)(callee, 'MemberAccess')) {
1242
+ const member = String(callee.memberName || '');
1243
+ if ((member === 'transfer' || member === 'send') && this.exprReferencesAny(expr.arguments?.[0], quoteVars)) {
1244
+ return true;
1245
+ }
1246
+ if (member === 'call')
1247
+ return false;
1248
+ }
1249
+ if ((0, ast_1.isNode)(callee, 'NameValueExpression')) {
1250
+ const target = callee.expression;
1251
+ if (!(0, ast_1.isNode)(target, 'MemberAccess') || target.memberName !== 'call')
1252
+ return false;
1253
+ return this.nameValueExpressionReferencesQuote(callee, quoteVars);
1254
+ }
1255
+ return false;
1256
+ }
1257
+ nameValueExpressionReferencesQuote(node, quoteVars) {
1258
+ const args = node?.arguments;
1259
+ if (!args)
1260
+ return false;
1261
+ if (Array.isArray(args))
1262
+ return args.some(arg => this.exprReferencesAny(arg, quoteVars));
1263
+ if (Array.isArray(args.arguments))
1264
+ return args.arguments.some((arg) => this.exprReferencesAny(arg, quoteVars));
1265
+ return this.exprReferencesAny(args, quoteVars);
1266
+ }
1267
+ exprHasManipulablePriceSource(expr, quoteVars) {
1268
+ let found = false;
1269
+ this.walk(expr, node => {
1270
+ if (found)
1271
+ return;
1272
+ if ((0, ast_1.isNode)(node, 'Identifier') && quoteVars.has(node.name || '')) {
1273
+ found = true;
1274
+ return;
1275
+ }
1276
+ if (this.isAddressThisBalance(node) || this.isSelfBalanceRef(node) || this.isGetReservesCall(node) || this.isGetAmountsOutCall(node) || this.isTotalSupplyRead(node)) {
1277
+ found = true;
1278
+ }
1279
+ });
1280
+ return found;
1281
+ }
1282
+ exprReferencesAny(expr, names) {
1283
+ if (!expr || names.size === 0)
1284
+ return false;
1285
+ let found = false;
1286
+ this.walk(expr, node => {
1287
+ if (found)
1288
+ return;
1289
+ if ((0, ast_1.isNode)(node, 'Identifier') && names.has(node.name || ''))
1290
+ found = true;
1291
+ });
1292
+ return found;
1293
+ }
1294
+ isSelfBalanceDecrease(expr) {
1295
+ if (!(0, ast_1.isNode)(expr, 'BinaryOperation'))
1296
+ return false;
1297
+ const op = String(expr.operator || '');
1298
+ const left = expr.left || expr.leftExpression;
1299
+ if (!this.isSelfBalanceRef(left))
1300
+ return false;
1301
+ if (op === '-=')
1302
+ return true;
1303
+ if (op !== '=')
1304
+ return false;
1305
+ const right = expr.right || expr.rightExpression;
1306
+ return this.exprContainsSelfBalanceRef(right) && this.exprContainsOperator(right, '-');
1307
+ }
1308
+ exprContainsSelfBalanceRef(expr) {
1309
+ let found = false;
1310
+ this.walk(expr, node => {
1311
+ if (found)
1312
+ return;
1313
+ if (this.isSelfBalanceRef(node))
1314
+ found = true;
1315
+ });
1316
+ return found;
1317
+ }
1318
+ exprContainsOperator(expr, operator) {
1319
+ let found = false;
1320
+ this.walk(expr, node => {
1321
+ if (found)
1322
+ return;
1323
+ if ((0, ast_1.isNode)(node, 'BinaryOperation') && node.operator === operator)
1324
+ found = true;
1325
+ });
1326
+ return found;
1327
+ }
1328
+ isSelfBalanceRef(node) {
1329
+ if (!(0, ast_1.isNode)(node, 'IndexAccess'))
1330
+ return false;
1331
+ const baseName = this.simpleName(node.base || node.baseExpression);
1332
+ if (baseName !== 'balanceOf' && baseName !== 'balances')
1333
+ return false;
1334
+ const index = node.index || node.indexExpression;
1335
+ return this.isAddressThis(index);
1336
+ }
1337
+ isAddressThisBalance(node) {
1338
+ return (0, ast_1.isNode)(node, 'MemberAccess') && node.memberName === 'balance' && this.isAddressThis(node.expression);
1339
+ }
1340
+ isAddressThis(node) {
1341
+ if (!(0, ast_1.isNode)(node, 'FunctionCall'))
1342
+ return false;
1343
+ const callee = node.expression;
1344
+ return (0, ast_1.isNode)(callee, 'Identifier') && callee.name === 'address' &&
1345
+ node.arguments?.length === 1 && (0, ast_1.isNode)(node.arguments[0], 'Identifier') && node.arguments[0].name === 'this';
1346
+ }
1347
+ isGetReservesCall(node) {
1348
+ if (!(0, ast_1.isNode)(node, 'FunctionCall'))
1349
+ return false;
1350
+ const callee = node.expression;
1351
+ return (0, ast_1.isNode)(callee, 'MemberAccess') && String(callee.memberName || '').toLowerCase() === 'getreserves';
1352
+ }
1353
+ isGetAmountsOutCall(node) {
1354
+ if (!(0, ast_1.isNode)(node, 'FunctionCall'))
1355
+ return false;
1356
+ const callee = node.expression;
1357
+ return (0, ast_1.isNode)(callee, 'MemberAccess') && String(callee.memberName || '').toLowerCase() === 'getamountsout';
1358
+ }
1359
+ isTotalSupplyRead(node) {
1360
+ if ((0, ast_1.isNode)(node, 'Identifier'))
1361
+ return String(node.name || '').toLowerCase() === 'totalsupply';
1362
+ if (!(0, ast_1.isNode)(node, 'FunctionCall'))
1363
+ return false;
1364
+ const callee = node.expression;
1365
+ if ((0, ast_1.isNode)(callee, 'Identifier'))
1366
+ return String(callee.name || '').toLowerCase() === 'totalsupply';
1367
+ return (0, ast_1.isNode)(callee, 'MemberAccess') && String(callee.memberName || '').toLowerCase() === 'totalsupply';
1368
+ }
1369
+ memberAccessRoot(node) {
1370
+ if (!node)
1371
+ return '';
1372
+ if ((0, ast_1.isNode)(node, 'Identifier'))
1373
+ return node.name || '';
1374
+ if ((0, ast_1.isNode)(node, 'MemberAccess'))
1375
+ return this.memberAccessRoot(node.expression);
1376
+ return '';
1377
+ }
1378
+ simpleName(node) {
1379
+ if (!node)
1380
+ return '';
1381
+ if ((0, ast_1.isNode)(node, 'Identifier'))
1382
+ return node.name || '';
1383
+ if ((0, ast_1.isNode)(node, 'MemberAccess')) {
1384
+ // For msg.sender style: return the memberName (`sender`) so the
1385
+ // index expression is human-readable in the finding message.
1386
+ return node.memberName || '';
1387
+ }
1388
+ return '';
1389
+ }
1390
+ walk(node, visit) {
1391
+ if (!node || typeof node !== 'object')
1392
+ return;
1393
+ visit(node);
1394
+ for (const child of this.children(node)) {
1395
+ this.walk(child, visit);
1396
+ }
1397
+ }
1398
+ children(node) {
1399
+ const out = [];
1400
+ for (const [key, value] of Object.entries(node)) {
1401
+ if (key === 'loc' || key === 'src' || key === 'range' || key === 'typeDescriptions')
1402
+ continue;
1403
+ if (Array.isArray(value)) {
1404
+ for (const item of value) {
1405
+ if (item && typeof item === 'object')
1406
+ out.push(item);
1407
+ }
1408
+ }
1409
+ else if (value && typeof value === 'object') {
1410
+ out.push(value);
1411
+ }
1412
+ }
1413
+ return out;
1414
+ }
1415
+ hasReentrancyGuardBody(body) {
1416
+ if (!body || body.type !== 'Block')
1417
+ return false;
1418
+ const stmts = body.statements || [];
1419
+ const guardNames = new Set();
1420
+ for (const stmt of stmts) {
1421
+ if (!(0, ast_1.isNode)(stmt, 'ExpressionStatement'))
1422
+ continue;
1423
+ const expr = stmt.expression;
1424
+ if (!(0, ast_1.isNode)(expr, 'FunctionCall'))
1425
+ continue;
1426
+ const callee = expr.expression;
1427
+ if (!(0, ast_1.isNode)(callee, 'Identifier'))
1428
+ continue;
1429
+ if ((callee.name || '') !== 'require')
1430
+ continue;
1431
+ const arg0 = expr.arguments?.[0];
1432
+ if (!(0, ast_1.isNode)(arg0, 'UnaryOperation') || arg0.operator !== '!')
1433
+ continue;
1434
+ const g = this.simpleName(arg0.subExpression);
1435
+ if (g)
1436
+ guardNames.add(g);
1437
+ }
1438
+ if (guardNames.size === 0)
1439
+ return false;
1440
+ for (const guardName of guardNames) {
1441
+ let setTrue = false;
1442
+ let setFalse = false;
1443
+ for (const s of stmts) {
1444
+ if (!(0, ast_1.isNode)(s, 'ExpressionStatement'))
1445
+ continue;
1446
+ const e = s.expression;
1447
+ if (!(0, ast_1.isNode)(e, 'BinaryOperation') || e.operator !== '=')
1448
+ continue;
1449
+ const lhs = e.left || e.leftHandSide;
1450
+ if (this.simpleName(lhs) !== guardName)
1451
+ continue;
1452
+ const lit = e.right || e.rightHandSide;
1453
+ if ((0, ast_1.isNode)(lit, 'BooleanLiteral')) {
1454
+ if (lit.value === true)
1455
+ setTrue = true;
1456
+ else if (lit.value === false)
1457
+ setFalse = true;
1458
+ }
1459
+ }
1460
+ if (setTrue && setFalse)
1461
+ return true;
1462
+ }
1463
+ return false;
1464
+ }
1465
+ hasRecognisedReentrancyGuardModifier(fn) {
1466
+ for (const modifier of fn?.modifiers || []) {
1467
+ const name = this.modifierName(modifier).toLowerCase();
1468
+ if (name && REENTRANCY_GUARD_MODIFIERS.has(name))
1469
+ return true;
1470
+ if (name.includes('nonreentrant'))
1471
+ return true;
1472
+ }
1473
+ return false;
1474
+ }
1475
+ modifierName(modifier) {
1476
+ if (!modifier)
1477
+ return '';
1478
+ if (typeof modifier === 'string')
1479
+ return modifier;
1480
+ if (typeof modifier.name === 'string')
1481
+ return modifier.name;
1482
+ if (modifier.name && typeof modifier.name.name === 'string')
1483
+ return modifier.name.name;
1484
+ if (modifier.modifierName && typeof modifier.modifierName.name === 'string')
1485
+ return modifier.modifierName.name;
1486
+ return '';
1487
+ }
1488
+ hasRecognisedFreshnessModifier(fn) {
1489
+ for (const modifier of fn?.modifiers || []) {
1490
+ const name = this.modifierName(modifier);
1491
+ if (name && (0, price_rate_1.isRecognizedFreshnessModifierName)(name))
1492
+ return true;
1493
+ }
1494
+ return false;
1495
+ }
1496
+ hasInlineAccessControl(body) {
1497
+ if (!body)
1498
+ return false;
1499
+ let found = false;
1500
+ this.walk(body, node => {
1501
+ if (found)
1502
+ return;
1503
+ if (!(0, ast_1.isNode)(node, 'FunctionCall'))
1504
+ return;
1505
+ const callee = node.expression;
1506
+ if (!(0, ast_1.isNode)(callee, 'Identifier'))
1507
+ return;
1508
+ if ((callee.name || '') !== 'require')
1509
+ return;
1510
+ const arg0 = node.arguments?.[0];
1511
+ if (!arg0)
1512
+ return;
1513
+ if ((0, access_control_1.requireExpressesAccessControl)(arg0, name => {
1514
+ const lower = name.toLowerCase();
1515
+ return lower.includes('owner') || lower.includes('admin') || lower.includes('governance') ||
1516
+ lower.includes('role') || lower.includes('auth') || lower.includes('manager') ||
1517
+ lower.includes('guardian') || lower.includes('operator');
1518
+ })) {
1519
+ found = true;
1520
+ }
1521
+ });
1522
+ return found;
1523
+ }
1524
+ findCsOutdatedGlobalVariable(fn) {
1525
+ const body = fn?.body;
1526
+ if (!body)
1527
+ return null;
1528
+ if (this.hasInlineAccessControl(body))
1529
+ return null;
1530
+ if (this.hasReentrancyGuardBody(body))
1531
+ return null;
1532
+ const params = this.collectFunctionParameters(fn);
1533
+ const caches = new Map();
1534
+ let sawInteraction = false;
1535
+ for (const stmt of body.statements || []) {
1536
+ const finding = this.applyCsOutdatedGlobalStatement(stmt, caches, sawInteraction, params);
1537
+ if (finding)
1538
+ return finding;
1539
+ if (this.csStatementHasExternalInteraction(stmt))
1540
+ sawInteraction = true;
1541
+ }
1542
+ return null;
1543
+ }
1544
+ applyCsOutdatedGlobalStatement(stmt, caches, sawInteraction, params) {
1545
+ if (!stmt)
1546
+ return null;
1547
+ if ((0, ast_1.isNode)(stmt, 'Block') || (0, ast_1.isNode)(stmt, 'UncheckedStatement')) {
1548
+ for (const child of stmt.statements || []) {
1549
+ const finding = this.applyCsOutdatedGlobalStatement(child, caches, sawInteraction, params);
1550
+ if (finding)
1551
+ return finding;
1552
+ if (this.csStatementHasExternalInteraction(child))
1553
+ sawInteraction = true;
1554
+ }
1555
+ return null;
1556
+ }
1557
+ if ((0, ast_1.isNode)(stmt, 'IfStatement')) {
1558
+ const finding = this.applyCsOutdatedGlobalStatement(stmt.trueBody, new Map(caches), sawInteraction, params) ||
1559
+ this.applyCsOutdatedGlobalStatement(stmt.falseBody, new Map(caches), sawInteraction, params);
1560
+ if (finding)
1561
+ return finding;
1562
+ return null;
1563
+ }
1564
+ if (!sawInteraction) {
1565
+ this.collectCsOutdatedGlobalCaches(stmt, caches);
1566
+ return null;
1567
+ }
1568
+ this.refreshCsOutdatedGlobalCaches(stmt, caches);
1569
+ for (const [localName, cache] of caches.entries()) {
1570
+ if (!cache.stale)
1571
+ continue;
1572
+ if (this.csStatementUsesStaleCacheInSink(stmt, localName, params)) {
1573
+ return { sourceName: cache.sourceName, sourceNode: cache.sourceNode, sinkNode: stmt };
1574
+ }
1575
+ }
1576
+ return null;
1577
+ }
1578
+ collectCsOutdatedGlobalCaches(stmt, caches) {
1579
+ const assignment = this.csAssignmentParts(stmt);
1580
+ if (!assignment?.init)
1581
+ return;
1582
+ const sourceName = this.csOutdatedGlobalSourceName(assignment.init);
1583
+ if (!sourceName)
1584
+ return;
1585
+ for (const variable of assignment.variables) {
1586
+ const localName = variable?.name;
1587
+ if (!localName)
1588
+ continue;
1589
+ caches.set(localName, {
1590
+ sourceName,
1591
+ sourceNode: assignment.init,
1592
+ stale: true,
1593
+ });
1594
+ }
1595
+ }
1596
+ refreshCsOutdatedGlobalCaches(stmt, caches) {
1597
+ const assignment = this.csAssignmentParts(stmt);
1598
+ if (!assignment?.init)
1599
+ return;
1600
+ const refreshedName = this.csOutdatedGlobalSourceName(assignment.init);
1601
+ if (!refreshedName)
1602
+ return;
1603
+ for (const variable of assignment.variables) {
1604
+ const cache = variable?.name ? caches.get(variable.name) : undefined;
1605
+ if (cache && cache.sourceName === refreshedName)
1606
+ cache.stale = false;
1607
+ }
1608
+ }
1609
+ csAssignmentParts(node) {
1610
+ if ((0, ast_1.isNode)(node, 'VariableDeclarationStatement')) {
1611
+ return { init: node.initialValue, variables: node.variables || [] };
1612
+ }
1613
+ if (!(0, ast_1.isNode)(node, 'ExpressionStatement'))
1614
+ return null;
1615
+ const expr = node.expression;
1616
+ if (!(0, ast_1.isNode)(expr, 'BinaryOperation') || String(expr.operator || '') !== '=')
1617
+ return null;
1618
+ const left = expr.left || expr.leftExpression;
1619
+ const right = expr.right || expr.rightExpression;
1620
+ if ((0, ast_1.isNode)(left, 'Identifier'))
1621
+ return { init: right, variables: [{ name: left.name }] };
1622
+ return null;
1623
+ }
1624
+ csOutdatedGlobalSourceName(expr) {
1625
+ const directName = this.csStateAccountingName(expr);
1626
+ if (directName)
1627
+ return directName;
1628
+ let found = '';
1629
+ this.walk(expr, node => {
1630
+ if (found)
1631
+ return;
1632
+ if (this.csIsLiveBalanceCall(node)) {
1633
+ found = 'balanceOf';
1634
+ return;
1635
+ }
1636
+ const name = this.csStateAccountingName(node);
1637
+ if (name)
1638
+ found = name;
1639
+ });
1640
+ return found;
1641
+ }
1642
+ csStateAccountingName(expr) {
1643
+ if ((0, ast_1.isNode)(expr, 'Identifier')) {
1644
+ const name = String(expr.name || '');
1645
+ if (this.stateVars.includes(name) && OUTDATED_GLOBAL_SOURCE_NAMES.test(name))
1646
+ return name;
1647
+ }
1648
+ if ((0, ast_1.isNode)(expr, 'IndexAccess')) {
1649
+ const baseName = this.simpleName(expr.base || expr.baseExpression);
1650
+ if (this.stateVars.includes(baseName) && OUTDATED_GLOBAL_SOURCE_NAMES.test(baseName))
1651
+ return baseName;
1652
+ }
1653
+ if ((0, ast_1.isNode)(expr, 'MemberAccess')) {
1654
+ const member = String(expr.memberName || '');
1655
+ if (OUTDATED_GLOBAL_SOURCE_NAMES.test(member) && this.csExpressionIsStateScoped(expr.expression))
1656
+ return member;
1657
+ }
1658
+ return '';
1659
+ }
1660
+ csExpressionIsStateScoped(expr) {
1661
+ const rootName = this.csStateRootName(expr);
1662
+ return !!rootName && this.stateVars.includes(rootName);
1663
+ }
1664
+ csStateRootName(expr) {
1665
+ if (!expr)
1666
+ return '';
1667
+ if ((0, ast_1.isNode)(expr, 'Identifier'))
1668
+ return String(expr.name || '');
1669
+ if ((0, ast_1.isNode)(expr, 'MemberAccess'))
1670
+ return this.csStateRootName(expr.expression);
1671
+ if ((0, ast_1.isNode)(expr, 'IndexAccess'))
1672
+ return this.csStateRootName(expr.base || expr.baseExpression);
1673
+ return '';
1674
+ }
1675
+ csIsLiveBalanceCall(node) {
1676
+ if (!(0, ast_1.isNode)(node, 'FunctionCall'))
1677
+ return false;
1678
+ const callee = node.expression;
1679
+ if (!(0, ast_1.isNode)(callee, 'MemberAccess'))
1680
+ return false;
1681
+ return String(callee.memberName || '').toLowerCase() === 'balanceof';
1682
+ }
1683
+ csStatementHasExternalInteraction(stmt) {
1684
+ let found = false;
1685
+ this.walk(stmt, node => {
1686
+ if (found || !(0, ast_1.isNode)(node, 'FunctionCall'))
1687
+ return;
1688
+ if (this.csIsRiskyExternalInteraction(node))
1689
+ found = true;
1690
+ });
1691
+ return found;
1692
+ }
1693
+ csIsRiskyExternalInteraction(call) {
1694
+ const callee = this.unwrapCallOptions(call.expression);
1695
+ if (!(0, ast_1.isNode)(callee, 'MemberAccess'))
1696
+ return false;
1697
+ const method = String(callee.memberName || '').toLowerCase();
1698
+ if (!['call', 'delegatecall', 'transfer', 'send', 'transferfrom', 'safetransfer', 'safetransferfrom'].includes(method)) {
1699
+ return false;
1700
+ }
1701
+ const root = this.memberAccessRoot(callee.expression).toLowerCase();
1702
+ return root !== 'this' && root !== 'super';
1703
+ }
1704
+ csStatementUsesStaleCacheInSink(stmt, localName, params) {
1705
+ let found = false;
1706
+ this.walk(stmt, node => {
1707
+ if (found)
1708
+ return;
1709
+ if ((0, ast_1.isNode)(node, 'BinaryOperation')) {
1710
+ const op = String(node.operator || '');
1711
+ const left = node.left || node.leftExpression;
1712
+ const right = node.right || node.rightExpression;
1713
+ if (/^[+\-*/]?=$/.test(op) &&
1714
+ this.csIsAccountingSink(left) &&
1715
+ this.exprUsesNames(right, new Set([localName]))) {
1716
+ found = true;
1717
+ return;
1718
+ }
1719
+ }
1720
+ if ((0, ast_1.isNode)(node, 'FunctionCall') && this.csIsValueMovingSink(node) && this.exprUsesNames(node, new Set([localName]))) {
1721
+ found = true;
1722
+ }
1723
+ });
1724
+ if (found)
1725
+ return true;
1726
+ // Guard-context bound sink: stale local bounds a caller-supplied
1727
+ // parameter in a require/assert argument or if-statement condition.
1728
+ // Pure equality checks and invariant-only bounds (e.g. `cached > 0`)
1729
+ // are too generic to flag.
1730
+ return this.csCacheUsedInGuardBound(stmt, localName, params);
1731
+ }
1732
+ csCacheUsedInGuardBound(stmt, localName, params) {
1733
+ let found = false;
1734
+ this.walk(stmt, node => {
1735
+ if (found)
1736
+ return;
1737
+ let condition = null;
1738
+ if ((0, ast_1.isNode)(node, 'IfStatement')) {
1739
+ condition = node.condition;
1740
+ }
1741
+ else if ((0, ast_1.isNode)(node, 'FunctionCall')) {
1742
+ const callee = node.expression;
1743
+ if ((0, ast_1.isNode)(callee, 'Identifier') && ['require', 'assert'].includes(String(callee.name || ''))) {
1744
+ condition = node.arguments?.[0];
1745
+ }
1746
+ }
1747
+ if (!condition)
1748
+ return;
1749
+ if (this.csConditionHasBoundOnLocal(condition, localName, params))
1750
+ found = true;
1751
+ });
1752
+ return found;
1753
+ }
1754
+ csConditionHasBoundOnLocal(expr, localName, params) {
1755
+ let found = false;
1756
+ this.walk(expr, node => {
1757
+ if (found)
1758
+ return;
1759
+ if (!(0, ast_1.isNode)(node, 'BinaryOperation'))
1760
+ return;
1761
+ if (!/^(<=|<|>=|>)$/.test(String(node.operator || '')))
1762
+ return;
1763
+ if (!this.exprUsesNames(node, new Set([localName])))
1764
+ return;
1765
+ if (params.size === 0 || !this.exprUsesNames(node, params))
1766
+ return;
1767
+ found = true;
1768
+ });
1769
+ return found;
1770
+ }
1771
+ csIsAccountingSink(expr) {
1772
+ const name = this.csAccountingSinkName(expr);
1773
+ return !!name && OUTDATED_GLOBAL_SINK_NAMES.test(name);
1774
+ }
1775
+ csAccountingSinkName(expr) {
1776
+ if ((0, ast_1.isNode)(expr, 'Identifier'))
1777
+ return String(expr.name || '');
1778
+ if ((0, ast_1.isNode)(expr, 'IndexAccess'))
1779
+ return this.csAccountingSinkName(expr.base || expr.baseExpression);
1780
+ if ((0, ast_1.isNode)(expr, 'MemberAccess'))
1781
+ return String(expr.memberName || '') || this.csAccountingSinkName(expr.expression);
1782
+ return '';
1783
+ }
1784
+ csIsValueMovingSink(call) {
1785
+ const callee = this.unwrapCallOptions(call.expression);
1786
+ if (!(0, ast_1.isNode)(callee, 'MemberAccess'))
1787
+ return false;
1788
+ const method = String(callee.memberName || '').toLowerCase();
1789
+ return ['transfer', 'send', 'safetransfer', 'mint', '_mint', 'burn', '_burn'].includes(method);
1790
+ }
1791
+ findPenpieRewardReentrancy(fn) {
1792
+ const body = fn?.body;
1793
+ if (!body)
1794
+ return null;
1795
+ if (this.hasRecognisedReentrancyGuardModifier(fn))
1796
+ return null;
1797
+ if (this.hasInlineAccessControl(body))
1798
+ return null;
1799
+ if (this.hasReentrancyGuardBody(body))
1800
+ return null;
1801
+ let lastRiskyExternalCall = null;
1802
+ let finding = null;
1803
+ const visit = (node, expandedHelpers = new Set(), depth = 0) => {
1804
+ if (!node || typeof node !== 'object' || finding)
1805
+ return;
1806
+ if ((0, ast_1.isNode)(node, 'VariableDeclarationStatement')) {
1807
+ this.walkPenpieExecution(node.initialValue, child => visit(child, expandedHelpers, depth));
1808
+ return;
1809
+ }
1810
+ if ((0, ast_1.isNode)(node, 'IfStatement')) {
1811
+ this.walkPenpieExecution(node.condition, child => visit(child, expandedHelpers, depth));
1812
+ const saved = lastRiskyExternalCall;
1813
+ this.walkPenpieExecution(node.trueBody, child => visit(child, new Set(expandedHelpers), depth));
1814
+ const afterTrue = lastRiskyExternalCall;
1815
+ lastRiskyExternalCall = saved;
1816
+ this.walkPenpieExecution(node.falseBody, child => visit(child, new Set(expandedHelpers), depth));
1817
+ lastRiskyExternalCall = afterTrue || lastRiskyExternalCall;
1818
+ return;
1819
+ }
1820
+ if ((0, ast_1.isNode)(node, 'BinaryOperation') && /^[+\-*/]?=$/.test(String(node.operator || ''))) {
1821
+ const right = node.right || node.rightExpression;
1822
+ this.walkPenpieExecution(right, child => visit(child, expandedHelpers, depth));
1823
+ const left = node.left || node.leftExpression;
1824
+ if (lastRiskyExternalCall && this.isPenpieAccountingWriteTarget(left)) {
1825
+ finding = { callNode: lastRiskyExternalCall, writeNode: node };
1826
+ return;
1827
+ }
1828
+ this.walkPenpieExecution(left, child => visit(child, expandedHelpers, depth));
1829
+ return;
1830
+ }
1831
+ if ((0, ast_1.isNode)(node, 'FunctionCall')) {
1832
+ if (this.isPenpieExternalInteraction(node) && this.isPenpieRiskyExternalAccountingPair(node)) {
1833
+ lastRiskyExternalCall = node;
1834
+ }
1835
+ this.expandPenpieHelperCall(node, expandedHelpers, depth, visit);
1836
+ if (finding)
1837
+ return;
1838
+ }
1839
+ for (const child of this.penpieChildren(node)) {
1840
+ visit(child, expandedHelpers, depth);
1841
+ if (finding)
1842
+ return;
1843
+ }
1844
+ };
1845
+ visit(body);
1846
+ return finding;
1847
+ }
1848
+ expandPenpieHelperCall(call, expandedHelpers, depth, visit) {
1849
+ if (depth >= 2)
1850
+ return;
1851
+ const internalName = this.internalCallName(call);
1852
+ if (!internalName || expandedHelpers.has(internalName))
1853
+ return;
1854
+ const helper = this.contractFunctions.get(internalName);
1855
+ if (!helper?.body || !INTERNAL_VISIBILITIES.has(String(helper.visibility || '').toLowerCase()))
1856
+ return;
1857
+ if (this.hasInlineAccessControl(helper.body))
1858
+ return;
1859
+ if (this.hasReentrancyGuardBody(helper.body))
1860
+ return;
1861
+ const helperExpanded = new Set(expandedHelpers);
1862
+ helperExpanded.add(internalName);
1863
+ for (const stmt of helper.body.statements || []) {
1864
+ visit(stmt, helperExpanded, depth + 1);
1865
+ }
1866
+ }
1867
+ walkPenpieExecution(node, visit) {
1868
+ if (!node || typeof node !== 'object')
1869
+ return;
1870
+ visit(node);
1871
+ }
1872
+ penpieChildren(node) {
1873
+ if (!node || typeof node !== 'object')
1874
+ return [];
1875
+ const out = [];
1876
+ for (const [key, value] of Object.entries(node)) {
1877
+ if (key === 'loc' || key === 'src' || key === 'range' || key === 'typeDescriptions')
1878
+ continue;
1879
+ if ((0, ast_1.isNode)(node, 'BinaryOperation') && /^[+\-*/]?=$/.test(String(node.operator || '')) && (key === 'left' || key === 'leftExpression' || key === 'right' || key === 'rightExpression')) {
1880
+ continue;
1881
+ }
1882
+ if ((0, ast_1.isNode)(node, 'VariableDeclarationStatement') && key === 'initialValue')
1883
+ continue;
1884
+ if (Array.isArray(value)) {
1885
+ for (const item of value) {
1886
+ if (item && typeof item === 'object')
1887
+ out.push(item);
1888
+ }
1889
+ }
1890
+ else if (value && typeof value === 'object') {
1891
+ out.push(value);
1892
+ }
1893
+ }
1894
+ return out;
1895
+ }
1896
+ isPenpieExternalInteraction(call) {
1897
+ const callee = this.unwrapCallOptions(call.expression);
1898
+ if (!(0, ast_1.isNode)(callee, 'MemberAccess'))
1899
+ return false;
1900
+ const method = String(callee.memberName || '').toLowerCase();
1901
+ if (!method)
1902
+ return false;
1903
+ if (PENPIE_IGNORED_EXTERNAL_METHODS.has(method))
1904
+ return false;
1905
+ if (PENPIE_LIBRARY_STYLE_METHODS.test(String(callee.memberName || '')))
1906
+ return false;
1907
+ if (['call', 'delegatecall', 'staticcall'].includes(method))
1908
+ return true;
1909
+ const root = this.memberAccessRoot(callee.expression).toLowerCase();
1910
+ if (root === 'this' || root === 'super' || root === 'abi')
1911
+ return false;
1912
+ return true;
1913
+ }
1914
+ unwrapCallOptions(expr) {
1915
+ if (!expr || typeof expr !== 'object')
1916
+ return expr;
1917
+ if ((0, ast_1.isNode)(expr, 'NameValueExpression'))
1918
+ return this.unwrapCallOptions(expr.expression);
1919
+ if ((0, ast_1.isNode)(expr, 'FunctionCallOptions'))
1920
+ return this.unwrapCallOptions(expr.expression);
1921
+ return expr;
1922
+ }
1923
+ isPenpieAccountingWriteTarget(expr) {
1924
+ const name = this.penpieWriteTargetName(expr);
1925
+ return !!name && PENPIE_ACCOUNTING_NAMES.test(name);
1926
+ }
1927
+ isPenpieRiskyExternalAccountingPair(call) {
1928
+ // The interaction itself must be callback-capable: a low-level
1929
+ // call/delegatecall, or a harvest/claim/reward/callback-style method.
1930
+ // A reward-named write target alone is NOT sufficient — an external
1931
+ // *view* getter (price feed read, pendingReward preview) followed by a
1932
+ // reward write is not a reentrancy vector. Accepting it was the
1933
+ // dominant false-positive shape in benchmark runs.
1934
+ const callee = this.unwrapCallOptions(call.expression);
1935
+ if (!(0, ast_1.isNode)(callee, 'MemberAccess'))
1936
+ return false;
1937
+ const method = String(callee.memberName || '').toLowerCase();
1938
+ if (['call', 'delegatecall'].includes(method))
1939
+ return true;
1940
+ if (/preview|pending|calc|view|read/.test(method))
1941
+ return false;
1942
+ return /harvest|reward|claim|callback/.test(method);
1943
+ }
1944
+ penpieWriteTargetName(expr) {
1945
+ if (!expr || typeof expr !== 'object')
1946
+ return '';
1947
+ if ((0, ast_1.isNode)(expr, 'Identifier'))
1948
+ return String(expr.name || '');
1949
+ if ((0, ast_1.isNode)(expr, 'IndexAccess'))
1950
+ return this.penpieWriteTargetName(expr.base || expr.baseExpression);
1951
+ if ((0, ast_1.isNode)(expr, 'MemberAccess'))
1952
+ return String(expr.memberName || '') || this.penpieWriteTargetName(expr.expression);
1953
+ return '';
1954
+ }
1955
+ findZksyncDonationAttack(fn) {
1956
+ if (!fn?.body)
1957
+ return null;
1958
+ const helperNames = this.collectZksyncLiveBalanceHelpers();
1959
+ const thisAliases = this.collectZksyncAddressThisAliases(fn.body);
1960
+ const balanceAliases = this.collectZksyncLiveBalanceAliases(fn.body, thisAliases, helperNames);
1961
+ const ratio = this.findZksyncDonationRatio(fn.body, thisAliases, balanceAliases, helperNames);
1962
+ if (!ratio)
1963
+ return null;
1964
+ // Suppression is per-ratio: only an additive virtual-offset directly on the
1965
+ // numerator/denominator of THIS conversion hardens it. Constructor
1966
+ // dead-share bookkeeping (e.g. `shares[address(0)] = X`) does not, because
1967
+ // donation can still inflate `balanceOf(address(this))` unless the
1968
+ // denominator itself has an additive offset.
1969
+ if (this.zksyncHasVirtualOffset(ratio, thisAliases, balanceAliases, helperNames))
1970
+ return null;
1971
+ const loc = (0, ast_1.tryLoc)(ratio) ?? (0, ast_1.tryLoc)(fn) ?? { line: 1, endLine: 1, column: 0 };
1972
+ return {
1973
+ loc: {
1974
+ line: loc.line || 1,
1975
+ endLine: loc.endLine || loc.line || 1,
1976
+ column: loc.column || 0,
1977
+ },
1978
+ };
1979
+ }
1980
+ collectZksyncLiveBalanceHelpers() {
1981
+ const helpers = new Set();
1982
+ let changed = true;
1983
+ let safety = 0;
1984
+ while (changed && safety < 8) {
1985
+ changed = false;
1986
+ safety += 1;
1987
+ for (const [name, fn] of this.contractFunctions.entries()) {
1988
+ if (helpers.has(name) || !fn?.body)
1989
+ continue;
1990
+ if (!this.zksyncFunctionReturnsLiveBalance(fn, helpers))
1991
+ continue;
1992
+ helpers.add(name);
1993
+ changed = true;
1994
+ }
1995
+ }
1996
+ return helpers;
1997
+ }
1998
+ zksyncFunctionReturnsLiveBalance(fn, helperNames) {
1999
+ const thisAliases = this.collectZksyncAddressThisAliases(fn.body);
2000
+ const balanceAliases = this.collectZksyncLiveBalanceAliases(fn.body, thisAliases, helperNames);
2001
+ let returnsLiveBalance = false;
2002
+ this.walk(fn.body, node => {
2003
+ if (returnsLiveBalance || !(0, ast_1.isNode)(node, 'ReturnStatement'))
2004
+ return;
2005
+ if (this.zksyncIsLiveBalanceExpr(node.expression, thisAliases, balanceAliases, helperNames)) {
2006
+ returnsLiveBalance = true;
2007
+ }
2008
+ });
2009
+ return returnsLiveBalance;
2010
+ }
2011
+ collectZksyncAddressThisAliases(body) {
2012
+ const aliases = new Set();
2013
+ let changed = true;
2014
+ let safety = 0;
2015
+ while (changed && safety < 8) {
2016
+ changed = false;
2017
+ safety += 1;
2018
+ this.walk(body, node => {
2019
+ const assignment = this.zksyncAssignmentParts(node);
2020
+ if (!assignment?.init)
2021
+ return;
2022
+ if (!this.zksyncIsAddressThisExpr(assignment.init, aliases))
2023
+ return;
2024
+ for (const variable of assignment.variables) {
2025
+ if (!variable?.name || aliases.has(variable.name))
2026
+ continue;
2027
+ aliases.add(variable.name);
2028
+ changed = true;
2029
+ }
2030
+ });
2031
+ }
2032
+ return aliases;
2033
+ }
2034
+ collectZksyncLiveBalanceAliases(body, thisAliases, helperNames) {
2035
+ const aliases = new Set();
2036
+ let changed = true;
2037
+ let safety = 0;
2038
+ while (changed && safety < 8) {
2039
+ changed = false;
2040
+ safety += 1;
2041
+ this.walk(body, node => {
2042
+ const assignment = this.zksyncAssignmentParts(node);
2043
+ if (!assignment?.init)
2044
+ return;
2045
+ if (!this.zksyncIsLiveBalanceExpr(assignment.init, thisAliases, aliases, helperNames))
2046
+ return;
2047
+ for (const variable of assignment.variables) {
2048
+ if (!variable?.name || aliases.has(variable.name))
2049
+ continue;
2050
+ aliases.add(variable.name);
2051
+ changed = true;
2052
+ }
2053
+ });
2054
+ }
2055
+ return aliases;
2056
+ }
2057
+ findZksyncDonationRatio(body, thisAliases, balanceAliases, helperNames) {
2058
+ let found = null;
2059
+ this.walk(body, node => {
2060
+ if (found)
2061
+ return;
2062
+ if (this.zksyncIsUnsafeMulDiv(node, thisAliases, balanceAliases, helperNames)) {
2063
+ found = node;
2064
+ return;
2065
+ }
2066
+ if (!(0, ast_1.isNode)(node, 'BinaryOperation'))
2067
+ return;
2068
+ if (String(node.operator || '') !== '/')
2069
+ return;
2070
+ if (!this.zksyncIsLiveBalanceExpr(node.right, thisAliases, balanceAliases, helperNames))
2071
+ return;
2072
+ if (!this.zksyncContainsSupply(node.left))
2073
+ return;
2074
+ if (!this.zksyncContainsAssetInput(node.left))
2075
+ return;
2076
+ found = node;
2077
+ });
2078
+ return found;
2079
+ }
2080
+ zksyncIsUnsafeMulDiv(node, thisAliases, balanceAliases, helperNames) {
2081
+ if (!(0, ast_1.isNode)(node, 'FunctionCall'))
2082
+ return false;
2083
+ const callee = node.expression;
2084
+ const method = (0, ast_1.isNode)(callee, 'MemberAccess') ? String(callee.memberName || '') : (0, ast_1.isNode)(callee, 'Identifier') ? String(callee.name || '') : '';
2085
+ if (method.toLowerCase() !== 'muldiv')
2086
+ return false;
2087
+ const args = node.arguments || [];
2088
+ if (args.length < 3)
2089
+ return false;
2090
+ return this.zksyncContainsAssetInput(args[0]) &&
2091
+ this.zksyncContainsSupply(args[1]) &&
2092
+ this.zksyncIsLiveBalanceExpr(args[2], thisAliases, balanceAliases, helperNames);
2093
+ }
2094
+ zksyncIsLiveBalanceExpr(expr, thisAliases, balanceAliases, helperNames) {
2095
+ const node = this.zksyncUnwrap(expr);
2096
+ if (!node)
2097
+ return false;
2098
+ if ((0, ast_1.isNode)(node, 'Identifier'))
2099
+ return balanceAliases.has(String(node.name || ''));
2100
+ if (this.zksyncIsBalanceOfThisCall(node, thisAliases))
2101
+ return true;
2102
+ if ((0, ast_1.isNode)(node, 'FunctionCall')) {
2103
+ const callee = node.expression;
2104
+ const name = (0, ast_1.isNode)(callee, 'Identifier') ? String(callee.name || '') : '';
2105
+ if (helperNames.has(name))
2106
+ return true;
2107
+ }
2108
+ if ((0, ast_1.isNode)(node, 'BinaryOperation') && ['+', '-'].includes(String(node.operator || ''))) {
2109
+ return this.zksyncIsLiveBalanceExpr(node.left, thisAliases, balanceAliases, helperNames) ||
2110
+ this.zksyncIsLiveBalanceExpr(node.right, thisAliases, balanceAliases, helperNames);
2111
+ }
2112
+ return false;
2113
+ }
2114
+ zksyncIsBalanceOfThisCall(node, thisAliases) {
2115
+ if (!(0, ast_1.isNode)(node, 'FunctionCall'))
2116
+ return false;
2117
+ const callee = node.expression;
2118
+ if (!(0, ast_1.isNode)(callee, 'MemberAccess'))
2119
+ return false;
2120
+ if (String(callee.memberName || '').toLowerCase() !== 'balanceof')
2121
+ return false;
2122
+ const arg = node.arguments?.[0];
2123
+ return this.zksyncIsAddressThisExpr(arg, thisAliases);
2124
+ }
2125
+ zksyncIsAddressThisExpr(expr, aliases) {
2126
+ const node = this.zksyncUnwrap(expr);
2127
+ if ((0, ast_1.isNode)(node, 'Identifier'))
2128
+ return node.name === 'this' || aliases.has(String(node.name || ''));
2129
+ if (!(0, ast_1.isNode)(node, 'FunctionCall'))
2130
+ return false;
2131
+ const callee = node.expression;
2132
+ if (!(0, ast_1.isNode)(callee, 'Identifier') || String(callee.name || '') !== 'address')
2133
+ return false;
2134
+ const args = node.arguments || [];
2135
+ return args.length === 1 && (0, ast_1.isNode)(this.zksyncUnwrap(args[0]), 'Identifier') && this.zksyncUnwrap(args[0]).name === 'this';
2136
+ }
2137
+ zksyncContainsSupply(expr) {
2138
+ let found = false;
2139
+ this.walk(expr, node => {
2140
+ if (found)
2141
+ return;
2142
+ if ((0, ast_1.isNode)(node, 'Identifier') && /^_?totalsupply$/i.test(String(node.name || '')))
2143
+ found = true;
2144
+ if ((0, ast_1.isNode)(node, 'FunctionCall')) {
2145
+ const callee = node.expression;
2146
+ const name = (0, ast_1.isNode)(callee, 'Identifier') ? String(callee.name || '') :
2147
+ (0, ast_1.isNode)(callee, 'MemberAccess') ? String(callee.memberName || '') : '';
2148
+ if (/^totalsupply$/i.test(name))
2149
+ found = true;
2150
+ }
2151
+ });
2152
+ return found;
2153
+ }
2154
+ zksyncContainsAssetInput(expr) {
2155
+ let found = false;
2156
+ this.walk(expr, node => {
2157
+ if (found || !(0, ast_1.isNode)(node, 'Identifier'))
2158
+ return;
2159
+ const name = String(node.name || '').toLowerCase();
2160
+ if (name === 'assets' || name === 'assetamount' || name === 'amount')
2161
+ found = true;
2162
+ });
2163
+ return found;
2164
+ }
2165
+ // Virtual-offset hardening is only recognised when the conversion ratio
2166
+ // itself carries an additive offset on the supply term in the numerator OR
2167
+ // on the live-balance term in the denominator. Exponentiation alone
2168
+ // (`10 ** decimals`) does NOT count — generic decimal scaling does not
2169
+ // mitigate donation inflation; only `+ <offset>` adjacent to the ratio's
2170
+ // own supply / live-balance terms does.
2171
+ zksyncHasVirtualOffset(ratio, thisAliases, balanceAliases, helperNames) {
2172
+ if ((0, ast_1.isNode)(ratio, 'BinaryOperation') && String(ratio.operator || '') === '/') {
2173
+ return this.zksyncExprHasAdditiveSupplyOffset(ratio.left) ||
2174
+ this.zksyncExprHasAdditiveLiveBalanceOffset(ratio.right, thisAliases, balanceAliases, helperNames);
2175
+ }
2176
+ if ((0, ast_1.isNode)(ratio, 'FunctionCall')) {
2177
+ const args = ratio.arguments || [];
2178
+ if (args.length >= 3) {
2179
+ return this.zksyncExprHasAdditiveSupplyOffset(args[1]) ||
2180
+ this.zksyncExprHasAdditiveLiveBalanceOffset(args[2], thisAliases, balanceAliases, helperNames);
2181
+ }
2182
+ }
2183
+ return false;
2184
+ }
2185
+ zksyncExprHasAdditiveSupplyOffset(expr) {
2186
+ let found = false;
2187
+ this.walk(expr, node => {
2188
+ if (found)
2189
+ return;
2190
+ if (!(0, ast_1.isNode)(node, 'BinaryOperation') || String(node.operator || '') !== '+')
2191
+ return;
2192
+ const left = this.zksyncUnwrap(node.left);
2193
+ const right = this.zksyncUnwrap(node.right);
2194
+ if (this.zksyncContainsSupply(left) && this.zksyncIsRecognizedOffset(right)) {
2195
+ found = true;
2196
+ return;
2197
+ }
2198
+ if (this.zksyncContainsSupply(right) && this.zksyncIsRecognizedOffset(left)) {
2199
+ found = true;
2200
+ }
2201
+ });
2202
+ return found;
2203
+ }
2204
+ zksyncExprHasAdditiveLiveBalanceOffset(expr, thisAliases, balanceAliases, helperNames) {
2205
+ let found = false;
2206
+ this.walk(expr, node => {
2207
+ if (found)
2208
+ return;
2209
+ if (!(0, ast_1.isNode)(node, 'BinaryOperation') || String(node.operator || '') !== '+')
2210
+ return;
2211
+ const left = this.zksyncUnwrap(node.left);
2212
+ const right = this.zksyncUnwrap(node.right);
2213
+ if (this.zksyncIsLiveBalanceExpr(left, thisAliases, balanceAliases, helperNames) &&
2214
+ this.zksyncIsRecognizedOffset(right)) {
2215
+ found = true;
2216
+ return;
2217
+ }
2218
+ if (this.zksyncIsLiveBalanceExpr(right, thisAliases, balanceAliases, helperNames) &&
2219
+ this.zksyncIsRecognizedOffset(left)) {
2220
+ found = true;
2221
+ }
2222
+ });
2223
+ return found;
2224
+ }
2225
+ // The kinds of offset values that count as virtual-share/virtual-asset
2226
+ // hardening when they appear additively next to the ratio's supply or
2227
+ // live-balance term: a non-zero numeric literal, an identifier whose name
2228
+ // signals virtual-share/offset intent, or `_decimalsOffset()` (including
2229
+ // the `10 ** _decimalsOffset()` form, where `**` is recognized *only*
2230
+ // because it expands a `_decimalsOffset()` call into its offset constant).
2231
+ zksyncIsRecognizedOffset(expr) {
2232
+ const node = this.zksyncUnwrap(expr);
2233
+ if (!node)
2234
+ return false;
2235
+ if ((0, ast_1.isNode)(node, 'NumberLiteral'))
2236
+ return String(node.number ?? node.value ?? '') !== '0';
2237
+ if ((0, ast_1.isNode)(node, 'DecimalNumber'))
2238
+ return String(node.value ?? '') !== '0';
2239
+ if ((0, ast_1.isNode)(node, 'HexNumber')) {
2240
+ const v = String(node.value ?? '').toLowerCase();
2241
+ return v !== '0' && v !== '0x0' && v !== '0x00';
2242
+ }
2243
+ if ((0, ast_1.isNode)(node, 'Identifier')) {
2244
+ const name = String(node.name || '').toLowerCase();
2245
+ return name.includes('virtual') || name.includes('offset');
2246
+ }
2247
+ if ((0, ast_1.isNode)(node, 'FunctionCall')) {
2248
+ const callee = node.expression;
2249
+ const name = (0, ast_1.isNode)(callee, 'Identifier') ? String(callee.name || '') :
2250
+ (0, ast_1.isNode)(callee, 'MemberAccess') ? String(callee.memberName || '') : '';
2251
+ return name.toLowerCase() === '_decimalsoffset';
2252
+ }
2253
+ if ((0, ast_1.isNode)(node, 'BinaryOperation') && String(node.operator || '') === '**') {
2254
+ // `**` only counts when it expands a `_decimalsOffset()`-linked term —
2255
+ // bare `10 ** decimals` is generic scaling, not virtual-offset hardening.
2256
+ return this.zksyncContainsDecimalsOffsetCall(node);
2257
+ }
2258
+ return false;
2259
+ }
2260
+ zksyncContainsDecimalsOffsetCall(expr) {
2261
+ let found = false;
2262
+ this.walk(expr, node => {
2263
+ if (found)
2264
+ return;
2265
+ if (!(0, ast_1.isNode)(node, 'FunctionCall'))
2266
+ return;
2267
+ const callee = node.expression;
2268
+ const name = (0, ast_1.isNode)(callee, 'Identifier') ? String(callee.name || '') :
2269
+ (0, ast_1.isNode)(callee, 'MemberAccess') ? String(callee.memberName || '') : '';
2270
+ if (name.toLowerCase() === '_decimalsoffset')
2271
+ found = true;
2272
+ });
2273
+ return found;
2274
+ }
2275
+ zksyncAssignmentParts(node) {
2276
+ if ((0, ast_1.isNode)(node, 'VariableDeclarationStatement')) {
2277
+ return { init: node.initialValue, variables: node.variables || [] };
2278
+ }
2279
+ if (!(0, ast_1.isNode)(node, 'ExpressionStatement'))
2280
+ return null;
2281
+ const expression = node.expression;
2282
+ if (!(0, ast_1.isNode)(expression, 'BinaryOperation') || String(expression.operator || '') !== '=')
2283
+ return null;
2284
+ const left = expression.left || expression.leftExpression;
2285
+ const right = expression.right || expression.rightExpression;
2286
+ if ((0, ast_1.isNode)(left, 'Identifier'))
2287
+ return { init: right, variables: [{ name: left.name }] };
2288
+ return null;
2289
+ }
2290
+ zksyncUnwrap(node) {
2291
+ let current = node;
2292
+ while ((0, ast_1.isNode)(current, 'TupleExpression') && current.components?.length === 1) {
2293
+ current = current.components[0];
2294
+ }
2295
+ return current;
2296
+ }
2297
+ findCellframeLiquidityMigration(fn, fnName) {
2298
+ const body = fn?.body;
2299
+ if (!body)
2300
+ return null;
2301
+ if (!/migrat|liquidity/i.test(fnName || ''))
2302
+ return null;
2303
+ const stateMutability = String(fn.stateMutability || '').toLowerCase();
2304
+ if (stateMutability === 'view' || stateMutability === 'pure')
2305
+ return null;
2306
+ if ((0, access_control_1.hasRecognisedAccessControlModifier)(fn))
2307
+ return null;
2308
+ if (this.hasRecognisedReentrancyGuardModifier(fn))
2309
+ return null;
2310
+ if (this.hasReentrancyGuardBody(body))
2311
+ return null;
2312
+ const paths = this.analyzeCellframeStatements(body.statements || [], [this.emptyCellframePathState()]);
2313
+ for (const path of paths) {
2314
+ if (!path.finding || path.hasGuardBeforeSink)
2315
+ continue;
2316
+ const source = path.finding.source || path.firstSource;
2317
+ if (!source)
2318
+ continue;
2319
+ return this.coinCutAnalysis(source, path.finding.sinkLabel);
2320
+ }
2321
+ return null;
2322
+ }
2323
+ emptyCellframePathState() {
2324
+ return {
2325
+ tainted: new Set(),
2326
+ sourceByName: new Map(),
2327
+ firstSource: null,
2328
+ hasGuardBeforeSink: false,
2329
+ finding: null,
2330
+ };
2331
+ }
2332
+ cloneCellframePathState(state) {
2333
+ return {
2334
+ tainted: new Set(state.tainted),
2335
+ sourceByName: new Map(state.sourceByName),
2336
+ firstSource: state.firstSource,
2337
+ hasGuardBeforeSink: state.hasGuardBeforeSink,
2338
+ finding: state.finding,
2339
+ };
2340
+ }
2341
+ analyzeCellframeStatements(statements, states) {
2342
+ let paths = states;
2343
+ for (const stmt of statements || []) {
2344
+ paths = this.applyCellframeStatement(stmt, paths);
2345
+ if (paths.length > MAX_INFLATE_PATHS)
2346
+ paths = paths.slice(0, MAX_INFLATE_PATHS);
2347
+ }
2348
+ return paths;
2349
+ }
2350
+ applyCellframeStatement(stmt, states) {
2351
+ if (!stmt)
2352
+ return states;
2353
+ if ((0, ast_1.isNode)(stmt, 'Block')) {
2354
+ return this.analyzeCellframeStatements(stmt.statements || [], states);
2355
+ }
2356
+ if ((0, ast_1.isNode)(stmt, 'UncheckedStatement')) {
2357
+ return this.applyCellframeStatement(stmt.block, states);
2358
+ }
2359
+ if ((0, ast_1.isNode)(stmt, 'IfStatement')) {
2360
+ const trueStmts = this.inflateBranchStatements(stmt.trueBody);
2361
+ const falseStmts = stmt.falseBody ? this.inflateBranchStatements(stmt.falseBody) : null;
2362
+ const out = [];
2363
+ for (const state of states) {
2364
+ out.push(...this.analyzeCellframeStatements(trueStmts, [this.cloneCellframePathState(state)]));
2365
+ if (falseStmts) {
2366
+ out.push(...this.analyzeCellframeStatements(falseStmts, [this.cloneCellframePathState(state)]));
2367
+ }
2368
+ else {
2369
+ out.push(this.cloneCellframePathState(state));
2370
+ }
2371
+ }
2372
+ return out;
2373
+ }
2374
+ if ((0, ast_1.isNode)(stmt, 'WhileStatement') || (0, ast_1.isNode)(stmt, 'ForStatement') || (0, ast_1.isNode)(stmt, 'DoWhileStatement')) {
2375
+ return this.analyzeCellframeStatements(this.inflateBranchStatements(stmt.body), states);
2376
+ }
2377
+ return states.map(state => this.applyCellframeLeaf(stmt, this.cloneCellframePathState(state)));
2378
+ }
2379
+ applyCellframeLeaf(stmt, state) {
2380
+ if (state.finding)
2381
+ return state;
2382
+ const rememberSource = (name, node, label) => {
2383
+ if (!name)
2384
+ return;
2385
+ state.tainted.add(name);
2386
+ if (!state.sourceByName.has(name))
2387
+ state.sourceByName.set(name, { node, label });
2388
+ if (!state.firstSource)
2389
+ state.firstSource = { node, label };
2390
+ };
2391
+ const assignment = this.coinCutAssignmentParts(stmt);
2392
+ if (assignment?.init) {
2393
+ const reserveSource = this.cellframeGetReservesSource(assignment.init);
2394
+ if (reserveSource) {
2395
+ for (const variable of assignment.variables) {
2396
+ if (variable?.name)
2397
+ rememberSource(variable.name, reserveSource.node, reserveSource.label);
2398
+ }
2399
+ }
2400
+ else {
2401
+ const source = this.cellframeSourceInExpression(assignment.init) ||
2402
+ this.coinCutTaintSource(assignment.init, state.tainted, state.sourceByName);
2403
+ if (source) {
2404
+ for (const variable of assignment.variables) {
2405
+ if (variable?.name)
2406
+ rememberSource(variable.name, source.node, source.label);
2407
+ }
2408
+ }
2409
+ }
2410
+ }
2411
+ if ((0, ast_1.isNode)(stmt, 'ExpressionStatement')) {
2412
+ const expr = stmt.expression;
2413
+ if (this.cellframeGuardCall(expr, state.tainted)) {
2414
+ state.hasGuardBeforeSink = true;
2415
+ }
2416
+ const sink = this.cellframeSink(expr, state.tainted, state.sourceByName);
2417
+ if (sink && !state.hasGuardBeforeSink) {
2418
+ state.finding = sink;
2419
+ }
2420
+ }
2421
+ return state;
2422
+ }
2423
+ cellframeGetReservesSource(node) {
2424
+ let found = null;
2425
+ this.walk(node, n => {
2426
+ if (found || !(0, ast_1.isNode)(n, 'FunctionCall'))
2427
+ return;
2428
+ const callee = n.expression;
2429
+ if ((0, ast_1.isNode)(callee, 'MemberAccess') && String(callee.memberName || '') === 'getReserves') {
2430
+ found = { node: n, label: 'getReserves()' };
2431
+ }
2432
+ });
2433
+ return found;
2434
+ }
2435
+ cellframeSourceInExpression(node) {
2436
+ let found = null;
2437
+ this.walk(node, n => {
2438
+ if (found || !(0, ast_1.isNode)(n, 'FunctionCall'))
2439
+ return;
2440
+ const callee = n.expression;
2441
+ if (!(0, ast_1.isNode)(callee, 'MemberAccess'))
2442
+ return;
2443
+ const member = String(callee.memberName || '');
2444
+ if (member === 'getReserves') {
2445
+ found = { node: n, label: 'getReserves()' };
2446
+ return;
2447
+ }
2448
+ if (member !== 'balanceOf')
2449
+ return;
2450
+ const arg = n.arguments?.[0];
2451
+ if (!arg)
2452
+ return;
2453
+ const target = this.flattenNames(arg, true).toLowerCase();
2454
+ if (target.includes('pair') || target.includes('pool')) {
2455
+ found = { node: n, label: 'balanceOf(pair)' };
2456
+ }
2457
+ });
2458
+ return found;
2459
+ }
2460
+ cellframeGuardCall(expr, tainted) {
2461
+ if (!(0, ast_1.isNode)(expr, 'FunctionCall'))
2462
+ return false;
2463
+ const callee = expr.expression;
2464
+ if (!(0, ast_1.isNode)(callee, 'Identifier'))
2465
+ return false;
2466
+ if (!['require', 'assert'].includes(String(callee.name || '')))
2467
+ return false;
2468
+ const condition = expr.arguments?.[0];
2469
+ if (!condition)
2470
+ return false;
2471
+ if ((0, access_control_1.requireExpressesAccessControl)(condition, name => {
2472
+ const lower = name.toLowerCase();
2473
+ return lower.includes('owner') || lower.includes('admin') || lower.includes('governance') ||
2474
+ lower.includes('role') || lower.includes('auth') || lower.includes('manager') ||
2475
+ lower.includes('guardian') || lower.includes('operator');
2476
+ })) {
2477
+ return true;
2478
+ }
2479
+ const scopedToLiveState = this.exprUsesNames(condition, tainted) || !!this.cellframeSourceInExpression(condition);
2480
+ if (!scopedToLiveState)
2481
+ return false;
2482
+ const text = this.flattenNames(condition, true);
2483
+ return /min|trusted|deviation|oracle|twap|reference|bound|snapshot|recorded|fixed/i.test(text);
2484
+ }
2485
+ cellframeSink(expr, tainted, sourceByName) {
2486
+ if (!(0, ast_1.isNode)(expr, 'FunctionCall'))
2487
+ return null;
2488
+ const callee = this.unwrapCallOptions(expr.expression);
2489
+ if (!(0, ast_1.isNode)(callee, 'MemberAccess'))
2490
+ return null;
2491
+ const method = String(callee.memberName || '').toLowerCase();
2492
+ if (COIN_CUT_VALUE_METHODS.has(method)) {
2493
+ for (const arg of expr.arguments || []) {
2494
+ const source = this.cellframeSourceInExpression(arg) || this.coinCutTaintSource(arg, tainted, sourceByName);
2495
+ if (source || this.exprUsesNames(arg, tainted)) {
2496
+ return { source, sinkLabel: 'value transfer amount', sinkNode: expr };
2497
+ }
2498
+ }
2499
+ return null;
2500
+ }
2501
+ if (method !== 'addliquidity')
2502
+ return null;
2503
+ const args = Array.isArray(expr.arguments) ? expr.arguments : [];
2504
+ const desiredArgs = [args[2], args[3]].filter(Boolean);
2505
+ if (desiredArgs.length === 0)
2506
+ return null;
2507
+ const hasTaintedDesired = desiredArgs.some(arg => !!this.cellframeSourceInExpression(arg) ||
2508
+ !!this.coinCutTaintSource(arg, tainted, sourceByName) ||
2509
+ this.exprUsesNames(arg, tainted));
2510
+ if (!hasTaintedDesired)
2511
+ return null;
2512
+ if (!this.isZeroLiteral(args[4]) || !this.isZeroLiteral(args[5]))
2513
+ return null;
2514
+ let source = null;
2515
+ for (const arg of desiredArgs) {
2516
+ source = this.cellframeSourceInExpression(arg) || this.coinCutTaintSource(arg, tainted, sourceByName);
2517
+ if (source)
2518
+ break;
2519
+ }
2520
+ return { source, sinkLabel: 'zero-slippage addLiquidity amounts', sinkNode: expr };
2521
+ }
2522
+ findCoinCutPriceManipulation(fn) {
2523
+ const body = fn?.body;
2524
+ if (!body)
2525
+ return null;
2526
+ const tainted = new Set();
2527
+ const sourceByName = new Map();
2528
+ let firstSource = null;
2529
+ const rememberSource = (name, node, label) => {
2530
+ if (!name)
2531
+ return;
2532
+ tainted.add(name);
2533
+ if (!sourceByName.has(name))
2534
+ sourceByName.set(name, { node, label });
2535
+ if (!firstSource)
2536
+ firstSource = { node, label };
2537
+ };
2538
+ this.walk(body, node => {
2539
+ const assignment = this.coinCutAssignmentParts(node);
2540
+ if (!assignment)
2541
+ return;
2542
+ const { init, variables } = assignment;
2543
+ if (!init)
2544
+ return;
2545
+ const source = this.coinCutSourceInExpression(init);
2546
+ if (source) {
2547
+ for (const v of variables) {
2548
+ if (v?.name)
2549
+ rememberSource(v.name, source.node, source.label);
2550
+ }
2551
+ return;
2552
+ }
2553
+ const taintSource = this.coinCutTaintSource(init, tainted, sourceByName);
2554
+ if (!taintSource)
2555
+ return;
2556
+ for (const v of variables) {
2557
+ if (v?.name)
2558
+ rememberSource(v.name, taintSource.node, taintSource.label);
2559
+ }
2560
+ });
2561
+ let changed = true;
2562
+ let safety = 0;
2563
+ while (changed && safety < 8) {
2564
+ changed = false;
2565
+ safety += 1;
2566
+ this.walk(body, node => {
2567
+ const assignment = this.coinCutAssignmentParts(node);
2568
+ if (!assignment)
2569
+ return;
2570
+ const { init, variables } = assignment;
2571
+ if (!init)
2572
+ return;
2573
+ const source = this.coinCutSourceInExpression(init) || this.coinCutTaintSource(init, tainted, sourceByName);
2574
+ if (!source)
2575
+ return;
2576
+ for (const v of variables) {
2577
+ if (!v?.name || tainted.has(v.name))
2578
+ continue;
2579
+ rememberSource(v.name, source.node, source.label);
2580
+ changed = true;
2581
+ }
2582
+ });
2583
+ }
2584
+ if (this.hasCoinCutReferenceGuard(body, tainted))
2585
+ return null;
2586
+ // Simple-sweep exclusion for `address(this)` balance reads: only treat
2587
+ // them as a live-price source if the function body actually does
2588
+ // price arithmetic with the read (split flows like
2589
+ // `bal = balanceOf(address(this)); amt = shares * bal;` qualify).
2590
+ const isSweepOnly = (source) => source.label === 'balanceOf(address(this))' && !this.hasArithmeticOperator(body);
2591
+ const transferSink = this.findCoinCutTransferSink(body, tainted, sourceByName);
2592
+ if (transferSink) {
2593
+ const source = transferSink.source || firstSource;
2594
+ if (!source)
2595
+ return null;
2596
+ if (isSweepOnly(source))
2597
+ return null;
2598
+ return this.coinCutAnalysis(source, 'value transfer amount');
2599
+ }
2600
+ const accountingSink = this.findCoinCutAccountingSink(body, tainted, sourceByName);
2601
+ if (accountingSink) {
2602
+ const source = accountingSink.source || firstSource;
2603
+ if (!source)
2604
+ return null;
2605
+ if (isSweepOnly(source))
2606
+ return null;
2607
+ return this.coinCutAnalysis(source, accountingSink.label);
2608
+ }
2609
+ return null;
2610
+ }
2611
+ findZenterestPriceOutOfDate(fn) {
2612
+ const body = fn?.body;
2613
+ if (!body)
2614
+ return null;
2615
+ if (this.hasRecognisedFreshnessModifier(fn))
2616
+ return null;
2617
+ const tainted = new Set();
2618
+ const freshnessNames = new Set(this.stateVars.filter(looksLikeFreshnessTrackingName));
2619
+ const sourceByName = new Map();
2620
+ let firstSource = null;
2621
+ const rememberSource = (name, node, label) => {
2622
+ if (!name)
2623
+ return;
2624
+ tainted.add(name);
2625
+ if (!sourceByName.has(name))
2626
+ sourceByName.set(name, { node, label });
2627
+ if (!firstSource)
2628
+ firstSource = { node, label };
2629
+ };
2630
+ const propagateAssignments = () => {
2631
+ let changed = false;
2632
+ this.walk(body, node => {
2633
+ const assignment = this.zenterestAssignmentParts(node);
2634
+ if (!assignment)
2635
+ return;
2636
+ const { init, variables } = assignment;
2637
+ if (!init)
2638
+ return;
2639
+ const latestRound = this.zenterestLatestRoundDataSource(init);
2640
+ if (latestRound) {
2641
+ const priceVar = variables[1];
2642
+ const freshnessVar = variables[3];
2643
+ if (priceVar?.name && !tainted.has(priceVar.name)) {
2644
+ rememberSource(priceVar.name, latestRound.node, latestRound.label);
2645
+ changed = true;
2646
+ }
2647
+ if (freshnessVar?.name && !freshnessNames.has(freshnessVar.name)) {
2648
+ freshnessNames.add(freshnessVar.name);
2649
+ changed = true;
2650
+ }
2651
+ return;
2652
+ }
2653
+ if (this.exprUsesNames(init, freshnessNames)) {
2654
+ for (const v of variables) {
2655
+ if (!v?.name || freshnessNames.has(v.name))
2656
+ continue;
2657
+ freshnessNames.add(v.name);
2658
+ changed = true;
2659
+ }
2660
+ }
2661
+ const source = this.zenterestSourceInExpression(init, tainted, sourceByName);
2662
+ if (!source)
2663
+ return;
2664
+ for (const v of variables) {
2665
+ if (!v?.name || tainted.has(v.name))
2666
+ continue;
2667
+ rememberSource(v.name, source.node, source.label);
2668
+ changed = true;
2669
+ }
2670
+ });
2671
+ return changed;
2672
+ };
2673
+ let safety = 0;
2674
+ while (propagateAssignments() && safety < 8)
2675
+ safety += 1;
2676
+ const transferSink = this.findZenterestTransferSink(body, tainted, sourceByName);
2677
+ if (transferSink) {
2678
+ if (this.hasZenterestFreshnessGuard(body, transferSink.sinkNode, freshnessNames))
2679
+ return null;
2680
+ const source = transferSink.source || firstSource;
2681
+ if (!source)
2682
+ return null;
2683
+ return this.zenterestAnalysis(source, 'value transfer amount');
2684
+ }
2685
+ const accountingSink = this.findZenterestAccountingSink(body, tainted, sourceByName);
2686
+ if (accountingSink) {
2687
+ if (this.hasZenterestFreshnessGuard(body, accountingSink.sinkNode, freshnessNames))
2688
+ return null;
2689
+ const source = accountingSink.source || firstSource;
2690
+ if (!source)
2691
+ return null;
2692
+ return this.zenterestAnalysis(source, accountingSink.label);
2693
+ }
2694
+ return null;
2695
+ }
2696
+ hasZenterestFreshnessGuard(body, sinkNode, freshnessNames) {
2697
+ let found = false;
2698
+ let sinkReached = false;
2699
+ this.walk(body, node => {
2700
+ if (found)
2701
+ return;
2702
+ if (node === sinkNode) {
2703
+ sinkReached = true;
2704
+ }
2705
+ if (sinkReached)
2706
+ return;
2707
+ if ((0, ast_1.isNode)(node, 'IfStatement') && this.isZenterestFreshnessGuard(node.condition, freshnessNames)) {
2708
+ if (this.haltsExecution(node.trueBody)) {
2709
+ found = true;
2710
+ }
2711
+ }
2712
+ else if ((0, ast_1.isNode)(node, 'FunctionCall') &&
2713
+ (0, ast_1.isNode)(node.expression, 'Identifier') &&
2714
+ ['require', 'assert'].includes(String(node.expression.name || '')) &&
2715
+ this.isZenterestFreshnessGuard(node, freshnessNames)) {
2716
+ found = true;
2717
+ }
2718
+ });
2719
+ return found;
2720
+ }
2721
+ isZenterestFreshnessGuard(condition, freshnessNames) {
2722
+ if ((0, price_rate_1.isFreshnessGuard)(condition))
2723
+ return true;
2724
+ if (freshnessNames.size === 0)
2725
+ return false;
2726
+ if ((0, ast_1.isNode)(condition, 'FunctionCall')) {
2727
+ const callee = condition.expression;
2728
+ if ((0, ast_1.isNode)(callee, 'Identifier') && ['require', 'assert'].includes(String(callee.name || ''))) {
2729
+ const arg = condition.arguments?.[0];
2730
+ return this.isZenterestFreshnessGuard(arg, freshnessNames);
2731
+ }
2732
+ }
2733
+ let hasBlockTimestamp = false;
2734
+ let hasFreshnessName = false;
2735
+ let hasFreshnessMath = false;
2736
+ this.walk(condition, node => {
2737
+ if ((0, ast_1.isNode)(node, 'MemberAccess')) {
2738
+ const root = node.expression;
2739
+ if ((0, ast_1.isNode)(root, 'Identifier') && String(root.name || '') === 'block' && String(node.memberName || '') === 'timestamp') {
2740
+ hasBlockTimestamp = true;
2741
+ }
2742
+ if (freshnessNames.has(String(node.memberName || '')))
2743
+ hasFreshnessName = true;
2744
+ }
2745
+ else if ((0, ast_1.isNode)(node, 'Identifier') && freshnessNames.has(String(node.name || ''))) {
2746
+ hasFreshnessName = true;
2747
+ }
2748
+ else if ((0, ast_1.isNode)(node, 'BinaryOperation') && ['+', '-'].includes(String(node.operator || ''))) {
2749
+ hasFreshnessMath = true;
2750
+ }
2751
+ });
2752
+ return hasBlockTimestamp && hasFreshnessName && hasFreshnessMath;
2753
+ }
2754
+ haltsExecution(body) {
2755
+ let halts = false;
2756
+ this.walk(body, node => {
2757
+ if (halts)
2758
+ return;
2759
+ if ((0, ast_1.isNode)(node, 'RevertStatement') || (0, ast_1.isNode)(node, 'ReturnStatement')) {
2760
+ halts = true;
2761
+ }
2762
+ else if ((0, ast_1.isNode)(node, 'FunctionCall')) {
2763
+ const callee = node.expression;
2764
+ if ((0, ast_1.isNode)(callee, 'Identifier')) {
2765
+ const name = String(callee.name || '');
2766
+ if (name === 'revert')
2767
+ halts = true;
2768
+ if ((name === 'require' || name === 'assert') && node.arguments?.length > 0) {
2769
+ const arg = node.arguments[0];
2770
+ if ((0, ast_1.isNode)(arg, 'BooleanLiteral') && arg.value === false) {
2771
+ halts = true;
2772
+ }
2773
+ }
2774
+ }
2775
+ }
2776
+ });
2777
+ return halts;
2778
+ }
2779
+ zenterestAnalysis(source, sinkLabel) {
2780
+ const loc = (0, ast_1.tryLoc)(source.node) ?? { line: 1, endLine: 1, column: 0 };
2781
+ return {
2782
+ sourceLoc: { line: loc.line || 1, endLine: loc.endLine || loc.line || 1, column: loc.column || 0 },
2783
+ sourceLabel: source.label,
2784
+ sinkLabel,
2785
+ };
2786
+ }
2787
+ zenterestLatestRoundDataSource(node) {
2788
+ if (!(0, ast_1.isNode)(node, 'FunctionCall'))
2789
+ return null;
2790
+ const callee = node.expression;
2791
+ if (!(0, ast_1.isNode)(callee, 'MemberAccess'))
2792
+ return null;
2793
+ if (String(callee.memberName || '') !== 'latestRoundData')
2794
+ return null;
2795
+ return { node, label: 'latestRoundData() answer' };
2796
+ }
2797
+ zenterestSourceInExpression(node, tainted, sourceByName) {
2798
+ let found = null;
2799
+ this.walk(node, n => {
2800
+ if (found)
2801
+ return;
2802
+ const latestRound = this.zenterestLatestRoundDataSource(n);
2803
+ if (latestRound) {
2804
+ found = latestRound;
2805
+ return;
2806
+ }
2807
+ if (!(0, ast_1.isNode)(n, 'Identifier'))
2808
+ return;
2809
+ const name = String(n.name || '');
2810
+ if (tainted.has(name)) {
2811
+ found = sourceByName.get(name) || { node: n, label: name };
2812
+ return;
2813
+ }
2814
+ if (this.stateVars.includes(name) && looksLikeCachedOraclePriceName(name) && this.contractHasFreshnessTrackingVar) {
2815
+ found = { node: n, label: `cached price state '${name}'` };
2816
+ }
2817
+ });
2818
+ return found;
2819
+ }
2820
+ findZenterestTransferSink(body, tainted, sourceByName) {
2821
+ let found = null;
2822
+ this.walk(body, node => {
2823
+ if (found)
2824
+ return;
2825
+ if (!(0, ast_1.isNode)(node, 'FunctionCall'))
2826
+ return;
2827
+ const callee = node.expression;
2828
+ if (!(0, ast_1.isNode)(callee, 'MemberAccess'))
2829
+ return;
2830
+ const method = String(callee.memberName || '').toLowerCase();
2831
+ if (!COIN_CUT_VALUE_METHODS.has(method))
2832
+ return;
2833
+ for (const arg of node.arguments || []) {
2834
+ const source = this.zenterestSourceInExpression(arg, tainted, sourceByName);
2835
+ if (source || this.exprUsesNames(arg, tainted)) {
2836
+ found = { source, sinkNode: node };
2837
+ return;
2838
+ }
2839
+ }
2840
+ });
2841
+ return found;
2842
+ }
2843
+ findZenterestAccountingSink(body, tainted, sourceByName) {
2844
+ let found = null;
2845
+ this.walk(body, node => {
2846
+ if (found)
2847
+ return;
2848
+ if ((0, ast_1.isNode)(node, 'BinaryOperation')) {
2849
+ if (!/^[+\-*/]?=$/.test(String(node.operator || '')))
2850
+ return;
2851
+ const left = node.left || node.leftExpression;
2852
+ const right = node.right || node.rightExpression;
2853
+ if (!this.isAccountingWriteTarget(left))
2854
+ return;
2855
+ const source = this.zenterestSourceInExpression(right, tainted, sourceByName);
2856
+ if (source || this.exprUsesNames(right, tainted)) {
2857
+ found = { source, label: 'share or supply accounting update', sinkNode: node };
2858
+ }
2859
+ return;
2860
+ }
2861
+ if ((0, ast_1.isNode)(node, 'FunctionCall')) {
2862
+ const callee = node.expression;
2863
+ if (!(0, ast_1.isNode)(callee, 'Identifier'))
2864
+ return;
2865
+ if (!/^(mint|_mint|burn|_burn)$/i.test(String(callee.name || '')))
2866
+ return;
2867
+ for (const arg of node.arguments || []) {
2868
+ const source = this.zenterestSourceInExpression(arg, tainted, sourceByName);
2869
+ if (source || this.exprUsesNames(arg, tainted)) {
2870
+ found = { source, label: 'mint or burn call', sinkNode: node };
2871
+ return;
2872
+ }
2873
+ }
2874
+ }
2875
+ });
2876
+ return found;
2877
+ }
2878
+ zenterestAssignmentParts(node) {
2879
+ if ((0, ast_1.isNode)(node, 'VariableDeclarationStatement')) {
2880
+ return { init: node.initialValue, variables: node.variables || [] };
2881
+ }
2882
+ if (!(0, ast_1.isNode)(node, 'ExpressionStatement'))
2883
+ return null;
2884
+ const expression = node.expression;
2885
+ if (!(0, ast_1.isNode)(expression, 'BinaryOperation'))
2886
+ return null;
2887
+ if (String(expression.operator || '') !== '=')
2888
+ return null;
2889
+ const left = expression.left || expression.leftExpression;
2890
+ const right = expression.right || expression.rightExpression;
2891
+ if ((0, ast_1.isNode)(left, 'Identifier'))
2892
+ return { init: right, variables: [{ name: left.name }] };
2893
+ if ((0, ast_1.isNode)(left, 'TupleExpression')) {
2894
+ const variables = [];
2895
+ for (const comp of left.components || []) {
2896
+ variables.push(comp && (0, ast_1.isNode)(comp, 'Identifier') ? { name: comp.name } : null);
2897
+ }
2898
+ return { init: right, variables };
2899
+ }
2900
+ return null;
2901
+ }
2902
+ coinCutAssignmentParts(node) {
2903
+ if ((0, ast_1.isNode)(node, 'VariableDeclarationStatement')) {
2904
+ return { init: node.initialValue, variables: node.variables || [] };
2905
+ }
2906
+ if (!(0, ast_1.isNode)(node, 'ExpressionStatement'))
2907
+ return null;
2908
+ const expression = node.expression;
2909
+ if (!(0, ast_1.isNode)(expression, 'BinaryOperation'))
2910
+ return null;
2911
+ if (String(expression.operator || '') !== '=')
2912
+ return null;
2913
+ const left = expression.left || expression.leftExpression;
2914
+ const right = expression.right || expression.rightExpression;
2915
+ const variables = [];
2916
+ if ((0, ast_1.isNode)(left, 'Identifier')) {
2917
+ variables.push({ name: left.name });
2918
+ }
2919
+ else if ((0, ast_1.isNode)(left, 'TupleExpression')) {
2920
+ // Split tuple assignment, e.g. `(r0, r1,) = pair.getReserves();`.
2921
+ // Blank/null tuple slots are skipped.
2922
+ for (const comp of left.components || []) {
2923
+ if (comp && (0, ast_1.isNode)(comp, 'Identifier'))
2924
+ variables.push({ name: comp.name });
2925
+ }
2926
+ }
2927
+ else {
2928
+ return null;
2929
+ }
2930
+ return { init: right, variables };
2931
+ }
2932
+ coinCutAnalysis(source, sinkLabel) {
2933
+ const loc = (0, ast_1.tryLoc)(source.node) ?? { line: 1, endLine: 1, column: 0 };
2934
+ return {
2935
+ sourceLoc: { line: loc.line || 1, endLine: loc.endLine || loc.line || 1, column: loc.column || 0 },
2936
+ sourceLabel: source.label,
2937
+ sinkLabel,
2938
+ };
2939
+ }
2940
+ findCoinCutTransferSink(body, tainted, sourceByName) {
2941
+ let found = null;
2942
+ this.walk(body, node => {
2943
+ if (found)
2944
+ return;
2945
+ if (!(0, ast_1.isNode)(node, 'FunctionCall'))
2946
+ return;
2947
+ const callee = node.expression;
2948
+ if (!(0, ast_1.isNode)(callee, 'MemberAccess'))
2949
+ return;
2950
+ const method = String(callee.memberName || '').toLowerCase();
2951
+ if (!COIN_CUT_VALUE_METHODS.has(method))
2952
+ return;
2953
+ for (const arg of node.arguments || []) {
2954
+ const source = this.coinCutSourceInExpression(arg) || this.coinCutTaintSource(arg, tainted, sourceByName);
2955
+ if (source || this.exprUsesNames(arg, tainted)) {
2956
+ found = { source };
2957
+ return;
2958
+ }
2959
+ }
2960
+ });
2961
+ return found;
2962
+ }
2963
+ findCoinCutAccountingSink(body, tainted, sourceByName) {
2964
+ let found = null;
2965
+ this.walk(body, node => {
2966
+ if (found)
2967
+ return;
2968
+ if ((0, ast_1.isNode)(node, 'BinaryOperation')) {
2969
+ if (!/^[+\-*/]?=$/.test(String(node.operator || '')))
2970
+ return;
2971
+ const left = node.left || node.leftExpression;
2972
+ const right = node.right || node.rightExpression;
2973
+ if (!this.isAccountingWriteTarget(left))
2974
+ return;
2975
+ const source = this.coinCutSourceInExpression(right) || this.coinCutTaintSource(right, tainted, sourceByName);
2976
+ if (source || this.exprUsesNames(right, tainted)) {
2977
+ found = { source, label: 'share or supply accounting update' };
2978
+ }
2979
+ return;
2980
+ }
2981
+ if ((0, ast_1.isNode)(node, 'FunctionCall')) {
2982
+ const callee = node.expression;
2983
+ if (!(0, ast_1.isNode)(callee, 'Identifier'))
2984
+ return;
2985
+ if (!/^(mint|_mint|burn|_burn)$/i.test(String(callee.name || '')))
2986
+ return;
2987
+ for (const arg of node.arguments || []) {
2988
+ const source = this.coinCutSourceInExpression(arg) || this.coinCutTaintSource(arg, tainted, sourceByName);
2989
+ if (source || this.exprUsesNames(arg, tainted)) {
2990
+ found = { source, label: 'mint or burn call' };
2991
+ return;
2992
+ }
2993
+ }
2994
+ }
2995
+ });
2996
+ return found;
2997
+ }
2998
+ coinCutSourceInExpression(node) {
2999
+ let found = null;
3000
+ this.walk(node, n => {
3001
+ if (found)
3002
+ return;
3003
+ if (!(0, ast_1.isNode)(n, 'FunctionCall'))
3004
+ return;
3005
+ const callee = n.expression;
3006
+ if (!(0, ast_1.isNode)(callee, 'MemberAccess'))
3007
+ return;
3008
+ const member = String(callee.memberName || '');
3009
+ if (member === 'getReserves') {
3010
+ found = { node: n, label: 'getReserves()' };
3011
+ return;
3012
+ }
3013
+ if (member !== 'balanceOf')
3014
+ return;
3015
+ const arg = n.arguments?.[0];
3016
+ if (!arg)
3017
+ return;
3018
+ const target = this.flattenNames(arg).toLowerCase();
3019
+ if (target.includes('pair') || target.includes('pool')) {
3020
+ found = { node: n, label: 'balanceOf()' };
3021
+ }
3022
+ else if (target.includes('this')) {
3023
+ // `node` is scoped to the immediate RHS, so arithmetic on a later
3024
+ // line is invisible here. Track the taint and apply the simple-sweep
3025
+ // exclusion at function-body scope in findCoinCutPriceManipulation.
3026
+ found = { node: n, label: 'balanceOf(address(this))' };
3027
+ }
3028
+ });
3029
+ return found;
3030
+ }
3031
+ coinCutTaintSource(node, tainted, sourceByName) {
3032
+ let found = null;
3033
+ this.walk(node, n => {
3034
+ if (found)
3035
+ return;
3036
+ if (!(0, ast_1.isNode)(n, 'Identifier'))
3037
+ return;
3038
+ const name = String(n.name || '');
3039
+ if (!tainted.has(name))
3040
+ return;
3041
+ found = sourceByName.get(name) || { node: n, label: name };
3042
+ });
3043
+ return found;
3044
+ }
3045
+ exprUsesNames(node, names) {
3046
+ let found = false;
3047
+ this.walk(node, n => {
3048
+ if (found)
3049
+ return;
3050
+ if ((0, ast_1.isNode)(n, 'Identifier') && names.has(String(n.name || '')))
3051
+ found = true;
3052
+ });
3053
+ return found;
3054
+ }
3055
+ hasCoinCutReferenceGuard(body, tainted) {
3056
+ // Only recognise guards at the top level of the function body. A
3057
+ // require/assert or if-revert that is a direct child of the block
3058
+ // dominates all subsequent code. A guard nested inside a conditional
3059
+ // branch (e.g. `if (check) { require(spot <= trustedRate); }`) does
3060
+ // NOT dominate the sink on every execution path and must not suppress
3061
+ // the finding.
3062
+ if (!body)
3063
+ return false;
3064
+ const stmts = body.statements || body.body || [];
3065
+ for (const stmt of stmts) {
3066
+ // Top-level require/assert
3067
+ if ((0, ast_1.isNode)(stmt, 'ExpressionStatement')) {
3068
+ const expr = stmt.expression;
3069
+ if ((0, ast_1.isNode)(expr, 'FunctionCall')) {
3070
+ const callee = expr.expression;
3071
+ if ((0, ast_1.isNode)(callee, 'Identifier') && ['require', 'assert'].includes(String(callee.name || ''))) {
3072
+ const cond = expr.arguments?.[0];
3073
+ if (cond && this.coinCutReferenceConditionIsGuard(cond, tainted))
3074
+ return true;
3075
+ }
3076
+ }
3077
+ }
3078
+ // Top-level if-revert
3079
+ if ((0, ast_1.isNode)(stmt, 'IfStatement')) {
3080
+ if (this.coinCutBranchContainsRevert(stmt.trueBody) || this.coinCutBranchContainsRevert(stmt.falseBody)) {
3081
+ const cond = stmt.condition;
3082
+ if (cond && this.coinCutReferenceConditionIsGuard(cond, tainted))
3083
+ return true;
3084
+ }
3085
+ }
3086
+ }
3087
+ return false;
3088
+ }
3089
+ coinCutReferenceConditionIsGuard(cond, tainted) {
3090
+ // Decide guard semantics from identifier/member names only — a revert
3091
+ // string literal alone (e.g. "limit") is not proof of a reference check.
3092
+ const text = this.flattenNames(cond, true);
3093
+ if (!COIN_CUT_REFERENCE_NAMES.test(text))
3094
+ return false;
3095
+ if (tainted !== undefined) {
3096
+ const scopedToLivePrice = (tainted.size > 0 && this.exprUsesNames(cond, tainted)) || !!this.coinCutSourceInExpression(cond);
3097
+ if (!scopedToLivePrice)
3098
+ return false;
3099
+ }
3100
+ return true;
3101
+ }
3102
+ coinCutBranchContainsRevert(branch) {
3103
+ let found = false;
3104
+ this.walk(branch, node => {
3105
+ if (found)
3106
+ return;
3107
+ if ((0, ast_1.isNode)(node, 'RevertStatement')) {
3108
+ found = true;
3109
+ return;
3110
+ }
3111
+ if (!(0, ast_1.isNode)(node, 'FunctionCall'))
3112
+ return;
3113
+ const callee = node.expression;
3114
+ if ((0, ast_1.isNode)(callee, 'Identifier') && String(callee.name || '') === 'revert')
3115
+ found = true;
3116
+ });
3117
+ return found;
3118
+ }
3119
+ isAccountingWriteTarget(node) {
3120
+ if (!node)
3121
+ return false;
3122
+ if ((0, ast_1.isNode)(node, 'IndexAccess'))
3123
+ return COIN_CUT_ACCOUNTING_NAMES.test(this.flattenNames(node.base || node.baseExpression));
3124
+ if ((0, ast_1.isNode)(node, 'MemberAccess'))
3125
+ return COIN_CUT_ACCOUNTING_NAMES.test(String(node.memberName || ''));
3126
+ if ((0, ast_1.isNode)(node, 'Identifier'))
3127
+ return COIN_CUT_ACCOUNTING_NAMES.test(String(node.name || ''));
3128
+ return false;
3129
+ }
3130
+ flattenNames(node, skipStrings = false) {
3131
+ const names = [];
3132
+ this.walk(node, n => {
3133
+ if ((0, ast_1.isNode)(n, 'Identifier') && n.name)
3134
+ names.push(String(n.name));
3135
+ else if ((0, ast_1.isNode)(n, 'MemberAccess') && n.memberName)
3136
+ names.push(String(n.memberName));
3137
+ else if (!skipStrings && (0, ast_1.isNode)(n, 'StringLiteral') && n.value)
3138
+ names.push(String(n.value));
3139
+ });
3140
+ return names.join(' ');
3141
+ }
3142
+ hasArithmeticOperator(node) {
3143
+ let found = false;
3144
+ this.walk(node, n => {
3145
+ if (found)
3146
+ return;
3147
+ if ((0, ast_1.isNode)(n, 'BinaryOperation') && ['*', '/'].includes(String(n.operator || '')))
3148
+ found = true;
3149
+ });
3150
+ return found;
3151
+ }
3152
+ findV3MigratorBiswapPattern(fnNode) {
3153
+ const body = fnNode.body;
3154
+ if (!body)
3155
+ return false;
3156
+ let hasTransferFrom = false;
3157
+ let hasBurn = false;
3158
+ let mintCall = null;
3159
+ this.walk(body, node => {
3160
+ if ((0, ast_1.isNode)(node, 'FunctionCall')) {
3161
+ const callee = this.unwrapCallOptions(node.expression);
3162
+ const member = this.extractV3MigratorMethodName(callee);
3163
+ if (member === 'transferFrom')
3164
+ hasTransferFrom = true;
3165
+ if (member === 'burn')
3166
+ hasBurn = true;
3167
+ if (member === 'mint')
3168
+ mintCall = node;
3169
+ }
3170
+ });
3171
+ if (!hasTransferFrom || !hasBurn || !mintCall)
3172
+ return false;
3173
+ const mintMinReferences = this.extractV3MigratorMintMinReferences(mintCall);
3174
+ const checkedTokens = new Set();
3175
+ let hasSlippageValidation = false;
3176
+ this.walk(body, node => {
3177
+ if ((0, ast_1.isNode)(node, 'FunctionCall') && (0, ast_1.isNode)(node.expression, 'Identifier')) {
3178
+ const name = String(node.expression.name || '');
3179
+ if (name === 'require' || name === 'assert') {
3180
+ const condition = node.arguments?.[0];
3181
+ for (const token of this.extractV3MigratorPairTokenChecks(condition))
3182
+ checkedTokens.add(token);
3183
+ if (this.hasV3MigratorSlippageCheck(condition, mintMinReferences))
3184
+ hasSlippageValidation = true;
3185
+ }
3186
+ }
3187
+ });
3188
+ const hasValidation = hasSlippageValidation || (checkedTokens.has('token0') && checkedTokens.has('token1'));
3189
+ if (hasValidation)
3190
+ return false;
3191
+ return true;
3192
+ }
3193
+ extractV3MigratorMintMinReferences(mintCall) {
3194
+ const references = new Set();
3195
+ const args = Array.isArray(mintCall?.arguments) ? mintCall.arguments : [];
3196
+ args.forEach((arg, index) => {
3197
+ const fieldName = this.extractV3MigratorNameValueName(arg);
3198
+ const expression = fieldName ? (arg.expression || arg.value || arg) : arg;
3199
+ const fieldLower = fieldName.toLowerCase();
3200
+ if (this.isV3MigratorMinOutputName(fieldLower) || index === 4 || index === 5) {
3201
+ this.collectV3MigratorReferenceNames(expression, references);
3202
+ }
3203
+ this.walk(arg, child => {
3204
+ const childField = this.extractV3MigratorNameValueName(child);
3205
+ if (!childField || !this.isV3MigratorMinOutputName(childField.toLowerCase()))
3206
+ return;
3207
+ this.collectV3MigratorReferenceNames(child.expression || child.value || child, references);
3208
+ });
3209
+ });
3210
+ return references;
3211
+ }
3212
+ extractV3MigratorPairTokenChecks(condition) {
3213
+ const checked = new Set();
3214
+ this.walk(condition, node => {
3215
+ if (!(0, ast_1.isNode)(node, 'BinaryOperation') || String(node.operator || '') !== '==')
3216
+ return;
3217
+ const leftToken = this.v3MigratorPairTokenMethod(node.left);
3218
+ const rightToken = this.v3MigratorPairTokenMethod(node.right);
3219
+ if (leftToken && this.v3MigratorReferencesTokenName(node.right, leftToken))
3220
+ checked.add(leftToken);
3221
+ if (rightToken && this.v3MigratorReferencesTokenName(node.left, rightToken))
3222
+ checked.add(rightToken);
3223
+ });
3224
+ return checked;
3225
+ }
3226
+ hasV3MigratorSlippageCheck(condition, mintMinReferences) {
3227
+ if (mintMinReferences.size === 0)
3228
+ return false;
3229
+ let amount0Checked = false;
3230
+ let amount1Checked = false;
3231
+ this.walk(condition, node => {
3232
+ if (!(0, ast_1.isNode)(node, 'BinaryOperation'))
3233
+ return;
3234
+ const operator = String(node.operator || '');
3235
+ if (!['>', '>=', '!=', '<', '<=', '=='].includes(operator))
3236
+ return;
3237
+ const leftRef = this.v3MigratorMinReferenceSide(node.left, mintMinReferences);
3238
+ const rightRef = this.v3MigratorMinReferenceSide(node.right, mintMinReferences);
3239
+ if (leftRef && (this.isV3MigratorNonZeroCheck(operator, 'left', node.right) || this.v3MigratorLooksLikeBurnAmount(node.right))) {
3240
+ if (leftRef === 'amount0')
3241
+ amount0Checked = true;
3242
+ if (leftRef === 'amount1')
3243
+ amount1Checked = true;
3244
+ }
3245
+ if (rightRef && (this.isV3MigratorNonZeroCheck(operator, 'right', node.left) || this.v3MigratorLooksLikeBurnAmount(node.left))) {
3246
+ if (rightRef === 'amount0')
3247
+ amount0Checked = true;
3248
+ if (rightRef === 'amount1')
3249
+ amount1Checked = true;
3250
+ }
3251
+ });
3252
+ return amount0Checked && amount1Checked;
3253
+ }
3254
+ v3MigratorMinReferenceSide(node, mintMinReferences) {
3255
+ let side = null;
3256
+ this.walk(node, child => {
3257
+ if (side)
3258
+ return;
3259
+ const name = this.v3MigratorReferenceName(child);
3260
+ if (!name || !mintMinReferences.has(name))
3261
+ return;
3262
+ if (name.includes('amount0') || name.includes('0min') || name.includes('min0'))
3263
+ side = 'amount0';
3264
+ else if (name.includes('amount1') || name.includes('1min') || name.includes('min1'))
3265
+ side = 'amount1';
3266
+ });
3267
+ return side;
3268
+ }
3269
+ collectV3MigratorReferenceNames(node, out) {
3270
+ this.walk(node, child => {
3271
+ const name = this.v3MigratorReferenceName(child);
3272
+ if (name)
3273
+ out.add(name);
3274
+ });
3275
+ }
3276
+ v3MigratorReferenceName(node) {
3277
+ if ((0, ast_1.isNode)(node, 'Identifier'))
3278
+ return String(node.name || '').toLowerCase();
3279
+ if ((0, ast_1.isNode)(node, 'MemberAccess'))
3280
+ return String(node.memberName || '').toLowerCase();
3281
+ return '';
3282
+ }
3283
+ extractV3MigratorNameValueName(node) {
3284
+ if (!(0, ast_1.isNode)(node, 'NameValueExpression'))
3285
+ return '';
3286
+ const name = node.name || node.key || node.argumentName;
3287
+ if (typeof name === 'string')
3288
+ return name;
3289
+ if (name && typeof name === 'object')
3290
+ return String(name.name || name.memberName || '');
3291
+ return '';
3292
+ }
3293
+ isV3MigratorMinOutputName(name) {
3294
+ return /amount[01]min|minamount[01]|amountoutmin|minout/.test(name);
3295
+ }
3296
+ v3MigratorPairTokenMethod(node) {
3297
+ let token = null;
3298
+ this.walk(node, child => {
3299
+ if (token || !(0, ast_1.isNode)(child, 'FunctionCall'))
3300
+ return;
3301
+ const method = this.extractV3MigratorMethodName(child.expression);
3302
+ if (method === 'token0' || method === 'token1')
3303
+ token = method;
3304
+ });
3305
+ return token;
3306
+ }
3307
+ v3MigratorReferencesTokenName(node, token) {
3308
+ let found = false;
3309
+ this.walk(node, child => {
3310
+ if (found)
3311
+ return;
3312
+ const name = this.v3MigratorReferenceName(child);
3313
+ if (name === token)
3314
+ found = true;
3315
+ });
3316
+ return found;
3317
+ }
3318
+ v3MigratorLooksLikeBurnAmount(node) {
3319
+ let found = false;
3320
+ this.walk(node, child => {
3321
+ if (found)
3322
+ return;
3323
+ const name = this.v3MigratorReferenceName(child);
3324
+ if (/^amount[01]$|burn/.test(name))
3325
+ found = true;
3326
+ });
3327
+ return found;
3328
+ }
3329
+ isV3MigratorNonZeroCheck(operator, minSide, otherSide) {
3330
+ if (!this.isZeroLiteral(otherSide))
3331
+ return false;
3332
+ if (operator === '!=')
3333
+ return true;
3334
+ return (minSide === 'left' && operator === '>') || (minSide === 'right' && operator === '<');
3335
+ }
3336
+ isZeroLiteral(node) {
3337
+ if (!node)
3338
+ return false;
3339
+ if ((0, ast_1.isNode)(node, 'NumberLiteral'))
3340
+ return String(node.number ?? node.value ?? '') === '0';
3341
+ if ((0, ast_1.isNode)(node, 'DecimalNumber'))
3342
+ return String(node.value ?? '') === '0';
3343
+ return false;
3344
+ }
3345
+ extractV3MigratorMethodName(expr) {
3346
+ if (!expr)
3347
+ return null;
3348
+ if ((0, ast_1.isNode)(expr, 'MemberAccess'))
3349
+ return String(expr.memberName || '');
3350
+ if ((0, ast_1.isNode)(expr, 'Identifier'))
3351
+ return String(expr.name || '');
3352
+ return null;
3353
+ }
3354
+ }
3355
+ exports.LogicFlawDetector = LogicFlawDetector;
3356
+ //# sourceMappingURL=logic-flaw.js.map