更新 Move.toml 文件以使用隐式依赖项 Sui 验证器遵循了 Move.toml 文件中近期的惯例,即依赖隐式依赖项。 因此,您需要移除对 Sui 和 MoveStdlib 的任何直接依赖关系。 有关更多背景信息,请参阅此消息。 // 如果move.toml有这一行请删除:

Sui = { git = "https://github.com/MystenLabs/sui.git", subdir = "crates/sui-framework/packages/sui-framework", rev = "framework/testnet", override = true }

如果您确实需要直接引用 Sui,请将相关规范放在单独的文件中。 基本用法 您可以在此处找到本指南中使用的完整示例:https://github.com/asymptotic-code/sui-kit/tree/main/examples/guide 要使用 Sui 验证器,您需要为您的智能合约编写规范。 然后,Sui 验证器将尝试证明您的智能合约满足这些规范。 示例 让我们以一个简单的示例来说明一下,即一个简化版的 LP(流动性池)智能合约中的取款功能:

module amm::simple_lp;

use sui::balance::{Balance, Supply, zero};

public struct LP<phantom T> has drop {}

public struct Pool<phantom T> has store {
    balance: Balance<T>,
    shares: Supply<LP<T>>,
}

public fun withdraw<T>(pool: &mut Pool<T>, shares_in: Balance<LP<T>>): Balance<T> {
    if (shares_in.value() == 0) {
        shares_in.destroy_zero();
        return zero()
    };

    let balance = pool.balance.value();
    let shares = pool.shares.supply_value();

    let balance_to_withdraw = (((shares_in.value() as u128) * (balance as u128)) / (shares as u128)) as u64;

    pool.shares.decrease_supply(shares_in);
    pool.balance.split(balance_to_withdraw)
}

为了验证取款功能是否正确,我们需要为其编写一份规范说明。该规范应描述该功能的预期行为。 规范的结构 规范通常具有以下结构:

#[spec(prove)]
fun withdraw_spec<T>(pool: &mut Pool<T>, shares_in: Balance<LP<T>>): Balance<T> {

    // 函数的参数所假定满足的条件
    let result = withdraw(pool, shares_in);

    // 作为函数调用的结果所必须满足的条件
    result
}

让我们来详细分析上述内容: 该规范将定义一个新的函数withdraw_spec,它与withdraw函数具有相同的参数,并返回相同的类型。 该函数通过 #[spec(prove)] 进行注解,以表明它是规范,并且将使用 Sui 验证器进行验证。 规范函数的主体通常包括: 首先,指定在函数参数上所假定成立的条件 然后调用要验证的函数 接着指定函数调用所产生的条件必须成立 最后返回函数调用的结果 示例规范 让我们编写一个规范,说明在提取资金时股票的价格不应下降:

#[spec(prove)]
fun withdraw_spec<T>(pool: &mut Pool<T>, shares_in: Balance<LP<T>>): Balance<T> {
    
    requires(shares_in.value() <= pool.shares.supply_value());

    let old_pool = old!(pool);

    let result = withdraw(pool, shares_in);

    let old_balance = old_pool.balance.value().to_int();
    let new_balance = pool.balance.value().to_int();

    let old_shares = old_pool.shares.supply_value().to_int();
    let new_shares = pool.shares.supply_value().to_int();

    ensures(new_shares.mul(old_balance).lte(old_shares.mul(new_balance)));

    result
}

让我们来详细解读一下这个规范: 我们明确了函数参数所必须满足的条件。要提取的股份数量必须小于或等于池中的总股份数量:

requires(shares_in.value() <= pool.shares.supply_value());

requires 是一个only-spec的关键字,用于指定函数参数所应满足的条件。 我们将池的旧状态保存在old_pool中:

let old_pool = old!(pool);

我们称要验证的函数为:

let result = withdraw(pool, shares_in);

我们计算出旧余额和新余额以及各自的份额,并将它们转换为无界整数:

let old_balance = old_pool.balance.value().to_int();
let new_balance = pool.balance.value().to_int();

let old_shares = old_pool.shares.supply_value().to_int();
let new_shares = pool.shares.supply_value().to_int();

无界整数是一种仅在规范中定义的类型,它允许我们编写条件而无需担心溢出问题。这种类型仅在规范环境中可用。 我们规定了作为函数调用效果所必须满足的条件。当提取资金时,股票的价格不应下降:

ensures(new_shares.mul(old_balance).lte(old_shares.mul(new_balance)));

ensures 是一个仅用于规范的关键字,它指定了函数调用所产生的必须满足的条件。 mullte 是上述未受限制整数类型上的仅用于规范的运算符。 最后,我们返回结果。 运行 Sui 验证器 要运行 Sui 验证器,您需要先安装并配置 Sui 验证器。 从 move.toml 目录中运行以下命令即可运行 Sui 验证器:

sui-prover

