0xAA

发布于 2022-08-11到 Mirror 阅读

WTF Solidity极简入门: 44. 代币锁

我最近在重新学solidity,巩固一下细节,也写一个“WTF Solidity极简入门”,供小白们使用(编程大佬可以另找教程),每周更新1-3讲。

推特:@0xAA_Science

社区:Discord微信群官网 wtf.academy

所有代码和教程开源在github: github.com/AmazingAng/WTFSolidity


这一讲,我们介绍什么是流动性提供者LP代币,为什么要锁定流动性,并写一个简单的ERC20代币锁合约。

代币锁

代币锁(Token Locker)是一种简单的时间锁合约,它可以把合约中的代币锁仓一段时间,受益人在锁仓期满后可以取走代币。代币锁一般是用来锁仓流动性提供者LP代币的。

什么是LP代币?

区块链中,用户在去中心化交易所DEX上交易代币,例如Uniswap交易所。DEX和中心化交易所不同,中心化交易所使用自动做市商机制,需要用户或项目方提供资金池,以使得其他用户能够即时买卖。简单来说,用户/项目方需要质押相应的币对(比如ETH/DAI)到资金池中,作为补偿,DEX会给他们铸造相应的流动性提供者LP代币凭证,证明他们质押了相应的份额,供他们收取手续费。

为什么要锁定流动性?

如果项目方毫无征兆的撤出流动性池中的LP代币,那么投资者手中的代币就无法变现,直接归零了。这种行为也叫rug-pull,仅2021年,各种rug-pull骗局从投资者那里骗取了价值超过28亿美元的加密货币。

但是如果LP代币是锁仓在代币锁合约中,在锁仓期结束以前,项目方无法撤出流动性池,也没办法rug pull。因此代币锁可以防止项目方过早跑路(要小心锁仓期满跑路的情况)。

代币锁合约

下面,我们就写一个锁仓ERC20代币的合约TokenLocker。它的逻辑很简单:

  • 开发者在部署合约时规定锁仓的时间,受益人地址,以及代币合约。

  • 开发者将代币转入TokenLocker合约。

  • 在锁仓期满,受益人可以取走合约里的代币。

事件

TokenLocker合约中共有2个事件。

  • TokenLockStart:锁仓开始事件,在合约部署时释放,记录受益人地址,代币地址,锁仓起始时间,和结束时间。

  • Release:代币释放事件,在受益人取出代币时释放,记录记录受益人地址,代币地址,释放代币时间,和代币数量。

    // 事件
    event TokenLockStart(address indexed beneficiary, address indexed token, uint256 startTime, uint256 lockTime);
    event Release(address indexed beneficiary, address indexed token, uint256 releaseTime, uint256 amount);

状态变量

TokenLocker合约中共有4个状态变量。

  • token:锁仓代币地址。

  • beneficiary:受益人地址。

  • locktime:锁仓时间(秒)。

  • startTime:锁仓起始时间戳(秒)。

    // 被锁仓的ERC20代币合约
    IERC20 public immutable token;
    // 受益人地址
    address public immutable beneficiary;
    // 锁仓时间(秒)
    uint256 public immutable lockTime;
    // 锁仓起始时间戳(秒)
    uint256 public immutable startTime;

函数

TokenLocker合约中共有2个函数。

  • 构造函数:初始化代币合约,受益人地址,以及锁仓时间。

  • release():在锁仓期满后,将代币释放给受益人。需要受益人主动调用release()函数提取代币。

    /**
     * @dev 部署时间锁合约,初始化代币合约地址,受益人地址和锁仓时间。
     * @param token_: 被锁仓的ERC20代币合约
     * @param beneficiary_: 受益人地址
     * @param lockTime_: 锁仓时间(秒)
     */
    constructor(
        IERC20 token_,
        address beneficiary_,
        uint256 lockTime_
    ) {
        require(lockTime_ > 0, "TokenLock: lock time should greater than 0");
        token = token_;
        beneficiary = beneficiary_;
        lockTime = lockTime_;
        startTime = block.timestamp;

        emit TokenLockStart(beneficiary_, address(token_), block.timestamp, lockTime_);
    }

    /**
     * @dev 在锁仓时间过后,将代币释放给受益人。
     */
    function release() public {
        require(block.timestamp >= startTime+lockTime, "TokenLock: current time is before release time");

        uint256 amount = token.balanceOf(address(this));
        require(amount > 0, "TokenLock: no tokens to release");

        token.transfer(beneficiary, amount);

        emit Release(msg.sender, address(token), block.timestamp, amount);
    }

Remix演示

1. 部署第31讲中的ERC20合约,并给自己铸造10000枚代币。

2. 部署ToeknLocker合约,代币地址为ERC20合约地址,受益人为自己,锁仓期填180秒。

3. 将10000枚代币转入合约。

4. 在锁仓期180秒内调用release()函数,无法取出代币。

5. 在锁仓期后调用release()函数,成功取出代币。

总结

这一讲,我们介绍了代币锁合约。项目方一般在DEX上提供流动性,供投资者交易。项目方突然撤出LP会造成rug-pull,而将LP锁在在代币锁合约中可以避免这种情况。