Icon Link测试断言

让我们再次回到我们的 MultiSig 项目中!

cd ../../multisig-predicate/predicate

Icon Link在 Rust 中生成测试模板

再次在 predicate 项目目录中使用 cargo-generate 执行以下步骤:

  1. 安装 cargo-generate
cargo install cargo-generate --locked
  1. 生成模板:
cargo generate --init fuellabs/sway templates/sway-test-rs --name sway-store

Icon Link导入

删除模板代码,并将以下导入复制到您的测试文件中。需要特别注意两个主要的导入:predicates,由于明显的原因,以及 ScriptTransactionBuilder,我们将用它来创建交易。这些交易必须在广播到本地网络之前进行签名。

use fuels::{
    crypto::SecretKey,
    accounts::{
        predicate::Predicate,
        wallet::WalletUnlocked,
        Account,
    },
    prelude::*,
    types::transaction_builders::{ScriptTransactionBuilder, BuildableTransaction},
};

与 Rust 测试合约类似,我们将导入断言 ABI(应用程序二进制接口)来与之进行交互。确保您的断言名称与您正在处理的名称相匹配。

abigen!(Predicate(
    name = "MultiSig",
    abi = "./out/debug/predicate-abi.json"
));

Icon Link设置

如果您熟悉 Rust 对 Sway 项目的测试,那么大部分设置将类似。将 setup_wallets_and_network 函数复制粘贴到您的测试文件中。

async fn setup_wallets_and_network() -> (Vec<WalletUnlocked>, Provider, AssetId) {
    // WALLETS
    let private_key_0: SecretKey =
        "0xc2620849458064e8f1eb2bc4c459f473695b443ac3134c82ddd4fd992bd138fd"
            .parse()
            .unwrap();
    let private_key_1: SecretKey =
        "0x37fa81c84ccd547c30c176b118d5cb892bdb113e8e80141f266519422ef9eefd"
            .parse()
            .unwrap();
    let private_key_2: SecretKey =
        "0x976e5c3fa620092c718d852ca703b6da9e3075b9f2ecb8ed42d9f746bf26aafb"
            .parse()
            .unwrap();
 
    let mut wallet_0: WalletUnlocked = WalletUnlocked::new_from_private_key(private_key_0, None);
    let mut wallet_1: WalletUnlocked = WalletUnlocked::new_from_private_key(private_key_1, None);
    let mut wallet_2: WalletUnlocked = WalletUnlocked::new_from_private_key(private_key_2, None);
 
    // TOKENS
    let asset_id = AssetId::default();
 
    let all_coins = [&wallet_0, &wallet_1, &wallet_2]
        .iter()
        .flat_map(|wallet| {
            setup_single_asset_coins(wallet.address(), AssetId::default(), 10, 1_000_000)
        })
        .collect::<Vec<_>>();
 
    // NETWORKS
    let node_config = NodeConfig::default();
 
    let provider = setup_test_provider(all_coins, vec![], Some(node_config), None).await.unwrap();
 
    [&mut wallet_0, &mut wallet_1, &mut wallet_2]
        .iter_mut()
        .for_each(|wallet| {
            wallet.set_provider(provider.clone());
        });
 
    return (
        vec![wallet_0, wallet_1, wallet_2],
        provider,
        asset_id,
    );
}

关键设置步骤包括:

  1. 配置将作为我们 MultiSig 的所有者的钱包,通过稍后在测试中看到的可配置项。
// WALLETS
let private_key_0: SecretKey =
    "0xc2620849458064e8f1eb2bc4c459f473695b443ac3134c82ddd4fd992bd138fd"
        .parse()
        .unwrap();
let private_key_1: SecretKey =
    "0x37fa81c84ccd547c30c176b118d5cb892bdb113e8e80141f266519422ef9eefd"
        .parse()
        .unwrap();
let private_key_2: SecretKey =
    "0x976e5c3fa620092c718d852ca703b6da9e3075b9f2ecb8ed42d9f746bf26aafb"
        .parse()
        .unwrap();
 
