Reproduced Exploit

Enso Finance strategy vault — short Uni V3 TWAP overvalues FARM deposits

The Enso strategy vault mints deposit shares as:

Sep 2026Ethereumoracle5 min read

Loss

Headline ~5.6 ETH spot of UNI/AAVE/MKR pulled, net of 0.683 WETH spent on FARM. PoC realizes 5.27720911387529…

Chain

Ethereum

Category

oracle

Date

Sep 2026

Source

DeFiHackLabs

EVM Playground

Source-level debugger — step opcodes and Solidity in sync

evm-hack-analyzer

The attack is replayed in an in-browser EVM preloaded with the exact dumped fork state. The execution tree shows every call; step by Solidity line or by opcode across all depths — source, Stack, Memory, Storage, Balances (native / ERC-20 / NFT), Transient storage and Return value stay in sync. Click a tree node, opcode, or source line to jump. No backend, no live RPC.

Loading fork state…

Source & credit. Exploit reproduction, trace data, and analysis adapted from DeFiHackLabs by SunWeb3Sec — an open registry of reproduced on-chain exploits. Standalone Foundry PoC and full write-up: 2026-09-EnsoFinance_exp in the evm-hack-registry mirror. Upstream DeFiHackLabs PoC: src/test/…/EnsoFinance_exp.sol.


Vulnerability classes: vuln/oracle/manipulable-twap · vuln/oracle/spot-price · vuln/oracle/missing-circuit-breaker · vuln/logic/price-calculation

Reproduction: the PoC compiles & runs in an isolated Foundry project at this project folder. Full verbose trace: output.txt. Source test: test/EnsoFinance_exp.sol.


Key info#

LossHeadline ~5.6 ETH spot of UNI/AAVE/MKR pulled, net of 0.683 WETH spent on FARM. PoC realizes 5.277209113875295697 ETH after sale slippage output.txt
Vulnerable contractsStrategy vault proxy 0x890ed1Ee…5942 · StrategyController 0x173cAe63…bE8 · EnsoOracle 0xAb7505eB…DC0
Attacker0x3196398321D77a2511d369DCB6eCa9d2aD87b73A (contract-creation exploit)
Attack tx0x63fbfc4b47e810d604dbdab0db35b17366f421337d8c60955eb81cd5d6071ad3 (block 25,934,827)
Chain / block / dateEthereum / fork 25,934,826 / 2026-09-09
Bug classPermissionless deposit mints shares on oracle-estimated value delta; FARM is priced off a thin Uni V3 pool with a near-spot TWAP window (~9× vs the V2 market)

TL;DR#

The Enso strategy vault mints deposit shares as:

mint = amountAddedValue * totalSupply / valueBefore

Valuation runs EnsoOracle → per-item estimators → Uniswap V3 TWAP (pool.observe). The FARM item is priced off an imbalanced, thinly-observed V3 pool with a short, per-pool TWAP window (trace: observe(uint32[]) selector 0x883bdbfd), so it prints ~ the V2 market.

Attacker (1 ETH capital):

  1. Buys 268.42 FARM for 0.6828 WETH on the correctly-priced Uni V2 WETH/FARM pair.
  2. controller.deposit(strategy, router, 0, 0, abi.encode(Call[{settleTransferFrom(FARM, this, strategy)}])) — permissionless. Oracle credits the FARM at several ETH.
  3. controller.withdrawWETH burns the inflated shares; GenericRouter transferFroms the strategy's real UNI / AAVE / MKR to the attacker (amounts from the live tx).
  4. Sells those tokens back to WETH, unwraps. Net 5.277 ETH in the PoC.

Controller/oracle impls are unverified; signatures are 4-byte-matched from the trace (deposit=0x71b8dc69, withdrawWETH=0x716e2615, settleTransferFrom=0xc5067ad4).


Background#

Enso StrategyController-managed baskets hold DeFi blue-chips. Deposits go through a GenericRouter Call[] so the controller can pull arbitrary tokens in, then mint shares on the oracle delta, not on a conservative min(spot, TWAP) per asset.

