use crate::new_index::FundingInfo;
use openassets_tapyrus::openassets::asset_id::AssetId;
use openassets_tapyrus::openassets::marker_output::Metadata;
use serde::ser::SerializeStruct;
use serde::{Serialize, Serializer};
use std::str::FromStr;
use tapyrus::blockdata::transaction::{Transaction, TxOut};
use tapyrus::network::constants::Network;
use serde::de::Deserialize;
use serde::de::Deserializer;
use serde::de::MapAccess;
use serde::de::Visitor;
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct OpenAsset {
pub asset_id: AssetId,
pub asset_quantity: u64,
pub metadata: Metadata,
}
impl Serialize for OpenAsset {
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut state = serializer.serialize_struct("OpenAsset", 3)?;
state.serialize_field("asset_id", &format!("{}", &self.asset_id))?;
state.serialize_field("asset_quantity", &self.asset_quantity)?;
state.serialize_field("metadata", &self.metadata)?;
state.end()
}
}
impl<'de> Deserialize<'de> for OpenAsset {
fn deserialize<D>(deserializer: D) -> Result<Self, <D as Deserializer<'de>>::Error>
where
D: Deserializer<'de>,
{
deserializer.deserialize_struct(
"OpenAsset",
&["asset_id", "asset_quantity", "metadata"],
OpenAssetVisitor,
)
}
}
struct OpenAssetVisitor;
#[derive(Deserialize)]
enum OpenAssetField {
AssetId,
AssetQuantity,
Metadata,
}
impl<'de> Visitor<'de> for OpenAssetVisitor {
type Value = OpenAsset;
fn expecting(
&self,
formatter: &mut std::fmt::Formatter<'_>,
) -> std::result::Result<(), std::fmt::Error> {
formatter.write_str("`asset_id`, `asset_quantity`, or `metadata`")
}
fn visit_map<A>(self, mut map: A) -> Result<Self::Value, <A as MapAccess<'de>>::Error>
where
A: MapAccess<'de>,
{
let mut asset_id = None;
let mut asset_quantity = None;
let mut metadata = None;
while let Some(key) = map.next_key()? {
match key {
OpenAssetField::AssetId => {
if asset_id.is_some() {
return Err(serde::de::Error::duplicate_field("asset_id"));
}
let str = map.next_value::<String>()?;
let deserialized = AssetId::from_str(&str).map_err(serde::de::Error::custom)?;
asset_id = Some(deserialized);
}
OpenAssetField::AssetQuantity => {
if asset_quantity.is_some() {
return Err(serde::de::Error::duplicate_field("asset_quantity"));
}
asset_quantity = Some(map.next_value::<u64>()?);
}
OpenAssetField::Metadata => {
if metadata.is_some() {
return Err(serde::de::Error::duplicate_field("metadata"));
}
let deserialized: Metadata = Metadata::new(map.next_value::<Vec<u8>>()?);
metadata = Some(deserialized);
}
}
}
let asset_id = asset_id.ok_or_else(|| serde::de::Error::missing_field("asset_id"))?;
let asset_quantity =
asset_quantity.ok_or_else(|| serde::de::Error::missing_field("asset_quantity"))?;
let metadata = metadata.ok_or_else(|| serde::de::Error::missing_field("metadata"))?;
Ok(OpenAsset {
asset_id,
asset_quantity,
metadata,
})
}
}
pub trait OpenAssetOutput {
fn open_assets_colored(&self) -> Option<&Self>;
fn open_assets_uncolored(&self) -> Option<&Self>;
}
impl OpenAssetOutput for FundingInfo {
fn open_assets_colored(&self) -> Option<&Self> {
if self.open_asset.is_some() {
Some(self)
} else {
None
}
}
fn open_assets_uncolored(&self) -> Option<&Self> {
if self.open_asset.is_none() {
Some(self)
} else {
None
}
}
}
pub fn compute_assets(
prev_outs: Vec<(TxOut, Option<OpenAsset>)>,
marker_output_index: usize,
txn: &Transaction,
quantities: Vec<u64>,
network_type: Network,
metadata: &Metadata,
) -> Vec<Option<OpenAsset>> {
assert!(quantities.len() <= txn.output.len() - 1);
assert!(!prev_outs.is_empty());
let mut result = Vec::new();
let issuance_asset_id = AssetId::new(
&prev_outs
.first()
.expect("previous outputs is not found")
.0
.script_pubkey,
network_type,
);
for i in 0..marker_output_index {
let asset = if i < quantities.len() && quantities[i] > 0 {
Some(OpenAsset {
asset_id: issuance_asset_id.clone(),
asset_quantity: quantities[i],
metadata: metadata.clone(),
})
} else {
None
};
result.push(asset);
}
result.push(None);
let mut input_enum = prev_outs.iter();
let mut input_units_left = 0;
let mut current_input = None;
for i in (marker_output_index + 1)..(quantities.len() + 1) {
let quantity = quantities[i - 1];
let mut output_units_left = quantity;
let mut asset_id: Option<AssetId> = None;
while output_units_left > 0 {
if input_units_left == 0 {
current_input = input_enum.next();
if let Some((_, Some(asset))) = current_input {
input_units_left = asset.asset_quantity;
}
}
if let Some((_, Some(asset))) = current_input {
let progress = if input_units_left < output_units_left {
input_units_left
} else {
output_units_left
};
output_units_left -= progress;
input_units_left -= progress;
if asset_id.is_none() {
asset_id = Some(asset.asset_id.clone());
} else if asset_id != Some(asset.asset_id.clone()) {
panic!("invalid asset");
}
}
}
let asset = if asset_id.is_some() && quantity > 0 {
Some(OpenAsset {
asset_id: asset_id.unwrap(),
asset_quantity: quantity,
metadata: metadata.clone(),
})
} else {
None
};
result.push(asset);
}
for _ in (quantities.len() + 1)..txn.output.len() {
result.push(None);
}
result
}
#[cfg(test)]
pub mod test_helper {
use super::*;
use openassets_tapyrus::openassets::marker_output::{Metadata, Payload};
use std::str::FromStr;
use tapyrus::blockdata::script::{Builder, Script};
use tapyrus::blockdata::transaction::{OutPoint, TxIn};
use tapyrus::consensus::deserialize;
use tapyrus::hash_types::Txid;
use tapyrus::network::constants::Network;
pub fn asset_1(quantity: u64, metadata: Metadata) -> Option<OpenAsset> {
let hex = "76a914010966776006953d5567439e5e39f86a0d273bee88ac";
let script = Builder::from(hex::decode(hex).unwrap()).into_script();
Some(OpenAsset {
asset_id: AssetId::new(&script, Network::Prod),
asset_quantity: quantity,
metadata: metadata,
})
}
pub fn asset_2(quantity: u64, metadata: Metadata) -> Option<OpenAsset> {
let hex = "76a914b60fd86c7464b08d83d98ebeb59655d71be3b22688ac";
let script = Builder::from(hex::decode(hex).unwrap()).into_script();
Some(OpenAsset {
asset_id: AssetId::new(&script, Network::Prod),
asset_quantity: quantity,
metadata: metadata,
})
}
pub fn asset_3(quantity: u64, metadata: Metadata) -> Option<OpenAsset> {
let hex = "76a9149f00983b75904599a5e9c2e53c8b1002fc42e9ac88ac";
let script = Builder::from(hex::decode(hex).unwrap()).into_script();
Some(OpenAsset {
asset_id: AssetId::new(&script, Network::Prod),
asset_quantity: quantity,
metadata: metadata,
})
}
pub fn default_input(index: u32) -> TxIn {
let out_point = OutPoint::new(
Txid::from_str("0000000000000000000000000000000000000000000000000000000000000000")
.unwrap(),
index,
);
TxIn {
previous_output: out_point,
script_sig: Script::new(),
sequence: 0,
witness: vec![],
}
}
pub fn empty_metadata() -> Metadata {
let payload_bytes = hex::decode("4f4101000000").unwrap();
let payload: std::result::Result<Payload, tapyrus::consensus::encode::Error> =
deserialize(&payload_bytes);
payload.unwrap().metadata
}
pub fn url_metadata() -> Metadata {
let payload_bytes =
hex::decode("4f4101000364007b1b753d68747470733a2f2f6370722e736d2f35596753553150672d71")
.unwrap();
let payload: std::result::Result<Payload, tapyrus::consensus::encode::Error> =
deserialize(&payload_bytes);
payload.unwrap().metadata
}
}
#[cfg(test)]
mod tests {
use super::*;
use hex;
use tapyrus::blockdata::script::Builder;
use tapyrus::blockdata::transaction::{Transaction, TxOut};
use tapyrus::network::constants::Network;
use crate::open_assets::test_helper::*;
#[test]
fn test_compute_assets_transfer() {
let prev_outs = vec![
(TxOut::default(), asset_1(10, empty_metadata())),
(TxOut::default(), asset_2(20, empty_metadata())),
];
let index = 0;
let valid_marker = TxOut {
value: 0,
script_pubkey: Builder::from(
hex::decode(
"6a244f410100030a01131b753d68747470733a2f2f6370722e736d2f35596753553150672d71",
)
.unwrap(),
)
.into_script(),
};
let txn = Transaction {
version: 1,
lock_time: 0,
input: vec![default_input(0), default_input(1)],
output: vec![
valid_marker,
TxOut::default(),
TxOut::default(),
TxOut::default(),
],
};
let quantities = vec![10, 1, 19];
let assets = compute_assets(
prev_outs,
index,
&txn,
quantities,
Network::Prod,
&url_metadata(),
);
assert_eq!(assets.len(), 4);
assert_eq!(assets[0], None);
assert_eq!(assets[1], asset_1(10, url_metadata()));
assert_eq!(assets[2], asset_2(1, url_metadata()));
assert_eq!(assets[3], asset_2(19, url_metadata()));
}
#[test]
fn test_compute_assets_issuance() {
let p2pkh = Builder::from(
hex::decode("76a914010966776006953d5567439e5e39f86a0d273bee88ac").unwrap(),
)
.into_script();
let prev_outs = vec![
(
TxOut {
value: 1000,
script_pubkey: p2pkh,
},
None,
),
(TxOut::default(), None),
];
let index = 3;
let valid_marker = TxOut {
value: 0,
script_pubkey: Builder::from(
hex::decode(
"6a244f410100030a01131b753d68747470733a2f2f6370722e736d2f35596753553150672d71",
)
.unwrap(),
)
.into_script(),
};
let txn = Transaction {
version: 1,
lock_time: 0,
input: vec![default_input(0), default_input(1)],
output: vec![
TxOut::default(),
TxOut::default(),
TxOut::default(),
valid_marker,
],
};
let quantities = vec![10, 1, 19];
let assets = compute_assets(
prev_outs,
index,
&txn,
quantities,
Network::Prod,
&url_metadata(),
);
assert_eq!(assets.len(), 4);
assert_eq!(assets[0], asset_1(10, url_metadata()));
assert_eq!(assets[1], asset_1(1, url_metadata()));
assert_eq!(assets[2], asset_1(19, url_metadata()));
assert_eq!(assets[3], None);
}
#[test]
fn test_compute_assets_both() {
let p2pkh = Builder::from(
hex::decode("76a914010966776006953d5567439e5e39f86a0d273bee88ac").unwrap(),
)
.into_script();
let prev_outs = vec![
(
TxOut {
value: 1000,
script_pubkey: p2pkh,
},
asset_2(3, empty_metadata()),
),
(TxOut::default(), asset_2(2, empty_metadata())),
(TxOut::default(), None),
(TxOut::default(), asset_2(5, empty_metadata())),
(TxOut::default(), asset_2(3, empty_metadata())),
(TxOut::default(), asset_3(9, empty_metadata())),
];
let index = 2;
let valid_marker = TxOut {
value: 0,
script_pubkey: Builder::from(
hex::decode(
"6a244f41010006000a060007031b753d68747470733a2f2f6370722e736d2f35596753553150672d71",
)
.unwrap(),
)
.into_script(),
};
let txn = Transaction {
version: 1,
lock_time: 0,
input: vec![
default_input(0),
default_input(1),
default_input(2),
default_input(3),
default_input(4),
default_input(5),
],
output: vec![
TxOut::default(),
TxOut::default(),
valid_marker,
TxOut::default(),
TxOut::default(),
TxOut::default(),
TxOut::default(),
],
};
let quantities = vec![0, 10, 6, 0, 7, 3];
let assets = compute_assets(
prev_outs,
index,
&txn,
quantities,
Network::Prod,
&empty_metadata(),
);
assert_eq!(assets.len(), 7);
assert_eq!(assets[0], None);
assert_eq!(assets[1], asset_1(10, empty_metadata()));
assert_eq!(assets[2], None);
assert_eq!(assets[3], asset_2(6, empty_metadata()));
assert_eq!(assets[4], None);
assert_eq!(assets[5], asset_2(7, empty_metadata()));
assert_eq!(assets[6], asset_3(3, empty_metadata()));
}
#[test]
fn test_compute_assets_contains_uncolored() {
let prev_outs = vec![
(TxOut::default(), asset_1(2, empty_metadata())),
(TxOut::default(), asset_1(5, empty_metadata())),
(TxOut::default(), asset_2(9, empty_metadata())),
];
let index = 0;
let valid_marker = TxOut {
value: 0,
script_pubkey: Builder::from(
hex::decode(
"6a244f410100030703031b753d68747470733a2f2f6370722e736d2f35596753553150672d71",
)
.unwrap(),
)
.into_script(),
};
let txn = Transaction {
version: 1,
lock_time: 0,
input: vec![default_input(0), default_input(1), default_input(2)],
output: vec![
valid_marker,
TxOut::default(),
TxOut::default(),
TxOut::default(),
TxOut::default(),
TxOut::default(),
],
};
let quantities = vec![7, 3, 3];
let assets = compute_assets(
prev_outs,
index,
&txn,
quantities,
Network::Prod,
&url_metadata(),
);
assert_eq!(assets.len(), 6);
assert_eq!(assets[0], None);
assert_eq!(assets[1], asset_1(7, url_metadata()));
assert_eq!(assets[2], asset_2(3, url_metadata()));
assert_eq!(assets[3], asset_2(3, url_metadata()));
assert_eq!(assets[4], None);
assert_eq!(assets[5], None);
}
}