RToken is an interest‐bearing token for the RAAC lending protocol. It is modeled similarly to Aave’s AToken, where user balances (and the total supply) are stored in “scaled” form and then converted to underlying units by multiplying with a “liquidity index” (or normalized income).
Balance Calculation:
The contract’s overridden balanceOf and totalSupply functions compute the actual (underlying) amounts by taking the stored scaled balances and multiplying them by the normalized income, obtained via:
Transfer Logic:
The transfer function converts the underlying amount provided by the caller into a scaled amount by dividing by the normalized income from the reserve pool:
In contrast, the transferFrom function uses the stored _liquidityIndex instead:
If the stored _liquidityIndex becomes out‑of‑sync with the current normalized income reported by the reserve pool, transfers executed via transferFrom will convert underlying amounts incorrectly relative to transfer (and relative to balance calculations). Such inconsistencies could lead to inadvertent token losses, unexpected rounding differences, or even potential exploitation if an attacker can force a discrepancy between the two values.
Root Cause:
The contract relies on two distinct sources for its conversion factor:
transfer (and balanceOf, totalSupply, _update) uses the current normalized income from the LendingPool.
transferFrom uses the internally stored _liquidityIndex, which is updated only when updateLiquidityIndex is called by the reserve pool.
In a properly functioning system these values should match; however, if for any reason they diverge (for instance, due to delayed updates or malicious manipulation of the reserve pool), the conversion in transferFrom will be incorrect.
Potential Impact:
User Losses: A user invoking transferFrom might end up transferring too few or too many tokens compared to their intended underlying amount.
Exploitation: An attacker might leverage any temporary discrepancy between _liquidityIndex and the normalized income to cause transfers that undervalue or overvalue token amounts, thereby disrupting the economic equilibrium.
Protocol Inconsistency: Inconsistent token conversion undermines the reliability of the interest accrual mechanism, affecting all downstream processes that rely on accurate token balances.
Attack Scenario (Hypothetical):
Suppose the reserve pool’s normalized income increases (reflecting accrued interest) but the stored _liquidityIndex is not updated immediately. Then:
A caller using transfer would convert an underlying amount using the higher (current) normalized income.
A caller using transferFrom would use the lower stored _liquidityIndex, resulting in a larger scaled amount being transferred.
This discrepancy could result in the recipient receiving an unexpectedly high underlying amount, potentially allowing an attacker to “drain” tokens from an unwary user or to profit from the rounding differences.
Simplified Foundry test illustrates the potential inconsistency when the stored _liquidityIndex differs from the normalized income returned by the reserve pool. In our PoC, we simulate the scenario by using a mock LendingPool and exposing a setter in our test version of RToken (RTokenMock) to modify the internal liquidity index.
In the PoC, the reserve pool’s normalized income is set to 2e27, while initially the stored liquidity index is 1e27.
The transfer function correctly converts an underlying amount (e.g., 50e18) using the normalized income, resulting in the recipient receiving the intended underlying amount.
However, in transferFrom, we manually set the stored _liquidityIndex to 1.5e27, causing the conversion to differ. As a result, transferring 25 underlying units ends up converting to a scaled amount based on 1.5e27 rather than 2e27, yielding a different underlying value (~33.33 tokens instead of 50 tokens).
This inconsistency proves that if the stored liquidity index and the reserve pool’s normalized income diverge, transferFrom will yield incorrect underlying transfers.
Recommended Fix:
To ensure consistent behavior across all transfer functions, both transfer and transferFrom must use the same conversion factor. The fix is to update transferFrom so that it uses the current normalized income from the reserve pool rather than the stored _liquidityIndex. For example, modify:
to
This change guarantees that all conversions from underlying amounts to scaled amounts are consistent, preventing potential token losses or exploitation due to conversion discrepancies.
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.