use serde_json::{json, Value};
use crate::{data::{ByteString, Constr0, Constr1, PlutusDataJson}, WError};
use whisky_macros::ImplConstr;
use super::{byte_string, constr0, constr1};
#[derive(Clone, Debug)]
pub enum Credential {
VerificationKey(VerificationKey),
Script(Script),
}
impl Credential {
pub fn new((hash, is_script): (&str, bool)) -> Self {
if is_script {
Credential::Script(Script::from(hash))
} else {
Credential::VerificationKey(VerificationKey::from(hash))
}
}
}
#[derive(Clone, Debug, ImplConstr)]
pub struct VerificationKey(pub Constr0<ByteString>);
#[derive(Clone, Debug, ImplConstr)]
pub struct Script(pub Constr1<ByteString>);
impl PlutusDataJson for Credential {
fn to_json(&self) -> Value {
match self {
Credential::VerificationKey(vk) => vk.to_json(),
Credential::Script(script) => script.to_json(),
}
}
fn from_json(value: &Value) -> Result<Self, WError> {
let tag = value
.get("constructor")
.ok_or_else(|| WError::new("Credential::from_json", "missing 'constructor' field"))?
.as_u64()
.ok_or_else(|| WError::new("Credential::from_json", "invalid 'constructor' value"))?;
match tag {
0 => VerificationKey::from_json(value).map(Credential::VerificationKey),
1 => Script::from_json(value).map(Credential::Script),
_ => Err(WError::new(
"Credential::from_json",
&format!("unknown constructor tag: {}", tag),
)),
}
}
}
#[derive(Clone, Debug)]
pub struct Address {
pub payment_key_hash: String,
pub stake_credential: Option<String>,
pub is_script_payment_key: bool,
pub is_script_stake_key: bool,
}
impl Address {
pub fn new(
payment_key_hash: &str,
stake_credential: Option<&str>,
is_script_payment_key: bool,
is_script_stake_key: bool,
) -> Self {
Address {
payment_key_hash: payment_key_hash.to_string(),
stake_credential: stake_credential.map(|s| s.to_string()),
is_script_payment_key,
is_script_stake_key,
}
}
}
impl PlutusDataJson for Address {
fn to_json(&self) -> Value {
if self.is_script_payment_key {
script_address(
&self.payment_key_hash,
self.stake_credential.as_deref(),
self.is_script_stake_key,
)
} else {
pub_key_address(
&self.payment_key_hash,
self.stake_credential.as_deref(),
self.is_script_stake_key,
)
}
}
fn to_json_string(&self) -> String {
self.to_json().to_string()
}
fn to_constr_field(&self) -> Vec<serde_json::Value> {
vec![self.to_json()]
}
fn from_json(value: &Value) -> Result<Self, WError> {
let tag = value
.get("constructor")
.ok_or_else(|| WError::new("Address::from_json", "missing 'constructor' field"))?
.as_u64()
.ok_or_else(|| WError::new("Address::from_json", "invalid 'constructor' value"))?;
if tag != 0 {
return Err(WError::new(
"Address::from_json",
&format!("expected constructor tag 0 for Address, got {}", tag),
));
}
let fields = value
.get("fields")
.ok_or_else(|| WError::new("Address::from_json", "missing 'fields' field"))?
.as_array()
.ok_or_else(|| WError::new("Address::from_json", "invalid 'fields' value"))?;
if fields.len() != 2 {
return Err(WError::new(
"Address::from_json",
"expected 2 fields for Address",
));
}
let payment_cred = &fields[0];
let payment_tag = payment_cred
.get("constructor")
.and_then(|c| c.as_u64())
.ok_or_else(|| WError::new("Address::from_json", "invalid payment credential"))?;
let is_script_payment_key = payment_tag == 1;
let payment_fields = payment_cred
.get("fields")
.and_then(|f| f.as_array())
.ok_or_else(|| WError::new("Address::from_json", "invalid payment credential fields"))?;
let payment_key_hash = payment_fields
.first()
.and_then(|f| f.get("bytes"))
.and_then(|b| b.as_str())
.ok_or_else(|| WError::new("Address::from_json", "invalid payment key hash"))?
.to_string();
let stake_cred = &fields[1];
let stake_tag = stake_cred
.get("constructor")
.and_then(|c| c.as_u64())
.ok_or_else(|| WError::new("Address::from_json", "invalid stake credential"))?;
let (stake_credential, is_script_stake_key) = if stake_tag == 1 {
(None, false)
} else {
let stake_fields = stake_cred
.get("fields")
.and_then(|f| f.as_array())
.ok_or_else(|| WError::new("Address::from_json", "invalid stake credential fields"))?;
if stake_fields.is_empty() {
(None, false)
} else {
let inner_wrapper = stake_fields.first()
.and_then(|f| f.get("fields"))
.and_then(|f| f.as_array())
.and_then(|f| f.first())
.ok_or_else(|| WError::new("Address::from_json", "invalid stake credential structure"))?;
let inner_tag = inner_wrapper
.get("constructor")
.and_then(|c| c.as_u64())
.ok_or_else(|| WError::new("Address::from_json", "invalid stake credential inner tag"))?;
let is_script = inner_tag == 1;
let stake_hash = inner_wrapper
.get("fields")
.and_then(|f| f.as_array())
.and_then(|f| f.first())
.and_then(|f| f.get("bytes"))
.and_then(|b| b.as_str())
.ok_or_else(|| WError::new("Address::from_json", "invalid stake key hash"))?
.to_string();
(Some(stake_hash), is_script)
}
};
Ok(Address {
payment_key_hash,
stake_credential,
is_script_payment_key,
is_script_stake_key,
})
}
}
pub fn payment_pub_key_hash(pub_key_hash: &str) -> Value {
byte_string(pub_key_hash)
}
pub fn pub_key_hash(pub_key_hash: &str) -> Value {
byte_string(pub_key_hash)
}
pub fn maybe_staking_hash(stake_credential: &str, is_script_stake_key: bool) -> Value {
if stake_credential.is_empty() {
constr1(json!([]))
} else if is_script_stake_key {
constr0(vec![constr0(vec![constr1(vec![byte_string(
stake_credential,
)])])])
} else {
constr0(vec![constr0(vec![constr0(vec![byte_string(
stake_credential,
)])])])
}
}
pub fn pub_key_address(
bytes: &str,
stake_credential: Option<&str>,
is_script_stake_key: bool,
) -> Value {
constr0(vec![
constr0(vec![byte_string(bytes)]),
maybe_staking_hash(stake_credential.unwrap_or(""), is_script_stake_key),
])
}
pub fn script_address(
bytes: &str,
stake_credential: Option<&str>,
is_script_stake_key: bool,
) -> Value {
constr0(vec![
constr1(vec![byte_string(bytes)]),
maybe_staking_hash(stake_credential.unwrap_or(""), is_script_stake_key),
])
}