快速实现一个约定代币归属条款的智能合约之线性释放

186 阅读5分钟

前言

本文实现一个线性释放合约,主要用来解决以下问题: 防止市场抛售压力、激励团队和投资者、稳定市场和建立信任

线性释放

定义:一种常见的代币或股权分配机制,通常用于激励团队成员、早期投资者或顾问在一定时间内持续为项目做出贡献。线性释放机制确保代币或股权在预定的时间内逐步解锁,而不是一次性发放。这种机制有助于保持团队的稳定性和长期承诺,同时也为项目提供了一种激励机制,确保关键人员在项目的关键阶段持续参与

特点
  1. 逐步解锁:代币或股权在预定的时间内逐步解锁,通常以固定的时间间隔(如每月或每季度)释放一定比例。
  2. 激励长期参与:通过逐步解锁,激励团队成员和投资者在项目的关键阶段持续参与,确保他们对项目的长期成功有共同的利益。
  3. 减少短期套现风险:避免一次性发放代币或股权后,团队成员或投资者立即套现,从而减少对项目的短期负面影响。
  4. 灵活的释放计划:可以根据项目的具体需求和目标,定制不同的释放计划,包括释放的总时间、释放的频率和每次释放的比例
场景
  1. 团队激励:确保团队成员在项目的关键阶段持续参与,激励他们为项目的长期成功做出贡献。
  2. 早期投资者:激励早期投资者在项目的关键阶段持续支持项目,确保他们对项目的长期成功有共同的利益。
  3. 顾问和合作伙伴:激励顾问和合作伙伴在项目的关键阶段提供持续的支持和资源,确保他们对项目的长期成功有共同的利益。

合约开发

// SPDX-License-Identifier: MIT
// wtf.academy
pragma solidity ^0.8.22;
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "hardhat/console.sol";
/**
 * @title ERC20代币线性释放
 * @dev 这个合约会将ERC20代币线性释放给给受益人`_beneficiary`。
 * 释放的代币可以是一种,也可以是多种。释放周期由起始时间`_start`和时长`_duration`定义。
 * 所有转到这个合约上的代币都会遵循同样的线性释放周期,并且需要受益人调用`release()`函数提取。
 * 合约是从OpenZeppelin的VestingWallet简化而来。
 */
contract LinearRelease {
    // 事件
    event ERC20Released(address indexed token, uint256 amount); // 提币事件

    // 状态变量
    mapping(address => uint256) public erc20Released; // 代币地址->释放数量的映射,记录受益人已领取的代币数量
    address public immutable beneficiary; // 受益人地址
    uint256 public immutable start; // 归属期起始时间戳
    uint256 public immutable duration; // 归属期 (秒)

    /**
     * @dev 初始化受益人地址,释放周期(秒), 起始时间戳(当前区块链时间戳)
     */
    constructor(
        address beneficiaryAddress,
        uint256 durationSeconds
    ) {
        require(beneficiaryAddress != address(0), "VestingWallet: beneficiary is zero address");
        beneficiary = beneficiaryAddress;
        start = block.timestamp;
        duration = durationSeconds;
    }

    /**
     * @dev 受益人提取已释放的代币。
     * 调用vestedAmount()函数计算可提取的代币数量,然后transfer给受益人。
     * 释放 {ERC20Released} 事件.
     */
    function release(address token) public {
        // 调用vestedAmount()函数计算可提取的代币数量
        uint256 releasable = vestedAmount(token, uint256(block.timestamp)) - erc20Released[token];
        // 更新已释放代币数量   
        erc20Released[token] += releasable; 
        // 转代币给受益人
        emit ERC20Released(token, releasable);
        IERC20(token).transfer(beneficiary, releasable);
    }

    /**
     * @dev 根据线性释放公式,计算已经释放的数量。开发者可以通过修改这个函数,自定义释放方式。
     * @param token: 代币地址
     * @param timestamp: 查询的时间戳
     */
    function  vestedAmount(address token, uint256 timestamp) public view returns (uint256) {
        // 合约里总共收到了多少代币(当前余额 + 已经提取)
        uint256 totalAllocation = IERC20(token).balanceOf(address(this)) + erc20Released[token];
        // 根据线性释放公式,计算已经释放的数量
        if (timestamp < start) {
            return 0;
        } else if (timestamp > start + duration) {
            return totalAllocation;
        } else {
            return (totalAllocation * (timestamp - start)) / duration;
        }
    }
}
# 编译指令 
# npx hardhat compile

