Skip to content

docs: add 2 new academic citations to README - #703

Merged
Jon-Becker merged 1 commit into
mainfrom
docs/add-citations
Jul 4, 2026
Merged

docs: add 2 new academic citations to README#703
Jon-Becker merged 1 commit into
mainfrom
docs/add-citations

Conversation

@Jon-Becker

@Jon-Becker Jon-Becker commented Jul 3, 2026

Copy link
Copy Markdown
Owner

What changed? Why?

Added 2 new academic citations to the README bibliography that cite heimdall-rs:

  1. Peng, C., Jiang, M., Zhou, Y., & Wu, L. (2026). Thought is all you need: smart contract vulnerability detection with thought-augmented large language model. Proceedings of the ACM on Software Engineering, 3(FSE). https://doi.org/10.1145/3808141

    • Proposes Synapse, a vulnerability detection framework using thought-augmented LLMs; references heimdall-rs as a smart contract analysis toolkit.
  2. Su, X., Wu, H., Liang, H., Jiang, Y., Cheng, Y., Liu, Y., & Xu, F. (2026). From transactions to exploits: automated PoC synthesis for real-world DeFi attacks. arXiv. https://doi.org/10.48550/arXiv.2601.16681

    • Presents TracExp, an automated framework for synthesizing exploit PoCs from on-chain attack traces; uses heimdall-rs as part of a dual-decompiler pipeline.

Both entries are placed in alphabetical order consistent with the existing citation list.

Notes to reviewers

  • Citations were discovered via automated scholarly search (Google Scholar, arXiv, SSRN)
  • Verified that neither citation was already listed in the README
  • Both papers are from 2026 and cite heimdall-rs in vulnerability detection / decompilation contexts
  • PR title follows conventional commit format (docs: ...)

How has it been tested?

  • Verified the citation entries match the bibliography format in the existing README
  • Confirmed DOI/URL links are valid and resolve to the correct papers
  • No code changes; documentation-only update

- Peng et al. (2026) - Synapse: smart contract vulnerability detection
  with thought-augmented LLM (FSE 2026, ACM)
- Su et al. (2026) - TracExp: automated PoC synthesis for real-world
  DeFi attacks (arXiv)
@Jon-Becker
Jon-Becker enabled auto-merge (squash) July 3, 2026 16:09
@Jon-Becker
Jon-Becker disabled auto-merge July 3, 2026 16:09
@Jon-Becker
Jon-Becker enabled auto-merge (squash) July 3, 2026 16:11
@github-actions

github-actions Bot commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

❌ Eval Report for 26774bb

Test Case CFG Decompilation
TransientStorage 100 25
NestedMapping 100 10
Mapping 100 92
SimpleStorage 100 85
NestedMappings 100 38
SimpleLoop 90 15
WhileLoop 85 20
Events 100 68
NestedLoop 55 20
WETH9 100 65
Average 93 43
⚠️ 8 eval(s) scoring <70%

TransientStorage (CFG: 100, Decompilation: 25)

Decompilation

{
  "score": 25,
  "summary": "The decompilation fails to preserve the logic of five of the six functions. incrementCounter, lock, unlock, getCounter, and isLocked are all collapsed into meaningless constant declarations rather than functions with their transient storage read/write logic. Only setTempOwner is recovered as a real function, and even that captures the transient store but mislabels it as pure. Overall program behavior involving transient counter increment, lock/unlock state changes, and view getters is essentially lost.",
  "differences": [
    "incrementCounter is not decompiled as a function; the transient load/increment/store (counter = counter + 1) logic is missing, represented as an empty constant.",
    "lock is not decompiled as a function; the write setting the transient locked flag to true is missing.",
    "unlock is not decompiled as a function; the write setting the transient locked flag to false is missing.",
    "getCounter is not decompiled as a function; the transient read and return of counter is missing (shown as constant = 1).",
    "isLocked is not decompiled as a function; the transient read and return of locked is missing (shown as constant true).",
    "setTempOwner is marked 'pure' but actually performs a transient storage write, so its mutability behavior is misrepresented.",
    "setTempOwner writes to transient[0x01] with a masking/OR operation that does not clearly correspond to the simple tempOwner = owner assignment in the source."
  ]
}

NestedMapping (CFG: 100, Decompilation: 10)

Decompilation

{
  "score": 10,
  "summary": "The decompilation fails to capture the contract's fundamental behavior. The original has functions that write to and read from nested mappings (allowances, grid, deepNested), but the decompiled output contains only pure functions with trivial no-op assertions (require(arg == arg)). No storage writes, no storage reads, no mapping slot computations, and no return values are preserved. The core state-changing logic is entirely absent.",
  "differences": [
    "setAllowance: original writes allowances[owner][spender] = amount (nested SSTORE); no storage write appears in any decompiled function",
    "setGrid: original writes grid[x][y] = value; no corresponding storage write is present",
    "setDeepNested: original writes deepNested[a][b][c] = value (triple-nested SSTORE); no such write is present",
    "getAllowance: original reads allowances[owner][spender] and returns a uint256; no decompiled function performs a storage read or returns a value",
    "All decompiled functions are marked 'pure' and contain only tautological require checks, whereas the originals are state-mutating or view functions with real storage interaction",
    "Function count/selectors do not correspond to the four original functions; the decompiled selectors and logic bear no functional relationship to the source"
  ]
}

