Skip to content

feat: update citations#690

Merged
Jon-Becker merged 1 commit into
mainfrom
jon-becker/update-citations-0414
Apr 14, 2026
Merged

feat: update citations#690
Jon-Becker merged 1 commit into
mainfrom
jon-becker/update-citations-0414

Conversation

@Jon-Becker

Copy link
Copy Markdown
Owner

What changed? Why?

Notes to reviewers

How has it been tested?

@Jon-Becker Jon-Becker merged commit 13ae7cb into main Apr 14, 2026
@github-actions

Copy link
Copy Markdown
Contributor

⚠️ Coverage Report for b7a943d

Metric Value
Base branch 66.17%
PR branch 66.05%
Diff -0.12%

@github-actions

Copy link
Copy Markdown
Contributor

❌ Eval Report for b7a943d

Test Case CFG Decompilation
WhileLoop 65 5
NestedMapping 97 5
NestedLoop 45 5
TransientStorage 100 5
Mapping 100 42
SimpleStorage 100 47
NestedMappings 70 20
SimpleLoop 70 5
Events 60 42
WETH9 85 45
Average 79 22
⚠️ 10 eval(s) scoring <70%

WhileLoop (CFG: 65, Decompilation: 5)

Decompilation

{
  "score": 5,
  "summary": "Decompilation completely fails to reconstruct the while loop. The function is incorrectly marked as view (no state mutation), the loop body and counter are entirely absent, and the output contains nonsensical require statements that bear no relation to the original logic.",
  "differences": [
    "While loop is entirely absent — no iteration logic of any kind is present",
    "State mutation of `number` (incremented each iteration) is missing entirely",
    "Function mutability is wrong: decompiled as `view` when original modifies state",
    "Loop counter variable `i` and its increment logic are not represented",
    "Three nonsensical require statements appear instead of loop logic: `require(arg0 == arg0)` (tautology), `require(!0 < arg0)` (malformed), and `require(!number > (number + 0x01))` (malformed and logically inverted)"
  ]
}

CFG

{
  "score": 65,
  "summary": "The CFG captures the while loop's initialization, condition check, and exit path, but is missing the loop back edge and the successful completion of the loop body (SSTORE for 'number', 'i = i + 1' increment, and the jump back to the loop header at 0x77). The loop's cyclic structure is not fully represented.",
  "missing_paths": [
    "Loop back edge: after the loop body executes successfully (number + 1 stored, i + 1 computed), there is no edge returning to the loop condition check at 0x77 (JUMPDEST). The CFG has no node or edge representing this path.",
    "Successful completion of loop body: the SSTORE for 'number = number + 1' and the 'i = i + 1' increment (with its own addition and the jump back to loop header) are entirely absent. Node 8 ends at the overflow JUMPI (0x018a) with only an edge to the overflow revert (Node 9); the fall-through path to 0x0193+ has no corresponding node or outgoing edge."
  ],
  "extra_paths": [
    "Overflow revert (Node 9 / 0x018b): compiler-added Panic(0x11) for arithmetic overflow on 'number + 1' — not in source logic, does not affect score.",
    "Constructor ETH value check (Node 0 -> Node 1 revert): compiler-added rejection of ETH sent to non-payable constructor.",
    "Calldata size < 4 revert (Node 2 -> Node 14): compiler dispatcher guard.",
    "Input type validation revert (Node 5 -> Node 6): compiler-added ABI decoding bounds check on the uint256 parameter."
  ],
  "observations": [
    "Node 7 correctly contains both the loop initializer (i = 0 at 0x73) and the loop header JUMPDEST (0x77), with the while condition branch (LT + ISZERO + JUMPI at 0x7f) producing edges to Node 8 (body) and Node 10 (exit) — these are correct.",
    "Node 8 inlines multiple utility basic blocks (the checked-addition helper at 0x0166–0x018a) but its only outgoing edge is to the overflow panic (Node 9); the non-overflow fall-through to 0x0193 is unrepresented, making the loop non-cyclic in the graph.",
    "The 'number()' public getter is fully captured (Node 12 -> Node 13) including SLOAD and RETURN.",
    "Function dispatcher and ABI decoding for 'loop(uint256)' are fully represented."
  ]
}

