// This file is part of Gear. // // Copyright (C) 2023-2024 Gear Technologies Inc. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, see . //! Requires node to be built in release mode use gear_core::{ ids::{prelude::*, CodeId, ProgramId}, message::ReplyInfo, }; use gear_core_errors::{ReplyCode, SuccessReplyReason}; use gsdk::{Api, Error, Result}; use jsonrpsee::types::error::{CallError, ErrorObject}; use parity_scale_codec::Encode; use std::{borrow::Cow, process::Command, str::FromStr, time::Instant}; use subxt::{error::RpcError, utils::H256, Error as SubxtError}; use utils::{alice_account_id, dev_node}; mod utils; #[tokio::test] async fn pallet_errors_formatting() -> Result<()> { let node = dev_node(); let api = Api::new(node.ws().as_str()).await?; let err = api .calculate_upload_gas( [0u8; 32].into(), /* invalid code */ vec![], vec![], 0, true, None, ) .await .expect_err("Must return error"); let expected_err = Error::Subxt(SubxtError::Rpc(RpcError::ClientError(Box::new( CallError::Custom(ErrorObject::owned( 8000, "Runtime error", Some("Extrinsic `gear.upload_program` failed: 'ProgramConstructionFailed'"), )), )))); assert_eq!(format!("{err}"), format!("{expected_err}")); Ok(()) } #[tokio::test] async fn test_calculate_upload_gas() -> Result<()> { let node = dev_node(); let api = Api::new(node.ws().as_str()).await?; let alice: [u8; 32] = *alice_account_id().as_ref(); api.calculate_upload_gas( alice.into(), demo_messenger::WASM_BINARY.to_vec(), vec![], 0, true, None, ) .await?; Ok(()) } #[tokio::test] async fn test_calculate_create_gas() -> Result<()> { let node = dev_node(); // 1. upload code. let signer = Api::new(node.ws().as_str()) .await? .signer("//Alice", None)?; signer .calls .upload_code(demo_messenger::WASM_BINARY.to_vec()) .await?; // 2. calculate create gas and create program. let code_id = CodeId::generate(demo_messenger::WASM_BINARY); let gas_info = signer .rpc .calculate_create_gas(None, code_id, vec![], 0, true, None) .await?; signer .calls .create_program(code_id, vec![], vec![], gas_info.min_limit, 0) .await?; Ok(()) } #[tokio::test] async fn test_calculate_handle_gas() -> Result<()> { let node = dev_node(); let salt = vec![]; let pid = ProgramId::generate_from_user(CodeId::generate(demo_messenger::WASM_BINARY), &salt); // 1. upload program. let signer = Api::new(node.ws().as_str()) .await? .signer("//Alice", None)?; signer .calls .upload_program( demo_messenger::WASM_BINARY.to_vec(), salt, vec![], 100_000_000_000, 0, ) .await?; assert!( signer.api().gprog(pid).await.is_ok(), "Program not exists on chain." ); // 2. calculate handle gas and send message. let gas_info = signer .rpc .calculate_handle_gas(None, pid, vec![], 0, true, None) .await?; signer .calls .send_message(pid, vec![], gas_info.min_limit, 0) .await?; Ok(()) } #[tokio::test] async fn test_calculate_reply_gas() -> Result<()> { let node = dev_node(); let alice: [u8; 32] = *alice_account_id().as_ref(); let salt = vec![]; let pid = ProgramId::generate_from_user(CodeId::generate(demo_waiter::WASM_BINARY), &salt); let payload = demo_waiter::Command::SendUpTo(alice, 10); // 1. upload program. let signer = Api::new(node.ws().as_str()) .await? .signer("//Alice", None)?; signer .calls .upload_program( demo_waiter::WASM_BINARY.to_vec(), salt, vec![], 100_000_000_000, 0, ) .await?; assert!( signer.api().gprog(pid).await.is_ok(), "Program not exists on chain" ); // 2. send wait message. signer .calls .send_message(pid, payload.encode(), 100_000_000_000, 0) .await?; let mailbox = signer .api() .mailbox(Some(alice_account_id().clone()), 10) .await?; assert_eq!(mailbox.len(), 1); let message_id = mailbox[0].0.id.into(); // 3. calculate reply gas and send reply. let gas_info = signer .rpc .calculate_reply_gas(None, message_id, vec![], 0, true, None) .await?; signer .calls .send_reply(message_id, vec![], gas_info.min_limit, 0) .await?; Ok(()) } #[tokio::test] async fn test_runtime_wasm_blob_version() -> Result<()> { let git_commit_hash = || -> Cow { // This code is taken from // https://github.com/paritytech/substrate/blob/ae1a608c91a5da441a0ee7c26a4d5d410713580d/utils/build-script-utils/src/version.rs#L21 let commit = if let Ok(hash) = std::env::var("SUBSTRATE_CLI_GIT_COMMIT_HASH") { Cow::from(hash.trim().to_owned()) } else { // We deliberately set the length here to `11` to ensure that // the emitted hash is always of the same length; otherwise // it can (and will!) vary between different build environments. match Command::new("git") .args(["rev-parse", "--short=11", "HEAD"]) .output() { Ok(o) if o.status.success() => { let sha = String::from_utf8_lossy(&o.stdout).trim().to_owned(); Cow::from(sha) } Ok(o) => { println!("cargo:warning=Git command failed with status: {}", o.status); Cow::from("unknown") } Err(err) => { println!("cargo:warning=Failed to execute git command: {}", err); Cow::from("unknown") } } }; commit }; // This test relies on the fact the node has been built from the same commit hash // as the test has been. let git_commit_hash = git_commit_hash(); assert_ne!(git_commit_hash, "unknown"); let node = dev_node(); let api = Api::new(node.ws().as_str()).await?; let mut finalized_blocks = api.subscribe_finalized_blocks().await?; let wasm_blob_version_1 = api.runtime_wasm_blob_version(None).await?; assert!( wasm_blob_version_1.ends_with(git_commit_hash.as_ref()), "The WASM blob version {} does not end with the git commit hash {}", wasm_blob_version_1, git_commit_hash ); let block_hash_1 = finalized_blocks.next_events().await?.unwrap().block_hash(); let wasm_blob_version_2 = api.runtime_wasm_blob_version(Some(block_hash_1)).await?; assert_eq!(wasm_blob_version_1, wasm_blob_version_2); let block_hash_2 = finalized_blocks.next_events().await?.unwrap().block_hash(); let wasm_blob_version_3 = api.runtime_wasm_blob_version(Some(block_hash_2)).await?; assert_ne!(block_hash_1, block_hash_2); assert_eq!(wasm_blob_version_2, wasm_blob_version_3); Ok(()) } #[tokio::test] async fn test_runtime_wasm_blob_version_history() -> Result<()> { let api = Api::new("wss://archive-rpc.vara.network:443").await?; { let no_method_block_hash = sp_core::H256::from_str( "0xa84349fc30b8f2d02cc31d49fe8d4a45b6de5a3ac1f1ad975b8920b0628dd6b9", ) .unwrap(); let wasm_blob_version_result = api .runtime_wasm_blob_version(Some(no_method_block_hash)) .await; let err = CallError::Custom(ErrorObject::owned( 9000, "Unable to find WASM blob version in WASM blob", None::, )); assert!( matches!( &wasm_blob_version_result, Err(Error::Subxt(SubxtError::Rpc(RpcError::ClientError(e)))) if e.to_string() == err.to_string() ), "Error does not match: {wasm_blob_version_result:?}" ); } Ok(()) } #[tokio::test] async fn test_original_code_storage() -> Result<()> { let node = dev_node(); let salt = vec![]; let pid = ProgramId::generate_from_user(CodeId::generate(demo_messenger::WASM_BINARY), &salt); let signer = Api::new(node.ws().as_str()) .await? .signer("//Alice", None)?; signer .calls .upload_program( demo_messenger::WASM_BINARY.to_vec(), salt, vec![], 100_000_000_000, 0, ) .await?; let program = signer.api().gprog(pid).await?; let rpc = signer.api().backend(); let block_hash = rpc.latest_finalized_block_ref().await?.hash(); let code = signer .api() .original_code_storage_at(program.code_hash.0.into(), Some(block_hash)) .await?; assert_eq!( code, demo_messenger::WASM_BINARY.to_vec(), "Program code mismatched" ); Ok(()) } // The test demonstrates how to query some storage at a lower level. #[ignore] #[tokio::test] async fn test_program_counters() -> Result<()> { // let uri = String::from("wss://rpc.vara.network:443"); // let uri = String::from("wss://archive-rpc.vara.network:443"); let uri = String::from("wss://testnet.vara.network:443"); // https://polkadot.js.org/apps/?rpc=wss://archive-rpc.vara.network#/explorer/query/9642000 // let block_hash = H256::from_slice(&hex::decode("533ab8551fc1ecc812cfa4fa91d8667bfb3bdbcf64eacc5fccdbbf9b20e539a3")?); let instant = Instant::now(); let (block_hash, block_number, count_program, count_active_program, count_memory_page) = query_program_counters(&uri, None).await?; println!("elapsed = {:?}", instant.elapsed()); println!("testnet block_hash = {block_hash}, block_number = {block_number}, count_program = {count_program}, count_active_program = {count_active_program}, count_memory_page = {count_memory_page}"); Ok(()) } #[tokio::test] async fn test_calculate_reply_for_handle() -> Result<()> { let node = dev_node(); let salt = vec![]; let pid = ProgramId::generate_from_user(CodeId::generate(demo_new_meta::WASM_BINARY), &salt); // 1. upload program. let signer = Api::new(node.ws().as_str()) .await? .signer("//Alice", None)?; signer .calls .upload_program( demo_new_meta::WASM_BINARY.to_vec(), salt, vec![], 100_000_000_000, 0, ) .await?; assert!( signer.api().gprog(pid).await.is_ok(), "Program not exists on chain." ); let message_in = demo_new_meta::MessageIn { id: demo_new_meta::Id { decimal: 1, hex: [1].to_vec(), }, }; let message_out = demo_new_meta::MessageOut { res: demo_new_meta::Wallet::test_sequence() .iter() .find(|w| w.id.decimal == message_in.id.decimal) .cloned(), }; // 2. calculate reply for handle let reply_info = signer .rpc .calculate_reply_for_handle(None, pid, message_in.encode(), 100_000_000_000, 0, None) .await?; // 3. assert assert_eq!( reply_info, ReplyInfo { payload: message_out.encode(), value: 0, code: ReplyCode::Success(SuccessReplyReason::Manual) } ); Ok(()) } #[tokio::test] async fn test_calculate_reply_for_handle_does_not_change_state() -> Result<()> { let node = dev_node(); let salt = vec![]; let pid = ProgramId::generate_from_user(CodeId::generate(demo_vec::WASM_BINARY), &salt); // 1. upload program. let signer = Api::new(node.ws().as_str()) .await? .signer("//Alice", None)?; signer .calls .upload_program( demo_vec::WASM_BINARY.to_vec(), salt, vec![], 100_000_000_000, 0, ) .await?; assert!( signer.api().gprog(pid).await.is_ok(), "Program not exists on chain." ); // 2. read initial state let pid_h256 = H256::from_slice(pid.as_ref()); let initial_state = signer.api().read_state(pid_h256, vec![], None).await?; // 3. calculate reply for handle let reply_info = signer .rpc .calculate_reply_for_handle(None, pid, 42i32.encode(), 100_000_000_000, 0, None) .await?; // 4. assert that calculated result correct assert_eq!( reply_info, ReplyInfo { payload: 42i32.encode(), value: 0, code: ReplyCode::Success(SuccessReplyReason::Manual) } ); // 5. read state after calculate let calculated_state = signer.api().read_state(pid_h256, vec![], None).await?; // 6. assert that state hasn't changed assert_eq!(initial_state, calculated_state); // 7. make call signer .calls .send_message(pid, 42i32.encode(), 100_000_000_000, 0) .await?; // 8. read state after call let updated_state = signer.api().read_state(pid_h256, vec![], None).await?; // 9. assert that state has changed assert_ne!(initial_state, updated_state); Ok(()) } async fn query_program_counters( uri: &str, block_hash: Option, ) -> Result<(H256, u32, u64, u64, u64)> { use gsdk::{ metadata::{runtime_types::gear_core::program::Program, storage::GearProgramStorage}, BlockNumber, }; use parity_scale_codec::Decode; use subxt::dynamic::Value; let signer = Api::new(uri).await?.signer("//Alice", None)?; let client_block = signer.api().blocks(); let (block_hash, block_number) = match block_hash { Some(hash) => { let block = client_block.at(hash).await?; assert_eq!(hash, block.hash(), "block hash mismatched"); (hash, block.number()) } None => { let latest_block = client_block.at_latest().await?; (latest_block.hash(), latest_block.number()) } }; let storage = signer.api().get_storage(Some(block_hash)).await?; let addr = Api::storage(GearProgramStorage::ProgramStorage, Vec::::new()); let mut iter = storage.iter(addr).await?; let mut count_memory_page = 0u64; let mut count_program = 0u64; let mut count_active_program = 0u64; while let Some(pair) = iter.next().await { let pair = pair?; let (key, value) = (pair.key_bytes, pair.value); let program = Program::::decode(&mut value.encoded())?; count_program += 1; let program_id = ProgramId::decode(&mut key.as_ref())?; if let Program::Active(_) = program { count_active_program += 1; count_memory_page += signer.api().gpages(program_id, None).await?.len() as u64; } } Ok(( block_hash, block_number, count_program, count_active_program, count_memory_page, )) }