线性释放(LinearVesting)是一种常见的代币释放机制,指得是:代币按照固定的速率、持续均匀地在设定时间内逐渐释放。而不是一次性就释放完。
线性释放(Linear Vesting
)一种常见的代币释放机制,指得是:代币按照固定的速率、持续均匀地在设定时间内逐渐释放。而不是一次性就释放完。
我们来思考一下,假如没有线性释放的场景。就比如,我们作为项目方,将要发行一个叫做 ShawnCoin
的 ERC20
代币,总量为 10
亿。为了奖励投资者(风投机构、私募),我们需要为他们发放 5
亿的代币,如果是一次性释放给他们的话,假如他们马上就抛售代币,会导致币价下跌非常迅猛,直接砸穿币价,散户成为接盘侠。
而有了线性释放的话,我们将代币均匀地按时间分摊释放给到投资者,将降低投资者抛售代币的压力,防止投资者过早跑路。
为了实现代币线程释放的要求,我们必须要设定一个释放的起始时间 start
,一个释放持续时间 duration
。在合约内,投资者主动调用 release
函数来获取代币,而在这个 release
函数中,我们通过一套数学公式来确定出每个时间片中该释放多少代币。
这么讲可能有点抽象,我们通过一个具体例子来展示流程:
ShawnCoin
代币,将要释放给一个叫 Steve
的投资者,释放给他的总量为 10
个。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
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!