Platform: Code4rena
Start Date: 14/04/2022
Pot Size: $75,000 USDC
Total HM: 8
Participants: 72
Period: 7 days
Judge: Jack the Pug
Total Solo HM: 2
Id: 110
League: ETH
Rank: 44/72
Findings: 2
Award: $144.74
๐ Selected for report: 0
๐ Solo Findings: 0
๐ Selected for report: IllIllI
Also found by: 0v3rf10w, 0x1f8b, 0xDjango, 0xkatana, AmitN, CertoraInc, Dravee, Funen, Hawkeye, Jujic, MaratCerby, Picodes, Ruhum, SolidityScan, TerrierLover, TomFrenchBlockchain, TrungOre, VAD37, Yiko, berndartmueller, cmichel, csanuragjain, danb, defsec, delfin454000, dipp, ellahi, fatherOfBlocks, georgypetrov, gs8nrv, gzeon, horsefacts, hubble, hyh, ilan, jah, joestakey, kebabsec, kenta, kyliek, m9800, minhquanym, oyc_109, p_crypt0, peritoflores, rayn, reassor, remora, rfa, robee, scaraven, securerodd, shenwilly, sorrynotsorry, tchkvsky, teryanarmen, z3s
92.0693 USDC - $92.07
Declare modifiers together
tchkvsky
Funding.sol
GlobalAccessControlManaged.sol
onlyCitadelPriceInAssetOracle()
modifier in Funding.sol
should be declared after the events.
Also, the modifiers declared in GlobalAccessControlManaged.sol
should be placed before any function declaration, specifically, __GlobalAccessControlManaged_init()
. This will aid contract readability.
Manual review
Declare modifiers together. Group events together. Follow the recommended contract layout. Inside each contract, library or interface, use the following order:
Type declarations
State variables
Events
Modifiers
Functions
Useful link:
Order Of Layout | Style Guide ๏ผ Solidity 0.8.14 documentation
Missing or incomplete natspec comments
CitadelMinter.sol
Funding.sol
GlobalAccessControl.sol
KnigthingRound.sol
StakedCitadel.sol
StakedCitadelVester.sol
SupplySchedule.sol
Missing or incomplete natspec comments in contracts
These functions do not have a comment or partially commented:
CitadelMinter.getFundingPoolWeights()
CitadelMinter.initializeLastMintTimestamp()
CitadelMinter._transferToFundingPools()
CitadelMinter._removeFundingPool()
CitadelMinter._addFundingPool()
Funding.clearCitadelPriceFlag()
Funding.setSaleRecipient()
Funding.setCitadelAssetPriceBounds()
GlobalAccessControl.pause()
GlobalAccessControl.unpause()
KnightingRound.claim()
- no @return tag
StakedCitadel.depositFor()
- no @param tag for third parameter i.e proof
StakedCitadel._depositWithAuthorization()
- no @param tags
StakedCitadel._depositForWithAuthorization()
- no @param tags
StakedCitadelVester.initialize()
SupplySchedule.sol
Manual review
Include comments in contracts where there is none. Update contracts which are partially commented
tchkvsky
SettAccessControl.sol#L37-L54
Missing zero address validation in permissioned actors
Alice calls setGovernance
without specifying the _governance
, so Alice loses ownership of the contract.
SettAccessControl.setStrategist
SettAccessControl.setKeeper
SettAccessControl.setGovernance
Manual review
Check that the address is not zero.
Complex pragma versions used in interfaces
tchkvsky
Complex pragma versions is used in interfaces:
pragma solidity >=0.5.0<=0.9.0
is too complex
Different versions of solidity might have different bugs that might cause unwanted behavior in contracts. It is recommended to use a single pragma version.
(src/interfaces/badger/IBadgerGuestlist.sol#2) (src/interfaces/badger/IBadgerVipGuestlist.sol#2) (src/interfaces/badger/IStrategy.sol#3) (src/interfaces/badger/IVault.sol#3) (src/interfaces/citadel/ICitadelToken.sol#3) (src/interfaces/citadel/IGac.sol#3) (src/interfaces/citadel/IMedianOracle.sol#3) (src/interfaces/citadel/IStakedCitadelLocker.sol#3) (src/interfaces/citadel/ISupplySchedule.sol#3) (src/interfaces/citadel/IVesting.sol#2) (src/interfaces/convex/BoringMath.sol#2) (src/interfaces/convex/IRewardStaking.sol#2) (src/interfaces/convex/IStakingProxy.sol#2) (src/interfaces/convex/MathUtil.sol#2) (src/interfaces/erc20/IERC20.sol#3)
Static analyzer
Consider modifying contracts to use a single pragma version. Alternatively, use a modern interface with a stable, yet single solidity version.
๐ Selected for report: Dravee
Also found by: 0v3rf10w, 0x1f8b, 0xAsm0d3us, 0xBug, 0xDjango, 0xNazgul, 0xkatana, CertoraInc, Cityscape, Funen, Hawkeye, IllIllI, MaratCerby, SolidityScan, TerrierLover, TomFrenchBlockchain, Tomio, TrungOre, bae11, berndartmueller, csanuragjain, defsec, delfin454000, ellahi, fatherOfBlocks, gs8nrv, gzeon, horsefacts, ilan, jah, joestakey, joshie, kebabsec, kenta, nahnah, oyc_109, rayn, rfa, robee, saian, securerodd, simon135, slywaters, sorrynotsorry, tchkvsky, teryanarmen, z3s
52.6692 USDC - $52.67
Pack variables together to save gas
tchkvsky
Funding.sol
StakedCitadelLocker.sol
uint256
, mapping
, bytes32
, string
all take 32 bytes in a slot. uint8
uses 1 byte (StakedCitadelLocker.sol
), bool
uses 1 byte, whileaddress
uses 20 bytes. Consider rearranging and packing values that can fit into a slot (i.e. 32 bytes) together. An example is packing a bool
and address
, since both will only use 21 bytes out of the 32 bytes in a slot (packing can be done in Funding.sol
).
This also applies to the values in a struct
. They can be packed together.
Manual review
Declare variables that occupy 32 bytes first, then declare the others according to their sizes, in decreasing order. Alternatively, pack variables with smaller size into others (that can accommodate) till a slot is used up.
Prefix increments/decrements are cheaper than the postfix form
tchkvsky
These functions do not use prefix increments/decrements (++i
/--i
)
Using prefix increment/decrement is more gas efficient
CitadelMinter.sol
#L152
StakedCitadelLocker.sol
#L243, #L322, #L373, #L412, #L464, #L493, #L516, #L535, #L567, #L806, #L953, #L1035
SupplySchedule.sol
#L208
GlobalAccessControlManaged.sol
#L48
Manual review
Consider using prefix increments/decrements (++i / --i) to save gas
public
function should be made external
tchkvsky
setGovernance()
is a public
function and it is never called from the contract. It should be declared external
instead. This will save some gas.
Manual review
Use external
specifier for functions that are never called from the contract.
Variable reinitialized to its default value
tchkvsky
GlobalAccessControlManged.sol
#L47
Variable reinitialized to its default value.
The default value for a bool
is false
and the validRoleFound
variable is reinitialized to false
. This wastes gas.
Manual review
Remove explicit initialization for default values
Use lower values of typeuint
instead of uint256
to store hardcoded values
tchkvsky
The state variable MAX_BPS
is declared using uint256
which takes 32 bytes. uint14
can be used to store this as the maximum value (i.e. 10,000) is still within range. Also, fundingBps
, stakingBps
, lockingBps
declared in state but used locally should be checked. Also, the parameter _weight
in CitadelMinter.setFundingPoolWeight()
is between 0 and 10,000 (inclusive). uint14
should be able to store this value for _weight
. uint256
uses 32 bytes for storing the value while uint14
almost uses 2 bytes. This will save gas.
Other contracts to consider:
StakedCitadelLocker.sol
, StakedCitadelVester.sol
###ย Proof of Concept
CitadelMinter.sol
#L42, #L244-L284,
Funding.sol
#L30
StakedCitadel.sol
#L68-L117
ONE_ETH
can be stored in uint64
MAX_BPS
can be stored in uint14
SECS_PER_YEAR
can be stored in uint26
WITHDRAWAL_FEE_HARD_CAP
and MANAGEMENT_FEE_HARD_CAP
can be stored in uint8
PERFORMANCE_FEE_HARD_CAP
can be stored in uint12
Manual review
Use lower values of type uint
instead of uint256
when storing hardcoded integers.