Badger Citadel contest - joestakey's results

Bringing BTC to DeFi

General Information

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

BadgerDAO

Findings Distribution

Researcher Performance

Rank: 29/72

Findings: 2

Award: $309.78

🌟 Selected for report: 0

🚀 Solo Findings: 0

QA Report

Table of Contents

Check zero denominator

PROBLEM

When a division is computed, it must be ensured that the denominator is non-zero to prevent failure of the function call.

SEVERITY

Low

PROOF OF CONCEPT

Instances include:

Funding.sol

Funding.sol:225: xCitadel.getPricePerFullShare()

StakeCitadel.sol

StakeCitadel.sol:808: totalSupply() StakeCitadel.sol:890: _pool

TOOLS USED

Manual Analysis

MITIGATION

Before doing these computations, add a non-zero check to the variables aforementioned.

nonReentrant modifier unused

PROBLEM

Some external functions calling the ERC20 methods safeTransfer or safeTransferFrom do not have the nonReentrant modifier and are hence unprotected to reentrancy (besides the gas limit on the methods)

SEVERITY

Low

PROOF OF CONCEPT

Instances include:

CitadelToken.sol

Funding.sol: 334 claimAssetToTreasury()

KnightingRound.sol

KnightingRound.sol: 209 claim()

StakedCitadel.sol

StakedCitadel.sol: 698 sweepExtraToken(address _token) StakedCitadel.sol: 717 earn()

StakedCitadelVester.sol

StakedCitadelVester.sol: 85 claim(address recipient, uint256 amount)

TOOLS USED

Manual Analysis

MITIGATION

Use the nonReentrant modifier on these functions.

Unbounded for loop

PROBLEM

There is no restriction on the numbers of pool added, either in _addFundingPool() or setFundingPoolWeight(). Hence, the for loop in CitadelMinter.sol is unbounded, which could result in the gas required to perform the function call exceeding the gas limit if the number of pools is too high, preventing the distribution of citadel tokens to the funding pools.

SEVERITY

Low

PROOF OF CONCEPT

Instances include:

CitadelMinter.sol

CitadelMinter.sol:344:

TOOLS USED

Manual Analysis

MITIGATION

Add an upper limit to the total amount of funding pools, and add a check before adding a pool in setFundingPoolWeight(): 274

Typos

PROBLEM

There are a few typos in some comments

SEVERITY

Non-Critical

PROOF OF CONCEPT

Instances include:

CitadelMinter.sol

CitadelMinter.sol: 101 expection

Funding.sol

Funding.sol: 289 cumulatiive

TOOLS USED

Manual Analysis

MITIGATION

Correct the typos.

Comment Missing function parameter

PROBLEM

Some of the function comments are missing function parameters or returns

SEVERITY

Non-Critical

PROOF OF CONCEPT

Instances include:

Funding.sol

Funding.sol:110 address _citadelPriceInAssetOracle

GlobalAccessControl.sol

GlobalAccessControl.sol: 107 bytes32 role, string memory roleString,bytes32 adminRole

KnightingRound.sol

KnightingRound.sol: 109 address _globalAccessControl KnightingRound.sol: 209 uint256 tokenOutAmount_

StakedCitadel.sol

StakedCitadel.sol: 175 address _vesting StakedCitadel.sol: 363 bytes32[] memory proof

TOOLS USED

Manual Analysis

MITIGATION

Add a comment for these parameters

Function missing comments

PROBLEM

Some functions are missing comments.

SEVERITY

Non-Critical

PROOF OF CONCEPT

Instances include:

CitadelMinter.sol

CitadelMinter.sol: 143 getFundingPoolWeights() CitadelMinter.sol: 314 initializeLastMintTimestamp() CitadelMinter.sol: 338 _transferToFundingPools(uint256 _citadelAmount) CitadelMinter.sol: 362 _removeFundingPool(address _pool) CitadelMinter.sol: 374 _addFundingPool(address _pool)

Funding.sol

Funding.sol: 278 clearCitadelPriceFlag() Funding.sol: 383 setSaleRecipient(address _saleRecipient) Funding.sol: 397 setCitadelAssetPriceBounds(uint256 _minPrice, uint256 _maxPrice)

GlobalAccessControl.sol

GlobalAccessControl.sol: 94 pause() GlobalAccessControl.sol: 99 unpause()

