use crate::cli::deploy::do_withdraw_amount_checks;
use crate::cli::{get_block, query_global_state};
#[cfg(feature = "std-fs-io")]
use crate::read_transaction_file;
#[cfg(feature = "std-fs-io")]
use crate::rpcs::v2_0_0::speculative_exec_transaction::SpeculativeExecTxnResult;
#[cfg(feature = "std-fs-io")]
use crate::speculative_exec_txn;
use crate::{
cli::{parse, CliError, TransactionBuilderParams, TransactionStrParams, TransactionV1Builder},
put_transaction as put_transaction_rpc_handler,
rpcs::results::PutTransactionResult,
SuccessResponse, Verbosity,
};
use casper_types::{
Digest, InitiatorAddr, Key, PublicKey, SecretKey, Transaction, TransactionArgs,
TransactionEntryPoint, TransactionInvocationTarget, TransactionRuntimeParams,
TransactionTarget, U512,
};
pub fn create_transaction(
builder_params: TransactionBuilderParams,
transaction_params: TransactionStrParams,
allow_unsigned_transaction: bool,
) -> Result<Transaction, CliError> {
let chain_name = transaction_params.chain_name.to_string();
let maybe_secret_key = get_maybe_secret_key(
transaction_params.secret_key,
allow_unsigned_transaction,
"create_transaction",
)?;
let timestamp = parse::timestamp(transaction_params.timestamp)?;
let ttl = parse::ttl(transaction_params.ttl)?;
let maybe_session_account = parse::session_account(&transaction_params.initiator_addr)?;
let is_v2_wasm = matches!(&builder_params, TransactionBuilderParams::Session { runtime, .. } if matches!(runtime, &TransactionRuntimeParams::VmCasperV2 { .. }));
let mut transaction_builder = make_transaction_builder(builder_params)?;
transaction_builder = transaction_builder
.with_timestamp(timestamp)
.with_ttl(ttl)
.with_chain_name(chain_name);
if transaction_params.pricing_mode.is_empty() {
return Err(CliError::InvalidArgument {
context: "create_transaction (pricing_mode)",
error: "pricing_mode is required to be non empty".to_string(),
});
}
let pricing_mode = if transaction_params.pricing_mode.to_lowercase().as_str() == "reserved" {
let digest = Digest::from_hex(transaction_params.receipt).map_err(|error| {
CliError::FailedToParseDigest {
context: "pricing_digest",
error,
}
})?;
parse::pricing_mode(
transaction_params.pricing_mode,
transaction_params.payment_amount,
transaction_params.gas_price_tolerance,
transaction_params.additional_computation_factor,
transaction_params.standard_payment,
Some(digest),
)?
} else {
parse::pricing_mode(
transaction_params.pricing_mode,
transaction_params.payment_amount,
transaction_params.gas_price_tolerance,
transaction_params.additional_computation_factor,
transaction_params.standard_payment,
None,
)?
};
transaction_builder = transaction_builder.with_pricing_mode(pricing_mode);
let maybe_json_args = parse::args_json::session::parse(transaction_params.session_args_json)?;
let maybe_simple_args =
parse::arg_simple::session::parse(&transaction_params.session_args_simple)?;
let chunked = transaction_params.chunked_args;
let args = parse::args_from_simple_or_json(maybe_simple_args, maybe_json_args, chunked);
match args {
TransactionArgs::Named(named_args) => {
if !named_args.is_empty() {
transaction_builder = transaction_builder.with_runtime_args(named_args);
}
}
TransactionArgs::Bytesrepr(chunked_args) => {
transaction_builder = transaction_builder.with_chunked_args(chunked_args);
}
}
if is_v2_wasm {
if let Some(entry_point) = transaction_params.session_entry_point {
transaction_builder = transaction_builder
.with_entry_point(TransactionEntryPoint::Custom(entry_point.to_owned()));
}
}
if let Some(secret_key) = &maybe_secret_key {
transaction_builder = transaction_builder.with_secret_key(secret_key);
}
if let Some(account) = maybe_session_account {
transaction_builder =
transaction_builder.with_initiator_addr(InitiatorAddr::PublicKey(account));
}
let txn = transaction_builder.build().map_err(crate::Error::from)?;
Ok(Transaction::V1(txn))
}
pub fn make_transaction(
builder_params: TransactionBuilderParams,
transaction_params: TransactionStrParams<'_>,
#[allow(unused_variables)] force: bool,
) -> Result<Transaction, CliError> {
let transaction = create_transaction(builder_params, transaction_params.clone(), true)?;
#[cfg(feature = "std-fs-io")]
{
let output = parse::output_kind(transaction_params.output_path, force);
crate::output_transaction(output, &transaction).map_err(CliError::from)?;
}
Ok(transaction)
}
pub async fn put_transaction(
rpc_id_str: &str,
node_address: &str,
verbosity_level: u64,
builder_params: TransactionBuilderParams<'_>,
transaction_params: TransactionStrParams<'_>,
) -> Result<SuccessResponse<PutTransactionResult>, CliError> {
let rpc_id = parse::rpc_id(rpc_id_str);
let verbosity_level = parse::verbosity(verbosity_level);
let min_bid_override = transaction_params.min_bid_override;
let transaction = create_transaction(builder_params, transaction_params, false)?;
if let Err(err) =
check_auction_state_for_withdraw(node_address, 0, min_bid_override, &transaction).await
{
if !min_bid_override {
return Err(err);
} else {
println!("[WARN] Skipping withdraw amount checks {}", err)
}
};
put_transaction_rpc_handler(rpc_id, node_address, verbosity_level, transaction)
.await
.map_err(CliError::from)
}
#[cfg(feature = "std-fs-io")]
pub async fn send_transaction_file(
rpc_id_str: &str,
node_address: &str,
verbosity_level: u64,
input_path: &str,
) -> Result<SuccessResponse<PutTransactionResult>, CliError> {
let rpc_id = parse::rpc_id(rpc_id_str);
let verbosity_level = parse::verbosity(verbosity_level);
let transaction = read_transaction_file(input_path)?;
put_transaction_rpc_handler(rpc_id, node_address, verbosity_level, transaction)
.await
.map_err(CliError::from)
}
#[cfg(feature = "std-fs-io")]
pub async fn speculative_send_transaction_file(
rpc_id_str: &str,
node_address: &str,
verbosity_level: u64,
input_path: &str,
) -> Result<SuccessResponse<SpeculativeExecTxnResult>, CliError> {
let rpc_id = parse::rpc_id(rpc_id_str);
let verbosity_level = parse::verbosity(verbosity_level);
let transaction = read_transaction_file(input_path).unwrap();
speculative_exec_txn(rpc_id, node_address, verbosity_level, transaction)
.await
.map_err(CliError::from)
}
#[cfg(feature = "std-fs-io")]
pub fn sign_transaction_file(
input_path: &str,
secret_key_path: &str,
maybe_output_path: Option<&str>,
force: bool,
) -> Result<(), CliError> {
let output = parse::output_kind(maybe_output_path.unwrap_or(""), force);
let secret_key = parse::secret_key_from_file(secret_key_path)?;
crate::sign_transaction_file(input_path, &secret_key, output).map_err(CliError::from)
}
pub fn make_transaction_builder(
transaction_builder_params: TransactionBuilderParams,
) -> Result<TransactionV1Builder, CliError> {
match transaction_builder_params {
TransactionBuilderParams::AddBid {
public_key,
delegation_rate,
amount,
minimum_delegation_amount,
maximum_delegation_amount,
reserved_slots,
} => {
let transaction_builder = TransactionV1Builder::new_add_bid(
public_key,
delegation_rate,
amount,
minimum_delegation_amount,
maximum_delegation_amount,
reserved_slots,
)?;
Ok(transaction_builder)
}
TransactionBuilderParams::Delegate {
delegator,
validator,
amount,
} => {
let transaction_builder =
TransactionV1Builder::new_delegate(delegator, validator, amount)?;
Ok(transaction_builder)
}
TransactionBuilderParams::Undelegate {
delegator,
validator,
amount,
} => {
let transaction_builder =
TransactionV1Builder::new_undelegate(delegator, validator, amount)?;
Ok(transaction_builder)
}
TransactionBuilderParams::Redelegate {
delegator,
validator,
amount,
new_validator,
} => {
let transaction_builder =
TransactionV1Builder::new_redelegate(delegator, validator, amount, new_validator)?;
Ok(transaction_builder)
}
TransactionBuilderParams::InvocableEntity {
entity_hash,
entry_point,
runtime,
} => {
let transaction_builder = TransactionV1Builder::new_targeting_invocable_entity(
entity_hash,
entry_point,
runtime,
);
Ok(transaction_builder)
}
TransactionBuilderParams::InvocableEntityAlias {
entity_alias,
entry_point,
runtime,
} => {
let transaction_builder =
TransactionV1Builder::new_targeting_invocable_entity_via_alias(
entity_alias,
entry_point,
runtime,
);
Ok(transaction_builder)
}
TransactionBuilderParams::Package {
package_hash,
maybe_entity_version,
entry_point,
runtime,
} => {
let transaction_builder = TransactionV1Builder::new_targeting_package(
package_hash,
maybe_entity_version,
entry_point,
runtime,
);
Ok(transaction_builder)
}
TransactionBuilderParams::PackageWithMajorVersion {
package_hash,
maybe_entity_version,
entry_point,
runtime,
major_protocol_version,
} => {
let transaction_builder = TransactionV1Builder::new_targeting_package_with_version_key(
package_hash,
maybe_entity_version,
major_protocol_version,
entry_point,
runtime,
);
Ok(transaction_builder)
}
TransactionBuilderParams::PackageAlias {
package_alias,
maybe_entity_version,
entry_point,
runtime,
} => {
let new_targeting_package_via_alias =
TransactionV1Builder::new_targeting_package_via_alias(
package_alias,
maybe_entity_version,
entry_point,
runtime,
);
let transaction_builder = new_targeting_package_via_alias;
Ok(transaction_builder)
}
TransactionBuilderParams::PackageAliasWithMajorVersion {
package_alias,
maybe_entity_version,
entry_point,
runtime,
major_protocol_version,
} => {
let new_targeting_package_via_alias =
TransactionV1Builder::new_targeting_package_via_alias_with_version_key(
package_alias,
maybe_entity_version,
major_protocol_version,
entry_point,
runtime,
);
let transaction_builder = new_targeting_package_via_alias;
Ok(transaction_builder)
}
TransactionBuilderParams::Session {
is_install_upgrade,
transaction_bytes,
runtime,
} => {
let transaction_builder =
TransactionV1Builder::new_session(is_install_upgrade, transaction_bytes, runtime);
Ok(transaction_builder)
}
TransactionBuilderParams::Transfer {
maybe_source,
target,
amount,
maybe_id,
} => {
let transaction_builder =
TransactionV1Builder::new_transfer(amount, maybe_source, target, maybe_id)?;
Ok(transaction_builder)
}
TransactionBuilderParams::WithdrawBid {
public_key, amount, ..
} => {
let transaction_builder = TransactionV1Builder::new_withdraw_bid(public_key, amount)?;
Ok(transaction_builder)
}
TransactionBuilderParams::ActivateBid { validator } => {
let transaction_builder = TransactionV1Builder::new_activate_bid(validator)?;
Ok(transaction_builder)
}
TransactionBuilderParams::ChangeBidPublicKey {
public_key,
new_public_key,
} => {
let transaction_builder =
TransactionV1Builder::new_change_bid_public_key(public_key, new_public_key)?;
Ok(transaction_builder)
}
TransactionBuilderParams::AddReservations { reservations } => {
let transaction_builder = TransactionV1Builder::new_add_reservations(reservations)?;
Ok(transaction_builder)
}
TransactionBuilderParams::CancelReservations {
validator,
delegators,
} => {
let transaction_builder =
TransactionV1Builder::new_cancel_reservations(validator, delegators)?;
Ok(transaction_builder)
}
}
}
pub fn get_maybe_secret_key(
secret_key: &str,
allow_unsigned_deploy: bool,
context: &'static str,
) -> Result<Option<SecretKey>, CliError> {
match (secret_key.is_empty(), allow_unsigned_deploy) {
(false, _) => {
#[cfg(feature = "std-fs-io")]
{
Ok(Some(parse::secret_key_from_file(secret_key)?))
}
#[cfg(not(feature = "std-fs-io"))]
{
let secret_key = SecretKey::from_pem(secret_key).map_err(|error| {
CliError::Core(crate::Error::CryptoError { context, error })
})?;
Ok(Some(secret_key))
}
}
(true, true) => Ok(None),
(true, false) => Err(CliError::InvalidArgument {
context,
error: "No secret key provided and unsigned deploys are not allowed".to_string(),
}),
}
}
async fn check_auction_state_for_withdraw(
node_address: &str,
verbosity_level: u64,
min_bid_override: bool,
transaction: &Transaction,
) -> Result<(), CliError> {
let state_root_hash = *get_block("", node_address, 0, "")
.await?
.result
.block_with_signatures
.ok_or_else(|| CliError::FailedToGetStateRootHash)?
.block
.state_root_hash();
let encoded_hash = base16::encode_lower(&state_root_hash);
match transaction {
Transaction::Deploy(_) => return Ok(()),
Transaction::V1(transaction_v1) => {
let entry_point = transaction_v1
.deserialize_field::<TransactionEntryPoint>(2)
.map_err(|err| {
CliError::FailedToParseTransactionPayloadField(format!("{:?}", err))
})?;
let do_amount_checks = match entry_point {
TransactionEntryPoint::WithdrawBid => true,
TransactionEntryPoint::Custom(name) => {
if *"withdraw_bid" != name {
return Ok(());
}
let transaction_invocation_target = transaction_v1
.payload()
.deserialize_field::<TransactionTarget>(1)
.map_err(|err| {
CliError::FailedToParseTransactionPayloadField(format!("{:?}", err))
})?;
let registry = crate::cli::get_system_hash_registry(
node_address,
verbosity_level,
&encoded_hash,
)
.await?;
let auction_hash_addr = *registry
.get("auction")
.ok_or_else(|| CliError::MissingAuctionHash)?;
let do_amount_checks = if let TransactionTarget::Stored { id, .. } =
transaction_invocation_target
{
match id {
TransactionInvocationTarget::ByHash(hash) => hash == auction_hash_addr,
TransactionInvocationTarget::ByName(name) => {
let base_key =
Key::Account(transaction_v1.initiator_addr().account_hash());
let account = query_global_state(
"",
node_address,
0,
"",
&encoded_hash,
&base_key.to_formatted_string(),
"",
)
.await?
.result
.stored_value
.into_account()
.ok_or_else(|| CliError::UnexpectedStoredValue)?;
let key = account.named_keys().get(&name);
match key {
Some(key) => match *key {
Key::Hash(addr) => addr == auction_hash_addr,
Key::AddressableEntity(addr) => {
addr.value() == auction_hash_addr
}
_ => false,
},
None => false,
}
}
TransactionInvocationTarget::ByPackageHash { addr, .. } => {
let key = Key::Hash(auction_hash_addr);
let package_addr = query_global_state(
"",
node_address,
verbosity_level,
"",
&encoded_hash,
&key.to_formatted_string(),
"",
)
.await?
.result
.stored_value
.as_contract()
.ok_or_else(|| CliError::FailedToGetSystemHashRegistry)?
.contract_package_hash()
.value();
package_addr == addr
}
TransactionInvocationTarget::ByPackageName { name, .. } => {
let base_key =
Key::Account(transaction_v1.initiator_addr().account_hash());
let account = query_global_state(
"",
node_address,
0,
"",
&encoded_hash,
&base_key.to_formatted_string(),
"",
)
.await?
.result
.stored_value
.into_account()
.ok_or_else(|| CliError::UnexpectedStoredValue)?;
let key = account.named_keys().get(&name);
match key {
Some(key) => match *key {
Key::Hash(addr) => {
let key = Key::Hash(auction_hash_addr);
let package_addr = query_global_state(
"",
node_address,
verbosity_level,
"",
&encoded_hash,
&key.to_formatted_string(),
"",
)
.await?
.result
.stored_value
.as_contract()
.ok_or_else(|| CliError::FailedToGetSystemHashRegistry)?
.contract_package_hash()
.value();
addr == package_addr
}
Key::SmartContract(addr) => {
let key = Key::Hash(auction_hash_addr);
let package_addr = query_global_state(
"",
node_address,
verbosity_level,
"",
&encoded_hash,
&key.to_formatted_string(),
"",
)
.await?
.result
.stored_value
.as_contract()
.ok_or_else(|| CliError::FailedToGetSystemHashRegistry)?
.contract_package_hash()
.value();
addr == package_addr
}
_ => false,
},
None => false,
}
}
}
} else {
false
};
do_amount_checks
}
_ => return Ok(()),
};
if do_amount_checks {
let args = transaction_v1
.payload()
.deserialize_field::<TransactionArgs>(0)
.map_err(|err| {
CliError::FailedToParseTransactionPayloadField(format!("{:?}", err))
})?;
if let Some(named_args) = args.into_named() {
let amount = named_args
.get("amount")
.ok_or_else(|| {
CliError::InvalidCLValue("failed to get amount arg".to_string())
})?
.to_t::<U512>()
.map_err(|err| CliError::InvalidCLValue(err.to_string()))?;
let public_key = named_args
.get("public_key")
.ok_or_else(|| {
CliError::InvalidCLValue("failed to get public key arg".to_string())
})?
.to_t::<PublicKey>()
.map_err(|err| CliError::InvalidCLValue(err.to_string()))?;
do_withdraw_amount_checks(node_address, 0, public_key, amount, min_bid_override)
.await?
}
} else if verbosity_level == Verbosity::High as u64 {
println!("Skipping amount checks for withdraw bid")
}
}
}
Ok(())
}