In the landscape of DeFi yield-bearing assets, developers typically encounter two types of tokens: those that increase in value by increasing their price (like RocketPool’s rETH), and those that increase in value by increasing the quantity of tokens in your wallet. The latter are known as rebasing tokens.
While rebasing tokens like AaveV3 aTokens and Lido stETH offer a seamless experience for end-users, they introduce significant complexity for smart contract integrations. Because these tokens represent 1:1 ownership of a fluctuating pool of assets, their balances are not stored as simple integers. Instead, they are calculated on-the-fly using an underlying, per-user "share" count and a global decimal "index."
This dynamic calculation introduces rounding errors that, while negligible in value, can cause logic reverts or leave "dust" in contracts. Understanding how these protocols handle the correspondence between shares and balances is essential for building safe, production-ready vaults and strategies.
The 4 Quirks
The rounding involved in the share-balance conversion breaks some properties that would otherwise be obvious for standard ERC20s, and which developers might be relying on to ensure correctness of their logic. We have identified 4 such "quirks":
- Balance deltas can be different from the transferred amount, both for sender and for receiver.
- The sum of the two balances can change, i.e. the balance deltas may not be equal for sender and receiver.
- Transferring one's whole balance in one go is always possible, but might leave dust behind.
- Splitting one's whole balance in two parts, and transferring both, is not always possible.
We have investigated these properties, and have precisely bounded the resulting "deviation" from the non-rebasing behaviour. The following table summarizes the results. Note that Aave aTokens and Lido stETH employ different rounding conventions:

1. Directional Rounding: Who Pays the Weis?
When a user calls transfer(amount), the protocol must determine how many shares (σ) to move. Since shares and the index are rarely perfectly divisible, the protocol must choose a rounding direction. If a is the transferred amount and i is the (decimal) index:
- AaveV3 (Rounding UP): Uses σ=⌈a/i⌉. Aave prioritizes the recipient, ensuring they get at least the requested amount.
- Lido stETH (Rounding DOWN): Uses σ=⌊a/i⌋. Lido prioritizes the sender, ensuring they never spend more than requested.
For both protocols, the maximum deviation turns out to be ⌈i⌉ weis: a larger index amplifies the rounding error. The index of Lido stETH, for example, is around 1.228 as of this writing, which implies a maximum deviation of 2 weis.
2. The 1-Wei Flutter
If we consider balances as decimal numbers with full precision, we see that sender and receiver may well have initial balances with different fractional parts. Then, when applying the same delta to both, calculated as σ⋅i (also a decimal), one of the balances might cross an integer threshold thanks to the fractional part of σ⋅i, while the other might not. Therefore, when finally rounding down, we might see that the two (rounded, integer) balances have not changed by the same quantity: one delta might be bigger than the other by one wei.
Our internal fuzz tests show that, in both protocols, the sum of balances stays unchanged roughly 66% of the time, whereas 33% of the time it changes by ±1 wei.
- Best Practice: Do not rely on sender's and receiver's balance differences being equal. Allow for a 1-wei tolerance.
3. The "Ghost" Share (Lido Dust)
Lido's downward rounding creates a "dust" problem. When a contract attempts to transfer its entire stETH balance b, the rounding logic σ=⌊b/i⌋ typically results in only s−1 shares being moved, where s is the user's total share count.
- The Result: The contract successfully sends the token balance but is left holding 1 share of dust.
- Integration Impact: If your vault logic requires a zero-balance state to close a position or delete a mapping, this 1-wei "ghost" share will prevent the state from clearing.
4. The "Split-Transfer" Revert (Aave Risk)
This is perhaps the most dangerous pitfall for developers. Imagine a contract that calculates its total aToken balance and tries to send 5% to a treasury and 95% to a user.
Because Aave rounds up the shares required for each individual transfer, this can accumulate positive rounding errors and eventually revert on the last transfer:
.png)
In roughly 66% of cases, the sum of these rounded-up shares will exceed the contract's total share balance. The second transaction will revert with "Insufficient Balance," even if your balance-tracking logic says you have enough.
The Aave v3.5 Upgrade: Why the Maths Changed
It is worth noting that Aave’s current behavior is relatively new. Prior to the Aave v3.5 upgrade (August 2025), aToken transfers used "half-up" (banker's) rounding.
The 3.5 upgrade was executed via Aave's governance system, effectively changing the underlying math for thousands of existing integrations "under their feet."
- The Good: This upgrade moved Aave toward the ERC-4626 philosophy of explicit, directional rounding. You now know for a fact that a recipient will never get less than they were sent.
- The Risk: By choosing a side, the protocol creates a permanent "rounding bias" that accumulates. As we saw in Quirk #4, always rounding shares up means that a series of small transfers will often cost more than a single large one.
The Wrapper Shortcut: stETH vs. aTokens
To avoid these complexities, Lido provides wstETH, a non-rebasing wrapper that maps 1:1 to underlying shares; using it bypasses the 4 Quirks entirely by keeping balances static. However, Aave does not offer an official "out-of-the-box" static wrapper for aTokens. Developers integrating Aave must either build their own custom wrapper or manually account for the rounding-induced reverts and "flutter" described above.
Conclusion: Mastering Rebasing Integrations
Rebasing tokens are a powerful tool for UX, but they break the "integer-exact" mental model of standard ERC20 tokens. When building integrations, keep these four fundamental deviations in mind:
- Deltas are not Amounts: Expect that balanceOf(user) will change by slightly more (Aave) or slightly less (Lido) than the amount passed to the transfer function.
- Accounting is not Zero-Sum: The global sum of all user balances is not a constant. It "flutters" by 1 wei as rounding errors shift between accounts. Use approximate equality in your unit tests.
- Dust is Persistent: In Lido, transferring your "max balance" usually leaves 1 share behind. If your contract logic depends on a 0-balance state (e.g., to close a vault), you must explicitly sweep the remaining shares.
- Splits are Dangerous: In Aave, the "Rounding Up" bias makes splitting a balance into multiple transfers a high-risk operation. The final transfer will likely revert because the previous hops "over-consumed" the underlying shares.
The Golden Rule: As highlighted by the existence of wstETH, the safest path is to perform internal accounting in shares rather than token amounts. Shares are the only source of truth in the contract's storage; the token balance is just a moving, rounded projection of that truth.