A short TWAP on a thin pool is a spot oracle with extra steps.


The vulnerable code#

RECONSTRUCTED from 4-byte-matched calls + trace (controller/oracle unverified):

SOLIDITY
function deposit(address strategy, address router, uint256 amount, uint256 slippage, bytes calldata data) external payable;
// data = abi.encode(Call[{target: router, callData: settleTransferFrom(FARM, attacker, strategy)}])
// shares minted from oracle value delta of tokens that landed in the strategy

function withdrawWETH(address strategy, address router, uint256 amount, uint256 slippage, bytes calldata data) external;
// burns `amount` shares; data = transferFrom(strategy, attacker, amt) per underlying

Oracle path confirmed in the trace: pool.observe(uint32[]) with short, per-pool-varying windows rather than one long window. Combined with an imbalanced FARM V3 pool this over-values FARM by ~9×.


Root cause#

  1. Permissionless deposit mints on estimated value, not on a bounded basket of known tokens with haircuts.
  2. FARM estimator uses a manipulable / stale-thin TWAP that diverges ~9× from the liquid V2 pool the attacker actually traded.
  3. Withdraw pays real reserves (UNI/AAVE/MKR) against those inflated shares.
  4. No circuit breaker comparing deposit token's V2 price vs the oracle print.

Preconditions#

  • Strategy still holds UNI/AAVE/MKR worth extracting.
  • FARM V3 TWAP remains dislocated vs V2.
  • 1 ETH of attacker capital (the live tx).

Attack walkthrough#

#StepAmount
1Wrap 1 ETH, Uni V2 swap0.682823567760593530 WETH → 268.422447447061825167 FARM
2deposit via GenericRouter settleTransferFromFARM into strategy; inflated shares minted
3withdrawWETH with 3 pulls859.918 UNI, 31.676 AAVE, 3.452 MKR
4Sell all three → WETH, unwrap, net out 1 ETH5.277 ETH profit
CODE
attacker profit (ETH): 5.277209113875295697
[PASS] testExploit()

Diagrams#

sequenceDiagram autonumber actor A as Attacker participant V2 as UniV2 WETH/FARM participant C as StrategyController participant O as EnsoOracle participant S as Strategy vault A->>V2: 0.683 WETH to 268.42 FARM Note over V2: correctly priced A->>C: deposit FARM via GenericRouter C->>O: value the FARM in O-->>C: ~9x vs V2 (~several ETH) C->>S: mint inflated shares A->>C: withdrawWETH burn shares S-->>A: real UNI AAVE MKR A->>A: sell to ETH, net +5.28
flowchart TD B["Buy FARM on liquid V2"] D["deposit mints shares on oracle delta"] T["FARM estimator: short Uni V3 observe"] W["withdraw pays real blue-chips"] B --> D --> T --> W style T fill:#ffcdd2,stroke:#c62828,stroke-width:2px style W fill:#c8e6c9,stroke:#2e7d32

Remediation#

  1. Price deposits off the same venue the token is actually liquid on, or min(V2, V3 TWAP) with a long window.
  2. Haircut / cap any single item's contribution to valueBefore / amountAddedValue.
  3. Whitelist deposit tokens that have deep, consistent oracles; drop FARM-like thin TWAPs.
  4. Compare oracle print vs a spot sanity bound and revert on >X% divergence.

How to reproduce#

BASH
_shared/run_poc.sh 2026-09-EnsoFinance_exp --mt testExploit -vvvvv

Expected: [PASS] testExploit() with profit > 4 ETH (observed 5.277).


Reference: https://x.com/telemnews/status/2097611731707048169

References#


Sources & further analysis#

Reproductions & code

Alerts & third-party analyses

These dashboards index community alerts tweets, post-mortems, and independent write-ups. Reach them through the protocol name above to cross-check this reproduction against other analyses.