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: 57/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
It’s the most gas efficient to make up to 3 event parameters indexed. If there are less than 3 parameters, you need to make all parameters indexed.
69 event BondingCurveStateChange(address indexed curve, bool isWhitelisted); 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);
ERC721A
instead ERC721
ERC721A
is an improvement standard for ERC721
tokens. It was proposed by the Azuki team and used for developing their NFT collection. Compared with ERC721
, ERC721A
is a more gas-efficient standard to mint a lot of of NFTs simultaneously. It allows developers to mint multiple NFTs at the same gas price. This has been a great improvement due to Ethereum’s sky-rocketing gas fee. Reference: <ins>https://nextrope.com/erc721-vs-erc721a-2/</ins>.
https://github.com/code-423n4/2023-11-canto/blob/main/asD/src/asD.sol#L8
import {IERC20, ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
_mint(msg.sender, _id, _amount, "");
https://github.com/code-423n4/2023-11-canto/blob/main/1155tech-contracts/src/Market.sol#L214
_burn(msg.sender, _id, _amount);
https://github.com/code-423n4/2023-11-canto/blob/main/1155tech-contracts/src/Market.sol#L236
https://github.com/code-423n4/2023-11-canto/blob/main/asD/src/asD.sol#L65C8-L66C59
https://github.com/code-423n4/2023-11-canto/blob/main/asD/src/asD.sol#L55
Instead of using a loop to calculate the price and fee, you can use multiplication directly. This eliminates the need for the loop and reduces gas costs.
function getPriceAndFee(uint256 shareCount, uint256 amount) external view override returns (uint256 price, uint256 fee) { - for (uint256 i = shareCount; i < shareCount + amount; i++) { - uint256 tokenPrice = priceIncrease * i; - price += tokenPrice; - fee += (getFee(i) * tokenPrice) / 1e18; - } + uint256 tokenPrice = priceIncrease * shareCount; + price = (amount * (2 * tokenPrice + (amount - 1) * priceIncrease)) / 2; + fee = (getFee(shareCount) * tokenPrice * amount) / 1e18; }
Mathematical Formula: The mathematical formula (n/2) * (2a + (n-1)d)
is used to calculate the sum of an arithmetic series, where:
n
is the number of terms (amount of shares in this case),a
is the first term (token price at shareCount
),d
is the common difference (price increase).Simplification:
tokenPrice
is calculated for the starting share, and the formula computes the sum without iterating through each share individually.Â
Simplify the fee calculation by eliminating the unnecessary conditional check.
function getFee(uint256 shareCount) public pure override returns (uint256) { uint256 divisor; - if (shareCount > 1) { - divisor = log2(shareCount); - } else { - divisor = 1; - } - // 0.1 / log2(shareCount) + // Simplified fee calculation + uint256 divisor = log2(shareCount); return 1e17 / divisor; }
Simplified Fee Calculation:
shareCount > 1
is unnecessary because the formula for fee calculation (1e17 / divisor
) works for shareCount = 1
as well.Simplified Divisor Assignment:
divisor
is directly set to log2(shareCount)
, making the calculation straightforward.Reduced Code Complexity:
Replace the log2
function with a simpler exponentiation approach to further optimize gas usage.
function getFee(uint256 shareCount) public pure override returns (uint256) { uint256 divisor; - if (shareCount > 1) { - divisor = log2(shareCount); - } else { - divisor = 1; - } - // 0.1 / log2(shareCount) + // Replaced log2 with exponentiation + uint256 divisor = 1 << shareCount; return 1e17 / divisor; } -function log2(uint256 x) internal pure returns (uint256 r) { - assembly { - r := shl(7, lt(0xffffffffffffffffffffffffffffffff, x)) - r := or(r, shl(6, lt(0xffffffffffffffff, shr(r, x)))) - r := or(r, shl(5, lt(0xffffffff, shr(r, x)))) - r := or(r, shl(4, lt(0xffff, shr(r, x)))) - r := or(r, shl(3, lt(0xff, shr(r, x)))) - r := or( - r, - byte( - and(0x1f, shr(shr(r, x), 0x8421084210842108cc6318c6db6d54be)), - 0x0706060506020504060203020504030106050205030304010505030400000000 - ) - ) - }
Explanation:
In the optimized code, the log2
function is entirely replaced with the 1 << shareCount
operation, which is a bitwise left shift.
The 1 << shareCount
operation effectively calculates 2 raised to the power of shareCount
, providing the same result as log2
but in a more gas-efficient manner.
Â
 the constructor contains logic to conditionally register on the Canto main- and testnet (chain IDs 7700 and 7701). However, this logic may not be necessary, and if the contract is intended for use on multiple networks, it's generally more efficient to keep the constructor simple.
https://github.com/code-423n4/2023-11-canto/blob/main/asD/src/asDFactory.sol#L24C5-L31C6
constructor(address _cNote) { cNote = _cNote; - if (block.chainid == 7700 || block.chainid == 7701) { - Turnstile turnstile = Turnstile(0xEcf044C5B4b867CFda001101c617eCd347095B44); - turnstile.register(tx.origin); - } }
Explanation:
Removal of Conditional Registration:
Turnstile
contract.Reasoning:
Turnstile
seems specific to certain chain IDs, and if this is not essential for the contract's functionality, removing it simplifies the constructor.Gas Efficiency:
Consideration:
 https://github.com/code-423n4/2023-11-canto/blob/main/asD/src/asDFactory.sol#L15
https://github.com/code-423n4/2023-11-canto/blob/main/asD/src/asDFactory.sol#L35
// Previous Code mapping(address => bool) public isAsD; // Optimized Code bool public isAsDInitialized; address[] public asDAddresses; // In create function isAsD[address(createdToken)] = true; // Replace with isAsDInitialized = true; asDAddresses.push(address(createdToken));
Explanation:
I introduced a boolean variable isAsDInitialized
to keep track of whether the list asDAddresses
has been initialized. This helps avoid unnecessary initialization during subsequent calls to the create
function.
 I replaced the mapping isAsD
with an array asDAddresses
. In the create
function, after initializing the asD
contract, I push its address to this array.
 Now, to check if an address is a legit asD
contract, you can iterate through the asDAddresses
array. This iteration is likely more gas-efficient than repeated mapping lookups, especially if the number of asD
contracts is relatively small.
#0 - c4-judge
2023-11-29T19:56:37Z
MarioPoneder marked the issue as grade-b