Platform: Code4rena
Start Date: 13/11/2023
Pot Size: $24,500 USDC
Total HM: 3
Participants: 120
Period: 4 days
Judge: 0xTheC0der
Id: 306
League: ETH
Rank: 62/120
Findings: 1
Award: $8.27
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: 0xVolcano
Also found by: 0xAnah, 0xhex, 0xta, 100su, JCK, K42, MrPotatoMagic, chaduke, cheatc0d3, hunter_w3b, lsaudit, mgf15, parlayan_yildizlar_takimi, sivanesh_808, tabriz, tala7985
8.2749 USDC - $8.27
The Market contract contains some unnecessary requires that can be removed to save gas. Each removed require check would save approximately 1000-2000 gas. With multiple opportunities in key functions like buy/sell, total savings could reach 5,000-10,000 gas per transaction.
For example, in the buy/sell functions it requires the user has enough tokens, but the transfer will revert anyways if not.
Remove unnecessary requires:
In buy/sell, remove the require checking the user is not the creator. The transfer will fail if they try to buy/sell their own shares.
In mintNFT/burnNFT, remove the require checking the user has enough tokens. The transfer will fail if not.
This avoids spending gas on require checks that don't provide any additional validation beyond the token transfers.
File: asD/src/asD.sol 15 address public immutable cNote; // Reference to the cNOTE token
https://github.com/code-423n4/2023-11-canto/blob/main/asD/src/asD.sol#L15
File: src/asDFactory.sol 12 address public immutable cNote;
https://github.com/code-423n4/2023-11-canto/blob/main/asD/src/asDFactory.sol#L12
The getPriceAndFee function recalculates the running totals of price and fee on each iteration of the loop. This is inefficient.
Fiel: src/bonding_curve/LinearBondingCurve.sol 20 for (uint256 i = shareCount; i < shareCount + amount; i++) { 21 uint256 tokenPrice = priceIncrease * i; 22 price += tokenPrice; 23 fee += (getFee(i) * tokenPrice) / 1e18; 24 }
Cache the running totals in state variables that are updated after each transaction rather than recalculating on every call.
For example:
Gas savings would be significant as it avoids recomputing the loop on every call. Exact savings would depend on loop length but easily hundreds/thousands of gas per call.
In the current getFee function, log2(i) is calculated separately for the fee and price calculations on each iteration.
File: src/bonding_curve/LinearBondingCurve.sol 27 function getFee(uint256 shareCount) public pure override returns (uint256) { 28 uint256 divisor; 29 if (shareCount > 1) { 30 divisor = log2(shareCount); 31 } else { 32 divisor = 1; 33 } 34 // 0.1 / log2(shareCount) 35 return 1e17 / divisor; 36 }
Calculate log2(i) only once per iteration and store in a variable to be used in both places:
function getPriceAndFee(uint256 shareCount, uint256 amount) { for (uint256 i = shareCount; i < shareCount + amount; i++) { uint256 logI = log2(i); uint256 tokenPrice = priceIncrease * i; uint256 fee = (tokenPrice * 1e17) / logI; // ... } }
Calculating log2(i) just once per iteration instead of twice will save approximately 200-500 gas per iteration.
For transactions involving many shares, this could compound to significant savings of thousands of gas.
Combining duplicate computations is a good pattern to optimize logic and parameters that are used in multiple places within a loop body. It avoids wasting gas on redundant operations.
File: src/asD.sol 20 event CarryWithdrawal(uint256 amount);
https://github.com/code-423n4/2023-11-canto/blob/main/asD/src/asD.sol#L20
File: src/Market.sol 71 event SharesBought(uint256 indexed id, address indexed buyer, uint256 amount, uint256 price, uint256 fee); 72 event SharesSold(uint256 indexed id, address indexed seller, uint256 amount, uint256 price, uint256 fee); 73 event NFTsCreated(uint256 indexed id, address indexed creator, uint256 amount, uint256 fee); 74 event NFTsBurned(uint256 indexed id, address indexed burner, uint256 amount, uint256 fee); 75 event PlatformFeeClaimed(address indexed claimer, uint256 amount); 76 event CreatorFeeClaimed(address indexed claimer, uint256 indexed id, uint256 amount); 77 event HolderFeeClaimed(address indexed claimer, uint256 indexed id, uint256 amount); 78 event ShareCreationRestricted(bool isRestricted);
https://github.com/code-423n4/2023-11-canto/blob/main/1155tech-contracts/src/Market.sol#L69-L78
File: src/asDFactory.sol 20 event CreatedToken(address token, string symbol, string name, address creator);
https://github.com/code-423n4/2023-11-canto/blob/main/asD/src/asDFactory.sol#L20
File: src/bonding_curve/LinearBondingCurve.sol 8 uint256 public immutable priceIncrease;
File: src/Market.sol 14 uint256 public constant NFT_FEE_BPS = 1_000; // 10% 15 uint256 public constant HOLDER_CUT_BPS = 3_300; // 33% 16 uint256 public constant CREATOR_CUT_BPS = 3_300; // 33% 20 IERC20 public immutable token; 27 uint256 public shareCount; 58 uint256 public platformPool; 61 bool public shareCreationRestricted = true;
https://github.com/code-423n4/2023-11-canto/blob/main/1155tech-contracts/src/Market.sol#L14
File: src/asDFactory.sol 12 address public immutable cNote;
https://github.com/code-423n4/2023-11-canto/blob/main/asD/src/asDFactory.sol#L12
File: src/asD.sol 15 address public immutable cNote; // Reference to the cNOTE token
https://github.com/code-423n4/2023-11-canto/blob/main/asD/src/asD.sol#L15
File: src/bonding_curve/LinearBondingCurve.sol 20 for (uint256 i = shareCount; i < shareCount + amount; i++) { 21 uint256 tokenPrice = priceIncrease * i; 22 price += tokenPrice; 23 fee += (getFee(i) * tokenPrice) / 1e18; 24 }
asD.sol
repeatedly calls CErc20Interface(cNote) when interacting with the cToken contract.Define a local CErc20Interface variable instead of calling the interface multiple times.
Calling an interface function has overhead. Caching it in a local variable avoids repeatedly paying this cost.
The current mint and burn functions make multiple state changes (mint/burn, approve, redeem) as separate transactions.
File: src/asD.sol 47 function mint(uint256 _amount) external { 60 function burn(uint256 _amount) external {
https://github.com/code-423n4/2023-11-canto/blob/main/asD/src/asD.sol#L47
Combine mint and burn into a single deposit/withdraw function that mints/burns and approves/redeems cNote in one transaction.
Combining the state changes avoids multiple external function calls and improves gas efficiency. Estimated savings of 1000-2000 gas per mint/burn.
#0 - c4-judge
2023-11-29T19:55:33Z
MarioPoneder marked the issue as grade-b