Skip to content
Closed
Show file tree
Hide file tree
Changes from 3 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
5 changes: 5 additions & 0 deletions changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@ This changelog is based on [Keep A Changelog](https://keepachangelog.com/en/1.1.

# Unreleased

## Added

* Added `--skip-d-parameter` and `--skip-permissioned-candidates` flags to the `setup-main-chain-state` wizard command.
These flags allow selective deployment of smart contracts, enabling mainnet deployments that exclude legacy Haskell D-parameter contracts while retaining the option to deploy permissioned candidates contracts.

## Changed

* Updated polkadot-sdk dependency to polkadot-stable2509.
Expand Down
162 changes: 125 additions & 37 deletions toolkit/partner-chains-cli/src/setup_main_chain_state/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,61 @@ use std::marker::PhantomData;
#[cfg(test)]
mod tests;

/// Configuration for which contracts to deploy during main chain state setup.
#[derive(Clone, Debug, Default)]
pub struct ContractDeploymentConfig {
/// Skip deploying D-parameter contract (legacy Haskell contract).
pub skip_d_parameter: bool,
/// Skip deploying permissioned candidates contract.
pub skip_permissioned_candidates: bool,
}

impl ContractDeploymentConfig {
/// Returns a summary of which contracts will be deployed.
pub fn deployment_summary(&self) -> String {
let mut deploying = Vec::new();
let mut skipping = Vec::new();

if self.skip_d_parameter {
skipping.push("D-parameter");
} else {
deploying.push("D-parameter");
}

if self.skip_permissioned_candidates {
skipping.push("Permissioned Candidates");
} else {
deploying.push("Permissioned Candidates");
}

let mut summary = String::new();
if !deploying.is_empty() {
summary.push_str(&format!("Contracts to deploy: {}", deploying.join(", ")));
}
if !skipping.is_empty() {
if !summary.is_empty() {
summary.push_str(". ");
}
summary.push_str(&format!("Contracts to skip: {}", skipping.join(", ")));
}
summary
}
}

#[derive(Clone, Debug, clap::Parser)]
pub struct SetupMainChainStateCmd<T: PartnerChainRuntime> {
#[clap(flatten)]
common_arguments: crate::CommonArguments,

/// Skip deploying the D-parameter contract (legacy Haskell contract).
/// Use this flag for mainnet deployments where D-parameter is not needed.
#[clap(long)]
skip_d_parameter: bool,

/// Skip deploying the permissioned candidates contract.
#[clap(long)]
skip_permissioned_candidates: bool,

#[clap(skip)]
_phantom: PhantomData<T>,
}
Expand All @@ -45,7 +96,7 @@ impl<Keys: MaybeFromCandidateKeys> TryFrom<PermissionedCandidateData>
}
}

#[derive(Debug, PartialEq)]
#[derive(Clone, Debug, PartialEq)]
struct SortedPermissionedCandidates(Vec<PermissionedCandidateData>);

