use super::*;
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CoeusAsset {
pub bundles: Vec<SignedBundle>,
}
impl CoeusAsset {
const MAX_ASSET_SIZE: usize = 1024 * 1024;
const FEE_BYTES_OFFSET: u64 = 0;
const FEE_FLAKES_PER_BYTES: u64 = 3000;
pub fn fee(&self) -> u64 {
fn inner(asset: &CoeusAsset) -> Option<u64> {
let bytes = asset.size()?.checked_add(CoeusAsset::FEE_BYTES_OFFSET)?;
let size_fee = bytes.checked_mul(CoeusAsset::FEE_FLAKES_PER_BYTES)?;
let price = asset.bundles.iter().try_fold(Price::fee(size_fee), |price, bundle| {
price.checked_add(bundle.get_price())
})?;
Some(price.fee)
}
inner(self).unwrap_or(u64::MAX)
}
pub fn to_bytes(&self) -> Result<Vec<u8>> {
let json_val = serde_json::to_value(self)?;
let json_str = canonical_json(&json_val)?;
Ok(json_str.into_bytes())
}
pub fn from_bytes(bytes: &[u8]) -> Result<Self> {
ensure!(
bytes.len() <= Self::MAX_ASSET_SIZE,
format!("CoeusAsset max size is {} bytes", Self::MAX_ASSET_SIZE)
);
let this: Self = serde_json::from_slice(bytes)?;
let canonical_bytes = this.to_bytes()?;
ensure!(
bytes == canonical_bytes.as_slice(),
"Attempt to construct CoeusAsset from non-canonical bytes"
);
Ok(this)
}
fn size(&self) -> Option<u64> {
let json = serde_json::to_string(self).ok()?;
Some(json.len() as u64)
}
}
#[cfg(test)]
pub(crate) mod test {
use super::*;
#[test]
fn canonical_format_works() {
let canonical_json = r#"{"bundles":[]}"#.as_bytes();
let asset = CoeusAsset::from_bytes(canonical_json).unwrap();
assert!(asset.bundles.is_empty());
}
#[test]
fn non_canonical_format_fails() {
let canonical_json = r#"{"bundles": []}"#.as_bytes();
let err = CoeusAsset::from_bytes(canonical_json).unwrap_err();
assert_eq!(err.to_string(), "Attempt to construct CoeusAsset from non-canonical bytes");
}
}