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
Rank: 62/141
Findings: 3
Award: $107.33
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: IllIllI
Also found by: 0x1f8b, 0x29A, Amithuddar, Avci, BowTiedWardens, Kthere, Limbooo, MEP, Ruhum, StyxRave, TomJ, Treasure-Seeker, TrungOre, Tutturu, Waze, bardamu, c3phas, cccz, codexploder, cryptphi, hake, horsefacts, hyh, oyc_109, pashov, peritoflores, scaraven, simon135, slywaters, sseefried, tofunmi, xiaoming90
1.3977 USDC - $1.40
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
It is recommended to avoid using payable.transfer, since it can cause the transaction to fail when the user is accessing this function with a smart contract and:
Reference: https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/
./modules/Migration.sol:172: payable(msg.sender).transfer(ethAmount); ./modules/Migration.sol:325: payable(msg.sender).transfer(userEth);
Manual Analysis
I recommend using low-level call() or OpenZeppelin Address.sendValue instead of transfer().
#0 - mehtaculous
2022-07-19T21:50:55Z
Duplicate of #325
#1 - HardlyDifficult
2022-07-28T15:47:37Z
Duping to #504
🌟 Selected for report: xiaoming90
Also found by: 0x1f8b, 0x29A, 0x52, 0xA5DF, 0xDjango, 0xNazgul, 0xNineDec, 0xf15ers, 0xsanson, 0xsolstars, 242, 8olidity, Amithuddar, Aymen0909, Bnke0x0, BowTiedWardens, David_, Deivitto, ElKu, Funen, Hawkeye, IllIllI, JC, Kaiziron, Keen_Sheen, Kthere, Kulk0, Kumpa, Lambda, MEP, ReyAdmirado, Rohan16, Ruhum, Sm4rty, TomJ, Tomio, Treasure-Seeker, TrungOre, Tutturu, Viksaa39, Waze, _Adam, __141345__, ak1, apostle0x01, asutorufos, async, ayeslick, aysha, bbrho, benbaessler, berndartmueller, c3phas, cccz, chatch, cloudjunky, codexploder, cryptphi, delfin454000, dipp, durianSausage, dy, exd0tpy, fatherOfBlocks, hake, hansfriese, horsefacts, hubble, joestakey, jonatascm, kebabsec, kenzo, kyteg, mektigboy, neumo, oyc_109, pashov, pedr02b2, peritoflores, rajatbeladiya, rbserver, robee, rokinot, s3cunda, sach1r0, sahar, sashik_eth, scaraven, shenwilly, simon135, sorrynotsorry, sseefried, svskaushik, unforgiven, z3s, zzzitron
62.1299 USDC - $62.13
 
Below 3 functions shown in the PoC is external function which calls transferFrom / safeTransferFrom with unauthorized "_from" parameter. It is recommended to hard code "_from" parameter to msg.sender.
batchDepositERC20() of BaseVault.sol https://github.com/code-423n4/2022-07-fractional/blob/e2c5a962a94106f9495eb96769d7f60f7d5b14c9/src/modules/protoforms/BaseVault.sol#L65
batchDepositERC721() of BaseVault.sol https://github.com/code-423n4/2022-07-fractional/blob/e2c5a962a94106f9495eb96769d7f60f7d5b14c9/src/modules/protoforms/BaseVault.sol#L84
batchDepositERC1155() of BaseVault.sol https://github.com/code-423n4/2022-07-fractional/blob/e2c5a962a94106f9495eb96769d7f60f7d5b14c9/src/modules/protoforms/BaseVault.sol#L108-L114
I recommend to hard code "_from" parameter of transferFrom / safeTransferFrom to msg.sender.
 
High privilege account such as admin / owner is changed with only single process. This can be a concern since an admin / owner role has a high privilege in the contract and mistakenly setting a new admin to an incorrect address will end up losing that privilege.
93: function transferOwnership(address _newOwner) external { 94: if (owner != msg.sender) revert NotOwner(owner, msg.sender); 95: owner = _newOwner; 96: emit TransferOwnership(msg.sender, _newOwner); 97: }
229: function transferController(address _newController) 230: external 231: onlyController 232: { 233: if (_newController == address(0)) revert ZeroAddress(); 234: _controller = _newController; 235: emit ControllerTransferred(_newController); 236: }
This can be fixed by implementing 2-step process. We can do this by following. First make the function that sets high privilege account approve a new address as a pending admin / owner. Next that pending admin / owner has to claim the ownership in a separate transaction to be a new admin / owner.
 
