切換語言為:簡體

區塊鏈中的時間鎖(Timelock)和時間鎖合約

  • 爱糖宝
  • 2024-07-15
  • 2058
  • 0
  • 0

今天,我們介紹時間鎖和時間鎖合約。程式碼由Compound的Timelock合約簡化而來。

時間鎖

時間鎖(Timelock)是銀行金庫和其他高安全性容器中常見的鎖定機制。它是一種計時器,旨在防止保險箱或保險庫在預設時間之前被開啟,即便開鎖的人知道正確密碼。

在區塊鏈,時間鎖被DeFiDAO大量採用。它是一段程式碼,他可以將智慧合約的某些功能鎖定一段時間。它可以大大改善智慧合約的安全性,舉個例子,假如一個駭客黑了Uniswap的多籤,準備提走金庫的錢,但金庫合約加了2天鎖定期的時間鎖,那麼駭客從建立提錢的交易,到實際把錢提走,需要2天的等待期。在這一段時間,專案方可以找應對辦法,投資者可以提前拋售代幣減少損失。

時間鎖合約

下面,我們介紹一下時間鎖Timelock合約。它的邏輯並不複雜:

  • 在建立Timelock合約時,專案方可以設定鎖定期,並把合約的管理員設為自己。

  • 時間鎖主要有三個功能:

    • 建立交易,並加入到時間鎖佇列。

    • 在交易的鎖定期滿後,執行交易。

    • 後悔了,取消時間鎖佇列中的某些交易。

  • 專案方一般會把時間鎖合約設為重要合約的管理員,例如金庫合約,再透過時間鎖操作他們。

  • 時間鎖合約的管理員一般為專案的多籤錢包,保證去中心化。

事件

Timelock合約中共有4個事件。

  • QueueTransaction:交易建立並進入時間鎖佇列的事件。

  • ExecuteTransaction:鎖定期滿後交易執行的事件。

  • CancelTransaction:交易取消事件。

  • NewAdmin:修改管理員地址的事件。

    // 事件
    // 交易取消事件
    event CancelTransaction(bytes32 indexed txHash, address indexed target, uint value, string signature,  bytes data, uint executeTime);
    // 交易執行事件
    event ExecuteTransaction(bytes32 indexed txHash, address indexed target, uint value, string signature,  bytes data, uint executeTime);
    // 交易建立並進入佇列 事件
    event QueueTransaction(bytes32 indexed txHash, address indexed target, uint value, string signature, bytes data, uint executeTime);
    // 修改管理員地址的事件
    event NewAdmin(address indexed newAdmin);

狀態變數

Timelock合約中共有4個狀態變數。

  • admin:管理員地址。

  • delay:鎖定期。

  • GRACE_PERIOD:交易過期時間。如果交易到了執行的時間點,但在GRACE_PERIOD沒有被執行,就會過期。

  • queuedTransactions:進入時間鎖佇列交易的識別符號txHashbool的對映,記錄所有在時間鎖佇列中的交易。

    // 狀態變數
    address public admin; // 管理員地址
    uint public constant GRACE_PERIOD = 7 days; // 交易有效期,過期的交易作廢
    uint public delay; // 交易鎖定時間 (秒)
    mapping (bytes32 => bool) public queuedTransactions; // txHash到bool,記錄所有在時間鎖佇列中的交易

修飾器

Timelock合約中共有2modifier

  • onlyOwner():被修飾的函式只能被管理員執行。

  • onlyTimelock():被修飾的函式只能被時間鎖合約執行。

    // onlyOwner modifier
    modifier onlyOwner() {
        require(msg.sender == admin, "Timelock: Caller not admin");
        _;
    }

    // onlyTimelock modifier
    modifier onlyTimelock() {
        require(msg.sender == address(this), "Timelock: Caller not Timelock");
        _;
    }

函式

