From f3ace553557d74546bb7004107242a27eaf43cb8 Mon Sep 17 00:00:00 2001 From: Kasey White Date: Wed, 10 Aug 2022 01:27:11 -0400 Subject: [PATCH] Implement more builtins for bytestring and integer Co-authored-by: rvcas --- add_integers.uplc | 2 +- crates/cli/src/main.rs | 17 +- crates/uplc/src/machine/cost_model.rs | 75 ++++++- crates/uplc/src/machine/error.rs | 4 + crates/uplc/src/machine/runtime.rs | 289 +++++++++++++++----------- 5 files changed, 251 insertions(+), 136 deletions(-) diff --git a/add_integers.uplc b/add_integers.uplc index 8da11bcc..f7b7ffab 100644 --- a/add_integers.uplc +++ b/add_integers.uplc @@ -1,5 +1,5 @@ (program 1.0.0 - [ (builtin decodeUtf8) [ (builtin encodeUtf8) (con string "hello world") ] ] + [ (builtin divideInteger) (con integer 5) (con integer 2) ] ) diff --git a/crates/cli/src/main.rs b/crates/cli/src/main.rs index bd2f2880..7db46c6c 100644 --- a/crates/cli/src/main.rs +++ b/crates/cli/src/main.rs @@ -2,6 +2,7 @@ use std::{fmt::Write as _, fs}; use uplc::{ ast::{DeBruijn, FakeNamedDeBruijn, Name, NamedDeBruijn, Program, Term}, + machine::cost_model::ExBudget, parser, }; @@ -102,14 +103,24 @@ fn main() -> anyhow::Result<()> { Ok(term) => { let term: Term = term.try_into()?; - println!("{}", term.to_pretty()); + println!("\nResult\n------\n\n{}\n", term.to_pretty()); } Err(err) => { - eprintln!("{}", err); + eprintln!("\nError\n-----\n\n{}\n", err); } } - println!("\nCosts - memory: {} & cpu: {}", cost.mem, cost.cpu); + let budget = ExBudget::default(); + + println!( + "\nCosts\n-----\ncpu: {}\nmemory: {}", + budget.cpu - cost.cpu, + budget.mem - cost.mem + ); + println!( + "\nBudget\n------\ncpu: {}\nmemory: {}\n", + cost.cpu, cost.mem + ); } }, } diff --git a/crates/uplc/src/machine/cost_model.rs b/crates/uplc/src/machine/cost_model.rs index 7e4e9d44..7d8a2fff 100644 --- a/crates/uplc/src/machine/cost_model.rs +++ b/crates/uplc/src/machine/cost_model.rs @@ -562,7 +562,16 @@ impl BuiltinCosts { .cpu .cost(args[0].to_ex_mem(), args[1].to_ex_mem()), }, - DefaultFunction::DivideInteger => todo!(), + DefaultFunction::DivideInteger => ExBudget { + mem: self + .divide_integer + .mem + .cost(args[0].to_ex_mem(), args[1].to_ex_mem()), + cpu: self + .divide_integer + .cpu + .cost(args[0].to_ex_mem(), args[1].to_ex_mem()), + }, DefaultFunction::QuotientInteger => todo!(), DefaultFunction::RemainderInteger => todo!(), DefaultFunction::ModInteger => todo!(), @@ -606,10 +615,42 @@ impl BuiltinCosts { .cpu .cost(args[0].to_ex_mem(), args[1].to_ex_mem()), }, - DefaultFunction::ConsByteString => todo!(), - DefaultFunction::SliceByteString => todo!(), - DefaultFunction::LengthOfByteString => todo!(), - DefaultFunction::IndexByteString => todo!(), + DefaultFunction::ConsByteString => ExBudget { + mem: self + .cons_byte_string + .mem + .cost(args[0].to_ex_mem(), args[1].to_ex_mem()), + cpu: self + .cons_byte_string + .cpu + .cost(args[0].to_ex_mem(), args[1].to_ex_mem()), + }, + DefaultFunction::SliceByteString => ExBudget { + mem: self.slice_byte_string.mem.cost( + args[0].to_ex_mem(), + args[1].to_ex_mem(), + args[2].to_ex_mem(), + ), + cpu: self.slice_byte_string.cpu.cost( + args[0].to_ex_mem(), + args[1].to_ex_mem(), + args[2].to_ex_mem(), + ), + }, + DefaultFunction::LengthOfByteString => ExBudget { + mem: self.length_of_byte_string.mem.cost(args[0].to_ex_mem()), + cpu: self.length_of_byte_string.cpu.cost(args[0].to_ex_mem()), + }, + DefaultFunction::IndexByteString => ExBudget { + mem: self + .index_byte_string + .mem + .cost(args[0].to_ex_mem(), args[1].to_ex_mem()), + cpu: self + .index_byte_string + .cpu + .cost(args[0].to_ex_mem(), args[1].to_ex_mem()), + }, DefaultFunction::EqualsByteString => ExBudget { mem: self .equals_byte_string @@ -620,8 +661,26 @@ impl BuiltinCosts { .cpu .cost(args[0].to_ex_mem(), args[1].to_ex_mem()), }, - DefaultFunction::LessThanByteString => todo!(), - DefaultFunction::LessThanEqualsByteString => todo!(), + DefaultFunction::LessThanByteString => ExBudget { + mem: self + .less_than_byte_string + .mem + .cost(args[0].to_ex_mem(), args[1].to_ex_mem()), + cpu: self + .less_than_byte_string + .cpu + .cost(args[0].to_ex_mem(), args[1].to_ex_mem()), + }, + DefaultFunction::LessThanEqualsByteString => ExBudget { + mem: self + .less_than_equals_byte_string + .mem + .cost(args[0].to_ex_mem(), args[1].to_ex_mem()), + cpu: self + .less_than_equals_byte_string + .cpu + .cost(args[0].to_ex_mem(), args[1].to_ex_mem()), + }, DefaultFunction::Sha2_256 => ExBudget { mem: self.sha2_256.mem.cost(args[0].to_ex_mem()), cpu: self.sha2_256.cpu.cost(args[0].to_ex_mem()), @@ -763,7 +822,7 @@ impl TwoArguments { TwoArguments::LinearInX(l) => l.slope * x + l.intercept, TwoArguments::LinearInY(l) => l.slope * y + l.intercept, TwoArguments::AddedSizes(s) => s.slope * (x + y) + s.intercept, - TwoArguments::SubtractedSizes(s) => s.slope * s.minimum.min(x - y) + s.intercept, + TwoArguments::SubtractedSizes(s) => s.slope * s.minimum.max(x - y) + s.intercept, TwoArguments::MultipliedSizes(s) => s.slope * (x * y) + s.intercept, TwoArguments::MinSize(s) => s.slope * x.min(y) + s.intercept, TwoArguments::MaxSize(s) => s.slope * x.max(y) + s.intercept, diff --git a/crates/uplc/src/machine/error.rs b/crates/uplc/src/machine/error.rs index 3b8f2745..5840ad47 100644 --- a/crates/uplc/src/machine/error.rs +++ b/crates/uplc/src/machine/error.rs @@ -32,4 +32,8 @@ pub enum Error { MachineNeverReachedDone, #[error("Decoding utf8")] Utf8(#[from] FromUtf8Error), + #[error("Out of Bounds\n\nindex: {}\nbytestring: {}\npossible: 0 - {}", .0, hex::encode(.1), .1.len() - 1)] + ByteStringOutOfBounds(isize, Vec), + #[error("Divide By Zero\n\n{0} / {1}")] + DivideByZero(isize, isize), } diff --git a/crates/uplc/src/machine/runtime.rs b/crates/uplc/src/machine/runtime.rs index a6f1b085..82c41332 100644 --- a/crates/uplc/src/machine/runtime.rs +++ b/crates/uplc/src/machine/runtime.rs @@ -75,7 +75,7 @@ impl DefaultFunction { DefaultFunction::AddInteger => 2, DefaultFunction::SubtractInteger => 2, DefaultFunction::MultiplyInteger => 2, - DefaultFunction::DivideInteger => todo!(), + DefaultFunction::DivideInteger => 2, DefaultFunction::QuotientInteger => todo!(), DefaultFunction::RemainderInteger => todo!(), DefaultFunction::ModInteger => todo!(), @@ -83,13 +83,13 @@ impl DefaultFunction { DefaultFunction::LessThanInteger => 2, DefaultFunction::LessThanEqualsInteger => 2, DefaultFunction::AppendByteString => 2, - DefaultFunction::ConsByteString => todo!(), - DefaultFunction::SliceByteString => todo!(), - DefaultFunction::LengthOfByteString => todo!(), - DefaultFunction::IndexByteString => todo!(), + DefaultFunction::ConsByteString => 2, + DefaultFunction::SliceByteString => 3, + DefaultFunction::LengthOfByteString => 1, + DefaultFunction::IndexByteString => 2, DefaultFunction::EqualsByteString => 2, - DefaultFunction::LessThanByteString => todo!(), - DefaultFunction::LessThanEqualsByteString => todo!(), + DefaultFunction::LessThanByteString => 2, + DefaultFunction::LessThanEqualsByteString => 2, DefaultFunction::Sha2_256 => 1, DefaultFunction::Sha3_256 => 1, DefaultFunction::Blake2b_256 => 1, @@ -134,7 +134,7 @@ impl DefaultFunction { DefaultFunction::AddInteger => 0, DefaultFunction::SubtractInteger => 0, DefaultFunction::MultiplyInteger => 0, - DefaultFunction::DivideInteger => todo!(), + DefaultFunction::DivideInteger => 0, DefaultFunction::QuotientInteger => todo!(), DefaultFunction::RemainderInteger => todo!(), DefaultFunction::ModInteger => todo!(), @@ -142,13 +142,13 @@ impl DefaultFunction { DefaultFunction::LessThanInteger => 0, DefaultFunction::LessThanEqualsInteger => 0, DefaultFunction::AppendByteString => 0, - DefaultFunction::ConsByteString => todo!(), - DefaultFunction::SliceByteString => todo!(), - DefaultFunction::LengthOfByteString => todo!(), - DefaultFunction::IndexByteString => todo!(), + DefaultFunction::ConsByteString => 0, + DefaultFunction::SliceByteString => 0, + DefaultFunction::LengthOfByteString => 0, + DefaultFunction::IndexByteString => 0, DefaultFunction::EqualsByteString => 0, - DefaultFunction::LessThanByteString => todo!(), - DefaultFunction::LessThanEqualsByteString => todo!(), + DefaultFunction::LessThanByteString => 0, + DefaultFunction::LessThanEqualsByteString => 0, DefaultFunction::Sha2_256 => 0, DefaultFunction::Sha3_256 => 0, DefaultFunction::Blake2b_256 => 0, @@ -193,7 +193,7 @@ impl DefaultFunction { DefaultFunction::AddInteger => arg.expect_type(Type::Integer), DefaultFunction::SubtractInteger => arg.expect_type(Type::Integer), DefaultFunction::MultiplyInteger => arg.expect_type(Type::Integer), - DefaultFunction::DivideInteger => todo!(), + DefaultFunction::DivideInteger => arg.expect_type(Type::Integer), DefaultFunction::QuotientInteger => todo!(), DefaultFunction::RemainderInteger => todo!(), DefaultFunction::ModInteger => todo!(), @@ -201,13 +201,31 @@ impl DefaultFunction { DefaultFunction::LessThanInteger => arg.expect_type(Type::Integer), DefaultFunction::LessThanEqualsInteger => arg.expect_type(Type::Integer), DefaultFunction::AppendByteString => arg.expect_type(Type::ByteString), - DefaultFunction::ConsByteString => todo!(), - DefaultFunction::SliceByteString => todo!(), - DefaultFunction::LengthOfByteString => todo!(), - DefaultFunction::IndexByteString => todo!(), + DefaultFunction::ConsByteString => { + if args.is_empty() { + arg.expect_type(Type::Integer) + } else { + arg.expect_type(Type::ByteString) + } + } + DefaultFunction::SliceByteString => { + if args.len() < 2 { + arg.expect_type(Type::Integer) + } else { + arg.expect_type(Type::ByteString) + } + } + DefaultFunction::LengthOfByteString => arg.expect_type(Type::ByteString), + DefaultFunction::IndexByteString => { + if args.is_empty() { + arg.expect_type(Type::ByteString) + } else { + arg.expect_type(Type::Integer) + } + } DefaultFunction::EqualsByteString => arg.expect_type(Type::ByteString), - DefaultFunction::LessThanByteString => todo!(), - DefaultFunction::LessThanEqualsByteString => todo!(), + DefaultFunction::LessThanByteString => arg.expect_type(Type::ByteString), + DefaultFunction::LessThanEqualsByteString => arg.expect_type(Type::ByteString), DefaultFunction::Sha2_256 => arg.expect_type(Type::ByteString), DefaultFunction::Sha3_256 => arg.expect_type(Type::ByteString), DefaultFunction::Blake2b_256 => arg.expect_type(Type::ByteString), @@ -270,100 +288,131 @@ impl DefaultFunction { // the unreachables look ugly, it's the reality of the situation. pub fn call(&self, args: &[Value], logs: &mut Vec) -> Result { match self { - DefaultFunction::AddInteger => { - let args = (&args[0], &args[1]); - - match args { - (Value::Con(Constant::Integer(arg1)), Value::Con(Constant::Integer(arg2))) => { - Ok(Value::Con(Constant::Integer(arg1 + arg2))) - } - _ => unreachable!(), + DefaultFunction::AddInteger => match (&args[0], &args[1]) { + (Value::Con(Constant::Integer(arg1)), Value::Con(Constant::Integer(arg2))) => { + Ok(Value::Con(Constant::Integer(arg1 + arg2))) } - } - DefaultFunction::SubtractInteger => { - let args = (&args[0], &args[1]); - - match args { - (Value::Con(Constant::Integer(arg1)), Value::Con(Constant::Integer(arg2))) => { - Ok(Value::Con(Constant::Integer(arg1 - arg2))) - } - _ => unreachable!(), + _ => unreachable!(), + }, + DefaultFunction::SubtractInteger => match (&args[0], &args[1]) { + (Value::Con(Constant::Integer(arg1)), Value::Con(Constant::Integer(arg2))) => { + Ok(Value::Con(Constant::Integer(arg1 - arg2))) } - } - DefaultFunction::MultiplyInteger => { - let args = (&args[0], &args[1]); - - match args { - (Value::Con(Constant::Integer(arg1)), Value::Con(Constant::Integer(arg2))) => { - Ok(Value::Con(Constant::Integer(arg1 * arg2))) - } - _ => unreachable!(), + _ => unreachable!(), + }, + DefaultFunction::MultiplyInteger => match (&args[0], &args[1]) { + (Value::Con(Constant::Integer(arg1)), Value::Con(Constant::Integer(arg2))) => { + Ok(Value::Con(Constant::Integer(arg1 * arg2))) } - } - DefaultFunction::DivideInteger => todo!(), + _ => unreachable!(), + }, + DefaultFunction::DivideInteger => match (&args[0], &args[1]) { + (Value::Con(Constant::Integer(arg1)), Value::Con(Constant::Integer(arg2))) => { + if *arg2 != 0 { + let ret = (*arg1 as f64) / (*arg2 as f64); + + Ok(Value::Con(Constant::Integer(ret.floor() as isize))) + } else { + Err(Error::DivideByZero(*arg1, *arg2)) + } + } + _ => unreachable!(), + }, DefaultFunction::QuotientInteger => todo!(), DefaultFunction::RemainderInteger => todo!(), DefaultFunction::ModInteger => todo!(), - DefaultFunction::EqualsInteger => { - let args = (&args[0], &args[1]); + DefaultFunction::EqualsInteger => match (&args[0], &args[1]) { + (Value::Con(Constant::Integer(arg1)), Value::Con(Constant::Integer(arg2))) => { + Ok(Value::Con(Constant::Bool(arg1 == arg2))) + } + _ => unreachable!(), + }, + DefaultFunction::LessThanInteger => match (&args[0], &args[1]) { + (Value::Con(Constant::Integer(arg1)), Value::Con(Constant::Integer(arg2))) => { + Ok(Value::Con(Constant::Bool(arg1 < arg2))) + } + _ => unreachable!(), + }, + DefaultFunction::LessThanEqualsInteger => match (&args[0], &args[1]) { + (Value::Con(Constant::Integer(arg1)), Value::Con(Constant::Integer(arg2))) => { + Ok(Value::Con(Constant::Bool(arg1 <= arg2))) + } + _ => unreachable!(), + }, + DefaultFunction::AppendByteString => match (&args[0], &args[1]) { + ( + Value::Con(Constant::ByteString(arg1)), + Value::Con(Constant::ByteString(arg2)), + ) => Ok(Value::Con(Constant::ByteString( + arg1.iter().copied().chain(arg2.iter().copied()).collect(), + ))), + _ => unreachable!(), + }, + DefaultFunction::ConsByteString => match (&args[0], &args[1]) { + (Value::Con(Constant::Integer(arg1)), Value::Con(Constant::ByteString(arg2))) => { + let mut ret = vec![(arg1 % 256) as u8]; + ret.extend(arg2.clone()); - match args { - (Value::Con(Constant::Integer(arg1)), Value::Con(Constant::Integer(arg2))) => { - Ok(Value::Con(Constant::Bool(arg1 == arg2))) + Ok(Value::Con(Constant::ByteString(ret))) + } + _ => unreachable!(), + }, + DefaultFunction::SliceByteString => match (&args[0], &args[1], &args[2]) { + ( + Value::Con(Constant::Integer(arg1)), + Value::Con(Constant::Integer(arg2)), + Value::Con(Constant::ByteString(arg3)), + ) => { + let skip = if 0 > *arg1 { 0 } else { *arg1 as usize }; + let take = if 0 > *arg2 { 0 } else { *arg2 as usize }; + + let ret: Vec = arg3.iter().skip(skip).take(take).cloned().collect(); + + Ok(Value::Con(Constant::ByteString(ret))) + } + _ => unreachable!(), + }, + DefaultFunction::LengthOfByteString => match &args[0] { + Value::Con(Constant::ByteString(arg1)) => { + Ok(Value::Con(Constant::Integer(arg1.len() as isize))) + } + _ => unreachable!(), + }, + DefaultFunction::IndexByteString => match (&args[0], &args[1]) { + (Value::Con(Constant::ByteString(arg1)), Value::Con(Constant::Integer(arg2))) => { + let index = *arg2 as usize; + + if 0 <= *arg2 && index < arg1.len() { + let ret = arg1[index] as isize; + + Ok(Value::Con(Constant::Integer(ret))) + } else { + Err(Error::ByteStringOutOfBounds(*arg2, arg1.to_vec())) } - _ => unreachable!(), } - } - DefaultFunction::LessThanInteger => { - let args = (&args[0], &args[1]); - - match args { - (Value::Con(Constant::Integer(arg1)), Value::Con(Constant::Integer(arg2))) => { - Ok(Value::Con(Constant::Bool(arg1 < arg2))) - } - _ => unreachable!(), - } - } - DefaultFunction::LessThanEqualsInteger => { - let args = (&args[0], &args[1]); - - match args { - (Value::Con(Constant::Integer(arg1)), Value::Con(Constant::Integer(arg2))) => { - Ok(Value::Con(Constant::Bool(arg1 <= arg2))) - } - _ => unreachable!(), - } - } - DefaultFunction::AppendByteString => { - let args = (&args[0], &args[1]); - - match args { - ( - Value::Con(Constant::ByteString(arg1)), - Value::Con(Constant::ByteString(arg2)), - ) => Ok(Value::Con(Constant::ByteString( - arg1.iter().copied().chain(arg2.iter().copied()).collect(), - ))), - _ => unreachable!(), - } - } - DefaultFunction::ConsByteString => todo!(), - DefaultFunction::SliceByteString => todo!(), - DefaultFunction::LengthOfByteString => todo!(), - DefaultFunction::IndexByteString => todo!(), - DefaultFunction::EqualsByteString => { - let args = (&args[0], &args[1]); - - match args { - ( - Value::Con(Constant::ByteString(arg1)), - Value::Con(Constant::ByteString(arg2)), - ) => Ok(Value::Con(Constant::Bool(arg1 == arg2))), - _ => unreachable!(), - } - } - DefaultFunction::LessThanByteString => todo!(), - DefaultFunction::LessThanEqualsByteString => todo!(), + _ => unreachable!(), + }, + DefaultFunction::EqualsByteString => match (&args[0], &args[1]) { + ( + Value::Con(Constant::ByteString(arg1)), + Value::Con(Constant::ByteString(arg2)), + ) => Ok(Value::Con(Constant::Bool(arg1 == arg2))), + _ => unreachable!(), + }, + DefaultFunction::LessThanByteString => match (&args[0], &args[1]) { + ( + Value::Con(Constant::ByteString(arg1)), + Value::Con(Constant::ByteString(arg2)), + ) => Ok(Value::Con(Constant::Bool(arg1 < arg2))), + _ => unreachable!(), + }, + DefaultFunction::LessThanEqualsByteString => match (&args[0], &args[1]) { + ( + Value::Con(Constant::ByteString(arg1)), + Value::Con(Constant::ByteString(arg2)), + ) => Ok(Value::Con(Constant::Bool(arg1 <= arg2))), + _ => unreachable!(), + }, DefaultFunction::Sha2_256 => match &args[0] { Value::Con(Constant::ByteString(arg1)) => { use cryptoxide::{digest::Digest, sha2::Sha256}; @@ -413,26 +462,18 @@ impl DefaultFunction { DefaultFunction::VerifySignature => todo!(), DefaultFunction::VerifyEcdsaSecp256k1Signature => todo!(), DefaultFunction::VerifySchnorrSecp256k1Signature => todo!(), - DefaultFunction::AppendString => { - let args = (&args[0], &args[1]); - - match args { - (Value::Con(Constant::String(arg1)), Value::Con(Constant::String(arg2))) => { - Ok(Value::Con(Constant::String(format!("{}{}", arg1, arg2)))) - } - _ => unreachable!(), + DefaultFunction::AppendString => match (&args[0], &args[1]) { + (Value::Con(Constant::String(arg1)), Value::Con(Constant::String(arg2))) => { + Ok(Value::Con(Constant::String(format!("{}{}", arg1, arg2)))) } - } - DefaultFunction::EqualsString => { - let args = (&args[0], &args[1]); - - match args { - (Value::Con(Constant::String(arg1)), Value::Con(Constant::String(arg2))) => { - Ok(Value::Con(Constant::Bool(arg1 == arg2))) - } - _ => unreachable!(), + _ => unreachable!(), + }, + DefaultFunction::EqualsString => match (&args[0], &args[1]) { + (Value::Con(Constant::String(arg1)), Value::Con(Constant::String(arg2))) => { + Ok(Value::Con(Constant::Bool(arg1 == arg2))) } - } + _ => unreachable!(), + }, DefaultFunction::EncodeUtf8 => match &args[0] { Value::Con(Constant::String(arg1)) => { let bytes = arg1.as_bytes().to_vec();