I recommend adding check of 0-address for immutable addresses. Not doing so might lead to non-functional contract when it is updated to 0-address accidentally.
Total of 3 issues found
Add 0-address check for above 3 immutable addresses.
 
Questions/Issues in the code should be resolved before the deployment.
./src/utils/MerkleBase.sol:24: // TODO: This can be aesthetically simplified with a switch. Not sure it will
Resolve before deployment and delete the comment.
 
Each event should have 3 indexed fields if there are 3 or more fields.
./src/interfaces/IERC20.sol:6-10: event Approval(address indexed _owner, address indexed _spender, uint256 _amount); ./src/interfaces/IERC20.sol:11: event Transfer(address indexed _from, address indexed _to, uint256 amount); ./src/interfaces/IERC1155.sol:6: event ApprovalForAll(address indexed _owner, address indexed _operator, bool _approved); ./src/interfaces/IERC1155.sol:25: event URI(string _value, uint256 indexed _id); ./src/interfaces/IVault.sol:25: event Execute(address indexed _target, bytes _data, bytes _response); ./src/interfaces/IVault.sol:29: event InstallPlugin(bytes4[] _selectors, address[] _plugins); ./src/interfaces/IVault.sol:39: event UninstallPlugin(bytes4[] _selectors); ./src/interfaces/IBuyout.sol:55: event Start(address indexed _vault, address indexed _proposer, uint256 _startTime, uint256 _buyoutPrice, uint256 _fractionPrice); ./src/interfaces/IBuyout.sol:65: event SellFractions(address indexed _seller, uint256 _amount); ./src/interfaces/IBuyout.sol:69: event BuyFractions(address indexed _buyer, uint256 _amount); ./src/interfaces/IBuyout.sol:74: event End(address _vault, State _state, address indexed _proposer); ./src/interfaces/IBuyout.sol:79: event Cash(address _vault, address indexed _casher, uint256 _amount); ./src/interfaces/IBuyout.sol:83: event Redeem(address _vault, address indexed _redeemer); ./src/interfaces/IERC721.sol:11: event ApprovalForAll(address indexed _owner, address indexed _operator, bool _approved); ./src/interfaces/IVaultRegistry.sol:33: event VaultDeployed(address indexed _vault, address indexed _token, uint256 _id); ./src/interfaces/IBaseVault.sol:12: event ActiveModules(address indexed _vault, address[] _modules); ./src/interfaces/IMigration.sol:61: event FractionsMigrated(address indexed _oldVault, address indexed _newVault, uint256 _proposalId, uint256 _amount); ./src/interfaces/IMigration.sol:74: event VaultMigrated(address indexed _oldVault, address indexed _newVault, uint256 _proposalId, address[] _modules, address[] _plugins, bytes4[] _selectors); ./src/interfaces/IFERC1155.sol:21: event SetMetadata(address indexed _metadata, uint256 _id); ./src/interfaces/IFERC1155.sol:26: event SetRoyalty(address indexed _receiver, uint256 _id, uint256 _percentage); ./src/interfaces/IFERC1155.sol:36: event SingleApproval(address indexed _owner, address indexed _operator, uint256 _id, bool _approved);
Add up to 3 indexed fields when possible.
 
#0 - HardlyDifficult
2022-07-26T18:40:51Z
Merging with #327
🌟 Selected for report: joestakey
Also found by: 0x1f8b, 0x29A, 0xA5DF, 0xKitsune, 0xNazgul, 0xNineDec, 0xalpharush, 0xkatana, 0xsanson, 0xsolstars, 8olidity, Avci, Bnke0x0, BowTiedWardens, Chom, Deivitto, ElKu, Fitraldys, Funen, IllIllI, JC, Kaiziron, Lambda, Limbooo, MEP, NoamYakov, PwnedNoMore, RedOneN, ReyAdmirado, Rohan16, Ruhum, Saintcode_, Sm4rty, TomJ, Tomio, TrungOre, Tutturu, Waze, _Adam, __141345__, ajtra, apostle0x01, asutorufos, benbaessler, brgltd, c3phas, codexploder, cryptphi, delfin454000, dharma09, djxploit, durianSausage, fatherOfBlocks, giovannidisiena, gogo, horsefacts, hrishibhat, hyh, ignacio, jocxyen, jonatascm, karanctf, kebabsec, kyteg, m_Rassska, mektigboy, oyc_109, pedr02b2, rbserver, robee, rokinot, sach1r0, sashik_eth, simon135, slywaters
43.798 USDC - $43.80
 
