ERC777 contract best practice

Hi all,

I would like to ask you to advise me about my ERC777 contract implementation.

  1. Do I follow the best practice and it’s ready to go live or I missed something important?
  2. What is the best practice for the fee collector account, Shall I use a normal address or contract? and I’ll be very thankful if you can provide me an example of that, please.

My Contract:

    // SPDX-License-Identifier: MIT

pragma solidity =0.6.8;

import "@openzeppelin/contracts-ethereum-package/contracts/token/ERC777/ERC777.sol";
import "@openzeppelin/contracts-ethereum-package/contracts/access/AccessControl.sol";
import "@openzeppelin/contracts-ethereum-package/contracts/utils/Pausable.sol";


contract BityEGP is Initializable, ERC777UpgradeSafe, PausableUpgradeSafe, AccessControlUpgradeSafe {
    bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
    bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE");

    function initialize(address[] memory defaultOperators) public initializer {
        __ERC777_init('BityEGP', 'bEGP', defaultOperators);
        __AccessControl_init();
        __Pausable_init();
        initRoles();
    }


    function initRoles() internal {
        _setupRole(DEFAULT_ADMIN_ROLE, _msgSender());
        
        _setupRole(MINTER_ROLE, _msgSender());
        _setupRole(PAUSER_ROLE, _msgSender());
    }


    function deposite(address to, uint256 amount) external {
        require(isOperatorFor(msg.sender, to), "Unauthorized token operator!");
        require(amount > 0, "No tokens to add!");

       _mint(to, amount * 100, "", "");
    }


    function Withdrawal(address from, uint256 amount, uint256 fee) external {
        require(isOperatorFor(msg.sender, from), "Unauthorized token operator!");
        require(amount > 0, "No tokens to Withdrawal!");

        uint256 total = amount.add(fee);
        balanceOf(from).sub(total, "BityEGP: withdrawal amount + fee exceed balance");

        operatorBurn(from, amount, "", "");
        operatorSend(from, msg.sender, fee, "", ""); //Todo: check the fees address best practice
    }


    function transfer(address from, address to, uint256 amount, uint256 fee) external {
        require(isOperatorFor(msg.sender, from), "Unauthorized token operator!");
        require(amount > 0, "No tokens to transfer!");

        uint256 total = amount.add(fee);
        balanceOf(from).sub(total, "BityEGP: transfer amount + fee exceed balance");

        operatorSend(from, to, amount, "", "");
        operatorSend(from, msg.sender, fee, "", ""); //Todo: check the fees address best practice
    }


    function pause() public {
        require(hasRole(PAUSER_ROLE, _msgSender()), "BityEGP: must have pauser role to pause");
        _pause();
    }

  
    function unpause() public {
        require(hasRole(PAUSER_ROLE, _msgSender()), "BityEGP: must have pauser role to unpause");
        _unpause();
    } 


    function _beforeTokenTransfer(address operator, address from, address to, uint256 tokenId) internal virtual override (ERC777UpgradeSafe) {
        super._beforeTokenTransfer(operator, from, to, tokenId);

        require(!paused(), "BityEGP: token transfer while paused");
    }


    function version() public pure returns (string memory) {
        return "v1";
    }

    uint256[50] private __gap;
}

Thanks in advance.

1 Like

Hi @MAbdulsamee,

Welcome to the community :wave:

At a high level, I suggest you look at: Points to consider when creating a fungible token (ERC20, ERC777)

As a summary, if your solution really needs a token, you should test, audit, have regulatory guidance, design tokenomics and have transparent governance for your access control. With an ERC777 token you also need to account for reentrancy.

I am not sure what you mean by fee collector?

Do you mean a wallet for collecting proceeds from a crowdsale?

If so, you may want to look at a multi-sig wallet contract (such as from Gnosis) using hardware wallets as signatories.

Please note Crowdsales were removed from the OpenZeppelin Contracts from v3.0.0 but are still available in v2.x, see the documentation for details: https://docs.openzeppelin.com/contracts/3.x/crowdsales

Thank you @abcoathup

Regarding the fee collector, it’s not a crowdsale, I am developing a stablecoin token backed by fiat and I am charging the users a small fee for each transaction. My question is what is the best way to collect and store those fees?

Regards,

1 Like

You can store those fees inside the contract and then have an adminWithdraw() mechanism, on the other hand you could collect the fees directly into an EOA // Wallet, but that would imply an extra gas cost for users.

1 Like

Hi @MAbdulsamee,

Apologies, I hadn’t spotted the fee taken during transfer in your contract (I had missed that there was more to the code and hadn’t scrolled down).

I would recommend that your tests include tests that your fee mechanism works the way that you intend as currently it occurs (_beforeTokenTransfer) on the _mint , _burn and _move functions. You may need to check the from and to addresses if you want to only do this action on _move.

Crowdsales.sol in OpenZeppelin Contracts 2.5.1 forwards funds to a wallet on purchase. (https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v2.5.1/contracts/crowdsale/Crowdsale.sol#L197). Though you would want to check the extra gas cost for users taking into account the amounts of your token they are likely to transfer at any one time.

As always, I would recommend appropriate testing and auditing, along with checking regulatory compliance.