Pieces Protocol

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

`abi.encodePacked()` should not be used with dynamic types when passing the result to a hash function such as `keccak256()`

Summary

abi.encodePacked() with dynamic types (like strings) can lead to hash collisions if the result is hashed. Although this specific code doesn't hash the result, using abi.encodePacked with dynamic types is discouraged as per best practices. The recommendation is to use bytes.concat() when dealing with bytes/strings to ensure safe concatenation without unexpected collisions.

<details><summary>2 Found Instances</summary>
- Found in src/TokenDivider.sol [Line: 116]()
```solidity
string(abi.encodePacked(ERC721(nftAddress).name(), "Fraccion")),
```
- Found in src/TokenDivider.sol [Line: 117]()
```solidity
string(abi.encodePacked("F", ERC721(nftAddress).symbol())));
```
</details>

Vulnerability Details

Technical Explanation:

  1. abi.encodePacked() Behavior:

    • abi.encodePacked() tightly packs input data without padding. For dynamic types (e.g., string, bytes), this can lead to ambiguous concatenation. For example:

      • abi.encodePacked("Hello", "World") and abi.encodePacked("Hell", "oWorld") both produce the same byte sequence (0x48656c6c6f576f726c64).

    • If these results are used in hashing (e.g., for generating identifiers, signatures, or verification), collisions can occur, leading to security vulnerabilities (e.g., signature replay attacks, identifier spoofing).

  2. Contract-Specific Risk:

    • In divideNft, the ERC20 token name and symbol are derived from the NFT’s name and symbol. For example:

      solidity

      Copy

      string(abi.encodePacked(ERC721(nftAddress).name(), "Fraccion"))
    • If two different NFTs have names that produce the same packed byte sequence when concatenated with "Fraccion", their ERC20 tokens would share the same name and symbol. This could mislead users or enable fraudulent token impersonation.

Impact

If two different inputs produce the same packed output (e.g., encodePacked("ab", "c") vs. encodePacked("a", "bc")), their concatenated results would be identical. This could lead to unintended behavior if these results are used in critical operations.

Tools Used

Aderyn

Recommendations

Use abi.encode() instead which will pad items to 32 bytes, which will prevent hash collisions (e.g. abi.encodePacked(0x123,0x456) => 0x123456 => abi.encodePacked(0x1,0x23456), but abi.encode(0x123,0x456) => 0x0...1230...456). Unless there is a compelling reason, abi.encode should be preferred. If there is only one argument to abi.encodePacked() it can often be cast to bytes() or bytes32() instead.
If all arguments are strings and or bytes, bytes.concat() should be used instead.

ERC20ToGenerateNftFraccion erc20Contract = new ERC20ToGenerateNftFraccion(
- string(abi.encodePacked(ERC721(nftAddress).name(), "Fraccion")),
- string(abi.encodePacked("F", ERC721(nftAddress).symbol()))
+ string(bytes.concat(bytes(ERC721(nftAddress).name()), bytes("Fraccion"))),
+ string(bytes.concat(bytes("F"), bytes(ERC721(nftAddress).symbol())))
);
Updates

Lead Judging Commences

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

Support

FAQs

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