Skip to content
15 changes: 1 addition & 14 deletions crates/tx3-cardano/src/compile/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,20 +76,7 @@ fn compile_struct(ir: &ir::StructExpr) -> Result<primitives::PlutusData, Error>
}

fn compile_data_expr(ir: &ir::Expression) -> Result<primitives::PlutusData, Error> {
match ir {
ir::Expression::Bytes(x) => Ok(x.as_data()),
ir::Expression::Number(x) => Ok(x.as_data()),
ir::Expression::Bool(x) => Ok(x.as_data()),
ir::Expression::String(x) => Ok(x.as_str().as_data()),
ir::Expression::Struct(x) => compile_struct(x),
ir::Expression::Map(x) => x.try_as_data(),
ir::Expression::Address(x) => Ok(x.as_data()),
ir::Expression::List(x) => x.try_as_data(),
_ => Err(Error::CoerceError(
format!("{ir:?}"),
"DataExpr".to_string(),
)),
}
ir.try_as_data()
}

fn compile_native_asset_for_output(
Expand Down
67 changes: 64 additions & 3 deletions crates/tx3-cardano/src/compile/plutus_data.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
pub use pallas::codec::utils::Int;
use pallas::codec::utils::KeyValuePairs;
use pallas::{codec::utils::KeyValuePairs, ledger::addresses};
pub use pallas::ledger::primitives::{BigInt, BoundedBytes, Constr, MaybeIndefArray, PlutusData};
use tx3_lang::ir;
use std::collections::HashMap;

pub trait IntoData {
fn as_data(&self) -> PlutusData;
Expand Down Expand Up @@ -148,10 +149,70 @@ impl TryIntoData for ir::Expression {
ir::Expression::Hash(x) => Ok(x.as_data()),
ir::Expression::List(x) => x.try_as_data(),
ir::Expression::Map(x) => x.try_as_data(),
x => Err(super::Error::CoerceError(
format!("{x:?}"),
ir::Expression::AdHocDirective(x) => match x.name.as_str() {
"cardano_address_payment_part" => address_payment_part(&x.data),
"cardano_address_staking_part" => address_staking_part(&x.data),
_ => Ok(().as_data()),
},
Comment on lines 152 to 156
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

relying on byte positions is too risky. Use pallas::ledger::addresses::Address to parse the bytes and return the data fragment we need.

_ => Err(super::Error::CoerceError(
format!("{self:?}"),
"PlutusData".to_string(),
)),
}
}
}

fn extract_address(data: &HashMap<String, ir::Expression>) -> Result<addresses::Address, super::Error> {
data.get("address")
.ok_or_else(|| super::Error::CoerceError(
"Address field not found in data".to_string(),
"Address".to_string(),
))
.and_then(|expr| match expr {
ir::Expression::Address(bytes) => {
addresses::Address::from_bytes(bytes)
.map_err(|_| super::Error::CoerceError(
"Invalid address bytes".to_string(),
"Address".to_string(),
))
}
_ => Err(super::Error::CoerceError(
format!("Expected Address expression, found {:?}", expr),
"Address".to_string(),
)),
})
}

fn address_payment_part(data: &HashMap<String, ir::Expression>) -> Result<PlutusData, super::Error> {
let addr = extract_address(data)?;

match addr {
addresses::Address::Shelley(shelley_addr) => {
Ok(shelley_addr.payment().to_vec().as_data())
}
addresses::Address::Byron(byron_addr) => {
Ok(byron_addr.to_vec().as_data())
}
_ => Err(super::Error::CoerceError(
"Address type does not support payment parts".to_string(),
"Payment credential".to_string(),
)),
}
}

fn address_staking_part(data: &HashMap<String, ir::Expression>) -> Result<PlutusData, super::Error> {
let addr = extract_address(data)?;

match addr {
addresses::Address::Shelley(shelley_addr) => {
Ok(shelley_addr.delegation().to_vec().as_data())
}
addresses::Address::Stake(stake_addr) => {
Ok(stake_addr.to_vec().as_data())
}
_ => Err(super::Error::CoerceError(
"Address type does not support staking parts".to_string(),
"Staking credential".to_string(),
)),
}
}
4 changes: 4 additions & 0 deletions crates/tx3-lang/src/analyzing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -703,6 +703,8 @@ impl Analyzable for DataExpr {
DataExpr::SlotToTime(x) => x.analyze(parent),
DataExpr::TimeToSlot(x) => x.analyze(parent),
DataExpr::ConcatOp(x) => x.analyze(parent),
DataExpr::AddressPaymentPart(x) => x.analyze(parent),
DataExpr::AddressStakingPart(x) => x.analyze(parent),
_ => AnalyzeReport::default(),
}
}
Expand All @@ -723,6 +725,8 @@ impl Analyzable for DataExpr {
DataExpr::SlotToTime(x) => x.is_resolved(),
DataExpr::TimeToSlot(x) => x.is_resolved(),
DataExpr::ConcatOp(x) => x.is_resolved(),
DataExpr::AddressPaymentPart(x) => x.is_resolved(),
DataExpr::AddressStakingPart(x) => x.is_resolved(),
_ => true,
}
}
Expand Down
4 changes: 4 additions & 0 deletions crates/tx3-lang/src/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -752,6 +752,8 @@ pub enum DataExpr {
NegateOp(NegateOp),
PropertyOp(PropertyOp),
UtxoRef(UtxoRef),
AddressPaymentPart(crate::cardano::AddressPaymentPart),
AddressStakingPart(crate::cardano::AddressStakingPart),
}

