From 9033b44044e9fb3b4cbe1006a4f8d51996414030 Mon Sep 17 00:00:00 2001 From: KtorZ Date: Wed, 5 Apr 2023 15:32:39 +0200 Subject: [PATCH] Implement quick builder on PlutusData. In the same spirit of the existing Term builder; I also added a `data` method to lift a `PlutusData` into a `Term` and generalized a bit the builder to only require a `Term` when necessary and remain generic otherwise. The `PlutusData` builder could potentially be upstreamed to pallas diretly. --- crates/uplc/src/ast.rs | 93 ++++++++++++++++++++++------- crates/uplc/src/builder.rs | 117 ++++++++++++++++++++----------------- 2 files changed, 135 insertions(+), 75 deletions(-) diff --git a/crates/uplc/src/ast.rs b/crates/uplc/src/ast.rs index 884527a6..61d5e52c 100644 --- a/crates/uplc/src/ast.rs +++ b/crates/uplc/src/ast.rs @@ -1,23 +1,3 @@ -use std::{ - fmt::{self, Display}, - hash::{self, Hash}, - rc::Rc, -}; - -use num_bigint::BigInt; -use serde::{ - self, - de::{self, Deserialize, Deserializer, MapAccess, Visitor}, - ser::{Serialize, SerializeStruct, Serializer}, -}; - -use pallas_addresses::{Network, ShelleyAddress, ShelleyDelegationPart, ShelleyPaymentPart}; -use pallas_primitives::{ - alonzo::PlutusData, - babbage::{self as cardano, Language}, -}; -use pallas_traverse::ComputeHash; - use crate::{ builtins::DefaultFunction, debruijn::{self, Converter}, @@ -28,6 +8,24 @@ use crate::{ Machine, }, }; +use num_bigint::BigInt; +use num_traits::ToPrimitive; +use pallas_addresses::{Network, ShelleyAddress, ShelleyDelegationPart, ShelleyPaymentPart}; +use pallas_primitives::{ + alonzo::{self as pallas, Constr, PlutusData}, + babbage::{self as cardano, Language}, +}; +use pallas_traverse::ComputeHash; +use serde::{ + self, + de::{self, Deserialize, Deserializer, MapAccess, Visitor}, + ser::{Serialize, SerializeStruct, Serializer}, +}; +use std::{ + fmt::{self, Display}, + hash::{self, Hash}, + rc::Rc, +}; /// This represents a program in Untyped Plutus Core. /// A program contains a version tuple and a term. @@ -239,6 +237,61 @@ pub enum Constant { Data(PlutusData), } +pub struct Data {} + +// TODO: See about moving these builders upstream to Pallas? +impl Data { + pub fn integer(i: BigInt) -> PlutusData { + match i.to_i64() { + Some(i) => PlutusData::BigInt(pallas::BigInt::Int(i.into())), + None => { + let (sign, bytes) = i.to_bytes_be(); + match sign { + num_bigint::Sign::Minus => { + PlutusData::BigInt(pallas::BigInt::BigNInt(bytes.into())) + } + _ => PlutusData::BigInt(pallas::BigInt::BigUInt(bytes.into())), + } + } + } + } + + pub fn bytestring(bytes: Vec) -> PlutusData { + PlutusData::BoundedBytes(bytes.into()) + } + + pub fn map(kvs: Vec<(PlutusData, PlutusData)>) -> PlutusData { + PlutusData::Map(kvs.into()) + } + + pub fn list(xs: Vec) -> PlutusData { + PlutusData::Array(xs) + } + + pub fn constr(ix: u64, fields: Vec) -> PlutusData { + // NOTE: see https://github.com/input-output-hk/plutus/blob/9538fc9829426b2ecb0628d352e2d7af96ec8204/plutus-core/plutus-core/src/PlutusCore/Data.hs#L139-L155 + if ix < 7 { + PlutusData::Constr(Constr { + tag: 121 + ix, + any_constructor: None, + fields, + }) + } else if ix < 128 { + PlutusData::Constr(Constr { + tag: 1280 + ix - 7, + any_constructor: None, + fields, + }) + } else { + PlutusData::Constr(Constr { + tag: 102, + any_constructor: Some(ix), + fields, + }) + } + } +} + #[derive(Debug, Clone, PartialEq)] pub enum Type { Bool, diff --git a/crates/uplc/src/builder.rs b/crates/uplc/src/builder.rs index d30d8c4c..91d2a763 100644 --- a/crates/uplc/src/builder.rs +++ b/crates/uplc/src/builder.rs @@ -2,13 +2,14 @@ use crate::{ ast::{Constant, Name, Term, Type}, builtins::DefaultFunction, }; +use pallas_primitives::alonzo::PlutusData; pub const CONSTR_FIELDS_EXPOSER: &str = "__constr_fields_exposer"; pub const CONSTR_INDEX_EXPOSER: &str = "__constr_index_exposer"; pub const CONSTR_GET_FIELD: &str = "__constr_get_field"; pub const EXPECT_ON_LIST: &str = "__expect_on_list"; -impl Term { +impl Term { pub fn apply(self, arg: Self) -> Self { Term::Apply { function: self.into(), @@ -16,13 +17,6 @@ impl Term { } } - pub fn lambda(self, parameter_name: impl ToString) -> Self { - Term::Lambda { - parameter_name: Name::text(parameter_name).into(), - body: self.into(), - } - } - pub fn force(self) -> Self { Term::Force(self.into()) } @@ -31,10 +25,6 @@ impl Term { Term::Delay(self.into()) } - pub fn var(name: impl ToString) -> Self { - Term::Var(Name::text(name).into()) - } - pub fn integer(i: num_bigint::BigInt) -> Self { Term::Constant(Constant::Integer(i).into()) } @@ -55,6 +45,10 @@ impl Term { Term::Constant(Constant::Unit.into()) } + pub fn data(d: PlutusData) -> Self { + Term::Constant(Constant::Data(d).into()) + } + pub fn empty_list() -> Self { Term::Constant(Constant::ProtoList(Type::Data, vec![]).into()) } @@ -204,7 +198,7 @@ impl Term { .force() } - pub fn trace(self, msg_term: Term) -> Self { + pub fn trace(self, msg_term: Self) -> Self { Term::Builtin(DefaultFunction::Trace) .force() .apply(msg_term) @@ -212,41 +206,34 @@ impl Term { .force() } - pub fn assert_on_list(self) -> Term { - self.lambda(EXPECT_ON_LIST.to_string()) - .apply( - Term::var(EXPECT_ON_LIST.to_string()).apply(Term::var(EXPECT_ON_LIST.to_string())), - ) - .lambda(EXPECT_ON_LIST.to_string()) - .apply( - Term::var("__list_to_check".to_string()) - .delayed_choose_list( - Term::unit(), - Term::var("__check_with".to_string()) - .apply( - Term::head_list().apply(Term::var("__list_to_check".to_string())), - ) - .choose_unit( - Term::var(EXPECT_ON_LIST.to_string()) - .apply(Term::var(EXPECT_ON_LIST.to_string())) - .apply( - Term::tail_list() - .apply(Term::var("__list_to_check".to_string())), - ) - .apply(Term::var("__check_with".to_string())), - ), - ) - .lambda("__check_with".to_string()) - .lambda("__list_to_check".to_string()) - .lambda(EXPECT_ON_LIST), - ) - } - - pub fn final_wrapper(self: Term) -> Term { + pub fn final_wrapper(self) -> Self { self.delayed_if_else(Term::unit(), Term::Error) } - pub fn constr_fields_exposer(self: Term) -> Term { + pub fn repeat_tail_list(self, repeat: usize) -> Self { + let mut term = self; + + for _ in 0..repeat { + term = Term::tail_list().apply(term); + } + + term + } +} + +impl Term { + pub fn lambda(self, parameter_name: impl ToString) -> Self { + Term::Lambda { + parameter_name: Name::text(parameter_name).into(), + body: self.into(), + } + } + + pub fn var(name: impl ToString) -> Self { + Term::Var(Name::text(name).into()) + } + + pub fn constr_fields_exposer(self) -> Self { self.lambda(CONSTR_FIELDS_EXPOSER.to_string()).apply( Term::snd_pair() .apply(Term::unconstr_data().apply(Term::var("__constr_var".to_string()))) @@ -254,7 +241,7 @@ impl Term { ) } - pub fn constr_index_exposer(self: Term) -> Term { + pub fn constr_index_exposer(self) -> Self { self.lambda(CONSTR_INDEX_EXPOSER.to_string()).apply( Term::fst_pair() .apply(Term::unconstr_data().apply(Term::var("__constr_var".to_string()))) @@ -262,7 +249,7 @@ impl Term { ) } - pub fn constr_get_field(self: Term) -> Term { + pub fn constr_get_field(self) -> Self { self.lambda(CONSTR_GET_FIELD.to_string()) .apply( Term::var(CONSTR_GET_FIELD.to_string()) @@ -298,13 +285,33 @@ impl Term { ) } - pub fn repeat_tail_list(self: Term, repeat: usize) -> Term { - let mut term = self; - - for _ in 0..repeat { - term = Term::tail_list().apply(term); - } - - term + pub fn assert_on_list(self) -> Self { + self.lambda(EXPECT_ON_LIST.to_string()) + .apply( + Term::var(EXPECT_ON_LIST.to_string()).apply(Term::var(EXPECT_ON_LIST.to_string())), + ) + .lambda(EXPECT_ON_LIST.to_string()) + .apply( + Term::var("__list_to_check".to_string()) + .delayed_choose_list( + Term::unit(), + Term::var("__check_with".to_string()) + .apply( + Term::head_list().apply(Term::var("__list_to_check".to_string())), + ) + .choose_unit( + Term::var(EXPECT_ON_LIST.to_string()) + .apply(Term::var(EXPECT_ON_LIST.to_string())) + .apply( + Term::tail_list() + .apply(Term::var("__list_to_check".to_string())), + ) + .apply(Term::var("__check_with".to_string())), + ), + ) + .lambda("__check_with".to_string()) + .lambda("__list_to_check".to_string()) + .lambda(EXPECT_ON_LIST), + ) } }