Basin - Rolezn's results

A composable EVM-native decentralized exchange protocol.

General Information

Platform: Code4rena

Start Date: 03/07/2023

Pot Size: $40,000 USDC

Total HM: 14

Participants: 74

Period: 7 days

Judge: alcueca

Total Solo HM: 9

Id: 259

League: ETH

Basin

Findings Distribution

Researcher Performance

Rank: 28/74

Findings: 2

Award: $64.54

QA:
grade-b
Gas:
grade-a

🌟 Selected for report: 0

🚀 Solo Findings: 0

QA Summary<a name="QA Summary">

Low Risk Issues

IssueContexts
LOW‑1Consider the case where lpTokenSupply is 02
LOW‑2Draft Import Dependencies1
LOW‑3Function can risk gas exhaustion on large receipt calls due to multiple mandatory loops1
LOW‑4Missing Contract-existence Checks Before Low-level Calls1
LOW‑5safeTransfer function does not check for contract existence1
LOW‑6Solidity version 0.8.20 may not work on other chains due to PUSH00
LOW‑7Consider case where the variable will not be set and will default to zero1

Total: 7 contexts over 7 issues

Non-critical Issues

IssueContexts
NC‑1Consider adding a deny-list1
NC‑2No need to initialize uints to zero1
NC‑3Use SMTChecker1

Total: 3 contexts over 3 issues

Low Risk Issues

<a href="#qa-summary">[LOW‑1]</a><a name="LOW&#x2011;1"> Consider the case where lpTokenSupply is 0

Consider the case where lpTokenSupply is 0. When lpTokenSupply is 0, it should return 0 directly, because there will be an error of dividing by 0.

<ins>Impact</ins>

This would cause the affected functions to revert and as a result can lead to potential loss.

<ins>Proof Of Concept</ins>
File: ProportionalLPToken2.sol

22: underlyingAmounts[0] = lpTokenAmount * reserves[0] / lpTokenSupply;
23: underlyingAmounts[1] = lpTokenAmount * reserves[1] / lpTokenSupply;

https://github.com/code-423n4/2023-07-basin/tree/main/src/functions/ProportionalLPToken2.sol#L22-L23

<ins>Recommended Mitigation Steps</ins>

Add check for zero value and return 0.

if ( lpTokenSupply == 0) return 0;

<a href="#qa-summary">[LOW‑2]</a><a name="LOW&#x2011;2"> Draft Import Dependencies

Contracts are importing draft dependencies. These imported contracts are still a draft and are not considered ready for mainnet use and have not received adequate security auditing or are liable to change with future development.

<ins>Proof Of Concept</ins>
File: Well.sol

6: import {ERC20Upgradeable, ERC20PermitUpgradeable} from "ozu/token/ERC20/extensions/draft-ERC20PermitUpgradeable.sol";

https://github.com/code-423n4/2023-07-basin/tree/main/src/Well.sol#L6

<ins>Recommended Mitigation Steps</ins>

Stop using draft imported contracts and use their release version if it exists.

<a href="#qa-summary">[LOW‑3]</a><a name="LOW&#x2011;3"> Function can risk gas exhaustion on large receipt calls due to multiple mandatory loops

The function contains loops over arrays that the user cannot influence. In any call for the function, users spend gas to iterate over the array. There are loops in the following functions that iterate all array values. Which could risk gas exhaustion on large array calls.

This risk is small, but could be lessened somewhat by allowing separate calls for small loops. Consider allowing partial calls via array arguments, or detecting expensive call bundles and only partially iterating over them. Since the logic already filters out calls, this would make the later loops smaller.

<ins>Proof Of Concept</ins>
<details>
File: MultiFlowPump.sol

