use alloc::string::String;
use keetanetwork_account::KeyPairType;
use crate::account_util::accounts_equal;
use crate::amount::Amount;
use crate::error::BlockError;
use crate::signer::AccountRef;
use crate::validation::TextRuleViolation;
use super::{BlockOperation, OperationContext, OperationType};
#[derive(Debug, Clone)]
pub struct Send {
pub to: AccountRef,
pub amount: Amount,
pub token: AccountRef,
pub external: Option<String>,
}
impl BlockOperation for Send {
const TYPE: OperationType = OperationType::Send;
fn validate(&self, ctx: &OperationContext<'_>) -> Result<(), BlockError> {
ctx.guard_token_amount(&self.token, self.amount.as_bigint())?;
if ctx.account_is_token() && !accounts_equal(&self.token, ctx.account) {
return Err(BlockError::TokenOperationForbidden);
}
if self.to.to_keypair_type() == KeyPairType::TOKEN && !accounts_equal(&self.to, &self.token) {
return Err(BlockError::TokenReceiveDiffers);
}
let config = ctx.config()?;
match &self.external {
Some(external) if !external.is_empty() => match config.external.check(external) {
Err(TextRuleViolation::TooLong { length, max }) => {
return Err(BlockError::ExternalTooLong { length, max });
}
Err(TextRuleViolation::InvalidCharacter) => return Err(BlockError::ExternalInvalid),
Ok(()) => {}
},
_ => {
if !config.external.can_be_empty {
return Err(BlockError::ExternalMissing);
}
}
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use alloc::string::ToString;
use super::*;
use crate::operation::harness::{assert_validation, send, token, Harness};
use crate::testing::generate_ed25519_ref;
#[test]
fn test_send_validation() {
assert_validation! {
"rejects_non_token_token_field":
(Harness::new(generate_ed25519_ref(1)), send(generate_ed25519_ref(2), generate_ed25519_ref(3)).into())
=> Err(BlockError::TokenFieldNotToken),
"rejects_token_account_sending_other_token":
(Harness::new(token(0)), send(token(1), generate_ed25519_ref(2)).into())
=> Err(BlockError::TokenOperationForbidden),
"rejects_token_destination_mismatch":
(Harness::new(generate_ed25519_ref(1)), send(token(0), token(1)).into())
=> Err(BlockError::TokenReceiveDiffers),
"rejects_external_too_long": {
let mut operation = send(token(0), generate_ed25519_ref(2));
operation.external = Some("A".repeat(1025));
(Harness::new(generate_ed25519_ref(1)), operation.into())
} => Err(BlockError::ExternalTooLong { .. }),
"rejects_external_invalid_character": {
let mut operation = send(token(0), generate_ed25519_ref(2));
operation.external = Some("bad☃".to_string());
(Harness::new(generate_ed25519_ref(1)), operation.into())
} => Err(BlockError::ExternalInvalid),
"rejects_negative_amount_after_cutoff": {
let mut harness = Harness::new(generate_ed25519_ref(1));
harness.date_ms = harness.config.numeric_cutoff_epoch_ms;
let mut operation = send(token(0), generate_ed25519_ref(2));
operation.amount = Amount::from(-1i64);
(harness, operation.into())
} => Err(BlockError::AmountBelowZero),
"accepts_valid_operation":
(Harness::new(generate_ed25519_ref(1)), send(token(0), generate_ed25519_ref(2)).into())
=> Ok(()),
}
}
}