Platform: Code4rena
Start Date: 04/11/2021
Pot Size: $50,000 USDC
Total HM: 20
Participants: 28
Period: 7 days
Judge: 0xean
Total Solo HM: 11
Id: 51
League: ETH
Rank: 5/28
Findings: 6
Award: $3,273.77
π Selected for report: 10
π Solo Findings: 0
1308.4274 USDC - $1,308.43
Reigada
As we can see in the contracts AirdropDistribution and InvestorDistribution, they both have the following approve() call: mainToken.approve(address(vestLock), 2**256-1); https://github.com/code-423n4/2021-11-bootfinance/blob/main/vesting/contracts/AirdropDistribution.sol#L499 https://github.com/code-423n4/2021-11-bootfinance/blob/main/vesting/contracts/InvestorDistribution.sol#L80
This is necessary because both contracts transfer tokens to the vesting contract by calling its vest() function: https://github.com/code-423n4/2021-11-bootfinance/blob/main/vesting/contracts/AirdropDistribution.sol#L544 https://github.com/code-423n4/2021-11-bootfinance/blob/main/vesting/contracts/AirdropDistribution.sol#L569 https://github.com/code-423n4/2021-11-bootfinance/blob/main/vesting/contracts/InvestorDistribution.sol#L134 https://github.com/code-423n4/2021-11-bootfinance/blob/main/vesting/contracts/InvestorDistribution.sol#L158
The code of the vest() function in the Vesting contract performs a transfer from msg.sender to Vesting contract address -> vestingToken.transferFrom(msg.sender, address(this), _amount); https://github.com/code-423n4/2021-11-bootfinance/blob/main/vesting/contracts/Vesting.sol#L95
Same is done in the BasicSale contract: https://github.com/code-423n4/2021-11-bootfinance/blob/main/tge/contracts/PublicSale.sol#L225
The problem is that this contract is missing the approve() call. For that reason, the contract is totally useless as the function _withdrawShare() will always revert with the following message: revert reason: ERC20: transfer amount exceeds allowance. This means that all the mainToken sent to the contract would be stuck there forever. No way to retrieve them.
How this issue was not detected in the testing phase? Very simple. The mock used by the team has an empty vest() function that performs no transfer call. https://github.com/code-423n4/2021-11-bootfinance/blob/main/tge/contracts/helper/MockVesting.sol#L10
See below Brownie's custom output: Calling -> publicsale.withdrawShare(1, 1, {'from': user2}) Transaction sent: 0x9976e4f48bd14f9be8e3e0f4d80fdb8f660afab96a7cbd64fa252510154e7fde Gas price: 0.0 gwei Gas limit: 6721975 Nonce: 5 BasicSale.withdrawShare confirmed (ERC20: transfer amount exceeds allowance) Block: 13577532 Gas used: 323334 (4.81%)
Call trace for '0x9976e4f48bd14f9be8e3e0f4d80fdb8f660afab96a7cbd64fa252510154e7fde': Initial call cost [21344 gas] BasicSale.withdrawShare 0:3724 [16114 / -193010 gas] βββ BasicSale._withdrawShare 111:1109 [8643 / 63957 gas] β βββ BasicSale._updateEmission 116:405 [53294 / 55739 gas] β β βββ BasicSale.getDayEmission 233:248 [2445 gas] β βββ BasicSale._processWithdrawal 437:993 [-7726 / -616 gas] β β βββ BasicSale.getEmissionShare 484:859 [4956 / 6919 gas] β β β β β β β βββ MockERC20.balanceOf [STATICCALL] 616:738 [1963 gas] β β β βββ address: mockerc20.address β β β βββ input arguments: β β β β βββ account: publicsale.address β β β βββ return value: 100000000000000000000 β β β β β βββ SafeMath.sub 924:984 [191 gas] β βββ SafeMath.sub 1040:1100 [191 gas] β βββ MockERC20.transfer [CALL] 1269:1554 [1115 / 30109 gas] β β βββ address: mockerc20.address β β βββ value: 0 β β βββ input arguments: β β β βββ recipient: user2.address β β β βββ amount: 27272727272727272727 β β βββ return value: True β β β βββ ERC20.transfer 1366:1534 [50 / 28994 gas] β βββ ERC20._transfer 1374:1526 [28944 gas] βββ Vesting.vest [CALL] 1705:3712 [-330491 / -303190 gas] β βββ address: vesting.address β βββ value: 0 β βββ input arguments: β β βββ _beneficiary: user2.address β β βββ _amount: 63636363636363636363 β β βββ _isRevocable: 0 β βββ revert reason: ERC20: transfer amount exceeds allowance <------------- β βββ SafeMath.add 1855:1883 [94 gas] βββ SafeMath.add 3182:3210 [94 gas] βββ SafeMath.add 3236:3264 [94 gas] β βββ MockERC20.transferFrom [CALL] 3341:3700 [99923 / 27019 gas] β βββ address: mockerc20.address β βββ value: 0 β βββ input arguments: β β βββ sender: publicsale.address β β βββ recipient: vesting.address β β βββ amount: 63636363636363636363 β βββ revert reason: ERC20: transfer amount exceeds allowance β βββ ERC20.transferFrom 3465:3700 [-97648 / -72904 gas] βββ ERC20._transfer 3473:3625 [24744 gas]
Manual testing
The following approve() call should be added in the constructor of the BasicSale contract: mainToken.approve(address(vestLock), 2**256-1);
52.1514 USDC - $52.15
Reigada
Multiple calls to transferFrom and transfer are frequently done without checking the results. For certain ERC20 tokens, if insufficient tokens are present, no revert occurs but a result of βfalseβ is returned. Itβs important to check this. If you donβt, in this concrete case, some airdrop eligible participants could be left without their tokens. It is also a best practice to check this.
AirdropDistributionMock.sol:132: mainToken.transfer(msg.sender, claimable_to_send); AirdropDistributionMock.sol:157: mainToken.transfer(msg.sender, claimable_to_send); AirdropDistribution.sol:542: mainToken.transfer(msg.sender, claimable_to_send); AirdropDistribution.sol:567: mainToken.transfer(msg.sender, claimable_to_send);
InvestorDistribution.sol:132: mainToken.transfer(msg.sender, claimable_to_send); InvestorDistribution.sol:156: mainToken.transfer(msg.sender, claimable_to_send); InvestorDistribution.sol:207: mainToken.transfer(msg.sender, bal);
Vesting.sol:95: vestingToken.transferFrom(msg.sender, address(this), _amount);
PublicSale.sol:224: mainToken.transfer(_member, v_value);
Manual testing
Check the result of transferFrom and transfer. Although if this is done, the contracts will not be compatible with non standard ERC20 tokens like USDT. For that reason, I would rather recommend making use of SafeERC20 library: safeTransfer and safeTransferFrom.
Reigada
The return value of these low-level calls are not checked, so if the call fails, the Ether will be locked in the contract. Setting the risk as medium as the smart contract has no function to withdraw the Ether. This Ether would remain stuck in the contract forever.
BasicSale.receive() (contracts/PublicSale.sol#148-156) ignores return value by burnAddress.call{value: msg.value}() (contracts/PublicSale.sol#154) BasicSale.burnEtherForMember(address) (contracts/PublicSale.sol#158-166) ignores return value by burnAddress.call{value: msg.value}() (contracts/PublicSale.sol#164)
https://github.com/code-423n4/2021-11-bootfinance/blob/main/tge/contracts/PublicSale.sol#L154 https://github.com/code-423n4/2021-11-bootfinance/blob/main/tge/contracts/PublicSale.sol#L164
Slither
Add a require statement that checks that the low-level call was successful.
#0 - chickenpie347
2021-11-16T14:08:09Z
Addressed in #145
Reigada
The contract InvestorDistribution does not follow the transfer ownership pattern. The current ownership transfer process involves the current admin calling InvestorDistribution.setAdmin(). This function checks that the caller is the current admin and that the new admin is not the zero address and proceeds to write the new adminβs address into the adminβs state variable. If the nominated EOA account is not a valid account, it is entirely possible the admin may accidentally transfer ownership to an uncontrolled account. That account would be able to call dev_rugpull() (after 5 years) claiming the unclaimed tokens. On the other hand, it is also possible and more likely that the new account does not realize the permissions gotten over the smart contract and that the unclaimed tokens remain in the contract stuck forever.
Manual testing
Consider implementing a two step process where the admin/owner nominates an account and the nominated account needs to call an acceptOwnership() function for the transfer of ownership to fully succeed. This ensures the nominated EOA account is a valid and active account.
#0 - chickenpie347
2022-01-03T21:16:32Z
Duplicate of #35
Reigada
In the contract BasicSale, the constructor is missing a zero address check on _burnAddress parameter: https://github.com/code-423n4/2021-11-bootfinance/blob/main/tge/contracts/PublicSale.sol#L112
As this address is where all the funds are sent it is very important to check it is valid.
Manual testing
Add the following require statement in the constructor: require(_burnAddress != address(0), "Invalid Burn address");
#0 - chickenpie347
2022-01-03T23:03:20Z
Duplicate of #146
π Selected for report: Reigada
290.7617 USDC - $290.76
Reigada
In the contracts AirdropDistribution and InvestorDistribution the event Vested is emitted with a wrong amount. The amount that should be used are:
function claim() external nonReentrant { require(msg.sender != address(0)); require(validated[msg.sender] == 1, "Address not validated to claim."); require(airdrop[msg.sender].amount != 0); uint256 avail = _available_supply(); require(avail > 0, "Nothing claimable (yet?)"); uint256 claimable = avail * airdrop[msg.sender].fraction / 10**18; assert(claimable > 0); if (airdrop[msg.sender].claimed != 0) { claimable -= airdrop[msg.sender].claimed; } assert(airdrop[msg.sender].amount - claimable != 0); airdrop[msg.sender].amount -= claimable; airdrop[msg.sender].claimed += claimable; uint256 claimable_to_send = claimable * 3 / 10; //30% released instantly mainToken.transfer(msg.sender, claimable_to_send); uint256 claimable_not_yet_vested = claimable - claimable_to_send; vestLock.vest(msg.sender, claimable_not_yet_vested, 0); //70% locked in vesting contract emit Vested(msg.sender, claimable, block.timestamp); } //Allow users to claim a specific amount instead of the entire amount function claimExact(uint256 _value) external nonReentrant { require(msg.sender != address(0)); require(airdrop[msg.sender].amount != 0); uint256 avail = _available_supply(); uint256 claimable = avail * airdrop[msg.sender].fraction / 10**18; // if (airdrop[msg.sender].claimed != 0){ claimable -= airdrop[msg.sender].claimed; } require(airdrop[msg.sender].amount >= claimable); require(_value <= claimable); airdrop[msg.sender].amount -= _value; airdrop[msg.sender].claimed += _value; uint256 claimable_to_send = _value * 3 / 10; mainToken.transfer(msg.sender, claimable_to_send); uint256 claimable_not_yet_vested = _value - claimable_to_send; vestLock.vest(msg.sender, claimable_not_yet_vested, 0); emit Vested(msg.sender, _value, block.timestamp); }
Manual testing
#0 - veloboot
2021-11-21T01:50:35Z
This may be a semantic issue since, in the latest version, the amount claimed is now emitted with a Claimed event rather than the vested amount that already has its own distinct event emitted from the Vesting contract. However, this was a bug in the code that was presented for audit. The recommended mitigation steps will not be followed, however.
π Selected for report: Reigada
290.7617 USDC - $290.76
Reigada
In both airdrop contracts: AirdropDistribution and InvestorDistribution, once all the participants have claimed their tokens, some will remain in the contract due to some imprecision in the calculations. There is no function that allows to substract them which means that those tokens will remain stuck in the contracts forever.
I have made the test with just 5 participants: user2, user3, user4, user5 & user6.
uint256[5] airdropBalances = [ 4032000, 4032000, 4032000, 4032000, 4032000 ];
4032000 * 5 20160000
Initially 20160000 tokens were transferred to the contract mockToken.transfer(airdropdist.address, 20160000000000000000000000)
After 260 weeks, these were the results:
----------------> mockToken.balanceOf(airdropdist.address) -> 2842805668532461833600 <-----------------
mockToken.balanceOf(user2) -> 1209429431659888052289865 vesting.benTotal(user2.address) -> 2822002007206405455343415 mockToken.balanceOf(user3) -> 1209429431659888052289984 vesting.benTotal(user3.address) -> 2822002007206405455343296 mockToken.balanceOf(user4) -> 1209429431659888052289984 vesting.benTotal(user4.address) -> 2822002007206405455343296 mockToken.balanceOf(user5) -> 1209429431659888052289984 vesting.benTotal(user5.address) -> 2822002007206405455343296 mockToken.balanceOf(user6) -> 1209429431659888052289984 vesting.benTotal(user6.address) -> 2822002007206405455343296
As we can see above 2842 tokens remain in the contract and there is no way to retrieve them.
Manual testing / brownie
Add an onlyOwner function that allows to retrieve all the remaining tokens once all the participants of the airdrop have claimed the whole amount of their rewards.
π Selected for report: Reigada
290.7617 USDC - $290.76
Reigada
In the Vesting contract, the function revoke() sends the vested tokens to the beneficiary and the remaining tokens that are not vested yet are sent to the multisig address. It makes no sense to allow calling this function once the address has already vested the 100% of the tokens (after 1 year in this case -> uint256 _unlockTimestamp = block.timestamp.add(unixYear);).
Basically in this case the function revoke() would behave like a claim() function but doing some extra checks which waste gas (gas paid by the owner of the contract instead of the beneficiary address) and also emitting an extra event -> https://github.com/code-423n4/2021-11-bootfinance/blob/main/vesting/contracts/Vesting.sol#L123
For that reason, it is recommended to add a require statement that handles this case:
uint256 index = timelocks[_addr].length - 1; require (block.timestamp < timelocks[_addr][index].releaseTimestamp, 'Account fully vested');
Manual testing
It is recommended to add a require statement that handles this case in the Vesting.revoke() function:
uint256 index = timelocks[_addr].length - 1; require (block.timestamp < timelocks[_addr][index].releaseTimestamp, 'Account fully vested');
π Selected for report: Reigada
290.7617 USDC - $290.76
Reigada
The contract BasicSale contains a fallback function and a burnEtherForMember() function with exactly the same implementation. These 2 functions do the following call: _recordBurn(msg.sender, msg.sender, currentEra, currentDay, msg.value);
The _recordBurn function contains the following if block: if (mapEraDay_MemberUnits[_era][_day][_member] == 0) { // If hasn't contributed to this Day yet mapMemberEra_Days[_member][_era].push(_day); // Add it mapEraDay_MemberCount[_era][_day] += 1; // Count member mapEraDay_Members[_era][_day].push(_member); // Add member }
What does this mean? If a user performs multiple calls to the contract sending 0 ether as msg.value, the if block will be entered and a new key will be pushed to the mapping. Luckily the cost of an addition to or a read from a mapping does not change with the number of keys mapped. But this would totally mess the function getDaysContributedForEra output. Currently this function is only used as a view function, and not used by the smart contract itself. But it's a risk for future implementations that may make use of it.
user2.transfer(to=publicsale.address, amount=0) Transaction sent: 0xd65ffecd5052314bc09f616461ff6aa9efeb857151c0339dc15653fb90ebb91f Gas price: 0.0 gwei Gas limit: 6721975 Nonce: 0 Transaction confirmed Block: 13577879 Gas used: 117368 (1.75%)
<Transaction '0xd65ffecd5052314bc09f616461ff6aa9efeb857151c0339dc15653fb90ebb91f'>
publicsale.getDaysContributedForEra(user2.address, 1) 1
user2.transfer(to=publicsale.address, amount=0) Transaction sent: 0x0a48f89c1266af2c3eea5cdcec93dae76e4ed2a0936e53e8713d669989b88b19 Gas price: 0.0 gwei Gas limit: 6721975 Nonce: 1 Transaction confirmed Block: 13577880 Gas used: 91568 (1.36%)
<Transaction '0x0a48f89c1266af2c3eea5cdcec93dae76e4ed2a0936e53e8713d669989b88b19'>
publicsale.getDaysContributedForEra(user2.address, 1) 2
user2.transfer(to=publicsale.address, amount=0) Transaction sent: 0xeb85e31664eb1578f54daba1be112f9533948d0e2414510874ed55fbb3a9a9e0 Gas price: 0.0 gwei Gas limit: 6721975 Nonce: 2 Transaction confirmed Block: 13577881 Gas used: 91568 (1.36%)
<Transaction '0xeb85e31664eb1578f54daba1be112f9533948d0e2414510874ed55fbb3a9a9e0'>
publicsale.getDaysContributedForEra(user2.address, 1) 3
Manual testing
Add the following require statement to the fallback and the burnEtherForMember() functions: require(msg.value > 0, "Some ether should be sent")
π Selected for report: Reigada
Also found by: Meta0xNull
20.1994 USDC - $20.20
Reigada
There is an unnecessary require statement in vesting.claim() -> https://github.com/code-423n4/2021-11-bootfinance/blob/main/vesting/contracts/Vesting.sol#L197
This check is already done in https://github.com/code-423n4/2021-11-bootfinance/blob/main/vesting/contracts/Vesting.sol#L186
Manual testing
Remove the require statement in the claim() function as it is totally unnecessary. The check is already performed in the function _claimableAmount(address _addr).
#0 - 0xean
2022-01-08T03:07:23Z
This is more of a gas savings since there is no risk to the additional check.
Reigada
The following require checks in the Swap and SwapUtils contracts can be removed to save some gas as a variable declared as an uint256 will always be >=0
https://github.com/code-423n4/2021-11-bootfinance/blob/main/customswap/contracts/Swap.sol#L190 -> a >= 0 can be removed https://github.com/code-423n4/2021-11-bootfinance/blob/main/customswap/contracts/Swap.sol#L191 -> a2 >= 0 can be removed https://github.com/code-423n4/2021-11-bootfinance/blob/main/customswap/contracts/SwapUtils.sol#L1563-L1566 -> Whole require statement can be removed here https://github.com/code-423n4/2021-11-bootfinance/blob/main/customswap/contracts/SwapUtils.sol#L1624 -> futureA >= 0 can be removed https://github.com/code-423n4/2021-11-bootfinance/blob/main/customswap/contracts/SwapUtils.sol#L1678 -> futureA2 >= 0 can be removed
Slither
Remove the tautology expressions mentioned to save some gas
#0 - chickenpie347
2022-01-04T02:17:23Z
Duplicate of #133
20.1994 USDC - $20.20
Reigada
In the contract Vesting, function vest(), the parameter _isRevocable is declared as an uint256 when it is used as a boolean.
https://github.com/code-423n4/2021-11-bootfinance/blob/main/vesting/contracts/Vesting.sol#L77
Manual review
Declare the parameter _isRevocable as a bool.
12.1196 USDC - $12.12
Reigada
The if statement in _updateEmission() can be removed as the condition is already checked in updateEmission() https://github.com/code-423n4/2021-11-bootfinance/blob/main/vesting/contracts/AirdropDistribution.sol#L596 https://github.com/code-423n4/2021-11-bootfinance/blob/main/vesting/contracts/InvestorDistribution.sol#L186
function _updateEmission() private { if (block.timestamp >= startEpochTime + RATE_TIME) { miningEpoch += 1; startEpochTime = startEpochTime.add(RATE_TIME); startEpochSupply = startEpochSupply.add(rate.mul(RATE_TIME)); if (miningEpoch < INITIAL_RATE_EPOCH_CUTTOF) { rate = rate.mul(EPOCH_INFLATION).div(100000); } else { rate = 0; } emit updateMiningParameters(block.timestamp, rate, startEpochSupply); } } //Update emission to be called at every step change to update emission inflation function updateEmission() public { require(block.timestamp >= startEpochTime + RATE_TIME, "Too soon"); // Condition already checked here _updateEmission(); }
Manual testing
Remove the if condition in the _updateEmission() private function
π Selected for report: TomFrenchBlockchain
Also found by: PranavG, Reigada, WatchPug, jah, nathaniel, pants, pauliax, pmerkleplant
Reigada
In order to save some gas, for example, contract AirdropDistribution:
This issue applies to most of the smart contracts in scope.
https://docs.soliditylang.org/en/v0.8.9/contracts.html#constant-and-immutable-state-variables
Manual testing
Check all the state variables. If the state variable will be written only in the constructor, declare it a immutable. If if will be a read only variable, declare it as constant.
#0 - chickenpie347
2022-01-03T21:19:24Z
Duplicate of #1 , #3
Reigada
As i is an uint, it is already initialized to 0. uint i = 0 reassings the 0 to i which wastes gas.
AirdropDistributionMock.sol:97: for (uint i = 0; i < airdropArray.length; i++) { PublicSaleBatchWithdraw.sol:29: for (uint i = 0; i < arrayDays.length; i++) { PublicSaleBatchWithdraw.sol:36: for (uint i = 0; i < length; i++) { AirdropDistribution.sol:507: for (uint i = 0; i < airdropArray.length; i++) {
Manual testing
Do not initialize i variable to 0 -> for (uint i; i < airdropArray.length; i++) {
#0 - chickenpie347
2022-01-03T21:08:45Z
Duplicate of #5
Reigada
There are functions marked as public but they are never directly called within the same contract or in any of its descendants, hence they can be declared as external to save some gas:
Slither
Declare those functions as external to save some gas.
#0 - chickenpie347
2022-01-04T01:50:45Z
Duplicate of #121
Reigada
Contracts: AirdropDistribution.sol, InvestorDistribution.sol, Vesting.sol and PublicSale.sol are using SafeMath with a pragma ^0.8.0. This makes no sense and just causes more gas to be used as versions ^0.8.0 of Solidity already check for overflows/underflows.
https://github.com/ethereum/solidity/releases/tag/v0.8.0
Manual testing
Remove SafeMath and modify the mathematical operations to use the standard math operators.
#0 - chickenpie347
2022-01-03T21:18:25Z
Duplicate of #7
π Selected for report: pmerkleplant
12.1196 USDC - $12.12
Reigada
The following state variables are declared but never used in the BasicSale contract, hence they can be removed: BasicSale._balances - https://github.com/code-423n4/2021-11-bootfinance/blob/main/tge/contracts/PublicSale.sol#L56 BasicSale._allowances - https://github.com/code-423n4/2021-11-bootfinance/blob/main/tge/contracts/PublicSale.sol#L57
Slither
Remove those 2 state variables to save gas.
#0 - chickenpie347
2022-01-04T01:54:29Z
Duplicate of #161
12.1196 USDC - $12.12
Reigada
Increased gas costs
A uint8 is used as the for loop variable in the Swap contract: https://github.com/code-423n4/2021-11-bootfinance/blob/main/customswap/contracts/Swap.sol#L158
Due to how the EVM natively works on 256 numbers, using a 8 bit number here introduces additional costs as the EVM has to properly enforce the limits of this smaller type.
See the warning at this link: https://docs.soliditylang.org/en/v0.8.0/internals/layout_in_storage.html#layout-of-state-variables-in-storage
Manual review
Change i to be a uint256
#0 - chickenpie347
2022-01-04T02:13:41Z
Duplicate of #175
π Selected for report: Reigada
Reigada
In all the loops, the variable i is incremented using i++. It is known that using ++i costs less gas per iteration than i++.
AirdropDistributionMock.sol:97: for (uint i = 0; i < airdropArray.length; i++) { SwapUtils.sol:433: for (uint256 i = 0; i < self.pooledTokens.length; i++) { SwapUtils.sol:535: for (uint256 i = 0; i < numTokens; i++) { SwapUtils.sol:549: for (uint256 i = 0; i < MAX_LOOP_LIMIT; i++) { SwapUtils.sol:574: for (uint256 i = 0; i < numTokens; i++) { SwapUtils.sol:585: for (uint256 i = 0; i < MAX_LOOP_LIMIT; i++) { SwapUtils.sol:648: for (uint256 i = 0; i < numTokens; i++) { SwapUtils.sol:852: for (uint256 i = 0; i < numTokens; i++) { SwapUtils.sol:872: for (uint256 i = 0; i < MAX_LOOP_LIMIT; i++) { SwapUtils.sol:970: for (uint256 i = 0; i < self.pooledTokens.length; i++) { SwapUtils.sol:1031: for (uint256 i = 0; i < numTokens; i++) { SwapUtils.sol:1190: for (uint256 i = 0; i < self.pooledTokens.length; i++) { SwapUtils.sol:1230: for (uint256 i = 0; i < self.pooledTokens.length; i++) { SwapUtils.sol:1341: for (uint256 i = 0; i < amounts.length; i++) { SwapUtils.sol:1440: for (uint256 i = 0; i < self.pooledTokens.length; i++) { SwapUtils.sol:1449: for (uint256 i = 0; i < self.pooledTokens.length; i++) { SwapUtils.sol:1471: for (uint256 i = 0; i < self.pooledTokens.length; i++) { SwapUtils.sol:1492: for (uint256 i = 0; i < self.pooledTokens.length; i++) { Vesting.sol:116: for (uint256 i = 0; i < timelocks[_addr].length; i++) { Vesting.sol:148: for (uint256 i = 0; i < timelocks[_addr].length; i++) { Vesting.sol:170: for (uint256 i = benVestingIndex[_addr]; i < timelocks[_addr].length; i++) { helper/test/TestSwapReturnValues.sol:27: for (uint8 i; i < n; i++) { helper/test/TestSwapReturnValues.sol:95: for (uint8 i = 0; i < n; i++) { helper/test/TestSwapReturnValues.sol:102: for (uint8 i = 0; i < n; i++) { Swap.sol:158: for (uint8 i = 0; i < _pooledTokens.length; i++) { PublicSaleBatchWithdraw.sol:29: for (uint i = 0; i < arrayDays.length; i++) { PublicSaleBatchWithdraw.sol:36: for (uint i = 0; i < length; i++) { AirdropDistribution.sol:507: for (uint i = 0; i < airdropArray.length; i++) {
Manual testing
Use ++i instead of i++ to increment the value of an uint variable.
#0 - chickenpie347
2022-01-03T21:09:24Z
Duplicate of #12
#1 - 0xean
2022-01-09T00:28:06Z
Not a duplicate of #12 which suggests unchecked, this has to do with the pre-increment vs increment.