near_primitives/action/
delegate.rs1use super::Action;
7use crate::signable_message::{SignableMessage, SignableMessageType};
8use borsh::{BorshDeserialize, BorshSerialize};
9use near_crypto::{PublicKey, Signature, Signer};
10use near_primitives_core::hash::{CryptoHash, hash};
11use near_primitives_core::types::BlockHeight;
12use near_primitives_core::types::{AccountId, Nonce};
13use near_schema_checker_lib::ProtocolSchema;
14use serde::{Deserialize, Serialize};
15use std::io::{Error, ErrorKind, Read};
16
17const ACTION_DELEGATE_NUMBER: u8 = 8;
19#[derive(
21 BorshSerialize,
22 BorshDeserialize,
23 Serialize,
24 Deserialize,
25 PartialEq,
26 Eq,
27 Clone,
28 Debug,
29 ProtocolSchema,
30)]
31#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
32pub struct DelegateAction {
33 pub sender_id: AccountId,
35 pub receiver_id: AccountId,
37 pub actions: Vec<NonDelegateAction>,
42 pub nonce: Nonce,
46 pub max_block_height: BlockHeight,
48 pub public_key: PublicKey,
50}
51
52#[derive(
53 BorshSerialize,
54 BorshDeserialize,
55 Serialize,
56 Deserialize,
57 PartialEq,
58 Eq,
59 Clone,
60 Debug,
61 ProtocolSchema,
62)]
63#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
64pub struct SignedDelegateAction {
65 pub delegate_action: DelegateAction,
66 pub signature: Signature,
67}
68
69impl SignedDelegateAction {
70 pub fn verify(&self) -> bool {
71 let delegate_action = &self.delegate_action;
72 let hash = delegate_action.get_nep461_hash();
73 let public_key = &delegate_action.public_key;
74
75 self.signature.verify(hash.as_ref(), public_key)
76 }
77
78 pub fn sign(singer: &Signer, delegate_action: DelegateAction) -> Self {
79 let signature = singer.sign(delegate_action.get_nep461_hash().as_bytes());
80 Self { delegate_action, signature }
81 }
82}
83
84impl From<SignedDelegateAction> for Action {
85 fn from(delegate_action: SignedDelegateAction) -> Self {
86 Self::Delegate(Box::new(delegate_action))
87 }
88}
89
90impl DelegateAction {
91 pub fn get_actions(&self) -> Vec<Action> {
92 self.actions.iter().map(|a| a.clone().into()).collect()
93 }
94
95 pub fn get_nep461_hash(&self) -> CryptoHash {
100 let signable = SignableMessage::new(&self, SignableMessageType::DelegateAction);
101 let bytes = borsh::to_vec(&signable).expect("Failed to deserialize");
102 hash(&bytes)
103 }
104}
105
106#[derive(Serialize, BorshSerialize, Deserialize, PartialEq, Eq, Clone, Debug, ProtocolSchema)]
117pub struct NonDelegateAction(Action);
118
119#[cfg(feature = "schemars")]
120impl schemars::JsonSchema for NonDelegateAction {
121 fn schema_name() -> std::borrow::Cow<'static, str> {
122 "NonDelegateAction".into()
123 }
124
125 fn json_schema(generator: &mut schemars::SchemaGenerator) -> schemars::Schema {
126 let mut action_schema = Action::json_schema(generator);
129
130 if let Some(one_of) = action_schema.get_mut("oneOf") {
132 if let Some(arr) = one_of.as_array_mut() {
133 arr.retain(|variant| {
135 !variant
136 .get("properties")
137 .and_then(|p| p.as_object())
138 .map(|p| p.contains_key("Delegate"))
139 .unwrap_or(false)
140 });
141 }
142 }
143
144 action_schema.insert("description".to_string(), serde_json::json!(
146 "An Action that can be included in a transaction or receipt, excluding delegate actions. \
147 This type represents all possible action types except DelegateAction to prevent \
148 infinite recursion in meta-transactions."
149 ));
150
151 action_schema
152 }
153}
154
155mod private_non_delegate_action {
157 use super::*;
158
159 impl From<NonDelegateAction> for Action {
160 fn from(action: NonDelegateAction) -> Self {
161 action.0
162 }
163 }
164
165 #[derive(Debug, thiserror::Error)]
166 #[error("attempted to construct NonDelegateAction from Action::Delegate")]
167 pub struct IsDelegateAction;
168
169 impl TryFrom<Action> for NonDelegateAction {
170 type Error = IsDelegateAction;
171
172 fn try_from(action: Action) -> Result<Self, IsDelegateAction> {
173 if matches!(action, Action::Delegate(_)) {
174 Err(IsDelegateAction)
175 } else {
176 Ok(Self(action))
177 }
178 }
179 }
180
181 impl borsh::de::BorshDeserialize for NonDelegateAction {
182 fn deserialize_reader<R: Read>(rd: &mut R) -> ::core::result::Result<Self, Error> {
183 match u8::deserialize_reader(rd)? {
184 ACTION_DELEGATE_NUMBER => Err(Error::new(
185 ErrorKind::InvalidInput,
186 "DelegateAction mustn't contain a nested one",
187 )),
188 n => borsh::de::EnumExt::deserialize_variant(rd, n).map(Self),
189 }
190 }
191 }
192}
193
194#[cfg(test)]
195mod tests {
196 use super::*;
197 use crate::action::CreateAccountAction;
198 use near_crypto::KeyType;
199
200 const DELEGATE_ACTION_HEX: &str = concat!(
207 "0803000000616161030000006262620100000000010000000000000002000000000000",
208 "0000000000000000000000000000000000000000000000000000000000000000000000",
209 "0000000000000000000000000000000000000000000000000000000000000000000000",
210 "0000000000000000000000000000000000000000000000000000000000"
211 );
212
213 fn create_delegate_action(actions: Vec<Action>) -> Action {
214 Action::Delegate(Box::new(SignedDelegateAction {
215 delegate_action: DelegateAction {
216 sender_id: "aaa".parse().unwrap(),
217 receiver_id: "bbb".parse().unwrap(),
218 actions: actions
219 .iter()
220 .map(|a| NonDelegateAction::try_from(a.clone()).unwrap())
221 .collect(),
222 nonce: 1,
223 max_block_height: 2,
224 public_key: PublicKey::empty(KeyType::ED25519),
225 },
226 signature: Signature::empty(KeyType::ED25519),
227 }))
228 }
229
230 #[test]
231 fn test_delegate_action_deserialization() {
232 assert_eq!(
234 NonDelegateAction::try_from_slice(Vec::new().as_ref()).map_err(|e| e.kind()),
235 Err(ErrorKind::InvalidData)
236 );
237
238 let delegate_action = create_delegate_action(Vec::<Action>::new());
239 let serialized_non_delegate_action = borsh::to_vec(&delegate_action).expect("Expect ok");
240
241 assert_eq!(serialized_non_delegate_action[0], ACTION_DELEGATE_NUMBER);
243
244 assert_eq!(
246 NonDelegateAction::try_from_slice(&serialized_non_delegate_action)
247 .map_err(|e| e.kind()),
248 Err(ErrorKind::InvalidInput)
249 );
250
251 let delegate_action =
252 create_delegate_action(vec![Action::CreateAccount(CreateAccountAction {})]);
253 let serialized_delegate_action = borsh::to_vec(&delegate_action).expect("Expect ok");
254
255 assert_eq!(
257 Action::try_from_slice(&serialized_delegate_action).expect("Expect ok"),
258 delegate_action
259 );
260 }
261
262 #[test]
264 fn test_delegate_action_deserialization_hard_coded() {
265 let serialized_delegate_action = hex::decode(DELEGATE_ACTION_HEX).expect("invalid hex");
266 let delegate_action =
268 create_delegate_action(vec![Action::CreateAccount(CreateAccountAction {})]);
269
270 assert_eq!(
272 Action::try_from_slice(&serialized_delegate_action).expect("Expect ok"),
273 delegate_action
274 );
275 }
276
277 #[cfg(feature = "schemars")]
278 #[test]
279 fn test_non_delegate_action_json_schema_excludes_delegate() {
280 use schemars::{JsonSchema, SchemaGenerator};
281
282 let mut generator = SchemaGenerator::default();
284
285 let non_delegate_schema = NonDelegateAction::json_schema(&mut generator);
287
288 let schema_json = serde_json::to_value(&non_delegate_schema).unwrap();
290
291 let one_of = schema_json
293 .get("oneOf")
294 .expect("NonDelegateAction schema must have oneOf")
295 .as_array()
296 .expect("NonDelegateAction oneOf must be an array");
297
298 for variant in one_of {
300 if let Some(properties) = variant.get("properties") {
301 if let Some(props_obj) = properties.as_object() {
302 assert!(
303 !props_obj.contains_key("Delegate"),
304 "NonDelegateAction schema should not contain Delegate variant"
305 );
306 }
307 }
308 }
309
310 let action_schema = Action::json_schema(&mut generator);
312 let action_json = serde_json::to_value(&action_schema).unwrap();
313
314 let action_one_of = action_json
316 .get("oneOf")
317 .expect("Action schema must have oneOf")
318 .as_array()
319 .expect("Action oneOf must be an array");
320
321 let delegate_count = action_one_of
323 .iter()
324 .filter(|variant| {
325 variant
326 .get("properties")
327 .and_then(|p| p.as_object())
328 .map(|p| p.contains_key("Delegate"))
329 .unwrap_or(false)
330 })
331 .count();
332
333 assert_eq!(delegate_count, 1, "Action schema should contain exactly one Delegate variant");
334
335 assert_eq!(
337 one_of.len(),
338 action_one_of.len() - 1,
339 "NonDelegateAction should have one less variant than Action (excluding Delegate)"
340 );
341 }
342}