🍣 sushiware.xyz 🍣

ロックアップしたトークンを線形にリリースするコントラクトを作る

2022-06-02

ERC20 ベースで作成したトークンを投資家や運営に対してロックアップし、時間推移と共に線形にリリースしていくコントラクトを作りたい。

TokenTimeLock.sol

TokenTimelock - OpenZeppelin Docs

まず、ただ、一定時間ロックアップするコントラクトを参考にする

function release() public virtual {
    require(block.timestamp >= releaseTime(), "TokenTimelock: current time is before release time");

    uint256 amount = token().balanceOf(address(this));
    require(amount > 0, "TokenTimelock: no tokens to release");

    token().safeTransfer(beneficiary(), amount);
}

このコントラクトはbeneficiaryは単一、releaseTimeはトークンをロックアップする時間を表す。

これを拡張していく。

線形にリリースしていく方法

下記の変数を用意する

  • beneficiary
    • 投資家や運営
  • total
    • beneficiaryが最終的に受け取るトークンの量
    • beneficiaryごとに設定する
  • releaseStartTime
    • トークンのリリースを開始する時間
    • トークンのreleaseFinishTimeまで毎秒、定額でトークンをリリースしていく。
  • releaseFinishTime
    • トークンのリリースが終了する時間

beneficiaryblock.timestamp時刻に下記の量のトークンを受けとれる

uint256 vested = amount * (block.timestamp - releaseStartTime) / (releaseFinishTime - releaseStartTime);
T=totalT = total
t=block.timestampt = block.timestamp
rst=releaseStartTimerst = releaseStartTime
rft=releaseFinishTimerft = releaseFinishTime
V(t)=TtfstrftrstV(t) = T\frac{t - fst}{rft - rst}

これを使って、block.timestampが進むたびに(1 秒ごとに)トークンをリリースできる。

すでにリリースされた分は除外したいので、

uint256 vested = total * (block.timestamp - releaseStartTime) / (releaseFinishTime - releaseStartTime);

uint256 transferable = vested - alreadyTransfered;

uint256なので、先にtotal * (block.timestamp - releaseStartTime)を計算しておく。

TokneLinerTimeLock.sol

下記が全コード。 DYOR。

finaliseReleaseTimereleaseStartTimereleaseFinishTimeを設定する。

depositTokenbeneficiaryamountをロックアップする。

finaliseDepositでデポジットを確定させる。

releaseメソッドで、beneficiaryへトークンをリリースする。

// SPDX-License-Identifier: MIT
pragma solidity 0.8.13;

import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "forge-std/console.sol";

contract TokenLinearTimelock is Ownable {
    using SafeERC20 for IERC20;

    address public token;

    bool public isDepositFinalised;

    bool public isReleaseTimeFinalised;
    uint256 public releaseStartTime;
    uint256 public releaseFinishTime;

    mapping(address => uint256) public alreadyTransfered;
    mapping(address => uint256) public balances;

    event TokensDeposited(address from, uint256 amount);
    event AllocationPerformed(address beneficiary, uint256 amount);
    event TokensUnlocked(address beneficiary, uint256 amount);

    constructor(address _token) {
        require(_token != address(0), "token address can not be zero");
        token = _token;
    }

    modifier notDepositFinalised() {
        require(!isDepositFinalised, "deposit have been finalised");
        _;
    }

    modifier notReleaseTimeFinalised() {
        require(releaseStartTime == 0, "Release time has already been set");
        _;
    }

    modifier releaseTimeFinalised() {
        require(releaseStartTime != 0, "Release time has not been set yet");
        _;
    }

    function finaliseDeposit()
        public
        onlyOwner
        releaseTimeFinalised
        notDepositFinalised
    {
        isDepositFinalised = true;
    }

    function finaliseReleaseTime(
        uint256 _periodUntilReleaseStartTime,
        uint256 _periodUntilReleaseFinishTime
    ) public onlyOwner notReleaseTimeFinalised {
        require(
            _periodUntilReleaseStartTime > 0,
            "The period until release start time must be greater than zero"
        );

        require(
            _periodUntilReleaseFinishTime > 0,
            "The period until release finish time must be greater than zero"
        );

        require(
            _periodUntilReleaseFinishTime > _periodUntilReleaseStartTime,
            "The period until release finish time must be greater than the period until release start time"
        );

        releaseStartTime = block.timestamp + _periodUntilReleaseStartTime;
        releaseFinishTime = block.timestamp + _periodUntilReleaseFinishTime;
    }

    function depositToken(address beneficiary, uint256 amount)
        public
        onlyOwner
        releaseTimeFinalised
        notDepositFinalised
    {
        _depositToken(beneficiary, amount);
    }

    function bulkDepositTokens(
        address[] calldata beneficiaries,
        uint256[] calldata amounts
    ) public onlyOwner releaseTimeFinalised notDepositFinalised {
        require(
            beneficiaries.length == amounts.length,
            "The recipients and amounts arrays must be the same size in length"
        );

        for (uint256 i = 0; i < beneficiaries.length; i++) {
            _depositToken(beneficiaries[i], amounts[i]);
        }
    }

    function _depositToken(address beneficiary, uint256 amount) private {
        require(
            amount >= (releaseFinishTime - releaseStartTime),
            "Amount deposited must be greater than release period"
        );

        require(
            beneficiary != address(0),
            "Beneficiary address can not be zero"
        );

        balances[beneficiary] += amount;

        IERC20(token).safeTransferFrom(msg.sender, address(this), amount);

        emit AllocationPerformed(beneficiary, amount);
    }

    function release(uint256 amount) public releaseTimeFinalised {
        require(
            balances[msg.sender] >= amount,
            "Insufficient token balance, try lesser amount"
        );

        require(
            block.timestamp > releaseStartTime,
            "Tokens are only available after correct time period has elapsed"
        );

        uint256 available = transferableAmount(msg.sender);

        require(
            available >= amount,
            "Token amount not available for unlock right now, please try lesser amount"
        );

        alreadyTransfered[msg.sender] += amount;
        balances[msg.sender] -= amount;

        IERC20(token).safeTransfer(msg.sender, amount);

        emit TokensUnlocked(msg.sender, amount);
    }

    function transferableAmount(address beneficiary)
        public
        view
        releaseTimeFinalised
        returns (uint256)
    {
        if (block.timestamp > releaseFinishTime) {
            return balances[beneficiary];
        }

        if (releaseStartTime > block.timestamp) {
            return 0;
        }

        uint256 total = alreadyTransfered[beneficiary] + balances[beneficiary];

        uint256 vested = (total * (block.timestamp - releaseStartTime)) /
            (releaseFinishTime - releaseStartTime);

        return vested - alreadyTransfered[beneficiary];
    }
}

いいね KaTeX\KaTeX

全然書き方わからんね KaTeX\KaTeX