KnightingRound.sol

KnightingRound.sol: 109 address _globalAccessControl KnightingRound.sol: 209 uint256 tokenOutAmount_

StakedCitadelVester.sol

StakedCitadelVester.sol: 59 initialize(address _gac, address _vestingToken, address _vault)

SupplySchedule.sol

SupplySchedule.sol: 43 initialize(address _gac) SupplySchedule.sol: 67 getCurrentEpoch() SupplySchedule.sol: 71 getEmissionsForEpoch(uint256 _epoch) SupplySchedule.sol: 79 getEmissionsForCurrentEpoch() SupplySchedule.sol: 84 getMintable(uint256 lastMintTimestamp) SupplySchedule.sol: 132 setMintingStart(uint256 _globalStartTimestamp) SupplySchedule.sol: 150 setEpochRate(uint256 _epoch, uint256 _rate)

TOOLS USED

Manual Analysis

MITIGATION

Add comments to these functions

Related data should be grouped in struct

PROBLEM

When there are mappings that use the same key value, having separate fields is error prone, for instance in case of deletion or with future new fields.

SEVERITY

Non-Critical

PROOF OF CONCEPT

Instances include:

KnightingRound.sol

KnightingRound.sol: 47 mapping(address => uint256) public boughtAmounts; KnightingRound.sol: 51 mapping(address => bool) public hasClaimed;

StakedCitadel.sol

StakedCitadel.sol: 94: mapping(address => uint256) public additionalTokensEarned; StakedCitadel.sol: 95: mapping(address => uint256) public lastAdditionalTokenAmount;

TOOLS USED

Manual Analysis

MITIGATION

Group the related data in a struct and use one mapping. For instance, for the StakedCitadel.sol mappings, the mitigation could be:

struct AdditionalTokens { uint256 tokensEarned; uint256 lastAmount; }

And it would be used as a state variable:

mapping(address => AdditionalTokens) additionalTokens;

Events emitted early

PROBLEM

It is not recommended to emit events before the end of the computations, as the function might revert based on conditions ahead of the event emission

SEVERITY

Non-Critical

PROOF OF CONCEPT

Instances include:

Funding.sol

Funding.sol: 328 Sweep(_token, amount) //emitted before amount transferred to saleRecipient

KnightingRound.sol

KnightingRound.sol: 413 Sweep(_token, amount) //emitted before amount transferred to saleRecipient

TOOLS USED

Manual Analysis

MITIGATION

Place the event emission in the last position in the function.

Unused storage variable

PROBLEM

Though they are allowed in Solidity and do not pose a direct security issue, it is best practice to avoid declaring storage variables that are not used.

They cause an increase in computations (and unnecessary gas consumption) and can decrease readability of the code

SEVERITY

Non-Critical

PROOF OF CONCEPT

Instances include:

GlobalAccessControl.sol

GlobalAccessControl.sol:51: //storage variable transferFromDisabled never initialized or read.

TOOLS USED

Manual Analysis

MITIGATION

Remove this storage variable

Redundant variable declaration

PROBLEM

It is best to use as few local variables as possible to avoid unnecessary computations.

SEVERITY

Non-Critical

PROOF OF CONCEPT

Instances include:

StakedCitadel.sol

StakedCitadel.sol: 773 uint256 _before = token.balanceOf(address(this)); //_pool is already initialized with token.balanceOf(address(this));

TOOLS USED

Manual Analysis

MITIGATION

remove the declaration of _before line 773, and replace _before line 776 with _pool

For clarity, you can rename _before and _after as _poolBefore and _poolAfter

Incorrect variable type

PROBLEM

cachedXCitadel is an instance of IVault, not an address. Even if the code compiles and works, the syntax in the deposit() call is misleading.

SEVERITY

Non-Critical

PROOF OF CONCEPT

Instances include:

CitadelMinter.sol

CitadelMinter.sol: 195 IVault(cachedXCitadel).deposit(lockingAmount); //cachedXCitadel is not an address

TOOLS USED

Manual Analysis

MITIGATION

replace IVault(cachedXCitadel) with cachedXCitadel

Uint256 alias

PROBLEM

uint is an alias for uint256.

It is better to use uint256: it brings readability and consistency in the code, and it future proofs it in case of any changes to the alias of uint

SEVERITY

Non-Critical

