Canto Application Specific Dollars and Bonding Curves for 1155s - K42's results

Tokenizable bonding curves using a Stablecoin-as-a-Service token

General Information

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

Canto

Findings Distribution

Researcher Performance

Rank: 42/120

Findings: 2

Award: $27.31

Gas:
grade-b
Analysis:
grade-b

🌟 Selected for report: 0

🚀 Solo Findings: 0

Findings Information

Awards

8.2749 USDC - $8.27

Labels

bug
G (Gas Optimization)
grade-b
G-08

External Links

Gas Optimization Report for Canto by K42

Possible Optimizations in Market.sol

Possible Optimization 1 =

  • The _splitFees() function writes to multiple state variables (shareData[_id].shareCreatorPool, shareData[_id].shareHolderRewardsPerTokenScaled, platformPool). By reducing the number of state writes, we can save gas. Accumulate the fees in memory variables and write them to the state in a single operation at the end of the function instead of current implementation to save gas.

Here is the optimized code snippet:

function _splitFees(uint256 _id, uint256 _fee, uint256 _tokenCount) internal {
    uint256 shareHolderFee = (_fee * HOLDER_CUT_BPS) / 10_000;
    uint256 shareCreatorFee = (_fee * CREATOR_CUT_BPS) / 10_000;
    uint256 platformFee = _fee - shareHolderFee - shareCreatorFee;

    uint256 newShareCreatorPool = shareData[_id].shareCreatorPool + shareCreatorFee;
    uint256 newShareHolderRewardsPerTokenScaled = shareData[_id].shareHolderRewardsPerTokenScaled;
    uint256 newPlatformPool = platformPool + platformFee;

    if (_tokenCount > 0) {
        newShareHolderRewardsPerTokenScaled += (shareHolderFee * 1e18) / _tokenCount;
    } else { 
        newPlatformPool += shareHolderFee; 
    }

    shareData[_id].shareCreatorPool = newShareCreatorPool;
    shareData[_id].shareHolderRewardsPerTokenScaled = newShareHolderRewardsPerTokenScaled;
    platformPool = newPlatformPool;
}
  • Estimated gas saved = Approximately 5,000 gas per transaction.

Possible Optimization 2 =

  • Frequently accessed storage variables like shareData[_id] are read multiple times in functions like buy(), sell(), mintNFT(), and burnNFT(). Each storage read is gas-intensive. Cache shareData[_id] in a memory variable at the start of the function and use this cached value throughout.

Here is the optimized code:

function buy(uint256 _id, uint256 _amount) external {
    ShareData memory cachedShareData = shareData[_id];
    require(cachedShareData.creator != msg.sender, "Creator cannot buy");
    // ... rest of the function using cachedShareData instead of shareData[_id]
}
  • Estimated gas saved = Approximately 2,000 to 5,000 gas per read operation.

Possible Optimization 3 =

Here is the optimized code snippet:

struct ShareData {
    // ... other variables
    bytes32 metadataURI; // Instead of string
}

function createNewShare(
    bytes32 _shareName, // Instead of string
    // ... other parameters
) external onlyShareCreator returns (uint256 id) {
    // ... function logic
}
  • Estimated gas saved = This can save a significant amount of gas, especially if these strings are frequently written to or read from storage. The exact savings depend on the size of the strings being replaced.

Possible Optimizations in LinearBondingCurve.sol

Possible Optimization 1 =

  • The getPriceAndFee() function uses a loop to calculate the price and fee, which can be gas-intensive, especially for large values of amount. You can replace the loop with a mathematical formula to calculate the total price and fee for the given amount.

Here is the optimized code snippet:

function getPriceAndFee(uint256 shareCount, uint256 amount)
    external
    view
    override
    returns (uint256 price, uint256 fee)
{
    uint256 endShareCount = shareCount + amount;
    // Calculate total price using arithmetic series sum formula
    price = (amount * (2 * priceIncrease * shareCount + (amount - 1) * priceIncrease)) / 2;
    // Calculate total fee
    for (uint256 i = shareCount; i < endShareCount; i++) {
        uint256 tokenPrice = priceIncrease * i;
        fee += (getFee(i) * tokenPrice) / 1e18;
    }
}
  • Estimated gas saved = This can save a significant amount of gas for large amount values by reducing the number of loop iterations. Exact savings depend on the amount value.

Possible Optimization 2 =

  • The getFee() function is called within a loop in getPriceAndFee(), potentially leading to repeated calculations of log2(). Cache the result of log2() for each unique shareCount value within the loop.

Here is the optimized code:

function getPriceAndFee(uint256 shareCount, uint256 amount)
    external
    view
    override
    returns (uint256 price, uint256 fee)
{
    uint256 endShareCount = shareCount + amount;
    price = (amount * (2 * priceIncrease * shareCount + (amount - 1) * priceIncrease)) / 2;

    uint256 lastLog2;
    uint256 lastShareCount = 0;
    for (uint256 i = shareCount; i < endShareCount; i++) {
        uint256 tokenPrice = priceIncrease * i;
        if (i != lastShareCount) {
            lastLog2 = log2(i);
            lastShareCount = i;
        }
        fee += (1e17 / (lastLog2 > 1 ? lastLog2 : 1) * tokenPrice) / 1e18;
    }
}
  • Estimated gas saved = This optimization can save gas by reducing the number of calls to log2. The savings are more significant for larger loops.

#0 - c4-judge

