增加 MAX EFFECTIVE BALANCE

EIP-7549旨在提高以太坊验证器MAX_EFFECTIVE_BALANCE至2048 ETH,保持最小质押32 ETH。此举允许大型节点整合验证器以降低网络开销,同时为小型质押者提供复合奖励。文章详细阐述了执行层和共识层的技术修改,包括合并请求机制和安全考量。

摘要

增加了常量 MAX_EFFECTIVE_BALANCE,同时保持最小质押余额为 32 ETH。这允许大型节点运营商整合为更少的验证者,同时也让独立质押者能够获得复利奖励并以更灵活的增量进行质押。

动机

截至2023年10月3日,目前有超过83万名验证者参与共识层。这个集合的规模持续增长,部分原因是 MAX_EFFECTIVE_BALANCE 将单个验证者的质押限制在 32 ETH。这导致了大量“冗余验证者”,它们由单一实体控制,可能运行在同一个信标节点上,但具有不同的BLS签名密钥。MAX_EFFECTIVE_BALANCE 的限制是原始分片设计遗留下来的技术债务,在该设计中,子委员会(不是证明委员会,而是 is_aggregator 中计算的委员会)需要多数诚实。因此,保持子委员会成员的权重大致相等降低了单个大型验证者拥有过多影响力的风险。在当前设计下,这些子委员会仅用于证明聚合,因此只有 $1/N$ 的诚实假设。

由于协议的安全模型不再依赖于较低的 MAX_EFFECTIVE_BALANCE 值,我们提议提高此值,同时保持 32 ETH 的最小验证者门槛。这次提高旨在减少验证者集合大小,从而减少网络上的P2P消息数量、每个epoch需要聚合的BLS签名数量以及 BeaconState 的内存占用。这一变更对小型和大型验证者都增加了价值。大型验证者可以整合,以运行更少的验证者,从而减少信标节点数量。小型验证者现在可以从复利奖励中受益,并且能够以更灵活的增量进行质押(例如,能够质押 40 ETH,而不是像今天这样需要累积 64 ETH 才能运行两个验证者)。

规范

常量

执行层
名称 注释
CONSOLIDATION_REQUEST_TYPE 0x02 EIP-7685 中用于合并请求的类型前缀
CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS 0x0000BBdDc7CE488642fb579F8B00f3a590007251 调用并存储合并请求机制相关详细信息的地址
SYSTEM_ADDRESS 0xfffffffffffffffffffffffffffffffffffffffe 用于在合约上调用系统操作的地址
EXCESS_CONSOLIDATION_REQUESTS_STORAGE_SLOT 0
CONSOLIDATION_REQUEST_COUNT_STORAGE_SLOT 1
CONSOLIDATION_REQUEST_QUEUE_HEAD_STORAGE_SLOT 2 指向合并请求消息队列头部的指针
CONSOLIDATION_REQUEST_QUEUE_TAIL_STORAGE_SLOT 3 指向合并请求消息队列尾部的指针
CONSOLIDATION_REQUEST_QUEUE_STORAGE_OFFSET 4 状态内合并请求消息队列的起始内存槽
MAX_CONSOLIDATION_REQUESTS_PER_BLOCK 2 每个区块可以出队的合并请求的最大数量
TARGET_CONSOLIDATION_REQUESTS_PER_BLOCK 1
MIN_CONSOLIDATION_REQUEST_FEE 1
CONSOLIDATION_REQUEST_FEE_UPDATE_FRACTION 17
EXCESS_INHIBITOR $2^{256}-1$ 在首次系统调用前用于计算费用的超额值
共识层
名称
COMPOUNDING_WITHDRAWAL_PREFIX Bytes1('0x02')
MIN_ACTIVATION_BALANCE Gwei($2^5 * 10^9$) (32 ETH)
MAX_EFFECTIVE_BALANCE_ELECTRA Gwei($2^{11} * 10^9$) (2048 ETH)

执行层

合并请求

新的合并请求是 EIP-7685 请求,类型为 0x02,包含以下字段:

  1. source_address: Bytes20
  2. source_pubkey: Bytes48
  3. target_pubkey: Bytes48

合并请求的 EIP-7685 编码如下。请注意,我们仅返回合约返回的编码请求值。

request_type = CONSOLIDATION_REQUEST_TYPE
request_data = dequeue_consolidation_requests()
合并请求合约

