syskey

syskey

twitter
github
discord server

淺析$smart合約,為什麼amount會為0?

$smart 是由 sushiswap 的 cto joseph.eth利用EIP-7702 提案在 BASE 網絡部署的 Token。

EIP=7702#

EIP-7702 的實現原理基於一個創新的機制,允許外部擁有賬戶(EOA)在單次交易期間臨時具有智能合約賬戶的功能,而不會對 EOA 進行永久性修改。其核心是通過添加一個 contract_code 字段,在交易過程中將該代碼分配給 EOA,使其擁有智能合約功能。當交易執行完成後,智能合約代碼自動被移除,EOA 恢復原始狀態。

如何正確領取 $smart#

簡單分析 $smart 合約#

```Solidity

function prepapreClaim() external {
    assembly {
        tstore(0, 1)
    }
}

function claim() external {
    assembly {
        if iszero(tload(0)) {
            mstore(0x00, 0x33adb0bc) // `RIPBozo()`.
            revert(0x1c, 0x04)
        }
    }
    if (hasClaimed[msg.sender]) {
        revert OnlyOneClaimPerWallet();
    }
    volatilityAccumulator = volatilityAccumulator + (block.timestamp - lastClaim) >> 1;
    uint256 amount = (DEFAULT_AMOUNT * volatilityAccumulator) / TARGET_INTERVAL;
    amount = (amount > DEFAULT_AMOUNT) ? DEFAULT_AMOUNT : amount;
    uint256 supply = totalSupply();

    // mintcap already hit
    if (supply == MINT_CAP) {
        revert MintCapExceeded();
    }
    // ensure we hit the mintcap
    else if (supply + amount > MINT_CAP) {
        amount = MINT_CAP - supply;
    }
    hasClaimed[msg.sender] = true;
    lastClaim = block.timestamp;
    _mint(msg.sender, amount);
}
```

$smart 合約主要的部分就 prepapreClaim 和 claim 函數,prepapreClaim 函數更改儲存插槽 0 的值為 1,claim 函數通過 tload (0) 讀取存插槽 0 的值,如果值為 0 則退出 claim 函數。結合 prepapreClaim 函數就可以得出要想成功 mint 出 Token,必須要在同一筆交易中先調用 prepapreClaim 函數將存插槽 0 的值改為 1,再調用 claim 函數領取 Token。而想要在同一筆交易中調用兩個函數,必須要使用合約錢包,因為EOA 錢包是無法在同一筆交易中執行兩個函數的。

為什麼使用合約錢包領取,amount 會為 0 呢?#

要回答這個問題需要先分析下 claim 函數中是如何計算 amount 的。在 claim 函數中計算 amount 的代碼主要由以下四行決定。

// 設置amount的釋放率 volatilityAccumulator全局變量 會動態更改volatilityAccumulator的值
volatilityAccumulator = volatilityAccumulator + (block.timestamp - lastClaim) >> 1;

// 釋放率 * 默認的amount數量
uint256 amount = (DEFAULT_AMOUNT * volatilityAccumulator) / TARGET_INTERVAL;

// 如果amount大於DEFAULT_AMOUNT則amount等於DEFAULT_AMOUNT,反之等於amount
amount = (amount > DEFAULT_AMOUNT) ? DEFAULT_AMOUNT : amount;
lastClaim = block.timestamp;

釋放率是計算 amount 的關鍵參數,根據代碼可以知道影響釋放率的參數主要由 volatilityAccumulator 和 block.timestamp 來決定的。合約部署時默認的 volatilityAccumulator 等於 600,那麼真正影響釋放率的只有 block.timestamp 時間了。

  • 正常情況的釋放率

    假設 lastClaim 與 block.timestamp 相差 1 秒,那麼計算 volatilityAccumulator 等於:

    volatilityAccumulator = 600 + 1 >> 1;
    volatilityAccumulator = 601 / 2;
    volatilityAccumulator = 300.5;
    
  • 極端情況的釋放率

    如果有用戶一直在調用 claim 函數 (block.timestamp 等於 lastClaim) 那麼 volatilityAccumulator 值在調用很多次之後會趨向等於 0,因為 volatilityAccumulator 是全局變量,並且每次調用 claim 函數都會動態的更改 volatilityAccumulator 的值。在這種情況下再計算 amount 數量將始終為 0,這也解釋了為什麼有些人領取的數量為 0 了。

    volatilityAccumulator = 0;
    uint256 amount = 0;
    amount = 0;
    

在 BASE 網絡部署的 $smart 實際是符合極端情況的,因為有 bot 一直在部署合約領取 smart,進而導致 volatilityAccumulator 的值基本為 0。要想獲得 Token 必須要 block.timestamp - lastClaim>=1 才行,而 base 網絡的出塊時間是 2 秒,這也就解釋了為什麼有人說同區塊只能領取一次。

在分析下在這種極端情況釋放率的情況下 block.timestamp - lastClaim>=1 時能獲得多少 Token:

volatilityAccumulator = 無線趨近於0,但不等於0;

// uint256 amount = (DEFAULT_AMOUNT * volatilityAccumulator) / TARGET_INTERVAL
// 計算amount的代碼,就可以近似於下面的代碼
// 默認的DEFAULT_AMOUNT 等於500000000000000000000000 wei,也就是500000 ether
// TARGET_INTERVAL 等於 600
// amount = 500000 / 600
// amount = 833.33333333
uint256 amount = DEFAULT_AMOUNT / TARGET_INTERVAL;

如何提高領取的成功率呢?#

經過上述分析,我們可以明確,只要確保自己的交易在每個區塊中被優先打包,就能 100% 領取到 833.33333333 Token。那么,如何才能保證自己的交易始終排在第一位呢?有人提到提高 GAS 價格,這一點我並不太確定。由於區塊鏈協議種類繁多,我對 Base 網絡的區塊打包規則並不熟悉,因此無法判斷單純提高 GAS 價格是否足以影響交易的排序。

尾聲#

非常感謝在此條推文留下回覆的推友們。

最後可以將以下代碼在 remix 中驗證下#

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;

contract ClaimHelper {
    uint256 lastClaim;
    uint256 volatilityAccumulator;

    uint256 public immutable DEFAULT_AMOUNT;
    uint256 public immutable TARGET_INTERVAL;
    uint256 public amount;

    constructor(
        uint256 _targetInterval,
        uint256 _defaultAmount
    ) {
        TARGET_INTERVAL = _targetInterval;
        DEFAULT_AMOUNT = _defaultAmount;
        lastClaim = block.timestamp;
        volatilityAccumulator = _targetInterval;
    }

    event amountUpdata(uint256 newValue);

    function claim() external {
        volatilityAccumulator = volatilityAccumulator + (block.timestamp - lastClaim) >> 1;
        amount = (DEFAULT_AMOUNT * volatilityAccumulator) / TARGET_INTERVAL;
        amount = (amount > DEFAULT_AMOUNT) ? DEFAULT_AMOUNT : amount;
        lastClaim = block.timestamp;
        emit amountUpdata(amount);
    }

    function getVolatilityAccumulator() external view returns (uint256) {
        return volatilityAccumulator;
    }
     function getAmount() external view returns (uint256) {
        return amount;
    }
}

_targetInterval 和_defaultAmount 設置為 600,連續多次運行 claim 函數後,再檢查下 amount 數量。

載入中......
此文章數據所有權由區塊鏈加密技術和智能合約保障僅歸創作者所有。