Icon Link测试合约

Icon Link生成 Rust 中的测试模板

要在合约项目目录中使用 cargo-generate 创建自己的测试模板,请按照以下步骤进行操作:

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

我们有两个新文件!

  • Cargo.toml 是我们新测试套件的清单,指定了所需的依赖项,包括 fuels(Fuel Rust SDK)。
  • tests/harness.rs 包含一些样板测试代码,让我们开始,尽管目前还没有调用任何合约方法。

打开您的 Cargo.toml 文件并检查在 dev-dependencies 下使用的 fuels 的版本。如果尚未安装,请将版本更改为 0.62.0

[dev-dependencies]
fuels = { version = "0.62.0", features = ["fuel-core-lib"] }
tokio = { version = "1.12", features = ["rt", "macros"] }

Icon Link导入

我们将更改已生成的现有 harness.rs 测试文件。首先,我们需要更改导入。通过导入 Fuel Rust SDK,您将获得大部分功能都包含在预导中。

use fuels::{prelude::*, types::{Identity, SizedAsciiString}};

在进行任何更改后始终编译您的合约。这样可以确保您使用的是最新生成的 contract-abi

abigen 宏中更新您的合约名称和 ABI 路径,以匹配您合约的名称:

// Load abi from json
abigen!(Contract(name="SwayStore", abi="out/debug/contract-abi.json"));

Icon Link初始化函数

在为 Sway 编写测试时,需要两个关键对象:合约实例和与之交互的钱包。此辅助函数确保每个新测试案例都有一个新的起点,因此将此函数复制到您的测试文件中。它将为此目的导出已部署的合约、合约 ID 和所有生成的钱包。

将您测试用例中的 get_contract_instance 函数替换为以下函数:

async fn get_contract_instance() -> (SwayStore<WalletUnlocked>, ContractId, Vec<WalletUnlocked>) {
    // Launch a local network and deploy the contract
    let wallets = launch_custom_provider_and_get_wallets(
        WalletsConfig::new(
            Some(3),             /* Three wallets */
            Some(1),             /* Single coin (UTXO) */
            Some(1_000_000_000), /* Amount per coin */
        ),
        None,
        None,
    )
    .await
    .unwrap();
 
    let wallet = wallets.get(0).unwrap().clone();
    
    let id = Contract::load_from(
        "./out/debug/contract.bin",
        LoadConfiguration::default(),
    )
    .unwrap()
    .deploy(&wallet, TxPolicies::default())
    .await
    .unwrap();
 
    let instance = SwayStore::new(id.clone(), wallet);
 
    (instance, id.into(), wallets)
}

Icon Link测试用例

考虑到智能合约的不可变性,重要的是在测试中覆盖所有潜在的边缘情况。 让我们删除示例 can_get_contract_id 测试用例,并开始在 harness.rs 文件底部编写一些测试用例。

Icon Link设置所有者

对于此测试用例,我们使用合约实例并使用 SDK 的 .with_account() 方法。这使我们能够冒充第一个钱包。要检查所有者是否已正确设置,我们可以查看合约给出的地址是否与钱包 1 的地址匹配。如果您想深入了解,查看合约存储将显示钱包 1 的地址是否已正确存储。

#[tokio::test]
async fn can_set_owner() {
    let (instance, _id, wallets) = get_contract_instance().await;
 
    // get access to a test wallet
    let wallet_1 = wallets.get(0).unwrap();
 
    // initialize wallet_1 as the owner
    let owner_result = instance
        .with_account(wallet_1.clone())
        .methods()
        .initialize_owner()
        .call()
        .await
        .unwrap();
 
    // make sure the returned identity matches wallet_1
    assert!(Identity::Address(wallet_1.address().into()) == owner_result.value);
}

Icon Link仅设置一次所有者

我们需要警惕的一个边缘情况是尝试两次设置所有者。我们绝对不希望未经授权的合同所有权转移!为了解决这个问题,我们在 Sway 合同中包含了以下行:require(owner.is_none(), "owner already initialized"); 这确保只有在之前未建立所有者时才能设置所有者。为了测试这一点,我们创建一个新的合同实例:最初,我们使用钱包 1 设置所有者。使用钱包 2 进行任何后续尝试设置所有者都应该不成功。

