1inch Fusion V1 Settlement Yul Calldata Corruption / Resolver-Substitution Drain (2025-03-05)

  • Name: 1inch Fusion V1 Settlement Yul calldata corruption / resolver-substitution drain
  • Date: 2025-03-05
  • Protocol: 1inch Fusion V1 / third-party resolvers
  • Chain: Ethereum
  • Category: settlement-context corruption / obsolete-protocol callback path / resolver-substitution drain
  • Loss: about $5 million temporarily drained from market maker TrustedVolumes; most funds were later returned and the attacker kept a negotiated bounty
  • Summary: An attacker exploited a Yul calldata-corruption bug in 1inch’s obsolete Fusion V1 Settlement contract at 0xa88800cd213da5ae406ce248380802bd53b47647. The contract was supposed to append a trusted dynamic suffix containing the current resolver address before routing through fillOrderInteraction() and ultimately into a resolver’s resolveOrders() callback. Instead, _settleOrder() let an attacker set a maliciously huge interactionLength that underflowed the suffix write offset. By padding calldata and planting a fake interaction/suffix blob, the attacker made Settlement call TrustedVolumes’ resolver at 0xb02f39e382c90160eb816de5e0e428ac771d77b5 as if it were the active resolver for the fill. Because the resolver only checked msg.sender == settlement and resolver == owner, it honored attacker-chosen token/amount outputs and funded impossible attacker-to-attacker swaps with its own USDC and USDT balances.
  • Trigger / entry point:
    • The vulnerable entry path was Settlement.settleOrders(bytes) in Fusion V1, which called _settleOrder(data, msg.sender, 0, new bytes(0)).
    • _settleOrder() copied calldata into memory, extended the encoded interaction payload, and appended a dynamic suffix that downstream logic trusted to carry the authenticated resolver, fee, token, and token-amount context.
    • In the Yul implementation, the suffix write location was computed as ptr + interactionOffset + interactionLength without bounding interactionLength as an unsigned forward offset.
    • The attacker supplied interactionLength = 0xffff...fe00 (effectively -512 modulo 2^256), so the suffix writes landed earlier in memory than intended.
    • A planted fake interaction block was then reinterpreted by the dynamic-suffix decoder as trusted settlement context, causing finalization to target the victim resolver instead of the attacker-controlled resolver flow the protocol thought it was executing.

Root cause

  • Fusion V1 treated a freshly assembled memory blob as an authenticated settlement envelope, but the code that assembled that blob used attacker-controlled length data in pointer arithmetic without proving the write stayed in-bounds.
  • The bug was not that TrustedVolumes’ resolver had no access control. It did have access control, but it trusted the Settlement contract to supply a truthful resolver identity and truthful token/amount settlement context.
  • Once _settleOrder() let the attacker corrupt that envelope, all later checks validated the wrong reality: Settlement was still the caller, the resolver field still matched the victim’s owner check, and the callback still looked like a legitimate settlement finalization.
  • In short: the exploit turned protocol-authored context into attacker-authored context before the resolver consumed it.

Exploit path

  1. The attacker funded deployer 0xa7264a43a57ca17012148c46adbc15a5f951766e, used exploit contract 0x019bfc71d43c3492926d4a9a6c781f36706970c9, and prepared a receiver address 0xbbb587e59251d219a7a05ce989ec1969c01522c0.
  2. They crafted apparently normal Fusion V1 settlement calls through Settlement at 0xa88800cd213da5ae406ce248380802bd53b47647, but set interactionLength to a huge wrapped value (0xffff...fe00) and padded calldata so the eventual suffix write underflowed into attacker-chosen memory.
  3. They embedded a fake interaction payload that, once the real suffix was miswritten and the calldata was later decoded, caused fillOrderInteraction() to treat TrustedVolumes’ resolver 0xb02f39e382c90160eb816de5e0e428ac771d77b5 as the authenticated resolver for the order.
  4. The attacker arranged attacker-to-attacker maker/taker orders with absurd economics, such as effectively swapping a few wei of one token for hundreds of thousands or millions of units of another token.
  5. Finalization reached IResolver(target).resolveOrders(...), where the target was the victim resolver and msg.sender was the legitimate Settlement contract. The resolver’s checks therefore passed: the caller really was settlement, and the supplied resolver/owner field had also been made to match the victim.
  6. The resolver then decoded tokensAndAmounts and transferred real balances such as USDC or USDT back to Settlement, which completed the fake fills and routed proceeds to attacker-controlled addresses.
  7. The attacker repeated the drain across multiple transactions, then sent an onchain message asking for a bounty. Negotiations succeeded the same night and most funds were returned hours later.

