From 420724411a0a8f38686a756fb3eec70ed066e0d9 Mon Sep 17 00:00:00 2001 From: Oleh Komendant Date: Mon, 4 May 2026 15:42:56 +0300 Subject: [PATCH 1/6] Fix tests --- .github/workflows/tests.yml | 2 +- .../src/indexer/handlers/lending_creation.rs | 2 +- .../src/indexer/handlers/loan_liquidation.rs | 13 ------------- crates/indexer/src/models/offer.rs | 2 +- crates/indexer/tests/indexer_integration.rs | 2 +- 5 files changed, 4 insertions(+), 17 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 61ff7a7..72dd4f8 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -12,7 +12,7 @@ env: SQLX_VERSION: 0.8.0 SQLX_FEATURES: "rustls,postgres" SQLX_FEATURES_KEY: "rustls-postgres" - SIMPLEX_VERSION: v0.0.3 + SIMPLEX_VERSION: v0.0.4 APP_USER: app APP_USER_PWD: secret APP_DB_NAME: lending-indexer diff --git a/crates/indexer/src/indexer/handlers/lending_creation.rs b/crates/indexer/src/indexer/handlers/lending_creation.rs index 5df5e2f..329f1a0 100644 --- a/crates/indexer/src/indexer/handlers/lending_creation.rs +++ b/crates/indexer/src/indexer/handlers/lending_creation.rs @@ -77,11 +77,11 @@ mod tests { 7, vec![ normal_output(), - explicit_asset_output(7), normal_output(), normal_output(), normal_output(), normal_output(), + explicit_asset_output(7), normal_output(), ], ); diff --git a/crates/indexer/src/indexer/handlers/loan_liquidation.rs b/crates/indexer/src/indexer/handlers/loan_liquidation.rs index 9f4930b..1b6c810 100644 --- a/crates/indexer/src/indexer/handlers/loan_liquidation.rs +++ b/crates/indexer/src/indexer/handlers/loan_liquidation.rs @@ -110,19 +110,6 @@ mod tests { assert!(!is_loan_liquidation_tx(&tx)); } - #[test] - fn output_4_is_null_data_returns_false() { - let tx = make_tx(vec![ - normal_output(), - null_output(), - null_output(), - null_output(), - null_output(), - ]); - - assert!(!is_loan_liquidation_tx(&tx)); - } - #[test] fn output_0_also_null_data_still_returns_true() { let tx = make_tx(vec![ diff --git a/crates/indexer/src/models/offer.rs b/crates/indexer/src/models/offer.rs index 8c5b5df..e1157e1 100644 --- a/crates/indexer/src/models/offer.rs +++ b/crates/indexer/src/models/offer.rs @@ -115,7 +115,7 @@ pub struct OfferModelShort { #[cfg(test)] mod tests { use super::{OfferModel, OfferStatus}; - use lending_contracts::{programs::PreLockParameters, utils::LendingOfferParameters}; + use lending_contracts::{programs::pre_lock::PreLockParameters, utils::LendingOfferParameters}; use simplex::{ provider::SimplicityNetwork, simplicityhl::elements::{AssetId, Txid, hashes::Hash, secp256k1_zkp::XOnlyPublicKey}, diff --git a/crates/indexer/tests/indexer_integration.rs b/crates/indexer/tests/indexer_integration.rs index ba2b73b..6cd788b 100644 --- a/crates/indexer/tests/indexer_integration.rs +++ b/crates/indexer/tests/indexer_integration.rs @@ -14,7 +14,7 @@ use axum::{ response::IntoResponse, routing::get, }; -use lending_contracts::{programs::PreLockParameters, utils::LendingOfferParameters}; +use lending_contracts::{programs::pre_lock::PreLockParameters, utils::LendingOfferParameters}; use lending_indexer::esplora_client::EsploraClient; use lending_indexer::indexer::{ UtxoCache, get_last_indexed_height, handle_pre_lock_creation, load_utxo_cache, process_block, From b456859c3703726b52340702db91d88d8e1e14e9 Mon Sep 17 00:00:00 2001 From: Oleh Komendant Date: Mon, 4 May 2026 16:41:55 +0300 Subject: [PATCH 2/6] Implement AssetAuthFactory covenant --- crates/contracts/simf/asset_auth_vault.simf | 238 ++++++++++++++++++ .../src/programs/asset_auth_vault/core.rs | 188 ++++++++++++++ .../src/programs/asset_auth_vault/mod.rs | 7 + .../src/programs/asset_auth_vault/params.rs | 84 +++++++ .../src/programs/asset_auth_vault/witness.rs | 75 ++++++ crates/contracts/src/programs/mod.rs | 1 + 6 files changed, 593 insertions(+) create mode 100644 crates/contracts/simf/asset_auth_vault.simf create mode 100644 crates/contracts/src/programs/asset_auth_vault/core.rs create mode 100644 crates/contracts/src/programs/asset_auth_vault/mod.rs create mode 100644 crates/contracts/src/programs/asset_auth_vault/params.rs create mode 100644 crates/contracts/src/programs/asset_auth_vault/witness.rs diff --git a/crates/contracts/simf/asset_auth_vault.simf b/crates/contracts/simf/asset_auth_vault.simf new file mode 100644 index 0000000..b329e16 --- /dev/null +++ b/crates/contracts/simf/asset_auth_vault.simf @@ -0,0 +1,238 @@ +// Helper getters + +fn get_script_hash(index: u32, is_input_index: bool) -> u256 { + let script_hash: u256 = match is_input_index { + true => unwrap(jet::input_script_hash(index)), + false => unwrap(jet::output_script_hash(index)), + }; + + script_hash +} + +fn get_asset_and_amount(index: u32, is_input_index: bool) -> (u256, u64) { + let pair: (Asset1, Amount1) = match is_input_index { + true => unwrap(jet::input_amount(index)), + false => unwrap(jet::output_amount(index)), + }; + let (asset, amount): (Asset1, Amount1) = pair; + let asset_bits: u256 = unwrap_right::<(u1, u256)>(asset); + let amount: u64 = unwrap_right::<(u1, u256)>(amount); + (asset_bits, amount) +} + +// Check helpers + +fn check_asset_amounts_eq(asset_amount_1: u64, asset_amount_2: u64) { + assert!(jet::eq_64(asset_amount_1, asset_amount_2)); +} + +fn check_assets_eq(asset_bits_1: u256, asset_bits_2: u256) { + assert!(jet::eq_256(asset_bits_1, asset_bits_2)); +} + +fn check_script_hashes_eq(script_1: u256, script_2: u256) { + assert!(jet::eq_256(script_1, script_2)); +} + +fn ensure_output_is_op_return(index: u32) { + match jet::output_null_datum(index, 0) { + Some(entry: Option>>) => (), + None => panic!(), + } +} + +fn ensure_asset_and_amount_ge(index: u32, is_input_index: bool, expected_asset_bits: u256, expected_amount: u64) { + let (asset_bits, amount): (u256, u64) = get_asset_and_amount(index, is_input_index); + assert!(jet::eq_256(asset_bits, expected_asset_bits)); + assert!(jet::le_64(expected_amount, amount)); +} + +fn ensure_finalization(expected_state: bool) { + assert!(jet::eq_1(::into(expected_state), ::into(param::IS_FINALIZED))); +} + +fn ensure_output_script_hash(index: u32, expected_script_hash: u256) { + check_script_hashes_eq(get_script_hash(index, false), expected_script_hash); +} + +fn ensure_vault_asset(index: u32, is_input_index: bool) -> u64 { + let (vault_asset_id, vault_amount): (u256, u64) = get_asset_and_amount(index, is_input_index); + + check_assets_eq(vault_asset_id, param::VAULT_ASSET_ID); + + vault_amount +} + +// Math helpers + +fn safe_add_64(first: u64, second: u64) -> u64 { + let (carry, result): (bool, u64) = jet::add_64(first, second); + + result +} + +// Auth logic + +fn auth_check( + input_asset_index: u32, + output_asset_index: u32, + expected_asset_id: u256, + expected_minimal_asset_amount: u64 +) { + ensure_asset_and_amount_ge(input_asset_index, true, expected_asset_id, expected_minimal_asset_amount); + ensure_asset_and_amount_ge(output_asset_index, false, expected_asset_id, expected_minimal_asset_amount); +} + +fn auth_with_burn_check( + input_asset_index: u32, + output_asset_index: u32, + expected_asset_id: u256, + expected_minimal_asset_amount: u64, + with_asset_burn: bool +) { + auth_check(input_asset_index, output_asset_index, expected_asset_id, expected_minimal_asset_amount); + + match with_asset_burn { + true => { + ensure_output_is_op_return(output_asset_index); + }, + false => {}, + } +} + +// Main paths logic + +fn withdraw_all(input_keeper_index: u32, output_keeper_index: u32) { + ensure_finalization(true); + + auth_with_burn_check( + input_keeper_index, + output_keeper_index, + param::KEEPER_AUTH_ASSET_ID, + param::KEEPER_AUTH_ASSET_AMOUNT, + param::WITH_KEEPER_ASSET_BURN + ); + + let vault_amount: u64 = ensure_vault_asset(jet::current_index(), true); +} + +fn withdraw_part(input_keeper_index: u32, output_keeper_index: u32, vault_output_index: u32, amount_to_withdraw: u64) { + ensure_finalization(false); + + auth_check( + input_keeper_index, + output_keeper_index, + param::KEEPER_AUTH_ASSET_ID, + param::KEEPER_AUTH_ASSET_AMOUNT + ); + + ensure_output_script_hash(vault_output_index, jet::current_script_hash()); + + let vault_amount: u64 = ensure_vault_asset(jet::current_index(), true); + let output_vault_amount: u64 = ensure_vault_asset(vault_output_index, false); + + assert!(jet::lt_64(amount_to_withdraw, vault_amount)); + + let (carry, vault_change): (bool, u64) = jet::subtract_64(vault_amount, amount_to_withdraw); + + check_asset_amounts_eq(output_vault_amount, vault_change); +} + +fn supply(input_supplier_index: u32, output_supplier_index: u32, vault_output_index: u32, amount_to_supply: u64) { + ensure_finalization(false); + + auth_check( + input_supplier_index, + output_supplier_index, + param::SUPPLIER_AUTH_ASSET_ID, + 1 // Allow any amount of the supplier auth asset + ); + + ensure_output_script_hash(vault_output_index, jet::current_script_hash()); + + let vault_amount: u64 = ensure_vault_asset(jet::current_index(), true); + let output_vault_amount: u64 = ensure_vault_asset(vault_output_index, false); + + let expected_output_vault_amount: u64 = safe_add_64(vault_amount, amount_to_supply); + + check_asset_amounts_eq(output_vault_amount, expected_output_vault_amount); +} + +fn final_supply( + input_supplier_index: u32, + output_supplier_index: u32, + finalized_vault_output_index: u32, + amount_to_supply: u64 +) { + ensure_finalization(false); + + auth_with_burn_check( + input_supplier_index, + output_supplier_index, + param::SUPPLIER_AUTH_ASSET_ID, + 1, // Allow any amount of the supplier auth asset + param::WITH_SUPPLIER_ASSET_BURN + ); + + ensure_output_script_hash(finalized_vault_output_index, param::FINALIZED_VAULT_COV_HASH); + + let vault_amount: u64 = ensure_vault_asset(jet::current_index(), true); + let output_vault_amount: u64 = ensure_vault_asset(finalized_vault_output_index, false); + + let expected_output_vault_amount: u64 = safe_add_64(vault_amount, amount_to_supply); + + check_asset_amounts_eq(output_vault_amount, expected_output_vault_amount); +} + +fn main() { + match witness::PATH { + Left(withdraw_params: Either<(u32, u32), (u32, u32, u32, u64)>) => { + match withdraw_params { + Left(withdraw_all_params: (u32, u32)) => { + let (input_keeper_index, output_keeper_index): (u32, u32) = withdraw_all_params; + + withdraw_all(input_keeper_index, output_keeper_index); + }, + Right(withdraw_part_params: (u32, u32, u32, u64)) => { + let ( + input_keeper_index, + output_keeper_index, + vault_output_index, + amount_to_withdraw, + ): (u32, u32, u32, u64) = withdraw_part_params; + + withdraw_part(input_keeper_index, output_keeper_index, vault_output_index, amount_to_withdraw); + }, + } + }, + Right(supply_params: Either<(u32, u32, u32, u64), (u32, u32, u32, u64)>) => { + match supply_params { + Left(supply_params: (u32, u32, u32, u64)) => { + let ( + input_supplier_index, + output_supplier_index, + vault_output_index, + amount_to_supply, + ): (u32, u32, u32, u64) = supply_params; + + supply(input_supplier_index, output_supplier_index, vault_output_index, amount_to_supply); + }, + Right(final_supply_params: (u32, u32, u32, u64)) => { + let ( + input_supplier_index, + output_supplier_index, + finalized_vault_output_index, + amount_to_supply, + ): (u32, u32, u32, u64) = final_supply_params; + + final_supply( + input_supplier_index, + output_supplier_index, + finalized_vault_output_index, + amount_to_supply + ); + } + } + }, + } +} \ No newline at end of file diff --git a/crates/contracts/src/programs/asset_auth_vault/core.rs b/crates/contracts/src/programs/asset_auth_vault/core.rs new file mode 100644 index 0000000..d291832 --- /dev/null +++ b/crates/contracts/src/programs/asset_auth_vault/core.rs @@ -0,0 +1,188 @@ +use simplex::{ + program::Program, + provider::SimplicityNetwork, + transaction::{FinalTransaction, UTXO}, +}; + +use crate::artifacts::asset_auth_vault::AssetAuthVaultProgram; +use crate::programs::asset_auth_vault::{AssetAuthVaultParameters, AssetAuthVaultWitnessBranch}; +use crate::programs::program::SimplexProgram; + +pub struct AssetAuthVault { + program: AssetAuthVaultProgram, + parameters: AssetAuthVaultParameters, +} + +impl AssetAuthVault { + pub fn new_active(parameters: AssetAuthVaultParameters) -> Self { + let finalized_vault = Self::new_finalized(parameters); + let finalized_vault_hash = finalized_vault.get_script_hash(); + + let parameters = parameters.from_finalized_parameters(finalized_vault_hash); + + Self { + program: AssetAuthVaultProgram::new(parameters.build_arguments()), + parameters, + } + } + + pub fn new_finalized(parameters: AssetAuthVaultParameters) -> Self { + assert!( + parameters.is_finalized(), + "Unable to create AssetAuthVault with non finalized parameters" + ); + + Self { + program: AssetAuthVaultProgram::new(parameters.build_arguments()), + parameters, + } + } + + pub fn get_parameters(&self) -> &AssetAuthVaultParameters { + &self.parameters + } + + pub fn attach_creation(&self, ft: &mut FinalTransaction, initial_asset_amount: u64) { + self.add_program_output(ft, self.parameters.vault_asset_id, initial_asset_amount); + } + + pub fn attach_withdrawing_all( + &self, + ft: &mut FinalTransaction, + program_utxo: UTXO, + input_keeper_index: u32, + output_keeper_index: u32, + ) { + self.ensure_finalized_vault(); + + let withdraw_all_witness_branch = AssetAuthVaultWitnessBranch::WithdrawAll { + input_keeper_index, + output_keeper_index, + }; + + self.add_program_input( + ft, + program_utxo, + withdraw_all_witness_branch.build_witness(), + ); + } + + pub fn attach_partial_withdrawing( + &self, + ft: &mut FinalTransaction, + program_utxo: UTXO, + input_keeper_index: u32, + output_keeper_index: u32, + amount_to_withdraw: u64, + ) { + self.ensure_non_finalized_vault(); + + let current_vault_amount = program_utxo.explicit_amount(); + + assert!( + amount_to_withdraw < current_vault_amount, + "Invalid amount to withdraw" + ); + + let vault_change = current_vault_amount - amount_to_withdraw; + + let vault_output_index = ft.n_outputs() as u32; + + let withdraw_part_witness_branch = AssetAuthVaultWitnessBranch::WithdrawPart { + input_keeper_index, + output_keeper_index, + vault_output_index, + amount_to_withdraw, + }; + + self.add_program_input( + ft, + program_utxo, + withdraw_part_witness_branch.build_witness(), + ); + + self.add_program_output(ft, self.parameters.vault_asset_id, vault_change); + } + + pub fn attach_supplying( + &self, + ft: &mut FinalTransaction, + program_utxo: UTXO, + input_supplier_index: u32, + output_supplier_index: u32, + amount_to_supply: u64, + ) { + self.ensure_non_finalized_vault(); + + assert!(amount_to_supply > 0, "Zero amount to supply"); + + let new_vault_amount = program_utxo.explicit_amount() + amount_to_supply; + + let vault_output_index = ft.n_outputs() as u32; + + let supply_witness_branch = AssetAuthVaultWitnessBranch::Supply { + input_supplier_index, + output_supplier_index, + vault_output_index, + amount_to_supply, + }; + + self.add_program_input(ft, program_utxo, supply_witness_branch.build_witness()); + + self.add_program_output(ft, self.parameters.vault_asset_id, new_vault_amount); + } + + pub fn attach_final_supplying( + &self, + ft: &mut FinalTransaction, + program_utxo: UTXO, + input_supplier_index: u32, + output_supplier_index: u32, + amount_to_supply: u64, + ) -> AssetAuthVault { + self.ensure_non_finalized_vault(); + + assert!(amount_to_supply > 0, "Zero amount to supply"); + + let new_vault_amount = program_utxo.explicit_amount() + amount_to_supply; + + let vault_output_index = ft.n_outputs() as u32; + + let supply_witness_branch = AssetAuthVaultWitnessBranch::FinalSupply { + input_supplier_index, + output_supplier_index, + vault_output_index, + amount_to_supply, + }; + + self.add_program_input(ft, program_utxo, supply_witness_branch.build_witness()); + + let finalized_vault = + AssetAuthVault::new_finalized(self.parameters.to_finalized_parameters()); + + finalized_vault.add_program_output(ft, self.parameters.vault_asset_id, new_vault_amount); + + finalized_vault + } + + fn ensure_finalized_vault(&self) { + assert!(self.parameters.is_finalized(), "Not a finalized vault"); + } + + fn ensure_non_finalized_vault(&self) { + assert!( + !self.parameters.is_finalized(), + "Vault is already finalized" + ); + } +} + +impl SimplexProgram for AssetAuthVault { + fn get_program(&self) -> &Program { + self.program.as_ref() + } + + fn get_network(&self) -> &SimplicityNetwork { + &self.parameters.network + } +} diff --git a/crates/contracts/src/programs/asset_auth_vault/mod.rs b/crates/contracts/src/programs/asset_auth_vault/mod.rs new file mode 100644 index 0000000..f021dc1 --- /dev/null +++ b/crates/contracts/src/programs/asset_auth_vault/mod.rs @@ -0,0 +1,7 @@ +mod core; +mod params; +mod witness; + +pub use core::AssetAuthVault; +pub use params::AssetAuthVaultParameters; +pub use witness::AssetAuthVaultWitnessBranch; diff --git a/crates/contracts/src/programs/asset_auth_vault/params.rs b/crates/contracts/src/programs/asset_auth_vault/params.rs new file mode 100644 index 0000000..25a750c --- /dev/null +++ b/crates/contracts/src/programs/asset_auth_vault/params.rs @@ -0,0 +1,84 @@ +use simplex::{provider::SimplicityNetwork, simplicityhl::elements::AssetId}; + +use crate::artifacts::asset_auth_vault::derived_asset_auth_vault::AssetAuthVaultArguments; + +#[derive(Debug, Clone, Copy)] +pub struct AssetAuthVaultParameters { + pub vault_asset_id: AssetId, + pub keeper_asset_id: AssetId, + pub supplier_asset_id: AssetId, + pub keeper_min_asset_amount: u64, + pub with_keeper_asset_burn: bool, + pub with_supplier_asset_burn: bool, + pub network: SimplicityNetwork, + finalized_vault_cov_hash: [u8; 32], +} + +impl AssetAuthVaultParameters { + pub fn new( + vault_asset_id: AssetId, + keeper_asset_id: AssetId, + supplier_asset_id: AssetId, + keeper_min_asset_amount: u64, + with_keeper_asset_burn: bool, + with_supplier_asset_burn: bool, + network: SimplicityNetwork, + ) -> Self { + Self { + vault_asset_id, + keeper_asset_id, + supplier_asset_id, + keeper_min_asset_amount, + with_keeper_asset_burn, + with_supplier_asset_burn, + network, + finalized_vault_cov_hash: [0u8; 32], + } + } + + pub fn from_finalized_parameters(self, finalized_vault_cov_hash: [u8; 32]) -> Self { + Self { + vault_asset_id: self.vault_asset_id, + keeper_asset_id: self.keeper_asset_id, + supplier_asset_id: self.supplier_asset_id, + keeper_min_asset_amount: self.keeper_min_asset_amount, + with_keeper_asset_burn: self.with_keeper_asset_burn, + with_supplier_asset_burn: self.with_supplier_asset_burn, + network: self.network, + finalized_vault_cov_hash, + } + } + + pub fn to_finalized_parameters(self) -> Self { + Self::new( + self.vault_asset_id, + self.keeper_asset_id, + self.supplier_asset_id, + self.keeper_min_asset_amount, + self.with_keeper_asset_burn, + self.with_supplier_asset_burn, + self.network, + ) + } + + pub fn build_arguments(&self) -> AssetAuthVaultArguments { + AssetAuthVaultArguments { + vault_asset_id: self.vault_asset_id.into_inner().0, + keeper_auth_asset_id: self.keeper_asset_id.into_inner().0, + supplier_auth_asset_id: self.supplier_asset_id.into_inner().0, + keeper_auth_asset_amount: self.keeper_min_asset_amount, + finalized_vault_cov_hash: self.finalized_vault_cov_hash, + is_finalized: self.is_finalized(), + with_keeper_asset_burn: self.with_keeper_asset_burn, + with_supplier_asset_burn: self.with_supplier_asset_burn, + } + } + + pub fn get_finalized_vault_hash(&self) -> [u8; 32] { + self.finalized_vault_cov_hash + } + + pub fn is_finalized(&self) -> bool { + self.finalized_vault_cov_hash == [0u8; 32] + } +} diff --git a/crates/contracts/src/programs/asset_auth_vault/witness.rs b/crates/contracts/src/programs/asset_auth_vault/witness.rs new file mode 100644 index 0000000..b5507b5 --- /dev/null +++ b/crates/contracts/src/programs/asset_auth_vault/witness.rs @@ -0,0 +1,75 @@ +use simplex::either::Either::{Left, Right}; + +use crate::artifacts::asset_auth_vault::derived_asset_auth_vault::AssetAuthVaultWitness; + +#[derive(Debug, Clone, Copy)] +pub enum AssetAuthVaultWitnessBranch { + WithdrawAll { + input_keeper_index: u32, + output_keeper_index: u32, + }, + WithdrawPart { + input_keeper_index: u32, + output_keeper_index: u32, + vault_output_index: u32, + amount_to_withdraw: u64, + }, + Supply { + input_supplier_index: u32, + output_supplier_index: u32, + vault_output_index: u32, + amount_to_supply: u64, + }, + FinalSupply { + input_supplier_index: u32, + output_supplier_index: u32, + vault_output_index: u32, + amount_to_supply: u64, + }, +} + +impl AssetAuthVaultWitnessBranch { + pub fn build_witness(&self) -> Box { + let path = match self { + AssetAuthVaultWitnessBranch::WithdrawAll { + input_keeper_index, + output_keeper_index, + } => Left(Left((*input_keeper_index, *output_keeper_index))), + AssetAuthVaultWitnessBranch::WithdrawPart { + input_keeper_index, + output_keeper_index, + vault_output_index, + amount_to_withdraw, + } => Left(Right(( + *input_keeper_index, + *output_keeper_index, + *vault_output_index, + *amount_to_withdraw, + ))), + AssetAuthVaultWitnessBranch::Supply { + input_supplier_index, + output_supplier_index, + vault_output_index, + amount_to_supply, + } => Right(Left(( + *input_supplier_index, + *output_supplier_index, + *vault_output_index, + *amount_to_supply, + ))), + AssetAuthVaultWitnessBranch::FinalSupply { + input_supplier_index, + output_supplier_index, + vault_output_index, + amount_to_supply, + } => Right(Right(( + *input_supplier_index, + *output_supplier_index, + *vault_output_index, + *amount_to_supply, + ))), + }; + + Box::new(AssetAuthVaultWitness { path }) + } +} diff --git a/crates/contracts/src/programs/mod.rs b/crates/contracts/src/programs/mod.rs index 94aa627..9bd9135 100644 --- a/crates/contracts/src/programs/mod.rs +++ b/crates/contracts/src/programs/mod.rs @@ -1,4 +1,5 @@ pub mod asset_auth; +pub mod asset_auth_vault; pub mod issuance_factory; pub mod lending; pub mod ownable_script_auth; From d8bd085f4b533f8d43b1a54bac220f3e46007a21 Mon Sep 17 00:00:00 2001 From: Oleh Komendant Date: Mon, 4 May 2026 23:21:07 +0300 Subject: [PATCH 3/6] Add tests for AssetAuthVault supply paths --- crates/contracts/simf/asset_auth_vault.simf | 47 ++-- .../src/programs/asset_auth_vault/core.rs | 21 +- .../tests/asset_auth/unlock_failure_flows.rs | 5 +- crates/contracts/tests/asset_auth_vault.rs | 2 + .../final_supply_failure_flows.rs | 239 ++++++++++++++++++ .../final_supply_success_flows.rs | 198 +++++++++++++++ .../contracts/tests/asset_auth_vault/mod.rs | 8 + .../contracts/tests/asset_auth_vault/setup.rs | 224 ++++++++++++++++ .../asset_auth_vault/supply_failure_flows.rs | 185 ++++++++++++++ .../asset_auth_vault/supply_success_flows.rs | 188 ++++++++++++++ .../issue_assets_failure_flows.rs | 15 +- .../remove_factory_failure_flows.rs | 5 +- 12 files changed, 1092 insertions(+), 45 deletions(-) create mode 100644 crates/contracts/tests/asset_auth_vault.rs create mode 100644 crates/contracts/tests/asset_auth_vault/final_supply_failure_flows.rs create mode 100644 crates/contracts/tests/asset_auth_vault/final_supply_success_flows.rs create mode 100644 crates/contracts/tests/asset_auth_vault/mod.rs create mode 100644 crates/contracts/tests/asset_auth_vault/setup.rs create mode 100644 crates/contracts/tests/asset_auth_vault/supply_failure_flows.rs create mode 100644 crates/contracts/tests/asset_auth_vault/supply_success_flows.rs diff --git a/crates/contracts/simf/asset_auth_vault.simf b/crates/contracts/simf/asset_auth_vault.simf index b329e16..7b99535 100644 --- a/crates/contracts/simf/asset_auth_vault.simf +++ b/crates/contracts/simf/asset_auth_vault.simf @@ -20,6 +20,13 @@ fn get_asset_and_amount(index: u32, is_input_index: bool) -> (u256, u64) { (asset_bits, amount) } +fn is_op_return(output_index: u32) -> bool { + match jet::output_null_datum(output_index, 0) { + Some(entry: Option>>) => true, + None => false, + } +} + // Check helpers fn check_asset_amounts_eq(asset_amount_1: u64, asset_amount_2: u64) { @@ -34,11 +41,16 @@ fn check_script_hashes_eq(script_1: u256, script_2: u256) { assert!(jet::eq_256(script_1, script_2)); } +fn check_flags_eq(flag_1: bool, flag_2: bool) { + assert!(jet::eq_1(::into(flag_1), ::into(flag_2))); +} + fn ensure_output_is_op_return(index: u32) { - match jet::output_null_datum(index, 0) { - Some(entry: Option>>) => (), - None => panic!(), - } + check_flags_eq(is_op_return(index), true); +} + +fn ensure_output_is_not_an_op_return(index: u32) { + check_flags_eq(is_op_return(index), false); } fn ensure_asset_and_amount_ge(index: u32, is_input_index: bool, expected_asset_bits: u256, expected_amount: u64) { @@ -73,16 +85,6 @@ fn safe_add_64(first: u64, second: u64) -> u64 { // Auth logic -fn auth_check( - input_asset_index: u32, - output_asset_index: u32, - expected_asset_id: u256, - expected_minimal_asset_amount: u64 -) { - ensure_asset_and_amount_ge(input_asset_index, true, expected_asset_id, expected_minimal_asset_amount); - ensure_asset_and_amount_ge(output_asset_index, false, expected_asset_id, expected_minimal_asset_amount); -} - fn auth_with_burn_check( input_asset_index: u32, output_asset_index: u32, @@ -90,13 +92,16 @@ fn auth_with_burn_check( expected_minimal_asset_amount: u64, with_asset_burn: bool ) { - auth_check(input_asset_index, output_asset_index, expected_asset_id, expected_minimal_asset_amount); + ensure_asset_and_amount_ge(input_asset_index, true, expected_asset_id, expected_minimal_asset_amount); + ensure_asset_and_amount_ge(output_asset_index, false, expected_asset_id, expected_minimal_asset_amount); match with_asset_burn { true => { ensure_output_is_op_return(output_asset_index); }, - false => {}, + false => { + ensure_output_is_not_an_op_return(output_asset_index) + }, } } @@ -119,11 +124,12 @@ fn withdraw_all(input_keeper_index: u32, output_keeper_index: u32) { fn withdraw_part(input_keeper_index: u32, output_keeper_index: u32, vault_output_index: u32, amount_to_withdraw: u64) { ensure_finalization(false); - auth_check( + auth_with_burn_check( input_keeper_index, output_keeper_index, param::KEEPER_AUTH_ASSET_ID, - param::KEEPER_AUTH_ASSET_AMOUNT + param::KEEPER_AUTH_ASSET_AMOUNT, + false ); ensure_output_script_hash(vault_output_index, jet::current_script_hash()); @@ -141,11 +147,12 @@ fn withdraw_part(input_keeper_index: u32, output_keeper_index: u32, vault_output fn supply(input_supplier_index: u32, output_supplier_index: u32, vault_output_index: u32, amount_to_supply: u64) { ensure_finalization(false); - auth_check( + auth_with_burn_check( input_supplier_index, output_supplier_index, param::SUPPLIER_AUTH_ASSET_ID, - 1 // Allow any amount of the supplier auth asset + 1, // Allow any amount of the supplier auth asset + false ); ensure_output_script_hash(vault_output_index, jet::current_script_hash()); diff --git a/crates/contracts/src/programs/asset_auth_vault/core.rs b/crates/contracts/src/programs/asset_auth_vault/core.rs index d291832..84eb5d9 100644 --- a/crates/contracts/src/programs/asset_auth_vault/core.rs +++ b/crates/contracts/src/programs/asset_auth_vault/core.rs @@ -53,8 +53,6 @@ impl AssetAuthVault { input_keeper_index: u32, output_keeper_index: u32, ) { - self.ensure_finalized_vault(); - let withdraw_all_witness_branch = AssetAuthVaultWitnessBranch::WithdrawAll { input_keeper_index, output_keeper_index, @@ -75,8 +73,6 @@ impl AssetAuthVault { output_keeper_index: u32, amount_to_withdraw: u64, ) { - self.ensure_non_finalized_vault(); - let current_vault_amount = program_utxo.explicit_amount(); assert!( @@ -112,8 +108,6 @@ impl AssetAuthVault { output_supplier_index: u32, amount_to_supply: u64, ) { - self.ensure_non_finalized_vault(); - assert!(amount_to_supply > 0, "Zero amount to supply"); let new_vault_amount = program_utxo.explicit_amount() + amount_to_supply; @@ -140,8 +134,6 @@ impl AssetAuthVault { output_supplier_index: u32, amount_to_supply: u64, ) -> AssetAuthVault { - self.ensure_non_finalized_vault(); - assert!(amount_to_supply > 0, "Zero amount to supply"); let new_vault_amount = program_utxo.explicit_amount() + amount_to_supply; @@ -160,21 +152,10 @@ impl AssetAuthVault { let finalized_vault = AssetAuthVault::new_finalized(self.parameters.to_finalized_parameters()); - finalized_vault.add_program_output(ft, self.parameters.vault_asset_id, new_vault_amount); + finalized_vault.attach_creation(ft, new_vault_amount); finalized_vault } - - fn ensure_finalized_vault(&self) { - assert!(self.parameters.is_finalized(), "Not a finalized vault"); - } - - fn ensure_non_finalized_vault(&self) { - assert!( - !self.parameters.is_finalized(), - "Vault is already finalized" - ); - } } impl SimplexProgram for AssetAuthVault { diff --git a/crates/contracts/tests/asset_auth/unlock_failure_flows.rs b/crates/contracts/tests/asset_auth/unlock_failure_flows.rs index 04b7b7b..541b5b2 100644 --- a/crates/contracts/tests/asset_auth/unlock_failure_flows.rs +++ b/crates/contracts/tests/asset_auth/unlock_failure_flows.rs @@ -66,7 +66,10 @@ fn fails_to_unlock_when_auth_input_amount_is_invalid( let result = signer.finalize(&ft); - assert!(result.is_err(), "Must fail but it does not"); + assert!( + result.is_err(), + "expected finalize to fail, but it succeeded" + ); Ok(()) } diff --git a/crates/contracts/tests/asset_auth_vault.rs b/crates/contracts/tests/asset_auth_vault.rs new file mode 100644 index 0000000..5216d7d --- /dev/null +++ b/crates/contracts/tests/asset_auth_vault.rs @@ -0,0 +1,2 @@ +#[path = "asset_auth_vault/mod.rs"] +mod asset_auth_vault_tests; diff --git a/crates/contracts/tests/asset_auth_vault/final_supply_failure_flows.rs b/crates/contracts/tests/asset_auth_vault/final_supply_failure_flows.rs new file mode 100644 index 0000000..7d42d2e --- /dev/null +++ b/crates/contracts/tests/asset_auth_vault/final_supply_failure_flows.rs @@ -0,0 +1,239 @@ +use lending_contracts::programs::asset_auth_vault::{AssetAuthVault, AssetAuthVaultParameters}; +use lending_contracts::programs::program::SimplexProgram; + +use simplex::simplicityhl::elements::Script; +use simplex::transaction::{FinalTransaction, PartialInput, PartialOutput, RequiredSignature}; + +use super::setup::{final_supply, issue_auth_assets, prepare_vault_asset, setup_asset_auth_vault}; + +fn default_final_vault_supplying_setup( + context: &simplex::TestContext, + with_supplier_asset_burn: bool, +) -> anyhow::Result<(AssetAuthVault, AssetAuthVaultParameters)> { + let (supplier_auth_asset_id, keeper_auth_asset_id) = issue_auth_assets(context, 1, 1)?; + + let vault_asset_amount = 1_000_000; + let vault_asset_amounts = vec![5000]; + + let vault_asset_id = prepare_vault_asset(context, vault_asset_amount, vault_asset_amounts)?; + + let vault_parameters = AssetAuthVaultParameters::new( + vault_asset_id, + keeper_auth_asset_id, + supplier_auth_asset_id, + 1, + false, + with_supplier_asset_burn, + *context.get_network(), + ); + + let asset_auth_vault = setup_asset_auth_vault(context, vault_parameters)?; + + Ok((asset_auth_vault, vault_parameters)) +} + +#[simplex::test] +fn final_supply_fails_when_vault_already_finalized( + context: simplex::TestContext, +) -> anyhow::Result<()> { + let provider = context.get_default_provider(); + let signer = context.get_default_signer(); + + let (asset_auth_vault, vault_parameters) = + default_final_vault_supplying_setup(&context, false)?; + + let finalized_asset_auth_vault = final_supply(&context, &asset_auth_vault, 300)?; + + let asset_auth_vault_utxo = provider + .fetch_scripthash_utxos(&finalized_asset_auth_vault.get_script_pubkey())?[0] + .clone(); + + let utxo_to_supply = signer.get_utxos_asset(vault_parameters.vault_asset_id)?[0].clone(); + let amount_to_supply = utxo_to_supply.explicit_amount(); + + let supplier_auth_utxo = signer.get_utxos_asset(vault_parameters.supplier_asset_id)?[0].clone(); + + let mut ft = FinalTransaction::new(); + + ft.add_output(PartialOutput::new( + signer.get_address().script_pubkey(), + supplier_auth_utxo.explicit_amount(), + supplier_auth_utxo.explicit_asset(), + )); + ft.add_input( + PartialInput::new(supplier_auth_utxo), + RequiredSignature::NativeEcdsa, + ); + + finalized_asset_auth_vault.attach_final_supplying( + &mut ft, + asset_auth_vault_utxo, + 0, + 0, + amount_to_supply, + ); + + ft.add_input( + PartialInput::new(utxo_to_supply), + RequiredSignature::NativeEcdsa, + ); + + let result = signer.finalize(&ft); + + assert!( + result.is_err(), + "expected finalize to fail, but it succeeded" + ); + + Ok(()) +} + +#[simplex::test] +fn final_supply_fails_when_auth_utxo_is_invalid( + context: simplex::TestContext, +) -> anyhow::Result<()> { + let provider = context.get_default_provider(); + let signer = context.get_default_signer(); + + let (asset_auth_vault, vault_parameters) = + default_final_vault_supplying_setup(&context, false)?; + + let asset_auth_vault_utxo = + provider.fetch_scripthash_utxos(&asset_auth_vault.get_script_pubkey())?[0].clone(); + + let invalid_auth_index_pairs = [(0, 1), (1, 0), (1, 1)]; + + for auth_index_pair in invalid_auth_index_pairs { + let utxo_to_supply = signer.get_utxos_asset(vault_parameters.vault_asset_id)?[0].clone(); + let amount_to_supply = utxo_to_supply.explicit_amount(); + + let supplier_auth_utxo = + signer.get_utxos_asset(vault_parameters.supplier_asset_id)?[0].clone(); + + let mut ft = FinalTransaction::new(); + + ft.add_output(PartialOutput::new( + signer.get_address().script_pubkey(), + supplier_auth_utxo.explicit_amount(), + supplier_auth_utxo.explicit_asset(), + )); + ft.add_input( + PartialInput::new(supplier_auth_utxo), + RequiredSignature::NativeEcdsa, + ); + ft.add_input( + PartialInput::new(utxo_to_supply), + RequiredSignature::NativeEcdsa, + ); + + asset_auth_vault.attach_final_supplying( + &mut ft, + asset_auth_vault_utxo.clone(), + auth_index_pair.0, + auth_index_pair.1, + amount_to_supply, + ); + + let result = signer.finalize(&ft); + + assert!( + result.is_err(), + "expected finalize to fail, but it succeeded" + ); + } + + Ok(()) +} + +#[simplex::test] +fn final_supply_fails_when_auth_utxo_burned_but_burn_flag_was_not_set( + context: simplex::TestContext, +) -> anyhow::Result<()> { + let provider = context.get_default_provider(); + let signer = context.get_default_signer(); + + let (asset_auth_vault, vault_parameters) = + default_final_vault_supplying_setup(&context, false)?; + + let asset_auth_vault_utxo = + provider.fetch_scripthash_utxos(&asset_auth_vault.get_script_pubkey())?[0].clone(); + + let utxo_to_supply = signer.get_utxos_asset(vault_parameters.vault_asset_id)?[0].clone(); + let amount_to_supply = utxo_to_supply.explicit_amount(); + + let supplier_auth_utxo = signer.get_utxos_asset(vault_parameters.supplier_asset_id)?[0].clone(); + + let mut ft = FinalTransaction::new(); + + ft.add_output(PartialOutput::new( + Script::new_op_return(b"burn"), + supplier_auth_utxo.explicit_amount(), + supplier_auth_utxo.explicit_asset(), + )); + ft.add_input( + PartialInput::new(supplier_auth_utxo), + RequiredSignature::NativeEcdsa, + ); + + asset_auth_vault.attach_final_supplying(&mut ft, asset_auth_vault_utxo, 0, 0, amount_to_supply); + + ft.add_input( + PartialInput::new(utxo_to_supply), + RequiredSignature::NativeEcdsa, + ); + + let result = signer.finalize(&ft); + + assert!( + result.is_err(), + "expected finalize to fail, but it succeeded" + ); + + Ok(()) +} + +#[simplex::test] +fn final_supply_fails_when_auth_utxo_was_not_burned_but_burn_flag_was_set( + context: simplex::TestContext, +) -> anyhow::Result<()> { + let provider = context.get_default_provider(); + let signer = context.get_default_signer(); + + let (asset_auth_vault, vault_parameters) = default_final_vault_supplying_setup(&context, true)?; + + let asset_auth_vault_utxo = + provider.fetch_scripthash_utxos(&asset_auth_vault.get_script_pubkey())?[0].clone(); + + let utxo_to_supply = signer.get_utxos_asset(vault_parameters.vault_asset_id)?[0].clone(); + let amount_to_supply = utxo_to_supply.explicit_amount(); + + let supplier_auth_utxo = signer.get_utxos_asset(vault_parameters.supplier_asset_id)?[0].clone(); + + let mut ft = FinalTransaction::new(); + + ft.add_output(PartialOutput::new( + signer.get_address().script_pubkey(), + supplier_auth_utxo.explicit_amount(), + supplier_auth_utxo.explicit_asset(), + )); + ft.add_input( + PartialInput::new(supplier_auth_utxo), + RequiredSignature::NativeEcdsa, + ); + + asset_auth_vault.attach_final_supplying(&mut ft, asset_auth_vault_utxo, 0, 0, amount_to_supply); + + ft.add_input( + PartialInput::new(utxo_to_supply), + RequiredSignature::NativeEcdsa, + ); + + let result = signer.finalize(&ft); + + assert!( + result.is_err(), + "expected finalize to fail, but it succeeded" + ); + + Ok(()) +} diff --git a/crates/contracts/tests/asset_auth_vault/final_supply_success_flows.rs b/crates/contracts/tests/asset_auth_vault/final_supply_success_flows.rs new file mode 100644 index 0000000..9ca2560 --- /dev/null +++ b/crates/contracts/tests/asset_auth_vault/final_supply_success_flows.rs @@ -0,0 +1,198 @@ +use lending_contracts::programs::asset_auth_vault::{AssetAuthVault, AssetAuthVaultParameters}; +use lending_contracts::programs::program::SimplexProgram; + +use simplex::simplicityhl::elements::Script; +use simplex::transaction::{FinalTransaction, PartialInput, PartialOutput, RequiredSignature}; + +use super::common::tx_steps::finalize_and_broadcast; +use super::setup::{ + check_vault_amount, issue_auth_assets, make_confidential, prepare_vault_asset, + setup_asset_auth_vault, +}; + +fn default_final_vault_supplying_setup( + context: &simplex::TestContext, + with_supplier_asset_burn: bool, +) -> anyhow::Result<(AssetAuthVault, AssetAuthVaultParameters)> { + let (supplier_auth_asset_id, keeper_auth_asset_id) = issue_auth_assets(context, 1, 1)?; + + let vault_asset_amount = 1_000_000; + let vault_asset_amounts = vec![5000]; + + let vault_asset_id = prepare_vault_asset(context, vault_asset_amount, vault_asset_amounts)?; + + let vault_parameters = AssetAuthVaultParameters::new( + vault_asset_id, + keeper_auth_asset_id, + supplier_auth_asset_id, + 1, + false, + with_supplier_asset_burn, + *context.get_network(), + ); + + let asset_auth_vault = setup_asset_auth_vault(context, vault_parameters)?; + + Ok((asset_auth_vault, vault_parameters)) +} + +#[simplex::test] +fn final_supply_succeeds_with_explicit_input_without_auth_burn( + context: simplex::TestContext, +) -> anyhow::Result<()> { + let provider = context.get_default_provider(); + let signer = context.get_default_signer(); + + let (asset_auth_vault, vault_parameters) = + default_final_vault_supplying_setup(&context, false)?; + + let asset_auth_vault_utxo = + provider.fetch_scripthash_utxos(&asset_auth_vault.get_script_pubkey())?[0].clone(); + + let utxo_to_supply = signer.get_utxos_asset(vault_parameters.vault_asset_id)?[0].clone(); + let amount_to_supply = utxo_to_supply.explicit_amount(); + + let supplier_auth_utxo = signer.get_utxos_asset(vault_parameters.supplier_asset_id)?[0].clone(); + + let mut ft = FinalTransaction::new(); + + ft.add_output(PartialOutput::new( + signer.get_address().script_pubkey(), + supplier_auth_utxo.explicit_amount(), + supplier_auth_utxo.explicit_asset(), + )); + ft.add_input( + PartialInput::new(supplier_auth_utxo), + RequiredSignature::NativeEcdsa, + ); + + let expected_vault_balance = asset_auth_vault_utxo.explicit_amount() + amount_to_supply; + + let finalized_vault = asset_auth_vault.attach_final_supplying( + &mut ft, + asset_auth_vault_utxo, + 0, + 0, + amount_to_supply, + ); + + ft.add_input( + PartialInput::new(utxo_to_supply), + RequiredSignature::NativeEcdsa, + ); + + let txid = finalize_and_broadcast(&context, &ft)?; + provider.wait(&txid)?; + + check_vault_amount(&context, &finalized_vault, expected_vault_balance)?; + + Ok(()) +} + +#[simplex::test] +fn final_supply_succeeds_with_explicit_input_and_auth_burn( + context: simplex::TestContext, +) -> anyhow::Result<()> { + let provider = context.get_default_provider(); + let signer = context.get_default_signer(); + + let (asset_auth_vault, vault_parameters) = default_final_vault_supplying_setup(&context, true)?; + + let asset_auth_vault_utxo = + provider.fetch_scripthash_utxos(&asset_auth_vault.get_script_pubkey())?[0].clone(); + + let utxo_to_supply = signer.get_utxos_asset(vault_parameters.vault_asset_id)?[0].clone(); + let amount_to_supply = utxo_to_supply.explicit_amount(); + + let supplier_auth_utxo = signer.get_utxos_asset(vault_parameters.supplier_asset_id)?[0].clone(); + + let mut ft = FinalTransaction::new(); + + ft.add_output(PartialOutput::new( + Script::new_op_return(b"burn"), + supplier_auth_utxo.explicit_amount(), + supplier_auth_utxo.explicit_asset(), + )); + ft.add_input( + PartialInput::new(supplier_auth_utxo), + RequiredSignature::NativeEcdsa, + ); + + let expected_vault_balance = asset_auth_vault_utxo.explicit_amount() + amount_to_supply; + + let finalized_vault = asset_auth_vault.attach_final_supplying( + &mut ft, + asset_auth_vault_utxo, + 0, + 0, + amount_to_supply, + ); + + ft.add_input( + PartialInput::new(utxo_to_supply), + RequiredSignature::NativeEcdsa, + ); + + let txid = finalize_and_broadcast(&context, &ft)?; + provider.wait(&txid)?; + + check_vault_amount(&context, &finalized_vault, expected_vault_balance)?; + + Ok(()) +} + +#[simplex::test] +fn final_supply_succeeds_with_confidential_input( + context: simplex::TestContext, +) -> anyhow::Result<()> { + let provider = context.get_default_provider(); + let signer = context.get_default_signer(); + + let (asset_auth_vault, vault_parameters) = + default_final_vault_supplying_setup(&context, false)?; + + let asset_auth_vault_utxo = + provider.fetch_scripthash_utxos(&asset_auth_vault.get_script_pubkey())?[0].clone(); + + let utxo_to_supply = signer.get_utxos_asset(vault_parameters.vault_asset_id)?[0].clone(); + let amount_to_supply = utxo_to_supply.explicit_amount(); + + make_confidential(&context, utxo_to_supply)?; + + let conf_utxo_to_supply = signer.get_utxos_asset(vault_parameters.vault_asset_id)?[0].clone(); + let supplier_auth_utxo = signer.get_utxos_asset(vault_parameters.supplier_asset_id)?[0].clone(); + + let mut ft = FinalTransaction::new(); + + ft.add_input( + PartialInput::new(conf_utxo_to_supply), + RequiredSignature::NativeEcdsa, + ); + ft.add_input( + PartialInput::new(supplier_auth_utxo.clone()), + RequiredSignature::NativeEcdsa, + ); + + let expected_vault_balance = asset_auth_vault_utxo.explicit_amount() + amount_to_supply; + + let finalized_vault = asset_auth_vault.attach_final_supplying( + &mut ft, + asset_auth_vault_utxo, + 1, + 1, + amount_to_supply, + ); + + ft.add_output(PartialOutput::new( + signer.get_address().script_pubkey(), + supplier_auth_utxo.explicit_amount(), + supplier_auth_utxo.explicit_asset(), + )); + + let txid = finalize_and_broadcast(&context, &ft)?; + provider.wait(&txid)?; + + check_vault_amount(&context, &finalized_vault, expected_vault_balance)?; + + Ok(()) +} diff --git a/crates/contracts/tests/asset_auth_vault/mod.rs b/crates/contracts/tests/asset_auth_vault/mod.rs new file mode 100644 index 0000000..e70c502 --- /dev/null +++ b/crates/contracts/tests/asset_auth_vault/mod.rs @@ -0,0 +1,8 @@ +#[path = "../common/mod.rs"] +mod common; + +mod final_supply_failure_flows; +mod final_supply_success_flows; +mod setup; +mod supply_failure_flows; +mod supply_success_flows; diff --git a/crates/contracts/tests/asset_auth_vault/setup.rs b/crates/contracts/tests/asset_auth_vault/setup.rs new file mode 100644 index 0000000..268448b --- /dev/null +++ b/crates/contracts/tests/asset_auth_vault/setup.rs @@ -0,0 +1,224 @@ +use lending_contracts::programs::asset_auth_vault::{AssetAuthVault, AssetAuthVaultParameters}; + +use lending_contracts::programs::program::SimplexProgram; +use lending_contracts::utils::get_random_seed; +use simplex::simplicityhl::elements::AssetId; +use simplex::transaction::partial_input::IssuanceInput; +use simplex::transaction::{ + FinalTransaction, PartialInput, PartialOutput, RequiredSignature, UTXO, +}; + +use super::common::issuance::issue_asset; +use super::common::tx_steps::finalize_and_broadcast; +use super::common::wallet::{get_split_utxo_ft, split_first_signer_utxo}; + +pub(super) fn issue_auth_assets( + context: &simplex::TestContext, + supplier_auth_asset_amount: u64, + keeper_auth_asset_amount: u64, +) -> anyhow::Result<(AssetId, AssetId)> { + let provider = context.get_default_provider(); + let signer = context.get_default_signer(); + + let txid = split_first_signer_utxo(context, vec![1000, 5000, 10000]); + provider.wait(&txid)?; + + let policy_utxos = signer.get_utxos_asset(context.get_network().policy_asset())?; + + let first_utxo = policy_utxos[0].clone(); + let second_utxo = policy_utxos[1].clone(); + + let issuance_entropy = get_random_seed(); + + let mut ft = FinalTransaction::new(); + + let supplier_auth_issuance_details = ft.add_issuance_input( + PartialInput::new(first_utxo), + IssuanceInput::new_issuance(supplier_auth_asset_amount, 0, issuance_entropy), + RequiredSignature::NativeEcdsa, + ); + let keeper_auth_issuance_details = ft.add_issuance_input( + PartialInput::new(second_utxo), + IssuanceInput::new_issuance(keeper_auth_asset_amount, 0, issuance_entropy), + RequiredSignature::NativeEcdsa, + ); + + ft.add_output(PartialOutput::new( + signer.get_address().script_pubkey(), + supplier_auth_asset_amount, + supplier_auth_issuance_details.asset_id, + )); + ft.add_output(PartialOutput::new( + signer.get_address().script_pubkey(), + keeper_auth_asset_amount, + keeper_auth_issuance_details.asset_id, + )); + + let txid = finalize_and_broadcast(context, &ft)?; + provider.wait(&txid)?; + + Ok(( + supplier_auth_issuance_details.asset_id, + keeper_auth_issuance_details.asset_id, + )) +} + +pub(super) fn prepare_vault_asset( + context: &simplex::TestContext, + total_vault_asset_amount: u64, + split_amounts: Vec, +) -> anyhow::Result { + let provider = context.get_default_provider(); + let signer = context.get_default_signer(); + + let (txid, vault_asset_id) = issue_asset(context, total_vault_asset_amount)?; + provider.wait(&txid)?; + + let vault_asset_utxo = signer.get_utxos_asset(vault_asset_id)?[0].clone(); + + let ft = get_split_utxo_ft( + vault_asset_utxo, + split_amounts, + signer, + *context.get_network(), + ); + + let txid = finalize_and_broadcast(context, &ft)?; + provider.wait(&txid)?; + + Ok(vault_asset_id) +} + +pub(super) fn make_confidential( + context: &simplex::TestContext, + asset_utxo: UTXO, +) -> anyhow::Result<()> { + let provider = context.get_default_provider(); + let signer = context.get_default_signer(); + + let mut ft = FinalTransaction::new(); + + ft.add_output( + PartialOutput::new( + signer.get_confidential_address().script_pubkey(), + asset_utxo.explicit_amount(), + asset_utxo.explicit_asset(), + ) + .with_blinding_key(signer.get_blinding_public_key()), + ); + ft.add_input( + PartialInput::new(asset_utxo), + RequiredSignature::NativeEcdsa, + ); + + let txid = finalize_and_broadcast(context, &ft)?; + provider.wait(&txid)?; + + Ok(()) +} + +pub(super) fn setup_asset_auth_vault( + context: &simplex::TestContext, + vault_parameters: AssetAuthVaultParameters, +) -> anyhow::Result { + let provider = context.get_default_provider(); + let signer = context.get_default_signer(); + + let vault_asset_utxo = signer.get_utxos_asset(vault_parameters.vault_asset_id)?[0].clone(); + let vault_asset_amount = vault_asset_utxo.explicit_amount(); + + let mut ft = FinalTransaction::new(); + + ft.add_input( + PartialInput::new(vault_asset_utxo), + RequiredSignature::NativeEcdsa, + ); + + let asset_auth_vault = AssetAuthVault::new_active(vault_parameters); + + asset_auth_vault.attach_creation(&mut ft, vault_asset_amount); + + let txid = finalize_and_broadcast(context, &ft)?; + provider.wait(&txid)?; + + let asset_auth_vault_utxo = + provider.fetch_scripthash_utxos(&asset_auth_vault.get_script_pubkey())?[0].clone(); + + assert_eq!(asset_auth_vault_utxo.explicit_amount(), vault_asset_amount); + + Ok(asset_auth_vault) +} + +pub(super) fn final_supply( + context: &simplex::TestContext, + asset_auth_vault: &AssetAuthVault, + amount_to_supply: u64, +) -> anyhow::Result { + let provider = context.get_default_provider(); + let signer = context.get_default_signer(); + + let vault_parameters = *asset_auth_vault.get_parameters(); + + let asset_auth_vault_utxo = + provider.fetch_scripthash_utxos(&asset_auth_vault.get_script_pubkey())?[0].clone(); + + let utxo_to_supply = signer.get_utxos_asset(vault_parameters.vault_asset_id)?[0].clone(); + let vault_asset_utxo_amount = utxo_to_supply.explicit_amount(); + + assert!(vault_asset_utxo_amount >= amount_to_supply); + + let supplier_auth_utxo = signer.get_utxos_asset(vault_parameters.supplier_asset_id)?[0].clone(); + + let mut ft = FinalTransaction::new(); + + ft.add_input( + PartialInput::new(supplier_auth_utxo.clone()), + RequiredSignature::NativeEcdsa, + ); + ft.add_input( + PartialInput::new(utxo_to_supply), + RequiredSignature::NativeEcdsa, + ); + + let finalized_vault = asset_auth_vault.attach_final_supplying( + &mut ft, + asset_auth_vault_utxo, + 0, + 1, + amount_to_supply, + ); + + ft.add_output(PartialOutput::new( + signer.get_address().script_pubkey(), + supplier_auth_utxo.explicit_amount(), + supplier_auth_utxo.explicit_asset(), + )); + + if vault_asset_utxo_amount > amount_to_supply { + ft.add_output(PartialOutput::new( + signer.get_address().script_pubkey(), + vault_asset_utxo_amount - amount_to_supply, + vault_parameters.vault_asset_id, + )); + } + + let txid = finalize_and_broadcast(context, &ft)?; + provider.wait(&txid)?; + + Ok(finalized_vault) +} + +pub(super) fn check_vault_amount( + context: &simplex::TestContext, + vault: &AssetAuthVault, + expected_amount: u64, +) -> anyhow::Result<()> { + let provider = context.get_default_provider(); + + let asset_auth_vault_utxo = + provider.fetch_scripthash_utxos(&vault.get_script_pubkey())?[0].clone(); + + assert_eq!(asset_auth_vault_utxo.explicit_amount(), expected_amount); + + Ok(()) +} diff --git a/crates/contracts/tests/asset_auth_vault/supply_failure_flows.rs b/crates/contracts/tests/asset_auth_vault/supply_failure_flows.rs new file mode 100644 index 0000000..220c66a --- /dev/null +++ b/crates/contracts/tests/asset_auth_vault/supply_failure_flows.rs @@ -0,0 +1,185 @@ +use lending_contracts::programs::asset_auth_vault::{AssetAuthVault, AssetAuthVaultParameters}; +use lending_contracts::programs::program::SimplexProgram; + +use simplex::simplicityhl::elements::Script; +use simplex::transaction::{FinalTransaction, PartialInput, PartialOutput, RequiredSignature}; + +use super::setup::{final_supply, issue_auth_assets, prepare_vault_asset, setup_asset_auth_vault}; + +fn default_vault_supplying_setup( + context: &simplex::TestContext, +) -> anyhow::Result<(AssetAuthVault, AssetAuthVaultParameters)> { + let (supplier_auth_asset_id, keeper_auth_asset_id) = issue_auth_assets(context, 1, 1)?; + + let vault_asset_amount = 1_000_000; + let vault_asset_amounts = vec![5000]; + + let vault_asset_id = prepare_vault_asset(context, vault_asset_amount, vault_asset_amounts)?; + + let vault_parameters = AssetAuthVaultParameters::new( + vault_asset_id, + keeper_auth_asset_id, + supplier_auth_asset_id, + 1, + false, + false, + *context.get_network(), + ); + + let asset_auth_vault = setup_asset_auth_vault(context, vault_parameters)?; + + Ok((asset_auth_vault, vault_parameters)) +} + +#[simplex::test] +fn fails_to_supply_to_finalized_vault(context: simplex::TestContext) -> anyhow::Result<()> { + let provider = context.get_default_provider(); + let signer = context.get_default_signer(); + + let (asset_auth_vault, vault_parameters) = default_vault_supplying_setup(&context)?; + + let finalized_asset_auth_vault = final_supply(&context, &asset_auth_vault, 300)?; + + let asset_auth_vault_utxo = provider + .fetch_scripthash_utxos(&finalized_asset_auth_vault.get_script_pubkey())?[0] + .clone(); + + let utxo_to_supply = signer.get_utxos_asset(vault_parameters.vault_asset_id)?[0].clone(); + let amount_to_supply = utxo_to_supply.explicit_amount(); + + let supplier_auth_utxo = signer.get_utxos_asset(vault_parameters.supplier_asset_id)?[0].clone(); + + let mut ft = FinalTransaction::new(); + + ft.add_output(PartialOutput::new( + signer.get_address().script_pubkey(), + supplier_auth_utxo.explicit_amount(), + supplier_auth_utxo.explicit_asset(), + )); + ft.add_input( + PartialInput::new(supplier_auth_utxo), + RequiredSignature::NativeEcdsa, + ); + + finalized_asset_auth_vault.attach_supplying( + &mut ft, + asset_auth_vault_utxo, + 0, + 0, + amount_to_supply, + ); + + ft.add_input( + PartialInput::new(utxo_to_supply), + RequiredSignature::NativeEcdsa, + ); + + let result = signer.finalize(&ft); + + assert!( + result.is_err(), + "expected finalize to fail, but it succeeded" + ); + + Ok(()) +} + +#[simplex::test] +fn fails_to_supply_when_auth_utxo_is_invalid(context: simplex::TestContext) -> anyhow::Result<()> { + let provider = context.get_default_provider(); + let signer = context.get_default_signer(); + + let (asset_auth_vault, vault_parameters) = default_vault_supplying_setup(&context)?; + + let asset_auth_vault_utxo = + provider.fetch_scripthash_utxos(&asset_auth_vault.get_script_pubkey())?[0].clone(); + + let invalid_auth_index_pairs = [(0, 1), (1, 0), (1, 1)]; + + for auth_index_pair in invalid_auth_index_pairs { + let utxo_to_supply = signer.get_utxos_asset(vault_parameters.vault_asset_id)?[0].clone(); + let amount_to_supply = utxo_to_supply.explicit_amount(); + + let supplier_auth_utxo = + signer.get_utxos_asset(vault_parameters.supplier_asset_id)?[0].clone(); + + let mut ft = FinalTransaction::new(); + + ft.add_output(PartialOutput::new( + signer.get_address().script_pubkey(), + supplier_auth_utxo.explicit_amount(), + supplier_auth_utxo.explicit_asset(), + )); + ft.add_input( + PartialInput::new(supplier_auth_utxo), + RequiredSignature::NativeEcdsa, + ); + ft.add_input( + PartialInput::new(utxo_to_supply), + RequiredSignature::NativeEcdsa, + ); + + asset_auth_vault.attach_supplying( + &mut ft, + asset_auth_vault_utxo.clone(), + auth_index_pair.0, + auth_index_pair.1, + amount_to_supply, + ); + + let result = signer.finalize(&ft); + + assert!( + result.is_err(), + "expected finalize to fail, but it succeeded" + ); + } + + Ok(()) +} + +#[simplex::test] +fn fails_to_supply_when_auth_utxo_burned_but_burn_flag_was_not_set( + context: simplex::TestContext, +) -> anyhow::Result<()> { + let provider = context.get_default_provider(); + let signer = context.get_default_signer(); + + let (asset_auth_vault, vault_parameters) = default_vault_supplying_setup(&context)?; + + let asset_auth_vault_utxo = + provider.fetch_scripthash_utxos(&asset_auth_vault.get_script_pubkey())?[0].clone(); + + let utxo_to_supply = signer.get_utxos_asset(vault_parameters.vault_asset_id)?[0].clone(); + let amount_to_supply = utxo_to_supply.explicit_amount(); + + let supplier_auth_utxo = signer.get_utxos_asset(vault_parameters.supplier_asset_id)?[0].clone(); + + let mut ft = FinalTransaction::new(); + + ft.add_output(PartialOutput::new( + Script::new_op_return(b"burn"), + supplier_auth_utxo.explicit_amount(), + supplier_auth_utxo.explicit_asset(), + )); + ft.add_input( + PartialInput::new(supplier_auth_utxo), + RequiredSignature::NativeEcdsa, + ); + + asset_auth_vault.attach_supplying(&mut ft, asset_auth_vault_utxo, 0, 0, amount_to_supply); + + ft.add_input( + PartialInput::new(utxo_to_supply), + RequiredSignature::NativeEcdsa, + ); + + let result = signer.finalize(&ft); + + assert!( + result.is_err(), + "expected finalize to fail, but it succeeded" + ); + + Ok(()) +} diff --git a/crates/contracts/tests/asset_auth_vault/supply_success_flows.rs b/crates/contracts/tests/asset_auth_vault/supply_success_flows.rs new file mode 100644 index 0000000..7b36354 --- /dev/null +++ b/crates/contracts/tests/asset_auth_vault/supply_success_flows.rs @@ -0,0 +1,188 @@ +use lending_contracts::programs::asset_auth_vault::{AssetAuthVault, AssetAuthVaultParameters}; +use lending_contracts::programs::program::SimplexProgram; + +use simplex::transaction::{FinalTransaction, PartialInput, PartialOutput, RequiredSignature}; + +use super::common::tx_steps::finalize_and_broadcast; +use super::setup::{ + check_vault_amount, issue_auth_assets, make_confidential, prepare_vault_asset, + setup_asset_auth_vault, +}; + +fn default_vault_supplying_setup( + context: &simplex::TestContext, + vault_asset_amounts: Vec, +) -> anyhow::Result<(AssetAuthVault, AssetAuthVaultParameters)> { + let (supplier_auth_asset_id, keeper_auth_asset_id) = issue_auth_assets(context, 1, 1)?; + + let vault_asset_amount = 1_000_000; + let vault_asset_id = prepare_vault_asset(context, vault_asset_amount, vault_asset_amounts)?; + + let vault_parameters = AssetAuthVaultParameters::new( + vault_asset_id, + keeper_auth_asset_id, + supplier_auth_asset_id, + 1, + false, + false, + *context.get_network(), + ); + + let asset_auth_vault = setup_asset_auth_vault(context, vault_parameters)?; + + Ok((asset_auth_vault, vault_parameters)) +} + +#[simplex::test] +fn supplies_to_vault_with_explicit_input(context: simplex::TestContext) -> anyhow::Result<()> { + let provider = context.get_default_provider(); + let signer = context.get_default_signer(); + + let (asset_auth_vault, vault_parameters) = default_vault_supplying_setup(&context, vec![5000])?; + + let asset_auth_vault_utxo = + provider.fetch_scripthash_utxos(&asset_auth_vault.get_script_pubkey())?[0].clone(); + + let utxo_to_supply = signer.get_utxos_asset(vault_parameters.vault_asset_id)?[0].clone(); + let amount_to_supply = utxo_to_supply.explicit_amount(); + + let supplier_auth_utxo = signer.get_utxos_asset(vault_parameters.supplier_asset_id)?[0].clone(); + + let mut ft = FinalTransaction::new(); + + ft.add_output(PartialOutput::new( + signer.get_address().script_pubkey(), + supplier_auth_utxo.explicit_amount(), + supplier_auth_utxo.explicit_asset(), + )); + ft.add_input( + PartialInput::new(supplier_auth_utxo), + RequiredSignature::NativeEcdsa, + ); + + let expected_vault_balance = asset_auth_vault_utxo.explicit_amount() + amount_to_supply; + + asset_auth_vault.attach_supplying(&mut ft, asset_auth_vault_utxo, 0, 0, amount_to_supply); + + ft.add_input( + PartialInput::new(utxo_to_supply), + RequiredSignature::NativeEcdsa, + ); + + let txid = finalize_and_broadcast(&context, &ft)?; + provider.wait(&txid)?; + + check_vault_amount(&context, &asset_auth_vault, expected_vault_balance)?; + + Ok(()) +} + +#[simplex::test] +fn supplies_to_vault_with_several_explicit_inputs( + context: simplex::TestContext, +) -> anyhow::Result<()> { + let provider = context.get_default_provider(); + let signer = context.get_default_signer(); + + let (asset_auth_vault, vault_parameters) = + default_vault_supplying_setup(&context, vec![5000, 10000])?; + + let asset_auth_vault_utxo = + provider.fetch_scripthash_utxos(&asset_auth_vault.get_script_pubkey())?[0].clone(); + + let utxos_to_supply = signer.get_utxos_asset(vault_parameters.vault_asset_id)?; + let first_utxo_to_supply = utxos_to_supply[0].clone(); + let second_utxo_to_supply = utxos_to_supply[1].clone(); + + let change_amount = 300; + let total_inputs_vault_asset_amount = + first_utxo_to_supply.explicit_amount() + second_utxo_to_supply.explicit_amount(); + let amount_to_supply = total_inputs_vault_asset_amount - change_amount; + + let supplier_auth_utxo = signer.get_utxos_asset(vault_parameters.supplier_asset_id)?[0].clone(); + + let mut ft = FinalTransaction::new(); + + ft.add_input( + PartialInput::new(supplier_auth_utxo.clone()), + RequiredSignature::NativeEcdsa, + ); + ft.add_input( + PartialInput::new(first_utxo_to_supply), + RequiredSignature::NativeEcdsa, + ); + ft.add_input( + PartialInput::new(second_utxo_to_supply), + RequiredSignature::NativeEcdsa, + ); + + ft.add_output(PartialOutput::new( + signer.get_address().script_pubkey(), + supplier_auth_utxo.explicit_amount(), + supplier_auth_utxo.explicit_asset(), + )); + + let expected_vault_balance = asset_auth_vault_utxo.explicit_amount() + amount_to_supply; + + asset_auth_vault.attach_supplying(&mut ft, asset_auth_vault_utxo, 0, 0, amount_to_supply); + + ft.add_output(PartialOutput::new( + signer.get_address().script_pubkey(), + change_amount, + vault_parameters.vault_asset_id, + )); + + let txid = finalize_and_broadcast(&context, &ft)?; + provider.wait(&txid)?; + + check_vault_amount(&context, &asset_auth_vault, expected_vault_balance)?; + + Ok(()) +} + +#[simplex::test] +fn supplies_to_vault_with_confidential_input(context: simplex::TestContext) -> anyhow::Result<()> { + let provider = context.get_default_provider(); + let signer = context.get_default_signer(); + + let (asset_auth_vault, vault_parameters) = default_vault_supplying_setup(&context, vec![5000])?; + + let asset_auth_vault_utxo = + provider.fetch_scripthash_utxos(&asset_auth_vault.get_script_pubkey())?[0].clone(); + + let utxo_to_supply = signer.get_utxos_asset(vault_parameters.vault_asset_id)?[0].clone(); + let amount_to_supply = utxo_to_supply.explicit_amount(); + + make_confidential(&context, utxo_to_supply)?; + + let conf_utxo_to_supply = signer.get_utxos_asset(vault_parameters.vault_asset_id)?[0].clone(); + let supplier_auth_utxo = signer.get_utxos_asset(vault_parameters.supplier_asset_id)?[0].clone(); + + let mut ft = FinalTransaction::new(); + + ft.add_input( + PartialInput::new(conf_utxo_to_supply), + RequiredSignature::NativeEcdsa, + ); + ft.add_input( + PartialInput::new(supplier_auth_utxo.clone()), + RequiredSignature::NativeEcdsa, + ); + + let expected_vault_balance = asset_auth_vault_utxo.explicit_amount() + amount_to_supply; + + asset_auth_vault.attach_supplying(&mut ft, asset_auth_vault_utxo, 1, 1, amount_to_supply); + + ft.add_output(PartialOutput::new( + signer.get_address().script_pubkey(), + supplier_auth_utxo.explicit_amount(), + supplier_auth_utxo.explicit_asset(), + )); + + let txid = finalize_and_broadcast(&context, &ft)?; + provider.wait(&txid)?; + + check_vault_amount(&context, &asset_auth_vault, expected_vault_balance)?; + + Ok(()) +} diff --git a/crates/contracts/tests/issuance_factory/issue_assets_failure_flows.rs b/crates/contracts/tests/issuance_factory/issue_assets_failure_flows.rs index 9ac53d2..df53005 100644 --- a/crates/contracts/tests/issuance_factory/issue_assets_failure_flows.rs +++ b/crates/contracts/tests/issuance_factory/issue_assets_failure_flows.rs @@ -51,7 +51,10 @@ fn fails_to_issue_wrong_assets_number(context: simplex::TestContext) -> anyhow:: let result = signer.finalize(&ft); - assert!(result.is_err(), "Must fail but it does not"); + assert!( + result.is_err(), + "expected finalize to fail, but it succeeded" + ); Ok(()) } @@ -112,7 +115,10 @@ fn fails_to_issue_assets_with_reissuance_tokens( let result = signer.finalize(&ft); - assert!(result.is_err(), "Must fail but it does not"); + assert!( + result.is_err(), + "expected finalize to fail, but it succeeded" + ); Ok(()) } @@ -164,7 +170,10 @@ fn fails_to_issue_assets_without_reissuance_tokens( let result = signer.finalize(&ft); - assert!(result.is_err(), "Must fail but it does not"); + assert!( + result.is_err(), + "expected finalize to fail, but it succeeded" + ); Ok(()) } diff --git a/crates/contracts/tests/issuance_factory/remove_factory_failure_flows.rs b/crates/contracts/tests/issuance_factory/remove_factory_failure_flows.rs index a7b77ab..d50f8e4 100644 --- a/crates/contracts/tests/issuance_factory/remove_factory_failure_flows.rs +++ b/crates/contracts/tests/issuance_factory/remove_factory_failure_flows.rs @@ -23,7 +23,10 @@ fn fails_to_remove_issuance_factory_with_invalid_signer( let result = random_signer.finalize(&ft); - assert!(result.is_err(), "Must fail but it does not"); + assert!( + result.is_err(), + "expected finalize to fail, but it succeeded" + ); Ok(()) } From 78de7f86b26f6ee509ca77caa6793035fad92598 Mon Sep 17 00:00:00 2001 From: Oleh Komendant Date: Tue, 5 May 2026 17:35:28 +0300 Subject: [PATCH 4/6] Refactor AssetAuthVault programs and parameters --- crates/contracts/simf/asset_auth_vault.simf | 12 +- .../src/programs/asset_auth_vault/core.rs | 122 ++++++++----- .../src/programs/asset_auth_vault/mod.rs | 4 +- .../src/programs/asset_auth_vault/params.rs | 167 +++++++++++++----- .../final_supply_failure_flows.rs | 33 ++-- .../final_supply_success_flows.rs | 25 +-- .../contracts/tests/asset_auth_vault/setup.rs | 16 +- .../asset_auth_vault/supply_failure_flows.rs | 35 ++-- .../asset_auth_vault/supply_success_flows.rs | 27 +-- 9 files changed, 272 insertions(+), 169 deletions(-) diff --git a/crates/contracts/simf/asset_auth_vault.simf b/crates/contracts/simf/asset_auth_vault.simf index 7b99535..2493ef0 100644 --- a/crates/contracts/simf/asset_auth_vault.simf +++ b/crates/contracts/simf/asset_auth_vault.simf @@ -59,8 +59,8 @@ fn ensure_asset_and_amount_ge(index: u32, is_input_index: bool, expected_asset_b assert!(jet::le_64(expected_amount, amount)); } -fn ensure_finalization(expected_state: bool) { - assert!(jet::eq_1(::into(expected_state), ::into(param::IS_FINALIZED))); +fn ensure_active_status(expected_status: bool) { + assert!(jet::eq_1(::into(expected_status), ::into(param::IS_ACTIVE))); } fn ensure_output_script_hash(index: u32, expected_script_hash: u256) { @@ -108,7 +108,7 @@ fn auth_with_burn_check( // Main paths logic fn withdraw_all(input_keeper_index: u32, output_keeper_index: u32) { - ensure_finalization(true); + ensure_active_status(false); auth_with_burn_check( input_keeper_index, @@ -122,7 +122,7 @@ fn withdraw_all(input_keeper_index: u32, output_keeper_index: u32) { } fn withdraw_part(input_keeper_index: u32, output_keeper_index: u32, vault_output_index: u32, amount_to_withdraw: u64) { - ensure_finalization(false); + ensure_active_status(true); auth_with_burn_check( input_keeper_index, @@ -145,7 +145,7 @@ fn withdraw_part(input_keeper_index: u32, output_keeper_index: u32, vault_output } fn supply(input_supplier_index: u32, output_supplier_index: u32, vault_output_index: u32, amount_to_supply: u64) { - ensure_finalization(false); + ensure_active_status(true); auth_with_burn_check( input_supplier_index, @@ -171,7 +171,7 @@ fn final_supply( finalized_vault_output_index: u32, amount_to_supply: u64 ) { - ensure_finalization(false); + ensure_active_status(true); auth_with_burn_check( input_supplier_index, diff --git a/crates/contracts/src/programs/asset_auth_vault/core.rs b/crates/contracts/src/programs/asset_auth_vault/core.rs index 84eb5d9..6f9df34 100644 --- a/crates/contracts/src/programs/asset_auth_vault/core.rs +++ b/crates/contracts/src/programs/asset_auth_vault/core.rs @@ -5,64 +5,47 @@ use simplex::{ }; use crate::artifacts::asset_auth_vault::AssetAuthVaultProgram; -use crate::programs::asset_auth_vault::{AssetAuthVaultParameters, AssetAuthVaultWitnessBranch}; +use crate::programs::asset_auth_vault::{ + ActiveAssetAuthVaultParameters, AssetAuthVaultWitnessBranch, FinalizedAssetAuthVaultParameters, +}; use crate::programs::program::SimplexProgram; -pub struct AssetAuthVault { +pub struct ActiveAssetAuthVault { program: AssetAuthVaultProgram, - parameters: AssetAuthVaultParameters, + parameters: ActiveAssetAuthVaultParameters, } -impl AssetAuthVault { - pub fn new_active(parameters: AssetAuthVaultParameters) -> Self { - let finalized_vault = Self::new_finalized(parameters); - let finalized_vault_hash = finalized_vault.get_script_hash(); - - let parameters = parameters.from_finalized_parameters(finalized_vault_hash); +pub struct FinalizedAssetAuthVault { + program: AssetAuthVaultProgram, + parameters: FinalizedAssetAuthVaultParameters, +} +impl ActiveAssetAuthVault { + pub fn new(parameters: ActiveAssetAuthVaultParameters) -> Self { Self { program: AssetAuthVaultProgram::new(parameters.build_arguments()), parameters, } } - pub fn new_finalized(parameters: AssetAuthVaultParameters) -> Self { - assert!( - parameters.is_finalized(), - "Unable to create AssetAuthVault with non finalized parameters" + pub fn from_finalized_vault(parameters: FinalizedAssetAuthVaultParameters) -> Self { + let finalized_vault = FinalizedAssetAuthVault::new(parameters); + let finalized_vault_hash = finalized_vault.get_script_hash(); + + let active_vault_parameters = ActiveAssetAuthVaultParameters::from_finalized_parameters( + ¶meters, + finalized_vault_hash, ); - Self { - program: AssetAuthVaultProgram::new(parameters.build_arguments()), - parameters, - } + Self::new(active_vault_parameters) } - pub fn get_parameters(&self) -> &AssetAuthVaultParameters { + pub fn get_parameters(&self) -> &ActiveAssetAuthVaultParameters { &self.parameters } - pub fn attach_creation(&self, ft: &mut FinalTransaction, initial_asset_amount: u64) { - self.add_program_output(ft, self.parameters.vault_asset_id, initial_asset_amount); - } - - pub fn attach_withdrawing_all( - &self, - ft: &mut FinalTransaction, - program_utxo: UTXO, - input_keeper_index: u32, - output_keeper_index: u32, - ) { - let withdraw_all_witness_branch = AssetAuthVaultWitnessBranch::WithdrawAll { - input_keeper_index, - output_keeper_index, - }; - - self.add_program_input( - ft, - program_utxo, - withdraw_all_witness_branch.build_witness(), - ); + pub fn attach_creation(&self, ft: &mut FinalTransaction, vault_asset_amount: u64) { + self.add_program_output(ft, self.parameters.vault_asset_id, vault_asset_amount); } pub fn attach_partial_withdrawing( @@ -80,8 +63,6 @@ impl AssetAuthVault { "Invalid amount to withdraw" ); - let vault_change = current_vault_amount - amount_to_withdraw; - let vault_output_index = ft.n_outputs() as u32; let withdraw_part_witness_branch = AssetAuthVaultWitnessBranch::WithdrawPart { @@ -97,7 +78,11 @@ impl AssetAuthVault { withdraw_part_witness_branch.build_witness(), ); - self.add_program_output(ft, self.parameters.vault_asset_id, vault_change); + self.add_program_output( + ft, + self.parameters.vault_asset_id, + current_vault_amount - amount_to_withdraw, + ); } pub fn attach_supplying( @@ -133,7 +118,7 @@ impl AssetAuthVault { input_supplier_index: u32, output_supplier_index: u32, amount_to_supply: u64, - ) -> AssetAuthVault { + ) -> FinalizedAssetAuthVault { assert!(amount_to_supply > 0, "Zero amount to supply"); let new_vault_amount = program_utxo.explicit_amount() + amount_to_supply; @@ -149,8 +134,7 @@ impl AssetAuthVault { self.add_program_input(ft, program_utxo, supply_witness_branch.build_witness()); - let finalized_vault = - AssetAuthVault::new_finalized(self.parameters.to_finalized_parameters()); + let finalized_vault = FinalizedAssetAuthVault::new(self.parameters.into()); finalized_vault.attach_creation(ft, new_vault_amount); @@ -158,7 +142,53 @@ impl AssetAuthVault { } } -impl SimplexProgram for AssetAuthVault { +impl FinalizedAssetAuthVault { + pub fn new(parameters: FinalizedAssetAuthVaultParameters) -> Self { + Self { + program: AssetAuthVaultProgram::new(parameters.build_arguments()), + parameters, + } + } + + pub fn get_parameters(&self) -> &FinalizedAssetAuthVaultParameters { + &self.parameters + } + + pub fn attach_creation(&self, ft: &mut FinalTransaction, vault_asset_amount: u64) { + self.add_program_output(ft, self.parameters.vault_asset_id, vault_asset_amount); + } + + pub fn attach_withdrawing_all( + &self, + ft: &mut FinalTransaction, + program_utxo: UTXO, + input_keeper_index: u32, + output_keeper_index: u32, + ) { + let withdraw_all_witness_branch = AssetAuthVaultWitnessBranch::WithdrawAll { + input_keeper_index, + output_keeper_index, + }; + + self.add_program_input( + ft, + program_utxo, + withdraw_all_witness_branch.build_witness(), + ); + } +} + +impl SimplexProgram for ActiveAssetAuthVault { + fn get_program(&self) -> &Program { + self.program.as_ref() + } + + fn get_network(&self) -> &SimplicityNetwork { + &self.parameters.network + } +} + +impl SimplexProgram for FinalizedAssetAuthVault { fn get_program(&self) -> &Program { self.program.as_ref() } diff --git a/crates/contracts/src/programs/asset_auth_vault/mod.rs b/crates/contracts/src/programs/asset_auth_vault/mod.rs index f021dc1..2db5508 100644 --- a/crates/contracts/src/programs/asset_auth_vault/mod.rs +++ b/crates/contracts/src/programs/asset_auth_vault/mod.rs @@ -2,6 +2,6 @@ mod core; mod params; mod witness; -pub use core::AssetAuthVault; -pub use params::AssetAuthVaultParameters; +pub use core::{ActiveAssetAuthVault, FinalizedAssetAuthVault}; +pub use params::{ActiveAssetAuthVaultParameters, FinalizedAssetAuthVaultParameters}; pub use witness::AssetAuthVaultWitnessBranch; diff --git a/crates/contracts/src/programs/asset_auth_vault/params.rs b/crates/contracts/src/programs/asset_auth_vault/params.rs index 25a750c..4cad408 100644 --- a/crates/contracts/src/programs/asset_auth_vault/params.rs +++ b/crates/contracts/src/programs/asset_auth_vault/params.rs @@ -3,64 +3,59 @@ use simplex::{provider::SimplicityNetwork, simplicityhl::elements::AssetId}; use crate::artifacts::asset_auth_vault::derived_asset_auth_vault::AssetAuthVaultArguments; #[derive(Debug, Clone, Copy)] -pub struct AssetAuthVaultParameters { +pub struct ActiveAssetAuthVaultParameters { pub vault_asset_id: AssetId, pub keeper_asset_id: AssetId, pub supplier_asset_id: AssetId, + pub finalized_vault_cov_hash: [u8; 32], pub keeper_min_asset_amount: u64, pub with_keeper_asset_burn: bool, pub with_supplier_asset_burn: bool, pub network: SimplicityNetwork, - finalized_vault_cov_hash: [u8; 32], } -impl AssetAuthVaultParameters { - pub fn new( - vault_asset_id: AssetId, - keeper_asset_id: AssetId, - supplier_asset_id: AssetId, - keeper_min_asset_amount: u64, - with_keeper_asset_burn: bool, - with_supplier_asset_burn: bool, - network: SimplicityNetwork, - ) -> Self { +#[derive(Debug, Clone, Copy)] +pub struct FinalizedAssetAuthVaultParameters { + pub vault_asset_id: AssetId, + pub keeper_asset_id: AssetId, + pub supplier_asset_id: AssetId, + pub keeper_min_asset_amount: u64, + pub with_keeper_asset_burn: bool, + pub with_supplier_asset_burn: bool, + pub network: SimplicityNetwork, +} + +impl From for FinalizedAssetAuthVaultParameters { + fn from(value: ActiveAssetAuthVaultParameters) -> Self { Self { - vault_asset_id, - keeper_asset_id, - supplier_asset_id, - keeper_min_asset_amount, - with_keeper_asset_burn, - with_supplier_asset_burn, - network, - finalized_vault_cov_hash: [0u8; 32], + vault_asset_id: value.vault_asset_id, + keeper_asset_id: value.keeper_asset_id, + supplier_asset_id: value.supplier_asset_id, + keeper_min_asset_amount: value.keeper_min_asset_amount, + with_keeper_asset_burn: value.with_keeper_asset_burn, + with_supplier_asset_burn: value.with_supplier_asset_burn, + network: value.network, } } +} - pub fn from_finalized_parameters(self, finalized_vault_cov_hash: [u8; 32]) -> Self { +impl ActiveAssetAuthVaultParameters { + pub fn from_finalized_parameters( + parameters: &FinalizedAssetAuthVaultParameters, + finalized_vault_hash: [u8; 32], + ) -> Self { Self { - vault_asset_id: self.vault_asset_id, - keeper_asset_id: self.keeper_asset_id, - supplier_asset_id: self.supplier_asset_id, - keeper_min_asset_amount: self.keeper_min_asset_amount, - with_keeper_asset_burn: self.with_keeper_asset_burn, - with_supplier_asset_burn: self.with_supplier_asset_burn, - network: self.network, - finalized_vault_cov_hash, + vault_asset_id: parameters.vault_asset_id, + keeper_asset_id: parameters.keeper_asset_id, + supplier_asset_id: parameters.supplier_asset_id, + finalized_vault_cov_hash: finalized_vault_hash, + keeper_min_asset_amount: parameters.keeper_min_asset_amount, + with_keeper_asset_burn: parameters.with_keeper_asset_burn, + with_supplier_asset_burn: parameters.with_supplier_asset_burn, + network: parameters.network, } } - pub fn to_finalized_parameters(self) -> Self { - Self::new( - self.vault_asset_id, - self.keeper_asset_id, - self.supplier_asset_id, - self.keeper_min_asset_amount, - self.with_keeper_asset_burn, - self.with_supplier_asset_burn, - self.network, - ) - } - pub fn build_arguments(&self) -> AssetAuthVaultArguments { AssetAuthVaultArguments { vault_asset_id: self.vault_asset_id.into_inner().0, @@ -68,17 +63,93 @@ impl AssetAuthVaultParameters { supplier_auth_asset_id: self.supplier_asset_id.into_inner().0, keeper_auth_asset_amount: self.keeper_min_asset_amount, finalized_vault_cov_hash: self.finalized_vault_cov_hash, - is_finalized: self.is_finalized(), + is_active: true, with_keeper_asset_burn: self.with_keeper_asset_burn, with_supplier_asset_burn: self.with_supplier_asset_burn, } } +} - pub fn get_finalized_vault_hash(&self) -> [u8; 32] { - self.finalized_vault_cov_hash - } - - pub fn is_finalized(&self) -> bool { - self.finalized_vault_cov_hash == [0u8; 32] +impl FinalizedAssetAuthVaultParameters { + pub fn build_arguments(&self) -> AssetAuthVaultArguments { + AssetAuthVaultArguments { + vault_asset_id: self.vault_asset_id.into_inner().0, + keeper_auth_asset_id: self.keeper_asset_id.into_inner().0, + supplier_auth_asset_id: self.supplier_asset_id.into_inner().0, + keeper_auth_asset_amount: self.keeper_min_asset_amount, + finalized_vault_cov_hash: [0u8; 32], + is_active: false, + with_keeper_asset_burn: self.with_keeper_asset_burn, + with_supplier_asset_burn: self.with_supplier_asset_burn, + } } } + +// impl AssetAuthVaultParameters { +// pub fn new( +// vault_asset_id: AssetId, +// keeper_asset_id: AssetId, +// supplier_asset_id: AssetId, +// keeper_min_asset_amount: u64, +// with_keeper_asset_burn: bool, +// with_supplier_asset_burn: bool, +// network: SimplicityNetwork, +// ) -> Self { +// Self { +// vault_asset_id, +// keeper_asset_id, +// supplier_asset_id, +// keeper_min_asset_amount, +// with_keeper_asset_burn, +// with_supplier_asset_burn, +// network, +// finalized_vault_cov_hash: [0u8; 32], +// } +// } + +// pub fn from_finalized_parameters(self, finalized_vault_cov_hash: [u8; 32]) -> Self { +// Self { +// vault_asset_id: self.vault_asset_id, +// keeper_asset_id: self.keeper_asset_id, +// supplier_asset_id: self.supplier_asset_id, +// keeper_min_asset_amount: self.keeper_min_asset_amount, +// with_keeper_asset_burn: self.with_keeper_asset_burn, +// with_supplier_asset_burn: self.with_supplier_asset_burn, +// network: self.network, +// finalized_vault_cov_hash, +// } +// } + +// pub fn to_finalized_parameters(self) -> Self { +// Self::new( +// self.vault_asset_id, +// self.keeper_asset_id, +// self.supplier_asset_id, +// self.keeper_min_asset_amount, +// self.with_keeper_asset_burn, +// self.with_supplier_asset_burn, +// self.network, +// ) +// } + +// pub fn build_arguments(&self) -> AssetAuthVaultArguments { +// AssetAuthVaultArguments { +// vault_asset_id: self.vault_asset_id.into_inner().0, +// keeper_auth_asset_id: self.keeper_asset_id.into_inner().0, +// supplier_auth_asset_id: self.supplier_asset_id.into_inner().0, +// keeper_auth_asset_amount: self.keeper_min_asset_amount, +// finalized_vault_cov_hash: self.finalized_vault_cov_hash, +// is_finalized: self.is_finalized(), +// with_keeper_asset_burn: self.with_keeper_asset_burn, +// with_supplier_asset_burn: self.with_supplier_asset_burn, +// } +// } + +// pub fn get_finalized_vault_hash(&self) -> [u8; 32] { +// self.finalized_vault_cov_hash +// } + +// pub fn is_finalized(&self) -> bool { +// self.finalized_vault_cov_hash == [0u8; 32] +// } +// } diff --git a/crates/contracts/tests/asset_auth_vault/final_supply_failure_flows.rs b/crates/contracts/tests/asset_auth_vault/final_supply_failure_flows.rs index 7d42d2e..f24c0cf 100644 --- a/crates/contracts/tests/asset_auth_vault/final_supply_failure_flows.rs +++ b/crates/contracts/tests/asset_auth_vault/final_supply_failure_flows.rs @@ -1,4 +1,6 @@ -use lending_contracts::programs::asset_auth_vault::{AssetAuthVault, AssetAuthVaultParameters}; +use lending_contracts::programs::asset_auth_vault::{ + ActiveAssetAuthVault, ActiveAssetAuthVaultParameters, FinalizedAssetAuthVaultParameters, +}; use lending_contracts::programs::program::SimplexProgram; use simplex::simplicityhl::elements::Script; @@ -9,27 +11,28 @@ use super::setup::{final_supply, issue_auth_assets, prepare_vault_asset, setup_a fn default_final_vault_supplying_setup( context: &simplex::TestContext, with_supplier_asset_burn: bool, -) -> anyhow::Result<(AssetAuthVault, AssetAuthVaultParameters)> { - let (supplier_auth_asset_id, keeper_auth_asset_id) = issue_auth_assets(context, 1, 1)?; +) -> anyhow::Result<(ActiveAssetAuthVault, ActiveAssetAuthVaultParameters)> { + let (supplier_asset_id, keeper_asset_id) = issue_auth_assets(context, 1, 1)?; let vault_asset_amount = 1_000_000; let vault_asset_amounts = vec![5000]; let vault_asset_id = prepare_vault_asset(context, vault_asset_amount, vault_asset_amounts)?; - let vault_parameters = AssetAuthVaultParameters::new( + let vault_parameters = FinalizedAssetAuthVaultParameters { vault_asset_id, - keeper_auth_asset_id, - supplier_auth_asset_id, - 1, - false, + keeper_asset_id, + supplier_asset_id, + keeper_min_asset_amount: 1, + with_keeper_asset_burn: false, with_supplier_asset_burn, - *context.get_network(), - ); + network: *context.get_network(), + }; let asset_auth_vault = setup_asset_auth_vault(context, vault_parameters)?; + let active_vault_parameters = *asset_auth_vault.get_parameters(); - Ok((asset_auth_vault, vault_parameters)) + Ok((asset_auth_vault, active_vault_parameters)) } #[simplex::test] @@ -65,13 +68,7 @@ fn final_supply_fails_when_vault_already_finalized( RequiredSignature::NativeEcdsa, ); - finalized_asset_auth_vault.attach_final_supplying( - &mut ft, - asset_auth_vault_utxo, - 0, - 0, - amount_to_supply, - ); + asset_auth_vault.attach_final_supplying(&mut ft, asset_auth_vault_utxo, 0, 0, amount_to_supply); ft.add_input( PartialInput::new(utxo_to_supply), diff --git a/crates/contracts/tests/asset_auth_vault/final_supply_success_flows.rs b/crates/contracts/tests/asset_auth_vault/final_supply_success_flows.rs index 9ca2560..34e737c 100644 --- a/crates/contracts/tests/asset_auth_vault/final_supply_success_flows.rs +++ b/crates/contracts/tests/asset_auth_vault/final_supply_success_flows.rs @@ -1,4 +1,6 @@ -use lending_contracts::programs::asset_auth_vault::{AssetAuthVault, AssetAuthVaultParameters}; +use lending_contracts::programs::asset_auth_vault::{ + ActiveAssetAuthVault, ActiveAssetAuthVaultParameters, FinalizedAssetAuthVaultParameters, +}; use lending_contracts::programs::program::SimplexProgram; use simplex::simplicityhl::elements::Script; @@ -13,27 +15,28 @@ use super::setup::{ fn default_final_vault_supplying_setup( context: &simplex::TestContext, with_supplier_asset_burn: bool, -) -> anyhow::Result<(AssetAuthVault, AssetAuthVaultParameters)> { - let (supplier_auth_asset_id, keeper_auth_asset_id) = issue_auth_assets(context, 1, 1)?; +) -> anyhow::Result<(ActiveAssetAuthVault, ActiveAssetAuthVaultParameters)> { + let (supplier_asset_id, keeper_asset_id) = issue_auth_assets(context, 1, 1)?; let vault_asset_amount = 1_000_000; let vault_asset_amounts = vec![5000]; let vault_asset_id = prepare_vault_asset(context, vault_asset_amount, vault_asset_amounts)?; - let vault_parameters = AssetAuthVaultParameters::new( + let vault_parameters = FinalizedAssetAuthVaultParameters { vault_asset_id, - keeper_auth_asset_id, - supplier_auth_asset_id, - 1, - false, + keeper_asset_id, + supplier_asset_id, + keeper_min_asset_amount: 1, + with_keeper_asset_burn: false, with_supplier_asset_burn, - *context.get_network(), - ); + network: *context.get_network(), + }; let asset_auth_vault = setup_asset_auth_vault(context, vault_parameters)?; + let active_vault_parameters = *asset_auth_vault.get_parameters(); - Ok((asset_auth_vault, vault_parameters)) + Ok((asset_auth_vault, active_vault_parameters)) } #[simplex::test] diff --git a/crates/contracts/tests/asset_auth_vault/setup.rs b/crates/contracts/tests/asset_auth_vault/setup.rs index 268448b..bdac8e3 100644 --- a/crates/contracts/tests/asset_auth_vault/setup.rs +++ b/crates/contracts/tests/asset_auth_vault/setup.rs @@ -1,4 +1,6 @@ -use lending_contracts::programs::asset_auth_vault::{AssetAuthVault, AssetAuthVaultParameters}; +use lending_contracts::programs::asset_auth_vault::{ + ActiveAssetAuthVault, FinalizedAssetAuthVault, FinalizedAssetAuthVaultParameters, +}; use lending_contracts::programs::program::SimplexProgram; use lending_contracts::utils::get_random_seed; @@ -119,8 +121,8 @@ pub(super) fn make_confidential( pub(super) fn setup_asset_auth_vault( context: &simplex::TestContext, - vault_parameters: AssetAuthVaultParameters, -) -> anyhow::Result { + vault_parameters: FinalizedAssetAuthVaultParameters, +) -> anyhow::Result { let provider = context.get_default_provider(); let signer = context.get_default_signer(); @@ -134,7 +136,7 @@ pub(super) fn setup_asset_auth_vault( RequiredSignature::NativeEcdsa, ); - let asset_auth_vault = AssetAuthVault::new_active(vault_parameters); + let asset_auth_vault = ActiveAssetAuthVault::from_finalized_vault(vault_parameters); asset_auth_vault.attach_creation(&mut ft, vault_asset_amount); @@ -151,9 +153,9 @@ pub(super) fn setup_asset_auth_vault( pub(super) fn final_supply( context: &simplex::TestContext, - asset_auth_vault: &AssetAuthVault, + asset_auth_vault: &ActiveAssetAuthVault, amount_to_supply: u64, -) -> anyhow::Result { +) -> anyhow::Result { let provider = context.get_default_provider(); let signer = context.get_default_signer(); @@ -210,7 +212,7 @@ pub(super) fn final_supply( pub(super) fn check_vault_amount( context: &simplex::TestContext, - vault: &AssetAuthVault, + vault: &impl SimplexProgram, expected_amount: u64, ) -> anyhow::Result<()> { let provider = context.get_default_provider(); diff --git a/crates/contracts/tests/asset_auth_vault/supply_failure_flows.rs b/crates/contracts/tests/asset_auth_vault/supply_failure_flows.rs index 220c66a..72f8d97 100644 --- a/crates/contracts/tests/asset_auth_vault/supply_failure_flows.rs +++ b/crates/contracts/tests/asset_auth_vault/supply_failure_flows.rs @@ -1,4 +1,6 @@ -use lending_contracts::programs::asset_auth_vault::{AssetAuthVault, AssetAuthVaultParameters}; +use lending_contracts::programs::asset_auth_vault::{ + ActiveAssetAuthVault, ActiveAssetAuthVaultParameters, FinalizedAssetAuthVaultParameters, +}; use lending_contracts::programs::program::SimplexProgram; use simplex::simplicityhl::elements::Script; @@ -8,27 +10,28 @@ use super::setup::{final_supply, issue_auth_assets, prepare_vault_asset, setup_a fn default_vault_supplying_setup( context: &simplex::TestContext, -) -> anyhow::Result<(AssetAuthVault, AssetAuthVaultParameters)> { - let (supplier_auth_asset_id, keeper_auth_asset_id) = issue_auth_assets(context, 1, 1)?; +) -> anyhow::Result<(ActiveAssetAuthVault, ActiveAssetAuthVaultParameters)> { + let (supplier_asset_id, keeper_asset_id) = issue_auth_assets(context, 1, 1)?; let vault_asset_amount = 1_000_000; let vault_asset_amounts = vec![5000]; let vault_asset_id = prepare_vault_asset(context, vault_asset_amount, vault_asset_amounts)?; - let vault_parameters = AssetAuthVaultParameters::new( + let vault_parameters = FinalizedAssetAuthVaultParameters { vault_asset_id, - keeper_auth_asset_id, - supplier_auth_asset_id, - 1, - false, - false, - *context.get_network(), - ); + keeper_asset_id, + supplier_asset_id, + keeper_min_asset_amount: 1, + with_keeper_asset_burn: false, + with_supplier_asset_burn: false, + network: *context.get_network(), + }; let asset_auth_vault = setup_asset_auth_vault(context, vault_parameters)?; + let active_vault_parameters = *asset_auth_vault.get_parameters(); - Ok((asset_auth_vault, vault_parameters)) + Ok((asset_auth_vault, active_vault_parameters)) } #[simplex::test] @@ -61,13 +64,7 @@ fn fails_to_supply_to_finalized_vault(context: simplex::TestContext) -> anyhow:: RequiredSignature::NativeEcdsa, ); - finalized_asset_auth_vault.attach_supplying( - &mut ft, - asset_auth_vault_utxo, - 0, - 0, - amount_to_supply, - ); + asset_auth_vault.attach_supplying(&mut ft, asset_auth_vault_utxo, 0, 0, amount_to_supply); ft.add_input( PartialInput::new(utxo_to_supply), diff --git a/crates/contracts/tests/asset_auth_vault/supply_success_flows.rs b/crates/contracts/tests/asset_auth_vault/supply_success_flows.rs index 7b36354..591ac70 100644 --- a/crates/contracts/tests/asset_auth_vault/supply_success_flows.rs +++ b/crates/contracts/tests/asset_auth_vault/supply_success_flows.rs @@ -1,4 +1,6 @@ -use lending_contracts::programs::asset_auth_vault::{AssetAuthVault, AssetAuthVaultParameters}; +use lending_contracts::programs::asset_auth_vault::{ + ActiveAssetAuthVault, ActiveAssetAuthVaultParameters, FinalizedAssetAuthVaultParameters, +}; use lending_contracts::programs::program::SimplexProgram; use simplex::transaction::{FinalTransaction, PartialInput, PartialOutput, RequiredSignature}; @@ -12,25 +14,26 @@ use super::setup::{ fn default_vault_supplying_setup( context: &simplex::TestContext, vault_asset_amounts: Vec, -) -> anyhow::Result<(AssetAuthVault, AssetAuthVaultParameters)> { - let (supplier_auth_asset_id, keeper_auth_asset_id) = issue_auth_assets(context, 1, 1)?; +) -> anyhow::Result<(ActiveAssetAuthVault, ActiveAssetAuthVaultParameters)> { + let (supplier_asset_id, keeper_asset_id) = issue_auth_assets(context, 1, 1)?; let vault_asset_amount = 1_000_000; let vault_asset_id = prepare_vault_asset(context, vault_asset_amount, vault_asset_amounts)?; - let vault_parameters = AssetAuthVaultParameters::new( + let vault_parameters = FinalizedAssetAuthVaultParameters { vault_asset_id, - keeper_auth_asset_id, - supplier_auth_asset_id, - 1, - false, - false, - *context.get_network(), - ); + keeper_asset_id, + supplier_asset_id, + keeper_min_asset_amount: 1, + with_keeper_asset_burn: false, + with_supplier_asset_burn: false, + network: *context.get_network(), + }; let asset_auth_vault = setup_asset_auth_vault(context, vault_parameters)?; + let active_vault_parameters = *asset_auth_vault.get_parameters(); - Ok((asset_auth_vault, vault_parameters)) + Ok((asset_auth_vault, active_vault_parameters)) } #[simplex::test] From a102b0f92eaa0bbd8de5bfc76ec102223c67e71a Mon Sep 17 00:00:00 2001 From: Oleh Komendant Date: Tue, 5 May 2026 22:07:03 +0300 Subject: [PATCH 5/6] Finish tests for the AssetAuthVault covenant --- crates/contracts/simf/asset_auth_vault.simf | 31 +- .../src/programs/asset_auth_vault/params.rs | 69 ---- .../contracts/tests/asset_auth_vault/mod.rs | 4 + .../partial_withdraw_failure_flows.rs | 280 +++++++++++++++ .../partial_withdraw_success_flows.rs | 313 +++++++++++++++++ .../contracts/tests/asset_auth_vault/setup.rs | 96 ++++++ .../withdraw_all_failure_flows.rs | 321 ++++++++++++++++++ .../withdraw_all_success_flows.rs | 266 +++++++++++++++ 8 files changed, 1295 insertions(+), 85 deletions(-) create mode 100644 crates/contracts/tests/asset_auth_vault/partial_withdraw_failure_flows.rs create mode 100644 crates/contracts/tests/asset_auth_vault/partial_withdraw_success_flows.rs create mode 100644 crates/contracts/tests/asset_auth_vault/withdraw_all_failure_flows.rs create mode 100644 crates/contracts/tests/asset_auth_vault/withdraw_all_success_flows.rs diff --git a/crates/contracts/simf/asset_auth_vault.simf b/crates/contracts/simf/asset_auth_vault.simf index 2493ef0..d24a7fe 100644 --- a/crates/contracts/simf/asset_auth_vault.simf +++ b/crates/contracts/simf/asset_auth_vault.simf @@ -27,6 +27,14 @@ fn is_op_return(output_index: u32) -> bool { } } +// Math helpers + +fn safe_add_64(first: u64, second: u64) -> u64 { + let (carry, result): (bool, u64) = jet::add_64(first, second); + + result +} + // Check helpers fn check_asset_amounts_eq(asset_amount_1: u64, asset_amount_2: u64) { @@ -75,12 +83,13 @@ fn ensure_vault_asset(index: u32, is_input_index: bool) -> u64 { vault_amount } -// Math helpers +fn ensure_vault_amount_after_supplying(vault_output_index: u32, amount_to_supply: u64) { + let vault_amount: u64 = ensure_vault_asset(jet::current_index(), true); + let output_vault_amount: u64 = ensure_vault_asset(vault_output_index, false); -fn safe_add_64(first: u64, second: u64) -> u64 { - let (carry, result): (bool, u64) = jet::add_64(first, second); + let expected_output_vault_amount: u64 = safe_add_64(vault_amount, amount_to_supply); - result + check_asset_amounts_eq(output_vault_amount, expected_output_vault_amount); } // Auth logic @@ -157,12 +166,7 @@ fn supply(input_supplier_index: u32, output_supplier_index: u32, vault_output_in ensure_output_script_hash(vault_output_index, jet::current_script_hash()); - let vault_amount: u64 = ensure_vault_asset(jet::current_index(), true); - let output_vault_amount: u64 = ensure_vault_asset(vault_output_index, false); - - let expected_output_vault_amount: u64 = safe_add_64(vault_amount, amount_to_supply); - - check_asset_amounts_eq(output_vault_amount, expected_output_vault_amount); + ensure_vault_amount_after_supplying(vault_output_index, amount_to_supply); } fn final_supply( @@ -183,12 +187,7 @@ fn final_supply( ensure_output_script_hash(finalized_vault_output_index, param::FINALIZED_VAULT_COV_HASH); - let vault_amount: u64 = ensure_vault_asset(jet::current_index(), true); - let output_vault_amount: u64 = ensure_vault_asset(finalized_vault_output_index, false); - - let expected_output_vault_amount: u64 = safe_add_64(vault_amount, amount_to_supply); - - check_asset_amounts_eq(output_vault_amount, expected_output_vault_amount); + ensure_vault_amount_after_supplying(finalized_vault_output_index, amount_to_supply); } fn main() { diff --git a/crates/contracts/src/programs/asset_auth_vault/params.rs b/crates/contracts/src/programs/asset_auth_vault/params.rs index 4cad408..86ab6a4 100644 --- a/crates/contracts/src/programs/asset_auth_vault/params.rs +++ b/crates/contracts/src/programs/asset_auth_vault/params.rs @@ -84,72 +84,3 @@ impl FinalizedAssetAuthVaultParameters { } } } - -// impl AssetAuthVaultParameters { -// pub fn new( -// vault_asset_id: AssetId, -// keeper_asset_id: AssetId, -// supplier_asset_id: AssetId, -// keeper_min_asset_amount: u64, -// with_keeper_asset_burn: bool, -// with_supplier_asset_burn: bool, -// network: SimplicityNetwork, -// ) -> Self { -// Self { -// vault_asset_id, -// keeper_asset_id, -// supplier_asset_id, -// keeper_min_asset_amount, -// with_keeper_asset_burn, -// with_supplier_asset_burn, -// network, -// finalized_vault_cov_hash: [0u8; 32], -// } -// } - -// pub fn from_finalized_parameters(self, finalized_vault_cov_hash: [u8; 32]) -> Self { -// Self { -// vault_asset_id: self.vault_asset_id, -// keeper_asset_id: self.keeper_asset_id, -// supplier_asset_id: self.supplier_asset_id, -// keeper_min_asset_amount: self.keeper_min_asset_amount, -// with_keeper_asset_burn: self.with_keeper_asset_burn, -// with_supplier_asset_burn: self.with_supplier_asset_burn, -// network: self.network, -// finalized_vault_cov_hash, -// } -// } - -// pub fn to_finalized_parameters(self) -> Self { -// Self::new( -// self.vault_asset_id, -// self.keeper_asset_id, -// self.supplier_asset_id, -// self.keeper_min_asset_amount, -// self.with_keeper_asset_burn, -// self.with_supplier_asset_burn, -// self.network, -// ) -// } - -// pub fn build_arguments(&self) -> AssetAuthVaultArguments { -// AssetAuthVaultArguments { -// vault_asset_id: self.vault_asset_id.into_inner().0, -// keeper_auth_asset_id: self.keeper_asset_id.into_inner().0, -// supplier_auth_asset_id: self.supplier_asset_id.into_inner().0, -// keeper_auth_asset_amount: self.keeper_min_asset_amount, -// finalized_vault_cov_hash: self.finalized_vault_cov_hash, -// is_finalized: self.is_finalized(), -// with_keeper_asset_burn: self.with_keeper_asset_burn, -// with_supplier_asset_burn: self.with_supplier_asset_burn, -// } -// } - -// pub fn get_finalized_vault_hash(&self) -> [u8; 32] { -// self.finalized_vault_cov_hash -// } - -// pub fn is_finalized(&self) -> bool { -// self.finalized_vault_cov_hash == [0u8; 32] -// } -// } diff --git a/crates/contracts/tests/asset_auth_vault/mod.rs b/crates/contracts/tests/asset_auth_vault/mod.rs index e70c502..501b7f2 100644 --- a/crates/contracts/tests/asset_auth_vault/mod.rs +++ b/crates/contracts/tests/asset_auth_vault/mod.rs @@ -3,6 +3,10 @@ mod common; mod final_supply_failure_flows; mod final_supply_success_flows; +mod partial_withdraw_failure_flows; +mod partial_withdraw_success_flows; mod setup; mod supply_failure_flows; mod supply_success_flows; +mod withdraw_all_failure_flows; +mod withdraw_all_success_flows; diff --git a/crates/contracts/tests/asset_auth_vault/partial_withdraw_failure_flows.rs b/crates/contracts/tests/asset_auth_vault/partial_withdraw_failure_flows.rs new file mode 100644 index 0000000..9ac986a --- /dev/null +++ b/crates/contracts/tests/asset_auth_vault/partial_withdraw_failure_flows.rs @@ -0,0 +1,280 @@ +use lending_contracts::programs::asset_auth_vault::{ + ActiveAssetAuthVault, ActiveAssetAuthVaultParameters, FinalizedAssetAuthVaultParameters, +}; +use lending_contracts::programs::program::SimplexProgram; + +use simplex::signer::Signer; +use simplex::simplicityhl::elements::Script; +use simplex::transaction::{FinalTransaction, PartialInput, PartialOutput, RequiredSignature}; + +use super::setup::{ + final_supply, fund_keeper, issue_auth_assets, prepare_vault_asset, setup_asset_auth_vault, + supply, +}; + +fn default_vault_withdrawing_setup( + context: &simplex::TestContext, + keeper: &Signer, + vault_asset_amounts: Vec, + keeper_auth_asset_amount: u64, + amount_to_supply: u64, +) -> anyhow::Result<(ActiveAssetAuthVault, ActiveAssetAuthVaultParameters)> { + let (supplier_asset_id, keeper_asset_id) = + issue_auth_assets(context, 1, keeper_auth_asset_amount)?; + + let vault_asset_amount = 1_000_000; + let vault_asset_id = prepare_vault_asset(context, vault_asset_amount, vault_asset_amounts)?; + + let vault_parameters = FinalizedAssetAuthVaultParameters { + vault_asset_id, + keeper_asset_id, + supplier_asset_id, + keeper_min_asset_amount: keeper_auth_asset_amount, + with_keeper_asset_burn: false, + with_supplier_asset_burn: false, + network: *context.get_network(), + }; + + let asset_auth_vault = setup_asset_auth_vault(context, vault_parameters)?; + let active_vault_parameters = *asset_auth_vault.get_parameters(); + + supply(context, &asset_auth_vault, amount_to_supply)?; + + fund_keeper(context, keeper, vault_parameters.keeper_asset_id)?; + + Ok((asset_auth_vault, active_vault_parameters)) +} + +#[simplex::test] +fn partial_withdraw_fails_when_vault_already_finalized( + context: simplex::TestContext, +) -> anyhow::Result<()> { + let provider = context.get_default_provider(); + let keeper = context.random_signer(); + + let (asset_auth_vault, vault_parameters) = + default_vault_withdrawing_setup(&context, &keeper, vec![5000], 1, 1000)?; + + let finalized_asset_auth_vault = final_supply(&context, &asset_auth_vault, 300)?; + + let asset_auth_vault_utxo = provider + .fetch_scripthash_utxos(&finalized_asset_auth_vault.get_script_pubkey())?[0] + .clone(); + + let current_vault_balance = asset_auth_vault_utxo.explicit_amount(); + let amount_to_withdraw = current_vault_balance / 2; + + let keeper_auth_utxo = keeper.get_utxos_asset(vault_parameters.keeper_asset_id)?[0].clone(); + + let mut ft = FinalTransaction::new(); + + ft.add_input( + PartialInput::new(keeper_auth_utxo.clone()), + RequiredSignature::NativeEcdsa, + ); + ft.add_output(PartialOutput::new( + keeper.get_address().script_pubkey(), + keeper_auth_utxo.explicit_amount(), + keeper_auth_utxo.explicit_asset(), + )); + + asset_auth_vault.attach_partial_withdrawing( + &mut ft, + asset_auth_vault_utxo, + 0, + 0, + amount_to_withdraw, + ); + + ft.add_output(PartialOutput::new( + keeper.get_address().script_pubkey(), + amount_to_withdraw, + vault_parameters.vault_asset_id, + )); + + let result = keeper.finalize(&ft); + + assert!( + result.is_err(), + "expected finalize to fail, but it succeeded" + ); + + Ok(()) +} + +#[simplex::test] +fn partial_withdraw_fails_when_auth_utxo_is_invalid( + context: simplex::TestContext, +) -> anyhow::Result<()> { + let provider = context.get_default_provider(); + let keeper = context.random_signer(); + + let (asset_auth_vault, vault_parameters) = + default_vault_withdrawing_setup(&context, &keeper, vec![5000], 1, 1000)?; + + let asset_auth_vault_utxo = + provider.fetch_scripthash_utxos(&asset_auth_vault.get_script_pubkey())?[0].clone(); + + let current_vault_balance = asset_auth_vault_utxo.explicit_amount(); + let amount_to_withdraw = current_vault_balance / 2; + + let invalid_auth_index_pairs = [(0, 1), (1, 0), (1, 1)]; + + for auth_index_pair in invalid_auth_index_pairs { + let keeper_auth_utxo = keeper.get_utxos_asset(vault_parameters.keeper_asset_id)?[0].clone(); + + let mut ft = FinalTransaction::new(); + + ft.add_input( + PartialInput::new(keeper_auth_utxo.clone()), + RequiredSignature::NativeEcdsa, + ); + ft.add_output(PartialOutput::new( + keeper.get_address().script_pubkey(), + keeper_auth_utxo.explicit_amount(), + keeper_auth_utxo.explicit_asset(), + )); + + asset_auth_vault.attach_partial_withdrawing( + &mut ft, + asset_auth_vault_utxo.clone(), + auth_index_pair.0, + auth_index_pair.1, + amount_to_withdraw, + ); + + ft.add_output(PartialOutput::new( + keeper.get_address().script_pubkey(), + amount_to_withdraw, + vault_parameters.vault_asset_id, + )); + + let result = keeper.finalize(&ft); + + assert!( + result.is_err(), + "expected finalize to fail, but it succeeded" + ); + } + + Ok(()) +} + +#[simplex::test] +fn partial_withdraw_fails_when_auth_utxo_has_less_than_minimum_amount( + context: simplex::TestContext, +) -> anyhow::Result<()> { + let provider = context.get_default_provider(); + let keeper = context.random_signer(); + + let keeper_auth_asset_amount = 1000; + let (asset_auth_vault, vault_parameters) = default_vault_withdrawing_setup( + &context, + &keeper, + vec![5000], + keeper_auth_asset_amount, + 1000, + )?; + + let asset_auth_vault_utxo = + provider.fetch_scripthash_utxos(&asset_auth_vault.get_script_pubkey())?[0].clone(); + + let current_vault_balance = asset_auth_vault_utxo.explicit_amount(); + let amount_to_withdraw = current_vault_balance / 2; + + let keeper_auth_utxo = keeper.get_utxos_asset(vault_parameters.keeper_asset_id)?[0].clone(); + + let mut ft = FinalTransaction::new(); + + ft.add_input( + PartialInput::new(keeper_auth_utxo.clone()), + RequiredSignature::NativeEcdsa, + ); + ft.add_output(PartialOutput::new( + keeper.get_address().script_pubkey(), + keeper_auth_asset_amount / 2, + vault_parameters.keeper_asset_id, + )); + ft.add_output(PartialOutput::new( + keeper.get_address().script_pubkey(), + keeper_auth_asset_amount / 2, + vault_parameters.keeper_asset_id, + )); + + asset_auth_vault.attach_partial_withdrawing( + &mut ft, + asset_auth_vault_utxo.clone(), + 0, + 1, + amount_to_withdraw, + ); + + ft.add_output(PartialOutput::new( + keeper.get_address().script_pubkey(), + amount_to_withdraw, + vault_parameters.vault_asset_id, + )); + + let result = keeper.finalize(&ft); + + assert!( + result.is_err(), + "expected finalize to fail, but it succeeded" + ); + + Ok(()) +} + +#[simplex::test] +fn partial_withdraw_fails_when_auth_utxo_burned_but_burn_flag_was_not_set( + context: simplex::TestContext, +) -> anyhow::Result<()> { + let provider = context.get_default_provider(); + let keeper = context.random_signer(); + + let (asset_auth_vault, vault_parameters) = + default_vault_withdrawing_setup(&context, &keeper, vec![5000], 1, 1000)?; + + let asset_auth_vault_utxo = + provider.fetch_scripthash_utxos(&asset_auth_vault.get_script_pubkey())?[0].clone(); + + let current_vault_balance = asset_auth_vault_utxo.explicit_amount(); + let amount_to_withdraw = current_vault_balance / 2; + + let keeper_auth_utxo = keeper.get_utxos_asset(vault_parameters.keeper_asset_id)?[0].clone(); + + let mut ft = FinalTransaction::new(); + + ft.add_input( + PartialInput::new(keeper_auth_utxo.clone()), + RequiredSignature::NativeEcdsa, + ); + ft.add_output(PartialOutput::new( + Script::new_op_return(b"burn"), + keeper_auth_utxo.explicit_amount(), + vault_parameters.keeper_asset_id, + )); + + asset_auth_vault.attach_partial_withdrawing( + &mut ft, + asset_auth_vault_utxo.clone(), + 0, + 1, + amount_to_withdraw, + ); + + ft.add_output(PartialOutput::new( + keeper.get_address().script_pubkey(), + amount_to_withdraw, + vault_parameters.vault_asset_id, + )); + + let result = keeper.finalize(&ft); + + assert!( + result.is_err(), + "expected finalize to fail, but it succeeded" + ); + + Ok(()) +} diff --git a/crates/contracts/tests/asset_auth_vault/partial_withdraw_success_flows.rs b/crates/contracts/tests/asset_auth_vault/partial_withdraw_success_flows.rs new file mode 100644 index 0000000..cb65056 --- /dev/null +++ b/crates/contracts/tests/asset_auth_vault/partial_withdraw_success_flows.rs @@ -0,0 +1,313 @@ +use lending_contracts::programs::asset_auth_vault::{ + ActiveAssetAuthVault, ActiveAssetAuthVaultParameters, FinalizedAssetAuthVaultParameters, +}; +use lending_contracts::programs::program::SimplexProgram; + +use simplex::signer::Signer; +use simplex::transaction::{FinalTransaction, PartialInput, PartialOutput, RequiredSignature}; + +use super::setup::{ + check_vault_amount, fund_keeper, issue_auth_assets, prepare_vault_asset, + setup_asset_auth_vault, supply, +}; + +fn default_vault_withdrawing_setup( + context: &simplex::TestContext, + keeper: &Signer, + vault_asset_amounts: Vec, + amount_to_supply: u64, +) -> anyhow::Result<(ActiveAssetAuthVault, ActiveAssetAuthVaultParameters)> { + let (supplier_asset_id, keeper_asset_id) = issue_auth_assets(context, 1, 1)?; + + let vault_asset_amount = 1_000_000; + let vault_asset_id = prepare_vault_asset(context, vault_asset_amount, vault_asset_amounts)?; + + let vault_parameters = FinalizedAssetAuthVaultParameters { + vault_asset_id, + keeper_asset_id, + supplier_asset_id, + keeper_min_asset_amount: 1, + with_keeper_asset_burn: false, + with_supplier_asset_burn: false, + network: *context.get_network(), + }; + + let asset_auth_vault = setup_asset_auth_vault(context, vault_parameters)?; + let active_vault_parameters = *asset_auth_vault.get_parameters(); + + supply(context, &asset_auth_vault, amount_to_supply)?; + + fund_keeper(context, keeper, vault_parameters.keeper_asset_id)?; + + Ok((asset_auth_vault, active_vault_parameters)) +} + +fn withdraw( + context: &simplex::TestContext, + keeper: &Signer, + asset_auth_vault: &ActiveAssetAuthVault, + amount_to_withdraw: u64, +) -> anyhow::Result<()> { + let provider = context.get_default_provider(); + + let vault_parameters = asset_auth_vault.get_parameters(); + + let asset_auth_vault_utxo = + provider.fetch_scripthash_utxos(&asset_auth_vault.get_script_pubkey())?[0].clone(); + + let keeper_auth_utxo = keeper.get_utxos_asset(vault_parameters.keeper_asset_id)?[0].clone(); + + let mut ft = FinalTransaction::new(); + + ft.add_input( + PartialInput::new(keeper_auth_utxo.clone()), + RequiredSignature::NativeEcdsa, + ); + ft.add_output(PartialOutput::new( + keeper.get_address().script_pubkey(), + keeper_auth_utxo.explicit_amount(), + keeper_auth_utxo.explicit_asset(), + )); + + asset_auth_vault.attach_partial_withdrawing( + &mut ft, + asset_auth_vault_utxo, + 0, + 0, + amount_to_withdraw, + ); + + ft.add_output(PartialOutput::new( + keeper.get_address().script_pubkey(), + amount_to_withdraw, + vault_parameters.vault_asset_id, + )); + + let txid = keeper.broadcast(&ft)?; + provider.wait(&txid)?; + + Ok(()) +} + +#[simplex::test] +fn partial_withdraw_succeeds_with_one_explicit_output( + context: simplex::TestContext, +) -> anyhow::Result<()> { + let provider = context.get_default_provider(); + let keeper = context.random_signer(); + + let (asset_auth_vault, vault_parameters) = + default_vault_withdrawing_setup(&context, &keeper, vec![5000], 1000)?; + + let asset_auth_vault_utxo = + provider.fetch_scripthash_utxos(&asset_auth_vault.get_script_pubkey())?[0].clone(); + + let current_vault_balance = asset_auth_vault_utxo.explicit_amount(); + let amount_to_withdraw = current_vault_balance / 2; + + let keeper_auth_utxo = keeper.get_utxos_asset(vault_parameters.keeper_asset_id)?[0].clone(); + + let mut ft = FinalTransaction::new(); + + ft.add_input( + PartialInput::new(keeper_auth_utxo.clone()), + RequiredSignature::NativeEcdsa, + ); + ft.add_output(PartialOutput::new( + keeper.get_address().script_pubkey(), + keeper_auth_utxo.explicit_amount(), + keeper_auth_utxo.explicit_asset(), + )); + + asset_auth_vault.attach_partial_withdrawing( + &mut ft, + asset_auth_vault_utxo, + 0, + 0, + amount_to_withdraw, + ); + + ft.add_output(PartialOutput::new( + keeper.get_address().script_pubkey(), + amount_to_withdraw, + vault_parameters.vault_asset_id, + )); + + let txid = keeper.broadcast(&ft)?; + provider.wait(&txid)?; + + check_vault_amount( + &context, + &asset_auth_vault, + current_vault_balance - amount_to_withdraw, + )?; + + Ok(()) +} + +#[simplex::test] +fn partial_withdraw_succeeds_with_several_explicit_outputs( + context: simplex::TestContext, +) -> anyhow::Result<()> { + let provider = context.get_default_provider(); + let keeper = context.random_signer(); + + let (asset_auth_vault, vault_parameters) = + default_vault_withdrawing_setup(&context, &keeper, vec![5000], 1000)?; + + let asset_auth_vault_utxo = + provider.fetch_scripthash_utxos(&asset_auth_vault.get_script_pubkey())?[0].clone(); + + let current_vault_balance = asset_auth_vault_utxo.explicit_amount(); + let amount_to_withdraw = current_vault_balance / 2; + + let keeper_auth_utxo = keeper.get_utxos_asset(vault_parameters.keeper_asset_id)?[0].clone(); + + let mut ft = FinalTransaction::new(); + + ft.add_input( + PartialInput::new(keeper_auth_utxo.clone()), + RequiredSignature::NativeEcdsa, + ); + ft.add_output(PartialOutput::new( + keeper.get_address().script_pubkey(), + keeper_auth_utxo.explicit_amount(), + keeper_auth_utxo.explicit_asset(), + )); + + asset_auth_vault.attach_partial_withdrawing( + &mut ft, + asset_auth_vault_utxo, + 0, + 0, + amount_to_withdraw, + ); + + ft.add_output(PartialOutput::new( + keeper.get_address().script_pubkey(), + amount_to_withdraw / 2, + vault_parameters.vault_asset_id, + )); + ft.add_output(PartialOutput::new( + keeper.get_address().script_pubkey(), + amount_to_withdraw / 2, + vault_parameters.vault_asset_id, + )); + + let txid = keeper.broadcast(&ft)?; + provider.wait(&txid)?; + + check_vault_amount( + &context, + &asset_auth_vault, + current_vault_balance - amount_to_withdraw, + )?; + + Ok(()) +} + +#[simplex::test] +fn partial_withdraw_succeeds_with_several_confidential_outputs( + context: simplex::TestContext, +) -> anyhow::Result<()> { + let provider = context.get_default_provider(); + let keeper = context.random_signer(); + + let (asset_auth_vault, vault_parameters) = + default_vault_withdrawing_setup(&context, &keeper, vec![5000], 1000)?; + + let asset_auth_vault_utxo = + provider.fetch_scripthash_utxos(&asset_auth_vault.get_script_pubkey())?[0].clone(); + + let current_vault_balance = asset_auth_vault_utxo.explicit_amount(); + let amount_to_withdraw = current_vault_balance / 2; + + let keeper_auth_utxo = keeper.get_utxos_asset(vault_parameters.keeper_asset_id)?[0].clone(); + + let mut ft = FinalTransaction::new(); + + ft.add_input( + PartialInput::new(keeper_auth_utxo.clone()), + RequiredSignature::NativeEcdsa, + ); + ft.add_output(PartialOutput::new( + keeper.get_address().script_pubkey(), + keeper_auth_utxo.explicit_amount(), + keeper_auth_utxo.explicit_asset(), + )); + + asset_auth_vault.attach_partial_withdrawing( + &mut ft, + asset_auth_vault_utxo, + 0, + 0, + amount_to_withdraw, + ); + + ft.add_output( + PartialOutput::new( + keeper.get_confidential_address().script_pubkey(), + amount_to_withdraw / 2, + vault_parameters.vault_asset_id, + ) + .with_blinding_key(keeper.get_blinding_public_key()), + ); + ft.add_output( + PartialOutput::new( + keeper.get_confidential_address().script_pubkey(), + amount_to_withdraw / 2, + vault_parameters.vault_asset_id, + ) + .with_blinding_key(keeper.get_blinding_public_key()), + ); + + let txid = keeper.broadcast(&ft)?; + provider.wait(&txid)?; + + check_vault_amount( + &context, + &asset_auth_vault, + current_vault_balance - amount_to_withdraw, + )?; + + Ok(()) +} + +#[simplex::test] +fn partial_withdraw_succeeds_several_times(context: simplex::TestContext) -> anyhow::Result<()> { + let provider = context.get_default_provider(); + let keeper = context.random_signer(); + + let (asset_auth_vault, _) = + default_vault_withdrawing_setup(&context, &keeper, vec![5000], 1000)?; + + let asset_auth_vault_utxo = + provider.fetch_scripthash_utxos(&asset_auth_vault.get_script_pubkey())?[0].clone(); + + let current_vault_balance = asset_auth_vault_utxo.explicit_amount(); + let amount_to_withdraw = current_vault_balance / 2; + + withdraw(&context, &keeper, &asset_auth_vault, amount_to_withdraw)?; + + check_vault_amount( + &context, + &asset_auth_vault, + current_vault_balance - amount_to_withdraw, + )?; + + let asset_auth_vault_utxo = + provider.fetch_scripthash_utxos(&asset_auth_vault.get_script_pubkey())?[0].clone(); + + let current_vault_balance = asset_auth_vault_utxo.explicit_amount(); + let amount_to_withdraw = current_vault_balance / 2; + + withdraw(&context, &keeper, &asset_auth_vault, amount_to_withdraw)?; + + check_vault_amount( + &context, + &asset_auth_vault, + current_vault_balance - amount_to_withdraw, + )?; + + Ok(()) +} diff --git a/crates/contracts/tests/asset_auth_vault/setup.rs b/crates/contracts/tests/asset_auth_vault/setup.rs index bdac8e3..66c334d 100644 --- a/crates/contracts/tests/asset_auth_vault/setup.rs +++ b/crates/contracts/tests/asset_auth_vault/setup.rs @@ -4,6 +4,7 @@ use lending_contracts::programs::asset_auth_vault::{ use lending_contracts::programs::program::SimplexProgram; use lending_contracts::utils::get_random_seed; +use simplex::signer::Signer; use simplex::simplicityhl::elements::AssetId; use simplex::transaction::partial_input::IssuanceInput; use simplex::transaction::{ @@ -151,6 +152,48 @@ pub(super) fn setup_asset_auth_vault( Ok(asset_auth_vault) } +pub(super) fn fund_keeper( + context: &simplex::TestContext, + keeper: &Signer, + keeper_asset_id: AssetId, +) -> anyhow::Result<()> { + let provider = context.get_default_provider(); + let signer = context.get_default_signer(); + + let keeper_auth_utxo = signer.get_utxos_asset(keeper_asset_id)?[0].clone(); + let policy_utxo = signer.get_utxos_asset(context.get_network().policy_asset())?[0].clone(); + + let keeper_auth_amount = keeper_auth_utxo.explicit_amount(); + let policy_amount_to_send = policy_utxo.explicit_amount() / 2; + + let mut ft = FinalTransaction::new(); + + ft.add_input( + PartialInput::new(keeper_auth_utxo), + RequiredSignature::NativeEcdsa, + ); + ft.add_input( + PartialInput::new(policy_utxo), + RequiredSignature::NativeEcdsa, + ); + + ft.add_output(PartialOutput::new( + keeper.get_address().script_pubkey(), + keeper_auth_amount, + keeper_asset_id, + )); + ft.add_output(PartialOutput::new( + keeper.get_address().script_pubkey(), + policy_amount_to_send, + context.get_network().policy_asset(), + )); + + let txid = finalize_and_broadcast(context, &ft)?; + provider.wait(&txid)?; + + Ok(()) +} + pub(super) fn final_supply( context: &simplex::TestContext, asset_auth_vault: &ActiveAssetAuthVault, @@ -210,6 +253,59 @@ pub(super) fn final_supply( Ok(finalized_vault) } +pub(super) fn supply( + context: &simplex::TestContext, + asset_auth_vault: &ActiveAssetAuthVault, + amount_to_supply: u64, +) -> anyhow::Result<()> { + let provider = context.get_default_provider(); + let signer = context.get_default_signer(); + + let vault_parameters = *asset_auth_vault.get_parameters(); + + let asset_auth_vault_utxo = + provider.fetch_scripthash_utxos(&asset_auth_vault.get_script_pubkey())?[0].clone(); + + let utxo_to_supply = signer.get_utxos_asset(vault_parameters.vault_asset_id)?[0].clone(); + let vault_asset_utxo_amount = utxo_to_supply.explicit_amount(); + + assert!(vault_asset_utxo_amount >= amount_to_supply); + + let supplier_auth_utxo = signer.get_utxos_asset(vault_parameters.supplier_asset_id)?[0].clone(); + + let mut ft = FinalTransaction::new(); + + ft.add_input( + PartialInput::new(supplier_auth_utxo.clone()), + RequiredSignature::NativeEcdsa, + ); + ft.add_input( + PartialInput::new(utxo_to_supply), + RequiredSignature::NativeEcdsa, + ); + + asset_auth_vault.attach_supplying(&mut ft, asset_auth_vault_utxo, 0, 1, amount_to_supply); + + ft.add_output(PartialOutput::new( + signer.get_address().script_pubkey(), + supplier_auth_utxo.explicit_amount(), + supplier_auth_utxo.explicit_asset(), + )); + + if vault_asset_utxo_amount > amount_to_supply { + ft.add_output(PartialOutput::new( + signer.get_address().script_pubkey(), + vault_asset_utxo_amount - amount_to_supply, + vault_parameters.vault_asset_id, + )); + } + + let txid = finalize_and_broadcast(context, &ft)?; + provider.wait(&txid)?; + + Ok(()) +} + pub(super) fn check_vault_amount( context: &simplex::TestContext, vault: &impl SimplexProgram, diff --git a/crates/contracts/tests/asset_auth_vault/withdraw_all_failure_flows.rs b/crates/contracts/tests/asset_auth_vault/withdraw_all_failure_flows.rs new file mode 100644 index 0000000..d052170 --- /dev/null +++ b/crates/contracts/tests/asset_auth_vault/withdraw_all_failure_flows.rs @@ -0,0 +1,321 @@ +use lending_contracts::programs::asset_auth_vault::{ + ActiveAssetAuthVault, ActiveAssetAuthVaultParameters, FinalizedAssetAuthVault, + FinalizedAssetAuthVaultParameters, +}; +use lending_contracts::programs::program::SimplexProgram; + +use simplex::signer::Signer; +use simplex::simplicityhl::elements::Script; +use simplex::transaction::{FinalTransaction, PartialInput, PartialOutput, RequiredSignature}; + +use super::setup::{ + final_supply, fund_keeper, issue_auth_assets, prepare_vault_asset, setup_asset_auth_vault, +}; + +fn setup_non_finalized_vault( + context: &simplex::TestContext, + keeper: &Signer, + vault_asset_amounts: Vec, + keeper_auth_asset_amount: u64, + with_keeper_asset_burn: bool, +) -> anyhow::Result<(ActiveAssetAuthVault, ActiveAssetAuthVaultParameters)> { + let (supplier_asset_id, keeper_asset_id) = + issue_auth_assets(context, 1, keeper_auth_asset_amount)?; + + let vault_asset_amount = 1_000_000; + let vault_asset_id = prepare_vault_asset(context, vault_asset_amount, vault_asset_amounts)?; + + let vault_parameters = FinalizedAssetAuthVaultParameters { + vault_asset_id, + keeper_asset_id, + supplier_asset_id, + keeper_min_asset_amount: keeper_auth_asset_amount, + with_keeper_asset_burn, + with_supplier_asset_burn: false, + network: *context.get_network(), + }; + + let asset_auth_vault = setup_asset_auth_vault(context, vault_parameters)?; + let asset_auth_vault_parameters = *asset_auth_vault.get_parameters(); + + fund_keeper(context, keeper, vault_parameters.keeper_asset_id)?; + + Ok((asset_auth_vault, asset_auth_vault_parameters)) +} + +fn default_vault_withdrawing_all_setup( + context: &simplex::TestContext, + keeper: &Signer, + vault_asset_amounts: Vec, + keeper_auth_asset_amount: u64, + with_keeper_asset_burn: bool, +) -> anyhow::Result<(FinalizedAssetAuthVault, FinalizedAssetAuthVaultParameters)> { + let (asset_auth_vault, vault_parameters) = setup_non_finalized_vault( + context, + keeper, + vault_asset_amounts, + keeper_auth_asset_amount, + with_keeper_asset_burn, + )?; + + let finalized_vault = final_supply(context, &asset_auth_vault, 300)?; + + Ok((finalized_vault, vault_parameters.into())) +} + +#[simplex::test] +fn withdraw_all_fails_when_vault_is_not_finalized( + context: simplex::TestContext, +) -> anyhow::Result<()> { + let provider = context.get_default_provider(); + let keeper = context.random_signer(); + + let (asset_auth_vault, vault_parameters) = + setup_non_finalized_vault(&context, &keeper, vec![5000], 1, false)?; + + let asset_auth_vault_utxo = + provider.fetch_scripthash_utxos(&asset_auth_vault.get_script_pubkey())?[0].clone(); + + let current_vault_balance = asset_auth_vault_utxo.explicit_amount(); + + let finalized_vault = FinalizedAssetAuthVault::new(vault_parameters.into()); + + let keeper_auth_utxo = keeper.get_utxos_asset(vault_parameters.keeper_asset_id)?[0].clone(); + + let mut ft = FinalTransaction::new(); + + ft.add_input( + PartialInput::new(keeper_auth_utxo.clone()), + RequiredSignature::NativeEcdsa, + ); + ft.add_output(PartialOutput::new( + keeper.get_address().script_pubkey(), + keeper_auth_utxo.explicit_amount(), + keeper_auth_utxo.explicit_asset(), + )); + + finalized_vault.attach_withdrawing_all(&mut ft, asset_auth_vault_utxo, 0, 0); + + ft.add_output(PartialOutput::new( + keeper.get_address().script_pubkey(), + current_vault_balance, + vault_parameters.vault_asset_id, + )); + + let result = keeper.finalize(&ft); + + assert!( + result.is_err(), + "expected finalize to fail, but it succeeded" + ); + + Ok(()) +} + +#[simplex::test] +fn withdraw_all_fails_when_auth_utxo_is_invalid( + context: simplex::TestContext, +) -> anyhow::Result<()> { + let provider = context.get_default_provider(); + let keeper = context.random_signer(); + + let (asset_auth_vault, vault_parameters) = + default_vault_withdrawing_all_setup(&context, &keeper, vec![5000], 1, true)?; + + let asset_auth_vault_utxo = + provider.fetch_scripthash_utxos(&asset_auth_vault.get_script_pubkey())?[0].clone(); + + let current_vault_balance = asset_auth_vault_utxo.explicit_amount(); + + let invalid_auth_index_pairs = [(0, 1), (1, 0), (1, 1)]; + + for auth_index_pair in invalid_auth_index_pairs { + let keeper_auth_utxo = keeper.get_utxos_asset(vault_parameters.keeper_asset_id)?[0].clone(); + + let mut ft = FinalTransaction::new(); + + ft.add_input( + PartialInput::new(keeper_auth_utxo.clone()), + RequiredSignature::NativeEcdsa, + ); + ft.add_output(PartialOutput::new( + keeper.get_address().script_pubkey(), + keeper_auth_utxo.explicit_amount(), + keeper_auth_utxo.explicit_asset(), + )); + + asset_auth_vault.attach_withdrawing_all( + &mut ft, + asset_auth_vault_utxo.clone(), + auth_index_pair.0, + auth_index_pair.1, + ); + + ft.add_output(PartialOutput::new( + keeper.get_address().script_pubkey(), + current_vault_balance, + vault_parameters.vault_asset_id, + )); + + let result = keeper.finalize(&ft); + + assert!( + result.is_err(), + "expected finalize to fail, but it succeeded" + ); + } + + Ok(()) +} + +#[simplex::test] +fn withdraw_all_fails_when_auth_utxo_has_less_than_minimum_amount( + context: simplex::TestContext, +) -> anyhow::Result<()> { + let provider = context.get_default_provider(); + let keeper = context.random_signer(); + + let keeper_auth_asset_amount = 1000; + let (asset_auth_vault, vault_parameters) = default_vault_withdrawing_all_setup( + &context, + &keeper, + vec![5000], + keeper_auth_asset_amount, + false, + )?; + + let asset_auth_vault_utxo = + provider.fetch_scripthash_utxos(&asset_auth_vault.get_script_pubkey())?[0].clone(); + + let current_vault_balance = asset_auth_vault_utxo.explicit_amount(); + + let keeper_auth_utxo = keeper.get_utxos_asset(vault_parameters.keeper_asset_id)?[0].clone(); + + let mut ft = FinalTransaction::new(); + + ft.add_input( + PartialInput::new(keeper_auth_utxo.clone()), + RequiredSignature::NativeEcdsa, + ); + ft.add_output(PartialOutput::new( + keeper.get_address().script_pubkey(), + keeper_auth_asset_amount / 2, + vault_parameters.keeper_asset_id, + )); + ft.add_output(PartialOutput::new( + keeper.get_address().script_pubkey(), + keeper_auth_asset_amount / 2, + vault_parameters.keeper_asset_id, + )); + + asset_auth_vault.attach_withdrawing_all(&mut ft, asset_auth_vault_utxo.clone(), 0, 1); + + ft.add_output(PartialOutput::new( + keeper.get_address().script_pubkey(), + current_vault_balance, + vault_parameters.vault_asset_id, + )); + + let result = keeper.finalize(&ft); + + assert!( + result.is_err(), + "expected finalize to fail, but it succeeded" + ); + + Ok(()) +} + +#[simplex::test] +fn withdraw_all_fails_when_auth_utxo_burned_but_burn_flag_was_not_set( + context: simplex::TestContext, +) -> anyhow::Result<()> { + let provider = context.get_default_provider(); + let keeper = context.random_signer(); + + let (asset_auth_vault, vault_parameters) = + default_vault_withdrawing_all_setup(&context, &keeper, vec![5000], 1, false)?; + + let asset_auth_vault_utxo = + provider.fetch_scripthash_utxos(&asset_auth_vault.get_script_pubkey())?[0].clone(); + + let current_vault_balance = asset_auth_vault_utxo.explicit_amount(); + + let keeper_auth_utxo = keeper.get_utxos_asset(vault_parameters.keeper_asset_id)?[0].clone(); + + let mut ft = FinalTransaction::new(); + + ft.add_input( + PartialInput::new(keeper_auth_utxo.clone()), + RequiredSignature::NativeEcdsa, + ); + ft.add_output(PartialOutput::new( + Script::new_op_return(b"burn"), + keeper_auth_utxo.explicit_amount(), + vault_parameters.keeper_asset_id, + )); + + asset_auth_vault.attach_withdrawing_all(&mut ft, asset_auth_vault_utxo.clone(), 0, 1); + + ft.add_output(PartialOutput::new( + keeper.get_address().script_pubkey(), + current_vault_balance, + vault_parameters.vault_asset_id, + )); + + let result = keeper.finalize(&ft); + + assert!( + result.is_err(), + "expected finalize to fail, but it succeeded" + ); + + Ok(()) +} + +#[simplex::test] +fn withdraw_all_fails_when_auth_utxo_was_not_burned_but_burn_flag_was_set( + context: simplex::TestContext, +) -> anyhow::Result<()> { + let provider = context.get_default_provider(); + let keeper = context.random_signer(); + + let (asset_auth_vault, vault_parameters) = + default_vault_withdrawing_all_setup(&context, &keeper, vec![5000], 1, true)?; + + let asset_auth_vault_utxo = + provider.fetch_scripthash_utxos(&asset_auth_vault.get_script_pubkey())?[0].clone(); + + let current_vault_balance = asset_auth_vault_utxo.explicit_amount(); + + let keeper_auth_utxo = keeper.get_utxos_asset(vault_parameters.keeper_asset_id)?[0].clone(); + + let mut ft = FinalTransaction::new(); + + ft.add_input( + PartialInput::new(keeper_auth_utxo.clone()), + RequiredSignature::NativeEcdsa, + ); + ft.add_output(PartialOutput::new( + keeper.get_address().script_pubkey(), + keeper_auth_utxo.explicit_amount(), + vault_parameters.keeper_asset_id, + )); + + asset_auth_vault.attach_withdrawing_all(&mut ft, asset_auth_vault_utxo.clone(), 0, 1); + + ft.add_output(PartialOutput::new( + keeper.get_address().script_pubkey(), + current_vault_balance, + vault_parameters.vault_asset_id, + )); + + let result = keeper.finalize(&ft); + + assert!( + result.is_err(), + "expected finalize to fail, but it succeeded" + ); + + Ok(()) +} diff --git a/crates/contracts/tests/asset_auth_vault/withdraw_all_success_flows.rs b/crates/contracts/tests/asset_auth_vault/withdraw_all_success_flows.rs new file mode 100644 index 0000000..d3555a6 --- /dev/null +++ b/crates/contracts/tests/asset_auth_vault/withdraw_all_success_flows.rs @@ -0,0 +1,266 @@ +use lending_contracts::programs::asset_auth_vault::{ + FinalizedAssetAuthVault, FinalizedAssetAuthVaultParameters, +}; +use lending_contracts::programs::program::SimplexProgram; + +use simplex::signer::Signer; +use simplex::simplicityhl::elements::Script; +use simplex::transaction::{FinalTransaction, PartialInput, PartialOutput, RequiredSignature}; + +use super::setup::{ + final_supply, fund_keeper, issue_auth_assets, prepare_vault_asset, setup_asset_auth_vault, +}; + +fn default_vault_withdrawing_all_setup( + context: &simplex::TestContext, + keeper: &Signer, + vault_asset_amounts: Vec, + amount_to_supply: u64, + with_keeper_asset_burn: bool, +) -> anyhow::Result<(FinalizedAssetAuthVault, FinalizedAssetAuthVaultParameters)> { + let (supplier_asset_id, keeper_asset_id) = issue_auth_assets(context, 1, 1)?; + + let vault_asset_amount = 1_000_000; + let vault_asset_id = prepare_vault_asset(context, vault_asset_amount, vault_asset_amounts)?; + + let vault_parameters = FinalizedAssetAuthVaultParameters { + vault_asset_id, + keeper_asset_id, + supplier_asset_id, + keeper_min_asset_amount: 1, + with_keeper_asset_burn, + with_supplier_asset_burn: false, + network: *context.get_network(), + }; + + let asset_auth_vault = setup_asset_auth_vault(context, vault_parameters)?; + + let finalized_vault = final_supply(context, &asset_auth_vault, amount_to_supply)?; + + fund_keeper(context, keeper, vault_parameters.keeper_asset_id)?; + + Ok((finalized_vault, vault_parameters)) +} + +#[simplex::test] +fn withdraw_all_succeeds_with_one_explicit_output_without_keeper_asset_burn( + context: simplex::TestContext, +) -> anyhow::Result<()> { + let provider = context.get_default_provider(); + let keeper = context.random_signer(); + + let (finalized_vault, vault_parameters) = + default_vault_withdrawing_all_setup(&context, &keeper, vec![5000], 1000, false)?; + + let finalized_vault_utxo = + provider.fetch_scripthash_utxos(&finalized_vault.get_script_pubkey())?[0].clone(); + + let current_vault_balance = finalized_vault_utxo.explicit_amount(); + + let keeper_auth_utxo = keeper.get_utxos_asset(vault_parameters.keeper_asset_id)?[0].clone(); + + let mut ft = FinalTransaction::new(); + + ft.add_input( + PartialInput::new(keeper_auth_utxo.clone()), + RequiredSignature::NativeEcdsa, + ); + ft.add_output(PartialOutput::new( + keeper.get_address().script_pubkey(), + keeper_auth_utxo.explicit_amount(), + keeper_auth_utxo.explicit_asset(), + )); + + finalized_vault.attach_withdrawing_all(&mut ft, finalized_vault_utxo, 0, 0); + + ft.add_output(PartialOutput::new( + keeper.get_address().script_pubkey(), + current_vault_balance, + vault_parameters.vault_asset_id, + )); + + let txid = keeper.broadcast(&ft)?; + provider.wait(&txid)?; + + Ok(()) +} + +#[simplex::test] +fn withdraw_all_succeeds_with_one_explicit_output_with_keeper_asset_burn( + context: simplex::TestContext, +) -> anyhow::Result<()> { + let provider = context.get_default_provider(); + let keeper = context.random_signer(); + + let (finalized_vault, vault_parameters) = + default_vault_withdrawing_all_setup(&context, &keeper, vec![5000], 1000, true)?; + + let finalized_vault_utxo = + provider.fetch_scripthash_utxos(&finalized_vault.get_script_pubkey())?[0].clone(); + + let current_vault_balance = finalized_vault_utxo.explicit_amount(); + + let keeper_auth_utxo = keeper.get_utxos_asset(vault_parameters.keeper_asset_id)?[0].clone(); + + let mut ft = FinalTransaction::new(); + + ft.add_input( + PartialInput::new(keeper_auth_utxo.clone()), + RequiredSignature::NativeEcdsa, + ); + ft.add_output(PartialOutput::new( + Script::new_op_return(b"burn"), + keeper_auth_utxo.explicit_amount(), + keeper_auth_utxo.explicit_asset(), + )); + + finalized_vault.attach_withdrawing_all(&mut ft, finalized_vault_utxo, 0, 0); + + ft.add_output(PartialOutput::new( + keeper.get_address().script_pubkey(), + current_vault_balance, + vault_parameters.vault_asset_id, + )); + + let txid = keeper.broadcast(&ft)?; + provider.wait(&txid)?; + + Ok(()) +} + +#[simplex::test] +fn withdraw_all_succeeds_with_one_explicit_output( + context: simplex::TestContext, +) -> anyhow::Result<()> { + let provider = context.get_default_provider(); + let keeper = context.random_signer(); + + let (finalized_vault, vault_parameters) = + default_vault_withdrawing_all_setup(&context, &keeper, vec![5000], 1000, false)?; + + let finalized_vault_utxo = + provider.fetch_scripthash_utxos(&finalized_vault.get_script_pubkey())?[0].clone(); + + let current_vault_balance = finalized_vault_utxo.explicit_amount(); + + let keeper_auth_utxo = keeper.get_utxos_asset(vault_parameters.keeper_asset_id)?[0].clone(); + + let mut ft = FinalTransaction::new(); + + ft.add_input( + PartialInput::new(keeper_auth_utxo.clone()), + RequiredSignature::NativeEcdsa, + ); + ft.add_output(PartialOutput::new( + keeper.get_address().script_pubkey(), + keeper_auth_utxo.explicit_amount(), + keeper_auth_utxo.explicit_asset(), + )); + + finalized_vault.attach_withdrawing_all(&mut ft, finalized_vault_utxo, 0, 0); + + ft.add_output(PartialOutput::new( + keeper.get_address().script_pubkey(), + current_vault_balance, + vault_parameters.vault_asset_id, + )); + + let txid = keeper.broadcast(&ft)?; + provider.wait(&txid)?; + + Ok(()) +} + +#[simplex::test] +fn withdraw_all_succeeds_with_several_explicit_outputs( + context: simplex::TestContext, +) -> anyhow::Result<()> { + let provider = context.get_default_provider(); + let keeper = context.random_signer(); + + let (finalized_vault, vault_parameters) = + default_vault_withdrawing_all_setup(&context, &keeper, vec![5000], 1000, false)?; + + let finalized_vault_utxo = + provider.fetch_scripthash_utxos(&finalized_vault.get_script_pubkey())?[0].clone(); + + let current_vault_balance = finalized_vault_utxo.explicit_amount(); + + let keeper_auth_utxo = keeper.get_utxos_asset(vault_parameters.keeper_asset_id)?[0].clone(); + + let mut ft = FinalTransaction::new(); + + ft.add_input( + PartialInput::new(keeper_auth_utxo.clone()), + RequiredSignature::NativeEcdsa, + ); + ft.add_output(PartialOutput::new( + keeper.get_address().script_pubkey(), + keeper_auth_utxo.explicit_amount(), + keeper_auth_utxo.explicit_asset(), + )); + + finalized_vault.attach_withdrawing_all(&mut ft, finalized_vault_utxo, 0, 0); + + ft.add_output(PartialOutput::new( + keeper.get_address().script_pubkey(), + current_vault_balance / 2, + vault_parameters.vault_asset_id, + )); + ft.add_output(PartialOutput::new( + keeper.get_address().script_pubkey(), + current_vault_balance / 2, + vault_parameters.vault_asset_id, + )); + + let txid = keeper.broadcast(&ft)?; + provider.wait(&txid)?; + + Ok(()) +} + +#[simplex::test] +fn withdraw_all_succeeds_with_confidential_output( + context: simplex::TestContext, +) -> anyhow::Result<()> { + let provider = context.get_default_provider(); + let keeper = context.random_signer(); + + let (finalized_vault, vault_parameters) = + default_vault_withdrawing_all_setup(&context, &keeper, vec![5000], 1000, false)?; + + let finalized_vault_utxo = + provider.fetch_scripthash_utxos(&finalized_vault.get_script_pubkey())?[0].clone(); + + let current_vault_balance = finalized_vault_utxo.explicit_amount(); + + let keeper_auth_utxo = keeper.get_utxos_asset(vault_parameters.keeper_asset_id)?[0].clone(); + + let mut ft = FinalTransaction::new(); + + ft.add_input( + PartialInput::new(keeper_auth_utxo.clone()), + RequiredSignature::NativeEcdsa, + ); + ft.add_output(PartialOutput::new( + keeper.get_address().script_pubkey(), + keeper_auth_utxo.explicit_amount(), + keeper_auth_utxo.explicit_asset(), + )); + + finalized_vault.attach_withdrawing_all(&mut ft, finalized_vault_utxo, 0, 0); + + ft.add_output( + PartialOutput::new( + keeper.get_confidential_address().script_pubkey(), + current_vault_balance, + vault_parameters.vault_asset_id, + ) + .with_blinding_key(keeper.get_blinding_public_key()), + ); + + let txid = keeper.broadcast(&ft)?; + provider.wait(&txid)?; + + Ok(()) +} From b03098b08754c029ef0816671160e11ee12d28e5 Mon Sep 17 00:00:00 2001 From: Oleh Komendant Date: Tue, 5 May 2026 22:38:17 +0300 Subject: [PATCH 6/6] Fix CI --- .github/workflows/lint.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 3a2d86b..4d98081 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -16,7 +16,7 @@ jobs: runs-on: ubuntu-latest env: SQLX_OFFLINE: true - SIMPLEX_VERSION: v0.0.3 + SIMPLEX_VERSION: v0.0.4 steps: - name: Checkout