Confusion about Assets on Sway

I’ve been trying to wrap my head around assets on Sway, and I’m confused to say the least. It’s quite different from how an ERC-20 token on the EVM is constructed. I’m using the Native Assets part of the docs, and this has confused me even further. I’ll try and elucidate my thinking process, and I’d love if someone could help clear this.

Essentially, the BASE_SUB_ID/BASE_ASSET_ID (which is an alias for ZERO_B256/0x0000000000000000000000000000000000000000000000000000000000000000) represents the native asset on the Fuel network (i.e Ether), so if I was writing a method that would accept only Ether, I’d construct it as follows:

fn deposit(amount: u64) {
    require(msg_asset_id() == BASE_SUB_ID, "Only Ether accepted");
    ...
}

Minting/Burning

The above makes empirical sense, but the confusion starts when attempting to mint and transfer your own assets. Following the Native Assets section of the docs, we see the following methods

/// Mint an amount of this contracts native asset to the contracts balance.
fn mint_coins(mint_amount: u64) {
	mint(ZERO_B256, mint_amount);
}
 
/// Burn an amount of this contracts native asset.
fn burn_coins(burn_amount: u64) {
	burn(ZERO_B256, burn_amount);
}

Based on this post, the ZERO_B256 is the sub id of the asset (the final asset id of which is a hash of the current contract_id() and this sub_id). So, this allows for multiple native tokens under the same contract (which is neat!).

Getting Balance

But because of this abstraction (the final asset id of which is a hash of the current contract_id() and the sub_id), querying the balance of a contract is a little confusing with balance_of.

From here, the implementation of balance_of is:

/// # Examples
///
/// ```sway
/// use std::{context::balance_of, constants::ZERO_B256, hash::sha256, asset::mint, call_frames::contract_id};
///
/// fn foo() {
///     mint(ZERO_B256, 50);
///     assert(balance_of(contract_id(), sha256((ZERO_B256, contract_id()))) == 50);
/// }
/// ```
pub fn balance_of(target: ContractId, asset_id: AssetId) -> u64 {
    asm(balance, asset: asset_id.value, id: target.value) {
        bal balance asset id;
        balance: u64
    }
}

Based on this design, there’s a slight nuance involved: there’s no obvious way of querying a contract’s balance for a particular sub_id outright. According to the docs, the right way of calling the balance_of method is: balance_of(contract_id(), sha256((ZERO_B256, contract_id()) where ZERO_B256 again is for the sub_id of the asset. But is this correct?

I wrote a helper method below to query the contract’s internal balance as below, according to the method docs, but this is incorrect

fn this_balance() -> u64 {
    balance_of(
        /* target: current contract */
        contract_id(), 
        /* asset_id: hash of sub_id and contract_id */
        AssetId::from(sha256((ZERO_B256, contract_id()))) 
    )
}

the correct way is by swapping the values of sub_id and contract_id() within the hash function like so:

fn this_balance() -> u64 {
    balance_of(
        /* target: current contract */
        contract_id(), 
        /* asset_id: hash of sub_id and contract_id */
        AssetId::from(sha256((contract_id(), ZERO_B256)))
    )
}

imo, there needs to be better documentation highlighting this, something like the following:

fn get_balance(
    target: ContractId, 
    native_asset_contr: ContractId, 
    sub_id: b256
) -> u64 {
    let asset_id = AssetId::from(sha256((native_asset_contr, sub_id)));
    balance_of(contract_id(), asset_id)
}

and something similar for the transfer method as well.

Question: is there no way to query the balance of a regular EOA on-chain like one can do with contracts? I am aware of the UTXO model for EOAs which doesn’t exist for contracts, but still curious if something like this is possible to achieve?

Question: since there exists a scope for many different types of sub_ids for a particular contract leading to many different native assets for said contract, how would their asset_ids be viewable in the explorer?

Edit: some resources that can help:

2 Likes

this is incorrect

fn this_balance() -> u64 {
   balance_of(
       /* target: current contract */
       contract_id(), 
      /* asset_id: hash of sub_id and contract_id */
       AssetId::from(sha256((ZERO_B256, contract_id()))) 
   )
}

Here you are hashing first the SubId and then the ContractId i.e. (SubId, ContractId). As the input/bytes for the hash are different from (ContractId, SubId) the resulting hash is different. Ordering here matters. For example, the hash digest of (0u8, 1u8) is different from (1u8, 0u8).

is there no way to query the balance of a regular EOA on-chain like one can do with contracts? I am aware of the UTXO model for EOAs which doesn’t exist for contracts, but still curious if something like this is possible to achieve?

This is not possible to do in the Sway language but can be done with the Rust or typescript SDK. One can however view the inputs and outputs of a transaction and thus see the balance with msg_amount().

since there exists a scope for many different types of sub_id s for a particular contract leading to many different native assets for said contract, how would their asset_id s be viewable in the explorer?

The AssetId of all assets minted by a contract are distinguishable from one another. They will each have a unique AssetId and would be viewed in the explorer as separate assets.

Anytime a new asset is minted, there is a receipt logged containing the ContractId and SubId. The SubId of an asset should only be relevant when minting and burning, as all transfer and mangement functions use the AssetId.

To create a new AssetId from a ContractId and SubId, you can use the AssetId::new() function found here.

In your example, you can query the balance like so:

// For any SubId within the current contract
let asset_id = AssetId::new(contract_id(), my_sub_id);
let balance = this_balance(asset_id);

// For DEFAULT_SUB_ID or the ZERO_B256 within the current contract
let asset_id = AssetId::default();
let balance = this_balance(asset_id);

// For the BASE_ASSET_ID within the current contract
let balance = this_balance(BASE_ASSET_ID);

// For any external contract and SubId
let asset_id = AssetId::new(my_contract, my_sub_id);
let balance = balance_of(my_contract, asset_id);
1 Like

yup, the comment over the balance_of implementation is incorrect (points to the incorrect ordering (sha256((ZERO_B256, contract_id())) as opposed to sha256((contract_id(), ZERO_B256))), so that added to the confusion.

Thanks a ton!

2 Likes

yup, the comment over the balance_of implementation is incorrect

Thank you for bringing this up. This is resolved in this PR.

1 Like

Whoops, @bitzoic beat me to responding, but here’s my original response just for reference :slight_smile:


Hey @theAusicist, Fuel’s native assets are definitely one of our more unique features. This unlocks some really interesting stuff, but can also be a bit of a transition compared to ERC20-style architectures.

I saw that you’re using the sha256 directly a few places, which certainly should work, however the suggested way to generate an AssetId is using the new function:

let assetId = AssetId::new(contract_id(), sub_id);

imo, there needs to be better documentation highlighting this, something like the

100%, this is great feedback, I’m sharing this internally to make sure we can make this clearer in the docs.

Question: is there no way to query the balance of a regular EOA on-chain like one can do with contracts?

Correct, the UTXO model of Fuel means that you can’t definitively check the balance of an address on-chain, because some UTXOs might not be included in a given transaction.

Question: since there exists a scope for many different types of sub_ids for a particular contract leading to many different native assets for said contract, how would their asset_ids be viewable in the explorer?

Great question, I’d have to defer to the team building our block explorer, but I believe the explorer will show a list of all assets minted by a given smart contract.

3 Likes

neat. thanks @david and @bitzoic.

2 Likes

Wrote an implementation of the Fuel Native Asset standard incorporating the SRC20 and SRC3 (mint/burn capabilities): GitHub - burralabs/fuel-asset-standard: An unofficial implementation of the Fuel Native Asset Standard