diff --git a/crates/uplc/src/machine/error.rs b/crates/uplc/src/machine/error.rs index bac398ca..db2d5607 100644 --- a/crates/uplc/src/machine/error.rs +++ b/crates/uplc/src/machine/error.rs @@ -40,7 +40,14 @@ pub enum Error { NotAConstant(Value), #[error("The evaluation never reached a final state")] MachineNeverReachedDone, - + #[error("integerToByteString encountered negative size {0}")] + IntegerToByteStringNegativeSize(BigInt), + #[error("integerToByteString encountered negative input {0}")] + IntegerToByteStringNegativeInput(BigInt), + #[error("integerToByteString encountered size {0} which is bigger than the max size of {1}")] + IntegerToByteStringSizeTooBig(BigInt, i64), + #[error("integerToByteString encountered size {0} which is not enough space for {1} bytes")] + IntegerToByteStringSizeTooSmall(BigInt, usize), #[error("Decoding utf8")] Utf8(#[from] FromUtf8Error), #[error("Out of Bounds\n\nindex: {}\nbytestring: {}\npossible: 0 - {}", .0, hex::encode(.1), .1.len() - 1)] diff --git a/crates/uplc/src/machine/runtime.rs b/crates/uplc/src/machine/runtime.rs index 0d6c3604..cede5cdd 100644 --- a/crates/uplc/src/machine/runtime.rs +++ b/crates/uplc/src/machine/runtime.rs @@ -2,12 +2,14 @@ use std::{mem::size_of, ops::Deref, rc::Rc}; use num_bigint::BigInt; use num_integer::Integer; +use num_traits::{Signed, ToBytes, Zero}; use once_cell::sync::Lazy; use pallas::ledger::primitives::babbage::{Language, PlutusData}; use crate::{ ast::{Constant, Data, Type}, builtins::DefaultFunction, + machine::value::integer_log2, plutus_data_to_bytes, }; @@ -32,6 +34,8 @@ const BLST_P1_COMPRESSED_SIZE: usize = 48; const BLST_P2_COMPRESSED_SIZE: usize = 96; +const INTEGER_TO_BYTE_STRING_MAXIMUM_OUTPUT_LENGTH: i64 = 8192; + //#[derive(std::cmp::PartialEq)] //pub enum EvalMode { // Immediate, @@ -1298,6 +1302,80 @@ impl DefaultFunction { Ok(Value::Con(constant.into())) } + DefaultFunction::IntegerToByteString => { + let endianness = args[0].unwrap_bool()?; + let size = args[1].unwrap_integer()?; + let input = args[2].unwrap_integer()?; + + if size.is_negative() { + return Err(Error::IntegerToByteStringNegativeSize(size.clone())); + } + + if size > &INTEGER_TO_BYTE_STRING_MAXIMUM_OUTPUT_LENGTH.into() { + return Err(Error::IntegerToByteStringSizeTooBig( + size.clone(), + INTEGER_TO_BYTE_STRING_MAXIMUM_OUTPUT_LENGTH, + )); + } + + if size.is_zero() + && integer_log2(input.clone()) + >= 8 * INTEGER_TO_BYTE_STRING_MAXIMUM_OUTPUT_LENGTH + { + let required = integer_log2(input.clone()) / 8 + 1; + + return Err(Error::IntegerToByteStringSizeTooBig( + required.into(), + INTEGER_TO_BYTE_STRING_MAXIMUM_OUTPUT_LENGTH, + )); + } + + if input.is_negative() { + return Err(Error::IntegerToByteStringNegativeInput(input.clone())); + } + + let size_unwrapped: usize = size.try_into().unwrap(); + + if input.is_zero() { + let constant = Constant::ByteString(vec![0; size_unwrapped]); + + return Ok(Value::Con(constant.into())); + } + + let mut bytes = if *endianness { + input.to_be_bytes() + } else { + input.to_le_bytes() + }; + + if !size.is_zero() && bytes.len() > size_unwrapped { + return Err(Error::IntegerToByteStringSizeTooSmall( + size.clone(), + bytes.len(), + )); + } + + if size_unwrapped > 0 { + let padding_size = size_unwrapped - bytes.len(); + + let mut padding = vec![0; padding_size]; + + if *endianness { + padding.append(&mut bytes); + + bytes = padding; + } else { + bytes.append(&mut padding); + } + }; + + let constant = Constant::ByteString(bytes); + + Ok(Value::Con(constant.into())) + } + DefaultFunction::ByteStringToInteger => { + todo!("do it not live") + } } } } diff --git a/crates/uplc/src/machine/value.rs b/crates/uplc/src/machine/value.rs index 687a355a..37cac1fc 100644 --- a/crates/uplc/src/machine/value.rs +++ b/crates/uplc/src/machine/value.rs @@ -1,7 +1,7 @@ use std::{collections::VecDeque, mem::size_of, ops::Deref, rc::Rc}; use num_bigint::BigInt; -use num_traits::{Signed, ToPrimitive}; +use num_traits::{Signed, ToPrimitive, Zero}; use pallas::ledger::primitives::babbage::{self, PlutusData}; use crate::{ @@ -386,8 +386,13 @@ impl TryFrom<&Value> for Constant { } } -fn integer_log2(i: BigInt) -> i64 { +pub fn integer_log2(i: BigInt) -> i64 { + if i.is_zero() { + return 0; + } + let (_, bytes) = i.to_bytes_be(); + match bytes.first() { None => unreachable!("empty number?"), Some(u) => (8 - u.leading_zeros() - 1) as i64 + 8 * (bytes.len() - 1) as i64, @@ -524,6 +529,7 @@ mod tests { #[test] fn integer_log2_oracle() { // Values come from the Haskell implementation + assert_eq!(integer_log2(0.into()), 0); assert_eq!(integer_log2(1.into()), 0); assert_eq!(integer_log2(42.into()), 5); assert_eq!(