Dry Aqua Sheep
Medium
Decimal Precision not scaled causing users fail to buy/sell receipt tokens for tokens with 6 decimals
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)
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"
);
No response
- Buyer wants to buy receipt tokens with 6 decimals tokens such as USDC.
No response
BuyOrder of 6 decimals tokens can never be fulfilled, breaking core functionailty.
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();
}
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))