let mut wallet_0: WalletUnlocked = WalletUnlocked::new_from_private_key(private_key_0, None);
let mut wallet_1: WalletUnlocked = WalletUnlocked::new_from_private_key(private_key_1, None);
let mut wallet_2: WalletUnlocked = WalletUnlocked::new_from_private_key(private_key_2, None);
  1. 设置默认代币(零地址)并将一些代币加载到每个钱包中。
// TOKENS
let asset_id = AssetId::default();
 
let all_coins = [&wallet_0, &wallet_1, &wallet_2]
    .iter()
    .flat_map(|wallet| {
        setup_single_asset_coins(wallet.address(), AssetId::default(), 10, 1_000_000)
    })
    .collect::<Vec<_>>();
  1. 准备网络以广播我们的交易,从而成功解锁来自断言的代币。
// NETWORKS
let node_config = NodeConfig::default();
 
let provider = setup_test_provider(all_coins, vec![], Some(node_config), None).await.unwrap();

由于断言地址是确定性的,因此我们不需要像部署智能合约那样复制它,后者每次都使用不同的地址。我们可以利用 SDK 构建断言,确保我们正在使用正确的地址而不会出错!

Icon Link测试用例

Icon Link有效的 2 of 3 签名

现在,让我们回顾一下我们将采取的步骤序列,以模拟真实世界的情况,复制并粘贴以下第一个测试,并逐步进行解释:

#[tokio::test]
async fn multisig_two_of_three() -> Result<()> {
    let (wallets, provider, asset_id) = setup_wallets_and_network().await;
 
    // CONFIGURABLES
    let required_signatures = 2;
    let signers: [Address; 3] = [
        wallets[0].address().into(),
        wallets[1].address().into(),
        wallets[2].address().into(),
    ];
 
    let configurables = MultiSigConfigurables::default()
        .with_REQUIRED_SIGNATURES(required_signatures)?
        .with_SIGNERS(signers)?;
 
    // PREDICATE
    let predicate_binary_path = "./out/debug/predicate.bin";
    let predicate: Predicate = Predicate::load_from(predicate_binary_path)?
        .with_provider(provider.clone())
        .with_configurables(configurables);
    
    // FUND PREDICATE
    let multisig_amount = 100;
    let wallet_0_amount = provider.get_asset_balance(wallets[0].address(), asset_id).await?;
 
    wallets[0]
        .transfer(predicate.address(), multisig_amount, asset_id, TxPolicies::default())
        .await?;
 
    // BUILD TRANSACTION
    let mut tb: ScriptTransactionBuilder = {
        let input_coin = predicate.get_asset_inputs_for_amount(asset_id, 1).await?;
        let output_coin =
            predicate.get_asset_outputs_for_amount(wallets[0].address().into(), asset_id, multisig_amount);
 
        ScriptTransactionBuilder::prepare_transfer(
            input_coin,
            output_coin,
            TxPolicies::default(),
        )
    };
 
    // SIGN TRANSACTION
    tb.add_signer(wallets[0].clone())?;
    tb.add_signer(wallets[1].clone())?;
 
    assert_eq!(provider.get_asset_balance(predicate.address(), asset_id).await?, multisig_amount);
    assert_eq!(provider.get_asset_balance(wallets[0].address(), asset_id).await?, wallet_0_amount - multisig_amount);
 
    // SPEND PREDICATE
    let tx: ScriptTransaction = tb.build(provider.clone()).await?;
    provider.send_transaction_and_await_commit(tx).await?;
 
    assert_eq!(provider.get_asset_balance(predicate.address(), asset_id).await?, 0);
    assert_eq!(provider.get_asset_balance(wallets[0].address(), asset_id).await?, wallet_0_amount);
 
    Ok(())
}
  1. 一组或个人通过指定将保护资金的钱包来创建他们的 MultiSig。
  2. 向断言注资。
  3. 在需要时通过构建交易并让原始钱包签名来提取代币。
  4. 广播交易以解锁来自断言的资金。

对于第 1 步,如前所述,当我们配置所需签名数(最多 3 个)和将保护我们资金的 3 个地址时。导入 ABI 将自动加载一个 PredicateNameConfigurable 类型。在我们的情况下,将加载相应的 with_configurable 函数来帮助您加载每个可配置项。在我们的情况下,with_REQUIRED_SIGNATURESwith_SIGNERS 都已加载!

多么方便啊!

