Platform: Code4rena
Start Date: 27/10/2022
Pot Size: $33,500 USDC
Total HM: 8
Participants: 96
Period: 3 days
Judge: kirk-baird
Total Solo HM: 1
Id: 176
League: ETH
Rank: 20/96
Findings: 2
Award: $257.44
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: ladboy233
Also found by: 0x52, 0xDecorativePineapple, 0xhunter, Aymen0909, Bnke0x0, Dravee, JTJabba, Jeiwan, Lambda, Nyx, Picodes, Trust, cccz, cryptonue, csanuragjain, dic0de, hansfriese, horsefacts, imare, minhtrng, pashov, peritoflores, rbserver, rvierdiiev, wagmi, yixxas
9.9073 USDC - $9.91
There is a recoverERC20()
function to withdraw ERC20 tokens from the contract.
Currently, it checks if the token isn't an active reward token but it can be passed easily if the admin removes the reward token using removeRewardToken()
.
So if the admin removes an active reward token and withdraws all amount of the token from the contract, the pledge creator using that token will lose their funds permanently.
Even if the admin doesn't do that by fault, it's a potential admin attack vector to steal all user funds.
function recoverERC20(address token) external onlyOwner returns(bool) { if(minAmountRewardToken[token] != 0) revert Errors.CannotRecoverToken(); uint256 amount = IERC20(token).balanceOf(address(this)); if(amount == 0) revert Errors.NullValue(); IERC20(token).safeTransfer(owner(), amount); return true; }
function removeRewardToken(address token) external onlyOwner { if(token == address(0)) revert Errors.ZeroAddress(); if(minAmountRewardToken[token] == 0) revert Errors.NotAllowedToken(); minAmountRewardToken[token] = 0; emit RemoveRewardToken(token); }
recoverERC20()
by fault or maliciously to steal funds, all pledge creators who used this token will lose their funds.Manual Review
I think we should add an additional mapping for the reward token like activePledgeCount
to count the number of active pledges to use the token and update the mapping properly when users create/close the pledges.
And we can check if activePledgeCount[token] == 0
in the removeRewardToken()
so that the admin can't remove the reward token that has an active pledge.
function removeRewardToken(address token) external onlyOwner { if(token == address(0)) revert Errors.ZeroAddress(); if(minAmountRewardToken[token] == 0) revert Errors.NotAllowedToken(); if(activePledgeCount[token] > 0) revert Errors.ActivePledgeExists(); //++++++++++++++++++++++++++++ minAmountRewardToken[token] = 0; emit RemoveRewardToken(token); }
#0 - Kogaroshi
2022-10-31T00:41:50Z
Duplicate of #17
#1 - c4-judge
2022-11-10T07:09:09Z
kirk-baird marked the issue as not a duplicate
#2 - c4-judge
2022-11-10T07:09:15Z
kirk-baird marked the issue as duplicate
#3 - c4-judge
2022-11-10T21:19:19Z
kirk-baird marked the issue as satisfactory
#4 - c4-judge
2022-11-10T21:19:24Z
kirk-baird marked the issue as not a duplicate
#5 - c4-judge
2022-11-10T21:19:30Z
kirk-baird marked the issue as duplicate of #17
#6 - c4-judge
2022-12-06T17:32:42Z
Simon-Busch marked the issue as duplicate of #68
🌟 Selected for report: rbserver
Also found by: 0x1f8b, 0xSmartContract, Trust, cccz, codexploder, ctf_sec, hansfriese
247.5252 USDC - $247.53
https://github.com/code-423n4/2022-10-paladin/blob/d6d0c0e57ad80f15e9691086c9c7270d4ccfe0e6/contracts/WardenPledge.sol#L456 https://github.com/code-423n4/2022-10-paladin/blob/d6d0c0e57ad80f15e9691086c9c7270d4ccfe0e6/contracts/WardenPledge.sol#L488
Currently, the pledge creators can't do anything after the protocol is paused.
So they can't withdraw their unused funds after the pledge is expired in the pause mode and the funds will be locked in the contract.
whenNotPaused
modifier.function retrievePledgeRewards(uint256 pledgeId, address receiver) external whenNotPaused nonReentrant { if(pledgeId >= pledgesIndex()) revert Errors.InvalidPledgeID(); address creator = pledgeOwner[pledgeId]; if(msg.sender != creator) revert Errors.NotPledgeCreator(); if(receiver == address(0)) revert Errors.ZeroAddress(); Pledge storage pledgeParams = pledges[pledgeId]; if(pledgeParams.endTimestamp > block.timestamp) revert Errors.PledgeNotExpired(); // Get the current remaining amount of rewards not distributed for the Pledge uint256 remainingAmount = pledgeAvailableRewardAmounts[pledgeId]; // Set the Pledge as Closed if(!pledgeParams.closed) pledgeParams.closed = true; if(remainingAmount > 0) { // Transfer the non used rewards and reset storage pledgeAvailableRewardAmounts[pledgeId] = 0; IERC20(pledgeParams.rewardToken).safeTransfer(receiver, remainingAmount); emit RetrievedPledgeRewards(pledgeId, receiver, remainingAmount); } } /** * @notice Closes a Pledge and retrieves all non distributed rewards from a Pledge * @dev Closes a Pledge and retrieves all non distributed rewards from a Pledge & send them to the given receiver * @param pledgeId ID fo the Pledge to close * @param receiver Address to receive the remaining rewards */ function closePledge(uint256 pledgeId, address receiver) external whenNotPaused nonReentrant { if(pledgeId >= pledgesIndex()) revert Errors.InvalidPledgeID(); address creator = pledgeOwner[pledgeId]; if(msg.sender != creator) revert Errors.NotPledgeCreator(); if(receiver == address(0)) revert Errors.ZeroAddress(); Pledge storage pledgeParams = pledges[pledgeId]; if(pledgeParams.closed) revert Errors.PledgeAlreadyClosed(); if(pledgeParams.endTimestamp <= block.timestamp) revert Errors.ExpiredPledge(); // Set the Pledge as Closed pledgeParams.closed = true; // Get the current remaining amount of rewards not distributed for the Pledge uint256 remainingAmount = pledgeAvailableRewardAmounts[pledgeId]; if(remainingAmount > 0) { // Transfer the non used rewards and reset storage pledgeAvailableRewardAmounts[pledgeId] = 0; IERC20(pledgeParams.rewardToken).safeTransfer(receiver, remainingAmount); emit RetrievedPledgeRewards(pledgeId, receiver, remainingAmount); } emit ClosePledge(pledgeId); }
Manual Review
I think retrievePledgeRewards()
and closePledge()
shouldn't have a whenNotPaused
modifier.
If the protocol wants to add a strict requirement for closePledge()
, at least retrievePledgeRewards()
shouldn't have a modifier so that the creators can recover their funds after the pledge is expired.
#0 - Kogaroshi
2022-10-31T00:39:54Z
Duplicate of #70
#1 - c4-judge
2022-11-11T08:00:59Z
kirk-baird marked the issue as not a duplicate
#2 - c4-judge
2022-11-11T08:01:24Z
kirk-baird marked the issue as duplicate of #70
#3 - c4-judge
2022-11-11T08:01:28Z
kirk-baird marked the issue as satisfactory
#4 - c4-judge
2022-11-11T08:01:33Z
kirk-baird marked the issue as not a duplicate
#5 - c4-judge
2022-11-11T08:01:44Z
kirk-baird marked the issue as duplicate of #70
#6 - c4-judge
2022-12-06T17:36:20Z
Simon-Busch marked the issue as duplicate of #269