285: function _readCumulativeReserves(address well) internal view returns (bytes16[] memory cumulativeReserves) {
        bytes32 slot = _getSlotForAddress(well);
        uint256[] memory reserves = IWell(well).getReserves();
        (uint8 numberOfReserves, uint40 lastTimestamp, bytes16[] memory lastReserves) = slot.readLastReserves();
        if (numberOfReserves == 0) {
            revert NotInitialized();
        }
        uint256 offset = _getSlotsOffset(numberOfReserves) << 1;
        assembly {
            slot := add(slot, offset)
        }
        cumulativeReserves = slot.readBytes16(numberOfReserves);
        uint256 deltaTimestamp = _getDeltaTimestamp(lastTimestamp);
        bytes16 deltaTimestampBytes = deltaTimestamp.fromUInt();
        bytes16 blocksPassed = (deltaTimestamp / BLOCK_TIME).fromUInt();
        
        for (uint256 i; i < cumulativeReserves.length; ++i) {
            lastReserves[i] = _capReserve(lastReserves[i], reserves[i].fromUIntToLog2(), blocksPassed);
            cumulativeReserves[i] = cumulativeReserves[i].add(lastReserves[i].mul(deltaTimestampBytes));
        }
    }

https://github.com/code-423n4/2023-07-basin/tree/main/src/pumps/MultiFlowPump.sol#L285

</details>

<a href="#qa-summary">[LOW‑4]</a><a name="LOW&#x2011;4"> Missing Contract-existence Checks Before Low-level Calls

Low-level calls return success if there is no code present at the specified address.

<ins>Proof Of Concept</ins>
File: Aquifer.sol

55: (bool success, bytes memory returnData) = well.call(initFunctionCall);

https://github.com/code-423n4/2023-07-basin/tree/main/src/Aquifer.sol#L55

<ins>Recommended Mitigation Steps</ins>

In addition to the zero-address checks, add a check to verify that <address>.code.length > 0

<a href="#qa-summary">[LOW‑5]</a><a name="LOW&#x2011;5"> safeTransfer function does not check for contract existence

According to the implementation of the of the library's safeTransfer function, the contract existences are not checked. It is possible that a token becomes non-existent in the future; for instance, if some bugs are found for the output token, it is possible that the output token will be destroyed through calling its selfdestruct function after migrating its data to another contract for fixing these bugs. When this happens, such output token becomes non-existent but calling the swap function to swap for it does not revert. As a result, the user can lose tokens

<ins>Proof Of Concept</ins>
File: Well.sol

774: function _safeTransferFromFeeOnTransfer(
        IERC20 token,
        address from,
        uint256 amount
    ) internal returns (uint256 amountTransferred) {
        uint256 balanceBefore = token.balanceOf(address(this));
        token.safeTransferFrom(from, address(this), amount);
        amountTransferred = token.balanceOf(address(this)) - balanceBefore;
    }

https://github.com/code-423n4/2023-07-basin/tree/main/src/Well.sol#L774

<ins>Recommended Mitigation Steps</ins>

In the library's safeTransfer function, the existence of the contract for token should be checked before performing the low-level call. If such contract does not exist, calling this function should revert.

<a href="#qa-summary">[LOW‑6]</a><a name="LOW&#x2011;6"> Solidity version 0.8.20 may not work on other chains due to PUSH0

The compiler for Solidity 0.8.20 switches the default target EVM version to Shanghai, which includes the new PUSH0 op code. This op code may not yet be implemented on all L2s, so deployment on these chains will fail. To work around this issue, use an earlier EVM version. While the project itself may or may not compile with 0.8.20, other projects with which it integrates, or which extend this project may, and those projects will have problems deploying these contracts/libraries.

<ins>Proof Of Concept</ins>
<details>
File: Aquifer.sol

pragma solidity ^0.8.17;

https://github.com/code-423n4/2023-07-basin/tree/main/src/Aquifer.sol#L3

File: Well.sol

pragma solidity ^0.8.17;

https://github.com/code-423n4/2023-07-basin/tree/main/src/Well.sol#L3

File: ConstantProduct2.sol

pragma solidity ^0.8.17;

https://github.com/code-423n4/2023-07-basin/tree/main/src/functions/ConstantProduct2.sol#L3

File: ProportionalLPToken2.sol

pragma solidity ^0.8.17;

https://github.com/code-423n4/2023-07-basin/tree/main/src/functions/ProportionalLPToken2.sol#L3

File: LibBytes.sol

pragma solidity ^0.8.17;

https://github.com/code-423n4/2023-07-basin/tree/main/src/libraries/LibBytes.sol#L3

