Prisma Finance MigrateTroveZap onFlashLoan() Spoofed-Migration Collateral Theft (2024-03-28)
- Name: Prisma Finance
MigrateTroveZaponFlashLoan()spoofed-migration collateral theft - Date: 2024-03-28
- Protocol: Prisma Finance
- Chain: Ethereum
- Category: migration trust-boundary failure / spoofed migration context / unvalidated flash-loan callback data / delegated-position collateral theft
- Loss: about
3,479.24 ETHtotal, roughly$12Mat the time; the primary exploiter accounted for about3,257 ETH - Summary: Prisma’s migration helper
MigrateTroveZap(0xcc7218100da61441905e0c327749972e3cbee9ee) was supposed to atomically move a user’s trove from oneTroveManagerto another by taking anmkUSDflash loan, closing the old trove, and reopening an equivalent one in the new manager. The fatal mistake was thatonFlashLoan()trusted the callback data as if it could only have been produced by Prisma’s ownmigrateTrove()flow. In reality, any user could callmkUSD.flashLoan()directly, setreceiver = MigrateTroveZap, and feed attacker-chosen migration parameters into the callback. Because affected users had grantedsetDelegateApproval(MigrateTroveZap, true)onBorrowerOperations, the zap could close victim troves on their behalf, reopen them inside the same manager with much less collateral but almost the same debt, leave the collateral difference stranded inside the zap, and then reuse a second spoofed migration to move that leftoverwstETHinto an attacker-controlled trove and cash it out. - Trigger / entry point:
- The exploiter did not call Prisma’s intended
migrateTrove()entry point. Instead, they calledmkUSD.flashLoan()directly and pointed the receiver atMigrateTroveZap. - That direct flash-loan path triggered
MigrateTroveZap.onFlashLoan()with attacker-supplieddata. onFlashLoan()did not authenticate that the callback came from a legitimate zap-initiated migration nor that the encoded migration plan matched the victim trove’s real collateral state.- The exploit only worked against trove owners who had previously approved the zap as a delegate on
BorrowerOperations.
- The exploiter did not call Prisma’s intended
- Root cause:
- Prisma treated the flash-loan callback context as trusted migration state instead of hostile user input.
MigrateTroveZapassumedonFlashLoan()would only ever be reached from the protocol’s own migration flow, even though the underlying debt-token flash loan was publicly callable.- The helper allowed a mismatch between the collateral removed from the old trove and the collateral used to reopen the new one, so excess collateral could remain inside the zap contract.
- Because delegated trove permissions were already in place, spoofed migration instructions became a direct way to reconfigure and strip victim positions.
- Exploit path:
- The main exploiter funded EOA
0x7e39e3b3ff7adef2613d5cc49558eab74b9a4202and deployed exploit contract0xd996073019c74b2fb94ead236e32032405bc027c. - In the first exploit transaction
0x00c503b595946bccaea3d58025b5f9b3726177bbdc9674e634244135282116c7, the attacker calledmkUSD.flashLoan()for roughly1,442,100 mkUSDand setMigrateTroveZapas the receiver. - The crafted callback data targeted victim trove owner
0x56a201b872b50bbdee0021ed4d1bb36359d291edinwstEthTroveManagerV2(0x1cc79f3f47bfc060b6f761fcd1afc6d399a968b6). That trove held about1,745.08 wstETHcollateral against about1,442,100 mkUSDdebt. - Using the delegate approval the victim had previously granted,
MigrateTroveZapclosed the victim trove and reopened it inside the same trove manager with only about463.18 wstETHcollateral and roughly the same debt. - The missing collateral — about
1,281.90 wstETHin the first example — remained inMigrateTroveZaprather than being returned to the victim. - The attacker then opened a tiny trove of their own, sourced
1 wstETHof additional flash liquidity, and executed a second spoofed migration so the zap reopened the attacker’s trove using the leftoverwstETHnow sitting inside the contract. - The attacker closed that newly collateralized trove, withdrew the captured
wstETH, and repaid the flash loans. - The same pattern was repeated across multiple approved victim troves until Prisma paused the protocol. Public postmortem data lists 11 addresses hit by the main exploiter, 11 more by a copycat exploiter, and 3 by a third exploiter.
- The main exploiter funded EOA
- What the attacker needed to be true:
mkUSD.flashLoan()had to be callable by arbitrary users with an arbitrary receiver and arbitrary callback data.MigrateTroveZap.onFlashLoan()had to trust that callback data as an authentic migration plan.- Victims had to have live delegate approval set for
MigrateTroveZapon Prisma’sBorrowerOperationscontract. - The migration helper had to allow the old trove’s full collateral to be withdrawn while reopening the replacement trove with less collateral, leaving a stealable remainder.
- The attacker needed a way to convert collateral marooned in the zap into an attacker-owned trove position before the protocol paused.
- Technical anatomy worth remembering:
- This was not just
missing access controlon a helper. The sharper failure is that migration-context authenticity lived in a public flash-loan callback with no proof that the callback represented a zap-authored migration. - The user approval surface mattered as much as the helper bug.
setDelegateApproval()turned a migration convenience contract into a standing authority over live troves, so once callback authenticity failed the attacker inherited real position-management rights. - The theft did not require inventing debt or breaking an oracle. It repurposed legitimate trove-management permissions to move a victim from
healthy collateralized positiontosame debt, much thinner collateral, then captured the difference. - Prisma is a strong reminder that migration code can fail even when the legacy source contracts are correct. If the helper does not authenticate who authored the migration plan, the migration wrapper itself becomes the exploit surface.
- This was not just
- Representative transactions and artifacts:
- Main exploiter EOA:
0x7e39e3b3ff7adef2613d5cc49558eab74b9a4202 - Main exploiter contract:
0xd996073019c74b2fb94ead236e32032405bc027c - Copycat exploiter EOAs:
0x7Fe83f45e0f53651b3ED9650d2a2C67D8855e385,0x7C9FC6E2B908e858F30c5c71a20273315Efd5cf8 - Vulnerable
MigrateTroveZap(mkUSD):0xcc7218100da61441905e0c327749972e3cbee9ee - Vulnerable
MigrateTroveZap(ULTRA):0xC3eAf094e2586965244aB6534f6Dc69c6C16b5D5 BorrowerOperations(LST):0x72c590349535AD52e6953744cb2A36B409542719wstEthTroveManagerV2:0x1cc79f3f47bfc060b6f761fcd1afc6d399a968b6- Main exploit tx:
0x00c503b595946bccaea3d58025b5f9b3726177bbdc9674e634244135282116c7 - Exploit contract deployment tx:
0x1319a80286b9c229a2d7566e21d7476b792956e4b59ee00d26449b6b77f2bcd0 - Funding tx highlighted in Prisma’s timeline:
0xe55bd15d2bd17c0a97356f612480887df91bc483f6affae8edd06a6d08b60f53 - Emergency pause tx:
0x1a35836eec322195046387fd0aa43f945aa484c062b8b1442166db6860d733ae
- Main exploiter EOA:
- Response / mitigation:
- Prisma publicly disclosed the incident, paused protocol operations, and told vault owners to revoke delegate approval for both zap contracts.
- The immediate operational fix was to shut down the zap-authority surface by revoking approvals and disabling new zap activity.
- The design-level fix is stricter: a migration helper must authenticate callback provenance and bind the migration plan to an internally initiated state transition, not to calldata echoed back from a public flash-loan primitive.
- The healthier long-term shape is one single-use migration-closeout object that binds
who initiated this migration,which trove may be closed,which destination manager may receive the position,how much debt and collateral may move, andwhere any leftover collateral must gobefore a callback is allowed to exercise delegated trove rights. - Prisma’s postmortem also made clear that the risk surface extended to both mkUSD and ULTRA versions of the helper, even though only mkUSD
wstETHtroves were exploited in practice.
Pattern tags
- migration-trust-boundary-failure
- migration-cutover-authenticity-and-single-record-consumption-discipline
- helper-path-invariant-reuse-and-fail-closed-discipline
- Prisma let a public flash-loan callback answer
what migration is happening right nowwhile delegated trove approval separately answeredwho may touch this user's position, so migration-context authenticity and position authority quietly split apart. - The theft rail was the leftover collateral sitting inside
MigrateTroveZap, not just the victim-trove close itself. Once the helper reopened the replacement trove with less collateral than it had withdrawn, the maroonedwstETHbecame a second migration-authentication problem: who was allowed to claim that residual balance. - This is the callback-authenticity branch of migration-trust-boundary failure. The old trove and the delegate approval were both real; the forged object was the migration story that said this callback represented a legitimate close-and-reopen flow for that victim.
Three migration-closeout promotions this incident collapsed
-
A callback-capable migration helper and a delegated trove authority path both existed
MigrateTroveZaphad real delegated closeout power over approved users, andmkUSD.flashLoan()could really callonFlashLoan().- By itself this only meant the ingredients for migration existed; it did not yet prove that any specific callback encoded a legitimate protocol-authored cutover.
-
Prisma promoted attacker-authored callback data into canonical migration truth for one victim trove
onFlashLoan()treated the calldata as if it already provedthis owner,this source trove,this destination manager, andthis close-and-reopen planwere the intended migration object.- That was the decisive authenticity break: the protocol accepted a public flash-loan callback as the authoritative answer to
what migration is happening right now?
-
That accepted migration story still retained live collateral-reallocation and residual-claim authority
- Once the forged callback was accepted, the zap could close the victim trove, reopen a thinner replacement, strand real
wstETHinside the helper, and later route that leftover collateral into an attacker-owned trove. callback acceptedandaccepted callback may now move and cash out live collateralwere separate promotions, and Prisma collapsed them together.
- Once the forged callback was accepted, the zap could close the victim trove, reopen a thinner replacement, strand real
Why the exploit worked
- Prisma authenticated the wrong thing.
BorrowerOperationsdelegate approval proved only thatMigrateTroveZapcould act for a user, not that this specific flash-loan callback encoded a legitimate protocol-authored migration for that user’s trove. onFlashLoan()treated attacker-supplied calldata as if it were continuation state frommigrateTrove(). BecausemkUSD.flashLoan()was publicly callable, the attacker could originate that supposedly trusted migration context directly.- The helper also failed to keep
collateral removed from the old trove,collateral assigned to the replacement trove, andresidual collateral recipientbound to one canonical consumed migration record. That let real victim collateral settle temporarily inside the zap and then get recycled into an attacker-owned trove. - The exploit did not require fake debt repayment or oracle corruption. Prisma already exposed real delegated trove-management authority; the bug was that a public callback could impersonate the missing
this migration was internally initiated and this residual belongs to the same userproof. - This is why the incident is sharper than generic
callback reentrancyordelegate approval risk: the protocol let a hostile flash-loan callback originate migration meaning, then let ordinary trove-close / trove-open machinery honor that fabricated meaning.
Detection and audit pivots
- Treat every migration callback as an auth surface. Ask what exact nonce, session record, or internally stored migration object proves this callback came from a protocol-initiated cutover rather than a user calling the flash-loan primitive directly.
- Trace one migration record end to end: which trove is being closed, which manager may receive the replacement, how much debt is repaid, how much collateral must be re-posted, and where any residual collateral is allowed to settle. If any one answer stays attacker-shaped in callback data, the migration object is already split.
- Fuzz direct callback reachability with attacker-crafted flash-loan calldata, especially when the helper already has standing delegate approval over user positions. If direct callback entry can close a victim trove or strand residual collateral in the helper, the cutover surface is live custody logic.
- Compare
collateral withdrawn from victim,collateral assigned to replacement trove, andcollateral left in helperper successful migration. Any gap must either revert or be deterministically returned to the same consumed legacy record’s owner. - Review leftover-balance handling as first-class custody code. Prisma shows that
temporary helper inventoryis not harmless transit state when the helper can later spend it into a second attacker-controlled migration. - Ask whether delegated approvals outlive the migration window. A zap with standing closeout authority plus a publicly reachable callback is already a high-risk composite surface even before a bug is found.
Failure-prevention checklist
- Bind every flash-loan or helper callback to an internally created single-use migration record that stores owner, source trove manager, destination trove manager, debt amount, collateral amount, residual-recipient rules, and expiry.
- Reject any callback that was not initiated by the helper itself in the same protocol-defined migration flow; public flash-loan reachability must not be enough to originate migration meaning.
- Derive replacement-position collateral and beneficiary from the consumed migration record, not from callback calldata that can quietly downsize the replacement trove or redirect residual balances.
- Require post-migration reconciliation such that
collateral removed from old trove = collateral assigned to replacement + explicitly derived residual returned to the same owner. - Keep residual collateral routing deterministic and provenance-bound. Leftover balances should return to the owner of the consumed trove or revert, never sit in the helper as reusable ambient inventory.
- Scope delegate approvals narrowly in time and function, or replace standing approval with per-migration signed authorization / permit-style consent that expires after one cutover.
- Add emergency disablement for migration helpers and fast user guidance to revoke helper approvals whenever callback authenticity or residual-routing assumptions become uncertain.
Audit prompts
- What exact stored object proves that this callback corresponds to a migration the protocol itself initiated, rather than a user calling the flash-loan lender directly?
- Can
delegate approved helperandauthenticated migration planever become two different facts, with the former true and the latter attacker-authored? - If the replacement trove is reopened with less collateral than the old trove surrendered, where is the difference forced to go and who is allowed to claim it?
- Can a successful callback ever leave economically meaningful balances stranded inside the helper, and if so what prevents a second call from reusing them for another user’s or the attacker’s position?
- Are source manager, destination manager, trove owner, debt amount, collateral amount, and residual recipient all derived from one canonical migration record, or can callback data silently reshape one of those answers?
- During incident response, can the protocol disable the helper or invalidate its migration sessions without depending on every user immediately revoking standing delegate approval?
- If the flash-loan primitive is permissionless, what onchain proof distinguishes a legitimate helper-initiated callback from a counterfeit one?
Lessons that should already be standard
- A public callback is part of the auth layer if it can carry migration meaning.
- Standing delegate approval is not proof that any particular migration story is legitimate.
- Residual balances inside a migration helper are part of the theft surface, not harmless transit dust.
We only call this callback from our helper flowis not a control unless the contract proves that claim onchain.- Migration helpers should spend one canonical closeout record, not improvise trove owner, replacement collateral, and leftover routing from fresh callback data.
- Temporary convenience approvals age into live custody authority unless they are tightly scoped and aggressively sunset.
Why this incident matters
- It is a strong modern example of migration code failing through context spoofing rather than through arbitrary old-contract injection. The protocol did not ask the attacker to supply a fake legacy contract; it let the attacker supply a fake migration story to a callback that was supposed to represent trusted protocol flow.
- It broadens the migration corpus beyond staking upgrades like STAX into CDP / trove systems where delegate approvals and helper contracts create their own standing authority boundary.
- It also preserves an important design lesson for any zap or migration helper: if a public primitive like a flash loan can call your settlement callback, then the callback itself is part of your auth layer and must prove why this particular migration is legitimate.
- The sharper healthy-side boundary is that delegated closeout, replacement-position creation, and leftover-collateral routing all needed to collapse onto one canonical migration-closeout record. Prisma instead let attacker-authored callback data define that record on the fly while live delegate approval supplied the authority.
Internal linkages
- Core recurring pattern: migration-trust-boundary-failure.
- Positive-design cutover companion: migration-cutover-authenticity-and-single-record-consumption-discipline — the clean healthy-side answer when the sharper question is how one consumed trove migration record should have kept callback authenticity, collateral identity, replacement ownership, and residual routing fused through the zap.
- Positive-design helper companion: helper-path-invariant-reuse-and-fail-closed-discipline — the zap should have reused a migration-only state machine that could be entered only from internally initiated flow, not treated a public flash-loan callback as if it were already trusted context.
- Positive-design entitlement companion: exit-right-consumed-state-binding — migration helpers should spend one canonical closeout / replacement entitlement so
close victim trove,reopen replacement trove, androute any residual collateralcannot drift into separate attacker-shaped accounting branches. - Adjacent pattern: privileged-message-dispatcher-abuse — both families turn a generic execution path into unauthorized state change, but Prisma’s decisive bug was migration-context authentication rather than a general arbitrary-call executor.
- Adjacent pattern: approval-by-address-mutable-code-execution — both incidents monetize a standing authority grant, but here the dangerous thing was not code mutability at an approved address; it was the zap’s ability to act on attacker-forged migration instructions once users had delegated it.
- Sibling migration variant: templedao-stax-migratestake-unvalidated-oldstaking-drain-2022-10-11 — STAX preserves the arbitrary-legacy-source cousin, where the attacker got to choose which contract vouched for migrated stake at all.
- Sibling migration variant: team-finance-v2-to-v3-migration-lock-identity-mismatch-refund-drain-2022-10-27 — Team Finance preserves the burn-and-recreate identity-mismatch cousin, where authorization, consumed LP position, and refund destination failed to collapse onto one canonical record.
Control surface
-
The decisive leverage sat in
MigrateTroveZap.onFlashLoan(), the publicmkUSD.flashLoan()entry that could reach it, the trove-level delegate approvals onBorrowerOperations, and the helper’s willingness to reopen a replacement trove with less collateral than it had just removed. -
More precisely, the real control surface was the migration-closeout object itself: who authored it, which trove it could touch, how much collateral and debt it was allowed to retire and recreate, and where any leftover collateral was permitted to settle. Prisma split that object across attacker-controlled callback calldata plus pre-existing delegate approval.
-
Prisma did not fail because flash loans exist. It failed because a public flash-loan callback was allowed to impersonate trusted migration state while holding live delegated authority over user troves.
-
Sources:
- https://hackmd.io/@PrismaRisk/PostMortem0328
- https://www.certik.com/blog/prisma-finance-incident-analysis
- https://olympixai.medium.com/decoding-the-prisma-finance-exploit-0aacecd5e876
- https://x.com/PrismaFi/status/1773316945430852058
- https://x.com/PrismaFi/status/1773426990114062497
- https://etherscan.io/tx/0x00c503b595946bccaea3d58025b5f9b3726177bbdc9674e634244135282116c7
- https://etherscan.io/tx/0x1319a80286b9c229a2d7566e21d7476b792956e4b59ee00d26449b6b77f2bcd0
- https://etherscan.io/tx/0x1a35836eec322195046387fd0aa43f945aa484c062b8b1442166db6860d733ae
- https://etherscan.io/address/0xcc7218100da61441905e0c327749972e3cbee9ee#code
-
Last reviewed: 2026-07-31 UTC