Fractional v2 contest - IllIllI's results

A collective ownership platform for NFTs on Ethereum.

General Information

Platform: Code4rena

Start Date: 07/07/2022

Pot Size: $75,000 USDC

Total HM: 32

Participants: 141

Period: 7 days

Judge: HardlyDifficult

Total Solo HM: 4

Id: 144

League: ETH

Fractional

Findings Distribution

Researcher Performance

Rank: 25/141

Findings: 3

Award: $585.20

🌟 Selected for report: 1

šŸš€ Solo Findings: 0

Awards

1.3977 USDC - $1.40

Labels

bug
2 (Med Risk)
old-submission-method

External Links

Lines of code

https://github.com/code-423n4/2022-07-fractional/blob/e2c5a962a94106f9495eb96769d7f60f7d5b14c9/src/modules/Migration.sol#L172 https://github.com/code-423n4/2022-07-fractional/blob/e2c5a962a94106f9495eb96769d7f60f7d5b14c9/src/modules/Migration.sol#L325

Vulnerability details

Impact

The use of payable.transfer() is heavily frowned upon because it can lead to the locking of funds. The transfer() call requires that the recipient has a payable callback, only provides 2300 gas for its operation. This means the following cases can cause the transfer to fail:

  • The contract does not have a payable callback
  • The contract's payable callback spends more than 2300 gas (which is only enough to emit something)
  • The contract is called through a proxy which itself uses up the 2300 gas

If a user falls into one of the above categories, they'll be unable to receive funds from the vault in a migration wrapper. Inaccessible funds means loss of funds, which is Medium severity.

Proof of Concept

Both leave():

File: src/modules/Migration.sol   #1

159           uint256 ethAmount = userProposalEth[_proposalId][msg.sender];
160           proposal.totalEth -= ethAmount;
161           userProposalEth[_proposalId][msg.sender] = 0;
162   
163           // Withdraws fractions from contract back to caller
164           IFERC1155(token).safeTransferFrom(
165               address(this),
166               msg.sender,
167               id,
168               amount,
169               ""
170           );
171           // Withdraws ether from contract back to caller
172           payable(msg.sender).transfer(ethAmount);

https://github.com/code-423n4/2022-07-fractional/blob/e2c5a962a94106f9495eb96769d7f60f7d5b14c9/src/modules/Migration.sol#L159-L172

and withdrawContribution() use payable.transfer()

File: src/modules/Migration.sol   #2

320           // Temporarily store user's eth for the transfer
321           uint256 userEth = userProposalEth[_proposalId][msg.sender];
322           // Udpates ether balance of caller
323           userProposalEth[_proposalId][msg.sender] = 0;
324           // Withdraws ether from contract back to caller
325           payable(msg.sender).transfer(userEth);

https://github.com/code-423n4/2022-07-fractional/blob/e2c5a962a94106f9495eb96769d7f60f7d5b14c9/src/modules/Migration.sol#L320-L325

While they both use msg.sender, the funds are tied to the address that deposited them (lines 159 and 321), and there is no mechanism to change the owner of the funds to an alternate address.

Tools Used

Code inspection

Use address.call{value:x}() instead

#0 - stevennevins

2022-07-19T21:43:55Z

Duplicate of #325

#1 - HardlyDifficult

2022-07-28T15:43:30Z

After an unsuccessful migration, a multisig user (or other contract) may find their funds unrecoverable. Since a contract is able to enter a migration successfully and there is no way to specify an alternative send to address or migrate their escrowed funds to another account -- assets can be lost; as the warden points out here. I agree with Medium risk for this.

Summary

Low Risk Issues

IssueInstances
[L‑01]Input array lengths may differ3
[L‑02]Code does not properly bubble up reverts where the size is less than 681
[L‑03]Return values of transfer()/transferFrom() not checked2
[L‑04]Unused/empty receive()/fallback() function3
[L‑05]Missing checks for address(0x0) when assigning values to address state variables10
[L‑06]Open TODOs1

Total: 20 instances over 6 issues

Non-critical Issues

IssueInstances
[N‑01]Inconsistent hash definitions1
[N‑02]Re-declaration of function already defined in parent2
[N‑03]Misleading comments1
[N‑04]Document specific commit used when forking code1
[N‑05]Duplicated code1
[N‑06]Consider addings checks for signature malleability1
[N‑07]Code does not follow the checks-effects-interactions pattern1
[N‑08]Unnecessary return value from function1
[N‑09]Invalid/extraneous/optional function definitions in interface10
[N‑10]Contract implements interface without extending the interface1
[N‑11]require()/revert() statements should have descriptive reason strings1
[N‑12]public functions not called by the contract should be declared external instead1
[N‑13]Non-assembly method available4
[N‑14]constants should be defined rather than using magic numbers21
[N‑15]Redundant cast4
[N‑16]Missing event and or timelock for critical parameter change2
[N‑17]Constant redefined elsewhere1
[N‑18]Typos8
[N‑19]File is missing NatSpec3
[N‑20]NatSpec is incomplete5
[N‑21]Event is missing indexed fields21

Total: 91 instances over 21 issues

Low Risk Issues

[L‑01] Input array lengths may differ

If the caller makes a copy-paste error, the lengths may be mismatchd and an operation believed to have been completed may not in fact have been completed

There are 3 instances of this issue:

File: src/modules/protoforms/BaseVault.sol

61           address[] calldata _tokens,
62:          uint256[] calldata _amounts

80           address[] calldata _tokens,
81:          uint256[] calldata _ids

101          address[] calldata _tokens,
102          uint256[] calldata _ids,
103:         uint256[] calldata _amounts,

https://github.com/code-423n4/2022-07-fractional/blob/8f2697ae727c60c93ea47276f8fa128369abfe51/src/modules/protoforms/BaseVault.sol#L61-L62

[L‑02] Code does not properly bubble up reverts where the size is less than 68

The check should be for a length of 68, not zero. A developer can return values less than 68, causing the current code to try and fail to convert it to a revert-with-reason

There is 1 instance of this issue:

File: src/utils/Multicall.sol

23                   if (result.length == 0) revert();
24                   // If there is return data and the call wasn't successful, the call reverted with a reason or a custom error.
25:                  _revertedWithReason(result);

https://github.com/code-423n4/2022-07-fractional/blob/8f2697ae727c60c93ea47276f8fa128369abfe51/src/utils/Multicall.sol#L23-L25

[L‑03] Return values of transfer()/transferFrom() not checked

Not all IERC20 implementations revert() when there's a failure in transfer()/transferFrom(). The function signature has a boolean return value and they indicate errors that way instead. By not checking the return value, operations that should have marked as failed, may potentially go through without actually making a payment

The cases where these are currently used can be retried, so I've marked this as low. However, if any other developer writes code using them, it's possible that it may not be retryable, leading to it being a Medium bug.

There are 2 instances of this issue:

File: src/modules/protoforms/BaseVault.sol

65:               IERC20(_tokens[i]).transferFrom(_from, _to, _amounts[i]);

https://github.com/code-423n4/2022-07-fractional/blob/e2c5a962a94106f9495eb96769d7f60f7d5b14c9/src/modules/protoforms/BaseVault.sol#L65

File: src/references/TransferReference.sol

22:           IERC20(_token).transfer(_to, _value);

https://github.com/code-423n4/2022-07-fractional/blob/e2c5a962a94106f9495eb96769d7f60f7d5b14c9/src/references/TransferReference.sol#L22

[L‑04] Unused/empty receive()/fallback() function

If the intention is for the Ether to be used, the function should call another function, otherwise it should revert (e.g. require(msg.sender == address(weth)))

There are 3 instances of this issue:

File: src/modules/Buyout.sol

53:       receive() external payable {}

https://github.com/code-423n4/2022-07-fractional/blob/e2c5a962a94106f9495eb96769d7f60f7d5b14c9/src/modules/Buyout.sol#L53

File: src/modules/Migration.sol

63:       receive() external payable {}

https://github.com/code-423n4/2022-07-fractional/blob/e2c5a962a94106f9495eb96769d7f60f7d5b14c9/src/modules/Migration.sol#L63

File: src/Vault.sol

32:       receive() external payable {}

https://github.com/code-423n4/2022-07-fractional/blob/e2c5a962a94106f9495eb96769d7f60f7d5b14c9/src/Vault.sol#L32

[L‑05] Missing checks for address(0x0) when assigning values to address state variables

There are 10 instances of this issue:

File: src/modules/Buyout.sol

47:           registry = _registry;

48:           supply = _supply;

49:           transfer = _transfer;

https://github.com/code-423n4/2022-07-fractional/blob/e2c5a962a94106f9495eb96769d7f60f7d5b14c9/src/modules/Buyout.sol#L47

File: src/modules/Migration.sol

59:           registry = _registry;

https://github.com/code-423n4/2022-07-fractional/blob/e2c5a962a94106f9495eb96769d7f60f7d5b14c9/src/modules/Migration.sol#L59

File: src/modules/Minter.sol

18:           supply = _supply;

https://github.com/code-423n4/2022-07-fractional/blob/e2c5a962a94106f9495eb96769d7f60f7d5b14c9/src/modules/Minter.sol#L18

File: src/modules/protoforms/BaseVault.sol

25:           registry = _registry;

https://github.com/code-423n4/2022-07-fractional/blob/e2c5a962a94106f9495eb96769d7f60f7d5b14c9/src/modules/protoforms/BaseVault.sol#L25

File: src/references/SupplyReference.sol

16:           registry = _registry;

https://github.com/code-423n4/2022-07-fractional/blob/e2c5a962a94106f9495eb96769d7f60f7d5b14c9/src/references/SupplyReference.sol#L16

File: src/targets/Supply.sol

17:           registry = _registry;

https://github.com/code-423n4/2022-07-fractional/blob/e2c5a962a94106f9495eb96769d7f60f7d5b14c9/src/targets/Supply.sol#L17

File: src/utils/Metadata.sol

17:           token = _token;

