该提案引入了一种新机制,允许验证者使用其执行层 (0x01) 提款凭证触发提款和退出操作。当前规范只允许活跃密钥启动退出,导致提款凭证持有者(最终资金所有者)在非标准托管关系中无法独立退出。为解决此信任问题并增强安全性,该机制将退出消息附加到执行层区块,并通过共识层处理。文章详细阐述了合约配置、执行层操作、费用机制和共识层交互。
markdown
增加了一种新机制,允许验证者从其执行层 (0x01) 提款凭证触发提款和退出。
这些新的执行层退出消息被附加到执行层区块中,然后由共识层处理。
验证者拥有两个密钥——一个活跃密钥和一个提款凭证。活跃密钥采用 BLS 密钥的形式,而提款凭证可以是 BLS 密钥 (0x00) 或执行层地址 (0x01)。活跃密钥是“热的”,积极地签名和执行验证者职责,而提款凭证可以保持“冷的”,仅执行与提款和质押 ETH 所有权相关的有限操作。由于这种安全关系,提款凭证最终是拥有质押 ETH 和任何奖励的密钥。
按照目前的规范,只有活跃密钥才能启动验证者退出。这意味着在任何非标准托管关系中(即活跃密钥与提款凭证是不同的实体),资金的最终所有者——提款凭证的持有者——不能独立选择退出并启动提款过程。这导致了信任问题(例如,ETH 可能被活跃密钥所有者“扣押”)或不足的变通方法,如预签名退出。此外,如果活跃密钥丢失,用户仍然应该能够通过使用其冷提款凭证来恢复资金。
为确保提款凭证(由 EOA 和智能合约拥有)可以无需信任地控制质押 ETH 的命运,本规范启用了由 0x01 提款凭证触发的退出。
请注意,0x00 提款凭证可以通过一次性签名消息更改为 0x01 提款凭证。因此,为 0x01 凭证启用的任何功能实际上也为 0x00 凭证启用了。
| 名称 | 值 | 注释 |
|---|---|---|
WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS |
0x00000961Ef480Eb55e80D19ad83579A64c007002 |
调用并存储有关退出/部分提款机制相关细节的地址 |
WITHDRAWAL_REQUEST_TYPE |
0x01 |
EIP-7685 提款请求的类型前缀 |
SYSTEM_ADDRESS |
0xfffffffffffffffffffffffffffffffffffffffe |
用于在合约上调用系统操作的地址 |
EXCESS_WITHDRAWAL_REQUESTS_STORAGE_SLOT |
0 | |
WITHDRAWAL_REQUEST_COUNT_STORAGE_SLOT |
1 | |
WITHDRAWAL_REQUEST_QUEUE_HEAD_STORAGE_SLOT |
2 | 指向提款请求消息队列头部的指针 |
WITHDRAWAL_REQUEST_QUEUE_TAIL_STORAGE_SLOT |
3 | 指向提款请求消息队列尾部的指针 |
WITHDRAWAL_REQUEST_QUEUE_STORAGE_OFFSET |
4 | 状态中提款请求消息队列的起始内存槽位 |
MAX_WITHDRAWAL_REQUESTS_PER_BLOCK |
16 | 每个区块可以出队的提款请求的最大数量 |
TARGET_WITHDRAWAL_REQUESTS_PER_BLOCK |
2 | |
MIN_WITHDRAWAL_REQUEST_FEE |
1 | |
WITHDRAWAL_REQUEST_FEE_UPDATE_FRACTION |
17 | |
EXCESS_INHIBITOR |
2**256-1 |
在首次系统调用之前用于计算费用的过量值 |
FORK_BLOCK —— 在此 EIP 激活后的区块链中的第一个区块。新的提款请求操作是一个类型为 0x01 的 EIP-7685 请求,包含以下字段:
source_address: Bytes20validator_pubkey: Bytes48amount: uint64提款请求的 EIP-7685 编码计算如下。请注意,amount 由合约以小端序返回,并且必须以此方式编码。
request_type = WITHDRAWAL_REQUEST_TYPE
request_data = read_withdrawal_requests()
该合约有三种不同的代码路径,可以概括为以下几点:
56 字节的输入,验证者的公钥与大端序 uint64 金额值连接。如果对合约的调用数据输入恰好是 56 字节,则执行以下操作:
check_fee())increment_count())insert_withdrawal_request_into_queue())具体来说,该功能在伪代码中定义为函数 add_withdrawal_request():
def add_withdrawal_request(Bytes48: validator_pubkey, uint64: amount):
"""
Add withdrawal request adds new request to the withdrawal request queue, so long as a sufficient fee is provided.
"""
# Verify sufficient fee was provided.
fee = get_fee()
require(msg.value >= fee, 'Insufficient value for fee')
# Increment withdrawal request count.
count = sload(WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS, WITHDRAWAL_REQUEST_COUNT_STORAGE_SLOT)
sstore(WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS, WITHDRAWAL_REQUEST_COUNT_STORAGE_SLOT, count + 1)
# Insert into queue.
queue_tail_index = sload(WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS, WITHDRAWAL_REQUEST_QUEUE_TAIL_STORAGE_SLOT)
queue_storage_slot = WITHDRAWAL_REQUEST_QUEUE_STORAGE_OFFSET + queue_tail_index * 3
sstore(WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS, queue_storage_slot, msg.sender)
sstore(WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS, queue_storage_slot + 1, validator_pubkey[0:32])
sstore(WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS, queue_storage_slot + 2, validator_pubkey[32:48] ++ uint64_to_little_endian(amount))
sstore(WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS, WITHDRAWAL_REQUEST_QUEUE_TAIL_STORAGE_SLOT, queue_tail_index + 1)
####### 费用计算
以下伪代码可以在给定一定数量的超额提款请求的情况下,计算单个提款请求的成本。
def get_fee() -> int:
excess = sload(WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS, EXCESS_WITHDRAWAL_REQUESTS_STORAGE_SLOT)
require(excess != EXCESS_INHIBITOR, 'Inhibitor still active')
return fake_exponential(
MIN_WITHDRAWAL_REQUEST_FEE,
excess,
WITHDRAWAL_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() 的结果。
在从 FORK_BLOCK 开始处理任何执行区块的末尾(即在处理所有交易并执行区块体提款请求验证之后),以 SYSTEM_ADDRESS 身份调用 WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS,不带调用数据。此调用触发以下操作:
dequeue_withdrawal_requests())update_excess_withdrawal_requests())reset_withdrawal_requests_count())每个提款请求必须按照 dequeue_withdrawal_requests() 返回的精确顺序出现在 EIP-7685 请求列表中。
此外,系统调用和该区块的处理必须符合以下规定:
30_000_000 的专用 Gas 限制。WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS 处没有代码,则相应的区块必须标记为无效。由系统调用触发的功能在伪代码中定义为函数 read_withdrawal_requests():
###################
## Public function
###################
def read_withdrawal_requests():
reqs = dequeue_withdrawal_requests()
update_excess_withdrawal_requests()
reset_withdrawal_requests_count()
return ssz.serialize(reqs)
###########
## Helpers
###########
def little_endian_to_uint64(data: bytes) -> uint64:
return uint64(int.from_bytes(data, 'little'))
def uint64_to_little_endian(num: uint64) -> bytes:
return num.to_bytes(8, 'little')
class ValidatorWithdrawalRequest(object):
source_address: Bytes20
validator_pubkey: Bytes48
amount: uint64
def dequeue_withdrawal_requests():
queue_head_index = sload(WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS, WITHDRAWAL_REQUEST_QUEUE_HEAD_STORAGE_SLOT)
queue_tail_index = sload(WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS, WITHDRAWAL_REQUEST_QUEUE_TAIL_STORAGE_SLOT)
num_in_queue = queue_tail_index - queue_head_index
num_dequeued = min(num_in_queue, MAX_WITHDRAWAL_REQUESTS_PER_BLOCK)
reqs = []
for i in range(num_dequeued):
queue_storage_slot = WITHDRAWAL_REQUEST_QUEUE_STORAGE_OFFSET + (queue_head_index + i) * 3
source_address = address(sload(WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS, queue_storage_slot)[0:20])
validator_pubkey = (
sload(WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS, queue_storage_slot + 1)[0:32] + sload(WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS, queue_storage_slot + 2)[0:16]
)
amount = little_endian_to_uint64(sload(WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS, queue_storage_slot + 2)[16:24])
req = ValidatorWithdrawalRequest(
source_address=Bytes20(source_address),
validator_pubkey=Bytes48(validator_pubkey),
amount=uint64(amount)
)
reqs.append(req)
new_queue_head_index = queue_head_index + num_dequeued
if new_queue_head_index == queue_tail_index:
# Queue is empty, reset queue pointers
sstore(WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS, WITHDRAWAL_REQUEST_QUEUE_HEAD_STORAGE_SLOT, 0)
sstore(WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS, WITHDRAWAL_REQUEST_QUEUE_TAIL_STORAGE_SLOT, 0)
else:
sstore(WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS, WITHDRAWAL_REQUEST_QUEUE_HEAD_STORAGE_SLOT, new_queue_head_index)
return reqs
def update_excess_withdrawal_requests():
previous_excess = sload(WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS, EXCESS_WITHDRAWAL_REQUESTS_STORAGE_SLOT)
if previous_excess == EXCESS_INHIBITOR:
previous_excess = 0
count = sload(WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS, WITHDRAWAL_REQUEST_COUNT_STORAGE_SLOT)
new_excess = 0
if previous_excess + count > TARGET_WITHDRAWAL_REQUESTS_PER_BLOCK:
new_excess = previous_excess + count - TARGET_WITHDRAWAL_REQUESTS_PER_BLOCK
sstore(WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS, EXCESS_WITHDRAWAL_REQUESTS_STORAGE_SLOT, new_excess)
def reset_withdrawal_requests_count():
sstore(WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS, WITHDRAWAL_REQUEST_COUNT_STORAGE_SLOT, 0)
caller
push20 0xfffffffffffffffffffffffffffffffffffffffe
eq
push1 0xcb
jumpi
push1 0x11
push0
sload
dup1
push32 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
eq
push2 0x01f4
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 0x38
eq
push1 0x88
jumpi
calldatasize
push2 0x01f4
jumpi
callvalue
push2 0x01f4
jumpi
push0
mstore
push1 0x20
push0
return
jumpdest
callvalue
lt
push2 0x01f4
jumpi
push1 0x01
sload
push1 0x01
add
push1 0x01
sstore
push1 0x03
sload
dup1
push1 0x03
mul
push1 0x04
add
caller
dup2
sstore
push1 0x01
add
push0
calldataload
dup2
sstore
push1 0x01
add
push1 0x20
calldataload
swap1
sstore
caller
push1 0x60
shl
push0
mstore
push1 0x38
push0
push1 0x14
calldatacopy
push1 0x4c
push0
log0
push1 0x01
add
push1 0x03
sstore
stop
jumpdest
push1 0x03
sload
push1 0x02
sload
dup1
dup3
sub
dup1
push1 0x10
gt
push1 0xdf
jumpi
pop
push1 0x10
jumpdest
push0
jumpdest
dup2
dup2
eq
push2 0x0183
jumpi
dup3
dup2
add
push1 0x03
mul
push1 0x04
add
dup2
push1 0x4c
mul
dup2
sload
push1 0x60
shl
dup2
mstore
push1 0x14
add
dup2
push1 0x01
add
sload
dup2
mstore
push1 0x20
add
swap1
push1 0x02
add
sload
dup1
push32 0xffffffffffffffffffffffffffffffff00000000000000000000000000000000
and
dup3
mstore
swap1
push1 0x10
add
swap1
push1 0x40
shr
swap1
dup2
push1 0x38
shr
dup2
push1 0x07
add
mstore8
dup2
push1 0x30
shr
dup2
push1 0x06
add
mstore8
dup2
push1 0x28
shr
dup2
push1 0x05
add
mstore8
dup2
push1 0x20
shr
dup2
push1 0x04
add
mstore8
dup2
push1 0x18
shr
dup2
push1 0x03
add
mstore8
dup2
push1 0x10
shr
dup2
push1 0x02
add
mstore8
dup2
push1 0x08
shr
dup2
push1 0x01
add
mstore8
mstore8
push1 0x01
add
push1 0xe1
jump
jumpdest
swap2
add
dup1
swap3
eq
push2 0x0195
jumpi
swap1
push1 0x02
sstore
push2 0x01a0
jump
jumpdest
swap1
pop
push0
push1 0x02
sstore
push0
push1 0x03
sstore
jumpdest
push0
sload
dup1
push32 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
eq
iszero
push2 0x01cd
jumpi
pop
push0
jumpdest
push1 0x01
sload
push1 0x02
dup3
dup3
add
gt
push2 0x01e2
jumpi
pop
pop
push0
push2 0x01e8
jump
jumpdest
add
push1 0x02
swap1
sub
jumpdest
push0
sstore
push0
push1 0x01
sstore
push1 0x4c
mul
push0
return
jumpdest
push0
push0
revert
提款请求合约像任何其他智能合约一样部署。通过从所需的部署交易反向工作,生成一个特殊的合成地址:
{
"type": "0x0",
"nonce": "0x0",
"to": null,
"gas": "0x3d090",
"gasPrice": "0xe8d4a51000",
"maxPriorityFeePerGas": null,
"maxFeePerGas": null,
"value": "0x0",
"input": "0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff5f556101f880602d5f395ff33373fffffffffffffffffffffffffffffffffffffffe1460cb5760115f54807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff146101f457600182026001905f5b5f82111560685781019083028483029004916001019190604d565b909390049250505036603814608857366101f457346101f4575f5260205ff35b34106101f457600154600101600155600354806003026004013381556001015f35815560010160203590553360601b5f5260385f601437604c5fa0600101600355005b6003546002548082038060101160df575060105b5f5b8181146101835782810160030260040181604c02815460601b8152601401816001015481526020019060020154807fffffffffffffffffffffffffffffffff00000000000000000000000000000000168252906010019060401c908160381c81600701538160301c81600601538160281c81600501538160201c81600401538160181c81600301538160101c81600201538160081c81600101535360010160e1565b910180921461019557906002556101a0565b90505f6002555f6003555b5f54807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff14156101cd57505f5b6001546002828201116101e25750505f6101e8565b01600290035b5f555f600155604c025ff35b5f5ffd",
"v": "0x1b",
"r": "0x539",
"s": "0x5feeb084551e4e03a3581e269bc2ea2f8d0008",
"hash": "0x8ded54be89448d78d4bc97782c0187b099e45380ab681742f9d3754e405c2572"
}
Sender: 0x8646861A7cF453dDD086874d622b0696dE5b9674
Address: 0x00000961Ef480Eb55e80D19ad83579A64c007002
规范的草图:
ExecutionLayerWithdrawalRequestMAX_WITHDRAWAL_REQUESTS_PER_BLOCK 限制的 SSZ 列表出现在 ExecutionPayload 中process_voluntary_exit,但可能会导致验证失败(例如验证者已退出),而不会导致区块失败(类似于来自 EL 的存款)process_operations 中为 ExecutionPayload 中找到的每个 ExecutionLayerWithdrawalRequest 调用validator_pubkey 字段多个验证者可以使用相同的执行层提款凭证,因此 validator_pubkey 字段用于区分正在退出的验证者。
请注意,validator_index 也可以区分验证者。
问题在于,一些质押池的智能合约不知道索引,因为索引只有在信标链上创建验证者之后才为人所知,而公钥是提前可用的。
合约维护一个提款请求消息的状态内队列,每个区块都将其出队到区块中,从而进入执行层。
可以传递到共识层的提款请求数量受 MAX_WITHDRAWAL_REQUESTS_PER_BLOCK 限制,以限制区块大小和共识层处理的负载。选择 16 作为 MAX_WITHDRAWAL_REQUESTS_PER_BLOCK 是为了与信标链上类似操作(例如 VoluntaryExit 和 Deposit)的限制保持一致。
尽管每个区块可以传递到共识层的提款请求数量有上限,但执行层的 Gas 限制可以支持每个区块对提款请求预部署合约进行更多调用。队列允许这些调用成功进行,同时仍保持系统速率限制。
考虑的替代设计是,在单个区块的上下文中成功调用 MAX_WITHDRAWAL_REQUESTS_PER_BLOCK 次后,对合约的调用失败。这将消除对消息队列的需求,但代价是,在退出量高时,合约调用失败会带来糟糕的用户体验。缓解这种糟糕用户体验的复杂性相对较低,并且目前受到青睐。
交易在执行层通过 Gas 限制自然地进行速率限制,但愿意支付市场价 Gas 费用(并可能利用构建者市场支付区块头部交易包含费用)的攻击者可以以相对便宜的价格填满退出操作限制,从而损害希望发出提款请求的诚实验证者。
有两种通用方法可以对抗这种损害——(a) 只允许验证者发送此类消息,并对每个时间段进行限制,或 (b) 利用经济方法使此类损害的成本越来越高。
方法 (a)(本 EIP 中未使用)将需要 EIP-4788(BEACON_ROOT 操作码),用于证明与验证者公钥相关的提款凭证,以及一个数据结构来跟踪每单位时间(例如 4 个月)的请求,以确保验证者不会通过提交许多请求来损害该机制。这种方法的缺点是它需要另一个跨层 EIP,并且具有更高的跨层复杂性(例如,如果信标根的默克尔树的形状发生变化,未来升级中可能需要注意,那么合约和证明结构可能需要更新)。
本 EIP 采用了方法 (b) 来消除额外的 EIP 要求并降低跨层复杂性,以使此 EIP 的正确性(现在和未来)更容易分析。采用动态调整费用机制的 EIP-1559 式机制允许用户在正常情况下(平均每个区块少于 2 个)为提款请求支付 MIN_WITHDRAWAL_REQUEST_FEE,但会根据高使用率(即潜在滥用)呈指数级增加费用。
TARGET_WITHDRAWAL_REQUESTS_PER_BLOCK 配置值TARGET_WITHDRAWAL_REQUESTS_PER_BLOCK 已选择为 2,以确保在退出高峰拥堵的极端情况下,信标状态中部分提款队列的增长可以忽略不计。
费用更新规则旨在近似公式 $fee = MIN_WITHDRAWAL_REQUEST_FEE * e^{excess / WITHDRAWAL_REQUEST_FEE_UPDATE_FRACTION}$,其中 $excess$ 是链相对于“目标”数量(每个区块 $TARGET_WITHDRAWAL_REQUESTS_PER_BLOCK$)处理的提款请求的“额外”总量。
与 EIP-1559 类似,它是一个自我修正的公式:随着 $excess$ 变高,$fee$ 会呈指数级增长,从而减少使用量并最终迫使 $excess$ 回落。
逐块行为大致如下。如果区块 $N$ 处理 $X$ 个请求,那么在区块 $N$ 结束时,$excess$ 增加 $X - TARGET_WITHDRAWAL_REQUESTS_PER_BLOCK$,因此区块 $N+1$ 中的 $fee$ 增加因子 $e^{((X - TARGET_WITHDRAWAL_REQUESTS_PER_BLOCK) / WITHDRAWAL_REQUEST_FEE_UPDATE_FRACTION)}$。因此,它与现有的 EIP-1559 具有类似的效果,但在某种意义上更“稳定”,因为它对相同的总提款请求以相同的方式响应,而无论它们是如何随时间分布的。
参数 WITHDRAWAL_REQUEST_FEE_UPDATE_FRACTION 控制 blob gas 价格的最大向下变化率。它被选择为目标最大向下变化率为每个区块 $e^{(TARGET_WITHDRAWAL_REQUESTS_PER_BLOCK / WITHDRAWAL_REQUEST_FEE_UPDATE_FRACTION)} \approx 1.125$。
有关费用机制的更详细分析可在此处找到。
提款请求被放置在信标区块的实际主体中。
有一个强烈的设计要求是共识层和执行层可以相互独立地执行。这意味着,在这种情况下,共识层不能依赖于对执行层的同步调用来获取当前区块所需的提款请求。相反,请求必须嵌入到信标区块中,这样即使执行层离线,共识层仍然拥有执行状态转换功能的共识部分所需的数据。
通过 Engine API 验证来自共识层的 secp256k1 签名是所提议请求机制的替代方案之一,其工程复杂性要低得多。
然而,这种方法将通过使拥有验证者提款凭证的智能合约无法从该功能中受益,从而在很大程度上限制提款请求的使用。
本 EIP 对区块结构和区块验证规则集引入了向后不兼容的更改。但这些更改均未破坏与当前用户活动和体验相关的任何内容。
可能存在一些现有的托管关系和/或产品依赖于提款凭证不能触发提款请求的假设。我们目前确信,额外的提款凭证功能不会影响现有验证者的安全性,原因如下:
如果现有验证者/托管人依赖于此,那么验证者可以退出并使用指向模拟此行为的智能合约的 0x01 提款凭证重新质押。
对系统合约的调用需要支付由当前合约状态定义的费用。超额支付的费用不会退还给调用者。通常无法提前计算确切的所需费用金额。当从合约添加提款请求时,合约可以执行读取操作来检查当前费用,然后精确支付所需的金额。以下是 Solidity 中的一个示例:
function addWithdrawal(bytes memory pubkey, uint64 amount, uint64 requestFeeLimit) private {
assert(pubkey.length == 48);
// Read current fee from the contract.
(bool readOK, bytes memory feeData) = WithdrawalsContract.staticcall('');
if (!readOK) {
revert('reading fee failed');
}
uint256 fee = uint256(bytes32(feeData));
// Check the fee is not too high.
if (fee > requestFeeLimit) {
revert('fee is too high');
}
// Add the request.
bytes memory callData = abi.encodePacked(pubkey, amount);
(bool writeOK,) = WithdrawalsContract.call{value: fee}(callData);
if (!writeOK) {
revert('adding request failed');
}
}
注意:系统合约使用 EVM CALLER 操作 (Solidity: msg.sender) 作为提款的目标地址,即调用系统合约的地址必须与信标状态中记录的 0x01 提款凭证匹配。
注意:上述代码在费用过高时会回滚,费用在创建提款请求交易和将其包含在区块之间可能会发生显著变化,因此,此检查对于避免超额支付非常重要。
使用 EOA 请求提款将始终导致费用超额支付。EOA 无法使用包装合约请求提款。即使存在这种方式,退还超额部分的 Gas 成本可能也会高于超额部分本身。如果希望通过系统合约向 EOA 请求提款,我们建议用户执行交易模拟以估算合理的发送费用。
尽管对 WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS 的系统调用失败的可能性极低,但在此类情况下的行为是明确定义的:区块被标记为无效。然而,如果失败是由于处理区块内的交易而导致的,即使区块无效,公共内存池仍可能保留该交易。这可能导致违规交易再次被包含,从而可能导致一个或多个后续Slot没有有效的区块。为了缓解这种情况,我们建议区块生产者实现在不包含违规交易的情况下重新洗牌其交易集,以增加生成有效区块的机会。区块生产者实现和/或内存池应了解系统调用失败场景,以启用此行为。
如果 WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS 处没有代码(即,如果链未“准备好”),则不应激活此 EIP。这样做会导致 FORK_BLOCK 之后的第一个及所有后续区块被标记为无效。
如果这种情况发生在实时链上,以下是两种潜在的恢复策略:
FORK_BLOCK 激活点,然后在分叉激活之前部署合约。版权及相关权利通过 CC0 放弃。
- 原文链接: github.com/nerolation/EI...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!