1use std::collections::BTreeMap;
5
6use iota_types::{
7 Address, Object, ObjectId, StructTag, Transaction, TransactionDigest, TransactionEffects,
8 UserSignature, Version,
9};
10
11#[derive(Default)]
13#[non_exhaustive]
14pub enum WaitForTransaction {
15 IndexedOnNode,
24 #[default]
27 Finalized,
28}
29
30#[derive(Clone, Debug)]
33pub struct ObjectsPage {
34 pub data: Vec<Object>,
36 pub next_cursor: Option<Vec<u8>>,
40}
41
42#[derive(Clone, Debug, Default)]
45pub struct ProtocolConfig {
46 pub(crate) attributes: BTreeMap<String, String>,
47}
48
49impl ProtocolConfig {
50 pub fn new(attributes: BTreeMap<String, String>) -> Self {
53 Self { attributes }
54 }
55
56 pub fn attributes(&self) -> &BTreeMap<String, String> {
59 &self.attributes
60 }
61
62 pub fn attribute(&self, name: &str) -> Option<&str> {
64 self.attributes.get(name).map(String::as_str)
65 }
66}
67
68pub trait TransactionBuilderClientBase {
71 type Error: 'static + std::error::Error + Send + Sync;
73}
74
75pub trait TransactionBuilderLedgerClient: TransactionBuilderClientBase {
79 fn object(
81 &self,
82 object_id: ObjectId,
83 version: impl Into<Option<Version>>,
84 ) -> impl std::future::Future<Output = Result<Option<Object>, Self::Error>>;
85
86 fn objects_by_id(
93 &self,
94 object_ids: &[(ObjectId, Option<Version>)],
95 ) -> impl std::future::Future<Output = Result<Vec<Option<Object>>, Self::Error>> {
96 async move {
97 let mut objects = Vec::with_capacity(object_ids.len());
98 for (object_id, version) in object_ids {
99 objects.push(self.object(*object_id, *version).await?);
100 }
101 Ok(objects)
102 }
103 }
104
105 fn objects(
113 &self,
114 struct_tag: Option<StructTag>,
115 owner: Address,
116 cursor: Option<Vec<u8>>,
117 limit: Option<usize>,
118 ) -> impl std::future::Future<Output = Result<ObjectsPage, Self::Error>>;
119
120 fn protocol_config(
122 &self,
123 ) -> impl std::future::Future<Output = Result<ProtocolConfig, Self::Error>>;
124
125 fn reference_gas_price(
127 &self,
128 epoch: impl Into<Option<u64>>,
129 ) -> impl std::future::Future<Output = Result<Option<u64>, Self::Error>>;
130}
131
132pub trait TransactionBuilderSimulationClient: TransactionBuilderClientBase {
136 type DryRunResult;
138
139 fn estimate_transaction_budget(
145 &self,
146 transaction: &Transaction,
147 ) -> impl std::future::Future<Output = Result<Option<u64>, Self::Error>>;
148
149 fn dry_run_transaction(
151 &self,
152 transaction: &Transaction,
153 skip_checks: bool,
154 ) -> impl std::future::Future<Output = Result<Self::DryRunResult, Self::Error>>;
155}
156
157pub trait TransactionBuilderExecutionClient: TransactionBuilderClientBase {
160 fn execute_transaction(
166 &self,
167 signatures: &[UserSignature],
168 transaction: &Transaction,
169 wait_for: impl Into<Option<WaitForTransaction>>,
170 ) -> impl std::future::Future<Output = Result<TransactionEffects, Self::Error>>;
171
172 fn wait_for_transaction(
174 &self,
175 digest: TransactionDigest,
176 wait_for: WaitForTransaction,
177 ) -> impl std::future::Future<Output = Result<(), Self::Error>>;
178
179 fn transaction_effects(
181 &self,
182 digest: TransactionDigest,
183 ) -> impl std::future::Future<Output = Result<Option<TransactionEffects>, Self::Error>>;
184}
185
186pub trait TransactionBuilderClient:
193 TransactionBuilderLedgerClient
194 + TransactionBuilderSimulationClient
195 + TransactionBuilderExecutionClient
196{
197}
198
199impl<T> TransactionBuilderClient for T where
200 T: TransactionBuilderLedgerClient
201 + TransactionBuilderSimulationClient
202 + TransactionBuilderExecutionClient
203{
204}
205
206impl<T: TransactionBuilderClientBase> TransactionBuilderClientBase for &T {
207 type Error = T::Error;
208}
209
210impl<T: TransactionBuilderLedgerClient> TransactionBuilderLedgerClient for &T {
211 fn object(
212 &self,
213 object_id: ObjectId,
214 version: impl Into<Option<Version>>,
215 ) -> impl std::future::Future<Output = Result<Option<Object>, Self::Error>> {
216 (*self).object(object_id, version)
217 }
218
219 fn objects_by_id(
220 &self,
221 object_ids: &[(ObjectId, Option<Version>)],
222 ) -> impl std::future::Future<Output = Result<Vec<Option<Object>>, Self::Error>> {
223 (*self).objects_by_id(object_ids)
224 }
225
226 fn objects(
227 &self,
228 struct_tag: Option<StructTag>,
229 owner: Address,
230 cursor: Option<Vec<u8>>,
231 limit: Option<usize>,
232 ) -> impl std::future::Future<Output = Result<ObjectsPage, Self::Error>> {
233 (*self).objects(struct_tag, owner, cursor, limit)
234 }
235
236 fn protocol_config(
237 &self,
238 ) -> impl std::future::Future<Output = Result<ProtocolConfig, Self::Error>> {
239 (*self).protocol_config()
240 }
241
242 fn reference_gas_price(
243 &self,
244 epoch: impl Into<Option<u64>>,
245 ) -> impl std::future::Future<Output = Result<Option<u64>, Self::Error>> {
246 (*self).reference_gas_price(epoch)
247 }
248}
249
250impl<T: TransactionBuilderSimulationClient> TransactionBuilderSimulationClient for &T {
251 type DryRunResult = T::DryRunResult;
252
253 fn estimate_transaction_budget(
254 &self,
255 transaction: &Transaction,
256 ) -> impl std::future::Future<Output = Result<Option<u64>, Self::Error>> {
257 (*self).estimate_transaction_budget(transaction)
258 }
259
260 fn dry_run_transaction(
261 &self,
262 transaction: &Transaction,
263 skip_checks: bool,
264 ) -> impl std::future::Future<Output = Result<Self::DryRunResult, Self::Error>> {
265 (*self).dry_run_transaction(transaction, skip_checks)
266 }
267}
268
269impl<T: TransactionBuilderExecutionClient> TransactionBuilderExecutionClient for &T {
270 fn execute_transaction(
271 &self,
272 signatures: &[UserSignature],
273 transaction: &Transaction,
274 wait_for: impl Into<Option<WaitForTransaction>>,
275 ) -> impl std::future::Future<Output = Result<TransactionEffects, Self::Error>> {
276 (*self).execute_transaction(signatures, transaction, wait_for)
277 }
278
279 fn wait_for_transaction(
280 &self,
281 digest: TransactionDigest,
282 wait_for: WaitForTransaction,
283 ) -> impl std::future::Future<Output = Result<(), Self::Error>> {
284 (*self).wait_for_transaction(digest, wait_for)
285 }
286
287 fn transaction_effects(
288 &self,
289 digest: TransactionDigest,
290 ) -> impl std::future::Future<Output = Result<Option<TransactionEffects>, Self::Error>> {
291 (*self).transaction_effects(digest)
292 }
293}
294
295impl<T: TransactionBuilderClientBase> TransactionBuilderClientBase for std::sync::Arc<T> {
296 type Error = T::Error;
297}
298
299impl<T: TransactionBuilderLedgerClient> TransactionBuilderLedgerClient for std::sync::Arc<T> {
300 fn object(
301 &self,
302 object_id: ObjectId,
303 version: impl Into<Option<Version>>,
304 ) -> impl std::future::Future<Output = Result<Option<Object>, Self::Error>> {
305 self.as_ref().object(object_id, version)
306 }
307
308 fn objects_by_id(
309 &self,
310 object_ids: &[(ObjectId, Option<Version>)],
311 ) -> impl std::future::Future<Output = Result<Vec<Option<Object>>, Self::Error>> {
312 self.as_ref().objects_by_id(object_ids)
313 }
314
315 fn objects(
316 &self,
317 struct_tag: Option<StructTag>,
318 owner: Address,
319 cursor: Option<Vec<u8>>,
320 limit: Option<usize>,
321 ) -> impl std::future::Future<Output = Result<ObjectsPage, Self::Error>> {
322 self.as_ref().objects(struct_tag, owner, cursor, limit)
323 }
324
325 fn protocol_config(
326 &self,
327 ) -> impl std::future::Future<Output = Result<ProtocolConfig, Self::Error>> {
328 self.as_ref().protocol_config()
329 }
330
331 fn reference_gas_price(
332 &self,
333 epoch: impl Into<Option<u64>>,
334 ) -> impl std::future::Future<Output = Result<Option<u64>, Self::Error>> {
335 self.as_ref().reference_gas_price(epoch)
336 }
337}
338
339impl<T: TransactionBuilderSimulationClient> TransactionBuilderSimulationClient
340 for std::sync::Arc<T>
341{
342 type DryRunResult = T::DryRunResult;
343
344 fn estimate_transaction_budget(
345 &self,
346 transaction: &Transaction,
347 ) -> impl std::future::Future<Output = Result<Option<u64>, Self::Error>> {
348 self.as_ref().estimate_transaction_budget(transaction)
349 }
350
351 fn dry_run_transaction(
352 &self,
353 transaction: &Transaction,
354 skip_checks: bool,
355 ) -> impl std::future::Future<Output = Result<Self::DryRunResult, Self::Error>> {
356 self.as_ref().dry_run_transaction(transaction, skip_checks)
357 }
358}
359
360impl<T: TransactionBuilderExecutionClient> TransactionBuilderExecutionClient for std::sync::Arc<T> {
361 fn execute_transaction(
362 &self,
363 signatures: &[UserSignature],
364 transaction: &Transaction,
365 wait_for: impl Into<Option<WaitForTransaction>>,
366 ) -> impl std::future::Future<Output = Result<TransactionEffects, Self::Error>> {
367 self.as_ref()
368 .execute_transaction(signatures, transaction, wait_for)
369 }
370
371 fn wait_for_transaction(
372 &self,
373 digest: TransactionDigest,
374 wait_for: WaitForTransaction,
375 ) -> impl std::future::Future<Output = Result<(), Self::Error>> {
376 self.as_ref().wait_for_transaction(digest, wait_for)
377 }
378
379 fn transaction_effects(
380 &self,
381 digest: TransactionDigest,
382 ) -> impl std::future::Future<Output = Result<Option<TransactionEffects>, Self::Error>> {
383 self.as_ref().transaction_effects(digest)
384 }
385}
386
387#[cfg(feature = "test-client")]
388pub(crate) mod test_client {
389 use iota_types::{
392 Address, MoveStruct, Object, ObjectData, ObjectId, Owner, StructTag, Transaction,
393 TransactionDigest, TransactionEffects, UserSignature, Version,
394 };
395
396 use super::{
397 TransactionBuilderClientBase, TransactionBuilderExecutionClient,
398 TransactionBuilderLedgerClient, TransactionBuilderSimulationClient, WaitForTransaction,
399 };
400 use crate::{
401 ObjectsPage,
402 builder::{BASE_TX_COST_FIXED_KEY, MAX_GAS_PAYMENT_OBJECTS_KEY},
403 };
404
405 const FABRICATED_COIN_BALANCE: u64 = 1_000_000_000_000;
408
409 fn fabricated_coin(object_id: ObjectId, owner: Owner, balance: u64) -> Object {
415 let mut contents = Vec::with_capacity(ObjectId::LENGTH + std::mem::size_of::<u64>());
416 contents.extend_from_slice(object_id.as_ref());
417 contents.extend_from_slice(&balance.to_le_bytes());
418 let move_struct = MoveStruct::new(
419 StructTag::new_gas_coin().into(),
420 Version::from_u64(1),
421 contents,
422 )
423 .expect("contents always contain a full object id");
424 Object::new(
425 ObjectData::Struct(move_struct),
426 owner,
427 TransactionDigest::ZERO,
428 0,
429 )
430 }
431
432 #[derive(Clone, Copy, Debug, Default)]
445 pub struct TestClient;
446
447 #[derive(Clone, Debug, thiserror::Error)]
449 #[error("TestClientError: {0}")]
450 pub struct TestClientError(pub String);
451
452 impl TransactionBuilderClientBase for TestClient {
453 type Error = TestClientError;
454 }
455
456 impl TransactionBuilderLedgerClient for TestClient {
457 async fn object(
458 &self,
459 object_id: ObjectId,
460 _version: impl Into<Option<Version>>,
461 ) -> Result<Option<Object>, Self::Error> {
462 let owner = if object_id == ObjectId::SYSTEM_STATE || object_id == ObjectId::CLOCK {
465 Owner::Shared(Version::from_u64(1))
466 } else {
467 Owner::Address(Address::ZERO)
468 };
469 Ok(Some(fabricated_coin(
470 object_id,
471 owner,
472 FABRICATED_COIN_BALANCE,
473 )))
474 }
475
476 async fn objects(
477 &self,
478 _struct_tag: Option<StructTag>,
479 owner: Address,
480 _cursor: Option<Vec<u8>>,
481 _limit: Option<usize>,
482 ) -> Result<ObjectsPage, Self::Error> {
483 let gas_coin_id = ObjectId::from_bytes([0xee; ObjectId::LENGTH])
487 .expect("32 bytes is a valid object id");
488 let owner = Owner::Address(owner);
489 Ok(ObjectsPage {
490 data: vec![fabricated_coin(gas_coin_id, owner, FABRICATED_COIN_BALANCE)],
491 next_cursor: None,
492 })
493 }
494
495 async fn reference_gas_price(
496 &self,
497 _epoch: impl Into<Option<u64>>,
498 ) -> Result<Option<u64>, Self::Error> {
499 Ok(Some(1000))
500 }
501
502 async fn protocol_config(&self) -> Result<super::ProtocolConfig, Self::Error> {
503 let mut config = super::ProtocolConfig::default();
504 config
505 .attributes
506 .insert(BASE_TX_COST_FIXED_KEY.to_owned(), "1000".to_owned());
507 config
508 .attributes
509 .insert(MAX_GAS_PAYMENT_OBJECTS_KEY.to_owned(), "256".to_owned());
510 Ok(config)
511 }
512 }
513
514 impl TransactionBuilderSimulationClient for TestClient {
515 type DryRunResult = ();
516
517 async fn estimate_transaction_budget(
518 &self,
519 _transaction: &Transaction,
520 ) -> Result<Option<u64>, Self::Error> {
521 Ok(Some(50_000_000))
522 }
523
524 async fn dry_run_transaction(
525 &self,
526 _transaction: &Transaction,
527 _skip_checks: bool,
528 ) -> Result<Self::DryRunResult, Self::Error> {
529 Ok(())
530 }
531 }
532
533 impl TransactionBuilderExecutionClient for TestClient {
534 async fn execute_transaction(
535 &self,
536 _signatures: &[UserSignature],
537 _transaction: &Transaction,
538 _wait_for: impl Into<Option<WaitForTransaction>>,
539 ) -> Result<TransactionEffects, Self::Error> {
540 Err(TestClientError(
541 "TestClient cannot execute transactions".to_string(),
542 ))
543 }
544
545 async fn wait_for_transaction(
546 &self,
547 _digest: TransactionDigest,
548 _wait_for: WaitForTransaction,
549 ) -> Result<(), Self::Error> {
550 Ok(())
551 }
552
553 async fn transaction_effects(
554 &self,
555 _digest: TransactionDigest,
556 ) -> Result<Option<TransactionEffects>, Self::Error> {
557 Ok(None)
558 }
559 }
560
561 #[derive(Clone, Default)]
564 pub struct RecordingClient {
565 pub batches: std::sync::Arc<std::sync::Mutex<Vec<Vec<ObjectId>>>>,
567 pub singles: std::sync::Arc<std::sync::Mutex<Vec<ObjectId>>>,
569 pub missing: Vec<ObjectId>,
571 }
572
573 impl RecordingClient {
574 pub fn batches(&self) -> Vec<Vec<ObjectId>> {
576 self.batches.lock().unwrap().clone()
577 }
578
579 pub fn singles(&self) -> Vec<ObjectId> {
581 self.singles.lock().unwrap().clone()
582 }
583 }
584
585 impl TransactionBuilderClientBase for RecordingClient {
586 type Error = crate::TestClientError;
587 }
588
589 impl TransactionBuilderLedgerClient for RecordingClient {
590 async fn object(
591 &self,
592 object_id: ObjectId,
593 version: impl Into<Option<Version>>,
594 ) -> Result<Option<Object>, Self::Error> {
595 self.singles.lock().unwrap().push(object_id);
596 if self.missing.contains(&object_id) {
597 return Ok(None);
598 }
599 crate::TestClient.object(object_id, version).await
600 }
601
602 async fn objects_by_id(
603 &self,
604 object_ids: &[(ObjectId, Option<Version>)],
605 ) -> Result<Vec<Option<Object>>, Self::Error> {
606 self.batches
607 .lock()
608 .unwrap()
609 .push(object_ids.iter().map(|(id, _)| *id).collect());
610 let mut objects = Vec::with_capacity(object_ids.len());
611 for (object_id, _) in object_ids {
612 objects.push(if self.missing.contains(object_id) {
613 None
614 } else {
615 crate::TestClient.object(*object_id, None).await?
616 });
617 }
618 Ok(objects)
619 }
620
621 async fn objects(
622 &self,
623 struct_tag: Option<StructTag>,
624 owner: Address,
625 cursor: Option<Vec<u8>>,
626 limit: Option<usize>,
627 ) -> Result<crate::ObjectsPage, Self::Error> {
628 crate::TestClient
629 .objects(struct_tag, owner, cursor, limit)
630 .await
631 }
632
633 async fn reference_gas_price(
634 &self,
635 epoch: impl Into<Option<u64>>,
636 ) -> Result<Option<u64>, Self::Error> {
637 crate::TestClient.reference_gas_price(epoch).await
638 }
639
640 async fn protocol_config(&self) -> Result<super::ProtocolConfig, Self::Error> {
641 crate::TestClient.protocol_config().await
642 }
643 }
644
645 impl TransactionBuilderSimulationClient for RecordingClient {
646 type DryRunResult = ();
647
648 async fn estimate_transaction_budget(
649 &self,
650 transaction: &Transaction,
651 ) -> Result<Option<u64>, Self::Error> {
652 crate::TestClient
653 .estimate_transaction_budget(transaction)
654 .await
655 }
656
657 async fn dry_run_transaction(
658 &self,
659 transaction: &Transaction,
660 skip_checks: bool,
661 ) -> Result<Self::DryRunResult, Self::Error> {
662 crate::TestClient
663 .dry_run_transaction(transaction, skip_checks)
664 .await
665 }
666 }
667
668 impl TransactionBuilderExecutionClient for RecordingClient {
669 async fn execute_transaction(
670 &self,
671 signatures: &[iota_types::UserSignature],
672 transaction: &Transaction,
673 wait_for: impl Into<Option<WaitForTransaction>>,
674 ) -> Result<TransactionEffects, Self::Error> {
675 crate::TestClient
676 .execute_transaction(signatures, transaction, wait_for)
677 .await
678 }
679
680 async fn wait_for_transaction(
681 &self,
682 digest: iota_types::TransactionDigest,
683 wait_for: WaitForTransaction,
684 ) -> Result<(), Self::Error> {
685 crate::TestClient
686 .wait_for_transaction(digest, wait_for)
687 .await
688 }
689
690 async fn transaction_effects(
691 &self,
692 digest: iota_types::TransactionDigest,
693 ) -> Result<Option<TransactionEffects>, Self::Error> {
694 crate::TestClient.transaction_effects(digest).await
695 }
696 }
697}