mod create_identifier;
mod manage_certificate;
mod modify_permissions;
mod receive;
mod send;
mod set_info;
mod set_rep;
mod token_admin_modify_balance;
mod token_admin_supply;
pub use create_identifier::{CreateIdentifier, IdentifierCreateArguments, MultisigCreateArguments};
pub use manage_certificate::{CertificateDer, CertificateOrHash, IntermediateCertificates, ManageCertificate};
pub use modify_permissions::{ModifyPermissions, ModifyPermissionsPrincipal};
pub use receive::Receive;
pub use send::Send;
pub use set_info::SetInfo;
pub use set_rep::SetRep;
pub use token_admin_modify_balance::TokenAdminModifyBalance;
pub use token_admin_supply::TokenAdminSupply;
use keetanetwork_account::{GenericAccount, KeyPairType};
use keetanetwork_crypto::hash::BlockHash;
use num_bigint::BigInt;
use crate::error::BlockError;
use crate::validation::ValidationConfig;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[repr(u8)]
pub enum AdjustMethod {
Add = 0,
Subtract = 1,
Set = 2,
}
impl AdjustMethod {
pub(crate) fn to_bigint(self) -> BigInt {
BigInt::from(u8::from(self))
}
}
impl From<AdjustMethod> for u8 {
fn from(method: AdjustMethod) -> Self {
method as u8
}
}
impl TryFrom<u8> for AdjustMethod {
type Error = BlockError;
fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
0 => Ok(AdjustMethod::Add),
1 => Ok(AdjustMethod::Subtract),
2 => Ok(AdjustMethod::Set),
_ => Err(BlockError::InvalidAdjustMethod),
}
}
}
impl TryFrom<&BigInt> for AdjustMethod {
type Error = BlockError;
fn try_from(value: &BigInt) -> Result<Self, Self::Error> {
u8::try_from(value)
.map_err(|_| BlockError::InvalidAdjustMethod)
.and_then(AdjustMethod::try_from)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[repr(u8)]
pub enum OperationType {
Send = 0,
SetRep = 1,
SetInfo = 2,
ModifyPermissions = 3,
CreateIdentifier = 4,
TokenAdminSupply = 5,
TokenAdminModifyBalance = 6,
Receive = 7,
ManageCertificate = 8,
}
impl From<OperationType> for u8 {
fn from(operation_type: OperationType) -> Self {
operation_type as u8
}
}
impl TryFrom<u8> for OperationType {
type Error = BlockError;
fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
0 => Ok(OperationType::Send),
1 => Ok(OperationType::SetRep),
2 => Ok(OperationType::SetInfo),
3 => Ok(OperationType::ModifyPermissions),
4 => Ok(OperationType::CreateIdentifier),
5 => Ok(OperationType::TokenAdminSupply),
6 => Ok(OperationType::TokenAdminModifyBalance),
7 => Ok(OperationType::Receive),
8 => Ok(OperationType::ManageCertificate),
_ => Err(BlockError::InvalidOperationType),
}
}
}
pub(crate) trait BlockOperation: Into<Operation> {
const TYPE: OperationType;
fn validate(&self, ctx: &OperationContext<'_>) -> Result<(), BlockError>;
}
pub(crate) trait FromOperationRef<'a>: Sized {
fn from_operation_ref(operation: &'a Operation) -> Option<Self>;
}
#[derive(Debug, Clone)]
pub enum Operation {
Send(Send),
SetRep(SetRep),
SetInfo(SetInfo),
ModifyPermissions(ModifyPermissions),
CreateIdentifier(CreateIdentifier),
TokenAdminSupply(TokenAdminSupply),
TokenAdminModifyBalance(TokenAdminModifyBalance),
Receive(Receive),
ManageCertificate(ManageCertificate),
}
macro_rules! dispatch_operations {
($($variant:ident),+ $(,)?) => {
$(
impl From<$variant> for Operation {
fn from(operation: $variant) -> Self {
Operation::$variant(operation)
}
}
impl<'a> FromOperationRef<'a> for &'a $variant {
fn from_operation_ref(operation: &'a Operation) -> Option<Self> {
match operation {
Operation::$variant(inner) => Some(inner),
_ => None,
}
}
}
)+
impl Operation {
pub fn operation_type(&self) -> OperationType {
match self {
$( Operation::$variant(_) => <$variant as BlockOperation>::TYPE, )+
}
}
pub(crate) fn validate(&self, ctx: &OperationContext<'_>) -> Result<(), BlockError> {
match self {
$( Operation::$variant(operation) => operation.validate(ctx), )+
}
}
}
};
}
dispatch_operations!(
Send,
SetRep,
SetInfo,
ModifyPermissions,
CreateIdentifier,
TokenAdminSupply,
TokenAdminModifyBalance,
Receive,
ManageCertificate,
);
pub(crate) struct OperationContext<'a> {
pub config: Option<&'a ValidationConfig>,
pub account: &'a GenericAccount,
pub operations: &'a [Operation],
pub previous: &'a BlockHash,
pub date_ms: i64,
pub operation_index: usize,
}
impl<'a> OperationContext<'a> {
fn iter_type<T: 'a>(&self) -> impl Iterator<Item = &'a T>
where
&'a T: FromOperationRef<'a>,
{
self.operations
.iter()
.filter_map(<&'a T>::from_operation_ref)
}
fn config(&self) -> Result<&ValidationConfig, BlockError> {
self.config.ok_or(BlockError::UnknownNetwork)
}
fn validate_numeric(&self, value: &BigInt) -> Result<(), BlockError> {
if *value >= BigInt::ZERO {
return Ok(());
}
self.config()?.validate_numeric_value(value, self.date_ms)
}
fn account_is_token(&self) -> bool {
self.account.to_keypair_type() == KeyPairType::TOKEN
}
fn guard_token_amount(&self, token: &GenericAccount, amount: &BigInt) -> Result<(), BlockError> {
require_token(token)?;
self.validate_numeric(amount)
}
fn reject_token_account(&self) -> Result<(), BlockError> {
if self.account_is_token() {
return Err(BlockError::TokenOperationForbidden);
}
Ok(())
}
}
fn require_token(account: &GenericAccount) -> Result<(), BlockError> {
if account.to_keypair_type() != KeyPairType::TOKEN {
return Err(BlockError::TokenFieldNotToken);
}
Ok(())
}
fn can_delegate(key_type: KeyPairType) -> bool {
if !key_type.is_identifier() {
return true;
}
key_type == KeyPairType::STORAGE
}
#[cfg(test)]
pub(crate) mod harness {
use alloc::string::{String, ToString};
use alloc::vec::Vec;
use keetanetwork_account::KeyPairType;
use keetanetwork_crypto::hash::BlockHash;
use crate::amount::Amount;
use crate::error::BlockError;
use crate::permissions::{BaseFlag, Permissions};
use crate::signer::AccountRef;
use crate::testing::{generate_ed25519_ref, generate_identifier_ref};
use crate::validation::ValidationConfig;
use super::{AdjustMethod, CertificateOrHash, Receive, Send, SetInfo};
use super::{ManageCertificate, ModifyPermissions, ModifyPermissionsPrincipal, Operation, OperationContext};
pub(crate) const PRE_CUTOFF_MS: i64 = 1_700_000_000_000;
pub(crate) struct Harness {
pub(crate) config: ValidationConfig,
pub(crate) account: AccountRef,
pub(crate) previous: BlockHash,
pub(crate) operations: Vec<Operation>,
pub(crate) date_ms: i64,
}
impl Harness {
pub(crate) fn new(account: AccountRef) -> Self {
let previous = account.to_opening_hash();
Self {
config: ValidationConfig::default(),
account,
previous,
operations: Vec::new(),
date_ms: PRE_CUTOFF_MS,
}
}
pub(crate) fn validate(&self, operation: &Operation) -> Result<(), BlockError> {
let ctx = OperationContext {
config: Some(&self.config),
account: &self.account,
operations: &self.operations,
previous: &self.previous,
date_ms: self.date_ms,
operation_index: 0,
};
operation.validate(&ctx)
}
}
pub(crate) fn send(token: AccountRef, to: AccountRef) -> Send {
Send { to, amount: Amount::from(1u64), token, external: None }
}
pub(crate) fn token(index: u32) -> AccountRef {
generate_identifier_ref(1, KeyPairType::TOKEN, index)
}
pub(crate) fn receive(token: AccountRef, from: AccountRef) -> Receive {
Receive { amount: Amount::from(1u64), token, from, exact: false, forward: None }
}
pub(crate) fn set_info() -> SetInfo {
SetInfo {
name: "MY_ACCOUNT".to_string(),
description: "A description".to_string(),
metadata: String::new(),
default_permission: None,
}
}
pub(crate) fn permissions(flags: &[BaseFlag], externals: &[u8]) -> Permissions {
Permissions::from_flags(flags, externals).expect("test permission construction must succeed")
}
pub(crate) fn modify_permissions(permissions: Option<Permissions>) -> ModifyPermissions {
ModifyPermissions {
principal: ModifyPermissionsPrincipal::Account(generate_ed25519_ref(2)),
method: AdjustMethod::Set,
permissions,
target: None,
}
}
pub(crate) fn manage_certificate_subtract(hash_byte: u8) -> ManageCertificate {
ManageCertificate {
method: AdjustMethod::Subtract,
certificate_or_hash: CertificateOrHash::Hash([hash_byte; 32]),
intermediate_certificates: None,
}
}
macro_rules! assert_validation {
($( $label:literal: $setup:expr => $expected:pat ),* $(,)?) => {{
$( {
let _case = $label;
let (harness, operation) = $setup;
assert!(matches!(harness.validate(&operation), $expected));
} )*
}};
}
pub(crate) use assert_validation;
}
#[cfg(test)]
mod tests {
use super::*;
use crate::operation::harness::{send, token};
use crate::testing::generate_ed25519_ref;
#[test]
fn test_operation_type_roundtrip() -> Result<(), BlockError> {
for tag in 0u8..=8 {
let operation_type = OperationType::try_from(tag)?;
assert_eq!(u8::from(operation_type), tag);
}
assert!(matches!(OperationType::try_from(9u8), Err(BlockError::InvalidOperationType)));
Ok(())
}
#[test]
fn test_adjust_method_roundtrip() -> Result<(), BlockError> {
for tag in 0u8..=2 {
let method = AdjustMethod::try_from(tag)?;
assert_eq!(u8::from(method), tag);
}
assert!(matches!(AdjustMethod::try_from(3u8), Err(BlockError::InvalidAdjustMethod)));
assert!(matches!(AdjustMethod::try_from(&BigInt::from(-1)), Err(BlockError::InvalidAdjustMethod)));
Ok(())
}
#[test]
fn test_operation_type_dispatch() {
let operation: Operation = send(token(0), generate_ed25519_ref(2)).into();
assert_eq!(operation.operation_type(), OperationType::Send);
}
}