第07课:空投合约

第07课:空投合约

上一篇对 Solidity 语法进行了讲解,本文将和大家一起编写我们常见的糖果空投合约。

“发送 0 个 ETH 到某个地址,立马获得 5000 枚 Token,每个地址只能获取一次”,相信大家对于此类糖果空投的信息都已经遇见过很多次了,也有很多朋友趁此机会薅了很多羊毛。身为开发人员,我们不应只是简单的薅羊毛,更应该深入地去研究此类糖果空投的实现原理。

最简单的实现方式是通过人工手动来处理每笔空投,但耗时耗力不够智能还极易出错。若是通过智能合约来实现,不仅省时省力,还不易出错。在学习糖果空投的技术实现之前,需要先对 ERC20 协议有所了解,网上已有很多此类文章,这里就不再介绍 ERC20 协议,默认大家已经有了 ERC20 协议的技术基础。

我们直接来看糖果空投的基础代码:

pragma solidity ^0.4.17;

//Token接口
interface Token {
    //ERC20 transfer()抽象方法
    function transfer(address _to, uint256 _value) external returns (bool);
}

contract Airdrop {
    //币种合约地址
    address tokenContractAddress;
    //需要空投的币种对象
    Token public airdropToken;
    //空投数量(单位为ether)
    uint public airdropNum = 10 ether;
    //空投黑名单(已空投过的地址)
    mapping (address => bool) public blackList;
    //构造函数
    constructor(address _tokenContractAddress) public {
        //校验token的合约地址是否为空地址
        require(_tokenContractAddress != address(0));

        tokenContractAddress = _tokenContractAddress;
        //获取币种合约对象
        airdropToken = Token(tokenContractAddress);
    }

    //空投(fallback函数)
    function () payable public {
        //校验转账金额是否小于0
        require(msg.value >= 0, '转账金额不能小于0');
        //校验是否是第一次领取空投
        require(blackList[msg.sender] == false, '你已领过空投');
        //调用transfer()转账方法
        airdropToken.transfer(msg.sender, airdropNum);
    }
}

我们先定义了一个 Token 接口合约,接口里定义了 ERC20 协议的 transfer() 抽象方法。然后实现了 Airdrop 空投合约,代码都加上了注释,很容易理解。需要注意的是,糖果空投的核心代码是通过 fallback 函数实现的。那么,何为 fallback 函数呢?

fallback 函数

合约中未命名的函数,并且没有声明参数,也没有返回值的函数就是 fallback 函数,在合约中有且仅有一个 fallback 函数。在合约调用时,若没有显示的声明调用的函数,且合约当中存在 fallback 函数时,那么合约中的 fallback 函数会被执行。当向某个合约直接转账时,由于这个行为没有发送任何数据,所以接收的合约也会调用 fallback 函数。我们就是利用了 Solidity 的这个特性,实现了空投的功能:

//空投(fallback函数)
function () payable public {
    //校验转账金额是否小于0
    require(msg.value >= 0, '转账金额不能小于0');
    //校验是否是第一次领取空投
    require(blackList[msg.sender] == false, '你已领过空投');
    //调用transfer()转账方法
    airdropToken.transfer(msg.sender, airdropNum);
}

部署上述合约后,当用户往合约里转入大于等于 0 个数量的 ETH 时,合约会自动向来源地址空投变量 airdropNum 等值的代币(这里是 10 枚)。需要注意的是,在合约部署成功后,要先往合约里转入一定数量的空投代币,才能正常给用户空投

时间控制

合约在部署成功后就会进行空投,如果我们限定只能在某个时间段内才能空投呢?只需要稍微修改一下代码即可实现。在 Airdrop 合约中添加如下状态变量:

//空投开始时间
uint startTime;
//空投持续天数
uint airdropDays;

并实现如下函数:

//返回当前时间戳
function getNow() public pure returns (uint) {
    return now;
}

//返回空投开始时间戳
function getStartTime() public view returns (uint) {
    return startTime;
}

//返回空投结束时间戳
function getEndTime() public view returns (uint) {
    return startTime + aridropDays * 1 days;
}

修改构造函数,传入空投开始时间 _startTime 及空投持续天数 _airdropDays 的参数:

