Icon LinkTesting the contract

You can find the full contract code, along with example tests using the Rust SDK, in the provided repository.

Icon LinkGenerating a Test Template in Rust

To create your own test template using Rust, follow these steps with cargo-generate:

  1. Install cargo-generate:
cargo install cargo-generate
  1. Generate the template:
cargo generate --init fuellabs/sway templates/sway-test-rs --name contract

Icon LinkRunning the Tests

For running tests located in harness.rs, use:

cargo test

If you wish to print outputs to the console during tests, execute:

cargo test -- --nocapture

Icon LinkImports

At the beginning of the harness.rs file, import the Fuel Rust SDK. The majority of the functionalities you'll need are housed within the prelude.

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;
 
    let wallet = wallets.get(0).unwrap().clone();
 
    // let storage_config =
    // StorageConfiguration::load_from("out/debug/contract-storage_slots.json").unwrap();
 
    // let load_config = LoadConfiguration::default().with_storage_configuration(storage_config);
 
    let id = Contract::load_from(
        "./out/debug/contract.bin",
        LoadConfiguration::default(),
    )
    .unwrap()
    .deploy(&wallet, TxParameters::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())
        .unwrap()
        .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
        .with_account(wallet_1.clone())
        .unwrap()
        .methods()
        .initialize_owner()
        .call()
        .await
        .unwrap();
 
    // this should fail
    // try to set the owner from wallet_2
    let _fail_owner_result = instance
        .with_account(wallet_2.clone())
        .unwrap()
        .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
        .with_account(wallet_1.clone())
        .unwrap()
        .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
        .with_account(wallet_2.clone())
        .unwrap()
        .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(&BASE_ASSET_ID).await.unwrap();
    let balance_2: u64 = wallet_2.get_asset_balance(&BASE_ASSET_ID).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
        .with_account(wallet_1.clone())
        .unwrap()
        .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
        .with_account(wallet_2.clone())
        .unwrap()
        .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
        .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
        .with_account(wallet_3.clone())
        .unwrap()
        .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())
        .unwrap()
        .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(&BASE_ASSET_ID).await.unwrap();
    let balance_2: u64 = wallet_2.get_asset_balance(&BASE_ASSET_ID).await.unwrap();
    let balance_3: u64 = wallet_3.get_asset_balance(&BASE_ASSET_ID).await.unwrap();
 
    // println!("BALANCE 1: {:?}", balance_1);
    assert!(balance_1 == 1007500000);
    // println!("BALANCE 2: {:?}", balance_2);
    assert!(balance_2 == 1142500000);
    // println!("BALANCE 3: {:?}", balance_3);
    assert!(balance_3 == 850000000);
}

Always compile your contracts after making any changes. This ensures you're working with the most recent contract-abi that gets generated.

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;
 
    let wallet = wallets.get(0).unwrap().clone();
 
    // let storage_config =
    // StorageConfiguration::load_from("out/debug/contract-storage_slots.json").unwrap();
 
    // let load_config = LoadConfiguration::default().with_storage_configuration(storage_config);
 
    let id = Contract::load_from(
        "./out/debug/contract.bin",
        LoadConfiguration::default(),
    )
    .unwrap()
    .deploy(&wallet, TxParameters::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())
        .unwrap()
        .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
        .with_account(wallet_1.clone())
        .unwrap()
        .methods()
        .initialize_owner()
        .call()
        .await
        .unwrap();
 
    // this should fail
    // try to set the owner from wallet_2
    let _fail_owner_result = instance
        .with_account(wallet_2.clone())
        .unwrap()
        .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
        .with_account(wallet_1.clone())
        .unwrap()
        .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
        .with_account(wallet_2.clone())
        .unwrap()
        .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(&BASE_ASSET_ID).await.unwrap();
    let balance_2: u64 = wallet_2.get_asset_balance(&BASE_ASSET_ID).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
        .with_account(wallet_1.clone())
        .unwrap()
        .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
        .with_account(wallet_2.clone())
        .unwrap()
        .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
        .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
        .with_account(wallet_3.clone())
        .unwrap()
        .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())
        .unwrap()
        .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(&BASE_ASSET_ID).await.unwrap();
    let balance_2: u64 = wallet_2.get_asset_balance(&BASE_ASSET_ID).await.unwrap();
    let balance_3: u64 = wallet_3.get_asset_balance(&BASE_ASSET_ID).await.unwrap();
 
    // println!("BALANCE 1: {:?}", balance_1);
    assert!(balance_1 == 1007500000);
    // println!("BALANCE 2: {:?}", balance_2);
    assert!(balance_2 == 1142500000);
    // println!("BALANCE 3: {:?}", balance_3);
    assert!(balance_3 == 850000000);
}

