mod protobuf;
#[cfg(test)]
mod tests;
use prost::Message;
use thiserror::Error;
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct IcpTransferFields {
pub from: [u8; 32],
pub to: [u8; 32],
pub amount_e8s: u64,
pub fee_e8s: u64,
pub memo: u64,
pub created_at_time_ns: u64,
}
#[derive(Clone, Copy, Debug, Error, Eq, PartialEq)]
pub enum IcpBlockError {
#[error("ICP block exceeds its byte budget")]
BudgetExceeded,
#[error("ICP transfer account identifier is not 32 bytes")]
InvalidAccount,
#[error("ICP block has an unsupported or noncanonical protobuf schema")]
UnsupportedBlock,
}
pub fn decode_transfer(
bytes: &[u8],
index: u64,
maximum_bytes: usize,
) -> Result<IcpTransferFields, IcpBlockError> {
if bytes.len() > maximum_bytes {
return Err(IcpBlockError::BudgetExceeded);
}
let block = protobuf::Block::decode(bytes).map_err(|_| IcpBlockError::UnsupportedBlock)?;
if block.encode_to_vec() != bytes || block.timestamp.is_none() {
return Err(IcpBlockError::UnsupportedBlock);
}
match (&block.parent_hash, index) {
(None, 0) => {}
(Some(hash), 1..) if hash.value.len() == 32 => {}
_ => return Err(IcpBlockError::UnsupportedBlock),
}
let transaction = block.transaction.ok_or(IcpBlockError::UnsupportedBlock)?;
let send = transaction.send.ok_or(IcpBlockError::UnsupportedBlock)?;
Ok(IcpTransferFields {
from: send
.from
.ok_or(IcpBlockError::UnsupportedBlock)?
.value
.try_into()
.map_err(|_| IcpBlockError::InvalidAccount)?,
to: send
.to
.ok_or(IcpBlockError::UnsupportedBlock)?
.value
.try_into()
.map_err(|_| IcpBlockError::InvalidAccount)?,
amount_e8s: send.amount.ok_or(IcpBlockError::UnsupportedBlock)?.value,
fee_e8s: send.fee.ok_or(IcpBlockError::UnsupportedBlock)?.value,
memo: transaction
.memo
.ok_or(IcpBlockError::UnsupportedBlock)?
.value,
created_at_time_ns: transaction
.created_at_time
.ok_or(IcpBlockError::UnsupportedBlock)?
.value,
})
}