Pieces Protocol

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

Insufficient Input Validation in TokenDivider's Core Functions Leading to Potential Contract State Corruption

Summary

The TokenDivider contract performs minimal input validation in critical functions, particularly in divideNft. While basic zero-checks are implemented, the contract lacks comprehensive validation of input parameters, contract interactions, and state preconditions. This insufficient validation could lead to contract state corruption and potential system-wide failures.

Impact

Severity: High

The insufficient input validation could result in:

  • Creation of invalid or unusable fractional tokens

  • Permanent locking of NFTs in the contract

  • State inconsistencies between NFTs and their fractional tokens

  • System-wide failure due to malformed ERC20 token creation

  • Potential DoS attacks through crafted invalid inputs

Detailed Proof of Concept

Current Implementation

function divideNft(address nftAddress, uint256 tokenId, uint256 amount)
onlyNftOwner(nftAddress, tokenId)
onlyNftOwner(nftAddress ,tokenId) external {
// Basic validations only
if(nftAddress == address(0)) {
revert TokenDivider__NftAddressIsZero();
}
if(amount == 0) {
revert TokenDivider__AmountCantBeZero();
}
// Missing validations before critical operations
ERC20ToGenerateNftFraccion erc20Contract = new ERC20ToGenerateNftFraccion(
string(abi.encodePacked(ERC721(nftAddress).name(), "Fraccion")),
string(abi.encodePacked("F", ERC721(nftAddress).symbol())));
// ... rest of the function
}

Attack Scenarios

  1. Non-Compliant NFT Contract

contract MalformedNFT {
// Missing ERC721 implementation
function ownerOf(uint256) external pure returns (address) {
return address(0);
}
function name() external pure returns (string memory) {
revert();
}
function symbol() external pure returns (string memory) {
return "";
}
}
  1. State Corruption through Invalid TokenId

contract TokenDividerExploit {
TokenDivider target;
function exploit(address nftAddress) external {
// Using maximum uint256 as tokenId
target.divideNft(nftAddress, type(uint256).max, 1000);
// Using nonexistent tokenId
target.divideNft(nftAddress, 999999, 1000);
}
}

Attack Simulation

  1. Deploy Malformed NFT

// Deploy a non-compliant NFT contract
MalformedNFT malNFT = new MalformedNFT();
// Attempt to fractionalize with invalid NFT
divider.divideNft(address(malNFT), 1, 1000);
// Result: Creates invalid ERC20 tokens with empty name/symbol
  1. Invalid Token ID Attack

function executeAttack(address legitimateNFT) external {
// Approve nonexistent token
IERC721(legitimateNFT).approve(address(divider), type(uint256).max);
// Attempt fractionalization
divider.divideNft(legitimateNFT, type(uint256).max, 1000);
// Result: Contract state corruption or permanent failure
}
  1. Amount Overflow Attack

function amountOverflowAttack(address nft, uint256 tokenId) external {
// Using massive amount value
uint256 largeAmount = type(uint256).max;
divider.divideNft(nft, tokenId, largeAmount);
// Result: Potential overflow in balance tracking
}

Recommendations

1. Comprehensive Input Validation Function

function _validateInputs(
address nftAddress,
uint256 tokenId,
uint256 amount
) private view {
// Basic checks
if(nftAddress == address(0)) {
revert TokenDivider__NftAddressIsZero();
}
if(amount == 0) {
revert TokenDivider__AmountCantBeZero();
}
// Amount bounds check
if(amount > type(uint128).max) {
revert TokenDivider__AmountTooLarge();
}
// Verify NFT contract existence and implementation
uint256 codeSize;
assembly {
codeSize := extcodesize(nftAddress)
}
if(codeSize == 0) {
revert TokenDivider__NonExistentContract();
}
// Verify ERC721 interface compliance
if(!IERC165(nftAddress).supportsInterface(type(IERC721).interfaceId)) {
revert TokenDivider__InvalidNFTImplementation();
}
// Verify token existence
try IERC721(nftAddress).ownerOf(tokenId) returns (address owner) {
if(owner == address(0)) {
revert TokenDivider__InvalidTokenId();
}
} catch {
revert TokenDivider__NonexistentToken();
}
// Verify token not already fractionalized
if(nftToErc20Info[nftAddress].erc20Address != address(0)) {
revert TokenDivider__AlreadyFractionalized();
}
}

2. Safe ERC20 Token Creation

function _createFractionToken(
address nftAddress,
uint256 tokenId
) private returns (ERC20ToGenerateNftFraccion) {
// Safely get NFT name and symbol
string memory nftName;
string memory nftSymbol;
try ERC721(nftAddress).name() returns (string memory name) {
nftName = name;
} catch {
nftName = "UnknownNFT";
}
try ERC721(nftAddress).symbol() returns (string memory symbol) {
nftSymbol = symbol;
} catch {
nftSymbol = "UNKNFT";
}
// Validate derived names
require(bytes(nftName).length > 0, "Invalid NFT name");
require(bytes(nftSymbol).length > 0, "Invalid NFT symbol");
return new ERC20ToGenerateNftFraccion(
string(abi.encodePacked(nftName, " Fraction")),
string(abi.encodePacked("f", nftSymbol))
);
}

3. Updated Main Function Implementation

function divideNft(
address nftAddress,
uint256 tokenId,
uint256 amount
) external nonReentrant {
// Complete input validation
_validateInputs(nftAddress, tokenId, amount);
// Safe token creation
ERC20ToGenerateNftFraccion erc20Contract = _createFractionToken(
nftAddress,
tokenId
);
// Rest of the implementation with additional safety checks
// ...
}

These improvements provide:

  • Comprehensive input validation

  • Protection against malformed NFT contracts

  • Safe handling of token names and symbols

  • Prevention of state corruption

  • Clear error messages for different failure cases

  • Protection against overflow and underflow

  • Validation of contract existence and interface compliance

Updates

Lead Judging Commences

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

Support

FAQs

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