DatingDapp

AI First Flight #6
Beginner FriendlyFoundrySolidityNFT
EXP
View results
Submission Details
Impact: high
Likelihood: high
Invalid

Soulbound Implementation Incomplete - approve() and setApprovalForAll() Bypass Transfer Rest

Root + Impact

Description

  • Describe the normal behavior in one or more sentences


  • Explain the specific issue or problem in one or more sentences

  • The contract blocks transferFrom() and safeTransferFrom() but fails to override approve() and setApprovalForAll().

// ❌ These functions are NOT overridden:
// function approve(address to, uint256 tokenId) public virtual override
// function setApprovalForAll(address operator, bool approved) public virtual override
function transferFrom(address, address, uint256) public pure override {
revert SoulboundTokenCannotBeTransferred(); // Never reached if caller is approved
}// Root cause in the codebase with @> marks to highlight the relevant section

Risk

Likelihood:

  • Reason 1 // Describe WHEN this will occur (avoid using "if" statements)

  • High issue and needs to be resolved

  • Reason 2

Impact:

  • Impact 1

  • Complete bypass of soulbound restrictions

  • Users can sell/trade their dating profiles

  • Impact 2

Proof of Concept

function testSoulboundBypass() public {
// User mints profile
vm.prank(alice);
nft.mintProfile("Alice", 25, "ipfs://alice");
uint256 tokenId = nft.profileToToken(alice);
// Alice approves Bob (or Alice's key is compromised)
vm.prank(alice);
nft.approve(bob, tokenId);
// Bob calls transferFrom through the inherited ERC721 function
vm.prank(bob);
// This bypasses the overridden transferFrom!
IERC721(address(nft)).transferFrom(alice, bob, tokenId);
// ❌ Transfer succeeded! Token is now owned by Bob
assertEq(nft.ownerOf(tokenId), bob);
assertEq(nft.profileToToken(bob), tokenId); // Bob has Alice's profile
}

Recommended Mitigation

- remove this code
+ add this code
/// @notice Override approve to prevent any approvals
function approve(address, uint256) public pure override {
revert SoulboundTokenCannotBeTransferred();
}
/// @notice Override setApprovalForAll to prevent operator approvals
function setApprovalForAll(address, bool) public pure override {
revert SoulboundTokenCannotBeTransferred();
}
/// @notice Override getApproved to always return zero address
function getApproved(uint256) public pure override returns (address) {
return address(0);
}
/// @notice Override isApprovedForAll to always return false
function isApprovedForAll(address, address) public pure override returns (bool) {
return false;
}
/// @notice Override transferFrom to prevent any transfer
function transferFrom(address, address, uint256) public pure override {
revert SoulboundTokenCannotBeTransferred();
}
/// @notice Override safeTransferFrom to prevent any transfer
function safeTransferFrom(address, address, uint256) public pure override {
revert SoulboundTokenCannotBeTransferred();
}
/// @notice Override safeTransferFrom with data to prevent any transfer
function safeTransferFrom(address, address, uint256, bytes memory) public pure override {
revert SoulboundTokenCannotBeTransferred();
}
/// @notice Override _update to prevent any transfer (internal safeguard)
function _update(address to, uint256 tokenId, address auth)
internal
override
returns (address)
{
address from = _ownerOf(tokenId);
// Allow minting (from == address(0))
if (from == address(0)) {
return super._update(to, tokenId, auth);
}
// Allow burning (to == address(0))
if (to == address(0)) {
return super._update(to, tokenId, auth);
}
// Prevent all other transfers
revert SoulboundTokenCannotBeTransferred();
}
Updates

Lead Judging Commences

ai-first-flight-judge Lead Judge about 2 hours ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

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

Give us feedback!