What the attacker needed to be true

  • A resolver still had to support the obsolete Fusion V1 settlement path instead of fully retiring it or filtering it away.
  • The resolver had to trust Settlement as the sole caller and trust the settlement-supplied resolver identity / token-amount envelope rather than independently reconstructing the intended swap context.
  • Settlement had to keep the vulnerable Yul pointer arithmetic live on mainnet for backward compatibility.
  • The victim resolver had to hold meaningful balances available for settlement, so forged fills could cash out directly from its inventory.
  • Monitoring and automation had to be slow enough that multiple drain transactions could land before the resolver was isolated.

Technical anatomy

  • The decisive primitive was not a fake pool, an arbitrary external call, or a missing whitelist. It was corruption of the trusted suffix that downstream resolver logic was allowed to believe.
  • settleOrders() seeded _settleOrder() with resolver = msg.sender, so under normal operation only the real resolver should have been able to drive the settlement path and later finalize its own orders.
  • The Yul code appended dynamic suffix fields at offset := ptr + interactionOffset + interactionLength. Because interactionLength was attacker-controlled and not range-checked as a forward-only length, the attacker could move offset backward.
  • The exploit used padding plus a fake suffix-shaped interaction region so the later decode path consumed attacker-laid-out fields as if they were the authentic protocol-appended suffix.
  • TrustedVolumes’ resolver resembled 1inch’s own example resolver design: require msg.sender == _settlement, require resolver == _owner, optionally execute arbitrary encoded target calls, then transfer each token/amount entry back to settlement. That design was safe only if settlement context could not be forged.
  • This is the cleaner positive-design handoff to settlement envelope truth as a single security object: the caller could stay legitimate while the protocol-authored settlement bytes silently stopped meaning the route / resolver / payout object downstream code thought it was settling.
  • This is also why obsolete protocol paths are dangerous even when end users have moved on. No user swap UI had to expose Fusion V1 anymore; it was enough that funded resolvers still accepted the legacy settlement path.

Three settlement-envelope promotions this incident collapsed

  1. A Fusion V1 settlement envelope still existed and still reached funded resolvers

    • Settlement.settleOrders() still built a dynamic interaction-plus-suffix blob, and third-party resolvers such as TrustedVolumes still honored that legacy path against real inventory.
    • This is only the reachability question: the obsolete serializer and callback lane were still live enough to matter.
  2. That blob was still allowed to count as canonical protocol-authored resolver truth for this exact fill

    • The Yul suffix constructor was supposed to pin resolver = msg.sender plus the downstream token / amount context, but attacker-chosen interactionLength moved the trusted write backward and let a fake interaction region be decoded as the authenticated suffix.
    • This is the semantic-authenticity question: the bytes TrustedVolumes treated as 1inch-authored settlement truth were no longer the bytes 1inch thought it had authored.
  3. That accepted envelope still carried direct inventory-spending authority

    • Once the counterfeit suffix survived, TrustedVolumes’ resolver was still allowed to honor attacker-shaped tokensAndAmounts and transfer real USDC / USDT back through settlement without a second route-level semantic cross-check.
    • This is the authority-budget question: even if settlement stayed the real caller, why did one accepted legacy envelope still deserve this much hot-balance authority?

Healthy incident review keeps those promotions separate instead of flattening the loss into Yul memory corruption or resolver bug.

Pattern tags

  • trusted-execution-envelope-corruption
  • The caller stayed legitimate while the protocol-authored settlement envelope became counterfeit first: Settlement was still the true caller, but the resolver / token / amount context it supplied had already been rewritten by attacker-shaped pointer arithmetic.
  • The obsolete-path lesson matters here: funded resolvers were still willing to trust Fusion V1’s deprecated settlement serializer even after the product surface had effectively moved on.
  • This was a clean producer lied to a strict consumer incident. TrustedVolumes’ resolver checks were locally correct for the envelope it received; the exploit worked because the upstream envelope constructor could be induced to mint fake truth.

