We do not have a reliable USD price for the recorded assets yet.
0x0053490215baf541362fc78be0de98e3147f40223238d5b12512b3e26c0a2c2f0xD265ff7e5487E9DD556a4BB900ccA6D087Eb3AD2Polygon0x23F43c1002EEB2b146F286105a9a2FC75Bf770A4Polygon0xFb6FE7802bA9290ef8b00CA16Af4Bc26eb663a28PolygonOn Polygon block 38118348, exploit transaction 0x0053490215baf541362fc78be0de98e3147f40223238d5b12512b3e26c0a2c2f used public Balancer and Aave flash-loan liquidity to manipulate the collateral price of Midas market 0x23F43c1002EEB2b146F286105a9a2FC75Bf770A4 (fWMATIC_STMATIC). The attacker first deposited Curve stMATIC/WPOL LP as collateral, then triggered a native-token callback inside Curve pool 0xFb6FE7802bA9290ef8b00CA16Af4Bc26eb663a28 and reentered Midas while the LP oracle was transiently overstated. During that callback window, the attacker borrowed four Jarvis jFIAT markets against the inflated collateral and then self-liquidated the position after the oracle normalized. The attacker contract 0x757e9f49acfab73c25b20d168603d54a66c723a1 finished the transaction with 663101.518942206398329551 native MATIC, while the sender EOA paid 1.2065462 MATIC in gas.
The root cause is a read-only reentrancy across Curve and Midas. Curve remove_liquidity(..., use_eth=true) burns LP supply and transfers native value before it refreshes cached invariant self.D. Midas prices the LP collateral through an oracle path that consumes Curve get_virtual_price() live during borrow checks. As a result, Midas observed a stale invariant divided by an already-reduced LP supply and temporarily valued the LP at about 10.091e18 instead of about 1.002e18, which let the attacker borrow against collateral value that did not exist in finalized pool state.
Midas uses Compound/Fuse-style liquidity checks. For each collateral market, the comptroller computes effective collateral from the cToken balance, exchangeRateStored(), the market collateral factor, and the oracle price returned for the underlying asset. For the affected market, the underlying asset was Curve LP token 0xe7CEA2F6d7b120174BF3A9Bc98efaF1fF72C997d, and the oracle price came from a dedicated LP-oracle path rather than from a direct single-token feed.
The key contracts in the pricing stack are:
0xD265ff7e5487E9DD556a4BB900ccA6D087Eb3AD20x23F43c1002EEB2b146F286105a9a2FC75Bf770A40xaCF3E1C6f2D6Ff12B8aEE44413D6834774B3f7A30x3803527dcd92Ac3e72A0A164Db82734DABa47Fac0x7ef2a6a6af8ee090da3a24ea947b3717c4d16d940xFb6FE7802bA9290ef8b00CA16Af4Bc26eb663a28The collected LP-oracle implementation note shows that getUnderlyingPrice() recursively prices the LP constituents and then reads the pool's live virtual price:
function getUnderlyingPrice(address arg0) public payable {
(bool success, bytes memory ret0) = address(arg0).underlying(); // staticcall
...
(bool success, bytes memory ret0) = address(msg.sender).Unresolved_aea91078(var_h); // price(underlying)
...
(bool success, bytes memory ret0) = address(storage_map_j[var_d]).get_virtual_price(); // staticcall
}
The recursive price oracle itself stayed stable during the exploit window. The collected block-38118347 direct reads show price(WPOL) = 1e18 and price(stMATIC) = 1056568563958165728. That isolates the exploitable price movement to the Curve LP virtual-price path rather than to any change in underlying-token feeds.
The adversary cluster is also trace-backed:
0x1863b74778cf5e1c9c482a1cdc2351362bd08611 deployed attack contract 0x757e... in tx 0xe709b50b92e9cb79d143c196505e2139d5ae765bb975ca5ac87f62dbc412c62c.0x757e... directly.0x97c347bacbd37d853c98de415fce3f459908b4f5 was created during the exploit transaction by the attacker contract.This incident is an ATTACK-class ACT exploit. The safety invariant is that the LP price consumed by Midas during borrowAllowed must reflect finalized Curve pool state. For an LP collateral market priced through get_virtual_price(), cached invariant self.D, pool balances, and LP total supply must all refer to the same post-transition state whenever the oracle is externally observable.
Curve violated that invariant along the native withdrawal path. The verified pool source shows that remove_liquidity burns LP, transfers native value to the receiver, and only then updates self.D. The same source shows that get_virtual_price() divides an invariant-derived quantity by the current LP total supply. Once LP supply has already been burned, but self.D still refers to the pre-withdrawal state, the virtual price becomes overstated.
Midas then turned that transient inconsistency into borrowable collateral value by trusting the live pool read inside its liquidity checks. The focused trace proves the distortion was not permanent: the LP price was normal before the callback, spiked only during the callback, and returned near parity after the callback completed. That behavior is precisely the signature of a read-only reentrancy oracle bug rather than a lasting repricing or a constituent-feed issue.
Because the attacker regained control during the callback and reentered Midas before Curve refreshed self.D, the exploit was deterministic. The attacker could borrow against collateral value that existed only in the inconsistent mid-withdrawal state, then realize the resulting bad debt through self-liquidation once the price normalized.
The ACT opportunity is defined at Polygon block 38118347, immediately before the exploit transaction. At that point:
fWMATIC_STMATIC;0x3803... and mapped the LP to the Curve stMATIC/WPOL pool;use_eth=true.The seed artifacts confirm that the pre-state LP price was normal. The seed evidence and focused oracle trace place the pre-state collateral price at about 1002157321772769944, close to parity.
The critical Curve withdrawal logic is:
lp_token: address = self.token
total_supply: uint256 = CurveToken(lp_token).totalSupply()
CurveToken(lp_token).burnFrom(msg.sender, _amount)
...
if use_eth and coin == WETH20:
raw_call(receiver, b"", value=d_balance)
...
D: uint256 = self.D
self.D = D - D * amount / total_supply
The corresponding pricing logic is:
def get_virtual_price() -> uint256:
return 10**18 * self.get_xcp(self.D) / CurveToken(self.token).totalSupply()
That ordering creates the exploit window. During the callback, LP supply is already lower, but self.D is still stale. Any external protocol that reads get_virtual_price() during that window receives an inflated LP price.
The focused oracle-call trace records the exact pricing sequence:
Pre-callback:
getUnderlyingPrice(fWMATIC_STMATIC) = 1002157321772769944
Curve.get_virtual_price() = 1002157321772769944
During callback:
getUnderlyingPrice(fWMATIC_STMATIC) = 10091002696492234934
Curve.get_virtual_price() = 10091002696492234934
Post-callback:
getUnderlyingPrice(fWMATIC_STMATIC) = 1002197920733900715
Curve.get_virtual_price() = 1002197920733900715
The same trace shows that the constituent prices remained stable during the exploit:
WPOL price = 1000000000000000000
stMATIC price = 1056568563958165728
So the exploitable price movement came entirely from the LP virtual price, not from any feed movement in the underlying assets.
The attacker first primed the Midas account by minting LP and depositing it into fWMATIC_STMATIC. It then performed a much larger one-sided Curve add/remove cycle to open the callback window. The collected evidence records:
131863.795920435857673899659318.97960217928836949522566542.13077532156433470068074207.302427837361065300During that callback, the attacker borrowed from four Jarvis markets:
296187.577226634081924767 jCHF425500 jEUR50000 jGBP50204.765122889316550500 EURAWhen Curve later refreshed self.D, the LP price normalized and the Midas position became undercollateralized. The attacker then created helper 0x97c347..., self-liquidated, seized 513648.897590228880099075 collateral cTokens, redeemed 102729.779518045776019814 LP, swapped the proceeds, and exited in native MATIC.
The adversary flow consisted of one setup transaction and one exploit transaction:
Setup deployment
0xe709b50b92e9cb79d143c196505e2139d5ae765bb975ca5ac87f62dbc412c62c381182510x757e9f49acfab73c25b20d168603d54a66c723a1.Exploit execution
0x0053490215baf541362fc78be0de98e3147f40223238d5b12512b3e26c0a2c2f38118348add_liquidity / remove_liquidity(..., true) cycle.0x97c347..., liquidated the position, redeemed seized collateral, swapped assets back to WMATIC, and withdrew to native MATIC.This remained a permissionless ACT path throughout. The helper contract was created by the attacker during the exploit transaction itself and did not provide any privileged capability beyond what any adversary could reproduce.
The measurable impact is the conversion of transiently overstated collateral into undercollateralized debt across four Jarvis markets, followed by profit realization at the attacker contract.
The seed balance diff shows:
0x757e... native MATIC delta: +663101518942206398329551 wei-1206546200000000000 weiThe lasting market losses recorded in the seed balance diff were:
| Token | Raw Delta | Display Delta |
|---|---|---|
| jCHF | -273973508934636525780410 | 273973.508934636525780410 |
| jEUR | -368057500000000000000000 | 368057.500000000000000000 |
| jGBP | -45250000000000000000000 | 45250.000000000000000000 |
| EURA | -45435312436214831478203 | 45435.312436214831478203 |
The native-MATIC profit is the attacker-side realization of that bad debt after liquidation and swaps. The protocol-level impact is broader than the profit amount: Midas accepted collateral priced at roughly 10x its finalized value and therefore authorized borrows that should never have passed liquidity checks.
0xe709b50b92e9cb79d143c196505e2139d5ae765bb975ca5ac87f62dbc412c62c0x0053490215baf541362fc78be0de98e3147f40223238d5b12512b3e26c0a2c2f0x3803527dcd92ac3e72a0a164db82734daba47fac0x7ef2a6a6af8ee090da3a24ea947b3717c4d16d940x1863..., 0x757e..., and 0x97c347...