Thala V1 Farming unstake_max Withdraw-Amount Validation Omission / LP Over-Withdrawal (2024-11-15)
- Name: Thala V1 farming
unstake_maxwithdraw-amount validation omission / LP over-withdrawal - Date: 2024-11-15
- Protocol: Thala
- Chain: Aptos
- Category: withdraw-amount validation failure / escrowed-LP over-withdrawal / farming receipt-accounting bug
- Loss: about
$25.5Min LP tokens at peak exposure; Thala later negotiated return of funds minus a$300kprotocol bounty and$40kpersonal compensation to founders - Summary: Thala’s v1 farming contracts held users’ LP tokens in escrow and tracked how many LP tokens each farmer was entitled to withdraw. A small Nov. 1 patch to the new boosted-farming release introduced an
unstake_maxbug that no longer bounded the caller-supplied withdrawal amount against the user’s real staked balance. The attacker first deposited a tiny amount of LP, then called the vulnerable unstake path with a huge forged amount and received far more LP tokens than they had ever staked. Those counterfeit withdrawal rights were then redeemed through real ThalaSwap and other Aptos liquidity, draining the MOD/USDC, MOD/THL, and THAPT/APT farming pools before Thala paused contracts and froze part of the loot. - Trigger / entry point:
- Thala released boosted farming on 2024-10-11.
- Users later reported a rounding issue with
unstake_max-style withdrawals. - On 2024-11-01 Thala shipped a small two-line patch to the v1 farming contracts to address that usability issue.
- The patch bypassed Thala’s normal security-review path and introduced the exploitable over-withdrawal condition.
- The attacker used the patched farming withdraw path on 2024-11-15 to redeem more LP tokens than their recorded stake justified.
- Root cause:
- The decisive failure was not an Aptos runtime issue and not a pool-pricing bug inside ThalaSwap. It was a local authorization and accounting failure in the farming layer.
- The updated
unstake_maxflow trusted a user-controlled withdraw-amount parameter strongly enough that the contract would release escrowed LP tokens without proving the amount was less than or equal to the user’s actual staked position. - In other words, Thala’s farming contract lost the invariant
LP released <= LP previously staked by this account. - Once that release invariant broke, the rest of the stack behaved honestly: the attacker held real LP tokens and downstream pools honored them as such.
- Exploit path:
- The attacker funded Aptos accounts and added a small amount of liquidity to at least one Thala LP pool, obtaining a small genuine LP position.
- They staked that LP in the vulnerable Thala v1 farming contract so the account had a legitimate farming position and could access the unstake path.
- They called the patched unstake function with an attacker-chosen oversized amount parameter; public analyses cite values on the order of
1e12for the manipulated withdraw amount. - Because the patched logic no longer enforced that the requested withdrawal was bounded by the user’s true staked amount, the farming contract released far more LP tokens than the attacker had deposited.
- The attacker repeated the over-withdrawal flow across affected farming pools, especially the THL/MOD, MOD/USDC, and THAPT/APT pools named in Thala’s postmortem.
- After receiving those excess LP tokens, the attacker removed underlying liquidity and swapped proceeds into more liquid assets, with public incident writeups describing conversion into large APT and stablecoin balances.
- A second associated attacker address performed additional draining transactions before Thala identified the bug, reverted the patch, and paused the affected contracts.
- What the attacker needed to be true:
- The farming contract had to custody real LP tokens rather than merely track rewards.
- The vulnerable unstake path had to accept a caller-shaped amount that could exceed the account’s real farming balance.
- The withdrawn LP tokens had to remain ordinary, redeemable LP claims once released from farming escrow.
- The pause and patch response had to arrive after enough oversized withdrawals had already been executed.
- Technical anatomy worth remembering:
- This was not a classic
reentrancyororacle manipulationexploit. The core mistake was simpler and more brutal: a withdraw helper stopped proving that the caller had actually earned the quantity being released. - The exploit is a good reminder that farming, staking, and escrow contracts are not peripheral bookkeeping. If they custody redeemable LP tokens, they sit directly on the protocol’s asset-release boundary.
- Thala’s own postmortem is especially useful because it captures the process failure as well as the code failure: a small patch, rushed outside the normal review cycle, modified the most dangerous question in the module —
how much can this user take back right now? - Thala’s listed remediation is also analytically useful. The team said future state-changing methods should prefer capability-based models and that patches would no longer bypass a full release cycle. That is a direct acknowledgment that raw user parameters were carrying too much authority.
- Unlike many nine-figure DeFi incidents, this one did not end in irreversible losses. Thala identified the exploiter quickly, froze MOD and THL assets on Aptos, and negotiated a return. That does not change the preserved exploit lesson: a local unstake-accounting bug briefly turned a farming contract into a
$25.5Mwithdrawal oracle.
- This was not a classic
- Representative transactions and addresses:
- Boosted-farming feature release:
1786004800 - Vulnerable Nov. 1 patch release:
1873380050 - First exploit transaction cited by Thala:
0x81489df372eeaa92a17bdaea53dd0d21c15a0bcd42f2cdc371535289d9f31963 - Final major exploit transaction cited by Thala:
0xd7f08882e3a84aa32982b44c2306a0b3fa91cb71c058dd59513cbe48336c89af - Emergency revert to the last safe version:
0xbebdda019893bb54e612ed965ecff521561b05816e28506d88435fa62c6ec669 - THL/MOD farming-pool reconciliation transaction:
0x439bdf800832f3b0a593edcdaa18f71cda6d0148cdd4a063375fc85b7c7bdfc5 - Remaining impacted-pool reconciliation transactions:
0x58ff0def3b6aeb85bef10d8b62cb05701e6ab17e2b33598e9ecc83f8d5a75a41,0x316a399171b7e32280dfa7675de973951e17e249c6b4abefc3ff68d4e93a3ec3 - Attacker 1 address prefix cited by Thala:
0xf7... - Attacker 2 address prefix cited by Thala:
0x80...
- Boosted-farming feature release:
- Response / mitigation:
- Thala engineering opened an incident war room with OtterSec after the second TVL-drop alert on 2024-11-15.
- The team identified the vulnerable patch, reverted the farming contracts to the last safe version, and paused the relevant contracts and frontend.
- Thala froze about
$9Mof MOD and$2.5Mof THL tied to the attacker, then used onchain and offchain contact to negotiate return of the remaining funds. - The attacker returned the exploited funds later the same day, keeping the agreed bounty.
- Thala later re-audited farming contracts, added protocol-wide withdrawal rate limits, tightened incident response and review procedures, and said future state-changing methods would move toward capability-based access patterns.
Pattern tags
- Core recurring pattern: issuance-redemption-solvency-asymmetry.
- Escrowed-exit-right authorization failure / the farming contract released ordinary redeemable LP tokens without binding the withdrawal amount to the caller’s real remaining stake.
- Helper-path invariant drift / a convenience-style
unstake_maxpath stopped reusing the core release invariantLP released <= LP staked and still unspent. - Custodied-receipt over-issuance / the protocol effectively minted extra withdrawal rights against escrowed LP inventory, then downstream liquidity-removal paths treated those rights as fully real.
Why the exploit worked
- Thala’s farming module was economically a custody surface, not just a rewards helper. Once users’ LP tokens sat inside the contract, the unstake path became the reserve-release boundary for claims that Aptos liquidity pools would still honor at face value.
- The Nov. 1 patch weakened the one invariant that mattered most at that boundary:
amount released <= amount this account can still withdraw. After the patch,unstake_maxaccepted a caller-shaped amount strongly enough that the contract would transfer escrowed LP out without proving that the user’s stored stake justified it. - That turned the farming contract into a local claim printer. Thala did not need to mint a new synthetic token for the exploit to work; by releasing extra LP tokens from escrow, the contract created counterfeit exit rights against real underlying pool reserves.
- The rest of the stack then behaved honestly. Once the attacker held genuine LP tokens, ThalaSwap and other Aptos liquidity-removal paths redeemed them for underlying assets exactly as designed. The exploit therefore combined a broken issuance side in farming with still-honest exit / redemption surfaces downstream.
- The rushed-patch process amplified the technical mistake. A two-line change outside the normal review path landed directly on the highest-value question in the module — how much value a user may take back right now — and no stronger capability, receipt-spend, or rate-limit boundary stopped the mistake from scaling into a protocol-wide drain.
Detection and audit pivots
- Enumerate every function that can release escrowed LP or reserve-backed receipt tokens —
unstake,unstake_max, migration helpers, emergency exits, admin rescues — and verify they all call one shared withdrawal primitive that recomputes entitlement from canonical storage immediately before transfer. - Ask whether the module is really
rewards logicor actually a custody boundary. If it escrows LP tokens that downstream venues will redeem for real assets, review it like vault-withdraw, redemption, or bridge-release code rather than like peripheral farming UX. - Search for any path where a raw caller-supplied amount can outrank stored entitlement state. The dangerous shape is not only
amount > stake; it is any helper that treats a fresh user parameter as the final authority instead of spending a pre-proven withdrawal right. - Diff rushed patches and hotfixes with special suspicion when they touch exit, unstake, or claim-release code. Tiny ergonomics fixes on withdrawal boundaries are often more dangerous than large feature launches elsewhere in the system.
- Simulate the full monetization path, not just the local over-withdraw. If an escrow bug can hand out extra LP tokens, tests should continue through liquidity removal and swaps to prove how quickly the false entitlement becomes a real reserve drain.
- Review blast-radius controls as if the withdrawal invariant might already be broken. Pool-level throttles, global rate limits, staged exits, and fast freeze authority matter precisely because logic bugs at custody boundaries are rarely caught before the first real asset leaves.
Failure-prevention checklist
- Keep the release invariant local and explicit: every unstake,
unstake_max, emergency-withdraw, migration, and admin recovery path must proveamount released <= amount this account is still entitled to withdrawfrom storage at the moment value leaves escrow. - Reuse one hardened withdrawal primitive instead of letting convenience helpers carry weaker checks. If
unstake_maxis justunstake(user_stake)in concept, encode that equivalence in code rather than through parallel logic. - Prefer capability- or receipt-based withdrawal authority over raw caller-supplied amount parameters when the contract escrows redeemable assets. The more the protocol can spend a previously minted withdrawal right instead of trusting a fresh number from the caller, the narrower this class becomes.
- Treat farming and staking escrows that hold LP tokens as reserve-release surfaces, not rewards sidecars. They need the same adversarial review depth as vault withdraw, redemption, and bridge-release code.
- Add protocol-level and pool-level withdrawal throttles sized for
release logic may already be wrong, not only for normal market volatility. Thala’s later rate limits are useful because they reduce blast radius even when logic review fails. - Do not let emergency patches bypass full tests, review, and release ceremony when they touch withdrawal or entitlement code. Small diffs at the custody boundary deserve the highest suspicion, not the lowest.
Audit prompts
- Which exact functions can move escrowed LP tokens back to users, and do all of them recompute the user’s withdrawable amount from canonical storage instead of trusting a caller-shaped number?
- Are
max,all, helper, migration, or admin-assisted exit paths implemented by calling the same checked withdrawal primitive, or do they maintain their own release logic? - If the caller supplies an amount parameter, where is the final inequality enforced that binds that amount to the user’s still-unspent farming entitlement immediately before transfer?
- Can a rushed patch, config toggle, or feature-flagged branch weaken the withdrawal invariant without failing end-to-end tests that actually drain escrowed assets?
- Does the contract custody redeemable LP or reserve-backed receipt tokens that downstream pools will honor at face value? If so, has the review treated the module as a direct balance-sheet surface rather than a rewards helper?
- What concrete rate limits, pause hooks, or staged-withdraw controls cap damage if one withdrawal path starts releasing assets under fiction before operators notice?
Lessons that should already be standard
Farmingis custody when the contract escrows redeemable LP tokens. If the module can hand the user back a claim that an AMM will cash out, its withdraw path is a treasury boundary.- Convenience verbs are not lower-risk verbs.
unstake_maxand similar helpers often sit exactly where teams are most tempted to skip ceremony while touching the most dangerous invariant in the system. - Strong language or runtime guarantees do not rescue weak entitlement logic. Move can protect resource ownership and still faithfully execute a business-logic bug that hands out too much real value.
- Raw user parameters should not be the final authority for asset release when the protocol can instead bind release rights to stored entitlements, capabilities, or previously minted receipts.
- Patch discipline is part of protocol security, not process theater. A tiny post-launch fix that bypasses review can be more dangerous than the feature it was meant to tidy up.
- Self-custody and receipt-first designs genuinely shrink blast radius. If users hold their own exit rights and farming only meters rewards, one buggy escrow helper has far less treasury to mis-release.
Why this incident matters
- It is a compact example of how a tiny hotfix on a custody boundary can be more dangerous than a large feature launch.
- It preserves a useful Aptos / Move lesson: strong resource semantics do not save a protocol if business logic still lets a caller overstate what they are entitled to withdraw.
- It also shows that
LP farmingis economically equivalent to custody when the contract escrows redeemable LP tokens. Once unstake accounting lies, downstream AMMs and liquidity-removal paths will faithfully cash out the lie.
Internal linkages
- Broad solvency cousin: issuance-redemption-solvency-asymmetry — Thala’s farming contract was not a mint, but it did create the same practical asymmetry: one release path started issuing redeemable LP claims under fiction while downstream liquidity-removal and swap paths still honored those claims against real reserves.
- Positive-design companion: helper-path-invariant-reuse-and-fail-closed-discipline — the important follow-up question is why a convenience / patched unstake path was allowed to define a weaker release invariant than the core
user may only recover what they actually stakedrule. - Adjacent payout-replay family: duplicate-claim-idempotency-failure — not because Thala literally double-consumed one claim, but because both families live at the same dangerous boundary where an exit helper releases more value than the entitlement object should authorize.
Control surface
-
The decisive leverage sat in Thala’s v1 farming escrow, the patched
unstake_maxlogic, and the amount-validation boundary betweenrecorded staked LPandLP tokens actually released back to the user. -
Thala did not mainly fail at AMM pricing, governance, or bridge semantics. It failed at the simplest high-value question in custody software: how much should this caller be allowed to withdraw right now?
-
Sources:
- https://thalalabs.medium.com/thala-nov-15-post-mortem-5aea82bb3916
- https://www.quillaudits.com/blog/hack-analysis/thala-defi-hack-analysis
- https://explorer.aptoslabs.com/txn/1786004800?network=mainnet
- https://explorer.aptoslabs.com/txn/1873380050?network=mainnet
- https://explorer.aptoslabs.com/txn/0x81489df372eeaa92a17bdaea53dd0d21c15a0bcd42f2cdc371535289d9f31963?network=mainnet
- https://explorer.aptoslabs.com/txn/0xd7f08882e3a84aa32982b44c2306a0b3fa91cb71c058dd59513cbe48336c89af?network=mainnet
-
Last reviewed: 2026-07-28 UTC