#[derive(Clone, Copy)]
enum DarSource {
SpliceDir(&'static str),
Env(&'static str),
Repo(&'static str),
Payloads(&'static str),
}
#[derive(Clone, Copy)]
enum Emits {
Everything,
Families(&'static [&'static str]),
}
const STDLIB: &[&str] = &["daml-stdlib", "daml-prim", "ghc-stdlib"];
const STDLIB_CRATE: &str = "canton_daml_stdlib";
const CRATES: &[(&str, DarSource, &[&str], Emits)] = &[
(
"canton-daml-stdlib",
DarSource::Repo("testdata/splice-api-token-holding-v1-1.0.0.dar"),
&[],
Emits::Families(STDLIB),
),
(
"canton-splice-api-token-metadata-v1",
DarSource::SpliceDir("splice-api-token-metadata-v1-1.0.0.dar"),
&[],
Emits::Everything,
),
(
"canton-splice-api-featured-app-v1",
DarSource::SpliceDir("splice-api-featured-app-v1-1.0.0.dar"),
&[],
Emits::Everything,
),
(
"canton-splice-api-token-holding-v1",
DarSource::SpliceDir("splice-api-token-holding-v1-1.0.0.dar"),
&[METADATA],
Emits::Everything,
),
(
"canton-splice-api-token-allocation-v1",
DarSource::SpliceDir("splice-api-token-allocation-v1-1.0.0.dar"),
HOLDING,
Emits::Everything,
),
(
"canton-splice-api-token-burn-mint-v1",
DarSource::SpliceDir("splice-api-token-burn-mint-v1-1.0.0.dar"),
HOLDING,
Emits::Everything,
),
(
"canton-splice-api-token-transfer-instruction-v1",
DarSource::SpliceDir("splice-api-token-transfer-instruction-v1-1.0.0.dar"),
HOLDING,
Emits::Everything,
),
(
"canton-splice-api-token-allocation-instruction-v1",
DarSource::SpliceDir("splice-api-token-allocation-instruction-v1-1.0.0.dar"),
ALLOCATION,
Emits::Everything,
),
(
"canton-splice-api-token-allocation-request-v1",
DarSource::SpliceDir("splice-api-token-allocation-request-v1-1.0.0.dar"),
ALLOCATION,
Emits::Everything,
),
(
"canton-splice-amulet",
DarSource::SpliceDir("splice-amulet-0.1.14.dar"),
API,
Emits::Everything,
),
(
"canton-splice-wallet-payments",
DarSource::SpliceDir("splice-wallet-payments-0.1.14.dar"),
WITH_AMULET,
Emits::Everything,
),
(
"canton-splice-wallet",
DarSource::SpliceDir("splice-wallet-0.1.14.dar"),
WITH_PAYMENTS,
Emits::Everything,
),
(
"canton-splice-api-token-holding-v2",
V2,
&[METADATA],
Emits::Families(&["splice-api-token-holding-v2"]),
),
(
"canton-splice-api-token-transfer-instruction-v2",
V2,
V2_HOLDING,
Emits::Families(&["splice-api-token-transfer-instruction-v2"]),
),
(
"canton-splice-api-token-transfer-events-v2",
V2,
V2_HOLDING,
Emits::Families(&["splice-api-token-transfer-events-v2"]),
),
(
"canton-splice-api-token-allocation-v2",
V2,
V2_HOLDING,
Emits::Families(&["splice-api-token-allocation-v2"]),
),
(
"canton-splice-api-token-allocation-instruction-v2",
V2,
V2_ALLOCATION,
Emits::Families(&["splice-api-token-allocation-instruction-v2"]),
),
(
"canton-splice-api-token-allocation-request-v2",
V2,
V2_ALLOCATION,
Emits::Families(&["splice-api-token-allocation-request-v2"]),
),
(
"canton-quickstart-licensing",
DarSource::Env("CANTON_LICENSING_DAR"),
ALLOCATION_REQUEST,
Emits::Everything,
),
];
const METADATA: &str = "splice-api-token-metadata-v1";
const V2: DarSource = DarSource::Payloads("testdata/token-standard-v2");
const V2_HOLDING: &[&str] = &[METADATA, "splice-api-token-holding-v2"];
const V2_ALLOCATION: &[&str] = &[
METADATA,
"splice-api-token-holding-v2",
"splice-api-token-allocation-v2",
];
const HOLDING: &[&str] = &[METADATA, "splice-api-token-holding-v1"];
const ALLOCATION: &[&str] = &[
METADATA,
"splice-api-token-holding-v1",
"splice-api-token-allocation-v1",
];
const ALLOCATION_REQUEST: &[&str] = &[
METADATA,
"splice-api-token-holding-v1",
"splice-api-token-allocation-v1",
"splice-api-token-allocation-request-v1",
];
const API: &[&str] = &[
METADATA,
"splice-api-token-holding-v1",
"splice-api-token-allocation-v1",
"splice-api-token-allocation-instruction-v1",
"splice-api-token-transfer-instruction-v1",
"splice-api-featured-app-v1",
];
const WITH_AMULET: &[&str] = &[
METADATA,
"splice-api-token-holding-v1",
"splice-api-token-allocation-v1",
"splice-api-token-allocation-instruction-v1",
"splice-api-token-transfer-instruction-v1",
"splice-api-featured-app-v1",
"splice-amulet",
];
const WITH_PAYMENTS: &[&str] = &[
METADATA,
"splice-api-token-holding-v1",
"splice-api-token-allocation-v1",
"splice-api-token-allocation-instruction-v1",
"splice-api-token-transfer-instruction-v1",
"splice-api-featured-app-v1",
"splice-amulet",
"splice-wallet-payments",
];
fn crate_ident(package: &str) -> String {
format!("canton-{package}").replace('-', "_")
}
fn externals_for(packages: &[&str], emits: Emits) -> canton_codegen::ExternalPackages {
let mut map = canton_codegen::ExternalPackages::new();
for package in packages {
map = map.with(*package, crate_ident(package));
}
let is_stdlib = matches!(emits, Emits::Families(f) if f == STDLIB);
if !is_stdlib {
for family in STDLIB {
map = map.with_prefixed(*family, STDLIB_CRATE);
}
}
map
}
fn selection_for(emits: Emits) -> canton_codegen::Selection {
match emits {
Emits::Everything => canton_codegen::Selection::everything(),
Emits::Families(families) => {
canton_codegen::Selection::only(std::iter::empty::<String>())
.and_prefixed(families.iter().copied())
}
}
}
fn dar_path(source: DarSource) -> Option<String> {
match source {
DarSource::SpliceDir(name) => std::env::var("SPLICE_DARS")
.ok()
.map(|dir| format!("{dir}/{name}")),
DarSource::Env(var) => std::env::var(var).ok(),
DarSource::Repo(rel) | DarSource::Payloads(rel) => {
Some(format!("{}/../../{rel}", env!("CARGO_MANIFEST_DIR")))
}
}
}
fn packages(
source: DarSource,
path: &str,
) -> Result<Vec<(String, canton_lf::pb::daml_lf_2::Package)>, Box<dyn std::error::Error>> {
let DarSource::Payloads(_) = source else {
return Ok(canton_lf::decode_all(&canton_lf::Dar::open(path)?)?);
};
let mut entries: Vec<_> = std::fs::read_dir(path)?
.filter_map(std::result::Result::ok)
.map(|entry| entry.path())
.filter(|p| p.extension().is_some_and(|e| e == "lfpayload"))
.collect();
entries.sort();
let mut packages = Vec::new();
for entry in entries {
let name = entry
.file_name()
.and_then(|n| n.to_str())
.ok_or("a payload with no name")?;
let id = name
.strip_suffix(".lfpayload")
.and_then(|stem| stem.rsplit_once('-'))
.map(|(_, id)| id)
.filter(|id| id.len() == 64 && id.chars().all(|c| c.is_ascii_hexdigit()))
.ok_or_else(|| {
format!("`{name}` does not end with `-<package id>.lfpayload`, so its bytes are unpinned")
})?;
let bytes = std::fs::read(&entry)?;
let computed = package_id_of(&bytes);
if computed != id {
return Err(format!("`{name}` hashes to {computed}, not to the id it is named for").into());
}
packages.push((id.to_string(), canton_lf::decode_payload(&bytes)?));
}
Ok(packages)
}
fn package_id_of(bytes: &[u8]) -> String {
use sha2::{Digest as _, Sha256};
use std::fmt::Write as _;
Sha256::digest(bytes)
.iter()
.fold(String::with_capacity(64), |mut out, byte| {
let _ = write!(out, "{byte:02x}");
out
})
}