#[tokio::test]
#[should_panic]
async fn can_set_owner_only_once() {
    let (instance, _id, wallets) = get_contract_instance().await;
 
    // get access to some test wallets
    let wallet_1 = wallets.get(0).unwrap();
    let wallet_2 = wallets.get(1).unwrap();
 
    // initialize wallet_1 as the owner
    let _owner_result = instance.clone()
        .with_account(wallet_1.clone())
        .methods()
        .initialize_owner()
        .call()
        .await
        .unwrap();
 
    // this should fail
    // try to set the owner from wallet_2
    let _fail_owner_result = instance.clone()
        .with_account(wallet_2.clone())
        .methods()
        .initialize_owner()
        .call()
        .await
        .unwrap();
}

Icon Link在市场上买卖

测试智能合约的基本功能是至关重要的,以确保其正常运行。 对于此测试,我们设置了两个钱包:

  1. 第一个钱包发起交易以列出待售物品。这是通过调用 .list_item() 方法完成的,指定要出售的物品的价格和详细信息。
  2. 第二个钱包继续购买列出的物品,使用 .buy_item() 方法,提供他们打算购买的物品的索引。

在执行这些交易后,我们将评估两个钱包的余额,以确认交易成功执行。

#[tokio::test]
async fn can_list_and_buy_item() {
    let (instance, _id, wallets) = get_contract_instance().await;
    // Now you have an instance of your contract you can use to test each function
 
    // get access to some test wallets
    let wallet_1 = wallets.get(0).unwrap();
    let wallet_2 = wallets.get(1).unwrap();
 
    // item 1 params
    let item_1_metadata: SizedAsciiString<20> = "metadata__url__here_"
        .try_into()
        .expect("Should have succeeded");
    let item_1_price: u64 = 15;
 
    // list item 1 from wallet_1
    let _item_1_result = instance.clone()
        .with_account(wallet_1.clone())
        .methods()
        .list_item(item_1_price, item_1_metadata)
        .call()
        .await
        .unwrap();
 
    // call params to send the project price in the buy_item fn
    let call_params = CallParameters::default().with_amount(item_1_price);
 
    // buy item 1 from wallet_2
    let _item_1_purchase = instance.clone()
        .with_account(wallet_2.clone())
        .methods()
        .buy_item(1)
        .append_variable_outputs(1)
        .call_params(call_params)
        .unwrap()
        .call()
        .await
        .unwrap();
 
    // check the balances of wallet_1 and wallet_2
    let balance_1: u64 = wallet_1.get_asset_balance(&AssetId::zeroed()).await.unwrap();
    let balance_2: u64 = wallet_2.get_asset_balance(&AssetId::zeroed()).await.unwrap();
 
    // make sure the price was transferred from wallet_2 to wallet_1
    assert!(balance_1 == 1000000015);
    assert!(balance_2 == 999999985);
 
    let item_1 = instance.methods().get_item(1).call().await.unwrap();
 
    assert!(item_1.value.price == item_1_price);
    assert!(item_1.value.id == 1);
    assert!(item_1.value.total_bought == 1);
}

Icon Link提取所有者费用

最重要的是,作为市场的创建者,您需要确保自己得到了补偿。与之前的测试类似,我们将调用相关函数进行交换。这次,我们将验证是否可以提取资金差额。

