During the past week (2026/03/09 - 2026/03/15), BlockSec detected and analyzed eight attack incidents, with total estimated losses of ~$1.66M. The table below summarizes these incidents, and detailed analyses for each case are provided in the following subsections.
| Date | Incident | Type | Estimated Loss |
|---|---|---|---|
| 2026/03/09 | EtherFreakers Incident | Flawed business logic | ~$25K |
| 2026/03/10 | Alkemi Incident | Flawed business logic | ~$89K |
| 2026/03/10 | MT Incident | Flawed business logic | ~$242K |
| 2026/03/11 | AAVE Liquidation Incident | Misconfiguration | ~$1.01M |
| 2026/03/11 | Planet Finance Incident | Flawed business logic | ~$10K |
| 2026/03/12 | AM Incident | Flawed business logic | ~$131K |
| 2026/03/12 | DBXen Incident | Flawed business logic | ~$149K |
| 2026/03/15 | Goose Finance Incident | Flawed business logic | ~$8K |
EtherFreakers Incident
Brief Summary
On March 9, 2026, EtherFreakers, an NFT game on Ethereum, was exploited due to incorrect double counting, resulting in a loss of ~$25K. Each NFT in the game holds a withdrawable ETH balance (called "energy"). As a game mechanic, players can use attack() to have one NFT capture another and claim the target's energy. However, the contract pays out the target's balance but transfers the NFT before settling its accounting. A transfer hook then reads stale, pre-payout data and feeds part of it back into a global dividend pool, inflating the pool without new ETH backing. The exploiter looped this capture mechanic to pump the global index and then drained the inflated balance from a batch of NFTs.
Background
EtherFreakers is an on-chain NFT game where each NFT (called a "Freaker") holds a withdrawable ETH balance called energy. The system works like a dividend pool: when certain actions occur, a fraction of ETH is distributed across all Freakers proportionally. Each Freaker's claimable ETH is tracked by a global accumulator freakerIndex combined with a per-token share weight fortune.
Concretely, the accounting formula is: energyOf = basic + (freakerIndex - index) * fortune. The freakerIndex increases when _dissipateEnergyIntoPool(amount) runs, distributing 80% of amount to all Freakers and 20% to creators. Direct deposits via charge() only increase basic without affecting freakerIndex. Therefore, increases in freakerIndex should always be backed by real Ether entering the system. If freakerIndex grows without corresponding ETH inflow, Freakers can redeem more Ether than the contract actually holds.
Vulnerability Analysis
The root cause is an incorrect execution order in the EtherFreak contract (0x3A27...c0f33). When a capture succeeds, the attack() function executes these steps in order:
- Line 237: Pay
targetCharge(the target NFT's full energy) to the defender as a direct ETH transfer. The energy is now spent. - Line 240: Call
_transfer(defender, capturer, targetId)to move the NFT. Internally,_transfer()invokes the ERC-721 hook_beforeTokenTransfer(), which calls_dissipateEnergyIntoPool()with 0.1% ofenergyOf(targetId). This is the first call to_dissipateEnergyIntoPool(), and it reads a stale value because step 5 has not happened yet. - Line 241: Call
_dissipateEnergyIntoPool(sourceSpent)explicitly. This is the second call, which is part of normal game logic. - Lines 244-251: Update
energyBalancesfor bothsourceIdandtargetId.
The bug is in step 2: because energyBalances[targetId] has not been updated yet, the hook still sees the pre-payout balance and feeds part of the already-spent energy into the dividend pool. The direct ETH payout in step 1 and the pool input in step 2 both draw from the same energy, inflating freakerIndex without new ETH backing.


freakerIndex grows whenever _dissipateEnergyIntoPool() is called:

Attack Analysis
The following analysis is based on the transaction 0x89e24d...9abd2942.
-
Step 1: Borrow
1,700WETH. -
Step 2: Mint two fresh Freakers, token
590and token591, under exploiter-controlled addresses. -
Step 3: Repeatedly call the game's
attack(590, 591)function and keep executions on the successful capture branch. -
Step 4: After each success, transfer token
591back to the helper so the same pair can be reused. -
Step 5: Each successful loop inflates
freakerIndexbeyond the Ether actually conserved by the system. -
Step 6: Once the index is high enough, discharge a batch of previously controlled Freakers. Token IDs
496through520are each discharged for0.278052246002402082Ether. -
Step 7: Wrap the drained Ether into
WETH, repay the1,700WETHflash loan, and keep about7.498WETHas profit.
Conclusion
The root cause is in the successful capture flow of attack(): EtherFreakers pays out targetCharge before the target token's energy state is settled. _transfer() then triggers _beforeTokenTransfer(), which reads stale pre-payout energyOf(targetId) and dissipates part of it into the pool. This increases freakerIndex without new Ether backing, so the same target energy is counted both as payout and as pool input. This is a business-logic inflation bug, not a reentrancy bug.
To reduce similar risks in the future:
-
Avoid recomputing economic values from mutable state inside transfer hooks while the same transaction is still settling.
-
If the transfer hook reads a state variable, ensure the order of execution does not affect the result (e.g., settle the state before the hook runs, not after).
Alkemi Incident
Brief Summary
On March 10, 2026, the Alkemi protocol on Ethereum was exploited, resulting in a loss of ~$89K. The root cause is an accounting error and flawed business logic. The flawed liquidation logic allows anyone to liquidate their own position within the same transaction and profit from it. In addition, an accounting error causes the deduction of the attacker's own collateral during liquidation to be overwritten, allowing the attacker to obtain liquidation rewards without bearing the intended cost.
Background
Alkemi is a lending protocol. When a borrower's position becomes undercollateralized, anyone can call liquidateBorrow() to repay part of the debt and seize collateral at a discount. To prevent excessive liquidation, the protocol caps the repayable amount per transaction at the minimum of three values:
- The borrower's current borrow balance (
currentBorrowBalance_TargetUnderwaterAsset). - The maximum repayment the borrower's collateral can cover after applying the liquidation discount (
calculateDiscountedBorrowDenominatedCollateral()). - The repayment amount needed to bring the account back to the liquidation boundary (
calculateDiscountedRepayToEvenAmount()), checked only when the marketisSupported.


