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

什么是线性释放

线性释放(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

```js  
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

    ```js 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)