From 4a8cb72708c76959ca11adc6067b4b79955790a1 Mon Sep 17 00:00:00 2001 From: Pi Lanningham Date: Sat, 1 Jul 2023 14:14:09 -0400 Subject: [PATCH] Add a blueprint policy command Computes the policy ID of a minting policy; added guards for blueprint address to check that it's not a minting policy; Wasn't 100% sure where the errors should live, so I'm happy to move them if there's objections --- crates/aiken-project/src/blueprint/error.rs | 20 ++++++++ crates/aiken-project/src/lib.rs | 41 ++++++++++++++- crates/aiken/src/cmd/blueprint/mod.rs | 3 ++ crates/aiken/src/cmd/blueprint/policy.rs | 55 +++++++++++++++++++++ 4 files changed, 117 insertions(+), 2 deletions(-) create mode 100644 crates/aiken/src/cmd/blueprint/policy.rs diff --git a/crates/aiken-project/src/blueprint/error.rs b/crates/aiken-project/src/blueprint/error.rs index 9aac7739..3a61c051 100644 --- a/crates/aiken-project/src/blueprint/error.rs +++ b/crates/aiken-project/src/blueprint/error.rs @@ -49,6 +49,26 @@ pub enum Error { ))] ParameterizedValidator { n: usize }, + #[error( + "I couldn't compute the address of the given validator because it's actually a minting policy!", + )] + #[diagnostic(code("aiken::blueprint::address::minting_validator"))] + #[diagnostic(help( + "I can only compute addresses for spending validators. Did you mean to call {blueprint_policy_command} instead?", + blueprint_policy_command = "blueprint policy".if_supports_color(Stdout, |s| s.purple()), + ))] + UnexpectedMintingValidator, + + #[error( + "I couldn't compute the policyId of the given validator because it's actually a spending policy!", + )] + #[diagnostic(code("aiken::blueprint::address::spending_validator"))] + #[diagnostic(help( + "I can only compute policyIds for minting validators. Did you mean to call {blueprint_address_command} instead?", + blueprint_address_command = "blueprint address".if_supports_color(Stdout, |s| s.purple()), + ))] + UnexpectedSpendingValidator, + #[error("I stumble upon something else than a constant when I expected one.")] #[diagnostic(code("aiken:blueprint::apply::malformed::argument"))] #[diagnostic(help( diff --git a/crates/aiken-project/src/lib.rs b/crates/aiken-project/src/lib.rs index c124d28c..42505eb2 100644 --- a/crates/aiken-project/src/lib.rs +++ b/crates/aiken-project/src/lib.rs @@ -27,9 +27,10 @@ use indexmap::IndexMap; use miette::NamedSource; use options::{CodeGenMode, Options}; use package_name::PackageName; -use pallas::ledger::addresses::{ +use pallas::ledger::{addresses::{ Address, Network, ShelleyAddress, ShelleyDelegationPart, StakePayload, -}; +}, primitives::babbage::{self as cardano, PolicyId}}; +use pallas_traverse::ComputeHash; use script::{EvalHint, EvalInfo, Script}; use std::{ collections::HashMap, @@ -369,6 +370,11 @@ where let when_missing = |known_validators| Error::NoValidatorNotFound { known_validators }; blueprint.with_validator(title, when_too_many, when_missing, |validator| { + // Make sure we're not calculating the address for a minting validator + if validator.datum.is_none() { + return Err(blueprint::error::Error::UnexpectedMintingValidator.into()); + } + let n = validator.parameters.len(); if n > 0 { Err(blueprint::error::Error::ParameterizedValidator { n }.into()) @@ -380,6 +386,37 @@ where }) } + pub fn policy( + &self, + title: Option<&String> + ) -> Result { + // Read blueprint + let blueprint = File::open(self.blueprint_path()) + .map_err(|_| blueprint::error::Error::InvalidOrMissingFile)?; + let blueprint: Blueprint = serde_json::from_reader(BufReader::new(blueprint))?; + + // Error handlers for ambiguous / missing validators + let when_too_many = + |known_validators| Error::MoreThanOneValidatorFound { known_validators }; + let when_missing = |known_validators| Error::NoValidatorNotFound { known_validators }; + + blueprint.with_validator(title, when_too_many, when_missing, |validator| { + // Make sure we're not calculating the policy for a spending validator + if validator.datum.is_some() { + return Err(blueprint::error::Error::UnexpectedSpendingValidator.into()); + } + + let n = validator.parameters.len(); + if n > 0 { + Err(blueprint::error::Error::ParameterizedValidator { n }.into()) + } else { + let cbor = validator.program.to_cbor().unwrap(); + let validator_hash = cardano::PlutusV2Script(cbor.into()).compute_hash(); + Ok(validator_hash) + } + }) + } + pub fn apply_parameter( &self, title: Option<&String>, diff --git a/crates/aiken/src/cmd/blueprint/mod.rs b/crates/aiken/src/cmd/blueprint/mod.rs index 3cf959ad..0b28159b 100644 --- a/crates/aiken/src/cmd/blueprint/mod.rs +++ b/crates/aiken/src/cmd/blueprint/mod.rs @@ -1,4 +1,5 @@ pub mod address; +pub mod policy; pub mod apply; pub mod convert; @@ -8,6 +9,7 @@ use clap::Subcommand; #[derive(Subcommand)] pub enum Cmd { Address(address::Args), + Policy(policy::Args), Apply(apply::Args), Convert(convert::Args), } @@ -15,6 +17,7 @@ pub enum Cmd { pub fn exec(cmd: Cmd) -> miette::Result<()> { match cmd { Cmd::Address(args) => address::exec(args), + Cmd::Policy(args) => policy::exec(args), Cmd::Apply(args) => apply::exec(args), Cmd::Convert(args) => convert::exec(args), } diff --git a/crates/aiken/src/cmd/blueprint/policy.rs b/crates/aiken/src/cmd/blueprint/policy.rs new file mode 100644 index 00000000..0c32f583 --- /dev/null +++ b/crates/aiken/src/cmd/blueprint/policy.rs @@ -0,0 +1,55 @@ +use crate::with_project; +use aiken_lang::ast::Tracing; +use std::path::PathBuf; + +/// Compute a validator's address. +#[derive(clap::Args)] +pub struct Args { + /// Path to project + directory: Option, + + /// Name of the validator's module within the project. Optional if there's only one validator + #[clap(short, long)] + module: Option, + + /// Name of the validator within the module. Optional if there's only one validator + #[clap(short, long)] + validator: Option, + + /// Force the project to be rebuilt, otherwise relies on existing artifacts (i.e. plutus.json) + #[clap(long)] + rebuild: bool, +} + +pub fn exec( + Args { + directory, + module, + validator, + rebuild, + }: Args, +) -> miette::Result<()> { + with_project(directory, |p| { + if rebuild { + p.build(false, Tracing::NoTraces)?; + } + + let title = module.as_ref().map(|m| { + format!( + "{m}{}", + validator + .as_ref() + .map(|v| format!(".{v}")) + .unwrap_or_default() + ) + }); + + let title = title.as_ref().or(validator.as_ref()); + + let policy = p.policy(title)?; + + println!("{}", policy); + + Ok(()) + }) +}