From a219f198ca7b01d7ed9f6f692a9d770e6fbd1113 Mon Sep 17 00:00:00 2001 From: Damir Shamanaev Date: Wed, 25 Sep 2024 12:59:50 +0300 Subject: [PATCH] [framework] Formatting: coin, token, balance, config, transfer 6/N (#19513) ## Description Final framework PR (excluding tests) in the formatting series. ## Test plan All tests must pass, framework must produce the same bytecode. --- ## Release notes Check each box that your changes affect. If none of the boxes relate to your changes, release notes aren't required. For each box you select, include information after the relevant heading that describes the impact of your changes that a user might notice and any actions they must take to implement updates. - [ ] Protocol: - [ ] Nodes (Validators and Full nodes): - [ ] Indexer: - [ ] JSON-RPC: - [ ] GraphQL: - [ ] CLI: - [ ] Rust SDK: - [ ] REST API: --- .../docs/sui-framework/authenticator_state.md | 6 +- .../docs/sui-framework/balance.md | 10 +- .../sui-framework/docs/sui-framework/coin.md | 107 +- .../docs/sui-framework/config.md | 2 +- .../docs/sui-framework/deny_list.md | 35 +- .../sui-framework/docs/sui-framework/sui.md | 2 +- .../sui-framework/docs/sui-framework/token.md | 107 +- .../docs/sui-framework/transfer.md | 10 +- .../sources/authenticator_state.move | 6 +- .../sui-framework/sources/balance.move | 290 ++-- .../packages/sui-framework/sources/coin.move | 1080 +++++++------- .../sui-framework/sources/config.move | 476 +++--- .../sui-framework/sources/deny_list.move | 782 +++++----- .../packages/sui-framework/sources/sui.move | 100 +- .../packages/sui-framework/sources/token.move | 1291 ++++++++--------- .../sui-framework/sources/transfer.move | 275 ++-- .../sui-framework/tests/balance_tests.move | 26 + 17 files changed, 2251 insertions(+), 2354 deletions(-) diff --git a/crates/sui-framework/docs/sui-framework/authenticator_state.md b/crates/sui-framework/docs/sui-framework/authenticator_state.md index fe4f48e3a07de..b529ea58e6ee0 100644 --- a/crates/sui-framework/docs/sui-framework/authenticator_state.md +++ b/crates/sui-framework/docs/sui-framework/authenticator_state.md @@ -308,9 +308,9 @@ Sender is not @0x0 the system address.
fun jwk_equal(a: &JWK, b: &JWK): bool {
     (&a.kty == &b.kty) &&
-           (&a.e == &b.e) &&
-           (&a.n == &b.n) &&
-           (&a.alg == &b.alg)
+        (&a.e == &b.e) &&
+        (&a.n == &b.n) &&
+        (&a.alg == &b.alg)
 }
 
diff --git a/crates/sui-framework/docs/sui-framework/balance.md b/crates/sui-framework/docs/sui-framework/balance.md index f901091e319b4..b1f07a40d5384 100644 --- a/crates/sui-framework/docs/sui-framework/balance.md +++ b/crates/sui-framework/docs/sui-framework/balance.md @@ -435,10 +435,7 @@ and nowhere else.
fun create_staking_rewards<T>(value: u64, ctx: &TxContext): Balance<T> {
     assert!(ctx.sender() == @0x0, ENotSystemAddress);
-    assert!(
-        std::type_name::get<T>().into_string().into_bytes() == SUI_TYPE_NAME,
-        ENotSUI,
-    );
+    assert!(std::type_name::get<T>().into_string().into_bytes() == SUI_TYPE_NAME, ENotSUI);
     Balance { value }
 }
 
@@ -467,10 +464,7 @@ and nowhere else.
fun destroy_storage_rebates<T>(self: Balance<T>, ctx: &TxContext) {
     assert!(ctx.sender() == @0x0, ENotSystemAddress);
-    assert!(
-        std::type_name::get<T>().into_string().into_bytes() == SUI_TYPE_NAME,
-        ENotSUI,
-    );
+    assert!(std::type_name::get<T>().into_string().into_bytes() == SUI_TYPE_NAME, ENotSUI);
     let Balance { value: _ } = self;
 }
 
diff --git a/crates/sui-framework/docs/sui-framework/coin.md b/crates/sui-framework/docs/sui-framework/coin.md index 307d3c710d138..869e0a6406a4c 100644 --- a/crates/sui-framework/docs/sui-framework/coin.md +++ b/crates/sui-framework/docs/sui-framework/coin.md @@ -646,12 +646,10 @@ Aborts if value > bal Implementation -
public fun take<T>(
-    balance: &mut Balance<T>, value: u64, ctx: &mut TxContext,
-): Coin<T> {
+
public fun take<T>(balance: &mut Balance<T>, value: u64, ctx: &mut TxContext): Coin<T> {
     Coin {
         id: object::new(ctx),
-        balance: balance.split(value)
+        balance: balance.split(value),
     }
 }
 
@@ -730,9 +728,7 @@ and the remaining balance is left is self. Implementation -
public fun split<T>(
-    self: &mut Coin<T>, split_amount: u64, ctx: &mut TxContext
-): Coin<T> {
+
public fun split<T>(self: &mut Coin<T>, split_amount: u64, ctx: &mut TxContext): Coin<T> {
     take(&mut self.balance, split_amount, ctx)
 }
 
@@ -758,9 +754,7 @@ Split coin self into n - 1 coins with equal balances. Implementation -
public fun divide_into_n<T>(
-    self: &mut Coin<T>, n: u64, ctx: &mut TxContext
-): vector<Coin<T>> {
+
public fun divide_into_n<T>(self: &mut Coin<T>, n: u64, ctx: &mut TxContext): vector<Coin<T>> {
     assert!(n > 0, EInvalidArg);
     assert!(n <= value(self), ENotEnough);
 
@@ -857,7 +851,7 @@ type, ensuring that there's only one vector<u8>,
     description: vector<u8>,
     icon_url: Option<Url>,
-    ctx: &mut TxContext
+    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);
@@ -865,7 +859,7 @@ type, ensuring that there's only one TreasuryCap {
             id: object::new(ctx),
-            total_supply: balance::create_supply(witness)
+            total_supply: balance::create_supply(witness),
         },
         CoinMetadata {
             id: object::new(ctx),
@@ -873,8 +867,8 @@ type, ensuring that there's only one string::utf8(name),
             symbol: ascii::string(symbol),
             description: string::utf8(description),
-            icon_url
-        }
+            icon_url,
+        },
     )
 }
 
