Deposit and withdraw

Consider the following contract:

contract;

use std::{
    call_frames::{
        contract_id,
        msg_asset_id,
    },
    context::msg_amount,
    token::{
        mint_to_address,
        transfer_to_address,
    },
};

abi LiquidityPool {
    #[payable]
    fn deposit(recipient: Address);
    #[payable]
    fn withdraw(recipient: Address);
}

const BASE_TOKEN: b256 = 0x9ae5b658754e096e4d681c548daf46354495a437cc61492599e33fc64dcdc30c;

impl LiquidityPool for Contract {
    #[payable]
    fn deposit(recipient: Address) {
        assert(ContractId::from(BASE_TOKEN) == msg_asset_id());
        assert(0 < msg_amount());

        // Mint two times the amount.
        let amount_to_mint = msg_amount() * 2;

        // Mint some LP token based upon the amount of the base token.
        mint_to_address(amount_to_mint, recipient);
    }

    #[payable]
    fn withdraw(recipient: Address) {
        assert(contract_id() == msg_asset_id());
        assert(0 < msg_amount());

        // Amount to withdraw.
        let amount_to_transfer = msg_amount() / 2;

        // Transfer base token to recipient.
        transfer_to_address(amount_to_transfer, ContractId::from(BASE_TOKEN), recipient);
    }
}

As its name suggests, it represents a simplified example of a liquidity pool contract. The method deposit() expects you to supply an arbitrary amount of the BASE_TOKEN. As a result, it mints double the amount of the liquidity asset to the calling address. Analogously, if you call withdraw() supplying it with the liquidity asset, it will transfer half that amount of the BASE_TOKEN back to the calling address except for deducting it from the contract balance instead of minting it.

The first step towards interacting with any contract in the Rust SDK is calling the abigen! macro to generate type-safe Rust bindings for the contract methods:

        abigen!(Contract(
            name = "MyContract",
            abi = "packages/fuels/tests/contracts/liquidity_pool/out/debug/liquidity_pool-abi.json"
        ));

Next, we set up a wallet with custom-defined assets. We give our wallet some of the contracts BASE_TOKEN and the default asset (required for contract deployment):

        let base_asset_id: AssetId =
            "0x9ae5b658754e096e4d681c548daf46354495a437cc61492599e33fc64dcdc30c"
                .parse()
                .unwrap();

        let asset_ids = [AssetId::default(), base_asset_id];
        let asset_configs = asset_ids
            .map(|id| AssetConfig {
                id,
                num_coins: 1,
                coin_amount: 1_000_000,
            })
            .into();

        let wallet_config = WalletsConfig::new_multiple_assets(1, asset_configs);
        let wallets = launch_custom_provider_and_get_wallets(wallet_config, None, None).await;
        let wallet = &wallets[0];

Having launched a provider and created the wallet, we can deploy our contract and create an instance of its methods:

        let contract_id = Contract::deploy(
            "../../packages/fuels/tests/contracts/liquidity_pool/out/debug/liquidity_pool.bin",
            wallet,
            TxParameters::default(),
            StorageConfiguration::default(),
        )
        .await?;

        let contract_methods = MyContract::new(contract_id.clone(), wallet.clone()).methods();

With the preparations out of the way, we can finally deposit to the liquidity pool by calling deposit() via the contract instance. Since we have to transfer assets to the contract, we create the appropriate CallParameters and chain them to the method call. To receive the minted liquidity pool asset, we have to append a variable output to our contract call.

        let deposit_amount = 1_000_000;
        let call_params = CallParameters::new(Some(deposit_amount), Some(base_asset_id), None);
        contract_methods
            .deposit(wallet.address().into())
            .call_params(call_params)?
            .append_variable_outputs(1)
            .call()
            .await?;

As a final demonstration, let's use all our liquidity asset balance to withdraw from the pool and confirm we retrieved the initial amount. For this, we get our liquidity asset balance and supply it to the withdraw() call via CallParameters.

        let lp_asset_id = AssetId::from(*contract_id.hash());
        let lp_token_balance = wallet.get_asset_balance(&lp_asset_id).await?;

        let call_params = CallParameters::new(Some(lp_token_balance), Some(lp_asset_id), None);
        contract_methods
            .withdraw(wallet.address().into())
            .call_params(call_params)?
            .append_variable_outputs(1)
            .call()
            .await?;

        let base_balance = wallet.get_asset_balance(&base_asset_id).await?;
        assert_eq!(base_balance, deposit_amount);