hive-rs 0.1.0

A Rust client library for the Hive blockchain with 1:1 dhive API parity
Documentation
mod asset_helpers;
mod nonce;

use serde_json::Value;

use crate::error::{HiveError, Result};
use crate::serialization::types::{
    write_asset, write_price, write_public_key, write_string, write_u16, write_u32,
};
use crate::types::OperationName;
use crate::types::{Asset, Price, WitnessProps, WitnessSetPropertiesOperation};

pub use asset_helpers::{get_vesting_share_price, get_vests};
pub use nonce::unique_nonce;

pub fn make_bit_mask_filter(operations: &[OperationName]) -> (u64, u64) {
    let mut lower = 0_u64;
    let mut upper = 0_u64;

    for operation in operations {
        let id = operation.id();
        if id < 64 {
            lower |= 1_u64 << id;
        } else {
            upper |= 1_u64 << (id - 64);
        }
    }

    (lower, upper)
}

pub fn build_witness_update_op(
    owner: &str,
    props: WitnessProps,
) -> Result<WitnessSetPropertiesOperation> {
    let mut serialized_props = Vec::new();

    for (key, value) in props.extra {
        let mut buf = Vec::new();
        match key.as_str() {
            "key" | "new_signing_key" => {
                let key_str = value
                    .as_str()
                    .ok_or_else(|| HiveError::Serialization(format!("{key} must be a string")))?;
                write_public_key(&mut buf, key_str)?;
            }
            "account_subsidy_budget" | "account_subsidy_decay" | "maximum_block_size" => {
                let number = parse_u32(&value, &key)?;
                write_u32(&mut buf, number);
            }
            "hbd_interest_rate" => {
                let number = parse_u16(&value, &key)?;
                write_u16(&mut buf, number);
            }
            "url" => {
                let url = value
                    .as_str()
                    .ok_or_else(|| HiveError::Serialization("url must be a string".to_string()))?;
                write_string(&mut buf, url);
            }
            "hbd_exchange_rate" => {
                let price: Price = serde_json::from_value(value).map_err(HiveError::from)?;
                write_price(&mut buf, &price)?;
            }
            "account_creation_fee" => {
                let fee: Asset = serde_json::from_value(value).map_err(HiveError::from)?;
                write_asset(&mut buf, &fee)?;
            }
            _ => {
                return Err(HiveError::Serialization(format!(
                    "unknown witness prop: {key}"
                )));
            }
        }

        serialized_props.push((key, buf));
    }

    serialized_props.sort_by(|a, b| a.0.cmp(&b.0));

    Ok(WitnessSetPropertiesOperation {
        owner: owner.to_string(),
        props: serialized_props,
        extensions: vec![],
    })
}

fn parse_u32(value: &Value, field: &str) -> Result<u32> {
    let Some(number) = value.as_u64() else {
        return Err(HiveError::Serialization(format!(
            "{field} must be an unsigned integer"
        )));
    };
    u32::try_from(number)
        .map_err(|_| HiveError::Serialization(format!("{field} is out of u32 range")))
}

fn parse_u16(value: &Value, field: &str) -> Result<u16> {
    let Some(number) = value.as_u64() else {
        return Err(HiveError::Serialization(format!(
            "{field} must be an unsigned integer"
        )));
    };
    u16::try_from(number)
        .map_err(|_| HiveError::Serialization(format!("{field} is out of u16 range")))
}

#[cfg(test)]
mod tests {
    use serde_json::json;

    use crate::types::{OperationName, WitnessProps};
    use crate::utils::{build_witness_update_op, make_bit_mask_filter};

    #[test]
    fn make_bitmask_filter_sets_expected_bits() {
        let (low, high) = make_bit_mask_filter(&[
            OperationName::Vote,
            OperationName::CustomJson,
            OperationName::RecurrentTransfer,
        ]);
        assert_eq!(low & (1 << 0), 1 << 0);
        assert_eq!(low & (1 << 18), 1 << 18);
        assert_eq!(low & (1 << 49), 1 << 49);
        assert_eq!(high, 0);
    }

    #[test]
    fn build_witness_update_op_serializes_and_sorts_props() {
        let mut props = WitnessProps::default();
        props
            .extra
            .insert("url".to_string(), json!("https://example.com"));
        props
            .extra
            .insert("hbd_interest_rate".to_string(), json!(1000));

        let operation = build_witness_update_op("alice", props).expect("op should build");
        assert_eq!(operation.owner, "alice");
        assert_eq!(operation.props.len(), 2);
        assert_eq!(operation.props[0].0, "hbd_interest_rate");
        assert_eq!(operation.props[1].0, "url");
    }
}