Platform: Code4rena
Start Date: 21/11/2022
Pot Size: $90,500 USDC
Total HM: 18
Participants: 101
Period: 7 days
Judge: Picodes
Total Solo HM: 4
Id: 183
League: ETH
Rank: 30/101
Findings: 3
Award: $193.05
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: deliriusz
Also found by: 0x52, 0xLad, 0xbepresent, Englave, R2, Ruhum, cccz, gzeon, hihen, keccak123, ladboy233, pashov, pedroais, perseverancesuccess, rbserver, rvierdiiev, simon135, unforgiven, wagmi, xiaoming90
7.9647 USDC - $7.96
https://github.com/code-423n4/2022-11-redactedcartel/blob/03b71a8d395c02324cb9fdaf92401357da5b19d1/src/vaults/AutoPxGmx.sol#L242 https://github.com/code-423n4/2022-11-redactedcartel/blob/03b71a8d395c02324cb9fdaf92401357da5b19d1/src/vaults/AutoPxGmx.sol#L227 https://github.com/code-423n4/2022-11-redactedcartel/blob/03b71a8d395c02324cb9fdaf92401357da5b19d1/src/vaults/AutoPxGmx.sol#L321 https://github.com/code-423n4/2022-11-redactedcartel/blob/03b71a8d395c02324cb9fdaf92401357da5b19d1/src/vaults/AutoPxGmx.sol#L345
The compound()
function helps to swap the gmxBaseReward (ether) for GMX tokens then the GMX tokens are deposited for pxGMX. In the swap function the amountOutMinimum
helps to put the minimum value that is expected for the swap. The fixed amountOutMInimum assumes that the price can not be manipulated by different techniques so it is better to calculate the minimum accepted amount.
The amountOutMinimum
should be calculated with the Uniswap SDK or an onchain price oracle as their documentation says:
For a real deployment, this value should be calculated using our SDK or an onchain price oracle - this helps protect against getting an unusually bad price for a trade due to a front running sandwich or another type of price manipulation.
The following lines use a fixed amountOutMinimum value:
VisualStudio
Calculates the amountOutMinimum
using the Uniswap SDK, an onchain price oracle or a time weighted average price.
#0 - c4-judge
2022-12-03T22:07:56Z
Picodes marked the issue as duplicate of #185
#1 - c4-judge
2022-12-03T22:08:05Z
Picodes marked the issue as partial-50
#2 - Picodes
2022-12-03T22:08:33Z
The finding is valid but does not clearly explains the actual consequences of the current design choice
#3 - C4-Staff
2023-01-10T22:10:37Z
JeeberC4 marked the issue as duplicate of #137
🌟 Selected for report: Jeiwan
Also found by: 0xbepresent, Koolex, __141345__, cryptoDave, cryptonue, datapunk, pashov, unforgiven
131.6023 USDC - $131.60
The user can lost his rewards if the reward token is removed from the producerTokens[producerToken].rewardTokens
list. If the reward token is removed, the rewardToken length is going to be zero, the user rewards going to be zero and the for statement will never enter so the user will lost his rewards for this token and the global state rewards will save inaccurate information.
I did a test for this situation:
// test/PirexRewards.t.sol function testClaimRewardsWithRemovedToken( uint32 secondsElapsed, uint8 multiplier, bool useETH ) external { vm.assume(secondsElapsed > 10); vm.assume(secondsElapsed < 365 days); vm.assume(multiplier != 0); vm.assume(multiplier < 10); address userX = testAccounts[0]; uint256 gmxAmount = 100e18; _mintApproveGmx(gmxAmount, address(this), address(pirexGmx), gmxAmount); pirexGmx.depositGmx(gmxAmount, userX); assertEq(weth.balanceOf(userX), 0); vm.warp(block.timestamp + secondsElapsed); // // Add reward token and harvest rewards from Pirex contract // pirexRewards.addRewardToken(pxGmx, weth); pirexRewards.harvest(); pirexRewards.userAccrue(pxGmx, userX); (, , uint256 globalRewardsBeforeClaimPxGmx) = _getGlobalState( pxGmx ); (, , uint256 userRewardsBeforeClaimPxGmx) = pirexRewards .getUserState(pxGmx, userX); // // Sum of reward amounts that the user is entitled to // uint256 expectedClaimAmount = ((pirexRewards.getRewardState( pxGmx, weth ) * _calculateUserRewards(pxGmx, userX)) / _calculateGlobalRewards(pxGmx)); // // Remove WETH from pxGMX // vm.expectEmit(true, false, false, true, address(pirexRewards)); emit RemoveRewardToken(pxGmx, 0); pirexRewards.removeRewardToken(pxGmx, 0); // vm.expectEmit(true, false, false, false, address(pirexGmx)); // emit ClaimUserReward(userX, address(0), 0, 0, 0); // this emit is not called vm.expectEmit(true, true, false, true, address(pirexRewards)); emit Claim(pxGmx, userX); pirexRewards.claim(pxGmx, userX); (, , uint256 globalRewardsAfterClaimPxGmx) = _getGlobalState(pxGmx); (, , uint256 userRewardsAfterClaimPxGmx) = pirexRewards.getUserState(pxGmx, userX); assertEq( globalRewardsBeforeClaimPxGmx - userRewardsBeforeClaimPxGmx, globalRewardsAfterClaimPxGmx ); assertEq(0, userRewardsAfterClaimPxGmx); // // Owner weth balance has not changed. It is still zero. // userX has not received their rewards // assertEq(weth.balanceOf(userX), 0); assertTrue(expectedClaimAmount != weth.balanceOf(userX)); // Add again the rewardToken pirexRewards.addRewardToken(pxGmx, weth); pirexRewards.harvest(); // userX balance rewards is still zero pirexRewards.claim(pxGmx, userX); assertEq(weth.balanceOf(userX), 0); }
Foundry/VSCode
Add a check if the reward token exists or not so the p.userStates[user].rewards
is not zero if the reward token was removed:
... ... ERC20[] memory rewardTokens = p.rewardTokens; uint256 rLen = rewardTokens.length; // Claim should be skipped and not reverted on zero global/user reward if (globalRewards != 0 && userRewards != 0 && rLen != 0) { // Update global and user reward states to reflect the claim p.globalState.rewards = globalRewards - userRewards; p.userStates[user].rewards = 0; ... ... }
#0 - c4-judge
2022-12-03T22:11:17Z
Picodes marked the issue as duplicate of #255
#1 - c4-judge
2022-12-05T10:38:57Z
Picodes marked the issue as duplicate of #255
#2 - c4-judge
2023-01-01T10:39:59Z
Picodes marked the issue as duplicate of #271
#3 - c4-judge
2023-01-01T10:45:00Z
Picodes marked the issue as satisfactory
🌟 Selected for report: 0xSmartContract
Also found by: 0xAgro, 0xNazgul, 0xPanda, 0xbepresent, 0xfuje, Awesome, B2, Bnke0x0, Deivitto, Diana, Funen, Jeiwan, JohnSmith, Josiah, R2, RaymondFam, Rolezn, Sathish9098, Waze, adriro, aphak5010, brgltd, btk, carrotsmuggler, ch0bu, chaduke, codeislight, codexploder, cryptostellar5, csanuragjain, danyams, datapunk, delfin454000, deliriusz, eierina, erictee, fatherOfBlocks, gz627, gzeon, hansfriese, hihen, jadezti, joestakey, keccak123, martin, nameruse, oyc_109, pedr02b2, perseverancesuccess, rbserver, rotcivegaf, rvierdiiev, sakshamguruji, shark, simon135, subtle77, unforgiven, xiaoming90, yixxas
53.4851 USDC - $53.49
totalAssets()
function.Use the totalAssets()
function in the corresponding contract.
Instances of this issue:
Save the newAssets calculation in a variable here src/vaults/AutoPxGmx.sol#L288 and use it in the 290 line. Basically it follows the same pattern from src/vaults/AutoPxGlp.sol#L250
Example:
uint256 newAssets = totalAssets() - assetsBeforeClaim; if (newAssets != 0) { totalFee = (newAssets * platformFee) / FEE_DENOMINATOR; }
#0 - c4-judge
2022-12-05T08:54:06Z
Picodes marked the issue as grade-b