如何使用 Solidity 和 Hardhat 编写 ERC20 代币预售智能合约
介绍
本文将向您介绍 ERC20 代币以及如何使用 Solidity 和 Hardhat 编写 ERC20 代币预售智能合约。
理论
什么是 ERC20 代币?
- ERC-20 是一种技术标准;它用于以太坊区块链上所有用于代币实现的智能合约,并提供了所有基于以太坊的代币必须遵循的规则列表。
- 您可以在继续之前检查所有 ERC20功能。
什么是首次代币发行(ICO)?
- 首次代币发行(ICO)是加密货币行业的一种融资机制,类似于传统金融领域的首次公开募股(IPO)。
智能合约的开发
ERC20代币合约
- 代币规范 
  - 代币名称:MARK 代币
- 代币符号:MRK
- 代币十进制:18
- 总供应量:100,000,000,000
- 代币类型:ERC20
 
- 代币合约
我们将使用 OpenZeppelin ERC20 合约来创建我们的代币,并向合约所有者铸造 1000 亿个代币。
//SPDX-License-Identifier: MIT
pragma solidity ^0.8.27;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
contract MKT is ERC20 {
    uint256 private _totalSupply = 100_000_000_000;
    constructor() ERC20("MARK Token", "MKT") {
        _mint(msg.sender, _totalSupply * 10 ** decimals());
    }
}
代币预售合约
- 
  预售规格 - 预售供应量:100亿(10%)
- 预售期:30天
- 软顶:300000 USDT
- 硬顶:1000000 USDT
- 使用 ETH 和 USDT 购买代币
 
- 
  主要功能 - 买
- 轮次管理
- 宣称
- 提取
 
- 
  执行 
我们将使用 Chainlink 预言机获取 USDT 和 ETH 的最新价格。您也可以使用 Uniswap 或 PancakeSwap 获取 USDT 和 ETH 的价格。
使用 ETH 购买 MARK 代币
  function buy_with_eth()
        external
        payable
        nonReentrant
        whenNotPaused
        canPurchase(_msgSender(), msg.value)
        returns (bool)
    {
        uint256 amount_in_usdt = (msg.value * get_eth_in_usdt()) / 1e30;
        require(
            round_list[current_round_index].usdt_round_raised + amount_in_usdt <
                round_list[current_round_index].usdt_round_cap,
            "BUY ERROR : Too much money already deposited."
        );
        uint256 amount_in_tokens = (amount_in_usdt *
            round_list[current_round_index].usdt_to_token_rate) * 1e3;
        users_list[_msgSender()].usdt_deposited += amount_in_usdt;
        users_list[_msgSender()].tokens_amount += amount_in_tokens;
        round_list[current_round_index].usdt_round_raised += amount_in_usdt;
        (bool sent,) = round_list[current_round_index].wallet.call{value: msg.value}("");
        require(sent, "Failed to send Ether");
        emit Deposit(_msgSender(), 1, amount_in_usdt, amount_in_tokens);
        return true;
    }
首先,该函数通过修饰符进行了几项检查:
- nonReentrant 可防止重入攻击
- whenNotPaused 确保合同没有暂停
- canPurchase 验证预售是否有效以及购买金额是否有效
接下来,它使用 Chainlink 预言机价格信息计算已发送 ETH 的 USDT 等值
  function get_eth_in_usdt() internal view returns (uint256) {
        (, int256 price, , , ) = price_feed.latestRoundData();
        price = price * 1e10;
        return uint256(price);
    }
