mirror of
https://github.com/aljazceru/breez-sdk-liquid.git
synced 2025-12-18 22:44:22 +01:00
Fix clippy test issues (#850)
* Fix clippy test issues * Address review feedback
This commit is contained in:
@@ -5,5 +5,5 @@ set -e
|
|||||||
# Cargo syntax checks
|
# Cargo syntax checks
|
||||||
dirs=("lib" "cli")
|
dirs=("lib" "cli")
|
||||||
for dir in ${dirs[@]}; do
|
for dir in ${dirs[@]}; do
|
||||||
(cd $dir; exec cargo fmt; exec cargo clippy -- -D warnings)
|
(cd $dir; exec cargo fmt; exec cargo clippy --all-targets -- -D warnings)
|
||||||
done
|
done
|
||||||
|
|||||||
4
.github/workflows/main.yml
vendored
4
.github/workflows/main.yml
vendored
@@ -48,11 +48,11 @@ jobs:
|
|||||||
|
|
||||||
- name: Clippy bindings
|
- name: Clippy bindings
|
||||||
working-directory: lib/bindings
|
working-directory: lib/bindings
|
||||||
run: cargo clippy -- -D warnings
|
run: cargo clippy --all-targets -- -D warnings
|
||||||
|
|
||||||
- name: Clippy core
|
- name: Clippy core
|
||||||
working-directory: lib/core
|
working-directory: lib/core
|
||||||
run: cargo clippy -- -D warnings
|
run: cargo clippy --all-targets -- -D warnings
|
||||||
|
|
||||||
- name: Clippy cli
|
- name: Clippy cli
|
||||||
working-directory: cli
|
working-directory: cli
|
||||||
|
|||||||
6
Makefile
6
Makefile
@@ -12,10 +12,8 @@ fmt:
|
|||||||
clippy: cargo-clippy wasm-clippy
|
clippy: cargo-clippy wasm-clippy
|
||||||
|
|
||||||
cargo-clippy:
|
cargo-clippy:
|
||||||
cd lib/bindings && cargo clippy -- -D warnings
|
cd lib/bindings && cargo clippy --all-targets -- -D warnings
|
||||||
cd lib/bindings && cargo clippy --tests -- -D warnings
|
cd lib/core && cargo clippy --all-targets -- -D warnings
|
||||||
cd lib/core && cargo clippy -- -D warnings
|
|
||||||
cd lib/core && cargo clippy --tests -- -D warnings
|
|
||||||
cd cli && cargo clippy -- -D warnings
|
cd cli && cargo clippy -- -D warnings
|
||||||
|
|
||||||
wasm-clippy:
|
wasm-clippy:
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ test: cargo-test wasm-test
|
|||||||
regtest-test: cargo-regtest-test wasm-regtest-test
|
regtest-test: cargo-regtest-test wasm-regtest-test
|
||||||
|
|
||||||
cargo-clippy:
|
cargo-clippy:
|
||||||
cargo clippy -- -D warnings
|
cargo clippy --all-targets -- -D warnings
|
||||||
|
|
||||||
cargo-test:
|
cargo-test:
|
||||||
cargo test
|
cargo test
|
||||||
@@ -40,7 +40,7 @@ cargo-regtest-test: check-regtest
|
|||||||
$(REGTEST_PREFIX) cargo test $(REGTEST_TESTS) --features "regtest"
|
$(REGTEST_PREFIX) cargo test $(REGTEST_TESTS) --features "regtest"
|
||||||
|
|
||||||
wasm-clippy:
|
wasm-clippy:
|
||||||
$(CLANG_PREFIX) cargo clippy --target=wasm32-unknown-unknown -- -D warnings
|
$(CLANG_PREFIX) cargo clippy --all-targets --target=wasm32-unknown-unknown -- -D warnings
|
||||||
|
|
||||||
BROWSER ?= firefox
|
BROWSER ?= firefox
|
||||||
|
|
||||||
|
|||||||
@@ -1,200 +1,191 @@
|
|||||||
#[cfg(test)]
|
#![cfg(test)]
|
||||||
mod tests {
|
use anyhow::Result;
|
||||||
use anyhow::Result;
|
use bip39::rand::{self, RngCore};
|
||||||
use bip39::rand::{self, RngCore};
|
use lwk_wollet::bitcoin;
|
||||||
use lwk_wollet::bitcoin;
|
use lwk_wollet::elements::address::AddressParams;
|
||||||
use lwk_wollet::elements::address::AddressParams;
|
use lwk_wollet::elements::confidential::{Asset, AssetBlindingFactor, Value, ValueBlindingFactor};
|
||||||
use lwk_wollet::elements::confidential::{
|
use lwk_wollet::elements::secp256k1_zkp::SecretKey;
|
||||||
Asset, AssetBlindingFactor, Value, ValueBlindingFactor,
|
use lwk_wollet::elements::{secp256k1_zkp, Address, AssetId, Script, TxOutSecrets, Txid};
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
use crate::payjoin::pset::{construct_pset, ConstructPsetRequest, PsetInput, PsetOutput};
|
||||||
|
|
||||||
|
#[cfg(all(target_family = "wasm", target_os = "unknown"))]
|
||||||
|
wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser);
|
||||||
|
|
||||||
|
fn create_test_secret_key() -> SecretKey {
|
||||||
|
let mut rng = rand::thread_rng();
|
||||||
|
let mut buf = [0u8; 32];
|
||||||
|
rng.fill_bytes(&mut buf);
|
||||||
|
SecretKey::from_slice(&buf).expect("Expected valid secret key")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_test_input(asset_id: AssetId, sk: &SecretKey) -> PsetInput {
|
||||||
|
// Create a dummy txid
|
||||||
|
let txid =
|
||||||
|
Txid::from_str("0000000000000000000000000000000000000000000000000000000000000001").unwrap();
|
||||||
|
|
||||||
|
// Create a dummy script pubkey
|
||||||
|
let script_pub_key =
|
||||||
|
Script::from_str("76a914000000000000000000000000000000000000000088ac").unwrap();
|
||||||
|
|
||||||
|
// Create dummy asset and value commitments
|
||||||
|
let secp = secp256k1_zkp::Secp256k1::new();
|
||||||
|
|
||||||
|
let asset_bf = AssetBlindingFactor::from_slice(&sk.secret_bytes()).unwrap();
|
||||||
|
let asset_gen =
|
||||||
|
secp256k1_zkp::Generator::new_blinded(&secp, asset_id.into_tag(), asset_bf.into_inner());
|
||||||
|
let asset_commitment = Asset::Confidential(asset_gen);
|
||||||
|
|
||||||
|
// Create a Pedersen commitment for the value
|
||||||
|
let value_bf = ValueBlindingFactor::from_slice(&sk.secret_bytes()).unwrap();
|
||||||
|
let value_commit =
|
||||||
|
secp256k1_zkp::PedersenCommitment::new(&secp, 10000, value_bf.into_inner(), asset_gen);
|
||||||
|
let value_commitment = Value::Confidential(value_commit);
|
||||||
|
|
||||||
|
// Create dummy txout secrets
|
||||||
|
let tx_out_sec = TxOutSecrets {
|
||||||
|
asset: asset_id,
|
||||||
|
value: 10000,
|
||||||
|
asset_bf,
|
||||||
|
value_bf,
|
||||||
};
|
};
|
||||||
use lwk_wollet::elements::secp256k1_zkp::SecretKey;
|
|
||||||
use lwk_wollet::elements::{secp256k1_zkp, Address, AssetId, Script, TxOutSecrets, Txid};
|
|
||||||
use std::str::FromStr;
|
|
||||||
|
|
||||||
use crate::payjoin::pset::{construct_pset, ConstructPsetRequest, PsetInput, PsetOutput};
|
PsetInput {
|
||||||
|
txid,
|
||||||
#[cfg(all(target_family = "wasm", target_os = "unknown"))]
|
vout: 0,
|
||||||
wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser);
|
script_pub_key,
|
||||||
|
asset_commitment,
|
||||||
fn create_test_secret_key() -> SecretKey {
|
value_commitment,
|
||||||
let mut rng = rand::thread_rng();
|
tx_out_sec,
|
||||||
let mut buf = [0u8; 32];
|
|
||||||
rng.fill_bytes(&mut buf);
|
|
||||||
SecretKey::from_slice(&buf).expect("Expected valid secret key")
|
|
||||||
}
|
|
||||||
|
|
||||||
fn create_test_input(asset_id: AssetId, sk: &SecretKey) -> PsetInput {
|
|
||||||
// Create a dummy txid
|
|
||||||
let txid =
|
|
||||||
Txid::from_str("0000000000000000000000000000000000000000000000000000000000000001")
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
// Create a dummy script pubkey
|
|
||||||
let script_pub_key =
|
|
||||||
Script::from_str("76a914000000000000000000000000000000000000000088ac").unwrap();
|
|
||||||
|
|
||||||
// Create dummy asset and value commitments
|
|
||||||
let secp = secp256k1_zkp::Secp256k1::new();
|
|
||||||
|
|
||||||
let asset_bf = AssetBlindingFactor::from_slice(&sk.secret_bytes()).unwrap();
|
|
||||||
let asset_gen = secp256k1_zkp::Generator::new_blinded(
|
|
||||||
&secp,
|
|
||||||
asset_id.into_tag(),
|
|
||||||
asset_bf.into_inner(),
|
|
||||||
);
|
|
||||||
let asset_commitment = Asset::Confidential(asset_gen);
|
|
||||||
|
|
||||||
// Create a Pedersen commitment for the value
|
|
||||||
let value_bf = ValueBlindingFactor::from_slice(&sk.secret_bytes()).unwrap();
|
|
||||||
let value_commit =
|
|
||||||
secp256k1_zkp::PedersenCommitment::new(&secp, 10000, value_bf.into_inner(), asset_gen);
|
|
||||||
let value_commitment = Value::Confidential(value_commit);
|
|
||||||
|
|
||||||
// Create dummy txout secrets
|
|
||||||
let tx_out_sec = TxOutSecrets {
|
|
||||||
asset: asset_id,
|
|
||||||
value: 10000,
|
|
||||||
asset_bf,
|
|
||||||
value_bf,
|
|
||||||
};
|
|
||||||
|
|
||||||
PsetInput {
|
|
||||||
txid,
|
|
||||||
vout: 0,
|
|
||||||
script_pub_key,
|
|
||||||
asset_commitment,
|
|
||||||
value_commitment,
|
|
||||||
tx_out_sec,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn create_test_output(asset_id: AssetId, sk: &SecretKey) -> PsetOutput {
|
|
||||||
// Create a dummy blinded address
|
|
||||||
let secp = secp256k1_zkp::Secp256k1::new();
|
|
||||||
let blinding_key =
|
|
||||||
bitcoin::PublicKey::new(secp256k1_zkp::PublicKey::from_secret_key(&secp, sk));
|
|
||||||
let address_pk =
|
|
||||||
bitcoin::PublicKey::new(secp256k1_zkp::PublicKey::from_secret_key(&secp, sk));
|
|
||||||
|
|
||||||
let address = Address::p2pkh(
|
|
||||||
&address_pk,
|
|
||||||
Some(blinding_key.inner),
|
|
||||||
&AddressParams::LIQUID,
|
|
||||||
);
|
|
||||||
|
|
||||||
PsetOutput {
|
|
||||||
address,
|
|
||||||
asset_id,
|
|
||||||
amount: 5000,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[sdk_macros::test_all]
|
|
||||||
fn test_construct_pset_basic() -> Result<()> {
|
|
||||||
// Create test data
|
|
||||||
let asset_id = AssetId::from_slice(&[2; 32]).unwrap();
|
|
||||||
let secret_key = create_test_secret_key();
|
|
||||||
|
|
||||||
let policy_asset = AssetId::from_slice(&[8; 32]).unwrap();
|
|
||||||
let inputs = vec![
|
|
||||||
create_test_input(asset_id, &secret_key),
|
|
||||||
create_test_input(asset_id, &secret_key),
|
|
||||||
];
|
|
||||||
let outputs = vec![create_test_output(asset_id, &secret_key)];
|
|
||||||
let network_fee = 1000;
|
|
||||||
|
|
||||||
let request = ConstructPsetRequest {
|
|
||||||
policy_asset,
|
|
||||||
inputs,
|
|
||||||
outputs,
|
|
||||||
network_fee,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Call the function
|
|
||||||
let pset = construct_pset(request)?;
|
|
||||||
|
|
||||||
// Validate the result
|
|
||||||
assert_eq!(pset.inputs().len(), 2);
|
|
||||||
assert_eq!(pset.outputs().len(), 2); // 1 regular output + 1 fee output
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[sdk_macros::test_all]
|
|
||||||
fn test_construct_pset_multiple_outputs() -> Result<()> {
|
|
||||||
// Create test data
|
|
||||||
let asset_id = AssetId::from_slice(&[3; 32]).unwrap();
|
|
||||||
let secret_key = create_test_secret_key();
|
|
||||||
|
|
||||||
let policy_asset = AssetId::from_slice(&[8; 32]).unwrap();
|
|
||||||
let inputs = vec![create_test_input(asset_id, &secret_key)];
|
|
||||||
let outputs = vec![
|
|
||||||
create_test_output(asset_id, &secret_key),
|
|
||||||
create_test_output(asset_id, &secret_key),
|
|
||||||
create_test_output(asset_id, &secret_key),
|
|
||||||
];
|
|
||||||
let network_fee = 1000;
|
|
||||||
|
|
||||||
let request = ConstructPsetRequest {
|
|
||||||
policy_asset,
|
|
||||||
inputs,
|
|
||||||
outputs,
|
|
||||||
network_fee,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Call the function
|
|
||||||
let pset = construct_pset(request)?;
|
|
||||||
|
|
||||||
// Validate the result
|
|
||||||
assert_eq!(pset.inputs().len(), 1);
|
|
||||||
assert_eq!(pset.outputs().len(), 4); // 3 regular outputs + 1 fee output
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[sdk_macros::test_all]
|
|
||||||
fn test_construct_pset_empty_inputs() {
|
|
||||||
// Create test data
|
|
||||||
let asset_id = AssetId::from_slice(&[4; 32]).unwrap();
|
|
||||||
let secret_key = create_test_secret_key();
|
|
||||||
|
|
||||||
let policy_asset = AssetId::from_slice(&[8; 32]).unwrap();
|
|
||||||
let inputs = vec![];
|
|
||||||
let outputs = vec![create_test_output(asset_id, &secret_key)];
|
|
||||||
let network_fee = 1000;
|
|
||||||
|
|
||||||
let request = ConstructPsetRequest {
|
|
||||||
policy_asset,
|
|
||||||
inputs,
|
|
||||||
outputs,
|
|
||||||
network_fee,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Blinding should fail with empty inputs
|
|
||||||
let result = construct_pset(request);
|
|
||||||
assert!(result.is_err());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[sdk_macros::test_all]
|
|
||||||
fn test_construct_pset_empty_outputs() {
|
|
||||||
// Create test data
|
|
||||||
let asset_id = AssetId::from_slice(&[5; 32]).unwrap();
|
|
||||||
let secret_key = create_test_secret_key();
|
|
||||||
|
|
||||||
let policy_asset = AssetId::from_slice(&[8; 32]).unwrap();
|
|
||||||
let inputs = vec![create_test_input(asset_id, &secret_key)];
|
|
||||||
let outputs = vec![];
|
|
||||||
let network_fee = 1000;
|
|
||||||
|
|
||||||
let request = ConstructPsetRequest {
|
|
||||||
policy_asset,
|
|
||||||
inputs,
|
|
||||||
outputs,
|
|
||||||
network_fee,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Call the function
|
|
||||||
let result = construct_pset(request);
|
|
||||||
assert!(result.is_err());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn create_test_output(asset_id: AssetId, sk: &SecretKey) -> PsetOutput {
|
||||||
|
// Create a dummy blinded address
|
||||||
|
let secp = secp256k1_zkp::Secp256k1::new();
|
||||||
|
let blinding_key =
|
||||||
|
bitcoin::PublicKey::new(secp256k1_zkp::PublicKey::from_secret_key(&secp, sk));
|
||||||
|
let address_pk = bitcoin::PublicKey::new(secp256k1_zkp::PublicKey::from_secret_key(&secp, sk));
|
||||||
|
|
||||||
|
let address = Address::p2pkh(
|
||||||
|
&address_pk,
|
||||||
|
Some(blinding_key.inner),
|
||||||
|
&AddressParams::LIQUID,
|
||||||
|
);
|
||||||
|
|
||||||
|
PsetOutput {
|
||||||
|
address,
|
||||||
|
asset_id,
|
||||||
|
amount: 5000,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[sdk_macros::test_all]
|
||||||
|
fn test_construct_pset_basic() -> Result<()> {
|
||||||
|
// Create test data
|
||||||
|
let asset_id = AssetId::from_slice(&[2; 32]).unwrap();
|
||||||
|
let secret_key = create_test_secret_key();
|
||||||
|
|
||||||
|
let policy_asset = AssetId::from_slice(&[8; 32]).unwrap();
|
||||||
|
let inputs = vec![
|
||||||
|
create_test_input(asset_id, &secret_key),
|
||||||
|
create_test_input(asset_id, &secret_key),
|
||||||
|
];
|
||||||
|
let outputs = vec![create_test_output(asset_id, &secret_key)];
|
||||||
|
let network_fee = 1000;
|
||||||
|
|
||||||
|
let request = ConstructPsetRequest {
|
||||||
|
policy_asset,
|
||||||
|
inputs,
|
||||||
|
outputs,
|
||||||
|
network_fee,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Call the function
|
||||||
|
let pset = construct_pset(request)?;
|
||||||
|
|
||||||
|
// Validate the result
|
||||||
|
assert_eq!(pset.inputs().len(), 2);
|
||||||
|
assert_eq!(pset.outputs().len(), 2); // 1 regular output + 1 fee output
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[sdk_macros::test_all]
|
||||||
|
fn test_construct_pset_multiple_outputs() -> Result<()> {
|
||||||
|
// Create test data
|
||||||
|
let asset_id = AssetId::from_slice(&[3; 32]).unwrap();
|
||||||
|
let secret_key = create_test_secret_key();
|
||||||
|
|
||||||
|
let policy_asset = AssetId::from_slice(&[8; 32]).unwrap();
|
||||||
|
let inputs = vec![create_test_input(asset_id, &secret_key)];
|
||||||
|
let outputs = vec![
|
||||||
|
create_test_output(asset_id, &secret_key),
|
||||||
|
create_test_output(asset_id, &secret_key),
|
||||||
|
create_test_output(asset_id, &secret_key),
|
||||||
|
];
|
||||||
|
let network_fee = 1000;
|
||||||
|
|
||||||
|
let request = ConstructPsetRequest {
|
||||||
|
policy_asset,
|
||||||
|
inputs,
|
||||||
|
outputs,
|
||||||
|
network_fee,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Call the function
|
||||||
|
let pset = construct_pset(request)?;
|
||||||
|
|
||||||
|
// Validate the result
|
||||||
|
assert_eq!(pset.inputs().len(), 1);
|
||||||
|
assert_eq!(pset.outputs().len(), 4); // 3 regular outputs + 1 fee output
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[sdk_macros::test_all]
|
||||||
|
fn test_construct_pset_empty_inputs() {
|
||||||
|
// Create test data
|
||||||
|
let asset_id = AssetId::from_slice(&[4; 32]).unwrap();
|
||||||
|
let secret_key = create_test_secret_key();
|
||||||
|
|
||||||
|
let policy_asset = AssetId::from_slice(&[8; 32]).unwrap();
|
||||||
|
let inputs = vec![];
|
||||||
|
let outputs = vec![create_test_output(asset_id, &secret_key)];
|
||||||
|
let network_fee = 1000;
|
||||||
|
|
||||||
|
let request = ConstructPsetRequest {
|
||||||
|
policy_asset,
|
||||||
|
inputs,
|
||||||
|
outputs,
|
||||||
|
network_fee,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Blinding should fail with empty inputs
|
||||||
|
let result = construct_pset(request);
|
||||||
|
assert!(result.is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[sdk_macros::test_all]
|
||||||
|
fn test_construct_pset_empty_outputs() {
|
||||||
|
// Create test data
|
||||||
|
let asset_id = AssetId::from_slice(&[5; 32]).unwrap();
|
||||||
|
let secret_key = create_test_secret_key();
|
||||||
|
|
||||||
|
let policy_asset = AssetId::from_slice(&[8; 32]).unwrap();
|
||||||
|
let inputs = vec![create_test_input(asset_id, &secret_key)];
|
||||||
|
let outputs = vec![];
|
||||||
|
let network_fee = 1000;
|
||||||
|
|
||||||
|
let request = ConstructPsetRequest {
|
||||||
|
policy_asset,
|
||||||
|
inputs,
|
||||||
|
outputs,
|
||||||
|
network_fee,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Call the function
|
||||||
|
let result = construct_pset(request);
|
||||||
|
assert!(result.is_err());
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,388 +1,386 @@
|
|||||||
#[cfg(test)]
|
#![cfg(test)]
|
||||||
mod tests {
|
use std::collections::BTreeMap;
|
||||||
use std::collections::BTreeMap;
|
|
||||||
|
|
||||||
use lwk_wollet::elements::AssetId;
|
use lwk_wollet::elements::AssetId;
|
||||||
|
|
||||||
use crate::payjoin::{
|
use crate::payjoin::{
|
||||||
model::InOut,
|
model::InOut,
|
||||||
utxo_select::{
|
utxo_select::{
|
||||||
utxo_select, utxo_select_basic, utxo_select_best, utxo_select_fixed,
|
utxo_select, utxo_select_basic, utxo_select_best, utxo_select_fixed, utxo_select_in_range,
|
||||||
utxo_select_in_range, UtxoSelectRequest,
|
UtxoSelectRequest,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[cfg(all(target_family = "wasm", target_os = "unknown"))]
|
||||||
|
wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser);
|
||||||
|
|
||||||
|
#[sdk_macros::test_all]
|
||||||
|
fn test_utxo_select_basic() {
|
||||||
|
// Basic case - should select UTXOs in order until target is met
|
||||||
|
let utxos = vec![100, 200, 300, 400];
|
||||||
|
let selected = utxo_select_basic(300, &utxos);
|
||||||
|
assert_eq!(selected, Some(vec![100, 200]));
|
||||||
|
|
||||||
|
// Exact match with one UTXO
|
||||||
|
let selected = utxo_select_basic(300, &[300, 400, 500]);
|
||||||
|
assert_eq!(selected, Some(vec![300]));
|
||||||
|
|
||||||
|
// First UTXO is enough
|
||||||
|
let selected = utxo_select_basic(50, &[100, 200, 300]);
|
||||||
|
assert_eq!(selected, Some(vec![100]));
|
||||||
|
|
||||||
|
// Need all UTXOs
|
||||||
|
let selected = utxo_select_basic(590, &[100, 200, 300]);
|
||||||
|
assert_eq!(selected, Some(vec![100, 200, 300]));
|
||||||
|
|
||||||
|
// Not enough UTXOs available
|
||||||
|
let selected = utxo_select_basic(1000, &[100, 200, 300]);
|
||||||
|
assert_eq!(selected, None);
|
||||||
|
|
||||||
|
// Empty UTXO list
|
||||||
|
let selected = utxo_select_basic(100, &[]);
|
||||||
|
assert_eq!(selected, None);
|
||||||
|
|
||||||
|
// Zero target amount
|
||||||
|
let selected = utxo_select_basic(0, &[100, 200]);
|
||||||
|
assert_eq!(selected, Some(vec![]));
|
||||||
|
|
||||||
|
// Large values to check for overflow
|
||||||
|
let large_value = u64::MAX / 3;
|
||||||
|
let utxos = vec![large_value, large_value, large_value];
|
||||||
|
let selected = utxo_select_basic(large_value * 2, &utxos);
|
||||||
|
assert_eq!(selected, Some(vec![large_value, large_value]));
|
||||||
|
|
||||||
|
// UTXO order matters - should take in original order
|
||||||
|
let utxos = vec![400, 100, 300, 200];
|
||||||
|
let selected = utxo_select_basic(450, &utxos);
|
||||||
|
assert_eq!(selected, Some(vec![400, 100]));
|
||||||
|
|
||||||
|
// With just-enough UTXOs
|
||||||
|
let utxos = vec![100, 200, 300, 400];
|
||||||
|
let selected = utxo_select_basic(1000, &utxos);
|
||||||
|
assert_eq!(selected, Some(vec![100, 200, 300, 400]));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[sdk_macros::test_all]
|
||||||
|
fn test_utxo_select_fixed() {
|
||||||
|
let utxos = vec![100, 200, 300, 400];
|
||||||
|
|
||||||
|
// Should take first two UTXOs (100 + 200 = 300)
|
||||||
|
let selected = utxo_select_fixed(300, 2, &utxos);
|
||||||
|
assert_eq!(selected, Some(vec![100, 200]));
|
||||||
|
|
||||||
|
// Not enough with just one UTXO
|
||||||
|
let selected = utxo_select_fixed(150, 1, &utxos);
|
||||||
|
assert_eq!(selected, None);
|
||||||
|
|
||||||
|
// Target exceeds available in requested count
|
||||||
|
let selected = utxo_select_fixed(350, 2, &utxos);
|
||||||
|
assert_eq!(selected, None);
|
||||||
|
|
||||||
|
// With exactly the required amount
|
||||||
|
let selected = utxo_select_fixed(300, 1, &[300]);
|
||||||
|
assert_eq!(selected, Some(vec![300]));
|
||||||
|
|
||||||
|
// With empty utxos
|
||||||
|
let selected = utxo_select_fixed(100, 1, &[]);
|
||||||
|
assert_eq!(selected, None);
|
||||||
|
|
||||||
|
// With zero target value
|
||||||
|
let selected = utxo_select_fixed(0, 2, &utxos);
|
||||||
|
assert_eq!(selected, Some(vec![100, 200]));
|
||||||
|
|
||||||
|
// With zero target count
|
||||||
|
let selected = utxo_select_fixed(100, 0, &utxos);
|
||||||
|
assert_eq!(selected, None);
|
||||||
|
|
||||||
|
// With more UTXOs than requested count but still not enough value
|
||||||
|
let selected = utxo_select_fixed(1000, 3, &utxos);
|
||||||
|
assert_eq!(selected, None);
|
||||||
|
|
||||||
|
// With exactly enough UTXOs to meet the target
|
||||||
|
let selected = utxo_select_fixed(600, 3, &utxos);
|
||||||
|
assert_eq!(selected, Some(vec![100, 200, 300]));
|
||||||
|
|
||||||
|
// With large values to test for potential overflow issues
|
||||||
|
let large_value = u64::MAX / 2;
|
||||||
|
let utxos = vec![large_value, large_value / 2];
|
||||||
|
let selected = utxo_select_fixed(large_value, 1, &utxos);
|
||||||
|
assert_eq!(selected, Some(vec![large_value]));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[sdk_macros::test_all]
|
||||||
|
fn test_utxo_select_best() {
|
||||||
|
let utxos = vec![100, 200, 300, 400];
|
||||||
|
|
||||||
|
// Should find optimal solution
|
||||||
|
let selected = utxo_select_best(300, &utxos);
|
||||||
|
assert_eq!(selected, Some(vec![300]));
|
||||||
|
|
||||||
|
// Should fallback to basic selection as no exact utxo set can be found
|
||||||
|
let selected: Option<Vec<u64>> = utxo_select_best(450, &utxos);
|
||||||
|
assert!(selected.is_some());
|
||||||
|
assert_eq!(selected.unwrap().iter().sum::<u64>(), 600);
|
||||||
|
|
||||||
|
// Should use all UTXOs as fallback when needed
|
||||||
|
let selected = utxo_select_best(950, &utxos);
|
||||||
|
assert_eq!(selected, Some(vec![100, 200, 300, 400]));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[sdk_macros::test_all]
|
||||||
|
fn test_utxo_select_in_range() {
|
||||||
|
let utxos = vec![50, 100, 200, 300, 400];
|
||||||
|
|
||||||
|
// Exact match
|
||||||
|
let selected = utxo_select_in_range(300, 0, 0, &utxos);
|
||||||
|
assert_eq!(selected, Some(vec![300]));
|
||||||
|
|
||||||
|
// Within range
|
||||||
|
let selected = utxo_select_in_range(350, 50, 0, &utxos);
|
||||||
|
assert_eq!(selected, Some(vec![400]));
|
||||||
|
|
||||||
|
// Multiple UTXOs needed
|
||||||
|
let selected = utxo_select_in_range(350, 0, 0, &utxos);
|
||||||
|
assert_eq!(selected, Some(vec![300, 50]));
|
||||||
|
|
||||||
|
// With target count
|
||||||
|
let selected = utxo_select_in_range(250, 0, 2, &utxos);
|
||||||
|
assert_eq!(selected, Some(vec![200, 50]));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[sdk_macros::test_all]
|
||||||
|
fn test_utxo_select_success() {
|
||||||
|
let policy_asset = AssetId::from_slice(&[1; 32]).unwrap();
|
||||||
|
let fee_asset = AssetId::from_slice(&[2; 32]).unwrap();
|
||||||
|
|
||||||
|
// Create wallet UTXOs with both policy and fee assets
|
||||||
|
let wallet_utxos = vec![
|
||||||
|
InOut {
|
||||||
|
asset_id: policy_asset,
|
||||||
|
value: 100000000,
|
||||||
},
|
},
|
||||||
|
InOut {
|
||||||
|
asset_id: policy_asset,
|
||||||
|
value: 200000000,
|
||||||
|
},
|
||||||
|
InOut {
|
||||||
|
asset_id: fee_asset,
|
||||||
|
value: 50000000,
|
||||||
|
},
|
||||||
|
InOut {
|
||||||
|
asset_id: fee_asset,
|
||||||
|
value: 80000000,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
// Create server UTXOs (only policy asset)
|
||||||
|
let server_utxos = vec![
|
||||||
|
InOut {
|
||||||
|
asset_id: policy_asset,
|
||||||
|
value: 150000000,
|
||||||
|
},
|
||||||
|
InOut {
|
||||||
|
asset_id: policy_asset,
|
||||||
|
value: 250000000,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
// User outputs (both assets)
|
||||||
|
let user_outputs = vec![
|
||||||
|
InOut {
|
||||||
|
asset_id: policy_asset,
|
||||||
|
value: 150000000,
|
||||||
|
},
|
||||||
|
InOut {
|
||||||
|
asset_id: fee_asset,
|
||||||
|
value: 20000000,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
let req = UtxoSelectRequest {
|
||||||
|
policy_asset,
|
||||||
|
fee_asset,
|
||||||
|
price: 84896.5,
|
||||||
|
fixed_fee: 4000000,
|
||||||
|
wallet_utxos,
|
||||||
|
server_utxos,
|
||||||
|
user_outputs,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(all(target_family = "wasm", target_os = "unknown"))]
|
let result = utxo_select(req);
|
||||||
wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser);
|
assert!(result.is_ok());
|
||||||
|
|
||||||
#[sdk_macros::test_all]
|
let selection = result.unwrap();
|
||||||
fn test_utxo_select_basic() {
|
|
||||||
// Basic case - should select UTXOs in order until target is met
|
|
||||||
let utxos = vec![100, 200, 300, 400];
|
|
||||||
let selected = utxo_select_basic(300, &utxos);
|
|
||||||
assert_eq!(selected, Some(vec![100, 200]));
|
|
||||||
|
|
||||||
// Exact match with one UTXO
|
// Verify network fee is covered by server inputs
|
||||||
let selected = utxo_select_basic(300, &[300, 400, 500]);
|
assert!(selection.network_fee.value > 0);
|
||||||
assert_eq!(selected, Some(vec![300]));
|
assert_eq!(selection.network_fee.asset_id, policy_asset);
|
||||||
|
|
||||||
// First UTXO is enough
|
// Verify server fee is in fee_asset and reasonable
|
||||||
let selected = utxo_select_basic(50, &[100, 200, 300]);
|
assert!(selection.server_fee.value >= 100); // at least fixed fee
|
||||||
assert_eq!(selected, Some(vec![100]));
|
assert_eq!(selection.server_fee.asset_id, fee_asset);
|
||||||
|
|
||||||
// Need all UTXOs
|
// Verify all user outputs are present
|
||||||
let selected = utxo_select_basic(590, &[100, 200, 300]);
|
assert_eq!(selection.user_outputs.len(), 2);
|
||||||
assert_eq!(selected, Some(vec![100, 200, 300]));
|
|
||||||
|
|
||||||
// Not enough UTXOs available
|
// Check input/output balance
|
||||||
let selected = utxo_select_basic(1000, &[100, 200, 300]);
|
let mut input_sum_by_asset = BTreeMap::<AssetId, u64>::new();
|
||||||
assert_eq!(selected, None);
|
let mut output_sum_by_asset = BTreeMap::<AssetId, u64>::new();
|
||||||
|
|
||||||
// Empty UTXO list
|
// Sum all inputs
|
||||||
let selected = utxo_select_basic(100, &[]);
|
for input in selection
|
||||||
assert_eq!(selected, None);
|
.user_inputs
|
||||||
|
.iter()
|
||||||
// Zero target amount
|
.chain(selection.client_inputs.iter())
|
||||||
let selected = utxo_select_basic(0, &[100, 200]);
|
.chain(selection.server_inputs.iter())
|
||||||
assert_eq!(selected, Some(vec![]));
|
{
|
||||||
|
*input_sum_by_asset.entry(input.asset_id).or_default() += input.value;
|
||||||
// Large values to check for overflow
|
|
||||||
let large_value = u64::MAX / 3;
|
|
||||||
let utxos = vec![large_value, large_value, large_value];
|
|
||||||
let selected = utxo_select_basic(large_value * 2, &utxos);
|
|
||||||
assert_eq!(selected, Some(vec![large_value, large_value]));
|
|
||||||
|
|
||||||
// UTXO order matters - should take in original order
|
|
||||||
let utxos = vec![400, 100, 300, 200];
|
|
||||||
let selected = utxo_select_basic(450, &utxos);
|
|
||||||
assert_eq!(selected, Some(vec![400, 100]));
|
|
||||||
|
|
||||||
// With just-enough UTXOs
|
|
||||||
let utxos = vec![100, 200, 300, 400];
|
|
||||||
let selected = utxo_select_basic(1000, &utxos);
|
|
||||||
assert_eq!(selected, Some(vec![100, 200, 300, 400]));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[sdk_macros::test_all]
|
// Sum all outputs
|
||||||
fn test_utxo_select_fixed() {
|
for output in selection
|
||||||
let utxos = vec![100, 200, 300, 400];
|
.user_outputs
|
||||||
|
.iter()
|
||||||
// Should take first two UTXOs (100 + 200 = 300)
|
.chain(selection.change_outputs.iter())
|
||||||
let selected = utxo_select_fixed(300, 2, &utxos);
|
.chain(std::iter::once(&selection.server_fee))
|
||||||
assert_eq!(selected, Some(vec![100, 200]));
|
.chain(selection.server_change.iter())
|
||||||
|
.chain(selection.fee_change.iter())
|
||||||
// Not enough with just one UTXO
|
.chain(std::iter::once(&selection.network_fee))
|
||||||
let selected = utxo_select_fixed(150, 1, &utxos);
|
{
|
||||||
assert_eq!(selected, None);
|
*output_sum_by_asset.entry(output.asset_id).or_default() += output.value;
|
||||||
|
|
||||||
// Target exceeds available in requested count
|
|
||||||
let selected = utxo_select_fixed(350, 2, &utxos);
|
|
||||||
assert_eq!(selected, None);
|
|
||||||
|
|
||||||
// With exactly the required amount
|
|
||||||
let selected = utxo_select_fixed(300, 1, &[300]);
|
|
||||||
assert_eq!(selected, Some(vec![300]));
|
|
||||||
|
|
||||||
// With empty utxos
|
|
||||||
let selected = utxo_select_fixed(100, 1, &[]);
|
|
||||||
assert_eq!(selected, None);
|
|
||||||
|
|
||||||
// With zero target value
|
|
||||||
let selected = utxo_select_fixed(0, 2, &utxos);
|
|
||||||
assert_eq!(selected, Some(vec![100, 200]));
|
|
||||||
|
|
||||||
// With zero target count
|
|
||||||
let selected = utxo_select_fixed(100, 0, &utxos);
|
|
||||||
assert_eq!(selected, None);
|
|
||||||
|
|
||||||
// With more UTXOs than requested count but still not enough value
|
|
||||||
let selected = utxo_select_fixed(1000, 3, &utxos);
|
|
||||||
assert_eq!(selected, None);
|
|
||||||
|
|
||||||
// With exactly enough UTXOs to meet the target
|
|
||||||
let selected = utxo_select_fixed(600, 3, &utxos);
|
|
||||||
assert_eq!(selected, Some(vec![100, 200, 300]));
|
|
||||||
|
|
||||||
// With large values to test for potential overflow issues
|
|
||||||
let large_value = u64::MAX / 2;
|
|
||||||
let utxos = vec![large_value, large_value / 2];
|
|
||||||
let selected = utxo_select_fixed(large_value, 1, &utxos);
|
|
||||||
assert_eq!(selected, Some(vec![large_value]));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[sdk_macros::test_all]
|
// Input and output sums should match for each asset
|
||||||
fn test_utxo_select_best() {
|
assert_eq!(input_sum_by_asset, output_sum_by_asset);
|
||||||
let utxos = vec![100, 200, 300, 400];
|
}
|
||||||
|
|
||||||
// Should find optimal solution
|
#[sdk_macros::test_all]
|
||||||
let selected = utxo_select_best(300, &utxos);
|
fn test_utxo_select_error_cases() {
|
||||||
assert_eq!(selected, Some(vec![300]));
|
let policy_asset = AssetId::from_slice(&[1; 32]).unwrap();
|
||||||
|
let fee_asset = AssetId::from_slice(&[2; 32]).unwrap();
|
||||||
|
|
||||||
// Should fallback to basic selection as no exact utxo set can be found
|
// Base valid request
|
||||||
let selected: Option<Vec<u64>> = utxo_select_best(450, &utxos);
|
let valid_req = UtxoSelectRequest {
|
||||||
assert!(selected.is_some());
|
policy_asset,
|
||||||
assert_eq!(selected.unwrap().iter().sum::<u64>(), 600);
|
fee_asset,
|
||||||
|
price: 84896.5,
|
||||||
// Should use all UTXOs as fallback when needed
|
fixed_fee: 4000000,
|
||||||
let selected = utxo_select_best(950, &utxos);
|
wallet_utxos: vec![
|
||||||
assert_eq!(selected, Some(vec![100, 200, 300, 400]));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[sdk_macros::test_all]
|
|
||||||
fn test_utxo_select_in_range() {
|
|
||||||
let utxos = vec![50, 100, 200, 300, 400];
|
|
||||||
|
|
||||||
// Exact match
|
|
||||||
let selected = utxo_select_in_range(300, 0, 0, &utxos);
|
|
||||||
assert_eq!(selected, Some(vec![300]));
|
|
||||||
|
|
||||||
// Within range
|
|
||||||
let selected = utxo_select_in_range(350, 50, 0, &utxos);
|
|
||||||
assert_eq!(selected, Some(vec![400]));
|
|
||||||
|
|
||||||
// Multiple UTXOs needed
|
|
||||||
let selected = utxo_select_in_range(350, 0, 0, &utxos);
|
|
||||||
assert_eq!(selected, Some(vec![300, 50]));
|
|
||||||
|
|
||||||
// With target count
|
|
||||||
let selected = utxo_select_in_range(250, 0, 2, &utxos);
|
|
||||||
assert_eq!(selected, Some(vec![200, 50]));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[sdk_macros::test_all]
|
|
||||||
fn test_utxo_select_success() {
|
|
||||||
let policy_asset = AssetId::from_slice(&[1; 32]).unwrap();
|
|
||||||
let fee_asset = AssetId::from_slice(&[2; 32]).unwrap();
|
|
||||||
|
|
||||||
// Create wallet UTXOs with both policy and fee assets
|
|
||||||
let wallet_utxos = vec![
|
|
||||||
InOut {
|
|
||||||
asset_id: policy_asset,
|
|
||||||
value: 100000000,
|
|
||||||
},
|
|
||||||
InOut {
|
|
||||||
asset_id: policy_asset,
|
|
||||||
value: 200000000,
|
|
||||||
},
|
|
||||||
InOut {
|
|
||||||
asset_id: fee_asset,
|
|
||||||
value: 50000000,
|
|
||||||
},
|
|
||||||
InOut {
|
|
||||||
asset_id: fee_asset,
|
|
||||||
value: 80000000,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
// Create server UTXOs (only policy asset)
|
|
||||||
let server_utxos = vec![
|
|
||||||
InOut {
|
|
||||||
asset_id: policy_asset,
|
|
||||||
value: 150000000,
|
|
||||||
},
|
|
||||||
InOut {
|
|
||||||
asset_id: policy_asset,
|
|
||||||
value: 250000000,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
// User outputs (both assets)
|
|
||||||
let user_outputs = vec![
|
|
||||||
InOut {
|
|
||||||
asset_id: policy_asset,
|
|
||||||
value: 150000000,
|
|
||||||
},
|
|
||||||
InOut {
|
|
||||||
asset_id: fee_asset,
|
|
||||||
value: 20000000,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
let req = UtxoSelectRequest {
|
|
||||||
policy_asset,
|
|
||||||
fee_asset,
|
|
||||||
price: 84896.5,
|
|
||||||
fixed_fee: 4000000,
|
|
||||||
wallet_utxos,
|
|
||||||
server_utxos,
|
|
||||||
user_outputs,
|
|
||||||
};
|
|
||||||
|
|
||||||
let result = utxo_select(req);
|
|
||||||
assert!(result.is_ok());
|
|
||||||
|
|
||||||
let selection = result.unwrap();
|
|
||||||
|
|
||||||
// Verify network fee is covered by server inputs
|
|
||||||
assert!(selection.network_fee.value > 0);
|
|
||||||
assert_eq!(selection.network_fee.asset_id, policy_asset);
|
|
||||||
|
|
||||||
// Verify server fee is in fee_asset and reasonable
|
|
||||||
assert!(selection.server_fee.value >= 100); // at least fixed fee
|
|
||||||
assert_eq!(selection.server_fee.asset_id, fee_asset);
|
|
||||||
|
|
||||||
// Verify all user outputs are present
|
|
||||||
assert_eq!(selection.user_outputs.len(), 2);
|
|
||||||
|
|
||||||
// Check input/output balance
|
|
||||||
let mut input_sum_by_asset = BTreeMap::<AssetId, u64>::new();
|
|
||||||
let mut output_sum_by_asset = BTreeMap::<AssetId, u64>::new();
|
|
||||||
|
|
||||||
// Sum all inputs
|
|
||||||
for input in selection
|
|
||||||
.user_inputs
|
|
||||||
.iter()
|
|
||||||
.chain(selection.client_inputs.iter())
|
|
||||||
.chain(selection.server_inputs.iter())
|
|
||||||
{
|
|
||||||
*input_sum_by_asset.entry(input.asset_id).or_default() += input.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sum all outputs
|
|
||||||
for output in selection
|
|
||||||
.user_outputs
|
|
||||||
.iter()
|
|
||||||
.chain(selection.change_outputs.iter())
|
|
||||||
.chain(std::iter::once(&selection.server_fee))
|
|
||||||
.chain(selection.server_change.iter())
|
|
||||||
.chain(selection.fee_change.iter())
|
|
||||||
.chain(std::iter::once(&selection.network_fee))
|
|
||||||
{
|
|
||||||
*output_sum_by_asset.entry(output.asset_id).or_default() += output.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Input and output sums should match for each asset
|
|
||||||
assert_eq!(input_sum_by_asset, output_sum_by_asset);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[sdk_macros::test_all]
|
|
||||||
fn test_utxo_select_error_cases() {
|
|
||||||
let policy_asset = AssetId::from_slice(&[1; 32]).unwrap();
|
|
||||||
let fee_asset = AssetId::from_slice(&[2; 32]).unwrap();
|
|
||||||
|
|
||||||
// Base valid request
|
|
||||||
let valid_req = UtxoSelectRequest {
|
|
||||||
policy_asset,
|
|
||||||
fee_asset,
|
|
||||||
price: 84896.5,
|
|
||||||
fixed_fee: 4000000,
|
|
||||||
wallet_utxos: vec![
|
|
||||||
InOut {
|
|
||||||
asset_id: policy_asset,
|
|
||||||
value: 1000,
|
|
||||||
},
|
|
||||||
InOut {
|
|
||||||
asset_id: fee_asset,
|
|
||||||
value: 500,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
server_utxos: vec![InOut {
|
|
||||||
asset_id: policy_asset,
|
|
||||||
value: 1500,
|
|
||||||
}],
|
|
||||||
user_outputs: vec![InOut {
|
|
||||||
asset_id: policy_asset,
|
|
||||||
value: 500,
|
|
||||||
}],
|
|
||||||
};
|
|
||||||
|
|
||||||
// Same asset for policy and fee - should error
|
|
||||||
let mut bad_req = valid_req.clone();
|
|
||||||
bad_req.fee_asset = policy_asset;
|
|
||||||
assert!(utxo_select(bad_req).is_err());
|
|
||||||
|
|
||||||
// Zero price - should error
|
|
||||||
let mut bad_req = valid_req.clone();
|
|
||||||
bad_req.price = 0.0;
|
|
||||||
assert!(utxo_select(bad_req).is_err());
|
|
||||||
|
|
||||||
// Zero fixed fee - should error
|
|
||||||
let mut bad_req = valid_req.clone();
|
|
||||||
bad_req.fixed_fee = 0;
|
|
||||||
assert!(utxo_select(bad_req).is_err());
|
|
||||||
|
|
||||||
// Invalid server UTXO asset - should error
|
|
||||||
let mut bad_req = valid_req.clone();
|
|
||||||
bad_req.server_utxos = vec![InOut {
|
|
||||||
asset_id: fee_asset,
|
|
||||||
value: 1500,
|
|
||||||
}];
|
|
||||||
assert!(utxo_select(bad_req).is_err());
|
|
||||||
|
|
||||||
// Insufficient fee assets - should error
|
|
||||||
let mut bad_req = valid_req.clone();
|
|
||||||
bad_req.wallet_utxos = vec![
|
|
||||||
InOut {
|
InOut {
|
||||||
asset_id: policy_asset,
|
asset_id: policy_asset,
|
||||||
value: 1000,
|
value: 1000,
|
||||||
},
|
},
|
||||||
InOut {
|
InOut {
|
||||||
asset_id: fee_asset,
|
asset_id: fee_asset,
|
||||||
value: 10,
|
value: 500,
|
||||||
}, // Too small
|
|
||||||
];
|
|
||||||
let result = utxo_select(bad_req);
|
|
||||||
assert!(result.is_err());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[sdk_macros::test_all]
|
|
||||||
fn test_utxo_select_with_change() {
|
|
||||||
let policy_asset = AssetId::from_slice(&[1; 32]).unwrap();
|
|
||||||
let fee_asset = AssetId::from_slice(&[2; 32]).unwrap();
|
|
||||||
|
|
||||||
// Create a scenario where change is needed
|
|
||||||
let wallet_utxos = vec![
|
|
||||||
InOut {
|
|
||||||
asset_id: policy_asset,
|
|
||||||
value: 50000000,
|
|
||||||
},
|
},
|
||||||
InOut {
|
],
|
||||||
asset_id: fee_asset,
|
server_utxos: vec![InOut {
|
||||||
value: 20000000,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
let server_utxos = vec![
|
|
||||||
InOut {
|
|
||||||
asset_id: policy_asset,
|
|
||||||
value: 10000000,
|
|
||||||
},
|
|
||||||
InOut {
|
|
||||||
asset_id: policy_asset,
|
|
||||||
value: 20000000,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
let user_outputs = vec![InOut {
|
|
||||||
asset_id: policy_asset,
|
asset_id: policy_asset,
|
||||||
value: 30000000,
|
value: 1500,
|
||||||
}];
|
}],
|
||||||
|
user_outputs: vec![InOut {
|
||||||
|
asset_id: policy_asset,
|
||||||
|
value: 500,
|
||||||
|
}],
|
||||||
|
};
|
||||||
|
|
||||||
let req = UtxoSelectRequest {
|
// Same asset for policy and fee - should error
|
||||||
policy_asset,
|
let mut bad_req = valid_req.clone();
|
||||||
fee_asset,
|
bad_req.fee_asset = policy_asset;
|
||||||
price: 84896.5,
|
assert!(utxo_select(bad_req).is_err());
|
||||||
fixed_fee: 4000000,
|
|
||||||
wallet_utxos,
|
|
||||||
server_utxos,
|
|
||||||
user_outputs,
|
|
||||||
};
|
|
||||||
|
|
||||||
let result = utxo_select(req);
|
// Zero price - should error
|
||||||
assert!(result.is_ok());
|
let mut bad_req = valid_req.clone();
|
||||||
|
bad_req.price = 0.0;
|
||||||
|
assert!(utxo_select(bad_req).is_err());
|
||||||
|
|
||||||
let selection = result.unwrap();
|
// Zero fixed fee - should error
|
||||||
|
let mut bad_req = valid_req.clone();
|
||||||
|
bad_req.fixed_fee = 0;
|
||||||
|
assert!(utxo_select(bad_req).is_err());
|
||||||
|
|
||||||
// Either policy asset change or fee asset change should exist
|
// Invalid server UTXO asset - should error
|
||||||
assert!(selection.fee_change.is_some() || !selection.change_outputs.is_empty());
|
let mut bad_req = valid_req.clone();
|
||||||
|
bad_req.server_utxos = vec![InOut {
|
||||||
|
asset_id: fee_asset,
|
||||||
|
value: 1500,
|
||||||
|
}];
|
||||||
|
assert!(utxo_select(bad_req).is_err());
|
||||||
|
|
||||||
// Verify change amounts are reasonable
|
// Insufficient fee assets - should error
|
||||||
if let Some(fee_change) = &selection.fee_change {
|
let mut bad_req = valid_req.clone();
|
||||||
assert_eq!(fee_change.asset_id, fee_asset);
|
bad_req.wallet_utxos = vec![
|
||||||
assert!(fee_change.value > 0);
|
InOut {
|
||||||
}
|
asset_id: policy_asset,
|
||||||
|
value: 1000,
|
||||||
// Check that we're not wasting fees unnecessarily
|
},
|
||||||
assert!(selection.cost <= selection.server_fee.value);
|
InOut {
|
||||||
}
|
asset_id: fee_asset,
|
||||||
|
value: 10,
|
||||||
|
}, // Too small
|
||||||
|
];
|
||||||
|
let result = utxo_select(bad_req);
|
||||||
|
assert!(result.is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[sdk_macros::test_all]
|
||||||
|
fn test_utxo_select_with_change() {
|
||||||
|
let policy_asset = AssetId::from_slice(&[1; 32]).unwrap();
|
||||||
|
let fee_asset = AssetId::from_slice(&[2; 32]).unwrap();
|
||||||
|
|
||||||
|
// Create a scenario where change is needed
|
||||||
|
let wallet_utxos = vec![
|
||||||
|
InOut {
|
||||||
|
asset_id: policy_asset,
|
||||||
|
value: 50000000,
|
||||||
|
},
|
||||||
|
InOut {
|
||||||
|
asset_id: fee_asset,
|
||||||
|
value: 20000000,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
let server_utxos = vec![
|
||||||
|
InOut {
|
||||||
|
asset_id: policy_asset,
|
||||||
|
value: 10000000,
|
||||||
|
},
|
||||||
|
InOut {
|
||||||
|
asset_id: policy_asset,
|
||||||
|
value: 20000000,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
let user_outputs = vec![InOut {
|
||||||
|
asset_id: policy_asset,
|
||||||
|
value: 30000000,
|
||||||
|
}];
|
||||||
|
|
||||||
|
let req = UtxoSelectRequest {
|
||||||
|
policy_asset,
|
||||||
|
fee_asset,
|
||||||
|
price: 84896.5,
|
||||||
|
fixed_fee: 4000000,
|
||||||
|
wallet_utxos,
|
||||||
|
server_utxos,
|
||||||
|
user_outputs,
|
||||||
|
};
|
||||||
|
|
||||||
|
let result = utxo_select(req);
|
||||||
|
assert!(result.is_ok());
|
||||||
|
|
||||||
|
let selection = result.unwrap();
|
||||||
|
|
||||||
|
// Either policy asset change or fee asset change should exist
|
||||||
|
assert!(selection.fee_change.is_some() || !selection.change_outputs.is_empty());
|
||||||
|
|
||||||
|
// Verify change amounts are reasonable
|
||||||
|
if let Some(fee_change) = &selection.fee_change {
|
||||||
|
assert_eq!(fee_change.asset_id, fee_asset);
|
||||||
|
assert!(fee_change.value > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that we're not wasting fees unnecessarily
|
||||||
|
assert!(selection.cost <= selection.server_fee.value);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ impl LiquidChainService for MockLiquidChainService {
|
|||||||
_script: &ElementsScript,
|
_script: &ElementsScript,
|
||||||
_retries: u64,
|
_retries: u64,
|
||||||
) -> Result<Vec<LBtcHistory>> {
|
) -> Result<Vec<LBtcHistory>> {
|
||||||
Ok(self.get_history().into_iter().map(Into::into).collect())
|
Ok(self.get_history().into_iter().collect())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_script_history(&self, _script: &ElementsScript) -> Result<Vec<LBtcHistory>> {
|
async fn get_script_history(&self, _script: &ElementsScript) -> Result<Vec<LBtcHistory>> {
|
||||||
@@ -156,14 +156,7 @@ impl BitcoinChainService for MockBitcoinChainService {
|
|||||||
_script: &Script,
|
_script: &Script,
|
||||||
_retries: u64,
|
_retries: u64,
|
||||||
) -> Result<Vec<BtcHistory>> {
|
) -> Result<Vec<BtcHistory>> {
|
||||||
Ok(self
|
Ok(self.history.lock().unwrap().clone().into_iter().collect())
|
||||||
.history
|
|
||||||
.lock()
|
|
||||||
.unwrap()
|
|
||||||
.clone()
|
|
||||||
.into_iter()
|
|
||||||
.map(Into::into)
|
|
||||||
.collect())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_script_history(&self, _scripts: &Script) -> Result<Vec<BtcHistory>> {
|
async fn get_script_history(&self, _scripts: &Script) -> Result<Vec<BtcHistory>> {
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ init:
|
|||||||
rustup target add wasm32-unknown-unknown
|
rustup target add wasm32-unknown-unknown
|
||||||
|
|
||||||
clippy:
|
clippy:
|
||||||
$(CLANG_PREFIX) cargo clippy --target=wasm32-unknown-unknown -- -D warnings
|
$(CLANG_PREFIX) cargo clippy --all-targets --target=wasm32-unknown-unknown -- -D warnings
|
||||||
|
|
||||||
build: build-bundle build-deno build-node build-web
|
build: build-bundle build-deno build-node build-web
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user