PROOF OF CONCEPT

Instances include:

Funding.sol

Funding.sol: 224 uint citadelAmount = getAmountOut(_assetAmountIn); Funding.sol: 419 uint _citadelPriceInAsset;

TOOLS USED

Manual Analysis

MITIGATION

replace uint with uint256

Gas Report

Table of Contents

Caching storage variables in memory to save gas

PROBLEM

If you are reading from storage more than once, it is cheaper in gas cost to cache the variable in memory: a SLOAD cost 100gas, while MLOAD and MSTORE cost 3 gas.

PROOF OF CONCEPT

Instances include:

CitadelMinter.sol

scope: mintAndDistribute()

  • citadelToken is read twice
CitadelMinter.sol:178 CitadelMinter.sol:217

Funding.sol

scope: claimAssetToTreasury()

  • asset is read 3 times
Funding.sol:339: Funding.sol:341: Funding.sol:343:

scope: updateCitadelPriceInAsset()

  • minCitadelPriceInAsset is read twice
Funding.sol:428 Funding.sol:434
  • maxCitadelPriceInAsset is read twice
Funding.sol:429 Funding.sol:435

KnightingRound.sol

scope: buy()

  • saleStart is read twice
KnightingRound.sol:167 KnightingRound.sol:169
  • totalTokenIn is read twice
KnightingRound.sol:174 KnightingRound.sol:198
  • guestlist is read twice
KnightingRound.sol:178 KnightingRound.sol:179

StakedCitadel.sol

scope: depositFor()

  • token is read 3 times
StakedCitadel.sol:773 StakedCitadel.sol:774 StakedCitadel.sol:775

scope: _withdraw()

  • token is read 3 times
StakedCitadel.sol:815 StakedCitadel.sol:819 StakedCitadel.sol:831

scope: _depositForWithAuthorization()

  • guestlist is read twice
StakedCitadel.sol:793 StakedCitadel.sol:795

scope: setStrategy()

  • strategy is read twice
StakedCitadel.sol:505 StakedCitadel.sol:507

scope: earn()

  • strategy is read twice
StakedCitadel.sol:722 StakedCitadel.sol:723

scope: withdraw()

  • vesting is read twice
StakedCitadel.sol:830 StakedCitadel.sol:831

SupplySchedule.sol

scope: getMintableDebug()

  • globalStartTimestamp is read (5 + endingEpoch - lastEpoch) times: number of reads depending on lastMintTimestamp as it is in a for loop
SupplySchedule.sol:180 SupplySchedule.sol:184 SupplySchedule.sol:197 SupplySchedule.sol:200 SupplySchedule.sol:204 SupplySchedule.sol:211 SupplySchedule.sol:212

TOOLS USED

Manual Analysis

MITIGATION

cache these storage variables in memory

Calldata instead of memory for RO function parameters

PROBLEM

If a reference type function parameter is read-only, it is cheaper in gas to use calldata instead of memory. Calldata is a non-modifiable, non-persistent area where function arguments are stored, and behaves mostly like memory.

Try to use calldata as a data location because it will avoid copies and also makes sure that the data cannot be modified.

PROOF OF CONCEPT

Instances include:

CitadelToken.sol

scope: initialize()

CitadelToken.sol:22: string memory _name, string memory _symbol

GlobalAccessControl.sol

scope: initializeNewRole()

GlobalAccessControl.sol:107: string memory roleString

StakedCitadel.sol

scope: initialize()

StakedCitadel.sol:167: string memory _name, string memory _symbol, uint256[4] memory _feeConfig

scope: deposit()

StakedCitadel.sol:319: bytes32[] memory proof

scope: depositAll()

StakedCitadel.sol:341: bytes32[] memory proof

scope: depositFor()

StakedCitadel.sol:363: bytes32[] memory proof

scope: _depositWithAuthorization()

StakedCitadel.sol:780: bytes32[] memory proof

scope: _depositForWithAuthorization()

StakedCitadel.sol:788: bytes32[] memory proof

TOOLS USED

Manual Analysis

MITIGATION

Replace memory with calldata

Comparisons with zero for unsigned integers

PROBLEM

>0 is less gas efficient than !0 if you enable the optimizer at 10k AND you’re in a require statement. Detailed explanation with the opcodes here

PROOF OF CONCEPT

Instances include:

