Althea Liquid Infrastructure - 0x11singh99's results

Liquid Infrastructure.

General Information

Platform: Code4rena

Start Date: 13/02/2024

Pot Size: $24,500 USDC

Total HM: 5

Participants: 84

Period: 6 days

Judge: 0xA5DF

Id: 331

League: ETH

Althea

Findings Distribution

Researcher Performance

Rank: 25/84

Findings: 1

Award: $151.16

🌟 Selected for report: 1

🚀 Solo Findings: 0

Findings Information

🌟 Selected for report: 0x11singh99

Also found by: 0xSmartContractSamurai, c3phas, dharma09

Labels

bug
G (Gas Optimization)
grade-a
selected for report
sufficient quality report
G-03

Awards

151.1628 USDC - $151.16

External Links

Gas Optimizations

Note : G-02 and G-04 contains only those instances which were missed by bot.

[G-01] Cache erc20EntitlementPerUnit and distributableERC20s storage arrays in memory outside of nested for loops to avoid multiple Gwarmsload for every i value.

Since two for loops are nested here. So internal for loop will run distributableERC20s.length times for every i value. That means every element in distributableERC20s and distributableERC20s.length elements in erc20EntitlementPerUnit array will be accessed for limit - nextDistributionRecipient times ie. for each i value.

By caching those arrays outside of both loops can result in accessing those array's distributableERC20s.length no. of starting elements only one times instead of multiple times.

It can save total 2 * limit - nextDistributionRecipient - 1 * distributableERC20s.length SLOAD. Which can save ~100 Gas (( 97 Gas technically)) on avoiding each SLOAD when accessed after first time . Since each SLOAD cost 100 Gas while each MLOAD takes only 3 Gas.

Total Gas Saved: 2 * limit - nextDistributionRecipient - 1 * distributableERC20s.length * 97 Gas will be saved.

File : contracts/LiquidInfrastructureERC20.sol

214:        for (i = nextDistributionRecipient; i < limit; i++) {
215:            address recipient = holders[i];
216:            if (isApprovedHolder(recipient)) {
217:                uint256[] memory receipts = new uint256[](
218:                    distributableERC20s.length
219:                );
220:                for (uint j = 0; j < distributableERC20s.length; j++) {
221:                    IERC20 toDistribute = IERC20(distributableERC20s[j]);
                      //@audit cache `erc20EntitlementPerUnit` and `distributableERC20s` outside from here and access here from memory
222:                    uint256 entitlement = erc20EntitlementPerUnit[j] *
223:                        this.balanceOf(recipient);
224:                    if (toDistribute.transfer(recipient, entitlement)) {
225:                        receipts[j] = entitlement;
226:                    }
227:                }
228:
229:                emit Distribution(recipient, distributableERC20s, receipts);
230:            }
231:        }

Recommended Mitigation Steps:

+           uint256[] memory m_erc20EntitlementPerUnit = new uint256[](distributableERC20s.length);
+           address[] memory m_distributableERC20s = new address[](distributableERC20s.length);
+           m_erc20EntitlementPerUnit = erc20EntitlementPerUnit;
+           m_distributableERC20s = distributableERC20s;

214:        for (i = nextDistributionRecipient; i < limit; i++) {
215:            address recipient = holders[i];
216:            if (isApprovedHolder(recipient)) {
217:                uint256[] memory receipts = new uint256[](
218:                    distributableERC20s.length
219:                );
220:                for (uint j = 0; j < distributableERC20s.length; j++) {
- 221:                    IERC20 toDistribute = IERC20(distributableERC20s[j]);
- 222:                    uint256 entitlement = erc20EntitlementPerUnit[j] *
+ 221:                    IERC20 toDistribute = IERC20(m_distributableERC20s[j]);
+ 222:                    uint256 entitlement = m_erc20EntitlementPerUnit[j] *
223:                        this.balanceOf(recipient);
224:                    if (toDistribute.transfer(recipient, entitlement)) {
225:                        receipts[j] = entitlement;
226:                    }
227:                }
228:
229:                emit Distribution(recipient, distributableERC20s, receipts);
230:            }
231:        }

LiquidInfrastructureERC20.sol#L214C1-L231C10

[G-02] State variables can be packed into fewer storage slot (saves 2000 Gas)(instance Missed by bot)

Total Gas Saved: 2000 GAS, 1 SLOT

If variables occupying the same slot are both written the same function or by the constructor, avoids a separate Gsset (20000 gas). Reads of the variables can also be cheaper when accessed in same transaction.

