unc_primitives/action/
delegate.rs

1//! DelegateAction is a type of action to support meta transactions.
2//!
3//! This is the module containing the types introduced for delegate actions.
4
5pub use self::private_non_delegate_action::NonDelegateAction;
6use super::Action;
7use crate::signable_message::{SignableMessage, SignableMessageType};
8use borsh::{BorshDeserialize, BorshSerialize};
9use serde::{Deserialize, Serialize};
10use std::io::{Error, ErrorKind, Read};
11use unc_crypto::{PublicKey, Signature};
12use unc_primitives_core::hash::{hash, CryptoHash};
13use unc_primitives_core::types::BlockHeight;
14use unc_primitives_core::types::{AccountId, Nonce};
15
16/// This is an index number of Action::Delegate in Action enumeration
17const ACTION_DELEGATE_NUMBER: u8 = 8;
18/// This action allows to execute the inner actions behalf of the defined sender.
19#[derive(BorshSerialize, BorshDeserialize, Serialize, Deserialize, PartialEq, Eq, Clone, Debug)]
20pub struct DelegateAction {
21    /// Signer of the delegated actions
22    pub sender_id: AccountId,
23    /// Receiver of the delegated actions.
24    pub receiver_id: AccountId,
25    /// List of actions to be executed.
26    ///
27    /// With the meta transactions MVP, nested
28    /// DelegateActions are not allowed. A separate type is used to enforce it.
29    pub actions: Vec<NonDelegateAction>,
30    /// Nonce to ensure that the same delegate action is not sent twice by a
31    /// relayer and should match for given account's `public_key`.
32    /// After this action is processed it will increment.
33    pub nonce: Nonce,
34    /// The maximal height of the block in the blockchain below which the given DelegateAction is valid.
35    pub max_block_height: BlockHeight,
36    /// Public key used to sign this delegated action.
37    pub public_key: PublicKey,
38}
39
40#[derive(BorshSerialize, BorshDeserialize, Serialize, Deserialize, PartialEq, Eq, Clone, Debug)]
41pub struct SignedDelegateAction {
42    pub delegate_action: DelegateAction,
43    pub signature: Signature,
44}
45
46impl SignedDelegateAction {
47    pub fn verify(&self) -> bool {
48        let delegate_action = &self.delegate_action;
49        let hash = delegate_action.get_nep461_hash();
50        let public_key = &delegate_action.public_key;
51
52        self.signature.verify(hash.as_ref(), public_key)
53    }
54}
55
56impl From<SignedDelegateAction> for Action {
57    fn from(delegate_action: SignedDelegateAction) -> Self {
58        Self::Delegate(Box::new(delegate_action))
59    }
60}
61
62impl DelegateAction {
63    pub fn get_actions(&self) -> Vec<Action> {
64        self.actions.iter().map(|a| a.clone().into()).collect()
65    }
66
67    /// Delegate action hash used for signature scheme which tags
68    /// different messages before hashing
69    ///
70    pub fn get_nep461_hash(&self) -> CryptoHash {
71        let signable = SignableMessage::new(&self, SignableMessageType::DelegateAction);
72        let bytes = borsh::to_vec(&signable).expect("Failed to deserialize");
73        hash(&bytes)
74    }
75}
76
77/// A small private module to protect the private fields inside `NonDelegateAction`.
78mod private_non_delegate_action {
79    use super::*;
80
81    /// This is Action which mustn't contain DelegateAction.
82    ///
83    /// This struct is needed to avoid the recursion when Action/DelegateAction is deserialized.
84    ///
85    /// Important: Don't make the inner Action public, this must only be constructed
86    /// through the correct interface that ensures the inner Action is actually not
87    /// a delegate action. That would break an assumption of this type, which we use
88    /// in several places. For example, borsh de-/serialization relies on it. If the
89    /// invariant is broken, we may end up with a `Transaction` or `Receipt` that we
90    /// can serialize but deserializing it back causes a parsing error.
91    #[derive(Serialize, BorshSerialize, Deserialize, PartialEq, Eq, Clone, Debug)]
92    pub struct NonDelegateAction(Action);
93
94    impl From<NonDelegateAction> for Action {
95        fn from(action: NonDelegateAction) -> Self {
96            action.0
97        }
98    }
99
100    #[derive(Debug, thiserror::Error)]
101    #[error("attempted to construct NonDelegateAction from Action::Delegate")]
102    pub struct IsDelegateAction;
103
104    impl TryFrom<Action> for NonDelegateAction {
105        type Error = IsDelegateAction;
106
107        fn try_from(action: Action) -> Result<Self, IsDelegateAction> {
108            if matches!(action, Action::Delegate(_)) {
109                Err(IsDelegateAction)
110            } else {
111                Ok(Self(action))
112            }
113        }
114    }
115
116    impl borsh::de::BorshDeserialize for NonDelegateAction {
117        fn deserialize_reader<R: Read>(rd: &mut R) -> ::core::result::Result<Self, Error> {
118            match u8::deserialize_reader(rd)? {
119                ACTION_DELEGATE_NUMBER => Err(Error::new(
120                    ErrorKind::InvalidInput,
121                    "DelegateAction mustn't contain a nested one",
122                )),
123                n => borsh::de::EnumExt::deserialize_variant(rd, n).map(Self),
124            }
125        }
126    }
127}
128
129#[cfg(test)]
130mod tests {
131    use super::*;
132    use crate::action::CreateAccountAction;
133    use unc_crypto::KeyType;
134
135    /// A serialized `Action::Delegate(SignedDelegateAction)` for testing.
136    ///
137    /// We want this to be parseable and accepted by protocol versions with meta
138    /// transactions enabled. But it should fail either in parsing or in
139    /// validation when this is included in a receipt for a block of an earlier
140    /// version. For now, it just fails to parse, as a test below checks.
141    const DELEGATE_ACTION_HEX: &str = concat!(
142        "0803000000616161030000006262620100000000010000000000000002000000000000",
143        "0000000000000000000000000000000000000000000000000000000000000000000000",
144        "0000000000000000000000000000000000000000000000000000000000000000000000",
145        "0000000000000000000000000000000000000000000000000000000000"
146    );
147
148    fn create_delegate_action(actions: Vec<Action>) -> Action {
149        Action::Delegate(Box::new(SignedDelegateAction {
150            delegate_action: DelegateAction {
151                sender_id: "aaa".parse().unwrap(),
152                receiver_id: "bbb".parse().unwrap(),
153                actions: actions
154                    .iter()
155                    .map(|a| NonDelegateAction::try_from(a.clone()).unwrap())
156                    .collect(),
157                nonce: 1,
158                max_block_height: 2,
159                public_key: PublicKey::empty(KeyType::ED25519),
160            },
161            signature: Signature::empty(KeyType::ED25519),
162        }))
163    }
164
165    #[test]
166    fn test_delegate_action_deserialization() {
167        // Expected an error. Buffer is empty
168        assert_eq!(
169            NonDelegateAction::try_from_slice(Vec::new().as_ref()).map_err(|e| e.kind()),
170            Err(ErrorKind::InvalidData)
171        );
172
173        let delegate_action = create_delegate_action(Vec::<Action>::new());
174        let serialized_non_delegate_action = borsh::to_vec(&delegate_action).expect("Expect ok");
175
176        // Expected Action::Delegate has not been moved in enum Action
177        assert_eq!(serialized_non_delegate_action[0], ACTION_DELEGATE_NUMBER);
178
179        // Expected a nested DelegateAction error
180        assert_eq!(
181            NonDelegateAction::try_from_slice(&serialized_non_delegate_action)
182                .map_err(|e| e.kind()),
183            Err(ErrorKind::InvalidInput)
184        );
185
186        let delegate_action =
187            create_delegate_action(vec![Action::CreateAccount(CreateAccountAction {})]);
188        let serialized_delegate_action = borsh::to_vec(&delegate_action).expect("Expect ok");
189
190        // Valid action
191        assert_eq!(
192            Action::try_from_slice(&serialized_delegate_action).expect("Expect ok"),
193            delegate_action
194        );
195    }
196
197    /// Check that the hard-coded delegate action is valid.
198    #[test]
199    fn test_delegate_action_deserialization_hard_coded() {
200        let serialized_delegate_action = hex::decode(DELEGATE_ACTION_HEX).expect("invalid hex");
201        // The hex data is the same as the one we create below.
202        let delegate_action =
203            create_delegate_action(vec![Action::CreateAccount(CreateAccountAction {})]);
204
205        // Valid action
206        assert_eq!(
207            Action::try_from_slice(&serialized_delegate_action).expect("Expect ok"),
208            delegate_action
209        );
210    }
211}