Vulnerability Analysis
The root cause is flawed business logic and an accounting error in the Alkemi protocol (0x4822...a888). Since currentBorrowBalance_TargetUnderwaterAsset is necessarily greater than 0 as long as the borrower has an outstanding debt, and the value returned by calculateDiscountedBorrowDenominatedCollateral() is also necessarily greater than 0 as long as the borrower has collateral, the AlkemiEarnPublic protocol effectively relies on calculateDiscountedRepayToEvenAmount() to determine whether a given loan can be liquidated. In this function, the amount of debt that needs to be liquidated should be calculated based on a variable called accountShortfall_TargetUser.
However, in the actual implementation, the function instead uses a global variable closeFactorMantissa to calculate an upper limit on the amount of debt that is allowed to be repaid, and returns this value. As a result, an attacker is able to borrow and immediately liquidate their own position within the same transaction.
In addition, in the function liquidateBorrow(), when the liquidator and the borrower are the same address, the variables supplyBalance_TargetCollateralAsset and supplyBalance_LiquidatorCollateralAsset point to the same storage slot. The function then calculates a "reduced balance" and a "rewarded balance" separately based on the same initial balance, and subsequently writes them back to the same storage slot in sequence. Because the reduced balance is written first and the rewarded balance is written afterward, the reduction effect is overwritten and lost, leaving only the rewarded result. This allows the attacker to further amplify their profit.
Attack Analysis
The following analysis is based on the transaction 0xa170...6d9d.
-
Step 1: The attacker takes a flash loan of
51e18WETHfrom Balancer. -
Step 2: The attacker unwraps
51e18WETHintoETHand supplies it to the Alkemi protocol. -
Step 3: The attacker borrows
39.5e18ETHfrom Alkemi. -
Step 4: The attacker liquidates their own position using
39.5395e18ETH. -
Step 5: The attacker withdraws
93.5e18ETHfrom Alkemi. -
Step 6: The attacker repays the flash loan and makes a profit of
43.4e18ETH.

