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: 16/96
Findings: 3
Award: $343.87
🌟 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
recoverERC20
does not allow recovering the token when minAmountRewardToken[token] != 0
such that the owner is not able to drain the reward tokens out of the contracts. However, the owner fully controls minAmountRewardToken[token]
and can reset it to 0 with removeRewardToken
, even if there is a remaining reward amount that is available for pledges.
This is a significant risk for users (especially pledge creators) that interact with the contract, as the reward tokens that they have transferred can be removed at any point.
Consider the following diff where the owner removes the reward token and then calls recoverERC20
(which does not revert, i.e. the test fails there):
--- a/test/wardenPledge.test.ts +++ b/test/wardenPledge.test.ts @@ -3439,6 +3439,12 @@ describe('Warden Pledge contract tests', () => { wardenPledge.connect(admin).recoverERC20(CRV.address) ).to.be.revertedWith('CannotRecoverToken') + await wardenPledge.connect(admin).removeRewardToken(CRV.address) + + await expect( + wardenPledge.connect(admin).recoverERC20(CRV.address) + ).to.be.revertedWith('CannotRecoverToken') + }); it(' should block non-admin caller', async () => {
Track for every token if there are remaining pledges. Do not allow recovering the tokens if this is the case.
#0 - Kogaroshi
2022-10-30T22:48:45Z
Duplicate of #17
#1 - c4-judge
2022-11-10T07:52:32Z
kirk-baird marked the issue as not a duplicate
#2 - c4-judge
2022-11-10T07:52:39Z
kirk-baird marked the issue as duplicate
#3 - c4-judge
2022-11-10T21:27:26Z
kirk-baird marked the issue as satisfactory
#4 - c4-judge
2022-11-10T21:27:30Z
kirk-baird marked the issue as not a duplicate
#5 - c4-judge
2022-11-10T21:27:36Z
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
314.3177 USDC - $314.32
https://github.com/code-423n4/2022-10-paladin/blob/2cef3087052f019c8043f66f954d630b81cb16fb/contracts/WardenPledge.sol#L387 https://github.com/code-423n4/2022-10-paladin/blob/2cef3087052f019c8043f66f954d630b81cb16fb/contracts/WardenPledge.sol#L432
When calculating the amount of new reward tokens that should be transferred, extendPledge
and increasePledgeRewardPerVote
use the variable pledgeParams.votesDifference
to calculate the added rewards:
uint256 totalRewardAmount = (rewardPerVoteDiff * pledgeParams.votesDifference * remainingDuration) / UNIT;
However, this variable is set within createPledge
and refers to the initial difference (when the pledge was created):
vars.votesDifference = targetVotes - votingEscrow.balanceOf(receiver);
When extendPledge
or increasePledgeRewardPerVote
is called, the balance might be significantly smaller, but the totalRewardAmount
is still calculated with the original one. This has two problems:
1.) Capital efficiency: The user has to provide too much reward tokens, making the system less attractive.
2.) Fees: The creator has to pay fees on the whole added reward amount, even when only a small amount of it will actually be distributed, leading to a loss of funds for the creator.
Let's say the initial difference is $x$ at time $t$. At time $t + 86400$, when the creator calls extendPledge
, the difference has decreased to $0.1x$ because a lot of user pledged. However, the additional reward amount that the creator has to transfer is still calculated with $x$ and he has to pay fees based on this value.
Recalculate the difference and use this value for the added reward amount (and the fees).
#0 - Kogaroshi
2022-10-30T23:30:57Z
Duplicate of #61
#1 - kirk-baird
2022-11-10T21:54:31Z
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:54:35Z
kirk-baird marked the issue as nullified
#3 - kirk-baird
2022-11-10T23:04:03Z
Since ached 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:04:07Z
kirk-baird marked the issue as not nullified
#5 - c4-judge
2022-11-10T23:04:11Z
kirk-baird marked the issue as satisfactory
#6 - c4-judge
2022-11-10T23:04:17Z
kirk-baird marked the issue as not a duplicate
#7 - c4-judge
2022-11-10T23:04:29Z
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/2cef3087052f019c8043f66f954d630b81cb16fb/contracts/WardenPledge.sol#L333 https://github.com/code-423n4/2022-10-paladin/blob/2cef3087052f019c8043f66f954d630b81cb16fb/contracts/WardenPledge.sol#L475
In createPledge
, pledgeAvailableRewardAmounts[vars.newPledgeID]
is increased by vars.totalRewardAmount
and this amount is transferred from the creator:
IERC20(rewardToken).safeTransferFrom(creator, address(this), vars.totalRewardAmount);
In retrievePledgeRewards
(and closePledge
), is retrieved and transferred to the receiver:
uint256 remainingAmount = pledgeAvailableRewardAmounts[pledgeId]; ... IERC20(pledgeParams.rewardToken).safeTransfer(receiver, remainingAmount);
This logic becomes very problematic for fee-on-transfer tokens. There, less than vars.totalRewardAmount
will be transferred to the contract. Therefore, retrieving the pledge rewards (or potentially also a call to _pledge
, but the consequences are not that severe there) will fail because the contract does not contain this amount of tokens.
A very simple example is the following: Someone creates a pledge with a fee-on-transfer token that takes a 1% fee on every transfer. The totalRewardAmount
is 1 million tokens, but only 999'000 arrive at the contract because of the fee. When the creator immediately calls closePledge
afterwards, the contract tries to transfer 1 million tokens to him (the value of pledgeAvailableRewardAmounts[pledgeId]
), but this will fail, because the contract balance is smaller than that. The creator can therefore not retrieve these tokens and they are lost.
Query and store the actual amount of tokens that was transferred, i.e. the difference between the balances.
#0 - Kogaroshi
2022-10-30T23:28:43Z
Duplicate of #27
#1 - c4-judge
2022-11-10T07:23:50Z
kirk-baird marked the issue as not a duplicate
#2 - c4-judge
2022-11-10T07:23:55Z
kirk-baird marked the issue as duplicate
#3 - c4-judge
2022-11-10T07:33:34Z
kirk-baird marked the issue as not a duplicate
#4 - c4-judge
2022-11-10T07:33:42Z
kirk-baird changed the severity to QA (Quality Assurance)
#5 - c4-judge
2022-11-12T00:01:01Z
kirk-baird marked the issue as grade-b