use std::borrow::Borrow;
use std::cell::RefCell;
use std::cmp::Ordering;
use std::collections::BTreeMap;
use std::fmt::{self, Debug, Display, Formatter};
use std::num::NonZeroU32;
use std::rc::Rc;
use bitcoin::{OutPoint as Outpoint, Txid};
use chrono::{MappedLocalTime, TimeZone, Utc};
use strict_encoding::{StrictDecode, StrictDumb, StrictEncode};
use crate::{
AssignmentType, AssignmentsRef, BundleId, ContractId, FungibleState, Genesis, GlobalState,
GlobalStateType, GraphSeal, Layer1, Metadata, OpFullType, OpId, Operation, RevealedData,
RevealedState, Transition, TransitionType, TypedAssigns, LIB_NAME_RGB_LOGIC,
};
pub type BlockHeight = NonZeroU32;
#[derive(Copy, Clone, PartialEq, Eq, Debug, From)]
pub enum OrdOpRef<'op> {
#[from]
Genesis(&'op Genesis),
Transition(&'op Transition, Txid, WitnessOrd, BundleId),
}
impl PartialOrd for OrdOpRef<'_> {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> { Some(self.cmp(other)) }
}
impl Ord for OrdOpRef<'_> {
fn cmp(&self, other: &Self) -> Ordering { self.op_ord().cmp(&other.op_ord()) }
}
impl OrdOpRef<'_> {
pub fn witness_id(&self) -> Option<Txid> {
match self {
OrdOpRef::Genesis(_) => None,
OrdOpRef::Transition(_, witness_id, ..) => Some(*witness_id),
}
}
pub fn bundle_id(&self) -> Option<BundleId> {
match self {
OrdOpRef::Genesis(_) => None,
OrdOpRef::Transition(_, _, _, bundle_id) => Some(*bundle_id),
}
}
pub fn op_ord(&self) -> OpOrd {
match self {
OrdOpRef::Genesis(_) => OpOrd::Genesis,
OrdOpRef::Transition(op, _, witness_ord, _) => OpOrd::Transition {
witness: *witness_ord,
ty: op.transition_type,
nonce: op.nonce,
opid: op.id(),
},
}
}
}
impl<'op> Operation for OrdOpRef<'op> {
fn full_type(&self) -> OpFullType {
match self {
OrdOpRef::Genesis(op) => op.full_type(),
OrdOpRef::Transition(op, ..) => op.full_type(),
}
}
fn id(&self) -> OpId {
match self {
OrdOpRef::Genesis(op) => op.id(),
OrdOpRef::Transition(op, ..) => op.id(),
}
}
fn contract_id(&self) -> ContractId {
match self {
OrdOpRef::Genesis(op) => op.contract_id(),
OrdOpRef::Transition(op, ..) => op.contract_id(),
}
}
fn nonce(&self) -> u64 {
match self {
OrdOpRef::Genesis(op) => op.nonce(),
OrdOpRef::Transition(op, ..) => op.nonce(),
}
}
fn metadata(&self) -> &Metadata {
match self {
OrdOpRef::Genesis(op) => op.metadata(),
OrdOpRef::Transition(op, ..) => op.metadata(),
}
}
fn globals(&self) -> &GlobalState {
match self {
OrdOpRef::Genesis(op) => op.globals(),
OrdOpRef::Transition(op, ..) => op.globals(),
}
}
fn assignments(&self) -> AssignmentsRef<'op> {
match self {
OrdOpRef::Genesis(op) => op.assignments(),
OrdOpRef::Transition(op, ..) => op.assignments(),
}
}
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),
}
}
}
#[derive(Getters, Copy, Clone, PartialEq, Eq, Hash, Debug)]
#[derive(StrictType, 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 WitnessPos {
#[getter(as_copy)]
layer1: Layer1,
#[getter(as_copy)]
height: BlockHeight,
#[getter(as_copy)]
timestamp: i64,
}
impl StrictDumb for WitnessPos {
fn strict_dumb() -> Self {
Self {
layer1: Layer1::Bitcoin,
height: NonZeroU32::MIN,
timestamp: 1231006505,
}
}
}
const BITCOIN_GENESIS_TIMESTAMP: i64 = 1231006505;
const LIQUID_GENESIS_TIMESTAMP: i64 = 1296692202;
impl WitnessPos {
#[deprecated(
since = "0.11.0-beta.9",
note = "please use `WitnessPos::bitcoin` or `WitnessPos::liquid` instead"
)]
pub fn new(height: NonZeroU32, timestamp: i64) -> Option<Self> {
Self::bitcoin(height, timestamp)
}
pub fn bitcoin(height: NonZeroU32, timestamp: i64) -> Option<Self> {
if timestamp < BITCOIN_GENESIS_TIMESTAMP {
return None;
}
Some(WitnessPos {
layer1: Layer1::Bitcoin,
height,
timestamp,
})
}
pub fn liquid(height: NonZeroU32, timestamp: i64) -> Option<Self> {
if timestamp < LIQUID_GENESIS_TIMESTAMP {
return None;
}
Some(WitnessPos {
layer1: Layer1::Liquid,
height,
timestamp,
})
}
}
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);
const BLOCK_TIME: i64 = 10 * 60 ;
match (self.layer1, other.layer1) {
(a, b) if a == b => self.height.cmp(&other.height),
(Layer1::Bitcoin, Layer1::Liquid)
if (self.timestamp - other.timestamp).abs() < BLOCK_TIME =>
{
Ordering::Greater
}
(Layer1::Liquid, Layer1::Bitcoin)
if (other.timestamp - self.timestamp).abs() < BLOCK_TIME =>
{
Ordering::Less
}
_ => self.timestamp.cmp(&other.timestamp),
}
}
}
impl Display for WitnessPos {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "{}:{}, ", self.layer1, self.height)?;
match Utc.timestamp_opt(self.timestamp, 0) {
MappedLocalTime::Single(time) => write!(f, "{}", time.format("%Y-%m-%d %H:%M:%S")),
_ => f.write_str("invalid 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 {
#[from]
#[display(inner)]
Mined(WitnessPos),
Tentative,
Ignored,
#[strict_type(dumb)]
Archived,
}
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 = 0xFF)]
Transition {
witness: WitnessOrd,
ty: TransitionType,
nonce: u64,
opid: OpId,
},
}
impl OpOrd {
#[inline]
pub fn is_archived(&self) -> bool {
matches!(self, 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,
ty: TransitionType,
nonce: u64,
witness: WitnessOrd,
) -> Self {
Self {
op_ord: OpOrd::Transition {
witness,
ty,
nonce,
opid,
},
idx,
}
}
}
pub trait GlobalsIter: Iterator {
fn at_depth(&self, depth: usize) -> Option<Self::Item>;
}
#[derive(Eq, PartialEq, Ord, PartialOrd, Clone, Getters)]
pub struct GlobalStateEntry {
ord: GlobalOrd,
data: RevealedData,
}
impl GlobalStateEntry {
pub fn new(ord: GlobalOrd, data: RevealedData) -> Self { Self { ord, data } }
}
#[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<impl GlobalsIter<Item = impl Borrow<GlobalStateEntry>>, UnknownGlobalStateType>;
fn rights(&self, outpoint: Outpoint, ty: AssignmentType) -> u32;
fn fungible(
&self,
outpoint: Outpoint,
ty: AssignmentType,
) -> impl DoubleEndedIterator<Item = FungibleState>;
fn data(
&self,
outpoint: Outpoint,
ty: AssignmentType,
) -> impl DoubleEndedIterator<Item = impl Borrow<RevealedData>>;
}
pub trait ContractStateEvolve {
type Context<'ctx>;
type Error: std::error::Error;
fn init(context: Self::Context<'_>) -> Self;
fn evolve_state(&mut self, op: OrdOpRef) -> Result<(), Self::Error>;
}
pub struct VmContext<'op, S: ContractStateAccess> {
pub contract_id: ContractId,
pub op_info: OpInfo<'op>,
pub contract_state: Rc<RefCell<S>>,
}
type PrevState = BTreeMap<AssignmentType, Vec<RevealedState>>;
pub struct OpInfo<'op> {
pub id: OpId,
pub prev_state: &'op PrevState,
pub op: &'op OrdOpRef<'op>,
}
impl<'op> OpInfo<'op> {
pub fn with(id: OpId, op: &'op OrdOpRef<'op>, prev_state: &'op PrevState) -> Self {
OpInfo { id, prev_state, op }
}
pub fn global(&self) -> &'op GlobalState { self.op.globals() }
pub fn metadata(&self) -> &'op Metadata { self.op.metadata() }
pub fn owned_state(&self) -> AssignmentsRef<'op> { self.op.assignments() }
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn witness_post_timestamp() {
assert_eq!(WitnessPos::bitcoin(NonZeroU32::MIN, BITCOIN_GENESIS_TIMESTAMP - 1), None);
assert_eq!(WitnessPos::liquid(NonZeroU32::MIN, LIQUID_GENESIS_TIMESTAMP - 1), None);
assert_eq!(WitnessPos::liquid(NonZeroU32::MIN, BITCOIN_GENESIS_TIMESTAMP), None);
assert!(WitnessPos::bitcoin(NonZeroU32::MIN, BITCOIN_GENESIS_TIMESTAMP).is_some());
assert!(WitnessPos::liquid(NonZeroU32::MIN, LIQUID_GENESIS_TIMESTAMP).is_some());
assert!(WitnessPos::bitcoin(NonZeroU32::MIN, LIQUID_GENESIS_TIMESTAMP).is_some());
}
#[test]
fn witness_pos_getters() {
let pos = WitnessPos::bitcoin(NonZeroU32::MIN, BITCOIN_GENESIS_TIMESTAMP).unwrap();
assert_eq!(pos.height(), NonZeroU32::MIN);
assert_eq!(pos.timestamp(), BITCOIN_GENESIS_TIMESTAMP);
assert_eq!(pos.layer1(), Layer1::Bitcoin);
let pos = WitnessPos::liquid(NonZeroU32::MIN, LIQUID_GENESIS_TIMESTAMP).unwrap();
assert_eq!(pos.height(), NonZeroU32::MIN);
assert_eq!(pos.timestamp(), LIQUID_GENESIS_TIMESTAMP);
assert_eq!(pos.layer1(), Layer1::Liquid);
}
}