Summary
Large Precision Loss
Vulnerability Details
The claimDittoMatchedReward
function have uint256 protocolTime variable at line 162 in which LibOrders.getOffsetTime() is divided with 1 days LibOrders.getOffsetTime() / 1 days
, after which the result is substracted in next line in uint256 elapsedTime
.
The resultant is then added and multiplied in line 164 in uint256 totalReward
.
function claimDittoMatchedReward(uint256 vault) external nonReentrant {
STypes.Vault storage Vault = s.vault[vault];
STypes.VaultUser storage VaultUser = s.vaultUser[vault][msg.sender];
uint88 shares = VaultUser.dittoMatchedShares;
if (shares <= 1) revert Errors.NoShares();
shares -= 1;
uint256 protocolTime = LibOrders.getOffsetTime() / 1 days;
uint256 elapsedTime = protocolTime - Vault.dittoMatchedTime;
uint256 totalReward =
Vault.dittoMatchedReward + elapsedTime * 1 days * Vault.dittoMatchedRate;
uint256 sharesTotal = Vault.dittoMatchedShares;
uint256 userReward = shares.mul(totalReward).div(sharesTotal);
if (elapsedTime > 0) {
Vault.dittoMatchedTime = uint16(protocolTime);
}
Vault.dittoMatchedShares -= shares;
if ((totalReward - userReward) > type(uint96).max) revert Errors.InvalidAmount();
Vault.dittoMatchedReward = uint96(totalReward - userReward);
VaultUser.dittoMatchedShares = 1;
if (userReward > type(uint80).max) revert Errors.InvalidAmount();
VaultUser.dittoReward += uint80(userReward);
emit Events.ClaimDittoMatchedReward(vault, msg.sender);
}
Impact
Carrying out arthimetic operation in function claimDittoMatchedReward
based on division of values, then subtracting it and after that multiplying the resultant cause large precision error or even loss of rewards for users.
Tools Used
Manual Code Review
Recommendations
The recommendation is made in area of first conducting multiplication of all values and carries out all other division arithmetic operation.
function claimDittoMatchedReward(uint256 vault) external nonReentrant {
STypes.Vault storage Vault = s.vault[vault];
STypes.VaultUser storage VaultUser = s.vaultUser[vault][msg.sender];
// User's shares total
uint88 shares = VaultUser.dittoMatchedShares;
// Implicitly checks for a valid vault
if (shares <= 1) revert Errors.NoShares();
// Decrease by 1 wei to account for 1 wei gas saving technique
shares -= 1;
// Total token reward amount for limit orders
- uint256 protocolTime = LibOrders.getOffsetTime() / 1 days;
+ uint256 protocolTime = LibOrders.getOffsetTime() * 1 days;
uint256 elapsedTime = protocolTime - Vault.dittoMatchedTime;
uint256 totalReward =
- Vault.dittoMatchedReward + elapsedTime * 1 days * Vault.dittoMatchedRate;
+ (Vault.dittoMatchedReward + elapsedTime * Vault.dittoMatchedRate) / 1 days;
// User's proportion of the total token reward
uint256 sharesTotal = Vault.dittoMatchedShares;
uint256 userReward = shares.mul(totalReward).div(sharesTotal);
// Only update dittoMatchedTime when totalReward increases
if (elapsedTime > 0) {
Vault.dittoMatchedTime = uint16(protocolTime); // @dev(safe-cast)
}
// Update remaining records
Vault.dittoMatchedShares -= shares;
if ((totalReward - userReward) > type(uint96).max) revert Errors.InvalidAmount();
Vault.dittoMatchedReward = uint96(totalReward - userReward);
VaultUser.dittoMatchedShares = 1; // keep as non-zero to save gas
if (userReward > type(uint80).max) revert Errors.InvalidAmount();
VaultUser.dittoReward += uint80(userReward);
emit Events.ClaimDittoMatchedReward(vault, msg.sender);
}