From 1cf57054b2313ad0bfc4d732a60cccb80cef9733 Mon Sep 17 00:00:00 2001 From: Krisztian Pinter Date: Thu, 16 Jan 2025 11:49:29 +0100 Subject: [PATCH] wip --- toolkit/offchain/src/csl.rs | 5 +- toolkit/offchain/src/reserve/mod.rs | 34 ++++- .../offchain/src/reserve/update_settings.rs | 134 +++++++++++++++++- toolkit/offchain/src/scripts_data.rs | 8 +- toolkit/offchain/tests/integration_tests.rs | 56 +++++++- 5 files changed, 216 insertions(+), 21 deletions(-) diff --git a/toolkit/offchain/src/csl.rs b/toolkit/offchain/src/csl.rs index 2e7a86f67..4f7d1de32 100644 --- a/toolkit/offchain/src/csl.rs +++ b/toolkit/offchain/src/csl.rs @@ -88,6 +88,7 @@ pub(crate) fn get_builder_config( .ref_script_coins_per_byte(&convert_reference_script_costs( &protocol_parameters.min_fee_reference_scripts.clone(), )?) + .deduplicate_explicit_ref_inputs_with_regular_inputs(true) .build() } @@ -276,7 +277,7 @@ impl MainchainPrivateKeyExt for MainchainPrivateKey { } } -pub(crate) struct TransactionContext { +pub struct TransactionContext { /// This key is added as required signer and used to sign the transaction. pub(crate) payment_key: PrivateKey, /// Used to pay for the transaction fees and uncovered transaction inputs @@ -289,7 +290,7 @@ pub(crate) struct TransactionContext { impl TransactionContext { /// Gets `TransactionContext`, having UTXOs for the given payment key and the network configuration, /// required to perform most of the partner-chains smart contract operations. - pub(crate) async fn for_payment_key( + pub async fn for_payment_key( payment_signing_key: [u8; 32], client: &C, ) -> Result { diff --git a/toolkit/offchain/src/reserve/mod.rs b/toolkit/offchain/src/reserve/mod.rs index 9ec776825..54deae0e9 100644 --- a/toolkit/offchain/src/reserve/mod.rs +++ b/toolkit/offchain/src/reserve/mod.rs @@ -2,6 +2,7 @@ use crate::{csl::TransactionContext, scripts_data}; use anyhow::anyhow; +use cardano_serialization_lib::PlutusData; use init::find_script_utxo; use ogmios_client::{ query_ledger_state::{QueryLedgerState, QueryUtxoByUtxoId}, @@ -9,6 +10,7 @@ use ogmios_client::{ transactions::Transactions, types::OgmiosUtxo, }; +use partner_chains_plutus_data::reserve::ReserveDatum; use sidechain_domain::UtxoId; pub mod create; @@ -17,14 +19,34 @@ pub mod init; pub mod update_settings; #[derive(Clone, Debug)] -pub(crate) struct ReserveData { - pub(crate) scripts: scripts_data::ReserveScripts, - pub(crate) auth_policy_version_utxo: OgmiosUtxo, - pub(crate) validator_version_utxo: OgmiosUtxo, - pub(crate) illiquid_circulation_supply_validator_version_utxo: OgmiosUtxo, +pub struct ReserveData { + pub scripts: scripts_data::ReserveScripts, + pub auth_policy_version_utxo: OgmiosUtxo, + pub validator_version_utxo: OgmiosUtxo, + pub illiquid_circulation_supply_validator_version_utxo: OgmiosUtxo, } -pub(crate) async fn get_reserve_data< +impl ReserveData { + pub async fn get_reserve_settings< + T: QueryLedgerState + Transactions + QueryNetwork + QueryUtxoByUtxoId, + >( + &self, + ctx: &TransactionContext, + client: &T, + ) -> Result, anyhow::Error> { + let validator_address = self.scripts.validator.address(ctx.network).to_bech32(None)?; + let validator_utxos = client.query_utxos(&[validator_address]).await?; + + Ok(validator_utxos.into_iter().find_map(|utxo| { + utxo.clone() + .datum + .and_then(|d| PlutusData::from_bytes(d.bytes).ok()) + .and_then(|d| ReserveDatum::try_from(d).ok()) + })) + } +} + +pub async fn get_reserve_data< T: QueryLedgerState + Transactions + QueryNetwork + QueryUtxoByUtxoId, >( genesis_utxo: UtxoId, diff --git a/toolkit/offchain/src/reserve/update_settings.rs b/toolkit/offchain/src/reserve/update_settings.rs index d7eaba5e8..18bbb1279 100644 --- a/toolkit/offchain/src/reserve/update_settings.rs +++ b/toolkit/offchain/src/reserve/update_settings.rs @@ -16,17 +16,127 @@ //! * Reserve Validator Version Utxo //! * Governance Policy Script +use super::get_reserve_data; use super::ReserveData; -use crate::{csl::*, init_governance::GovernanceData}; +use crate::{ + await_tx::AwaitTx, csl::*, init_governance::get_governance_data, + init_governance::GovernanceData, +}; +use anyhow::anyhow; use cardano_serialization_lib::*; +use ogmios_client::transactions::OgmiosEvaluateTransactionResponse; +use ogmios_client::{ + query_ledger_state::{QueryLedgerState, QueryUtxoByUtxoId}, + query_network::QueryNetwork, + transactions::Transactions, +}; use partner_chains_plutus_data::reserve::{ReserveDatum, ReserveRedeemer}; +use sidechain_domain::{McTxHash, ScriptHash, UtxoId}; +use std::collections::HashMap; + +pub async fn update_reserve_settings< + T: QueryLedgerState + Transactions + QueryNetwork + QueryUtxoByUtxoId, + A: AwaitTx, +>( + genesis_utxo: UtxoId, + payment_key: [u8; 32], + total_accrued_function_script_hash: ScriptHash, + reserve_initial_incentive_amount: u64, + client: &T, + await_tx: &A, +) -> anyhow::Result { + let ctx = TransactionContext::for_payment_key(payment_key, client).await?; + let governance = get_governance_data(genesis_utxo, client).await?; + let reserve = get_reserve_data(genesis_utxo, &ctx, client).await?; + let mut reserve_settings = reserve + .get_reserve_settings(&ctx, client) + .await? + .expect("reserve settings must exist"); + + reserve_settings.mutable_settings.total_accrued_function_script_hash = + total_accrued_function_script_hash; + reserve_settings.mutable_settings.initial_incentive = reserve_initial_incentive_amount; + + let tx_to_evaluate = update_reserve_settings_tx( + &reserve_settings, + &reserve, + &governance, + &zero_ex_units(), + &zero_ex_units(), + &ctx, + )?; + + let evaluate_response = client.evaluate_transaction(&tx_to_evaluate.to_bytes()).await?; + + let costs = match_costs( + &tx_to_evaluate, + &[reserve.scripts.validator.csl_script_hash(), governance.policy_script_hash()], + evaluate_response, + )?; + + let tx = update_reserve_settings_tx( + &reserve_settings, + &reserve, + &governance, + costs + .get(&governance.policy_script_hash()) + .expect("governance policy has cost calculated"), + costs + .get(&reserve.scripts.validator.csl_script_hash()) + .expect("reserve validator has cost calculated"), + &ctx, + )?; + let signed_tx = ctx.sign(&tx).to_bytes(); + let res = client.submit_transaction(&signed_tx).await.map_err(|e| { + anyhow::anyhow!( + "Update Reserve Settings transaction request failed: {}, tx bytes: {}", + e, + hex::encode(signed_tx) + ) + })?; + let tx_id = res.transaction.id; + log::info!("Update Reserve Settings transaction submitted: {}", hex::encode(tx_id)); + await_tx.await_tx_output(client, UtxoId::new(tx_id, 0)).await?; + Ok(McTxHash(tx_id)) +} + +fn match_costs( + evaluated_transaction: &Transaction, + script_hashes: &[cardano_serialization_lib::ScriptHash], + evaluate_response: Vec, +) -> Result, anyhow::Error> { + let mint_keys = evaluated_transaction + .body() + .mint() + .expect("Transaction should have mints") + .keys(); + let mut script_to_index: HashMap = HashMap::new(); + for i in 0..mint_keys.len() { + script_to_index.insert(mint_keys.get(i), i); + } + let mint_ex_units = get_validator_budgets(evaluate_response).mint_ex_units; + if mint_ex_units.len() == script_hashes.len() { + let mut all_ex_units = HashMap::new(); + for sh in script_hashes { + let script_idx = *script_to_index.get(sh).unwrap_or_else(|| { + panic!("Token {} should be present in transaction mints", sh.to_hex()) + }); + let ex_units = + mint_ex_units.get(script_idx).expect("mint_ex_units has script hash").clone(); + all_ex_units.insert(sh.clone(), ex_units); + } + Ok(all_ex_units) + } else { + Err(anyhow!("Could not build transaction to submit, evaluate response has wrong number of mint keys.")) + } +} fn update_reserve_settings_tx( datum: &ReserveDatum, reserve: &ReserveData, governance: &GovernanceData, - governance_script_cost: ExUnits, - redeemer_script_cost: ExUnits, + governance_script_cost: &ExUnits, + reserve_script_cost: &ExUnits, ctx: &TransactionContext, ) -> Result { let mut tx_builder = TransactionBuilder::new(&get_builder_config(ctx)?); @@ -50,7 +160,7 @@ fn update_reserve_settings_tx( // CSL will set redeemer index for the index of script input after sorting transaction inputs &0u32.into(), &ReserveRedeemer::UpdateReserve { governance_version: 1u64 }.into(), - &redeemer_script_cost, + reserve_script_cost, ), ); inputs.add_plutus_script_input(&witness, &input, amount); @@ -82,12 +192,22 @@ fn update_reserve_settings_tx( tx_builder.add_mint_one_script_token_using_reference_script( &governance.policy_script, &gov_tx_input, - &governance_script_cost, + governance_script_cost, )?; tx_builder.add_script_reference_input( &reserve.illiquid_circulation_supply_validator_version_utxo.to_csl_tx_input(), reserve.scripts.illiquid_circulation_supply_validator.bytes.len(), ); + + tx_builder.add_script_reference_input( + &reserve.auth_policy_version_utxo.to_csl_tx_input(), + reserve.scripts.auth_policy.bytes.len(), + ); + tx_builder.add_script_reference_input( + &reserve.validator_version_utxo.to_csl_tx_input(), + reserve.scripts.validator.bytes.len(), + ); + tx_builder.add_required_signer(&ctx.payment_key_hash()); tx_builder.balance_update_and_build(ctx) @@ -144,8 +264,8 @@ mod tests { &(¶meters).into(), &reserve, &test_governance_data(), - governance_script_cost(), - redeemer_script_cost(), + &governance_script_cost(), + &redeemer_script_cost(), &test_transaction_context(), ) .unwrap(); diff --git a/toolkit/offchain/src/scripts_data.rs b/toolkit/offchain/src/scripts_data.rs index c12caa1cd..8cad15d89 100644 --- a/toolkit/offchain/src/scripts_data.rs +++ b/toolkit/offchain/src/scripts_data.rs @@ -212,10 +212,10 @@ pub(crate) fn registered_candidates_scripts( } #[derive(Clone, Debug)] -pub(crate) struct ReserveScripts { - pub(crate) validator: PlutusScript, - pub(crate) auth_policy: PlutusScript, - pub(crate) illiquid_circulation_supply_validator: PlutusScript, +pub struct ReserveScripts { + pub validator: PlutusScript, + pub auth_policy: PlutusScript, + pub illiquid_circulation_supply_validator: PlutusScript, } pub(crate) fn reserve_scripts( diff --git a/toolkit/offchain/tests/integration_tests.rs b/toolkit/offchain/tests/integration_tests.rs index 53b7bdfee..35074106c 100644 --- a/toolkit/offchain/tests/integration_tests.rs +++ b/toolkit/offchain/tests/integration_tests.rs @@ -16,10 +16,12 @@ use ogmios_client::{ }; use partner_chains_cardano_offchain::{ await_tx::{AwaitTx, FixedDelayRetries}, + csl::TransactionContext, d_param, init_governance, permissioned_candidates, register::Register, reserve, scripts_data, update_governance, }; +use partner_chains_plutus_data::reserve::ReserveMutableSettings; use sidechain_domain::{ AdaBasedStaking, AssetId, AssetName, AuraPublicKey, CandidateRegistration, DParameter, GrandpaPublicKey, MainchainAddressHash, MainchainPrivateKey, MainchainPublicKey, @@ -63,6 +65,13 @@ const REWARDS_TOKEN_ASSET_NAME_STR: &str = "52657761726420746f6b656e"; const INITIAL_DEPOSIT_AMOUNT: u64 = 500000; const DEPOSIT_AMOUNT: u64 = 100000; +const UPDATED_TOTAL_ACCRUED_FUNCTION_SCRIPT_HASH: PolicyId = PolicyId([234u8; 28]); +const UPDATED_INITIAL_INCENTIVE: u64 = 101; +const UPDATED_MUTABLE_SETTINGS: ReserveMutableSettings = ReserveMutableSettings { + total_accrued_function_script_hash: UPDATED_TOTAL_ACCRUED_FUNCTION_SCRIPT_HASH, + initial_incentive: UPDATED_INITIAL_INCENTIVE, +}; + #[tokio::test] async fn governance_flow() { let image = GenericImage::new(TEST_IMAGE, TEST_IMAGE_TAG); @@ -119,6 +128,8 @@ async fn reserve_management_scenario() { assert_reserve_deposited(genesis_utxo, INITIAL_DEPOSIT_AMOUNT, &client).await; run_deposit_to_reserve(genesis_utxo, &client).await; assert_reserve_deposited(genesis_utxo, INITIAL_DEPOSIT_AMOUNT + DEPOSIT_AMOUNT, &client).await; + let _ = run_update_reserve_settings_management(genesis_utxo, &client).await; + assert_mutable_settings_eq(genesis_utxo, UPDATED_MUTABLE_SETTINGS, &client).await; } #[tokio::test] @@ -162,6 +173,7 @@ async fn await_ogmios(ogmios_port: u16) -> Result { /// * "dave" address: addr_test1vphpcf32drhhznv6rqmrmgpuwq06kug0lkg22ux777rtlqst2er0r /// * "eve" address: addr_test1vzzt5pwz3pum9xdgxalxyy52m3aqur0n43pcl727l37ggscl8h7v8 /// * addr_test1vzuasm5nqzh7n909f7wang7apjprpg29l2f9sk6shlt84rqep6nyc - has attached V-function script +/// /// Its hash is 0xf8fbe7316561e57de9ecd1c86ee8f8b512a314ba86499ba9a584bfa8fe2edc8d async fn initial_transaction( client: &T, @@ -314,12 +326,30 @@ async fn run_create_reserve_management< .unwrap() } +async fn run_update_reserve_settings_management< + T: QueryLedgerState + Transactions + QueryNetwork + QueryUtxoByUtxoId, +>( + genesis_utxo: UtxoId, + client: &T, +) -> McTxHash { + reserve::update_settings::update_reserve_settings( + genesis_utxo, + GOVERNANCE_AUTHORITY_PAYMENT_KEY.0, + UPDATED_TOTAL_ACCRUED_FUNCTION_SCRIPT_HASH, + UPDATED_INITIAL_INCENTIVE, + client, + &FixedDelayRetries::new(Duration::from_millis(500), 100), + ) + .await + .unwrap() +} + async fn run_deposit_to_reserve< T: QueryLedgerState + Transactions + QueryNetwork + QueryUtxoByUtxoId, >( genesis_utxo: UtxoId, client: &T, -) -> () { +) { reserve::deposit::deposit_to_reserve( reserve::deposit::TokenAmount { token: AssetId { @@ -383,7 +413,7 @@ async fn assert_token_amount_eq( }), "Expected to find UTXO with {} of {}.{} at {}", expected_amount, - hex::encode(&token_policy_id.0), + hex::encode(token_policy_id.0), hex::encode(&token_asset_name.0), address, ); @@ -404,3 +434,25 @@ async fn assert_reserve_deposited( ) .await; } + +async fn assert_mutable_settings_eq( + genesis_utxo: UtxoId, + updated_mutable_settings: ReserveMutableSettings, + client: &T, +) { + let ctx = TransactionContext::for_payment_key(GOVERNANCE_AUTHORITY_PAYMENT_KEY.0, client) + .await + .unwrap(); + let reserve_data = crate::reserve::get_reserve_data(genesis_utxo, &ctx, client).await.unwrap(); + let mutable_settings = reserve_data + .get_reserve_settings(&ctx, client) + .await + .unwrap() + .unwrap() + .mutable_settings; + assert_eq!( + mutable_settings.total_accrued_function_script_hash, + updated_mutable_settings.total_accrued_function_script_hash + ); + assert_eq!(mutable_settings.initial_incentive, updated_mutable_settings.initial_incentive); +}