RocketTokenRPL - inaccurate inflation rate and potential for manipulation lowering the real APY
RocketTokenRPL allows users to swap their fixed-rate tokens to the inflationary RocketTokenRPL ERC20 token via a `swapToken` function. The DAO defines the inflation rate of this token and is initially set to be 5% APY. This APY is configured as a daily inflation rate (APD) with the corresponding `1 day in blocks` inflation interval in the `rocketDAOProtocolSettingsInflation` contract. The DAO members control the inflation settings.\\nAnyone can call `inflationMintTokens` to inflate the token, which mints tokens to the contracts RocketVault. Tokens are minted for discreet intervals since the last time `inflationMintTokens` was called (recorded as inflationCalcBlock). The inflation is then calculated for the passed intervals without taking the current not yet completed interval. However, the `inflationCalcBlock` is set to the current `block.number`, effectively skipping some “time”/blocks of the APY calculation.\\nThe more often `inflationMintTokens` is called, the higher the APY likelihood dropping below the configured 5%. In the worst case, one could manipulate the APY down to 2.45% (assuming that the APD for a 5% APY was configured) by calling `inflationMintTokens` close to the end of every second interval. This would essentially restart the APY interval at `block.number`, skipping blocks of the current interval that have not been accounted for.\\nThe following diagram illustrates the skipped blocks due to the incorrect recording of `inflationCalcBlock` as `block.number`. The example assumes that we are in interval 4 but have not completed it. `3` APD intervals have passed, and this is what the inflation rate is based on. However, the `inflationCalcBlock` is updated to the current `block.number`, skipping some time/blocks that are now unaccounted in the APY restarting the 4th interval at `block.number`.\\n\\nNote: updating the inflation rate will directly affect past inflation intervals that have not been minted! this might be undesirable, and it could be considered to force an inflation mint if the APY changes\\nNote: if the interval is small enough and there is a history of unaccounted intervals to be minted, and the Ethereum network is congested, gas fees may be high and block limits hit, the calculations in the for loop might be susceptible to DoS the inflation mechanism because of gas constraints.\\nNote: The inflation seems only to be triggered regularly on `RocketRewardsPool.claim` (or at any point by external actors). If the price establishes based on the total supply of tokens, then this may give attackers an opportunity to front-run other users trading large amounts of RPL that may previously have calculated their prices based on the un-inflated supply.\\nNote: that the discrete interval-based inflation (e.g., once a day) might create dynamics that put pressure on users to trade their RPL in windows instead of consecutively\\nthe inflation intervals passed is the number of completed intervals. The current interval that is started is not included.\\n```\\nfunction getInlfationIntervalsPassed() override public view returns(uint256) {\\n    // The block that inflation was last calculated at\\n    uint256 inflationLastCalculatedBlock = getInflationCalcBlock();\\n    // Get the daily inflation in blocks\\n    uint256 inflationInterval = getInflationIntervalBlocks();\\n    // Calculate now if inflation has begun\\n    if(inflationLastCalculatedBlock > 0) {\\n        return (block.number).sub(inflationLastCalculatedBlock).div(inflationInterval);\\n    }else{\\n        return 0;\\n    }\\n}\\n```\\n\\nthe inflation calculation calculates the to-be-minted tokens for the inflation rate at `newTokens = supply * rateAPD^intervals - supply`\\n```\\nfunction inflationCalculate() override public view returns (uint256) {\\n    // The inflation amount\\n    uint256 inflationTokenAmount = 0;\\n    // Optimisation\\n    uint256 inflationRate = getInflationIntervalRate();\\n    // Compute the number of inflation intervals elapsed since the last time we minted infation tokens\\n    uint256 intervalsSinceLastMint = getInlfationIntervalsPassed();\\n    // Only update if last interval has passed and inflation rate is > 0\\n    if(intervalsSinceLastMint > 0 && inflationRate > 0) {\\n        // Our inflation rate\\n        uint256 rate = inflationRate;\\n        // Compute inflation for total inflation intervals elapsed\\n        for (uint256 i = 1; i < intervalsSinceLastMint; i++) {\\n            rate = rate.mul(inflationRate).div(10 \\*\\* 18);\\n        }\\n        // Get the total supply now\\n        uint256 totalSupplyCurrent = totalSupply();\\n        // Return inflation amount\\n        inflationTokenAmount = totalSupplyCurrent.mul(rate).div(10 \\*\\* 18).sub(totalSupplyCurrent);\\n    }\\n    // Done\\n    return inflationTokenAmount;\\n}\\n```\\n
Properly track `inflationCalcBlock` as the end of the previous interval, as this is up to where the inflation was calculated, instead of the block at which the method was invoked.\\nEnsure APY/APD and interval configuration match up. Ensure the interval is not too small (potential gas DoS blocking inflation mint and RocketRewardsPool.claim).
```\\nfunction getInlfationIntervalsPassed() override public view returns(uint256) {\\n    // The block that inflation was last calculated at\\n    uint256 inflationLastCalculatedBlock = getInflationCalcBlock();\\n    // Get the daily inflation in blocks\\n    uint256 inflationInterval = getInflationIntervalBlocks();\\n    // Calculate now if inflation has begun\\n    if(inflationLastCalculatedBlock > 0) {\\n        return (block.number).sub(inflationLastCalculatedBlock).div(inflationInterval);\\n    }else{\\n        return 0;\\n    }\\n}\\n```\\n