Summary
If user has not mown since germination, they'll lose their portion of plenty
Vulnerability Details
After a user deposits, they have to wait 2 seasons until their deposit germinates
(until they receive their stalk
and roots
). Currently, if the user hasn't mown since germination, their roots will be included in the sum of all roots that should receive plenty, although that's only on paper and they won't realistically be able to claim any of these funds.
When a user deposits, LibSilo.mintGerminatingStalk
is called which adds the necessary stalk to the global accounting of unclaimedGerminating
.
function mintGerminatingStalk(address account, uint128 stalk, GerminationSide side) internal {
AppStorage storage s = LibAppStorage.diamondStorage();
s.accts[account].germinatingStalk[side] += stalk;
uint32 season = s.sys.season.current;
if (LibGerminate.getSeasonGerminationSide() == side) {
s.sys.silo.unclaimedGerminating[season].stalk += stalk;
} else {
s.sys.silo.unclaimedGerminating[season - 1].stalk += stalk;
}
emit LibGerminate.FarmerGerminatingStalkBalanceChanged(
account,
int256(uint256(stalk)),
side
);
emit LibGerminate.TotalGerminatingStalkChanged(season, int256(uint256(stalk)));
}
Then, when the actual germinating season comes, the global stalk
and roots
accounting is increased by these values
function endTotalGermination(uint32 season, address[] memory tokens) external {
AppStorage storage s = LibAppStorage.diamondStorage();
if (season < 2) return;
uint32 germinationSeason = season.sub(2);
uint128 finishedGerminatingStalk = s.sys.silo.unclaimedGerminating[germinationSeason].stalk;
uint128 rootsFromGerminatingStalk;
if (s.sys.silo.roots == 0) {
rootsFromGerminatingStalk = finishedGerminatingStalk.mul(uint128(C.getRootsBase()));
} else if (s.sys.silo.unclaimedGerminating[germinationSeason].stalk > 0) {
rootsFromGerminatingStalk = s
.sys
.silo
.roots
.mul(finishedGerminatingStalk)
.div(s.sys.silo.stalk)
.toUint128();
}
s.sys.silo.unclaimedGerminating[germinationSeason].roots = rootsFromGerminatingStalk;
s.sys.silo.stalk = s.sys.silo.stalk.add(finishedGerminatingStalk);
s.sys.silo.roots = s.sys.silo.roots.add(rootsFromGerminatingStalk);
Then, when a new raining season starts, this global accounting value is used to determine the total amount of roots to distribute the rewards to:
function handleRain(uint256 caseId) external {
AppStorage storage s = LibAppStorage.diamondStorage();
if (caseId.mod(36) < 3 || caseId.mod(36) > 8) {
if (s.sys.season.raining) {
s.sys.season.raining = false;
}
return;
} else if (!s.sys.season.raining) {
s.sys.season.raining = true;
address[] memory wells = LibWhitelistedTokens.getCurrentlySoppableWellLpTokens();
uint32 season = s.sys.season.current;
uint32 rainstartSeason = s.sys.season.rainStart;
for (uint i; i < wells.length; i++) {
s.sys.sop.sops[season][wells[i]] = s.sys.sop.sops[rainstartSeason][wells[i]];
}
s.sys.season.rainStart = s.sys.season.current;
s.sys.rain.pods = s.sys.fields[s.sys.activeField].pods;
s.sys.rain.roots = s.sys.silo.roots;
Hence, we've just shown that although these roots are not claimed to the user, they're included in the plenty allocation.
Now, if we look at the code of _mow
, we'll see that first handleRainAndSops
is called and then LibGerminate.endAccountGermination
.
function _mow(address account, address token) external {
AppStorage storage s = LibAppStorage.diamondStorage();
uint32 lastUpdate = _lastUpdate(account);
uint32 currentSeason = s.sys.season.current;
if (s.sys.season.rainStart > s.sys.season.stemStartSeason) {
if (lastUpdate <= s.sys.season.rainStart && lastUpdate <= currentSeason) {
LibFlood.handleRainAndSops(account, lastUpdate);
}
}
if (lastUpdate < currentSeason) {
LibGerminate.endAccountGermination(account, lastUpdate, currentSeason);
}
__mow(account, token);
s.accts[account].lastUpdate = currentSeason;
}
Since handleRainAndSops
calculates the plenty
user should get based on their roots and LibGerminate.endAccountGermination
is the function which actually gives the user their roots, because of the order of the functions, in case the user hasn't mown since germination, user will not get the plenty they should get.
Impact
Loss of funds
Tools Used
Manual review
Recommendations
Simply reversing the order would introduce new issue. Fix is non-trivial.