use super::*;
use linked_hash_map::LinkedHashMap;
const MD_MAX_LEN: usize = 64;
#[wasm_bindgen]
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct MetadataMap(LinkedHashMap<TransactionMetadatum, TransactionMetadatum>);
to_from_bytes!(MetadataMap);
#[wasm_bindgen]
impl MetadataMap {
pub fn new() -> Self {
Self(LinkedHashMap::new())
}
pub fn len(&self) -> usize {
self.0.len()
}
pub fn insert(
&mut self,
key: &TransactionMetadatum,
value: &TransactionMetadatum,
) -> Option<TransactionMetadatum> {
self.0.insert(key.clone(), value.clone())
}
pub fn insert_str(
&mut self,
key: &str,
value: &TransactionMetadatum,
) -> Result<Option<TransactionMetadatum>, JsError> {
Ok(self.insert(&TransactionMetadatum::new_text(key.to_owned())?, value))
}
pub fn insert_i32(
&mut self,
key: i32,
value: &TransactionMetadatum,
) -> Option<TransactionMetadatum> {
self.insert(&TransactionMetadatum::new_int(&Int::new_i32(key)), value)
}
pub fn get(&self, key: &TransactionMetadatum) -> Result<TransactionMetadatum, JsError> {
self.0
.get(key)
.map(|v| v.clone())
.ok_or_else(|| JsError::from_str(&format!("key {:?} not found", key)))
}
pub fn get_str(&self, key: &str) -> Result<TransactionMetadatum, JsError> {
self.get(&TransactionMetadatum::new_text(key.to_owned())?)
}
pub fn get_i32(&self, key: i32) -> Result<TransactionMetadatum, JsError> {
self.get(&TransactionMetadatum::new_int(&Int::new_i32(key)))
}
pub fn has(&self, key: &TransactionMetadatum) -> bool {
self.0.contains_key(key)
}
pub fn keys(&self) -> MetadataList {
MetadataList(
self.0
.iter()
.map(|(k, _v)| k.clone())
.collect::<Vec<TransactionMetadatum>>(),
)
}
}
#[wasm_bindgen]
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct MetadataList(Vec<TransactionMetadatum>);
to_from_bytes!(MetadataList);
#[wasm_bindgen]
impl MetadataList {
pub fn new() -> Self {
Self(Vec::new())
}
pub fn len(&self) -> usize {
self.0.len()
}
pub fn get(&self, index: usize) -> TransactionMetadatum {
self.0[index].clone()
}
pub fn add(&mut self, elem: &TransactionMetadatum) {
self.0.push(elem.clone());
}
}
#[wasm_bindgen]
#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
pub enum TransactionMetadatumKind {
MetadataMap,
MetadataList,
Int,
Bytes,
Text,
}
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
enum TransactionMetadatumEnum {
MetadataMap(MetadataMap),
MetadataList(MetadataList),
Int(Int),
Bytes(Vec<u8>),
Text(String),
}
#[wasm_bindgen]
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct TransactionMetadatum(TransactionMetadatumEnum);
to_from_bytes!(TransactionMetadatum);
#[wasm_bindgen]
impl TransactionMetadatum {
pub fn new_map(map: &MetadataMap) -> Self {
Self(TransactionMetadatumEnum::MetadataMap(map.clone()))
}
pub fn new_list(list: &MetadataList) -> Self {
Self(TransactionMetadatumEnum::MetadataList(list.clone()))
}
pub fn new_int(int: &Int) -> Self {
Self(TransactionMetadatumEnum::Int(int.clone()))
}
pub fn new_bytes(bytes: Vec<u8>) -> Result<TransactionMetadatum, JsError> {
if bytes.len() > MD_MAX_LEN {
Err(JsError::from_str(&format!(
"Max metadata bytes too long: {}, max = {}",
bytes.len(),
MD_MAX_LEN
)))
} else {
Ok(Self(TransactionMetadatumEnum::Bytes(bytes)))
}
}
pub fn new_text(text: String) -> Result<TransactionMetadatum, JsError> {
if text.len() > MD_MAX_LEN {
Err(JsError::from_str(&format!(
"Max metadata string too long: {}, max = {}",
text.len(),
MD_MAX_LEN
)))
} else {
Ok(Self(TransactionMetadatumEnum::Text(text)))
}
}
pub fn kind(&self) -> TransactionMetadatumKind {
match &self.0 {
TransactionMetadatumEnum::MetadataMap(_) => TransactionMetadatumKind::MetadataMap,
TransactionMetadatumEnum::MetadataList(_) => TransactionMetadatumKind::MetadataList,
TransactionMetadatumEnum::Int(_) => TransactionMetadatumKind::Int,
TransactionMetadatumEnum::Bytes(_) => TransactionMetadatumKind::Bytes,
TransactionMetadatumEnum::Text(_) => TransactionMetadatumKind::Text,
}
}
pub fn as_map(&self) -> Result<MetadataMap, JsError> {
match &self.0 {
TransactionMetadatumEnum::MetadataMap(x) => Ok(x.clone()),
_ => Err(JsError::from_str("not a map")),
}
}
pub fn as_list(&self) -> Result<MetadataList, JsError> {
match &self.0 {
TransactionMetadatumEnum::MetadataList(x) => Ok(x.clone()),
_ => Err(JsError::from_str("not a list")),
}
}
pub fn as_int(&self) -> Result<Int, JsError> {
match &self.0 {
TransactionMetadatumEnum::Int(x) => Ok(x.clone()),
_ => Err(JsError::from_str("not an int")),
}
}
pub fn as_bytes(&self) -> Result<Vec<u8>, JsError> {
match &self.0 {
TransactionMetadatumEnum::Bytes(x) => Ok(x.clone()),
_ => Err(JsError::from_str("not bytes")),
}
}
pub fn as_text(&self) -> Result<String, JsError> {
match &self.0 {
TransactionMetadatumEnum::Text(x) => Ok(x.clone()),
_ => Err(JsError::from_str("not text")),
}
}
}
impl serde::Serialize for TransactionMetadatum {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let json_str = decode_metadatum_to_json_str(self, MetadataJsonSchema::DetailedSchema)
.map_err(|e| serde::ser::Error::custom(&format!("{:?}", e)))?;
serializer.serialize_str(&json_str)
}
}
impl<'de> serde::de::Deserialize<'de> for TransactionMetadatum {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::de::Deserializer<'de>,
{
let s = <String as serde::de::Deserialize>::deserialize(deserializer)?;
encode_json_str_to_metadatum(s.clone(), MetadataJsonSchema::DetailedSchema).map_err(|e| {
serde::de::Error::invalid_value(
serde::de::Unexpected::Str(&s),
&format!("{:?}", e).as_str(),
)
})
}
}
impl JsonSchema for TransactionMetadatum {
fn schema_name() -> String {
String::from("TransactionMetadatum")
}
fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
String::json_schema(gen)
}
fn is_referenceable() -> bool {
String::is_referenceable()
}
}
pub type TransactionMetadatumLabel = BigNum;
#[wasm_bindgen]
#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
pub struct TransactionMetadatumLabels(Vec<TransactionMetadatumLabel>);
to_from_bytes!(TransactionMetadatumLabels);
#[wasm_bindgen]
impl TransactionMetadatumLabels {
pub fn new() -> Self {
Self(Vec::new())
}
pub fn len(&self) -> usize {
self.0.len()
}
pub fn get(&self, index: usize) -> TransactionMetadatumLabel {
self.0[index].clone()
}
pub fn add(&mut self, elem: &TransactionMetadatumLabel) {
self.0.push(elem.clone());
}
}
#[wasm_bindgen]
#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
pub struct GeneralTransactionMetadata(
LinkedHashMap<TransactionMetadatumLabel, TransactionMetadatum>,
);
impl_to_from!(GeneralTransactionMetadata);
#[wasm_bindgen]
impl GeneralTransactionMetadata {
pub fn new() -> Self {
Self(LinkedHashMap::new())
}
pub fn len(&self) -> usize {
self.0.len()
}
pub fn insert(
&mut self,
key: &TransactionMetadatumLabel,
value: &TransactionMetadatum,
) -> Option<TransactionMetadatum> {
self.0.insert(key.clone(), value.clone())
}
pub fn get(&self, key: &TransactionMetadatumLabel) -> Option<TransactionMetadatum> {
self.0.get(key).map(|v| v.clone())
}
pub fn keys(&self) -> TransactionMetadatumLabels {
TransactionMetadatumLabels(
self.0
.iter()
.map(|(k, _v)| k.clone())
.collect::<Vec<TransactionMetadatumLabel>>(),
)
}
}
impl serde::Serialize for GeneralTransactionMetadata {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let map = self.0.iter().collect::<std::collections::BTreeMap<_, _>>();
map.serialize(serializer)
}
}
impl<'de> serde::de::Deserialize<'de> for GeneralTransactionMetadata {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::de::Deserializer<'de>,
{
let map = <std::collections::BTreeMap<_, _> as serde::de::Deserialize>::deserialize(
deserializer,
)?;
Ok(Self(map.into_iter().collect()))
}
}
impl JsonSchema for GeneralTransactionMetadata {
fn schema_name() -> String {
String::from("GeneralTransactionMetadata")
}
fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
std::collections::BTreeMap::<TransactionMetadatumLabel, TransactionMetadatum>::json_schema(
gen,
)
}
fn is_referenceable() -> bool {
std::collections::BTreeMap::<TransactionMetadatumLabel, TransactionMetadatum>::is_referenceable()
}
}
#[wasm_bindgen]
#[derive(Clone, Debug, Ord, PartialOrd, serde::Serialize, serde::Deserialize, JsonSchema)]
pub struct AuxiliaryData {
metadata: Option<GeneralTransactionMetadata>,
native_scripts: Option<NativeScripts>,
plutus_scripts: Option<PlutusScripts>,
prefer_alonzo_format: bool,
}
impl std::cmp::PartialEq<Self> for AuxiliaryData {
fn eq(&self, other: &Self) -> bool {
self.metadata.eq(&other.metadata)
&& self.native_scripts.eq(&other.native_scripts)
&& self.plutus_scripts.eq(&other.plutus_scripts)
}
}
impl std::cmp::Eq for AuxiliaryData {}
impl_to_from!(AuxiliaryData);
#[wasm_bindgen]
impl AuxiliaryData {
pub fn new() -> Self {
Self {
metadata: None,
native_scripts: None,
plutus_scripts: None,
prefer_alonzo_format: false,
}
}
pub fn metadata(&self) -> Option<GeneralTransactionMetadata> {
self.metadata.clone()
}
pub fn set_metadata(&mut self, metadata: &GeneralTransactionMetadata) {
self.metadata = Some(metadata.clone());
}
pub fn native_scripts(&self) -> Option<NativeScripts> {
self.native_scripts.clone()
}
pub fn set_native_scripts(&mut self, native_scripts: &NativeScripts) {
self.native_scripts = Some(native_scripts.clone())
}
pub fn plutus_scripts(&self) -> Option<PlutusScripts> {
self.plutus_scripts.clone()
}
pub fn set_plutus_scripts(&mut self, plutus_scripts: &PlutusScripts) {
self.plutus_scripts = Some(plutus_scripts.clone())
}
pub fn prefer_alonzo_format(&self) -> bool {
self.prefer_alonzo_format.clone()
}
pub fn set_prefer_alonzo_format(&mut self, prefer: bool) {
self.prefer_alonzo_format = prefer
}
}
#[wasm_bindgen]
pub fn encode_arbitrary_bytes_as_metadatum(bytes: &[u8]) -> TransactionMetadatum {
let mut list = MetadataList::new();
for chunk in bytes.chunks(MD_MAX_LEN) {
list.add(&TransactionMetadatum::new_bytes(chunk.to_vec()).unwrap());
}
TransactionMetadatum::new_list(&list)
}
#[wasm_bindgen]
pub fn decode_arbitrary_bytes_from_metadatum(
metadata: &TransactionMetadatum,
) -> Result<Vec<u8>, JsError> {
let mut bytes = Vec::new();
for elem in metadata.as_list()?.0 {
bytes.append(&mut elem.as_bytes()?);
}
Ok(bytes)
}
#[wasm_bindgen]
#[derive(Copy, Clone, Eq, PartialEq)]
pub enum MetadataJsonSchema {
NoConversions,
BasicConversions,
DetailedSchema,
}
fn supports_tagged_values(schema: MetadataJsonSchema) -> bool {
match schema {
MetadataJsonSchema::NoConversions | MetadataJsonSchema::BasicConversions => false,
MetadataJsonSchema::DetailedSchema => true,
}
}
fn hex_string_to_bytes(hex: &str) -> Option<Vec<u8>> {
if hex.starts_with("0x") {
hex::decode(&hex[2..]).ok()
} else {
None
}
}
fn bytes_to_hex_string(bytes: &[u8]) -> String {
format!("0x{}", hex::encode(bytes))
}
#[wasm_bindgen]
pub fn encode_json_str_to_metadatum(
json: String,
schema: MetadataJsonSchema,
) -> Result<TransactionMetadatum, JsError> {
let value = serde_json::from_str(&json).map_err(|e| JsError::from_str(&e.to_string()))?;
encode_json_value_to_metadatum(value, schema)
}
pub fn encode_json_value_to_metadatum(
value: serde_json::Value,
schema: MetadataJsonSchema,
) -> Result<TransactionMetadatum, JsError> {
use serde_json::Value;
fn encode_number(x: serde_json::Number) -> Result<TransactionMetadatum, JsError> {
if let Some(x) = x.as_u64() {
Ok(TransactionMetadatum::new_int(&Int::new(&utils::to_bignum(
x,
))))
} else if let Some(x) = x.as_i64() {
Ok(TransactionMetadatum::new_int(&Int::new_negative(
&utils::to_bignum(-x as u64),
)))
} else {
Err(JsError::from_str("floats not allowed in metadata"))
}
}
fn encode_string(
s: String,
schema: MetadataJsonSchema,
) -> Result<TransactionMetadatum, JsError> {
if schema == MetadataJsonSchema::BasicConversions {
match hex_string_to_bytes(&s) {
Some(bytes) => TransactionMetadatum::new_bytes(bytes),
None => TransactionMetadatum::new_text(s),
}
} else {
TransactionMetadatum::new_text(s)
}
}
fn encode_array(
json_arr: Vec<Value>,
schema: MetadataJsonSchema,
) -> Result<TransactionMetadatum, JsError> {
let mut arr = MetadataList::new();
for value in json_arr {
arr.add(&encode_json_value_to_metadatum(value, schema)?);
}
Ok(TransactionMetadatum::new_list(&arr))
}
match schema {
MetadataJsonSchema::NoConversions | MetadataJsonSchema::BasicConversions => match value {
Value::Null => Err(JsError::from_str("null not allowed in metadata")),
Value::Bool(_) => Err(JsError::from_str("bools not allowed in metadata")),
Value::Number(x) => encode_number(x),
Value::String(s) => encode_string(s, schema),
Value::Array(json_arr) => encode_array(json_arr, schema),
Value::Object(json_obj) => {
let mut map = MetadataMap::new();
for (raw_key, value) in json_obj {
let key = if schema == MetadataJsonSchema::BasicConversions {
match raw_key.parse::<i128>() {
Ok(x) => TransactionMetadatum::new_int(&Int(x)),
Err(_) => encode_string(raw_key, schema)?,
}
} else {
TransactionMetadatum::new_text(raw_key)?
};
map.insert(&key, &encode_json_value_to_metadatum(value, schema)?);
}
Ok(TransactionMetadatum::new_map(&map))
}
},
MetadataJsonSchema::DetailedSchema => match value {
Value::Object(obj) if obj.len() == 1 => {
let (k, v) = obj.into_iter().next().unwrap();
fn tag_mismatch() -> JsError {
JsError::from_str("key does not match type")
}
match k.as_str() {
"int" => match v {
Value::Number(x) => encode_number(x),
_ => Err(tag_mismatch()),
},
"string" => {
encode_string(v.as_str().ok_or_else(tag_mismatch)?.to_owned(), schema)
}
"bytes" => match hex::decode(v.as_str().ok_or_else(tag_mismatch)?) {
Ok(bytes) => TransactionMetadatum::new_bytes(bytes),
Err(_) => Err(JsError::from_str(
"invalid hex string in tagged byte-object",
)),
},
"list" => encode_array(v.as_array().ok_or_else(tag_mismatch)?.clone(), schema),
"map" => {
let mut map = MetadataMap::new();
fn map_entry_err() -> JsError {
JsError::from_str("entry format in detailed schema map object not correct. Needs to be of form {\"k\": \"key\", \"v\": value}")
}
for entry in v.as_array().ok_or_else(tag_mismatch)? {
let entry_obj = entry.as_object().ok_or_else(map_entry_err)?;
let raw_key = entry_obj.get("k").ok_or_else(map_entry_err)?;
let value = entry_obj.get("v").ok_or_else(map_entry_err)?;
let key = encode_json_value_to_metadatum(raw_key.clone(), schema)?;
map.insert(
&key,
&encode_json_value_to_metadatum(value.clone(), schema)?,
);
}
Ok(TransactionMetadatum::new_map(&map))
}
invalid_key => Err(JsError::from_str(&format!(
"key '{}' in tagged object not valid",
invalid_key
))),
}
}
_ => Err(JsError::from_str(
"DetailedSchema requires types to be tagged objects",
)),
},
}
}
#[wasm_bindgen]
pub fn decode_metadatum_to_json_str(
metadatum: &TransactionMetadatum,
schema: MetadataJsonSchema,
) -> Result<String, JsError> {
let value = decode_metadatum_to_json_value(metadatum, schema)?;
serde_json::to_string(&value).map_err(|e| JsError::from_str(&e.to_string()))
}
pub fn decode_metadatum_to_json_value(
metadatum: &TransactionMetadatum,
schema: MetadataJsonSchema,
) -> Result<serde_json::Value, JsError> {
use serde_json::Value;
use std::convert::TryFrom;
fn decode_key(
key: &TransactionMetadatum,
schema: MetadataJsonSchema,
) -> Result<String, JsError> {
match &key.0 {
TransactionMetadatumEnum::Text(s) => Ok(s.clone()),
TransactionMetadatumEnum::Bytes(b) if schema != MetadataJsonSchema::NoConversions => {
Ok(bytes_to_hex_string(b.as_ref()))
}
TransactionMetadatumEnum::Int(i) if schema != MetadataJsonSchema::NoConversions => {
let int_str = if i.0 >= 0 {
u64::try_from(i.0).map(|x| x.to_string())
} else {
i64::try_from(i.0).map(|x| x.to_string())
};
int_str.map_err(|e| JsError::from_str(&e.to_string()))
}
TransactionMetadatumEnum::MetadataList(list)
if schema == MetadataJsonSchema::DetailedSchema =>
{
decode_metadatum_to_json_str(&TransactionMetadatum::new_list(&list), schema)
}
TransactionMetadatumEnum::MetadataMap(map)
if schema == MetadataJsonSchema::DetailedSchema =>
{
decode_metadatum_to_json_str(&TransactionMetadatum::new_map(&map), schema)
}
_ => Err(JsError::from_str(&format!(
"key type {:?} not allowed in JSON under specified schema",
key.0
))),
}
}
let (type_key, value) = match &metadatum.0 {
TransactionMetadatumEnum::MetadataMap(map) => match schema {
MetadataJsonSchema::NoConversions | MetadataJsonSchema::BasicConversions => {
let mut json_map = serde_json::map::Map::with_capacity(map.len());
for (key, value) in map.0.iter() {
json_map.insert(
decode_key(key, schema)?,
decode_metadatum_to_json_value(value, schema)?,
);
}
("map", Value::from(json_map))
}
MetadataJsonSchema::DetailedSchema => (
"map",
Value::from(
map.0
.iter()
.map(|(key, value)| {
let k = decode_metadatum_to_json_value(key, schema)?;
let v = decode_metadatum_to_json_value(value, schema)?;
let mut kv_obj = serde_json::map::Map::with_capacity(2);
kv_obj.insert(String::from("k"), Value::from(k));
kv_obj.insert(String::from("v"), v);
Ok(Value::from(kv_obj))
})
.collect::<Result<Vec<_>, JsError>>()?,
),
),
},
TransactionMetadatumEnum::MetadataList(arr) => (
"list",
Value::from(
arr.0
.iter()
.map(|e| decode_metadatum_to_json_value(e, schema))
.collect::<Result<Vec<_>, JsError>>()?,
),
),
TransactionMetadatumEnum::Int(x) => (
"int",
if x.0 >= 0 {
Value::from(u64::try_from(x.0).map_err(|e| JsError::from_str(&e.to_string()))?)
} else {
Value::from(i64::try_from(x.0).map_err(|e| JsError::from_str(&e.to_string()))?)
},
),
TransactionMetadatumEnum::Bytes(bytes) => (
"bytes",
match schema {
MetadataJsonSchema::NoConversions => Err(JsError::from_str(
"bytes not allowed in JSON in specified schema",
)),
MetadataJsonSchema::BasicConversions => {
Ok(Value::from(bytes_to_hex_string(bytes.as_ref())))
}
MetadataJsonSchema::DetailedSchema => Ok(Value::from(hex::encode(bytes))),
}?,
),
TransactionMetadatumEnum::Text(s) => ("string", Value::from(s.clone())),
};
if supports_tagged_values(schema) {
let mut wrapper = serde_json::map::Map::with_capacity(1);
wrapper.insert(String::from(type_key), value);
Ok(Value::from(wrapper))
} else {
Ok(value)
}
}
impl cbor_event::se::Serialize for MetadataMap {
fn serialize<'se, W: Write>(
&self,
serializer: &'se mut Serializer<W>,
) -> cbor_event::Result<&'se mut Serializer<W>> {
serializer.write_map(cbor_event::Len::Len(self.0.len() as u64))?;
for (key, value) in &self.0 {
key.serialize(serializer)?;
value.serialize(serializer)?;
}
Ok(serializer)
}
}
impl Deserialize for MetadataMap {
fn deserialize<R: BufRead + Seek>(raw: &mut Deserializer<R>) -> Result<Self, DeserializeError> {
let mut table = LinkedHashMap::new();
let mut entries: Vec<(TransactionMetadatum, TransactionMetadatum)> = Vec::new();
(|| -> Result<_, DeserializeError> {
let len = raw.map()?;
while match len {
cbor_event::Len::Len(n) => entries.len() < n as usize,
cbor_event::Len::Indefinite => true,
} {
if raw.cbor_type()? == CBORType::Special {
assert_eq!(raw.special()?, CBORSpecial::Break);
break;
}
let key = TransactionMetadatum::deserialize(raw)?;
let value = TransactionMetadatum::deserialize(raw)?;
entries.push((key.clone(), value));
}
Ok(())
})()
.map_err(|e| e.annotate("MetadataMap"))?;
entries.iter().for_each(|(k, v)| {
if table.insert(k.clone(), v.clone()).is_some() {
}
});
Ok(Self(table))
}
}
impl cbor_event::se::Serialize for MetadataList {
fn serialize<'se, W: Write>(
&self,
serializer: &'se mut Serializer<W>,
) -> cbor_event::Result<&'se mut Serializer<W>> {
serializer.write_array(cbor_event::Len::Len(self.0.len() as u64))?;
for element in &self.0 {
element.serialize(serializer)?;
}
Ok(serializer)
}
}
impl Deserialize for MetadataList {
fn deserialize<R: BufRead + Seek>(raw: &mut Deserializer<R>) -> Result<Self, DeserializeError> {
let mut arr = Vec::new();
(|| -> Result<_, DeserializeError> {
let len = raw.array()?;
while match len {
cbor_event::Len::Len(n) => arr.len() < n as usize,
cbor_event::Len::Indefinite => true,
} {
if raw.cbor_type()? == CBORType::Special {
assert_eq!(raw.special()?, CBORSpecial::Break);
break;
}
arr.push(TransactionMetadatum::deserialize(raw)?);
}
Ok(())
})()
.map_err(|e| e.annotate("MetadataList"))?;
Ok(Self(arr))
}
}
impl cbor_event::se::Serialize for TransactionMetadatumEnum {
fn serialize<'se, W: Write>(
&self,
serializer: &'se mut Serializer<W>,
) -> cbor_event::Result<&'se mut Serializer<W>> {
match self {
TransactionMetadatumEnum::MetadataMap(x) => x.serialize(serializer),
TransactionMetadatumEnum::MetadataList(x) => x.serialize(serializer),
TransactionMetadatumEnum::Int(x) => x.serialize(serializer),
TransactionMetadatumEnum::Bytes(x) => serializer.write_bytes(&x),
TransactionMetadatumEnum::Text(x) => serializer.write_text(&x),
}
}
}
impl Deserialize for TransactionMetadatumEnum {
fn deserialize<R: BufRead + Seek>(raw: &mut Deserializer<R>) -> Result<Self, DeserializeError> {
match raw.cbor_type()? {
CBORType::Array => {
MetadataList::deserialize(raw).map(TransactionMetadatumEnum::MetadataList)
}
CBORType::Map => {
MetadataMap::deserialize(raw).map(TransactionMetadatumEnum::MetadataMap)
}
CBORType::Bytes => TransactionMetadatum::new_bytes(raw.bytes()?)
.map(|m| m.0)
.map_err(|e| DeserializeFailure::Metadata(e).into()),
CBORType::Text => TransactionMetadatum::new_text(raw.text()?)
.map(|m| m.0)
.map_err(|e| DeserializeFailure::Metadata(e).into()),
CBORType::UnsignedInteger | CBORType::NegativeInteger => {
Int::deserialize(raw).map(TransactionMetadatumEnum::Int)
}
_ => Err(DeserializeError::new(
"TransactionMetadatumEnum",
DeserializeFailure::NoVariantMatched.into(),
)),
}
}
}
impl cbor_event::se::Serialize for TransactionMetadatum {
fn serialize<'se, W: Write>(
&self,
serializer: &'se mut Serializer<W>,
) -> cbor_event::Result<&'se mut Serializer<W>> {
self.0.serialize(serializer)
}
}
impl Deserialize for TransactionMetadatum {
fn deserialize<R: BufRead + Seek>(raw: &mut Deserializer<R>) -> Result<Self, DeserializeError> {
Ok(Self(TransactionMetadatumEnum::deserialize(raw)?))
}
}
impl cbor_event::se::Serialize for TransactionMetadatumLabels {
fn serialize<'se, W: Write>(
&self,
serializer: &'se mut Serializer<W>,
) -> cbor_event::Result<&'se mut Serializer<W>> {
serializer.write_array(cbor_event::Len::Len(self.0.len() as u64))?;
for element in &self.0 {
element.serialize(serializer)?;
}
Ok(serializer)
}
}
impl Deserialize for TransactionMetadatumLabels {
fn deserialize<R: BufRead + Seek>(raw: &mut Deserializer<R>) -> Result<Self, DeserializeError> {
let mut arr = Vec::new();
(|| -> Result<_, DeserializeError> {
let len = raw.array()?;
while match len {
cbor_event::Len::Len(n) => arr.len() < n as usize,
cbor_event::Len::Indefinite => true,
} {
if raw.cbor_type()? == CBORType::Special {
assert_eq!(raw.special()?, CBORSpecial::Break);
break;
}
arr.push(TransactionMetadatumLabel::deserialize(raw)?);
}
Ok(())
})()
.map_err(|e| e.annotate("TransactionMetadatumLabels"))?;
Ok(Self(arr))
}
}
impl cbor_event::se::Serialize for GeneralTransactionMetadata {
fn serialize<'se, W: Write>(
&self,
serializer: &'se mut Serializer<W>,
) -> cbor_event::Result<&'se mut Serializer<W>> {
serializer.write_map(cbor_event::Len::Len(self.0.len() as u64))?;
for (key, value) in &self.0 {
key.serialize(serializer)?;
value.serialize(serializer)?;
}
Ok(serializer)
}
}
impl Deserialize for GeneralTransactionMetadata {
fn deserialize<R: BufRead + Seek>(raw: &mut Deserializer<R>) -> Result<Self, DeserializeError> {
let mut table = LinkedHashMap::new();
(|| -> Result<_, DeserializeError> {
let len = raw.map()?;
while match len {
cbor_event::Len::Len(n) => table.len() < n as usize,
cbor_event::Len::Indefinite => true,
} {
if raw.cbor_type()? == CBORType::Special {
assert_eq!(raw.special()?, CBORSpecial::Break);
break;
}
let key = TransactionMetadatumLabel::deserialize(raw)?;
let value = TransactionMetadatum::deserialize(raw)?;
if table.insert(key.clone(), value).is_some() {
return Err(DeserializeFailure::DuplicateKey(Key::Str(String::from(
"some complicated/unsupported type",
)))
.into());
}
}
Ok(())
})()
.map_err(|e| e.annotate("GeneralTransactionMetadata"))?;
Ok(Self(table))
}
}
impl cbor_event::se::Serialize for AuxiliaryData {
fn serialize<'se, W: Write>(
&self,
serializer: &'se mut Serializer<W>,
) -> cbor_event::Result<&'se mut Serializer<W>> {
if !self.prefer_alonzo_format && self.metadata.is_some() && self.plutus_scripts.is_none() {
match &self.native_scripts() {
Some(native_scripts) => {
serializer.write_array(cbor_event::Len::Len(2))?;
self.metadata.as_ref().unwrap().serialize(serializer)?;
native_scripts.serialize(serializer)
}
None => self.metadata.as_ref().unwrap().serialize(serializer),
}
} else {
let plutus_added_length = match &self.plutus_scripts {
Some(scripts) => 1 + (scripts.has_version(&Language::new_plutus_v2()) as u64),
_ => 0,
};
serializer.write_tag(259u64)?;
serializer.write_map(cbor_event::Len::Len(
opt64(&self.metadata) + opt64(&self.native_scripts) + plutus_added_length,
))?;
if let Some(metadata) = &self.metadata {
serializer.write_unsigned_integer(0)?;
metadata.serialize(serializer)?;
}
if let Some(native_scripts) = &self.native_scripts {
serializer.write_unsigned_integer(1)?;
native_scripts.serialize(serializer)?;
}
if let Some(plutus_scripts) = &self.plutus_scripts {
serializer.write_unsigned_integer(2)?;
plutus_scripts
.by_version(&Language::new_plutus_v1())
.serialize(serializer)?;
let v2 = plutus_scripts.by_version(&Language::new_plutus_v2());
if v2.len() > 0 {
serializer.write_unsigned_integer(3)?;
v2.serialize(serializer)?;
}
}
Ok(serializer)
}
}
}
impl Deserialize for AuxiliaryData {
fn deserialize<R: BufRead + Seek>(raw: &mut Deserializer<R>) -> Result<Self, DeserializeError> {
(|| -> Result<_, DeserializeError> {
match raw.cbor_type()? {
CBORType::Tag => {
let tag = raw.tag()?;
if tag != 259 {
return Err(DeserializeError::new(
"AuxiliaryData",
DeserializeFailure::TagMismatch {
found: tag,
expected: 259,
},
));
}
let len = raw.map()?;
let mut read_len = CBORReadLen::new(len);
let mut metadata = None;
let mut native_scripts = None;
let mut plutus_scripts_v1 = None;
let mut plutus_scripts_v2 = None;
let mut read = 0;
while match len {
cbor_event::Len::Len(n) => read < n as usize,
cbor_event::Len::Indefinite => true,
} {
match raw.cbor_type()? {
CBORType::UnsignedInteger => match raw.unsigned_integer()? {
0 => {
if metadata.is_some() {
return Err(
DeserializeFailure::DuplicateKey(Key::Uint(0)).into()
);
}
metadata = Some(
(|| -> Result<_, DeserializeError> {
read_len.read_elems(1)?;
Ok(GeneralTransactionMetadata::deserialize(raw)?)
})()
.map_err(|e| e.annotate("metadata"))?,
);
}
1 => {
if native_scripts.is_some() {
return Err(
DeserializeFailure::DuplicateKey(Key::Uint(1)).into()
);
}
native_scripts = Some(
(|| -> Result<_, DeserializeError> {
read_len.read_elems(1)?;
Ok(NativeScripts::deserialize(raw)?)
})()
.map_err(|e| e.annotate("native_scripts"))?,
);
}
2 => {
if plutus_scripts_v1.is_some() {
return Err(
DeserializeFailure::DuplicateKey(Key::Uint(2)).into()
);
}
plutus_scripts_v1 = Some(
(|| -> Result<_, DeserializeError> {
read_len.read_elems(1)?;
Ok(PlutusScripts::deserialize(raw)?)
})()
.map_err(|e| e.annotate("plutus_scripts_v1"))?,
);
}
3 => {
if plutus_scripts_v2.is_some() {
return Err(
DeserializeFailure::DuplicateKey(Key::Uint(3)).into()
);
}
plutus_scripts_v2 = Some(
(|| -> Result<_, DeserializeError> {
read_len.read_elems(1)?;
Ok(PlutusScripts::deserialize(raw)?
.map_as_version(&Language::new_plutus_v2()))
})()
.map_err(|e| e.annotate("plutus_scripts_v2"))?,
);
}
unknown_key => {
return Err(DeserializeFailure::UnknownKey(Key::Uint(
unknown_key,
))
.into())
}
},
CBORType::Text => match raw.text()?.as_str() {
unknown_key => {
return Err(DeserializeFailure::UnknownKey(Key::Str(
unknown_key.to_owned(),
))
.into())
}
},
CBORType::Special => match len {
cbor_event::Len::Len(_) => {
return Err(DeserializeFailure::BreakInDefiniteLen.into())
}
cbor_event::Len::Indefinite => match raw.special()? {
CBORSpecial::Break => break,
_ => return Err(DeserializeFailure::EndingBreakMissing.into()),
},
},
other_type => {
return Err(DeserializeFailure::UnexpectedKeyType(other_type).into())
}
}
read += 1;
}
read_len.finish()?;
let plutus_scripts = match (plutus_scripts_v1, plutus_scripts_v2) {
(Some(v1), Some(v2)) => Some(v1.merge(&v2)),
(Some(v1), _) => Some(v1),
(_, Some(v2)) => Some(v2),
_ => None,
};
Ok(Self {
metadata,
native_scripts,
plutus_scripts,
prefer_alonzo_format: true,
})
}
CBORType::Array => {
let len = raw.array()?;
let mut read_len = CBORReadLen::new(len);
read_len.read_elems(2)?;
let metadata = (|| -> Result<_, DeserializeError> {
Ok(GeneralTransactionMetadata::deserialize(raw)?)
})()
.map_err(|e| e.annotate("metadata"))?;
let native_scripts = (|| -> Result<_, DeserializeError> {
Ok(NativeScripts::deserialize(raw)?)
})()
.map_err(|e| e.annotate("native_scripts"))?;
match len {
cbor_event::Len::Len(_) => (),
cbor_event::Len::Indefinite => match raw.special()? {
CBORSpecial::Break => (),
_ => return Err(DeserializeFailure::EndingBreakMissing.into()),
},
}
Ok(Self {
metadata: Some(metadata),
native_scripts: Some(native_scripts),
plutus_scripts: None,
prefer_alonzo_format: false,
})
}
CBORType::Map => Ok(Self {
metadata: Some(
GeneralTransactionMetadata::deserialize(raw)
.map_err(|e| e.annotate("metadata"))?,
),
native_scripts: None,
plutus_scripts: None,
prefer_alonzo_format: false,
}),
_ => return Err(DeserializeFailure::NoVariantMatched)?,
}
})()
.map_err(|e| e.annotate("AuxiliaryData"))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn binary_encoding() {
let input_bytes = (0..1000).map(|x| x as u8).collect::<Vec<u8>>();
let metadata = encode_arbitrary_bytes_as_metadatum(input_bytes.as_ref());
let output_bytes = decode_arbitrary_bytes_from_metadatum(&metadata).expect("decode failed");
assert_eq!(input_bytes, output_bytes);
}
#[test]
fn json_encoding_no_conversions() {
let input_str = String::from("{\"receiver_id\": \"SJKdj34k3jjKFDKfjFUDfdjkfd\",\"sender_id\": \"jkfdsufjdk34h3Sdfjdhfduf873\",\"comment\": \"happy birthday\",\"tags\": [0, 264, -1024, 32]}");
let metadata =
encode_json_str_to_metadatum(input_str.clone(), MetadataJsonSchema::NoConversions)
.expect("encode failed");
let map = metadata.as_map().unwrap();
assert_eq!(
map.get_str("receiver_id").unwrap().as_text().unwrap(),
"SJKdj34k3jjKFDKfjFUDfdjkfd"
);
assert_eq!(
map.get_str("sender_id").unwrap().as_text().unwrap(),
"jkfdsufjdk34h3Sdfjdhfduf873"
);
assert_eq!(
map.get_str("comment").unwrap().as_text().unwrap(),
"happy birthday"
);
let tags = map.get_str("tags").unwrap().as_list().unwrap();
let tags_i32 = tags
.0
.iter()
.map(|md| md.as_int().unwrap().as_i32_or_fail().unwrap())
.collect::<Vec<i32>>();
assert_eq!(tags_i32, vec![0, 264, -1024, 32]);
let output_str = decode_metadatum_to_json_str(&metadata, MetadataJsonSchema::NoConversions)
.expect("decode failed");
let input_json: serde_json::Value = serde_json::from_str(&input_str).unwrap();
let output_json: serde_json::Value = serde_json::from_str(&output_str).unwrap();
assert_eq!(input_json, output_json);
}
#[test]
fn json_encoding_basic() {
let input_str = String::from(
"{\"0x8badf00d\": \"0xdeadbeef\",\"9\": 5,\"obj\": {\"a\":[{\"5\": 2},{}]}}",
);
let metadata =
encode_json_str_to_metadatum(input_str.clone(), MetadataJsonSchema::BasicConversions)
.expect("encode failed");
json_encoding_check_example_metadatum(&metadata);
let output_str =
decode_metadatum_to_json_str(&metadata, MetadataJsonSchema::BasicConversions)
.expect("decode failed");
let input_json: serde_json::Value = serde_json::from_str(&input_str).unwrap();
let output_json: serde_json::Value = serde_json::from_str(&output_str).unwrap();
assert_eq!(input_json, output_json);
}
#[test]
fn json_encoding_detailed() {
let input_str = String::from(
"{\"map\":[
{
\"k\":{\"bytes\":\"8badf00d\"},
\"v\":{\"bytes\":\"deadbeef\"}
},
{
\"k\":{\"int\":9},
\"v\":{\"int\":5}
},
{
\"k\":{\"string\":\"obj\"},
\"v\":{\"map\":[
{
\"k\":{\"string\":\"a\"},
\"v\":{\"list\":[
{\"map\":[
{
\"k\":{\"int\":5},
\"v\":{\"int\":2}
}
]},
{\"map\":[
]}
]}
}
]}
}
]}",
);
let metadata =
encode_json_str_to_metadatum(input_str.clone(), MetadataJsonSchema::DetailedSchema)
.expect("encode failed");
json_encoding_check_example_metadatum(&metadata);
let output_str =
decode_metadatum_to_json_str(&metadata, MetadataJsonSchema::DetailedSchema)
.expect("decode failed");
let input_json: serde_json::Value = serde_json::from_str(&input_str).unwrap();
let output_json: serde_json::Value = serde_json::from_str(&output_str).unwrap();
assert_eq!(input_json, output_json);
}
fn json_encoding_check_example_metadatum(metadata: &TransactionMetadatum) {
let map = metadata.as_map().unwrap();
assert_eq!(
map.get(&TransactionMetadatum::new_bytes(hex::decode("8badf00d").unwrap()).unwrap())
.unwrap()
.as_bytes()
.unwrap(),
hex::decode("deadbeef").unwrap()
);
assert_eq!(
map.get_i32(9)
.unwrap()
.as_int()
.unwrap()
.as_i32_or_fail()
.unwrap(),
5
);
let inner_map = map.get_str("obj").unwrap().as_map().unwrap();
let a = inner_map.get_str("a").unwrap().as_list().unwrap();
let a1 = a.get(0).as_map().unwrap();
assert_eq!(
a1.get_i32(5)
.unwrap()
.as_int()
.unwrap()
.as_i32_or_fail()
.unwrap(),
2
);
let a2 = a.get(1).as_map().unwrap();
assert_eq!(a2.keys().len(), 0);
}
#[test]
fn json_encoding_detailed_complex_key() {
let input_str = String::from(
"{\"map\":[
{
\"k\":{\"list\":[
{\"map\": [
{
\"k\": {\"int\": 5},
\"v\": {\"int\": -7}
},
{
\"k\": {\"string\": \"hello\"},
\"v\": {\"string\": \"world\"}
}
]},
{\"bytes\": \"ff00ff00\"}
]},
\"v\":{\"int\":5}
}
]}",
);
let metadata =
encode_json_str_to_metadatum(input_str.clone(), MetadataJsonSchema::DetailedSchema)
.expect("encode failed");
let map = metadata.as_map().unwrap();
let key = map.keys().get(0);
assert_eq!(
map.get(&key)
.unwrap()
.as_int()
.unwrap()
.as_i32_or_fail()
.unwrap(),
5
);
let key_list = key.as_list().unwrap();
assert_eq!(key_list.len(), 2);
let key_map = key_list.get(0).as_map().unwrap();
assert_eq!(
key_map
.get_i32(5)
.unwrap()
.as_int()
.unwrap()
.as_i32_or_fail()
.unwrap(),
-7
);
assert_eq!(
key_map.get_str("hello").unwrap().as_text().unwrap(),
"world"
);
let key_bytes = key_list.get(1).as_bytes().unwrap();
assert_eq!(key_bytes, hex::decode("ff00ff00").unwrap());
let output_str =
decode_metadatum_to_json_str(&metadata, MetadataJsonSchema::DetailedSchema)
.expect("decode failed");
let input_json: serde_json::Value = serde_json::from_str(&input_str).unwrap();
let output_json: serde_json::Value = serde_json::from_str(&output_str).unwrap();
assert_eq!(input_json, output_json);
}
#[test]
fn metadata_serialize() {
let mut gmd = GeneralTransactionMetadata::new();
let mdatum = TransactionMetadatum::new_text(String::from("string md")).unwrap();
gmd.insert(&to_bignum(100), &mdatum);
let mut aux_data = AuxiliaryData::new();
let ad0_deser = AuxiliaryData::from_bytes(aux_data.to_bytes()).unwrap();
assert_eq!(aux_data.to_bytes(), ad0_deser.to_bytes());
aux_data.set_metadata(&gmd);
let ad1_deser = AuxiliaryData::from_bytes(aux_data.to_bytes()).unwrap();
assert_eq!(aux_data.to_bytes(), ad1_deser.to_bytes());
let mut native_scripts = NativeScripts::new();
native_scripts.add(&NativeScript::new_timelock_start(&TimelockStart::new(20)));
aux_data.set_native_scripts(&native_scripts);
let ad2_deser = AuxiliaryData::from_bytes(aux_data.to_bytes()).unwrap();
assert_eq!(aux_data.to_bytes(), ad2_deser.to_bytes());
let mut plutus_scripts = PlutusScripts::new();
plutus_scripts.add(&PlutusScript::new([61u8; 29].to_vec()));
aux_data.set_plutus_scripts(&plutus_scripts);
let ad3_deser = AuxiliaryData::from_bytes(aux_data.to_bytes()).unwrap();
assert_eq!(aux_data.to_bytes(), ad3_deser.to_bytes());
}
#[test]
fn alonzo_metadata_round_trip() {
let bytes_alonzo = hex::decode("d90103a100a1186469737472696e67206d64").unwrap();
let aux_alonzo = AuxiliaryData::from_bytes(bytes_alonzo.clone()).unwrap();
assert!(aux_alonzo.prefer_alonzo_format);
assert_eq!(aux_alonzo.to_bytes(), bytes_alonzo);
let bytes_pre_alonzo = hex::decode("a1186469737472696e67206d64").unwrap();
let aux_pre_alonzo = AuxiliaryData::from_bytes(bytes_pre_alonzo.clone()).unwrap();
assert!(!aux_pre_alonzo.prefer_alonzo_format);
assert_eq!(aux_pre_alonzo.to_bytes(), bytes_pre_alonzo);
}
#[test]
fn metadatum_map_duplicate_keys() {
let bytes = hex::decode("a105a4781b232323232323232323232323232323232323232323232323232323827840232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323237840232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323236e232323232323232323232323232382a36f2323232323232323232323232323236a323030302d30312d303166232323232323784023232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323712323232323232323232323232323232323784023232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323a36f2323232323232323232323232323236a323030302d30312d303166232323232323784023232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323712323232323232323232323232323232323784023232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323752323232323232323232323232323232323232323236a323030302d30312d3031752323232323232323232323232323232323232323236a323030302d30312d3031").unwrap();
TransactionMetadatum::from_bytes(bytes).unwrap();
}
#[test]
fn test_auxiliary_data_roundtrip() {
fn auxiliary_data_roundtrip(plutus_scripts: &PlutusScripts) {
let mut aux = AuxiliaryData::new();
let mut metadata = GeneralTransactionMetadata::new();
metadata.insert(
&to_bignum(42),
&encode_json_str_to_metadatum(
"{ \"test\": 148 }".to_string(),
MetadataJsonSchema::BasicConversions,
)
.unwrap(),
);
aux.set_metadata(&metadata);
aux.set_native_scripts(&NativeScripts::from(vec![
NativeScript::new_timelock_start(&TimelockStart::new(1234556)),
]));
aux.set_plutus_scripts(plutus_scripts);
assert_eq!(AuxiliaryData::from_bytes(aux.to_bytes()).unwrap(), aux);
}
let bytes = hex::decode("4e4d01000033222220051200120011").unwrap();
let script_v1 = PlutusScript::from_bytes(bytes.clone()).unwrap();
let script_v2 = PlutusScript::from_bytes_v2(bytes.clone()).unwrap();
auxiliary_data_roundtrip(&PlutusScripts(vec![]));
auxiliary_data_roundtrip(&PlutusScripts(vec![script_v1.clone()]));
auxiliary_data_roundtrip(&PlutusScripts(vec![script_v2.clone()]));
auxiliary_data_roundtrip(&PlutusScripts(vec![script_v1.clone(), script_v2.clone()]));
}
}