use crate::casper_client::Result;
use crate::error::LivenetError;
use crate::log;
use casper_client::cli::TransactionV1Builder;
use casper_client::put_transaction;
use casper_types::bytesrepr::{Bytes, ToBytes};
use casper_types::execution::ExecutionResultV1::{Failure, Success};
use casper_types::{
execution::ExecutionResult, runtime_args, PricingMode, RuntimeArgs, Timestamp, Transaction,
TransactionHash, TransactionRuntimeParams, TransferTarget, U512
};
use odra_core::consts::{
AMOUNT_ARG, ARGS_ARG, ATTACHED_VALUE_ARG, ENTRY_POINT_ARG, PACKAGE_HASH_ARG,
PACKAGE_HASH_KEY_NAME_ARG
};
use odra_core::prelude::*;
use odra_core::CallDef;
const NATIVE_TRANSFER_GAS: u64 = 100_000_000u64;
impl super::CasperClient {
pub fn transfer(
&self,
to: Address,
amount: U512,
timestamp: Timestamp
) -> Result<TransactionHash> {
let rt = self.runtime();
rt.block_on(self.transfer_async(to, amount, timestamp))
}
async fn transfer_async(
&self,
to: Address,
amount: U512,
timestamp: Timestamp
) -> Result<TransactionHash> {
let transaction = self.new_transfer_transaction(to, amount, timestamp)?;
log::debug(serde_json::to_string_pretty(&transaction).unwrap());
self.put_transaction(transaction).await
}
pub fn deploy_wasm(
&mut self,
contract_name: &str,
args: RuntimeArgs,
timestamp: Timestamp,
wasm_bytes: Vec<u8>
) -> Result<Address> {
let rt = self.runtime();
rt.block_on(self.deploy_wasm_async(contract_name, args, timestamp, wasm_bytes))
}
async fn deploy_wasm_async(
&mut self,
contract_name: &str,
args: RuntimeArgs,
timestamp: Timestamp,
wasm_bytes: Vec<u8>
) -> Result<Address> {
log::info(format!("Deploying \"{}\".", contract_name));
let package_hash_key_name: String = args
.get(PACKAGE_HASH_KEY_NAME_ARG)
.ok_or_else(|| {
LivenetError::ExecutionError(format!(
"Missing required argument: {}",
PACKAGE_HASH_KEY_NAME_ARG
))
})?
.clone()
.into_t()
.map_err(|e| {
LivenetError::ExecutionError(format!(
"Failed to parse {} argument: {:?}",
PACKAGE_HASH_KEY_NAME_ARG, e
))
})?;
if self.gas.is_zero() {
return Err(LivenetError::GasNotSet);
}
let transaction =
self.new_wasm_deploy_transaction(Bytes::from(wasm_bytes), args, timestamp)?;
log::debug(serde_json::to_string_pretty(&transaction).unwrap());
self.put_transaction(transaction).await?;
let address = self.get_contract_address(&package_hash_key_name).await?;
log::info(format!(
"Contract {:?} deployed.",
&address.to_formatted_string()
));
Ok(address)
}
pub fn deploy_entrypoint_call_with_proxy(
&self,
address: Address,
call_def: CallDef,
timestamp: Timestamp
) -> Result<Bytes> {
let rt = self.runtime();
rt.block_on(self.deploy_entrypoint_call_with_proxy_async(address, call_def, timestamp))
}
async fn deploy_entrypoint_call_with_proxy_async(
&self,
address: Address,
call_def: CallDef,
timestamp: Timestamp
) -> Result<Bytes> {
log::info(format!(
"Calling {:?} with entrypoint \"{}\" through proxy.",
address.to_formatted_string(),
call_def.entry_point()
));
let hash = address.as_contract_package_hash().ok_or_else(|| {
LivenetError::ExecutionError(format!(
"Address {:?} is not a contract package hash. Expected contract address.",
address.to_formatted_string()
))
})?;
let args_bytes: Vec<u8> = call_def
.args()
.to_bytes()
.expect("Should serialize to bytes");
let entry_point = call_def.entry_point();
let args = runtime_args! {
PACKAGE_HASH_ARG => hash,
ENTRY_POINT_ARG => entry_point,
ARGS_ARG => Bytes::from(args_bytes),
ATTACHED_VALUE_ARG => call_def.amount(),
AMOUNT_ARG => call_def.amount(),
};
let module_bytes = include_bytes!("../../resources/proxy_caller_with_return.wasm")
.to_vec()
.into();
let transaction = self.new_wasm_deploy_transaction(module_bytes, args, timestamp)?;
log::debug(serde_json::to_string_pretty(&transaction).unwrap());
let watch = self.watcher.start_watching().await?;
let response = put_transaction(
self.rpc_id_typed(),
self.configuration.node_address(),
self.configuration.verbosity_typed(),
transaction
)
.await
.map_err(|e| match e {
casper_client::Error::ResponseIsRpcError {
rpc_method, error, ..
} => LivenetError::RpcRequestError(
rpc_method.to_string(),
error
.data
.map_or_else(|| "No data".to_string(), |d| d.to_string())
),
_ => LivenetError::ExecutionError(format!("Failed to put transaction: {}", e))
})?;
let transaction_hash = response.result.transaction_hash;
let result = watch.wait_for_transaction_hash(&transaction_hash).await?;
self.process_transaction(result, transaction_hash)?;
Ok(self.get_proxy_result().await)
}
pub fn deploy_entrypoint_call(
&self,
addr: Address,
call_def: CallDef,
timestamp: Timestamp
) -> Result<Bytes> {
let rt = self.runtime();
rt.block_on(self.deploy_entrypoint_call_async(addr, call_def, timestamp))
}
async fn deploy_entrypoint_call_async(
&self,
addr: Address,
call_def: CallDef,
timestamp: Timestamp
) -> Result<Bytes> {
log::info(format!(
"Calling {:?} directly with entrypoint \"{}\".",
addr.to_formatted_string(),
call_def.entry_point()
));
let transaction = self.new_call_transaction(addr, call_def, timestamp)?;
log::debug(serde_json::to_string_pretty(&transaction).unwrap());
let watch = self.watcher.start_watching().await?;
let response = put_transaction(
self.rpc_id_typed(),
self.configuration.node_address(),
self.configuration.verbosity_typed(),
transaction
)
.await;
let transaction_hash = match response {
Ok(r) => r.result.transaction_hash,
Err(e) => {
return match e {
casper_client::Error::ResponseIsRpcError {
rpc_method, error, ..
} => Err(LivenetError::RpcRequestError(
rpc_method.to_string(),
error
.data
.map_or_else(|| "No data".to_string(), |d| d.to_string())
)),
_ => Err(LivenetError::ExecutionError(e.to_string()))
}
}
};
let result = watch.wait_for_transaction_hash(&transaction_hash).await?;
self.process_transaction(result, transaction_hash).map(|_| {
().to_bytes()
.expect("Couldn't serialize (). This shouldn't happen.")
.into()
})
}
async fn put_transaction(&self, transaction: Transaction) -> Result<TransactionHash> {
log::debug("[TX] Starting event watcher before sending transaction...");
let watch = self.watcher.start_watching().await?;
log::debug("[TX] Event watcher ready, now sending transaction...");
let response = put_transaction(
self.rpc_id_typed(),
self.configuration.node_address(),
self.configuration.verbosity_typed(),
transaction
)
.await
.map_err(|e| match e {
casper_client::Error::ResponseIsRpcError {
rpc_method, error, ..
} => LivenetError::RpcRequestError(
rpc_method.to_string(),
error
.data
.map_or_else(|| "No data".to_string(), |d| d.to_string())
),
_ => LivenetError::ExecutionError(format!("Failed to put transaction: {}", e))
})?;
let transaction_hash = response.result.transaction_hash;
log::debug(format!(
"[TX] Transaction sent with hash: {}",
transaction_hash.to_hex_string()
));
let result = watch.wait_for_transaction_hash(&transaction_hash).await?;
self.process_transaction(result, transaction_hash)?;
Ok(transaction_hash)
}
fn process_transaction(
&self,
result: ExecutionResult,
transaction_hash: TransactionHash
) -> Result<()> {
let deploy_hash_str = transaction_hash.to_hex_string();
match result {
ExecutionResult::V1(r) => match r {
Failure { error_message, .. } => {
log::error(format!(
"Deploy V1 {:?} failed with error: {:?}.",
deploy_hash_str, error_message
));
Err(LivenetError::ExecutionError(error_message.to_string()))
}
Success { .. } => {
log::info(format!(
"Deploy {:?} successfully executed.",
deploy_hash_str
));
Ok(())
}
},
ExecutionResult::V2(r) => match r.error_message {
None => {
log::info(format!(
"Transaction {:?} successfully executed.",
&deploy_hash_str,
));
if let Some(url) = self.configuration.transaction_url(&deploy_hash_str) {
log::link(url);
}
Ok(())
}
Some(error_message) => {
log::error(format!(
"Transaction {:?} failed with error: {:?}.",
deploy_hash_str, error_message,
));
if let Some(url) = self.configuration.transaction_url(&deploy_hash_str) {
log::link(url);
}
Err(LivenetError::ExecutionError(error_message.to_string()))
}
}
}
}
fn new_wasm_deploy_transaction(
&self,
transaction_bytes: Bytes,
args: RuntimeArgs,
timestamp: Timestamp
) -> Result<Transaction> {
let transaction_builder = TransactionV1Builder::new_session(
true,
transaction_bytes,
TransactionRuntimeParams::VmCasperV1
);
Ok(Transaction::V1(
transaction_builder
.with_runtime_args(args)
.with_ttl(self.configuration.ttl())
.with_chain_name(self.configuration.chain_name())
.with_pricing_mode(self.pricing_mode())
.with_secret_key(self.secret_key())
.with_timestamp(timestamp)
.build()
.map_err(|_| LivenetError::InvalidTransaction)?
))
}
fn new_transfer_transaction(
&self,
to: Address,
amount: U512,
timestamp: Timestamp
) -> Result<Transaction> {
let target = TransferTarget::AccountHash(
*to.as_account_hash()
.ok_or(LivenetError::InvalidTransferTarget(to))?
);
let transaction_builder = TransactionV1Builder::new_transfer(amount, None, target, None)
.map_err(|_| LivenetError::SerializationError)?;
Ok(Transaction::V1(
transaction_builder
.with_ttl(self.configuration.ttl())
.with_chain_name(self.configuration.chain_name())
.with_pricing_mode(PricingMode::PaymentLimited {
payment_amount: NATIVE_TRANSFER_GAS,
gas_price_tolerance: self.configuration.gas_price_tolerance(),
standard_payment: true
})
.with_secret_key(self.secret_key())
.with_timestamp(timestamp)
.build()
.map_err(|_| LivenetError::InvalidTransaction)?
))
}
fn new_call_transaction(
&self,
to: Address,
call_def: CallDef,
timestamp: Timestamp
) -> Result<Transaction> {
let package_hash = to
.as_package_hash()
.ok_or(LivenetError::InvalidTransferTarget(to))?;
let transaction_builder = TransactionV1Builder::new_targeting_package(
package_hash,
None,
call_def.entry_point(),
TransactionRuntimeParams::VmCasperV1
);
let transaction_v1 = transaction_builder
.with_ttl(self.configuration.ttl())
.with_chain_name(self.configuration.chain_name())
.with_pricing_mode(PricingMode::PaymentLimited {
payment_amount: call_def.amount().as_u64() + self.gas.as_u64(),
gas_price_tolerance: self.configuration.gas_price_tolerance(),
standard_payment: true
})
.with_secret_key(self.secret_key())
.with_timestamp(timestamp)
.with_runtime_args(call_def.args().clone())
.build()
.map_err(|_| LivenetError::InvalidTransaction)?;
Ok(Transaction::V1(transaction_v1))
}
fn pricing_mode(&self) -> PricingMode {
PricingMode::PaymentLimited {
payment_amount: self.gas.as_u64(),
gas_price_tolerance: self.configuration.gas_price_tolerance(),
standard_payment: true
}
}
}