# Virtual removed from safeTransferFrom in ERC721Upgradeable

**URL:** <https://forum.openzeppelin.com/t/virtual-removed-from-safetransferfrom-in-erc721upgradeable/39194>\
**Category:** Contracts\
**Tags:** erc721\
**Created:** [January 12, 2024, 2:24am UTC](https://forum.openzeppelin.com/t/virtual-removed-from-safetransferfrom-in-erc721upgradeable/39194 "2024-01-12T02:24:46Z")\
**Posts on this page:** 7\
**Page:** 1

<div class="post-metadata">

**Author:** ![gr347wh173n0r7h](https://sea2.discourse-cdn.com/flex022/user_avatar/forum.openzeppelin.com/gr347wh173n0r7h/32/20256_2.png) [@gr347wh173n0r7h](https://forum.openzeppelin.com/u/gr347wh173n0r7h)\
**Post date:** [January 12, 2024, 2:24am UTC](https://forum.openzeppelin.com/t/virtual-removed-from-safetransferfrom-in-erc721upgradeable/39194/1 "2024-01-12T02:24:46Z")

</div>

Why was `virtual` removed from `safeTransferFrom` function in [ERC721Upgradeable](https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable/blob/0f59c30b8c11b18a919ba69a6c33807480838e1c/contracts/token/ERC721/ERC721Upgradeable.sol#L175)

Looks like it happened in this [commit](https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable/commit/0a670d08c1364dd91ed50564405ede08f172ef0e)

This is preventing this `safeTransferFrom` function from being overridden.

#### 🔢 Code to reproduce

```auto
function safeTransferFrom(address from, address to, uint256 tokenId)
    public
    override (ERC721Upgradeable, IERC721) onlyAllowedOperator(from) {
        super.safeTransferFrom(from, to, tokenId);
    }

```

#### 💻 Environment

Hardhat

---

<div class="post-metadata">

**Author:** ![barakman](https://sea2.discourse-cdn.com/flex022/user_avatar/forum.openzeppelin.com/barakman/32/855_2.png) [@barakman](https://forum.openzeppelin.com/u/barakman)\
**Post date:** [January 12, 2024, 8:14am UTC](https://forum.openzeppelin.com/t/virtual-removed-from-safetransferfrom-in-erc721upgradeable/39194/2 "2024-01-12T08:14:25Z")

</div>

It calls [a function of the same name](https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable/blob/master/contracts/token/ERC721/ERC721Upgradeable.sol#L182), which receives the same input arguments in the same order, alongside an additional `bytes memory data` input argument:

```auto
function safeTransferFrom(address from, address to, uint256 tokenId) public {
    safeTransferFrom(from, to, tokenId, "");
}

function safeTransferFrom(address from, address to, uint256 tokenId, bytes memory data) public virtual {
    transferFrom(from, to, tokenId);
    _checkOnERC721Received(from, to, tokenId, data);
}

```

As you can see, that other function IS virtual, so you can override it instead and change the behavior of the original function as desired.

---

<div class="post-metadata">

**Author:** ![gr347wh173n0r7h](https://sea2.discourse-cdn.com/flex022/user_avatar/forum.openzeppelin.com/gr347wh173n0r7h/32/20256_2.png) [@gr347wh173n0r7h](https://forum.openzeppelin.com/u/gr347wh173n0r7h)\
**Post date:** [January 12, 2024, 8:50pm UTC](https://forum.openzeppelin.com/t/virtual-removed-from-safetransferfrom-in-erc721upgradeable/39194/3 "2024-01-12T20:50:32Z")

</div>

Ahh I see my mistake thank you for clarifying.

---

<div class="post-metadata">

**Author:** ![ernestognw](https://sea2.discourse-cdn.com/flex022/user_avatar/forum.openzeppelin.com/ernestognw/32/18576_2.png) [@ernestognw](https://forum.openzeppelin.com/u/ernestognw)\
**Post date:** [January 12, 2024, 10:53pm UTC](https://forum.openzeppelin.com/t/virtual-removed-from-safetransferfrom-in-erc721upgradeable/39194/4 "2024-01-12T22:53:45Z")

</div>

We kept it this way exactly to avoid a situation where you may override the `safeTransferFrom(address from, address to, uint256 tokenId)` version without overriding the other. It may be a critical bug!

---

<div class="post-metadata">

**Author:** ![Upendra\_Jaiswal](https://sea2.discourse-cdn.com/flex022/user_avatar/forum.openzeppelin.com/upendra_jaiswal/32/20271_2.png) [@Upendra\_Jaiswal](https://forum.openzeppelin.com/u/Upendra_Jaiswal)\
**Post date:** [January 16, 2024, 2:27am UTC](https://forum.openzeppelin.com/t/virtual-removed-from-safetransferfrom-in-erc721upgradeable/39194/5 "2024-01-16T02:27:47Z")

</div>

Thanks for clarification

---

<div class="post-metadata">

**Author:** ![Upendra\_Jaiswal](https://sea2.discourse-cdn.com/flex022/user_avatar/forum.openzeppelin.com/upendra_jaiswal/32/20271_2.png) [@Upendra\_Jaiswal](https://forum.openzeppelin.com/u/Upendra_Jaiswal)\
**Post date:** [January 16, 2024, 2:30am UTC](https://forum.openzeppelin.com/t/virtual-removed-from-safetransferfrom-in-erc721upgradeable/39194/6 "2024-01-16T02:30:20Z")

</div>

As both are with same name, so only the number of arguments will differentiate the two functions ?? And ofc we have to override the function with virtual keyword in it, so why they kept other functions eliminating argument of bytes memory data ??

---

<div class="post-metadata">

**Author:** ![ernestognw](https://sea2.discourse-cdn.com/flex022/user_avatar/forum.openzeppelin.com/ernestognw/32/18576_2.png) [@ernestognw](https://forum.openzeppelin.com/u/ernestognw)\
**Post date:** [January 16, 2024, 3:57am UTC](https://forum.openzeppelin.com/t/virtual-removed-from-safetransferfrom-in-erc721upgradeable/39194/7 "2024-01-16T03:57:09Z")

</div>

It's mandated by [the specification](https://eips.ethereum.org/EIPS/eip-721#specification):

```auto
/// @notice Transfers the ownership of an NFT from one address to another address
/// @dev Throws unless `msg.sender` is the current owner, an authorized
/// operator, or the approved address for this NFT. Throws if `_from` is
/// not the current owner. Throws if `_to` is the zero address. Throws if
/// `_tokenId` is not a valid NFT. When transfer is complete, this function
/// checks if `_to` is a smart contract (code size > 0). If so, it calls
/// `onERC721Received` on `_to` and throws if the return value is not
/// `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`.
/// @param _from The current owner of the NFT
/// @param _to The new owner
/// @param _tokenId The NFT to transfer
/// @param data Additional data with no specified format, sent in call to `_to`
function safeTransferFrom(address _from, address _to, uint256 _tokenId, bytes data) external payable;

/// @notice Transfers the ownership of an NFT from one address to another address
/// @dev This works identically to the other function with an extra data parameter,
/// except this function just sets data to "".
/// @param _from The current owner of the NFT
/// @param _to The new owner
/// @param _tokenId The NFT to transfer
function safeTransferFrom(address _from, address _to, uint256 _tokenId) external payable;

```

We just adhere to it.
