Skip to main content

near_api_types/transaction/
actions.rs

1use std::collections::BTreeMap;
2use std::str::FromStr;
3
4use crate::AccountId;
5use base64::Engine;
6use base64::prelude::BASE64_STANDARD;
7use borsh::{BorshDeserialize, BorshSerialize};
8use serde::{Deserialize, Serialize};
9use serde_with::{base64::Base64, serde_as};
10
11use crate::errors::DataConversionError;
12use crate::json::U64;
13use crate::transaction::delegate_action::SignedDelegateAction;
14use crate::utils::near_gas_as_u64;
15use crate::{CryptoHash, NearGas, NearToken, PublicKey, Signature};
16
17#[derive(Serialize, Deserialize, Debug, Clone, BorshSerialize, BorshDeserialize, PartialEq, Eq)]
18pub enum Action {
19    /// Create an (sub)account using a transaction `receiver_id` as an ID for
20    /// a new account ID must pass validation rules described here
21    /// <http://nomicon.io/Primitives/Account.html>.
22    CreateAccount(CreateAccountAction),
23    /// Sets a Wasm code to a receiver_id
24    DeployContract(DeployContractAction),
25    /// Call a function on a contract
26    FunctionCall(Box<FunctionCallAction>),
27    /// Transfer tokens to an account
28    Transfer(TransferAction),
29    /// Stake tokens from an account to a validator
30    /// As a not a developer of staking pool, you should consider using a staking pool contract instead
31    Stake(Box<StakeAction>),
32    /// Add a key to an account
33    AddKey(Box<AddKeyAction>),
34    /// Delete a key from an account
35    DeleteKey(Box<DeleteKeyAction>),
36    /// Delete an account and transfer all tokens to a beneficiary account
37    DeleteAccount(DeleteAccountAction),
38    /// Delegate your action submission to some relayer that will cover the cost of the transaction
39    Delegate(Box<SignedDelegateAction>),
40    /// Deploy a global contract
41    DeployGlobalContract(DeployGlobalContractAction),
42    /// Use a global contract to link code to an account
43    UseGlobalContract(Box<UseGlobalContractAction>),
44    /// Deploy a deterministic account with global contract and state.
45    ///
46    /// See [NEP-616](https://github.com/near/NEPs/blob/master/neps/nep-0616.md) for more details
47    DeterministicStateInit(Box<DeterministicStateInitAction>),
48
49    /// Creates a gas key for an account to be used for gas payments
50    ///
51    /// See [NEP-611](https://github.com/near/NEPs/blob/master/neps/nep-0611.md) for more details
52    AddGasKey(Box<AddGasKeyAction>),
53    /// Deletes a gas key for an account
54    ///
55    /// See [NEP-611](https://github.com/near/NEPs/blob/master/neps/nep-0611.md) for more details
56    DeleteGasKey(Box<DeleteGasKeyAction>),
57    /// Transfers tokens to a gas key
58    ///
59    /// See [NEP-611](https://github.com/near/NEPs/blob/master/neps/nep-0611.md) for more details
60    TransferToGasKey(Box<TransferToGasKeyAction>),
61}
62
63#[derive(Serialize, Deserialize, Debug, Clone, BorshSerialize, BorshDeserialize, PartialEq, Eq)]
64pub struct DeterministicStateInitAction {
65    pub state_init: DeterministicAccountStateInit,
66    pub deposit: NearToken,
67}
68
69#[derive(Serialize, Deserialize, Debug, Clone, BorshSerialize, BorshDeserialize, PartialEq, Eq)]
70#[borsh(use_discriminant = true)]
71#[repr(u8)]
72pub enum DeterministicAccountStateInit {
73    V1(DeterministicAccountStateInitV1),
74}
75
76#[serde_as]
77#[derive(Serialize, Deserialize, Debug, Clone, BorshSerialize, BorshDeserialize, PartialEq, Eq)]
78pub struct DeterministicAccountStateInitV1 {
79    pub code: GlobalContractIdentifier,
80    #[serde_as(as = "BTreeMap<Base64, Base64>")]
81    pub data: BTreeMap<Vec<u8>, Vec<u8>>,
82}
83
84impl TryFrom<near_openapi_types::DeterministicStateInitAction> for DeterministicStateInitAction {
85    type Error = DataConversionError;
86    fn try_from(
87        val: near_openapi_types::DeterministicStateInitAction,
88    ) -> Result<Self, Self::Error> {
89        let near_openapi_types::DeterministicStateInitAction {
90            state_init,
91            deposit,
92        } = val;
93
94        match state_init {
95            near_openapi_types::DeterministicAccountStateInit::V1(v1) => Ok(Self {
96                state_init: DeterministicAccountStateInit::V1(DeterministicAccountStateInitV1 {
97                    code: v1.code.into(),
98                    data: v1
99                        .data
100                        .into_iter()
101                        .map(|(k, v)| {
102                            Ok::<(Vec<u8>, Vec<u8>), DataConversionError>((
103                                BASE64_STANDARD.decode(k)?,
104                                BASE64_STANDARD.decode(v)?,
105                            ))
106                        })
107                        .collect::<Result<BTreeMap<Vec<u8>, Vec<u8>>, _>>()?,
108                }),
109                deposit,
110            }),
111        }
112    }
113}
114
115#[serde_as]
116#[derive(Serialize, Deserialize, Debug, Clone, BorshSerialize, BorshDeserialize, PartialEq, Eq)]
117pub struct DeployGlobalContractAction {
118    #[serde_as(as = "Base64")]
119    pub code: Vec<u8>,
120    pub deploy_mode: GlobalContractDeployMode,
121}
122
123impl TryFrom<near_openapi_types::DeployGlobalContractAction> for DeployGlobalContractAction {
124    type Error = DataConversionError;
125    fn try_from(val: near_openapi_types::DeployGlobalContractAction) -> Result<Self, Self::Error> {
126        let near_openapi_types::DeployGlobalContractAction { code, deploy_mode } = val;
127        Ok(Self {
128            code: BASE64_STANDARD.decode(code)?,
129            deploy_mode: deploy_mode.into(),
130        })
131    }
132}
133#[derive(Serialize, Deserialize, Debug, Clone, BorshSerialize, BorshDeserialize, PartialEq, Eq)]
134pub struct UseGlobalContractAction {
135    pub contract_identifier: GlobalContractIdentifier,
136}
137
138impl From<near_openapi_types::UseGlobalContractAction> for UseGlobalContractAction {
139    fn from(val: near_openapi_types::UseGlobalContractAction) -> Self {
140        let near_openapi_types::UseGlobalContractAction {
141            contract_identifier,
142        } = val;
143        Self {
144            contract_identifier: contract_identifier.into(),
145        }
146    }
147}
148
149#[derive(Serialize, Deserialize, Debug, Clone, BorshSerialize, BorshDeserialize, PartialEq, Eq)]
150pub struct CreateAccountAction {}
151
152impl From<near_openapi_types::CreateAccountAction> for CreateAccountAction {
153    fn from(_: near_openapi_types::CreateAccountAction) -> Self {
154        Self {}
155    }
156}
157
158#[serde_as]
159#[derive(Serialize, Deserialize, Debug, Clone, BorshSerialize, BorshDeserialize, PartialEq, Eq)]
160pub struct DeployContractAction {
161    #[serde_as(as = "Base64")]
162    pub code: Vec<u8>,
163}
164
165impl TryFrom<near_openapi_types::DeployContractAction> for DeployContractAction {
166    type Error = DataConversionError;
167    fn try_from(val: near_openapi_types::DeployContractAction) -> Result<Self, Self::Error> {
168        let near_openapi_types::DeployContractAction { code } = val;
169        Ok(Self {
170            code: BASE64_STANDARD.decode(code)?,
171        })
172    }
173}
174
175#[serde_as]
176#[derive(Serialize, Deserialize, Debug, Clone, BorshSerialize, BorshDeserialize, PartialEq, Eq)]
177pub struct FunctionCallAction {
178    pub method_name: String,
179    #[serde_as(as = "Base64")]
180    pub args: Vec<u8>,
181    #[serde(serialize_with = "near_gas_as_u64::serialize")]
182    pub gas: NearGas,
183    pub deposit: NearToken,
184}
185
186impl TryFrom<near_openapi_types::FunctionCallAction> for FunctionCallAction {
187    type Error = DataConversionError;
188    fn try_from(val: near_openapi_types::FunctionCallAction) -> Result<Self, Self::Error> {
189        let near_openapi_types::FunctionCallAction {
190            method_name,
191            args,
192            gas,
193            deposit,
194        } = val;
195        Ok(Self {
196            method_name,
197            args: BASE64_STANDARD.decode(args)?,
198            gas,
199            deposit,
200        })
201    }
202}
203
204#[derive(Serialize, Deserialize, Debug, Clone, BorshSerialize, BorshDeserialize, PartialEq, Eq)]
205pub struct TransferAction {
206    pub deposit: NearToken,
207}
208
209impl TryFrom<near_openapi_types::TransferAction> for TransferAction {
210    type Error = DataConversionError;
211    fn try_from(val: near_openapi_types::TransferAction) -> Result<Self, Self::Error> {
212        let near_openapi_types::TransferAction { deposit } = val;
213        Ok(Self { deposit })
214    }
215}
216
217#[derive(Serialize, Deserialize, Debug, Clone, BorshSerialize, BorshDeserialize, PartialEq, Eq)]
218pub struct StakeAction {
219    /// Amount of tokens to stake.
220    pub stake: NearToken,
221    /// Validator key which will be used to sign transactions on behalf of signer_id
222    pub public_key: PublicKey,
223}
224
225impl TryFrom<near_openapi_types::StakeAction> for StakeAction {
226    type Error = DataConversionError;
227    fn try_from(val: near_openapi_types::StakeAction) -> Result<Self, Self::Error> {
228        let near_openapi_types::StakeAction { public_key, stake } = val;
229        Ok(Self {
230            public_key: public_key.try_into()?,
231            stake,
232        })
233    }
234}
235
236#[derive(Serialize, Deserialize, Debug, Clone, BorshSerialize, BorshDeserialize, PartialEq, Eq)]
237pub struct AddGasKeyAction {
238    pub public_key: PublicKey,
239    pub num_nonces: u32,
240    pub permission: AccessKeyPermission,
241}
242
243impl TryFrom<near_openapi_types::AddGasKeyAction> for AddGasKeyAction {
244    type Error = DataConversionError;
245    fn try_from(val: near_openapi_types::AddGasKeyAction) -> Result<Self, Self::Error> {
246        let near_openapi_types::AddGasKeyAction {
247            public_key,
248            num_nonces,
249            permission,
250        } = val;
251        Ok(Self {
252            public_key: public_key.try_into()?,
253            num_nonces,
254            permission: permission.try_into()?,
255        })
256    }
257}
258
259#[derive(Serialize, Deserialize, Debug, Clone, BorshSerialize, BorshDeserialize, PartialEq, Eq)]
260pub struct DeleteGasKeyAction {
261    pub public_key: PublicKey,
262}
263
264impl TryFrom<near_openapi_types::DeleteGasKeyAction> for DeleteGasKeyAction {
265    type Error = DataConversionError;
266    fn try_from(val: near_openapi_types::DeleteGasKeyAction) -> Result<Self, Self::Error> {
267        let near_openapi_types::DeleteGasKeyAction { public_key } = val;
268        Ok(Self {
269            public_key: public_key.try_into()?,
270        })
271    }
272}
273
274#[derive(Serialize, Deserialize, Debug, Clone, BorshSerialize, BorshDeserialize, PartialEq, Eq)]
275pub struct TransferToGasKeyAction {
276    pub public_key: PublicKey,
277    pub deposit: NearToken,
278}
279
280impl TryFrom<near_openapi_types::TransferToGasKeyAction> for TransferToGasKeyAction {
281    type Error = DataConversionError;
282    fn try_from(val: near_openapi_types::TransferToGasKeyAction) -> Result<Self, Self::Error> {
283        let near_openapi_types::TransferToGasKeyAction {
284            public_key,
285            deposit,
286        } = val;
287        Ok(Self {
288            public_key: public_key.try_into()?,
289            deposit,
290        })
291    }
292}
293
294#[derive(Serialize, Deserialize, Debug, Clone, BorshSerialize, BorshDeserialize, PartialEq, Eq)]
295pub struct AddKeyAction {
296    /// A public key which will be associated with an access_key
297    pub public_key: PublicKey,
298    /// An access key with the permission
299    pub access_key: AccessKey,
300}
301
302impl TryFrom<near_openapi_types::AddKeyAction> for AddKeyAction {
303    type Error = DataConversionError;
304    fn try_from(val: near_openapi_types::AddKeyAction) -> Result<Self, Self::Error> {
305        let near_openapi_types::AddKeyAction {
306            public_key,
307            access_key,
308        } = val;
309        Ok(Self {
310            public_key: public_key.try_into()?,
311            access_key: access_key.try_into()?,
312        })
313    }
314}
315
316#[derive(Serialize, Deserialize, Debug, Clone, BorshSerialize, BorshDeserialize, PartialEq, Eq)]
317pub struct AccessKey {
318    /// Nonce for this access key, used for tx nonce generation. When access key is created, nonce
319    /// is set to `(block_height - 1) * 1e6` to avoid tx hash collision on access key re-creation.
320    /// See <https://github.com/near/nearcore/issues/3779> for more details.
321    pub nonce: U64,
322    /// Defines permissions for this access key.
323    pub permission: AccessKeyPermission,
324}
325
326impl TryFrom<near_openapi_types::AccessKeyView> for AccessKey {
327    type Error = DataConversionError;
328    fn try_from(val: near_openapi_types::AccessKeyView) -> Result<Self, Self::Error> {
329        let near_openapi_types::AccessKeyView { nonce, permission } = val;
330        Ok(Self {
331            nonce: U64(nonce),
332            permission: permission.try_into()?,
333        })
334    }
335}
336
337impl TryFrom<near_openapi_types::AccessKey> for AccessKey {
338    type Error = DataConversionError;
339    fn try_from(val: near_openapi_types::AccessKey) -> Result<Self, Self::Error> {
340        let near_openapi_types::AccessKey { nonce, permission } = val;
341        Ok(Self {
342            nonce: U64(nonce),
343            permission: permission.try_into()?,
344        })
345    }
346}
347
348#[derive(Serialize, Deserialize, Debug, Clone, BorshSerialize, BorshDeserialize, PartialEq, Eq)]
349pub enum AccessKeyPermission {
350    FunctionCall(FunctionCallPermission),
351    /// Grants full access to the account.
352    /// NOTE: It's used to replace account-level public keys.
353    FullAccess,
354}
355
356impl TryFrom<near_openapi_types::AccessKeyPermissionView> for AccessKeyPermission {
357    type Error = DataConversionError;
358    fn try_from(val: near_openapi_types::AccessKeyPermissionView) -> Result<Self, Self::Error> {
359        match val {
360            near_openapi_types::AccessKeyPermissionView::FunctionCall {
361                allowance,
362                method_names,
363                receiver_id,
364            } => Ok(Self::FunctionCall(FunctionCallPermission {
365                allowance,
366                receiver_id,
367                method_names,
368            })),
369            near_openapi_types::AccessKeyPermissionView::FullAccess => Ok(Self::FullAccess),
370        }
371    }
372}
373
374impl TryFrom<near_openapi_types::AccessKeyPermission> for AccessKeyPermission {
375    type Error = DataConversionError;
376    fn try_from(val: near_openapi_types::AccessKeyPermission) -> Result<Self, Self::Error> {
377        match val {
378            near_openapi_types::AccessKeyPermission::FunctionCall(function_call_permission) => {
379                Ok(Self::FunctionCall(function_call_permission.try_into()?))
380            }
381            near_openapi_types::AccessKeyPermission::FullAccess => Ok(Self::FullAccess),
382        }
383    }
384}
385
386#[derive(Serialize, Deserialize, Debug, Clone, BorshSerialize, BorshDeserialize, PartialEq, Eq)]
387pub struct FunctionCallPermission {
388    pub allowance: Option<NearToken>,
389    pub receiver_id: String,
390    pub method_names: Vec<String>,
391}
392
393impl TryFrom<near_openapi_types::FunctionCallPermission> for FunctionCallPermission {
394    type Error = DataConversionError;
395    fn try_from(val: near_openapi_types::FunctionCallPermission) -> Result<Self, Self::Error> {
396        let near_openapi_types::FunctionCallPermission {
397            allowance,
398            receiver_id,
399            method_names,
400        } = val;
401        Ok(Self {
402            allowance,
403            receiver_id,
404            method_names,
405        })
406    }
407}
408
409#[derive(Serialize, Deserialize, Debug, Clone, BorshSerialize, BorshDeserialize, PartialEq, Eq)]
410pub struct DeleteKeyAction {
411    /// A public key associated with the access_key to be deleted.
412    pub public_key: PublicKey,
413}
414
415impl TryFrom<near_openapi_types::DeleteKeyAction> for DeleteKeyAction {
416    type Error = DataConversionError;
417    fn try_from(val: near_openapi_types::DeleteKeyAction) -> Result<Self, Self::Error> {
418        let near_openapi_types::DeleteKeyAction { public_key } = val;
419        Ok(Self {
420            public_key: public_key.try_into()?,
421        })
422    }
423}
424
425#[derive(Serialize, Deserialize, Debug, Clone, BorshSerialize, BorshDeserialize, PartialEq, Eq)]
426pub struct DeleteAccountAction {
427    pub beneficiary_id: AccountId,
428}
429
430impl From<near_openapi_types::DeleteAccountAction> for DeleteAccountAction {
431    fn from(val: near_openapi_types::DeleteAccountAction) -> Self {
432        let near_openapi_types::DeleteAccountAction { beneficiary_id } = val;
433        Self { beneficiary_id }
434    }
435}
436
437#[derive(
438    BorshSerialize,
439    BorshDeserialize,
440    serde::Serialize,
441    serde::Deserialize,
442    PartialEq,
443    Eq,
444    Clone,
445    Debug,
446)]
447#[repr(u8)]
448pub enum GlobalContractDeployMode {
449    /// Contract is deployed under its code hash.
450    /// Users will be able reference it by that hash.
451    /// This effectively makes the contract immutable.
452    CodeHash,
453    /// Contract is deployed under the owner account id.
454    /// Users will be able reference it by that account id.
455    /// This allows the owner to update the contract for all its users.
456    AccountId,
457}
458
459impl From<near_openapi_types::GlobalContractDeployMode> for GlobalContractDeployMode {
460    fn from(val: near_openapi_types::GlobalContractDeployMode) -> Self {
461        match val {
462            near_openapi_types::GlobalContractDeployMode::CodeHash => Self::CodeHash,
463            near_openapi_types::GlobalContractDeployMode::AccountId => Self::AccountId,
464        }
465    }
466}
467#[derive(Serialize, Deserialize, Debug, Clone, BorshSerialize, BorshDeserialize, PartialEq, Eq)]
468pub enum GlobalContractIdentifier {
469    CodeHash(CryptoHash),
470    AccountId(AccountId),
471}
472
473impl From<near_openapi_types::GlobalContractIdentifier> for GlobalContractIdentifier {
474    fn from(val: near_openapi_types::GlobalContractIdentifier) -> Self {
475        match val {
476            near_openapi_types::GlobalContractIdentifier::CodeHash(code_hash) => {
477                Self::CodeHash(code_hash.into())
478            }
479            near_openapi_types::GlobalContractIdentifier::AccountId(account_id) => {
480                Self::AccountId(account_id)
481            }
482        }
483    }
484}
485
486impl From<near_openapi_types::GlobalContractIdentifierView> for GlobalContractIdentifier {
487    fn from(val: near_openapi_types::GlobalContractIdentifierView) -> Self {
488        match val {
489            near_openapi_types::GlobalContractIdentifierView::Hash(code_hash) => {
490                Self::CodeHash(code_hash.into())
491            }
492            near_openapi_types::GlobalContractIdentifierView::AccountId(account_id) => {
493                Self::AccountId(account_id)
494            }
495        }
496    }
497}
498
499impl TryFrom<near_openapi_types::ActionView> for Action {
500    type Error = DataConversionError;
501    fn try_from(val: near_openapi_types::ActionView) -> Result<Self, Self::Error> {
502        match val {
503            near_openapi_types::ActionView::DeterministicStateInit {
504                code,
505                data,
506                deposit,
507            } => Ok(Self::DeterministicStateInit(Box::new(
508                DeterministicStateInitAction {
509                    state_init: DeterministicAccountStateInit::V1(
510                        DeterministicAccountStateInitV1 {
511                            code: code.into(),
512                            data: data
513                                .into_iter()
514                                .map(|(k, v)| {
515                                    Ok::<(Vec<u8>, Vec<u8>), DataConversionError>((
516                                        BASE64_STANDARD.decode(k)?,
517                                        BASE64_STANDARD.decode(v)?,
518                                    ))
519                                })
520                                .collect::<Result<BTreeMap<Vec<u8>, Vec<u8>>, _>>()?,
521                        },
522                    ),
523                    deposit,
524                },
525            ))),
526            near_openapi_types::ActionView::CreateAccount => {
527                Ok(Self::CreateAccount(CreateAccountAction {}))
528            }
529            near_openapi_types::ActionView::DeployContract { code } => {
530                Ok(Self::DeployContract(DeployContractAction {
531                    code: BASE64_STANDARD.decode(code)?,
532                }))
533            }
534            near_openapi_types::ActionView::FunctionCall {
535                method_name,
536                args,
537                gas,
538                deposit,
539            } => Ok(Self::FunctionCall(Box::new(FunctionCallAction {
540                method_name,
541                args: BASE64_STANDARD.decode(args.0)?,
542                gas,
543                deposit,
544            }))),
545            near_openapi_types::ActionView::Transfer { deposit } => {
546                Ok(Self::Transfer(TransferAction { deposit }))
547            }
548            near_openapi_types::ActionView::Stake { public_key, stake } => {
549                Ok(Self::Stake(Box::new(StakeAction {
550                    public_key: public_key.try_into()?,
551                    stake,
552                })))
553            }
554            near_openapi_types::ActionView::AddKey {
555                access_key,
556                public_key,
557            } => Ok(Self::AddKey(Box::new(AddKeyAction {
558                public_key: public_key.try_into()?,
559                access_key: access_key.try_into()?,
560            }))),
561            near_openapi_types::ActionView::DeleteKey { public_key } => {
562                Ok(Self::DeleteKey(Box::new(DeleteKeyAction {
563                    public_key: public_key.try_into()?,
564                })))
565            }
566            near_openapi_types::ActionView::DeleteAccount { beneficiary_id } => {
567                Ok(Self::DeleteAccount(DeleteAccountAction { beneficiary_id }))
568            }
569            near_openapi_types::ActionView::Delegate {
570                delegate_action,
571                signature,
572            } => Ok(Self::Delegate(Box::new(SignedDelegateAction {
573                delegate_action: delegate_action.try_into()?,
574                signature: Signature::from_str(&signature)?,
575            }))),
576            near_openapi_types::ActionView::DeployGlobalContract { code } => {
577                Ok(Self::DeployGlobalContract(DeployGlobalContractAction {
578                    code: BASE64_STANDARD.decode(code)?,
579                    deploy_mode: GlobalContractDeployMode::CodeHash,
580                }))
581            }
582            near_openapi_types::ActionView::DeployGlobalContractByAccountId { code } => {
583                Ok(Self::DeployGlobalContract(DeployGlobalContractAction {
584                    code: BASE64_STANDARD.decode(code)?,
585                    deploy_mode: GlobalContractDeployMode::AccountId,
586                }))
587            }
588            near_openapi_types::ActionView::UseGlobalContract { code_hash } => {
589                Ok(Self::UseGlobalContract(Box::new(UseGlobalContractAction {
590                    contract_identifier: GlobalContractIdentifier::CodeHash(code_hash.into()),
591                })))
592            }
593            near_openapi_types::ActionView::UseGlobalContractByAccountId { account_id } => {
594                Ok(Self::UseGlobalContract(Box::new(UseGlobalContractAction {
595                    contract_identifier: GlobalContractIdentifier::AccountId(account_id),
596                })))
597            }
598            near_openapi_types::ActionView::AddGasKey {
599                num_nonces,
600                permission,
601                public_key,
602            } => Ok(Self::AddGasKey(Box::new(AddGasKeyAction {
603                public_key: public_key.try_into()?,
604                num_nonces,
605                permission: permission.try_into()?,
606            }))),
607            near_openapi_types::ActionView::DeleteGasKey { public_key } => {
608                Ok(Self::DeleteGasKey(Box::new(DeleteGasKeyAction {
609                    public_key: public_key.try_into()?,
610                })))
611            }
612            near_openapi_types::ActionView::TransferToGasKey {
613                amount: deposit,
614                public_key,
615            } => Ok(Self::TransferToGasKey(Box::new(TransferToGasKeyAction {
616                public_key: public_key.try_into()?,
617                deposit,
618            }))),
619        }
620    }
621}
622
623#[cfg(test)]
624mod tests {
625    use std::collections::BTreeMap;
626    use std::sync::Arc;
627
628    use super::*;
629    use crate::crypto::{ED25519_PUBLIC_KEY_LENGTH, public_key::ED25519PublicKey};
630    use crate::transaction::delegate_action::{DelegateAction, NonDelegateAction};
631    use near_primitives::action as npa;
632    use near_primitives::deterministic_account_id::{
633        DeterministicAccountStateInit as npaDeterministicAccountStateInit,
634        DeterministicAccountStateInitV1 as npaDeterministicAccountStateInitV1,
635    };
636    use near_primitives::gas::Gas;
637    use near_primitives::global_contract::GlobalContractIdentifier as npaGlobalContractIdentifier;
638    use serde_json;
639
640    fn get_actions() -> (Vec<Action>, Vec<npa::Action>) {
641        let btreemap = BTreeMap::from([(b"key".to_vec(), b"value".to_vec())]);
642
643        let local_actions = vec![
644            Action::CreateAccount(CreateAccountAction {}),
645            Action::DeployContract(DeployContractAction {
646                code: vec![1, 2, 3],
647            }),
648            Action::FunctionCall(Box::new(FunctionCallAction {
649                method_name: "test".to_string(),
650                args: vec![4, 5, 6],
651                gas: NearGas::from_gas(1000000),
652                deposit: NearToken::from_yoctonear(0),
653            })),
654            Action::Transfer(TransferAction {
655                deposit: NearToken::from_yoctonear(1000000000),
656            }),
657            Action::Stake(Box::new(StakeAction {
658                stake: NearToken::from_yoctonear(100000000),
659                public_key: PublicKey::ED25519(ED25519PublicKey([0; ED25519_PUBLIC_KEY_LENGTH])),
660            })),
661            Action::AddKey(Box::new(AddKeyAction {
662                public_key: PublicKey::ED25519(ED25519PublicKey([0; ED25519_PUBLIC_KEY_LENGTH])),
663                access_key: AccessKey {
664                    nonce: U64(0),
665                    permission: AccessKeyPermission::FullAccess,
666                },
667            })),
668            Action::DeleteKey(Box::new(DeleteKeyAction {
669                public_key: PublicKey::ED25519(ED25519PublicKey([0; ED25519_PUBLIC_KEY_LENGTH])),
670            })),
671            Action::DeleteAccount(DeleteAccountAction {
672                beneficiary_id: "alice.near".parse().unwrap(),
673            }),
674            Action::DeployGlobalContract(DeployGlobalContractAction {
675                code: vec![7, 8, 9],
676                deploy_mode: GlobalContractDeployMode::CodeHash,
677            }),
678            Action::UseGlobalContract(Box::new(UseGlobalContractAction {
679                contract_identifier: GlobalContractIdentifier::AccountId(
680                    "global.near".parse().unwrap(),
681                ),
682            })),
683            Action::Delegate(Box::new(SignedDelegateAction {
684                delegate_action: DelegateAction {
685                    sender_id: "sender.near".parse().unwrap(),
686                    receiver_id: "receiver.near".parse().unwrap(),
687                    actions: vec![
688                        NonDelegateAction::try_from(Action::Transfer(TransferAction {
689                            deposit: NearToken::from_yoctonear(1000),
690                        }))
691                        .unwrap(),
692                    ],
693                    nonce: 1,
694                    max_block_height: 1000,
695                    public_key: PublicKey::ED25519(ED25519PublicKey(
696                        [0; ED25519_PUBLIC_KEY_LENGTH],
697                    )),
698                },
699                signature: Signature::from_parts(crate::crypto::KeyType::ED25519, &[0u8; 64])
700                    .unwrap(),
701            })),
702            Action::DeterministicStateInit(Box::new(DeterministicStateInitAction {
703                state_init: DeterministicAccountStateInit::V1(DeterministicAccountStateInitV1 {
704                    code: GlobalContractIdentifier::AccountId("init.near".parse().unwrap()),
705                    data: btreemap.clone(),
706                }),
707                deposit: NearToken::from_yoctonear(5000000000),
708            })),
709        ];
710
711        let near_primitives_actions = vec![
712            npa::Action::CreateAccount(npa::CreateAccountAction {}),
713            npa::Action::DeployContract(npa::DeployContractAction {
714                code: vec![1, 2, 3],
715            }),
716            npa::Action::FunctionCall(Box::new(npa::FunctionCallAction {
717                method_name: "test".to_string(),
718                args: vec![4, 5, 6],
719                gas: Gas::from_gas(1000000),
720                deposit: NearToken::ZERO,
721            })),
722            npa::Action::Transfer(npa::TransferAction {
723                deposit: NearToken::from_yoctonear(1000000000),
724            }),
725            npa::Action::Stake(Box::new(npa::StakeAction {
726                stake: NearToken::from_yoctonear(100000000),
727                public_key: near_crypto::PublicKey::empty(near_crypto::KeyType::ED25519),
728            })),
729            npa::Action::AddKey(Box::new(npa::AddKeyAction {
730                public_key: near_crypto::PublicKey::empty(near_crypto::KeyType::ED25519),
731                access_key: near_primitives::account::AccessKey {
732                    nonce: 0,
733                    permission: near_primitives::account::AccessKeyPermission::FullAccess,
734                },
735            })),
736            npa::Action::DeleteKey(Box::new(npa::DeleteKeyAction {
737                public_key: near_crypto::PublicKey::empty(near_crypto::KeyType::ED25519),
738            })),
739            npa::Action::DeleteAccount(npa::DeleteAccountAction {
740                beneficiary_id: "alice.near".parse().unwrap(),
741            }),
742            npa::Action::DeployGlobalContract(npa::DeployGlobalContractAction {
743                code: Arc::new([7, 8, 9]),
744                deploy_mode: npa::GlobalContractDeployMode::CodeHash,
745            }),
746            npa::Action::UseGlobalContract(Box::new(npa::UseGlobalContractAction {
747                contract_identifier: npa::GlobalContractIdentifier::AccountId(
748                    "global.near".parse().unwrap(),
749                ),
750            })),
751            npa::Action::Delegate(Box::new(npa::delegate::SignedDelegateAction {
752                delegate_action: npa::delegate::DelegateAction {
753                    sender_id: "sender.near".parse().unwrap(),
754                    receiver_id: "receiver.near".parse().unwrap(),
755                    actions: vec![
756                        npa::delegate::NonDelegateAction::try_from(npa::Action::Transfer(
757                            npa::TransferAction {
758                                deposit: NearToken::from_yoctonear(1000),
759                            },
760                        ))
761                        .unwrap(),
762                    ],
763                    nonce: 1,
764                    max_block_height: 1000,
765                    public_key: near_crypto::PublicKey::empty(near_crypto::KeyType::ED25519),
766                },
767                signature: near_crypto::Signature::from_parts(
768                    near_crypto::KeyType::ED25519,
769                    &[0u8; 64],
770                )
771                .unwrap(),
772            })),
773            npa::Action::DeterministicStateInit(Box::new(npa::DeterministicStateInitAction {
774                state_init: npaDeterministicAccountStateInit::V1(
775                    npaDeterministicAccountStateInitV1 {
776                        code: npaGlobalContractIdentifier::AccountId("init.near".parse().unwrap()),
777                        data: btreemap,
778                    },
779                ),
780                deposit: NearToken::from_yoctonear(5000000000),
781            })),
782        ];
783
784        (local_actions, near_primitives_actions)
785    }
786
787    #[test]
788    fn test_action_serialization() {
789        let (local_actions, _) = get_actions();
790
791        for action in local_actions {
792            let serialized =
793                serde_json::to_string(&action).expect("Failed to serialize action to JSON");
794
795            let deserialized: Action =
796                serde_json::from_str(&serialized).expect("Failed to deserialize action from JSON");
797
798            assert_eq!(
799                action, deserialized,
800                "Serialization/Deserialization mismatch: original action: {action:?}, deserialized action: {deserialized:?}"
801            );
802        }
803    }
804
805    #[test]
806    fn test_action_borsh_serialization() {
807        let (local_actions, _) = get_actions();
808
809        for action in local_actions {
810            let serialized = borsh::to_vec(&action).expect("Failed to serialize action to borsh");
811
812            let deserialized: Action = Action::try_from_slice(&serialized)
813                .expect("Failed to deserialize action from borsh");
814
815            assert_eq!(
816                action, deserialized,
817                "Serialization/Deserialization mismatch: original action: {action:?}, deserialized action: {deserialized:?}"
818            );
819        }
820    }
821
822    #[test]
823    fn serialization_comparison_with_near_primitives() {
824        let (local_actions, near_primitives_actions) = get_actions();
825
826        assert_eq!(
827            local_actions.len(),
828            near_primitives_actions.len(),
829            "Action lists should have the same length"
830        );
831
832        for (local_action, np_action) in local_actions.iter().zip(near_primitives_actions.iter()) {
833            // Compare borsh serialization - this is what matters for on-chain compatibility
834            let local_borsh =
835                borsh::to_vec(local_action).expect("Failed to serialize local action to borsh");
836            let np_borsh = borsh::to_vec(np_action)
837                .expect("Failed to serialize near_primitives action to borsh");
838
839            assert_eq!(local_borsh, np_borsh, "Borsh serialization mismatch");
840
841            // Note: We intentionally diverge from near_primitives JSON serialization format.
842            // Our U64 and U128 types serialize as strings for JavaScript compatibility,
843            // while near_primitives uses numbers. This is by design - JSON is for API/RPC
844            // communication where JavaScript compatibility matters, while Borsh is for
845            // on-chain data where binary compatibility is what counts.
846        }
847    }
848}