Timelock合約中共有7個函式。

  • 建構函式:初始化交易鎖定時間(秒)和管理員地址。

  • queueTransaction():建立交易並新增到時間鎖佇列中。引數比較複雜,因為要描述一個完整的交易:

    • target:目標合約地址

    • value:傳送ETH數額

    • signature:呼叫的函式簽名(function signature)

    • data:交易的call data

    • executeTime:交易執行的區塊鏈時間戳。

      呼叫這個函式時,要保證交易預計執行時間executeTime大於當前區塊鏈時間戳+鎖定時間delay。交易的唯一識別符號為所有引數的雜湊值,利用getTxHash()函式計算。進入佇列的交易會更新在queuedTransactions變數中,並釋放QueueTransaction事件。

  • executeTransaction():執行交易。它的引數與queueTransaction()相同。要求被執行的交易在時間鎖佇列中,達到交易的執行時間,且沒有過期。執行交易時用到了solidity的低階成員函式call

  • cancelTransaction():取消交易。它的引數與queueTransaction()相同。它要求被取消的交易在佇列中,會更新queuedTransactions並釋放CancelTransaction事件。

  • changeAdmin():修改管理員地址,只能被Timelock合約呼叫。

  • getBlockTimestamp():獲取當前區塊鏈時間戳。

  • getTxHash():返回交易的識別符號,為很多交易引數的hash

    /**
     * @dev 建構函式,初始化交易鎖定時間 (秒)和管理員地址
     */
    constructor(uint delay_) {
        delay = delay_;
        admin = msg.sender;
    }

    /**
     * @dev 改變管理員地址,呼叫者必須是Timelock合約。
     */
    function changeAdmin(address newAdmin) public onlyTimelock {
        admin = newAdmin;

        emit NewAdmin(newAdmin);
    }

    /**
     * @dev 建立交易並新增到時間鎖佇列中。
     * @param target: 目標合約地址
     * @param value: 傳送eth數額
     * @param signature: 要呼叫的函式簽名(function signature)
     * @param data: call data,裡面是一些引數
     * @param executeTime: 交易執行的區塊鏈時間戳
     *
     * 要求:executeTime 大於 當前區塊鏈時間戳+delay
     */
    function queueTransaction(address target, uint256 value, string memory signature, bytes memory data, uint256 executeTime) public onlyOwner returns (bytes32) {
        // 檢查:交易執行時間滿足鎖定時間
        require(executeTime >= getBlockTimestamp() + delay, "Timelock::queueTransaction: Estimated execution block must satisfy delay.");
        // 計算交易的唯一識別符:一堆東西的hash
        bytes32 txHash = getTxHash(target, value, signature, data, executeTime);
        // 將交易新增到佇列
        queuedTransactions[txHash] = true;

        emit QueueTransaction(txHash, target, value, signature, data, executeTime);
        return txHash;
    }

    /**
     * @dev 取消特定交易。
     *
     * 要求:交易在時間鎖佇列中
     */
    function cancelTransaction(address target, uint256 value, string memory signature, bytes memory data, uint256 executeTime) public onlyOwner{
        // 計算交易的唯一識別符:一堆東西的hash
        bytes32 txHash = getTxHash(target, value, signature, data, executeTime);
        // 檢查:交易在時間鎖佇列中
        require(queuedTransactions[txHash], "Timelock::cancelTransaction: Transaction hasn't been queued.");
        // 將交易移出佇列
        queuedTransactions[txHash] = false;

        emit CancelTransaction(txHash, target, value, signature, data, executeTime);
    }

    /**
     * @dev 執行特定交易。
     *
     * 要求:
     * 1. 交易在時間鎖佇列中
     * 2. 達到交易的執行時間
     * 3. 交易沒過期
     */
    function executeTransaction(address target, uint256 value, string memory signature, bytes memory data, uint256 executeTime) public payable onlyOwner returns (bytes memory) {
        bytes32 txHash = getTxHash(target, value, signature, data, executeTime);
        // 檢查:交易是否在時間鎖佇列中
        require(queuedTransactions[txHash], "Timelock::executeTransaction: Transaction hasn't been queued.");
        // 檢查:達到交易的執行時間
        require(getBlockTimestamp() >= executeTime, "Timelock::executeTransaction: Transaction hasn't surpassed time lock.");
        // 檢查:交易沒過期
       require(getBlockTimestamp() <= executeTime + GRACE_PERIOD, "Timelock::executeTransaction: Transaction is stale.");
        // 將交易移出佇列
        queuedTransactions[txHash] = false;

        // 獲取call data
        bytes memory callData;
        if (bytes(signature).length == 0) {
            callData = data;
        } else {
            callData = abi.encodePacked(bytes4(keccak256(bytes(signature))), data);
        }
        // 利用call執行交易
        (bool success, bytes memory returnData) = target.call{value: value}(callData);
        require(success, "Timelock::executeTransaction: Transaction execution reverted.");

        emit ExecuteTransaction(txHash, target, value, signature, data, executeTime);

        return returnData;
    }

    /**
     * @dev 獲取當前區塊鏈時間戳
     */
    function getBlockTimestamp() public view returns (uint) {
        return block.timestamp;
    }

    /**
     * @dev 將一堆東西拼成交易的識別符號
     */
    function getTxHash(
        address target,
        uint value,
        string memory signature,
        bytes memory data,
        uint executeTime
    ) public pure returns (bytes32) {
        return keccak256(abi.encode(target, value, signature, data, executeTime));
    }

總結

時間鎖可以將智慧合約的某些功能鎖定一段時間,大大減少專案方rug pull和駭客攻擊的機會,增加去中心化應用的安全性。它被DeFiDAO大量採用,其中包括UniswapCompound

0則評論

您的電子郵件等資訊不會被公開,以下所有項目均必填

OK! You can skip this field.