#[tokio::test]
async fn can_withdraw_funds() {
    let (instance, _id, wallets) = get_contract_instance().await;
    // Now you have an instance of your contract you can use to test each function
 
    // get access to some test wallets
    let wallet_1 = wallets.get(0).unwrap();
    let wallet_2 = wallets.get(1).unwrap();
    let wallet_3 = wallets.get(2).unwrap();
 
    // initialize wallet_1 as the owner
    let owner_result = instance.clone()
        .with_account(wallet_1.clone())
        .methods()
        .initialize_owner()
        .call()
        .await
        .unwrap();
 
    // make sure the returned identity matches wallet_1
    assert!(Identity::Address(wallet_1.address().into()) == owner_result.value);
 
    // item 1 params
    let item_1_metadata: SizedAsciiString<20> = "metadata__url__here_"
        .try_into()
        .expect("Should have succeeded");
    let item_1_price: u64 = 150_000_000;
 
    // list item 1 from wallet_2
    let item_1_result = instance.clone()
        .with_account(wallet_2.clone())
        .methods()
        .list_item(item_1_price, item_1_metadata)
        .call()
        .await;
    assert!(item_1_result.is_ok());
 
    // make sure the item count increased
    let count = instance.clone()
        .methods()
        .get_count()
        .simulate()
        .await
        .unwrap();
    assert_eq!(count.value, 1);
 
    // call params to send the project price in the buy_item fn
    let call_params = CallParameters::default().with_amount(item_1_price);
    
    // buy item 1 from wallet_3
    let item_1_purchase = instance.clone()
        .with_account(wallet_3.clone())
        .methods()
        .buy_item(1)
        .append_variable_outputs(1)
        .call_params(call_params)
        .unwrap()
        .call()
        .await;
    assert!(item_1_purchase.is_ok());
 
     // make sure the item's total_bought count increased
     let listed_item = instance
     .methods()
     .get_item(1)
     .simulate()
     .await
     .unwrap();
 assert_eq!(listed_item.value.total_bought, 1);
 
    // withdraw the balance from the owner's wallet
    let withdraw = instance
        .with_account(wallet_1.clone())
        .methods()
        .withdraw_funds()
        .append_variable_outputs(1)
        .call()
        .await;
    assert!(withdraw.is_ok());
 
    // check the balances of wallet_1 and wallet_2
    let balance_1: u64 = wallet_1.get_asset_balance(&AssetId::zeroed()).await.unwrap();
    let balance_2: u64 = wallet_2.get_asset_balance(&AssetId::zeroed()).await.unwrap();
    let balance_3: u64 = wallet_3.get_asset_balance(&AssetId::zeroed()).await.unwrap();
 
    assert!(balance_1 == 1007500000);
    assert!(balance_2 == 1142500000);
    assert!(balance_3 == 850000000);
}

Icon Link检查点

如果您已正确执行之前的步骤,则您的 harness.rs 测试文件应如下所示:

use fuels::{prelude::*, types::{Identity, SizedAsciiString}};
 
// Load abi from json
abigen!(Contract(name="SwayStore", abi="out/debug/contract-abi.json"));
 
async fn get_contract_instance() -> (SwayStore<WalletUnlocked>, ContractId, Vec<WalletUnlocked>) {
    // Launch a local network and deploy the contract
    let wallets = launch_custom_provider_and_get_wallets(
        WalletsConfig::new(
            Some(3),             /* Three wallets */
            Some(1),             /* Single coin (UTXO) */
            Some(1_000_000_000), /* Amount per coin */
        ),
        None,
        None,
    )
    .await
    .unwrap();
 
    let wallet = wallets.get(0).unwrap().clone();
    
    let id = Contract::load_from(
        "./out/debug/contract.bin",
        LoadConfiguration::default(),
    )
    .unwrap()
    .deploy(&wallet, TxPolicies::default())
    .await
    .unwrap();
 
    let instance = SwayStore::new(id.clone(), wallet);
 
    (instance, id.into(), wallets)
}
 
#[tokio::test]
async fn can_set_owner() {
    let (instance, _id, wallets) = get_contract_instance().await;
 
    // get access to a test wallet
    let wallet_1 = wallets.get(0).unwrap();
 
    // initialize wallet_1 as the owner
    let owner_result = instance
        .with_account(wallet_1.clone())
        .methods()
        .initialize_owner()
        .call()
        .await
        .unwrap();
 
    // make sure the returned identity matches wallet_1
    assert!(Identity::Address(wallet_1.address().into()) == owner_result.value);
}
 
#[tokio::test]
#[should_panic]
async fn can_set_owner_only_once() {
    let (instance, _id, wallets) = get_contract_instance().await;
 
    // get access to some test wallets
    let wallet_1 = wallets.get(0).unwrap();
    let wallet_2 = wallets.get(1).unwrap();
 
    // initialize wallet_1 as the owner
    let _owner_result = instance.clone()
        .with_account(wallet_1.clone())
        .methods()
        .initialize_owner()
        .call()
        .await
        .unwrap();
 
    // this should fail
    // try to set the owner from wallet_2
    let _fail_owner_result = instance.clone()
        .with_account(wallet_2.clone())
        .methods()
        .initialize_owner()
        .call()
        .await
        .unwrap();
}
 
