Pieces Protocol

First Flight #32
Beginner FriendlyFoundrySolidityNFT
100 EXP
View results
Submission Details
Severity: high
Valid

There should be a check to ensure the price is not zero by mistake.

Summary

The sellErc20 function in the TokenDivider contract does not validate if the price parameter is greater than zero. This oversight can lead to the creation of orders with zero price, causing unintended behavior where sellers might lose control over their tokens without receiving any Ether in return.

Lines of code
https://github.com/Cyfrin/2025-01-pieces-protocol/blob/main/src/TokenDivider.sol#L221-241

Vulnerability Details

If user A wants to make a sell order and mitsakenly sets the price as 0 ether.

User B will buy the Order, expected to pay 0 ether + the fee (0.5% of the price)

User B entered 0 as the msg.value, 0 ether will deducted from him/her

Making him/her not paying and user A will lose access to his/her order or money once set.

**Proof Of Concept**
https://github.com/Cyfrin/2025-01-pieces-protocol/blob/main/src/TokenDivider.sol#L221-L249
Issue
The function didn't checks if the price is greater than zero.
If the seller accidentally sets the price to zero.
```solidity
s_userToSellOrders[msg.sender].push(
SellOrder({seller: msg.sender, erc20Address: tokenInfo.erc20Address, price: price, amount: amount})
);
```
there is no mechanism to revert that.
This leads to potential loss of funds.
A test case
```solidity
function testSellingAtZeroErc20() public nftDivided {
ERC20Mock erc20Mock = ERC20Mock(tokenDivider.getErc20InfoFromNft(address(erc721Mock)).erc20Address);
uint256 ownerBalanceBefore = address(tokenDivider.owner()).balance;
uint256 userBalanceBefore = address(USER).balance;
uint256 user2TokenBalanceBefore = tokenDivider.getBalanceOf(USER2, address(erc20Mock));
uint256 user2BalanceBefore = address(USER2).balance;
console.log("user2TokenBalanceBefore :", user2TokenBalanceBefore);
console.log("user2BalanceBefore :", user2BalanceBefore);
vm.startPrank(USER);
erc20Mock.approve(address(tokenDivider), AMOUNT);
uint256 price = 0;
//setting the price as zero
tokenDivider.sellErc20(address(erc721Mock), price, AMOUNT); // Creamos una orden de venta por 1 ETH
uint256 fees = price / 100;
uint256 sellerFee = fees / 2;
vm.stopPrank();
vm.prank(USER2);
tokenDivider.buyOrder{value: (0)}(0, USER);
uint256 ownerBalanceAfter = address(tokenDivider.owner()).balance;
uint256 user2TokenBalanceAfter = tokenDivider.getBalanceOf(USER2, address(erc20Mock));
uint256 userBalanceAfter = address(USER).balance;
uint256 user2BalanceAfter = address(USER2).balance;
//AMount the USER2 supposed to pay
console.log("AmountUser2IntendToPay :", price + fees / 2);
console.log("user2TokenBalanceAfter :", user2TokenBalanceAfter);
console.log("user2BalanceAfter :", user2BalanceAfter);
assertEq(user2TokenBalanceAfter - AMOUNT, user2TokenBalanceBefore);
assertEq(ownerBalanceAfter - fees, ownerBalanceBefore);
vm.expectRevert();
assertEq(user2BalanceAfter, user2BalanceBefore - price - sellerFee, "All USER2 ETH gone");
if (block.chainid != 31337) {
assertEq(userBalanceAfter - AMOUNT + fees / 2, userBalanceBefore);
} else {
assertEq(user2TokenBalanceAfter, 1e18);
}
}
```
Result
```solidity
Ran 1 test for test/unit/TokenDividerTest.t.sol:TokenDiverTest
[PASS] testSellingAtZeroErc20() (gas: 1195262)
Logs:
user2TokenBalanceBefore : 0
user2BalanceBefore : 10000000000000000000
AmountUser2IntendToPay : 0
user2TokenBalanceAfter : 2000000000000000000
user2BalanceAfter : 10000000000000000000
Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 12.33ms (5.50ms CPU time)
Ran 1 test suite in 45.82ms (12.33ms CPU time): 1 tests passed, 0 failed, 0 skipped (1 total tests)
```

Impact

In the `sellErcErc20()` function, there is no check to handle cases where the seller mistakenly set the `price` to 0 and no function to `cancelOrder`.
This could lead to a situation where seller lose funds and access to his/her order, causing potential financial harm.

Tools Used

Manual Review and Foundry

Recommendations

1. To address this issue, modify the `sellErc20()` function to check if the price is greater than zero.

2. Let there be a function for seller to cancelOrder if they want to.

Updates

Lead Judging Commences

fishy Lead Judge 5 months ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity

Appeal created

osuolale Submitter
5 months ago
fishy Lead Judge 5 months ago
Submission Judgement Published
Validated
Assigned finding tags:

Sell orders cant be canceled

Support

FAQs

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