Merge pull request #4954 from nymtech/feature/contract-state-tools

Feature/contract state tools
This commit is contained in:
Jędrzej Stuczyński
2024-10-08 15:32:28 +01:00
committed by GitHub
19 changed files with 782 additions and 17 deletions
Generated
+36
View File
@@ -576,6 +576,12 @@ version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b"
[[package]]
name = "base85rs"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87678d33a2af71f019ed11f52db246ca6c5557edee2cccbe689676d1ad9c6b5a"
[[package]]
name = "basic-toml"
version = "0.1.9"
@@ -3299,6 +3305,34 @@ dependencies = [
"unicode-normalization",
]
[[package]]
name = "importer-cli"
version = "0.1.0"
dependencies = [
"anyhow",
"bip39",
"clap 4.5.18",
"dirs 5.0.1",
"importer-contract",
"nym-bin-common",
"nym-network-defaults",
"nym-validator-client",
"serde",
"serde_json",
"tokio",
"tracing",
]
[[package]]
name = "importer-contract"
version = "0.1.0"
dependencies = [
"base85rs",
"cosmwasm-schema",
"cosmwasm-std",
"cosmwasm-storage",
]
[[package]]
name = "indenter"
version = "0.3.3"
@@ -5898,6 +5932,7 @@ version = "0.1.0"
dependencies = [
"base64 0.22.1",
"bs58",
"hex",
"serde",
"time",
]
@@ -6334,6 +6369,7 @@ dependencies = [
"nym-mixnet-contract-common",
"nym-multisig-contract-common",
"nym-network-defaults",
"nym-serde-helpers",
"nym-vesting-contract-common",
"prost 0.12.6",
"reqwest 0.12.4",
+2
View File
@@ -138,6 +138,8 @@ members = [
"tools/internal/testnet-manager",
"tools/internal/testnet-manager/dkg-bypass-contract",
"tools/echo-server",
"tools/internal/contract-state-importer/importer-cli",
"tools/internal/contract-state-importer/importer-contract",
]
default-members = [
@@ -20,6 +20,7 @@ nym-coconut-bandwidth-contract-common = { path = "../../cosmwasm-smart-contracts
nym-ecash-contract-common = { path = "../../cosmwasm-smart-contracts/ecash-contract" }
nym-multisig-contract-common = { path = "../../cosmwasm-smart-contracts/multisig-contract" }
nym-group-contract-common = { path = "../../cosmwasm-smart-contracts/group-contract" }
nym-serde-helpers = { path = "../../serde-helpers", features = ["hex", "base64"] }
serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true }
nym-http-api-client = { path = "../../../common/http-api-client" }
@@ -5,7 +5,7 @@ use crate::nyxd;
use crate::nyxd::coin::Coin;
use crate::nyxd::cosmwasm_client::helpers::{create_pagination, next_page_key};
use crate::nyxd::cosmwasm_client::types::{
Account, CodeDetails, Contract, ContractCodeId, SequenceResponse, SimulateResponse,
Account, CodeDetails, Contract, ContractCodeId, Model, SequenceResponse, SimulateResponse,
};
use crate::nyxd::error::NyxdError;
use crate::nyxd::Query;
@@ -21,11 +21,11 @@ use cosmrs::proto::cosmos::tx::v1beta1::{
SimulateRequest, SimulateResponse as ProtoSimulateResponse,
};
use cosmrs::proto::cosmwasm::wasm::v1::{
QueryCodeRequest, QueryCodeResponse, QueryCodesRequest, QueryCodesResponse,
QueryContractHistoryRequest, QueryContractHistoryResponse, QueryContractInfoRequest,
QueryContractInfoResponse, QueryContractsByCodeRequest, QueryContractsByCodeResponse,
QueryRawContractStateRequest, QueryRawContractStateResponse, QuerySmartContractStateRequest,
QuerySmartContractStateResponse,
QueryAllContractStateRequest, QueryAllContractStateResponse, QueryCodeRequest,
QueryCodeResponse, QueryCodesRequest, QueryCodesResponse, QueryContractHistoryRequest,
QueryContractHistoryResponse, QueryContractInfoRequest, QueryContractInfoResponse,
QueryContractsByCodeRequest, QueryContractsByCodeResponse, QueryRawContractStateRequest,
QueryRawContractStateResponse, QuerySmartContractStateRequest, QuerySmartContractStateResponse,
};
use cosmrs::tendermint::{block, chain, Hash};
use cosmrs::{AccountId, Coin as CosmosCoin, Tx};
@@ -444,6 +444,38 @@ pub trait CosmWasmClient: TendermintRpcClient {
.collect::<Result<_, _>>()?)
}
async fn query_all_contract_state(&self, address: &AccountId) -> Result<Vec<Model>, NyxdError> {
let path = Some("/cosmwasm.wasm.v1.Query/AllContractState".to_owned());
let mut models = Vec::new();
let mut pagination = None;
loop {
let req = QueryAllContractStateRequest {
address: address.to_string(),
pagination,
};
let mut res = self
.make_abci_query::<_, QueryAllContractStateResponse>(path.clone(), req)
.await?;
let empty_response = res.models.is_empty();
models.append(&mut res.models);
if empty_response {
break;
}
if let Some(next_key) = next_page_key(res.pagination) {
pagination = Some(create_pagination(next_key))
} else {
break;
}
}
Ok(models.into_iter().map(Into::into).collect())
}
async fn query_contract_raw(
&self,
address: &AccountId,
@@ -27,13 +27,34 @@ use cosmrs::vesting::{
};
use cosmrs::{AccountId, Any, Coin as CosmosCoin};
use prost::Message;
use serde::Serialize;
use serde::{Deserialize, Serialize};
pub use cosmrs::abci::GasInfo;
pub use cosmrs::abci::MsgResponse;
pub type ContractCodeId = u64;
// yet another thing to put in cosmrs
#[derive(Serialize, Deserialize)]
pub struct Model {
#[serde(with = "nym_serde_helpers::hex")]
pub key: Vec<u8>,
#[serde(with = "nym_serde_helpers::base64")]
pub value: Vec<u8>,
}
// follow the cosmwasm serialisation format, i.e. hex for key and base64 for value
impl From<cosmrs::proto::cosmwasm::wasm::v1::Model> for Model {
fn from(model: cosmrs::proto::cosmwasm::wasm::v1::Model) -> Self {
Model {
key: model.key,
value: model.value,
}
}
}
#[derive(Serialize)]
pub struct EmptyMsg {}
@@ -7,13 +7,14 @@ pub mod execute_contract;
pub mod generators;
pub mod init_contract;
pub mod migrate_contract;
pub mod raw_contract_state;
pub mod upload_contract;
#[derive(Debug, Args)]
#[clap(args_conflicts_with_subcommands = true, subcommand_required = true)]
pub struct Cosmwasm {
#[clap(subcommand)]
pub command: Option<CosmwasmCommands>,
pub command: CosmwasmCommands,
}
#[derive(Debug, Subcommand)]
@@ -28,4 +29,6 @@ pub enum CosmwasmCommands {
Migrate(crate::validator::cosmwasm::migrate_contract::Args),
/// Execute a WASM smart contract method
Execute(crate::validator::cosmwasm::execute_contract::Args),
/// Obtain raw contract state of a cosmwasm smart contract
RawContractState(crate::validator::cosmwasm::raw_contract_state::Args),
}
@@ -0,0 +1,39 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::context::QueryClient;
use clap::Parser;
use cosmrs::AccountId;
use log::trace;
use nym_validator_client::nyxd::CosmWasmClient;
use std::fs;
use std::fs::File;
use std::path::PathBuf;
#[derive(Debug, Parser)]
pub struct Args {
#[clap(long, value_parser)]
#[clap(help = "The address of contract to get the state of")]
pub contract: AccountId,
#[clap(short, long)]
#[clap(help = "Output file for the retrieved contract state")]
pub output: PathBuf,
}
pub async fn execute(args: Args, client: QueryClient) -> anyhow::Result<()> {
trace!("args: {args:?}");
let output = File::create(&args.output)?;
let raw = client.query_all_contract_state(&args.contract).await?;
serde_json::to_writer(output, &raw)?;
println!(
"wrote {} key-value from {} pairs into '{}'",
raw.len(),
args.contract,
fs::canonicalize(args.output)?.display()
);
Ok(())
}
+2
View File
@@ -13,11 +13,13 @@ license.workspace = true
[dependencies]
serde = { workspace = true }
hex = { workspace = true, optional = true }
bs58 = { workspace = true, optional = true }
base64 = { workspace = true, optional = true }
time = { workspace = true, features = ["formatting", "parsing"], optional = true }
[features]
hex = ["dep:hex"]
bs58 = ["dep:bs58"]
base64 = ["dep:base64"]
date = ["time"]
+14
View File
@@ -32,6 +32,20 @@ pub mod bs58 {
}
}
#[cfg(feature = "hex")]
pub mod hex {
use serde::{Deserialize, Deserializer, Serializer};
pub fn serialize<S: Serializer>(bytes: &[u8], serializer: S) -> Result<S::Ok, S::Error> {
serializer.serialize_str(&::hex::encode(bytes))
}
pub fn deserialize<'de, D: Deserializer<'de>>(deserializer: D) -> Result<Vec<u8>, D::Error> {
let s = String::deserialize(deserializer)?;
::hex::decode(&s).map_err(serde::de::Error::custom)
}
}
#[cfg(feature = "date")]
pub mod date {
use serde::ser::Error;
+2
View File
@@ -3361,6 +3361,7 @@ version = "0.1.0"
dependencies = [
"base64 0.22.1",
"bs58",
"hex",
"serde",
"time",
]
@@ -3449,6 +3450,7 @@ dependencies = [
"nym-mixnet-contract-common",
"nym-multisig-contract-common",
"nym-network-defaults",
"nym-serde-helpers",
"nym-vesting-contract-common",
"prost",
"reqwest 0.12.4",
@@ -0,0 +1,2 @@
build-importer-contract:
$(MAKE) -C importer-contract build
@@ -0,0 +1,26 @@
[package]
name = "importer-cli"
version = "0.1.0"
authors.workspace = true
repository.workspace = true
homepage.workspace = true
documentation.workspace = true
edition.workspace = true
license.workspace = true
rust-version.workspace = true
readme.workspace = true
[dependencies]
anyhow = { workspace = true }
bip39 = { workspace = true }
clap = { workspace = true, features = ["derive"] }
dirs = { workspace = true }
serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true }
tokio = { workspace = true, features = ["rt-multi-thread", "net", "signal"] }
tracing = { workspace = true }
importer-contract = { path = "../importer-contract" }
nym-validator-client = { path = "../../../../common/client-libs/validator-client" }
nym-bin-common = { path = "../../../../common/bin-common", features = ["basic_tracing"] }
nym-network-defaults = { path = "../../../../common/network-defaults" }
@@ -0,0 +1,477 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
use anyhow::bail;
use clap::ArgGroup;
use clap::{Args, Parser, Subcommand};
use importer_contract::contract::EmptyMessage;
use importer_contract::{base85rs, ExecuteMsg};
use nym_bin_common::bin_info;
use nym_bin_common::logging::setup_tracing_logger;
use nym_network_defaults::{setup_env, NymNetworkDetails};
use nym_validator_client::nyxd::cosmwasm_client::types::{InstantiateOptions, Model};
use nym_validator_client::nyxd::AccountId;
use nym_validator_client::{nyxd, DirectSigningHttpRpcNyxdClient};
use serde::{Deserialize, Serialize};
use std::env::current_dir;
use std::fs;
use std::fs::{create_dir_all, File};
use std::io::Read;
use std::path::PathBuf;
use std::sync::OnceLock;
use tracing::{debug, info};
fn pretty_build_info_static() -> &'static str {
static PRETTY_BUILD_INFORMATION: OnceLock<String> = OnceLock::new();
PRETTY_BUILD_INFORMATION.get_or_init(|| bin_info!().pretty_print())
}
#[derive(Parser)]
#[clap(author = "Nymtech", version, long_version = pretty_build_info_static(), about)]
pub struct Cli {
/// Path pointing to an env file that configures the CLI.
#[clap(short, long)]
pub(crate) config_env_file: Option<PathBuf>,
#[clap(long)]
pub(crate) mnemonic: bip39::Mnemonic,
#[clap(subcommand)]
command: Commands,
}
#[derive(Serialize, Deserialize)]
pub struct CachedState {
pub importer_address: AccountId,
pub state_imported: bool,
}
impl CachedState {
pub fn save(&self) -> anyhow::Result<()> {
let path = cached_state_file();
if let Some(parent) = path.parent() {
create_dir_all(parent)?;
}
let file = File::create(&path)?;
serde_json::to_writer_pretty(file, self)?;
info!("saved cached details to {}", path.display());
Ok(())
}
pub fn load() -> anyhow::Result<Self> {
let file = File::open(cached_state_file())?;
Ok(serde_json::from_reader(&file)?)
}
}
fn cached_state_file() -> PathBuf {
dirs::cache_dir()
.unwrap()
.join("contract-state-importer")
.join(".state.json")
}
// this only works if the cli is called from somewhere within the nym directory
// (which realistically is going to be the case most of the time)
fn importer_contract_path(explicit: Option<PathBuf>) -> anyhow::Result<PathBuf> {
if let Some(explicit) = explicit {
return Ok(explicit);
}
for ancestor in current_dir()?.ancestors() {
debug!("checking {:?}", fs::canonicalize(ancestor));
for content in ancestor.read_dir()? {
let dir_entry = content?;
let Ok(name) = dir_entry.file_name().into_string() else {
continue;
};
if name == "target" {
let maybe_contract_path = dir_entry
.path()
.join("wasm32-unknown-unknown")
.join("release")
.join("importer_contract.wasm");
if maybe_contract_path.exists() {
return Ok(maybe_contract_path);
}
}
}
}
bail!("could not find importer_contract.wasm")
}
fn importer_contract_address(explicit: Option<AccountId>) -> anyhow::Result<AccountId> {
if let Some(explicit) = explicit {
return Ok(explicit);
}
let state = CachedState::load()?;
Ok(state.importer_address)
}
#[derive(Args, Clone)]
pub struct PrepareArgs {
/// Path to the .wasm file with the importer contract
/// If not provided, the CLI will attempt to traverse the parent directories until it finds
/// "target/wasm32-unknown-unknown/release/importer_contract.wasm"
#[clap(long)]
pub importer_contract_path: Option<PathBuf>,
}
#[derive(Args, Clone)]
pub struct SetStateArgs {
/// Explicit address of the initialised importer contract.
/// If not set, the value from the cached state will be attempted to be used
#[clap(long)]
pub importer_contract_address: Option<AccountId>,
/// Path to the file containing state dump of a cosmwasm contract
#[clap(long)]
pub raw_state: PathBuf,
}
#[derive(Args, Clone)]
#[clap(group(ArgGroup::new("contract").required(true)))]
pub struct SwapContractArgs {
/// Explicit address of the initialised importer contract.
/// If not set, the value from the cached state will be attempted to be used
#[clap(long)]
pub importer_contract_address: Option<AccountId>,
/// Code id of the previously uploaded cosmwasm smart contract that will be applied to the imported state
#[clap(long, group = "contract")]
pub target_contract_code_id: Option<u64>,
/// Path to a cosmwasm smart contract that will be uploaded and applied to the imported state
#[clap(long, group = "contract")]
pub target_contract_path: Option<PathBuf>,
/// The custom migrate message used for migrating into target contract.
/// If none is provided an empty object will be used instead, i.e. '{}'
#[clap(long)]
pub migrate_msg: Option<serde_json::Value>,
}
#[derive(Args, Clone)]
#[clap(group(ArgGroup::new("contract").required(true)))]
pub struct InitialiseWithStateArgs {
/// Path to the .wasm file with the importer contract
/// If not provided, the CLI will attempt to traverse the parent directories until it finds
/// "target/wasm32-unknown-unknown/release/importer_contract.wasm"
#[clap(long)]
pub importer_contract_path: Option<PathBuf>,
/// Path to the file containing state dump of a cosmwasm contract
#[clap(long)]
pub raw_state: PathBuf,
/// Code id of the previously uploaded cosmwasm smart contract that will be applied to the imported state
#[clap(long, group = "contract")]
pub target_contract_code_id: Option<u64>,
/// Path to a cosmwasm smart contract that will be uploaded and applied to the imported state
#[clap(long, group = "contract")]
pub target_contract_path: Option<PathBuf>,
#[clap(long)]
pub migrate_msg: Option<serde_json::Value>,
}
#[derive(Subcommand)]
pub(crate) enum Commands {
/// Upload and instantiates the importer contract
PrepareContract(PrepareArgs),
/// Set the state of the previously instantiated importer contract with the provided state dump
SetState(SetStateArgs),
/// Swap the importer contract code with the one corresponding to the previously uploaded state dump
SwapContract(SwapContractArgs),
/// Combines the functionalities of `prepare-contract`, `set-state` and `swap-contract`
InitialiseWithState(InitialiseWithStateArgs),
}
async fn create_importer_contract(
explicit_contract_path: Option<PathBuf>,
client: &DirectSigningHttpRpcNyxdClient,
) -> anyhow::Result<AccountId> {
info!("attempting to create the importer contract");
let importer_path = importer_contract_path(explicit_contract_path)?;
info!(
"going to use the following importer contract: '{}'",
fs::canonicalize(&importer_path)?.display()
);
let mut data = Vec::new();
File::open(importer_path)?.read_to_end(&mut data)?;
let res = client.upload(data, "<empty>", None).await?;
let importer_code_id = res.code_id;
info!(
" ✅ uploaded the importer contract in {}",
res.transaction_hash
);
let res = client
.instantiate(
importer_code_id,
&EmptyMessage {},
"importer-contract".into(),
"<empty>",
Some(InstantiateOptions::default().with_admin(client.address())),
None,
)
.await?;
let importer_address = res.contract_address;
info!(
" ✅ instantiated the importer contract in {}",
res.transaction_hash
);
info!("IMPORTER CONTRACT ADDRESS: {importer_address}");
CachedState {
importer_address: importer_address.clone(),
state_imported: false,
}
.save()?;
Ok(importer_address)
}
async fn execute_prepare_contract(
args: PrepareArgs,
client: DirectSigningHttpRpcNyxdClient,
) -> anyhow::Result<()> {
create_importer_contract(args.importer_contract_path, &client).await?;
Ok(())
}
fn approximate_size(pair: &Model) -> usize {
base85rs::encode(&pair.key).len() + base85rs::encode(&pair.value).len()
}
fn models_to_exec(data: Vec<Model>) -> ExecuteMsg {
let pairs = data
.into_iter()
.map(|kv| (kv.key, kv.value))
.collect::<Vec<_>>();
pairs.into()
}
fn split_into_importable_execute_msgs(
kv_pairs: Vec<Model>,
approximate_max_chunk: usize,
) -> Vec<ExecuteMsg> {
let mut chunks: Vec<ExecuteMsg> = Vec::new();
let mut current_wip_chunk = Vec::new();
let mut current_chunk_size = 0;
for kv in kv_pairs {
if current_chunk_size + approximate_size(&kv) > approximate_max_chunk {
let taken = std::mem::take(&mut current_wip_chunk);
chunks.push(models_to_exec(taken));
current_chunk_size = 0;
}
current_chunk_size += approximate_size(&kv);
current_wip_chunk.push(kv);
}
if !current_wip_chunk.is_empty() {
chunks.push(models_to_exec(current_wip_chunk))
}
chunks
}
async fn set_importer_state(
state_dump_path: PathBuf,
explicit_importer_address: Option<AccountId>,
client: &DirectSigningHttpRpcNyxdClient,
) -> anyhow::Result<()> {
info!("attempting to set the importer contract state");
// this is the value that we found to be optimal during v1->v2 mixnet migration
const MAX_CHUNK_SIZE: usize = 350 * 1000;
let importer_address = importer_contract_address(explicit_importer_address)?;
if let Ok(state) = CachedState::load() {
if state.state_imported && state.importer_address == importer_address {
bail!("the state has already been imported for {importer_address}")
}
}
let dump_file = File::open(state_dump_path)?;
info!("attempting to decode the state dump. for bigger contracts this might take a while...");
let kv_pairs: Vec<Model> = serde_json::from_reader(&dump_file)?;
info!("there are {} key-value pairs to import", kv_pairs.len());
info!("attempting to split them into {MAX_CHUNK_SIZE}B chunks ExecuteMsgs...");
let chunks = split_into_importable_execute_msgs(kv_pairs, MAX_CHUNK_SIZE);
info!("obtained {} execute msgs", chunks.len());
let total = chunks.len();
for (i, msg) in chunks.into_iter().enumerate() {
info!("executing message {}/{total}...", i + 1);
let res = client
.execute(
&importer_address,
&msg,
None,
"importing contract state",
Vec::new(),
)
.await?;
info!(" ✅ OK: {}", res.transaction_hash);
}
info!("Finished migrating storage to {importer_address}!");
CachedState {
importer_address,
state_imported: true,
}
.save()?;
Ok(())
}
async fn execute_set_state(
args: SetStateArgs,
client: DirectSigningHttpRpcNyxdClient,
) -> anyhow::Result<()> {
set_importer_state(args.raw_state, args.importer_contract_address, &client).await?;
Ok(())
}
async fn swap_contract(
target_code_id: Option<u64>,
target_contract_path: Option<PathBuf>,
explicit_importer_address: Option<AccountId>,
migrate_msg: Option<serde_json::Value>,
client: &DirectSigningHttpRpcNyxdClient,
) -> anyhow::Result<()> {
info!("attempting to swap the contract code");
let importer_address = importer_contract_address(explicit_importer_address)?;
if let Ok(state) = CachedState::load() {
if !state.state_imported && state.importer_address == importer_address {
bail!("the state hasn't been imported for {importer_address}")
}
}
// one of those must have been set via clap
let code_id = match target_code_id {
Some(explicit) => explicit,
None => {
// upload the contract
let mut data = Vec::new();
File::open(target_contract_path.unwrap())?.read_to_end(&mut data)?;
let res = client.upload(data, "<empty>", None).await?;
info!(
" ✅ uploaded the target contract in {}",
res.transaction_hash
);
res.code_id
}
};
let migrate_msg = migrate_msg.unwrap_or(serde_json::Value::Object(Default::default()));
let res = client
.migrate(
&importer_address,
code_id,
&migrate_msg,
"migrating into target contract",
None,
)
.await?;
info!(
" ✅ migrated into the target contract: {}",
res.transaction_hash
);
Ok(())
}
async fn execute_swap_contract(
args: SwapContractArgs,
client: DirectSigningHttpRpcNyxdClient,
) -> anyhow::Result<()> {
swap_contract(
args.target_contract_code_id,
args.target_contract_path,
args.importer_contract_address,
args.migrate_msg,
&client,
)
.await?;
Ok(())
}
async fn initialise_with_state(
args: InitialiseWithStateArgs,
client: DirectSigningHttpRpcNyxdClient,
) -> anyhow::Result<()> {
let importer_address = create_importer_contract(args.importer_contract_path, &client).await?;
set_importer_state(args.raw_state, Some(importer_address.clone()), &client).await?;
swap_contract(
args.target_contract_code_id,
args.target_contract_path,
Some(importer_address.clone()),
args.migrate_msg,
&client,
)
.await?;
info!("the contract is ready at {importer_address}");
Ok(())
}
impl Cli {
pub async fn execute(self) -> anyhow::Result<()> {
let network_details = NymNetworkDetails::new_from_env();
let client_config = nyxd::Config::try_from_nym_network_details(&network_details)?;
let nyxd_url = network_details
.endpoints
.first()
.expect("network details are not defined")
.nyxd_url
.as_str();
let client = DirectSigningHttpRpcNyxdClient::connect_with_mnemonic(
client_config,
nyxd_url,
self.mnemonic,
)?;
match self.command {
Commands::PrepareContract(args) => execute_prepare_contract(args, client).await,
Commands::SetState(args) => execute_set_state(args, client).await,
Commands::SwapContract(args) => execute_swap_contract(args, client).await,
Commands::InitialiseWithState(args) => initialise_with_state(args, client).await,
}
}
}
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let cli = Cli::parse();
setup_env(cli.config_env_file.as_ref());
setup_tracing_logger();
cli.execute().await
}
@@ -0,0 +1,24 @@
[package]
name = "importer-contract"
version = "0.1.0"
authors.workspace = true
repository.workspace = true
homepage.workspace = true
documentation.workspace = true
edition.workspace = true
license.workspace = true
readme.workspace = true
[lib]
crate-type = ["cdylib", "rlib"]
[dependencies]
base85rs = "0.1.3"
cosmwasm-std = { workspace = true }
cosmwasm-storage = { workspace = true }
cosmwasm-schema = { workspace = true }
[features]
default = ["library"]
library = []
@@ -0,0 +1,5 @@
all: build
build:
RUSTFLAGS='-C link-arg=-s' cargo build --release --lib --target wasm32-unknown-unknown --no-default-features
wasm-opt --signext-lowering -O ../../../../target/wasm32-unknown-unknown/release/importer_contract.wasm -o ../../../../target/wasm32-unknown-unknown/release/importer_contract.wasm
@@ -0,0 +1,45 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
use crate::ExecuteMsg;
use cosmwasm_schema::cw_serde;
use cosmwasm_std::{Deps, DepsMut, Env, MessageInfo, QueryResponse, Response, StdError};
#[cw_serde]
pub struct EmptyMessage {}
#[cfg_attr(not(feature = "library"), cosmwasm_std::entry_point)]
pub fn instantiate(
_: DepsMut<'_>,
_: Env,
_: MessageInfo,
_: EmptyMessage,
) -> Result<Response, StdError> {
Ok(Response::new())
}
#[cfg_attr(not(feature = "library"), cosmwasm_std::entry_point)]
pub fn execute(
deps: DepsMut<'_>,
_env: Env,
_info: MessageInfo,
msg: ExecuteMsg,
) -> Result<Response, StdError> {
for (key, value) in msg.pairs {
let key = base85rs::decode(&key).unwrap();
let value = base85rs::decode(&value).unwrap();
deps.storage.set(&key, &value);
}
Ok(Default::default())
}
#[cfg_attr(not(feature = "library"), cosmwasm_std::entry_point)]
pub fn query(_: Deps<'_>, _: Env, _: EmptyMessage) -> Result<QueryResponse, StdError> {
Ok(Default::default())
}
#[cfg_attr(not(feature = "library"), cosmwasm_std::entry_point)]
pub fn migrate(_deps: DepsMut<'_>, _env: Env, _msg: EmptyMessage) -> Result<Response, StdError> {
Ok(Default::default())
}
@@ -0,0 +1,8 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
pub mod contract;
pub mod msg;
pub use base85rs;
pub use msg::ExecuteMsg;
@@ -0,0 +1,20 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
use cosmwasm_schema::cw_serde;
#[cw_serde]
pub struct ExecuteMsg {
pub pairs: Vec<(String, String)>,
}
impl From<Vec<(Vec<u8>, Vec<u8>)>> for ExecuteMsg {
fn from(raw: Vec<(Vec<u8>, Vec<u8>)>) -> Self {
ExecuteMsg {
pairs: raw
.into_iter()
.map(|(k, v)| (base85rs::encode(&k), base85rs::encode(&v)))
.collect(),
}
}
}
+15 -9
View File
@@ -1,4 +1,4 @@
use nym_cli_commands::context::{create_signing_client, ClientArgs};
use nym_cli_commands::context::{create_query_client, create_signing_client, ClientArgs};
use nym_network_defaults::NymNetworkDetails;
pub(crate) mod generators;
@@ -9,14 +9,14 @@ pub(crate) async fn execute(
network_details: &NymNetworkDetails,
) -> anyhow::Result<()> {
match cosmwasm.command {
Some(nym_cli_commands::validator::cosmwasm::CosmwasmCommands::Upload(args)) => {
nym_cli_commands::validator::cosmwasm::CosmwasmCommands::Upload(args) => {
nym_cli_commands::validator::cosmwasm::upload_contract::upload(
args,
create_signing_client(global_args, network_details)?,
)
.await
}
Some(nym_cli_commands::validator::cosmwasm::CosmwasmCommands::Init(args)) => {
nym_cli_commands::validator::cosmwasm::CosmwasmCommands::Init(args) => {
nym_cli_commands::validator::cosmwasm::init_contract::init(
args,
create_signing_client(global_args, network_details)?,
@@ -25,24 +25,30 @@ pub(crate) async fn execute(
.await
}
Some(nym_cli_commands::validator::cosmwasm::CosmwasmCommands::GenerateInitMessage(
generator,
)) => generators::execute(generator).await?,
Some(nym_cli_commands::validator::cosmwasm::CosmwasmCommands::Migrate(args)) => {
nym_cli_commands::validator::cosmwasm::CosmwasmCommands::GenerateInitMessage(generator) => {
generators::execute(generator).await?
}
nym_cli_commands::validator::cosmwasm::CosmwasmCommands::Migrate(args) => {
nym_cli_commands::validator::cosmwasm::migrate_contract::migrate(
args,
create_signing_client(global_args, network_details)?,
)
.await
}
Some(nym_cli_commands::validator::cosmwasm::CosmwasmCommands::Execute(args)) => {
nym_cli_commands::validator::cosmwasm::CosmwasmCommands::Execute(args) => {
nym_cli_commands::validator::cosmwasm::execute_contract::execute(
args,
create_signing_client(global_args, network_details)?,
)
.await
}
_ => unreachable!(),
nym_cli_commands::validator::cosmwasm::CosmwasmCommands::RawContractState(args) => {
nym_cli_commands::validator::cosmwasm::raw_contract_state::execute(
args,
create_query_client(network_details)?,
)
.await?
}
}
Ok(())
}