1use std::collections::BTreeMap;
5
6use iota_types::{
7 Address, Object, ObjectId, StructTag, Transaction, TransactionDigest, TransactionEffects,
8 UserSignature, Version,
9};
10
11#[derive(Default)]
21#[non_exhaustive]
22pub enum WaitForTransaction {
23 IndexedOnNode,
33 #[default]
36 Finalized,
37}
38
39#[derive(Clone, Debug)]
42pub struct ObjectsPage {
43 pub data: Vec<Object>,
45 pub next_cursor: Option<Vec<u8>>,
49}
50
51#[derive(Clone, Debug, Default)]
54pub struct ProtocolConfig {
55 pub attributes: BTreeMap<String, String>,
58}
59
60pub trait TransactionBuilderClientBase {
63 type Error: 'static + std::error::Error + Send + Sync;
65}
66
67pub trait TransactionBuilderLedgerClient: TransactionBuilderClientBase {
71 fn object(
73 &self,
74 object_id: ObjectId,
75 version: impl Into<Option<Version>>,
76 ) -> impl std::future::Future<Output = Result<Option<Object>, Self::Error>>;
77
78 fn objects_by_id(
85 &self,
86 object_ids: &[(ObjectId, Option<Version>)],
87 ) -> impl std::future::Future<Output = Result<Vec<Option<Object>>, Self::Error>> {
88 async move {
89 let mut objects = Vec::with_capacity(object_ids.len());
90 for (object_id, version) in object_ids {
91 objects.push(self.object(*object_id, *version).await?);
92 }
93 Ok(objects)
94 }
95 }
96
97 fn objects(
105 &self,
106 struct_tag: Option<StructTag>,
107 owner: Address,
108 cursor: Option<Vec<u8>>,
109 limit: Option<usize>,
110 ) -> impl std::future::Future<Output = Result<ObjectsPage, Self::Error>>;
111
112 fn protocol_config(
116 &self,
117 ) -> impl std::future::Future<Output = Result<ProtocolConfig, Self::Error>> {
118 std::future::ready(Ok(ProtocolConfig::default()))
119 }
120
121 fn reference_gas_price(
123 &self,
124 epoch: impl Into<Option<u64>>,
125 ) -> impl std::future::Future<Output = Result<Option<u64>, Self::Error>>;
126}
127
128pub trait TransactionBuilderSimulationClient: TransactionBuilderClientBase {
132 type DryRunResult;
134
135 fn estimate_transaction_budget(
141 &self,
142 transaction: &Transaction,
143 ) -> impl std::future::Future<Output = Result<Option<u64>, Self::Error>>;
144
145 fn dry_run_transaction(
147 &self,
148 transaction: &Transaction,
149 skip_checks: bool,
150 ) -> impl std::future::Future<Output = Result<Self::DryRunResult, Self::Error>>;
151}
152
153pub trait TransactionBuilderExecutionClient: TransactionBuilderClientBase {
156 fn execute_transaction(
158 &self,
159 signatures: &[UserSignature],
160 transaction: &Transaction,
161 wait_for: impl Into<Option<WaitForTransaction>>,
162 ) -> impl std::future::Future<Output = Result<TransactionEffects, Self::Error>>;
163
164 fn wait_for_transaction(
166 &self,
167 digest: TransactionDigest,
168 wait_for: WaitForTransaction,
169 ) -> impl std::future::Future<Output = Result<(), Self::Error>>;
170
171 fn transaction_effects(
173 &self,
174 digest: TransactionDigest,
175 ) -> impl std::future::Future<Output = Result<Option<TransactionEffects>, Self::Error>>;
176}
177
178pub trait TransactionBuilderClient:
185 TransactionBuilderLedgerClient
186 + TransactionBuilderSimulationClient
187 + TransactionBuilderExecutionClient
188{
189}
190
191impl<T> TransactionBuilderClient for T where
192 T: TransactionBuilderLedgerClient
193 + TransactionBuilderSimulationClient
194 + TransactionBuilderExecutionClient
195{
196}
197
198impl<T: TransactionBuilderClientBase> TransactionBuilderClientBase for &T {
199 type Error = T::Error;
200}
201
202impl<T: TransactionBuilderLedgerClient> TransactionBuilderLedgerClient for &T {
203 fn object(
204 &self,
205 object_id: ObjectId,
206 version: impl Into<Option<Version>>,
207 ) -> impl std::future::Future<Output = Result<Option<Object>, Self::Error>> {
208 (*self).object(object_id, version)
209 }
210
211 fn objects_by_id(
212 &self,
213 object_ids: &[(ObjectId, Option<Version>)],
214 ) -> impl std::future::Future<Output = Result<Vec<Option<Object>>, Self::Error>> {
215 (*self).objects_by_id(object_ids)
216 }
217
218 fn objects(
219 &self,
220 struct_tag: Option<StructTag>,
221 owner: Address,
222 cursor: Option<Vec<u8>>,
223 limit: Option<usize>,
224 ) -> impl std::future::Future<Output = Result<ObjectsPage, Self::Error>> {
225 (*self).objects(struct_tag, owner, cursor, limit)
226 }
227
228 fn protocol_config(
229 &self,
230 ) -> impl std::future::Future<Output = Result<ProtocolConfig, Self::Error>> {
231 (*self).protocol_config()
232 }
233
234 fn reference_gas_price(
235 &self,
236 epoch: impl Into<Option<u64>>,
237 ) -> impl std::future::Future<Output = Result<Option<u64>, Self::Error>> {
238 (*self).reference_gas_price(epoch)
239 }
240}
241
242impl<T: TransactionBuilderSimulationClient> TransactionBuilderSimulationClient for &T {
243 type DryRunResult = T::DryRunResult;
244
245 fn estimate_transaction_budget(
246 &self,
247 transaction: &Transaction,
248 ) -> impl std::future::Future<Output = Result<Option<u64>, Self::Error>> {
249 (*self).estimate_transaction_budget(transaction)
250 }
251
252 fn dry_run_transaction(
253 &self,
254 transaction: &Transaction,
255 skip_checks: bool,
256 ) -> impl std::future::Future<Output = Result<Self::DryRunResult, Self::Error>> {
257 (*self).dry_run_transaction(transaction, skip_checks)
258 }
259}
260
261impl<T: TransactionBuilderExecutionClient> TransactionBuilderExecutionClient for &T {
262 fn execute_transaction(
263 &self,
264 signatures: &[UserSignature],
265 transaction: &Transaction,
266 wait_for: impl Into<Option<WaitForTransaction>>,
267 ) -> impl std::future::Future<Output = Result<TransactionEffects, Self::Error>> {
268 (*self).execute_transaction(signatures, transaction, wait_for)
269 }
270
271 fn wait_for_transaction(
272 &self,
273 digest: TransactionDigest,
274 wait_for: WaitForTransaction,
275 ) -> impl std::future::Future<Output = Result<(), Self::Error>> {
276 (*self).wait_for_transaction(digest, wait_for)
277 }
278
279 fn transaction_effects(
280 &self,
281 digest: TransactionDigest,
282 ) -> impl std::future::Future<Output = Result<Option<TransactionEffects>, Self::Error>> {
283 (*self).transaction_effects(digest)
284 }
285}
286
287impl<T: TransactionBuilderClientBase> TransactionBuilderClientBase for std::sync::Arc<T> {
288 type Error = T::Error;
289}
290
291impl<T: TransactionBuilderLedgerClient> TransactionBuilderLedgerClient for std::sync::Arc<T> {
292 fn object(
293 &self,
294 object_id: ObjectId,
295 version: impl Into<Option<Version>>,
296 ) -> impl std::future::Future<Output = Result<Option<Object>, Self::Error>> {
297 self.as_ref().object(object_id, version)
298 }
299
300 fn objects_by_id(
301 &self,
302 object_ids: &[(ObjectId, Option<Version>)],
303 ) -> impl std::future::Future<Output = Result<Vec<Option<Object>>, Self::Error>> {
304 self.as_ref().objects_by_id(object_ids)
305 }
306
307 fn objects(
308 &self,
309 struct_tag: Option<StructTag>,
310 owner: Address,
311 cursor: Option<Vec<u8>>,
312 limit: Option<usize>,
313 ) -> impl std::future::Future<Output = Result<ObjectsPage, Self::Error>> {
314 self.as_ref().objects(struct_tag, owner, cursor, limit)
315 }
316
317 fn protocol_config(
318 &self,
319 ) -> impl std::future::Future<Output = Result<ProtocolConfig, Self::Error>> {
320 self.as_ref().protocol_config()
321 }
322
323 fn reference_gas_price(
324 &self,
325 epoch: impl Into<Option<u64>>,
326 ) -> impl std::future::Future<Output = Result<Option<u64>, Self::Error>> {
327 self.as_ref().reference_gas_price(epoch)
328 }
329}
330
331impl<T: TransactionBuilderSimulationClient> TransactionBuilderSimulationClient
332 for std::sync::Arc<T>
333{
334 type DryRunResult = T::DryRunResult;
335
336 fn estimate_transaction_budget(
337 &self,
338 transaction: &Transaction,
339 ) -> impl std::future::Future<Output = Result<Option<u64>, Self::Error>> {
340 self.as_ref().estimate_transaction_budget(transaction)
341 }
342
343 fn dry_run_transaction(
344 &self,
345 transaction: &Transaction,
346 skip_checks: bool,
347 ) -> impl std::future::Future<Output = Result<Self::DryRunResult, Self::Error>> {
348 self.as_ref().dry_run_transaction(transaction, skip_checks)
349 }
350}
351
352impl<T: TransactionBuilderExecutionClient> TransactionBuilderExecutionClient for std::sync::Arc<T> {
353 fn execute_transaction(
354 &self,
355 signatures: &[UserSignature],
356 transaction: &Transaction,
357 wait_for: impl Into<Option<WaitForTransaction>>,
358 ) -> impl std::future::Future<Output = Result<TransactionEffects, Self::Error>> {
359 self.as_ref()
360 .execute_transaction(signatures, transaction, wait_for)
361 }
362
363 fn wait_for_transaction(
364 &self,
365 digest: TransactionDigest,
366 wait_for: WaitForTransaction,
367 ) -> impl std::future::Future<Output = Result<(), Self::Error>> {
368 self.as_ref().wait_for_transaction(digest, wait_for)
369 }
370
371 fn transaction_effects(
372 &self,
373 digest: TransactionDigest,
374 ) -> impl std::future::Future<Output = Result<Option<TransactionEffects>, Self::Error>> {
375 self.as_ref().transaction_effects(digest)
376 }
377}
378
379#[cfg(feature = "test-client")]
380pub(crate) mod test_client {
381 use iota_types::{
384 Address, MoveStruct, Object, ObjectData, ObjectId, Owner, StructTag, Transaction,
385 TransactionDigest, TransactionEffects, UserSignature, Version,
386 };
387
388 use super::{
389 TransactionBuilderClientBase, TransactionBuilderExecutionClient,
390 TransactionBuilderLedgerClient, TransactionBuilderSimulationClient, WaitForTransaction,
391 };
392 use crate::ObjectsPage;
393
394 const FABRICATED_COIN_BALANCE: u64 = 1_000_000_000_000;
397
398 fn fabricated_coin(object_id: ObjectId, owner: Owner, balance: u64) -> Object {
404 let mut contents = Vec::with_capacity(ObjectId::LENGTH + std::mem::size_of::<u64>());
405 contents.extend_from_slice(object_id.as_ref());
406 contents.extend_from_slice(&balance.to_le_bytes());
407 let move_struct = MoveStruct::new(
408 StructTag::new_gas_coin().into(),
409 Version::from_u64(1),
410 contents,
411 )
412 .expect("contents always contain a full object id");
413 Object::new(
414 ObjectData::Struct(move_struct),
415 owner,
416 TransactionDigest::ZERO,
417 0,
418 )
419 }
420
421 #[derive(Clone, Copy, Debug, Default)]
434 pub struct TestClient;
435
436 #[derive(Clone, Debug, thiserror::Error)]
438 #[error("TestClientError: {0}")]
439 pub struct TestClientError(pub String);
440
441 impl TransactionBuilderClientBase for TestClient {
442 type Error = TestClientError;
443 }
444
445 impl TransactionBuilderLedgerClient for TestClient {
446 async fn object(
447 &self,
448 object_id: ObjectId,
449 _version: impl Into<Option<Version>>,
450 ) -> Result<Option<Object>, Self::Error> {
451 let owner = if object_id == ObjectId::SYSTEM_STATE || object_id == ObjectId::CLOCK {
454 Owner::Shared(Version::from_u64(1))
455 } else {
456 Owner::Address(Address::ZERO)
457 };
458 Ok(Some(fabricated_coin(
459 object_id,
460 owner,
461 FABRICATED_COIN_BALANCE,
462 )))
463 }
464
465 async fn objects(
466 &self,
467 _struct_tag: Option<StructTag>,
468 owner: Address,
469 _cursor: Option<Vec<u8>>,
470 _limit: Option<usize>,
471 ) -> Result<ObjectsPage, Self::Error> {
472 let gas_coin_id = ObjectId::from_bytes([0xee; ObjectId::LENGTH])
476 .expect("32 bytes is a valid object id");
477 let owner = Owner::Address(owner);
478 Ok(ObjectsPage {
479 data: vec![fabricated_coin(gas_coin_id, owner, FABRICATED_COIN_BALANCE)],
480 next_cursor: None,
481 })
482 }
483
484 async fn reference_gas_price(
485 &self,
486 _epoch: impl Into<Option<u64>>,
487 ) -> Result<Option<u64>, Self::Error> {
488 Ok(Some(1000))
489 }
490 }
491
492 impl TransactionBuilderSimulationClient for TestClient {
493 type DryRunResult = ();
494
495 async fn estimate_transaction_budget(
496 &self,
497 _transaction: &Transaction,
498 ) -> Result<Option<u64>, Self::Error> {
499 Ok(Some(50_000_000))
500 }
501
502 async fn dry_run_transaction(
503 &self,
504 _transaction: &Transaction,
505 _skip_checks: bool,
506 ) -> Result<Self::DryRunResult, Self::Error> {
507 Ok(())
508 }
509 }
510
511 impl TransactionBuilderExecutionClient for TestClient {
512 async fn execute_transaction(
513 &self,
514 _signatures: &[UserSignature],
515 _transaction: &Transaction,
516 _wait_for: impl Into<Option<WaitForTransaction>>,
517 ) -> Result<TransactionEffects, Self::Error> {
518 Err(TestClientError(
519 "TestClient cannot execute transactions".to_string(),
520 ))
521 }
522
523 async fn wait_for_transaction(
524 &self,
525 _digest: TransactionDigest,
526 _wait_for: WaitForTransaction,
527 ) -> Result<(), Self::Error> {
528 Ok(())
529 }
530
531 async fn transaction_effects(
532 &self,
533 _digest: TransactionDigest,
534 ) -> Result<Option<TransactionEffects>, Self::Error> {
535 Ok(None)
536 }
537 }
538
539 #[derive(Clone, Default)]
542 pub struct RecordingClient {
543 pub batches: std::sync::Arc<std::sync::Mutex<Vec<Vec<ObjectId>>>>,
545 pub singles: std::sync::Arc<std::sync::Mutex<Vec<ObjectId>>>,
547 pub missing: Vec<ObjectId>,
549 }
550
551 impl RecordingClient {
552 pub fn batches(&self) -> Vec<Vec<ObjectId>> {
554 self.batches.lock().unwrap().clone()
555 }
556
557 pub fn singles(&self) -> Vec<ObjectId> {
559 self.singles.lock().unwrap().clone()
560 }
561 }
562
563 impl TransactionBuilderClientBase for RecordingClient {
564 type Error = crate::TestClientError;
565 }
566
567 impl TransactionBuilderLedgerClient for RecordingClient {
568 async fn object(
569 &self,
570 object_id: ObjectId,
571 version: impl Into<Option<Version>>,
572 ) -> Result<Option<Object>, Self::Error> {
573 self.singles.lock().unwrap().push(object_id);
574 if self.missing.contains(&object_id) {
575 return Ok(None);
576 }
577 crate::TestClient.object(object_id, version).await
578 }
579
580 async fn objects_by_id(
581 &self,
582 object_ids: &[(ObjectId, Option<Version>)],
583 ) -> Result<Vec<Option<Object>>, Self::Error> {
584 self.batches
585 .lock()
586 .unwrap()
587 .push(object_ids.iter().map(|(id, _)| *id).collect());
588 let mut objects = Vec::with_capacity(object_ids.len());
589 for (object_id, _) in object_ids {
590 objects.push(if self.missing.contains(object_id) {
591 None
592 } else {
593 crate::TestClient.object(*object_id, None).await?
594 });
595 }
596 Ok(objects)
597 }
598
599 async fn objects(
600 &self,
601 struct_tag: Option<StructTag>,
602 owner: Address,
603 cursor: Option<Vec<u8>>,
604 limit: Option<usize>,
605 ) -> Result<crate::ObjectsPage, Self::Error> {
606 crate::TestClient
607 .objects(struct_tag, owner, cursor, limit)
608 .await
609 }
610
611 async fn reference_gas_price(
612 &self,
613 epoch: impl Into<Option<u64>>,
614 ) -> Result<Option<u64>, Self::Error> {
615 crate::TestClient.reference_gas_price(epoch).await
616 }
617 }
618
619 impl TransactionBuilderSimulationClient for RecordingClient {
620 type DryRunResult = ();
621
622 async fn estimate_transaction_budget(
623 &self,
624 transaction: &Transaction,
625 ) -> Result<Option<u64>, Self::Error> {
626 crate::TestClient
627 .estimate_transaction_budget(transaction)
628 .await
629 }
630
631 async fn dry_run_transaction(
632 &self,
633 transaction: &Transaction,
634 skip_checks: bool,
635 ) -> Result<Self::DryRunResult, Self::Error> {
636 crate::TestClient
637 .dry_run_transaction(transaction, skip_checks)
638 .await
639 }
640 }
641
642 impl TransactionBuilderExecutionClient for RecordingClient {
643 async fn execute_transaction(
644 &self,
645 signatures: &[iota_types::UserSignature],
646 transaction: &Transaction,
647 wait_for: impl Into<Option<WaitForTransaction>>,
648 ) -> Result<TransactionEffects, Self::Error> {
649 crate::TestClient
650 .execute_transaction(signatures, transaction, wait_for)
651 .await
652 }
653
654 async fn wait_for_transaction(
655 &self,
656 digest: iota_types::TransactionDigest,
657 wait_for: WaitForTransaction,
658 ) -> Result<(), Self::Error> {
659 crate::TestClient
660 .wait_for_transaction(digest, wait_for)
661 .await
662 }
663
664 async fn transaction_effects(
665 &self,
666 digest: iota_types::TransactionDigest,
667 ) -> Result<Option<TransactionEffects>, Self::Error> {
668 crate::TestClient.transaction_effects(digest).await
669 }
670 }
671}