Skip to content

Commit

Permalink
update 43 token vesting
Browse files Browse the repository at this point in the history
  • Loading branch information
AmazingAng committed Aug 7, 2022
1 parent a2171d5 commit 6d1615a
Show file tree
Hide file tree
Showing 3 changed files with 209 additions and 0 deletions.
69 changes: 69 additions & 0 deletions 43_TokenVesting/TokenVesting.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
// SPDX-License-Identifier: MIT
// wtf.academy
pragma solidity ^0.8.0;

import "../31_ERC20/ERC20.sol";

/**
* @title ERC20代币线性释放
* @dev 这个合约会将ERC20代币线性释放给给受益人`_beneficiary`。
* 释放的代币可以是一种,也可以是多种。释放周期由起始时间`_start`和时长`_duration`定义。
* 所有转到这个合约上的代币都会遵循同样的线性释放周期,并且需要受益人调用`release()`函数提取。
* 合约是从openzepplin的VestingWallet简化而来。
*/
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 初始化受益人地址,释放周期(秒), 起始时间戳(当前区块链时间戳)
*/
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;
}
}
}
Binary file added 43_TokenVesting/img/43-1.jpeg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
140 changes: 140 additions & 0 deletions 43_TokenVesting/readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
---
title: 43. 线性释放
tags:
- solidity
- application
- ERC20

---

# Solidity极简入门: 43. 线性释放

我最近在重新学solidity,巩固一下细节,也写一个“Solidity极简入门”,供小白们使用(编程大佬可以另找教程),每周更新1-3讲。

推特:[@0xAA_Science](https://twitter.com/0xAA_Science)

社区:[Discord](https://discord.wtf.academy)[微信群](https://wechat.wtf.academy)[官网 wtf.academy](https://wtf.academy)

所有代码和教程开源在github: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTFSolidity)

-----

这一讲,我们将介绍代币归属条款,并写一个线性释放`ERC20`代币的合约。代码由`OpenZepplin``VestingWallet`合约简化而来。

## 代币归属条款

![部署](./img/43-1.jpeg)

在传统金融领域,一些公司会向员工和管理层提供股权。但大量股权同时释放会在短期产生抛售压力,拖累股价。因此,公司通常会引入一个归属期来延迟承诺资产的所有权。同样的,在区块链领域,`Web3`初创公司会给团队分配代币,同时也会将代币低价出售给风投和私募。如果他们把这些低成本的代币提到交易所变现,币价将被砸穿,散户直接成为接盘侠。

通常,项目方会约定代币归属条款(token vesting),在给定时间范围(归属期)内逐步释放代币,减缓抛压,并防止团队和资本过早躺平。

## 线性释放

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

下面,我们就写一个锁仓并线性释放`ERC20`代币的合约`TokenVesting`。它的逻辑很简单:

- 项目方规定线性释放的起始时间、归属期和受益人。
- 项目方将锁仓的`ERC20`转账给`TokenVesting`合约。
- 受益人可以调用`release`函数,从合约中取出释放的代币。

### 事件
线性释放合约中共有`1`个事件。
- `ERC20Released`:提币事件,当受益人提取释放代币时释放。

```solidity
contract TokenVesting {
// 事件
event ERC20Released(address indexed token, uint256 amount); // 提币事件
```

### 状态变量
线性释放合约中共有`4`个状态变量。
- `beneficiary`:受益人地址。
- `start`:归属期起始时间戳。
- `duration`:归属期,单位为秒。
- `erc20Released`:代币地址->释放数量的映射,记录受益人已领取的代币数量。

```solidity
// 状态变量
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`

```solidity
/**
* @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;
}
}
```

## `Remix`演示

### 1. 部署[第31讲](../31_ERC20/readme.md)中的`ERC20`合约,并给自己铸造`10000`枚代币。

### 2. 部署`TokenVesting`线性释放合约,输入设为自己,归属期设为`100`秒。

### 3. 将`10000``ERC20`代币转给线性释放合约。

### 4. 调用`release()`函数提取代币。

## 总结

代币短期大量解锁会对币价造成巨大压力,而约定代币归属条款可以缓解抛压,并防止团队和资本过早躺平。这一讲,我们介绍了代币归属条款,并写了一个线性释放`ERC20`代币的合约。





0 comments on commit 6d1615a

Please sign in to comment.