On Sui mainnet on April 29, 2026, attacker 0x1a65086c85114c1a3f8dc74140115c6e18438d48d33a21fd112311561112d41e exploited AftermathFi Perpetuals by supplying a negative integrator taker fee to the clearing house fee path. The bug let the attacker turn fees into a collateral credit, deallocate that collateral back into an account balance, and then withdraw real USDC. On-chain evidence shows 11 profitable exploit transactions over 35m59.648s, matching Blockaid’s public summary. Across those 11 drains, the sender realized 1,139,927.483185 USDC of net USDC gain, while the protocol emitted 1,141,027.483185 USDC of WithdrewCollateral events and the attacker seeded only 1,100 USDC of maker-side deposits. Including three flat attempts and three losing attempts on the same exploit path, the sender’s net gain from perps interactions is 1,139,652.059864 USDC.
The exploit did not require protocol-level privileged access. The attacker repeatedly created fresh accounts, deposited only 100 USDC into the maker-side account, set the taker-side account’s integrator fee cap to 0, then passed a negative fee through clearing_house::create_integrator_info. Because the fee validation only enforced an upper bound and never enforced 0 <= fee, the negative signed fixed-point value passed validation. The downstream accounting subtracted this negative fee from the taker position’s collateral delta, which increased collateral instead of decreasing it. The attacker then deallocated free collateral from the clearing house back into the account balance and withdrew that balance as USDC.
Root Cause
Vulnerable Package
- Core package:
0x21d001e8b07da2e3facb3e2d636bbaef43ba3c978bd84810368840b7d57c5068 - Interface wrapper used by the exploit transactions:
0x9e208bed81b7072fa75af8f9eaca42ced3ec8154bb04f5b948c2a9455125d136 - Signed fixed-point helper package:
0x46234ba81f3a5ba6571383233df0f9ea5fe60a3a327537be1f5fec447bced693 - USDC clearing house object:
0x95969906ca735c9d44e8a44b5b7791b4dacaddf70fbdfbda40ccd3f8a9fd4920 - Source type: on-chain Move bytecode disassembly preserved in
artifacts/analysis_531W14qrdyoD8tZrA34CzSU8pe4Dz1bNEAEcq2mC7E7u/
At the time of collection, the current clearing house object snapshot reports paused: true, which is consistent with an emergency response after exploitation.
Critical Exploit Path Functions
clearing_house::calculate_taker_feesclearing_house::process_fill_takeraccount::add_integrator_configclearing_house::deallocate_collateralaccount::withdraw_collateral
Vulnerable Logic
The critical validation appears in clearing_house::calculate_taker_fees:
// clearing_house.disasm.move
// Conceptual reconstruction from on-chain disassembly
integrator_cfg = account::get_integrator_config(account, integrator_address);
max_fee = account::get_integrator_max_taker_fee(integrator_cfg);
assert!(ifixed::less_than_eq(integrator_info.taker_fee, max_fee), invalid_integrator_taker_fee());
integrator_fee = integrator_info.taker_fee;
return total_taker_fee * quote_delta, integrator_fee * quote_delta, some(integrator_address);
The collateral update happens in process_fill_taker:
// clearing_house.disasm.move
// Conceptual reconstruction from on-chain disassembly
(taker_fee_total, integrator_fee_total, integrator_addr) =
calculate_taker_fees(position, account, quote_filled, base_fee, gas_fee, integrator_info);
collateral_delta = filled_value - (taker_fee_total + integrator_fee_total);
position::add_to_collateral_usd(position, collateral_delta, price_scale);
The account configuration path lets a user set a per-integrator ceiling:
// account.disasm.move
public(friend) add_integrator_config<T>(account: &mut Account<T>, integrator: address, max_taker_fee: u256) {
dynamic_field::add(&mut account.id, keys::integrator_config(integrator), IntegratorConfig { max_taker_fee });
}
Why It’s Vulnerable
The validation only checks integrator_taker_fee <= max_taker_fee. In the exploit path, the attacker set max_taker_fee = 0 on the taker account, then passed a negative signed fixed-point fee. Because the fee type is represented as signed fixed-point inside u256, any negative value is less than or equal to zero, so the check passes.
The disassembly of ifixed::less_than_eq confirms the comparison is signed-aware, not raw unsigned:
public less_than_eq(a: u256, b: u256): bool {
return (a ^ SIGN_BIT) <= (b ^ SIGN_BIT);
}
The disassembly of ifixed::to_balance also aborts on negative values, which confirms the type is intentionally signed and that negative fee values are first-class values, not decoding artifacts:
public to_balance(value: u256, scale: u256): u64 {
if (value >= SIGN_BIT) abort 0;
return (value / scale) as u64;
}
That signed behavior is exactly why the exploit works:
- The attacker sets
max_taker_fee = 0. - The attacker passes a negative
integrator_taker_fee. calculate_taker_feesaccepts it because negative<= 0.process_fill_takercomputesfilled_value - (taker_fee + negative_integrator_fee).- Subtracting a negative fee increases collateral instead of reducing it.
deallocate_free_collateralmoves that synthetic collateral back from the clearing house into the taker account balance.withdraw_collateralturns that account balance into real USDC.
The missing condition is a lower-bound check. The code should have enforced 0 <= integrator_taker_fee <= max_taker_fee, but it enforced only the upper bound.
Attack Execution
High-Level Flow
For each profitable exploit transaction, the attacker followed the same pattern:
- Create two fresh accounts through
interface::create_and_return_account. - Create positions for both accounts.
- Deposit
100USDC into the maker-side account only. - Create an integrator vault for the attacker address.
- Add integrator config to the taker-side account with
max_taker_fee = 0. - Start a session for the maker-side account and place a limit order.
- Call
clearing_house::create_integrator_info(attacker_address, negative_fee)and start a session for the taker-side account. - Fill the order with
place_market_order, causingcalculate_taker_feesto accept the negative fee. - End the session, deallocate free collateral, and withdraw collateral from the taker-side account.
- Share the clearing house and consume share policies to clean up the transient accounts.
The first profitable transaction demonstrates the pattern clearly:
| Signal | Value |
|---|---|
| Digest | 531W14qrdyoD8tZrA34CzSU8pe4Dz1bNEAEcq2mC7E7u |
| Maker deposit | 100.000000 USDC into account 1201 |
| Taker config | max_taker_fee = 0 on account 1202 |
| Filled taker order fee | raw integrator_taker_fees encodes a negative signed fixed-point value |
| Paid integrator fees event | raw fees field carries the same negative signed value for the attacker integrator address |
| Withdrawal | 261,752.224099 USDC from account 1202 |
| Net sender change | +261,652.224099 USDC |
The maker-side deposit was only 100 USDC, but the paired taker-side account withdrew 261,752.224099 USDC in the same PTB. That delta cannot be explained by trading PnL alone; it is synthetic collateral minted by subtracting a negative fee.
Profitable Drain Transactions
These 11 profitable transactions match the public “11 transactions in ~36 minutes” summary:
| UTC time | Digest | Net sender delta (USDC) |
|---|---|---|
| 2026-04-29T08:55:50.037Z | 531W14qrdyoD8tZrA34CzSU8pe4Dz1bNEAEcq2mC7E7u | 261,652.224099 |
| 2026-04-29T08:55:54.621Z | FQALUkZD1NnY9cDYoah2Zus6Z4TSp6voj1k2XKuA863Y | 199,564.413377 |
| 2026-04-29T09:06:40.373Z | u4zE7XBnPKxETVPBfF7XtKYd694qEHa6QTx1LDHwujT | 71,202.339922 |
| 2026-04-29T09:11:40.312Z | 2s3ybJFhwzzLSMTsugGSAN9b1SgjaL1uc8yPaqVbJ1wz | 69,416.507849 |
| 2026-04-29T09:13:30.486Z | 9UJLFirEqNuXEPkVZQL1MfNHZTfHy2zDH99MyoSb7k61 | 75,354.800010 |
| 2026-04-29T09:14:47.208Z | CKfb8nYzTA9XWSs6A3PpaCT6HqQFp48nBLBPwEV4EGPC | 96,912.047700 |
| 2026-04-29T09:15:27.136Z | 7P8TFYuvACtoPG79SpsBhucEiJ7MvmdVj11DomLjgrXu | 69,538.670010 |
| 2026-04-29T09:18:13.659Z | BqWYBVHnx8USdbKH24BNJD1gB7WxZWGeGz4WqMVmEFMn | 70,955.786777 |
| 2026-04-29T09:18:52.818Z | CSaHz1ABSeUbaLv7zdqFkH4ibLxEBxPWqYTBmXyCXVXB | 68,239.650407 |
| 2026-04-29T09:19:05.836Z | 7gR7xpvy3GiPMUyB62R5a4j5yw7tx1NnubcVdf4535xg | 77,480.596967 |
| 2026-04-29T09:31:49.685Z | 4pGQdfFG96Ghqj1xqkaeeAgMQCpttivdkgSRUGc6wVD8 | 79,610.446067 |
Across those 11 transactions, WithdrewCollateral events total 1,141,027.483185 USDC, funded by only 1,100 USDC of maker-side seed deposits, for a profitable-subset net sender gain of 1,139,927.483185 USDC. The full sender history also contains six additional perps-path transactions in the same window: three flat attempts with zero USDC gain and three losing attempts totaling -275.423321 USDC. Those extra attempts explain why raw sender history shows 17 exploit-path transactions while the public alert mentioned 11 drains.
Financial Impact
| Metric | Amount |
|---|---|
Gross WithdrewCollateral across 11 profitable drains | 1,141,027.483185 USDC |
| Maker-side seed deposits across 11 profitable drains | 1,100.000000 USDC |
| Net sender gain across 11 profitable drains | 1,139,927.483185 USDC |
| Net sender gain across all 17 perps-path transactions | 1,139,652.059864 USDC |
| Losing exploit attempts | -275.423321 USDC |
| Profitable exploit count | 11 |
| Total perps-path interaction count | 17 |
| Profitable drain window | 35m59.648s |
Post-exploit cashout behavior is also visible on-chain:
939,000USDC was transferred out through 19 direct transfer-only transactions to fresh addresses.200,000USDC was swapped into213,338.343466958SUI across four router transactions.210,000SUI was then forwarded to four recipient addresses across five transfers.- At the time of collection, the tracked attacker address still held
930.789478USDC and3,443.28852349SUI.
This means the exploit and immediate fan-out can be separated cleanly:
- Protocol drain: the perps transactions produced the USDC gain.
- Cashout and fan-out: the later transfer-only and router transactions redistributed those proceeds.
Evidence
- Primary exploit artifact:
artifacts/analysis_531W14qrdyoD8tZrA34CzSU8pe4Dz1bNEAEcq2mC7E7u/tx_primary.json - Full sender ledger:
artifacts/analysis_531W14qrdyoD8tZrA34CzSU8pe4Dz1bNEAEcq2mC7E7u/attacker_ledger.md - Parsed sender summary:
artifacts/analysis_531W14qrdyoD8tZrA34CzSU8pe4Dz1bNEAEcq2mC7E7u/attacker_transaction_summary.json - Summary totals:
artifacts/analysis_531W14qrdyoD8tZrA34CzSU8pe4Dz1bNEAEcq2mC7E7u/analysis_summary.json - Core package object:
artifacts/analysis_531W14qrdyoD8tZrA34CzSU8pe4Dz1bNEAEcq2mC7E7u/core_package_object.json - Interface package object:
artifacts/analysis_531W14qrdyoD8tZrA34CzSU8pe4Dz1bNEAEcq2mC7E7u/interface_package_object.json - Signed fixed-point package object:
artifacts/analysis_531W14qrdyoD8tZrA34CzSU8pe4Dz1bNEAEcq2mC7E7u/ifixed_package_object.json - Relevant disassemblies:
artifacts/analysis_531W14qrdyoD8tZrA34CzSU8pe4Dz1bNEAEcq2mC7E7u/clearing_house.disasm.moveartifacts/analysis_531W14qrdyoD8tZrA34CzSU8pe4Dz1bNEAEcq2mC7E7u/account.disasm.moveartifacts/analysis_531W14qrdyoD8tZrA34CzSU8pe4Dz1bNEAEcq2mC7E7u/position.disasm.moveartifacts/analysis_531W14qrdyoD8tZrA34CzSU8pe4Dz1bNEAEcq2mC7E7u/constants.disasm.moveartifacts/analysis_531W14qrdyoD8tZrA34CzSU8pe4Dz1bNEAEcq2mC7E7u/ifixed.disasm.move
- Current paused clearing house snapshot:
artifacts/analysis_531W14qrdyoD8tZrA34CzSU8pe4Dz1bNEAEcq2mC7E7u/clearing_house_object_current.json
Related URLs
- Attacker activity:
https://suivision.xyz/account/0x1a65086c85114c1a3f8dc74140115c6e18438d48d33a21fd112311561112d41e?tab=Activity - Primary exploit transaction:
https://suivision.xyz/txblock/531W14qrdyoD8tZrA34CzSU8pe4Dz1bNEAEcq2mC7E7u