diff --git a/crates/uplc/src/machine/cost_model.rs b/crates/uplc/src/machine/cost_model.rs index 88242de2..22e2e016 100644 --- a/crates/uplc/src/machine/cost_model.rs +++ b/crates/uplc/src/machine/cost_model.rs @@ -2538,8 +2538,8 @@ impl BuiltinCosts { .cpu .cost(args[0].to_ex_mem(), args[1].to_ex_mem()), }, - DefaultFunction::IntegerToByteString => { - let size = args[1].cost_as_size()?; + d @ DefaultFunction::IntegerToByteString => { + let size = args[1].cost_as_size(d)?; ExBudget { mem: self.integer_to_byte_string.mem.cost( @@ -2630,8 +2630,8 @@ impl BuiltinCosts { ), } } - DefaultFunction::ReplicateByte => { - let size = args[0].cost_as_size()?; + d @ DefaultFunction::ReplicateByte => { + let size = args[0].cost_as_size(d)?; ExBudget { mem: self.replicate_byte.mem.cost(size, args[1].to_ex_mem()), diff --git a/crates/uplc/src/machine/error.rs b/crates/uplc/src/machine/error.rs index 9052d4dc..98278453 100644 --- a/crates/uplc/src/machine/error.rs +++ b/crates/uplc/src/machine/error.rs @@ -120,10 +120,14 @@ pub enum Error { DeserialisationError(String, Value), #[error("integer overflow")] OverflowError, - #[error("{0} is not within the bounds of Natural")] + #[error("{0} is not within the bounds of a Natural")] OutsideNaturalBounds(BigInt), + #[error("{0} is not within the bounds of a Byte")] + OutsideByteBounds(BigInt), #[error("readBit: index out of bounds")] ReadBitOutOfBounds, + #[error("writeBits: index out of bounds")] + WriteBitsOutOfBounds, #[error("blst error {0:?}")] Blst(blst::BLST_ERROR), #[error("blst::hashToGroup")] diff --git a/crates/uplc/src/machine/runtime.rs b/crates/uplc/src/machine/runtime.rs index d22f73c6..79685dc6 100644 --- a/crates/uplc/src/machine/runtime.rs +++ b/crates/uplc/src/machine/runtime.rs @@ -10,12 +10,12 @@ use crate::{ plutus_data_to_bytes, }; use itertools::Itertools; -use num_bigint::BigInt; +use num_bigint::{BigInt, Sign}; use num_integer::Integer; -use num_traits::{Signed, Zero}; +use num_traits::{FromPrimitive, Signed, Zero}; use once_cell::sync::Lazy; use pallas_primitives::conway::{Language, PlutusData}; -use std::{mem::size_of, ops::Deref, rc::Rc}; +use std::{io::Read, mem::size_of, ops::Deref, rc::Rc}; static SCALAR_PERIOD: Lazy = Lazy::new(|| { BigInt::from_bytes_be( @@ -1522,7 +1522,7 @@ impl DefaultFunction { .collect_vec() }; - Ok(Value::Con(Constant::ByteString(bytes_result).into())) + Ok(Value::byte_string(bytes_result)) } DefaultFunction::OrByteString => { let should_pad = args[0].unwrap_bool()?; @@ -1550,7 +1550,7 @@ impl DefaultFunction { .collect_vec() }; - Ok(Value::Con(Constant::ByteString(bytes_result).into())) + Ok(Value::byte_string(bytes_result)) } DefaultFunction::XorByteString => { let should_pad = args[0].unwrap_bool()?; @@ -1578,14 +1578,14 @@ impl DefaultFunction { .collect_vec() }; - Ok(Value::Con(Constant::ByteString(bytes_result).into())) + Ok(Value::byte_string(bytes_result)) } DefaultFunction::ComplementByteString => { let bytes = args[0].unwrap_byte_string()?; let result = bytes.into_iter().map(|b| b ^ 255).collect_vec(); - Ok(Value::Con(Constant::ByteString(result).into())) + Ok(Value::byte_string(result)) } DefaultFunction::ReadBit => { let bytes = args[0].unwrap_byte_string()?; @@ -1606,11 +1606,88 @@ impl DefaultFunction { let bit_test = (byte >> bit_offset) & 1 == 1; - Ok(Value::Con(Constant::Bool(bit_test).into())) + Ok(Value::bool(bit_test)) + } + DefaultFunction::WriteBits => { + let mut bytes = args[0].unwrap_byte_string()?.clone(); + let indices = args[1].unwrap_int_list()?; + let set_bit = args[2].unwrap_bool()?; + + for index in indices { + let Constant::Integer(bit_index) = index else { + unreachable!() + }; + + if *bit_index < 0.into() || *bit_index >= (bytes.len() * 8).into() { + return Err(Error::WriteBitsOutOfBounds); + } + + let (byte_index, bit_offset) = bit_index.div_rem(&8.into()); + + let bit_offset = usize::try_from(bit_offset).unwrap(); + + let flipped_index = bytes.len() - 1 - usize::try_from(byte_index).unwrap(); + + let bit_mask: u8 = 1 >> bit_offset; + + if *set_bit { + bytes[flipped_index] |= bit_mask; + } else { + bytes[flipped_index] &= !bit_mask; + } + } + + Ok(Value::byte_string(bytes)) + } + DefaultFunction::ReplicateByte => { + let size = args[0].unwrap_integer()?; + let byte = args[1].unwrap_integer()?; + + // Safe since this is checked by cost model + let size = usize::try_from(size).unwrap(); + + let Ok(byte) = u8::try_from(byte) else { + return Err(Error::OutsideByteBounds(byte.clone())); + }; + + let value = if size == 0 { + Value::byte_string(vec![]) + } else { + Value::byte_string([byte].repeat(size - 1)) + }; + + Ok(value) + } + DefaultFunction::ShiftByteString => { + let bytes = args[0].unwrap_byte_string()?; + let shift = args[1].unwrap_integer()?; + + let byte_length = bytes.len(); + + if BigInt::from_usize(byte_length).unwrap() * 8 < shift.abs() { + let mut new_vec = vec![]; + + new_vec.resize(byte_length, 0); + return Ok(Value::byte_string(new_vec)); + } + + let bytes = BigInt::from_bytes_be(Sign::NoSign, bytes); + + let is_shl = shift >= &0.into(); + + let bytes = if is_shl { + bytes << usize::try_from(shift.abs()).unwrap() + } else { + bytes >> usize::try_from(shift.abs()).unwrap() + } + .to_bytes_be() + .1 + .into_iter() + .take(byte_length) + .collect_vec(); + + Ok(Value::byte_string(bytes)) } - DefaultFunction::WriteBits => todo!(), - DefaultFunction::ReplicateByte => todo!(), - DefaultFunction::ShiftByteString => todo!(), DefaultFunction::RotateByteString => todo!(), DefaultFunction::CountSetBits => todo!(), DefaultFunction::FindFirstSetBit => todo!(), diff --git a/crates/uplc/src/machine/value.rs b/crates/uplc/src/machine/value.rs index 1f2fc626..d4df0f94 100644 --- a/crates/uplc/src/machine/value.rs +++ b/crates/uplc/src/machine/value.rs @@ -173,6 +173,19 @@ impl Value { Ok(list) } + pub(super) fn unwrap_int_list(&self) -> Result<&Vec, Error> { + let inner = self.unwrap_constant()?; + + let Constant::ProtoList(Type::Integer, list) = inner else { + return Err(Error::TypeMismatch( + Type::List(Type::Integer.into()), + inner.into(), + )); + }; + + Ok(list) + } + pub(super) fn unwrap_bls12_381_g1_element(&self) -> Result<&blst::blst_p1, Error> { let inner = self.unwrap_constant()?; @@ -211,18 +224,30 @@ impl Value { matches!(self, Value::Con(b) if matches!(b.as_ref(), Constant::Bool(_))) } - pub fn cost_as_size(&self) -> Result { + pub fn cost_as_size(&self, func: DefaultFunction) -> Result { let size = self.unwrap_integer()?; if size.is_negative() { - return Err(Error::IntegerToByteStringNegativeSize(size.clone())); + let error = match func { + DefaultFunction::IntegerToByteString => { + Error::IntegerToByteStringNegativeSize(size.clone()) + } + DefaultFunction::ReplicateByte => todo!(), + _ => unreachable!(), + }; + return Err(error); } if size > &BigInt::from(runtime::INTEGER_TO_BYTE_STRING_MAXIMUM_OUTPUT_LENGTH) { - return Err(Error::IntegerToByteStringSizeTooBig( - size.clone(), - runtime::INTEGER_TO_BYTE_STRING_MAXIMUM_OUTPUT_LENGTH, - )); + let error = match func { + DefaultFunction::IntegerToByteString => Error::IntegerToByteStringSizeTooBig( + size.clone(), + runtime::INTEGER_TO_BYTE_STRING_MAXIMUM_OUTPUT_LENGTH, + ), + DefaultFunction::ReplicateByte => todo!(), + _ => unreachable!(), + }; + return Err(error); } let arg1: i64 = u64::try_from(size).unwrap().try_into().unwrap();