-
Notifications
You must be signed in to change notification settings - Fork 17
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(sui-genesis-builder): Add Basic Output object validation (#237)
* implement output validation against * whoops * Add aliases to map * Add struct to define potentially created objects. Update basic output validation to account for generated coins. * move created objects map to migration struct * Refactor module structure. * Move migration logic out of mod.rs * Add native token coin validation * Fix native token validation * Update crates/sui-genesis-builder/src/stardust/migration/verification/mod.rs Co-authored-by: /alex/ <[email protected]> * re-add test methods * refactor: Fix some clippy --------- Co-authored-by: /alex/ <[email protected]> Co-authored-by: samuel_rufi <[email protected]>
- Loading branch information
1 parent
97db2e3
commit 4bd974b
Showing
14 changed files
with
1,265 additions
and
688 deletions.
There are no files selected for viewing
631 changes: 631 additions & 0 deletions
631
crates/sui-genesis-builder/src/stardust/migration/executor.rs
Large diffs are not rendered by default.
Oops, something went wrong.
760 changes: 100 additions & 660 deletions
760
crates/sui-genesis-builder/src/stardust/migration/migration.rs
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,10 @@ | ||
// Copyright (c) 2024 IOTA Stiftung | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
pub mod migration; | ||
mod executor; | ||
mod migration; | ||
#[cfg(test)] | ||
mod tests; | ||
pub mod verification; | ||
|
||
pub use migration::*; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
16 changes: 16 additions & 0 deletions
16
crates/sui-genesis-builder/src/stardust/migration/verification/alias.rs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
// Copyright (c) 2024 IOTA Stiftung | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
use iota_sdk::types::block::output::AliasOutput; | ||
use sui_types::in_memory_storage::InMemoryStorage; | ||
|
||
use super::created_objects::CreatedObjects; | ||
|
||
pub fn verify_alias_output( | ||
output: &AliasOutput, | ||
created_objects: &CreatedObjects, | ||
storage: &InMemoryStorage, | ||
) -> anyhow::Result<()> { | ||
// TODO: Implementation. Returns Ok for now so the migration can be tested. | ||
Ok(()) | ||
} |
141 changes: 141 additions & 0 deletions
141
crates/sui-genesis-builder/src/stardust/migration/verification/basic.rs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,141 @@ | ||
// Copyright (c) 2024 IOTA Stiftung | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
use anyhow::{anyhow, ensure, Result}; | ||
use iota_sdk::types::block::output::BasicOutput; | ||
use sui_types::{balance::Balance, dynamic_field::Field, in_memory_storage::InMemoryStorage}; | ||
|
||
use crate::stardust::migration::verification::{ | ||
created_objects::CreatedObjects, | ||
util::{ | ||
verify_expiration_unlock_condition, verify_metadata_feature, verify_native_tokens, | ||
verify_sender_feature, verify_storage_deposit_unlock_condition, verify_tag_feature, | ||
verify_timelock_unlock_condition, | ||
}, | ||
}; | ||
|
||
pub fn verify_basic_output( | ||
output: &BasicOutput, | ||
created_objects: &CreatedObjects, | ||
storage: &InMemoryStorage, | ||
) -> Result<()> { | ||
// If the output has multiple unlock conditions, then a genesis object should have been created. | ||
if output.unlock_conditions().len() > 1 { | ||
let created_output = created_objects | ||
.output() | ||
.and_then(|id| { | ||
storage | ||
.get_object(id) | ||
.ok_or_else(|| anyhow!("missing object")) | ||
})? | ||
.to_rust::<crate::stardust::types::output::BasicOutput>() | ||
.ok_or_else(|| anyhow!("invalid basic output object"))?; | ||
|
||
// Amount | ||
ensure!( | ||
created_output.iota.value() == output.amount(), | ||
"amount mismatch: found {}, expected {}", | ||
created_output.iota.value(), | ||
output.amount() | ||
); | ||
|
||
// Native Tokens | ||
ensure!( | ||
created_output.native_tokens.size == output.native_tokens().len() as u64, | ||
"native tokens bag length mismatch: found {}, expected {}", | ||
created_output.native_tokens.size, | ||
output.native_tokens().len() | ||
); | ||
let created_native_token_fields = created_objects.native_tokens().and_then(|ids| { | ||
ids.iter() | ||
.map(|id| { | ||
let obj = storage | ||
.get_object(id) | ||
.ok_or_else(|| anyhow!("missing native token field for {id}"))?; | ||
obj.to_rust::<Field<String, Balance>>().ok_or_else(|| { | ||
anyhow!("expected a native token field, found {:?}", obj.type_()) | ||
}) | ||
}) | ||
.collect::<Result<Vec<_>, _>>() | ||
})?; | ||
verify_native_tokens(output.native_tokens(), created_native_token_fields)?; | ||
|
||
// Storage Deposit Return Unlock Condition | ||
verify_storage_deposit_unlock_condition( | ||
output.unlock_conditions().storage_deposit_return(), | ||
created_output.storage_deposit_return.as_ref(), | ||
)?; | ||
|
||
// Timelock Unlock Condition | ||
verify_timelock_unlock_condition( | ||
output.unlock_conditions().timelock(), | ||
created_output.timelock.as_ref(), | ||
)?; | ||
|
||
// Expiration Unlock Condition | ||
verify_expiration_unlock_condition( | ||
output.unlock_conditions().expiration(), | ||
created_output.expiration.as_ref(), | ||
output.address(), | ||
)?; | ||
|
||
// Metadata Feature | ||
verify_metadata_feature( | ||
output.features().metadata(), | ||
created_output.metadata.as_ref(), | ||
)?; | ||
|
||
// Tag Feature | ||
verify_tag_feature(output.features().tag(), created_output.tag.as_ref())?; | ||
|
||
// Sender Feature | ||
verify_sender_feature(output.features().sender(), created_output.sender)?; | ||
|
||
// Otherwise the output contains only an address unlock condition and only a coin | ||
// and possibly native tokens should have been created. | ||
} else { | ||
ensure!( | ||
created_objects.output().is_err(), | ||
"unexpected output object created for simple deposit" | ||
); | ||
|
||
// Coin value. | ||
let created_coin = created_objects | ||
.coin() | ||
.and_then(|id| { | ||
storage | ||
.get_object(id) | ||
.ok_or_else(|| anyhow!("missing coin")) | ||
})? | ||
.as_coin_maybe() | ||
.ok_or_else(|| anyhow!("expected a coin"))?; | ||
ensure!( | ||
created_coin.value() == output.amount(), | ||
"coin amount mismatch: found {}, expected {}", | ||
created_coin.value(), | ||
output.amount() | ||
); | ||
|
||
// Native Tokens | ||
let created_native_token_coins = created_objects.native_tokens().and_then(|ids| { | ||
ids.iter() | ||
.map(|id| { | ||
let obj = storage | ||
.get_object(id) | ||
.ok_or_else(|| anyhow!("missing native token coin for {id}"))?; | ||
obj.as_coin_maybe().ok_or_else(|| { | ||
anyhow!("expected a native token coin, found {:?}", obj.type_()) | ||
}) | ||
}) | ||
.collect::<Result<Vec<_>, _>>() | ||
})?; | ||
verify_native_tokens(output.native_tokens(), created_native_token_coins)?; | ||
} | ||
|
||
ensure!( | ||
created_objects.package().is_err(), | ||
"unexpected package found" | ||
); | ||
|
||
Ok(()) | ||
} |
72 changes: 72 additions & 0 deletions
72
crates/sui-genesis-builder/src/stardust/migration/verification/created_objects.rs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
// Copyright (c) 2024 IOTA Stiftung | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
use anyhow::{anyhow, bail, Result}; | ||
use sui_types::base_types::ObjectID; | ||
|
||
/// Defines objects that may have been created by migrating an [`Output`]. | ||
#[derive(Default)] | ||
pub struct CreatedObjects { | ||
output: Option<ObjectID>, | ||
coin: Option<ObjectID>, | ||
package: Option<ObjectID>, | ||
native_tokens: Option<Vec<ObjectID>>, | ||
} | ||
|
||
impl CreatedObjects { | ||
pub fn output(&self) -> Result<&ObjectID> { | ||
self.output | ||
.as_ref() | ||
.ok_or_else(|| anyhow!("no created output object")) | ||
} | ||
|
||
pub(crate) fn set_output(&mut self, id: ObjectID) -> Result<()> { | ||
if let Some(id) = self.output { | ||
bail!("output already set: {id}") | ||
} | ||
self.output.replace(id); | ||
Ok(()) | ||
} | ||
|
||
pub fn coin(&self) -> Result<&ObjectID> { | ||
self.coin | ||
.as_ref() | ||
.ok_or_else(|| anyhow!("no created coin object")) | ||
} | ||
|
||
pub(crate) fn set_coin(&mut self, id: ObjectID) -> Result<()> { | ||
if let Some(id) = self.coin { | ||
bail!("coin already set: {id}") | ||
} | ||
self.coin.replace(id); | ||
Ok(()) | ||
} | ||
|
||
pub fn package(&self) -> Result<&ObjectID> { | ||
self.package | ||
.as_ref() | ||
.ok_or_else(|| anyhow!("no created package object")) | ||
} | ||
|
||
pub(crate) fn set_package(&mut self, id: ObjectID) -> Result<()> { | ||
if let Some(id) = self.package { | ||
bail!("package already set: {id}") | ||
} | ||
self.package.replace(id); | ||
Ok(()) | ||
} | ||
|
||
pub fn native_tokens(&self) -> Result<&[ObjectID]> { | ||
self.native_tokens | ||
.as_deref() | ||
.ok_or_else(|| anyhow!("no created native token objects")) | ||
} | ||
|
||
pub(crate) fn set_native_tokens(&mut self, ids: Vec<ObjectID>) -> Result<()> { | ||
if let Some(id) = &self.native_tokens { | ||
bail!("native tokens already set: {id:?}") | ||
} | ||
self.native_tokens.replace(ids); | ||
Ok(()) | ||
} | ||
} |
16 changes: 16 additions & 0 deletions
16
crates/sui-genesis-builder/src/stardust/migration/verification/foundry.rs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
// Copyright (c) 2024 IOTA Stiftung | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
use iota_sdk::types::block::output::FoundryOutput; | ||
use sui_types::in_memory_storage::InMemoryStorage; | ||
|
||
use super::created_objects::CreatedObjects; | ||
|
||
pub fn verify_foundry_output( | ||
output: &FoundryOutput, | ||
created_objects: &CreatedObjects, | ||
storage: &InMemoryStorage, | ||
) -> anyhow::Result<()> { | ||
// TODO: Implementation. Returns Ok for now so the migration can be tested. | ||
Ok(()) | ||
} |
35 changes: 35 additions & 0 deletions
35
crates/sui-genesis-builder/src/stardust/migration/verification/mod.rs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
// Copyright (c) 2024 IOTA Stiftung | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
//! The [`verification`] module contains the validation logic to make sure that the stardust outputs are correctly converted to the move objects. | ||
use iota_sdk::types::block::output::Output; | ||
use sui_types::in_memory_storage::InMemoryStorage; | ||
|
||
use crate::stardust::types::snapshot::OutputHeader; | ||
|
||
use self::created_objects::CreatedObjects; | ||
|
||
pub mod alias; | ||
pub mod basic; | ||
pub mod created_objects; | ||
pub mod foundry; | ||
pub mod nft; | ||
mod util; | ||
|
||
pub fn verify_output( | ||
header: &OutputHeader, | ||
output: &Output, | ||
created_objects: &CreatedObjects, | ||
storage: &InMemoryStorage, | ||
) -> anyhow::Result<()> { | ||
match output { | ||
Output::Alias(output) => alias::verify_alias_output(output, created_objects, storage), | ||
Output::Basic(output) => basic::verify_basic_output(output, created_objects, storage), | ||
Output::Foundry(output) => foundry::verify_foundry_output(output, created_objects, storage), | ||
Output::Nft(output) => nft::verify_nft_output(output, created_objects, storage), | ||
// Treasury outputs aren't used since Stardust, so no need to verify anything here. | ||
Output::Treasury(_) => return Ok(()), | ||
} | ||
.map_err(|e| anyhow::anyhow!("error verifying output {}: {}", header.output_id(), e)) | ||
} |
Oops, something went wrong.