diff --git a/.gitignore b/.gitignore index e5fc064b..1492c5df 100644 --- a/.gitignore +++ b/.gitignore @@ -17,5 +17,6 @@ target/ #.idea/ .scratchpad/ +**/.DS_Store .opencode diff --git a/Cargo.lock b/Cargo.lock index a7158f51..2aee485b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2994,6 +2994,7 @@ dependencies = [ "thiserror 2.0.11", "tracing", "trait-variant", + "tx3-cardano", "tx3-tir", "uuid", ] diff --git a/crates/tx3-cardano/src/coercion.rs b/crates/tx3-cardano/src/coercion.rs index 599bb186..b10c59b1 100644 --- a/crates/tx3-cardano/src/coercion.rs +++ b/crates/tx3-cardano/src/coercion.rs @@ -71,7 +71,7 @@ pub fn expr_into_metadatum( pub fn expr_into_utxo_refs(expr: &tir::Expression) -> Result, Error> { match expr { tir::Expression::UtxoRefs(x) => Ok(x.clone()), - tir::Expression::UtxoSet(x) => Ok(x.iter().map(|x| x.r#ref.clone()).collect()), + tir::Expression::UtxoSet(x) => Ok(x.refs_sorted()), tir::Expression::String(x) => { let (raw_txid, raw_output_ix) = x.split_once("#").expect("Invalid utxo ref"); Ok(vec![UtxoRef { diff --git a/crates/tx3-cardano/src/lib.rs b/crates/tx3-cardano/src/lib.rs index 4aed3a22..98b97b9f 100644 --- a/crates/tx3-cardano/src/lib.rs +++ b/crates/tx3-cardano/src/lib.rs @@ -22,6 +22,7 @@ pub type Network = pallas::ledger::primitives::NetworkId; pub type PlutusVersion = u8; pub type CostModel = Vec; +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub struct PParams { pub network: Network, pub min_fee_coefficient: u64, @@ -38,7 +39,7 @@ pub const EXECUTION_UNITS: primitives::ExUnits = primitives::ExUnits { const DEFAULT_EXTRA_FEES: u64 = 200_000; const MIN_UTXO_BYTES: i128 = 197; -#[derive(Debug, Clone, Default)] +#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)] pub struct Config { pub extra_fees: Option, } @@ -46,16 +47,18 @@ pub struct Config { pub type TxBody = pallas::codec::utils::KeepRaw<'static, primitives::conway::TransactionBody<'static>>; -#[derive(Debug, Clone)] +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub struct ChainPoint { pub slot: u64, pub hash: Vec, pub timestamp: u128, } +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub struct Compiler { pub pparams: PParams, pub config: Config, + #[serde(skip)] pub latest_tx_body: Option, pub cursor: ChainPoint, } diff --git a/crates/tx3-cardano/src/tests.rs b/crates/tx3-cardano/src/tests.rs index f87bad57..f6f4e4f0 100644 --- a/crates/tx3-cardano/src/tests.rs +++ b/crates/tx3-cardano/src/tests.rs @@ -1,9 +1,9 @@ -use std::collections::{BTreeMap, HashMap, HashSet}; +use std::collections::{BTreeMap, HashMap}; use tx3_lang::Workspace; use tx3_tir::compile::{CompiledTx, Compiler as _}; use tx3_tir::model::assets::CanonicalAssets; -use tx3_tir::model::core::{Utxo, UtxoRef}; +use tx3_tir::model::core::{Utxo, UtxoRef, UtxoSet}; use tx3_tir::model::v1beta0 as tir; use tx3_tir::reduce::{self, Apply as _, ArgValue}; use tx3_tir::Node as _; @@ -79,7 +79,7 @@ fn address_to_bytes(address: &str) -> ArgValue { ) } -fn wildcard_utxos(datum: Option) -> HashSet { +fn wildcard_utxos(datum: Option) -> UtxoSet { let tx_hash = hex::decode("267aae354f0d14d82877fa5720f7ddc9b0e3eea3cd2a0757af77db4d975ba81c") .unwrap() .try_into() @@ -98,10 +98,10 @@ fn wildcard_utxos(datum: Option) -> HashSet { script: None, }; - HashSet::from([utxo]) + [utxo].into() } -fn fill_inputs(tx: tir::Tx, utxos: HashSet) -> tir::Tx { +fn fill_inputs(tx: tir::Tx, utxos: UtxoSet) -> tir::Tx { let mut tx = tx; for (name, _) in reduce::find_queries(&tx) { @@ -120,7 +120,7 @@ fn compile_tx_round( args: &BTreeMap, fees: u64, compiler: &mut Compiler, - utxos: HashSet, + utxos: UtxoSet, ) -> CompiledTx { tx = reduce::apply_args(tx, args).unwrap(); tx = reduce::apply_fees(tx, fees).unwrap(); @@ -138,7 +138,7 @@ fn test_compile( tx: tir::Tx, args: &BTreeMap, compiler: &mut Compiler, - utxos: HashSet, + utxos: UtxoSet, ) -> CompiledTx { let mut fees = 0; let mut rounds = 0; diff --git a/crates/tx3-resolver/Cargo.toml b/crates/tx3-resolver/Cargo.toml index aa45315a..5200aa6b 100644 --- a/crates/tx3-resolver/Cargo.toml +++ b/crates/tx3-resolver/Cargo.toml @@ -32,6 +32,7 @@ pollster = { version = "0.4.0", features = ["macro"] } chainfuzz = { version = "0.2.0" } proptest = "1.7.0" jsonschema = "0.17.1" +tx3-cardano = { path = "../tx3-cardano" } [features] naive_selector = [] diff --git a/crates/tx3-resolver/src/dump.rs b/crates/tx3-resolver/src/dump.rs new file mode 100644 index 00000000..f95fae9f --- /dev/null +++ b/crates/tx3-resolver/src/dump.rs @@ -0,0 +1,218 @@ +use std::path::Path; + +use serde_json::{json, Map, Value}; + +use tx3_tir::compile::CompiledTx; +use tx3_tir::encoding::AnyTir; +use tx3_tir::model::assets::CanonicalAssets; +use tx3_tir::model::core::{Utxo, UtxoRef}; +use tx3_tir::reduce::ArgValue; + +use crate::inputs::CanonicalQuery; +use crate::interop::{self, TirEnvelope}; +use crate::job::{QueryResolution, ResolveJob, ResolveLog, ResolveLogEntry}; + +pub trait DiagnosticDump { + fn to_dump(&self) -> Value; +} + +// --------------------------------------------------------------------------- +// Leaf types +// --------------------------------------------------------------------------- + +impl DiagnosticDump for UtxoRef { + fn to_dump(&self) -> Value { + interop::utxo_ref_to_json(self) + } +} + +impl DiagnosticDump for AnyTir { + fn to_dump(&self) -> Value { + let envelope = TirEnvelope::from(self.clone()); + serde_json::to_value(envelope).expect("TirEnvelope should serialize") + } +} + +impl DiagnosticDump for CompiledTx { + fn to_dump(&self) -> Value { + json!({ + "payload": hex::encode(&self.payload), + "hash": hex::encode(&self.hash), + "fee": self.fee, + "ex_units": self.ex_units, + }) + } +} + +impl DiagnosticDump for ArgValue { + fn to_dump(&self) -> Value { + interop::arg_to_json(self) + } +} + +impl DiagnosticDump for CanonicalAssets { + fn to_dump(&self) -> Value { + let map: Map = self + .iter() + .map(|(class, amount)| (class.to_string(), json!(amount))) + .collect(); + Value::Object(map) + } +} + +impl DiagnosticDump for Utxo { + fn to_dump(&self) -> Value { + interop::utxo_to_json(self) + } +} + +// --------------------------------------------------------------------------- +// Canonical query +// --------------------------------------------------------------------------- + +impl DiagnosticDump for CanonicalQuery { + fn to_dump(&self) -> Value { + json!({ + "address": self.address.as_ref().map(|a| hex::encode(a)), + "min_amount": self.min_amount.as_ref().map(|a| a.to_dump()), + "refs": self.refs.iter().map(|r| r.to_dump()).collect::>(), + "support_many": self.support_many, + "collateral": self.collateral, + }) + } +} + +// --------------------------------------------------------------------------- +// Resolve pipeline types +// --------------------------------------------------------------------------- + +impl DiagnosticDump for QueryResolution { + fn to_dump(&self) -> Value { + json!({ + "name": self.name, + "query": self.query.to_dump(), + "selection": self.selection.as_ref().map(|sel| { + sel.iter().map(|u| u.to_dump()).collect::>() + }), + }) + } +} + +impl DiagnosticDump for ResolveLog { + fn to_dump(&self) -> Value { + match self { + ResolveLog::ArgsApplied(tir) => json!({"args_applied": tir.to_dump()}), + ResolveLog::FeesApplied(tir) => json!({"fees_applied": tir.to_dump()}), + ResolveLog::CompilerApplied(tir) => json!({"compiler_applied": tir.to_dump()}), + ResolveLog::Reduced(tir) => json!({"reduced": tir.to_dump()}), + ResolveLog::InputsResolved(tir) => json!({"inputs_resolved": tir.to_dump()}), + ResolveLog::FinalReduced(tir) => json!({"final_reduced": tir.to_dump()}), + ResolveLog::Compiled(tx) => json!({"compiled": tx.to_dump()}), + ResolveLog::Converged => json!("converged"), + } + } +} + +impl DiagnosticDump for ResolveLogEntry { + fn to_dump(&self) -> Value { + json!({ + "round": self.round, + "event": self.event.to_dump(), + }) + } +} + +// --------------------------------------------------------------------------- +// ResolveJob — top-level +// --------------------------------------------------------------------------- + +impl DiagnosticDump for ResolveJob { + fn to_dump(&self) -> Value { + let args: Map = self + .args + .iter() + .map(|(k, v)| (k.clone(), interop::arg_to_json(v))) + .collect(); + + let input_pool = self.input_pool.as_ref().map(|pool| { + let map: Map = pool + .iter() + .map(|(r, u)| { + let key = format!("{}#{}", hex::encode(&r.txid), r.index); + (key, interop::utxo_to_json(u)) + }) + .collect(); + Value::Object(map) + }); + + json!({ + "original_tir": self.original_tir.to_dump(), + "args": args, + "compiler": &self.compiler, + "round": self.round, + "last_eval": self.last_eval.as_ref().map(|e| e.to_dump()), + "converged": self.converged, + "log": self.log.iter().map(|e| e.to_dump()).collect::>(), + "input_queries": self.input_queries.iter().map(|q| q.to_dump()).collect::>(), + "input_pool": input_pool, + }) + } +} + +pub fn dump_to_dir(job: &ResolveJob, dir: &Path) -> Result { + std::fs::create_dir_all(dir)?; + let dump_id = uuid::Uuid::new_v4().to_string(); + let path = dir.join(format!("resolve-job-{dump_id}.json")); + let file = std::fs::File::create(&path)?; + serde_json::to_writer_pretty(file, &job.to_dump()) + .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?; + tracing::debug!(dump_id = %dump_id, path = %path.display(), "diagnostic dump written"); + Ok(dump_id) +} + +#[cfg(test)] +mod tests { + use tx3_tir::encoding::AnyTir; + use tx3_tir::model::v1beta0 as tir; + use tx3_tir::reduce::ArgMap; + + use crate::{ + dump::dump_to_dir, + job::{ResolveJob, ResolveLog}, + }; + + fn dummy_tir() -> AnyTir { + AnyTir::V1Beta0(tir::Tx { + fees: tir::Expression::None, + references: vec![], + inputs: vec![], + outputs: vec![], + validity: None, + mints: vec![], + burns: vec![], + adhoc: vec![], + collateral: vec![], + signers: None, + metadata: vec![], + }) + } + + #[test] + fn dump_creates_valid_json_file() { + let mut job = ResolveJob::new(dummy_tir(), ArgMap::new()); + job.record(ResolveLog::ArgsApplied(dummy_tir())); + job.record(ResolveLog::Converged); + + let dir = std::env::temp_dir().join("tx3-test-dump"); + let dump_id = dump_to_dir(&job, &dir).expect("dump should succeed"); + + let path = dir.join(format!("resolve-job-{dump_id}.json")); + assert!(path.exists()); + + let contents = std::fs::read_to_string(&path).unwrap(); + let value: serde_json::Value = serde_json::from_str(&contents).unwrap(); + assert!(value.get("compiler").is_some()); + + let _ = std::fs::remove_file(&path); + } +} diff --git a/crates/tx3-resolver/src/inputs/approximate/filter.rs b/crates/tx3-resolver/src/inputs/approximate/filter.rs index c54c60d0..63aea70f 100644 --- a/crates/tx3-resolver/src/inputs/approximate/filter.rs +++ b/crates/tx3-resolver/src/inputs/approximate/filter.rs @@ -53,7 +53,7 @@ mod tests { use proptest::prelude::*; use tx3_tir::model::{assets::CanonicalAssets, core::UtxoRef}; - use crate::inputs::test_utils::{ + use crate::test_utils::{ self, any_address, any_utxo, query, utxo, utxo_with_asset, utxo_with_ref, }; diff --git a/crates/tx3-resolver/src/inputs/approximate/rank/vector.rs b/crates/tx3-resolver/src/inputs/approximate/rank/vector.rs index b883d9cb..f8e72590 100644 --- a/crates/tx3-resolver/src/inputs/approximate/rank/vector.rs +++ b/crates/tx3-resolver/src/inputs/approximate/rank/vector.rs @@ -1,7 +1,7 @@ use std::collections::HashSet; use tx3_tir::model::{ assets::{AssetClass, CanonicalAssets}, - core::{Utxo, UtxoSet}, + core::{CanonicalOrd, Utxo, UtxoSet}, }; use super::Rank; @@ -137,10 +137,16 @@ pub struct VectorRanker; impl Rank for VectorRanker { fn sorted_candidates(search_space: UtxoSet, target: &CanonicalAssets) -> Vec { - let classes: Vec<_> = class_union!(search_space, target); + let class_set: HashSet<_> = class_union!(search_space, target); + let mut classes: Vec<_> = class_set.into_iter().collect(); + classes.sort(); let mut candidates = Vec::from_iter(search_space); - candidates.sort_by_cached_key(|utxo| utxo.assets.distance(target, &classes)); + candidates.sort_by(|a, b| { + let ad = a.assets.distance(target, &classes); + let bd = b.assets.distance(target, &classes); + ad.cmp(&bd).then_with(|| a.cmp_canonical(b)) + }); candidates } @@ -150,10 +156,9 @@ impl Rank for VectorRanker { mod tests { use super::*; use proptest::prelude::*; + use tx3_tir::model::core::UtxoRef; - use crate::inputs::test_utils::{ - any_composite_asset, any_defined_asset, any_naked_asset, - }; + use crate::test_utils::{any_composite_asset, any_defined_asset, any_naked_asset}; proptest! { #[test] @@ -217,4 +222,32 @@ mod tests { assert!(distance_a <= distance_b); } } + + #[test] + fn equal_distance_candidates_use_canonical_ref_tiebreaker() { + let target = CanonicalAssets::from_naked_amount(1_000); + + let a = Utxo { + r#ref: UtxoRef::new(&[2], 0), + address: vec![], + assets: CanonicalAssets::from_naked_amount(1_000), + datum: None, + script: None, + }; + + let b = Utxo { + r#ref: UtxoRef::new(&[1], 0), + address: vec![], + assets: CanonicalAssets::from_naked_amount(1_000), + datum: None, + script: None, + }; + + let pool: UtxoSet = [a, b].into(); + let ranked = VectorRanker::sorted_candidates(pool, &target); + + assert_eq!(ranked.len(), 2); + assert_eq!(ranked[0].r#ref.txid, vec![1]); + assert_eq!(ranked[1].r#ref.txid, vec![2]); + } } diff --git a/crates/tx3-resolver/src/inputs/assign/mod.rs b/crates/tx3-resolver/src/inputs/assign/mod.rs index 7b3c0aab..9976b24b 100644 --- a/crates/tx3-resolver/src/inputs/assign/mod.rs +++ b/crates/tx3-resolver/src/inputs/assign/mod.rs @@ -3,14 +3,13 @@ use std::collections::HashSet; +use crate::job::{QueryResolution, ResolveJob}; +use crate::Error; use tx3_tir::model::{ assets::CanonicalAssets, core::{Utxo, UtxoRef, UtxoSet}, }; -use crate::job::{QueryResolution, ResolveJob}; -use crate::Error; - #[cfg(test)] mod tests; @@ -74,7 +73,7 @@ fn pick_single(candidates: Vec, target: &CanonicalAssets) -> UtxoSet { /// Accumulate candidates greedily until the target is fully covered, /// then trim any excess UTxOs that aren't needed. fn pick_many(candidates: Vec, target: &CanonicalAssets) -> UtxoSet { - let mut matched = HashSet::new(); + let mut matched = UtxoSet::new(); let mut pending = target.clone(); for candidate in candidates { @@ -90,7 +89,7 @@ fn pick_many(candidates: Vec, target: &CanonicalAssets) -> UtxoSet { } if !pending.is_empty_or_negative() { - return HashSet::new(); + return UtxoSet::new(); } while let Some(utxo) = find_first_excess_utxo(&matched, target) { @@ -100,14 +99,12 @@ fn pick_many(candidates: Vec, target: &CanonicalAssets) -> UtxoSet { matched } -fn find_first_excess_utxo(utxos: &HashSet, target: &CanonicalAssets) -> Option { +fn find_first_excess_utxo(utxos: &UtxoSet, target: &CanonicalAssets) -> Option { if utxos.len() == 1 { return None; } - let available = utxos - .iter() - .fold(CanonicalAssets::empty(), |acc, x| acc + x.assets.clone()); + let available = utxos.total_assets(); let excess = available - target.clone(); @@ -115,7 +112,7 @@ fn find_first_excess_utxo(utxos: &HashSet, target: &CanonicalAssets) -> Op return None; } - for utxo in utxos.iter() { + for utxo in utxos.iter_sorted_by_ref() { if excess.contains_total(&utxo.assets) { return Some(utxo.clone()); } diff --git a/crates/tx3-resolver/src/inputs/assign/tests.rs b/crates/tx3-resolver/src/inputs/assign/tests.rs index 6238fff9..9a396007 100644 --- a/crates/tx3-resolver/src/inputs/assign/tests.rs +++ b/crates/tx3-resolver/src/inputs/assign/tests.rs @@ -1,9 +1,9 @@ use std::collections::HashSet; use proptest::prelude::*; -use tx3_tir::model::{assets::CanonicalAssets, core::UtxoRef}; +use tx3_tir::model::{assets::CanonicalAssets, core::UtxoRef, core::UtxoSet}; -use crate::inputs::test_utils::{self, utxo, utxo_with_asset}; +use crate::test_utils::{self, utxo, utxo_with_asset}; use crate::inputs::canonical::CanonicalQuery; use crate::job::{QueryResolution, ResolveJob}; @@ -14,12 +14,21 @@ use super::*; // Helpers for unit tests // --------------------------------------------------------------------------- -fn simple_query(many: bool, collateral: bool, min_amount: Option) -> CanonicalQuery { +fn simple_query( + many: bool, + collateral: bool, + min_amount: Option, +) -> CanonicalQuery { test_utils::query(None, min_amount, HashSet::new(), many, collateral) } fn qr(name: &str, q: CanonicalQuery, candidates: Vec) -> QueryResolution { - QueryResolution { name: name.to_string(), query: q, candidates, selection: None } + QueryResolution { + name: name.to_string(), + query: q, + candidates, + selection: None, + } } fn make_job(queries: Vec) -> ResolveJob { @@ -47,10 +56,7 @@ fn pick_single_returns_first_sufficient_match() { #[test] fn pick_single_returns_empty_when_none_sufficient() { let target = CanonicalAssets::from_naked_amount(10_000_000); - let candidates = vec![ - utxo(1, 0, b"a", 3_000_000), - utxo(2, 0, b"a", 5_000_000), - ]; + let candidates = vec![utxo(1, 0, b"a", 3_000_000), utxo(2, 0, b"a", 5_000_000)]; let result = pick_single(candidates, &target); assert!(result.is_empty()); @@ -78,17 +84,17 @@ fn pick_many_accumulates_until_target_met() { let result = pick_many(candidates, &target); assert!(result.len() >= 2); - let total: i128 = result.iter().map(|u| u.assets.naked_amount().unwrap_or(0)).sum(); + let total: i128 = result + .iter() + .map(|u| u.assets.naked_amount().unwrap_or(0)) + .sum(); assert!(total >= 7_000_000); } #[test] fn pick_many_returns_empty_when_insufficient() { let target = CanonicalAssets::from_naked_amount(100_000_000); - let candidates = vec![ - utxo(1, 0, b"a", 3_000_000), - utxo(2, 0, b"a", 3_000_000), - ]; + let candidates = vec![utxo(1, 0, b"a", 3_000_000), utxo(2, 0, b"a", 3_000_000)]; let result = pick_many(candidates, &target); assert!(result.is_empty()); @@ -121,8 +127,8 @@ fn pick_many_with_multi_asset_target() { + CanonicalAssets::from_asset(Some(policy), Some(name), 100); let candidates = vec![ - utxo(1, 0, b"a", 3_000_000), // has ADA only - utxo_with_asset(2, 0, b"a", 1_000_000, policy, name, 100), // has token only + utxo(1, 0, b"a", 3_000_000), // has ADA only + utxo_with_asset(2, 0, b"a", 1_000_000, policy, name, 100), // has token only ]; let result = pick_many(candidates, &target); @@ -136,32 +142,35 @@ fn pick_many_with_multi_asset_target() { #[test] fn excess_returns_none_for_single_utxo() { let target = CanonicalAssets::from_naked_amount(1_000_000); - let utxos = HashSet::from([utxo(1, 0, b"a", 5_000_000)]); + let utxos: UtxoSet = [utxo(1, 0, b"a", 5_000_000)].into(); assert!(find_first_excess_utxo(&utxos, &target).is_none()); } #[test] fn excess_returns_none_when_exactly_covered() { let target = CanonicalAssets::from_naked_amount(6_000_000); - let utxos = HashSet::from([ - utxo(1, 0, b"a", 3_000_000), - utxo(2, 0, b"a", 3_000_000), - ]); + let utxos: UtxoSet = [utxo(1, 0, b"a", 3_000_000), utxo(2, 0, b"a", 3_000_000)].into(); assert!(find_first_excess_utxo(&utxos, &target).is_none()); } #[test] fn excess_finds_removable_utxo() { let target = CanonicalAssets::from_naked_amount(3_000_000); - let utxos = HashSet::from([ - utxo(1, 0, b"a", 5_000_000), - utxo(2, 0, b"a", 2_000_000), - ]); + let utxos: UtxoSet = [utxo(1, 0, b"a", 5_000_000), utxo(2, 0, b"a", 2_000_000)].into(); // 5M + 2M = 7M, excess = 4M, utxo(2) with 2M fits in excess let excess = find_first_excess_utxo(&utxos, &target); assert!(excess.is_some()); } +#[test] +fn excess_picks_canonical_ref_when_multiple_removable() { + let target = CanonicalAssets::from_naked_amount(1_000_000); + let utxos: UtxoSet = [utxo(2, 0, b"a", 3_000_000), utxo(1, 0, b"a", 3_000_000)].into(); + + let excess = find_first_excess_utxo(&utxos, &target).unwrap(); + assert_eq!(excess.r#ref.txid[0], 1); +} + // --------------------------------------------------------------------------- // assign_all // --------------------------------------------------------------------------- @@ -176,7 +185,11 @@ fn selection<'a>(job: &'a ResolveJob, name: &str) -> &'a UtxoSet { #[test] fn assign_all_single_query_resolved() { - let q = simple_query(false, false, Some(CanonicalAssets::from_naked_amount(1_000_000))); + let q = simple_query( + false, + false, + Some(CanonicalAssets::from_naked_amount(1_000_000)), + ); let candidates = vec![utxo(1, 0, b"a", 5_000_000)]; let mut job = make_job(vec![qr("input", q, candidates)]); @@ -186,7 +199,11 @@ fn assign_all_single_query_resolved() { #[test] fn assign_all_single_query_unresolved() { - let q = simple_query(false, false, Some(CanonicalAssets::from_naked_amount(10_000_000))); + let q = simple_query( + false, + false, + Some(CanonicalAssets::from_naked_amount(10_000_000)), + ); let candidates = vec![utxo(1, 0, b"a", 5_000_000)]; // insufficient let mut job = make_job(vec![qr("input", q, candidates)]); @@ -200,8 +217,16 @@ fn assign_all_tighter_query_picks_first() { let u2 = utxo(2, 0, b"a", 5_000_000); let target = CanonicalAssets::from_naked_amount(1_000_000); - let tight = qr("tight", simple_query(false, false, Some(target.clone())), vec![u1.clone()]); - let loose = qr("loose", simple_query(false, false, Some(target)), vec![u1, u2]); + let tight = qr( + "tight", + simple_query(false, false, Some(target.clone())), + vec![u1.clone()], + ); + let loose = qr( + "loose", + simple_query(false, false, Some(target)), + vec![u1, u2], + ); let mut job = make_job(vec![loose, tight]); // order shouldn't matter job.assign_all().unwrap(); @@ -217,7 +242,11 @@ fn assign_all_exclusivity_second_query_fails_when_utxo_taken() { let u1 = utxo(1, 0, b"a", 5_000_000); let target = CanonicalAssets::from_naked_amount(1_000_000); - let q1 = qr("a", simple_query(false, false, Some(target.clone())), vec![u1.clone()]); + let q1 = qr( + "a", + simple_query(false, false, Some(target.clone())), + vec![u1.clone()], + ); let q2 = qr("b", simple_query(false, false, Some(target)), vec![u1]); // same sole candidate let mut job = make_job(vec![q1, q2]); @@ -230,8 +259,16 @@ fn assign_all_collateral_has_priority_over_regular() { let u1 = utxo(1, 0, b"a", 5_000_000); let target = CanonicalAssets::from_naked_amount(1_000_000); - let regular = qr("regular", simple_query(false, false, Some(target.clone())), vec![u1.clone()]); - let collateral = qr("collateral", simple_query(false, true, Some(target)), vec![u1]); + let regular = qr( + "regular", + simple_query(false, false, Some(target.clone())), + vec![u1.clone()], + ); + let collateral = qr( + "collateral", + simple_query(false, true, Some(target)), + vec![u1], + ); let mut job = make_job(vec![regular, collateral]); // Collateral wins the only UTxO, regular fails @@ -247,7 +284,11 @@ fn assign_all_many_query_accumulates() { utxo(3, 0, b"a", 3_000_000), ]; - let mut job = make_job(vec![qr("input", simple_query(true, false, Some(target)), candidates)]); + let mut job = make_job(vec![qr( + "input", + simple_query(true, false, Some(target)), + candidates, + )]); job.assign_all().unwrap(); assert!(selection(&job, "input").len() >= 2); } diff --git a/crates/tx3-resolver/src/inputs/canonical.rs b/crates/tx3-resolver/src/inputs/canonical.rs index b96ced2b..99481b7d 100644 --- a/crates/tx3-resolver/src/inputs/canonical.rs +++ b/crates/tx3-resolver/src/inputs/canonical.rs @@ -3,7 +3,7 @@ use std::collections::HashSet; use tx3_tir::model::v1beta0 as tir; -use tx3_tir::model::{assets::CanonicalAssets, core::UtxoRef, core::UtxoSet}; +use tx3_tir::model::{assets::CanonicalAssets, core::UtxoRef}; use crate::Error; @@ -33,12 +33,6 @@ macro_rules! data_or_bail { }; } -pub struct Diagnostic { - pub query: tir::InputQuery, - pub utxos: UtxoSet, - pub selected: UtxoSet, -} - #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub struct CanonicalQuery { pub address: Option>, diff --git a/crates/tx3-resolver/src/inputs/mod.rs b/crates/tx3-resolver/src/inputs/mod.rs index c8a8c4b9..72a26030 100644 --- a/crates/tx3-resolver/src/inputs/mod.rs +++ b/crates/tx3-resolver/src/inputs/mod.rs @@ -15,8 +15,6 @@ pub(crate) mod assign; mod canonical; mod narrow; -#[cfg(test)] -pub(crate) mod test_utils; #[cfg(test)] mod tests; diff --git a/crates/tx3-resolver/src/inputs/narrow.rs b/crates/tx3-resolver/src/inputs/narrow.rs index 8916d8c1..80b57108 100644 --- a/crates/tx3-resolver/src/inputs/narrow.rs +++ b/crates/tx3-resolver/src/inputs/narrow.rs @@ -2,7 +2,7 @@ use std::collections::{HashMap, HashSet}; use tx3_tir::model::{ assets::AssetClass, - core::{Utxo, UtxoRef}, + core::{CanonicalOrd, Utxo, UtxoRef}, }; use crate::job::ResolveJob; @@ -148,7 +148,9 @@ impl SearchSpace { if best.len() < take { let others: HashSet<_> = self.union.clone().into(); let diff: HashSet<_> = others.difference(&best).cloned().collect(); - let remaining: HashSet<_> = diff.into_iter().take(take - best.len()).collect(); + let mut sorted_diff: Vec<_> = diff.into_iter().collect(); + sorted_diff.sort_by(|a, b| a.cmp_canonical(b)); + let remaining: HashSet<_> = sorted_diff.into_iter().take(take - best.len()).collect(); best.union(&remaining).cloned().collect() } else { best @@ -245,7 +247,7 @@ mod tests { use super::*; - use crate::inputs::test_utils as mock; + use crate::test_utils as mock; fn assets_for(asset: mock::KnownAsset, amount: i128) -> CanonicalAssets { CanonicalAssets::from_asset( @@ -430,4 +432,34 @@ mod tests { let criteria = cq(Some(&addr), Some(min_assets), refs); assert_space_matches(&store, criteria, expected_best).await; } + + #[test] + fn test_take_prefers_canonical_refs_when_filling_from_union() { + let mkref = |b: u8| UtxoRef::new(&[b], 0); + + let best_ref = mkref(3); + let mut best = HashSet::new(); + best.insert(best_ref.clone()); + + let mut union = HashSet::new(); + union.insert(best_ref); + union.insert(mkref(9)); + union.insert(mkref(1)); + union.insert(mkref(5)); + + let space = SearchSpace { + union: Subset::Specific(union), + intersection: Subset::Specific(best), + by_address_count: None, + by_asset_class_count: None, + by_ref_count: None, + }; + + let got = space.take(Some(3)); + + assert!(got.contains(&mkref(3))); + assert!(got.contains(&mkref(1))); + assert!(got.contains(&mkref(5))); + assert!(!got.contains(&mkref(9))); + } } diff --git a/crates/tx3-resolver/src/inputs/tests.rs b/crates/tx3-resolver/src/inputs/tests.rs index 2fcd97a4..f51cc909 100644 --- a/crates/tx3-resolver/src/inputs/tests.rs +++ b/crates/tx3-resolver/src/inputs/tests.rs @@ -5,9 +5,7 @@ use chainfuzz::utxos::UtxoBuilder; use tx3_tir::model::{assets::CanonicalAssets, core::UtxoSet, v1beta0 as tir}; use crate::{ - inputs::{canonical::CanonicalQuery, test_utils as mock}, - job::ResolveJob, - Error, UtxoStore, + inputs::canonical::CanonicalQuery, job::ResolveJob, test_utils as mock, Error, UtxoStore, }; fn new_input_query( @@ -51,11 +49,7 @@ fn stub_job(queries: Vec<(String, CanonicalQuery)>) -> ResolveJob { job } -async fn resolve_single( - store: &S, - name: &str, - criteria: &CanonicalQuery, -) -> UtxoSet { +async fn resolve_single(store: &S, name: &str, criteria: &CanonicalQuery) -> UtxoSet { let mut job = stub_job(vec![(name.to_string(), criteria.clone())]); match job.resolve_queries(store).await { Ok(()) => job.to_input_map().remove(name).unwrap_or_default(), @@ -138,14 +132,22 @@ async fn test_resolve_by_naked_amount() { // too much — no single UTxO covers it let criteria = new_input_query( - &mock::KnownAddress::Alice, Some(6_000_000), vec![], false, false, + &mock::KnownAddress::Alice, + Some(6_000_000), + vec![], + false, + false, ); let utxos = resolve_single(&store, "q", &criteria).await; assert!(utxos.is_empty()); // within range let criteria = new_input_query( - &mock::KnownAddress::Alice, Some(4_000_000), vec![], false, false, + &mock::KnownAddress::Alice, + Some(4_000_000), + vec![], + false, + false, ); let utxos = resolve_single(&store, "q", &criteria).await; assert_eq!(dbg!(utxos.len()), 1); @@ -162,23 +164,53 @@ async fn test_resolve_by_asset_amount() { for address in mock::KnownAddress::everyone() { // single, too much - let criteria = new_input_query(&address, None, vec![(mock::KnownAsset::Hosky, 1001)], false, false); + let criteria = new_input_query( + &address, + None, + vec![(mock::KnownAsset::Hosky, 1001)], + false, + false, + ); assert!(resolve_single(&store, "q", &criteria).await.is_empty()); // many, accumulates - let criteria = new_input_query(&address, None, vec![(mock::KnownAsset::Hosky, 1001)], true, false); + let criteria = new_input_query( + &address, + None, + vec![(mock::KnownAsset::Hosky, 1001)], + true, + false, + ); assert!(resolve_single(&store, "q", &criteria).await.len() > 1); // many, still not enough - let criteria = new_input_query(&address, None, vec![(mock::KnownAsset::Hosky, 4001)], true, false); + let criteria = new_input_query( + &address, + None, + vec![(mock::KnownAsset::Hosky, 4001)], + true, + false, + ); assert!(resolve_single(&store, "q", &criteria).await.is_empty()); // wrong asset - let criteria = new_input_query(&address, None, vec![(mock::KnownAsset::Snek, 500)], false, false); + let criteria = new_input_query( + &address, + None, + vec![(mock::KnownAsset::Snek, 500)], + false, + false, + ); assert!(resolve_single(&store, "q", &criteria).await.is_empty()); // right asset, within range - let criteria = new_input_query(&address, None, vec![(mock::KnownAsset::Hosky, 500)], false, false); + let criteria = new_input_query( + &address, + None, + vec![(mock::KnownAsset::Hosky, 500)], + false, + false, + ); assert_eq!(resolve_single(&store, "q", &criteria).await.len(), 1); } } @@ -285,8 +317,13 @@ async fn test_resolve_competing_queries() { ); let address = mock::KnownAddress::Alice; - let asset_query = - new_input_query(&address, None, vec![(mock::KnownAsset::Hosky, 1)], false, false); + let asset_query = new_input_query( + &address, + None, + vec![(mock::KnownAsset::Hosky, 1)], + false, + false, + ); let naked_query = new_input_query(&address, Some(1), vec![], false, false); let mut job = stub_job(vec![ @@ -308,7 +345,12 @@ async fn test_resolve_competing_queries() { Some(mock::KnownAsset::Hosky.name().as_ref()), 1, ); - assert!(asset_utxos.iter().next().unwrap().assets.contains_total(&target_asset)); + assert!(asset_utxos + .iter() + .next() + .unwrap() + .assets + .contains_total(&target_asset)); } #[pollster::test] @@ -325,15 +367,22 @@ async fn test_resolve_competing_queries_no_solution() { ); let address = mock::KnownAddress::Alice; - let query_a = - new_input_query(&address, None, vec![(mock::KnownAsset::Hosky, 1)], false, false); - let query_b = - new_input_query(&address, None, vec![(mock::KnownAsset::Hosky, 1)], false, false); + let query_a = new_input_query( + &address, + None, + vec![(mock::KnownAsset::Hosky, 1)], + false, + false, + ); + let query_b = new_input_query( + &address, + None, + vec![(mock::KnownAsset::Hosky, 1)], + false, + false, + ); - let mut job = stub_job(vec![ - ("a".to_string(), query_a), - ("b".to_string(), query_b), - ]); + let mut job = stub_job(vec![("a".to_string(), query_a), ("b".to_string(), query_b)]); let result = job.resolve_queries(&store).await; assert!(result.is_err()); @@ -384,13 +433,16 @@ async fn test_cross_query_pool_doesnt_leak_wrong_address() { let addr_a = mock::KnownAddress::Alice; let addr_b = mock::KnownAddress::Bob; - let query_a = new_input_query(&addr_a, None, vec![(mock::KnownAsset::Hosky, 1)], false, false); + let query_a = new_input_query( + &addr_a, + None, + vec![(mock::KnownAsset::Hosky, 1)], + false, + false, + ); let query_b = new_input_query(&addr_b, Some(1_000_000), vec![], false, false); - let mut job = stub_job(vec![ - ("a".to_string(), query_a), - ("b".to_string(), query_b), - ]); + let mut job = stub_job(vec![("a".to_string(), query_a), ("b".to_string(), query_b)]); let result = job.resolve_queries(&store).await; assert!(result.is_err()); diff --git a/crates/tx3-resolver/src/interop.rs b/crates/tx3-resolver/src/interop.rs index 26320139..f5d2d03a 100644 --- a/crates/tx3-resolver/src/interop.rs +++ b/crates/tx3-resolver/src/interop.rs @@ -1,9 +1,10 @@ use base64::Engine as _; use serde::{Deserialize, Serialize}; -use serde_json::{Number, Value}; +use serde_json::{Map, Number, Value}; use thiserror::Error; -use tx3_tir::model::core::{Type, UtxoRef}; +use tx3_tir::model::assets::{AssetClass, CanonicalAssets}; +use tx3_tir::model::core::{Type, Utxo, UtxoRef}; pub use tx3_tir::reduce::ArgValue; #[derive(Debug, Error)] @@ -73,6 +74,17 @@ pub struct BytesEnvelope { pub content_type: BytesEncoding, } +/// Envelope for serialized TIR (Transaction Intermediate Representation) bytes. +/// Used for serialization/deserialization of TIR across dump mechanism, TRP, etc. +#[derive(Debug, Deserialize, Serialize, Clone)] +pub struct TirEnvelope { + // Aliases for backward compatibility + #[serde(alias = "bytecode", alias = "payload")] + pub content: String, + pub encoding: BytesEncoding, + pub version: String, +} + impl BytesEnvelope { pub fn from_hex(hex: &str) -> Result { Ok(Self { @@ -91,6 +103,39 @@ impl From for Vec { } } +impl From for Vec { + fn from(envelope: TirEnvelope) -> Self { + match envelope.encoding { + BytesEncoding::Base64 => base64_to_bytes(&envelope.content).unwrap(), + BytesEncoding::Hex => hex_to_bytes(&envelope.content).unwrap(), + } + } +} + +impl TryFrom for tx3_tir::encoding::AnyTir { + type Error = crate::Error; + + fn try_from(envelope: TirEnvelope) -> Result { + let version = tx3_tir::encoding::TirVersion::try_from(envelope.version.as_str())?; + let bytes: Vec = envelope.into(); + let tir = tx3_tir::encoding::from_bytes(&bytes, version)?; + Ok(tir) + } +} + +impl From for TirEnvelope { + fn from(tir: tx3_tir::encoding::AnyTir) -> Self { + let (bytes, version) = match tir { + tx3_tir::encoding::AnyTir::V1Beta0(tx) => tx3_tir::encoding::to_bytes(&tx), + }; + Self { + content: hex::encode(bytes), + encoding: BytesEncoding::Hex, + version: version.to_string(), + } + } +} + fn has_hex_prefix(s: &str) -> bool { s.starts_with("0x") } @@ -197,7 +242,7 @@ fn value_to_underfined(value: Value) -> Result { } } -fn string_to_utxo_ref(s: &str) -> Result { +pub fn string_to_utxo_ref(s: &str) -> Result { let (txid, index) = s .split_once('#') .ok_or(Error::InvalidUtxoRef(s.to_string()))?; @@ -244,6 +289,99 @@ pub fn from_json(value: Value, target: &Type) -> Result { } } +// --------------------------------------------------------------------------- +// Rust → JSON marshalling +// --------------------------------------------------------------------------- + +pub fn utxo_ref_to_json(r: &UtxoRef) -> Value { + Value::String(format!("{}#{}", hex::encode(&r.txid), r.index)) +} + +pub fn arg_to_json(arg: &ArgValue) -> Value { + match arg { + ArgValue::Int(i) => serde_json::json!(i), + ArgValue::Bool(b) => Value::Bool(*b), + ArgValue::String(s) => Value::String(s.clone()), + ArgValue::Bytes(v) => Value::String(hex::encode(v)), + ArgValue::Address(v) => Value::String(hex::encode(v)), + ArgValue::UtxoRef(r) => utxo_ref_to_json(r), + ArgValue::UtxoSet(_) => Value::Null, + } +} + +pub fn utxo_to_json(utxo: &Utxo) -> Value { + let assets: Map = utxo + .assets + .iter() + .map(|(class, amount)| (class.to_string(), serde_json::json!(amount))) + .collect(); + + serde_json::json!({ + "ref": utxo_ref_to_json(&utxo.r#ref), + "address": hex::encode(&utxo.address), + "assets": assets, + "datum": utxo.datum, + "script": utxo.script, + }) +} + +fn parse_asset_class(key: &str) -> AssetClass { + if key == "naked" { + AssetClass::Naked + } else if let Some((policy, name)) = key.split_once('.') { + let policy_bytes = hex::decode(policy).unwrap_or_default(); + let name_bytes = hex::decode(name).unwrap_or_default(); + AssetClass::Defined(policy_bytes, name_bytes) + } else { + let name_bytes = hex::decode(key).unwrap_or_default(); + AssetClass::Named(name_bytes) + } +} + +fn assets_from_json(value: &Value) -> Result { + let obj = value.as_object().ok_or(Error::ValueIsNotAString)?; + + let mut assets = CanonicalAssets::empty(); + for (key, amount_val) in obj { + let class = parse_asset_class(key); + let amount = value_to_bigint(amount_val.clone())?; + assets = assets + CanonicalAssets::from_class_and_amount(class, amount); + } + + Ok(assets) +} + +pub fn utxo_from_json(value: &Value) -> Result { + let ref_str = value["ref"].as_str().ok_or(Error::ValueIsNotAString)?; + let utxo_ref = string_to_utxo_ref(ref_str)?; + + let address = hex_to_bytes(value["address"].as_str().ok_or(Error::ValueIsNotAString)?)?; + + let assets = assets_from_json(&value["assets"])?; + + let datum = value + .get("datum") + .filter(|v| !v.is_null()) + .map(|v| serde_json::from_value(v.clone())) + .transpose() + .map_err(|e| Error::InvalidBytesEnvelope(e))?; + + let script = value + .get("script") + .filter(|v| !v.is_null()) + .map(|v| serde_json::from_value(v.clone())) + .transpose() + .map_err(|e| Error::InvalidBytesEnvelope(e))?; + + Ok(Utxo { + r#ref: utxo_ref, + address, + assets, + datum, + script, + }) +} + #[cfg(test)] mod tests { use super::*; @@ -283,24 +421,28 @@ mod tests { } } - fn json_to_value_test(provided: Value, target: Type, expected: ArgValue) { + fn assert_from_json(provided: Value, target: Type, expected: ArgValue) { let value = from_json(provided, &target).unwrap(); assert!(partial_eq(value, expected)); } + // ----------------------------------------------------------------------- + // JSON → Rust (from_json) + // ----------------------------------------------------------------------- + #[test] - fn test_round_trip_small_int() { - json_to_value_test(json!(123456789), Type::Int, ArgValue::Int(123456789)); + fn from_json_small_int() { + assert_from_json(json!(123456789), Type::Int, ArgValue::Int(123456789)); } #[test] - fn test_round_trip_negative_int() { - json_to_value_test(json!(-123456789), Type::Int, ArgValue::Int(-123456789)); + fn from_json_negative_int() { + assert_from_json(json!(-123456789), Type::Int, ArgValue::Int(-123456789)); } #[test] - fn test_round_trip_big_int() { - json_to_value_test( + fn from_json_big_int() { + assert_from_json( json!("12345678901234567890"), Type::Int, ArgValue::Int(12345678901234567890), @@ -308,13 +450,13 @@ mod tests { } #[test] - fn test_round_trip_int_overflow() { - json_to_value_test( + fn from_json_int_overflow() { + assert_from_json( json!(i128::MIN.to_string()), Type::Int, ArgValue::Int(i128::MIN), ); - json_to_value_test( + assert_from_json( json!(i128::MAX.to_string()), Type::Int, ArgValue::Int(i128::MAX), @@ -322,32 +464,32 @@ mod tests { } #[test] - fn test_round_trip_bool() { - json_to_value_test(json!(true), Type::Bool, ArgValue::Bool(true)); - json_to_value_test(json!(false), Type::Bool, ArgValue::Bool(false)); + fn from_json_bool() { + assert_from_json(json!(true), Type::Bool, ArgValue::Bool(true)); + assert_from_json(json!(false), Type::Bool, ArgValue::Bool(false)); } #[test] - fn test_round_trip_bool_number() { - json_to_value_test(json!(1), Type::Bool, ArgValue::Bool(true)); - json_to_value_test(json!(0), Type::Bool, ArgValue::Bool(false)); + fn from_json_bool_number() { + assert_from_json(json!(1), Type::Bool, ArgValue::Bool(true)); + assert_from_json(json!(0), Type::Bool, ArgValue::Bool(false)); } #[test] - fn test_round_trip_bool_string() { - json_to_value_test(json!("true"), Type::Bool, ArgValue::Bool(true)); - json_to_value_test(json!("false"), Type::Bool, ArgValue::Bool(false)); + fn from_json_bool_string() { + assert_from_json(json!("true"), Type::Bool, ArgValue::Bool(true)); + assert_from_json(json!("false"), Type::Bool, ArgValue::Bool(false)); } #[test] - fn test_round_trip_bytes() { - json_to_value_test( + fn from_json_bytes() { + assert_from_json( json!(hex::encode("hello")), Type::Bytes, ArgValue::Bytes(b"hello".to_vec()), ); - json_to_value_test( + assert_from_json( json!(format!("0x{}", hex::encode("hello"))), Type::Bytes, ArgValue::Bytes(b"hello".to_vec()), @@ -355,28 +497,28 @@ mod tests { } #[test] - fn test_round_trip_bytes_base64() { + fn from_json_bytes_base64() { let json = json!(BytesEnvelope { content: "aGVsbG8=".to_string(), content_type: BytesEncoding::Base64, }); - json_to_value_test(json, Type::Bytes, ArgValue::Bytes(b"hello".to_vec())); + assert_from_json(json, Type::Bytes, ArgValue::Bytes(b"hello".to_vec())); } #[test] - fn test_round_trip_bytes_hex() { + fn from_json_bytes_hex() { let json = json!(BytesEnvelope { content: "68656c6c6f".to_string(), content_type: BytesEncoding::Hex, }); - json_to_value_test(json, Type::Bytes, ArgValue::Bytes(b"hello".to_vec())); + assert_from_json(json, Type::Bytes, ArgValue::Bytes(b"hello".to_vec())); } #[test] - fn test_round_trip_address() { - json_to_value_test( + fn from_json_address() { + assert_from_json( json!(hex::encode("abc123")), Type::Address, ArgValue::Address(b"abc123".to_vec()), @@ -384,15 +526,15 @@ mod tests { } #[test] - fn test_round_trip_address_bech32() { + fn from_json_address_bech32() { let json = json!("addr1vx2fxv2umyhttkxyxp8x0dlpdt3k6cwng5pxj3jhsydzers66hrl8"); let bytes = hex::decode("619493315cd92eb5d8c4304e67b7e16ae36d61d34502694657811a2c8e").unwrap(); - json_to_value_test(json, Type::Address, ArgValue::Address(bytes)); + assert_from_json(json, Type::Address, ArgValue::Address(bytes)); } #[test] - fn test_round_trip_utxo_ref() { + fn from_json_utxo_ref() { let json = json!("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef#0"); let utxo_ref = UtxoRef { @@ -401,6 +543,125 @@ mod tests { index: 0, }; - json_to_value_test(json, Type::UtxoRef, ArgValue::UtxoRef(utxo_ref)); + assert_from_json(json, Type::UtxoRef, ArgValue::UtxoRef(utxo_ref)); + } + + // ----------------------------------------------------------------------- + // Rust → JSON (to_json) + // ----------------------------------------------------------------------- + + use tx3_tir::model::assets::{AssetClass, CanonicalAssets}; + use tx3_tir::model::core::Utxo; + + fn sample_utxo_ref() -> UtxoRef { + UtxoRef { + txid: hex::decode("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef") + .unwrap(), + index: 2, + } + } + + #[test] + fn utxo_ref_to_json_format() { + let r = sample_utxo_ref(); + let v = utxo_ref_to_json(&r); + assert_eq!( + v, + json!("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef#2") + ); + } + + #[test] + fn arg_to_json_int() { + assert_eq!(arg_to_json(&ArgValue::Int(42)), json!(42)); + } + + #[test] + fn arg_to_json_bool() { + assert_eq!(arg_to_json(&ArgValue::Bool(true)), json!(true)); + } + + #[test] + fn arg_to_json_string() { + assert_eq!( + arg_to_json(&ArgValue::String("hello".into())), + json!("hello") + ); + } + + #[test] + fn arg_to_json_bytes() { + assert_eq!( + arg_to_json(&ArgValue::Bytes(b"hello".to_vec())), + json!("68656c6c6f") + ); + } + + #[test] + fn arg_to_json_address() { + assert_eq!( + arg_to_json(&ArgValue::Address(b"\x01\x02".to_vec())), + json!("0102") + ); + } + + #[test] + fn arg_to_json_utxo_ref() { + let r = sample_utxo_ref(); + assert_eq!( + arg_to_json(&ArgValue::UtxoRef(r.clone())), + utxo_ref_to_json(&r) + ); + } + + #[test] + fn utxo_to_json_empty_assets() { + let utxo = Utxo { + r#ref: sample_utxo_ref(), + address: b"\xab\xcd".to_vec(), + assets: CanonicalAssets::empty(), + datum: None, + script: None, + }; + + let v = utxo_to_json(&utxo); + assert_eq!(v["assets"], json!({})); + assert_eq!(v["address"], json!("abcd")); + assert!(v["datum"].is_null()); + assert!(v["script"].is_null()); + } + + #[test] + fn utxo_to_json_naked_assets() { + let utxo = Utxo { + r#ref: sample_utxo_ref(), + address: b"\x01".to_vec(), + assets: CanonicalAssets::from_naked_amount(5_000_000), + datum: None, + script: None, + }; + + let v = utxo_to_json(&utxo); + assert_eq!(v["assets"]["naked"], json!(5_000_000)); + } + + #[test] + fn utxo_to_json_defined_assets() { + let assets = CanonicalAssets::from_class_and_amount( + AssetClass::Defined(b"policy1".to_vec(), b"token1".to_vec()), + 100, + ); + + let utxo = Utxo { + r#ref: sample_utxo_ref(), + address: b"\x01".to_vec(), + assets, + datum: None, + script: None, + }; + + let v = utxo_to_json(&utxo); + let key = format!("{}.{}", hex::encode(b"policy1"), hex::encode(b"token1")); + assert_eq!(v["assets"][&key], json!(100)); } } diff --git a/crates/tx3-resolver/src/job.rs b/crates/tx3-resolver/src/job.rs index f05225a2..4a8fcf16 100644 --- a/crates/tx3-resolver/src/job.rs +++ b/crates/tx3-resolver/src/job.rs @@ -2,6 +2,7 @@ use std::collections::{BTreeMap, HashMap}; use std::path::Path; use serde::{Deserialize, Serialize}; +use serde_json::Value; use tx3_tir::compile::{CompiledTx, Compiler}; use tx3_tir::encoding::AnyTir; use tx3_tir::model::core::{Utxo, UtxoRef, UtxoSet}; @@ -35,6 +36,7 @@ pub struct ResolveJob { // Inputs (set once at creation) pub original_tir: AnyTir, pub args: ArgMap, + pub compiler: Value, // Pipeline state pub round: usize, @@ -80,6 +82,7 @@ impl ResolveJob { Self { original_tir: tx, args, + compiler: Value::Null, round: 0, last_eval: None, converged: false, @@ -126,17 +129,6 @@ impl ResolveJob { .unwrap_or_default() } - pub fn dump_to_dir(&self, dir: &Path) -> Result { - std::fs::create_dir_all(dir)?; - let dump_id = uuid::Uuid::new_v4().to_string(); - let path = dir.join(format!("resolve-job-{dump_id}.json")); - let file = std::fs::File::create(&path)?; - serde_json::to_writer_pretty(file, self) - .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?; - tracing::debug!(dump_id = %dump_id, path = %path.display(), "diagnostic dump written"); - Ok(dump_id) - } - pub fn to_input_map(&self) -> BTreeMap { self.input_queries .iter() @@ -222,6 +214,14 @@ impl ResolveJob { { let max_optimize_rounds = max_optimize_rounds.max(3); + self.compiler = match serde_json::to_value(&*compiler) { + Ok(value) => value, + Err(err) => { + tracing::warn!(error = %err, "failed to serialize compiler for diagnostic dump"); + Value::Null + } + }; + self.apply_args()?; loop { @@ -252,7 +252,7 @@ where let result = job.execute(compiler, utxos, max_optimize_rounds).await; if let Ok(dir) = std::env::var("TX3_DIAGNOSTIC_DUMP") { - let _ = job.dump_to_dir(Path::new(&dir)); + let _ = crate::dump::dump_to_dir(&job, Path::new(&dir)); } result diff --git a/crates/tx3-resolver/src/lib.rs b/crates/tx3-resolver/src/lib.rs index 86257500..c0d45150 100644 --- a/crates/tx3-resolver/src/lib.rs +++ b/crates/tx3-resolver/src/lib.rs @@ -2,11 +2,15 @@ use std::collections::HashSet; use crate::inputs::CanonicalQuery; +pub mod dump; pub mod inputs; pub mod interop; pub mod job; pub mod trp; +#[cfg(test)] +pub(crate) mod test_utils; + pub use job::resolve_tx; pub use tx3_tir::model::assets::CanonicalAssets; pub use tx3_tir::model::core::{Type, Utxo, UtxoRef, UtxoSet}; diff --git a/crates/tx3-resolver/src/inputs/test_utils.rs b/crates/tx3-resolver/src/test_utils.rs similarity index 99% rename from crates/tx3-resolver/src/inputs/test_utils.rs rename to crates/tx3-resolver/src/test_utils.rs index 78d0e687..e64c37a7 100644 --- a/crates/tx3-resolver/src/inputs/test_utils.rs +++ b/crates/tx3-resolver/src/test_utils.rs @@ -8,7 +8,7 @@ use tx3_tir::model::{ core::{Utxo, UtxoRef}, }; -use super::canonical::CanonicalQuery; +use crate::inputs::CanonicalQuery; // --------------------------------------------------------------------------- // proptest strategies — primitives diff --git a/crates/tx3-resolver/src/trp/mod.rs b/crates/tx3-resolver/src/trp/mod.rs index 2af02524..803b4eb9 100644 --- a/crates/tx3-resolver/src/trp/mod.rs +++ b/crates/tx3-resolver/src/trp/mod.rs @@ -3,38 +3,9 @@ pub mod spec; pub use spec::*; -use tx3_tir::{ - encoding::{AnyTir, TirVersion}, - reduce::ArgMap, -}; +use tx3_tir::{encoding::AnyTir, reduce::ArgMap}; -use crate::{ - interop::{self, base64_to_bytes, hex_to_bytes, BytesEncoding}, - Error, -}; - -impl From for Vec { - fn from(envelope: TirEnvelope) -> Self { - match envelope.encoding { - BytesEncoding::Base64 => base64_to_bytes(&envelope.content).unwrap(), - BytesEncoding::Hex => hex_to_bytes(&envelope.content).unwrap(), - } - } -} - -impl TryFrom for AnyTir { - type Error = Error; - - fn try_from(envelope: TirEnvelope) -> Result { - let version = TirVersion::try_from(envelope.version.as_str())?; - - let bytes: Vec = envelope.into(); - - let tir = tx3_tir::encoding::from_bytes(&bytes, version)?; - - Ok(tir) - } -} +use crate::{interop, Error}; pub fn parse_resolve_request(request: spec::ResolveParams) -> Result<(AnyTir, ArgMap), Error> { let tir = AnyTir::try_from(request.tir)?; diff --git a/crates/tx3-resolver/src/trp/spec.rs b/crates/tx3-resolver/src/trp/spec.rs index 4b93e610..d163f42b 100644 --- a/crates/tx3-resolver/src/trp/spec.rs +++ b/crates/tx3-resolver/src/trp/spec.rs @@ -1,20 +1,11 @@ // Generated by cargo xtask gen --lang rust use serde::{Deserialize, Serialize}; -use crate::interop::{BytesEncoding, BytesEnvelope}; +use crate::interop::{BytesEnvelope, TirEnvelope}; pub type EnvMap = serde_json::Map; pub type JsonArgMap = serde_json::Map; -#[derive(Debug, Deserialize, Serialize, Clone)] -pub struct TirEnvelope { - // Aliases for backward compatibility - #[serde(alias = "bytecode", alias = "payload")] - pub content: String, - pub encoding: BytesEncoding, - pub version: String, -} - #[derive(Debug, Clone, Serialize, Deserialize)] pub struct SubmitParams { #[serde(rename = "tx")] @@ -37,7 +28,6 @@ pub struct SubmitResponse { pub hash: String, } - #[derive(Debug, Clone, Serialize, Deserialize)] pub struct InputQueryDiagnostic { #[serde(rename = "address")] diff --git a/crates/tx3-resolver/tests/golden.rs b/crates/tx3-resolver/tests/golden.rs new file mode 100644 index 00000000..cfcf596f --- /dev/null +++ b/crates/tx3-resolver/tests/golden.rs @@ -0,0 +1,193 @@ +use std::collections::{HashMap, HashSet}; + +use serde_json::Value; +use tx3_cardano::Compiler; +use tx3_resolver::{resolve_tx, Error, UtxoPattern, UtxoRef, UtxoSet, UtxoStore}; +use tx3_resolver::interop::TirEnvelope; +use tx3_tir::encoding::AnyTir; +use tx3_tir::model::assets::AssetClass; +use tx3_tir::model::core::Utxo; +use tx3_tir::reduce::ArgMap; + +// --------------------------------------------------------------------------- +// Helpers — parse compiler from dump JSON +// --------------------------------------------------------------------------- + +fn compiler_from_json(compiler: &Value) -> Compiler { + serde_json::from_value(compiler.clone()).expect("compiler should deserialize from dump") +} + +// --------------------------------------------------------------------------- +// Fixture parsing — cherry-pick values from dump JSON +// --------------------------------------------------------------------------- + +struct ResolveFixture { + tir: AnyTir, + args: ArgMap, + input_pool: HashMap, + compiler: Value, + expected_hash: String, + expected_fee: u64, +} + +fn load_fixture(name: &str) -> ResolveFixture { + let path = format!("{}/tests/golden/{name}.json", env!("CARGO_MANIFEST_DIR")); + let contents = + std::fs::read_to_string(&path).unwrap_or_else(|e| panic!("failed to read {path}: {e}")); + let value: Value = + serde_json::from_str(&contents).unwrap_or_else(|e| panic!("failed to parse {path}: {e}")); + + // 1. Decode TIR from TirEnvelope + let tir_envelope: TirEnvelope = serde_json::from_value(value["original_tir"].clone()) + .expect("original_tir should be a valid TirEnvelope"); + let tir = AnyTir::try_from(tir_envelope).expect("tir should decode from TirEnvelope"); + + // 2. Parse args using TIR param types + interop + let params = tx3_tir::reduce::find_params(&tir); + let mut args = ArgMap::new(); + if let Some(args_obj) = value["args"].as_object() { + for (key, val) in args_obj { + if let Some(ty) = params.get(key) { + let arg = tx3_resolver::interop::from_json(val.clone(), ty) + .unwrap_or_else(|e| panic!("failed to parse arg '{key}': {e}")); + args.insert(key.clone(), arg); + } + } + } + + // 3. Parse input_pool UTxOs + let mut input_pool = HashMap::new(); + if let Some(pool_obj) = value["input_pool"].as_object() { + for (_, utxo_val) in pool_obj { + let utxo = tx3_resolver::interop::utxo_from_json(utxo_val) + .unwrap_or_else(|e| panic!("failed to parse utxo: {e}")); + input_pool.insert(utxo.r#ref.clone(), utxo); + } + } + + // 4. Parse compiler config + let compiler = value["compiler"].clone(); + assert!( + !compiler.is_null(), + "golden fixture '{name}' is missing compiler — regenerate the dump" + ); + + // 5. Read expected values directly + let expected_hash = value["last_eval"]["hash"] + .as_str() + .expect("last_eval.hash should be a hex string") + .to_string(); + let expected_fee = value["last_eval"]["fee"] + .as_u64() + .expect("last_eval.fee should be a number"); + + ResolveFixture { + tir, + args, + input_pool, + compiler, + expected_hash, + expected_fee, + } +} + +/// Lists all golden dump files available in the `tests/golden/` directory. +fn golden_fixtures() -> Vec { + let dir = format!("{}/tests/golden", env!("CARGO_MANIFEST_DIR")); + let mut names = Vec::new(); + + for entry in std::fs::read_dir(&dir).expect("golden directory should exist") { + let entry = entry.unwrap(); + let path = entry.path(); + if path.extension().and_then(|e| e.to_str()) == Some("json") { + let stem = path.file_stem().unwrap().to_str().unwrap().to_string(); + names.push(stem); + } + } + + names.sort(); + names +} + +// --------------------------------------------------------------------------- +// DumpStore — a simple UtxoStore backed by the fixture's input_pool +// --------------------------------------------------------------------------- + +struct DumpStore { + pool: HashMap, +} + +impl From> for DumpStore { + fn from(pool: HashMap) -> Self { + Self { pool } + } +} + +impl DumpStore { + fn matches_pattern(utxo: &Utxo, pattern: &UtxoPattern<'_>) -> bool { + match pattern { + UtxoPattern::ByAddress(addr) => utxo.address == *addr, + UtxoPattern::ByAssetPolicy(policy) => utxo + .assets + .iter() + .any(|(class, _)| matches!(class, AssetClass::Defined(p, _) if p == policy)), + UtxoPattern::ByAsset(policy, name) => utxo.assets.asset_amount2(policy, name).is_some(), + } + } +} + +impl UtxoStore for DumpStore { + async fn narrow_refs(&self, pattern: UtxoPattern<'_>) -> Result, Error> { + let refs = self + .pool + .iter() + .filter(|(_, utxo)| Self::matches_pattern(utxo, &pattern)) + .map(|(r, _)| r.clone()) + .collect(); + Ok(refs) + } + + async fn fetch_utxos(&self, refs: HashSet) -> Result { + Ok(refs + .iter() + .filter_map(|r| self.pool.get(r).cloned()) + .collect()) + } +} + +// --------------------------------------------------------------------------- +// Tests +// --------------------------------------------------------------------------- + +#[pollster::test] +async fn golden_resolve_jobs() { + let fixtures = golden_fixtures(); + + if fixtures.is_empty() { + eprintln!("no golden dump files found in tests/golden/, skipping"); + return; + } + + for name in &fixtures { + let fixture = load_fixture(name); + let store = DumpStore::from(fixture.input_pool); + let mut compiler = compiler_from_json(&fixture.compiler); + + let compiled = resolve_tx(fixture.tir, &fixture.args, &mut compiler, &store, 10) + .await + .unwrap_or_else(|e| panic!("resolve_tx failed for '{name}': {e}")); + + dbg!(hex::encode(compiled.payload)); + + assert_eq!( + hex::encode(&compiled.hash), + fixture.expected_hash, + "hash mismatch for '{name}'" + ); + + assert_eq!( + compiled.fee, fixture.expected_fee, + "fee mismatch for '{name}'" + ); + } +} diff --git a/crates/tx3-resolver/tests/golden/.gitkeep b/crates/tx3-resolver/tests/golden/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/crates/tx3-resolver/tests/golden/simple_transfer.json b/crates/tx3-resolver/tests/golden/simple_transfer.json new file mode 100644 index 00000000..c807fbe9 --- /dev/null +++ b/crates/tx3-resolver/tests/golden/simple_transfer.json @@ -0,0 +1,1229 @@ +{ + "args": { + "quantity": 100000000, + "receiver": "001b71a43b8dbb51b27541c2c8039fbe4b1cdb14e3dcabf5542320dad60dd95be0e31d20781507071c4fd651012a7655ec39f4adf3e3517791", + "sender": "001b71a43b8dbb51b27541c2c8039fbe4b1cdb14e3dcabf5542320dad60dd95be0e31d20781507071c4fd651012a7655ec39f4adf3e3517791" + }, + "compiler": { + "config": { + "extra_fees": null + }, + "cursor": { + "hash": [ + 201, + 97, + 237, + 237, + 131, + 230, + 236, + 100, + 129, + 60, + 223, + 249, + 176, + 58, + 98, + 183, + 25, + 38, + 56, + 235, + 17, + 140, + 219, + 37, + 36, + 115, + 222, + 62, + 107, + 96, + 230, + 81 + ], + "slot": 113419151, + "timestamp": 1769102351 + }, + "pparams": { + "coins_per_utxo_byte": 4310, + "cost_models": { + "0": [ + 100788, + 420, + 1, + 1, + 1000, + 173, + 0, + 1, + 1000, + 59957, + 4, + 1, + 11183, + 32, + 201305, + 8356, + 4, + 16000, + 100, + 16000, + 100, + 16000, + 100, + 16000, + 100, + 16000, + 100, + 16000, + 100, + 100, + 100, + 16000, + 100, + 94375, + 32, + 132994, + 32, + 61462, + 4, + 72010, + 178, + 0, + 1, + 22151, + 32, + 91189, + 769, + 4, + 2, + 85848, + 228465, + 122, + 0, + 1, + 1, + 1000, + 42921, + 4, + 2, + 24548, + 29498, + 38, + 1, + 898148, + 27279, + 1, + 51775, + 558, + 1, + 39184, + 1000, + 60594, + 1, + 141895, + 32, + 83150, + 32, + 15299, + 32, + 76049, + 1, + 13169, + 4, + 22100, + 10, + 28999, + 74, + 1, + 28999, + 74, + 1, + 43285, + 552, + 1, + 44749, + 541, + 1, + 33852, + 32, + 68246, + 32, + 72362, + 32, + 7243, + 32, + 7391, + 32, + 11546, + 32, + 85848, + 228465, + 122, + 0, + 1, + 1, + 90434, + 519, + 0, + 1, + 74433, + 32, + 85848, + 228465, + 122, + 0, + 1, + 1, + 85848, + 228465, + 122, + 0, + 1, + 1, + 270652, + 22588, + 4, + 1457325, + 64566, + 4, + 20467, + 1, + 4, + 0, + 141992, + 32, + 100788, + 420, + 1, + 1, + 81663, + 32, + 59498, + 32, + 20142, + 32, + 24588, + 32, + 20744, + 32, + 25933, + 32, + 24623, + 32, + 53384111, + 14333, + 10 + ], + "1": [ + 100788, + 420, + 1, + 1, + 1000, + 173, + 0, + 1, + 1000, + 59957, + 4, + 1, + 11183, + 32, + 201305, + 8356, + 4, + 16000, + 100, + 16000, + 100, + 16000, + 100, + 16000, + 100, + 16000, + 100, + 16000, + 100, + 100, + 100, + 16000, + 100, + 94375, + 32, + 132994, + 32, + 61462, + 4, + 72010, + 178, + 0, + 1, + 22151, + 32, + 91189, + 769, + 4, + 2, + 85848, + 228465, + 122, + 0, + 1, + 1, + 1000, + 42921, + 4, + 2, + 24548, + 29498, + 38, + 1, + 898148, + 27279, + 1, + 51775, + 558, + 1, + 39184, + 1000, + 60594, + 1, + 141895, + 32, + 83150, + 32, + 15299, + 32, + 76049, + 1, + 13169, + 4, + 22100, + 10, + 28999, + 74, + 1, + 28999, + 74, + 1, + 43285, + 552, + 1, + 44749, + 541, + 1, + 33852, + 32, + 68246, + 32, + 72362, + 32, + 7243, + 32, + 7391, + 32, + 11546, + 32, + 85848, + 228465, + 122, + 0, + 1, + 1, + 90434, + 519, + 0, + 1, + 74433, + 32, + 85848, + 228465, + 122, + 0, + 1, + 1, + 85848, + 228465, + 122, + 0, + 1, + 1, + 955506, + 213312, + 0, + 2, + 270652, + 22588, + 4, + 1457325, + 64566, + 4, + 20467, + 1, + 4, + 0, + 141992, + 32, + 100788, + 420, + 1, + 1, + 81663, + 32, + 59498, + 32, + 20142, + 32, + 24588, + 32, + 20744, + 32, + 25933, + 32, + 24623, + 32, + 43053543, + 10, + 53384111, + 14333, + 10, + 43574283, + 26308, + 10 + ], + "2": [ + 100788, + 420, + 1, + 1, + 1000, + 173, + 0, + 1, + 1000, + 59957, + 4, + 1, + 11183, + 32, + 201305, + 8356, + 4, + 16000, + 100, + 16000, + 100, + 16000, + 100, + 16000, + 100, + 16000, + 100, + 16000, + 100, + 100, + 100, + 16000, + 100, + 94375, + 32, + 132994, + 32, + 61462, + 4, + 72010, + 178, + 0, + 1, + 22151, + 32, + 91189, + 769, + 4, + 2, + 85848, + 123203, + 7305, + -900, + 1716, + 549, + 57, + 85848, + 0, + 1, + 1, + 1000, + 42921, + 4, + 2, + 24548, + 29498, + 38, + 1, + 898148, + 27279, + 1, + 51775, + 558, + 1, + 39184, + 1000, + 60594, + 1, + 141895, + 32, + 83150, + 32, + 15299, + 32, + 76049, + 1, + 13169, + 4, + 22100, + 10, + 28999, + 74, + 1, + 28999, + 74, + 1, + 43285, + 552, + 1, + 44749, + 541, + 1, + 33852, + 32, + 68246, + 32, + 72362, + 32, + 7243, + 32, + 7391, + 32, + 11546, + 32, + 85848, + 123203, + 7305, + -900, + 1716, + 549, + 57, + 85848, + 0, + 1, + 90434, + 519, + 0, + 1, + 74433, + 32, + 85848, + 123203, + 7305, + -900, + 1716, + 549, + 57, + 85848, + 0, + 1, + 1, + 85848, + 123203, + 7305, + -900, + 1716, + 549, + 57, + 85848, + 0, + 1, + 955506, + 213312, + 0, + 2, + 270652, + 22588, + 4, + 1457325, + 64566, + 4, + 20467, + 1, + 4, + 0, + 141992, + 32, + 100788, + 420, + 1, + 1, + 81663, + 32, + 59498, + 32, + 20142, + 32, + 24588, + 32, + 20744, + 32, + 25933, + 32, + 24623, + 32, + 43053543, + 10, + 53384111, + 14333, + 10, + 43574283, + 26308, + 10, + 16000, + 100, + 16000, + 100, + 962335, + 18, + 2780678, + 6, + 442008, + 1, + 52538055, + 3756, + 18, + 267929, + 18, + 76433006, + 8868, + 18, + 52948122, + 18, + 1995836, + 36, + 3227919, + 12, + 901022, + 1, + 166917843, + 4307, + 36, + 284546, + 36, + 158221314, + 26549, + 36, + 74698472, + 36, + 333849714, + 1, + 254006273, + 72, + 2174038, + 72, + 2261318, + 64571, + 4, + 207616, + 8310, + 4, + 1293828, + 28716, + 63, + 0, + 1, + 1006041, + 43623, + 251, + 0, + 1, + 100181, + 726, + 719, + 0, + 1, + 100181, + 726, + 719, + 0, + 1, + 100181, + 726, + 719, + 0, + 1, + 107878, + 680, + 0, + 1, + 95336, + 1, + 281145, + 18848, + 0, + 1, + 180194, + 159, + 1, + 1, + 158519, + 8942, + 0, + 1, + 159378, + 8813, + 0, + 1, + 107490, + 3298, + 1, + 106057, + 655, + 1, + 1964219, + 24520, + 3 + ] + }, + "min_fee_coefficient": 44, + "min_fee_constant": 155381, + "network": "Testnet" + } + }, + "converged": true, + "input_pool": { + "0a6b64c1699fda78a157e10a78f2944c19a883840353b39c8c8362148e5f528c#0": { + "address": "001b71a43b8dbb51b27541c2c8039fbe4b1cdb14e3dcabf5542320dad60dd95be0e31d20781507071c4fd651012a7655ec39f4adf3e3517791", + "assets": { + "naked": 30000000 + }, + "datum": null, + "ref": "0a6b64c1699fda78a157e10a78f2944c19a883840353b39c8c8362148e5f528c#0", + "script": null + }, + "0ddf1b1b621a7ed40077a9bd9d078839b7688110859d04707f7ebc4b5dc59674#0": { + "address": "001b71a43b8dbb51b27541c2c8039fbe4b1cdb14e3dcabf5542320dad60dd95be0e31d20781507071c4fd651012a7655ec39f4adf3e3517791", + "assets": { + "naked": 12000000 + }, + "datum": null, + "ref": "0ddf1b1b621a7ed40077a9bd9d078839b7688110859d04707f7ebc4b5dc59674#0", + "script": null + }, + "0ebb4f9a6731e445c16ccd2ff82bb62bf49437896a685276872ce4f24307e230#0": { + "address": "001b71a43b8dbb51b27541c2c8039fbe4b1cdb14e3dcabf5542320dad60dd95be0e31d20781507071c4fd651012a7655ec39f4adf3e3517791", + "assets": { + "naked": 4000000 + }, + "datum": null, + "ref": "0ebb4f9a6731e445c16ccd2ff82bb62bf49437896a685276872ce4f24307e230#0", + "script": null + }, + "14dabd766dba51db7366c23fe9b555d1baba86c1263b2161529d859f68ad416c#4": { + "address": "001b71a43b8dbb51b27541c2c8039fbe4b1cdb14e3dcabf5542320dad60dd95be0e31d20781507071c4fd651012a7655ec39f4adf3e3517791", + "assets": { + "1add5bf9d0be4636d260d34be5869c2f89f65afd6734826c63c673b9.4d61696e7461696e6572": 1, + "naked": 1172320 + }, + "datum": null, + "ref": "14dabd766dba51db7366c23fe9b555d1baba86c1263b2161529d859f68ad416c#4", + "script": null + }, + "1dbbacfdc5cecc41bf0016a3d655a565eeab7ddae8bac519a8d1eeda0b331809#0": { + "address": "001b71a43b8dbb51b27541c2c8039fbe4b1cdb14e3dcabf5542320dad60dd95be0e31d20781507071c4fd651012a7655ec39f4adf3e3517791", + "assets": { + "naked": 30000000 + }, + "datum": null, + "ref": "1dbbacfdc5cecc41bf0016a3d655a565eeab7ddae8bac519a8d1eeda0b331809#0", + "script": null + }, + "2348c9e0ebc93766f00eede169cbba413e69ab6ddb396f0e22af8660dc913fc2#0": { + "address": "001b71a43b8dbb51b27541c2c8039fbe4b1cdb14e3dcabf5542320dad60dd95be0e31d20781507071c4fd651012a7655ec39f4adf3e3517791", + "assets": { + "naked": 4000000 + }, + "datum": null, + "ref": "2348c9e0ebc93766f00eede169cbba413e69ab6ddb396f0e22af8660dc913fc2#0", + "script": null + }, + "2d1ebe554ef1f908d4fc0ba6510fdedf2005d5f69ce00fe6116365ee320b627f#2": { + "address": "001b71a43b8dbb51b27541c2c8039fbe4b1cdb14e3dcabf5542320dad60dd95be0e31d20781507071c4fd651012a7655ec39f4adf3e3517791", + "assets": { + "24568e6d0b1f20c394678633c58a68c8091d8a55e9b3b8f0efc523aa.4d61696e7461696e6572": 1, + "naked": 1172320 + }, + "datum": null, + "ref": "2d1ebe554ef1f908d4fc0ba6510fdedf2005d5f69ce00fe6116365ee320b627f#2", + "script": null + }, + "4babd06c8ceda932ce1403b090aeb4ab4809c0d3bc6d717fe4bba6cfdae90b5c#1": { + "address": "001b71a43b8dbb51b27541c2c8039fbe4b1cdb14e3dcabf5542320dad60dd95be0e31d20781507071c4fd651012a7655ec39f4adf3e3517791", + "assets": { + "naked": 7324286764 + }, + "datum": null, + "ref": "4babd06c8ceda932ce1403b090aeb4ab4809c0d3bc6d717fe4bba6cfdae90b5c#1", + "script": null + }, + "5391db3f321e19456bc5d961c901d94a792409ea4b7dd77437cd05904561be83#0": { + "address": "001b71a43b8dbb51b27541c2c8039fbe4b1cdb14e3dcabf5542320dad60dd95be0e31d20781507071c4fd651012a7655ec39f4adf3e3517791", + "assets": { + "naked": 12000000 + }, + "datum": null, + "ref": "5391db3f321e19456bc5d961c901d94a792409ea4b7dd77437cd05904561be83#0", + "script": null + }, + "6883235a5955048688738e234f0d85326ed92eb7f62f69f9df3054f63b8871a4#1": { + "address": "001b71a43b8dbb51b27541c2c8039fbe4b1cdb14e3dcabf5542320dad60dd95be0e31d20781507071c4fd651012a7655ec39f4adf3e3517791", + "assets": { + "naked": 50000000 + }, + "datum": null, + "ref": "6883235a5955048688738e234f0d85326ed92eb7f62f69f9df3054f63b8871a4#1", + "script": null + }, + "6926a55900b0b10be97c1acd23c4026489688c57ed7b0e25ec3850a969c65c9a#4": { + "address": "001b71a43b8dbb51b27541c2c8039fbe4b1cdb14e3dcabf5542320dad60dd95be0e31d20781507071c4fd651012a7655ec39f4adf3e3517791", + "assets": { + "b4a5de5867ffbaedbf5e5fd92b88a8dc537592a74c025185ceca72ac.4d61696e7461696e6572": 1, + "naked": 1172320 + }, + "datum": null, + "ref": "6926a55900b0b10be97c1acd23c4026489688c57ed7b0e25ec3850a969c65c9a#4", + "script": null + }, + "6c0948656eb42ea76f571804ecde2d8bf8977319c94d282786554b74f972a556#4": { + "address": "001b71a43b8dbb51b27541c2c8039fbe4b1cdb14e3dcabf5542320dad60dd95be0e31d20781507071c4fd651012a7655ec39f4adf3e3517791", + "assets": { + "9b71b47901e8000b5adb3bf402032b9468fc680802bd7ce779daf991.4d61696e7461696e6572": 1, + "naked": 1172320 + }, + "datum": null, + "ref": "6c0948656eb42ea76f571804ecde2d8bf8977319c94d282786554b74f972a556#4", + "script": null + }, + "714fcfbf9dcee8e8fde8637b613ba69dce8f25d7a7214a6e983fb7c54a56038f#4": { + "address": "001b71a43b8dbb51b27541c2c8039fbe4b1cdb14e3dcabf5542320dad60dd95be0e31d20781507071c4fd651012a7655ec39f4adf3e3517791", + "assets": { + "1417f7aecaa0fc6189c5725a7a42a8c6b1ce7bea4dee3f16db719a54.4d61696e7461696e6572": 1, + "naked": 1172320 + }, + "datum": null, + "ref": "714fcfbf9dcee8e8fde8637b613ba69dce8f25d7a7214a6e983fb7c54a56038f#4", + "script": null + }, + "76e7aeb57595219fe9d5fc0053cfaa498b86f4cd4b5d4e79cff78bc6173bda76#0": { + "address": "001b71a43b8dbb51b27541c2c8039fbe4b1cdb14e3dcabf5542320dad60dd95be0e31d20781507071c4fd651012a7655ec39f4adf3e3517791", + "assets": { + "naked": 12000000 + }, + "datum": null, + "ref": "76e7aeb57595219fe9d5fc0053cfaa498b86f4cd4b5d4e79cff78bc6173bda76#0", + "script": null + }, + "82afeb6114e2d356c625684640c40556bfad774716359fd3ecca7b42b52f8997#0": { + "address": "001b71a43b8dbb51b27541c2c8039fbe4b1cdb14e3dcabf5542320dad60dd95be0e31d20781507071c4fd651012a7655ec39f4adf3e3517791", + "assets": { + "9b71b47901e8000b5adb3bf402032b9468fc680802bd7ce779daf991.446576656c6f706572": 1, + "naked": 1159390 + }, + "datum": null, + "ref": "82afeb6114e2d356c625684640c40556bfad774716359fd3ecca7b42b52f8997#0", + "script": null + }, + "99bf1b15aac118d9ba581e7c19ab7204b428cb8e8ba4b012891a64bd51ee55b5#4": { + "address": "001b71a43b8dbb51b27541c2c8039fbe4b1cdb14e3dcabf5542320dad60dd95be0e31d20781507071c4fd651012a7655ec39f4adf3e3517791", + "assets": { + "4c2ba85d2a52e0e624b1d6b65566ddbc5b2181723069d3622038cb6a.4d61696e7461696e6572": 1, + "naked": 1172320 + }, + "datum": null, + "ref": "99bf1b15aac118d9ba581e7c19ab7204b428cb8e8ba4b012891a64bd51ee55b5#4", + "script": null + }, + "a7d1c14b76547e328c4c7e7160eed1fc0bc0644730b6ddc9c80360778e94f61c#0": { + "address": "001b71a43b8dbb51b27541c2c8039fbe4b1cdb14e3dcabf5542320dad60dd95be0e31d20781507071c4fd651012a7655ec39f4adf3e3517791", + "assets": { + "naked": 6000000 + }, + "datum": null, + "ref": "a7d1c14b76547e328c4c7e7160eed1fc0bc0644730b6ddc9c80360778e94f61c#0", + "script": null + }, + "b3a62a039b0e5994640b735532bfa9ae3644f5059a2bd54a68133b36c6e06f55#4": { + "address": "001b71a43b8dbb51b27541c2c8039fbe4b1cdb14e3dcabf5542320dad60dd95be0e31d20781507071c4fd651012a7655ec39f4adf3e3517791", + "assets": { + "cf02cad1f63c0db55a5b4b3dacf52d93fe7df574e4ad5d8be139ed4d.4d61696e7461696e6572": 1, + "naked": 1172320 + }, + "datum": null, + "ref": "b3a62a039b0e5994640b735532bfa9ae3644f5059a2bd54a68133b36c6e06f55#4", + "script": null + }, + "bf72a2fb5eb76fd3f181fc63add10d77054921f28e22e8e104baed4bb7fa9ac6#0": { + "address": "001b71a43b8dbb51b27541c2c8039fbe4b1cdb14e3dcabf5542320dad60dd95be0e31d20781507071c4fd651012a7655ec39f4adf3e3517791", + "assets": { + "naked": 5000000 + }, + "datum": null, + "ref": "bf72a2fb5eb76fd3f181fc63add10d77054921f28e22e8e104baed4bb7fa9ac6#0", + "script": null + }, + "c5e4573f5b17ff377474e5a9b8994d7edce18a251c4bdadf6414dbe79967c4ba#4": { + "address": "001b71a43b8dbb51b27541c2c8039fbe4b1cdb14e3dcabf5542320dad60dd95be0e31d20781507071c4fd651012a7655ec39f4adf3e3517791", + "assets": { + "e8edf9a5a1f70fb1ef17d18817401fab9041627fafe104b9930f954e.4d61696e7461696e6572": 1, + "naked": 1172320 + }, + "datum": null, + "ref": "c5e4573f5b17ff377474e5a9b8994d7edce18a251c4bdadf6414dbe79967c4ba#4", + "script": null + }, + "cc79d8e1e2d667c372a2b2bd1c75a25493aa5d25a6d150e1816ad8fbf6b24874#4": { + "address": "001b71a43b8dbb51b27541c2c8039fbe4b1cdb14e3dcabf5542320dad60dd95be0e31d20781507071c4fd651012a7655ec39f4adf3e3517791", + "assets": { + "23d3bb54df2d71cc2413a13cdea47c57db55f6ba46094745c65d3d37.4d61696e7461696e6572": 1, + "naked": 1172320 + }, + "datum": null, + "ref": "cc79d8e1e2d667c372a2b2bd1c75a25493aa5d25a6d150e1816ad8fbf6b24874#4", + "script": null + }, + "d1b8f48fae0e7a7950dd7ee1e3e43462969cbc6d986d14ebe29d0fefc7df8c03#0": { + "address": "001b71a43b8dbb51b27541c2c8039fbe4b1cdb14e3dcabf5542320dad60dd95be0e31d20781507071c4fd651012a7655ec39f4adf3e3517791", + "assets": { + "naked": 12000000 + }, + "datum": null, + "ref": "d1b8f48fae0e7a7950dd7ee1e3e43462969cbc6d986d14ebe29d0fefc7df8c03#0", + "script": null + }, + "d9710af05ba815c42b9b0809177299f5b08fd42b7fc14a0564300380d018ea1f#0": { + "address": "001b71a43b8dbb51b27541c2c8039fbe4b1cdb14e3dcabf5542320dad60dd95be0e31d20781507071c4fd651012a7655ec39f4adf3e3517791", + "assets": { + "naked": 2000000 + }, + "datum": null, + "ref": "d9710af05ba815c42b9b0809177299f5b08fd42b7fc14a0564300380d018ea1f#0", + "script": null + }, + "ef05e3efe31e8074c8b08d0130975b854cc07e8bf380c7cdcb89d01c16195992#0": { + "address": "001b71a43b8dbb51b27541c2c8039fbe4b1cdb14e3dcabf5542320dad60dd95be0e31d20781507071c4fd651012a7655ec39f4adf3e3517791", + "assets": { + "1417f7aecaa0fc6189c5725a7a42a8c6b1ce7bea4dee3f16db719a54.446576656c6f706572": 1, + "naked": 1159390 + }, + "datum": null, + "ref": "ef05e3efe31e8074c8b08d0130975b854cc07e8bf380c7cdcb89d01c16195992#0", + "script": null + } + }, + "input_queries": [ + { + "name": "source", + "query": { + "address": "001b71a43b8dbb51b27541c2c8039fbe4b1cdb14e3dcabf5542320dad60dd95be0e31d20781507071c4fd651012a7655ec39f4adf3e3517791", + "collateral": false, + "min_amount": { + "naked": 100363917 + }, + "refs": [], + "support_many": false + }, + "selection": [ + { + "address": "001b71a43b8dbb51b27541c2c8039fbe4b1cdb14e3dcabf5542320dad60dd95be0e31d20781507071c4fd651012a7655ec39f4adf3e3517791", + "assets": { + "naked": 7324286764 + }, + "datum": null, + "ref": "4babd06c8ceda932ce1403b090aeb4ab4809c0d3bc6d717fe4bba6cfdae90b5c#1", + "script": null + } + ] + } + ], + "last_eval": { + "ex_units": 0, + "fee": 363917, + "hash": "3d3b5f256fbc03da3917923b487919c78f08a8e870aa5819e7a14ada763a6b2e", + "payload": "84a400d90102818258204babd06c8ceda932ce1403b090aeb4ab4809c0d3bc6d717fe4bba6cfdae90b5c010182a2005839001b71a43b8dbb51b27541c2c8039fbe4b1cdb14e3dcabf5542320dad60dd95be0e31d20781507071c4fd651012a7655ec39f4adf3e3517791011a05f5e100a2005839001b71a43b8dbb51b27541c2c8039fbe4b1cdb14e3dcabf5542320dad60dd95be0e31d20781507071c4fd651012a7655ec39f4adf3e3517791011b00000001ae94509f021a00058d8d0f00a0f5f6" + }, + "log": [ + { + "event": { + "args_applied": { + "content": "ab6466656573a1694576616c506172616d6a457870656374466565736a7265666572656e6365738066696e7075747381a3646e616d6566736f75726365657574786f73a1694576616c506172616da16b457870656374496e7075748266736f75726365a56761646472657373a1694576616c506172616da163536574a16741646472657373983900181b187118a4183b188d18bb185118b21875184118c218c803189f18be184b181c18db1418e318dc18ab18f518541823182018da18d60d18d9185b18e018e3181d18201878150707181c184f18d6185101182a1876185518ec183918f418ad18f318e31851187718916a6d696e5f616d6f756e74a16b4576616c4275696c74496ea16341646482a16641737365747381a366706f6c696379644e6f6e656a61737365745f6e616d65644e6f6e6566616d6f756e74a1694576616c506172616da163536574a1664e756d6265721a05f5e100a1694576616c506172616d6a4578706563744665657363726566644e6f6e65646d616e79f46a636f6c6c61746572616cf46872656465656d6572644e6f6e65676f75747075747382a46761646472657373a1694576616c506172616da163536574a16741646472657373983900181b187118a4183b188d18bb185118b21875184118c218c803189f18be184b181c18db1418e318dc18ab18f518541823182018da18d60d18d9185b18e018e3181d18201878150707181c184f18d6185101182a1876185518ec183918f418ad18f318e318511877189165646174756d644e6f6e6566616d6f756e74a16641737365747381a366706f6c696379644e6f6e656a61737365745f6e616d65644e6f6e6566616d6f756e74a1694576616c506172616da163536574a1664e756d6265721a05f5e100686f7074696f6e616cf4a46761646472657373a1694576616c506172616da163536574a16741646472657373983900181b187118a4183b188d18bb185118b21875184118c218c803189f18be184b181c18db1418e318dc18ab18f518541823182018da18d60d18d9185b18e018e3181d18201878150707181c184f18d6185101182a1876185518ec183918f418ad18f318e318511877189165646174756d644e6f6e6566616d6f756e74a16b4576616c4275696c74496ea16353756282a16b4576616c4275696c74496ea16353756282a16a4576616c436f65726365a16a496e746f417373657473a1694576616c506172616da16b457870656374496e7075748266736f75726365a56761646472657373a1694576616c506172616da163536574a16741646472657373983900181b187118a4183b188d18bb185118b21875184118c218c803189f18be184b181c18db1418e318dc18ab18f518541823182018da18d60d18d9185b18e018e3181d18201878150707181c184f18d6185101182a1876185518ec183918f418ad18f318e31851187718916a6d696e5f616d6f756e74a16b4576616c4275696c74496ea16341646482a16641737365747381a366706f6c696379644e6f6e656a61737365745f6e616d65644e6f6e6566616d6f756e74a1694576616c506172616da163536574a1664e756d6265721a05f5e100a1694576616c506172616d6a4578706563744665657363726566644e6f6e65646d616e79f46a636f6c6c61746572616cf4a16641737365747381a366706f6c696379644e6f6e656a61737365745f6e616d65644e6f6e6566616d6f756e74a1694576616c506172616da163536574a1664e756d6265721a05f5e100a1694576616c506172616d6a45787065637446656573686f7074696f6e616cf46876616c6964697479f6656d696e747380656275726e7380656164686f63806a636f6c6c61746572616c80677369676e657273f6686d6574616461746180", + "encoding": "hex", + "version": "v1beta0" + } + }, + "round": 0 + }, + { + "event": { + "fees_applied": { + "content": "ab6466656573a1694576616c506172616da163536574a16641737365747381a366706f6c696379644e6f6e656a61737365745f6e616d65644e6f6e6566616d6f756e74a1664e756d626572006a7265666572656e6365738066696e7075747381a3646e616d6566736f75726365657574786f73a1694576616c506172616da16b457870656374496e7075748266736f75726365a56761646472657373a1694576616c506172616da163536574a16741646472657373983900181b187118a4183b188d18bb185118b21875184118c218c803189f18be184b181c18db1418e318dc18ab18f518541823182018da18d60d18d9185b18e018e3181d18201878150707181c184f18d6185101182a1876185518ec183918f418ad18f318e31851187718916a6d696e5f616d6f756e74a16b4576616c4275696c74496ea16341646482a16641737365747381a366706f6c696379644e6f6e656a61737365745f6e616d65644e6f6e6566616d6f756e74a1694576616c506172616da163536574a1664e756d6265721a05f5e100a1694576616c506172616da163536574a16641737365747381a366706f6c696379644e6f6e656a61737365745f6e616d65644e6f6e6566616d6f756e74a1664e756d6265720063726566644e6f6e65646d616e79f46a636f6c6c61746572616cf46872656465656d6572644e6f6e65676f75747075747382a46761646472657373a1694576616c506172616da163536574a16741646472657373983900181b187118a4183b188d18bb185118b21875184118c218c803189f18be184b181c18db1418e318dc18ab18f518541823182018da18d60d18d9185b18e018e3181d18201878150707181c184f18d6185101182a1876185518ec183918f418ad18f318e318511877189165646174756d644e6f6e6566616d6f756e74a16641737365747381a366706f6c696379644e6f6e656a61737365745f6e616d65644e6f6e6566616d6f756e74a1694576616c506172616da163536574a1664e756d6265721a05f5e100686f7074696f6e616cf4a46761646472657373a1694576616c506172616da163536574a16741646472657373983900181b187118a4183b188d18bb185118b21875184118c218c803189f18be184b181c18db1418e318dc18ab18f518541823182018da18d60d18d9185b18e018e3181d18201878150707181c184f18d6185101182a1876185518ec183918f418ad18f318e318511877189165646174756d644e6f6e6566616d6f756e74a16b4576616c4275696c74496ea16353756282a16b4576616c4275696c74496ea16353756282a16a4576616c436f65726365a16a496e746f417373657473a1694576616c506172616da16b457870656374496e7075748266736f75726365a56761646472657373a1694576616c506172616da163536574a16741646472657373983900181b187118a4183b188d18bb185118b21875184118c218c803189f18be184b181c18db1418e318dc18ab18f518541823182018da18d60d18d9185b18e018e3181d18201878150707181c184f18d6185101182a1876185518ec183918f418ad18f318e31851187718916a6d696e5f616d6f756e74a16b4576616c4275696c74496ea16341646482a16641737365747381a366706f6c696379644e6f6e656a61737365745f6e616d65644e6f6e6566616d6f756e74a1694576616c506172616da163536574a1664e756d6265721a05f5e100a1694576616c506172616da163536574a16641737365747381a366706f6c696379644e6f6e656a61737365745f6e616d65644e6f6e6566616d6f756e74a1664e756d6265720063726566644e6f6e65646d616e79f46a636f6c6c61746572616cf4a16641737365747381a366706f6c696379644e6f6e656a61737365745f6e616d65644e6f6e6566616d6f756e74a1694576616c506172616da163536574a1664e756d6265721a05f5e100a1694576616c506172616da163536574a16641737365747381a366706f6c696379644e6f6e656a61737365745f6e616d65644e6f6e6566616d6f756e74a1664e756d62657200686f7074696f6e616cf46876616c6964697479f6656d696e747380656275726e7380656164686f63806a636f6c6c61746572616c80677369676e657273f6686d6574616461746180", + "encoding": "hex", + "version": "v1beta0" + } + }, + "round": 0 + }, + { + "event": { + "compiler_applied": { + "content": "ab6466656573a1694576616c506172616da163536574a16641737365747381a366706f6c696379644e6f6e656a61737365745f6e616d65644e6f6e6566616d6f756e74a1664e756d626572006a7265666572656e6365738066696e7075747381a3646e616d6566736f75726365657574786f73a1694576616c506172616da16b457870656374496e7075748266736f75726365a56761646472657373a1694576616c506172616da163536574a16741646472657373983900181b187118a4183b188d18bb185118b21875184118c218c803189f18be184b181c18db1418e318dc18ab18f518541823182018da18d60d18d9185b18e018e3181d18201878150707181c184f18d6185101182a1876185518ec183918f418ad18f318e31851187718916a6d696e5f616d6f756e74a16b4576616c4275696c74496ea16341646482a16641737365747381a366706f6c696379644e6f6e656a61737365745f6e616d65644e6f6e6566616d6f756e74a1694576616c506172616da163536574a1664e756d6265721a05f5e100a1694576616c506172616da163536574a16641737365747381a366706f6c696379644e6f6e656a61737365745f6e616d65644e6f6e6566616d6f756e74a1664e756d6265720063726566644e6f6e65646d616e79f46a636f6c6c61746572616cf46872656465656d6572644e6f6e65676f75747075747382a46761646472657373a1694576616c506172616da163536574a16741646472657373983900181b187118a4183b188d18bb185118b21875184118c218c803189f18be184b181c18db1418e318dc18ab18f518541823182018da18d60d18d9185b18e018e3181d18201878150707181c184f18d6185101182a1876185518ec183918f418ad18f318e318511877189165646174756d644e6f6e6566616d6f756e74a16641737365747381a366706f6c696379644e6f6e656a61737365745f6e616d65644e6f6e6566616d6f756e74a1694576616c506172616da163536574a1664e756d6265721a05f5e100686f7074696f6e616cf4a46761646472657373a1694576616c506172616da163536574a16741646472657373983900181b187118a4183b188d18bb185118b21875184118c218c803189f18be184b181c18db1418e318dc18ab18f518541823182018da18d60d18d9185b18e018e3181d18201878150707181c184f18d6185101182a1876185518ec183918f418ad18f318e318511877189165646174756d644e6f6e6566616d6f756e74a16b4576616c4275696c74496ea16353756282a16b4576616c4275696c74496ea16353756282a16a4576616c436f65726365a16a496e746f417373657473a1694576616c506172616da16b457870656374496e7075748266736f75726365a56761646472657373a1694576616c506172616da163536574a16741646472657373983900181b187118a4183b188d18bb185118b21875184118c218c803189f18be184b181c18db1418e318dc18ab18f518541823182018da18d60d18d9185b18e018e3181d18201878150707181c184f18d6185101182a1876185518ec183918f418ad18f318e31851187718916a6d696e5f616d6f756e74a16b4576616c4275696c74496ea16341646482a16641737365747381a366706f6c696379644e6f6e656a61737365745f6e616d65644e6f6e6566616d6f756e74a1694576616c506172616da163536574a1664e756d6265721a05f5e100a1694576616c506172616da163536574a16641737365747381a366706f6c696379644e6f6e656a61737365745f6e616d65644e6f6e6566616d6f756e74a1664e756d6265720063726566644e6f6e65646d616e79f46a636f6c6c61746572616cf4a16641737365747381a366706f6c696379644e6f6e656a61737365745f6e616d65644e6f6e6566616d6f756e74a1694576616c506172616da163536574a1664e756d6265721a05f5e100a1694576616c506172616da163536574a16641737365747381a366706f6c696379644e6f6e656a61737365745f6e616d65644e6f6e6566616d6f756e74a1664e756d62657200686f7074696f6e616cf46876616c6964697479f6656d696e747380656275726e7380656164686f63806a636f6c6c61746572616c80677369676e657273f6686d6574616461746180", + "encoding": "hex", + "version": "v1beta0" + } + }, + "round": 0 + }, + { + "event": { + "reduced": { + "content": "ab6466656573a16641737365747381a366706f6c696379644e6f6e656a61737365745f6e616d65644e6f6e6566616d6f756e74a1664e756d626572006a7265666572656e6365738066696e7075747381a3646e616d6566736f75726365657574786f73a1694576616c506172616da16b457870656374496e7075748266736f75726365a56761646472657373a16741646472657373983900181b187118a4183b188d18bb185118b21875184118c218c803189f18be184b181c18db1418e318dc18ab18f518541823182018da18d60d18d9185b18e018e3181d18201878150707181c184f18d6185101182a1876185518ec183918f418ad18f318e31851187718916a6d696e5f616d6f756e74a16641737365747381a366706f6c696379644e6f6e656a61737365745f6e616d65644e6f6e6566616d6f756e74a1664e756d6265721a05f5e10063726566644e6f6e65646d616e79f46a636f6c6c61746572616cf46872656465656d6572644e6f6e65676f75747075747382a46761646472657373a16741646472657373983900181b187118a4183b188d18bb185118b21875184118c218c803189f18be184b181c18db1418e318dc18ab18f518541823182018da18d60d18d9185b18e018e3181d18201878150707181c184f18d6185101182a1876185518ec183918f418ad18f318e318511877189165646174756d644e6f6e6566616d6f756e74a16641737365747381a366706f6c696379644e6f6e656a61737365745f6e616d65644e6f6e6566616d6f756e74a1664e756d6265721a05f5e100686f7074696f6e616cf4a46761646472657373a16741646472657373983900181b187118a4183b188d18bb185118b21875184118c218c803189f18be184b181c18db1418e318dc18ab18f518541823182018da18d60d18d9185b18e018e3181d18201878150707181c184f18d6185101182a1876185518ec183918f418ad18f318e318511877189165646174756d644e6f6e6566616d6f756e74a16b4576616c4275696c74496ea16353756282a16b4576616c4275696c74496ea16353756282a16a4576616c436f65726365a16a496e746f417373657473a1694576616c506172616da16b457870656374496e7075748266736f75726365a56761646472657373a16741646472657373983900181b187118a4183b188d18bb185118b21875184118c218c803189f18be184b181c18db1418e318dc18ab18f518541823182018da18d60d18d9185b18e018e3181d18201878150707181c184f18d6185101182a1876185518ec183918f418ad18f318e31851187718916a6d696e5f616d6f756e74a16641737365747381a366706f6c696379644e6f6e656a61737365745f6e616d65644e6f6e6566616d6f756e74a1664e756d6265721a05f5e10063726566644e6f6e65646d616e79f46a636f6c6c61746572616cf4a16641737365747381a366706f6c696379644e6f6e656a61737365745f6e616d65644e6f6e6566616d6f756e74a1664e756d6265721a05f5e100a16641737365747381a366706f6c696379644e6f6e656a61737365745f6e616d65644e6f6e6566616d6f756e74a1664e756d62657200686f7074696f6e616cf46876616c6964697479f6656d696e747380656275726e7380656164686f63806a636f6c6c61746572616c80677369676e657273f6686d6574616461746180", + "encoding": "hex", + "version": "v1beta0" + } + }, + "round": 0 + }, + { + "event": { + "inputs_resolved": { + "content": "ab6466656573a16641737365747381a366706f6c696379644e6f6e656a61737365745f6e616d65644e6f6e6566616d6f756e74a1664e756d626572006a7265666572656e6365738066696e7075747381a3646e616d6566736f75726365657574786f73a1694576616c506172616da163536574a1675574786f53657481a563726566a264747869649820184b18ab18d0186c188c18ed18a9183218ce140318b0189018ae18b418ab18480918c018d318bc186d1871187f18e418bb18a618cf18da18e90b185c65696e646578016761646472657373983900181b187118a4183b188d18bb185118b21875184118c218c803189f18be184b181c18db1418e318dc18ab18f518541823182018da18d60d18d9185b18e018e3181d18201878150707181c184f18d6185101182a1876185518ec183918f418ad18f318e318511877189166617373657473a1654e616b65641b00000001b48fbf2c65646174756df666736372697074f66872656465656d6572644e6f6e65676f75747075747382a46761646472657373a16741646472657373983900181b187118a4183b188d18bb185118b21875184118c218c803189f18be184b181c18db1418e318dc18ab18f518541823182018da18d60d18d9185b18e018e3181d18201878150707181c184f18d6185101182a1876185518ec183918f418ad18f318e318511877189165646174756d644e6f6e6566616d6f756e74a16641737365747381a366706f6c696379644e6f6e656a61737365745f6e616d65644e6f6e6566616d6f756e74a1664e756d6265721a05f5e100686f7074696f6e616cf4a46761646472657373a16741646472657373983900181b187118a4183b188d18bb185118b21875184118c218c803189f18be184b181c18db1418e318dc18ab18f518541823182018da18d60d18d9185b18e018e3181d18201878150707181c184f18d6185101182a1876185518ec183918f418ad18f318e318511877189165646174756d644e6f6e6566616d6f756e74a16b4576616c4275696c74496ea16353756282a16b4576616c4275696c74496ea16353756282a16a4576616c436f65726365a16a496e746f417373657473a1694576616c506172616da163536574a1675574786f53657481a563726566a264747869649820184b18ab18d0186c188c18ed18a9183218ce140318b0189018ae18b418ab18480918c018d318bc186d1871187f18e418bb18a618cf18da18e90b185c65696e646578016761646472657373983900181b187118a4183b188d18bb185118b21875184118c218c803189f18be184b181c18db1418e318dc18ab18f518541823182018da18d60d18d9185b18e018e3181d18201878150707181c184f18d6185101182a1876185518ec183918f418ad18f318e318511877189166617373657473a1654e616b65641b00000001b48fbf2c65646174756df666736372697074f6a16641737365747381a366706f6c696379644e6f6e656a61737365745f6e616d65644e6f6e6566616d6f756e74a1664e756d6265721a05f5e100a16641737365747381a366706f6c696379644e6f6e656a61737365745f6e616d65644e6f6e6566616d6f756e74a1664e756d62657200686f7074696f6e616cf46876616c6964697479f6656d696e747380656275726e7380656164686f63806a636f6c6c61746572616c80677369676e657273f6686d6574616461746180", + "encoding": "hex", + "version": "v1beta0" + } + }, + "round": 0 + }, + { + "event": { + "final_reduced": { + "content": "ab6466656573a16641737365747381a366706f6c696379644e6f6e656a61737365745f6e616d65644e6f6e6566616d6f756e74a1664e756d626572006a7265666572656e6365738066696e7075747381a3646e616d6566736f75726365657574786f73a1675574786f53657481a563726566a264747869649820184b18ab18d0186c188c18ed18a9183218ce140318b0189018ae18b418ab18480918c018d318bc186d1871187f18e418bb18a618cf18da18e90b185c65696e646578016761646472657373983900181b187118a4183b188d18bb185118b21875184118c218c803189f18be184b181c18db1418e318dc18ab18f518541823182018da18d60d18d9185b18e018e3181d18201878150707181c184f18d6185101182a1876185518ec183918f418ad18f318e318511877189166617373657473a1654e616b65641b00000001b48fbf2c65646174756df666736372697074f66872656465656d6572644e6f6e65676f75747075747382a46761646472657373a16741646472657373983900181b187118a4183b188d18bb185118b21875184118c218c803189f18be184b181c18db1418e318dc18ab18f518541823182018da18d60d18d9185b18e018e3181d18201878150707181c184f18d6185101182a1876185518ec183918f418ad18f318e318511877189165646174756d644e6f6e6566616d6f756e74a16641737365747381a366706f6c696379644e6f6e656a61737365745f6e616d65644e6f6e6566616d6f756e74a1664e756d6265721a05f5e100686f7074696f6e616cf4a46761646472657373a16741646472657373983900181b187118a4183b188d18bb185118b21875184118c218c803189f18be184b181c18db1418e318dc18ab18f518541823182018da18d60d18d9185b18e018e3181d18201878150707181c184f18d6185101182a1876185518ec183918f418ad18f318e318511877189165646174756d644e6f6e6566616d6f756e74a16641737365747381a366706f6c696379644e6f6e656a61737365745f6e616d65644e6f6e6566616d6f756e74a1664e756d6265721b00000001ae99de2c686f7074696f6e616cf46876616c6964697479f6656d696e747380656275726e7380656164686f63806a636f6c6c61746572616c80677369676e657273f6686d6574616461746180", + "encoding": "hex", + "version": "v1beta0" + } + }, + "round": 0 + }, + { + "event": { + "compiled": { + "ex_units": 0, + "fee": 363741, + "hash": "1a5959e4d13ae6c88c114cb4ce09a5825d1e907b9a5f8a6de6b16220ed23d8bc", + "payload": "84a400d90102818258204babd06c8ceda932ce1403b090aeb4ab4809c0d3bc6d717fe4bba6cfdae90b5c010182a2005839001b71a43b8dbb51b27541c2c8039fbe4b1cdb14e3dcabf5542320dad60dd95be0e31d20781507071c4fd651012a7655ec39f4adf3e3517791011a05f5e100a2005839001b71a43b8dbb51b27541c2c8039fbe4b1cdb14e3dcabf5542320dad60dd95be0e31d20781507071c4fd651012a7655ec39f4adf3e3517791011b00000001ae99de2c02000f00a0f5f6" + } + }, + "round": 0 + }, + { + "event": { + "fees_applied": { + "content": "ab6466656573a1694576616c506172616da163536574a16641737365747381a366706f6c696379644e6f6e656a61737365745f6e616d65644e6f6e6566616d6f756e74a1664e756d6265721a00058cdd6a7265666572656e6365738066696e7075747381a3646e616d6566736f75726365657574786f73a1694576616c506172616da16b457870656374496e7075748266736f75726365a56761646472657373a1694576616c506172616da163536574a16741646472657373983900181b187118a4183b188d18bb185118b21875184118c218c803189f18be184b181c18db1418e318dc18ab18f518541823182018da18d60d18d9185b18e018e3181d18201878150707181c184f18d6185101182a1876185518ec183918f418ad18f318e31851187718916a6d696e5f616d6f756e74a16b4576616c4275696c74496ea16341646482a16641737365747381a366706f6c696379644e6f6e656a61737365745f6e616d65644e6f6e6566616d6f756e74a1694576616c506172616da163536574a1664e756d6265721a05f5e100a1694576616c506172616da163536574a16641737365747381a366706f6c696379644e6f6e656a61737365745f6e616d65644e6f6e6566616d6f756e74a1664e756d6265721a00058cdd63726566644e6f6e65646d616e79f46a636f6c6c61746572616cf46872656465656d6572644e6f6e65676f75747075747382a46761646472657373a1694576616c506172616da163536574a16741646472657373983900181b187118a4183b188d18bb185118b21875184118c218c803189f18be184b181c18db1418e318dc18ab18f518541823182018da18d60d18d9185b18e018e3181d18201878150707181c184f18d6185101182a1876185518ec183918f418ad18f318e318511877189165646174756d644e6f6e6566616d6f756e74a16641737365747381a366706f6c696379644e6f6e656a61737365745f6e616d65644e6f6e6566616d6f756e74a1694576616c506172616da163536574a1664e756d6265721a05f5e100686f7074696f6e616cf4a46761646472657373a1694576616c506172616da163536574a16741646472657373983900181b187118a4183b188d18bb185118b21875184118c218c803189f18be184b181c18db1418e318dc18ab18f518541823182018da18d60d18d9185b18e018e3181d18201878150707181c184f18d6185101182a1876185518ec183918f418ad18f318e318511877189165646174756d644e6f6e6566616d6f756e74a16b4576616c4275696c74496ea16353756282a16b4576616c4275696c74496ea16353756282a16a4576616c436f65726365a16a496e746f417373657473a1694576616c506172616da16b457870656374496e7075748266736f75726365a56761646472657373a1694576616c506172616da163536574a16741646472657373983900181b187118a4183b188d18bb185118b21875184118c218c803189f18be184b181c18db1418e318dc18ab18f518541823182018da18d60d18d9185b18e018e3181d18201878150707181c184f18d6185101182a1876185518ec183918f418ad18f318e31851187718916a6d696e5f616d6f756e74a16b4576616c4275696c74496ea16341646482a16641737365747381a366706f6c696379644e6f6e656a61737365745f6e616d65644e6f6e6566616d6f756e74a1694576616c506172616da163536574a1664e756d6265721a05f5e100a1694576616c506172616da163536574a16641737365747381a366706f6c696379644e6f6e656a61737365745f6e616d65644e6f6e6566616d6f756e74a1664e756d6265721a00058cdd63726566644e6f6e65646d616e79f46a636f6c6c61746572616cf4a16641737365747381a366706f6c696379644e6f6e656a61737365745f6e616d65644e6f6e6566616d6f756e74a1694576616c506172616da163536574a1664e756d6265721a05f5e100a1694576616c506172616da163536574a16641737365747381a366706f6c696379644e6f6e656a61737365745f6e616d65644e6f6e6566616d6f756e74a1664e756d6265721a00058cdd686f7074696f6e616cf46876616c6964697479f6656d696e747380656275726e7380656164686f63806a636f6c6c61746572616c80677369676e657273f6686d6574616461746180", + "encoding": "hex", + "version": "v1beta0" + } + }, + "round": 1 + }, + { + "event": { + "compiler_applied": { + "content": "ab6466656573a1694576616c506172616da163536574a16641737365747381a366706f6c696379644e6f6e656a61737365745f6e616d65644e6f6e6566616d6f756e74a1664e756d6265721a00058cdd6a7265666572656e6365738066696e7075747381a3646e616d6566736f75726365657574786f73a1694576616c506172616da16b457870656374496e7075748266736f75726365a56761646472657373a1694576616c506172616da163536574a16741646472657373983900181b187118a4183b188d18bb185118b21875184118c218c803189f18be184b181c18db1418e318dc18ab18f518541823182018da18d60d18d9185b18e018e3181d18201878150707181c184f18d6185101182a1876185518ec183918f418ad18f318e31851187718916a6d696e5f616d6f756e74a16b4576616c4275696c74496ea16341646482a16641737365747381a366706f6c696379644e6f6e656a61737365745f6e616d65644e6f6e6566616d6f756e74a1694576616c506172616da163536574a1664e756d6265721a05f5e100a1694576616c506172616da163536574a16641737365747381a366706f6c696379644e6f6e656a61737365745f6e616d65644e6f6e6566616d6f756e74a1664e756d6265721a00058cdd63726566644e6f6e65646d616e79f46a636f6c6c61746572616cf46872656465656d6572644e6f6e65676f75747075747382a46761646472657373a1694576616c506172616da163536574a16741646472657373983900181b187118a4183b188d18bb185118b21875184118c218c803189f18be184b181c18db1418e318dc18ab18f518541823182018da18d60d18d9185b18e018e3181d18201878150707181c184f18d6185101182a1876185518ec183918f418ad18f318e318511877189165646174756d644e6f6e6566616d6f756e74a16641737365747381a366706f6c696379644e6f6e656a61737365745f6e616d65644e6f6e6566616d6f756e74a1694576616c506172616da163536574a1664e756d6265721a05f5e100686f7074696f6e616cf4a46761646472657373a1694576616c506172616da163536574a16741646472657373983900181b187118a4183b188d18bb185118b21875184118c218c803189f18be184b181c18db1418e318dc18ab18f518541823182018da18d60d18d9185b18e018e3181d18201878150707181c184f18d6185101182a1876185518ec183918f418ad18f318e318511877189165646174756d644e6f6e6566616d6f756e74a16b4576616c4275696c74496ea16353756282a16b4576616c4275696c74496ea16353756282a16a4576616c436f65726365a16a496e746f417373657473a1694576616c506172616da16b457870656374496e7075748266736f75726365a56761646472657373a1694576616c506172616da163536574a16741646472657373983900181b187118a4183b188d18bb185118b21875184118c218c803189f18be184b181c18db1418e318dc18ab18f518541823182018da18d60d18d9185b18e018e3181d18201878150707181c184f18d6185101182a1876185518ec183918f418ad18f318e31851187718916a6d696e5f616d6f756e74a16b4576616c4275696c74496ea16341646482a16641737365747381a366706f6c696379644e6f6e656a61737365745f6e616d65644e6f6e6566616d6f756e74a1694576616c506172616da163536574a1664e756d6265721a05f5e100a1694576616c506172616da163536574a16641737365747381a366706f6c696379644e6f6e656a61737365745f6e616d65644e6f6e6566616d6f756e74a1664e756d6265721a00058cdd63726566644e6f6e65646d616e79f46a636f6c6c61746572616cf4a16641737365747381a366706f6c696379644e6f6e656a61737365745f6e616d65644e6f6e6566616d6f756e74a1694576616c506172616da163536574a1664e756d6265721a05f5e100a1694576616c506172616da163536574a16641737365747381a366706f6c696379644e6f6e656a61737365745f6e616d65644e6f6e6566616d6f756e74a1664e756d6265721a00058cdd686f7074696f6e616cf46876616c6964697479f6656d696e747380656275726e7380656164686f63806a636f6c6c61746572616c80677369676e657273f6686d6574616461746180", + "encoding": "hex", + "version": "v1beta0" + } + }, + "round": 1 + }, + { + "event": { + "reduced": { + "content": "ab6466656573a16641737365747381a366706f6c696379644e6f6e656a61737365745f6e616d65644e6f6e6566616d6f756e74a1664e756d6265721a00058cdd6a7265666572656e6365738066696e7075747381a3646e616d6566736f75726365657574786f73a1694576616c506172616da16b457870656374496e7075748266736f75726365a56761646472657373a16741646472657373983900181b187118a4183b188d18bb185118b21875184118c218c803189f18be184b181c18db1418e318dc18ab18f518541823182018da18d60d18d9185b18e018e3181d18201878150707181c184f18d6185101182a1876185518ec183918f418ad18f318e31851187718916a6d696e5f616d6f756e74a16641737365747381a366706f6c696379644e6f6e656a61737365745f6e616d65644e6f6e6566616d6f756e74a1664e756d6265721a05fb6ddd63726566644e6f6e65646d616e79f46a636f6c6c61746572616cf46872656465656d6572644e6f6e65676f75747075747382a46761646472657373a16741646472657373983900181b187118a4183b188d18bb185118b21875184118c218c803189f18be184b181c18db1418e318dc18ab18f518541823182018da18d60d18d9185b18e018e3181d18201878150707181c184f18d6185101182a1876185518ec183918f418ad18f318e318511877189165646174756d644e6f6e6566616d6f756e74a16641737365747381a366706f6c696379644e6f6e656a61737365745f6e616d65644e6f6e6566616d6f756e74a1664e756d6265721a05f5e100686f7074696f6e616cf4a46761646472657373a16741646472657373983900181b187118a4183b188d18bb185118b21875184118c218c803189f18be184b181c18db1418e318dc18ab18f518541823182018da18d60d18d9185b18e018e3181d18201878150707181c184f18d6185101182a1876185518ec183918f418ad18f318e318511877189165646174756d644e6f6e6566616d6f756e74a16b4576616c4275696c74496ea16353756282a16b4576616c4275696c74496ea16353756282a16a4576616c436f65726365a16a496e746f417373657473a1694576616c506172616da16b457870656374496e7075748266736f75726365a56761646472657373a16741646472657373983900181b187118a4183b188d18bb185118b21875184118c218c803189f18be184b181c18db1418e318dc18ab18f518541823182018da18d60d18d9185b18e018e3181d18201878150707181c184f18d6185101182a1876185518ec183918f418ad18f318e31851187718916a6d696e5f616d6f756e74a16641737365747381a366706f6c696379644e6f6e656a61737365745f6e616d65644e6f6e6566616d6f756e74a1664e756d6265721a05fb6ddd63726566644e6f6e65646d616e79f46a636f6c6c61746572616cf4a16641737365747381a366706f6c696379644e6f6e656a61737365745f6e616d65644e6f6e6566616d6f756e74a1664e756d6265721a05f5e100a16641737365747381a366706f6c696379644e6f6e656a61737365745f6e616d65644e6f6e6566616d6f756e74a1664e756d6265721a00058cdd686f7074696f6e616cf46876616c6964697479f6656d696e747380656275726e7380656164686f63806a636f6c6c61746572616c80677369676e657273f6686d6574616461746180", + "encoding": "hex", + "version": "v1beta0" + } + }, + "round": 1 + }, + { + "event": { + "inputs_resolved": { + "content": "ab6466656573a16641737365747381a366706f6c696379644e6f6e656a61737365745f6e616d65644e6f6e6566616d6f756e74a1664e756d6265721a00058cdd6a7265666572656e6365738066696e7075747381a3646e616d6566736f75726365657574786f73a1694576616c506172616da163536574a1675574786f53657481a563726566a264747869649820184b18ab18d0186c188c18ed18a9183218ce140318b0189018ae18b418ab18480918c018d318bc186d1871187f18e418bb18a618cf18da18e90b185c65696e646578016761646472657373983900181b187118a4183b188d18bb185118b21875184118c218c803189f18be184b181c18db1418e318dc18ab18f518541823182018da18d60d18d9185b18e018e3181d18201878150707181c184f18d6185101182a1876185518ec183918f418ad18f318e318511877189166617373657473a1654e616b65641b00000001b48fbf2c65646174756df666736372697074f66872656465656d6572644e6f6e65676f75747075747382a46761646472657373a16741646472657373983900181b187118a4183b188d18bb185118b21875184118c218c803189f18be184b181c18db1418e318dc18ab18f518541823182018da18d60d18d9185b18e018e3181d18201878150707181c184f18d6185101182a1876185518ec183918f418ad18f318e318511877189165646174756d644e6f6e6566616d6f756e74a16641737365747381a366706f6c696379644e6f6e656a61737365745f6e616d65644e6f6e6566616d6f756e74a1664e756d6265721a05f5e100686f7074696f6e616cf4a46761646472657373a16741646472657373983900181b187118a4183b188d18bb185118b21875184118c218c803189f18be184b181c18db1418e318dc18ab18f518541823182018da18d60d18d9185b18e018e3181d18201878150707181c184f18d6185101182a1876185518ec183918f418ad18f318e318511877189165646174756d644e6f6e6566616d6f756e74a16b4576616c4275696c74496ea16353756282a16b4576616c4275696c74496ea16353756282a16a4576616c436f65726365a16a496e746f417373657473a1694576616c506172616da163536574a1675574786f53657481a563726566a264747869649820184b18ab18d0186c188c18ed18a9183218ce140318b0189018ae18b418ab18480918c018d318bc186d1871187f18e418bb18a618cf18da18e90b185c65696e646578016761646472657373983900181b187118a4183b188d18bb185118b21875184118c218c803189f18be184b181c18db1418e318dc18ab18f518541823182018da18d60d18d9185b18e018e3181d18201878150707181c184f18d6185101182a1876185518ec183918f418ad18f318e318511877189166617373657473a1654e616b65641b00000001b48fbf2c65646174756df666736372697074f6a16641737365747381a366706f6c696379644e6f6e656a61737365745f6e616d65644e6f6e6566616d6f756e74a1664e756d6265721a05f5e100a16641737365747381a366706f6c696379644e6f6e656a61737365745f6e616d65644e6f6e6566616d6f756e74a1664e756d6265721a00058cdd686f7074696f6e616cf46876616c6964697479f6656d696e747380656275726e7380656164686f63806a636f6c6c61746572616c80677369676e657273f6686d6574616461746180", + "encoding": "hex", + "version": "v1beta0" + } + }, + "round": 1 + }, + { + "event": { + "final_reduced": { + "content": "ab6466656573a16641737365747381a366706f6c696379644e6f6e656a61737365745f6e616d65644e6f6e6566616d6f756e74a1664e756d6265721a00058cdd6a7265666572656e6365738066696e7075747381a3646e616d6566736f75726365657574786f73a1675574786f53657481a563726566a264747869649820184b18ab18d0186c188c18ed18a9183218ce140318b0189018ae18b418ab18480918c018d318bc186d1871187f18e418bb18a618cf18da18e90b185c65696e646578016761646472657373983900181b187118a4183b188d18bb185118b21875184118c218c803189f18be184b181c18db1418e318dc18ab18f518541823182018da18d60d18d9185b18e018e3181d18201878150707181c184f18d6185101182a1876185518ec183918f418ad18f318e318511877189166617373657473a1654e616b65641b00000001b48fbf2c65646174756df666736372697074f66872656465656d6572644e6f6e65676f75747075747382a46761646472657373a16741646472657373983900181b187118a4183b188d18bb185118b21875184118c218c803189f18be184b181c18db1418e318dc18ab18f518541823182018da18d60d18d9185b18e018e3181d18201878150707181c184f18d6185101182a1876185518ec183918f418ad18f318e318511877189165646174756d644e6f6e6566616d6f756e74a16641737365747381a366706f6c696379644e6f6e656a61737365745f6e616d65644e6f6e6566616d6f756e74a1664e756d6265721a05f5e100686f7074696f6e616cf4a46761646472657373a16741646472657373983900181b187118a4183b188d18bb185118b21875184118c218c803189f18be184b181c18db1418e318dc18ab18f518541823182018da18d60d18d9185b18e018e3181d18201878150707181c184f18d6185101182a1876185518ec183918f418ad18f318e318511877189165646174756d644e6f6e6566616d6f756e74a16641737365747381a366706f6c696379644e6f6e656a61737365745f6e616d65644e6f6e6566616d6f756e74a1664e756d6265721b00000001ae94514f686f7074696f6e616cf46876616c6964697479f6656d696e747380656275726e7380656164686f63806a636f6c6c61746572616c80677369676e657273f6686d6574616461746180", + "encoding": "hex", + "version": "v1beta0" + } + }, + "round": 1 + }, + { + "event": { + "compiled": { + "ex_units": 0, + "fee": 363917, + "hash": "7cb4ddefbee9e13ee3d66ea814f5c4cb3506077e94698ff39ef152de4d970e49", + "payload": "84a400d90102818258204babd06c8ceda932ce1403b090aeb4ab4809c0d3bc6d717fe4bba6cfdae90b5c010182a2005839001b71a43b8dbb51b27541c2c8039fbe4b1cdb14e3dcabf5542320dad60dd95be0e31d20781507071c4fd651012a7655ec39f4adf3e3517791011a05f5e100a2005839001b71a43b8dbb51b27541c2c8039fbe4b1cdb14e3dcabf5542320dad60dd95be0e31d20781507071c4fd651012a7655ec39f4adf3e3517791011b00000001ae94514f021a00058cdd0f00a0f5f6" + } + }, + "round": 1 + }, + { + "event": { + "fees_applied": { + "content": "ab6466656573a1694576616c506172616da163536574a16641737365747381a366706f6c696379644e6f6e656a61737365745f6e616d65644e6f6e6566616d6f756e74a1664e756d6265721a00058d8d6a7265666572656e6365738066696e7075747381a3646e616d6566736f75726365657574786f73a1694576616c506172616da16b457870656374496e7075748266736f75726365a56761646472657373a1694576616c506172616da163536574a16741646472657373983900181b187118a4183b188d18bb185118b21875184118c218c803189f18be184b181c18db1418e318dc18ab18f518541823182018da18d60d18d9185b18e018e3181d18201878150707181c184f18d6185101182a1876185518ec183918f418ad18f318e31851187718916a6d696e5f616d6f756e74a16b4576616c4275696c74496ea16341646482a16641737365747381a366706f6c696379644e6f6e656a61737365745f6e616d65644e6f6e6566616d6f756e74a1694576616c506172616da163536574a1664e756d6265721a05f5e100a1694576616c506172616da163536574a16641737365747381a366706f6c696379644e6f6e656a61737365745f6e616d65644e6f6e6566616d6f756e74a1664e756d6265721a00058d8d63726566644e6f6e65646d616e79f46a636f6c6c61746572616cf46872656465656d6572644e6f6e65676f75747075747382a46761646472657373a1694576616c506172616da163536574a16741646472657373983900181b187118a4183b188d18bb185118b21875184118c218c803189f18be184b181c18db1418e318dc18ab18f518541823182018da18d60d18d9185b18e018e3181d18201878150707181c184f18d6185101182a1876185518ec183918f418ad18f318e318511877189165646174756d644e6f6e6566616d6f756e74a16641737365747381a366706f6c696379644e6f6e656a61737365745f6e616d65644e6f6e6566616d6f756e74a1694576616c506172616da163536574a1664e756d6265721a05f5e100686f7074696f6e616cf4a46761646472657373a1694576616c506172616da163536574a16741646472657373983900181b187118a4183b188d18bb185118b21875184118c218c803189f18be184b181c18db1418e318dc18ab18f518541823182018da18d60d18d9185b18e018e3181d18201878150707181c184f18d6185101182a1876185518ec183918f418ad18f318e318511877189165646174756d644e6f6e6566616d6f756e74a16b4576616c4275696c74496ea16353756282a16b4576616c4275696c74496ea16353756282a16a4576616c436f65726365a16a496e746f417373657473a1694576616c506172616da16b457870656374496e7075748266736f75726365a56761646472657373a1694576616c506172616da163536574a16741646472657373983900181b187118a4183b188d18bb185118b21875184118c218c803189f18be184b181c18db1418e318dc18ab18f518541823182018da18d60d18d9185b18e018e3181d18201878150707181c184f18d6185101182a1876185518ec183918f418ad18f318e31851187718916a6d696e5f616d6f756e74a16b4576616c4275696c74496ea16341646482a16641737365747381a366706f6c696379644e6f6e656a61737365745f6e616d65644e6f6e6566616d6f756e74a1694576616c506172616da163536574a1664e756d6265721a05f5e100a1694576616c506172616da163536574a16641737365747381a366706f6c696379644e6f6e656a61737365745f6e616d65644e6f6e6566616d6f756e74a1664e756d6265721a00058d8d63726566644e6f6e65646d616e79f46a636f6c6c61746572616cf4a16641737365747381a366706f6c696379644e6f6e656a61737365745f6e616d65644e6f6e6566616d6f756e74a1694576616c506172616da163536574a1664e756d6265721a05f5e100a1694576616c506172616da163536574a16641737365747381a366706f6c696379644e6f6e656a61737365745f6e616d65644e6f6e6566616d6f756e74a1664e756d6265721a00058d8d686f7074696f6e616cf46876616c6964697479f6656d696e747380656275726e7380656164686f63806a636f6c6c61746572616c80677369676e657273f6686d6574616461746180", + "encoding": "hex", + "version": "v1beta0" + } + }, + "round": 2 + }, + { + "event": { + "compiler_applied": { + "content": "ab6466656573a1694576616c506172616da163536574a16641737365747381a366706f6c696379644e6f6e656a61737365745f6e616d65644e6f6e6566616d6f756e74a1664e756d6265721a00058d8d6a7265666572656e6365738066696e7075747381a3646e616d6566736f75726365657574786f73a1694576616c506172616da16b457870656374496e7075748266736f75726365a56761646472657373a1694576616c506172616da163536574a16741646472657373983900181b187118a4183b188d18bb185118b21875184118c218c803189f18be184b181c18db1418e318dc18ab18f518541823182018da18d60d18d9185b18e018e3181d18201878150707181c184f18d6185101182a1876185518ec183918f418ad18f318e31851187718916a6d696e5f616d6f756e74a16b4576616c4275696c74496ea16341646482a16641737365747381a366706f6c696379644e6f6e656a61737365745f6e616d65644e6f6e6566616d6f756e74a1694576616c506172616da163536574a1664e756d6265721a05f5e100a1694576616c506172616da163536574a16641737365747381a366706f6c696379644e6f6e656a61737365745f6e616d65644e6f6e6566616d6f756e74a1664e756d6265721a00058d8d63726566644e6f6e65646d616e79f46a636f6c6c61746572616cf46872656465656d6572644e6f6e65676f75747075747382a46761646472657373a1694576616c506172616da163536574a16741646472657373983900181b187118a4183b188d18bb185118b21875184118c218c803189f18be184b181c18db1418e318dc18ab18f518541823182018da18d60d18d9185b18e018e3181d18201878150707181c184f18d6185101182a1876185518ec183918f418ad18f318e318511877189165646174756d644e6f6e6566616d6f756e74a16641737365747381a366706f6c696379644e6f6e656a61737365745f6e616d65644e6f6e6566616d6f756e74a1694576616c506172616da163536574a1664e756d6265721a05f5e100686f7074696f6e616cf4a46761646472657373a1694576616c506172616da163536574a16741646472657373983900181b187118a4183b188d18bb185118b21875184118c218c803189f18be184b181c18db1418e318dc18ab18f518541823182018da18d60d18d9185b18e018e3181d18201878150707181c184f18d6185101182a1876185518ec183918f418ad18f318e318511877189165646174756d644e6f6e6566616d6f756e74a16b4576616c4275696c74496ea16353756282a16b4576616c4275696c74496ea16353756282a16a4576616c436f65726365a16a496e746f417373657473a1694576616c506172616da16b457870656374496e7075748266736f75726365a56761646472657373a1694576616c506172616da163536574a16741646472657373983900181b187118a4183b188d18bb185118b21875184118c218c803189f18be184b181c18db1418e318dc18ab18f518541823182018da18d60d18d9185b18e018e3181d18201878150707181c184f18d6185101182a1876185518ec183918f418ad18f318e31851187718916a6d696e5f616d6f756e74a16b4576616c4275696c74496ea16341646482a16641737365747381a366706f6c696379644e6f6e656a61737365745f6e616d65644e6f6e6566616d6f756e74a1694576616c506172616da163536574a1664e756d6265721a05f5e100a1694576616c506172616da163536574a16641737365747381a366706f6c696379644e6f6e656a61737365745f6e616d65644e6f6e6566616d6f756e74a1664e756d6265721a00058d8d63726566644e6f6e65646d616e79f46a636f6c6c61746572616cf4a16641737365747381a366706f6c696379644e6f6e656a61737365745f6e616d65644e6f6e6566616d6f756e74a1694576616c506172616da163536574a1664e756d6265721a05f5e100a1694576616c506172616da163536574a16641737365747381a366706f6c696379644e6f6e656a61737365745f6e616d65644e6f6e6566616d6f756e74a1664e756d6265721a00058d8d686f7074696f6e616cf46876616c6964697479f6656d696e747380656275726e7380656164686f63806a636f6c6c61746572616c80677369676e657273f6686d6574616461746180", + "encoding": "hex", + "version": "v1beta0" + } + }, + "round": 2 + }, + { + "event": { + "reduced": { + "content": "ab6466656573a16641737365747381a366706f6c696379644e6f6e656a61737365745f6e616d65644e6f6e6566616d6f756e74a1664e756d6265721a00058d8d6a7265666572656e6365738066696e7075747381a3646e616d6566736f75726365657574786f73a1694576616c506172616da16b457870656374496e7075748266736f75726365a56761646472657373a16741646472657373983900181b187118a4183b188d18bb185118b21875184118c218c803189f18be184b181c18db1418e318dc18ab18f518541823182018da18d60d18d9185b18e018e3181d18201878150707181c184f18d6185101182a1876185518ec183918f418ad18f318e31851187718916a6d696e5f616d6f756e74a16641737365747381a366706f6c696379644e6f6e656a61737365745f6e616d65644e6f6e6566616d6f756e74a1664e756d6265721a05fb6e8d63726566644e6f6e65646d616e79f46a636f6c6c61746572616cf46872656465656d6572644e6f6e65676f75747075747382a46761646472657373a16741646472657373983900181b187118a4183b188d18bb185118b21875184118c218c803189f18be184b181c18db1418e318dc18ab18f518541823182018da18d60d18d9185b18e018e3181d18201878150707181c184f18d6185101182a1876185518ec183918f418ad18f318e318511877189165646174756d644e6f6e6566616d6f756e74a16641737365747381a366706f6c696379644e6f6e656a61737365745f6e616d65644e6f6e6566616d6f756e74a1664e756d6265721a05f5e100686f7074696f6e616cf4a46761646472657373a16741646472657373983900181b187118a4183b188d18bb185118b21875184118c218c803189f18be184b181c18db1418e318dc18ab18f518541823182018da18d60d18d9185b18e018e3181d18201878150707181c184f18d6185101182a1876185518ec183918f418ad18f318e318511877189165646174756d644e6f6e6566616d6f756e74a16b4576616c4275696c74496ea16353756282a16b4576616c4275696c74496ea16353756282a16a4576616c436f65726365a16a496e746f417373657473a1694576616c506172616da16b457870656374496e7075748266736f75726365a56761646472657373a16741646472657373983900181b187118a4183b188d18bb185118b21875184118c218c803189f18be184b181c18db1418e318dc18ab18f518541823182018da18d60d18d9185b18e018e3181d18201878150707181c184f18d6185101182a1876185518ec183918f418ad18f318e31851187718916a6d696e5f616d6f756e74a16641737365747381a366706f6c696379644e6f6e656a61737365745f6e616d65644e6f6e6566616d6f756e74a1664e756d6265721a05fb6e8d63726566644e6f6e65646d616e79f46a636f6c6c61746572616cf4a16641737365747381a366706f6c696379644e6f6e656a61737365745f6e616d65644e6f6e6566616d6f756e74a1664e756d6265721a05f5e100a16641737365747381a366706f6c696379644e6f6e656a61737365745f6e616d65644e6f6e6566616d6f756e74a1664e756d6265721a00058d8d686f7074696f6e616cf46876616c6964697479f6656d696e747380656275726e7380656164686f63806a636f6c6c61746572616c80677369676e657273f6686d6574616461746180", + "encoding": "hex", + "version": "v1beta0" + } + }, + "round": 2 + }, + { + "event": { + "inputs_resolved": { + "content": "ab6466656573a16641737365747381a366706f6c696379644e6f6e656a61737365745f6e616d65644e6f6e6566616d6f756e74a1664e756d6265721a00058d8d6a7265666572656e6365738066696e7075747381a3646e616d6566736f75726365657574786f73a1694576616c506172616da163536574a1675574786f53657481a563726566a264747869649820184b18ab18d0186c188c18ed18a9183218ce140318b0189018ae18b418ab18480918c018d318bc186d1871187f18e418bb18a618cf18da18e90b185c65696e646578016761646472657373983900181b187118a4183b188d18bb185118b21875184118c218c803189f18be184b181c18db1418e318dc18ab18f518541823182018da18d60d18d9185b18e018e3181d18201878150707181c184f18d6185101182a1876185518ec183918f418ad18f318e318511877189166617373657473a1654e616b65641b00000001b48fbf2c65646174756df666736372697074f66872656465656d6572644e6f6e65676f75747075747382a46761646472657373a16741646472657373983900181b187118a4183b188d18bb185118b21875184118c218c803189f18be184b181c18db1418e318dc18ab18f518541823182018da18d60d18d9185b18e018e3181d18201878150707181c184f18d6185101182a1876185518ec183918f418ad18f318e318511877189165646174756d644e6f6e6566616d6f756e74a16641737365747381a366706f6c696379644e6f6e656a61737365745f6e616d65644e6f6e6566616d6f756e74a1664e756d6265721a05f5e100686f7074696f6e616cf4a46761646472657373a16741646472657373983900181b187118a4183b188d18bb185118b21875184118c218c803189f18be184b181c18db1418e318dc18ab18f518541823182018da18d60d18d9185b18e018e3181d18201878150707181c184f18d6185101182a1876185518ec183918f418ad18f318e318511877189165646174756d644e6f6e6566616d6f756e74a16b4576616c4275696c74496ea16353756282a16b4576616c4275696c74496ea16353756282a16a4576616c436f65726365a16a496e746f417373657473a1694576616c506172616da163536574a1675574786f53657481a563726566a264747869649820184b18ab18d0186c188c18ed18a9183218ce140318b0189018ae18b418ab18480918c018d318bc186d1871187f18e418bb18a618cf18da18e90b185c65696e646578016761646472657373983900181b187118a4183b188d18bb185118b21875184118c218c803189f18be184b181c18db1418e318dc18ab18f518541823182018da18d60d18d9185b18e018e3181d18201878150707181c184f18d6185101182a1876185518ec183918f418ad18f318e318511877189166617373657473a1654e616b65641b00000001b48fbf2c65646174756df666736372697074f6a16641737365747381a366706f6c696379644e6f6e656a61737365745f6e616d65644e6f6e6566616d6f756e74a1664e756d6265721a05f5e100a16641737365747381a366706f6c696379644e6f6e656a61737365745f6e616d65644e6f6e6566616d6f756e74a1664e756d6265721a00058d8d686f7074696f6e616cf46876616c6964697479f6656d696e747380656275726e7380656164686f63806a636f6c6c61746572616c80677369676e657273f6686d6574616461746180", + "encoding": "hex", + "version": "v1beta0" + } + }, + "round": 2 + }, + { + "event": { + "final_reduced": { + "content": "ab6466656573a16641737365747381a366706f6c696379644e6f6e656a61737365745f6e616d65644e6f6e6566616d6f756e74a1664e756d6265721a00058d8d6a7265666572656e6365738066696e7075747381a3646e616d6566736f75726365657574786f73a1675574786f53657481a563726566a264747869649820184b18ab18d0186c188c18ed18a9183218ce140318b0189018ae18b418ab18480918c018d318bc186d1871187f18e418bb18a618cf18da18e90b185c65696e646578016761646472657373983900181b187118a4183b188d18bb185118b21875184118c218c803189f18be184b181c18db1418e318dc18ab18f518541823182018da18d60d18d9185b18e018e3181d18201878150707181c184f18d6185101182a1876185518ec183918f418ad18f318e318511877189166617373657473a1654e616b65641b00000001b48fbf2c65646174756df666736372697074f66872656465656d6572644e6f6e65676f75747075747382a46761646472657373a16741646472657373983900181b187118a4183b188d18bb185118b21875184118c218c803189f18be184b181c18db1418e318dc18ab18f518541823182018da18d60d18d9185b18e018e3181d18201878150707181c184f18d6185101182a1876185518ec183918f418ad18f318e318511877189165646174756d644e6f6e6566616d6f756e74a16641737365747381a366706f6c696379644e6f6e656a61737365745f6e616d65644e6f6e6566616d6f756e74a1664e756d6265721a05f5e100686f7074696f6e616cf4a46761646472657373a16741646472657373983900181b187118a4183b188d18bb185118b21875184118c218c803189f18be184b181c18db1418e318dc18ab18f518541823182018da18d60d18d9185b18e018e3181d18201878150707181c184f18d6185101182a1876185518ec183918f418ad18f318e318511877189165646174756d644e6f6e6566616d6f756e74a16641737365747381a366706f6c696379644e6f6e656a61737365745f6e616d65644e6f6e6566616d6f756e74a1664e756d6265721b00000001ae94509f686f7074696f6e616cf46876616c6964697479f6656d696e747380656275726e7380656164686f63806a636f6c6c61746572616c80677369676e657273f6686d6574616461746180", + "encoding": "hex", + "version": "v1beta0" + } + }, + "round": 2 + }, + { + "event": { + "compiled": { + "ex_units": 0, + "fee": 363917, + "hash": "3d3b5f256fbc03da3917923b487919c78f08a8e870aa5819e7a14ada763a6b2e", + "payload": "84a400d90102818258204babd06c8ceda932ce1403b090aeb4ab4809c0d3bc6d717fe4bba6cfdae90b5c010182a2005839001b71a43b8dbb51b27541c2c8039fbe4b1cdb14e3dcabf5542320dad60dd95be0e31d20781507071c4fd651012a7655ec39f4adf3e3517791011a05f5e100a2005839001b71a43b8dbb51b27541c2c8039fbe4b1cdb14e3dcabf5542320dad60dd95be0e31d20781507071c4fd651012a7655ec39f4adf3e3517791011b00000001ae94509f021a00058d8d0f00a0f5f6" + } + }, + "round": 2 + }, + { + "event": { + "fees_applied": { + "content": "ab6466656573a1694576616c506172616da163536574a16641737365747381a366706f6c696379644e6f6e656a61737365745f6e616d65644e6f6e6566616d6f756e74a1664e756d6265721a00058d8d6a7265666572656e6365738066696e7075747381a3646e616d6566736f75726365657574786f73a1694576616c506172616da16b457870656374496e7075748266736f75726365a56761646472657373a1694576616c506172616da163536574a16741646472657373983900181b187118a4183b188d18bb185118b21875184118c218c803189f18be184b181c18db1418e318dc18ab18f518541823182018da18d60d18d9185b18e018e3181d18201878150707181c184f18d6185101182a1876185518ec183918f418ad18f318e31851187718916a6d696e5f616d6f756e74a16b4576616c4275696c74496ea16341646482a16641737365747381a366706f6c696379644e6f6e656a61737365745f6e616d65644e6f6e6566616d6f756e74a1694576616c506172616da163536574a1664e756d6265721a05f5e100a1694576616c506172616da163536574a16641737365747381a366706f6c696379644e6f6e656a61737365745f6e616d65644e6f6e6566616d6f756e74a1664e756d6265721a00058d8d63726566644e6f6e65646d616e79f46a636f6c6c61746572616cf46872656465656d6572644e6f6e65676f75747075747382a46761646472657373a1694576616c506172616da163536574a16741646472657373983900181b187118a4183b188d18bb185118b21875184118c218c803189f18be184b181c18db1418e318dc18ab18f518541823182018da18d60d18d9185b18e018e3181d18201878150707181c184f18d6185101182a1876185518ec183918f418ad18f318e318511877189165646174756d644e6f6e6566616d6f756e74a16641737365747381a366706f6c696379644e6f6e656a61737365745f6e616d65644e6f6e6566616d6f756e74a1694576616c506172616da163536574a1664e756d6265721a05f5e100686f7074696f6e616cf4a46761646472657373a1694576616c506172616da163536574a16741646472657373983900181b187118a4183b188d18bb185118b21875184118c218c803189f18be184b181c18db1418e318dc18ab18f518541823182018da18d60d18d9185b18e018e3181d18201878150707181c184f18d6185101182a1876185518ec183918f418ad18f318e318511877189165646174756d644e6f6e6566616d6f756e74a16b4576616c4275696c74496ea16353756282a16b4576616c4275696c74496ea16353756282a16a4576616c436f65726365a16a496e746f417373657473a1694576616c506172616da16b457870656374496e7075748266736f75726365a56761646472657373a1694576616c506172616da163536574a16741646472657373983900181b187118a4183b188d18bb185118b21875184118c218c803189f18be184b181c18db1418e318dc18ab18f518541823182018da18d60d18d9185b18e018e3181d18201878150707181c184f18d6185101182a1876185518ec183918f418ad18f318e31851187718916a6d696e5f616d6f756e74a16b4576616c4275696c74496ea16341646482a16641737365747381a366706f6c696379644e6f6e656a61737365745f6e616d65644e6f6e6566616d6f756e74a1694576616c506172616da163536574a1664e756d6265721a05f5e100a1694576616c506172616da163536574a16641737365747381a366706f6c696379644e6f6e656a61737365745f6e616d65644e6f6e6566616d6f756e74a1664e756d6265721a00058d8d63726566644e6f6e65646d616e79f46a636f6c6c61746572616cf4a16641737365747381a366706f6c696379644e6f6e656a61737365745f6e616d65644e6f6e6566616d6f756e74a1694576616c506172616da163536574a1664e756d6265721a05f5e100a1694576616c506172616da163536574a16641737365747381a366706f6c696379644e6f6e656a61737365745f6e616d65644e6f6e6566616d6f756e74a1664e756d6265721a00058d8d686f7074696f6e616cf46876616c6964697479f6656d696e747380656275726e7380656164686f63806a636f6c6c61746572616c80677369676e657273f6686d6574616461746180", + "encoding": "hex", + "version": "v1beta0" + } + }, + "round": 3 + }, + { + "event": { + "compiler_applied": { + "content": "ab6466656573a1694576616c506172616da163536574a16641737365747381a366706f6c696379644e6f6e656a61737365745f6e616d65644e6f6e6566616d6f756e74a1664e756d6265721a00058d8d6a7265666572656e6365738066696e7075747381a3646e616d6566736f75726365657574786f73a1694576616c506172616da16b457870656374496e7075748266736f75726365a56761646472657373a1694576616c506172616da163536574a16741646472657373983900181b187118a4183b188d18bb185118b21875184118c218c803189f18be184b181c18db1418e318dc18ab18f518541823182018da18d60d18d9185b18e018e3181d18201878150707181c184f18d6185101182a1876185518ec183918f418ad18f318e31851187718916a6d696e5f616d6f756e74a16b4576616c4275696c74496ea16341646482a16641737365747381a366706f6c696379644e6f6e656a61737365745f6e616d65644e6f6e6566616d6f756e74a1694576616c506172616da163536574a1664e756d6265721a05f5e100a1694576616c506172616da163536574a16641737365747381a366706f6c696379644e6f6e656a61737365745f6e616d65644e6f6e6566616d6f756e74a1664e756d6265721a00058d8d63726566644e6f6e65646d616e79f46a636f6c6c61746572616cf46872656465656d6572644e6f6e65676f75747075747382a46761646472657373a1694576616c506172616da163536574a16741646472657373983900181b187118a4183b188d18bb185118b21875184118c218c803189f18be184b181c18db1418e318dc18ab18f518541823182018da18d60d18d9185b18e018e3181d18201878150707181c184f18d6185101182a1876185518ec183918f418ad18f318e318511877189165646174756d644e6f6e6566616d6f756e74a16641737365747381a366706f6c696379644e6f6e656a61737365745f6e616d65644e6f6e6566616d6f756e74a1694576616c506172616da163536574a1664e756d6265721a05f5e100686f7074696f6e616cf4a46761646472657373a1694576616c506172616da163536574a16741646472657373983900181b187118a4183b188d18bb185118b21875184118c218c803189f18be184b181c18db1418e318dc18ab18f518541823182018da18d60d18d9185b18e018e3181d18201878150707181c184f18d6185101182a1876185518ec183918f418ad18f318e318511877189165646174756d644e6f6e6566616d6f756e74a16b4576616c4275696c74496ea16353756282a16b4576616c4275696c74496ea16353756282a16a4576616c436f65726365a16a496e746f417373657473a1694576616c506172616da16b457870656374496e7075748266736f75726365a56761646472657373a1694576616c506172616da163536574a16741646472657373983900181b187118a4183b188d18bb185118b21875184118c218c803189f18be184b181c18db1418e318dc18ab18f518541823182018da18d60d18d9185b18e018e3181d18201878150707181c184f18d6185101182a1876185518ec183918f418ad18f318e31851187718916a6d696e5f616d6f756e74a16b4576616c4275696c74496ea16341646482a16641737365747381a366706f6c696379644e6f6e656a61737365745f6e616d65644e6f6e6566616d6f756e74a1694576616c506172616da163536574a1664e756d6265721a05f5e100a1694576616c506172616da163536574a16641737365747381a366706f6c696379644e6f6e656a61737365745f6e616d65644e6f6e6566616d6f756e74a1664e756d6265721a00058d8d63726566644e6f6e65646d616e79f46a636f6c6c61746572616cf4a16641737365747381a366706f6c696379644e6f6e656a61737365745f6e616d65644e6f6e6566616d6f756e74a1694576616c506172616da163536574a1664e756d6265721a05f5e100a1694576616c506172616da163536574a16641737365747381a366706f6c696379644e6f6e656a61737365745f6e616d65644e6f6e6566616d6f756e74a1664e756d6265721a00058d8d686f7074696f6e616cf46876616c6964697479f6656d696e747380656275726e7380656164686f63806a636f6c6c61746572616c80677369676e657273f6686d6574616461746180", + "encoding": "hex", + "version": "v1beta0" + } + }, + "round": 3 + }, + { + "event": { + "reduced": { + "content": "ab6466656573a16641737365747381a366706f6c696379644e6f6e656a61737365745f6e616d65644e6f6e6566616d6f756e74a1664e756d6265721a00058d8d6a7265666572656e6365738066696e7075747381a3646e616d6566736f75726365657574786f73a1694576616c506172616da16b457870656374496e7075748266736f75726365a56761646472657373a16741646472657373983900181b187118a4183b188d18bb185118b21875184118c218c803189f18be184b181c18db1418e318dc18ab18f518541823182018da18d60d18d9185b18e018e3181d18201878150707181c184f18d6185101182a1876185518ec183918f418ad18f318e31851187718916a6d696e5f616d6f756e74a16641737365747381a366706f6c696379644e6f6e656a61737365745f6e616d65644e6f6e6566616d6f756e74a1664e756d6265721a05fb6e8d63726566644e6f6e65646d616e79f46a636f6c6c61746572616cf46872656465656d6572644e6f6e65676f75747075747382a46761646472657373a16741646472657373983900181b187118a4183b188d18bb185118b21875184118c218c803189f18be184b181c18db1418e318dc18ab18f518541823182018da18d60d18d9185b18e018e3181d18201878150707181c184f18d6185101182a1876185518ec183918f418ad18f318e318511877189165646174756d644e6f6e6566616d6f756e74a16641737365747381a366706f6c696379644e6f6e656a61737365745f6e616d65644e6f6e6566616d6f756e74a1664e756d6265721a05f5e100686f7074696f6e616cf4a46761646472657373a16741646472657373983900181b187118a4183b188d18bb185118b21875184118c218c803189f18be184b181c18db1418e318dc18ab18f518541823182018da18d60d18d9185b18e018e3181d18201878150707181c184f18d6185101182a1876185518ec183918f418ad18f318e318511877189165646174756d644e6f6e6566616d6f756e74a16b4576616c4275696c74496ea16353756282a16b4576616c4275696c74496ea16353756282a16a4576616c436f65726365a16a496e746f417373657473a1694576616c506172616da16b457870656374496e7075748266736f75726365a56761646472657373a16741646472657373983900181b187118a4183b188d18bb185118b21875184118c218c803189f18be184b181c18db1418e318dc18ab18f518541823182018da18d60d18d9185b18e018e3181d18201878150707181c184f18d6185101182a1876185518ec183918f418ad18f318e31851187718916a6d696e5f616d6f756e74a16641737365747381a366706f6c696379644e6f6e656a61737365745f6e616d65644e6f6e6566616d6f756e74a1664e756d6265721a05fb6e8d63726566644e6f6e65646d616e79f46a636f6c6c61746572616cf4a16641737365747381a366706f6c696379644e6f6e656a61737365745f6e616d65644e6f6e6566616d6f756e74a1664e756d6265721a05f5e100a16641737365747381a366706f6c696379644e6f6e656a61737365745f6e616d65644e6f6e6566616d6f756e74a1664e756d6265721a00058d8d686f7074696f6e616cf46876616c6964697479f6656d696e747380656275726e7380656164686f63806a636f6c6c61746572616c80677369676e657273f6686d6574616461746180", + "encoding": "hex", + "version": "v1beta0" + } + }, + "round": 3 + }, + { + "event": { + "inputs_resolved": { + "content": "ab6466656573a16641737365747381a366706f6c696379644e6f6e656a61737365745f6e616d65644e6f6e6566616d6f756e74a1664e756d6265721a00058d8d6a7265666572656e6365738066696e7075747381a3646e616d6566736f75726365657574786f73a1694576616c506172616da163536574a1675574786f53657481a563726566a264747869649820184b18ab18d0186c188c18ed18a9183218ce140318b0189018ae18b418ab18480918c018d318bc186d1871187f18e418bb18a618cf18da18e90b185c65696e646578016761646472657373983900181b187118a4183b188d18bb185118b21875184118c218c803189f18be184b181c18db1418e318dc18ab18f518541823182018da18d60d18d9185b18e018e3181d18201878150707181c184f18d6185101182a1876185518ec183918f418ad18f318e318511877189166617373657473a1654e616b65641b00000001b48fbf2c65646174756df666736372697074f66872656465656d6572644e6f6e65676f75747075747382a46761646472657373a16741646472657373983900181b187118a4183b188d18bb185118b21875184118c218c803189f18be184b181c18db1418e318dc18ab18f518541823182018da18d60d18d9185b18e018e3181d18201878150707181c184f18d6185101182a1876185518ec183918f418ad18f318e318511877189165646174756d644e6f6e6566616d6f756e74a16641737365747381a366706f6c696379644e6f6e656a61737365745f6e616d65644e6f6e6566616d6f756e74a1664e756d6265721a05f5e100686f7074696f6e616cf4a46761646472657373a16741646472657373983900181b187118a4183b188d18bb185118b21875184118c218c803189f18be184b181c18db1418e318dc18ab18f518541823182018da18d60d18d9185b18e018e3181d18201878150707181c184f18d6185101182a1876185518ec183918f418ad18f318e318511877189165646174756d644e6f6e6566616d6f756e74a16b4576616c4275696c74496ea16353756282a16b4576616c4275696c74496ea16353756282a16a4576616c436f65726365a16a496e746f417373657473a1694576616c506172616da163536574a1675574786f53657481a563726566a264747869649820184b18ab18d0186c188c18ed18a9183218ce140318b0189018ae18b418ab18480918c018d318bc186d1871187f18e418bb18a618cf18da18e90b185c65696e646578016761646472657373983900181b187118a4183b188d18bb185118b21875184118c218c803189f18be184b181c18db1418e318dc18ab18f518541823182018da18d60d18d9185b18e018e3181d18201878150707181c184f18d6185101182a1876185518ec183918f418ad18f318e318511877189166617373657473a1654e616b65641b00000001b48fbf2c65646174756df666736372697074f6a16641737365747381a366706f6c696379644e6f6e656a61737365745f6e616d65644e6f6e6566616d6f756e74a1664e756d6265721a05f5e100a16641737365747381a366706f6c696379644e6f6e656a61737365745f6e616d65644e6f6e6566616d6f756e74a1664e756d6265721a00058d8d686f7074696f6e616cf46876616c6964697479f6656d696e747380656275726e7380656164686f63806a636f6c6c61746572616c80677369676e657273f6686d6574616461746180", + "encoding": "hex", + "version": "v1beta0" + } + }, + "round": 3 + }, + { + "event": { + "final_reduced": { + "content": "ab6466656573a16641737365747381a366706f6c696379644e6f6e656a61737365745f6e616d65644e6f6e6566616d6f756e74a1664e756d6265721a00058d8d6a7265666572656e6365738066696e7075747381a3646e616d6566736f75726365657574786f73a1675574786f53657481a563726566a264747869649820184b18ab18d0186c188c18ed18a9183218ce140318b0189018ae18b418ab18480918c018d318bc186d1871187f18e418bb18a618cf18da18e90b185c65696e646578016761646472657373983900181b187118a4183b188d18bb185118b21875184118c218c803189f18be184b181c18db1418e318dc18ab18f518541823182018da18d60d18d9185b18e018e3181d18201878150707181c184f18d6185101182a1876185518ec183918f418ad18f318e318511877189166617373657473a1654e616b65641b00000001b48fbf2c65646174756df666736372697074f66872656465656d6572644e6f6e65676f75747075747382a46761646472657373a16741646472657373983900181b187118a4183b188d18bb185118b21875184118c218c803189f18be184b181c18db1418e318dc18ab18f518541823182018da18d60d18d9185b18e018e3181d18201878150707181c184f18d6185101182a1876185518ec183918f418ad18f318e318511877189165646174756d644e6f6e6566616d6f756e74a16641737365747381a366706f6c696379644e6f6e656a61737365745f6e616d65644e6f6e6566616d6f756e74a1664e756d6265721a05f5e100686f7074696f6e616cf4a46761646472657373a16741646472657373983900181b187118a4183b188d18bb185118b21875184118c218c803189f18be184b181c18db1418e318dc18ab18f518541823182018da18d60d18d9185b18e018e3181d18201878150707181c184f18d6185101182a1876185518ec183918f418ad18f318e318511877189165646174756d644e6f6e6566616d6f756e74a16641737365747381a366706f6c696379644e6f6e656a61737365745f6e616d65644e6f6e6566616d6f756e74a1664e756d6265721b00000001ae94509f686f7074696f6e616cf46876616c6964697479f6656d696e747380656275726e7380656164686f63806a636f6c6c61746572616c80677369676e657273f6686d6574616461746180", + "encoding": "hex", + "version": "v1beta0" + } + }, + "round": 3 + }, + { + "event": { + "compiled": { + "ex_units": 0, + "fee": 363917, + "hash": "3d3b5f256fbc03da3917923b487919c78f08a8e870aa5819e7a14ada763a6b2e", + "payload": "84a400d90102818258204babd06c8ceda932ce1403b090aeb4ab4809c0d3bc6d717fe4bba6cfdae90b5c010182a2005839001b71a43b8dbb51b27541c2c8039fbe4b1cdb14e3dcabf5542320dad60dd95be0e31d20781507071c4fd651012a7655ec39f4adf3e3517791011a05f5e100a2005839001b71a43b8dbb51b27541c2c8039fbe4b1cdb14e3dcabf5542320dad60dd95be0e31d20781507071c4fd651012a7655ec39f4adf3e3517791011b00000001ae94509f021a00058d8d0f00a0f5f6" + } + }, + "round": 3 + }, + { + "event": "converged", + "round": 3 + } + ], + "original_tir": { + "content": "ab6466656573a1694576616c506172616d6a457870656374466565736a7265666572656e6365738066696e7075747381a3646e616d6566736f75726365657574786f73a1694576616c506172616da16b457870656374496e7075748266736f75726365a56761646472657373a1694576616c506172616da16b45787065637456616c7565826673656e64657267416464726573736a6d696e5f616d6f756e74a16b4576616c4275696c74496ea16341646482a16641737365747381a366706f6c696379644e6f6e656a61737365745f6e616d65644e6f6e6566616d6f756e74a1694576616c506172616da16b45787065637456616c756582687175616e7469747963496e74a1694576616c506172616d6a4578706563744665657363726566644e6f6e65646d616e79f46a636f6c6c61746572616cf46872656465656d6572644e6f6e65676f75747075747382a46761646472657373a1694576616c506172616da16b45787065637456616c756582687265636569766572674164647265737365646174756d644e6f6e6566616d6f756e74a16641737365747381a366706f6c696379644e6f6e656a61737365745f6e616d65644e6f6e6566616d6f756e74a1694576616c506172616da16b45787065637456616c756582687175616e7469747963496e74686f7074696f6e616cf4a46761646472657373a1694576616c506172616da16b45787065637456616c7565826673656e646572674164647265737365646174756d644e6f6e6566616d6f756e74a16b4576616c4275696c74496ea16353756282a16b4576616c4275696c74496ea16353756282a16a4576616c436f65726365a16a496e746f417373657473a1694576616c506172616da16b457870656374496e7075748266736f75726365a56761646472657373a1694576616c506172616da16b45787065637456616c7565826673656e64657267416464726573736a6d696e5f616d6f756e74a16b4576616c4275696c74496ea16341646482a16641737365747381a366706f6c696379644e6f6e656a61737365745f6e616d65644e6f6e6566616d6f756e74a1694576616c506172616da16b45787065637456616c756582687175616e7469747963496e74a1694576616c506172616d6a4578706563744665657363726566644e6f6e65646d616e79f46a636f6c6c61746572616cf4a16641737365747381a366706f6c696379644e6f6e656a61737365745f6e616d65644e6f6e6566616d6f756e74a1694576616c506172616da16b45787065637456616c756582687175616e7469747963496e74a1694576616c506172616d6a45787065637446656573686f7074696f6e616cf46876616c6964697479f6656d696e747380656275726e7380656164686f63806a636f6c6c61746572616c80677369676e657273f6686d6574616461746180", + "encoding": "hex", + "version": "v1beta0" + }, + "round": 4 +} \ No newline at end of file diff --git a/crates/tx3-resolver/tests/job_dump.rs b/crates/tx3-resolver/tests/job_dump.rs deleted file mode 100644 index 2580e9d7..00000000 --- a/crates/tx3-resolver/tests/job_dump.rs +++ /dev/null @@ -1,38 +0,0 @@ -use tx3_resolver::job::{ResolveJob, ResolveLog}; -use tx3_tir::encoding::AnyTir; -use tx3_tir::model::v1beta0 as tir; -use tx3_tir::reduce::ArgMap; - -fn dummy_tir() -> AnyTir { - AnyTir::V1Beta0(tir::Tx { - fees: tir::Expression::None, - references: vec![], - inputs: vec![], - outputs: vec![], - validity: None, - mints: vec![], - burns: vec![], - adhoc: vec![], - collateral: vec![], - signers: None, - metadata: vec![], - }) -} - -#[test] -fn dump_creates_valid_json_file() { - let mut job = ResolveJob::new(dummy_tir(), ArgMap::new()); - job.record(ResolveLog::ArgsApplied(dummy_tir())); - job.record(ResolveLog::Converged); - - let dir = std::env::temp_dir().join("tx3-test-dump"); - let dump_id = job.dump_to_dir(&dir).expect("dump should succeed"); - - let path = dir.join(format!("resolve-job-{dump_id}.json")); - assert!(path.exists()); - - let contents = std::fs::read_to_string(&path).unwrap(); - assert!(serde_json::from_str::(&contents).is_ok()); - - let _ = std::fs::remove_file(&path); -} diff --git a/crates/tx3-resolver/tests/trp_schema.rs b/crates/tx3-resolver/tests/trp_schema.rs index 9626f049..2f7d84e5 100644 --- a/crates/tx3-resolver/tests/trp_schema.rs +++ b/crates/tx3-resolver/tests/trp_schema.rs @@ -3,7 +3,7 @@ use std::collections::HashMap; use jsonschema::{Draft, JSONSchema}; use serde_json::{json, Map, Value}; -use tx3_resolver::interop::{BytesEncoding, BytesEnvelope}; +use tx3_resolver::interop::{BytesEncoding, BytesEnvelope, TirEnvelope}; use tx3_resolver::trp::spec::*; fn load_trp_schema() -> Value { diff --git a/crates/tx3-tir/src/compile.rs b/crates/tx3-tir/src/compile.rs index 3f4f8378..0a39b761 100644 --- a/crates/tx3-tir/src/compile.rs +++ b/crates/tx3-tir/src/compile.rs @@ -3,6 +3,7 @@ use crate::{ model::v1beta0, Visitor, }; +use serde::{de::DeserializeOwned, Serialize}; #[derive(Debug, thiserror::Error)] pub enum Error { @@ -30,7 +31,7 @@ pub struct CompiledTx { pub ex_units: u64, } -pub trait Compiler { +pub trait Compiler: Serialize + DeserializeOwned { type CompilerOp; type Expression; diff --git a/crates/tx3-tir/src/encoding.rs b/crates/tx3-tir/src/encoding.rs index b6322ade..196d3fa7 100644 --- a/crates/tx3-tir/src/encoding.rs +++ b/crates/tx3-tir/src/encoding.rs @@ -81,7 +81,7 @@ impl Apply for AnyTir { fn apply_inputs( self, - args: &BTreeMap>, + args: &BTreeMap, ) -> Result { match self { AnyTir::V1Beta0(tx) => Ok(AnyTir::V1Beta0(tx.apply_inputs(args)?)), diff --git a/crates/tx3-tir/src/model/core.rs b/crates/tx3-tir/src/model/core.rs index a30896d2..23d49e9d 100644 --- a/crates/tx3-tir/src/model/core.rs +++ b/crates/tx3-tir/src/model/core.rs @@ -1,3 +1,4 @@ +use std::cmp::Ordering; use std::collections::HashSet; use serde::{Deserialize, Serialize}; @@ -17,6 +18,18 @@ impl UtxoRef { } } +pub trait CanonicalOrd { + fn cmp_canonical(&self, other: &Self) -> Ordering; +} + +impl CanonicalOrd for UtxoRef { + fn cmp_canonical(&self, other: &Self) -> Ordering { + self.txid + .cmp(&other.txid) + .then_with(|| self.index.cmp(&other.index)) + } +} + impl std::fmt::Display for UtxoRef { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}#{}", hex::encode(&self.txid), self.index) @@ -48,7 +61,126 @@ impl PartialEq for Utxo { impl Eq for Utxo {} -pub type UtxoSet = HashSet; +impl CanonicalOrd for Utxo { + fn cmp_canonical(&self, other: &Self) -> Ordering { + self.r#ref.cmp_canonical(&other.r#ref) + } +} + +#[derive(Serialize, Deserialize, Debug, Clone, Default, PartialEq, Eq)] +#[serde(transparent)] +pub struct UtxoSet(HashSet); + +impl UtxoSet { + pub fn new() -> Self { + Self(HashSet::new()) + } + + pub fn iter(&self) -> std::collections::hash_set::Iter<'_, Utxo> { + self.0.iter() + } + + pub fn iter_sorted_by_ref(&self) -> Vec<&Utxo> { + let mut out: Vec<_> = self.0.iter().collect(); + out.sort_by(|a, b| a.cmp_canonical(b)); + out + } + + pub fn into_sorted_by_ref(self) -> Vec { + let mut out: Vec<_> = self.0.into_iter().collect(); + out.sort_by(|a, b| a.cmp_canonical(b)); + out + } + + pub fn refs(&self) -> HashSet { + self.iter().map(|utxo| utxo.r#ref.clone()).collect() + } + + pub fn into_refs(self) -> HashSet { + self.into_iter().map(|utxo| utxo.r#ref).collect() + } + + pub fn refs_sorted(&self) -> Vec { + self.iter_sorted_by_ref() + .into_iter() + .map(|utxo| utxo.r#ref.clone()) + .collect() + } + + pub fn into_refs_sorted(self) -> Vec { + self.into_sorted_by_ref() + .into_iter() + .map(|utxo| utxo.r#ref) + .collect() + } + + pub fn total_assets(&self) -> super::assets::CanonicalAssets { + self.iter() + .fold(super::assets::CanonicalAssets::empty(), |acc, x| { + acc + x.assets.clone() + }) + } + + pub fn first_by_ref(&self) -> Option<&Utxo> { + self.iter_sorted_by_ref().into_iter().next() + } +} + +impl std::ops::Deref for UtxoSet { + type Target = HashSet; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl std::ops::DerefMut for UtxoSet { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl From> for UtxoSet { + fn from(value: HashSet) -> Self { + Self(value) + } +} + +impl From<[Utxo; N]> for UtxoSet { + fn from(value: [Utxo; N]) -> Self { + value.into_iter().collect() + } +} + +impl From for HashSet { + fn from(value: UtxoSet) -> Self { + value.0 + } +} + +impl FromIterator for UtxoSet { + fn from_iter>(iter: T) -> Self { + Self(HashSet::from_iter(iter)) + } +} + +impl IntoIterator for UtxoSet { + type Item = Utxo; + type IntoIter = std::collections::hash_set::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.0.into_iter() + } +} + +impl<'a> IntoIterator for &'a UtxoSet { + type Item = &'a Utxo; + type IntoIter = std::collections::hash_set::Iter<'a, Utxo>; + + fn into_iter(self) -> Self::IntoIter { + self.iter() + } +} #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] pub enum Type { @@ -65,3 +197,38 @@ pub enum Type { Map, Custom(String), } + +#[cfg(test)] +mod tests { + use super::*; + use crate::model::assets::CanonicalAssets; + use crate::model::v1beta0::Expression; + + fn utxo(txid: u8, index: u32, amount: i128) -> Utxo { + Utxo { + r#ref: UtxoRef::new(&[txid], index), + address: vec![], + assets: CanonicalAssets::from_naked_amount(amount), + datum: Some(Expression::None), + script: None, + } + } + + #[test] + fn utxo_set_sorted_helpers_are_canonical() { + let set: UtxoSet = [utxo(3, 0, 3), utxo(1, 1, 1), utxo(1, 0, 2)].into(); + + let refs = set.refs_sorted(); + assert_eq!(refs[0], UtxoRef::new(&[1], 0)); + assert_eq!(refs[1], UtxoRef::new(&[1], 1)); + assert_eq!(refs[2], UtxoRef::new(&[3], 0)); + + assert_eq!(set.first_by_ref().unwrap().r#ref, UtxoRef::new(&[1], 0)); + } + + #[test] + fn utxo_set_total_assets_sums_values() { + let set: UtxoSet = [utxo(1, 0, 2), utxo(2, 0, 3)].into(); + assert_eq!(set.total_assets().naked_amount(), Some(5)); + } +} diff --git a/crates/tx3-tir/src/model/v1beta0.rs b/crates/tx3-tir/src/model/v1beta0.rs index 1d0ad338..aed3e905 100644 --- a/crates/tx3-tir/src/model/v1beta0.rs +++ b/crates/tx3-tir/src/model/v1beta0.rs @@ -9,7 +9,7 @@ //! representation. use serde::{Deserialize, Serialize}; -use std::collections::{HashMap, HashSet}; +use std::collections::HashMap; use crate::{ encoding::{TirRoot, TirVersion}, @@ -196,7 +196,7 @@ pub enum Expression { Address(Vec), Hash(Vec), UtxoRefs(Vec), - UtxoSet(HashSet), + UtxoSet(UtxoSet), Assets(Vec), EvalParam(Box), diff --git a/crates/tx3-tir/src/reduce/mod.rs b/crates/tx3-tir/src/reduce/mod.rs index eb60cf4c..d5dd5691 100644 --- a/crates/tx3-tir/src/reduce/mod.rs +++ b/crates/tx3-tir/src/reduce/mod.rs @@ -1,4 +1,4 @@ -use std::collections::{BTreeMap, HashMap, HashSet}; +use std::collections::{BTreeMap, HashMap}; use crate::model::assets::CanonicalAssets; use crate::model::core::*; @@ -259,14 +259,7 @@ impl Coerceable for Expression { match self { Expression::None => Ok(Expression::None), Expression::Assets(x) => Ok(Expression::Assets(x)), - Expression::UtxoSet(x) => { - let all = x - .into_iter() - .map(|x| x.assets) - .fold(CanonicalAssets::empty(), |acc, x| acc + x); - - Ok(Expression::Assets(all.into())) - } + Expression::UtxoSet(x) => Ok(Expression::Assets(x.total_assets().into())), _ => Err(Error::CannotCoerceIntoAssets(self)), } } @@ -275,9 +268,8 @@ impl Coerceable for Expression { match self { Expression::None => Ok(Expression::None), Expression::UtxoSet(x) => Ok(x - .into_iter() - .next() - .and_then(|x| x.datum) + .first_by_ref() + .and_then(|utxo| utxo.datum.clone()) .unwrap_or(Expression::None)), Expression::List(x) => Ok(Expression::List(x)), Expression::Map(x) => Ok(Expression::Map(x)), @@ -307,7 +299,7 @@ fn arg_value_into_expr(arg: ArgValue) -> Expression { pub trait Apply: Sized + std::fmt::Debug { fn apply_args(self, args: &BTreeMap) -> Result; - fn apply_inputs(self, args: &BTreeMap>) -> Result; + fn apply_inputs(self, args: &BTreeMap) -> Result; fn apply_fees(self, fees: u64) -> Result; fn is_constant(&self) -> bool; @@ -342,7 +334,7 @@ where self.try_map_components(|x| x.apply_args(args)) } - fn apply_inputs(self, args: &BTreeMap>) -> Result { + fn apply_inputs(self, args: &BTreeMap) -> Result { self.try_map_components(|x| x.apply_inputs(args)) } @@ -381,7 +373,7 @@ where self.map(|x| x.apply_args(args)).transpose() } - fn apply_inputs(self, args: &BTreeMap>) -> Result { + fn apply_inputs(self, args: &BTreeMap) -> Result { self.map(|x| x.apply_inputs(args)).transpose() } @@ -423,7 +415,7 @@ where self.into_iter().map(|x| x.apply_args(args)).collect() } - fn apply_inputs(self, args: &BTreeMap>) -> Result { + fn apply_inputs(self, args: &BTreeMap) -> Result { self.into_iter().map(|x| x.apply_inputs(args)).collect() } @@ -458,7 +450,7 @@ where .collect() } - fn apply_inputs(self, args: &BTreeMap>) -> Result { + fn apply_inputs(self, args: &BTreeMap) -> Result { self.into_iter() .map(|(k, v)| v.apply_inputs(args).map(|v| (k, v))) .collect() @@ -793,7 +785,7 @@ impl Apply for Param { } } - fn apply_inputs(self, args: &BTreeMap>) -> Result { + fn apply_inputs(self, args: &BTreeMap) -> Result { match self { Param::ExpectInput(name, query) => { let defined = args.get(&name).cloned(); @@ -903,7 +895,7 @@ impl Apply for Expression { } } - fn apply_inputs(self, args: &BTreeMap>) -> Result { + fn apply_inputs(self, args: &BTreeMap) -> Result { match self { Self::List(x) => Ok(Self::List( x.into_iter() @@ -1305,7 +1297,7 @@ impl Apply for Tx { Ok(tx) } - fn apply_inputs(self, args: &BTreeMap>) -> Result { + fn apply_inputs(self, args: &BTreeMap) -> Result { Ok(Self { references: self.references.apply_inputs(args)?, inputs: self.inputs.apply_inputs(args)?, @@ -1456,10 +1448,7 @@ pub fn apply_args(template: T, args: &ArgMap) -> Result { template.apply_args(args) } -pub fn apply_inputs( - template: T, - args: &BTreeMap>, -) -> Result { +pub fn apply_inputs(template: T, args: &BTreeMap) -> Result { template.apply_inputs(args) } @@ -1599,7 +1588,7 @@ mod tests { let inputs = BTreeMap::from([( "source".to_string(), - HashSet::from([Utxo { + UtxoSet::from([Utxo { r#ref: UtxoRef::new(b"abc", 0), address: b"abc".to_vec(), datum: None, @@ -1812,9 +1801,9 @@ mod tests { script: None, }]; - let op = Coerce::IntoAssets(Expression::UtxoSet(HashSet::from_iter( - utxos.clone().into_iter(), - ))); + let op = Coerce::IntoAssets(Expression::UtxoSet( + std::collections::HashSet::from_iter(utxos.clone().into_iter()).into(), + )); let reduced = op.reduce().unwrap(); @@ -1834,9 +1823,9 @@ mod tests { script: None, }]; - let op = Coerce::IntoDatum(Expression::UtxoSet(HashSet::from_iter( - utxos.clone().into_iter(), - ))); + let op = Coerce::IntoDatum(Expression::UtxoSet( + std::collections::HashSet::from_iter(utxos.clone().into_iter()).into(), + )); let reduced = op.reduce().unwrap();