Skip to content

Commit

Permalink
ETCM-9064 reserve update settings transaction (#383)
Browse files Browse the repository at this point in the history
kpinter-iohk authored Jan 14, 2025

Verified

This commit was signed with the committer’s verified signature. The key has expired.
atmoos Thomas Kägi
1 parent 586f3b2 commit 8d4f152
Showing 5 changed files with 243 additions and 18 deletions.
10 changes: 4 additions & 6 deletions toolkit/offchain/src/init_governance/transaction.rs
Original file line number Diff line number Diff line change
@@ -8,10 +8,6 @@ use ogmios_client::types::OgmiosUtxo;
use partner_chains_plutus_data::version_oracle::VersionOracleDatum;
use sidechain_domain::MainchainAddressHash;

// Script ID of the governance script in the script cache.
// TODO: Use a proper value of raw_scripts::ScripId once we upgrade to a version that has it.
const SCRIPT_ID: u32 = 32;

pub(crate) fn init_governance_transaction(
governance_authority: MainchainAddressHash,
tx_context: &TransactionContext,
@@ -67,7 +63,9 @@ fn mint_witness(
&0u32.into(),
&PlutusData::new_constr_plutus_data(&ConstrPlutusData::new(&0u64.into(), &{
let mut list = PlutusList::new();
list.add(&PlutusData::new_integer(&SCRIPT_ID.into()));
list.add(&PlutusData::new_integer(
&(raw_scripts::ScriptId::GovernancePolicy as u32).into(),
));
list.add(&PlutusData::new_bytes(multi_sig_policy.script_hash().to_vec()));
list
})),
@@ -84,7 +82,7 @@ pub(crate) fn version_oracle_datum_output(
tx_context: &TransactionContext,
) -> anyhow::Result<cardano_serialization_lib::TransactionOutput> {
let datum: PlutusData = VersionOracleDatum {
version_oracle: SCRIPT_ID,
version_oracle: raw_scripts::ScriptId::GovernancePolicy as u32,
currency_symbol: version_oracle_policy.policy_id().0,
}
.into();
1 change: 1 addition & 0 deletions toolkit/offchain/src/reserve/mod.rs
Original file line number Diff line number Diff line change
@@ -14,6 +14,7 @@ use sidechain_domain::UtxoId;
pub mod create;
pub mod deposit;
pub mod init;
pub mod update_settings;

pub(crate) struct ReserveData {
pub(crate) scripts: scripts_data::ReserveScripts,
229 changes: 229 additions & 0 deletions toolkit/offchain/src/reserve/update_settings.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,229 @@
//! Transaction that updates reserve settings.
//!
//! Specification:
//! 1. The transaction should mint one token:
//! * 1 Governance Policy Token (using reference script)
//! 2. The transaction should spend one token:
//! * 1 Reserve Auth Policy Token (using reference script)
//! 3. The transaction should have two outputs:
//! * Reserve Validator output that:
//! * * has value from existing reserve UTXO
//! * * has the updated Plutus Data (in our "versioned format"): `[[[Int(t0), <Encoded Token>], [Bytes(v_function_hash), Int(initial_incentive)], [Int(0)]], Constr(0, []), Int(0)]`,
//! where `<Encoded Token>` is `Constr(0, [Bytes(policy_id), Bytes(asset_name)])`.
//! * Change output that keeps the Governance Token and change of other tokens
//! 4. The transaction should have three script reference inputs:
//! * Reserve Auth Version Utxo
//! * Reserve Validator Version Utxo
//! * Governance Policy Script
use super::ReserveData;
use crate::{csl::*, init_governance::GovernanceData};
use cardano_serialization_lib::*;
use partner_chains_plutus_data::reserve::{ReserveDatum, ReserveRedeemer};

fn update_reserve_settings_tx(
datum: &ReserveDatum,
reserve: &ReserveData,
governance: &GovernanceData,
governance_script_cost: ExUnits,
redeemer_script_cost: ExUnits,
ctx: &TransactionContext,
) -> Result<Transaction, JsError> {
let mut tx_builder = TransactionBuilder::new(&get_builder_config(ctx)?);

// spend old settings
{
let mut inputs = TxInputsBuilder::new();

let utxo = &reserve.validator_version_utxo;
let input = utxo.to_csl_tx_input();
let amount = &utxo.value.to_csl()?;
let witness = PlutusWitness::new_with_ref_without_datum(
&PlutusScriptSource::new_ref_input(
&reserve.scripts.validator.csl_script_hash(),
&reserve.validator_version_utxo.to_csl_tx_input(),
&reserve.scripts.validator.language,
reserve.scripts.validator.bytes.len(),
),
&Redeemer::new(
&RedeemerTag::new_spend(),
// 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,
),
);
inputs.add_plutus_script_input(&witness, &input, amount);

tx_builder.set_inputs(&inputs);
}
// mint new settings
{
let amount_builder = TransactionOutputBuilder::new()
.with_address(&reserve.scripts.validator.address(ctx.network))
.with_plutus_data(&(datum.clone().into()))
.with_script_ref(&ScriptRef::new_plutus_script(&reserve.scripts.validator.to_csl()))
.next()?;
let mut val = reserve.validator_version_utxo.value.to_csl()?;
let output = amount_builder.with_value(&val).build()?;
let min_ada = MinOutputAdaCalculator::new(
&output,
&DataCost::new_coins_per_byte(
&ctx.protocol_parameters.min_utxo_deposit_coefficient.into(),
),
)
.calculate_ada()?;
val.set_coin(&min_ada);
let a = amount_builder.with_value(&val).build()?;
tx_builder.add_output(&a)?;
}

let gov_tx_input = governance.utxo_id_as_tx_input();
tx_builder.add_mint_one_script_token_using_reference_script(
&governance.policy_script,
&gov_tx_input,
&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_required_signer(&ctx.payment_key_hash());

tx_builder.balance_update_and_build(ctx)
}

#[cfg(test)]
mod tests {

use cardano_serialization_lib::{ExUnits, Language, NetworkIdKind};
use hex_literal::hex;
use ogmios_client::types::{OgmiosTx, OgmiosUtxo};
use partner_chains_plutus_data::reserve::ReserveDatum;
use sidechain_domain::{AssetName, PolicyId, TokenId};

use super::update_reserve_settings_tx;
use crate::{
csl::TransactionContext,
init_governance::GovernanceData,
plutus_script::PlutusScript,
reserve::ReserveData,
scripts_data::ReserveScripts,
test_values::{make_utxo, payment_addr, payment_key, protocol_parameters},
};

const REWARDS_TOKEN_POLICY_ID: PolicyId =
PolicyId(hex!("1fab25f376bc49a181d03a869ee8eaa3157a3a3d242a619ca7995b2b"));

// Reward token
const REWARDS_TOKEN_ASSET_NAME_STR: &str = "52657761726420746f6b656e";

#[test]
fn update_reserve_settings_tx_test() {
let parameters = crate::reserve::create::ReserveParameters {
initial_incentive: 100,
total_accrued_function_script_hash: PolicyId([233u8; 28]),
token: TokenId::AssetId {
policy_id: REWARDS_TOKEN_POLICY_ID,
asset_name: AssetName::from_hex_unsafe(REWARDS_TOKEN_ASSET_NAME_STR),
},
initial_deposit: 500000,
};

let reserve: ReserveData = ReserveData {
scripts: ReserveScripts {
validator: test_validator_script(),
auth_policy: test_auth_policy_script(),
illiquid_circulation_supply_validator: test_ics_validator_script(),
},
auth_policy_version_utxo: test_auth_policy_version_utxo(),
validator_version_utxo: test_validator_version_utxo(),
illiquid_circulation_supply_validator_version_utxo: test_ics_validator_version_utxo(),
};
let tx = update_reserve_settings_tx(
&(&parameters).into(),
&reserve,
&test_governance_data(),
governance_script_cost(),
redeemer_script_cost(),
&test_transaction_context(),
)
.unwrap();

let body = tx.body();
let inputs = body.inputs();
let outputs = body.outputs();

// Both inputs are used to cover transaction
assert_eq!(
inputs.get(0).to_string(),
"1212121212121212121212121212121212121212121212121212121212121212#0"
);
assert_eq!(
inputs.get(1).to_string(),
"7474747474747474747474747474747474747474747474747474747474747474#3"
);
assert_eq!(
outputs.get(0).address(),
test_validator_script().address(cardano_serialization_lib::NetworkIdKind::Testnet)
);
assert_eq!(
outputs.get(1).address().to_hex(),
"6032230eeaaae0ff7a97e7f088e65874bd79c0fe2a99399f795e84543a"
);
assert_eq!(outputs.get(0).plutus_data().unwrap(), ReserveDatum::from(&parameters).into());
}

fn test_transaction_context() -> TransactionContext {
TransactionContext {
payment_key: payment_key(),
payment_key_utxos: vec![make_utxo(116u8, 3, 996272387, &payment_addr())],
network: NetworkIdKind::Testnet,
protocol_parameters: protocol_parameters(),
}
}

fn test_validator_script() -> PlutusScript {
PlutusScript { bytes: hex!("445566").to_vec(), language: Language::new_plutus_v2() }
}

fn test_auth_policy_script() -> PlutusScript {
PlutusScript { bytes: hex!("556677").to_vec(), language: Language::new_plutus_v2() }
}

fn test_ics_validator_script() -> PlutusScript {
PlutusScript { bytes: hex!("667788").to_vec(), language: Language::new_plutus_v2() }
}

fn test_governance_script() -> PlutusScript {
PlutusScript { bytes: hex!("112233").to_vec(), language: Language::new_plutus_v2() }
}

fn test_governance_input() -> OgmiosUtxo {
OgmiosUtxo { transaction: OgmiosTx { id: [16; 32] }, index: 0, ..Default::default() }
}

fn test_auth_policy_version_utxo() -> OgmiosUtxo {
OgmiosUtxo { transaction: OgmiosTx { id: [17; 32] }, index: 0, ..Default::default() }
}

fn test_validator_version_utxo() -> OgmiosUtxo {
OgmiosUtxo { transaction: OgmiosTx { id: [18; 32] }, index: 0, ..Default::default() }
}

fn test_ics_validator_version_utxo() -> OgmiosUtxo {
OgmiosUtxo { transaction: OgmiosTx { id: [19; 32] }, index: 0, ..Default::default() }
}

fn test_governance_data() -> GovernanceData {
GovernanceData { policy_script: test_governance_script(), utxo: test_governance_input() }
}

fn governance_script_cost() -> ExUnits {
ExUnits::new(&100u64.into(), &200u64.into())
}

fn redeemer_script_cost() -> ExUnits {
ExUnits::new(&300u64.into(), &400u64.into())
}
}
4 changes: 1 addition & 3 deletions toolkit/offchain/src/update_governance/mod.rs
Original file line number Diff line number Diff line change
@@ -32,8 +32,6 @@ mod test;
#[cfg(test)]
mod test_values;

const SCRIPT_ID: u32 = 32;

pub async fn run_update_governance<
T: QueryLedgerState + Transactions + QueryNetwork + QueryUtxoByUtxoId,
A: AwaitTx,
@@ -141,7 +139,7 @@ fn update_governance_tx(
inputs.add_script_utxo_input_with_data(
&governance_data.utxo,
&version_oracle_validator,
&PlutusData::new_integer(&SCRIPT_ID.into()),
&PlutusData::new_integer(&(raw_scripts::ScriptId::GovernancePolicy as u32).into()),
&spend_ex_units,
)?;

17 changes: 8 additions & 9 deletions toolkit/offchain/tests/integration_tests.rs
Original file line number Diff line number Diff line change
@@ -160,6 +160,7 @@ async fn await_ogmios(ogmios_port: u16) -> Result<OgmiosClients, String> {
/// * governance authority: 1000000 REWARDS_TOKEN
/// * "dave" address: addr_test1vphpcf32drhhznv6rqmrmgpuwq06kug0lkg22ux777rtlqst2er0r
/// * "eve" address: addr_test1vzzt5pwz3pum9xdgxalxyy52m3aqur0n43pcl727l37ggscl8h7v8
///
/// Its hash is 0x61ca664e056ce49a9d4fd2fb3aa2b750ea753fe4ad5c9e6167482fd88394cf7d
async fn initial_transaction<T: Transactions + QueryUtxoByUtxoId>(
client: &T,
@@ -202,7 +203,7 @@ async fn run_update_goveranance<
>(
client: &T,
genesis_utxo: UtxoId,
) -> () {
) {
let _ = update_governance::run_update_governance(
EVE_PUBLIC_KEY_HASH,
GOVERNANCE_AUTHORITY_PAYMENT_KEY,
@@ -232,12 +233,11 @@ async fn run_upsert_d_param<
)
.await
.unwrap();
match tx_hash {
Some(tx_hash) => FixedDelayRetries::new(Duration::from_millis(500), 100)
if let Some(tx_hash) = tx_hash {
FixedDelayRetries::new(Duration::from_millis(500), 100)
.await_tx_output(client, UtxoId::new(tx_hash.0, 0))
.await
.unwrap(),
None => (),
.unwrap()
};
tx_hash
}
@@ -263,12 +263,11 @@ async fn run_upsert_permissioned_candidates<
)
.await
.unwrap();
match tx_hash {
Some(tx_hash) => FixedDelayRetries::new(Duration::from_millis(500), 100)
if let Some(tx_hash) = tx_hash {
FixedDelayRetries::new(Duration::from_millis(500), 100)
.await_tx_output(client, UtxoId::new(tx_hash.0, 0))
.await
.unwrap(),
None => (),
.unwrap()
};
tx_hash
}

0 comments on commit 8d4f152

Please sign in to comment.