mod box_id;
pub mod box_value;
mod register;
use crate::ergo_tree::ErgoTree;
use crate::mir::constant::Constant;
use crate::serialization::sigma_byte_reader::SigmaByteRead;
use crate::serialization::sigma_byte_writer::SigmaByteWrite;
use crate::serialization::SigmaParsingError;
use crate::serialization::SigmaSerializable;
use crate::serialization::SigmaSerializationError;
use crate::serialization::SigmaSerializeResult;
pub use box_id::*;
use ergo_chain_types::Digest32;
pub use register::*;
use bounded_vec::BoundedVec;
use indexmap::IndexSet;
use sigma_util::hash::blake2b256_hash;
use sigma_util::AsVecI8;
use std::convert::TryFrom;
use std::convert::TryInto;
use self::box_value::BoxValue;
use super::token::Token;
use super::token::TokenId;
use super::tx_id::TxId;
pub type BoxTokens = BoundedVec<Token, 1, { ErgoBox::MAX_TOKENS_COUNT }>;
#[cfg_attr(feature = "json", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
feature = "json",
serde(try_from = "crate::chain::json::ergo_box::ErgoBoxJson"),
serde(into = "crate::chain::json::ergo_box::ErgoBoxJson")
)]
#[derive(PartialEq, Eq, Debug, Clone)]
pub struct ErgoBox {
pub(crate) box_id: BoxId,
pub value: BoxValue,
pub ergo_tree: ErgoTree,
pub tokens: Option<BoxTokens>,
pub additional_registers: NonMandatoryRegisters,
pub creation_height: u32,
pub transaction_id: TxId,
pub index: u16,
}
impl ErgoBox {
pub const MAX_TOKENS_COUNT: usize = 122;
pub fn new(
value: BoxValue,
ergo_tree: ErgoTree,
tokens: Option<BoxTokens>,
additional_registers: NonMandatoryRegisters,
creation_height: u32,
transaction_id: TxId,
index: u16,
) -> Result<ErgoBox, SigmaSerializationError> {
let box_with_zero_id = ErgoBox {
box_id: BoxId::zero(),
value,
ergo_tree,
tokens,
additional_registers,
creation_height,
transaction_id,
index,
};
let box_id = box_with_zero_id.calc_box_id()?;
Ok(ErgoBox {
box_id,
..box_with_zero_id
})
}
pub fn box_id(&self) -> BoxId {
self.box_id
}
pub fn from_box_candidate(
box_candidate: &ErgoBoxCandidate,
transaction_id: TxId,
index: u16,
) -> Result<ErgoBox, SigmaSerializationError> {
let box_with_zero_id = ErgoBox {
box_id: BoxId::zero(),
value: box_candidate.value,
ergo_tree: box_candidate.ergo_tree.clone(),
tokens: box_candidate.tokens.clone(),
additional_registers: box_candidate.additional_registers.clone(),
creation_height: box_candidate.creation_height,
transaction_id,
index,
};
let box_id = box_with_zero_id.calc_box_id()?;
Ok(ErgoBox {
box_id,
..box_with_zero_id
})
}
pub(crate) fn calc_box_id(&self) -> Result<BoxId, SigmaSerializationError> {
let bytes = self.sigma_serialize_bytes()?;
let hash = blake2b256_hash(&bytes);
Ok(Digest32::from(*hash).into())
}
pub fn get_register(&self, id: RegisterId) -> Option<Constant> {
match id {
RegisterId::MandatoryRegisterId(id) => match id {
MandatoryRegisterId::R0 => Some(self.value.into()),
#[allow(clippy::unwrap_used)]
MandatoryRegisterId::R1 => Some(self.script_bytes().unwrap().into()),
MandatoryRegisterId::R2 => Some(self.tokens_raw().into()),
MandatoryRegisterId::R3 => Some(self.creation_info().into()),
},
RegisterId::NonMandatoryRegisterId(id) => {
self.additional_registers.get_constant(id).cloned()
}
}
}
pub fn tokens_raw(&self) -> Vec<(Vec<i8>, i64)> {
self.tokens
.clone()
.into_iter()
.flatten()
.map(Into::into)
.collect()
}
pub fn script_bytes(&self) -> Result<Vec<i8>, SigmaSerializationError> {
Ok(self.ergo_tree.sigma_serialize_bytes()?.as_vec_i8())
}
pub fn creation_info(&self) -> (i32, Vec<i8>) {
let mut bytes = Vec::with_capacity(Digest32::SIZE + 2);
bytes.extend_from_slice(self.transaction_id.0 .0.as_ref());
bytes.extend_from_slice(&self.index.to_be_bytes());
(self.creation_height as i32, bytes.as_vec_i8())
}
pub fn bytes_without_ref(&self) -> Result<Vec<i8>, SigmaSerializationError> {
let candidate: ErgoBoxCandidate = self.clone().into();
Ok(candidate.sigma_serialize_bytes()?.as_vec_i8())
}
}
impl SigmaSerializable for ErgoBox {
fn sigma_serialize<W: SigmaByteWrite>(&self, w: &mut W) -> SigmaSerializeResult {
let ergo_tree_bytes = self.ergo_tree.sigma_serialize_bytes()?;
serialize_box_with_indexed_digests(
&self.value,
ergo_tree_bytes,
&self.tokens,
&self.additional_registers,
self.creation_height,
None,
w,
)?;
self.transaction_id.sigma_serialize(w)?;
w.put_u16(self.index)?;
Ok(())
}
fn sigma_parse<R: SigmaByteRead>(r: &mut R) -> Result<Self, SigmaParsingError> {
let box_candidate = ErgoBoxCandidate::parse_body_with_indexed_digests(None, r)?;
let tx_id = TxId::sigma_parse(r)?;
let index = r.get_u16()?;
Ok(ErgoBox::from_box_candidate(&box_candidate, tx_id, index)?)
}
}
#[cfg_attr(feature = "json", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
feature = "json",
serde(try_from = "crate::chain::json::ergo_box::ErgoBoxCandidateJson"),
serde(into = "crate::chain::json::ergo_box::ErgoBoxCandidateJson")
)]
#[derive(PartialEq, Eq, Clone, Debug)]
pub struct ErgoBoxCandidate {
pub value: BoxValue,
pub ergo_tree: ErgoTree,
pub tokens: Option<BoxTokens>,
pub additional_registers: NonMandatoryRegisters,
pub creation_height: u32,
}
impl ErgoBoxCandidate {
pub fn serialize_body_with_indexed_digests<W: SigmaByteWrite>(
&self,
token_ids_in_tx: Option<&IndexSet<TokenId>>,
w: &mut W,
) -> SigmaSerializeResult {
serialize_box_with_indexed_digests(
&self.value,
self.ergo_tree.sigma_serialize_bytes()?,
&self.tokens,
&self.additional_registers,
self.creation_height,
token_ids_in_tx,
w,
)
}
pub fn parse_body_with_indexed_digests<R: SigmaByteRead>(
digests_in_tx: Option<&IndexSet<TokenId>>,
r: &mut R,
) -> Result<ErgoBoxCandidate, SigmaParsingError> {
parse_box_with_indexed_digests(digests_in_tx, r)
}
}
impl SigmaSerializable for ErgoBoxCandidate {
fn sigma_serialize<W: SigmaByteWrite>(&self, w: &mut W) -> SigmaSerializeResult {
self.serialize_body_with_indexed_digests(None, w)
}
fn sigma_parse<R: SigmaByteRead>(r: &mut R) -> Result<Self, SigmaParsingError> {
ErgoBoxCandidate::parse_body_with_indexed_digests(None, r)
}
}
impl From<ErgoBox> for ErgoBoxCandidate {
fn from(b: ErgoBox) -> Self {
ErgoBoxCandidate {
value: b.value,
ergo_tree: b.ergo_tree,
tokens: b.tokens,
additional_registers: b.additional_registers,
creation_height: b.creation_height,
}
}
}
pub fn serialize_box_with_indexed_digests<W: SigmaByteWrite>(
box_value: &BoxValue,
ergo_tree_bytes: Vec<u8>,
tokens: &Option<BoxTokens>,
additional_registers: &NonMandatoryRegisters,
creation_height: u32,
token_ids_in_tx: Option<&IndexSet<TokenId>>,
w: &mut W,
) -> SigmaSerializeResult {
box_value.sigma_serialize(w)?;
w.write_all(&ergo_tree_bytes[..])?;
w.put_u32(creation_height)?;
let tokens: &[Token] = tokens.as_ref().map(BoundedVec::as_ref).unwrap_or(&[]);
#[allow(clippy::unwrap_used)]
w.put_u8(u8::try_from(tokens.len()).unwrap())?;
tokens.iter().try_for_each(|t| {
match token_ids_in_tx {
Some(token_ids) => Ok(w.put_u32(
#[allow(clippy::unwrap_used)]
u32::try_from(
#[allow(clippy::expect_used)]
token_ids
.get_full(&t.token_id)
.expect("failed to find token id in tx's digest index")
.0,
)
.unwrap(),
)?),
None => t.token_id.sigma_serialize(w),
}
.and_then(|()| Ok(w.put_u64(t.amount.into())?))
})?;
additional_registers.sigma_serialize(w)
}
pub fn parse_box_with_indexed_digests<R: SigmaByteRead>(
digests_in_tx: Option<&IndexSet<TokenId>>,
r: &mut R,
) -> Result<ErgoBoxCandidate, SigmaParsingError> {
let value = BoxValue::sigma_parse(r)?;
let ergo_tree = ErgoTree::sigma_parse(r)?;
let creation_height = r.get_u32()?;
let tokens_count = r.get_u8()?;
let mut tokens = Vec::with_capacity(tokens_count as usize);
for _ in 0..tokens_count {
let token_id = match digests_in_tx {
None => TokenId::sigma_parse(r)?,
Some(digests) => {
let digest_index = r.get_u32()?;
match digests.get_index(digest_index as usize) {
Some(i) => Ok(*i),
None => Err(SigmaParsingError::Misc(
"failed to find token id in tx digests".to_string(),
)),
}?
}
};
let amount = r.get_u64()?;
tokens.push(Token {
token_id,
amount: amount.try_into()?,
})
}
let tokens = if tokens.is_empty() {
None
} else {
Some(BoxTokens::from_vec(tokens)?)
};
let additional_registers = NonMandatoryRegisters::sigma_parse(r)?;
Ok(ErgoBoxCandidate {
value,
ergo_tree,
tokens,
additional_registers,
creation_height,
})
}
#[allow(clippy::unwrap_used)]
#[cfg(feature = "arbitrary")]
pub mod arbitrary {
use super::box_value::arbitrary::ArbBoxValueRange;
use super::*;
use proptest::{arbitrary::Arbitrary, collection::vec, option::of, prelude::*};
impl Arbitrary for ErgoBoxCandidate {
type Parameters = ArbBoxValueRange;
fn arbitrary_with(args: Self::Parameters) -> Self::Strategy {
(
any_with::<BoxValue>(args),
any::<ErgoTree>(),
of(vec(any::<Token>(), 1..3)),
any::<u32>(),
any::<NonMandatoryRegisters>(),
)
.prop_map(
|(value, ergo_tree, tokens, creation_height, additional_registers)| Self {
value,
ergo_tree,
tokens: tokens.map(BoundedVec::from_vec).map(Result::unwrap),
additional_registers,
creation_height,
},
)
.boxed()
}
type Strategy = BoxedStrategy<Self>;
}
impl Arbitrary for ErgoBox {
type Parameters = ArbBoxValueRange;
fn arbitrary_with(args: Self::Parameters) -> Self::Strategy {
(
any_with::<ErgoBoxCandidate>(args),
any::<TxId>(),
any::<u16>(),
)
.prop_map(|(box_candidate, tx_id, index)| {
Self::from_box_candidate(&box_candidate, tx_id, index).unwrap()
})
.boxed()
}
type Strategy = BoxedStrategy<Self>;
}
impl ErgoBox {
pub fn with_additional_registers(self, registers: NonMandatoryRegisters) -> ErgoBox {
ErgoBox {
additional_registers: registers,
..self
}
}
}
}
#[allow(clippy::panic)]
#[allow(clippy::unwrap_used)]
#[cfg(test)]
mod tests {
use super::*;
use crate::chain::token::arbitrary::ArbTokenIdParam;
use crate::serialization::sigma_serialize_roundtrip;
use proptest::collection::SizeRange;
use proptest::prelude::*;
use sigma_test_util::force_any_val;
use sigma_test_util::force_any_val_with;
#[test]
fn get_register_mandatory() {
let b = force_any_val::<ErgoBox>();
assert_eq!(b.get_register(RegisterId::R0).unwrap(), b.value.into());
assert_eq!(
b.get_register(RegisterId::R1).unwrap(),
b.script_bytes().unwrap().into()
);
assert_eq!(
b.get_register(RegisterId::R2).unwrap(),
b.tokens_raw().into()
);
assert_eq!(
b.get_register(RegisterId::R3).unwrap(),
b.creation_info().into()
);
}
#[test]
fn creation_info() {
let b = force_any_val::<ErgoBox>();
assert_eq!(b.creation_info().0, b.creation_height as i32);
let mut expected_bytes = Vec::new();
expected_bytes.extend_from_slice(b.transaction_id.0 .0.as_ref());
expected_bytes.extend_from_slice(&b.index.to_be_bytes());
assert_eq!(b.creation_info().1, expected_bytes.to_vec().as_vec_i8());
}
#[test]
fn test_max_tokens() {
let tokens = force_any_val_with::<Vec<Token>>((
SizeRange::new(ErgoBox::MAX_TOKENS_COUNT..=ErgoBox::MAX_TOKENS_COUNT),
ArbTokenIdParam::Arbitrary,
));
let b = ErgoBox::from_box_candidate(
&ErgoBoxCandidate {
value: BoxValue::SAFE_USER_MIN,
ergo_tree: force_any_val::<ErgoTree>(),
tokens: Some(BoxTokens::from_vec(tokens).unwrap()),
additional_registers: NonMandatoryRegisters::empty(),
creation_height: 0,
},
TxId::zero(),
0,
)
.unwrap();
assert_eq!(sigma_serialize_roundtrip(&b), b);
}
proptest! {
#[test]
fn ergo_box_candidate_ser_roundtrip(v in any::<ErgoBoxCandidate>()) {
prop_assert_eq![sigma_serialize_roundtrip(&v), v];
}
#[test]
fn ergo_box_ser_roundtrip(v in any::<ErgoBox>()) {
prop_assert_eq![sigma_serialize_roundtrip(&v), v];
}
}
}