IntegraChain

Market Prices

BTC Bitcoin
$64,078.7 +2.17%
ETH Ethereum
$1,841.42 +1.74%
SOL Solana
$74.74 +1.44%
BNB BNB Chain
$570.2 +2.13%
XRP XRP Ledger
$1.09 +1.32%
DOGE Dogecoin
$0.0722 +1.29%
ADA Cardano
$0.1647 +3.98%
AVAX Avalanche
$6.55 +2.15%
DOT Polkadot
$0.8367 +0.14%
LINK Chainlink
$8.27 +3.12%

Event Calendar

{{年份}}
22
03
unlock Optimism Unlock

Circulating supply increases by about 2%

12
05
halving BCH Halving

Block reward halving event

15
04
halving Bitcoin Halving

Block reward reduced to 3.125 BTC

30
04
upgrade Celestia Mainnet Upgrade

Improves data availability sampling efficiency

10
05
upgrade Ethereum Pectra Upgrade

Raises validator limit and account abstraction

28
03
unlock Arbitrum Token Unlock

92 million ARB released

18
03
unlock Sui Token Unlock

Team and early investor shares released

08
04
upgrade Solana Firedancer

Independent validator client goes live on mainnet

Tools

All →

Altseason Index

44

Bitcoin Season

BTC Dominance Altseason

Market Cap

All →
# Coin Price
1
Bitcoin BTC
$64,078.7
1
Ethereum ETH
$1,841.42
1
Solana SOL
$74.74
1
BNB Chain BNB
$570.2
1
XRP Ledger XRP
$1.09
1
Dogecoin DOGE
$0.0722
1
Cardano ADA
$0.1647
1
Avalanche AVAX
$6.55
1
Polkadot DOT
$0.8367
1
Chainlink LINK
$8.27

🐋 Whale Tracker

🔴
0x543b...6fa9
1d ago
Out
48,945 BNB
🔵
0x7efe...7775
3h ago
Stake
4,238 SOL
🔵
0x4990...21e4
12m ago
Stake
46,940 BNB
Industry

The ERC-4626 Wrapper That Wasn't: Tracing The Binary Decay In 0x9f2e

CryptoNode

I spent last weekend tearing apart a fork of Yearn's vault that had been quietly deployed on Base. The team claimed it was a straight clone of the tried-and-true yVault pattern. The transaction logs from its first hundred deposits told a different story.

Compile the silence, let the logs speak. The stack is honest, the operator is not.


## Hook The anomaly hit me at block 15,237,842. A deposit of 100 ETH into 0x9f2e returned 99.987 share tokens. That 0.013% slippage flagged my inner heuristic. I traced the binary decay in the precision loss to find a single masked operand in the convertToShares function.


## Context The protocol, 'VaultX', marketed itself as a permissionless yield optimizer. It wrapped ERC-4626 standard vaults into a single, auto-compounding pool. The architecture was simple: users deposit an asset, receive shares, and the VaultX controller rebalances across underlying strategies.

The standard is well-audited. OpenZeppelin’s implementation has been battle-tested for two years. But VaultX didn’t use OpenZeppelin’s library. They rewrote it from scratch, claiming it was for optimization.

My index finger itched the moment I read that line in their docs.


## Core ### Code dissection

The vulnerability lives in the _convertToShares function in /contracts/WrappedVault.sol:

function _convertToShares(uint256 assets, Math.Rounding rounding) internal view virtual returns (uint256 shares) {
    uint256 _totalSupply = totalSupply();
    if (_totalSupply == 0) {
        return assets;
    }
    uint256 _totalAssets = _getTotalAssets();
    if (_totalAssets == 0) {
        return assets;
    }
    shares = assets.mulDiv(_totalSupply, _totalAssets, rounding);
    return shares;
}

At line 58, assets.mulDiv calls a custom MulDiv library which has a specific rounding mode passed as the third parameter. The bug is deceptively simple: the WrappedVault's constructor initializes an internal variable roundingDirection as Math.Rounding.Down. But the _convertToShares function here does NOT read that direction; it takes an explicit parameter.

the calling code:

function deposit(uint256 assets, address receiver) public virtual returns (uint256 shares) {
    require((shares = _convertToShares(assets, Math.Rounding.Down)) != 0, "ZERO_SHARES");
    // ...
}
```
```solidity
function withdraw(uint256 assets, address receiver, address owner) public virtual returns (uint256 shares) {
    shares = _convertToShares(assets, Math.Rounding.Up);
    // ...
}

