Calculated from recorded token losses using historical USD prices at the incident time.
0x4ccde7fc6b240397228c1a740d15a149d2062ae0c11336ff81ad394603d9dfd80xc396231ef9b6d4d3d00e61b433e1bf0a5a605c91657d8ecb1428dd3d406cf1160xcb4718f18f1ad6b1036baecbe73deeff038c916933fa950f7653abbb47eec90a0xee1727f5074e747716637e1776b7f7c7133f16b1Chain 11010xbc59506a5ce024b892776d4f7dd450b0fb3584a2Chain 1101KEOM was drained on Polygon zkEVM, not on Ethereum mainnet. The Ethereum transaction 0x8501e36313413bbc9f668a59ebdc0df1c3871f2e58247391fb2bfe7480b4ebee is only a ransom note sent to the attacker after the fact. The actual exploit was executed by attacker EOA 0xb343fe12f86f785a88918599b29b690c4a5da6d5, which used helper contract 0x5a2f4151ea961d3dfc4ddf116ca95bfa5865f16f to drain KEOM's kNative and kWETH markets on Polygon zkEVM.
The root cause is a supply-accounting bug in KEOM's shared redemption path. In KToken.redeemFresh, the protocol subtracts totalSupply using the full computed redeemTokens value before later capping the caller-side burn to accountTokens[redeemer]. That breaks the invariant that global supply reduction must equal the redeemer's token burn, which lets a dust depositor withdraw nearly all market cash with only a tiny kToken balance.
KEOM is a Compound-style lending fork. The two drained markets, kNative at 0xee1727f5074e747716637e1776b7f7c7133f16b1 and kWETH at 0xbc59506a5ce024b892776d4f7dd450b0fb3584a2, both route through the shared KToken redemption implementation. Their Comptroller is 0x6ea32f626e3a5c41547235ebbdf861526e11f482.
0x50d4c79f1dcab99f68a7176aecbf9e03de32d11cd4c353d2a37d05506da339110xc42a4fb28cafe451a5ac13d996617f800ee239638a3337e05ec5ec2181b782f4For a Compound-style market, the redemption quote is derived from the stored exchange rate:
exchangeRateStored = (cash + totalBorrows - totalReserves) / totalSupply;
If a market has positive cash and the requested redeemUnderlying amount implies redeemTokens <= totalSupply, the faulty subtraction can succeed without underflow even when the caller owns only a tiny balance.
The updated auditor package also resolves contract provenance deterministically. The kNative proxy points to implementation 0xf7faa3f174e780e3d317dd475fde0de0dfe358fb, the kWETH proxy points to 0x6a40d2fe56c990db316e00491d4dbdc088693d5a, and the Comptroller proxy points to implementation 0x968d00c4b188db962250c1876204870487a514dd. The selector sets and Solidity metadata are consistent with the public keomprotocol/keom-contracts repository at commit eb0d7b8149889041cd3824d7301e9e24514e23b9.
This is an ATTACK-class protocol bug, not a pure MEV opportunity. The vulnerable behavior sits in KEOM's redemption logic, which mutates protocol-wide supply before validating how many kTokens the redeemer can actually burn. The Comptroller only gates redemption based on the quoted redeemTokens and membership / liquidity checks; it does not repair the later accounting mismatch. As a result, an attacker can mint a dust position, exit the market, and redeem nearly all available market cash. The emitted burn and account-balance change stay tiny, but totalSupply drops by the oversized quoted value. The exploit therefore drains liquid assets and leaves protocol accounting inconsistent in the same transaction.
The critical code path is the shared KToken.redeemFresh implementation:
(vars.mathErr, vars.totalSupplyNew) = subUInt(
totalSupply,
vars.redeemTokens
);
if (vars.mathErr != MathError.NO_ERROR) {
return failOpaque(...);
}
if (vars.redeemTokens > accountTokens[redeemer])
vars.redeemTokens = accountTokens[redeemer];
(vars.mathErr, vars.accountTokensNew) = subUInt(
accountTokens[redeemer],
vars.redeemTokens
);
The invariant is straightforward: for any redemption, the reduction in totalSupply must equal the reduction in accountTokens[redeemer], and both must match the kToken amount implied by the redeemed underlying. KEOM violates that invariant because it computes totalSupplyNew before clamping vars.redeemTokens to the redeemer's actual balance.
The exploit works as follows:
30488584, where public RPC state shows kNative.getCash() = 38600532887381810523 and kWETH.getCash() = 1658030939350857144.0.001 ETH or 0.001 WETH.Comptroller.exitMarket, so the subsequent liquidity check does not block the oversized redemption.redeemUnderlying(getCash()) for the market's full cash balance.redeemTokens quote, passes it into redeemAllowed, subtracts it from totalSupply, then caps the caller-side burn to the tiny minted balance.getCashPrior() >= redeemAmount, KEOM still transfers the full underlying amount out to the attacker helper.The exploit conditions are also concrete. The target market must be listed, redemption must not be guardian-paused, the attacker must be able to mint any positive amount of the market, the chosen redeemAmount must imply redeemTokens <= totalSupply so the pre-cap subtraction does not underflow, and the market must hold at least that much cash. Those conditions held for both victim markets at the observed pre-state.
The Comptroller gate does not prevent this. The relevant logic is:
function redeemAllowed(
address kToken,
address redeemer,
uint256 redeemTokens
) external override returns (uint256) {
require(!guardianPaused[kToken].redeem, "redeem is paused");
uint256 allowed = redeemAllowedInternal(kToken, redeemer, redeemTokens);
if (allowed != uint256(Error.NO_ERROR)) {
return allowed;
}
return uint256(Error.NO_ERROR);
}
Independent evidence matches the bug exactly. The kNative exploit transaction 0x4ccde7fc6b240397228c1a740d15a149d2062ae0c11336ff81ad394603d9dfd8 emitted a mint of 4918683 shares for 0.001 ETH, then a redeem of 38601532887381810523 wei while still burning only 4918683 shares. The kWETH exploit transaction 0x50d4c79f1dcab99f68a7176aecbf9e03de32d11cd4c353d2a37d05506da33911 emitted a mint of 4972902 shares for 0.001 WETH, then a redeem of 1659030939350857144 wei while burning only 4972902 shares. A forked reproduction confirms that the total supply drop exceeds the attacker burn by orders of magnitude in both markets.
The attacker flow is a simple multi-transaction ACT sequence:
0x4ccde7fc6b240397228c1a740d15a149d2062ae0c11336ff81ad394603d9dfd8 on Polygon zkEVM block 30488585
The helper contract mints 0.001 ETH into kNative, exits the market, and calls redeemUnderlying(getCash()), draining 38.601532887381810523 ETH from the market.0xc396231ef9b6d4d3d00e61b433e1bf0a5a605c91657d8ecb1428dd3d406cf116 on block 30488624
The attacker wraps ETH into WETH to fund the second leg.0xcb4718f18f1ad6b1036baecbe73deeff038c916933fa950f7653abbb47eec90a on block 30488626
The attacker transfers 0.001 WETH into the helper contract.0x50d4c79f1dcab99f68a7176aecbf9e03de32d11cd4c353d2a37d05506da33911 on block 30488628
The helper approves WETH, mints kWETH, exits the market, and calls redeemUnderlying(getCash()), draining 1.659030939350857144 WETH.0xc42a4fb28cafe451a5ac13d996617f800ee239638a3337e05ec5ec2181b782f4 on block 30488707
The attacker unwraps the drained WETH back into native ETH.After the drain, the attacker bridged the proceeds back to Ethereum mainnet using zkEVM bridge transactions 0xb5b48dec168905db59c9be483402370a39192b8cc5b57031b325e83ab8a54a4f and 0xe1fd8e2030b9230572c09f2bd85e94197991d55ded727feb38b125d0372d2b4a, then claimed them on Ethereum in 0xd1e96ef881074a71cb3c716e9b06d2327f5f56f4979a5837b7575a0871ec771f and 0xbd8fea447cfdc9f578c04f6aa9f429b2c3a6957e32e443f4a0241540b132e307.
This is therefore a single-actor, multi-transaction ACT exploit followed by bridge-out and cleanup, not a privileged compromise.
The exploit drained all immediately available cash from the targeted KEOM markets:
kNative: 38600532887381810523 wei of ETHkWETH: 1658030939350857144 wei of WETHThe attacker's gross proceeds after the exploit legs were 38.602532887381810523 ETH from the kNative leg and 1.660030939350857144 WETH from the kWETH leg, against only 0.004 ETH equivalent of seed capital. The root-cause artifact values the realized gain at 40.258563826732667667 ETH equivalent before later cleanup activity. Beyond the asset loss, KEOM's accounting invariant was violated because totalSupply was reduced by far more shares than the attacker ever burned.
The non-monetary success condition is equally important: both markets' liquid cash balances reached zero while the attacker only supplied 0.001 units of underlying to each market. That is the semantic on-chain manifestation of the broken supply accounting. The violated security principles are that global supply accounting must equal the sum of account balances, validation must happen before mutating protocol-wide state, and redemption must never transfer more underlying than the caller's kToken balance represents.
0x8501e36313413bbc9f668a59ebdc0df1c3871f2e58247391fb2bfe7480b4ebee0x4ccde7fc6b240397228c1a740d15a149d2062ae0c11336ff81ad394603d9dfd8, 0xc396231ef9b6d4d3d00e61b433e1bf0a5a605c91657d8ecb1428dd3d406cf116, 0xcb4718f18f1ad6b1036baecbe73deeff038c916933fa950f7653abbb47eec90a, 0x50d4c79f1dcab99f68a7176aecbf9e03de32d11cd4c353d2a37d05506da33911, 0xc42a4fb28cafe451a5ac13d996617f800ee239638a3337e05ec5ec2181b782f40xb5b48dec168905db59c9be483402370a39192b8cc5b57031b325e83ab8a54a4f, 0xe1fd8e2030b9230572c09f2bd85e94197991d55ded727feb38b125d0372d2b4a, 0xd1e96ef881074a71cb3c716e9b06d2327f5f56f4979a5837b7575a0871ec771f, 0xbd8fea447cfdc9f578c04f6aa9f429b2c3a6957e32e443f4a0241540b132e307kNative 0xee1727f5074e747716637e1776b7f7c7133f16b1, kWETH 0xbc59506a5ce024b892776d4f7dd450b0fb3584a2, Comptroller 0x6ea32f626e3a5c41547235ebbdf861526e11f482eb0d7b8149889041cd3824d7301e9e24514e23b9, especially ktokens/abstract/KToken.sol and comptroller/Comptroller.solartifacts/auditor/iter_1/