https://github.com/code-423n4/2022-07-fractional/blob/e2c5a962a94106f9495eb96769d7f60f7d5b14c9/src/utils/Metadata.sol#L17

File: src/Vault.sol

95:           owner = _newOwner;

https://github.com/code-423n4/2022-07-fractional/blob/e2c5a962a94106f9495eb96769d7f60f7d5b14c9/src/Vault.sol#L95

[L‑06] Open TODOs

Code architecture, incentives, and error handling/reporting questions/issues should be resolved before deployment

There is 1 instance of this issue:

File: src/utils/MerkleBase.sol

24:               // TODO: This can be aesthetically simplified with a switch. Not sure it will

https://github.com/code-423n4/2022-07-fractional/blob/e2c5a962a94106f9495eb96769d7f60f7d5b14c9/src/utils/MerkleBase.sol#L24

Non-critical Issues

[N‑01] Inconsistent hash definitions

The ones in this file call keccak256(), whereas the ones in other files use hex literals with comments. The code should be consistent throughout

There is 1 instance of this issue:

File: src/constants/Permit.sol

10   bytes32 constant PERMIT_TYPEHASH = keccak256(
11       "Permit(address owner,address operator,uint256 tokenId,bool approved,uint256 nonce,uint256 deadline)"
12:  );

https://github.com/code-423n4/2022-07-fractional/blob/8f2697ae727c60c93ea47276f8fa128369abfe51/src/constants/Permit.sol#L10-L12

[N‑02] Re-declaration of function already defined in parent

There are 2 instances of this issue:

File: src/interfaces/IBaseVault.sol

/// @audit same as defined in `IProtoform`
37       function deployVault(
38           uint256 _fractionSupply,
39           address[] memory _modules,
40           address[] calldata _plugins,
41           bytes4[] calldata _selectors,
42           bytes32[] memory _mintProof
43:      ) external returns (address vault);

https://github.com/code-423n4/2022-07-fractional/blob/8f2697ae727c60c93ea47276f8fa128369abfe51/src/interfaces/IBaseVault.sol#L37-L43

File: src/interfaces/IFERC1155.sol

/// @audit same as defined in `IERC1155`
106      function safeTransferFrom(
107          address _from,
108          address _to,
109          uint256 _id,
110          uint256 _amount,
111          bytes memory _data
112:     ) external;

https://github.com/code-423n4/2022-07-fractional/blob/8f2697ae727c60c93ea47276f8fa128369abfe51/src/interfaces/IFERC1155.sol#L106-L112

[N‑03] Misleading comments

The increment cannot overflow due to length being less than uint256 max, not due to human time scales

There is 1 instance of this issue:

File: src/utils/Multicall.sol

30               // cannot realistically overflow on human timescales
31               unchecked {
32                   ++i;
33:              }

https://github.com/code-423n4/2022-07-fractional/blob/8f2697ae727c60c93ea47276f8fa128369abfe51/src/utils/Multicall.sol#L30-L33

[N‑04] Document specific commit used when forking code

I believe the version forked is this one. Including the full URL to it will help auditors to do a better job, and will make future updates easier

There is 1 instance of this issue:

File: src/utils/MerkleBase.sol

5:   /// @author Modified from Murky (https://github.com/dmfxyz/murky/blob/main/src/common/MurkyBase.sol)

https://github.com/code-423n4/2022-07-fractional/blob/8f2697ae727c60c93ea47276f8fa128369abfe51/src/utils/MerkleBase.sol#L5

[N‑05] Duplicated code

The code below is the same as what appears here. Common code should be refactored into a shared function

There is 1 instance of this issue:

File: src/Vault.sol

142      function _revertedWithReason(bytes memory _response) internal pure {
143          assembly {
144              let returndata_size := mload(_response)
145              revert(add(32, _response), returndata_size)
146          }
147:     }

https://github.com/code-423n4/2022-07-fractional/blob/e2c5a962a94106f9495eb96769d7f60f7d5b14c9/src/Vault.sol#L142-L147

[N‑06] Consider addings checks for signature malleability

Use OpenZeppelin's ECDSA contract rather than calling ecrecover() directly

There is 1 instance of this issue:

File: src/FERC1155.sol

126              address signer = ecrecover(digest, _v, _r, _s);
127  
128              if (signer == address(0) || signer != _owner)
129:                 revert InvalidSignature(signer, _owner);

https://github.com/code-423n4/2022-07-fractional/blob/e2c5a962a94106f9495eb96769d7f60f7d5b14c9/src/FERC1155.sol#L126-L129

[N‑07] Code does not follow the checks-effects-interactions pattern

Even through FERC721 doesn't have the opportunity for reentrancy here, the best coding practice is to follow the check-effects-interaction pattern

There is 1 instance of this issue:

File: src/FERC1155.sol

85           _mint(_to, _id, _amount, _data);
86:          totalSupply[_id] += _amount;

https://github.com/code-423n4/2022-07-fractional/blob/e2c5a962a94106f9495eb96769d7f60f7d5b14c9/src/FERC1155.sol#L85-L86

[N‑08] Unnecessary return value from function

The _execute() function reverts if there is a failure, so it's not clear why it returns a success variable

There is 1 instance of this issue:

File: src/Vault.sol

134          // Revert if execution was unsuccessful
135          if (!success) {
136              if (response.length == 0) revert ExecutionReverted();
137              _revertedWithReason(response);
138:         }

https://github.com/code-423n4/2022-07-fractional/blob/e2c5a962a94106f9495eb96769d7f60f7d5b14c9/src/Vault.sol#L134-L138

[N‑09] Invalid/extraneous/optional function definitions in interface

There are 10 instances of this issue:

File: src/interfaces/IERC1155.sol

/// @audit uri(uint256) isn't defined with those arguments in the standard IERC1155 definition
59:       function uri(uint256 _id) external view returns (string memory);

https://github.com/code-423n4/2022-07-fractional/blob/e2c5a962a94106f9495eb96769d7f60f7d5b14c9/src/interfaces/IERC1155.sol#L59

File: src/interfaces/IERC20.sol

/// @audit DOMAIN_SEPARATOR() isn't defined with those arguments in the standard IERC20 definition
13:       function DOMAIN_SEPARATOR() external view returns (bytes32);

/// @audit decimals() isn't defined with those arguments in the standard IERC20 definition
21:       function decimals() external view returns (uint8);

/// @audit name() isn't defined with those arguments in the standard IERC20 definition
23:       function name() external view returns (string memory);

/// @audit nonces(address) isn't defined with those arguments in the standard IERC20 definition
25:       function nonces(address) external view returns (uint256);

