From 6763cdb52aed5a360d28067cf3ae8768b38860dc Mon Sep 17 00:00:00 2001 From: Thounyy <thouny@tuta.io> Date: Wed, 27 Sep 2023 10:39:26 +0700 Subject: [PATCH 1/3] add nft subscription module --- Cargo.lock | 17 ++ Cargo.toml | 2 + .../examples/nft-subscription/.gitignore | 7 + .../examples/nft-subscription/Cargo.toml | 21 ++ .../examples/nft-subscription/meta/Cargo.toml | 13 + .../nft-subscription/meta/src/main.rs | 3 + .../examples/nft-subscription/multiversx.json | 3 + .../nft-subscription/scenarios/init.scen.json | 78 ++++++ .../scenarios/mint_nft.scen.json | 93 +++++++ .../scenarios/test_subscription.scen.json | 255 ++++++++++++++++++ .../examples/nft-subscription/src/lib.rs | 55 ++++ .../nft_subscription_scenario_rs_test.rs | 23 ++ .../tests/scenario_go_test.rs | 20 ++ .../examples/nft-subscription/wasm/Cargo.lock | 217 +++++++++++++++ .../examples/nft-subscription/wasm/Cargo.toml | 26 ++ .../examples/nft-subscription/wasm/src/lib.rs | 36 +++ contracts/modules/src/lib.rs | 1 + contracts/modules/src/subscription.rs | 51 ++++ 18 files changed, 921 insertions(+) create mode 100644 contracts/examples/nft-subscription/.gitignore create mode 100644 contracts/examples/nft-subscription/Cargo.toml create mode 100644 contracts/examples/nft-subscription/meta/Cargo.toml create mode 100644 contracts/examples/nft-subscription/meta/src/main.rs create mode 100644 contracts/examples/nft-subscription/multiversx.json create mode 100644 contracts/examples/nft-subscription/scenarios/init.scen.json create mode 100644 contracts/examples/nft-subscription/scenarios/mint_nft.scen.json create mode 100644 contracts/examples/nft-subscription/scenarios/test_subscription.scen.json create mode 100644 contracts/examples/nft-subscription/src/lib.rs create mode 100644 contracts/examples/nft-subscription/tests/nft_subscription_scenario_rs_test.rs create mode 100644 contracts/examples/nft-subscription/tests/scenario_go_test.rs create mode 100644 contracts/examples/nft-subscription/wasm/Cargo.lock create mode 100644 contracts/examples/nft-subscription/wasm/Cargo.toml create mode 100644 contracts/examples/nft-subscription/wasm/src/lib.rs create mode 100644 contracts/modules/src/subscription.rs diff --git a/Cargo.lock b/Cargo.lock index a2e1cd41fe..60f615f584 100755 --- a/Cargo.lock +++ b/Cargo.lock @@ -2300,6 +2300,23 @@ dependencies = [ "nft-storage-prepay", ] +[[package]] +name = "nft-subscription" +version = "0.0.0" +dependencies = [ + "multiversx-sc", + "multiversx-sc-modules", + "multiversx-sc-scenario", +] + +[[package]] +name = "nft-subscription-meta" +version = "0.0.0" +dependencies = [ + "multiversx-sc-meta", + "nft-subscription", +] + [[package]] name = "nibble_vec" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index 3e2a20a57d..ef92a8a7b9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -82,6 +82,8 @@ members = [ "contracts/examples/multisig/interact", "contracts/examples/nft-minter", "contracts/examples/nft-minter/meta", + "contracts/examples/nft-subscription", + "contracts/examples/nft-subscription/meta", "contracts/examples/nft-storage-prepay", "contracts/examples/nft-storage-prepay/meta", "contracts/examples/order-book/factory", diff --git a/contracts/examples/nft-subscription/.gitignore b/contracts/examples/nft-subscription/.gitignore new file mode 100644 index 0000000000..9494cb146e --- /dev/null +++ b/contracts/examples/nft-subscription/.gitignore @@ -0,0 +1,7 @@ +# Generated by Cargo +# will have compiled files and executables +/target/ +*/target/ + +# The mxpy output +output diff --git a/contracts/examples/nft-subscription/Cargo.toml b/contracts/examples/nft-subscription/Cargo.toml new file mode 100644 index 0000000000..f97cd8b819 --- /dev/null +++ b/contracts/examples/nft-subscription/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "nft-subscription" +version = "0.0.0" +authors = ["Thouny <thouny@tuta.io>"] +edition = "2021" +publish = false + +[lib] +path = "src/lib.rs" + +[dependencies.multiversx-sc] +version = "0.43.4" +path = "../../../framework/base" + +[dependencies.multiversx-sc-modules] +version = "0.43.4" +path = "../../../contracts/modules" + +[dev-dependencies.multiversx-sc-scenario] +version = "0.43.4" +path = "../../../framework/scenario" diff --git a/contracts/examples/nft-subscription/meta/Cargo.toml b/contracts/examples/nft-subscription/meta/Cargo.toml new file mode 100644 index 0000000000..5eed1a06b6 --- /dev/null +++ b/contracts/examples/nft-subscription/meta/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "nft-subscription-meta" +version = "0.0.0" +authors = ["Thouny <thouny@tuta.io>"] +edition = "2021" +publish = false + +[dependencies.nft-subscription] +path = ".." + +[dependencies.multiversx-sc-meta] +version = "0.43.4" +path = "../../../../framework/meta" diff --git a/contracts/examples/nft-subscription/meta/src/main.rs b/contracts/examples/nft-subscription/meta/src/main.rs new file mode 100644 index 0000000000..27c3b0f0ad --- /dev/null +++ b/contracts/examples/nft-subscription/meta/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + multiversx_sc_meta::cli_main::<nft_subscription::AbiProvider>(); +} diff --git a/contracts/examples/nft-subscription/multiversx.json b/contracts/examples/nft-subscription/multiversx.json new file mode 100644 index 0000000000..7365539625 --- /dev/null +++ b/contracts/examples/nft-subscription/multiversx.json @@ -0,0 +1,3 @@ +{ + "language": "rust" +} \ No newline at end of file diff --git a/contracts/examples/nft-subscription/scenarios/init.scen.json b/contracts/examples/nft-subscription/scenarios/init.scen.json new file mode 100644 index 0000000000..40a0f7da51 --- /dev/null +++ b/contracts/examples/nft-subscription/scenarios/init.scen.json @@ -0,0 +1,78 @@ +{ + "name": "init", + "steps": [ + { + "step": "setState", + "accounts": { + "address:owner": { + "nonce": "0", + "balance": "0" + }, + "address:buyer": { + "nonce": "0", + "balance": "1000" + } + }, + "newAddresses": [ + { + "creatorAddress": "address:owner", + "creatorNonce": "0", + "newAddress": "sc:nft-subscription" + } + ] + }, + { + "step": "scDeploy", + "id": "deploy", + "tx": { + "from": "address:owner", + "contractCode": "file:../output/nft-subscription.wasm", + "arguments": [], + "gasLimit": "10,000,000", + "gasPrice": "0" + }, + "expect": { + "out": [], + "status": "0", + "logs": [], + "gas": "*", + "refund": "*" + } + }, + { + "step": "checkState", + "accounts": { + "sc:nft-subscription": { + "nonce": "0", + "balance": "0", + "storage": {}, + "code": "file:../output/nft-subscription.wasm" + }, + "+": "" + } + }, + { + "step": "setState", + "accounts": { + "sc:nft-subscription": { + "nonce": "0", + "balance": "0", + "esdt": { + "str:NFT-123456": { + "lastNonce": "0", + "roles": [ + "ESDTRoleNFTCreate", + "ESDTRoleNFTUpdateAttributes" + ] + } + }, + "storage": { + "str:tokenId": "str:NFT-123456" + }, + "code": "file:../output/nft-subscription.wasm", + "owner": "address:owner" + } + } + } + ] +} diff --git a/contracts/examples/nft-subscription/scenarios/mint_nft.scen.json b/contracts/examples/nft-subscription/scenarios/mint_nft.scen.json new file mode 100644 index 0000000000..045284cc60 --- /dev/null +++ b/contracts/examples/nft-subscription/scenarios/mint_nft.scen.json @@ -0,0 +1,93 @@ +{ + "name": "mint nfts", + "steps": [ + { + "step": "externalSteps", + "path": "init.scen.json" + }, + { + "step": "scCall", + "id": "create-NFT-1", + "tx": { + "from": "address:owner", + "to": "sc:nft-subscription", + "function": "mint", + "arguments": [], + "gasLimit": "20,000,000", + "gasPrice": "0" + }, + "expect": { + "out": [], + "status": "0", + "message": "", + "gas": "*", + "refund": "*" + } + }, + { + "step": "scCall", + "id": "create-NFT-2", + "tx": { + "from": "address:owner", + "to": "sc:nft-subscription", + "function": "mint", + "arguments": [], + "gasLimit": "20,000,000", + "gasPrice": "0" + }, + "expect": { + "out": [], + "status": "0", + "message": "", + "gas": "*", + "refund": "*" + } + }, + { + "step": "checkState", + "accounts": { + "address:owner": { + "nonce": "3", + "balance": "0", + "esdt": { + "str:NFT-123456": { + "instances": [ + { + "nonce": "1", + "balance": "1", + "creator": "sc:nft-subscription", + "attributes": "0" + }, + { + "nonce": "2", + "balance": "1", + "creator": "sc:nft-subscription", + "attributes": "0" + } + ] + } + } + }, + "sc:nft-subscription": { + "nonce": "0", + "balance": "0", + "esdt": { + "str:NFT-123456": { + "lastNonce": "2", + "roles": [ + "ESDTRoleNFTCreate", + "ESDTRoleNFTUpdateAttributes" + ] + } + }, + "storage": { + "str:tokenId": "str:NFT-123456" + }, + "code": "file:../output/nft-subscription.wasm", + "owner": "address:owner" + }, + "+": "" + } + } + ] +} diff --git a/contracts/examples/nft-subscription/scenarios/test_subscription.scen.json b/contracts/examples/nft-subscription/scenarios/test_subscription.scen.json new file mode 100644 index 0000000000..8820a92b2b --- /dev/null +++ b/contracts/examples/nft-subscription/scenarios/test_subscription.scen.json @@ -0,0 +1,255 @@ +{ + "name": "subscription", + "steps": [ + { + "step": "externalSteps", + "path": "mint_nft.scen.json" + }, + { + "step": "setState", + "currentBlockInfo": { + "blockTimestamp": "1" + } + }, + { + "step": "scCall", + "id": "add-subscription", + "tx": { + "from": "address:owner", + "to": "sc:nft-subscription", + "esdtValue": [ + { + "tokenIdentifier": "str:NFT-123456", + "nonce": "1", + "value": "1" + } + ], + "function": "renew", + "arguments": [ + "2" + ], + "gasLimit": "20,000,000", + "gasPrice": "0" + }, + "expect": { + "out": [], + "status": "0", + "message": "", + "gas": "*", + "refund": "*" + } + }, + { + "step": "checkState", + "accounts": { + "address:owner": { + "nonce": "*", + "balance": "0", + "esdt": { + "str:NFT-123456": { + "instances": [ + { + "nonce": "1", + "balance": "1", + "creator": "sc:nft-subscription", + "attributes": "3" + }, + { + "nonce": "2", + "balance": "1", + "creator": "sc:nft-subscription", + "attributes": "0" + } + ] + } + } + }, + "+": "" + } + }, + { + "step": "setState", + "currentBlockInfo": { + "blockTimestamp": "2" + } + }, + { + "step": "scCall", + "id": "renew-subscription-not-ended", + "tx": { + "from": "address:owner", + "to": "sc:nft-subscription", + "esdtValue": [ + { + "tokenIdentifier": "str:NFT-123456", + "nonce": "1", + "value": "1" + } + ], + "function": "renew", + "arguments": [ + "3" + ], + "gasLimit": "20,000,000", + "gasPrice": "0" + }, + "expect": { + "out": [], + "status": "0", + "message": "", + "gas": "*", + "refund": "*" + } + }, + { + "step": "checkState", + "accounts": { + "address:owner": { + "nonce": "*", + "balance": "0", + "esdt": { + "str:NFT-123456": { + "instances": [ + { + "nonce": "1", + "balance": "1", + "creator": "sc:nft-subscription", + "attributes": "6" + }, + { + "nonce": "2", + "balance": "1", + "creator": "sc:nft-subscription", + "attributes": "0" + } + ] + } + } + }, + "+": "" + } + }, + { + "step": "setState", + "currentBlockInfo": { + "blockTimestamp": "10" + } + }, + { + "step": "scCall", + "id": "renew-subscription-already-ended", + "tx": { + "from": "address:owner", + "to": "sc:nft-subscription", + "esdtValue": [ + { + "tokenIdentifier": "str:NFT-123456", + "nonce": "1", + "value": "1" + } + ], + "function": "renew", + "arguments": [ + "5" + ], + "gasLimit": "20,000,000", + "gasPrice": "0" + }, + "expect": { + "out": [], + "status": "0", + "message": "", + "gas": "*", + "refund": "*" + } + }, + { + "step": "checkState", + "accounts": { + "address:owner": { + "nonce": "*", + "balance": "0", + "esdt": { + "str:NFT-123456": { + "instances": [ + { + "nonce": "1", + "balance": "1", + "creator": "sc:nft-subscription", + "attributes": "15" + }, + { + "nonce": "2", + "balance": "1", + "creator": "sc:nft-subscription", + "attributes": "0" + } + ] + } + } + }, + "+": "" + } + }, + { + "step": "setState", + "currentBlockInfo": { + "blockTimestamp": "11" + } + }, + { + "step": "scCall", + "id": "cancel-subscription", + "tx": { + "from": "address:owner", + "to": "sc:nft-subscription", + "esdtValue": [ + { + "tokenIdentifier": "str:NFT-123456", + "nonce": "1", + "value": "1" + } + ], + "function": "cancel", + "arguments": [], + "gasLimit": "20,000,000", + "gasPrice": "0" + }, + "expect": { + "out": [], + "status": "0", + "message": "", + "gas": "*", + "refund": "*" + } + }, + { + "step": "checkState", + "accounts": { + "address:owner": { + "nonce": "*", + "balance": "0", + "esdt": { + "str:NFT-123456": { + "instances": [ + { + "nonce": "1", + "balance": "1", + "creator": "sc:nft-subscription", + "attributes": "0" + }, + { + "nonce": "2", + "balance": "1", + "creator": "sc:nft-subscription", + "attributes": "0" + } + ] + } + } + }, + "+": "" + } + } + ] +} \ No newline at end of file diff --git a/contracts/examples/nft-subscription/src/lib.rs b/contracts/examples/nft-subscription/src/lib.rs new file mode 100644 index 0000000000..9120206671 --- /dev/null +++ b/contracts/examples/nft-subscription/src/lib.rs @@ -0,0 +1,55 @@ +#![no_std] + +multiversx_sc::imports!(); +multiversx_sc::derive_imports!(); + +use multiversx_sc_modules::default_issue_callbacks; +use multiversx_sc_modules::subscription; + +#[multiversx_sc::contract] +pub trait NftSubscription: default_issue_callbacks::DefaultIssueCallbacksModule + subscription::SubscriptionModule { + #[init] + fn init(&self) {} + + #[endpoint] + fn issue(&self) { + self.token_id().issue_and_set_all_roles( + EsdtTokenType::NonFungible, + self.call_value().egld_value().clone_value(), + ManagedBuffer::from(b"Subscription"), + ManagedBuffer::from(b"SUB"), + 0, + None + ) + } + + #[endpoint] + fn mint(&self) { + self.token_id().nft_create_and_send(&self.blockchain().get_caller(), BigUint::from(1u8), &0u64); + } + + #[payable("*")] + #[endpoint] + fn renew(&self, duration: u64) { + let (id, nonce, _) = self.call_value().single_esdt().into_tuple(); + self.renew_subscription(&id, nonce, duration); + self.send().direct_esdt(&self.blockchain().get_caller(), &id, nonce, &BigUint::from(1u8)); + } + + #[payable("*")] + #[endpoint] + fn cancel(&self) { + let (id, nonce, _) = self.call_value().single_esdt().into_tuple(); + self.cancel_subscription(&id, nonce); + self.send().direct_esdt(&self.blockchain().get_caller(), &id, nonce, &BigUint::from(1u8)); + } + + #[view] + fn expires(&self, id: &TokenIdentifier, nonce: u64) -> u64 { + self.expires_at(id, nonce) + } + + #[storage_mapper("tokenId")] + fn token_id(&self) -> NonFungibleTokenMapper; + +} diff --git a/contracts/examples/nft-subscription/tests/nft_subscription_scenario_rs_test.rs b/contracts/examples/nft-subscription/tests/nft_subscription_scenario_rs_test.rs new file mode 100644 index 0000000000..d37faa2bca --- /dev/null +++ b/contracts/examples/nft-subscription/tests/nft_subscription_scenario_rs_test.rs @@ -0,0 +1,23 @@ +use multiversx_sc_scenario::*; + +fn world() -> ScenarioWorld { + todo!() +} + +#[test] +#[ignore = "not supported"] +fn test_subscription_rs() { + world().run("scenarios/test_subscription.scen.json"); +} + +#[test] +#[ignore = "not supported"] +fn mint_nft_rs() { + world().run("scenarios/mint_nft.scen.json"); +} + +#[test] +#[ignore = "not supported"] +fn init_rs() { + world().run("scenarios/init.scen.json"); +} diff --git a/contracts/examples/nft-subscription/tests/scenario_go_test.rs b/contracts/examples/nft-subscription/tests/scenario_go_test.rs new file mode 100644 index 0000000000..504c7992b0 --- /dev/null +++ b/contracts/examples/nft-subscription/tests/scenario_go_test.rs @@ -0,0 +1,20 @@ +use multiversx_sc_scenario::*; + +fn world() -> ScenarioWorld { + ScenarioWorld::vm_go() +} + +#[test] +fn test_subscription_go() { + world().run("scenarios/test_subscription.scen.json"); +} + +#[test] +fn mint_nft_go() { + world().run("scenarios/mint_nft.scen.json"); +} + +#[test] +fn init_go() { + world().run("scenarios/init.scen.json"); +} diff --git a/contracts/examples/nft-subscription/wasm/Cargo.lock b/contracts/examples/nft-subscription/wasm/Cargo.lock new file mode 100644 index 0000000000..f618f1a62d --- /dev/null +++ b/contracts/examples/nft-subscription/wasm/Cargo.lock @@ -0,0 +1,217 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "ahash" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", +] + +[[package]] +name = "arrayvec" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "endian-type" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d" + +[[package]] +name = "hashbrown" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" +dependencies = [ + "ahash", +] + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hex-literal" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ebdb29d2ea9ed0083cd8cece49bbd968021bd99b0849edb4a9a7ee0fdf6a4e0" + +[[package]] +name = "multiversx-sc" +version = "0.43.4" +dependencies = [ + "bitflags", + "hashbrown", + "hex-literal", + "multiversx-sc-codec", + "multiversx-sc-derive", + "num-traits", +] + +[[package]] +name = "multiversx-sc-codec" +version = "0.18.1" +dependencies = [ + "arrayvec", + "multiversx-sc-codec-derive", +] + +[[package]] +name = "multiversx-sc-codec-derive" +version = "0.18.1" +dependencies = [ + "hex", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "multiversx-sc-derive" +version = "0.43.4" +dependencies = [ + "hex", + "proc-macro2", + "quote", + "radix_trie", + "syn", +] + +[[package]] +name = "multiversx-sc-modules" +version = "0.43.4" +dependencies = [ + "multiversx-sc", +] + +[[package]] +name = "multiversx-sc-wasm-adapter" +version = "0.43.4" +dependencies = [ + "multiversx-sc", +] + +[[package]] +name = "nft-subscription" +version = "0.0.0" +dependencies = [ + "multiversx-sc", + "multiversx-sc-modules", +] + +[[package]] +name = "nft-subscription-wasm" +version = "0.0.0" +dependencies = [ + "multiversx-sc-wasm-adapter", + "nft-subscription", +] + +[[package]] +name = "nibble_vec" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a5d83df9f36fe23f0c3648c6bbb8b0298bb5f1939c8f2704431371f4b84d43" +dependencies = [ + "smallvec", +] + +[[package]] +name = "num-traits" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f30b0abd723be7e2ffca1272140fac1a2f084c77ec3e123c192b66af1ee9e6c2" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" + +[[package]] +name = "proc-macro2" +version = "1.0.67" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d433d9f1a3e8c1263d9456598b16fec66f4acc9a74dacffd35c7bb09b3a1328" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "radix_trie" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c069c179fcdc6a2fe24d8d18305cf085fdbd4f922c041943e203685d6a1c58fd" +dependencies = [ + "endian-type", + "nibble_vec", +] + +[[package]] +name = "smallvec" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "942b4a808e05215192e39f4ab80813e599068285906cc91aa64f923db842bd5a" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" diff --git a/contracts/examples/nft-subscription/wasm/Cargo.toml b/contracts/examples/nft-subscription/wasm/Cargo.toml new file mode 100644 index 0000000000..4cfb2cf9a4 --- /dev/null +++ b/contracts/examples/nft-subscription/wasm/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "nft-subscription-wasm" +version = "0.0.0" +authors = ["Thouny <thouny@tuta.io>"] +edition = "2021" +publish = false + +[lib] +crate-type = ["cdylib"] + +[profile.release] +codegen-units = 1 +opt-level = "z" +lto = true +debug = false +panic = "abort" + +[dependencies.nft-subscription] +path = ".." + +[dependencies.multiversx-sc-wasm-adapter] +version = "0.43.4" +path = "../../../../framework/wasm-adapter" + +[workspace] +members = ["."] diff --git a/contracts/examples/nft-subscription/wasm/src/lib.rs b/contracts/examples/nft-subscription/wasm/src/lib.rs new file mode 100644 index 0000000000..eba09fdde8 --- /dev/null +++ b/contracts/examples/nft-subscription/wasm/src/lib.rs @@ -0,0 +1,36 @@ +// Code generated by the multiversx-sc multi-contract system. DO NOT EDIT. + +//////////////////////////////////////////////////// +////////////////// AUTO-GENERATED ////////////////// +//////////////////////////////////////////////////// + +// Init: 1 +// Endpoints: 8 +// Async Callback: 1 +// Total number of exported functions: 10 + +#![no_std] + +// Configuration that works with rustc < 1.73.0. +// TODO: Recommended rustc version: 1.73.0 or newer. +#![feature(lang_items)] + +multiversx_sc_wasm_adapter::allocator!(); +multiversx_sc_wasm_adapter::panic_handler!(); + +multiversx_sc_wasm_adapter::endpoints! { + nft_subscription + ( + init => init + issue => issue + mint => mint + renew => renew + cancel => cancel + expires => expires + renewSubscription => renew_subscription + cancelSubscription => cancel_subscription + getExpiresAt => expires_at + ) +} + +multiversx_sc_wasm_adapter::async_callback! { nft_subscription } diff --git a/contracts/modules/src/lib.rs b/contracts/modules/src/lib.rs index 3229e4f742..bb21e297eb 100644 --- a/contracts/modules/src/lib.rs +++ b/contracts/modules/src/lib.rs @@ -15,3 +15,4 @@ pub mod staking; pub mod token_merge; pub mod transfer_role_proxy; pub mod users; +pub mod subscription; diff --git a/contracts/modules/src/subscription.rs b/contracts/modules/src/subscription.rs new file mode 100644 index 0000000000..5fb1a5e938 --- /dev/null +++ b/contracts/modules/src/subscription.rs @@ -0,0 +1,51 @@ +multiversx_sc::imports!(); + +/// Standard smart contract module for managing a Subscription NFT. +/// Adaptation of the EIP-5643 for MultiversX, more here https://eips.ethereum.org/EIPS/eip-5643 +/// +/// This standard is an extension of MultiversX NFT standard. +/// It proposes an additional interface for NFTs to be used as recurring, expirable subscriptions. +/// The interface includes functions to renew and cancel the subscription. +/// +/// It provides functions for: +/// * renewing a subscription +/// * cancelling a subscription +/// * getting the expiration +/// +#[multiversx_sc::module] +pub trait SubscriptionModule { + #[event("subscriptionUpdate")] + fn subscription_update_event( + &self, + #[indexed] token_id: &ManagedBuffer, + #[indexed] token_nonce: u64, + #[indexed] expiration: u64, + ); + + #[endpoint(renewSubscription)] + fn renew_subscription(&self, id: &TokenIdentifier, nonce: u64, duration: u64) { + let expiration = self.expires_at(id, nonce); + let time = self.blockchain().get_block_timestamp(); + + let new_expiration = if expiration > time { + expiration + duration + } else { + time + duration + }; + + self.send().nft_update_attributes(id, nonce, &new_expiration); + self.subscription_update_event(id.as_managed_buffer(), nonce, new_expiration); + } + + #[endpoint(cancelSubscription)] + fn cancel_subscription(&self, id: &TokenIdentifier, nonce: u64) { + self.send().nft_update_attributes(id, nonce, &0); + self.subscription_update_event(id.as_managed_buffer(), nonce, 0); + } + + // @dev should only be called if the nft is owned by the contract + #[view(getExpiresAt)] + fn expires_at(&self, id: &TokenIdentifier, nonce: u64) -> u64 { + self.blockchain().get_token_attributes(id, nonce) + } +} \ No newline at end of file From b77ac19416d3cebc138566e05f8745bf697ad5cb Mon Sep 17 00:00:00 2001 From: Thounyy <thouny@tuta.io> Date: Thu, 28 Sep 2023 11:39:51 +0700 Subject: [PATCH 2/3] cargo fmt --- .../examples/nft-subscription/src/lib.rs | 40 +++++++++++++------ contracts/modules/src/lib.rs | 2 +- contracts/modules/src/subscription.rs | 15 +++---- 3 files changed, 36 insertions(+), 21 deletions(-) diff --git a/contracts/examples/nft-subscription/src/lib.rs b/contracts/examples/nft-subscription/src/lib.rs index 9120206671..6394556e83 100644 --- a/contracts/examples/nft-subscription/src/lib.rs +++ b/contracts/examples/nft-subscription/src/lib.rs @@ -3,29 +3,34 @@ multiversx_sc::imports!(); multiversx_sc::derive_imports!(); -use multiversx_sc_modules::default_issue_callbacks; -use multiversx_sc_modules::subscription; +use multiversx_sc_modules::{default_issue_callbacks, subscription}; #[multiversx_sc::contract] -pub trait NftSubscription: default_issue_callbacks::DefaultIssueCallbacksModule + subscription::SubscriptionModule { +pub trait NftSubscription: + default_issue_callbacks::DefaultIssueCallbacksModule + subscription::SubscriptionModule +{ #[init] fn init(&self) {} #[endpoint] fn issue(&self) { self.token_id().issue_and_set_all_roles( - EsdtTokenType::NonFungible, - self.call_value().egld_value().clone_value(), - ManagedBuffer::from(b"Subscription"), - ManagedBuffer::from(b"SUB"), - 0, - None + EsdtTokenType::NonFungible, + self.call_value().egld_value().clone_value(), + ManagedBuffer::from(b"Subscription"), + ManagedBuffer::from(b"SUB"), + 0, + None, ) } #[endpoint] fn mint(&self) { - self.token_id().nft_create_and_send(&self.blockchain().get_caller(), BigUint::from(1u8), &0u64); + self.token_id().nft_create_and_send( + &self.blockchain().get_caller(), + BigUint::from(1u8), + &0u64, + ); } #[payable("*")] @@ -33,7 +38,12 @@ pub trait NftSubscription: default_issue_callbacks::DefaultIssueCallbacksModule fn renew(&self, duration: u64) { let (id, nonce, _) = self.call_value().single_esdt().into_tuple(); self.renew_subscription(&id, nonce, duration); - self.send().direct_esdt(&self.blockchain().get_caller(), &id, nonce, &BigUint::from(1u8)); + self.send().direct_esdt( + &self.blockchain().get_caller(), + &id, + nonce, + &BigUint::from(1u8), + ); } #[payable("*")] @@ -41,7 +51,12 @@ pub trait NftSubscription: default_issue_callbacks::DefaultIssueCallbacksModule fn cancel(&self) { let (id, nonce, _) = self.call_value().single_esdt().into_tuple(); self.cancel_subscription(&id, nonce); - self.send().direct_esdt(&self.blockchain().get_caller(), &id, nonce, &BigUint::from(1u8)); + self.send().direct_esdt( + &self.blockchain().get_caller(), + &id, + nonce, + &BigUint::from(1u8), + ); } #[view] @@ -51,5 +66,4 @@ pub trait NftSubscription: default_issue_callbacks::DefaultIssueCallbacksModule #[storage_mapper("tokenId")] fn token_id(&self) -> NonFungibleTokenMapper; - } diff --git a/contracts/modules/src/lib.rs b/contracts/modules/src/lib.rs index bb21e297eb..b1973e3fc0 100644 --- a/contracts/modules/src/lib.rs +++ b/contracts/modules/src/lib.rs @@ -12,7 +12,7 @@ pub mod ongoing_operation; pub mod only_admin; pub mod pause; pub mod staking; +pub mod subscription; pub mod token_merge; pub mod transfer_role_proxy; pub mod users; -pub mod subscription; diff --git a/contracts/modules/src/subscription.rs b/contracts/modules/src/subscription.rs index 5fb1a5e938..68c05a57a6 100644 --- a/contracts/modules/src/subscription.rs +++ b/contracts/modules/src/subscription.rs @@ -2,9 +2,9 @@ multiversx_sc::imports!(); /// Standard smart contract module for managing a Subscription NFT. /// Adaptation of the EIP-5643 for MultiversX, more here https://eips.ethereum.org/EIPS/eip-5643 -/// -/// This standard is an extension of MultiversX NFT standard. -/// It proposes an additional interface for NFTs to be used as recurring, expirable subscriptions. +/// +/// This standard is an extension of MultiversX NFT standard. +/// It proposes an additional interface for NFTs to be used as recurring, expirable subscriptions. /// The interface includes functions to renew and cancel the subscription. /// /// It provides functions for: @@ -33,19 +33,20 @@ pub trait SubscriptionModule { time + duration }; - self.send().nft_update_attributes(id, nonce, &new_expiration); + self.send() + .nft_update_attributes(id, nonce, &new_expiration); self.subscription_update_event(id.as_managed_buffer(), nonce, new_expiration); } - #[endpoint(cancelSubscription)] + #[endpoint(cancelSubscription)] fn cancel_subscription(&self, id: &TokenIdentifier, nonce: u64) { self.send().nft_update_attributes(id, nonce, &0); self.subscription_update_event(id.as_managed_buffer(), nonce, 0); } // @dev should only be called if the nft is owned by the contract - #[view(getExpiresAt)] + #[view(getExpiresAt)] fn expires_at(&self, id: &TokenIdentifier, nonce: u64) -> u64 { self.blockchain().get_token_attributes(id, nonce) } -} \ No newline at end of file +} From d226f58ecba373adf50bb5dc2989a1357929dad5 Mon Sep 17 00:00:00 2001 From: Thounyy <thouny@tuta.io> Date: Wed, 4 Oct 2023 20:15:48 +0700 Subject: [PATCH 3/3] enable any custom attributes in addition to the subscription --- .../scenarios/mint_nft.scen.json | 85 ++++++------ .../scenarios/test_subscription.scen.json | 72 +++------- .../examples/nft-subscription/src/lib.rs | 39 ++++-- .../examples/nft-subscription/wasm/src/lib.rs | 9 +- contracts/modules/src/subscription.rs | 130 ++++++++++++++++-- 5 files changed, 206 insertions(+), 129 deletions(-) diff --git a/contracts/examples/nft-subscription/scenarios/mint_nft.scen.json b/contracts/examples/nft-subscription/scenarios/mint_nft.scen.json index 045284cc60..b51d81fb3c 100644 --- a/contracts/examples/nft-subscription/scenarios/mint_nft.scen.json +++ b/contracts/examples/nft-subscription/scenarios/mint_nft.scen.json @@ -1,5 +1,5 @@ { - "name": "mint nfts", + "name": "mint and update nft", "steps": [ { "step": "externalSteps", @@ -15,32 +15,52 @@ "arguments": [], "gasLimit": "20,000,000", "gasPrice": "0" - }, - "expect": { - "out": [], - "status": "0", - "message": "", - "gas": "*", - "refund": "*" + } + }, + { + "step": "checkState", + "accounts": { + "address:owner": { + "nonce": "2", + "balance": "0", + "esdt": { + "str:NFT-123456": { + "instances": [ + { + "nonce": "1", + "balance": "1", + "creator": "sc:nft-subscription", + "attributes": { + "0-expiration": "u64:0", + "1-attributes": "nested:str:common" + } + } + ] + } + } + }, + "+": "" } }, { "step": "scCall", - "id": "create-NFT-2", + "id": "update-NFT-1", "tx": { "from": "address:owner", "to": "sc:nft-subscription", - "function": "mint", - "arguments": [], + "esdtValue": [ + { + "tokenIdentifier": "str:NFT-123456", + "nonce": "1", + "value": "1" + } + ], + "function": "update_attributes", + "arguments": [ + "str:rare" + ], "gasLimit": "20,000,000", "gasPrice": "0" - }, - "expect": { - "out": [], - "status": "0", - "message": "", - "gas": "*", - "refund": "*" } }, { @@ -56,36 +76,15 @@ "nonce": "1", "balance": "1", "creator": "sc:nft-subscription", - "attributes": "0" - }, - { - "nonce": "2", - "balance": "1", - "creator": "sc:nft-subscription", - "attributes": "0" + "attributes": { + "0-expiration": "u64:0", + "1-attributes": "nested:str:rare" + } } ] } } }, - "sc:nft-subscription": { - "nonce": "0", - "balance": "0", - "esdt": { - "str:NFT-123456": { - "lastNonce": "2", - "roles": [ - "ESDTRoleNFTCreate", - "ESDTRoleNFTUpdateAttributes" - ] - } - }, - "storage": { - "str:tokenId": "str:NFT-123456" - }, - "code": "file:../output/nft-subscription.wasm", - "owner": "address:owner" - }, "+": "" } } diff --git a/contracts/examples/nft-subscription/scenarios/test_subscription.scen.json b/contracts/examples/nft-subscription/scenarios/test_subscription.scen.json index 8820a92b2b..7948b03bcc 100644 --- a/contracts/examples/nft-subscription/scenarios/test_subscription.scen.json +++ b/contracts/examples/nft-subscription/scenarios/test_subscription.scen.json @@ -30,13 +30,6 @@ ], "gasLimit": "20,000,000", "gasPrice": "0" - }, - "expect": { - "out": [], - "status": "0", - "message": "", - "gas": "*", - "refund": "*" } }, { @@ -52,13 +45,10 @@ "nonce": "1", "balance": "1", "creator": "sc:nft-subscription", - "attributes": "3" - }, - { - "nonce": "2", - "balance": "1", - "creator": "sc:nft-subscription", - "attributes": "0" + "attributes": { + "0-expiration": "u64:3", + "1-attributes": "nested:str:rare" + } } ] } @@ -92,13 +82,6 @@ ], "gasLimit": "20,000,000", "gasPrice": "0" - }, - "expect": { - "out": [], - "status": "0", - "message": "", - "gas": "*", - "refund": "*" } }, { @@ -114,13 +97,10 @@ "nonce": "1", "balance": "1", "creator": "sc:nft-subscription", - "attributes": "6" - }, - { - "nonce": "2", - "balance": "1", - "creator": "sc:nft-subscription", - "attributes": "0" + "attributes": { + "0-expiration": "u64:6", + "1-attributes": "nested:str:rare" + } } ] } @@ -154,13 +134,6 @@ ], "gasLimit": "20,000,000", "gasPrice": "0" - }, - "expect": { - "out": [], - "status": "0", - "message": "", - "gas": "*", - "refund": "*" } }, { @@ -176,13 +149,10 @@ "nonce": "1", "balance": "1", "creator": "sc:nft-subscription", - "attributes": "15" - }, - { - "nonce": "2", - "balance": "1", - "creator": "sc:nft-subscription", - "attributes": "0" + "attributes": { + "0-expiration": "u64:15", + "1-attributes": "nested:str:rare" + } } ] } @@ -214,13 +184,6 @@ "arguments": [], "gasLimit": "20,000,000", "gasPrice": "0" - }, - "expect": { - "out": [], - "status": "0", - "message": "", - "gas": "*", - "refund": "*" } }, { @@ -236,13 +199,10 @@ "nonce": "1", "balance": "1", "creator": "sc:nft-subscription", - "attributes": "0" - }, - { - "nonce": "2", - "balance": "1", - "creator": "sc:nft-subscription", - "attributes": "0" + "attributes": { + "0-expiration": "u64:0", + "1-attributes": "nested:str:rare" + } } ] } diff --git a/contracts/examples/nft-subscription/src/lib.rs b/contracts/examples/nft-subscription/src/lib.rs index 6394556e83..b9c4fcdb8d 100644 --- a/contracts/examples/nft-subscription/src/lib.rs +++ b/contracts/examples/nft-subscription/src/lib.rs @@ -26,18 +26,29 @@ pub trait NftSubscription: #[endpoint] fn mint(&self) { - self.token_id().nft_create_and_send( + let nonce = self.create_subscription_nft( + self.token_id().get_token_id_ref(), + &BigUint::from(1u8), + &ManagedBuffer::new(), + &BigUint::from(0u8), + &ManagedBuffer::new(), + 0, + ManagedBuffer::from(b"common"), + &ManagedVec::new(), + ); + self.send().direct_esdt( &self.blockchain().get_caller(), - BigUint::from(1u8), - &0u64, + self.token_id().get_token_id_ref(), + nonce, + &BigUint::from(1u8), ); } #[payable("*")] #[endpoint] - fn renew(&self, duration: u64) { + fn update_attributes(&self, attributes: ManagedBuffer) { let (id, nonce, _) = self.call_value().single_esdt().into_tuple(); - self.renew_subscription(&id, nonce, duration); + self.update_subscription_attributes::<ManagedBuffer>(&id, nonce, attributes); self.send().direct_esdt( &self.blockchain().get_caller(), &id, @@ -48,9 +59,9 @@ pub trait NftSubscription: #[payable("*")] #[endpoint] - fn cancel(&self) { + fn renew(&self, duration: u64) { let (id, nonce, _) = self.call_value().single_esdt().into_tuple(); - self.cancel_subscription(&id, nonce); + self.renew_subscription::<ManagedBuffer>(&id, nonce, duration); self.send().direct_esdt( &self.blockchain().get_caller(), &id, @@ -59,9 +70,17 @@ pub trait NftSubscription: ); } - #[view] - fn expires(&self, id: &TokenIdentifier, nonce: u64) -> u64 { - self.expires_at(id, nonce) + #[payable("*")] + #[endpoint] + fn cancel(&self) { + let (id, nonce, _) = self.call_value().single_esdt().into_tuple(); + self.cancel_subscription::<ManagedBuffer>(&id, nonce); + self.send().direct_esdt( + &self.blockchain().get_caller(), + &id, + nonce, + &BigUint::from(1u8), + ); } #[storage_mapper("tokenId")] diff --git a/contracts/examples/nft-subscription/wasm/src/lib.rs b/contracts/examples/nft-subscription/wasm/src/lib.rs index eba09fdde8..76a79ce530 100644 --- a/contracts/examples/nft-subscription/wasm/src/lib.rs +++ b/contracts/examples/nft-subscription/wasm/src/lib.rs @@ -5,9 +5,9 @@ //////////////////////////////////////////////////// // Init: 1 -// Endpoints: 8 +// Endpoints: 5 // Async Callback: 1 -// Total number of exported functions: 10 +// Total number of exported functions: 7 #![no_std] @@ -24,12 +24,9 @@ multiversx_sc_wasm_adapter::endpoints! { init => init issue => issue mint => mint + update_attributes => update_attributes renew => renew cancel => cancel - expires => expires - renewSubscription => renew_subscription - cancelSubscription => cancel_subscription - getExpiresAt => expires_at ) } diff --git a/contracts/modules/src/subscription.rs b/contracts/modules/src/subscription.rs index 68c05a57a6..3f142abbe4 100644 --- a/contracts/modules/src/subscription.rs +++ b/contracts/modules/src/subscription.rs @@ -1,19 +1,95 @@ multiversx_sc::imports!(); +multiversx_sc::derive_imports!(); /// Standard smart contract module for managing a Subscription NFT. /// Adaptation of the EIP-5643 for MultiversX, more here https://eips.ethereum.org/EIPS/eip-5643 /// -/// This standard is an extension of MultiversX NFT standard. +/// This standard is an extension of the MultiversX NFT standard. /// It proposes an additional interface for NFTs to be used as recurring, expirable subscriptions. /// The interface includes functions to renew and cancel the subscription. /// -/// It provides functions for: +/// Since the NFT standard only has one field for adding arbitrary data (attributes), +/// The module also provides functions for creating NFTs with subscription as well as for reading and updating attributes +/// This allows developers to add additional data to the subscription expiration +/// +/// Developers should be careful when interacting with custom attributes at the same time as subscription +/// They should exclusively use the functions from this module +/// The use of the generic function for updating nft attributes might result in data loss +/// +/// The module provides functions for: +/// * creating a subscription nft +/// * updating custom attributes +/// * getting custom attributes /// * renewing a subscription /// * cancelling a subscription /// * getting the expiration /// +#[derive(TypeAbi, TopEncode, TopDecode)] +pub struct SubscriptionAttributes<T: NestedEncode + NestedDecode + TypeAbi> { + pub expiration: u64, + pub attributes: T, +} + #[multiversx_sc::module] pub trait SubscriptionModule { + // ** NFT and Attributes + + fn create_subscription_nft<T: NestedEncode + NestedDecode + TypeAbi>( + &self, + token_id: &TokenIdentifier, + amount: &BigUint, + name: &ManagedBuffer, + royalties: &BigUint, + hash: &ManagedBuffer, + duration: u64, + attributes: T, + uris: &ManagedVec<ManagedBuffer>, + ) -> u64 { + let subscription_attributes = SubscriptionAttributes::<T> { + expiration: self.blockchain().get_block_timestamp() + duration, + attributes, + }; + + self.send().esdt_nft_create( + token_id, + amount, + name, + royalties, + hash, + &subscription_attributes, + uris, + ) + } + + fn update_subscription_attributes<T: NestedEncode + NestedDecode + TypeAbi>( + &self, + id: &TokenIdentifier, + nonce: u64, + attributes: T, + ) { + let subscription_attributes = SubscriptionAttributes::<T> { + expiration: self.get_subscription::<T>(id, nonce), + attributes, + }; + + self.send() + .nft_update_attributes(id, nonce, &subscription_attributes); + } + + // @dev should only be called if the nft is owned by the contract + fn get_subscription_attributes<T: NestedEncode + NestedDecode + TypeAbi>( + &self, + id: &TokenIdentifier, + nonce: u64, + ) -> T { + let subscription_attributes: SubscriptionAttributes<T> = + self.blockchain().get_token_attributes(id, nonce); + + subscription_attributes.attributes + } + + // ** Subscription + #[event("subscriptionUpdate")] fn subscription_update_event( &self, @@ -22,31 +98,57 @@ pub trait SubscriptionModule { #[indexed] expiration: u64, ); - #[endpoint(renewSubscription)] - fn renew_subscription(&self, id: &TokenIdentifier, nonce: u64, duration: u64) { - let expiration = self.expires_at(id, nonce); + fn renew_subscription<T: NestedEncode + NestedDecode + TypeAbi>( + &self, + id: &TokenIdentifier, + nonce: u64, + duration: u64, + ) { let time = self.blockchain().get_block_timestamp(); + let mut subscription_attributes: SubscriptionAttributes<T> = + self.blockchain().get_token_attributes(id, nonce); + let expiration = subscription_attributes.expiration; - let new_expiration = if expiration > time { + subscription_attributes.expiration = if expiration > time { expiration + duration } else { time + duration }; self.send() - .nft_update_attributes(id, nonce, &new_expiration); - self.subscription_update_event(id.as_managed_buffer(), nonce, new_expiration); + .nft_update_attributes(id, nonce, &subscription_attributes); + + self.subscription_update_event( + id.as_managed_buffer(), + nonce, + subscription_attributes.expiration, + ); } - #[endpoint(cancelSubscription)] - fn cancel_subscription(&self, id: &TokenIdentifier, nonce: u64) { - self.send().nft_update_attributes(id, nonce, &0); + fn cancel_subscription<T: NestedEncode + NestedDecode + TypeAbi>( + &self, + id: &TokenIdentifier, + nonce: u64, + ) { + let mut subscription_attributes: SubscriptionAttributes<T> = + self.blockchain().get_token_attributes(id, nonce); + subscription_attributes.expiration = 0; + + self.send() + .nft_update_attributes(id, nonce, &subscription_attributes); + self.subscription_update_event(id.as_managed_buffer(), nonce, 0); } // @dev should only be called if the nft is owned by the contract - #[view(getExpiresAt)] - fn expires_at(&self, id: &TokenIdentifier, nonce: u64) -> u64 { - self.blockchain().get_token_attributes(id, nonce) + fn get_subscription<T: NestedEncode + NestedDecode + TypeAbi>( + &self, + id: &TokenIdentifier, + nonce: u64, + ) -> u64 { + let subscription_attributes: SubscriptionAttributes<T> = + self.blockchain().get_token_attributes(id, nonce); + + subscription_attributes.expiration } }