File: LibBytes16.sol

pragma solidity ^0.8.17;

https://github.com/code-423n4/2023-07-basin/tree/main/src/libraries/LibBytes16.sol#L3

File: LibContractInfo.sol

pragma solidity ^0.8.17;

https://github.com/code-423n4/2023-07-basin/tree/main/src/libraries/LibContractInfo.sol#L3

File: LibLastReserveBytes.sol

pragma solidity ^0.8.17;

https://github.com/code-423n4/2023-07-basin/tree/main/src/libraries/LibLastReserveBytes.sol#L3

File: LibWellConstructor.sol

pragma solidity ^0.8.17;

https://github.com/code-423n4/2023-07-basin/tree/main/src/libraries/LibWellConstructor.sol#L4

File: MultiFlowPump.sol

pragma solidity ^0.8.17;

https://github.com/code-423n4/2023-07-basin/tree/main/src/pumps/MultiFlowPump.sol#L3

</details>

<a href="#qa-summary">[LOW‑7]</a><a name="LOW&#x2011;7"> Consider case where the variable will not be set and will default to zero

As per the _init function in the MultiFlowPump.sol contract will just continue its logic even though reserves was not set (0 value) as there is no revert case when reserves is equal to 0

<ins>Proof Of Concept</ins>
<details>
File: MultiFlowPump.sol

146: function _init(bytes32 slot, uint40 lastTimestamp, uint256[] memory reserves) internal {
        uint256 numberOfReserves = reserves.length;
        bytes16[] memory byteReserves = new bytes16[](numberOfReserves);

        // Skip {_capReserve} since we have no prior reference

        for (uint256 i; i < numberOfReserves; ++i) {
            if (reserves[i] == 0) return;
            byteReserves[i] = reserves[i].fromUIntToLog2();
        }

        // Write: Last Timestamp & Last Reserves
        slot.storeLastReserves(lastTimestamp, byteReserves);

        // Write: EMA Reserves
        // Start at the slot after `byteReserves`
        uint256 numSlots = _getSlotsOffset(byteReserves.length);
        assembly {
            slot := add(slot, numSlots)
        }
        slot.storeBytes16(byteReserves); // EMA Reserves
    }

https://github.com/code-423n4/2023-07-basin/tree/main/src/pumps/MultiFlowPump.sol#L146

Non Critical Issues

<a href="#qa-summary">[NC‑1]</a><a name="NC&#x2011;1"> Consider adding a deny-list

Doing so will significantly increase centralization, but will help to prevent hackers from using stolen tokens

<ins>Proof Of Concept</ins>
File: Well.sol

22: contract Well is ERC20PermitUpgradeable, IWell, IWellErrors, ReentrancyGuardUpgradeable, ClonePlus

https://github.com/code-423n4/2023-07-basin/tree/main/src/Well.sol#L22

<a href="#qa-summary">[NC‑2]</a><a name="NC&#x2011;2"> No need to initialize uints to zero

There is no need to initialize uint variables to zero as their default value is 0

<ins>Proof Of Concept</ins>
File: Well.sol

77: uint256 constant LOC_AQUIFER_ADDR = 0;

https://github.com/code-423n4/2023-07-basin/tree/main/src/Well.sol#L77

<a href="#qa-summary">[NC‑3]</a><a name="NC&#x2011;3"> Use SMTChecker

The highest tier of smart contract behavior assurance is formal mathematical verification. All assertions that are made are guaranteed to be true across all inputs → The quality of your asserts is the quality of your verification

https://twitter.com/0xOwenThurm/status/1614359896350425088?t=dbG9gHFigBX85Rv29lOjIQ&s=19

#0 - c4-pre-sort

2023-07-13T14:56:48Z

141345 marked the issue as low quality report

#1 - c4-judge

2023-08-04T21:14:36Z

alcueca marked the issue as grade-b

Awards

58.4732 USDC - $58.47

Labels

bug
G (Gas Optimization)
grade-a
high quality report
sponsor acknowledged
G-21

External Links

GAS Summary<a name="GAS Summary">

Gas Optimizations