// CONFIGURABLES
let required_signatures = 2;
let signers: [Address; 3] = [
    wallets[0].address().into(),
    wallets[1].address().into(),
    wallets[2].address().into(),
];
 
let configurables = MultiSigConfigurables::default()
    .with_REQUIRED_SIGNATURES(required_signatures)?
    .with_SIGNERS(signers)?;

接下来,我们将使用我们的新配置加载我们的原始断言二进制文件,以生成我们的个性化断言实例。只需使用 with_configurables 函数输入您的配置项即可,这将为我们提供基于我们的输入的唯一断言根。

// PREDICATE
let predicate_binary_path = "./out/debug/predicate.bin";
let predicate: Predicate = Predicate::load_from(predicate_binary_path)?
    .with_provider(provider.clone())
    .with_configurables(configurables);

对于第 2 步,向我们新生成的断言根转移资金与任何其他区块链转移一样简单。

// FUND PREDICATE
let multisig_amount = 100;
let wallet_0_amount = provider.get_asset_balance(wallets[0].address(), asset_id).await?;
 
wallets[0]
    .transfer(predicate.address(), multisig_amount, asset_id, TxPolicies::default())
    .await?;

在第 3 步中,当 MultiSig 持有者决定使用锁定的资金时,我们构建一个交易来指定输入和输出。请特别注意输出;我们需要指定来自断言的代币去向、它们涉及的本机资产以及金额。我们实质上是从原始基础资产中提取一部分到断言。

// BUILD TRANSACTION
let mut tb: ScriptTransactionBuilder = {
    let input_coin = predicate.get_asset_inputs_for_amount(asset_id, 1).await?;
    let output_coin =
        predicate.get_asset_outputs_for_amount(wallets[0].address().into(), asset_id, multisig_amount);
 
    ScriptTransactionBuilder::prepare_transfer(
        input_coin,
        output_coin,
        TxPolicies::default(),
    )
};

配置中配置的正确钱包地址必须签署交易。这些信息作为见证数据加载,将评估我们的断言为真。提供足够的正确、唯一的签名非常重要;否则,交易将失败,如后续测试所示。由于我们的测试仅需要 2 个签名,因此我们只需要提供这些签名。

// SIGN TRANSACTION
tb.add_signer(wallets[0].clone())?;
tb.add_signer(wallets[1].clone())?;

在确认评估正确后,我们需要做的就是广播交易,请求的资金应该返回到钱包 1。

// SPEND PREDICATE
let tx: ScriptTransaction = tb.build(provider.clone()).await?;
provider.send_transaction_and_await_commit(tx).await?;

Icon Link无序的有效 3 of 3 签名

第二个测试 multisig_mixed_three_of_three 的设置遵循相同的方案,展示了交易签名可以由有效的钱包以任何顺序完成。

#[tokio::test]
async fn multisig_mixed_three_of_three() -> Result<()> {
    let (wallets, provider, asset_id) = setup_wallets_and_network().await;
 
    // CONFIGURABLES
    let required_signatures = 3;
    let signers: [Address; 3] = [
        wallets[0].address().into(),
        wallets[1].address().into(),
        wallets[2].address().into(),
    ];
 
    let configurables = MultiSigConfigurables::default()
        .with_REQUIRED_SIGNATURES(required_signatures)?
        .with_SIGNERS(signers)?;
 
    // PREDICATE
    let predicate_binary_path = "./out/debug/predicate.bin";
    let predicate: Predicate = Predicate::load_from(predicate_binary_path)?
        .with_provider(provider.clone())
        .with_configurables(configurables);
 
    let multisig_amount = 100;
    let wallet_0_amount = provider.get_asset_balance(wallets[0].address(), asset_id).await?;
 
    wallets[0]
        .transfer(predicate.address(), multisig_amount, asset_id, TxPolicies::default())
        .await?;
 
    let mut tb: ScriptTransactionBuilder = {
        let input_coin = predicate.get_asset_inputs_for_amount(asset_id, 1).await?;
 
        let output_coin =
            predicate.get_asset_outputs_for_amount(wallets[0].address().into(), asset_id, multisig_amount);
 
        ScriptTransactionBuilder::prepare_transfer(
            input_coin,
            output_coin,
            TxPolicies::default(),
        )
    };
 
    // NOTE Cannot be signed in any order
    tb.add_signer(wallets[2].clone())?;
    tb.add_signer(wallets[0].clone())?;
    tb.add_signer(wallets[1].clone())?;
 
    assert_eq!(provider.get_asset_balance(predicate.address(), asset_id).await?, multisig_amount);
    assert_eq!(provider.get_asset_balance(wallets[0].address(), asset_id).await?, wallet_0_amount - multisig_amount);
 
    // SPEND PREDICATE
    let tx: ScriptTransaction = tb.build(provider.clone()).await?;
    provider.send_transaction_and_await_commit(tx).await?;
 
    assert_eq!(provider.get_asset_balance(predicate.address(), asset_id).await?, 0);
    assert_eq!(provider.get_asset_balance(wallets[0].address(), asset_id).await?, wallet_0_amount);
 
    Ok(())
}

