发币防止大户提前跑路 - 手搓一个线性释放合约
什么是线性释放
线性释放(Linear Vesting
)一种常见的代币释放机制,指得是:代币按照固定的速率、持续均匀地在设定时间内逐渐释放。而不是一次性就释放完。
为什么需要线性释放
我们来思考一下,假如没有线性释放的场景。就比如,我们作为项目方,将要发行一个叫做 ShawnCoin
的 ERC20
代币,总量为 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
版权声明
本文仅代表作者观点,不代表区块链技术网立场。
本文系作者授权本站发表,未经许可,不得转载。
发表评论:
◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。