SLOADs cost 100 gas where MLOADs/MSTOREs cost only 3 gas. Whenever function reads storage value more than once, it should be cached to save gas.
Cache _controller of function controller 2 SLOADs to 1 SLOAD, 1 MSTORE and 1 MLOAD https://github.com/code-423n4/2022-07-fractional/blob/e2c5a962a94106f9495eb96769d7f60f7d5b14c9/src/FERC1155.sol#L303 https://github.com/code-423n4/2022-07-fractional/blob/e2c5a962a94106f9495eb96769d7f60f7d5b14c9/src/FERC1155.sol#L305
Cache metadata of function emitSetURI 2 SLOADs to 1 SLOAD, 1 MSTORE and 1 MLOAD https://github.com/code-423n4/2022-07-fractional/blob/e2c5a962a94106f9495eb96769d7f60f7d5b14c9/src/FERC1155.sol#L69-L70
Cache metadata of function uri 2 SLOADs to 1 SLOAD, 1 MSTORE and 1 MLOAD https://github.com/code-423n4/2022-07-fractional/blob/e2c5a962a94106f9495eb96769d7f60f7d5b14c9/src/FERC1155.sol#L297-L298
 
When certain state variable is read more than once, cache it to local variable to save gas.
 
Certain variables is defined even though they are used only once. Remove these unnecessary variables to save gas. For cases where it will reduce the readability, one can use comments to help describe what the code is doing.
39: address plugin = methods[msg.sig]; 40: (,response) = _execute(plugin, _data);
Mitigation: Delete line 39 and replace line 40 with below line
(,response) = _execute(methods[msg.sig], _data);
Don't define variable that is used only once. Details are listed on above PoC.
 
Certain function is defined even though it is called only once. Inline it instead to where it is called to avoid usage of extra gas.
_revertedWithReason() of Vault.sol _revertedWithReason function called only once at line 137 https://github.com/code-423n4/2022-07-fractional/blob/e2c5a962a94106f9495eb96769d7f60f7d5b14c9/src/Vault.sol#L142-L146 https://github.com/code-423n4/2022-07-fractional/blob/e2c5a962a94106f9495eb96769d7f60f7d5b14c9/src/Vault.sol#L137
_revertedWithReason() of Multicall.sol _revertedWithReason function called only once at line 25 https://github.com/code-423n4/2022-07-fractional/blob/e2c5a962a94106f9495eb96769d7f60f7d5b14c9/src/utils/Multicall.sol#L39-L43 https://github.com/code-423n4/2022-07-fractional/blob/e2c5a962a94106f9495eb96769d7f60f7d5b14c9/src/utils/Multicall.sol#L25
_computePermitStructHash() of FERC1155.sol _computePermitStructHash function called only once at line 113 https://github.com/code-423n4/2022-07-fractional/blob/e2c5a962a94106f9495eb96769d7f60f7d5b14c9/src/FERC1155.sol#L324-L342 https://github.com/code-423n4/2022-07-fractional/blob/e2c5a962a94106f9495eb96769d7f60f7d5b14c9/src/FERC1155.sol#L113
_computePermitAllStructHash() of FERC1155.sol _computePermitAllStructHash function called only once at line 159 https://github.com/code-423n4/2022-07-fractional/blob/e2c5a962a94106f9495eb96769d7f60f7d5b14c9/src/FERC1155.sol#L350-L366 https://github.com/code-423n4/2022-07-fractional/blob/e2c5a962a94106f9495eb96769d7f60f7d5b14c9/src/FERC1155.sol#L159-L164
_attemptETHTransfer() of SafeSend.sol _attemptETHTransfer function called only once at line 31 https://github.com/code-423n4/2022-07-fractional/blob/e2c5a962a94106f9495eb96769d7f60f7d5b14c9/src/utils/SafeSend.sol#L18-L25 https://github.com/code-423n4/2022-07-fractional/blob/e2c5a962a94106f9495eb96769d7f60f7d5b14c9/src/utils/SafeSend.sol#L31
I recommend to not define above functions and instead inline it to place it is called.
 
