From 5257dc7f19d1893df749cd84240fee4158a12481 Mon Sep 17 00:00:00 2001 From: Cameron Carstens <54727135+bitzoic@users.noreply.github.com> Date: Wed, 4 Oct 2023 11:11:03 +0200 Subject: [PATCH] Add SRC-7 helper functions (#194) ## Type of change - New feature ## Changes The following changes have been made: - Splits the single file into multiple files and groups functions by standard - Adds helper functions for the `Metadata` type of the SRC-7 standard ## Notes - This will require https://github.com/FuelLabs/sway-standards/pull/20 to be merged first --------- Co-authored-by: bitzoic --- README.md | 2 +- libs/token/Forc.toml | 3 + libs/token/README.md | 6 +- libs/token/SPECIFICATION.md | 69 ++++-- libs/token/src/base.sw | 318 ++++++++++++++++++++++++++ libs/token/src/lib.sw | 444 +----------------------------------- libs/token/src/metadata.sw | 397 ++++++++++++++++++++++++++++++++ libs/token/src/mint.sw | 128 +++++++++++ tests/src/token/Forc.toml | 1 + tests/src/token/src/main.sw | 213 +++++++++++++++-- 10 files changed, 1105 insertions(+), 476 deletions(-) create mode 100644 libs/token/src/base.sw create mode 100644 libs/token/src/metadata.sw create mode 100644 libs/token/src/mint.sw diff --git a/README.md b/README.md index 9f404763..6e15f46b 100644 --- a/README.md +++ b/README.md @@ -37,7 +37,7 @@ These libraries contain helper functions and other tools valuable to blockchain - [Signed Integers](./libs/signed_integers/) is an interface to implement signed integers. - [Fixed Point Number](./libs/fixed_point/) is an interface to implement fixed-point numbers. - [Queue](./libs/queue/) is a linear data structure that provides First-In-First-Out (FIFO) operations. -- [Token](./libs/token/) is a basic implementation of the [SRC-20](https://github.com/FuelLabs/sway-standards/tree/master/standards/src_20) and [SRC-3](https://github.com/FuelLabs/sway-standards/tree/master/standards/src_3) standards. +- [Token](./libs/token/) provides helper functions for the [SRC-20](https://github.com/FuelLabs/sway-standards/tree/master/standards/src_20), [SRC-3](https://github.com/FuelLabs/sway-standards/tree/master/standards/src_3), and [SRC-7](https://github.com/FuelLabs/sway-standards/tree/master/standards/src_7) standards. ## Using a library diff --git a/libs/token/Forc.toml b/libs/token/Forc.toml index 651dbaae..a7df4a8a 100644 --- a/libs/token/Forc.toml +++ b/libs/token/Forc.toml @@ -3,3 +3,6 @@ authors = ["Fuel Labs "] entry = "lib.sw" license = "Apache-2.0" name = "token" + +[dependencies] +src_7 = { git = "https://github.com/FuelLabs/sway-standards", tag = "v0.1.2" } diff --git a/libs/token/README.md b/libs/token/README.md index 690f3488..2b0f6587 100644 --- a/libs/token/README.md +++ b/libs/token/README.md @@ -7,7 +7,7 @@ # Overview -The Token library provides basic function implementations of the [SRC-20; Token Standard](https://github.com/FuelLabs/sway-standards/tree/master/standards/src_20) and the [SRC-3; Mint and Burn Standard](https://github.com/FuelLabs/sway-standards/tree/master/standards/src_3). It is intended to make develpment of Native Assets using Sway quick and easy while following the standard's specifications. +The Token library provides basic helper functions for the [SRC-20; Token Standard](https://github.com/FuelLabs/sway-standards/tree/master/standards/src_20), [SRC-3; Mint and Burn Standard](https://github.com/FuelLabs/sway-standards/tree/master/standards/src_3), and the [SRC-7; Arbitrary Asset Metadata Standard](https://github.com/FuelLabs/sway-standards/tree/master/standards/src_7). It is intended to make develpment of Native Assets using Sway quick and easy while following the standard's specifications. For more information please see the [specification](./SPECIFICATION.md). @@ -40,7 +40,7 @@ storage { To use a function, simply pass the `StorageKey` from the prescribed storage block above. The example below shows the implementation of the [SRC-20](https://github.com/FuelLabs/sway-standards/tree/master/standards/src_20) standard in combination with the Token library with no user defined restrictions or custom functionality. ```rust -use token::{ +use token::base::{ _total_assets, _total_supply, _name, @@ -87,3 +87,5 @@ impl SRC20 for Contract { ``` For more information please see the [specification](./SPECIFICATION.md). + +> **NOTE** Until [Issue #5025](https://github.com/FuelLabs/sway/issues/5025) is resolved, in order to use the SRC-7 portion of the library, you must also add the [SRC-7](https://github.com/FuelLabs/sway-standards/tree/master/standards/src_7) standard as a dependency. diff --git a/libs/token/SPECIFICATION.md b/libs/token/SPECIFICATION.md index e1ca971d..c5a535eb 100644 --- a/libs/token/SPECIFICATION.md +++ b/libs/token/SPECIFICATION.md @@ -10,42 +10,85 @@ The Token library can be used anytime a contract needs a basic implementation of ## Public Functions -### `_total_assets()` +### SRC-20 + +#### `_total_assets()` This function will return the total number of individual assets for a contract. -### `_total_supply()` +#### `_total_supply()` This function will return the total supply of tokens for an asset. -### `_name()` +#### `_name()` This function will return the name of an asset, such as “Ether”. -### `_symbol()` +#### `_symbol()` This function will return the symbol of an asset, such as “ETH”. -### `_decimals()` +#### `_decimals()` This function will return the number of decimals an asset uses. -### `_mint()` +#### `_set_name()` + +This function will unconditionally set the name of an asset. + +#### `_set_symbol()` + +This function will unconditionally set the symbol of an asset. + +#### `_set_decimals` + +This function will unconditionally set the decimals of an asset. + +### SRC-3 + +#### `_mint()` This function will unconditionally mint new tokens using a sub-identifier. -### `_burn()` +#### `_burn()` This function will burns tokens with the given sub-identifier. -### `_set_name()` +### SRC-7 -This function will unconditionally set the name of an asset. +### `_set_metadata()` -### `_set_symbol()` +This function will set metdata for an asset. -This function will unconditionally set the symbol of an asset. +#### `as_string()` -### `_set_decimals` +This function will return the metadata as a string. + +#### `as_int()` + +This function will return the metadata as an int. + +#### `as_bytes()` + +This function will return the metadata as bytes. + +#### `as_b256()` + +This function will return the metadata as a b256. + +#### `is_string()` + +This function will return whether the metadata is a string. + +#### `is_int()` + +This function will return the whether metadata is an int. + +#### `is_bytes()` + +This function will return the whether metadata are bytes. + +#### `is_b256()` + +This function will return the whether metadata is a b256. -This function will unconditionally set the decimals of an asset. diff --git a/libs/token/src/base.sw b/libs/token/src/base.sw new file mode 100644 index 00000000..66bd3fca --- /dev/null +++ b/libs/token/src/base.sw @@ -0,0 +1,318 @@ +library; + +use std::{hash::{Hash, sha256}, storage::storage_string::*, string::String}; + +/// Returns the total number of individual assets for a contract. +/// +/// # Arguments +/// +/// * `total_assets_key`: [StorageKey] - The location in storage that the `u64` which represents the total assets is stored. +/// +/// # Returns +/// +/// * [u64] - The number of assets that this contract has minted. +/// +/// # Number of Storage Accesses +/// +/// * Reads: `1` +/// +/// # Examples +/// +/// ```sway +/// use token::_total_assets; +/// +/// storage { +/// total_assets: u64 = 0, +/// } +/// +/// fn foo() { +/// let assets = _total_assets(storage.total_assets); +/// assert(assets == 0); +/// } +/// ``` +#[storage(read)] +pub fn _total_assets(total_assets_key: StorageKey) -> u64 { + total_assets_key.try_read().unwrap_or(0) +} + +/// Returns the total supply of tokens for an asset. +/// +/// # Arguments +/// +/// * `total_supply_key`: [StorageKey>] - The location in storage which the `StorageMap` that stores the total supply of assets is stored. +/// * `asset`: [AssetId] - The asset of which to query the total supply. +/// +/// # Returns +/// +/// * [Option] - The total supply of an `asset`. +/// +/// # Number of Storage Accesses +/// +/// * Reads: `1` +/// +/// # Examples +/// +/// ```sway +/// use token::_total_supply; +/// +/// storage { +/// total_supply: StorageMap = StorageMap {}, +/// } +/// +/// fn foo(asset_id: AssetId) { +/// let supply = _total_supply(storage.total_supply, asset_id); +/// assert(supply.unwrap_or(0) != 0); +/// } +/// ``` +#[storage(read)] +pub fn _total_supply( + total_supply_key: StorageKey>, + asset: AssetId, +) -> Option { + total_supply_key.get(asset).try_read() +} + +/// Returns the name of the asset, such as “Ether”. +/// +/// # Arguments +/// +/// * `name_key`: [StorageKey>>] - The location in storage which the `StorageMap` that stores the names of assets is stored. +/// * `asset`: [AssetId] - The asset of which to query the name. +/// +/// # Returns +/// +/// * [Option] - The name of `asset`. +/// +/// # Number of Storage Accesses +/// +/// * Reads: `1` +/// +/// # Examples +/// +/// ```sway +/// use token::_name; +/// use std::string::String; +/// +/// storage { +/// name: StorageMap = StorageMap {}, +/// } +/// +/// fn foo(asset: AssetId) { +/// let name = _name(storage.name, asset); +/// assert(name.unwrap_or(String::new()).len() != 0); +/// } +/// ``` +#[storage(read)] +pub fn _name( + name_key: StorageKey>, + asset: AssetId, +) -> Option { + name_key.get(asset).read_slice() +} +/// Returns the symbol of the asset, such as “ETH”. +/// +/// # Arguments +/// +/// * `symbol_key`: [StorageKey>>] - The location in storage which the `StorageMap` that stores the symbols of assets is stored. +/// * `asset`: [AssetId] - The asset of which to query the symbol. +/// +/// # Returns +/// +/// * [Option] - The symbol of `asset`. +/// +/// # Number of Storage Accesses +/// +/// * Reads: `1` +/// +/// # Examples +/// +/// ```sway +/// use token::_symbol; +/// use std::string::String; +/// +/// storage { +/// symbol: StorageMap = StorageMap {}, +/// } +/// +/// fn foo(asset: AssetId) { +/// let symbol = _symbol(storage.symbol, asset); +/// assert(symbol.unwrap_or(String::new()).len() != 0); +/// } +/// ``` +#[storage(read)] +pub fn _symbol( + symbol_key: StorageKey>, + asset: AssetId, +) -> Option { + symbol_key.get(asset).read_slice() +} +/// Returns the number of decimals the asset uses. +/// +/// # Additional Information +/// +/// e.g. 8, means to divide the token amount by 100000000 to get its user representation. +/// +/// # Arguments +/// +/// * `decimals_key`: [StorageKey>] - The location in storage which the `StorageMap` that stores the decimals of assets is stored. +/// * `asset`: [AssetId] - The asset of which to query the decimals. +/// +/// # Returns +/// +/// * [Option] - The decimal precision used by `asset`. +/// +/// # Number of Storage Accesses +/// +/// * Reads: `1` +/// +/// # Examples +/// +/// ```sway +/// use token::_decimals; +/// +/// storage { +/// decimals: StorageMap = StorageMap {}, +/// } +/// +/// fn foo(asset: AssedId) { +/// let decimals = _decimals(storage.decimals, asset); +/// assert(decimals.unwrap_or(0u8) == 8); +/// } +/// ``` +#[storage(read)] +pub fn _decimals( + decimals_key: StorageKey>, + asset: AssetId, +) -> Option { + decimals_key.get(asset).try_read() +} +/// Unconditionally sets the name of an asset. +/// +/// # Additional Information +/// +/// NOTE: This does not check whether the asset id provided is valid or already exists. +/// +/// # Arguments +/// +/// * `name_key`: [StorageKey>>] - The location in storage which the `StorageMap` that stores the names of assets is stored. +/// * `asset`: [AssetId] - The asset of which to set the name. +/// * `name`: [String] - The name of the asset. +/// +/// # Number of Storage Accesses +/// +/// * Writes: `2` +/// +/// # Examples +/// +/// ```sway +/// use token::{_set_name, _name}; +/// use std::string::String; +/// +/// storage { +/// name: StorageMap = StorageMap {}, +/// } +/// +/// fn foo(asset: AssetId) { +/// let name = String::from_ascii_str("Ether"); +/// _set_name(storage.name, asset, name); +/// assert(_name(storage.name, asset) == name); +/// } +/// ``` +#[storage(write)] +pub fn _set_name( + name_key: StorageKey>, + asset: AssetId, + name: String, +) { + name_key.insert(asset, StorageString {}); + name_key.get(asset).write_slice(name); +} +/// Unconditionally sets the symbol of an asset. +/// +/// # Additional Information +/// +/// NOTE: This does not check whether the asset id provided is valid or already exists. +/// +/// # Arguments +/// +/// * `symbol_key`: [StorageKey>>] - The location in storage which the `StorageMap` that stores the symbols of assets is stored. +/// * `asset`: [AssetId] - The asset of which to set the symbol. +/// * `symbol`: [String] - The symbol of the asset. +/// +/// # Number of Storage Accesses +/// +/// * Writes: `2` +/// +/// # Examples +/// +/// ```sway +/// use token::{_set_symbol, _symbol}; +/// use std::string::String; +/// +/// storage { +/// symbol: StorageMap = StorageMap {}, +/// } +/// +/// fn foo(asset: AssetId) { +/// let symbol = String::from_ascii_str("ETH"); +/// _set_symbol(storage.symbol, asset, symbol); +/// assert(_symbol(storage.symbol, asset) == symbol); +/// } +/// ``` +#[storage(write)] +pub fn _set_symbol( + symbol_key: StorageKey>, + asset: AssetId, + symbol: String, +) { + symbol_key.insert(asset, StorageString {}); + symbol_key.get(asset).write_slice(symbol); +} +/// Unconditionally sets the decimals of an asset. +/// +/// # Additional Information +/// +/// NOTE: This does not check whether the asset id provided is valid or already exists. +/// +/// # Arguments +/// +/// * `decimals_key`: [StorageKey>] - The location in storage which the `StorageMap` that stores the decimals of assets is stored. +/// * `asset`: [AssetId] - The asset of which to set the decimals. +/// * `decimal`: [u8] - The decimals of the asset. +/// +/// # Number of Storage Accesses +/// +/// * Writes: `1` +/// +/// # Examples +/// +/// ```sway +/// use token::{_set_decimals, _decimals}; +/// use std::string::String; +/// +/// storage { +/// decimals: StorageMap = StorageMap {}, +/// } +/// +/// fn foo(asset: AssetId) { +/// let decimals = 8u8; +/// _set_decimals(storage.decimals, asset, decimals); +/// assert(_decimals(storage.decimals, asset) == decimals); +/// } +/// ``` +#[storage(write)] +pub fn _set_decimals( + decimals_key: StorageKey>, + asset: AssetId, + decimals: u8, +) { + decimals_key.insert(asset, decimals); +} +abi SetTokenAttributes { + #[storage(write)] + fn set_name(asset: AssetId, name: String); + #[storage(write)] + fn set_symbol(asset: AssetId, symbol: String); + #[storage(write)] + fn set_decimals(asset: AssetId, decimals: u8); +} diff --git a/libs/token/src/lib.sw b/libs/token/src/lib.sw index b2c9137b..20f25594 100644 --- a/libs/token/src/lib.sw +++ b/libs/token/src/lib.sw @@ -1,442 +1,6 @@ library; -mod errors; - -use errors::BurnError; -use std::{ - call_frames::{ - contract_id, - msg_asset_id, - }, - context::this_balance, - hash::{ - Hash, - sha256, - }, - storage::storage_string::*, - string::String, - token::{ - burn, - mint_to, - }, -}; - -/// Returns the total number of individual assets for a contract. -/// -/// # Arguments -/// -/// * `total_assets_key`: [StorageKey] - The location in storage that the `u64` which represents the total assets is stored. -/// -/// # Returns -/// -/// * [u64] - The number of assets that this contract has minted. -/// -/// # Number of Storage Accesses -/// -/// * Reads: `1` -/// -/// # Examples -/// -/// ```sway -/// use token::_total_assets; -/// -/// storage { -/// total_assets: u64 = 0, -/// } -/// -/// fn foo() { -/// let assets = _total_assets(storage.total_assets); -/// assert(assets == 0); -/// } -/// ``` -#[storage(read)] -pub fn _total_assets(total_assets_key: StorageKey) -> u64 { - total_assets_key.try_read().unwrap_or(0) -} - -/// Returns the total supply of tokens for an asset. -/// -/// # Arguments -/// -/// * `total_supply_key`: [StorageKey>] - The location in storage which the `StorageMap` that stores the total supply of assets is stored. -/// * `asset`: [AssetId] - The asset of which to query the total supply. -/// -/// # Returns -/// -/// * [Option] - The total supply of an `asset`. -/// -/// # Number of Storage Accesses -/// -/// * Reads: `1` -/// -/// # Examples -/// -/// ```sway -/// use token::_total_supply; -/// -/// storage { -/// total_supply: StorageMap = StorageMap {}, -/// } -/// -/// fn foo(asset_id: AssetId) { -/// let supply = _total_supply(storage.total_supply, asset_id); -/// assert(supply.unwrap_or(0) != 0); -/// } -/// ``` -#[storage(read)] -pub fn _total_supply( - total_supply_key: StorageKey>, - asset: AssetId, -) -> Option { - total_supply_key.get(asset).try_read() -} - -/// Returns the name of the asset, such as “Ether”. -/// -/// # Arguments -/// -/// * `name_key`: [StorageKey>>] - The location in storage which the `StorageMap` that stores the names of assets is stored. -/// * `asset`: [AssetId] - The asset of which to query the name. -/// -/// # Returns -/// -/// * [Option] - The name of `asset`. -/// -/// # Number of Storage Accesses -/// -/// * Reads: `1` -/// -/// # Examples -/// -/// ```sway -/// use token::_name; -/// use std::string::String; -/// -/// storage { -/// name: StorageMap> = StorageMap {}, -/// } -/// -/// fn foo(asset: AssetId) { -/// let name = _name(storage.name, asset); -/// assert(name.unwrap_or(String::new()).len() != 0); -/// } -/// ``` -#[storage(read)] -pub fn _name( - name_key: StorageKey>, - asset: AssetId, -) -> Option { - name_key.get(asset).read_slice() -} -/// Returns the symbol of the asset, such as “ETH”. -/// -/// # Arguments -/// -/// * `symbol_key`: [StorageKey>>] - The location in storage which the `StorageMap` that stores the symbols of assets is stored. -/// * `asset`: [AssetId] - The asset of which to query the symbol. -/// -/// # Returns -/// -/// * [Option] - The symbol of `asset`. -/// -/// # Number of Storage Accesses -/// -/// * Reads: `1` -/// -/// # Examples -/// -/// ```sway -/// use token::_symbol; -/// use std::string::String; -/// -/// storage { -/// symbol: StorageMap> = StorageMap {}, -/// } -/// -/// fn foo(asset: AssetId) { -/// let symbol = _symbol(storage.symbol, asset); -/// assert(symbol.unwrap_or(String::new()).len() != 0); -/// } -/// ``` -#[storage(read)] -pub fn _symbol( - symbol_key: StorageKey>, - asset: AssetId, -) -> Option { - symbol_key.get(asset).read_slice() -} -/// Returns the number of decimals the asset uses. -/// -/// # Additional Information -/// -/// e.g. 8, means to divide the token amount by 100000000 to get its user representation. -/// -/// # Arguments -/// -/// * `decimals_key`: [StorageKey>] - The location in storage which the `StorageMap` that stores the decimals of assets is stored. -/// * `asset`: [AssetId] - The asset of which to query the decimals. -/// -/// # Returns -/// -/// * [Option] - The decimal precision used by `asset`. -/// -/// # Number of Storage Accesses -/// -/// * Reads: `1` -/// -/// # Examples -/// -/// ```sway -/// use token::_decimals; -/// -/// storage { -/// decimals: StorageMap = StorageMap {}, -/// } -/// -/// fn foo(asset: AssedId) { -/// let decimals = _decimals(storage.decimals, asset); -/// assert(decimals.unwrap_or(0u8) == 8); -/// } -/// ``` -#[storage(read)] -pub fn _decimals( - decimals_key: StorageKey>, - asset: AssetId, -) -> Option { - decimals_key.get(asset).try_read() -} -/// Unconditionally mints new tokens using the `sub_id` sub-identifier. -/// -/// # Arguments -/// -/// * `total_assets_key`: [StorageKey] - The location in storage that the `u64` which represents the total assets is stored. -/// * `total_supply_key`: [StorageKey>] - The location in storage which the `StorageMap` that stores the total supply of assets is stored. -/// * `recipient`: [Identity] - The user to which the newly minted tokens are transferred to. -/// * `sub_id`: [SubId] - The sub-identifier of the newly minted token. -/// * `amount`: [u64] - The quantity of tokens to mint. -/// -/// # Returns -/// -/// * [AssetId] - The `AssetId` of the newly minted asset. -/// -/// # Number of Storage Accesses -/// -/// * Reads: `2` -/// * Writes: `2` -/// -/// # Examples -/// -/// ```sway -/// use token::_mint; -/// use std::{constants::ZERO_B256, context::balance_of}; -/// -/// storage { -/// total_assets: u64 = 0, -/// total_supply: StorageMap = StorageMap {}, -/// } -/// -/// fn foo(recipient: Identity) { -/// let recipient = Identity::ContractId(ContractId::from(ZERO_B256)); -/// let asset_id = _mint(storage.total_assets, storage.total_supply, recipient, ZERO_B256, 100); -/// assert(balance_of(recipient.as_contract_id(), asset_id), 100); -/// } -/// ``` -#[storage(read, write)] -pub fn _mint( - total_assets_key: StorageKey, - total_supply_key: StorageKey>, - recipient: Identity, - sub_id: SubId, - amount: u64, -) -> AssetId { - let asset_id = AssetId::new(contract_id(), sub_id); - let supply = _total_supply(total_supply_key, asset_id); - // Only increment the number of assets minted by this contract if it hasn't been minted before. - if supply.is_none() { - total_assets_key.write(_total_assets(total_assets_key) + 1); - } - let current_supply = supply.unwrap_or(0); - total_supply_key.insert(asset_id, current_supply + amount); - mint_to(recipient, sub_id, amount); - asset_id -} -/// Burns tokens with the given `sub_id`. -/// -/// # Additional Information -/// -/// **Warning** This function burns tokens unequivocally. It does not check that tokens are sent to the calling contract. -/// -/// # Arguments -/// -/// * `total_assets_key`: [StorageKey] - The location in storage that the `u64` which represents the total assets is stored. -/// * `sub_id`: [SubId] - The sub-identifier of the token to burn. -/// * `amount`: [u64] - The quantity of tokens to burn. -/// -/// # Reverts -/// -/// * When the calling contract does not have enough tokens. -/// -/// # Number of Storage Accesses -/// -/// * Reads: `1` -/// * Writes: `1` -/// -/// # Examples -/// -/// ```sway -/// use token::_burn; -/// use std::{call_frames::contract_id, constants::ZERO_B256, context::balance_of}; -/// -/// storage { -/// total_supply: StorageMap = StorageMap {}, -/// } -/// -/// fn foo(asset_id: AssetId) { -/// assert(balance_of(contract_id(), asset_id) == 100); -/// _burn(storage.total_supply, ZERO_B256, 100); -/// assert(balance_of(contract_id(), asset_id) == 0); -/// } -/// ``` -#[storage(read, write)] -pub fn _burn( - total_supply_key: StorageKey>, - sub_id: SubId, - amount: u64, -) { - let asset_id = AssetId::new(contract_id(), sub_id); - require(this_balance(asset_id) >= amount, BurnError::NotEnoughTokens); - // If we pass the check above, we can assume it is safe to unwrap. - let supply = _total_supply(total_supply_key, asset_id).unwrap(); - total_supply_key.insert(asset_id, supply - amount); - burn(sub_id, amount); -} -/// Unconditionally sets the name of an asset. -/// -/// # Additional Information -/// -/// NOTE: This does not check whether the asset id provided is valid or already exists. -/// -/// # Arguments -/// -/// * `name_key`: [StorageKey>>] - The location in storage which the `StorageMap` that stores the names of assets is stored. -/// * `asset`: [AssetId] - The asset of which to set the name. -/// * `name`: [String] - The name of the asset. -/// -/// # Number of Storage Accesses -/// -/// * Writes: `2` -/// -/// # Examples -/// -/// ```sway -/// use token::{_set_name, _name}; -/// use std::string::String; -/// -/// storage { -/// name: StorageMap> = StorageMap {}, -/// } -/// -/// fn foo(asset: AssetId) { -/// let name = String::from_ascii_str("Ether"); -/// _set_name(storage.name, asset, name); -/// assert(_name(storage.name, asset) == name); -/// } -/// ``` -#[storage(write)] -pub fn _set_name( - name_key: StorageKey>, - asset: AssetId, - name: String, -) { - name_key.insert(asset, StorageString {}); - name_key.get(asset).write_slice(name); -} -/// Unconditionally sets the symbol of an asset. -/// -/// # Additional Information -/// -/// NOTE: This does not check whether the asset id provided is valid or already exists. -/// -/// # Arguments -/// -/// * `symbol_key`: [StorageKey>>] - The location in storage which the `StorageMap` that stores the symbols of assets is stored. -/// * `asset`: [AssetId] - The asset of which to set the symbol. -/// * `symbol`: [String] - The symbol of the asset. -/// -/// # Number of Storage Accesses -/// -/// * Writes: `2` -/// -/// # Examples -/// -/// ```sway -/// use token::{_set_symbol, _symbol}; -/// use std::string::String; -/// -/// storage { -/// symbol: StorageMap> = StorageMap {}, -/// } -/// -/// fn foo(asset: AssetId) { -/// let symbol = String::from_ascii_str("ETH"); -/// _set_symbol(storage.symbol, asset, symbol); -/// assert(_symbol(storage.symbol, asset) == symbol); -/// } -/// ``` -#[storage(write)] -pub fn _set_symbol( - symbol_key: StorageKey>, - asset: AssetId, - symbol: String, -) { - symbol_key.insert(asset, StorageString {}); - symbol_key.get(asset).write_slice(symbol); -} -/// Unconditionally sets the decimals of an asset. -/// -/// # Additional Information -/// -/// NOTE: This does not check whether the asset id provided is valid or already exists. -/// -/// # Arguments -/// -/// * `decimals_key`: [StorageKey>] - The location in storage which the `StorageMap` that stores the decimals of assets is stored. -/// * `asset`: [AssetId] - The asset of which to set the decimals. -/// * `decimal`: [u8] - The decimals of the asset. -/// -/// # Number of Storage Accesses -/// -/// * Writes: `1` -/// -/// # Examples -/// -/// ```sway -/// use token::{_set_decimals, _decimals}; -/// use std::string::String; -/// -/// storage { -/// decimals: StorageMap = StorageMap {}, -/// } -/// -/// fn foo(asset: AssetId) { -/// let decimals = 8u8; -/// _set_decimals(storage.decimals, asset, decimals); -/// assert(_decimals(storage.decimals, asset) == decimals); -/// } -/// ``` -#[storage(write)] -pub fn _set_decimals( - decimals_key: StorageKey>, - asset: AssetId, - decimals: u8, -) { - decimals_key.insert(asset, decimals); -} -abi SetTokenAttributes { - #[storage(write)] - fn set_name(asset: AssetId, name: String); - #[storage(write)] - fn set_symbol(asset: AssetId, symbol: String); - #[storage(write)] - fn set_decimals(asset: AssetId, decimals: u8); -} +pub mod errors; +pub mod base; +pub mod metadata; +pub mod mint; diff --git a/libs/token/src/metadata.sw b/libs/token/src/metadata.sw new file mode 100644 index 00000000..ae4ea9f8 --- /dev/null +++ b/libs/token/src/metadata.sw @@ -0,0 +1,397 @@ +library; + +use src_7::Metadata; +use std::{ + bytes::Bytes, + hash::{ + Hash, + sha256, + }, + storage::{ + storage_api::{ + read, + write, + }, + storage_bytes::*, + storage_string::*, + }, + string::String, +}; + +/// A persistent storage type to store the SRC-7; Metadata Standard type. +/// +/// # Additional Information +/// +/// This type should only be used if metadata changes or cannot be set at contract deployment time. +pub struct StorageMetadata {} + +impl StorageKey { + /// Stores metadata for a specific asset and key pair. + /// + /// # Arugments + /// + /// * `asset`: [AssetId] - The asset for the metadata to be stored. + /// * `key`: [String] - The key for the metadata to be stored. + /// * `metadata`: [Metadata] - The metadata which to be stored. + /// + /// # Number of Storage Accesses + /// + /// * Writes: `2` + /// + /// # Example + /// + /// ```sway + /// use src_7::Metadata; + /// use token::metdata::*; + /// + /// storage { + /// metadata: StorageMetadata = StorageMetadata {} + /// } + /// + /// fn foo(asset: AssetId, key: String, metadata: Metadata) { + /// storage.metadata.insert(asset, key, metadata); + /// } + /// ``` + #[storage(read, write)] + pub fn insert(self, asset: AssetId, key: String, metadata: Metadata) { + let hashed_key = sha256((asset, key)); + + match metadata { + Metadata::Int(data) => { + write(hashed_key, 0, data); + write(sha256((hashed_key, self.slot)), 0, 0); + }, + Metadata::B256(data) => { + write(hashed_key, 0, data); + write(sha256((hashed_key, self.slot)), 0, 1); + }, + Metadata::String(data) => { + let storage_string: StorageKey = StorageKey::new(hashed_key, 0, hashed_key); + storage_string.write_slice(data); + write(sha256((hashed_key, self.slot)), 0, 2); + }, + Metadata::Bytes(data) => { + let storage_bytes: StorageKey = StorageKey::new(hashed_key, 0, hashed_key); + storage_bytes.write_slice(data); + write(sha256((hashed_key, self.slot)), 0, 3); + } + } + } + + /// Returns metadata for a specific asset and key pair. + /// + /// # Arugments + /// + /// * `asset`: [AssetId] - The asset for the metadata to be queried. + /// * `key`: [String] - The key for the metadata to be queried. + /// + /// # Returns + /// + /// * [Option] - The stored metadata or `None`. + /// + /// # Number of Storage Accesses + /// + /// * Reads: `2` + /// + /// # Example + /// + /// ```sway + /// use src_7::Metadata; + /// use token::metdata::*; + /// + /// storage { + /// metadata: StorageMetadata = StorageMetadata {} + /// } + /// + /// fn foo(asset: AssetId, key: String) { + /// let returned_metadata = storage.metadata.get(asset, key, metadata); + /// assert(returned_metadata.is_some()); + /// } + /// ``` + #[storage(read)] + pub fn get(self, asset: AssetId, key: String) -> Option { + let hashed_key = sha256((asset, key)); + + match read::(sha256((hashed_key, self.slot)), 0) { + Option::Some(0) => { + Option::Some(Metadata::Int(read::(hashed_key, 0).unwrap())) + }, + Option::Some(1) => { + Option::Some(Metadata::B256(read::(hashed_key, 0).unwrap())) + }, + Option::Some(2) => { + let storage_string: StorageKey = StorageKey::new(hashed_key, 0, hashed_key); + Option::Some(Metadata::String(storage_string.read_slice().unwrap())) + }, + Option::Some(3) => { + let storage_bytes: StorageKey = StorageKey::new(hashed_key, 0, hashed_key); + Option::Some(Metadata::Bytes(storage_bytes.read_slice().unwrap())) + }, + _ => Option::None, + } + } +} + +/// Unconditionally stores metadata for a specific asset and key pair. +/// +/// # Arguments +/// +/// * `metadata_key`: [StorageKey] - The storage location for metadata. +/// * `asset`: [AssetId] - The asset for the metadata to be stored. +/// * `key`: [String] - The key for the metadata to be stored. +/// * `metadata`: [Metadata] - The metadata which to be stored. +/// +/// # Number of Storage Accesses +/// +/// * Writes: `2` +/// +/// # Example +/// +/// ```sway +/// use src_7::Metadata; +/// use token::metdata::*; +/// +/// storage { +/// metadata: StorageMetadata = StorageMetadata {} +/// } +/// +/// fn foo(asset: AssetId, key: String, metadata: Metadata) { +/// _set_metadata(storage.metadata, asset, key, metadata); +/// } +/// ``` +#[storage(read, write)] +pub fn _set_metadata( + metadata_key: StorageKey, + asset: AssetId, + key: String, + metadata: Metadata, +) { + metadata_key.insert(asset, key, metadata); +} + +abi SetTokenMetadata { + #[storage(read, write)] + fn set_metadata(asset: AssetId, key: String, metadata: Metadata); +} + +impl Metadata { + /// Returns the underlying metadata as a `String`. + /// + /// # Returns + /// + /// * [Option] - `Some` if the underlying type is a `String`, otherwise `None`. + /// + /// # Examples + /// + /// ```sway + /// use std::string::String; + /// use token::src_7::*; + /// use src_7::{SRC7, Metadata}; + /// + /// fn foo(contract_id: ContractId, asset: AssetId, key: String) { + /// let metadata_abi = abi(SRC7, contract_id); + /// let metadata = metadata_abi.metadata(asset, key); + /// + /// let string = metadata.unwrap().as_string(); + /// assert(string.len() != 0); + /// } + /// ``` + pub fn as_string(self) -> Option { + match self { + Self::String(data) => Option::Some(data), + _ => Option::None, + } + } + + /// Returns whether the underlying metadata is a `String`. + /// + /// # Returns + /// + /// * [bool] - `true` if the metadata is a `String`, otherwise `false`. + /// + /// # Examples + /// + /// ```sway + /// use std::string::String; + /// use token::src_7::*; + /// use src_7::{SRC7, Metadata}; + /// + /// fn foo(contract_id: ContractId, asset: AssetId, key: String) { + /// let metadata_abi = abi(SRC7, contract_id); + /// let metadata = metadata_abi.metadata(asset, key); + /// + /// assert(metadata.unwrap().is_string()); + /// } + /// ``` + pub fn is_string(self) -> bool { + match self { + Self::String(data) => true, + _ => false, + } + } + + /// Returns the underlying metadata as a `u64`. + /// + /// # Returns + /// + /// * [Option] - `Some` if the underlying type is a `u64`, otherwise `None`. + /// + /// # Examples + /// + /// ```sway + /// use std::string::String; + /// use token::src_7::*; + /// use src_7::{SRC7, Metadata}; + /// + /// fn foo(contract_id: ContractId, asset: AssetId, key: String) { + /// let metadata_abi = abi(SRC7, contract_id); + /// let metadata = metadata_abi.metadata(asset, key); + /// + /// let int = metadata.unwrap().as_u64(); + /// assert(int != 0); + /// } + /// ``` + pub fn as_u64(self) -> Option { + match self { + Self::Int(data) => Option::Some(data), + _ => Option::None, + } + } + + /// Returns whether the underlying metadata is a `u64`. + /// + /// # Returns + /// + /// * [bool] - `true` if the metadata is a `u64`, otherwise `false`. + /// + /// # Examples + /// + /// ```sway + /// use std::string::String; + /// use token::src_7::*; + /// use src_7::{SRC7, Metadata}; + /// + /// fn foo(contract_id: ContractId, asset: AssetId, key: String) { + /// let metadata_abi = abi(SRC7, contract_id); + /// let metadata = metadata_abi.metadata(asset, key); + /// + /// assert(metadata.unwrap().is_u64()); + /// } + /// ``` + pub fn is_u64(self) -> bool { + match self { + Self::Int(data) => true, + _ => false, + } + } + + /// Returns the underlying metadata as `Bytes`. + /// + /// # Returns + /// + /// * [Option] - `Some` if the underlying type is `Bytes`, otherwise `None`. + /// + /// # Examples + /// + /// ```sway + /// use std::{bytes::Bytes, string::String}; + /// use token::src_7::*; + /// use src_7::{SRC7, Metadata}; + /// + /// fn foo(contract_id: ContractId, asset: AssetId, key: String) { + /// let metadata_abi = abi(SRC7, contract_id); + /// let metadata = metadata_abi.metadata(asset, key); + /// + /// let bytes = metadata.unwrap().as_bytes(); + /// assert(bytes.len() != 0); + /// } + /// ``` + pub fn as_bytes(self) -> Option { + match self { + Self::Bytes(data) => Option::Some(data), + _ => Option::None, + } + } + + /// Returns whether the underlying metadata is `Bytes`. + /// + /// # Returns + /// + /// * [bool] - `true` if the metadata is `Bytes`, otherwise `false`. + /// + /// # Examples + /// + /// ```sway + /// use std::{bytes::Bytes, string::String}; + /// use token::src_7::*; + /// use src_7::{SRC7, Metadata}; + /// + /// fn foo(contract_id: ContractId, asset: AssetId, key: String) { + /// let metadata_abi = abi(SRC7, contract_id); + /// let metadata = metadata_abi.metadata(asset, key); + /// + /// assert(metadata.unwrap().is_bytes()); + /// } + /// ``` + pub fn is_bytes(self) -> bool { + match self { + Self::Bytes(data) => true, + _ => false, + } + } + + /// Returns the underlying metadata as a `b256`. + /// + /// # Returns + /// + /// * [Option] - `Some` if the underlying type is a `b256`, otherwise `None`. + /// + /// # Examples + /// + /// ```sway + /// use std::{constants::ZERO_B256, string::String}; + /// use token::src_7::*; + /// use src_7::{SRC7, Metadata}; + /// + /// fn foo(contract_id: ContractId, asset: AssetId, key: String) { + /// let metadata_abi = abi(SRC7, contract_id); + /// let metadata = metadata_abi.metadata(asset, key); + /// + /// let val = metadata.unwrap().as_b256(); + /// assert(val != ZERO_B256); + /// } + /// ``` + pub fn as_b256(self) -> Option { + match self { + Self::B256(data) => Option::Some(data), + _ => Option::None, + } + } + + /// Returns whether the underlying metadata is a `b256`. + /// + /// # Returns + /// + /// * [bool] - `true` if the metadata is a `b256`, otherwise `false`. + /// + /// # Examples + /// + /// ```sway + /// use std::string::String; + /// use token::src_7::*; + /// use src_7::{SRC7, Metadata}; + /// + /// fn foo(contract_id: ContractId, asset: AssetId, key: String) { + /// let metadata_abi = abi(SRC7, contract_id); + /// let metadata = metadata_abi.metadata(asset, key); + /// + /// assert(metadata.unwrap().is_b256()); + /// } + /// ``` + pub fn is_b256(self) -> bool { + match self { + Self::B256(data) => true, + _ => false, + } + } +} diff --git a/libs/token/src/mint.sw b/libs/token/src/mint.sw new file mode 100644 index 00000000..9f2dea4b --- /dev/null +++ b/libs/token/src/mint.sw @@ -0,0 +1,128 @@ +library; + +use ::errors::BurnError; +use ::base::{_total_assets, _total_supply}; +use std::{ + call_frames::{ + contract_id, + msg_asset_id, + }, + context::this_balance, + hash::{ + Hash, + sha256, + }, + storage::storage_string::*, + string::String, + token::{ + burn, + mint_to, + }, +}; + +/// Unconditionally mints new tokens using the `sub_id` sub-identifier. +/// +/// # Arguments +/// +/// * `total_assets_key`: [StorageKey] - The location in storage that the `u64` which represents the total assets is stored. +/// * `total_supply_key`: [StorageKey>] - The location in storage which the `StorageMap` that stores the total supply of assets is stored. +/// * `recipient`: [Identity] - The user to which the newly minted tokens are transferred to. +/// * `sub_id`: [SubId] - The sub-identifier of the newly minted token. +/// * `amount`: [u64] - The quantity of tokens to mint. +/// +/// # Returns +/// +/// * [AssetId] - The `AssetId` of the newly minted asset. +/// +/// # Number of Storage Accesses +/// +/// * Reads: `2` +/// * Writes: `2` +/// +/// # Examples +/// +/// ```sway +/// use token::_mint; +/// use std::{constants::ZERO_B256, context::balance_of}; +/// +/// storage { +/// total_assets: u64 = 0, +/// total_supply: StorageMap = StorageMap {}, +/// } +/// +/// fn foo(recipient: Identity) { +/// let recipient = Identity::ContractId(ContractId::from(ZERO_B256)); +/// let asset_id = _mint(storage.total_assets, storage.total_supply, recipient, ZERO_B256, 100); +/// assert(balance_of(recipient.as_contract_id(), asset_id), 100); +/// } +/// ``` +#[storage(read, write)] +pub fn _mint( + total_assets_key: StorageKey, + total_supply_key: StorageKey>, + recipient: Identity, + sub_id: SubId, + amount: u64, +) -> AssetId { + let asset_id = AssetId::new(contract_id(), sub_id); + let supply = _total_supply(total_supply_key, asset_id); + // Only increment the number of assets minted by this contract if it hasn't been minted before. + if supply.is_none() { + total_assets_key.write(_total_assets(total_assets_key) + 1); + } + let current_supply = supply.unwrap_or(0); + total_supply_key.insert(asset_id, current_supply + amount); + mint_to(recipient, sub_id, amount); + asset_id +} + +/// Burns tokens with the given `sub_id`. +/// +/// # Additional Information +/// +/// **Warning** This function burns tokens unequivocally. It does not check that tokens are sent to the calling contract. +/// +/// # Arguments +/// +/// * `total_assets_key`: [StorageKey] - The location in storage that the `u64` which represents the total assets is stored. +/// * `sub_id`: [SubId] - The sub-identifier of the token to burn. +/// * `amount`: [u64] - The quantity of tokens to burn. +/// +/// # Reverts +/// +/// * When the calling contract does not have enough tokens. +/// +/// # Number of Storage Accesses +/// +/// * Reads: `1` +/// * Writes: `1` +/// +/// # Examples +/// +/// ```sway +/// use token::_burn; +/// use std::{call_frames::contract_id, constants::ZERO_B256, context::balance_of}; +/// +/// storage { +/// total_supply: StorageMap = StorageMap {}, +/// } +/// +/// fn foo(asset_id: AssetId) { +/// assert(balance_of(contract_id(), asset_id) == 100); +/// _burn(storage.total_supply, ZERO_B256, 100); +/// assert(balance_of(contract_id(), asset_id) == 0); +/// } +/// ``` +#[storage(read, write)] +pub fn _burn( + total_supply_key: StorageKey>, + sub_id: SubId, + amount: u64, +) { + let asset_id = AssetId::new(contract_id(), sub_id); + require(this_balance(asset_id) >= amount, BurnError::NotEnoughTokens); + // If we pass the check above, we can assume it is safe to unwrap. + let supply = _total_supply(total_supply_key, asset_id).unwrap(); + total_supply_key.insert(asset_id, supply - amount); + burn(sub_id, amount); +} diff --git a/tests/src/token/Forc.toml b/tests/src/token/Forc.toml index cdf899d3..325fa696 100644 --- a/tests/src/token/Forc.toml +++ b/tests/src/token/Forc.toml @@ -7,4 +7,5 @@ name = "token_test" [dependencies] src_20 = { git = "https://github.com/FuelLabs/sway-standards", tag = "v0.1.1" } src_3 = { git = "https://github.com/FuelLabs/sway-standards", tag = "v0.1.1" } +src_7 = { git = "https://github.com/FuelLabs/sway-standards", tag = "v0.1.2" } token = { path = "../../../libs/token" } diff --git a/tests/src/token/src/main.sw b/tests/src/token/src/main.sw index 0a1eb7db..9fb63249 100644 --- a/tests/src/token/src/main.sw +++ b/tests/src/token/src/main.sw @@ -2,20 +2,30 @@ contract; use src_20::SRC20; use src_3::SRC3; +use src_7::{SRC7, Metadata}; use token::{ - _burn, - _decimals, - _mint, - _name, - _set_decimals, - _set_name, - _set_symbol, - _symbol, - _total_assets, - _total_supply, - SetTokenAttributes, + base::{ + _decimals, + _name, + _set_decimals, + _set_name, + _set_symbol, + _symbol, + _total_assets, + _total_supply, + SetTokenAttributes, + }, + mint::{ + _burn, + _mint, + }, + metadata::*, +}; +use std::{ + hash::Hash, + storage::storage_string::*, + string::String }; -use std::{hash::Hash, storage::storage_string::*, string::String}; storage { total_assets: u64 = 0, @@ -23,6 +33,7 @@ storage { name: StorageMap = StorageMap {}, symbol: StorageMap = StorageMap {}, decimals: StorageMap = StorageMap {}, + metadata: StorageMetadata = StorageMetadata {}, } impl SRC20 for Contract { @@ -64,6 +75,13 @@ impl SRC3 for Contract { } } +impl SRC7 for Contract { + #[storage(read)] + fn metadata(asset: AssetId, key: String) -> Option { + storage.metadata.get(asset, key) + } +} + impl SetTokenAttributes for Contract { #[storage(write)] fn set_name(asset: AssetId, name: String) { @@ -81,14 +99,23 @@ impl SetTokenAttributes for Contract { } } +impl SetTokenMetadata for Contract { + #[storage(read, write)] + fn set_metadata(asset: AssetId, key: String, metadata: Metadata) { + _set_metadata(storage.metadata, asset, key, metadata); + } +} + #[test] fn test_total_assets() { + use std::constants::ZERO_B256; + let src3_abi = abi(SRC3, CONTRACT_ID); let src20_abi = abi(SRC20, CONTRACT_ID); let recipient = Identity::ContractId(ContractId::from(CONTRACT_ID)); - let sub_id1 = 0x0000000000000000000000000000000000000000000000000000000000000000; - let sub_id2 = 0x0000000000000000000000000000000000000000000000000000000000000001; + let sub_id1 = 0x0000000000000000000000000000000000000000000000000000000000000001; + let sub_id2 = 0x0000000000000000000000000000000000000000000000000000000000000002; assert(src20_abi.total_assets() == 0); @@ -101,11 +128,13 @@ fn test_total_assets() { #[test] fn test_total_supply() { + use std::constants::ZERO_B256; + let src3_abi = abi(SRC3, CONTRACT_ID); let src20_abi = abi(SRC20, CONTRACT_ID); let recipient = Identity::ContractId(ContractId::from(CONTRACT_ID)); - let sub_id = 0x0000000000000000000000000000000000000000000000000000000000000000; + let sub_id = ZERO_B256; let asset_id = AssetId::new(ContractId::from(CONTRACT_ID), sub_id); assert(src20_abi.total_supply(asset_id).is_none()); @@ -119,11 +148,13 @@ fn test_total_supply() { #[test] fn test_name() { + use std::constants::ZERO_B256; + let src20_abi = abi(SRC20, CONTRACT_ID); let attributes_abi = abi(SetTokenAttributes, CONTRACT_ID); let recipient = Identity::ContractId(ContractId::from(CONTRACT_ID)); - let sub_id = 0x0000000000000000000000000000000000000000000000000000000000000000; + let sub_id = ZERO_B256; let asset_id = AssetId::new(ContractId::from(CONTRACT_ID), sub_id); let name = String::from_ascii_str("Fuel Token"); @@ -135,11 +166,13 @@ fn test_name() { #[test] fn test_symbol() { + use std::constants::ZERO_B256; + let src20_abi = abi(SRC20, CONTRACT_ID); let attributes_abi = abi(SetTokenAttributes, CONTRACT_ID); let recipient = Identity::ContractId(ContractId::from(CONTRACT_ID)); - let sub_id = 0x0000000000000000000000000000000000000000000000000000000000000000; + let sub_id = ZERO_B256; let asset_id = AssetId::new(ContractId::from(CONTRACT_ID), sub_id); let symbol = String::from_ascii_str("FUEL"); @@ -151,11 +184,13 @@ fn test_symbol() { #[test] fn test_decimals() { + use std::constants::ZERO_B256; + let src20_abi = abi(SRC20, CONTRACT_ID); let attributes_abi = abi(SetTokenAttributes, CONTRACT_ID); let recipient = Identity::ContractId(ContractId::from(CONTRACT_ID)); - let sub_id = 0x0000000000000000000000000000000000000000000000000000000000000000; + let sub_id = ZERO_B256; let asset_id = AssetId::new(ContractId::from(CONTRACT_ID), sub_id); let decimals = 8u8; @@ -168,12 +203,13 @@ fn test_decimals() { #[test] fn test_mint() { use std::context::balance_of; + use std::constants::ZERO_B256; let src3_abi = abi(SRC3, CONTRACT_ID); let src20_abi = abi(SRC20, CONTRACT_ID); let recipient = Identity::ContractId(ContractId::from(CONTRACT_ID)); - let sub_id = 0x0000000000000000000000000000000000000000000000000000000000000000; + let sub_id = ZERO_B256; let asset_id = AssetId::new(ContractId::from(CONTRACT_ID), sub_id); assert(balance_of(ContractId::from(CONTRACT_ID), asset_id) == 0); @@ -185,12 +221,13 @@ fn test_mint() { #[test] fn test_burn() { use std::context::balance_of; + use std::constants::ZERO_B256; let src3_abi = abi(SRC3, CONTRACT_ID); let src20_abi = abi(SRC20, CONTRACT_ID); let recipient = Identity::ContractId(ContractId::from(CONTRACT_ID)); - let sub_id = 0x0000000000000000000000000000000000000000000000000000000000000000; + let sub_id = ZERO_B256; let asset_id = AssetId::new(ContractId::from(CONTRACT_ID), sub_id); src3_abi.mint(recipient, sub_id, 10); @@ -199,3 +236,139 @@ fn test_burn() { src3_abi.burn(sub_id, 10); assert(balance_of(ContractId::from(CONTRACT_ID), asset_id) == 0); } + +#[test] +fn test_metadata_as_string() { + let data_string = String::from_ascii_str("Fuel is blazingly fast"); + let metadata = Metadata::String(data_string); + + assert(data_string == metadata.as_string().unwrap()); +} + +#[test] +fn test_metadata_is_string() { + let data_string = String::from_ascii_str("Fuel is blazingly fast"); + let metadata = Metadata::String(data_string); + + assert(metadata.is_string()); +} + +#[test] +fn test_metadata_as_u64() { + let data_int = 1; + let metadata = Metadata::Int(data_int); + + assert(data_int == metadata.as_u64().unwrap()); +} + +#[test] +fn test_metadata_is_u64() { + let data_int = 1; + let metadata = Metadata::Int(data_int); + + assert(metadata.is_u64()); +} + +#[test] +fn test_metadata_as_bytes() { + let data_bytes = String::from_ascii_str("Fuel is blazingly fast").bytes; + let metadata = Metadata::Bytes(data_bytes); + + assert(data_bytes == metadata.as_bytes().unwrap()); +} + +#[test] +fn test_metadata_is_bytes() { + let data_bytes = String::from_ascii_str("Fuel is blazingly fast").bytes; + let metadata = Metadata::Bytes(data_bytes); + + assert(metadata.is_bytes()); +} + +#[test] +fn test_metadata_as_b256() { + let data_b256 = 0x0000000000000000000000000000000000000000000000000000000000000001; + let metadata = Metadata::B256(data_b256); + + assert(data_b256 == metadata.as_b256().unwrap()); +} + +#[test] +fn test_metadata_is_b256() { + let data_b256 = 0x0000000000000000000000000000000000000000000000000000000000000001; + let metadata = Metadata::B256(data_b256); + + assert(metadata.is_b256()); +} + +#[test] +fn test_set_metadata_b256() { + use std::constants::ZERO_B256; + + let data_b256 = 0x0000000000000000000000000000000000000000000000000000000000000001; + let metadata = Metadata::B256(data_b256); + let asset_id = AssetId::new(ContractId::from(CONTRACT_ID), ZERO_B256); + let src7_abi = abi(SRC7, CONTRACT_ID); + let set_metadata_abi = abi(SetTokenMetadata, CONTRACT_ID); + let key = String::from_ascii_str("my_key"); + + set_metadata_abi.set_metadata(asset_id, key, metadata); + + let returned_metadata = src7_abi.metadata(asset_id, key); + assert(returned_metadata.is_some()); + assert(returned_metadata.unwrap() == metadata); +} + +#[test] +fn test_set_metadata_u64() { + use std::constants::ZERO_B256; + + let data_int = 1; + let metadata = Metadata::Int(data_int); + let asset_id = AssetId::new(ContractId::from(CONTRACT_ID), ZERO_B256); + let src7_abi = abi(SRC7, CONTRACT_ID); + let set_metadata_abi = abi(SetTokenMetadata, CONTRACT_ID); + let key = String::from_ascii_str("my_key"); + + set_metadata_abi.set_metadata(asset_id, key, metadata); + + let returned_metadata = src7_abi.metadata(asset_id, key); + assert(returned_metadata.is_some()); + assert(returned_metadata.unwrap() == metadata); +} + +#[test] +fn test_set_metadata_string() { + use std::constants::ZERO_B256; + + let data_string = String::from_ascii_str("Fuel is blazingly fast"); + let metadata = Metadata::String(data_string); + let asset_id = AssetId::new(ContractId::from(CONTRACT_ID), ZERO_B256); + let src7_abi = abi(SRC7, CONTRACT_ID); + let set_metadata_abi = abi(SetTokenMetadata, CONTRACT_ID); + let key = String::from_ascii_str("my_key"); + + set_metadata_abi.set_metadata(asset_id, key, metadata); + + let returned_metadata = src7_abi.metadata(asset_id, key); + assert(returned_metadata.is_some()); + assert(returned_metadata.unwrap() == metadata); +} + +#[test] +fn test_set_metadata_bytes() { + use std::constants::ZERO_B256; + + let data_bytes = String::from_ascii_str("Fuel is blazingly fast").bytes; + let metadata = Metadata::Bytes(data_bytes); + let asset_id = AssetId::new(ContractId::from(CONTRACT_ID), ZERO_B256); + let src7_abi = abi(SRC7, CONTRACT_ID); + let set_metadata_abi = abi(SetTokenMetadata, CONTRACT_ID); + let key = String::from_ascii_str("my_key"); + + set_metadata_abi.set_metadata(asset_id, key, metadata); + + let returned_metadata = src7_abi.metadata(asset_id, key); + assert(returned_metadata.is_some()); + assert(returned_metadata.unwrap() == metadata); +}