Skip to content

Latest commit

 

History

History
184 lines (140 loc) · 6.2 KB

031.md

File metadata and controls

184 lines (140 loc) · 6.2 KB

Dry Aqua Sheep

Medium

Decimal Precision not scaled causing users fail to buy/sell receipt tokens for tokens with 6 decimals

Summary

There is a missing adjustment for tokens with 6 decimals when creating a buyOrder for receiptTokens, where the veNFT's underlying token is $AERO, an 18-decimal token. This issue causes failures when a seller trades their receipt NFT for a buyer's token with 6 decimals, such as USDC, thereby breaking core functionality.

README.md Any ERC20 that follows exactly the standard (eg. 18/6 decimals)

Root Cause

The calculation, uses collateralDecimals which is always 18 dp for $AERO tokens. This causes the transaction to fail since the token with 6 dp cannot buy the receipt token..

        uint amount = (buyInformation.buyRatio * collateralAmount) /
            (10 ** collateralDecimals);
        require(
            amount <= buyInformation.availableAmount,
            "Amount exceeds available amount"
        );

https://github.com/sherlock-audit/2024-11-debita-finance-v3/blob/main/Debita-V3-Contracts/contracts/buyOrders/buyOrder.sol#L111

Internal pre-conditions

No response

External pre-conditions

  1. Buyer wants to buy receipt tokens with 6 decimals tokens such as USDC.

Attack Path

No response

Impact

BuyOrder of 6 decimals tokens can never be fulfilled, breaking core functionailty.

PoC

Create file in same directory Debita-V3-Contracts/test/fork/BuyOrders/USDCMock.sol

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import {ERC20} from "../../../lib/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol";

contract USDCMock is ERC20 {
    constructor() ERC20("ERC20Mock", "E20M") {}

    function mint(address account, uint256 amount) external {
        _mint(account, amount);
    }
    function burn(address account, uint256 amount) external {
        _burn(account, amount);
    }
    function decimals() public view override returns (uint8) {
        return 6;
    }
}

Run forge test --match-contract PoC --match-test testSellReceiptPoC -vvvv --fork-url https://base-mainnet.infura.io/v3/<API_KEY>

pragma solidity ^0.8.0;

import {Test, console} from "forge-std/Test.sol";
import {VotingEscrow} from "@aerodrome/VotingEscrow.sol";
import {BuyOrder, buyOrderFactory} from "@contracts/buyOrders/buyOrderFactory.sol";
// DutchAuction_veNFT
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
// import ERC20
import {ERC20Mock} from "@openzeppelin/contracts/mocks/token/ERC20Mock.sol";
import {USDCMock} from "./USDCMock.sol";

import {veNFTAerodrome} from "@contracts/Non-Fungible-Receipts/veNFTS/Aerodrome/Receipt-veNFT.sol";
import {veNFTVault} from "@contracts/Non-Fungible-Receipts/veNFTS/Aerodrome/veNFTAerodrome.sol";
import "@openzeppelin/contracts/token/ERC721/utils/ERC721Holder.sol";
import {DynamicData} from "../../interfaces/getDynamicData.sol";

contract PoC is Test {
    VotingEscrow public ABIERC721Contract;
    buyOrderFactory public factory;
    BuyOrder public buyOrder;
    veNFTAerodrome public receiptContract;
    DynamicData public allDynamicData;
    ERC20Mock public AEROContract;
    USDCMock public USDCContract;

    BuyOrder public buyOrderContract;

    address signer = 0x5F35576Ae82553209224d85Bbe9657565ab16a4f;
    address seller = 0x81B2c95353d69580875a7aFF5E8f018F1761b7D1;
    address buyer = address(0x02);
    address veAERO = 0xeBf418Fe2512e7E6bd9b87a8F0f294aCDC67e6B4;
    address AERO = 0x940181a94A35A4569E4529A3CDfB74e38FD98631;
    address USDC = 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913 ; // From BaseScan

    uint receiptID;
    function setUp() public {
        deal(AERO, seller, 100e18, false);
        deal(AERO, buyer, 100e18, false);
        deal(USDC, buyer, 100e6, false);
        deal(USDC, buyer, 100e6, false);

        // USDCMock.mint(buyer, 100e6);
        // USDCMock.mint(buyer, 100e6);

        BuyOrder instanceDeployment = new BuyOrder();
        factory = new buyOrderFactory(address(instanceDeployment));
        allDynamicData = new DynamicData();
        AEROContract = ERC20Mock(AERO);
        USDCContract = USDCMock(USDC);


        receiptContract = new veNFTAerodrome(veAERO, AERO);

        ABIERC721Contract = VotingEscrow(veAERO);
        vm.startPrank(seller);
        ERC20Mock(AERO).approve(address(ABIERC721Contract), 1000e18);
        uint veNFTID = ABIERC721Contract.createLock(100e18, 365 * 4 * 86400);

        ABIERC721Contract.approve(address(receiptContract), veNFTID);
        uint[] memory nftID = allDynamicData.getDynamicUintArray(1);
        nftID[0] = veNFTID;
        receiptContract.deposit(nftID);
        receiptID = receiptContract.lastReceiptID();

        vm.stopPrank();

        vm.startPrank(buyer);
        USDCContract.approve(address(factory), 1000e18);
        address _buyOrderAddress = factory.createBuyOrder(
            USDC,
            address(receiptContract),
            100e6,
            7e17
        );
        buyOrderContract = BuyOrder(_buyOrderAddress);

        vm.stopPrank();
    }


    function testSellReceiptPoC() public {
        vm.startPrank(seller);
        receiptContract.approve(address(buyOrderContract), receiptID);
        uint balanceBeforeAero = USDCContract.balanceOf(seller);
        vm.expectRevert();
        buyOrderContract.sellNFT(receiptID);
        uint balanceAfterAero = USDCContract.balanceOf(seller);
        vm.stopPrank();

    }

Mitigation

Round down the amount to 6 dp to match the buyInformation.availableAmount decimals. Follow the documentation to scale down decimals: Link This is assuming that the token input value is 1 to 1 of veNFT collateral.

        uint amount = (buyInformation.buyRatio * collateralAmount) /
            (10 ** collateralDecimals);
+      if (IERC20(wantedToken).decimal == 6) {
+         uint amount = amount / 10 ** (IERC20(wantedToken).decimals)
+      }
        require(
            amount <= buyInformation.availableAmount,
            "Amount exceeds available amount"
        );
**To convert valueA to valueB: (18 dp -> 6 dp)
    valueB = valueA / 10**(A.decimals - B.decimals)
    valueB = valueA / (10**(18-6))