Why the exploit worked

  • Fusion V1’s Settlement contract was effectively a signer of resolver-side settlement truth. Once attacker-controlled interactionLength could move the dynamic suffix write backwards, the contract stopped appending authenticated resolver context where downstream code thought it lived.
  • TrustedVolumes’ resolver did not need to be broadly permissionless to be exploitable. It only needed to trust that msg.sender == Settlement plus resolver == owner meant the surrounding token / amount / route object was still authentic.
  • The exploit therefore preserved the uncomfortable version of access control failure: downstream checks can all be right while the upstream contract quietly changes which bytes those checks are validating.
  • Backward-compatibility residue amplified the impact. No mainstream user flow needed to expose Fusion V1 anymore; it was enough that a funded resolver still honored the legacy settlement path and kept inventory available behind it.
  • Blast radius stayed large because one forged settlement envelope had direct line-of-sight to real resolver inventory. There was no second semantic cross-check that re-derived which resolver is settling which tokens for which route before balances were transferred out.

Detection and audit pivots

  • Reconstruct the exact execution envelope that resolvers are supposed to trust: which bytes are protocol-authored suffix fields, which are user-authored interaction bytes, and what proves those two regions cannot overlap or alias under wrapped arithmetic.
  • Treat every ptr + offset + length write in settlement Yul as an authenticity boundary, not a mere memory-safety detail. The important question is not only can this underflow? but which trusted field becomes attacker-authored if it does?
  • Property-test deprecated settlement paths with maximum-length, wrapped-length, and fake-suffix-shaped payloads. A compatibility serializer that still reaches funded resolvers is still custody code.
  • Audit downstream resolvers for semantic cross-checks rather than caller-only checks. If Settlement says this is your resolver context, what impossible token / amount / route combinations could the resolver cheaply reject even when the caller is legitimate?
  • Inventory legacy integrations that still honor obsolete settlement entrypoints and hold meaningful balances. Product deprecation does not reduce exploitability if resolver inventory and callback trust stay live.

Representative transactions and addresses

  • Settlement V1: 0xa88800cd213da5ae406ce248380802bd53b47647
  • Victim resolver / TrustedVolumes: 0xb02f39e382c90160eb816de5e0e428ac771d77b5
  • Attacker deployer: 0xa7264a43a57ca17012148c46adbc15a5f951766e
  • Exploit contract: 0x019bfc71d43c3492926d4a9a6c781f36706970c9
  • Funds receiver: 0xbbb587e59251d219a7a05ce989ec1969c01522c0
  • Attack txs highlighted in Decurity’s postmortem: 0x04975648e0db631b0620759ca934861830472678dae82b4bed493f1e1e3ed03a, 0xb5c94efa0c8fd8f5c8cc2826e374a99620b01061d395b59b8f45dddc9fce1c60, 0xb16bbf03d324b66685c94d62dbe31c739ee23c114b3915d169c74cd7c98eec8c, 0xc69b4c8029c70ae468e92af31120ac6b01bb89c6e35d34818413e9942aedebb6, 0xefcb740bf9ec17ed99839ffcc05393fae5ec2d44149aee91ba119f48bc20a1ef, 0x74bc4d5dc7f8da468788da6087bb9f73465966ab5b8cf9cf1053d98e78a9bf96, 0x3947e5a4d98104e313e08ee321673e1183db3d6ff8b7207f3eabb36f71436c1d, 0x9ce5187c7160f531189e4765f21af5975dc2a62d961fb61ae09866d082918256, 0xb0688eb1f46c28f36d7397366146fced23d3f8da7e08b760a5f612ce134ee9d2, 0x62734ce80311e64630a009dd101a967ea0a9c012fabbfce8eac90f0f4ca090d6
  • Onchain bounty-request message tx: 0xf70d0aa5a3692540fc484945e9f50b672e4b752ff366a03d92cd5e1a1b459301
  • Indicator-of-compromise function selector for affected resolvers: 0x1944799f (resolveOrders(address,bytes,bytes))

Response / mitigation

  • 1inch publicly stated that the issue affected only obsolete Fusion V1 resolver integrations, not end-user funds executing through Fusion V2.
  • The team redeployed the affected settlement contract path as a precaution and coordinated with integrators / resolvers to retire or harden lingering V1 support.
  • TrustedVolumes negotiated with the attacker the same evening; the attacker returned nearly all funds within hours and retained only a bounty-sized remainder.
  • The incident also triggered postmortem reflection from Decurity because the vulnerable Yul refactor had been audited multiple times but survived in the deprecated deployed version.

