When the protocol calculates the holders' and pendingStakes' eligible fee in distributeFees(), it does not consider for how long has the pendingStake been in the system. Due to this -
A malicious user can monitor the mempool for any function call that generates a fee for the protocol,
Front-run it to register a stake in the system via increasePosition(). This makes him eligible to get a share of the fee.
decreasePosition() after 1 DAY and get back his staked amount + gained fee.
Root Cause: The age of stakes are never considered in the protocol, apart from the distinction between confirmed & pending stake holders. This is problematic even in the normal course of events since this results in dilution of holders' stake by pendingStake holders (explained below).
Let us see an example for clarity:
Let's start with no fee present in the system
Alice stakes 100 TST at time t + 0
Bob stakes 100 TST at time t + 1.01 days. This could be under the normal flow of protocol usage OR could be a malicious front-running action.
A fee of amount 500 EUROs is credited into the system via some fee-generating activity at time t + 1.02 days
Alice is eligible for fee-share of
Timestamp t + 2.02 days is reached
Bob is eligible for the same fee-share of in spite of having staked just before the fee had come into the system. His action diluted Alice's share even though she had staked much earlier and was in essence a confirmed holder when the fee first came into the system.
Patch the following diff inside test/liquidationPool.js and run via npx hardhat test --grep 'allows front-running & fee collection'. The test will pass all assertions.
Malicious users are able to game the system and claim a portion of the holder fee.
This dilutes the fee of a rightful holder who has staked for a longer duration.
Even considering the normal flow of events involving non-malicious users, we are diluting older user's fee-share if a newer user calls increasePosition() before a fee-generating action. This totally disregards the age of stakes.
Hardhat
The easiest way to remedy this would be -
to have the function, LiquidationPoolManager::distributeFees() triggered every time any fee-generation activity credits LiquidationPoolManager.sol with EUROs. This would require EUROs (the fee-token) to be implemented as a token which supports callback, like an ERC-777 for example.
then, we ought to modify LiquidationPool::distributeFees() like so:
In case the protocol decides against having an ERC-777 style token, then another way to implement the fix would be to -
track each fee-generating action's value and timestamp.
track each timestamp of when the pendingStake got consolidated into a confirmed holder stake.
Then, at the time of distributing fees,
consolidatePendingStakes()
compare the fee-timestamp and the confirmed holder stake conversion timestamp. Credit EUROs only if their difference is more than 1 DAY in the past.
This of course means that just like in the above style, while calculating tstTotal, we need to consider only eligible stakes' TST and not use getTstTotal().
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.