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: 18/38
Findings: 2
Award: $431.30
๐ Selected for report: 1
๐ Solo Findings: 0
104.9942 USDC - $104.99
receive()
function will lock Ether in contractIf the intention is for the Ether to be used, the function should emit
or call another function, otherwise it should revert
receive() external payable {}
receive() external payable {}
payable
with the fallback()
function will lock Ether in the contractThe Ether is never used by any of the proxied contracts - only WETH is
fallback() external payable {
fallback() external payable {
address(0x0)
when assigning values to address
state variables_implement = _imp;
collection = _collection;
splitFactory = _splitFactory;
royaltyVault = _royaltyVault;
splitAsset = _splitAsset;
royaltyAsset = _splitAsset;
splitAsset = _splitAsset;
royaltyAsset = _splitAsset;
splitterProxy = splitProxy;
revert()
strings and variable namesThe contract is not specific to either ETH or WETH. This appears to be left over from forking from mirror-xyz/splits
require(didSucceed, "Failed to transfer ETH");
uint256 wethBalance;
payableToken
does not have enough decimals and amounts are smallThere are tokens such as ERC884 tokens, which are ERC20-compatible and are required to have zero decimals. The fix would be to require a minimum number of decimals.
scaledAmount = (amount * scaledPercent) / (10000);
public
functions not called by the contract should be declared external
insteadContracts are allowed to override their parents' functions and change the visibility from external
to public
.
function baseURI() public view returns (string memory) {
function incrementWindow(uint256 royaltyAmount) public returns (bool) {
constant
s should be defined rather than using magic numbersuint256 platformShare = (balanceOfVault * platformFee) / 10000;
platformFee = 500; // 5%
platformFeeRecipient = 0x70388C130222eae55a0527a2367486bF5D12d6e7;
scaledAmount = (amount * scaledPercent) / (10000);
return (amount * percent) / 100;
(bool success, ) = to.call{value: value, gas: 30000}("");
pragma solidity ^0.8.4;
pragma solidity ^0.8.0;
Use a solidity version of at least 0.8.12 to get string.concat()
to be used instead of abi.encodePacked(<str>,<str>)
pragma solidity ^0.8.4;
Use a solidity version of at least 0.8.4 to get bytes.concat()
instead of abi.encodePacked(<bytes>,<bytes>)
Use a solidity version of at least 0.8.12 to get string.concat()
instead of abi.encodePacked(<str>,<str>)
pragma solidity ^0.8.0;
pragma solidity ^0.8.0;
pragma solidity ^0.8.0;
const
/immutable
variablesIf the variable needs to be different based on which class it comes from, a view
/pure
function should be used instead (e.g. like this).
string public HASHED_PROOF = "";
pragma solidity ^0.8.0;
pragma solidity ^0.8.0;
pragma solidity ^0.8.0;
pragma solidity ^0.8.4;
pragma solidity ^0.8.4;
pragma solidity ^0.8.4;
pragma solidity ^0.8.4;
pragma solidity ^0.8.4;
* @dev All revenue (Primary sales + royalties from secondardy sales)
secondardy
* @notice Instanciates/Deploys a collection
Instanciates
/**** Mmutable storage ****/
Mmutable
* @param isClaim Whether the user want claim a token that has been airdropped to him or want to purchase the token
user want claim
-> user wants to claim
* @param merkleProof Proof */ function canClaim( address who, uint256 claimableAmount, uint256 claimedAmount, bytes32[] calldata merkleProof ) public view returns (bool) {
Missing: @return
* @param _to Token recipient */ function mint(address _to) private returns (uint256 tokenId) {
Missing: @return
* @param _collection Collection that needs to be deployed */ function addCollection( string memory _projectId, Collection memory _collection ) external onlyProjectOwner(_projectId) returns (address) {
Missing: @return
* @param _collection Collection that needs to be deployed */ function _createCollection(Collection memory _collection) private onlyAvailableCollection(_collection.id) returns (address)
Missing: @return
* @param _splitId The split identifier. */ function createSplit( bytes32 _merkleRoot, address _splitAsset, address _collectionContract, string memory _splitId ) external onlyAvailableSplit(_splitId) returns (address splitProxy) {
Missing: @return
* @param _splitId The split identifier. */ function createSplit( bytes32 _merkleRoot, address _splitAsset, string memory _splitId ) external onlyAvailableSplit(_splitId) returns (address splitProxy) {
Missing: @return
* @param value {uint256} Amount to transfer. */ function transferSplitAsset(address to, uint256 value) private returns (bool didSucceed)
Missing: @return
indexed
fieldsEach event
should use three indexed
fields if there are three or more fields
event NewPayment( address from, address to, uint256 amount, bool royaltyVaultPayment );
event ClaimInitialized(bytes32 root);
event NewCollectionMeta(string name, string symbol);
event NewClaim(address claimedBy, address to, uint256 tokenId);
event StartingIndexSet(uint256 index);
event RoyaltyVaultInitialized(address royaltyVault);
event NewHashedProof(string proof);
event NewWithdrawal(address to, uint256 amount);
event NewProject(string id, address creator);
event NewCollection( string collectionId, address collection, string projectId );
event RoyaltySentToSplitter(address indexed splitter, uint256 amount);
event FeeSentToPlatform( address indexed platformFeeRecipient, uint256 amount );
event NewRoyaltyVaultPlatformFee(uint256 platformFee);
event NewRoyaltyVaultPlatformFeeRecipient(address recipient);
event SplitCreated(address indexed splitter, string splitId);
event VaultCreated( address indexed vault, address indexed splitter, uint256 platformFee, address platformFeeRecipient );
event TransferETH( // The account to which the transfer was attempted. address account, // The amount for transfer that was attempted. uint256 amount, // Whether or not the transfer succeeded. bool success );
event WindowIncremented(uint256 currentWindow, uint256 fundsAvailable);
Reentrancy in ProxyVault.constructor() (contracts/ProxyVault.sol#16-22): External calls: - royaltyVault = IVaultFactory(msg.sender).royaltyVault() (contracts/ProxyVault.sol#17) - splitterProxy = IVaultFactory(msg.sender).splitterProxy() (contracts/ProxyVault.sol#18) State variables written after the call(s): - splitterProxy = IVaultFactory(msg.sender).splitterProxy() (contracts/ProxyVault.sol#18) Reentrancy in ProxyVault.constructor() (contracts/ProxyVault.sol#16-22): External calls: - royaltyVault = IVaultFactory(msg.sender).royaltyVault() (contracts/ProxyVault.sol#17) - splitterProxy = IVaultFactory(msg.sender).splitterProxy() (contracts/ProxyVault.sol#18) - royaltyAsset = IVaultFactory(msg.sender).royaltyAsset() (contracts/ProxyVault.sol#19) State variables written after the call(s): - royaltyAsset = IVaultFactory(msg.sender).royaltyAsset() (contracts/ProxyVault.sol#19) Reentrancy in ProxyVault.constructor() (contracts/ProxyVault.sol#16-22): External calls: - royaltyVault = IVaultFactory(msg.sender).royaltyVault() (contracts/ProxyVault.sol#17) - splitterProxy = IVaultFactory(msg.sender).splitterProxy() (contracts/ProxyVault.sol#18) - royaltyAsset = IVaultFactory(msg.sender).royaltyAsset() (contracts/ProxyVault.sol#19) - platformFee = IVaultFactory(msg.sender).platformFee() (contracts/ProxyVault.sol#20) State variables written after the call(s): - platformFee = IVaultFactory(msg.sender).platformFee() (contracts/ProxyVault.sol#20) Reentrancy in ProxyVault.constructor() (contracts/ProxyVault.sol#16-22): External calls: - royaltyVault = IVaultFactory(msg.sender).royaltyVault() (contracts/ProxyVault.sol#17) - splitterProxy = IVaultFactory(msg.sender).splitterProxy() (contracts/ProxyVault.sol#18) - royaltyAsset = IVaultFactory(msg.sender).royaltyAsset() (contracts/ProxyVault.sol#19) - platformFee = IVaultFactory(msg.sender).platformFee() (contracts/ProxyVault.sol#20) - platformFeeRecipient = IVaultFactory(msg.sender).platformFeeRecipient() (contracts/ProxyVault.sol#21) State variables written after the call(s): - platformFeeRecipient = IVaultFactory(msg.sender).platformFeeRecipient() (contracts/ProxyVault.sol#21) Reentrancy in RoyaltyVaultFactory.createVault(address,address) (contracts/RoyaltyVaultFactory.sol#37-50): External calls: - vault = address(new ProxyVault()) (contracts/RoyaltyVaultFactory.sol#44-46) State variables written after the call(s): - delete royaltyAsset (contracts/RoyaltyVaultFactory.sol#49) - delete splitterProxy (contracts/RoyaltyVaultFactory.sol#48)
Reentrancy in RoyaltyVault.sendToSplitter() (contracts/RoyaltyVault.sol#31-61): External calls: - require(bool,string)(IERC20(royaltyAsset).transfer(splitterProxy,splitterShare) == true,Failed to transfer royalty Asset to splitter) (contracts/RoyaltyVault.sol#43-46) - require(bool,string)(ISplitter(splitterProxy).incrementWindow(splitterShare) == true,Failed to increment splitter window) (contracts/RoyaltyVault.sol#47-50) - require(bool,string)(IERC20(royaltyAsset).transfer(platformFeeRecipient,platformShare) == true,Failed to transfer royalty Asset to platform fee recipient) (contracts/RoyaltyVault.sol#51-57) Event emitted after the call(s): - FeeSentToPlatform(platformFeeRecipient,platformShare) (contracts/RoyaltyVault.sol#60) - RoyaltySentToSplitter(splitterProxy,splitterShare) (contracts/RoyaltyVault.sol#59)
#0 - sofianeOuafir
2022-04-15T16:26:55Z
high quality report
326.3062 USDC - $326.31
require()
/revert()
strings longer than 32 bytes cost extra gasrequire( !royaltyVaultInitialized(), 'CoreCollection: Royalty Vault already initialized' );
require( royaltyVaultInitialized(), 'CoreCollection: Royalty Vault not initialized' );
require(!claimableSet(), 'ERC721Claimable: Claimable is already set');
require(!initialized, "CoreCollection: Already initialized");
require( _maxSupply > 0, "CoreCollection: Max supply should be greater than 0" );
require(amount > 0, "CoreCollection: Amount should be greater than 0");
require( msg.sender == splitFactory || msg.sender == owner(), "CoreCollection: Only Split Factory or owner can initialize vault." );
require( bytes(HASHED_PROOF).length == 0, "CoreCollection: Hashed Proof is set" );
require( startingIndex == 0, "CoreCollection: Starting index is already set" );
require( projects[_projectId].creator == address(0), 'CoreFactory: Unavailable project id' );
require( projects[_projectId].creator == msg.sender, 'CoreFactory: Not an owner of the project' );
require( collections[_collectionId] == address(0), 'CoreFactory: Unavailable collection id' );
require( _collections.length > 0, 'CoreFactory: should have more at least one collection' );
require( balanceOfVault > 0, "Vault does not have enough royalty Asset to send" );
require( IERC20(royaltyAsset).transfer(splitterProxy, splitterShare) == true, "Failed to transfer royalty Asset to splitter" );
require( ISplitter(splitterProxy).incrementWindow(splitterShare) == true, "Failed to increment splitter window" );
require( IERC20(royaltyAsset).transfer( platformFeeRecipient, platformShare ) == true, "Failed to transfer royalty Asset to platform fee recipient" );
require( splits[_splitId] == address(0), 'SplitFactory : Split ID already in use' );
require( ICoreCollection(_collectionContract).owner() == msg.sender, 'Transaction sender is not collection owner' );
require( !isClaimed(msg.sender, window), "NFT has already claimed the given window" );
Use a solidity version of at least 0.8.10 to have external calls skip contract existence checks if the external call has a return value
pragma solidity ^0.8.4;
pragma solidity ^0.8.4;
pragma solidity ^0.8.4;
pragma solidity ^0.8.4;
pragma solidity ^0.8.4;
Use a solidity version of at least 0.8.2 to get compiler automatic inlining
Use a solidity version of at least 0.8.3 to get better struct packing and cheaper multiple storage reads
Use a solidity version of at least 0.8.4 to get custom errors, which are cheaper at deployment than revert()/require()
strings
Use a solidity version of at least 0.8.10 to have external calls skip contract existence checks if the external call has a return value
pragma solidity ^0.8.0;
pragma solidity ^0.8.0;
pragma solidity ^0.8.0;
pragma solidity ^0.8.0;
pragma solidity ^0.8.0;
bool
s for storage incurs overhead// Booleans are more expensive than uint256 or any type that takes up a full // word because each write operation emits an extra SLOAD to first read the // slot's contents, replace the bits taken up by the boolean, and then write // back. This is the compiler's defense against contract upgrades and // pointer aliasing, and it cannot be disabled.
bool public isForSale;
bool public initialized;
> 0
costs more gas than != 0
when used on a uint
in a require()
statementrequire( _maxSupply > 0, "CoreCollection: Max supply should be greater than 0" );
require(amount > 0, "CoreCollection: Amount should be greater than 0");
require( balanceOfVault > 0, "Vault does not have enough royalty Asset to send" );
require(royaltyAmount > 0, "No additional funds for window");
<array>.length
should not be looked up in every loop of a for
-loopEven memory arrays incur the overhead of bit tests and bit shifts to calculate the array length
for (uint256 i; i < _collections.length; i++) {
for (uint256 i = 0; i < proof.length; i++) {
for (uint256 i = 0; i < _amount; i++) {
uint256 amount = 0;
for (uint256 i = 0; i < currentWindow; i++) {
for (uint256 i = 0; i < proof.length; i++) {
The instances below point to the second access of a state variable within a function. Less obvious optimizations include having local storage variables of mappings within state variable mappings or mappings within state variable structs, having local storage variables of structs within mappings, or having local caches of state variable contracts/addresses.
emit StartingIndexSet(startingIndex);
tokenId = ((startingIndex + totalSupply()) % maxSupply) + 1;
delete merkleRoot;
delete splitterProxy;
calldata
instead of memory
for read-only arguments in external
functions saves gasstring memory _collectionName,
string memory _collectionSymbol,
string memory _collectionURI,
string memory _collectionName,
string memory _collectionSymbol
string memory _projectId,
Collection[] memory _collections
string memory _projectId,
Collection memory _collection
function getProject(string memory _projectId)
string memory _splitId
string memory _splitId
++i
/i++
should be unchecked{++i}
/unchecked{++i}
when it is not possible for them to overflow, as is the case when used in for
- and while
-loopsfor (uint256 i = 0; i < _amount; i++) {
for (uint256 i; i < _collections.length; i++) {
for (uint256 i = 0; i < currentWindow; i++) {
for (uint256 i = 0; i < proof.length; i++) {
++i
costs less gas than ++i
, especially when it's used in for
-loops (--i
/i--
too)for (uint256 i = 0; i < _amount; i++) {
for (uint256 i; i < _collections.length; i++) {
for (uint256 i = 0; i < currentWindow; i++) {
for (uint256 i = 0; i < proof.length; i++) {
uints
/ints
smaller than 32 bytes (256 bits) incurs overheadWhen using elements that are smaller than 32 bytes, your contractโs gas usage may be higher. This is because the EVM operates on 32 bytes at a time. Therefore, if the element is smaller than that, the EVM must use more operations in order to reduce the size of the element from 32 bytes to the desired size.
https://docs.soliditylang.org/en/v0.8.11/internals/layout_in_storage.html Use a larger size then downcast where needed
function amountFromPercent(uint256 amount, uint32 percent)
private
rather than public
for constants, saves gasIf needed, the value can be read from the verified contract source code
uint256 public constant PERCENTAGE_SCALE = 10e5;
bytes4 public constant IID_IROYALTY = type(IRoyaltyVault).interfaceId;
if (<x> == true)
=> if (<x>)
, if (<x> == false)
=> if (!<x>)
IERC20(royaltyAsset).transfer(splitterProxy, splitterShare) == true,
ISplitter(splitterProxy).incrementWindow(splitterShare) == true,
IERC20(royaltyAsset).transfer( platformFeeRecipient, platformShare ) == true,
uint256 public constant PERCENTAGE_SCALE = 10e5;
immutable
address internal royaltyVault;
require()
or revert()
statements that check input arguments should be at the top of the functionrequire(royaltyAmount > 0, "No additional funds for window");
private
functions not called by the contract should be removed to save deployment gasfunction amountFromPercent(uint256 amount, uint32 percent) private pure returns (uint256)
function attemptETHTransfer(address to, uint256 value) private returns (bool)
public
functions not called by the contract should be declared external
insteadContracts are allowed to override their parents' functions and change the visibility from external
to public
.
function supportsInterface(bytes4 interfaceId) public view virtual override(IRoyaltyVault, ERC165) returns (bool)
function getSplitter() public view override returns (address) {
revert()
/require()
strings to save deployment gaspayable
If a function modifier such as onlyOwner
is used, the function will revert if a normal user tries to pay the function. Marking the function as payable
will lower the gas cost for legitimate callers because the compiler will not include checks for whether a payment was provided.
function initialize( string memory _collectionName, string memory _collectionSymbol, string memory _collectionURI, uint256 _maxSupply, uint256 _mintFee, address _payableToken, bool _isForSale, address _splitFactory ) external onlyOwner onlyValidSupply(_maxSupply) {
function initialize( string memory _collectionName, string memory _collectionSymbol, string memory _collectionURI, uint256 _maxSupply, uint256 _mintFee, address _payableToken, bool _isForSale, address _splitFactory ) external onlyOwner onlyValidSupply(_maxSupply) {
function initializeClaims(bytes32 _root) external onlyOwner onlyNotClaimableSet onlyValidRoot(_root)
function initializeClaims(bytes32 _root) external onlyOwner onlyNotClaimableSet onlyValidRoot(_root)
function initializeClaims(bytes32 _root) external onlyOwner onlyNotClaimableSet onlyValidRoot(_root)
function setCollectionMeta( string memory _collectionName, string memory _collectionSymbol ) external onlyOwner {
function mintToken( address to, bool isClaim, uint256 claimableAmount, uint256 amount, bytes32[] calldata merkleProof ) external onlyInitialized {
function withdraw() external onlyOwner {
function setRoyaltyVault(address _royaltyVault) external onlyVaultUninitialized
function setHashedProof(string calldata _proof) external onlyOwner {
function createProject( string memory _projectId, Collection[] memory _collections ) external onlyAvailableProject(_projectId) {
function addCollection( string memory _projectId, Collection memory _collection ) external onlyProjectOwner(_projectId) returns (address) {
function _createCollection(Collection memory _collection) private onlyAvailableCollection(_collection.id) returns (address)
function setPlatformFee(uint256 _platformFee) external override onlyOwner {
function setPlatformFeeRecipient(address _platformFeeRecipient) external override onlyOwner
function createSplit( bytes32 _merkleRoot, address _splitAsset, address _collectionContract, string memory _splitId ) external onlyAvailableSplit(_splitId) returns (address splitProxy) {
function createSplit( bytes32 _merkleRoot, address _splitAsset, string memory _splitId ) external onlyAvailableSplit(_splitId) returns (address splitProxy) {
function setPlatformFee(address _vault, uint256 _platformFee) external onlyOwner
function setPlatformFeeRecipient( address _vault, address _platformFeeRecipient ) external onlyOwner {
#0 - sofianeOuafir
2022-04-15T16:18:26Z
high quality report