@@ -923,7 +917,7 @@ will not change the result of the "contains" APIs. name, description, icon_url, - ctx + ctx, ); let deny_cap = DenyCapV2 { id: object::new(ctx), @@ -998,12 +992,10 @@ in cap accordingly. Implementation -
public fun mint<T>(
-    cap: &mut TreasuryCap<T>, value: u64, ctx: &mut TxContext,
-): Coin<T> {
+
public fun mint<T>(cap: &mut TreasuryCap<T>, value: u64, ctx: &mut TxContext): Coin<T> {
     Coin {
         id: object::new(ctx),
-        balance: cap.total_supply.increase_supply(value)
+        balance: cap.total_supply.increase_supply(value),
     }
 }
 
@@ -1030,9 +1022,7 @@ Aborts if value + cap.total_supply >= U64_MAX Implementation -
public fun mint_balance<T>(
-    cap: &mut TreasuryCap<T>, value: u64
-): Balance<T> {
+
public fun mint_balance<T>(cap: &mut TreasuryCap<T>, value: u64): Balance<T> {
     cap.total_supply.increase_supply(value)
 }
 
@@ -1184,10 +1174,7 @@ start of the next epoch, the address will be unable to receive objects of this c Implementation -
public fun deny_list_v2_contains_next_epoch<T>(
-    deny_list: &DenyList,
-    addr: address,
-): bool {
+
public fun deny_list_v2_contains_next_epoch<T>(deny_list: &DenyList, addr: address): bool {
     let ty = type_name::get_with_original_ids<T>().into_string().into_bytes();
     deny_list.v2_contains_next_epoch(DENY_LIST_COIN_INDEX, ty, addr)
 }
@@ -1308,9 +1295,7 @@ Check if the global pause is enabled for the given coin type in the next epoch.
 Implementation
 
 
-
public fun deny_list_v2_is_global_pause_enabled_next_epoch<T>(
-    deny_list: &DenyList,
-): bool {
+
public fun deny_list_v2_is_global_pause_enabled_next_epoch<T>(deny_list: &DenyList): bool {
     let ty = type_name::get_with_original_ids<T>().into_string().into_bytes();
     deny_list.v2_is_global_pause_enabled_next_epoch(DENY_LIST_COIN_INDEX, ty)
 }
@@ -1337,7 +1322,10 @@ Mint amount of mint_and_transfer<T>(
-    c: &mut TreasuryCap<T>, amount: u64, recipient: address, ctx: &mut TxContext
+    c: &mut TreasuryCap<T>,
+    amount: u64,
+    recipient: address,
+    ctx: &mut TxContext,
 ) {
     transfer::public_transfer(mint(c, amount, ctx), recipient)
 }
@@ -1364,7 +1352,9 @@ Update name of the coin in update_name<T>(
-    _treasury: &TreasuryCap<T>, metadata: &mut CoinMetadata<T>, name: string::String
+    _treasury: &TreasuryCap<T>,
+    metadata: &mut CoinMetadata<T>,
+    name: string::String,
 ) {
     metadata.name = name;
 }
@@ -1391,7 +1381,9 @@ Update the symbol of the coin in update_symbol<T>(
-    _treasury: &TreasuryCap<T>, metadata: &mut CoinMetadata<T>, symbol: ascii::String
+    _treasury: &TreasuryCap<T>,
+    metadata: &mut CoinMetadata<T>,
+    symbol: ascii::String,
 ) {
     metadata.symbol = symbol;
 }
@@ -1418,7 +1410,9 @@ Update the description of the coin in update_description<T>(
-    _treasury: &TreasuryCap<T>, metadata: &mut CoinMetadata<T>, description: string::String
+    _treasury: &TreasuryCap<T>,
+    metadata: &mut CoinMetadata<T>,
+    description: string::String,
 ) {
     metadata.description = description;
 }
@@ -1445,7 +1439,9 @@ Update the url of the coin in update_icon_url<T>(
-    _treasury: &TreasuryCap<T>, metadata: &mut CoinMetadata<T>, url: ascii::String
+    _treasury: &TreasuryCap<T>,
+    metadata: &mut CoinMetadata<T>,
+    url: ascii::String,
 ) {
     metadata.icon_url = option::some(url::new_unsafe(url));
 }
@@ -1624,7 +1620,7 @@ with the coin as input objects.
     name: vector<u8>,
     description: vector<u8>,
     icon_url: Option<Url>,
-    ctx: &mut TxContext
+    ctx: &mut TxContext,
 ): (TreasuryCap<T>, DenyCap<T>, CoinMetadata<T>) {
     let (treasury_cap, metadata) = create_currency(
         witness,
@@ -1633,7 +1629,7 @@ with the coin as input objects.
         name,
         description,
         icon_url,
-        ctx
+        ctx,
     );
     let deny_cap = DenyCap {
         id: object::new(ctx),
@@ -1669,18 +1665,13 @@ from interacting with the specified coin type as an input to a transaction.
 
 
 
public fun deny_list_add<T>(
-   deny_list: &mut DenyList,
-   _deny_cap: &mut DenyCap<T>,
-   addr: address,
-   _ctx: &mut TxContext
+    deny_list: &mut DenyList,
+    _deny_cap: &mut DenyCap<T>,
+    addr: address,
+    _ctx: &mut TxContext,
 ) {
-    let `type` =
-        type_name::into_string(type_name::get_with_original_ids<T>()).into_bytes();
-    deny_list.v1_add(
-        DENY_LIST_COIN_INDEX,
-        `type`,
-        addr,
-    )
+    let `type` = type_name::into_string(type_name::get_with_original_ids<T>()).into_bytes();
+    deny_list.v1_add(DENY_LIST_COIN_INDEX, `type`, addr)
 }
 
@@ -1706,18 +1697,13 @@ Aborts with ENotFrozen if the address is not already in the list.
public fun deny_list_remove<T>(
-   deny_list: &mut DenyList,
-   _deny_cap: &mut DenyCap<T>,
-   addr: address,
-   _ctx: &mut TxContext
+    deny_list: &mut DenyList,
+    _deny_cap: &mut DenyCap<T>,
+    addr: address,
+    _ctx: &mut TxContext,
 ) {
-    let `type` =
-        type_name::into_string(type_name::get_with_original_ids<T>()).into_bytes();
-    deny_list.v1_remove(
-        DENY_LIST_COIN_INDEX,
-        `type`,
-        addr,
-    )
+    let `type` = type_name::into_string(type_name::get_with_original_ids<T>()).into_bytes();
+    deny_list.v1_remove(DENY_LIST_COIN_INDEX, `type`, addr)
 }
 
@@ -1742,10 +1728,7 @@ return false if given a non-coin type. Implementation -
public fun deny_list_contains<T>(
-   deny_list: &DenyList,
-   addr: address,
-): bool {
+
public fun deny_list_contains<T>(deny_list: &DenyList, addr: address): bool {
     let name = type_name::get_with_original_ids<T>();
     if (type_name::is_primitive(&name)) return false;
 
diff --git a/crates/sui-framework/docs/sui-framework/config.md b/crates/sui-framework/docs/sui-framework/config.md
index b14717f6eb345..769468e8648cb 100644
--- a/crates/sui-framework/docs/sui-framework/config.md
+++ b/crates/sui-framework/docs/sui-framework/config.md
@@ -406,7 +406,7 @@ title: Module `0x2::config`
     Name: copy + drop + store,
     Value: copy + drop + store,
 >(
-    config: & Config<WriteCap>,
+    config: &Config<WriteCap>,
     name: Name,
     ctx: &TxContext,
 ): bool {
diff --git a/crates/sui-framework/docs/sui-framework/deny_list.md b/crates/sui-framework/docs/sui-framework/deny_list.md
index bde0440aff568..275c1bc5c2a08 100644
--- a/crates/sui-framework/docs/sui-framework/deny_list.md
+++ b/crates/sui-framework/docs/sui-framework/deny_list.md
@@ -366,7 +366,7 @@ meaningless to add them to the deny list.
 ) {
     let per_type_config = deny_list.per_type_config_entry!(per_type_index, per_type_key, ctx);
     let setting_name = AddressKey(addr);
-    let next_epoch_entry = per_type_config.entry!<_,AddressKey, bool>(
+    let next_epoch_entry = per_type_config.entry!<_, AddressKey, bool>(
         &mut ConfigWriteCap(),
         setting_name,
         |_deny_list, _cap, _ctx| true,
@@ -638,9 +638,8 @@ meaningless to add them to the deny list.
     ctx: &mut TxContext,
 ) {
     let bag_entry: &mut PerTypeList = &mut deny_list.lists[per_type_index];
-    let elements =
-        if (!bag_entry.denied_addresses.contains(per_type_key)) vector[]
-        else bag_entry.denied_addresses.remove(per_type_key).into_keys();
+    let elements = if (!bag_entry.denied_addresses.contains(per_type_key)) vector[]
+    else bag_entry.denied_addresses.remove(per_type_key).into_keys();
     elements.do_ref!(|addr| {
         let addr = *addr;
         let denied_count = &mut bag_entry.denied_count[addr];
@@ -650,9 +649,9 @@ meaningless to add them to the deny list.
         }
     });
     let per_type_config = deny_list.per_type_config_entry!(per_type_index, per_type_key, ctx);
-    elements.do!(|addr|  {
+    elements.do!(|addr| {
         let setting_name = AddressKey(addr);
-        let next_epoch_entry = per_type_config.entry!<_,AddressKey, bool>(
+        let next_epoch_entry = per_type_config.entry!<_, AddressKey, bool>(
             &mut ConfigWriteCap(),
             setting_name,
             |_deny_list, _cap, _ctx| true,
@@ -773,11 +772,7 @@ meaningless to add them to the deny list.
 Implementation
 
 
-
fun per_type_exists(
-    deny_list: &DenyList,
-    per_type_index: u64,
-    per_type_key: vector<u8>,
-): bool {
+
fun per_type_exists(deny_list: &DenyList, per_type_index: u64, per_type_key: vector<u8>): bool {
     let key = ConfigKey { per_type_index, per_type_key };
     ofield::exists_(&deny_list.id, key)
 }
@@ -838,11 +833,7 @@ the type specified is the type of the coin, not the coin type itself. For exampl
 Implementation
 
 
-
fun v1_per_type_list_add(
-    list: &mut PerTypeList,
-    `type`: vector<u8>,
-    addr: address,
-) {
+
fun v1_per_type_list_add(list: &mut PerTypeList, `type`: vector<u8>, addr: address) {
     if (!list.denied_addresses.contains(`type`)) {
         list.denied_addresses.add(`type`, vec_set::empty());
     };
@@ -912,11 +903,7 @@ Aborts with v1_per_type_list_remove(
-    list: &mut PerTypeList,
-    `type`: vector<u8>,
-    addr: address,
-) {
+
fun v1_per_type_list_remove(list: &mut PerTypeList, `type`: vector<u8>, addr: address) {
     let denied_addresses = &mut list.denied_addresses[`type`];
     assert!(denied_addresses.contains(&addr), ENotDenied);
     denied_addresses.remove(&addr);
@@ -980,11 +967,7 @@ Returns true iff the given address is denied for the given type.
 Implementation
 
 
-
fun v1_per_type_list_contains(
-    list: &PerTypeList,
-    `type`: vector<u8>,
-    addr: address,
-): bool {
+
fun v1_per_type_list_contains(list: &PerTypeList, `type`: vector<u8>, addr: address): bool {
     if (!list.denied_count.contains(addr)) return false;
 
     let denied_count = &list.denied_count[addr];
diff --git a/crates/sui-framework/docs/sui-framework/sui.md b/crates/sui-framework/docs/sui-framework/sui.md
index d32f25fc6a385..0c87894c47b07 100644
--- a/crates/sui-framework/docs/sui-framework/sui.md
+++ b/crates/sui-framework/docs/sui-framework/sui.md
@@ -134,7 +134,7 @@ This should be called only once during genesis creation.
         // TODO: add appropriate description and logo url
         b"",
         option::none(),
-        ctx
+        ctx,
     );
     transfer::public_freeze_object(metadata);
     let mut supply = treasury.treasury_into_supply();
diff --git a/crates/sui-framework/docs/sui-framework/token.md b/crates/sui-framework/docs/sui-framework/token.md
index 03426586ec21c..b1a1f0c96b20f 100644
--- a/crates/sui-framework/docs/sui-framework/token.md
+++ b/crates/sui-framework/docs/sui-framework/token.md
@@ -496,17 +496,18 @@ hence it is safe to use it for authorization.
 
 
 
public fun new_policy<T>(
-    _treasury_cap: &TreasuryCap<T>, ctx: &mut TxContext
+    _treasury_cap: &TreasuryCap<T>,
+    ctx: &mut TxContext,
 ): (TokenPolicy<T>, TokenPolicyCap<T>) {
     let policy = TokenPolicy {
         id: object::new(ctx),
         spent_balance: balance::zero(),
-        rules: vec_map::empty()
+        rules: vec_map::empty(),
     };
 
     let cap = TokenPolicyCap {
         id: object::new(ctx),
-        `for`: object::id(&policy)
+        `for`: object::id(&policy),
     };
 
     (policy, cap)
@@ -566,9 +567,7 @@ to be used in verification.
 Implementation
 
 
-
public fun transfer<T>(
-    t: Token<T>, recipient: address, ctx: &mut TxContext
-): ActionRequest<T> {
+
public fun transfer<T>(t: Token<T>, recipient: address, ctx: &mut TxContext): ActionRequest<T> {
     let amount = t.balance.value();
     transfer::transfer(t, recipient);
 
@@ -577,7 +576,7 @@ to be used in verification.
         amount,
         option::some(recipient),
         option::none(),
-        ctx
+        ctx,
     )
 }
 
@@ -616,7 +615,7 @@ request and join the spent balance with the balance.value(), option::none(), option::some(balance), - ctx + ctx, ) }
@@ -642,9 +641,7 @@ Convert Token into an open < Implementation -
public fun to_coin<T>(
-    t: Token<T>, ctx: &mut TxContext
-): (Coin<T>, ActionRequest<T>) {
+
public fun to_coin<T>(t: Token<T>, ctx: &mut TxContext): (Coin<T>, ActionRequest<T>) {
     let Token { id, balance } = t;
     let amount = balance.value();
     id.delete();
@@ -656,8 +653,8 @@ Convert Token into an open <
             amount,
             option::none(),
             option::none(),
-            ctx
-        )
+            ctx,
+        ),
     )
 }
 
@@ -683,13 +680,11 @@ the "from_coin" action. Implementation -
public fun from_coin<T>(
-    coin: Coin<T>, ctx: &mut TxContext
-): (Token<T>, ActionRequest<T>) {
+
public fun from_coin<T>(coin: Coin<T>, ctx: &mut TxContext): (Token<T>, ActionRequest<T>) {
     let amount = coin.value();
     let token = Token {
         id: object::new(ctx),
-        balance: coin.into_balance()
+        balance: coin.into_balance(),
     };
 
     (
@@ -699,8 +694,8 @@ the "from_coin" action.
             amount,
             option::none(),
             option::none(),
-            ctx
-        )
+            ctx,
+        ),
     )
 }
 
@@ -753,9 +748,7 @@ Aborts if the Token.split<T>( - token: &mut Token<T>, amount: u64, ctx: &mut TxContext -): Token<T> { +
public fun split<T>(token: &mut Token<T>, amount: u64, ctx: &mut TxContext): Token<T> {
     assert!(token.balance.value() >= amount, EBalanceTooLow);
     Token {
         id: object::new(ctx),
@@ -872,7 +865,7 @@ Publicly available method to allow for custom actions.
     amount: u64,
     recipient: Option<address>,
     spent_balance: Option<Balance<T>>,
-    ctx: &TxContext
+    ctx: &TxContext,
 ): ActionRequest<T> {
     ActionRequest {
         name,
@@ -917,15 +910,18 @@ Aborts if:
 
public fun confirm_request<T>(
     policy: &TokenPolicy<T>,
     request: ActionRequest<T>,
-    _ctx: &mut TxContext
+    _ctx: &mut TxContext,
 ): (String, u64, address, Option<address>) {
     assert!(request.spent_balance.is_none(), ECantConsumeBalance);
     assert!(policy.rules.contains(&request.name), EUnknownAction);
 
     let ActionRequest {
-        name, approvals,
+        name,
+        approvals,
         spent_balance,
-        amount, sender, recipient,
+        amount,
+        sender,
+        recipient,
     } = request;
 
     spent_balance.destroy_none();
@@ -974,7 +970,7 @@ See confirm_request for the list of abort conditions.
 
public fun confirm_request_mut<T>(
     policy: &mut TokenPolicy<T>,
     mut request: ActionRequest<T>,
-    ctx: &mut TxContext
+    ctx: &mut TxContext,
 ): (String, u64, address, Option<address>) {
     assert!(policy.rules.contains(&request.name), EUnknownAction);
     assert!(request.spent_balance.is_some(), EUseImmutableConfirm);
@@ -1014,12 +1010,17 @@ Aborts if request contains spent_balance due to inability of the
 
public fun confirm_with_policy_cap<T>(
     _policy_cap: &TokenPolicyCap<T>,
     request: ActionRequest<T>,
-    _ctx: &mut TxContext
+    _ctx: &mut TxContext,
 ): (String, u64, address, Option<address>) {
     assert!(request.spent_balance.is_none(), ECantConsumeBalance);
 
     let ActionRequest {
-        name, amount, sender, recipient, approvals: _, spent_balance
+        name,
+        amount,
+        sender,
+        recipient,
+        approvals: _,
+        spent_balance,
     } = request;
 
     spent_balance.destroy_none();
@@ -1056,11 +1057,15 @@ to be consumed, decreasing the total_supply of the confirm_with_treasury_cap<T>(
     treasury_cap: &mut TreasuryCap<T>,
     request: ActionRequest<T>,
-    _ctx: &mut TxContext
+    _ctx: &mut TxContext,
 ): (String, u64, address, Option<address>) {
     let ActionRequest {
-        name, amount, sender, recipient, approvals: _,
-        spent_balance
+        name,
+        amount,
+        sender,
+        recipient,
+        approvals: _,
+        spent_balance,
     } = request;
 
     if (spent_balance.is_some()) {
@@ -1096,9 +1101,7 @@ required by the TokenPolicyImplementation
 
 
-
public fun add_approval<T, W: drop>(
-    _t: W, request: &mut ActionRequest<T>, _ctx: &mut TxContext
-) {
+
public fun add_approval<T, W: drop>(_t: W, request: &mut ActionRequest<T>, _ctx: &mut TxContext) {
     request.approvals.insert(type_name::get<W>())
 }
 
@@ -1135,7 +1138,7 @@ the TokenPolicy owner. self: &mut TokenPolicy<T>, cap: &TokenPolicyCap<T>, config: Config, - _ctx: &mut TxContext + _ctx: &mut TxContext, ) { assert!(object::id(self) == cap.`for`, ENotAuthorized); df::add(&mut self.id, key<Rule>(), config) @@ -1168,9 +1171,7 @@ Aborts if the Config is not present. Implementation -
public fun rule_config<T, Rule: drop, Config: store>(
-    _rule: Rule, self: &TokenPolicy<T>
-): &Config {
+
public fun rule_config<T, Rule: drop, Config: store>(_rule: Rule, self: &TokenPolicy<T>): &Config {
     assert!(has_rule_config_with_type<T, Rule, Config>(self), ENoConfig);
     df::borrow(&self.id, key<Rule>())
 }
@@ -1204,7 +1205,9 @@ Aborts if:
 
 
 
public fun rule_config_mut<T, Rule: drop, Config: store>(
-    _rule: Rule, self: &mut TokenPolicy<T>, cap: &TokenPolicyCap<T>
+    _rule: Rule,
+    self: &mut TokenPolicy<T>,
+    cap: &TokenPolicyCap<T>,
 ): &mut Config {
     assert!(has_rule_config_with_type<T, Rule, Config>(self), ENoConfig);
     assert!(object::id(self) == cap.`for`, ENotAuthorized);
@@ -1244,7 +1247,7 @@ Aborts if:
 
public fun remove_rule_config<T, Rule, Config: store>(
     self: &mut TokenPolicy<T>,
     cap: &TokenPolicyCap<T>,
-    _ctx: &mut TxContext
+    _ctx: &mut TxContext,
 ): Config {
     assert!(has_rule_config_with_type<T, Rule, Config>(self), ENoConfig);
     assert!(object::id(self) == cap.`for`, ENotAuthorized);
@@ -1299,9 +1302,7 @@ it matches the type provided.
 Implementation
 
 
-
public fun has_rule_config_with_type<T, Rule, Config: store>(
-    self: &TokenPolicy<T>
-): bool {
+
public fun has_rule_config_with_type<T, Rule, Config: store>(self: &TokenPolicy<T>): bool {
     df::exists_with_type<RuleKey<Rule>, Config>(&self.id, key<Rule>())
 }
 
@@ -1333,7 +1334,7 @@ Aborts if the TokenPolicyCapmut TokenPolicy<T>, cap: &TokenPolicyCap<T>, action: String, - _ctx: &mut TxContext + _ctx: &mut TxContext, ) { assert!(object::id(self) == cap.`for`, ENotAuthorized); self.rules.insert(action, vec_set::empty()); @@ -1367,7 +1368,7 @@ Aborts if the TokenPolicyCapmut TokenPolicy<T>, cap: &TokenPolicyCap<T>, action: String, - _ctx: &mut TxContext + _ctx: &mut TxContext, ) { assert!(object::id(self) == cap.`for`, ENotAuthorized); self.rules.remove(&action); @@ -1400,7 +1401,7 @@ Aborts if the TokenPolicyCapmut TokenPolicy<T>, cap: &TokenPolicyCap<T>, action: String, - ctx: &mut TxContext + ctx: &mut TxContext, ) { assert!(object::id(self) == cap.`for`, ENotAuthorized); if (!self.rules.contains(&action)) { @@ -1438,7 +1439,7 @@ Aborts if the TokenPolicyCapmut TokenPolicy<T>, cap: &TokenPolicyCap<T>, action: String, - _ctx: &mut TxContext + _ctx: &mut TxContext, ) { assert!(object::id(self) == cap.`for`, ENotAuthorized); @@ -1466,9 +1467,7 @@ Mint a Token with a given Implementation -
public fun mint<T>(
-    cap: &mut TreasuryCap<T>, amount: u64, ctx: &mut TxContext
-): Token<T> {
+
public fun mint<T>(cap: &mut TreasuryCap<T>, amount: u64, ctx: &mut TxContext): Token<T> {
     let balance = cap.supply_mut().increase_supply(amount);
     Token { id: object::new(ctx), balance }
 }
@@ -1525,7 +1524,7 @@ action is only available to the TreasuryCap owner.
 
public fun flush<T>(
     self: &mut TokenPolicy<T>,
     cap: &mut TreasuryCap<T>,
-    _ctx: &mut TxContext
+    _ctx: &mut TxContext,
 ): u64 {
     let amount = self.spent_balance.value();
     let balance = self.spent_balance.split(amount);
@@ -1578,9 +1577,7 @@ Returns the rules required for a specific action.
 Implementation
 
 
-
public fun rules<T>(
-    self: &TokenPolicy<T>, action: &String
-): VecSet<TypeName> {
+
public fun rules<T>(self: &TokenPolicy<T>, action: &String): VecSet<TypeName> {
     *self.rules.get(action)
 }
 
diff --git a/crates/sui-framework/docs/sui-framework/transfer.md b/crates/sui-framework/docs/sui-framework/transfer.md index ad5eaeae4fd73..95359ab5f8057 100644 --- a/crates/sui-framework/docs/sui-framework/transfer.md +++ b/crates/sui-framework/docs/sui-framework/transfer.md @@ -321,10 +321,7 @@ that T is an object defined in the module where receivepublic fun receive<T: key>(parent: &mut UID, to_receive: Receiving<T>): T { - let Receiving { - id, - version, - } = to_receive; + let Receiving { id, version } = to_receive; receive_impl(parent.to_address(), id, version) }
@@ -353,10 +350,7 @@ The object must have store to be received outside of its defining m
public fun public_receive<T: key + store>(parent: &mut UID, to_receive: Receiving<T>): T {
-    let Receiving {
-        id,
-        version,
-    } = to_receive;
+    let Receiving { id, version } = to_receive;
     receive_impl(parent.to_address(), id, version)
 }
 
diff --git a/crates/sui-framework/packages/sui-framework/sources/authenticator_state.move b/crates/sui-framework/packages/sui-framework/sources/authenticator_state.move index fb39f865ac071..30f9f5402d0eb 100644 --- a/crates/sui-framework/packages/sui-framework/sources/authenticator_state.move +++ b/crates/sui-framework/packages/sui-framework/sources/authenticator_state.move @@ -80,9 +80,9 @@ fun active_jwk_equal(a: &ActiveJwk, b: &ActiveJwk): bool { fun jwk_equal(a: &JWK, b: &JWK): bool { (&a.kty == &b.kty) && - (&a.e == &b.e) && - (&a.n == &b.n) && - (&a.alg == &b.alg) + (&a.e == &b.e) && + (&a.n == &b.n) && + (&a.alg == &b.alg) } fun jwk_id_equal(a: &JwkId, b: &JwkId): bool { diff --git a/crates/sui-framework/packages/sui-framework/sources/balance.move b/crates/sui-framework/packages/sui-framework/sources/balance.move index 90e44002db85c..c69f56bee0762 100644 --- a/crates/sui-framework/packages/sui-framework/sources/balance.move +++ b/crates/sui-framework/packages/sui-framework/sources/balance.move @@ -4,177 +4,139 @@ /// A storable handler for Balances in general. Is used in the `Coin` /// module to allow balance operations and can be used to implement /// custom coins with `Supply` and `Balance`s. -module sui::balance { - - /// Allows calling `.into_coin()` on a `Balance` to turn it into a coin. - public use fun sui::coin::from_balance as Balance.into_coin; - - /// For when trying to destroy a non-zero balance. - const ENonZero: u64 = 0; - /// For when an overflow is happening on Supply operations. - const EOverflow: u64 = 1; - /// For when trying to withdraw more than there is. - const ENotEnough: u64 = 2; - /// Sender is not @0x0 the system address. - const ENotSystemAddress: u64 = 3; - /// System operation performed for a coin other than SUI - const ENotSUI: u64 = 4; - - /// A Supply of T. Used for minting and burning. - /// Wrapped into a `TreasuryCap` in the `Coin` module. - public struct Supply has store { - value: u64 - } - - /// Storable balance - an inner struct of a Coin type. - /// Can be used to store coins which don't need the key ability. - public struct Balance has store { - value: u64 - } - - /// Get the amount stored in a `Balance`. - public fun value(self: &Balance): u64 { - self.value - } - - /// Get the `Supply` value. - public fun supply_value(supply: &Supply): u64 { - supply.value - } - - /// Create a new supply for type T. - public fun create_supply(_: T): Supply { - Supply { value: 0 } - } - - /// Increase supply by `value` and create a new `Balance` with this value. - public fun increase_supply(self: &mut Supply, value: u64): Balance { - assert!(value < (18446744073709551615u64 - self.value), EOverflow); - self.value = self.value + value; - Balance { value } - } - - /// Burn a Balance and decrease Supply. - public fun decrease_supply(self: &mut Supply, balance: Balance): u64 { - let Balance { value } = balance; - assert!(self.value >= value, EOverflow); - self.value = self.value - value; - value - } - - /// Create a zero `Balance` for type `T`. - public fun zero(): Balance { - Balance { value: 0 } - } - - /// Join two balances together. - public fun join(self: &mut Balance, balance: Balance): u64 { - let Balance { value } = balance; - self.value = self.value + value; - self.value - } - - /// Split a `Balance` and take a sub balance from it. - public fun split(self: &mut Balance, value: u64): Balance { - assert!(self.value >= value, ENotEnough); - self.value = self.value - value; - Balance { value } - } - - /// Withdraw all balance. After this the remaining balance must be 0. - public fun withdraw_all(self: &mut Balance): Balance { - let value = self.value; - split(self, value) - } - - /// Destroy a zero `Balance`. - public fun destroy_zero(balance: Balance) { - assert!(balance.value == 0, ENonZero); - let Balance { value: _ } = balance; - } - - const SUI_TYPE_NAME: vector = - b"0000000000000000000000000000000000000000000000000000000000000002::sui::SUI"; - - #[allow(unused_function)] - /// CAUTION: this function creates a `Balance` without increasing the supply. - /// It should only be called by the epoch change system txn to create staking rewards, - /// and nowhere else. - fun create_staking_rewards(value: u64, ctx: &TxContext): Balance { - assert!(ctx.sender() == @0x0, ENotSystemAddress); - assert!( - std::type_name::get().into_string().into_bytes() == SUI_TYPE_NAME, - ENotSUI, - ); - Balance { value } - } - - #[allow(unused_function)] - /// CAUTION: this function destroys a `Balance` without decreasing the supply. - /// It should only be called by the epoch change system txn to destroy storage rebates, - /// and nowhere else. - fun destroy_storage_rebates(self: Balance, ctx: &TxContext) { - assert!(ctx.sender() == @0x0, ENotSystemAddress); - assert!( - std::type_name::get().into_string().into_bytes() == SUI_TYPE_NAME, - ENotSUI, - ); - let Balance { value: _ } = self; - } - - /// Destroy a `Supply` preventing any further minting and burning. - public(package) fun destroy_supply(self: Supply): u64 { - let Supply { value } = self; - value - } - - #[test_only] - /// Create a `Balance` of any coin for testing purposes. - public fun create_for_testing(value: u64): Balance { - Balance { value } - } - - #[test_only] - /// Destroy a `Balance` of any coin for testing purposes. - public fun destroy_for_testing(self: Balance): u64 { - let Balance { value } = self; - value - } - - #[test_only] - /// Create a `Supply` of any coin for testing purposes. - public fun create_supply_for_testing(): Supply { - Supply { value: 0 } - } +module sui::balance; + +/// Allows calling `.into_coin()` on a `Balance` to turn it into a coin. +public use fun sui::coin::from_balance as Balance.into_coin; + +/// For when trying to destroy a non-zero balance. +const ENonZero: u64 = 0; +/// For when an overflow is happening on Supply operations. +const EOverflow: u64 = 1; +/// For when trying to withdraw more than there is. +const ENotEnough: u64 = 2; +/// Sender is not @0x0 the system address. +const ENotSystemAddress: u64 = 3; +/// System operation performed for a coin other than SUI +const ENotSUI: u64 = 4; + +/// A Supply of T. Used for minting and burning. +/// Wrapped into a `TreasuryCap` in the `Coin` module. +public struct Supply has store { + value: u64, } -#[test_only] -module sui::balance_tests { - use sui::balance; - use sui::sui::SUI; - use sui::test_utils; +/// Storable balance - an inner struct of a Coin type. +/// Can be used to store coins which don't need the key ability. +public struct Balance has store { + value: u64, +} - #[test] - fun test_balance() { - let mut balance = balance::zero(); - let another = balance::create_for_testing(1000); +/// Get the amount stored in a `Balance`. +public fun value(self: &Balance): u64 { + self.value +} - balance.join(another); +/// Get the `Supply` value. +public fun supply_value(supply: &Supply): u64 { + supply.value +} - assert!(balance.value() == 1000); +/// Create a new supply for type T. +public fun create_supply(_: T): Supply { + Supply { value: 0 } +} - let balance1 = balance.split(333); - let balance2 = balance.split(333); - let balance3 = balance.split(334); +/// Increase supply by `value` and create a new `Balance` with this value. +public fun increase_supply(self: &mut Supply, value: u64): Balance { + assert!(value < (18446744073709551615u64 - self.value), EOverflow); + self.value = self.value + value; + Balance { value } +} - balance.destroy_zero(); +/// Burn a Balance and decrease Supply. +public fun decrease_supply(self: &mut Supply, balance: Balance): u64 { + let Balance { value } = balance; + assert!(self.value >= value, EOverflow); + self.value = self.value - value; + value +} - assert!(balance1.value() == 333); - assert!(balance2.value() == 333); - assert!(balance3.value() == 334); +/// Create a zero `Balance` for type `T`. +public fun zero(): Balance { + Balance { value: 0 } +} - test_utils::destroy(balance1); - test_utils::destroy(balance2); - test_utils::destroy(balance3); - } +/// Join two balances together. +public fun join(self: &mut Balance, balance: Balance): u64 { + let Balance { value } = balance; + self.value = self.value + value; + self.value +} + +/// Split a `Balance` and take a sub balance from it. +public fun split(self: &mut Balance, value: u64): Balance { + assert!(self.value >= value, ENotEnough); + self.value = self.value - value; + Balance { value } +} + +/// Withdraw all balance. After this the remaining balance must be 0. +public fun withdraw_all(self: &mut Balance): Balance { + let value = self.value; + split(self, value) +} + +/// Destroy a zero `Balance`. +public fun destroy_zero(balance: Balance) { + assert!(balance.value == 0, ENonZero); + let Balance { value: _ } = balance; +} + +const SUI_TYPE_NAME: vector = + b"0000000000000000000000000000000000000000000000000000000000000002::sui::SUI"; + +#[allow(unused_function)] +/// CAUTION: this function creates a `Balance` without increasing the supply. +/// It should only be called by the epoch change system txn to create staking rewards, +/// and nowhere else. +fun create_staking_rewards(value: u64, ctx: &TxContext): Balance { + assert!(ctx.sender() == @0x0, ENotSystemAddress); + assert!(std::type_name::get().into_string().into_bytes() == SUI_TYPE_NAME, ENotSUI); + Balance { value } +} + +#[allow(unused_function)] +/// CAUTION: this function destroys a `Balance` without decreasing the supply. +/// It should only be called by the epoch change system txn to destroy storage rebates, +/// and nowhere else. +fun destroy_storage_rebates(self: Balance, ctx: &TxContext) { + assert!(ctx.sender() == @0x0, ENotSystemAddress); + assert!(std::type_name::get().into_string().into_bytes() == SUI_TYPE_NAME, ENotSUI); + let Balance { value: _ } = self; +} + +/// Destroy a `Supply` preventing any further minting and burning. +public(package) fun destroy_supply(self: Supply): u64 { + let Supply { value } = self; + value +} + +#[test_only] +/// Create a `Balance` of any coin for testing purposes. +public fun create_for_testing(value: u64): Balance { + Balance { value } +} + +#[test_only] +/// Destroy a `Balance` of any coin for testing purposes. +public fun destroy_for_testing(self: Balance): u64 { + let Balance { value } = self; + value +} + +#[test_only] +/// Create a `Supply` of any coin for testing purposes. +public fun create_supply_for_testing(): Supply { + Supply { value: 0 } } diff --git a/crates/sui-framework/packages/sui-framework/sources/coin.move b/crates/sui-framework/packages/sui-framework/sources/coin.move index 66de87af13c24..92dfc3d59832a 100644 --- a/crates/sui-framework/packages/sui-framework/sources/coin.move +++ b/crates/sui-framework/packages/sui-framework/sources/coin.move @@ -4,613 +4,609 @@ /// Defines the `Coin` type - platform wide representation of fungible /// tokens and coins. `Coin` can be described as a secure wrapper around /// `Balance` type. -module sui::coin { - use std::string; - use std::ascii; - use sui::balance::{Self, Balance, Supply}; - use sui::url::{Self, Url}; - use sui::deny_list::DenyList; - use std::type_name; - - // Allows calling `.split_vec(amounts, ctx)` on `coin` - public use fun sui::pay::split_vec as Coin.split_vec; - - // Allows calling `.join_vec(coins)` on `coin` - public use fun sui::pay::join_vec as Coin.join_vec; - - // Allows calling `.split_and_transfer(amount, recipient, ctx)` on `coin` - public use fun sui::pay::split_and_transfer as Coin.split_and_transfer; - - // Allows calling `.divide_and_keep(n, ctx)` on `coin` - public use fun sui::pay::divide_and_keep as Coin.divide_and_keep; - - /// A type passed to create_supply is not a one-time witness. - const EBadWitness: u64 = 0; - /// Invalid arguments are passed to a function. - const EInvalidArg: u64 = 1; - /// Trying to split a coin more times than its balance allows. - const ENotEnough: u64 = 2; - // #[error] - // const EGlobalPauseNotAllowed: vector = - // b"Kill switch was not allowed at the creation of the DenyCapV2"; - const EGlobalPauseNotAllowed: u64 = 3; - - /// A coin of type `T` worth `value`. Transferable and storable - public struct Coin has key, store { - id: UID, - balance: Balance - } - - /// Each Coin type T created through `create_currency` function will have a - /// unique instance of CoinMetadata that stores the metadata for this coin type. - public struct CoinMetadata has key, store { - id: UID, - /// Number of decimal places the coin uses. - /// A coin with `value ` N and `decimals` D should be shown as N / 10^D - /// E.g., a coin with `value` 7002 and decimals 3 should be displayed as 7.002 - /// This is metadata for display usage only. - decimals: u8, - /// Name for the token - name: string::String, - /// Symbol for the token - symbol: ascii::String, - /// Description of the token - description: string::String, - /// URL for the token logo - icon_url: Option - } +module sui::coin; + +use std::ascii; +use std::string; +use std::type_name; +use sui::balance::{Self, Balance, Supply}; +use sui::deny_list::DenyList; +use sui::url::{Self, Url}; + +// Allows calling `.split_vec(amounts, ctx)` on `coin` +public use fun sui::pay::split_vec as Coin.split_vec; + +// Allows calling `.join_vec(coins)` on `coin` +public use fun sui::pay::join_vec as Coin.join_vec; + +// Allows calling `.split_and_transfer(amount, recipient, ctx)` on `coin` +public use fun sui::pay::split_and_transfer as Coin.split_and_transfer; + +// Allows calling `.divide_and_keep(n, ctx)` on `coin` +public use fun sui::pay::divide_and_keep as Coin.divide_and_keep; + +/// A type passed to create_supply is not a one-time witness. +const EBadWitness: u64 = 0; +/// Invalid arguments are passed to a function. +const EInvalidArg: u64 = 1; +/// Trying to split a coin more times than its balance allows. +const ENotEnough: u64 = 2; +// #[error] +// const EGlobalPauseNotAllowed: vector = +// b"Kill switch was not allowed at the creation of the DenyCapV2"; +const EGlobalPauseNotAllowed: u64 = 3; + +/// A coin of type `T` worth `value`. Transferable and storable +public struct Coin has key, store { + id: UID, + balance: Balance, +} - /// Similar to CoinMetadata, but created only for regulated coins that use the DenyList. - /// This object is always immutable. - public struct RegulatedCoinMetadata has key { - id: UID, - /// The ID of the coin's CoinMetadata object. - coin_metadata_object: ID, - /// The ID of the coin's DenyCap object. - deny_cap_object: ID, - } +/// Each Coin type T created through `create_currency` function will have a +/// unique instance of CoinMetadata that stores the metadata for this coin type. +public struct CoinMetadata has key, store { + id: UID, + /// Number of decimal places the coin uses. + /// A coin with `value ` N and `decimals` D should be shown as N / 10^D + /// E.g., a coin with `value` 7002 and decimals 3 should be displayed as 7.002 + /// This is metadata for display usage only. + decimals: u8, + /// Name for the token + name: string::String, + /// Symbol for the token + symbol: ascii::String, + /// Description of the token + description: string::String, + /// URL for the token logo + icon_url: Option, +} - /// Capability allowing the bearer to mint and burn - /// coins of type `T`. Transferable - public struct TreasuryCap has key, store { - id: UID, - total_supply: Supply - } +/// Similar to CoinMetadata, but created only for regulated coins that use the DenyList. +/// This object is always immutable. +public struct RegulatedCoinMetadata has key { + id: UID, + /// The ID of the coin's CoinMetadata object. + coin_metadata_object: ID, + /// The ID of the coin's DenyCap object. + deny_cap_object: ID, +} - /// Capability allowing the bearer to deny addresses from using the currency's coins-- - /// immediately preventing those addresses from interacting with the coin as an input to a - /// transaction and at the start of the next preventing them from receiving the coin. - /// If `allow_global_pause` is true, the bearer can enable a global pause that behaves as if - /// all addresses were added to the deny list. - public struct DenyCapV2 has key, store { - id: UID, - allow_global_pause: bool, - } +/// Capability allowing the bearer to mint and burn +/// coins of type `T`. Transferable +public struct TreasuryCap has key, store { + id: UID, + total_supply: Supply, +} - // === Supply <-> TreasuryCap morphing and accessors === +/// Capability allowing the bearer to deny addresses from using the currency's coins-- +/// immediately preventing those addresses from interacting with the coin as an input to a +/// transaction and at the start of the next preventing them from receiving the coin. +/// If `allow_global_pause` is true, the bearer can enable a global pause that behaves as if +/// all addresses were added to the deny list. +public struct DenyCapV2 has key, store { + id: UID, + allow_global_pause: bool, +} - /// Return the total number of `T`'s in circulation. - public fun total_supply(cap: &TreasuryCap): u64 { - balance::supply_value(&cap.total_supply) - } +// === Supply <-> TreasuryCap morphing and accessors === - /// Unwrap `TreasuryCap` getting the `Supply`. - /// - /// Operation is irreversible. Supply cannot be converted into a `TreasuryCap` due - /// to different security guarantees (TreasuryCap can be created only once for a type) - public fun treasury_into_supply(treasury: TreasuryCap): Supply { - let TreasuryCap { id, total_supply } = treasury; - id.delete(); - total_supply - } +/// Return the total number of `T`'s in circulation. +public fun total_supply(cap: &TreasuryCap): u64 { + balance::supply_value(&cap.total_supply) +} - /// Get immutable reference to the treasury's `Supply`. - public fun supply_immut(treasury: &TreasuryCap): &Supply { - &treasury.total_supply - } +/// Unwrap `TreasuryCap` getting the `Supply`. +/// +/// Operation is irreversible. Supply cannot be converted into a `TreasuryCap` due +/// to different security guarantees (TreasuryCap can be created only once for a type) +public fun treasury_into_supply(treasury: TreasuryCap): Supply { + let TreasuryCap { id, total_supply } = treasury; + id.delete(); + total_supply +} - /// Get mutable reference to the treasury's `Supply`. - public fun supply_mut(treasury: &mut TreasuryCap): &mut Supply { - &mut treasury.total_supply - } +/// Get immutable reference to the treasury's `Supply`. +public fun supply_immut(treasury: &TreasuryCap): &Supply { + &treasury.total_supply +} - // === Balance <-> Coin accessors and type morphing === +/// Get mutable reference to the treasury's `Supply`. +public fun supply_mut(treasury: &mut TreasuryCap): &mut Supply { + &mut treasury.total_supply +} - /// Public getter for the coin's value - public fun value(self: &Coin): u64 { - self.balance.value() - } +// === Balance <-> Coin accessors and type morphing === - /// Get immutable reference to the balance of a coin. - public fun balance(coin: &Coin): &Balance { - &coin.balance - } +/// Public getter for the coin's value +public fun value(self: &Coin): u64 { + self.balance.value() +} - /// Get a mutable reference to the balance of a coin. - public fun balance_mut(coin: &mut Coin): &mut Balance { - &mut coin.balance - } +/// Get immutable reference to the balance of a coin. +public fun balance(coin: &Coin): &Balance { + &coin.balance +} - /// Wrap a balance into a Coin to make it transferable. - public fun from_balance(balance: Balance, ctx: &mut TxContext): Coin { - Coin { id: object::new(ctx), balance } - } +/// Get a mutable reference to the balance of a coin. +public fun balance_mut(coin: &mut Coin): &mut Balance { + &mut coin.balance +} - /// Destruct a Coin wrapper and keep the balance. - public fun into_balance(coin: Coin): Balance { - let Coin { id, balance } = coin; - id.delete(); - balance - } +/// Wrap a balance into a Coin to make it transferable. +public fun from_balance(balance: Balance, ctx: &mut TxContext): Coin { + Coin { id: object::new(ctx), balance } +} - /// Take a `Coin` worth of `value` from `Balance`. - /// Aborts if `value > balance.value` - public fun take( - balance: &mut Balance, value: u64, ctx: &mut TxContext, - ): Coin { - Coin { - id: object::new(ctx), - balance: balance.split(value) - } - } +/// Destruct a Coin wrapper and keep the balance. +public fun into_balance(coin: Coin): Balance { + let Coin { id, balance } = coin; + id.delete(); + balance +} - /// Put a `Coin` to the `Balance`. - public fun put(balance: &mut Balance, coin: Coin) { - balance.join(into_balance(coin)); +/// Take a `Coin` worth of `value` from `Balance`. +/// Aborts if `value > balance.value` +public fun take(balance: &mut Balance, value: u64, ctx: &mut TxContext): Coin { + Coin { + id: object::new(ctx), + balance: balance.split(value), } +} - // === Base Coin functionality === +/// Put a `Coin` to the `Balance`. +public fun put(balance: &mut Balance, coin: Coin) { + balance.join(into_balance(coin)); +} - /// Consume the coin `c` and add its value to `self`. - /// Aborts if `c.value + self.value > U64_MAX` - public entry fun join(self: &mut Coin, c: Coin) { - let Coin { id, balance } = c; - id.delete(); - self.balance.join(balance); - } +// === Base Coin functionality === - /// Split coin `self` to two coins, one with balance `split_amount`, - /// and the remaining balance is left is `self`. - public fun split( - self: &mut Coin, split_amount: u64, ctx: &mut TxContext - ): Coin { - take(&mut self.balance, split_amount, ctx) - } +/// Consume the coin `c` and add its value to `self`. +/// Aborts if `c.value + self.value > U64_MAX` +public entry fun join(self: &mut Coin, c: Coin) { + let Coin { id, balance } = c; + id.delete(); + self.balance.join(balance); +} - /// Split coin `self` into `n - 1` coins with equal balances. The remainder is left in - /// `self`. Return newly created coins. - public fun divide_into_n( - self: &mut Coin, n: u64, ctx: &mut TxContext - ): vector> { - assert!(n > 0, EInvalidArg); - assert!(n <= value(self), ENotEnough); - - let mut vec = vector[]; - let mut i = 0; - let split_amount = value(self) / n; - while (i < n - 1) { - vec.push_back(self.split(split_amount, ctx)); - i = i + 1; - }; - vec - } +/// Split coin `self` to two coins, one with balance `split_amount`, +/// and the remaining balance is left is `self`. +public fun split(self: &mut Coin, split_amount: u64, ctx: &mut TxContext): Coin { + take(&mut self.balance, split_amount, ctx) +} - /// Make any Coin with a zero value. Useful for placeholding - /// bids/payments or preemptively making empty balances. - public fun zero(ctx: &mut TxContext): Coin { - Coin { id: object::new(ctx), balance: balance::zero() } - } +/// Split coin `self` into `n - 1` coins with equal balances. The remainder is left in +/// `self`. Return newly created coins. +public fun divide_into_n(self: &mut Coin, n: u64, ctx: &mut TxContext): vector> { + assert!(n > 0, EInvalidArg); + assert!(n <= value(self), ENotEnough); + + let mut vec = vector[]; + let mut i = 0; + let split_amount = value(self) / n; + while (i < n - 1) { + vec.push_back(self.split(split_amount, ctx)); + i = i + 1; + }; + vec +} - /// Destroy a coin with value zero - public fun destroy_zero(c: Coin) { - let Coin { id, balance } = c; - id.delete(); - balance.destroy_zero() - } +/// Make any Coin with a zero value. Useful for placeholding +/// bids/payments or preemptively making empty balances. +public fun zero(ctx: &mut TxContext): Coin { + Coin { id: object::new(ctx), balance: balance::zero() } +} - // === Registering new coin types and managing the coin supply === - - /// Create a new currency type `T` as and return the `TreasuryCap` for - /// `T` to the caller. Can only be called with a `one-time-witness` - /// type, ensuring that there's only one `TreasuryCap` per `T`. - public 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), - total_supply: balance::create_supply(witness) - }, - CoinMetadata { - id: object::new(ctx), - decimals, - name: string::utf8(name), - symbol: ascii::string(symbol), - description: string::utf8(description), - icon_url - } - ) - } +/// Destroy a coin with value zero +public fun destroy_zero(c: Coin) { + let Coin { id, balance } = c; + id.delete(); + balance.destroy_zero() +} - /// This creates a new currency, via `create_currency`, but with an extra capability that - /// allows for specific addresses to have their coins frozen. When an address is added to the - /// deny list, it is immediately unable to interact with the currency's coin as input objects. - /// Additionally at the start of the next epoch, they will be unable to receive the currency's - /// coin. - /// The `allow_global_pause` flag enables an additional API that will cause all addresses to be - /// be denied. Note however, that this doesn't affect per-address entries of the deny list and - /// will not change the result of the "contains" APIs. - public fun create_regulated_currency_v2( - witness: T, - decimals: u8, - symbol: vector, - name: vector, - description: vector, - icon_url: Option, - allow_global_pause: bool, - ctx: &mut TxContext, - ): (TreasuryCap, DenyCapV2, CoinMetadata) { - let (treasury_cap, metadata) = create_currency( - witness, - decimals, - symbol, - name, - description, - icon_url, - ctx - ); - let deny_cap = DenyCapV2 { - id: object::new(ctx), - allow_global_pause, - }; - transfer::freeze_object(RegulatedCoinMetadata { +// === Registering new coin types and managing the coin supply === + +/// Create a new currency type `T` as and return the `TreasuryCap` for +/// `T` to the caller. Can only be called with a `one-time-witness` +/// type, ensuring that there's only one `TreasuryCap` per `T`. +public 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), - coin_metadata_object: object::id(&metadata), - deny_cap_object: object::id(&deny_cap), - }); - (treasury_cap, deny_cap, metadata) - } - - /// 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. - public fun migrate_regulated_currency_to_v2( - deny_list: &mut DenyList, - cap: DenyCap, - allow_global_pause: bool, - ctx: &mut TxContext, - ): DenyCapV2 { - let DenyCap { id } = cap; - object::delete(id); - let ty = type_name::get_with_original_ids().into_string().into_bytes(); - deny_list.migrate_v1_to_v2(DENY_LIST_COIN_INDEX, ty, ctx); - DenyCapV2 { + total_supply: balance::create_supply(witness), + }, + CoinMetadata { id: object::new(ctx), - allow_global_pause, - } - } + decimals, + name: string::utf8(name), + symbol: ascii::string(symbol), + description: string::utf8(description), + icon_url, + }, + ) +} - /// Create a coin worth `value` and increase the total supply - /// in `cap` accordingly. - public fun mint( - cap: &mut TreasuryCap, value: u64, ctx: &mut TxContext, - ): Coin { - Coin { - id: object::new(ctx), - balance: cap.total_supply.increase_supply(value) - } - } +/// This creates a new currency, via `create_currency`, but with an extra capability that +/// allows for specific addresses to have their coins frozen. When an address is added to the +/// deny list, it is immediately unable to interact with the currency's coin as input objects. +/// Additionally at the start of the next epoch, they will be unable to receive the currency's +/// coin. +/// The `allow_global_pause` flag enables an additional API that will cause all addresses to be +/// be denied. Note however, that this doesn't affect per-address entries of the deny list and +/// will not change the result of the "contains" APIs. +public fun create_regulated_currency_v2( + witness: T, + decimals: u8, + symbol: vector, + name: vector, + description: vector, + icon_url: Option, + allow_global_pause: bool, + ctx: &mut TxContext, +): (TreasuryCap, DenyCapV2, CoinMetadata) { + let (treasury_cap, metadata) = create_currency( + witness, + decimals, + symbol, + name, + description, + icon_url, + ctx, + ); + let deny_cap = DenyCapV2 { + id: object::new(ctx), + allow_global_pause, + }; + transfer::freeze_object(RegulatedCoinMetadata { + id: object::new(ctx), + coin_metadata_object: object::id(&metadata), + deny_cap_object: object::id(&deny_cap), + }); + (treasury_cap, deny_cap, metadata) +} - /// Mint some amount of T as a `Balance` and increase the total - /// supply in `cap` accordingly. - /// Aborts if `value` + `cap.total_supply` >= U64_MAX - public fun mint_balance( - cap: &mut TreasuryCap, value: u64 - ): Balance { - cap.total_supply.increase_supply(value) +/// 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. +public fun migrate_regulated_currency_to_v2( + deny_list: &mut DenyList, + cap: DenyCap, + allow_global_pause: bool, + ctx: &mut TxContext, +): DenyCapV2 { + let DenyCap { id } = cap; + object::delete(id); + let ty = type_name::get_with_original_ids().into_string().into_bytes(); + deny_list.migrate_v1_to_v2(DENY_LIST_COIN_INDEX, ty, ctx); + DenyCapV2 { + id: object::new(ctx), + allow_global_pause, } +} - /// Destroy the coin `c` and decrease the total supply in `cap` - /// accordingly. - public entry fun burn(cap: &mut TreasuryCap, c: Coin): u64 { - let Coin { id, balance } = c; - id.delete(); - cap.total_supply.decrease_supply(balance) +/// Create a coin worth `value` and increase the total supply +/// in `cap` accordingly. +public fun mint(cap: &mut TreasuryCap, value: u64, ctx: &mut TxContext): Coin { + Coin { + id: object::new(ctx), + balance: cap.total_supply.increase_supply(value), } +} - /// Adds the given address to the deny list, preventing it from interacting with the specified - /// coin type as an input to a transaction. Additionally at the start of the next epoch, the - /// address will be unable to receive objects of this coin type. - public fun deny_list_v2_add( - deny_list: &mut DenyList, - _deny_cap: &mut DenyCapV2, - addr: address, - ctx: &mut TxContext, - ) { - let ty = type_name::get_with_original_ids().into_string().into_bytes(); - deny_list.v2_add(DENY_LIST_COIN_INDEX, ty, addr, ctx) - } +/// Mint some amount of T as a `Balance` and increase the total +/// supply in `cap` accordingly. +/// Aborts if `value` + `cap.total_supply` >= U64_MAX +public fun mint_balance(cap: &mut TreasuryCap, value: u64): Balance { + cap.total_supply.increase_supply(value) +} - /// Removes an address from the deny list. Similar to `deny_list_v2_add`, the effect for input - /// objects will be immediate, but the effect for receiving objects will be delayed until the - /// next epoch. - public fun deny_list_v2_remove( - deny_list: &mut DenyList, - _deny_cap: &mut DenyCapV2, - addr: address, - ctx: &mut TxContext, - ) { - let ty = type_name::get_with_original_ids().into_string().into_bytes(); - deny_list.v2_remove(DENY_LIST_COIN_INDEX, ty, addr, ctx) - } +/// Destroy the coin `c` and decrease the total supply in `cap` +/// accordingly. +public entry fun burn(cap: &mut TreasuryCap, c: Coin): u64 { + let Coin { id, balance } = c; + id.delete(); + cap.total_supply.decrease_supply(balance) +} - /// Check if the deny list contains the given address for the current epoch. Denied addresses - /// in the current epoch will be unable to receive objects of this coin type. - public fun deny_list_v2_contains_current_epoch( - deny_list: &DenyList, - addr: address, - ctx: &TxContext, - ): bool { - let ty = type_name::get_with_original_ids().into_string().into_bytes(); - deny_list.v2_contains_current_epoch(DENY_LIST_COIN_INDEX, ty, addr, ctx) - } +/// Adds the given address to the deny list, preventing it from interacting with the specified +/// coin type as an input to a transaction. Additionally at the start of the next epoch, the +/// address will be unable to receive objects of this coin type. +public fun deny_list_v2_add( + deny_list: &mut DenyList, + _deny_cap: &mut DenyCapV2, + addr: address, + ctx: &mut TxContext, +) { + let ty = type_name::get_with_original_ids().into_string().into_bytes(); + deny_list.v2_add(DENY_LIST_COIN_INDEX, ty, addr, ctx) +} - /// Check if the deny list contains the given address for the next epoch. Denied addresses in - /// the next epoch will immediately be unable to use objects of this coin type as inputs. At the - /// start of the next epoch, the address will be unable to receive objects of this coin type. - public fun deny_list_v2_contains_next_epoch( - deny_list: &DenyList, - addr: address, - ): bool { - let ty = type_name::get_with_original_ids().into_string().into_bytes(); - deny_list.v2_contains_next_epoch(DENY_LIST_COIN_INDEX, ty, addr) - } +/// Removes an address from the deny list. Similar to `deny_list_v2_add`, the effect for input +/// objects will be immediate, but the effect for receiving objects will be delayed until the +/// next epoch. +public fun deny_list_v2_remove( + deny_list: &mut DenyList, + _deny_cap: &mut DenyCapV2, + addr: address, + ctx: &mut TxContext, +) { + let ty = type_name::get_with_original_ids().into_string().into_bytes(); + deny_list.v2_remove(DENY_LIST_COIN_INDEX, ty, addr, ctx) +} - /// Enable the global pause for the given coin type. This will immediately prevent all addresses - /// from using objects of this coin type as inputs. At the start of the next epoch, all - /// addresses will be unable to receive objects of this coin type. - #[allow(unused_mut_parameter)] - public fun deny_list_v2_enable_global_pause( - deny_list: &mut DenyList, - deny_cap: &mut DenyCapV2, - ctx: &mut TxContext, - ) { - assert!(deny_cap.allow_global_pause, EGlobalPauseNotAllowed); - let ty = type_name::get_with_original_ids().into_string().into_bytes(); - deny_list.v2_enable_global_pause(DENY_LIST_COIN_INDEX, ty, ctx) - } +/// Check if the deny list contains the given address for the current epoch. Denied addresses +/// in the current epoch will be unable to receive objects of this coin type. +public fun deny_list_v2_contains_current_epoch( + deny_list: &DenyList, + addr: address, + ctx: &TxContext, +): bool { + let ty = type_name::get_with_original_ids().into_string().into_bytes(); + deny_list.v2_contains_current_epoch(DENY_LIST_COIN_INDEX, ty, addr, ctx) +} - /// Disable the global pause for the given coin type. This will immediately allow all addresses - /// to resume using objects of this coin type as inputs. However, receiving objects of this coin - /// type will still be paused until the start of the next epoch. - #[allow(unused_mut_parameter)] - public fun deny_list_v2_disable_global_pause( - deny_list: &mut DenyList, - deny_cap: &mut DenyCapV2, - ctx: &mut TxContext, - ) { - assert!(deny_cap.allow_global_pause, EGlobalPauseNotAllowed); - let ty = type_name::get_with_original_ids().into_string().into_bytes(); - deny_list.v2_disable_global_pause(DENY_LIST_COIN_INDEX, ty, ctx) - } +/// Check if the deny list contains the given address for the next epoch. Denied addresses in +/// the next epoch will immediately be unable to use objects of this coin type as inputs. At the +/// start of the next epoch, the address will be unable to receive objects of this coin type. +public fun deny_list_v2_contains_next_epoch(deny_list: &DenyList, addr: address): bool { + let ty = type_name::get_with_original_ids().into_string().into_bytes(); + deny_list.v2_contains_next_epoch(DENY_LIST_COIN_INDEX, ty, addr) +} - /// Check if the global pause is enabled for the given coin type in the current epoch. - public fun deny_list_v2_is_global_pause_enabled_current_epoch( - deny_list: &DenyList, - ctx: &TxContext, - ): bool { - let ty = type_name::get_with_original_ids().into_string().into_bytes(); - deny_list.v2_is_global_pause_enabled_current_epoch(DENY_LIST_COIN_INDEX, ty, ctx) - } +/// Enable the global pause for the given coin type. This will immediately prevent all addresses +/// from using objects of this coin type as inputs. At the start of the next epoch, all +/// addresses will be unable to receive objects of this coin type. +#[allow(unused_mut_parameter)] +public fun deny_list_v2_enable_global_pause( + deny_list: &mut DenyList, + deny_cap: &mut DenyCapV2, + ctx: &mut TxContext, +) { + assert!(deny_cap.allow_global_pause, EGlobalPauseNotAllowed); + let ty = type_name::get_with_original_ids().into_string().into_bytes(); + deny_list.v2_enable_global_pause(DENY_LIST_COIN_INDEX, ty, ctx) +} - /// Check if the global pause is enabled for the given coin type in the next epoch. - public fun deny_list_v2_is_global_pause_enabled_next_epoch( - deny_list: &DenyList, - ): bool { - let ty = type_name::get_with_original_ids().into_string().into_bytes(); - deny_list.v2_is_global_pause_enabled_next_epoch(DENY_LIST_COIN_INDEX, ty) - } +/// Disable the global pause for the given coin type. This will immediately allow all addresses +/// to resume using objects of this coin type as inputs. However, receiving objects of this coin +/// type will still be paused until the start of the next epoch. +#[allow(unused_mut_parameter)] +public fun deny_list_v2_disable_global_pause( + deny_list: &mut DenyList, + deny_cap: &mut DenyCapV2, + ctx: &mut TxContext, +) { + assert!(deny_cap.allow_global_pause, EGlobalPauseNotAllowed); + let ty = type_name::get_with_original_ids().into_string().into_bytes(); + deny_list.v2_disable_global_pause(DENY_LIST_COIN_INDEX, ty, ctx) +} - // === Entrypoints === +/// Check if the global pause is enabled for the given coin type in the current epoch. +public fun deny_list_v2_is_global_pause_enabled_current_epoch( + deny_list: &DenyList, + ctx: &TxContext, +): bool { + let ty = type_name::get_with_original_ids().into_string().into_bytes(); + deny_list.v2_is_global_pause_enabled_current_epoch(DENY_LIST_COIN_INDEX, ty, ctx) +} - /// Mint `amount` of `Coin` and send it to `recipient`. Invokes `mint()`. - public entry fun mint_and_transfer( - c: &mut TreasuryCap, amount: u64, recipient: address, ctx: &mut TxContext - ) { - transfer::public_transfer(mint(c, amount, ctx), recipient) - } +/// Check if the global pause is enabled for the given coin type in the next epoch. +public fun deny_list_v2_is_global_pause_enabled_next_epoch(deny_list: &DenyList): bool { + let ty = type_name::get_with_original_ids().into_string().into_bytes(); + deny_list.v2_is_global_pause_enabled_next_epoch(DENY_LIST_COIN_INDEX, ty) +} - // === Update coin metadata === +// === Entrypoints === - /// Update name of the coin in `CoinMetadata` - public entry fun update_name( - _treasury: &TreasuryCap, metadata: &mut CoinMetadata, name: string::String - ) { - metadata.name = name; - } +/// Mint `amount` of `Coin` and send it to `recipient`. Invokes `mint()`. +public entry fun mint_and_transfer( + c: &mut TreasuryCap, + amount: u64, + recipient: address, + ctx: &mut TxContext, +) { + transfer::public_transfer(mint(c, amount, ctx), recipient) +} - /// Update the symbol of the coin in `CoinMetadata` - public entry fun update_symbol( - _treasury: &TreasuryCap, metadata: &mut CoinMetadata, symbol: ascii::String - ) { - metadata.symbol = symbol; - } +// === Update coin metadata === - /// Update the description of the coin in `CoinMetadata` - public entry fun update_description( - _treasury: &TreasuryCap, metadata: &mut CoinMetadata, description: string::String - ) { - metadata.description = description; - } +/// Update name of the coin in `CoinMetadata` +public entry fun update_name( + _treasury: &TreasuryCap, + metadata: &mut CoinMetadata, + name: string::String, +) { + metadata.name = name; +} - /// Update the url of the coin in `CoinMetadata` - public entry fun update_icon_url( - _treasury: &TreasuryCap, metadata: &mut CoinMetadata, url: ascii::String - ) { - metadata.icon_url = option::some(url::new_unsafe(url)); - } +/// Update the symbol of the coin in `CoinMetadata` +public entry fun update_symbol( + _treasury: &TreasuryCap, + metadata: &mut CoinMetadata, + symbol: ascii::String, +) { + metadata.symbol = symbol; +} - // === Get coin metadata fields for on-chain consumption === +/// Update the description of the coin in `CoinMetadata` +public entry fun update_description( + _treasury: &TreasuryCap, + metadata: &mut CoinMetadata, + description: string::String, +) { + metadata.description = description; +} - public fun get_decimals(metadata: &CoinMetadata): u8 { - metadata.decimals - } +/// Update the url of the coin in `CoinMetadata` +public entry fun update_icon_url( + _treasury: &TreasuryCap, + metadata: &mut CoinMetadata, + url: ascii::String, +) { + metadata.icon_url = option::some(url::new_unsafe(url)); +} - public fun get_name(metadata: &CoinMetadata): string::String { - metadata.name - } +// === Get coin metadata fields for on-chain consumption === - public fun get_symbol(metadata: &CoinMetadata): ascii::String { - metadata.symbol - } +public fun get_decimals(metadata: &CoinMetadata): u8 { + metadata.decimals +} - public fun get_description(metadata: &CoinMetadata): string::String { - metadata.description - } +public fun get_name(metadata: &CoinMetadata): string::String { + metadata.name +} - public fun get_icon_url(metadata: &CoinMetadata): Option { - metadata.icon_url - } +public fun get_symbol(metadata: &CoinMetadata): ascii::String { + metadata.symbol +} - // === Test-only code === +public fun get_description(metadata: &CoinMetadata): string::String { + metadata.description +} - #[test_only] - /// Mint coins of any type for (obviously!) testing purposes only - public fun mint_for_testing(value: u64, ctx: &mut TxContext): Coin { - Coin { id: object::new(ctx), balance: balance::create_for_testing(value) } - } +public fun get_icon_url(metadata: &CoinMetadata): Option { + metadata.icon_url +} - #[test_only] - /// Burn coins of any type for testing purposes only - public fun burn_for_testing(coin: Coin): u64 { - let Coin { id, balance } = coin; - id.delete(); - balance.destroy_for_testing() - } +// === Test-only code === - #[test_only] - /// Create a `TreasuryCap` for any `Coin` for testing purposes. - public fun create_treasury_cap_for_testing( - ctx: &mut TxContext - ): TreasuryCap { - TreasuryCap { - id: object::new(ctx), - total_supply: balance::create_supply_for_testing() - } - } +#[test_only] +/// Mint coins of any type for (obviously!) testing purposes only +public fun mint_for_testing(value: u64, ctx: &mut TxContext): Coin { + Coin { id: object::new(ctx), balance: balance::create_for_testing(value) } +} - // === Deprecated code === +#[test_only] +/// Burn coins of any type for testing purposes only +public fun burn_for_testing(coin: Coin): u64 { + let Coin { id, balance } = coin; + id.delete(); + balance.destroy_for_testing() +} - // oops, wanted treasury: &TreasuryCap - public fun supply(treasury: &mut TreasuryCap): &Supply { - &treasury.total_supply +#[test_only] +/// Create a `TreasuryCap` for any `Coin` for testing purposes. +public fun create_treasury_cap_for_testing(ctx: &mut TxContext): TreasuryCap { + TreasuryCap { + id: object::new(ctx), + total_supply: balance::create_supply_for_testing(), } +} - // deprecated as we have CoinMetadata now - #[allow(unused_field)] - public struct CurrencyCreated has copy, drop { - decimals: u8 - } +// === Deprecated code === - /// Capability allowing the bearer to freeze addresses, preventing those addresses from - /// interacting with the coin as an input to a transaction. - public struct DenyCap has key, store { - id: UID, - } +// oops, wanted treasury: &TreasuryCap +public fun supply(treasury: &mut TreasuryCap): &Supply { + &treasury.total_supply +} - /// This creates a new currency, via `create_currency`, but with an extra capability that - /// allows for specific addresses to have their coins frozen. Those addresses cannot interact - /// with the coin as input objects. - #[deprecated(note = b"For new coins, use `create_regulated_currency_v2`. To migrate existing regulated currencies, migrate with `migrate_regulated_currency_to_v2`")] - public fun create_regulated_currency( - witness: T, - decimals: u8, - symbol: vector, - name: vector, - description: vector, - icon_url: Option, - ctx: &mut TxContext - ): (TreasuryCap, DenyCap, CoinMetadata) { - let (treasury_cap, metadata) = create_currency( - witness, - decimals, - symbol, - name, - description, - icon_url, - ctx - ); - let deny_cap = DenyCap { - id: object::new(ctx), - }; - transfer::freeze_object(RegulatedCoinMetadata { - id: object::new(ctx), - coin_metadata_object: object::id(&metadata), - deny_cap_object: object::id(&deny_cap), - }); - (treasury_cap, deny_cap, metadata) - } +// deprecated as we have CoinMetadata now +#[allow(unused_field)] +public struct CurrencyCreated has copy, drop { + decimals: u8, +} +/// Capability allowing the bearer to freeze addresses, preventing those addresses from +/// interacting with the coin as an input to a transaction. +public struct DenyCap has key, store { + id: UID, +} - /// The index into the deny list vector for the `sui::coin::Coin` type. - const DENY_LIST_COIN_INDEX: u64 = 0; // TODO public(package) const - - /// Adds the given address to the deny list, preventing it - /// from interacting with the specified coin type as an input to a transaction. - #[deprecated(note = b"Use `migrate_regulated_currency_to_v2` to migrate to v2 and then use `deny_list_v2_add`")] - public fun deny_list_add( - deny_list: &mut DenyList, - _deny_cap: &mut DenyCap, - addr: address, - _ctx: &mut TxContext - ) { - let `type` = - type_name::into_string(type_name::get_with_original_ids()).into_bytes(); - deny_list.v1_add( - DENY_LIST_COIN_INDEX, - `type`, - addr, - ) - } +/// This creates a new currency, via `create_currency`, but with an extra capability that +/// allows for specific addresses to have their coins frozen. Those addresses cannot interact +/// with the coin as input objects. +#[ + deprecated( + note = b"For new coins, use `create_regulated_currency_v2`. To migrate existing regulated currencies, migrate with `migrate_regulated_currency_to_v2`", + ), +] +public fun create_regulated_currency( + witness: T, + decimals: u8, + symbol: vector, + name: vector, + description: vector, + icon_url: Option, + ctx: &mut TxContext, +): (TreasuryCap, DenyCap, CoinMetadata) { + let (treasury_cap, metadata) = create_currency( + witness, + decimals, + symbol, + name, + description, + icon_url, + ctx, + ); + let deny_cap = DenyCap { + id: object::new(ctx), + }; + transfer::freeze_object(RegulatedCoinMetadata { + id: object::new(ctx), + coin_metadata_object: object::id(&metadata), + deny_cap_object: object::id(&deny_cap), + }); + (treasury_cap, deny_cap, metadata) +} - /// Removes an address from the deny list. - /// Aborts with `ENotFrozen` if the address is not already in the list. - #[deprecated(note = b"Use `migrate_regulated_currency_to_v2` to migrate to v2 and then use `deny_list_v2_remove`")] - public fun deny_list_remove( - deny_list: &mut DenyList, - _deny_cap: &mut DenyCap, - addr: address, - _ctx: &mut TxContext - ) { - let `type` = - type_name::into_string(type_name::get_with_original_ids()).into_bytes(); - deny_list.v1_remove( - DENY_LIST_COIN_INDEX, - `type`, - addr, - ) - } +/// The index into the deny list vector for the `sui::coin::Coin` type. +const DENY_LIST_COIN_INDEX: u64 = 0; // TODO public(package) const + +/// Adds the given address to the deny list, preventing it +/// from interacting with the specified coin type as an input to a transaction. +#[ + deprecated( + note = b"Use `migrate_regulated_currency_to_v2` to migrate to v2 and then use `deny_list_v2_add`", + ), +] +public fun deny_list_add( + deny_list: &mut DenyList, + _deny_cap: &mut DenyCap, + addr: address, + _ctx: &mut TxContext, +) { + let `type` = type_name::into_string(type_name::get_with_original_ids()).into_bytes(); + deny_list.v1_add(DENY_LIST_COIN_INDEX, `type`, addr) +} - /// Returns true iff the given address is denied for the given coin type. It will - /// return false if given a non-coin type. - #[deprecated(note = b"Use `migrate_regulated_currency_to_v2` to migrate to v2 and then use `deny_list_v2_contains_next_epoch` or `deny_list_v2_contains_current_epoch`")] - public fun deny_list_contains( - deny_list: &DenyList, - addr: address, - ): bool { - let name = type_name::get_with_original_ids(); - if (type_name::is_primitive(&name)) return false; - - let `type` = type_name::into_string(name).into_bytes(); - deny_list.v1_contains(DENY_LIST_COIN_INDEX, `type`, addr) - } +/// Removes an address from the deny list. +/// Aborts with `ENotFrozen` if the address is not already in the list. +#[ + deprecated( + note = b"Use `migrate_regulated_currency_to_v2` to migrate to v2 and then use `deny_list_v2_remove`", + ), +] +public fun deny_list_remove( + deny_list: &mut DenyList, + _deny_cap: &mut DenyCap, + addr: address, + _ctx: &mut TxContext, +) { + let `type` = type_name::into_string(type_name::get_with_original_ids()).into_bytes(); + deny_list.v1_remove(DENY_LIST_COIN_INDEX, `type`, addr) +} + +/// Returns true iff the given address is denied for the given coin type. It will +/// return false if given a non-coin type. +#[ + deprecated( + note = b"Use `migrate_regulated_currency_to_v2` to migrate to v2 and then use `deny_list_v2_contains_next_epoch` or `deny_list_v2_contains_current_epoch`", + ), +] +public fun deny_list_contains(deny_list: &DenyList, addr: address): bool { + let name = type_name::get_with_original_ids(); + if (type_name::is_primitive(&name)) return false; + + let `type` = type_name::into_string(name).into_bytes(); + deny_list.v1_contains(DENY_LIST_COIN_INDEX, `type`, addr) } diff --git a/crates/sui-framework/packages/sui-framework/sources/config.move b/crates/sui-framework/packages/sui-framework/sources/config.move index 5c32204a4d9f2..116203066814b 100644 --- a/crates/sui-framework/packages/sui-framework/sources/config.move +++ b/crates/sui-framework/packages/sui-framework/sources/config.move @@ -1,115 +1,76 @@ // Copyright (c) Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 -module sui::config { +module sui::config; - use sui::dynamic_field as field; +use sui::dynamic_field as field; - // #[error] - // const EAlreadySetForEpoch: vector = - // b"Setting was already updated at this epoch for the provided Config"; - const EAlreadySetForEpoch: u64 = 0; +// #[error] +// const EAlreadySetForEpoch: vector = +// b"Setting was already updated at this epoch for the provided Config"; +const EAlreadySetForEpoch: u64 = 0; - // #[error] - // const ENotSetForEpoch: vector = - // b"Setting was not updated at this epoch for the provided Config"; - const ENotSetForEpoch: u64 = 1; +// #[error] +// const ENotSetForEpoch: vector = +// b"Setting was not updated at this epoch for the provided Config"; +const ENotSetForEpoch: u64 = 1; - // #[error] - // const ENotSetForEpoch: vector = b"Could not generate a layout for the type"; - #[allow(unused_const)] - const EBCSSerializationFailure: u64 = 2; +// #[error] +// const ENotSetForEpoch: vector = b"Could not generate a layout for the type"; +#[allow(unused_const)] +const EBCSSerializationFailure: u64 = 2; - public struct Config has key { - id: UID, - } +public struct Config has key { + id: UID, +} - public struct Setting has store, drop { - data: Option>, - } +public struct Setting has store, drop { + data: Option>, +} - public struct SettingData has store, drop { - newer_value_epoch: u64, - newer_value: Option, - older_value_opt: Option, - } +public struct SettingData has store, drop { + newer_value_epoch: u64, + newer_value: Option, + older_value_opt: Option, +} - public(package) fun new(_cap: &mut WriteCap, ctx: &mut TxContext): Config { - Config { id: object::new(ctx) } - } +public(package) fun new(_cap: &mut WriteCap, ctx: &mut TxContext): Config { + Config { id: object::new(ctx) } +} - #[allow(lint(share_owned))] - public(package) fun share(config: Config) { - transfer::share_object(config) - } +#[allow(lint(share_owned))] +public(package) fun share(config: Config) { + transfer::share_object(config) +} - public(package) fun transfer(config: Config, owner: address) { - transfer::transfer(config, owner) - } +public(package) fun transfer(config: Config, owner: address) { + transfer::transfer(config, owner) +} - #[allow(unused_mut_parameter)] - public(package) fun add_for_next_epoch< - WriteCap, - Name: copy + drop + store, - Value: copy + drop + store, - >( - config: &mut Config, - _cap: &mut WriteCap, - name: Name, - value: Value, - ctx: &mut TxContext, - ): Option { - let epoch = ctx.epoch(); - if (!field::exists_(&config.id, name)) { - let sobj = Setting { - data: option::some(SettingData { - newer_value_epoch: epoch, - newer_value: option::some(value), - older_value_opt: option::none(), - }), - }; - field::add(&mut config.id, name, sobj); - option::none() - } else { - let sobj: &mut Setting = field::borrow_mut(&mut config.id, name); - let SettingData { - newer_value_epoch, - newer_value, - older_value_opt, - } = sobj.data.extract(); - let (older_value_opt, removed_value) = - if (epoch > newer_value_epoch) { - // if the `newer_value` is for a previous epoch, move it to `older_value_opt` - (move newer_value, move older_value_opt) - } else { - // the current epoch cannot be less than the `newer_value_epoch` - assert!(epoch == newer_value_epoch); - // if the `newer_value` is for the current epoch, then the option must be `none` - assert!(newer_value.is_none(), EAlreadySetForEpoch); - (move older_value_opt, option::none()) - }; - sobj.data.fill(SettingData { +#[allow(unused_mut_parameter)] +public(package) fun add_for_next_epoch< + WriteCap, + Name: copy + drop + store, + Value: copy + drop + store, +>( + config: &mut Config, + _cap: &mut WriteCap, + name: Name, + value: Value, + ctx: &mut TxContext, +): Option { + let epoch = ctx.epoch(); + if (!field::exists_(&config.id, name)) { + let sobj = Setting { + data: option::some(SettingData { newer_value_epoch: epoch, newer_value: option::some(value), - older_value_opt, - }); - removed_value - } - } - - #[allow(unused_mut_parameter)] - public(package) fun remove_for_next_epoch< - WriteCap, - Name: copy + drop + store, - Value: copy + drop + store, - >( - config: &mut Config, - _cap: &mut WriteCap, - name: Name, - ctx: &mut TxContext, - ): Option { - let epoch = ctx.epoch(); - if (!field::exists_(&config.id, name)) return option::none(); + older_value_opt: option::none(), + }), + }; + field::add(&mut config.id, name, sobj); + option::none() + } else { let sobj: &mut Setting = field::borrow_mut(&mut config.id, name); let SettingData { newer_value_epoch, @@ -119,171 +80,208 @@ module sui::config { let (older_value_opt, removed_value) = if (epoch > newer_value_epoch) { // if the `newer_value` is for a previous epoch, move it to `older_value_opt` - (move newer_value, option::none()) + (move newer_value, move older_value_opt) } else { // the current epoch cannot be less than the `newer_value_epoch` assert!(epoch == newer_value_epoch); - (move older_value_opt, move newer_value) + // if the `newer_value` is for the current epoch, then the option must be `none` + assert!(newer_value.is_none(), EAlreadySetForEpoch); + (move older_value_opt, option::none()) }; - let older_value_opt_is_none = older_value_opt.is_none(); sobj.data.fill(SettingData { newer_value_epoch: epoch, - newer_value: option::none(), + newer_value: option::some(value), older_value_opt, }); - if (older_value_opt_is_none) { - field::remove<_, Setting>(&mut config.id, name); - }; removed_value } +} - public(package) fun exists_with_type< - WriteCap, - Name: copy + drop + store, - Value: copy + drop + store, - >( - config: &Config, - name: Name, - ): bool { - field::exists_with_type<_, Setting>(&config.id, name) - } +#[allow(unused_mut_parameter)] +public(package) fun remove_for_next_epoch< + WriteCap, + Name: copy + drop + store, + Value: copy + drop + store, +>( + config: &mut Config, + _cap: &mut WriteCap, + name: Name, + ctx: &mut TxContext, +): Option { + let epoch = ctx.epoch(); + if (!field::exists_(&config.id, name)) return option::none(); + let sobj: &mut Setting = field::borrow_mut(&mut config.id, name); + let SettingData { + newer_value_epoch, + newer_value, + older_value_opt, + } = sobj.data.extract(); + let (older_value_opt, removed_value) = + if (epoch > newer_value_epoch) { + // if the `newer_value` is for a previous epoch, move it to `older_value_opt` + (move newer_value, option::none()) + } else { + // the current epoch cannot be less than the `newer_value_epoch` + assert!(epoch == newer_value_epoch); + (move older_value_opt, move newer_value) + }; + let older_value_opt_is_none = older_value_opt.is_none(); + sobj.data.fill(SettingData { + newer_value_epoch: epoch, + newer_value: option::none(), + older_value_opt, + }); + if (older_value_opt_is_none) { + field::remove<_, Setting>(&mut config.id, name); + }; + removed_value +} - #[allow(unused_mut_parameter)] - public(package) fun exists_with_type_for_next_epoch< - WriteCap, - Name: copy + drop + store, - Value: copy + drop + store, - >( - config: & Config, - name: Name, - ctx: &TxContext, - ): bool { - field::exists_with_type<_, Setting>(&config.id, name) && { - let epoch = ctx.epoch(); - let sobj: &Setting = field::borrow(&config.id, name); - epoch == sobj.data.borrow().newer_value_epoch && - sobj.data.borrow().newer_value.is_some() - } - } +public(package) fun exists_with_type< + WriteCap, + Name: copy + drop + store, + Value: copy + drop + store, +>( + config: &Config, + name: Name, +): bool { + field::exists_with_type<_, Setting>(&config.id, name) +} - #[allow(unused_mut_parameter)] - public(package) fun borrow_for_next_epoch_mut< - WriteCap, - Name: copy + drop + store, - Value: copy + drop + store, - >( - config: &mut Config, - _cap: &mut WriteCap, - name: Name, - ctx: &mut TxContext, - ): &mut Value { +#[allow(unused_mut_parameter)] +public(package) fun exists_with_type_for_next_epoch< + WriteCap, + Name: copy + drop + store, + Value: copy + drop + store, +>( + config: &Config, + name: Name, + ctx: &TxContext, +): bool { + field::exists_with_type<_, Setting>(&config.id, name) && { let epoch = ctx.epoch(); - let sobj: &mut Setting = field::borrow_mut(&mut config.id, name); - let data = sobj.data.borrow_mut(); - assert!(data.newer_value_epoch == epoch, ENotSetForEpoch); - assert!(data.newer_value.is_some(), ENotSetForEpoch); - data.newer_value.borrow_mut() - } - - public(package) fun read_setting_for_next_epoch< - WriteCap, - Name: copy + drop + store, - Value: copy + drop + store, - >( - config: &Config, - name: Name, - ): Option { - if (!field::exists_with_type<_, Setting>(&config.id, name)) return option::none(); let sobj: &Setting = field::borrow(&config.id, name); - let data = sobj.data.borrow(); - data.newer_value + epoch == sobj.data.borrow().newer_value_epoch && + sobj.data.borrow().newer_value.is_some() } +} + +#[allow(unused_mut_parameter)] +public(package) fun borrow_for_next_epoch_mut< + WriteCap, + Name: copy + drop + store, + Value: copy + drop + store, +>( + config: &mut Config, + _cap: &mut WriteCap, + name: Name, + ctx: &mut TxContext, +): &mut Value { + let epoch = ctx.epoch(); + let sobj: &mut Setting = field::borrow_mut(&mut config.id, name); + let data = sobj.data.borrow_mut(); + assert!(data.newer_value_epoch == epoch, ENotSetForEpoch); + assert!(data.newer_value.is_some(), ENotSetForEpoch); + data.newer_value.borrow_mut() +} + +public(package) fun read_setting_for_next_epoch< + WriteCap, + Name: copy + drop + store, + Value: copy + drop + store, +>( + config: &Config, + name: Name, +): Option { + if (!field::exists_with_type<_, Setting>(&config.id, name)) return option::none(); + let sobj: &Setting = field::borrow(&config.id, name); + let data = sobj.data.borrow(); + data.newer_value +} + +public(package) macro fun entry< + $WriteCap, + $Name: copy + drop + store, + $Value: copy + drop + store, +>( + $config: &mut Config<$WriteCap>, + $cap: &mut $WriteCap, + $name: $Name, + $initial_for_next_epoch: |&mut Config<$WriteCap>, &mut $WriteCap, &mut TxContext| -> $Value, + $ctx: &mut TxContext, +): &mut $Value { + let config = $config; + let cap = $cap; + let name = $name; + let ctx = $ctx; + if (!config.exists_with_type_for_next_epoch<_, _, $Value>(name, ctx)) { + let initial = $initial_for_next_epoch(config, cap, ctx); + config.add_for_next_epoch(cap, name, initial, ctx); + }; + config.borrow_for_next_epoch_mut(cap, name, ctx) +} - public(package) macro fun entry< - $WriteCap, - $Name: copy + drop + store, - $Value: copy + drop + store, - >( - $config: &mut Config<$WriteCap>, - $cap: &mut $WriteCap, - $name: $Name, - $initial_for_next_epoch: |&mut Config<$WriteCap>, &mut $WriteCap, &mut TxContext| -> $Value, - $ctx: &mut TxContext, - ): &mut $Value { - let config = $config; - let cap = $cap; - let name = $name; - let ctx = $ctx; +public(package) macro fun update< + $WriteCap, + $Name: copy + drop + store, + $Value: copy + drop + store, +>( + $config: &mut Config<$WriteCap>, + $cap: &mut $WriteCap, + $name: $Name, + $initial_for_next_epoch: |&mut Config<$WriteCap>, &mut $WriteCap, &mut TxContext| -> $Value, + $update_for_next_epoch: |Option<$Value>, &mut $Value|, + $ctx: &mut TxContext, +) { + let config = $config; + let cap = $cap; + let name = $name; + let ctx = $ctx; + let old_value_opt = if (!config.exists_with_type_for_next_epoch<_, _, $Value>(name, ctx)) { let initial = $initial_for_next_epoch(config, cap, ctx); - config.add_for_next_epoch(cap, name, initial, ctx); + config.add_for_next_epoch(cap, name, initial, ctx) + } else { + option::none() }; - config.borrow_for_next_epoch_mut(cap, name, ctx) - } - - public(package) macro fun update< - $WriteCap, - $Name: copy + drop + store, - $Value: copy + drop + store, - >( - $config: &mut Config<$WriteCap>, - $cap: &mut $WriteCap, - $name: $Name, - $initial_for_next_epoch: |&mut Config<$WriteCap>, &mut $WriteCap, &mut TxContext| -> $Value, - $update_for_next_epoch: |Option<$Value>, &mut $Value|, - $ctx: &mut TxContext, - ) { - let config = $config; - let cap = $cap; - let name = $name; - let ctx = $ctx; - let old_value_opt = - if (!config.exists_with_type_for_next_epoch<_, _, $Value>(name, ctx)) { - let initial = $initial_for_next_epoch(config, cap, ctx); - config.add_for_next_epoch(cap, name, initial, ctx) - } else { - option::none() - }; - $update_for_next_epoch(old_value_opt, config.borrow_for_next_epoch_mut(cap, name, ctx)); - } + $update_for_next_epoch(old_value_opt, config.borrow_for_next_epoch_mut(cap, name, ctx)); +} - public(package) fun read_setting( - config: ID, - name: Name, - ctx: &TxContext, - ): Option { - use sui::dynamic_field::Field; - let config_id = config.to_address(); - let setting_df = field::hash_type_and_key(config_id, name); - read_setting_impl>, Setting, SettingData, Value>( - config_id, - setting_df, - ctx.epoch(), - ) - } +public(package) fun read_setting( + config: ID, + name: Name, + ctx: &TxContext, +): Option { + use sui::dynamic_field::Field; + let config_id = config.to_address(); + let setting_df = field::hash_type_and_key(config_id, name); + read_setting_impl>, Setting, SettingData, Value>( + config_id, + setting_df, + ctx.epoch(), + ) +} +/* +This is kept native to keep gas costing consistent. +*/ +native fun read_setting_impl< + FieldSettingValue: key, + SettingValue: store, + SettingDataValue: store, + Value: copy + drop + store, +>( + config: address, + name: address, + current_epoch: u64, +): Option; /* - This is kept native to keep gas costing consistent. - */ - native fun read_setting_impl< - FieldSettingValue: key, - SettingValue: store, - SettingDataValue: store, - Value: copy + drop + store, - >( - config: address, - name: address, - current_epoch: u64, - ): Option; - /* - // but the code is essentially - if (!field::exists_with_type(&config.id, setting)) return option::none() - let sobj: &Setting = field::borrow(&config.id, setting); - let data = sobj.data.borrow(); - if (current_epoch > data.newer_value_epoch) option::some(data.newer_value) - else data.older_value_opt - - } - */ +// but the code is essentially + if (!field::exists_with_type(&config.id, setting)) return option::none() + let sobj: &Setting = field::borrow(&config.id, setting); + let data = sobj.data.borrow(); + if (current_epoch > data.newer_value_epoch) option::some(data.newer_value) + else data.older_value_opt } +*/ diff --git a/crates/sui-framework/packages/sui-framework/sources/deny_list.move b/crates/sui-framework/packages/sui-framework/sources/deny_list.move index f2e74ee41b52e..51a1c91d8c9f3 100644 --- a/crates/sui-framework/packages/sui-framework/sources/deny_list.move +++ b/crates/sui-framework/packages/sui-framework/sources/deny_list.move @@ -4,445 +4,427 @@ /// Defines the `DenyList` type. The `DenyList` shared object is used to restrict access to /// instances of certain core types from being used as inputs by specified addresses in the deny /// list. -module sui::deny_list { - use sui::config::{Self, Config}; - use sui::dynamic_object_field as ofield; - use sui::table::{Self, Table}; - use sui::bag::{Self, Bag}; - use sui::vec_set::{Self, VecSet}; - - /// Trying to create a deny list object when not called by the system address. - const ENotSystemAddress: u64 = 0; - /// The specified address to be removed is not already in the deny list. - const ENotDenied: u64 = 1; - /// The specified address cannot be added to the deny list. - const EInvalidAddress: u64 = 1; - - /// The index into the deny list vector for the `sui::coin::Coin` type. - const COIN_INDEX: u64 = 0; - - /// These addresses are reserved and cannot be added to the deny list. - /// The addresses listed are well known package and object addresses. So it would be - /// meaningless to add them to the deny list. - const RESERVED: vector
= vector[ - @0x0, - @0x1, - @0x2, - @0x3, - @0x4, - @0x5, - @0x6, - @0x7, - @0x8, - @0x9, - @0xA, - @0xB, - @0xC, - @0xD, - @0xE, - @0xF, - @0x403, - @0xDEE9, - ]; - - /// A shared object that stores the addresses that are blocked for a given core type. - public struct DenyList has key { - id: UID, - /// The individual deny lists. - lists: Bag, - } +module sui::deny_list; + +use sui::bag::{Self, Bag}; +use sui::config::{Self, Config}; +use sui::dynamic_object_field as ofield; +use sui::table::{Self, Table}; +use sui::vec_set::{Self, VecSet}; + +/// Trying to create a deny list object when not called by the system address. +const ENotSystemAddress: u64 = 0; +/// The specified address to be removed is not already in the deny list. +const ENotDenied: u64 = 1; +/// The specified address cannot be added to the deny list. +const EInvalidAddress: u64 = 1; + +/// The index into the deny list vector for the `sui::coin::Coin` type. +const COIN_INDEX: u64 = 0; + +/// These addresses are reserved and cannot be added to the deny list. +/// The addresses listed are well known package and object addresses. So it would be +/// meaningless to add them to the deny list. +const RESERVED: vector
= vector[ + @0x0, + @0x1, + @0x2, + @0x3, + @0x4, + @0x5, + @0x6, + @0x7, + @0x8, + @0x9, + @0xA, + @0xB, + @0xC, + @0xD, + @0xE, + @0xF, + @0x403, + @0xDEE9, +]; + +/// A shared object that stores the addresses that are blocked for a given core type. +public struct DenyList has key { + id: UID, + /// The individual deny lists. + lists: Bag, +} +// === V2 === - // === V2 === +/// The capability used to write to the deny list config. Ensures that the Configs for the +/// DenyList are modified only by this module. +public struct ConfigWriteCap() has drop; - /// The capability used to write to the deny list config. Ensures that the Configs for the - /// DenyList are modified only by this module. - public struct ConfigWriteCap() has drop; +/// The dynamic object field key used to store the `Config` for a given type, essentially a +/// `(per_type_index, per_type_key)` pair. +public struct ConfigKey has copy, drop, store { + per_type_index: u64, + per_type_key: vector, +} - /// The dynamic object field key used to store the `Config` for a given type, essentially a - /// `(per_type_index, per_type_key)` pair. - public struct ConfigKey has copy, drop, store { - per_type_index: u64, - per_type_key: vector, - } +/// The setting key used to store the deny list for a given address in the `Config`. +public struct AddressKey(address) has copy, drop, store; - /// The setting key used to store the deny list for a given address in the `Config`. - public struct AddressKey(address) has copy, drop, store; +/// The setting key used to store the global pause setting in the `Config`. +public struct GlobalPauseKey() has copy, drop, store; - /// The setting key used to store the global pause setting in the `Config`. - public struct GlobalPauseKey() has copy, drop, store; +/// The event emitted when a new `Config` is created for a given type. This can be useful for +/// tracking the `ID` of a type's `Config` object. +public struct PerTypeConfigCreated has copy, drop, store { + key: ConfigKey, + config_id: ID, +} - /// The event emitted when a new `Config` is created for a given type. This can be useful for - /// tracking the `ID` of a type's `Config` object. - public struct PerTypeConfigCreated has copy, drop, store { - key: ConfigKey, - config_id: ID, - } +public(package) fun v2_add( + deny_list: &mut DenyList, + per_type_index: u64, + per_type_key: vector, + addr: address, + ctx: &mut TxContext, +) { + let per_type_config = deny_list.per_type_config_entry!(per_type_index, per_type_key, ctx); + let setting_name = AddressKey(addr); + let next_epoch_entry = per_type_config.entry!<_, AddressKey, bool>( + &mut ConfigWriteCap(), + setting_name, + |_deny_list, _cap, _ctx| true, + ctx, + ); + *next_epoch_entry = true; +} - public(package) fun v2_add( - deny_list: &mut DenyList, - per_type_index: u64, - per_type_key: vector, - addr: address, - ctx: &mut TxContext, - ) { - let per_type_config = deny_list.per_type_config_entry!(per_type_index, per_type_key, ctx); - let setting_name = AddressKey(addr); - let next_epoch_entry = per_type_config.entry!<_,AddressKey, bool>( - &mut ConfigWriteCap(), - setting_name, - |_deny_list, _cap, _ctx| true, - ctx, - ); - *next_epoch_entry = true; - } +public(package) fun v2_remove( + deny_list: &mut DenyList, + per_type_index: u64, + per_type_key: vector, + addr: address, + ctx: &mut TxContext, +) { + let per_type_config = deny_list.per_type_config_entry!(per_type_index, per_type_key, ctx); + let setting_name = AddressKey(addr); + per_type_config.remove_for_next_epoch<_, AddressKey, bool>( + &mut ConfigWriteCap(), + setting_name, + ctx, + ); +} - public(package) fun v2_remove( - deny_list: &mut DenyList, - per_type_index: u64, - per_type_key: vector, - addr: address, - ctx: &mut TxContext, - ) { - let per_type_config = deny_list.per_type_config_entry!(per_type_index, per_type_key, ctx); - let setting_name = AddressKey(addr); - per_type_config.remove_for_next_epoch<_, AddressKey, bool>( - &mut ConfigWriteCap(), - setting_name, - ctx, - ); - } +public(package) fun v2_contains_current_epoch( + deny_list: &DenyList, + per_type_index: u64, + per_type_key: vector, + addr: address, + ctx: &TxContext, +): bool { + if (!deny_list.per_type_exists(per_type_index, per_type_key)) return false; + let per_type_config = deny_list.borrow_per_type_config(per_type_index, per_type_key); + let setting_name = AddressKey(addr); + config::read_setting(object::id(per_type_config), setting_name, ctx).destroy_or!(false) +} - public(package) fun v2_contains_current_epoch( - deny_list: &DenyList, - per_type_index: u64, - per_type_key: vector, - addr: address, - ctx: &TxContext, - ): bool { - if (!deny_list.per_type_exists(per_type_index, per_type_key)) return false; - let per_type_config = deny_list.borrow_per_type_config(per_type_index, per_type_key); - let setting_name = AddressKey(addr); - config::read_setting(object::id(per_type_config), setting_name, ctx).destroy_or!(false) - } +public(package) fun v2_contains_next_epoch( + deny_list: &DenyList, + per_type_index: u64, + per_type_key: vector, + addr: address, +): bool { + if (!deny_list.per_type_exists(per_type_index, per_type_key)) return false; + let per_type_config = deny_list.borrow_per_type_config(per_type_index, per_type_key); + let setting_name = AddressKey(addr); + per_type_config.read_setting_for_next_epoch(setting_name).destroy_or!(false) +} - public(package) fun v2_contains_next_epoch( - deny_list: &DenyList, - per_type_index: u64, - per_type_key: vector, - addr: address, - ): bool { - if (!deny_list.per_type_exists(per_type_index, per_type_key)) return false; - let per_type_config = deny_list.borrow_per_type_config(per_type_index, per_type_key); - let setting_name = AddressKey(addr); - per_type_config.read_setting_for_next_epoch(setting_name).destroy_or!(false) - } +// public(package) fun v2_per_type_contains( +// per_type_config: ID, +// addr: address, +// ): bool { +// // TODO can read from the config directly once the ID is set +// } + +public(package) fun v2_enable_global_pause( + deny_list: &mut DenyList, + per_type_index: u64, + per_type_key: vector, + ctx: &mut TxContext, +) { + let per_type_config = deny_list.per_type_config_entry!(per_type_index, per_type_key, ctx); + let setting_name = GlobalPauseKey(); + let next_epoch_entry = per_type_config.entry!<_, GlobalPauseKey, bool>( + &mut ConfigWriteCap(), + setting_name, + |_deny_list, _cap, _ctx| true, + ctx, + ); + *next_epoch_entry = true; +} + +public(package) fun v2_disable_global_pause( + deny_list: &mut DenyList, + per_type_index: u64, + per_type_key: vector, + ctx: &mut TxContext, +) { + let per_type_config = deny_list.per_type_config_entry!(per_type_index, per_type_key, ctx); + let setting_name = GlobalPauseKey(); + per_type_config.remove_for_next_epoch<_, GlobalPauseKey, bool>( + &mut ConfigWriteCap(), + setting_name, + ctx, + ); +} + +public(package) fun v2_is_global_pause_enabled_current_epoch( + deny_list: &DenyList, + per_type_index: u64, + per_type_key: vector, + ctx: &TxContext, +): bool { + if (!deny_list.per_type_exists(per_type_index, per_type_key)) return false; + let per_type_config = deny_list.borrow_per_type_config(per_type_index, per_type_key); + let setting_name = GlobalPauseKey(); + config::read_setting(object::id(per_type_config), setting_name, ctx).destroy_or!(false) +} - // public(package) fun v2_per_type_contains( - // per_type_config: ID, - // addr: address, - // ): bool { - // // TODO can read from the config directly once the ID is set - // } - - public(package) fun v2_enable_global_pause( - deny_list: &mut DenyList, - per_type_index: u64, - per_type_key: vector, - ctx: &mut TxContext, - ) { - let per_type_config = deny_list.per_type_config_entry!(per_type_index, per_type_key, ctx); - let setting_name = GlobalPauseKey(); - let next_epoch_entry = per_type_config.entry!<_, GlobalPauseKey, bool>( +public(package) fun v2_is_global_pause_enabled_next_epoch( + deny_list: &DenyList, + per_type_index: u64, + per_type_key: vector, +): bool { + if (!deny_list.per_type_exists(per_type_index, per_type_key)) return false; + let per_type_config = deny_list.borrow_per_type_config(per_type_index, per_type_key); + let setting_name = GlobalPauseKey(); + per_type_config.read_setting_for_next_epoch(setting_name).destroy_or!(false) +} + +// public(package) fun v2_per_type_is_global_pause_enabled( +// per_type_config: ID, +// ): bool { +// // TODO can read from the config directly once the ID is set +// } + +public(package) fun migrate_v1_to_v2( + deny_list: &mut DenyList, + per_type_index: u64, + per_type_key: vector, + ctx: &mut TxContext, +) { + let bag_entry: &mut PerTypeList = &mut deny_list.lists[per_type_index]; + let elements = if (!bag_entry.denied_addresses.contains(per_type_key)) vector[] + else bag_entry.denied_addresses.remove(per_type_key).into_keys(); + elements.do_ref!(|addr| { + let addr = *addr; + let denied_count = &mut bag_entry.denied_count[addr]; + *denied_count = *denied_count - 1; + if (*denied_count == 0) { + bag_entry.denied_count.remove(addr); + } + }); + let per_type_config = deny_list.per_type_config_entry!(per_type_index, per_type_key, ctx); + elements.do!(|addr| { + let setting_name = AddressKey(addr); + let next_epoch_entry = per_type_config.entry!<_, AddressKey, bool>( &mut ConfigWriteCap(), setting_name, |_deny_list, _cap, _ctx| true, ctx, ); *next_epoch_entry = true; - } - - public(package) fun v2_disable_global_pause( - deny_list: &mut DenyList, - per_type_index: u64, - per_type_key: vector, - ctx: &mut TxContext, - ) { - let per_type_config = deny_list.per_type_config_entry!(per_type_index, per_type_key, ctx); - let setting_name = GlobalPauseKey(); - per_type_config.remove_for_next_epoch<_, GlobalPauseKey, bool>( - &mut ConfigWriteCap(), - setting_name, - ctx, - ); - } - - public(package) fun v2_is_global_pause_enabled_current_epoch( - deny_list: &DenyList, - per_type_index: u64, - per_type_key: vector, - ctx: &TxContext, - ): bool { - if (!deny_list.per_type_exists(per_type_index, per_type_key)) return false; - let per_type_config = deny_list.borrow_per_type_config(per_type_index, per_type_key); - let setting_name = GlobalPauseKey(); - config::read_setting(object::id(per_type_config), setting_name, ctx).destroy_or!(false) - } - - public(package) fun v2_is_global_pause_enabled_next_epoch( - deny_list: &DenyList, - per_type_index: u64, - per_type_key: vector, - ): bool { - if (!deny_list.per_type_exists(per_type_index, per_type_key)) return false; - let per_type_config = deny_list.borrow_per_type_config(per_type_index, per_type_key); - let setting_name = GlobalPauseKey(); - per_type_config.read_setting_for_next_epoch(setting_name).destroy_or!(false) - } - - // public(package) fun v2_per_type_is_global_pause_enabled( - // per_type_config: ID, - // ): bool { - // // TODO can read from the config directly once the ID is set - // } - - public(package) fun migrate_v1_to_v2( - deny_list: &mut DenyList, - per_type_index: u64, - per_type_key: vector, - ctx: &mut TxContext, - ) { - let bag_entry: &mut PerTypeList = &mut deny_list.lists[per_type_index]; - let elements = - if (!bag_entry.denied_addresses.contains(per_type_key)) vector[] - else bag_entry.denied_addresses.remove(per_type_key).into_keys(); - elements.do_ref!(|addr| { - let addr = *addr; - let denied_count = &mut bag_entry.denied_count[addr]; - *denied_count = *denied_count - 1; - if (*denied_count == 0) { - bag_entry.denied_count.remove(addr); - } - }); - let per_type_config = deny_list.per_type_config_entry!(per_type_index, per_type_key, ctx); - elements.do!(|addr| { - let setting_name = AddressKey(addr); - let next_epoch_entry = per_type_config.entry!<_,AddressKey, bool>( - &mut ConfigWriteCap(), - setting_name, - |_deny_list, _cap, _ctx| true, - ctx, - ); - *next_epoch_entry = true; - }); - } + }); +} - fun add_per_type_config( - deny_list: &mut DenyList, - per_type_index: u64, - per_type_key: vector, - ctx: &mut TxContext, - ) { - let key = ConfigKey { per_type_index, per_type_key }; - let config = config::new(&mut ConfigWriteCap(), ctx); - let config_id = object::id(&config); - ofield::internal_add(&mut deny_list.id, key, config); - sui::event::emit(PerTypeConfigCreated { key, config_id }); - } +fun add_per_type_config( + deny_list: &mut DenyList, + per_type_index: u64, + per_type_key: vector, + ctx: &mut TxContext, +) { + let key = ConfigKey { per_type_index, per_type_key }; + let config = config::new(&mut ConfigWriteCap(), ctx); + let config_id = object::id(&config); + ofield::internal_add(&mut deny_list.id, key, config); + sui::event::emit(PerTypeConfigCreated { key, config_id }); +} - fun borrow_per_type_config_mut( - deny_list: &mut DenyList, - per_type_index: u64, - per_type_key: vector, - ): &mut Config { - let key = ConfigKey { per_type_index, per_type_key }; - ofield::internal_borrow_mut(&mut deny_list.id, key) - } +fun borrow_per_type_config_mut( + deny_list: &mut DenyList, + per_type_index: u64, + per_type_key: vector, +): &mut Config { + let key = ConfigKey { per_type_index, per_type_key }; + ofield::internal_borrow_mut(&mut deny_list.id, key) +} - fun borrow_per_type_config( - deny_list: &DenyList, - per_type_index: u64, - per_type_key: vector, - ): &Config { - let key = ConfigKey { per_type_index, per_type_key }; - ofield::internal_borrow(&deny_list.id, key) - } +fun borrow_per_type_config( + deny_list: &DenyList, + per_type_index: u64, + per_type_key: vector, +): &Config { + let key = ConfigKey { per_type_index, per_type_key }; + ofield::internal_borrow(&deny_list.id, key) +} - fun per_type_exists( - deny_list: &DenyList, - per_type_index: u64, - per_type_key: vector, - ): bool { - let key = ConfigKey { per_type_index, per_type_key }; - ofield::exists_(&deny_list.id, key) - } +fun per_type_exists(deny_list: &DenyList, per_type_index: u64, per_type_key: vector): bool { + let key = ConfigKey { per_type_index, per_type_key }; + ofield::exists_(&deny_list.id, key) +} - macro fun per_type_config_entry( - $deny_list: &mut DenyList, - $per_type_index: u64, - $per_type_key: vector, - $ctx: &mut TxContext, - ): &mut Config { - let deny_list = $deny_list; - let per_type_index = $per_type_index; - let per_type_key = $per_type_key; - let ctx = $ctx; - if (!deny_list.per_type_exists(per_type_index, per_type_key)) { - deny_list.add_per_type_config(per_type_index, per_type_key, ctx); - }; - deny_list.borrow_per_type_config_mut(per_type_index, per_type_key) - } +macro fun per_type_config_entry( + $deny_list: &mut DenyList, + $per_type_index: u64, + $per_type_key: vector, + $ctx: &mut TxContext, +): &mut Config { + let deny_list = $deny_list; + let per_type_index = $per_type_index; + let per_type_key = $per_type_key; + let ctx = $ctx; + if (!deny_list.per_type_exists(per_type_index, per_type_key)) { + deny_list.add_per_type_config(per_type_index, per_type_key, ctx); + }; + deny_list.borrow_per_type_config_mut(per_type_index, per_type_key) +} - // === V1 === - - /// Stores the addresses that are denied for a given core type. - public struct PerTypeList has key, store { - id: UID, - /// Number of object types that have been banned for a given address. - /// Used to quickly skip checks for most addresses. - denied_count: Table, - /// Set of addresses that are banned for a given type. - /// For example with `sui::coin::Coin`: If addresses A and B are banned from using - /// "0...0123::my_coin::MY_COIN", this will be "0...0123::my_coin::MY_COIN" -> {A, B}. - denied_addresses: Table, VecSet
>, - } +// === V1 === + +/// Stores the addresses that are denied for a given core type. +public struct PerTypeList has key, store { + id: UID, + /// Number of object types that have been banned for a given address. + /// Used to quickly skip checks for most addresses. + denied_count: Table, + /// Set of addresses that are banned for a given type. + /// For example with `sui::coin::Coin`: If addresses A and B are banned from using + /// "0...0123::my_coin::MY_COIN", this will be "0...0123::my_coin::MY_COIN" -> {A, B}. + denied_addresses: Table, VecSet
>, +} - /// Adds the given address to the deny list of the specified type, preventing it - /// from interacting with instances of that type as an input to a transaction. For coins, - /// the type specified is the type of the coin, not the coin type itself. For example, - /// "00...0123::my_coin::MY_COIN" would be the type, not "00...02::coin::Coin". - public(package) fun v1_add( - deny_list: &mut DenyList, - per_type_index: u64, - `type`: vector, - addr: address, - ) { - let reserved = RESERVED; - assert!(!reserved.contains(&addr), EInvalidAddress); - let bag_entry: &mut PerTypeList = &mut deny_list.lists[per_type_index]; - bag_entry.v1_per_type_list_add(`type`, addr) - } +/// Adds the given address to the deny list of the specified type, preventing it +/// from interacting with instances of that type as an input to a transaction. For coins, +/// the type specified is the type of the coin, not the coin type itself. For example, +/// "00...0123::my_coin::MY_COIN" would be the type, not "00...02::coin::Coin". +public(package) fun v1_add( + deny_list: &mut DenyList, + per_type_index: u64, + `type`: vector, + addr: address, +) { + let reserved = RESERVED; + assert!(!reserved.contains(&addr), EInvalidAddress); + let bag_entry: &mut PerTypeList = &mut deny_list.lists[per_type_index]; + bag_entry.v1_per_type_list_add(`type`, addr) +} - fun v1_per_type_list_add( - list: &mut PerTypeList, - `type`: vector, - addr: address, - ) { - if (!list.denied_addresses.contains(`type`)) { - list.denied_addresses.add(`type`, vec_set::empty()); - }; - let denied_addresses = &mut list.denied_addresses[`type`]; - let already_denied = denied_addresses.contains(&addr); - if (already_denied) return; - - denied_addresses.insert(addr); - if (!list.denied_count.contains(addr)) { - list.denied_count.add(addr, 0); - }; - let denied_count = &mut list.denied_count[addr]; - *denied_count = *denied_count + 1; - } +fun v1_per_type_list_add(list: &mut PerTypeList, `type`: vector, addr: address) { + if (!list.denied_addresses.contains(`type`)) { + list.denied_addresses.add(`type`, vec_set::empty()); + }; + let denied_addresses = &mut list.denied_addresses[`type`]; + let already_denied = denied_addresses.contains(&addr); + if (already_denied) return; + + denied_addresses.insert(addr); + if (!list.denied_count.contains(addr)) { + list.denied_count.add(addr, 0); + }; + let denied_count = &mut list.denied_count[addr]; + *denied_count = *denied_count + 1; +} - /// Removes a previously denied address from the list. - /// Aborts with `ENotDenied` if the address is not on the list. - public(package) fun v1_remove( - deny_list: &mut DenyList, - per_type_index: u64, - `type`: vector, - addr: address, - ) { - let reserved = RESERVED; - assert!(!reserved.contains(&addr), EInvalidAddress); - let bag_entry: &mut PerTypeList = &mut deny_list.lists[per_type_index]; - bag_entry.v1_per_type_list_remove(`type`, addr) - } +/// Removes a previously denied address from the list. +/// Aborts with `ENotDenied` if the address is not on the list. +public(package) fun v1_remove( + deny_list: &mut DenyList, + per_type_index: u64, + `type`: vector, + addr: address, +) { + let reserved = RESERVED; + assert!(!reserved.contains(&addr), EInvalidAddress); + let bag_entry: &mut PerTypeList = &mut deny_list.lists[per_type_index]; + bag_entry.v1_per_type_list_remove(`type`, addr) +} - fun v1_per_type_list_remove( - list: &mut PerTypeList, - `type`: vector, - addr: address, - ) { - let denied_addresses = &mut list.denied_addresses[`type`]; - assert!(denied_addresses.contains(&addr), ENotDenied); - denied_addresses.remove(&addr); - let denied_count = &mut list.denied_count[addr]; - *denied_count = *denied_count - 1; - if (*denied_count == 0) { - list.denied_count.remove(addr); - } +fun v1_per_type_list_remove(list: &mut PerTypeList, `type`: vector, addr: address) { + let denied_addresses = &mut list.denied_addresses[`type`]; + assert!(denied_addresses.contains(&addr), ENotDenied); + denied_addresses.remove(&addr); + let denied_count = &mut list.denied_count[addr]; + *denied_count = *denied_count - 1; + if (*denied_count == 0) { + list.denied_count.remove(addr); } +} - /// Returns true iff the given address is denied for the given type. - public(package) fun v1_contains( - deny_list: &DenyList, - per_type_index: u64, - `type`: vector, - addr: address, - ): bool { - let reserved = RESERVED; - if (reserved.contains(&addr)) return false; - let bag_entry: &PerTypeList = &deny_list.lists[per_type_index]; - bag_entry.v1_per_type_list_contains(`type`, addr) - } +/// Returns true iff the given address is denied for the given type. +public(package) fun v1_contains( + deny_list: &DenyList, + per_type_index: u64, + `type`: vector, + addr: address, +): bool { + let reserved = RESERVED; + if (reserved.contains(&addr)) return false; + let bag_entry: &PerTypeList = &deny_list.lists[per_type_index]; + bag_entry.v1_per_type_list_contains(`type`, addr) +} - fun v1_per_type_list_contains( - list: &PerTypeList, - `type`: vector, - addr: address, - ): bool { - if (!list.denied_count.contains(addr)) return false; +fun v1_per_type_list_contains(list: &PerTypeList, `type`: vector, addr: address): bool { + if (!list.denied_count.contains(addr)) return false; - let denied_count = &list.denied_count[addr]; - if (*denied_count == 0) return false; + let denied_count = &list.denied_count[addr]; + if (*denied_count == 0) return false; - if (!list.denied_addresses.contains(`type`)) return false; + if (!list.denied_addresses.contains(`type`)) return false; - let denied_addresses = &list.denied_addresses[`type`]; - denied_addresses.contains(&addr) - } + let denied_addresses = &list.denied_addresses[`type`]; + denied_addresses.contains(&addr) +} - #[allow(unused_function)] - /// Creation of the deny list object is restricted to the system address - /// via a system transaction. - fun create(ctx: &mut TxContext) { - assert!(ctx.sender() == @0x0, ENotSystemAddress); - - let mut lists = bag::new(ctx); - lists.add(COIN_INDEX, per_type_list(ctx)); - let deny_list_object = DenyList { - id: object::sui_deny_list_object_id(), - lists, - }; - transfer::share_object(deny_list_object); - } +#[allow(unused_function)] +/// Creation of the deny list object is restricted to the system address +/// via a system transaction. +fun create(ctx: &mut TxContext) { + assert!(ctx.sender() == @0x0, ENotSystemAddress); + + let mut lists = bag::new(ctx); + lists.add(COIN_INDEX, per_type_list(ctx)); + let deny_list_object = DenyList { + id: object::sui_deny_list_object_id(), + lists, + }; + transfer::share_object(deny_list_object); +} - fun per_type_list(ctx: &mut TxContext): PerTypeList { - PerTypeList { - id: object::new(ctx), - denied_count: table::new(ctx), - denied_addresses: table::new(ctx), - } +fun per_type_list(ctx: &mut TxContext): PerTypeList { + PerTypeList { + id: object::new(ctx), + denied_count: table::new(ctx), + denied_addresses: table::new(ctx), } +} - #[test_only] - public fun reserved_addresses(): vector
{ - RESERVED - } +#[test_only] +public fun reserved_addresses(): vector
{ + RESERVED +} - #[test_only] - public fun create_for_test(ctx: &mut TxContext) { - create(ctx); - } +#[test_only] +public fun create_for_test(ctx: &mut TxContext) { + create(ctx); +} - #[test_only] - /// Creates and returns a new DenyList object for testing purposes. It - /// doesn't matter which object ID the list has in this kind of test. - public fun new_for_testing(ctx: &mut TxContext): DenyList { - let mut lists = bag::new(ctx); - lists.add(COIN_INDEX, per_type_list(ctx)); - DenyList { - id: object::new(ctx), - lists, - } +#[test_only] +/// Creates and returns a new DenyList object for testing purposes. It +/// doesn't matter which object ID the list has in this kind of test. +public fun new_for_testing(ctx: &mut TxContext): DenyList { + let mut lists = bag::new(ctx); + lists.add(COIN_INDEX, per_type_list(ctx)); + DenyList { + id: object::new(ctx), + lists, } } diff --git a/crates/sui-framework/packages/sui-framework/sources/sui.move b/crates/sui-framework/packages/sui-framework/sources/sui.move index 90c7daab59cdf..0bbc843e321df 100644 --- a/crates/sui-framework/packages/sui-framework/sources/sui.move +++ b/crates/sui-framework/packages/sui-framework/sources/sui.move @@ -3,54 +3,54 @@ /// Coin is the token used to pay for gas in Sui. /// It has 9 decimals, and the smallest unit (10^-9) is called "mist". -module sui::sui { - use sui::balance::Balance; - use sui::coin; - - const EAlreadyMinted: u64 = 0; - /// Sender is not @0x0 the system address. - const ENotSystemAddress: u64 = 1; - - #[allow(unused_const)] - /// The amount of Mist per Sui token based on the fact that mist is - /// 10^-9 of a Sui token - const MIST_PER_SUI: u64 = 1_000_000_000; - - #[allow(unused_const)] - /// The total supply of Sui denominated in whole Sui tokens (10 Billion) - const TOTAL_SUPPLY_SUI: u64 = 10_000_000_000; - - /// The total supply of Sui denominated in Mist (10 Billion * 10^9) - const TOTAL_SUPPLY_MIST: u64 = 10_000_000_000_000_000_000; - - /// Name of the coin - public struct SUI has drop {} - - #[allow(unused_function)] - /// Register the `SUI` Coin to acquire its `Supply`. - /// This should be called only once during genesis creation. - fun new(ctx: &mut TxContext): Balance { - assert!(ctx.sender() == @0x0, ENotSystemAddress); - assert!(ctx.epoch() == 0, EAlreadyMinted); - - let (treasury, metadata) = coin::create_currency( - SUI {}, - 9, - b"SUI", - b"Sui", - // TODO: add appropriate description and logo url - b"", - option::none(), - ctx - ); - transfer::public_freeze_object(metadata); - let mut supply = treasury.treasury_into_supply(); - let total_sui = supply.increase_supply(TOTAL_SUPPLY_MIST); - supply.destroy_supply(); - total_sui - } - - public entry fun transfer(c: coin::Coin, recipient: address) { - transfer::public_transfer(c, recipient) - } +module sui::sui; + +use sui::balance::Balance; +use sui::coin; + +const EAlreadyMinted: u64 = 0; +/// Sender is not @0x0 the system address. +const ENotSystemAddress: u64 = 1; + +#[allow(unused_const)] +/// The amount of Mist per Sui token based on the fact that mist is +/// 10^-9 of a Sui token +const MIST_PER_SUI: u64 = 1_000_000_000; + +#[allow(unused_const)] +/// The total supply of Sui denominated in whole Sui tokens (10 Billion) +const TOTAL_SUPPLY_SUI: u64 = 10_000_000_000; + +/// The total supply of Sui denominated in Mist (10 Billion * 10^9) +const TOTAL_SUPPLY_MIST: u64 = 10_000_000_000_000_000_000; + +/// Name of the coin +public struct SUI has drop {} + +#[allow(unused_function)] +/// Register the `SUI` Coin to acquire its `Supply`. +/// This should be called only once during genesis creation. +fun new(ctx: &mut TxContext): Balance { + assert!(ctx.sender() == @0x0, ENotSystemAddress); + assert!(ctx.epoch() == 0, EAlreadyMinted); + + let (treasury, metadata) = coin::create_currency( + SUI {}, + 9, + b"SUI", + b"Sui", + // TODO: add appropriate description and logo url + b"", + option::none(), + ctx, + ); + transfer::public_freeze_object(metadata); + let mut supply = treasury.treasury_into_supply(); + let total_sui = supply.increase_supply(TOTAL_SUPPLY_MIST); + supply.destroy_supply(); + total_sui +} + +public entry fun transfer(c: coin::Coin, recipient: address) { + transfer::public_transfer(c, recipient) } diff --git a/crates/sui-framework/packages/sui-framework/sources/token.move b/crates/sui-framework/packages/sui-framework/sources/token.move index 634efa027b99f..e625eb2a1079d 100644 --- a/crates/sui-framework/packages/sui-framework/sources/token.move +++ b/crates/sui-framework/packages/sui-framework/sources/token.move @@ -19,726 +19,719 @@ /// The Token system allows for fine-grained control over the actions performed /// on the token. And hence it is highly suitable for applications that require /// control over the currency which a simple open-loop system can't provide. -module sui::token { - use std::string::String; - use std::type_name::{Self, TypeName}; - use sui::coin::{Coin, TreasuryCap}; - use sui::balance::{Self, Balance}; - use sui::vec_map::{Self, VecMap}; - use sui::vec_set::{Self, VecSet}; - use sui::dynamic_field as df; - use sui::event; - - /// The action is not allowed (defined) in the policy. - const EUnknownAction: u64 = 0; - /// The rule was not approved. - const ENotApproved: u64 = 1; - /// Trying to perform an admin action with a wrong cap. - const ENotAuthorized: u64 = 2; - /// The balance is too low to perform the action. - const EBalanceTooLow: u64 = 3; - /// The balance is not zero. - const ENotZero: u64 = 4; - /// The balance is not zero when trying to confirm with `TransferPolicyCap`. - const ECantConsumeBalance: u64 = 5; - /// Rule is trying to access a missing config (with type). - const ENoConfig: u64 = 6; - /// Using `confirm_request_mut` without `spent_balance`. Immutable version - /// of the function must be used instead. - const EUseImmutableConfirm: u64 = 7; - - // === Protected Actions === - - /// A Tag for the `spend` action. - const SPEND: vector = b"spend"; - /// A Tag for the `transfer` action. - const TRANSFER: vector = b"transfer"; - /// A Tag for the `to_coin` action. - const TO_COIN: vector = b"to_coin"; - /// A Tag for the `from_coin` action. - const FROM_COIN: vector = b"from_coin"; - - /// A single `Token` with `Balance` inside. Can only be owned by an address, - /// and actions performed on it must be confirmed in a matching `TokenPolicy`. - public struct Token has key { - id: UID, - /// The Balance of the `Token`. - balance: Balance, - } +module sui::token; + +use std::{string::String, type_name::{Self, TypeName}}; +use sui::{ + balance::{Self, Balance}, + coin::{Coin, TreasuryCap}, + dynamic_field as df, + event, + vec_map::{Self, VecMap}, + vec_set::{Self, VecSet} +}; + +/// The action is not allowed (defined) in the policy. +const EUnknownAction: u64 = 0; +/// The rule was not approved. +const ENotApproved: u64 = 1; +/// Trying to perform an admin action with a wrong cap. +const ENotAuthorized: u64 = 2; +/// The balance is too low to perform the action. +const EBalanceTooLow: u64 = 3; +/// The balance is not zero. +const ENotZero: u64 = 4; +/// The balance is not zero when trying to confirm with `TransferPolicyCap`. +const ECantConsumeBalance: u64 = 5; +/// Rule is trying to access a missing config (with type). +const ENoConfig: u64 = 6; +/// Using `confirm_request_mut` without `spent_balance`. Immutable version +/// of the function must be used instead. +const EUseImmutableConfirm: u64 = 7; + +// === Protected Actions === + +/// A Tag for the `spend` action. +const SPEND: vector = b"spend"; +/// A Tag for the `transfer` action. +const TRANSFER: vector = b"transfer"; +/// A Tag for the `to_coin` action. +const TO_COIN: vector = b"to_coin"; +/// A Tag for the `from_coin` action. +const FROM_COIN: vector = b"from_coin"; + +/// A single `Token` with `Balance` inside. Can only be owned by an address, +/// and actions performed on it must be confirmed in a matching `TokenPolicy`. +public struct Token has key { + id: UID, + /// The Balance of the `Token`. + balance: Balance, +} - /// A Capability that manages a single `TokenPolicy` specified in the `for` - /// field. Created together with `TokenPolicy` in the `new` function. - public struct TokenPolicyCap has key, store { id: UID, `for`: ID } +/// A Capability that manages a single `TokenPolicy` specified in the `for` +/// field. Created together with `TokenPolicy` in the `new` function. +public struct TokenPolicyCap has key, store { id: UID, `for`: ID } - /// `TokenPolicy` represents a set of rules that define what actions can be - /// performed on a `Token` and which `Rules` must be satisfied for the - /// action to succeed. +/// `TokenPolicy` represents a set of rules that define what actions can be +/// performed on a `Token` and which `Rules` must be satisfied for the +/// action to succeed. +/// +/// - For the sake of availability, `TokenPolicy` is a `key`-only object. +/// - Each `TokenPolicy` is managed by a matching `TokenPolicyCap`. +/// - For an action to become available, there needs to be a record in the +/// `rules` VecMap. To allow an action to be performed freely, there's an +/// `allow` function that can be called by the `TokenPolicyCap` owner. +public struct TokenPolicy has key { + id: UID, + /// The balance that is effectively spent by the user on the "spend" + /// action. However, actual decrease of the supply can only be done by + /// the `TreasuryCap` owner when `flush` is called. /// - /// - For the sake of availability, `TokenPolicy` is a `key`-only object. - /// - Each `TokenPolicy` is managed by a matching `TokenPolicyCap`. - /// - For an action to become available, there needs to be a record in the - /// `rules` VecMap. To allow an action to be performed freely, there's an - /// `allow` function that can be called by the `TokenPolicyCap` owner. - public struct TokenPolicy has key { - id: UID, - /// The balance that is effectively spent by the user on the "spend" - /// action. However, actual decrease of the supply can only be done by - /// the `TreasuryCap` owner when `flush` is called. - /// - /// This balance is effectively spent and cannot be accessed by anyone - /// but the `TreasuryCap` owner. - spent_balance: Balance, - /// The set of rules that define what actions can be performed on the - /// token. For each "action" there's a set of Rules that must be - /// satisfied for the `ActionRequest` to be confirmed. - rules: VecMap> - } + /// This balance is effectively spent and cannot be accessed by anyone + /// but the `TreasuryCap` owner. + spent_balance: Balance, + /// The set of rules that define what actions can be performed on the + /// token. For each "action" there's a set of Rules that must be + /// satisfied for the `ActionRequest` to be confirmed. + rules: VecMap>, +} - /// A request to perform an "Action" on a token. Stores the information - /// about the action to be performed and must be consumed by the `confirm_request` - /// or `confirm_request_mut` functions when the Rules are satisfied. - public struct ActionRequest { - /// Name of the Action to look up in the Policy. Name can be one of the - /// default actions: `transfer`, `spend`, `to_coin`, `from_coin` or a - /// custom action. - name: String, - /// Amount is present in all of the txs - amount: u64, - /// Sender is a permanent field always - sender: address, - /// Recipient is only available in `transfer` action. - recipient: Option
, - /// The balance to be "spent" in the `TokenPolicy`, only available - /// in the `spend` action. - spent_balance: Option>, - /// Collected approvals (stamps) from completed `Rules`. They're matched - /// against `TokenPolicy.rules` to determine if the request can be - /// confirmed. - approvals: VecSet, - } +/// A request to perform an "Action" on a token. Stores the information +/// about the action to be performed and must be consumed by the `confirm_request` +/// or `confirm_request_mut` functions when the Rules are satisfied. +public struct ActionRequest { + /// Name of the Action to look up in the Policy. Name can be one of the + /// default actions: `transfer`, `spend`, `to_coin`, `from_coin` or a + /// custom action. + name: String, + /// Amount is present in all of the txs + amount: u64, + /// Sender is a permanent field always + sender: address, + /// Recipient is only available in `transfer` action. + recipient: Option
, + /// The balance to be "spent" in the `TokenPolicy`, only available + /// in the `spend` action. + spent_balance: Option>, + /// Collected approvals (stamps) from completed `Rules`. They're matched + /// against `TokenPolicy.rules` to determine if the request can be + /// confirmed. + approvals: VecSet, +} - /// Dynamic field key for the `TokenPolicy` to store the `Config` for a - /// specific action `Rule`. There can be only one configuration per - /// `Rule` per `TokenPolicy`. - public struct RuleKey has store, copy, drop { is_protected: bool } - - /// An event emitted when a `TokenPolicy` is created and shared. Because - /// `TokenPolicy` can only be shared (and potentially frozen in the future), - /// we emit this event in the `share_policy` function and mark it as mutable. - public struct TokenPolicyCreated has copy, drop { - /// ID of the `TokenPolicy` that was created. - id: ID, - /// Whether the `TokenPolicy` is "shared" (mutable) or "frozen" - /// (immutable) - TBD. - is_mutable: bool, - } +/// Dynamic field key for the `TokenPolicy` to store the `Config` for a +/// specific action `Rule`. There can be only one configuration per +/// `Rule` per `TokenPolicy`. +public struct RuleKey has store, copy, drop { is_protected: bool } + +/// An event emitted when a `TokenPolicy` is created and shared. Because +/// `TokenPolicy` can only be shared (and potentially frozen in the future), +/// we emit this event in the `share_policy` function and mark it as mutable. +public struct TokenPolicyCreated has copy, drop { + /// ID of the `TokenPolicy` that was created. + id: ID, + /// Whether the `TokenPolicy` is "shared" (mutable) or "frozen" + /// (immutable) - TBD. + is_mutable: bool, +} - /// Create a new `TokenPolicy` and a matching `TokenPolicyCap`. - /// The `TokenPolicy` must then be shared using the `share_policy` method. - /// - /// `TreasuryCap` guarantees full ownership over the currency, and is unique, - /// hence it is safe to use it for authorization. - public fun new_policy( - _treasury_cap: &TreasuryCap, ctx: &mut TxContext - ): (TokenPolicy, TokenPolicyCap) { - let policy = TokenPolicy { - id: object::new(ctx), - spent_balance: balance::zero(), - rules: vec_map::empty() - }; - - let cap = TokenPolicyCap { - id: object::new(ctx), - `for`: object::id(&policy) - }; - - (policy, cap) - } +/// Create a new `TokenPolicy` and a matching `TokenPolicyCap`. +/// The `TokenPolicy` must then be shared using the `share_policy` method. +/// +/// `TreasuryCap` guarantees full ownership over the currency, and is unique, +/// hence it is safe to use it for authorization. +public fun new_policy( + _treasury_cap: &TreasuryCap, + ctx: &mut TxContext, +): (TokenPolicy, TokenPolicyCap) { + let policy = TokenPolicy { + id: object::new(ctx), + spent_balance: balance::zero(), + rules: vec_map::empty(), + }; + + let cap = TokenPolicyCap { + id: object::new(ctx), + `for`: object::id(&policy), + }; + + (policy, cap) +} - /// Share the `TokenPolicy`. Due to `key`-only restriction, it must be - /// shared after initialization. - public fun share_policy(policy: TokenPolicy) { - event::emit(TokenPolicyCreated { - id: object::id(&policy), - is_mutable: true, - }); +/// Share the `TokenPolicy`. Due to `key`-only restriction, it must be +/// shared after initialization. +public fun share_policy(policy: TokenPolicy) { + event::emit(TokenPolicyCreated { + id: object::id(&policy), + is_mutable: true, + }); - transfer::share_object(policy) - } + transfer::share_object(policy) +} + +// === Protected Actions === + +/// Transfer a `Token` to a `recipient`. Creates an `ActionRequest` for the +/// "transfer" action. The `ActionRequest` contains the `recipient` field +/// to be used in verification. +public fun transfer(t: Token, recipient: address, ctx: &mut TxContext): ActionRequest { + let amount = t.balance.value(); + transfer::transfer(t, recipient); + + new_request( + transfer_action(), + amount, + option::some(recipient), + option::none(), + ctx, + ) +} - // === Protected Actions === +/// Spend a `Token` by unwrapping it and storing the `Balance` in the +/// `ActionRequest` for the "spend" action. The `ActionRequest` contains +/// the `spent_balance` field to be used in verification. +/// +/// Spend action requires `confirm_request_mut` to be called to confirm the +/// request and join the spent balance with the `TokenPolicy.spent_balance`. +public fun spend(t: Token, ctx: &mut TxContext): ActionRequest { + let Token { id, balance } = t; + id.delete(); + + new_request( + spend_action(), + balance.value(), + option::none(), + option::some(balance), + ctx, + ) +} - /// Transfer a `Token` to a `recipient`. Creates an `ActionRequest` for the - /// "transfer" action. The `ActionRequest` contains the `recipient` field - /// to be used in verification. - public fun transfer( - t: Token, recipient: address, ctx: &mut TxContext - ): ActionRequest { - let amount = t.balance.value(); - transfer::transfer(t, recipient); +/// Convert `Token` into an open `Coin`. Creates an `ActionRequest` for the +/// "to_coin" action. +public fun to_coin(t: Token, ctx: &mut TxContext): (Coin, ActionRequest) { + let Token { id, balance } = t; + let amount = balance.value(); + id.delete(); + ( + balance.into_coin(ctx), new_request( - transfer_action(), + to_coin_action(), amount, - option::some(recipient), option::none(), - ctx - ) - } - - /// Spend a `Token` by unwrapping it and storing the `Balance` in the - /// `ActionRequest` for the "spend" action. The `ActionRequest` contains - /// the `spent_balance` field to be used in verification. - /// - /// Spend action requires `confirm_request_mut` to be called to confirm the - /// request and join the spent balance with the `TokenPolicy.spent_balance`. - public fun spend(t: Token, ctx: &mut TxContext): ActionRequest { - let Token { id, balance } = t; - id.delete(); + option::none(), + ctx, + ), + ) +} +/// Convert an open `Coin` into a `Token`. Creates an `ActionRequest` for +/// the "from_coin" action. +public fun from_coin(coin: Coin, ctx: &mut TxContext): (Token, ActionRequest) { + let amount = coin.value(); + let token = Token { + id: object::new(ctx), + balance: coin.into_balance(), + }; + + ( + token, new_request( - spend_action(), - balance.value(), + from_coin_action(), + amount, option::none(), - option::some(balance), - ctx - ) - } - - /// Convert `Token` into an open `Coin`. Creates an `ActionRequest` for the - /// "to_coin" action. - public fun to_coin( - t: Token, ctx: &mut TxContext - ): (Coin, ActionRequest) { - let Token { id, balance } = t; - let amount = balance.value(); - id.delete(); - - ( - balance.into_coin(ctx), - new_request( - to_coin_action(), - amount, - option::none(), - option::none(), - ctx - ) - ) - } - - /// Convert an open `Coin` into a `Token`. Creates an `ActionRequest` for - /// the "from_coin" action. - public fun from_coin( - coin: Coin, ctx: &mut TxContext - ): (Token, ActionRequest) { - let amount = coin.value(); - let token = Token { - id: object::new(ctx), - balance: coin.into_balance() - }; - - ( - token, - new_request( - from_coin_action(), - amount, - option::none(), - option::none(), - ctx - ) - ) - } + option::none(), + ctx, + ), + ) +} - // === Public Actions === +// === Public Actions === - /// Join two `Token`s into one, always available. - public fun join(token: &mut Token, another: Token) { - let Token { id, balance } = another; - token.balance.join(balance); - id.delete(); - } +/// Join two `Token`s into one, always available. +public fun join(token: &mut Token, another: Token) { + let Token { id, balance } = another; + token.balance.join(balance); + id.delete(); +} - /// Split a `Token` with `amount`. - /// Aborts if the `Token.balance` is lower than `amount`. - public fun split( - token: &mut Token, amount: u64, ctx: &mut TxContext - ): Token { - assert!(token.balance.value() >= amount, EBalanceTooLow); - Token { - id: object::new(ctx), - balance: token.balance.split(amount), - } +/// Split a `Token` with `amount`. +/// Aborts if the `Token.balance` is lower than `amount`. +public fun split(token: &mut Token, amount: u64, ctx: &mut TxContext): Token { + assert!(token.balance.value() >= amount, EBalanceTooLow); + Token { + id: object::new(ctx), + balance: token.balance.split(amount), } +} - /// Create a zero `Token`. - public fun zero(ctx: &mut TxContext): Token { - Token { - id: object::new(ctx), - balance: balance::zero(), - } +/// Create a zero `Token`. +public fun zero(ctx: &mut TxContext): Token { + Token { + id: object::new(ctx), + balance: balance::zero(), } +} - /// Destroy an empty `Token`, fails if the balance is non-zero. - /// Aborts if the `Token.balance` is not zero. - public fun destroy_zero(token: Token) { - let Token { id, balance } = token; - assert!(balance.value() == 0, ENotZero); - balance.destroy_zero(); - id.delete(); - } +/// Destroy an empty `Token`, fails if the balance is non-zero. +/// Aborts if the `Token.balance` is not zero. +public fun destroy_zero(token: Token) { + let Token { id, balance } = token; + assert!(balance.value() == 0, ENotZero); + balance.destroy_zero(); + id.delete(); +} - #[allow(lint(self_transfer))] - /// Transfer the `Token` to the transaction sender. - public fun keep(token: Token, ctx: &mut TxContext) { - transfer::transfer(token, ctx.sender()) - } +#[allow(lint(self_transfer))] +/// Transfer the `Token` to the transaction sender. +public fun keep(token: Token, ctx: &mut TxContext) { + transfer::transfer(token, ctx.sender()) +} - // === Request Handling === - - /// Create a new `ActionRequest`. - /// Publicly available method to allow for custom actions. - public fun new_request( - name: String, - amount: u64, - recipient: Option
, - spent_balance: Option>, - ctx: &TxContext - ): ActionRequest { - ActionRequest { - name, - amount, - recipient, - spent_balance, - sender: ctx.sender(), - approvals: vec_set::empty(), - } +// === Request Handling === + +/// Create a new `ActionRequest`. +/// Publicly available method to allow for custom actions. +public fun new_request( + name: String, + amount: u64, + recipient: Option
, + spent_balance: Option>, + ctx: &TxContext, +): ActionRequest { + ActionRequest { + name, + amount, + recipient, + spent_balance, + sender: ctx.sender(), + approvals: vec_set::empty(), } +} - /// Confirm the request against the `TokenPolicy` and return the parameters - /// of the request: (Name, Amount, Sender, Recipient). - /// - /// Cannot be used for `spend` and similar actions that deliver `spent_balance` - /// to the `TokenPolicy`. For those actions use `confirm_request_mut`. - /// - /// Aborts if: - /// - the action is not allowed (missing record in `rules`) - /// - action contains `spent_balance` (use `confirm_request_mut`) - /// - the `ActionRequest` does not meet the `TokenPolicy` rules for the action - public fun confirm_request( - policy: &TokenPolicy, - request: ActionRequest, - _ctx: &mut TxContext - ): (String, u64, address, Option
) { - assert!(request.spent_balance.is_none(), ECantConsumeBalance); - assert!(policy.rules.contains(&request.name), EUnknownAction); - - let ActionRequest { - name, approvals, - spent_balance, - amount, sender, recipient, - } = request; - - spent_balance.destroy_none(); - - let rules = &(*policy.rules.get(&name)).into_keys(); - let rules_len = rules.length(); - let mut i = 0; - - while (i < rules_len) { - let rule = &rules[i]; - assert!(approvals.contains(rule), ENotApproved); - i = i + 1; - }; - - (name, amount, sender, recipient) - } +/// Confirm the request against the `TokenPolicy` and return the parameters +/// of the request: (Name, Amount, Sender, Recipient). +/// +/// Cannot be used for `spend` and similar actions that deliver `spent_balance` +/// to the `TokenPolicy`. For those actions use `confirm_request_mut`. +/// +/// Aborts if: +/// - the action is not allowed (missing record in `rules`) +/// - action contains `spent_balance` (use `confirm_request_mut`) +/// - the `ActionRequest` does not meet the `TokenPolicy` rules for the action +public fun confirm_request( + policy: &TokenPolicy, + request: ActionRequest, + _ctx: &mut TxContext, +): (String, u64, address, Option
) { + assert!(request.spent_balance.is_none(), ECantConsumeBalance); + assert!(policy.rules.contains(&request.name), EUnknownAction); + + let ActionRequest { + name, + approvals, + spent_balance, + amount, + sender, + recipient, + } = request; + + spent_balance.destroy_none(); + + let rules = &(*policy.rules.get(&name)).into_keys(); + let rules_len = rules.length(); + let mut i = 0; + + while (i < rules_len) { + let rule = &rules[i]; + assert!(approvals.contains(rule), ENotApproved); + i = i + 1; + }; + + (name, amount, sender, recipient) +} - /// Confirm the request against the `TokenPolicy` and return the parameters - /// of the request: (Name, Amount, Sender, Recipient). - /// - /// Unlike `confirm_request` this function requires mutable access to the - /// `TokenPolicy` and must be used on `spend` action. After dealing with the - /// spent balance it calls `confirm_request` internally. - /// - /// See `confirm_request` for the list of abort conditions. - public fun confirm_request_mut( - policy: &mut TokenPolicy, - mut request: ActionRequest, - ctx: &mut TxContext - ): (String, u64, address, Option
) { - assert!(policy.rules.contains(&request.name), EUnknownAction); - assert!(request.spent_balance.is_some(), EUseImmutableConfirm); - - policy.spent_balance.join(request.spent_balance.extract()); - - confirm_request(policy, request, ctx) - } +/// Confirm the request against the `TokenPolicy` and return the parameters +/// of the request: (Name, Amount, Sender, Recipient). +/// +/// Unlike `confirm_request` this function requires mutable access to the +/// `TokenPolicy` and must be used on `spend` action. After dealing with the +/// spent balance it calls `confirm_request` internally. +/// +/// See `confirm_request` for the list of abort conditions. +public fun confirm_request_mut( + policy: &mut TokenPolicy, + mut request: ActionRequest, + ctx: &mut TxContext, +): (String, u64, address, Option
) { + assert!(policy.rules.contains(&request.name), EUnknownAction); + assert!(request.spent_balance.is_some(), EUseImmutableConfirm); + + policy.spent_balance.join(request.spent_balance.extract()); + + confirm_request(policy, request, ctx) +} - /// Confirm an `ActionRequest` as the `TokenPolicyCap` owner. This function - /// allows `TokenPolicy` owner to perform Capability-gated actions ignoring - /// the ruleset specified in the `TokenPolicy`. - /// - /// Aborts if request contains `spent_balance` due to inability of the - /// `TokenPolicyCap` to decrease supply. For scenarios like this a - /// `TreasuryCap` is required (see `confirm_with_treasury_cap`). - public fun confirm_with_policy_cap( - _policy_cap: &TokenPolicyCap, - request: ActionRequest, - _ctx: &mut TxContext - ): (String, u64, address, Option
) { - assert!(request.spent_balance.is_none(), ECantConsumeBalance); - - let ActionRequest { - name, amount, sender, recipient, approvals: _, spent_balance - } = request; +/// Confirm an `ActionRequest` as the `TokenPolicyCap` owner. This function +/// allows `TokenPolicy` owner to perform Capability-gated actions ignoring +/// the ruleset specified in the `TokenPolicy`. +/// +/// Aborts if request contains `spent_balance` due to inability of the +/// `TokenPolicyCap` to decrease supply. For scenarios like this a +/// `TreasuryCap` is required (see `confirm_with_treasury_cap`). +public fun confirm_with_policy_cap( + _policy_cap: &TokenPolicyCap, + request: ActionRequest, + _ctx: &mut TxContext, +): (String, u64, address, Option
) { + assert!(request.spent_balance.is_none(), ECantConsumeBalance); + + let ActionRequest { + name, + amount, + sender, + recipient, + approvals: _, + spent_balance, + } = request; + + spent_balance.destroy_none(); + + (name, amount, sender, recipient) +} +/// Confirm an `ActionRequest` as the `TreasuryCap` owner. This function +/// allows `TreasuryCap` owner to perform Capability-gated actions ignoring +/// the ruleset specified in the `TokenPolicy`. +/// +/// Unlike `confirm_with_policy_cap` this function allows `spent_balance` +/// to be consumed, decreasing the `total_supply` of the `Token`. +public fun confirm_with_treasury_cap( + treasury_cap: &mut TreasuryCap, + request: ActionRequest, + _ctx: &mut TxContext, +): (String, u64, address, Option
) { + let ActionRequest { + name, + amount, + sender, + recipient, + approvals: _, + spent_balance, + } = request; + + if (spent_balance.is_some()) { + treasury_cap.supply_mut().decrease_supply(spent_balance.destroy_some()); + } else { spent_balance.destroy_none(); + }; - (name, amount, sender, recipient) - } - - /// Confirm an `ActionRequest` as the `TreasuryCap` owner. This function - /// allows `TreasuryCap` owner to perform Capability-gated actions ignoring - /// the ruleset specified in the `TokenPolicy`. - /// - /// Unlike `confirm_with_policy_cap` this function allows `spent_balance` - /// to be consumed, decreasing the `total_supply` of the `Token`. - public fun confirm_with_treasury_cap( - treasury_cap: &mut TreasuryCap, - request: ActionRequest, - _ctx: &mut TxContext - ): (String, u64, address, Option
) { - let ActionRequest { - name, amount, sender, recipient, approvals: _, - spent_balance - } = request; - - if (spent_balance.is_some()) { - treasury_cap.supply_mut().decrease_supply(spent_balance.destroy_some()); - } else { - spent_balance.destroy_none(); - }; - - (name, amount, sender, recipient) - } + (name, amount, sender, recipient) +} - // === Rules API === +// === Rules API === - /// Add an "approval" to the `ActionRequest` by providing a Witness. - /// Intended to be used by Rules to add their own approvals, however, can - /// be used to add arbitrary approvals to the request (not only the ones - /// required by the `TokenPolicy`). - public fun add_approval( - _t: W, request: &mut ActionRequest, _ctx: &mut TxContext - ) { - request.approvals.insert(type_name::get()) - } +/// Add an "approval" to the `ActionRequest` by providing a Witness. +/// Intended to be used by Rules to add their own approvals, however, can +/// be used to add arbitrary approvals to the request (not only the ones +/// required by the `TokenPolicy`). +public fun add_approval(_t: W, request: &mut ActionRequest, _ctx: &mut TxContext) { + request.approvals.insert(type_name::get()) +} - /// Add a `Config` for a `Rule` in the `TokenPolicy`. Rule configuration is - /// independent from the `TokenPolicy.rules` and needs to be managed by the - /// Rule itself. Configuration is stored per `Rule` and not per `Rule` per - /// `Action` to allow reuse in different actions. - /// - /// - Rule witness guarantees that the `Config` is approved by the Rule. - /// - `TokenPolicyCap` guarantees that the `Config` setup is initiated by - /// the `TokenPolicy` owner. - public fun add_rule_config( - _rule: Rule, - self: &mut TokenPolicy, - cap: &TokenPolicyCap, - config: Config, - _ctx: &mut TxContext - ) { - assert!(object::id(self) == cap.`for`, ENotAuthorized); - df::add(&mut self.id, key(), config) - } +/// Add a `Config` for a `Rule` in the `TokenPolicy`. Rule configuration is +/// independent from the `TokenPolicy.rules` and needs to be managed by the +/// Rule itself. Configuration is stored per `Rule` and not per `Rule` per +/// `Action` to allow reuse in different actions. +/// +/// - Rule witness guarantees that the `Config` is approved by the Rule. +/// - `TokenPolicyCap` guarantees that the `Config` setup is initiated by +/// the `TokenPolicy` owner. +public fun add_rule_config( + _rule: Rule, + self: &mut TokenPolicy, + cap: &TokenPolicyCap, + config: Config, + _ctx: &mut TxContext, +) { + assert!(object::id(self) == cap.`for`, ENotAuthorized); + df::add(&mut self.id, key(), config) +} - /// Get a `Config` for a `Rule` in the `TokenPolicy`. Requires `Rule` - /// witness, hence can only be read by the `Rule` itself. This requirement - /// guarantees safety of the stored `Config` and allows for simpler dynamic - /// field management inside the Rule Config (custom type keys are not needed - /// for access gating). - /// - /// Aborts if the Config is not present. - public fun rule_config( - _rule: Rule, self: &TokenPolicy - ): &Config { - assert!(has_rule_config_with_type(self), ENoConfig); - df::borrow(&self.id, key()) - } +/// Get a `Config` for a `Rule` in the `TokenPolicy`. Requires `Rule` +/// witness, hence can only be read by the `Rule` itself. This requirement +/// guarantees safety of the stored `Config` and allows for simpler dynamic +/// field management inside the Rule Config (custom type keys are not needed +/// for access gating). +/// +/// Aborts if the Config is not present. +public fun rule_config(_rule: Rule, self: &TokenPolicy): &Config { + assert!(has_rule_config_with_type(self), ENoConfig); + df::borrow(&self.id, key()) +} - /// Get mutable access to the `Config` for a `Rule` in the `TokenPolicy`. - /// Requires `Rule` witness, hence can only be read by the `Rule` itself, - /// as well as `TokenPolicyCap` to guarantee that the `TokenPolicy` owner - /// is the one who initiated the `Config` modification. - /// - /// Aborts if: - /// - the Config is not present - /// - `TokenPolicyCap` is not matching the `TokenPolicy` - public fun rule_config_mut( - _rule: Rule, self: &mut TokenPolicy, cap: &TokenPolicyCap - ): &mut Config { - assert!(has_rule_config_with_type(self), ENoConfig); - assert!(object::id(self) == cap.`for`, ENotAuthorized); - df::borrow_mut(&mut self.id, key()) - } +/// Get mutable access to the `Config` for a `Rule` in the `TokenPolicy`. +/// Requires `Rule` witness, hence can only be read by the `Rule` itself, +/// as well as `TokenPolicyCap` to guarantee that the `TokenPolicy` owner +/// is the one who initiated the `Config` modification. +/// +/// Aborts if: +/// - the Config is not present +/// - `TokenPolicyCap` is not matching the `TokenPolicy` +public fun rule_config_mut( + _rule: Rule, + self: &mut TokenPolicy, + cap: &TokenPolicyCap, +): &mut Config { + assert!(has_rule_config_with_type(self), ENoConfig); + assert!(object::id(self) == cap.`for`, ENotAuthorized); + df::borrow_mut(&mut self.id, key()) +} - /// Remove a `Config` for a `Rule` in the `TokenPolicy`. - /// Unlike the `add_rule_config`, this function does not require a `Rule` - /// witness, hence can be performed by the `TokenPolicy` owner on their own. - /// - /// Rules need to make sure that the `Config` is present when performing - /// verification of the `ActionRequest`. - /// - /// Aborts if: - /// - the Config is not present - /// - `TokenPolicyCap` is not matching the `TokenPolicy` - public fun remove_rule_config( - self: &mut TokenPolicy, - cap: &TokenPolicyCap, - _ctx: &mut TxContext - ): Config { - assert!(has_rule_config_with_type(self), ENoConfig); - assert!(object::id(self) == cap.`for`, ENotAuthorized); - df::remove(&mut self.id, key()) - } +/// Remove a `Config` for a `Rule` in the `TokenPolicy`. +/// Unlike the `add_rule_config`, this function does not require a `Rule` +/// witness, hence can be performed by the `TokenPolicy` owner on their own. +/// +/// Rules need to make sure that the `Config` is present when performing +/// verification of the `ActionRequest`. +/// +/// Aborts if: +/// - the Config is not present +/// - `TokenPolicyCap` is not matching the `TokenPolicy` +public fun remove_rule_config( + self: &mut TokenPolicy, + cap: &TokenPolicyCap, + _ctx: &mut TxContext, +): Config { + assert!(has_rule_config_with_type(self), ENoConfig); + assert!(object::id(self) == cap.`for`, ENotAuthorized); + df::remove(&mut self.id, key()) +} - /// Check if a config for a `Rule` is set in the `TokenPolicy` without - /// checking the type of the `Config`. - public fun has_rule_config(self: &TokenPolicy): bool { - df::exists_>(&self.id, key()) - } +/// Check if a config for a `Rule` is set in the `TokenPolicy` without +/// checking the type of the `Config`. +public fun has_rule_config(self: &TokenPolicy): bool { + df::exists_>(&self.id, key()) +} - /// Check if a `Config` for a `Rule` is set in the `TokenPolicy` and that - /// it matches the type provided. - public fun has_rule_config_with_type( - self: &TokenPolicy - ): bool { - df::exists_with_type, Config>(&self.id, key()) - } +/// Check if a `Config` for a `Rule` is set in the `TokenPolicy` and that +/// it matches the type provided. +public fun has_rule_config_with_type(self: &TokenPolicy): bool { + df::exists_with_type, Config>(&self.id, key()) +} - // === Protected: Setting Rules === +// === Protected: Setting Rules === - /// Allows an `action` to be performed on the `Token` freely by adding an - /// empty set of `Rules` for the `action`. - /// - /// Aborts if the `TokenPolicyCap` is not matching the `TokenPolicy`. - public fun allow( - self: &mut TokenPolicy, - cap: &TokenPolicyCap, - action: String, - _ctx: &mut TxContext - ) { - assert!(object::id(self) == cap.`for`, ENotAuthorized); - self.rules.insert(action, vec_set::empty()); - } +/// Allows an `action` to be performed on the `Token` freely by adding an +/// empty set of `Rules` for the `action`. +/// +/// Aborts if the `TokenPolicyCap` is not matching the `TokenPolicy`. +public fun allow( + self: &mut TokenPolicy, + cap: &TokenPolicyCap, + action: String, + _ctx: &mut TxContext, +) { + assert!(object::id(self) == cap.`for`, ENotAuthorized); + self.rules.insert(action, vec_set::empty()); +} - /// Completely disallows an `action` on the `Token` by removing the record - /// from the `TokenPolicy.rules`. - /// - /// Aborts if the `TokenPolicyCap` is not matching the `TokenPolicy`. - public fun disallow( - self: &mut TokenPolicy, - cap: &TokenPolicyCap, - action: String, - _ctx: &mut TxContext - ) { - assert!(object::id(self) == cap.`for`, ENotAuthorized); - self.rules.remove(&action); - } +/// Completely disallows an `action` on the `Token` by removing the record +/// from the `TokenPolicy.rules`. +/// +/// Aborts if the `TokenPolicyCap` is not matching the `TokenPolicy`. +public fun disallow( + self: &mut TokenPolicy, + cap: &TokenPolicyCap, + action: String, + _ctx: &mut TxContext, +) { + assert!(object::id(self) == cap.`for`, ENotAuthorized); + self.rules.remove(&action); +} - /// Adds a Rule for an action with `name` in the `TokenPolicy`. - /// - /// Aborts if the `TokenPolicyCap` is not matching the `TokenPolicy`. - public fun add_rule_for_action( - self: &mut TokenPolicy, - cap: &TokenPolicyCap, - action: String, - ctx: &mut TxContext - ) { - assert!(object::id(self) == cap.`for`, ENotAuthorized); - if (!self.rules.contains(&action)) { - allow(self, cap, action, ctx); - }; - - self.rules.get_mut(&action).insert(type_name::get()) - } +/// Adds a Rule for an action with `name` in the `TokenPolicy`. +/// +/// Aborts if the `TokenPolicyCap` is not matching the `TokenPolicy`. +public fun add_rule_for_action( + self: &mut TokenPolicy, + cap: &TokenPolicyCap, + action: String, + ctx: &mut TxContext, +) { + assert!(object::id(self) == cap.`for`, ENotAuthorized); + if (!self.rules.contains(&action)) { + allow(self, cap, action, ctx); + }; + + self.rules.get_mut(&action).insert(type_name::get()) +} - /// Removes a rule for an action with `name` in the `TokenPolicy`. Returns - /// the config object to be handled by the sender (or a Rule itself). - /// - /// Aborts if the `TokenPolicyCap` is not matching the `TokenPolicy`. - public fun remove_rule_for_action( - self: &mut TokenPolicy, - cap: &TokenPolicyCap, - action: String, - _ctx: &mut TxContext - ) { - assert!(object::id(self) == cap.`for`, ENotAuthorized); - - self.rules.get_mut(&action).remove(&type_name::get()) - } +/// Removes a rule for an action with `name` in the `TokenPolicy`. Returns +/// the config object to be handled by the sender (or a Rule itself). +/// +/// Aborts if the `TokenPolicyCap` is not matching the `TokenPolicy`. +public fun remove_rule_for_action( + self: &mut TokenPolicy, + cap: &TokenPolicyCap, + action: String, + _ctx: &mut TxContext, +) { + assert!(object::id(self) == cap.`for`, ENotAuthorized); + + self.rules.get_mut(&action).remove(&type_name::get()) +} - // === Protected: Treasury Management === +// === Protected: Treasury Management === - /// Mint a `Token` with a given `amount` using the `TreasuryCap`. - public fun mint( - cap: &mut TreasuryCap, amount: u64, ctx: &mut TxContext - ): Token { - let balance = cap.supply_mut().increase_supply(amount); - Token { id: object::new(ctx), balance } - } +/// Mint a `Token` with a given `amount` using the `TreasuryCap`. +public fun mint(cap: &mut TreasuryCap, amount: u64, ctx: &mut TxContext): Token { + let balance = cap.supply_mut().increase_supply(amount); + Token { id: object::new(ctx), balance } +} - /// Burn a `Token` using the `TreasuryCap`. - public fun burn(cap: &mut TreasuryCap, token: Token) { - let Token { id, balance } = token; - cap.supply_mut().decrease_supply(balance); - id.delete(); - } +/// Burn a `Token` using the `TreasuryCap`. +public fun burn(cap: &mut TreasuryCap, token: Token) { + let Token { id, balance } = token; + cap.supply_mut().decrease_supply(balance); + id.delete(); +} - /// Flush the `TokenPolicy.spent_balance` into the `TreasuryCap`. This - /// action is only available to the `TreasuryCap` owner. - public fun flush( - self: &mut TokenPolicy, - cap: &mut TreasuryCap, - _ctx: &mut TxContext - ): u64 { - let amount = self.spent_balance.value(); - let balance = self.spent_balance.split(amount); - cap.supply_mut().decrease_supply(balance) - } +/// Flush the `TokenPolicy.spent_balance` into the `TreasuryCap`. This +/// action is only available to the `TreasuryCap` owner. +public fun flush( + self: &mut TokenPolicy, + cap: &mut TreasuryCap, + _ctx: &mut TxContext, +): u64 { + let amount = self.spent_balance.value(); + let balance = self.spent_balance.split(amount); + cap.supply_mut().decrease_supply(balance) +} - // === Getters: `TokenPolicy` and `Token` === +// === Getters: `TokenPolicy` and `Token` === - /// Check whether an action is present in the rules VecMap. - public fun is_allowed(self: &TokenPolicy, action: &String): bool { - self.rules.contains(action) - } +/// Check whether an action is present in the rules VecMap. +public fun is_allowed(self: &TokenPolicy, action: &String): bool { + self.rules.contains(action) +} - /// Returns the rules required for a specific action. - public fun rules( - self: &TokenPolicy, action: &String - ): VecSet { - *self.rules.get(action) - } +/// Returns the rules required for a specific action. +public fun rules(self: &TokenPolicy, action: &String): VecSet { + *self.rules.get(action) +} - /// Returns the `spent_balance` of the `TokenPolicy`. - public fun spent_balance(self: &TokenPolicy): u64 { - self.spent_balance.value() - } +/// Returns the `spent_balance` of the `TokenPolicy`. +public fun spent_balance(self: &TokenPolicy): u64 { + self.spent_balance.value() +} - /// Returns the `balance` of the `Token`. - public fun value(t: &Token): u64 { - t.balance.value() - } +/// Returns the `balance` of the `Token`. +public fun value(t: &Token): u64 { + t.balance.value() +} - // === Action Names === +// === Action Names === - /// Name of the Transfer action. - public fun transfer_action(): String { - let transfer_str = TRANSFER; - transfer_str.to_string() - } +/// Name of the Transfer action. +public fun transfer_action(): String { + let transfer_str = TRANSFER; + transfer_str.to_string() +} - /// Name of the `Spend` action. - public fun spend_action(): String { - let spend_str = SPEND; - spend_str.to_string() - } +/// Name of the `Spend` action. +public fun spend_action(): String { + let spend_str = SPEND; + spend_str.to_string() +} - /// Name of the `ToCoin` action. - public fun to_coin_action(): String { - let to_coin_str = TO_COIN; - to_coin_str.to_string() - } +/// Name of the `ToCoin` action. +public fun to_coin_action(): String { + let to_coin_str = TO_COIN; + to_coin_str.to_string() +} - /// Name of the `FromCoin` action. - public fun from_coin_action(): String { - let from_coin_str = FROM_COIN; - from_coin_str.to_string() - } +/// Name of the `FromCoin` action. +public fun from_coin_action(): String { + let from_coin_str = FROM_COIN; + from_coin_str.to_string() +} - // === Action Request Fields == +// === Action Request Fields == - /// The Action in the `ActionRequest`. - public fun action(self: &ActionRequest): String { self.name } +/// The Action in the `ActionRequest`. +public fun action(self: &ActionRequest): String { self.name } - /// Amount of the `ActionRequest`. - public fun amount(self: &ActionRequest): u64 { self.amount } +/// Amount of the `ActionRequest`. +public fun amount(self: &ActionRequest): u64 { self.amount } - /// Sender of the `ActionRequest`. - public fun sender(self: &ActionRequest): address { self.sender } +/// Sender of the `ActionRequest`. +public fun sender(self: &ActionRequest): address { self.sender } - /// Recipient of the `ActionRequest`. - public fun recipient(self: &ActionRequest): Option
{ - self.recipient - } +/// Recipient of the `ActionRequest`. +public fun recipient(self: &ActionRequest): Option
{ + self.recipient +} - /// Approvals of the `ActionRequest`. - public fun approvals(self: &ActionRequest): VecSet { - self.approvals - } +/// Approvals of the `ActionRequest`. +public fun approvals(self: &ActionRequest): VecSet { + self.approvals +} - /// Burned balance of the `ActionRequest`. - public fun spent(self: &ActionRequest): Option { - if (self.spent_balance.is_some()) { - option::some(self.spent_balance.borrow().value()) - } else { - option::none() - } +/// Burned balance of the `ActionRequest`. +public fun spent(self: &ActionRequest): Option { + if (self.spent_balance.is_some()) { + option::some(self.spent_balance.borrow().value()) + } else { + option::none() } +} - // === Internal === +// === Internal === - /// Create a new `RuleKey` for a `Rule`. The `is_protected` field is kept - /// for potential future use, if Rules were to have a freely modifiable - /// storage as addition / replacement for the `Config` system. - /// - /// The goal of `is_protected` is to potentially allow Rules store a mutable - /// version of their configuration and mutate state on user action. - fun key(): RuleKey { RuleKey { is_protected: true } } - - // === Testing === - - #[test_only] - public fun new_policy_for_testing( - ctx: &mut TxContext - ): (TokenPolicy, TokenPolicyCap) { - let policy = TokenPolicy { - id: object::new(ctx), - rules: vec_map::empty(), - spent_balance: balance::zero(), - }; - let cap = TokenPolicyCap { - id: object::new(ctx), - `for`: object::id(&policy) - }; - - (policy, cap) - } +/// Create a new `RuleKey` for a `Rule`. The `is_protected` field is kept +/// for potential future use, if Rules were to have a freely modifiable +/// storage as addition / replacement for the `Config` system. +/// +/// The goal of `is_protected` is to potentially allow Rules store a mutable +/// version of their configuration and mutate state on user action. +fun key(): RuleKey { RuleKey { is_protected: true } } + +// === Testing === + +#[test_only] +public fun new_policy_for_testing(ctx: &mut TxContext): (TokenPolicy, TokenPolicyCap) { + let policy = TokenPolicy { + id: object::new(ctx), + rules: vec_map::empty(), + spent_balance: balance::zero(), + }; + let cap = TokenPolicyCap { + id: object::new(ctx), + `for`: object::id(&policy), + }; + + (policy, cap) +} - #[test_only] - public fun burn_policy_for_testing( - policy: TokenPolicy, - cap: TokenPolicyCap - ) { - let TokenPolicyCap { id: cap_id, `for`: _ } = cap; - let TokenPolicy { id, rules: _, spent_balance } = policy; - spent_balance.destroy_for_testing(); - cap_id.delete(); - id.delete(); - } +#[test_only] +public fun burn_policy_for_testing(policy: TokenPolicy, cap: TokenPolicyCap) { + let TokenPolicyCap { id: cap_id, `for`: _ } = cap; + let TokenPolicy { id, rules: _, spent_balance } = policy; + spent_balance.destroy_for_testing(); + cap_id.delete(); + id.delete(); +} - #[test_only] - public fun mint_for_testing(amount: u64, ctx: &mut TxContext): Token { - let balance = balance::create_for_testing(amount); - Token { id: object::new(ctx), balance } - } +#[test_only] +public fun mint_for_testing(amount: u64, ctx: &mut TxContext): Token { + let balance = balance::create_for_testing(amount); + Token { id: object::new(ctx), balance } +} - #[test_only] - public fun burn_for_testing(token: Token) { - let Token { id, balance } = token; - balance.destroy_for_testing(); - id.delete(); - } +#[test_only] +public fun burn_for_testing(token: Token) { + let Token { id, balance } = token; + balance.destroy_for_testing(); + id.delete(); } diff --git a/crates/sui-framework/packages/sui-framework/sources/transfer.move b/crates/sui-framework/packages/sui-framework/sources/transfer.move index 7c603da4c9f64..dfa1db2046e2e 100644 --- a/crates/sui-framework/packages/sui-framework/sources/transfer.move +++ b/crates/sui-framework/packages/sui-framework/sources/transfer.move @@ -2,147 +2,136 @@ // SPDX-License-Identifier: Apache-2.0 #[allow(unused_const)] -module sui::transfer { - - /// This represents the ability to `receive` an object of type `T`. - /// This type is ephemeral per-transaction and cannot be stored on-chain. - /// This does not represent the obligation to receive the object that it - /// references, but simply the ability to receive the object with object ID - /// `id` at version `version` if you can prove mutable access to the parent - /// object during the transaction. - /// Internals of this struct are opaque outside this module. - public struct Receiving has drop { - id: ID, - version: u64, - } - - /// Shared an object that was previously created. Shared objects must currently - /// be constructed in the transaction they are created. - const ESharedNonNewObject: u64 = 0; - - #[allow(unused_const)] - /// Serialization of the object failed. - const EBCSSerializationFailure: u64 = 1; - - #[allow(unused_const)] - /// The object being received is not of the expected type. - const EReceivingObjectTypeMismatch: u64 = 2; - - #[allow(unused_const)] - /// Represents both the case where the object does not exist and the case where the object is not - /// able to be accessed through the parent that is passed-in. - const EUnableToReceiveObject: u64 = 3; - - #[allow(unused_const)] - /// Shared object operations such as wrapping, freezing, and converting to owned are not allowed. - const ESharedObjectOperationNotSupported: u64 = 4; - - - /// Transfer ownership of `obj` to `recipient`. `obj` must have the `key` attribute, - /// which (in turn) ensures that `obj` has a globally unique ID. Note that if the recipient - /// address represents an object ID, the `obj` sent will be inaccessible after the transfer - /// (though they will be retrievable at a future date once new features are added). - /// This function has custom rules performed by the Sui Move bytecode verifier that ensures - /// that `T` is an object defined in the module where `transfer` is invoked. Use - /// `public_transfer` to transfer an object with `store` outside of its module. - public fun transfer(obj: T, recipient: address) { - transfer_impl(obj, recipient) - } - - /// Transfer ownership of `obj` to `recipient`. `obj` must have the `key` attribute, - /// which (in turn) ensures that `obj` has a globally unique ID. Note that if the recipient - /// address represents an object ID, the `obj` sent will be inaccessible after the transfer - /// (though they will be retrievable at a future date once new features are added). - /// The object must have `store` to be transferred outside of its module. - public fun public_transfer(obj: T, recipient: address) { - transfer_impl(obj, recipient) - } - - /// Freeze `obj`. After freezing `obj` becomes immutable and can no longer be transferred or - /// mutated. - /// This function has custom rules performed by the Sui Move bytecode verifier that ensures - /// that `T` is an object defined in the module where `freeze_object` is invoked. Use - /// `public_freeze_object` to freeze an object with `store` outside of its module. - public fun freeze_object(obj: T) { - freeze_object_impl(obj) - } - - /// Freeze `obj`. After freezing `obj` becomes immutable and can no longer be transferred or - /// mutated. - /// The object must have `store` to be frozen outside of its module. - public fun public_freeze_object(obj: T) { - freeze_object_impl(obj) - } - - /// Turn the given object into a mutable shared object that everyone can access and mutate. - /// This is irreversible, i.e. once an object is shared, it will stay shared forever. - /// Aborts with `ESharedNonNewObject` of the object being shared was not created in this - /// transaction. This restriction may be relaxed in the future. - /// This function has custom rules performed by the Sui Move bytecode verifier that ensures - /// that `T` is an object defined in the module where `share_object` is invoked. Use - /// `public_share_object` to share an object with `store` outside of its module. - public fun share_object(obj: T) { - share_object_impl(obj) - } - - /// Turn the given object into a mutable shared object that everyone can access and mutate. - /// This is irreversible, i.e. once an object is shared, it will stay shared forever. - /// Aborts with `ESharedNonNewObject` of the object being shared was not created in this - /// transaction. This restriction may be relaxed in the future. - /// The object must have `store` to be shared outside of its module. - public fun public_share_object(obj: T) { - share_object_impl(obj) - } - - /// Given mutable (i.e., locked) access to the `parent` and a `Receiving` argument - /// referencing an object of type `T` owned by `parent` use the `to_receive` - /// argument to receive and return the referenced owned object of type `T`. - /// This function has custom rules performed by the Sui Move bytecode verifier that ensures - /// that `T` is an object defined in the module where `receive` is invoked. Use - /// `public_receive` to receivne an object with `store` outside of its module. - public fun receive(parent: &mut UID, to_receive: Receiving): T { - let Receiving { - id, - version, - } = to_receive; - receive_impl(parent.to_address(), id, version) - } - - /// Given mutable (i.e., locked) access to the `parent` and a `Receiving` argument - /// referencing an object of type `T` owned by `parent` use the `to_receive` - /// argument to receive and return the referenced owned object of type `T`. - /// The object must have `store` to be received outside of its defining module. - public fun public_receive(parent: &mut UID, to_receive: Receiving): T { - let Receiving { - id, - version, - } = to_receive; - receive_impl(parent.to_address(), id, version) - } - - /// Return the object ID that the given `Receiving` argument references. - public fun receiving_object_id(receiving: &Receiving): ID { - receiving.id - } - - public(package) native fun freeze_object_impl(obj: T); - - public(package) native fun share_object_impl(obj: T); - - public(package) native fun transfer_impl(obj: T, recipient: address); - - native fun receive_impl(parent: address, to_receive: ID, version: u64): T; - - #[test_only] - public(package) fun make_receiver(id: ID, version: u64): Receiving { - Receiving { - id, - version, - } - } - - #[test_only] - public(package) fun receiving_id(r: &Receiving): ID { - r.id - } +module sui::transfer; + +/// This represents the ability to `receive` an object of type `T`. +/// This type is ephemeral per-transaction and cannot be stored on-chain. +/// This does not represent the obligation to receive the object that it +/// references, but simply the ability to receive the object with object ID +/// `id` at version `version` if you can prove mutable access to the parent +/// object during the transaction. +/// Internals of this struct are opaque outside this module. +public struct Receiving has drop { + id: ID, + version: u64, +} + +/// Shared an object that was previously created. Shared objects must currently +/// be constructed in the transaction they are created. +const ESharedNonNewObject: u64 = 0; + +#[allow(unused_const)] +/// Serialization of the object failed. +const EBCSSerializationFailure: u64 = 1; + +#[allow(unused_const)] +/// The object being received is not of the expected type. +const EReceivingObjectTypeMismatch: u64 = 2; + +#[allow(unused_const)] +/// Represents both the case where the object does not exist and the case where the object is not +/// able to be accessed through the parent that is passed-in. +const EUnableToReceiveObject: u64 = 3; + +#[allow(unused_const)] +/// Shared object operations such as wrapping, freezing, and converting to owned are not allowed. +const ESharedObjectOperationNotSupported: u64 = 4; + +/// Transfer ownership of `obj` to `recipient`. `obj` must have the `key` attribute, +/// which (in turn) ensures that `obj` has a globally unique ID. Note that if the recipient +/// address represents an object ID, the `obj` sent will be inaccessible after the transfer +/// (though they will be retrievable at a future date once new features are added). +/// This function has custom rules performed by the Sui Move bytecode verifier that ensures +/// that `T` is an object defined in the module where `transfer` is invoked. Use +/// `public_transfer` to transfer an object with `store` outside of its module. +public fun transfer(obj: T, recipient: address) { + transfer_impl(obj, recipient) +} + +/// Transfer ownership of `obj` to `recipient`. `obj` must have the `key` attribute, +/// which (in turn) ensures that `obj` has a globally unique ID. Note that if the recipient +/// address represents an object ID, the `obj` sent will be inaccessible after the transfer +/// (though they will be retrievable at a future date once new features are added). +/// The object must have `store` to be transferred outside of its module. +public fun public_transfer(obj: T, recipient: address) { + transfer_impl(obj, recipient) +} + +/// Freeze `obj`. After freezing `obj` becomes immutable and can no longer be transferred or +/// mutated. +/// This function has custom rules performed by the Sui Move bytecode verifier that ensures +/// that `T` is an object defined in the module where `freeze_object` is invoked. Use +/// `public_freeze_object` to freeze an object with `store` outside of its module. +public fun freeze_object(obj: T) { + freeze_object_impl(obj) +} + +/// Freeze `obj`. After freezing `obj` becomes immutable and can no longer be transferred or +/// mutated. +/// The object must have `store` to be frozen outside of its module. +public fun public_freeze_object(obj: T) { + freeze_object_impl(obj) +} + +/// Turn the given object into a mutable shared object that everyone can access and mutate. +/// This is irreversible, i.e. once an object is shared, it will stay shared forever. +/// Aborts with `ESharedNonNewObject` of the object being shared was not created in this +/// transaction. This restriction may be relaxed in the future. +/// This function has custom rules performed by the Sui Move bytecode verifier that ensures +/// that `T` is an object defined in the module where `share_object` is invoked. Use +/// `public_share_object` to share an object with `store` outside of its module. +public fun share_object(obj: T) { + share_object_impl(obj) +} + +/// Turn the given object into a mutable shared object that everyone can access and mutate. +/// This is irreversible, i.e. once an object is shared, it will stay shared forever. +/// Aborts with `ESharedNonNewObject` of the object being shared was not created in this +/// transaction. This restriction may be relaxed in the future. +/// The object must have `store` to be shared outside of its module. +public fun public_share_object(obj: T) { + share_object_impl(obj) +} + +/// Given mutable (i.e., locked) access to the `parent` and a `Receiving` argument +/// referencing an object of type `T` owned by `parent` use the `to_receive` +/// argument to receive and return the referenced owned object of type `T`. +/// This function has custom rules performed by the Sui Move bytecode verifier that ensures +/// that `T` is an object defined in the module where `receive` is invoked. Use +/// `public_receive` to receivne an object with `store` outside of its module. +public fun receive(parent: &mut UID, to_receive: Receiving): T { + let Receiving { id, version } = to_receive; + receive_impl(parent.to_address(), id, version) +} + +/// Given mutable (i.e., locked) access to the `parent` and a `Receiving` argument +/// referencing an object of type `T` owned by `parent` use the `to_receive` +/// argument to receive and return the referenced owned object of type `T`. +/// The object must have `store` to be received outside of its defining module. +public fun public_receive(parent: &mut UID, to_receive: Receiving): T { + let Receiving { id, version } = to_receive; + receive_impl(parent.to_address(), id, version) +} + +/// Return the object ID that the given `Receiving` argument references. +public fun receiving_object_id(receiving: &Receiving): ID { + receiving.id +} + +public(package) native fun freeze_object_impl(obj: T); + +public(package) native fun share_object_impl(obj: T); + +public(package) native fun transfer_impl(obj: T, recipient: address); + +native fun receive_impl(parent: address, to_receive: ID, version: u64): T; + +#[test_only] +public(package) fun make_receiver(id: ID, version: u64): Receiving { + Receiving { id, version } +} + +#[test_only] +public(package) fun receiving_id(r: &Receiving): ID { + r.id } diff --git a/crates/sui-framework/packages/sui-framework/tests/balance_tests.move b/crates/sui-framework/packages/sui-framework/tests/balance_tests.move index ca2f11a5cbd79..bb2ce02ed4692 100644 --- a/crates/sui-framework/packages/sui-framework/tests/balance_tests.move +++ b/crates/sui-framework/packages/sui-framework/tests/balance_tests.move @@ -8,6 +8,8 @@ module sui::coin_balance_tests { use sui::coin; use sui::balance; use sui::sui::SUI; + use sui::test_utils; + #[test] fun type_morphing() { @@ -35,4 +37,28 @@ module sui::coin_balance_tests { pay::keep(coin, scenario.ctx()); scenario.end(); } + + #[test] + fun test_balance() { + let mut balance = balance::zero(); + let another = balance::create_for_testing(1000); + + balance.join(another); + + assert!(balance.value() == 1000); + + let balance1 = balance.split(333); + let balance2 = balance.split(333); + let balance3 = balance.split(334); + + balance.destroy_zero(); + + assert!(balance1.value() == 333); + assert!(balance2.value() == 333); + assert!(balance3.value() == 334); + + test_utils::destroy(balance1); + test_utils::destroy(balance2); + test_utils::destroy(balance3); + } }