Paladin - Warden Pledges contest - Lambda's results

A governance lending protocol transforming users voting power into a new money lego.

General Information

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

Paladin

Findings Distribution

Researcher Performance

Rank: 16/96

Findings: 3

Award: $343.87

QA:
grade-b

🌟 Selected for report: 0

🚀 Solo Findings: 0

Awards

9.9073 USDC - $9.91

Labels

bug
2 (Med Risk)
satisfactory
duplicate-68

External Links

Lines of code

https://github.com/code-423n4/2022-10-paladin/blob/2cef3087052f019c8043f66f954d630b81cb16fb/contracts/WardenPledge.sol#L654

Vulnerability details

Impact

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.

Proof Of Concept

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

Findings Information

🌟 Selected for report: Jeiwan

Also found by: 0xDjango, Aymen0909, Chom, Lambda, Ruhum, Trust

Labels

bug
2 (Med Risk)
satisfactory
duplicate-163

Awards

314.3177 USDC - $314.32

External Links

Lines of code

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

Vulnerability details

Impact

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.

Proof Of Concept

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

Lines of code

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

Vulnerability details

Impact

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.

Proof Of Concept

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

AuditHub

A portfolio for auditors, a security profile for protocols, a hub for web3 security.

Built bymalatrax © 2024

Auditors

Browse

Contests

Browse

Get in touch

ContactTwitter