Skip to content

Commit

Permalink
feat: add init-reserve-management smart contract execution
Browse files Browse the repository at this point in the history
  • Loading branch information
LGLO committed Dec 17, 2024
1 parent f2e90ab commit 220f4a1
Show file tree
Hide file tree
Showing 8 changed files with 324 additions and 13 deletions.
8 changes: 6 additions & 2 deletions toolkit/offchain/src/csl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -184,15 +184,15 @@ impl ScriptExUnits {

pub(crate) fn get_validator_budgets(
mut responses: Vec<OgmiosEvaluateTransactionResponse>,
) -> Result<ScriptExUnits, JsError> {
) -> ScriptExUnits {
responses.sort_by_key(|r| r.validator.index);
let (mint_ex_units, spend_ex_units) = responses
.into_iter()
.partition::<Vec<_>, _>(|response| response.validator.purpose == "mint");
let mint_ex_units = mint_ex_units.into_iter().map(ex_units_from_response).collect();
let spend_ex_units = spend_ex_units.into_iter().map(ex_units_from_response).collect();

Ok(ScriptExUnits { mint_ex_units, spend_ex_units })
ScriptExUnits { mint_ex_units, spend_ex_units }
}

fn ex_units_from_response(resp: OgmiosEvaluateTransactionResponse) -> ExUnits {
Expand All @@ -208,6 +208,10 @@ pub(crate) fn empty_asset_name() -> AssetName {
AssetName::new(vec![]).expect("Hardcoded empty asset name is valid")
}

pub fn zero_ex_units() -> ExUnits {
ExUnits::new(&BigNum::zero(), &BigNum::zero())
}

pub(crate) trait OgmiosUtxoExt {
fn to_csl_tx_input(&self) -> TransactionInput;
fn to_csl_tx_output(&self) -> Result<TransactionOutput, JsError>;
Expand Down
8 changes: 2 additions & 6 deletions toolkit/offchain/src/d_param/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ where
hex::encode(tx.to_bytes())
)
})?;
let mut mint_witness_ex_units = get_validator_budgets(evaluate_response)?;
let mut mint_witness_ex_units = get_validator_budgets(evaluate_response);
mint_witness_ex_units.mint_ex_units.reverse();
let tx = mint_d_param_token_tx(
validator,
Expand Down Expand Up @@ -206,7 +206,7 @@ where
hex::encode(tx.to_bytes())
)
})?;
let spend_ex_units = get_validator_budgets(evaluate_response)?;
let spend_ex_units = get_validator_budgets(evaluate_response);

