From f5539c14f16469e9103eb6c7c09e34d333c8ec5c Mon Sep 17 00:00:00 2001 From: Cameron Carstens <54727135+bitzoic@users.noreply.github.com> Date: Wed, 23 Nov 2022 13:20:49 -0500 Subject: [PATCH] NFT ABI (#56) ## Type of change - Improvement (refactoring, restructuring repository, cleaning tech debt, ...) ## Changes The following changes have been made: - Add NFT ABI - Add Supply ABI - Add Administrator ABI - Add Burn ABI - Rename metadata to token_metadata to differentiate between contract and individual token metadata ## Notes - All NFT implementations should follow the same ABI, meaning we should provide this ABI in the library. ## Related Issues Closes #55 Co-authored-by: bitzoic --- sway_libs/src/nft/SPECIFICATION.md | 26 +++--- .../extensions/administrator/administrator.sw | 7 ++ .../src/nft/extensions/burnable/burnable.sw | 5 ++ sway_libs/src/nft/extensions/supply/supply.sw | 7 ++ .../token_metadata.sw} | 36 ++++---- .../token_metadata_structures.sw} | 6 +- sway_libs/src/nft/nft.sw | 23 ++++- sway_libs/src/nft/nft_storage.sw | 2 +- tests/src/test_projects/harness.rs | 10 +-- tests/src/test_projects/nft_core/src/main.sw | 24 +----- .../test_projects/nft_extensions/src/main.sw | 84 +++++++++++-------- .../tests/functions/meta_data.rs | 14 ++-- .../tests/functions/set_meta_data.rs | 16 ++-- .../nft_extensions/tests/utils/mod.rs | 10 +-- 14 files changed, 152 insertions(+), 118 deletions(-) rename sway_libs/src/nft/extensions/{meta_data/meta_data.sw => token_metadata/token_metadata.sw} (54%) rename sway_libs/src/nft/extensions/{meta_data/meta_data_structures.sw => token_metadata/token_metadata_structures.sw} (78%) diff --git a/sway_libs/src/nft/SPECIFICATION.md b/sway_libs/src/nft/SPECIFICATION.md index 2931b7a2..90f2532b 100644 --- a/sway_libs/src/nft/SPECIFICATION.md +++ b/sway_libs/src/nft/SPECIFICATION.md @@ -5,24 +5,24 @@ Table of Contents - [`approve()`](#approve) - [`approved()`](#approved) - [`balance_of()`](#balance_of) - - [`is_approved_for_all()`](#is-approved-for-all) + - [`is_approved_for_all()`](#is_approved_for_all) - [`mint()`](#mint) - - [`owner_of()`](#owner-of) - - [`set_approval_for_all()`](#set_approval-for-all) - - [`tokens_minted()`](#tokens-minted) + - [`owner_of()`](#owner_of) + - [`set_approval_for_all()`](#set_approval_for_all) + - [`tokens_minted()`](#tokens_minted) - [`transfer()`](#transfer) - [Extension Public Functions](#extension-public-functions) - [Administrator](#administrator) - [`admin()`](#admin) - - [`set_admin()`](#set-admin) + - [`set_admin()`](#set_admin) - [Burnable](#burnable) - [`burn()`](#burn) - - [Metadata](#metadata) - - [`meta_data()`](#meta-data) - - [`set_meta_data()`](#set-meta-data) + - [Token Metadata](#token-metadata) + - [`token_metadata()`](#token_metadata) + - [`set_token_metadata()`](#set_token_metadata) - [Supply](#supply) - - [`max_supply()`](#max-supply) - - [`set_max_supply()`](#set-max-supply) + - [`max_supply()`](#max_supply) + - [`set_max_supply()`](#set_max_supply) # Overview @@ -96,13 +96,13 @@ Sets the administrator of the `NFT` library. Deletes the specified token. -### Metadata +### Token Metadata -#### `meta_data()` +#### `token_metadata()` Returns the stored data associated with the specified token. -#### `set_meta_data()` +#### `set_token_metadata()` Stores a struct containing information / data particular to an individual token. diff --git a/sway_libs/src/nft/extensions/administrator/administrator.sw b/sway_libs/src/nft/extensions/administrator/administrator.sw index 7f2a35ad..411b66d2 100644 --- a/sway_libs/src/nft/extensions/administrator/administrator.sw +++ b/sway_libs/src/nft/extensions/administrator/administrator.sw @@ -8,6 +8,13 @@ use administrator_events::AdminEvent; use ::nft::nft_storage::ADMIN; use std::{auth::msg_sender, logging::log, storage::{get, store}}; +abi Administrator { + #[storage(read)] + fn admin() -> Option; + #[storage(read, write)] + fn set_admin(new_admin: Option); +} + /// Returns the administrator for the library. #[storage(read)] pub fn admin() -> Option { diff --git a/sway_libs/src/nft/extensions/burnable/burnable.sw b/sway_libs/src/nft/extensions/burnable/burnable.sw index c07cc318..84e95da9 100644 --- a/sway_libs/src/nft/extensions/burnable/burnable.sw +++ b/sway_libs/src/nft/extensions/burnable/burnable.sw @@ -6,6 +6,11 @@ use burnable_events::BurnEvent; use ::nft::{errors::{AccessError, InputError}, nft_core::NFTCore, nft_storage::{BALANCES, TOKENS}}; use std::{auth::msg_sender, hash::sha256, logging::log, storage::{get, store}}; +abi Burn { + #[storage(read, write)] + fn burn(token_id: u64); +} + pub trait Burnable { /// Deletes this token from storage and decrements the balance of the owner. /// diff --git a/sway_libs/src/nft/extensions/supply/supply.sw b/sway_libs/src/nft/extensions/supply/supply.sw index bb21250d..7002ed4b 100644 --- a/sway_libs/src/nft/extensions/supply/supply.sw +++ b/sway_libs/src/nft/extensions/supply/supply.sw @@ -8,6 +8,13 @@ use std::{logging::log, storage::{get, store}}; use supply_errors::SupplyError; use supply_events::SupplyEvent; +abi Supply { + #[storage(read)] + fn max_supply() -> Option; + #[storage(read, write)] + fn set_max_supply(supply: Option); +} + /// Returns the maximum supply that has been set for the NFT library. #[storage(read)] pub fn max_supply() -> Option { diff --git a/sway_libs/src/nft/extensions/meta_data/meta_data.sw b/sway_libs/src/nft/extensions/token_metadata/token_metadata.sw similarity index 54% rename from sway_libs/src/nft/extensions/meta_data/meta_data.sw rename to sway_libs/src/nft/extensions/token_metadata/token_metadata.sw index 5ac54186..6ddba7d9 100644 --- a/sway_libs/src/nft/extensions/meta_data/meta_data.sw +++ b/sway_libs/src/nft/extensions/token_metadata/token_metadata.sw @@ -1,34 +1,34 @@ -library meta_data; +library token_metadata; -dep meta_data_structures; +dep token_metadata_structures; -use meta_data_structures::NFTMetaData; -use ::nft::{errors::InputError, nft_core::NFTCore, nft_storage::{META_DATA, TOKENS}}; +use token_metadata_structures::NFTMetadata; +use ::nft::{errors::InputError, nft_core::NFTCore, nft_storage::{TOKEN_METADATA, TOKENS}}; use std::{hash::sha256, storage::{get, store}}; -pub trait MetaData { +pub trait TokenMetadata { /// Returns the metadata for this token #[storage(read)] - fn meta_data(self) -> Option; + fn token_metadata(self) -> Option; /// Creates new metadata for this token. /// /// # Arguments /// - /// * `metadata` - The new metadata to overwrite the existing metadata. + /// * `token_metadata` - The new metadata to overwrite the existing metadata. #[storage(write)] - fn set_meta_data(self, metadata: Option); + fn set_token_metadata(self, token_metadata: Option); } -impl MetaData for NFTCore { +impl TokenMetadata for NFTCore { #[storage(read)] - fn meta_data(self) -> Option { - get::>(sha256((META_DATA, self.token_id))) + fn token_metadata(self) -> Option { + get::>(sha256((TOKEN_METADATA, self.token_id))) } #[storage(write)] - fn set_meta_data(self, metadata: Option) { - store(sha256((META_DATA, self.token_id)), metadata); + fn set_token_metadata(self, token_metadata: Option) { + store(sha256((TOKEN_METADATA, self.token_id)), token_metadata); } } @@ -38,11 +38,11 @@ impl MetaData for NFTCore { /// /// * `token_id` - The id of the token which the metadata should be returned #[storage(read)] -pub fn meta_data(token_id: u64) -> Option { +pub fn token_metadata(token_id: u64) -> Option { let nft = get::>(sha256((TOKENS, token_id))); match nft { Option::Some(nft) => { - nft.meta_data() + nft.token_metadata() }, Option::None => { Option::None @@ -54,16 +54,16 @@ pub fn meta_data(token_id: u64) -> Option { /// /// # Arguments /// -/// * `metadata` - The metadata which should be set. +/// * `token_metadata` - The metadata which should be set. /// * `token_id` - The token which the metadata should be set for. /// /// # Reverts /// /// * When the `token_id` does not map to an existing token #[storage(read, write)] -pub fn set_meta_data(metadata: Option, token_id: u64) { +pub fn set_token_metadata(token_metadata: Option, token_id: u64) { let nft = get::>(sha256((TOKENS, token_id))); require(nft.is_some(), InputError::TokenDoesNotExist); - nft.unwrap().set_meta_data(metadata); + nft.unwrap().set_token_metadata(token_metadata); } diff --git a/sway_libs/src/nft/extensions/meta_data/meta_data_structures.sw b/sway_libs/src/nft/extensions/token_metadata/token_metadata_structures.sw similarity index 78% rename from sway_libs/src/nft/extensions/meta_data/meta_data_structures.sw rename to sway_libs/src/nft/extensions/token_metadata/token_metadata_structures.sw index 33f28858..5bbdf332 100644 --- a/sway_libs/src/nft/extensions/meta_data/meta_data_structures.sw +++ b/sway_libs/src/nft/extensions/token_metadata/token_metadata_structures.sw @@ -1,13 +1,13 @@ -library meta_data_structures; +library token_metadata_structures; -pub struct NFTMetaData { +pub struct NFTMetadata { // This is left as an example. Support for StorageVec in struct is needed here. // Developers may also implement their own metadata structs with properties they may need // and use the MetaData trait. value: u64, } -impl NFTMetaData { +impl NFTMetadata { fn new(value: u64) -> Self { Self { value } } diff --git a/sway_libs/src/nft/nft.sw b/sway_libs/src/nft/nft.sw index b5981797..fa4e670b 100644 --- a/sway_libs/src/nft/nft.sw +++ b/sway_libs/src/nft/nft.sw @@ -7,7 +7,7 @@ dep errors; dep events; dep extensions/administrator/administrator; dep extensions/burnable/burnable; -dep extensions/meta_data/meta_data; +dep extensions/token_metadata/token_metadata; dep extensions/supply/supply; use errors::InputError; @@ -16,6 +16,27 @@ use nft_core::NFTCore; use nft_storage::{BALANCES, OPERATOR_APPROVAL, TOKENS, TOKENS_MINTED}; use std::{auth::msg_sender, hash::sha256, logging::log, storage::{get, store}}; +abi NFT { + #[storage(read, write)] + fn approve(approved: Option, token_id: u64); + #[storage(read)] + fn approved(token_id: u64) -> Option; + #[storage(read)] + fn balance_of(owner: Identity) -> u64; + #[storage(read)] + fn is_approved_for_all(operator: Identity, owner: Identity) -> bool; + #[storage(read, write)] + fn mint(amount: u64, to: Identity); + #[storage(read)] + fn owner_of(token_id: u64) -> Option; + #[storage(write)] + fn set_approval_for_all(approve: bool, operator: Identity); + #[storage(read)] + fn tokens_minted() -> u64; + #[storage(read, write)] + fn transfer(to: Identity, token_id: u64); +} + /// Sets the approved identity for a specific token. /// /// To revoke approval the approved user should be `None`. diff --git a/sway_libs/src/nft/nft_storage.sw b/sway_libs/src/nft/nft_storage.sw index 0f93c5be..c7ef998a 100644 --- a/sway_libs/src/nft/nft_storage.sw +++ b/sway_libs/src/nft/nft_storage.sw @@ -6,7 +6,7 @@ pub const ADMIN: b256 = 0x100000000000000000000000000000000000000000000000000000 pub const APPROVED: b256 = 0x2000000000000000000000000000000000000000000000000000000000000000; pub const BALANCES: b256 = 0x3000000000000000000000000000000000000000000000000000000000000000; pub const MAX_SUPPLY: b256 = 0x4000000000000000000000000000000000000000000000000000000000000000; -pub const META_DATA: b256 = 0x5000000000000000000000000000000000000000000000000000000000000000; +pub const TOKEN_METADATA: b256 = 0x5000000000000000000000000000000000000000000000000000000000000000; pub const OPERATOR_APPROVAL: b256 = 0x6000000000000000000000000000000000000000000000000000000000000000; pub const TOKENS: b256 = 0x7000000000000000000000000000000000000000000000000000000000000000; pub const TOKENS_MINTED: b256 = 0x8000000000000000000000000000000000000000000000000000000000000000; diff --git a/tests/src/test_projects/harness.rs b/tests/src/test_projects/harness.rs index 5f9bddc1..96096536 100644 --- a/tests/src/test_projects/harness.rs +++ b/tests/src/test_projects/harness.rs @@ -1,12 +1,12 @@ // Add test modules here: mod merkle_proof; -mod signed_i8; +mod nft_core; +mod nft_extensions; +mod signed_i128; mod signed_i16; +mod signed_i256; mod signed_i32; mod signed_i64; -mod signed_i128; -mod signed_i256; -mod nft_core; -mod nft_extensions; +mod signed_i8; mod string; diff --git a/tests/src/test_projects/nft_core/src/main.sw b/tests/src/test_projects/nft_core/src/main.sw index 982f0e8b..de5c1bcc 100644 --- a/tests/src/test_projects/nft_core/src/main.sw +++ b/tests/src/test_projects/nft_core/src/main.sw @@ -6,34 +6,14 @@ use sway_libs::nft::{ balance_of, is_approved_for_all, mint, + NFT, owner_of, set_approval_for_all, tokens_minted, transfer, }; -abi NFT_Core_Test { - #[storage(read, write)] - fn approve(approved: Option, token_id: u64); - #[storage(read)] - fn approved(token_id: u64) -> Option; - #[storage(read)] - fn balance_of(owner: Identity) -> u64; - #[storage(read)] - fn is_approved_for_all(operator: Identity, owner: Identity) -> bool; - #[storage(read, write)] - fn mint(amount: u64, to: Identity); - #[storage(read)] - fn owner_of(token_id: u64) -> Option; - #[storage(write)] - fn set_approval_for_all(approve: bool, operator: Identity); - #[storage(read)] - fn tokens_minted() -> u64; - #[storage(read, write)] - fn transfer(to: Identity, token_id: u64); -} - -impl NFT_Core_Test for Contract { +impl NFT for Contract { #[storage(read, write)] fn approve(approved_identity: Option, token_id: u64) { approve(approved_identity, token_id); diff --git a/tests/src/test_projects/nft_extensions/src/main.sw b/tests/src/test_projects/nft_extensions/src/main.sw index 4690bdd6..51d9173f 100644 --- a/tests/src/test_projects/nft_extensions/src/main.sw +++ b/tests/src/test_projects/nft_extensions/src/main.sw @@ -1,91 +1,105 @@ contract; + use sway_libs::nft::{ administrator::{ admin, + Administrator, set_admin, }, balance_of, - burnable::burn, - meta_data::{ - meta_data, - meta_data_structures::NFTMetaData, - set_meta_data, + burnable::{ + burn, + Burn, }, mint, owner_of, supply::{ max_supply, set_max_supply, + Supply, + }, + token_metadata::{ + set_token_metadata, + token_metadata, + token_metadata_structures::NFTMetadata, }, tokens_minted, }; + abi NFT_Extensions_Test { - #[storage(read)] - fn admin() -> Option; #[storage(read)] fn balance_of(owner: Identity) -> u64; - #[storage(read, write)] - fn burn(token_id: u64); #[storage(read)] - fn max_supply() -> Option; - #[storage(read)] - fn meta_data(token_id: u64) -> Option; + fn token_metadata(token_id: u64) -> Option; #[storage(read, write)] fn mint(amount: u64, to: Identity); #[storage(read)] fn owner_of(token_id: u64) -> Option; #[storage(read, write)] - fn set_admin(new_admin: Option); - #[storage(read, write)] - fn set_max_supply(supply: Option); - #[storage(read, write)] - fn set_meta_data(metadata: Option, token_id: u64); + fn set_token_metadata(token_metadata: Option, token_id: u64); #[storage(read)] fn tokens_minted() -> u64; } -impl NFT_Extensions_Test for Contract { + +impl Administrator for Contract { #[storage(read)] fn admin() -> Option { admin() } - #[storage(read)] - fn balance_of(owner: Identity) -> u64 { - balance_of(owner) + + #[storage(read, write)] + fn set_admin(new_admin: Option) { + set_admin(new_admin); } +} + +impl Burn for Contract { #[storage(read, write)] fn burn(token_id: u64) { burn(token_id); } +} + +impl NFT_Extensions_Test for Contract { #[storage(read)] - fn max_supply() -> Option { - max_supply() + fn balance_of(owner: Identity) -> u64 { + balance_of(owner) } + #[storage(read)] - fn meta_data(token_id: u64) -> Option { - meta_data(token_id) + fn token_metadata(token_id: u64) -> Option { + token_metadata(token_id) } + #[storage(read, write)] fn mint(amount: u64, to: Identity) { mint(amount, to); } + #[storage(read)] fn owner_of(token_id: u64) -> Option { owner_of(token_id) } + #[storage(read, write)] - fn set_admin(new_admin: Option) { - set_admin(new_admin); - } - #[storage(read, write)] - fn set_max_supply(supply: Option) { - set_max_supply(supply) - } - #[storage(read, write)] - fn set_meta_data(metadata: Option, token_id: u64) { - set_meta_data(metadata, token_id); + fn set_token_metadata(metadata: Option, token_id: u64) { + set_token_metadata(metadata, token_id); } + #[storage(read)] fn tokens_minted() -> u64 { tokens_minted() } } + +impl Supply for Contract { + #[storage(read)] + fn max_supply() -> Option { + max_supply() + } + + #[storage(read, write)] + fn set_max_supply(supply: Option) { + set_max_supply(supply) + } +} diff --git a/tests/src/test_projects/nft_extensions/tests/functions/meta_data.rs b/tests/src/test_projects/nft_extensions/tests/functions/meta_data.rs index 98f010c9..964adfe8 100644 --- a/tests/src/test_projects/nft_extensions/tests/functions/meta_data.rs +++ b/tests/src/test_projects/nft_extensions/tests/functions/meta_data.rs @@ -1,7 +1,7 @@ use crate::nft_extensions::tests::utils::{ - abi_calls::{meta_data, mint, set_meta_data}, + abi_calls::{mint, set_token_metadata, token_metadata}, test_helpers::setup, - NFTMetaData, + NFTMetadata, }; use fuels::prelude::Identity; @@ -16,13 +16,13 @@ mod success { let minter = Identity::Address(owner1.wallet.address().into()); mint(1, &owner1.contract, minter.clone()).await; - assert_eq!(meta_data(&owner1.contract, 0).await, None); + assert_eq!(token_metadata(&owner1.contract, 0).await, None); - let nft_meta_data = NFTMetaData { value: 1 }; - set_meta_data(&owner1.contract, Some(nft_meta_data.clone()), 0).await; + let nft_meta_data = NFTMetadata { value: 1 }; + set_token_metadata(&owner1.contract, Some(nft_meta_data.clone()), 0).await; assert_eq!( - meta_data(&owner1.contract, 0).await, + token_metadata(&owner1.contract, 0).await, Some(nft_meta_data.clone()) ); } @@ -31,6 +31,6 @@ mod success { async fn get_meta_data_on_token_that_doesnt_exist() { let (_deploy_wallet, owner1, _owner2) = setup().await; - assert_eq!(meta_data(&owner1.contract, 0).await, None); + assert_eq!(token_metadata(&owner1.contract, 0).await, None); } } diff --git a/tests/src/test_projects/nft_extensions/tests/functions/set_meta_data.rs b/tests/src/test_projects/nft_extensions/tests/functions/set_meta_data.rs index 25c703e5..d962d8fa 100644 --- a/tests/src/test_projects/nft_extensions/tests/functions/set_meta_data.rs +++ b/tests/src/test_projects/nft_extensions/tests/functions/set_meta_data.rs @@ -1,7 +1,7 @@ use crate::nft_extensions::tests::utils::{ - abi_calls::{meta_data, mint, set_meta_data}, + abi_calls::{mint, set_token_metadata, token_metadata}, test_helpers::setup, - NFTMetaData, + NFTMetadata, }; use fuels::prelude::Identity; @@ -16,13 +16,13 @@ mod success { let minter = Identity::Address(owner1.wallet.address().into()); mint(1, &owner1.contract, minter.clone()).await; - assert_eq!(meta_data(&owner1.contract, 0).await, None); + assert_eq!(token_metadata(&owner1.contract, 0).await, None); - let nft_meta_data = NFTMetaData { value: 1 }; - set_meta_data(&owner1.contract, Some(nft_meta_data.clone()), 0).await; + let nft_meta_data = NFTMetadata { value: 1 }; + set_token_metadata(&owner1.contract, Some(nft_meta_data.clone()), 0).await; assert_eq!( - meta_data(&owner1.contract, 0).await, + token_metadata(&owner1.contract, 0).await, Some(nft_meta_data.clone()) ); } @@ -37,7 +37,7 @@ mod revert { async fn when_token_does_not_exist() { let (_deploy_wallet, owner1, _owner2) = setup().await; - let nft_meta_data = NFTMetaData { value: 1 }; - set_meta_data(&owner1.contract, Some(nft_meta_data.clone()), 0).await; + let nft_meta_data = NFTMetadata { value: 1 }; + set_token_metadata(&owner1.contract, Some(nft_meta_data.clone()), 0).await; } } diff --git a/tests/src/test_projects/nft_extensions/tests/utils/mod.rs b/tests/src/test_projects/nft_extensions/tests/utils/mod.rs index 446db03a..da28df9c 100644 --- a/tests/src/test_projects/nft_extensions/tests/utils/mod.rs +++ b/tests/src/test_projects/nft_extensions/tests/utils/mod.rs @@ -37,10 +37,10 @@ pub mod abi_calls { contract.methods().max_supply().call().await.unwrap().value } - pub async fn meta_data(contract: &NftExtensions, token_id: u64) -> Option { + pub async fn token_metadata(contract: &NftExtensions, token_id: u64) -> Option { contract .methods() - .meta_data(token_id) + .token_metadata(token_id) .call() .await .unwrap() @@ -74,14 +74,14 @@ pub mod abi_calls { .unwrap() } - pub async fn set_meta_data( + pub async fn set_token_metadata( contract: &NftExtensions, - metadata: Option, + metadata: Option, token_id: u64, ) -> CallResponse<()> { contract .methods() - .set_meta_data(metadata, token_id) + .set_token_metadata(metadata, token_id) .call() .await .unwrap()