Conclusion
The root cause is that the flawed liquidation logic allows the attacker to borrow and then liquidate their own position within the same transaction to make a profit, while the incorrect accounting logic further amplifies the attacker's gains.
To reduce similar risks in the future:
- For balance updates to the borrower and the liquidator, the protocol should operate directly on storage variables rather than copying the balances into temporary memory variables for separate calculation and write-back.
MT Incident
Brief Summary
On March 10, 2026, MT Token, a deflationary token on BNB Chain, was exploited, resulting in a loss of ~$242K. The root cause is flawed trading restriction logic combined with inconsistent handling of special transfer conditions. During the deflation phase, the contract restricts buy operations when the pool reserve exceeds a fixed threshold. However, the contract treats transfers of exact amounts (e.g., 2e17 MT) as referral-binding actions, allowing the attacker to bypass the buy restriction and acquire initial tokens. In addition, the restriction logic relies on incomplete path detection (isBuy) and fails to cover indirect swap routes such as Pair to Router, while whitelist checks further short-circuit critical validations. The attacker accumulated MT tokens without triggering restrictions or fees, manipulated pendingBurnAmount via controlled liquidity operations and trades, and forced the pool into an abnormal state where the token price was artificially inflated.
Background
MT Token is a deflationary token on BNB Chain with built-in trading restrictions. During the deflation phase, the contract blocks buy operations when the MT reserve in the pool exceeds 21,000e18. Once the reserve falls below this threshold, the deflation phase ends and buying is re-enabled. MT Token also includes a referral mechanism: a transfer of exactly 2e17 MT or 1e17 MT is treated as a referrer-binding action rather than a normal trade.
Vulnerability Analysis
The root cause was a flawed buy-restriction design in the MT contract (0x037E...b449). Under normal conditions, attackers should be unable to acquire MT as seed capital during the restricted phase. However, the contract treats a transfer of exactly 2e17 MT as a referral-binding action rather than a buy, which allows an attacker to purchase 2e17 MT while bypassing the buy restriction.

Additionally, the trading restriction relies on the isBuy branch to block purchases, but it does not cover the "Pair to Router" path. Since both the Router and Pair are whitelist addresses, such transfers short-circuit at the whitelist check and never reach the buy restriction logic, allowing an attacker to acquire MT by routing buys to the Router and subsequently extracting tokens back to themselves by removing liquidity.

Attack Analysis
The following analysis is based on the transaction 0xfb57...fca6.
-
Step 1: The attacker flash-loaned
~358,681e18WBNB. -
Step 2: The attacker purchased
2e17MT, thereby bypassing the buy restriction. -
Step 3: The attacker supplied
4e12WBNBand2e17MTto the Pair to add liquidity. This transfer bypassed the fee-charging logic for the same reason as above. -
Step 4: The attacker bought
~10,000,000e18MTtokens from the Pair to the Router, thereby bypassing both the buy restriction and the fee-charging logic. -
Step 5: The attacker removed half of their liquidity position, extracting all
MTtokens held by the Router in the process, and then sold the recoveredMTforWBNB. In this step,pendingBurnAmountwas manipulated to approximately9,000,000e18.
-
Step 6: The attacker bought
~10,000,000e18MTtokens again, driving the pool'sMTreserve down to~6,756,516e18, which was lower thanpendingBurnAmount.
-
Step 7: The attacker removed the remaining half of their liquidity position, withdrew the purchased
MTtokens, and then calleddistributeDailyRewards()to burnMTfrom the pool. As a result, theMTreserve was reduced to21,000e18.
-
Step 8: The attacker swapped all
MTback for~1,198e18WBNB, repaid the flash loan, and finalized the profit.
Conclusion
This exploit was caused by incorrect trading restrictions, which allowed the attacker to bypass the buy ban by purchasing exactly BINDING_AMOUNT of MT tokens. After obtaining MT tokens, the attacker was further able to bypass both the fee-charging logic and the buy restriction by first adding liquidity, then buying MT into the Router, and finally removing liquidity to retrieve the tokens. The attacker then accumulated pendingBurnAmount via sell operations and executed the burn to push the pool reserves into an abnormal state, enabling them to sell MT at an inflated price and profit.
To reduce similar risks in the future:
- Enforce strict separation between transfer semantics and trading logic.
AAVE Liquidation Incident
Brief Summary
On March 11, 2026, AAVE suffered $21M in incorrect liquidations on Ethereum, resulting in a loss of ~$1.01M. The root cause was an incorrect oracle price for wstETH, which caused positions that were originally healthy to become undercollateralized. As a result, users' positions were liquidated, leading to financial losses.
Background
AAVE uses oracle adapters to price wrapped assets like wstETH. The adapter CAPO (Capped Price Oracle) derives the wstETH price by multiplying the base ETH/USD price by a conversion ratio (getRatio(), i.e., how much ETH one wstETH is worth). To prevent ratio manipulation, CAPO applies a snapshot-based growth cap:
maxRatio = snapshotRatio + maxGrowthPerSecond x (currentTime - snapshotTimestamp)
and clamps the getRatio() output during pricing (if currentRatio > maxRatio, it uses maxRatio). This mechanism effectively limits the maximum upward drift of the ratio and the resulting price.
Vulnerability Analysis
The root cause was a time-ratio mismatch in the CAPO oracle anchor configuration (0xe1D9...61Ef): the snapshot timestamp and snapshot ratio were set, but the snapshot ratio was configured below the true wstETH/ETH ratio. As a result, the adapter's computed maxRatio fell below the live ratio and clamped getRatio() downward, systematically undervaluing the wstETH/USD oracle price. This depressed collateral valuation reduced the health factor of positions using wstETH as collateral, causing otherwise healthy accounts to be incorrectly classified as unhealthy and liquidated.