合约测试

说明:测试释放方法release,1.保证链接的时受益人的账号地址,2.传递参数:代币的合约地址

const {ethers,getNamedAccounts,deployments} = require("hardhat");
const { assert,expect } = require("chai");
describe("LinearRelease",function(){
    let LinearRelease;//合约
    let MyToken;//代币合约
    let firstAccount//第一个账户
    let secondAccount//第二个账户
    let addr1;
    let addr2;
    beforeEach(async function(){
        await deployments.fixture(["token","LinearRelease"]);
        [addr1,addr2]=await ethers.getSigners();
        firstAccount=(await getNamedAccounts()).firstAccount;
        secondAccount=(await getNamedAccounts()).secondAccount;
        //代币
        const TokenDeployment = await ethers.getContract("MyToken");//获取合约
        MyToken = await ethers.getContractAt("MyToken",TokenDeployment.address);//已经部署的合约交互
        //线性释放

        const LinearReleaseDeployment = await deployments.get("LinearRelease");
        LinearRelease = await ethers.getContractAt("LinearRelease",LinearReleaseDeployment.address);//已经部署的合约交互
    });
    describe("LinearRelease测试",function(){
        it("测试",async function(){
             // 获取当前区块时间
            const currentTime = await ethers.provider.getBlock("latest").then(block => block.timestamp);
            console.log("获取当前区块时间",currentTime)
            // 预定执行时间:当前时间 + 100s
            const executeTime = currentTime + 100;
            console.log("预定执行时间",executeTime)
            //获取token账号余额
            console.log(await MyToken.balanceOf(firstAccount))
            //把代币发给线性释放合约
            await MyToken.mint(firstAccount,ethers.utils.parseEther("200"));
            console.log(await MyToken.balanceOf(addr1.address))
            await MyToken.transfer(LinearRelease.address,ethers.utils.parseEther("1200"));
            console.log("线性合约余额",await MyToken.balanceOf(LinearRelease.address))
            console.log("代币账号余额",await MyToken.balanceOf(firstAccount))
            let beneficiary=await LinearRelease.beneficiary()
            console.log("收益人地址",beneficiary)
            console.log("开始时间",await LinearRelease.start())
            let time=await LinearRelease.duration()
            console.log("时长秒",time)
            //模拟10s后修改 动态设置时间来模拟
            let simulateTime=10;
            
            await ethers.provider.send("evm_increaseTime", [simulateTime]);
            await ethers.provider.send("evm_mine", []);
            //线性释放 参数说明 保证收益人账号正确链接,release传的参数 锁定代币合约地址
            await LinearRelease.connect(addr1).release(MyToken.address);
            let balance=await MyToken.balanceOf(addr1.address)
            console.log("线性合约释放的代币,受益人的代币额度",`${ethers.utils.formatEther(balance)} ETH`)
            
            
        })
    })
})
# 测试指令
# npx hardhat test ./test/xxx.js

合约部署

module.exports = async function ({getNamedAccounts,deployments}) {
  const  firstAccount= (await getNamedAccounts()).firstAccount;
  const  secondAccount= (await getNamedAccounts()).secondAccount;
  const {deploy,log}=deployments;
  const LinearRelease=await deploy("LinearRelease",{
    from:firstAccount,
    args: [firstAccount,100],//参数  
    log: true,
  })
  console.log('LinearRelease合约地址',LinearRelease.address)
}
module.exports.tags = ["all", "LinearRelease"];
# 部署指令
# npx hardhat deploy

总结

以上就是线性释放合约开发、测试、部署全流程以及合约的使用场景的介绍,注意:在调用释放方法release时链接受益人账号,参数:代币的合约地址;