NestedMapping (CFG: 97, Decompilation: 5)

Decompilation

{
  "score": 5,
  "summary": "Decompilation completely failed to recover any functional logic. All seven functions are reduced to trivial input-validation no-ops (require(arg0 == arg0)), with no storage reads, storage writes, mapping accesses, or return values present. The correct function arity is partially reflected in a few functions, but no actual program behavior is preserved.",
  "differences": [
    "setAllowance: missing nested mapping write allowances[owner][spender] = amount; replaced with a no-op identity check",
    "setGrid: missing nested mapping write grid[x][y] = value; replaced with a no-op identity check",
    "setDeepNested: missing triple-nested mapping write deepNested[a][b][c] = value; replaced with a no-op identity check",
    "getAllowance: missing storage read and return of allowances[owner][spender]; replaced with a no-op identity check with no return value",
    "All public getter functions (allowances, grid, deepNested) missing their storage read and return logic entirely",
    "All functions incorrectly marked as pure despite performing storage reads/writes in the original",
    "No storage slot calculations or mapping key hashing logic is represented anywhere in the decompiled output"
  ]
}

NestedLoop (CFG: 45, Decompilation: 5)

Decompilation

{
  "score": 5,
  "summary": "Decompilation almost entirely fails to capture the original logic. The nested loop structure is completely absent, and the storage write (number += 1) is never represented. The output contains meaningless or tautological require statements instead of the actual loop body and increment logic.",
  "differences": [
    "Nested loop structure (double for-loop iterating i and j from 0 to loops) is completely missing — no looping construct of any kind is present in the decompiled output",
    "Storage write 'number += 1' (executed loops*loops times) is entirely absent — the decompiled output never reads or writes the 'number' state variable in a mutating way",
    "Function mutability is incorrectly marked 'view' when the original modifies state",
    "'require(arg0 == arg0)' is a tautology that does not exist in the original and always passes",
    "'require(!0 < arg0)' introduces a spurious guard (requiring arg0 > 1) not present in the original — the original allows any value including 0",
    "'require(!number > (number + 0x01))' is always false due to overflow semantics, introducing a permanent revert condition that does not exist in the original"
  ]
}

CFG

{
  "score": 45,
  "summary": "The CFG captures function dispatch, loop condition entry points, and the outer loop exit, but is missing all loop back-edges and the inner loop exit path, leaving the cyclic structure of both nested loops unrepresented.",
  "missing_paths": [
    "Inner loop false-branch/exit path: when j >= loops (0x8c JUMPI false target 0xb1 is absent), so the inner loop has no represented exit edge",
    "Successful number += 1 completion path: after the overflow check passes at 0x01a3 JUMPI, the target 0x01ac (SSTORE + continue) is absent from the CFG entirely",
    "Inner loop back-edge: after j++ increment, execution should return to inner loop condition check at 0x84 (node 8), but no back-edge exists",
    "Outer loop increment path: after the inner loop exits (i++ at ~0xb1), this block is not represented",
    "Outer loop back-edge: after i++ increment, execution should return to outer loop condition check at 0x77 (node 7), but no back-edge exists"
  ],
  "extra_paths": [
    "Payable check at entry: CALLVALUE/ISZERO/JUMPI (node 0 -> 1) — compiler-enforced non-payable guard",
    "Calldatasize minimum length check (node 2 -> 15 revert) — compiler-added ABI dispatch guard",
    "Calldata input decoding/validation revert (node 4 -> 12, node 5 -> 6) — compiler-added parameter validation",
    "Integer overflow check for number += 1 (node 9 -> 10 revert via Panic 0x11) — compiler-added SafeMath equivalent",
    "number() public getter function path (nodes 13, 14) — compiler-generated accessor"
  ],
  "observations": [
    "Both loop condition checks are present: outer loop JUMPI at 0x7f (node 7) and inner loop JUMPI at 0x8c (node 8)",
    "The outer loop false-branch/exit is correctly captured: node 7 -> node 11 (0xbf JUMPDEST -> STOP)",
    "Node 8 has only one outgoing edge (to node 9, the loop body) — the false branch to 0xb1 is entirely absent",
    "Node 9 traces into the overflow helper but terminates at the overflow revert branch (node 10); the successful path at 0x01ac is not traced at all",
    "Without back-edges, the CFG represents at most one iteration of each loop — the iterative/cyclic nature of both for-loops is lost",
    "The function entry/exit and selector dispatch logic is fully and correctly represented"
  ]
}

