Tadle

Tadle
DeFi
30,000 USDC
View results
Submission Details
Severity: low
Invalid

Users with bid offers will be unable to listOffer

Summary

When creating a new StockInfo by calling the function createTaker( ), It incorrectly assigns the opposite stock type to what it should be based on the offer type . When a user creates a taker for an Buy offer, it sets the stock type to Ask , and vice versa. This inconsistency leads to incorrect behavior in other parts of the contract that rely on the stock type information.

Vulnerability Details

https://github.com/Cyfrin/2024-08-tadle/blob/04fd8634701697184a3f3a5558b41c109866e5f8/src/core/PreMarkets.sol#L138

There is a systematic mismatch between offer types and stock types. When creating an offer:

  • Ask offers are assigned Bid stock types

  • Bid offers are assigned Ask stock types

This initial misclassification then propagates through the system, affecting the listOffer ( )leading to users not being able to list bid offers

The enum StockType is defined as:

enum StockType {
Ask, // 0
Bid // 1
}

In the createTaker ( ) function, within the StockInfo creation:

function createTaker(address _offer, uint256 _points) external payable {
// ... (rest of logic)
stockInfoMap[stockAddr] = StockInfo({
id: offerId,
stockStatus: StockStatus.Initialized,
stockType: offerInfo.offerType == OfferType.Ask
? StockType.Bid //@audit-issue
: StockType.Ask,
authority: _msgSender(),
maker: offerInfo.maker,
preOffer: _offer,
points: _points,
amount: depositAmount,
offer: address(0x0)
});
// ... (rest of logic)
}

As you can see there is a mismatch between offer types and stock types. When updating the an stockType

Proof of concepts

// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity ^0.8.13;
import {Test, console2} from "forge-std/Test.sol";
import {SystemConfig} from "../src/core/SystemConfig.sol";
import {CapitalPool} from "../src/core/CapitalPool.sol";
import {TokenManager} from "../src/core/TokenManager.sol";
import {PreMarktes} from "../src/core/PreMarkets.sol";
import {DeliveryPlace} from "../src/core/DeliveryPlace.sol";
import {TadleFactory} from "../src/factory/TadleFactory.sol";
import {OfferStatus, StockStatus, AbortOfferStatus, OfferType, StockType, OfferSettleType} from "../src/storage/OfferStatus.sol";
import {IPerMarkets, OfferInfo, StockInfo, MakerInfo, CreateOfferParams} from "../src/interfaces/IPerMarkets.sol";
import {TokenBalanceType, ITokenManager} from "../src/interfaces/ITokenManager.sol";
import {GenerateAddress} from "../src/libraries/GenerateAddress.sol";
import {MockERC20Token} from "./mocks/MockERC20Token.sol";
import {WETH9} from "./mocks/WETH9.sol";
import {UpgradeableProxy} from "../src/proxy/UpgradeableProxy.sol";
contract Bid_Offer_Test is Test {
SystemConfig systemConfig;
CapitalPool capitalPool;
TokenManager tokenManager;
PreMarktes preMarktes;
DeliveryPlace deliveryPlace;
address marketPlace;
WETH9 weth9;
MockERC20Token mockUSDCToken;
MockERC20Token mockPointToken;
address admin = vm.addr(0x123);
address user = vm.addr(0x124);
address user2 = vm.addr(0xd125);
uint256 basePlatformFeeRate = 5_000;
uint256 baseReferralRate = 300_000;
bytes4 private constant INITIALIZE_OWNERSHIP_SELECTOR =
bytes4(keccak256(bytes("initializeOwnership(address)")));
function setUp() public {
// deploy mocks
weth9 = new WETH9();
TadleFactory tadleFactory = new TadleFactory(admin);
mockUSDCToken = new MockERC20Token();
mockPointToken = new MockERC20Token();
SystemConfig systemConfigLogic = new SystemConfig();
CapitalPool capitalPoolLogic = new CapitalPool();
TokenManager tokenManagerLogic = new TokenManager();
PreMarktes preMarktesLogic = new PreMarktes();
DeliveryPlace deliveryPlaceLogic = new DeliveryPlace();
bytes memory deploy_data = abi.encodeWithSelector(
INITIALIZE_OWNERSHIP_SELECTOR,
admin
);
vm.startPrank(admin);
address systemConfigProxy = tadleFactory.deployUpgradeableProxy(
1,
address(systemConfigLogic),
bytes(deploy_data)
);
address preMarktesProxy = tadleFactory.deployUpgradeableProxy(
2,
address(preMarktesLogic),
bytes(deploy_data)
);
address deliveryPlaceProxy = tadleFactory.deployUpgradeableProxy(
3,
address(deliveryPlaceLogic),
bytes(deploy_data)
);
address capitalPoolProxy = tadleFactory.deployUpgradeableProxy(
4,
address(capitalPoolLogic),
bytes(deploy_data)
);
address tokenManagerProxy = tadleFactory.deployUpgradeableProxy(
5,
address(tokenManagerLogic),
bytes(deploy_data)
);
// Deploy Proxy
systemConfig = SystemConfig(systemConfigProxy);
capitalPool = CapitalPool(capitalPoolProxy);
tokenManager = TokenManager(tokenManagerProxy);
preMarktes = PreMarktes(preMarktesProxy);
deliveryPlace = DeliveryPlace(deliveryPlaceProxy);
// initialize
systemConfig.initialize(basePlatformFeeRate, baseReferralRate);
tokenManager.initialize(address(weth9));
address[] memory tokenAddressList = new address[](2);
tokenAddressList[0] = address(mockUSDCToken);
tokenAddressList[1] = address(weth9);
tokenManager.updateTokenWhiteListed(tokenAddressList, true);
// create market place
systemConfig.createMarketPlace("Backpack", false);
marketPlace = GenerateAddress.generateMarketPlaceAddress("Backpack");
deal(address(mockUSDCToken), admin, 100000000 * 10 ** 18);
vm.stopPrank();
vm.warp(1719826275);
}
function test_Bid_Offer_Will_Revert_When_listed () public {
// Give user some balance
deal(user, 1);
deal(address(mockUSDCToken), user, 1 ether);
uint256 offerId = 0 ;
// Generate makerAddr Address
address makerAddr = GenerateAddress.generateMakerAddress(offerId);
// Generate offer Address
address offerAddr = GenerateAddress.generateOfferAddress(offerId);
// Generate offer Stock Address
address stockAddr = GenerateAddress.generateStockAddress(offerId);
// points
uint256 _points = 500;
// amount
uint256 _amount = 63 ;
// Collateral Rate
uint256 collateralRate = 12000;
uint256 tax = 500 ;
// Create offer params
CreateOfferParams memory params = CreateOfferParams(
marketPlace,
address(mockUSDCToken),
_points,
_amount,
collateralRate,
tax,
OfferType.Bid, //@audit-info offer must be bid to List offer
// This line in listOffer() if (stockInfo.stockType != StockType.Bid) {
// revert InvalidStockType(StockType.Bid, stockInfo.stockType);
// } enforces it.
OfferSettleType.Turbo
);
vm.startPrank(user);
// Aprove TokenManager
mockUSDCToken.approve(address(tokenManager), type(uint256).max);
//@audit-info Expected Event Emitted by the function CreateOffer()
vm.expectEmit(true, true, true, true , address(preMarktes));
emit CreateOffer(
offerAddr, // offer address
makerAddr, // makeraddress
stockAddr, // stock address
marketPlace, // marketPlace
user, // user
_points, // point
_amount // amount
);
// create Offer with msg.value == 0
preMarktes.createOffer(params);
// We can as well Create Taker with msg.value == 0
preMarktes.createTaker(offerAddr, 20);
// after account creation offerId is incremented by one
// Generate stock address from that id
address stock1Addr = GenerateAddress.generateStockAddress(1);
//@@audit-info Expected error selector
// enum StockType {
// Ask, = 0
// Bid = 1
// }
bytes4 selector = IPerMarkets.InvalidStockType.selector;
vm.expectRevert(abi.encodeWithSelector(selector, StockType.Bid, 0));
// List offer for sale will revert if stocktype is bid
preMarktes.listOffer(stock1Addr, _amount, collateralRate);
vm.stopPrank();
}
// expected events
event CreateOffer(
address indexed _offer,
address indexed _maker,
address indexed _stock,
address _marketPlace,
address _authority,
uint256 _points,
uint256 _amount
);
}

Run command :

forge test --mt forge test --mt test_Bid_Offer_Will_Revert_When_listed -vvv

Impact

This affects core functionality of the protocol and users with bid offers will be unable to list Offer

Tools Used

Manual Review

Recommendations

function createTaker(address _offer, uint256 _points) external payable {
// ... (rest of logic)
stockInfoMap[stockAddr] = StockInfo({
id: offerId,
stockStatus: StockStatus.Initialized,
stockType: offerInfo.offerType == OfferType.Ask
+ ? StockType.Ask
+ : StockType.Bid,
- ? StockType.Bid
- : StockType.Ask,
authority: _msgSender(),
maker: offerInfo.maker,
preOffer: _offer,
points: _points,
amount: depositAmount,
offer: address(0x0)
});
// ... (rest of logic)
}
Updates

Lead Judging Commences

0xnevi Lead Judge
11 months ago
0xnevi Lead Judge 11 months ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement
Assigned finding tags:

[invalid] finding-PreMarkets-createTaker-stockType

Appeal created

air Submitter
11 months ago
0xnevi Lead Judge
10 months ago
0xnevi Lead Judge 10 months ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement
Assigned finding tags:

[invalid] finding-PreMarkets-createTaker-stockType

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.