0%

solidity | 线性释放

参考资料

在区块链领域,Web3 初创公司会给团队分配代币,同时也会将代币低价出售给风投和私募。如果他们把这些低成本的代币同时提到交易所变现,币价将被砸穿,散户直接成为接盘侠。

所以,项目方一般会约定代币归属条款(token vesting),在归属期内逐步释放代币,减缓抛压,并防止团队和资本方过早躺平。

线性释放指的是代币在归属期内匀速释放。举个例子,某私募持有 365,000ICU 代币,归属期为1年(365天),那么每天会释放 1,000 枚代币。

逻辑

  • 创造一个 erc20 的合约
  • 创造一个「线性释放」合约
  • erc20 合约向 「线性释放」传入固定数量的代币
  • 相关机构可以调用「线性释放」合约进行获得

线性释放合约

状态变量

线性释放合约中共有 4 个状态变量。

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

函数

线性释放合约中共有 3 个函数。

  • 构造函数:初始化受益人地址,归属期(秒), 起始时间戳。参数为受益人地址 beneficiaryAddress 和归属期 durationSeconds。为了方便,起始时间戳用的部署时的区块链时间戳 block.timestamp
  • release():提取代币函数,将已释放的代币转账给受益人。调用了 vestedAmount() 函数计算可提取的代币数量,释放 ERC20Released 事件,然后将代币 transfer 给受益人。参数为代币地址 token
  • vestedAmount():根据线性释放公式,查询已经释放的代币数量。开发者可以通过修改这个函数,自定义释放方式。参数为代币地址 token 和查询的时间戳 timestamp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
/**
* @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;
}
}

使用 @openzeppelin

首先使用 erc20

我们借助 @openzeppelin

  • import "@openzeppelin/contracts/finance/VestingWallet.sol";

代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/finance/VestingWallet.sol";
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";

contract erc20 is ERC20 {
constructor(string memory name_, string memory symbol_) ERC20(name_, symbol_){
}

function mint() public {
_mint(msg.sender, 10000);
}
}

contract WTFApe is VestingWallet {

constructor(
address beneficiaryAddress,
uint64 startTimestamp,
uint64 durationSeconds
)VestingWallet(beneficiaryAddress, startTimestamp, durationSeconds){

}
}
  • 使用账号
    • 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4
  • 部署好 erc20
    • 合约地址 0xd8b934580fcE35a11B58C6D73aDeE468a2833fa8
    • mint token
      • 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4balanceof10000
  • 部署 WTFApe
    • 参数
      • 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4
      • 当前时间戳
      • 1000
    • 合约地址 0xD7ACd2a9FD159E69Bb102A1ca21C9a3e3A5F771B
  • 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4 转移 erc20 的币到 WTFApe 合约中
    • 此时在 erc20 中查询 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4balance0
  • 等待一段时间
  • WTFApe 调用 releasable 传入 erc20 地址为 0xd8b934580fcE35a11B58C6D73aDeE468a2833fa8
    • 返回 3000
  • WTCApe 调用取回函数
    • release(0xd8b934580fcE35a11B58C6D73aDeE468a2833fa8)
  • 此时在 erc20 查询 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4balance3000
请我喝杯咖啡吧~