NestedMappings (CFG: 100, Decompilation: 38)

Decompilation

{
  "score": 38,
  "summary": "The decompilation fails to preserve the core behavior of the nested mapping contract. Both read functions (allowance and the auto-generated allowances getter) lose their entire storage-read and return logic, appearing as no-op 'pure' functions. The approve write is partially captured but keys storage only on the spender, dropping the msg.sender level and nested slot derivation.",
  "differences": [
    "The allowance getter (selector 0xdd62ed3e) loses all functional logic: it does not read nested mapping storage and returns no value; it is incorrectly marked 'pure' with only a no-op address-mask check.",
    "The public allowances(address,address) getter (selector 0x55b6ed5c) similarly loses its nested storage read and return value, decompiled as a no-op 'pure' function.",
    "approve writes arg1 to storage keyed only on arg0 (spender), omitting the msg.sender key and the nested keccak slot computation, so the storage location does not correspond to allowances[msg.sender][spender].",
    "The two-level nested mapping structure is flattened to a single-level mapping, losing the second index dimension across all functions.",
    "Both getter functions take only a single address argument, losing the second parameter present in the original two-argument nested-mapping accessors."
  ]
}

SimpleLoop (CFG: 90, Decompilation: 15)

Decompilation

{
  "score": 15,
  "summary": "The decompilation fails to capture the core behavior of the contract. The original loops `loops` times incrementing the `number` storage variable, but the decompiled output has no loop, no storage write, incorrectly marks the function as `view`, and replaces the logic with meaningless/tautological require statements.",
  "differences": [
    "The for-loop control flow is entirely missing; no iteration over `arg0` is represented",
    "The storage write `number++` (SSTORE to increment state) is not captured; the function is incorrectly marked `view` implying no state changes",
    "The require statements are nonsensical (`arg0 == arg0` is always true, `!0 < arg0`, and an unrelated subtraction) and do not correspond to any original logic",
    "The state-changing nature of the function (mutability) is wrong: original mutates `number`, decompiled is pure/view with no effects"
  ]
}

WhileLoop (CFG: 85, Decompilation: 20)

Decompilation

{
  "score": 20,
  "summary": "The decompilation fails to capture the core behavior of the function. The original performs a while loop that repeatedly increments the storage variable `number` for `loops` iterations. The decompiled output contains only nonsensical require statements, omits the loop entirely, omits the storage write to `number`, and incorrectly marks the function as `view` (non-state-changing) when the original mutates state.",
  "differences": [
    "The while loop and its iteration over `loops` is completely absent from the decompiled output",
    "The storage write `number = number + 1` is not represented; there is no SSTORE-equivalent, only a comparison `require(!number > (number + 0x01))`",
    "The loop counter `i` and its increment/comparison logic is missing",
    "Function is marked `view` but the original mutates state (writes to `number`), so mutability behavior is incorrect",
    "The require conditions produced (`require(arg0 == arg0)`, `require(!0 < arg0)`) do not correspond to any logic in the original and are effectively meaningless"
  ]
}

Events (CFG: 100, Decompilation: 68)

Decompilation

{
  "score": 68,
  "summary": "Most event emissions are preserved with correct events and parameters (Deposit, Withdrawal, LogBytes, Log, and the multi-emit function emitting Deposit + Transfer(0,...) + Log). However, two of the seven functions lost their event emissions entirely: emitTransfer and emitApproval are reduced to bare require checks with no emit, and the Approval event is not even declared in the decompiled output. String/bytes emit parameters are represented with low-level offset/length notation, which is acceptable.",
  "differences": [
    "emitTransfer: the emit Transfer(from, to, value) is missing; the corresponding decompiled function (Unresolved_5687f2b8) only performs an argument require and emits nothing.",
    "emitApproval: the emit Approval(owner, spender, value) is missing; the corresponding decompiled function (Unresolved_23de6651) only performs a require and emits nothing. The Approval event is also absent from the event declarations.",
    "emitTransfer/emitApproval decompiled functions only recover a single address argument rather than the full (address, address, uint256) signature, indicating the parameter loading and emission logic for these two functions was not reconstructed."
  ]
}

NestedLoop (CFG: 55, Decompilation: 20)

Decompilation

