Skip to content

Commit

Permalink
Pretty print install args.
Browse files Browse the repository at this point in the history
  • Loading branch information
daniel-wong-dfinity-org committed Aug 20, 2024
1 parent 64fb154 commit eabff35
Show file tree
Hide file tree
Showing 4 changed files with 184 additions and 14 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ tiny-bip39 = "1.0.0"
tokio = { version = "1.18.5", features = ["full"] }

[dev-dependencies]
pretty_assertions = "1"
tempfile = "3.3.0"
shellwords = "1"
serial_test = "2.0.0"
Expand Down
14 changes: 8 additions & 6 deletions src/lib/format/nns_governance.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use std::fmt::Write;
use std::fmt::{Display, Write};

use anyhow::{anyhow, bail, Context};
use bigdecimal::BigDecimal;
Expand All @@ -25,10 +25,9 @@ use ic_nns_governance::pb::v1::{
};
use itertools::Itertools;
use sha2::{Digest, Sha256};
use std::fmt::Display;

use crate::lib::{
e8s_to_tokens,
display_init_args, e8s_to_tokens,
format::{format_duration_seconds, format_timestamp_seconds},
get_default_role, get_idl_string, AnyhowResult,
};
Expand Down Expand Up @@ -608,6 +607,7 @@ fn display_proposal_info(proposal_info: ProposalInfo) -> AnyhowResult<String> {

// Humanify fields (aka interpret them).

let canister_principal_id = canister_id;
let canister_id = canister_id_to_nns_canister_name(
CanisterId::unchecked_from_principal(canister_id),
);
Expand All @@ -622,16 +622,18 @@ fn display_proposal_info(proposal_info: ProposalInfo) -> AnyhowResult<String> {

let skip_stopping_before_installing =
if skip_stopping_before_installing.unwrap_or_default() {
" (Warning: canister will not be stopped before installing new WASM)"
" (Warning: canister will NOT be stopped before installing new WASM!)"
} else {
""
};

let arg = match arg {
None => "no arg".to_string(),
Some(ok) => format!("arg = {}", hex::encode(ok)),
Some(arg) => {
let args = display_init_args(&arg, canister_principal_id);
format!("init args = {}", args)
}
};
// TODO Do something like didc decode --defs $DEFS[canister_id] $arg

writeln!(
fmt,
Expand Down
182 changes: 174 additions & 8 deletions src/lib/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,20 @@ use bigdecimal::BigDecimal;
use bip32::DerivationPath;
use bip39::{Mnemonic, Seed};
use candid::{types::Function, Nat, Principal, TypeEnv};
use candid_parser::{typing::check_prog, IDLProg};
use candid_parser::{typing::check_prog, utils::CandidSource, IDLProg};
use crc32fast::Hasher;
use data_encoding::BASE32_NOPAD;
use ic_agent::{
identity::{AnonymousIdentity, BasicIdentity, Secp256k1Identity},
Agent, Identity,
};
use ic_base_types::PrincipalId;
use ic_base_types::{CanisterId, PrincipalId};
#[cfg(feature = "hsm")]
use ic_identity_hsm::HardwareIdentity;
use ic_nns_constants::{
GENESIS_TOKEN_CANISTER_ID, GOVERNANCE_CANISTER_ID, LEDGER_CANISTER_ID, REGISTRY_CANISTER_ID,
SNS_WASM_CANISTER_ID,
canister_id_to_nns_canister_name,
};
use icp_ledger::{AccountIdentifier, Subaccount};
use icrc_ledger_types::icrc1::account::Account;
Expand All @@ -26,15 +27,17 @@ use pkcs8::pkcs5::{pbes2::Parameters, scrypt::Params};
use ring::signature::Ed25519KeyPair;
use serde_cbor::Value;

#[cfg(feature = "hsm")]
use std::{cell::RefCell, path::PathBuf};
use std::{
env,
fmt::{self, Display, Formatter},
path::Path,
time::Duration,
rc::Rc,
str::FromStr,
time::{Duration, SystemTime},
};
use std::{str::FromStr, time::SystemTime};

#[cfg(feature = "hsm")]
use std::{cell::RefCell, path::PathBuf};

#[cfg(feature = "ledger")]
use self::ledger::LedgerIdentity;
Expand Down Expand Up @@ -240,6 +243,115 @@ pub fn get_idl_string(
Ok(format!("{}", result?))
}

/// Returns a string representation of init_args.
///
/// Similar to get_idl_string, but that assumes you have a blob that gets passed
/// to, or returned from a method. Whereas, this deals with data that you pass
/// during installation (or upgrade).
///
/// Ideally, the string is human-readable. Otherwise, this falls back to
/// encoding init_args as in hex.
///
/// This only works well in for some NNS canisters, specifically,
///
/// - governance
/// - ledger
/// - gtc
/// - registry
/// - sns-wasm
fn display_init_args(init_args: &[u8], canister_id: PrincipalId) -> String {
let canister_name = canister_id_to_nns_canister_name(
CanisterId::unchecked_from_principal(canister_id)
);

let main = || {
let canister_role = get_default_role(canister_id.0)
.with_context(|| {
format!(
"unable to humanize install args, because the role of {} is unknown.",
canister_id,
)
})?;

// Glean supporting information about how to (decode and) interpret args
// from (embedded) .did file.
let interface = get_local_candid(canister_id.0, canister_role)
.with_context(|| {
format!(
"unable to display install args, because we do not have \
the interface definition of the {} canister",
canister_name,
)
})?;

let (name_to_type, service) = CandidSource::Text(interface)
.load()
.with_context(|| {
format!(
"unable to display install args, because we could not \
parse the interface definition of the {} canister.",
canister_name,
)
})?;

let service = service.with_context(|| {
format!(
"unable to display install args, because there seems to \
be no service in the interface definition of the {} canister.",
canister_name,
)
})?;
let service = unwrap_type(service)
.context("unable to display install args")?;

let init_args_type = match service {
candid::types::TypeInner::Class(init_args_type, _methods) => init_args_type,
not_class => bail!("Somehow, service is not a service??? {:?}", not_class),
};

let init_args_type = init_args_type
.into_iter()
.map(|arg_type| {
let arg_type = unwrap_type(arg_type)
.context("unable to display install args")?;
match arg_type {
candid::types::TypeInner::Var(name) => {
name_to_type.find_type(&name)
.context("DO NOT MERGE")
.cloned()
}
arg_type => Ok(candid::types::Type::from(arg_type)),
}
})
.collect::<Result<Vec<_>, _>>()
.context("DO NOT MERGE")?;

// Finally, decode and interpret init args.
candid::IDLArgs::from_bytes_with_types(
init_args,
&name_to_type,
&init_args_type,
)
.context("DO NOT MERGE")
};

match main() {
Ok(ok) => format!("{}", ok),
Err(err) => { // DO NOT MERGE: Log err
hex::encode(init_args)
}
}
}

fn unwrap_type(type_: candid::types::Type) -> anyhow::Result<candid::types::TypeInner> {
let type_: Rc<_> = match type_ {
candid::types::Type(ok) => ok,
};

Rc::into_inner(type_)
.context("unable to unwrap type")
}

/// Returns pretty-printed encoding of a candid value.
pub fn display_response(
blob: &[u8],
Expand Down Expand Up @@ -638,8 +750,12 @@ pub fn key_encryption_params<'a>(salt: &'a [u8; 16], iv: &'a [u8; 16]) -> Parame

#[cfg(test)]
mod tests {
use super::{ParsedAccount, ParsedSubaccount};
use candid::Principal;
use super::{ParsedAccount, ParsedSubaccount, display_init_args};
use candid::{Encode, Principal};
use ic_base_types::PrincipalId;
use ic_nns_governance::pb::v1::Governance as GovernanceProto;
use ic_nns_constants::GOVERNANCE_CANISTER_ID;
use pretty_assertions::assert_eq;
use std::str::FromStr;

#[test]
Expand Down Expand Up @@ -754,4 +870,54 @@ mod tests {
*b"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x01\x02"
);
}

#[test]
fn test_display_init_args() {
// Step 1: Construct input.
let governance_proto = GovernanceProto {
wait_for_quiet_threshold_seconds: 123_456_789,
..Default::default()
};
let encoded = Encode!(&governance_proto).unwrap();

// Step 2: Call code under test.
let decoded = display_init_args(&encoded, PrincipalId::from(GOVERNANCE_CANISTER_ID));

// Step 3: Inspect results.
assert_eq!(
decoded,
// If you dig deep enough, you will see a line in this string that
// looks like the field value we choose at the beginning of this
// test. Other fields are "false-y" (according to their type).
// Common "false-y" values are null, vec {}, and 0. (This assert
// needs to be updated every time a field is added to
// GovernanceProto.)
"(
record {
default_followees = vec {};
making_sns_proposal = null;
most_recent_monthly_node_provider_rewards = null;
maturity_modulation_last_updated_at_timestamp_seconds = null;
wait_for_quiet_threshold_seconds = 123_456_789 : nat64;
metrics = null;
neuron_management_voting_period_seconds = null;
node_providers = vec {};
cached_daily_maturity_modulation_basis_points = null;
economics = null;
restore_aging_summary = null;
spawning_neurons = null;
latest_reward_event = null;
to_claim_transfers = vec {};
short_voting_period_seconds = 0 : nat64;
topic_followee_index = vec {};
migrations = null;
proposals = vec {};
xdr_conversion_rate = null;
in_flight_commands = vec {};
neurons = vec {};
genesis_timestamp_seconds = 0 : nat64;
},
)",
);
}
}

0 comments on commit eabff35

Please sign in to comment.