diff --git a/add_integers.uplc b/add_integers.uplc index f7b7ffab..bb78578c 100644 --- a/add_integers.uplc +++ b/add_integers.uplc @@ -1,5 +1,5 @@ (program 1.0.0 - [ (builtin divideInteger) (con integer 5) (con integer 2) ] + [ (builtin remainderInteger) (con integer 5) (con integer 2)] ) diff --git a/crates/uplc/src/machine.rs b/crates/uplc/src/machine.rs index 48d59807..3521b12d 100644 --- a/crates/uplc/src/machine.rs +++ b/crates/uplc/src/machine.rs @@ -450,7 +450,9 @@ impl Value { Constant::String(s) => s.chars().count() as i64, Constant::Unit => 1, Constant::Bool(_) => 1, - Constant::ProtoList(_, _) => todo!(), + Constant::ProtoList(_, items) => items.iter().fold(0, |acc, constant| { + acc + Value::Con(constant.clone()).to_ex_mem() + }), Constant::ProtoPair(_, _, _, _) => todo!(), Constant::Data(_) => todo!(), }, @@ -461,17 +463,49 @@ impl Value { } pub fn expect_type(&self, r#type: Type) -> Result<(), Error> { - match self { - Value::Con(constant) => { - let constant_type = Type::from(constant); + let constant: Constant = self.clone().try_into()?; - if constant_type == r#type { - Ok(()) - } else { - Err(Error::TypeMismatch(r#type, constant_type)) - } - } - rest => Err(Error::NotAConstant(rest.clone())), + let constant_type = Type::from(&constant); + + if constant_type == r#type { + Ok(()) + } else { + Err(Error::TypeMismatch(r#type, constant_type)) + } + } + + pub fn expect_list(&self) -> Result<(), Error> { + let constant: Constant = self.clone().try_into()?; + + let constant_type = Type::from(&constant); + + if matches!(constant_type, Type::List(_)) { + Ok(()) + } else { + Err(Error::ListTypeMismatch(constant_type)) + } + } +} + +impl TryFrom for Type { + type Error = Error; + + fn try_from(value: Value) -> Result { + let constant: Constant = value.try_into()?; + + let constant_type = Type::from(&constant); + + Ok(constant_type) + } +} + +impl TryFrom for Constant { + type Error = Error; + + fn try_from(value: Value) -> Result { + match value { + Value::Con(constant) => Ok(constant), + rest => Err(Error::NotAConstant(rest)), } } } diff --git a/crates/uplc/src/machine/cost_model.rs b/crates/uplc/src/machine/cost_model.rs index 9211cfff..143a7f4c 100644 --- a/crates/uplc/src/machine/cost_model.rs +++ b/crates/uplc/src/machine/cost_model.rs @@ -572,9 +572,36 @@ impl BuiltinCosts { .cpu .cost(args[0].to_ex_mem(), args[1].to_ex_mem()), }, - DefaultFunction::QuotientInteger => todo!(), - DefaultFunction::RemainderInteger => todo!(), - DefaultFunction::ModInteger => todo!(), + DefaultFunction::QuotientInteger => ExBudget { + mem: self + .quotient_integer + .mem + .cost(args[0].to_ex_mem(), args[1].to_ex_mem()), + cpu: self + .quotient_integer + .cpu + .cost(args[0].to_ex_mem(), args[1].to_ex_mem()), + }, + DefaultFunction::RemainderInteger => ExBudget { + mem: self + .remainder_integer + .mem + .cost(args[0].to_ex_mem(), args[1].to_ex_mem()), + cpu: self + .remainder_integer + .cpu + .cost(args[0].to_ex_mem(), args[1].to_ex_mem()), + }, + DefaultFunction::ModInteger => ExBudget { + mem: self + .mod_integer + .mem + .cost(args[0].to_ex_mem(), args[1].to_ex_mem()), + cpu: self + .mod_integer + .cpu + .cost(args[0].to_ex_mem(), args[1].to_ex_mem()), + }, DefaultFunction::EqualsInteger => ExBudget { mem: self .equals_integer @@ -770,10 +797,28 @@ impl BuiltinCosts { DefaultFunction::FstPair => todo!(), DefaultFunction::SndPair => todo!(), DefaultFunction::ChooseList => todo!(), - DefaultFunction::MkCons => todo!(), - DefaultFunction::HeadList => todo!(), - DefaultFunction::TailList => todo!(), - DefaultFunction::NullList => todo!(), + DefaultFunction::MkCons => ExBudget { + mem: self + .mk_cons + .mem + .cost(args[0].to_ex_mem(), args[1].to_ex_mem()), + cpu: self + .mk_cons + .cpu + .cost(args[0].to_ex_mem(), args[1].to_ex_mem()), + }, + DefaultFunction::HeadList => ExBudget { + mem: self.head_list.mem.cost(args[0].to_ex_mem()), + cpu: self.head_list.cpu.cost(args[0].to_ex_mem()), + }, + DefaultFunction::TailList => ExBudget { + mem: self.tail_list.mem.cost(args[0].to_ex_mem()), + cpu: self.tail_list.cpu.cost(args[0].to_ex_mem()), + }, + DefaultFunction::NullList => ExBudget { + mem: self.null_list.mem.cost(args[0].to_ex_mem()), + cpu: self.null_list.cpu.cost(args[0].to_ex_mem()), + }, DefaultFunction::ChooseData => todo!(), DefaultFunction::ConstrData => todo!(), DefaultFunction::MapData => todo!(), diff --git a/crates/uplc/src/machine/error.rs b/crates/uplc/src/machine/error.rs index 5840ad47..cd789dcb 100644 --- a/crates/uplc/src/machine/error.rs +++ b/crates/uplc/src/machine/error.rs @@ -22,6 +22,10 @@ pub enum Error { NonFunctionalApplication(Value), #[error("Type mismatch expected '{0}' got '{1}'")] TypeMismatch(Type, Type), + #[error("Type mismatch expected '(list a)' got '{0}'")] + ListTypeMismatch(Type), + #[error("Empty List:\n\n{0:#?}")] + EmptyList(Value), #[error("A builtin received a term argument when something else was expected:\n\n{0}\n\nYou probably forgot to wrap the builtin with a force.")] UnexpectedBuiltinTermArgument(Term), #[error("A builtin expected a term argument, but something else was received:\n\n{0}\n\nYou probably have an extra force wrapped around a builtin")] @@ -36,4 +40,8 @@ pub enum Error { ByteStringOutOfBounds(isize, Vec), #[error("Divide By Zero\n\n{0} / {1}")] DivideByZero(isize, isize), + #[error("Ed25519S PublicKey should be 32 bytes but it was {0}")] + UnexpectedEd25519PublicKeyLength(usize), + #[error("Ed25519S Signature should be 64 bytes but it was {0}")] + UnexpectedEd25519SignatureLength(usize), } diff --git a/crates/uplc/src/machine/runtime.rs b/crates/uplc/src/machine/runtime.rs index 4dedbb53..bdf819c8 100644 --- a/crates/uplc/src/machine/runtime.rs +++ b/crates/uplc/src/machine/runtime.rs @@ -76,9 +76,9 @@ impl DefaultFunction { DefaultFunction::SubtractInteger => 2, DefaultFunction::MultiplyInteger => 2, DefaultFunction::DivideInteger => 2, - DefaultFunction::QuotientInteger => todo!(), - DefaultFunction::RemainderInteger => todo!(), - DefaultFunction::ModInteger => todo!(), + DefaultFunction::QuotientInteger => 2, + DefaultFunction::RemainderInteger => 2, + DefaultFunction::ModInteger => 2, DefaultFunction::EqualsInteger => 2, DefaultFunction::LessThanInteger => 2, DefaultFunction::LessThanEqualsInteger => 2, @@ -106,10 +106,10 @@ impl DefaultFunction { DefaultFunction::FstPair => todo!(), DefaultFunction::SndPair => todo!(), DefaultFunction::ChooseList => todo!(), - DefaultFunction::MkCons => todo!(), - DefaultFunction::HeadList => todo!(), - DefaultFunction::TailList => todo!(), - DefaultFunction::NullList => todo!(), + DefaultFunction::MkCons => 2, + DefaultFunction::HeadList => 1, + DefaultFunction::TailList => 1, + DefaultFunction::NullList => 1, DefaultFunction::ChooseData => todo!(), DefaultFunction::ConstrData => todo!(), DefaultFunction::MapData => todo!(), @@ -135,9 +135,9 @@ impl DefaultFunction { DefaultFunction::SubtractInteger => 0, DefaultFunction::MultiplyInteger => 0, DefaultFunction::DivideInteger => 0, - DefaultFunction::QuotientInteger => todo!(), - DefaultFunction::RemainderInteger => todo!(), - DefaultFunction::ModInteger => todo!(), + DefaultFunction::QuotientInteger => 0, + DefaultFunction::RemainderInteger => 0, + DefaultFunction::ModInteger => 0, DefaultFunction::EqualsInteger => 0, DefaultFunction::LessThanInteger => 0, DefaultFunction::LessThanEqualsInteger => 0, @@ -165,10 +165,10 @@ impl DefaultFunction { DefaultFunction::FstPair => todo!(), DefaultFunction::SndPair => todo!(), DefaultFunction::ChooseList => todo!(), - DefaultFunction::MkCons => todo!(), - DefaultFunction::HeadList => todo!(), - DefaultFunction::TailList => todo!(), - DefaultFunction::NullList => todo!(), + DefaultFunction::MkCons => 1, + DefaultFunction::HeadList => 1, + DefaultFunction::TailList => 1, + DefaultFunction::NullList => 1, DefaultFunction::ChooseData => todo!(), DefaultFunction::ConstrData => todo!(), DefaultFunction::MapData => todo!(), @@ -194,9 +194,9 @@ impl DefaultFunction { DefaultFunction::SubtractInteger => arg.expect_type(Type::Integer), DefaultFunction::MultiplyInteger => arg.expect_type(Type::Integer), DefaultFunction::DivideInteger => arg.expect_type(Type::Integer), - DefaultFunction::QuotientInteger => todo!(), - DefaultFunction::RemainderInteger => todo!(), - DefaultFunction::ModInteger => todo!(), + DefaultFunction::QuotientInteger => arg.expect_type(Type::Integer), + DefaultFunction::RemainderInteger => arg.expect_type(Type::Integer), + DefaultFunction::ModInteger => arg.expect_type(Type::Integer), DefaultFunction::EqualsInteger => arg.expect_type(Type::Integer), DefaultFunction::LessThanInteger => arg.expect_type(Type::Integer), DefaultFunction::LessThanEqualsInteger => arg.expect_type(Type::Integer), @@ -260,10 +260,18 @@ impl DefaultFunction { DefaultFunction::FstPair => todo!(), DefaultFunction::SndPair => todo!(), DefaultFunction::ChooseList => todo!(), - DefaultFunction::MkCons => todo!(), - DefaultFunction::HeadList => todo!(), - DefaultFunction::TailList => todo!(), - DefaultFunction::NullList => todo!(), + DefaultFunction::MkCons => { + if args.is_empty() { + Ok(()) + } else { + let first = args[0].clone(); + + arg.expect_type(Type::List(Box::new(first.try_into()?))) + } + } + DefaultFunction::HeadList => arg.expect_list(), + DefaultFunction::TailList => arg.expect_list(), + DefaultFunction::NullList => arg.expect_list(), DefaultFunction::ChooseData => todo!(), DefaultFunction::ConstrData => todo!(), DefaultFunction::MapData => todo!(), @@ -318,9 +326,44 @@ impl DefaultFunction { } _ => unreachable!(), }, - DefaultFunction::QuotientInteger => todo!(), - DefaultFunction::RemainderInteger => todo!(), - DefaultFunction::ModInteger => todo!(), + DefaultFunction::QuotientInteger => 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); + + let ret = if ret < 0. { ret.ceil() } else { ret.floor() }; + + Ok(Value::Con(Constant::Integer(ret as isize))) + } else { + Err(Error::DivideByZero(*arg1, *arg2)) + } + } + _ => unreachable!(), + }, + DefaultFunction::RemainderInteger => match (&args[0], &args[1]) { + (Value::Con(Constant::Integer(arg1)), Value::Con(Constant::Integer(arg2))) => { + if *arg2 != 0 { + let ret = arg1 % arg2; + + Ok(Value::Con(Constant::Integer(ret))) + } else { + Err(Error::DivideByZero(*arg1, *arg2)) + } + } + _ => unreachable!(), + }, + DefaultFunction::ModInteger => match (&args[0], &args[1]) { + (Value::Con(Constant::Integer(arg1)), Value::Con(Constant::Integer(arg2))) => { + if *arg2 != 0 { + let ret = arg1 % arg2; + + Ok(Value::Con(Constant::Integer(ret.abs()))) + } else { + Err(Error::DivideByZero(*arg1, *arg2)) + } + } + _ => unreachable!(), + }, DefaultFunction::EqualsInteger => match (&args[0], &args[1]) { (Value::Con(Constant::Integer(arg1)), Value::Con(Constant::Integer(arg2))) => { Ok(Value::Con(Constant::Bool(arg1 == arg2))) @@ -461,11 +504,25 @@ impl DefaultFunction { }, DefaultFunction::VerifyEd25519Signature => match (&args[0], &args[1], &args[2]) { ( - Value::Con(Constant::ByteString(_arg1)), - Value::Con(Constant::ByteString(_arg2)), - Value::Con(Constant::ByteString(_arg3)), + Value::Con(Constant::ByteString(public_key)), + Value::Con(Constant::ByteString(message)), + Value::Con(Constant::ByteString(signature)), ) => { - todo!() + use cryptoxide::ed25519; + + let public_key: [u8; 32] = public_key + .clone() + .try_into() + .map_err(|e: Vec| Error::UnexpectedEd25519PublicKeyLength(e.len()))?; + + let signature: [u8; 64] = signature + .clone() + .try_into() + .map_err(|e: Vec| Error::UnexpectedEd25519SignatureLength(e.len()))?; + + let valid = ed25519::verify(message, &public_key, &signature); + + Ok(Value::Con(Constant::Bool(valid))) } _ => unreachable!(), }, @@ -524,10 +581,44 @@ impl DefaultFunction { DefaultFunction::FstPair => todo!(), DefaultFunction::SndPair => todo!(), DefaultFunction::ChooseList => todo!(), - DefaultFunction::MkCons => todo!(), - DefaultFunction::HeadList => todo!(), - DefaultFunction::TailList => todo!(), - DefaultFunction::NullList => todo!(), + DefaultFunction::MkCons => match (&args[0], &args[1]) { + (Value::Con(item), Value::Con(Constant::ProtoList(r#type, list))) => { + let mut ret = vec![item.clone()]; + ret.extend(list.clone()); + + Ok(Value::Con(Constant::ProtoList(r#type.clone(), ret))) + } + _ => unreachable!(), + }, + DefaultFunction::HeadList => match &args[0] { + c @ Value::Con(Constant::ProtoList(_, list)) => { + if list.is_empty() { + Err(Error::EmptyList(c.clone())) + } else { + Ok(Value::Con(list[0].clone())) + } + } + _ => unreachable!(), + }, + DefaultFunction::TailList => match &args[0] { + c @ Value::Con(Constant::ProtoList(r#type, list)) => { + if list.is_empty() { + Err(Error::EmptyList(c.clone())) + } else { + Ok(Value::Con(Constant::ProtoList( + r#type.clone(), + list[1..].to_vec(), + ))) + } + } + _ => unreachable!(), + }, + DefaultFunction::NullList => match &args[0] { + Value::Con(Constant::ProtoList(_, list)) => { + Ok(Value::Con(Constant::Bool(list.is_empty()))) + } + _ => unreachable!(), + }, DefaultFunction::ChooseData => todo!(), DefaultFunction::ConstrData => todo!(), DefaultFunction::MapData => todo!(), diff --git a/crates/uplc/src/pretty.rs b/crates/uplc/src/pretty.rs index 94461883..06b18fa2 100644 --- a/crates/uplc/src/pretty.rs +++ b/crates/uplc/src/pretty.rs @@ -1,7 +1,7 @@ use pretty::RcDoc; use crate::{ - ast::{Constant, Program, Term}, + ast::{Constant, Program, Term, Type}, flat::Binder, }; @@ -170,9 +170,65 @@ impl Constant { Constant::Bool(b) => RcDoc::text("bool") .append(RcDoc::line()) .append(RcDoc::text(if *b { "True" } else { "False" })), - Constant::ProtoList(_, _) => todo!(), + Constant::ProtoList(r#type, items) => RcDoc::text("(") + .append( + RcDoc::text("list") + .append(RcDoc::line()) + .append(r#type.to_doc()), + ) + .append(RcDoc::line_()) + .append(RcDoc::text(")")) + .append(RcDoc::line()) + .append(RcDoc::text("[")) + .append(RcDoc::intersperse( + items.iter().map(|c| c.to_doc_list()), + RcDoc::text(","), + )) + .append(RcDoc::text("]")), + Constant::ProtoPair(_, _, _, _) => todo!(), + Constant::Data(_) => todo!(), + } + } + + fn to_doc_list(&self) -> RcDoc<()> { + match self { + Constant::Integer(i) => RcDoc::as_string(i), + Constant::ByteString(bs) => RcDoc::text("#").append(RcDoc::text(hex::encode(bs))), + Constant::String(s) => RcDoc::text("\"") + .append(RcDoc::text(s)) + .append(RcDoc::text("\"")), + Constant::Unit => RcDoc::text("()"), + Constant::Bool(b) => RcDoc::text(if *b { "True" } else { "False" }), + Constant::ProtoList(_, items) => RcDoc::text("[") + .append(RcDoc::intersperse( + items.iter().map(|c| c.to_doc_list()), + RcDoc::text(","), + )) + .append(RcDoc::text("]")), Constant::ProtoPair(_, _, _, _) => todo!(), Constant::Data(_) => todo!(), } } } + +impl Type { + fn to_doc(&self) -> RcDoc<()> { + match self { + Type::Bool => RcDoc::text("bool"), + Type::Integer => RcDoc::text("integer"), + Type::String => RcDoc::text("string"), + Type::ByteString => RcDoc::text("bytestring"), + Type::Unit => RcDoc::text("unit"), + Type::List(r#type) => RcDoc::text("(") + .append( + RcDoc::text("list") + .append(RcDoc::line()) + .append(r#type.to_doc()), + ) + .append(RcDoc::line_()) + .append(RcDoc::text(")")), + Type::Pair(_, _) => todo!(), + Type::Data => todo!(), + } + } +}