{
  "score": 20,
  "summary": "The decompilation fails to capture the core behavior of the contract. The original performs a nested loop that increments the storage variable `number` by 1 on each inner iteration (loops*loops total increments). The decompiled output preserves neither the loop control flow, the storage write, nor the correct mutability, reducing the function to a series of degenerate require statements.",
  "differences": [
    "The nested for-loops (outer and inner iteration over `loops`) are entirely absent; no loop control flow is represented, only leftover comparison fragments as require conditions.",
    "The storage write `number += 1` is not preserved as a state change; `number + 0x01` appears only inside a require expression, so the SSTORE is lost.",
    "Function mutability is incorrectly marked as `view`, whereas the original mutates storage (`number`), a functional divergence in state-change behavior.",
    "Comparison conditions are malformed/incorrect (`require(!0 < arg0)`, `require(!number > (number + 0x01))`) and do not reflect the loop bound checks `i < loops` / `j < loops`.",
    "The accumulation semantics (total increments equal to loops squared) are entirely lost."
  ]
}

CFG

{
  "score": 55,
  "summary": "The CFG fully captures the function-dispatch logic, the public getter path, revert/require paths, and the outer loop's two-way condition branch. However the nested loop structure is materially incomplete: the inner loop's exit branch is absent and neither loop has its back-edge (increment + jump-back), so both loops appear as linear dead-ends rather than iterative cycles.",
  "missing_paths": [
    "Inner loop exit branch: node 8 (0x85-0x8c) has a JUMPI to 0xb1 (inner loop exit) when j<loops is false, but no corresponding node/edge exists - only the fall-through (enter body) edge 8->9 is present",
    "Inner loop back-edge: the increment of j and the jump back to the inner condition at 0x84 is not represented, so the inner for-loop's iteration cycle is missing",
    "Outer loop back-edge: the increment of i and the jump back to the outer condition at 0x77 is not represented, so the outer for-loop never loops in the CFG",
    "Loop-body continuation after the overflow check (0x01ac) that stores 'number' and returns into the loop is missing (the CFG only shows the overflow-revert branch 9->10)"
  ],
  "extra_paths": [
    "Constructor/CALLVALUE non-payable check 0->1 (REVERT) - compiler-added",
    "Overflow Panic(0x11) revert path 9->10 (0x0152) on 'number += 1' - compiler-added arithmetic check",
    "Calldata SLT/decode validation reverts (nodes 6 and 12) - compiler-added ABI decoding checks",
    "CALLDATASIZE < 4 fallback revert (node 15) - compiler dispatch"
  ],
  "observations": [
    "Function-selector dispatch is fully modeled as an if-else chain: loop() selector 0x0b7d796e and number() getter selector 0x8381f58a each produce a proper two-way branch, plus the no-match fallback revert.",
    "The outer loop's conditional (i<loops) is correctly represented with both an enter-body edge (7->8) and an exit edge (7->11 -> STOP).",
    "The inner loop is only half-represented: its enter-body edge exists but its exit edge is absent, so the inner condition looks like an unconditional flow.",
    "The absence of both loops' back-edges is the dominant gap - the CFG does not express the cyclic nature of either for-loop, likely because the iteration jumps resolve to dynamic JUMP targets."
  ]
}

WETH9 (CFG: 100, Decompilation: 65)

Decompilation

{
  "score": 65,
  "summary": "Core WETH9 behavior is recognizable: deposit, withdraw (with the ETH transfer and balance decrement), totalSupply via this.balance, balanceOf, and the balance arithmetic in transfer/transferFrom are all preserved along with all four events. However, the allowance-related logic is significantly garbled: approve writes to the balances slot rather than a distinct allowance mapping, the nested allowance mapping is flattened, and the conditional allowance check/decrement in transferFrom is mangled with inverted requires and unreachable duplicated code blocks.",
  "differences": [
    "approve() writes the value into storage_map_c (the same slot used for balanceOf) instead of a separate allowance mapping; the write is captured but targets the wrong/mislabeled storage location, and allowance() reads from a different slot (storage_map_d), making approve and allowance inconsistent.",
    "The nested allowance mapping (address => address => uint) is flattened to a single-key mapping, so the double-key structure (owner/spender) is not preserved.",
    "The conditional allowance logic in transfer/transferFrom is incorrect: the original decrements allowance only when src != msg.sender AND allowance != uint(-1), but the decompilation reduces the sender check to a trivially-true comparison (msg.sender == msg.sender) and represents the allowance branch as require(allowance == max), inverting the intended condition.",
    "The allowance decrement (allowance[src][msg.sender] -= wad) from the original is not clearly represented in the primary execution path.",
    "transfer() and transferFrom() contain large blocks of unreachable duplicated code after return statements, obscuring the actual control flow of the allowance handling."
  ]
}

@github-actions

github-actions Bot commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

⚠️ Coverage Report for 26774bb

Metric Value
Base branch 66.23%
PR branch 66.14%
Diff -0.09%

@Jon-Becker
Jon-Becker disabled auto-merge July 4, 2026 14:27
@Jon-Becker
Jon-Becker merged commit adafa44 into main Jul 4, 2026
13 of 15 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant