concordium_smart_contract_testing/impls.rs
1use crate::{
2 constants,
3 invocation::{ChangeSet, EntrypointInvocationHandler, TestConfigurationError},
4 types::*,
5 CONTRACT_MODULE_OUTPUT_PATH_ENV_VAR,
6};
7use anyhow::anyhow;
8use concordium_rust_sdk::{
9 self as sdk, base,
10 base::{
11 base::{AccountThreshold, Energy, InsufficientEnergy},
12 constants::MAX_WASM_MODULE_SIZE,
13 contracts_common::{
14 self, AccountAddress, AccountBalance, Address, Amount, ChainMetadata, ContractAddress,
15 Deserial, Duration, ExchangeRate, ExchangeRates, ModuleReference, OwnedPolicy,
16 ParseResult, SlotTime, Timestamp,
17 },
18 hashes::BlockHash,
19 smart_contracts::{ContractEvent, ModuleSource, WasmModule, WasmVersion},
20 transactions::{
21 self, cost, AccountAccessStructure, InitContractPayload, UpdateContractPayload,
22 },
23 },
24 smart_contracts::engine::{
25 v0,
26 v1::{self, DebugTracker, InvalidReturnCodeError, InvokeResponse},
27 wasm,
28 wasm::validate::ValidationConfig,
29 DebugInfo, InterpreterEnergy,
30 },
31 v2::Endpoint,
32};
33use num_bigint::BigUint;
34use num_integer::Integer;
35use sdk::{
36 smart_contracts::engine::wasm::CostConfigurationV1,
37 types::smart_contracts::InvokeContractResult,
38};
39use std::{
40 collections::{BTreeMap, BTreeSet},
41 env,
42 future::Future,
43 path::Path,
44 sync::Arc,
45};
46use tokio::{runtime, time::timeout};
47
48/// The timeout duration set for queries with an external node.
49const EXTERNAL_NODE_QUERY_TIMEOUT: tokio::time::Duration = tokio::time::Duration::from_secs(10);
50
51/// The timeout duration set for connecting to an external node.
52const EXTERNAL_NODE_CONNECT_TIMEOUT: tokio::time::Duration = tokio::time::Duration::from_secs(3);
53
54impl Default for Chain {
55 fn default() -> Self { Self::new() }
56}
57
58impl ChainParameters {
59 /// Create a new [`ChainParameters`](Self) where
60 /// - `block_time` defaults to `0`,
61 /// - `micro_ccd_per_euro` defaults to `50000 / 1`
62 /// - `euro_per_energy` defaults to `1 / 50000`.
63 ///
64 /// With these exchange rates, one energy costs one microCCD.
65 pub fn new() -> Self {
66 Self::new_with_time_and_rates(
67 Timestamp::from_timestamp_millis(0),
68 ExchangeRate::new_unchecked(50000, 1),
69 ExchangeRate::new_unchecked(1, 50000),
70 )
71 .expect("Parameters are in range.")
72 }
73
74 /// Create a new [`ChainParameters`](Self) with a specified `block_time`
75 /// where
76 /// - `micro_ccd_per_euro` defaults to `50000 / 1`
77 /// - `euro_per_energy` defaults to `1 / 50000`.
78 pub fn new_with_time(block_time: SlotTime) -> Self {
79 Self {
80 block_time,
81 ..Self::new()
82 }
83 }
84
85 /// Create a new [`ChainParameters`](Self) where all the configurable
86 /// parameters are provided.
87 ///
88 /// Returns an error if the exchange rates provided makes one energy cost
89 /// more than `u64::MAX / 100_000_000_000`.
90 pub fn new_with_time_and_rates(
91 block_time: SlotTime,
92 micro_ccd_per_euro: ExchangeRate,
93 euro_per_energy: ExchangeRate,
94 ) -> Result<Self, ExchangeRateError> {
95 // Ensure the exchange rates are within a valid range.
96 check_exchange_rates(euro_per_energy, micro_ccd_per_euro)?;
97
98 Ok(Self {
99 block_time,
100 micro_ccd_per_euro,
101 euro_per_energy,
102 })
103 }
104
105 /// Helper function for converting [`Energy`] to [`Amount`] using the two
106 /// [`ExchangeRate`]s `euro_per_energy` and `micro_ccd_per_euro`.
107 pub fn calculate_energy_cost(&self, energy: Energy) -> Amount {
108 energy_to_amount(energy, self.euro_per_energy, self.micro_ccd_per_euro)
109 }
110}
111
112impl ChainBuilder {
113 /// Create a new [`ChainBuilder`] for constructing the [`Chain`].
114 ///
115 /// Can also be created via the [`Chain::builder`] method.
116 ///
117 /// To complete the building process, use [`ChainBuilder::build`], see the
118 /// example below.
119 ///
120 /// # Example
121 /// ```
122 /// # use concordium_smart_contract_testing::*;
123 ///
124 /// let chain = ChainBuilder::new()
125 /// // Use zero or more builder methods, for example:
126 /// .micro_ccd_per_euro(ExchangeRate::new_unchecked(50000, 1))
127 /// .block_time(Timestamp::from_timestamp_millis(123))
128 /// // Then build:
129 /// .build()
130 /// .unwrap();
131 /// ```
132 pub fn new() -> Self {
133 Self {
134 external_node_endpoint: None,
135 external_query_block: None,
136 micro_ccd_per_euro: None,
137 micro_ccd_per_euro_from_external: false,
138 euro_per_energy: None,
139 euro_per_energy_from_external: false,
140 block_time: None,
141 block_time_from_external: false,
142 }
143 }
144
145 /// Configure a connection to an external Concordium node.
146 ///
147 /// The connection can be used for getting the current exchange rates
148 /// between CCD, Euro and Energy.
149 ///
150 /// # Example
151 ///
152 /// ```no_run
153 /// # use concordium_smart_contract_testing::*;
154 /// let chain = Chain::builder()
155 /// .external_node_connection(Endpoint::from_static("http://node.testnet.concordium.com:20000"))
156 /// .build()
157 /// .unwrap();
158 /// ```
159 pub fn external_node_connection(mut self, endpoint: impl Into<Endpoint>) -> Self {
160 self.external_node_endpoint = Some(endpoint.into());
161 self
162 }
163
164 /// Configure the block to be used for all external queries.
165 ///
166 /// If this is not set, then the last final block will be queried during
167 /// [`ChainBuilder::build`] and saved, so it can be used for future queries.
168 ///
169 /// This can only be used in combination with
170 /// [`external_node_connection`][Self::external_node_connection].
171 ///
172 /// To view the configured block, see [`Chain::external_query_block`].
173 ///
174 /// # Example
175 ///
176 /// ```no_run
177 /// # use concordium_smart_contract_testing::*;
178 /// let chain = Chain::builder()
179 /// .external_node_connection(Endpoint::from_static("http://node.testnet.concordium.com:20000"))
180 /// .external_query_block(
181 /// "95ff82f26892a2327c3e7ac582224a54d75c367341fbff209bce552d81349eb0".parse().unwrap(),
182 /// )
183 /// .build()
184 /// .unwrap();
185 /// ```
186 pub fn external_query_block(mut self, query_block: BlockHash) -> Self {
187 self.external_query_block = Some(query_block);
188 self
189 }
190
191 /// Configure the 'microCCD per euro' exchange rate.
192 ///
193 /// By default the rate is `50000 / 1`.
194 ///
195 /// This cannot be used together with
196 /// [`ChainBuilder::micro_ccd_per_euro_from_external`].
197 ///
198 /// # Example
199 /// ```
200 /// # use concordium_smart_contract_testing::*;
201 /// let chain = ChainBuilder::new()
202 /// .micro_ccd_per_euro(ExchangeRate::new_unchecked(50000, 1))
203 /// .build()
204 /// .unwrap();
205 /// ```
206 pub fn micro_ccd_per_euro(mut self, exchange_rate: ExchangeRate) -> Self {
207 self.micro_ccd_per_euro = Some(exchange_rate);
208 self
209 }
210
211 /// Configure the 'euro per energy' exchange rate.
212 ///
213 /// By default the rate is `1 / 50000`.
214 ///
215 /// This cannot be used together with
216 /// [`ChainBuilder::euro_per_energy_from_external`].
217 ///
218 /// # Example
219 /// ```
220 /// # use concordium_smart_contract_testing::*;
221 /// let chain =
222 /// ChainBuilder::new().euro_per_energy(ExchangeRate::new_unchecked(1, 50000)).build().unwrap();
223 /// ```
224 pub fn euro_per_energy(mut self, exchange_rate: ExchangeRate) -> Self {
225 self.euro_per_energy = Some(exchange_rate);
226 self
227 }
228
229 /// Configure the exchange rate between microCCD and euro using the external
230 /// node connection.
231 ///
232 /// This can only be used in combination with
233 /// [`external_node_connection`][Self::external_node_connection], and it
234 /// cannot be used together with
235 /// [`micro_ccd_per_euro`][Self::micro_ccd_per_euro].
236 ///
237 /// # Example
238 /// ```
239 /// # use concordium_smart_contract_testing::*;
240 /// let chain = ChainBuilder::new()
241 /// .external_node_connection(Endpoint::from_static("http://node.testnet.concordium.com:20000"))
242 /// .micro_ccd_per_euro_from_external()
243 /// .build()
244 /// .unwrap();
245 /// ```
246 pub fn micro_ccd_per_euro_from_external(mut self) -> Self {
247 self.micro_ccd_per_euro_from_external = true;
248 self
249 }
250
251 /// Configure the exchange rate between euro and energy using the external
252 /// node connection.
253 ///
254 /// This can only be used in combination with
255 /// [`external_node_connection`][Self::external_node_connection], and it
256 /// cannot be used together with
257 /// [`euro_per_energy`][Self::euro_per_energy].
258 ///
259 /// # Example
260 /// ```
261 /// # use concordium_smart_contract_testing::*;
262 /// let chain = ChainBuilder::new()
263 /// .external_node_connection(Endpoint::from_static("http://node.testnet.concordium.com:20000"))
264 /// .euro_per_energy_from_external()
265 /// .build()
266 /// .unwrap();
267 /// ```
268 pub fn euro_per_energy_from_external(mut self) -> Self {
269 self.euro_per_energy_from_external = true;
270 self
271 }
272
273 /// Configure the block time.
274 ///
275 /// By default the block time is `0`.
276 ///
277 /// This cannot be used in combination with
278 /// [`ChainBuilder::block_time_from_external`].
279 ///
280 /// # Example
281 /// ```
282 /// # use concordium_smart_contract_testing::*;
283 /// let chain = ChainBuilder::new()
284 /// .block_time(Timestamp::from_timestamp_millis(1687440701000))
285 /// .build()
286 /// .unwrap();
287 /// ```
288 pub fn block_time(mut self, block_time: Timestamp) -> Self {
289 self.block_time = Some(block_time);
290 self
291 }
292
293 /// Configure the block time using the external node connection.
294 ///
295 /// This can only be used in combination with
296 /// [`external_node_connection`][Self::external_node_connection], and it
297 /// cannot be used together with
298 /// [`block_time`][Self::block_time].
299 ///
300 /// # Example
301 /// ```
302 /// # use concordium_smart_contract_testing::*;
303 /// let chain = ChainBuilder::new()
304 /// .external_node_connection(Endpoint::from_static("http://node.testnet.concordium.com:20000"))
305 /// .block_time_from_external()
306 /// .build()
307 /// .unwrap();
308 /// ```
309 pub fn block_time_from_external(mut self) -> Self {
310 self.block_time_from_external = true;
311 self
312 }
313
314 /// Build the [`Chain`] with the configured options.
315 ///
316 /// # Example
317 /// ```
318 /// # use concordium_smart_contract_testing::*;
319 ///
320 /// let chain = Chain::builder()
321 /// // Use zero or more builder methods, for example:
322 /// .euro_per_energy(ExchangeRate::new_unchecked(1, 50000))
323 /// .micro_ccd_per_euro(ExchangeRate::new_unchecked(50000, 1))
324 /// // Then build:
325 /// .build()
326 /// .unwrap();
327 /// ```
328 pub fn build(self) -> Result<Chain, ChainBuilderError> {
329 // Create the chain with default parameters.
330 let mut chain = Chain::new();
331
332 // Setup the external node connection if provided. This also forwards and sets
333 // the external query block.
334 if let Some(endpoint) = self.external_node_endpoint {
335 chain.setup_external_node_connection(endpoint, self.external_query_block)?;
336 }
337
338 // Check for conflicting exchange rate configurations.
339 if self.micro_ccd_per_euro.is_some() && self.micro_ccd_per_euro_from_external {
340 return Err(ChainBuilderError::ConflictingMicroCCDPerEuro);
341 }
342 if self.euro_per_energy.is_some() && self.euro_per_energy_from_external {
343 return Err(ChainBuilderError::ConflictingEuroPerEnergy);
344 }
345
346 // Set the exchange rates via an external query.
347 if self.micro_ccd_per_euro_from_external || self.euro_per_energy_from_external {
348 let exchange_rates = chain.get_exchange_rates_via_external_node()?;
349 if self.micro_ccd_per_euro_from_external {
350 chain.parameters.micro_ccd_per_euro = exchange_rates.micro_ccd_per_euro;
351 }
352 if self.euro_per_energy_from_external {
353 chain.parameters.euro_per_energy = exchange_rates.euro_per_energy;
354 }
355 }
356
357 // Set the exchange rates directly.
358 if let Some(micro_ccd_per_euro) = self.micro_ccd_per_euro {
359 chain.parameters.micro_ccd_per_euro = micro_ccd_per_euro;
360 }
361 if let Some(euro_per_energy) = self.euro_per_energy {
362 chain.parameters.euro_per_energy = euro_per_energy;
363 }
364
365 // Check the exchange rates and return early if they are invalid.
366 check_exchange_rates(
367 chain.parameters.euro_per_energy,
368 chain.parameters.micro_ccd_per_euro,
369 )?;
370
371 match (self.block_time, self.block_time_from_external) {
372 (Some(_), true) => return Err(ChainBuilderError::ConflictingBlockTime),
373 (Some(block_time), false) => {
374 chain.parameters.block_time = block_time;
375 }
376 (None, true) => {
377 chain.set_block_time_via_external_node()?;
378 }
379 (None, false) => (),
380 }
381
382 // Replace the default block time if provided.
383 if let Some(block_time) = self.block_time {
384 chain.parameters.block_time = block_time;
385 }
386
387 Ok(chain)
388 }
389}
390
391impl Default for ChainBuilder {
392 fn default() -> Self { Self::new() }
393}
394
395// Exit early with an out of energy error.
396macro_rules! exit_ooe {
397 ($charge:expr, $trace:expr) => {
398 if let Err(InsufficientEnergy) = $charge {
399 return Err(ContractInitErrorKind::OutOfEnergy {
400 debug_trace: $trace,
401 });
402 }
403 };
404}
405
406impl Chain {
407 /// Get a [`ChainBuilder`] for constructing a new [`Chain`] with a builder
408 /// pattern.
409 ///
410 /// See the [`ChainBuilder`] for more details.
411 pub fn builder() -> ChainBuilder { ChainBuilder::new() }
412
413 /// Create a new [`Chain`](Self) where all the configurable parameters are
414 /// provided.
415 ///
416 /// Returns an error if the exchange rates provided makes one energy cost
417 /// more than `u64::MAX / 100_000_000_000`.
418 ///
419 /// *For more configuration options and flexibility, use the builder
420 /// pattern. See [`Chain::builder`].*
421 pub fn new_with_time_and_rates(
422 block_time: SlotTime,
423 micro_ccd_per_euro: ExchangeRate,
424 euro_per_energy: ExchangeRate,
425 ) -> Result<Self, ExchangeRateError> {
426 Ok(Self {
427 parameters: ChainParameters::new_with_time_and_rates(
428 block_time,
429 micro_ccd_per_euro,
430 euro_per_energy,
431 )?,
432 accounts: BTreeMap::new(),
433 modules: BTreeMap::new(),
434 contracts: BTreeMap::new(),
435 next_contract_index: 0,
436 external_node_connection: None,
437 })
438 }
439
440 /// Create a new [`Chain`](Self) with a specified `block_time` where
441 /// - `micro_ccd_per_euro` defaults to `50000 / 1`
442 /// - `euro_per_energy` defaults to `1 / 50000`.
443 ///
444 /// *For more configuration options and flexibility, use the builder
445 /// pattern. See [`Chain::builder`].*
446 pub fn new_with_time(block_time: SlotTime) -> Self {
447 Self {
448 parameters: ChainParameters::new_with_time(block_time),
449 ..Self::new()
450 }
451 }
452
453 /// Create a new [`Chain`](Self) where
454 /// - `block_time` defaults to `0`,
455 /// - `micro_ccd_per_euro` defaults to `50000 / 1`
456 /// - `euro_per_energy` defaults to `1 / 50000`.
457 ///
458 /// With these exchange rates, one energy costs one microCCD.
459 ///
460 /// *For more configuration options and flexibility, use the builder
461 /// pattern. See [`Chain::builder`].*
462 pub fn new() -> Self {
463 Self::new_with_time_and_rates(
464 Timestamp::from_timestamp_millis(0),
465 ExchangeRate::new_unchecked(50000, 1),
466 ExchangeRate::new_unchecked(1, 50000),
467 )
468 .expect("Rates known to be within range.")
469 }
470
471 /// Helper function for converting [`Energy`] to [`Amount`] using the two
472 /// [`ExchangeRate`]s `euro_per_energy` and `micro_ccd_per_euro`.
473 pub fn calculate_energy_cost(&self, energy: Energy) -> Amount {
474 self.parameters.calculate_energy_cost(energy)
475 }
476
477 /// Get the state of the contract if it exists in the [`Chain`](Self).
478 pub fn get_contract(&self, address: ContractAddress) -> Option<&Contract> {
479 self.contracts.get(&address)
480 }
481
482 /// Get the the module if it exists in the [`Chain`](Self).
483 pub fn get_module(&self, module: ModuleReference) -> Option<&ContractModule> {
484 self.modules.get(&module)
485 }
486
487 /// Get the state of the account if it exists in the [`Chain`](Self).
488 /// Account addresses that are aliases will return the same account.
489 pub fn get_account(&self, address: AccountAddress) -> Option<&Account> {
490 self.accounts.get(&address.into())
491 }
492
493 /// Deploy a smart contract module using the same validation rules as
494 /// enforced by the node.
495 ///
496 /// The `WasmModule` can be loaded from disk with either
497 /// [`module_load_v1`] or [`module_load_v1_raw`].
498 ///
499 /// Parameters:
500 /// - `signer`: the signer with a number of keys, which affects the cost.
501 /// - `sender`: the sender account.
502 /// - `module`: the v1 wasm module.
503 pub fn module_deploy_v1(
504 &mut self,
505 signer: Signer,
506 sender: AccountAddress,
507 wasm_module: WasmModule,
508 ) -> Result<ModuleDeploySuccess, ModuleDeployError> {
509 self.module_deploy_v1_debug(signer, sender, wasm_module, false)
510 }
511
512 /// Like [`module_deploy_v1`](Self::module_deploy_v1)
513 /// except that optionally debugging output may be allowed in the module.
514 pub fn module_deploy_v1_debug(
515 &mut self,
516 signer: Signer,
517 sender: AccountAddress,
518 wasm_module: WasmModule,
519 enable_debug: bool,
520 ) -> Result<ModuleDeploySuccess, ModuleDeployError> {
521 // For maintainers:
522 //
523 // This function does not correspond exactly to what happens in the node.
524 // There a user is also expected to give a max energy bound and the failures are
525 // slightly different. There it is possible to fail with "out of energy"
526 // error whereas here we only fail with "insufficient funds" if the user does
527 // not have enough CCD to pay.
528 //
529 // If users use our tools to deploy modules the costs are calculated for them so
530 // that deployment should never fail with out of energy. Not requiring energy
531 // provides a more ergonomic experience.
532 let Ok(sender_account) = self.accounts.get_mut(&sender.into()).ok_or(AccountDoesNotExist {
533 address: sender,
534 }) else {
535 // Ensure sender account exists.
536 return Err(ModuleDeployError {
537 kind: ModuleDeployErrorKind::SenderDoesNotExist(AccountDoesNotExist {
538 address: sender,
539 }),
540 energy_used: 0.into(),
541 transaction_fee: Amount::zero(),
542 });
543 };
544
545 // Only v1 modules are supported in this testing library.
546 // This error case does not exist in the node, so we don't need to match a
547 // specific cost. We charge 0 for it.
548 if wasm_module.version != WasmVersion::V1 {
549 return Err(ModuleDeployError {
550 kind: ModuleDeployErrorKind::UnsupportedModuleVersion(
551 wasm_module.version,
552 ),
553 energy_used: 0.into(),
554 transaction_fee: Amount::zero(),
555 });
556 }
557
558 let parameters = &self.parameters;
559 let check_header_energy = {
560 // +1 for the tag, +8 for size and version
561 let payload_size = 1
562 + 8
563 + wasm_module.source.size()
564 + transactions::construct::TRANSACTION_HEADER_SIZE;
565 cost::base_cost(payload_size, signer.num_keys)
566 };
567
568 // Calculate the deploy module cost.
569 let deploy_module_energy = cost::deploy_module(wasm_module.source.size());
570 let energy_used = check_header_energy + deploy_module_energy;
571 let transaction_fee = parameters.calculate_energy_cost(energy_used);
572
573 // Check if the account has sufficient balance to cover the transaction fee.
574 // This fee corresponds to the energy_reserved that our tools calculate when
575 // sending the transaction to the node. The account is not charged in the node
576 // unless it has sufficient balance to pay for the full deployment (and thus all
577 // the energy).
578 if sender_account.balance.available() < transaction_fee {
579 return Err(ModuleDeployError {
580 kind: ModuleDeployErrorKind::InsufficientFunds,
581 energy_used: 0.into(),
582 transaction_fee: Amount::zero(),
583 });
584 };
585
586 // Charge the account.
587 sender_account.balance.total -= transaction_fee;
588
589 // Construct the artifact.
590 let artifact = match wasm::utils::instantiate_with_metering::<v1::ProcessedImports>(
591 ValidationConfig::V1,
592 CostConfigurationV1,
593 &v1::ConcordiumAllowedImports {
594 support_upgrade: true,
595 enable_debug,
596 },
597 wasm_module.source.as_ref(),
598 ) {
599 Ok(artifact) => artifact,
600 Err(err) => {
601 return Err(ModuleDeployError {
602 kind: ModuleInvalidError(err).into(),
603 energy_used,
604 transaction_fee,
605 })
606 }
607 };
608
609 let module_reference: ModuleReference = wasm_module.get_module_ref();
610
611 // Ensure module hasn't been deployed before.
612 if self.modules.contains_key(&module_reference) {
613 return Err(ModuleDeployError {
614 kind: ModuleDeployErrorKind::DuplicateModule(module_reference),
615 energy_used,
616 transaction_fee,
617 });
618 }
619 self.modules.insert(module_reference, ContractModule {
620 // we follow protocol 6 semantics, and don't count the custom section size towards
621 // module size.
622 size: wasm_module.source.size().saturating_sub(artifact.custom_sections_size),
623 artifact: Arc::new(artifact.artifact),
624 });
625 Ok(ModuleDeploySuccess {
626 module_reference,
627 energy_used,
628 transaction_fee,
629 })
630 }
631
632 /// Initialize a contract.
633 ///
634 /// **Parameters:**
635 /// - `signer`: the signer with a number of keys, which affects the cost.
636 /// - `sender`: The account paying for the transaction. Will also become
637 /// the owner of the contract created.
638 /// - `energy_reserved`: Amount of energy reserved for executing the init
639 /// method.
640 /// - `payload`:
641 /// - `amount`: The initial balance of the contract. Subtracted from the
642 /// `sender` account.
643 /// - `mod_ref`: The reference to the a module that has already been
644 /// deployed.
645 /// - `init_name`: Name of the contract to initialize.
646 /// - `param`: Parameter provided to the init method.
647 pub fn contract_init(
648 &mut self,
649 signer: Signer,
650 sender: AccountAddress,
651 energy_reserved: Energy,
652 payload: InitContractPayload,
653 ) -> Result<ContractInitSuccess, ContractInitError> {
654 let mut remaining_energy = energy_reserved;
655 if !self.account_exists(sender) {
656 return Err(self.convert_to_init_error(
657 ContractInitErrorKind::SenderDoesNotExist(AccountDoesNotExist {
658 address: sender,
659 }),
660 energy_reserved,
661 remaining_energy,
662 ));
663 }
664
665 let res = self.contract_init_worker(
666 signer,
667 sender,
668 energy_reserved,
669 payload,
670 &mut remaining_energy,
671 );
672
673 let (res, transaction_fee) = match res {
674 Ok(s) => {
675 let transaction_fee = s.transaction_fee;
676 (Ok(s), transaction_fee)
677 }
678 Err(e) => {
679 let err = self.convert_to_init_error(e, energy_reserved, remaining_energy);
680 let transaction_fee = err.transaction_fee;
681 (Err(err), transaction_fee)
682 }
683 };
684
685 // Charge the account.
686 self.account_mut(sender).expect("existence already checked").balance.total -=
687 transaction_fee;
688 res
689 }
690
691 /// Helper method for initializing contracts, which does most of the actual
692 /// work.
693 ///
694 /// The main reason for splitting init in two is to have this method return
695 /// early if it runs out of energy. `contract_init` will then always
696 /// ensure to charge the account for the energy used.
697 fn contract_init_worker(
698 &mut self,
699 signer: Signer,
700 sender: AccountAddress,
701 energy_reserved: Energy,
702 payload: InitContractPayload,
703 remaining_energy: &mut Energy,
704 ) -> Result<ContractInitSuccess, ContractInitErrorKind> {
705 // Get the account and check that it has sufficient balance to pay for the
706 // reserved_energy and amount.
707 let account_info = self.account(sender)?;
708
709 let energy_reserved_cost = self.parameters.calculate_energy_cost(energy_reserved);
710
711 // Check that the account can pay for the reserved energy.
712 if account_info.balance.available() < energy_reserved_cost {
713 return Err(ContractInitErrorKind::InsufficientFunds);
714 }
715
716 // Compute the base cost for checking the transaction header.
717 let check_header_cost = {
718 // 1 byte for the tag.
719 let transaction_size =
720 transactions::construct::TRANSACTION_HEADER_SIZE + 1 + payload.size() as u64;
721 transactions::cost::base_cost(transaction_size, signer.num_keys)
722 };
723
724 // Charge the header cost.
725 exit_ooe!(remaining_energy.tick_energy(check_header_cost), DebugTracker::empty_trace());
726
727 // Ensure that the parameter has a valid size.
728 if payload.param.as_ref().len() > contracts_common::constants::MAX_PARAMETER_LEN {
729 return Err(ContractInitErrorKind::ParameterTooLarge);
730 }
731
732 // Charge the base cost for initializing a contract.
733 exit_ooe!(
734 remaining_energy.tick_energy(constants::INITIALIZE_CONTRACT_INSTANCE_BASE_COST),
735 DebugTracker::empty_trace()
736 );
737
738 // Check that the account also has enough funds to pay for the amount (in
739 // addition to the reserved energy).
740 if account_info.balance.available() < energy_reserved_cost + payload.amount {
741 return Err(ContractInitErrorKind::AmountTooLarge);
742 }
743
744 // Lookup module.
745 let module = self.contract_module(payload.mod_ref)?;
746 let lookup_cost = lookup_module_cost(&module);
747
748 // Charge the cost for looking up the module.
749 exit_ooe!(remaining_energy.tick_energy(lookup_cost), DebugTracker::empty_trace());
750
751 // Ensure the module contains the provided init name.
752 let init_name = payload.init_name.as_contract_name().get_chain_name();
753 if !module.artifact.export.contains_key(init_name) {
754 return Err(ContractInitErrorKind::ContractNotPresentInModule {
755 name: payload.init_name,
756 });
757 }
758
759 // Sender policies have a very bespoke serialization in
760 // order to allow skipping portions of them in smart contracts.
761 let sender_policies = {
762 let mut out = Vec::new();
763 account_info
764 .policy
765 .serial_for_smart_contract(&mut out)
766 .expect("Writing to a vector should succeed.");
767 out
768 };
769
770 // Construct the context.
771 let init_ctx = v0::InitContext {
772 metadata: ChainMetadata {
773 slot_time: self.parameters.block_time,
774 },
775 init_origin: sender,
776 sender_policies,
777 };
778 // Initialize contract
779 // We create an empty loader as no caching is used in this testing library
780 // presently, so the loader is not used.
781 let mut loader = v1::trie::Loader::new(&[][..]);
782
783 let energy_given_to_interpreter =
784 InterpreterEnergy::new(to_interpreter_energy(*remaining_energy));
785 let res = v1::invoke_init::<_, _, DebugTracker>(
786 module.artifact,
787 init_ctx,
788 v1::InitInvocation {
789 amount: payload.amount,
790 init_name,
791 parameter: payload.param.as_ref(),
792 energy: energy_given_to_interpreter,
793 },
794 false, // We only support protocol P5 and up, so no limiting.
795 loader,
796 );
797 // Handle the result
798 match res {
799 Ok(v1::InitResult::Success {
800 logs,
801 return_value: _, /* Ignore return value for now, since our tools do not support
802 * it for inits, currently. */
803 remaining_energy: remaining_interpreter_energy,
804 mut state,
805 trace,
806 }) => {
807 let contract_address = self.create_contract_address();
808 let mut collector = v1::trie::SizeCollector::default();
809
810 let persisted_state = state.freeze(&mut loader, &mut collector);
811
812 // Perform the subtraction in the more finegrained (*1000) `InterpreterEnergy`,
813 // and *then* convert to `Energy`. This is how it is done in the node, and if we
814 // swap the operations, it can result in a small discrepancy due to rounding.
815 let energy_used_in_interpreter = from_interpreter_energy(
816 &energy_given_to_interpreter.saturating_sub(&remaining_interpreter_energy),
817 );
818 exit_ooe!(remaining_energy.tick_energy(energy_used_in_interpreter), trace);
819
820 // Charge one energy per stored state byte.
821 let energy_for_state_storage = Energy::from(collector.collect());
822 exit_ooe!(remaining_energy.tick_energy(energy_for_state_storage), trace);
823
824 // Charge the constant cost for initializing a contract.
825 exit_ooe!(
826 remaining_energy
827 .tick_energy(constants::INITIALIZE_CONTRACT_INSTANCE_CREATE_COST),
828 trace
829 );
830
831 let contract = Contract {
832 module_reference: payload.mod_ref,
833 contract_name: payload.init_name,
834 state: persisted_state,
835 owner: sender,
836 self_balance: payload.amount,
837 address: contract_address,
838 };
839
840 // Save the contract.
841 self.contracts.insert(contract_address, contract);
842
843 // Subtract the amount from the invoker.
844 self.account_mut(sender).expect("Account known to exist").balance.total -=
845 payload.amount;
846
847 let energy_used = energy_reserved - *remaining_energy;
848 let transaction_fee = self.parameters.calculate_energy_cost(energy_used);
849
850 Ok(ContractInitSuccess {
851 contract_address,
852 events: contract_events_from_logs(logs),
853 energy_used,
854 transaction_fee,
855 debug_trace: trace,
856 })
857 }
858 Ok(v1::InitResult::Reject {
859 reason,
860 return_value,
861 remaining_energy: remaining_interpreter_energy,
862 trace,
863 }) => {
864 let energy_used_in_interpreter = from_interpreter_energy(
865 &energy_given_to_interpreter.saturating_sub(&remaining_interpreter_energy),
866 );
867 exit_ooe!(remaining_energy.tick_energy(energy_used_in_interpreter), trace);
868 Err(ContractInitErrorKind::ExecutionError {
869 error: InitExecutionError::Reject {
870 reason,
871 return_value,
872 },
873 debug_trace: trace,
874 })
875 }
876 Ok(v1::InitResult::Trap {
877 error,
878 remaining_energy: remaining_interpreter_energy,
879 trace,
880 }) => {
881 let energy_used_in_interpreter = from_interpreter_energy(
882 &energy_given_to_interpreter.saturating_sub(&remaining_interpreter_energy),
883 );
884 exit_ooe!(remaining_energy.tick_energy(energy_used_in_interpreter), trace);
885 Err(ContractInitErrorKind::ExecutionError {
886 error: InitExecutionError::Trap {
887 error: error.into(),
888 },
889 debug_trace: trace,
890 })
891 }
892 Ok(v1::InitResult::OutOfEnergy {
893 trace,
894 }) => {
895 *remaining_energy = Energy::from(0);
896 Err(ContractInitErrorKind::ExecutionError {
897 error: InitExecutionError::OutOfEnergy,
898 debug_trace: trace,
899 })
900 }
901 Err(InvalidReturnCodeError {
902 value,
903 debug_trace,
904 }) => Err(ContractInitErrorKind::ExecutionError {
905 error: InitExecutionError::Trap {
906 error: anyhow::anyhow!("Invalid return value received: {value:?}").into(),
907 },
908 debug_trace,
909 }),
910 }
911 }
912
913 /// Helper method that handles contract invocation.
914 ///
915 /// *Preconditions:*
916 /// - `invoker` exists.
917 /// - `sender` exists.
918 /// - `invoker` has sufficient balance to pay for `energy_reserved`.
919 fn contract_invocation_worker(
920 &self,
921 invoker: AccountAddress,
922 sender: Address,
923 energy_reserved: Energy,
924 amount_reserved_for_energy: Amount,
925 payload: UpdateContractPayload,
926 remaining_energy: &mut Energy,
927 ) -> Result<(InvokeResponse, ChangeSet, Vec<DebugTraceElement>, Energy), ContractInvokeError>
928 {
929 // Check if the contract to invoke exists.
930 if !self.contract_exists(payload.address) {
931 return Err(self.convert_to_invoke_error(
932 ContractDoesNotExist {
933 address: payload.address,
934 }
935 .into(),
936 Vec::new(),
937 energy_reserved,
938 *remaining_energy,
939 0.into(),
940 ));
941 }
942
943 // Ensure that the parameter has a valid size.
944 if payload.message.as_ref().len() > contracts_common::constants::MAX_PARAMETER_LEN {
945 return Err(self.convert_to_invoke_error(
946 ContractInvokeErrorKind::ParameterTooLarge,
947 Vec::new(),
948 energy_reserved,
949 *remaining_energy,
950 0.into(),
951 ));
952 }
953
954 // Check that the invoker has sufficient funds to pay for amount (in addition to
955 // the energy reserved, which is already checked).
956 if self
957 .account(invoker)
958 .expect("Precondition violation: must already exist")
959 .balance
960 .available()
961 < amount_reserved_for_energy + payload.amount
962 {
963 return Err(self.convert_to_invoke_error(
964 ContractInvokeErrorKind::AmountTooLarge,
965 Vec::new(),
966 energy_reserved,
967 *remaining_energy,
968 0.into(),
969 ));
970 }
971
972 let mut contract_invocation = EntrypointInvocationHandler {
973 changeset: ChangeSet::new(),
974 remaining_energy,
975 energy_reserved,
976 chain: self,
977 reserved_amount: amount_reserved_for_energy,
978 invoker,
979 // Starts at 1 since 0 is the "initial state" of all contracts in the current
980 // transaction.
981 next_contract_modification_index: 1,
982 module_load_energy: 0.into(),
983 };
984 let module_load_energy = contract_invocation.module_load_energy;
985 let res = contract_invocation.invoke_entrypoint(invoker, sender, payload);
986 match res {
987 Ok((result, trace_elements)) => Ok((
988 result,
989 contract_invocation.changeset,
990 trace_elements,
991 contract_invocation.module_load_energy,
992 )),
993 Err(err) => Err(self.convert_to_invoke_error(
994 err.into(),
995 Vec::new(),
996 energy_reserved,
997 *remaining_energy,
998 module_load_energy,
999 )),
1000 }
1001 }
1002
1003 // Since this is an internal function it seems better to allow rather than
1004 // introduce a new struct just to call this function.
1005 #[allow(clippy::too_many_arguments)]
1006 fn contract_invocation_process_response(
1007 &self,
1008 result: InvokeResponse,
1009 trace_elements: Vec<DebugTraceElement>,
1010 energy_reserved: Energy,
1011 remaining_energy: Energy,
1012 state_energy: Energy,
1013 state_changed: bool,
1014 module_load_energy: Energy,
1015 ) -> Result<ContractInvokeSuccess, ContractInvokeError> {
1016 match result {
1017 v1::InvokeResponse::Success {
1018 new_balance,
1019 data,
1020 } => {
1021 let energy_used = energy_reserved - remaining_energy;
1022 let transaction_fee = self.parameters.calculate_energy_cost(energy_used);
1023 Ok(ContractInvokeSuccess {
1024 trace_elements,
1025 energy_used,
1026 transaction_fee,
1027 return_value: data.unwrap_or_default(),
1028 state_changed,
1029 new_balance,
1030 storage_energy: state_energy,
1031 module_load_energy,
1032 })
1033 }
1034 v1::InvokeResponse::Failure {
1035 kind,
1036 } => Err(self.convert_to_invoke_error(
1037 ContractInvokeErrorKind::ExecutionError {
1038 failure_kind: kind,
1039 },
1040 trace_elements,
1041 energy_reserved,
1042 remaining_energy,
1043 module_load_energy,
1044 )),
1045 }
1046 }
1047
1048 /// Update a contract by calling one of its entrypoints.
1049 ///
1050 /// If successful, all changes will be saved.
1051 ///
1052 /// **Parameters:**
1053 /// - `signer`: a [`Signer`] with a number of keys. The number of keys
1054 /// affects the cost of the transaction.
1055 /// - `invoker`: the account paying for the transaction.
1056 /// - `sender`: the sender of the message, can be an account or contract.
1057 /// For top-level invocations, such as those caused by sending a contract
1058 /// update transaction on the chain, the `sender` is always the
1059 /// `invoker`. Here we provide extra freedom for testing invocations
1060 /// where the sender differs.
1061 /// - `energy_reserved`: the maximum energy that can be used in the update.
1062 /// - `payload`: The data detailing which contract and receive method to
1063 /// call etc.
1064 pub fn contract_update(
1065 &mut self,
1066 signer: Signer,
1067 invoker: AccountAddress,
1068 sender: Address,
1069 energy_reserved: Energy,
1070 payload: UpdateContractPayload,
1071 ) -> Result<ContractInvokeSuccess, ContractInvokeError> {
1072 // Ensure the sender exists.
1073 if !self.address_exists(sender) {
1074 // This situation never happens on the chain since to send a message the sender
1075 // is verified upfront. So what we do here is custom behaviour, and we reject
1076 // without consuming any energy.
1077 return Err(ContractInvokeError {
1078 energy_used: Energy::from(0),
1079 transaction_fee: Amount::zero(),
1080 trace_elements: Vec::new(),
1081 kind: ContractInvokeErrorKind::SenderDoesNotExist(sender),
1082 module_load_energy: 0.into(),
1083 });
1084 }
1085
1086 // Ensure the invoker exists.
1087 let Ok(account_info) = self.account(invoker) else {
1088 return Err(ContractInvokeError {
1089 energy_used: Energy::from(0),
1090 transaction_fee: Amount::zero(),
1091 trace_elements: Vec::new(),
1092 kind: ContractInvokeErrorKind::InvokerDoesNotExist(
1093 AccountDoesNotExist {
1094 address: invoker,
1095 },
1096 ),
1097 module_load_energy: 0.into(),
1098 });
1099 };
1100
1101 // Compute the base cost for checking the transaction header.
1102 let check_header_cost = {
1103 // 1 byte for the tag.
1104 let transaction_size =
1105 transactions::construct::TRANSACTION_HEADER_SIZE + 1 + payload.size() as u64;
1106 transactions::cost::base_cost(transaction_size, signer.num_keys)
1107 };
1108
1109 // Charge the header cost.
1110 let mut remaining_energy =
1111 energy_reserved.checked_sub(check_header_cost).ok_or(ContractInvokeError {
1112 energy_used: Energy::from(0),
1113 transaction_fee: Amount::zero(),
1114 trace_elements: Vec::new(),
1115 kind: ContractInvokeErrorKind::OutOfEnergy {
1116 debug_trace: DebugTracker::empty_trace(), // we haven't done anything yet.
1117 },
1118 module_load_energy: 0.into(),
1119 })?;
1120
1121 let invoker_amount_reserved_for_nrg =
1122 self.parameters.calculate_energy_cost(energy_reserved);
1123
1124 // Ensure the account has sufficient funds to pay for the energy.
1125 if account_info.balance.available() < invoker_amount_reserved_for_nrg {
1126 let energy_used = energy_reserved - remaining_energy;
1127 return Err(ContractInvokeError {
1128 energy_used,
1129 transaction_fee: self.parameters.calculate_energy_cost(energy_used),
1130 trace_elements: Vec::new(),
1131 kind: ContractInvokeErrorKind::InsufficientFunds,
1132 module_load_energy: 0.into(),
1133 });
1134 }
1135
1136 let contract_address = payload.address;
1137 let res = self.contract_invocation_worker(
1138 invoker,
1139 sender,
1140 energy_reserved,
1141 invoker_amount_reserved_for_nrg,
1142 payload,
1143 &mut remaining_energy,
1144 );
1145 let res = match res {
1146 Ok((result, changeset, trace_elements, module_load_energy)) => {
1147 // Charge energy for contract storage. Or return an error if out
1148 // of energy.
1149 let (state_energy, state_changed) =
1150 if matches!(result, v1::InvokeResponse::Success { .. }) {
1151 let energy_before = remaining_energy;
1152 let res = changeset.persist(
1153 &mut remaining_energy,
1154 contract_address,
1155 &mut self.accounts,
1156 &mut self.contracts,
1157 );
1158 let state_energy = energy_before.checked_sub(remaining_energy).unwrap();
1159 if let Ok(res) = res {
1160 (state_energy, res)
1161 } else {
1162 // the error happens when storing the state, so there are no trace
1163 // elements associated with it. The trace is
1164 // already in the "debug trace" vector.
1165 return Err(self.invocation_out_of_energy_error(
1166 energy_reserved,
1167 DebugTracker::empty_trace(),
1168 module_load_energy,
1169 ));
1170 }
1171 } else {
1172 // An error occurred, so state hasn't changed.
1173 (0.into(), false)
1174 };
1175 self.contract_invocation_process_response(
1176 result,
1177 trace_elements,
1178 energy_reserved,
1179 remaining_energy,
1180 state_energy,
1181 state_changed,
1182 module_load_energy,
1183 )
1184 }
1185 Err(e) => Err(e),
1186 };
1187
1188 let transaction_fee = match &res {
1189 Ok(s) => s.transaction_fee,
1190 Err(e) => e.transaction_fee,
1191 };
1192 // Charge for execution.
1193 self.account_mut(invoker).expect("existence already checked").balance.total -=
1194 transaction_fee;
1195 res
1196 }
1197
1198 /// Invoke a contract by calling an entrypoint.
1199 ///
1200 /// Similar to [`Chain::contract_update`](Self::contract_update) except that
1201 /// all changes are discarded afterwards. Typically used for "view"
1202 /// functions.
1203 ///
1204 /// **Parameters:**
1205 /// - `invoker`: the account used as invoker. Since this isn't a
1206 /// transaction, it won't be charged.
1207 /// - `sender`: the sender. Can be either a contract address or an account
1208 /// address.
1209 /// - `energy_reserved`: the maximum energy that can be used in the update.
1210 /// - `payload`: The data detailing which contract and receive method to
1211 /// call etc.
1212 pub fn contract_invoke(
1213 &self,
1214 invoker: AccountAddress,
1215 sender: Address,
1216 energy_reserved: Energy,
1217 payload: UpdateContractPayload,
1218 ) -> Result<ContractInvokeSuccess, ContractInvokeError> {
1219 // Ensure the sender exists.
1220 if !self.address_exists(sender) {
1221 return Err(ContractInvokeError {
1222 energy_used: Energy::from(0),
1223 transaction_fee: Amount::zero(),
1224 trace_elements: Vec::new(),
1225 kind: ContractInvokeErrorKind::SenderDoesNotExist(sender),
1226 module_load_energy: 0.into(),
1227 });
1228 }
1229
1230 let Some(account_info) = self.accounts.get(&invoker.into()) else {
1231 return Err(ContractInvokeError {
1232 energy_used: Energy::from(0),
1233 transaction_fee: Amount::zero(),
1234 trace_elements: Vec::new(),
1235 kind: ContractInvokeErrorKind::InvokerDoesNotExist(
1236 AccountDoesNotExist {
1237 address: invoker,
1238 },
1239 ),
1240 module_load_energy: 0.into(),
1241 });
1242 };
1243
1244 let invoker_amount_reserved_for_nrg =
1245 self.parameters.calculate_energy_cost(energy_reserved);
1246
1247 if account_info.balance.available() < invoker_amount_reserved_for_nrg {
1248 let energy_used = Energy::from(0);
1249 return Err(ContractInvokeError {
1250 energy_used,
1251 transaction_fee: self.parameters.calculate_energy_cost(energy_used),
1252 trace_elements: Vec::new(),
1253 kind: ContractInvokeErrorKind::InsufficientFunds,
1254 module_load_energy: 0.into(),
1255 });
1256 }
1257
1258 let mut remaining_energy = energy_reserved;
1259
1260 let contract_address = payload.address;
1261
1262 let res = self.contract_invocation_worker(
1263 invoker,
1264 sender,
1265 energy_reserved,
1266 invoker_amount_reserved_for_nrg,
1267 payload,
1268 &mut remaining_energy,
1269 );
1270 match res {
1271 Ok((result, changeset, trace_elements, module_load_energy)) => {
1272 // Charge energy for contract storage. Or return an error if out
1273 // of energy.
1274 let (state_energy, state_changed) =
1275 if matches!(result, v1::InvokeResponse::Success { .. }) {
1276 let energy_before = remaining_energy;
1277 if let Ok(state_changed) = changeset
1278 .collect_energy_for_state(&mut remaining_energy, contract_address)
1279 {
1280 let state_energy = energy_before.checked_sub(remaining_energy).unwrap();
1281 (state_energy, state_changed)
1282 } else {
1283 // the error happens when storing the state, so there are no trace
1284 // elements associated with it. The trace is
1285 // already in the "debug trace" vector.
1286 return Err(self.invocation_out_of_energy_error(
1287 energy_reserved,
1288 DebugTracker::empty_trace(),
1289 module_load_energy,
1290 ));
1291 }
1292 } else {
1293 // An error occurred, so state hasn't changed.
1294 (0.into(), false)
1295 };
1296 self.contract_invocation_process_response(
1297 result,
1298 trace_elements,
1299 energy_reserved,
1300 remaining_energy,
1301 state_energy,
1302 state_changed,
1303 module_load_energy,
1304 )
1305 }
1306 Err(e) => Err(e),
1307 }
1308 }
1309
1310 /// Invoke an external contract entrypoint.
1311 ///
1312 /// Similar to [`Chain::contract_invoke`](Self::contract_invoke) except that
1313 /// it invokes/dry runs a contract on the external node.
1314 ///
1315 /// **Parameters:**
1316 /// - `invoker`: the account used as invoker.
1317 /// - The account must exist on the connected node.
1318 /// - `sender`: the sender, can also be a contract.
1319 /// - The sender must exist on the connected node.
1320 /// - `energy_reserved`: the maximum energy that can be used in the update.
1321 /// - `payload`: The data detailing which contract and receive method to
1322 /// call etc.
1323 /// - `block`: The block in which the invocation will be simulated, as if
1324 /// it was at the end of the block. If `None` is provided, the
1325 /// `external_query_block` is used instead.
1326 ///
1327 /// # Example:
1328 ///
1329 /// ```no_run
1330 /// # use concordium_smart_contract_testing::*;
1331 /// let mut chain = Chain::builder()
1332 /// .external_node_connection(Endpoint::from_static("http://node.testnet.concordium.com:20000"))
1333 /// .build()
1334 /// .unwrap();
1335 ///
1336 /// // Set up an external contract.
1337 /// let external_contract =
1338 /// chain.add_external_contract(ContractAddress::new(1010, 0)).unwrap();
1339 ///
1340 /// // Set up an external account.
1341 /// let external_acc =
1342 /// chain.add_external_account("
1343 /// 3U4sfVSqGG6XK8g6eho2qRYtnHc4MWJBG1dfxdtPGbfHwFxini".parse().unwrap()).
1344 /// unwrap();
1345 ///
1346 /// let res = chain.contract_invoke_external(
1347 /// Some(ExternalAddress::Account(external_acc)),
1348 /// 10000.into(),
1349 /// InvokeExternalContractPayload {
1350 /// amount: Amount::zero(),
1351 /// address: external_contract,
1352 /// receive_name:
1353 /// OwnedReceiveName::new_unchecked("my_contract.view".to_string()),
1354 /// message: OwnedParameter::empty(),
1355 /// },
1356 /// None,
1357 /// );
1358 /// ```
1359 pub fn contract_invoke_external(
1360 &self,
1361 sender: Option<ExternalAddress>,
1362 energy_reserved: Energy,
1363 payload: InvokeExternalContractPayload,
1364 block: Option<BlockHash>,
1365 ) -> Result<ContractInvokeExternalSuccess, ContractInvokeExternalError> {
1366 let connection = self.external_node_connection().unwrap();
1367
1368 // Make the invocation.
1369 let invoke_result: InvokeContractResult =
1370 connection.with_client(block, |block_identifier, mut client| async move {
1371 let invoke_result = client
1372 .invoke_instance(
1373 block_identifier,
1374 &sdk::types::smart_contracts::ContractContext {
1375 invoker: sender.map(|ext_addr| ext_addr.to_address()),
1376 contract: payload.address.address,
1377 amount: payload.amount,
1378 method: payload.receive_name,
1379 parameter: payload.message,
1380 energy: Some(energy_reserved),
1381 },
1382 )
1383 .await?
1384 .response;
1385 Ok::<_, ExternalNodeError>(invoke_result)
1386 })?;
1387
1388 // Convert the result.
1389 match invoke_result {
1390 InvokeContractResult::Success {
1391 return_value,
1392 events,
1393 used_energy,
1394 } => Ok(ContractInvokeExternalSuccess {
1395 trace_elements: events,
1396 energy_used: used_energy,
1397 return_value: return_value.map(|rv| rv.value).unwrap_or_default(),
1398 }),
1399 InvokeContractResult::Failure {
1400 return_value,
1401 reason,
1402 used_energy,
1403 } => Err(ContractInvokeExternalError::Failure {
1404 reason,
1405 energy_used: used_energy,
1406 return_value: return_value.map(|rv| rv.value).unwrap_or_default(),
1407 }),
1408 }
1409 }
1410
1411 /// Create an account.
1412 ///
1413 /// If an account with a matching address already exists this method will
1414 /// replace it and return the old account.
1415 ///
1416 /// Note that if the first 29-bytes of an account are identical, then
1417 /// they are *considered aliases* on each other in all methods.
1418 /// See the example below:
1419 ///
1420 /// ```
1421 /// # use concordium_smart_contract_testing::*;
1422 /// let mut chain = Chain::new();
1423 /// let acc = AccountAddress([
1424 /// 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1425 /// 0, 0,
1426 /// ]);
1427 /// let acc_alias = AccountAddress([
1428 /// 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
1429 /// 2, 3, // Only last three bytes differ.
1430 /// ]);
1431 ///
1432 /// chain.create_account(Account::new(acc, Amount::from_ccd(123)));
1433 /// assert_eq!(
1434 /// chain.account_balance_available(acc_alias), // Using the alias for lookup.
1435 /// Some(Amount::from_ccd(123))
1436 /// );
1437 /// ```
1438 pub fn create_account(&mut self, account: Account) -> Option<Account> {
1439 self.accounts.insert(account.address.into(), account)
1440 }
1441
1442 /// Add an external account from a connected external node.
1443 ///
1444 /// If the account exists on the external node at the time of the
1445 /// `external_query_block`, then an [`ExternalAccountAddress`] is returned.
1446 /// The address can be used with [`Chain::contract_invoke_external`].
1447 /// Otherwise, an error is returned.
1448 ///
1449 /// Barring external node errors, the method is idempotent, and so it can be
1450 /// called multiple times with the same effect.
1451 pub fn add_external_account(
1452 &mut self,
1453 address: AccountAddress,
1454 ) -> Result<ExternalAccountAddress, ExternalNodeError> {
1455 let connection = self.external_node_connection_mut()?;
1456
1457 let external_addr =
1458 connection.with_client(None, |block_identifier, mut client| async move {
1459 // Try to get the account info to verify the existence of the account, but
1460 // discard the result.
1461 client
1462 .get_account_info(
1463 &sdk::v2::AccountIdentifier::Address(address),
1464 block_identifier,
1465 )
1466 .await?;
1467 Ok::<_, ExternalNodeError>(ExternalAccountAddress {
1468 address,
1469 })
1470 })?;
1471
1472 connection.accounts.insert(external_addr);
1473
1474 Ok(external_addr)
1475 }
1476
1477 /// Add an external contract from a connected external node.
1478 ///
1479 /// If the contract exists on the external node at the time of the
1480 /// `external_query_block`, then an [`ExternalContractAddress`] is returned.
1481 /// The address can be used with [`Chain::contract_invoke_external`].
1482 /// Otherwise, an error is returned.
1483 ///
1484 /// Barring external node errors, the method is idempotent, and so it can be
1485 /// called multiple times with the same effect.
1486 pub fn add_external_contract(
1487 &mut self,
1488 address: ContractAddress,
1489 ) -> Result<ExternalContractAddress, ExternalNodeError> {
1490 let connection = self.external_node_connection_mut()?;
1491
1492 let external_addr =
1493 connection.with_client(None, |block_identifier, mut client| async move {
1494 // Try to get the contract instance info to verify the existence of the
1495 // contract, but discard the result.
1496 client.get_instance_info(address, block_identifier).await?;
1497 Ok::<_, ExternalNodeError>(ExternalContractAddress {
1498 address,
1499 })
1500 })?;
1501
1502 connection.contracts.insert(external_addr);
1503
1504 Ok(external_addr)
1505 }
1506
1507 /// Create a contract address by giving it the next available index.
1508 fn create_contract_address(&mut self) -> ContractAddress {
1509 let index = self.next_contract_index;
1510 let subindex = 0;
1511 self.next_contract_index += 1;
1512 ContractAddress::new(index, subindex)
1513 }
1514
1515 /// Returns the balance of an account if it exists.
1516 pub fn account_balance(&self, address: AccountAddress) -> Option<AccountBalance> {
1517 self.accounts.get(&address.into()).map(|ai| ai.balance)
1518 }
1519
1520 /// Returns the available balance of an account if it exists.
1521 pub fn account_balance_available(&self, address: AccountAddress) -> Option<Amount> {
1522 self.accounts.get(&address.into()).map(|ai| ai.balance.available())
1523 }
1524
1525 /// Returns the balance of an contract if it exists.
1526 pub fn contract_balance(&self, address: ContractAddress) -> Option<Amount> {
1527 self.contracts.get(&address).map(|ci| ci.self_balance)
1528 }
1529
1530 /// Helper method for looking up part of the state of a smart contract,
1531 /// which is a key-value store.
1532 pub fn contract_state_lookup(&self, address: ContractAddress, key: &[u8]) -> Option<Vec<u8>> {
1533 let mut loader = v1::trie::Loader::new(&[][..]);
1534 self.contracts.get(&address)?.state.lookup(&mut loader, key)
1535 }
1536
1537 /// Return a clone of the [`ContractModule`] (which has an `Arc` around the
1538 /// artifact so cloning is cheap).
1539 fn contract_module(
1540 &self,
1541 module_ref: ModuleReference,
1542 ) -> Result<ContractModule, ModuleDoesNotExist> {
1543 let module = self.modules.get(&module_ref).ok_or(ModuleDoesNotExist {
1544 module_reference: module_ref,
1545 })?;
1546 Ok(module.clone())
1547 }
1548
1549 /// Returns an immutable reference to an [`Account`].
1550 pub fn account(&self, address: AccountAddress) -> Result<&Account, AccountDoesNotExist> {
1551 self.accounts.get(&address.into()).ok_or(AccountDoesNotExist {
1552 address,
1553 })
1554 }
1555
1556 /// Returns a mutable reference to [`Account`].
1557 fn account_mut(
1558 &mut self,
1559 address: AccountAddress,
1560 ) -> Result<&mut Account, AccountDoesNotExist> {
1561 self.accounts.get_mut(&address.into()).ok_or(AccountDoesNotExist {
1562 address,
1563 })
1564 }
1565
1566 /// Check whether an [`Account`] exists.
1567 pub fn account_exists(&self, address: AccountAddress) -> bool {
1568 self.accounts.contains_key(&address.into())
1569 }
1570
1571 /// Check whether a [`Contract`] exists.
1572 pub fn contract_exists(&self, address: ContractAddress) -> bool {
1573 self.contracts.contains_key(&address)
1574 }
1575
1576 /// Check whether an object with the [`Address`] exists.
1577 ///
1578 /// That is, if it is an account address, whether the account exists,
1579 /// and if it is a contract address, whether the contract exists.
1580 fn address_exists(&self, address: Address) -> bool {
1581 match address {
1582 Address::Account(acc) => self.account_exists(acc),
1583 Address::Contract(contr) => self.contract_exists(contr),
1584 }
1585 }
1586
1587 /// Convert a [`ContractInvokeErrorKind`] to a
1588 /// [`ContractInvokeError`] by calculating the `energy_used` and
1589 /// `transaction_fee`.
1590 ///
1591 /// If the `kind` is an out of energy, then `0` is used instead of the
1592 /// `remaining_energy` parameter, as it will likely not be `0` due to short
1593 /// circuiting during execution.
1594 fn convert_to_invoke_error(
1595 &self,
1596 kind: ContractInvokeErrorKind,
1597 trace_elements: Vec<DebugTraceElement>,
1598 energy_reserved: Energy,
1599 remaining_energy: Energy,
1600 module_load_energy: Energy,
1601 ) -> ContractInvokeError {
1602 let remaining_energy = if matches!(kind, ContractInvokeErrorKind::OutOfEnergy { .. }) {
1603 0.into()
1604 } else {
1605 remaining_energy
1606 };
1607 let energy_used = energy_reserved - remaining_energy;
1608 let transaction_fee = self.parameters.calculate_energy_cost(energy_used);
1609 ContractInvokeError {
1610 energy_used,
1611 transaction_fee,
1612 trace_elements,
1613 kind,
1614 module_load_energy,
1615 }
1616 }
1617
1618 /// Construct a [`ContractInvokeErrorKind`] of the `OutOfEnergy` kind with
1619 /// the energy and transaction fee fields based on the `energy_reserved`
1620 /// parameter.
1621 fn invocation_out_of_energy_error(
1622 &self,
1623 energy_reserved: Energy,
1624 debug_trace: DebugTracker,
1625 module_load_energy: Energy,
1626 ) -> ContractInvokeError {
1627 self.convert_to_invoke_error(
1628 ContractInvokeErrorKind::OutOfEnergy {
1629 debug_trace,
1630 },
1631 Vec::new(),
1632 energy_reserved,
1633 Energy::from(0),
1634 module_load_energy,
1635 )
1636 }
1637
1638 /// Convert a [`ContractInitErrorKind`] to a
1639 /// [`ContractInitError`] by calculating the `energy_used` and
1640 /// `transaction_fee`.
1641 fn convert_to_init_error(
1642 &self,
1643 kind: ContractInitErrorKind,
1644 energy_reserved: Energy,
1645 remaining_energy: Energy,
1646 ) -> ContractInitError {
1647 let energy_used = energy_reserved - remaining_energy;
1648 let transaction_fee = self.parameters.calculate_energy_cost(energy_used);
1649 ContractInitError {
1650 energy_used,
1651 transaction_fee,
1652 kind,
1653 }
1654 }
1655
1656 /// Try to set the exchange rates on the chain.
1657 ///
1658 /// Will fail if they result in the cost of one energy being larger than
1659 /// `u64::MAX / 100_000_000_000`.
1660 pub fn set_exchange_rates(
1661 &mut self,
1662 micro_ccd_per_euro: ExchangeRate,
1663 euro_per_energy: ExchangeRate,
1664 ) -> Result<(), ExchangeRateError> {
1665 // Ensure the exchange rates are within a valid range.
1666 check_exchange_rates(euro_per_energy, micro_ccd_per_euro)?;
1667 self.parameters.micro_ccd_per_euro = micro_ccd_per_euro;
1668 self.parameters.euro_per_energy = euro_per_energy;
1669 Ok(())
1670 }
1671
1672 /// Get the microCCD per euro and euro per energy exchange rates by querying
1673 /// an external node using the external query block.
1674 fn get_exchange_rates_via_external_node(&self) -> Result<ExchangeRates, ExternalNodeError> {
1675 let connection = self.external_node_connection()?;
1676
1677 // Get the values from the external node.
1678 connection.with_client(None, |block_identifier, mut client| async move {
1679 let (euro_per_energy, micro_ccd_per_euro) =
1680 match client.get_block_chain_parameters(block_identifier).await?.response {
1681 sdk::v2::ChainParameters::V0(p) => (p.euro_per_energy, p.micro_ccd_per_euro),
1682 sdk::v2::ChainParameters::V1(p) => (p.euro_per_energy, p.micro_ccd_per_euro),
1683 sdk::v2::ChainParameters::V2(p) => (p.euro_per_energy, p.micro_ccd_per_euro),
1684 sdk::v2::ChainParameters::V3(p) => (p.euro_per_energy, p.micro_ccd_per_euro),
1685 };
1686 Ok(ExchangeRates {
1687 euro_per_energy,
1688 micro_ccd_per_euro,
1689 })
1690 })
1691 }
1692
1693 /// Tick the block time on the [`Chain`] by a [`Duration`].
1694 ///
1695 /// Returns an error if ticking causes the block time to overflow.
1696 ///
1697 /// # Example
1698 ///
1699 /// ```
1700 /// # use concordium_smart_contract_testing::*;
1701 ///
1702 /// // Block time defaults to 0.
1703 /// let mut chain = Chain::new();
1704 ///
1705 /// // Increase block time by 123 milliseconds.
1706 /// chain.tick_block_time(Duration::from_millis(123)).unwrap();
1707 ///
1708 /// // Block time has now increased.
1709 /// assert_eq!(chain.block_time(), Timestamp::from_timestamp_millis(123));
1710 /// ```
1711 pub fn tick_block_time(&mut self, duration: Duration) -> Result<(), BlockTimeOverflow> {
1712 self.parameters.block_time =
1713 self.parameters.block_time.checked_add(duration).ok_or(BlockTimeOverflow)?;
1714 Ok(())
1715 }
1716
1717 /// Set the block time by querying the external node.
1718 ///
1719 /// The default query block is always used.
1720 ///
1721 /// The external node must be setup prior to this call via the method
1722 /// [`Chain::setup_external_node_connection`], otherwise an error is
1723 /// returned.
1724 fn set_block_time_via_external_node(&mut self) -> Result<(), ExternalNodeError> {
1725 let connection = self.external_node_connection_mut()?;
1726
1727 // Get the timestamp in milliseconds.
1728 let timestamp =
1729 connection.with_client(None, |block_identifier, mut client| async move {
1730 Ok(client
1731 .get_block_info(block_identifier)
1732 .await?
1733 .response
1734 .block_slot_time
1735 .timestamp_millis() as u64) // The node never returns
1736 // timestamps < 0, so it is safe
1737 // to cast it to `u64`.
1738 })?;
1739 // Update the block time.
1740 self.parameters.block_time = Timestamp::from_timestamp_millis(timestamp);
1741
1742 Ok(())
1743 }
1744
1745 /// Set up a connection to an external Concordium node.
1746 ///
1747 /// This method also queries the block info for one of two reasons:
1748 /// 1) If `query_block` is provided, its existence is checked.
1749 /// 2) Otherwise, the last final block is queried to get its blockhash which
1750 /// will be saved in [`ExternalNodeConnection`].
1751 fn setup_external_node_connection(
1752 &mut self,
1753 endpoint: Endpoint,
1754 query_block: Option<BlockHash>,
1755 ) -> Result<(), SetupExternalNodeError> {
1756 // Create the Tokio runtime. This should never fail, unless nested runtimes are
1757 // created.
1758 let runtime = runtime::Builder::new_multi_thread()
1759 // Enable time, so timeouts can be used.
1760 .enable_time()
1761 // Enable I/O, so networking and other types of calls are possible.
1762 .enable_io()
1763 .build()
1764 .expect("Internal error: Could not create Tokio runtime.");
1765
1766 // A future for getting the client. Executed below.
1767 let get_client = async {
1768 // Try to create the client, which also checks that the connection is valid.
1769 let client = sdk::v2::Client::new(endpoint).await?;
1770 Ok::<sdk::v2::Client, SetupExternalNodeError>(client)
1771 };
1772
1773 // A future for checking the block.
1774 let get_block_info = |mut client: sdk::v2::Client| async move {
1775 let block_identifier = if let Some(query_block) = query_block {
1776 sdk::v2::BlockIdentifier::Given(query_block)
1777 } else {
1778 sdk::v2::BlockIdentifier::LastFinal
1779 };
1780
1781 let block_hash = match client.get_block_info(block_identifier).await {
1782 Ok(res) => res.block_hash,
1783 Err(sdk::v2::QueryError::NotFound) => {
1784 return Err(SetupExternalNodeError::QueryBlockDoesNotExist {
1785 // It should never be possible to get `NotFound` when querying `LastFinal`,
1786 // and so, the `query_block` must be `Some`.
1787 query_block: query_block.expect(
1788 "Internal error: Got `QueryError::NotFound` for when querying last \
1789 final block.",
1790 ),
1791 });
1792 }
1793 Err(sdk::v2::QueryError::RPCError(error)) => {
1794 return Err(SetupExternalNodeError::CannotCheckQueryBlockExistence {
1795 error,
1796 })
1797 }
1798 };
1799 Ok(block_hash)
1800 };
1801
1802 // Get the client synchronously by blocking until the async returns.
1803 let (client, checked_query_block) = runtime.block_on(async {
1804 let client = timeout(EXTERNAL_NODE_CONNECT_TIMEOUT, get_client)
1805 .await
1806 .map_err(|_| SetupExternalNodeError::ConnectTimeout)??;
1807 let checked_query_block =
1808 timeout(EXTERNAL_NODE_QUERY_TIMEOUT, get_block_info(client.clone()))
1809 .await
1810 .map_err(|_| SetupExternalNodeError::CheckQueryBlockTimeout)??;
1811 Ok::<_, SetupExternalNodeError>((client, checked_query_block))
1812 })?;
1813
1814 // Set or replace the node connection.
1815 self.external_node_connection = Some(ExternalNodeConnection {
1816 client,
1817 runtime,
1818 query_block: checked_query_block,
1819 accounts: BTreeSet::new(),
1820 contracts: BTreeSet::new(),
1821 });
1822
1823 Ok(())
1824 }
1825
1826 /// Try to get a mutable reference to [`ExternalNodeConnection`] or return
1827 /// an error.
1828 ///
1829 /// The connection is only available, if the [`Chain`] has been set up with
1830 /// an external node connection via
1831 /// [`ChainBuilder::external_node_connection`] in the [`ChainBuilder`].
1832 fn external_node_connection_mut(
1833 &mut self,
1834 ) -> Result<&mut ExternalNodeConnection, ExternalNodeNotConfigured> {
1835 match &mut self.external_node_connection {
1836 None => Err(ExternalNodeNotConfigured),
1837 Some(data) => Ok(data),
1838 }
1839 }
1840
1841 /// Try to get an immutable reference to [`ExternalNodeConnection`] or
1842 /// return an error.
1843 ///
1844 /// The connection is only available, if the [`Chain`] has been set up with
1845 /// an external node connection via
1846 /// [`ChainBuilder::external_node_connection`] in the [`ChainBuilder`].
1847 fn external_node_connection(
1848 &self,
1849 ) -> Result<&ExternalNodeConnection, ExternalNodeNotConfigured> {
1850 match &self.external_node_connection {
1851 None => Err(ExternalNodeNotConfigured),
1852 Some(data) => Ok(data),
1853 }
1854 }
1855
1856 /// Return the current microCCD per euro exchange rate.
1857 pub fn micro_ccd_per_euro(&self) -> ExchangeRate { self.parameters.micro_ccd_per_euro }
1858
1859 /// Return the current euro per energy exchange rate.
1860 pub fn euro_per_energy(&self) -> ExchangeRate { self.parameters.euro_per_energy }
1861
1862 /// Return the current block time.
1863 pub fn block_time(&self) -> Timestamp { self.parameters.block_time }
1864
1865 /// Return the block used for external queries by default.
1866 ///
1867 /// The block can be set with [`ChainBuilder::external_query_block`] when
1868 /// building the [`Chain`].
1869 ///
1870 /// This method returns an error if the external node has not been
1871 /// configured.
1872 pub fn external_query_block(&self) -> Result<BlockHash, ExternalNodeNotConfigured> {
1873 self.external_node_connection().map(|conn| conn.query_block)
1874 }
1875}
1876
1877impl ExternalNodeConnection {
1878 /// Execute an async task with the [`sdk::v2::Client`].
1879 ///
1880 /// If a block is provided, it will be used for the query. Otherwise, it
1881 /// will use the default query block.
1882 ///
1883 /// If the task takes longer than [`EXTERNAL_NODE_TIMEOUT_DURATION`] then
1884 /// the connection times out and an [`Err(ExternalNodeError::Timeout)`] is
1885 /// returned.
1886 ///
1887 /// *This method cannot be nested, as that will cause a panic.*
1888 fn with_client<T, F, Fut>(
1889 &self,
1890 block: Option<BlockHash>,
1891 f: F,
1892 ) -> Result<T, ExternalNodeError>
1893 where
1894 F: FnOnce(sdk::v2::BlockIdentifier, sdk::v2::Client) -> Fut,
1895 Fut: Future<Output = Result<T, ExternalNodeError>>, {
1896 // Get the block identifier, either using the provided block or the default
1897 // query block.
1898 let block_identifier = if let Some(block) = block {
1899 sdk::v2::BlockIdentifier::Given(block)
1900 } else {
1901 sdk::v2::BlockIdentifier::Given(self.query_block)
1902 };
1903 // Clone the client so it can be moved to the async block.
1904 let client = self.client.clone();
1905 // Run the future and timeout if it takes too long.
1906 self.runtime.block_on(async move {
1907 timeout(EXTERNAL_NODE_QUERY_TIMEOUT, f(block_identifier, client))
1908 .await
1909 .map_err(|_| ExternalNodeError::QueryTimeout)?
1910 })
1911 }
1912}
1913
1914impl Account {
1915 /// Create new [`Account`](Self) with the provided account policy and keys.
1916 pub fn new_with_policy_and_keys(
1917 address: AccountAddress,
1918 balance: AccountBalance,
1919 policy: OwnedPolicy,
1920 keys: AccountAccessStructure,
1921 ) -> Self {
1922 Self {
1923 balance,
1924 policy,
1925 address,
1926 keys,
1927 }
1928 }
1929
1930 /// Create new [`Account`](Self) with the provided account keys and balance.
1931 /// See [`new`][Self::new] for what the default policy is.
1932 pub fn new_with_keys(
1933 address: AccountAddress,
1934 balance: AccountBalance,
1935 keys: AccountAccessStructure,
1936 ) -> Self {
1937 Self {
1938 balance,
1939 policy: Self::empty_policy(),
1940 address,
1941 keys,
1942 }
1943 }
1944
1945 /// Create new [`Account`](Self) with the provided account policy.
1946 /// The account keys are initialized with an [`AccountAccessStructure`]
1947 /// with a threshold of 1, and no keys. So it is impossible to verify any
1948 /// signatures with the access structure.
1949 pub fn new_with_policy(
1950 address: AccountAddress,
1951 balance: AccountBalance,
1952 policy: OwnedPolicy,
1953 ) -> Self {
1954 Self::new_with_policy_and_keys(address, balance, policy, AccountAccessStructure {
1955 threshold: AccountThreshold::try_from(1u8).expect("1 is a valid threshold."),
1956 keys: BTreeMap::new(),
1957 })
1958 }
1959
1960 /// Create a new [`Account`](Self) with the provided balance and a default
1961 /// account policy and default account access structure.
1962 ///
1963 /// See [`new`][Self::new] for what the default policy is.
1964 pub fn new_with_balance(address: AccountAddress, balance: AccountBalance) -> Self {
1965 Self::new_with_policy(address, balance, Self::empty_policy())
1966 }
1967
1968 /// Create new [`Account`](Self) with the provided total balance.
1969 ///
1970 /// The `policy` will have:
1971 /// - `identity_provider`: 0,
1972 /// - `created_at`: unix epoch,
1973 /// - `valid_to`: unix epoch + `u64::MAX` milliseconds,
1974 /// - `items`: none,
1975 ///
1976 /// The account keys are initialized with an [`AccountAccessStructure`]
1977 /// with a threshold of 1, and no keys. So it is impossible to verify any
1978 /// signatures with the access structure.
1979 ///
1980 /// The [`AccountBalance`] will be created with the provided
1981 /// `total_balance`.
1982 pub fn new(address: AccountAddress, total_balance: Amount) -> Self {
1983 Self::new_with_policy(
1984 address,
1985 AccountBalance {
1986 total: total_balance,
1987 staked: Amount::zero(),
1988 locked: Amount::zero(),
1989 },
1990 Self::empty_policy(),
1991 )
1992 }
1993
1994 /// Helper for creating an empty policy.
1995 ///
1996 /// It has identity provider `0`, no items, and is valid from unix epoch
1997 /// until unix epoch + u64::MAX milliseconds.
1998 fn empty_policy() -> OwnedPolicy {
1999 OwnedPolicy {
2000 identity_provider: 0,
2001 created_at: Timestamp::from_timestamp_millis(0),
2002 valid_to: Timestamp::from_timestamp_millis(u64::MAX),
2003 items: Vec::new(),
2004 }
2005 }
2006}
2007
2008/// Load a raw wasm module, i.e. one **without** the prefix of 4 version
2009/// bytes and 4 module length bytes.
2010/// The module still has to be a valid V1 smart contract module.
2011pub fn module_load_v1_raw(module_path: impl AsRef<Path>) -> Result<WasmModule, ModuleLoadError> {
2012 let module_path = module_path.as_ref();
2013 // To avoid reading a giant file, we open the file for reading, check its size
2014 // and then load the contents.
2015 let (mut reader, metadata) = std::fs::File::open(module_path)
2016 .and_then(|reader| reader.metadata().map(|metadata| (reader, metadata)))
2017 .map_err(|e| ModuleLoadError {
2018 path: module_path.to_path_buf(),
2019 kind: e.into(),
2020 })?;
2021 if metadata.len() > MAX_WASM_MODULE_SIZE.into() {
2022 return Err(ModuleLoadError {
2023 path: module_path.to_path_buf(),
2024 kind: ModuleLoadErrorKind::ReadModule(
2025 anyhow!("Maximum size of a Wasm module is {}", MAX_WASM_MODULE_SIZE).into(),
2026 ),
2027 });
2028 }
2029 // We cannot deserialize directly to [`ModuleSource`] as it expects the first
2030 // four bytes to be the length, which it isn't for this raw file.
2031 let mut buffer = Vec::new();
2032 std::io::Read::read_to_end(&mut reader, &mut buffer).map_err(|e| ModuleLoadError {
2033 path: module_path.to_path_buf(),
2034 kind: ModuleLoadErrorKind::OpenFile(e), /* This is unlikely to happen, since
2035 * we already opened it. */
2036 })?;
2037 Ok(WasmModule {
2038 version: WasmVersion::V1,
2039 source: ModuleSource::from(buffer),
2040 })
2041}
2042
2043/// Load a v1 wasm module as it is output from `cargo concordium build`,
2044/// i.e. **including** the prefix of 4 version bytes and 4 module length
2045/// bytes.
2046pub fn module_load_v1(module_path: impl AsRef<Path>) -> Result<WasmModule, ModuleLoadError> {
2047 let module_path = module_path.as_ref();
2048 // To avoid reading a giant file, we just open the file for reading and then
2049 // parse it as a wasm module, which checks the length up front.
2050 let mut reader = std::fs::File::open(module_path).map_err(|e| ModuleLoadError {
2051 path: module_path.to_path_buf(),
2052 kind: e.into(),
2053 })?;
2054 let module: WasmModule =
2055 base::common::from_bytes(&mut reader).map_err(|e| ModuleLoadError {
2056 path: module_path.to_path_buf(),
2057 kind: ModuleLoadErrorKind::ReadModule(e.into()),
2058 })?;
2059 if module.version != WasmVersion::V1 {
2060 return Err(ModuleLoadError {
2061 path: module_path.to_path_buf(),
2062 kind: ModuleLoadErrorKind::UnsupportedModuleVersion(module.version),
2063 });
2064 }
2065 Ok(module)
2066}
2067
2068/// Load the current smart contract module output using the environment variable
2069/// `CARGO_CONCORDIUM_TEST_MODULE_OUTPUT_PATH` which is set when running using
2070/// `cargo concordium test`.
2071pub fn module_load_output() -> Result<WasmModule, OutputModuleLoadError> {
2072 let module_path = env::var(CONTRACT_MODULE_OUTPUT_PATH_ENV_VAR)?;
2073 let module = module_load_v1(module_path)?;
2074 Ok(module)
2075}
2076
2077impl Signer {
2078 /// Create a signer which always signs with one key.
2079 pub const fn with_one_key() -> Self {
2080 Self {
2081 num_keys: 1,
2082 }
2083 }
2084
2085 /// Create a signer with a non-zero number of keys.
2086 pub const fn with_keys(num_keys: u32) -> Result<Self, ZeroKeysError> {
2087 if num_keys == 0 {
2088 return Err(ZeroKeysError);
2089 }
2090 Ok(Self {
2091 num_keys,
2092 })
2093 }
2094}
2095
2096impl ContractInvokeError {
2097 /// Try to extract the value returned.
2098 ///
2099 /// This only returns `Some` if the contract rejected on its own.
2100 /// As opposed to when it runs out of energy, traps, or similar, in which
2101 /// case there won't be a return value.
2102 pub fn return_value(&self) -> Option<&[u8]> {
2103 match &self.kind {
2104 ContractInvokeErrorKind::ExecutionError {
2105 failure_kind:
2106 v1::InvokeFailure::ContractReject {
2107 data,
2108 ..
2109 },
2110 } => Some(data),
2111 _ => None,
2112 }
2113 }
2114
2115 /// If the contract execution rejected the transaction, this returns the
2116 /// code the contract used to signal the rejection.
2117 pub fn reject_code(&self) -> Option<i32> {
2118 match &self.kind {
2119 ContractInvokeErrorKind::ExecutionError {
2120 failure_kind:
2121 v1::InvokeFailure::ContractReject {
2122 code,
2123 ..
2124 },
2125 } => Some(*code),
2126 _ => None,
2127 }
2128 }
2129
2130 /// Try to extract and parse the value returned into a type that implements
2131 /// [`Deserial`].
2132 ///
2133 /// Returns an error if the return value:
2134 /// - isn't present
2135 /// - see [`Self::return_value`] for details about when this happens
2136 /// - is present
2137 /// - but could not be parsed into `T`
2138 /// - could parse into `T`, but there were leftover bytes
2139 pub fn parse_return_value<T: Deserial>(&self) -> ParseResult<T> {
2140 use contracts_common::{Cursor, Get, ParseError};
2141 let return_value = self.return_value().ok_or_else(ParseError::default)?;
2142 let mut cursor = Cursor::new(return_value);
2143 let res = cursor.get()?;
2144 // Check that all bytes have been read, as leftover bytes usually indicate
2145 // errors.
2146 if cursor.offset != return_value.len() {
2147 return Err(ParseError::default());
2148 }
2149 Ok(res)
2150 }
2151}
2152
2153impl From<TestConfigurationError> for ContractInvokeErrorKind {
2154 fn from(err: TestConfigurationError) -> Self {
2155 match err {
2156 TestConfigurationError::OutOfEnergy {
2157 debug_trace,
2158 } => Self::OutOfEnergy {
2159 debug_trace,
2160 },
2161 TestConfigurationError::BalanceOverflow => Self::BalanceOverflow,
2162 }
2163 }
2164}
2165
2166/// Convert [`Energy`] to [`InterpreterEnergy`] by multiplying by `1000`.
2167pub(crate) fn to_interpreter_energy(energy: Energy) -> u64 { energy.energy * 1000 }
2168
2169/// Convert [`InterpreterEnergy`] to [`Energy`] by dividing by `1000`.
2170pub(crate) fn from_interpreter_energy(interpreter_energy: &InterpreterEnergy) -> Energy {
2171 Energy::from(interpreter_energy.energy / 1000)
2172}
2173
2174/// Calculate the energy for looking up a [`ContractModule`].
2175pub(crate) fn lookup_module_cost(module: &ContractModule) -> Energy {
2176 // The ratio is from Concordium/Cost.hs::lookupModule
2177 Energy::from(module.size / 500)
2178}
2179
2180/// Calculate the microCCD(mCCD) cost of energy(NRG) using the two exchange
2181/// rates provided.
2182///
2183/// To find the mCCD/NRG exchange rate:
2184/// ```markdown
2185/// euro mCCD euro * mCCD mCCD
2186/// ---- * ---- = ----------- = ----
2187/// NRG euro NRG * euro NRG
2188/// ```
2189///
2190/// To convert the `energy` parameter to mCCD (the vertical lines represent
2191/// ceiling):
2192/// ```markdown
2193/// ⌈ mCCD ⌉ ⌈ NRG * mCCD ⌉
2194/// | NRG * ---- | = | ---------- | = mCCD
2195/// | NRG | | NRG |
2196/// ```
2197pub fn energy_to_amount(
2198 energy: Energy,
2199 euro_per_energy: ExchangeRate,
2200 micro_ccd_per_euro: ExchangeRate,
2201) -> Amount {
2202 let micro_ccd_per_energy_numerator: BigUint =
2203 BigUint::from(euro_per_energy.numerator()) * micro_ccd_per_euro.numerator();
2204 let micro_ccd_per_energy_denominator: BigUint =
2205 BigUint::from(euro_per_energy.denominator()) * micro_ccd_per_euro.denominator();
2206 let cost: BigUint = (micro_ccd_per_energy_numerator * energy.energy)
2207 .div_ceil(µ_ccd_per_energy_denominator);
2208 let cost: u64 = u64::try_from(cost).expect(
2209 "Should never overflow since reasonable exchange rates are ensured when constructing the \
2210 [`Chain`].",
2211 );
2212 Amount::from_micro_ccd(cost)
2213}
2214
2215/// Helper function that checks the validity of the exchange rates.
2216///
2217/// More specifically, it checks that the cost of one energy is <= `u64::MAX /
2218/// `100_000_000_000`, which ensures that overflows won't occur.
2219fn check_exchange_rates(
2220 euro_per_energy: ExchangeRate,
2221 micro_ccd_per_euro: ExchangeRate,
2222) -> Result<(), ExchangeRateError> {
2223 let micro_ccd_per_energy_numerator: BigUint =
2224 BigUint::from(euro_per_energy.numerator()) * micro_ccd_per_euro.numerator();
2225 let micro_ccd_per_energy_denominator: BigUint =
2226 BigUint::from(euro_per_energy.denominator()) * micro_ccd_per_euro.denominator();
2227 let max_allowed_micro_ccd_to_energy = u64::MAX / 100_000_000_000u64;
2228 let micro_ccd_per_energy =
2229 u64::try_from(micro_ccd_per_energy_numerator / micro_ccd_per_energy_denominator)
2230 .map_err(|_| ExchangeRateError)?;
2231 if micro_ccd_per_energy > max_allowed_micro_ccd_to_energy {
2232 return Err(ExchangeRateError);
2233 }
2234 Ok(())
2235}
2236
2237/// A helper function for converting `[v0::Logs]` into [`Vec<ContractEvent>`].
2238pub(crate) fn contract_events_from_logs(logs: v0::Logs) -> Vec<ContractEvent> {
2239 logs.logs.into_iter().map(ContractEvent::from).collect()
2240}
2241
2242impl From<ExternalNodeNotConfigured> for ExternalNodeError {
2243 fn from(_: ExternalNodeNotConfigured) -> Self { Self::NotConfigured }
2244}
2245
2246impl From<ExchangeRateError> for ChainBuilderError {
2247 fn from(_: ExchangeRateError) -> Self { Self::ExchangeRateError }
2248}
2249
2250#[cfg(test)]
2251mod tests {
2252 use concordium_rust_sdk::base::base::AccountAddressEq;
2253
2254 use super::*;
2255
2256 /// A few checks that test whether the function behavior matches its doc
2257 /// comments.
2258 #[test]
2259 fn check_exchange_rates_works() {
2260 let max_allowed_micro_ccd_per_energy = u64::MAX / 100_000_000_000;
2261 check_exchange_rates(
2262 ExchangeRate::new_unchecked(max_allowed_micro_ccd_per_energy + 1, 1),
2263 ExchangeRate::new_unchecked(1, 1),
2264 )
2265 .expect_err("should fail");
2266
2267 check_exchange_rates(
2268 ExchangeRate::new_unchecked(max_allowed_micro_ccd_per_energy / 2 + 1, 1),
2269 ExchangeRate::new_unchecked(2, 1),
2270 )
2271 .expect_err("should fail");
2272
2273 check_exchange_rates(
2274 ExchangeRate::new_unchecked(max_allowed_micro_ccd_per_energy, 1),
2275 ExchangeRate::new_unchecked(1, 1),
2276 )
2277 .expect("should succeed");
2278
2279 check_exchange_rates(
2280 ExchangeRate::new_unchecked(50000, 1),
2281 ExchangeRate::new_unchecked(1, 50000),
2282 )
2283 .expect("should succeed");
2284 }
2285
2286 /// Test that account aliases are seen as one account.
2287 #[test]
2288 fn test_account_aliases() {
2289 let mut chain = Chain::new();
2290 let acc = AccountAddress([
2291 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
2292 0, 0, 0,
2293 ]);
2294 let acc_alias = AccountAddress([
2295 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
2296 1, 2, 3, // Last three bytes can differ for aliases.
2297 ]);
2298 let acc_other = AccountAddress([
2299 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
2300 2, 3, 4, // This differs on last four bytes, so it is a different account.
2301 ]);
2302 let acc_eq: AccountAddressEq = acc.into();
2303 let acc_alias_eq: AccountAddressEq = acc_alias.into();
2304 let acc_other_eq: AccountAddressEq = acc_other.into();
2305
2306 let expected_amount = Amount::from_ccd(10);
2307 let expected_amount_other = Amount::from_ccd(123);
2308
2309 chain.create_account(Account::new(acc, expected_amount));
2310 chain.create_account(Account::new(acc_other, expected_amount_other));
2311
2312 assert_eq!(acc_eq, acc_alias_eq);
2313 assert_ne!(acc_eq, acc_other_eq);
2314
2315 assert_eq!(acc_eq.cmp(&acc_alias_eq), std::cmp::Ordering::Equal);
2316 assert_eq!(acc_eq.cmp(&acc_other_eq), std::cmp::Ordering::Less);
2317
2318 assert_eq!(chain.account_balance_available(acc_alias), Some(expected_amount));
2319 assert_eq!(chain.account_balance_available(acc_other), Some(expected_amount_other));
2320 }
2321
2322 /// Test that building a chain with valid parameters succeeds.
2323 ///
2324 /// This test does *not* include external node endpoint, see
2325 /// [`test_chain_builder_with_valid_parameters_and_with_io`] for the reason.
2326 #[test]
2327 fn test_chain_builder_with_valid_parameters() {
2328 let micro_ccd_per_euro = ExchangeRate::new_unchecked(123, 1);
2329 let euro_per_energy = ExchangeRate::new_unchecked(1, 1234);
2330 let block_time = Timestamp::from_timestamp_millis(12345);
2331 let chain = Chain::builder()
2332 .micro_ccd_per_euro(micro_ccd_per_euro)
2333 .euro_per_energy(euro_per_energy)
2334 .block_time(block_time)
2335 .build()
2336 .unwrap();
2337
2338 assert_eq!(chain.micro_ccd_per_euro(), micro_ccd_per_euro);
2339 assert_eq!(chain.euro_per_energy(), euro_per_energy);
2340 assert_eq!(chain.block_time(), block_time);
2341 }
2342
2343 /// Test that building a chain with exchange rates that are out of bounds
2344 /// fails with the right error.
2345 #[test]
2346 fn test_chain_builder_with_invalid_exchange_rates() {
2347 let micro_ccd_per_euro = ExchangeRate::new_unchecked(1000000, 1);
2348 let euro_per_energy = ExchangeRate::new_unchecked(100000000, 1);
2349 let error = Chain::builder()
2350 .micro_ccd_per_euro(micro_ccd_per_euro)
2351 .euro_per_energy(euro_per_energy)
2352 .build()
2353 .unwrap_err();
2354
2355 assert!(matches!(error, ChainBuilderError::ExchangeRateError));
2356 }
2357}
2358
2359/// Return whether execution is running under `cargo concordium test` with
2360/// debugging enabled.
2361pub fn is_debug_enabled() -> bool {
2362 let Some(value) = option_env!("CARGO_CONCORDIUM_TEST_ALLOW_DEBUG") else {
2363 return false;
2364 };
2365 value != "0" && value != "false"
2366}
2367
2368/// Tests that use I/O (network) and should therefore *not* be run in the CI.
2369///
2370/// To skip the tests use `cargo test -- --skip io_tests`
2371#[cfg(test)]
2372mod io_tests {
2373 use super::*;
2374 use crate::*;
2375
2376 /// Test that building a chain using the external node parameters works.
2377 #[test]
2378 fn test_chain_builder_with_valid_parameters_and_external_node() {
2379 let chain = Chain::builder()
2380 .micro_ccd_per_euro_from_external()
2381 .euro_per_energy_from_external()
2382 .external_query_block(
2383 "45c53a19cd782a8de981941feb5e0f875cefaba8d2cda958e76f471a4710a797" // A block from testnet.
2384 .parse()
2385 .unwrap(),
2386 )
2387 .external_node_connection(Endpoint::from_static(
2388 "http://node.testnet.concordium.com:20000",
2389 ))
2390 .block_time_from_external()
2391 .build()
2392 .unwrap();
2393
2394 // These values are queried manually from the node.
2395 assert_eq!(
2396 chain.micro_ccd_per_euro(),
2397 ExchangeRate::new_unchecked(10338559485590134784, 79218205097)
2398 );
2399 assert_eq!(chain.euro_per_energy(), ExchangeRate::new_unchecked(1, 50000));
2400 assert_eq!(chain.block_time(), Timestamp::from_timestamp_millis(1687865059500));
2401 }
2402
2403 /// Test that the correct error is returned when an unknown query block is
2404 /// given.
2405 ///
2406 /// The block used is one from mainnet, which is extremely unlikely to also
2407 /// appear on testnet.
2408 #[test]
2409 fn test_block_time_from_unknown_block() {
2410 let err =
2411 Chain::builder()
2412 .external_node_connection(Endpoint::from_static(
2413 "http://node.testnet.concordium.com:20000",
2414 ))
2415 .external_query_block(
2416 "4f38c7e63645c59e9bf32f7ca837a029810b21c439f7492c3cebe229a2e3ea07"
2417 .parse()
2418 .unwrap(), // A block from mainnet.
2419 )
2420 .build()
2421 .unwrap_err();
2422 assert!(matches!(err, ChainBuilderError::SetupExternalNodeError {
2423 error: SetupExternalNodeError::CannotCheckQueryBlockExistence { .. },
2424 }));
2425 }
2426
2427 /// Invoke an external contract and check that it succeeds. Also check that
2428 /// the energy is correct.
2429 #[test]
2430 fn test_contract_invoke_external() {
2431 let mut chain = Chain::builder()
2432 .external_node_connection(Endpoint::from_static(
2433 "http://node.testnet.concordium.com:20000",
2434 ))
2435 .build()
2436 .unwrap();
2437
2438 // A CIS-2 contract.
2439 let external_contr = chain.add_external_contract(ContractAddress::new(5089, 0)).unwrap();
2440
2441 let external_acc = chain
2442 .add_external_account(
2443 "3U4sfVSqGG6XK8g6eho2qRYtnHc4MWJBG1dfxdtPGbfHwFxini".parse().unwrap(),
2444 )
2445 .unwrap();
2446
2447 let res = chain
2448 .contract_invoke_external(
2449 Some(external_acc.into()),
2450 10000.into(),
2451 InvokeExternalContractPayload {
2452 amount: Amount::zero(),
2453 address: external_contr,
2454 receive_name: OwnedReceiveName::new_unchecked("cis2_multi.view".into()),
2455 message: OwnedParameter::empty(),
2456 },
2457 None,
2458 )
2459 .unwrap();
2460 assert_eq!(res.energy_used, 1851.into());
2461 }
2462}