1.如何组合使用spec

Move Prover 将依据某个函数的spec来验证调用该函数的其他函数。

例如,如果有一个名为foo的函数会调用bar,并且我们还有针对bar函数的bar_spec规范,那么移动证明器将会利用bar_spec来验证foo

使用 #[spec(prove)] 标记一个规范说明将促使证明者尝试对其进行证明。 若省略 prove 属性,则证明者将依据该规范说明来验证其他函数,但不会尝试对其进行证明。

2. 如何让验证器专注于特定的规范呢?

在规格说明/场景中添加focus属性(使用#[spec(prove, focus)语法)会使验证者仅尝试验证此特定的规格说明。您可以同时为多个规格应用focus属性,这样验证者将在一次运行中尝试验证所有这些规格。

3.如何设定一个场景,而非针对某个特定功能来编写说明呢?

通过在不以_spec结尾且没有target属性的函数上使用#[spec]属性来指定场景。 与功能规范不同,场景无需调用其所针对的函数,因为根本就没有这样的目标函数。

4.我该如何为位于不同模块中的一个函数编写说明文档呢?

target属性可用于指定所要使用的具体函数,而非依赖于_spec后缀。 按照惯例,如果将名为 <function_name>_spec 的函数放在同一模块中,那么它将针对 <function_name> 进行操作。 如果我们想要指定一个不在同一模块中的函数,可以使用target属性来指定所要指定的函数。 例如,如果我们有一个名为foo的函数,并且想要编写一个具有不同名称的测试脚本,可以使用target属性来指定其目标为foo

#![allow(unused)]
fn main() {
module 0x42::foo {
    public fun inc(x: u64): u64 {
        x + 1
    }
}

module 0x43::foo_spec {
    // ...

    #[spec(prove, target = foo::inc)]
    public fun this_is_not_inc_spec_yet_it_targets_inc(x: u64): u64 {
        let res = foo::inc(x);
        // ...
        res
    }
}

}

5. 在编写规范文档时,我该如何访问私有成员或函数呢?

这些规格只是普通的 Move 代码,因此无法访问私有成员或函数。 但在某些情况下这是必要的,尤其是当为不同模块中的函数编写规格时。 为绕过这一限制,您可以使用 #[spec_only] 来添加所需的获取器和其他访问函数。 正如其名称所示,这些访问器仅对规格可见,并且不会包含在常规编译中。

6. 我该如何设定交易终止的条件呢?

在某些算术条件(例如溢出)以及 assert! 语句失败的情况下,事务会自动终止——详情请参阅终止与断言。 函数的规格说明还必须详细阐述该函数在何种情况下会终止运行。这一内容是通过使用asserts函数来实现的。 例如,如果我们有一个名为 foo 的函数,其会在 x 不小于 y 的情况下终止执行:

#![allow(unused)]
fn main() {
fun foo(x: u64, y: u64): u64 {
    assert!(x < y);
    x
}

}

我们可以为foo编写一个规范说明,其中明确指出foo只有在x小于y时才会停止运行:

#![allow(unused)]
fn main() {
#[spec(prove)]
fun foo_spec(x: u64, y: u64): u64 {
    asserts(x < y); // This is the condition under which foo aborts
    let res = foo(x, y);
    // ...
    res
}

}

请参见下文第 8 条,其中介绍了避免指定终止条件的一种方法。

7. 如何设定溢出中断条件呢?

规格说明必须明确指出一个功能终止运行的所有条件。请参考上述第 6 个问题中的示例,了解如何通过 assert! 语句来指定终止条件。 下面,让我们以一个指定溢出中断条件的示例来进行探讨。假设我们有一个名为 add 的函数,它用于将两个 u64 类型的值相加:

#![allow(unused)]
fn main() {
fun add(x: u64, y: u64): u64 {
    x + y
}

}

可以为 add 函数编写一个规格说明,其中明确指出:如果 xy 的和大于 u64 的最大值,则 add 函数将终止执行。

#![allow(unused)]
fn main() {
#[spec(prove)]
fun add_spec(x: u64, y: u64): u64 {
    asserts((x as u128) + (y as u128) <= u64::max_value!() as u128);
    let res = add(x, y);
    // ...
    res
}
}

请参见下文第 8 条,其中介绍了避免指定终止条件的一种方法。

8. 我该如何避免明确设定终止条件呢??

ignore_abort注解允许我们省略对精确终止条件的明确说明:

#![allow(unused)]
fn main() {
#[spec(prove, ignore_abort)]
fun add_spec(x: u64, y: u64): u64 {
    let res = add(x, y);
    // ...
    res
}
}

9. 为什么我在添加spec时会出现编译错误?

这是一个棘手的问题。证明者需要对主 Move 编译流程以及stdlibsui-framework进行修改, 不幸的是,这目前使得规范无法与常规的 Move 代码很好地共存。 正在与 Mysten 团队合作,将这些能够让您将规范保留在同一代码库中的更改向上推送到 Sui 的主仓库。 与此同时,您需要创建一个新的包来存放这些证明文件,并使用目标机制来引用您的代码。

10. 我们如何避免在验证其他规格的过程中使用到那些规格本身呢?

默认情况下,在证明另一个规范的过程中使用函数的规范时,会采用该函数的规范。

#![allow(unused)]
fn main() {
fun foo(x: &mut u8) {
    *x = 70;
}

fun bar(x: &mut u8) {
    foo(x);
}

#[spec(prove, no_opaque)]
fun foo_spec(x: &mut u8) {
    foo(x);
}

#[spec(prove)]
fun bar_spec(x: &mut u8) {
    bar(x);

    ensures(x == 70); // ok
}
}

在上述示例中,对于 bar_spec 的证明将尝试将 foo_spec 用作 foo实现部分,除非存在 no_opaque 属性。