use crate::types::{
entries::{global::lanes::UnitIssuerMap, GlobalDefinitionExt, LaneDefinition, UnitDefinition},
Ledger, TxStateManager,
};
use hdi::prelude::{
trace, wasm_error, ActionHash, ActionHashB64, AgentPubKeyB64, Deserialize, ExternResult,
Serialize,
};
use rhai::Map;
use serde::Deserializer;
use serde_json::Value;
use std::collections::BTreeMap;
use zfuel::fuel::ZFuel;
pub type UnitIndex = u8;
#[derive(Debug, Serialize, Clone, PartialEq, Eq, Default)]
pub struct UnitIndexMap(pub BTreeMap<String, ActionHashB64>);
impl UnitIndexMap {
pub fn new() -> Self {
Self(BTreeMap::new())
}
pub fn get_unit_indexes(&self) -> Vec<String> {
self.0.keys().cloned().collect()
}
}
impl<'de> Deserialize<'de> for UnitIndexMap {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let map: BTreeMap<String, ActionHashB64> = BTreeMap::deserialize(deserializer)?;
for key in map.keys() {
key.parse::<UnitIndex>().map_err(serde::de::Error::custom)?;
}
Ok(Self(map))
}
}
#[derive(Debug, Serialize, Clone, PartialEq, Eq)]
pub struct UnitMap(BTreeMap<String, ZFuel>);
impl<'de> Deserialize<'de> for UnitMap {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let map: BTreeMap<String, ZFuel> = BTreeMap::deserialize(deserializer)?;
for key in map.keys() {
key.parse::<UnitIndex>().map_err(serde::de::Error::custom)?;
}
Ok(Self(map))
}
}
impl TryFrom<Value> for UnitMap {
type Error = String;
fn try_from(value: Value) -> Result<Self, Self::Error> {
serde_json::from_value(value).map_err(|e| e.to_string())
}
}
impl Default for UnitMap {
fn default() -> Self {
Self::new()
}
}
impl From<Vec<(u32, &str)>> for UnitMap {
fn from(amounts: Vec<(u32, &str)>) -> Self {
let mut map = std::collections::BTreeMap::new();
for (k, v) in amounts {
map.insert(k.to_string(), v.parse().expect("Failed to parse amount"));
}
Self(map)
}
}
impl UnitMap {
pub fn remove_zero_amounts(&mut self, skip_base_unit: bool) {
self.0.retain(|key, amount| {
if skip_base_unit && key == "0" {
return true;
}
amount != &ZFuel::zero()
});
}
pub fn invert_signs(
&self,
global_definition: &GlobalDefinitionExt,
lane_definitions: &[ActionHash],
) -> ExternResult<Self> {
let unit_def_map =
self.get_unit_definition(global_definition, lane_definitions.to_vec())?;
let mut new_amount = Self::new();
for (key, amount) in self.0.iter() {
let unit_def = unit_def_map
.get(key)
.ok_or(wasm_error!("Unit definition not found for index: {}", key))?;
if unit_def.unit_type.should_invert_sign() {
new_amount.0.insert(key.clone(), (ZFuel::zero() - amount)?);
} else {
new_amount.0.insert(key.clone(), *amount);
}
}
Ok(new_amount)
}
pub fn check_unit_index_that_are_not_the_same(
&self,
other: &Option<Self>,
) -> ExternResult<Vec<String>> {
trace!("comparing self: {:?} with other: {:?}", self, other);
if let Some(other) = other {
Ok(self
.0
.iter()
.filter(|(key, value)| {
other.0.get(key.as_str()) != Some(*value)
})
.map(|(key, _)| key.clone())
.collect())
} else {
Ok(self.0.keys().cloned().collect())
}
}
pub fn should_run_credit_check(
&self,
expected_ledger: &Ledger,
previous_ledger: &Ledger,
unit_def_map: &BTreeMap<String, UnitDefinition>,
) -> bool {
for (key, _) in self.0.iter() {
let expected_value = expected_ledger.balance.get_safe(key);
let expected_proposed = expected_ledger.proposed_balance.get_safe(key);
let previous_value = previous_ledger.balance.get_safe(key);
let previous_proposed = previous_ledger.proposed_balance.get_safe(key);
let expected_net = match expected_value + expected_proposed {
Ok(value) => value,
Err(_) => continue,
};
let previous_net = match previous_value + previous_proposed {
Ok(value) => value,
Err(_) => continue,
};
if expected_net < previous_net && expected_net < ZFuel::zero() {
if let Some(unit_def) = unit_def_map.get(key) {
if unit_def.unit_type.first_law_of_thermodynamics() {
return true;
}
}
}
}
false
}
pub fn summarize(
&mut self,
handle: &TxStateManager,
new_amount: Self,
unit_def_map: &BTreeMap<String, UnitDefinition>,
) -> ExternResult<()> {
for (key, unit) in new_amount.0.iter() {
let unit_def = unit_def_map
.get(key)
.ok_or(wasm_error!("Unit definition not found for index: {}", key))?;
if let Some(updated_balance) = unit_def
.unit_type
.summarize(handle, key, self.0.get(key).unwrap_or(&ZFuel::zero()), unit)
.unwrap_or_else(|_| panic!("Failed to summarize {handle:?}"))
{
self.0.insert(key.clone(), updated_balance);
}
}
Ok(())
}
fn check_validity(&self, unit_def_map: &BTreeMap<String, UnitDefinition>) -> ExternResult<()> {
for index in self.get_unit_indexes() {
if self.0.get(&index).unwrap_or(&ZFuel::zero()) == &ZFuel::zero() {
let unit_def = unit_def_map.get(&index).ok_or(wasm_error!(
"Unit definition not found for index: {}",
index
))?;
if !unit_def.is_valid_for_zero() {
return Err(wasm_error!(
"unit index {} ({}) is not valid for zero",
index,
unit_def.unit_symbol
));
}
}
}
Ok(())
}
pub fn normalize_precision(
&mut self,
global_definition: &GlobalDefinitionExt,
lane_definitions: &[ActionHash],
) -> ExternResult<BTreeMap<String, UnitDefinition>> {
let unit_def_map =
self.get_unit_definition_inner(global_definition, lane_definitions.to_vec())?;
for (index, amount) in self.0.iter_mut() {
let unit_def = unit_def_map.get(index).ok_or(wasm_error!(
"Unit definition not found for index: {}",
index
))?;
unit_def.unit_type.normalize_precision(amount)?;
}
self.check_validity(&unit_def_map)?;
Ok(unit_def_map)
}
pub fn validate_units(
&self,
unit_def_map: &BTreeMap<String, UnitDefinition>,
) -> ExternResult<()> {
for (index, amount) in self.0.iter() {
let unit_def = unit_def_map.get(index).ok_or(wasm_error!(
"Unit definition not found for index: {}",
index
))?;
unit_def.unit_type.validate_unit(amount)?
}
Ok(())
}
pub fn disallow_rated_and_nameable_units(
&self,
unit_def_map: &BTreeMap<String, UnitDefinition>,
) -> ExternResult<()> {
for (index, amount) in self.0.iter() {
if amount == &ZFuel::zero() {
continue;
}
let unit_def = unit_def_map.get(index).ok_or(wasm_error!(
"Unit definition not found for index: {}",
index
))?;
if matches!(
unit_def.unit_type,
crate::types::entries::UnytType::RatedUnit(_)
| crate::types::entries::UnytType::NameableUnit(_)
) {
return Err(wasm_error!(
"Rated and Nameable units may only be transferred via agreements (parked spend and receipt), not in proposal/commitment/accept flow"
));
}
}
Ok(())
}
pub fn validate_unit_issuers(
&self,
author: &AgentPubKeyB64,
unit_issuers: &UnitIssuerMap,
unit_def_map: &BTreeMap<String, UnitDefinition>,
) -> ExternResult<()> {
for (index, amount) in self.0.iter() {
if amount <= &ZFuel::zero() {
continue;
}
let unit_def = unit_def_map.get(index).ok_or(wasm_error!(
"Unit definition not found for index: {}",
index
))?;
if !matches!(
unit_def.unit_type,
crate::types::entries::UnytType::RatedUnit(_)
| crate::types::entries::UnytType::NameableUnit(_)
) {
continue;
}
if let Some(allowed_issuers) = unit_issuers.get(index) {
if !allowed_issuers.contains(author) {
return Err(wasm_error!(format!(
"Agent {} is not allowed to issue unit index {}",
author, index
)));
}
}
}
Ok(())
}
pub fn get_unit_definition(
&self,
global_definition: &GlobalDefinitionExt,
lane_id: Vec<ActionHash>,
) -> ExternResult<BTreeMap<String, UnitDefinition>> {
let unit_def_map = self.get_unit_definition_inner(global_definition, lane_id.clone())?;
Ok(unit_def_map)
}
pub fn get_unit_definition_inner(
&self,
global_definition: &GlobalDefinitionExt,
lane_id: Vec<ActionHash>,
) -> ExternResult<BTreeMap<String, UnitDefinition>> {
let mut lanes = vec![];
for hash in lane_id {
let lane = LaneDefinition::must_get(&hash)?;
lanes.push(lane);
}
let unit_indexes = self.get_unit_indexes();
let mut result = BTreeMap::new();
for index in unit_indexes {
if let Some(service_unit_hash) = global_definition.lane_def.service_units.0.get(&index)
{
let service_unit = UnitDefinition::must_get(&service_unit_hash.clone().into())?;
result.insert(index, service_unit);
} else {
let service_unit_hash = lanes.iter().find_map(|l| l.service_units.0.get(&index));
if let Some(service_unit_hash) = service_unit_hash {
let service_unit = UnitDefinition::must_get(&service_unit_hash.clone().into())?;
result.insert(index, service_unit);
} else {
return Err(wasm_error!(
"unable to find the service unit definition for the index: {}",
index
));
}
}
}
Ok(result)
}
pub fn new() -> Self {
Self(BTreeMap::new())
}
pub fn get(&self, key: &str) -> Option<ZFuel> {
self.0.get(key).cloned()
}
pub fn get_safe(&self, key: &String) -> ZFuel {
self.0.get(key).cloned().unwrap_or(ZFuel::zero())
}
pub fn get_unit_indexes(&self) -> Vec<String> {
self.0.keys().cloned().collect()
}
pub fn into_iter(&self) -> BTreeMap<String, ZFuel> {
self.0.clone()
}
pub fn load(map: BTreeMap<String, ZFuel>) -> Self {
Self(map)
}
pub fn add(&mut self, amount: Self) -> ExternResult<()> {
for (key, amount) in amount.0.iter() {
if self.0.contains_key(key) {
self.0.insert(
key.clone(),
(self.0.get(key).unwrap_or(&ZFuel::zero()) + amount)?,
);
} else {
self.0.insert(key.clone(), *amount);
}
}
Ok(())
}
pub fn sub(&mut self, amount: Self) -> ExternResult<()> {
for (key, amount) in amount.0.iter() {
if self.0.contains_key(key) {
self.0.insert(
key.clone(),
(self.0.get(key).unwrap_or(&ZFuel::zero()) - amount)?,
);
} else {
let new_amount = (ZFuel::zero() - amount)?;
self.0.insert(key.clone(), new_amount);
}
}
Ok(())
}
pub fn to_value(&self) -> Value {
let json_map: serde_json::Map<String, Value> = self
.0
.iter()
.map(|(k, v)| (k.clone(), v.to_string().into()))
.collect();
json_map.into()
}
pub fn to_map(&self) -> Map {
let mut map = Map::new();
for (k, v) in self.0.iter() {
map.insert(k.clone().into(), v.to_string().into());
}
map
}
pub fn is_zero(&self) -> bool {
if self.0.is_empty() {
return true;
}
self.0.iter().all(|(_, value)| value == &ZFuel::zero())
}
pub fn is_negative(&self) -> bool {
if self.0.is_empty() {
return false;
}
self.0.iter().any(|(_, value)| value < &ZFuel::zero())
}
pub fn sum_vec(units: Vec<UnitMap>) -> ExternResult<UnitMap> {
let mut sum = Self::new();
for unit in units {
sum.add(unit)?;
}
Ok(sum)
}
pub fn push_base_unit(&mut self, amount: ZFuel) {
self.0.insert("0".to_string(), amount);
}
pub fn get_base_unyt(&self) -> ZFuel {
match self.0.get("0") {
Some(amount) => *amount,
None => ZFuel::zero(),
}
}
pub fn less_than(&self, other: &Self) -> bool {
for (key, amount) in self.0.iter() {
if amount < other.0.get(key).unwrap_or(&ZFuel::zero()) {
return true;
}
}
false
}
pub fn contains_base_unit(&self) -> bool {
self.0.contains_key("0")
}
pub fn get_service_units(&self) -> Vec<String> {
self.0.keys().filter(|k| k != &"0").cloned().collect()
}
}