1use alloy::{
3 network::EthereumWallet, primitives::Address, providers::ProviderBuilder, signers::local::PrivateKeySigner,
4 transports::http::reqwest::Url,
5};
6use clap::Parser;
7use eyre::{Context, Result};
8use newton_prover_chainio::{
9 policy_client::get_policy_address_for_client,
10 version::{
11 get_policy_data_factory_for_policy_data, get_policy_data_factory_version, get_policy_factory_for_policy,
12 get_policy_factory_version,
13 },
14};
15use newton_prover_core::{
16 config::rpc::RpcProviderConfig,
17 newton_policy::NewtonPolicy,
18 newton_policy_data::NewtonPolicyData,
19 newton_policy_data_factory::NewtonPolicyDataFactory,
20 newton_policy_factory::NewtonPolicyFactory,
21 version::{is_compatible, MIN_COMPATIBLE_POLICY_DATA_VERSION, MIN_COMPATIBLE_POLICY_VERSION, PROTOCOL_VERSION},
22};
23use serde::Serialize;
24use std::str::FromStr;
25use tracing::{debug, error, info, warn};
26
27#[derive(Parser, Debug)]
28pub enum VersionCommand {
29 CheckCompatibility {
31 #[arg(long)]
33 policy_client: Address,
34
35 #[arg(long)]
37 chain_id: u64,
38
39 #[arg(long)]
41 rpc_url: Option<String>,
42 },
43
44 Migrate {
46 #[arg(long)]
48 policy_client: Address,
49
50 #[arg(long, env = "PRIVATE_KEY")]
52 private_key: String,
53
54 #[arg(long)]
56 chain_id: u64,
57
58 #[arg(long)]
60 rpc_url: Option<String>,
61
62 #[arg(long)]
64 skip_check: bool,
65
66 #[arg(long)]
68 dry_run: bool,
69 },
70
71 Info,
73}
74
75#[derive(Serialize, Debug)]
76pub struct CompatibilityReport {
77 pub protocol_version: String,
78 pub policy_client: Address,
79 pub policy_address: Address,
80 pub policy_factory_version: String,
81 pub policy_compatible: bool,
82 pub policy_data_reports: Vec<PolicyDataReport>,
83 pub all_compatible: bool,
84 pub migration_required: bool,
85}
86
87#[derive(Serialize, Debug)]
88pub struct PolicyDataReport {
89 pub address: Address,
90 pub factory_version: String,
91 pub compatible: bool,
92}
93
94impl VersionCommand {
95 pub async fn run(self) -> Result<()> {
96 match self {
97 VersionCommand::CheckCompatibility {
98 policy_client,
99 chain_id,
100 rpc_url,
101 } => check_compatibility(policy_client, chain_id, rpc_url).await,
102 VersionCommand::Migrate {
103 policy_client,
104 private_key,
105 chain_id,
106 rpc_url,
107 skip_check,
108 dry_run,
109 } => migrate_policy(policy_client, private_key, chain_id, rpc_url, skip_check, dry_run).await,
110 VersionCommand::Info => {
111 print_version_info();
112 Ok(())
113 }
114 }
115 }
116}
117
118async fn check_compatibility(policy_client: Address, chain_id: u64, rpc_url: Option<String>) -> Result<()> {
119 let rpc_url = rpc_url.unwrap_or_else(|| {
120 RpcProviderConfig::load(chain_id)
121 .expect("Failed to load RPC config")
122 .http
123 });
124
125 info!("Checking version compatibility for policy client: {}", policy_client);
126 info!("Protocol version: {}", PROTOCOL_VERSION);
127
128 let policy_address = get_policy_address_for_client(policy_client, rpc_url.clone()).await?;
130 info!("Policy address: {}", policy_address);
131
132 let policy_factory = get_policy_factory_for_policy(policy_address, &rpc_url).await?;
134 let policy_version = get_policy_factory_version(policy_factory, &rpc_url).await?;
135 info!("Policy factory version: {}", policy_version);
136
137 let policy_compatible = is_compatible(&policy_version, MIN_COMPATIBLE_POLICY_VERSION)?;
139 if policy_compatible {
140 info!("✓ Policy version is compatible");
141 } else {
142 warn!(
143 "✗ Policy version is INCOMPATIBLE (minimum required: v{})",
144 MIN_COMPATIBLE_POLICY_VERSION
145 );
146 }
147
148 info!("Checking policy data...");
150
151 let provider = ProviderBuilder::new().connect_http(rpc_url.parse()?);
153 let policy_contract = NewtonPolicy::new(policy_address, provider);
154
155 let policy_data_addresses = policy_contract
156 .getPolicyData()
157 .call()
158 .await
159 .context("Failed to get policy data addresses")?;
160
161 info!("Found {} policy data contracts", policy_data_addresses.len());
162
163 let mut policy_data_reports = Vec::new();
164 let mut all_compatible = policy_compatible;
165
166 for policy_data_addr in policy_data_addresses {
167 debug!("Checking policy data at {}...", policy_data_addr);
168
169 match check_policy_data_compatibility(policy_data_addr, &rpc_url).await {
170 Ok((version, compatible)) => {
171 debug!("Version: {}, Compatible: {}", version, compatible);
172 policy_data_reports.push(PolicyDataReport {
173 address: policy_data_addr,
174 factory_version: version,
175 compatible,
176 });
177 all_compatible = all_compatible && compatible;
178 }
179 Err(e) => {
180 error!("Error checking compatibility: {}", e);
181 policy_data_reports.push(PolicyDataReport {
183 address: policy_data_addr,
184 factory_version: "unknown".to_string(),
185 compatible: false,
186 });
187 all_compatible = false;
188 }
189 }
190 }
191
192 let report = CompatibilityReport {
193 protocol_version: PROTOCOL_VERSION.to_string(),
194 policy_client,
195 policy_address,
196 policy_factory_version: policy_version.clone(),
197 policy_compatible,
198 policy_data_reports,
199 all_compatible,
200 migration_required: !all_compatible,
201 };
202
203 info!("=== Compatibility Report ===");
204 info!("{}", serde_json::to_string_pretty(&report)?);
205
206 if report.migration_required {
207 warn!("Migration required!");
208 info!("To migrate your policy to the latest version:");
209 info!(" newton-cli policy migrate --policy-client {} \\", policy_client);
210 info!(" --private-key $YOUR_PRIVATE_KEY \\");
211 info!(" --chain-id {}", chain_id);
212 info!("Guide: https://docs.newt.foundation/versioning/migration");
213 std::process::exit(1);
214 } else {
215 info!("✓ All versions are compatible. No migration needed.");
216 Ok(())
217 }
218}
219
220fn print_version_info() {
221 info!("Newton Protocol Version Information");
222 info!("====================================");
223 info!("Protocol Version: {}", PROTOCOL_VERSION);
224 info!("Minimum Policy Version: {}", MIN_COMPATIBLE_POLICY_VERSION);
225 info!("Minimum Policy Data Version: {}", MIN_COMPATIBLE_POLICY_DATA_VERSION);
226 info!("Version Compatibility:");
227 info!(" - Major versions must match exactly");
228 info!(" - Minor version must be >= minimum");
229 info!(" - Patch version must be >= minimum (if minor versions match)");
230 info!("For more information, see: https://docs.newt.foundation/versioning");
231}
232
233async fn check_policy_data_compatibility(policy_data_addr: Address, rpc_url: &str) -> Result<(String, bool)> {
235 let factory = get_policy_data_factory_for_policy_data(policy_data_addr, rpc_url).await?;
236 let version = get_policy_data_factory_version(factory, rpc_url).await?;
237 let compatible = is_compatible(&version, MIN_COMPATIBLE_POLICY_DATA_VERSION)?;
238 Ok((version, compatible))
239}
240
241async fn get_policy_info(policy_address: Address, rpc_url: &str) -> Result<PolicyInfo> {
243 let provider = ProviderBuilder::new().connect_http(rpc_url.parse()?);
244 let policy_contract = NewtonPolicy::new(policy_address, provider);
245
246 let policy_cid = policy_contract
247 .getPolicyCid()
248 .call()
249 .await
250 .context("Failed to get policy CID")?;
251
252 let schema_cid = policy_contract
253 .getSchemaCid()
254 .call()
255 .await
256 .context("Failed to get schema CID")?;
257
258 let entrypoint = policy_contract
259 .getEntrypoint()
260 .call()
261 .await
262 .context("Failed to get entrypoint")?;
263
264 let metadata_cid = policy_contract
265 .getMetadataCid()
266 .call()
267 .await
268 .context("Failed to get metadata CID")?;
269
270 let policy_data = policy_contract
271 .getPolicyData()
272 .call()
273 .await
274 .context("Failed to get policy data addresses")?;
275
276 Ok(PolicyInfo {
277 policy_cid,
278 schema_cid,
279 entrypoint,
280 metadata_cid,
281 policy_data,
282 })
283}
284
285#[derive(Debug, Clone)]
286struct PolicyInfo {
287 policy_cid: String,
288 schema_cid: String,
289 entrypoint: String,
290 metadata_cid: String,
291 policy_data: Vec<Address>,
292}
293
294async fn migrate_policy(
296 policy_client: Address,
297 private_key: String,
298 chain_id: u64,
299 rpc_url: Option<String>,
300 skip_check: bool,
301 dry_run: bool,
302) -> Result<()> {
303 let rpc_url = rpc_url.unwrap_or_else(|| {
304 RpcProviderConfig::load(chain_id)
305 .expect("Failed to load RPC config")
306 .http
307 });
308
309 info!("=== Policy Migration Tool ===");
310 info!("Policy Client: {}", policy_client);
311 info!("Chain ID: {}", chain_id);
312 info!("Protocol Version: {}", PROTOCOL_VERSION);
313
314 if !skip_check {
316 info!("Step 1: Checking current compatibility...");
317
318 let policy_address = get_policy_address_for_client(policy_client, rpc_url.clone()).await?;
319 let policy_factory = get_policy_factory_for_policy(policy_address, &rpc_url).await?;
320 let current_version = get_policy_factory_version(policy_factory, &rpc_url).await?;
321
322 info!("Current policy version: {}", current_version);
323 info!("Minimum required version: {}", MIN_COMPATIBLE_POLICY_VERSION);
324
325 let is_compatible_now = is_compatible(¤t_version, MIN_COMPATIBLE_POLICY_VERSION)?;
326
327 if is_compatible_now {
328 info!("✓ Policy is already compatible!");
329 info!("No migration needed.");
330 return Ok(());
331 }
332
333 warn!("✗ Policy is incompatible and needs migration");
334 }
335
336 info!("Step 2: Reading current policy configuration...");
338
339 let policy_address = get_policy_address_for_client(policy_client, rpc_url.clone()).await?;
341 info!("Current policy address: {}", policy_address);
342
343 let policy_info = get_policy_info(policy_address, &rpc_url).await?;
345 info!("- Policy CID: {}", policy_info.policy_cid);
346 info!("- Schema CID: {}", policy_info.schema_cid);
347 info!("- Entrypoint: {}", policy_info.entrypoint);
348 info!("- Metadata CID: {}", policy_info.metadata_cid);
349 info!("- Policy Data count: {}", policy_info.policy_data.len());
350
351 info!("Step 3: Deploying new policy data (if needed) and new policy with latest factory version...");
353
354 let mut new_policy_data_addresses = Vec::new();
356 let mut incompatible_policy_data = Vec::new();
357
358 for (i, policy_data_addr) in policy_info.policy_data.iter().enumerate() {
359 debug!("Checking policy data {} at {}...", i + 1, policy_data_addr);
360
361 match check_policy_data_compatibility(*policy_data_addr, &rpc_url).await {
362 Ok((version, compatible)) => {
363 debug!("Version: {}, Compatible: {}", version, compatible);
364 if !compatible {
365 incompatible_policy_data.push(*policy_data_addr);
366 } else {
367 new_policy_data_addresses.push(*policy_data_addr);
369 }
370 }
371 Err(e) => {
372 error!("Error checking compatibility: {}", e);
373 incompatible_policy_data.push(*policy_data_addr);
374 }
375 }
376 }
377
378 if !incompatible_policy_data.is_empty() {
379 info!(
380 "Step 3a: Migrating {} incompatible policy data contracts...",
381 incompatible_policy_data.len()
382 );
383
384 if dry_run {
385 warn!(
386 "DRY RUN MODE - Would migrate {} incompatible policy data contracts:",
387 incompatible_policy_data.len()
388 );
389 for addr in &incompatible_policy_data {
390 info!("- {}", addr);
391 }
392 for _ in &incompatible_policy_data {
394 new_policy_data_addresses.push(Address::ZERO);
395 }
396 } else {
397 let private_key_str = private_key.strip_prefix("0x").unwrap_or(&private_key);
399 let signer = PrivateKeySigner::from_str(private_key_str).context("Failed to parse private key")?;
400 let signer_address = signer.address();
401 let wallet = EthereumWallet::from(signer);
402
403 let url = Url::parse(&rpc_url).context("Invalid RPC URL")?;
405 let provider = ProviderBuilder::new().wallet(wallet).connect_http(url);
406
407 for (i, policy_data_addr) in incompatible_policy_data.iter().enumerate() {
408 info!(
409 "Migrating policy data {} of {}: {}",
410 i + 1,
411 incompatible_policy_data.len(),
412 policy_data_addr
413 );
414
415 let policy_data_factory = get_policy_data_factory_for_policy_data(*policy_data_addr, &rpc_url).await?;
417 info!("Using policy data factory at: {}", policy_data_factory);
418
419 let policy_data_contract = NewtonPolicyData::new(*policy_data_addr, provider.clone());
421 let wasm_cid = policy_data_contract
422 .getWasmCid()
423 .call()
424 .await
425 .context("Failed to get WASM CID")?;
426 let secrets_schema_cid = policy_data_contract
427 .getSecretsSchemaCid()
428 .call()
429 .await
430 .context("Failed to get secrets schema")?;
431 let expire_after = policy_data_contract
432 .getExpireAfter()
433 .call()
434 .await
435 .context("Failed to get expireAfter")?;
436 let metadata_cid = policy_data_contract
437 .getMetadataCid()
438 .call()
439 .await
440 .context("Failed to get metadata CID")?;
441
442 info!("- WASM CID: {}", wasm_cid);
443 info!("- Secrets Schema CID: {}", secrets_schema_cid);
444 info!("- Expire After: {}", expire_after);
445 info!("- Metadata CID: {}", metadata_cid);
446
447 let factory_contract = NewtonPolicyDataFactory::new(policy_data_factory, provider.clone());
449
450 info!("Submitting deployPolicyData transaction...");
451 let tx = factory_contract
452 .deployPolicyData(wasm_cid, secrets_schema_cid, expire_after, metadata_cid, signer_address)
453 .send()
454 .await
455 .context("Failed to send deployPolicyData transaction")?;
456
457 info!("Transaction sent: {:?}", tx.tx_hash());
458 info!("Waiting for transaction confirmation...");
459
460 let receipt = tx.get_receipt().await.context("Failed to get transaction receipt")?;
461
462 let logs = receipt.inner.logs();
464 let policy_data_deployed_event = logs
465 .iter()
466 .find_map(|log| log.log_decode::<NewtonPolicyDataFactory::PolicyDataDeployed>().ok())
467 .ok_or_else(|| eyre::eyre!("PolicyDataDeployed event not found in transaction logs"))?;
468
469 let new_policy_data = policy_data_deployed_event.data().policyData;
470 info!("✓ New policy data deployed at: {}", new_policy_data);
471 info!(
472 "Factory version: {}",
473 policy_data_deployed_event.data().implementationVersion
474 );
475
476 new_policy_data_addresses.push(new_policy_data);
477 }
478
479 info!("✓ All incompatible policy data contracts migrated successfully");
480 }
481 } else {
482 info!("✓ All policy data contracts are compatible, no migration needed");
483 }
484
485 info!("Step 3b: Deploying new policy with latest factory version...");
487
488 let new_policy_address = if dry_run {
489 info!("DRY RUN MODE - Would deploy new policy with:");
490 info!("- Factory version: {} (latest)", PROTOCOL_VERSION);
491 info!("- Entrypoint: {}", policy_info.entrypoint);
492 info!("- Policy CID: {}", policy_info.policy_cid);
493 info!("- Schema CID: {}", policy_info.schema_cid);
494 info!("- Policy Data: {} contracts", new_policy_data_addresses.len());
495 info!("- Metadata CID: {}", policy_info.metadata_cid);
496 info!("Note: This would call NewtonPolicyFactory.deployPolicy() with the above parameters");
497 Address::ZERO } else {
499 info!("Deploying new policy using latest factory...");
500
501 let private_key_str = private_key.strip_prefix("0x").unwrap_or(&private_key);
503 let signer = PrivateKeySigner::from_str(private_key_str).context("Failed to parse private key")?;
504 let signer_address = signer.address();
505 let wallet = EthereumWallet::from(signer);
506
507 let url = Url::parse(&rpc_url).context("Invalid RPC URL")?;
509 let provider = ProviderBuilder::new().wallet(wallet).connect_http(url);
510
511 let policy_factory = get_policy_factory_for_policy(policy_address, &rpc_url).await?;
513 info!("Using policy factory at: {}", policy_factory);
514
515 let factory_contract = NewtonPolicyFactory::new(policy_factory, provider.clone());
516
517 info!("Submitting deployPolicy transaction...");
519 let tx = factory_contract
520 .deployPolicy(
521 policy_info.entrypoint.clone(),
522 policy_info.policy_cid.clone(),
523 policy_info.schema_cid.clone(),
524 new_policy_data_addresses.clone(),
525 policy_info.metadata_cid.clone(),
526 signer_address,
527 )
528 .send()
529 .await
530 .context("Failed to send deployPolicy transaction")?;
531
532 info!("Transaction sent: {:?}", tx.tx_hash());
533 info!("Waiting for transaction confirmation...");
534
535 let receipt = tx.get_receipt().await.context("Failed to get transaction receipt")?;
536
537 let logs = receipt.inner.logs();
539 let policy_deployed_event = logs
540 .iter()
541 .find_map(|log| log.log_decode::<NewtonPolicyFactory::PolicyDeployed>().ok())
542 .ok_or_else(|| eyre::eyre!("PolicyDeployed event not found in transaction logs"))?;
543
544 let new_policy = policy_deployed_event.data().policy;
545 info!("✓ New policy deployed at: {}", new_policy);
546 info!(
547 "Factory version: {}",
548 policy_deployed_event.data().implementationVersion
549 );
550
551 new_policy
552 };
553
554 info!("Step 4: Policy client architecture analysis...");
556
557 warn!("⚠️ IMPORTANT: Policy Client Architecture Limitation");
558 warn!("");
559 warn!("After analyzing the NewtonPolicyClient architecture:");
560 warn!("- The policy client stores a single immutable policy contract address");
561 warn!("- This address is set during initialization and cannot be changed");
562 warn!("- The policy address is NOT stored as upgradeable/mutable state");
563 warn!("");
564 warn!(
565 "To complete the migration to use the new policy at {}:",
566 new_policy_address
567 );
568 warn!("");
569 info!("REQUIRED: Deploy a NEW policy client");
570 info!(" 1. Deploy a new policy client contract");
571 info!(" 2. Initialize it with:");
572 info!(" - policyTaskManager: <YOUR_TASK_MANAGER_ADDRESS>");
573 info!(" - policy: {}", new_policy_address);
574 info!(" - owner: <YOUR_OWNER_ADDRESS>");
575 info!(" 3. Call setPolicy(policyConfig) on the new client to register your configuration");
576 info!(" 4. Update your application to use the new client address");
577 info!(
578 " 5. Migrate any state/assets from old client {} to new client",
579 policy_client
580 );
581 info!("");
582 info!("Example deployment using cast:");
583 info!(" # Deploy new policy client");
584 info!(" cast send <FACTORY_ADDRESS> \"deployPolicyClient(address,address,address)\" \\");
585 info!(" <TASK_MANAGER> {} <OWNER>", new_policy_address);
586 info!("");
587 info!("Alternative: If your policy client is upgradeable (proxy pattern):");
588 info!(" - Check if you can upgrade the implementation to point to new policy");
589 info!(" - Or deploy a new implementation with the new policy address");
590 info!(" - Update the proxy to use the new implementation");
591 info!("");
592
593 if !dry_run {
594 info!("✓ New policy successfully deployed at: {}", new_policy_address);
595 info!("✓ New policy includes all migrated policy data");
596 info!("✗ Policy client update requires manual deployment (see above)");
597 return Ok(());
598 }
599
600 info!("Step 5: Verifying migration...");
602
603 if dry_run {
604 info!("DRY RUN MODE - Would verify:");
605 info!("✓ New policy is deployed and accessible");
606 info!("✓ Policy client correctly references new policy");
607 info!("✓ New policy version is compatible with protocol v{}", PROTOCOL_VERSION);
608 info!("✓ All policy data is migrated or compatible");
609 info!("Verification would include:");
610 info!("1. Calling getPolicyAddress() on policy client");
611 info!("2. Checking factory version of new policy");
612 info!("3. Verifying policy configuration matches original");
613 info!("4. Testing policy functionality with a test transaction");
614 } else {
615 info!("Verifying migration...");
616
617 let current_policy = get_policy_address_for_client(policy_client, rpc_url.clone()).await?;
619 if current_policy != new_policy_address {
620 return Err(eyre::eyre!(
621 "Verification failed: Policy client points to {}, expected {}",
622 current_policy,
623 new_policy_address
624 ));
625 }
626 info!("✓ Policy client correctly references new policy");
627
628 let new_policy_factory = get_policy_factory_for_policy(new_policy_address, &rpc_url).await?;
630 let new_policy_version = get_policy_factory_version(new_policy_factory, &rpc_url).await?;
631
632 if !is_compatible(&new_policy_version, MIN_COMPATIBLE_POLICY_VERSION)? {
633 return Err(eyre::eyre!(
634 "Verification failed: New policy version {} is not compatible with minimum required {}",
635 new_policy_version,
636 MIN_COMPATIBLE_POLICY_VERSION
637 ));
638 }
639 info!("✓ New policy version {} is compatible", new_policy_version);
640
641 let new_policy_info = get_policy_info(new_policy_address, &rpc_url).await?;
643 if new_policy_info.policy_cid != policy_info.policy_cid {
644 warn!(
645 "Policy CID mismatch: old={}, new={}",
646 policy_info.policy_cid, new_policy_info.policy_cid
647 );
648 }
649 if new_policy_info.schema_cid != policy_info.schema_cid {
650 warn!(
651 "Schema CID mismatch: old={}, new={}",
652 policy_info.schema_cid, new_policy_info.schema_cid
653 );
654 }
655 if new_policy_info.entrypoint != policy_info.entrypoint {
656 warn!(
657 "Entrypoint mismatch: old={}, new={}",
658 policy_info.entrypoint, new_policy_info.entrypoint
659 );
660 }
661 if new_policy_info.metadata_cid != policy_info.metadata_cid {
662 warn!(
663 "Metadata CID mismatch: old={}, new={}",
664 policy_info.metadata_cid, new_policy_info.metadata_cid
665 );
666 }
667 info!("✓ Policy configuration verified");
668
669 if new_policy_info.policy_data.len() != new_policy_data_addresses.len() {
671 return Err(eyre::eyre!(
672 "Verification failed: Policy data count mismatch. Expected {}, got {}",
673 new_policy_data_addresses.len(),
674 new_policy_info.policy_data.len()
675 ));
676 }
677
678 for (i, addr) in new_policy_info.policy_data.iter().enumerate() {
679 if let Ok(factory) = get_policy_data_factory_for_policy_data(*addr, &rpc_url).await {
680 if let Ok(version) = get_policy_data_factory_version(factory, &rpc_url).await {
681 if !is_compatible(&version, MIN_COMPATIBLE_POLICY_DATA_VERSION)? {
682 return Err(eyre::eyre!(
683 "Verification failed: Policy data {} version {} is not compatible",
684 i + 1,
685 version
686 ));
687 }
688 }
689 }
690 }
691 info!("✓ All policy data verified as compatible");
692
693 info!("✓ Migration verified successfully!");
694 }
695
696 info!("=== Migration Summary ===");
697
698 if dry_run {
699 info!("✓ Dry run completed successfully");
700 info!("The following would be performed:");
701 info!("1. Deploy new policy at factory version {}", PROTOCOL_VERSION);
702 if !incompatible_policy_data.is_empty() {
703 info!(
704 "2. Migrate {} incompatible policy data contracts",
705 incompatible_policy_data.len()
706 );
707 } else {
708 info!("2. Reuse all existing policy data (all compatible)");
709 }
710 info!("3. Update policy client {} to reference new policy", policy_client);
711 info!("4. Verify migration success");
712 info!("");
713 info!("Remove --dry-run to execute the actual migration");
714 } else {
715 info!("✓ Migration completed successfully!");
716 info!("Summary:");
717 info!("- Old policy: {}", policy_address);
718 info!("- New policy: {}", new_policy_address);
719 info!("- Policy client: {}", policy_client);
720 if !incompatible_policy_data.is_empty() {
721 info!("- Migrated {} policy data contracts", incompatible_policy_data.len());
722 }
723 }
724
725 Ok(())
726}