diff --git a/crates/iota-framework/packages/iota-framework/sources/display.move b/crates/iota-framework/packages/iota-framework/sources/display.move index bf6cc92765a..80008d0c471 100644 --- a/crates/iota-framework/packages/iota-framework/sources/display.move +++ b/crates/iota-framework/packages/iota-framework/sources/display.move @@ -2,13 +2,13 @@ // Modifications Copyright (c) 2024 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -/// Defines a Display struct which defines the way an Object +/// Defines a `Display` struct which defines the way an Object /// should be displayed. The intention is to keep data as independent /// from its display as possible, protecting the development process /// and keeping it separate from the ecosystem agreements. /// -/// Each of the fields of the Display object should allow for pattern -/// substitution and filling-in the pieces using the data from the object T. +/// Each of the fields of the `Display` object should allow for pattern +/// substitution and filling-in the pieces using the data from the object `T`. /// /// More entry functions might be added in the future depending on the use cases. module iota::display { @@ -17,20 +17,20 @@ module iota::display { use iota::event; use std::string::String; - /// For when T does not belong to the package `Publisher`. + /// For when `T` does not belong to the package `Publisher`. const ENotOwner: u64 = 0; /// For when vectors passed into one of the multiple insert functions /// don't match in their lengths. const EVecLengthMismatch: u64 = 1; - /// The Display object. Defines the way a T instance should be - /// displayed. Display object can only be created and modified with - /// a PublisherCap, making sure that the rules are set by the owner - /// of the type. + /// The `Display` object. Defines the way a `T` instance should be + /// displayed. `Display` object can only be created and modified with + /// ether a `Publisher` cap or `SystemDisplayCap`, making sure that + /// the rules are set by the owner of the type or the system object. /// /// Each of the display properties should support patterns outside - /// of the system, making it simpler to customize Display based + /// of the system, making it simpler to customize `Display` based /// on the property values of an Object. /// ``` /// // Example of a display object @@ -43,64 +43,72 @@ module iota::display { /// } /// ``` /// - /// Uses only String type due to external-facing nature of the object, + /// Uses only `String` type due to external-facing nature of the object, /// the property names have a priority over their types. public struct Display has key, store { id: UID, /// Contains fields for display. Currently supported /// fields are: name, link, image and description. fields: VecMap, - /// Version that can only be updated manually by the Publisher. + /// Version that can only be updated manually by the `Publisher`. version: u16 } - /// Event: emitted when a new Display object has been created for type T. + /// Event: emitted when a new `Display` object has been created for type `T`. /// Type signature of the event corresponds to the type while id serves for /// the discovery. /// - /// Since Iota RPC supports querying events by type, finding a Display for the T + /// Since Iota RPC supports querying events by type, finding a `Display` for the `T` /// would be as simple as looking for the first event with `Display`. public struct DisplayCreated has copy, drop { id: ID } - /// Version of Display got updated - + /// Version of `Display` got updated. public struct VersionUpdated has copy, drop { id: ID, version: u16, fields: VecMap, } + /// `SystemDisplayCap` allows to create a `Display` object. + public struct SystemDisplayCap has store {} + // === Initializer Methods === - /// Create an empty Display object. It can either be shared empty or filled - /// with data right away via cheaper `set_owned` method. + /// Create an empty `Display` object with `Publisher`. public fun new(pub: &Publisher, ctx: &mut TxContext): Display { assert!(is_authorized(pub), ENotOwner); create_internal(ctx) } - /// Create a new Display object with a set of fields. + /// Create a new `Display` object with a set of fields using `Publisher`. public fun new_with_fields( pub: &Publisher, fields: vector, values: vector, ctx: &mut TxContext ): Display { - let len = fields.length(); - assert!(len == values.length(), EVecLengthMismatch); - - let mut i = 0; let mut display = new(pub, ctx); - while (i < len) { - display.add_internal(fields[i], values[i]); - i = i + 1; - }; + add_multiple(&mut display, fields, values); + display + } + /// Create an empty `Display` object with `SystemDisplayCap`. + public fun system_new(_: &SystemDisplayCap, ctx: &mut TxContext): Display { + create_internal(ctx) + } + + /// Create a new Display object with a set of fields using `SystemDisplayCap`. + public fun system_new_with_fields( + cap: &SystemDisplayCap, fields: vector, values: vector, ctx: &mut TxContext + ): Display { + let mut display = system_new(cap, ctx); + add_multiple(&mut display, fields, values); display } // === Entry functions: Create === #[allow(lint(self_transfer))] - /// Create a new empty Display object and keep it. + /// Create a new empty `Display` object and keep it. entry public fun create_and_keep(pub: &Publisher, ctx: &mut TxContext) { transfer::public_transfer(new(pub, ctx), ctx.sender()) } @@ -145,14 +153,14 @@ module iota::display { self.add_internal(name, value) } - /// Remove the key from the Display. + /// Remove the key from the `Display`. entry public fun remove(self: &mut Display, name: String) { self.fields.remove(&name); } // === Access fields === - /// Authorization check; can be performed externally to implement protection rules for Display. + /// Authorization check; can be performed externally to implement protection rules for `Display`. public fun is_authorized(pub: &Publisher): bool { pub.from_package() } @@ -188,14 +196,23 @@ module iota::display { fun add_internal(display: &mut Display, name: String, value: String) { display.fields.insert(name, value) } + + #[test_only] + /// Create a `SystemDisplayCap` for testing purposes. + public fun new_system_display_cap_for_testing(): SystemDisplayCap { + SystemDisplayCap { } + } } #[test_only] module iota::display_tests { - use iota::test_scenario as test; use std::string::String; - use iota::package; + use iota::display; + use iota::package; + + use iota::test_scenario as test; + use iota::test_utils; #[allow(unused_field)] /// An example object. @@ -225,4 +242,23 @@ module iota::display_tests { transfer::public_transfer(display, @0x2); test.end(); } + + #[test] + fun nft_test_sys_init() { + let mut test = test::begin(@0x2); + let cap = display::new_system_display_cap_for_testing(); + + // create a new display object + let mut display = display::system_new(&cap, test.ctx()); + + display.add(b"name".to_string(), b"IOTEST Nft {name}".to_string()); + display.add(b"link".to_string(), b"https://iotestnft.com/nft/{id}".to_string()); + display.add(b"image".to_string(), b"https://api.iotestnft.com/nft/{id}/svg".to_string()); + display.add(b"description".to_string(), b"One of many Iotest Nfts".to_string()); + + transfer::public_transfer(display, @0x2); + + test_utils::destroy(cap); + test.end(); + } } diff --git a/crates/iota-framework/packages/iota-system/sources/genesis.move b/crates/iota-framework/packages/iota-system/sources/genesis.move index 804298911cb..e6e3bb74e68 100644 --- a/crates/iota-framework/packages/iota-system/sources/genesis.move +++ b/crates/iota-framework/packages/iota-system/sources/genesis.move @@ -7,6 +7,7 @@ module iota_system::genesis { use std::string::String; use iota::balance; + use iota::display::SystemDisplayCap; use iota::iota::{Self, IotaTreasuryCap}; use iota::timelock::SystemTimelockCap; use iota_system::iota_system; @@ -86,6 +87,7 @@ module iota_system::genesis { token_distribution_schedule: TokenDistributionSchedule, timelock_genesis_label: Option, system_timelock_cap: SystemTimelockCap, + system_display_cap: SystemDisplayCap, ctx: &mut TxContext, ) { // Ensure this is only called at genesis @@ -185,6 +187,7 @@ module iota_system::genesis { genesis_chain_parameters.chain_start_timestamp_ms, system_parameters, system_timelock_cap, + system_display_cap, ctx, ); } diff --git a/crates/iota-framework/packages/iota-system/sources/iota_system.move b/crates/iota-framework/packages/iota-system/sources/iota_system.move index 09da71387a4..5b51e63471d 100644 --- a/crates/iota-framework/packages/iota-system/sources/iota_system.move +++ b/crates/iota-framework/packages/iota-system/sources/iota_system.move @@ -43,6 +43,7 @@ module iota_system::iota_system { use iota::balance::Balance; use iota::coin::Coin; + use iota::display::SystemDisplayCap; use iota_system::staking_pool::StakedIota; use iota::iota::{IOTA, IotaTreasuryCap}; use iota::table::Table; @@ -67,6 +68,7 @@ module iota_system::iota_system { const EWrongInnerVersion: u64 = 1; const SYSTEM_TIMELOCK_CAP_DF_KEY: vector = b"sys_timelock_cap"; + const SYSTEM_DISPLAY_CAP_DF_KEY: vector = b"sys_display_cap"; // ==== functions that can only be called by genesis ==== @@ -81,6 +83,7 @@ module iota_system::iota_system { epoch_start_timestamp_ms: u64, parameters: SystemParametersV1, system_timelock_cap: SystemTimelockCap, + system_display_cap: SystemDisplayCap, ctx: &mut TxContext, ) { let system_state = iota_system_state_inner::create( @@ -99,6 +102,7 @@ module iota_system::iota_system { }; dynamic_field::add(&mut self.id, version, system_state); dynamic_field::add(&mut self.id, SYSTEM_TIMELOCK_CAP_DF_KEY, system_timelock_cap); + dynamic_field::add(&mut self.id, SYSTEM_DISPLAY_CAP_DF_KEY, system_display_cap); transfer::share_object(self); } @@ -569,6 +573,13 @@ module iota_system::iota_system { ) } + public(package) fun load_system_display_cap(self: &IotaSystemState): &SystemDisplayCap { + dynamic_field::borrow( + &self.id, + SYSTEM_DISPLAY_CAP_DF_KEY + ) + } + #[allow(unused_function)] /// Returns the voting power of the active validators, values are voting power in the scale of 10000. fun validator_voting_powers(wrapper: &mut IotaSystemState): VecMap { diff --git a/crates/iota-framework/packages/iota-system/sources/iota_system_display.move b/crates/iota-framework/packages/iota-system/sources/iota_system_display.move new file mode 100644 index 00000000000..bebbe6adc08 --- /dev/null +++ b/crates/iota-framework/packages/iota-system/sources/iota_system_display.move @@ -0,0 +1,37 @@ +// Copyright (c) 2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +module iota_system::iota_system_display { + + use std::string::String; + + use iota::display::{Self, Display}; + + use iota_system::iota_system::IotaSystemState; + + /// Create an empty `Display` object with `SystemDisplayCap`. + public fun system_new( + iota_system: &mut IotaSystemState, + ctx: &mut TxContext + ): Display { + // Load the `SystemDisplayCap` instance. + let sys_display_cap = iota_system.load_system_display_cap(); + + // Create a `Display` object. + display::system_new(sys_display_cap, ctx) + } + + /// Create a new Display object with a set of fields using `SystemDisplayCap`. + public fun system_new_with_fields( + iota_system: &mut IotaSystemState, + fields: vector, + values: vector, + ctx: &mut TxContext + ): Display { + // Load the `SystemDisplayCap` instance. + let sys_display_cap = iota_system.load_system_display_cap(); + + // Create a `Display` object with fields. + display::system_new_with_fields(sys_display_cap, fields, values, ctx) + } +} diff --git a/crates/iota-framework/packages/iota-system/tests/governance_test_utils.move b/crates/iota-framework/packages/iota-system/tests/governance_test_utils.move index 97d3af016ca..c2c2005ea91 100644 --- a/crates/iota-framework/packages/iota-system/tests/governance_test_utils.move +++ b/crates/iota-framework/packages/iota-system/tests/governance_test_utils.move @@ -6,6 +6,7 @@ module iota_system::governance_test_utils { use iota::address; use iota::balance; + use iota::display; use iota::iota::{Self, IOTA}; use iota::coin::{Self, Coin}; use iota_system::staking_pool::{StakedIota, StakingPoolV1}; @@ -95,6 +96,7 @@ module iota_system::governance_test_utils { 0, // chain_start_timestamp_ms system_parameters, timelock::new_system_timelock_cap_for_testing(), + display::new_system_display_cap_for_testing(), ctx, ) }