Platform: Code4rena
Start Date: 30/05/2023
Pot Size: $300,500 USDC
Total HM: 79
Participants: 101
Period: about 1 month
Judge: Trust
Total Solo HM: 36
Id: 242
League: ETH
Rank: 92/101
Findings: 1
Award: $23.91
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: Kamil-Chmielewski
Also found by: ByteBandits, Co0nan, Madalad, T1MOH, Udsen, Voyvoda, bin2chen, chaduke, jasonxiale, kutugu, said, xuwinnie, zzebra83
23.9127 USDC - $23.91
https://github.com/code-423n4/2023-05-maia/blob/main/src/uni-v3-staker/UniswapV3Staker.sol#L342
The function UniswapV3Staker::restakeToken
was supposed to allow anyone to call it and restake the tokenID, however, the function call _unstakeToken
passing the isNotRestake
variable setting it as true insteand of false. Causing the _unstakeToken
function to revert on L374
UniswapV3Staker::restakeToken
got called by anyone as supposed:
https://github.com/code-423n4/2023-05-maia/blob/main/src/uni-v3-staker/UniswapV3Staker.sol#L340function restakeToken(uint256 tokenId) external { IncentiveKey storage incentiveId = stakedIncentiveKey[tokenId]; if (incentiveId.startTime != 0) _unstakeToken(incentiveId, tokenId, true); (IUniswapV3Pool pool, int24 tickLower, int24 tickUpper, uint128 liquidity) = NFTPositionInfo.getPositionInfo(factory, nonfungiblePositionManager, tokenId); _stakeToken(tokenId, pool, tickLower, tickUpper, liquidity); }
It makes a call to _unstakeToken
in order to unstake the tokenId, passing the isNotRestake
argument as true.
on _unstakeToken
at L374, the condition below will result in the following since the owner of the tokenID is not the sender.
if ((isNotRestake || block.timestamp < endTime) && owner != msg.sender) revert NotCalledByOwner(); ----> if (true || true && true) == True - function revert
unstakeToken
, however, the restakeToken
function supposed to be called by anyone. So the isNotRestake
here should be set to false in order to pass the condition.We need to first define the function into the IUniswapV3StakerTest.sol
interface so we can use it inside the test below:
function restakeToken(uint256 tokenId) external;
Add the following function on /maia/test/uniswap-v3/UniswapV3StakerTest.t.sol
function testRestakeTokenFail() public { // Create a Uniswap V3 pool (pool, poolContract) = UniswapV3Assistant.createPool(uniswapV3Factory, address(token0), address(token1), poolFee); // Initialize 1:1 0.3% fee pool UniswapV3Assistant.initializeBalanced(poolContract); hevm.warp(block.timestamp + 100); // 3338502497096994491500 to give 1 ether per token with 0.3% fee and -60,60 ticks uint256 tokenId = newNFT(-60, 60, 3338502497096994491500); uint256 minWidth = 10; // Create a gauge gauge = createGaugeAndAddToGaugeBoost(pool, minWidth); // Create a Uniswap V3 Staker incentive key = IUniswapV3Staker.IncentiveKey({pool: pool, startTime: IncentiveTime.computeEnd(block.timestamp)}); uint256 rewardAmount = 1 ether; rewardToken.mint(address(this), rewardAmount); rewardToken.approve(address(uniswapV3Staker), rewardAmount); createIncentive(key, rewardAmount); hevm.warp(key.startTime); // Transfer and stake the position in Uniswap V3 Staker nonfungiblePositionManager.safeTransferFrom(address(this), address(uniswapV3Staker), tokenId); (address owner,,, uint256 stakedTimestamp) = uniswapV3Staker.deposits(tokenId); address anon = address(123); hevm.startPrank(anon); // Simulate call the function by 3rd party. uniswapV3Staker.restakeToken(tokenId); hevm.stopPrank(); }
Run it forge test --match-path test/uniswap-v3/UniswapV3StakerTest.t.sol --match-test testRestakeTokenFail
and notice the function will revert with error NotCalledByOwner
Manual
Correct the boolean value to false
when calling _unstakeToken
inside restakeToken
.
Context
#0 - c4-judge
2023-07-09T11:43:25Z
trust1995 marked the issue as duplicate of #745
#1 - c4-judge
2023-07-09T11:43:34Z
trust1995 marked the issue as satisfactory