1use crate::error::{ErrorKind, RpcErrorCode};
4use crate::result::{Execution, ExecutionFinalResult, Result, ViewResultDetails};
5use crate::rpc::client::{
6 send_batch_tx_and_retry, send_batch_tx_async_and_retry, DEFAULT_CALL_DEPOSIT,
7 DEFAULT_CALL_FN_GAS,
8};
9use crate::rpc::query::{Query, ViewFunction};
10use crate::types::{
11 AccessKey, AccountId, Gas, InMemorySigner, KeyType, UncToken, PublicKey, SecretKey,
12};
13use crate::worker::Worker;
14use crate::{Account, CryptoHash, Network};
15
16use unc_account_id::ParseAccountError;
17use unc_gas::UncGas;
18use unc_jsonrpc_client::errors::{JsonRpcError, JsonRpcServerError};
19use unc_jsonrpc_client::methods::tx::RpcTransactionError;
20use unc_primitives::borsh;
21use unc_primitives::transaction::{
22 Action, AddKeyAction, CreateAccountAction, DeleteAccountAction, DeleteKeyAction,
23 DeployContractAction, FunctionCallAction, PledgeAction, TransferAction,
24};
25use unc_primitives::views::FinalExecutionOutcomeView;
26use std::convert::TryInto;
27use std::fmt;
28use std::future::IntoFuture;
29use std::pin::Pin;
30use std::task::Poll;
31
32const MAX_GAS: UncGas = UncGas::from_tgas(300);
33
34#[derive(Debug)]
37pub struct Function {
38 pub(crate) name: String,
39 pub(crate) args: Result<Vec<u8>>,
40 pub(crate) deposit: UncToken,
41 pub(crate) gas: Gas,
42}
43
44impl Function {
45 pub fn new(name: &str) -> Self {
48 Self {
49 name: name.into(),
50 args: Ok(vec![]),
51 deposit: DEFAULT_CALL_DEPOSIT,
52 gas: DEFAULT_CALL_FN_GAS,
53 }
54 }
55
56 pub fn args(mut self, args: Vec<u8>) -> Self {
60 if self.args.is_err() {
61 return self;
62 }
63 self.args = Ok(args);
64 self
65 }
66
67 pub fn args_json<U: serde::Serialize>(mut self, args: U) -> Self {
71 match serde_json::to_vec(&args) {
72 Ok(args) => self.args = Ok(args),
73 Err(e) => self.args = Err(ErrorKind::DataConversion.custom(e)),
74 }
75 self
76 }
77
78 pub fn args_borsh<U: borsh::BorshSerialize>(mut self, args: U) -> Self {
81 match borsh::to_vec(&args) {
82 Ok(args) => self.args = Ok(args),
83 Err(e) => self.args = Err(ErrorKind::DataConversion.custom(e)),
84 }
85 self
86 }
87
88 pub fn deposit(mut self, deposit: UncToken) -> Self {
91 self.deposit = deposit;
92 self
93 }
94
95 pub fn gas(mut self, gas: Gas) -> Self {
97 self.gas = gas;
98 self
99 }
100
101 pub fn max_gas(self) -> Self {
103 self.gas(MAX_GAS)
104 }
105}
106
107pub struct Transaction {
118 worker: Worker<dyn Network>,
119 signer: InMemorySigner,
120 receiver_id: AccountId,
121 actions: Result<Vec<Action>>,
123}
124
125impl Transaction {
126 pub(crate) fn new(
127 worker: Worker<dyn Network>,
128 signer: InMemorySigner,
129 receiver_id: AccountId,
130 ) -> Self {
131 Self {
132 worker,
133 signer,
134 receiver_id,
135 actions: Ok(Vec::new()),
136 }
137 }
138
139 pub fn add_key(mut self, pk: PublicKey, ak: AccessKey) -> Self {
142 if let Ok(actions) = &mut self.actions {
143 actions.push(
144 AddKeyAction {
145 public_key: pk.into(),
146 access_key: ak.into(),
147 }
148 .into(),
149 );
150 }
151
152 self
153 }
154
155 pub fn call(mut self, function: Function) -> Self {
157 let args = match function.args {
158 Ok(args) => args,
159 Err(err) => {
160 self.actions = Err(err);
161 return self;
162 }
163 };
164
165 if let Ok(actions) = &mut self.actions {
166 actions.push(Action::FunctionCall(Box::new(FunctionCallAction {
167 method_name: function.name.to_string(),
168 args,
169 deposit: function.deposit.as_attounc(),
170 gas: function.gas.as_gas(),
171 })));
172 }
173
174 self
175 }
176
177 pub fn create_account(mut self) -> Self {
179 if let Ok(actions) = &mut self.actions {
180 actions.push(CreateAccountAction {}.into());
181 }
182 self
183 }
184
185 pub fn delete_account(mut self, beneficiary_id: &AccountId) -> Self {
188 if let Ok(actions) = &mut self.actions {
189 actions.push(
190 DeleteAccountAction {
191 beneficiary_id: beneficiary_id.clone(),
192 }
193 .into(),
194 );
195 }
196 self
197 }
198
199 pub fn delete_key(mut self, pk: PublicKey) -> Self {
202 if let Ok(actions) = &mut self.actions {
203 actions.push(DeleteKeyAction { public_key: pk.0 }.into());
204 }
205 self
206 }
207
208 pub fn deploy(mut self, code: &[u8]) -> Self {
210 if let Ok(actions) = &mut self.actions {
211 actions.push(DeployContractAction { code: code.into() }.into());
212 }
213 self
214 }
215
216 pub fn stake(mut self, stake: UncToken, pk: PublicKey) -> Self {
218 if let Ok(actions) = &mut self.actions {
219 actions.push(
220 PledgeAction {
221 pledge: stake.as_attounc(),
222 public_key: pk.0,
223 }
224 .into(),
225 );
226 }
227 self
228 }
229
230 pub fn transfer(mut self, deposit: UncToken) -> Self {
232 if let Ok(actions) = &mut self.actions {
233 actions.push(
234 TransferAction {
235 deposit: deposit.as_attounc(),
236 }
237 .into(),
238 );
239 }
240 self
241 }
242
243 async fn transact_raw(self) -> Result<FinalExecutionOutcomeView> {
244 let view = send_batch_tx_and_retry(
245 self.worker.client(),
246 &self.signer,
247 &self.receiver_id,
248 self.actions?,
249 )
250 .await?;
251
252 if !self.worker.tx_callbacks.is_empty() {
253 let total_gas_burnt = view.transaction_outcome.outcome.gas_burnt
254 + view
255 .receipts_outcome
256 .iter()
257 .map(|t| t.outcome.gas_burnt)
258 .sum::<u64>();
259
260 for callback in self.worker.tx_callbacks {
261 callback(Gas::from_gas(total_gas_burnt))?;
262 }
263 }
264
265 Ok(view)
266 }
267
268 pub async fn transact(self) -> Result<ExecutionFinalResult> {
270 self.transact_raw()
271 .await
272 .map(ExecutionFinalResult::from_view)
273 .map_err(crate::error::Error::from)
274 }
275
276 pub async fn transact_async(self) -> Result<TransactionStatus> {
284 send_batch_tx_async_and_retry(self.worker, &self.signer, &self.receiver_id, self.actions?)
285 .await
286 }
287}
288
289pub struct CallTransaction {
292 worker: Worker<dyn Network>,
293 signer: InMemorySigner,
294 contract_id: AccountId,
295 function: Function,
296}
297
298impl CallTransaction {
299 pub(crate) fn new(
300 worker: Worker<dyn Network>,
301 contract_id: AccountId,
302 signer: InMemorySigner,
303 function: &str,
304 ) -> Self {
305 Self {
306 worker,
307 signer,
308 contract_id,
309 function: Function::new(function),
310 }
311 }
312
313 pub fn args(mut self, args: Vec<u8>) -> Self {
317 self.function = self.function.args(args);
318 self
319 }
320
321 pub fn args_json<U: serde::Serialize>(mut self, args: U) -> Self {
325 self.function = self.function.args_json(args);
326 self
327 }
328
329 pub fn args_borsh<U: borsh::BorshSerialize>(mut self, args: U) -> Self {
332 self.function = self.function.args_borsh(args);
333 self
334 }
335
336 pub fn deposit(mut self, deposit: UncToken) -> Self {
339 self.function = self.function.deposit(deposit);
340 self
341 }
342
343 pub fn gas(mut self, gas: UncGas) -> Self {
345 self.function = self.function.gas(gas);
346 self
347 }
348
349 pub fn max_gas(self) -> Self {
351 self.gas(MAX_GAS)
352 }
353
354 pub async fn transact(self) -> Result<ExecutionFinalResult> {
358 let txn = self
359 .worker
360 .client()
361 .call(
362 &self.signer,
363 &self.contract_id,
364 self.function.name.to_string(),
365 self.function.args?,
366 self.function.gas.as_gas(),
367 self.function.deposit,
368 )
369 .await
370 .map(ExecutionFinalResult::from_view)
371 .map_err(crate::error::Error::from)?;
372
373 for callback in self.worker.tx_callbacks.iter() {
374 callback(txn.total_gas_burnt)?;
375 }
376 Ok(txn)
377 }
378
379 pub async fn transact_async(self) -> Result<TransactionStatus> {
387 send_batch_tx_async_and_retry(
388 self.worker,
389 &self.signer,
390 &self.contract_id,
391 vec![FunctionCallAction {
392 args: self.function.args?,
393 method_name: self.function.name,
394 gas: self.function.gas.as_gas(),
395 deposit: self.function.deposit.as_attounc(),
396 }
397 .into()],
398 )
399 .await
400 }
401
402 pub async fn view(self) -> Result<ViewResultDetails> {
404 Query::new(
405 self.worker.client(),
406 ViewFunction {
407 account_id: self.contract_id.clone(),
408 function: self.function,
409 },
410 )
411 .await
412 }
413}
414
415pub struct CreateAccountTransaction<'a, 'b> {
418 worker: &'a Worker<dyn Network>,
419 signer: InMemorySigner,
420 parent_id: AccountId,
421 new_account_id: &'b str,
422
423 initial_balance: UncToken,
424 secret_key: Option<SecretKey>,
425}
426
427impl<'a, 'b> CreateAccountTransaction<'a, 'b> {
428 pub(crate) fn new(
429 worker: &'a Worker<dyn Network>,
430 signer: InMemorySigner,
431 parent_id: AccountId,
432 new_account_id: &'b str,
433 ) -> Self {
434 Self {
435 worker,
436 signer,
437 parent_id,
438 new_account_id,
439 initial_balance: UncToken::from_attounc(100000000000000000000000u128),
440 secret_key: None,
441 }
442 }
443
444 pub fn initial_balance(mut self, initial_balance: UncToken) -> Self {
447 self.initial_balance = initial_balance;
448 self
449 }
450
451 pub fn keys(mut self, secret_key: SecretKey) -> Self {
453 self.secret_key = Some(secret_key);
454 self
455 }
456
457 pub async fn transact(self) -> Result<Execution<Account>> {
460 let sk = self
461 .secret_key
462 .unwrap_or_else(|| SecretKey::from_seed(KeyType::ED25519, "subaccount.seed"));
463 let id: AccountId = format!("{}.{}", self.new_account_id, self.parent_id)
464 .try_into()
465 .map_err(|e: ParseAccountError| ErrorKind::DataConversion.custom(e))?;
466
467 let outcome = self
468 .worker
469 .client()
470 .create_account(&self.signer, &id, sk.public_key(), self.initial_balance)
471 .await?;
472
473 let signer = InMemorySigner::from_secret_key(id, sk);
474 let account = Account::new(signer, self.worker.clone());
475 let details = ExecutionFinalResult::from_view(outcome);
476
477 for callback in self.worker.tx_callbacks.iter() {
478 callback(details.total_gas_burnt)?;
479 }
480
481 Ok(Execution {
482 result: account,
483 details,
484 })
485 }
486}
487
488#[must_use]
493pub struct TransactionStatus {
494 worker: Worker<dyn Network>,
495 sender_id: AccountId,
496 hash: CryptoHash,
497}
498
499impl TransactionStatus {
500 pub(crate) fn new(
501 worker: Worker<dyn Network>,
502 id: AccountId,
503 hash: unc_primitives::hash::CryptoHash,
504 ) -> Self {
505 Self {
506 worker,
507 sender_id: id,
508 hash: CryptoHash(hash.0),
509 }
510 }
511
512 pub async fn status(&self) -> Result<Poll<ExecutionFinalResult>> {
516 let result = self
517 .worker
518 .client()
519 .tx_async_status(
520 &self.sender_id,
521 unc_primitives::hash::CryptoHash(self.hash.0),
522 )
523 .await
524 .map(ExecutionFinalResult::from_view);
525
526 match result {
527 Ok(result) => Ok(Poll::Ready(result)),
528 Err(err) => match err {
529 JsonRpcError::ServerError(JsonRpcServerError::HandlerError(
530 RpcTransactionError::UnknownTransaction { .. },
531 )) => Ok(Poll::Pending),
532 other => Err(RpcErrorCode::BroadcastTxFailure.custom(other)),
533 },
534 }
535 }
536
537 pub(crate) async fn wait(self) -> Result<ExecutionFinalResult> {
539 loop {
540 match self.status().await? {
541 Poll::Ready(val) => break Ok(val),
542 Poll::Pending => (),
543 }
544
545 tokio::time::sleep(std::time::Duration::from_millis(300)).await;
546 }
547 }
548
549 pub fn sender_id(&self) -> &AccountId {
551 &self.sender_id
552 }
553
554 pub fn hash(&self) -> &CryptoHash {
556 &self.hash
557 }
558}
559
560impl fmt::Debug for TransactionStatus {
561 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
562 f.debug_struct("TransactionStatus")
563 .field("sender_id", &self.sender_id)
564 .field("hash", &self.hash)
565 .finish()
566 }
567}
568
569impl IntoFuture for TransactionStatus {
570 type Output = Result<ExecutionFinalResult>;
571 type IntoFuture = Pin<Box<dyn std::future::Future<Output = Self::Output>>>;
572
573 fn into_future(self) -> Self::IntoFuture {
574 Box::pin(async { self.wait().await })
575 }
576}