Pieces Protocol

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

Critical: Zero Price Exploit in sellErc20 Function Due to Missing Price Validation

The sellErc20 function lacks input validation for the price parameter, allowing users to create sell orders with zero price. This leads to fee calculation issues and enables free token acquisitions.
**Lines of code**
https://github.com/Cyfrin/2025-01-pieces-protocol/blob/4ef5e96fced27334f2a62e388a8a377f97a7f8cb/src/TokenDivider.sol#L221
```solidity
//Vulnerable Code Section
function sellErc20(address nftPegged, uint256 price, uint256 amount) external {
// ❌ No validation for price == 0
if(nftPegged == address(0)) {
revert TokenDivider__NftAddressIsZero();
}
if( amount == 0) {
revert TokenDivider__AmountCantBeZero();
}
// ... rest of function
}
```
**In buyOrder function:**
https://github.com/Cyfrin/2025-01-pieces-protocol/blob/4ef5e96fced27334f2a62e388a8a377f97a7f8cb/src/TokenDivider.sol#L274
```solidity
uint256 fee = order.price / 100; // ❌ Zero price leads to zero fees
```
## Vulnerability Details
Here's the sequence that demonstrates the problem:
**User lists tokens for sale with zero price:**
```solidity
divider.sellErc20(nftAddress, 0, amount);
```
**Buyer can purchase tokens for free:**
**Calculations in buyOrder:**
```solidity
fee = price/100 = 0/100 = 0
sellerFee = fee/2 = 0/2 = 0
//price + sellerFee = 0 + 0 = 0
if(msg.value < order.price + sellerFee ) {
revert TokenDivider__InsuficientEtherForFees();
}
```
## Coded POC
- create a file in the test folder in the project repo, paste this code and run the test.
```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;
import {Test, console} from "forge-std/Test.sol";
import {TokenDivider} from "../src/TokenDivider.sol";
import {ERC721Mock} from './mocks/ERC721Mock.sol';
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
contract TokenDividerTest is Test {
TokenDivider public tokenDivider;
ERC721Mock public mockNFT;
address public owner;
address public user1;
address public user2;
uint256 constant NFT_ID = 0; //first nft id will be 0
uint256 constant INITIAL_AMOUNT = 1000;
function setUp() public {
owner = makeAddr("owner");
user1 = makeAddr("user1");
user2 = makeAddr("user2");
vm.startPrank(owner);
tokenDivider = new TokenDivider();
mockNFT = new ERC721Mock();
vm.stopPrank();
vm.startPrank(owner);
mockNFT.mint(user1); // minted tokenId 0 to user1
vm.stopPrank();
}
function testZeroPriceExploit() public {
// Step 1: User1 divides their NFT into fractions
vm.startPrank(user1);
mockNFT.approve(address(tokenDivider), 0);
tokenDivider.divideNft(address(mockNFT), 0, INITIAL_AMOUNT);
// Get the ERC20 token address
TokenDivider.ERC20Info memory info = tokenDivider.getErc20InfoFromNft(address(mockNFT));
address erc20Address = info.erc20Address;
// Approve tokens for selling
IERC20(erc20Address).approve(address(tokenDivider), INITIAL_AMOUNT);
// Step 2: Create a sell order with price = 0
uint256 sellAmount = 500;
uint256 price = 0; // Zero price!
tokenDivider.sellErc20(address(mockNFT), price, sellAmount);
vm.stopPrank();
// Step 3: User2 buys the tokens
vm.startPrank(user2);
// sent 0 ETH to buy the tokens
// Fee calculation: fee = price/100 = 0/100 = 0
// sellerFee = fee/2 = 0/2 = 0
// Total required = price + sellerFee = 0 + 0 = 0
tokenDivider.buyOrder{value: 0}(0, user1);
// Verify user2 received the tokens
uint256 user2Balance = tokenDivider.getBalanceOf(user2, erc20Address);
assertEq(user2Balance, sellAmount, "User2 should receive the tokens");
// Verify no ETH was transferred (since price and fees are 0)
assertEq(user1.balance, 0, "Seller should receive no ETH");
assertEq(owner.balance, 0, "Owner should receive no fees");
vm.stopPrank();
}
}
```
## Impact:
**Free Token Acquisition:** Buyers can obtain tokens without payment
**Lost Protocol Fees:** Platform receives no fees *(fee = price/100 = 0)*
**Lost Seller Revenue:** Sellers receive no payment *(price - sellerFee = 0 - 0 = 0)*
**Economic Implications:**
- Undermines the entire trading mechanism
- Protocol loses revenue from fees
- Creates unfair market dynamics
## Tools Used
Manual Review
## Fix Recommendation:
Add price validation in the sellErc20 function:
```solidity
// Correct Implementation
function sellErc20(address nftPegged, uint256 price, uint256 amount) external {
if(price == 0) {
revert TokenDivider__InvalidPrice();
}
if(nftPegged == address(0)) {
revert TokenDivider__NftAddressIsZero();
}
// ... rest of the function
}
```
The fix ensures that all sell orders must have a non-zero price, maintaining the economic integrity of the platform and ensuring proper fee collection.
Updates

Lead Judging Commences

juan_pedro_ventu Lead Judge 4 months ago
Submission Judgement Published
Invalidated
Reason: Design choice

Support

FAQs

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