Attack Analysis
The following analysis is based on the transaction 0x9064...8a9c.
-
Step 1: The liquidator flash-loaned
~6304e18WETHand liquidated the borrower. -
Step 2: The liquidator repaid the flash loan, completing the liquidation.
Conclusion
This liquidation was caused by an incorrect oracle price configuration, which incorrectly pushed borrowers who should have remained healthy into an unhealthy state, thereby triggering liquidation of their positions.
To reduce similar risks in the future:
-
Ensure critical parameters are verified for correctness before each update.
-
Add validation checks in the implementation to reject incorrect parameters and prevent incorrect configurations from being applied successfully.
Planet Finance Incident
Brief Summary
On March 11, 2026, Planet Finance was exploited on BNB Chain, resulting in an estimated loss of ~$10K. The root cause was that the protocol mistakenly treated increases in a borrower's stored borrow balance as accrued interest, allowing an attacker to repeatedly borrow and trigger discount settlement to understate their recorded debt.
Background
Planet Finance is a lending protocol that allows borrowers to repay with an interest discount. The discount is tiered and is determined by the ratio between a user's staked GAMMA and their staked value in other assets: the higher this ratio, the higher the repayment discount. The discount schedule comprises three tiers, ranging from 0% (minimum) to 50% (maximum).
Vulnerability Analysis
The root cause was that when settling a borrower's discount in changeUserBorrowDiscount(), the protocol (0x4c9E...F467) mistakenly treated the increase in the borrower's stored borrow balance as newly accrued interest. As a result, the discount intended to apply only to accrued interest was incorrectly applied to the newly borrowed principal, improperly reducing the borrower's recorded debt. An attacker could repeatedly perform the borrow then changeUserBorrowDiscount loop to accumulate excessive discounts, causing the on-chain recorded liability to be consistently lower than the true borrowed amount, and ultimately profit from the discrepancy.


