> 文档中心 > Solidity学习之路 - 一步一步写一个空投合约

Solidity学习之路 - 一步一步写一个空投合约

// SPDX-License-Identifier: MITpragma solidity >=0.8.0;import "@openzeppelin/contracts/token/ERC20/ERC20.sol";import "@openzeppelin/contracts/token/ERC20/IERC20.sol";import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";contract Airdropper {    using SafeERC20 for IERC20;    address private owner;    uint256 public airdropPerAmount; // 每人每次可以领取的空投数量    uint256 public airdropTotalAmount; // 每人可以领取的空投总数    uint256 public airdropTerm; // 多久可以领取一次(单位s),未设置则每人只能领一次    uint256 public airdropStartTime; // 空投开始时间(时间戳)    uint256 public airdropDeadline; // 空投截止时间(时间戳)    address public tokenAddress; // 空投token地址    mapping(address => uint256) public airdropRecord; // 每个人空投领取总额    mapping(address => uint256) public airdropTimeRecord; // 最后一次领取空投时间    // 发布合约时需传入5个参数,注意精度问题    constructor(uint256 _airdropPerAmount, uint256 _airdropTotalAmount, uint256 _airdropTerm, uint256 _airdropStartTime, uint256 _airdropDeadline) { owner = msg.sender; airdropPerAmount = _airdropPerAmount; airdropTotalAmount = _airdropTotalAmount; airdropTerm = _airdropTerm; airdropStartTime = _airdropStartTime; airdropDeadline = _airdropDeadline;    }    // 批量发放空投,dests和values两个数组长度若相等,则给不同地址发放对应数量token,如果values只有一个元素,则每个地址发放等量token    function doAirdrop(address[] memory dests, uint256[] memory values) external virtual returns (uint256) { // 批量发放一般为官方操作,不受时间、领取额度等以上各种条件限制 require(msg.sender == owner, 'Airdropper: forbidden'); require(tokenAddress != address(0), 'Airdropper: address not zero'); uint256 i = 0; while (i < dests.length) {     uint sendAmount = values.length == 1 ? values[0] : values[i];     // 判断当前合约中剩余token是否够发放数量,如果不够则结束发放并返回已发放的最后一个索引     if(ERC20(tokenAddress).balanceOf(address(this))  0){  IERC20(tokenAddress).safeTransfer(dests[i], sendAmount);     }   i++; } return i;    }    // 个人领取空投    function getAirdrop() external virtual returns(bool){ // token地址不能为0地址 require(tokenAddress != address(0), 'Airdropper: address not zero'); // 每人每次可以领取的空投数量要大于0 require(airdropPerAmount > 0, 'Airdropper: no parameter set'); // 当前时间要大于空投开始时间 require(block.timestamp >= airdropStartTime, 'Airdropper: not started'); if(airdropTotalAmount > 0){     // 如果设置了 每人可以领取的空投总数 这个参数,则验证已领取数量要小于这个总数     require(airdropRecord[msg.sender]  0) {     // 如果设置了领取周期参数,则验证当前时间减去上次领取时间大于这个周期     require(block.timestamp - airdropTimeRecord[msg.sender] > airdropTerm , 'Airdropper: term limit'); } else {     // 如果没有设置周期参数,则验证没有领取过可以领取,只能领1次     require(airdropRecord[msg.sender] == 0, 'Airdropper: you have already received'); } if (airdropDeadline > 0) {     // 如果设置了空投截止时间,则验证当前时间小于截止时间     require(airdropDeadline > block.timestamp, 'Airdropper: deadline'); } // 验证当前合约token数量够发放数量 require(ERC20(tokenAddress).balanceOf(address(this)) >= airdropPerAmount, 'Airdropper: insufficient assets'); // 执行发放 IERC20(tokenAddress).safeTransfer(msg.sender, airdropPerAmount); // 累计领取总数 airdropRecord[msg.sender] += airdropPerAmount; // 记录最后领取时间 airdropTimeRecord[msg.sender] = block.timestamp; return true;    }    // 充入token    function recharge(address _tokenAddress, uint256 _amount) external virtual returns(bool) { require(msg.sender == owner, 'Airdropper: forbidden'); require(_tokenAddress != address(0), 'Airdropper: forbidden'); if(tokenAddress == address(0)){     // 第一次充入,配置token地址     tokenAddress = _tokenAddress; } else {     // 否则验证充入的token和配置的地址一致     require(_tokenAddress == tokenAddress, 'Airdropper: Error token address'); } // 执行充入token IERC20(tokenAddress).safeTransferFrom(msg.sender, address(this), _amount); return true;    }    // 提出剩余token    function withdraw() external virtual returns(bool) { require(msg.sender == owner, 'Airdropper: forbidden'); require(tokenAddress != address(0), 'Airdropper: address not zero'); // 将剩余token全部转给合约发布者 IERC20(tokenAddress).safeTransfer(owner, ERC20(tokenAddress).balanceOf(address(this))); tokenAddress = address(0); // 重置token地址 return true;    }    /**     * 以下是配置各个参数的接口,只有合约发布者可以调用     */    function setPerAmount(uint256 _airdropPerAmount) external virtual returns(bool) { require(msg.sender == owner, 'Airdropper: forbidden'); airdropPerAmount = _airdropPerAmount; return true;    }    function setTotalAmount(uint256 _airdropTotalAmount) external virtual returns(bool) { require(msg.sender == owner, 'Airdropper: forbidden'); airdropTotalAmount = _airdropTotalAmount; return true;    }    function setTerm(uint256 _airdropTerm) external virtual returns(bool) { require(msg.sender == owner, 'Airdropper: forbidden'); airdropTerm = _airdropTerm; return true;    }    function setStartTime(uint256 _airdropStartTime) external virtual returns(bool) { require(msg.sender == owner, 'Airdropper: forbidden'); airdropStartTime = _airdropStartTime; return true;    }    function setDeadline(uint256 _airdropDeadline) external virtual returns(bool) { require(msg.sender == owner, 'Airdropper: forbidden'); airdropDeadline = _airdropDeadline; return true;    }}

这是一个经过实践的空投合约,在bsc和matic可以直接使用,在trc上问题应该也不大。很高兴分享给大家。在一些审计比较严格的情况下会报warning,每个函数都要有Event,自己加上就行。提示词就略过吧,实在懒得翻译,用的时候自己换一个自己喜欢的就好。

后续还会不断发布各类经过审计且完整可用的合约模板。现在正在整理过往项目,准备开发一个通用的Dapp快速开发框架。

加关注,不迷路,很开心和大家一起交流学习心得。