Skip to content

Commit

Permalink
feat(iota-framework): added and integrated SystemDisplayCap
Browse files Browse the repository at this point in the history
  • Loading branch information
valeriyr committed Nov 4, 2024
1 parent bf5189f commit a3b474e
Show file tree
Hide file tree
Showing 5 changed files with 119 additions and 30 deletions.
96 changes: 66 additions & 30 deletions crates/iota-framework/packages/iota-framework/sources/display.move
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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<T> 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<T>` 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
Expand All @@ -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<phantom T: key> has key, store {
id: UID,
/// Contains fields for display. Currently supported
/// fields are: name, link, image and description.
fields: VecMap<String, String>,
/// 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<T>`.
public struct DisplayCreated<phantom T: key> has copy, drop {
id: ID
}

/// Version of Display got updated -
/// Version of `Display` got updated.
public struct VersionUpdated<phantom T: key> has copy, drop {
id: ID,
version: u16,
fields: VecMap<String, String>,
}

/// `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<T: key>(pub: &Publisher, ctx: &mut TxContext): Display<T> {
assert!(is_authorized<T>(pub), ENotOwner);
create_internal(ctx)
}

/// Create a new Display<T> object with a set of fields.
/// Create a new `Display<T>` object with a set of fields using `Publisher`.
public fun new_with_fields<T: key>(
pub: &Publisher, fields: vector<String>, values: vector<String>, ctx: &mut TxContext
): Display<T> {
let len = fields.length();
assert!(len == values.length(), EVecLengthMismatch);

let mut i = 0;
let mut display = new<T>(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<T: key>(_: &SystemDisplayCap, ctx: &mut TxContext): Display<T> {
create_internal(ctx)
}

/// Create a new Display<T> object with a set of fields using `SystemDisplayCap`.
public fun system_new_with_fields<T: key>(
cap: &SystemDisplayCap, fields: vector<String>, values: vector<String>, ctx: &mut TxContext
): Display<T> {
let mut display = system_new<T>(cap, ctx);
add_multiple(&mut display, fields, values);
display
}

// === Entry functions: Create ===

#[allow(lint(self_transfer))]
/// Create a new empty Display<T> object and keep it.
/// Create a new empty `Display<T>` object and keep it.
entry public fun create_and_keep<T: key>(pub: &Publisher, ctx: &mut TxContext) {
transfer::public_transfer(new<T>(pub, ctx), ctx.sender())
}
Expand Down Expand Up @@ -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<T: key>(self: &mut Display<T>, 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<T: key>(pub: &Publisher): bool {
pub.from_package<T>()
}
Expand Down Expand Up @@ -188,14 +196,23 @@ module iota::display {
fun add_internal<T: key>(display: &mut Display<T>, 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.
Expand Down Expand Up @@ -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<IotestNft>(&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();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -86,6 +87,7 @@ module iota_system::genesis {
token_distribution_schedule: TokenDistributionSchedule,
timelock_genesis_label: Option<String>,
system_timelock_cap: SystemTimelockCap,
system_display_cap: SystemDisplayCap,
ctx: &mut TxContext,
) {
// Ensure this is only called at genesis
Expand Down Expand Up @@ -185,6 +187,7 @@ module iota_system::genesis {
genesis_chain_parameters.chain_start_timestamp_ms,
system_parameters,
system_timelock_cap,
system_display_cap,
ctx,
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -67,6 +68,7 @@ module iota_system::iota_system {
const EWrongInnerVersion: u64 = 1;

const SYSTEM_TIMELOCK_CAP_DF_KEY: vector<u8> = b"sys_timelock_cap";
const SYSTEM_DISPLAY_CAP_DF_KEY: vector<u8> = b"sys_display_cap";

// ==== functions that can only be called by genesis ====

Expand All @@ -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(
Expand All @@ -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);
}

Expand Down Expand Up @@ -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<address, u64> {
Expand Down
Original file line number Diff line number Diff line change
@@ -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<T: key>(
iota_system: &mut IotaSystemState,
ctx: &mut TxContext
): Display<T> {
// Load the `SystemDisplayCap` instance.
let sys_display_cap = iota_system.load_system_display_cap();

// Create a `Display` object.
display::system_new<T>(sys_display_cap, ctx)
}

/// Create a new Display<T> object with a set of fields using `SystemDisplayCap`.
public fun system_new_with_fields<T: key>(
iota_system: &mut IotaSystemState,
fields: vector<String>,
values: vector<String>,
ctx: &mut TxContext
): Display<T> {
// 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<T>(sys_display_cap, fields, values, ctx)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -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,
)
}
Expand Down

0 comments on commit a3b474e

Please sign in to comment.