Below require / revert checks are used more than once, I recommend making these to a modifier or a function to save gas.
Total of 11 of these issue was found.
./src/FERC1155.sol:108-109: if (block.timestamp > _deadline) revert SignatureExpired(block.timestamp, _deadline); ./src/FERC1155.sol:154-155: if (block.timestamp > _deadline) revert SignatureExpired(block.timestamp, _deadline);
./src/FERC1155.sol:128-129: if (signer == address(0) || signer != _owner) revert InvalidSignature(signer, _owner); ./src/FERC1155.sol:173-174: if (signer == address(0) || signer != _owner) revert InvalidSignature(signer, _owner);
./src/Vault.sol:76: if (owner != msg.sender) revert NotOwner(owner, msg.sender); ./src/Vault.sol:87: if (owner != msg.sender) revert NotOwner(owner, msg.sender); ./src/Vault.sol:94: if (owner != msg.sender) revert NotOwner(owner, msg.sender); ./src/Vault.sol:102: if (owner != msg.sender) revert NotOwner(owner, msg.sender);
./src/modules/Buyout.sol:64: if (id == 0) revert NotVault(_vault); ./src/modules/Buyout.sol:117: if (id == 0) revert NotVault(_vault); ./src/modules/Buyout.sol:154: if (id == 0) revert NotVault(_vault); ./src/modules/Buyout.sol:189: if (id == 0) revert NotVault(_vault); ./src/modules/Buyout.sol:249: if (id == 0) revert NotVault(_vault); ./src/modules/Buyout.sol:281: if (id == 0) revert NotVault(_vault); ./src/modules/Buyout.sol:320: if (id == 0) revert NotVault(_vault); ./src/modules/Buyout.sol:352: if (id == 0) revert NotVault(_vault); ./src/modules/Buyout.sol:388: if (id == 0) revert NotVault(_vault); ./src/modules/Buyout.sol:425: if (id == 0) revert NotVault(_vault);
./src/modules/Buyout.sol:68: if (current != required) revert InvalidState(required, current); ./src/modules/Buyout.sol:122: if (current != required) revert InvalidState(required, current); ./src/modules/Buyout.sol:159: if (current != required) revert InvalidState(required, current); ./src/modules/Buyout.sol:200: if (current != required) revert InvalidState(required, current); ./src/modules/Buyout.sol:253: if (current != required) revert InvalidState(required, current); ./src/modules/Buyout.sol:285: if (current != required) revert InvalidState(required, current); ./src/modules/Buyout.sol:324: if (current != required) revert InvalidState(required, current); ./src/modules/Buyout.sol:356: if (current != required) revert InvalidState(required, current); ./src/modules/Buyout.sol:392: if (current != required) revert InvalidState(required, current); ./src/modules/Buyout.sol:429: if (current != required) revert InvalidState(required, current);
./src/modules/Buyout.sol:126: revert TimeExpired(block.timestamp, endTime); ./src/modules/Buyout.sol:163: revert TimeExpired(block.timestamp, endTime);
./src/modules/Buyout.sol:326: if (msg.sender != proposer) revert NotWinner(); ./src/modules/Buyout.sol:358: if (msg.sender != proposer) revert NotWinner(); ./src/modules/Buyout.sol:394: if (msg.sender != proposer) revert NotWinner(); ./src/modules/Buyout.sol:431: if (msg.sender != proposer) revert NotWinner();
./src/modules/Migration.sol:82: if (id == 0) revert NotVault(_vault); ./src/modules/Migration.sol:114: if (id == 0) revert NotVault(_vault); ./src/modules/Migration.sol:146: if (id == 0) revert NotVault(_vault); ./src/modules/Migration.sol:187: if (id == 0) revert NotVault(_vault); ./src/modules/Migration.sol:299: if (id == 0) revert NotVault(_vault); ./src/modules/Migration.sol:436: if (id == 0) revert NotVault(_vault);
./src/modules/Migration.sol:86: if (current != required) revert IBuyout.InvalidState(required, current); ./src/modules/Migration.sol:118: if (current != required) revert IBuyout.InvalidState(required, current); ./src/modules/Migration.sol:150: if (current != required) revert IBuyout.InvalidState(required, current); ./src/modules/Migration.sol:191: if (current != required) revert IBuyout.InvalidState(required, current); ./src/modules/Migration.sol:442: if (current != required) revert IBuyout.InvalidState(required, current);
./src/modules/Migration.sol:226: if (current != State.SUCCESS) revert UnsuccessfulMigration(); ./src/modules/Migration.sol:264: if (current != State.SUCCESS) revert UnsuccessfulMigration();
./src/VaultRegistry.sol:42: if (id == 0) revert UnregisteredVault(msg.sender); ./src/VaultRegistry.sol:120: if (id == 0) revert UnregisteredVault(msg.sender);
I recommend making duplicate require statement into modifier or a function.
 