IssueContextsEstimated Gas Saved
GAS‑1Consider activating via-ir for deploying1250
GAS‑2Use assembly to emit events8304
GAS‑3Counting down in for statements is more gas efficient338481
GAS‑4Empty Blocks Should Be Removed Or Emit Something333
GAS‑5Using delete statement can save gas18
GAS‑6Use assembly to write address storage values174
GAS‑7Multiple accesses of a mapping/array should use a local variable cache2160
GAS‑8Using XOR (^) and AND (&) bitwise equivalents26338

Total: 75 contexts over 8 issues

Gas Optimizations

<a href="#gas-summary">[GAS‑1]</a><a name="GAS&#x2011;1"> Consider activating via-ir for deploying

The IR-based code generator was introduced with an aim to not only allow code generation to be more transparent and auditable but also to enable more powerful optimization passes that span across functions.

You can enable it on the command line using --via-ir or with the option {"viaIR": true}.

This will take longer to compile, but you can just simple test it before deploying and if you got a better benchmark then you can add --via-ir to your deploy command

More on: https://docs.soliditylang.org/en/v0.8.17/ir-breaking-changes.html

<a href="#gas-summary">[GAS‑2]</a><a name="GAS&#x2011;2"> Use assembly to emit events

We can use assembly to emit events efficiently by utilizing scratch space and the free memory pointer. This will allow us to potentially avoid memory expansion costs. Note: In order to do this optimization safely, we will need to cache and restore the free memory pointer.

For example, for a generic emit event for eventSentAmountExample:

// uint256 id, uint256 value, uint256 amount
emit eventSentAmountExample(id, value, amount);

We can use the following assembly emit events:

        assembly {
            let memptr := mload(0x40)
            mstore(0x00, calldataload(0x44))
            mstore(0x20, calldataload(0xa4))
            mstore(0x40, amount)
            log1(
                0x00,
                0x60,
                // keccak256("eventSentAmountExample(uint256,uint256,uint256)")
                0xa622cf392588fbf2cd020ff96b2f4ebd9c76d7a4bc7f3e6b2f18012312e76bc3
            )
            mstore(0x40, memptr)
        }
<ins>Proof Of Concept</ins>
<details>
File: Well.sol

238: emit Swap(fromToken, toToken, amountIn, amountOut, recipient);

https://github.com/code-423n4/2023-07-basin/tree/main/src/Well.sol#L238

File: Well.sol

305: emit Swap(fromToken, toToken, amountIn, amountOut, recipient);

https://github.com/code-423n4/2023-07-basin/tree/main/src/Well.sol#L305

File: Well.sol

373: emit Shift(reserves, tokenOut, amountOut, recipient);

https://github.com/code-423n4/2023-07-basin/tree/main/src/Well.sol#L373

File: Well.sol

443: emit AddLiquidity(tokenAmountsIn, lpAmountOut, recipient);

https://github.com/code-423n4/2023-07-basin/tree/main/src/Well.sol#L443

File: Well.sol

482: emit RemoveLiquidity(lpAmountIn, tokenAmountsOut, recipient);

https://github.com/code-423n4/2023-07-basin/tree/main/src/Well.sol#L482

File: Well.sol

516: emit RemoveLiquidityOneToken(lpAmountIn, tokenOut, tokenAmountOut, recipient);

https://github.com/code-423n4/2023-07-basin/tree/main/src/Well.sol#L516

File: Well.sol

569: emit RemoveLiquidity(lpAmountIn, tokenAmountsOut, recipient);

https://github.com/code-423n4/2023-07-basin/tree/main/src/Well.sol#L569

File: Well.sol

597: emit Sync(reserves);

https://github.com/code-423n4/2023-07-basin/tree/main/src/Well.sol#L597

</details>

<a href="#gas-summary">[GAS‑3]</a><a name="GAS&#x2011;3"> Counting down in for statements is more gas efficient

Counting down is more gas efficient than counting up because neither we are making zero variable to non-zero variable and also we will get gas refund in the last transaction when making non-zero to zero variable.

<ins>Proof Of Concept</ins>
<details>
File: Well.sol