TransientStorage (CFG: 100, Decompilation: 5)

Decompilation

{
  "score": 5,
  "summary": "Decompilation catastrophically failed. 5 of 6 functions are misrepresented as constant state variable declarations with nonsensical values. The transient storage pattern completely confused the decompiler, resulting in output that bears almost no resemblance to the original program's behavior.",
  "differences": [
    "incrementCounter() is missing entirely — decompiled as 'bytes public constant incrementCounter = ;' instead of a function that increments transient counter by 1",
    "lock() is missing entirely — decompiled as 'bytes public constant lock = ;' instead of a function that sets transient locked = true",
    "unlock() is missing entirely — decompiled as 'bytes public constant unlock = ;' instead of a function that sets transient locked = false",
    "getCounter() view function is missing — decompiled as 'uint256 public constant getCounter = 1;' (a hardcoded constant) instead of reading and returning the transient counter",
    "isLocked() view function is missing — decompiled as 'bool public constant isLocked = 0xBool(true);' (a hardcoded constant) instead of reading and returning transient locked",
    "setTempOwner() uses incorrect write logic: performs bitwise OR mask '(address(arg0) * 0x01) | (uint96(transient[0x01]))' instead of a plain transient store of the address",
    "setTempOwner() is incorrectly marked 'pure' despite writing to transient storage"
  ]
}

Mapping (CFG: 100, Decompilation: 42)

Decompilation

{
  "score": 42,
  "summary": "The decompiler identifies all six function signatures correctly but critically conflates three distinct storage mappings (slots 0, 1, 2) into two symbolic names (storage_map_a and storage_map_b), causing setter and getter pairs to reference different storage locations. setBalance writes to storage_map_a while the balances getter reads from storage_map_b, meaning written values would never be read back correctly. This same disconnect applies to setOwner/owners and register/registered. Additionally, setOwner uses a bitmask OR pattern instead of a simple assignment, which corrupts existing slot data on updates.",
  "differences": [
    "Three distinct storage mappings (balances at slot 0, owners at slot 1, registered at slot 2) are conflated into two symbolic names (storage_map_a, storage_map_b), causing all setter functions to write to one symbolic map while all getter functions read from another, breaking storage consistency",
    "setBalance writes to storage_map_a but the balances() getter reads from storage_map_b — they do not reference the same storage slot, so a written balance cannot be retrieved via the getter",
    "setOwner and register write to storage_map_a but the owners() and registered() getters read from storage_map_b — same cross-slot routing failure as setBalance/balances",
    "setOwner uses a bitmask OR operation '(address(arg1) * 0x01) | (uint96(storage_map_a[var_a]))' instead of a direct store; if the slot has a prior value, the lower 96 bits of the old address are OR-ed into the new address, corrupting it",
    "All state-mutating functions (setBalance, setOwner, register) are incorrectly marked payable; the originals are not payable and would revert on receiving ETH"
  ]
}

SimpleStorage (CFG: 100, Decompilation: 47)

Decompilation

{
  "score": 47,
  "summary": "setValue and initialize are functionally adequate, but setOwner destroys the packed initialized flag and corrupts lower address bits via incorrect masking, while reset fails to fully zero the packed owner/initialized storage slot — leaving bits 0-95 of the old address in storage.",
  "differences": [
    "setOwner does not preserve the initialized bool packed at bit 160 of slot 1: the expression `(address(arg0) * 0x01) | (uint96(owner))` produces a result whose bits 160+ are always zero, so every call to setOwner silently resets initialized to false",
    "setOwner incorrectly ORs `uint96(owner)` (the lower 96 bits of the old slot) into the new value, corrupting the lower 96 bits of the newly-set address whenever the previous owner was non-zero",
    "reset does not fully zero slot 1: the sequence `owner = 0 | uint96(owner)` only clears bits 96-255, leaving bits 0-95 of the old owner address in storage rather than setting the entire slot to zero"
  ]
}

