unc_primitives/
test_utils.rs

1use std::collections::HashMap;
2use std::sync::Arc;
3
4use unc_crypto::{EmptySigner, InMemorySigner, KeyType, PublicKey, SecretKey, Signature, Signer};
5use unc_primitives_core::account::id::AccountIdRef;
6use unc_primitives_core::types::{Power, ProtocolVersion};
7
8use crate::account::{AccessKey, AccessKeyPermission, Account};
9use crate::block::Block;
10use crate::block::BlockV3;
11use crate::block_header::BlockHeader;
12use crate::errors::EpochError;
13use crate::hash::CryptoHash;
14use crate::merkle::PartialMerkleTree;
15use crate::num_rational::Ratio;
16use crate::sharding::{ShardChunkHeader, ShardChunkHeaderV3};
17use crate::transaction::{
18    Action, AddKeyAction, CreateAccountAction, DeleteAccountAction, DeleteKeyAction,
19    DeployContractAction, FunctionCallAction, PledgeAction, SignedTransaction, Transaction,
20    TransferAction,
21};
22use crate::types::{AccountId, Balance, EpochId, EpochInfoProvider, Gas, Nonce};
23use crate::validator_signer::{InMemoryValidatorSigner, ValidatorSigner};
24use crate::version::PROTOCOL_VERSION;
25use crate::views::{ExecutionStatusView, FinalExecutionOutcomeView, FinalExecutionStatus};
26
27pub fn account_new(amount: Balance, code_hash: CryptoHash) -> Account {
28    Account::new(amount, 0, 0, code_hash, std::mem::size_of::<Account>() as u64)
29}
30
31impl Transaction {
32    pub fn new(
33        signer_id: AccountId,
34        public_key: PublicKey,
35        receiver_id: AccountId,
36        nonce: Nonce,
37        block_hash: CryptoHash,
38    ) -> Self {
39        Self { signer_id, public_key, nonce, receiver_id, block_hash, actions: vec![] }
40    }
41
42    pub fn sign(self, signer: &dyn Signer) -> SignedTransaction {
43        let signature = signer.sign(self.get_hash_and_size().0.as_ref());
44        SignedTransaction::new(signature, self)
45    }
46
47    pub fn create_account(mut self) -> Self {
48        self.actions.push(Action::CreateAccount(CreateAccountAction {}));
49        self
50    }
51
52    pub fn deploy_contract(mut self, code: Vec<u8>) -> Self {
53        self.actions.push(Action::DeployContract(DeployContractAction { code }));
54        self
55    }
56
57    pub fn function_call(
58        mut self,
59        method_name: String,
60        args: Vec<u8>,
61        gas: Gas,
62        deposit: Balance,
63    ) -> Self {
64        self.actions.push(Action::FunctionCall(Box::new(FunctionCallAction {
65            method_name,
66            args,
67            gas,
68            deposit,
69        })));
70        self
71    }
72
73    pub fn transfer(mut self, deposit: Balance) -> Self {
74        self.actions.push(Action::Transfer(TransferAction { deposit }));
75        self
76    }
77
78    pub fn pledge(mut self, pledge: Balance, public_key: PublicKey) -> Self {
79        self.actions.push(Action::Pledge(Box::new(PledgeAction { pledge, public_key })));
80        self
81    }
82    pub fn add_key(mut self, public_key: PublicKey, access_key: AccessKey) -> Self {
83        self.actions.push(Action::AddKey(Box::new(AddKeyAction { public_key, access_key })));
84        self
85    }
86
87    pub fn delete_key(mut self, public_key: PublicKey) -> Self {
88        self.actions.push(Action::DeleteKey(Box::new(DeleteKeyAction { public_key })));
89        self
90    }
91
92    pub fn delete_account(mut self, beneficiary_id: AccountId) -> Self {
93        self.actions.push(Action::DeleteAccount(DeleteAccountAction { beneficiary_id }));
94        self
95    }
96}
97
98impl SignedTransaction {
99    pub fn from_actions(
100        nonce: Nonce,
101        signer_id: AccountId,
102        receiver_id: AccountId,
103        signer: &dyn Signer,
104        actions: Vec<Action>,
105        block_hash: CryptoHash,
106    ) -> Self {
107        Transaction {
108            nonce,
109            signer_id,
110            public_key: signer.public_key(),
111            receiver_id,
112            block_hash,
113            actions,
114        }
115        .sign(signer)
116    }
117
118    pub fn send_money(
119        nonce: Nonce,
120        signer_id: AccountId,
121        receiver_id: AccountId,
122        signer: &dyn Signer,
123        deposit: Balance,
124        block_hash: CryptoHash,
125    ) -> Self {
126        Self::from_actions(
127            nonce,
128            signer_id,
129            receiver_id,
130            signer,
131            vec![Action::Transfer(TransferAction { deposit })],
132            block_hash,
133        )
134    }
135
136    pub fn pledge(
137        nonce: Nonce,
138        signer_id: AccountId,
139        signer: &dyn Signer,
140        pledge: Balance,
141        public_key: PublicKey,
142        block_hash: CryptoHash,
143    ) -> Self {
144        Self::from_actions(
145            nonce,
146            signer_id.clone(),
147            signer_id,
148            signer,
149            vec![Action::Pledge(Box::new(PledgeAction { pledge, public_key }))],
150            block_hash,
151        )
152    }
153
154    pub fn create_account(
155        nonce: Nonce,
156        originator: AccountId,
157        new_account_id: AccountId,
158        amount: Balance,
159        public_key: PublicKey,
160        signer: &dyn Signer,
161        block_hash: CryptoHash,
162    ) -> Self {
163        Self::from_actions(
164            nonce,
165            originator,
166            new_account_id,
167            signer,
168            vec![
169                Action::CreateAccount(CreateAccountAction {}),
170                Action::AddKey(Box::new(AddKeyAction {
171                    public_key,
172                    access_key: AccessKey { nonce: 0, permission: AccessKeyPermission::FullAccess },
173                })),
174                Action::Transfer(TransferAction { deposit: amount }),
175            ],
176            block_hash,
177        )
178    }
179
180    pub fn create_contract(
181        nonce: Nonce,
182        originator: AccountId,
183        new_account_id: AccountId,
184        code: Vec<u8>,
185        amount: Balance,
186        public_key: PublicKey,
187        signer: &dyn Signer,
188        block_hash: CryptoHash,
189    ) -> Self {
190        Self::from_actions(
191            nonce,
192            originator,
193            new_account_id,
194            signer,
195            vec![
196                Action::CreateAccount(CreateAccountAction {}),
197                Action::AddKey(Box::new(AddKeyAction {
198                    public_key,
199                    access_key: AccessKey { nonce: 0, permission: AccessKeyPermission::FullAccess },
200                })),
201                Action::Transfer(TransferAction { deposit: amount }),
202                Action::DeployContract(DeployContractAction { code }),
203            ],
204            block_hash,
205        )
206    }
207
208    pub fn call(
209        nonce: Nonce,
210        signer_id: AccountId,
211        receiver_id: AccountId,
212        signer: &dyn Signer,
213        deposit: Balance,
214        method_name: String,
215        args: Vec<u8>,
216        gas: Gas,
217        block_hash: CryptoHash,
218    ) -> Self {
219        Self::from_actions(
220            nonce,
221            signer_id,
222            receiver_id,
223            signer,
224            vec![Action::FunctionCall(Box::new(FunctionCallAction {
225                args,
226                method_name,
227                gas,
228                deposit,
229            }))],
230            block_hash,
231        )
232    }
233
234    pub fn delete_account(
235        nonce: Nonce,
236        signer_id: AccountId,
237        receiver_id: AccountId,
238        beneficiary_id: AccountId,
239        signer: &dyn Signer,
240        block_hash: CryptoHash,
241    ) -> Self {
242        Self::from_actions(
243            nonce,
244            signer_id,
245            receiver_id,
246            signer,
247            vec![Action::DeleteAccount(DeleteAccountAction { beneficiary_id })],
248            block_hash,
249        )
250    }
251
252    pub fn empty(block_hash: CryptoHash) -> Self {
253        Self::from_actions(
254            0,
255            "test".parse().unwrap(),
256            "test".parse().unwrap(),
257            &EmptySigner {},
258            vec![],
259            block_hash,
260        )
261    }
262}
263
264impl Block {
265    pub fn get_mut(&mut self) -> &mut BlockV3 {
266        match self {
267            Block::BlockV1(_) | Block::BlockV2(_) => {
268                panic!("older block version should not appear in tests")
269            }
270            Block::BlockV3(block) => Arc::make_mut(block),
271        }
272    }
273}
274
275impl BlockHeader {
276    pub fn get_mut(&mut self) -> &mut crate::block_header::BlockHeaderV4 {
277        match self {
278            BlockHeader::BlockHeaderV1(_)
279            | BlockHeader::BlockHeaderV2(_)
280            | BlockHeader::BlockHeaderV3(_) => {
281                panic!("old header should not appear in tests")
282            }
283            BlockHeader::BlockHeaderV4(header) => Arc::make_mut(header),
284        }
285    }
286
287    pub fn set_latest_protocol_version(&mut self, latest_protocol_version: ProtocolVersion) {
288        match self {
289            BlockHeader::BlockHeaderV1(header) => {
290                let header = Arc::make_mut(header);
291                header.inner_rest.latest_protocol_version = latest_protocol_version;
292            }
293            BlockHeader::BlockHeaderV2(header) => {
294                let header = Arc::make_mut(header);
295                header.inner_rest.latest_protocol_version = latest_protocol_version;
296            }
297            BlockHeader::BlockHeaderV3(header) => {
298                let header = Arc::make_mut(header);
299                header.inner_rest.latest_protocol_version = latest_protocol_version;
300            }
301            BlockHeader::BlockHeaderV4(header) => {
302                let header = Arc::make_mut(header);
303                header.inner_rest.latest_protocol_version = latest_protocol_version;
304            }
305        }
306    }
307
308    pub fn resign(&mut self, signer: &dyn ValidatorSigner) {
309        let (hash, signature) = signer.sign_block_header_parts(
310            *self.prev_hash(),
311            &self.inner_lite_bytes(),
312            &self.inner_rest_bytes(),
313        );
314        match self {
315            BlockHeader::BlockHeaderV1(header) => {
316                let header = Arc::make_mut(header);
317                header.hash = hash;
318                header.signature = signature;
319            }
320            BlockHeader::BlockHeaderV2(header) => {
321                let header = Arc::make_mut(header);
322                header.hash = hash;
323                header.signature = signature;
324            }
325            BlockHeader::BlockHeaderV3(header) => {
326                let header = Arc::make_mut(header);
327                header.hash = hash;
328                header.signature = signature;
329            }
330            BlockHeader::BlockHeaderV4(header) => {
331                let header = Arc::make_mut(header);
332                header.hash = hash;
333                header.signature = signature;
334            }
335        }
336    }
337}
338
339impl ShardChunkHeader {
340    pub fn get_mut(&mut self) -> &mut ShardChunkHeaderV3 {
341        match self {
342            ShardChunkHeader::V1(_) | ShardChunkHeader::V2(_) => {
343                unreachable!("old header should not appear in tests")
344            }
345            ShardChunkHeader::V3(chunk) => chunk,
346        }
347    }
348}
349/// Builder class for blocks to make testing easier.
350/// # Examples
351///
352/// // TODO: change it to doc-tested code once we have easy way to create a genesis block.
353/// let signer = EmptyValidatorSigner::default();
354/// let test_block = test_utils::TestBlockBuilder::new(prev, signer).height(33).build();
355///
356
357pub struct TestBlockBuilder {
358    prev: Block,
359    signer: Arc<dyn ValidatorSigner>,
360    height: u64,
361    epoch_id: EpochId,
362    next_epoch_id: EpochId,
363    next_bp_hash: CryptoHash,
364    approvals: Vec<Option<Box<Signature>>>,
365    block_merkle_root: CryptoHash,
366}
367
368impl TestBlockBuilder {
369    pub fn new(prev: &Block, signer: Arc<dyn ValidatorSigner>) -> Self {
370        let mut tree = PartialMerkleTree::default();
371        tree.insert(*prev.hash());
372
373        Self {
374            prev: prev.clone(),
375            signer: signer.clone(),
376            height: prev.header().height() + 1,
377            epoch_id: prev.header().epoch_id().clone(),
378            next_epoch_id: if prev.header().prev_hash() == &CryptoHash::default() {
379                EpochId(*prev.hash())
380            } else {
381                prev.header().next_epoch_id().clone()
382            },
383            next_bp_hash: *prev.header().next_bp_hash(),
384            approvals: vec![],
385            block_merkle_root: tree.root(),
386        }
387    }
388    pub fn height(mut self, height: u64) -> Self {
389        self.height = height;
390        self
391    }
392    pub fn epoch_id(mut self, epoch_id: EpochId) -> Self {
393        self.epoch_id = epoch_id;
394        self
395    }
396    pub fn next_epoch_id(mut self, next_epoch_id: EpochId) -> Self {
397        self.next_epoch_id = next_epoch_id;
398        self
399    }
400    pub fn next_bp_hash(mut self, next_bp_hash: CryptoHash) -> Self {
401        self.next_bp_hash = next_bp_hash;
402        self
403    }
404    pub fn approvals(mut self, approvals: Vec<Option<Box<Signature>>>) -> Self {
405        self.approvals = approvals;
406        self
407    }
408
409    /// Updates the merkle tree by adding the previous hash, and updates the new block's merkle_root.
410    pub fn block_merkle_tree(mut self, block_merkle_tree: &mut PartialMerkleTree) -> Self {
411        block_merkle_tree.insert(*self.prev.hash());
412        self.block_merkle_root = block_merkle_tree.root();
413        self
414    }
415
416    pub fn build(self) -> Block {
417        tracing::debug!(target: "test", height=self.height, ?self.epoch_id, "produce block");
418        Block::produce(
419            PROTOCOL_VERSION,
420            PROTOCOL_VERSION,
421            self.prev.header(),
422            self.height,
423            self.prev.header().block_ordinal() + 1,
424            self.prev.chunks().iter().cloned().collect(),
425            self.epoch_id,
426            self.next_epoch_id,
427            None,
428            self.approvals,
429            Ratio::new(0, 1),
430            0,
431            0,
432            Some(0),
433            vec![],
434            vec![],
435            self.signer.as_ref(),
436            self.next_bp_hash,
437            self.block_merkle_root,
438            None,
439        )
440    }
441}
442
443impl Block {
444    pub fn mut_header(&mut self) -> &mut BlockHeader {
445        match self {
446            Block::BlockV1(block) => {
447                let block = Arc::make_mut(block);
448                &mut block.header
449            }
450            Block::BlockV2(block) => {
451                let block = Arc::make_mut(block);
452                &mut block.header
453            }
454            Block::BlockV3(block) => {
455                let block = Arc::make_mut(block);
456                &mut block.header
457            }
458        }
459    }
460
461    pub fn set_chunks(&mut self, chunks: Vec<ShardChunkHeader>) {
462        match self {
463            Block::BlockV1(block) => {
464                let block = Arc::make_mut(block);
465                let legacy_chunks = chunks
466                    .into_iter()
467                    .map(|chunk| match chunk {
468                        ShardChunkHeader::V1(header) => header,
469                        ShardChunkHeader::V2(_) => {
470                            panic!("Attempted to set V1 block chunks with V2")
471                        }
472                        ShardChunkHeader::V3(_) => {
473                            panic!("Attempted to set V1 block chunks with V3")
474                        }
475                    })
476                    .collect();
477                block.chunks = legacy_chunks;
478            }
479            Block::BlockV2(block) => {
480                let block = Arc::make_mut(block);
481                block.chunks = chunks;
482            }
483            Block::BlockV3(block) => {
484                let block = Arc::make_mut(block);
485                block.body.chunks = chunks;
486            }
487        }
488    }
489}
490
491#[derive(Default)]
492pub struct MockEpochInfoProvider {
493    pub validators: HashMap<AccountId, (Power, Balance)>,
494}
495
496impl MockEpochInfoProvider {
497    pub fn new(validators: impl Iterator<Item = (AccountId, (Power, Balance))>) -> Self {
498        MockEpochInfoProvider { validators: validators.collect() }
499    }
500}
501
502impl EpochInfoProvider for MockEpochInfoProvider {
503    fn validator_power(
504        &self,
505        _epoch_id: &EpochId,
506        _last_block_hash: &CryptoHash,
507        account_id: &AccountId,
508    ) -> Result<Option<Power>, EpochError> {
509        if let Some((power, _balance)) = self.validators.get(account_id) {
510            Ok(Some(power).cloned())
511        } else {
512            Ok(None)
513        }
514    }
515
516    fn validator_total_power(
517        &self,
518        _epoch_id: &EpochId,
519        _last_block_hash: &CryptoHash,
520    ) -> Result<Power, EpochError> {
521        let total_power: Power = self.validators.values().map(|(power, _)| power).sum();
522        Ok(total_power)
523    }
524
525    fn minimum_power(&self, _prev_block_hash: &CryptoHash) -> Result<Power, EpochError> {
526        Ok(0)
527    }
528
529    fn validator_stake(
530        &self,
531        _epoch_id: &EpochId,
532        _last_block_hash: &CryptoHash,
533        account_id: &AccountId,
534    ) -> Result<Option<Balance>, EpochError> {
535        if let Some((_power, balance)) = self.validators.get(account_id) {
536            Ok(Some(balance).cloned())
537        } else {
538            Ok(None)
539        }
540    }
541
542    fn validator_total_stake(
543        &self,
544        _epoch_id: &EpochId,
545        _last_block_hash: &CryptoHash,
546    ) -> Result<Balance, EpochError> {
547        let total_pledge: Balance = self.validators.values().map(|(_, pledge)| pledge).sum();
548        Ok(total_pledge)
549    }
550
551    fn minimum_pledge(&self, _prev_block_hash: &CryptoHash) -> Result<Balance, EpochError> {
552        Ok(0)
553    }
554}
555
556/// Encode array of `u64` to be passed as a smart contract argument.
557pub fn encode(xs: &[u64]) -> Vec<u8> {
558    xs.iter().flat_map(|it| it.to_le_bytes()).collect()
559}
560
561// Helper function that creates a new signer for a given account, that uses the account name as seed.
562// Should be used only in tests.
563pub fn create_test_signer(account_name: &str) -> InMemoryValidatorSigner {
564    InMemoryValidatorSigner::from_seed(
565        account_name.parse().unwrap(),
566        KeyType::ED25519,
567        account_name,
568    )
569}
570
571/// Helper function that creates a new signer for a given account, that uses the account name as seed.
572///
573/// This also works for predefined implicit accounts, where the signer will use the implicit key.
574///
575/// Should be used only in tests.
576pub fn create_user_test_signer(account_name: &AccountIdRef) -> InMemorySigner {
577    let account_id = account_name.to_owned();
578    if account_id == unc_implicit_test_account() {
579        InMemorySigner::from_secret_key(account_id, unc_implicit_test_account_secret())
580    } else {
581        InMemorySigner::from_seed(account_id, KeyType::ED25519, account_name.as_str())
582    }
583}
584
585/// A fixed UNC-implicit account for which tests can know the private key.
586pub fn unc_implicit_test_account() -> AccountId {
587    "061b1dd17603213b00e1a1e53ba060ad427cef4887bd34a5e0ef09010af23b0a".parse().unwrap()
588}
589
590/// Private key for the fixed UNC-implicit test account.
591pub fn unc_implicit_test_account_secret() -> SecretKey {
592    "ed25519:5roj6k68kvZu3UEJFyXSfjdKGrodgZUfFLZFpzYXWtESNsLWhYrq3JGi4YpqeVKuw1m9R2TEHjfgWT1fjUqB1DNy".parse().unwrap()
593}
594
595/// A fixed ETH-implicit account.
596pub fn eth_implicit_test_account() -> AccountId {
597    "0x96791e923f8cf697ad9c3290f2c9059f0231b24c".parse().unwrap()
598}
599
600impl FinalExecutionOutcomeView {
601    #[track_caller]
602    /// Check transaction and all transitive receipts for success status.
603    pub fn assert_success(&self) {
604        assert!(matches!(self.status, FinalExecutionStatus::SuccessValue(_)));
605        for (i, receipt) in self.receipts_outcome.iter().enumerate() {
606            assert!(
607                matches!(
608                    receipt.outcome.status,
609                    ExecutionStatusView::SuccessReceiptId(_) | ExecutionStatusView::SuccessValue(_),
610                ),
611                "receipt #{i} failed: {receipt:?}",
612            );
613        }
614    }
615}