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
Rank: 21/74
Findings: 2
Award: $111.57
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: SM3_SS
Also found by: 0x11singh99, 0xAnah, 0xSmartContract, 0xn006e7, 0xprinc, DavidGiladi, ElCid, JCN, K42, MIQUINHO, Raihan, Rolezn, SAAJ, SY_S, Strausses, TheSavageTeddy, bigtone, erebus, hunter_w3b, josephdara, lsaudit, mahdirostami, oakcobalt, peanuts, pfapostol, seth_lawson
58.4732 USDC - $58.47
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... }
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; } }
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); }
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); }
_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
53.0984 USDC - $53.10
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.
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.
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.
The main contracts in the Basin codebase are
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