Icon Link不足的有效签名

相同的原则适用于第三个测试 multisig_not_enough_signatures_fails,如果签名不足,则交易将失败。

#[tokio::test]
async fn multisig_not_enough_signatures_fails() -> Result<()> {
    let (wallets, provider, asset_id) = setup_wallets_and_network().await;
 
    // CONFIGURABLES
    let required_signatures = 2;
    let signers: [Address; 3] = [
        wallets[0].address().into(),
        wallets[1].address().into(),
        wallets[2].address().into(),
    ];
 
    let configurables = MultiSigConfigurables::default()
        .with_REQUIRED_SIGNATURES(required_signatures)?
        .with_SIGNERS(signers)?;
 
    // PREDICATE
    let predicate_binary_path = "./out/debug/predicate.bin";
    let predicate: Predicate = Predicate::load_from(predicate_binary_path)?
        .with_provider(provider.clone())
        .with_configurables(configurables);
 
    let multisig_amount = 100;
    let wallet_0_amount = provider.get_asset_balance(wallets[0].address(), asset_id).await?;
 
    wallets[0]
        .transfer(predicate.address(), multisig_amount, asset_id, TxPolicies::default())
        .await?;
 
    let mut tb: ScriptTransactionBuilder = {
        let input_coin = predicate.get_asset_inputs_for_amount(asset_id, 1).await?;
 
        let output_coin =
            predicate.get_asset_outputs_for_amount(wallets[0].address().into(), asset_id, multisig_amount);
 
        ScriptTransactionBuilder::prepare_transfer(
            input_coin,
            output_coin,
            TxPolicies::default(),
        )
    };
 
    tb.add_signer(wallets[0].clone())?;
 
    assert_eq!(provider.get_asset_balance(predicate.address(), asset_id).await?, multisig_amount);
    assert_eq!(provider.get_asset_balance(wallets[0].address(), asset_id).await?, wallet_0_amount - multisig_amount);
 
    // SPEND PREDICATE
    let tx: ScriptTransaction = tb.build(provider.clone()).await?;
    let _ = provider.send_transaction_and_await_commit(tx).await.is_err();
 
    Ok(())
}

Icon Link检查点

如果您正确地按照先前的步骤操作,您的 harness.rs 测试文件应如下所示:

use fuels::{
    crypto::SecretKey,
    accounts::{
        predicate::Predicate,
        wallet::WalletUnlocked,
        Account,
    },
    prelude::*,
    types::transaction_builders::{ScriptTransactionBuilder, BuildableTransaction},
};
 
abigen!(Predicate(
    name = "MultiSig",
    abi = "./out/debug/predicate-abi.json"
));
 
