# RACE #8 - ERC721 Roles

{% embed url="<https://ventral.digital/posts/2022/7/25/secureum-bootcamp-epoch-july-race-8>" %}
RACE #8
{% endembed %}

<figure><img src="/files/QiwxEVomXUoVcuXEqQmB" alt=""><figcaption><p>RACE #8 result</p></figcaption></figure>

{% hint style="info" %}
*Note: All 8 questions in this RACE are based on the following ERC721 implementation. This is the same contract you will see for all the 8 questions in this RACE. The implementation is adapted from a well-known contract. The question is below the shown contract.*
{% endhint %}

```solidity
pragma solidity >=0.8.0;

abstract contract ERC721 {

    event Transfer(address indexed from, address indexed to, uint256 indexed id);

    event Approval(address indexed owner, address indexed spender, uint256 indexed id);

    event ApprovalForAll(address indexed owner, address indexed operator, bool approved);

    string public name;

    string public symbol;

    function tokenURI(uint256 id) public view virtual returns (string memory);

    mapping(uint256 => address) internal _ownerOf;

    mapping(address => uint256) internal _balanceOf;

    function ownerOf(uint256 id) public view virtual returns (address owner) {
        require((owner = _ownerOf[id]) != address(0), "NOT_MINTED");
    }

    function balanceOf(address owner) public view virtual returns (uint256) {
        require(owner != address(0), "ZERO_ADDRESS");

        return _balanceOf[owner];
    }

    mapping(uint256 => address) public getApproved;

    mapping(address => mapping(address => bool)) public isApprovedForAll;

    constructor(string memory _name, string memory _symbol) {
        name = _name;
        symbol = _symbol;
    }

    function approve(address spender, uint256 id) public virtual {
        address owner = _ownerOf[id];

        require(msg.sender == owner || isApprovedForAll[owner][msg.sender], "NOT_AUTHORIZED");

        getApproved[id] = spender;

        emit Approval(owner, spender, id);
    }

    function setApprovalForAll(address operator, bool approved) public virtual {
        isApprovedForAll[msg.sender][operator] = approved;

        emit ApprovalForAll(msg.sender, operator, approved);
    }

    function transferFrom(
        address from,
        address to,
        uint256 id
    ) public virtual {
        require(to != address(0), "INVALID_RECIPIENT");

        require(
            msg.sender == from || isApprovedForAll[from][msg.sender] || msg.sender == getApproved[id],
            "NOT_AUTHORIZED"
        );

        unchecked {
            _balanceOf[from]--;

            _balanceOf[to]++;
        }

        _ownerOf[id] = to;

        delete getApproved[id];

        emit Transfer(from, to, id);
    }

    function safeTransferFrom(
        address from,
        address to,
        uint256 id
    ) public virtual {
        transferFrom(from, to, id);

        require(
            to.code.length == 0 ||
                ERC721TokenReceiver(to).onERC721Received(msg.sender, from, id, "") ==
                ERC721TokenReceiver.onERC721Received.selector,
            "UNSAFE_RECIPIENT"
        );
    }

    function safeTransferFrom(
        address from,
        address to,
        uint256 id,
        bytes calldata data
    ) public virtual {
        transferFrom(from, to, id);

        require(
            to.code.length == 0 ||
                ERC721TokenReceiver(to).onERC721Received(msg.sender, from, id, data) ==
                ERC721TokenReceiver.onERC721Received.selector,
            "UNSAFE_RECIPIENT"
        );
    }

    function supportsInterface(bytes4 interfaceId) public view virtual returns (bool) {
        return
            interfaceId == 0x01ffc9a7 || // ERC165 Interface ID for ERC165
            interfaceId == 0x80ac58cd || // ERC165 Interface ID for ERC721
            interfaceId == 0x5b5e139f; // ERC165 Interface ID for ERC721Metadata
    }

    function _mint(address to, uint256 id) internal virtual {
        require(to != address(0), "INVALID_RECIPIENT");

        require(_ownerOf[id] == address(0), "ALREADY_MINTED");

        unchecked {
            _balanceOf[to]++;
        }

        _ownerOf[id] = to;

        emit Transfer(address(0), to, id);
    }

    function _burn(uint256 id) external virtual {
        address owner = _ownerOf[id];

        require(owner != address(0), "NOT_MINTED");

        unchecked {
            _balanceOf[owner]--;
        }

        delete _ownerOf[id];

        delete getApproved[id];

        emit Transfer(owner, address(0), id);
    }

    function _safeMint(address to, uint256 id) internal virtual {
        _mint(to, id);

        require(
            to.code.length == 0 ||
                ERC721TokenReceiver(to).onERC721Received(msg.sender, address(0), id, "") ==
                ERC721TokenReceiver.onERC721Received.selector,
            "UNSAFE_RECIPIENT"
        );
    }

    function _safeMint(
        address to,
        uint256 id,
        bytes memory data
    ) internal virtual {
        _mint(to, id);

        require(
            to.code.length == 0 ||
                ERC721TokenReceiver(to).onERC721Received(msg.sender, address(0), id, data) ==
                ERC721TokenReceiver.onERC721Received.selector,
            "UNSAFE_RECIPIENT"
        );
    }
}

abstract contract ERC721TokenReceiver {
    function onERC721Received(
        address,
        address,
        uint256,
        bytes calldata
    ) external virtual returns (bytes4) {
        return ERC721TokenReceiver.onERC721Received.selector;
    }
}
```

