use async_trait::async_trait;
use primitive_types::H160;
use serde::{Deserialize, Serialize};
use crate::{
neo_builder::TransactionBuilder,
neo_clients::{JsonRpcProvider, RpcClient},
neo_contract::{traits::SmartContractTrait, ContractError},
neo_types::{
serde_with_utils::{deserialize_script_hash, serialize_script_hash},
ScriptHash, StackItem,
},
};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct NotaryDeposit {
pub amount: i64,
pub till: u32,
}
impl NotaryDeposit {
pub fn from_stack_item(item: &StackItem) -> Result<Self, String> {
match item {
StackItem::Struct { value } | StackItem::Array { value } if value.len() >= 2 => {
let amount = value[0].as_int().ok_or_else(|| "Invalid amount".to_string())?;
let till = value[1].as_int().ok_or_else(|| "Invalid till".to_string())? as u32;
Ok(Self { amount, till })
},
_ => Err("Expected Struct or Array with 2 elements".to_string()),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NotaryContract<'a, P: JsonRpcProvider> {
#[serde(deserialize_with = "deserialize_script_hash")]
#[serde(serialize_with = "serialize_script_hash")]
script_hash: ScriptHash,
#[serde(skip)]
provider: Option<&'a RpcClient<P>>,
}
impl<'a, P: JsonRpcProvider + 'static> NotaryContract<'a, P> {
pub const NAME: &'static str = "Notary";
pub const DEFAULT_MAX_NOT_VALID_BEFORE_DELTA: u32 = 140;
pub const DEFAULT_DEPOSIT_DELTA_TILL: u32 = 5760;
pub fn new(provider: Option<&'a RpcClient<P>>) -> Self {
Self { script_hash: Self::calc_native_contract_hash_unchecked(Self::NAME), provider }
}
pub async fn balance_of(&self, account: &H160) -> Result<i64, ContractError> {
Ok(self.call_function_returning_int("balanceOf", vec![account.into()]).await? as i64)
}
pub async fn expiration_of(&self, account: &H160) -> Result<u32, ContractError> {
Ok(self.call_function_returning_int("expirationOf", vec![account.into()]).await? as u32)
}
pub async fn get_max_not_valid_before_delta(&self) -> Result<u32, ContractError> {
Ok(self.call_function_returning_int("getMaxNotValidBeforeDelta", vec![]).await? as u32)
}
pub async fn lock_deposit_until(
&self,
account: &H160,
till: u32,
) -> Result<TransactionBuilder<'_, P>, ContractError> {
self.invoke_function("lockDepositUntil", vec![account.into(), till.into()])
.await
}
pub async fn withdraw(
&self,
from: &H160,
to: Option<&H160>,
) -> Result<TransactionBuilder<'_, P>, ContractError> {
let params = match to {
Some(to_addr) => vec![from.into(), to_addr.into()],
None => vec![from.into(), crate::neo_types::ContractParameter::any()],
};
self.invoke_function("withdraw", params).await
}
pub async fn set_max_not_valid_before_delta(
&self,
value: u32,
) -> Result<TransactionBuilder<'_, P>, ContractError> {
self.invoke_function("setMaxNotValidBeforeDelta", vec![value.into()]).await
}
pub fn supported_standards() -> Vec<&'static str> {
vec!["NEP-27", "NEP-30"]
}
}
#[async_trait]
impl<'a, P: JsonRpcProvider> SmartContractTrait<'a> for NotaryContract<'a, P> {
type P = P;
fn script_hash(&self) -> H160 {
self.script_hash
}
fn set_script_hash(&mut self, script_hash: H160) {
self.script_hash = script_hash;
}
fn provider(&self) -> Option<&RpcClient<P>> {
self.provider
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::neo_clients::MockProvider;
#[test]
fn test_notary_contract_name() {
assert_eq!(NotaryContract::<MockProvider>::NAME, "Notary");
}
#[test]
fn test_notary_default_constants() {
assert_eq!(NotaryContract::<MockProvider>::DEFAULT_MAX_NOT_VALID_BEFORE_DELTA, 140);
assert_eq!(NotaryContract::<MockProvider>::DEFAULT_DEPOSIT_DELTA_TILL, 5760);
}
#[test]
fn test_notary_supported_standards() {
let standards = NotaryContract::<MockProvider>::supported_standards();
assert!(standards.contains(&"NEP-27"));
assert!(standards.contains(&"NEP-30"));
}
#[test]
fn test_notary_contract_hash() {
let notary = NotaryContract::<MockProvider>::new(None);
assert!(!notary.script_hash.is_zero());
}
#[test]
fn test_notary_deposit_from_stack_item() {
let item = StackItem::Struct {
value: vec![
StackItem::Integer { value: 1000000000 }, StackItem::Integer { value: 100000 }, ],
};
let deposit = NotaryDeposit::from_stack_item(&item).unwrap();
assert_eq!(deposit.amount, 1000000000i64);
assert_eq!(deposit.till, 100000);
}
}