Summary
When Stalk is partially transferred via LibSilo.transferStalk()
, for some reason global values of Stalk and Roots are used.
However the assumption that ratio Roots / Stalk is constant can be broken in future, therefore real issue will arise.
Vulnerability Details
Here you can see that roots
is calculated using global values and further can underflow in subtraction:
function transferStalk(address sender, address recipient, uint256 stalk) internal {
AppStorage storage s = LibAppStorage.diamondStorage();
uint256 roots;
roots = stalk == s.accts[sender].stalk
? s.accts[sender].roots
@> : s.sys.silo.roots.sub(1).mul(stalk).div(s.sys.silo.stalk).add(1);
s.accts[sender].stalk = s.accts[sender].stalk.sub(stalk);
@> s.accts[sender].roots = s.accts[sender].roots.sub(roots);
emit StalkBalanceChanged(sender, -int256(stalk), -int256(roots));
s.accts[recipient].stalk = s.accts[recipient].stalk.add(stalk);
s.accts[recipient].roots = s.accts[recipient].roots.add(roots);
emit StalkBalanceChanged(recipient, int256(stalk), int256(roots));
}
Impact
As soon as ratio Roots / Stalk becomes higher for any reason, there will be revert on partial transfer of Stalk.
Tools Used
Manual Review
Recommendations
Use account's values instead of global:
function transferStalk(address sender, address recipient, uint256 stalk) internal {
AppStorage storage s = LibAppStorage.diamondStorage();
uint256 roots;
roots = stalk == s.accts[sender].stalk
? s.accts[sender].roots
- : s.sys.silo.roots.sub(1).mul(stalk).div(s.sys.silo.stalk).add(1);
+ : s.accts[sender].roots.sub(1).mul(stalk).div(s.accts[sender].stalk).add(1);
...
}