Description:
The formula for calculating costInEuros
in the distributeAsset()
function is incorrect. It erroneously multiplies _portion
by 10e(18 - asset.token.dec)
instead of the correct 1e(18 - asset.token.dec)
, resulting in a cost that is an order of magnitude higher, causing the following if block to run and make erroneous changes to stakers position.
Impact:
This error wipes out the EUROs position of holders without compensating them with the corresponding reward.
Proof of Concept:
Vulnerability Breakdown in Pseudocode
Given the following scenario:
Liquidated Asset: wBtc = 0.005e8 (one token for simplicity)
btcusd price = 40000e8
wbtc decimals = 8
eurusd price = 1.12e8
_hundredPC = 1e5
_collateralRate = 110000
Position0.EUROs value = 100e18 * 1e0 * 1.12e8 * 1e-8 = 112e8
Position1.EUROs value = 0 * 1e0 * 1.12e8 * 1e-8 = 0
Position2.EUROs value = 100e18 * 1e0 * 1.12e8 * 1e-8 = 112e8
Position3.EUROs value = 5e18 * 1e0 * 1.12e8 * 1e-8 = 5.6e8
Position4.EUROs value = 100e18 * 1e0 * 1.12e8 * 1e-8 = 112e8
Total Euros Locked = 341.6e8
Note:
- Focus is on EUROs as its the only token in the pool that contributes to the objective function of the pool
distributeAssets() logic breakdown
stakeTotal = getStakeTotal() == 341.6e8
Note:
The bugs in getStakeTotal() caused by incorrect comparison of TST and EUROs, and inaccurately calculation of total stake is assume to have been fixed, in effort to show differences in root causes. However, the Proof of Code, shows this bug persist with the original contract state.
burnEuros == 0; // tracks EUROs used up by the pool, to be burnt for supply balance
nativePurchased == 0; // tracks native token bought by the pool -- not relevant here
For Loop: loop through all holders and distribute rewards to them
{
Loop 1 //for position 0
_position = Position0
_positionStake = stake(_position) == 112e8
(if 112e8 > 0) {
Loop 1 (for wbtc)
asset = wbtc
(if 0.005e8 > 0) {
_portion = 0.005e8 * 112e8 / 341.6e8 == 0.00163934e8
costInEuros = 0.00163934e8 * 10e10 * 40000e8 / 1.12e8 * 1e5 / 110000 == 532.25325e18 // Should be 53.25e18
(if 532.25455e18 > 100e18) { // This condition should have been false
_portion = 0.00163934e8 * 100e18 / 532.25455e18 == 0.00030799.924585e8 // 0
costInEuros = _position.EUROs == 100e8 ;
}
Position0.EUROs -= 100e8 // wipes out stakers position
burnEuros += 100e8
(if reward native is token) {
// We are not dealing with a native token
}
(if reward native is erc20) {
// transfers 0 tokens to the pool.
}
}
}
// Update Position0
}
// After looping through all holders it:
// burns the burnEuros
// Return native tokens that weren't bought
Key:
{logic} : Logic block
bold font : code variables
(conditional statements)
Proof of Code:
The provided test suite demonstrates the vulnerability's validity and severity.
Due to the file size required to run this PoC, the suite is hosted on Github.
To run the PoC, clone the repository.
Minor changes, such as modifying function visibility, were made to enable successful test runs.
All changes and additional files made to the original code are documented in the README and the respective files where the changes are made.
Requirements:
Install Foundry.
Clone the project codebase into your local workspace.
Run the following commands to install dependencies:
Run the following command to execute the PoC:
Tools Used:
Manual review
Foundry
Recommended Mitigation Steps:
Change 10e10
to 1e10
in the costInEuros
calculation.
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.