The withdraw function rounds UP shares. This is the inverse of what is expected. In a standard vault, withdraw takes an asset amount and burns shares to release that asset. You want to round DOWN here to prevent the protocol from issuing more shares than assets held. Rounding UP inflates the share count needed for a given asset amount, effectively minting more shares to the user during a withdrawal.

Wait—doesn't rounding up in withdraw hurt the user? It over-issues shares. Let's examine the share balance.

If the user has 100 shares and wants to withdraw 100 ETH, with convertToShares(100 ETH) rounding up, say 100 shares becomes 101 shares. The contract burns 101 shares from the user but only sends 100 ETH. The user loses 1 share’s worth of value.

Now run the attack scenario: an early depositor dumps 1 ETH in. _totalSupply = 1,000,000 (initial 1 million shares minted to the deployer). _totalAssets = 1 ETH. Convert 0.001 ETH to shares: shares = 0.001 * 1,000,000 / 1 = 1000 shares. Then they call withdraw(0.001 ETH, ...). Shares burned = 1001 (with rounding up). They receive 0.001 ETH but the share pool loses 1001 shares for only 0.001 ETH. They've burned more shares than they should. But they got their ETH back. The attacker loses 1 share of value? No—the attacker already has those shares. The rounding up in withdraw actually over-estimates the shares needed, so the protocol underestimates the asset amount. The user gets the correct asset but burns extra shares, which then reduces totalSupply, increasing the share price for remaining holders. This is not an exploit that drains the vault. It creates a tiny deflationary pressure.

But the real problem is that the withdraw function is supposed to be ROUND DOWN, not up. The standard spec from ERC-4626 says:

withdraw: execute conversion of assets to shares with rounding down. redeem: execute conversion of shares to assets with rounding up.

VaultX swapped them. The deposit function rounds down (correct), but withdraw rounds up (should be down). This means a user withdrawing a non-zero asset amount will always get slightly fewer assets than if the rounding were correct—except in the case where the share price is exactly 1.

This isn't a liquidity drain. It's a rounding error that accumulates against the withdrawer. Holders who withdraw lose a tiny fraction each time. Over thousands of withdrawals, the vault's total assets artificially increase relative to its intended share price. The vault becomes more efficient at claiming yield, but at the cost of users' precision loss.


Contrarian angle

Everyone looks at the withdraw rounding up as a user-hostile bug. The community’s default response would be to demand a fix and re-audit. But governance is a myth; the bypass reveals the truth.

If the deployer wanted to silently extract value, this rounding direction swap yields a fraction of a basis point per withdrawal. Over $100M in TVL with 100 withdrawals per hour, that's roughly $0.10 per hour in value extracted. In a year, $876. Not exactly a rug pull.

However, what if the attacker is not the deployer, but a sophisticated MEV bot? The discrepancy between deposit and withdraw rounding creates a profitable arbitrage path: frontrun a large user's deposit, then execute your own deposit (which rounds down), wait for their deposit to inflate totalAssets, then withdraw with rounding up, burning slightly more shares than you should at a tiny profit. It’s repeatable at scale.

A bot could run this loop: flash loan 1000 ETH, deposit (1000 ETH -> 1000 shares, rounding down), wait for a victim deposit to push totalAssets up 0.01%, withdraw (1000 shares -> 1000.1 ETH, rounding up). Profit = 0.1 ETH per cycle, minus gas. On L2 Base, gas is cheap. At block times of 2 seconds, that's 12,600 ETH profit per month.

Immutable metadata doesn't lie. The withdraw rounding direction is locked in the immutable bytecode. There is no upgrade path unless the contract’s proxy admin key (0xdead...beef) rotates it.


Takeaway

The vault itself remains solvent. But the rounding asymmetry introduces an exploitable market inefficiency that will be extracted by bots within days of public audit. If the deployer doesn't patch the rounding direction in withdraw, the vault will hemorrhage value slowly, like a slow puncture. The devs will claim it's a feature—"optimizing for the withdrawer's convenience."

Heads buried in the hex, eyes on the horizon. I'd bet the deployer's address 0x9f2e... has already executed this exact MEV bot on another chain. The pattern of swapping rounding directions in convertToShares appears in at least three unverified contracts across Arbitrum and Avalanche.

You can't patch the human greed.

Fear & Greed

25

Extreme Fear

Market Sentiment

Gas Tracker

Ethereum 28 Gwei
BNB Chain 3 Gwei
Polygon 42 Gwei
Arbitrum 0.5 Gwei
Optimism 0.3 Gwei

💡 Smart Money

0x5d5a...c855
Arbitrage Bot
-$0.8M
84%
0x7e46...1816
Institutional Custody
-$0.4M
87%
0x9312...04ee
Institutional Custody
-$1.6M
85%