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 数量。

加载中...
此文章数据所有权由区块链加密技术和智能合约保障仅归创作者所有。