并检查购买金额是否在圆形上限之内。
接下来,根据本轮汇率,计算相当于 USDT 的代币数量:
接下来,它更新用户和回合的状态:
- 记录用户的 USDT 存款和代币分配
- 更新本轮募集的 USDT 总额
- 将 ETH 转入本轮的钱包地址
最后,它会发出一个包含购买详情的存款事件,并在交易成功时返回 true。
使用 USDT 购买 MARK 代币
与 ETH 购买类似,我们可以用 USDT 定义 buy 函数,如下所示。唯一的区别在于,它处理直接的 USDT 转账,而不是使用价格预言机进行转换。
 function buy_with_usdt(uint256 amount_)
        external
        nonReentrant
        whenNotPaused
        canPurchase(_msgSender(), amount_)
        returns (bool)
    {
        uint256 amount_in_usdt = amount_;
        require(
            round_list[current_round_index].usdt_round_raised + amount_in_usdt <
                round_list[current_round_index].usdt_round_cap,
            "BUY ERROR : Too much money already deposited."
        );
        uint256 allowance = usdt_interface.allowance(msg.sender, address(this));
        require(amount_ <= allowance, "BUY ERROR: Allowance is too small!");
        (bool success_receive, ) = address(usdt_interface).call(
            abi.encodeWithSignature(
                "transferFrom(address,address,uint256)",
                msg.sender,
                round_list[current_round_index].wallet,
                amount_in_usdt
            )
        );
        require(success_receive, "BUY ERROR: Transaction has failed!");
        uint256 amount_in_tokens = (amount_in_usdt *
            round_list[current_round_index].usdt_to_token_rate) * 1e3;
        users_list[_msgSender()].usdt_deposited += amount_in_usdt;
        users_list[_msgSender()].tokens_amount += amount_in_tokens;
        round_list[current_round_index].usdt_round_raised += amount_in_usdt;
        emit Deposit(_msgSender(), 3, amount_in_usdt, amount_in_tokens);
        return true;
    }
领取令牌
function claim_tokens() external returns (bool) {
        require(presale_ended, "CLAIM ERROR : Presale has not ended!");
        require(
            users_list[_msgSender()].tokens_amount != 0,
            "CLAIM ERROR : User already claimed tokens!"
        );
        require(
            !users_list[_msgSender()].has_claimed,
            "CLAIM ERROR : User already claimed tokens"
        );
        uint256 tokens_to_claim = users_list[_msgSender()].tokens_amount;
        users_list[_msgSender()].tokens_amount = 0;
        users_list[_msgSender()].has_claimed = true;
        (bool success, ) = address(token_interface).call(
            abi.encodeWithSignature(
                "transfer(address,uint256)",
                msg.sender,
                tokens_to_claim
            )
        );
        require(success, "CLAIM ERROR : Couldn't transfer tokens to client!");
        return true;
    }
此功能
- 检查预售是否已结束
- 验证用户是否有可领取的令牌,并且之前没有领取过
- 检索并存储用户可领取的代币数量
- 将用户的代币余额重置为 0,并标记为已认领
- 使用代币合约接口将代币转移给用户
- 索赔成功则返回 true
提现代币
 function withdrawToken(address tokenContract, uint256 amount) external onlyOwner {
        IERC20(tokenContract).transfer(_msgSender(), amount);
    }
此功能
- 仅限合同所有者通过 onlyOwner 修饰符
- 允许所有者从合约中提取任何 ERC20 代币
- 以代币合约地址和金额作为参数
- 将指定金额转移到所有者的地址
回合管理
我们还需要定义函数来管理回合:
  function start_next_round(
        address payable wallet_,
        uint256 usdt_to_token_rate_,
        uint256 usdt_round_cap_
    ) external onlyOwner {
        current_round_index = current_round_index + 1;
        round_list.push(
            Round(wallet_, usdt_to_token_rate_, 0, usdt_round_cap_ * (10**6))
        );
    }
  function set_current_round(
        address payable wallet_,
        uint256 usdt_to_token_rate_,
        uint256 usdt_round_cap_
    ) external onlyOwner {
        round_list[current_round_index].wallet = wallet_;
        round_list[current_round_index]
            .usdt_to_token_rate = usdt_to_token_rate_;
        round_list[current_round_index].usdt_round_cap = usdt_round_cap_ * (10**6);
    }
  function get_current_round()
        external
        view
        returns (
            address,
            uint256,
            uint256,
            uint256
        )
    {
        return (
            round_list[current_round_index].wallet,
            round_list[current_round_index].usdt_to_token_rate,
            round_list[current_round_index].usdt_round_raised,
            round_list[current_round_index].usdt_round_cap
        );
    }
  function get_current_raised() external view returns (uint256) {
        return round_list[current_round_index].usdt_round_raised;
    }
结论
此 ERC20 代币合约和预售合约是进行代币预售的全面且安全的解决方案。它提供管理预售轮次、充值 USDT、领取代币、提取代币以及轮次管理等功能。该合约设计灵活,可针对不同的预售场景进行定制。
文章来源:https://dev.to/marksantiago02/how-to-write-ico-smart-contract-using-solidity-and-hardhat-4pmg 后端开发教程 - Java、Spring Boot 实战 - msg200.com
            后端开发教程 - Java、Spring Boot 实战 - msg200.com