partner_chains_smart_contracts_commands/
lib.rs

1//! This crate provides types and functions that can be used to create a CLI
2//! for managing the mainchain smart contracts relevant for a given partner
3//! chain instance.
4//!
5//! ## Common Arguments
6//!
7//! Most type commands (usualy ending in "Cmd") take a [CommonArguments]
8//! struct as argument. It stores the information neccessary for connecting
9//! to the Ogmios server and retrying the operations like checking if a transaction
10//! is included in the blockchain.
11//!
12//! ## Subcommands
13//!
14//! Each subcommand has its own command type, which implements the [clap::Parser]
15//! trait. Each command type also has a `execute` method, which is used to execute
16//! the command.
17//!
18//! Subcommands can execute transactions on the mainchain, query the mainchain
19//! and also provide other utilities for managing the smart contracts.
20//!
21//! ## Result types
22//!
23//! Most commands return result of [serde_json::Value].
24//! The returned value is printed to the ouptut at the end of the command execution.
25use ogmios_client::jsonrpsee::{OgmiosClients, client_for_url};
26use partner_chains_cardano_offchain::{
27	await_tx::FixedDelayRetries,
28	cardano_keys::{CardanoKeyFileContent, CardanoPaymentSigningKey},
29	multisig::MultiSigSmartContractResult,
30};
31use serde::Serialize;
32use sidechain_domain::*;
33use std::time::Duration;
34
35pub mod assemble_tx;
36pub mod bridge;
37pub mod d_parameter;
38pub mod get_scripts;
39pub mod governance;
40pub mod governed_map;
41pub mod permissioned_candidates;
42pub mod register;
43pub mod reserve;
44pub mod sign_tx;
45pub mod versioning;
46
47#[derive(Clone, Debug, clap::Subcommand)]
48#[allow(clippy::large_enum_variant)]
49/// Commands for managing the mainchain smart contracts
50pub enum SmartContractsCmd {
51	/// Prints validator addresses and policy IDs of Partner Chain smart contracts
52	GetScripts(get_scripts::GetScripts),
53	/// Upsert DParameter
54	UpsertDParameter(d_parameter::UpsertDParameterCmd),
55	/// Upsert Permissioned Candidates
56	UpsertPermissionedCandidates(permissioned_candidates::UpsertPermissionedCandidatesCmd),
57	/// Register candidate
58	Register(register::RegisterCmd),
59	/// Deregister candidate
60	Deregister(register::DeregisterCmd),
61	#[command(subcommand)]
62	/// Commands for management of rewards reserve
63	Reserve(reserve::ReserveCmd),
64	#[command(subcommand)]
65	/// Commands for management of on-chain governance
66	Governance(governance::GovernanceCmd),
67	/// Assemble and submit a transaction
68	AssembleAndSubmitTx(assemble_tx::AssembleAndSubmitCmd),
69	/// Sign a transaction CBOR using a payment signing key
70	SignTx(sign_tx::SignTxCmd),
71	#[command(subcommand)]
72	/// Manage the Governed Map key-value store on Cardano
73	GovernedMap(governed_map::GovernedMapCmd),
74	#[command(subcommand)]
75	/// Send token to bridge contract
76	Bridge(bridge::BridgeCmd),
77	/// Upsert versioned smart contract
78	UpsertScript(versioning::UpsertScriptCmd),
79}
80
81#[derive(Clone, Debug, clap::Parser)]
82#[command(author, version, about, long_about = None)]
83/// Common command arguments
84pub struct CommonArguments {
85	#[arg(default_value = "ws://localhost:1337", long, short = 'O', env)]
86	/// URL of the Ogmios server
87	ogmios_url: String,
88	#[arg(default_value = "180", long, env)]
89	/// Timeout in seconds for Ogmios requests.
90	ogmios_requests_timeout_seconds: u64,
91	#[arg(default_value = "5", long)]
92	/// Delay between retries in seconds. System will wait this long between
93	/// queries checking if transaction is included in the blockchain.
94	retry_delay_seconds: u64,
95	#[arg(default_value = "59", long)]
96	/// Number of retries. After transaction is submitted, system will try to check
97	/// if it's included in the blockchain this many times.
98	retry_count: usize,
99}
100
101impl CommonArguments {
102	/// Connects to the Ogmios server and returns a client
103	pub async fn get_ogmios_client(&self) -> crate::CmdResult<OgmiosClients> {
104		Ok(client_for_url(
105			&self.ogmios_url,
106			Duration::from_secs(self.ogmios_requests_timeout_seconds),
107		)
108		.await
109		.map_err(|e| format!("Failed to connect to Ogmios at {} with: {}", &self.ogmios_url, e))?)
110	}
111
112	/// Builds a `FixedDelayRetries` instance for retrying failed operations
113	pub fn retries(&self) -> FixedDelayRetries {
114		FixedDelayRetries::new(Duration::from_secs(self.retry_delay_seconds), self.retry_count)
115	}
116}
117
118/// Result type for commands
119type CmdResult<T> = Result<T, Box<dyn std::error::Error + Send + Sync>>;
120/// Result type for subcommands
121type SubCmdResult = Result<serde_json::Value, Box<dyn std::error::Error + Send + Sync>>;
122
123impl SmartContractsCmd {
124	/// Executes the internal command, and prints the result
125	pub async fn execute(self) -> CmdResult<()> {
126		let result: serde_json::Value = match self {
127			Self::Governance(cmd) => cmd.execute().await,
128			Self::GetScripts(cmd) => cmd.execute().await,
129			Self::UpsertDParameter(cmd) => cmd.execute().await,
130			Self::UpsertPermissionedCandidates(cmd) => cmd.execute().await,
131			Self::Register(cmd) => cmd.execute().await,
132			Self::Deregister(cmd) => cmd.execute().await,
133			Self::Reserve(cmd) => cmd.execute().await,
134			Self::AssembleAndSubmitTx(cmd) => cmd.execute().await,
135			Self::SignTx(cmd) => cmd.execute().await,
136			Self::GovernedMap(cmd) => cmd.execute().await,
137			Self::Bridge(cmd) => cmd.execute().await,
138			Self::UpsertScript(cmd) => cmd.execute().await,
139		}?;
140		println!("{}", result);
141		Ok(())
142	}
143
144	/// Executes the internal command in a blocking manner
145	pub fn execute_blocking(self) -> CmdResult<()> {
146		tokio::runtime::Runtime::new()?.block_on(self.execute())
147	}
148}
149
150/// Make a JSON object for a transaction hash. By default [McTxHash] is serialized
151/// to a JSONString.
152pub(crate) fn transaction_submitted_json(tx_hash: McTxHash) -> serde_json::Value {
153	serde_json::json!(MultiSigSmartContractResult::TransactionSubmitted(tx_hash))
154}
155
156/// Converts an optional value to a JSON object. None values are converted to an empty object.
157pub(crate) fn option_to_json<T: Serialize>(value_opt: Option<T>) -> serde_json::Value {
158	match value_opt {
159		Some(value) => serde_json::json!(value),
160		None => serde_json::json!({}),
161	}
162}
163
164#[derive(Clone, Debug, clap::Parser)]
165pub(crate) struct PaymentFilePath {
166	#[arg(long, short = 'k')]
167	/// Path to the Cardano Signing Key file used to sign transaction(s) and pay for them
168	payment_key_file: String,
169}
170
171impl PaymentFilePath {
172	/// Reads the Cardano Signing Key file from the given path and returns a [CardanoPaymentSigningKey]
173	pub(crate) fn read_key(&self) -> CmdResult<CardanoPaymentSigningKey> {
174		let key_file = CardanoKeyFileContent::parse_file(&self.payment_key_file)?;
175		Ok(CardanoPaymentSigningKey::try_from(key_file)?)
176	}
177}
178
179#[derive(Clone, Debug, clap::Parser)]
180pub(crate) struct GenesisUtxo {
181	#[arg(long, short = 'c')]
182	/// Genesis UTXO that identifies the partner chain
183	genesis_utxo: UtxoId,
184}
185
186impl From<GenesisUtxo> for UtxoId {
187	fn from(value: GenesisUtxo) -> Self {
188		value.genesis_utxo
189	}
190}
191
192#[cfg(test)]
193mod test {
194	use std::str::FromStr;
195
196	use hex_literal::hex;
197	use sidechain_domain::{
198		AuraPublicKey, CandidateKey, CandidateKeys, GrandpaPublicKey, PermissionedCandidateData,
199		SidechainPublicKey,
200	};
201
202	#[test]
203	fn parse_partnerchain_public_keys_legacy_format_without_0x_prefix() {
204		let input = "039799ff93d184146deacaa455dade51b13ed16f23cdad11d1ad6af20103391180:e85534c93315d60f808568d1dce5cb9e8ba6ed0b204209c5cc8f3bec56c10b73:cdf3e5b33f53c8b541bbaea383225c45654f24de38c585725f3cff25b2802f55";
205		assert_eq!(PermissionedCandidateData::from_str(input).unwrap(), expected_public_keys())
206	}
207
208	#[test]
209	fn parse_partnerchain_public_keys_legacy_format_with_0x_prefix() {
210		let input = "0x039799ff93d184146deacaa455dade51b13ed16f23cdad11d1ad6af20103391180:0xe85534c93315d60f808568d1dce5cb9e8ba6ed0b204209c5cc8f3bec56c10b73:0xcdf3e5b33f53c8b541bbaea383225c45654f24de38c585725f3cff25b2802f55";
211		assert_eq!(PermissionedCandidateData::from_str(input).unwrap(), expected_public_keys())
212	}
213
214	#[test]
215	fn parse_partnerchain_public_keys_generic_format_without_0x_prefix() {
216		let input = "039799ff93d184146deacaa455dade51b13ed16f23cdad11d1ad6af20103391180,aura:e85534c93315d60f808568d1dce5cb9e8ba6ed0b204209c5cc8f3bec56c10b73,gran:cdf3e5b33f53c8b541bbaea383225c45654f24de38c585725f3cff25b2802f55";
217		assert_eq!(PermissionedCandidateData::from_str(input).unwrap(), expected_public_keys())
218	}
219
220	#[test]
221	fn parse_partnerchain_public_keys_generic_format_with_0x_prefix() {
222		let input = "0x039799ff93d184146deacaa455dade51b13ed16f23cdad11d1ad6af20103391180,aura:0xe85534c93315d60f808568d1dce5cb9e8ba6ed0b204209c5cc8f3bec56c10b73,gran:0xcdf3e5b33f53c8b541bbaea383225c45654f24de38c585725f3cff25b2802f55";
223		assert_eq!(PermissionedCandidateData::from_str(input).unwrap(), expected_public_keys())
224	}
225
226	#[test]
227	fn key_id_can_contain_0x() {
228		let input = "0x0102,0xxd:0xffff";
229		assert_eq!(
230			PermissionedCandidateData::from_str(input).unwrap(),
231			PermissionedCandidateData {
232				sidechain_public_key: SidechainPublicKey([1, 2].to_vec()),
233				keys: CandidateKeys(vec![CandidateKey {
234					id: *b"0xxd",
235					bytes: [255, 255].to_vec()
236				}])
237			}
238		)
239	}
240
241	fn expected_public_keys() -> PermissionedCandidateData {
242		PermissionedCandidateData {
243			sidechain_public_key: SidechainPublicKey(
244				hex!("039799ff93d184146deacaa455dade51b13ed16f23cdad11d1ad6af20103391180").to_vec(),
245			),
246			keys: CandidateKeys(vec![
247				AuraPublicKey(
248					hex!("e85534c93315d60f808568d1dce5cb9e8ba6ed0b204209c5cc8f3bec56c10b73")
249						.to_vec(),
250				)
251				.into(),
252				GrandpaPublicKey(
253					hex!("cdf3e5b33f53c8b541bbaea383225c45654f24de38c585725f3cff25b2802f55")
254						.to_vec(),
255				)
256				.into(),
257			]),
258		}
259	}
260}