Calm Basil Lark
Medium
A noun owner can wait for the last moment to return his nouns and profit from all benefits one more day by avoiding paying one tick. To do that, the malicious user just has to front run forwardAll()
by calling cancelStream()
and return his noun before the current tick value is sent to the DAO.
There is no protection logic on cancelStream
, it can be called whenever a noun holder wants during a day to return his noun and stop the stream. A malicious user can use his noun one day without having to pay the tick he is supposed to.
- attacker holds a noun
- attacker has a current stream open for this
nounId
- someone (likely the DAO) is about to execute
forwardAll()
which send current tick value to DAO treasury - OR
settleAuction()
is about to be called and will executeforwardAll()
- attacker front-run
forwardAll
by callingcancelStream()
- attacker avoid paying 1 tick even though he kept his noun all day.
DAO looses one day of tick for a given noun. The malicious user can use his noun to vote/propose and interact with nouns DAO one more day for free.
Historically it happened that Nouns have been sold to 30 ether
In current deployment condition, 80 % will go to StreamEscrow.sol
-> 26 ETH.
And the stream is about 4 years, so 1460 ticks. Meaning 1 ticks = 0.018 ETH = 65$ (with ETH/USD 3600$)
One tick = 0.068% of total ticks (1460)
Per Sherlock rules; it's enough to qualify as a Medium :
The protocol loses more than 0.01% and more than $10 of the fees.
It's possible to mitigate with differents approach :
- add a maximum time after
lastForwardTimestamp
incancelStream
.
require(lastForwardTimestamp + 50400 < block.timestamp) // 14h as max time given to return the noun
For example, if you don't return your noun 14h (example value, to be chosen by the DAO) after last update of lastForwardTimestamp
(forwardAll()
call) you have to wait next tick to return it. As forwardAll()
is public and will be called almost everyday, not an issue to lock.
- add a time logic to pay a part of the tick in
cancelStream()
, meaning user will be refund a fraction of the current tick, depending on the difference betweenblock.timestamp
andlastForwardTimestamp