花式发币法之发行各类 ERC20 代币

  • Blue
  • 更新于 2024-01-22 10:40
  • 阅读 6282

在Web3的世界中,我们会接触到许许多多的token,如ERC20标准的token,即同质化代币,就是我们常说的在交易所可交易的虚拟货币,还有如ERC721,即非同质化代币,也是我们熟知的NFT,这篇文章主要给大家讲讲如何使用Solidity发行各类的ERC20代币。

Web3的世界中,我们会接触到许许多多的token,如ERC20标准的token,即同质化代币,就是我们常说的在交易所可交易的虚拟货币,还有如ERC721,即非同质化代币,也是我们熟知的NFT,这篇文章主要给大家讲讲如何使用Solidity发行各类的ERC20代币。

下面的各类花式发币法是参考了 崔棉大师的花式发币法,不过其中的Solidity版本和OpenZeppelin都使用了最新版本Solidity 8OpenZeppelin 5,写法也都有相应的变化。

一、发行固定总量的ERC20代币

ERC20FixedSupply.sol:

// SPDX-License-Identifier: MIT 
pragma solidity >=0.8.0;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";

// 固定总量代币
contract ERC20FixedSupply is ERC20 {
    constructor(
        string memory name, // 代币名称
        string memory symbol, // 代币缩写
        uint256 totalSupply // 发行总量
    ) ERC20(name, symbol) {
        _mint(msg.sender, totalSupply);
    }
}

固定总量代币即在发行时就发行全部到指定账户,这里是会把全部币增发到合约部署人账户里。后期该币也不能再次增发。

二、发行可销毁的ERC20代币

ERC20WithBurnable.sol:

// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol";

// 可销毁代币
contract ERC20WithBurnable is ERC20, ERC20Burnable {
    constructor(
        string memory name, // 代币名称
        string memory symbol, // 代币缩写
        uint256 initialSupply // 初始发行总量
    ) ERC20(name, symbol) {
        _mint(msg.sender, initialSupply);
    }
}

可销毁代币可以在发行之后,通过调用burn(uint256 amount)方法来销毁自己手上的代币。

三、发行可增发的ERC20代币

ERC20WithMintable.sol:

// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";

// 可增发代币
contract ERC20WithMintable is ERC20, Ownable {
    constructor(
        string memory name, // 代币名称
        string memory symbol, // 代币缩写
        uint256 initialSupply // 初始发行总量
    ) ERC20(name, symbol) Ownable(msg.sender) {
        _mint(msg.sender, initialSupply);
    }

    function mint(address to, uint256 amount) public onlyOwner {
        _mint(to, amount);
    }
}

可增发代币可以在发行之后,owner也随时可以调用mint(address to, uint256 amount)进行代币的增发。

四、通过角色权限控制的可增发ERC20代币

ERC20WithMintableByAccess.sol:

// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/access/AccessControl.sol";

// 通过角色控制的可增发代币
contract ERC20WithMintableByAccess is ERC20, AccessControl {
    bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");

    constructor(
        string memory name, // 代币名称
        string memory symbol, // 代币缩写
        uint256 initialSupply // 初始发行总量
    ) ERC20(name, symbol) {
        _mint(msg.sender, initialSupply);
        _grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
        _grantRole(MINTER_ROLE, msg.sender);
    }

    function mint(address to, uint256 amount) public onlyRole(MINTER_ROLE) {
        _mint(to, amount);
    }
}

普通的可增发代币,一般只有owner才有增发的权限,如果我们想要让多个属于该角色的账号都有增发权限,就可以使用OpenZeppelin提供的角色权限控制合约,其他如销毁权限等也可这样控制。

五、发行有上限总量的ERC20代币

ERC20WithCapped.sol:

// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;

import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Capped.sol";
import "@openzeppelin/contracts/access/Ownable.sol";

// 有封顶上限总量的代币
contract ERC20WithCapped is ERC20Capped, Ownable {
    constructor(
        string memory name, // 代币名称
        string memory symbol, // 代币缩写
        uint256 initialSupply, // 初始发行总量
        uint256 cap // 上限总量
    ) ERC20(name, symbol) ERC20Capped(cap) Ownable(msg.sender) {
        _mint(msg.sender, initialSupply);
    }

    function mint(address to, uint256 amount) public onlyOwner {
        _mint(to, amount);
    }
}