//构造函数
constructor(uint _startTime, uint _airdropDays, address _tokenContractAddress) public {
    //校验空投开始时间是否大于0
    require(_startTime > 0, '空投开始时间必须大于0');
    //校验空投持续天数是否大于0
    require(_airdropDays > 0, '空投持续天数必须大于0');
    //校验空投开始时间是否大于当前时间
    require(_startTime >= now, '空投开始时间必须大于当前时间');
    //校验token的合约地址是否为空地址
    require(_tokenContractAddress != address(0));

    startTime = _startTime;
    airdropDays = _airdropDays;
    tokenContractAddress = _tokenContractAddress;
    //获取币种合约对象
    airdropToken = Token(tokenContractAddress);
}

在 fallback 函数中添加时间的判断条件:

//空投(fallback函数)
function () payable public {
    //时间校验
    if(now >= startTime && now < startTime + airdropDays * 1 days){
        //校验转账金额是否小于0
        require(msg.value >= 0, '转账金额不能小于0');
        //校验是否是第一次领取空投
        require(blackList[msg.sender] == false, '你已领过空投');
        //调用transfer()转账方法
        airdropToken.transfer(msg.sender, airdropNum);
    }
}

其中 now 会返回当前时间戳,但采用的是 UTC 时区,此处需要特别注意。

上述代码中使用了 Solidity 的时间单位进行时间条件判断,这里简单介绍下。seconds,minutes,hours,days、weeks、years 都是 Solidity 中时间单位的关键字,并且相互间可以转换:

1 minutes == 60 seconds
1 hours = 60 minutes
1 days == 24 hours
1 weeks = 7 days
1 years = 365 days

需要注意的是,以上时间单位不是非常精确,因为不是每年都有 365 天,也不是每天都是 24 小整,因为还有闰秒的存在。

提现合约中的 ETH

在上述空投合约代码中,用户只要转入大于等于 0 枚数量的 ETH 就能获得空投代币,可能绝大部分用户只会转账 0 ETH,但难免也会有部分用户进行“打赏”。转入合约的 ETH 会冻结在合约账户里,我们怎么提现合约中的 ETH 呢?

先在 Airdrop 合约中添加一个状态变量:

//合约部署者
address owner;

并修改构造函数,给其赋值:

//构造函数
constructor(uint _startTime, uint _airdropDays, address _tokenContractAddress) public {
    //校验空投开始时间是否大于0
    require(_startTime > 0, '空投开始时间必须大于0');
    //校验空投持续天数是否大于0
    require(_airdropDays > 0, '空投持续天数必须大于0');
    //校验空投开始时间是否大于当前时间
    require(_startTime >= now, '空投开始时间必须大于当前时间');
    //校验token的合约地址是否为空地址
    require(_tokenContractAddress != address(0));
    //将合约部署者的地址赋值给状态变量owner
    owner = msg.sender;
    startTime = _startTime;
    airdropDays = _airdropDays;
    tokenContractAddress = _tokenContractAddress;
    //获取币种合约对象
    airdropToken = Token(tokenContractAddress);
}

我们希望只有合约部署者才能提现合约里的 ETH,而不是任何人都可以提现,所以我们需要先实现一个函数修饰器,限制只有合约部署者才能调用:

modifier onlyOwner() {
    require(owner == msg.sender, '必须是合约部署者才能调用');
    _;
}

再实现提现函数:

function withdraw(address _address) external onlyOwner {
    //校验接收地址是否有效
    require(_address != address(0), '无效的接收地址');
    //将合约中的ETH余额转入_address
    _address.transfer(this.balance);
}

只需使用合约部署账户调用此函数就可将合约账户里的 ETH 全部提现出来。

批量空投

以上讲解的都是基于转账 0 ETH 空投的情况,一笔转账即一笔空投。如果我们想直接给一批活跃地址进行空投,需要如何实现呢?

代码逻辑并不是特别复杂,只需要实现一个批量转账的函数即可:

//批量空投
function batchTransfer(address[] _addresses) public onlyOwner{
    //校验地址数组是否为空
    require(_addresses.length > 0, '地址数组不能为空');
    //循环地址数组
    for(uint32 i=0; i<_addresses.length; i++){
        //校验某个地址是否已领过空投
        if(blackList[_addresses[i]] == false){
            //空投
            bool result = airdropToken.transfer(_addresses[i], airdropNum);
            if(result){
                //设置为已领过空投
                blackList[_addresses[i]] = true;
            }
        }
    }
}

通过外部调用传入一个地址数组,再循环进行转账(空投),即可实现在一笔 Transaction 里批量转账的处理。

上一篇
下一篇
内容互动
写评论
加载更多
评论文章