It is cheaper gas to use calldata than memory if the function parameter is read only. Calldata is a non-modifiable, non-persistent area where function arguments are stored, and behaves mostly like memory. More details on following link. link: https://docs.soliditylang.org/en/v0.8.15/types.html#data-location
./src/FERC1155.sol:68: function emitSetURI(uint256 _id, string memory _uri) external { ./src/FERC1155.sol:83: bytes memory _data ./src/FERC1155.sol:261: bytes memory _data ./src/utils/Metadata.sol:24: function setURI(uint256 _id, string memory _uri) external { ./src/utils/Multicall.sol:39: function _revertedWithReason(bytes memory _response) internal pure { ./src/utils/MerkleBase.sol:45: bytes32[] memory _proof, ./src/utils/MerkleBase.sol:61: function getRoot(bytes32[] memory _data) public pure returns (bytes32) { ./src/utils/MerkleBase.sol:73: function getProof(bytes32[] memory _data, uint256 _node) ./src/utils/MerkleBase.sol:125: function hashLevel(bytes32[] memory _data) ./src/Vault.sol:73: function install(bytes4[] memory _selectors, address[] memory _plugins) ./src/Vault.sol:101: function uninstall(bytes4[] memory _selectors) external { ./src/Vault.sol:142: function _revertedWithReason(bytes memory _response) internal pure { ./src/modules/Migration.sol:487: function generateMerkleTree(address[] memory _modules) ./src/VaultRegistry.sol:53: address[] memory _plugins, ./src/VaultRegistry.sol:54: bytes4[] memory _selectors ./src/VaultRegistry.sol:70: address[] memory _plugins, ./src/VaultRegistry.sol:71: bytes4[] memory _selectors ./src/VaultRegistry.sol:85: address[] memory _plugins, ./src/VaultRegistry.sol:86: bytes4[] memory _selectors ./src/VaultRegistry.sol:105: address[] memory _plugins, ./src/VaultRegistry.sol:106: bytes4[] memory _selectors ./src/VaultRegistry.sol:150: address[] memory _plugins, ./src/VaultRegistry.sol:151: bytes4[] memory _selectors ./src/VaultRegistry.sol:168: address[] memory _plugins, ./src/VaultRegistry.sol:169: bytes4[] memory _selectors ./src/interfaces/IERC1155.sol:29: function balanceOfBatch(address[] memory _owners, uint256[] memory ids) ./src/interfaces/IERC1155.sol:39: uint256[] memory _ids, ./src/interfaces/IERC1155.sol:40: uint256[] memory _amounts, ./src/interfaces/IERC1155.sol:41: bytes memory _data ./src/interfaces/IERC1155.sol:49: bytes memory _data ./src/interfaces/IVault.sol:43: bytes memory _data, ./src/interfaces/IVault.sol:44: bytes32[] memory _proof ./src/interfaces/IVault.sol:49: function install(bytes4[] memory _selectors, address[] memory _plugins) ./src/interfaces/IBuyout.sol:93: uint256[] memory _ids, ./src/interfaces/IBuyout.sol:94: uint256[] memory _values, ./src/interfaces/IBuyout.sol:95: bytes32[] memory _erc1155BatchTransferProof ./src/interfaces/IBuyout.sol:140: bytes32[] memory _erc20TransferProof ./src/interfaces/IBuyout.sol:148: bytes32[] memory _erc721TransferProof ./src/interfaces/IBuyout.sol:157: bytes32[] memory _erc1155TransferProof ./src/interfaces/IERC721.sol:44: bytes memory _data ./src/interfaces/IVaultRegistry.sol:43: address[] memory _plugins, ./src/interfaces/IVaultRegistry.sol:44: bytes4[] memory _selectors ./src/interfaces/IVaultRegistry.sol:49: address[] memory _plugins, ./src/interfaces/IVaultRegistry.sol:50: bytes4[] memory _selectors ./src/interfaces/IVaultRegistry.sol:56: address[] memory _plugins, ./src/interfaces/IVaultRegistry.sol:57: bytes4[] memory _selectors ./src/interfaces/IVaultRegistry.sol:63: address[] memory _plugins, ./src/interfaces/IVaultRegistry.sol:64: bytes4[] memory _selectors ./src/interfaces/IVaultRegistry.sol:70: address[] memory _plugins, ./src/interfaces/IVaultRegistry.sol:71: bytes4[] memory _selectors ./src/interfaces/IBaseVault.sol:17: address[] memory _tokens, ./src/interfaces/IBaseVault.sol:18: uint256[] memory _amounts ./src/interfaces/IBaseVault.sol:24: address[] memory _tokens, ./src/interfaces/IBaseVault.sol:25: uint256[] memory _ids ./src/interfaces/IBaseVault.sol:31: address[] memory _tokens, ./src/interfaces/IBaseVault.sol:32: uint256[] memory _ids, ./src/interfaces/IBaseVault.sol:33: uint256[] memory _amounts, ./src/interfaces/IBaseVault.sol:34: bytes[] memory _datas ./src/interfaces/IBaseVault.sol:39: address[] memory _modules, ./src/interfaces/IBaseVault.sol:42: bytes32[] memory _mintProof ./src/interfaces/IMigration.sol:89: uint256[] memory _ids, ./src/interfaces/IMigration.sol:90: uint256[] memory _amounts, ./src/interfaces/IMigration.sol:91: bytes32[] memory _erc1155BatchTransferProof ./src/interfaces/IMigration.sol:100: function generateMerkleTree(address[] memory _modules) ./src/interfaces/IMigration.sol:120: bytes32[] memory _erc20TransferProof ./src/interfaces/IMigration.sol:128: bytes32[] memory _erc721TransferProof ./src/interfaces/IMigration.sol:150: address[] memory _modules, ./src/interfaces/IMigration.sol:151: address[] memory _plugins, ./src/interfaces/IMigration.sol:152: bytes4[] memory _selectors, ./src/interfaces/IMigration.sol:162: bytes32[] memory _mintProof ./src/interfaces/IFERC1155.sol:75: bytes memory _data ./src/interfaces/IFERC1155.sol:111: bytes memory _data
Change memory to calldata
 
When using constant it is expected that the value should be converted into a constant value at compile time. However when using a call to keccak256(), the expression is re-calculated each time the constant is referenced. Resulting in costing about 100 gas more on each access to this constant. link for more details: https://github.com/ethereum/solidity/issues/9232
./src/constants/Permit.sol:5-7:bytes32 constant DOMAIN_TYPEHASH = keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"); ./src/constants/Permit.sol:10-12:bytes32 constant PERMIT_TYPEHASH = keccak256("Permit(address owner,address operator,uint256 tokenId,bool approved,uint256 nonce,uint256 deadline)"); ./src/constants/Permit.sol:15-17:bytes32 constant PERMIT_ALL_TYPEHASH = keccak256("PermitAll(address owner,address operator,bool approved,uint256 nonce,uint256 deadline)");
Change the variable from constant to immutable.
 
Since EVM operates on 32 bytes at a time, it acctually cost more gas to use elements smaller than 32 bytes. Reference: https://docs.soliditylang.org/en/v0.8.15/internals/layout_in_storage.html
./src/FERC1155.sol:104: uint8 _v, ./src/FERC1155.sol:150: uint8 _v, ./src/utils/SelfPermit.sol:23: uint8 _v, ./src/utils/SelfPermit.sol:50: uint8 _v, ./src/interfaces/IERC20.sol:21: function decimals() external view returns (uint8); ./src/interfaces/IERC20.sol:32: uint8 _v, ./src/interfaces/IFERC1155.sol:86: uint8 _v, ./src/interfaces/IFERC1155.sol:96: uint8 _v,
I suggest using uint256 instead of anything smaller.
 
When variable is not initialized, it will have its default values. For example, 0 for uint, false for bool and address(0) for address. Reference: https://docs.soliditylang.org/en/v0.8.15/control-structures.html#scoping-and-declarations
./src/utils/MerkleBase.sol:51: for (uint256 i = 0; i < _proof.length; ++i) { ./src/Vault.sol:78: for (uint256 i = 0; i < length; i++) { ./src/Vault.sol:104: for (uint256 i = 0; i < length; i++) { ./src/modules/protoforms/BaseVault.sol:64: for (uint256 i = 0; i < _tokens.length; ) { ./src/modules/protoforms/BaseVault.sol:83: for (uint256 i = 0; i < _tokens.length; ) { ./src/modules/protoforms/BaseVault.sol:107: for (uint256 i = 0; i < _tokens.length; ++i) {
I suggest removing default value initialization. For example,
 
By storing an array's length as a variable before the for-loop, can save 3 gas per iteration.
./src/utils/MerkleBase.sol:51: for (uint256 i = 0; i < _proof.length; ++i) { ./src/utils/MerkleBase.sol:110: for (uint256 i; i < result.length; ++i) { ./src/modules/Buyout.sol:454: for (uint256 i; i < permissions.length; ) { ./src/modules/protoforms/BaseVault.sol:64: for (uint256 i = 0; i < _tokens.length; ) { ./src/modules/protoforms/BaseVault.sol:83: for (uint256 i = 0; i < _tokens.length; ) { ./src/modules/protoforms/BaseVault.sol:107: for (uint256 i = 0; i < _tokens.length; ++i) { ./src/modules/protoforms/BaseVault.sol:130: for (uint256 i; i < _modules.length; ++i) { ./src/modules/protoforms/BaseVault.sol:132: for (uint256 j; j < leaves.length; ++j) {
Store array's length as a variable before looping it. For example, I suggest changing it to
uint256 length = _proof.length for (uint256 i = 0; i < length; ++i) {
 
Prefix increments/decrements (++i or --i) costs cheaper gas than postfix increment/decrements (i++ or i--).
./src/FERC1155.sol:339: nonces[_owner]++, ./src/FERC1155.sol:363: nonces[_owner]++, ./src/utils/MerkleBase.sol:188: ceil++; ./src/Vault.sol:78: for (uint256 i = 0; i < length; i++) { ./src/Vault.sol:104: for (uint256 i = 0; i < length; i++) { ./src/modules/protoforms/BaseVault.sol:133: hashes[counter++] = leaves[j]; ./src/modules/Migration.sol:508: hashes[counter++] = leaves[j];
Change it to postfix increments/decrements. For example,
for (uint256 i = 0; i < length; ++i) {
 
Since each storage slot is size of 32 bytes, error messages that is longer than this will need extra storage slot leading to more gas cost.
./src/utils/MerkleBase.sol:62: require(_data.length > 1, "wont generate root for single leaf"); ./src/utils/MerkleBase.sol:78: require(_data.length > 1, "wont generate proof for single leaf");
Simply keep the error messages within 32 bytes to avoid extra storage slot cost.
 
Custom errors from Solidity 0.8.4 are cheaper than revert strings. Details are explained here: https://blog.soliditylang.org/2021/04/21/custom-errors/
./src/FERC1155.sol:263-268: require(msg.sender == _from || isApprovedForAll[_from][msg.sender] || isApproved[_from][msg.sender][_id], "NOT_AUTHORIZED"); ./src/FERC1155.sol:275-286: require(_to.code.length == 0 ? _to != address(0) : INFTReceiver(_to).onERC1155Received(msg.sender, _from, _id, _amount, _data) == INFTReceiver.onERC1155Received.selector, "UNSAFE_RECIPIENT"); ./src/FERC1155.sol:297: require(metadata[_id] != address(0), "NO METADATA"); ./src/utils/MerkleBase.sol:62: require(_data.length > 1, "wont generate root for single leaf"); ./src/utils/MerkleBase.sol:78: require(_data.length > 1, "wont generate proof for single leaf");
I suggest implementing custom errors to save gas.