Implement new command: address
This calculates a validator's address from validators found in a blueprint. It also provides a convenient way to attach a delegation part to the validator if needs be. The command is meant to provide a nice user experience and works 'out of the box' for projects that have only a single validator. Just call 'aiken address' to get the validator's address. Note that the command-line doesn't provide any option to configure the target network. This automatically assumes testnet, and will until we deem the project ready for mainnet. Those brave enough to run an Aiken's program on mainnet will find a way anyway.
This commit is contained in:
parent
1aa12fb368
commit
daee8e39d6
|
@ -66,6 +66,7 @@ dependencies = [
|
|||
"pallas-primitives",
|
||||
"pallas-traverse",
|
||||
"regex",
|
||||
"serde_json",
|
||||
"thiserror",
|
||||
"uplc",
|
||||
]
|
||||
|
|
|
@ -27,7 +27,7 @@ pub enum Error {
|
|||
source_code: NamedSource,
|
||||
return_type: Arc<Type>,
|
||||
},
|
||||
#[error("A {} validator requires at least {at_least} arguments", name.purple().bold())]
|
||||
#[error("A {} validator requires at least {} arguments.", name.purple().bold(), at_least.to_string().purple().bold())]
|
||||
#[diagnostic(code("aiken::blueprint::invalid::arity"))]
|
||||
WrongValidatorArity {
|
||||
name: String,
|
||||
|
@ -47,6 +47,11 @@ pub enum Error {
|
|||
#[source_code]
|
||||
source_code: NamedSource,
|
||||
},
|
||||
|
||||
#[error("Invalid or missing project's blueprint file.")]
|
||||
#[diagnostic(code("aiken::blueprint::missing"))]
|
||||
#[diagnostic(help("Did you forget to {build} the project?", build = "build".purple().bold()))]
|
||||
InvalidOrMissingFile,
|
||||
}
|
||||
|
||||
pub fn assert_return_bool(module: &CheckedModule, def: &TypedFunction) -> Result<(), Error> {
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
use crate::{
|
||||
blueprint::error as blueprint, deps::manifest::Package, package_name::PackageName, pretty,
|
||||
blueprint::{error as blueprint, validator},
|
||||
deps::manifest::Package,
|
||||
package_name::PackageName,
|
||||
pretty,
|
||||
script::EvalHint,
|
||||
};
|
||||
use aiken_lang::{
|
||||
|
@ -10,6 +13,7 @@ use aiken_lang::{
|
|||
use miette::{
|
||||
Diagnostic, EyreContext, LabeledSpan, MietteHandlerOpts, NamedSource, RgbColors, SourceCode,
|
||||
};
|
||||
use owo_colors::OwoColorize;
|
||||
use std::{
|
||||
fmt::{Debug, Display},
|
||||
io,
|
||||
|
@ -49,6 +53,9 @@ pub enum Error {
|
|||
#[error(transparent)]
|
||||
JoinError(#[from] tokio::task::JoinError),
|
||||
|
||||
#[error(transparent)]
|
||||
Json(#[from] serde_json::Error),
|
||||
|
||||
#[error("{help}")]
|
||||
TomlLoading {
|
||||
path: PathBuf,
|
||||
|
@ -120,6 +127,21 @@ pub enum Error {
|
|||
package.name.repo
|
||||
)]
|
||||
UnknownPackageVersion { package: Package },
|
||||
|
||||
#[error("I couldn't parse the provided stake address.")]
|
||||
MalformedStakeAddress {
|
||||
error: Option<pallas::ledger::addresses::Error>,
|
||||
},
|
||||
|
||||
#[error("I didn't find any validator matching your criteria.")]
|
||||
NoValidatorNotFound {
|
||||
known_validators: Vec<(String, validator::Purpose)>,
|
||||
},
|
||||
|
||||
#[error("I found multiple suitable validators and I need you to tell me which one to pick.")]
|
||||
MoreThanOneValidatorFound {
|
||||
known_validators: Vec<(String, validator::Purpose)>,
|
||||
},
|
||||
}
|
||||
|
||||
impl Error {
|
||||
|
@ -203,6 +225,10 @@ impl Error {
|
|||
Error::ZipExtract(_) => None,
|
||||
Error::JoinError(_) => None,
|
||||
Error::UnknownPackageVersion { .. } => None,
|
||||
Error::Json { .. } => None,
|
||||
Error::MalformedStakeAddress { .. } => None,
|
||||
Error::NoValidatorNotFound { .. } => None,
|
||||
Error::MoreThanOneValidatorFound { .. } => None,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -226,6 +252,10 @@ impl Error {
|
|||
Error::ZipExtract(_) => None,
|
||||
Error::JoinError(_) => None,
|
||||
Error::UnknownPackageVersion { .. } => None,
|
||||
Error::Json { .. } => None,
|
||||
Error::MalformedStakeAddress { .. } => None,
|
||||
Error::NoValidatorNotFound { .. } => None,
|
||||
Error::MoreThanOneValidatorFound { .. } => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -277,6 +307,10 @@ impl Diagnostic for Error {
|
|||
Error::ZipExtract(_) => None,
|
||||
Error::JoinError(_) => None,
|
||||
Error::UnknownPackageVersion { .. } => Some(Box::new("aiken::packages::resolve")),
|
||||
Error::Json { .. } => None,
|
||||
Error::MalformedStakeAddress { .. } => None,
|
||||
Error::NoValidatorNotFound { .. } => None,
|
||||
Error::MoreThanOneValidatorFound { .. } => None,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -287,7 +321,7 @@ impl Diagnostic for Error {
|
|||
first.display(),
|
||||
second.display()
|
||||
))),
|
||||
Error::FileIo { .. } => None,
|
||||
Error::FileIo { error, .. } => Some(Box::new(format!("{error}"))),
|
||||
Error::Blueprint(e) => e.help(),
|
||||
Error::ImportCycle { modules } => Some(Box::new(format!(
|
||||
"Try moving the shared code to a separate module that the others can depend on\n- {}",
|
||||
|
@ -334,6 +368,23 @@ impl Diagnostic for Error {
|
|||
Error::ZipExtract(_) => None,
|
||||
Error::JoinError(_) => None,
|
||||
Error::UnknownPackageVersion{..} => Some(Box::new("Perhaps, double-check the package repository and version?")),
|
||||
Error::Json(error) => Some(Box::new(format!("{error}"))),
|
||||
Error::MalformedStakeAddress { error } => Some(Box::new(format!("A stake address must be provided either as a base16-encoded string, or as a bech32-encoded string with the 'stake' or 'stake_test' prefix.{hint}", hint = match error {
|
||||
Some(error) => format!("\n\nHere's the error I encountered: {error}"),
|
||||
None => String::new(),
|
||||
}))),
|
||||
Error::NoValidatorNotFound { known_validators } => {
|
||||
Some(Box::new(format!(
|
||||
"Here's a list of all validators (and their purpose) I've found in your project. Please double-check this list against the options that you've provided:\n\n{}",
|
||||
known_validators.iter().map(|(name, purpose)| format!("→ {name} (purpose = {purpose})", name = name.purple().bold(), purpose = purpose.bright_blue())).collect::<Vec<String>>().join("\n")
|
||||
)))
|
||||
},
|
||||
Error::MoreThanOneValidatorFound { known_validators } => {
|
||||
Some(Box::new(format!(
|
||||
"Here's a list of all validators (and their purpose) I've found in your project. Select one of them using the appropriate options:\n\n{}",
|
||||
known_validators.iter().map(|(name, purpose)| format!("→ {name} (purpose = {purpose})", name = name.purple().bold(), purpose = purpose.bright_blue())).collect::<Vec<String>>().join("\n")
|
||||
)))
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -369,6 +420,10 @@ impl Diagnostic for Error {
|
|||
Error::ZipExtract(_) => None,
|
||||
Error::JoinError(_) => None,
|
||||
Error::UnknownPackageVersion { .. } => None,
|
||||
Error::Json { .. } => None,
|
||||
Error::MalformedStakeAddress { .. } => None,
|
||||
Error::NoValidatorNotFound { .. } => None,
|
||||
Error::MoreThanOneValidatorFound { .. } => None,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -392,6 +447,10 @@ impl Diagnostic for Error {
|
|||
Error::ZipExtract(_) => None,
|
||||
Error::JoinError(_) => None,
|
||||
Error::UnknownPackageVersion { .. } => None,
|
||||
Error::Json { .. } => None,
|
||||
Error::MalformedStakeAddress { .. } => None,
|
||||
Error::NoValidatorNotFound { .. } => None,
|
||||
Error::MoreThanOneValidatorFound { .. } => None,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -415,6 +474,10 @@ impl Diagnostic for Error {
|
|||
Error::ZipExtract { .. } => None,
|
||||
Error::JoinError { .. } => None,
|
||||
Error::UnknownPackageVersion { .. } => None,
|
||||
Error::Json { .. } => None,
|
||||
Error::MalformedStakeAddress { .. } => None,
|
||||
Error::NoValidatorNotFound { .. } => None,
|
||||
Error::MoreThanOneValidatorFound { .. } => None,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -438,6 +501,10 @@ impl Diagnostic for Error {
|
|||
Error::ZipExtract { .. } => None,
|
||||
Error::JoinError { .. } => None,
|
||||
Error::UnknownPackageVersion { .. } => None,
|
||||
Error::Json { .. } => None,
|
||||
Error::MalformedStakeAddress { .. } => None,
|
||||
Error::NoValidatorNotFound { .. } => None,
|
||||
Error::MoreThanOneValidatorFound { .. } => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@ pub mod pretty;
|
|||
pub mod script;
|
||||
pub mod telemetry;
|
||||
|
||||
use crate::blueprint::Blueprint;
|
||||
use crate::blueprint::{schema::Schema, validator, Blueprint};
|
||||
use aiken_lang::{
|
||||
ast::{Definition, Function, ModuleKind, TypedDataType, TypedFunction},
|
||||
builder::{DataTypeKey, FunctionAccessKey},
|
||||
|
@ -25,10 +25,14 @@ use indexmap::IndexMap;
|
|||
use miette::NamedSource;
|
||||
use options::{CodeGenMode, Options};
|
||||
use package_name::PackageName;
|
||||
use pallas::ledger::addresses::{
|
||||
Address, Network, ShelleyAddress, ShelleyDelegationPart, StakePayload,
|
||||
};
|
||||
use script::{EvalHint, EvalInfo, Script};
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
fs,
|
||||
fs::{self, File},
|
||||
io::BufReader,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
use telemetry::EventListener;
|
||||
|
@ -184,6 +188,10 @@ where
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub fn blueprint_path(&self) -> PathBuf {
|
||||
self.root.join("plutus.json")
|
||||
}
|
||||
|
||||
pub fn compile(&mut self, options: Options) -> Result<(), Error> {
|
||||
self.compile_deps()?;
|
||||
|
||||
|
@ -202,10 +210,9 @@ where
|
|||
|
||||
match options.code_gen_mode {
|
||||
CodeGenMode::Build(uplc_dump) => {
|
||||
let blueprint_path = self.root.join("plutus.json");
|
||||
self.event_listener
|
||||
.handle_event(Event::GeneratingBlueprint {
|
||||
path: blueprint_path.clone(),
|
||||
path: self.blueprint_path(),
|
||||
});
|
||||
|
||||
let mut generator = self.checked_modules.new_generator(
|
||||
|
@ -226,9 +233,9 @@ where
|
|||
}
|
||||
|
||||
let json = serde_json::to_string_pretty(&blueprint).unwrap();
|
||||
fs::write(&blueprint_path, json).map_err(|error| Error::FileIo {
|
||||
fs::write(self.blueprint_path(), json).map_err(|error| Error::FileIo {
|
||||
error,
|
||||
path: blueprint_path,
|
||||
path: self.blueprint_path(),
|
||||
})
|
||||
}
|
||||
CodeGenMode::Test {
|
||||
|
@ -274,6 +281,71 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
pub fn address(
|
||||
&self,
|
||||
with_title: Option<&String>,
|
||||
with_purpose: Option<&validator::Purpose>,
|
||||
stake_address: Option<&String>,
|
||||
) -> Result<ShelleyAddress, Error> {
|
||||
// Parse stake address
|
||||
let stake_address = stake_address
|
||||
.map(|s| {
|
||||
Address::from_hex(s)
|
||||
.or_else(|_| Address::from_bech32(s))
|
||||
.map_err(|error| Error::MalformedStakeAddress { error: Some(error) })
|
||||
.and_then(|addr| match addr {
|
||||
Address::Stake(addr) => Ok(addr),
|
||||
_ => Err(Error::MalformedStakeAddress { error: None }),
|
||||
})
|
||||
})
|
||||
.transpose()?;
|
||||
let delegation_part = match stake_address.map(|addr| addr.payload().to_owned()) {
|
||||
None => ShelleyDelegationPart::Null,
|
||||
Some(StakePayload::Stake(key)) => ShelleyDelegationPart::Key(key),
|
||||
Some(StakePayload::Script(script)) => ShelleyDelegationPart::Script(script),
|
||||
};
|
||||
|
||||
// Read blueprint
|
||||
let filepath = self.blueprint_path();
|
||||
let blueprint =
|
||||
File::open(filepath).map_err(|_| blueprint::error::Error::InvalidOrMissingFile)?;
|
||||
let blueprint: Blueprint<serde_json::Value> =
|
||||
serde_json::from_reader(BufReader::new(blueprint))?;
|
||||
|
||||
// Find validator's program
|
||||
let mut program = None;
|
||||
for v in blueprint.validators.iter() {
|
||||
if Some(&v.title) == with_title.or(Some(&v.title))
|
||||
&& Some(&v.purpose) == with_purpose.or(Some(&v.purpose))
|
||||
{
|
||||
program = Some(if program.is_none() {
|
||||
Ok(v.program.clone())
|
||||
} else {
|
||||
Err(Error::MoreThanOneValidatorFound {
|
||||
known_validators: blueprint
|
||||
.validators
|
||||
.iter()
|
||||
.map(|v| (v.title.clone(), v.purpose.clone()))
|
||||
.collect(),
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Print the address
|
||||
match program {
|
||||
Some(Ok(program)) => Ok(program.address(Network::Testnet, delegation_part)),
|
||||
Some(Err(e)) => Err(e),
|
||||
None => Err(Error::NoValidatorNotFound {
|
||||
known_validators: blueprint
|
||||
.validators
|
||||
.iter()
|
||||
.map(|v| (v.title.clone(), v.purpose.clone()))
|
||||
.collect(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
fn compile_deps(&mut self) -> Result<(), Error> {
|
||||
let manifest = deps::download(
|
||||
&self.event_listener,
|
||||
|
|
|
@ -28,3 +28,4 @@ aiken-lang = { path = "../aiken-lang", version = "0.0.28" }
|
|||
aiken-lsp = { path = "../aiken-lsp", version = "0.0.28" }
|
||||
aiken-project = { path = '../aiken-project', version = "0.0.28" }
|
||||
uplc = { path = '../uplc', version = "0.0.28" }
|
||||
serde_json = "1.0.91"
|
||||
|
|
|
@ -0,0 +1,53 @@
|
|||
use crate::with_project;
|
||||
use aiken_lang::VALIDATOR_NAMES;
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[derive(clap::Args)]
|
||||
#[clap(setting(clap::AppSettings::DeriveDisplayOrder))]
|
||||
/// Compute a validator's address.
|
||||
pub struct Args {
|
||||
/// Path to project
|
||||
directory: Option<PathBuf>,
|
||||
|
||||
/// Name of the validator's module within the project. Optional if there's only one validator.
|
||||
#[clap(short, long)]
|
||||
validator: Option<String>,
|
||||
|
||||
/// Purpose of the validator within the module. Optional if there's only one validator.
|
||||
#[clap(short, long, possible_values=&VALIDATOR_NAMES)]
|
||||
purpose: Option<String>,
|
||||
|
||||
/// Stake address to attach, if any.
|
||||
#[clap(long)]
|
||||
delegated_to: Option<String>,
|
||||
|
||||
/// Force the project to be rebuilt, otherwise relies on existing artifacts (i.e. plutus.json).
|
||||
#[clap(long)]
|
||||
rebuild: bool,
|
||||
}
|
||||
|
||||
pub fn exec(
|
||||
Args {
|
||||
directory,
|
||||
validator,
|
||||
purpose,
|
||||
delegated_to,
|
||||
rebuild,
|
||||
}: Args,
|
||||
) -> miette::Result<()> {
|
||||
with_project(directory, |p| {
|
||||
if rebuild {
|
||||
p.build(false)?;
|
||||
}
|
||||
let address = p.address(
|
||||
validator.as_ref(),
|
||||
purpose
|
||||
.as_ref()
|
||||
.map(|p| p.clone().try_into().unwrap())
|
||||
.as_ref(),
|
||||
delegated_to.as_ref(),
|
||||
)?;
|
||||
println!("{}", address.to_bech32().unwrap());
|
||||
Ok(())
|
||||
})
|
||||
}
|
|
@ -1,3 +1,4 @@
|
|||
pub mod address;
|
||||
pub mod build;
|
||||
pub mod check;
|
||||
pub mod docs;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use aiken::cmd::{
|
||||
build, check, docs, fmt, lsp, new,
|
||||
address, build, check, docs, fmt, lsp, new,
|
||||
packages::{self, add},
|
||||
tx, uplc,
|
||||
};
|
||||
|
@ -14,6 +14,7 @@ pub enum Cmd {
|
|||
New(new::Args),
|
||||
Fmt(fmt::Args),
|
||||
Build(build::Args),
|
||||
Address(address::Args),
|
||||
Check(check::Args),
|
||||
Docs(docs::Args),
|
||||
Add(add::Args),
|
||||
|
@ -43,6 +44,7 @@ fn main() -> miette::Result<()> {
|
|||
Cmd::New(args) => new::exec(args),
|
||||
Cmd::Fmt(args) => fmt::exec(args),
|
||||
Cmd::Build(args) => build::exec(args),
|
||||
Cmd::Address(args) => address::exec(args),
|
||||
Cmd::Check(args) => check::exec(args),
|
||||
Cmd::Docs(args) => docs::exec(args),
|
||||
Cmd::Add(args) => add::exec(args),
|
||||
|
|
Loading…
Reference in New Issue