Calculated from recorded token losses using historical USD prices at the incident time.
0x2836b64a39d5b73d8f534c9fd6c6abd81df2beb7BSC0x2b9bda587ee04fe51c5431709afbafb295f94bb4BSCIn BSC block 39684703, an unprivileged adversary executed a two-transaction just-in-time deposit against DysonVault and extracted 48.978729275 BNB of net value from incumbent vault depositors. The exploit worked because DysonVault minted shares from a pool value derived from strategy.balanceOf() only, while the strategy had already-earned THE rewards sitting outside that accounting path until anyone could call public harvest().
The result was a stale entry price. The attacker first deposited enough Overnight LP to acquire 804,853,325,834,497 vault shares, then immediately called harvest(), which converted pre-existing rewards into additional LP for the strategy. Because the attacker already owned the overwhelming majority of shares, it captured nearly all of that latent value on withdrawal.
DysonVault is a share-based vault whose deposit pricing depends on the current underlying pool value. Its core valuation path is:
function balance() public view returns (uint256) {
return want().balanceOf(address(this)) + IStrategyDystopia(strategy).balanceOf();
}
function deposit(uint256 _amount) public nonReentrant {
strategy.beforeDeposit();
uint256 _pool = balance();
want().safeTransferFrom(msg.sender, address(this), _amount);
earn();
uint256 _after = balance();
_amount = _after - _pool;
uint256 shares = (_amount * totalSupply()) / _pool;
_mint(msg.sender, shares);
}
Source: DysonVault implementation used by the incident.
That design assumes strategy.balanceOf() fully represents the strategy's economically accrued assets. The assumption is false here. The linked strategy reports only held-and-staked LP as balance, while separately exposing harvestable rewards:
function balanceOf() public view returns (uint256) {
return balanceOfWant() + balanceOfPool();
}
function rewardsAvailable() public view returns (uint256) {
return IRewardPool(rewardPool).earned(address(this));
}
function harvest() external virtual {
_harvest(tx.origin);
}
Source: StrategyCommonSolidlyHybridPoolLP.
The Overnight-specific strategy variant then converts harvested THE into USD+/USDT+ and mints new LP:
IOvernightExchange(overnightUsdPlusMinter).mint(params0);
IOvernightExchange(overnightUsdtPlusMinter).mint(params1);
ISolidlyRouter(dystRouter2).addLiquidity(
usdPlus,
usdtPlus,
stable,
lp0Bal,
lp1Bal,
1,
1,
address(this),
block.timestamp
);
Source: StrategyCommonSolidlyHybridPoolLPOvernight.
The vulnerability is a vault share-pricing error coupled with an unrestricted value-realization hook. DysonVault priced deposits from balance(), and balance() depended on strategy.balanceOf(). In this strategy, balanceOf() omitted already-earned THE rewards because those rewards remained external to the LP position until harvest() called getReward() and converted the output into fresh LP. The strategy exposed harvest() publicly, so any unprivileged caller could trigger that realization at an advantageous moment. This let a same-block depositor buy shares before the pending rewards were reflected, then immediately crystallize them after obtaining dominant ownership. The violated invariant is that a new depositor must not receive shares entitling it to rewards that accrued before the deposit. The code-level breakpoint is DysonVault minting shares from _pool = balance() even though strategy rewardsAvailable() held material value outside balanceOf().
Immediately before the exploit setup transaction, the strategy already controlled a live staked LP position and also had a large unclaimed THE balance in the gauge. The validator replay confirmed strategy.balanceOf() = 1,020,386,490,758 LP-equivalent units while strategy.rewardsAvailable() = 87,796.690518816196085254 THE. Those pending rewards were economically real but absent from the vault's deposit pricing path.
In the first adversary transaction, 0x86710f1f6833391c59e1a8d3fc21bbe6af95c8de43bd45219e670b5ae36d0924, the attacker funded the sequence with 3 BNB, converted into USDT and USDC through public Pancake pairs, minted Overnight receipt tokens, then minted 823,962,027,350,954 LP and deposited them into DysonVault. The balance diff for that transaction shows the DysonVault share mint directly:
{
"token": "0x1561d9618db2dcfe954f5d51f4381fa99c8e5689",
"holder": "0x3877c2c3d75ae80f2ed8e9d4d68e3c1bfc77e5a6",
"delta": "823962027350954"
}
{
"token": "0x2836b64a39d5b73d8f534c9fd6c6abd81df2beb7",
"holder": "0x00db72390c1843de815ef635ee58ac19b54af4ef",
"delta": "804853325834497"
}
Origin: setup transaction balance diff.
The setup trace also shows the stale-pricing arithmetic, with pre-deposit supply 996,722,462,276, attacker deposit amount 823,962,027,350,954, and minted shares 804,853,325,834,497. That gave the attacker roughly 99.87% of the post-deposit share supply before any reward realization occurred.
In the second transaction, 0xbac614f4d103939a9611ca35f4ec9451e1e98512d573c822fbff70fafdbbb5a0, the attacker called the public strategy harvest(). The replay trace shows the critical sequence:
StrategyCommonSolidlyHybridPoolLPOvernight::harvest()
GaugeV2::getReward()
Pair::mint(... amount: 16336129951128242)
GaugeV2::deposit(16336129951128242)
emit StratHarvest(... wantHarvested: 16336129951128242, tvl: 17161112364969954)
Origin: exit transaction trace.
That transaction claimed the already-earned THE, charged fees, converted the remainder into Overnight mint inputs, minted 16,336,129,951,128,242 new LP, and restaked it. Only after that value had been brought on-balance did the attacker withdraw. The same trace shows DysonVault burning 804,853,325,834,497 shares and withdrawing 17,139,886,497,691,096 LP to the attacker-controlled helper, far exceeding the setup deposit.
The exit balance diff closes the loop:
{
"address": "0x4ced363484dfebd0fab1b33c3eca0edca44a346c",
"before_wei": "1317724903000000000",
"after_wei": "53304321053000000000",
"delta_wei": "51986596150000000000"
}
Origin: exit transaction balance diff.
Combining the native balance deltas across the two adversary-crafted transactions yields the stated 48.978729275 BNB net increase. The economic theft is not from a pricing oracle or external market bug; it is from incumbent DysonVault holders whose accrued-but-unharvested rewards were sold to a new entrant at a stale share price.
The exploit used two adversary-crafted transactions in the same block.
Tx 1: 0x86710f1f6833391c59e1a8d3fc21bbe6af95c8de43bd45219e670b5ae36d0924
Tx 2: 0xbac614f4d103939a9611ca35f4ec9451e1e98512d573c822fbff70fafdbbb5a0
The first transaction was the setup leg. The attacker EOA 0x4ced363484dfebd0fab1b33c3eca0edca44a346c funded helper contract 0x00db72390c1843de815ef635ee58ac19b54af4ef, which performed the public swaps and Overnight mints needed to obtain the vault's LP asset. That helper then deposited the LP into DysonVault and received 804,853,325,834,497 shares.
The second transaction was the realization and exit leg. The same helper called public harvest(), causing the strategy to claim 87,796.690518816196085254 THE and convert the harvest into additional LP. It then withdrew the now-upvalued share position, burned the LP, redeemed the resulting Overnight assets, swapped back to WBNB, and returned value to the controlling EOA.
The trace shows the decisive withdrawal amounts:
emit Transfer(... amount: 804853325834497)
StrategyCommonSolidlyHybridPoolLPOvernight::withdraw(17139886497691096)
Pair::transfer(... amount: 17139886497691096)
Pair::burn(0x00Db72390C1843De815ef635EE58Ac19b54AF4EF)
WBNB::withdraw(52008000000000000000)
Origin: exit transaction trace.
The attacker did not require privileged keys, private orderflow, or protocol-admin access. The exploit conditions were fully ACT-compliant: public contracts, public balances, public rewards accounting, and a public harvest() entrypoint.
The measured impact is a net attacker profit of 48.978729275 BNB, represented in metadata as raw smallest-unit amount "48978729275000000000" with decimal 18.
The loss came from incumbent DysonVault depositors whose accrued harvest value was diluted away. The attack turned a 3 BNB setup position into a much larger exit by capturing rewards that had already been economically earned before the attacker entered. The exploit therefore constitutes value extraction from existing vault holders rather than a neutral arbitrage.
0x2836b64a39d5b73d8f534c9fd6c6abd81df2beb70x2b9bda587ee04fe51c5431709afbafb295f94bb40x86710f1f6833391c59e1a8d3fc21bbe6af95c8de43bd45219e670b5ae36d09240xbac614f4d103939a9611ca35f4ec9451e1e98512d573c822fbff70fafdbbb5a0