The rateLimiter functionality in the Throttle.sol contract can be bypassed. The issuance rate limiter can be bypassed by sandwiching the redemption transaction with two issuance transactions by a issuer.
Let's consider the following scenario:
hourly issuance rate is 200
hourly redemption rate is 300
Consider the lastAvailabe amounts are as follows:
issuanceThrottle.lastAvailable is 200
redemptionThrottle.lastAvailable is 300
Now let's consider the following transaction execution:
1. The User B decides to call the redmption of 300 RTokens.
The current state of respective lastAvailable amounts are as follows:
issuanceThrottle.lastAvailable is 200
redemptionThrottle.lastAvailable is 300
2. User A sees the above transaction and front runs it by calling the issuance for 100 RTokens.
The current state of respective lastAvailable amounts are as follows:
issuanceThrottle.lastAvailable is 100
redemptionThrottle.lastAvailable is 400
3. Now the User B's redemption transaction of 300 RTokens is executed.
The current state of respective lastAvailable amounts are as follows:
issuanceThrottle.lastAvailable is 400
redemptionThrottle.lastAvailable is 0
4. Now the User A sanwitches User B's redemption transaction by calling the issuance of 200 RTokens.
The current state of respective lastAvailable amounts are as follows:
issuanceThrottle.lastAvailable is 0
redemptionThrottle.lastAvailable is 200
In this scenario, User A was able to issue a total of 300 RTokens (100 + 200) within a single block, effectively bypassing the hourly issuance rate limit of 200.
The reason this exploit works is due to the way the Throttle.useAvailable function updates the lastAvailable values for issuance and redemption. When a redemption occurs, the issuanceThrottle.lastAvailable value is increased by the redemption amount, allowing for a higher issuance amount.
This behavior can be exploited by alternating between issuance and redemption operations, effectively accumulating a higher available amount for issuance than the configured hourly rate limit as explained in the previous example.
Proof of Concept
function useAvailable(
Throttle storage throttle,
uint256 supply,
int256 amount
) internal {
// untestable: amtRate will always be > 0 due to previous validations
if (throttle.params.amtRate == 0 && throttle.params.pctRate == 0) return;
// Calculate hourly limit
uint256 limit = hourlyLimit(throttle, supply); // {qRTok}
// Calculate available amount before supply change
uint256 available = currentlyAvailable(throttle, limit); //@audit-info - get teh avialbel amount
// Update throttle.timestamp if available amount changed or at limit
if (available != throttle.lastAvailable || available == limit) {
throttle.lastTimestamp = uint48(block.timestamp);
} //@audit-info - update teh last timestamp
// Update throttle.lastAvailable
if (amount > 0) {
require(uint256(amount) <= available, "supply change throttled");
available -= uint256(amount);
// untestable: the final else statement, amount will never be 0
} else if (amount < 0) {
available += uint256(-amount);
}
throttle.lastAvailable = available; //@audit-info - update the throttle last available amount
}
To mitigate this vulnerability, the Throttle.sol contract should be modified to prevent the issuanceThrottle.lastAvailable value from increasing beyond the configured hourly issuance rate limit, regardless of any redemption operations. One possible solution could be to introduce separate checks or constraints to ensure that the lastAvailable values for issuance and redemption are capped by their respective hourly rate limits.
Lines of code
https://github.com/code-423n4/2024-07-reserve/blob/main/contracts/libraries/Throttle.sol#L37-L65
Vulnerability details
Impact
The
rateLimiter
functionality in theThrottle.sol
contract can be bypassed. Theissuance rate limiter
can be bypassed bysandwiching
theredemption transaction
withtwo issuance transactions
by aissuer
.Let's consider the following scenario:
Consider the
lastAvailabe
amounts are as follows:Now let's consider the following transaction execution:
In this scenario,
User A
was able toissue a total of 300 RTokens (100 + 200)
within a single block, effectivelybypassing the hourly issuance rate limit of 200
.The reason this exploit works is due to the way the
Throttle.useAvailable
functionupdates
thelastAvailable values
forissuance and redemption
. When aredemption occurs
, theissuanceThrottle.lastAvailable value is increased by the redemption amount
, allowing for ahigher issuance amount
.This behavior can be exploited by alternating between issuance and redemption operations, effectively accumulating a higher available amount for issuance than the configured hourly rate limit as explained in the previous example.
Proof of Concept
https://github.com/code-423n4/2024-07-reserve/blob/main/contracts/libraries/Throttle.sol#L37-L65
Tools Used
VSCode and Manual Review
Recommended Mitigation Steps
To mitigate this vulnerability, the
Throttle.sol contract
should be modified toprevent the issuanceThrottle.lastAvailable value
fromincreasing beyond
theconfigured hourly issuance rate limit
, regardless of any redemption operations. One possible solution could be tointroduce separate checks or constraints
toensure that the lastAvailable values
forissuance and redemption are capped by their respective hourly rate limits
.Assessed type
Other