Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
35 changes: 32 additions & 3 deletions bin/sozo/src/commands/migrate.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
use std::sync::Arc;

use anyhow::{anyhow, Context, Result};
use clap::Args;
use clap::{Args, ValueEnum};
use colored::*;
use dojo_utils::{self, provider as provider_utils, TxnConfig};
use dojo_world::config::migration_config::ManifestAbiFormat;
use dojo_world::contracts::WorldContract;
use dojo_world::services::IpfsService;
use scarb_metadata::Metadata;
Expand Down Expand Up @@ -39,6 +40,10 @@ pub struct MigrateArgs {

#[command(flatten)]
pub ipfs: IpfsOptions,

/// Select how ABIs are written in the generated manifest.
#[arg(long, value_enum, value_name = "FORMAT")]
pub manifest_abi_format: Option<ManifestAbiFormatArg>,
}

impl MigrateArgs {
Expand All @@ -48,7 +53,7 @@ impl MigrateArgs {

scarb_metadata.ensure_profile_artifacts()?;

let MigrateArgs { world, starknet, account, ipfs, .. } = self;
let MigrateArgs { world, starknet, account, ipfs, manifest_abi_format, .. } = self;

print_banner(ui, scarb_metadata, &starknet).await?;

Expand Down Expand Up @@ -80,9 +85,17 @@ impl MigrateArgs {
is_guest,
);

let MigrationResult { manifest, has_changes } =
let MigrationResult { mut manifest, has_changes } =
migration.migrate(ui).await.context("Migration failed.")?;

let config_format =
profile_config.migration.as_ref().and_then(|m| m.manifest_abi_format.clone());

let manifest_abi_format =
manifest_abi_format.map(ManifestAbiFormat::from).or(config_format).unwrap_or_default();

manifest.apply_abi_format(manifest_abi_format);

let ipfs_config =
ipfs.config().or(profile_config.env.map(|env| env.ipfs_config).unwrap_or(None));

Expand Down Expand Up @@ -124,6 +137,22 @@ impl MigrateArgs {
}
}

#[derive(Debug, Clone, Copy, ValueEnum)]
#[value(rename_all = "snake_case")]
pub enum ManifestAbiFormatArg {
AllInOne,
PerContract,
}

impl From<ManifestAbiFormatArg> for ManifestAbiFormat {
fn from(value: ManifestAbiFormatArg) -> Self {
match value {
ManifestAbiFormatArg::AllInOne => ManifestAbiFormat::AllInOne,
ManifestAbiFormatArg::PerContract => ManifestAbiFormat::PerContract,
}
}
}

#[derive(Debug, Tabled)]
pub struct Banner {
pub profile: String,
Expand Down
15 changes: 15 additions & 0 deletions crates/dojo/world/src/config/migration_config.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,18 @@
use serde::Deserialize;

#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ManifestAbiFormat {
AllInOne,
PerContract,
}

impl Default for ManifestAbiFormat {
fn default() -> Self {
Self::AllInOne
}
}

#[derive(Debug, Clone, Deserialize, Default)]
pub struct MigrationConfig {
/// Contracts to skip during migration.
Expand All @@ -10,4 +23,6 @@ pub struct MigrationConfig {
/// Determine the contract initialization order.
/// Expecting tags.
pub order_inits: Option<Vec<String>>,
/// Controls how ABIs are represented in the generated manifest.
pub manifest_abi_format: Option<ManifestAbiFormat>,
}
116 changes: 102 additions & 14 deletions crates/dojo/world/src/diff/manifest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ use starknet::core::types::contract::{AbiEntry, AbiEvent, TypedAbiEvent, Untyped
use starknet::core::types::Felt;

use super::{ResourceDiff, WorldDiff};
use crate::config::migration_config::ManifestAbiFormat;
use crate::local::{ExternalContractLocal, ResourceLocal};
use crate::remote::ResourceRemote;
use crate::ResourceType;
Expand All @@ -27,7 +28,9 @@ pub struct Manifest {
/// vector. When serialized, the entries are always sorted alphabetically by name.
#[serde(
serialize_with = "serialize_abis_hashmap",
deserialize_with = "deserialize_abis_hashmap"
deserialize_with = "deserialize_abis_hashmap",
default,
skip_serializing_if = "HashMap::is_empty"
)]
pub abis: HashMap<String, AbiEntry>,
}
Expand All @@ -47,8 +50,8 @@ pub struct WorldContract {
pub name: String,
/// Entrypoints of the world.
pub entrypoints: Vec<String>,
/// Abi of the world, skipped during serialization in favor of the `abis` field.
#[serde(skip)]
/// Abi of the world. Cleared when we omit inline ABIs from the manifest.
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub abi: Vec<AbiEntry>,
}

Expand All @@ -61,8 +64,8 @@ pub struct DojoContract {
/// Class hash of the contract.
#[serde_as(as = "UfeHex")]
pub class_hash: Felt,
/// ABI of the contract, skipped during serialization in favor of the `abis` field.
#[serde(skip)]
/// ABI of the contract. Cleared when we omit inline ABIs from the manifest.
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub abi: Vec<AbiEntry>,
/// Initialization call data.
#[serde(default)]
Expand All @@ -82,8 +85,8 @@ pub struct DojoLibrary {
/// Class hash of the contract.
#[serde_as(as = "UfeHex")]
pub class_hash: Felt,
/// ABI of the contract, skipped during serialization in favor of the `abis` field.
#[serde(skip)]
/// ABI of the contract. Cleared when we omit inline ABIs from the manifest.
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub abi: Vec<AbiEntry>,
/// Tag of the contract.
pub tag: String,
Expand All @@ -109,8 +112,8 @@ pub struct DojoModel {
/// Selector of the model.
#[serde_as(as = "UfeHex")]
pub selector: Felt,
/// ABI of the model, skipped during serialization in favor of the `abis` field.
#[serde(skip)]
/// ABI of the model. Cleared when we omit inline ABIs from the manifest.
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub abi: Vec<AbiEntry>,
}

Expand Down Expand Up @@ -138,8 +141,8 @@ pub struct DojoEvent {
/// Selector of the event.
#[serde_as(as = "UfeHex")]
pub selector: Felt,
/// ABI of the event, skipped during serialization in favor of the `abis` field.
#[serde(skip)]
/// ABI of the event. Cleared when we omit inline ABIs from the manifest.
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub abi: Vec<AbiEntry>,
}

Expand All @@ -166,9 +169,8 @@ pub struct ExternalContract {
/// Contract address
#[serde_as(as = "UfeHex")]
pub address: Felt,
/// ABI of the contract.
/// Ignored during serialization in favor of the `abis` field.
#[serde(skip)]
/// ABI of the contract. Cleared when we omit inline ABIs from the manifest.
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub abi: Vec<AbiEntry>,
/// Human-readeable constructor call data.
#[serde(default)]
Expand Down Expand Up @@ -251,6 +253,24 @@ impl Manifest {
Self { world, contracts, models, events, libraries, external_contracts, abis }
}

/// Removes inline ABI definitions so they are omitted during serialization.
pub fn strip_inline_abis(&mut self) {
self.world.abi.clear();
self.contracts.iter_mut().for_each(|c| c.abi.clear());
self.models.iter_mut().for_each(|m| m.abi.clear());
self.events.iter_mut().for_each(|e| e.abi.clear());
self.libraries.iter_mut().for_each(|l| l.abi.clear());
self.external_contracts.iter_mut().for_each(|c| c.abi.clear());
}

/// Applies the ABI layout requested for the manifest output.
pub fn apply_abi_format(&mut self, format: ManifestAbiFormat) {
match format {
ManifestAbiFormat::AllInOne => self.strip_inline_abis(),
ManifestAbiFormat::PerContract => self.abis.clear(),
}
}

pub fn get_contract_address(&self, tag: &str) -> Option<Felt> {
self.contracts.iter().find_map(|c| if c.tag == tag { Some(c.address) } else { None })
}
Expand Down Expand Up @@ -522,3 +542,71 @@ fn add_abi_entries(abis: &mut HashMap<String, AbiEntry>, abi: Vec<AbiEntry>) {
abis.insert(entry_name, abi_entry);
}
}

#[cfg(test)]
mod tests {
use serde_json::json;
use starknet::macros::felt;

use super::*;

#[test]
fn apply_abi_format_controls_serialization() {
let abi_entry: AbiEntry = serde_json::from_value(json!({
"type": "function",
"name": "foo",
"inputs": [],
"outputs": [],
"state_mutability": "view"
}))
.unwrap();

let mut manifest_base = Manifest {
world: WorldContract {
class_hash: felt!("0x1"),
address: felt!("0x2"),
seed: "seed".to_string(),
name: "world".to_string(),
entrypoints: vec!["execute".to_string()],
abi: vec![abi_entry.clone()],
},
contracts: vec![DojoContract {
address: felt!("0x3"),
class_hash: felt!("0x4"),
abi: vec![abi_entry.clone()],
init_calldata: vec![],
tag: "ns-contract".to_string(),
systems: vec!["system".to_string()],
selector: felt!("0x5"),
}],
libraries: vec![],
models: vec![],
events: vec![],
external_contracts: vec![],
abis: HashMap::new(),
};

manifest_base.abis.insert("foo".to_string(), abi_entry.clone());

let default_json = serde_json::to_value(&manifest_base).unwrap();
assert!(default_json["contracts"][0].get("abi").is_some());
assert!(default_json["world"].get("abi").is_some());
assert!(default_json.get("abis").is_some());

let mut all_in_one = manifest_base.clone();
all_in_one.apply_abi_format(ManifestAbiFormat::AllInOne);

let all_in_one_json = serde_json::to_value(&all_in_one).unwrap();
assert!(all_in_one_json["contracts"][0].get("abi").is_none());
assert!(all_in_one_json["world"].get("abi").is_none());
assert!(all_in_one_json.get("abis").is_some());

let mut per_contract = manifest_base;
per_contract.apply_abi_format(ManifestAbiFormat::PerContract);

let per_contract_json = serde_json::to_value(&per_contract).unwrap();
assert!(per_contract_json["contracts"][0].get("abi").is_some());
assert!(per_contract_json["world"].get("abi").is_some());
assert!(per_contract_json.get("abis").is_none());
}
}
Loading
Loading