code-423n4 / 2024-07-reserve-validation

0 stars 0 forks source link

in `RTokenP1::issueTo` wrong Dev assumptions about new RToken holders getting revenue from the past #164

Closed c4-bot-3 closed 1 month ago

c4-bot-3 commented 1 month ago

Lines of code

https://github.com/code-423n4/2024-07-reserve/blob/3f133997e186465f4904553b0f8e86ecb7bbacbf/contracts/p1/RToken.sol#L124-L128

Vulnerability details

Impact

New RToken issuers will get rewards from the past due to outdated Melt state, leading to loss of funds for old users

Proof of Concept

in RTokenP1issueTo whenever a user calls issueTo Furnace.melt is not called

Although, Devs assume here that the Furnace is Up-To-Date

This will lead to a state where:

Although the same case and logic of vesting RSR rewards is the same of melt operations, yet RSR contract calls _payoutRewards in every stake operation (which is expected and the good behavior)

File: StRSR.sol
227:     function stake(uint256 rsrAmount) public {
228:         _notZero(rsrAmount);
229: 
230:         _payoutRewards();

yet we don't do that in RToken::issueTo which will lead to the described behavior above and loss of funds for old genuine users

Assessed type

Context

Al-Qa-qa commented 1 month ago

My initial concern about this was how the comments assumed that melting is up-to-date on his comments

But this is harmful and unfair on its own to old holders and profitable to new issuers due to not calling melt during issuance

the logic is implemented correctly on RSR rewards payout during staking, but on the contrary its not for issuance, although its clearly mentioned in the comments that its assumed as invariant that its up-to-date

thereksfour commented 1 month ago

rToken will be added to AssetRegistry as an asset, and assetRegistry.refresh() will call RTokenAsset.refresh(), thereby calling melt to make it up to date.

        assets[0] = new RTokenAsset(components.rToken, params.rTokenMaxTradeVolume);
        assets[1] = rsrAsset;

        // Init Asset Registry
        components.assetRegistry.init(main, assets);
    function refresh() public virtual override {
        // No need to save lastPrice; can piggyback off the backing collateral's saved prices

        furnace.melt();
        if (msg.sender != address(assetRegistry)) assetRegistry.refresh();

        cachedOracleData.cachedAtTime = 0; // force oracle refresh
    }