NestedMappings (CFG: 70, Decompilation: 20)

Decompilation

{
  "score": 20,
  "summary": "Decompilation severely misrepresents the contract. The nested mapping structure is collapsed to a flat mapping, the allowance() function loses its second parameter and its return value entirely, and approve() drops msg.sender as the outer mapping key. The two core operations — nested mapping read and nested mapping write — are both functionally incorrect.",
  "differences": [
    "allowance(address,address) is decompiled with only one parameter instead of two; the spender argument is missing entirely",
    "allowance() produces no return value in the decompiled output; the nested mapping lookup result is never returned",
    "approve() writes to a flat single-level mapping (storage_map_a[spender] = amount) instead of the nested mapping (allowances[msg.sender][spender] = amount); msg.sender as the outer key is lost",
    "The nested mapping storage layout (double keccak256 slot derivation using both msg.sender and spender) is not represented; the decompiled storage type is mapping(bytes32 => bytes32) rather than a two-level nested mapping",
    "A third function (Unresolved_55b6ed5c) appears with no correspondence to any function in the original source, suggesting a selector mis-dispatch or artifact"
  ]
}

SimpleLoop (CFG: 70, Decompilation: 5)

Decompilation

{
  "score": 5,
  "summary": "Decompilation fails to capture the fundamental behavior of the loop function. The for-loop body, state modification, and mutability are all absent or incorrect. The output contains nonsensical require statements that have no correspondence to the original logic.",
  "differences": [
    "Loop structure is entirely absent — the for loop iterating 'loops' times is not represented at all",
    "State modification is missing — 'number++' (the core side effect) is never emitted; the decompiled function never writes to storage",
    "Function mutability is wrong — marked 'view' when the original modifies state",
    "require(arg0 == arg0) is a tautology with no basis in the original code",
    "require(!0 < arg0) incorrectly gates on arg0 > 1; the original loop runs for any arg0 including 0 and 1",
    "require(number - 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff) has no correspondence to the original logic and would underflow for virtually all values of 'number'"
  ]
}

Events (CFG: 60, Decompilation: 42)

Decompilation

{
  "score": 42,
  "summary": "Decompilation captures 2 of 7 functions correctly (emitDeposit, emitWithdrawal), partially captures emitMultiple, and fails on emitTransfer, emitApproval, emitLog, and emitLogBytes. The Approval event is not declared at all, and calldata functions (emitLog, emitLogBytes) have their argument types wrong and malformed emissions.",
  "differences": [
    "emitTransfer is not captured: the two single-arg address functions (Unresolved_5687f2b8, Unresolved_23de6651) only perform address validation and emit nothing, losing the Transfer event emission entirely",
    "emitApproval is completely absent: the Approval event is not declared in the decompiled output and no function emits it",
    "emitLog maps to Unresolved_2e3c2a4d which takes uint256 instead of string calldata, and the emit uses raw memory pointer arithmetic rather than the string argument",
    "emitLogBytes maps to Unresolved_d0062fbb which takes uint256 instead of bytes calldata, and the emit uses raw memory pointer arithmetic rather than the bytes argument",
    "emitMultiple partially captured: Deposit and Transfer(address(0), account, amount) are correct, but the Log emission is malformed (raw memory pointers and hex literal instead of the string 'Multiple events emitted')",
    "Spurious size-bound require statements added (e.g. require(!arg0 > 0xffffffffffffffff)) with no correspondence in the original"
  ]
}

CFG