#[tokio::test]
async fn can_list_and_buy_item() {
    let (instance, _id, wallets) = get_contract_instance().await;
    // Now you have an instance of your contract you can use to test each function
 
    // get access to some test wallets
    let wallet_1 = wallets.get(0).unwrap();
    let wallet_2 = wallets.get(1).unwrap();
 
    // item 1 params
    let item_1_metadata: SizedAsciiString<20> = "metadata__url__here_"
        .try_into()
        .expect("Should have succeeded");
    let item_1_price: u64 = 15;
 
    // list item 1 from wallet_1
    let _item_1_result = instance.clone()
        .with_account(wallet_1.clone())
        .methods()
        .list_item(item_1_price, item_1_metadata)
        .call()
        .await
        .unwrap();
 
    // call params to send the project price in the buy_item fn
    let call_params = CallParameters::default().with_amount(item_1_price);
 
    // buy item 1 from wallet_2
    let _item_1_purchase = instance.clone()
        .with_account(wallet_2.clone())
        .methods()
        .buy_item(1)
        .append_variable_outputs(1)
        .call_params(call_params)
        .unwrap()
        .call()
        .await
        .unwrap();
 
    // check the balances of wallet_1 and wallet_2
    let balance_1: u64 = wallet_1.get_asset_balance(&AssetId::zeroed()).await.unwrap();
    let balance_2: u64 = wallet_2.get_asset_balance(&AssetId::zeroed()).await.unwrap();
 
    // make sure the price was transferred from wallet_2 to wallet_1
    assert!(balance_1 == 1000000015);
    assert!(balance_2 == 999999985);
 
    let item_1 = instance.methods().get_item(1).call().await.unwrap();
 
    assert!(item_1.value.price == item_1_price);
    assert!(item_1.value.id == 1);
    assert!(item_1.value.total_bought == 1);
}
 
#[tokio::test]
async fn can_withdraw_funds() {
    let (instance, _id, wallets) = get_contract_instance().await;
    // Now you have an instance of your contract you can use to test each function
 
    // get access to some test wallets
    let wallet_1 = wallets.get(0).unwrap();
    let wallet_2 = wallets.get(1).unwrap();
    let wallet_3 = wallets.get(2).unwrap();
 
    // initialize wallet_1 as the owner
    let owner_result = instance.clone()
        .with_account(wallet_1.clone())
        .methods()
        .initialize_owner()
        .call()
        .await
        .unwrap();
 
    // make sure the returned identity matches wallet_1
    assert!(Identity::Address(wallet_1.address().into()) == owner_result.value);
 
    // item 1 params
    let item_1_metadata: SizedAsciiString<20> = "metadata__url__here_"
        .try_into()
        .expect("Should have succeeded");
    let item_1_price: u64 = 150_000_000;
 
    // list item 1 from wallet_2
    let item_1_result = instance.clone()
        .with_account(wallet_2.clone())
        .methods()
        .list_item(item_1_price, item_1_metadata)
        .call()
        .await;
    assert!(item_1_result.is_ok());
 
    // make sure the item count increased
    let count = instance.clone()
        .methods()
        .get_count()
        .simulate()
        .await
        .unwrap();
    assert_eq!(count.value, 1);
 
    // call params to send the project price in the buy_item fn
    let call_params = CallParameters::default().with_amount(item_1_price);
    
    // buy item 1 from wallet_3
    let item_1_purchase = instance.clone()
        .with_account(wallet_3.clone())
        .methods()
        .buy_item(1)
        .append_variable_outputs(1)
        .call_params(call_params)
        .unwrap()
        .call()
        .await;
    assert!(item_1_purchase.is_ok());
 
     // make sure the item's total_bought count increased
     let listed_item = instance
     .methods()
     .get_item(1)
     .simulate()
     .await
     .unwrap();
 assert_eq!(listed_item.value.total_bought, 1);
 
    // withdraw the balance from the owner's wallet
    let withdraw = instance
        .with_account(wallet_1.clone())
        .methods()
        .withdraw_funds()
        .append_variable_outputs(1)
        .call()
        .await;
    assert!(withdraw.is_ok());
 
    // check the balances of wallet_1 and wallet_2
    let balance_1: u64 = wallet_1.get_asset_balance(&AssetId::zeroed()).await.unwrap();
    let balance_2: u64 = wallet_2.get_asset_balance(&AssetId::zeroed()).await.unwrap();
    let balance_3: u64 = wallet_3.get_asset_balance(&AssetId::zeroed()).await.unwrap();
 
    assert!(balance_1 == 1007500000);
    assert!(balance_2 == 1142500000);
    assert!(balance_3 == 850000000);
}

Icon Link运行测试

要运行位于 tests/harness.rs 中的测试,请在您的 contract 文件夹内运行以下命令:

cargo test

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

cargo test -- --nocapture

既然我们对智能合约的功能感到自信,现在是时候构建前端了。这将允许用户与我们的新市场无缝交互!