您正在查看: Surou 发布的文章

为什么LINK使用ERC-677标准发行token

等等,LINK不是ERC20吗,怎么又成了ERC677了?
别急,我们先从ERC20开始说起。

ERC20是一套协议标准,代码角度来说就是一套接口API。在这个协议标准下,只要实现了协议标准所规定的方法,都可以作为ERC20代币的实现。协议规定必须实现的方法有:

// 1. 代币发行总量
function totalSupply() public view returns (uint256)

// 2. _owner账户的代币余额
function balanceOf(address _owner) public view returns (uint256 balance)

// 3. 转移_value数量的代币到_to地址
function transfer(address _to, uint256 _value) public returns (bool success)

// 4. 从_address地址转移_value数量的代币到_to地址
function transferFrom(address _from, address _to, uint256 _value) public returns (bool success)

// 5. 允许_spender可以提取总额为_value数量的代币,提取不限次数
function approve(address _spender, uint256 _value) public returns (bool success)

// 6. 返回_spender还可以从_owner提取的代币数量
function allowance(address _owner, address _spender) public view returns (uint256 remaining)

除了这几个方法,ERC20还规定了两个事件:

// 当成功转移token时,触发Transfer事件,记录转账的发送方、接收方和转账金额
event Transfer(address indexed _from, address indexed _to, uint256 _value)

// 当调用approval函数成功时,触发Approval事件,记录批准的所有方、获取方和批准金额
event Approval(address indexed _owner, address indexed _spender, uint256 _value)

以上基本上就是ERC20代币的全部内容了。由于LINK代币不仅仅是代币,还承担了链上与链下数据传递的功能,所以如果使用ERC20代币的标准无法满足这个需求,于是LINK选择了ERC677协议标准来实现。

ERC677标准是ERC20的一个扩展,它继承了ERC20的所有方法和事件,由Chainlink的CTO Steve Ellis首次提出。ERC677除了包含了ERC20的所有方法和事件之外,增加了一个transferAndCall 方法:

function onTokenTransfer(address from, uint256 amount, bytes data) returns (bool success)

接收合约就可以在这个方法中定义自己的业务逻辑,可以在发生转账的时候自动触发。换句话说,智能合约中的业务逻辑,可以通过代币转账的方式来触发自动运行。这就给了智能合约的应用场景有了很大的想象空间。比如LINK的token合约就是一个ERC677合约,而Chainlink的Oracle合约,是一个可以接收ERC677的合约,它含有onTokenTransfer方法,可以在收到LINK的转账的时候执行预言机相关的业务逻辑。

LINK token contract:

...

  /**
  * @dev 转移token到合约地址,并携带额外数据
  * @param _to 转到的地址
  * @param _value 转账金额
  * @param _data 传递给接受合约的额外数据
  */
  function transferAndCall(address _to, uint _value, bytes _data)
    public
    returns (bool success)
  {
    super.transfer(_to, _value);
    Transfer(msg.sender, _to, _value, _data);
    if (isContract(_to)) {
      contractFallback(_to, _value, _data);
    }
    return true;
  }

...

Oracle 合约:

...

  /**
    * @notice 在LINK通过`transferAndCall`方法发送到合约时被调用
    * @dev 负载数据的前两个字节会被`_sender`和 `_amount`的值覆盖来保证正确性。并会调用oracleRequest方法
    * @param _sender 发送方地址
    * @param _amount 发送的LINK数量(单位是wei)
    * @param _data 交易的负载数据
    */
  function onTokenTransfer(
    address _sender,
    uint256 _amount,
    bytes _data
  )
    public
    onlyLINK
    validRequestLength(_data)
    permittedFunctionsForLINK(_data)
  {
    assembly {
      // solium-disable-next-line security/no-low-level-calls
      mstore(add(_data, 36), _sender) // ensure correct sender is passed
      // solium-disable-next-line security/no-low-level-calls
      mstore(add(_data, 68), _amount)    // ensure correct amount is passed
    }
    // solium-disable-next-line security/no-low-level-calls
    require(address(this).delegatecall(_data), "Unable to create request"); // calls oracleRequest
  }

...

总结:

LINK代币合约是ERC677合约,它是ERC20合约的一个扩展,兼容ERC20协议标准。它可以在转账时携带数据,并触发接收合约的业务逻辑,这一特点可以帮助智能合约扩大应用场景。

