use crate::error::{ErrorKind, RpcErrorCode};
use crate::result::{Execution, ExecutionFinalResult, Result, ViewResultDetails};
use crate::rpc::client::{
send_batch_tx_and_retry, send_batch_tx_async_and_retry, DEFAULT_CALL_DEPOSIT,
DEFAULT_CALL_FN_GAS,
};
use crate::rpc::query::{Query, ViewFunction};
use crate::types::{
AccessKey, AccountId, Gas, InMemorySigner, KeyType, PublicKey, SecretKey, UncToken,
};
use crate::worker::Worker;
use crate::{Account, CryptoHash, Network};
use std::convert::TryInto;
use std::fmt;
use std::future::IntoFuture;
use std::pin::Pin;
use std::task::Poll;
use unc_account_id::ParseAccountError;
use unc_gas::UncGas;
use unc_jsonrpc_client::errors::{JsonRpcError, JsonRpcServerError};
use unc_jsonrpc_client::methods::tx::RpcTransactionError;
use unc_primitives::borsh;
use unc_primitives::transaction::{
Action, AddKeyAction, CreateAccountAction, DeleteAccountAction, DeleteKeyAction,
DeployContractAction, FunctionCallAction, PledgeAction, TransferAction,
};
use unc_primitives::views::FinalExecutionOutcomeView;
const MAX_GAS: UncGas = UncGas::from_tgas(300);
#[derive(Debug)]
pub struct Function {
pub(crate) name: String,
pub(crate) args: Result<Vec<u8>>,
pub(crate) deposit: UncToken,
pub(crate) gas: Gas,
}
impl Function {
pub fn new(name: &str) -> Self {
Self {
name: name.into(),
args: Ok(vec![]),
deposit: DEFAULT_CALL_DEPOSIT,
gas: DEFAULT_CALL_FN_GAS,
}
}
pub fn args(mut self, args: Vec<u8>) -> Self {
if self.args.is_err() {
return self;
}
self.args = Ok(args);
self
}
pub fn args_json<U: serde::Serialize>(mut self, args: U) -> Self {
match serde_json::to_vec(&args) {
Ok(args) => self.args = Ok(args),
Err(e) => self.args = Err(ErrorKind::DataConversion.custom(e)),
}
self
}
pub fn args_borsh<U: borsh::BorshSerialize>(mut self, args: U) -> Self {
match borsh::to_vec(&args) {
Ok(args) => self.args = Ok(args),
Err(e) => self.args = Err(ErrorKind::DataConversion.custom(e)),
}
self
}
pub fn deposit(mut self, deposit: UncToken) -> Self {
self.deposit = deposit;
self
}
pub fn gas(mut self, gas: Gas) -> Self {
self.gas = gas;
self
}
pub fn max_gas(self) -> Self {
self.gas(MAX_GAS)
}
}
pub struct Transaction {
worker: Worker<dyn Network>,
signer: InMemorySigner,
receiver_id: AccountId,
actions: Result<Vec<Action>>,
}
impl Transaction {
pub(crate) fn new(
worker: Worker<dyn Network>,
signer: InMemorySigner,
receiver_id: AccountId,
) -> Self {
Self {
worker,
signer,
receiver_id,
actions: Ok(Vec::new()),
}
}
pub fn add_key(mut self, pk: PublicKey, ak: AccessKey) -> Self {
if let Ok(actions) = &mut self.actions {
actions.push(
AddKeyAction {
public_key: pk.into(),
access_key: ak.into(),
}
.into(),
);
}
self
}
pub fn call(mut self, function: Function) -> Self {
let args = match function.args {
Ok(args) => args,
Err(err) => {
self.actions = Err(err);
return self;
}
};
if let Ok(actions) = &mut self.actions {
actions.push(Action::FunctionCall(Box::new(FunctionCallAction {
method_name: function.name.to_string(),
args,
deposit: function.deposit.as_attounc(),
gas: function.gas.as_gas(),
})));
}
self
}
pub fn create_account(mut self) -> Self {
if let Ok(actions) = &mut self.actions {
actions.push(CreateAccountAction {}.into());
}
self
}
pub fn delete_account(mut self, beneficiary_id: &AccountId) -> Self {
if let Ok(actions) = &mut self.actions {
actions.push(
DeleteAccountAction {
beneficiary_id: beneficiary_id.clone(),
}
.into(),
);
}
self
}
pub fn delete_key(mut self, pk: PublicKey) -> Self {
if let Ok(actions) = &mut self.actions {
actions.push(DeleteKeyAction { public_key: pk.0 }.into());
}
self
}
pub fn deploy(mut self, code: &[u8]) -> Self {
if let Ok(actions) = &mut self.actions {
actions.push(DeployContractAction { code: code.into() }.into());
}
self
}
pub fn stake(mut self, stake: UncToken, pk: PublicKey) -> Self {
if let Ok(actions) = &mut self.actions {
actions.push(
PledgeAction {
pledge: stake.as_attounc(),
public_key: pk.0,
}
.into(),
);
}
self
}
pub fn transfer(mut self, deposit: UncToken) -> Self {
if let Ok(actions) = &mut self.actions {
actions.push(
TransferAction {
deposit: deposit.as_attounc(),
}
.into(),
);
}
self
}
async fn transact_raw(self) -> Result<FinalExecutionOutcomeView> {
let view = send_batch_tx_and_retry(
self.worker.client(),
&self.signer,
&self.receiver_id,
self.actions?,
)
.await?;
if !self.worker.tx_callbacks.is_empty() {
let total_gas_burnt = view.transaction_outcome.outcome.gas_burnt
+ view
.receipts_outcome
.iter()
.map(|t| t.outcome.gas_burnt)
.sum::<u64>();
for callback in self.worker.tx_callbacks {
callback(Gas::from_gas(total_gas_burnt))?;
}
}
Ok(view)
}
pub async fn transact(self) -> Result<ExecutionFinalResult> {
self.transact_raw()
.await
.map(ExecutionFinalResult::from_view)
.map_err(crate::error::Error::from)
}
pub async fn transact_async(self) -> Result<TransactionStatus> {
send_batch_tx_async_and_retry(self.worker, &self.signer, &self.receiver_id, self.actions?)
.await
}
}
pub struct CallTransaction {
worker: Worker<dyn Network>,
signer: InMemorySigner,
contract_id: AccountId,
function: Function,
}
impl CallTransaction {
pub(crate) fn new(
worker: Worker<dyn Network>,
contract_id: AccountId,
signer: InMemorySigner,
function: &str,
) -> Self {
Self {
worker,
signer,
contract_id,
function: Function::new(function),
}
}
pub fn args(mut self, args: Vec<u8>) -> Self {
self.function = self.function.args(args);
self
}
pub fn args_json<U: serde::Serialize>(mut self, args: U) -> Self {
self.function = self.function.args_json(args);
self
}
pub fn args_borsh<U: borsh::BorshSerialize>(mut self, args: U) -> Self {
self.function = self.function.args_borsh(args);
self
}
pub fn deposit(mut self, deposit: UncToken) -> Self {
self.function = self.function.deposit(deposit);
self
}
pub fn gas(mut self, gas: UncGas) -> Self {
self.function = self.function.gas(gas);
self
}
pub fn max_gas(self) -> Self {
self.gas(MAX_GAS)
}
pub async fn transact(self) -> Result<ExecutionFinalResult> {
let txn = self
.worker
.client()
.call(
&self.signer,
&self.contract_id,
self.function.name.to_string(),
self.function.args?,
self.function.gas.as_gas(),
self.function.deposit,
)
.await
.map(ExecutionFinalResult::from_view)
.map_err(crate::error::Error::from)?;
for callback in self.worker.tx_callbacks.iter() {
callback(txn.total_gas_burnt)?;
}
Ok(txn)
}
pub async fn transact_async(self) -> Result<TransactionStatus> {
send_batch_tx_async_and_retry(
self.worker,
&self.signer,
&self.contract_id,
vec![FunctionCallAction {
args: self.function.args?,
method_name: self.function.name,
gas: self.function.gas.as_gas(),
deposit: self.function.deposit.as_attounc(),
}
.into()],
)
.await
}
pub async fn view(self) -> Result<ViewResultDetails> {
Query::new(
self.worker.client(),
ViewFunction {
account_id: self.contract_id.clone(),
function: self.function,
},
)
.await
}
}
pub struct CreateAccountTransaction<'a, 'b> {
worker: &'a Worker<dyn Network>,
signer: InMemorySigner,
parent_id: AccountId,
new_account_id: &'b str,
initial_balance: UncToken,
secret_key: Option<SecretKey>,
}
impl<'a, 'b> CreateAccountTransaction<'a, 'b> {
pub(crate) fn new(
worker: &'a Worker<dyn Network>,
signer: InMemorySigner,
parent_id: AccountId,
new_account_id: &'b str,
) -> Self {
Self {
worker,
signer,
parent_id,
new_account_id,
initial_balance: UncToken::from_attounc(100000000000000000000000u128),
secret_key: None,
}
}
pub fn initial_balance(mut self, initial_balance: UncToken) -> Self {
self.initial_balance = initial_balance;
self
}
pub fn keys(mut self, secret_key: SecretKey) -> Self {
self.secret_key = Some(secret_key);
self
}
pub async fn transact(self) -> Result<Execution<Account>> {
let sk = self
.secret_key
.unwrap_or_else(|| SecretKey::from_seed(KeyType::ED25519, "subaccount.seed"));
let id: AccountId = format!("{}.{}", self.new_account_id, self.parent_id)
.try_into()
.map_err(|e: ParseAccountError| ErrorKind::DataConversion.custom(e))?;
let outcome = self
.worker
.client()
.create_account(&self.signer, &id, sk.public_key(), self.initial_balance)
.await?;
let signer = InMemorySigner::from_secret_key(id, sk);
let account = Account::new(signer, self.worker.clone());
let details = ExecutionFinalResult::from_view(outcome);
for callback in self.worker.tx_callbacks.iter() {
callback(details.total_gas_burnt)?;
}
Ok(Execution {
result: account,
details,
})
}
}
#[must_use]
pub struct TransactionStatus {
worker: Worker<dyn Network>,
sender_id: AccountId,
hash: CryptoHash,
}
impl TransactionStatus {
pub(crate) fn new(
worker: Worker<dyn Network>,
id: AccountId,
hash: unc_primitives::hash::CryptoHash,
) -> Self {
Self {
worker,
sender_id: id,
hash: CryptoHash(hash.0),
}
}
pub async fn status(&self) -> Result<Poll<ExecutionFinalResult>> {
let result = self
.worker
.client()
.tx_async_status(
&self.sender_id,
unc_primitives::hash::CryptoHash(self.hash.0),
)
.await
.map(ExecutionFinalResult::from_view);
match result {
Ok(result) => Ok(Poll::Ready(result)),
Err(err) => match err {
JsonRpcError::ServerError(JsonRpcServerError::HandlerError(
RpcTransactionError::UnknownTransaction { .. },
)) => Ok(Poll::Pending),
other => Err(RpcErrorCode::BroadcastTxFailure.custom(other)),
},
}
}
pub(crate) async fn wait(self) -> Result<ExecutionFinalResult> {
loop {
match self.status().await? {
Poll::Ready(val) => break Ok(val),
Poll::Pending => (),
}
tokio::time::sleep(std::time::Duration::from_millis(300)).await;
}
}
pub fn sender_id(&self) -> &AccountId {
&self.sender_id
}
pub fn hash(&self) -> &CryptoHash {
&self.hash
}
}
impl fmt::Debug for TransactionStatus {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("TransactionStatus")
.field("sender_id", &self.sender_id)
.field("hash", &self.hash)
.finish()
}
}
impl IntoFuture for TransactionStatus {
type Output = Result<ExecutionFinalResult>;
type IntoFuture = Pin<Box<dyn std::future::Future<Output = Self::Output>>>;
fn into_future(self) -> Self::IntoFuture {
Box::pin(async { self.wait().await })
}
}