使用幽灵变量 幽灵变量是一种用于声明仅在规范中使用的全局变量的方法。 它们特别适用于在不同规范之间传递信息。 以上述示例为例,接下来: 添加一个功能,使其在进行大额取款时触发事件, 然后添加一个幽灵变量来检查该事件是否正确触发。 更新后的示例 首先,让我们将事件触发功能添加到原始示例中:

module amm::simple_lp;

use sui::balance::{Balance, Supply, zero};
use sui::event;

#[spec_only]
use prover::prover::{requires, ensures, asserts, old};
#[spec_only]
use prover::ghost::{declare_global, global};

public struct LP<phantom T> has drop {}

public struct Pool<phantom T> has store {
    balance: Balance<T>,
    shares: Supply<LP<T>>,
}

const LARGE_WITHDRAW_AMOUNT: u64 = 10000;

public struct LargeWithdrawEvent has copy, drop {}

fun emit_large_withdraw_event() {
    event::emit(LargeWithdrawEvent { });
}

public fun withdraw<T>(pool: &mut Pool<T>, shares_in: Balance<LP<T>>): Balance<T> {
    if (shares_in.value() == 0) {
        shares_in.destroy_zero();
        return zero()
    };

    let balance = pool.balance.value();
    let shares = pool.shares.supply_value();
    let shares_in_value = shares_in.value();

    let balance_to_withdraw = (((shares_in.value() as u128) * (balance as u128)) / (shares as u128)) as u64;

    pool.shares.decrease_supply(shares_in);

    if (shares_in_value >= LARGE_WITHDRAW_AMOUNT) {
        emit_large_withdraw_event();
    };

    pool.balance.split(balance_to_withdraw)
}

我们进行了以下更改: 添加了事件模块, 声明了LARGE_WITHDRAW_AMOUNT限制条件, 添加了一个LargeWithdrawEvent结构体, 添加了用于发出事件的event::emit函数。 更新了规范 现在,为了检查该事件是否正确发出,我们需要声明一个虚拟变量,并检查在事件发出后该变量是否被设置为: 首先,我们在规范的开头添加以下行来声明这个虚拟变量:

declare_global<LargeWithdrawEvent, bool>();

然后,我们检查在事件发出后该值是否已被设置为。 我们可以使用常规的 if 语句来实现这一点,而在规范中它会成为一个逻辑条件。

if (shares_in_value >= LARGE_WITHDRAW_AMOUNT) {
    ensures(*global<LargeWithdrawEvent, bool>());
};

最后,我们确保原始函数在触发事件时需要先设置幽灵变量:

requires(*global<LargeWithdrawEvent, bool>());

完整的代码如下所示。 完整的示例以供参考 以下是完整的示例供参考:

module amm::simple_lp;

use sui::balance::{Balance, Supply, zero};
use sui::event;

#[spec_only]
use prover::prover::{requires, ensures, asserts, old};
#[spec_only]
use prover::ghost::{declare_global, global};

public struct LP<phantom T> has drop {}

public struct Pool<phantom T> has store {
balance: Balance<T>,
shares: Supply<LP<T>>,
}

const LARGE_WITHDRAW_AMOUNT: u64 = 10000;

public struct LargeWithdrawEvent has copy, drop {}

fun emit_large_withdraw_event() {
event::emit(LargeWithdrawEvent { });
requires(*global<LargeWithdrawEvent, bool>());
}

public fun withdraw<T>(pool: &mut Pool<T>, shares_in: Balance<LP<T>>): Balance<T> {
if (shares_in.value() == 0) {
shares_in.destroy_zero();
return zero()
};

    let balance = pool.balance.value();
    let shares = pool.shares.supply_value();
    let shares_in_value = shares_in.value();

    let balance_to_withdraw = (((shares_in.value() as u128) * (balance as u128)) / (shares as u128)) as u64;

    pool.shares.decrease_supply(shares_in);

    if (shares_in_value >= LARGE_WITHDRAW_AMOUNT) {
        emit_large_withdraw_event();
    };

    pool.balance.split(balance_to_withdraw)
}

// Verify that the price of the token is not decreased by withdrawing liquidity
#[spec(prove)]
fun withdraw_spec<T>(pool: &mut Pool<T>, shares_in: Balance<LP<T>>): Balance<T> {
requires(shares_in.value() <= pool.shares.supply_value());

    declare_global<LargeWithdrawEvent, bool>();

    let old_pool = old!(pool);
    let shares_in_value = shares_in.value();

    let result = withdraw(pool, shares_in);

    let old_balance = old_pool.balance.value().to_int();
    let new_balance = pool.balance.value().to_int();

    let old_shares = old_pool.shares.supply_value().to_int();
    let new_shares = pool.shares.supply_value().to_int();

    ensures(new_shares.mul(old_balance).lte(old_shares.mul(new_balance)));

    if (shares_in_value >= LARGE_WITHDRAW_AMOUNT) {
        ensures(*global<LargeWithdrawEvent, bool>());
    };

    result
}