Icon LinkInitializing Functions

When writing tests for Sway, two crucial objects are required: the contract instance and the wallets that interact with it. This helper function ensures a fresh start for every new test case. It will export the deployed contracts, the contract ID, and all the generated wallets for this purpose.

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;
 
    let wallet = wallets.get(0).unwrap().clone();
 
    // let storage_config =
    // StorageConfiguration::load_from("out/debug/contract-storage_slots.json").unwrap();
 
    // let load_config = LoadConfiguration::default().with_storage_configuration(storage_config);
 
    let id = Contract::load_from(
        "./out/debug/contract.bin",
        LoadConfiguration::default(),
    )
    .unwrap()
    .deploy(&wallet, TxParameters::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())
        .unwrap()
        .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
        .with_account(wallet_1.clone())
        .unwrap()
        .methods()
        .initialize_owner()
        .call()
        .await
        .unwrap();
 
    // this should fail
    // try to set the owner from wallet_2
    let _fail_owner_result = instance
        .with_account(wallet_2.clone())
        .unwrap()
        .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
        .with_account(wallet_1.clone())
        .unwrap()
        .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
        .with_account(wallet_2.clone())
        .unwrap()
        .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(&BASE_ASSET_ID).await.unwrap();
    let balance_2: u64 = wallet_2.get_asset_balance(&BASE_ASSET_ID).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
        .with_account(wallet_1.clone())
        .unwrap()
        .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
        .with_account(wallet_2.clone())
        .unwrap()
        .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
        .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
        .with_account(wallet_3.clone())
        .unwrap()
        .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())
        .unwrap()
        .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(&BASE_ASSET_ID).await.unwrap();
    let balance_2: u64 = wallet_2.get_asset_balance(&BASE_ASSET_ID).await.unwrap();
    let balance_3: u64 = wallet_3.get_asset_balance(&BASE_ASSET_ID).await.unwrap();
 
    // println!("BALANCE 1: {:?}", balance_1);
    assert!(balance_1 == 1007500000);
    // println!("BALANCE 2: {:?}", balance_2);
    assert!(balance_2 == 1142500000);
    // println!("BALANCE 3: {:?}", balance_3);
    assert!(balance_3 == 850000000);
}

Icon LinkContract Storage and Binary

Besides the ABI, which outlines the interaction protocol with the smart contract, it's imperative to also load the contract's storage and binary. These three components are vital for the successful creation and deployment of the contract instance, guaranteeing precise testing in the following phases.

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;
 
    let wallet = wallets.get(0).unwrap().clone();
 
    // let storage_config =
    // StorageConfiguration::load_from("out/debug/contract-storage_slots.json").unwrap();
 
    // let load_config = LoadConfiguration::default().with_storage_configuration(storage_config);
 
    let id = Contract::load_from(
        "./out/debug/contract.bin",
        LoadConfiguration::default(),
    )
    .unwrap()
    .deploy(&wallet, TxParameters::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())
        .unwrap()
        .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
        .with_account(wallet_1.clone())
        .unwrap()
        .methods()
        .initialize_owner()
        .call()
        .await
        .unwrap();
 
    // this should fail
    // try to set the owner from wallet_2
    let _fail_owner_result = instance
        .with_account(wallet_2.clone())
        .unwrap()
        .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
        .with_account(wallet_1.clone())
        .unwrap()
        .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
        .with_account(wallet_2.clone())
        .unwrap()
        .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(&BASE_ASSET_ID).await.unwrap();
    let balance_2: u64 = wallet_2.get_asset_balance(&BASE_ASSET_ID).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
        .with_account(wallet_1.clone())
        .unwrap()
        .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
        .with_account(wallet_2.clone())
        .unwrap()
        .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
        .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
        .with_account(wallet_3.clone())
        .unwrap()
        .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())
        .unwrap()
        .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(&BASE_ASSET_ID).await.unwrap();
    let balance_2: u64 = wallet_2.get_asset_balance(&BASE_ASSET_ID).await.unwrap();
    let balance_3: u64 = wallet_3.get_asset_balance(&BASE_ASSET_ID).await.unwrap();
 
    // println!("BALANCE 1: {:?}", balance_1);
    assert!(balance_1 == 1007500000);
    // println!("BALANCE 2: {:?}", balance_2);
    assert!(balance_2 == 1142500000);
    // println!("BALANCE 3: {:?}", balance_3);
    assert!(balance_3 == 850000000);
}

