DatingDapp

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

[L-01] `approve()` and `setApprovalForAll()` Not Blocked on Soulbound Tokens

[L-01] approve() and setApprovalForAll() Not Blocked on Soulbound Tokens

Scope

  • SoulboundProfileNFT.sol

Description

The contract overrides transferFrom and safeTransferFrom to enforce soulbound behavior, but does NOT override approve() or setApprovalForAll(). Users can grant approvals on non-transferable tokens, emitting misleading Approval events.

Risk

Likelihood: High — Any token holder can call approve.

Impact: Low — No fund loss. Approvals are functionally useless since transfers revert. But misleading for integrating protocols and violates ERC-721 behavioral expectations.

Severity: Low

  • SWC: N/A

  • CWE: CWE-284 (Improper Access Control)

  • Evidence Grade: A

Proof of Concept

Alice mints a soulbound profile NFT. She then calls approve(bob, tokenId) to grant Bob approval over her token. The call succeeds and emits an Approval event, even though the token can never actually be transferred. She can also call setApprovalForAll(bob, true) to grant Bob operator status across all her tokens. Both approvals are accepted and stored on-chain despite being functionally useless.

function test_FINDING007_approve_not_blocked() public {
vm.prank(alice); nft.mintProfile("Alice", 25, "ipfs://a");
uint256 tokenId = nft.profileToToken(alice);
// approve() succeeds on soulbound token
vm.prank(alice); nft.approve(bob, tokenId);
assertEq(nft.getApproved(tokenId), bob, "Approval set on soulbound");
// setApprovalForAll() also succeeds
vm.prank(alice); nft.setApprovalForAll(bob, true);
assertTrue(nft.isApprovedForAll(alice, bob), "Operator approved on soulbound");
}

forge test --match-test test_FINDING007_approve_not_blocked -vvvvPASS

Recommended Mitigation

For a soulbound token, all transfer-related operations should be blocked — not just the transfers themselves, but also the approvals that precede them. Overriding approve() and setApprovalForAll() to revert with the same custom error used by transferFrom ensures consistent behavior: no approval can be set, no misleading events are emitted, and integrating protocols receive an immediate signal that this token is non-transferable.

+function approve(address, uint256) public pure override {
+ revert SoulboundTokenCannotBeTransferred();
+}
+function setApprovalForAll(address, bool) public pure override {
+ 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!