NEAR跨合约调用异常如何处理?

How to handle exceptions in NEAR cross contract calls?

如何在合约之间的异步调用链中捕获并处理异常?

假设我的事务发起了以下调用:

contractA.run()
  -> do changes in contractA
  -> calls contractB.run()
     -> do changes in contractB
  -> then calls another method on contractA: contractA.callback()
     * callback() crashes

在 Promise 出现异常后,NEAR 不会回滚过去 Promise 中发生的更改。我也没有在 near-sdk.

中看到任何处理异常的方法

一个想法是 return 错误而不是抛出异常并创建一堆私有函数来更新错误值和添加/释放互斥体后的状态。然而,这并不能解决有时我们无法控制的问题,例如在外部智能合约中(例如,如果 contractB.do 会在上面的示例中出现恐慌)。

捕获异常的唯一方法是对生成异常的承诺进行回调。

在解释的场景中,contractA.callback() 应该不会崩溃。您需要足够仔细地构建合约,以避免回调失败。大多数情况下这是可行的,因为您可以控制回调的输入和附加的气体量。如果回调失败,则类似于在异常处理代码中出现异常。

另请注意,您可以确保 callback 已正确安排,并在 contractA.run() 中附加了足够的气体。如果不是这种情况,例如你没有足够的 gas 附加到 run,回调和其他承诺的调度将失败并且 run 更改的整个状态被回滚。 但是一旦 run 完成,来自 run 的状态更改将被提交并且必须仔细处理 callback

我们在 lockup 合约中有几个地方允许回调失败:https://github.com/near/core-contracts/blob/6fb13584d5c9eb1b372cfd80cd18f4a4ba8d15b6/lockup/src/owner_callbacks.rs#L7-L24

还有大部分回调不会失败的地方:https://github.com/near/core-contracts/blob/6fb13584d5c9eb1b372cfd80cd18f4a4ba8d15b6/lockup/src/owner_callbacks.rs#L28-L61


指出在某些情况下合约不想依赖其他合约的稳定性,例如当流量为 A --> B --> A --> B 时。在这种情况下 B 无法将回调附加到提供给 A 的资源。对于这些场景,我们正在讨论添加一个特定构造的可能性,该构造是一个原子并且一旦被删除就会有一个解析回调。我们称它为 Safehttps://github.com/nearprotocol/NEPs/pull/26


编辑

What if contractB.run fails and I will like to update the state in contractA to rollback changes from contractA.run?

在这种情况下,仍然会调用 contractA.callback(),但它的依赖项 contractB.run 具有 PromiseResult::Failed

因此 callback() 可以修改 contractA 的状态以恢复更改。

例如,锁定合约实现的回调处理从质押池合约中的提款:https://github.com/near/core-contracts/blob/6fb13584d5c9eb1b372cfd80cd18f4a4ba8d15b6/lockup/src/foundation_callbacks.rs#L143-L185

如果我们调整名称以匹配示例:

锁定合约(contractA)尝试从质押池(contractB)中提取资金(run()),但由于最近未质押,资金可能仍处于锁定状态,所以取款失败(contractB.run() 失败)。 调用回调 (contractA.callback()) 并检查承诺是否成功 (contractB.run)。由于提现失败,callback reverts the state back to the original(恢复状态)

实际上,它稍微复杂一些,因为实际序列是 A.withdraw_all -> B.get_amount -> A.on_amount_for_withdraw -> B.withdraw(amount) -> A.on_withdraw