有封顶上限总量代币一般是在可增发代币的基础上,设置一个总供应量上限,当达到这个上限时不允许再继续增发。

六、发行可暂停的ERC20代币

ERC20WithPausable.sol:

// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Pausable.sol";
import "@openzeppelin/contracts/access/Ownable.sol";

// 可暂停代币
contract ERC20WithPausable is ERC20, ERC20Pausable, Ownable {
    constructor(
        string memory name, // 代币名称
        string memory symbol, // 代币缩写
        uint256 initialSupply // 初始发行总量
    ) ERC20(name, symbol) Ownable(msg.sender) {
        _mint(msg.sender, initialSupply);
    }

    function mint(address to, uint256 amount) public onlyOwner {
        _mint(to, amount);
    }

    function pause() public onlyOwner {
        _pause();
    }

    function unpause() public onlyOwner {
        _unpause();
    }

    function _update(
        address from,
        address to,
        uint256 value
    ) internal override(ERC20, ERC20Pausable) {
        super._update(from, to, value);
    }
}

可暂停代币可以让owner通过调用pause()方法来暂停代币的所有交易功能(如增发、销毁、转移等)。

七、可锁仓的ERC20代币

可锁仓的ERC20代币需要我们在发行一个普通ERC20代币的合约情况下,再发行一个锁仓合约,通过把ERC20代币发送到锁仓合约进行锁仓,再由锁仓合约去控制解锁的时间和解锁的代币数量,最后会把解锁的代币发送给一个受益者(一般是锁仓的人或合约的部署人)。

这里我以最开始部署的固定总量代币和锁仓合约来演示可锁仓的ERC20代币

ERC20FixedSupply.sol:

// SPDX-License-Identifier: MIT 
pragma solidity >=0.8.0;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";

// 固定总量代币
contract ERC20FixedSupply is ERC20 {
    constructor(
        string memory name, // 代币名称
        string memory symbol, // 代币缩写
        uint256 totalSupply // 发行总量
    ) ERC20(name, symbol) {
        _mint(msg.sender, totalSupply);
    }
}

ERC20WithVesting.sol:

// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;

import "@openzeppelin/contracts/finance/VestingWallet.sol";

// 锁仓合约
contract ERC20WithVesting is VestingWallet {
    constructor(
        address beneficiary, // 受益人
        uint64 startTimestamp, // 开始解锁时间戳(秒)
        uint64 durationSeconds // 持续解锁时间(秒)
    ) VestingWallet(beneficiary, startTimestamp, durationSeconds) {}
}

部署完成这两个合约后,调用ERC20FixedSupplytransfer方法发送代币给ERC20WithVesting锁仓合约,锁仓合约会根据我们部署时给构造函数设置的受益人、开始时间、持续时间自动解锁代币,有解锁代币时,我们调用ERC20WithVestingrelease方法就可把解锁的代币发送给受益人。

八、ERC20代币水龙头

代币水龙头也是基于普通ERC20代币合约的情况下,再发行一个水龙头合约,我们在ERC20代币合约中发送代币给水龙头合约,水龙头合约去控制每次给用户是否发放代币及发送代币的数量等。

ERC20FixedSupply.sol:

// SPDX-License-Identifier: MIT 
pragma solidity >=0.8.0;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";

// 固定总量代币
contract ERC20FixedSupply is ERC20 {
    constructor(
        string memory name, // 代币名称
        string memory symbol, // 代币缩写
        uint256 totalSupply // 发行总量
    ) ERC20(name, symbol) {
        _mint(msg.sender, totalSupply);
    }
}

ERC20Faucet.sol:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";

// ERC20代币的水龙头合约
contract Faucet {
    uint256 public amountAllowed = 100; // 每次可领的代币数
    address public tokenContract; // token合约地址
    mapping(address => bool) public requestedAddress; // 领取过代币的地址

    // SendToken事件
    event SendToken(address indexed Receiver, uint256 indexed Amount);

    // 部署时设定ERC20合约地址
    constructor(address _tokenContract) {
        tokenContract = _tokenContract;
    }

    // 用户领取代币函数
    function requestToken() external {
        require(
            requestedAddress[msg.sender] == false,
            "Can't Request Multiple Times!"
        );
        ERC20 token = ERC20(tokenContract); // 创建合约对象
        require(
            token.balanceOf(address(this)) >= amountAllowed,
            "Faucet Empty!"
        );

        token.transfer(msg.sender, amountAllowed); // 发送token
        requestedAddress[msg.sender] = true; // 记录领取地址

        emit SendToken(msg.sender, amountAllowed); // 记录SendTOken事件
    }
}