impl DataExpr {
Expand Down Expand Up @@ -787,6 +789,8 @@ impl DataExpr {
DataExpr::UtxoRef(_) => Some(Type::UtxoRef),
DataExpr::MinUtxo(_) => Some(Type::AnyAsset),
DataExpr::ComputeTipSlot => Some(Type::Int),
DataExpr::AddressPaymentPart(_) => Some(Type::Bytes),
DataExpr::AddressStakingPart(_) => Some(Type::Bytes),
DataExpr::SlotToTime(_) => Some(Type::Int),
DataExpr::TimeToSlot(_) => Some(Type::Int),
}
Expand Down
114 changes: 114 additions & 0 deletions crates/tx3-lang/src/cardano.rs
Original file line number Diff line number Diff line change
Expand Up @@ -724,6 +724,34 @@ impl IntoLower for CardanoPublishBlock {
}
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct AddressPaymentPart {
pub address: Box<DataExpr>,
pub span: Span,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct AddressStakingPart {
pub address: Box<DataExpr>,
pub span: Span,
}

pub fn parse_cardano_function(pair: Pair<Rule>) -> Result<DataExpr, Error> {
let mut inner = pair.into_inner();
let function_pair = inner.next().unwrap();

match function_pair.as_rule() {
Rule::cardano_address_payment_part => {
Ok(DataExpr::AddressPaymentPart(AddressPaymentPart::parse(function_pair)?))
}
Rule::cardano_address_staking_part => {
Ok(DataExpr::AddressStakingPart(AddressStakingPart::parse(function_pair)?))
}
x => unreachable!("Unexpected rule in cardano_functions: {:?}", x),
}
}


#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub enum CardanoBlock {
VoteDelegationCertificate(VoteDelegationCertificate),
Expand All @@ -735,6 +763,38 @@ pub enum CardanoBlock {
Publish(CardanoPublishBlock),
}

impl AstNode for AddressPaymentPart {
const RULE: Rule = Rule::cardano_address_payment_part;

fn parse(pair: Pair<Rule>) -> Result<Self, Error> {
let span = pair.as_span().into();
let mut inner = pair.into_inner();
let address = DataExpr::parse(inner.next().unwrap())?.into();

Ok(AddressPaymentPart { address, span })
}

fn span(&self) -> &Span {
&self.span
}
}

impl AstNode for AddressStakingPart {
const RULE: Rule = Rule::cardano_address_staking_part;

fn parse(pair: Pair<Rule>) -> Result<Self, Error> {
let span = pair.as_span().into();
let mut inner = pair.into_inner();
let address = DataExpr::parse(inner.next().unwrap())?.into();

Ok(AddressStakingPart { address, span })
}

fn span(&self) -> &Span {
&self.span
}
}

impl AstNode for CardanoBlock {
const RULE: Rule = Rule::cardano_block;

Expand Down Expand Up @@ -781,6 +841,26 @@ impl AstNode for CardanoBlock {
}
}

impl Analyzable for AddressPaymentPart {
fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
self.address.analyze(parent)
}

fn is_resolved(&self) -> bool {
self.address.is_resolved()
}
}

impl Analyzable for AddressStakingPart {
fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
self.address.analyze(parent)
}

fn is_resolved(&self) -> bool {
self.address.is_resolved()
}
}