/// @audit permit(address,address,uint256,uint256,uint8,bytes32,bytes32) isn't defined with those arguments in the standard IERC20 definition
27        function permit(
28            address _owner,
29            address _spender,
30            uint256 _value,
31            uint256 _deadline,
32            uint8 _v,
33            bytes32 _r,
34:           bytes32 _s

/// @audit symbol() isn't defined with those arguments in the standard IERC20 definition
37:       function symbol() external view returns (string memory);

https://github.com/code-423n4/2022-07-fractional/blob/e2c5a962a94106f9495eb96769d7f60f7d5b14c9/src/interfaces/IERC20.sol#L13

File: src/interfaces/IERC721.sol

/// @audit name() isn't defined with those arguments in the standard IERC721 definition
30:       function name() external view returns (string memory);

/// @audit symbol() isn't defined with those arguments in the standard IERC721 definition
54:       function symbol() external view returns (string memory);

/// @audit tokenURI(uint256) isn't defined with those arguments in the standard IERC721 definition
56:       function tokenURI(uint256 _id) external view returns (string memory);

https://github.com/code-423n4/2022-07-fractional/blob/e2c5a962a94106f9495eb96769d7f60f7d5b14c9/src/interfaces/IERC721.sol#L30

[N‑10] Contract implements interface without extending the interface

Not extending the interface may lead to the wrong function signature being used, leading to unexpected behavior. If the interface is in fact being implemented, use the override keyword to indicate that fact

There is 1 instance of this issue:

File: src/utils/Metadata.sol

/// @audit IERC1155MetadataURI.uri()
9:    contract Metadata {

https://github.com/code-423n4/2022-07-fractional/blob/e2c5a962a94106f9495eb96769d7f60f7d5b14c9/src/utils/Metadata.sol#L9

[N‑11] require()/revert() statements should have descriptive reason strings

There is 1 instance of this issue:

File: src/utils/Multicall.sol

23:                   if (result.length == 0) revert();

https://github.com/code-423n4/2022-07-fractional/blob/e2c5a962a94106f9495eb96769d7f60f7d5b14c9/src/utils/Multicall.sol#L23

[N‑12] public functions not called by the contract should be declared external instead

Contracts are allowed to override their parents' functions and change the visibility from external to public.

There is 1 instance of this issue:

File: src/utils/Metadata.sol

36:       function uri(uint256 _id) public view returns (string memory) {

https://github.com/code-423n4/2022-07-fractional/blob/e2c5a962a94106f9495eb96769d7f60f7d5b14c9/src/utils/Metadata.sol#L36

[N‑13] Non-assembly method available

assembly{ id := chainid() } => uint256 id = block.chainid, assembly { size := extcodesize() } => uint256 size = address().code.length There are some automated tools that will flag a project as having higher complexity if there is inline-assembly, so it's best to avoid using it where it's not necessary

There are 4 instances of this issue:

File: src/targets/Transfer.sol

231:              if iszero(extcodesize(_token)) {

351:              if iszero(extcodesize(_token)) {

494:              if iszero(extcodesize(token)) {

https://github.com/code-423n4/2022-07-fractional/blob/e2c5a962a94106f9495eb96769d7f60f7d5b14c9/src/targets/Transfer.sol#L231

File: src/Vault.sol

122:              codeSize := extcodesize(_target)

https://github.com/code-423n4/2022-07-fractional/blob/e2c5a962a94106f9495eb96769d7f60f7d5b14c9/src/Vault.sol#L122

[N‑14] constants should be defined rather than using magic numbers

Even assembly can benefit from using readable constants instead of hex/numeric literals

There are 21 instances of this issue:

File: src/FERC1155.sol

/// @audit 100
247:          royaltyAmount = (_salePrice * royaltyPercent[_id]) / 100;

/// @audit 20
315:          return _getArgAddress(20);

https://github.com/code-423n4/2022-07-fractional/blob/e2c5a962a94106f9495eb96769d7f60f7d5b14c9/src/FERC1155.sol#L247

File: src/modules/Buyout.sol

/// @audit 100
86:           uint256 buyoutPrice = (msg.value * 100) /

/// @audit 100
/// @audit 100
87:               (100 - ((depositAmount * 100) / totalSupply));

/// @audit 1000
209:              (tokenBalance * 1000) /

/// @audit 500
211:              500

/// @audit 5
451:          nodes = new bytes32[](5);

/// @audit 5
472:          permissions = new Permission[](5);

/// @audit 3
492:          permissions[3] = Permission(

/// @audit 4
498:          permissions[4] = Permission(

https://github.com/code-423n4/2022-07-fractional/blob/e2c5a962a94106f9495eb96769d7f60f7d5b14c9/src/modules/Buyout.sol#L86

File: src/modules/Migration.sol

/// @audit 100
199:              100,

https://github.com/code-423n4/2022-07-fractional/blob/e2c5a962a94106f9495eb96769d7f60f7d5b14c9/src/modules/Migration.sol#L199

File: src/modules/protoforms/BaseVault.sol

/// @audit 6
128:          hashes = new bytes32[](6);

https://github.com/code-423n4/2022-07-fractional/blob/e2c5a962a94106f9495eb96769d7f60f7d5b14c9/src/modules/protoforms/BaseVault.sol#L128

File: src/utils/MerkleBase.sol

/// @audit 0x0
27:                   mstore(0x0, _left)

/// @audit 0x20
28:                   mstore(0x20, _right)

/// @audit 0x0
31:                   mstore(0x0, _right)

/// @audit 0x20
32:                   mstore(0x20, _left)

/// @audit 0x0
/// @audit 0x40
34:               data := keccak256(0x0, 0x40)

/// @audit 0x1
135:              if (length & 0x1 == 1) {

https://github.com/code-423n4/2022-07-fractional/blob/e2c5a962a94106f9495eb96769d7f60f7d5b14c9/src/utils/MerkleBase.sol#L27

File: src/VaultFactory.sol

/// @audit 0xff
47:               abi.encodePacked(bytes1(0xff), address(this), salt, creationHash)

https://github.com/code-423n4/2022-07-fractional/blob/e2c5a962a94106f9495eb96769d7f60f7d5b14c9/src/VaultFactory.sol#L47

[N‑15] Redundant cast

The type of the variable is the same as the type to which the variable is being cast

There are 4 instances of this issue:

File: src/modules/Migration.sol

/// @audit address(buyout)
208:              IFERC1155(token).setApprovalFor(address(buyout), id, true);

https://github.com/code-423n4/2022-07-fractional/blob/e2c5a962a94106f9495eb96769d7f60f7d5b14c9/src/modules/Migration.sol#L208

File: src/VaultFactory.sol

/// @audit address(vault)
87:               address(vault)

https://github.com/code-423n4/2022-07-fractional/blob/e2c5a962a94106f9495eb96769d7f60f7d5b14c9/src/VaultFactory.sol#L87

File: src/VaultRegistry.sol

/// @audit address(fNFT)
56:           vault = _deployVault(_merkleRoot, address(fNFT), _plugins, _selectors);

/// @audit address(fNFT)
73:           vault = _deployVault(_merkleRoot, address(fNFT), _plugins, _selectors);

https://github.com/code-423n4/2022-07-fractional/blob/e2c5a962a94106f9495eb96769d7f60f7d5b14c9/src/VaultRegistry.sol#L56

[N‑16] Missing event and or timelock for critical parameter change

Events help non-contract tools to track changes, and events prevent users from being surprised by changes

There are 2 instances of this issue:

File: src/FERC1155.sol

198       function setContractURI(string calldata _uri) external onlyController {
199           contractURI = _uri;
200:      }

https://github.com/code-423n4/2022-07-fractional/blob/e2c5a962a94106f9495eb96769d7f60f7d5b14c9/src/FERC1155.sol#L198-L200

File: src/Vault.sol

86        function setMerkleRoot(bytes32 _rootHash) external {
87            if (owner != msg.sender) revert NotOwner(owner, msg.sender);
88            merkleRoot = _rootHash;
89:       }

https://github.com/code-423n4/2022-07-fractional/blob/e2c5a962a94106f9495eb96769d7f60f7d5b14c9/src/Vault.sol#L86-L89

[N‑17] Constant redefined elsewhere

Consider defining in only one contract so that values cannot become out of sync when only one location is updated. A cheap way to store constants in a single location is to create an internal constant in a library. If the variable is a local cache of another contract's value, consider making the cache variable internal or private, which will require external users to query the contract with the source of truth, so that callers don't get out of sync.

There is 1 instance of this issue:

File: src/modules/Migration.sol

/// @audit seen in src/modules/Buyout.sol 
43:       uint256 public constant PROPOSAL_PERIOD = 7 days;

https://github.com/code-423n4/2022-07-fractional/blob/e2c5a962a94106f9495eb96769d7f60f7d5b14c9/src/modules/Migration.sol#L43

[N‑18] Typos

There are 8 instances of this issue:

File: src/constants/Transfer.sol

/// @audit ERC1155_safeTransferFrom_data_offset_ptr
25:    *      to some parent value. For example, ERC1155_safeTransferFrom_data_offset_ptr

https://github.com/code-423n4/2022-07-fractional/blob/e2c5a962a94106f9495eb96769d7f60f7d5b14c9/src/constants/Transfer.sol#L25

File: src/interfaces/IMigration.sol

/// @audit succesful
23:       // Address for the new vault to migrate to (if buyout is succesful)

/// @audit propoal
25:       // Boolean status to check if the propoal is active

/// @audit succesfully
29:       // New fraction supply for a given vault that has succesfully migrated

https://github.com/code-423n4/2022-07-fractional/blob/e2c5a962a94106f9495eb96769d7f60f7d5b14c9/src/interfaces/IMigration.sol#L23

File: src/interfaces/ITransfer.sol

/// @audit falsey
6:        /// @dev Emitted when an ERC-20 token transfer returns a falsey value

https://github.com/code-423n4/2022-07-fractional/blob/e2c5a962a94106f9495eb96769d7f60f7d5b14c9/src/interfaces/ITransfer.sol#L6

File: src/modules/Migration.sol

/// @audit Udpates
322:          // Udpates ether balance of caller

https://github.com/code-423n4/2022-07-fractional/blob/e2c5a962a94106f9495eb96769d7f60f7d5b14c9/src/modules/Migration.sol#L322

File: src/targets/Transfer.sol

/// @audit BatchTransfer1155Params_ptr
557:              // at location BatchTransfer1155Params_ptr

https://github.com/code-423n4/2022-07-fractional/blob/e2c5a962a94106f9495eb96769d7f60f7d5b14c9/src/targets/Transfer.sol#L557

File: src/utils/SafeSend.sol

/// @audit attemping
15:       /// @param _to Address attemping to send to

https://github.com/code-423n4/2022-07-fractional/blob/e2c5a962a94106f9495eb96769d7f60f7d5b14c9/src/utils/SafeSend.sol#L15

[N‑19] File is missing NatSpec

There are 3 instances of this issue:

File: src/constants/Memory.sol

https://github.com/code-423n4/2022-07-fractional/blob/e2c5a962a94106f9495eb96769d7f60f7d5b14c9/src/constants/Memory.sol

File: src/constants/Supply.sol

https://github.com/code-423n4/2022-07-fractional/blob/e2c5a962a94106f9495eb96769d7f60f7d5b14c9/src/constants/Supply.sol

File: src/constants/Transfer.sol

https://github.com/code-423n4/2022-07-fractional/blob/e2c5a962a94106f9495eb96769d7f60f7d5b14c9/src/constants/Transfer.sol

[N‑20] NatSpec is incomplete

There are 5 instances of this issue:

File: src/FERC1155.sol

/// @audit Missing: '@return'
239       /// @param _id Token ID royalties are being updated for
240       /// @param _salePrice Sale price to calculate the royalty for
241       function royaltyInfo(uint256 _id, uint256 _salePrice)
242           external
243           view
244:          returns (address receiver, uint256 royaltyAmount)

/// @audit Missing: '@return'
289       /// @notice Getter for URI of a token type
290       /// @param _id ID of the token type
291       function uri(uint256 _id)
292           public
293           view
294           override(ERC1155, IFERC1155)
295:          returns (string memory)

/// @audit Missing: '@return'
322       /// @param _approved Approval status for the token type
323       /// @param _deadline Expiration of the signature
324       function _computePermitStructHash(
325           address _owner,
326           address _operator,
327           uint256 _id,
328           bool _approved,
329           uint256 _deadline
330:      ) internal returns (bytes32) {

/// @audit Missing: '@return'
348       /// @param _approved Approval status for the token type
349       /// @param _deadline Expiration of the signature
350       function _computePermitAllStructHash(
351           address _owner,
352           address _operator,
353           bool _approved,
354           uint256 _deadline
355:      ) internal returns (bytes32) {

https://github.com/code-423n4/2022-07-fractional/blob/e2c5a962a94106f9495eb96769d7f60f7d5b14c9/src/FERC1155.sol#L239-L244

File: src/modules/protoforms/BaseVault.sol

/// @audit Missing: '@return'
32        /// @param _selectors List of function selectors
33        /// @param _mintProof List of proofs to execute a mint function
34        function deployVault(
35            uint256 _fractionSupply,
36            address[] calldata _modules,
37            address[] calldata _plugins,
38            bytes4[] calldata _selectors,
39            bytes32[] calldata _mintProof
40:       ) external returns (address vault) {

https://github.com/code-423n4/2022-07-fractional/blob/e2c5a962a94106f9495eb96769d7f60f7d5b14c9/src/modules/protoforms/BaseVault.sol#L32-L40

[N‑21] Event is missing indexed fields

Index event fields make the field more quickly accessible to off-chain tools that parse events. However, note that each index field costs extra gas during emission, so it's not necessarily best to index the maximum allowed per event (threefields). Each event should use three indexed fields if there are three or more fields, and gas usage is not particularly of concern for the events in question

There are 21 instances of this issue:

File: src/interfaces/IBaseVault.sol

12:       event ActiveModules(address indexed _vault, address[] _modules);

https://github.com/code-423n4/2022-07-fractional/blob/e2c5a962a94106f9495eb96769d7f60f7d5b14c9/src/interfaces/IBaseVault.sol#L12

File: src/interfaces/IBuyout.sol

55        event Start(
56            address indexed _vault,
57            address indexed _proposer,
58            uint256 _startTime,
59            uint256 _buyoutPrice,
60            uint256 _fractionPrice
61:       );

65:       event SellFractions(address indexed _seller, uint256 _amount);

69:       event BuyFractions(address indexed _buyer, uint256 _amount);

74:       event End(address _vault, State _state, address indexed _proposer);

79:       event Cash(address _vault, address indexed _casher, uint256 _amount);

83:       event Redeem(address _vault, address indexed _redeemer);

https://github.com/code-423n4/2022-07-fractional/blob/e2c5a962a94106f9495eb96769d7f60f7d5b14c9/src/interfaces/IBuyout.sol#L55-L61

File: src/interfaces/IERC1155.sol

6         event ApprovalForAll(
7             address indexed _owner,
8             address indexed _operator,
9             bool _approved
10:       );

25:       event URI(string _value, uint256 indexed _id);

https://github.com/code-423n4/2022-07-fractional/blob/e2c5a962a94106f9495eb96769d7f60f7d5b14c9/src/interfaces/IERC1155.sol#L6-L10

File: src/interfaces/IERC20.sol

6         event Approval(
7             address indexed _owner,
8             address indexed _spender,
9             uint256 _amount
10:       );

11:       event Transfer(address indexed _from, address indexed _to, uint256 amount);

https://github.com/code-423n4/2022-07-fractional/blob/e2c5a962a94106f9495eb96769d7f60f7d5b14c9/src/interfaces/IERC20.sol#L6-L10

File: src/interfaces/IERC721.sol

11        event ApprovalForAll(
12            address indexed _owner,
13            address indexed _operator,
14            bool _approved
15:       );

https://github.com/code-423n4/2022-07-fractional/blob/e2c5a962a94106f9495eb96769d7f60f7d5b14c9/src/interfaces/IERC721.sol#L11-L15

File: src/interfaces/IFERC1155.sol

21:       event SetMetadata(address indexed _metadata, uint256 _id);

26        event SetRoyalty(
27            address indexed _receiver,
28            uint256 _id,
29            uint256 _percentage
30:       );

36        event SingleApproval(
37            address indexed _owner,
38            address indexed _operator,
39            uint256 _id,
40            bool _approved
41:       );

https://github.com/code-423n4/2022-07-fractional/blob/e2c5a962a94106f9495eb96769d7f60f7d5b14c9/src/interfaces/IFERC1155.sol#L21

File: src/interfaces/IMigration.sol

61        event FractionsMigrated(
62            address indexed _oldVault,
63            address indexed _newVault,
64            uint256 _proposalId,
65            uint256 _amount
66:       );

74        event VaultMigrated(
75            address indexed _oldVault,
76            address indexed _newVault,
77            uint256 _proposalId,
78            address[] _modules,
79            address[] _plugins,
80            bytes4[] _selectors
81:       );

https://github.com/code-423n4/2022-07-fractional/blob/e2c5a962a94106f9495eb96769d7f60f7d5b14c9/src/interfaces/IMigration.sol#L61-L66

File: src/interfaces/IVaultRegistry.sol

33        event VaultDeployed(
34            address indexed _vault,
35            address indexed _token,
36            uint256 _id
37:       );

https://github.com/code-423n4/2022-07-fractional/blob/e2c5a962a94106f9495eb96769d7f60f7d5b14c9/src/interfaces/IVaultRegistry.sol#L33-L37

File: src/interfaces/IVault.sol

25:       event Execute(address indexed _target, bytes _data, bytes _response);

29:       event InstallPlugin(bytes4[] _selectors, address[] _plugins);

39:       event UninstallPlugin(bytes4[] _selectors);

https://github.com/code-423n4/2022-07-fractional/blob/e2c5a962a94106f9495eb96769d7f60f7d5b14c9/src/interfaces/IVault.sol#L25

Summary

Gas Optimizations

IssueInstances
[G‑01]Check the first bit of the number rather than using the modulo operator, to save gas1
[G‑02]Multiple address/ID mappings can be combined into a single mapping of an address/ID to a struct, where appropriate2
[G‑03]State variables only set in the constructor should be declared immutable70
[G‑04]Structs can be packed into fewer storage slots1
[G‑05]Using calldata instead of memory for read-only arguments in external functions saves gas15
[G‑06]Using storage instead of memory for structs/arrays saves gas2
[G‑07]State variables should be cached in stack variables rather than re-reading them from storage27
[G‑08]Multiple accesses of a mapping/array should use a local variable cache1
[G‑09]internal functions only called once can be inlined to save gas3
[G‑10]<array>.length should not be looked up in every loop of a for-loop8
[G‑11]++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-loops2
[G‑12]require()/revert() strings longer than 32 bytes cost extra gas2
[G‑13]Optimize names to save gas28
[G‑14]Using bools for storage incurs overhead1
[G‑15]++i costs less gas than i++, especially when it's used in for-loops (--i/i-- too)3
[G‑16]Using private rather than public for constants, saves gas5
[G‑17]Division by two should use bit shifting3
[G‑18]Empty blocks should be removed or emit something3
[G‑19]Use custom errors rather than revert()/require() strings to save gas5
[G‑20]Functions guaranteed to revert when called by normal users can be marked payable6

Total: 188 instances over 20 issues

Gas Optimizations

[G‑01] Check the first bit of the number rather than using the modulo operator, to save gas

Do the same sort of bit check as is on line 135

There is 1 instance of this issue:

File: src/utils/MerkleBase.sol

91:                  if (_node % 2 == 1) {

https://github.com/code-423n4/2022-07-fractional/blob/8f2697ae727c60c93ea47276f8fa128369abfe51/src/utils/MerkleBase.sol#L91

[G‑02] Multiple address/ID mappings can be combined into a single mapping of an address/ID to a struct, where appropriate

Saves a storage slot for the mapping. Depending on the circumstances and sizes of types, can avoid a Gsset (20000 gas) per mapping combined. Reads and subsequent writes can also be cheaper when a function requires both values and they both fit in the same storage slot. Finally, if both fields are accessed in the same function, can save ~42 gas per access due to not having to recalculate the key's keccak256 hash (Gkeccak256 - 30 gas) and that calculation's associated stack operations.

There are 2 instances of this issue:

File: src/FERC1155.sol

23        mapping(address => mapping(address => mapping(uint256 => bool)))
24            public isApproved;
25        /// @notice Mapping of metadata contracts for token ID types => metadata address
26        mapping(uint256 => address) public metadata;
27        /// @notice Mapping to track account nonces for metadata txs owner => nonces
28        mapping(address => uint256) public nonces;
29        /// @notice Mapping to track total supply for token ID types => totalSupply
30        mapping(uint256 => uint256) public totalSupply;
31        /// @notice Mapping to track royalty receivers for token ID types => royaltyAddress
32        mapping(uint256 => address) private royaltyAddress;
33        /// @notice Mapping to track the royalty percent for token ID types => royaltyPercent
34:       mapping(uint256 => uint256) private royaltyPercent;

https://github.com/code-423n4/2022-07-fractional/blob/e2c5a962a94106f9495eb96769d7f60f7d5b14c9/src/FERC1155.sol#L23-L34

File: src/modules/Migration.sol

45        mapping(address => mapping(uint256 => Proposal)) public migrationInfo;
46        /// @notice Mapping of a proposal ID to a user's ether contribution
47        mapping(uint256 => mapping(address => uint256)) private userProposalEth;
48        /// @notice Mapping of a proposal ID to a user's fractions contribution
49        mapping(uint256 => mapping(address => uint256))
50:           private userProposalFractions;

https://github.com/code-423n4/2022-07-fractional/blob/e2c5a962a94106f9495eb96769d7f60f7d5b14c9/src/modules/Migration.sol#L45-L50

[G‑03] State variables only set in the constructor should be declared immutable

Avoids a Gsset (20000 gas) in the constructor, and replaces the first access in each transaction (Gcoldsload - 2100 gas) and each access thereafter (Gwarmacces - 100 gas) with a PUSH32 (3 gas).

There are 70 instances of this issue:

File: src/modules/Buyout.sol

/// @audit registry
47:           registry = _registry;

/// @audit registry
61:           (address token, uint256 id) = IVaultRegistry(registry).vaultToToken(

/// @audit registry
71:           uint256 totalSupply = IVaultRegistry(registry).totalSupply(_vault);

/// @audit registry
114:          (address token, uint256 id) = IVaultRegistry(registry).vaultToToken(

/// @audit registry
151:          (address token, uint256 id) = IVaultRegistry(registry).vaultToToken(

/// @audit registry
186:          (address token, uint256 id) = IVaultRegistry(registry).vaultToToken(

/// @audit registry
210:                  IVaultRegistry(registry).totalSupply(_vault) >

/// @audit registry
246:          (address token, uint256 id) = IVaultRegistry(registry).vaultToToken(

/// @audit registry
267:          uint256 totalSupply = IVaultRegistry(registry).totalSupply(_vault);

/// @audit registry
280:          (, uint256 id) = IVaultRegistry(registry).vaultToToken(_vault);

/// @audit registry
288:          uint256 totalSupply = IVaultRegistry(registry).totalSupply(_vault);

/// @audit registry
319:          (, uint256 id) = IVaultRegistry(registry).vaultToToken(_vault);

/// @audit registry
351:          (, uint256 id) = IVaultRegistry(registry).vaultToToken(_vault);

/// @audit registry
387:              (, uint256 id) = IVaultRegistry(registry).vaultToToken(_vault);

/// @audit registry
424:              (, uint256 id) = IVaultRegistry(registry).vaultToToken(_vault);

/// @audit supply
48:           supply = _supply;

/// @audit supply
219:              IVault(payable(_vault)).execute(supply, data, _burnProof);

/// @audit supply
264:          IVault(payable(_vault)).execute(supply, data, _burnProof);

/// @audit supply
294:          IVault(payable(_vault)).execute(supply, data, _burnProof);

/// @audit supply
476:              supply,

/// @audit supply
477:              ISupply(supply).burn.selector

/// @audit transfer
49:           transfer = _transfer;

/// @audit transfer
334:          IVault(payable(_vault)).execute(transfer, data, _erc20TransferProof);

/// @audit transfer
366:          IVault(payable(_vault)).execute(transfer, data, _erc721TransferProof);

/// @audit transfer
403:          IVault(payable(_vault)).execute(transfer, data, _erc1155TransferProof);

/// @audit transfer
441:              transfer,

/// @audit transfer
482:              transfer,

/// @audit transfer
483:              ITransfer(transfer).ERC20Transfer.selector

/// @audit transfer
488:              transfer,

/// @audit transfer
489:              ITransfer(transfer).ERC721TransferFrom.selector

/// @audit transfer
494:              transfer,

/// @audit transfer
495:              ITransfer(transfer).ERC1155TransferFrom.selector

/// @audit transfer
500:              transfer,

/// @audit transfer
501:              ITransfer(transfer).ERC1155BatchTransferFrom.selector

https://github.com/code-423n4/2022-07-fractional/blob/e2c5a962a94106f9495eb96769d7f60f7d5b14c9/src/modules/Buyout.sol#L47

File: src/modules/Migration.sol

/// @audit buyout
58:           buyout = payable(_buyout);

/// @audit buyout
84:           (, , State current, , , ) = IBuyout(buyout).buyoutInfo(_vault);

/// @audit buyout
116:          (, , State current, , , ) = IBuyout(buyout).buyoutInfo(_vault);

/// @audit buyout
148:          (, , State current, , , ) = IBuyout(buyout).buyoutInfo(_vault);

/// @audit buyout
189:          (, , State current, , , ) = IBuyout(buyout).buyoutInfo(_vault);

/// @audit buyout
208:              IFERC1155(token).setApprovalFor(address(buyout), id, true);

/// @audit buyout
210:              IBuyout(buyout).start{value: proposal.totalEth}(_vault);

/// @audit buyout
225:          (, , State current, , , ) = IBuyout(buyout).buyoutInfo(_vault);

/// @audit buyout
263:          (, , State current, , , ) = IBuyout(buyout).buyoutInfo(_vault);

/// @audit buyout
301:          (, , State current, , , ) = IBuyout(buyout).buyoutInfo(_vault);

/// @audit buyout
343:          IBuyout(buyout).withdrawERC20(

/// @audit buyout
367:          IBuyout(buyout).withdrawERC721(

/// @audit buyout
393:          IBuyout(buyout).withdrawERC1155(

/// @audit buyout
420:          IBuyout(buyout).batchWithdrawERC1155(

/// @audit buyout
438:          (, address proposer, State current, , , ) = IBuyout(buyout).buyoutInfo(

/// @audit buyout
447:          (, , , , , uint256 lastTotalSupply) = IBuyout(buyout).buyoutInfo(

/// @audit registry
59:           registry = _registry;

/// @audit registry
81:           (, uint256 id) = IVaultRegistry(registry).vaultToToken(_vault);

/// @audit registry
95:           proposal.oldFractionSupply = IVaultRegistry(registry).totalSupply(

/// @audit registry
111:          (address token, uint256 id) = IVaultRegistry(registry).vaultToToken(

/// @audit registry
143:          (address token, uint256 id) = IVaultRegistry(registry).vaultToToken(

/// @audit registry
184:          (address token, uint256 id) = IVaultRegistry(registry).vaultToToken(

/// @audit registry
200:              IVaultRegistry(registry).totalSupply(_vault),

/// @audit registry
235:          address newVault = IVaultRegistry(registry).create(

/// @audit registry
296:          (address token, uint256 id) = IVaultRegistry(registry).vaultToToken(

/// @audit registry
435:          (, uint256 id) = IVaultRegistry(registry).vaultToToken(_vault);

/// @audit registry
467:          (address token, uint256 newFractionId) = IVaultRegistry(registry)

/// @audit registry
470:          uint256 newTotalSupply = IVaultRegistry(registry).totalSupply(newVault);

https://github.com/code-423n4/2022-07-fractional/blob/e2c5a962a94106f9495eb96769d7f60f7d5b14c9/src/modules/Migration.sol#L58

File: src/modules/Minter.sol

/// @audit supply
18:           supply = _supply;

/// @audit supply
40:               supply,

/// @audit supply
60:           IVault(payable(_vault)).execute(supply, data, _mintProof);

https://github.com/code-423n4/2022-07-fractional/blob/e2c5a962a94106f9495eb96769d7f60f7d5b14c9/src/modules/Minter.sol#L18

File: src/modules/protoforms/BaseVault.sol

/// @audit registry
25:           registry = _registry;

/// @audit registry
43:           vault = IVaultRegistry(registry).create(

https://github.com/code-423n4/2022-07-fractional/blob/e2c5a962a94106f9495eb96769d7f60f7d5b14c9/src/modules/protoforms/BaseVault.sol#L25

File: src/VaultFactory.sol

/// @audit implementation
21:           implementation = address(new Vault());

/// @audit implementation
39:           (uint256 creationPtr, uint256 creationSize) = implementation

/// @audit implementation
69:           vault = implementation.clone(salt, data);

https://github.com/code-423n4/2022-07-fractional/blob/e2c5a962a94106f9495eb96769d7f60f7d5b14c9/src/VaultFactory.sol#L21

[G‑04] Structs can be packed into fewer storage slots

Each slot saved can avoid an extra Gsset (20000 gas) for the first setting of the struct. Subsequent reads as well as writes have smaller gas savings

There is 1 instance of this issue:

File: src/interfaces/IMigration.sol

/// @audit Variable ordering with 10 slots instead of the current 11:
///           uint256(32):startTime, uint256(32):targetPrice, uint256(32):totalEth, uint256(32):totalFractions, address[](32):modules, address[](32):plugins, bytes4[](32):selectors, uint256(32):oldFractionSupply, uint256(32):newFractionSupply, address(20):newVault, bool(1):isCommited, bool(1):fractionsMigrated
8     struct Proposal {
9         // Start time of the migration proposal
10        uint256 startTime;
11        // Target buyout price for the migration
12        uint256 targetPrice;
13        // Total ether contributed to the migration
14        uint256 totalEth;
15        // Total fractions contributed to the migration
16        uint256 totalFractions;
17        // Module contract addresses proposed for the migration
18        address[] modules;
19        // Plugin contract addresses proposed for the migration
20        address[] plugins;
21        // Function selectors for the proposed plugins
22        bytes4[] selectors;
23        // Address for the new vault to migrate to (if buyout is succesful)
24        address newVault;
25        // Boolean status to check if the propoal is active
26        bool isCommited;
27        // Old fraction supply for a given vault
28        uint256 oldFractionSupply;
29        // New fraction supply for a given vault that has succesfully migrated
30        uint256 newFractionSupply;
31        // Boolean status to check that the fractions have already been migrated
32        bool fractionsMigrated;
33:   }

https://github.com/code-423n4/2022-07-fractional/blob/e2c5a962a94106f9495eb96769d7f60f7d5b14c9/src/interfaces/IMigration.sol#L8-L33

[G‑05] Using calldata instead of memory for read-only arguments in external functions saves gas

When a function with a memory array is called externally, the abi.decode() step has to use a for-loop to copy each index of the calldata to the memory index. Each iteration of this for-loop costs at least 60 gas (i.e. 60 * <mem_array>.length). Using calldata directly, obliviates the need for such a loop in the contract code and runtime execution. Note that even if an interface defines a function as having memory arguments, it's still valid for implementation contracs to use calldata arguments instead.

If the array is passed to an internal function which passes the array to another internal function where the array is modified and therefore memory is used in the external call, it's still more gass-efficient to use calldata when the external function uses modifiers, since the modifiers may prevent the internal functions from being called. Structs have the same overhead as an array of length one

Note that I've also flagged instances where the function is public but can be marked as external since it's not called by the contract, and cases where a constructor is involved

There are 15 instances of this issue:

File: src/FERC1155.sol

/// @audit _uri
68:       function emitSetURI(uint256 _id, string memory _uri) external {

/// @audit _data
79        function mint(
80            address _to,
81            uint256 _id,
82            uint256 _amount,
83            bytes memory _data
84:       ) external onlyRegistry {

https://github.com/code-423n4/2022-07-fractional/blob/e2c5a962a94106f9495eb96769d7f60f7d5b14c9/src/FERC1155.sol#L68

File: src/utils/MerkleBase.sol

/// @audit _proof
43        function verifyProof(
44            bytes32 _root,
45            bytes32[] memory _proof,
46            bytes32 _valueToProve
47:       ) public pure returns (bool) {

https://github.com/code-423n4/2022-07-fractional/blob/e2c5a962a94106f9495eb96769d7f60f7d5b14c9/src/utils/MerkleBase.sol#L43-L47

File: src/utils/Metadata.sol

/// @audit _uri
24:       function setURI(uint256 _id, string memory _uri) external {

https://github.com/code-423n4/2022-07-fractional/blob/e2c5a962a94106f9495eb96769d7f60f7d5b14c9/src/utils/Metadata.sol#L24

File: src/VaultRegistry.sol

/// @audit _plugins
/// @audit _selectors
51        function create(
52            bytes32 _merkleRoot,
53            address[] memory _plugins,
54            bytes4[] memory _selectors
55:       ) external returns (address vault) {

/// @audit _plugins
/// @audit _selectors
67        function createFor(
68            bytes32 _merkleRoot,
69            address _owner,
70            address[] memory _plugins,
71            bytes4[] memory _selectors
72:       ) external returns (address vault) {

/// @audit _plugins
/// @audit _selectors
83        function createCollection(
84            bytes32 _merkleRoot,
85            address[] memory _plugins,
86            bytes4[] memory _selectors
87:       ) external returns (address vault, address token) {

/// @audit _plugins
/// @audit _selectors
102       function createInCollection(
103           bytes32 _merkleRoot,
104           address _token,
105           address[] memory _plugins,
106           bytes4[] memory _selectors
107:      ) external returns (address vault) {

https://github.com/code-423n4/2022-07-fractional/blob/e2c5a962a94106f9495eb96769d7f60f7d5b14c9/src/VaultRegistry.sol#L51-L55

File: src/Vault.sol

/// @audit _selectors
/// @audit _plugins
73:       function install(bytes4[] memory _selectors, address[] memory _plugins)

/// @audit _selectors
101:      function uninstall(bytes4[] memory _selectors) external {

https://github.com/code-423n4/2022-07-fractional/blob/e2c5a962a94106f9495eb96769d7f60f7d5b14c9/src/Vault.sol#L73

[G‑06] Using storage instead of memory for structs/arrays saves gas

When fetching data from a storage location, assigning the data to a memory variable causes all fields of the struct/array to be read from storage, which incurs a Gcoldsload (2100 gas) for each field of the struct/array. If the fields are read from the new memory variable, they incur an additional MLOAD rather than a cheap stack read. Instead of declearing the variable with the memory keyword, declaring the variable with the storage keyword and caching any fields that need to be re-read in stack variables, will be much cheaper, only incuring the Gcoldsload for the fields actually read. The only time it makes sense to read the whole struct/array into a memory variable, is if the full struct/array is being returned by the function, is being passed to a function that requires memory, or if the array/struct is being read from another memory array/struct

There are 2 instances of this issue:

File: src/VaultRegistry.sol

40:           VaultInfo memory info = vaultToToken[msg.sender];

118:          VaultInfo memory info = vaultToToken[msg.sender];

https://github.com/code-423n4/2022-07-fractional/blob/e2c5a962a94106f9495eb96769d7f60f7d5b14c9/src/VaultRegistry.sol#L40

[G‑07] State variables should be cached in stack variables rather than re-reading them from storage

The instances below point to the second+ access of a state variable within a function. Caching of a state variable replace each Gwarmaccess (100 gas) with a much cheaper stack read. Other less obvious fixes/optimizations include having local memory caches of state variable structs, or having local caches of state variable contracts/addresses.

There are 27 instances of this issue:

File: src/FERC1155.sol

/// @audit _controller on line 303
305:              : controllerAddress = _controller;

https://github.com/code-423n4/2022-07-fractional/blob/e2c5a962a94106f9495eb96769d7f60f7d5b14c9/src/FERC1155.sol#L305

File: src/modules/Buyout.sol

/// @audit registry on line 61
71:           uint256 totalSupply = IVaultRegistry(registry).totalSupply(_vault);

/// @audit registry on line 186
210:                  IVaultRegistry(registry).totalSupply(_vault) >

/// @audit registry on line 246
267:          uint256 totalSupply = IVaultRegistry(registry).totalSupply(_vault);

/// @audit registry on line 280
288:          uint256 totalSupply = IVaultRegistry(registry).totalSupply(_vault);

/// @audit supply on line 476
477:              ISupply(supply).burn.selector

/// @audit transfer on line 482
483:              ITransfer(transfer).ERC20Transfer.selector

/// @audit transfer on line 483
488:              transfer,

/// @audit transfer on line 488
489:              ITransfer(transfer).ERC721TransferFrom.selector

/// @audit transfer on line 489
494:              transfer,

/// @audit transfer on line 494
495:              ITransfer(transfer).ERC1155TransferFrom.selector

/// @audit transfer on line 495
500:              transfer,

/// @audit transfer on line 500
501:              ITransfer(transfer).ERC1155BatchTransferFrom.selector

https://github.com/code-423n4/2022-07-fractional/blob/e2c5a962a94106f9495eb96769d7f60f7d5b14c9/src/modules/Buyout.sol#L71

File: src/modules/Migration.sol

/// @audit buyout on line 189
208:              IFERC1155(token).setApprovalFor(address(buyout), id, true);

/// @audit buyout on line 208
210:              IBuyout(buyout).start{value: proposal.totalEth}(_vault);

/// @audit buyout on line 438
447:          (, , , , , uint256 lastTotalSupply) = IBuyout(buyout).buyoutInfo(

/// @audit registry on line 81
95:           proposal.oldFractionSupply = IVaultRegistry(registry).totalSupply(

/// @audit registry on line 184
200:              IVaultRegistry(registry).totalSupply(_vault),

/// @audit registry on line 435
467:          (address token, uint256 newFractionId) = IVaultRegistry(registry)

/// @audit registry on line 467
470:          uint256 newTotalSupply = IVaultRegistry(registry).totalSupply(newVault);

https://github.com/code-423n4/2022-07-fractional/blob/e2c5a962a94106f9495eb96769d7f60f7d5b14c9/src/modules/Migration.sol#L208

File: src/Vault.sol

/// @audit owner on line 76
76:           if (owner != msg.sender) revert NotOwner(owner, msg.sender);

/// @audit owner on line 87
87:           if (owner != msg.sender) revert NotOwner(owner, msg.sender);

/// @audit owner on line 94
94:           if (owner != msg.sender) revert NotOwner(owner, msg.sender);

/// @audit owner on line 102
102:          if (owner != msg.sender) revert NotOwner(owner, msg.sender);

/// @audit owner on line 126
/// @audit owner on line 132
132:          if (owner_ != owner) revert OwnerChanged(owner_, owner);

/// @audit nonce on line 25
25:           if (nonce != 0) revert Initialized(owner, msg.sender, nonce);

https://github.com/code-423n4/2022-07-fractional/blob/e2c5a962a94106f9495eb96769d7f60f7d5b14c9/src/Vault.sol#L76

[G‑08] Multiple accesses of a mapping/array should use a local variable cache

The instances below point to the second+ access of a value inside a mapping/array, within a function. Caching a mapping's value in a local storage or calldata variable when the value is accessed multiple times, saves ~42 gas per access due to not having to recalculate the key's keccak256 hash (Gkeccak256 - 30 gas) and that calculation's associated stack operations. Caching an array's struct avoids recalculating the array offsets into memory/calldata

There is 1 instance of this issue:

File: src/modules/Buyout.sol

/// @audit buyoutInfo[_vault] on line 297
297:          (buyoutInfo[_vault].state, buyoutInfo[_vault].proposer) = (

https://github.com/code-423n4/2022-07-fractional/blob/e2c5a962a94106f9495eb96769d7f60f7d5b14c9/src/modules/Buyout.sol#L297

[G‑09] internal functions only called once can be inlined to save gas

Not inlining costs 20 to 40 gas because of two extra JUMP instructions and additional stack operations needed for function calls.

There are 3 instances of this issue:

File: src/FERC1155.sol

324       function _computePermitStructHash(
325           address _owner,
326           address _operator,
327           uint256 _id,
328           bool _approved,
329           uint256 _deadline
330:      ) internal returns (bytes32) {

350       function _computePermitAllStructHash(
351           address _owner,
352           address _operator,
353           bool _approved,
354           uint256 _deadline
355:      ) internal returns (bytes32) {

https://github.com/code-423n4/2022-07-fractional/blob/e2c5a962a94106f9495eb96769d7f60f7d5b14c9/src/FERC1155.sol#L324-L330

File: src/Vault.sol

142:      function _revertedWithReason(bytes memory _response) internal pure {

https://github.com/code-423n4/2022-07-fractional/blob/e2c5a962a94106f9495eb96769d7f60f7d5b14c9/src/Vault.sol#L142

[G‑10] <array>.length should not be looked up in every loop of a for-loop

The overheads outlined below are PER LOOP, excluding the first loop

  • storage arrays incur a Gwarmaccess (100 gas)
  • memory arrays use MLOAD (3 gas)
  • calldata arrays use CALLDATALOAD (3 gas)

Caching the length changes each of these to a DUP<N> (3 gas), and gets rid of the extra DUP<N> needed to store the stack offset

There are 8 instances of this issue:

File: src/modules/Buyout.sol

454:          for (uint256 i; i < permissions.length; ) {

https://github.com/code-423n4/2022-07-fractional/blob/e2c5a962a94106f9495eb96769d7f60f7d5b14c9/src/modules/Buyout.sol#L454

File: src/modules/protoforms/BaseVault.sol

64:           for (uint256 i = 0; i < _tokens.length; ) {

83:           for (uint256 i = 0; i < _tokens.length; ) {

107:              for (uint256 i = 0; i < _tokens.length; ++i) {

130:              for (uint256 i; i < _modules.length; ++i) {

132:                  for (uint256 j; j < leaves.length; ++j) {

https://github.com/code-423n4/2022-07-fractional/blob/e2c5a962a94106f9495eb96769d7f60f7d5b14c9/src/modules/protoforms/BaseVault.sol#L64

File: src/utils/MerkleBase.sol

51:               for (uint256 i = 0; i < _proof.length; ++i) {

110:              for (uint256 i; i < result.length; ++i) {

https://github.com/code-423n4/2022-07-fractional/blob/e2c5a962a94106f9495eb96769d7f60f7d5b14c9/src/utils/MerkleBase.sol#L51

[G‑11] ++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-loops

The unchecked keyword is new in solidity version 0.8.0, so this only applies to that version or higher, which these instances are. This saves 30-40 gas per loop

There are 2 instances of this issue:

File: src/Vault.sol

78:           for (uint256 i = 0; i < length; i++) {

104:          for (uint256 i = 0; i < length; i++) {

https://github.com/code-423n4/2022-07-fractional/blob/e2c5a962a94106f9495eb96769d7f60f7d5b14c9/src/Vault.sol#L78

[G‑12] require()/revert() strings longer than 32 bytes cost extra gas

Each extra memory word of bytes past the original 32 incurs an MSTORE which costs 3 gas

There are 2 instances of this issue:

File: src/utils/MerkleBase.sol

62:           require(_data.length > 1, "wont generate root for single leaf");

78:           require(_data.length > 1, "wont generate proof for single leaf");

https://github.com/code-423n4/2022-07-fractional/blob/e2c5a962a94106f9495eb96769d7f60f7d5b14c9/src/utils/MerkleBase.sol#L62

[G‑13] Optimize names to save gas

public/external function names and public member variable names can be optimized to save gas. See this link for an example of how it works. Below are the interfaces/abstract contracts that can be optimized so that the most frequently-called functions use the least amount of gas possible during method lookup. Method IDs that have two leading zero bytes can save 128 gas each during deployment, and renaming functions to have lower method IDs will save 22 gas per call, per sorted position shifted

There are 28 instances of this issue:

File: src/FERC1155.sol

/// @audit burn(), emitSetURI(), mint(), permit(), permitAll(), setApprovalFor(), setContractURI(), setMetadata(), setRoyalties(), transferController(), royaltyInfo(), controller(), INITIAL_CONTROLLER(), VAULT_REGISTRY()
13:   contract FERC1155 is Clone, ERC1155, IFERC1155 {

https://github.com/code-423n4/2022-07-fractional/blob/e2c5a962a94106f9495eb96769d7f60f7d5b14c9/src/FERC1155.sol#L13

File: src/interfaces/IBaseVault.sol

/// @audit batchDepositERC20(), batchDepositERC721(), batchDepositERC1155(), deployVault(), registry()
8:    interface IBaseVault is IProtoform {

https://github.com/code-423n4/2022-07-fractional/blob/e2c5a962a94106f9495eb96769d7f60f7d5b14c9/src/interfaces/IBaseVault.sol#L8

File: src/interfaces/IBuyout.sol

/// @audit PROPOSAL_PERIOD(), REJECTION_PERIOD(), batchWithdrawERC1155(), buyFractions(), buyoutInfo(), cash(), end(), getLeafNodes(), getPermissions(), redeem(), registry(), sellFractions(), start(), supply(), transfer(), withdrawERC20(), withdrawERC721(), withdrawERC1155()
31:   interface IBuyout is IModule {

https://github.com/code-423n4/2022-07-fractional/blob/e2c5a962a94106f9495eb96769d7f60f7d5b14c9/src/interfaces/IBuyout.sol#L31

File: src/interfaces/IFERC1155.sol

/// @audit INITIAL_CONTROLLER(), NAME(), VAULT_REGISTRY(), VERSION(), burn(), contractURI(), controller(), emitSetURI(), isApproved(), metadata(), mint(), permit(), permitAll(), royaltyInfo(), setApprovalFor(), setContractURI(), setMetadata(), setRoyalties(), totalSupply(), transferController()
5:    interface IFERC1155 {

https://github.com/code-423n4/2022-07-fractional/blob/e2c5a962a94106f9495eb96769d7f60f7d5b14c9/src/interfaces/IFERC1155.sol#L5

File: src/interfaces/IMigration.sol

/// @audit PROPOSAL_PERIOD(), batchMigrateVaultERC1155(), buyout(), commit(), generateMerkleTree(), join(), leave(), migrateFractions(), migrateVaultERC20(), migrateVaultERC721(), migrationInfo(), nextId(), propose(), registry(), settleFractions(), settleVault(), withdrawContribution()
36:   interface IMigration {

https://github.com/code-423n4/2022-07-fractional/blob/e2c5a962a94106f9495eb96769d7f60f7d5b14c9/src/interfaces/IMigration.sol#L36

File: src/interfaces/IMinter.sol

/// @audit supply()
8:    interface IMinter is IModule {

https://github.com/code-423n4/2022-07-fractional/blob/e2c5a962a94106f9495eb96769d7f60f7d5b14c9/src/interfaces/IMinter.sol#L8

File: src/interfaces/IModule.sol

/// @audit getLeafNodes(), getPermissions()
7:    interface IModule {

https://github.com/code-423n4/2022-07-fractional/blob/e2c5a962a94106f9495eb96769d7f60f7d5b14c9/src/interfaces/IModule.sol#L7

File: src/interfaces/IProtoform.sol

/// @audit deployVault(), generateMerkleTree()
7:    interface IProtoform {

https://github.com/code-423n4/2022-07-fractional/blob/e2c5a962a94106f9495eb96769d7f60f7d5b14c9/src/interfaces/IProtoform.sol#L7

File: src/interfaces/ISupply.sol

/// @audit mint(), burn()
5:    interface ISupply {

https://github.com/code-423n4/2022-07-fractional/blob/e2c5a962a94106f9495eb96769d7f60f7d5b14c9/src/interfaces/ISupply.sol#L5

File: src/interfaces/ITransfer.sol

/// @audit ERC20Transfer(), ERC721TransferFrom(), ERC1155TransferFrom(), ERC1155BatchTransferFrom()
5:    interface ITransfer {

https://github.com/code-423n4/2022-07-fractional/blob/e2c5a962a94106f9495eb96769d7f60f7d5b14c9/src/interfaces/ITransfer.sol#L5

File: src/interfaces/IVaultFactory.sol

/// @audit deploy(), deployFor(), getNextAddress(), getNextSeed(), implementation()
5:    interface IVaultFactory {

https://github.com/code-423n4/2022-07-fractional/blob/e2c5a962a94106f9495eb96769d7f60f7d5b14c9/src/interfaces/IVaultFactory.sol#L5

File: src/interfaces/IVaultRegistry.sol

/// @audit burn(), create(), createCollection(), createCollectionFor(), createFor(), createInCollection(), factory(), fNFT(), fNFTImplementation(), mint(), nextId(), totalSupply(), uri(), vaultToToken()
23:   interface IVaultRegistry {

https://github.com/code-423n4/2022-07-fractional/blob/e2c5a962a94106f9495eb96769d7f60f7d5b14c9/src/interfaces/IVaultRegistry.sol#L23

File: src/interfaces/IVault.sol

/// @audit execute(), init(), install(), merkleRoot(), methods(), nonce(), setMerkleRoot(), uninstall()
5:    interface IVault {

https://github.com/code-423n4/2022-07-fractional/blob/e2c5a962a94106f9495eb96769d7f60f7d5b14c9/src/interfaces/IVault.sol#L5

File: src/modules/Buyout.sol

/// @audit start(), sellFractions(), buyFractions(), end(), cash(), redeem(), withdrawERC20(), withdrawERC721(), withdrawERC1155(), batchWithdrawERC1155(), getLeafNodes(), getPermissions()
27:   contract Buyout is IBuyout, Multicall, NFTReceiver, SafeSend, SelfPermit {

https://github.com/code-423n4/2022-07-fractional/blob/e2c5a962a94106f9495eb96769d7f60f7d5b14c9/src/modules/Buyout.sol#L27

File: src/modules/Migration.sol

/// @audit propose(), join(), leave(), commit(), settleVault(), settleFractions(), withdrawContribution(), migrateVaultERC20(), migrateVaultERC721(), migrateVaultERC1155(), batchMigrateVaultERC1155(), migrateFractions(), generateMerkleTree()
28:   contract Migration is

https://github.com/code-423n4/2022-07-fractional/blob/e2c5a962a94106f9495eb96769d7f60f7d5b14c9/src/modules/Migration.sol#L28

File: src/modules/Minter.sol

/// @audit getLeafNodes(), getPermissions()
12:   contract Minter is IMinter {

https://github.com/code-423n4/2022-07-fractional/blob/e2c5a962a94106f9495eb96769d7f60f7d5b14c9/src/modules/Minter.sol#L12

File: src/modules/protoforms/BaseVault.sol

/// @audit deployVault(), batchDepositERC20(), batchDepositERC721(), batchDepositERC1155(), generateMerkleTree()
17:   contract BaseVault is IBaseVault, MerkleBase, Minter, Multicall {

https://github.com/code-423n4/2022-07-fractional/blob/e2c5a962a94106f9495eb96769d7f60f7d5b14c9/src/modules/protoforms/BaseVault.sol#L17

File: src/references/SupplyReference.sol

/// @audit mint(), burn()
10:   contract SupplyReference is ISupply {

https://github.com/code-423n4/2022-07-fractional/blob/e2c5a962a94106f9495eb96769d7f60f7d5b14c9/src/references/SupplyReference.sol#L10

File: src/references/TransferReference.sol

/// @audit ERC20Transfer(), ERC721TransferFrom(), ERC1155TransferFrom(), ERC1155BatchTransferFrom()
12:   contract TransferReference is ITransfer {

https://github.com/code-423n4/2022-07-fractional/blob/e2c5a962a94106f9495eb96769d7f60f7d5b14c9/src/references/TransferReference.sol#L12

File: src/targets/Supply.sol

/// @audit mint(), burn()
11:   contract Supply is ISupply {

https://github.com/code-423n4/2022-07-fractional/blob/e2c5a962a94106f9495eb96769d7f60f7d5b14c9/src/targets/Supply.sol#L11

File: src/targets/Transfer.sol

/// @audit ERC20Transfer(), ERC721TransferFrom(), ERC1155TransferFrom(), ERC1155BatchTransferFrom()
13:   contract Transfer is ITransfer {

https://github.com/code-423n4/2022-07-fractional/blob/e2c5a962a94106f9495eb96769d7f60f7d5b14c9/src/targets/Transfer.sol#L13

File: src/utils/MerkleBase.sol

/// @audit hashLeafPairs(), verifyProof(), getRoot(), getProof(), log2ceil_naive()
7:    abstract contract MerkleBase {

https://github.com/code-423n4/2022-07-fractional/blob/e2c5a962a94106f9495eb96769d7f60f7d5b14c9/src/utils/MerkleBase.sol#L7

File: src/utils/Metadata.sol

/// @audit setURI()
9:    contract Metadata {

https://github.com/code-423n4/2022-07-fractional/blob/e2c5a962a94106f9495eb96769d7f60f7d5b14c9/src/utils/Metadata.sol#L9

File: src/utils/Multicall.sol

/// @audit multicall()
7:    abstract contract Multicall {

https://github.com/code-423n4/2022-07-fractional/blob/e2c5a962a94106f9495eb96769d7f60f7d5b14c9/src/utils/Multicall.sol#L7

File: src/utils/SelfPermit.sol

/// @audit selfPermit(), selfPermitAll()
9:    abstract contract SelfPermit {

https://github.com/code-423n4/2022-07-fractional/blob/e2c5a962a94106f9495eb96769d7f60f7d5b14c9/src/utils/SelfPermit.sol#L9

File: src/VaultFactory.sol

/// @audit deploy(), getNextAddress(), getNextSeed(), deployFor()
11:   contract VaultFactory is IVaultFactory {

https://github.com/code-423n4/2022-07-fractional/blob/e2c5a962a94106f9495eb96769d7f60f7d5b14c9/src/VaultFactory.sol#L11

File: src/VaultRegistry.sol

/// @audit burn(), create(), createFor(), createCollection(), createInCollection(), mint(), totalSupply(), uri(), createCollectionFor()
13:   contract VaultRegistry is IVaultRegistry {

https://github.com/code-423n4/2022-07-fractional/blob/e2c5a962a94106f9495eb96769d7f60f7d5b14c9/src/VaultRegistry.sol#L13

File: src/Vault.sol

/// @audit init(), execute(), install(), setMerkleRoot(), uninstall()
11:   contract Vault is IVault, NFTReceiver {

https://github.com/code-423n4/2022-07-fractional/blob/e2c5a962a94106f9495eb96769d7f60f7d5b14c9/src/Vault.sol#L11

[G‑14] Using bools 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.

https://github.com/OpenZeppelin/openzeppelin-contracts/blob/58f635312aa21f947cae5f8578638a85aa2519f5/contracts/security/ReentrancyGuard.sol#L23-L27 Use uint256(1) and uint256(2) for true/false to avoid a Gwarmaccess (100 gas) for the extra SLOAD, and to avoid Gsset (20000 gas) when changing from false to true, after having been true in the past

There is 1 instance of this issue:

File: src/FERC1155.sol

23        mapping(address => mapping(address => mapping(uint256 => bool)))
24:           public isApproved;

https://github.com/code-423n4/2022-07-fractional/blob/e2c5a962a94106f9495eb96769d7f60f7d5b14c9/src/FERC1155.sol#L23-L24

[G‑15] ++i costs less gas than i++, especially when it's used in for-loops (--i/i-- too)

Saves 5 gas per loop

There are 3 instances of this issue:

File: src/utils/MerkleBase.sol

188:                  ceil++;

https://github.com/code-423n4/2022-07-fractional/blob/e2c5a962a94106f9495eb96769d7f60f7d5b14c9/src/utils/MerkleBase.sol#L188

File: src/Vault.sol

78:           for (uint256 i = 0; i < length; i++) {

104:          for (uint256 i = 0; i < length; i++) {

https://github.com/code-423n4/2022-07-fractional/blob/e2c5a962a94106f9495eb96769d7f60f7d5b14c9/src/Vault.sol#L78

[G‑16] Using private rather than public for constants, saves gas

If needed, the values can be read from the verified contract source code, or if there are multiple values there can be a single getter function that returns a tuple of the values of all currently-public constants. Saves 3406-3606 gas in deployment gas due to the compiler not having to create non-payable getter functions for deployment calldata, not having to store the bytes of the value outside of where it's used, and not adding another entry to the method ID table

There are 5 instances of this issue:

File: src/FERC1155.sol

15:       string public constant NAME = "FERC1155";

17:       string public constant VERSION = "1";

https://github.com/code-423n4/2022-07-fractional/blob/e2c5a962a94106f9495eb96769d7f60f7d5b14c9/src/FERC1155.sol#L15

File: src/modules/Buyout.sol

35:       uint256 public constant PROPOSAL_PERIOD = 2 days;

37:       uint256 public constant REJECTION_PERIOD = 4 days;

https://github.com/code-423n4/2022-07-fractional/blob/e2c5a962a94106f9495eb96769d7f60f7d5b14c9/src/modules/Buyout.sol#L35

File: src/modules/Migration.sol

43:       uint256 public constant PROPOSAL_PERIOD = 7 days;

https://github.com/code-423n4/2022-07-fractional/blob/e2c5a962a94106f9495eb96769d7f60f7d5b14c9/src/modules/Migration.sol#L43

[G‑17] Division by two should use bit shifting

<x> / 2 is the same as <x> >> 1. While the compiler uses the SHR opcode to accomplish both, the version that uses division incurs an overhead of 20 gas due to JUMPs to and from a compiler utility function that introduces checks which can be avoided by using unchecked {} around the division by two

There are 3 instances of this issue:

File: src/utils/MerkleBase.sol

100:                  _node = _node / 2;

136:                  result = new bytes32[](length / 2 + 1);

142:                  result = new bytes32[](length / 2);

https://github.com/code-423n4/2022-07-fractional/blob/e2c5a962a94106f9495eb96769d7f60f7d5b14c9/src/utils/MerkleBase.sol#L100

[G‑18] Empty blocks should be removed or emit something

The code should be refactored such that they no longer exist, or the block should do something useful, such as emitting an event or reverting. If the contract is meant to be extended, the contract should be abstract and the function signatures be added without any default implementation. If the block is an empty if-statement block to avoid doing subsequent checks in the else-if/else conditions, the else-if/else conditions should be nested under the negation of the if-statement, because they involve different classes of checks, which may lead to the introduction of errors when the code is later modified (if(x){}else if(y){...}else{...} => if(!x){if(y){...}else{...}}). Empty receive()/fallback() payable functions that are not used, can be removed to save deployment gas.

There are 3 instances of this issue:

File: src/modules/Buyout.sol

53:       receive() external payable {}

https://github.com/code-423n4/2022-07-fractional/blob/e2c5a962a94106f9495eb96769d7f60f7d5b14c9/src/modules/Buyout.sol#L53

File: src/modules/Migration.sol

63:       receive() external payable {}

https://github.com/code-423n4/2022-07-fractional/blob/e2c5a962a94106f9495eb96769d7f60f7d5b14c9/src/modules/Migration.sol#L63

File: src/Vault.sol

32:       receive() external payable {}

https://github.com/code-423n4/2022-07-fractional/blob/e2c5a962a94106f9495eb96769d7f60f7d5b14c9/src/Vault.sol#L32

[G‑19] Use custom errors rather than revert()/require() strings to save gas

Custom errors are available from solidity version 0.8.4. Custom errors save ~50 gas each time they're hit by avoiding having to allocate and store the revert string. Not defining the strings also save deployment gas

There are 5 instances of this issue:

File: src/FERC1155.sol

263           require(
264               msg.sender == _from ||
265                   isApprovedForAll[_from][msg.sender] ||
266                   isApproved[_from][msg.sender][_id],
267               "NOT_AUTHORIZED"
268:          );

275           require(
276               _to.code.length == 0
277                   ? _to != address(0)
278                   : INFTReceiver(_to).onERC1155Received(
279                       msg.sender,
280                       _from,
281                       _id,
282                       _amount,
283                       _data
284                   ) == INFTReceiver.onERC1155Received.selector,
285               "UNSAFE_RECIPIENT"
286:          );

297:          require(metadata[_id] != address(0), "NO METADATA");

https://github.com/code-423n4/2022-07-fractional/blob/e2c5a962a94106f9495eb96769d7f60f7d5b14c9/src/FERC1155.sol#L263-L268

File: src/utils/MerkleBase.sol

62:           require(_data.length > 1, "wont generate root for single leaf");

78:           require(_data.length > 1, "wont generate proof for single leaf");

https://github.com/code-423n4/2022-07-fractional/blob/e2c5a962a94106f9495eb96769d7f60f7d5b14c9/src/utils/MerkleBase.sol#L62

[G‑20] Functions guaranteed to revert when called by normal users can be marked payable

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. The extra opcodes avoided are CALLVALUE(2),DUP1(3),ISZERO(3),PUSH2(3),JUMPI(10),PUSH1(3),DUP1(3),REVERT(0),JUMPDEST(1),POP(2), which costs an average of about 21 gas per call to the function, in addition to the extra deployment cost

There are 6 instances of this issue:

File: src/FERC1155.sol

56        function burn(
57            address _from,
58            uint256 _id,
59            uint256 _amount
60:       ) external onlyRegistry {

79        function mint(
80            address _to,
81            uint256 _id,
82            uint256 _amount,
83            bytes memory _data
84:       ) external onlyRegistry {

198:      function setContractURI(string calldata _uri) external onlyController {

205       function setMetadata(address _metadata, uint256 _id)
206           external
207:          onlyController

217       function setRoyalties(
218           uint256 _id,
219           address _receiver,
220           uint256 _percentage
221:      ) external onlyController {

229       function transferController(address _newController)
230           external
231:          onlyController

https://github.com/code-423n4/2022-07-fractional/blob/e2c5a962a94106f9495eb96769d7f60f7d5b14c9/src/FERC1155.sol#L56-L60

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