Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

change: ETCM-9063 add update-governance offchain and command #329

Draft
wants to merge 11 commits into
base: master
Choose a base branch
from
Draft
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
4 changes: 4 additions & 0 deletions toolkit/cli/smart-contracts-commands/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ pub mod d_parameter;
pub mod get_scripts;
pub mod init_governance;
pub mod register;
pub mod update_governance;

#[derive(Clone, Debug, clap::Subcommand)]
#[allow(clippy::large_enum_variant)]
Expand All @@ -12,6 +13,8 @@ pub enum SmartContractsCmd {
GetScripts(get_scripts::GetScripts),
/// Initialize Partner Chain governance
InitGovernance(init_governance::InitGovernanceCmd),
/// Update Partner Chain governance
UpdateGovernance(update_governance::UpdateGovernanceCmd),
/// Upsert DParameter
UpsertDParameter(d_parameter::UpsertDParameterCmd),
/// Register candidate
Expand All @@ -33,6 +36,7 @@ impl SmartContractsCmd {
pub async fn execute(self) -> CmdResult<()> {
match self {
Self::InitGovernance(cmd) => cmd.execute().await,
Self::UpdateGovernance(cmd) => cmd.execute().await,
Self::GetScripts(cmd) => cmd.execute().await,
Self::UpsertDParameter(cmd) => cmd.execute().await,
Self::Register(cmd) => cmd.execute().await,
Expand Down
40 changes: 40 additions & 0 deletions toolkit/cli/smart-contracts-commands/src/update_governance.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
use jsonrpsee::http_client::HttpClient;
use partner_chains_cardano_offchain::{
await_tx::FixedDelayRetries, update_governance::run_update_governance,
};
use sidechain_domain::{MainchainAddressHash, UtxoId};

use crate::read_private_key_from_file;

#[derive(Clone, Debug, clap::Parser)]
pub struct UpdateGovernanceCmd {
#[clap(flatten)]
common_arguments: crate::CommonArguments,
/// Governance authority hash to be set.
#[arg(long, short = 'g')]
new_governance_authority: MainchainAddressHash,
/// Path to the Cardano Payment Key file.
#[arg(long, short = 'k')]
payment_key_file: String,
/// Genesis UTXO of the new chain, it will be spent durning initialization. If not set, then one will be selected from UTXOs of the payment key.
#[arg(long, short = 'c')]
genesis_utxo: UtxoId,
}

impl UpdateGovernanceCmd {
pub async fn execute(self) -> crate::CmdResult<()> {
let payment_key = read_private_key_from_file(&self.payment_key_file)?;
let client = HttpClient::builder().build(self.common_arguments.ogmios_url)?;

run_update_governance(
self.new_governance_authority,
payment_key,
self.genesis_utxo,
&client,
FixedDelayRetries::two_minutes(),
)
.await?;

Ok(())
}
}
2 changes: 1 addition & 1 deletion toolkit/offchain/src/init_governance/transaction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ fn mint_witness(
))
}

fn version_oracle_datum_output(
pub(crate) fn version_oracle_datum_output(
version_oracle_validator: PlutusScript,
version_oracle_policy: PlutusScript,
multi_sig_policy: PlutusScript,
Expand Down
2 changes: 2 additions & 0 deletions toolkit/offchain/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ pub mod scripts_data;
pub mod test_values;
/// Module for interaction with the untyped plutus scripts
pub mod untyped_plutus;
/// Supports governance updates
pub mod update_governance;

#[derive(Clone, Debug, PartialEq, Eq)]
pub enum OffchainError {
Expand Down
275 changes: 275 additions & 0 deletions toolkit/offchain/src/update_governance/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,275 @@
#![allow(unused_imports)]
#![allow(unused_variables)]
#![allow(dead_code)]
use crate::{
await_tx::{self, AwaitTx},
csl::{
convert_ex_units, convert_value, empty_asset_name, InputsBuilderExt, OgmiosUtxoExt,
TransactionBuilderExt, TransactionContext,
},
init_governance::transaction::{
multisig_governance_policy_configuration, version_oracle_datum_output,
},
plutus_script::PlutusScript,
scripts_data::version_scripts_and_address,
};
use anyhow::{anyhow, Context};
use cardano_serialization_lib::{
Coin, DatumSource, ExUnits, Int, LanguageKind, MintBuilder, MintWitness, MultiAsset,
PlutusData, PlutusScriptSource, PlutusWitness, Redeemer, RedeemerTag, Transaction,
TransactionBuilder, TransactionOutputBuilder, TxInputsBuilder,
};
use ogmios_client::{
query_ledger_state::{QueryLedgerState, QueryUtxoByUtxoId},
query_network::QueryNetwork,
transactions::Transactions,
types::{OgmiosTx, OgmiosUtxo},
};
use partner_chains_plutus_data::version_oracle::VersionOracleDatum;
use sidechain_domain::{
byte_string::ByteString, MainchainAddressHash, MainchainPrivateKey, McTxHash, UtxoId, UtxoIndex,
};

#[cfg(test)]
mod test_values;

pub async fn run_update_governance<
T: QueryLedgerState + Transactions + QueryNetwork + QueryUtxoByUtxoId,
A: AwaitTx,
>(
new_governance_authority: MainchainAddressHash,
payment_key: MainchainPrivateKey,
genesis_utxo_id: UtxoId,
client: &T,
await_tx: A,
) -> anyhow::Result<OgmiosTx> {
let tx_context = TransactionContext::for_payment_key(payment_key.0, client).await?;
let (version_validator, version_policy, version_validator_address) =
version_scripts_and_address(genesis_utxo_id, tx_context.network)?;

log::info!(
"Querying version oracle validator address ({version_validator_address}) for utxos..."
);
let version_utxos = client.query_utxos(&[version_validator_address.clone()]).await?;

// 1. find the utxo with the {VersionOracle, 32} datum under the VersionOracleValidator address
// (it also contains the 1 governance token)
let governance_utxo = version_utxos
.into_iter()
.find(|utxo| {
utxo.value.native_tokens.iter().any(|(policy, multiasset)| {
*policy == version_policy.script_hash()
&& multiasset.iter().any(|asset| asset.name == "Version oracle".as_bytes())
})
})
.ok_or_else(|| {
anyhow!("Failed to find the governance utxo at validator address {version_validator_address}")
})?;

log::info!(
"Governance utxo found: {}#{}",
ByteString(governance_utxo.transaction.id.to_vec()).to_hex_string(),
governance_utxo.index,
);

let tx = update_governance_tx(
raw_scripts::MULTI_SIG_POLICY,
raw_scripts::VERSION_ORACLE_VALIDATOR,
raw_scripts::VERSION_ORACLE_POLICY,
genesis_utxo_id,
governance_utxo.clone(),
new_governance_authority,
&tx_context,
ExUnits::new(&0u64.into(), &0u64.into()),
ExUnits::new(&0u64.into(), &0u64.into()),
)?;

let mut costs = client.evaluate_transaction(&tx.to_bytes()).await?;
costs.sort_by_key(|cost| cost.validator.index);

let [mint_cost, spend_cost] = costs.as_slice() else {
return Err(anyhow!("Error retrieving witness costs: expected 2 entries."));
};

let tx = update_governance_tx(
raw_scripts::MULTI_SIG_POLICY,
raw_scripts::VERSION_ORACLE_VALIDATOR,
raw_scripts::VERSION_ORACLE_POLICY,
genesis_utxo_id,
governance_utxo,
new_governance_authority,
&tx_context,
convert_ex_units(&mint_cost.budget),
convert_ex_units(&spend_cost.budget),
)?;
let signed_tx = tx_context.sign(&tx);

let response = client.submit_transaction(&signed_tx.to_bytes()).await?;
println!("Submitted transaction: {}", hex::encode(response.transaction.id));

await_tx
.await_tx_output(
client,
UtxoId { tx_hash: McTxHash(response.transaction.id), index: UtxoIndex(0) },
)
.await?;

Ok(response.transaction)
}

fn update_governance_tx(
multi_sig_policy: &[u8],
version_oracle_validator: &[u8],
version_oracle_policy: &[u8],
genesis_utxo: UtxoId,
governance_utxo: OgmiosUtxo,
new_governance_authority: MainchainAddressHash,
tx_context: &TransactionContext,
mint_ex_units: ExUnits,
spend_ex_units: ExUnits,
) -> anyhow::Result<Transaction> {
let multi_sig_policy =
PlutusScript::from_wrapped_cbor(multi_sig_policy, LanguageKind::PlutusV2)?
.apply_uplc_data(multisig_governance_policy_configuration(new_governance_authority))?;
let version_oracle_validator =
PlutusScript::from_wrapped_cbor(version_oracle_validator, LanguageKind::PlutusV2)?
.apply_data(genesis_utxo)?;
let version_oracle_policy =
PlutusScript::from_wrapped_cbor(version_oracle_policy, LanguageKind::PlutusV2)?
.apply_data(genesis_utxo)?
.apply_uplc_data(version_oracle_validator.address_data(tx_context.network)?)?;

let config = crate::csl::get_builder_config(tx_context)?;
let mut tx_builder = TransactionBuilder::new(&config);

tx_builder.add_mint_one_script_token(&multi_sig_policy, mint_ex_units)?;

tx_builder.add_output(&version_oracle_datum_output(
version_oracle_validator.clone(),
version_oracle_policy.clone(),
multi_sig_policy.clone(),
tx_context.network,
tx_context,
)?)?;

tx_builder.set_inputs(&{
let mut inputs = TxInputsBuilder::new();
inputs.add_script_utxo_input(
&governance_utxo,
&version_oracle_validator,
spend_ex_units,
)?;

inputs
});

Ok(tx_builder.balance_update_and_build(tx_context)?)
}

#[cfg(test)]
mod test {
use super::*;
use crate::test_values::protocol_parameters;
use cardano_serialization_lib::*;
use hex_literal::hex;
use ogmios_client::types::{Asset, Datum, OgmiosValue};
use pretty_assertions::assert_eq;

fn payment_key_domain() -> MainchainPrivateKey {
MainchainPrivateKey(hex!(
"94f7531c9639654b77fa7e10650702b6937e05cd868f419f54bcb8368e413f04"
))
}

fn payment_key() -> PrivateKey {
PrivateKey::from_normal_bytes(&payment_key_domain().0).unwrap()
}

fn test_address_bech32() -> String {
"addr_test1vpmd59ajuvm34d723r8q2qzyz9ylq0x9pygqn7vun8qgpkgs7y5hw".into()
}

fn payment_utxo() -> OgmiosUtxo {
OgmiosUtxo {
transaction: OgmiosTx {
id: hex!("1bc6eeebd308616860384b9748801d586a93a7291faedb464e73e9f6355e392b"),
},
index: 0,
value: OgmiosValue { lovelace: 9922945937, native_tokens: [].into() },
address: test_address_bech32(),

..OgmiosUtxo::default()
}
}

fn governance_utxo() -> OgmiosUtxo {
OgmiosUtxo {
transaction: OgmiosTx {
id: hex!("40db7e41a67c5c560aa3d4bce389cb2eecd7c5f88188dbe472eb95069d1357b3"),
},
index: 0,
value: OgmiosValue {
lovelace: 2945937,
// native_tokens: [].into(),
native_tokens: [(
hex!("c11dee532646a9b226aac75f77ea7ae5fba9270674327c882794701e"),
vec![Asset { name: hex!("56657273696f6e206f7261636c65").to_vec(), amount: 1 }],
)]
.into(),
},
address: "addr_test1wqrlc9gqxnyyzwyzgtvrf77famec87zme6zfxgq2sq4up8gccxfnc".to_string(),
datum: Some(Datum {
bytes: hex!("9f1820581cc11dee532646a9b226aac75f77ea7ae5fba9270674327c882794701eff")
.to_vec(),
}),
..OgmiosUtxo::default()
}
}

fn tx_context() -> TransactionContext {
TransactionContext {
payment_key: payment_key(),
payment_key_utxos: vec![payment_utxo()],
network: NetworkIdKind::Testnet,
protocol_parameters: protocol_parameters(),
}
}

fn genesis_utxo() -> OgmiosUtxo {
OgmiosUtxo {
transaction: OgmiosTx {
id: hex!("071ce86f4b21214f35df5e7f2931a10b67f4a11360e56c1e2bcd7978980adca5"),
},
index: 1,
value: OgmiosValue::new_lovelace(10000),
address: test_address_bech32(),

..Default::default()
}
}

#[test]
fn update_governance_test() {
let tx: serde_json::Value = serde_json::from_str(
&update_governance_tx(
test_values::MULTI_SIG_POLICY,
test_values::VERSION_ORACLE_VALIDATOR,
test_values::VERSION_ORACLE_POLICY,
genesis_utxo().to_domain(),
governance_utxo(),
MainchainAddressHash(hex_literal::hex!(
"76da17b2e3371ab7ca88ce0500441149f03cc5091009f99c99c080d9"
)),
&tx_context(),
ExUnits::new(&0u64.into(), &0u64.into()),
ExUnits::new(&0u64.into(), &0u64.into()),
)
.unwrap()
.to_json()
.unwrap(),
)
.unwrap();

assert_eq!(tx, test_values::test_update_governance_tx())
}
}
Loading
Loading