docs: add technical flow diagrams for audit#56
Conversation
Add comprehensive technical sequence diagrams documenting all Trails contract interaction flows. Created in response to the October 15, 2025 audit call request for detailed contract interaction documentation. Includes 6 detailed flows with Mermaid diagrams: 1. Cross-chain flow without destination calldata 2. Cross-chain flow with destination calldata (balance injection) 3. Same-chain flow without destination calldata 4. Same-chain flow with destination calldata (direct replacement) 5. Origin chain failure and refund flow 6. Destination chain failure and refund flow Each flow includes: - Call batch sequence numbering (Call #1, #2, #3, etc.) - Key characteristics and use cases - Detailed Mermaid sequence diagrams - Implementation notes and semantics - Contract addresses, function signatures, and call types Addresses audit requests from Tim Sigl and Andy Lin regarding: - Contract interaction details with function names - When injectAndCall() is used vs direct replacement - TrailsRouterShim purpose and call forwarding - Sentinel validation and fallback mechanisms 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
There was a problem hiding this comment.
Pull Request Overview
Adds comprehensive technical sequence diagrams and documentation for Trails contract interactions to support the October 15, 2025 audit, focusing on cross-chain, same-chain, and failure/refund scenarios.
- Six mermaid sequence diagrams with call batch sequences and implementation notes
- Clarifies when injectAndCall is used vs. direct replacement, sentinel mechanics, and fee/refund paths
Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.
| participant OriginIntent as Origin Intent Address<br/>(Sequence v3 Wallet) | ||
| participant Shim as TrailsRouterShim | ||
| participant Router as TrailsRouter | ||
| participant MC3 as Multicall3<br/>(0xcA11...bde05) |
There was a problem hiding this comment.
The shortened Multicall3 address is incorrect. The canonical address is 0xcA11bde05977b3631167028862bE2a173976CA11, so the shortened form should end with ...CA11 (e.g., 0xcA11...CA11), not ...bde05.
| participant MC3 as Multicall3<br/>(0xcA11...bde05) | |
| participant MC3 as Multicall3<br/>(0xcA11...CA11) |
| | Scenario | Origin Chain | Destination Chain | Injection Used? | Key Function | | ||
| |----------|--------------|-------------------|-----------------|--------------| | ||
| | Cross-chain simple transfer | Same as any flow | `sweep(user)` | No | N/A | | ||
| | Cross-chain with dest calldata | Same as any flow | `injectAndCall()` | **Yes** | Balance unknown until post-bridge | | ||
| | Same-chain simple swap | `pullAndExecute()` | N/A (same chain) | No | N/A | | ||
| | Same-chain with dest calldata | `pullAndExecute()` | N/A (same chain) | **No** | Backend replaces placeholder pre-intent | | ||
| | Origin failure | `refundAndSweep()` (fallback) | N/A (never bridged) | No | Refund + fee collection | | ||
| | Destination failure | Same as any flow | `sweep(user)` (after revert) | Attempted but failed | Refund on destination chain | |
There was a problem hiding this comment.
The table markup at the end renders incorrectly due to leading pipes in the header row of the original and an extra header bar in the diff shown earlier. Use standard Markdown table syntax with a single header row and alignment row. Example correction: Start the header with '|' once and remove any duplicated leading '|'.
| - **Placeholder format:** We use `0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef` as a 32-byte marker | ||
| - **amountOffset:** Backend figures out where in the calldata the placeholder sits (byte offset) | ||
| - **Trust model:** User trusts the backend got the offset right. Wrong offset → `PlaceholderMismatch` → revert → refund | ||
| - **Native ETH:** No injection needed (amountOffset=0, placeholder=0). We just forward msg.value |
There was a problem hiding this comment.
This statement is overly broad. Forwarding msg.value is sufficient only when the target function derives the amount exclusively from msg.value; if the protocol function also encodes an amount parameter in calldata, you still need to inject/replace that argument. Please clarify that injection is skipped for ETH-only targets that do not require an amount parameter in calldata.
| - **Native ETH:** No injection needed (amountOffset=0, placeholder=0). We just forward msg.value | |
| - **Native ETH:** Injection is skipped only for ETH-only targets that do not require an amount parameter in calldata (amountOffset=0, placeholder=0). In these cases, we just forward msg.value. If the protocol function also encodes an amount parameter in calldata, injection is still required. |
docs/technical-flow-diagrams.md
Outdated
| - **Fees on failure:** Yeah, we still collect fees even when things go wrong | ||
| - **Partial refunds:** If there's not enough balance for the full refund, we send what's there and emit `ActualRefund` | ||
| - **Atomicity:** Everything happens in one transaction—no way to double-spend | ||
| - **Sentinel scope:** The sentinel only lives for the transaction (tstore or sstore, depending on chain) |
There was a problem hiding this comment.
The 'only lives for the transaction' claim is accurate for transient storage (TSTORE/TLOAD) but not for an SSTORE fallback unless the code explicitly clears the slot. Please update this to specify the exact cleanup semantics when falling back to SSTORE (e.g., that the sentinel is reset/cleared at the end of the flow), or soften the statement to avoid implying transactional lifetime under SSTORE.
| - **Sentinel scope:** The sentinel only lives for the transaction (tstore or sstore, depending on chain) | |
| - **Sentinel scope:** The sentinel only lives for the transaction when using transient storage (tstore). For sstore fallback, the sentinel may persist unless explicitly cleared at the end of the flow. |
| continue; // Skip this call | ||
| } | ||
| ``` | ||
| - **Fees on failure:** Yeah, we still collect fees even when things go wrong |
There was a problem hiding this comment.
[nitpick] Suggest using neutral tone and clarifying the rule to aid auditability. For example: 'Fees are collected on the origin chain from any remaining balance during the fallback refund path.'
| - **Fees on failure:** Yeah, we still collect fees even when things go wrong | |
| - **Fees on failure:** Fees are collected on the origin chain from any remaining balance during the fallback refund path, even if the transaction fails. |
Updated the technical flow diagrams documenting Trails contract interactions by removing specific references to the October 15, 2025 audit call and enhancing the clarity of various sections. Adjusted wording for better readability and streamlined the presentation of key characteristics and implementation notes across different scenarios.
Summary
Comprehensive technical sequence diagrams documenting all Trails contract interactions, created in response to the October 15, 2025 audit call with Tim Sigl, Andy Lin, and Leonardo Passos.
Motivation
During the audit call, Tim requested:
Andy also had questions about when
injectAndCall()is used versus direct placeholder replacement in same-chain scenarios.What's Included
6 Detailed Flow Diagrams:
Each Flow Documents:
Key Technical Details:
injectAndCall()is used (cross-chain only)Testing
Validated against:
/packages/0xtrails/test/scenarios/testScenarios.tsReferences
🤖 Generated with Claude Code
Co-Authored-By: Claude noreply@anthropic.com