diff --git a/crates/sui-cost/tests/snapshots/empirical_transaction_cost__good_snapshot.snap b/crates/sui-cost/tests/snapshots/empirical_transaction_cost__good_snapshot.snap index f16001b9b0275..cd05ff13dbaa4 100644 --- a/crates/sui-cost/tests/snapshots/empirical_transaction_cost__good_snapshot.snap +++ b/crates/sui-cost/tests/snapshots/empirical_transaction_cost__good_snapshot.snap @@ -11,7 +11,7 @@ expression: common_costs_actual }, "Publish": { "computationCost": "1000000", - "storageCost": "13505200", + "storageCost": "15671200", "storageRebate": "0", "nonRefundableStorageFee": "0" }, diff --git a/crates/sui-framework/docs/sui-framework/coin.md b/crates/sui-framework/docs/sui-framework/coin.md index 869e0a6406a4c..0bd9befd1a3c4 100644 --- a/crates/sui-framework/docs/sui-framework/coin.md +++ b/crates/sui-framework/docs/sui-framework/coin.md @@ -12,8 +12,10 @@ tokens and coins. CoinCoinCoinuse 0x1::type_name; use 0x2::balance; use 0x2::deny_list; +use 0x2::dynamic_field; use 0x2::object; use 0x2::transfer; use 0x2::tx_context; @@ -285,6 +290,35 @@ all addresses were added to the deny list. + + + + +## Struct `RegulatedMarker` + +A dynamic field key added to CoinMetadata objects created after protocol version 63. +This is bound to a RegulatedInfo enum + + +
struct RegulatedMarker has copy, drop, store
+
+ + + +
+Fields + + +
+
+dummy_field: bool +
+
+ +
+
+ +
@@ -341,6 +375,40 @@ interacting with the coin as an input to a transaction. + + + + +## Enum `RegulatedInfo` + +Auxiliary metadata about the regulated operations supported by the coin (if any) + + +
public enum RegulatedInfo has copy, drop, store
+
+ + + +
+Variants + + +
+
+Variant Ordinary +
+
+ An ordinary coin that does not support a denylist or global pause +
+
+Variant Regulated +
+
+ A regulated coin that supports a denylist, and (maybe) global paus +
+
+ +
@@ -852,10 +920,51 @@ type, ensuring that there's only one vector<u8>, icon_url: Option<Url>, ctx: &mut TxContext, +): (TreasuryCap<T>, CoinMetadata<T>) { + let (treasury_cap, mut metadata) = create_currency_( + witness, + decimals, + symbol, + name, + description, + icon_url, + ctx, + ); + dynamic_field::add(&mut metadata.id, RegulatedMarker(), RegulatedInfo::Ordinary); + (treasury_cap, metadata) +} + + + + + + + + +## Function `create_currency_` + + + +
fun create_currency_<T: drop>(witness: T, decimals: u8, symbol: vector<u8>, name: vector<u8>, description: vector<u8>, icon_url: option::Option<url::Url>, ctx: &mut tx_context::TxContext): (coin::TreasuryCap<T>, coin::CoinMetadata<T>)
+
+ + + +
+Implementation + + +
fun create_currency_<T: drop>(
+    witness: T,
+    decimals: u8,
+    symbol: vector<u8>,
+    name: vector<u8>,
+    description: vector<u8>,
+    icon_url: Option<Url>,
+    ctx: &mut TxContext,
 ): (TreasuryCap<T>, CoinMetadata<T>) {
     // Make sure there's only one instance of the type T
     assert!(sui::types::is_one_time_witness(&witness), EBadWitness);
-
     (
         TreasuryCap {
             id: object::new(ctx),
@@ -910,7 +1019,7 @@ will not change the result of the "contains" APIs.
     allow_global_pause: bool,
     ctx: &mut TxContext,
 ): (TreasuryCap<T>, DenyCapV2<T>, CoinMetadata<T>) {
-    let (treasury_cap, metadata) = create_currency(
+    let (treasury_cap, mut metadata) = create_currency_(
         witness,
         decimals,
         symbol,
@@ -928,12 +1037,49 @@ will not change the result of the "contains" APIs.
         coin_metadata_object: object::id(&metadata),
         deny_cap_object: object::id(&deny_cap),
     });
+    dynamic_field::add(
+        &mut metadata.id,
+        RegulatedMarker(),
+        RegulatedInfo::Regulated,
+    );
     (treasury_cap, deny_cap, metadata)
 }
 
+
+ + + +## Function `is_regulated_currency` + +Return Some(true) if T is a regulated currency, Some(false) if it is an ordinary currency. +Return None if T was created at a protocol version prior to 63 + + +
public fun is_regulated_currency<T>(metadata: &coin::CoinMetadata<T>): option::Option<bool>
+
+ + + +
+Implementation + + +
public fun is_regulated_currency<T>(metadata: &CoinMetadata<T>): Option<bool> {
+    if (!dynamic_field::exists_(&metadata.id, RegulatedMarker())) {
+        return option::none()
+    };
+    match (dynamic_field::borrow(&metadata.id, RegulatedMarker())) {
+        RegulatedInfo::Ordinary => option::some(false),
+        RegulatedInfo::Regulated => option::some(true),
+    }
+}
+
+ + +
@@ -1622,7 +1768,7 @@ with the coin as input objects. icon_url: Option<Url>, ctx: &mut TxContext, ): (TreasuryCap<T>, DenyCap<T>, CoinMetadata<T>) { - let (treasury_cap, metadata) = create_currency( + let (treasury_cap, mut metadata) = create_currency_( witness, decimals, symbol, @@ -1639,6 +1785,11 @@ with the coin as input objects. coin_metadata_object: object::id(&metadata), deny_cap_object: object::id(&deny_cap), }); + dynamic_field::add( + &mut metadata.id, + RegulatedMarker(), + RegulatedInfo::Regulated, + ); (treasury_cap, deny_cap, metadata) }
diff --git a/crates/sui-framework/packages/sui-framework/sources/coin.move b/crates/sui-framework/packages/sui-framework/sources/coin.move index 92dfc3d59832a..e9a0b52d96264 100644 --- a/crates/sui-framework/packages/sui-framework/sources/coin.move +++ b/crates/sui-framework/packages/sui-framework/sources/coin.move @@ -11,6 +11,7 @@ use std::string; use std::type_name; use sui::balance::{Self, Balance, Supply}; use sui::deny_list::DenyList; +use sui::dynamic_field; use sui::url::{Self, Url}; // Allows calling `.split_vec(amounts, ctx)` on `coin` @@ -88,6 +89,18 @@ public struct DenyCapV2 has key, store { allow_global_pause: bool, } +/// A dynamic field key added to `CoinMetadata` objects created after protocol version 63. +/// This is bound to a `RegulatedInfo` enum +public struct RegulatedMarker() has copy, drop, store; + +/// Auxiliary metadata about the regulated operations supported by the coin (if any) +public enum RegulatedInfo has copy, drop, store { + /// An ordinary coin that does not support a denylist or global pause + Ordinary, + /// A regulated coin that supports a denylist, and (maybe) global paus + Regulated, +} + // === Supply <-> TreasuryCap morphing and accessors === /// Return the total number of `T`'s in circulation. @@ -216,10 +229,31 @@ public fun create_currency( description: vector, icon_url: Option, ctx: &mut TxContext, +): (TreasuryCap, CoinMetadata) { + let (treasury_cap, mut metadata) = create_currency_( + witness, + decimals, + symbol, + name, + description, + icon_url, + ctx, + ); + dynamic_field::add(&mut metadata.id, RegulatedMarker(), RegulatedInfo::Ordinary); + (treasury_cap, metadata) +} + +fun create_currency_( + witness: T, + decimals: u8, + symbol: vector, + name: vector, + description: vector, + icon_url: Option, + ctx: &mut TxContext, ): (TreasuryCap, CoinMetadata) { // Make sure there's only one instance of the type T assert!(sui::types::is_one_time_witness(&witness), EBadWitness); - ( TreasuryCap { id: object::new(ctx), @@ -254,7 +288,7 @@ public fun create_regulated_currency_v2( allow_global_pause: bool, ctx: &mut TxContext, ): (TreasuryCap, DenyCapV2, CoinMetadata) { - let (treasury_cap, metadata) = create_currency( + let (treasury_cap, mut metadata) = create_currency_( witness, decimals, symbol, @@ -272,9 +306,26 @@ public fun create_regulated_currency_v2( coin_metadata_object: object::id(&metadata), deny_cap_object: object::id(&deny_cap), }); + dynamic_field::add( + &mut metadata.id, + RegulatedMarker(), + RegulatedInfo::Regulated, + ); (treasury_cap, deny_cap, metadata) } +/// Return Some(true) if `T` is a regulated currency, Some(false) if it is an ordinary currency. +/// Return None if T was created at a protocol version prior to 63 +public fun is_regulated_currency(metadata: &CoinMetadata): Option { + if (!dynamic_field::exists_(&metadata.id, RegulatedMarker())) { + return option::none() + }; + match (dynamic_field::borrow(&metadata.id, RegulatedMarker())) { + RegulatedInfo::Ordinary => option::some(false), + RegulatedInfo::Regulated => option::some(true), + } +} + /// Given the `DenyCap` for a regulated currency, migrate it to the new `DenyCapV2` type. /// All entries in the deny list will be migrated to the new format. /// See `create_regulated_currency_v2` for details on the new v2 of the deny list. @@ -539,7 +590,7 @@ public fun create_regulated_currency( icon_url: Option, ctx: &mut TxContext, ): (TreasuryCap, DenyCap, CoinMetadata) { - let (treasury_cap, metadata) = create_currency( + let (treasury_cap, mut metadata) = create_currency_( witness, decimals, symbol, @@ -556,6 +607,11 @@ public fun create_regulated_currency( coin_metadata_object: object::id(&metadata), deny_cap_object: object::id(&deny_cap), }); + dynamic_field::add( + &mut metadata.id, + RegulatedMarker(), + RegulatedInfo::Regulated, + ); (treasury_cap, deny_cap, metadata) } diff --git a/crates/sui-framework/packages/sui-framework/tests/coin_tests.move b/crates/sui-framework/packages/sui-framework/tests/coin_tests.move index 641070d931f4a..55e3bd2b3adae 100644 --- a/crates/sui-framework/packages/sui-framework/tests/coin_tests.move +++ b/crates/sui-framework/packages/sui-framework/tests/coin_tests.move @@ -8,6 +8,7 @@ module sui::coin_tests { use sui::url; use sui::test_scenario; use sui::deny_list; + use sui::test_utils; public struct COIN_TESTS has drop {} @@ -648,4 +649,70 @@ module sui::coin_tests { transfer::public_freeze_object(deny_cap); scenario.end(); } + + #[test] + fun test_is_regulated_currency() { + let ctx = &mut tx_context::dummy(); + deny_list::create_for_test(ctx); + + let witness = COIN_TESTS {}; + let (treasury, deny_cap, metadata) = coin::create_regulated_currency( + witness, + 6, + b"COIN_TESTS", + b"coin_name", + b"description", + option::some(url::new_unsafe_from_bytes(b"icon_url")), + ctx, + ); + assert!(metadata.is_regulated_currency().is_some()); + assert!(metadata.is_regulated_currency().extract()); + test_utils::destroy(treasury); + test_utils::destroy(deny_cap); + test_utils::destroy(metadata); + } + + + #[test] + fun test_is_regulated_currency_v2() { + let ctx = &mut tx_context::dummy(); + deny_list::create_for_test(ctx); + + let witness = COIN_TESTS {}; + let (treasury, deny_cap, metadata) = coin::create_regulated_currency_v2( + witness, + 6, + b"COIN_TESTS", + b"coin_name", + b"description", + option::some(url::new_unsafe_from_bytes(b"icon_url")), + true, + ctx, + ); + assert!(metadata.is_regulated_currency().is_some()); + assert!(metadata.is_regulated_currency().extract()); + test_utils::destroy(treasury); + test_utils::destroy(deny_cap); + test_utils::destroy(metadata); + } + + #[test] + fun test_is_regulated_currency_negative() { + let ctx = &mut tx_context::dummy(); + + let witness = COIN_TESTS {}; + let (treasury, metadata) = coin::create_currency( + witness, + 6, + b"COIN_TESTS", + b"coin_name", + b"description", + option::some(url::new_unsafe_from_bytes(b"icon_url")), + ctx, + ); + assert!(metadata.is_regulated_currency().is_some()); + assert!(!metadata.is_regulated_currency().extract()); + test_utils::destroy(treasury); + test_utils::destroy(metadata); + } } diff --git a/crates/sui-framework/packages_compiled/sui-framework b/crates/sui-framework/packages_compiled/sui-framework index 2a8ed674c2787..1ec2c88a17ac5 100644 Binary files a/crates/sui-framework/packages_compiled/sui-framework and b/crates/sui-framework/packages_compiled/sui-framework differ diff --git a/crates/sui-framework/published_api.txt b/crates/sui-framework/published_api.txt index 0950ed0b7d98b..8a2558129bc96 100644 --- a/crates/sui-framework/published_api.txt +++ b/crates/sui-framework/published_api.txt @@ -2047,12 +2047,18 @@ TreasuryCap DenyCapV2 public struct 0x2::coin +RegulatedMarker + public struct + 0x2::coin CurrencyCreated public struct 0x2::coin DenyCap public struct 0x2::coin +RegulatedInfo + public enum + 0x2::coin total_supply public fun 0x2::coin @@ -2104,9 +2110,15 @@ destroy_zero create_currency public fun 0x2::coin +create_currency_ + fun + 0x2::coin create_regulated_currency_v2 public fun 0x2::coin +is_regulated_currency + public fun + 0x2::coin migrate_regulated_currency_to_v2 public fun 0x2::coin