I’m trying to use an ERC721 token in an election application. I’ve been tracing through the ERC721PresetMinterPauserAutoId contract and inherited contracts to understand how everything is connected and functions. I’m stuck on _beforeTokenTransfer.
""Hook that is called before any token transfer. This includes minting and burning.
Calling conditions:
When from and to are both non-zero, from 's tokenId will be transferred to to .
When from is zero, tokenId will be minted for to .
When to is zero, from 's tokenId will be burned.
from cannot be the zero address.
to cannot be the zero address.
To learn more about hooks, head to Using Hooks.
“”
However, the implementation of the function in ERC721 (see below) appears to be an empty function body.
@dev Hook that is called before any token transfer. This includes minting
and burning.
Calling conditions:
When from and to are both non-zero, from's tokenId will be
transferred to to.
When from is zero, tokenId will be minted for to.
When to is zero, from's tokenId will be burned.
from cannot be the zero address.
to cannot be the zero address.
To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
*/
function _beforeTokenTransfer(address from, address to, uint256 tokenId) internal virtual { }
What am I missing? Is this something about hooks that I don’t understand (not that I have a particularly firm grasp on hooks)? What does _beforeTokenTransfer do and where is it implemented?
Any advice or explanation would be appreciated. Cheers.
Hooks allow you lots of flexibility in modifying the behaviour of a token by allowing you to execute functionality, in the case of the _beforeTokenTransfer hook, you can execute functionality before the token is transferred.
You could restrict transfer (other than minting) to only registered candidates, you could mint another token to signify that the original holder has voted, you could prevent transfer outside of specific voting times, you could emit a vote event.
If you don’t need to use hooks, then you don’t need to include the function in your child contract.
An example is shown below (though I have only left comments rather than actually do anything in the hook):
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.2;
import "@openzeppelin/contracts/presets/ERC721PresetMinterPauserAutoId.sol";
contract MyToken is ERC721PresetMinterPauserAutoId {
constructor()
public
ERC721PresetMinterPauserAutoId(
"My Token",
"TKN",
"https://example.com/api/token/"
)
{}
function _beforeTokenTransfer(
address from,
address to,
uint256 tokenId
) internal virtual override(ERC721, ERC721Pausable) {
super._beforeTokenTransfer(from, to, tokenId);
// do stuff before every transfer
// e.g. check that vote (other than when minted)
// being transferred to registered candidate
}
}
I now understand that the _beforeTokenTransfer function is empty on purpose. It’s up to me to implement or not implement that function.
Relatedly, the documentation that I linked in the original post describes calling conditions for _beforeTokenTransfer. I know understand that this is simply describing what the arguments for _beforeTokenTransfer will be for certain actions (transfer, mint, burn) and what the arguments can never be (from and to cannot be the zero address). This is all in aid of me writing some functionality for the blank _beforeTokenTransfer hook.
Last question. Is it fair to compare hooks to function modifiers? The difference being that while function modifiers can only be placed before or after the function body, hooks can be place anywhere in the function body? Is that an oversimplification?
They both can be used to modifiy behavior. Hooks are a way to extend/modify behavior without having to give access to override functions and to do it in a single place, rather than having to override mint, burn, transfer/transferFrom.
For tokens we have the _beforeTokenTransfer hook, and this lets us add functionality to our token before the transfer, hooking in on any functions that use transfer, so covering mint, transfer/transferFrom and burn.
Modifiers on the other hand would require overriding internal functions or every external function that transfer is used to get the same level of functionality but more likely to be error prone.