CitadelMinter.sol

scope: _transferToFundingPools()

CitadelMinter.sol:343

Funding.sol

scope: deposit()

Funding.sol:170

scope: sweep()

Funding.sol:322

scope: claimAssetToTreasury()

Funding.sol:340

scope: updateCitadelPriceInAsset()

Funding.sol:424

scope: updateCitadelPriceInAsset()

Funding.sol:452

KnightingRound.sol

scope: function initialize()

KnightingRound.sol:125 KnightingRound.sol:129

scope: buy()

KnightingRound.sol:172

scope: claim()

KnightingRound.sol:215

scope: setSaleDuration()

KnightingRound.sol:313

scope: setTokenOutPrice()

KnightingRound.sol:332

scope: sweep()

KnightingRound.sol:411

StakedCitadelVester.sol

scope: vest()

StakedCitadelVester.sol:138

SupplySchedule.sol

scope: getEpochAtTimestamp()

SupplySchedule.sol:61

scope: getMintable()

SupplySchedule.sol:91

scope: getMintableDebug()

SupplySchedule.sol:180

TOOLS USED

Manual Analysis

MITIGATION

Replace > 0 with !0

Comparison Operators

PROBLEM

In the EVM, there is no opcode for >= or <=. When using greater than or equal, two operations are performed: > and =.

Using strict comparison operators hence saves gas

PROOF OF CONCEPT

Instances include:

CitadelMinter.sol

CitadelMinter.sol:272

Funding.sol

Funding.sol:172 Funding.sol:178 Funding.sol:270 Funding.sol:271

KnightingRound.sol

KnightingRound.sol:120 KnightingRound.sol:167 KnightingRound.sol:173 KnightingRound.sol:259 KnightingRound.sol:275 KnightingRound.sol:293

StakedCitadel.sol

StakedCitadel.sol:190 StakedCitadel.sol:194 StakedCitadel.sol:198 StakedCitadel.sol:202 StakedCitadel.sol:523 StakedCitadel.sol:535 StakedCitadel.sol:550 StakedCitadel.sol:588 StakedCitadel.sol:613 StakedCitadel.sol:630 StakedCitadel.sol:651 StakedCitadel.sol:666

StakedCitadelVester.sol

StakedCitadelVester:111

SupplySchedule.sol

SupplySchedule.sol:111 SupplySchedule.sol:142 SupplySchedule.sol:208

TOOLS USED

Manual Analysis

MITIGATION

Replace <= with <, and >= with >. Do not forget to increment/decrement the compared variable

example:

-require(citadelAmount_ >= _minCitadelOut, "minCitadelOut"); +require(citadelAmount_ > _minCitadelOut - 1, "minCitadelOut");

When the comparison is with a constant storage variable, you can also do the increment in the storage variable declaration

example:

-require( _feeConfig[1] <= PERFORMANCE_FEE_HARD_CAP, "performanceFeeStrategist too high" ); +require( _feeConfig[1] < PERFORMANCE_FEE_HARD_CAP_PLUS_ONE, "performanceFeeStrategist too high" );

and

-uint256 public constant PERFORMANCE_FEE_HARD_CAP = 3_000; +uint256 public constant PERFORMANCE_FEE_HARD_CAP_PLUS_ONE = 3_001;

Custom Errors

PROBLEM

Custom errors from Solidity 0.8.4 are cheaper than revert strings (cheaper deployment cost and runtime cost when the revert condition is met) while providing the same amount of information, as explained here

Custom errors are defined using the error statement

PROOF OF CONCEPT

Instances include:

CitadelMinter.sol

CitadelMinter.sol:116 CitadelMinter.sol:256 CitadelMinter.sol:272 CitadelMinter.sol:299 CitadelMinter.sol:319 CitadelMinter.sol:326 CitadelMinter.sol:343 CitadelMinter.sol:368 CitadelMinter.sol:374

Funding.sol

Funding.sol:113 Funding.sol:117 Funding.sol:170 Funding.sol:171 Funding.sol:178 Funding.sol:270 Funding.sol:271 Funding.sol:296 Funding.sol:322 Funding.sol:323 Funding.sol:340 Funding.sol:361 Funding.sol:388 Funding.sol:424 Funding.sol:425 Funding.sol:452

GlobalAccessControl.sol

