use super::Action;
use crate::signable_message::{SignableMessage, SignableMessageType};
use borsh::{BorshDeserialize, BorshSerialize};
use near_crypto::{PublicKey, Signature, Signer};
use near_primitives_core::hash::{CryptoHash, hash};
use near_primitives_core::types::BlockHeight;
use near_primitives_core::types::{AccountId, Nonce};
use near_schema_checker_lib::ProtocolSchema;
use serde::{Deserialize, Serialize};
use std::io::{Error, ErrorKind, Read};
const ACTION_DELEGATE_NUMBER: u8 = 8;
#[derive(
BorshSerialize,
BorshDeserialize,
Serialize,
Deserialize,
PartialEq,
Eq,
Clone,
Debug,
ProtocolSchema,
)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct DelegateAction {
pub sender_id: AccountId,
pub receiver_id: AccountId,
pub actions: Vec<NonDelegateAction>,
pub nonce: Nonce,
pub max_block_height: BlockHeight,
pub public_key: PublicKey,
}
#[derive(
BorshSerialize,
BorshDeserialize,
Serialize,
Deserialize,
PartialEq,
Eq,
Clone,
Debug,
ProtocolSchema,
)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct SignedDelegateAction {
pub delegate_action: DelegateAction,
pub signature: Signature,
}
impl SignedDelegateAction {
pub fn verify(&self) -> bool {
let delegate_action = &self.delegate_action;
let hash = delegate_action.get_nep461_hash();
let public_key = &delegate_action.public_key;
self.signature.verify(hash.as_ref(), public_key)
}
pub fn sign(singer: &Signer, delegate_action: DelegateAction) -> Self {
let signature = singer.sign(delegate_action.get_nep461_hash().as_bytes());
Self { delegate_action, signature }
}
}
impl From<SignedDelegateAction> for Action {
fn from(delegate_action: SignedDelegateAction) -> Self {
Self::Delegate(Box::new(delegate_action))
}
}
impl DelegateAction {
pub fn get_actions(&self) -> Vec<Action> {
self.actions.iter().map(|a| a.clone().into()).collect()
}
pub fn get_nep461_hash(&self) -> CryptoHash {
let signable = SignableMessage::new(&self, SignableMessageType::DelegateAction);
let bytes = borsh::to_vec(&signable).expect("Failed to deserialize");
hash(&bytes)
}
}
#[derive(Serialize, BorshSerialize, Deserialize, PartialEq, Eq, Clone, Debug, ProtocolSchema)]
pub struct NonDelegateAction(Action);
#[cfg(feature = "schemars")]
impl schemars::JsonSchema for NonDelegateAction {
fn schema_name() -> std::borrow::Cow<'static, str> {
"NonDelegateAction".into()
}
fn json_schema(generator: &mut schemars::SchemaGenerator) -> schemars::Schema {
let mut action_schema = Action::json_schema(generator);
if let Some(one_of) = action_schema.get_mut("oneOf")
&& let Some(arr) = one_of.as_array_mut()
{
arr.retain(|variant| {
!variant
.get("properties")
.and_then(|p| p.as_object())
.map(|p| p.contains_key("Delegate"))
.unwrap_or(false)
});
}
action_schema.insert("description".to_string(), serde_json::json!(
"An Action that can be included in a transaction or receipt, excluding delegate actions. \
This type represents all possible action types except DelegateAction to prevent \
infinite recursion in meta-transactions."
));
action_schema
}
}
mod private_non_delegate_action {
use super::*;
impl From<NonDelegateAction> for Action {
fn from(action: NonDelegateAction) -> Self {
action.0
}
}
#[derive(Debug, thiserror::Error)]
#[error("attempted to construct NonDelegateAction from Action::Delegate")]
pub struct IsDelegateAction;
impl TryFrom<Action> for NonDelegateAction {
type Error = IsDelegateAction;
fn try_from(action: Action) -> Result<Self, IsDelegateAction> {
if matches!(action, Action::Delegate(_)) {
Err(IsDelegateAction)
} else {
Ok(Self(action))
}
}
}
impl borsh::de::BorshDeserialize for NonDelegateAction {
fn deserialize_reader<R: Read>(rd: &mut R) -> ::core::result::Result<Self, Error> {
match u8::deserialize_reader(rd)? {
ACTION_DELEGATE_NUMBER => Err(Error::new(
ErrorKind::InvalidInput,
"DelegateAction mustn't contain a nested one",
)),
n => borsh::de::EnumExt::deserialize_variant(rd, n).map(Self),
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::action::CreateAccountAction;
use near_crypto::KeyType;
const DELEGATE_ACTION_HEX: &str = concat!(
"0803000000616161030000006262620100000000010000000000000002000000000000",
"0000000000000000000000000000000000000000000000000000000000000000000000",
"0000000000000000000000000000000000000000000000000000000000000000000000",
"0000000000000000000000000000000000000000000000000000000000"
);
fn create_delegate_action(actions: Vec<Action>) -> Action {
Action::Delegate(Box::new(SignedDelegateAction {
delegate_action: DelegateAction {
sender_id: "aaa".parse().unwrap(),
receiver_id: "bbb".parse().unwrap(),
actions: actions
.iter()
.map(|a| NonDelegateAction::try_from(a.clone()).unwrap())
.collect(),
nonce: 1,
max_block_height: 2,
public_key: PublicKey::empty(KeyType::ED25519),
},
signature: Signature::empty(KeyType::ED25519),
}))
}
#[test]
fn test_delegate_action_deserialization() {
assert_eq!(
NonDelegateAction::try_from_slice(Vec::new().as_ref()).map_err(|e| e.kind()),
Err(ErrorKind::InvalidData)
);
let delegate_action = create_delegate_action(Vec::<Action>::new());
let serialized_non_delegate_action = borsh::to_vec(&delegate_action).expect("Expect ok");
assert_eq!(serialized_non_delegate_action[0], ACTION_DELEGATE_NUMBER);
assert_eq!(
NonDelegateAction::try_from_slice(&serialized_non_delegate_action)
.map_err(|e| e.kind()),
Err(ErrorKind::InvalidInput)
);
let delegate_action =
create_delegate_action(vec![Action::CreateAccount(CreateAccountAction {})]);
let serialized_delegate_action = borsh::to_vec(&delegate_action).expect("Expect ok");
assert_eq!(
Action::try_from_slice(&serialized_delegate_action).expect("Expect ok"),
delegate_action
);
}
#[test]
fn test_delegate_action_deserialization_hard_coded() {
let serialized_delegate_action = hex::decode(DELEGATE_ACTION_HEX).expect("invalid hex");
let delegate_action =
create_delegate_action(vec![Action::CreateAccount(CreateAccountAction {})]);
assert_eq!(
Action::try_from_slice(&serialized_delegate_action).expect("Expect ok"),
delegate_action
);
}
#[cfg(feature = "schemars")]
#[test]
fn test_non_delegate_action_json_schema_excludes_delegate() {
use schemars::{JsonSchema, SchemaGenerator};
let mut generator = SchemaGenerator::default();
let non_delegate_schema = NonDelegateAction::json_schema(&mut generator);
let schema_json = serde_json::to_value(&non_delegate_schema).unwrap();
let one_of = schema_json
.get("oneOf")
.expect("NonDelegateAction schema must have oneOf")
.as_array()
.expect("NonDelegateAction oneOf must be an array");
for variant in one_of {
if let Some(properties) = variant.get("properties") {
if let Some(props_obj) = properties.as_object() {
assert!(
!props_obj.contains_key("Delegate"),
"NonDelegateAction schema should not contain Delegate variant"
);
}
}
}
let action_schema = Action::json_schema(&mut generator);
let action_json = serde_json::to_value(&action_schema).unwrap();
let action_one_of = action_json
.get("oneOf")
.expect("Action schema must have oneOf")
.as_array()
.expect("Action oneOf must be an array");
let delegate_count = action_one_of
.iter()
.filter(|variant| {
variant
.get("properties")
.and_then(|p| p.as_object())
.map(|p| p.contains_key("Delegate"))
.unwrap_or(false)
})
.count();
assert_eq!(delegate_count, 1, "Action schema should contain exactly one Delegate variant");
assert_eq!(
one_of.len(),
action_one_of.len() - 1,
"NonDelegateAction should have one less variant than Action (excluding Delegate)"
);
}
}