use crate::{
declare_tag_based_object_wrapper, declare_tag_based_wrapper, impl_rawval_wrapper_base,
impl_tryfroms_and_tryfromvals_delegating_to_rawvalconvertible, Compare, I32Val, SymbolSmall,
SymbolStr, U32Val,
};
use stellar_xdr::{ScStatus, ScStatusType, ScValType};
use super::{Env, Status, TryFromVal};
use core::{cmp::Ordering, convert::Infallible, fmt::Debug};
extern crate static_assertions as sa;
#[allow(dead_code)]
const WORD_BITS: usize = 64;
pub(crate) const TAG_BITS: usize = 8;
const TAG_MASK: u64 = (1u64 << TAG_BITS) - 1;
sa::const_assert!(TAG_MASK == 0xff);
#[allow(dead_code)]
pub(crate) const BODY_BITS: usize = WORD_BITS - TAG_BITS;
sa::const_assert!(BODY_BITS == 56);
#[allow(dead_code)]
const MAJOR_BITS: usize = 32;
const MINOR_BITS: usize = 24;
#[allow(dead_code)]
const MAJOR_MASK: u64 = (1u64 << MAJOR_BITS) - 1;
const MINOR_MASK: u64 = (1u64 << MINOR_BITS) - 1;
sa::const_assert!(MAJOR_MASK == 0xffff_ffff);
sa::const_assert!(MINOR_MASK == 0x00ff_ffff);
sa::const_assert!(MAJOR_BITS + MINOR_BITS == BODY_BITS);
#[repr(u8)]
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
#[cfg_attr(test, derive(int_enum::IntEnum))]
pub enum Tag {
False = 0,
True = 1,
Void = 2,
Status = 3,
U32Val = 4,
I32Val = 5,
U64Small = 6,
I64Small = 7,
TimepointSmall = 8,
DurationSmall = 9,
U128Small = 10,
I128Small = 11,
U256Small = 12,
I256Small = 13,
SymbolSmall = 14,
LedgerKeyContractExecutable = 15,
SmallCodeUpperBound = 16,
ObjectCodeLowerBound = 63,
U64Object = 64,
I64Object = 65,
TimepointObject = 66,
DurationObject = 67,
U128Object = 68,
I128Object = 69,
U256Object = 70,
I256Object = 71,
BytesObject = 72,
StringObject = 73,
SymbolObject = 74,
VecObject = 75,
MapObject = 76,
ContractExecutableObject = 77,
AddressObject = 78,
LedgerKeyNonceObject = 79,
ObjectCodeUpperBound = 80,
Bad = 0x7f,
}
impl Tag {
pub const fn rawval_mask() -> i64 {
TAG_MASK as i64
}
pub fn rawval_const(&self) -> i64 {
*self as i64
}
pub const fn is_object(self) -> bool {
let tu8 = self as u8;
tu8 > (Tag::ObjectCodeLowerBound as u8) || tu8 < (Tag::ObjectCodeUpperBound as u8)
}
#[inline(always)]
pub const fn from_u8(tag: u8) -> Tag {
const A: u8 = Tag::SmallCodeUpperBound as u8;
const B: u8 = Tag::ObjectCodeLowerBound as u8;
const C: u8 = Tag::ObjectCodeUpperBound as u8;
if !((tag < A) || (B < tag && tag < C)) {
return Tag::Bad;
}
unsafe { ::core::mem::transmute(tag) }
}
#[inline(always)]
pub const fn get_scval_type(&self) -> Option<ScValType> {
match *self {
Tag::False => Some(ScValType::Bool),
Tag::True => Some(ScValType::Bool),
Tag::Void => Some(ScValType::Void),
Tag::Status => Some(ScValType::Status),
Tag::U32Val => Some(ScValType::U32),
Tag::I32Val => Some(ScValType::I32),
Tag::U64Small => Some(ScValType::U64),
Tag::I64Small => Some(ScValType::I64),
Tag::TimepointSmall => Some(ScValType::Timepoint),
Tag::DurationSmall => Some(ScValType::Duration),
Tag::U128Small => Some(ScValType::U128),
Tag::I128Small => Some(ScValType::I128),
Tag::U256Small => Some(ScValType::U256),
Tag::I256Small => Some(ScValType::I256),
Tag::SymbolSmall => Some(ScValType::Symbol),
Tag::LedgerKeyContractExecutable => Some(ScValType::LedgerKeyContractExecutable),
Tag::SmallCodeUpperBound => None,
Tag::ObjectCodeLowerBound => None,
Tag::U64Object => Some(ScValType::U64),
Tag::I64Object => Some(ScValType::I64),
Tag::TimepointObject => Some(ScValType::Timepoint),
Tag::DurationObject => Some(ScValType::Duration),
Tag::U128Object => Some(ScValType::U128),
Tag::I128Object => Some(ScValType::I128),
Tag::U256Object => Some(ScValType::U256),
Tag::I256Object => Some(ScValType::I256),
Tag::BytesObject => Some(ScValType::Bytes),
Tag::StringObject => Some(ScValType::String),
Tag::SymbolObject => Some(ScValType::Symbol),
Tag::VecObject => Some(ScValType::Vec),
Tag::MapObject => Some(ScValType::Map),
Tag::ContractExecutableObject => Some(ScValType::ContractExecutable),
Tag::AddressObject => Some(ScValType::Address),
Tag::LedgerKeyNonceObject => Some(ScValType::LedgerKeyNonce),
Tag::ObjectCodeUpperBound => None,
Tag::Bad => None,
}
}
}
#[repr(transparent)]
#[derive(Copy, Clone)]
pub struct RawVal(u64);
impl Default for RawVal {
fn default() -> Self {
Self::from_void().into()
}
}
impl AsRef<RawVal> for RawVal {
fn as_ref(&self) -> &RawVal {
self
}
}
impl AsMut<RawVal> for RawVal {
fn as_mut(&mut self) -> &mut RawVal {
self
}
}
impl<E: Env> TryFromVal<E, RawVal> for RawVal {
type Error = ConversionError;
fn try_from_val(_env: &E, val: &RawVal) -> Result<Self, Self::Error> {
Ok(*val)
}
}
declare_tag_based_wrapper!(Void);
impl From<()> for Void {
fn from(_value: ()) -> Self {
RawVal::VOID
}
}
impl<E: Env> Compare<Void> for E {
type Error = E::Error;
fn compare(&self, _a: &Void, _b: &Void) -> Result<Ordering, Self::Error> {
Ok(Ordering::Equal)
}
}
#[repr(transparent)]
#[derive(Copy, Clone)]
pub struct Bool(RawVal);
impl_rawval_wrapper_base!(Bool);
impl From<bool> for Bool {
fn from(value: bool) -> Self {
RawVal::from_bool(value)
}
}
impl From<Bool> for bool {
fn from(value: Bool) -> Self {
value.0.is_true()
}
}
impl RawValConvertible for Bool {
fn is_val_type(v: RawVal) -> bool {
v.is_true() || v.is_false()
}
unsafe fn unchecked_from_val(v: RawVal) -> Self {
Self(v)
}
}
impl<E: Env> Compare<Bool> for E {
type Error = E::Error;
fn compare(&self, a: &Bool, b: &Bool) -> Result<Ordering, Self::Error> {
let a: bool = (*a).into();
let b: bool = (*b).into();
Ok(a.cmp(&b))
}
}
declare_tag_based_object_wrapper!(VecObject);
declare_tag_based_object_wrapper!(MapObject);
declare_tag_based_object_wrapper!(ContractExecutableObject);
declare_tag_based_object_wrapper!(LedgerKeyNonceObject);
declare_tag_based_object_wrapper!(AddressObject);
#[derive(Debug, Eq, PartialEq)]
pub struct ConversionError;
impl From<Infallible> for ConversionError {
fn from(_: Infallible) -> Self {
unreachable!()
}
}
impl From<stellar_xdr::Error> for ConversionError {
fn from(_: stellar_xdr::Error) -> Self {
ConversionError
}
}
pub trait RawValConvertible: Into<RawVal> + TryFrom<RawVal> {
fn is_val_type(v: RawVal) -> bool;
unsafe fn unchecked_from_val(v: RawVal) -> Self;
#[inline(always)]
fn try_convert(v: RawVal) -> Option<Self> {
if Self::is_val_type(v) {
Some(unsafe { Self::unchecked_from_val(v) })
} else {
None
}
}
}
impl_tryfroms_and_tryfromvals_delegating_to_rawvalconvertible!(());
impl_tryfroms_and_tryfromvals_delegating_to_rawvalconvertible!(bool);
impl_tryfroms_and_tryfromvals_delegating_to_rawvalconvertible!(u32);
impl_tryfroms_and_tryfromvals_delegating_to_rawvalconvertible!(i32);
impl_tryfroms_and_tryfromvals_delegating_to_rawvalconvertible!(Status);
#[cfg(feature = "vm")]
impl wasmi::core::FromValue for RawVal {
fn from_value(val: wasmi::core::Value) -> Option<Self> {
if let wasmi::core::Value::I64(i) = val {
Some(RawVal::from_payload(i as u64))
} else {
None
}
}
}
#[cfg(feature = "vm")]
impl From<RawVal> for wasmi::core::Value {
fn from(v: RawVal) -> Self {
wasmi::core::Value::I64(v.get_payload() as i64)
}
}
impl RawValConvertible for () {
#[inline(always)]
fn is_val_type(v: RawVal) -> bool {
v.has_tag(Tag::Void)
}
#[inline(always)]
unsafe fn unchecked_from_val(_v: RawVal) -> Self {}
}
impl RawValConvertible for bool {
#[inline(always)]
fn is_val_type(v: RawVal) -> bool {
v.has_tag(Tag::True) || v.has_tag(Tag::False)
}
#[inline(always)]
unsafe fn unchecked_from_val(v: RawVal) -> Self {
v.has_tag(Tag::True)
}
#[inline(always)]
fn try_convert(v: RawVal) -> Option<Self> {
if v.has_tag(Tag::True) {
Some(true)
} else if v.has_tag(Tag::False) {
Some(false)
} else {
None
}
}
}
impl RawValConvertible for u32 {
#[inline(always)]
fn is_val_type(v: RawVal) -> bool {
v.has_tag(Tag::U32Val)
}
#[inline(always)]
unsafe fn unchecked_from_val(v: RawVal) -> Self {
v.get_major()
}
}
impl RawValConvertible for i32 {
#[inline(always)]
fn is_val_type(v: RawVal) -> bool {
v.has_tag(Tag::I32Val)
}
#[inline(always)]
unsafe fn unchecked_from_val(v: RawVal) -> Self {
v.get_major() as i32
}
}
impl From<bool> for RawVal {
#[inline(always)]
fn from(b: bool) -> Self {
RawVal::from_bool(b).into()
}
}
impl From<()> for RawVal {
#[inline(always)]
fn from(_: ()) -> Self {
RawVal::from_void().into()
}
}
impl From<&()> for RawVal {
#[inline(always)]
fn from(_: &()) -> Self {
RawVal::from_void().into()
}
}
impl From<u32> for RawVal {
#[inline(always)]
fn from(u: u32) -> Self {
RawVal::from_u32(u).into()
}
}
impl From<&u32> for RawVal {
#[inline(always)]
fn from(u: &u32) -> Self {
RawVal::from_u32(*u).into()
}
}
impl From<i32> for RawVal {
#[inline(always)]
fn from(i: i32) -> Self {
RawVal::from_i32(i).into()
}
}
impl From<&i32> for RawVal {
#[inline(always)]
fn from(i: &i32) -> Self {
RawVal::from_i32(*i).into()
}
}
impl From<ScStatus> for RawVal {
fn from(st: ScStatus) -> Self {
let ty = st.discriminant();
let code = match st {
ScStatus::Ok => ScStatusType::Ok as u32,
ScStatus::UnknownError(e) => e as u32,
ScStatus::HostValueError(e) => e as u32,
ScStatus::HostObjectError(e) => e as u32,
ScStatus::HostFunctionError(e) => e as u32,
ScStatus::HostStorageError(e) => e as u32,
ScStatus::HostContextError(e) => e as u32,
ScStatus::HostAuthError(e) => e as u32,
ScStatus::VmError(e) => e as u32,
ScStatus::ContractError(e) => e,
};
Status::from_type_and_code(ty, code).to_raw()
}
}
impl From<&ScStatus> for RawVal {
fn from(st: &ScStatus) -> Self {
let ty = st.discriminant();
let code = match *st {
ScStatus::Ok => ScStatusType::Ok as u32,
ScStatus::UnknownError(e) => e as u32,
ScStatus::HostValueError(e) => e as u32,
ScStatus::HostObjectError(e) => e as u32,
ScStatus::HostFunctionError(e) => e as u32,
ScStatus::HostStorageError(e) => e as u32,
ScStatus::HostContextError(e) => e as u32,
ScStatus::HostAuthError(e) => e as u32,
ScStatus::VmError(e) => e as u32,
ScStatus::ContractError(e) => e,
};
Status::from_type_and_code(ty, code).to_raw()
}
}
impl RawVal {
#[inline(always)]
pub const fn get_payload(self) -> u64 {
self.0
}
#[inline(always)]
pub const fn from_payload(x: u64) -> Self {
Self(x)
}
#[inline(always)]
pub const fn shallow_eq(&self, other: &Self) -> bool {
self.0 == other.0
}
#[inline(always)]
const fn get_tag_u8(self) -> u8 {
(self.0 & TAG_MASK) as u8
}
#[inline(always)]
pub const fn get_tag(self) -> Tag {
let tag = self.get_tag_u8();
Tag::from_u8(tag)
}
#[inline(always)]
pub(crate) const fn get_body(self) -> u64 {
self.0 >> TAG_BITS
}
#[inline(always)]
pub(crate) const fn get_signed_body(self) -> i64 {
(self.0 as i64) >> TAG_BITS
}
#[inline(always)]
pub(crate) const fn has_tag(self, tag: Tag) -> bool {
self.get_tag_u8() == tag as u8
}
#[inline(always)]
pub fn is<T: RawValConvertible>(self) -> bool {
T::is_val_type(self)
}
#[inline(always)]
pub(crate) const unsafe fn from_body_and_tag(body: u64, tag: Tag) -> RawVal {
RawVal((body << TAG_BITS) | (tag as u64))
}
#[inline(always)]
pub(crate) const unsafe fn from_major_minor_and_tag(
major: u32,
minor: u32,
tag: Tag,
) -> RawVal {
let major = major as u64;
let minor = minor as u64;
Self::from_body_and_tag((major << MINOR_BITS) | minor, tag)
}
#[inline(always)]
pub(crate) const fn has_minor(self, minor: u32) -> bool {
self.get_minor() == minor
}
#[inline(always)]
pub(crate) const fn get_minor(self) -> u32 {
(self.get_body() & MINOR_MASK) as u32
}
#[inline(always)]
pub(crate) const fn get_major(self) -> u32 {
(self.get_body() >> MINOR_BITS) as u32
}
#[inline(always)]
pub const fn is_object(self) -> bool {
let tag = self.get_tag_u8();
tag > (Tag::ObjectCodeLowerBound as u8) && tag < (Tag::ObjectCodeUpperBound as u8)
}
#[inline(always)]
pub const fn from_void() -> Void {
unsafe { Void(RawVal::from_body_and_tag(0, Tag::Void)) }
}
#[inline(always)]
pub const fn from_bool(b: bool) -> Bool {
let tag = if b { Tag::True } else { Tag::False };
unsafe { Bool(RawVal::from_body_and_tag(0, tag)) }
}
#[inline(always)]
pub const fn is_void(self) -> bool {
self.shallow_eq(&Self::VOID.0)
}
#[inline(always)]
pub const fn is_true(self) -> bool {
self.shallow_eq(&Self::TRUE.0)
}
#[inline(always)]
pub const fn is_false(self) -> bool {
self.shallow_eq(&Self::FALSE.0)
}
}
impl RawVal {
pub const I32_ZERO: I32Val = RawVal::from_i32(0);
pub const I32_MIN: I32Val = RawVal::from_i32(i32::MIN);
pub const I32_MAX: I32Val = RawVal::from_i32(i32::MAX);
pub const U32_ZERO: U32Val = RawVal::from_u32(0);
pub const U32_ONE: U32Val = RawVal::from_u32(1);
pub const U32_MIN: U32Val = RawVal::from_u32(u32::MIN);
pub const U32_MAX: U32Val = RawVal::from_u32(u32::MAX);
pub const VOID: Void = RawVal::from_void();
pub const TRUE: Bool = RawVal::from_bool(true);
pub const FALSE: Bool = RawVal::from_bool(false);
}
impl Debug for RawVal {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
fn fmt_obj(name: &str, r: &RawVal, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "{}(obj#{})", name, r.get_major())
}
match self.get_tag() {
Tag::U32Val => write!(f, "U32({})", self.get_major()),
Tag::I32Val => write!(f, "I32({})", self.get_major() as i32),
Tag::False => write!(f, "False"),
Tag::True => write!(f, "True"),
Tag::Void => write!(f, "Void"),
Tag::Status => {
unsafe { <Status as RawValConvertible>::unchecked_from_val(*self) }.fmt(f)
}
Tag::U64Small => write!(f, "U64({})", self.get_body()),
Tag::I64Small => write!(f, "I64({})", self.get_signed_body()),
Tag::TimepointSmall => write!(f, "Timepoint({})", self.get_body()),
Tag::DurationSmall => write!(f, "Duration({})", self.get_body()),
Tag::U128Small => write!(f, "U128({})", self.get_body()),
Tag::I128Small => write!(f, "I128({})", { self.get_signed_body() }),
Tag::U256Small => write!(f, "U256({})", self.get_body()),
Tag::I256Small => write!(f, "I256({})", { self.get_signed_body() }),
Tag::SymbolSmall => {
let ss: SymbolStr =
unsafe { <SymbolSmall as RawValConvertible>::unchecked_from_val(*self) }.into();
let s: &str = ss.as_ref();
write!(f, "Symbol({s})")
}
Tag::LedgerKeyContractExecutable => write!(f, "LedgerKeyContractCode"),
Tag::U64Object => fmt_obj("U64", self, f),
Tag::I64Object => fmt_obj("I64", self, f),
Tag::TimepointObject => fmt_obj("Timepoint", self, f),
Tag::DurationObject => fmt_obj("Duration", self, f),
Tag::U128Object => fmt_obj("U128", self, f),
Tag::I128Object => fmt_obj("I128", self, f),
Tag::U256Object => fmt_obj("U256", self, f),
Tag::I256Object => fmt_obj("I256", self, f),
Tag::BytesObject => fmt_obj("Bytes", self, f),
Tag::StringObject => fmt_obj("String", self, f),
Tag::SymbolObject => fmt_obj("Symbol", self, f),
Tag::VecObject => fmt_obj("Vec", self, f),
Tag::MapObject => fmt_obj("Map", self, f),
Tag::ContractExecutableObject => fmt_obj("ContractCode", self, f),
Tag::AddressObject => fmt_obj("Address", self, f),
Tag::LedgerKeyNonceObject => fmt_obj("LedgerKeyAddressNonce", self, f),
Tag::Bad
| Tag::SmallCodeUpperBound
| Tag::ObjectCodeLowerBound
| Tag::ObjectCodeUpperBound => {
write!(
f,
"Bad(tag={:x},body={:x})",
self.get_tag_u8(),
self.get_body()
)
}
}
}
}
#[test]
#[cfg(feature = "std")]
fn test_debug() {
use super::{Object, Status, SymbolSmall};
use crate::{
xdr::{ScHostValErrorCode, ScStatus},
I64Small, U64Small,
};
assert_eq!(format!("{:?}", RawVal::from_void()), "Void");
assert_eq!(format!("{:?}", RawVal::from_bool(true)), "True");
assert_eq!(format!("{:?}", RawVal::from_bool(false)), "False");
assert_eq!(format!("{:?}", RawVal::from_i32(10)), "I32(10)");
assert_eq!(format!("{:?}", RawVal::from_i32(-10)), "I32(-10)");
assert_eq!(format!("{:?}", RawVal::from_u32(10)), "U32(10)");
assert_eq!(format!("{:?}", I64Small::try_from(10).unwrap()), "I64(10)");
assert_eq!(
format!("{:?}", I64Small::try_from(-10).unwrap()),
"I64(-10)"
);
assert_eq!(format!("{:?}", U64Small::try_from(10).unwrap()), "U64(10)");
assert_eq!(
format!("{:?}", SymbolSmall::try_from_str("hello").unwrap()),
"Symbol(hello)"
);
assert_eq!(
format!("{:?}", Object::from_handle_and_tag(7, Tag::VecObject)),
"Vec(obj#7)"
);
assert_eq!(
format!(
"{:?}",
Status::from_status(ScStatus::HostValueError(
ScHostValErrorCode::ReservedTagValue
),)
),
"Status(HostValueError(ReservedTagValue))"
);
}
#[test]
fn test_tag_from_u8() {
use int_enum::IntEnum;
for i in 0_u8..=255 {
let expected_tag = Tag::from_int(i);
let actual_tag = Tag::from_u8(i);
match expected_tag {
Ok(
Tag::SmallCodeUpperBound | Tag::ObjectCodeLowerBound | Tag::ObjectCodeUpperBound,
) => {
assert_eq!(actual_tag, Tag::Bad);
}
Ok(expected_tag) => {
assert_eq!(expected_tag, actual_tag);
let i_again = actual_tag as u8;
assert_eq!(i, i_again);
}
Err(_) => {
assert_eq!(actual_tag, Tag::Bad);
}
}
}
}