Attack Analysis
The following analysis is based on the transaction 0x5f45...5ec9.
-
Step 1: The attacker flash-loaned
200,000e18USDT. -
Step 2: The attacker used
5,000e18USDTto purchaseWBNB, and then used the acquiredWBNBto buy~8,726,524e18GAMMA. -
Step 3: The attacker first staked all acquired
GAMMAinto the gGAMMA market, then supplied the remainingUSDTas collateral, which increased their repayment discount to 5% and enabled subsequent borrowing. -
Step 4: The attacker repeatedly called
borrowand thenupdateUserDiscountto continuously reduce their recorded debt.
-
Step 5: The attacker ultimately repaid the debt, redeemed the collateral, and realized the profit.
Conclusion
This incident was caused by Planet Finance's flawed discount-settlement logic in changeUserBorrowDiscount(), which mistakenly treats the increase in a borrower's stored borrow balance as newly accrued interest and applies the interest discount to that delta. An attacker can repeatedly call borrow followed by updateUserDiscount to understate their recorded debt and ultimately repay less than the true liability to extract profit.
To reduce similar risks in the future:
- Distinguish interest and new borrows in the lending protocol.
AM Incident
Brief Summary
On March 12, 2026, AM Token, a deflationary token on BNB Chain, was exploited with an estimated loss of ~$131K. AM Token implements a deflationary mechanism where each sell triggers an additional burn from the liquidity pool, permanently removing tokens to reduce total supply. However, the burn is not executed immediately - instead, the full sell amount is recorded as toBurnAmount and the actual burn is deferred to the next sell. This delay creates a window between recording and execution, during which an attacker can buy back AM to shrink the pool's AM reserve down to toBurnAmount. When the next sell triggers the deferred burn, the entire AM reserve is wiped out, driving the price to an extreme level and allowing the attacker to sell AM for profit.
Background
AM Token is a deflationary token on BNB Chain. On each sell, the contract records the amount of AM involved in the swap as toBurnAmount, and burns that recorded amount from the liquidity pool during the next sell. In effect, sells trigger a delayed burn that shrinks the pool's AM reserve. In addition, before executing the burn, the protocol swaps the accumulated totalTokenFee into USDT and distributes it according to its fee-allocation logic.
Vulnerability Analysis
The root cause was that the token's (0x27f9...213f) sell logic accumulates the full swapped AM amount as toBurnAmount and only executes the burn on the next sell by removing tokens from the AM/USDT pair and calling pair.sync() to update reserves. This design allows an attacker to manipulate the pool's AM reserves, distort the on-chain price, and profit via arbitrage.


Attack Analysis
The following analysis is based on the transaction 0xd0d1...f859.
-
Step 1: The attacker flash-loaned
~27,265,119e18USDCand~361,710e18WBNB, then swapped them for~100,423,811e18USDT. -
Step 2: The attacker swapped
~5,062e18AMtokens forUSDT, which manipulated the contract's recordedtoBurnAmountto~4,303e18.
-
Step 3: The attacker swapped
USDTfor AM Token, pushing the pool'sAMreserve down to~4,303e18.
-
Step 4: The attacker transferred
6 weiAMto the pool, triggering the sell-path burn logic. As a result, the contract burned the entireAMbalance from the pool, driving theAMreserve down to 0. Note: the protocol first attempts to swap the accumulated fees intoUSDTbefore the burn. This fee-conversion path also triggers the sell-branch burn logic. After the burn executes and theAMreserve reaches 0, the fee swap fails. Because it is wrapped in a try/catch, the failure does not revert the transaction. Instead, execution continues and the fee accumulator is reset to 0.
-
Step 5: The attacker called
pool.sync()and transferred the remainingUSDTand1 weiAMinto the pool. Because both tokens were transferred simultaneously, the contract treated this as addLiquidity, sotoBurnAmountwas not accumulated. TheAMreserve was updated to 7.

-
Step 6: The attacker swapped the remaining
AMtokens forUSDT. During this swap, transferringAMinto the Pair triggered the sell-path burn logic, reducing theAMreserve to 1. In addition, becausetotalFeeAmounthad been reset to 0 in Step 4, the fee-to-USDTconversion was no longer executed, allowing the attacker to sellAMat an artificially inflated price.
-
Step 7: The attacker repaid the flash loan and realized the remaining profit.
Conclusion
This incident was caused by AM Token's flawed burn mechanism, which accumulates each sell's swap-involved AM as toBurnAmount and then burns that amount from the AM/USDT Pair on the next sell while calling pool.sync(). This allows an attacker to manipulate the Pair's AM reserves down to an extreme level and sell AM at an artificially inflated price to drain USDT.
To reduce similar risks in the future:
- Limit the maximum burn amount per transaction and rate-limit how frequently burn can be triggered, preventing attackers from consuming a large portion of the pool's token reserves within a short period of time.
DBXen Incident
Brief Summary
On March 12, 2026, DBXen, a burn-to-earn protocol on Ethereum and BNB Chain, was exploited with a total loss of ~$149K. The root cause was an inconsistency between _msgSender() and msg.sender. When burnBatch() is called through the forwarder, the burned XEN amount is recorded under _msgSender() (attacker-controlled), but the cycle records are updated on msg.sender (the forwarder). This split allows the attacker to claim rewards and fees against stale cycle records, resulting in abnormally large payouts.
Background
DBXen is a burn-to-earn protocol: users burn XEN tokens in exchange for DXN rewards and a share of accumulated protocol fees. The key mechanism works in cycles. When a user calls burnBatch(), two things happen: (1) the burned XEN amount is recorded under the caller's address (identified by _msgSender()), and (2) the XEN contract calls back into DBXen via onTokenBurned() to update the caller's cycle records (burn cycle and lastFeeUpdateCycle) to the current cycle.
Rewards and fees are settled via updateStats(). The reward is proportional to the user's share of total XEN burned in their burn cycle. The fee is based on the cumulative protocol fees that accrued since the user's last recorded cycle. Both calculations depend on the user's cycle records being up to date.




