Block ERC20 transfer

In case I want to block the transfer with a blacklist, shouldn’t I check for it before calling super._beforeTokenTransfer(from, to, tokenId); ?

I put together a burnable ERC20 token with a blacklist , would you mind to skim over it if that’s ok ?

// SPDX-License-Identifier: MIT

pragma solidity >=0.7.0 <0.8.0;

import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/release-v3.2.0-solc-0.7/contracts/token/ERC20/ERC20.sol";
import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/release-v3.2.0-solc-0.7/contracts/token/ERC20/ERC20Burnable.sol";
import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/release-v3.2.0-solc-0.7/contracts/access/Ownable.sol";

contract BADtoken is ERC20Burnable, Ownable {

    mapping(address => bool) _blacklist;
    
    event BlacklistUpdated(address indexed user, bool value);

    constructor()
        ERC20("BAD Token", "BAD")
        Ownable()
    {
        // _owner = _msgSender();
        _mint(msg.sender, 10000000 * (10 ** uint256(decimals())));
    }
    
    function mint(address to, uint256 amount) public virtual onlyOwner {
        // require(_owner == _msgSender(), "Only owner is allowed to mint token.");
        _mint(to, amount);
    }
    
    function blacklistUpdate(address user, bool value) public virtual onlyOwner {
        // require(_owner == _msgSender(), "Only owner is allowed to modify blacklist.");
        _blacklist[user] = value;
        emit BlacklistUpdated(user, value);
    }
    
    function isBlackListed(address user) public view returns (bool) {
        return _blacklist[user];
    }
    
    
    function _beforeTokenTransfer(address from, address to, uint256 amount) internal virtual override(ERC20) {
        require (!isBlackListed(to), "Token transfer refused. Receiver is on blacklist");
        super._beforeTokenTransfer(from, to, amount);
    }

}
1 Like

So far the contract is working fine for me, at least with manual testing …

1 Like

Hi @SvenMeyer,

We should only use code from an official release of OpenZeppelin Contracts, otherwise you are using code from a release branch which could be changed. I recommend updating your GitHub imports to use a release tag, e.g.:

https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v3.2.2-solc-0.7/contracts/token/ERC20/ERC20.sol

I recommend using a more inclusive and descriptive term for your list, such as denylist or blocklist.

As you have at least two roles, I recommend using AccessControl rather than Ownable. This would mean that you could have a Minter Role and a Block Role (as well as a Default Admin Role), giving you finer granularity over your roles.

I would be tempted to have a function and an event for blocking and unblocking, rather than combining in a single function and event. e.g. function block with event Blocked(address), function unblock with event Unblocked(address)

Currently you are only blocking transfer to an address on the blocklist, which also includes minting. This means that blocked accounts could transfer any tokens that they held but couldn’t receive any more.


Once you have a basic structure, I recommend moving to either Truffle or Hardhat and writing unit tests with a high level of coverage to ensure that your block list works as expected. (see: https://docs.openzeppelin.com/learn/writing-automated-tests)

When using privileged roles, you should consider using a multi-sig. (see: https://docs.openzeppelin.com/learn/preparing-for-mainnet#admin-accounts)

To manage administration you could use OpenZeppelin Defender (see: https://docs.openzeppelin.com/defender/admin). Defender is free to sign up: https://defender.openzeppelin.com/

See the example: Manage an ERC20 token using Defender Admin and Gnosis Safe


Finally, if you are implementing a block on transfers, you would ideally have a lot of transparency for your community around what circumstances that a block would be used and how it would be communicated prior to and or after use.