Skip to content

Commit e8d8b44

Browse files
authored
feat(sozo): add parameter to control abi format in manifest (#3387)
1 parent 7c52a41 commit e8d8b44

File tree

4 files changed

+2393
-2463
lines changed

4 files changed

+2393
-2463
lines changed

bin/sozo/src/commands/migrate.rs

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
use std::sync::Arc;
22

33
use anyhow::{anyhow, Context, Result};
4-
use clap::Args;
4+
use clap::{Args, ValueEnum};
55
use colored::*;
66
use dojo_utils::{self, provider as provider_utils, TxnConfig};
7+
use dojo_world::config::migration_config::ManifestAbiFormat;
78
use dojo_world::contracts::WorldContract;
89
use dojo_world::services::IpfsService;
910
use scarb_metadata::Metadata;
@@ -39,6 +40,10 @@ pub struct MigrateArgs {
3940

4041
#[command(flatten)]
4142
pub ipfs: IpfsOptions,
43+
44+
/// Select how ABIs are written in the generated manifest.
45+
#[arg(long, value_enum, value_name = "FORMAT")]
46+
pub manifest_abi_format: Option<ManifestAbiFormatArg>,
4247
}
4348

4449
impl MigrateArgs {
@@ -48,7 +53,7 @@ impl MigrateArgs {
4853

4954
scarb_metadata.ensure_profile_artifacts()?;
5055

51-
let MigrateArgs { world, starknet, account, ipfs, .. } = self;
56+
let MigrateArgs { world, starknet, account, ipfs, manifest_abi_format, .. } = self;
5257

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

@@ -80,9 +85,17 @@ impl MigrateArgs {
8085
is_guest,
8186
);
8287

83-
let MigrationResult { manifest, has_changes } =
88+
let MigrationResult { mut manifest, has_changes } =
8489
migration.migrate(ui).await.context("Migration failed.")?;
8590

91+
let config_format =
92+
profile_config.migration.as_ref().and_then(|m| m.manifest_abi_format.clone());
93+
94+
let manifest_abi_format =
95+
manifest_abi_format.map(ManifestAbiFormat::from).or(config_format).unwrap_or_default();
96+
97+
manifest.apply_abi_format(manifest_abi_format);
98+
8699
let ipfs_config =
87100
ipfs.config().or(profile_config.env.map(|env| env.ipfs_config).unwrap_or(None));
88101

@@ -124,6 +137,22 @@ impl MigrateArgs {
124137
}
125138
}
126139

140+
#[derive(Debug, Clone, Copy, ValueEnum)]
141+
#[value(rename_all = "snake_case")]
142+
pub enum ManifestAbiFormatArg {
143+
AllInOne,
144+
PerContract,
145+
}
146+
147+
impl From<ManifestAbiFormatArg> for ManifestAbiFormat {
148+
fn from(value: ManifestAbiFormatArg) -> Self {
149+
match value {
150+
ManifestAbiFormatArg::AllInOne => ManifestAbiFormat::AllInOne,
151+
ManifestAbiFormatArg::PerContract => ManifestAbiFormat::PerContract,
152+
}
153+
}
154+
}
155+
127156
#[derive(Debug, Tabled)]
128157
pub struct Banner {
129158
pub profile: String,

crates/dojo/world/src/config/migration_config.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,18 @@
11
use serde::Deserialize;
22

3+
#[derive(Debug, Clone, Deserialize)]
4+
#[serde(rename_all = "snake_case")]
5+
pub enum ManifestAbiFormat {
6+
AllInOne,
7+
PerContract,
8+
}
9+
10+
impl Default for ManifestAbiFormat {
11+
fn default() -> Self {
12+
Self::AllInOne
13+
}
14+
}
15+
316
#[derive(Debug, Clone, Deserialize, Default)]
417
pub struct MigrationConfig {
518
/// Contracts to skip during migration.
@@ -10,4 +23,6 @@ pub struct MigrationConfig {
1023
/// Determine the contract initialization order.
1124
/// Expecting tags.
1225
pub order_inits: Option<Vec<String>>,
26+
/// Controls how ABIs are represented in the generated manifest.
27+
pub manifest_abi_format: Option<ManifestAbiFormat>,
1328
}

crates/dojo/world/src/diff/manifest.rs

Lines changed: 102 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ use starknet::core::types::contract::{AbiEntry, AbiEvent, TypedAbiEvent, Untyped
1010
use starknet::core::types::Felt;
1111

1212
use super::{ResourceDiff, WorldDiff};
13+
use crate::config::migration_config::ManifestAbiFormat;
1314
use crate::local::{ExternalContractLocal, ResourceLocal};
1415
use crate::remote::ResourceRemote;
1516
use crate::ResourceType;
@@ -27,7 +28,9 @@ pub struct Manifest {
2728
/// vector. When serialized, the entries are always sorted alphabetically by name.
2829
#[serde(
2930
serialize_with = "serialize_abis_hashmap",
30-
deserialize_with = "deserialize_abis_hashmap"
31+
deserialize_with = "deserialize_abis_hashmap",
32+
default,
33+
skip_serializing_if = "HashMap::is_empty"
3134
)]
3235
pub abis: HashMap<String, AbiEntry>,
3336
}
@@ -47,8 +50,8 @@ pub struct WorldContract {
4750
pub name: String,
4851
/// Entrypoints of the world.
4952
pub entrypoints: Vec<String>,
50-
/// Abi of the world, skipped during serialization in favor of the `abis` field.
51-
#[serde(skip)]
53+
/// Abi of the world. Cleared when we omit inline ABIs from the manifest.
54+
#[serde(default, skip_serializing_if = "Vec::is_empty")]
5255
pub abi: Vec<AbiEntry>,
5356
}
5457

@@ -61,8 +64,8 @@ pub struct DojoContract {
6164
/// Class hash of the contract.
6265
#[serde_as(as = "UfeHex")]
6366
pub class_hash: Felt,
64-
/// ABI of the contract, skipped during serialization in favor of the `abis` field.
65-
#[serde(skip)]
67+
/// ABI of the contract. Cleared when we omit inline ABIs from the manifest.
68+
#[serde(default, skip_serializing_if = "Vec::is_empty")]
6669
pub abi: Vec<AbiEntry>,
6770
/// Initialization call data.
6871
#[serde(default)]
@@ -82,8 +85,8 @@ pub struct DojoLibrary {
8285
/// Class hash of the contract.
8386
#[serde_as(as = "UfeHex")]
8487
pub class_hash: Felt,
85-
/// ABI of the contract, skipped during serialization in favor of the `abis` field.
86-
#[serde(skip)]
88+
/// ABI of the contract. Cleared when we omit inline ABIs from the manifest.
89+
#[serde(default, skip_serializing_if = "Vec::is_empty")]
8790
pub abi: Vec<AbiEntry>,
8891
/// Tag of the contract.
8992
pub tag: String,
@@ -109,8 +112,8 @@ pub struct DojoModel {
109112
/// Selector of the model.
110113
#[serde_as(as = "UfeHex")]
111114
pub selector: Felt,
112-
/// ABI of the model, skipped during serialization in favor of the `abis` field.
113-
#[serde(skip)]
115+
/// ABI of the model. Cleared when we omit inline ABIs from the manifest.
116+
#[serde(default, skip_serializing_if = "Vec::is_empty")]
114117
pub abi: Vec<AbiEntry>,
115118
}
116119

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

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

256+
/// Removes inline ABI definitions so they are omitted during serialization.
257+
pub fn strip_inline_abis(&mut self) {
258+
self.world.abi.clear();
259+
self.contracts.iter_mut().for_each(|c| c.abi.clear());
260+
self.models.iter_mut().for_each(|m| m.abi.clear());
261+
self.events.iter_mut().for_each(|e| e.abi.clear());
262+
self.libraries.iter_mut().for_each(|l| l.abi.clear());
263+
self.external_contracts.iter_mut().for_each(|c| c.abi.clear());
264+
}
265+
266+
/// Applies the ABI layout requested for the manifest output.
267+
pub fn apply_abi_format(&mut self, format: ManifestAbiFormat) {
268+
match format {
269+
ManifestAbiFormat::AllInOne => self.strip_inline_abis(),
270+
ManifestAbiFormat::PerContract => self.abis.clear(),
271+
}
272+
}
273+
254274
pub fn get_contract_address(&self, tag: &str) -> Option<Felt> {
255275
self.contracts.iter().find_map(|c| if c.tag == tag { Some(c.address) } else { None })
256276
}
@@ -522,3 +542,71 @@ fn add_abi_entries(abis: &mut HashMap<String, AbiEntry>, abi: Vec<AbiEntry>) {
522542
abis.insert(entry_name, abi_entry);
523543
}
524544
}
545+
546+
#[cfg(test)]
547+
mod tests {
548+
use serde_json::json;
549+
use starknet::macros::felt;
550+
551+
use super::*;
552+
553+
#[test]
554+
fn apply_abi_format_controls_serialization() {
555+
let abi_entry: AbiEntry = serde_json::from_value(json!({
556+
"type": "function",
557+
"name": "foo",
558+
"inputs": [],
559+
"outputs": [],
560+
"state_mutability": "view"
561+
}))
562+
.unwrap();
563+
564+
let mut manifest_base = Manifest {
565+
world: WorldContract {
566+
class_hash: felt!("0x1"),
567+
address: felt!("0x2"),
568+
seed: "seed".to_string(),
569+
name: "world".to_string(),
570+
entrypoints: vec!["execute".to_string()],
571+
abi: vec![abi_entry.clone()],
572+
},
573+
contracts: vec![DojoContract {
574+
address: felt!("0x3"),
575+
class_hash: felt!("0x4"),
576+
abi: vec![abi_entry.clone()],
577+
init_calldata: vec![],
578+
tag: "ns-contract".to_string(),
579+
systems: vec!["system".to_string()],
580+
selector: felt!("0x5"),
581+
}],
582+
libraries: vec![],
583+
models: vec![],
584+
events: vec![],
585+
external_contracts: vec![],
586+
abis: HashMap::new(),
587+
};
588+
589+
manifest_base.abis.insert("foo".to_string(), abi_entry.clone());
590+
591+
let default_json = serde_json::to_value(&manifest_base).unwrap();
592+
assert!(default_json["contracts"][0].get("abi").is_some());
593+
assert!(default_json["world"].get("abi").is_some());
594+
assert!(default_json.get("abis").is_some());
595+
596+
let mut all_in_one = manifest_base.clone();
597+
all_in_one.apply_abi_format(ManifestAbiFormat::AllInOne);
598+
599+
let all_in_one_json = serde_json::to_value(&all_in_one).unwrap();
600+
assert!(all_in_one_json["contracts"][0].get("abi").is_none());
601+
assert!(all_in_one_json["world"].get("abi").is_none());
602+
assert!(all_in_one_json.get("abis").is_some());
603+
604+
let mut per_contract = manifest_base;
605+
per_contract.apply_abi_format(ManifestAbiFormat::PerContract);
606+
607+
let per_contract_json = serde_json::to_value(&per_contract).unwrap();
608+
assert!(per_contract_json["contracts"][0].get("abi").is_some());
609+
assert!(per_contract_json["world"].get("abi").is_some());
610+
assert!(per_contract_json.get("abis").is_none());
611+
}
612+
}

0 commit comments

Comments
 (0)