Merge rust-bitcoin/rust-bitcoin#1191: Hex macros cleanup
43827a1e1c
Hex macro clean up (Martin Habovstiak)4dedf7d555
Remove duplicated `#[cfg(test)]` conditions (Martin Habovstiak) Pull request description: Few cleanups of hex macros. Preparation for #1189 ACKs for top commit: apoelstra: ACK43827a1e1c
tcharding: ACK43827a1e1c
Tree-SHA512: ba59b8bd1bc4cbf4914b000918f685edec18f58f9ea4cfbe918dad54b62767d34fdef2d2e4233ebdd66193cb7253c3ebc6f315a23e70a9cdd2316d96a0a9dce4
This commit is contained in:
commit
d37fb99c96
|
@ -933,17 +933,11 @@ mod tests {
|
||||||
use secp256k1::XOnlyPublicKey;
|
use secp256k1::XOnlyPublicKey;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::blockdata::script::Script;
|
|
||||||
use crate::hashes::hex::{FromHex, ToHex};
|
use crate::hashes::hex::{FromHex, ToHex};
|
||||||
|
use crate::internal_macros::{hex, hex_into, hex_script};
|
||||||
use crate::network::constants::Network::{Bitcoin, Testnet};
|
use crate::network::constants::Network::{Bitcoin, Testnet};
|
||||||
use crate::util::key::PublicKey;
|
use crate::util::key::PublicKey;
|
||||||
|
|
||||||
macro_rules! hex (($hex:literal) => (Vec::from_hex($hex).unwrap()));
|
|
||||||
macro_rules! hex_key (($hex:literal) => (PublicKey::from_slice(&hex!($hex)).unwrap()));
|
|
||||||
macro_rules! hex_script (($hex:literal) => (Script::from(hex!($hex))));
|
|
||||||
macro_rules! hex_pubkeyhash (($hex:literal) => (PubkeyHash::from_hex(&$hex).unwrap()));
|
|
||||||
macro_rules! hex_scripthash (($hex:literal) => (ScriptHash::from_hex($hex).unwrap()));
|
|
||||||
|
|
||||||
fn roundtrips(addr: &Address) {
|
fn roundtrips(addr: &Address) {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Address::from_str(&addr.to_string()).unwrap(),
|
Address::from_str(&addr.to_string()).unwrap(),
|
||||||
|
@ -964,9 +958,7 @@ mod tests {
|
||||||
fn test_p2pkh_address_58() {
|
fn test_p2pkh_address_58() {
|
||||||
let addr = Address {
|
let addr = Address {
|
||||||
network: Bitcoin,
|
network: Bitcoin,
|
||||||
payload: Payload::PubkeyHash(hex_pubkeyhash!(
|
payload: Payload::PubkeyHash(hex_into!("162c5ea71c0b23f5b9022ef047c4a86470a5b070")),
|
||||||
"162c5ea71c0b23f5b9022ef047c4a86470a5b070"
|
|
||||||
)),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
@ -980,11 +972,11 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_p2pkh_from_key() {
|
fn test_p2pkh_from_key() {
|
||||||
let key = hex_key!("048d5141948c1702e8c95f438815794b87f706a8d4cd2bffad1dc1570971032c9b6042a0431ded2478b5c9cf2d81c124a5e57347a3c63ef0e7716cf54d613ba183");
|
let key = hex_into!("048d5141948c1702e8c95f438815794b87f706a8d4cd2bffad1dc1570971032c9b6042a0431ded2478b5c9cf2d81c124a5e57347a3c63ef0e7716cf54d613ba183");
|
||||||
let addr = Address::p2pkh(&key, Bitcoin);
|
let addr = Address::p2pkh(&key, Bitcoin);
|
||||||
assert_eq!(&addr.to_string(), "1QJVDzdqb1VpbDK7uDeyVXy9mR27CJiyhY");
|
assert_eq!(&addr.to_string(), "1QJVDzdqb1VpbDK7uDeyVXy9mR27CJiyhY");
|
||||||
|
|
||||||
let key = hex_key!("03df154ebfcf29d29cc10d5c2565018bce2d9edbab267c31d2caf44a63056cf99f");
|
let key = hex_into!("03df154ebfcf29d29cc10d5c2565018bce2d9edbab267c31d2caf44a63056cf99f");
|
||||||
let addr = Address::p2pkh(&key, Testnet);
|
let addr = Address::p2pkh(&key, Testnet);
|
||||||
assert_eq!(&addr.to_string(), "mqkhEMH6NCeYjFybv7pvFC22MFeaNT9AQC");
|
assert_eq!(&addr.to_string(), "mqkhEMH6NCeYjFybv7pvFC22MFeaNT9AQC");
|
||||||
assert_eq!(addr.address_type(), Some(AddressType::P2pkh));
|
assert_eq!(addr.address_type(), Some(AddressType::P2pkh));
|
||||||
|
@ -995,9 +987,7 @@ mod tests {
|
||||||
fn test_p2sh_address_58() {
|
fn test_p2sh_address_58() {
|
||||||
let addr = Address {
|
let addr = Address {
|
||||||
network: Bitcoin,
|
network: Bitcoin,
|
||||||
payload: Payload::ScriptHash(hex_scripthash!(
|
payload: Payload::ScriptHash(hex_into!("162c5ea71c0b23f5b9022ef047c4a86470a5b070")),
|
||||||
"162c5ea71c0b23f5b9022ef047c4a86470a5b070"
|
|
||||||
)),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
@ -1028,7 +1018,7 @@ mod tests {
|
||||||
fn test_p2wpkh() {
|
fn test_p2wpkh() {
|
||||||
// stolen from Bitcoin transaction: b3c8c2b6cfc335abbcb2c7823a8453f55d64b2b5125a9a61e8737230cdb8ce20
|
// stolen from Bitcoin transaction: b3c8c2b6cfc335abbcb2c7823a8453f55d64b2b5125a9a61e8737230cdb8ce20
|
||||||
let mut key =
|
let mut key =
|
||||||
hex_key!("033bc8c83c52df5712229a2f72206d90192366c36428cb0c12b6af98324d97bfbc");
|
hex_into!("033bc8c83c52df5712229a2f72206d90192366c36428cb0c12b6af98324d97bfbc");
|
||||||
let addr = Address::p2wpkh(&key, Bitcoin).unwrap();
|
let addr = Address::p2wpkh(&key, Bitcoin).unwrap();
|
||||||
assert_eq!(&addr.to_string(), "bc1qvzvkjn4q3nszqxrv3nraga2r822xjty3ykvkuw");
|
assert_eq!(&addr.to_string(), "bc1qvzvkjn4q3nszqxrv3nraga2r822xjty3ykvkuw");
|
||||||
assert_eq!(addr.address_type(), Some(AddressType::P2wpkh));
|
assert_eq!(addr.address_type(), Some(AddressType::P2wpkh));
|
||||||
|
@ -1056,7 +1046,7 @@ mod tests {
|
||||||
fn test_p2shwpkh() {
|
fn test_p2shwpkh() {
|
||||||
// stolen from Bitcoin transaction: ad3fd9c6b52e752ba21425435ff3dd361d6ac271531fc1d2144843a9f550ad01
|
// stolen from Bitcoin transaction: ad3fd9c6b52e752ba21425435ff3dd361d6ac271531fc1d2144843a9f550ad01
|
||||||
let mut key =
|
let mut key =
|
||||||
hex_key!("026c468be64d22761c30cd2f12cbc7de255d592d7904b1bab07236897cc4c2e766");
|
hex_into!("026c468be64d22761c30cd2f12cbc7de255d592d7904b1bab07236897cc4c2e766");
|
||||||
let addr = Address::p2shwpkh(&key, Bitcoin).unwrap();
|
let addr = Address::p2shwpkh(&key, Bitcoin).unwrap();
|
||||||
assert_eq!(&addr.to_string(), "3QBRmWNqqBGme9er7fMkGqtZtp4gjMFxhE");
|
assert_eq!(&addr.to_string(), "3QBRmWNqqBGme9er7fMkGqtZtp4gjMFxhE");
|
||||||
assert_eq!(addr.address_type(), Some(AddressType::P2sh));
|
assert_eq!(addr.address_type(), Some(AddressType::P2sh));
|
||||||
|
|
|
@ -119,19 +119,68 @@ macro_rules! debug_from_display {
|
||||||
pub(crate) use debug_from_display;
|
pub(crate) use debug_from_display;
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
macro_rules! hex_script (($s:expr) => (<$crate::Script as core::str::FromStr>::from_str($s).unwrap()));
|
pub(crate) use test_macros::*;
|
||||||
#[cfg(test)]
|
|
||||||
pub(crate) use hex_script;
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
macro_rules! hex_hash (($h:ident, $s:expr) => ($h::from_slice(&<$crate::prelude::Vec<u8> as $crate::hashes::hex::FromHex>::from_hex($s).unwrap()).unwrap()));
|
mod test_macros {
|
||||||
#[cfg(test)]
|
use crate::hashes::hex::FromHex;
|
||||||
pub(crate) use hex_hash;
|
use crate::PublicKey;
|
||||||
|
|
||||||
#[cfg(test)]
|
/// Trait used to create a value from hex string for testing purposes.
|
||||||
macro_rules! hex_decode (($h:ident, $s:expr) => (deserialize::<$h>(&<$crate::prelude::Vec<u8> as $crate::hashes::hex::FromHex>::from_hex($s).unwrap()).unwrap()));
|
pub(crate) trait TestFromHex {
|
||||||
#[cfg(test)]
|
/// Produces the value from hex.
|
||||||
pub(crate) use hex_decode;
|
///
|
||||||
|
/// ## Panics
|
||||||
|
///
|
||||||
|
/// The function panics if the hex or the value is invalid.
|
||||||
|
fn test_from_hex(hex: &str) -> Self;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: FromHex> TestFromHex for T {
|
||||||
|
fn test_from_hex(hex: &str) -> Self { Self::from_hex(hex).unwrap() }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TestFromHex for PublicKey {
|
||||||
|
fn test_from_hex(hex: &str) -> Self {
|
||||||
|
PublicKey::from_slice(&Vec::from_hex(hex).unwrap()).unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! hex (($hex:literal) => (Vec::from_hex($hex).unwrap()));
|
||||||
|
pub(crate) use hex;
|
||||||
|
|
||||||
|
macro_rules! hex_into {
|
||||||
|
($hex:expr) => {
|
||||||
|
$crate::internal_macros::hex_into!(_, $hex)
|
||||||
|
};
|
||||||
|
($type:ty, $hex:expr) => {
|
||||||
|
<$type as $crate::internal_macros::TestFromHex>::test_from_hex($hex)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
pub(crate) use hex_into;
|
||||||
|
|
||||||
|
// Script is commonly used in places where inference may fail
|
||||||
|
macro_rules! hex_script (($hex:expr) => ($crate::internal_macros::hex_into!($crate::Script, $hex)));
|
||||||
|
pub(crate) use hex_script;
|
||||||
|
|
||||||
|
// For types that can't use TestFromHex due to coherence rules or reversed hex
|
||||||
|
macro_rules! hex_from_slice {
|
||||||
|
($hex:expr) => {
|
||||||
|
$crate::internal_macros::hex_from_slice!(_, $hex)
|
||||||
|
};
|
||||||
|
($type:ty, $hex:expr) => {
|
||||||
|
<$type>::from_slice(
|
||||||
|
&<$crate::prelude::Vec<u8> as $crate::hashes::hex::FromHex>::from_hex($hex)
|
||||||
|
.unwrap(),
|
||||||
|
)
|
||||||
|
.unwrap()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
pub(crate) use hex_from_slice;
|
||||||
|
|
||||||
|
macro_rules! hex_decode (($h:ident, $s:expr) => (deserialize::<$h>(&<$crate::prelude::Vec<u8> as $crate::hashes::hex::FromHex>::from_hex($s).unwrap()).unwrap()));
|
||||||
|
pub(crate) use hex_decode;
|
||||||
|
}
|
||||||
|
|
||||||
macro_rules! serde_string_impl {
|
macro_rules! serde_string_impl {
|
||||||
($name:ident, $expecting:literal) => {
|
($name:ident, $expecting:literal) => {
|
||||||
|
|
|
@ -263,6 +263,7 @@ impl PublicKey {
|
||||||
pub fn from_private_key<C: secp256k1::Signing>(secp: &Secp256k1<C>, sk: &PrivateKey) -> PublicKey {
|
pub fn from_private_key<C: secp256k1::Signing>(secp: &Secp256k1<C>, sk: &PrivateKey) -> PublicKey {
|
||||||
sk.public_key(secp)
|
sk.public_key(secp)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An opaque return type for PublicKey::to_sort_key
|
/// An opaque return type for PublicKey::to_sort_key
|
||||||
|
|
|
@ -1104,7 +1104,7 @@ mod tests {
|
||||||
use crate::hashes::hex::{FromHex, ToHex};
|
use crate::hashes::hex::{FromHex, ToHex};
|
||||||
use crate::hashes::{Hash, HashEngine};
|
use crate::hashes::{Hash, HashEngine};
|
||||||
use crate::hash_types::Sighash;
|
use crate::hash_types::Sighash;
|
||||||
use crate::internal_macros::{hex_hash, hex_script, hex_decode};
|
use crate::internal_macros::{hex_into, hex_script, hex_decode, hex_from_slice};
|
||||||
use crate::network::constants::Network;
|
use crate::network::constants::Network;
|
||||||
use crate::util::key::PublicKey;
|
use crate::util::key::PublicKey;
|
||||||
use crate::util::sighash::{Annex, Error, Prevouts, ScriptPath, SighashCache};
|
use crate::util::sighash::{Annex, Error, Prevouts, ScriptPath, SighashCache};
|
||||||
|
@ -1447,19 +1447,19 @@ mod tests {
|
||||||
|
|
||||||
for inp in key_path["inputSpending"].as_array().unwrap() {
|
for inp in key_path["inputSpending"].as_array().unwrap() {
|
||||||
let tx_ind = inp["given"]["txinIndex"].as_u64().unwrap() as usize;
|
let tx_ind = inp["given"]["txinIndex"].as_u64().unwrap() as usize;
|
||||||
let internal_priv_key = hex_hash!(SecretKey, inp["given"]["internalPrivkey"].as_str().unwrap());
|
let internal_priv_key = hex_from_slice!(SecretKey, inp["given"]["internalPrivkey"].as_str().unwrap());
|
||||||
let merkle_root = if inp["given"]["merkleRoot"].is_null() {
|
let merkle_root = if inp["given"]["merkleRoot"].is_null() {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
Some(hex_hash!(TapBranchHash, inp["given"]["merkleRoot"].as_str().unwrap()))
|
Some(hex_into!(TapBranchHash, inp["given"]["merkleRoot"].as_str().unwrap()))
|
||||||
};
|
};
|
||||||
let hash_ty = SchnorrSighashType::from_consensus_u8(inp["given"]["hashType"].as_u64().unwrap() as u8).unwrap();
|
let hash_ty = SchnorrSighashType::from_consensus_u8(inp["given"]["hashType"].as_u64().unwrap() as u8).unwrap();
|
||||||
|
|
||||||
let expected_internal_pk = hex_hash!(XOnlyPublicKey, inp["intermediary"]["internalPubkey"].as_str().unwrap());
|
let expected_internal_pk = hex_from_slice!(XOnlyPublicKey, inp["intermediary"]["internalPubkey"].as_str().unwrap());
|
||||||
let expected_tweak = hex_hash!(TapTweakHash, inp["intermediary"]["tweak"].as_str().unwrap());
|
let expected_tweak = hex_into!(TapTweakHash, inp["intermediary"]["tweak"].as_str().unwrap());
|
||||||
let expected_tweaked_priv_key = hex_hash!(SecretKey, inp["intermediary"]["tweakedPrivkey"].as_str().unwrap());
|
let expected_tweaked_priv_key = hex_from_slice!(SecretKey, inp["intermediary"]["tweakedPrivkey"].as_str().unwrap());
|
||||||
let expected_sig_msg = Vec::<u8>::from_hex(inp["intermediary"]["sigMsg"].as_str().unwrap()).unwrap();
|
let expected_sig_msg = Vec::<u8>::from_hex(inp["intermediary"]["sigMsg"].as_str().unwrap()).unwrap();
|
||||||
let expected_sighash = hex_hash!(TapSighashHash, inp["intermediary"]["sigHash"].as_str().unwrap());
|
let expected_sighash = hex_into!(TapSighashHash, inp["intermediary"]["sigHash"].as_str().unwrap());
|
||||||
let sig_str = inp["expected"]["witness"][0].as_str().unwrap();
|
let sig_str = inp["expected"]["witness"][0].as_str().unwrap();
|
||||||
let (expected_key_spend_sig, expected_hash_ty) = if sig_str.len() == 128 {
|
let (expected_key_spend_sig, expected_hash_ty) = if sig_str.len() == 128 {
|
||||||
(secp256k1::schnorr::Signature::from_str(sig_str).unwrap(), SchnorrSighashType::Default)
|
(secp256k1::schnorr::Signature::from_str(sig_str).unwrap(), SchnorrSighashType::Default)
|
||||||
|
@ -1566,18 +1566,18 @@ mod tests {
|
||||||
let mut cache = SighashCache::new(&tx);
|
let mut cache = SighashCache::new(&tx);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
cache.segwit_signature_hash(1, &witness_script, value, EcdsaSighashType::All).unwrap(),
|
cache.segwit_signature_hash(1, &witness_script, value, EcdsaSighashType::All).unwrap(),
|
||||||
hex_hash!(Sighash, "c37af31116d1b27caf68aae9e3ac82f1477929014d5b917657d0eb49478cb670")
|
hex_from_slice!(Sighash, "c37af31116d1b27caf68aae9e3ac82f1477929014d5b917657d0eb49478cb670")
|
||||||
);
|
);
|
||||||
|
|
||||||
let cache = cache.segwit_cache();
|
let cache = cache.segwit_cache();
|
||||||
assert_eq!(cache.prevouts, hex_hash!(
|
assert_eq!(cache.prevouts, hex_from_slice!(
|
||||||
Hash, "96b827c8483d4e9b96712b6713a7b68d6e8003a781feba36c31143470b4efd37"
|
"96b827c8483d4e9b96712b6713a7b68d6e8003a781feba36c31143470b4efd37"
|
||||||
));
|
));
|
||||||
assert_eq!(cache.sequences, hex_hash!(
|
assert_eq!(cache.sequences, hex_from_slice!(
|
||||||
Hash, "52b0a642eea2fb7ae638c36f6252b6750293dbe574a806984b8e4d8548339a3b"
|
"52b0a642eea2fb7ae638c36f6252b6750293dbe574a806984b8e4d8548339a3b"
|
||||||
));
|
));
|
||||||
assert_eq!(cache.outputs, hex_hash!(
|
assert_eq!(cache.outputs, hex_from_slice!(
|
||||||
Hash, "863ef3e1a92afbfdb97f31ad0fc7683ee943e9abcf2501590ff8f6551f47e5e5"
|
"863ef3e1a92afbfdb97f31ad0fc7683ee943e9abcf2501590ff8f6551f47e5e5"
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1597,18 +1597,18 @@ mod tests {
|
||||||
let mut cache = SighashCache::new(&tx);
|
let mut cache = SighashCache::new(&tx);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
cache.segwit_signature_hash(0, &witness_script, value, EcdsaSighashType::All).unwrap(),
|
cache.segwit_signature_hash(0, &witness_script, value, EcdsaSighashType::All).unwrap(),
|
||||||
hex_hash!(Sighash, "64f3b0f4dd2bb3aa1ce8566d220cc74dda9df97d8490cc81d89d735c92e59fb6")
|
hex_from_slice!(Sighash, "64f3b0f4dd2bb3aa1ce8566d220cc74dda9df97d8490cc81d89d735c92e59fb6")
|
||||||
);
|
);
|
||||||
|
|
||||||
let cache = cache.segwit_cache();
|
let cache = cache.segwit_cache();
|
||||||
assert_eq!(cache.prevouts, hex_hash!(
|
assert_eq!(cache.prevouts, hex_from_slice!(
|
||||||
Hash, "b0287b4a252ac05af83d2dcef00ba313af78a3e9c329afa216eb3aa2a7b4613a"
|
"b0287b4a252ac05af83d2dcef00ba313af78a3e9c329afa216eb3aa2a7b4613a"
|
||||||
));
|
));
|
||||||
assert_eq!(cache.sequences, hex_hash!(
|
assert_eq!(cache.sequences, hex_from_slice!(
|
||||||
Hash, "18606b350cd8bf565266bc352f0caddcf01e8fa789dd8a15386327cf8cabe198"
|
"18606b350cd8bf565266bc352f0caddcf01e8fa789dd8a15386327cf8cabe198"
|
||||||
));
|
));
|
||||||
assert_eq!(cache.outputs, hex_hash!(
|
assert_eq!(cache.outputs, hex_from_slice!(
|
||||||
Hash, "de984f44532e2173ca0d64314fcefe6d30da6f8cf27bafa706da61df8a226c83"
|
"de984f44532e2173ca0d64314fcefe6d30da6f8cf27bafa706da61df8a226c83"
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1634,18 +1634,18 @@ mod tests {
|
||||||
let mut cache = SighashCache::new(&tx);
|
let mut cache = SighashCache::new(&tx);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
cache.segwit_signature_hash(0, &witness_script, value, EcdsaSighashType::All).unwrap(),
|
cache.segwit_signature_hash(0, &witness_script, value, EcdsaSighashType::All).unwrap(),
|
||||||
hex_hash!(Sighash, "185c0be5263dce5b4bb50a047973c1b6272bfbd0103a89444597dc40b248ee7c")
|
hex_from_slice!(Sighash, "185c0be5263dce5b4bb50a047973c1b6272bfbd0103a89444597dc40b248ee7c")
|
||||||
);
|
);
|
||||||
|
|
||||||
let cache = cache.segwit_cache();
|
let cache = cache.segwit_cache();
|
||||||
assert_eq!(cache.prevouts, hex_hash!(
|
assert_eq!(cache.prevouts, hex_from_slice!(
|
||||||
Hash, "74afdc312af5183c4198a40ca3c1a275b485496dd3929bca388c4b5e31f7aaa0"
|
"74afdc312af5183c4198a40ca3c1a275b485496dd3929bca388c4b5e31f7aaa0"
|
||||||
));
|
));
|
||||||
assert_eq!(cache.sequences, hex_hash!(
|
assert_eq!(cache.sequences, hex_from_slice!(
|
||||||
Hash, "3bb13029ce7b1f559ef5e747fcac439f1455a2ec7c5f09b72290795e70665044"
|
"3bb13029ce7b1f559ef5e747fcac439f1455a2ec7c5f09b72290795e70665044"
|
||||||
));
|
));
|
||||||
assert_eq!(cache.outputs, hex_hash!(
|
assert_eq!(cache.outputs, hex_from_slice!(
|
||||||
Hash, "bc4d309071414bed932f98832b27b4d76dad7e6c1346f487a8fdbb8eb90307cc"
|
"bc4d309071414bed932f98832b27b4d76dad7e6c1346f487a8fdbb8eb90307cc"
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue