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: 19/96
Findings: 2
Award: $267.17
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 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#L636 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
Creator fund is stucked in the contract if the admin owner pause the contract and never unpause the contract.
In current implementation, the admin can pause the contract,
/** * @notice Pauses the contract */ function pause() external onlyOwner { _pause(); } /** * @notice Unpauses the contract */ function unpause() external onlyOwner { _unpause(); }
after contract is paused, the user is not able to pledge and get reward.
function pledge(uint256 pledgeId, uint256 amount, uint256 endTimestamp) external whenNotPaused
but the creator is not able to call
function closePledge(uint256 pledgeId, address receiver) external whenNotPaused nonReentrant
function retrievePledgeRewards(uint256 pledgeId, address receiver) external whenNotPaused nonReentrant
then the creator fund that means to reward the pledged user is locked in the contract as long as the creator not unpause the contract.
Admin invokes the pause function
function pause() external onlyOwner
Creator cannot retrieve pledge reward or close pledge because these two function use the modifier whenNotPaused
Manual Review
Usually in a reward distribution contract, the admin can pause the contract disable inbound action, but not blocking the outbound action.
For example,
in a staking contract, the user can stake token for reward, admin can pause the contract to not let user stake more token. but should not block user from withdraw their staked token when pausing the contract.
Same ideas applys here, we recommend the project remove WhenNotPaused in function below
function closePledge function retrievePledgeRewards
#1 - c4-judge
2022-11-11T07:46:13Z
kirk-baird marked the issue as primary issue
#2 - c4-judge
2022-11-11T08:05:53Z
kirk-baird marked the issue as satisfactory
#3 - c4-judge
2022-12-06T17:36:20Z
Simon-Busch marked the issue as duplicate of #269
🌟 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 code below calculate the reward that user eligible to claim after user delegates the veToken voting power.
// Rewards are set in the Pledge as reward/veToken/sec // To find the total amount of veToken delegated through the whole Boost duration // based on the Boost bias & the Boost duration, to take in account that the delegated amount decreases // each second of the Boost duration uint256 totalDelegatedAmount = ((bias * boostDuration) + bias) / 2; // Then we can calculate the total amount of rewards for this Boost uint256 rewardAmount = (totalDelegatedAmount * pledgeParams.rewardPerVote) / UNIT; if(rewardAmount > pledgeAvailableRewardAmounts[pledgeId]) revert Errors.RewardsBalanceTooLow(); pledgeAvailableRewardAmounts[pledgeId] -= rewardAmount; // Send the rewards to the user IERC20(pledgeParams.rewardToken).safeTransfer(user, rewardAmount);
note the logic
// each second of the Boost duration uint256 totalDelegatedAmount = ((bias * boostDuration) + bias) / 2; // Then we can calculate the total amount of rewards for this Boost uint256 rewardAmount = (totalDelegatedAmount * pledgeParams.rewardPerVote) / UNIT;
if (totalDelegatedAmount * pledgeParams.rewardPerVote) / UNIT is 0, the user is not getting any reward.
given
uint256 rewardAmount = (totalDelegatedAmount * pledgeParams.rewardPerVote) / UNIT;
UNIT is hardcoded: 10 ** 18 if pledgeParams.rewardPerVote is a small number, such as 1 wei. and totalDelegatedAmount is for example, 100000000,
100000000 * 1 / UNIT is rounded to 0.
User delegates the vote power but does not get any reward.
Manual Review.
Please add check to make sure that the user can get the reward after pledging.
uint256 rewardAmount = (totalDelegatedAmount * pledgeParams.rewardPerVote) / UNIT; if(rewardAmount == 0) revert ZeroRewardAmount();
#0 - trust1995
2022-10-30T22:04:27Z
It's fine because totalDelegatedAmount uses UNIT decimals.
#1 - Kogaroshi
2022-10-30T22:54:24Z
Invalid, the Pledge reward per vote must be higher than the value minAmountRewardToken
for the used token rewards, so the admin can enforce a minimal value preventing such issues.
#2 - kirk-baird
2022-11-11T21:10:01Z
While totalDelegatedAmount
does use UINT
decimals it's possible to round to zero still. However, minAmountRewardToken
is in place to prevent the rounding to zero.
If mintAmountRewardToken
is not set adequately for this token it's possible to round to zero. I'm going to consider this issue QA since it requires a misconfiguration to be exploited.
#3 - c4-judge
2022-11-11T21:10:12Z
kirk-baird changed the severity to QA (Quality Assurance)
#4 - c4-judge
2022-11-11T23:59:32Z
kirk-baird marked the issue as grade-b