1use std::fmt;
4use std::future::Future;
5use std::pin::Pin;
6use std::sync::Arc;
7
8use thruput_json_rpc_types::responses::{ReceiptExecution, TransactionReceipt};
9use thruput_xdr::contract::ContractEntrypointID;
10use thruput_xdr::types::{ContractAddress, SHA256Hash};
11
12use crate::account::{
13 Account, DeployOptions, Deployed, SubmittedDeploy, verify_deployment_receipt,
14};
15use crate::artifact::{
16 ArtifactEntry, ArtifactKind, PendingRegistration, RegistrationOwner, Registry,
17};
18use xdrs::{ReadXDR, WriteXDR};
19
20use crate::caller::Caller;
21use crate::clock::Clock;
22use crate::error::{Result, TestkitError};
23use crate::mailbox::{Envelope, Mailbox, Message};
24use crate::network::{Network, SubmittedTransaction};
25use crate::rng::AgentRng;
26
27enum RegistrationResolution {
28 NeedsRegistration,
29 Registered,
30 Recovered(Box<SubmittedDeploy>),
31}
32
33#[derive(Debug, Clone, PartialEq, Eq, Hash)]
35pub struct AgentId {
36 index: usize,
37 name: Arc<str>,
38}
39
40impl AgentId {
41 pub(crate) fn new(index: usize, name: Arc<str>) -> Self {
42 Self { index, name }
43 }
44
45 pub fn index(&self) -> usize {
47 self.index
48 }
49
50 pub fn name(&self) -> &str {
52 &self.name
53 }
54}
55
56type AgentFuture = Pin<Box<dyn Future<Output = Result<()>> + Send>>;
57type AgentBody = Box<dyn FnOnce(AgentContext) -> AgentFuture + Send>;
58
59pub struct Agent {
61 name: Arc<str>,
62 body: AgentBody,
63}
64
65impl Agent {
66 pub fn new<F, Fut>(name: impl Into<String>, body: F) -> Self
71 where
72 F: FnOnce(AgentContext) -> Fut + Send + 'static,
73 Fut: Future<Output = Result<()>> + Send + 'static,
74 {
75 let name: Arc<str> = Arc::from(name.into());
76 Self {
77 name,
78 body: Box::new(move |ctx| Box::pin(body(ctx))),
79 }
80 }
81
82 pub fn name(&self) -> &str {
84 &self.name
85 }
86
87 pub(crate) fn name_arc(&self) -> Arc<str> {
88 Arc::clone(&self.name)
89 }
90
91 pub(crate) fn into_body(self) -> AgentBody {
92 self.body
93 }
94}
95
96impl fmt::Debug for Agent {
97 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
98 f.debug_struct("Agent")
99 .field("name", &self.name)
100 .finish_non_exhaustive()
101 }
102}
103
104#[derive(Debug)]
118pub struct AgentContext {
119 id: AgentId,
120 caller: Caller,
121 clock: Clock,
122 rng: AgentRng,
123 mailbox: Mailbox,
124 artifacts: Arc<Registry>,
125}
126
127impl AgentContext {
128 pub(crate) fn new(
129 id: AgentId,
130 network: Network,
131 clock: Clock,
132 rng: AgentRng,
133 mailbox: Mailbox,
134 account: Account,
135 artifacts: Arc<Registry>,
136 ) -> Self {
137 Self {
138 id,
139 caller: Caller::new(account, network),
140 clock,
141 rng,
142 mailbox,
143 artifacts,
144 }
145 }
146
147 pub fn address(&self) -> ContractAddress {
149 self.caller.account.address()
150 }
151
152 pub fn account_index(&self) -> u32 {
154 self.caller.account.index()
155 }
156
157 pub async fn deploy_artifact(
174 &mut self,
175 name: &str,
176 options: DeployOptions<'_>,
177 ) -> Result<Deployed> {
178 loop {
179 match self.submit_deploy_artifact(name, options).await {
180 Ok(submitted) => return submitted.await_receipt().await,
181 Err(TestkitError::ArtifactRegistrationPending { .. }) => {
182 let entry = self.artifacts.get(name)?;
183 if let Some(deployed) = await_pending_registration(
184 &self.caller.network,
185 &entry,
186 self.caller.account.registration_owner(),
187 )
188 .await?
189 {
190 return Ok(deployed);
191 }
192 }
193 Err(error) => return Err(error),
194 }
195 }
196 }
197
198 pub async fn submit_deploy_artifact(
207 &mut self,
208 name: &str,
209 options: DeployOptions<'_>,
210 ) -> Result<SubmittedDeploy> {
211 let entry = self.artifacts.get(name)?;
212 let mut pending = entry.registration_guard().await;
213 match reconcile_pending_registration(
214 &self.caller.network,
215 name,
216 &entry,
217 &mut pending,
218 self.caller.account.registration_owner(),
219 options,
220 )
221 .await?
222 {
223 RegistrationResolution::Registered => {
224 drop(pending);
225 return self
226 .caller
227 .account
228 .submit_deploy_entry(&self.caller.network, &entry, false, options)
229 .await;
230 }
231 RegistrationResolution::Recovered(submitted) => return Ok(*submitted),
232 RegistrationResolution::NeedsRegistration => {}
233 }
234 let prepared = self.caller.account.prepare_deploy_entry(
235 &self.caller.network,
236 &entry,
237 true,
238 options,
239 )?;
240 *pending = Some(prepared.pending_registration());
241 drop(pending);
242 let submitted = prepared.submit().await?;
243 Ok(submitted.with_registration(entry))
244 }
245
246 pub async fn register_artifact(
250 &mut self,
251 name: &str,
252 options: DeployOptions<'_>,
253 ) -> Result<Deployed> {
254 loop {
255 match self.submit_register_artifact(name, options).await {
256 Ok(submitted) => return submitted.await_receipt().await,
257 Err(TestkitError::ArtifactRegistrationPending { .. }) => {
258 let entry = self.artifacts.get(name)?;
259 if let Some(deployed) = await_pending_registration(
260 &self.caller.network,
261 &entry,
262 self.caller.account.registration_owner(),
263 )
264 .await?
265 {
266 return Ok(deployed);
267 }
268 }
269 Err(error) => return Err(error),
270 }
271 }
272 }
273
274 pub async fn submit_register_artifact(
279 &mut self,
280 name: &str,
281 options: DeployOptions<'_>,
282 ) -> Result<SubmittedDeploy> {
283 let entry = self.artifacts.get(name)?;
284 let mut pending = entry.registration_guard().await;
285 match reconcile_pending_registration(
286 &self.caller.network,
287 name,
288 &entry,
289 &mut pending,
290 self.caller.account.registration_owner(),
291 options,
292 )
293 .await?
294 {
295 RegistrationResolution::Registered => {
296 drop(pending);
297 return self
298 .caller
299 .account
300 .submit_deploy_entry(&self.caller.network, &entry, true, options)
301 .await;
302 }
303 RegistrationResolution::Recovered(submitted) => return Ok(*submitted),
304 RegistrationResolution::NeedsRegistration => {}
305 }
306 let prepared = self.caller.account.prepare_deploy_entry(
307 &self.caller.network,
308 &entry,
309 true,
310 options,
311 )?;
312 *pending = Some(prepared.pending_registration());
313 drop(pending);
314 let submitted = prepared.submit().await?;
315 Ok(submitted.with_registration(entry))
316 }
317
318 pub async fn deploy_registered(
323 &self,
324 name: &str,
325 options: DeployOptions<'_>,
326 ) -> Result<Deployed> {
327 self.submit_deploy_registered(name, options)
328 .await?
329 .await_receipt()
330 .await
331 }
332
333 pub async fn submit_deploy_registered(
335 &self,
336 name: &str,
337 options: DeployOptions<'_>,
338 ) -> Result<SubmittedDeploy> {
339 let entry = self.artifacts.get(name)?;
340 self.caller
341 .account
342 .submit_deploy_entry(&self.caller.network, &entry, false, options)
343 .await
344 }
345
346 pub fn contract_hash(&self, name: &str) -> Result<SHA256Hash> {
348 Ok(self.artifacts.get(name)?.hash())
349 }
350
351 pub fn is_registered(&self, name: &str) -> Result<bool> {
353 Ok(self.artifacts.get(name)?.registered())
354 }
355
356 pub async fn deploy(
360 &self,
361 artifact: &[u8],
362 kind: ArtifactKind,
363 options: DeployOptions<'_>,
364 ) -> Result<Deployed> {
365 self.submit_deploy(artifact, kind, options)
366 .await?
367 .await_receipt()
368 .await
369 }
370
371 pub async fn submit_deploy(
373 &self,
374 artifact: &[u8],
375 _kind: ArtifactKind,
376 options: DeployOptions<'_>,
377 ) -> Result<SubmittedDeploy> {
378 self.caller
379 .account
380 .submit_deploy(&self.caller.network, artifact, options)
381 .await
382 }
383
384 pub async fn submit_fund(&self, amount: u128) -> Result<SubmittedTransaction> {
388 self.caller
389 .network
390 .submit_fund(self.address(), amount)
391 .await
392 }
393
394 pub async fn fund(&self, amount: u128) -> Result<TransactionReceipt> {
397 let receipt = self.submit_fund(amount).await?.await_receipt().await?;
398 if !matches!(receipt.execution, ReceiptExecution::Return { .. }) {
399 return Err(TestkitError::Reverted {
400 method: "fund".to_owned(),
401 status: format!("{:?}", receipt.execution),
402 });
403 }
404 Ok(receipt)
405 }
406
407 pub async fn call(
410 &self,
411 target: ContractAddress,
412 entrypoint: ContractEntrypointID,
413 calldata: &[u8],
414 ) -> Result<TransactionReceipt> {
415 self.caller.call(target, entrypoint, calldata).await
416 }
417
418 pub async fn submit_call(
420 &self,
421 target: ContractAddress,
422 entrypoint: ContractEntrypointID,
423 calldata: &[u8],
424 ) -> Result<SubmittedTransaction> {
425 self.caller.submit_call(target, entrypoint, calldata).await
426 }
427
428 pub fn id(&self) -> &AgentId {
430 &self.id
431 }
432
433 pub fn name(&self) -> &str {
435 self.id.name()
436 }
437
438 pub fn network(&self) -> &Network {
440 &self.caller.network
441 }
442
443 pub fn caller(&self) -> &Caller {
446 &self.caller
447 }
448
449 pub fn clock(&self) -> &Clock {
451 &self.clock
452 }
453
454 pub fn rng(&mut self) -> &mut AgentRng {
456 &mut self.rng
457 }
458
459 pub fn send<T>(&self, to: &str, msg: &T) -> Result<()>
461 where
462 T: WriteXDR + ?Sized,
463 {
464 let bytes = xdrs::cxs::encode(msg).map_err(TestkitError::encode)?;
465 self.mailbox.send_to(to, bytes)
466 }
467
468 pub async fn recv<T>(&mut self) -> Option<Result<Message<T>>>
473 where
474 T: for<'r> ReadXDR<'r>,
475 {
476 self.mailbox.recv().await.map(Envelope::decode)
477 }
478
479 pub fn try_recv<T>(&mut self) -> Option<Result<Message<T>>>
482 where
483 T: for<'r> ReadXDR<'r>,
484 {
485 self.mailbox.try_recv().map(Envelope::decode)
486 }
487
488 pub async fn recv_from<T>(&mut self, from: &str) -> Option<Result<Message<T>>>
496 where
497 T: for<'r> ReadXDR<'r>,
498 {
499 self.mailbox.recv_from(from).await.map(Envelope::decode)
500 }
501
502 pub fn try_recv_from<T>(&mut self, from: &str) -> Option<Result<Message<T>>>
505 where
506 T: for<'r> ReadXDR<'r>,
507 {
508 self.mailbox.try_recv_from(from).map(Envelope::decode)
509 }
510
511 pub async fn recv_from_any<T>(&mut self, from: &[&str]) -> Option<Result<Message<T>>>
517 where
518 T: for<'r> ReadXDR<'r>,
519 {
520 self.mailbox.recv_from_any(from).await.map(Envelope::decode)
521 }
522
523 pub fn try_recv_from_any<T>(&mut self, from: &[&str]) -> Option<Result<Message<T>>>
526 where
527 T: for<'r> ReadXDR<'r>,
528 {
529 self.mailbox.try_recv_from_any(from).map(Envelope::decode)
530 }
531}
532
533impl AsRef<Caller> for AgentContext {
534 fn as_ref(&self) -> &Caller {
535 &self.caller
536 }
537}
538
539async fn reconcile_pending_registration(
540 network: &Network,
541 name: &str,
542 entry: &Arc<ArtifactEntry>,
543 pending: &mut Option<PendingRegistration>,
544 caller: RegistrationOwner,
545 options: DeployOptions<'_>,
546) -> Result<RegistrationResolution> {
547 let Some(attempt) = pending.clone() else {
548 return if entry.registered() {
549 Ok(RegistrationResolution::Registered)
550 } else {
551 Ok(RegistrationResolution::NeedsRegistration)
552 };
553 };
554 if attempt.owner == caller && attempt.effects != options.effects()? {
555 return Err(TestkitError::DeploymentRequestMismatch {
556 name: name.to_owned(),
557 });
558 }
559 if entry.registered() && attempt.owner != caller {
560 return Ok(RegistrationResolution::Registered);
561 }
562 let tx_hash = attempt.tx_hash;
563 let Some(receipt) = network.receipt(tx_hash).await? else {
564 return Err(TestkitError::ArtifactRegistrationPending {
566 name: name.to_owned(),
567 tx_hash,
568 signed_transaction: attempt.tx_hex.clone(),
569 });
570 };
571 verify_deployment_receipt(tx_hash, attempt.address, &attempt.tx_hex, &receipt)?;
572 if matches!(receipt.execution, ReceiptExecution::Return { .. }) {
573 entry.mark_registered();
574 if attempt.owner == caller {
575 *pending = None;
576 Ok(RegistrationResolution::Recovered(Box::new(
577 SubmittedDeploy::recovered(network.clone(), &attempt, receipt),
578 )))
579 } else {
580 Ok(RegistrationResolution::Registered)
581 }
582 } else {
583 *pending = None;
584 Err(TestkitError::Deploy {
585 detail: format!("{:?}", receipt.execution),
586 })
587 }
588}
589
590async fn await_pending_registration(
591 network: &Network,
592 entry: &ArtifactEntry,
593 caller: RegistrationOwner,
594) -> Result<Option<Deployed>> {
595 let attempt = {
596 let pending = entry.registration_guard().await;
597 pending.clone()
598 };
599 let Some(attempt) = attempt else {
600 return Ok(None);
601 };
602 let tx_hash = attempt.tx_hash;
603 let receipt = network.await_receipt(tx_hash).await?;
604 finish_registration_receipt(entry, &attempt, receipt, caller).await
605}
606
607async fn finish_registration_receipt(
608 entry: &ArtifactEntry,
609 attempt: &PendingRegistration,
610 receipt: TransactionReceipt,
611 caller: RegistrationOwner,
612) -> Result<Option<Deployed>> {
613 verify_deployment_receipt(attempt.tx_hash, attempt.address, &attempt.tx_hex, &receipt)?;
614 let succeeded = matches!(receipt.execution, ReceiptExecution::Return { .. });
615 if !succeeded || attempt.owner == caller {
616 entry.finish_registration(attempt.tx_hash, succeeded).await;
617 } else {
618 entry.mark_registered();
619 }
620 if !succeeded {
621 return Err(TestkitError::Deploy {
622 detail: format!("{:?}", receipt.execution),
623 });
624 }
625 Ok((attempt.owner == caller).then_some(Deployed {
626 address: attempt.address,
627 tx_hash: attempt.tx_hash,
628 receipt,
629 registered: true,
630 }))
631}