LastDistribution and LockedForDistribution can be packed in single slot SAVES: 2000 GAS, 1 Storage SLOTs

LastDistribution This can be reduced to uint48 since it holds block number so uint48 is more than sufficient to hold any realistic block number of any blockchain.

Note: It only include instance which was missed by bot. MinDistributionPeriod already covered in bot report. So it doesn't included here. While LastDistribution reducing size is not covered in bot report. And it is major gas saving so it is covered here.

File : contracts/LiquidInfrastructureERC20.sol

70:    uint256 public LastDistribution;

75:    uint256 public MinDistributionPeriod;

80:    bool public LockedForDistribution;

LiquidInfrastructureERC20.sol#L70-L80

Recommended Mitigation Steps:

File : contracts/LiquidInfrastructureERC20.sol

-70:    uint256 public LastDistribution;


75:     uint256 public MinDistributionPeriod;

+       uint48 public LastDistribution;
80:     bool public LockedForDistribution;

[G-03] Unnecessary nonReentrant modifier checks in functions(saves ~24000 GAS)

SAVES ~24000 GAS when every time respective function called

Remove the unnecessary nonReentrant modifier since this function does not contain any external calls, and it is not invoked within the _mint function flow. This function mint not called again in _mint flow also not in overridden _beforeTokenTransfer and _afterTokenTransfer.So there is no chance of reentrancy in this context. The _mint function, which is part of the OpenZeppelin library, includes hooks before and after token transfer, and they do not involve any external calls in their overridden functions.and _mint already doesn't have any external call. Removing the nonReentrant modifier results in a gas saving of approximately ~24700 gas.

File : contracts/LiquidInfrastructureERC20.sol

318: function mint(
319:    address account,
320:    uint256 amount
321:   ) public onlyOwner nonReentrant {
322:     _mint(account, amount);
323:    }

LiquidInfrastructureERC20.sol#L318C4-L323C6

Recommended Mitigation Steps:

File : contracts/LiquidInfrastructureERC20.sol

318: function mint(
319:    address account,
320:    uint256 amount
-321:   ) public onlyOwner nonReentrant {
+321:   ) public onlyOwner  {
322:     _mint(account, amount);
323:    }

[G-04] Use already cached value instead of re-reading from storage( instance Missed by bot)

Since reading state var. from storage takes 100 Gas in other consecutive after first access. So to avoid 1 SLOAD we can use already cached value.

Note: It only include those instances which were missed by bot

Total Gas Saved : ~100 Gas

File : liquid-infrastructure/contracts/LiquidInfrastructureERC20.sol

184:    function distributeToAllHolders() public {
185:          uint256 num = holders.length;
186:          if (num > 0) {
187:              distribute(holders.length);
188:          }
189:      }

LiquidInfrastructureERC20.sol#L184C4-L189C6

Recommended Mitigation Steps:

File : liquid-infrastructure/contracts/LiquidInfrastructureERC20.sol

    function distributeToAllHolders() public {
        uint256 num = holders.length;
        if (num > 0) {
-            distribute(holders.length);
+            distribute(num);
        }
    }

[G-05] No need to inherit ERC721 again

Since OwnableApprovableERC721 already inherits ERC721 so no need to inherit ERC721 again in LiquidInfrastructureNFT contract.

