Canto Application Specific Dollars and Bonding Curves for 1155s - wangxx2026'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: 16/120

Findings: 1

Award: $690.37

🌟 Selected for report: 0

🚀 Solo Findings: 0

Findings Information

Awards

690.3741 USDC - $690.37

Labels

bug
3 (High Risk)
satisfactory
edited-by-warden
duplicate-181

External Links

Lines of code

https://github.com/code-423n4/2023-11-canto/blob/335930cd53cf9a137504a57f1215be52c6d67cb3/asD/src/asD.sol#L73-L77

Vulnerability details

Impact

Incorrect precision will result in profits being unable to be withdrawn normally.

Proof of Concept

Document address: https://docs.compound.finance/v2/#protocol-math wrote: The cToken Exchange Rate is scaled by the difference in decimals between the cToken and the underlying asset.

\Large \rm oneCTokenInUnderlying = exchangeRateCurrent / (1 * 10 ^ { (18 + underlyingDecimals - cTokenDecimals)})

The calculation in the code is as follows:

\Large \rm 1 * 10^{(18 - 8 + Underlying Token Decimals)}  = 10^{(28)}

The specific code is as follows:

        uint256 exchangeRate = CTokenInterface(cNote).exchangeRateCurrent(); // Scaled by 1 * 10^(18 - 8 + Underlying Token Decimals), i.e. 10^(28) in our case
        // The amount of cNOTE the contract has to hold (based on the current exchange rate which is always increasing) such that it is always possible to receive 1 NOTE when burning 1 asD
        uint256 maximumWithdrawable = (CTokenInterface(cNote).balanceOf(address(this)) * exchangeRate) /
            1e28 -
            totalSupply();

It is considered that the precision of cNOTE is 8 and the precision of underlyingDecimals(Note) is 18

But the cNOTE precision deployed on the canto chain is 18

The deployment address is as follows(Already confirmed with sponsor): https://tuber.build/address/0xEe602429Ef7eCe0a13e4FfE8dBC16e101049504C?tab=read_contract#313ce567

So the correct precision is:

\Large \rm (1 * 10^ {(18 + underlyingDecimals - cTokenDecimals))} = 1*10^{ (18 + 18 - 18)} = 1*10^{18}

Because the precision is too high, maximumWithdrawable will be negative and cannot be positive until it reaches $10^{10}$ times of totalSupply, resulting in the inability to withdraw profits normally.

POC: run forge install compound-finance/compound-protocol@master After installing the dependencies, it can run normally

// SPDX-License-Identifier: GPL-3.0-only
pragma solidity >=0.8.0;
import {asD} from "../asD.sol";
import {IERC20, ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "forge-std/Test.sol";
import "@compound-protocol/contracts/CErc20.sol";
import "@compound-protocol/contracts/CErc20Delegator.sol";
import "@compound-protocol/contracts/Comptroller.sol";
import "@compound-protocol/contracts/JumpRateModelV2.sol";
import "@compound-protocol/contracts/CErc20Delegate.sol";
import "@compound-protocol/contracts/CToken.sol";

contract MockERC20 is ERC20 {
    constructor(string memory symbol, string memory name) ERC20(symbol, name) {}

    function mint(address to, uint256 amount) public {
        _mint(to, amount);
    }
}

contract asD2Factory is Test {

    MockERC20 public note;
    CErc20Delegate public cnote;
    CErc20Delegator public cnoteDet;
    Comptroller public comptroller;
    JumpRateModelV2 public interestRateModel;
    uint public baseRatePerYear;
    uint public multiplierPerYear;
    uint public jumpMultiplierPerYear;

    uint initialExchangeRateMantissa;

    string asDName = "Test";
    string asDSymbol = "TST";

    asD asdToken;

    function setUp() public {
        cnote = new CErc20Delegate();
        note = new MockERC20("note", "note"); // Underlying token decimals 18 
        comptroller = new Comptroller();

        baseRatePerYear = 1e17;
        multiplierPerYear = 1e17;
        jumpMultiplierPerYear = 1e17;
        interestRateModel = new JumpRateModelV2(baseRatePerYear, multiplierPerYear, jumpMultiplierPerYear, 1, address(this));
        initialExchangeRateMantissa = 1e18; // Initialize ExchangeRate 1e18

        cnoteDet = new CErc20Delegator(address(note), comptroller, interestRateModel,initialExchangeRateMantissa, "cnote", "cnote", 
        18, // cnote decimals 18 
        payable(address(this)), address(cnote), bytes(""));

        comptroller._supportMarket(CToken(address(cnoteDet)));
        asdToken = new asD(asDName, asDSymbol, address(this), address(cnoteDet), address(this));
        
    }

    function testexchangeRateCurrent() public {
        uint256 mintAmount = 10e18;
        note.mint(address(this), mintAmount);
        uint256 initialBalance = note.balanceOf(address(this));
        note.approve(address(asdToken), mintAmount);
        asdToken.mint(mintAmount);
        assertEq(note.balanceOf(address(this)), initialBalance - mintAmount);
        assertEq(asdToken.balanceOf(address(this)), mintAmount);
        assertEq(note.balanceOf(address(cnoteDet)), mintAmount);
        assertEq(cnoteDet.totalSupply(), mintAmount); // Totalsupply is greater than 0, and will re -calculate the ExchangeErate

        uint256 exchange = cnoteDet.exchangeRateCurrent(); 
        assertEq(exchange, 1e18);
    }
}

Tools Used

VSCode Foundry

The correct writing should be changed to:

 uint256 maximumWithdrawable = (CTokenInterface(cNote).balanceOf(address(this)) * exchangeRate) /
            1e18 -
            totalSupply();

or

 uint256 maximumWithdrawable = (CTokenInterface(cNote).balanceOfUnderlying(address(this)) - totalSupply();

Assessed type

Decimal

#0 - c4-pre-sort

2023-11-19T09:13:19Z

minhquanym marked the issue as duplicate of #227

#1 - c4-judge

2023-11-28T22:58:15Z

MarioPoneder marked the issue as satisfactory

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