{
  "score": 60,
  "summary": "The CFG captures the full function dispatch structure and revert/validation paths, but truncates the success execution paths for 5 of 7 functions before reaching event emission. Only emitLog and emitLogBytes are traced completely to their LOG1 instructions. The source has no in-function conditionals (all functions are purely sequential), so the missing portions are the happy-path executions for emitTransfer, emitApproval, emitDeposit, emitWithdrawal, and emitMultiple.",
  "missing_paths": [
    "emitApproval (0x5687f2b8): Success path after address validation (node 6) has no outgoing edge — the Approval event emission and STOP are not reachable in the CFG",
    "emitWithdrawal (0xe3a379c5): 2-arg parser subroutine (0x0506) success branch at 0x051c is not mapped to any node — Withdrawal event emission is absent",
    "emitDeposit (0x28ba84ca): Node 30 has no outgoing edges — parser success continuation and Deposit event emission are unreachable in the CFG",
    "emitTransfer (0x23de6651): Node 28 has no outgoing edges — 3-arg parser success path and Transfer event emission are unreachable in the CFG",
    "emitMultiple (0xfc4ae4ba): Node 25 has no outgoing edges — all three sequential event emissions (Deposit, Transfer, Log) are absent from the CFG"
  ],
  "extra_paths": [
    "CALLVALUE check at entry (node 0→1): compiler-added payability guard not present in source",
    "Multiple CALLDATASIZE and SLT bounds checks throughout argument parsers: compiler-generated input validation",
    "Nested bounds checks for dynamic-type (string/bytes) length fields: compiler overflow guards",
    "Memory overflow guard in bytes copy helper (node 16/38): compiler-added safety check"
  ],
  "observations": [
    "The 7-way function selector dispatch tree is fully and correctly represented — all source functions are reachable from the entry node",
    "String/bytes argument functions (emitLog, emitLogBytes) are the only two functions fully traced to their event emission (LOG1) and STOP",
    "All five fixed-size argument functions share the same gap: the shared 2-arg parser at 0x0506 has a success continuation at 0x051c that is not present as any node in the graph, causing all callers (nodes 22, 25, 30) to appear as dead ends",
    "The 3-arg parser at 0x04b6 similarly has a gap: node 28 (emitTransfer) and node 6's success edge (emitApproval) point to unmapped bytecode offsets",
    "The source contract contains no in-function conditional logic (no if/else, no loops, no try/catch) — the only source-level branching is the function dispatch, which is fully captured"
  ]
}

WETH9 (CFG: 85, Decompilation: 45)

Decompilation

{
  "score": 45,
  "summary": "Basic ETH wrapping operations (deposit, withdraw, totalSupply) are mostly preserved, but the ERC20 allowance mechanism is fundamentally broken. Storage maps are confused between balanceOf and allowance, the nested allowance mapping is not captured, control flow for allowance checks in transfer/transferFrom is inverted, and allowance deduction is missing. Multiple functions contain unreachable dead code indicating failed control flow reconstruction.",
  "differences": [
    "allowance logic is inverted in transfer and transferFrom: the decompiled code requires allowance == uint(-1) on the first executed branch (when src == msg.sender), whereas the original skips the allowance check entirely when src == msg.sender",
    "allowance deduction is missing in transferFrom: the original decrements allowance[src][msg.sender] when src != msg.sender and allowance != uint(-1), but no deduction appears in any executed path of the decompiled output",
    "approve writes to storage_map_c keyed only by spender address instead of the nested allowance[msg.sender][spender] mapping, corrupting the wrong storage slot",
    "balanceOf and allowance view functions both read from storage_map_d, but deposit/withdraw/transfer write to storage_map_c — mismatched storage slots mean reads and writes operate on different data",
    "allowance() view function ignores the first argument (owner address), assigning arg1 over arg0 and returning storage_map_d[arg1], so it cannot return the correct allowance for any owner/spender pair",
    "the nested allowance[src][msg.sender] double-key mapping is not represented anywhere — all storage accesses use single-key lookups, making the allowance subsystem structurally incorrect",
    "transfer and transferFrom contain unreachable dead code after early return statements, indicating the decompiler failed to reconstruct the conditional branching for the allowance check paths"
  ]
}

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