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: 15/96
Findings: 3
Award: $345.48
🌟 Selected for report: 0
🚀 Solo Findings: 0
314.3177 USDC - $314.32
https://github.com/code-423n4/2022-10-paladin/blob/main/contracts/WardenPledge.sol#L387 https://github.com/code-423n4/2022-10-paladin/blob/main/contracts/WardenPledge.sol#L432 https://github.com/code-423n4/2022-10-paladin/blob/main/contracts/WardenPledge.sol#L327 https://github.com/code-423n4/2022-10-paladin/blob/main/contracts/WardenPledge.sol#L245
When a pledge owner increases the reward per vote or the duration of the pledge, the contract locks up more of their reward tokens than necessary.
The total amount of reward tokens issued to users is specified in createPledge()
as:
vars.totalRewardAmount = (rewardPerVote * vars.votesDifference * vars.duration) / UNIT;
When a pledge is updated, the total reward amount of recalculated:
// extendPledge() uint256 totalRewardAmount = (pledgeParams.rewardPerVote * pledgeParams.votesDifference * addedDuration) / UNIT; // increasePledgeRewardPerVote() uint256 totalRewardAmount = (rewardPerVoteDiff * pledgeParams.votesDifference * remainingDuration) / UNIT;
A pledge can be updated at any time between its creation and its endTimestamp
as long as it's not closed. When recalculating the new totalRewardAmount
, the contract doesn't check whether anybody has already pledged. The votesDifference
value can be different between the creation and update of a pledge. And the contract only allows a pledge to gather as many votes as specified in their targetVotes
property.
Consider the following situation:
Pledge creation: - initialVotes = 0 - targetVotes = 100e18 - votesDifference = 100e18 - rewardPerVote = 2 - duration = 2 weeks (1209600)
$totalRewardAmount = 2 * 100e18 * 1209600 / 1e18$ $totalRewardAmount = 241920000$
1 week passed and50e18
votes were pledged. The creator decides to update the rewardPerVote
parameter to 3
to get the remaining half of them. The contract would determine the totalRewardAmount
the creator has to supply as:
uint256 totalRewardAmount = (rewardPerVoteDiff * pledgeParams.votesDifference * remainingDuration) / UNIT;
$totalRewardAmount = 3 * 100e18 * 604800 / 1e18$ $totalRewardAmount = 181440000$
So the contract locks up another 181440000
tokens. But, because 50e18
votes were already pledged, only 50e18
more votes remain. Thus, the actual amount of tokens that are distributed as a reward are:
$totalRewardAmount = 3 * 50e18 * 604800 / 1e18$ $totalRewardAmount = 90720000$
The contract locks up more funds than necessary. Those tokens are not lost. After the pledge ends, the creator can reclaim them using retrievePledgeRewards()
. It's only a temporary freezing of funds.
none
When a pledge is updated, recalculate the votesDifference
value and determine the totalRewardAmount
using that new value.
#0 - Kogaroshi
2022-10-30T23:09:11Z
This is the desired behavior, as delegated boost could end before the endTimestamp of the Pledge, requiring new delegation, but the contract would not have enough reward tokens to reward the delegation. This is why the rewards amounts are always ignoring the delegation, as it could bring issue where the amount of available rewards amount would be insufficient.
#1 - kirk-baird
2022-11-10T21:53:41Z
Nicely written up and explained however as the sponsor mentions it may be required to use the full amount of tokens transferred if existing pledges end before the desired end timestamp. I'll use Nullify
so the warden is not penalised for this submission.
#2 - c4-judge
2022-11-10T21:53:45Z
kirk-baird marked the issue as nullified
#3 - kirk-baird
2022-11-10T23:03:00Z
Since cached voteDifference
needs to be updated for each call to extendPledge()
and increasePledgeRewards()
I'm going to mark this as a duplicate of #163 .
#4 - c4-judge
2022-11-10T23:03:05Z
kirk-baird marked the issue as not nullified
#5 - c4-judge
2022-11-10T23:03:10Z
kirk-baird marked the issue as satisfactory
#6 - c4-judge
2022-11-10T23:03:16Z
kirk-baird marked the issue as duplicate of #163
🌟 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
https://github.com/code-423n4/2022-10-paladin/blob/main/contracts/WardenPledge.sol#L333 https://github.com/code-423n4/2022-10-paladin/blob/main/contracts/WardenPledge.sol#L394 https://github.com/code-423n4/2022-10-paladin/blob/main/contracts/WardenPledge.sol#L438 https://github.com/code-423n4/2022-10-paladin/blob/main/contracts/WardenPledge.sol#L271
Some tokens charge a fee on transfers. The contract can't handle those tokens. It will think that it has more tokens than it actually does. That in turn will result in a pledge not being fulfillable. The most prominent token that might take fees in the future is USDT.
When the pledge creator provides the reward tokens, the contract doesn't verify that it received the correct amount
IERC20(pledgeParams.rewardToken).safeTransferFrom(creator, address(this), totalRewardAmount);
totalRewardAmount
is the total amount of tokens that will be distributed to users who delegate their votes. If the contract doesn't actually have a balance of totalRewardAmount
, the very last call to pledge()
will fail because of the following transfer at the end of _pledge()
:
IERC20(pledgeParams.rewardToken).safeTransfer(user, rewardAmount);
Because the contract doesn't have rewardAmount
tokens left, the tx will fail. Because the last call to pledge fails, it will never be able to be fulfilled. Nobody will be able to delegate the last votes.
none
Use the actual amount of tokens the contract received instead of the user provided value.
#0 - Kogaroshi
2022-10-30T23:38:43Z
Duplicate of #72
#1 - c4-judge
2022-11-10T07:25:05Z
kirk-baird marked the issue as not a duplicate
#2 - c4-judge
2022-11-10T07:25:14Z
kirk-baird marked the issue as duplicate
#3 - c4-judge
2022-11-10T07:31:46Z
kirk-baird marked the issue as not a duplicate
#4 - c4-judge
2022-11-10T07:32:04Z
kirk-baird changed the severity to QA (Quality Assurance)
#5 - c4-judge
2022-11-12T00:13:37Z
kirk-baird marked the issue as grade-b
🌟 Selected for report: c3phas
Also found by: 0x1f8b, 0xNazgul, 0xRoxas, 0xSmartContract, 0xbepresent, Amithuddar, Awesome, B2, Bnke0x0, Dravee, KoKo, Mathieu, Picodes, RaymondFam, RedOneN, ReyAdmirado, RockingMiles, Ruhum, SadBase, SooYa, Waze, __141345__, adriro, ajtra, ballx, carlitox477, ch0bu, cylzxje, djxploit, durianSausage, emrekocak, erictee, gogo, halden, horsefacts, imare, indijanc, karanctf, leosathya, lukris02, neko_nyaa, oyc_109, peiw, sakman, shark, skyle, tnevler
11.5153 USDC - $11.52
_pledge()
shouldn't verify user's balanceretrievePledgeRewards()
and closePledge()
can be merged to reduce deployment gas_pledge()
shouldn't verify user's balanceIn the _pledge()
function you check whether the user has enough boost delegation & set the correct allowance. The same checks are already done in BoostV2.boost()
. You're wasting gas by repeating the same checks in your contract. If the user doesn't fulfill the necessary requirements, the call will simply fail.
By adding an if-clause to prevent underflows you're increasing the gas costs of the happy path. Just let the value underflow and trigger a revert that way. Most wallets prevent txs that revert from being executed. Thus, you're wasting gas for no reason.
retrievePledgeRewards()
and closePledge()
can be merged to reduce deployment gasBoth functions implement the same thing. One is callable before the pledge ended and the other one after. The effect of both is the same (retrieval of funds). You can merge them into a single one to reduce deployment gas.
#0 - c4-judge
2022-11-11T23:58:17Z
kirk-baird marked the issue as grade-b