In the current implementation, the calculation of decimalOffset
is dynamic and based on the difference between SYSTEM_DECIMALS
(18) and the decimals of the indexToken
:
This offset is used in the formula:
The purpose of adding 10 ** decimalOffset
is to introduce a virtual share adjustment to mitigate donation attacks.
OpenZeppelin ERC-4626 Documentation
The value of decimalOffset
depends on indexToken.decimals()
, leading to inconsistent behavior across different tokens.
If indexToken.decimals()
is 18, decimalOffset
is 0
, effectively bypassing the intended protection.
If indexToken.decimals()
is 6, decimalOffset
is 12
, which may be overly restrictive.
IERC20Metadata(vault.indexToken).decimals()
The decimals()
function returns the sum of the asset token’s decimals and decimalsOffset
:
decimalsOffset
is set by the deployer of the ZLP vault.
https://github.com/Cyfrin/2025-01-zaros-part-2/blob/35deb3e92b2a32cd304bf61d27e6071ef36e446d/src/zlp/ZlpVault.sol#L74
If an 18-decimal asset is used, decimalsOffset
must be set to zero to ensure that indexToken.decimals()
remains 18.
This means that the offset used to mitigate donation attacks will also be zero, nullifying its intended protection.
A decimalOffset
of 0
results in an added value of 1
, offering no real defense.
A decimalOffset
of 1
or 2
introduces a minor buffer but still allows donation attacks.
The inconsistency makes the protocol vulnerable in some cases and overly strict in others.
The protocol includes a check that reverts deposits if they result in zero shares, preventing donation attacks. However, this allows attackers to create a denial-of-service (DoS) scenario for users with small balances by increasing the minimum asset deposit required to receive at least one share. This issue arises because, for tokens with 18 decimals, no effective offset is applied, making donations inexpensive.
In the createZlpVaults
function used for testing, the decimalsOffset
is determined by Constants.SYSTEM_DECIMALS - vaultsConfig[i].decimals
:
https://github.com/Cyfrin/2025-01-zaros-part-2/blob/35deb3e92b2a32cd304bf61d27e6071ef36e446d/script/vaults/Vaults.sol#L109
For an asset with 18 decimals:
The decimalsOffset
in the vault is calculated as Constants.SYSTEM_DECIMALS - 18 = 0
.
The indexToken
decimals become 18 + 0 = 18
.
The decimalOffset
used for donation protection is 18 - 18 = 0
.
This means that for assets with 18 decimals, no effective offset is applied, making donation attacks inexpensive and reducing the protocol’s protection against such exploits.
To ensure consistent and effective mitigation of donation attacks, we propose replacing the dynamic decimalOffset
with a fixed value.
Instead of computing decimalOffset
dynamically, define it as a constant:
Note: An offset of 3
forces an attacker to make a donation 1,000 times as large.
Then modify the calculation as follows:
The contest is live. Earn rewards by submitting a finding.
This is your time to appeal against judgements on your submissions.
Appeals are being carefully reviewed by our judges.