1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245
use std::rc::Rc;
use contract_transcode::{ContractMessageTranscoder, Value};
use parity_scale_codec::{Decode, Encode};
use crate::{
errors::MessageResult,
runtime::{minimal::RuntimeEvent, AccountIdFor, MinimalRuntime, Runtime},
session::{error::SessionError, BalanceOf},
EventRecordOf, Sandbox,
};
type ContractInstantiateResult<R> =
pallet_contracts::ContractInstantiateResult<AccountIdFor<R>, BalanceOf<R>, EventRecordOf<R>>;
type ContractExecResult<R> = pallet_contracts::ContractExecResult<BalanceOf<R>, EventRecordOf<R>>;
/// Data structure storing the results of contract interaction during a session.
///
/// # Naming convention
///
/// By `result` we mean the full result (enriched with some context information) of the contract
/// interaction, like `ContractExecResult`. By `return` we mean the return value of the contract
/// execution, like a value returned from a message or the address of a newly instantiated contract.
pub struct Record<T: pallet_contracts::Config> {
/// The results of contract instantiation.
deploy_results: Vec<ContractInstantiateResult<T>>,
/// The return values of contract instantiation (i.e. the addresses of the newly instantiated
/// contracts).
deploy_returns: Vec<AccountIdFor<T>>,
/// The results of contract calls.
call_results: Vec<ContractExecResult<T>>,
/// The return values of contract calls (in the SCALE-encoded form).
call_returns: Vec<Vec<u8>>,
/// The events emitted by the contracts.
event_batches: Vec<EventBatch<T>>,
/// Because `drink` normally doesn't have a continuous block production, everything implicitly
/// happens within a single block (unless user explicitly trigger a new block). This means that
/// all runtime events (from consecutive transactions) are stacked up in a common buffer.
/// `Record` is capable of recording only the events that happened during a single transaction
/// by remembering the number of events that were already in the buffer before the transaction
/// started. However, this is must be explicitly enabled by calling `start_recording_events`
/// before the transaction and `stop_recording_events` after the transaction.
block_events_so_far: Option<usize>,
}
impl<T: pallet_contracts::Config> Default for Record<T> {
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,
}
}
}
// API for `Session` to record results and events related to contract interaction.
impl<T: pallet_contracts::Config> Record<T> {
pub(super) fn push_deploy_result(&mut self, result: ContractInstantiateResult<T>) {
self.deploy_results.push(result);
}
pub(super) fn push_deploy_return(&mut self, return_value: AccountIdFor<T>) {
self.deploy_returns.push(return_value);
}
pub(super) fn push_call_result(&mut self, result: ContractExecResult<T>) {
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<impl Runtime<Config = T>>,
) {
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<impl Runtime<Config = T>>,
) {
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 });
}
}
// API for the end user.
impl<T: pallet_contracts::Config> Record<T> {
/// Returns all the results of contract instantiations that happened during the session.
pub fn deploy_results(&self) -> &[ContractInstantiateResult<T>] {
&self.deploy_results
}
/// Returns the last result of contract instantiation that happened during the session. Panics
/// if there were no contract instantiations.
pub fn last_deploy_result(&self) -> &ContractInstantiateResult<T> {
self.deploy_results.last().expect("No deploy results")
}
/// Returns all the return values of contract instantiations that happened during the session.
pub fn deploy_returns(&self) -> &[AccountIdFor<T>] {
&self.deploy_returns
}
/// Returns the last return value of contract instantiation that happened during the session.
/// Panics if there were no contract instantiations.
pub fn last_deploy_return(&self) -> &AccountIdFor<T> {
self.deploy_returns.last().expect("No deploy returns")
}
/// Returns all the results of contract calls that happened during the session.
pub fn call_results(&self) -> &[ContractExecResult<T>] {
&self.call_results
}
/// Returns the last result of contract call that happened during the session. Panics if there
/// were no contract calls.
pub fn last_call_result(&self) -> &ContractExecResult<T> {
self.call_results.last().expect("No call results")
}
/// Returns all the (encoded) return values of contract calls that happened during the session.
pub fn call_returns(&self) -> &[Vec<u8>] {
&self.call_returns
}
/// Returns the last (encoded) return value of contract call that happened during the session.
/// Panics if there were no contract calls.
pub fn last_call_return(&self) -> &[u8] {
self.call_returns.last().expect("No call returns")
}
/// Returns the last (decoded) return value of contract call that happened during the session.
/// Panics if there were no contract calls.
pub fn last_call_return_decoded<V: Decode>(&self) -> Result<MessageResult<V>, 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:?}"
))
})
}
/// Returns all the event batches that were recorded for contract interactions during the
/// session.
pub fn event_batches(&self) -> &[EventBatch<T>] {
&self.event_batches
}
/// Returns the last event batch that was recorded for contract interactions during the session.
/// Panics if there were no event batches.
pub fn last_event_batch(&self) -> &EventBatch<T> {
self.event_batches.last().expect("No event batches")
}
}
/// A batch of runtime events that were emitted during a single contract interaction.
pub struct EventBatch<T: frame_system::Config> {
events: Vec<EventRecordOf<T>>,
}
impl<T: frame_system::Config> EventBatch<T> {
/// Returns all the events that were emitted during the contract interaction.
pub fn all_events(&self) -> &[EventRecordOf<T>] {
&self.events
}
}
impl EventBatch<MinimalRuntime> {
/// Returns all the contract events that were emitted during the contract interaction.
///
/// **WARNING**: This method will return all the events that were emitted by ANY contract. If your
/// call triggered multiple contracts, you will have to filter the events yourself.
///
/// We have to match against static enum variant, and thus (at least for now) we support only
/// `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()
}
/// The same as `contract_events`, but decodes the events using the given transcoder.
///
/// **WARNING**: This method will try to decode all the events that were emitted by ANY
/// contract. This means that some contract events might either fail to decode or be decoded
/// incorrectly (to some rubbish). In the former case, they will be skipped, but with the latter
/// case, you will have to filter the events yourself.
///
/// **WARNING 2**: This method will ignore anonymous events.
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
// We have to `encode` the data because `decode_contract_event` is targeted
// at decoding the data from the runtime, and not directly from the contract
// events.
.decode_contract_event(&signature_topic.into(), &mut &*data.encode())
{
return Some(decoded);
}
}
None
})
.collect()
}
}