英文原开发文档: https://book.clarity-lang.org/ch06-04-response-checking.html
响应检查
我们已经知道公共函数返回的响应决定了在链上是否实现了状态变化。这不仅适用于初始合约程序的调用,也适用于后续的合约程序的调用。如果一个标准的主体用户调用了合约程序 A,后者又调用了合约程序 B,那么合约 B 的响应只会影响合约 B 的内部状态。 也就是说,如果合约 B 返回一个错误,那么对它数据成员的任何修改会被恢复,但合约程序 A 仍然可以修改自己的数据成员,如果它本身返回一个 ok,则可以实现对数据成员的修改。但是,调用的第一个合约仍处于被控制状态。如果它返回一个错误,那么任何事情都不会实现。
因此,提交或恢复更改是按顺序确定的。
这意味着在多合约程序调用链中,调用的合约程序绝对知道如果子调用返回 一个 err 响应,它就不会在链上实现。尽管如此,合约程序可能取决于子合约程序调用的成功。例如,钱包合约调用代币合约程序来转移代币。开发人员经常忘记检查返回值。为了防止出现此类错误,Clarity 禁止不加检查地进行中间响应。中间响应是一种响应,虽然它是表达式的一部分,但不是返回的响应。我们可以用 begin 函数来说明:
(begin
true ;; this is a boolean, so it is fine.
(err false) ;; this is an *intermediary response*.
(ok true) ;; this is the response returned by the begin.
)
执行该代码段会返回以下错误:
Analysis error: intermediary responses in consecutive statements must be checked
由于响应旨在表明操作的成功或失败,因此它们不能悬而未决。检查响应只是意味着处理它;也就是说,解开它或广播它。因此,我们可以用 try! 或围绕它的另一个控制流函数来修复代码。
任何返回响应的函数调用都必须由调用函数返回或检查。通常,这采用 inter-contract 函数调用的形式,但一些内置函数也会返回响应类型。stx-transfer?函数就是其中之一。
我们将通过以下存款合约程序看到这种行为的样子。它包含允许用户存入 STX 并跟踪个人用户存款的功能。由于一个未经检查的中间响应,该函数无效。您的任务是尝试找到并修复它。
(define-map deposits principal uint)
(define-read-only (get-total-deposit (who principal))
(default-to u0 (map-get? deposits who))
)
(define-public (deposit (amount uint))
(begin
(stx-transfer? amount tx-sender (as-contract tx-sender))
(map-set deposits tx-sender (+ (get-total-deposit tx-sender) amount))
(ok true)
)
)
;; Try a test deposit
(print (deposit u500))
你找到原因了吗?分析给了我们一个提示,表明 begin 表达式包含一个中间响应。在这种情况下,它是 stx-transfer? 的返回值。检查响应的最简单方法是简单地将转账包装在 try!函数 中。这样,转账的结果在成功时被解包,如果是 err 则被传播。因此,我们将这一行代码改写如下:
(try! (stx-transfer? amount tx-sender (as-contract tx-sender)))
我们也可以通过解开响应并使用条件语句来解决这个问题。然而,这样的结构会使函数变得更加复杂,老实说会使事情变得过于复杂。编写智能合约程序的艺术是提出一个简单的应用程序流程,以最少的代码实现所需的功能。
下面的新定义的函数看起来不错,但实际上可以进一步简化函数:
(define-public (deposit (amount uint))
(begin
(map-set deposits tx-sender (+ (get-total-deposit tx-sender) amount))
(stx-transfer? amount tx-sender (as-contract tx-sender))
)
)
此实现在功能上是等效的。stx-transfer? 返回的类型响应为 (response bool uint),因此我们不需要在 begin 函数的末尾重申我们自己的( ok true )。通过将 transfer 表达式移到最后一行,它的响应不再是中间,而是从 deposit 函数返回——无论它是 ok 还是 err 。
最佳实践的章节将教你一些关于如何发现可以以相同方式简化的代码的技术。