Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(iota-framework): Display object extension #3861

Closed
wants to merge 12 commits into from
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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>()
miker83z marked this conversation as resolved.
Show resolved Hide resolved
}
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 {
valeriyr marked this conversation as resolved.
Show resolved Hide resolved

use std::string::String;

use iota::display::{Self, Display};

use iota_system::iota_system::IotaSystemState;

/// Create an empty `Display` object with `SystemDisplayCap`.
public(package) fun system_new<T: key>(
iota_system: &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(package) fun system_new_with_fields<T: key>(
iota_system: &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
Loading