From 65e09f95ea8b9476b171a66c8a47108f352fa32c Mon Sep 17 00:00:00 2001 From: Cameron Carstens Date: Tue, 27 Aug 2024 15:12:14 +0800 Subject: [PATCH] Release v0.6.0 (#140) * Hotfix: Update Cargo.toml to v0.5.1 (#115) * Hotfix: Update Cargo.toml to v0.5.1 * Udpate CHANGELOG * fix vault reads * add changelog * move changelog to new empty changelog * add "unreleased" to changelog heads * remove date * add v0.5.1 * SRC-6 example contract does not update managed assets (#122) * Update SRC-6 example with decrementation of managed assets * Update CHANGELOG * Fix link on CHANGELOG.md * Update CHANGELOG to resolve markdown error with duplicate headers * Write to storage * Use new namespace syntax for storage (#120) * chore: fix compiler warnings * remove for examples * Update changelog * Prepare master for v0.5.2 release (#126) * Prepare for v0.5.2 release * Update CHANGELOG * Update CHANGELOG formatting * Make the `SubId` an `Option` in SRC-3's `mint()` function (#131) * Update specifications to change SRC-3 mint sub_id to an Option * Update SRC-3 standard for option in mint * Update examples * Udpate CHANGELOG * Run formatter * Fix spelling * Add event logging to SRC-20 and SRC-7 standards (#130) * Add event logging to SRC-20 and SRC-7 specification * Add event logging structs to SRC-20 and SRC-7 * Update CHANGELOG * Add inline docs to SRC-20 events * Fix CI * Require that logs of metadata are emitted even with contants * Update standards with additional log and ordering * Update examples to follow new specs * Add custom word to spell checker * Run formatter * Fix markdown formatting * Resolve warnings in examples * Build CI with release * Store srv7 metadata to storage * Remove cancel in progress from CI * Update name for TotalSupplyEvent * Split examples into seperate workspace projects * Prepare for v0.6.0 release * Update CHANGELOG * Add trailing line in CHANGELOG --------- Co-authored-by: SwayStar123 Co-authored-by: SwayStar123 <46050679+SwayStar123@users.noreply.github.com> Co-authored-by: IGI-111 Co-authored-by: Sophie Co-authored-by: K1-R1 <77465250+K1-R1@users.noreply.github.com> --- .github/workflows/ci.yaml | 58 +++++++++- CHANGELOG.md | 32 +++++- Cargo.toml | 2 +- README.md | 2 +- docs/spell-check-custom-words.txt | 7 +- docs/src/index.md | 2 +- docs/src/src-20-native-asset.md | 87 ++++++++++++++ docs/src/src-3-minting-and-burning.md | 9 +- docs/src/src-7-asset-metadata.md | 29 +++++ examples/Forc.toml | 20 ---- examples/src11-security-information/Forc.toml | 2 + examples/src12-contract-factory/Forc.toml | 2 + examples/src14-simple-proxy/Forc.toml | 2 + examples/src20-native-asset/Forc.toml | 2 + .../multi_asset/src/multi_asset.sw | 55 ++++++++- .../single_asset/src/single_asset.sw | 40 ++++++- examples/src3-mint-burn/Forc.toml | 2 + .../multi_asset/src/multi_asset.sw | 70 ++++++++++-- .../single_asset/src/single_asset.sw | 65 +++++++++-- examples/src5-ownership/Forc.toml | 2 + examples/src6-vault/Forc.toml | 6 + examples/src7-metadata/Forc.toml | 2 + .../multi_asset/src/multi_asset.sw | 107 +++++++++++++++++- .../single_asset/src/single_asset.sw | 84 +++++++++++++- standards/src/src20.sw | 40 +++++++ standards/src/src3.sw | 4 +- standards/src/src7.sw | 12 ++ 27 files changed, 691 insertions(+), 54 deletions(-) delete mode 100644 examples/Forc.toml create mode 100644 examples/src11-security-information/Forc.toml create mode 100644 examples/src12-contract-factory/Forc.toml create mode 100644 examples/src14-simple-proxy/Forc.toml create mode 100644 examples/src20-native-asset/Forc.toml create mode 100644 examples/src3-mint-burn/Forc.toml create mode 100644 examples/src5-ownership/Forc.toml create mode 100644 examples/src6-vault/Forc.toml create mode 100644 examples/src7-metadata/Forc.toml diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index a133f9e3..a8f67d15 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -21,7 +21,7 @@ env: PATH_TO_SCRIPTS: .github/scripts jobs: - build-sway-lib: + build-sway-standards: runs-on: ubuntu-latest steps: @@ -61,10 +61,60 @@ jobs: run: forc fmt --path standards --check - name: Build All Standards - run: forc build --error-on-warnings --path standards + run: forc build --error-on-warnings --path standards --release + + build-examples: + runs-on: ubuntu-latest + + strategy: + matrix: + project: + [ + "examples/src3-mint-burn", + "examples/src5-ownership", + "examples/src6-vault", + "examples/src7-metadata", + "examples/src11-security-information", + "examples/src12-contract-factory", + "examples/src14-simple-proxy", + "examples/src20-native-asset", + ] + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + + - name: Install Rust toolchain + uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: ${{ env.RUST_VERSION }} + override: true + + - name: Init cache + uses: Swatinem/rust-cache@v1 + + - name: Install a modern linker (mold) + uses: rui314/setup-mold@v1 + + - name: Force Rust to use mold globally for compilation + run: | + touch ~/.cargo/config.toml + echo "[target.x86_64-unknown-linux-gnu]" > ~/.cargo/config.toml + echo 'linker = "clang"' >> ~/.cargo/config.toml + echo 'rustflags = ["-C", "link-arg=-fuse-ld=/usr/local/bin/mold"]' >> ~/.cargo/config.toml + + - name: Install rustfmt + run: rustup component add rustfmt + + - name: Install Fuel toolchain + uses: FuelLabs/action-fuel-toolchain@v0.6.0 + with: + name: my-toolchain + components: forc@${{ env.FORC_VERSION }}, fuel-core@${{ env.CORE_VERSION }} - name: Check Sway Formatting Examples - run: forc fmt --path examples --check + run: forc fmt --path ${{ matrix.project }} --check - name: Build All Examples - run: forc build --path examples + run: forc build --path ${{ matrix.project }} --release diff --git a/CHANGELOG.md b/CHANGELOG.md index a248bf62..3488ed6e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] +Description of the upcoming release here. + ### Added Unreleased - Something new here 1 @@ -22,11 +24,39 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - Some fix here 1 - Some fix here 2 -### Breaking Unreleased +#### Breaking Unreleased - Some breaking change here 1 - Some breaking change here 2 +## [Version 0.6.0] + +### Added v0.6.0 + +- [#130](https://github.com/FuelLabs/sway-standards/pull/130) Adds the `SetNameEvent`, `SetSymbolEvent`, `SetDecimalsEvent` and `TotalSupplyEvent` to the SRC-20 standard. +- [#130](https://github.com/FuelLabs/sway-standards/pull/130) Adds the `SetMetadataEvent` to the SRC-7 standard. + +### Changed v0.6.0 + +- [#130](https://github.com/FuelLabs/sway-standards/pull/130) Splits examples into seperate workspace projects for improved continuous integration. +- [#139](https://github.com/FuelLabs/sway-standards/pull/139) Prepares for the v0.6.0 release. + +### Breaking v0.6.0 + +- [#131](https://github.com/FuelLabs/sway-standards/pull/131) Makes the SRC-3 `mint()` function's `SubId` argument an `Option`. + +Before: + +```sway +mint(Identity::Address(Address::zero()), SubId::zero(), 100); +``` + +After: + +```sway +mint(Identity::Address(Address::zero()), Some(SubId::zero()), 100); +``` + ## [Version 0.5.2] ### Changed v0.5.2 diff --git a/Cargo.toml b/Cargo.toml index 2891dc90..00b54aa1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,4 +1,4 @@ [package] name = "sway-standards" -version = "0.5.2" +version = "0.6.0" edition = "2021" diff --git a/README.md b/README.md index ae45a846..0848b8db 100644 --- a/README.md +++ b/README.md @@ -65,7 +65,7 @@ If you don't find what you're looking for, feel free to create an issue and prop To import a standard the following should be added to the project's `Forc.toml` file under `[dependencies]` with the most recent release: ```toml -standards = { git = "https://github.com/FuelLabs/sway-standards", tag = "v0.5.2" } +standards = { git = "https://github.com/FuelLabs/sway-standards", tag = "v0.6.0" } ``` > **NOTE:** diff --git a/docs/spell-check-custom-words.txt b/docs/spell-check-custom-words.txt index e85a196d..e2cb87cd 100644 --- a/docs/spell-check-custom-words.txt +++ b/docs/spell-check-custom-words.txt @@ -260,4 +260,9 @@ OGA glTF GLB Uninitalized -upgradeability \ No newline at end of file +upgradeability +SetMetadataEvent +SetNameEvent +SetSymbolEvent +SetDecimalsEvent +UpdateTotalSupplyEvent \ No newline at end of file diff --git a/docs/src/index.md b/docs/src/index.md index 3935ca0b..cc260e07 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -14,7 +14,7 @@ If you don't find what you're looking for, feel free to create an issue and prop To import a standard the following should be added to the project's `Forc.toml` file under `[dependencies]` with the most recent release: ```toml -standards = { git = "https://github.com/FuelLabs/sway-standards", tag = "v0.5.2" } +standards = { git = "https://github.com/FuelLabs/sway-standards", tag = "v0.6.0" } ``` > **NOTE:** diff --git a/docs/src/src-20-native-asset.md b/docs/src/src-20-native-asset.md index 2e02fa84..9a89c14e 100644 --- a/docs/src/src-20-native-asset.md +++ b/docs/src/src-20-native-asset.md @@ -47,6 +47,93 @@ Non-Fungible Tokens (NFT) or Non-Fungible Assets on Fuel are Native Assets and t * Non-Fungible Assets SHALL have a total supply of one per asset. * Non-Fungible Assets SHALL have a decimal of `0u8`. +### Logging + +The following logs MUST be implemented and emitted to follow the SRC-20 standard. + +* IF a value is updated via a function call, a log MUST be emitted. +* IF a value is embedded in a contract as a constant, configurable, or other manner, an event MUST be emitted at least once. + +#### SetNameEvent + +The `SetNameEvent` MUST be emitted when the name of an asset has updated. + +There SHALL be the following fields in the `SetNameEvent` struct: + +* `asset`: The `asset` field SHALL be used for the corresponding `AssetId` of the asset has been updated. +* `name`: The `name` field SHALL be used for the corresponding `Option` which represents the name of the asset. +* `sender`: The `sender` field SHALL be used for the corresponding `Identity` which made the function call that has updated the name of the asset. + +Example: + +```sway +pub struct SetNameEvent { + pub asset: AssetId, + pub name: Option, + pub sender: Identity, +} +``` + +#### SetSymbolEvent + +The `SetSymbolEvent` MUST be emitted when the symbol of an asset has updated. + +There SHALL be the following fields in the `SetSymbolEvent` struct: + +* `asset`: The `asset` field SHALL be used for the corresponding `AssetId` of the asset has been updated. +* `symbol`: The `symbol` field SHALL be used for the corresponding `Option` which represents the symbol of the asset. +* `sender`: The `sender` field SHALL be used for the corresponding `Identity` which made the function call that has updated the symbol of the asset. + +Example: + +```sway +pub struct SetSymbolEvent { + pub asset: AssetId, + pub symbol: Option, + pub sender: Identity, +} +``` + +#### SetDecimalsEvent + +The `SetDecimalsEvent` MUST be emitted when the decimals of an asset has updated. + +There SHALL be the following fields in the `SetDecimalsEvent` struct: + +* `asset`: The `asset` field SHALL be used for the corresponding `AssetId` of the asset has been updated. +* `decimals`: The `decimals` field SHALL be used for the corresponding `u8` which represents the decimals of the asset. +* `sender`: The `sender` field SHALL be used for the corresponding `Identity` which made the function call that has updated the decimals of the asset. + +Example: + +```sway +pub struct SetDecimalsEvent { + pub asset: AssetId, + pub decimals: u8, + pub sender: Identity, +} +``` + +#### UpdateTotalSupplyEvent + +The `UpdateTotalSupplyEvent` MUST be emitted when the total supply of an asset has updated. + +There SHALL be the following fields in the `UpdateTotalSupplyEvent` struct: + +* `asset`: The `asset` field SHALL be used for the corresponding `AssetId` of the asset has been updated. +* `supply`: The `supply` field SHALL be used for the corresponding `u64` which represents the total supply of the asset. +* `sender`: The `sender` field SHALL be used for the corresponding `Identity` which made the function call that has updated the total supply of the asset. + +Example: + +```sway +pub struct UpdateTotalSupplyEvent { + pub asset: AssetId, + pub supply: u64, + pub sender: Identity, +} +``` + ## Rationale As the SRC-20 Native Asset Standard leverages Native Assets on Fuel, we do not require the implementation of certain functions such as transfer or approval. This is done directly within the FuelVM and there is no smart contract that requires updating of balances. As Fuel is UTXO based, any transfer events may be indexed on transaction receipts. diff --git a/docs/src/src-3-minting-and-burning.md b/docs/src/src-3-minting-and-burning.md index 53fd3525..c36b1771 100644 --- a/docs/src/src-3-minting-and-burning.md +++ b/docs/src/src-3-minting-and-burning.md @@ -16,15 +16,16 @@ Minting and burning were initially added to the [SRC-20](./src-20-native-asset.m The following functions MUST be implemented to follow the SRC-3 standard: -#### `fn mint(recipient: Identity, sub_id: SubId, amount: u64)` +#### `fn mint(recipient: Identity, sub_id: Option, amount: u64)` -This function MUST mint `amount` coins with sub-identifier `sub_id` and transfer them to the `recipient`. +This function MUST mint `amount` coins with a sub-identifier and transfer them to the `recipient`. +This function MUST use the `sub_id` as the sub-identifier IF `sub_id` is `Some`, otherwise this function MUST assign a `SubId` if the `sub_id` argument is `None`. This function MAY contain arbitrary conditions for minting, and revert if those conditions are not met. ##### Mint Arguments * `recipient` - The `Identity` to which the newly minted asset is transferred to. -* `sub_id` - The sub-identifier of the asset to mint. +* `sub_id` - The sub-identifier of the asset to mint. If this is `None`, a `SubId` MUST be assigned. * `amount` - The quantity of coins to mint. #### `fn burn(sub_id: SubId, amount: u64)` @@ -57,7 +58,7 @@ The burn function may also introduce a security consideration if the total suppl ```sway abi MySRC3Asset { #[storage(read, write)] - fn mint(recipient: Identity, sub_id: SubId, amount: u64); + fn mint(recipient: Identity, sub_id: Option, amount: u64); #[payable] #[storage(read, write)] fn burn(sub_id: SubId, amount: u64); diff --git a/docs/src/src-7-asset-metadata.md b/docs/src/src-7-asset-metadata.md index 0dcb7df2..001749d8 100644 --- a/docs/src/src-7-asset-metadata.md +++ b/docs/src/src-7-asset-metadata.md @@ -44,6 +44,35 @@ The `String` variant SHALL be used when the stored metadata for the correspondin This function MUST return valid metadata for the corresponding `asset` and `key`, where the data is either a `B256`, `Bytes`, `Int`, or `String` variant. If the asset does not exist or no metadata exists, the function MUST return `None`. +### Logging + +The following logs MUST be implemented and emitted to follow the SRC-7 standard. + +* IF a value is updated via a function call, a log MUST be emitted. +* IF a value is embedded in a contract as a constant, configurable, or other manner, an event MUST be emitted at least once. + +#### SetMetadataEvent + +The `SetMetadataEvent` MUST be emitted when the metadata of an asset has updated. + +There SHALL be the following fields in the `SetMetadataEvent` struct: + +* `asset`: The `asset` field SHALL be used for the corresponding `AssetId` of the asset has been updated. +* `metadata`: The `metadata` field SHALL be used for the corresponding `Option` which represents the metadata of the asset. +* `key`: The `key` field SHALL be used for the corresponding `String` which represents the key used for storing the metadata. +* `sender`: The `sender` field SHALL be used for the corresponding `Identity` which made the function call that has updated the metadata of the asset. + +Example: + +```sway +pub struct SetMetadataEvent { + pub asset: AssetId, + pub metadata: Option, + pub key: String, + pub sender: Identity, +} +``` + ## Rationale The SRC-7 standard should allow for data-rich assets to interact with one another in a safe manner. diff --git a/examples/Forc.toml b/examples/Forc.toml deleted file mode 100644 index 4f5338f6..00000000 --- a/examples/Forc.toml +++ /dev/null @@ -1,20 +0,0 @@ -[workspace] -members = [ - "src3-mint-burn/single_asset", - "src3-mint-burn/multi_asset", - "src5-ownership/initialized_example", - "src5-ownership/uninitialized_example", - "src6-vault/multi_asset_vault", - "src6-vault/single_asset_single_sub_vault", - "src6-vault/single_asset_vault", - "src7-metadata/single_asset", - "src7-metadata/multi_asset", - "src11-security-information/hardcoded-information", - "src11-security-information/variable-information", - "src12-contract-factory/with_configurables", - "src12-contract-factory/without_configurables", - "src14-simple-proxy/owned", - "src14-simple-proxy/minimal", - "src20-native-asset/single_asset", - "src20-native-asset/multi_asset", -] diff --git a/examples/src11-security-information/Forc.toml b/examples/src11-security-information/Forc.toml new file mode 100644 index 00000000..343d46d1 --- /dev/null +++ b/examples/src11-security-information/Forc.toml @@ -0,0 +1,2 @@ +[workspace] +members = ["hardcoded-information", "variable-information"] diff --git a/examples/src12-contract-factory/Forc.toml b/examples/src12-contract-factory/Forc.toml new file mode 100644 index 00000000..9d957dfe --- /dev/null +++ b/examples/src12-contract-factory/Forc.toml @@ -0,0 +1,2 @@ +[workspace] +members = ["with_configurables", "without_configurables"] diff --git a/examples/src14-simple-proxy/Forc.toml b/examples/src14-simple-proxy/Forc.toml new file mode 100644 index 00000000..6fcc521f --- /dev/null +++ b/examples/src14-simple-proxy/Forc.toml @@ -0,0 +1,2 @@ +[workspace] +members = ["owned", "minimal"] diff --git a/examples/src20-native-asset/Forc.toml b/examples/src20-native-asset/Forc.toml new file mode 100644 index 00000000..c75476cc --- /dev/null +++ b/examples/src20-native-asset/Forc.toml @@ -0,0 +1,2 @@ +[workspace] +members = ["single_asset", "multi_asset"] diff --git a/examples/src20-native-asset/multi_asset/src/multi_asset.sw b/examples/src20-native-asset/multi_asset/src/multi_asset.sw index e20a95e8..62b3ad47 100644 --- a/examples/src20-native-asset/multi_asset/src/multi_asset.sw +++ b/examples/src20-native-asset/multi_asset/src/multi_asset.sw @@ -1,6 +1,6 @@ contract; -use standards::src20::SRC20; +use standards::src20::{SetDecimalsEvent, SetNameEvent, SetSymbolEvent, SRC20, TotalSupplyEvent,}; use std::{hash::Hash, storage::storage_string::*, string::String}; storage { @@ -171,3 +171,56 @@ impl SRC20 for Contract { storage.decimals.get(asset).try_read() } } + +abi SetSRC20Data { + #[storage(read)] + fn set_src20_data( + asset: AssetId, + total_supply: u64, + name: String, + symbol: String, + decimals: u8, + ); +} + +impl SetSRC20Data for Contract { + #[storage(read)] + fn set_src20_data( + asset: AssetId, + supply: u64, + name: String, + symbol: String, + decimals: u8, + ) { + // NOTE: There are no checks for if the caller has permissions to update the metadata + // If this asset does not exist, revert + if storage.total_supply.get(asset).try_read().is_none() { + revert(0); + } + let sender = msg_sender().unwrap(); + + log(SetNameEvent { + asset, + name: Some(name), + sender, + }); + + log(SetSymbolEvent { + asset, + symbol: Some(symbol), + sender, + }); + + log(SetDecimalsEvent { + asset, + decimals, + sender, + }); + + log(TotalSupplyEvent { + asset, + supply, + sender, + }); + } +} diff --git a/examples/src20-native-asset/single_asset/src/single_asset.sw b/examples/src20-native-asset/single_asset/src/single_asset.sw index a2c51bfa..81b0cdf5 100644 --- a/examples/src20-native-asset/single_asset/src/single_asset.sw +++ b/examples/src20-native-asset/single_asset/src/single_asset.sw @@ -1,7 +1,7 @@ contract; -use standards::src20::SRC20; -use std::string::String; +use standards::src20::{SetDecimalsEvent, SetNameEvent, SetSymbolEvent, SRC20, TotalSupplyEvent,}; +use std::{auth::msg_sender, string::String}; configurable { /// The total supply of coins for the asset minted by this contract. @@ -165,3 +165,39 @@ impl SRC20 for Contract { } } } + +abi EmitSRC20Events { + fn emit_src20_events(); +} + +impl EmitSRC20Events for Contract { + fn emit_src20_events() { + // Metadata that is stored as a configurable should only be emitted once. + let asset = AssetId::default(); + let sender = msg_sender().unwrap(); + + log(SetNameEvent { + asset, + name: Some(String::from_ascii_str(from_str_array(NAME))), + sender, + }); + + log(SetSymbolEvent { + asset, + symbol: Some(String::from_ascii_str(from_str_array(SYMBOL))), + sender, + }); + + log(SetDecimalsEvent { + asset, + decimals: DECIMALS, + sender, + }); + + log(TotalSupplyEvent { + asset, + supply: TOTAL_SUPPLY, + sender, + }); + } +} diff --git a/examples/src3-mint-burn/Forc.toml b/examples/src3-mint-burn/Forc.toml new file mode 100644 index 00000000..c75476cc --- /dev/null +++ b/examples/src3-mint-burn/Forc.toml @@ -0,0 +1,2 @@ +[workspace] +members = ["single_asset", "multi_asset"] diff --git a/examples/src3-mint-burn/multi_asset/src/multi_asset.sw b/examples/src3-mint-burn/multi_asset/src/multi_asset.sw index b8af9ba7..02e19670 100644 --- a/examples/src3-mint-burn/multi_asset/src/multi_asset.sw +++ b/examples/src3-mint-burn/multi_asset/src/multi_asset.sw @@ -1,12 +1,23 @@ contract; -use standards::{src20::SRC20, src3::SRC3}; +use standards::{ + src20::{ + SetDecimalsEvent, + SetNameEvent, + SetSymbolEvent, + SRC20, + TotalSupplyEvent, + }, + src3::SRC3, +}; use std::{ asset::{ burn, mint_to, }, + auth::msg_sender, call_frames::msg_asset_id, + constants::DEFAULT_SUB_ID, context::msg_amount, hash::Hash, storage::storage_string::*, @@ -36,7 +47,7 @@ impl SRC3 for Contract { /// # Arguments /// /// * `recipient`: [Identity] - The user to which the newly minted asset is transferred to. - /// * `sub_id`: [SubId] - The sub-identifier of the newly minted asset. + /// * `sub_id`: [Option] - The sub-identifier of the newly minted asset. /// * `amount`: [u64] - The quantity of coins to mint. /// /// # Number of Storage Accesses @@ -52,11 +63,15 @@ impl SRC3 for Contract { /// /// fn foo(contract_id: ContractId) { /// let contract_abi = abi(SRC3, contract_id); - /// contract_abi.mint(Identity::ContractId(contract_id), DEFAULT_SUB_ID, 100); + /// contract_abi.mint(Identity::ContractId(contract_id), Some(DEFAULT_SUB_ID), 100); /// } /// ``` #[storage(read, write)] - fn mint(recipient: Identity, sub_id: SubId, amount: u64) { + fn mint(recipient: Identity, sub_id: Option, amount: u64) { + let sub_id = match sub_id { + Some(s) => s, + None => DEFAULT_SUB_ID, + }; let asset_id = AssetId::new(ContractId::this(), sub_id); // If this SubId is new, increment the total number of distinguishable assets this contract has minted. @@ -69,9 +84,15 @@ impl SRC3 for Contract { } // Increment total supply of the asset and mint to the recipient. - storage - .total_supply - .insert(asset_id, amount + asset_supply.unwrap_or(0)); + let new_supply = amount + asset_supply.unwrap_or(0); + storage.total_supply.insert(asset_id, new_supply); + + log(TotalSupplyEvent { + asset: asset_id, + supply: new_supply, + sender: msg_sender().unwrap(), + }); + mint_to(recipient, sub_id, amount); } @@ -158,3 +179,38 @@ impl SRC20 for Contract { } } } + +abi SetSRC20Data { + #[storage(read)] + fn set_src20_data(asset: AssetId); +} + +impl SetSRC20Data for Contract { + #[storage(read)] + fn set_src20_data(asset: AssetId) { + // NOTE: There are no checks for if the caller has permissions to update the metadata + // If this asset does not exist, revert + if storage.total_supply.get(asset).try_read().is_none() { + revert(0); + } + let sender = msg_sender().unwrap(); + + log(SetNameEvent { + asset, + name: Some(String::from_ascii_str(from_str_array(NAME))), + sender, + }); + + log(SetSymbolEvent { + asset, + symbol: Some(String::from_ascii_str(from_str_array(SYMBOL))), + sender, + }); + + log(SetDecimalsEvent { + asset, + decimals: DECIMALS, + sender, + }); + } +} diff --git a/examples/src3-mint-burn/single_asset/src/single_asset.sw b/examples/src3-mint-burn/single_asset/src/single_asset.sw index d21b31cd..237d0505 100644 --- a/examples/src3-mint-burn/single_asset/src/single_asset.sw +++ b/examples/src3-mint-burn/single_asset/src/single_asset.sw @@ -1,11 +1,21 @@ contract; -use standards::{src20::SRC20, src3::SRC3}; +use standards::{ + src20::{ + SetDecimalsEvent, + SetNameEvent, + SetSymbolEvent, + SRC20, + TotalSupplyEvent, + }, + src3::SRC3, +}; use std::{ asset::{ burn, mint_to, }, + auth::msg_sender, call_frames::msg_asset_id, constants::DEFAULT_SUB_ID, context::msg_amount, @@ -52,17 +62,28 @@ impl SRC3 for Contract { /// /// fn foo(contract_id: ContractId) { /// let contract_abi = abi(SRC3, contract); - /// contract_abi.mint(Identity::ContractId(contract_id), DEFAULT_SUB_ID, 100); + /// contract_abi.mint(Identity::ContractId(contract_id), Some(DEFAULT_SUB_ID), 100); /// } /// ``` #[storage(read, write)] - fn mint(recipient: Identity, sub_id: SubId, amount: u64) { - require(sub_id == DEFAULT_SUB_ID, "Incorrect Sub Id"); + fn mint(recipient: Identity, sub_id: Option, amount: u64) { + require( + sub_id + .is_some() && sub_id + .unwrap() == DEFAULT_SUB_ID, + "Incorrect Sub Id", + ); // Increment total supply of the asset and mint to the recipient. - storage - .total_supply - .write(amount + storage.total_supply.read()); + let new_supply = amount + storage.total_supply.read(); + storage.total_supply.write(new_supply); + + log(TotalSupplyEvent { + asset: AssetId::new(ContractId::this(), DEFAULT_SUB_ID), + supply: new_supply, + sender: msg_sender().unwrap(), + }); + mint_to(recipient, DEFAULT_SUB_ID, amount); } @@ -160,3 +181,33 @@ impl SRC20 for Contract { } } } + +abi EmitSRC20Events { + fn emit_src20_events(); +} + +impl EmitSRC20Events for Contract { + fn emit_src20_events() { + // Metadata that is stored as a configurable should only be emitted once. + let asset = AssetId::default(); + let sender = msg_sender().unwrap(); + + log(SetNameEvent { + asset, + name: Some(String::from_ascii_str(from_str_array(NAME))), + sender, + }); + + log(SetSymbolEvent { + asset, + symbol: Some(String::from_ascii_str(from_str_array(SYMBOL))), + sender, + }); + + log(SetDecimalsEvent { + asset, + decimals: DECIMALS, + sender, + }); + } +} diff --git a/examples/src5-ownership/Forc.toml b/examples/src5-ownership/Forc.toml new file mode 100644 index 00000000..0bf139c9 --- /dev/null +++ b/examples/src5-ownership/Forc.toml @@ -0,0 +1,2 @@ +[workspace] +members = ["initialized_example", "uninitialized_example"] diff --git a/examples/src6-vault/Forc.toml b/examples/src6-vault/Forc.toml new file mode 100644 index 00000000..dcddd95e --- /dev/null +++ b/examples/src6-vault/Forc.toml @@ -0,0 +1,6 @@ +[workspace] +members = [ + "multi_asset_vault", + "single_asset_single_sub_vault", + "single_asset_vault", +] diff --git a/examples/src7-metadata/Forc.toml b/examples/src7-metadata/Forc.toml new file mode 100644 index 00000000..c75476cc --- /dev/null +++ b/examples/src7-metadata/Forc.toml @@ -0,0 +1,2 @@ +[workspace] +members = ["single_asset", "multi_asset"] diff --git a/examples/src7-metadata/multi_asset/src/multi_asset.sw b/examples/src7-metadata/multi_asset/src/multi_asset.sw index e658bbce..dfc880d5 100644 --- a/examples/src7-metadata/multi_asset/src/multi_asset.sw +++ b/examples/src7-metadata/multi_asset/src/multi_asset.sw @@ -1,6 +1,19 @@ contract; -use standards::{src20::SRC20, src7::{Metadata, SRC7}}; +use standards::{ + src20::{ + SetDecimalsEvent, + SetNameEvent, + SetSymbolEvent, + SRC20, + TotalSupplyEvent, + }, + src7::{ + Metadata, + SetMetadataEvent, + SRC7, + }, +}; use std::{hash::Hash, storage::storage_string::*, string::String}; @@ -56,11 +69,19 @@ impl SRC7 for Contract { /// ``` #[storage(read)] fn metadata(asset: AssetId, key: String) -> Option { + // If this asset does not exist, return None + if storage.total_supply.get(asset).try_read().is_none() { + return None + } + if key == String::from_ascii_str("social:x") { + // The "social:x" for all assets minted by this contract are the same. Some(Metadata::String(String::from_ascii_str(from_str_array(SOCIAL_X)))) } else if key == String::from_ascii_str("site:forum") { + // The "site:forums" for all assets minted by this contract are the same. Some(Metadata::String(String::from_ascii_str(from_str_array(SITE_FORUM)))) } else if key == String::from_ascii_str("image:svg") { + // The SVG image is stored as a String in storage for each asset let svg_image = storage.svg_images.get(asset).read_slice(); match svg_image { @@ -68,6 +89,7 @@ impl SRC7 for Contract { None => None, } } else if key == String::from_ascii_str("attr:health") { + // The health attribute is stored as a u64 in storage for each asset let health_attribute = storage.health_attributes.get(asset).try_read(); match health_attribute { @@ -80,6 +102,54 @@ impl SRC7 for Contract { } } +abi SetSRC7Events { + #[storage(read, write)] + fn set_src7_events(asset: AssetId, svg_image: String, health_attribute: u64); +} + +impl SetSRC7Events for Contract { + #[storage(read, write)] + fn set_src7_events(asset: AssetId, svg_image: String, health_attribute: u64) { + // NOTE: There are no checks for if the caller has permissions to update the metadata + // If this asset does not exist, revert + if storage.total_supply.get(asset).try_read().is_none() { + revert(0); + } + let sender = msg_sender().unwrap(); + + log(SetMetadataEvent { + asset, + metadata: Some(Metadata::String(String::from_ascii_str(from_str_array(SOCIAL_X)))), + key: String::from_ascii_str("social:x"), + sender, + }); + + log(SetMetadataEvent { + asset, + metadata: Some(Metadata::String(String::from_ascii_str(from_str_array(SITE_FORUM)))), + key: String::from_ascii_str("site:forum"), + sender, + }); + + storage.svg_images.try_insert(asset, StorageString {}); + storage.svg_images.get(asset).write_slice(svg_image); + log(SetMetadataEvent { + asset, + metadata: Some(Metadata::String(svg_image)), + key: String::from_ascii_str("image:svg"), + sender, + }); + + storage.health_attributes.insert(asset, health_attribute); + log(SetMetadataEvent { + asset, + metadata: Some(Metadata::Int(health_attribute)), + key: String::from_ascii_str("attr:health"), + sender, + }); + } +} + // SRC7 extends SRC20, so this must be included impl SRC20 for Contract { #[storage(read)] @@ -116,3 +186,38 @@ impl SRC20 for Contract { } } } + +abi SetSRC20Data { + fn set_src20_data(asset: AssetId, total_supply: u64); +} + +impl SetSRC20Data for Contract { + fn set_src20_data(asset: AssetId, supply: u64) { + // NOTE: There are no checks for if the caller has permissions to update the metadata + let sender = msg_sender().unwrap(); + + log(SetNameEvent { + asset, + name: Some(String::from_ascii_str(from_str_array(NAME))), + sender, + }); + + log(SetSymbolEvent { + asset, + symbol: Some(String::from_ascii_str(from_str_array(SYMBOL))), + sender, + }); + + log(SetDecimalsEvent { + asset, + decimals: DECIMALS, + sender, + }); + + log(TotalSupplyEvent { + asset, + supply, + sender, + }); + } +} diff --git a/examples/src7-metadata/single_asset/src/single_asset.sw b/examples/src7-metadata/single_asset/src/single_asset.sw index cc017ffd..9e68bc17 100644 --- a/examples/src7-metadata/single_asset/src/single_asset.sw +++ b/examples/src7-metadata/single_asset/src/single_asset.sw @@ -1,6 +1,19 @@ contract; -use standards::{src20::SRC20, src7::{Metadata, SRC7}}; +use standards::{ + src20::{ + SetDecimalsEvent, + SetNameEvent, + SetSymbolEvent, + SRC20, + TotalSupplyEvent, + }, + src7::{ + Metadata, + SetMetadataEvent, + SRC7, + }, +}; use std::string::String; @@ -66,6 +79,39 @@ impl SRC7 for Contract { } } +abi EmitSRC7Events { + fn emit_src7_events(); +} + +impl EmitSRC7Events for Contract { + fn emit_src7_events() { + let asset = AssetId::default(); + let sender = msg_sender().unwrap(); + + log(SetMetadataEvent { + asset, + metadata: Some(Metadata::String(String::from_ascii_str(from_str_array(SOCIAL_X)))), + key: String::from_ascii_str("social:x"), + sender, + }); + + log(SetMetadataEvent { + asset, + metadata: Some(Metadata::String(String::from_ascii_str(from_str_array(SITE_FORUM)))), + key: String::from_ascii_str("site:forum"), + sender, + }); + + log(SetMetadataEvent { + asset, + metadata: Some(Metadata::Int(ATTR_HEALTH)), + key: String::from_ascii_str("attr:health"), + sender, + }); + } +} + +// SRC7 extends SRC20, so this must be included impl SRC20 for Contract { #[storage(read)] fn total_assets() -> u64 { @@ -108,3 +154,39 @@ impl SRC20 for Contract { } } } + +abi EmitSRC20Events { + fn emit_src20_events(); +} + +impl EmitSRC20Events for Contract { + fn emit_src20_events() { + // Metadata that is stored as a configurable should only be emitted once. + let asset = AssetId::default(); + let sender = msg_sender().unwrap(); + + log(SetNameEvent { + asset, + name: Some(String::from_ascii_str(from_str_array(NAME))), + sender, + }); + + log(SetSymbolEvent { + asset, + symbol: Some(String::from_ascii_str(from_str_array(SYMBOL))), + sender, + }); + + log(SetDecimalsEvent { + asset, + decimals: DECIMALS, + sender, + }); + + log(TotalSupplyEvent { + asset, + supply: TOTAL_SUPPLY, + sender, + }); + } +} diff --git a/standards/src/src20.sw b/standards/src/src20.sw index 89cff770..fe093668 100644 --- a/standards/src/src20.sw +++ b/standards/src/src20.sw @@ -123,3 +123,43 @@ abi SRC20 { #[storage(read)] fn decimals(asset: AssetId) -> Option; } + +/// The event emitted when the name is set. +pub struct SetNameEvent { + /// The asset for which name is set. + pub asset: AssetId, + /// The name that is set. + pub name: Option, + /// The caller that set the name. + pub sender: Identity, +} + +/// The event emitted when the symbol is set. +pub struct SetSymbolEvent { + /// The asset for which symbol is set. + pub asset: AssetId, + /// The symbol that is set. + pub symbol: Option, + /// The caller that set the symbol. + pub sender: Identity, +} + +/// The event emitted when the decimals is set. +pub struct SetDecimalsEvent { + /// The asset for which decimals is set. + pub asset: AssetId, + /// The decimals that is set. + pub decimals: u8, + /// The caller that set the decimals. + pub sender: Identity, +} + +/// The event emitted when the total supply is changed. +pub struct TotalSupplyEvent { + /// The asset for which supply is updated. + pub asset: AssetId, + /// The new supply of the asset. + pub supply: u64, + /// The caller that updated the supply. + pub sender: Identity, +} diff --git a/standards/src/src3.sw b/standards/src/src3.sw index faeb8702..8f1b9275 100644 --- a/standards/src/src3.sw +++ b/standards/src/src3.sw @@ -6,7 +6,7 @@ abi SRC3 { /// # Arguments /// /// * `recipient`: [Identity] - The user to which the newly minted asset is transferred to. - /// * `sub_id`: [SubId] - The sub-identifier of the newly minted asset. + /// * `sub_id`: [Option] - The sub-identifier of the newly minted asset. /// * `amount`: [u64] - The quantity of coins to mint. /// /// # Examples @@ -20,7 +20,7 @@ abi SRC3 { /// } /// ``` #[storage(read, write)] - fn mint(recipient: Identity, sub_id: SubId, amount: u64); + fn mint(recipient: Identity, sub_id: Option, amount: u64); /// Burns assets sent with the given `sub_id`. /// diff --git a/standards/src/src7.sw b/standards/src/src7.sw index e307d114..9bd030ed 100644 --- a/standards/src/src7.sw +++ b/standards/src/src7.sw @@ -43,6 +43,18 @@ pub enum Metadata { String: String, } +/// The event emitted when metadata is set via a function call. +pub struct SetMetadataEvent { + /// The asset for which metadata is set. + pub asset: AssetId, + /// The Metadata that is set. + pub metadata: Option, + /// The key used for the metadata. + pub key: String, + /// The `Identity` of the caller that set the metadata. + pub sender: Identity, +} + impl core::ops::Eq for Metadata { fn eq(self, other: Self) -> bool { match (self, other) {