GlobalAccessControl.sol:95 GlobalAccessControl.sol:100 GlobalAccessControl.sol:112 GlobalAccessControl.sol:116

KnightingRound.sol

KnightingRound.sol:120 KnightingRound.sol:124 KnightingRound.sol:128 KnightingRound.sol:132 KnightingRound.sol:167 KnightingRound.sol:168 KnightingRound.sol:172 KnightingRound.sol:173 KnightingRound.sol:179 KnightingRound.sol:185 KnightingRound.sol:210 KnightingRound.sol:211 KnightingRound.sol:215 KnightingRound.sol:273 KnightingRound.sol:274 KnightingRound.sol:275 KnightingRound.sol:293 KnightingRound.sol:297 KnightingRound.sol:312 KnightingRound.sol:316 KnightingRound.sol:331 KnightingRound.sol:349 KnightingRound.sol:384 KnightingRound.sol:411

StakedCitadel.sol

StakedCitadel.sol:180 StakedCitadel.sol:181 StakedCitadel.sol:182 StakedCitadel.sol:183 StakedCitadel.sol:184 StakedCitadel.sol:185 StakedCitadel.sol:186 StakedCitadel.sol:187 StakedCitadel.sol:190 StakedCitadel.sol:194 StakedCitadel.sol:198 StakedCitadel.sol:202 StakedCitadel.sol:262 StakedCitadel.sol:270 StakedCitadel.sol:441 StakedCitadel.sol:487 StakedCitadel.sol:502 StakedCitadel.sol:506 StakedCitadel.sol:523 StakedCitadel.sol:535 StakedCitadel.sol:550 StakedCitadel.sol:562 StakedCitadel.sol:574 StakedCitadel.sol:588 StakedCitadel.sol:613 StakedCitadel.sol:630 StakedCitadel.sol:650 StakedCitadel.sol:666 StakedCitadel.sol:700 StakedCitadel.sol:718 StakedCitadel.sol:768 StakedCitadel.sol:769 StakedCitadel.sol:770 StakedCitadel.sol:794 StakedCitadel.sol:809

StakedCitadelVester.sol

StakedCitadelVester.sol:64 StakedCitadelVester.sol:65 StakedCitadelVester.sol:137 StakedCitadelVester.sol:138

SupplySchedule.sol

SupplySchedule.sol:60 SupplySchedule.sol:90 SupplySchedule.sol:94 SupplySchedule.sol:137 SupplySchedule.sol:141 SupplySchedule.sol:155 SupplySchedule.sol:179 SupplySchedule.sol:183 SupplySchedule.sol:187

TOOLS USED

Manual Analysis

MITIGATION

Replace require statements with custom errors.

Default value initialization

PROBLEM

If a variable is not set/initialized, it is assumed to have the default value (0, false, 0x0 etc depending on the data type). Explicitly initializing it with its default value is an anti-pattern and wastes gas.

PROOF OF CONCEPT

Instances include:

CitadelMinter.sol