Vulnerability Analysis
The root cause is flawed business logic in the DBXen protocol (0xf5c8...2abd). The _msgSender() function checks whether msg.sender is the forwarder. If so, it returns the last 20 bytes of calldata, and this returned value can be arbitrarily controlled in the forwarder context. However, burnBatch() directly burns the XEN held by msg.sender. As a result, an attacker can invoke burnBatch() through the forwarder, causing the protocol to burn the XEN held by the forwarder and update both the forwarder's burn-cycle record and fee-update cycle record to the current cycle. At the same time, however, the protocol records the burned XEN amount under the address corresponding to _msgSender().
After that, the attacker calls claimFees(), which invokes updateStats(). Because the _msgSender() address's cycle records were never updated (both burn cycle and lastFeeUpdateCycle remain at 0), updateStats() calculates rewards in the current cycle and computes fees accumulated since cycle 0 - spanning the protocol's entire fee history. The attacker then profits by calling claimFees() and claimRewards().

Attack Analysis
The following analysis is based on the transaction 0x914a5a...b808bc37.
-
Step 1: The attacker first called the
registerDomainSeparator()function of theForwardercontract, enabling subsequent calls toForwarder.execute(). -
Step 2: The attacker swapped
0.14e18ETHfor13,900,000,000e18XENin a Uniswap V2 pool. -
Step 3: The attacker transferred
13,900,000,000e18XENto theForwardercontract. -
Step 4: The attacker used
Forwarder.execute()to approve DBXen to spend the13,900,000,000e18XENheld by theForwarder. -
Step 5: The attacker used
Forwarder.execute()to callDBXen.burnBatch()and burned13,900,000,000e18XEN. The burn amount was recorded under the address0x425D3eC2DCeBE2c04bA1687504D43AFC6be7328d, while during the burn execution,XENcalled back intoDBXenviaonTokenBurned(), updating the relevant cycle records on theForwarder.
-
Step 6: The attacker used
Forwarder.execute()to callDBXen.claimFees()and obtained65.36e18ETH. -
Step 7: The attacker used
Forwarder.execute()to callDBXen.claimRewards()and minted2,305.4e18DXN.
Conclusion
The root cause of this incident was that the DBXen protocol inconsistently used _msgSender() and msg.sender. Because these two values could differ, the protocol's internal accounting became inconsistent, which allowed the attacker to exploit the discrepancy for profit.
To reduce similar risks in the future:
- Use
_msgSender()consistently throughout all logic paths, or ensure thatmsg.sender-dependent operations and_msgSender()-dependent accounting always reference the same address.
Goose Finance Incident
Brief Summary
On March 15, 2026, Goose Finance, a yield-farming protocol on BNB Chain, was exploited for about $8K. The root cause was a share-pricing order flaw in StrategyGooseEgg: deposit() mints shares before settling harvested rewards into the accounting, so the total-asset denominator used for share pricing excludes these rewards and is lower than the true value. This means depositors receive more shares than they should. When withdraw() is called, it triggers a reward harvest that increases the total assets, making each share worth more. By looping deposit and withdraw in a single transaction, the attacker repeatedly minted over-priced shares and redeemed them at the corrected (higher) value, extracting the difference as profit.
Background
Goose Finance is a BNB Chain yield-farming protocol where user funds flow from a vault into a strategy, which stakes assets in MasterChef to earn EGG rewards.
The components relevant to this incident are:
-
VaultChef(0x3f64...): tracks user positions and forwards capital toStrategyGooseEgg. -
StrategyGooseEgg(0x0980...): maintains strategy-level accounting withsharesTotalandwantLockedTotal. -
MasterChef(0xe70e...): receives staked assets and paysEGGrewards. -
WrappedEgg(0xb815...): wrapsEGG1:1 intoWEGGfor staking.
Operationally, deposits are routed from VaultChef to StrategyGooseEgg, then staked into MasterChef. Withdrawals are initiated from VaultChef and executed by the strategy.
A key accounting expectation is that share pricing should reflect total strategy assets at pricing time (staked principal plus idle rewards already held by the strategy). In StrategyGooseEgg, however, deposit() mints shares before _farm() settles idle assets into wantLockedTotal, while withdraw() can trigger reward harvest from MasterChef. This order is the basis of the vulnerability analyzed below.


