Platform: Code4rena
Start Date: 30/03/2022
Pot Size: $30,000 USDC
Total HM: 21
Participants: 38
Period: 3 days
Judge: Michael De Luca
Total Solo HM: 10
Id: 104
League: ETH
Rank: 25/38
Findings: 3
Award: $197.77
🌟 Selected for report: 0
🚀 Solo Findings: 0
61.7413 USDC - $61.74
Some ERC20 tokens, such as USDT, don't revert when transfer/transferFrom fails. The transfer return value has to be checked (as there are some other tokens that returns false instead revert). safeTransfer should be used instead of transfer
safeTransferFrom should be used instead of transferFrom on this line https://github.com/code-423n4/2022-03-joyn/blob/main/core-contracts/contracts/CoreCollection.sol#L175
Manual analysis
Use safeTransfer instead of transfer or check the return value of transfer. The return value of transfer is checked properly in these locations https://github.com/code-423n4/2022-03-joyn/blob/main/royalty-vault/contracts/RoyaltyVault.sol#L44 https://github.com/code-423n4/2022-03-joyn/blob/main/royalty-vault/contracts/RoyaltyVault.sol#L52 https://github.com/code-423n4/2022-03-joyn/blob/main/splits/contracts/Splitter.sol#L237
The CoreCollection initialize function is missing the onlyUnInitialized function. The onlyUnInitialized modifier is not used in the contract right now and this allows the initialize function to be called more than once.
The onlyUnInitialized modifier prevents functions from being run if the initialized is set to true https://github.com/code-423n4/2022-03-joyn/blob/main/core-contracts/contracts/CoreCollection.sol#L46
This modifier is missing from the initialize function https://github.com/code-423n4/2022-03-joyn/blob/main/core-contracts/contracts/CoreCollection.sol#L87
Manual analysis
Add the onlyUnInitialized modifier to the initialize function
#0 - sofianeOuafir
2022-04-14T19:48:27Z
In my opinion, the severity level should be 3 (High Risk)
duplicate of #4 & #52
#1 - deluca-mike
2022-04-22T04:47:54Z
Second issue became #136
65.8604 USDC - $65.86
Using a prefix increment (++i) instead of a postfix increment (i++) saves gas for each loop cycle and so can have a big gas impact when the loop executes on a large number of elements.
There is one example in CoreFactory
https://github.com/code-423n4/2022-03-joyn/blob/main/core-contracts/contracts/CoreFactory.sol#L79
for (uint256 i; i < _collections.length; i++) {
Two examples in Splitter https://github.com/code-423n4/2022-03-joyn/blob/main/splits/contracts/Splitter.sol#L50 https://github.com/code-423n4/2022-03-joyn/blob/main/splits/contracts/Splitter.sol#L274
Manual analysis
Use prefix not postfix to increment in a loop
Use calldata instead of memory for function parameters. Having function arguments use calldata instead of memory can save gas.
There are many cases of function arguments using memory instead of calldata https://github.com/code-423n4/2022-03-joyn/blob/main/core-contracts/contracts/CoreFactory.sol#L71-L72 https://github.com/code-423n4/2022-03-joyn/blob/main/core-contracts/contracts/CoreFactory.sol#L109-L110 https://github.com/code-423n4/2022-03-joyn/blob/main/core-contracts/contracts/CoreFactory.sol#L128 https://github.com/code-423n4/2022-03-joyn/blob/main/core-contracts/contracts/CoreFactory.sol#L142 https://github.com/code-423n4/2022-03-joyn/blob/main/core-contracts/contracts/CoreCollection.sol#L79-L81 https://github.com/code-423n4/2022-03-joyn/blob/main/core-contracts/contracts/CoreCollection.sol#L122-L123
Manual analysis
Change function arguments from memory to calldata
Identifying a function as payable saves gas. Functions that have the onlyOwner modifier cannot be called by normal users and will not mistakenly receive ETH. These functions can be payable to save gas.
There are many functions that have the onlyOwner modifier and can be payable
initializeClaims function https://github.com/code-423n4/2022-03-joyn/blob/main/core-contracts/contracts/CoreCollection.sol#L105
setCollectionMeta function https://github.com/code-423n4/2022-03-joyn/blob/main/core-contracts/contracts/CoreCollection.sol#L121
withdraw function https://github.com/code-423n4/2022-03-joyn/blob/main/core-contracts/contracts/CoreCollection.sol#L173
setHashedProof function https://github.com/code-423n4/2022-03-joyn/blob/main/core-contracts/contracts/CoreCollection.sol#L203
initialize function https://github.com/code-423n4/2022-03-joyn/blob/main/core-contracts/contracts/CoreCollection.sol#L87
Manual analysis
Add payable to these functions for gas savings
Strings in solidity are handled in 32 byte chunks. A require string longer than 32 bytes uses more gas. Shortening these strings will save gas.
Several cases of this gas optimization were found. These are a few examples, but more may exist
Manual analysis
Shorten require strings
The tokenExists modifier is never used in CoreCollection or elsewhere in Joyn. It can be removed for gas savings.
The onlyUnInitialized modifier is never used either, but onlyUnInitialized should be used on the initialize function, which is a separate issue
tokenExists is declared here but never used https://github.com/code-423n4/2022-03-joyn/blob/main/core-contracts/contracts/CoreCollection.sol#L59
Manual analysis
Remove unused code
Solidity does not recognize null as a value, so uint variables are initialized to zero. Setting a uint variable to zero is redundant and can waste gas.
https://github.com/code-423n4/2022-03-joyn/blob/main/splits/contracts/Splitter.sol#L49
uint256 amount = 0;
Manual analysis
Remove the redundant zero initialization
uint256 amount;
The comparison operators >= and <= use more gas than >, <, or ==. Replacing the >= and ≤ operators with a comparison operator that has an opcode in the EVM saves gas
This code is in verifyProof from Splitter is the same as an if/else statement https://github.com/code-423n4/2022-03-joyn/blob/main/splits/contracts/Splitter.sol#L277-L287
if (computedHash <= proofElement) { // Hash(current computed hash + current element of the proof) computedHash = keccak256( abi.encodePacked(computedHash, proofElement) ); } else { // Hash(current element of the proof + current computed hash) computedHash = keccak256( abi.encodePacked(proofElement, computedHash) ); }
A simple comparison can be used for gas savings by reversing the logic
if (computedHash > proofElement) { // Hash(current element of the proof + current computed hash) computedHash = keccak256( abi.encodePacked(proofElement, computedHash) ); } else { // Hash(current computed hash + current element of the proof) computedHash = keccak256( abi.encodePacked(computedHash, proofElement) ); }
Manual analysis
Replace the comparison operator and reverse the logic to save gas using the suggestions above
Declaring a function as external instead of public saves gas
Some functions can be declared external instead of public
Slither
Change function from public to external to save gas
The depositedInWindow variable is declared but never used, wasting gas without a clear purpose
depositedInWindow is declared here but never used https://github.com/code-423n4/2022-03-joyn/blob/main/splits/contracts/SplitStorage.sol#L15
Manual analysis
Remove unused variable