CitadelMinter.sol:152: for (uint256 i = 0; i < numPools; i++) { CitadelMinter.sol:180: uint256 lockingAmount = 0; CitadelMinter.sol:181: uint256 stakingAmount = 0; CitadelMinter.sol:182: uint256 fundingAmount = 0;

SupplySchedule.sol

SupplySchedule.sol:103: uint256 mintable = 0; SupplySchedule.sol:192: uint256 mintable = 0;

TOOLS USED

Manual Analysis

MITIGATION

Remove explicit initialization for default values.

Event emitting of local variable

PROBLEM

When emitting an event, using a local variable instead of a storage variable saves gas.

PROOF OF CONCEPT

Instances include:

Funding.sol

Funding.sol: 343

SupplySchedule.sol

SupplySchedule.sol: 197

TOOLS USED

Manual Analysis

MITIGATION

In both instances, the storage variable is read multiple times in the function and it is recommended to cache them into memory, then passing these cached variables in the emit statement, as explained in the cache paragraph

Inline functions

PROBLEM

When we define internal functions to perform computation:

  • The contract’s code size gets bigger
  • the function call consumes more gas than executing it as an inlined function (part of the code, without the function call)

When it does not affect readability, it is recommended to inline functions in order to save gas

PROOF OF CONCEPT

Instances include:

StakedCitadel.sol

StakedCitadel.sol: 780

TOOLS USED

Manual Analysis

MITIGATION

Inline this function where it is called:

StakedCitadel.sol: 310 StakedCitadel.sol: 323 StakedCitadel.sol: 330 StakedCitadel.sol: 342

Prefix increments

PROBLEM

Prefix increments are cheaper than postfix increments.

PROOF OF CONCEPT

Instances include:

CitadelMinter.sol

CitadelMinter.sol:152 CitadelMinter.sol:344

SupplySchedule.sol

SupplySchedule.sol:208

TOOLS USED

Manual Analysis

MITIGATION

change i++ to ++i

Short-circuiting

PROBLEM

Reading from storage costs 100 gas. When checking conditions using the AND operator, we can save gas by not evaluating the second expression if the first one is false.

PROOF OF CONCEPT

Instances include:

CitadelMinter.sol

CitadelMinter.sol:266

TOOLS USED

Manual Analysis

MITIGATION

  • Remove the declaration of poolExists line 261 and inline the computation of fundingPools.contains(_pool) line 266 and 273.

Tight Variable Packing

PROBLEM

Solidity contracts have contiguous 32 bytes (256 bits) slots used in storage. By arranging the variables, it is possible to minimize the number of slots used within a contract's storage and therefore reduce deployment costs.

address type variables are each of 20 bytes size (way less than 32 bytes). However, they here take up a whole 32 bytes slot (they are contiguous).

As bool type variables are of size 1 byte, there's a slot here that can get saved by moving them closer to an address

PROOF OF CONCEPT

Instances include:

Funding.sol

Funding.sol:39: bool public citadelPriceFlag; /// Flag citadel price for review by guardian if it exceeds min and max bounds; uint256 public assetDecimalsNormalizationValue; address public citadelPriceInAssetOracle; address public saleRecipient;

TOOLS USED

Manual Analysis

MITIGATION

Place citadelPriceFlag after SaleRecipient to save one storage slot

uint256 public assetDecimalsNormalizationValue; address public citadelPriceInAssetOracle; address public saleRecipient; +bool public citadelPriceFlag;

Uint256 instead of Uint8

PROBLEM

Each storage slot has 256 bits. Whenever a uint smaller than 256 (or even a bool) is pulled from storage, the EVM casts it to a uint256. Calculations are also unexceptionally performed in uint256 by the EVM.

PROOF OF CONCEPT

Instances include

KnightingRound.sol

KnightingRound.sol:69 KnightingRound.sol:70 KnightingRound.sol:76 KnightingRound.sol:162

TOOLS USED

Manual Analysis

MITIGATION

Replace uint8 with uint256

Unchecked arithmetic

PROBLEM

The default "checked" behavior costs more gas when adding/diving/multiplying, because under-the-hood those checks are implemented as a series of opcodes that, prior to performing the actual arithmetic, check for under/overflow and revert if it is detected.

if it can statically be determined there is no possible way for your arithmetic to under/overflow (such as a condition in an if statement), surrounding the arithmetic in an unchecked block will save gas

PROOF OF CONCEPT

Instances include:

CitadelMinter.sol

CitadelMinter.sol: 152: i bounded by numPools, overflow check unnecessary CitadelMinter.sol: 344: i bounded by length, overflow check unnecessary CitadelMinter.sol: 364: totalFundingPoolWeight is greater than or equal to currentPoolWeight, underflow check unnecessary

Funding.sol

Funding.sol: 212: MAX_BPS is less than the maximum discount, underflow check unnecessary Funding.sol: 236: assetCumulativeFunded is less than assetCap, underflow check unnecessary

KnightingRound.sol

KnightingRound.sol: 198: totalTokenIn + _tokenInAmount is capped by tokenInLimit, overflow check unnecessary KnightingRound.sol: 250: totalTokenIn is less than tokenInLimit, underflow check unnecessary

StakedCitadel.sol

StakedCitadel.sol: 817: b is less than r, underflow check unnecessary StakedCitadel.sol: 821: b + diff capped by r (see line 821 and 817), overflow check unnecessary StakedCitadel.sol: 827: _fee is less than r, underflow check unnecessary

SupplySchedule.sol

SupplySchedule.sol: 208: i is capped by endingEpoch, overflow check unnecessary

TOOLS USED

Manual Analysis

MITIGATION

Place the arithmetic operations in an unchecked block

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