From b61268cd66e91204110de051e1aee3076e2c1ee2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marijan=20Petri=C4=8Devi=C4=87?= Date: Tue, 10 Sep 2024 10:59:15 +0200 Subject: [PATCH] cctl: make deployable contracts runtime args specifiable in the cli --- Cargo.lock | 2 + Cargo.toml | 2 + bin/cctld.rs | 20 ++----- nixos/modules/cctl.nix | 36 ++++++++---- nixos/tests/verify-cctl-service.nix | 8 ++- src/lib.rs | 56 ++++++++++++------- ...st_cctl_deploys_a_contract_successfully.rs | 6 +- 7 files changed, 77 insertions(+), 53 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 119015a..b5e18d9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -299,6 +299,8 @@ dependencies = [ "itertools 0.13.0", "nom", "sd-notify", + "serde", + "serde_json", "tempfile", "tokio", "tracing", diff --git a/Cargo.toml b/Cargo.toml index 7931408..898610a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,6 +26,8 @@ itertools = "0.13" nom = "7" hex = "0.4" sd-notify = "0.4" +serde = "1" +serde_json = "1" tokio = { version = "1", features = [ "full", "tracing", "macros" ] } tempfile = "3" tracing = "0.1" diff --git a/bin/cctld.rs b/bin/cctld.rs index 62906b8..69a0b1f 100644 --- a/bin/cctld.rs +++ b/bin/cctld.rs @@ -1,4 +1,3 @@ -use casper_types::runtime_args; use clap::Parser; use sd_notify::NotifyState; use std::path::PathBuf; @@ -10,10 +9,10 @@ pub struct Cli { #[arg(short, long)] pub working_dir: Option, #[arg(short, long)] - pub deploy_contract: Option, - #[arg(short, long)] + pub deploy_contracts: Option>, + #[arg(short = 's', long)] pub chainspec_path: Option, - #[arg(short, long)] + #[arg(short = 'c', long)] pub config_path: Option, } @@ -24,20 +23,9 @@ async fn main() -> Result<(), Box> { .with_writer(std::io::stderr) .init(); let cli = Cli::parse(); - let deploy_contract = cli.deploy_contract.map(|deploy_contracts_arg| { - match deploy_contracts_arg.split_once(':') { - Some((hash_name, path)) => cctl::DeployableContract { - hash_name: hash_name.to_string(), - // FIXME at some point we want to make this parametrizable - runtime_args: runtime_args! {}, - path: PathBuf::from(&path), - }, - None => panic!("Error parsing the provided deploy contracts argument."), - } - }); let _network = cctl::CCTLNetwork::run( cli.working_dir, - deploy_contract, + cli.deploy_contracts, cli.chainspec_path, cli.config_path, ) diff --git a/nixos/modules/cctl.nix b/nixos/modules/cctl.nix index bbc630a..05f304a 100644 --- a/nixos/modules/cctl.nix +++ b/nixos/modules/cctl.nix @@ -62,17 +62,29 @@ in ''; }; - contract = mkOption { - type = types.nullOr (types.attrsOf types.path); + contracts = mkOption { default = null; - example = { "contract hash name" = "/path/to/contract.wasm"; }; - description = '' - The wasm compiled contract that should be deployed once the network is up and ready. - The name of the attribute should correspond to the contracts hash name when calling - https://docs.rs/casper-contract/latest/casper_contract/contract_api/storage/fn.new_locked_contract.html - ''; + type = types.nullOr (types.listOf (types.submodule { + options = { + hash_name = mkOption { + type = types.str; + description = '' + The contracts hash name which was provided when calling + https://docs.rs/casper-contract/latest/casper_contract/contract_api/storage/fn.new_locked_contract.html + ''; + }; + path = mkOption { + type = types.path; + description = "The wasm compiled contract that should be deployed once the network is up and ready."; + }; + runtime_args = mkOption { + default = null; + type = types.nullOr types.attrs; + description = "The runtime arguments expected by this contract."; + }; + }; + })); }; - }; config = mkIf cfg.enable { @@ -83,9 +95,9 @@ in "--working-dir" cfg.workingDirectory ] - ++ optionals (!builtins.isNull cfg.contract) ([ - "--deploy-contract" - ] ++ (lib.mapAttrsToList (hash_name: contract_path: "${hash_name}:${contract_path}") cfg.contract)) + ++ optionals (!builtins.isNull cfg.contracts) ([ + "--deploy-contracts" + ] ++ (lib.map (contract: builtins.toJSON contract) cfg.contracts)) ++ optionals (!builtins.isNull cfg.chainspec) [ "--chainspec-path" cfg.chainspec diff --git a/nixos/tests/verify-cctl-service.nix b/nixos/tests/verify-cctl-service.nix index b34caf6..a02285b 100644 --- a/nixos/tests/verify-cctl-service.nix +++ b/nixos/tests/verify-cctl-service.nix @@ -14,7 +14,13 @@ nixosTest { ]; services.cctl = { enable = true; - contract = { "contract-hash" = contractWasm; }; + contracts = [ + { + hash_name = "contract-hash"; + path = contractWasm; + runtime_args = null; + } + ]; }; networking.firewall.allowedTCPPorts = [ 80 config.services.cctl.port ]; }; diff --git a/src/lib.rs b/src/lib.rs index c025a8c..85e683d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,10 +4,12 @@ use anyhow::anyhow; use backoff::{future::retry, ExponentialBackoff}; use hex::FromHex; use itertools::{Either, Itertools}; +use serde::{Deserialize, Serialize}; use std::env; use std::io::{self, Write}; use std::path::PathBuf; use std::process::Command; +use std::str::FromStr; use std::{ fs, time::{Duration, Instant}, @@ -21,8 +23,8 @@ use casper_types::{ account::AccountHash, contracts::ContractHash, execution::{execution_result_v1::ExecutionResultV1, ExecutionResult}, - DeployBuilder, ExecutableDeployItem, Key, PublicKey, RuntimeArgs, SecretKey, StoredValue, - TimeDiff, Timestamp, + runtime_args, DeployBuilder, ExecutableDeployItem, Key, PublicKey, RuntimeArgs, SecretKey, + StoredValue, TimeDiff, Timestamp, }; use parsers::RawNodeType; @@ -68,13 +70,22 @@ pub struct CCTLNetwork { pub casper_sidecars: Vec, } +#[derive(Clone, Serialize, Deserialize)] pub struct DeployableContract { /// This is the named key under which the contract hash is located pub hash_name: String, - pub runtime_args: RuntimeArgs, + pub runtime_args: Option, pub path: PathBuf, } +impl FromStr for DeployableContract { + type Err = serde_json::Error; + + fn from_str(s: &str) -> Result { + serde_json::from_str(s) + } +} + /// Configures the casper-client verbosity level depending on the tracing log level pub fn casper_client_verbosity() -> Verbosity { if tracing::enabled!(tracing::Level::TRACE) { @@ -98,7 +109,7 @@ impl CCTLNetwork { /// Ensure that two instances of this function are not running at the same time even in different processes. pub async fn run( working_dir: Option, - contract_to_deploy: Option, + contracts_to_deploy: Option>, chainspec_path: Option, config_path: Option, ) -> anyhow::Result { @@ -218,23 +229,26 @@ impl CCTLNetwork { let deployer_pkey = PublicKey::from_file(working_dir.join("assets/users/user-1/public_key.pem"))?; - let (hash_name, contract_hash) = deploy_contract( - &casper_sidecar_rpc_url, - &deployer_skey, - &deployer_pkey.to_account_hash(), - &contract_to_deploy, - ) - .await?; let contracts_dir = working_dir.join("contracts"); fs::create_dir_all(&contracts_dir)?; - fs::write( - contracts_dir.join(hash_name), - // For a ContractHash contract- will always be the prefix - contract_hash - .to_formatted_string() - .strip_prefix("contract-") - .unwrap(), - )? + + for contract_to_deploy in contracts_to_deploy { + let (hash_name, contract_hash) = deploy_contract( + &casper_sidecar_rpc_url, + &deployer_skey, + &deployer_pkey.to_account_hash(), + &contract_to_deploy, + ) + .await?; + fs::write( + contracts_dir.join(hash_name), + // For a ContractHash contract- will always be the prefix + contract_hash + .to_formatted_string() + .strip_prefix("contract-") + .unwrap(), + )? + } } Ok(CCTLNetwork { working_dir, @@ -284,8 +298,8 @@ async fn deploy_contract( let casper_client_verbosity = casper_client_verbosity(); let contract_bytes = fs::read(path)?; - let contract = - ExecutableDeployItem::new_module_bytes(contract_bytes.into(), runtime_args.clone()); + let runtime_args = runtime_args.clone().unwrap_or(runtime_args! {}); + let contract = ExecutableDeployItem::new_module_bytes(contract_bytes.into(), runtime_args); let deploy = DeployBuilder::new( // TODO ideally make the chain-name configurable "cspr-dev-cctl", diff --git a/tests/test_cctl_deploys_a_contract_successfully.rs b/tests/test_cctl_deploys_a_contract_successfully.rs index 845101d..59b7d6b 100644 --- a/tests/test_cctl_deploys_a_contract_successfully.rs +++ b/tests/test_cctl_deploys_a_contract_successfully.rs @@ -4,7 +4,7 @@ use std::fs; use std::path::PathBuf; use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt, EnvFilter}; -use casper_types::{contracts::ContractHash, runtime_args}; +use casper_types::contracts::ContractHash; use cctl::{CCTLNetwork, DeployableContract}; fn tracing_init() { @@ -23,11 +23,11 @@ async fn test_cctl_deploys_a_contract_successfully() { let hash_name = "contract-hash"; let contract_to_deploy = DeployableContract { hash_name: hash_name.to_string(), - runtime_args: runtime_args! {}, + runtime_args: None, path: contract_wasm_path, }; - let network = CCTLNetwork::run(None, Some(contract_to_deploy), None, None) + let network = CCTLNetwork::run(None, Some(vec![contract_to_deploy]), None, None) .await .unwrap(); let expected_contract_hash_path = network.working_dir.join("contracts").join(hash_name);