Why this incident matters

  • 1inch’s Fusion V1 incident is a strong reminder that a protocol can lose funds even when the immediate victim contract’s access checks are locally correct.
  • The real security boundary was the integrity of the protocol-authored settlement envelope. Once that envelope was corruptible, every downstream only settlement or resolver == owner check became theater.
  • It also preserves a useful obsolete-code lesson: backward-compatibility deployment residue can remain a live exploit surface long after the product path is considered dead.

Failure-prevention checklist

  • Treat settlement-envelope construction as a first-class security boundary. Any serializer that appends resolver identity, token list, fee fields, payout meaning, or callback rights should prove non-overlap, forward-only writes, and exact region boundaries before downstream code is allowed to trust the result.
  • Eliminate wrapped pointer arithmetic in hand-written Yul wherever possible. If low-level assembly stays, guard every offset := ptr + userOffset + userLength style calculation with explicit range checks and invariant assertions about where trusted suffix fields may land.
  • Keep user-authored interaction bytes and protocol-authored suffix bytes in structurally separate regions or typed objects. Designs that rely on append trusted fields after untrusted blob need stronger proofs than we intended the pointer to move forward.
  • Make downstream resolvers cross-check semantics, not only caller identity. Even if msg.sender is the canonical settlement contract, resolvers should still be able to reject impossible token / amount / route combinations or unexpected resolver identities for that order family.
  • Fully retire obsolete settlement paths once the ecosystem migrates. Deprecated code that still reaches funded resolver inventory is still live custody plumbing.
  • Bound funded resolver inventory behind narrower per-path allowances, inventory caps, or route-specific balances where feasible. One forged settlement envelope should not expose the resolver’s entire hot balance sheet.
  • Property-test legacy and assembly-heavy settlement code with max-length, wrapped-length, overlapping-region, and suffix-alias payloads. The important failure is often not memory corruption exists but trusted context becomes attacker-authored.
  • Maintain an explicit inventory of which integrators still accept each settlement version, which contracts remain funded, and which feature flags or kill switches can sever deprecated flows quickly.

Audit prompts

  • Which exact bytes does the downstream resolver treat as protocol-authored truth, and what proves those bytes cannot be influenced by user-controlled length or offset arithmetic earlier in the path?
  • Can any user-controlled length, offset, or pointer arithmetic cause trusted suffix fields to overlap, alias, or underflow into attacker-prepared memory?
  • Are resolvers validating only caller == settlement, or do they also sanity-check that the resolver identity, token set, amount vector, and route semantics are plausible for the order being finalized?
  • What obsolete settlement versions are still deployed, still reachable, and still trusted by funded third-party resolvers even if the main product flow no longer uses them?
  • If a serializer bug corrupts settlement context, what second line of defense exists before resolver balances move: semantic cross-checks, per-route inventory isolation, or version-gated disablement?
  • Which assembly blocks effectively mint authenticated protocol context out of mixed trusted and untrusted bytes, and how are those blocks property-tested against wrapped arithmetic and region-overlap cases?
  • During incident response, can operators identify every resolver still honoring the vulnerable path and cut them off quickly, or is the real fallback a race to contact integrators one by one?

Lessons that should already be standard

  • Legitimate caller identity is not enough. A contract can be the right caller while carrying the wrong settlement truth, and downstream checks will still bless the fake story if they validate the corrupted envelope.
  • Serializer code is security-critical logic, not mere plumbing. Pointer arithmetic that writes protocol-authored fields is part of the authorization boundary.
  • Deprecated routes do not become harmless just because the UI moved on. If balances, trust, and code paths remain live, the old path is still production.
  • Strict consumers can still be exploitable when the producer lies first. Resolver-side access control only helps if the upstream system cannot counterfeit the fields being checked.
  • Assembly optimizations deserve extra skepticism where they combine user-controlled lengths with authenticated context construction. Gas savings are cheap compared with the cost of silently rewriting trust boundaries.
  • Inventory isolation matters even for professional market makers. If one compatibility path can settle against a broad shared hot balance, a single context-forgery bug can immediately monetize that whole inventory.
  • The clean design question is blunt: what exact object is the resolver being asked to trust, who authored it, and what proves no attacker-shaped bytes could have become part of that object first?

Internal linkages

Control surface