impl SortedPermissionedCandidates {
Expand All @@ -57,48 +108,85 @@ impl SortedPermissionedCandidates {

impl<T: PartnerChainRuntime> CmdRun for SetupMainChainStateCmd<T> {
fn run<C: IOContext>(&self, context: &C) -> anyhow::Result<()> {
let deployment_config = ContractDeploymentConfig {
skip_d_parameter: self.skip_d_parameter,
skip_permissioned_candidates: self.skip_permissioned_candidates,
};

// Show deployment summary if any contracts are being skipped
if deployment_config.skip_d_parameter || deployment_config.skip_permissioned_candidates {
context.print(&deployment_config.deployment_summary());
}

let chain_config = crate::config::load_chain_config(context)?;
context.print(
"This wizard will set or update D-Parameter and Permissioned Candidates on the main chain. Setting either of these costs ADA!",
);
let config_initial_authorities =
initial_permissioned_candidates_from_chain_config::<C, T::Keys>(context)?;

// Build info message based on which contracts will be deployed
let info_message = if deployment_config.skip_d_parameter
&& deployment_config.skip_permissioned_candidates
{
return Err(anyhow!(
"Both D-parameter and Permissioned Candidates are skipped. Nothing to deploy."
));
} else if deployment_config.skip_d_parameter {
"This wizard will set or update Permissioned Candidates on the main chain. Setting this costs ADA!"
} else if deployment_config.skip_permissioned_candidates {
"This wizard will set or update D-Parameter on the main chain. Setting this costs ADA!"
} else {
"This wizard will set or update D-Parameter and Permissioned Candidates on the main chain. Setting either of these costs ADA!"
};
context.print(info_message);

// Only load permissioned candidates config if we're going to deploy them
let config_initial_authorities = if !deployment_config.skip_permissioned_candidates {
Some(initial_permissioned_candidates_from_chain_config::<C, T::Keys>(context)?)
} else {
None
};

context.print("Will read the current D-Parameter and Permissioned Candidates from the main chain using Ogmios client.");
let ogmios_config = prompt_ogmios_configuration(context)?;
let offchain = context.offchain_impl(&ogmios_config)?;
let config_file_path = context.config_file_path(ConfigFile::Chain);

match get_permissioned_candidates::<C>(&offchain, &chain_config)? {
Some(candidates) if candidates == config_initial_authorities => {
context.print(&format!("Permissioned candidates in the {} file match the most recent on-chain initial permissioned candidates.", config_file_path));
},
candidates => {
print_on_chain_and_config_permissioned_candidates(
context,
candidates,
&config_initial_authorities,
);
set_candidates_on_main_chain(
self.common_arguments.retries(),
context,
&offchain,
config_initial_authorities,
chain_config.chain_parameters.genesis_utxo,
)?;
},
};
let d_parameter = get_d_parameter::<C>(&offchain, &chain_config)?;
print_on_chain_d_parameter(context, &d_parameter);
set_d_parameter_on_main_chain(
self.common_arguments.retries(),
context,
&offchain,
d_parameter.unwrap_or(DParameter {
num_permissioned_candidates: 0,
num_registered_candidates: 0,
}),
chain_config.chain_parameters.genesis_utxo,
)?;
// Handle permissioned candidates if not skipped
if let Some(ref config_initial_authorities) = config_initial_authorities {
match get_permissioned_candidates::<C>(&offchain, &chain_config)? {
Some(candidates) if candidates == *config_initial_authorities => {
context.print(&format!("Permissioned candidates in the {} file match the most recent on-chain initial permissioned candidates.", config_file_path));
},
candidates => {
print_on_chain_and_config_permissioned_candidates(
context,
candidates,
config_initial_authorities,
);
set_candidates_on_main_chain(
self.common_arguments.retries(),
context,
&offchain,
config_initial_authorities.clone(),
chain_config.chain_parameters.genesis_utxo,
)?;
},
};
}

// Handle D-parameter if not skipped
if !deployment_config.skip_d_parameter {
let d_parameter = get_d_parameter::<C>(&offchain, &chain_config)?;
print_on_chain_d_parameter(context, &d_parameter);
set_d_parameter_on_main_chain(
self.common_arguments.retries(),
context,
&offchain,
d_parameter.unwrap_or(DParameter {
num_permissioned_candidates: 0,
num_registered_candidates: 0,
}),
chain_config.chain_parameters.genesis_utxo,
)?;
}

context.print("Done. Please remember that any changes to the Cardano state can be observed immediately, but from the Partner Chain point of view they will be effective in two main chain epochs.");
Ok(())
}
Expand Down
119 changes: 119 additions & 0 deletions toolkit/partner-chains-cli/src/setup_main_chain_state/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -192,9 +192,104 @@ fn candidates_on_main_chain_are_same_as_in_config_no_updates() {
);
}

#[test]
fn skip_d_parameter_only_deploys_permissioned_candidates() {
let offchain_mock = mock_with_ariadne_parameters_not_found()
.with_upsert_permissioned_candidates(
genesis_utxo(),
&initial_permissioned_candidates(),
payment_signing_key(),
Ok(Some(MultiSigSmartContractResult::tx_submitted([2; 32]))),
);
let mock_context = MockIOContext::new()
.with_json_file(CHAIN_CONFIG_FILE_PATH, test_chain_config_content())
.with_json_file(RESOURCES_CONFIG_FILE_PATH, test_resources_config_content())
.with_json_file("payment.skey", valid_payment_signing_key_content())
.with_offchain_mocks(OffchainMocks::new_with_mock("http://localhost:1337", offchain_mock))
.with_expected_io(vec![
print_skip_d_parameter_summary_io(),
print_permissioned_candidates_only_info_io(),
get_ariadne_parameters_io(),
print_permissioned_candidates_are_not_set(),
prompt_permissioned_candidates_update_io(true),
upsert_permissioned_candidates_io(),
print_post_update_info_io(),
]);
let result = setup_main_chain_state_cmd_skip_d_parameter().run(&mock_context);
result.expect("should succeed");
}

#[test]
fn skip_permissioned_candidates_only_deploys_d_parameter() {
let offchain_mock = mock_with_ariadne_parameters_not_found().with_upsert_d_param(
genesis_utxo(),
new_d_parameter(),
payment_signing_key(),
Ok(Some(MultiSigSmartContractResult::tx_submitted([1; 32]))),
);
let mock_context = MockIOContext::new()
.with_json_file(CHAIN_CONFIG_FILE_PATH, test_chain_config_content())
.with_json_file(RESOURCES_CONFIG_FILE_PATH, test_resources_config_content())
.with_json_file("payment.skey", valid_payment_signing_key_content())
.with_offchain_mocks(OffchainMocks::new_with_mock("http://localhost:1337", offchain_mock))
.with_expected_io(vec![
print_skip_permissioned_candidates_summary_io(),
print_d_parameter_only_info_io(),
get_ariadne_parameters_io(),
prompt_d_parameter_update_io(true),
insert_d_parameter_io(),
print_post_update_info_io(),
]);
let result = setup_main_chain_state_cmd_skip_permissioned().run(&mock_context);
result.expect("should succeed");
}

#[test]
fn skip_both_contracts_fails_with_error() {
let mock_context = MockIOContext::new()
.with_json_file(CHAIN_CONFIG_FILE_PATH, test_chain_config_content())
.with_json_file(RESOURCES_CONFIG_FILE_PATH, test_resources_config_content())
.with_expected_io(vec![print_skip_both_summary_io()]);
let result = setup_main_chain_state_cmd_skip_both().run(&mock_context);
let err = result.expect_err("should return error");
assert!(
err.to_string()
.contains("Both D-parameter and Permissioned Candidates are skipped")
);
}

fn setup_main_chain_state_cmd() -> SetupMainChainStateCmd<MockRuntime> {
SetupMainChainStateCmd {
common_arguments: CommonArguments { retry_delay_seconds: 5, retry_count: 59 },
skip_d_parameter: false,
skip_permissioned_candidates: false,
_phantom: std::marker::PhantomData,
}
}

fn setup_main_chain_state_cmd_skip_d_parameter() -> SetupMainChainStateCmd<MockRuntime> {
SetupMainChainStateCmd {
common_arguments: CommonArguments { retry_delay_seconds: 5, retry_count: 59 },
skip_d_parameter: true,
skip_permissioned_candidates: false,
_phantom: std::marker::PhantomData,
}
}

fn setup_main_chain_state_cmd_skip_permissioned() -> SetupMainChainStateCmd<MockRuntime> {
SetupMainChainStateCmd {
common_arguments: CommonArguments { retry_delay_seconds: 5, retry_count: 59 },
skip_d_parameter: false,
skip_permissioned_candidates: true,
_phantom: std::marker::PhantomData,
}
}

fn setup_main_chain_state_cmd_skip_both() -> SetupMainChainStateCmd<MockRuntime> {
SetupMainChainStateCmd {
common_arguments: CommonArguments { retry_delay_seconds: 5, retry_count: 59 },
skip_d_parameter: true,
skip_permissioned_candidates: true,
_phantom: std::marker::PhantomData,
}
}
Expand All @@ -205,6 +300,30 @@ fn print_info_io() -> MockIO {
)
}

fn print_permissioned_candidates_only_info_io() -> MockIO {
MockIO::print(
"This wizard will set or update Permissioned Candidates on the main chain. Setting this costs ADA!",
)
}

fn print_d_parameter_only_info_io() -> MockIO {
MockIO::print(
"This wizard will set or update D-Parameter on the main chain. Setting this costs ADA!",
)
}

fn print_skip_d_parameter_summary_io() -> MockIO {
MockIO::print("Contracts to deploy: Permissioned Candidates. Contracts to skip: D-parameter")
}

fn print_skip_permissioned_candidates_summary_io() -> MockIO {
MockIO::print("Contracts to deploy: D-parameter. Contracts to skip: Permissioned Candidates")
}

fn print_skip_both_summary_io() -> MockIO {
MockIO::print("Contracts to skip: D-parameter, Permissioned Candidates")
}

fn get_ariadne_parameters_io() -> MockIO {
MockIO::Group(vec![
MockIO::print(
Expand Down
Loading