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 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/contracts/simf/asset_auth_vault.simf b/crates/contracts/simf/asset_auth_vault.simf new file mode 100644 index 0000000..d24a7fe --- /dev/null +++ b/crates/contracts/simf/asset_auth_vault.simf @@ -0,0 +1,244 @@ +// 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) +} + +fn is_op_return(output_index: u32) -> bool { + match jet::output_null_datum(output_index, 0) { + Some(entry: Option>>) => true, + None => false, + } +} + +// 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) { + 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 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) { + 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) { + 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_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) { + 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 +} + +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); + + 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); +} + +// Auth logic + +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 +) { + 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 => { + ensure_output_is_not_an_op_return(output_asset_index) + }, + } +} + +// Main paths logic + +fn withdraw_all(input_keeper_index: u32, output_keeper_index: u32) { + ensure_active_status(false); + + 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_active_status(true); + + auth_with_burn_check( + input_keeper_index, + output_keeper_index, + param::KEEPER_AUTH_ASSET_ID, + param::KEEPER_AUTH_ASSET_AMOUNT, + false + ); + + 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_active_status(true); + + auth_with_burn_check( + input_supplier_index, + output_supplier_index, + param::SUPPLIER_AUTH_ASSET_ID, + 1, // Allow any amount of the supplier auth asset + false + ); + + ensure_output_script_hash(vault_output_index, jet::current_script_hash()); + + ensure_vault_amount_after_supplying(vault_output_index, amount_to_supply); +} + +fn final_supply( + input_supplier_index: u32, + output_supplier_index: u32, + finalized_vault_output_index: u32, + amount_to_supply: u64 +) { + ensure_active_status(true); + + 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); + + ensure_vault_amount_after_supplying(finalized_vault_output_index, amount_to_supply); +} + +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..6f9df34 --- /dev/null +++ b/crates/contracts/src/programs/asset_auth_vault/core.rs @@ -0,0 +1,199 @@ +use simplex::{ + program::Program, + provider::SimplicityNetwork, + transaction::{FinalTransaction, UTXO}, +}; + +use crate::artifacts::asset_auth_vault::AssetAuthVaultProgram; +use crate::programs::asset_auth_vault::{ + ActiveAssetAuthVaultParameters, AssetAuthVaultWitnessBranch, FinalizedAssetAuthVaultParameters, +}; +use crate::programs::program::SimplexProgram; + +pub struct ActiveAssetAuthVault { + program: AssetAuthVaultProgram, + parameters: ActiveAssetAuthVaultParameters, +} + +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 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::new(active_vault_parameters) + } + + pub fn get_parameters(&self) -> &ActiveAssetAuthVaultParameters { + &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_partial_withdrawing( + &self, + ft: &mut FinalTransaction, + program_utxo: UTXO, + input_keeper_index: u32, + output_keeper_index: u32, + amount_to_withdraw: u64, + ) { + let current_vault_amount = program_utxo.explicit_amount(); + + assert!( + amount_to_withdraw < current_vault_amount, + "Invalid 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, + current_vault_amount - amount_to_withdraw, + ); + } + + pub fn attach_supplying( + &self, + ft: &mut FinalTransaction, + program_utxo: UTXO, + input_supplier_index: u32, + output_supplier_index: u32, + amount_to_supply: u64, + ) { + 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, + ) -> FinalizedAssetAuthVault { + 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 = FinalizedAssetAuthVault::new(self.parameters.into()); + + finalized_vault.attach_creation(ft, new_vault_amount); + + finalized_vault + } +} + +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() + } + + 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..2db5508 --- /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::{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 new file mode 100644 index 0000000..86ab6a4 --- /dev/null +++ b/crates/contracts/src/programs/asset_auth_vault/params.rs @@ -0,0 +1,86 @@ +use simplex::{provider::SimplicityNetwork, simplicityhl::elements::AssetId}; + +use crate::artifacts::asset_auth_vault::derived_asset_auth_vault::AssetAuthVaultArguments; + +#[derive(Debug, Clone, Copy)] +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, +} + +#[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: 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, + } + } +} + +impl ActiveAssetAuthVaultParameters { + pub fn from_finalized_parameters( + parameters: &FinalizedAssetAuthVaultParameters, + finalized_vault_hash: [u8; 32], + ) -> Self { + Self { + 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 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_active: true, + with_keeper_asset_burn: self.with_keeper_asset_burn, + with_supplier_asset_burn: self.with_supplier_asset_burn, + } + } +} + +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, + } + } +} 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; 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..f24c0cf --- /dev/null +++ b/crates/contracts/tests/asset_auth_vault/final_supply_failure_flows.rs @@ -0,0 +1,236 @@ +use lending_contracts::programs::asset_auth_vault::{ + ActiveAssetAuthVault, ActiveAssetAuthVaultParameters, FinalizedAssetAuthVaultParameters, +}; +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<(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 = FinalizedAssetAuthVaultParameters { + vault_asset_id, + keeper_asset_id, + supplier_asset_id, + keeper_min_asset_amount: 1, + with_keeper_asset_burn: false, + with_supplier_asset_burn, + 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, active_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, + ); + + 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..34e737c --- /dev/null +++ b/crates/contracts/tests/asset_auth_vault/final_supply_success_flows.rs @@ -0,0 +1,201 @@ +use lending_contracts::programs::asset_auth_vault::{ + ActiveAssetAuthVault, ActiveAssetAuthVaultParameters, FinalizedAssetAuthVaultParameters, +}; +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<(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 = FinalizedAssetAuthVaultParameters { + vault_asset_id, + keeper_asset_id, + supplier_asset_id, + keeper_min_asset_amount: 1, + with_keeper_asset_burn: false, + with_supplier_asset_burn, + 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, active_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..501b7f2 --- /dev/null +++ b/crates/contracts/tests/asset_auth_vault/mod.rs @@ -0,0 +1,12 @@ +#[path = "../common/mod.rs"] +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 new file mode 100644 index 0000000..66c334d --- /dev/null +++ b/crates/contracts/tests/asset_auth_vault/setup.rs @@ -0,0 +1,322 @@ +use lending_contracts::programs::asset_auth_vault::{ + ActiveAssetAuthVault, FinalizedAssetAuthVault, FinalizedAssetAuthVaultParameters, +}; + +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::{ + 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: FinalizedAssetAuthVaultParameters, +) -> 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 = ActiveAssetAuthVault::from_finalized_vault(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 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, + 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 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, + 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..72f8d97 --- /dev/null +++ b/crates/contracts/tests/asset_auth_vault/supply_failure_flows.rs @@ -0,0 +1,182 @@ +use lending_contracts::programs::asset_auth_vault::{ + ActiveAssetAuthVault, ActiveAssetAuthVaultParameters, FinalizedAssetAuthVaultParameters, +}; +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<(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 = 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(); + + Ok((asset_auth_vault, active_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, + ); + + 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..591ac70 --- /dev/null +++ b/crates/contracts/tests/asset_auth_vault/supply_success_flows.rs @@ -0,0 +1,191 @@ +use lending_contracts::programs::asset_auth_vault::{ + ActiveAssetAuthVault, ActiveAssetAuthVaultParameters, FinalizedAssetAuthVaultParameters, +}; +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<(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(); + + Ok((asset_auth_vault, active_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/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(()) +} 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(()) } 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,