合约有三种不同的代码路径,可以概括为以下高级别内容:

  1. 添加合并请求——需要 96 字节输入,源验证者和目标验证者的公钥拼接。
  2. 费用获取器——如果输入长度为零,则返回添加合并请求所需的当前费用。
  3. 系统处理——如果由系统地址调用,则从队列中弹出当前区块的合并请求。
添加合并请求

如果对合约的调用数据输入正好是 96 字节,执行以下操作:

  1. 确保发送了足够的ETH来支付当前的合并请求费用(msg.value >= get_fee()
  2. 当前区块的合并请求计数增加 1increment_count()
  3. 将合并请求插入队列,包含源地址和源验证者与目标验证者的公钥(insert_consolidation_request_into_queue()

具体而言,其功能在伪代码中定义为函数 add_consolidation_request()

def add_consolidation_request(Bytes48: source_pubkey, Bytes48: target_pubkey):
    """
    添加合并请求会将新请求添加到合并请求队列,前提是提供了足够的费用。
    """

    # 验证提供了足够的费用。
    fee = get_fee()
    require(msg.value >= fee, 'Insufficient value for fee')

    # 增加合并请求计数。
    count = sload(CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS, CONSOLIDATION_REQUEST_COUNT_STORAGE_SLOT)
    sstore(CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS, CONSOLIDATION_REQUEST_COUNT_STORAGE_SLOT, count + 1)

    # 插入队列。
    queue_tail_index = sload(CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS, CONSOLIDATION_REQUEST_QUEUE_TAIL_STORAGE_SLOT)
    queue_storage_slot = CONSOLIDATION_REQUEST_QUEUE_STORAGE_OFFSET + queue_tail_index * 4
    sstore(CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS, queue_storage_slot, msg.sender)
    sstore(CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS, queue_storage_slot + 1, source_pubkey[0:32])
    sstore(CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS, queue_storage_slot + 2, source_pubkey[32:48] ++ target_pubkey[0:16])
    sstore(CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS, queue_storage_slot + 3, target_pubkey[16:48])
    sstore(CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS, CONSOLIDATION_REQUEST_QUEUE_TAIL_STORAGE_SLOT, queue_tail_index + 1)
费用计算

以下伪代码可以计算单个合并请求的成本,给定一定数量的超额合并请求。

def get_fee() -> int:
    excess = sload(CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS, EXCESS_CONSOLIDATION_REQUESTS_STORAGE_SLOT)
    require(excess != EXCESS_INHIBITOR, 'Inhibitor still active')
    return fake_exponential(
        MIN_CONSOLIDATION_REQUEST_FEE,
        excess,
        CONSOLIDATION_REQUEST_FEE_UPDATE_FRACTION
    )

def fake_exponential(factor: int, numerator: int, denominator: int) -> int:
    i = 1
    output = 0
    numerator_accum = factor * denominator
    while numerator_accum > 0:
        output += numerator_accum
        numerator_accum = (numerator_accum * numerator) // (denominator * i)
        i += 1
    return output // denominator
费用获取器

当合约的输入长度为零时,将其解释为获取当前费用的请求,即合约返回 get_fee() 的结果。

系统调用

在处理完任何激活此EIP的执行区块结束时(即在处理完所有交易并执行区块体合并请求验证之后),以 SYSTEM_ADDRESS 调用 CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS 处的合约,不带任何calldata。此调用会触发以下操作:

  • 合约的队列会根据出队的合并请求进行更新,如果队列已清空,合并请求队列的头部/尾部指针会被重置(dequeue_consolidation_requests()
  • 合约的超额合并请求会根据当前区块的使用情况进行更新(update_excess_consolidation_requests()
  • 合约的合并请求计数被重置为 0reset_consolidation_requests_count()

每个合并请求必须按照 dequeue_consolidation_requests() 返回的精确顺序出现在EIP-7685请求列表中。

此外,系统调用和该区块的处理必须符合以下规定:

  • 该调用具有 30_000_000 的专用 gas 限制。
  • 此调用消耗的 gas 不计入区块的总 gas 使用量。
  • 分配给该调用的 gas 限制和消耗的 gas 都排除在针对区块 gas 限制的任何检查之外。
  • 该调用不遵循 EIP-1559 费用销毁语义——不应作为此调用的一部分进行价值转移。
  • 如果在 CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS 处没有代码,则相应的区块必须被标记为无效。
  • 如果对合约的调用失败或返回错误,则区块必须被标记为无效。

由系统调用触发的功能在伪代码中定义为函数 process_consolidation_requests()

###################
## 公共函数
###################

def process_consolidation_requests():
    reqs = dequeue_consolidation_requests()
    update_excess_consolidation_requests()
    reset_consolidation_requests_count()
    return ssz.serialize(reqs)

###########
## 辅助函数
###########

class ConsolidationRequest(object):
    source_address: Bytes20
    source_pubkey: Bytes48
    target_pubkey: Bytes48

def dequeue_consolidation_requests():
    queue_head_index = sload(CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS, CONSOLIDATION_REQUEST_QUEUE_HEAD_STORAGE_SLOT)
    queue_tail_index = sload(CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS, CONSOLIDATION_REQUEST_QUEUE_TAIL_STORAGE_SLOT)
    num_in_queue = queue_tail_index - queue_head_index
    num_dequeued = min(num_in_queue, MAX_CONSOLIDATION_REQUESTS_PER_BLOCK)

    reqs = []
    for i in range(num_dequeued):
        queue_storage_slot = CONSOLIDATION_REQUEST_QUEUE_STORAGE_OFFSET + (queue_head_index + i) * 4
        source_address = address(sload(CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS, queue_storage_slot)[0:20])
        source_pubkey = (
            sload(CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS, queue_storage_slot + 1)[0:32] + sload(CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS, queue_storage_slot + 2)[0:16]
        )
        target_pubkey = (
            sload(CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS, queue_storage_slot + 2)[16:32] + sload(CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS, queue_storage_slot + 3)[0:32]
        )
        req = ConsolidationRequest(
            source_address=Bytes20(source_address),
            source_pubkey=Bytes48(source_pubkey),
            target_pubkey=Bytes48(target_pubkey)
        )
        reqs.append(req)

    new_queue_head_index = queue_head_index + num_dequeued
    if new_queue_head_index == queue_tail_index:
        # 队列为空,重置队列指针
        sstore(CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS, CONSOLIDATION_REQUEST_QUEUE_HEAD_STORAGE_SLOT, 0)
        sstore(CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS, CONSOLIDATION_REQUEST_QUEUE_TAIL_STORAGE_SLOT, 0)
    else:
        sstore(CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS, CONSOLIDATION_REQUEST_QUEUE_HEAD_STORAGE_SLOT, new_queue_head_index)

    return reqs

def update_excess_consolidation_requests():
    previous_excess = sload(CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS, EXCESS_CONSOLIDATION_REQUESTS_STORAGE_SLOT)
    # 检查是否需要在激活后的第一次迭代中将excess重置为0
    if previous_excess == EXCESS_INHIBITOR:
        previous_excess = 0

    count = sload(CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS, CONSOLIDATION_REQUEST_COUNT_STORAGE_SLOT)

    new_excess = 0
    if previous_excess + count > TARGET_CONSOLIDATION_REQUESTS_PER_BLOCK:
        new_excess = previous_excess + count - TARGET_CONSOLIDATION_REQUESTS_PER_BLOCK

    sstore(CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS, EXCESS_CONSOLIDATION_REQUESTS_STORAGE_SLOT, new_excess)

def reset_consolidation_requests_count():
    sstore(CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS, CONSOLIDATION_REQUEST_COUNT_STORAGE_SLOT, 0)
字节码
caller
push20 0xfffffffffffffffffffffffffffffffffffffffe
eq
push1 0xd3
jumpi

push1 0x11
push0
sload
dup1
push32 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
eq
push2 0x019a
jumpi

push1 0x01
dup3
mul
push1 0x01
swap1
push0

jumpdest
push0
dup3
gt
iszero
push1 0x68
jumpi

dup2
add
swap1
dup4
mul
dup5
dup4
mul
swap1
div
swap2
push1 0x01
add
swap2
swap1
push1 0x4d
jump

jumpdest
swap1
swap4
swap1
div
swap3
pop
pop
pop
calldatasize
push1 0x60
eq
push1 0x88
jumpi

calldatasize
push2 0x019a
jumpi

callvalue
push2 0x019a
jumpi

push0
mstore
push1 0x20
push0
return

jumpdest
callvalue
lt
push2 0x019a
jumpi

push1 0x01
sload
push1 0x01
add
push1 0x01
sstore
push1 0x03
sload
dup1
push1 0x04
mul
push1 0x04
add
caller
dup2
sstore
push1 0x01
add
push0
calldataload
dup2
sstore
push1 0x01
add
push1 0x20
calldataload
dup2
sstore
push1 0x01
add
push1 0x40
calldataload
swap1
sstore
caller
push1 0x60
shl
push0
mstore
push1 0x60
push0
push1 0x14
calldatacopy
push1 0x74
push0
log0
push1 0x01
add
push1 0x03
sstore
stop

jumpdest
push1 0x03
sload
push1 0x02
sload
dup1
dup3
sub
dup1
push1 0x02
gt
push1 0xe7
jumpi

pop
push1 0x02

jumpdest
push0

jumpdest
dup2
dup2
eq
push2 0x0129
jumpi

dup3
dup2
add
push1 0x04
mul
push1 0x04
add
dup2
push1 0x74
mul
dup2
sload
push1 0x60
shl
dup2
mstore
push1 0x14
add
dup2
push1 0x01
add
sload
dup2
mstore
push1 0x20
add
dup2
push1 0x02
add
sload
dup2
mstore
push1 0x20
add
swap1
push1 0x03
add
sload
swap1
mstore
push1 0x01
add
push1 0xe9
jump

jumpdest
swap2
add
dup1
swap3
eq
push2 0x013b
jumpi

swap1
push1 0x02
sstore
push2 0x0146
jump

jumpdest
swap1
pop
push0
push1 0x02
sstore
push0
push1 0x03
sstore

jumpdest
push0
sload
dup1
push32 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
eq
iszero
push2 0x0173
jumpi

pop
push0

jumpdest
push1 0x01
sload
push1 0x01
dup3
dup3
add
gt
push2 0x0188
jumpi

pop
pop
push0
push2 0x018e
jump

jumpdest
add
push1 0x01
swap1
sub

jumpdest
push0
sstore
push0
push1 0x01
sstore
push1 0x74
mul
push0
return

jumpdest
push0
push0
revert
部署

合并请求合约像其他智能合约一样部署。通过从所需的部署交易反向推导,生成了一个特殊的合成地址:

{
  "type": "0x0",
  "nonce": "0x0",
  "to": null,
  "gas": "0x3d090",
  "gasPrice": "0xe8d4a51000",
  "maxPriorityFeePerGas": null,
  "maxFeePerGas": null,
  "value": "0x0",
  "input": "0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff5f5561019e80602d5f395ff33373fffffffffffffffffffffffffffffffffffffffe1460d35760115f54807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1461019a57600182026001905f5b5f82111560685781019083028483029004916001019190604d565b9093900492505050366060146088573661019a573461019a575f5260205ff35b341061019a57600154600101600155600354806004026004013381556001015f358155600101602035815560010160403590553360601b5f5260605f60143760745fa0600101600355005b6003546002548082038060021160e7575060025b5f5b8181146101295782810160040260040181607402815460601b815260140181600101548152602001816002015481526020019060030154905260010160e9565b910180921461013b5790600255610146565b90505f6002555f6003555b5f54807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff141561017357505f5b6001546001828201116101885750505f61018e565b01600190035b5f555f6001556074025ff35b5f5ffd",
  "v": "0x1b",
  "r": "0x539",
  "s": "0xc0730f92dc275b663d377a7cbb141b6600052",
  "hash": "0x379269d571beff3ed1d8eba3abb24076a7267b0eaf0cc66d728fb0544f5a690d"
}
Sender: 0x13d1913d623E6a9D8811736359E50fCA
Address: 0x0000BBdDc7CE488642fb579F8B00f3a590007251

共识层

此EIP的定义性特征是:

  1. 增加 MAX_EFFECTIVE_BALANCE,同时创建 MIN_ACTIVATION_BALANCE。允许可变大小验证者的核心功能。
  2. 允许通过协议合并多个验证者索引。大型节点运营商无需通过退出和激活队列循环即可合并验证者的机制。
  3. 添加执行层部分提款(EIP-7002 的一部分)。允许执行层消息除了触发完全退出外,还能触发部分提款(例如,一个 100 ETH 的验证者可以移除最多 68 ETH 而无需退出验证者)。
  4. 使初始罚没惩罚可忽略不计。这降低了大型验证者合并的风险。

原理部分解释了每个提议核心功能背后的原因。下面是共识层修改的草图。

  1. 添加 COMPOUNDING_WITHDRAWAL_PREFIXMIN_ACTIVATION_BALANCE 常量,同时引入 MAX_EFFECTIVE_BALANCE 的更新值(MAX_EFFECTIVE_BALANCE_ELECTRA)。
  2. 创建 PendingDeposit 容器,用于在基于权重的速率限制机制中跟踪传入的存款。
  3. 使用存款和部分提款队列、存款所需字段以及退出队列基于权重的速率限制来更新 BeaconState
  4. 修改 is_eligible_for_activation_queue 以检查 MIN_ACTIVATION_BALANCE 而不是 MAX_EFFECTIVE_BALANCE
  5. 修改 get_validator_churn_limit 以依赖于验证者权重而不是验证者数量。
  6. 创建一个辅助函数 compute_exit_epoch_and_update_churn,根据当前待处理提款计算退出 epoch。
  7. 修改 initiate_validator_exit 以通过余额而不是验证者数量来限制退出队列的速率。
  8. 修改 initialize_beacon_state_from_eth1 以使用 MIN_ACTIVATION_BALANCE
  9. 修改 process_registry_updates 以激活所有符合条件的验证者。
  10. 添加一个每epoch辅助函数 process_pending_deposits,以消耗一些待处理存款。
  11. 修改 get_validator_from_deposit 以将有效余额初始化为零(它由待处理存款流程更新)。
  12. 修改 process_deposit 以将传入存款存储在 state.pending_deposits 中。
  13. 修改 compute_weak_subjectivity_period 以使用新的搅动限制函数。
  14. 添加 has_compounding_withdrawal_credential 以检查 0x02 凭证。
  15. 修改 is_fully_withdrawable_validator 以检查复利凭证。
  16. 添加 get_validator_excess_balance 以计算验证者的超额余额。
  17. 修改 is_partially_withdrawable_validator 以检查超额余额。
  18. 修改 get_expected_withdrawals 以使用超额余额并处理待处理的部分提款。
  19. 添加 process_consolidation 以启动协议内验证者合并,并将验证者切换到复利提款凭证。
  20. 将退出搅动的使用限制在 256 ETH(相当于 8 个有效余额为 32 ETH 的验证者),并将搅动的盈余推迟到处理合并。

完整的共识层规范可在 ./electra/beacon-chain.md 中找到

原理

此EIP旨在减少验证者总数,同时不改变协议的经济安全性。它为控制大量质押的大型节点运营商提供了一种机制,使其能够整合为更少的验证者。我们分析了每个核心功能背后的原因。

增加 MAX_EFFECTIVE_BALANCE,同时创建 MIN_ACTIVATION_BALANCE

在增加 MAX_EFFECTIVE_BALANCE 以允许更大质押的验证者的同时,保持 32 ETH 的下限(通过引入新的常量 – MIN_ACTIVATION_BALANCE)以鼓励独立质押是重要的。

允许通过协议合并多个验证者索引

对于已经控制数千个验证者的大型质押池,退出和重新进入会极其缓慢且成本高昂。通过允许协议内合并,EIP的采用率将大大提高。

添加执行层部分提款(EIP-7002 的一部分)

对于选择提高其有效余额上限的验证者,允许从执行层触发自定义部分提款增加了质押配置的灵活性。验证者可以选择何时以及提取多少,但必须支付EL交易的 gas。

使初始罚没惩罚可忽略不计

为了鼓励合并,我们可以修改罚没惩罚。最大的打击来自验证者有效余额的初始罚款 $1/32$。由于这与有效余额线性相关,高质押的验证者直接承担更高的风险。通过改变其缩放特性,我们可以使合并更具吸引力。

合并合约参数值

合并智能合约使用费用机制来限制每个区块的请求数量。费用机制的详细信息可在 EIP-7002 中找到。

选择 TARGET_CONSOLIDATION_REQUESTS_PER_BLOCK1 是为了尽可能限制合并请求的速率。 选择 MAX_CONSOLIDATION_REQUESTS_PER_BLOCK2 是为了允许在同一区块内切换验证者到复利凭证并请求合并。

每个区块一个合并请求仍高于合并搅动的大小,这可能导致合并队列的无限制增长。 因此,合并队列的大小有一个硬性限制,等于 262,144 个请求,即信标状态中 4 MB 的数据。

向后兼容性

此EIP引入了与区块验证规则集不向后兼容的更改,并且必须伴随硬分叉。这些更改不会破坏与当前用户活动和体验相关的任何内容。

安全注意事项

此更改修改了委员会和搅动,但不会显著影响安全属性。

证明委员会的安全性

即使在最坏情况下的完全合并下,委员会被恶意接管的概率仍然很低。即使在高合并场景下,所需的诚实验证者份额仍远低于终结性所需的 $2/3$ 多数。

聚合器选择

在最初的分片路线图中,子委员会需要以极高概率确保安全。现在,由于唯一的职责是证明聚合,我们只要求每个委员会至少有一个诚实的聚合器。目前,聚合器通过VRF抽奖选择,目标是几个验证者单元,这些单元可能受到未合并攻击者的偏向。本提案将VRF抽奖改为考虑权重,因此至少有一个诚实聚合器的概率不会变差。

提议者选择概率

提议者选择已经通过其有效余额与 MAX_EFFECTIVE_BALANCE 的比率进行加权。由于概率较低,此更改将略微增加计算下一个提议者索引所需的时间。

同步委员会选择概率

同步委员会选择也已按有效余额加权,因此本提案不需要修改同步协议。轻客户端仍然可以检查绝大多数参与者已签署更新,无论其权重如何,因为我们保持了基于权重的选择概率。

搅动不变量

本提案维护了激活和退出搅动不变量,限制的是活跃权重而非验证者数量。余额充值现在得到明确处理,并遵循与完整存款相同的激活队列。

费用超额支付

对系统合约的调用需要支付由当前合约状态定义的费用。超额支付的费用不会退还给调用者。通常无法提前计算出所需的精确费用金额。当从合约添加合并请求时,合约可以执行读取操作以检查当前费用,然后精确支付所需金额。以下是Solidity中的一个示例:

function addConsolidation(bytes memory srcPubkey, bytes memory targetPubkey, uint64 requestFeeLimit) private {
    assert(srcPubkey.length == 48);
    assert(targetPubkey.length == 48);

    // 从合约读取当前费用。
    (bool readOK, bytes memory feeData) = ConsolidationsContract.staticcall('');
    if (!readOK) {
        revert('reading fee failed');
    }
    uint256 fee = uint256(bytes32(feeData));

    // 检查费用是否过高。
    if (fee > requestFeeLimit) {
        revert('fee is too high');
    }

    // 添加请求。
    bytes memory callData = bytes.concat(srcPubkey, targetPubkey);
    (bool writeOK,) = ConsolidationsContract.call{value: fee}(callData);
    if (!writeOK) {
        revert('adding request failed');
    }
}

注意:系统合约使用EVM CALLER 操作(Solidity: msg.sender)来获取合并请求中使用的地址,即调用系统合约的地址必须与信标状态中记录的 0x01 提款凭证匹配。

注意:如果费用过高,上述代码会回滚,费用在合并请求交易创建和包含到区块之间可能会发生显著变化,因此,此检查对于避免超额支付非常重要

使用EOA请求合并将始终导致费用超额支付。EOA无法使用包装合约来请求合并。即使存在某种方式,退还超额部分的 gas 成本也可能高于超额部分本身。如果希望从EOA向系统合约请求合并,我们建议用户执行交易模拟以估算要发送的合理费用金额。

合并队列硬性限制

超过合并队列硬性限制(262,144 个请求)的合并将被共识层丢弃,需要重新提交,请注意在这种情况下费用不予退还。

系统调用失败

尽管对 CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS 的系统调用失败的可能性极低,但在这种情况下的行为是明确定义的:区块被标记为无效。此考量直接遵循 EIP-7002

空代码失败

如果 CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS 处没有代码(即,如果链没有“就绪”),则不应激活此EIP。这与 EIP-7002 中的考量类似。

  • 原文链接: github.com/nerolation/EI...
  • 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
Nerolation
Nerolation
江湖只有他的大名,没有他的介绍。