参考

https://eips.ethereum.org/EIPS/eip-20
http://blockchainers.org/index.php/2018/02/08/token-erc-comparison-for-fungible-tokens/
https://github.com/ethereum/EIPs/issues/677
https://etherscan.io/address/0x514910771af9ca656af840dff83e8264ecf986ca#code
https://etherscan.io/address/0x64fe692be4b42f4ac9d4617ab824e088350c11c2#code

转载自:https://learnblockchain.cn/article/588

block.Hash()计算得到的hash 与 eth_getBlockByNumber获取到的值不一致

1.10.8版本出现的一个bug

rpc returndata 使用一个字段miner,但不包含coinbase. 因此,如果调用者想要在远程端计算头/块哈希,这是不可能的,因为我们不再提供正确的值coinbase。

通过revert提交解决
https://github.com/ethereum/go-ethereum/pull/23466/files

等待 1.10.9解决,或者使用master分支,文章此时已合入

SHA-1: 62ad17fb0046243255048fbf8cb0882f48d8d850

* Revert "eth, internal/ethapi: make RPC block miner field show block sealer correctly (#23312)" (#23466)

https://github.com/ethereum/go-ethereum/issues/23523
https://github.com/ethereum/go-ethereum/issues/23463
https://github.com/ethereum/go-ethereum/issues/23512
https://github.com/ethereum/go-ethereum/pull/23466

合约内执行ERC20转账

假设有个合约ERC20充值方法,方法接收msg.sender发送的ERC20代币,并增加合约中的余额统计

import "openzeppelin-solidity/contracts/token/ERC20/IERC20.sol";
...
function rechargeMain(address mainChainErc20Addr, uint256 amount) payable external notContract returns (bool){
    ...
    require(amount > 0, "The recharge amount is too small");
    require(IERC20(mainChainErc20Addr).balanceOf(msg.sender) >= amount, "Insufficient contract account balance");

    IERC20(mainChainErc20Addr).transfer(address(this), amount);
    ...
    return true;
}

执行后提示

execution reverted: ERC20: transfer amount exceeds balance

经测试(Github Code

function transfer(address recipient, uint256 amount) public virtual override returns (bool) {
    _transfer(_msgSender(), recipient, amount);
    return true;
}

_msgSender()的地址为合约地址,测试交易(跳转地址

所以我们需要使用transferFrom,代码修改为

import "openzeppelin-solidity/contracts/token/ERC20/IERC20.sol";
...
function rechargeMain(address mainChainErc20Addr, uint256 amount) payable external notContract returns (bool){
    ...
    require(amount > 0, "The recharge amount is too small");
    require(IERC20(mainChainErc20Addr).balanceOf(msg.sender) >= amount, "Insufficient contract account balance");

    IERC20(mainChainErc20Addr).transferFrom(msg.sender, address(this), amount);
    ...
    return true;
}

执行完,提示

execution reverted: ERC20: transfer amount exceeds allowance

查看代码(Github code

function transferFrom(
address sender,
 address recipient,
 uint256 amount
) public virtual override returns (bool) {
    _transfer(sender, recipient, amount);

    uint256 currentAllowance = _allowances[sender][_msgSender()];
    require(currentAllowance >= amount, "ERC20: transfer amount exceeds allowance");
    unchecked {
        _approve(sender, _msgSender(), currentAllowance - amount);
    }

    return true;
}

需要先批准交易金额(津贴)(Github Code

 function approve(address spender, uint256 amount) public virtual override returns (bool) {
     _approve(_msgSender(), spender, amount);
     return true;
 }

查看当前剩余允许值,(Github Code

function allowance(address owner, address spender) public view virtual override returns (uint256) {
    return _allowances[owner][spender];
}

也可以执行increaseAllowancedecreaseAllowance增量增减调整

对于此次需求,执行rechargeMain前,需要先执行津贴充值

increaseAllowance(address spender, uint256 addedValue)

执行increaseAllowance的地址为当前充值转出地址
spender为当前转入合约地址
addedValue为当前充值金额
测试交易:https://ropsten.etherscan.io/tx/0x0f65cb2a7ca2bf0d6b1fdd65e943afb5c6305573dc3c3a960c2f86f71757c897

openzeppelin ERC20合约的使用

推荐 :https://wizard.openzeppelin.com/

basic erc20

Snapshot 1

`// SPDX-License-Identifier: MIT␊
pragma solidity ^0.8.2;␊
␊
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";␊
␊
contract MyToken is ERC20 {␊
    constructor() ERC20("MyToken", "MTK") {}␊
}␊
`

erc20 with snapshots

Snapshot 1

`// SPDX-License-Identifier: MIT␊
pragma solidity ^0.8.2;␊
␊
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";␊
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Snapshot.sol";␊
import "@openzeppelin/contracts/access/Ownable.sol";␊
␊
contract MyToken is ERC20, ERC20Snapshot, Ownable {␊
    constructor() ERC20("MyToken", "MTK") {}␊
␊
    function snapshot() public onlyOwner {␊
        _snapshot();␊
    }␊
␊
    // The following functions are overrides required by Solidity.␊
␊
    function _beforeTokenTransfer(address from, address to, uint256 amount)␊
        internal␊
        override(ERC20, ERC20Snapshot)␊
    {␊
        super._beforeTokenTransfer(from, to, amount);␊
    }␊
}␊
`

erc20 burnable

Snapshot 1

`// SPDX-License-Identifier: MIT␊
pragma solidity ^0.8.2;␊
␊
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";␊
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol";␊
␊
contract MyToken is ERC20, ERC20Burnable {␊
    constructor() ERC20("MyToken", "MTK") {}␊
}␊
`

erc20 burnable with snapshots

Snapshot 1

`// SPDX-License-Identifier: MIT␊
pragma solidity ^0.8.2;␊
␊
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";␊
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol";␊
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Snapshot.sol";␊
import "@openzeppelin/contracts/access/Ownable.sol";␊
␊
contract MyToken is ERC20, ERC20Burnable, ERC20Snapshot, Ownable {␊
    constructor() ERC20("MyToken", "MTK") {}␊
␊
    function snapshot() public onlyOwner {␊
        _snapshot();␊
    }␊
␊
    // The following functions are overrides required by Solidity.␊
␊
    function _beforeTokenTransfer(address from, address to, uint256 amount)␊
        internal␊
        override(ERC20, ERC20Snapshot)␊
    {␊
        super._beforeTokenTransfer(from, to, amount);␊
    }␊
}␊
`

erc20 pausable

Snapshot 1

`// SPDX-License-Identifier: MIT␊
pragma solidity ^0.8.2;␊
␊
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";␊
import "@openzeppelin/contracts/security/Pausable.sol";␊
import "@openzeppelin/contracts/access/Ownable.sol";␊
␊
contract MyToken is ERC20, Pausable, Ownable {␊
    constructor() ERC20("MyToken", "MTK") {}␊
␊
    function pause() public onlyOwner {␊
        _pause();␊
    }␊
␊
    function unpause() public onlyOwner {␊
        _unpause();␊
    }␊
␊
    function _beforeTokenTransfer(address from, address to, uint256 amount)␊
        internal␊
        whenNotPaused␊
        override␊
    {␊
        super._beforeTokenTransfer(from, to, amount);␊
    }␊
}␊
`

erc20 pausable with roles

Snapshot 1

`// SPDX-License-Identifier: MIT␊
pragma solidity ^0.8.2;␊
␊
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";␊
import "@openzeppelin/contracts/security/Pausable.sol";␊
import "@openzeppelin/contracts/access/AccessControl.sol";␊
␊
contract MyToken is ERC20, Pausable, AccessControl {␊
    bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE");␊
␊
    constructor() ERC20("MyToken", "MTK") {␊
        _setupRole(DEFAULT_ADMIN_ROLE, msg.sender);␊
        _setupRole(PAUSER_ROLE, msg.sender);␊
    }␊
␊
    function pause() public onlyRole(PAUSER_ROLE) {␊
        _pause();␊
    }␊
␊
    function unpause() public onlyRole(PAUSER_ROLE) {␊
        _unpause();␊
    }␊
␊
    function _beforeTokenTransfer(address from, address to, uint256 amount)␊
        internal␊
        whenNotPaused␊
        override␊
    {␊
        super._beforeTokenTransfer(from, to, amount);␊
    }␊
}␊
`

erc20 burnable pausable

Snapshot 1

`// SPDX-License-Identifier: MIT␊
pragma solidity ^0.8.2;␊
␊
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";␊
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol";␊
import "@openzeppelin/contracts/security/Pausable.sol";␊
import "@openzeppelin/contracts/access/Ownable.sol";␊
␊
contract MyToken is ERC20, ERC20Burnable, Pausable, Ownable {␊
    constructor() ERC20("MyToken", "MTK") {}␊
␊
    function pause() public onlyOwner {␊
        _pause();␊
    }␊
␊
    function unpause() public onlyOwner {␊
        _unpause();␊
    }␊
␊
    function _beforeTokenTransfer(address from, address to, uint256 amount)␊
        internal␊
        whenNotPaused␊
        override␊
    {␊
        super._beforeTokenTransfer(from, to, amount);␊
    }␊
}␊
`

erc20 burnable pausable with snapshots

Snapshot 1

`// SPDX-License-Identifier: MIT␊
pragma solidity ^0.8.2;␊
␊
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";␊
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol";␊
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Snapshot.sol";␊
import "@openzeppelin/contracts/access/Ownable.sol";␊
import "@openzeppelin/contracts/security/Pausable.sol";␊
␊
contract MyToken is ERC20, ERC20Burnable, ERC20Snapshot, Ownable, Pausable {␊
    constructor() ERC20("MyToken", "MTK") {}␊
␊
    function snapshot() public onlyOwner {␊
        _snapshot();␊
    }␊
␊
    function pause() public onlyOwner {␊
        _pause();␊
    }␊
␊
    function unpause() public onlyOwner {␊
        _unpause();␊
    }␊
␊
    function _beforeTokenTransfer(address from, address to, uint256 amount)␊
        internal␊
        whenNotPaused␊
        override(ERC20, ERC20Snapshot)␊
    {␊
        super._beforeTokenTransfer(from, to, amount);␊
    }␊
}␊
`

erc20 preminted

Snapshot 1

`// SPDX-License-Identifier: MIT␊
pragma solidity ^0.8.2;␊
␊
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";␊
␊
contract MyToken is ERC20 {␊
    constructor() ERC20("MyToken", "MTK") {␊
        _mint(msg.sender, 1000 * 10 ** decimals());␊
    }␊
}␊
`

erc20 premint of 0

Snapshot 1

`// SPDX-License-Identifier: MIT␊
pragma solidity ^0.8.2;␊
␊
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";␊
␊
contract MyToken is ERC20 {␊
    constructor() ERC20("MyToken", "MTK") {}␊
}␊
`

erc20 mintable

Snapshot 1

`// SPDX-License-Identifier: MIT␊
pragma solidity ^0.8.2;␊
␊
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";␊
import "@openzeppelin/contracts/access/Ownable.sol";␊
␊
contract MyToken is ERC20, Ownable {␊
    constructor() ERC20("MyToken", "MTK") {}␊
␊
    function mint(address to, uint256 amount) public onlyOwner {␊
        _mint(to, amount);␊
    }␊
}␊
`

erc20 mintable with roles

Snapshot 1

`// SPDX-License-Identifier: MIT␊
pragma solidity ^0.8.2;␊
␊
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";␊
import "@openzeppelin/contracts/access/AccessControl.sol";␊
␊
contract MyToken is ERC20, AccessControl {␊
    bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");␊
␊
    constructor() ERC20("MyToken", "MTK") {␊
        _setupRole(DEFAULT_ADMIN_ROLE, msg.sender);␊
        _setupRole(MINTER_ROLE, msg.sender);␊
    }␊
␊
    function mint(address to, uint256 amount) public onlyRole(MINTER_ROLE) {␊
        _mint(to, amount);␊
    }␊
}␊
`

erc20 permit

Snapshot 1

`// SPDX-License-Identifier: MIT␊
pragma solidity ^0.8.2;␊
␊
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";␊
import "@openzeppelin/contracts/token/ERC20/extensions/draft-ERC20Permit.sol";␊
␊
contract MyToken is ERC20, ERC20Permit {␊
    constructor() ERC20("MyToken", "MTK") ERC20Permit("MyToken") {}␊
}␊
`

erc20 votes

Snapshot 1

`// SPDX-License-Identifier: MIT␊
pragma solidity ^0.8.2;␊
␊
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";␊
import "@openzeppelin/contracts/token/ERC20/extensions/draft-ERC20Permit.sol";␊
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Votes.sol";␊
␊
contract MyToken is ERC20, ERC20Permit, ERC20Votes {␊
    constructor() ERC20("MyToken", "MTK") ERC20Permit("MyToken") {}␊
␊
    // The following functions are overrides required by Solidity.␊
␊
    function _afterTokenTransfer(address from, address to, uint256 amount)␊
        internal␊
        override(ERC20, ERC20Votes)␊
    {␊
        super._afterTokenTransfer(from, to, amount);␊
    }␊
␊
    function _mint(address to, uint256 amount)␊
        internal␊
        override(ERC20, ERC20Votes)␊
    {␊
        super._mint(to, amount);␊
    }␊
␊
    function _burn(address account, uint256 amount)␊
        internal␊
        override(ERC20, ERC20Votes)␊
    {␊
        super._burn(account, amount);␊
    }␊
}␊
`

erc20 flashmint

Snapshot 1

`// SPDX-License-Identifier: MIT␊
pragma solidity ^0.8.2;␊
␊
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";␊
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20FlashMint.sol";␊
␊
contract MyToken is ERC20, ERC20FlashMint {␊
    constructor() ERC20("MyToken", "MTK") {}␊
}␊
`

erc20 full upgradeable transparent

Snapshot 1

`// SPDX-License-Identifier: MIT␊
pragma solidity ^0.8.2;␊
␊
import "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol";␊
import "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20BurnableUpgradeable.sol";␊
import "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20SnapshotUpgradeable.sol";␊
import "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol";␊
import "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol";␊
import "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/draft-ERC20PermitUpgradeable.sol";␊
import "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20VotesUpgradeable.sol";␊
import "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20FlashMintUpgradeable.sol";␊
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";␊
␊
contract MyToken is Initializable, ERC20Upgradeable, ERC20BurnableUpgradeable, ERC20SnapshotUpgradeable, AccessControlUpgradeable, PausableUpgradeable, ERC20PermitUpgradeable, ERC20VotesUpgradeable, ERC20FlashMintUpgradeable {␊
    bytes32 public constant SNAPSHOT_ROLE = keccak256("SNAPSHOT_ROLE");␊
    bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE");␊
    bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");␊
␊
    function initialize() initializer public {␊
        __ERC20_init("MyToken", "MTK");␊
        __ERC20Burnable_init();␊
        __ERC20Snapshot_init();␊
        __AccessControl_init();␊
        __Pausable_init();␊
        __ERC20Permit_init("MyToken");␊
        __ERC20FlashMint_init();␊
␊
        _setupRole(DEFAULT_ADMIN_ROLE, msg.sender);␊
        _setupRole(SNAPSHOT_ROLE, msg.sender);␊
        _setupRole(PAUSER_ROLE, msg.sender);␊
        _mint(msg.sender, 2000 * 10 ** decimals());␊
        _setupRole(MINTER_ROLE, msg.sender);␊
    }␊
␊
    function snapshot() public onlyRole(SNAPSHOT_ROLE) {␊
        _snapshot();␊
    }␊
␊
    function pause() public onlyRole(PAUSER_ROLE) {␊
        _pause();␊
    }␊
␊
    function unpause() public onlyRole(PAUSER_ROLE) {␊
        _unpause();␊
    }␊
␊
    function mint(address to, uint256 amount) public onlyRole(MINTER_ROLE) {␊
        _mint(to, amount);␊
    }␊
␊
    function _beforeTokenTransfer(address from, address to, uint256 amount)␊
        internal␊
        whenNotPaused␊
        override(ERC20Upgradeable, ERC20SnapshotUpgradeable)␊
    {␊
        super._beforeTokenTransfer(from, to, amount);␊
    }␊
␊
    // The following functions are overrides required by Solidity.␊
␊
    function _afterTokenTransfer(address from, address to, uint256 amount)␊
        internal␊
        override(ERC20Upgradeable, ERC20VotesUpgradeable)␊
    {␊
        super._afterTokenTransfer(from, to, amount);␊
    }␊
␊
    function _mint(address to, uint256 amount)␊
        internal␊
        override(ERC20Upgradeable, ERC20VotesUpgradeable)␊
    {␊
        super._mint(to, amount);␊
    }␊
␊
    function _burn(address account, uint256 amount)␊
        internal␊
        override(ERC20Upgradeable, ERC20VotesUpgradeable)␊
    {␊
        super._burn(account, amount);␊
    }␊
}␊
`

erc20 full upgradeable uups

Snapshot 1

`// SPDX-License-Identifier: MIT␊
pragma solidity ^0.8.2;␊
␊
import "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol";␊
import "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20BurnableUpgradeable.sol";␊
import "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20SnapshotUpgradeable.sol";␊
import "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol";␊
import "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol";␊
import "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/draft-ERC20PermitUpgradeable.sol";␊
import "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20VotesUpgradeable.sol";␊
import "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20FlashMintUpgradeable.sol";␊
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";␊
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";␊
␊
contract MyToken is Initializable, ERC20Upgradeable, ERC20BurnableUpgradeable, ERC20SnapshotUpgradeable, AccessControlUpgradeable, PausableUpgradeable, ERC20PermitUpgradeable, ERC20VotesUpgradeable, ERC20FlashMintUpgradeable, UUPSUpgradeable {␊
    bytes32 public constant SNAPSHOT_ROLE = keccak256("SNAPSHOT_ROLE");␊
    bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE");␊
    bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");␊
    bytes32 public constant UPGRADER_ROLE = keccak256("UPGRADER_ROLE");␊
␊
    function initialize() initializer public {␊
        __ERC20_init("MyToken", "MTK");␊
        __ERC20Burnable_init();␊
        __ERC20Snapshot_init();␊
        __AccessControl_init();␊
        __Pausable_init();␊
        __ERC20Permit_init("MyToken");␊
        __ERC20FlashMint_init();␊
        __UUPSUpgradeable_init();␊
␊
        _setupRole(DEFAULT_ADMIN_ROLE, msg.sender);␊
        _setupRole(SNAPSHOT_ROLE, msg.sender);␊
        _setupRole(PAUSER_ROLE, msg.sender);␊
        _mint(msg.sender, 2000 * 10 ** decimals());␊
        _setupRole(MINTER_ROLE, msg.sender);␊
        _setupRole(UPGRADER_ROLE, msg.sender);␊
    }␊
␊
    function snapshot() public onlyRole(SNAPSHOT_ROLE) {␊
        _snapshot();␊
    }␊
␊
    function pause() public onlyRole(PAUSER_ROLE) {␊
        _pause();␊
    }␊
␊
    function unpause() public onlyRole(PAUSER_ROLE) {␊
        _unpause();␊
    }␊
␊
    function mint(address to, uint256 amount) public onlyRole(MINTER_ROLE) {␊
        _mint(to, amount);␊
    }␊
␊
    function _beforeTokenTransfer(address from, address to, uint256 amount)␊
        internal␊
        whenNotPaused␊
        override(ERC20Upgradeable, ERC20SnapshotUpgradeable)␊
    {␊
        super._beforeTokenTransfer(from, to, amount);␊
    }␊
␊
    function _authorizeUpgrade(address newImplementation)␊
        internal␊
        onlyRole(UPGRADER_ROLE)␊
        override␊
    {}␊
␊
    // The following functions are overrides required by Solidity.␊
␊
    function _afterTokenTransfer(address from, address to, uint256 amount)␊
        internal␊
        override(ERC20Upgradeable, ERC20VotesUpgradeable)␊
    {␊
        super._afterTokenTransfer(from, to, amount);␊
    }␊
␊
    function _mint(address to, uint256 amount)␊
        internal␊
        override(ERC20Upgradeable, ERC20VotesUpgradeable)␊
    {␊
        super._mint(to, amount);␊
    }␊
␊
    function _burn(address account, uint256 amount)␊
        internal␊
        override(ERC20Upgradeable, ERC20VotesUpgradeable)␊
    {␊
        super._burn(account, amount);␊
    }␊
}␊
`

转载自:https://github.com/OpenZeppelin/contracts-wizard/blob/0dbf5d669adcdb8e801fbb67a269fc7931613d66/packages/core/src/erc20.test.ts.md

安装solc指定版本

pip3 install solc-select
solc-select install 版本