20: abstract contract OwnableApprovableERC721 is Context, ERC721 {

There is no need to explicitly inherit ERC721 again in this context. Additionally removing the unnecessary import of ERC721 above will streamline the code.

File : contracts/LiquidInfrastructureNFT.sol

5:  import "@openzeppelin/contracts/token/ERC721/ERC721.sol";

...

33: contract LiquidInfrastructureNFT is ERC721, OwnableApprovableERC721 {

LiquidInfrastructureNFT.sol#L5C1-L33C70 , OwnableApprovableERC721.sol#L20

Recommended Mitigation steps:

File : contracts/LiquidInfrastructureNFT.sol

-5:  import "@openzeppelin/contracts/token/ERC721/ERC721.sol";

...

-33: contract LiquidInfrastructureNFT is ERC721, OwnableApprovableERC721 {
+33: contract LiquidInfrastructureNFT is  OwnableApprovableERC721 {

[G-06] Remove unnecessary require statements

Instance#1

Saves ~115 Gas on every time _beginDistribution function called.

Since _beginDistribution function called only once inside distribute when LockedForDistribution is false. So LockedForDistribution will always be false when _beginDistribution will be called. So there is no requirement of require check in _beginDistribution starting at line 258. It can save 1 SLOAD (~100 Gas) + 1 NOT opcode(~3 gas) + 1 require check which will always be true(~15 Gas).

File : contracts/LiquidInfrastructureERC20.sol

      function distribute(uint256 numDistributions) public nonReentrant {
200:        if (!LockedForDistribution) {
201:            require(
202:                _isPastMinDistributionPeriod(),
203:                "MinDistributionPeriod not met"
204:            );
205:            _beginDistribution();
206:        }

Remove the below require statement

File : contracts/LiquidInfrastructureERC20.sol

257:    function _beginDistribution() internal {
258:        require(
259:            !LockedForDistribution,
260:            "cannot begin distribution when already locked"
263:        );//@audit gas remove this unnecessary require
264:        LockedForDistribution = true;

LiquidInfrastructureERC20.sol#L200C1-L206C10 , LiquidInfrastructureERC20.sol#L257C5-L262C38

Instance#2

require will never revert here so remove it. It doesn't have any utility here only wasting little gas in executing and always pass.

File : contracts/LiquidInfrastructureERC20.sol

431: require(true, "unable to find released NFT in ManagedNFTs");

LiquidInfrastructureERC20.sol#L431

File : contracts/LiquidInfrastructureERC20.sol

- 431: require(true, "unable to find released NFT in ManagedNFTs");

#0 - c4-pre-sort

2024-02-22T17:49:14Z

0xRobocop marked the issue as sufficient quality report

#1 - c4-judge

2024-03-08T14:34:36Z

0xA5DF marked the issue as grade-b

#2 - c4-judge

2024-03-08T16:08:59Z

0xA5DF marked the issue as selected for report

#3 - c4-judge

2024-03-08T16:09:20Z

0xA5DF marked the issue as grade-a

#4 - thenua3bhai

2024-03-10T07:13:37Z

Hi @0xA5DF Thanks for Judging. I request you to please re-evaluate gas saved by G-03 in this report. Since it saves significant amount of gas(avg. total ~2596 GAS) instead of currently stated (~100 GAS) in judging sheet. Majorly gas(~2396) will be saved every time when LiquidInfrastructureERC20::mint function will be called after removing unnecessary nonReentrant modifier from it.

Since removing nonReentrant modifier from LiquidInfrastructureERC20::mint function will save both deployment gas and execution gas every time mint function is called not only on deployment. But in judging sheet gas saved by G-03 is mentioned only ~100 GAS. But removing nonReentrant modifier from mint function will save Total avg. of ~2596 GAS considering your method including 1% of deployment gas(1% of ~20000 deployment gas saved -> ~200 GAS) and calling mint function will also save ~2396 GAS every time this function is called. I have stated gas in report by adding both deployment gas + execution gas, but you are considering only 1% of deployment gas. So even if taking 1% of deployment gas and total execution gas(~2396 GAS) every time mint function is called. Total avg. gas saved will be ~2596 by G-03. I have calculated this on sepolia network. I have explained this in report that reentrancy is not possible in mint function so we can safely remove nonReentrant modifier from mint function. Since in complete logic flow of this mint function no external call is present not even cross-function. It is not possible to re-enter in mint function.

I am flagging this here even though being selected for report. Since it will make total avg. gas saved by my report from ~7.5k to ~10.1k. Which can change the grade of other grade-a reports to b or c. If same formula you have used in QA to decide other QA reports grades The threshold are 80% of best for grade A, and 60% of best for grade B. will be applied here 2 of the currently grade-a Gas reports can be grade-b and one can be grade-c. Or any other curve you have used to decide other gas reports grade can be used to re-calculate the other 3 reports grade.

Thanks

#5 - 0xA5DF

2024-03-10T09:06:57Z

As you can see in the note in the jduging sheet - this is because this comes at the cost of a security feature. Normally gas optimizations don't affect the functionality of the code. In this case it does, it removes the reentrancy protection, so I don't think it can stand in the same line with normal gas optimizations. I still gave it 100 points because the devs might consider that.

#6 - thenua3bhai

2024-03-10T10:43:36Z

Thanks for your reply. I think it will be completely safe to remove it at no security risk since there is no chance of re-entrancy in that function(reason explained in report). I have checked the logic flow in that function. It is purely removed for significant gas saving purpose at no security/logic risk. I think it can be considered as gas optimization. Thanks

#7 - 0xA5DF

2024-03-10T14:30:49Z

I'm aware of that (otherwise I wouldn't credit the 100 either), but you can never be 100% sure that you don't need that. The reentrancy protection is there to add some additional security, and removing that can't be considered as a normal gas optimization.

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