use std::{collections::HashMap, error::Error};
use anyhow::{anyhow, bail, Result};
use contract_transcode::Value;
use futures::{channel::mpsc::UnboundedSender, StreamExt};
use subxt::events::EventDetails;
use crate::{
api::contracts::events::ContractEmitted, connections::TxInfo, contract::ContractInstance,
utility::BlocksApi, AccountId, Connection,
};
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct ContractEvent {
pub contract: AccountId,
pub name: Option<String>,
pub data: HashMap<String, Value>,
}
pub async fn get_contract_events(
conn: &Connection,
contract: &ContractInstance,
tx_info: TxInfo,
) -> Result<Vec<ContractEvent>> {
let events = conn.get_tx_events(tx_info).await?;
translate_events(events.iter(), &[contract])
.into_iter()
.collect()
}
pub async fn listen_contract_events(
conn: &Connection,
contracts: &[&ContractInstance],
sender: UnboundedSender<Result<ContractEvent>>,
) -> Result<()> {
let mut block_subscription = conn.as_client().blocks().subscribe_finalized().await?;
while let Some(block) = block_subscription.next().await {
if sender.is_closed() {
break;
}
let events = block?.events().await?;
for event in translate_events(events.iter(), contracts) {
sender.unbounded_send(event)?;
}
}
Ok(())
}
fn translate_events<
Err: Error + Into<anyhow::Error> + Send + Sync + 'static,
E: Iterator<Item = Result<EventDetails, Err>>,
>(
events: E,
contracts: &[&ContractInstance],
) -> Vec<Result<ContractEvent>> {
events
.filter_map(|maybe_event| {
maybe_event
.map(|e| e.as_event::<ContractEmitted>().ok().flatten())
.transpose()
})
.map(|maybe_event| match maybe_event {
Ok(e) => translate_event(&e, contracts),
Err(e) => Err(anyhow::Error::from(e)),
})
.collect()
}
fn translate_event(
event: &ContractEmitted,
contracts: &[&ContractInstance],
) -> Result<ContractEvent> {
let matching_contract = contracts
.iter()
.find(|contract| contract.address() == &event.contract)
.ok_or_else(|| anyhow!("The event wasn't emitted by any of the provided contracts"))?;
let data = zero_prefixed(&event.data);
let data = matching_contract
.transcoder
.decode_contract_event(&mut data.as_slice())?;
build_event(matching_contract.address.clone(), data)
}
fn zero_prefixed(data: &[u8]) -> Vec<u8> {
let mut result = vec![0];
result.extend_from_slice(data);
result
}
fn build_event(address: AccountId, event_data: Value) -> Result<ContractEvent> {
match event_data {
Value::Map(map) => Ok(ContractEvent {
contract: address,
name: map.ident(),
data: map
.iter()
.map(|(key, value)| (key.to_string(), value.clone()))
.collect(),
}),
_ => bail!("Contract event data is not a map"),
}
}