use std::collections::BTreeSet;
use std::fs;
use std::num::NonZeroU64;
use std::path::{Path, PathBuf};
use std::str::FromStr;
use assert_matches::assert_matches;
use borsh::BorshDeserialize;
use color_eyre::eyre::Result;
use data_encoding::HEXLOWER;
use namada_apps_lib::wallet::defaults::{self, is_use_device};
use namada_core::chain::Epoch;
use namada_core::dec::Dec;
use namada_core::hash::Hash;
use namada_core::storage::{DbColFam, Key};
use namada_core::token::NATIVE_MAX_DECIMAL_PLACES;
use namada_node::shell::SnapshotSync;
use namada_node::shell::testing::client::run;
use namada_node::shell::testing::node::NodeResults;
use namada_node::shell::testing::utils::{Bin, CapturedOutput};
use namada_node::storage::DbSnapshot;
use namada_sdk::account::AccountPublicKeysMap;
use namada_sdk::borsh::BorshSerializeExt;
use namada_sdk::collections::HashMap;
use namada_sdk::error::TxSubmitError;
use namada_sdk::migrations;
use namada_sdk::proof_of_stake::parameters::MAX_VALIDATOR_METADATA_LEN;
use namada_sdk::queries::RPC;
use namada_sdk::token::{self, DenominatedAmount};
use namada_sdk::tx::{self, TX_TRANSFER_WASM, Tx, VP_USER_WASM};
use namada_test_utils::TestWasms;
use test_log::test;
use crate::e2e::ledger_tests::prepare_proposal_data;
use crate::e2e::setup::apply_use_device;
use crate::e2e::setup::constants::{
ALBERT, ALBERT_KEY, APFEL, BERTHA, BERTHA_KEY, BTC, CHRISTEL, CHRISTEL_KEY,
DAEWON, DOT, ESTER, ETH, GOVERNANCE_ADDRESS, KARTOFFEL, NAM, PGF_ADDRESS,
SCHNITZEL,
};
use crate::integration::helpers::{
find_address, make_temp_account, prepare_steward_commission_update_data,
};
use crate::integration::setup;
use crate::strings::{
TX_APPLIED_SUCCESS, TX_INSUFFICIENT_BALANCE, TX_REJECTED,
};
use crate::tendermint::abci::ApplySnapshotChunkResult;
use crate::tx::tx_host_env::gov_storage::proposal::{
PGFInternalTarget, PGFTarget,
};
use crate::tx::tx_host_env::governance::cli::onchain::{
PgfFunding, StewardsUpdate,
};
use crate::tx::tx_host_env::governance::pgf::cli::steward::Commission;
#[test]
fn ledger_txs_and_queries() -> Result<()> {
let validator_one_rpc = "http://127.0.0.1:26567";
let (node, _services) = setup::setup()?;
let transfer = token::Transfer::default()
.transfer(
defaults::bertha_address(),
defaults::albert_address(),
node.native_token(),
token::DenominatedAmount::new(
token::Amount::native_whole(10),
token::NATIVE_MAX_DECIMAL_PLACES.into(),
),
)
.unwrap()
.serialize_to_vec();
let tx_data_path = node.test_dir.path().join("tx.data");
std::fs::write(&tx_data_path, transfer).unwrap();
let tx_data_path = tx_data_path.to_string_lossy();
let multisig_account =
format!("{},{},{}", BERTHA_KEY, ALBERT_KEY, CHRISTEL_KEY);
let txs_args = vec![
apply_use_device(vec![
"transparent-transfer",
"--source",
BERTHA,
"--target",
ALBERT,
"--token",
NAM,
"--amount",
"10.1",
"--signing-keys",
BERTHA_KEY,
"--node",
&validator_one_rpc,
]),
apply_use_device(vec![
"transparent-transfer",
"--source",
DAEWON,
"--target",
ALBERT,
"--token",
NAM,
"--amount",
"10.1",
"--signing-keys",
DAEWON,
"--node",
&validator_one_rpc,
]),
apply_use_device(vec![
"transparent-transfer",
"--source",
ESTER,
"--target",
ALBERT,
"--token",
NAM,
"--amount",
"10.1",
"--node",
&validator_one_rpc,
]),
apply_use_device(vec![
"update-account",
"--address",
BERTHA,
"--code-path",
VP_USER_WASM,
"--signing-keys",
BERTHA_KEY,
"--node",
&validator_one_rpc,
]),
apply_use_device(vec![
"tx",
"--code-path",
TX_TRANSFER_WASM,
"--data-path",
&tx_data_path,
"--owner",
BERTHA,
"--signing-keys",
BERTHA_KEY,
"--node",
&validator_one_rpc,
]),
apply_use_device(vec![
"init-account",
"--public-keys",
"tpknam1qpqfzxu3gt05jx2mvg82f4anf90psqerkwqhjey4zlqv0qfgwuvkzt5jhkp",
"--threshold",
"1",
"--code-path",
VP_USER_WASM,
"--alias",
"Test-Account",
"--signing-keys",
BERTHA_KEY,
"--node",
&validator_one_rpc,
]),
apply_use_device(vec![
"init-account",
"--public-keys",
&multisig_account,
"--threshold",
"2",
"--code-path",
VP_USER_WASM,
"--alias",
"Test-Account-2",
"--signing-keys",
BERTHA_KEY,
"--node",
&validator_one_rpc,
]),
];
for tx_args in &txs_args {
for &dry_run in &[true, false] {
let tx_args = if dry_run && (tx_args[0] == "tx" || is_use_device())
{
continue;
} else if dry_run {
[tx_args.clone(), vec!["--dry-run"]].concat()
} else {
tx_args.clone()
};
let captured =
CapturedOutput::of(|| run(&node, Bin::Client, tx_args));
assert_matches!(captured.result, Ok(_));
assert!(captured.contains(TX_APPLIED_SUCCESS));
}
}
let query_args_and_expected_response = vec![
(
vec![
"balance",
"--owner",
BERTHA,
"--token",
NAM,
"--node",
&validator_one_rpc,
],
vec![r"nam: \d+(\.\d+)?"],
),
(
vec![
"balance",
"--owner",
ALBERT,
"--token",
APFEL,
"--node",
&validator_one_rpc,
],
vec![r"apfel: \d+(\.\d+)?"],
),
(
vec![
"balance",
"--owner",
ALBERT,
"--token",
BTC,
"--node",
&validator_one_rpc,
],
vec![r"btc: \d+(\.\d+)?"],
),
(
vec![
"balance",
"--owner",
ALBERT,
"--token",
DOT,
"--node",
&validator_one_rpc,
],
vec![r"dot: \d+(\.\d+)?"],
),
(
vec![
"balance",
"--owner",
ALBERT,
"--token",
ETH,
"--node",
&validator_one_rpc,
],
vec![r"eth: \d+(\.\d+)?"],
),
(
vec![
"balance",
"--owner",
ALBERT,
"--token",
KARTOFFEL,
"--node",
&validator_one_rpc,
],
vec![r"kartoffel: \d+(\.\d+)?"],
),
(
vec![
"balance",
"--owner",
ALBERT,
"--token",
SCHNITZEL,
"--node",
&validator_one_rpc,
],
vec![r"schnitzel: \d+(\.\d+)?"],
),
(
vec![
"query-account",
"--owner",
"Test-Account-2",
"--node",
&validator_one_rpc,
],
vec!["Threshold: 2"],
),
];
for (query_args, expected) in query_args_and_expected_response {
let captured =
CapturedOutput::of(|| run(&node, Bin::Client, query_args));
assert_matches!(captured.result, Ok(_));
for pattern in expected {
assert!(captured.contains(pattern));
}
}
let christel = defaults::christel_address();
let nam = node.native_token();
let christel_balance = token::Amount::native_whole(2000000);
let storage_key =
token::storage_key::balance_key(&nam, &christel).to_string();
let query_args_and_expected_response = vec![
(
vec![
"query-bytes",
"--storage-key",
&storage_key,
"--node",
&validator_one_rpc,
],
HEXLOWER.encode(&christel_balance.serialize_to_vec()),
),
];
for (query_args, expected) in query_args_and_expected_response {
let captured =
CapturedOutput::of(|| run(&node, Bin::Client, query_args));
assert_matches!(captured.result, Ok(_));
assert!(captured.contains(&expected));
}
Ok(())
}
#[test]
fn invalid_transactions() -> Result<()> {
let validator_one_rpc = "http://127.0.0.1:26567";
let (node, _services) = setup::setup()?;
let tx_args = apply_use_device(vec![
"transparent-transfer",
"--source",
BERTHA,
"--target",
ALBERT,
"--token",
NAM,
"--amount",
"1",
"--signing-keys",
ALBERT_KEY,
"--node",
&validator_one_rpc,
"--force",
]);
let captured = CapturedOutput::of(|| run(&node, Bin::Client, tx_args));
assert_matches!(captured.result, Ok(_));
assert!(captured.contains(TX_REJECTED));
node.finalize_and_commit(None);
{
let locked = node.shell.lock().unwrap();
assert_ne!(
locked.last_state("").last_block_app_hash,
Default::default()
);
}
let daewon_lower = DAEWON.to_lowercase();
let tx_args = apply_use_device(vec![
"transparent-transfer",
"--source",
DAEWON,
"--signing-keys",
&daewon_lower,
"--target",
ALBERT,
"--token",
BERTHA,
"--amount",
"1000000.1",
"--force",
"--node",
&validator_one_rpc,
]);
let captured = CapturedOutput::of(|| run(&node, Bin::Client, tx_args));
assert!(captured.contains(TX_INSUFFICIENT_BALANCE));
Ok(())
}
#[test]
fn pos_rewards() -> Result<()> {
let validator_one_rpc = "http://127.0.0.1:26567";
let (mut node, _services) = setup::setup()?;
let tx_args = vec![
"rewards",
"--validator",
"validator-0-validator",
"--node",
&validator_one_rpc,
];
let captured = CapturedOutput::of(|| run(&node, Bin::Client, tx_args));
assert_matches!(captured.result, Ok(_));
let res = captured
.matches(r"Current rewards available for claim: [0-9\.]+ NAM")
.expect("Test failed");
let words = res.split(' ').collect::<Vec<_>>();
let res = words[words.len() - 2];
let mut last_amount = token::Amount::from_str(
res.split(' ').next_back().unwrap(),
NATIVE_MAX_DECIMAL_PLACES,
)
.unwrap();
for _ in 0..4 {
node.next_epoch();
let tx_args = vec![
"rewards",
"--validator",
"validator-0-validator",
"--node",
&validator_one_rpc,
];
let captured = CapturedOutput::of(|| run(&node, Bin::Client, tx_args));
assert_matches!(captured.result, Ok(_));
let res = captured
.matches(r"Current rewards available for claim: [0-9\.]+ NAM")
.expect("Test failed");
let words = res.split(' ').collect::<Vec<_>>();
let res = words[words.len() - 2];
let amount = token::Amount::from_str(
res.split(' ').next_back().unwrap(),
NATIVE_MAX_DECIMAL_PLACES,
)
.unwrap();
assert!(amount > last_amount);
last_amount = amount;
}
let query_balance_args = vec![
"balance",
"--owner",
"validator-0-validator",
"--token",
NAM,
"--node",
&validator_one_rpc,
];
let captured =
CapturedOutput::of(|| run(&node, Bin::Client, query_balance_args));
assert_matches!(captured.result, Ok(_));
let res = captured.matches(r"nam: [0-9\.]+").expect("Test failed");
let amount_pre = token::Amount::from_str(
res.split(' ').next_back().unwrap(),
NATIVE_MAX_DECIMAL_PLACES,
)
.unwrap();
let tx_args = apply_use_device(vec![
"claim-rewards",
"--validator",
"validator-0-validator",
"--signing-keys",
"validator-0-account-key",
"--node",
&validator_one_rpc,
]);
let captured = CapturedOutput::of(|| run(&node, Bin::Client, tx_args));
println!("{:?}", captured.result);
assert_matches!(captured.result, Ok(_));
assert!(captured.contains(TX_APPLIED_SUCCESS));
let query_balance_args = vec![
"balance",
"--owner",
"validator-0-validator",
"--token",
NAM,
"--node",
&validator_one_rpc,
];
let captured =
CapturedOutput::of(|| run(&node, Bin::Client, query_balance_args));
assert_matches!(captured.result, Ok(_));
let res = captured.matches(r"nam: [0-9\.]+").expect("Test failed");
let amount_post = token::Amount::from_str(
res.split(' ').next_back().unwrap(),
NATIVE_MAX_DECIMAL_PLACES,
)
.unwrap();
assert!(amount_post > amount_pre);
let query_staking_rewards_rate =
vec!["staking-rewards-rate", "--node", &validator_one_rpc];
let captured = CapturedOutput::of(|| {
run(&node, Bin::Client, query_staking_rewards_rate)
});
assert_matches!(captured.result, Ok(_));
let _res = captured
.matches(r"Current annual staking rewards rate: 65.705255877354")
.expect("Test failed");
let _res = captured
.matches(r"PoS inflation rate: 0.066593164444")
.expect("Test failed");
Ok(())
}
#[test]
fn test_bond_queries() -> Result<()> {
let validator_one_rpc = "http://127.0.0.1:26567";
let (mut node, _services) = setup::setup()?;
let validator_alias = "validator-0-validator";
let tx_args = apply_use_device(vec![
"bond",
"--validator",
validator_alias,
"--amount",
"100",
"--ledger-address",
&validator_one_rpc,
]);
let captured = CapturedOutput::of(|| run(&node, Bin::Client, tx_args));
assert_matches!(captured.result, Ok(_));
assert!(captured.contains(TX_APPLIED_SUCCESS));
let tx_args = apply_use_device(vec![
"bond",
"--validator",
"validator-0-validator",
"--source",
BERTHA,
"--amount",
"200",
"--signing-keys",
BERTHA_KEY,
"--ledger-address",
&validator_one_rpc,
]);
let captured = CapturedOutput::of(|| run(&node, Bin::Client, tx_args));
assert_matches!(captured.result, Ok(_));
assert!(captured.contains(TX_APPLIED_SUCCESS));
for _ in 0..4 {
node.next_epoch();
}
let tx_args = apply_use_device(vec![
"bond",
"--validator",
validator_alias,
"--source",
BERTHA,
"--amount",
"300",
"--signing-keys",
BERTHA_KEY,
"--ledger-address",
&validator_one_rpc,
]);
let captured = CapturedOutput::of(|| run(&node, Bin::Client, tx_args));
assert_matches!(captured.result, Ok(_));
assert!(captured.contains(TX_APPLIED_SUCCESS));
let tx_args = apply_use_device(vec![
"unbond",
"--validator",
validator_alias,
"--source",
BERTHA,
"--amount",
"412",
"--signing-keys",
BERTHA_KEY,
"--ledger-address",
&validator_one_rpc,
]);
let captured = CapturedOutput::of(|| run(&node, Bin::Client, tx_args));
assert_matches!(captured.result, Ok(_));
assert!(captured.contains(TX_APPLIED_SUCCESS));
let res = captured
.matches(r"withdrawable starting from epoch [0-9]+")
.expect("Test failed");
let withdraw_epoch =
Epoch::from_str(res.split(' ').next_back().unwrap()).unwrap();
loop {
if node.current_epoch() >= withdraw_epoch {
break;
} else {
node.next_epoch();
}
}
let tx_args = vec!["bonds", "--ledger-address", &validator_one_rpc];
let captured = CapturedOutput::of(|| run(&node, Bin::Client, tx_args));
assert_matches!(captured.result, Ok(_));
assert!(captured.contains(
"All bonds total active: 120188.000000
All bonds total: 120188.000000
All bonds total slashed: 0.000000
All unbonds total active: 412.000000
All unbonds total: 412.000000
All unbonds total withdrawable: 412.000000
All unbonds total slashed: 0.000000",
));
Ok(())
}
#[test]
fn proposal_submission() -> Result<()> {
let validator_one_rpc = "http://127.0.0.1:26567";
let (mut node, _services) = setup::setup()?;
let tx_args = apply_use_device(vec![
"bond",
"--validator",
"validator-0-validator",
"--source",
BERTHA,
"--amount",
"900",
"--node",
&validator_one_rpc,
]);
let captured = CapturedOutput::of(|| run(&node, Bin::Client, tx_args));
assert_matches!(captured.result, Ok(_));
assert!(captured.contains(TX_APPLIED_SUCCESS));
let albert = defaults::albert_address();
let valid_proposal_json_path = prepare_proposal_data(
node.test_dir.path(),
albert.clone(),
TestWasms::TxProposalCode.read_bytes(),
12,
);
let submit_proposal_args = apply_use_device(vec![
"init-proposal",
"--data-path",
valid_proposal_json_path.to_str().unwrap(),
"--gas-limit",
"2200000",
"--node",
&validator_one_rpc,
]);
let captured =
CapturedOutput::of(|| run(&node, Bin::Client, submit_proposal_args));
assert_matches!(captured.result, Ok(_));
assert!(captured.contains(TX_APPLIED_SUCCESS));
let proposal_query_args = vec![
"query-proposal",
"--proposal-id",
"0",
"--node",
&validator_one_rpc,
];
let captured =
CapturedOutput::of(|| run(&node, Bin::Client, proposal_query_args));
assert_matches!(captured.result, Ok(_));
assert!(captured.contains("Proposal Id: 0"));
let query_balance_args = vec![
"balance",
"--owner",
ALBERT,
"--token",
NAM,
"--node",
&validator_one_rpc,
];
let captured =
CapturedOutput::of(|| run(&node, Bin::Client, query_balance_args));
assert_matches!(captured.result, Ok(_));
assert!(captured.contains("nam: 1979500"));
let query_balance_args = vec![
"balance",
"--owner",
GOVERNANCE_ADDRESS,
"--token",
NAM,
"--node",
&validator_one_rpc,
];
let captured =
CapturedOutput::of(|| run(&node, Bin::Client, query_balance_args));
assert_matches!(captured.result, Ok(_));
assert!(captured.contains("nam: 500"));
while node.current_epoch().0 <= 13 {
node.next_epoch();
}
let submit_proposal_vote = apply_use_device(vec![
"vote-proposal",
"--proposal-id",
"0",
"--vote",
"yay",
"--address",
"validator-0-validator",
"--node",
&validator_one_rpc,
]);
let captured =
CapturedOutput::of(|| run(&node, Bin::Client, submit_proposal_vote));
assert_matches!(captured.result, Ok(_));
assert!(captured.contains(TX_APPLIED_SUCCESS));
let submit_proposal_vote_delegator = apply_use_device(vec![
"vote-proposal",
"--proposal-id",
"0",
"--vote",
"nay",
"--address",
BERTHA,
"--node",
&validator_one_rpc,
]);
let captured = CapturedOutput::of(|| {
run(&node, Bin::Client, submit_proposal_vote_delegator)
});
assert_matches!(captured.result, Ok(_));
assert!(captured.contains(TX_APPLIED_SUCCESS));
let submit_proposal_vote = apply_use_device(vec![
"vote-proposal",
"--proposal-id",
"0",
"--vote",
"yay",
"--address",
CHRISTEL,
"--node",
&validator_one_rpc,
]);
let captured =
CapturedOutput::of(|| run(&node, Bin::Client, submit_proposal_vote));
assert!(captured.result.is_err());
assert!(captured.err_contains(r"The account .* has no active delegations"));
while node.current_epoch().0 <= 25 {
node.next_epoch();
}
let query_proposal = vec![
"query-proposal-result",
"--proposal-id",
"0",
"--node",
&validator_one_rpc,
];
let captured =
CapturedOutput::of(|| run(&node, Bin::Client, query_proposal));
assert_matches!(captured.result, Ok(_));
assert!(captured.contains("Proposal Id: 0"));
let expected = regex::escape(
"Passed with 120000.000000 yay votes, 900.000000 nay votes and \
0.000000 abstain votes, total voting power: 120900.000000, threshold \
(fraction) of total voting power needed to tally: 48360.000000 (0.4)",
);
assert!(captured.contains(&expected));
while node.current_epoch().0 < 31 {
node.next_epoch();
}
let query_balance_args = vec![
"balance",
"--owner",
ALBERT,
"--token",
NAM,
"--node",
&validator_one_rpc,
];
let captured =
CapturedOutput::of(|| run(&node, Bin::Client, query_balance_args));
assert_matches!(captured.result, Ok(_));
assert!(captured.contains("nam: 1980000"));
let query_balance_args = vec![
"balance",
"--owner",
GOVERNANCE_ADDRESS,
"--token",
NAM,
"--node",
&validator_one_rpc,
];
let captured =
CapturedOutput::of(|| run(&node, Bin::Client, query_balance_args));
assert_matches!(captured.result, Ok(_));
assert!(captured.contains("nam: 0"));
let query_protocol_parameters =
vec!["query-protocol-parameters", "--node", &validator_one_rpc];
let captured = CapturedOutput::of(|| {
run(&node, Bin::Client, query_protocol_parameters)
});
assert_matches!(captured.result, Ok(_));
assert!(captured.contains(".*Min. proposal grace epochs: 9.*"));
let init_account = apply_use_device(vec![
"init-account",
"--public-keys",
"tpknam1qpqfzxu3gt05jx2mvg82f4anf90psqerkwqhjey4zlqv0qfgwuvkzt5jhkp",
"--threshold",
"1",
"--code-path",
VP_USER_WASM,
"--alias",
"Test-Account",
"--signing-keys",
BERTHA_KEY,
"--node",
&validator_one_rpc,
]);
let captured = CapturedOutput::of(|| run(&node, Bin::Client, init_account));
assert_matches!(captured.result, Ok(_));
assert!(
captured.contains(".*VP code is not allowed in allowlist parameter.*")
);
let transfer = apply_use_device(vec![
"transparent-transfer",
"--source",
BERTHA,
"--target",
ALBERT,
"--token",
NAM,
"--amount",
"10",
"--signing-keys",
BERTHA_KEY,
"--node",
&validator_one_rpc,
]);
let captured = CapturedOutput::of(|| run(&node, Bin::Client, transfer));
assert_matches!(captured.result, Ok(_));
assert!(captured.contains(TX_APPLIED_SUCCESS));
Ok(())
}
#[test]
fn inflation() -> Result<()> {
let validator_one_rpc = "http://127.0.0.1:26567";
let (mut node, _services) = setup::initialize_genesis(|mut genesis| {
genesis.parameters.pos_params.max_inflation_rate =
Dec::from_str("0.1").unwrap();
genesis.parameters.pgf_params.stewards_inflation_rate =
Dec::from_str("0.1").unwrap();
genesis.parameters.pgf_params.pgf_inflation_rate =
Dec::from_str("0.1").unwrap();
genesis.parameters.pgf_params.stewards =
BTreeSet::from_iter([defaults::albert_address()]);
genesis
})?;
let pos_inflation = [
118400000.813463,
118400001.689407,
118400002.627832,
118400003.628738,
118400004.692125,
];
let steward_inflation = [
1980000.375443,
1980000.750886,
1980001.126329,
1980001.501772,
1980001.877215,
];
let pgf_inflation = [0.41299, 0.819698, 1.242026, 1.679974, 2.133543];
for epoch in 0..5 {
node.next_epoch();
let query_total_supply_args = vec![
"total-supply",
"--token",
NAM,
"--ledger-address",
&validator_one_rpc,
];
let captured = CapturedOutput::of(|| {
run(&node, Bin::Client, query_total_supply_args)
});
assert_matches!(captured.result, Ok(_));
assert!(captured.contains(&format!(
"token tnam1q9kn74xfzytqkqyycfrhycr8ajam8ny935cge0z5: {}",
pos_inflation[epoch]
)));
let query_balance_args = vec![
"balance",
"--owner",
PGF_ADDRESS,
"--token",
NAM,
"--ledger-address",
&validator_one_rpc,
];
let captured =
CapturedOutput::of(|| run(&node, Bin::Client, query_balance_args));
assert_matches!(captured.result, Ok(_));
assert!(captured.contains(&format!("nam: {}", pgf_inflation[epoch])));
let query_balance_args = vec![
"balance",
"--owner",
ALBERT,
"--token",
NAM,
"--ledger-address",
&validator_one_rpc,
];
let captured =
CapturedOutput::of(|| run(&node, Bin::Client, query_balance_args));
assert_matches!(captured.result, Ok(_));
assert!(
captured.contains(&format!("nam: {}", steward_inflation[epoch]))
);
let query_balance_args = vec![
"balance",
"--owner",
BERTHA,
"--token",
NAM,
"--ledger-address",
&validator_one_rpc,
];
let captured =
CapturedOutput::of(|| run(&node, Bin::Client, query_balance_args));
assert_matches!(captured.result, Ok(_));
assert!(captured.contains(&format!("nam: {}", 2000000)));
}
Ok(())
}
#[test]
fn pgf_governance_proposal() -> Result<()> {
let validator_one_rpc = "http://127.0.0.1:26567";
let (mut node, _services) = setup::setup()?;
let tx_args = apply_use_device(vec![
"bond",
"--validator",
"validator-0-validator",
"--source",
BERTHA,
"--amount",
"900",
"--ledger-address",
&validator_one_rpc,
]);
let captured = CapturedOutput::of(|| run(&node, Bin::Client, tx_args));
assert_matches!(captured.result, Ok(_));
assert!(captured.contains(TX_APPLIED_SUCCESS));
let albert = defaults::albert_address();
let pgf_stewards = StewardsUpdate {
add: Some(albert.clone()),
remove: vec![],
};
let valid_proposal_json_path =
prepare_proposal_data(node.test_dir.path(), albert, pgf_stewards, 12);
let submit_proposal_args = apply_use_device(vec![
"init-proposal",
"--pgf-stewards",
"--data-path",
valid_proposal_json_path.to_str().unwrap(),
"--ledger-address",
&validator_one_rpc,
]);
let captured =
CapturedOutput::of(|| run(&node, Bin::Client, submit_proposal_args));
assert_matches!(captured.result, Ok(_));
assert!(captured.contains(TX_APPLIED_SUCCESS));
let proposal_query_args = vec![
"query-proposal",
"--proposal-id",
"0",
"--ledger-address",
&validator_one_rpc,
];
let captured =
CapturedOutput::of(|| run(&node, Bin::Client, proposal_query_args));
assert_matches!(captured.result, Ok(_));
assert!(captured.contains("Proposal Id: 0"));
let query_balance_args = vec![
"balance",
"--owner",
ALBERT,
"--token",
NAM,
"--ledger-address",
&validator_one_rpc,
];
let captured =
CapturedOutput::of(|| run(&node, Bin::Client, query_balance_args));
assert_matches!(captured.result, Ok(_));
assert!(captured.contains("nam: 1979500"));
let query_balance_args = vec![
"balance",
"--owner",
GOVERNANCE_ADDRESS,
"--token",
NAM,
"--ledger-address",
&validator_one_rpc,
];
let captured =
CapturedOutput::of(|| run(&node, Bin::Client, query_balance_args));
assert_matches!(captured.result, Ok(_));
assert!(captured.contains("nam: 500"));
while node.current_epoch().0 <= 13 {
node.next_epoch();
}
let submit_proposal_vote = apply_use_device(vec![
"vote-proposal",
"--proposal-id",
"0",
"--vote",
"yay",
"--address",
"validator-0-validator",
"--ledger-address",
&validator_one_rpc,
]);
let captured =
CapturedOutput::of(|| run(&node, Bin::Client, submit_proposal_vote));
assert_matches!(captured.result, Ok(_));
assert!(captured.contains(TX_APPLIED_SUCCESS));
let submit_proposal_vote_delegator = apply_use_device(vec![
"vote-proposal",
"--proposal-id",
"0",
"--vote",
"yay",
"--address",
BERTHA,
"--ledger-address",
&validator_one_rpc,
]);
let captured = CapturedOutput::of(|| {
run(&node, Bin::Client, submit_proposal_vote_delegator)
});
assert_matches!(captured.result, Ok(_));
assert!(captured.contains(TX_APPLIED_SUCCESS));
while node.current_epoch().0 <= 25 {
node.next_epoch();
}
let query_proposal = vec![
"query-proposal-result",
"--proposal-id",
"0",
"--ledger-address",
&validator_one_rpc,
];
let captured =
CapturedOutput::of(|| run(&node, Bin::Client, query_proposal));
assert_matches!(captured.result, Ok(_));
assert!(captured.contains("Passed"));
while node.current_epoch().0 < 31 {
node.next_epoch();
}
let query_balance_args = vec![
"balance",
"--owner",
ALBERT,
"--token",
NAM,
"--ledger-address",
&validator_one_rpc,
];
let captured =
CapturedOutput::of(|| run(&node, Bin::Client, query_balance_args));
assert_matches!(captured.result, Ok(_));
assert!(captured.contains("nam: 1980000"));
let query_balance_args = vec![
"balance",
"--owner",
GOVERNANCE_ADDRESS,
"--token",
NAM,
"--ledger-address",
&validator_one_rpc,
];
let captured =
CapturedOutput::of(|| run(&node, Bin::Client, query_balance_args));
assert_matches!(captured.result, Ok(_));
assert!(captured.contains("nam: 0"));
let query_pgf = vec!["query-pgf", "--node", &validator_one_rpc];
let captured = CapturedOutput::of(|| run(&node, Bin::Client, query_pgf));
assert_matches!(captured.result, Ok(_));
assert!(captured.contains("Pgf stewards:"));
assert!(captured.contains(&format!("- {}", defaults::albert_address())));
assert!(captured.contains("Reward distribution:"));
assert!(
captured.contains(&format!("- 1 to {}", defaults::albert_address()))
);
assert!(captured.contains("Pgf fundings: no fundings are currently set."));
let query_balance_args = vec![
"balance",
"--owner",
PGF_ADDRESS,
"--token",
NAM,
"--ledger-address",
&validator_one_rpc,
];
let captured =
CapturedOutput::of(|| run(&node, Bin::Client, query_balance_args));
assert_matches!(captured.result, Ok(_));
assert!(captured.contains("nam: 14.267253"));
let query_total_supply_args = vec![
"total-supply",
"--token",
NAM,
"--ledger-address",
&validator_one_rpc,
];
let captured =
CapturedOutput::of(|| run(&node, Bin::Client, query_total_supply_args));
assert_matches!(captured.result, Ok(_));
assert!(captured.contains(
"token tnam1q9kn74xfzytqkqyycfrhycr8ajam8ny935cge0z5: 118400024.740301"
));
let query_native_supply_args =
vec!["native-supply", "--ledger-address", &validator_one_rpc];
let captured = CapturedOutput::of(|| {
run(&node, Bin::Client, query_native_supply_args)
});
assert_matches!(captured.result, Ok(_));
assert!(captured.contains("nam: 118400010.473048"));
let albert = defaults::albert_address();
let bertha = defaults::bertha_address();
let christel = defaults::christel_address();
let pgf_funding = PgfFunding {
continuous: vec![PGFTarget::Internal(PGFInternalTarget {
amount: token::Amount::from_u64(10),
target: bertha.clone(),
})],
retro: vec![PGFTarget::Internal(PGFInternalTarget {
amount: token::Amount::from_u64(5),
target: christel,
})],
};
let valid_proposal_json_path =
prepare_proposal_data(node.test_dir.path(), albert, pgf_funding, 36);
let submit_proposal_args = apply_use_device(vec![
"init-proposal",
"--pgf-funding",
"--data-path",
valid_proposal_json_path.to_str().unwrap(),
"--ledger-address",
&validator_one_rpc,
]);
let captured =
CapturedOutput::of(|| run(&node, Bin::Client, submit_proposal_args));
assert_matches!(captured.result, Ok(_));
assert!(captured.contains(TX_APPLIED_SUCCESS));
let proposal_query_args = vec![
"query-proposal",
"--proposal-id",
"1",
"--ledger-address",
&validator_one_rpc,
];
let captured =
CapturedOutput::of(|| run(&node, Bin::Client, proposal_query_args));
assert_matches!(captured.result, Ok(_));
assert!(captured.contains("Proposal Id: 1"));
while node.current_epoch().0 < 55 {
node.next_epoch();
}
let query_pgf = vec!["query-pgf", "--node", &validator_one_rpc];
let captured = CapturedOutput::of(|| run(&node, Bin::Client, query_pgf));
assert_matches!(captured.result, Ok(_));
assert!(captured.contains("Pgf fundings"));
assert!(captured.contains(&format!(
"{} for {}",
bertha,
token::Amount::from_u64(10).to_string_native()
)));
Ok(())
}
#[test]
fn pgf_steward_change_commission() -> Result<()> {
let validator_one_rpc = "http://127.0.0.1:26567";
let (node, _services) = setup::initialize_genesis(|mut genesis| {
genesis.parameters.pgf_params.stewards_inflation_rate =
Dec::from_str("0.1").unwrap();
genesis.parameters.pgf_params.stewards =
BTreeSet::from_iter([defaults::albert_address()]);
genesis
})?;
let query_pgf = vec!["query-pgf", "--node", &validator_one_rpc];
let captured = CapturedOutput::of(|| run(&node, Bin::Client, query_pgf));
assert_matches!(captured.result, Ok(_));
assert!(captured.contains("Pgf stewards:"));
assert!(captured.contains(&format!("- {}", defaults::albert_address())));
assert!(captured.contains("Reward distribution:"));
assert!(
captured.contains(&format!("- 1 to {}", defaults::albert_address()))
);
assert!(captured.contains("Pgf fundings: no fundings are currently set."));
let commission = Commission {
reward_distribution: HashMap::from_iter([
(defaults::albert_address(), Dec::from_str("0.25").unwrap()),
(defaults::bertha_address(), Dec::from_str("0.70").unwrap()),
(defaults::christel_address(), Dec::from_str("0.05").unwrap()),
]),
};
let commission_path = prepare_steward_commission_update_data(
node.test_dir.path(),
commission,
);
let tx_args = apply_use_device(vec![
"update-steward-rewards",
"--steward",
ALBERT,
"--data-path",
commission_path.to_str().unwrap(),
"--node",
&validator_one_rpc,
]);
let captured = CapturedOutput::of(|| run(&node, Bin::Client, tx_args));
assert_matches!(captured.result, Ok(_));
assert!(captured.contains(TX_APPLIED_SUCCESS));
let query_pgf = vec!["query-pgf", "--node", &validator_one_rpc];
let captured = CapturedOutput::of(|| run(&node, Bin::Client, query_pgf));
assert_matches!(captured.result, Ok(_));
assert!(captured.contains("Pgf stewards:"));
assert!(captured.contains(&format!("- {}", defaults::albert_address())));
assert!(captured.contains("Reward distribution:"));
assert!(
captured.contains(&format!("- 0.25 to {}", defaults::albert_address()))
);
assert!(
captured.contains(&format!("- 0.7 to {}", defaults::bertha_address()))
);
assert!(
captured
.contains(&format!("- 0.05 to {}", defaults::christel_address()))
);
assert!(captured.contains("Pgf fundings: no fundings are currently set."));
Ok(())
}
#[test]
fn implicit_account_reveal_pk() -> Result<()> {
let validator_one_rpc = "http://127.0.0.1:26567";
let (node, _services) = setup::setup()?;
#[allow(clippy::type_complexity)]
let txs_args: Vec<Box<dyn Fn(&str) -> Vec<String>>> = vec![
Box::new(|source| {
let author = find_address(&node, source).unwrap();
let valid_proposal_json_path = prepare_proposal_data(
node.test_dir.path(),
author,
TestWasms::TxProposalCode.read_bytes(),
12,
);
vec![
"init-proposal",
"--data-path",
valid_proposal_json_path.to_str().unwrap(),
"--signing-keys",
source,
"--gas-limit",
"2200000",
"--node",
&validator_one_rpc,
]
.into_iter()
.map(|x| x.to_owned())
.collect()
}),
Box::new(|source| {
[
"transparent-transfer",
"--source",
source,
"--target",
ALBERT,
"--token",
NAM,
"--amount",
"10.1",
"--signing-keys",
source,
"--node",
validator_one_rpc,
]
.into_iter()
.map(|x| x.to_owned())
.collect()
}),
Box::new(|source| {
vec![
"bond",
"--validator",
"validator-0-validator",
"--source",
source,
"--amount",
"10.1",
"--signing-keys",
source,
"--node",
&validator_one_rpc,
]
.into_iter()
.map(|x| x.to_owned())
.collect()
}),
];
for (ix, tx_args) in txs_args.into_iter().enumerate() {
let key_alias = format!("key-{ix}");
run(
&node,
Bin::Wallet,
vec![
"gen",
"--alias",
&key_alias,
"--unsafe-dont-encrypt",
"--raw",
],
)?;
let tx_args = tx_args(&key_alias);
let credit_args = apply_use_device(vec![
"transparent-transfer",
"--source",
BERTHA,
"--target",
&key_alias,
"--token",
NAM,
"--amount",
"2000",
"--signing-keys",
BERTHA_KEY,
"--node",
&validator_one_rpc,
]);
let captured =
CapturedOutput::of(|| run(&node, Bin::Client, credit_args));
assert!(captured.result.is_ok());
assert!(captured.contains(TX_APPLIED_SUCCESS));
let captured = CapturedOutput::of(|| {
run(
&node,
Bin::Client,
tx_args.iter().map(|arg| arg.as_ref()).collect(),
)
});
assert!(captured.result.is_ok());
assert!(captured.contains("Submitting a tx to reveal the public key"));
assert!(captured.contains(TX_APPLIED_SUCCESS));
let captured = CapturedOutput::of(|| {
run(
&node,
Bin::Client,
tx_args.iter().map(|arg| arg.as_ref()).collect(),
)
});
assert!(captured.result.is_ok());
assert!(!captured.contains("Submitting a tx to reveal the public key"));
assert!(captured.result.is_ok());
assert!(captured.contains(TX_APPLIED_SUCCESS));
}
Ok(())
}
#[test]
fn change_validator_metadata() -> Result<()> {
let validator_one_rpc = "http://127.0.0.1:26567";
let (node, _services) = setup::setup()?;
let metadata_query_args = vec![
"validator-metadata",
"--validator",
"validator-0-validator",
"--node",
&validator_one_rpc,
];
let captured = CapturedOutput::of(|| {
run(&node, Bin::Client, metadata_query_args.clone())
});
assert_matches!(captured.result, Ok(_));
assert!(captured.contains("No validator name"));
assert!(captured.contains("Email:"));
assert!(captured.contains("No description"));
assert!(captured.contains("No website"));
assert!(captured.contains("No discord handle"));
assert!(captured.contains("commission rate:"));
assert!(captured.contains("max change per epoch:"));
let metadata_change_args = apply_use_device(vec![
"change-metadata",
"--validator",
"validator-0-validator",
"--name",
"theokayestvalidator",
"--email",
"theokayestvalidator@namada.net",
"--description",
"We are just an okay validator node trying to get by",
"--website",
"theokayestvalidator.com",
"--node",
&validator_one_rpc,
]);
let captured =
CapturedOutput::of(|| run(&node, Bin::Client, metadata_change_args));
assert_matches!(captured.result, Ok(_));
assert!(captured.contains(TX_APPLIED_SUCCESS));
let captured = CapturedOutput::of(|| {
run(&node, Bin::Client, metadata_query_args.clone())
});
assert_matches!(captured.result, Ok(_));
assert!(captured.contains("Validator name: theokayestvalidator"));
assert!(captured.contains("Email: theokayestvalidator@namada.net"));
assert!(captured.contains(
"Description: We are just an okay validator node trying to get by"
));
assert!(captured.contains("Website: theokayestvalidator.com"));
assert!(captured.contains("No discord handle"));
assert!(captured.contains("commission rate:"));
assert!(captured.contains("max change per epoch:"));
let metadata_change_args = apply_use_device(vec![
"change-metadata",
"--validator",
"validator-0-validator",
"--website",
"",
"--node",
&validator_one_rpc,
]);
let captured =
CapturedOutput::of(|| run(&node, Bin::Client, metadata_change_args));
assert_matches!(captured.result, Ok(_));
assert!(captured.contains(TX_APPLIED_SUCCESS));
let captured =
CapturedOutput::of(|| run(&node, Bin::Client, metadata_query_args));
assert_matches!(captured.result, Ok(_));
assert!(captured.contains("Validator name: theokayestvalidator"));
assert!(captured.contains("Email: theokayestvalidator@namada.net"));
assert!(captured.contains(
"Description: We are just an okay validator node trying to get by"
));
assert!(captured.contains("No website"));
assert!(captured.contains("No discord handle"));
assert!(captured.contains("commission rate:"));
assert!(captured.contains("max change per epoch:"));
Ok(())
}
#[test]
fn offline_sign() -> Result<()> {
let validator_one_rpc = "http://127.0.0.1:26567";
let (node, _services) = setup::setup()?;
let (bradley_alias, _bradley_key) =
make_temp_account(&node, validator_one_rpc, "Bradley", NAM, 500_000)?;
let (cooper_alias, _cooper_key) =
make_temp_account(&node, validator_one_rpc, "Cooper", NAM, 500_000)?;
let output_folder = tempfile::tempdir().unwrap();
let captured = CapturedOutput::of(|| {
run(
&node,
Bin::Client,
apply_use_device(vec![
"transparent-transfer",
"--source",
bradley_alias.as_ref(),
"--target",
ALBERT,
"--token",
NAM,
"--amount",
"100",
"--gas-limit",
"200000",
"--gas-price",
"1",
"--gas-payer",
cooper_alias.as_ref(),
"--node",
&validator_one_rpc,
"--dump-wrapper-tx",
"--output-folder-path",
&output_folder.path().to_str().unwrap(),
]),
)
});
assert!(captured.result.is_ok());
let offline_tx = find_files_with_ext(output_folder.path(), "tx")
.unwrap()
.first()
.expect("Offline tx should be found.")
.to_path_buf()
.display()
.to_string();
let captured = CapturedOutput::of(|| {
run(
&node,
Bin::Client,
vec![
"utils",
"sign-offline",
"--data-path",
&offline_tx,
"--secret-keys",
&bradley_alias.as_ref(),
"--secret-key",
&cooper_alias.as_ref(),
"--output-folder-path",
&output_folder.path().to_str().unwrap(),
],
)
});
assert!(captured.result.is_ok());
let sig_files = find_files_with_ext(output_folder.path(), "sig").unwrap();
assert_eq!(sig_files.len(), 2);
let offline_sig = sig_files
.iter()
.find(|path| {
path.file_name()
.unwrap()
.to_str()
.unwrap()
.starts_with("offline_signature")
})
.expect("Offline signature should be found.")
.to_path_buf()
.display()
.to_string();
let offline_wrapper_sig = sig_files
.iter()
.find(|path| {
path.file_name()
.unwrap()
.to_str()
.unwrap()
.starts_with("offline_wrapper_signature")
})
.expect("Offline wrapper signature should be found.")
.to_path_buf()
.display()
.to_string();
let captured = CapturedOutput::of(|| {
run(
&node,
Bin::Client,
vec![
"tx",
"--tx-path",
&offline_tx,
"--signatures",
&offline_sig,
"--gas-signature",
&offline_wrapper_sig,
"--node",
&validator_one_rpc,
],
)
});
assert!(captured.result.is_ok());
let captured = CapturedOutput::of(|| {
run(
&node,
Bin::Client,
vec![
"balance",
"--owner",
bradley_alias.as_ref(),
"--token",
NAM,
"--node",
validator_one_rpc,
],
)
});
assert!(captured.result.is_ok());
assert!(captured.contains("nam: 499900"));
let captured = CapturedOutput::of(|| {
run(
&node,
Bin::Client,
vec![
"balance",
"--owner",
cooper_alias.as_ref(),
"--token",
NAM,
"--node",
validator_one_rpc,
],
)
});
assert!(captured.result.is_ok());
assert!(captured.contains("nam: 300000"));
Ok(())
}
#[test]
fn enforce_fee_payment() -> Result<()> {
let validator_one_rpc = "http://127.0.0.1:26567";
let (node, _services) = setup::setup()?;
let (adam_alias, adam_key) =
make_temp_account(&node, validator_one_rpc, "Adam", NAM, 2_000_000)?;
let tempdir = tempfile::tempdir().unwrap();
let mut txs_bytes = vec![];
let captured = CapturedOutput::of(|| {
run(
&node,
Bin::Client,
vec![
"balance",
"--owner",
adam_alias.as_ref(),
"--token",
NAM,
"--node",
validator_one_rpc,
],
)
});
assert!(captured.result.is_ok());
assert!(captured.contains("nam: 2000000"));
run(
&node,
Bin::Client,
apply_use_device(vec![
"transparent-transfer",
"--source",
adam_alias.as_ref(),
"--target",
BERTHA,
"--token",
NAM,
"--amount",
"1900000",
"--output-folder-path",
tempdir.path().to_str().unwrap(),
"--dump-tx",
"--ledger-address",
validator_one_rpc,
]),
)?;
assert!(captured.result.is_ok());
let file_path = tempdir
.path()
.read_dir()
.unwrap()
.next()
.unwrap()
.unwrap()
.path();
txs_bytes.push(std::fs::read(&file_path).unwrap());
std::fs::remove_file(&file_path).unwrap();
run(
&node,
Bin::Client,
apply_use_device(vec![
"transparent-transfer",
"--source",
adam_alias.as_ref(),
"--target",
CHRISTEL,
"--token",
NAM,
"--amount",
"50",
"--gas-payer",
adam_alias.as_ref(),
"--output-folder-path",
tempdir.path().to_str().unwrap(),
"--dump-tx",
"--ledger-address",
validator_one_rpc,
]),
)?;
assert!(captured.result.is_ok());
let file_path = tempdir
.path()
.read_dir()
.unwrap()
.next()
.unwrap()
.unwrap()
.path();
txs_bytes.push(std::fs::read(&file_path).unwrap());
std::fs::remove_file(&file_path).unwrap();
let sk = adam_key;
let pk = sk.to_public();
let native_token = node
.shell
.lock()
.unwrap()
.state
.in_mem()
.native_token
.clone();
let mut txs = vec![];
for bytes in txs_bytes {
let mut tx = Tx::try_from_json_bytes(&bytes).unwrap();
tx.add_wrapper(
tx::data::wrapper::Fee {
amount_per_gas_unit: DenominatedAmount::native(
token::Amount::native_whole(1),
),
token: native_token.clone(),
},
pk.clone(),
100_000.into(),
);
tx.sign_raw(vec![sk.clone()], AccountPublicKeysMap::default(), None);
tx.sign_wrapper(sk.clone());
txs.push(tx.to_bytes());
}
node.clear_results();
node.submit_txs(txs);
let codes = node.tx_result_codes.lock().unwrap();
assert!(!codes.is_empty());
for code in codes.iter() {
assert!(matches!(code, NodeResults::Ok));
}
let results = node.tx_results.lock().unwrap();
assert_eq!(results.len(), 2);
let first_result = &results[0];
let second_result = &results[1];
assert_eq!(first_result.len(), 1);
assert_eq!(second_result.len(), 1);
assert!(first_result.are_any_err());
assert!(second_result.are_results_successfull());
let captured = CapturedOutput::of(|| {
run(
&node,
Bin::Client,
vec![
"balance",
"--owner",
adam_alias.as_ref(),
"--token",
NAM,
"--node",
validator_one_rpc,
],
)
});
assert!(captured.result.is_ok());
assert!(captured.contains("nam: 1799950"));
let captured = CapturedOutput::of(|| {
run(
&node,
Bin::Client,
vec![
"balance",
"--owner",
BERTHA,
"--token",
NAM,
"--node",
validator_one_rpc,
],
)
});
assert!(captured.result.is_ok());
assert!(captured.contains("nam: 2000000"));
let captured = CapturedOutput::of(|| {
run(
&node,
Bin::Client,
vec![
"balance",
"--owner",
CHRISTEL,
"--token",
NAM,
"--node",
validator_one_rpc,
],
)
});
assert!(captured.result.is_ok());
assert!(captured.contains("nam: 2000050"));
Ok(())
}
#[test]
fn apply_snapshot() -> Result<()> {
use namada_node::tendermint::abci::{
request as tm_request, response as tm_response,
};
let validator_one_rpc = "http://127.0.0.1:26567";
let (mut node, _services) = setup::setup()?;
{
let mut locked = node.shell.lock().unwrap();
locked.blocks_between_snapshots =
Some(NonZeroU64::try_from(10_000u64).unwrap());
}
for _ in 0..3 {
node.next_epoch();
}
let tx_args = apply_use_device(vec![
"transparent-transfer",
"--source",
BERTHA,
"--target",
ALBERT,
"--token",
NAM,
"--amount",
"1234",
"--signing-keys",
BERTHA_KEY,
"--node",
&validator_one_rpc,
"--force",
]);
let captured = CapturedOutput::of(|| run(&node, Bin::Client, tx_args));
assert_matches!(captured.result, Ok(_));
assert!(captured.contains(TX_APPLIED_SUCCESS));
let args = vec![
"balance",
"--owner",
ALBERT,
"--token",
NAM,
"--node",
&validator_one_rpc,
];
let captured = CapturedOutput::of(|| run(&node, Bin::Client, args));
assert!(captured.contains("1981234"));
namada_sdk::state::DB::flush(node.shell.lock().unwrap().state.db(), true)
.unwrap();
let base_dir = node.test_dir.path();
let db = namada_node::storage::open(node.db_path(), true, None)
.expect("Could not open DB");
let last_height = node.block_height();
let snapshot = db
.checkpoint(base_dir.to_path_buf(), last_height)
.expect("Test failed");
snapshot.package().expect("Test failed");
DbSnapshot::cleanup(last_height, base_dir, 1).expect("Test failed");
let (node2, _services) = setup::setup()?;
let (offer, resp) = {
let shell = node.shell.lock().unwrap();
let offer =
shell.list_snapshots().snapshots.pop().expect("Test failed");
let mut shell = node2.shell.lock().unwrap();
(
offer.clone(),
shell.offer_snapshot(tm_request::OfferSnapshot {
snapshot: offer,
app_hash: Default::default(),
}),
)
};
assert_eq!(tm_response::OfferSnapshot::Accept, resp);
{
let shell = node.shell.lock().unwrap();
let mut shell2 = node2.shell.lock().unwrap();
for c in 0..offer.chunks {
let chunk =
shell.load_snapshot_chunk(tm_request::LoadSnapshotChunk {
height: (last_height.0 as u32).into(),
format: 0,
chunk: c,
});
let resp =
shell2.apply_snapshot_chunk(tm_request::ApplySnapshotChunk {
index: c,
chunk: chunk.chunk,
sender: "".to_string(),
});
assert_eq!(
resp,
tm_response::ApplySnapshotChunk {
result: ApplySnapshotChunkResult::Accept,
refetch_chunks: vec![],
reject_senders: vec![],
}
);
}
}
let (app_hash1, app_hash2) = {
(
node.shell.lock().unwrap().state.in_mem().merkle_root(),
node2.shell.lock().unwrap().state.in_mem().merkle_root(),
)
};
assert_eq!(app_hash1, app_hash2);
let args = vec![
"balance",
"--owner",
ALBERT,
"--token",
NAM,
"--node",
&validator_one_rpc,
];
let captured = CapturedOutput::of(|| run(&node2, Bin::Client, args));
assert!(captured.contains("1981234"));
Ok(())
}
#[test]
fn snapshot_unhappy_flows() -> Result<()> {
use namada_node::tendermint::abci::{
request as tm_request, response as tm_response,
};
let (node, _services) = setup::setup()?;
let resp = {
let mut shell = node.shell.lock().unwrap();
shell.apply_snapshot_chunk(tm_request::ApplySnapshotChunk {
index: 0,
chunk: Default::default(),
sender: "".to_string(),
})
};
assert_eq!(
resp,
tm_response::ApplySnapshotChunk {
result: ApplySnapshotChunkResult::Abort,
refetch_chunks: vec![],
reject_senders: vec![],
}
);
{
let mut locked = node.shell.lock().unwrap();
locked.syncing = Some(SnapshotSync {
next_chunk: 0,
height: Default::default(),
expected: vec![Default::default()],
strikes: 0,
snapshot: tempfile::tempfile().unwrap(),
});
}
let resp = {
let mut shell = node.shell.lock().unwrap();
shell.apply_snapshot_chunk(tm_request::ApplySnapshotChunk {
index: 1,
chunk: Default::default(),
sender: "".to_string(),
})
};
assert_eq!(
resp,
tm_response::ApplySnapshotChunk {
result: ApplySnapshotChunkResult::Unknown,
refetch_chunks: vec![0],
reject_senders: vec![],
}
);
for _ in 0..4 {
let resp = {
let mut shell = node.shell.lock().unwrap();
shell.apply_snapshot_chunk(tm_request::ApplySnapshotChunk {
index: 0,
chunk: Default::default(),
sender: "".to_string(),
})
};
assert_eq!(
resp,
tm_response::ApplySnapshotChunk {
result: ApplySnapshotChunkResult::Retry,
refetch_chunks: vec![0],
reject_senders: vec![],
}
);
}
let resp = {
let mut shell = node.shell.lock().unwrap();
shell.apply_snapshot_chunk(tm_request::ApplySnapshotChunk {
index: 0,
chunk: Default::default(),
sender: "satan".to_string(),
})
};
assert_eq!(
resp,
tm_response::ApplySnapshotChunk {
result: ApplySnapshotChunkResult::RejectSnapshot,
refetch_chunks: vec![],
reject_senders: vec!["satan".to_string()],
}
);
Ok(())
}
#[test]
fn scheduled_migration() -> Result<()> {
let (node, _services) = setup::setup()?;
let (hash, migrations_file) = make_migration_json();
let scheduled_migration = migrations::ScheduledMigration::from_path(
migrations_file.path(),
hash,
5.into(),
)
.expect("Test failed");
{
let mut locked = node.shell.lock().unwrap();
locked.scheduled_migration = Some(scheduled_migration);
}
while node.block_height().0 != 4 {
node.finalize_and_commit(None)
}
let rt = tokio::runtime::Runtime::new().unwrap();
let bytes = rt
.block_on(RPC.shell().storage_value(
&node,
None,
None,
false,
&Key::parse("bing/fucking/bong").expect("Test failed"),
))
.expect("Test failed")
.data;
assert!(bytes.is_empty());
node.finalize_and_commit(None);
let rt = tokio::runtime::Runtime::new().unwrap();
let bytes = rt
.block_on(RPC.shell().storage_value(
&node,
None,
None,
false,
&Key::parse("bing/fucking/bong").expect("Test failed"),
))
.expect("Test failed")
.data;
let amount = token::Amount::try_from_slice(&bytes).expect("Test failed");
assert_eq!(amount, token::Amount::native_whole(1337));
{
let locked = node.shell.lock().unwrap();
assert!(locked.scheduled_migration.is_none());
}
Ok(())
}
#[test]
fn wrap_tx_by_elsewho() -> Result<()> {
let (node, _services) = setup::setup()?;
let key_alias = "new-account";
let captured = CapturedOutput::of(|| {
run(
&node,
Bin::Wallet,
vec!["gen", "--alias", key_alias, "--unsafe-dont-encrypt"],
)
});
assert!(captured.result.is_ok());
let captured = CapturedOutput::of(|| {
run(
&node,
Bin::Client,
apply_use_device(vec![
"transparent-transfer",
"--source",
ALBERT,
"--target",
key_alias,
"--token",
NAM,
"--amount",
"0.500001",
]),
)
});
assert!(captured.result.is_ok());
assert!(captured.contains(TX_APPLIED_SUCCESS));
let captured = CapturedOutput::of(|| {
run(
&node,
Bin::Client,
apply_use_device(vec!["reveal-pk", "--public-key", key_alias]),
)
});
assert!(captured.result.is_ok());
assert!(captured.contains(TX_APPLIED_SUCCESS));
let captured = CapturedOutput::of(|| {
run(
&node,
Bin::Client,
vec!["balance", "--owner", key_alias, "--token", NAM],
)
});
assert!(captured.result.is_ok());
assert!(captured.contains("nam: 0.000001"));
let captured = CapturedOutput::of(|| {
run(
&node,
Bin::Client,
apply_use_device(vec![
"transparent-transfer",
"--source",
key_alias,
"--target",
ALBERT,
"--token",
NAM,
"--amount",
"0.000001",
]),
)
});
assert!(captured.result.is_err());
assert_matches!(
captured
.result
.unwrap_err()
.downcast_ref::<namada_sdk::error::Error>()
.unwrap(),
namada_sdk::error::Error::Tx(TxSubmitError::BalanceTooLowForFees(
_,
_,
_,
_
))
);
let output_folder = node.test_dir.path();
let captured = CapturedOutput::of(|| {
run(
&node,
Bin::Client,
apply_use_device(vec![
"transparent-transfer",
"--source",
key_alias,
"--target",
ALBERT,
"--token",
NAM,
"--amount",
"0.000001",
"--force",
"--dump-tx",
"--output-folder-path",
&output_folder.to_str().unwrap(),
]),
)
});
assert!(captured.result.is_ok());
let tx = find_files_with_ext(output_folder, "tx")
.unwrap()
.first()
.expect("Offline tx should be found.")
.to_path_buf()
.display()
.to_string();
let captured = CapturedOutput::of(|| {
run(
&node,
Bin::Client,
vec![
"utils",
"sign-offline",
"--data-path",
&tx,
"--secret-keys",
&key_alias,
"--output-folder-path",
&output_folder.to_str().unwrap(),
],
)
});
assert!(captured.result.is_ok());
let sig_files = find_files_with_ext(output_folder, "sig").unwrap();
assert_eq!(sig_files.len(), 1);
let offline_sig = sig_files.first().unwrap().to_str().unwrap();
let captured = CapturedOutput::of(|| {
run(
&node,
Bin::Client,
apply_use_device(vec![
"tx",
"--tx-path",
&tx,
"--signatures",
&offline_sig,
"--gas-payer",
CHRISTEL_KEY,
]),
)
});
assert!(captured.result.is_ok());
assert!(captured.contains(TX_APPLIED_SUCCESS));
let captured = CapturedOutput::of(|| {
run(
&node,
Bin::Client,
vec!["balance", "--owner", ALBERT, "--token", NAM],
)
});
assert!(captured.contains("nam: 1979999.5\n"));
let captured = CapturedOutput::of(|| {
run(
&node,
Bin::Client,
vec!["balance", "--owner", key_alias, "--token", NAM],
)
});
assert!(captured.result.is_ok());
assert!(captured.contains("nam: 0\n"));
Ok(())
}
#[test]
fn offline_wrap_tx_by_elsewho() -> Result<()> {
let (node, _services) = setup::setup()?;
let key_alias = "new-account";
let captured = CapturedOutput::of(|| {
run(
&node,
Bin::Wallet,
vec!["gen", "--alias", key_alias, "--unsafe-dont-encrypt"],
)
});
assert!(captured.result.is_ok());
let captured = CapturedOutput::of(|| {
run(
&node,
Bin::Client,
apply_use_device(vec![
"transparent-transfer",
"--source",
ALBERT,
"--target",
key_alias,
"--token",
NAM,
"--amount",
"0.500001",
]),
)
});
assert!(captured.result.is_ok());
assert!(captured.contains(TX_APPLIED_SUCCESS));
let captured = CapturedOutput::of(|| {
run(
&node,
Bin::Client,
apply_use_device(vec!["reveal-pk", "--public-key", key_alias]),
)
});
assert!(captured.result.is_ok());
assert!(captured.contains(TX_APPLIED_SUCCESS));
let captured = CapturedOutput::of(|| {
run(
&node,
Bin::Client,
vec!["balance", "--owner", key_alias, "--token", NAM],
)
});
assert!(captured.result.is_ok());
assert!(captured.contains("nam: 0.000001"));
let captured = CapturedOutput::of(|| {
run(
&node,
Bin::Client,
apply_use_device(vec![
"transparent-transfer",
"--source",
key_alias,
"--target",
ALBERT,
"--token",
NAM,
"--amount",
"0.000001",
]),
)
});
assert!(captured.result.is_err());
assert_matches!(
captured
.result
.unwrap_err()
.downcast_ref::<namada_sdk::error::Error>()
.unwrap(),
namada_sdk::error::Error::Tx(TxSubmitError::BalanceTooLowForFees(
_,
_,
_,
_
))
);
let output_folder = node.test_dir.path();
let captured = CapturedOutput::of(|| {
run(
&node,
Bin::Client,
apply_use_device(vec![
"transparent-transfer",
"--source",
key_alias,
"--target",
ALBERT,
"--token",
NAM,
"--amount",
"0.000001",
"--force",
"--dump-tx",
"--output-folder-path",
&output_folder.to_str().unwrap(),
]),
)
});
assert!(captured.result.is_ok());
let tx_path_buf = find_files_with_ext(output_folder, "tx")
.unwrap()
.first()
.expect("Offline tx should be found.")
.to_owned();
let tx = tx_path_buf.to_path_buf().display().to_string();
let captured = CapturedOutput::of(|| {
run(
&node,
Bin::Client,
vec![
"utils",
"sign-offline",
"--data-path",
&tx,
"--secret-keys",
&key_alias,
"--output-folder-path",
&output_folder.to_str().unwrap(),
],
)
});
assert!(captured.result.is_ok());
let sig_files = find_files_with_ext(output_folder, "sig").unwrap();
assert_eq!(sig_files.len(), 1);
let offline_sig_path = sig_files.first().unwrap();
let offline_sig = offline_sig_path.to_str().unwrap();
let captured = CapturedOutput::of(|| {
run(
&node,
Bin::Client,
apply_use_device(vec![
"tx",
"--tx-path",
&tx,
"--signatures",
&offline_sig,
"--gas-payer",
CHRISTEL_KEY,
"--dump-wrapper-tx",
"--output-folder-path",
&output_folder.to_str().unwrap(),
]),
)
});
assert!(captured.result.is_ok());
let wrapper_tx = find_files_with_ext(output_folder, "tx")
.unwrap()
.into_iter()
.find(|wrapper_tx| wrapper_tx != &tx_path_buf)
.expect("Offline wrapper tx should be found.")
.to_path_buf()
.display()
.to_string();
let captured = CapturedOutput::of(|| {
run(
&node,
Bin::Client,
vec![
"utils",
"sign-offline",
"--data-path",
&wrapper_tx,
"--secret-key",
CHRISTEL_KEY,
"--output-folder-path",
&output_folder.to_str().unwrap(),
],
)
});
assert!(captured.result.is_ok());
let sig_files = find_files_with_ext(output_folder, "sig").unwrap();
assert_eq!(sig_files.len(), 2);
let offline_wrapper_sig = sig_files
.into_iter()
.find(|wrapper_sig| wrapper_sig != offline_sig_path)
.unwrap()
.to_str()
.unwrap()
.to_owned();
let captured = CapturedOutput::of(|| {
run(
&node,
Bin::Client,
apply_use_device(vec![
"tx",
"--tx-path",
&wrapper_tx,
"--gas-signature",
&offline_wrapper_sig,
]),
)
});
assert!(captured.result.is_ok());
assert!(captured.contains(TX_APPLIED_SUCCESS));
let captured = CapturedOutput::of(|| {
run(
&node,
Bin::Client,
vec!["balance", "--owner", ALBERT, "--token", NAM],
)
});
assert!(captured.contains("nam: 1979999.5\n"));
let captured = CapturedOutput::of(|| {
run(
&node,
Bin::Client,
vec!["balance", "--owner", key_alias, "--token", NAM],
)
});
assert!(captured.result.is_ok());
assert!(captured.contains("nam: 0\n"));
Ok(())
}
#[test]
fn offline_wrapper_tx() -> Result<()> {
let (node, _services) = setup::setup()?;
let key_alias = "new-account";
let captured = CapturedOutput::of(|| {
run(
&node,
Bin::Wallet,
vec!["gen", "--alias", key_alias, "--unsafe-dont-encrypt"],
)
});
assert!(captured.result.is_ok());
let captured = CapturedOutput::of(|| {
run(
&node,
Bin::Client,
apply_use_device(vec![
"transparent-transfer",
"--source",
ALBERT,
"--target",
key_alias,
"--token",
NAM,
"--amount",
"0.500001",
]),
)
});
assert!(captured.result.is_ok());
assert!(captured.contains(TX_APPLIED_SUCCESS));
let captured = CapturedOutput::of(|| {
run(
&node,
Bin::Client,
apply_use_device(vec!["reveal-pk", "--public-key", key_alias]),
)
});
assert!(captured.result.is_ok());
assert!(captured.contains(TX_APPLIED_SUCCESS));
let captured = CapturedOutput::of(|| {
run(
&node,
Bin::Client,
vec!["balance", "--owner", key_alias, "--token", NAM],
)
});
assert!(captured.result.is_ok());
assert!(captured.contains("nam: 0.000001"));
let captured = CapturedOutput::of(|| {
run(
&node,
Bin::Client,
apply_use_device(vec![
"transparent-transfer",
"--source",
key_alias,
"--target",
ALBERT,
"--token",
NAM,
"--amount",
"0.000001",
]),
)
});
assert!(captured.result.is_err());
assert_matches!(
captured
.result
.unwrap_err()
.downcast_ref::<namada_sdk::error::Error>()
.unwrap(),
namada_sdk::error::Error::Tx(TxSubmitError::BalanceTooLowForFees(
_,
_,
_,
_
))
);
let output_folder = node.test_dir.path();
let captured = CapturedOutput::of(|| {
run(
&node,
Bin::Client,
apply_use_device(vec![
"transparent-transfer",
"--source",
key_alias,
"--target",
ALBERT,
"--token",
NAM,
"--amount",
"0.000001",
"--gas-payer",
CHRISTEL_KEY,
"--dump-wrapper-tx",
"--output-folder-path",
&output_folder.to_str().unwrap(),
]),
)
});
assert!(captured.result.is_ok());
let tx_path_buf = find_files_with_ext(output_folder, "tx")
.unwrap()
.first()
.expect("Offline tx should be found.")
.to_owned();
let tx = tx_path_buf.to_path_buf().display().to_string();
let captured = CapturedOutput::of(|| {
run(
&node,
Bin::Client,
vec![
"utils",
"sign-offline",
"--data-path",
&tx,
"--secret-keys",
&key_alias,
"--secret-key",
CHRISTEL_KEY,
"--output-folder-path",
&output_folder.to_str().unwrap(),
],
)
});
assert!(captured.result.is_ok());
let sig_files = find_files_with_ext(output_folder, "sig").unwrap();
assert_eq!(sig_files.len(), 2);
let offline_sig = sig_files
.iter()
.find(|path| {
path.file_name()
.unwrap()
.to_str()
.unwrap()
.contains("offline_signature")
})
.unwrap()
.to_str()
.unwrap()
.to_owned();
let offline_wrapper_sig = sig_files
.iter()
.find(|path| {
path.file_name()
.unwrap()
.to_str()
.unwrap()
.contains("offline_wrapper_signature")
})
.unwrap()
.to_str()
.unwrap()
.to_owned();
let captured = CapturedOutput::of(|| {
run(
&node,
Bin::Client,
apply_use_device(vec![
"tx",
"--tx-path",
&tx,
"--signatures",
&offline_sig,
"--gas-signature",
&offline_wrapper_sig,
]),
)
});
assert!(captured.result.is_ok());
assert!(captured.contains(TX_APPLIED_SUCCESS));
let captured = CapturedOutput::of(|| {
run(
&node,
Bin::Client,
vec!["balance", "--owner", ALBERT, "--token", NAM],
)
});
assert!(captured.contains("nam: 1979999.5\n"));
let captured = CapturedOutput::of(|| {
run(
&node,
Bin::Client,
vec!["balance", "--owner", key_alias, "--token", NAM],
)
});
assert!(captured.result.is_ok());
assert!(captured.contains("nam: 0\n"));
Ok(())
}
#[test]
fn pos_validator_metadata_validation() -> Result<()> {
let (node, _services) = setup::setup()?;
let valid_desc: String = "0123456789".repeat(50);
assert_eq!(valid_desc.len() as u64, MAX_VALIDATOR_METADATA_LEN);
let tx_args = apply_use_device(vec![
"change-metadata",
"--validator",
"validator-0-validator",
"--description",
&valid_desc,
]);
let captured = CapturedOutput::of(|| run(&node, Bin::Client, tx_args));
println!("{:?}", captured.result);
assert_matches!(captured.result, Ok(_));
assert!(captured.contains(TX_APPLIED_SUCCESS));
let query_args =
vec!["validator-metadata", "--validator", "validator-0-validator"];
let captured =
CapturedOutput::of(|| run(&node, Bin::Client, query_args.clone()));
println!("{:?}", captured.result);
assert!(captured.contains(&valid_desc));
let invalid_desc: String = format!("N{valid_desc}");
assert!(invalid_desc.len() as u64 > MAX_VALIDATOR_METADATA_LEN);
let tx_args = apply_use_device(vec![
"change-metadata",
"--validator",
"validator-0-validator",
"--description",
&invalid_desc,
"--force",
]);
let captured = CapturedOutput::of(|| run(&node, Bin::Client, tx_args));
println!("{:?}", captured.result);
assert_matches!(captured.result, Ok(_));
assert!(captured.contains(TX_REJECTED));
let captured = CapturedOutput::of(|| run(&node, Bin::Client, query_args));
println!("{:?}", captured.result);
assert!(captured.contains(&valid_desc));
let new_validator = "new-validator";
let tx_args = apply_use_device(vec![
"init-validator",
"--alias",
new_validator,
"--name",
new_validator,
"--account-keys",
"bertha-key",
"--commission-rate",
"0.05",
"--max-commission-rate-change",
"0.01",
"--email",
"null@null.net",
"--signing-keys",
"bertha-key",
"--description",
&invalid_desc,
"--unsafe-dont-encrypt",
]);
let captured = CapturedOutput::of(|| run(&node, Bin::Client, tx_args));
assert_matches!(captured.result, Err(_));
assert!(captured.contains(TX_REJECTED));
Ok(())
}
#[test]
fn client_events_reconstruction() -> Result<()> {
let validator_one_rpc = "http://127.0.0.1:26567";
let (node, _services) = setup::setup()?;
let tx_args = apply_use_device(vec![
"transparent-transfer",
"--source",
BERTHA_KEY,
"--target",
ALBERT_KEY,
"--token",
NAM,
"--amount",
"1",
"--node",
&validator_one_rpc,
"--force",
]);
let captured = CapturedOutput::of(|| run(&node, Bin::Client, tx_args));
assert_matches!(captured.result, Ok(_));
assert!(captured.contains(TX_APPLIED_SUCCESS));
assert!(captured.contains("Events:"));
assert!(captured.contains("- tx - token/transfer:"));
Ok(())
}
fn make_migration_json() -> (Hash, tempfile::NamedTempFile) {
let file = tempfile::Builder::new().tempfile().expect("Test failed");
let updates = [migrations::DbUpdateType::Add {
key: Key::parse("bing/fucking/bong").expect("Test failed"),
cf: DbColFam::SUBSPACE,
value: token::Amount::native_whole(1337).into(),
force: false,
}];
let changes = migrations::DbChanges {
changes: updates.into_iter().collect(),
};
let json = serde_json::to_string(&changes).expect("Test failed");
let hash = Hash::sha256(json.as_bytes());
std::fs::write(file.path(), json).expect("Test failed");
(hash, file)
}
pub fn find_files_with_ext(
dir: &Path,
extension: &str,
) -> Result<Vec<PathBuf>> {
let mut result = vec![];
for entry in fs::read_dir(dir)? {
let entry = entry?;
let path = entry.path();
if path.is_file() {
if let Some(file_extension) = path.extension() {
if file_extension == extension {
result.push(path);
}
}
}
}
Ok(result)
}