use std::rc::Rc;
use contract_transcode::{ContractMessageTranscoder, Value};
use parity_scale_codec::{Decode, Encode};
use crate::{
errors::MessageResult,
runtime::{minimal::RuntimeEvent, AccountIdFor, MinimalRuntime},
session::{error::SessionError, BalanceOf},
EventRecordOf, Sandbox, SandboxConfig,
};
type ContractInstantiateResult<R> =
pallet_contracts::ContractInstantiateResult<AccountIdFor<R>, BalanceOf<R>, EventRecordOf<R>>;
type ContractExecResult<R> = pallet_contracts::ContractExecResult<BalanceOf<R>, EventRecordOf<R>>;
pub struct Record<Config: SandboxConfig>
where
Config::Runtime: pallet_contracts::Config,
{
deploy_results: Vec<ContractInstantiateResult<Config::Runtime>>,
deploy_returns: Vec<AccountIdFor<Config::Runtime>>,
call_results: Vec<ContractExecResult<Config::Runtime>>,
call_returns: Vec<Vec<u8>>,
event_batches: Vec<EventBatch<Config::Runtime>>,
block_events_so_far: Option<usize>,
}
impl<Config: SandboxConfig> Default for Record<Config>
where
Config::Runtime: pallet_contracts::Config,
{
fn default() -> Self {
Self {
deploy_results: Vec::new(),
deploy_returns: Vec::new(),
call_results: Vec::new(),
call_returns: Vec::new(),
event_batches: Vec::new(),
block_events_so_far: None,
}
}
}
impl<Config: SandboxConfig> Record<Config>
where
Config::Runtime: pallet_contracts::Config,
{
pub(super) fn push_deploy_result(
&mut self,
result: ContractInstantiateResult<Config::Runtime>,
) {
self.deploy_results.push(result);
}
pub(super) fn push_deploy_return(&mut self, return_value: AccountIdFor<Config::Runtime>) {
self.deploy_returns.push(return_value);
}
pub(super) fn push_call_result(&mut self, result: ContractExecResult<Config::Runtime>) {
self.call_results.push(result);
}
pub(super) fn push_call_return(&mut self, return_value: Vec<u8>) {
self.call_returns.push(return_value);
}
pub(super) fn start_recording_events(&mut self, sandbox: &mut Sandbox<Config>) {
assert!(
self.block_events_so_far.is_none(),
"Already recording events"
);
self.block_events_so_far = Some(sandbox.events().len());
}
pub(super) fn stop_recording_events(&mut self, sandbox: &mut Sandbox<Config>) {
let start = self
.block_events_so_far
.take()
.expect("Not recording events");
let end = sandbox.events().len();
let events = sandbox.events()[start..end].to_vec();
self.event_batches.push(EventBatch { events });
}
}
impl<Config: SandboxConfig> Record<Config>
where
Config::Runtime: pallet_contracts::Config,
{
pub fn deploy_results(&self) -> &[ContractInstantiateResult<Config::Runtime>] {
&self.deploy_results
}
pub fn last_deploy_result(&self) -> &ContractInstantiateResult<Config::Runtime> {
self.deploy_results.last().expect("No deploy results")
}
pub fn deploy_returns(&self) -> &[AccountIdFor<Config::Runtime>] {
&self.deploy_returns
}
pub fn last_deploy_return(&self) -> &AccountIdFor<Config::Runtime> {
self.deploy_returns.last().expect("No deploy returns")
}
pub fn call_results(&self) -> &[ContractExecResult<Config::Runtime>] {
&self.call_results
}
pub fn last_call_result(&self) -> &ContractExecResult<Config::Runtime> {
self.call_results.last().expect("No call results")
}
pub fn call_returns(&self) -> &[Vec<u8>] {
&self.call_returns
}
pub fn last_call_return(&self) -> &[u8] {
self.call_returns.last().expect("No call returns")
}
pub fn last_call_return_decoded<T: Decode>(&self) -> Result<MessageResult<T>, SessionError> {
let mut raw = self.last_call_return();
MessageResult::decode(&mut raw).map_err(|err| {
SessionError::Decoding(format!(
"Failed to decode the result of calling a contract: {err:?}"
))
})
}
pub fn event_batches(&self) -> &[EventBatch<Config::Runtime>] {
&self.event_batches
}
pub fn last_event_batch(&self) -> &EventBatch<Config::Runtime> {
self.event_batches.last().expect("No event batches")
}
}
pub struct EventBatch<R: frame_system::Config> {
events: Vec<EventRecordOf<R>>,
}
impl<R: frame_system::Config> EventBatch<R> {
pub fn all_events(&self) -> &[EventRecordOf<R>] {
&self.events
}
}
impl EventBatch<MinimalRuntime> {
pub fn contract_events(&self) -> Vec<&[u8]> {
self.events
.iter()
.filter_map(|event| match &event.event {
RuntimeEvent::Contracts(
pallet_contracts::Event::<MinimalRuntime>::ContractEmitted { data, .. },
) => Some(data.as_slice()),
_ => None,
})
.collect()
}
pub fn contract_events_decoded(
&self,
transcoder: &Rc<ContractMessageTranscoder>,
) -> Vec<Value> {
let signature_topics = transcoder
.metadata()
.spec()
.events()
.iter()
.filter_map(|event| event.signature_topic())
.map(|sig| sig.as_bytes().try_into().unwrap())
.collect::<Vec<[u8; 32]>>();
self.contract_events()
.into_iter()
.filter_map(|data| {
for signature_topic in &signature_topics {
if let Ok(decoded) = transcoder
.decode_contract_event(&signature_topic.into(), &mut &*data.encode())
{
return Some(decoded);
}
}
None
})
.collect()
}
}