2023-11-29T19:55:42Z

MarioPoneder marked the issue as grade-b

Awards

19.0443 USDC - $19.04

Labels

analysis-advanced
grade-b
A-10

External Links

Advanced Analysis Report for Canto by K42

Overview

  • The Canto scope for this audit, represented by the four contracts Market, LinearBondingCurve, asDFactory, and asD, showcases a complex interplay of token creation, exchange, and management functionalities. Each contract plays a distinct role, contributing to the ecosystem's overall functionality and security.

Contract-Specific Analyses

Market Contract

  • Key Functions:
    • createNewShare(): Creates new shares, involves multiple state changes.
    • buy(): Allows buying of shares, complex logic for pricing and fee calculations.
    • sell(): Facilitates selling of shares, similar complexity to buy().
    • mintNFT(): Mints NFTs, involves fee calculations.
    • burnNFT(): Burns NFTs, centralizes fee distribution.
  • Data Structures:
    • ShareData: Manages token counts, circulation, rewards, and metadata.
    • shareIDs: Maps share names to their unique IDs.
    • whitelistedBondingCurves: Tracks approved bonding curves.
  • Inter-Contract Communication: Interacts with bonding curves and ERC20 tokens for pricing and fee calculations.
  • Security: Follows standard security practices, but complexity could hide vulnerabilities.
  • Centralization Risks: onlyOwner modifier in administrative functions could lead to centralization.
  • Mechanism Review:
    • Fee Distribution: Clear structure, but potential for gas inefficiency.
    • Share Creation and Management: Robust but centralized.
  • Specific Risks and Mitigations:
    • Centralization in share creation and bonding curve approval.
    • Gas inefficiencies in loops and multiple state changes.
  • Recommendations: Decentralize control mechanisms, optimize state changes, simplify complex functions, and ensure transparency in the whitelisting process.

LinearBondingCurve Contract

  • Key Functions:
    • getPriceAndFee(): Calculates price and fee for shares, complexity in fee calculations.
    • getFee(): Determines fee amount, dependent on log2 function.
    • log2(): Assembly code for logarithmic calculations, security critical.
  • Data Structures:
    • priceIncrease: Immutable variable defining the price increase per share.
  • Inter-Contract Communication: Provides pricing data for shares.
  • Security: Simple logic but uses assembly in log2 function, requiring careful review.
  • Centralization Risks: Primarily a utility contract, exhibiting minimal centralization risks.
  • Mechanism Review: Linear increase in price per share, subject to potential gaming.
  • Specific Risks and Mitigations:
    • Manipulation in pricing mechanism.
    • Security concerns with low-level assembly code in log2.
  • Recommendations: Review pricing and fee calculation mechanisms, and ensure robust testing.

asDFactory Contract

  • Key Functions:
    • create(): Allows creation of asD tokens, central to token creation mechanism.
  • Data Structures:
    • cNote: Immutable variable storing the address of the cNOTE token.
    • isAsD: Mapping to track legitimate asD tokens.
  • Inter-Contract Communication: Interacts with asD contracts and other ecosystem parts.
  • Security: Simplicity reduces risk, but depends on the security of the asD contract.
  • Centralization Risks: Allows token creation by any user, mitigating centralization risks.
  • Mechanism Review: Open and permissionless token creation mechanism.
  • Specific Risks and Mitigations:
    • Potential abuse in token creation if asD has vulnerabilities.
  • Recommendations: Consider implementing safeguards in the create() function.

asD Contract

  • Key Functions:
    • mint(): Mints asD tokens, relies on external contract CErc20Interface.
    • burn(): Burns asD tokens for redeeming NOTE, critical for exchange rate integrity.
    • withdrawCarry(): Withdraw function, centralization risk due to owner-only access.
  • Data Structures:
    • cNote: Immutable variable storing the address of the cNOTE token.
  • Inter-Contract Communication: Interacts with CErc20Interface and CTokenInterface for token operations.
  • Security: Straightforward design but relies on external contracts.
  • Centralization Risks: withdrawCarry function is only callable by the owner.
  • Mechanism Review: 1:1 exchange rate between NOTE and asD.
  • Specific Risks and Mitigations:
    • Manipulation or errors in the exchange rate mechanism.
    • Centralization risk due to owner-controlled functions.
  • Recommendations: Implement thorough checks for exchange rate calculations, decentralize owner functions, and ensure robust error handling in external contract interactions.

General Recommendations for the Ecosystem

  • Decentralization: Implement decentralized governance mechanisms where feasible.
  • Optimization: Focus on gas optimization and state change efficiency.
  • Testing: Conduct comprehensive testing especially for the financial transactions and assembly code.
  • Transparency and Integrity: Ensure transparency in processes like whitelisting and token creation, and maintain the integrity of the ecosystem through robust security practices.

Contract Details

I made function interaction graphs for the key contracts to better visualize interactions, as seen below:

Conclusion

  • The Canto scope of this audit, through its interconnected contracts, offers a sophisticated platform for token management and NFT functionalities. While the design is intricate and efficient, it necessitates careful consideration of security, especially regarding inter-contract dependencies, centralization risks, and potential manipulation in token economics. Addressing these concerns through comprehensive audits, architectural improvements, and a focus on decentralization and transparency will be crucial in ensuring the ecosystem's integrity and resilience.

Time spent:

16 hours

#0 - c4-judge

2023-11-29T20:47:57Z

MarioPoneder 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