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: 53/96
Findings: 2
Award: $29.55
🌟 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
https://github.com/code-423n4/2022-10-paladin/blob/d6d0c0e57ad80f15e9691086c9c7270d4ccfe0e6/contracts/WardenPledge.sol#L585-L592 https://github.com/code-423n4/2022-10-paladin/blob/d6d0c0e57ad80f15e9691086c9c7270d4ccfe0e6/contracts/WardenPledge.sol#L653-L661
There is a function that is intended to be used to recover ERC20 tokens that were sent to the WardenPledge
contract by accident. The function is only usable by the owner and contains a check that no tokens can be taken which are currently whitelisted as reward tokens. However, the owner also has enough privileges to bypass that check and can instantly take all reward tokens that are part of currently running pledges.
The function to move out accidentally sent ERC20 tokens as described under #Impact is recoverERC20
:
/** * @notice Recovers ERC2O tokens sent by mistake to the contract * @dev Recovers ERC2O tokens sent by mistake to the contract * @param token Address tof the EC2O token * @return bool: success */ 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; }
It can be seen that it reverts if minAmountRewardToken[token] != 0
, which means the token can be used during the creation of a new pledge and also that there might be currently pledges running with that token as a reward.
However, the owner can use the removeRewardToken
function at will to "de-whitelist" any such tokens, which would allow him to sweep all reward tokens with recoverERC20
right after:
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); }
Manual Review
#0 - Kogaroshi
2022-10-31T00:50:16Z
Duplicate of #17
#1 - c4-judge
2022-11-10T06:40:31Z
kirk-baird marked the issue as not a duplicate
#2 - c4-judge
2022-11-10T06:40:37Z
kirk-baird marked the issue as duplicate
#3 - c4-judge
2022-11-10T21:15:35Z
kirk-baird marked the issue as satisfactory
#4 - c4-judge
2022-11-10T21:15:40Z
kirk-baird marked the issue as not a duplicate
#5 - c4-judge
2022-11-10T21:15:47Z
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: robee
Also found by: 0x007, 0x1f8b, 0x52, 0xDjango, 0xNazgul, 0xSmartContract, 8olidity, Awesome, B2, Bnke0x0, Chom, Diana, Dravee, JTJabba, Jeiwan, Josiah, Lambda, Mathieu, Picodes, RaoulSchaffranek, RaymondFam, RedOneN, ReyAdmirado, Rolezn, Ruhum, Sm4rty, Tricko, Trust, Waze, __141345__, a12jmx, adriro, ajtra, brgltd, c3phas, carlitox477, cccz, ch0bu, chaduke, chrisdior4, corerouter, cryptonue, csanuragjain, ctf_sec, cylzxje, delfin454000, dic0de, djxploit, horsefacts, imare, jayphbee, jwood, ktg, ladboy233, leosathya, lukris02, minhtrng, neko_nyaa, oyc_109, pashov, peritoflores, rbserver, rvierdiiev, shark, tnevler, yixxas
19.6449 USDC - $19.64
The accounting for the reward token amount currently assumes that the WardenPledge
contract will have a balance equal to an amount x
if a transferFrom for said amount x
happens to the contract. If an ERC20 token gets whitelisted that has a fee-on-transfer mechanism and is subsequently used for a pledge, the accounting will be incorrect and the creator of the pledge will not be able to close the pledge or withdraw the remaining tokens.
In the function createPledge
the amount that is assigned to pledgeAvailableRewardAmounts
will be the same as was was requested in a call to rewardtoken.safeTransferFrom
:
IERC20(rewardToken).safeTransferFrom(creator, address(this), vars.totalRewardAmount); ... vars.newPledgeID = pledgesIndex(); // Add the total reards as available for the Pledge & write Pledge parameters in storage pledgeAvailableRewardAmounts[vars.newPledgeID] += vars.totalRewardAmount;
In case of rewardToken
having fees-on-transfer, the actual value by which the balance of reward tokens has increased on the WardenPledge
contract will be lower than that. This can make the functions retrievePledgeRewards
and closePledge
uncallable if there is only one currently running pledge with that token as a reward, as they both try to perform to transfer more tokens than the contract currently holds:
// 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);
In case of multiple running pledges the first pledge creators to call either function will effectively steal tokens from other pledges.
Manual Review
balanceOf
function of the reward token before and after a safeTransferFrom
and assign the difference to pledgeAvailableRewardAmounts[vars.newPledgeID]
#0 - Kogaroshi
2022-10-31T00:49:46Z
Duplicate of #27
#1 - c4-judge
2022-11-10T07:32:55Z
kirk-baird changed the severity to QA (Quality Assurance)
#2 - c4-judge
2022-11-12T01:03:01Z
kirk-baird marked the issue as grade-b