Basin - K42'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: 21/74

Findings: 2

Award: $111.57

Gas:
grade-a
Analysis:
grade-b

🌟 Selected for report: 0

🚀 Solo Findings: 0

Awards

58.4732 USDC - $58.47

Labels

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

External Links

Gas (mana) Optimization Report by K42

Possible Optimization in MultiFlowPump.sol

Possible Optimization = location -- In the update function, the reserves[i] > 0 ? reserves[i] : 1 operation is performed for each element in the reserves array. Each comparison and ternary operation consumes 3 and 10 gas respectively. We can reduce the gas consumption by iterating through the reserves array once at the beginning of the function and replacing any 0 values with 1, like so function update(uint256[] calldata reserves, bytes calldata) external { uint256 numberOfReserves = reserves.length; for (uint256 i = 0; i < numberOfReserves; ++i) { if (reserves[i] == 0) { reserves[i] = 1; } } // rest of the function... }

  • Estimated gas saved = 13 gas per element in the reserves array. The exact amount of gas saved will depend on the size of the reserves array and the frequency of calls to the update function. If this function is called frequently and the reserves array is large, the gas savings could be significant.

Possible Optimizations in Well.sol

Possible Optimization 1 = location -- In the init function, the tokens() function is called, which returns an array of tokens. This array is then iterated over twice in a nested loop to check for duplicate tokens. Each function call and array iteration consumes gas. We can reduce the gas consumption by using a mapping to check for duplicates instead of a nested loop, like so function init(string memory name, string memory symbol) public initializer { __ERC20Permit_init(name); __ERC20_init(name, symbol); IERC20[] memory _tokens = tokens(); mapping(IERC20 => bool) seen; for (uint256 i; i < _tokens.length; ++i) { if (seen[_tokens[i]]) { revert DuplicateTokens(_tokens[i]); } seen[_tokens[i]] = true; } }

  • Estimated gas saved = Around 2000-3000 gas per call to the init function, depending on the number of tokens.

Possible Optimization 2 = location1 and location2 -- In the swapFrom and swapFromFeeOnTransfer functions, the fromToken.safeTransferFrom(msg.sender, address(this), amountIn); operation is performed. This operation is expensive in terms of gas as it involves an external function call. We can reduce the gas consumption by checking if the msg.sender is the contract itself. If it is, we can skip the safeTransferFrom call as it would be unnecessary. This can be done like so function swapFrom( IERC20 fromToken, IERC20 toToken, uint256 amountIn, uint256 minAmountOut, address recipient, uint256 deadline ) external nonReentrant expire(deadline) returns (uint256 amountOut) { if (msg.sender != address(this)) { fromToken.safeTransferFrom(msg.sender, address(this), amountIn); } amountOut = _swapFrom(fromToken, toToken, amountIn, minAmountOut, recipient); }

  • Estimated gas saved = Around 2000-3000 gas per call to the swapFrom and swapFromFeeOnTransfer functions. The exact amount of gas saved will depend on the frequency of calls to these functions. If these functions are called frequently, the gas savings could be significant.

Possible Optimization 3 = location -- In the _swapFrom function, the reserves[i] += amountIn; operation is performed. This operation consumes 20,000 gas for the first storage (when the original value is 0) and 5,000 gas for subsequent storage operations. We can reduce the gas consumption by storing the result of reserves[i] + amountIn in a local variable and reusing it, like so function _swapFrom( IERC20 fromToken, IERC20 toToken, uint256 amountIn, uint256 minAmountOut, address recipient ) internal returns (uint256 amountOut) { IERC20[] memory _tokens = tokens(); uint256[] memory reserves = _updatePumps(_tokens.length); (uint256 i, uint256 j) = _getIJ(_tokens, fromToken, toToken); uint256 newReserveI = reserves[i] + amountIn; reserves[i] = newReserveI; uint256 reserveJBefore = reserves[j]; reserves[j] = _calcReserve(wellFunction(), reserves, j, totalSupply()); amountOut = reserveJBefore - reserves[j]; if (amountOut < minAmountOut) { revert SlippageOut(amountOut, minAmountOut); } toToken.safeTransfer(recipient, amountOut); emit Swap(fromToken, toToken, amountIn, amountOut, recipient); _setReserves(_tokens, reserves); }

  • Estimated gas saved = 15,000 gas for the first call to the _swapFrom function and 5,000 gas for subsequent calls. The exact amount of gas saved will depend on the frequency of calls to the _swapFrom function. If this function is called frequently, the gas savings could be significant.

#0 - c4-pre-sort

2023-07-12T10:18:18Z

141345 marked the issue as high quality report

#1 - c4-sponsor

2023-07-17T18:54:11Z

publiuss marked the issue as sponsor confirmed

#2 - c4-sponsor

2023-08-03T22:47:31Z

publiuss marked the issue as sponsor acknowledged

#3 - c4-judge

2023-08-05T10:54:18Z

alcueca marked the issue as grade-a

Findings Information

🌟 Selected for report: Trust

Also found by: 0xSmartContract, Eeyore, K42, glcanvas, oakcobalt, peanuts

Labels

analysis-advanced
grade-b
high quality report
sponsor confirmed
A-07

Awards

53.0984 USDC - $53.10

External Links

Advanced Analysis Report for Basin

Overview

  • Basin is a decentralized exchange (DEX) that aims to improve upon existing DEX architectures by introducing composability at its core. The project is designed to allow developers to compose new and existing exchange functions, oracles, and exchange implementations to create a customized liquidity pool, referred to as a "Well". The project is built on the Ethereum blockchain and is compatible with ERC-20 tokens.

Understanding the Ecosystem

  • Basin operates within the Decentralized Finance (DeFi) ecosystem, specifically within the realm of decentralized exchanges (DEXs). It interacts with ERC-20 tokens and provides a platform for users to swap these tokens, add liquidity, and remove liquidity. The project's unique selling point is its composability, allowing developers to create custom liquidity pools with different exchange functions and oracles.

Codebase Quality Analysis

  • The codebase of Basin is well-structured and modular, with clear separation of concerns. The contracts are well-documented, which aids in understanding the functionality and purpose of each contract. The use of libraries for common functions helps to reduce code duplication and increase readability. The code also follows Solidity best practices, including the use of SafeMath to prevent integer overflow and underflow attacks, and the use of ReentrancyGuard to prevent reentrancy attacks.

Architecture Recommendations

  • Modular Design: The Basin project follows a modular design approach, which is commendable. However, the complexity of the system could potentially lead to unexpected behaviour or vulnerabilities. Therefore, thorough testing and auditing are crucial to ensure the security of the system.

  • Gas Optimization: There are several areas in the code where gas optimization can be improved. I sent a gas report to hopefully assist in this. For instance, redundant operations, unnecessary storage operations, and external function calls can be better minimized or optimized.

  • Code Reusability: The codebase has a high degree of reusability, with several utility libraries and interfaces. However, there are areas where the code can be further refactored for better reusability and readability.

  • Error Handling: The codebase uses revert statements for error handling, which is a good practice. However, it would be beneficial to have more granular error messages to help with debugging and to provide more information to the end-user.

  • Security Practices: The use of the OpenZeppelin library for security-sensitive operations such as SafeMath and SafeERC20 is commendable. However, given the complexity of the system, additional security measures such as access controls, rate limiting, and additional checks and balances could be beneficial.

  • Upgradeability: The current architecture does not seem to support upgradeability. Considering the complexity and the potential for future improvements or fixes, an upgradeability mechanism might be beneficial.

  • Interoperability: The system is designed to interact with ERC-20 tokens, which makes it interoperable with a wide range of other projects in the Ethereum ecosystem. However, considering the rapid development in the space, it might be beneficial to design the system to be compatible with other token standards as well.

  • Scalability: Given the complexity of the system and the potential for high gas costs, scalability could be a concern. Layer 2 solutions or other scalability solutions might be worth considering.

Centralization Risks

  • Basin does not have any apparent centralization risks. The contracts do not contain any owner-only functions or centralized points of failure. However, users should be aware that anyone can deploy a Well with malicious components, and new Wells should not be trusted without careful review.

Mechanism Review

  • Basin introduces a novel mechanism for creating customizable liquidity pools, or "Wells". Each Well is defined by its tokens, exchange function (Well function), and oracle (Pump). This allows for a high degree of flexibility and customization, enabling developers to create Wells that are tailored to specific use cases.

Systemic Risks

Basin's architecture, while innovative, introduces several systemic risks due to its high degree of composability and the complexity of its components:

  • Composability Risks: The ability to compose arbitrary exchange functions, oracles, and exchange implementations for ERC-20 tokens in a Well introduces the risk of unexpected interactions between these components. These interactions could lead to unforeseen vulnerabilities or system behaviours, especially when new components are introduced.

  • Smart Contract Vulnerabilities: The complexity of the system and the use of advanced Solidity features increase the risk of smart contract vulnerabilities. These vulnerabilities could be exploited by malicious actors, leading to loss of funds or other adverse outcomes.

  • Dependence on External Contracts: Basin's functionality relies on interactions with external contracts (ERC-20 tokens, oracles, etc.). Any vulnerabilities or failures in these external contracts could potentially impact the operation and security of the Basin system.

  • Upgradeability Risks: While upgradeability allows for the continuous improvement and bug fixing of the system, it also introduces the risk of malicious upgrades if the upgrade process is not adequately secured.

Areas of Concern

Given the systemic risks identified, several areas of concern arise:

  • Well Component Trustworthiness: Since anyone can deploy a Well with arbitrary components, there's a risk that malicious or poorly implemented components could be used. Users must exercise due diligence when interacting with new Wells.

  • Gas Efficiency: The complex operations involved in the system, such as the use of advanced mathematical functions and multiple iterations over arrays, could lead to high gas costs for users.

  • Auditability: The complexity and novelty of the system could make it challenging to audit. It's crucial to ensure that thorough and ongoing audits are conducted to identify and mitigate potential vulnerabilities.

  • User Education: Given the complexity of the system, there's a risk that users may not fully understand how to interact with the system safely and effectively. It's important to provide comprehensive and accessible documentation and educational resources to users.

Codebase Analysis

  • The codebase is well-structured and follows Solidity best practices. The contracts are well-documented, which aids in understanding the functionality and purpose of each contract. The use of libraries for common functions helps to reduce code duplication and increase readability. However, the complexity of the system could potentially lead to unexpected behaviour or vulnerabilities.

Recommendations

  • Implement a system for verifying the trustworthiness of new Wells, such as an on-chain Well registry.
  • Continue to monitor and address any potential smart contract vulnerabilities, use Defender or Tenderly to future monitor the contracts.

Contract Details

The main contracts in the Basin codebase are

  • Well.sol: This is the main contract that implements the functionality of a Well. It allows for swapping, adding liquidity, and removing liquidity.
  • MultiFlowPump.sol: This contract implements the Pump functionality, which serves as an on-chain oracle.
  • Aquifer.sol: This contract is the main contract that implements the functionality of a Well. It allows for swapping, adding liquidity, and removing liquidity.

Conclusion

  • Basin is a well-designed project that introduces a novel mechanism for creating customizable liquidity pools. The codebase is well-structured and follows Solidity best practices. However, the complexity of the system and the composability of its components could potentially lead to unexpected behaviour or vulnerabilities. Therefore, thorough testing and auditing are crucial to ensure the security of the system. Users should also be cautious when interacting with new Wells and should conduct thorough due diligence before doing so.

Time spent:

20 hours

#0 - c4-pre-sort

2023-07-12T12:10:35Z

141345 marked the issue as high quality report

#1 - c4-sponsor

2023-07-22T10:23:33Z

publiuss marked the issue as sponsor confirmed

#2 - c4-judge

2023-08-05T20:11:30Z

alcueca marked the issue as grade-b

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