Pieces Protocol

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

Absent Validation of NFT Fractionalization State

Summary

The transferErcTokens function in the TokenDivider contract lacks validation to ensure that the provided NFT address has been properly fractionalized before proceeding with token transfers. This oversight could lead to operations on non-existent or invalid token states.

Vulnerability Detail

Current implementation:

function transferErcTokens(address nftAddress, address to, uint256 amount) external {
// Basic input validation...
// Direct access without validation
ERC20Info memory tokenInfo = nftToErc20Info[nftAddress];
// No validation if tokenInfo.erc20Address exists or is valid
// Continues with transfer operations...
balances[msg.sender][tokenInfo.erc20Address] -= amount;
balances[to][tokenInfo.erc20Address] += amount;
IERC20(tokenInfo.erc20Address).transferFrom(msg.sender, to, amount);
}

Impact

This vulnerability could result in:

  1. Operations on non-existent ERC20 tokens

  2. Silent failures in transfer operations

  3. Inconsistent contract state

  4. Loss of user funds through failed transfers

  5. Potential system-wide state corruption

Tool Used

Foundry

Proof of Concept

contract NFTFractionalizationAttacker {
TokenDivider target;
constructor(address _target) {
target = TokenDivider(_target);
}
function exploit() external {
// 1. Create a random NFT address that hasn't been fractionalized
address randomNft = address(uint160(uint256(keccak256("random"))));
// 2. Attempt transfer with unfractionalized NFT
target.transferErcTokens(
randomNft,
address(this),
1000
);
// Function will access uninitialized tokenInfo
}
}
// Test Contract to Demonstrate Vulnerability
contract ValidationTestSuite {
TokenDivider target;
function testUnfractionalizedTransfer() public {
// 1. Deploy legitimate NFT
MockNFT nft = new MockNFT();
// 2. Attempt transfer without fractionalization
try target.transferErcTokens(
address(nft),
address(1),
100
) {
fail("Should revert but didn't");
} catch {
// Expected revert
}
}
}

Recommended Mitigation Steps

  1. Add Fractionalization Status Validation

function _validateFractionalizationStatus(
address nftAddress
) private view returns (ERC20Info memory) {
ERC20Info memory tokenInfo = nftToErc20Info[nftAddress];
// Validate ERC20 token existence
require(
tokenInfo.erc20Address != address(0),
"NFT not fractionalized"
);
// Validate ERC20 contract exists
require(
AddressUtils.isContract(tokenInfo.erc20Address),
"Invalid ERC20 contract"
);
// Validate token implementation
require(
IERC165(tokenInfo.erc20Address).supportsInterface(type(IERC20).interfaceId),
"Invalid token implementation"
);
return tokenInfo;
}
  1. Implement Status Tracking

contract TokenDivider {
// Add status tracking
mapping(address => bool) public isNFTFractionalized;
// Update status in divideNft
function divideNft(...) {
// ... existing logic ...
isNFTFractionalized[nftAddress] = true;
}
// Clear status in claimNft
function claimNft(...) {
// ... existing logic ...
isNFTFractionalized[nftAddress] = false;
}
}
  1. Complete Implementation Example

function transferErcTokens(
address nftAddress,
address to,
uint256 amount
) external nonReentrant {
// Input validation
_validateInputs(nftAddress, to, amount);
// Validate fractionalization status
ERC20Info memory tokenInfo = _validateFractionalizationStatus(nftAddress);
// Validate balances
require(
balances[msg.sender][tokenInfo.erc20Address] >= amount,
"Insufficient balance"
);
// Update state
_updateBalances(msg.sender, to, tokenInfo.erc20Address, amount);
// Execute transfer
require(
IERC20(tokenInfo.erc20Address).transferFrom(msg.sender, to, amount),
"Transfer failed"
);
emit TokensTransferred(
msg.sender,
to,
tokenInfo.erc20Address,
amount
);
}
  1. Add Recovery Mechanism

function recoverInvalidState(
address nftAddress
) external onlyOwner {
require(
!isNFTFractionalized[nftAddress],
"NFT is still fractionalized"
);
// Clean up invalid state
delete nftToErc20Info[nftAddress];
emit StateRecovered(nftAddress);
}

Additional Recommendations

  1. Implement State Verification

function verifyFractionalization(
address nftAddress
) external view returns (bool, string memory) {
if (!isNFTFractionalized[nftAddress]) {
return (false, "Not fractionalized");
}
ERC20Info memory info = nftToErc20Info[nftAddress];
if (info.erc20Address == address(0)) {
return (false, "Invalid ERC20 address");
}
return (true, "Valid fractionalization");
}
  1. Add Event Logging

event FractionalizationStatusChanged(
address indexed nftAddress,
bool status,
uint256 timestamp
);

Updates

Lead Judging Commences

fishy Lead Judge 5 months ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

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