提款
提款是跨域交易,在 L2 上发起,并由在 L1 上执行的交易完成。值得注意的是,L2 账户可以使用提款来调用 L1 合约,或者将 ETH 从 L2 账户转移到 L1 账户。
词汇注释:提现可以指交易过程中各个阶段的交易,但我们引入更具体的术语来区分:
- 提款发起交易特指 L2 上发送到提款预部署的交易。
- 提现证明交易特指证明提现正确(已包含在根在L1上的merkle树中)的L1交易。
- 提现完成交易特指完成并中继提现的L1交易。
提款是通过调用 Message Passer 预部署合约在 L2 上发起的,该合约在其存储中记录了消息的重要属性。通过调用 来在 L1 上证明提款OptimismPortal,这证明包含此提款消息。提款是通过调用合约在 L1 上完成的OptimismPortal,该合约验证自提款消息被证明以来故障挑战期已经过去。
这样,提款与存款不同,存款在执行引擎客户端中使用特殊的交易类型 。相反,提款交易必须使用 L1 上的智能合约来完成。
目录
提现流程
我们首先描述发起和完成提款的端到端流程:
在 L2 上
L2 账户向L2ToL1MessagePasser预部署合约发送一条提款消息(也可能是 ETH)。这是一个非常简单的合约,存储提款数据的哈希值。
位于 L1
- 中继者提交提款证明交易以及合约所需的输入
OptimismPortal。中继者不一定是在 L2 上发起撤回的同一实体。这些输入包括提款交易数据、包含证明和区块号。区块号必须是存在 L2 输出根的区块号,该根号承诺在 L2 上注册的提款。 - 合约从的 函数
OptimismPortal中检索给定块号的输出根,并在内部执行其余的验证过程。L2OutputOracle``getL2Output() - 如果证明验证失败,则调用恢复。否则,哈希值将被记录以防止其被重新证明。请注意,如果相应的输出根发生变化,则可以多次证明撤回。
- 提现被证明后,进入 7 天的挑战期,让其他网络参与者有时间挑战相应输出根的完整性。
- 一旦挑战期结束,中继者就会向
OptimismPortal合约提交提款完成交易。中继者不需要是在 L2 上发起提款的同一实体。 - 合约
OptimismPortal接收提现交易数据并验证提现是否已被证明并通过挑战期。 - 如果不满足要求,呼叫将恢复。否则,呼叫将被转发,并记录哈希值以防止重播。
L2ToL1MessagePasser 合约
通过调用 L2ToL1MessagePasser 合约的函数来发起提款initiateWithdrawal。L2ToL1MessagePasser 是一个简单的预部署合约,用于0x4200000000000000000000000000000000000016 存储要撤回的消息。
interface L2ToL1MessagePasser {
event MessagePassed(
uint256 indexed nonce, // this is a global nonce value for all withdrawal messages
address indexed sender,
address indexed target,
uint256 value,
uint256 gasLimit,
bytes data,
bytes32 withdrawalHash
);
event WithdrawerBalanceBurnt(uint256 indexed amount);
function burn() external;
function initiateWithdrawal(address _target, uint256 _gasLimit, bytes memory _data) payable external;
function messageNonce() public view returns (uint256);
function sentMessages(bytes32) view external returns (bool);
}
该MessagePassed事件包括散列并存储在映射中的所有数据sentMessages以及散列本身。
提款时地址不会使用别名
当合约进行存款时,发送者的地址是别名。提款则不然,提款不会修改发件人的地址。不同之处在于:
- 在 L2 上,存款发送者的地址由操作码返回
CALLER,这意味着合约无法轻易判断调用是源自 L1 还是 L2,而 l2Sender()在L1上,通过调用合约上的函数来访问提款发送者的地址OptimismPortal。
呼叫l2Sender()消除了有关呼叫源自哪个域的任何歧义。尽管如此,开发人员需要认识到,拥有相同的地址并不意味着 L2 上的合约将与 L1 上的合约表现相同。
乐观门户合约
Optimism Portal 充当 Optimism L2 的入口和出口点。它是一个继承自OptimismPortal合约的合约,此外还提供了以下提现接口:
interface OptimismPortal {
event WithdrawalFinalized(bytes32 indexed withdrawalHash, bool success);
function l2Sender() returns(address) external;
function proveWithdrawalTransaction(
Types.WithdrawalTransaction memory _tx,
uint256 _l2OutputIndex,
Types.OutputRootProof calldata _outputRootProof,
bytes[] calldata _withdrawalProof
) external;
function finalizeWithdrawalTransaction(
Types.WithdrawalTransaction memory _tx
) external;
}
提款验证和最终确定
需要提供以下信息来证明并最终确定提款:
- 提现交易数据:
nonce:所提供消息的随机数。sender:L2 上的消息发送者地址。target:L1 上的目标地址。value:发送到目标的 ETH。data:要发送到目标的数据。gasLimit:要转发到目标的气体。
- 证明和验证数据:
l2OutputIndex:L2 输出中的索引,可以在其中找到适用的输出根。outputRootProof``bytes32:用于导出输出根的四个值。withdrawalProof:L2ToL1MessagePasser 合约中给定提款的包含证明。
这些输入必须满足以下条件:
- 必须
l2OutputIndex是 L2 输出中包含适用输出根的索引。 L2OutputOracle.getL2Output(l2OutputIndex)返回一个非零值OutputProposal。- 值的 keccak256 哈希值
outputRootProof等于outputRoot. - 这
withdrawalProof是一个有效的包含证明,证明提款交易数据的哈希值包含在 L2 上的 L2ToL1MessagePasser 合约的存储中。
安全考虑
提款验证的关键属性
- 不应“双花”提款,即。在 L1 上中继与 L2 上发起的消息不对应的撤回。作为参考,请参阅Polygon 上发现的此类漏洞的文章。
- 对于 L2 上发起的每次提款(即具有唯一的
messageNonce()),必须满足以下属性:- 除非提款的outputRoot已经改变,否则只能证明一次提款。
- 应该只能完成一次提款。
- 应该不可能在任何字段被修改的情况下中继消息,即。
- 修改该
sender字段将启用“欺骗”攻击。 - 修改
target、data或value字段将使攻击者能够危险地改变提款的预期结果。 - 修改
gasLimit可能会使中继的成本过高,或者允许中继器导致target.
- 修改该
处理已成功验证但中继失败的消息
如果合约中中继调用的执行失败target,不幸的是无法确定它是否“应该”失败,以及它是否应该“可重放”。因此,为了最大限度地降低复杂性,我们没有提供任何重播功能,如果需要,可以在外部公用事业合同中实现。