发币防止大户提前跑路 - 手搓一个线性释放合约

线性释放(LinearVesting)是一种常见的代币释放机制,指得是:代币按照固定的速率、持续均匀地在设定时间内逐渐释放。而不是一次性就释放完。

什么是线性释放

线性释放(Linear Vesting)一种常见的代币释放机制,指得是:代币按照固定的速率、持续均匀地在设定时间内逐渐释放。而不是一次性就释放完。

为什么需要线性释放

我们来思考一下,假如没有线性释放的场景。就比如,我们作为项目方,将要发行一个叫做 ShawnCoinERC20 代币,总量为 10 亿。为了奖励投资者(风投机构、私募),我们需要为他们发放 5 亿的代币,如果是一次性释放给他们的话,假如他们马上就抛售代币,会导致币价下跌非常迅猛,直接砸穿币价,散户成为接盘侠。

而有了线性释放的话,我们将代币均匀地按时间分摊释放给到投资者,将降低投资者抛售代币的压力,防止投资者过早跑路。

线性释放的核心流程

为了实现代币线程释放的要求,我们必须要设定一个释放的起始时间 start ,一个释放持续时间 duration 。在合约内,投资者主动调用 release 函数来获取代币,而在这个 release 函数中,我们通过一套数学公式来确定出每个时间片中该释放多少代币。 这么讲可能有点抽象,我们通过一个具体例子来展示流程:

  • 我们的 ShawnCoin 代币,将要释放给一个叫 Steve 的投资者,释放给他的总量为 10 个。
  • 释放的开始时间为 12798910(假设的时间戳),持续时间为 10 s。(即释放到 12798920 这个时间戳后停止释放),那么,每秒的释放个数为 1 个代币。
  • 拥有了这个关系之后,那么我们在这个 release 函数中,就可以通过数学关系来确定投资者可收到的代币数量了。具体看以下代码。

核心状态变量

线性释放合约中的核心状态变量有 4

  • beneficiary:受益人地址,在合约中,我们设定为合约的 owner。规定了这个合约是专供此人释放代币使用的。
  • start:即我们上面讲的 start 时间。释放的开始时间。
  • duration:释放的持续时间,在这个持续时间内,代币均匀释放。
  • erc20Released: 这是一个 mapping 的结构,记录了受益人已经领取的代币数量。供在计算可领取数量的时候减去已经领取的数量。

核心函数

  • 构造函数:初始化受益人地址、开始时间、持续时间。
  • release(): 投资者提币的函数,投资者主动触发该函数,在次函数内,根据线性释放的关系,计算可提币的数量,将代币发送到受益人地址中。
  • vestedAmount(): 计算可提币数量的函数,可供外部查询以及 release() 提币中计算使用。

手搓一个线性释放的合约

手搓最小代码实现,参考 OZ 代码库 VestingWallet.sol

  • 链上合约 LinearVesting.sol

    contract LinearVesting is Ownable {
    /*记录已领取数量*/
    mapping(address => uint256) public erc20Released;
    uint256 public immutable start;
    uint256 public immutable duration;
    
    /*构造函数,初始化合约参数*/
    constructor(uint256 _start, uint256 _duration, address beneficiary) Ownable(beneficiary){
        start = _start;
        duration = _duration;
    }
    /*受益人提币*/
    function release(address token) external {
        /*计算可提币数量 = 已释放数量 - 已提取数量*/
        uint256 releasable = vestedAmount(token, uint256(block.timestamp)) - erc20Released[token];
        /*更新已提取数量*/
        erc20Released[token] += releasable;
      /*转出*/
        IERC20(token).transfer(owner(), releasable);
    }
    
    /*计算已释放数量*/
    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 {
            /* (总量 x 已过时长)/ 总时长*/
            return (totalAllocation * (timestamp - start)) / duration;
        }
    }
    }
  • 线性释放测试 LinearVestingTest.t.sol

    contract MockToken is OZERC20 {
    constructor() OZERC20("MockToken", "MTK") {}
    function mint(address to, uint256 amount) external {
        _mint(to, amount);
    }
    }
    
    contract LinearVestingTest is Test {
    LinearVesting vesting;
    MockToken token;
    address beneficiary;
    
    uint256 start = uint256(keccak256(abi.encodePacked(block.timestamp)));
    uint256 duration = 30 minutes;
    uint256 constant TOTAL_AMOUNT = 1_000 ether;
    
    function setUp() public {
        /*受益人*/
        beneficiary = address(0xBEEF);
        /*构建合约*/
        vesting = new LinearVesting(start, duration, beneficiary);
        token = new MockToken();
        /*铸币*/
        token.mint(address(this), TOTAL_AMOUNT);
        /*转入合约中,待释放*/
        token.transfer(address(vesting), TOTAL_AMOUNT);
    }
    
    /*释放之前、之间、之后*/
    function test_Releases() public {
        /*之前*/
        console.log("==== start =====");
        console.logUint(start);
        console.log("===== duration ====");
        console.logUint(duration);
        vm.warp(start - duration);
        vm.prank(beneficiary);
        vesting.release(address(token));
    
        uint256 expectedBefore = 0;
        /*释放 0 */
        assertEq(token.balanceOf(beneficiary), expectedBefore);
    
        /*之间*/
        vm.warp(start + duration / 2);
        vm.prank(beneficiary);
        vesting.release(address(token));
    
        uint256 expected = TOTAL_AMOUNT / 2;
        /*一半时间提取,应该释放一半代币*/
        assertEq(token.balanceOf(beneficiary), expected);
    
    }
    }
  • 测试命令
 forge test --match-path "./test/linearVesting/LinearVestingTest.t.sol"  -vvv

image.png

点赞 0
收藏 0
分享
10 订阅 25 篇文章

0 条评论

请先 登录 后评论
shawn_shaw
shawn_shaw
web3潜水员、技术爱好者、web3钱包开发工程师、欢迎交流工作机会。欢迎闲聊、交流技术、交流工作:vx:cola_ocean