impl Analyzable for CardanoBlock {
fn analyze(&mut self, parent: Option<Rc<Scope>>) -> AnalyzeReport {
match self {
Expand All @@ -807,6 +887,40 @@ impl Analyzable for CardanoBlock {
}
}

impl IntoLower for AddressPaymentPart {
type Output = ir::AdHocDirective;

fn into_lower(
&self,
ctx: &crate::lowering::Context,
) -> Result<Self::Output, crate::lowering::Error> {
let address_expr = self.address.into_lower(ctx)?;
let mut data = std::collections::HashMap::new();
data.insert("address".to_string(), address_expr);
Ok(ir::AdHocDirective {
name: "cardano_address_payment_part".to_string(),
data,
})
}
}

impl IntoLower for AddressStakingPart {
type Output = ir::AdHocDirective;

fn into_lower(
&self,
ctx: &crate::lowering::Context,
) -> Result<Self::Output, crate::lowering::Error> {
let address_expr = self.address.into_lower(ctx)?;
let mut data = std::collections::HashMap::new();
data.insert("address".to_string(), address_expr);
Ok(ir::AdHocDirective {
name: "cardano_address_staking_part".to_string(),
data,
})
}
}

impl IntoLower for CardanoBlock {
type Output = ir::AdHocDirective;

Expand Down
8 changes: 8 additions & 0 deletions crates/tx3-lang/src/lowering.rs
Original file line number Diff line number Diff line change
Expand Up @@ -491,6 +491,12 @@ impl IntoLower for ast::DataExpr {
ast::DataExpr::ComputeTipSlot => {
ir::Expression::EvalCompiler(Box::new(ir::CompilerOp::ComputeTipSlot))
}
ast::DataExpr::AddressPaymentPart(x) => {
ir::Expression::AdHocDirective(Box::new(x.into_lower(ctx)?))
}
ast::DataExpr::AddressStakingPart(x) => {
ir::Expression::AdHocDirective(Box::new(x.into_lower(ctx)?))
}
ast::DataExpr::SlotToTime(x) => ir::Expression::EvalCompiler(Box::new(
ir::CompilerOp::ComputeSlotToTime(x.into_lower(ctx)?),
)),
Expand Down Expand Up @@ -961,4 +967,6 @@ mod tests {
test_lowering!(donation);

test_lowering!(list_concat);

test_lowering!(party_serialization);
}
17 changes: 10 additions & 7 deletions crates/tx3-lang/src/parsing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,8 @@ use pest::{
};
use pest_derive::Parser;

use crate::{
ast::*,
cardano::{PlutusWitnessBlock, PlutusWitnessField},
};
use crate::ast::*;

#[derive(Parser)]
#[grammar = "tx3.pest"]
pub(crate) struct Tx3Grammar;
Expand Down Expand Up @@ -1252,6 +1250,7 @@ impl AstNode for DataExpr {
Rule::concat_constructor => DataExpr::concat_constructor_parse(x),
Rule::min_utxo => DataExpr::min_utxo_parse(x),
Rule::tip_slot => DataExpr::tip_slot_parse(x),
Rule::cardano_functions => crate::cardano::parse_cardano_function(x),
Rule::slot_to_time => DataExpr::slot_to_time_parse(x),
Rule::time_to_slot => DataExpr::time_to_slot_parse(x),
Rule::data_expr => DataExpr::parse(x),
Expand Down Expand Up @@ -1295,6 +1294,8 @@ impl AstNode for DataExpr {
DataExpr::PropertyOp(x) => &x.span,
DataExpr::UtxoRef(x) => x.span(),
DataExpr::MinUtxo(x) => x.span(),
DataExpr::AddressPaymentPart(x) => x.span(),
DataExpr::AddressStakingPart(x) => x.span(),
DataExpr::SlotToTime(x) => x.span(),
DataExpr::TimeToSlot(x) => x.span(),
DataExpr::ComputeTipSlot => &Span::DUMMY, // TODO
Expand Down Expand Up @@ -2749,11 +2750,11 @@ mod tests {
#[test]
fn test_spans_are_respected() {
let program = parse_well_known_example("lang_tour");
assert_eq!(program.span, Span::new(0, 1560));
assert_eq!(program.span, Span::new(0, 2370));

assert_eq!(program.parties[0].span, Span::new(47, 61));
assert_eq!(program.parties[0].span, Span::new(92, 106));

assert_eq!(program.types[0].span, Span::new(63, 158));
assert_eq!(program.types[0].span, Span::new(108, 232));
}

fn make_snapshot_if_missing(example: &str, program: &Program) {
Expand Down Expand Up @@ -2822,4 +2823,6 @@ mod tests {
test_parsing!(donation);

test_parsing!(list_concat);

test_parsing!(party_serialization);
}
12 changes: 12 additions & 0 deletions crates/tx3-lang/src/tx3.pest
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@ data_expr = { data_prefix* ~ data_primary ~ data_postfix* ~ (data_infix ~ data_p
string |
min_utxo |
tip_slot |
cardano_functions |
slot_to_time |
time_to_slot |
struct_constructor |
Expand Down Expand Up @@ -402,6 +403,17 @@ cardano_publish_block = {
"}"
}

cardano_address_payment_part = { "payment_part" ~ "(" ~ data_expr ~ ")" }
cardano_address_staking_part = { "staking_part" ~ "(" ~ data_expr ~ ")" }

cardano_functions = {
"cardano" ~ "::" ~ (
cardano_address_payment_part |
cardano_address_staking_part
)
}


cardano_block = {
"cardano" ~ "::" ~ (
cardano_stake_delegation_certificate |
Expand Down
Loading