Closed code423n4 closed 1 year ago
Had a little trouble following this one, but if I am understanding this correctly, it most likely should be QA due to the nature of the rounding error and this having significantly less impact as the numbers scale.
For example, the pctRate provided isn't in the expected range of values per the docs
Default value: `2.5e16` = 2.5% per hour
Mainnet reasonable range: 1e15 to 1e18 (0.1% per hour to 100% per hour)
tbrent marked the issue as disagree with severity
tbrent marked the issue as sponsor confirmed
0xean changed the severity to QA (Quality Assurance)
Lines of code
https://github.com/reserve-protocol/protocol/blob/c4ec2473bbcb4831d62af55d275368e73e16b984/contracts/libraries/Throttle.sol#L62 https://github.com/reserve-protocol/protocol/blob/c4ec2473bbcb4831d62af55d275368e73e16b984/contracts/libraries/Throttle.sol#L73
Vulnerability details
Description
Function
Throttle::currentlyAvailable
calculates the current amount available for consumption. Even though the output is right, it has a problem when it is used insideThrottle::useAvailable
Considering
available = throttle.lastAvailable + (limit * delta) / ONE_HOUR;
, we can see there is a possible rounding error iflimit * delta < ONE_HOUR
, thenavailable = throttle.lastAvailable
. This rounding error is inevitable, but we must notice thatdelta = uint48(block.timestamp) - throttle.lastTimestamp;
.If then we consider that, inside
Throttle::useAvailable
the last line isthrottle.lastTimestamp = uint48(block.timestamp);
, we can force this rounding, DOSing the Throttle until its amount available become 0.Impact
DOS of
Throttle.lastAvailable
update iflimit * delta < ONE_HOUR
. We can DOS this update forever if we are allowed to sentamount = 0
POC
Let's suppose we have a Throttle with next values in Ethereum network:
And that supply is
280_000 * 1e18
If this was the case, the
limit = (supply * params.pctRate) / FIX_ONE_256 = 280_000 * 1e18 * 1000 / 1e18 = 280
Let's suppose that 1 block has passed, then
block.timestamp = 12
. If we callcurrentlyAvailable(throttle, limit)
:delta = block.timestamp - throttle.lastTimestamp = 12 - 0 = 12
available = throttle.lastAvailable + (limit * delta) / ONE_HOUR = 1000 + (280 * 12) / 3600 = 1000 + 3360 / 3600 = 1000
.Considering
amount = 1
, the amount available would be updated butthrottle.lastTimestamp = uint48(block.timestamp) = 12
not.If in next block we try to do the same call, the only change of state would be
lastTimestamp = uint48(block.timestamp) = 24
. Meaning we can DOSlastTimestamp
for 250 blocks. Which is equal to 50 minutes.Mitigation steps
Modify
Throttle::currentlyAvailable
andThrottle::useAvailable
in order to avoid this error. In particular I would suggest next refactor:Assessed type
Other