async fn setup_wallets_and_network() -> (Vec<WalletUnlocked>, Provider, AssetId) {
    // WALLETS
    let private_key_0: SecretKey =
        "0xc2620849458064e8f1eb2bc4c459f473695b443ac3134c82ddd4fd992bd138fd"
            .parse()
            .unwrap();
    let private_key_1: SecretKey =
        "0x37fa81c84ccd547c30c176b118d5cb892bdb113e8e80141f266519422ef9eefd"
            .parse()
            .unwrap();
    let private_key_2: SecretKey =
        "0x976e5c3fa620092c718d852ca703b6da9e3075b9f2ecb8ed42d9f746bf26aafb"
            .parse()
            .unwrap();
 
    let mut wallet_0: WalletUnlocked = WalletUnlocked::new_from_private_key(private_key_0, None);
    let mut wallet_1: WalletUnlocked = WalletUnlocked::new_from_private_key(private_key_1, None);
    let mut wallet_2: WalletUnlocked = WalletUnlocked::new_from_private_key(private_key_2, None);
 
    // TOKENS
    let asset_id = AssetId::default();
 
    let all_coins = [&wallet_0, &wallet_1, &wallet_2]
        .iter()
        .flat_map(|wallet| {
            setup_single_asset_coins(wallet.address(), AssetId::default(), 10, 1_000_000)
        })
        .collect::<Vec<_>>();
 
    // NETWORKS
    let node_config = NodeConfig::default();
 
    let provider = setup_test_provider(all_coins, vec![], Some(node_config), None).await.unwrap();
 
    [&mut wallet_0, &mut wallet_1, &mut wallet_2]
        .iter_mut()
        .for_each(|wallet| {
            wallet.set_provider(provider.clone());
        });
 
    return (
        vec![wallet_0, wallet_1, wallet_2],
        provider,
        asset_id,
    );
}
 
#[tokio::test]
async fn multisig_two_of_three() -> Result<()> {
    let (wallets, provider, asset_id) = setup_wallets_and_network().await;
 
    // CONFIGURABLES
    let required_signatures = 2;
    let signers: [Address; 3] = [
        wallets[0].address().into(),
        wallets[1].address().into(),
        wallets[2].address().into(),
    ];
 
    let configurables = MultiSigConfigurables::default()
        .with_REQUIRED_SIGNATURES(required_signatures)?
        .with_SIGNERS(signers)?;
 
    // PREDICATE
    let predicate_binary_path = "./out/debug/predicate.bin";
    let predicate: Predicate = Predicate::load_from(predicate_binary_path)?
        .with_provider(provider.clone())
        .with_configurables(configurables);
    
    // FUND PREDICATE
    let multisig_amount = 100;
    let wallet_0_amount = provider.get_asset_balance(wallets[0].address(), asset_id).await?;
 
    wallets[0]
        .transfer(predicate.address(), multisig_amount, asset_id, TxPolicies::default())
        .await?;
 
    // BUILD TRANSACTION
    let mut tb: ScriptTransactionBuilder = {
        let input_coin = predicate.get_asset_inputs_for_amount(asset_id, 1).await?;
        let output_coin =
            predicate.get_asset_outputs_for_amount(wallets[0].address().into(), asset_id, multisig_amount);
 
        ScriptTransactionBuilder::prepare_transfer(
            input_coin,
            output_coin,
            TxPolicies::default(),
        )
    };
 
    // SIGN TRANSACTION
    tb.add_signer(wallets[0].clone())?;
    tb.add_signer(wallets[1].clone())?;
 
    assert_eq!(provider.get_asset_balance(predicate.address(), asset_id).await?, multisig_amount);
    assert_eq!(provider.get_asset_balance(wallets[0].address(), asset_id).await?, wallet_0_amount - multisig_amount);
 
    // SPEND PREDICATE
    let tx: ScriptTransaction = tb.build(provider.clone()).await?;
    provider.send_transaction_and_await_commit(tx).await?;
 
    assert_eq!(provider.get_asset_balance(predicate.address(), asset_id).await?, 0);
    assert_eq!(provider.get_asset_balance(wallets[0].address(), asset_id).await?, wallet_0_amount);
 
    Ok(())
}
 
