diff --git a/README.md b/README.md index 67ab5e39..44574e11 100644 --- a/README.md +++ b/README.md @@ -197,6 +197,7 @@ Some of the options available are: - You also can specify the url of your node with `--url ws://your-endpoint`, by default it is using `ws://localhost:9944`. +- To perform a dry-run via RPC to estimate the gas usage without submitting a transaction use the `--dry-run` flag. For more information about the options, check [cargo-contract documentation](https://github.com/paritytech/cargo-contract/blob/master/crates/extrinsics/README.md#instantiate) diff --git a/crates/pop-cli/src/commands/call/contract.rs b/crates/pop-cli/src/commands/call/contract.rs index 7caeb66a..94df4f03 100644 --- a/crates/pop-cli/src/commands/call/contract.rs +++ b/crates/pop-cli/src/commands/call/contract.rs @@ -50,6 +50,9 @@ pub struct CallContractCommand { /// Submit an extrinsic for on-chain execution. #[clap(short('x'), long)] execute: bool, + /// Perform a dry-run via RPC to estimate the gas usage. This does not submit a transaction. + #[clap(long, conflicts_with = "execute")] + dry_run: bool, } impl CallContractCommand { @@ -73,6 +76,22 @@ impl CallContractCommand { }) .await?; + if self.dry_run { + let spinner = cliclack::spinner(); + spinner.start("Doing a dry run to estimate the gas..."); + match dry_run_gas_estimate_call(&call_exec).await { + Ok(w) => { + log::info(format!("Gas limit: {:?}", w))?; + log::warning("Your call has not been executed.")?; + }, + Err(e) => { + spinner.error(format!("{e}")); + outro_cancel("Call failed.")?; + }, + }; + return Ok(()); + } + if !self.execute { let spinner = cliclack::spinner(); spinner.start("Calling the contract..."); @@ -93,12 +112,12 @@ impl CallContractCommand { spinner.start("Doing a dry run to estimate the gas..."); weight_limit = match dry_run_gas_estimate_call(&call_exec).await { Ok(w) => { - log::info(format!("Gas limit {:?}", w))?; + log::info(format!("Gas limit: {:?}", w))?; w }, Err(e) => { spinner.error(format!("{e}")); - outro_cancel("Deployment failed.")?; + outro_cancel("Call failed.")?; return Ok(()); }, }; diff --git a/crates/pop-cli/src/commands/up/contract.rs b/crates/pop-cli/src/commands/up/contract.rs index c74a2684..ea77a3dc 100644 --- a/crates/pop-cli/src/commands/up/contract.rs +++ b/crates/pop-cli/src/commands/up/contract.rs @@ -49,6 +49,9 @@ pub struct UpContractCommand { /// - with a password "//Alice///SECRET_PASSWORD" #[clap(name = "suri", long, short, default_value = "//Alice")] suri: String, + /// Perform a dry-run via RPC to estimate the gas usage. This does not submit a transaction. + #[clap(long)] + dry_run: bool, /// Before start a local node, do not ask the user for confirmation. #[clap(short('y'), long)] skip_confirm: bool, @@ -92,8 +95,6 @@ impl UpContractCommand { // if build exists then proceed intro(format!("{}: Deploy a smart contract", style(" Pop CLI ").black().on_magenta()))?; - println!("{}: Deploying a smart contract", style(" Pop CLI ").black().on_magenta()); - let instantiate_exec = set_up_deployment(UpOpts { path: self.path.clone(), constructor: self.constructor.clone(), @@ -115,7 +116,7 @@ impl UpContractCommand { spinner.start("Doing a dry run to estimate the gas..."); weight_limit = match dry_run_gas_estimate_instantiate(&instantiate_exec).await { Ok(w) => { - log::info(format!("Gas limit {:?}", w))?; + log::info(format!("Gas limit: {:?}", w))?; w }, Err(e) => { @@ -125,16 +126,18 @@ impl UpContractCommand { }, }; } - let spinner = cliclack::spinner(); - spinner.start("Uploading and instantiating the contract..."); - let contract_address = instantiate_smart_contract(instantiate_exec, weight_limit) - .await - .map_err(|err| anyhow!("{} {}", "ERROR:", format!("{err:?}")))?; - spinner.stop(format!( - "Contract deployed and instantiated: The Contract Address is {:?}", - contract_address - )); - outro("Deployment complete")?; + if !self.dry_run { + let spinner = cliclack::spinner(); + spinner.start("Uploading and instantiating the contract..."); + let contract_address = instantiate_smart_contract(instantiate_exec, weight_limit) + .await + .map_err(|err| anyhow!("{} {}", "ERROR:", format!("{err:?}")))?; + spinner.stop(format!( + "Contract deployed and instantiated: The Contract Address is {:?}", + contract_address + )); + outro("Deployment complete")?; + } Ok(()) } } diff --git a/crates/pop-contracts/src/call.rs b/crates/pop-contracts/src/call.rs index bbc3d9fd..f39c4c19 100644 --- a/crates/pop-contracts/src/call.rs +++ b/crates/pop-contracts/src/call.rs @@ -1,5 +1,12 @@ // SPDX-License-Identifier: GPL-3.0 +use crate::{ + errors::Error, + utils::{ + helpers::{get_manifest_path, parse_account, parse_balance}, + signer::create_signer, + }, +}; use anyhow::Context; use contract_build::Verbosity; use contract_extrinsics::{ @@ -13,11 +20,6 @@ use subxt::{Config, PolkadotConfig as DefaultConfig}; use subxt_signer::sr25519::Keypair; use url::Url; -use crate::utils::{ - helpers::{get_manifest_path, parse_account, parse_balance}, - signer::create_signer, -}; - /// Attributes for the `call` command. pub struct CallOpts { /// Path to the contract build folder. @@ -84,28 +86,22 @@ pub async fn set_up_call( /// pub async fn dry_run_call( call_exec: &CallExec, -) -> anyhow::Result { +) -> Result { let call_result = call_exec.call_dry_run().await?; match call_result.result { - Ok(ref ret_val) => { - let value = call_exec + Ok(ref ret_val) => { + let value = call_exec .transcoder() - .decode_message_return( - call_exec.message(), - &mut &ret_val.data[..], - ) - .context(format!( - "Failed to decode return value {:?}", - &ret_val - ))?; + .decode_message_return(call_exec.message(), &mut &ret_val.data[..]) + .context(format!("Failed to decode return value {:?}", &ret_val))?; Ok(value.to_string()) - } - Err(ref _err) => { - Err(anyhow::anyhow!( - "Pre-submission dry-run failed. Add gas_limit and proof_size manually to skip this step." - )) - } - } + }, + Err(ref err) => { + let error_variant = + ErrorVariant::from_dispatch_error(err, &call_exec.client().metadata())?; + Err(Error::DryRunCallContractError(format!("{error_variant}"))) + }, + } } /// Estimate the gas required for a contract call without modifying the state of the blockchain. @@ -116,25 +112,23 @@ pub async fn dry_run_call( /// pub async fn dry_run_gas_estimate_call( call_exec: &CallExec, -) -> anyhow::Result { +) -> Result { let call_result = call_exec.call_dry_run().await?; match call_result.result { - Ok(_) => { - // use user specified values where provided, otherwise use the estimates - let ref_time = call_exec - .gas_limit() - .unwrap_or_else(|| call_result.gas_required.ref_time()); - let proof_size = call_exec - .proof_size() - .unwrap_or_else(|| call_result.gas_required.proof_size()); - Ok(Weight::from_parts(ref_time, proof_size)) - } - Err(ref _err) => { - Err(anyhow::anyhow!( - "Pre-submission dry-run failed. Add gas_limit and proof_size manually to skip this step." - )) - } - } + Ok(_) => { + // Use user specified values where provided, otherwise use the estimates. + let ref_time = + call_exec.gas_limit().unwrap_or_else(|| call_result.gas_required.ref_time()); + let proof_size = + call_exec.proof_size().unwrap_or_else(|| call_result.gas_required.proof_size()); + Ok(Weight::from_parts(ref_time, proof_size)) + }, + Err(ref err) => { + let error_variant = + ErrorVariant::from_dispatch_error(err, &call_exec.client().metadata())?; + Err(Error::DryRunCallContractError(format!("{error_variant}"))) + }, + } } /// Call a smart contract on the blockchain. @@ -161,36 +155,42 @@ pub async fn call_smart_contract( Ok(output) } -#[cfg(feature = "unit_contract")] #[cfg(test)] mod tests { use super::*; - use crate::{build_smart_contract, create_smart_contract}; - use anyhow::{Error, Result}; - use std::fs; - use tempfile::TempDir; + use crate::{create_smart_contract, errors::Error, Template}; + use anyhow::Result; + use std::{env, fs}; const CONTRACTS_NETWORK_URL: &str = "wss://rococo-contracts-rpc.polkadot.io"; - fn generate_smart_contract_test_environment() -> Result { + fn generate_smart_contract_test_environment() -> Result { let temp_dir = tempfile::tempdir().expect("Could not create temp dir"); - let temp_contract_dir = temp_dir.path().join("test_contract"); + let temp_contract_dir = temp_dir.path().join("testing"); fs::create_dir(&temp_contract_dir)?; - create_smart_contract("test_contract", temp_contract_dir.as_path())?; + create_smart_contract("testing", temp_contract_dir.as_path(), &Template::Standard)?; Ok(temp_dir) } - fn build_smart_contract_test_environment(temp_dir: &TempDir) -> Result<(), Error> { - build_smart_contract(&Some(temp_dir.path().join("test_contract")), true)?; + // Function that mocks the build process generating the contract artifacts. + fn mock_build_process(temp_contract_dir: PathBuf) -> Result<(), Error> { + // Create a target directory + let target_contract_dir = temp_contract_dir.join("target"); + fs::create_dir(&target_contract_dir)?; + fs::create_dir(&target_contract_dir.join("ink"))?; + // Copy a mocked testing.contract file inside the target directory + let current_dir = env::current_dir().expect("Failed to get current directory"); + let contract_file = current_dir.join("tests/files/testing.contract"); + fs::copy(contract_file, &target_contract_dir.join("ink/testing.contract"))?; Ok(()) } #[tokio::test] - async fn test_set_up_call() -> Result<(), Error> { + async fn test_set_up_call() -> Result<()> { let temp_dir = generate_smart_contract_test_environment()?; - build_smart_contract_test_environment(&temp_dir)?; + mock_build_process(temp_dir.path().join("testing"))?; let call_opts = CallOpts { - path: Some(temp_dir.path().join("test_contract")), + path: Some(temp_dir.path().join("testing")), contract: "5CLPm1CeUvJhZ8GCDZCR7nWZ2m3XXe4X5MtAQK69zEjut36A".to_string(), message: "get".to_string(), args: [].to_vec(), @@ -207,10 +207,10 @@ mod tests { } #[tokio::test] - async fn test_set_up_call_error_contract_not_build() -> Result<(), Error> { + async fn test_set_up_call_error_contract_not_build() -> Result<()> { let temp_dir = generate_smart_contract_test_environment()?; let call_opts = CallOpts { - path: Some(temp_dir.path().join("test_contract")), + path: Some(temp_dir.path().join("testing")), contract: "5CLPm1CeUvJhZ8GCDZCR7nWZ2m3XXe4X5MtAQK69zEjut36A".to_string(), message: "get".to_string(), args: [].to_vec(), @@ -229,7 +229,7 @@ mod tests { Ok(()) } #[tokio::test] - async fn test_set_up_call_fails_no_smart_contract_folder() -> Result<(), Error> { + async fn test_set_up_call_fails_no_smart_contract_folder() -> Result<()> { let call_opts = CallOpts { path: None, contract: "5CLPm1CeUvJhZ8GCDZCR7nWZ2m3XXe4X5MtAQK69zEjut36A".to_string(), @@ -249,4 +249,51 @@ mod tests { Ok(()) } + + #[tokio::test] + async fn test_dry_run_call_error_contract_not_deployed() -> Result<()> { + let temp_dir = generate_smart_contract_test_environment()?; + mock_build_process(temp_dir.path().join("testing"))?; + + let call_opts = CallOpts { + path: Some(temp_dir.path().join("testing")), + contract: "5CLPm1CeUvJhZ8GCDZCR7nWZ2m3XXe4X5MtAQK69zEjut36A".to_string(), + message: "get".to_string(), + args: [].to_vec(), + value: "1000".to_string(), + gas_limit: None, + proof_size: None, + url: Url::parse(CONTRACTS_NETWORK_URL)?, + suri: "//Alice".to_string(), + execute: false, + }; + let call = set_up_call(call_opts).await?; + assert!(matches!(dry_run_call(&call).await, Err(Error::DryRunCallContractError(..)))); + Ok(()) + } + + #[tokio::test] + async fn test_dry_run_estimate_call_error_contract_not_deployed() -> Result<()> { + let temp_dir = generate_smart_contract_test_environment()?; + mock_build_process(temp_dir.path().join("testing"))?; + + let call_opts = CallOpts { + path: Some(temp_dir.path().join("testing")), + contract: "5CLPm1CeUvJhZ8GCDZCR7nWZ2m3XXe4X5MtAQK69zEjut36A".to_string(), + message: "get".to_string(), + args: [].to_vec(), + value: "1000".to_string(), + gas_limit: None, + proof_size: None, + url: Url::parse(CONTRACTS_NETWORK_URL)?, + suri: "//Alice".to_string(), + execute: false, + }; + let call = set_up_call(call_opts).await?; + assert!(matches!( + dry_run_gas_estimate_call(&call).await, + Err(Error::DryRunCallContractError(..)) + )); + Ok(()) + } } diff --git a/crates/pop-contracts/src/errors.rs b/crates/pop-contracts/src/errors.rs index 5958304c..6f7201ca 100644 --- a/crates/pop-contracts/src/errors.rs +++ b/crates/pop-contracts/src/errors.rs @@ -31,6 +31,18 @@ pub enum Error { #[error("Failed to parse hex encoded bytes: {0}")] HexParsing(String), + #[error("Pre-submission dry-run failed: {0}")] + DryRunUploadContractError(String), + + #[error("Pre-submission dry-run failed: {0}")] + DryRunCallContractError(String), + + #[error("Anyhow error: {0}")] + AnyhowError(#[from] anyhow::Error), + + #[error("Failed to install {0}")] + InstallContractsNode(String), + #[error("ParseError error: {0}")] ParseError(#[from] url::ParseError), @@ -43,9 +55,6 @@ pub enum Error { #[error("Unsupported platform: {os}")] UnsupportedPlatform { os: &'static str }, - #[error("Anyhow error: {0}")] - AnyhowError(#[from] anyhow::Error), - #[error("HTTP error: {0}")] HttpError(#[from] reqwest::Error), } diff --git a/crates/pop-contracts/src/up.rs b/crates/pop-contracts/src/up.rs index 585c1147..3cbcb0a3 100644 --- a/crates/pop-contracts/src/up.rs +++ b/crates/pop-contracts/src/up.rs @@ -1,8 +1,10 @@ // SPDX-License-Identifier: GPL-3.0 - -use crate::utils::{ - helpers::{get_manifest_path, parse_balance}, - signer::create_signer, +use crate::{ + errors::Error, + utils::{ + helpers::{get_manifest_path, parse_balance}, + signer::create_signer, + }, }; use contract_extrinsics::{ BalanceVariant, ErrorVariant, ExtrinsicOptsBuilder, InstantiateCommandBuilder, InstantiateExec, @@ -81,11 +83,11 @@ pub async fn set_up_deployment( /// pub async fn dry_run_gas_estimate_instantiate( instantiate_exec: &InstantiateExec, -) -> anyhow::Result { +) -> Result { let instantiate_result = instantiate_exec.instantiate_dry_run().await?; match instantiate_result.result { Ok(_) => { - // use user specified values where provided, otherwise use the estimates + // Use user specified values where provided, otherwise use the estimates. let ref_time = instantiate_exec .args() .gas_limit() @@ -96,10 +98,10 @@ pub async fn dry_run_gas_estimate_instantiate( .unwrap_or_else(|| instantiate_result.gas_required.proof_size()); Ok(Weight::from_parts(ref_time, proof_size)) }, - Err(ref _err) => { - Err(anyhow::anyhow!( - "Pre-submission dry-run failed. Add gas_limit and proof_size manually to skip this step." - )) + Err(ref err) => { + let error_variant = + ErrorVariant::from_dispatch_error(err, &instantiate_exec.client().metadata())?; + Err(Error::DryRunUploadContractError(format!("{error_variant}"))) }, } } @@ -118,3 +120,75 @@ pub async fn instantiate_smart_contract( let instantiate_result = instantiate_exec.instantiate(Some(gas_limit)).await?; Ok(instantiate_result.contract_address.to_string()) } + +#[cfg(test)] +mod tests { + use super::*; + use crate::{create_smart_contract, errors::Error, Template}; + use anyhow::Result; + use std::{env, fs}; + use url::Url; + + const CONTRACTS_NETWORK_URL: &str = "wss://rococo-contracts-rpc.polkadot.io"; + + fn generate_smart_contract_test_environment() -> Result { + let temp_dir = tempfile::tempdir().expect("Could not create temp dir"); + let temp_contract_dir = temp_dir.path().join("testing"); + fs::create_dir(&temp_contract_dir)?; + create_smart_contract("testing", temp_contract_dir.as_path(), &Template::Standard)?; + Ok(temp_dir) + } + // Function that mocks the build process generating the contract artifacts. + fn mock_build_process(temp_contract_dir: PathBuf) -> Result<(), Error> { + // Create a target directory + let target_contract_dir = temp_contract_dir.join("target"); + fs::create_dir(&target_contract_dir)?; + fs::create_dir(&target_contract_dir.join("ink"))?; + // Copy a mocked testing.contract file inside the target directory + let current_dir = env::current_dir().expect("Failed to get current directory"); + let contract_file = current_dir.join("tests/files/testing.contract"); + fs::copy(contract_file, &target_contract_dir.join("ink/testing.contract"))?; + Ok(()) + } + + #[tokio::test] + async fn test_set_up_deployment() -> Result<()> { + let temp_dir = generate_smart_contract_test_environment()?; + mock_build_process(temp_dir.path().join("testing"))?; + let up_opts = UpOpts { + path: Some(temp_dir.path().join("testing")), + constructor: "new".to_string(), + args: ["false".to_string()].to_vec(), + value: "1000".to_string(), + gas_limit: None, + proof_size: None, + salt: None, + url: Url::parse(CONTRACTS_NETWORK_URL)?, + suri: "//Alice".to_string(), + }; + set_up_deployment(up_opts).await?; + Ok(()) + } + #[tokio::test] + async fn test_dry_run_gas_estimate_instantiate_throw_custom_error() -> Result<()> { + let temp_dir = generate_smart_contract_test_environment()?; + mock_build_process(temp_dir.path().join("testing"))?; + let up_opts = UpOpts { + path: Some(temp_dir.path().join("testing")), + constructor: "new".to_string(), + args: ["false".to_string()].to_vec(), + value: "1000".to_string(), + gas_limit: None, + proof_size: None, + salt: None, + url: Url::parse(CONTRACTS_NETWORK_URL)?, + suri: "//Alice".to_string(), + }; + let instantiate_exec = set_up_deployment(up_opts).await?; + assert!(matches!( + dry_run_gas_estimate_instantiate(&instantiate_exec).await, + Err(Error::DryRunUploadContractError(..)) + )); + Ok(()) + } +} diff --git a/crates/pop-contracts/tests/files/testing.contract b/crates/pop-contracts/tests/files/testing.contract new file mode 100644 index 00000000..77006686 --- /dev/null +++ b/crates/pop-contracts/tests/files/testing.contract @@ -0,0 +1 @@ +{"source":{"hash":"0xca7b0d5a7425ba48720ad9314e9fac4aaf141d0edd54ca257c12f0e706b24f4c","language":"ink! 5.0.0","compiler":"rustc 1.78.0","wasm":"0x0061736d01000000012b0860027f7f0060037f7f7f017f60000060047f7f7f7f017f60037f7f7f0060017f006000017f60017f017f027406057365616c310b6765745f73746f726167650003057365616c3005696e7075740000057365616c320b7365745f73746f726167650003057365616c300b7365616c5f72657475726e0004057365616c301176616c75655f7472616e73666572726564000003656e76066d656d6f72790201021003100f0101010105040006070002050002020616037f01418080040b7f00418080050b7f00418080050b0711020463616c6c0012066465706c6f7900130aab0b0f2b01017f037f2002200346047f200005200020036a200120036a2d00003a0000200341016a21030c010b0b0b6f01017f0240200020014d04402000210303402002450d02200320012d00003a0000200341016a2103200141016a2101200241016b21020c000b000b200041016b2103200141016b210103402002450d01200220036a200120026a2d00003a0000200241016b21020c000b000b20000b2501017f037f2002200346047f200005200020036a20013a0000200341016a21030c010b0b0b3f01027f0340200245044041000f0b200241016b210220012d0000210320002d00002104200141016a2101200041016a210020032004460d000b200420036b0b2601017f230041106b220124002001410036020c20002001410c6a4104100a200141106a24000b4801027f024002402000280208220320026a22042003490d00200420002802044b0d00200420036b2002470d01200028020020036a2001200210051a200020043602080f0b000b000b2601017f230041106b22022400200220003a000f20012002410f6a4101100a200241106a24000b6102027f027e230041206b22002400200041106a22014200370300200042003703082000411036021c200041086a2000411c6a1004200028021c41114f0440000b2001290300210220002903082103200041206a2400410541042002200384501b0b3f01017f2000280204220145044041020f0b2000200141016b36020420002000280200220041016a3602004101410220002d000022004101461b410020001b0b3c01027f027f200145044041808004210141010c010b410121024180800441013a000041818004210141020b2103200120023a0000200020031011000b12004180800441003b0100410041021011000b8a0101057f230041106b22012400200142808001370208200141808004360204200141046a22041009024020012802082205200128020c2202490d00200128020421032001410036020c2001200520026b3602082001200220036a36020420002004100b200128020c220020012802084b0d00200320022001280204200010021a200141106a24000f0b000b0d0020004180800420011003000b990301067f230041106b22002400024002400240100c41ff01714105470d0020004180800136020441808004200041046a100120002802042201418180014f0d0020014104490d02418380042d00002101418280042d00002103418180042d000021020240418080042d00002204412f470440200441e300470d04410121052002413a47200341a5014772200141d1004772450d010c040b200241860147200341db004772200141d90147720d030b200042808001370208200041808004360204200041046a2203100920002802082204200028020c2201490d00200028020421022000200420016b220436020420022001200120026a2201200310002000280204220220044b720d0020002002360208200020013602042003100d220141ff01714102460d0020002802080d0020050d01230041106b220024002000418080043602044180800441003a00002000428080818010370208200141ff0171410047200041046a100b200028020c2200418180014f0440000b410020001011000b000b200141ff017145101041004100100e000b41014101100e000be80101057f230041106b2200240002400240100c41ff01714105470d0020004180800136020c418080042000410c6a1001200028020c2201418180014f0d0020014104490d012000418480043602042000200141046b360208418380042d00002101418280042d00002102418180042d000021030240418080042d0000220441ed014704402004419b0147200341ae0147722002419d0147200141de004772720d03200041046a100d220041ff01714102470d010c030b200341cb00462002419d0146712001411b4671450d0241011010100f000b20004101731010100f000b000b41014101100e000b","build_info":{"build_mode":"Release","cargo_contract_version":"4.1.1","rust_toolchain":"stable-aarch64-apple-darwin","wasm_opt_settings":{"keep_debug_symbols":false,"optimization_passes":"Z"}}},"contract":{"name":"testing","version":"0.1.0","authors":["[your_name] <[your_email]>"]},"image":null,"spec":{"constructors":[{"args":[{"label":"init_value","type":{"displayName":["bool"],"type":0}}],"default":false,"docs":["Constructor that initializes the `bool` value to the given `init_value`."],"label":"new","payable":false,"returnType":{"displayName":["ink_primitives","ConstructorResult"],"type":2},"selector":"0x9bae9d5e"},{"args":[],"default":false,"docs":["Constructor that initializes the `bool` value to `false`.","","Constructors can delegate to other constructors."],"label":"default","payable":false,"returnType":{"displayName":["ink_primitives","ConstructorResult"],"type":2},"selector":"0xed4b9d1b"}],"docs":[],"environment":{"accountId":{"displayName":["AccountId"],"type":6},"balance":{"displayName":["Balance"],"type":9},"blockNumber":{"displayName":["BlockNumber"],"type":12},"chainExtension":{"displayName":["ChainExtension"],"type":13},"hash":{"displayName":["Hash"],"type":10},"maxEventTopics":4,"staticBufferSize":16384,"timestamp":{"displayName":["Timestamp"],"type":11}},"events":[],"lang_error":{"displayName":["ink","LangError"],"type":4},"messages":[{"args":[],"default":false,"docs":[" A message that can be called on instantiated contracts."," This one flips the value of the stored `bool` from `true`"," to `false` and vice versa."],"label":"flip","mutates":true,"payable":false,"returnType":{"displayName":["ink","MessageResult"],"type":2},"selector":"0x633aa551"},{"args":[],"default":false,"docs":[" Simply returns the current value of our `bool`."],"label":"get","mutates":false,"payable":false,"returnType":{"displayName":["ink","MessageResult"],"type":5},"selector":"0x2f865bd9"}]},"storage":{"root":{"layout":{"struct":{"fields":[{"layout":{"leaf":{"key":"0x00000000","ty":0}},"name":"value"}],"name":"Testing2"}},"root_key":"0x00000000","ty":1}},"types":[{"id":0,"type":{"def":{"primitive":"bool"}}},{"id":1,"type":{"def":{"composite":{"fields":[{"name":"value","type":0,"typeName":",>>::Type"}]}},"path":["testing","testing","Testing2"]}},{"id":2,"type":{"def":{"variant":{"variants":[{"fields":[{"type":3}],"index":0,"name":"Ok"},{"fields":[{"type":4}],"index":1,"name":"Err"}]}},"params":[{"name":"T","type":3},{"name":"E","type":4}],"path":["Result"]}},{"id":3,"type":{"def":{"tuple":[]}}},{"id":4,"type":{"def":{"variant":{"variants":[{"index":1,"name":"CouldNotReadInput"}]}},"path":["ink_primitives","LangError"]}},{"id":5,"type":{"def":{"variant":{"variants":[{"fields":[{"type":0}],"index":0,"name":"Ok"},{"fields":[{"type":4}],"index":1,"name":"Err"}]}},"params":[{"name":"T","type":0},{"name":"E","type":4}],"path":["Result"]}},{"id":6,"type":{"def":{"composite":{"fields":[{"type":7,"typeName":"[u8; 32]"}]}},"path":["ink_primitives","types","AccountId"]}},{"id":7,"type":{"def":{"array":{"len":32,"type":8}}}},{"id":8,"type":{"def":{"primitive":"u8"}}},{"id":9,"type":{"def":{"primitive":"u128"}}},{"id":10,"type":{"def":{"composite":{"fields":[{"type":7,"typeName":"[u8; 32]"}]}},"path":["ink_primitives","types","Hash"]}},{"id":11,"type":{"def":{"primitive":"u64"}}},{"id":12,"type":{"def":{"primitive":"u32"}}},{"id":13,"type":{"def":{"variant":{}},"path":["ink_env","types","NoChainExtension"]}}],"version":5} \ No newline at end of file