let tx = update_d_param_tx(
validator,
Expand Down Expand Up @@ -272,8 +272,6 @@ fn mint_d_param_token_tx(
.unwrap_or_else(|| panic!("Mint ex units not found")),
)?;

let tx_hash = TransactionHash::from_bytes(gov_utxo.transaction.id.into())?;
let gov_tx_input = TransactionInput::new(&tx_hash, gov_utxo.index.into());
tx_builder.add_script_reference_input(&gov_tx_input, gov_policy.bytes.len());
tx_builder.add_required_signer(&ctx.payment_key_hash());
tx_builder.balance_update_and_build(ctx)
Expand Down Expand Up @@ -327,8 +325,6 @@ fn update_d_param_tx(
.unwrap_or_else(|| panic!("Mint ex units not found")),
)?;

let tx_hash = TransactionHash::from_bytes(gov_utxo.transaction.id.into())?;
let gov_tx_input = TransactionInput::new(&tx_hash, gov_utxo.index.into());
tx_builder.add_script_reference_input(&gov_tx_input, gov_policy.bytes.len());
tx_builder.add_required_signer(&ctx.payment_key_hash());
tx_builder.balance_update_and_build(ctx)
Expand Down
19 changes: 15 additions & 4 deletions toolkit/offchain/src/init_governance/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use crate::csl::OgmiosUtxoExt;
use crate::plutus_script;
use crate::scripts_data;
use crate::{
await_tx::{AwaitTx, FixedDelayRetries},
Expand Down Expand Up @@ -164,13 +165,20 @@ pub async fn get_governance_utxo<T: QueryLedgerState + Transactions + QueryNetwo
}

pub(crate) struct GovernanceData {
policy_script: PlutusScript,
utxo_id: UtxoId,
pub(crate) policy_script: plutus_script::PlutusScript,
pub(crate) utxo_id: UtxoId,
}

impl GovernanceData {
pub(crate) fn policy_script_hash(&self) -> ScriptHash {
self.policy_script.hash()
self.policy_script.csl_script_hash()
}

pub(crate) fn utxo_id_as_tx_input(&self) -> TransactionInput {
TransactionInput::new(
&TransactionHash::from_bytes(self.utxo_id.tx_hash.0.to_vec()).unwrap(),
self.utxo_id.index.0.into(),
)
}
}

Expand All @@ -181,7 +189,10 @@ pub(crate) async fn get_governance_data<T: QueryLedgerState + Transactions + Que
let utxo = get_governance_utxo(genesis_utxo, client).await?;
let utxo_id = utxo.to_domain();
if let Some(OgmiosScript::Plutus(ps)) = utxo.script.clone() {
Ok(GovernanceData { policy_script: PlutusScript::new_v2(ps.cbor), utxo_id })
Ok(GovernanceData {
policy_script: plutus_script::PlutusScript::from_cbor(&ps.cbor, LanguageKind::PlutusV2),
utxo_id,
})
} else {
Err(anyhow!("Programmatic Error: Governance UTXO script is not PlutusScript"))
}
Expand Down
1 change: 1 addition & 0 deletions toolkit/offchain/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ pub mod permissioned_candidates;
mod plutus_script;
/// Supports candidate registration
pub mod register;
pub mod reserve;
/// Provides synthetized scripts data
pub mod scripts_data;
#[cfg(test)]
Expand Down
2 changes: 1 addition & 1 deletion toolkit/offchain/src/plutus_script.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ impl PlutusScript {
}

/// This function is needed to create [PlutusScript] from scripts in [raw_scripts],
/// which are encoded as a cibor byte string containing the cbor of the script
/// which are encoded as a cbor byte string containing the cbor of the script
/// itself. This function removes this layer of wrapping.
pub fn from_wrapped_cbor(cbor: &[u8], language: LanguageKind) -> anyhow::Result<Self> {
Ok(Self::from_cbor(&unwrap_one_layer_of_cbor(cbor)?, language))
Expand Down
265 changes: 265 additions & 0 deletions toolkit/offchain/src/reserve/init.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,265 @@
//! Initialization of the reserve management is execution of three similar transaction to
//! initialize three scripts: Rerserve Management Validator, Reserve Management Policy and
//! Illiquid Circulation Supply Validator.
//!
//! Transaction for each of these scripts should have:
//! * an output to Version Oracle Validator address that should:
//! * * have script reference with the script being initialized attached, script should be applied with Version Oracle Policy Id
//! * * contain 1 of token Version Oracle Policy with "Version oracle" asset name, minted in this transaction
//! * * * mint redeemer should be Constr(1, [Int: SCRIPT_ID, Bytes: Applied Script Bytes])
//! * * have Plutus Data that is [Int: SCRIPT_ID, Bytes: Version Oracle Policy Id]
//! * an output to the current governance that should:
//! * * contain a new Goveranance Policy token, minted in this transaction,
//! * * * mint redeemer should be empty contructor Plutus Data
//! * a script reference rnput of the current Goveranance UTXO
//! * signature of the current goveranance
use crate::{
await_tx::AwaitTx,
csl::{
get_builder_config, get_validator_budgets, zero_ex_units, TransactionBuilderExt,
TransactionContext,
},
init_governance::{get_governance_data, GovernanceData},
plutus_script::PlutusScript,
scripts_data::{self, VersionOracleData},
};
use anyhow::anyhow;
use cardano_serialization_lib::{
AssetName, Assets, BigNum, ConstrPlutusData, DataCost, ExUnits, Int, JsError, LanguageKind,
MinOutputAdaCalculator, MintBuilder, MintWitness, MultiAsset, PlutusData, PlutusList,
PlutusScriptSource, Redeemer, RedeemerTag, ScriptHash, ScriptRef, Transaction,
TransactionBuilder, TransactionOutputBuilder,
};
use ogmios_client::{
query_ledger_state::{QueryLedgerState, QueryUtxoByUtxoId},
query_network::QueryNetwork,
transactions::{OgmiosEvaluateTransactionResponse, Transactions},
};
use raw_scripts::{ILLIQUID_CIRCULATION_SUPPLY_VALIDATOR, RESERVE_AUTH_POLICY, RESERVE_VALIDATOR};
use sidechain_domain::{McTxHash, UtxoId};
use std::collections::HashMap;

pub async fn init_reserve_management<
T: QueryLedgerState + Transactions + QueryNetwork + QueryUtxoByUtxoId,
A: AwaitTx,
>(
genesis_utxo: UtxoId,
payment_key: [u8; 32],
client: &T,
await_tx: &A,
) -> anyhow::Result<Vec<McTxHash>> {
let reserve_validator =
ScriptData::new("Reserve Management Validator", RESERVE_VALIDATOR.to_vec(), 28);
let reserve_policy =
ScriptData::new("Reserve Management Policy", RESERVE_AUTH_POLICY.to_vec(), 29);
let ics_validator = ScriptData::new(
"Illiquid Circulation Validator",
ILLIQUID_CIRCULATION_SUPPLY_VALIDATOR.to_vec(),
30,
);
Ok(vec![
initialize_script(reserve_validator, genesis_utxo, payment_key, client, await_tx).await?,
initialize_script(reserve_policy, genesis_utxo, payment_key, client, await_tx).await?,
initialize_script(ics_validator, genesis_utxo, payment_key, client, await_tx).await?,
]
.into_iter()
.flatten()
.collect())
}

struct ScriptData {
name: String,
plutus_script: PlutusScript,
id: u16,
}

impl ScriptData {
fn new(name: &str, raw_bytes: Vec<u8>, id: u16) -> Self {
let plutus_script = PlutusScript::from_wrapped_cbor(&raw_bytes, LanguageKind::PlutusV2)
.expect("Plutus script should be valid");
Self { name: name.to_string(), plutus_script, id }
}

fn applied_plutus_script(
&self,
version_oracle: &VersionOracleData,
) -> Result<PlutusScript, JsError> {
let policy = version_oracle.policy.script_hash();
self.plutus_script
.clone()
.apply_uplc_data(uplc::PlutusData::BoundedBytes(policy.to_vec().into()))
.map_err(|e| JsError::from_str(&e.to_string()))
}
}

/// TODO: make it idempotent is the next step
async fn initialize_script<
T: QueryLedgerState + Transactions + QueryNetwork + QueryUtxoByUtxoId,
A: AwaitTx,
>(
script: ScriptData,
genesis_utxo: UtxoId,
payment_key: [u8; 32],
client: &T,
await_tx: &A,
) -> anyhow::Result<Option<McTxHash>> {
let ctx = TransactionContext::for_payment_key(payment_key, client).await?;
let governance = get_governance_data(genesis_utxo, client).await?;
let version_oracle = scripts_data::version_oracle(genesis_utxo, ctx.network)?;

let tx_to_evaluate = init_script_tx(
&script,
&governance,
zero_ex_units(),
&version_oracle,
zero_ex_units(),
&ctx,
)?;
let evaluate_response = client.evaluate_transaction(&tx_to_evaluate.to_bytes()).await?;

let (version_oracle_ex_units, governance_ex_units) = match_costs(
&tx_to_evaluate,
&version_oracle.policy.csl_script_hash(),
&governance.policy_script_hash(),
evaluate_response,
)?;

let tx = init_script_tx(
&script,
&governance,
governance_ex_units,
&version_oracle,
version_oracle_ex_units,
&ctx,
)?;
let signed_tx = ctx.sign(&tx).to_bytes();
let res = client.submit_transaction(&signed_tx).await.map_err(|e| {
anyhow!(
"Initialize Versioned '{}' transaction request failed: {}, tx bytes: {}",
script.name,
e,
hex::encode(signed_tx)
)
})?;
let tx_id = res.transaction.id;
log::info!(
"Initialized Versioned '{}' transaction submitted: {}",
script.name,
hex::encode(tx_id)
);
await_tx.await_tx_output(client, UtxoId::new(tx_id, 0)).await?;
Ok(Some(McTxHash(tx_id)))
}

fn init_script_tx(
script: &ScriptData,
governance: &GovernanceData,
governance_script_cost: ExUnits,
version_oracle: &VersionOracleData,
versioning_script_cost: ExUnits,
ctx: &TransactionContext,
) -> Result<Transaction, JsError> {
let mut tx_builder = TransactionBuilder::new(&get_builder_config(ctx)?);

let applied_script = script.applied_plutus_script(version_oracle)?;
{
let mut mint_builder = tx_builder.get_mint_builder().unwrap_or(MintBuilder::new());
let mint_witness = MintWitness::new_plutus_script(
&PlutusScriptSource::new(&version_oracle.policy.to_csl()),
&Redeemer::new(
&RedeemerTag::new_mint(),
&0u32.into(),
&PlutusData::new_constr_plutus_data(&ConstrPlutusData::new(
&BigNum::one(),
&version_oracle_plutus_list(script.id, &applied_script.script_hash()),
)),
&versioning_script_cost,
),
);
mint_builder.add_asset(&mint_witness, &version_oracle_asset_name(), &Int::new_i32(1))?;
tx_builder.set_mint_builder(&mint_builder);
}
{
let script_ref = ScriptRef::new_plutus_script(&applied_script.to_csl());
let amount_builder = TransactionOutputBuilder::new()
.with_address(&version_oracle.validator.address(ctx.network))
.with_plutus_data(&PlutusData::new_list(&version_oracle_plutus_list(
script.id,
&version_oracle.policy_id().0,
)))
.with_script_ref(&script_ref)
.next()?;
let mut ma = MultiAsset::new();
let mut assets = Assets::new();
assets.insert(&version_oracle_asset_name(), &1u64.into());
ma.insert(&version_oracle.policy_id().0.into(), &assets);
let output = amount_builder.with_coin_and_asset(&0u64.into(), &ma).build()?;
let min_ada = MinOutputAdaCalculator::new(
&output,
&DataCost::new_coins_per_byte(
&ctx.protocol_parameters.min_utxo_deposit_coefficient.into(),
),
)
.calculate_ada()?;
let output = amount_builder.with_coin_and_asset(&min_ada, &ma).build()?;
tx_builder.add_output(&output)?;
}
// Mint governance token
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(&gov_tx_input, governance.policy_script.bytes.len());
tx_builder.add_required_signer(&ctx.payment_key_hash());
tx_builder.balance_update_and_build(ctx)
}

fn version_oracle_asset_name() -> AssetName {
AssetName::new(b"Version oracle".to_vec()).unwrap()
}

fn version_oracle_plutus_list(script_id: u16, script_hash: &[u8]) -> PlutusList {
let mut list = PlutusList::new();
list.add(&PlutusData::new_integer(&script_id.into()));
list.add(&PlutusData::new_bytes(script_hash.to_vec()));
list
}

fn match_costs(
evaluated_transaction: &Transaction,
version_oracle_policy: &ScriptHash,
governance_policy: &ScriptHash,
evaluate_response: Vec<OgmiosEvaluateTransactionResponse>,
) -> Result<(ExUnits, ExUnits), anyhow::Error> {
let mint_keys = evaluated_transaction
.body()
.mint()
.expect("Every Init Reserve Management transaction should have two mints")
.keys();
let script_to_index: HashMap<ScriptHash, usize> =
vec![(mint_keys.get(0), 0), (mint_keys.get(1), 1)].into_iter().collect();
let mint_ex_units = get_validator_budgets(evaluate_response).mint_ex_units;
if mint_ex_units.len() == 2 {
let version_policy_idx = script_to_index
.get(version_oracle_policy)
.expect("Version Oracle Policy script is present in transaction mints")
.clone();
let version_oracle_ex_units = mint_ex_units
.get(version_policy_idx)
.expect("mint_ex_units have two items")
.clone();
let gov_policy_idx = script_to_index
.get(governance_policy)
.expect("Governance Policy script is present in transaction mints")
.clone();
let governance_ex_units =
mint_ex_units.get(gov_policy_idx).expect("mint_ex_units have two items").clone();
Ok((version_oracle_ex_units, governance_ex_units))
} else {
Err(anyhow!("Could not build transaction to submit, evaluate response has wrong number of mint keys."))
}
}
3 changes: 3 additions & 0 deletions toolkit/offchain/src/reserve/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
//! All smart-contracts related to rewads token reserve management
pub mod init;
Loading

0 comments on commit 220f4a1

Please sign in to comment.