Icon LinkTest Cases

Given the immutable nature of smart contracts, it's not only vital to test basic functionalities but also imperative to thoroughly cover all potential edge cases.

Icon LinkSetting Owner

For this test case, we utilize the contract instance and harness the SDK's .with_account() method. This method allows us to mimic the actions of the first wallet, especially the 'initialize owner' procedure. To ascertain the accuracy of the owner designation, we can confirm that the address returned by the contract aligns with that of wallet 1. For a more in-depth verification, probing the contract storage will reveal whether wallet 1's address is appropriately stored.

#[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())
		.unwrap()
		.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 LinkSetting Owner Once

An edge case we need to be vigilant about is an attempt to set the owner twice. We certainly don't want unauthorized ownership transfer of our contract! To address this, we've included the following line in our Sway contract: require(owner.is_none(), "owner already initialized"); This ensures the owner can only be set when it hasn't been previously established. To test this, we create a new contract instance: initially, we set the owner using wallet 1. Any subsequent attempt to set the owner with wallet 2 should be unsuccessful.

#[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
		.with_account(wallet_1.clone())
		.unwrap()
		.methods()
		.initialize_owner()
		.call()
		.await
		.unwrap();
 
	// this should fail
	// try to set the owner from wallet_2
	let _fail_owner_result = instance
		.with_account(wallet_2.clone())
		.unwrap()
		.methods()
		.initialize_owner()
		.call()
		.await
		.unwrap();
}

Icon LinkBuying and Selling in the Marketplace

It's essential to test the basic functionalities of a smart contract to ensure its proper operation. For this test, we have two wallets set up:

  1. The first wallet initiates a transaction to list an item for sale. This is done by calling the .list_item() method, specifying both the price and details of the item they're selling.
  2. The second wallet proceeds to purchase the listed item using the .buy_item() method, providing the index of the item they intend to buy.

Following these transactions, we'll assess the balances of both wallets to confirm the successful execution of the transactions.

#[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
		.with_account(wallet_1.clone())
		.unwrap()
		.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
		.with_account(wallet_2.clone())
		.unwrap()
		.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(&BASE_ASSET_ID).await.unwrap();
	let balance_2: u64 = wallet_2.get_asset_balance(&BASE_ASSET_ID).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 LinkWithdraw Owner Fees

Most importantly, as the creator of the marketplace, you need to ensure you're compensated. Similar to the previous tests, we'll invoke the relevant functions to make an exchange. This time, we'll verify if you can extract the difference in funds.

#[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
		.with_account(wallet_1.clone())
		.unwrap()
		.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
		.with_account(wallet_2.clone())
		.unwrap()
		.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
		.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
		.with_account(wallet_3.clone())
		.unwrap()
		.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())
		.unwrap()
		.methods()
		.withdraw_funds()
		.append_variable_outputs(1)
		.call()
		.await;
	assert!(withdraw.is_ok());
 
	 // Bytes representation of the asset ID of the "base" asset used for gas fees.
	 const BASE_ASSET_ID: AssetId = AssetId::new([0u8; 32]);
 
	// check the balances of wallet_1 and wallet_2
	let balance_1: u64 = wallet_1.get_asset_balance(&BASE_ASSET_ID).await.unwrap();
	let balance_2: u64 = wallet_2.get_asset_balance(&BASE_ASSET_ID).await.unwrap();
	let balance_3: u64 = wallet_3.get_asset_balance(&BASE_ASSET_ID).await.unwrap();
 
	// println!("BALANCE 1: {:?}", balance_1);
	assert!(balance_1 == 1007500000);
	// println!("BALANCE 2: {:?}", balance_2);
	assert!(balance_2 == 1142500000);
	// println!("BALANCE 3: {:?}", balance_3);
	assert!(balance_3 == 850000000);
}

Now that we're confident in the functionality of our smart contract, it's time to build a frontend. This will allow users to seamlessly interact with our new marketplace!

Was this page helpful?