use blueprint_tangle_extra::serde::{BoundedVec, new_bounded_string};
use rust_decimal::{Decimal, prelude::ToPrimitive};
use tangle_subxt::{
subxt::utils::H160,
tangle_testnet_runtime::api::runtime_types::{
sp_arithmetic::per_things::Percent,
tangle_primitives::services::{
pricing::{PricingQuote, ResourcePricing},
types::{Asset, AssetSecurityCommitment},
},
},
};
use crate::{
PricingError,
pricing_engine::{QuoteDetails, asset::AssetType},
};
use rust_decimal::prelude::FromPrimitive;
pub const PRICING_SCALE_PLACES: u32 = 9;
pub fn pricing_scale() -> Decimal {
let scale = 10u128.pow(PRICING_SCALE_PLACES);
Decimal::from_u128(scale).expect("Invalid pricing scale - this is a bug")
}
pub fn u128_to_bytes(value: u128) -> Vec<u8> {
value.to_le_bytes().to_vec()
}
pub fn bytes_to_u128(bytes: &[u8]) -> u128 {
let mut array = [0u8; 16];
if bytes.len() != 16 {
panic!("bytes_to_u128: Expected 16 bytes, got {}", bytes.len());
}
array.copy_from_slice(bytes);
u128::from_le_bytes(array)
}
pub fn u32_to_u128_bytes(value: u32) -> Vec<u8> {
let mut bytes = [0u8; 16];
bytes[0..4].copy_from_slice(&value.to_le_bytes());
bytes.to_vec()
}
pub fn create_on_chain_quote_type(
quote_details: &QuoteDetails,
) -> Result<PricingQuote, PricingError> {
let scale = pricing_scale();
let mapped_resources: Vec<tangle_subxt::tangle_testnet_runtime::api::runtime_types::tangle_primitives::services::pricing::ResourcePricing> = quote_details.resources
.iter()
.map(|resource| {
let resource_price = Decimal::from_f64(resource.price_per_unit_rate).unwrap_or_default();
let decimal_scaled_price = (resource_price * scale).trunc();
let scaled_price = if decimal_scaled_price.is_zero() {
let minimum_price = Decimal::new(1, PRICING_SCALE_PLACES);
minimum_price.to_u128().unwrap()
} else {
decimal_scaled_price.to_u128().unwrap()
};
tangle_subxt::tangle_testnet_runtime::api::runtime_types::tangle_primitives::services::pricing::ResourcePricing {
kind: new_bounded_string(resource.kind.clone()),
count: resource.count,
price_per_unit_rate: scaled_price,
}
})
.collect();
let resources = BoundedVec::<ResourcePricing>(mapped_resources.clone());
let security_commitments = if let Some(security_commitment) =
quote_details.security_commitments.clone()
{
let inner_asset_type = security_commitment
.asset
.ok_or(PricingError::Pricing("Missing asset".to_string()))?
.asset_type
.ok_or(PricingError::Pricing("Missing asset type".to_string()))?;
let asset = match inner_asset_type {
AssetType::Custom(asset) => {
let asset_id = bytes_to_u128(&asset);
Asset::Custom(asset_id)
}
AssetType::Erc20(address) => {
let address_bytes: [u8; 20] = address
.as_slice()
.try_into()
.expect("ERC20 address should be 20 bytes");
Asset::Erc20(H160::from(address_bytes))
}
};
let exposure_percent = Percent(security_commitment.exposure_percent as u8);
let mapped_security_commitment =
vec![tangle_subxt::tangle_testnet_runtime::api::runtime_types::tangle_primitives::services::types::AssetSecurityCommitment {
asset: asset.clone(),
exposure_percent,
}];
BoundedVec::<AssetSecurityCommitment<u128>>(mapped_security_commitment.clone())
} else {
BoundedVec::<AssetSecurityCommitment<u128>>(Vec::new())
};
let total_cost_rate = Decimal::from_f64(quote_details.total_cost_rate).unwrap_or_default();
let scaled_total_cost = (total_cost_rate * scale).trunc();
let scaled_total_cost = if scaled_total_cost.is_zero() {
let minimum_price = Decimal::new(1, PRICING_SCALE_PLACES);
minimum_price.to_u128().unwrap()
} else {
scaled_total_cost.to_u128().unwrap()
};
Ok(PricingQuote {
blueprint_id: quote_details.blueprint_id,
ttl_blocks: quote_details.ttl_blocks,
resources,
security_commitments,
total_cost_rate: scaled_total_cost,
timestamp: quote_details.timestamp,
expiry: quote_details.expiry,
})
}