From e2f9212cc6d768adfd4b44e0d719856bf09361df Mon Sep 17 00:00:00 2001 From: Doris Benda Date: Thu, 3 Aug 2023 14:47:58 +0300 Subject: [PATCH 01/20] Initial deploy script skeleton --- templates/default/deploy-scripts/Cargo.toml | 20 ++ templates/default/deploy-scripts/README.md | 80 +++++ .../default/deploy-scripts/src/deployer.rs | 323 ++++++++++++++++++ templates/default/deploy-scripts/src/main.rs | 162 +++++++++ 4 files changed, 585 insertions(+) create mode 100644 templates/default/deploy-scripts/Cargo.toml create mode 100644 templates/default/deploy-scripts/README.md create mode 100644 templates/default/deploy-scripts/src/deployer.rs create mode 100644 templates/default/deploy-scripts/src/main.rs diff --git a/templates/default/deploy-scripts/Cargo.toml b/templates/default/deploy-scripts/Cargo.toml new file mode 100644 index 00000000..119a762a --- /dev/null +++ b/templates/default/deploy-scripts/Cargo.toml @@ -0,0 +1,20 @@ +[package] +edition = "2021" +name = "deploy_scripts" +version = "1.0.0" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +anyhow = "1.0" +chrono = "0.4.26" +hex = "0.4" +reqwest = {version = "0.11", features = ["json"]} +serde = "1.0" +serde_json = "1.0" +sha2 = "0.10" +thiserror = "1.0" +tokio = {version = "1.18", features = ["rt", "macros"]} +clap = { version = "4", features = ["derive", "env"]} +concordium-rust-sdk="2.4" + diff --git a/templates/default/deploy-scripts/README.md b/templates/default/deploy-scripts/README.md new file mode 100644 index 00000000..3e629367 --- /dev/null +++ b/templates/default/deploy-scripts/README.md @@ -0,0 +1,80 @@ +# Deploy smart contract instances for Bridge Manager + +# Setup: + +Make sure to initialize and update the submodules of this repository + +``` +git submodule update --init --recursive +``` + +Build and run the scripts using +``` +cargo run +``` + +The following options are necessary + +``` + --node + V2 API of the concordium node. [default: http://localhost:20001] + --wallet + Location of the Concordium wallet. + --tokens + JSON file with a list of tokens. + --manager-source + Location of the compiled BridgeManager contract. + --cis2-bridgeable + Source of the CIS2 token contract. +``` + +The `tokens` file should be a valid JSON file with a list of objects of the form +``` +{ + name: "USDC.eth", + token_metadata_url: "http://domain/path", + token_metadata_hash: "6a6ca3243935653bf3b271aa1257a3f9351663757c66a498750d4622f81c08f5" +} +``` + +The `wallet` parameter should be a Concordium wallet either exported from the +Browser wallet or the new mobile wallets, or in the format emitted by the +genesis tool. + +# Deploy contracts: + +``` +$ cargo run + +Deploying CIS2-Bridgeable.... +Module with reference 56a6ca3243935653bf3b271aa1257a3f9351663757c66a498750d4622f81c08f already exists. +Deployed CIS2-Bridgeable, module_ref: 56a6ca3243935653bf3b271aa1257a3f9351663757c66a498750d4622f81c08f + +Deploying Bridge-Manager.... +Module with reference 4dae844ef592e011b67d4c44da9604232976857fcf8ad8d14438afe6125d6c24 already exists. +Deployed Bridge-Manager, module_ref: 4dae844ef592e011b67d4c44da9604232976857fcf8ad8d14438afe6125d6c24 + +Initializing BridgeManager.... +Sent tx: ea5376d2a58a268fd06188840fea46e1f9b09ce2eaa0d929eebd771f6c622588 +Transaction finalized, tx_hash=ea5376d2a58a268fd06188840fea46e1f9b09ce2eaa0d929eebd771f6c622588 contract=(605, 0) +Initialized BridgeManager, address: (605, 0) +Granting Manager address Manager role on BridgeManager.... +Sent tx: 8e7883a2a9e49cdc5f9181df851f0ba6cf8ab8051ba559713233703b505b4d83 +Granted Manager address Manager role on BridgeManager + +Initializing CIS2-Bridgeable MOCK.et.... +Sent tx: 9dcaac74c42e0cc23b4b02be7c688e8c7f8e48779b69d52690ab76aa3c939fef +Transaction finalized, tx_hash=9dcaac74c42e0cc23b4b02be7c688e8c7f8e48779b69d52690ab76aa3c939fef contract=(606, 0) +Initialized CIS2-Bridgeable MOCK.et at address: (606, 0) +Granting BridgeManager Manager role on MOCK.et token.... +Sent tx: 8707603fc0dd1e2733c9e6b77ff341e576f12dd0076620aaec6e28f15f77357b +Granted BridgeManager Manager role on MOCK.et token + +Initializing CIS2-Bridgeable USDC.et.... +Sent tx: 5dc31a314dc1c512c1ba355d05224e308de9cb31334ad8412a0914a2b1796000 +Transaction finalized, tx_hash=5dc31a314dc1c512c1ba355d05224e308de9cb31334ad8412a0914a2b1796000 contract=(607, 0) +Initialized CIS2-Bridgeable USDC.et at address: (607, 0) +Granting BridgeManager Manager role on USDC.et token.... +Sent tx: e1db48b78699cbfb6bc2585745fcb1378de115e23e833999bc0c2589d8ede154 +Granted BridgeManager Manager role on USDC.et token +``` diff --git a/templates/default/deploy-scripts/src/deployer.rs b/templates/default/deploy-scripts/src/deployer.rs new file mode 100644 index 00000000..77f57d92 --- /dev/null +++ b/templates/default/deploy-scripts/src/deployer.rs @@ -0,0 +1,323 @@ +use concordium_rust_sdk::{ + common::types::TransactionTime, + id::types::AccountAddress, + smart_contracts::common::{Address, ModuleReference}, + types::{ + queries::AccountNonceResponse, + smart_contracts::{ContractContext, InvokeContractResult, WasmModule}, + transactions::{ + self, + send::{deploy_module, init_contract}, + InitContractPayload, UpdateContractPayload, + }, + AccountTransactionEffects, BlockItemSummary, BlockItemSummaryDetails, ContractAddress, + Energy, RejectReason, TransactionType, WalletAccount, + }, + v2, +}; +use std::path::Path; + +use crate::DeployError; + +#[derive(Clone, Debug)] +pub struct ModuleDeployed { + pub module_ref: ModuleReference, +} + +#[derive(Clone, Debug)] +pub struct ContractInitialized { + pub contract: ContractAddress, +} + +#[derive(Debug)] +pub struct Deployer { + pub client: v2::Client, + pub key: WalletAccount, +} + +impl Deployer { + pub fn new(client: v2::Client, wallet_account_file: &Path) -> Result { + let key_data = WalletAccount::from_json_file(wallet_account_file)?; + + Ok(Deployer { + client, + key: key_data, + }) + } + + pub async fn module_exists(&self, wasm_module: WasmModule) -> Result { + let consensus_info = self.client.clone().get_consensus_info().await?; + + let latest_block = consensus_info.last_finalized_block; + + let module_ref = wasm_module.get_module_ref(); + + let module_ref = self + .client + .clone() + .get_module_source(&module_ref, &latest_block) + .await; + + match module_ref { + Ok(_) => Ok(true), + Err(e) if e.is_not_found() => Ok(false), + Err(e) => Err(e.into()), + } + } + + pub async fn deploy_wasm_module( + &self, + wasm_module: WasmModule, + ) -> Result { + println!(); + + println!("Deploying contract...."); + + let exists = self.module_exists(wasm_module.clone()).await?; + if exists { + println!( + "Module with reference {} already exists on chain.", + wasm_module.get_module_ref() + ); + return Ok(ModuleDeployed { + module_ref: wasm_module.get_module_ref(), + }); + } + + let nonce = self.get_nonce(self.key.address).await?; + + if !nonce.all_final { + return Err(DeployError::NonceNotFinal); + } + + let expiry = TransactionTime::from_seconds((chrono::Utc::now().timestamp() + 300) as u64); + + let tx = deploy_module( + &self.key, + self.key.address, + nonce.nonce, + expiry, + wasm_module, + ); + let bi = transactions::BlockItem::AccountTransaction(tx); + + let tx_hash = self + .client + .clone() + .send_block_item(&bi) + .await + .map_err(DeployError::TransactionRejected)?; + + let (_, block_item) = self.client.clone().wait_until_finalized(&tx_hash).await?; + + let module_deployed = self.parse_deploy_module_event(block_item)?; + + println!( + "Transaction finalized, tx_hash={} module_ref={}", + tx_hash, module_deployed.module_ref, + ); + + Ok(module_deployed) + } + + pub async fn init_contract( + &self, + payload: InitContractPayload, + ) -> Result { + println!(); + + println!("Initializing contract...."); + + let nonce = self.get_nonce(self.key.address).await?; + + if !nonce.all_final { + return Err(DeployError::NonceNotFinal); + } + + let expiry = TransactionTime::from_seconds((chrono::Utc::now().timestamp() + 300) as u64); + let energy = Energy { energy: 5000 }; + + let tx = init_contract( + &self.key, + self.key.address, + nonce.nonce, + expiry, + payload, + energy, + ); + + let bi = transactions::BlockItem::AccountTransaction(tx); + + let tx_hash = self + .client + .clone() + .send_block_item(&bi) + .await + .map_err(DeployError::TransactionRejected)?; + + println!("Sent tx: {tx_hash}"); + + let (_, block_item) = self.client.clone().wait_until_finalized(&tx_hash).await?; + + let contract_init = self.parse_contract_init_event(block_item)?; + + println!( + "Transaction finalized, tx_hash={} contract=({}, {})", + tx_hash, contract_init.contract.index, contract_init.contract.subindex, + ); + + Ok(contract_init.contract) + } + + async fn estimate_energy(&self, payload: UpdateContractPayload) -> Result { + let consensus_info = self.client.clone().get_consensus_info().await?; + + let context = ContractContext { + invoker: Some(Address::Account(self.key.address)), + contract: payload.address, + amount: payload.amount, + method: payload.receive_name, + parameter: payload.message, + energy: 100000.into(), + }; + + let result = self + .client + .clone() + .invoke_instance(&consensus_info.best_block, &context) + .await?; + + match result.response { + InvokeContractResult::Failure { + return_value, + reason, + used_energy, + } => Err(DeployError::InvokeContractFailed(format!( + "contract invoke failed: {reason:?}, used_energy={used_energy}, return \ + value={return_value:?}" + ))), + InvokeContractResult::Success { + return_value: _, + events: _, + used_energy, + } => { + let e = used_energy.energy; + println!("Estimated energy: {e}"); + Ok(Energy { energy: e + 100 }) + } + } + } + + pub async fn get_nonce( + &self, + address: AccountAddress, + ) -> Result { + let nonce = self + .client + .clone() + .get_next_account_sequence_number(&address) + .await?; + Ok(nonce) + } + + fn parse_deploy_module_event( + &self, + block_item: BlockItemSummary, + ) -> Result { + match block_item.details { + BlockItemSummaryDetails::AccountTransaction(a) => match a.effects { + AccountTransactionEffects::None { + transaction_type, + reject_reason, + } => { + if transaction_type != Some(TransactionType::DeployModule) { + return Err(DeployError::InvalidBlockItem( + "Expected transaction type to be DeployModule if rejected".into(), + )); + } + + match reject_reason { + RejectReason::ModuleHashAlreadyExists { contents } => Ok(ModuleDeployed { + module_ref: contents, + }), + _ => Err(DeployError::TransactionRejectedR(format!( + "module deploy rejected with reason: {reject_reason:?}" + ))), + } + } + AccountTransactionEffects::ModuleDeployed { module_ref } => { + Ok(ModuleDeployed { module_ref }) + } + _ => Err(DeployError::InvalidBlockItem( + "invalid transaction effects".into(), + )), + }, + _ => Err(DeployError::InvalidBlockItem( + "Expected Account transaction".into(), + )), + } + } + + fn parse_contract_init_event( + &self, + block_item: BlockItemSummary, + ) -> Result { + match block_item.details { + BlockItemSummaryDetails::AccountTransaction(a) => match a.effects { + AccountTransactionEffects::None { + transaction_type, + reject_reason, + } => { + if transaction_type != Some(TransactionType::InitContract) { + return Err(DeployError::InvalidBlockItem( + "Expected transaction type to be InitContract if rejected".into(), + )); + } + + Err(DeployError::TransactionRejectedR(format!( + "contract init rejected with reason: {reject_reason:?}" + ))) + } + AccountTransactionEffects::ContractInitialized { data } => { + Ok(ContractInitialized { + contract: data.address, + }) + } + _ => Err(DeployError::InvalidBlockItem( + "invalid transaction effects".into(), + )), + }, + _ => Err(DeployError::InvalidBlockItem( + "Expected Account transaction".into(), + )), + } + } + + fn parse_contract_update_event(&self, block_item: BlockItemSummary) -> Result<(), DeployError> { + match block_item.details { + BlockItemSummaryDetails::AccountTransaction(a) => match a.effects { + AccountTransactionEffects::None { + transaction_type, + reject_reason, + } => { + if transaction_type != Some(TransactionType::Update) { + return Err(DeployError::InvalidBlockItem( + "Expected transaction type to be Update if rejected".into(), + )); + } + + Err(DeployError::TransactionRejectedR(format!( + "contract update rejected with reason: {reject_reason:?}" + ))) + } + AccountTransactionEffects::ContractUpdateIssued { effects: _ } => Ok(()), + _ => Err(DeployError::InvalidBlockItem( + "invalid transaction effects".into(), + )), + }, + _ => Err(DeployError::InvalidBlockItem( + "Expected Account transaction".into(), + )), + } + } +} diff --git a/templates/default/deploy-scripts/src/main.rs b/templates/default/deploy-scripts/src/main.rs new file mode 100644 index 00000000..cf9b215c --- /dev/null +++ b/templates/default/deploy-scripts/src/main.rs @@ -0,0 +1,162 @@ +pub mod deployer; + +use anyhow::Context; +use clap::Parser; +use concordium_rust_sdk::common::types::Amount; +use concordium_rust_sdk::smart_contracts::types::OwnedContractName; +use concordium_rust_sdk::smart_contracts::types::OwnedParameter; +use concordium_rust_sdk::{ + endpoints::{self, RPCError}, + smart_contracts::common::{NewContractNameError, NewReceiveNameError}, + types::{ + hashes::TransactionHash, + smart_contracts::{ExceedsParameterSize, ModuleReference, WasmModule}, + ContractAddress, + }, + v2, +}; + +use concordium_rust_sdk::types::transactions::InitContractPayload; +use deployer::{Deployer, ModuleDeployed}; +use hex::FromHexError; +use serde::{Deserialize, Serialize}; +use std::{ + io::Cursor, + path::{Path, PathBuf}, +}; +use thiserror::Error; + +#[derive(Deserialize, Clone, Debug)] +pub struct WrappedToken { + pub name: String, + pub token_metadata_url: String, + pub token_metadata_hash: TransactionHash, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct Output { + pub bridge_manager: ContractAddress, + pub tokens: Vec, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct OutputToken { + pub name: String, + pub token_url: String, + pub contract: ContractAddress, +} + +#[derive(Error, Debug)] +pub enum DeployError { + #[error("concordium error: {0}")] + RPCError(#[from] RPCError), + #[error("transport error: {0}")] + TransportError(#[from] v2::Error), + #[error("query error: {0}")] + QueryError(#[from] endpoints::QueryError), + #[error("anyhow error: {0}")] + AnyhowError(#[from] anyhow::Error), + #[error("There are unfinalized transactions. Transaction nonce is not reliable enough.")] + NonceNotFinal, + #[error("Transaction rejected: {0}")] + TransactionRejected(RPCError), + #[error("Transaction rejected: {0:?}")] + TransactionRejectedR(String), + #[error("Invalid block item: {0}")] + InvalidBlockItem(String), + #[error("Invalid contract name: {0}")] + InvalidContractName(String), + #[error("hex decoding error: {0}")] + HexDecodingError(#[from] FromHexError), + #[error("failed to parse receive name: {0}")] + FailedToParseReceiveName(String), + #[error("Json error: {0}")] + JSONError(#[from] serde_json::Error), + #[error("Parameter size error: {0}")] + ParameterSizeError(#[from] ExceedsParameterSize), + #[error("Receive name error: {0}")] + ReceiveNameError(#[from] NewReceiveNameError), + #[error("Contract name error: {0}")] + ContractNameError(#[from] NewContractNameError), + #[error("Reqwest error: {0}")] + ReqwestError(#[from] reqwest::Error), + #[error("Invalid metadata hash: {0}")] + InvalidHash(String), + #[error("IO error: {0}")] + IOError(#[from] std::io::Error), + #[error("Invoke contract failed: {0}")] + InvokeContractFailed(String), +} + +#[allow(dead_code)] +fn module_deployed(module_ref: &str) -> Result { + let mut bytes = [0u8; 32]; + hex::decode_to_slice(module_ref, &mut bytes)?; + + let module_deployed = ModuleDeployed { + module_ref: ModuleReference::from(bytes), + }; + + Ok(module_deployed) +} + +fn get_wasm_module(file: &Path) -> Result { + let wasm_module = std::fs::read(file).context("Could not read the contract WASM file")?; + let mut cursor = Cursor::new(wasm_module); + let wasm_module: WasmModule = concordium_rust_sdk::common::from_bytes(&mut cursor)?; + Ok(wasm_module) +} + +#[derive(clap::Parser, Debug)] +#[clap(author, version, about)] +struct App { + #[clap( + long = "node", + default_value = "http://node.testnet.concordium.com:20000", + help = "V2 API of the concordium node." + )] + url: v2::Endpoint, + #[clap( + long = "account", + help = "Location of the Concordium account key file." + )] + key_file: PathBuf, +} + +const CONTRACTS: &[&str] = &["./cis2_nft.wasm.v1"]; + +#[tokio::main(flavor = "current_thread")] +async fn main() -> Result<(), DeployError> { + let app: App = App::parse(); + + let concordium_client = v2::Client::new(app.url).await?; + + let deployer = Deployer::new(concordium_client, &app.key_file)?; + + let mut modules_deployed: Vec = Vec::new(); + + for contract in CONTRACTS { + let wasm_module = get_wasm_module(PathBuf::from(contract).as_path())?; + + let module = deployer.deploy_wasm_module(wasm_module).await?; + + modules_deployed.push(module); + } + + // Write your own deployment/initialization script below. Here is an example given. + + let param: OwnedParameter = OwnedParameter::default(); + + let init_method_name: &str = "init_cis2_nft"; + + let payload = InitContractPayload { + init_name: OwnedContractName::new(init_method_name.into())?, + amount: Amount::from_micro_ccd(0), + mod_ref: modules_deployed[0].module_ref, + param, + }; + + let _contract = deployer.init_contract(payload).await?; + + Ok(()) +} From 63997f38a460a497218d826c0f63600b42ede329 Mon Sep 17 00:00:00 2001 From: Doris Benda Date: Mon, 2 Oct 2023 18:08:40 +0300 Subject: [PATCH 02/20] Add Read.me file --- templates/default/deploy-scripts/README.md | 131 ++++++++++++--------- 1 file changed, 75 insertions(+), 56 deletions(-) diff --git a/templates/default/deploy-scripts/README.md b/templates/default/deploy-scripts/README.md index 3e629367..e2073a29 100644 --- a/templates/default/deploy-scripts/README.md +++ b/templates/default/deploy-scripts/README.md @@ -1,80 +1,99 @@ -# Deploy smart contract instances for Bridge Manager +# Deploy, Initialize, and Update Script Template -# Setup: +This project has boilerplate code to write deployment, initilization, and update scripts for Concordium smart contract protocols. -Make sure to initialize and update the submodules of this repository +# Purpose + +Automatic scripts are useful to speed up the development and testing of your protocol on chain. +In addition, scripts help to set up identical protocols on different chains easily. E.g. you can deploy your protocol to testnet or mainnet by just specifiying a corresponding node connection when running the script. + +# Setup + +Option 1: + +``` +cargo concordium init +``` + +Option 2 (alternative command): ``` -git submodule update --init --recursive +cargo generate --git https://github.com/Concordium/concordium-rust-smart-contracts.git ``` -Build and run the scripts using +Any of the two commands will work and will give you several templates to choose from. + +- Choose the `templates/default` and answer the questions to complete the setup process. + +At the end, you will have a Rust project setup with this boilerplate code included. + +# Running The Script + +Build and run the script from the root folder using ``` cargo run ``` -The following options are necessary +The following options are necessary when running the script ``` --node - V2 API of the concordium node. [default: http://localhost:20001] - --wallet - Location of the Concordium wallet. - --tokens - JSON file with a list of tokens. - --manager-source - Location of the compiled BridgeManager contract. - --cis2-bridgeable - Source of the CIS2 token contract. + V2 API of the concordium node. [default: http://node.testnet.concordium.com:20000] + --account + Path to the Concordium account (with account keys). + --modules + A list of wasm modules. [default: [./default.wasm.v1]] ``` -The `tokens` file should be a valid JSON file with a list of objects of the form +The `account` parameter should be a Concordium wallet account either exported from the +Browser wallet or the mobile wallets, or in the format emitted by the +genesis tool. + +Example: ``` -{ - name: "USDC.eth", - token_metadata_url: "http://domain/path", - token_metadata_hash: "6a6ca3243935653bf3b271aa1257a3f9351663757c66a498750d4622f81c08f5" -} +cargo run -- --node http://node.testnet.concordium.com:20000 --account ./3PXwJYYPf6fyVb4GJquxSZU8puxrHfzc4XogdMVot8MUQK53tW.export --modules ["./default.wasm.v1"] ``` -The `wallet` parameter should be a Concordium wallet either exported from the -Browser wallet or the new mobile wallets, or in the format emitted by the -genesis tool. +# Functionalities + +The boilerplate code has support for the following functionalities: + +Read functions: +- `estimate_energy`: To estmiate the energy needed to execute one of the three write functions below. +- `module_exists`: To check if a module has already been deployed on chain. +- `get_nonce`: To get the current nonce of the provided wallet account. + +Write functions: +- `deploy_wasm_module`: To deploy a new smart contract module on chain. +- `init_contract`: To initilization a smart contract instance on chain. +- `update_contract`: To updating a smart conract instance on chain. + +Event parsing helper functions: +- `parse_deploy_module_event`: To parse the chain events after deploying a modules. +- `parse_contract_init_event`: To parse the chain events after initilization a smart contract instance. +- `parse_contract_update_event`: To parse the chain events after updating a smart conract instance. + +The `main.rs` file has a section (marked with `// Write your own deployment/initialization script below. An example is given here.`) that you should replace with your custom logic to deploy, initilize, and update your smart contract protocol. + +# Running the Example + +The `main.rs` file has a section (marked with `// Write your own deployment/initialization script below. An example is given here.`) which provides an example that you can run. + +Navigate into the root folder and compile the `default` smart contract with the command: +``` +cargo concordium build --out ./deploy-scripts/default.wasm.v1 +``` + +Then, deploy the `default` smart contract on chain (replace your wallet account in the below command): + +``` +cargo run -- --node http://node.testnet.concordium.com:20000 --account ./3PXwJYYPf6fyVb4GJquxSZU8puxrHfzc4XogdMVot8MUQK53tW.export --modules ["./default.wasm.v1"] +``` -# Deploy contracts: +The output should be: ``` $ cargo run -Deploying CIS2-Bridgeable.... -Module with reference 56a6ca3243935653bf3b271aa1257a3f9351663757c66a498750d4622f81c08f already exists. -Deployed CIS2-Bridgeable, module_ref: 56a6ca3243935653bf3b271aa1257a3f9351663757c66a498750d4622f81c08f - -Deploying Bridge-Manager.... -Module with reference 4dae844ef592e011b67d4c44da9604232976857fcf8ad8d14438afe6125d6c24 already exists. -Deployed Bridge-Manager, module_ref: 4dae844ef592e011b67d4c44da9604232976857fcf8ad8d14438afe6125d6c24 - -Initializing BridgeManager.... -Sent tx: ea5376d2a58a268fd06188840fea46e1f9b09ce2eaa0d929eebd771f6c622588 -Transaction finalized, tx_hash=ea5376d2a58a268fd06188840fea46e1f9b09ce2eaa0d929eebd771f6c622588 contract=(605, 0) -Initialized BridgeManager, address: (605, 0) -Granting Manager address Manager role on BridgeManager.... -Sent tx: 8e7883a2a9e49cdc5f9181df851f0ba6cf8ab8051ba559713233703b505b4d83 -Granted Manager address Manager role on BridgeManager - -Initializing CIS2-Bridgeable MOCK.et.... -Sent tx: 9dcaac74c42e0cc23b4b02be7c688e8c7f8e48779b69d52690ab76aa3c939fef -Transaction finalized, tx_hash=9dcaac74c42e0cc23b4b02be7c688e8c7f8e48779b69d52690ab76aa3c939fef contract=(606, 0) -Initialized CIS2-Bridgeable MOCK.et at address: (606, 0) -Granting BridgeManager Manager role on MOCK.et token.... -Sent tx: 8707603fc0dd1e2733c9e6b77ff341e576f12dd0076620aaec6e28f15f77357b -Granted BridgeManager Manager role on MOCK.et token - -Initializing CIS2-Bridgeable USDC.et.... -Sent tx: 5dc31a314dc1c512c1ba355d05224e308de9cb31334ad8412a0914a2b1796000 -Transaction finalized, tx_hash=5dc31a314dc1c512c1ba355d05224e308de9cb31334ad8412a0914a2b1796000 contract=(607, 0) -Initialized CIS2-Bridgeable USDC.et at address: (607, 0) -Granting BridgeManager Manager role on USDC.et token.... -Sent tx: e1db48b78699cbfb6bc2585745fcb1378de115e23e833999bc0c2589d8ede154 -Granted BridgeManager Manager role on USDC.et token +... ``` From 270600324424ce0e32aa9399792af6b094e04794 Mon Sep 17 00:00:00 2001 From: Doris Benda Date: Thu, 5 Oct 2023 14:42:16 +0200 Subject: [PATCH 03/20] Add contract update functionalities --- .../default/deploy-scripts/src/deployer.rs | 63 ++++++++++++++++--- templates/default/deploy-scripts/src/main.rs | 37 +++++++++-- 2 files changed, 87 insertions(+), 13 deletions(-) diff --git a/templates/default/deploy-scripts/src/deployer.rs b/templates/default/deploy-scripts/src/deployer.rs index 77f57d92..7ede0b0f 100644 --- a/templates/default/deploy-scripts/src/deployer.rs +++ b/templates/default/deploy-scripts/src/deployer.rs @@ -7,7 +7,7 @@ use concordium_rust_sdk::{ smart_contracts::{ContractContext, InvokeContractResult, WasmModule}, transactions::{ self, - send::{deploy_module, init_contract}, + send::{deploy_module, init_contract, GivenEnergy}, InitContractPayload, UpdateContractPayload, }, AccountTransactionEffects, BlockItemSummary, BlockItemSummaryDetails, ContractAddress, @@ -16,6 +16,7 @@ use concordium_rust_sdk::{ v2, }; use std::path::Path; +use crate::v2::BlockIdentifier::Best; use crate::DeployError; @@ -46,18 +47,19 @@ impl Deployer { } pub async fn module_exists(&self, wasm_module: WasmModule) -> Result { - let consensus_info = self.client.clone().get_consensus_info().await?; - let latest_block = consensus_info.last_finalized_block; + let best_block = self.client.clone().get_block_finalization_summary(Best).await?; + + let best_block_hash = best_block.block_hash; let module_ref = wasm_module.get_module_ref(); let module_ref = self .client .clone() - .get_module_source(&module_ref, &latest_block) + .get_module_source(&module_ref, &best_block_hash) .await; - + match module_ref { Ok(_) => Ok(true), Err(e) if e.is_not_found() => Ok(false), @@ -74,6 +76,7 @@ impl Deployer { println!("Deploying contract...."); let exists = self.module_exists(wasm_module.clone()).await?; + if exists { println!( "Module with reference {} already exists on chain.", @@ -169,8 +172,52 @@ impl Deployer { Ok(contract_init.contract) } - async fn estimate_energy(&self, payload: UpdateContractPayload) -> Result { - let consensus_info = self.client.clone().get_consensus_info().await?; + pub async fn update_contract( + &self, + update_payload: UpdateContractPayload, + energy: GivenEnergy, + ) -> Result<(), DeployError> { + let nonce = self.get_nonce(self.key.address).await?; + + if !nonce.all_final { + return Err(DeployError::NonceNotFinal); + } + + let payload = transactions::Payload::Update { + payload: update_payload, + }; + + let expiry = TransactionTime::from_seconds((chrono::Utc::now().timestamp() + 300) as u64); + + let tx = transactions::send::make_and_sign_transaction( + &self.key, + self.key.address, + nonce.nonce, + expiry, + energy, + payload, + ); + let bi = transactions::BlockItem::AccountTransaction(tx); + + let tx_hash = self + .client + .clone() + .send_block_item(&bi) + .await + .map_err(DeployError::TransactionRejected)?; + println!("Sent tx: {tx_hash}"); + + let (_, block_item) = self.client.clone().wait_until_finalized(&tx_hash).await?; + + self.parse_contract_update_event(block_item)?; + + Ok(()) + } + + pub async fn estimate_energy(&self, payload: UpdateContractPayload) -> Result { + let best_block = self.client.clone().get_block_finalization_summary(Best).await?; + + let best_block_hash = best_block.block_hash; let context = ContractContext { invoker: Some(Address::Account(self.key.address)), @@ -184,7 +231,7 @@ impl Deployer { let result = self .client .clone() - .invoke_instance(&consensus_info.best_block, &context) + .invoke_instance(&best_block_hash, &context) .await?; match result.response { diff --git a/templates/default/deploy-scripts/src/main.rs b/templates/default/deploy-scripts/src/main.rs index cf9b215c..1a1217ca 100644 --- a/templates/default/deploy-scripts/src/main.rs +++ b/templates/default/deploy-scripts/src/main.rs @@ -1,8 +1,14 @@ pub mod deployer; +use concordium_rust_sdk::smart_contracts::types::OwnedReceiveName; +use concordium_rust_sdk::types::transactions; +use concordium_rust_sdk::{ + common::types::Amount, + smart_contracts::common::{self as contracts_common}, +}; + use anyhow::Context; use clap::Parser; -use concordium_rust_sdk::common::types::Amount; use concordium_rust_sdk::smart_contracts::types::OwnedContractName; use concordium_rust_sdk::smart_contracts::types::OwnedParameter; use concordium_rust_sdk::{ @@ -15,6 +21,7 @@ use concordium_rust_sdk::{ }, v2, }; +use concordium_rust_sdk::types::transactions::send::GivenEnergy; use concordium_rust_sdk::types::transactions::InitContractPayload; use deployer::{Deployer, ModuleDeployed}; @@ -123,7 +130,7 @@ struct App { key_file: PathBuf, } -const CONTRACTS: &[&str] = &["./cis2_nft.wasm.v1"]; +const CONTRACTS: &[&str] = &["./default.wasm.v1"]; #[tokio::main(flavor = "current_thread")] async fn main() -> Result<(), DeployError> { @@ -143,11 +150,11 @@ async fn main() -> Result<(), DeployError> { modules_deployed.push(module); } - // Write your own deployment/initialization script below. Here is an example given. + // Write your own deployment/initialization script below. An example is given here. let param: OwnedParameter = OwnedParameter::default(); - let init_method_name: &str = "init_cis2_nft"; + let init_method_name: &str = "init_default"; let payload = InitContractPayload { init_name: OwnedContractName::new(init_method_name.into())?, @@ -156,7 +163,27 @@ async fn main() -> Result<(), DeployError> { param, }; - let _contract = deployer.init_contract(payload).await?; + let contract = deployer.init_contract(payload).await?; + + let bytes = contracts_common::to_bytes(&false); + + let update_payload = transactions::UpdateContractPayload { + amount: Amount::from_ccd(0), + address: contract, + receive_name: OwnedReceiveName::new_unchecked("default.receive".to_string()), + message: bytes.try_into()?, + }; + + let energy = deployer.estimate_energy(update_payload.clone()).await?; + + let _update_contract = deployer + .update_contract( + update_payload, + GivenEnergy::Add(energy), + ) + .await?; + + // Write your own deployment/initialization script above. An example is given here. Ok(()) } From a3c770aa98ebdf93198545ba930b450a16fbe5b6 Mon Sep 17 00:00:00 2001 From: Doris Benda Date: Thu, 5 Oct 2023 16:04:49 +0200 Subject: [PATCH 04/20] Change modules are passed in via flag --- templates/default/deploy-scripts/README.md | 32 +++++++++++--------- templates/default/deploy-scripts/src/main.rs | 12 +++++--- 2 files changed, 24 insertions(+), 20 deletions(-) diff --git a/templates/default/deploy-scripts/README.md b/templates/default/deploy-scripts/README.md index e2073a29..b9950c24 100644 --- a/templates/default/deploy-scripts/README.md +++ b/templates/default/deploy-scripts/README.md @@ -1,11 +1,11 @@ # Deploy, Initialize, and Update Script Template -This project has boilerplate code to write deployment, initilization, and update scripts for Concordium smart contract protocols. +This project has boilerplate code to write deployment, initialization, and update scripts for Concordium smart contract protocols. # Purpose Automatic scripts are useful to speed up the development and testing of your protocol on chain. -In addition, scripts help to set up identical protocols on different chains easily. E.g. you can deploy your protocol to testnet or mainnet by just specifiying a corresponding node connection when running the script. +In addition, scripts help to set up identical protocols on different chains easily. E.g. you can deploy your protocol to testnet or mainnet by just specifying a corresponding node connection when running the script. # Setup @@ -23,7 +23,7 @@ cargo generate --git https://github.com/Concordium/concordium-rust-smart-contrac Any of the two commands will work and will give you several templates to choose from. -- Choose the `templates/default` and answer the questions to complete the setup process. +- Choose the `templates/default` and answer the questions to complete the setup process (answer `default` for the two questions if you want to run the below example). At the end, you will have a Rust project setup with this boilerplate code included. @@ -41,8 +41,8 @@ The following options are necessary when running the script V2 API of the concordium node. [default: http://node.testnet.concordium.com:20000] --account Path to the Concordium account (with account keys). - --modules - A list of wasm modules. [default: [./default.wasm.v1]] + --modules + A list of wasm modules. ``` The `account` parameter should be a Concordium wallet account either exported from the @@ -51,7 +51,7 @@ genesis tool. Example: ``` -cargo run -- --node http://node.testnet.concordium.com:20000 --account ./3PXwJYYPf6fyVb4GJquxSZU8puxrHfzc4XogdMVot8MUQK53tW.export --modules ["./default.wasm.v1"] +cargo run -- --node http://node.testnet.concordium.com:20000 --account ./3PXwJYYPf6fyVb4GJquxSZU8puxrHfzc4XogdMVot8MUQK53tW.export --modules ./default.wasm.v1 --modules ./default2.wasm.v1 ``` # Functionalities @@ -59,25 +59,27 @@ cargo run -- --node http://node.testnet.concordium.com:20000 --account ./3PXwJYY The boilerplate code has support for the following functionalities: Read functions: -- `estimate_energy`: To estmiate the energy needed to execute one of the three write functions below. +- `estimate_energy`: To estimate the energy needed to execute one of the three write functions below. - `module_exists`: To check if a module has already been deployed on chain. - `get_nonce`: To get the current nonce of the provided wallet account. Write functions: - `deploy_wasm_module`: To deploy a new smart contract module on chain. -- `init_contract`: To initilization a smart contract instance on chain. -- `update_contract`: To updating a smart conract instance on chain. +- `init_contract`: To initialize a smart contract instance on chain. +- `update_contract`: To update a smart contract instance on chain. Event parsing helper functions: -- `parse_deploy_module_event`: To parse the chain events after deploying a modules. -- `parse_contract_init_event`: To parse the chain events after initilization a smart contract instance. -- `parse_contract_update_event`: To parse the chain events after updating a smart conract instance. +- `parse_deploy_module_event`: To parse the chain events after deploying a module. +- `parse_contract_init_event`: To parse the chain events after initialization of a smart contract instance. +- `parse_contract_update_event`: To parse the chain events after updating a smart contract instance. -The `main.rs` file has a section (marked with `// Write your own deployment/initialization script below. An example is given here.`) that you should replace with your custom logic to deploy, initilize, and update your smart contract protocol. +The `main.rs` file has a section (marked with `// Write your own deployment/initialization script below. An example is given here.`) that you should replace with your custom logic to deploy, initialize, and update your smart contract protocol. # Running the Example -The `main.rs` file has a section (marked with `// Write your own deployment/initialization script below. An example is given here.`) which provides an example that you can run. +The `main.rs` file has a section (marked with `// Write your own deployment/initialization script below. An example is given here.`) that provides an example that you can run. + +ATTENTION: You have to have created a smart contract with the name `default` to run the given example. This can be done by answering `default` to the two questions when creating the project via `cargo-generate`. Navigate into the root folder and compile the `default` smart contract with the command: ``` @@ -87,7 +89,7 @@ cargo concordium build --out ./deploy-scripts/default.wasm.v1 Then, deploy the `default` smart contract on chain (replace your wallet account in the below command): ``` -cargo run -- --node http://node.testnet.concordium.com:20000 --account ./3PXwJYYPf6fyVb4GJquxSZU8puxrHfzc4XogdMVot8MUQK53tW.export --modules ["./default.wasm.v1"] +cargo run -- --node http://node.testnet.concordium.com:20000 --account ./3PXwJYYPf6fyVb4GJquxSZU8puxrHfzc4XogdMVot8MUQK53tW.export --modules ./default.wasm.v1 ``` The output should be: diff --git a/templates/default/deploy-scripts/src/main.rs b/templates/default/deploy-scripts/src/main.rs index 1a1217ca..951ee43a 100644 --- a/templates/default/deploy-scripts/src/main.rs +++ b/templates/default/deploy-scripts/src/main.rs @@ -1,12 +1,10 @@ pub mod deployer; - use concordium_rust_sdk::smart_contracts::types::OwnedReceiveName; use concordium_rust_sdk::types::transactions; use concordium_rust_sdk::{ common::types::Amount, smart_contracts::common::{self as contracts_common}, }; - use anyhow::Context; use clap::Parser; use concordium_rust_sdk::smart_contracts::types::OwnedContractName; @@ -128,10 +126,14 @@ struct App { help = "Location of the Concordium account key file." )] key_file: PathBuf, + #[clap( + long = "modules", + help = "Location paths and names of Concordium smart contract modules. Use this flag several times \ + if you have several smart contract modules to be deployed (e.g. --modules ./default.wasm.v1 --modules ./default2.wasm.v1)" + )] + modules: Vec, } -const CONTRACTS: &[&str] = &["./default.wasm.v1"]; - #[tokio::main(flavor = "current_thread")] async fn main() -> Result<(), DeployError> { let app: App = App::parse(); @@ -142,7 +144,7 @@ async fn main() -> Result<(), DeployError> { let mut modules_deployed: Vec = Vec::new(); - for contract in CONTRACTS { + for contract in app.modules { let wasm_module = get_wasm_module(PathBuf::from(contract).as_path())?; let module = deployer.deploy_wasm_module(wasm_module).await?; From c38405a044240670b7fab772223d63f6963cf38a Mon Sep 17 00:00:00 2001 From: Doris Benda Date: Tue, 10 Oct 2023 11:58:04 +0300 Subject: [PATCH 05/20] Add comments --- templates/default/deploy-scripts/README.md | 21 ++- .../default/deploy-scripts/src/deployer.rs | 147 ++++++++++++++---- templates/default/deploy-scripts/src/main.rs | 90 ++++------- 3 files changed, 160 insertions(+), 98 deletions(-) diff --git a/templates/default/deploy-scripts/README.md b/templates/default/deploy-scripts/README.md index b9950c24..28f05f16 100644 --- a/templates/default/deploy-scripts/README.md +++ b/templates/default/deploy-scripts/README.md @@ -40,9 +40,10 @@ The following options are necessary when running the script --node V2 API of the concordium node. [default: http://node.testnet.concordium.com:20000] --account - Path to the Concordium account (with account keys). + Location path and file name of the Concordium account key file (e.g. ./myPath/3PXwJYYPf6fyVb4GJquxSZU8puxrHfzc4XogdMVot8MUQK53tW.export). --modules - A list of wasm modules. + Location paths and names of Concordium smart contract modules. Use this flag several times \ + if you have several smart contract modules to be deployed (e.g. --modules ./myPath/default.wasm.v1 --modules ./default2.wasm.v1). ``` The `account` parameter should be a Concordium wallet account either exported from the @@ -86,7 +87,7 @@ Navigate into the root folder and compile the `default` smart contract with the cargo concordium build --out ./deploy-scripts/default.wasm.v1 ``` -Then, deploy the `default` smart contract on chain (replace your wallet account in the below command): +Then, deploy, initialize, and update the `default` smart contract on chain (replace your wallet account in the below command): ``` cargo run -- --node http://node.testnet.concordium.com:20000 --account ./3PXwJYYPf6fyVb4GJquxSZU8puxrHfzc4XogdMVot8MUQK53tW.export --modules ./default.wasm.v1 @@ -95,7 +96,17 @@ cargo run -- --node http://node.testnet.concordium.com:20000 --account ./3PXwJYY The output should be: ``` -$ cargo run +Deploying module.... +Module with reference 15c936d9f60dc99c543282a8f16823d2ec5c6faae689772ae06a9b2de45a39d0 already exists on chain. -... +Initializing contract.... +Sent tx: 09ecaa6a66e4fe2a756dd9ad8c91f5fc2099a6dd30ebd4532cb8c5aad1bab440 +Transaction finalized, tx_hash=09ecaa6a66e4fe2a756dd9ad8c91f5fc2099a6dd30ebd4532cb8c5aad1bab440 contract=(6941, 0) + +Estimating energy.... +Contract invoke success: estimated_energy=731 + +Updating contract.... +Sent tx: c61b40a09e422835c70b07369bc5f4bba8292499be80cd735af21941c9798dd2 +Transaction finalized, tx_hash=c61b40a09e422835c70b07369bc5f4bba8292499be80cd735af21941c9798dd2 ``` diff --git a/templates/default/deploy-scripts/src/deployer.rs b/templates/default/deploy-scripts/src/deployer.rs index 7ede0b0f..0489891f 100644 --- a/templates/default/deploy-scripts/src/deployer.rs +++ b/templates/default/deploy-scripts/src/deployer.rs @@ -1,8 +1,11 @@ +use crate::v2::BlockIdentifier::Best; +use concordium_rust_sdk::types::hashes::TransactionMarker; use concordium_rust_sdk::{ common::types::TransactionTime, id::types::AccountAddress, smart_contracts::common::{Address, ModuleReference}, types::{ + hashes::HashBytes, queries::AccountNonceResponse, smart_contracts::{ContractContext, InvokeContractResult, WasmModule}, transactions::{ @@ -16,27 +19,34 @@ use concordium_rust_sdk::{ v2, }; use std::path::Path; -use crate::v2::BlockIdentifier::Best; use crate::DeployError; +/// A struct representing the deployed module on chain. #[derive(Clone, Debug)] pub struct ModuleDeployed { + /// Module reference on chain. pub module_ref: ModuleReference, } +/// A struct representing a smart contract instance on chain. #[derive(Clone, Debug)] pub struct ContractInitialized { + /// Smart contract address on chain. pub contract: ContractAddress, } +/// A struct containing connection and wallet information. #[derive(Debug)] pub struct Deployer { + /// The client to establish a connection to a Concordium node (V2 API). pub client: v2::Client, + /// The account keys to be used for sending transactions. pub key: WalletAccount, } impl Deployer { + /// A function to create a new deployer instance. pub fn new(client: v2::Client, wallet_account_file: &Path) -> Result { let key_data = WalletAccount::from_json_file(wallet_account_file)?; @@ -46,10 +56,14 @@ impl Deployer { }) } + /// A function to check if a module exists on chain. pub async fn module_exists(&self, wasm_module: WasmModule) -> Result { + let best_block = self + .client + .clone() + .get_block_finalization_summary(Best) + .await?; - let best_block = self.client.clone().get_block_finalization_summary(Best).await?; - let best_block_hash = best_block.block_hash; let module_ref = wasm_module.get_module_ref(); @@ -67,13 +81,17 @@ impl Deployer { } } + /// A function to deploy a wasm module on chain. The transaction hash and the module reference is returned. + /// If the module already exists on chain, this function returns the module reference of the already deployed module instead. + /// An optional expiry time for the transaction can be given. If `None` is provided, the local time + 300 seconds is used as a default expiry time. pub async fn deploy_wasm_module( &self, wasm_module: WasmModule, - ) -> Result { + expiry: Option, + ) -> Result<(Option>, ModuleDeployed), DeployError> { println!(); - println!("Deploying contract...."); + println!("Deploying module...."); let exists = self.module_exists(wasm_module.clone()).await?; @@ -82,9 +100,12 @@ impl Deployer { "Module with reference {} already exists on chain.", wasm_module.get_module_ref() ); - return Ok(ModuleDeployed { - module_ref: wasm_module.get_module_ref(), - }); + return Ok(( + None, + ModuleDeployed { + module_ref: wasm_module.get_module_ref(), + }, + )); } let nonce = self.get_nonce(self.key.address).await?; @@ -93,7 +114,10 @@ impl Deployer { return Err(DeployError::NonceNotFinal); } - let expiry = TransactionTime::from_seconds((chrono::Utc::now().timestamp() + 300) as u64); + let expiry = match expiry { + Some(expiry) => expiry, + None => TransactionTime::from_seconds((chrono::Utc::now().timestamp() + 300) as u64), + }; let tx = deploy_module( &self.key, @@ -111,6 +135,8 @@ impl Deployer { .await .map_err(DeployError::TransactionRejected)?; + println!("Sent tx: {tx_hash}"); + let (_, block_item) = self.client.clone().wait_until_finalized(&tx_hash).await?; let module_deployed = self.parse_deploy_module_event(block_item)?; @@ -120,13 +146,18 @@ impl Deployer { tx_hash, module_deployed.module_ref, ); - Ok(module_deployed) + Ok((Some(tx_hash), module_deployed)) } + /// A function to initialize a smart contract instance on chain. The transaction hash and the contract address is returned. + /// An optional energy for the transaction can be given. If `None` is provided, 5000 energy is used as a default energy value. + /// An optional expiry time for the transaction can be given. If `None` is provided, the local time + 300 seconds is used as a default expiry time. pub async fn init_contract( &self, payload: InitContractPayload, - ) -> Result { + energy: Option, + expiry: Option, + ) -> Result<(HashBytes, ContractAddress), DeployError> { println!(); println!("Initializing contract...."); @@ -137,8 +168,15 @@ impl Deployer { return Err(DeployError::NonceNotFinal); } - let expiry = TransactionTime::from_seconds((chrono::Utc::now().timestamp() + 300) as u64); - let energy = Energy { energy: 5000 }; + let energy = match energy { + Some(energy) => energy, + None => Energy { energy: 5000 }, + }; + + let expiry = match expiry { + Some(expiry) => expiry, + None => TransactionTime::from_seconds((chrono::Utc::now().timestamp() + 300) as u64), + }; let tx = init_contract( &self.key, @@ -169,14 +207,22 @@ impl Deployer { tx_hash, contract_init.contract.index, contract_init.contract.subindex, ); - Ok(contract_init.contract) + Ok((tx_hash, contract_init.contract)) } + /// A function to update a smart contract instance on chain. The transaction hash is returned. + /// An optional energy for the transaction can be given. If `None` is provided, 50000 energy is used as a default energy value. + /// An optional expiry time for the transaction can be given. If `None` is provided, the local time + 300 seconds is used as a default expiry time. pub async fn update_contract( &self, update_payload: UpdateContractPayload, - energy: GivenEnergy, - ) -> Result<(), DeployError> { + energy: Option, + expiry: Option, + ) -> Result, DeployError> { + println!(); + + println!("Updating contract...."); + let nonce = self.get_nonce(self.key.address).await?; if !nonce.all_final { @@ -187,7 +233,15 @@ impl Deployer { payload: update_payload, }; - let expiry = TransactionTime::from_seconds((chrono::Utc::now().timestamp() + 300) as u64); + let expiry = match expiry { + Some(expiry) => expiry, + None => TransactionTime::from_seconds((chrono::Utc::now().timestamp() + 300) as u64), + }; + + let energy = match energy { + Some(energy) => energy, + None => GivenEnergy::Absolute(Energy { energy: 50000 }), + }; let tx = transactions::send::make_and_sign_transaction( &self.key, @@ -205,27 +259,50 @@ impl Deployer { .send_block_item(&bi) .await .map_err(DeployError::TransactionRejected)?; + println!("Sent tx: {tx_hash}"); let (_, block_item) = self.client.clone().wait_until_finalized(&tx_hash).await?; self.parse_contract_update_event(block_item)?; - Ok(()) + println!("Transaction finalized, tx_hash={}", tx_hash,); + + Ok(tx_hash) } - pub async fn estimate_energy(&self, payload: UpdateContractPayload) -> Result { - let best_block = self.client.clone().get_block_finalization_summary(Best).await?; + /// A function to estimate the energy needed to send a transaction on chain. This function can be used to dry-run a transaction. + /// The transaction energy is returned. + /// An optional max energy for the transaction can be given. If `None` is provided, 50000 energy is used as a default max_energy value. + pub async fn estimate_energy( + &self, + payload: UpdateContractPayload, + max_energy: Option, + ) -> Result { + println!(); + + println!("Estimating energy...."); + + let best_block = self + .client + .clone() + .get_block_finalization_summary(Best) + .await?; let best_block_hash = best_block.block_hash; + let max_energy = match max_energy { + Some(energy) => energy, + None => Energy { energy: 50000 }, + }; + let context = ContractContext { invoker: Some(Address::Account(self.key.address)), contract: payload.address, amount: payload.amount, method: payload.receive_name, parameter: payload.message, - energy: 100000.into(), + energy: max_energy, }; let result = self @@ -240,7 +317,7 @@ impl Deployer { reason, used_energy, } => Err(DeployError::InvokeContractFailed(format!( - "contract invoke failed: {reason:?}, used_energy={used_energy}, return \ + "Contract invoke failed: {reason:?}, used_energy={used_energy}, return \ value={return_value:?}" ))), InvokeContractResult::Success { @@ -249,12 +326,13 @@ impl Deployer { used_energy, } => { let e = used_energy.energy; - println!("Estimated energy: {e}"); - Ok(Energy { energy: e + 100 }) + println!("Contract invoke success: estimated_energy={e}"); + Ok(Energy { energy: e }) } } } + /// A function to get the current nonce of the wallet account. pub async fn get_nonce( &self, address: AccountAddress, @@ -267,6 +345,7 @@ impl Deployer { Ok(nonce) } + /// A function to parse the deploy module events. fn parse_deploy_module_event( &self, block_item: BlockItemSummary, @@ -279,7 +358,7 @@ impl Deployer { } => { if transaction_type != Some(TransactionType::DeployModule) { return Err(DeployError::InvalidBlockItem( - "Expected transaction type to be DeployModule if rejected".into(), + "Expected transaction type to be DeployModule".into(), )); } @@ -288,7 +367,7 @@ impl Deployer { module_ref: contents, }), _ => Err(DeployError::TransactionRejectedR(format!( - "module deploy rejected with reason: {reject_reason:?}" + "Module deploy rejected with reason: {reject_reason:?}" ))), } } @@ -296,7 +375,7 @@ impl Deployer { Ok(ModuleDeployed { module_ref }) } _ => Err(DeployError::InvalidBlockItem( - "invalid transaction effects".into(), + "Invalid transaction effects".into(), )), }, _ => Err(DeployError::InvalidBlockItem( @@ -305,6 +384,7 @@ impl Deployer { } } + /// A function to parse the initialization events. fn parse_contract_init_event( &self, block_item: BlockItemSummary, @@ -317,12 +397,12 @@ impl Deployer { } => { if transaction_type != Some(TransactionType::InitContract) { return Err(DeployError::InvalidBlockItem( - "Expected transaction type to be InitContract if rejected".into(), + "Expected transaction type to be InitContract".into(), )); } Err(DeployError::TransactionRejectedR(format!( - "contract init rejected with reason: {reject_reason:?}" + "Contract init rejected with reason: {reject_reason:?}" ))) } AccountTransactionEffects::ContractInitialized { data } => { @@ -331,7 +411,7 @@ impl Deployer { }) } _ => Err(DeployError::InvalidBlockItem( - "invalid transaction effects".into(), + "Invalid transaction effects".into(), )), }, _ => Err(DeployError::InvalidBlockItem( @@ -340,6 +420,7 @@ impl Deployer { } } + /// A function to parse the contract update events. fn parse_contract_update_event(&self, block_item: BlockItemSummary) -> Result<(), DeployError> { match block_item.details { BlockItemSummaryDetails::AccountTransaction(a) => match a.effects { @@ -349,17 +430,17 @@ impl Deployer { } => { if transaction_type != Some(TransactionType::Update) { return Err(DeployError::InvalidBlockItem( - "Expected transaction type to be Update if rejected".into(), + "Expected transaction type to be Update".into(), )); } Err(DeployError::TransactionRejectedR(format!( - "contract update rejected with reason: {reject_reason:?}" + "Contract update rejected with reason: {reject_reason:?}" ))) } AccountTransactionEffects::ContractUpdateIssued { effects: _ } => Ok(()), _ => Err(DeployError::InvalidBlockItem( - "invalid transaction effects".into(), + "Invalid transaction effects".into(), )), }, _ => Err(DeployError::InvalidBlockItem( diff --git a/templates/default/deploy-scripts/src/main.rs b/templates/default/deploy-scripts/src/main.rs index 951ee43a..de2462d3 100644 --- a/templates/default/deploy-scripts/src/main.rs +++ b/templates/default/deploy-scripts/src/main.rs @@ -1,56 +1,31 @@ pub mod deployer; +use anyhow::Context; +use clap::Parser; +use concordium_rust_sdk::smart_contracts::types::OwnedContractName; +use concordium_rust_sdk::smart_contracts::types::OwnedParameter; use concordium_rust_sdk::smart_contracts::types::OwnedReceiveName; use concordium_rust_sdk::types::transactions; +use concordium_rust_sdk::types::transactions::send::GivenEnergy; use concordium_rust_sdk::{ common::types::Amount, smart_contracts::common::{self as contracts_common}, }; -use anyhow::Context; -use clap::Parser; -use concordium_rust_sdk::smart_contracts::types::OwnedContractName; -use concordium_rust_sdk::smart_contracts::types::OwnedParameter; use concordium_rust_sdk::{ endpoints::{self, RPCError}, smart_contracts::common::{NewContractNameError, NewReceiveNameError}, - types::{ - hashes::TransactionHash, - smart_contracts::{ExceedsParameterSize, ModuleReference, WasmModule}, - ContractAddress, - }, + types::smart_contracts::{ExceedsParameterSize, WasmModule}, v2, }; -use concordium_rust_sdk::types::transactions::send::GivenEnergy; use concordium_rust_sdk::types::transactions::InitContractPayload; use deployer::{Deployer, ModuleDeployed}; use hex::FromHexError; -use serde::{Deserialize, Serialize}; use std::{ io::Cursor, path::{Path, PathBuf}, }; use thiserror::Error; -#[derive(Deserialize, Clone, Debug)] -pub struct WrappedToken { - pub name: String, - pub token_metadata_url: String, - pub token_metadata_hash: TransactionHash, -} - -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct Output { - pub bridge_manager: ContractAddress, - pub tokens: Vec, -} - -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct OutputToken { - pub name: String, - pub token_url: String, - pub contract: ContractAddress, -} - #[derive(Error, Debug)] pub enum DeployError { #[error("concordium error: {0}")] @@ -93,47 +68,40 @@ pub enum DeployError { InvokeContractFailed(String), } -#[allow(dead_code)] -fn module_deployed(module_ref: &str) -> Result { - let mut bytes = [0u8; 32]; - hex::decode_to_slice(module_ref, &mut bytes)?; - - let module_deployed = ModuleDeployed { - module_ref: ModuleReference::from(bytes), - }; - - Ok(module_deployed) -} - +/// Reads the wasm module from a given file path and file name. fn get_wasm_module(file: &Path) -> Result { - let wasm_module = std::fs::read(file).context("Could not read the contract WASM file")?; + let wasm_module = std::fs::read(file).context("Could not read the WASM file")?; let mut cursor = Cursor::new(wasm_module); let wasm_module: WasmModule = concordium_rust_sdk::common::from_bytes(&mut cursor)?; Ok(wasm_module) } +/// Command line flags. #[derive(clap::Parser, Debug)] #[clap(author, version, about)] struct App { #[clap( long = "node", default_value = "http://node.testnet.concordium.com:20000", - help = "V2 API of the concordium node." + help = "V2 API of the Concordium node." )] url: v2::Endpoint, #[clap( long = "account", - help = "Location of the Concordium account key file." + help = "Location path and file name of the Concordium account key file (e.g. ./myPath/3PXwJYYPf6fyVb4GJquxSZU8puxrHfzc4XogdMVot8MUQK53tW.export)." )] key_file: PathBuf, #[clap( long = "modules", help = "Location paths and names of Concordium smart contract modules. Use this flag several times \ - if you have several smart contract modules to be deployed (e.g. --modules ./default.wasm.v1 --modules ./default2.wasm.v1)" + if you have several smart contract modules to be deployed (e.g. --modules ./myPath/default.wasm.v1 --modules ./default2.wasm.v1)." )] modules: Vec, } +/// Main function: It deploys to chain all wasm modules from the command line `--modules` flags. +/// Write your own custom deployment/initialization script in this function. +/// An deployment/initialization script example is given in this function for the `default` smart contract. #[tokio::main(flavor = "current_thread")] async fn main() -> Result<(), DeployError> { let app: App = App::parse(); @@ -147,43 +115,45 @@ async fn main() -> Result<(), DeployError> { for contract in app.modules { let wasm_module = get_wasm_module(PathBuf::from(contract).as_path())?; - let module = deployer.deploy_wasm_module(wasm_module).await?; + let (_, module) = deployer.deploy_wasm_module(wasm_module, None).await?; modules_deployed.push(module); } // Write your own deployment/initialization script below. An example is given here. - let param: OwnedParameter = OwnedParameter::default(); + let param: OwnedParameter = OwnedParameter::default(); // Example - let init_method_name: &str = "init_default"; + let init_method_name: &str = "init_default"; // Example let payload = InitContractPayload { init_name: OwnedContractName::new(init_method_name.into())?, amount: Amount::from_micro_ccd(0), mod_ref: modules_deployed[0].module_ref, param, - }; + }; // Example - let contract = deployer.init_contract(payload).await?; + let (_, contract) = deployer.init_contract(payload, None, None).await?; // Example - let bytes = contracts_common::to_bytes(&false); + let bytes = contracts_common::to_bytes(&false); // Example let update_payload = transactions::UpdateContractPayload { amount: Amount::from_ccd(0), address: contract, receive_name: OwnedReceiveName::new_unchecked("default.receive".to_string()), message: bytes.try_into()?, - }; + }; // Example + + let mut energy = deployer + .estimate_energy(update_payload.clone(), None) + .await?; // Example - let energy = deployer.estimate_energy(update_payload.clone()).await?; + // We add 100 energy to be save. + energy.energy = energy.energy + 100; // Example let _update_contract = deployer - .update_contract( - update_payload, - GivenEnergy::Add(energy), - ) - .await?; + .update_contract(update_payload, Some(GivenEnergy::Add(energy)), None) + .await?; // Example // Write your own deployment/initialization script above. An example is given here. From 2a9746f10a184a2a9c5fb2a035c925c69d3ab1f8 Mon Sep 17 00:00:00 2001 From: Doris Benda Date: Tue, 10 Oct 2023 12:10:25 +0300 Subject: [PATCH 06/20] Run linter --- templates/default/deploy-scripts/README.md | 8 +- .../default/deploy-scripts/src/deployer.rs | 172 ++++++++---------- templates/default/deploy-scripts/src/main.rs | 63 +++---- 3 files changed, 110 insertions(+), 133 deletions(-) diff --git a/templates/default/deploy-scripts/README.md b/templates/default/deploy-scripts/README.md index 28f05f16..40542c54 100644 --- a/templates/default/deploy-scripts/README.md +++ b/templates/default/deploy-scripts/README.md @@ -40,7 +40,7 @@ The following options are necessary when running the script --node V2 API of the concordium node. [default: http://node.testnet.concordium.com:20000] --account - Location path and file name of the Concordium account key file (e.g. ./myPath/3PXwJYYPf6fyVb4GJquxSZU8puxrHfzc4XogdMVot8MUQK53tW.export). + Location path and file name of the Concordium account key file (e.g. ./myPath/4SizPU2ipqQQza9Xa6fUkQBCDjyd1vTNUNDGbBeiRGpaJQc6qX.export). --modules Location paths and names of Concordium smart contract modules. Use this flag several times \ if you have several smart contract modules to be deployed (e.g. --modules ./myPath/default.wasm.v1 --modules ./default2.wasm.v1). @@ -52,7 +52,7 @@ genesis tool. Example: ``` -cargo run -- --node http://node.testnet.concordium.com:20000 --account ./3PXwJYYPf6fyVb4GJquxSZU8puxrHfzc4XogdMVot8MUQK53tW.export --modules ./default.wasm.v1 --modules ./default2.wasm.v1 +cargo run -- --node http://node.testnet.concordium.com:20000 --account ./myPath/4SizPU2ipqQQza9Xa6fUkQBCDjyd1vTNUNDGbBeiRGpaJQc6qX.export --modules ./myPath/default.wasm.v1 --modules ./default2.wasm.v1 ``` # Functionalities @@ -87,10 +87,10 @@ Navigate into the root folder and compile the `default` smart contract with the cargo concordium build --out ./deploy-scripts/default.wasm.v1 ``` -Then, deploy, initialize, and update the `default` smart contract on chain (replace your wallet account in the below command): +Navigate into the deploy-scripts folder and run the example with the `default` smart contract (replace your wallet account in the below command): ``` -cargo run -- --node http://node.testnet.concordium.com:20000 --account ./3PXwJYYPf6fyVb4GJquxSZU8puxrHfzc4XogdMVot8MUQK53tW.export --modules ./default.wasm.v1 +cargo run -- --node http://node.testnet.concordium.com:20000 --account ./4SizPU2ipqQQza9Xa6fUkQBCDjyd1vTNUNDGbBeiRGpaJQc6qX.export --modules ./default.wasm.v1 ``` The output should be: diff --git a/templates/default/deploy-scripts/src/deployer.rs b/templates/default/deploy-scripts/src/deployer.rs index 0489891f..8d84a25e 100644 --- a/templates/default/deploy-scripts/src/deployer.rs +++ b/templates/default/deploy-scripts/src/deployer.rs @@ -1,11 +1,10 @@ use crate::v2::BlockIdentifier::Best; -use concordium_rust_sdk::types::hashes::TransactionMarker; use concordium_rust_sdk::{ common::types::TransactionTime, id::types::AccountAddress, smart_contracts::common::{Address, ModuleReference}, types::{ - hashes::HashBytes, + hashes::{HashBytes, TransactionMarker}, queries::AccountNonceResponse, smart_contracts::{ContractContext, InvokeContractResult, WasmModule}, transactions::{ @@ -42,7 +41,7 @@ pub struct Deployer { /// The client to establish a connection to a Concordium node (V2 API). pub client: v2::Client, /// The account keys to be used for sending transactions. - pub key: WalletAccount, + pub key: WalletAccount, } impl Deployer { @@ -58,21 +57,13 @@ impl Deployer { /// A function to check if a module exists on chain. pub async fn module_exists(&self, wasm_module: WasmModule) -> Result { - let best_block = self - .client - .clone() - .get_block_finalization_summary(Best) - .await?; + let best_block = self.client.clone().get_block_finalization_summary(Best).await?; let best_block_hash = best_block.block_hash; let module_ref = wasm_module.get_module_ref(); - let module_ref = self - .client - .clone() - .get_module_source(&module_ref, &best_block_hash) - .await; + let module_ref = self.client.clone().get_module_source(&module_ref, &best_block_hash).await; match module_ref { Ok(_) => Ok(true), @@ -81,9 +72,12 @@ impl Deployer { } } - /// A function to deploy a wasm module on chain. The transaction hash and the module reference is returned. - /// If the module already exists on chain, this function returns the module reference of the already deployed module instead. - /// An optional expiry time for the transaction can be given. If `None` is provided, the local time + 300 seconds is used as a default expiry time. + /// A function to deploy a wasm module on chain. The transaction hash and + /// the module reference is returned. If the module already exists on + /// chain, this function returns the module reference of the already + /// deployed module instead. An optional expiry time for the transaction + /// can be given. If `None` is provided, the local time + 300 seconds is + /// used as a default expiry time. pub async fn deploy_wasm_module( &self, wasm_module: WasmModule, @@ -100,12 +94,9 @@ impl Deployer { "Module with reference {} already exists on chain.", wasm_module.get_module_ref() ); - return Ok(( - None, - ModuleDeployed { - module_ref: wasm_module.get_module_ref(), - }, - )); + return Ok((None, ModuleDeployed { + module_ref: wasm_module.get_module_ref(), + })); } let nonce = self.get_nonce(self.key.address).await?; @@ -119,13 +110,7 @@ impl Deployer { None => TransactionTime::from_seconds((chrono::Utc::now().timestamp() + 300) as u64), }; - let tx = deploy_module( - &self.key, - self.key.address, - nonce.nonce, - expiry, - wasm_module, - ); + let tx = deploy_module(&self.key, self.key.address, nonce.nonce, expiry, wasm_module); let bi = transactions::BlockItem::AccountTransaction(tx); let tx_hash = self @@ -149,9 +134,12 @@ impl Deployer { Ok((Some(tx_hash), module_deployed)) } - /// A function to initialize a smart contract instance on chain. The transaction hash and the contract address is returned. - /// An optional energy for the transaction can be given. If `None` is provided, 5000 energy is used as a default energy value. - /// An optional expiry time for the transaction can be given. If `None` is provided, the local time + 300 seconds is used as a default expiry time. + /// A function to initialize a smart contract instance on chain. The + /// transaction hash and the contract address is returned. An optional + /// energy for the transaction can be given. If `None` is provided, 5000 + /// energy is used as a default energy value. An optional expiry time + /// for the transaction can be given. If `None` is provided, the local time + /// + 300 seconds is used as a default expiry time. pub async fn init_contract( &self, payload: InitContractPayload, @@ -170,7 +158,9 @@ impl Deployer { let energy = match energy { Some(energy) => energy, - None => Energy { energy: 5000 }, + None => Energy { + energy: 5000, + }, }; let expiry = match expiry { @@ -178,14 +168,7 @@ impl Deployer { None => TransactionTime::from_seconds((chrono::Utc::now().timestamp() + 300) as u64), }; - let tx = init_contract( - &self.key, - self.key.address, - nonce.nonce, - expiry, - payload, - energy, - ); + let tx = init_contract(&self.key, self.key.address, nonce.nonce, expiry, payload, energy); let bi = transactions::BlockItem::AccountTransaction(tx); @@ -210,9 +193,12 @@ impl Deployer { Ok((tx_hash, contract_init.contract)) } - /// A function to update a smart contract instance on chain. The transaction hash is returned. - /// An optional energy for the transaction can be given. If `None` is provided, 50000 energy is used as a default energy value. - /// An optional expiry time for the transaction can be given. If `None` is provided, the local time + 300 seconds is used as a default expiry time. + /// A function to update a smart contract instance on chain. The transaction + /// hash is returned. An optional energy for the transaction can be + /// given. If `None` is provided, 50000 energy is used as a default energy + /// value. An optional expiry time for the transaction can be given. If + /// `None` is provided, the local time + 300 seconds is used as a default + /// expiry time. pub async fn update_contract( &self, update_payload: UpdateContractPayload, @@ -240,7 +226,9 @@ impl Deployer { let energy = match energy { Some(energy) => energy, - None => GivenEnergy::Absolute(Energy { energy: 50000 }), + None => GivenEnergy::Absolute(Energy { + energy: 50000, + }), }; let tx = transactions::send::make_and_sign_transaction( @@ -271,9 +259,11 @@ impl Deployer { Ok(tx_hash) } - /// A function to estimate the energy needed to send a transaction on chain. This function can be used to dry-run a transaction. - /// The transaction energy is returned. - /// An optional max energy for the transaction can be given. If `None` is provided, 50000 energy is used as a default max_energy value. + /// A function to estimate the energy needed to send a transaction on chain. + /// This function can be used to dry-run a transaction. The transaction + /// energy is returned. An optional max energy for the transaction can + /// be given. If `None` is provided, 50000 energy is used as a default + /// max_energy value. pub async fn estimate_energy( &self, payload: UpdateContractPayload, @@ -283,33 +273,27 @@ impl Deployer { println!("Estimating energy...."); - let best_block = self - .client - .clone() - .get_block_finalization_summary(Best) - .await?; + let best_block = self.client.clone().get_block_finalization_summary(Best).await?; let best_block_hash = best_block.block_hash; let max_energy = match max_energy { Some(energy) => energy, - None => Energy { energy: 50000 }, + None => Energy { + energy: 50000, + }, }; let context = ContractContext { - invoker: Some(Address::Account(self.key.address)), - contract: payload.address, - amount: payload.amount, - method: payload.receive_name, + invoker: Some(Address::Account(self.key.address)), + contract: payload.address, + amount: payload.amount, + method: payload.receive_name, parameter: payload.message, - energy: max_energy, + energy: max_energy, }; - let result = self - .client - .clone() - .invoke_instance(&best_block_hash, &context) - .await?; + let result = self.client.clone().invoke_instance(&best_block_hash, &context).await?; match result.response { InvokeContractResult::Failure { @@ -327,7 +311,9 @@ impl Deployer { } => { let e = used_energy.energy; println!("Contract invoke success: estimated_energy={e}"); - Ok(Energy { energy: e }) + Ok(Energy { + energy: e, + }) } } } @@ -337,11 +323,7 @@ impl Deployer { &self, address: AccountAddress, ) -> Result { - let nonce = self - .client - .clone() - .get_next_account_sequence_number(&address) - .await?; + let nonce = self.client.clone().get_next_account_sequence_number(&address).await?; Ok(nonce) } @@ -363,7 +345,9 @@ impl Deployer { } match reject_reason { - RejectReason::ModuleHashAlreadyExists { contents } => Ok(ModuleDeployed { + RejectReason::ModuleHashAlreadyExists { + contents, + } => Ok(ModuleDeployed { module_ref: contents, }), _ => Err(DeployError::TransactionRejectedR(format!( @@ -371,16 +355,14 @@ impl Deployer { ))), } } - AccountTransactionEffects::ModuleDeployed { module_ref } => { - Ok(ModuleDeployed { module_ref }) - } - _ => Err(DeployError::InvalidBlockItem( - "Invalid transaction effects".into(), - )), + AccountTransactionEffects::ModuleDeployed { + module_ref, + } => Ok(ModuleDeployed { + module_ref, + }), + _ => Err(DeployError::InvalidBlockItem("Invalid transaction effects".into())), }, - _ => Err(DeployError::InvalidBlockItem( - "Expected Account transaction".into(), - )), + _ => Err(DeployError::InvalidBlockItem("Expected Account transaction".into())), } } @@ -405,18 +387,14 @@ impl Deployer { "Contract init rejected with reason: {reject_reason:?}" ))) } - AccountTransactionEffects::ContractInitialized { data } => { - Ok(ContractInitialized { - contract: data.address, - }) - } - _ => Err(DeployError::InvalidBlockItem( - "Invalid transaction effects".into(), - )), + AccountTransactionEffects::ContractInitialized { + data, + } => Ok(ContractInitialized { + contract: data.address, + }), + _ => Err(DeployError::InvalidBlockItem("Invalid transaction effects".into())), }, - _ => Err(DeployError::InvalidBlockItem( - "Expected Account transaction".into(), - )), + _ => Err(DeployError::InvalidBlockItem("Expected Account transaction".into())), } } @@ -438,14 +416,12 @@ impl Deployer { "Contract update rejected with reason: {reject_reason:?}" ))) } - AccountTransactionEffects::ContractUpdateIssued { effects: _ } => Ok(()), - _ => Err(DeployError::InvalidBlockItem( - "Invalid transaction effects".into(), - )), + AccountTransactionEffects::ContractUpdateIssued { + effects: _, + } => Ok(()), + _ => Err(DeployError::InvalidBlockItem("Invalid transaction effects".into())), }, - _ => Err(DeployError::InvalidBlockItem( - "Expected Account transaction".into(), - )), + _ => Err(DeployError::InvalidBlockItem("Expected Account transaction".into())), } } } diff --git a/templates/default/deploy-scripts/src/main.rs b/templates/default/deploy-scripts/src/main.rs index de2462d3..7e69dac9 100644 --- a/templates/default/deploy-scripts/src/main.rs +++ b/templates/default/deploy-scripts/src/main.rs @@ -1,19 +1,18 @@ pub mod deployer; use anyhow::Context; use clap::Parser; -use concordium_rust_sdk::smart_contracts::types::OwnedContractName; -use concordium_rust_sdk::smart_contracts::types::OwnedParameter; -use concordium_rust_sdk::smart_contracts::types::OwnedReceiveName; -use concordium_rust_sdk::types::transactions; -use concordium_rust_sdk::types::transactions::send::GivenEnergy; use concordium_rust_sdk::{ common::types::Amount, - smart_contracts::common::{self as contracts_common}, -}; -use concordium_rust_sdk::{ endpoints::{self, RPCError}, - smart_contracts::common::{NewContractNameError, NewReceiveNameError}, - types::smart_contracts::{ExceedsParameterSize, WasmModule}, + smart_contracts::{ + common::{self as contracts_common, NewContractNameError, NewReceiveNameError}, + types::{OwnedContractName, OwnedParameter, OwnedReceiveName}, + }, + types::{ + smart_contracts::{ExceedsParameterSize, WasmModule}, + transactions, + transactions::send::GivenEnergy, + }, v2, }; @@ -85,23 +84,26 @@ struct App { default_value = "http://node.testnet.concordium.com:20000", help = "V2 API of the Concordium node." )] - url: v2::Endpoint, + url: v2::Endpoint, #[clap( long = "account", - help = "Location path and file name of the Concordium account key file (e.g. ./myPath/3PXwJYYPf6fyVb4GJquxSZU8puxrHfzc4XogdMVot8MUQK53tW.export)." + help = "Location path and file name of the Concordium account key file (e.g. \ + ./myPath/3PXwJYYPf6fyVb4GJquxSZU8puxrHfzc4XogdMVot8MUQK53tW.export)." )] key_file: PathBuf, #[clap( long = "modules", - help = "Location paths and names of Concordium smart contract modules. Use this flag several times \ - if you have several smart contract modules to be deployed (e.g. --modules ./myPath/default.wasm.v1 --modules ./default2.wasm.v1)." + help = "Location paths and names of Concordium smart contract modules. Use this flag \ + several times if you have several smart contract modules to be deployed (e.g. \ + --modules ./myPath/default.wasm.v1 --modules ./default2.wasm.v1)." )] - modules: Vec, + modules: Vec, } -/// Main function: It deploys to chain all wasm modules from the command line `--modules` flags. -/// Write your own custom deployment/initialization script in this function. -/// An deployment/initialization script example is given in this function for the `default` smart contract. +/// Main function: It deploys to chain all wasm modules from the command line +/// `--modules` flags. Write your own custom deployment/initialization script in +/// this function. An deployment/initialization script example is given in this +/// function for the `default` smart contract. #[tokio::main(flavor = "current_thread")] async fn main() -> Result<(), DeployError> { let app: App = App::parse(); @@ -113,14 +115,15 @@ async fn main() -> Result<(), DeployError> { let mut modules_deployed: Vec = Vec::new(); for contract in app.modules { - let wasm_module = get_wasm_module(PathBuf::from(contract).as_path())?; + let wasm_module = get_wasm_module(contract.as_path())?; let (_, module) = deployer.deploy_wasm_module(wasm_module, None).await?; modules_deployed.push(module); } - // Write your own deployment/initialization script below. An example is given here. + // Write your own deployment/initialization script below. An example is given + // here. let param: OwnedParameter = OwnedParameter::default(); // Example @@ -138,24 +141,22 @@ async fn main() -> Result<(), DeployError> { let bytes = contracts_common::to_bytes(&false); // Example let update_payload = transactions::UpdateContractPayload { - amount: Amount::from_ccd(0), - address: contract, + amount: Amount::from_ccd(0), + address: contract, receive_name: OwnedReceiveName::new_unchecked("default.receive".to_string()), - message: bytes.try_into()?, + message: bytes.try_into()?, }; // Example - let mut energy = deployer - .estimate_energy(update_payload.clone(), None) - .await?; // Example + let mut energy = deployer.estimate_energy(update_payload.clone(), None).await?; // Example // We add 100 energy to be save. - energy.energy = energy.energy + 100; // Example + energy.energy += 100; // Example - let _update_contract = deployer - .update_contract(update_payload, Some(GivenEnergy::Add(energy)), None) - .await?; // Example + let _update_contract = + deployer.update_contract(update_payload, Some(GivenEnergy::Add(energy)), None).await?; // Example - // Write your own deployment/initialization script above. An example is given here. + // Write your own deployment/initialization script above. An example is given + // here. Ok(()) } From bb110f9a22c90a5656c9d4832171d5035dc0bfd8 Mon Sep 17 00:00:00 2001 From: Doris Benda Date: Wed, 11 Oct 2023 13:15:38 +0300 Subject: [PATCH 07/20] Address comments --- templates/default/deploy-scripts/Cargo.toml | 1 + templates/default/deploy-scripts/README.md | 16 +-- .../default/deploy-scripts/src/deployer.rs | 112 +++++++++++------- templates/default/deploy-scripts/src/main.rs | 5 +- 4 files changed, 84 insertions(+), 50 deletions(-) diff --git a/templates/default/deploy-scripts/Cargo.toml b/templates/default/deploy-scripts/Cargo.toml index 119a762a..5c2bae7d 100644 --- a/templates/default/deploy-scripts/Cargo.toml +++ b/templates/default/deploy-scripts/Cargo.toml @@ -17,4 +17,5 @@ thiserror = "1.0" tokio = {version = "1.18", features = ["rt", "macros"]} clap = { version = "4", features = ["derive", "env"]} concordium-rust-sdk="2.4" +itertools = "0.11.0" diff --git a/templates/default/deploy-scripts/README.md b/templates/default/deploy-scripts/README.md index 40542c54..f168c1c9 100644 --- a/templates/default/deploy-scripts/README.md +++ b/templates/default/deploy-scripts/README.md @@ -4,7 +4,7 @@ This project has boilerplate code to write deployment, initialization, and updat # Purpose -Automatic scripts are useful to speed up the development and testing of your protocol on chain. +Automatic scripts are useful to speed up the development and testing of your protocol on the chain. In addition, scripts help to set up identical protocols on different chains easily. E.g. you can deploy your protocol to testnet or mainnet by just specifying a corresponding node connection when running the script. # Setup @@ -29,7 +29,7 @@ At the end, you will have a Rust project setup with this boilerplate code includ # Running The Script -Build and run the script from the root folder using +Build and run the script from the deploy-scripts folder using ``` cargo run ``` @@ -61,20 +61,20 @@ The boilerplate code has support for the following functionalities: Read functions: - `estimate_energy`: To estimate the energy needed to execute one of the three write functions below. -- `module_exists`: To check if a module has already been deployed on chain. +- `module_exists`: To check if a module has already been deployed on the chain. - `get_nonce`: To get the current nonce of the provided wallet account. Write functions: -- `deploy_wasm_module`: To deploy a new smart contract module on chain. -- `init_contract`: To initialize a smart contract instance on chain. -- `update_contract`: To update a smart contract instance on chain. +- `deploy_wasm_module`: To deploy a new smart contract module on the chain. +- `init_contract`: To initialize a smart contract instance on the chain. +- `update_contract`: To update a smart contract instance on the chain. Event parsing helper functions: - `parse_deploy_module_event`: To parse the chain events after deploying a module. - `parse_contract_init_event`: To parse the chain events after initialization of a smart contract instance. - `parse_contract_update_event`: To parse the chain events after updating a smart contract instance. -The `main.rs` file has a section (marked with `// Write your own deployment/initialization script below. An example is given here.`) that you should replace with your custom logic to deploy, initialize, and update your smart contract protocol. +The `main.rs` file has a section (marked with `// Write your own deployment/initialization script below. An example is given here.`) that you should replace with your custom logic. You can write your script using `deploy`, `initialize`, and contract `update` transactions. # Running the Example @@ -97,7 +97,7 @@ The output should be: ``` Deploying module.... -Module with reference 15c936d9f60dc99c543282a8f16823d2ec5c6faae689772ae06a9b2de45a39d0 already exists on chain. +Module with reference 15c936d9f60dc99c543282a8f16823d2ec5c6faae689772ae06a9b2de45a39d0 already exists on the chain. Initializing contract.... Sent tx: 09ecaa6a66e4fe2a756dd9ad8c91f5fc2099a6dd30ebd4532cb8c5aad1bab440 diff --git a/templates/default/deploy-scripts/src/deployer.rs b/templates/default/deploy-scripts/src/deployer.rs index 8d84a25e..e414975e 100644 --- a/templates/default/deploy-scripts/src/deployer.rs +++ b/templates/default/deploy-scripts/src/deployer.rs @@ -21,17 +21,17 @@ use std::path::Path; use crate::DeployError; -/// A struct representing the deployed module on chain. +/// A struct representing the deployed module on the chain. #[derive(Clone, Debug)] pub struct ModuleDeployed { - /// Module reference on chain. + /// Module reference on the chain. pub module_ref: ModuleReference, } -/// A struct representing a smart contract instance on chain. +/// A struct representing a smart contract instance on the chain. #[derive(Clone, Debug)] pub struct ContractInitialized { - /// Smart contract address on chain. + /// Smart contract address on the chain. pub contract: ContractAddress, } @@ -55,7 +55,7 @@ impl Deployer { }) } - /// A function to check if a module exists on chain. + /// A function to check if a module exists on the chain. pub async fn module_exists(&self, wasm_module: WasmModule) -> Result { let best_block = self.client.clone().get_block_finalization_summary(Best).await?; @@ -63,19 +63,24 @@ impl Deployer { let module_ref = wasm_module.get_module_ref(); - let module_ref = self.client.clone().get_module_source(&module_ref, &best_block_hash).await; + let module_src = self.client.clone().get_module_source(&module_ref, &best_block_hash).await; - match module_ref { + match module_src { Ok(_) => Ok(true), Err(e) if e.is_not_found() => Ok(false), Err(e) => Err(e.into()), } } - /// A function to deploy a wasm module on chain. The transaction hash and - /// the module reference is returned. If the module already exists on + /// A function to deploy a wasm module on the chain. + /// + /// If successful, the transaction hash and + /// the module reference is returned. + /// If the module already exists on /// chain, this function returns the module reference of the already - /// deployed module instead. An optional expiry time for the transaction + /// deployed module instead. + /// + /// An optional expiry time for the transaction /// can be given. If `None` is provided, the local time + 300 seconds is /// used as a default expiry time. pub async fn deploy_wasm_module( @@ -83,15 +88,13 @@ impl Deployer { wasm_module: WasmModule, expiry: Option, ) -> Result<(Option>, ModuleDeployed), DeployError> { - println!(); - - println!("Deploying module...."); + println!("\nDeploying module...."); let exists = self.module_exists(wasm_module.clone()).await?; if exists { println!( - "Module with reference {} already exists on chain.", + "Module with reference {} already exists on the chain.", wasm_module.get_module_ref() ); return Ok((None, ModuleDeployed { @@ -134,21 +137,22 @@ impl Deployer { Ok((Some(tx_hash), module_deployed)) } - /// A function to initialize a smart contract instance on chain. The - /// transaction hash and the contract address is returned. An optional - /// energy for the transaction can be given. If `None` is provided, 5000 - /// energy is used as a default energy value. An optional expiry time - /// for the transaction can be given. If `None` is provided, the local time - /// + 300 seconds is used as a default expiry time. + /// A function to initialize a smart contract instance on the chain. + /// + /// If successful, the transaction hash and the contract address is + /// returned. + /// + /// An optional energy for the transaction can be given. If `None` is + /// provided, 5000 energy is used as a default energy value. An optional + /// expiry time for the transaction can be given. If `None` is provided, + /// the local time + 300 seconds is used as a default expiry time. pub async fn init_contract( &self, payload: InitContractPayload, energy: Option, expiry: Option, ) -> Result<(HashBytes, ContractAddress), DeployError> { - println!(); - - println!("Initializing contract...."); + println!("\nInitializing contract...."); let nonce = self.get_nonce(self.key.address).await?; @@ -193,8 +197,12 @@ impl Deployer { Ok((tx_hash, contract_init.contract)) } - /// A function to update a smart contract instance on chain. The transaction - /// hash is returned. An optional energy for the transaction can be + /// A function to update a smart contract instance on the chain. + /// + /// If successful, the transaction + /// hash is returned. + /// + /// An optional energy for the transaction can be /// given. If `None` is provided, 50000 energy is used as a default energy /// value. An optional expiry time for the transaction can be given. If /// `None` is provided, the local time + 300 seconds is used as a default @@ -205,9 +213,7 @@ impl Deployer { energy: Option, expiry: Option, ) -> Result, DeployError> { - println!(); - - println!("Updating contract...."); + println!("\nUpdating contract...."); let nonce = self.get_nonce(self.key.address).await?; @@ -259,9 +265,13 @@ impl Deployer { Ok(tx_hash) } - /// A function to estimate the energy needed to send a transaction on chain. - /// This function can be used to dry-run a transaction. The transaction - /// energy is returned. An optional max energy for the transaction can + /// A function to estimate the energy needed to send a transaction on the + /// chain. + /// + /// If successful, the transaction energy is returned by this function. + /// This function can be used to dry-run a transaction. + /// + /// An optional max_energy value for the transaction can /// be given. If `None` is provided, 50000 energy is used as a default /// max_energy value. pub async fn estimate_energy( @@ -269,9 +279,7 @@ impl Deployer { payload: UpdateContractPayload, max_energy: Option, ) -> Result { - println!(); - - println!("Estimating energy...."); + println!("\nEstimating energy...."); let best_block = self.client.clone().get_block_finalization_summary(Best).await?; @@ -360,9 +368,17 @@ impl Deployer { } => Ok(ModuleDeployed { module_ref, }), - _ => Err(DeployError::InvalidBlockItem("Invalid transaction effects".into())), + _ => Err(DeployError::InvalidBlockItem( + "The parsed account transaction effect should be of type `ModuleDeployed` or \ + `None` (in case the transaction reverted)" + .into(), + )), }, - _ => Err(DeployError::InvalidBlockItem("Expected Account transaction".into())), + _ => Err(DeployError::InvalidBlockItem( + "Can only parse an account transaction (no account creation transaction or chain \ + update transaction)" + .into(), + )), } } @@ -392,9 +408,17 @@ impl Deployer { } => Ok(ContractInitialized { contract: data.address, }), - _ => Err(DeployError::InvalidBlockItem("Invalid transaction effects".into())), + _ => Err(DeployError::InvalidBlockItem( + "The parsed account transaction effect should be of type \ + `ContractInitialized` or `None` (in case the transaction reverted)" + .into(), + )), }, - _ => Err(DeployError::InvalidBlockItem("Expected Account transaction".into())), + _ => Err(DeployError::InvalidBlockItem( + "Can only parse an account transaction (no account creation transaction or chain \ + update transaction)" + .into(), + )), } } @@ -419,9 +443,17 @@ impl Deployer { AccountTransactionEffects::ContractUpdateIssued { effects: _, } => Ok(()), - _ => Err(DeployError::InvalidBlockItem("Invalid transaction effects".into())), + _ => Err(DeployError::InvalidBlockItem( + "The parsed account transaction effect should be of type \ + `ContractUpdateIssued` or `None` (in case the transaction reverted)" + .into(), + )), }, - _ => Err(DeployError::InvalidBlockItem("Expected Account transaction".into())), + _ => Err(DeployError::InvalidBlockItem( + "Can only parse an account transaction (no account creation transaction or chain \ + update transaction)" + .into(), + )), } } } diff --git a/templates/default/deploy-scripts/src/main.rs b/templates/default/deploy-scripts/src/main.rs index 7e69dac9..0c3f86e5 100644 --- a/templates/default/deploy-scripts/src/main.rs +++ b/templates/default/deploy-scripts/src/main.rs @@ -15,6 +15,7 @@ use concordium_rust_sdk::{ }, v2, }; +use itertools::Itertools; use concordium_rust_sdk::types::transactions::InitContractPayload; use deployer::{Deployer, ModuleDeployed}; @@ -114,7 +115,7 @@ async fn main() -> Result<(), DeployError> { let mut modules_deployed: Vec = Vec::new(); - for contract in app.modules { + for contract in app.modules.iter().unique() { let wasm_module = get_wasm_module(contract.as_path())?; let (_, module) = deployer.deploy_wasm_module(wasm_module, None).await?; @@ -149,7 +150,7 @@ async fn main() -> Result<(), DeployError> { let mut energy = deployer.estimate_energy(update_payload.clone(), None).await?; // Example - // We add 100 energy to be save. + // We add 100 energy to be safe. energy.energy += 100; // Example let _update_contract = From 94c1859eb506dfe7dc40cd8a265d73b0fec8a513 Mon Sep 17 00:00:00 2001 From: Doris Benda Date: Fri, 13 Oct 2023 09:57:45 +0300 Subject: [PATCH 08/20] Remove cloning of client --- templates/default/deploy-scripts/README.md | 20 ----- .../default/deploy-scripts/src/deployer.rs | 87 +++++++++---------- templates/default/deploy-scripts/src/main.rs | 4 +- 3 files changed, 41 insertions(+), 70 deletions(-) diff --git a/templates/default/deploy-scripts/README.md b/templates/default/deploy-scripts/README.md index f168c1c9..c0a3e9e9 100644 --- a/templates/default/deploy-scripts/README.md +++ b/templates/default/deploy-scripts/README.md @@ -7,26 +7,6 @@ This project has boilerplate code to write deployment, initialization, and updat Automatic scripts are useful to speed up the development and testing of your protocol on the chain. In addition, scripts help to set up identical protocols on different chains easily. E.g. you can deploy your protocol to testnet or mainnet by just specifying a corresponding node connection when running the script. -# Setup - -Option 1: - -``` -cargo concordium init -``` - -Option 2 (alternative command): - -``` -cargo generate --git https://github.com/Concordium/concordium-rust-smart-contracts.git -``` - -Any of the two commands will work and will give you several templates to choose from. - -- Choose the `templates/default` and answer the questions to complete the setup process (answer `default` for the two questions if you want to run the below example). - -At the end, you will have a Rust project setup with this boilerplate code included. - # Running The Script Build and run the script from the deploy-scripts folder using diff --git a/templates/default/deploy-scripts/src/deployer.rs b/templates/default/deploy-scripts/src/deployer.rs index e414975e..493c053b 100644 --- a/templates/default/deploy-scripts/src/deployer.rs +++ b/templates/default/deploy-scripts/src/deployer.rs @@ -37,16 +37,19 @@ pub struct ContractInitialized { /// A struct containing connection and wallet information. #[derive(Debug)] -pub struct Deployer { +pub struct Deployer<'a> { /// The client to establish a connection to a Concordium node (V2 API). - pub client: v2::Client, + pub client: &'a mut v2::Client, /// The account keys to be used for sending transactions. pub key: WalletAccount, } -impl Deployer { +impl<'a> Deployer<'a> { /// A function to create a new deployer instance. - pub fn new(client: v2::Client, wallet_account_file: &Path) -> Result { + pub fn new( + client: &'a mut v2::Client, + wallet_account_file: &Path, + ) -> Result, DeployError> { let key_data = WalletAccount::from_json_file(wallet_account_file)?; Ok(Deployer { @@ -56,14 +59,14 @@ impl Deployer { } /// A function to check if a module exists on the chain. - pub async fn module_exists(&self, wasm_module: WasmModule) -> Result { - let best_block = self.client.clone().get_block_finalization_summary(Best).await?; + pub async fn module_exists(&mut self, wasm_module: WasmModule) -> Result { + let best_block = self.client.get_block_finalization_summary(Best).await?; let best_block_hash = best_block.block_hash; let module_ref = wasm_module.get_module_ref(); - let module_src = self.client.clone().get_module_source(&module_ref, &best_block_hash).await; + let module_src = self.client.get_module_source(&module_ref, &best_block_hash).await; match module_src { Ok(_) => Ok(true), @@ -84,7 +87,7 @@ impl Deployer { /// can be given. If `None` is provided, the local time + 300 seconds is /// used as a default expiry time. pub async fn deploy_wasm_module( - &self, + &mut self, wasm_module: WasmModule, expiry: Option, ) -> Result<(Option>, ModuleDeployed), DeployError> { @@ -108,10 +111,9 @@ impl Deployer { return Err(DeployError::NonceNotFinal); } - let expiry = match expiry { - Some(expiry) => expiry, - None => TransactionTime::from_seconds((chrono::Utc::now().timestamp() + 300) as u64), - }; + let expiry = expiry.unwrap_or(TransactionTime::from_seconds( + (chrono::Utc::now().timestamp() + 300) as u64, + )); let tx = deploy_module(&self.key, self.key.address, nonce.nonce, expiry, wasm_module); let bi = transactions::BlockItem::AccountTransaction(tx); @@ -125,7 +127,7 @@ impl Deployer { println!("Sent tx: {tx_hash}"); - let (_, block_item) = self.client.clone().wait_until_finalized(&tx_hash).await?; + let (_, block_item) = self.client.wait_until_finalized(&tx_hash).await?; let module_deployed = self.parse_deploy_module_event(block_item)?; @@ -147,7 +149,7 @@ impl Deployer { /// expiry time for the transaction can be given. If `None` is provided, /// the local time + 300 seconds is used as a default expiry time. pub async fn init_contract( - &self, + &mut self, payload: InitContractPayload, energy: Option, expiry: Option, @@ -160,17 +162,13 @@ impl Deployer { return Err(DeployError::NonceNotFinal); } - let energy = match energy { - Some(energy) => energy, - None => Energy { - energy: 5000, - }, - }; + let energy = energy.unwrap_or(Energy { + energy: 5000, + }); - let expiry = match expiry { - Some(expiry) => expiry, - None => TransactionTime::from_seconds((chrono::Utc::now().timestamp() + 300) as u64), - }; + let expiry = expiry.unwrap_or(TransactionTime::from_seconds( + (chrono::Utc::now().timestamp() + 300) as u64, + )); let tx = init_contract(&self.key, self.key.address, nonce.nonce, expiry, payload, energy); @@ -185,7 +183,7 @@ impl Deployer { println!("Sent tx: {tx_hash}"); - let (_, block_item) = self.client.clone().wait_until_finalized(&tx_hash).await?; + let (_, block_item) = self.client.wait_until_finalized(&tx_hash).await?; let contract_init = self.parse_contract_init_event(block_item)?; @@ -208,7 +206,7 @@ impl Deployer { /// `None` is provided, the local time + 300 seconds is used as a default /// expiry time. pub async fn update_contract( - &self, + &mut self, update_payload: UpdateContractPayload, energy: Option, expiry: Option, @@ -225,17 +223,13 @@ impl Deployer { payload: update_payload, }; - let expiry = match expiry { - Some(expiry) => expiry, - None => TransactionTime::from_seconds((chrono::Utc::now().timestamp() + 300) as u64), - }; + let expiry = expiry.unwrap_or(TransactionTime::from_seconds( + (chrono::Utc::now().timestamp() + 300) as u64, + )); - let energy = match energy { - Some(energy) => energy, - None => GivenEnergy::Absolute(Energy { - energy: 50000, - }), - }; + let energy = energy.unwrap_or(GivenEnergy::Absolute(Energy { + energy: 50000, + })); let tx = transactions::send::make_and_sign_transaction( &self.key, @@ -256,7 +250,7 @@ impl Deployer { println!("Sent tx: {tx_hash}"); - let (_, block_item) = self.client.clone().wait_until_finalized(&tx_hash).await?; + let (_, block_item) = self.client.wait_until_finalized(&tx_hash).await?; self.parse_contract_update_event(block_item)?; @@ -275,22 +269,19 @@ impl Deployer { /// be given. If `None` is provided, 50000 energy is used as a default /// max_energy value. pub async fn estimate_energy( - &self, + &mut self, payload: UpdateContractPayload, max_energy: Option, ) -> Result { println!("\nEstimating energy...."); - let best_block = self.client.clone().get_block_finalization_summary(Best).await?; + let best_block = self.client.get_block_finalization_summary(Best).await?; let best_block_hash = best_block.block_hash; - let max_energy = match max_energy { - Some(energy) => energy, - None => Energy { - energy: 50000, - }, - }; + let max_energy = max_energy.unwrap_or(Energy { + energy: 50000, + }); let context = ContractContext { invoker: Some(Address::Account(self.key.address)), @@ -301,7 +292,7 @@ impl Deployer { energy: max_energy, }; - let result = self.client.clone().invoke_instance(&best_block_hash, &context).await?; + let result = self.client.invoke_instance(&best_block_hash, &context).await?; match result.response { InvokeContractResult::Failure { @@ -328,10 +319,10 @@ impl Deployer { /// A function to get the current nonce of the wallet account. pub async fn get_nonce( - &self, + &mut self, address: AccountAddress, ) -> Result { - let nonce = self.client.clone().get_next_account_sequence_number(&address).await?; + let nonce = self.client.get_next_account_sequence_number(&address).await?; Ok(nonce) } diff --git a/templates/default/deploy-scripts/src/main.rs b/templates/default/deploy-scripts/src/main.rs index 0c3f86e5..f019316d 100644 --- a/templates/default/deploy-scripts/src/main.rs +++ b/templates/default/deploy-scripts/src/main.rs @@ -109,9 +109,9 @@ struct App { async fn main() -> Result<(), DeployError> { let app: App = App::parse(); - let concordium_client = v2::Client::new(app.url).await?; + let mut concordium_client = v2::Client::new(app.url).await?; - let deployer = Deployer::new(concordium_client, &app.key_file)?; + let mut deployer = Deployer::new(&mut concordium_client, &app.key_file)?; let mut modules_deployed: Vec = Vec::new(); From 0598217677bc4dcf5a7bedb040326d93222f55d8 Mon Sep 17 00:00:00 2001 From: Doris Benda Date: Fri, 13 Oct 2023 15:39:03 +0300 Subject: [PATCH 09/20] Address comments --- templates/default/deploy-scripts/README.md | 10 ++-- .../default/deploy-scripts/src/deployer.rs | 53 ++++++++++--------- templates/default/deploy-scripts/src/main.rs | 4 +- 3 files changed, 34 insertions(+), 33 deletions(-) diff --git a/templates/default/deploy-scripts/README.md b/templates/default/deploy-scripts/README.md index c0a3e9e9..376138b3 100644 --- a/templates/default/deploy-scripts/README.md +++ b/templates/default/deploy-scripts/README.md @@ -49,10 +49,10 @@ Write functions: - `init_contract`: To initialize a smart contract instance on the chain. - `update_contract`: To update a smart contract instance on the chain. -Event parsing helper functions: -- `parse_deploy_module_event`: To parse the chain events after deploying a module. -- `parse_contract_init_event`: To parse the chain events after initialization of a smart contract instance. -- `parse_contract_update_event`: To parse the chain events after updating a smart contract instance. +Helper functions to check the outcome of the transactions: +- `check_outcome_of_deploy_transaction`: To check the outcome of a deploy module transaction. +- `check_outcome_of_initialization_transaction`: To check the outcome of a smart contract instance initialization transaction. +- `check_outcome_of_update_transaction`: To check the outcome of an update smart contract instance transaction. The `main.rs` file has a section (marked with `// Write your own deployment/initialization script below. An example is given here.`) that you should replace with your custom logic. You can write your script using `deploy`, `initialize`, and contract `update` transactions. @@ -60,8 +60,6 @@ The `main.rs` file has a section (marked with `// Write your own deployment/init The `main.rs` file has a section (marked with `// Write your own deployment/initialization script below. An example is given here.`) that provides an example that you can run. -ATTENTION: You have to have created a smart contract with the name `default` to run the given example. This can be done by answering `default` to the two questions when creating the project via `cargo-generate`. - Navigate into the root folder and compile the `default` smart contract with the command: ``` cargo concordium build --out ./deploy-scripts/default.wasm.v1 diff --git a/templates/default/deploy-scripts/src/deployer.rs b/templates/default/deploy-scripts/src/deployer.rs index 493c053b..14c03831 100644 --- a/templates/default/deploy-scripts/src/deployer.rs +++ b/templates/default/deploy-scripts/src/deployer.rs @@ -13,7 +13,7 @@ use concordium_rust_sdk::{ InitContractPayload, UpdateContractPayload, }, AccountTransactionEffects, BlockItemSummary, BlockItemSummaryDetails, ContractAddress, - Energy, RejectReason, TransactionType, WalletAccount, + Energy, TransactionType, WalletAccount, }, v2, }; @@ -59,14 +59,15 @@ impl<'a> Deployer<'a> { } /// A function to check if a module exists on the chain. - pub async fn module_exists(&mut self, wasm_module: WasmModule) -> Result { + pub async fn module_exists( + &mut self, + module_reference: &ModuleReference, + ) -> Result { let best_block = self.client.get_block_finalization_summary(Best).await?; let best_block_hash = best_block.block_hash; - let module_ref = wasm_module.get_module_ref(); - - let module_src = self.client.get_module_source(&module_ref, &best_block_hash).await; + let module_src = self.client.get_module_source(module_reference, &best_block_hash).await; match module_src { Ok(_) => Ok(true), @@ -93,7 +94,7 @@ impl<'a> Deployer<'a> { ) -> Result<(Option>, ModuleDeployed), DeployError> { println!("\nDeploying module...."); - let exists = self.module_exists(wasm_module.clone()).await?; + let exists = self.module_exists(&wasm_module.get_module_ref()).await?; if exists { println!( @@ -129,7 +130,7 @@ impl<'a> Deployer<'a> { let (_, block_item) = self.client.wait_until_finalized(&tx_hash).await?; - let module_deployed = self.parse_deploy_module_event(block_item)?; + let module_deployed = self.check_outcome_of_deploy_transaction(block_item)?; println!( "Transaction finalized, tx_hash={} module_ref={}", @@ -185,7 +186,7 @@ impl<'a> Deployer<'a> { let (_, block_item) = self.client.wait_until_finalized(&tx_hash).await?; - let contract_init = self.parse_contract_init_event(block_item)?; + let contract_init = self.check_outcome_of_initialization_transaction(block_item)?; println!( "Transaction finalized, tx_hash={} contract=({}, {})", @@ -252,7 +253,7 @@ impl<'a> Deployer<'a> { let (_, block_item) = self.client.wait_until_finalized(&tx_hash).await?; - self.parse_contract_update_event(block_item)?; + self.check_outcome_of_update_transaction(block_item)?; println!("Transaction finalized, tx_hash={}", tx_hash,); @@ -326,8 +327,10 @@ impl<'a> Deployer<'a> { Ok(nonce) } - /// A function to parse the deploy module events. - fn parse_deploy_module_event( + /// A function that checks the outcome of the deploy transaction. + /// It throws if the `block_item` is not a deploy transaction. + /// It returns the error code if the transaction reverted. + fn check_outcome_of_deploy_transaction( &self, block_item: BlockItemSummary, ) -> Result { @@ -343,16 +346,9 @@ impl<'a> Deployer<'a> { )); } - match reject_reason { - RejectReason::ModuleHashAlreadyExists { - contents, - } => Ok(ModuleDeployed { - module_ref: contents, - }), - _ => Err(DeployError::TransactionRejectedR(format!( - "Module deploy rejected with reason: {reject_reason:?}" - ))), - } + Err(DeployError::TransactionRejectedR(format!( + "Module deploy rejected with reason: {reject_reason:?}" + ))) } AccountTransactionEffects::ModuleDeployed { module_ref, @@ -373,8 +369,10 @@ impl<'a> Deployer<'a> { } } - /// A function to parse the initialization events. - fn parse_contract_init_event( + /// A function that checks the outcome of the initialization transaction. + /// It throws if the `block_item` is not a initialization transaction. + /// It returns the error code if the transaction reverted. + fn check_outcome_of_initialization_transaction( &self, block_item: BlockItemSummary, ) -> Result { @@ -413,8 +411,13 @@ impl<'a> Deployer<'a> { } } - /// A function to parse the contract update events. - fn parse_contract_update_event(&self, block_item: BlockItemSummary) -> Result<(), DeployError> { + /// A function that checks the outcome of the update transaction. + /// It throws if the `block_item` is not a update transaction. + /// It returns the error code if the transaction reverted. + fn check_outcome_of_update_transaction( + &self, + block_item: BlockItemSummary, + ) -> Result<(), DeployError> { match block_item.details { BlockItemSummaryDetails::AccountTransaction(a) => match a.effects { AccountTransactionEffects::None { diff --git a/templates/default/deploy-scripts/src/main.rs b/templates/default/deploy-scripts/src/main.rs index f019316d..0e6a1448 100644 --- a/templates/default/deploy-scripts/src/main.rs +++ b/templates/default/deploy-scripts/src/main.rs @@ -128,7 +128,7 @@ async fn main() -> Result<(), DeployError> { let param: OwnedParameter = OwnedParameter::default(); // Example - let init_method_name: &str = "init_default"; // Example + let init_method_name: &str = "init_{{crate_name}}"; // Example let payload = InitContractPayload { init_name: OwnedContractName::new(init_method_name.into())?, @@ -144,7 +144,7 @@ async fn main() -> Result<(), DeployError> { let update_payload = transactions::UpdateContractPayload { amount: Amount::from_ccd(0), address: contract, - receive_name: OwnedReceiveName::new_unchecked("default.receive".to_string()), + receive_name: OwnedReceiveName::new_unchecked("{{crate_name}}.receive".to_string()), message: bytes.try_into()?, }; // Example From 6b1681fc888650144a7eaced4ebd4b3d38347a5f Mon Sep 17 00:00:00 2001 From: Doris Benda Date: Fri, 13 Oct 2023 16:30:34 +0300 Subject: [PATCH 10/20] Showcase how to use type from smart contract in script --- templates/default/deploy-scripts/Cargo.toml | 2 +- templates/default/deploy-scripts/src/main.rs | 9 ++++++++- templates/default/src/lib.rs | 4 +++- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/templates/default/deploy-scripts/Cargo.toml b/templates/default/deploy-scripts/Cargo.toml index 5c2bae7d..b7d558df 100644 --- a/templates/default/deploy-scripts/Cargo.toml +++ b/templates/default/deploy-scripts/Cargo.toml @@ -18,4 +18,4 @@ tokio = {version = "1.18", features = ["rt", "macros"]} clap = { version = "4", features = ["derive", "env"]} concordium-rust-sdk="2.4" itertools = "0.11.0" - +{{crate_name}} = {path = "../"} diff --git a/templates/default/deploy-scripts/src/main.rs b/templates/default/deploy-scripts/src/main.rs index 0e6a1448..53136436 100644 --- a/templates/default/deploy-scripts/src/main.rs +++ b/templates/default/deploy-scripts/src/main.rs @@ -139,7 +139,14 @@ async fn main() -> Result<(), DeployError> { let (_, contract) = deployer.init_contract(payload, None, None).await?; // Example - let bytes = contracts_common::to_bytes(&false); // Example + // This is how you can use a type from your smart contract. + use {{crate_name}}::{MyInputType}; + + let input_parameter: MyInputType = false; + + // Create a successful transaction. + + let bytes = contracts_common::to_bytes(&input_parameter); // Example let update_payload = transactions::UpdateContractPayload { amount: Amount::from_ccd(0), diff --git a/templates/default/src/lib.rs b/templates/default/src/lib.rs index f46b9f09..b9ba8fa5 100644 --- a/templates/default/src/lib.rs +++ b/templates/default/src/lib.rs @@ -31,13 +31,15 @@ fn init( Ok(State {}) } +pub type MyInputType = bool; + /// Receive function. The input parameter is the boolean variable `throw_error`. /// If `throw_error == true`, the receive function will throw a custom error. /// If `throw_error == false`, the receive function executes successfully. #[receive( contract = "{{crate_name}}", name = "receive", - parameter = "bool", + parameter = "MyInputType", error = "Error", mutable )] From 834c0f780524e0932b49ab4bab4ec6a73801bee7 Mon Sep 17 00:00:00 2001 From: Doris Benda Date: Mon, 16 Oct 2023 15:46:51 +0300 Subject: [PATCH 11/20] Add optional logging --- templates/default/deploy-scripts/README.md | 33 +++++----- .../default/deploy-scripts/src/deployer.rs | 65 ++++++++++++------- templates/default/deploy-scripts/src/main.rs | 40 +++++++----- 3 files changed, 82 insertions(+), 56 deletions(-) diff --git a/templates/default/deploy-scripts/README.md b/templates/default/deploy-scripts/README.md index 376138b3..62857611 100644 --- a/templates/default/deploy-scripts/README.md +++ b/templates/default/deploy-scripts/README.md @@ -17,13 +17,15 @@ cargo run The following options are necessary when running the script ``` - --node - V2 API of the concordium node. [default: http://node.testnet.concordium.com:20000] - --account - Location path and file name of the Concordium account key file (e.g. ./myPath/4SizPU2ipqQQza9Xa6fUkQBCDjyd1vTNUNDGbBeiRGpaJQc6qX.export). - --modules - Location paths and names of Concordium smart contract modules. Use this flag several times \ - if you have several smart contract modules to be deployed (e.g. --modules ./myPath/default.wasm.v1 --modules ./default2.wasm.v1). + --node + V2 API of the concordium node. [default: http://node.testnet.concordium.com:20000] + --account + Path to the file containing the Concordium account keys exported from the wallet (e.g. ./myPath/4SizPU2ipqQQza9Xa6fUkQBCDjyd1vTNUNDGbBeiRGpaJQc6qX.export). + --module + Path of the Concordium smart contract module. Use this flag several times \ + if you have several smart contract modules to be deployed (e.g. --module ./myPath/default.wasm.v1 --module ./default2.wasm.v1). + --no-logging + To specify if verbose logging should be disabled when running the script. ``` The `account` parameter should be a Concordium wallet account either exported from the @@ -32,7 +34,7 @@ genesis tool. Example: ``` -cargo run -- --node http://node.testnet.concordium.com:20000 --account ./myPath/4SizPU2ipqQQza9Xa6fUkQBCDjyd1vTNUNDGbBeiRGpaJQc6qX.export --modules ./myPath/default.wasm.v1 --modules ./default2.wasm.v1 +cargo run -- --node http://node.testnet.concordium.com:20000 --account ./myPath/4SizPU2ipqQQza9Xa6fUkQBCDjyd1vTNUNDGbBeiRGpaJQc6qX.export --module ./myPath/default.wasm.v1 --module ./default2.wasm.v1 ``` # Functionalities @@ -68,23 +70,20 @@ cargo concordium build --out ./deploy-scripts/default.wasm.v1 Navigate into the deploy-scripts folder and run the example with the `default` smart contract (replace your wallet account in the below command): ``` -cargo run -- --node http://node.testnet.concordium.com:20000 --account ./4SizPU2ipqQQza9Xa6fUkQBCDjyd1vTNUNDGbBeiRGpaJQc6qX.export --modules ./default.wasm.v1 +cargo run -- --node http://node.testnet.concordium.com:20000 --account ./4SizPU2ipqQQza9Xa6fUkQBCDjyd1vTNUNDGbBeiRGpaJQc6qX.export --module ./default.wasm.v1 ``` The output should be: ``` Deploying module.... -Module with reference 15c936d9f60dc99c543282a8f16823d2ec5c6faae689772ae06a9b2de45a39d0 already exists on the chain. +Module with reference 3774d4b9ae86ae3c5192e13455d7515073f5163a25deabc55abdab31d1cc002e already exists on the chain. Initializing contract.... -Sent tx: 09ecaa6a66e4fe2a756dd9ad8c91f5fc2099a6dd30ebd4532cb8c5aad1bab440 -Transaction finalized, tx_hash=09ecaa6a66e4fe2a756dd9ad8c91f5fc2099a6dd30ebd4532cb8c5aad1bab440 contract=(6941, 0) - -Estimating energy.... -Contract invoke success: estimated_energy=731 +Sent tx: bdb43d1f00a4c5ba02ec81e0e2da52b6920582a16acd21a364ec3e3734ad4f12 +Transaction finalized: tx_hash=bdb43d1f00a4c5ba02ec81e0e2da52b6920582a16acd21a364ec3e3734ad4f12 contract=(7000, 0) Updating contract.... -Sent tx: c61b40a09e422835c70b07369bc5f4bba8292499be80cd735af21941c9798dd2 -Transaction finalized, tx_hash=c61b40a09e422835c70b07369bc5f4bba8292499be80cd735af21941c9798dd2 +Sent tx: 4843efc3b700bce8e67f2cc3f17da3124cf0a7323652fb778412ecd768ae2fe5 +Transaction finalized: tx_hash=4843efc3b700bce8e67f2cc3f17da3124cf0a7323652fb778412ecd768ae2fe5 ``` diff --git a/templates/default/deploy-scripts/src/deployer.rs b/templates/default/deploy-scripts/src/deployer.rs index 14c03831..63ff29e3 100644 --- a/templates/default/deploy-scripts/src/deployer.rs +++ b/templates/default/deploy-scripts/src/deployer.rs @@ -91,16 +91,21 @@ impl<'a> Deployer<'a> { &mut self, wasm_module: WasmModule, expiry: Option, + logging: bool, ) -> Result<(Option>, ModuleDeployed), DeployError> { - println!("\nDeploying module...."); + if logging { + println!("\nDeploying module...."); + } let exists = self.module_exists(&wasm_module.get_module_ref()).await?; if exists { - println!( - "Module with reference {} already exists on the chain.", - wasm_module.get_module_ref() - ); + if logging { + println!( + "Module with reference {} already exists on the chain.", + wasm_module.get_module_ref() + ); + } return Ok((None, ModuleDeployed { module_ref: wasm_module.get_module_ref(), })); @@ -126,16 +131,20 @@ impl<'a> Deployer<'a> { .await .map_err(DeployError::TransactionRejected)?; - println!("Sent tx: {tx_hash}"); + if logging { + println!("Sent tx: {tx_hash}"); + } let (_, block_item) = self.client.wait_until_finalized(&tx_hash).await?; let module_deployed = self.check_outcome_of_deploy_transaction(block_item)?; - println!( - "Transaction finalized, tx_hash={} module_ref={}", - tx_hash, module_deployed.module_ref, - ); + if logging { + println!( + "Transaction finalized: tx_hash={} module_ref={}", + tx_hash, module_deployed.module_ref, + ); + } Ok((Some(tx_hash), module_deployed)) } @@ -154,8 +163,11 @@ impl<'a> Deployer<'a> { payload: InitContractPayload, energy: Option, expiry: Option, + logging: bool, ) -> Result<(HashBytes, ContractAddress), DeployError> { - println!("\nInitializing contract...."); + if logging { + println!("\nInitializing contract...."); + } let nonce = self.get_nonce(self.key.address).await?; @@ -182,16 +194,20 @@ impl<'a> Deployer<'a> { .await .map_err(DeployError::TransactionRejected)?; - println!("Sent tx: {tx_hash}"); + if logging { + println!("Sent tx: {tx_hash}"); + } let (_, block_item) = self.client.wait_until_finalized(&tx_hash).await?; let contract_init = self.check_outcome_of_initialization_transaction(block_item)?; - println!( - "Transaction finalized, tx_hash={} contract=({}, {})", - tx_hash, contract_init.contract.index, contract_init.contract.subindex, - ); + if logging { + println!( + "Transaction finalized: tx_hash={} contract=({}, {})", + tx_hash, contract_init.contract.index, contract_init.contract.subindex, + ); + } Ok((tx_hash, contract_init.contract)) } @@ -211,8 +227,11 @@ impl<'a> Deployer<'a> { update_payload: UpdateContractPayload, energy: Option, expiry: Option, + logging: bool, ) -> Result, DeployError> { - println!("\nUpdating contract...."); + if logging { + println!("\nUpdating contract...."); + } let nonce = self.get_nonce(self.key.address).await?; @@ -249,13 +268,17 @@ impl<'a> Deployer<'a> { .await .map_err(DeployError::TransactionRejected)?; - println!("Sent tx: {tx_hash}"); + if logging { + println!("Sent tx: {tx_hash}"); + } let (_, block_item) = self.client.wait_until_finalized(&tx_hash).await?; self.check_outcome_of_update_transaction(block_item)?; - println!("Transaction finalized, tx_hash={}", tx_hash,); + if logging { + println!("Transaction finalized: tx_hash={}", tx_hash,); + } Ok(tx_hash) } @@ -274,8 +297,6 @@ impl<'a> Deployer<'a> { payload: UpdateContractPayload, max_energy: Option, ) -> Result { - println!("\nEstimating energy...."); - let best_block = self.client.get_block_finalization_summary(Best).await?; let best_block_hash = best_block.block_hash; @@ -310,7 +331,7 @@ impl<'a> Deployer<'a> { used_energy, } => { let e = used_energy.energy; - println!("Contract invoke success: estimated_energy={e}"); + Ok(Energy { energy: e, }) diff --git a/templates/default/deploy-scripts/src/main.rs b/templates/default/deploy-scripts/src/main.rs index 53136436..7af425b2 100644 --- a/templates/default/deploy-scripts/src/main.rs +++ b/templates/default/deploy-scripts/src/main.rs @@ -85,24 +85,29 @@ struct App { default_value = "http://node.testnet.concordium.com:20000", help = "V2 API of the Concordium node." )] - url: v2::Endpoint, + url: v2::Endpoint, #[clap( long = "account", - help = "Location path and file name of the Concordium account key file (e.g. \ - ./myPath/3PXwJYYPf6fyVb4GJquxSZU8puxrHfzc4XogdMVot8MUQK53tW.export)." + help = "Path to the file containing the Concordium account keys exported from the wallet \ + (e.g. ./myPath/3PXwJYYPf6fyVb4GJquxSZU8puxrHfzc4XogdMVot8MUQK53tW.export)." )] - key_file: PathBuf, + key_file: PathBuf, #[clap( - long = "modules", - help = "Location paths and names of Concordium smart contract modules. Use this flag \ - several times if you have several smart contract modules to be deployed (e.g. \ - --modules ./myPath/default.wasm.v1 --modules ./default2.wasm.v1)." + long = "module", + help = "Path of the Concordium smart contract module. Use this flag several times if you \ + have several smart contract modules to be deployed (e.g. --module \ + ./myPath/default.wasm.v1 --module ./default2.wasm.v1)." )] - modules: Vec, + module: Vec, + #[clap( + long = "no_logging", + help = "To specify if verbose logging should be disabled when running the script." + )] + no_logging: bool, } /// Main function: It deploys to chain all wasm modules from the command line -/// `--modules` flags. Write your own custom deployment/initialization script in +/// `--module` flags. Write your own custom deployment/initialization script in /// this function. An deployment/initialization script example is given in this /// function for the `default` smart contract. #[tokio::main(flavor = "current_thread")] @@ -115,10 +120,10 @@ async fn main() -> Result<(), DeployError> { let mut modules_deployed: Vec = Vec::new(); - for contract in app.modules.iter().unique() { + for contract in app.module.iter().unique() { let wasm_module = get_wasm_module(contract.as_path())?; - let (_, module) = deployer.deploy_wasm_module(wasm_module, None).await?; + let (_, module) = deployer.deploy_wasm_module(wasm_module, None, !app.no_logging).await?; modules_deployed.push(module); } @@ -137,12 +142,12 @@ async fn main() -> Result<(), DeployError> { param, }; // Example - let (_, contract) = deployer.init_contract(payload, None, None).await?; // Example + let (_, contract) = deployer.init_contract(payload, None, None, !app.no_logging).await?; // Example // This is how you can use a type from your smart contract. - use {{crate_name}}::{MyInputType}; + use {{crate_name}}::MyInputType; // Example - let input_parameter: MyInputType = false; + let input_parameter: MyInputType = false; // Example // Create a successful transaction. @@ -160,8 +165,9 @@ async fn main() -> Result<(), DeployError> { // We add 100 energy to be safe. energy.energy += 100; // Example - let _update_contract = - deployer.update_contract(update_payload, Some(GivenEnergy::Add(energy)), None).await?; // Example + let _update_contract = deployer + .update_contract(update_payload, Some(GivenEnergy::Add(energy)), None, !app.no_logging) + .await?; // Example // Write your own deployment/initialization script above. An example is given // here. From 82e711f131ed9f3a94aaa153c5fb0ef7811bdcd2 Mon Sep 17 00:00:00 2001 From: Doris Benda Date: Mon, 16 Oct 2023 17:28:02 +0300 Subject: [PATCH 12/20] Address comments --- templates/default/deploy-scripts/Cargo.toml | 2 +- templates/default/deploy-scripts/README.md | 2 +- .../default/deploy-scripts/src/deployer.rs | 153 ++++++++---------- templates/default/deploy-scripts/src/main.rs | 14 +- 4 files changed, 75 insertions(+), 96 deletions(-) diff --git a/templates/default/deploy-scripts/Cargo.toml b/templates/default/deploy-scripts/Cargo.toml index b7d558df..d1eb7fe5 100644 --- a/templates/default/deploy-scripts/Cargo.toml +++ b/templates/default/deploy-scripts/Cargo.toml @@ -14,7 +14,7 @@ serde = "1.0" serde_json = "1.0" sha2 = "0.10" thiserror = "1.0" -tokio = {version = "1.18", features = ["rt", "macros"]} +tokio = {version = "1.18", features = ["rt", "macros", "rt-multi-thread"] } clap = { version = "4", features = ["derive", "env"]} concordium-rust-sdk="2.4" itertools = "0.11.0" diff --git a/templates/default/deploy-scripts/README.md b/templates/default/deploy-scripts/README.md index 62857611..385266de 100644 --- a/templates/default/deploy-scripts/README.md +++ b/templates/default/deploy-scripts/README.md @@ -5,7 +5,7 @@ This project has boilerplate code to write deployment, initialization, and updat # Purpose Automatic scripts are useful to speed up the development and testing of your protocol on the chain. -In addition, scripts help to set up identical protocols on different chains easily. E.g. you can deploy your protocol to testnet or mainnet by just specifying a corresponding node connection when running the script. +In addition, scripts help to set up identical protocols on different chains easily. E.g. you can deploy your protocol to testnet or mainnet by just specifying a corresponding node connection and account keys for the respective network when running the script. # Running The Script diff --git a/templates/default/deploy-scripts/src/deployer.rs b/templates/default/deploy-scripts/src/deployer.rs index 63ff29e3..75c89f4e 100644 --- a/templates/default/deploy-scripts/src/deployer.rs +++ b/templates/default/deploy-scripts/src/deployer.rs @@ -1,10 +1,12 @@ -use crate::v2::BlockIdentifier::Best; +use crate::DeployError; +use anyhow::Context; +use concordium_rust_sdk::smart_contracts::types::DEFAULT_INVOKE_ENERGY; +use concordium_rust_sdk::types::hashes::TransactionHash; use concordium_rust_sdk::{ common::types::TransactionTime, id::types::AccountAddress, - smart_contracts::common::{Address, ModuleReference}, + smart_contracts::common::ModuleReference, types::{ - hashes::{HashBytes, TransactionMarker}, queries::AccountNonceResponse, smart_contracts::{ContractContext, InvokeContractResult, WasmModule}, transactions::{ @@ -15,19 +17,10 @@ use concordium_rust_sdk::{ AccountTransactionEffects, BlockItemSummary, BlockItemSummaryDetails, ContractAddress, Energy, TransactionType, WalletAccount, }, - v2, + v2::{self, BlockIdentifier}, }; use std::path::Path; -use crate::DeployError; - -/// A struct representing the deployed module on the chain. -#[derive(Clone, Debug)] -pub struct ModuleDeployed { - /// Module reference on the chain. - pub module_ref: ModuleReference, -} - /// A struct representing a smart contract instance on the chain. #[derive(Clone, Debug)] pub struct ContractInitialized { @@ -41,16 +34,17 @@ pub struct Deployer<'a> { /// The client to establish a connection to a Concordium node (V2 API). pub client: &'a mut v2::Client, /// The account keys to be used for sending transactions. - pub key: WalletAccount, + pub key: WalletAccount, } impl<'a> Deployer<'a> { - /// A function to create a new deployer instance. + /// A function to create a new deployer instance from a network client and a path to the wallet. pub fn new( client: &'a mut v2::Client, wallet_account_file: &Path, ) -> Result, DeployError> { - let key_data = WalletAccount::from_json_file(wallet_account_file)?; + let key_data = WalletAccount::from_json_file(wallet_account_file) + .context("Unable to read wallet file.")?; Ok(Deployer { client, @@ -63,11 +57,10 @@ impl<'a> Deployer<'a> { &mut self, module_reference: &ModuleReference, ) -> Result { - let best_block = self.client.get_block_finalization_summary(Best).await?; - - let best_block_hash = best_block.block_hash; - - let module_src = self.client.get_module_source(module_reference, &best_block_hash).await; + let module_src = self + .client + .get_module_source(module_reference, &BlockIdentifier::LastFinal) + .await; match module_src { Ok(_) => Ok(true), @@ -92,23 +85,23 @@ impl<'a> Deployer<'a> { wasm_module: WasmModule, expiry: Option, logging: bool, - ) -> Result<(Option>, ModuleDeployed), DeployError> { + ) -> Result<(Option, ModuleReference), DeployError> { if logging { println!("\nDeploying module...."); } - let exists = self.module_exists(&wasm_module.get_module_ref()).await?; + let module_reference = wasm_module.get_module_ref(); + + let exists = self.module_exists(&module_reference).await?; if exists { if logging { println!( "Module with reference {} already exists on the chain.", - wasm_module.get_module_ref() + module_reference ); } - return Ok((None, ModuleDeployed { - module_ref: wasm_module.get_module_ref(), - })); + return Ok((None, module_reference)); } let nonce = self.get_nonce(self.key.address).await?; @@ -121,7 +114,13 @@ impl<'a> Deployer<'a> { (chrono::Utc::now().timestamp() + 300) as u64, )); - let tx = deploy_module(&self.key, self.key.address, nonce.nonce, expiry, wasm_module); + let tx = deploy_module( + &self.key, + self.key.address, + nonce.nonce, + expiry, + wasm_module, + ); let bi = transactions::BlockItem::AccountTransaction(tx); let tx_hash = self @@ -137,16 +136,16 @@ impl<'a> Deployer<'a> { let (_, block_item) = self.client.wait_until_finalized(&tx_hash).await?; - let module_deployed = self.check_outcome_of_deploy_transaction(block_item)?; + let module_reference = self.check_outcome_of_deploy_transaction(block_item)?; if logging { println!( "Transaction finalized: tx_hash={} module_ref={}", - tx_hash, module_deployed.module_ref, + tx_hash, module_reference, ); } - Ok((Some(tx_hash), module_deployed)) + Ok((Some(tx_hash), module_reference)) } /// A function to initialize a smart contract instance on the chain. @@ -164,7 +163,7 @@ impl<'a> Deployer<'a> { energy: Option, expiry: Option, logging: bool, - ) -> Result<(HashBytes, ContractAddress), DeployError> { + ) -> Result<(TransactionHash, ContractAddress), DeployError> { if logging { println!("\nInitializing contract...."); } @@ -175,15 +174,20 @@ impl<'a> Deployer<'a> { return Err(DeployError::NonceNotFinal); } - let energy = energy.unwrap_or(Energy { - energy: 5000, - }); + let energy = energy.unwrap_or(Energy { energy: 5000 }); let expiry = expiry.unwrap_or(TransactionTime::from_seconds( (chrono::Utc::now().timestamp() + 300) as u64, )); - let tx = init_contract(&self.key, self.key.address, nonce.nonce, expiry, payload, energy); + let tx = init_contract( + &self.key, + self.key.address, + nonce.nonce, + expiry, + payload, + energy, + ); let bi = transactions::BlockItem::AccountTransaction(tx); @@ -228,7 +232,7 @@ impl<'a> Deployer<'a> { energy: Option, expiry: Option, logging: bool, - ) -> Result, DeployError> { + ) -> Result { if logging { println!("\nUpdating contract...."); } @@ -247,9 +251,7 @@ impl<'a> Deployer<'a> { (chrono::Utc::now().timestamp() + 300) as u64, )); - let energy = energy.unwrap_or(GivenEnergy::Absolute(Energy { - energy: 50000, - })); + let energy = energy.unwrap_or(GivenEnergy::Absolute(Energy { energy: 50000 })); let tx = transactions::send::make_and_sign_transaction( &self.key, @@ -288,33 +290,17 @@ impl<'a> Deployer<'a> { /// /// If successful, the transaction energy is returned by this function. /// This function can be used to dry-run a transaction. - /// - /// An optional max_energy value for the transaction can - /// be given. If `None` is provided, 50000 energy is used as a default - /// max_energy value. pub async fn estimate_energy( &mut self, payload: UpdateContractPayload, - max_energy: Option, ) -> Result { - let best_block = self.client.get_block_finalization_summary(Best).await?; - - let best_block_hash = best_block.block_hash; - - let max_energy = max_energy.unwrap_or(Energy { - energy: 50000, - }); + let context = + ContractContext::new_from_payload(self.key.address, DEFAULT_INVOKE_ENERGY, payload); - let context = ContractContext { - invoker: Some(Address::Account(self.key.address)), - contract: payload.address, - amount: payload.amount, - method: payload.receive_name, - parameter: payload.message, - energy: max_energy, - }; - - let result = self.client.invoke_instance(&best_block_hash, &context).await?; + let result = self + .client + .invoke_instance(&BlockIdentifier::LastFinal, &context) + .await?; match result.response { InvokeContractResult::Failure { @@ -329,13 +315,7 @@ impl<'a> Deployer<'a> { return_value: _, events: _, used_energy, - } => { - let e = used_energy.energy; - - Ok(Energy { - energy: e, - }) - } + } => Ok(used_energy), } } @@ -344,17 +324,20 @@ impl<'a> Deployer<'a> { &mut self, address: AccountAddress, ) -> Result { - let nonce = self.client.get_next_account_sequence_number(&address).await?; + let nonce = self + .client + .get_next_account_sequence_number(&address) + .await?; Ok(nonce) } /// A function that checks the outcome of the deploy transaction. - /// It throws if the `block_item` is not a deploy transaction. + /// It returns an error if the `block_item` is not a deploy transaction. /// It returns the error code if the transaction reverted. fn check_outcome_of_deploy_transaction( &self, block_item: BlockItemSummary, - ) -> Result { + ) -> Result { match block_item.details { BlockItemSummaryDetails::AccountTransaction(a) => match a.effects { AccountTransactionEffects::None { @@ -371,11 +354,9 @@ impl<'a> Deployer<'a> { "Module deploy rejected with reason: {reject_reason:?}" ))) } - AccountTransactionEffects::ModuleDeployed { - module_ref, - } => Ok(ModuleDeployed { - module_ref, - }), + AccountTransactionEffects::ModuleDeployed { module_ref } => { + Ok( module_ref ) + } _ => Err(DeployError::InvalidBlockItem( "The parsed account transaction effect should be of type `ModuleDeployed` or \ `None` (in case the transaction reverted)" @@ -391,7 +372,7 @@ impl<'a> Deployer<'a> { } /// A function that checks the outcome of the initialization transaction. - /// It throws if the `block_item` is not a initialization transaction. + /// It returns an error if the `block_item` is not an initialization transaction. /// It returns the error code if the transaction reverted. fn check_outcome_of_initialization_transaction( &self, @@ -413,11 +394,11 @@ impl<'a> Deployer<'a> { "Contract init rejected with reason: {reject_reason:?}" ))) } - AccountTransactionEffects::ContractInitialized { - data, - } => Ok(ContractInitialized { - contract: data.address, - }), + AccountTransactionEffects::ContractInitialized { data } => { + Ok(ContractInitialized { + contract: data.address, + }) + } _ => Err(DeployError::InvalidBlockItem( "The parsed account transaction effect should be of type \ `ContractInitialized` or `None` (in case the transaction reverted)" @@ -433,7 +414,7 @@ impl<'a> Deployer<'a> { } /// A function that checks the outcome of the update transaction. - /// It throws if the `block_item` is not a update transaction. + /// It returns an error if the `block_item` is not an update transaction. /// It returns the error code if the transaction reverted. fn check_outcome_of_update_transaction( &self, @@ -455,9 +436,7 @@ impl<'a> Deployer<'a> { "Contract update rejected with reason: {reject_reason:?}" ))) } - AccountTransactionEffects::ContractUpdateIssued { - effects: _, - } => Ok(()), + AccountTransactionEffects::ContractUpdateIssued { effects: _ } => Ok(()), _ => Err(DeployError::InvalidBlockItem( "The parsed account transaction effect should be of type \ `ContractUpdateIssued` or `None` (in case the transaction reverted)" diff --git a/templates/default/deploy-scripts/src/main.rs b/templates/default/deploy-scripts/src/main.rs index 7af425b2..583bfa74 100644 --- a/templates/default/deploy-scripts/src/main.rs +++ b/templates/default/deploy-scripts/src/main.rs @@ -9,7 +9,7 @@ use concordium_rust_sdk::{ types::{OwnedContractName, OwnedParameter, OwnedReceiveName}, }, types::{ - smart_contracts::{ExceedsParameterSize, WasmModule}, + smart_contracts::{ExceedsParameterSize, WasmModule, ModuleReference}, transactions, transactions::send::GivenEnergy, }, @@ -18,7 +18,7 @@ use concordium_rust_sdk::{ use itertools::Itertools; use concordium_rust_sdk::types::transactions::InitContractPayload; -use deployer::{Deployer, ModuleDeployed}; +use deployer::Deployer; use hex::FromHexError; use std::{ io::Cursor, @@ -110,7 +110,7 @@ struct App { /// `--module` flags. Write your own custom deployment/initialization script in /// this function. An deployment/initialization script example is given in this /// function for the `default` smart contract. -#[tokio::main(flavor = "current_thread")] +#[tokio::main] async fn main() -> Result<(), DeployError> { let app: App = App::parse(); @@ -118,7 +118,7 @@ async fn main() -> Result<(), DeployError> { let mut deployer = Deployer::new(&mut concordium_client, &app.key_file)?; - let mut modules_deployed: Vec = Vec::new(); + let mut modules_deployed: Vec = Vec::new(); for contract in app.module.iter().unique() { let wasm_module = get_wasm_module(contract.as_path())?; @@ -131,14 +131,14 @@ async fn main() -> Result<(), DeployError> { // Write your own deployment/initialization script below. An example is given // here. - let param: OwnedParameter = OwnedParameter::default(); // Example + let param: OwnedParameter = OwnedParameter::empty(); // Example let init_method_name: &str = "init_{{crate_name}}"; // Example let payload = InitContractPayload { init_name: OwnedContractName::new(init_method_name.into())?, amount: Amount::from_micro_ccd(0), - mod_ref: modules_deployed[0].module_ref, + mod_ref: modules_deployed[0], param, }; // Example @@ -160,7 +160,7 @@ async fn main() -> Result<(), DeployError> { message: bytes.try_into()?, }; // Example - let mut energy = deployer.estimate_energy(update_payload.clone(), None).await?; // Example + let mut energy = deployer.estimate_energy(update_payload.clone()).await?; // Example // We add 100 energy to be safe. energy.energy += 100; // Example From 90ec7d7eb490d5a70df7283884458f789e19b9db Mon Sep 17 00:00:00 2001 From: Doris Benda Date: Mon, 16 Oct 2023 18:08:34 +0300 Subject: [PATCH 13/20] Simplified deployer struct --- templates/default/deploy-scripts/src/deployer.rs | 10 +++++----- templates/default/deploy-scripts/src/main.rs | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/templates/default/deploy-scripts/src/deployer.rs b/templates/default/deploy-scripts/src/deployer.rs index 75c89f4e..d484b4c8 100644 --- a/templates/default/deploy-scripts/src/deployer.rs +++ b/templates/default/deploy-scripts/src/deployer.rs @@ -30,19 +30,19 @@ pub struct ContractInitialized { /// A struct containing connection and wallet information. #[derive(Debug)] -pub struct Deployer<'a> { +pub struct Deployer { /// The client to establish a connection to a Concordium node (V2 API). - pub client: &'a mut v2::Client, + pub client: v2::Client, /// The account keys to be used for sending transactions. pub key: WalletAccount, } -impl<'a> Deployer<'a> { +impl Deployer { /// A function to create a new deployer instance from a network client and a path to the wallet. pub fn new( - client: &'a mut v2::Client, + client: v2::Client, wallet_account_file: &Path, - ) -> Result, DeployError> { + ) -> Result { let key_data = WalletAccount::from_json_file(wallet_account_file) .context("Unable to read wallet file.")?; diff --git a/templates/default/deploy-scripts/src/main.rs b/templates/default/deploy-scripts/src/main.rs index 583bfa74..b2da87eb 100644 --- a/templates/default/deploy-scripts/src/main.rs +++ b/templates/default/deploy-scripts/src/main.rs @@ -114,9 +114,9 @@ struct App { async fn main() -> Result<(), DeployError> { let app: App = App::parse(); - let mut concordium_client = v2::Client::new(app.url).await?; + let concordium_client = v2::Client::new(app.url).await?; - let mut deployer = Deployer::new(&mut concordium_client, &app.key_file)?; + let mut deployer = Deployer::new( concordium_client, &app.key_file)?; let mut modules_deployed: Vec = Vec::new(); From ae2dd2922a8bba59c21baf6d9e31d67c30721809 Mon Sep 17 00:00:00 2001 From: Doris Benda Date: Tue, 17 Oct 2023 10:50:20 +0300 Subject: [PATCH 14/20] Revised return values --- templates/default/deploy-scripts/Cargo.toml | 2 +- .../default/deploy-scripts/src/deployer.rs | 79 ++++++++----------- templates/default/deploy-scripts/src/main.rs | 33 +++++--- 3 files changed, 58 insertions(+), 56 deletions(-) diff --git a/templates/default/deploy-scripts/Cargo.toml b/templates/default/deploy-scripts/Cargo.toml index d1eb7fe5..3ba8a056 100644 --- a/templates/default/deploy-scripts/Cargo.toml +++ b/templates/default/deploy-scripts/Cargo.toml @@ -18,4 +18,4 @@ tokio = {version = "1.18", features = ["rt", "macros", "rt-multi-thread"] } clap = { version = "4", features = ["derive", "env"]} concordium-rust-sdk="2.4" itertools = "0.11.0" -{{crate_name}} = {path = "../"} +default = {path = "../"} diff --git a/templates/default/deploy-scripts/src/deployer.rs b/templates/default/deploy-scripts/src/deployer.rs index d484b4c8..21072ee5 100644 --- a/templates/default/deploy-scripts/src/deployer.rs +++ b/templates/default/deploy-scripts/src/deployer.rs @@ -21,13 +21,6 @@ use concordium_rust_sdk::{ }; use std::path::Path; -/// A struct representing a smart contract instance on the chain. -#[derive(Clone, Debug)] -pub struct ContractInitialized { - /// Smart contract address on the chain. - pub contract: ContractAddress, -} - /// A struct containing connection and wallet information. #[derive(Debug)] pub struct Deployer { @@ -39,10 +32,7 @@ pub struct Deployer { impl Deployer { /// A function to create a new deployer instance from a network client and a path to the wallet. - pub fn new( - client: v2::Client, - wallet_account_file: &Path, - ) -> Result { + pub fn new(client: v2::Client, wallet_account_file: &Path) -> Result { let key_data = WalletAccount::from_json_file(wallet_account_file) .context("Unable to read wallet file.")?; @@ -85,7 +75,14 @@ impl Deployer { wasm_module: WasmModule, expiry: Option, logging: bool, - ) -> Result<(Option, ModuleReference), DeployError> { + ) -> Result< + ( + Option, + Option, + ModuleReference, + ), + DeployError, + > { if logging { println!("\nDeploying module...."); } @@ -101,7 +98,7 @@ impl Deployer { module_reference ); } - return Ok((None, module_reference)); + return Ok((None, None, module_reference)); } let nonce = self.get_nonce(self.key.address).await?; @@ -136,7 +133,7 @@ impl Deployer { let (_, block_item) = self.client.wait_until_finalized(&tx_hash).await?; - let module_reference = self.check_outcome_of_deploy_transaction(block_item)?; + self.check_outcome_of_deploy_transaction(&block_item)?; if logging { println!( @@ -145,7 +142,7 @@ impl Deployer { ); } - Ok((Some(tx_hash), module_reference)) + Ok((Some(tx_hash), Some(block_item), module_reference)) } /// A function to initialize a smart contract instance on the chain. @@ -163,7 +160,7 @@ impl Deployer { energy: Option, expiry: Option, logging: bool, - ) -> Result<(TransactionHash, ContractAddress), DeployError> { + ) -> Result<(TransactionHash, BlockItemSummary, ContractAddress), DeployError> { if logging { println!("\nInitializing contract...."); } @@ -204,16 +201,16 @@ impl Deployer { let (_, block_item) = self.client.wait_until_finalized(&tx_hash).await?; - let contract_init = self.check_outcome_of_initialization_transaction(block_item)?; + let contract_address = self.check_outcome_of_initialization_transaction(&block_item)?; if logging { println!( "Transaction finalized: tx_hash={} contract=({}, {})", - tx_hash, contract_init.contract.index, contract_init.contract.subindex, + tx_hash, contract_address.index, contract_address.subindex, ); } - Ok((tx_hash, contract_init.contract)) + Ok((tx_hash, block_item, contract_address)) } /// A function to update a smart contract instance on the chain. @@ -232,7 +229,7 @@ impl Deployer { energy: Option, expiry: Option, logging: bool, - ) -> Result { + ) -> Result<(TransactionHash, BlockItemSummary), DeployError> { if logging { println!("\nUpdating contract...."); } @@ -276,13 +273,13 @@ impl Deployer { let (_, block_item) = self.client.wait_until_finalized(&tx_hash).await?; - self.check_outcome_of_update_transaction(block_item)?; + self.check_outcome_of_update_transaction(&block_item)?; if logging { println!("Transaction finalized: tx_hash={}", tx_hash,); } - Ok(tx_hash) + Ok((tx_hash, block_item)) } /// A function to estimate the energy needed to send a transaction on the @@ -336,15 +333,15 @@ impl Deployer { /// It returns the error code if the transaction reverted. fn check_outcome_of_deploy_transaction( &self, - block_item: BlockItemSummary, - ) -> Result { - match block_item.details { - BlockItemSummaryDetails::AccountTransaction(a) => match a.effects { + block_item: &BlockItemSummary, + ) -> Result<(), DeployError> { + match &block_item.details { + BlockItemSummaryDetails::AccountTransaction(a) => match &a.effects { AccountTransactionEffects::None { transaction_type, reject_reason, } => { - if transaction_type != Some(TransactionType::DeployModule) { + if *transaction_type != Some(TransactionType::DeployModule) { return Err(DeployError::InvalidBlockItem( "Expected transaction type to be DeployModule".into(), )); @@ -354,9 +351,7 @@ impl Deployer { "Module deploy rejected with reason: {reject_reason:?}" ))) } - AccountTransactionEffects::ModuleDeployed { module_ref } => { - Ok( module_ref ) - } + AccountTransactionEffects::ModuleDeployed { module_ref: _ } => Ok(()), _ => Err(DeployError::InvalidBlockItem( "The parsed account transaction effect should be of type `ModuleDeployed` or \ `None` (in case the transaction reverted)" @@ -376,15 +371,15 @@ impl Deployer { /// It returns the error code if the transaction reverted. fn check_outcome_of_initialization_transaction( &self, - block_item: BlockItemSummary, - ) -> Result { - match block_item.details { - BlockItemSummaryDetails::AccountTransaction(a) => match a.effects { + block_item: &BlockItemSummary, + ) -> Result { + match &block_item.details { + BlockItemSummaryDetails::AccountTransaction(a) => match &a.effects { AccountTransactionEffects::None { transaction_type, reject_reason, } => { - if transaction_type != Some(TransactionType::InitContract) { + if *transaction_type != Some(TransactionType::InitContract) { return Err(DeployError::InvalidBlockItem( "Expected transaction type to be InitContract".into(), )); @@ -394,11 +389,7 @@ impl Deployer { "Contract init rejected with reason: {reject_reason:?}" ))) } - AccountTransactionEffects::ContractInitialized { data } => { - Ok(ContractInitialized { - contract: data.address, - }) - } + AccountTransactionEffects::ContractInitialized { data } => Ok(data.address), _ => Err(DeployError::InvalidBlockItem( "The parsed account transaction effect should be of type \ `ContractInitialized` or `None` (in case the transaction reverted)" @@ -418,15 +409,15 @@ impl Deployer { /// It returns the error code if the transaction reverted. fn check_outcome_of_update_transaction( &self, - block_item: BlockItemSummary, + block_item: &BlockItemSummary, ) -> Result<(), DeployError> { - match block_item.details { - BlockItemSummaryDetails::AccountTransaction(a) => match a.effects { + match &block_item.details { + BlockItemSummaryDetails::AccountTransaction(a) => match &a.effects { AccountTransactionEffects::None { transaction_type, reject_reason, } => { - if transaction_type != Some(TransactionType::Update) { + if *transaction_type != Some(TransactionType::Update) { return Err(DeployError::InvalidBlockItem( "Expected transaction type to be Update".into(), )); diff --git a/templates/default/deploy-scripts/src/main.rs b/templates/default/deploy-scripts/src/main.rs index b2da87eb..ef8e2a75 100644 --- a/templates/default/deploy-scripts/src/main.rs +++ b/templates/default/deploy-scripts/src/main.rs @@ -9,7 +9,7 @@ use concordium_rust_sdk::{ types::{OwnedContractName, OwnedParameter, OwnedReceiveName}, }, types::{ - smart_contracts::{ExceedsParameterSize, WasmModule, ModuleReference}, + smart_contracts::{ExceedsParameterSize, ModuleReference, WasmModule}, transactions, transactions::send::GivenEnergy, }, @@ -76,6 +76,8 @@ fn get_wasm_module(file: &Path) -> Result { Ok(wasm_module) } +//wasm_module.get_module_ref(); + /// Command line flags. #[derive(clap::Parser, Debug)] #[clap(author, version, about)] @@ -85,20 +87,20 @@ struct App { default_value = "http://node.testnet.concordium.com:20000", help = "V2 API of the Concordium node." )] - url: v2::Endpoint, + url: v2::Endpoint, #[clap( long = "account", help = "Path to the file containing the Concordium account keys exported from the wallet \ (e.g. ./myPath/3PXwJYYPf6fyVb4GJquxSZU8puxrHfzc4XogdMVot8MUQK53tW.export)." )] - key_file: PathBuf, + key_file: PathBuf, #[clap( long = "module", help = "Path of the Concordium smart contract module. Use this flag several times if you \ have several smart contract modules to be deployed (e.g. --module \ ./myPath/default.wasm.v1 --module ./default2.wasm.v1)." )] - module: Vec, + module: Vec, #[clap( long = "no_logging", help = "To specify if verbose logging should be disabled when running the script." @@ -116,14 +118,16 @@ async fn main() -> Result<(), DeployError> { let concordium_client = v2::Client::new(app.url).await?; - let mut deployer = Deployer::new( concordium_client, &app.key_file)?; + let mut deployer = Deployer::new(concordium_client, &app.key_file)?; let mut modules_deployed: Vec = Vec::new(); for contract in app.module.iter().unique() { let wasm_module = get_wasm_module(contract.as_path())?; - let (_, module) = deployer.deploy_wasm_module(wasm_module, None, !app.no_logging).await?; + let (_, _, module) = deployer + .deploy_wasm_module(wasm_module, None, !app.no_logging) + .await?; modules_deployed.push(module); } @@ -142,7 +146,9 @@ async fn main() -> Result<(), DeployError> { param, }; // Example - let (_, contract) = deployer.init_contract(payload, None, None, !app.no_logging).await?; // Example + let (_, _, contract) = deployer + .init_contract(payload, None, None, !app.no_logging) + .await?; // Example // This is how you can use a type from your smart contract. use {{crate_name}}::MyInputType; // Example @@ -154,10 +160,10 @@ async fn main() -> Result<(), DeployError> { let bytes = contracts_common::to_bytes(&input_parameter); // Example let update_payload = transactions::UpdateContractPayload { - amount: Amount::from_ccd(0), - address: contract, + amount: Amount::from_ccd(0), + address: contract, receive_name: OwnedReceiveName::new_unchecked("{{crate_name}}.receive".to_string()), - message: bytes.try_into()?, + message: bytes.try_into()?, }; // Example let mut energy = deployer.estimate_energy(update_payload.clone()).await?; // Example @@ -166,7 +172,12 @@ async fn main() -> Result<(), DeployError> { energy.energy += 100; // Example let _update_contract = deployer - .update_contract(update_payload, Some(GivenEnergy::Add(energy)), None, !app.no_logging) + .update_contract( + update_payload, + Some(GivenEnergy::Add(energy)), + None, + !app.no_logging, + ) .await?; // Example // Write your own deployment/initialization script above. An example is given From 1635ef457a5ebaa775c04b621582be46ebb95d38 Mon Sep 17 00:00:00 2001 From: Doris Benda Date: Tue, 17 Oct 2023 11:35:52 +0300 Subject: [PATCH 15/20] Remove logging flag --- templates/default/deploy-scripts/README.md | 2 - .../default/deploy-scripts/src/deployer.rs | 72 +++++++------------ templates/default/deploy-scripts/src/main.rs | 20 +----- 3 files changed, 28 insertions(+), 66 deletions(-) diff --git a/templates/default/deploy-scripts/README.md b/templates/default/deploy-scripts/README.md index 385266de..25679ec3 100644 --- a/templates/default/deploy-scripts/README.md +++ b/templates/default/deploy-scripts/README.md @@ -24,8 +24,6 @@ The following options are necessary when running the script --module Path of the Concordium smart contract module. Use this flag several times \ if you have several smart contract modules to be deployed (e.g. --module ./myPath/default.wasm.v1 --module ./default2.wasm.v1). - --no-logging - To specify if verbose logging should be disabled when running the script. ``` The `account` parameter should be a Concordium wallet account either exported from the diff --git a/templates/default/deploy-scripts/src/deployer.rs b/templates/default/deploy-scripts/src/deployer.rs index 21072ee5..372a9395 100644 --- a/templates/default/deploy-scripts/src/deployer.rs +++ b/templates/default/deploy-scripts/src/deployer.rs @@ -20,6 +20,7 @@ use concordium_rust_sdk::{ v2::{self, BlockIdentifier}, }; use std::path::Path; +use std::sync::Arc; /// A struct containing connection and wallet information. #[derive(Debug)] @@ -27,7 +28,7 @@ pub struct Deployer { /// The client to establish a connection to a Concordium node (V2 API). pub client: v2::Client, /// The account keys to be used for sending transactions. - pub key: WalletAccount, + pub key: Arc, } impl Deployer { @@ -38,7 +39,7 @@ impl Deployer { Ok(Deployer { client, - key: key_data, + key: key_data.into(), }) } @@ -74,7 +75,6 @@ impl Deployer { &mut self, wasm_module: WasmModule, expiry: Option, - logging: bool, ) -> Result< ( Option, @@ -83,21 +83,17 @@ impl Deployer { ), DeployError, > { - if logging { - println!("\nDeploying module...."); - } + println!("\nDeploying module...."); let module_reference = wasm_module.get_module_ref(); let exists = self.module_exists(&module_reference).await?; if exists { - if logging { - println!( - "Module with reference {} already exists on the chain.", - module_reference - ); - } + println!( + "Module with reference {} already exists on the chain.", + module_reference + ); return Ok((None, None, module_reference)); } @@ -112,7 +108,7 @@ impl Deployer { )); let tx = deploy_module( - &self.key, + &*self.key, self.key.address, nonce.nonce, expiry, @@ -127,20 +123,16 @@ impl Deployer { .await .map_err(DeployError::TransactionRejected)?; - if logging { - println!("Sent tx: {tx_hash}"); - } + println!("Sent tx: {tx_hash}"); let (_, block_item) = self.client.wait_until_finalized(&tx_hash).await?; self.check_outcome_of_deploy_transaction(&block_item)?; - if logging { - println!( - "Transaction finalized: tx_hash={} module_ref={}", - tx_hash, module_reference, - ); - } + println!( + "Transaction finalized: tx_hash={} module_ref={}", + tx_hash, module_reference, + ); Ok((Some(tx_hash), Some(block_item), module_reference)) } @@ -159,11 +151,8 @@ impl Deployer { payload: InitContractPayload, energy: Option, expiry: Option, - logging: bool, ) -> Result<(TransactionHash, BlockItemSummary, ContractAddress), DeployError> { - if logging { - println!("\nInitializing contract...."); - } + println!("\nInitializing contract...."); let nonce = self.get_nonce(self.key.address).await?; @@ -178,7 +167,7 @@ impl Deployer { )); let tx = init_contract( - &self.key, + &*self.key, self.key.address, nonce.nonce, expiry, @@ -195,20 +184,16 @@ impl Deployer { .await .map_err(DeployError::TransactionRejected)?; - if logging { - println!("Sent tx: {tx_hash}"); - } + println!("Sent tx: {tx_hash}"); let (_, block_item) = self.client.wait_until_finalized(&tx_hash).await?; let contract_address = self.check_outcome_of_initialization_transaction(&block_item)?; - if logging { - println!( - "Transaction finalized: tx_hash={} contract=({}, {})", - tx_hash, contract_address.index, contract_address.subindex, - ); - } + println!( + "Transaction finalized: tx_hash={} contract=({}, {})", + tx_hash, contract_address.index, contract_address.subindex, + ); Ok((tx_hash, block_item, contract_address)) } @@ -228,11 +213,8 @@ impl Deployer { update_payload: UpdateContractPayload, energy: Option, expiry: Option, - logging: bool, ) -> Result<(TransactionHash, BlockItemSummary), DeployError> { - if logging { - println!("\nUpdating contract...."); - } + println!("\nUpdating contract...."); let nonce = self.get_nonce(self.key.address).await?; @@ -251,7 +233,7 @@ impl Deployer { let energy = energy.unwrap_or(GivenEnergy::Absolute(Energy { energy: 50000 })); let tx = transactions::send::make_and_sign_transaction( - &self.key, + &*self.key, self.key.address, nonce.nonce, expiry, @@ -267,17 +249,13 @@ impl Deployer { .await .map_err(DeployError::TransactionRejected)?; - if logging { - println!("Sent tx: {tx_hash}"); - } + println!("Sent tx: {tx_hash}"); let (_, block_item) = self.client.wait_until_finalized(&tx_hash).await?; self.check_outcome_of_update_transaction(&block_item)?; - if logging { - println!("Transaction finalized: tx_hash={}", tx_hash,); - } + println!("Transaction finalized: tx_hash={}", tx_hash,); Ok((tx_hash, block_item)) } diff --git a/templates/default/deploy-scripts/src/main.rs b/templates/default/deploy-scripts/src/main.rs index ef8e2a75..c00d9082 100644 --- a/templates/default/deploy-scripts/src/main.rs +++ b/templates/default/deploy-scripts/src/main.rs @@ -101,11 +101,6 @@ struct App { ./myPath/default.wasm.v1 --module ./default2.wasm.v1)." )] module: Vec, - #[clap( - long = "no_logging", - help = "To specify if verbose logging should be disabled when running the script." - )] - no_logging: bool, } /// Main function: It deploys to chain all wasm modules from the command line @@ -125,9 +120,7 @@ async fn main() -> Result<(), DeployError> { for contract in app.module.iter().unique() { let wasm_module = get_wasm_module(contract.as_path())?; - let (_, _, module) = deployer - .deploy_wasm_module(wasm_module, None, !app.no_logging) - .await?; + let (_, _, module) = deployer.deploy_wasm_module(wasm_module, None).await?; modules_deployed.push(module); } @@ -146,9 +139,7 @@ async fn main() -> Result<(), DeployError> { param, }; // Example - let (_, _, contract) = deployer - .init_contract(payload, None, None, !app.no_logging) - .await?; // Example + let (_, _, contract) = deployer.init_contract(payload, None, None).await?; // Example // This is how you can use a type from your smart contract. use {{crate_name}}::MyInputType; // Example @@ -172,12 +163,7 @@ async fn main() -> Result<(), DeployError> { energy.energy += 100; // Example let _update_contract = deployer - .update_contract( - update_payload, - Some(GivenEnergy::Add(energy)), - None, - !app.no_logging, - ) + .update_contract(update_payload, Some(GivenEnergy::Add(energy)), None) .await?; // Example // Write your own deployment/initialization script above. An example is given From 19c57bb2ff71fec9820afa46288ad2397194cc3b Mon Sep 17 00:00:00 2001 From: Doris Benda Date: Tue, 17 Oct 2023 12:12:30 +0300 Subject: [PATCH 16/20] Use anyhow:Error --- .../default/deploy-scripts/src/deployer.rs | 108 +++++++----------- templates/default/deploy-scripts/src/main.rs | 57 +-------- 2 files changed, 45 insertions(+), 120 deletions(-) diff --git a/templates/default/deploy-scripts/src/deployer.rs b/templates/default/deploy-scripts/src/deployer.rs index 372a9395..a9834dac 100644 --- a/templates/default/deploy-scripts/src/deployer.rs +++ b/templates/default/deploy-scripts/src/deployer.rs @@ -1,5 +1,4 @@ -use crate::DeployError; -use anyhow::Context; +use anyhow::{bail, Context, Error}; use concordium_rust_sdk::smart_contracts::types::DEFAULT_INVOKE_ENERGY; use concordium_rust_sdk::types::hashes::TransactionHash; use concordium_rust_sdk::{ @@ -33,7 +32,7 @@ pub struct Deployer { impl Deployer { /// A function to create a new deployer instance from a network client and a path to the wallet. - pub fn new(client: v2::Client, wallet_account_file: &Path) -> Result { + pub fn new(client: v2::Client, wallet_account_file: &Path) -> Result { let key_data = WalletAccount::from_json_file(wallet_account_file) .context("Unable to read wallet file.")?; @@ -47,7 +46,7 @@ impl Deployer { pub async fn module_exists( &mut self, module_reference: &ModuleReference, - ) -> Result { + ) -> Result { let module_src = self .client .get_module_source(module_reference, &BlockIdentifier::LastFinal) @@ -81,7 +80,7 @@ impl Deployer { Option, ModuleReference, ), - DeployError, + Error, > { println!("\nDeploying module...."); @@ -100,7 +99,7 @@ impl Deployer { let nonce = self.get_nonce(self.key.address).await?; if !nonce.all_final { - return Err(DeployError::NonceNotFinal); + anyhow::bail!("Nonce not final") } let expiry = expiry.unwrap_or(TransactionTime::from_seconds( @@ -116,12 +115,7 @@ impl Deployer { ); let bi = transactions::BlockItem::AccountTransaction(tx); - let tx_hash = self - .client - .clone() - .send_block_item(&bi) - .await - .map_err(DeployError::TransactionRejected)?; + let tx_hash = self.client.clone().send_block_item(&bi).await?; println!("Sent tx: {tx_hash}"); @@ -151,13 +145,13 @@ impl Deployer { payload: InitContractPayload, energy: Option, expiry: Option, - ) -> Result<(TransactionHash, BlockItemSummary, ContractAddress), DeployError> { + ) -> Result<(TransactionHash, BlockItemSummary, ContractAddress), Error> { println!("\nInitializing contract...."); let nonce = self.get_nonce(self.key.address).await?; if !nonce.all_final { - return Err(DeployError::NonceNotFinal); + bail!("Nonce not final") } let energy = energy.unwrap_or(Energy { energy: 5000 }); @@ -177,12 +171,7 @@ impl Deployer { let bi = transactions::BlockItem::AccountTransaction(tx); - let tx_hash = self - .client - .clone() - .send_block_item(&bi) - .await - .map_err(DeployError::TransactionRejected)?; + let tx_hash = self.client.clone().send_block_item(&bi).await?; println!("Sent tx: {tx_hash}"); @@ -213,13 +202,13 @@ impl Deployer { update_payload: UpdateContractPayload, energy: Option, expiry: Option, - ) -> Result<(TransactionHash, BlockItemSummary), DeployError> { + ) -> Result<(TransactionHash, BlockItemSummary), Error> { println!("\nUpdating contract...."); let nonce = self.get_nonce(self.key.address).await?; if !nonce.all_final { - return Err(DeployError::NonceNotFinal); + bail!("Nonce not final") } let payload = transactions::Payload::Update { @@ -242,12 +231,7 @@ impl Deployer { ); let bi = transactions::BlockItem::AccountTransaction(tx); - let tx_hash = self - .client - .clone() - .send_block_item(&bi) - .await - .map_err(DeployError::TransactionRejected)?; + let tx_hash = self.client.clone().send_block_item(&bi).await?; println!("Sent tx: {tx_hash}"); @@ -268,7 +252,7 @@ impl Deployer { pub async fn estimate_energy( &mut self, payload: UpdateContractPayload, - ) -> Result { + ) -> Result { let context = ContractContext::new_from_payload(self.key.address, DEFAULT_INVOKE_ENERGY, payload); @@ -282,10 +266,10 @@ impl Deployer { return_value, reason, used_energy, - } => Err(DeployError::InvokeContractFailed(format!( + } => bail!(format!( "Contract invoke failed: {reason:?}, used_energy={used_energy}, return \ value={return_value:?}" - ))), + )), InvokeContractResult::Success { return_value: _, events: _, @@ -298,7 +282,7 @@ impl Deployer { pub async fn get_nonce( &mut self, address: AccountAddress, - ) -> Result { + ) -> Result { let nonce = self .client .get_next_account_sequence_number(&address) @@ -312,7 +296,7 @@ impl Deployer { fn check_outcome_of_deploy_transaction( &self, block_item: &BlockItemSummary, - ) -> Result<(), DeployError> { + ) -> Result<(), Error> { match &block_item.details { BlockItemSummaryDetails::AccountTransaction(a) => match &a.effects { AccountTransactionEffects::None { @@ -320,27 +304,23 @@ impl Deployer { reject_reason, } => { if *transaction_type != Some(TransactionType::DeployModule) { - return Err(DeployError::InvalidBlockItem( - "Expected transaction type to be DeployModule".into(), - )); + bail!("Expected transaction type to be DeployModule",); } - Err(DeployError::TransactionRejectedR(format!( + bail!(format!( "Module deploy rejected with reason: {reject_reason:?}" - ))) + )) } AccountTransactionEffects::ModuleDeployed { module_ref: _ } => Ok(()), - _ => Err(DeployError::InvalidBlockItem( + _ => bail!( "The parsed account transaction effect should be of type `ModuleDeployed` or \ `None` (in case the transaction reverted)" - .into(), - )), + ), }, - _ => Err(DeployError::InvalidBlockItem( + _ => bail!( "Can only parse an account transaction (no account creation transaction or chain \ update transaction)" - .into(), - )), + ), } } @@ -350,7 +330,7 @@ impl Deployer { fn check_outcome_of_initialization_transaction( &self, block_item: &BlockItemSummary, - ) -> Result { + ) -> Result { match &block_item.details { BlockItemSummaryDetails::AccountTransaction(a) => match &a.effects { AccountTransactionEffects::None { @@ -358,27 +338,23 @@ impl Deployer { reject_reason, } => { if *transaction_type != Some(TransactionType::InitContract) { - return Err(DeployError::InvalidBlockItem( - "Expected transaction type to be InitContract".into(), - )); + bail!("Expected transaction type to be InitContract"); } - Err(DeployError::TransactionRejectedR(format!( + bail!(format!( "Contract init rejected with reason: {reject_reason:?}" - ))) + )) } AccountTransactionEffects::ContractInitialized { data } => Ok(data.address), - _ => Err(DeployError::InvalidBlockItem( + _ => bail!( "The parsed account transaction effect should be of type \ `ContractInitialized` or `None` (in case the transaction reverted)" - .into(), - )), + ), }, - _ => Err(DeployError::InvalidBlockItem( + _ => bail!( "Can only parse an account transaction (no account creation transaction or chain \ update transaction)" - .into(), - )), + ), } } @@ -388,7 +364,7 @@ impl Deployer { fn check_outcome_of_update_transaction( &self, block_item: &BlockItemSummary, - ) -> Result<(), DeployError> { + ) -> Result<(), Error> { match &block_item.details { BlockItemSummaryDetails::AccountTransaction(a) => match &a.effects { AccountTransactionEffects::None { @@ -396,27 +372,23 @@ impl Deployer { reject_reason, } => { if *transaction_type != Some(TransactionType::Update) { - return Err(DeployError::InvalidBlockItem( - "Expected transaction type to be Update".into(), - )); + bail!("Expected transaction type to be Update"); } - Err(DeployError::TransactionRejectedR(format!( + bail!(format!( "Contract update rejected with reason: {reject_reason:?}" - ))) + )) } AccountTransactionEffects::ContractUpdateIssued { effects: _ } => Ok(()), - _ => Err(DeployError::InvalidBlockItem( + _ => bail!( "The parsed account transaction effect should be of type \ `ContractUpdateIssued` or `None` (in case the transaction reverted)" - .into(), - )), + ), }, - _ => Err(DeployError::InvalidBlockItem( + _ => bail!( "Can only parse an account transaction (no account creation transaction or chain \ update transaction)" - .into(), - )), + ), } } } diff --git a/templates/default/deploy-scripts/src/main.rs b/templates/default/deploy-scripts/src/main.rs index c00d9082..045e3480 100644 --- a/templates/default/deploy-scripts/src/main.rs +++ b/templates/default/deploy-scripts/src/main.rs @@ -1,15 +1,14 @@ pub mod deployer; -use anyhow::Context; +use anyhow::{Error,Context}; use clap::Parser; use concordium_rust_sdk::{ common::types::Amount, - endpoints::{self, RPCError}, smart_contracts::{ - common::{self as contracts_common, NewContractNameError, NewReceiveNameError}, + common::{self as contracts_common}, types::{OwnedContractName, OwnedParameter, OwnedReceiveName}, }, types::{ - smart_contracts::{ExceedsParameterSize, ModuleReference, WasmModule}, + smart_contracts::{ ModuleReference, WasmModule}, transactions, transactions::send::GivenEnergy, }, @@ -19,65 +18,19 @@ use itertools::Itertools; use concordium_rust_sdk::types::transactions::InitContractPayload; use deployer::Deployer; -use hex::FromHexError; use std::{ io::Cursor, path::{Path, PathBuf}, }; -use thiserror::Error; - -#[derive(Error, Debug)] -pub enum DeployError { - #[error("concordium error: {0}")] - RPCError(#[from] RPCError), - #[error("transport error: {0}")] - TransportError(#[from] v2::Error), - #[error("query error: {0}")] - QueryError(#[from] endpoints::QueryError), - #[error("anyhow error: {0}")] - AnyhowError(#[from] anyhow::Error), - #[error("There are unfinalized transactions. Transaction nonce is not reliable enough.")] - NonceNotFinal, - #[error("Transaction rejected: {0}")] - TransactionRejected(RPCError), - #[error("Transaction rejected: {0:?}")] - TransactionRejectedR(String), - #[error("Invalid block item: {0}")] - InvalidBlockItem(String), - #[error("Invalid contract name: {0}")] - InvalidContractName(String), - #[error("hex decoding error: {0}")] - HexDecodingError(#[from] FromHexError), - #[error("failed to parse receive name: {0}")] - FailedToParseReceiveName(String), - #[error("Json error: {0}")] - JSONError(#[from] serde_json::Error), - #[error("Parameter size error: {0}")] - ParameterSizeError(#[from] ExceedsParameterSize), - #[error("Receive name error: {0}")] - ReceiveNameError(#[from] NewReceiveNameError), - #[error("Contract name error: {0}")] - ContractNameError(#[from] NewContractNameError), - #[error("Reqwest error: {0}")] - ReqwestError(#[from] reqwest::Error), - #[error("Invalid metadata hash: {0}")] - InvalidHash(String), - #[error("IO error: {0}")] - IOError(#[from] std::io::Error), - #[error("Invoke contract failed: {0}")] - InvokeContractFailed(String), -} /// Reads the wasm module from a given file path and file name. -fn get_wasm_module(file: &Path) -> Result { +fn get_wasm_module(file: &Path) -> Result { let wasm_module = std::fs::read(file).context("Could not read the WASM file")?; let mut cursor = Cursor::new(wasm_module); let wasm_module: WasmModule = concordium_rust_sdk::common::from_bytes(&mut cursor)?; Ok(wasm_module) } -//wasm_module.get_module_ref(); - /// Command line flags. #[derive(clap::Parser, Debug)] #[clap(author, version, about)] @@ -108,7 +61,7 @@ struct App { /// this function. An deployment/initialization script example is given in this /// function for the `default` smart contract. #[tokio::main] -async fn main() -> Result<(), DeployError> { +async fn main() -> Result<(), Error> { let app: App = App::parse(); let concordium_client = v2::Client::new(app.url).await?; From 19434a2cd2904426237fdf7011b7a2f35a4f51fa Mon Sep 17 00:00:00 2001 From: Doris Benda Date: Tue, 17 Oct 2023 14:24:23 +0300 Subject: [PATCH 17/20] Update comments --- templates/default/deploy-scripts/Cargo.toml | 8 +------- templates/default/deploy-scripts/src/deployer.rs | 12 ++++++------ templates/default/deploy-scripts/src/main.rs | 4 ++-- 3 files changed, 9 insertions(+), 15 deletions(-) diff --git a/templates/default/deploy-scripts/Cargo.toml b/templates/default/deploy-scripts/Cargo.toml index 3ba8a056..50d4df9a 100644 --- a/templates/default/deploy-scripts/Cargo.toml +++ b/templates/default/deploy-scripts/Cargo.toml @@ -8,14 +8,8 @@ version = "1.0.0" [dependencies] anyhow = "1.0" chrono = "0.4.26" -hex = "0.4" -reqwest = {version = "0.11", features = ["json"]} -serde = "1.0" -serde_json = "1.0" -sha2 = "0.10" -thiserror = "1.0" tokio = {version = "1.18", features = ["rt", "macros", "rt-multi-thread"] } clap = { version = "4", features = ["derive", "env"]} concordium-rust-sdk="2.4" itertools = "0.11.0" -default = {path = "../"} +{{crate_name}} = {path = "../"} diff --git a/templates/default/deploy-scripts/src/deployer.rs b/templates/default/deploy-scripts/src/deployer.rs index a9834dac..5b9abd5c 100644 --- a/templates/default/deploy-scripts/src/deployer.rs +++ b/templates/default/deploy-scripts/src/deployer.rs @@ -61,11 +61,11 @@ impl Deployer { /// A function to deploy a wasm module on the chain. /// - /// If successful, the transaction hash and - /// the module reference is returned. + /// If successful, the transaction hash, the block item, and + /// the module reference are returned. /// If the module already exists on /// chain, this function returns the module reference of the already - /// deployed module instead. + /// deployed module (no transaction hash or the block item returned). /// /// An optional expiry time for the transaction /// can be given. If `None` is provided, the local time + 300 seconds is @@ -133,7 +133,7 @@ impl Deployer { /// A function to initialize a smart contract instance on the chain. /// - /// If successful, the transaction hash and the contract address is + /// If successful, the transaction hash, the block item, and the contract address are /// returned. /// /// An optional energy for the transaction can be given. If `None` is @@ -190,7 +190,7 @@ impl Deployer { /// A function to update a smart contract instance on the chain. /// /// If successful, the transaction - /// hash is returned. + /// hash, and the block item are returned. /// /// An optional energy for the transaction can be /// given. If `None` is provided, 50000 energy is used as a default energy @@ -278,7 +278,7 @@ impl Deployer { } } - /// A function to get the current nonce of the wallet account. + /// A function to get the next nonce of the wallet account. pub async fn get_nonce( &mut self, address: AccountAddress, diff --git a/templates/default/deploy-scripts/src/main.rs b/templates/default/deploy-scripts/src/main.rs index 045e3480..b9c57f14 100644 --- a/templates/default/deploy-scripts/src/main.rs +++ b/templates/default/deploy-scripts/src/main.rs @@ -1,5 +1,5 @@ pub mod deployer; -use anyhow::{Error,Context}; +use anyhow::{Context, Error}; use clap::Parser; use concordium_rust_sdk::{ common::types::Amount, @@ -8,7 +8,7 @@ use concordium_rust_sdk::{ types::{OwnedContractName, OwnedParameter, OwnedReceiveName}, }, types::{ - smart_contracts::{ ModuleReference, WasmModule}, + smart_contracts::{ModuleReference, WasmModule}, transactions, transactions::send::GivenEnergy, }, From f40979e4821f128a7e3a43256fef63fe378ca01b Mon Sep 17 00:00:00 2001 From: Doris Benda Date: Wed, 18 Oct 2023 14:51:05 +0300 Subject: [PATCH 18/20] Address comments --- templates/default/deploy-scripts/Cargo.toml | 2 +- templates/default/deploy-scripts/README.md | 4 +- .../default/deploy-scripts/src/deployer.rs | 60 ++++++++++++------- templates/default/deploy-scripts/src/main.rs | 36 +++++++---- 4 files changed, 67 insertions(+), 35 deletions(-) diff --git a/templates/default/deploy-scripts/Cargo.toml b/templates/default/deploy-scripts/Cargo.toml index 50d4df9a..e81f818f 100644 --- a/templates/default/deploy-scripts/Cargo.toml +++ b/templates/default/deploy-scripts/Cargo.toml @@ -10,6 +10,6 @@ anyhow = "1.0" chrono = "0.4.26" tokio = {version = "1.18", features = ["rt", "macros", "rt-multi-thread"] } clap = { version = "4", features = ["derive", "env"]} -concordium-rust-sdk="2.4" +concordium-rust-sdk="3" itertools = "0.11.0" {{crate_name}} = {path = "../"} diff --git a/templates/default/deploy-scripts/README.md b/templates/default/deploy-scripts/README.md index 25679ec3..1d957ff4 100644 --- a/templates/default/deploy-scripts/README.md +++ b/templates/default/deploy-scripts/README.md @@ -78,10 +78,10 @@ Deploying module.... Module with reference 3774d4b9ae86ae3c5192e13455d7515073f5163a25deabc55abdab31d1cc002e already exists on the chain. Initializing contract.... -Sent tx: bdb43d1f00a4c5ba02ec81e0e2da52b6920582a16acd21a364ec3e3734ad4f12 +Sent transaction with hash: bdb43d1f00a4c5ba02ec81e0e2da52b6920582a16acd21a364ec3e3734ad4f12 Transaction finalized: tx_hash=bdb43d1f00a4c5ba02ec81e0e2da52b6920582a16acd21a364ec3e3734ad4f12 contract=(7000, 0) Updating contract.... -Sent tx: 4843efc3b700bce8e67f2cc3f17da3124cf0a7323652fb778412ecd768ae2fe5 +Sent transaction with hash: 4843efc3b700bce8e67f2cc3f17da3124cf0a7323652fb778412ecd768ae2fe5 Transaction finalized: tx_hash=4843efc3b700bce8e67f2cc3f17da3124cf0a7323652fb778412ecd768ae2fe5 ``` diff --git a/templates/default/deploy-scripts/src/deployer.rs b/templates/default/deploy-scripts/src/deployer.rs index 5b9abd5c..98f8c8d4 100644 --- a/templates/default/deploy-scripts/src/deployer.rs +++ b/templates/default/deploy-scripts/src/deployer.rs @@ -1,11 +1,10 @@ use anyhow::{bail, Context, Error}; -use concordium_rust_sdk::smart_contracts::types::DEFAULT_INVOKE_ENERGY; -use concordium_rust_sdk::types::hashes::TransactionHash; use concordium_rust_sdk::{ common::types::TransactionTime, id::types::AccountAddress, - smart_contracts::common::ModuleReference, + smart_contracts::{common::ModuleReference, types::DEFAULT_INVOKE_ENERGY}, types::{ + hashes::TransactionHash, queries::AccountNonceResponse, smart_contracts::{ContractContext, InvokeContractResult, WasmModule}, transactions::{ @@ -18,8 +17,7 @@ use concordium_rust_sdk::{ }, v2::{self, BlockIdentifier}, }; -use std::path::Path; -use std::sync::Arc; +use std::{path::Path, sync::Arc}; /// A struct containing connection and wallet information. #[derive(Debug)] @@ -30,6 +28,14 @@ pub struct Deployer { pub key: Arc, } +/// A struct containing the results of the deploy_wasm_module function. +#[derive(Debug)] +pub struct DeployResult { + pub tx_hash: Option, + pub block_item: Option, + pub module_reference: ModuleReference, +} + impl Deployer { /// A function to create a new deployer instance from a network client and a path to the wallet. pub fn new(client: v2::Client, wallet_account_file: &Path) -> Result { @@ -74,14 +80,7 @@ impl Deployer { &mut self, wasm_module: WasmModule, expiry: Option, - ) -> Result< - ( - Option, - Option, - ModuleReference, - ), - Error, - > { + ) -> Result { println!("\nDeploying module...."); let module_reference = wasm_module.get_module_ref(); @@ -93,7 +92,11 @@ impl Deployer { "Module with reference {} already exists on the chain.", module_reference ); - return Ok((None, None, module_reference)); + return Ok(DeployResult { + tx_hash: None, + block_item: None, + module_reference, + }); } let nonce = self.get_nonce(self.key.address).await?; @@ -117,7 +120,7 @@ impl Deployer { let tx_hash = self.client.clone().send_block_item(&bi).await?; - println!("Sent tx: {tx_hash}"); + println!("Sent transaction with hash: {tx_hash}"); let (_, block_item) = self.client.wait_until_finalized(&tx_hash).await?; @@ -128,7 +131,11 @@ impl Deployer { tx_hash, module_reference, ); - Ok((Some(tx_hash), Some(block_item), module_reference)) + Ok(DeployResult { + tx_hash: Some(tx_hash), + block_item: Some(block_item), + module_reference, + }) } /// A function to initialize a smart contract instance on the chain. @@ -173,7 +180,7 @@ impl Deployer { let tx_hash = self.client.clone().send_block_item(&bi).await?; - println!("Sent tx: {tx_hash}"); + println!("Sent transaction with hash: {tx_hash}"); let (_, block_item) = self.client.wait_until_finalized(&tx_hash).await?; @@ -233,7 +240,7 @@ impl Deployer { let tx_hash = self.client.clone().send_block_item(&bi).await?; - println!("Sent tx: {tx_hash}"); + println!("Sent transaction with hash: {tx_hash}"); let (_, block_item) = self.client.wait_until_finalized(&tx_hash).await?; @@ -244,11 +251,20 @@ impl Deployer { Ok((tx_hash, block_item)) } - /// A function to estimate the energy needed to send a transaction on the + /// A function to estimate the energy needed to execute a transaction on the /// chain. /// /// If successful, the transaction energy is returned by this function. /// This function can be used to dry-run a transaction. + /// + /// The transaction costs on Concordium have two components, one is based on the size of the + /// transaction and the number of signatures, and then there is a + /// transaction specific one for executing the transaction (which is estimated with this function). + /// In your main deployment script, you want to use the `energy` value returned by this function + /// and add the transaction cost of the first component before sending the transaction. `GivenEnergy::Add(energy)` + /// is the recommended helper function to be used in the main deployment script to handle the fixed + /// costs for the first component to send the correct transaction cost. + /// [GivenEnergy](https://docs.rs/concordium-rust-sdk/latest/concordium_rust_sdk/types/transactions/construct/enum.GivenEnergy.html) pub async fn estimate_energy( &mut self, payload: UpdateContractPayload, @@ -304,7 +320,7 @@ impl Deployer { reject_reason, } => { if *transaction_type != Some(TransactionType::DeployModule) { - bail!("Expected transaction type to be DeployModule",); + bail!("Expected transaction type to be of type DeployModule but it was instead {transaction_type:?}",); } bail!(format!( @@ -338,7 +354,7 @@ impl Deployer { reject_reason, } => { if *transaction_type != Some(TransactionType::InitContract) { - bail!("Expected transaction type to be InitContract"); + bail!("Expected transaction type to be of type InitContract but it was instead {transaction_type:?}"); } bail!(format!( @@ -372,7 +388,7 @@ impl Deployer { reject_reason, } => { if *transaction_type != Some(TransactionType::Update) { - bail!("Expected transaction type to be Update"); + bail!("Expected transaction type to be of type Update but it was instead {transaction_type:?}"); } bail!(format!( diff --git a/templates/default/deploy-scripts/src/main.rs b/templates/default/deploy-scripts/src/main.rs index b9c57f14..db09fe15 100644 --- a/templates/default/deploy-scripts/src/main.rs +++ b/templates/default/deploy-scripts/src/main.rs @@ -10,20 +10,18 @@ use concordium_rust_sdk::{ types::{ smart_contracts::{ModuleReference, WasmModule}, transactions, - transactions::send::GivenEnergy, + transactions::{send::GivenEnergy, InitContractPayload}, }, v2, }; +use deployer::{DeployResult, Deployer}; use itertools::Itertools; - -use concordium_rust_sdk::types::transactions::InitContractPayload; -use deployer::Deployer; use std::{ io::Cursor, path::{Path, PathBuf}, }; -/// Reads the wasm module from a given file path and file name. +/// Reads the wasm module from a given file path. fn get_wasm_module(file: &Path) -> Result { let wasm_module = std::fs::read(file).context("Could not read the WASM file")?; let mut cursor = Cursor::new(wasm_module); @@ -73,9 +71,12 @@ async fn main() -> Result<(), Error> { for contract in app.module.iter().unique() { let wasm_module = get_wasm_module(contract.as_path())?; - let (_, _, module) = deployer.deploy_wasm_module(wasm_module, None).await?; + let result: DeployResult = deployer + .deploy_wasm_module(wasm_module, None) + .await + .context("Failed to deploy a module.")?; - modules_deployed.push(module); + modules_deployed.push(result.module_reference); } // Write your own deployment/initialization script below. An example is given @@ -92,7 +93,10 @@ async fn main() -> Result<(), Error> { param, }; // Example - let (_, _, contract) = deployer.init_contract(payload, None, None).await?; // Example + let (_, _, contract) = deployer + .init_contract(payload, None, None) + .await + .context("Failed to initialize the contract.")?; // Example // This is how you can use a type from your smart contract. use {{crate_name}}::MyInputType; // Example @@ -110,14 +114,26 @@ async fn main() -> Result<(), Error> { message: bytes.try_into()?, }; // Example - let mut energy = deployer.estimate_energy(update_payload.clone()).await?; // Example + let mut energy = deployer + .estimate_energy(update_payload.clone()) + .await + .context("Failed to estimate the energy.")?; // Example // We add 100 energy to be safe. energy.energy += 100; // Example + // The transaction costs on Concordium have two components, one is based on the size of the + // transaction and the number of signatures, and then there is a + // transaction specific one for executing the transaction (which is estimated with this function). + // In your main deployment script, you want to use the `energy` value returned by this function + // and add the transaction cost of the first component before sending the transaction. `GivenEnergy::Add(energy)` + // is the recommended helper function to be used in the main deployment script to handle the fixed + // costs for the first component to send the correct transaction cost. + // [GivenEnergy](https://docs.rs/concordium-rust-sdk/latest/concordium_rust_sdk/types/transactions/construct/enum.GivenEnergy.html) let _update_contract = deployer .update_contract(update_payload, Some(GivenEnergy::Add(energy)), None) - .await?; // Example + .await + .context("Failed to update the contract.")?; // Example // Write your own deployment/initialization script above. An example is given // here. From b9a0a7b0580ccdd0268a48d7dc73b85d68e705df Mon Sep 17 00:00:00 2001 From: Doris Benda Date: Wed, 18 Oct 2023 17:27:14 +0300 Subject: [PATCH 19/20] Improve comments on transaction costs --- templates/default/deploy-scripts/Cargo.toml | 1 - templates/default/deploy-scripts/README.md | 2 +- .../default/deploy-scripts/src/deployer.rs | 81 +++++++++++++------ templates/default/deploy-scripts/src/main.rs | 30 +++---- 4 files changed, 72 insertions(+), 42 deletions(-) diff --git a/templates/default/deploy-scripts/Cargo.toml b/templates/default/deploy-scripts/Cargo.toml index e81f818f..c8df1a78 100644 --- a/templates/default/deploy-scripts/Cargo.toml +++ b/templates/default/deploy-scripts/Cargo.toml @@ -11,5 +11,4 @@ chrono = "0.4.26" tokio = {version = "1.18", features = ["rt", "macros", "rt-multi-thread"] } clap = { version = "4", features = ["derive", "env"]} concordium-rust-sdk="3" -itertools = "0.11.0" {{crate_name}} = {path = "../"} diff --git a/templates/default/deploy-scripts/README.md b/templates/default/deploy-scripts/README.md index 1d957ff4..cc044dfe 100644 --- a/templates/default/deploy-scripts/README.md +++ b/templates/default/deploy-scripts/README.md @@ -42,7 +42,7 @@ The boilerplate code has support for the following functionalities: Read functions: - `estimate_energy`: To estimate the energy needed to execute one of the three write functions below. - `module_exists`: To check if a module has already been deployed on the chain. -- `get_nonce`: To get the current nonce of the provided wallet account. +- `get_nonce`: To get the next nonce of the provided wallet account. Write functions: - `deploy_wasm_module`: To deploy a new smart contract module on the chain. diff --git a/templates/default/deploy-scripts/src/deployer.rs b/templates/default/deploy-scripts/src/deployer.rs index 98f8c8d4..0640850d 100644 --- a/templates/default/deploy-scripts/src/deployer.rs +++ b/templates/default/deploy-scripts/src/deployer.rs @@ -28,14 +28,41 @@ pub struct Deployer { pub key: Arc, } -/// A struct containing the results of the deploy_wasm_module function. +/// A struct containing the return values of the `deploy_wasm_module` function. +/// If the module does not exist on the chain, it is deployed by the `deploy_wasm_module` +/// function and the transaction hash, the block item, and the module reference are returned. +/// If the module already exists on the chain, no deployment transaction is sent and only the +/// module reference is returned. #[derive(Debug)] -pub struct DeployResult { - pub tx_hash: Option, - pub block_item: Option, +pub enum DeployResult { + /// Module is deployed with a deployment transaction. + ModuleDeployed(Box), + /// Module already exists on the chain. + ModuleExists(ModuleReference), +} + +/// A struct containing part of the return values of the `deploy_wasm_module` function. +#[derive(Debug)] +pub struct ModuleDeployedResult { + /// The transaction hash of the deployment transaction. + pub tx_hash: TransactionHash, + /// The block_item of the deployment transaction. + pub block_item: BlockItemSummary, + /// The module reference of the wasm module. pub module_reference: ModuleReference, } +/// A struct containing the return values of the `init_contract` function. +#[derive(Debug)] +pub struct InitResult { + /// The transaction hash of the initialization transaction. + pub tx_hash: TransactionHash, + /// The block_item of the initialization transaction. + pub block_item: BlockItemSummary, + /// The contract address of the smart contract instance. + pub contract_address: ContractAddress, +} + impl Deployer { /// A function to create a new deployer instance from a network client and a path to the wallet. pub fn new(client: v2::Client, wallet_account_file: &Path) -> Result { @@ -69,9 +96,8 @@ impl Deployer { /// /// If successful, the transaction hash, the block item, and /// the module reference are returned. - /// If the module already exists on - /// chain, this function returns the module reference of the already - /// deployed module (no transaction hash or the block item returned). + /// If the module already exists on chain, this function returns + /// the module reference of the already deployed module. /// /// An optional expiry time for the transaction /// can be given. If `None` is provided, the local time + 300 seconds is @@ -92,11 +118,8 @@ impl Deployer { "Module with reference {} already exists on the chain.", module_reference ); - return Ok(DeployResult { - tx_hash: None, - block_item: None, - module_reference, - }); + + return Ok(DeployResult::ModuleExists(module_reference)); } let nonce = self.get_nonce(self.key.address).await?; @@ -118,7 +141,7 @@ impl Deployer { ); let bi = transactions::BlockItem::AccountTransaction(tx); - let tx_hash = self.client.clone().send_block_item(&bi).await?; + let tx_hash = self.client.send_block_item(&bi).await?; println!("Sent transaction with hash: {tx_hash}"); @@ -131,11 +154,13 @@ impl Deployer { tx_hash, module_reference, ); - Ok(DeployResult { - tx_hash: Some(tx_hash), - block_item: Some(block_item), - module_reference, - }) + Ok(DeployResult::ModuleDeployed(Box::from( + ModuleDeployedResult { + tx_hash, + block_item, + module_reference, + }, + ))) } /// A function to initialize a smart contract instance on the chain. @@ -152,7 +177,7 @@ impl Deployer { payload: InitContractPayload, energy: Option, expiry: Option, - ) -> Result<(TransactionHash, BlockItemSummary, ContractAddress), Error> { + ) -> Result { println!("\nInitializing contract...."); let nonce = self.get_nonce(self.key.address).await?; @@ -178,7 +203,7 @@ impl Deployer { let bi = transactions::BlockItem::AccountTransaction(tx); - let tx_hash = self.client.clone().send_block_item(&bi).await?; + let tx_hash = self.client.send_block_item(&bi).await?; println!("Sent transaction with hash: {tx_hash}"); @@ -191,7 +216,11 @@ impl Deployer { tx_hash, contract_address.index, contract_address.subindex, ); - Ok((tx_hash, block_item, contract_address)) + Ok(InitResult { + tx_hash, + block_item, + contract_address, + }) } /// A function to update a smart contract instance on the chain. @@ -238,7 +267,7 @@ impl Deployer { ); let bi = transactions::BlockItem::AccountTransaction(tx); - let tx_hash = self.client.clone().send_block_item(&bi).await?; + let tx_hash = self.client.send_block_item(&bi).await?; println!("Sent transaction with hash: {tx_hash}"); @@ -254,16 +283,16 @@ impl Deployer { /// A function to estimate the energy needed to execute a transaction on the /// chain. /// - /// If successful, the transaction energy is returned by this function. + /// If successful, the execution cost in energy is returned by this function. /// This function can be used to dry-run a transaction. /// /// The transaction costs on Concordium have two components, one is based on the size of the /// transaction and the number of signatures, and then there is a - /// transaction specific one for executing the transaction (which is estimated with this function). + /// transaction-specific one for executing the transaction (which is estimated with this function). /// In your main deployment script, you want to use the `energy` value returned by this function /// and add the transaction cost of the first component before sending the transaction. `GivenEnergy::Add(energy)` - /// is the recommended helper function to be used in the main deployment script to handle the fixed - /// costs for the first component to send the correct transaction cost. + /// is the recommended helper function to be used in the main deployment script to handle the + /// cost for the first component automatically. /// [GivenEnergy](https://docs.rs/concordium-rust-sdk/latest/concordium_rust_sdk/types/transactions/construct/enum.GivenEnergy.html) pub async fn estimate_energy( &mut self, diff --git a/templates/default/deploy-scripts/src/main.rs b/templates/default/deploy-scripts/src/main.rs index db09fe15..7b06af19 100644 --- a/templates/default/deploy-scripts/src/main.rs +++ b/templates/default/deploy-scripts/src/main.rs @@ -14,8 +14,7 @@ use concordium_rust_sdk::{ }, v2, }; -use deployer::{DeployResult, Deployer}; -use itertools::Itertools; +use deployer::{DeployResult, Deployer, InitResult}; use std::{ io::Cursor, path::{Path, PathBuf}, @@ -68,15 +67,20 @@ async fn main() -> Result<(), Error> { let mut modules_deployed: Vec = Vec::new(); - for contract in app.module.iter().unique() { + for contract in app.module { let wasm_module = get_wasm_module(contract.as_path())?; - let result: DeployResult = deployer + let deploy_result = deployer .deploy_wasm_module(wasm_module, None) .await .context("Failed to deploy a module.")?; - modules_deployed.push(result.module_reference); + match deploy_result { + DeployResult::ModuleDeployed(module_deploy_result) => { + modules_deployed.push(module_deploy_result.module_reference) + } + DeployResult::ModuleExists(module_reference) => modules_deployed.push(module_reference), + } } // Write your own deployment/initialization script below. An example is given @@ -93,7 +97,7 @@ async fn main() -> Result<(), Error> { param, }; // Example - let (_, _, contract) = deployer + let init_result: InitResult = deployer .init_contract(payload, None, None) .await .context("Failed to initialize the contract.")?; // Example @@ -109,11 +113,14 @@ async fn main() -> Result<(), Error> { let update_payload = transactions::UpdateContractPayload { amount: Amount::from_ccd(0), - address: contract, + address: init_result.contract_address, receive_name: OwnedReceiveName::new_unchecked("{{crate_name}}.receive".to_string()), message: bytes.try_into()?, }; // Example + // The transaction costs on Concordium have two components, one is based on the size of the + // transaction and the number of signatures, and then there is a + // transaction-specific one for executing the transaction (which is estimated with this function). let mut energy = deployer .estimate_energy(update_payload.clone()) .await @@ -122,13 +129,8 @@ async fn main() -> Result<(), Error> { // We add 100 energy to be safe. energy.energy += 100; // Example - // The transaction costs on Concordium have two components, one is based on the size of the - // transaction and the number of signatures, and then there is a - // transaction specific one for executing the transaction (which is estimated with this function). - // In your main deployment script, you want to use the `energy` value returned by this function - // and add the transaction cost of the first component before sending the transaction. `GivenEnergy::Add(energy)` - // is the recommended helper function to be used in the main deployment script to handle the fixed - // costs for the first component to send the correct transaction cost. + // `GivenEnergy::Add(energy)` is the recommended helper function to handle the transaction cost automatically for the first component + // (based on the size of the transaction and the number of signatures). // [GivenEnergy](https://docs.rs/concordium-rust-sdk/latest/concordium_rust_sdk/types/transactions/construct/enum.GivenEnergy.html) let _update_contract = deployer .update_contract(update_payload, Some(GivenEnergy::Add(energy)), None) From 0336c3ddec38f35287b77b18f10ff7470b1dbd52 Mon Sep 17 00:00:00 2001 From: Doris Benda Date: Wed, 18 Oct 2023 17:33:41 +0300 Subject: [PATCH 20/20] Small improvement --- .../default/deploy-scripts/src/deployer.rs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/templates/default/deploy-scripts/src/deployer.rs b/templates/default/deploy-scripts/src/deployer.rs index 0640850d..d858e4fc 100644 --- a/templates/default/deploy-scripts/src/deployer.rs +++ b/templates/default/deploy-scripts/src/deployer.rs @@ -128,9 +128,9 @@ impl Deployer { anyhow::bail!("Nonce not final") } - let expiry = expiry.unwrap_or(TransactionTime::from_seconds( - (chrono::Utc::now().timestamp() + 300) as u64, - )); + let expiry = expiry.unwrap_or_else(|| { + TransactionTime::from_seconds((chrono::Utc::now().timestamp() + 300) as u64) + }); let tx = deploy_module( &*self.key, @@ -188,9 +188,9 @@ impl Deployer { let energy = energy.unwrap_or(Energy { energy: 5000 }); - let expiry = expiry.unwrap_or(TransactionTime::from_seconds( - (chrono::Utc::now().timestamp() + 300) as u64, - )); + let expiry = expiry.unwrap_or_else(|| { + TransactionTime::from_seconds((chrono::Utc::now().timestamp() + 300) as u64) + }); let tx = init_contract( &*self.key, @@ -251,9 +251,9 @@ impl Deployer { payload: update_payload, }; - let expiry = expiry.unwrap_or(TransactionTime::from_seconds( - (chrono::Utc::now().timestamp() + 300) as u64, - )); + let expiry = expiry.unwrap_or_else(|| { + TransactionTime::from_seconds((chrono::Utc::now().timestamp() + 300) as u64) + }); let energy = energy.unwrap_or(GivenEnergy::Absolute(Energy { energy: 50000 }));