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_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?
Edit: some resources that can help: