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: 50/96
Findings: 2
Award: $31.16
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 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
In the WardenPledge.sol
contract, there is no threshold for the minTargetVotes
contract variable which could be manipulated by the contract's owner.
I would recommend to add a threshold in the update function of the variable: updateMinTargetVotes
https://github.com/code-423n4/2022-10-paladin/blob/main/contracts/WardenPledge.sol#L612:L618
During the creation of a pledge (createPledge
function), there is no check that endTimestamp >= block.timestamp
as we can see in the others functions. In the case where a creator accidentally put an endTimestamp
value lower than block.timestamp
, it will throw an execution error and the transaction will be reverted because the second value is subtracted from the first (line 319) and an uint
cannot be a negative value. We can just avoid this kind of accidental error by adding a check below line 316 - https://github.com/code-423n4/2022-10-paladin/blob/main/contracts/WardenPledge.sol#L316 :
if (endTimestamp < block.timestamp) revert Errors.InvalidEndTimestamp();
#0 - c4-judge
2022-11-08T22:00:42Z
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
In the WardenPledge.sol
contract, the minDelegationTime
variable is not modifiable and is defined as a simple variable whereas it could be defined as a constant in order to save some gas.
Here is the recommendation at lines 21 : https://github.com/code-423n4/2022-10-paladin/blob/main/contracts/WardenPledge.sol#L21
// Constants : uint256 public constant UNIT = 1e18; uint256 public constant MAX_PCT = 10000; uint256 public constant WEEK = 7 * 86400; uint256 public constant MIN_DELEGATION_TIME = 1 weeks;
Therefore, when this variable is called later in contract's functions at lines 320 and 386, some changes are required:
As follows:
// L320 if(vars.duration < MIN_DELEGATION_TIME) revert Errors.DurationTooShort(); // L386 if(addedDuration < MIN_DELEGATION_TIME) revert Errors.DurationTooShort();
Finally, the previous variable declared at line 79 can be removed : https://github.com/code-423n4/2022-10-paladin/blob/main/contracts/WardenPledge.sol#L79
In Solidity, we can pack multiple contract and struct variables into the same 32 bytes slot in storage. With this pattern, we can greatly optimize gas consumption when storing or loading statically-sized variables.
Under this Struct defined from line 28 to line 44 : https://github.com/code-423n4/2022-10-paladin/blob/main/contracts/WardenPledge.sol#L28:L44 , we can pack the last 3 variables into a full 32 bytes slot: rewardToken
, endTimestamp
and closed
.
Here are the bytes used per these types:
address
= 20 bytesuint64
= 8 bytesbool
= 1So, these 3 variables are using a total of 29 bytes (20 + 8 + 1). However, we can easily make them use a full 32 bytes storage slot by changing the uint64
to an uint88
(11 bytes) => 20 + 11 + 1 = 32. With this, the EVM doesn't have to fill the remaining bytes anymore, so we save gas.
The new Struct would look like this:
struct Pledge{ // Target amount of veCRV (balance scaled by Boost v2, fetched as adjusted_balance) uint256 targetVotes; // Difference of votes between the target and the receiver balance at the start of the Pledge // (used for later extend/increase of some parameters the Pledge) uint256 votesDifference; // Price per vote per second, set by the owner uint256 rewardPerVote; // Address to receive the Boosts address receiver; // Address of the token given as rewards to Boosters address rewardToken; // Timestamp of end of the Pledge uint88 endTimestamp; // Set to true if the Pledge is canceled, or when closed after the endTimestamp bool closed; }
Of course, we must change the content and the name of the safe64
function to match the new uint88
pattern : https://github.com/code-423n4/2022-10-paladin/blob/main/contracts/WardenPledge.sol#L665:L668
function safe88(uint256 n) internal pure returns (uint88) { if(n > type(uint88).max) revert Errors.NumberExceed88Bits(); return uint88(n); }
With that being applied, we also must change the lines where this function is called as we renamed it, this happens at those lines:
Finally, the revert error name must be changed in this file: https://github.com/code-423n4/2022-10-paladin/blob/main/contracts/utils/Errors.sol#L46
error NumberExceed88Bits();
In addition to the use of a constant for the minDelegationTime
variable, we can pack 2 variables into the same slot: protocalFeeRatio
and chestAddress
. The fee ratio is expressed in basis points and therefore will never exceed 10000, which means we can change its uint256
type into an uint96
(12 bytes). So the address and the uint96 will take one single full 32 bytes slot (20 + 12) instead of 2 slots.
https://github.com/code-423n4/2022-10-paladin/blob/main/contracts/WardenPledge.sol#L71
/** @notice Address to receive protocol fees */ address public chestAddress; /** @notice ratio of fees to pay the protocol (in BPS) */ uint96 public protocalFeeRatio = 250; //bps
Of course, the update function of the protocalFeeRatio
must be changed accordingly with this new variable type: https://github.com/code-423n4/2022-10-paladin/blob/main/contracts/WardenPledge.sol#L625:L631
function updatePlatformFee(uint96 newFee) external onlyOwner { if(newFee > 500) revert Errors.InvalidValue(); uint96 oldfee = protocalFeeRatio; protocalFeeRatio = newFee; emit PlatformFeeUpdated(oldfee, newFee); }
Finally, the PlatformFeeUpdated
definition must be updated : https://github.com/code-423n4/2022-10-paladin/blob/main/contracts/WardenPledge.sol#L117
event PlatformFeeUpdated(uint96 oldfee, uint96 newFee);
#0 - c4-judge
2022-11-11T23:57:58Z
kirk-baird marked the issue as grade-b