36: for (uint256 i; i < _tokens.length - 1; ++i) {
37: for (uint256 j = i + 1; j < _tokens.length; ++j) {

https://github.com/code-423n4/2023-07-basin/tree/main/src/Well.sol#L36-L37

File: Well.sol

101: for (uint256 i; i < _pumps.length; i++) {

https://github.com/code-423n4/2023-07-basin/tree/main/src/Well.sol#L101

File: Well.sol

363: for (uint256 i; i < _tokens.length; ++i) {

https://github.com/code-423n4/2023-07-basin/tree/main/src/Well.sol#L363

File: Well.sol

382: for (uint256 i; i < _tokens.length; ++i) {

https://github.com/code-423n4/2023-07-basin/tree/main/src/Well.sol#L382

File: Well.sol

423: for (uint256 i; i < _tokens.length; ++i) {
423: for (uint256 i; i < _tokens.length; ++i) {

https://github.com/code-423n4/2023-07-basin/tree/main/src/Well.sol#L423

https://github.com/code-423n4/2023-07-basin/tree/main/src/Well.sol#L423

File: Well.sol

452: for (uint256 i; i < _tokens.length; ++i) {

https://github.com/code-423n4/2023-07-basin/tree/main/src/Well.sol#L452

File: Well.sol

473: for (uint256 i; i < _tokens.length; ++i) {

https://github.com/code-423n4/2023-07-basin/tree/main/src/Well.sol#L473

File: Well.sol

557: for (uint256 i; i < _tokens.length; ++i) {

https://github.com/code-423n4/2023-07-basin/tree/main/src/Well.sol#L557

File: Well.sol

579: for (uint256 i; i < _tokens.length; ++i) {

https://github.com/code-423n4/2023-07-basin/tree/main/src/Well.sol#L579

File: Well.sol

593: for (uint256 i; i < _tokens.length; ++i) {

https://github.com/code-423n4/2023-07-basin/tree/main/src/Well.sol#L593

File: Well.sol

607: for (uint256 i; i < _tokens.length; ++i) {

https://github.com/code-423n4/2023-07-basin/tree/main/src/Well.sol#L607

File: Well.sol

633: for (uint256 i; i < reserves.length; ++i) {

https://github.com/code-423n4/2023-07-basin/tree/main/src/Well.sol#L633

File: Well.sol

662: for (uint256 i; i < _pumps.length; ++i) {

https://github.com/code-423n4/2023-07-basin/tree/main/src/Well.sol#L662

File: Well.sol

738: for (uint256 k; k < _tokens.length; ++k) {

https://github.com/code-423n4/2023-07-basin/tree/main/src/Well.sol#L738

File: Well.sol

760: for (j; j < _tokens.length; ++j) {

https://github.com/code-423n4/2023-07-basin/tree/main/src/Well.sol#L760

File: LibBytes.sol

48: for (uint256 i; i < maxI; ++i) {

https://github.com/code-423n4/2023-07-basin/tree/main/src/libraries/LibBytes.sol#L48

File: LibBytes.sol

92: for (uint256 i = 1; i <= n; ++i) {

https://github.com/code-423n4/2023-07-basin/tree/main/src/libraries/LibBytes.sol#L92

File: LibBytes16.sol

28: for (uint256 i; i < maxI; ++i) {

https://github.com/code-423n4/2023-07-basin/tree/main/src/libraries/LibBytes16.sol#L28

File: LibBytes16.sol

69: for (uint256 i = 1; i <= n; ++i) {

https://github.com/code-423n4/2023-07-basin/tree/main/src/libraries/LibBytes16.sol#L69

File: LibLastReserveBytes.sol

41: for (uint256 i = 1; i < maxI; ++i) {

https://github.com/code-423n4/2023-07-basin/tree/main/src/libraries/LibLastReserveBytes.sol#L41

File: LibLastReserveBytes.sol

93: for (uint256 i = 3; i <= n; ++i) {

https://github.com/code-423n4/2023-07-basin/tree/main/src/libraries/LibLastReserveBytes.sol#L93

File: LibWellConstructor.sol

43: for (uint256 i; i < _pumps.length; ++i) {

https://github.com/code-423n4/2023-07-basin/tree/main/src/libraries/LibWellConstructor.sol#L43

File: LibWellConstructor.sol

73: for (uint256 i = 1; i < _tokens.length; ++i) {

https://github.com/code-423n4/2023-07-basin/tree/main/src/libraries/LibWellConstructor.sol#L73

File: MultiFlowPump.sol

84: for (uint256 i; i < numberOfReserves; ++i) {
84: for (uint256 i; i < numberOfReserves; ++i) {

https://github.com/code-423n4/2023-07-basin/tree/main/src/pumps/MultiFlowPump.sol#L84

https://github.com/code-423n4/2023-07-basin/tree/main/src/pumps/MultiFlowPump.sol#L84

File: MultiFlowPump.sol

152: for (uint256 i; i < numberOfReserves; ++i) {

https://github.com/code-423n4/2023-07-basin/tree/main/src/pumps/MultiFlowPump.sol#L152

File: MultiFlowPump.sol

178: for (uint256 i; i < numberOfReserves; ++i) {

https://github.com/code-423n4/2023-07-basin/tree/main/src/pumps/MultiFlowPump.sol#L178

File: MultiFlowPump.sol

234: for (uint256 i; i < numberOfReserves; ++i) {

https://github.com/code-423n4/2023-07-basin/tree/main/src/pumps/MultiFlowPump.sol#L234

File: MultiFlowPump.sol

255: for (uint256 i; i < numberOfReserves; ++i) {

https://github.com/code-423n4/2023-07-basin/tree/main/src/pumps/MultiFlowPump.sol#L255

File: MultiFlowPump.sol

301: for (uint256 i; i < cumulativeReserves.length; ++i) {

https://github.com/code-423n4/2023-07-basin/tree/main/src/pumps/MultiFlowPump.sol#L301

File: MultiFlowPump.sol

322: for (uint256 i; i < byteCumulativeReserves.length; ++i) {

https://github.com/code-423n4/2023-07-basin/tree/main/src/pumps/MultiFlowPump.sol#L322

</details>
Test Code
contract GasTest is DSTest { Contract0 c0; Contract1 c1; function setUp() public { c0 = new Contract0(); c1 = new Contract1(); } function testGas() public { c0.AddNum(); c1.AddNum(); } } contract Contract0 { uint256 num = 3; function AddNum() public { uint256 _num = num; for(uint i=0;i<=9;i++){ _num = _num +1; } num = _num; } } contract Contract1 { uint256 num = 3; function AddNum() public { uint256 _num = num; for(uint i=9;i>=0;i--){ _num = _num +1; } num = _num; } }
Gas Test Report
Contract0 contract
Deployment CostDeployment Size
77011311
Function Nameminavgmedianmax# calls
AddNum70407040704070401
Contract1 contract
Deployment CostDeployment Size
73811295
Function Nameminavgmedianmax# calls
AddNum38193819381938191

<a href="#gas-summary">[GAS‑4]</a><a name="GAS&#x2011;4"> 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{...}})

<ins>Proof Of Concept</ins>
File: Well.sol

114: function wellData() public pure returns (bytes memory) {}

https://github.com/code-423n4/2023-07-basin/tree/main/src/Well.sol#L114

File: Well.sol

656: try IPump(_pump.target).update(reserves, _pump.data) {}

https://github.com/code-423n4/2023-07-basin/tree/main/src/Well.sol#L656

File: Well.sol

664: try IPump(_pumps[i].target).update(reserves, _pumps[i].data) {}

https://github.com/code-423n4/2023-07-basin/tree/main/src/Well.sol#L664

<a href="#gas-summary">[GAS‑5]</a><a name="GAS&#x2011;5"> Using delete statement can save gas

<ins>Proof Of Concept</ins>
File: Well.sol

77: uint256 constant LOC_AQUIFER_ADDR = 0;

https://github.com/code-423n4/2023-07-basin/tree/main/src/Well.sol#L77

<a href="#gas-summary">[GAS‑6]</a><a name="GAS&#x2011;6"> Use assembly to write address storage values

<ins>Proof Of Concept</ins>
File: LibBytes.sol

24: _bytes = ZERO_BYTES;

https://github.com/code-423n4/2023-07-basin/tree/main/src/libraries/LibBytes.sol#L24

<a href="#gas-summary">[GAS‑7]</a><a name="GAS&#x2011;7"> Multiple accesses of a mapping/array should use a local variable cache

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 perform the same offset calculation every time. Help the Optimizer by saving a storage variable's reference instead of repeatedly fetching it

To help the optimizer,declare a storage type variable and use it instead of repeatedly fetching the reference in a map or an array. As an example, instead of repeatedly calling someMap[someIndex], save its reference like this: SomeStruct storage someStruct = someMap[someIndex] and use it.

<ins>Proof Of Concept</ins>
<details>
File: LibWellConstructor.sol

74: name = string.concat(name, ":", LibContractInfo.getSymbol(address(_tokens[i])));
75: symbol = string.concat(symbol, LibContractInfo.getSymbol(address(_tokens[i])));

https://github.com/code-423n4/2023-07-basin/tree/main/src/libraries/LibWellConstructor.sol#L74-L75

</details>

<a href="#gas-summary">[GAS‑8]</a><a name="GAS&#x2011;8"> Using XOR (^) and AND (&) bitwise equivalents

Given 4 variables a, b, c and d represented as such:

0 0 0 0 0 1 1 0 <- a 0 1 1 0 0 1 1 0 <- b 0 0 0 0 0 0 0 0 <- c 1 1 1 1 1 1 1 1 <- d

To have a == b means that every 0 and 1 match on both variables. Meaning that a XOR (operator ^) would evaluate to 0 ((a ^ b) == 0), as it excludes by definition any equalities.Now, if a != b, this means that there’s at least somewhere a 1 and a 0 not matching between a and b, making (a ^ b) != 0.Both formulas are logically equivalent and using the XOR bitwise operator costs actually the same amount of gas.However, it is much cheaper to use the bitwise OR operator (|) than comparing the truthy or falsy values.These are logically equivalent too, as the OR bitwise operator (|) would result in a 1 somewhere if any value is not 0 between the XOR (^) statements, meaning if any XOR (^) statement verifies that its arguments are different.

<ins>Proof Of Concept</ins>
<details>
File: Well.sol

739: if (iToken == _tokens[k]) {
742: } else if (jToken == _tokens[k]) {

https://github.com/code-423n4/2023-07-basin/tree/main/src/Well.sol#L739

https://github.com/code-423n4/2023-07-basin/tree/main/src/Well.sol#L742

File: Well.sol

761: if (jToken == _tokens[j]) {

https://github.com/code-423n4/2023-07-basin/tree/main/src/Well.sol#L761

File: ConstantProduct2.sol

66: reserve = LibMath.roundUpDiv(reserve, reserves[j == 1 ? 0 : 1] * EXP_PRECISION);

https://github.com/code-423n4/2023-07-basin/tree/main/src/functions/ConstantProduct2.sol#L66

File: ConstantProduct2.sol

85: uint256 i = j == 1 ? 0 : 1;

https://github.com/code-423n4/2023-07-basin/tree/main/src/functions/ConstantProduct2.sol#L85

File: ConstantProduct2.sol

98: uint256 i = j == 1 ? 0 : 1;

https://github.com/code-423n4/2023-07-basin/tree/main/src/functions/ConstantProduct2.sol#L98

File: LibBytes.sol

39: if (reserves.length == 2) {
62: if (reserves.length & 1 == 1) {

https://github.com/code-423n4/2023-07-basin/tree/main/src/libraries/LibBytes.sol#L39

https://github.com/code-423n4/2023-07-basin/tree/main/src/libraries/LibBytes.sol#L62

File: LibBytes.sol

83: if (n == 2) {
98: if (i & 1 == 1) {

https://github.com/code-423n4/2023-07-basin/tree/main/src/libraries/LibBytes.sol#L83

https://github.com/code-423n4/2023-07-basin/tree/main/src/libraries/LibBytes.sol#L98

File: LibBytes16.sol

21: if (reserves.length == 2) {
40: if (reserves.length & 1 == 1) {

https://github.com/code-423n4/2023-07-basin/tree/main/src/libraries/LibBytes16.sol#L21

https://github.com/code-423n4/2023-07-basin/tree/main/src/libraries/LibBytes16.sol#L40

File: LibBytes16.sol

60: if (n == 2) {
75: if (i & 1 == 1) {

https://github.com/code-423n4/2023-07-basin/tree/main/src/libraries/LibBytes16.sol#L60

https://github.com/code-423n4/2023-07-basin/tree/main/src/libraries/LibBytes16.sol#L75

File: LibLastReserveBytes.sol

22: if (n == 1) {
53: if (reserves.length & 1 == 1) {

https://github.com/code-423n4/2023-07-basin/tree/main/src/libraries/LibLastReserveBytes.sol#L22

https://github.com/code-423n4/2023-07-basin/tree/main/src/libraries/LibLastReserveBytes.sol#L53

File: LibLastReserveBytes.sol

80: if (n == 0) return (n, lastTimestamp, reserves);
86: if (n == 1) return (n, lastTimestamp, reserves);
99: if (i & 1 == 1) {

https://github.com/code-423n4/2023-07-basin/tree/main/src/libraries/LibLastReserveBytes.sol#L80

https://github.com/code-423n4/2023-07-basin/tree/main/src/libraries/LibLastReserveBytes.sol#L86

https://github.com/code-423n4/2023-07-basin/tree/main/src/libraries/LibLastReserveBytes.sol#L99

File: MultiFlowPump.sol

83: if (pumpState.lastTimestamp == 0) {

https://github.com/code-423n4/2023-07-basin/tree/main/src/pumps/MultiFlowPump.sol#L83

File: MultiFlowPump.sol

174: if (numberOfReserves == 0) {

https://github.com/code-423n4/2023-07-basin/tree/main/src/pumps/MultiFlowPump.sol#L174

File: MultiFlowPump.sol

225: if (numberOfReserves == 0) {

https://github.com/code-423n4/2023-07-basin/tree/main/src/pumps/MultiFlowPump.sol#L225

File: MultiFlowPump.sol

243: if (numberOfReserves == 0) {

https://github.com/code-423n4/2023-07-basin/tree/main/src/pumps/MultiFlowPump.sol#L243

File: MultiFlowPump.sol

270: if (numberOfReserves == 0) {

https://github.com/code-423n4/2023-07-basin/tree/main/src/pumps/MultiFlowPump.sol#L270

File: MultiFlowPump.sol

289: if (numberOfReserves == 0) {

https://github.com/code-423n4/2023-07-basin/tree/main/src/pumps/MultiFlowPump.sol#L289

File: MultiFlowPump.sol

319: if (deltaTimestamp == bytes16(0)) {

https://github.com/code-423n4/2023-07-basin/tree/main/src/pumps/MultiFlowPump.sol#L319

</details>
Test Code
contract GasTest is DSTest { Contract0 c0; Contract1 c1; function setUp() public { c0 = new Contract0(); c1 = new Contract1(); } function testGas() public { c0.not_optimized(1,2); c1.optimized(1,2); } } contract Contract0 { function not_optimized(uint8 a,uint8 b) public returns(bool){ return ((a==1) || (b==1)); } } contract Contract1 { function optimized(uint8 a,uint8 b) public returns(bool){ return ((a ^ 1) & (b ^ 1)) == 0; } }
Gas Test Report
Contract0 contract
Deployment CostDeployment Size
46099261
Function Nameminavgmedianmax# calls
not_optimized4564564564561
Contract1 contract
Deployment CostDeployment Size
42493243
Function Nameminavgmedianmax# calls
optimized4304304304301

#0 - c4-pre-sort

2023-07-12T09:41:13Z

141345 marked the issue as high quality report

#1 - c4-sponsor

2023-07-17T20:12:18Z

publiuss marked the issue as sponsor confirmed

#2 - c4-sponsor

2023-08-03T22:43:39Z

publiuss marked the issue as sponsor acknowledged

#3 - c4-judge

2023-08-05T11:07:35Z

alcueca marked the issue as grade-b

#4 - c4-judge

2023-08-05T11:07:51Z

alcueca marked the issue as grade-a

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