diff --git a/pallas-validate/src/utils.rs b/pallas-validate/src/utils.rs index e9dcdc41..1c771f00 100644 --- a/pallas-validate/src/utils.rs +++ b/pallas-validate/src/utils.rs @@ -128,17 +128,25 @@ pub fn empty_value() -> Value { Value::Multiasset(0, std::collections::BTreeMap::new()) } +fn add_lovelace(first: Coin, second: Coin, err: &ValidationError) -> Result { + first.checked_add(second).ok_or_else(|| err.clone()) +} + pub fn add_values( first: &Value, second: &Value, err: &ValidationError, ) -> Result { match (first, second) { - (Value::Coin(f), Value::Coin(s)) => Ok(Value::Coin(f + s)), - (Value::Multiasset(f, fma), Value::Coin(s)) => Ok(Value::Multiasset(f + s, fma.clone())), - (Value::Coin(f), Value::Multiasset(s, sma)) => Ok(Value::Multiasset(f + s, sma.clone())), + (Value::Coin(f), Value::Coin(s)) => Ok(Value::Coin(add_lovelace(*f, *s, err)?)), + (Value::Multiasset(f, fma), Value::Coin(s)) => { + Ok(Value::Multiasset(add_lovelace(*f, *s, err)?, fma.clone())) + } + (Value::Coin(f), Value::Multiasset(s, sma)) => { + Ok(Value::Multiasset(add_lovelace(*f, *s, err)?, sma.clone())) + } (Value::Multiasset(f, fma), Value::Multiasset(s, sma)) => Ok(Value::Multiasset( - f + s, + add_lovelace(*f, *s, err)?, coerce_to_coin( &add_multiasset_values(&coerce_to_i64(fma), &coerce_to_i64(sma)), err, @@ -153,16 +161,20 @@ pub fn conway_add_values( err: &ValidationError, ) -> Result { match (first, second) { - (ConwayValue::Coin(f), ConwayValue::Coin(s)) => Ok(ConwayValue::Coin(f + s)), - (ConwayValue::Multiasset(f, fma), ConwayValue::Coin(s)) => { - Ok(ConwayValue::Multiasset(f + s, fma.clone())) - } - (ConwayValue::Coin(f), ConwayValue::Multiasset(s, sma)) => { - Ok(ConwayValue::Multiasset(f + s, sma.clone())) + (ConwayValue::Coin(f), ConwayValue::Coin(s)) => { + Ok(ConwayValue::Coin(add_lovelace(*f, *s, err)?)) } + (ConwayValue::Multiasset(f, fma), ConwayValue::Coin(s)) => Ok(ConwayValue::Multiasset( + add_lovelace(*f, *s, err)?, + fma.clone(), + )), + (ConwayValue::Coin(f), ConwayValue::Multiasset(s, sma)) => Ok(ConwayValue::Multiasset( + add_lovelace(*f, *s, err)?, + sma.clone(), + )), (ConwayValue::Multiasset(f, fma), ConwayValue::Multiasset(s, sma)) => { Ok(ConwayValue::Multiasset( - f + s, + add_lovelace(*f, *s, err)?, conway_coerce_to_coin( &conway_add_multiasset_values(&coerce_to_u64(fma), &coerce_to_u64(sma)), err, @@ -380,13 +392,16 @@ fn coerce_to_u64(value: &ConwayMultiasset) -> ConwayMultiasset, - _err: &ValidationError, + err: &ValidationError, ) -> Result, ValidationError> { let mut res: Vec<(PolicyId, _)> = Vec::new(); for (policy, assets) in value.iter() { let mut aa: Vec<(AssetName, Coin)> = Vec::new(); for (asset_name, amount) in assets.iter() { - aa.push((asset_name.clone(), *amount as u64)); + aa.push(( + asset_name.clone(), + u64::try_from(*amount).map_err(|_| err.clone())?, + )); } res.push((*policy, aa.into_iter().collect())); } diff --git a/pallas-validate/tests/utils.rs b/pallas-validate/tests/utils.rs new file mode 100644 index 00000000..4a60b2f6 --- /dev/null +++ b/pallas-validate/tests/utils.rs @@ -0,0 +1,71 @@ +use std::collections::BTreeMap; + +use pallas_primitives::{ + AssetName, PolicyId, + alonzo::{Multiasset, Value}, + conway::Value as ConwayValue, +}; +use pallas_validate::utils::{ + PostAlonzoError, ValidationError, add_minted_value, add_values, conway_add_values, +}; + +fn single_asset(amount: A) -> Multiasset { + let policy = PolicyId::from([0u8; 28]); + let asset_name = AssetName::from(b"asset".to_vec()); + + let mut assets = BTreeMap::new(); + assets.insert(asset_name, amount); + + let mut multiasset = BTreeMap::new(); + multiasset.insert(policy, assets); + multiasset +} + +#[test] +fn add_minted_value_rejects_burn_of_missing_asset() { + let err = ValidationError::PostAlonzo(PostAlonzoError::NegativeValue); + + let result = add_minted_value(&Value::Coin(0), &single_asset(-1), &err); + + assert!(matches!( + result, + Err(ValidationError::PostAlonzo(PostAlonzoError::NegativeValue)) + )); +} + +#[test] +fn add_minted_value_rejects_burn_below_zero() { + let err = ValidationError::PostAlonzo(PostAlonzoError::NegativeValue); + let base = Value::Multiasset(0, single_asset(1)); + + let result = add_minted_value(&base, &single_asset(-2), &err); + + assert!(matches!( + result, + Err(ValidationError::PostAlonzo(PostAlonzoError::NegativeValue)) + )); +} + +#[test] +fn add_values_rejects_lovelace_overflow() { + let err = ValidationError::PostAlonzo(PostAlonzoError::NegativeValue); + + let result = add_values(&Value::Coin(u64::MAX), &Value::Coin(1), &err); + + assert!(matches!( + result, + Err(ValidationError::PostAlonzo(PostAlonzoError::NegativeValue)) + )); +} + +#[test] +fn conway_add_values_rejects_lovelace_overflow() { + let err = ValidationError::PostAlonzo(PostAlonzoError::NegativeValue); + + let result = conway_add_values(&ConwayValue::Coin(u64::MAX), &ConwayValue::Coin(1), &err); + + assert!(matches!( + result, + Err(ValidationError::PostAlonzo(PostAlonzoError::NegativeValue)) + )); +}