use std::cmp::Ordering;
use std::fmt::Debug;
use std::hash::Hash;
use std::num::ParseIntError;
use std::ops::{Deref, DerefMut};
use std::str::FromStr;
use amplify::confinement::{LargeOrdMap, LargeOrdSet, SmallVec, TinyOrdMap};
use amplify::hex;
use bp::Outpoint;
use strict_encoding::{StrictDecode, StrictDumb, StrictEncode};
use crate::{
Assign, AssignmentType, Assignments, AssignmentsRef, ContractId, ExposedSeal, ExposedState,
Extension, Genesis, GlobalStateType, OpId, Operation, RevealedAttach, RevealedData,
RevealedValue, SchemaId, SealDefinition, SubSchema, Transition, TypedAssigns, VoidState,
WitnessAnchor, WitnessId, LIB_NAME_RGB,
};
#[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, Display)]
#[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)]
#[strict_type(lib = LIB_NAME_RGB, tags = custom, dumb = Self::Bitcoin(strict_dumb!()))]
#[cfg_attr(
feature = "serde",
derive(Serialize, Deserialize),
serde(crate = "serde_crate", rename_all = "camelCase")
)]
#[display(inner)]
#[non_exhaustive]
pub enum Output {
#[strict_type(tag = 0x00)]
Bitcoin(Outpoint),
#[strict_type(tag = 0x01)]
Liquid(Outpoint),
}
#[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("{op}/{ty}/{no}")]
pub struct Opout {
pub op: OpId,
pub ty: AssignmentType,
pub no: u16,
}
impl Opout {
pub fn new(op: OpId, ty: AssignmentType, no: u16) -> Opout { Opout { op, ty, no } }
}
#[derive(Clone, Eq, PartialEq, Debug, Display, Error, From)]
#[display(inner)]
pub enum OpoutParseError {
#[from]
InvalidNodeId(hex::Error),
InvalidType(ParseIntError),
InvalidOutputNo(ParseIntError),
#[display(doc_comments)]
WrongFormat(String),
}
impl FromStr for Opout {
type Err = OpoutParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut split = s.split('/');
match (split.next(), split.next(), split.next(), split.next()) {
(Some(op), Some(ty), Some(no), None) => Ok(Opout {
op: op.parse()?,
ty: ty.parse().map_err(OpoutParseError::InvalidType)?,
no: no.parse().map_err(OpoutParseError::InvalidOutputNo)?,
}),
_ => Err(OpoutParseError::WrongFormat(s.to_owned())),
}
}
}
#[derive(Clone, Eq, Debug)]
#[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")
)]
pub struct OutputAssignment<State: ExposedState> {
pub opout: Opout,
pub output: Output,
pub state: State,
pub witness: Option<WitnessId>,
}
impl<State: ExposedState> PartialEq for OutputAssignment<State> {
fn eq(&self, other: &Self) -> bool {
if self.opout == other.opout &&
(self.output != other.output ||
self.witness != other.witness ||
self.state != other.state)
{
panic!(
"RGB was provided with an updated operation using different witness transaction. \
This may happen for instance when some ephemeral state (like a commitment or \
HTLC transactions in the lightning channels) is added to the stash.\nThis error \
means the software uses RGB stash in an invalid way and has business logic bug \
which has to be fixed.\nOperation in stash: {:?}\nNew operation: {:?}\n",
self, other
)
}
self.opout == other.opout
}
}
impl<State: ExposedState> PartialOrd for OutputAssignment<State> {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> { Some(self.cmp(other)) }
}
impl<State: ExposedState> Ord for OutputAssignment<State> {
fn cmp(&self, other: &Self) -> Ordering {
if self == other {
return Ordering::Equal;
}
self.opout.cmp(&other.opout)
}
}
impl<State: ExposedState> OutputAssignment<State> {
pub fn with_witness<Seal: ExposedSeal>(
seal: SealDefinition<Seal>,
witness_id: WitnessId,
state: State,
opid: OpId,
ty: AssignmentType,
no: u16,
) -> Self {
OutputAssignment {
opout: Opout::new(opid, ty, no),
output: seal.output_or_witness(witness_id).expect(
"processing contract from unverified/invalid stash: witness seal chain doesn't \
match anchor's chain",
),
state,
witness: Some(witness_id),
}
}
pub fn with_no_witness<Seal: ExposedSeal>(
seal: SealDefinition<Seal>,
state: State,
opid: OpId,
ty: AssignmentType,
no: u16,
) -> Self {
OutputAssignment {
opout: Opout::new(opid, ty, no),
output: seal.output().expect(
"processing contract from unverified/invalid stash: seal must have txid \
information since it comes from genesis or extension",
),
state,
witness: None,
}
}
}
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
#[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")
)]
pub struct GlobalOrd {
pub witness_anchor: Option<WitnessAnchor>,
pub idx: u16,
}
impl PartialOrd for GlobalOrd {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> { Some(self.cmp(other)) }
}
impl Ord for GlobalOrd {
fn cmp(&self, other: &Self) -> Ordering {
if self == other {
return Ordering::Equal;
}
match (self.witness_anchor, &other.witness_anchor) {
(None, None) => self.idx.cmp(&other.idx),
(None, Some(_)) => Ordering::Less,
(Some(_), None) => Ordering::Greater,
(Some(ord1), Some(ord2)) if ord1 == *ord2 => self.idx.cmp(&other.idx),
(Some(ord1), Some(ord2)) => ord1.cmp(ord2),
}
}
}
impl GlobalOrd {
pub fn with_anchor(ord_txid: WitnessAnchor, idx: u16) -> Self {
GlobalOrd {
witness_anchor: Some(ord_txid),
idx,
}
}
pub fn genesis(idx: u16) -> Self {
GlobalOrd {
witness_anchor: None,
idx,
}
}
}
pub type RightsOutput = OutputAssignment<VoidState>;
pub type FungibleOutput = OutputAssignment<RevealedValue>;
pub type DataOutput = OutputAssignment<RevealedData>;
pub type AttachOutput = OutputAssignment<RevealedAttach>;
#[derive(Getters, Clone, Eq, PartialEq, Debug)]
#[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")
)]
pub struct ContractHistory {
#[getter(as_copy)]
schema_id: SchemaId,
#[getter(as_copy)]
root_schema_id: Option<SchemaId>,
#[getter(as_copy)]
contract_id: ContractId,
#[getter(skip)]
global: TinyOrdMap<GlobalStateType, LargeOrdMap<GlobalOrd, RevealedData>>,
rights: LargeOrdSet<RightsOutput>,
fungibles: LargeOrdSet<FungibleOutput>,
data: LargeOrdSet<DataOutput>,
attach: LargeOrdSet<AttachOutput>,
}
impl ContractHistory {
pub fn with(
schema_id: SchemaId,
root_schema_id: Option<SchemaId>,
contract_id: ContractId,
genesis: &Genesis,
) -> Self {
let mut state = ContractHistory {
schema_id,
root_schema_id,
contract_id,
global: empty!(),
rights: empty!(),
fungibles: empty!(),
data: empty!(),
attach: empty!(),
};
state.update_genesis(genesis);
state
}
pub fn update_genesis(&mut self, genesis: &Genesis) { self.add_operation(genesis, None); }
pub fn add_transition(&mut self, transition: &Transition, witness_anchor: WitnessAnchor) {
self.add_operation(transition, Some(witness_anchor));
}
pub fn add_extension(&mut self, extension: &Extension, witness_anchor: WitnessAnchor) {
self.add_operation(extension, Some(witness_anchor));
}
fn add_operation(&mut self, op: &impl Operation, witness_anchor: Option<WitnessAnchor>) {
let opid = op.id();
for (ty, state) in op.globals() {
let map = match self.global.get_mut(ty) {
Some(map) => map,
None => {
self.global.insert(*ty, empty!()).expect(
"consensus rules violation: do not add to the state consignments without \
validation against the schema",
);
self.global.get_mut(ty).expect("just inserted")
}
};
for (idx, s) in state.iter().enumerate() {
let idx = idx as u16;
let glob_idx = GlobalOrd {
witness_anchor,
idx,
};
map.insert(glob_idx, s.clone())
.expect("contract global state exceeded 2^32 items, which is unrealistic");
}
}
let witness_id = witness_anchor.map(|wa| wa.witness_id);
match op.assignments() {
AssignmentsRef::Genesis(assignments) => {
self.add_assignments(witness_id, opid, assignments)
}
AssignmentsRef::Graph(assignments) => {
self.add_assignments(witness_id, opid, assignments)
}
}
}
fn add_assignments<Seal: ExposedSeal>(
&mut self,
witness_id: Option<WitnessId>,
opid: OpId,
assignments: &Assignments<Seal>,
) {
fn process<State: ExposedState, Seal: ExposedSeal>(
contract_state: &mut LargeOrdSet<OutputAssignment<State>>,
assignments: &[Assign<State, Seal>],
opid: OpId,
ty: AssignmentType,
witness_id: Option<WitnessId>,
) {
for (no, seal, state) in assignments
.iter()
.enumerate()
.filter_map(|(n, a)| a.to_revealed().map(|(seal, state)| (n, seal, state)))
{
let assigned_state = match witness_id {
Some(witness_id) => {
OutputAssignment::with_witness(seal, witness_id, state, opid, ty, no as u16)
}
None => OutputAssignment::with_no_witness(seal, state, opid, ty, no as u16),
};
contract_state
.push(assigned_state)
.expect("contract state exceeded 2^32 items, which is unrealistic");
}
}
for (ty, assignments) in assignments.iter() {
match assignments {
TypedAssigns::Declarative(assignments) => {
process(&mut self.rights, assignments, opid, *ty, witness_id)
}
TypedAssigns::Fungible(assignments) => {
process(&mut self.fungibles, assignments, opid, *ty, witness_id)
}
TypedAssigns::Structured(assignments) => {
process(&mut self.data, assignments, opid, *ty, witness_id)
}
TypedAssigns::Attachment(assignments) => {
process(&mut self.attach, assignments, opid, *ty, witness_id)
}
}
}
}
}
#[derive(Clone, Eq, PartialEq, Debug)]
#[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")
)]
pub struct ContractState {
pub schema: SubSchema,
pub history: ContractHistory,
}
impl Deref for ContractState {
type Target = ContractHistory;
fn deref(&self) -> &Self::Target { &self.history }
}
impl DerefMut for ContractState {
fn deref_mut(&mut self) -> &mut Self::Target { &mut self.history }
}
impl ContractState {
pub unsafe fn global_unchecked(&self, state_type: GlobalStateType) -> SmallVec<&RevealedData> {
let schema = self
.schema
.global_types
.get(&state_type)
.expect("global type is not in the schema");
let Some(state) = self.global.get(&state_type) else {
return SmallVec::new();
};
let iter = state.values().take(schema.max_items as usize);
SmallVec::try_from_iter(iter).expect("same size as previous confined collection")
}
}