use std::borrow::Borrow;
use std::cell::RefCell;
use std::cmp::Ordering;
use std::fmt::Debug;
use std::num::NonZeroU32;
use std::rc::Rc;
use amplify::confinement;
use amplify::num::u24;
use bp::seals::txout::{CloseMethod, ExplicitSeal, VerifyError, Witness};
use bp::{dbc, Tx, Txid};
use commit_verify::mpc;
use single_use_seals::SealWitness;
use strict_encoding::{StrictDecode, StrictDumb, StrictEncode};
use crate::{
AssetTags, AssignmentType, Assignments, AssignmentsRef, AttachState, ContractId, DataState,
ExposedSeal, Extension, ExtensionType, FungibleState, Genesis, GlobalState, GlobalStateType,
GraphSeal, Impossible, Inputs, Metadata, OpFullType, OpId, OpType, Operation, Transition,
TransitionType, TxoSeal, TypedAssigns, Valencies, XChain, XOutpoint, XOutputSeal,
LIB_NAME_RGB_LOGIC,
};
pub type XWitnessId = XChain<Txid>;
pub type XWitnessTx<X = Impossible> = XChain<Tx, X>;
impl XWitnessTx {
pub fn witness_id(&self) -> XWitnessId {
match self {
Self::Bitcoin(tx) => XWitnessId::Bitcoin(tx.txid()),
Self::Liquid(tx) => XWitnessId::Liquid(tx.txid()),
Self::Other(_) => unreachable!(),
}
}
}
impl<Dbc: dbc::Proof> XChain<Witness<Dbc>> {
pub fn witness_id(&self) -> XWitnessId {
match self {
Self::Bitcoin(w) => XWitnessId::Bitcoin(w.txid),
Self::Liquid(w) => XWitnessId::Liquid(w.txid),
Self::Other(_) => unreachable!(),
}
}
}
impl<Dbc: dbc::Proof, Seal: TxoSeal> SealWitness<Seal> for XChain<Witness<Dbc>> {
type Message = mpc::Commitment;
type Error = VerifyError<Dbc::Error>;
fn verify_seal(&self, seal: &Seal, msg: &Self::Message) -> Result<(), Self::Error> {
match self {
Self::Bitcoin(witness) | Self::Liquid(witness) => witness.verify_seal(seal, msg),
Self::Other(_) => unreachable!(),
}
}
fn verify_many_seals<'seal>(
&self,
seals: impl IntoIterator<Item = &'seal Seal>,
msg: &Self::Message,
) -> Result<(), Self::Error>
where
Seal: 'seal,
{
match self {
Self::Bitcoin(witness) | Self::Liquid(witness) => witness.verify_many_seals(seals, msg),
Self::Other(_) => unreachable!(),
}
}
}
impl<U: ExposedSeal> XChain<U> {
pub fn method(self) -> CloseMethod
where U: TxoSeal {
match self {
XChain::Bitcoin(seal) => seal.method(),
XChain::Liquid(seal) => seal.method(),
XChain::Other(_) => unreachable!(),
}
}
#[inline]
pub fn to_output_seal(self) -> Option<XOutputSeal>
where U: TxoSeal {
Some(match self {
XChain::Bitcoin(seal) => {
let outpoint = seal.outpoint()?;
XChain::Bitcoin(ExplicitSeal::new(seal.method(), outpoint))
}
XChain::Liquid(seal) => {
let outpoint = seal.outpoint()?;
XChain::Liquid(ExplicitSeal::new(seal.method(), outpoint))
}
XChain::Other(_) => unreachable!(),
})
}
pub fn try_to_output_seal(self, witness_id: XWitnessId) -> Result<XOutputSeal, Self>
where U: TxoSeal {
self.to_output_seal()
.or(match (self, witness_id) {
(XChain::Bitcoin(seal), XWitnessId::Bitcoin(txid)) => {
Some(XChain::Bitcoin(ExplicitSeal::new(seal.method(), seal.outpoint_or(txid))))
}
(XChain::Liquid(seal), XWitnessId::Liquid(txid)) => {
Some(XChain::Liquid(ExplicitSeal::new(seal.method(), seal.outpoint_or(txid))))
}
_ => None,
})
.ok_or(self)
}
}
#[derive(Copy, Clone, PartialEq, Eq, Debug, From)]
pub enum OrdOpRef<'op> {
#[from]
Genesis(&'op Genesis),
Transition(&'op Transition, XWitnessId, WitnessOrd),
Extension(&'op Extension, XWitnessId, WitnessOrd),
}
impl<'op> PartialOrd for OrdOpRef<'op> {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> { Some(self.cmp(other)) }
}
impl<'op> Ord for OrdOpRef<'op> {
fn cmp(&self, other: &Self) -> Ordering { self.op_ord().cmp(&other.op_ord()) }
}
impl<'op> OrdOpRef<'op> {
pub fn witness_id(&self) -> Option<XWitnessId> {
match self {
OrdOpRef::Genesis(_) => None,
OrdOpRef::Transition(_, witness_id, ..) | OrdOpRef::Extension(_, witness_id, ..) => {
Some(*witness_id)
}
}
}
pub fn op_ord(&self) -> OpOrd {
match self {
OrdOpRef::Genesis(_) => OpOrd::Genesis,
OrdOpRef::Transition(op, _, witness_ord) => OpOrd::Transition {
witness: *witness_ord,
nonce: op.nonce,
opid: op.id(),
},
OrdOpRef::Extension(op, _, witness_ord) => OpOrd::Extension {
witness: *witness_ord,
nonce: op.nonce,
opid: op.id(),
},
}
}
}
impl<'op> Operation for OrdOpRef<'op> {
fn op_type(&self) -> OpType {
match self {
OrdOpRef::Genesis(op) => op.op_type(),
OrdOpRef::Transition(op, ..) => op.op_type(),
OrdOpRef::Extension(op, ..) => op.op_type(),
}
}
fn full_type(&self) -> OpFullType {
match self {
OrdOpRef::Genesis(op) => op.full_type(),
OrdOpRef::Transition(op, ..) => op.full_type(),
OrdOpRef::Extension(op, ..) => op.full_type(),
}
}
fn id(&self) -> OpId {
match self {
OrdOpRef::Genesis(op) => op.id(),
OrdOpRef::Transition(op, ..) => op.id(),
OrdOpRef::Extension(op, ..) => op.id(),
}
}
fn contract_id(&self) -> ContractId {
match self {
OrdOpRef::Genesis(op) => op.contract_id(),
OrdOpRef::Transition(op, ..) => op.contract_id(),
OrdOpRef::Extension(op, ..) => op.contract_id(),
}
}
fn nonce(&self) -> u8 {
match self {
OrdOpRef::Genesis(op) => op.nonce(),
OrdOpRef::Transition(op, ..) => op.nonce(),
OrdOpRef::Extension(op, ..) => op.nonce(),
}
}
fn transition_type(&self) -> Option<TransitionType> {
match self {
OrdOpRef::Genesis(op) => op.transition_type(),
OrdOpRef::Transition(op, ..) => op.transition_type(),
OrdOpRef::Extension(op, ..) => op.transition_type(),
}
}
fn extension_type(&self) -> Option<ExtensionType> {
match self {
OrdOpRef::Genesis(op) => op.extension_type(),
OrdOpRef::Transition(op, ..) => op.extension_type(),
OrdOpRef::Extension(op, ..) => op.extension_type(),
}
}
fn metadata(&self) -> &Metadata {
match self {
OrdOpRef::Genesis(op) => op.metadata(),
OrdOpRef::Transition(op, ..) => op.metadata(),
OrdOpRef::Extension(op, ..) => op.metadata(),
}
}
fn globals(&self) -> &GlobalState {
match self {
OrdOpRef::Genesis(op) => op.globals(),
OrdOpRef::Transition(op, ..) => op.globals(),
OrdOpRef::Extension(op, ..) => op.globals(),
}
}
fn valencies(&self) -> &Valencies {
match self {
OrdOpRef::Genesis(op) => op.valencies(),
OrdOpRef::Transition(op, ..) => op.valencies(),
OrdOpRef::Extension(op, ..) => op.valencies(),
}
}
fn assignments(&self) -> AssignmentsRef<'op> {
match self {
OrdOpRef::Genesis(op) => (&op.assignments).into(),
OrdOpRef::Transition(op, ..) => (&op.assignments).into(),
OrdOpRef::Extension(op, ..) => (&op.assignments).into(),
}
}
fn assignments_by_type(&self, t: AssignmentType) -> Option<TypedAssigns<GraphSeal>> {
match self {
OrdOpRef::Genesis(op) => op.assignments_by_type(t),
OrdOpRef::Transition(op, ..) => op.assignments_by_type(t),
OrdOpRef::Extension(op, ..) => op.assignments_by_type(t),
}
}
fn inputs(&self) -> Inputs {
match self {
OrdOpRef::Genesis(op) => op.inputs(),
OrdOpRef::Transition(op, ..) => op.inputs(),
OrdOpRef::Extension(op, ..) => op.inputs(),
}
}
}
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, Display)]
#[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)]
#[strict_type(lib = LIB_NAME_RGB_LOGIC)]
#[cfg_attr(
feature = "serde",
derive(Serialize, Deserialize),
serde(crate = "serde_crate", rename_all = "camelCase")
)]
#[display("{height}@{timestamp}")]
pub struct WitnessPos {
height: u32,
timestamp: i64,
}
impl WitnessPos {
pub fn new(height: u32, timestamp: i64) -> Option<Self> {
if height == 0 || timestamp < 1231006505 {
return None;
}
Some(WitnessPos { height, timestamp })
}
pub fn height(&self) -> NonZeroU32 { NonZeroU32::new(self.height).expect("invariant") }
}
impl PartialOrd for WitnessPos {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> { Some(self.cmp(other)) }
}
impl Ord for WitnessPos {
fn cmp(&self, other: &Self) -> Ordering {
assert!(self.timestamp > 0);
assert!(other.timestamp > 0);
self.timestamp.cmp(&other.timestamp)
}
}
#[derive(Copy, Clone, PartialOrd, Ord, PartialEq, Eq, Hash, Debug, Display, From)]
#[display(lowercase)]
#[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)]
#[strict_type(lib = LIB_NAME_RGB_LOGIC, tags = order)]
#[cfg_attr(
feature = "serde",
derive(Serialize, Deserialize),
serde(crate = "serde_crate", rename_all = "camelCase")
)]
pub enum WitnessOrd {
#[strict_type(dumb)]
Archived,
#[from]
#[display(inner)]
Mined(WitnessPos),
Tentative,
}
impl WitnessOrd {
#[inline]
pub fn is_valid(self) -> bool { self != Self::Archived }
}
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
#[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)]
#[strict_type(lib = LIB_NAME_RGB_LOGIC, tags = custom)]
#[cfg_attr(
feature = "serde",
derive(Serialize, Deserialize),
serde(crate = "serde_crate", rename_all = "camelCase")
)]
pub enum OpOrd {
#[strict_type(tag = 0x00, dumb)]
Genesis,
#[strict_type(tag = 0x01)]
Extension {
witness: WitnessOrd,
nonce: u8,
opid: OpId,
},
#[strict_type(tag = 0xFF)]
Transition {
witness: WitnessOrd,
nonce: u8,
opid: OpId,
},
}
impl OpOrd {
#[inline]
pub fn is_archived(&self) -> bool {
matches!(
self,
Self::Extension {
witness: WitnessOrd::Archived,
..
} | Self::Transition {
witness: WitnessOrd::Archived,
..
}
)
}
}
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
#[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)]
#[strict_type(lib = LIB_NAME_RGB_LOGIC)]
#[cfg_attr(
feature = "serde",
derive(Serialize, Deserialize),
serde(crate = "serde_crate", rename_all = "camelCase")
)]
pub struct GlobalOrd {
pub op_ord: OpOrd,
pub idx: u16,
}
impl GlobalOrd {
pub fn genesis(idx: u16) -> Self {
Self {
op_ord: OpOrd::Genesis,
idx,
}
}
pub fn transition(opid: OpId, idx: u16, nonce: u8, witness: WitnessOrd) -> Self {
Self {
op_ord: OpOrd::Transition {
witness,
nonce,
opid,
},
idx,
}
}
pub fn extension(opid: OpId, idx: u16, nonce: u8, witness: WitnessOrd) -> Self {
Self {
op_ord: OpOrd::Extension {
witness,
nonce,
opid,
},
idx,
}
}
}
pub trait GlobalStateIter {
type Data: Borrow<DataState>;
fn size(&mut self) -> u24;
fn prev(&mut self) -> Option<(GlobalOrd, Self::Data)>;
fn last(&mut self) -> Option<(GlobalOrd, Self::Data)>;
fn reset(&mut self, depth: u24);
}
impl<I: GlobalStateIter> GlobalStateIter for &mut I {
type Data = I::Data;
#[inline]
fn size(&mut self) -> u24 { GlobalStateIter::size(*self) }
#[inline]
fn prev(&mut self) -> Option<(GlobalOrd, Self::Data)> { (*self).prev() }
#[inline]
fn last(&mut self) -> Option<(GlobalOrd, Self::Data)> { (*self).last() }
#[inline]
fn reset(&mut self, depth: u24) { (*self).reset(depth) }
}
pub struct GlobalContractState<I: GlobalStateIter> {
checked_depth: u24,
last_ord: Option<GlobalOrd>,
iter: I,
}
impl<I: GlobalStateIter> GlobalContractState<I> {
#[inline]
pub fn new(iter: I) -> Self {
Self {
iter,
checked_depth: u24::ONE,
last_ord: None,
}
}
#[inline]
pub fn size(&mut self) -> u24 { self.iter.size() }
fn prev_checked(&mut self) -> Option<(GlobalOrd, I::Data)> {
let (ord, item) = self.iter.prev()?;
if self.last_ord.map(|last| ord <= last).unwrap_or_default() {
panic!(
"global contract state iterator has invalid implementation: it fails to order \
global state according to the consensus ordering"
);
}
if ord.op_ord.is_archived() {
panic!("invalid GlobalStateIter implementation returning WitnessOrd::Archived")
}
self.checked_depth += u24::ONE;
self.last_ord = Some(ord);
Some((ord, item))
}
pub fn nth(&mut self, depth: u24) -> Option<impl Borrow<DataState> + '_> {
if depth >= self.iter.size() {
return None;
}
if depth >= self.checked_depth {
self.iter.reset(depth);
} else {
self.iter.reset(self.checked_depth);
let size = self.iter.size();
let to = (depth - self.checked_depth).to_u32();
for inc in 0..to {
if self.prev_checked().is_none() {
panic!(
"global contract state iterator has invalid implementation: it reports \
more global state items {size} than the contract has ({})",
self.checked_depth + inc
);
}
}
}
self.iter.last().map(|(_, item)| item)
}
}
impl<I: GlobalStateIter> Iterator for GlobalContractState<I> {
type Item = I::Data;
#[inline]
fn next(&mut self) -> Option<Self::Item> { Some(self.prev_checked()?.1) }
}
#[derive(Copy, Clone, Debug, Display, Error)]
#[display("unknown global state type {0} requested from the contract")]
pub struct UnknownGlobalStateType(pub GlobalStateType);
pub trait ContractStateAccess: Debug {
fn global(
&self,
ty: GlobalStateType,
) -> Result<GlobalContractState<impl GlobalStateIter>, UnknownGlobalStateType>;
fn rights(&self, outpoint: XOutpoint, ty: AssignmentType) -> u32;
fn fungible(
&self,
outpoint: XOutpoint,
ty: AssignmentType,
) -> impl DoubleEndedIterator<Item = FungibleState>;
fn data(
&self,
outpoint: XOutpoint,
ty: AssignmentType,
) -> impl DoubleEndedIterator<Item = impl Borrow<DataState>>;
fn attach(
&self,
outpoint: XOutpoint,
ty: AssignmentType,
) -> impl DoubleEndedIterator<Item = impl Borrow<AttachState>>;
}
pub trait ContractStateEvolve {
type Context<'ctx>;
fn init(context: Self::Context<'_>) -> Self;
fn evolve_state(&mut self, op: OrdOpRef) -> Result<(), confinement::Error>;
}
pub struct VmContext<'op, S: ContractStateAccess> {
pub contract_id: ContractId,
pub asset_tags: &'op AssetTags,
pub op_info: OpInfo<'op>,
pub contract_state: Rc<RefCell<S>>,
}
pub struct OpInfo<'op> {
pub id: OpId,
pub ty: OpFullType,
pub metadata: &'op Metadata,
pub prev_state: &'op Assignments<GraphSeal>,
pub owned_state: AssignmentsRef<'op>,
pub redeemed: &'op Valencies,
pub valencies: &'op Valencies,
pub global: &'op GlobalState,
}
impl<'op> OpInfo<'op> {
pub fn with(
id: OpId,
op: &'op OrdOpRef<'op>,
prev_state: &'op Assignments<GraphSeal>,
redeemed: &'op Valencies,
) -> Self {
OpInfo {
id,
ty: op.full_type(),
metadata: op.metadata(),
prev_state,
owned_state: op.assignments(),
redeemed,
valencies: op.valencies(),
global: op.globals(),
}
}
}