diff --git a/contracts/plugins/assets/compoundv3/CusdcV3Wrapper.sol b/contracts/plugins/assets/compoundv3/CusdcV3Wrapper.sol index 923432218..5b7b17606 100644 --- a/contracts/plugins/assets/compoundv3/CusdcV3Wrapper.sol +++ b/contracts/plugins/assets/compoundv3/CusdcV3Wrapper.sol @@ -20,14 +20,14 @@ contract CusdcV3Wrapper is ICusdcV3Wrapper, WrappedERC20, CometHelpers { /// From cUSDCv3, used in principal <> present calculations uint256 public constant TRACKING_INDEX_SCALE = 1e15; /// From cUSDCv3, scaling factor for USDC rewards - uint64 public constant RESCALE_FACTOR = 1e12; + uint256 public constant RESCALE_FACTOR = 1e12; CometInterface public immutable underlyingComet; ICometRewards public immutable rewardsAddr; IERC20 public immutable rewardERC20; - mapping(address => uint64) public baseTrackingIndex; - mapping(address => uint64) public baseTrackingAccrued; + mapping(address => uint64) public baseTrackingIndex; // uint64 for consistency with CometHelpers + mapping(address => uint256) public baseTrackingAccrued; // uint256 to avoid overflow in L:199 mapping(address => uint256) public rewardsClaimed; constructor( @@ -257,7 +257,8 @@ contract CusdcV3Wrapper is ICusdcV3Wrapper, WrappedERC20, CometHelpers { uint256 indexDelta = uint256(trackingSupplyIndex - baseTrackingIndex[account]); uint256 newBaseTrackingAccrued = baseTrackingAccrued[account] + - safe64((safe104(balanceOf(account)) * indexDelta) / TRACKING_INDEX_SCALE); + (safe104(balanceOf(account)) * indexDelta) / + TRACKING_INDEX_SCALE; uint256 claimed = rewardsClaimed[account]; uint256 accrued = newBaseTrackingAccrued * RESCALE_FACTOR; @@ -286,9 +287,7 @@ contract CusdcV3Wrapper is ICusdcV3Wrapper, WrappedERC20, CometHelpers { (, uint64 trackingSupplyIndex) = getSupplyIndices(); uint256 indexDelta = uint256(trackingSupplyIndex - baseTrackingIndex[account]); - baseTrackingAccrued[account] += safe64( - (safe104(accountBal) * indexDelta) / TRACKING_INDEX_SCALE - ); + baseTrackingAccrued[account] += (safe104(accountBal) * indexDelta) / TRACKING_INDEX_SCALE; baseTrackingIndex[account] = trackingSupplyIndex; } diff --git a/contracts/plugins/assets/compoundv3/ICusdcV3Wrapper.sol b/contracts/plugins/assets/compoundv3/ICusdcV3Wrapper.sol index f1514ec8e..89a9dcfb3 100644 --- a/contracts/plugins/assets/compoundv3/ICusdcV3Wrapper.sol +++ b/contracts/plugins/assets/compoundv3/ICusdcV3Wrapper.sol @@ -51,7 +51,7 @@ interface ICusdcV3Wrapper is IWrappedERC20, IRewardable { function convertDynamicToStatic(uint256 amount) external view returns (uint104); - function baseTrackingAccrued(address account) external view returns (uint64); + function baseTrackingAccrued(address account) external view returns (uint256); function baseTrackingIndex(address account) external view returns (uint64); diff --git a/contracts/plugins/assets/yearnv2/YearnV2CurveFiatCollateral.sol b/contracts/plugins/assets/yearnv2/YearnV2CurveFiatCollateral.sol index 89b11d861..912198256 100644 --- a/contracts/plugins/assets/yearnv2/YearnV2CurveFiatCollateral.sol +++ b/contracts/plugins/assets/yearnv2/YearnV2CurveFiatCollateral.sol @@ -77,11 +77,15 @@ contract YearnV2CurveFiatCollateral is CurveStableCollateral { return (low, high, 0); } + // solhint-disable no-empty-blocks + /// DEPRECATED: claimRewards() will be removed from all assets and collateral plugins function claimRewards() external virtual override { // No rewards to claim, everything is part of the pricePerShare } + // solhint-enable no-empty-blocks + // === Internal === /// @return {ref/tok} Actual quantity of whole reference units per whole collateral tokens diff --git a/test/plugins/individual-collateral/compoundv3/CusdcV3Wrapper.test.ts b/test/plugins/individual-collateral/compoundv3/CusdcV3Wrapper.test.ts index b47b71bff..cbbd48ed4 100644 --- a/test/plugins/individual-collateral/compoundv3/CusdcV3Wrapper.test.ts +++ b/test/plugins/individual-collateral/compoundv3/CusdcV3Wrapper.test.ts @@ -598,6 +598,18 @@ describeFork('Wrapped CUSDCv3', () => { ) }) + it('regression test: able to claim rewards even when they are big without overflow', async () => { + // Nov 28 2023: uint64 math in CusdcV3Wrapper contract results in overflow when COMP rewards are even moderately large + + const compToken = await ethers.getContractAt('ERC20Mock', COMP) + expect(await compToken.balanceOf(wcusdcV3.address)).to.equal(0) + await advanceTime(1000) + await enableRewardsAccrual(cusdcV3, bn('2e18')) // enough to revert on uint64 implementation + + await expect(wcusdcV3.connect(bob).claimRewards()).to.emit(wcusdcV3, 'RewardsClaimed') + expect(await compToken.balanceOf(bob.address)).to.be.greaterThan(0) + }) + it('claims rewards and sends to claimer (claimTo)', async () => { const compToken = await ethers.getContractAt('ERC20Mock', COMP) expect(await compToken.balanceOf(wcusdcV3.address)).to.equal(0)