pub mod box_builder;
mod box_id;
mod box_value;
mod register;
pub use box_id::*;
pub use box_value::*;
pub use register::*;
#[cfg(feature = "json")]
use super::json;
use super::token::TokenAmount;
use super::{
digest32::blake2b256_hash,
token::{Token, TokenId},
transaction::TxId,
};
use crate::{
ergo_tree::ErgoTree,
serialization::{
ergo_box::{parse_box_with_indexed_digests, serialize_box_with_indexed_digests},
sigma_byte_reader::SigmaByteRead,
sigma_byte_writer::SigmaByteWrite,
SerializationError, SigmaSerializable,
},
};
use indexmap::IndexSet;
#[cfg(feature = "json")]
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[cfg(feature = "json")]
use std::convert::TryFrom;
use std::io;
#[cfg(feature = "json")]
use thiserror::Error;
#[cfg_attr(feature = "json", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "json", serde(try_from = "json::ergo_box::ErgoBoxFromJson"))]
#[derive(PartialEq, Eq, Debug, Clone)]
pub struct ErgoBox {
#[cfg_attr(feature = "json", serde(rename = "boxId"))]
box_id: BoxId,
#[cfg_attr(feature = "json", serde(rename = "value"))]
pub value: BoxValue,
#[cfg_attr(feature = "json", serde(rename = "ergoTree", with = "json::ergo_tree"))]
pub ergo_tree: ErgoTree,
#[cfg_attr(feature = "json", serde(rename = "assets"))]
pub tokens: Vec<Token>,
#[cfg_attr(feature = "json", serde(rename = "additionalRegisters"))]
pub additional_registers: NonMandatoryRegisters,
#[cfg_attr(feature = "json", serde(rename = "creationHeight"))]
pub creation_height: u32,
#[cfg_attr(feature = "json", serde(rename = "transactionId"))]
pub transaction_id: TxId,
#[cfg_attr(feature = "json", serde(rename = "index"))]
pub index: u16,
}
impl ErgoBox {
pub const MAX_TOKENS_COUNT: usize = u8::MAX as usize;
pub fn new(
value: BoxValue,
ergo_tree: ErgoTree,
tokens: Vec<Token>,
additional_registers: NonMandatoryRegisters,
creation_height: u32,
transaction_id: TxId,
index: u16,
) -> ErgoBox {
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();
ErgoBox {
box_id,
..box_with_zero_id
}
}
pub fn box_id(&self) -> BoxId {
self.box_id.clone()
}
pub fn from_box_candidate(
box_candidate: &ErgoBoxCandidate,
transaction_id: TxId,
index: u16,
) -> ErgoBox {
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();
ErgoBox {
box_id,
..box_with_zero_id
}
}
fn calc_box_id(&self) -> BoxId {
let bytes = self.sigma_serialize_bytes();
BoxId(blake2b256_hash(&bytes))
}
}
pub trait ErgoBoxAssets {
fn value(&self) -> BoxValue;
fn tokens(&self) -> Vec<Token>;
}
pub fn sum_value<T: ErgoBoxAssets>(bs: &[T]) -> u64 {
bs.iter().map(|b| *b.value().as_u64()).sum()
}
pub fn sum_tokens(ts: &[Token]) -> HashMap<TokenId, TokenAmount> {
let mut res: HashMap<TokenId, TokenAmount> = HashMap::new();
ts.iter().for_each(|t| {
res.entry(t.token_id.clone())
.and_modify(|amt| *amt = amt.checked_add(&t.amount).unwrap())
.or_insert(t.amount);
});
res
}
pub fn sum_tokens_from_boxes<T: ErgoBoxAssets>(bs: &[T]) -> HashMap<TokenId, TokenAmount> {
let mut res: HashMap<TokenId, TokenAmount> = HashMap::new();
bs.iter().for_each(|b| {
b.tokens().iter().for_each(|t| {
res.entry(t.token_id.clone())
.and_modify(|amt| *amt = amt.checked_add(&t.amount).unwrap())
.or_insert(t.amount);
});
});
res
}
#[derive(PartialEq, Eq, Debug, Clone)]
pub struct ErgoBoxAssetsData {
pub value: BoxValue,
pub tokens: Vec<Token>,
}
impl ErgoBoxAssets for ErgoBoxAssetsData {
fn value(&self) -> BoxValue {
self.value
}
fn tokens(&self) -> Vec<Token> {
self.tokens.clone()
}
}
impl ErgoBoxAssets for ErgoBoxCandidate {
fn value(&self) -> BoxValue {
self.value
}
fn tokens(&self) -> Vec<Token> {
self.tokens.clone()
}
}
impl ErgoBoxAssets for ErgoBox {
fn value(&self) -> BoxValue {
self.value
}
fn tokens(&self) -> Vec<Token> {
self.tokens.clone()
}
}
pub trait ErgoBoxId {
fn box_id(&self) -> BoxId;
}
impl ErgoBoxId for ErgoBox {
fn box_id(&self) -> BoxId {
self.box_id.clone()
}
}
#[cfg(feature = "json")]
#[derive(Error, PartialEq, Eq, Debug, Clone)]
pub enum ErgoBoxFromJsonError {
#[error("Box id parsed from JSON differs from calculated from box serialized bytes")]
InvalidBoxId,
}
#[cfg(feature = "json")]
impl TryFrom<json::ergo_box::ErgoBoxFromJson> for ErgoBox {
type Error = ErgoBoxFromJsonError;
fn try_from(box_json: json::ergo_box::ErgoBoxFromJson) -> Result<Self, Self::Error> {
let box_with_zero_id = ErgoBox {
box_id: BoxId::zero(),
value: box_json.value,
ergo_tree: box_json.ergo_tree,
tokens: box_json.tokens,
additional_registers: box_json.additional_registers,
creation_height: box_json.creation_height,
transaction_id: box_json.transaction_id,
index: box_json.index,
};
let box_id = box_with_zero_id.calc_box_id();
let ergo_box = ErgoBox {
box_id,
..box_with_zero_id
};
if ergo_box.box_id() == box_json.box_id {
Ok(ergo_box)
} else {
Err(ErgoBoxFromJsonError::InvalidBoxId)
}
}
}
impl SigmaSerializable for ErgoBox {
fn sigma_serialize<W: SigmaByteWrite>(&self, w: &mut W) -> Result<(), io::Error> {
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, SerializationError> {
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))
}
}
#[derive(PartialEq, Eq, Clone, Debug)]
pub struct ErgoBoxCandidate {
pub value: BoxValue,
pub ergo_tree: ErgoTree,
pub tokens: Vec<Token>,
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,
) -> Result<(), io::Error> {
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, SerializationError> {
parse_box_with_indexed_digests(digests_in_tx, r)
}
}
impl SigmaSerializable for ErgoBoxCandidate {
fn sigma_serialize<W: SigmaByteWrite>(&self, w: &mut W) -> Result<(), io::Error> {
self.serialize_body_with_indexed_digests(None, w)
}
fn sigma_parse<R: SigmaByteRead>(r: &mut R) -> Result<Self, SerializationError> {
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,
}
}
}
#[cfg(test)]
mod tests {
use super::box_value::tests::ArbBoxValueRange;
use super::*;
use crate::serialization::sigma_serialize_roundtrip;
use crate::test_util::force_any_val;
use proptest::{arbitrary::Arbitrary, collection::vec, prelude::*};
impl Arbitrary for ErgoBoxCandidate {
type Parameters = ArbBoxValueRange;
fn arbitrary_with(args: Self::Parameters) -> Self::Strategy {
(
any_with::<BoxValue>(args),
any::<ErgoTree>(),
vec(any::<Token>(), 0..3),
any::<u32>(),
any::<NonMandatoryRegisters>(),
)
.prop_map(
|(value, ergo_tree, tokens, creation_height, additional_registers)| Self {
value,
ergo_tree,
tokens,
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)
})
.boxed()
}
type Strategy = BoxedStrategy<Self>;
}
impl Arbitrary for ErgoBoxAssetsData {
type Parameters = ArbBoxValueRange;
fn arbitrary_with(args: Self::Parameters) -> Self::Strategy {
(any_with::<BoxValue>(args), vec(any::<Token>(), 0..3))
.prop_map(|(value, tokens)| Self { value, tokens })
.boxed()
}
type Strategy = BoxedStrategy<Self>;
}
#[test]
fn test_sum_tokens_repeating_token_id() {
let token = force_any_val::<Token>();
let b = ErgoBoxAssetsData {
value: BoxValue::SAFE_USER_MIN,
tokens: vec![token.clone(), token.clone()],
};
assert_eq!(
u64::from(
*sum_tokens_from_boxes(vec![b.clone(), b].as_slice())
.get(&token.token_id)
.unwrap()
),
u64::from(token.amount) * 4
);
}
proptest! {
#[test]
fn sum_tokens_eq(b in any::<ErgoBoxAssetsData>()) {
prop_assert_eq!(sum_tokens(b.tokens().as_slice()), sum_tokens_from_boxes(vec![b].as_slice()))
}
#[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];
}
}
}