iT邦幫忙

2024 iThome 鐵人賽

DAY 29
0
自我挑戰組

Solidity 初學之路系列 第 29

DAY 29 - 線性釋放、代幣鎖

  • 分享至 

  • xImage
  •  

代幣歸屬條款

在傳統金融領域,有些公司會向員工和管理階層提供股權。但大量股權同時釋出會在短期產生拋售壓力,拖累股價。因此,公司通常會引入一個歸屬期來延遲承諾資產的所有權。同樣的,在區塊鏈領域,Web3 新創公司會分配給團隊代幣,同時也會將代幣低價賣給風險投資和私募。如果他們把這些低成本的代幣同時提到交易所變現,幣價將被砸穿,散戶直接成為接盤俠。
所以,專案方一般會約定代幣歸屬條款(token vesting),在歸屬期內逐步釋放代幣,減緩拋壓,並防止團隊和資本方過早躺平。

線性釋放

線性釋放指的是代幣在歸屬期內勻速釋放。舉個例子,某私募持有 365000 枚 ICU 代幣,歸屬期為 1 年(365天),那麼每天會釋放 1,000 枚代幣。
下面是一個鎖倉並線性釋放 ERC20 代幣的合約 TokenVesting:

  1. 專案方規定線性釋放的起始時間、歸屬期和受益人。
  2. 專案方將鎖倉的 ERC20 代幣轉帳給 TokenVesting 合約。
  3. 受益人可以呼叫 release 函數,從合約中取出釋放的代幣。
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 代幣的。

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:

  1. 開發者在部署合約時規定鎖倉的時間,受益人地址,以及代幣合約。
  2. 開發者將代幣轉入TokenLocker合約。
  3. 在鎖倉期滿時,受益人可以取走合約裡的代幣。
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);
    }
}

上一篇
DAY 28 - WETH、分帳
下一篇
DAY 30 - 時間鎖、代理合約
系列文
Solidity 初學之路30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言