use std::fmt;
use std::future::Future;
use std::pin::Pin;
use std::sync::Arc;
use thruput_json_rpc_types::responses::{ReceiptExecution, TransactionReceipt};
use thruput_xdr::contract::ContractEntrypointID;
use thruput_xdr::types::{ContractAddress, SHA256Hash};
use crate::account::{
Account, DeployOptions, Deployed, SubmittedDeploy, verify_deployment_receipt,
};
use crate::artifact::{
ArtifactEntry, ArtifactKind, PendingRegistration, RegistrationOwner, Registry,
};
use xdrs::{ReadXDR, WriteXDR};
use crate::caller::Caller;
use crate::clock::Clock;
use crate::error::{Result, TestkitError};
use crate::mailbox::{Envelope, Mailbox, Message};
use crate::network::{Network, SubmittedTransaction};
use crate::rng::AgentRng;
enum RegistrationResolution {
NeedsRegistration,
Registered,
Recovered(Box<SubmittedDeploy>),
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct AgentId {
index: usize,
name: Arc<str>,
}
impl AgentId {
pub(crate) fn new(index: usize, name: Arc<str>) -> Self {
Self { index, name }
}
pub fn index(&self) -> usize {
self.index
}
pub fn name(&self) -> &str {
&self.name
}
}
type AgentFuture = Pin<Box<dyn Future<Output = Result<()>> + Send>>;
type AgentBody = Box<dyn FnOnce(AgentContext) -> AgentFuture + Send>;
pub struct Agent {
name: Arc<str>,
body: AgentBody,
}
impl Agent {
pub fn new<F, Fut>(name: impl Into<String>, body: F) -> Self
where
F: FnOnce(AgentContext) -> Fut + Send + 'static,
Fut: Future<Output = Result<()>> + Send + 'static,
{
let name: Arc<str> = Arc::from(name.into());
Self {
name,
body: Box::new(move |ctx| Box::pin(body(ctx))),
}
}
pub fn name(&self) -> &str {
&self.name
}
pub(crate) fn name_arc(&self) -> Arc<str> {
Arc::clone(&self.name)
}
pub(crate) fn into_body(self) -> AgentBody {
self.body
}
}
impl fmt::Debug for Agent {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Agent")
.field("name", &self.name)
.finish_non_exhaustive()
}
}
#[derive(Debug)]
pub struct AgentContext {
id: AgentId,
caller: Caller,
clock: Clock,
rng: AgentRng,
mailbox: Mailbox,
artifacts: Arc<Registry>,
}
impl AgentContext {
pub(crate) fn new(
id: AgentId,
network: Network,
clock: Clock,
rng: AgentRng,
mailbox: Mailbox,
account: Account,
artifacts: Arc<Registry>,
) -> Self {
Self {
id,
caller: Caller::new(account, network),
clock,
rng,
mailbox,
artifacts,
}
}
pub fn address(&self) -> ContractAddress {
self.caller.account.address()
}
pub fn account_index(&self) -> u32 {
self.caller.account.index()
}
pub async fn deploy_artifact(
&mut self,
name: &str,
options: DeployOptions<'_>,
) -> Result<Deployed> {
loop {
match self.submit_deploy_artifact(name, options).await {
Ok(submitted) => return submitted.await_receipt().await,
Err(TestkitError::ArtifactRegistrationPending { .. }) => {
let entry = self.artifacts.get(name)?;
if let Some(deployed) = await_pending_registration(
&self.caller.network,
&entry,
self.caller.account.registration_owner(),
)
.await?
{
return Ok(deployed);
}
}
Err(error) => return Err(error),
}
}
}
pub async fn submit_deploy_artifact(
&mut self,
name: &str,
options: DeployOptions<'_>,
) -> Result<SubmittedDeploy> {
let entry = self.artifacts.get(name)?;
let mut pending = entry.registration_guard().await;
match reconcile_pending_registration(
&self.caller.network,
name,
&entry,
&mut pending,
self.caller.account.registration_owner(),
options,
)
.await?
{
RegistrationResolution::Registered => {
drop(pending);
return self
.caller
.account
.submit_deploy_entry(&self.caller.network, &entry, false, options)
.await;
}
RegistrationResolution::Recovered(submitted) => return Ok(*submitted),
RegistrationResolution::NeedsRegistration => {}
}
let prepared = self.caller.account.prepare_deploy_entry(
&self.caller.network,
&entry,
true,
options,
)?;
*pending = Some(prepared.pending_registration());
drop(pending);
let submitted = prepared.submit().await?;
Ok(submitted.with_registration(entry))
}
pub async fn register_artifact(
&mut self,
name: &str,
options: DeployOptions<'_>,
) -> Result<Deployed> {
loop {
match self.submit_register_artifact(name, options).await {
Ok(submitted) => return submitted.await_receipt().await,
Err(TestkitError::ArtifactRegistrationPending { .. }) => {
let entry = self.artifacts.get(name)?;
if let Some(deployed) = await_pending_registration(
&self.caller.network,
&entry,
self.caller.account.registration_owner(),
)
.await?
{
return Ok(deployed);
}
}
Err(error) => return Err(error),
}
}
}
pub async fn submit_register_artifact(
&mut self,
name: &str,
options: DeployOptions<'_>,
) -> Result<SubmittedDeploy> {
let entry = self.artifacts.get(name)?;
let mut pending = entry.registration_guard().await;
match reconcile_pending_registration(
&self.caller.network,
name,
&entry,
&mut pending,
self.caller.account.registration_owner(),
options,
)
.await?
{
RegistrationResolution::Registered => {
drop(pending);
return self
.caller
.account
.submit_deploy_entry(&self.caller.network, &entry, true, options)
.await;
}
RegistrationResolution::Recovered(submitted) => return Ok(*submitted),
RegistrationResolution::NeedsRegistration => {}
}
let prepared = self.caller.account.prepare_deploy_entry(
&self.caller.network,
&entry,
true,
options,
)?;
*pending = Some(prepared.pending_registration());
drop(pending);
let submitted = prepared.submit().await?;
Ok(submitted.with_registration(entry))
}
pub async fn deploy_registered(
&self,
name: &str,
options: DeployOptions<'_>,
) -> Result<Deployed> {
self.submit_deploy_registered(name, options)
.await?
.await_receipt()
.await
}
pub async fn submit_deploy_registered(
&self,
name: &str,
options: DeployOptions<'_>,
) -> Result<SubmittedDeploy> {
let entry = self.artifacts.get(name)?;
self.caller
.account
.submit_deploy_entry(&self.caller.network, &entry, false, options)
.await
}
pub fn contract_hash(&self, name: &str) -> Result<SHA256Hash> {
Ok(self.artifacts.get(name)?.hash())
}
pub fn is_registered(&self, name: &str) -> Result<bool> {
Ok(self.artifacts.get(name)?.registered())
}
pub async fn deploy(
&self,
artifact: &[u8],
kind: ArtifactKind,
options: DeployOptions<'_>,
) -> Result<Deployed> {
self.submit_deploy(artifact, kind, options)
.await?
.await_receipt()
.await
}
pub async fn submit_deploy(
&self,
artifact: &[u8],
_kind: ArtifactKind,
options: DeployOptions<'_>,
) -> Result<SubmittedDeploy> {
self.caller
.account
.submit_deploy(&self.caller.network, artifact, options)
.await
}
pub async fn submit_fund(&self, amount: u128) -> Result<SubmittedTransaction> {
self.caller
.network
.submit_fund(self.address(), amount)
.await
}
pub async fn fund(&self, amount: u128) -> Result<TransactionReceipt> {
let receipt = self.submit_fund(amount).await?.await_receipt().await?;
if !matches!(receipt.execution, ReceiptExecution::Return { .. }) {
return Err(TestkitError::Reverted {
method: "fund".to_owned(),
status: format!("{:?}", receipt.execution),
});
}
Ok(receipt)
}
pub async fn call(
&self,
target: ContractAddress,
entrypoint: ContractEntrypointID,
calldata: &[u8],
) -> Result<TransactionReceipt> {
self.caller.call(target, entrypoint, calldata).await
}
pub async fn submit_call(
&self,
target: ContractAddress,
entrypoint: ContractEntrypointID,
calldata: &[u8],
) -> Result<SubmittedTransaction> {
self.caller.submit_call(target, entrypoint, calldata).await
}
pub fn id(&self) -> &AgentId {
&self.id
}
pub fn name(&self) -> &str {
self.id.name()
}
pub fn network(&self) -> &Network {
&self.caller.network
}
pub fn caller(&self) -> &Caller {
&self.caller
}
pub fn clock(&self) -> &Clock {
&self.clock
}
pub fn rng(&mut self) -> &mut AgentRng {
&mut self.rng
}
pub fn send<T>(&self, to: &str, msg: &T) -> Result<()>
where
T: WriteXDR + ?Sized,
{
let bytes = xdrs::cxs::encode(msg).map_err(TestkitError::encode)?;
self.mailbox.send_to(to, bytes)
}
pub async fn recv<T>(&mut self) -> Option<Result<Message<T>>>
where
T: for<'r> ReadXDR<'r>,
{
self.mailbox.recv().await.map(Envelope::decode)
}
pub fn try_recv<T>(&mut self) -> Option<Result<Message<T>>>
where
T: for<'r> ReadXDR<'r>,
{
self.mailbox.try_recv().map(Envelope::decode)
}
pub async fn recv_from<T>(&mut self, from: &str) -> Option<Result<Message<T>>>
where
T: for<'r> ReadXDR<'r>,
{
self.mailbox.recv_from(from).await.map(Envelope::decode)
}
pub fn try_recv_from<T>(&mut self, from: &str) -> Option<Result<Message<T>>>
where
T: for<'r> ReadXDR<'r>,
{
self.mailbox.try_recv_from(from).map(Envelope::decode)
}
pub async fn recv_from_any<T>(&mut self, from: &[&str]) -> Option<Result<Message<T>>>
where
T: for<'r> ReadXDR<'r>,
{
self.mailbox.recv_from_any(from).await.map(Envelope::decode)
}
pub fn try_recv_from_any<T>(&mut self, from: &[&str]) -> Option<Result<Message<T>>>
where
T: for<'r> ReadXDR<'r>,
{
self.mailbox.try_recv_from_any(from).map(Envelope::decode)
}
}
impl AsRef<Caller> for AgentContext {
fn as_ref(&self) -> &Caller {
&self.caller
}
}
async fn reconcile_pending_registration(
network: &Network,
name: &str,
entry: &Arc<ArtifactEntry>,
pending: &mut Option<PendingRegistration>,
caller: RegistrationOwner,
options: DeployOptions<'_>,
) -> Result<RegistrationResolution> {
let Some(attempt) = pending.clone() else {
return if entry.registered() {
Ok(RegistrationResolution::Registered)
} else {
Ok(RegistrationResolution::NeedsRegistration)
};
};
if attempt.owner == caller && attempt.effects != options.effects()? {
return Err(TestkitError::DeploymentRequestMismatch {
name: name.to_owned(),
});
}
if entry.registered() && attempt.owner != caller {
return Ok(RegistrationResolution::Registered);
}
let tx_hash = attempt.tx_hash;
let Some(receipt) = network.receipt(tx_hash).await? else {
return Err(TestkitError::ArtifactRegistrationPending {
name: name.to_owned(),
tx_hash,
signed_transaction: attempt.tx_hex.clone(),
});
};
verify_deployment_receipt(tx_hash, attempt.address, &attempt.tx_hex, &receipt)?;
if matches!(receipt.execution, ReceiptExecution::Return { .. }) {
entry.mark_registered();
if attempt.owner == caller {
*pending = None;
Ok(RegistrationResolution::Recovered(Box::new(
SubmittedDeploy::recovered(network.clone(), &attempt, receipt),
)))
} else {
Ok(RegistrationResolution::Registered)
}
} else {
*pending = None;
Err(TestkitError::Deploy {
detail: format!("{:?}", receipt.execution),
})
}
}
async fn await_pending_registration(
network: &Network,
entry: &ArtifactEntry,
caller: RegistrationOwner,
) -> Result<Option<Deployed>> {
let attempt = {
let pending = entry.registration_guard().await;
pending.clone()
};
let Some(attempt) = attempt else {
return Ok(None);
};
let tx_hash = attempt.tx_hash;
let receipt = network.await_receipt(tx_hash).await?;
finish_registration_receipt(entry, &attempt, receipt, caller).await
}
async fn finish_registration_receipt(
entry: &ArtifactEntry,
attempt: &PendingRegistration,
receipt: TransactionReceipt,
caller: RegistrationOwner,
) -> Result<Option<Deployed>> {
verify_deployment_receipt(attempt.tx_hash, attempt.address, &attempt.tx_hex, &receipt)?;
let succeeded = matches!(receipt.execution, ReceiptExecution::Return { .. });
if !succeeded || attempt.owner == caller {
entry.finish_registration(attempt.tx_hash, succeeded).await;
} else {
entry.mark_registered();
}
if !succeeded {
return Err(TestkitError::Deploy {
detail: format!("{:?}", receipt.execution),
});
}
Ok((attempt.owner == caller).then_some(Deployed {
address: attempt.address,
tx_hash: attempt.tx_hash,
receipt,
registered: true,
}))
}