Skip to content

Commit

Permalink
feat(sui-genesis-builder): migrate Alias Outputs (#163)
Browse files Browse the repository at this point in the history
* feat(sui-genesis-builder): store TypeOrigin of native tokens

While creating foundries now maps the `(ObjectID, TypeOrigin)` to the
`TokenId`.

* refactor(sui-genesis-builder): split stardust::types module

* feat(sui-genesis-builder): add unlock types

* feat(sui-genesis-builder): add BasicOutput type

* refactor(sui-genesis-builder): use OutputHeader while creating outputs

* feat(sui-genesis-builder): implement stardust::migration::Executor::create_basic_objects

* fixup! feat(sui-genesis-builder): implement stardust::migration::Executor::create_basic_objects

* fixup! fixup! feat(sui-genesis-builder): implement stardust::migration::Executor::create_basic_objects

* feat(sui-genesis-builder): fix native-token object ids during migration

* fixup! fixup! fixup! feat(sui-genesis-builder): implement stardust::migration::Executor::create_basic_objects

* fix(sui-genesis-builder): correct BasicOutput::type_ module and name

Co-authored-by: Philipp Gackstatter <[email protected]>

* fix(sui-genesis-builder): load packages and input objects correctly

* fix(sui-genesis-builder): dummy transfer Bag object

* Implement move data model in Rust

* Execute alias creation transaction

* Fix dependencies during PTB execution

* Fix alias tags, extend test

* Add TODO for dynamic object field

* Expose `attach_alias` function

* Fix outstanding alias migration TODOs

* Prettify alias migration test

* `cargo fmt` the genesis builder

* Make state controller non-optional

* Move alias migration test to separate file

* Cleanup alias migration test

* Add zeroized check and simplify match statement

* Add non-zeroed alias id test

* Use to_genesis_object approach

* Use fresh_id as the alias output ID

* Compute version of aliases via `lamport_timestamp`

* Move crate-level migration test to module

---------

Co-authored-by: Konstantinos Demartinos <[email protected]>
  • Loading branch information
PhilippGackstatter and kodemartin authored May 16, 2024
1 parent 775f8a3 commit 54b588c
Show file tree
Hide file tree
Showing 11 changed files with 452 additions and 15 deletions.
6 changes: 3 additions & 3 deletions crates/sui-framework/docs/stardust/alias.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ have to be received via this object once extracted from <code>AliasOutput</code>
This is the AliasID from Stardust.
</dd>
<dt>
<code>legacy_state_controller: <a href="../move-stdlib/option.md#0x1_option_Option">option::Option</a>&lt;<b>address</b>&gt;</code>
<code>legacy_state_controller: <b>address</b></code>
</dt>
<dd>
The last State Controller address assigned before the migration.
Expand Down Expand Up @@ -138,7 +138,7 @@ Destroy the <code><a href="alias.md#0x107a_alias_Alias">Alias</a></code> object,
Get the Alias's <code>legacy_state_controller</code>.


<pre><code><b>public</b> <b>fun</b> <a href="alias.md#0x107a_alias_legacy_state_controller">legacy_state_controller</a>(self: &<a href="alias.md#0x107a_alias_Alias">alias::Alias</a>): &<a href="../move-stdlib/option.md#0x1_option_Option">option::Option</a>&lt;<b>address</b>&gt;
<pre><code><b>public</b> <b>fun</b> <a href="alias.md#0x107a_alias_legacy_state_controller">legacy_state_controller</a>(self: &<a href="alias.md#0x107a_alias_Alias">alias::Alias</a>): &<b>address</b>
</code></pre>


Expand All @@ -147,7 +147,7 @@ Get the Alias's <code>legacy_state_controller</code>.
<summary>Implementation</summary>


<pre><code><b>public</b> <b>fun</b> <a href="alias.md#0x107a_alias_legacy_state_controller">legacy_state_controller</a>(self: &<a href="alias.md#0x107a_alias_Alias">Alias</a>): &Option&lt;<b>address</b>&gt; {
<pre><code><b>public</b> <b>fun</b> <a href="alias.md#0x107a_alias_legacy_state_controller">legacy_state_controller</a>(self: &<a href="alias.md#0x107a_alias_Alias">Alias</a>): &<b>address</b> {
&self.legacy_state_controller
}
</code></pre>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ module stardust::alias {
id: UID,

/// The last State Controller address assigned before the migration.
legacy_state_controller: Option<address>,
legacy_state_controller: address,
/// A counter increased by 1 every time the alias was state transitioned.
state_index: u32,
/// State metadata that can be used to store additional information.
Expand Down Expand Up @@ -47,7 +47,7 @@ module stardust::alias {
// === Public-Mutative Functions ===

/// Get the Alias's `legacy_state_controller`.
public fun legacy_state_controller(self: &Alias): &Option<address> {
public fun legacy_state_controller(self: &Alias): &address {
&self.legacy_state_controller
}

Expand Down Expand Up @@ -92,7 +92,7 @@ module stardust::alias {

#[test_only]
public fun create_for_testing(
legacy_state_controller: Option<address>,
legacy_state_controller: address,
state_index: u32,
state_metadata: Option<vector<u8>>,
sender: Option<address>,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ module stardust::alias_tests {

let alias = alias::create_for_testing(
// legacy state controller
option::some(owner),
owner,
// state index
0,
// state metadata
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ module stardust::address_unlock_condition_tests {

let alias = alias::create_for_testing(
// legacy state controller
option::some(owner),
owner,
// state index
0,
// state metadata
Expand Down
77 changes: 70 additions & 7 deletions crates/sui-genesis-builder/src/stardust/migration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ use sui_types::{
use anyhow::Result;
use fastcrypto::hash::HashFunction;
use iota_sdk::types::block::output::{
AliasOutput, BasicOutput, FoundryOutput, NativeTokens, NftOutput, Output, TokenId,
TreasuryOutput,
AliasOutput as StardustAlias, BasicOutput, FoundryOutput, NativeTokens, NftOutput, Output,
TokenId, TreasuryOutput,
};
use move_vm_runtime_v2::move_vm::MoveVM;
use sui_adapter_v2::{
Expand Down Expand Up @@ -46,7 +46,7 @@ use sui_types::{
MOVE_STDLIB_PACKAGE_ID, STARDUST_PACKAGE_ID, SUI_FRAMEWORK_PACKAGE_ID, SUI_SYSTEM_PACKAGE_ID,
};

use super::types::snapshot::OutputHeader;
use super::types::{snapshot::OutputHeader, stardust_to_sui_address_owner, Alias, AliasOutput};
use crate::process_package;
use crate::stardust::native_token::package_builder;
use crate::stardust::native_token::package_data::NativeTokenPackageData;
Expand Down Expand Up @@ -121,7 +121,7 @@ impl Migration {
outputs.sort_by_key(|(header, _)| (header.ms_timestamp(), header.output_id()));
for (header, output) in outputs {
match output {
Output::Alias(alias) => self.executor.create_alias_objects(alias)?,
Output::Alias(alias) => self.executor.create_alias_objects(header, alias)?,
Output::Basic(basic) => self.executor.create_basic_objects(header, basic)?,
Output::Nft(nft) => self.executor.create_nft_objects(nft)?,
Output::Treasury(treasury) => self.executor.create_treasury_objects(treasury)?,
Expand Down Expand Up @@ -213,6 +213,7 @@ impl Executor {
)?;
}
let move_vm = Arc::new(new_move_vm(all_natives(silent), &protocol_config, None)?);

Ok(Self {
protocol_config,
tx_context,
Expand Down Expand Up @@ -341,8 +342,69 @@ impl Executor {
Ok(())
}

fn create_alias_objects(&mut self, _alias: AliasOutput) -> Result<()> {
todo!();
fn create_alias_objects(&mut self, header: OutputHeader, alias: StardustAlias) -> Result<()> {
// Take the Alias ID set in the output or, if its zeroized, compute it from the Output ID.
let alias_id = ObjectID::new(*alias.alias_id().or_from_output_id(&header.output_id()));
let move_alias = Alias::try_from_stardust(alias_id, &alias)?;

// TODO: We should ensure that no circular ownership exists.
let alias_output_owner = stardust_to_sui_address_owner(alias.governor_address())?;

let package_deps = InputObjects::new(self.load_packages(PACKAGE_DEPS).collect());
let version = package_deps.lamport_timestamp(&[]);
let move_alias_object = move_alias.to_genesis_object(
alias_output_owner,
&self.protocol_config,
&self.tx_context,
version,
)?;

let move_alias_object_ref = move_alias_object.compute_object_reference();
self.store.insert_object(move_alias_object);

let (bag, version) = self.create_bag(alias.native_tokens())?;
let move_alias_output =
AliasOutput::try_from_stardust(self.tx_context.fresh_id(), &alias, bag)?;

// The bag will be wrapped into the alias output object, so
// by equating their versions we emulate a ptb.
let move_alias_output_object = move_alias_output.to_genesis_object(
alias_output_owner,
&self.protocol_config,
&self.tx_context,
version,
)?;
let move_alias_output_object_ref = move_alias_output_object.compute_object_reference();
self.store.insert_object(move_alias_output_object);

// Attach the Alias to the Alias Output as a dynamic object field via the attach_alias convenience method.
let pt = {
let mut builder = ProgrammableTransactionBuilder::new();

let alias_output_arg =
builder.obj(ObjectArg::ImmOrOwnedObject(move_alias_output_object_ref))?;
let alias_arg = builder.obj(ObjectArg::ImmOrOwnedObject(move_alias_object_ref))?;
builder.programmable_move_call(
STARDUST_PACKAGE_ID,
ident_str!("alias_output").into(),
ident_str!("attach_alias").into(),
vec![],
vec![alias_output_arg, alias_arg],
);

builder.finish()
};

let input_objects = CheckedInputObjects::new_for_genesis(
self.load_input_objects([move_alias_object_ref, move_alias_output_object_ref])
.chain(self.load_packages(PACKAGE_DEPS))
.collect(),
);

let InnerTemporaryStore { written, .. } = self.execute_pt_unmetered(input_objects, pt)?;
self.store.finish(written);

Ok(())
}

/// Create a [`Bag`] of balances of native tokens.
Expand Down Expand Up @@ -510,7 +572,8 @@ impl Executor {

/// Verify the ledger state represented by the objects in [`InMemoryStorage`].
fn verify_ledger_state(_store: &InMemoryStorage) -> Result<()> {
todo!();
// TODO: Implementation. Returns Ok for now so the migration can be tested.
Ok(())
}

/// Serialize the objects stored in [`InMemoryStorage`] into a file using
Expand Down
125 changes: 125 additions & 0 deletions crates/sui-genesis-builder/src/stardust/migration_tests.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
use iota_sdk::types::block::{
address::Ed25519Address,
output::{
feature::{IssuerFeature, MetadataFeature, SenderFeature},
unlock_condition::{GovernorAddressUnlockCondition, StateControllerAddressUnlockCondition},
AliasId, AliasOutput as StardustAlias, AliasOutputBuilder, Feature,
},
};
use crate::stardust::{
migration::Migration,
types::{snapshot::OutputHeader, Alias, AliasOutput},
};
use sui_types::{base_types::ObjectID, object::Object};

fn migrate_alias(
header: OutputHeader,
stardust_alias: StardustAlias,
) -> (ObjectID, Alias, AliasOutput) {
let alias_id: AliasId = stardust_alias
.alias_id()
.or_from_output_id(&header.output_id())
.to_owned();
let mut snapshot_buffer = Vec::new();
Migration::new()
.unwrap()
.run(
[].into_iter(),
[(header, stardust_alias.into())].into_iter(),
&mut snapshot_buffer,
)
.unwrap();

let migrated_objects: Vec<Object> = bcs::from_bytes(&snapshot_buffer).unwrap();

// Ensure the migrated objects exist under the expected identifiers.
let alias_object_id = ObjectID::new(*alias_id);
let alias_object = migrated_objects
.iter()
.find(|obj| obj.id() == alias_object_id)
.expect("alias object should be present in the migrated snapshot");
assert_eq!(alias_object.struct_tag().unwrap(), Alias::tag(),);
let alias_output_object = migrated_objects
.iter()
.find(|obj| match obj.struct_tag() {
Some(tag) => tag == AliasOutput::tag(),
None => false,
})
.expect("alias object should be present in the migrated snapshot");

// Version is set to 1 when the alias is created based on the computed lamport timestamp.
// When the alias is attached to the alias output, the version should be incremented.
assert!(
alias_object.version().value() > 1,
"alias object version should have been incremented"
);
assert!(
alias_output_object.version().value() > 1,
"alias output object version should have been incremented"
);

let alias_output: AliasOutput =
bcs::from_bytes(alias_output_object.data.try_as_move().unwrap().contents()).unwrap();
let alias: Alias =
bcs::from_bytes(alias_object.data.try_as_move().unwrap().contents()).unwrap();

(alias_object_id, alias, alias_output)
}

/// Test that the migrated alias objects in the snapshot contain the expected data.
#[test]
fn test_alias_migration() {
let alias_id = AliasId::new(rand::random());
let random_address = Ed25519Address::from(rand::random::<[u8; Ed25519Address::LENGTH]>());
let header = OutputHeader::new_testing(
rand::random(),
rand::random(),
rand::random(),
rand::random(),
);

let stardust_alias = AliasOutputBuilder::new_with_amount(1_000_000, alias_id)
.add_unlock_condition(StateControllerAddressUnlockCondition::new(random_address))
.add_unlock_condition(GovernorAddressUnlockCondition::new(random_address))
.with_state_metadata([0xff; 1])
.with_features(vec![
Feature::Metadata(MetadataFeature::new([0xdd; 1]).unwrap()),
Feature::Sender(SenderFeature::new(random_address)),
])
.with_immutable_features(vec![
Feature::Metadata(MetadataFeature::new([0xaa; 1]).unwrap()),
Feature::Issuer(IssuerFeature::new(random_address)),
])
.with_state_index(3)
.finish()
.unwrap();

let (alias_object_id, alias, alias_output) = migrate_alias(header, stardust_alias.clone());
let expected_alias = Alias::try_from_stardust(alias_object_id, &stardust_alias).unwrap();

// Compare only the balance. The ID is newly generated and the bag is tested separately.
assert_eq!(stardust_alias.amount(), alias_output.iota.value());

assert_eq!(expected_alias, alias);
}

#[test]
fn test_alias_migration_with_zeroed_id() {
let random_address = Ed25519Address::from(rand::random::<[u8; Ed25519Address::LENGTH]>());
let header = OutputHeader::new_testing(
rand::random(),
rand::random(),
rand::random(),
rand::random(),
);

let stardust_alias = AliasOutputBuilder::new_with_amount(1_000_000, AliasId::null())
.add_unlock_condition(StateControllerAddressUnlockCondition::new(random_address))
.add_unlock_condition(GovernorAddressUnlockCondition::new(random_address))
.finish()
.unwrap();

// If this function does not panic, then the created aliases
// were found at the correct non-zeroed Alias ID.
migrate_alias(header, stardust_alias);
}
2 changes: 2 additions & 0 deletions crates/sui-genesis-builder/src/stardust/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,5 @@ pub mod migration;
pub mod native_token;
pub mod parse;
pub mod types;
#[cfg(test)]
mod migration_tests;
26 changes: 26 additions & 0 deletions crates/sui-genesis-builder/src/stardust/types/address.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
use iota_sdk::types::block::address::Address;
use sui_types::{base_types::SuiAddress, object::Owner};

/// Converts a ["Stardust" `Address`](Address) to a [`SuiAddress`].
///
/// This is intended as the only conversion function to go from Stardust to Sui addresses, so there is only
/// one place to potentially update it if we decide to change it later.
pub fn stardust_to_sui_address(stardust_address: impl Into<Address>) -> anyhow::Result<SuiAddress> {
stardust_address.into().to_string().parse()
}

/// Converts a ["Stardust" `Address`](Address) to a [`SuiAddress`] and then wraps it into an [`Owner`]
/// which is either address- or object-owned depending on the stardust address.
pub fn stardust_to_sui_address_owner(
stardust_address: impl Into<Address>,
) -> anyhow::Result<Owner> {
let stardust_address = stardust_address.into();
match &stardust_address {
Address::Ed25519(_) => Ok(Owner::AddressOwner(stardust_to_sui_address(
stardust_address,
)?)),
Address::Alias(_) | Address::Nft(_) => Ok(Owner::ObjectOwner(stardust_to_sui_address(
stardust_address,
)?)),
}
}
Loading

0 comments on commit 54b588c

Please sign in to comment.