部署完这两个合约后,先通过ERC20代币合约transfer代币到水龙头合约,然后其他用户就可以从水龙头合约去领取代币了,这里我们实现是每个用户只能领取一次,也可以实现为每个用户24小时只能领取一次,改一下判定是否领取的逻辑就行。

九、ERC20代币空投

代币空投也是需要在普通ERC20代币的基础上部署一个空投合约,通过在ERC20代币approve给空投合约可以转移的权限,然后空投合约去处理空投的逻辑。

ERC20FixedSupply.sol:

// SPDX-License-Identifier: MIT 
pragma solidity >=0.8.0;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";

// 固定总量代币
contract ERC20FixedSupply is ERC20 {
    constructor(
        string memory name, // 代币名称
        string memory symbol, // 代币缩写
        uint256 totalSupply // 发行总量
    ) ERC20(name, symbol) {
        _mint(msg.sender, totalSupply);
    }
}

ERC20Airdrop.sol:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";

// 空投合约
contract ERC20Airdrop {
    function multiTransferToken(
        address _token,
        address[] calldata _addresses,
        uint256[] calldata _amounts
    ) external {
        require(
            _addresses.length == _amounts.length,
            "Lengths of Addresses and Amounts NOT EQUAL"
        );
        ERC20 token = ERC20(_token);
        uint256 _amountSum = getSum(_amounts);
        require(
            token.allowance(msg.sender, address(this)) >= _amountSum,
            "Need Approve ERC20 token"
        );

        for (uint8 i = 0; i < _addresses.length; i++) {
            token.transferFrom(msg.sender, _addresses[i], _amounts[i]);
        }
    }

    // 数组求和函数
    function getSum(uint256[] calldata _arr) public pure returns (uint256 sum) {
        for (uint256 i = 0; i < _arr.length; i++) sum = sum + _arr[i];
    }
}

部署完这两个合约后,首先通过在ERC20代币合约中approve给空投合约可以转移代币的权限及代币数量,最后调用空投合约的multiTransferToken(address _token, address[] calldata _addresses, uint256[] calldata _amounts),通过传入ERC20代币地址、空投的地址数组、地址对应空投的代币数来实现空投的逻辑。

十、ERC20代币之WETH

WETHETH通过智能合约包装的版本,我们常见的WETHWBTCWBNB都是对区块链原生代币通过智能合约包装的版本,因为如以太坊链原生代币ETH本身是不符合ERC20标准的,WETH可以包装后可以使ETH用于如DAPP,或跨链等。

ERC20WETH.sol:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";

contract WETH is ERC20{
    // 事件:存款和取款
    event  Deposit(address indexed dst, uint wad);
    event  Withdrawal(address indexed src, uint wad);

    // 构造函数,初始化ERC20的名字和代号
    constructor() ERC20("WETH", "WETH"){
    }

    // 回调函数,当用户往WETH合约转ETH时,会触发deposit()函数
    fallback() external payable {
        deposit();
    }
    // 回调函数,当用户往WETH合约转ETH时,会触发deposit()函数
    receive() external payable {
        deposit();
    }

    // 存款函数,当用户存入ETH时,给他铸造等量的WETH
    function deposit() public payable {
        _mint(msg.sender, msg.value);
        emit Deposit(msg.sender, msg.value);
    }

    // 提款函数,用户销毁WETH,取回等量的ETH
    function withdraw(uint amount) public {
        require(balanceOf(msg.sender) >= amount);
        _burn(msg.sender, amount);
        payable(msg.sender).transfer(amount);
        emit Withdrawal(msg.sender, amount);
    }
}

使用一个智能合约,用户在转入ETH时给他生成等量的WETH,用户在取款ETH时给他销毁等量的WETH,实现对ETH的包装。

到此我们就完成了常见的ERC20代币的发行,希望小伙伴给我多点赞、收藏哦~

点赞 5
收藏 5
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
Blue
Blue
0xA108...9999
积极主动,顺势而为!