Hello, I am working towards deploying an upgradeable and pausable ERC-20 token contract and would appreciate any constructive criticism of the current implementation (71 lines). The token also has an access control mechanism, which is fairly self-explanatory.
One aspect I would particularly appreciate comments on is the initialisation. In particular, the contract uses two init functions, which can apparently (see readme in OZ's upgradeable contracts github) cause insecurities. I believe that the use of the two init calls in lines 17 and 18 do not create such an insecurity and would value any dissenting view.
pragma solidity ^0.8.0;
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20PausableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol";
contract SimpleToken is Initializable, ERC20PausableUpgradeable, AccessControlUpgradeable {
bytes32 public constant USER_ROLE = keccak256("USER_ROLE");
bytes32 public constant COMPLIANCE_ROLE = keccak256("COMPLIANCE_ROLE");
mapping(address => bool) private _frozen;
function initialize() public initializer {
_setupRole(DEFAULT_ADMIN_ROLE, msg.sender);
_setRoleAdmin(USER_ROLE, COMPLIANCE_ROLE);
__ERC20_init("SimpleToken","SMT");
__ERC20Pausable_init();
}
function mint(address to, uint256 amount) public {
require(hasRole(DEFAULT_ADMIN_ROLE, msg.sender), "Must be ADMIN.");
require(hasRole(USER_ROLE, to), "Recipient must be USER.");
_mint(to, amount);
}
function burn(address from, uint256 amount) public {
require(hasRole(DEFAULT_ADMIN_ROLE, msg.sender), "Must be ADMIN.");
_burn(from, amount);
}
function transfer(address to, uint256 value) public override returns (bool) {
require(hasRole(USER_ROLE, to), "Recipient must be USER.");
require(!_frozen[msg.sender], "Must be unfrozen.");
require(!_frozen[to], "Recipient address must be unfrozen.");
return super.transfer(to, value);
}
function transferFrom(address from, address to, uint256 value) public override returns (bool) {
require(hasRole(USER_ROLE, to), "Recipient must be a USER.");
require(hasRole(USER_ROLE, msg.sender), "Must be USER.");
require(!_frozen[msg.sender], "Must be unfrozen.");
require(!_frozen[to], "Recipient must be unfrozen.");
require(!_frozen[from], "Sender must be unfrozen.");
return super.transferFrom(from, to, value);
}
function freeze(address account) public {
require(hasRole(COMPLIANCE_ROLE, msg.sender), "Must hold COMPLIANCE role.");
_frozen[account] = true;
}
function unfreeze(address account) public {
require(hasRole(COMPLIANCE_ROLE, msg.sender), "Must hold COMPLIANCE role. ");
_frozen[account] = false;
}
function isAccountFrozen(address account) public view returns (bool) {
return _frozen[account];
}
function pause() public whenNotPaused {
require(hasRole(DEFAULT_ADMIN_ROLE, msg.sender), "Must be ADMIN.");
_pause();
}
function unpause() public whenPaused {
require(hasRole(DEFAULT_ADMIN_ROLE, msg.sender), "Must be ADMIN.");
_unpause();
}
}