#[tokio::test]
async fn multisig_mixed_three_of_three() -> Result<()> {
    let (wallets, provider, asset_id) = setup_wallets_and_network().await;
 
    // CONFIGURABLES
    let required_signatures = 3;
    let signers: [Address; 3] = [
        wallets[0].address().into(),
        wallets[1].address().into(),
        wallets[2].address().into(),
    ];
 
    let configurables = MultiSigConfigurables::default()
        .with_REQUIRED_SIGNATURES(required_signatures)?
        .with_SIGNERS(signers)?;
 
    // PREDICATE
    let predicate_binary_path = "./out/debug/predicate.bin";
    let predicate: Predicate = Predicate::load_from(predicate_binary_path)?
        .with_provider(provider.clone())
        .with_configurables(configurables);
 
    let multisig_amount = 100;
    let wallet_0_amount = provider.get_asset_balance(wallets[0].address(), asset_id).await?;
 
    wallets[0]
        .transfer(predicate.address(), multisig_amount, asset_id, TxPolicies::default())
        .await?;
 
    let mut tb: ScriptTransactionBuilder = {
        let input_coin = predicate.get_asset_inputs_for_amount(asset_id, 1).await?;
 
        let output_coin =
            predicate.get_asset_outputs_for_amount(wallets[0].address().into(), asset_id, multisig_amount);
 
        ScriptTransactionBuilder::prepare_transfer(
            input_coin,
            output_coin,
            TxPolicies::default(),
        )
    };
 
    // NOTE Cannot be signed in any order
    tb.add_signer(wallets[2].clone())?;
    tb.add_signer(wallets[0].clone())?;
    tb.add_signer(wallets[1].clone())?;
 
    assert_eq!(provider.get_asset_balance(predicate.address(), asset_id).await?, multisig_amount);
    assert_eq!(provider.get_asset_balance(wallets[0].address(), asset_id).await?, wallet_0_amount - multisig_amount);
 
    // SPEND PREDICATE
    let tx: ScriptTransaction = tb.build(provider.clone()).await?;
    provider.send_transaction_and_await_commit(tx).await?;
 
    assert_eq!(provider.get_asset_balance(predicate.address(), asset_id).await?, 0);
    assert_eq!(provider.get_asset_balance(wallets[0].address(), asset_id).await?, wallet_0_amount);
 
    Ok(())
}
 
#[tokio::test]
async fn multisig_not_enough_signatures_fails() -> Result<()> {
    let (wallets, provider, asset_id) = setup_wallets_and_network().await;
 
    // CONFIGURABLES
    let required_signatures = 2;
    let signers: [Address; 3] = [
        wallets[0].address().into(),
        wallets[1].address().into(),
        wallets[2].address().into(),
    ];
 
    let configurables = MultiSigConfigurables::default()
        .with_REQUIRED_SIGNATURES(required_signatures)?
        .with_SIGNERS(signers)?;
 
    // PREDICATE
    let predicate_binary_path = "./out/debug/predicate.bin";
    let predicate: Predicate = Predicate::load_from(predicate_binary_path)?
        .with_provider(provider.clone())
        .with_configurables(configurables);
 
    let multisig_amount = 100;
    let wallet_0_amount = provider.get_asset_balance(wallets[0].address(), asset_id).await?;
 
    wallets[0]
        .transfer(predicate.address(), multisig_amount, asset_id, TxPolicies::default())
        .await?;
 
    let mut tb: ScriptTransactionBuilder = {
        let input_coin = predicate.get_asset_inputs_for_amount(asset_id, 1).await?;
 
        let output_coin =
            predicate.get_asset_outputs_for_amount(wallets[0].address().into(), asset_id, multisig_amount);
 
        ScriptTransactionBuilder::prepare_transfer(
            input_coin,
            output_coin,
            TxPolicies::default(),
        )
    };
 
    tb.add_signer(wallets[0].clone())?;
 
    assert_eq!(provider.get_asset_balance(predicate.address(), asset_id).await?, multisig_amount);
    assert_eq!(provider.get_asset_balance(wallets[0].address(), asset_id).await?, wallet_0_amount - multisig_amount);
 
    // SPEND PREDICATE
    let tx: ScriptTransaction = tb.build(provider.clone()).await?;
    let _ = provider.send_transaction_and_await_commit(tx).await.is_err();
 
    Ok(())
}

Icon Link运行测试

要运行位于 tests/harness.rs 中的测试,请使用:

cargo test

如果您想在测试期间将输出打印到控制台,请使用 nocapture 标志:

cargo test -- --nocapture

祝贺您取得了如此进展!我们已确认我们的 MultiSig 可以正常工作。

断言不应该让人望而却步。状态最小化的 DeFi 应用应该是标准,而不是通过气体竞赛或编写汇编代码来进行这些优化。现在您的工具箱中有了断言,请继续探索您可以构建的其他状态最小化的 DeFi 应用!