Platform: Code4rena
Start Date: 15/12/2022
Pot Size: $128,000 USDC
Total HM: 28
Participants: 111
Period: 19 days
Judge: GalloDaSballo
Total Solo HM: 1
Id: 194
League: ETH
Rank: 52/111
Findings: 1
Award: $145.30
š Selected for report: 0
š Solo Findings: 0
š Selected for report: wagmi
Also found by: HollaDieWaldfee, __141345__, chaduke, hansfriese, peritoflores, rvierdiiev, sces60107, slowmoses, supernova
145.3017 USDC - $145.30
GGP inflation calculation can be impacted by external parties . Ā
The protocol through RewardsPool contract mints GGP tokens by checking if enough time has passed since the last mint.
Anyone can call the startRewardsCycle
function to run a GGP rewards cycle.
This function then calls the inflate
internal function below :-
function inflate() internal { ProtocolDAO dao = ProtocolDAO(getContractAddress("ProtocolDAO")); uint256 inflationIntervalElapsedSeconds = (block.timestamp - getInflationIntervalStartTime()); (uint256 currentTotalSupply, uint256 newTotalSupply) = getInflationAmt(); TokenGGP ggp = TokenGGP(getContractAddress("TokenGGP")); if (newTotalSupply > ggp.totalSupply()) { revert MaximumTokensReached(); } uint256 newTokens = newTotalSupply - currentTotalSupply; emit GGPInflated(newTokens); dao.setTotalGGPCirculatingSupply(newTotalSupply); addUint(keccak256("RewardsPool.InflationIntervalStartTime"), inflationIntervalElapsedSeconds); setUint(keccak256("RewardsPool.RewardsCycleTotalAmt"), newTokens); }
Here , the currentTotalSupply
and newTotalSupply
is derived by calling getInflationAmt
below : -
function getInflationAmt() public view returns (uint256 currentTotalSupply, uint256 newTotalSupply) { ProtocolDAO dao = ProtocolDAO(getContractAddress("ProtocolDAO")); uint256 inflationRate = dao.getInflationIntervalRate(); uint256 inflationIntervalsElapsed = getInflationIntervalsElapsed(); currentTotalSupply = dao.getTotalGGPCirculatingSupply(); newTotalSupply = currentTotalSupply; // Compute inflation for total inflation intervals elapsed for (uint256 i = 0; i < inflationIntervalsElapsed; i++) { newTotalSupply = newTotalSupply.mulWadDown(inflationRate); } return (currentTotalSupply, newTotalSupply); }
Here it loops through the inflationIntervalsElapsed
which is derived by calling getInflationIntervalsElapsed
below:-
function getInflationIntervalsElapsed() public view returns (uint256) { ProtocolDAO dao = ProtocolDAO(getContractAddress("ProtocolDAO")); uint256 startTime = getInflationIntervalStartTime(); if (startTime == 0) { revert ContractHasNotBeenInitialized(); } return (block.timestamp - startTime) / dao.getInflationIntervalSeconds(); }
The problem lies in this function above .
It returns the intervals by dividing the time elapsed by the constant(dao.getInflationIntervalSeconds()) which is currently set 1 days
.
If time elapsed i.e ( block.timestamp - startTime )= 1 days and 23 hours , it will return 1 (169200/ 84600) . Now , if someone calls startRewardsCycle
, the following will happen :-
Demonstrating in reverse
getInflationIntervalsElapsed
will return 1 .getInflationAmt
function will loop only 1 time .inflate
function will receive the currentTotalSupply
and newTotalSupply
values accordingly .Now see this line in the inflate function , which updates the RewardsPool.InflationIntervalStartTime
by inflationIntervalElapsedSeconds(1 days and 23 hours ) (which is nothing but the time elapsed since we called the function ).
Hence, this is the end state:-
RewardsPool.InflationIntervalStartTime
is updated by full time elapsed i.e 1 days and 23 hours.Manual Review
Rather than updating the RewardsPool.InflationIntervalStartTime by the actual time elapsed , update it using below method
addUint(keccak256("RewardsPool.InflationIntervalStartTime"), getInflationIntervalsElapsed() * 1 days);
This will update the RewardsPool.InflationIntervalStartTime
by 1 days , and thus maintaining the difference of 23 hours , which can be used in future in the next calll.
#0 - c4-judge
2023-01-10T09:52:42Z
GalloDaSballo marked the issue as duplicate of #648
#1 - c4-judge
2023-01-29T18:57:24Z
GalloDaSballo changed the severity to 2 (Med Risk)
#2 - c4-judge
2023-02-08T10:02:36Z
GalloDaSballo marked the issue as satisfactory