use std::collections::{btree_map, btree_set};
use std::fmt::{self, Display, Formatter};
use std::iter;
use std::str::FromStr;
use amplify::confinement::{SmallBlob, TinyOrdMap, TinyOrdSet};
use amplify::hex::{FromHex, ToHex};
use amplify::{hex, ByteArray, Bytes32, FromSliceError, Wrapper};
use baid58::{Baid58ParseError, Chunking, FromBaid58, ToBaid58, CHUNKING_32CHECKSUM};
use commit_verify::{mpc, CommitmentId, Conceal};
use strict_encoding::{StrictDeserialize, StrictEncode, StrictSerialize};
use crate::schema::{self, ExtensionType, OpFullType, OpType, SchemaId, TransitionType};
use crate::{
AltLayer1Set, AssignmentType, Assignments, AssignmentsRef, Ffv, GenesisSeal, GlobalState,
GraphSeal, Opout, ReservedByte, TypedAssigns, LIB_NAME_RGB,
};
#[derive(Wrapper, WrapperMut, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, Default, From)]
#[wrapper(Deref)]
#[wrapper_mut(DerefMut)]
#[derive(StrictType, StrictEncode, StrictDecode)]
#[strict_type(lib = LIB_NAME_RGB)]
#[derive(CommitEncode)]
#[commit_encode(strategy = strict)]
#[cfg_attr(
feature = "serde",
derive(Serialize, Deserialize),
serde(crate = "serde_crate", rename_all = "camelCase")
)]
pub struct Valencies(TinyOrdSet<schema::ValencyType>);
impl<'a> IntoIterator for &'a Valencies {
type Item = schema::ValencyType;
type IntoIter = iter::Copied<btree_set::Iter<'a, schema::ValencyType>>;
fn into_iter(self) -> Self::IntoIter { self.0.iter().copied() }
}
#[derive(Wrapper, WrapperMut, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, Default, From)]
#[wrapper(Deref)]
#[wrapper_mut(DerefMut)]
#[derive(StrictType, StrictEncode, StrictDecode)]
#[strict_type(lib = LIB_NAME_RGB)]
#[derive(CommitEncode)]
#[commit_encode(strategy = strict)]
#[cfg_attr(
feature = "serde",
derive(Serialize, Deserialize),
serde(crate = "serde_crate", rename_all = "camelCase")
)]
pub struct Redeemed(TinyOrdMap<schema::ValencyType, OpId>);
impl<'a> IntoIterator for &'a Redeemed {
type Item = (&'a schema::ValencyType, &'a OpId);
type IntoIter = btree_map::Iter<'a, schema::ValencyType, OpId>;
fn into_iter(self) -> Self::IntoIter { self.0.iter() }
}
#[derive(Wrapper, WrapperMut, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, Default, From)]
#[wrapper(Deref)]
#[wrapper_mut(DerefMut)]
#[derive(StrictType, StrictEncode, StrictDecode)]
#[strict_type(lib = LIB_NAME_RGB)]
#[derive(CommitEncode)]
#[commit_encode(strategy = strict)]
#[cfg_attr(
feature = "serde",
derive(Serialize, Deserialize),
serde(crate = "serde_crate", rename_all = "camelCase")
)]
pub struct Inputs(TinyOrdSet<Input>);
impl<'a> IntoIterator for &'a Inputs {
type Item = Input;
type IntoIter = iter::Copied<btree_set::Iter<'a, Input>>;
fn into_iter(self) -> Self::IntoIter { self.0.iter().copied() }
}
#[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, Display)]
#[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)]
#[strict_type(lib = LIB_NAME_RGB)]
#[cfg_attr(
feature = "serde",
derive(Serialize, Deserialize),
serde(crate = "serde_crate", rename_all = "camelCase")
)]
#[display("{prev_out}")]
pub struct Input {
pub prev_out: Opout,
reserved: ReservedByte,
}
impl Input {
pub fn with(prev_out: Opout) -> Input {
Input {
prev_out,
reserved: default!(),
}
}
}
#[derive(Wrapper, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, Display, From)]
#[wrapper(Deref, BorrowSlice, Hex, Index, RangeOps)]
#[display(Self::to_hex)]
#[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)]
#[strict_type(lib = LIB_NAME_RGB)]
#[cfg_attr(
feature = "serde",
derive(Serialize, Deserialize),
serde(crate = "serde_crate", transparent)
)]
pub struct OpId(
#[from]
#[from([u8; 32])]
Bytes32,
);
impl FromStr for OpId {
type Err = hex::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> { Self::from_hex(s) }
}
impl OpId {
pub fn copy_from_slice(slice: impl AsRef<[u8]>) -> Result<Self, FromSliceError> {
Bytes32::copy_from_slice(slice).map(Self)
}
}
#[derive(Wrapper, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, From)]
#[wrapper(Deref, BorrowSlice, Hex, Index, RangeOps)]
#[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)]
#[strict_type(lib = LIB_NAME_RGB)]
#[cfg_attr(
feature = "serde",
derive(Serialize, Deserialize),
serde(crate = "serde_crate", transparent)
)]
pub struct ContractId(
#[from]
#[from([u8; 32])]
Bytes32,
);
impl PartialEq<OpId> for ContractId {
fn eq(&self, other: &OpId) -> bool { self.to_byte_array() == other.to_byte_array() }
}
impl PartialEq<ContractId> for OpId {
fn eq(&self, other: &ContractId) -> bool { self.to_byte_array() == other.to_byte_array() }
}
impl ContractId {
pub fn copy_from_slice(slice: impl AsRef<[u8]>) -> Result<Self, FromSliceError> {
Bytes32::copy_from_slice(slice).map(Self)
}
}
impl ToBaid58<32> for ContractId {
const HRI: &'static str = "rgb";
const CHUNKING: Option<Chunking> = CHUNKING_32CHECKSUM;
fn to_baid58_payload(&self) -> [u8; 32] { self.to_byte_array() }
fn to_baid58_string(&self) -> String { self.to_string() }
}
impl FromBaid58<32> for ContractId {}
impl Display for ContractId {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
if f.alternate() {
write!(f, "{::^}", self.to_baid58())
} else {
write!(f, "{::^.3}", self.to_baid58())
}
}
}
impl FromStr for ContractId {
type Err = Baid58ParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::from_baid58_maybe_chunked_str(s, ':', '#')
}
}
impl From<mpc::ProtocolId> for ContractId {
fn from(id: mpc::ProtocolId) -> Self { ContractId(id.into_inner()) }
}
impl From<ContractId> for mpc::ProtocolId {
fn from(id: ContractId) -> Self { mpc::ProtocolId::from_inner(id.into_inner()) }
}
pub trait Operation {
fn op_type(&self) -> OpType;
fn full_type(&self) -> OpFullType;
fn id(&self) -> OpId;
fn contract_id(&self) -> ContractId;
fn transition_type(&self) -> Option<TransitionType>;
fn extension_type(&self) -> Option<ExtensionType>;
fn metadata(&self) -> &SmallBlob;
fn globals(&self) -> &GlobalState;
fn valencies(&self) -> &Valencies;
fn assignments(&self) -> AssignmentsRef;
fn assignments_by_type(&self, t: AssignmentType) -> Option<TypedAssigns<GraphSeal>>;
fn inputs(&self) -> Inputs;
}
#[derive(Clone, PartialEq, Eq, Hash, Debug)]
#[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)]
#[strict_type(lib = LIB_NAME_RGB)]
#[derive(CommitEncode)]
#[cfg_attr(
feature = "serde",
derive(Serialize, Deserialize),
serde(crate = "serde_crate", rename_all = "camelCase")
)]
pub struct Genesis {
pub ffv: Ffv,
pub schema_id: SchemaId,
pub testnet: bool,
pub alt_layers1: AltLayer1Set,
pub metadata: SmallBlob,
pub globals: GlobalState,
pub assignments: Assignments<GenesisSeal>,
pub valencies: Valencies,
}
impl StrictSerialize for Genesis {}
impl StrictDeserialize for Genesis {}
#[derive(Clone, PartialEq, Eq, Hash, Debug)]
#[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)]
#[strict_type(lib = LIB_NAME_RGB)]
#[derive(CommitEncode)]
#[cfg_attr(
feature = "serde",
derive(Serialize, Deserialize),
serde(crate = "serde_crate", rename_all = "camelCase")
)]
pub struct Extension {
pub ffv: Ffv,
pub contract_id: ContractId,
pub extension_type: ExtensionType,
pub metadata: SmallBlob,
pub globals: GlobalState,
pub assignments: Assignments<GenesisSeal>,
pub redeemed: Redeemed,
pub valencies: Valencies,
}
impl StrictSerialize for Extension {}
impl StrictDeserialize for Extension {}
#[derive(Clone, PartialEq, Eq, Hash, Debug)]
#[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)]
#[strict_type(lib = LIB_NAME_RGB)]
#[derive(CommitEncode)]
#[cfg_attr(
feature = "serde",
derive(Serialize, Deserialize),
serde(crate = "serde_crate", rename_all = "camelCase")
)]
pub struct Transition {
pub ffv: Ffv,
pub contract_id: ContractId,
pub transition_type: TransitionType,
pub metadata: SmallBlob,
pub globals: GlobalState,
pub inputs: Inputs,
pub assignments: Assignments<GraphSeal>,
pub valencies: Valencies,
}
impl StrictSerialize for Transition {}
impl StrictDeserialize for Transition {}
impl Conceal for Genesis {
type Concealed = Genesis;
fn conceal(&self) -> Self::Concealed {
let mut concealed = self.clone();
concealed
.assignments
.keyed_values_mut()
.for_each(|(_, a)| *a = a.conceal());
concealed
}
}
impl CommitmentId for Genesis {
const TAG: [u8; 32] = *b"urn:lnpbp:rgb:genesis:v02#202304";
type Id = ContractId;
}
impl Conceal for Transition {
type Concealed = Transition;
fn conceal(&self) -> Self::Concealed {
let mut concealed = self.clone();
concealed
.assignments
.keyed_values_mut()
.for_each(|(_, a)| *a = a.conceal());
concealed
}
}
impl CommitmentId for Transition {
const TAG: [u8; 32] = *b"urn:lnpbp:rgb:transition:v02#23B";
type Id = OpId;
}
impl Conceal for Extension {
type Concealed = Extension;
fn conceal(&self) -> Self::Concealed {
let mut concealed = self.clone();
concealed
.assignments
.keyed_values_mut()
.for_each(|(_, a)| *a = a.conceal());
concealed
}
}
impl CommitmentId for Extension {
const TAG: [u8; 32] = *b"urn:lnpbp:rgb:extension:v02#2304";
type Id = OpId;
}
impl Transition {
pub fn prev_state(&self) -> &Inputs { &self.inputs }
}
impl Extension {
pub fn redeemed(&self) -> &Redeemed { &self.redeemed }
}
impl Operation for Genesis {
#[inline]
fn op_type(&self) -> OpType { OpType::Genesis }
#[inline]
fn full_type(&self) -> OpFullType { OpFullType::Genesis }
#[inline]
fn id(&self) -> OpId { OpId(self.commitment_id().into_inner()) }
#[inline]
fn contract_id(&self) -> ContractId { ContractId::from_inner(self.id().into_inner()) }
#[inline]
fn transition_type(&self) -> Option<TransitionType> { None }
#[inline]
fn extension_type(&self) -> Option<ExtensionType> { None }
#[inline]
fn metadata(&self) -> &SmallBlob { &self.metadata }
#[inline]
fn globals(&self) -> &GlobalState { &self.globals }
#[inline]
fn valencies(&self) -> &Valencies { &self.valencies }
#[inline]
fn assignments(&self) -> AssignmentsRef { (&self.assignments).into() }
#[inline]
fn assignments_by_type(&self, t: AssignmentType) -> Option<TypedAssigns<GraphSeal>> {
self.assignments
.get(&t)
.map(TypedAssigns::transmutate_seals)
}
#[inline]
fn inputs(&self) -> Inputs { empty!() }
}
impl Operation for Extension {
#[inline]
fn op_type(&self) -> OpType { OpType::StateExtension }
#[inline]
fn full_type(&self) -> OpFullType { OpFullType::StateExtension(self.extension_type) }
#[inline]
fn id(&self) -> OpId { self.commitment_id() }
#[inline]
fn contract_id(&self) -> ContractId { self.contract_id }
#[inline]
fn transition_type(&self) -> Option<TransitionType> { None }
#[inline]
fn extension_type(&self) -> Option<ExtensionType> { Some(self.extension_type) }
#[inline]
fn metadata(&self) -> &SmallBlob { &self.metadata }
#[inline]
fn globals(&self) -> &GlobalState { &self.globals }
#[inline]
fn valencies(&self) -> &Valencies { &self.valencies }
#[inline]
fn assignments(&self) -> AssignmentsRef { (&self.assignments).into() }
#[inline]
fn assignments_by_type(&self, t: AssignmentType) -> Option<TypedAssigns<GraphSeal>> {
self.assignments
.get(&t)
.map(TypedAssigns::transmutate_seals)
}
#[inline]
fn inputs(&self) -> Inputs { empty!() }
}
impl Operation for Transition {
#[inline]
fn op_type(&self) -> OpType { OpType::StateTransition }
#[inline]
fn full_type(&self) -> OpFullType { OpFullType::StateTransition(self.transition_type) }
#[inline]
fn id(&self) -> OpId { self.commitment_id() }
#[inline]
fn contract_id(&self) -> ContractId { self.contract_id }
#[inline]
fn transition_type(&self) -> Option<TransitionType> { Some(self.transition_type) }
#[inline]
fn extension_type(&self) -> Option<ExtensionType> { None }
#[inline]
fn metadata(&self) -> &SmallBlob { &self.metadata }
#[inline]
fn globals(&self) -> &GlobalState { &self.globals }
#[inline]
fn valencies(&self) -> &Valencies { &self.valencies }
#[inline]
fn assignments(&self) -> AssignmentsRef { (&self.assignments).into() }
#[inline]
fn assignments_by_type(&self, t: AssignmentType) -> Option<TypedAssigns<GraphSeal>> {
self.assignments.get(&t).cloned()
}
fn inputs(&self) -> Inputs { self.inputs.clone() }
}
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, From)]
pub enum OpRef<'op> {
#[from]
Genesis(&'op Genesis),
#[from]
Transition(&'op Transition),
#[from]
Extension(&'op Extension),
}
impl<'op> Operation for OpRef<'op> {
fn op_type(&self) -> OpType {
match self {
OpRef::Genesis(op) => op.op_type(),
OpRef::Transition(op) => op.op_type(),
OpRef::Extension(op) => op.op_type(),
}
}
fn full_type(&self) -> OpFullType {
match self {
OpRef::Genesis(op) => op.full_type(),
OpRef::Transition(op) => op.full_type(),
OpRef::Extension(op) => op.full_type(),
}
}
fn id(&self) -> OpId {
match self {
OpRef::Genesis(op) => op.id(),
OpRef::Transition(op) => op.id(),
OpRef::Extension(op) => op.id(),
}
}
fn contract_id(&self) -> ContractId {
match self {
OpRef::Genesis(op) => op.contract_id(),
OpRef::Transition(op) => op.contract_id(),
OpRef::Extension(op) => op.contract_id(),
}
}
fn transition_type(&self) -> Option<TransitionType> {
match self {
OpRef::Genesis(op) => op.transition_type(),
OpRef::Transition(op) => op.transition_type(),
OpRef::Extension(op) => op.transition_type(),
}
}
fn extension_type(&self) -> Option<ExtensionType> {
match self {
OpRef::Genesis(op) => op.extension_type(),
OpRef::Transition(op) => op.extension_type(),
OpRef::Extension(op) => op.extension_type(),
}
}
fn metadata(&self) -> &SmallBlob {
match self {
OpRef::Genesis(op) => op.metadata(),
OpRef::Transition(op) => op.metadata(),
OpRef::Extension(op) => op.metadata(),
}
}
fn globals(&self) -> &GlobalState {
match self {
OpRef::Genesis(op) => op.globals(),
OpRef::Transition(op) => op.globals(),
OpRef::Extension(op) => op.globals(),
}
}
fn valencies(&self) -> &Valencies {
match self {
OpRef::Genesis(op) => op.valencies(),
OpRef::Transition(op) => op.valencies(),
OpRef::Extension(op) => op.valencies(),
}
}
fn assignments(&self) -> AssignmentsRef<'op> {
match self {
OpRef::Genesis(op) => (&op.assignments).into(),
OpRef::Transition(op) => (&op.assignments).into(),
OpRef::Extension(op) => (&op.assignments).into(),
}
}
fn assignments_by_type(&self, t: AssignmentType) -> Option<TypedAssigns<GraphSeal>> {
match self {
OpRef::Genesis(op) => op.assignments_by_type(t),
OpRef::Transition(op) => op.assignments_by_type(t),
OpRef::Extension(op) => op.assignments_by_type(t),
}
}
fn inputs(&self) -> Inputs {
match self {
OpRef::Genesis(op) => op.inputs(),
OpRef::Transition(op) => op.inputs(),
OpRef::Extension(op) => op.inputs(),
}
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn contract_id_display() {
const ID: &str = "rgb:pkXwpsb-aemTWhtSg-VDGF25hEi-jtTAnPjzh-B63ZwSehE-WvfhF9";
let id = ContractId::from_byte_array([0x6c; 32]);
assert_eq!(ID.len(), 58);
assert_eq!(ID.replace('-', ""), format!("{id:#}"));
assert_eq!(ID, id.to_string());
assert_eq!(ID, id.to_baid58_string());
}
#[test]
fn contract_id_from_str() {
let id = ContractId::from_byte_array([0x6c; 32]);
assert_eq!(
Ok(id),
ContractId::from_str("rgb:pkXwpsb-aemTWhtSg-VDGF25hEi-jtTAnPjzh-B63ZwSehE-WvfhF9")
);
assert_eq!(
Ok(id),
ContractId::from_str("pkXwpsb-aemTWhtSg-VDGF25hEi-jtTAnPjzh-B63ZwSehE-WvfhF9")
);
assert_eq!(
Ok(id),
ContractId::from_str("rgb:pkXwpsbaemTWhtSgVDGF25hEijtTAnPjzhB63ZwSehEWvfhF9")
);
assert_eq!(
Ok(id),
ContractId::from_str("pkXwpsbaemTWhtSgVDGF25hEijtTAnPjzhB63ZwSehEWvfhF9")
);
assert!(
ContractId::from_str("rgb:pkXwpsb-aemTWhtSg-VDGF25hEi-jtTAnPjzh-B63ZwSeh-EWvfhF9")
.is_err()
);
assert!(
ContractId::from_str("rgb:pkXwpsb-aemTWhtSg-VDGF25hEi-jtTAnPjzh-B63ZwSehEWvfhF9")
.is_err()
);
}
}