在傳統金融領域,有些公司會向員工和管理階層提供股權。但大量股權同時釋出會在短期產生拋售壓力,拖累股價。因此,公司通常會引入一個歸屬期來延遲承諾資產的所有權。同樣的,在區塊鏈領域,Web3 新創公司會分配給團隊代幣,同時也會將代幣低價賣給風險投資和私募。如果他們把這些低成本的代幣同時提到交易所變現,幣價將被砸穿,散戶直接成為接盤俠。
所以,專案方一般會約定代幣歸屬條款(token vesting),在歸屬期內逐步釋放代幣,減緩拋壓,並防止團隊和資本方過早躺平。
線性釋放指的是代幣在歸屬期內勻速釋放。舉個例子,某私募持有 365000 枚 ICU 代幣,歸屬期為 1 年(365天),那麼每天會釋放 1,000 枚代幣。
下面是一個鎖倉並線性釋放 ERC20 代幣的合約 TokenVesting:
contract TokenVesting {
// 事件
event ERC20Released(address indexed token, uint256 amount); // 提幣事件,當受益人提取釋放代幣時釋放
// 狀態變數
mapping(address => uint256) public erc20Released; // 代幣地址->釋放數量的映射,記錄受益人已領取的代幣數量
address public immutable beneficiary; // 受益人地址
uint256 public immutable start; // 歸屬期起始時間戳記
uint256 public immutable duration; // 歸屬期,單位為秒
// 函數
/**
* @dev 建構子:初始化受益人地址,釋放週期(秒), 起始時間戳記(當前區塊鏈時間戳)
* 參數為受益人地址beneficiaryAddress和歸屬期durationSeconds。
* 為了方便,起始時間戳使用部署時的區塊鏈時間戳block.timestamp。
*/
constructor(
address beneficiaryAddress,
uint256 durationSeconds
) {
require(beneficiaryAddress != address(0), "VestingWallet: beneficiary is zero address");
beneficiary = beneficiaryAddress;
start = block.timestamp;
duration = durationSeconds;
}
/**
* @dev 提取代幣函數,將已釋放的代幣轉帳給受益人
* 呼叫了vestedAmount()函數計算可提取的代幣數量,然後將代幣transfer給受益人
* 參數為代幣地址token
* 釋放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;
}
}
代幣鎖(Token Locker)是一種簡單的時間鎖合約,它可以把合約中的代幣鎖倉一段時間,受益人在鎖倉期滿後可以取走代幣。代幣鎖一般是用來鎖倉流動性提供者 LP 代幣的。
區塊鏈中,使用者在去中心化交易所(DEX)上交易代幣,例如 Uniswap 交易所。DEX 和中心化交易所(CEX)不同,去中心化交易所使用自動做市商(AMM,Automated Market Maker)機制,需要使用者或專案方提供資金池,以使得其他用戶能夠即時買賣。簡單來說,使用者/專案方需要質押相應的幣對(例如ETH/DAI)到資金池中,作為補償,DEX 會給他們鑄造相應的流動性提供者 LP 代幣憑證,證明他們質押了相應的份額,供他們收取手續費。
如果專案方毫無徵兆的撤出流動性池中的 LP 代幣,那麼投資者手中的代幣就無法變現,直接歸零了。這種行為也叫 rug-pull,光是 2021 年,各種 rug-pull 騙局就從投資者那裡騙取了價值超過28億美元的加密貨幣。
但如果 LP 代幣是鎖倉在代幣鎖合約中,在鎖倉期結束前,專案方無法撤出流動性池,也沒辦法 rug pull。因此代幣鎖可以防止專案方過早跑路(要小心鎖倉期滿跑路的情況)。
下面是一個鎖倉 ERC20 代幣的合約 TokenLocker:
contract TokenLocker {
// 事件
event TokenLockStart(address indexed beneficiary, address indexed token, uint256 startTime, uint256 lockTime); // 鎖倉開始事件,在合約部署時釋放,記錄受益人地址,代幣地址,鎖倉起始時間,和結束時間。
event Release(address indexed beneficiary, address indexed token, uint256 releaseTime, uint256 amount); // 代幣釋放事件,在受益人取出代幣時釋放,記錄記錄受益人地址,代幣地址,釋放代幣時間,和代幣數量。
// 狀態變數
IERC20 public immutable token; // 被鎖倉的ERC20代幣合約
address public immutable beneficiary; // 受益人地址
uint256 public immutable lockTime; // 鎖倉時間(秒)
uint256 public immutable startTime; // 鎖倉起始時間戳(秒)
// 函數
/**
* @dev 初始化代幣合約,受益人地址,以及鎖倉時間
* @param token_: 被鎖倉的 ERC20 代幣合約
* @param beneficiary_: 受益人地址
* @param lockTime_: 鎖倉時間(秒)
*/
constructor(
IERC20 token_,
address beneficiary_,
uint256 lockTime_
) {
require(lockTime_ > 0, "TokenLock: lock time should greater than 0");
token = token_;
beneficiary = beneficiary_;
lockTime = lockTime_;
startTime = block.timestamp;
emit TokenLockStart(beneficiary_, address(token_), block.timestamp, lockTime_);
}
/**
* @dev 在鎖倉時間過後,將代幣釋放給受益人。
* 需要受益人主動調用release()函數提取代幣
*/
function release() public {
require(block.timestamp >= startTime+lockTime, "TokenLock: current time is before release time");
uint256 amount = token.balanceOf(address(this));
require(amount > 0, "TokenLock: no tokens to release");
token.transfer(beneficiary, amount);
emit Release(msg.sender, address(token), block.timestamp, amount);
}
}