## Question 1 :white\_check\_mark:

The security concern(s) addressed explicitly in \_mint include

* [x] &#x20;A. Prevent minting to zero address -> `require(to != address(0))`
* [x] &#x20;B. Prevent reminting of NFTs -> `require(_ownerOf[id] == address(0))`
* [x] &#x20;C. Transparency by emitting event -> `emit Transfer(address(0), to, id)`
* [ ] &#x20;D. None of the above

## Question 2 :white\_check\_mark:

The security concerns in \_burn include

* [x] &#x20;A. Anyone can arbitrarily burn NFTs -> Does not verify if msg.sender is actually the owner
* [ ] &#x20;B. Potential integer underflow because of unchecked
* [ ] &#x20;C. Incorrect emission of event
* [ ] &#x20;D. None of the above

## Question 3 :white\_check\_mark:

The security concern(s) addressed explicitly in `_safeMint` include

* [x] &#x20;A. Validating if the recipient is an EOA -> `require(to.code.length == 0 || ...)`
* [ ] &#x20;B. Ensuring that the recipient can only be an EOA -> Can also be a contract that implements the `onERC721Received()` callback
* [x] &#x20;C. Validating if the recipient is an ERC721 aware contract -> `require(... || RC721TokenReceiver(to).onERC721Received(msg.sender, address(0), id, "") == ERC721TokenReceiver.onERC721Received.selector)`
* [ ] &#x20;D. None of the above

## Question 4 :white\_check\_mark:

Function `approve()`

* [x] &#x20;A. Allows the NFT owner to approve a spender -> `require(msg.sender == owner || ...)`
* [ ] &#x20;B. Allows the NFT spender to approve an operator -> That is `setApprovalForAll()`'s functionality
* [x] &#x20;C. Allows the NFT operator to approve a spender -> `require(... || isApprovedForAll[owner][msg.sender])`
* [ ] &#x20;D. None of the above

## Question 5 :white\_check\_mark:

Function `setApprovalForAll()`

* [ ] &#x20;A. Approves msg.sender to manage operator’s NFTs -> It actually approves operator to manager msg.sender's NFTs
* [ ] &#x20;B. Gives everyone approval to manage msg.sender’s NFTs -> Gives only the operator approval to manage msg.sender's NFTs
* [ ] &#x20;C. Revokes everyone’s approvals to manage msg.sender’s NFTs -> Revokes operator's approval to manage msg.sender's NFTs
* [x] &#x20;D. None of the above

## Question 6 :white\_check\_mark:

The security concern(s) in `transferFrom()` include

* [x] &#x20;A. Allowing the msg.sender to transfer any NFT -> Does not verify if msg.sender is the owner
* [x] &#x20;B. NFTs potentially stuck in recipient contracts -> Does not contain `onERC721Received()` callback verification
* [x] &#x20;C. Potential integer underflow -> `unchecked {_balanceOf[from]--; ...}`, `from` might have 0 NFT
* [ ] &#x20;D. None of the above

**My Comment:**

Here is OpenZeppelin's ERC721 `transferFrom()` implementation

```solidity
function transferFrom(address from, address to, uint256 tokenId) public virtual override {
    //solhint-disable-next-line max-line-length
    require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC721: caller is not token owner or approved");

    _transfer(from, to, tokenId);
}
```

```solidity
function _isApprovedOrOwner(address spender, uint256 tokenId) internal view virtual returns (bool) {
    address owner = ERC721.ownerOf(tokenId);
    return (spender == owner || isApprovedForAll(owner, spender) || getApproved(tokenId) == spender);
}
```

## Question 7 :white\_check\_mark:

Which of the following is/are true?

* [x] &#x20;A. NFT ownership is tracked by \_ownerOf
* [x] &#x20;B. NFT balance is tracked by \_balanceOf
* [x] &#x20;C. NFT approvals are tracked by getApproved
* [x] &#x20;D. NFT operator can transfer all of owner’s NFTs

## Question 8 :white\_check\_mark:

ERC721 recognizes the following role(s)

* [x] &#x20;A. Owner
* [x] &#x20;B. Spender (Approved address)
* [x] &#x20;C. Operator
* [ ] &#x20;D. None of the above


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://ret2basic.gitbook.io/ctfnote/web3-security-research/secureum/epoch-infinity/race-8-erc721-roles.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