Vulnerability Analysis
The root cause is an accounting desynchronization in StrategyGooseEgg (0x0980...b26b) between reward harvest and share pricing.
In StrategyGooseEgg, share pricing uses wantLockedTotal as the denominator: shares = deposit * sharesTotal / wantLockedTotal. For this to be fair, wantLockedTotal must reflect all assets the strategy actually holds, including any idle EGG rewards sitting in the contract. However, deposit() mints shares before _farm() settles idle rewards into wantLockedTotal. This means the denominator excludes unaccounted rewards and is lower than the true total assets, causing the depositor to receive more shares than they should.
Furthermore, withdraw() calls MasterChef.withdraw(), which returns staked principal plus pending EGG rewards to the strategy. The strategy's accounting only subtracts the requested _wantAmt from wantLockedTotal, so the harvested rewards remain on the strategy's balance without being reflected in wantLockedTotal. This widens the gap between the actual assets held and the recorded wantLockedTotal, making any subsequent deposit() share pricing even more inaccurate.
Attack Analysis
The following analysis is based on transaction 0x86efdf...ce316223.
-
Step 1: The attacker flash-borrows
EGGfrom two Pancake pairs. -
Step 2: First deposit into
VaultChef/StrategyGooseEgg(10,170,000e18EGG). -
Step 3: First withdraw (
12,593,884e18EGG) harvests rewards fromMasterChef;359,561e18EGGis transferred toStrategyGooseEggand remains as idle/unaccounted value (R > 0). -
Step 4: Second deposit reuses the withdrawn capital (
12,593,884e18EGG). Shares are priced before idle value is settled, so this is the over-mint step. -
Step 5: Second withdraw (
12,826,027e18EGG) realizes profit from over-minted shares (i.e.,232,143EGGabove step-4 deposit input). -
Step 6: The attacker repays flash swaps and keeps the net spread.
Conclusion
The exploit stems from a share-pricing order flaw in StrategyGooseEgg: deposit() mints shares before _farm() updates wantLockedTotal, while withdraw() can harvest rewards from MasterChef that remain temporarily idle and unaccounted. This allows deposits to mint against a stale denominator and later withdraw against updated assets.
To reduce similar risks in the future:
-
Settle rewards and update accounting before both share-mint and share-burn calculations.
-
Price shares against a single
totalAssets(staked + idle) at the exact calculation point. -
Add invariant tests for
shares_minted <= D * S / (A + R)under non-zero idle reward conditions.
About BlockSec
BlockSec is a full-stack blockchain security and crypto compliance provider. We build products and services that help customers to perform code audit (including smart contracts, blockchain and wallets), intercept attacks in real time, analyze incidents, trace illicit funds, and meet AML/CFT obligations, across the full lifecycle of protocols and platforms.
BlockSec has published multiple blockchain security papers in prestigious conferences, reported several zero-day attacks of DeFi applications, blocked multiple hacks to rescue more than 20 million dollars, and secured billions of cryptocurrencies.
-
Official website: https://blocksec.com/
-
Official Twitter account: https://twitter.com/BlockSecTeam



