use crate::{
error::usage_panic,
program::{Gas, ProgramIdWrapper},
Value, GAS_MULTIPLIER,
};
use codec::{Codec, Encode};
use core_processor::configs::BlockInfo;
use gear_core::{
ids::{MessageId, ProgramId},
message::{Payload, StoredMessage, UserStoredMessage},
};
use gear_core_errors::{ErrorReplyReason, ReplyCode, SimpleExecutionError, SuccessReplyReason};
use std::{
collections::{BTreeMap, BTreeSet},
convert::TryInto,
fmt::Debug,
};
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct CoreLog {
id: MessageId,
source: ProgramId,
destination: ProgramId,
payload: Payload,
reply_code: Option<ReplyCode>,
reply_to: Option<MessageId>,
}
impl CoreLog {
pub fn id(&self) -> MessageId {
self.id
}
pub fn source(&self) -> ProgramId {
self.source
}
pub fn destination(&self) -> ProgramId {
self.destination
}
pub fn payload(&self) -> &[u8] {
self.payload.inner()
}
pub fn reply_code(&self) -> Option<ReplyCode> {
self.reply_code
}
pub fn reply_to(&self) -> Option<MessageId> {
self.reply_to
}
}
impl From<StoredMessage> for CoreLog {
fn from(other: StoredMessage) -> Self {
Self {
id: other.id(),
source: other.source(),
destination: other.destination(),
payload: other.payload_bytes().to_vec().try_into().unwrap(),
reply_code: other
.details()
.and_then(|d| d.to_reply_details().map(|d| d.to_reply_code())),
reply_to: other
.details()
.and_then(|d| d.to_reply_details().map(|d| d.to_message_id())),
}
}
}
#[derive(Debug)]
pub struct DecodedCoreLog<T: Codec + Debug> {
id: MessageId,
source: ProgramId,
destination: ProgramId,
payload: T,
reply_code: Option<ReplyCode>,
reply_to: Option<MessageId>,
}
impl<T: Codec + Debug> DecodedCoreLog<T> {
pub(crate) fn try_from_log(log: CoreLog) -> Option<Self> {
let payload = T::decode(&mut log.payload.inner()).ok()?;
Some(Self {
id: log.id,
source: log.source,
destination: log.destination,
payload,
reply_code: log.reply_code,
reply_to: log.reply_to,
})
}
pub fn id(&self) -> MessageId {
self.id
}
pub fn source(&self) -> ProgramId {
self.source
}
pub fn destination(&self) -> ProgramId {
self.destination
}
pub fn payload(&self) -> &T {
&self.payload
}
pub fn reply_code(&self) -> Option<ReplyCode> {
self.reply_code
}
pub fn reply_to(&self) -> Option<MessageId> {
self.reply_to
}
}
#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
pub struct Log {
pub(crate) source: Option<ProgramId>,
pub(crate) destination: Option<ProgramId>,
pub(crate) payload: Option<Payload>,
pub(crate) reply_code: Option<ReplyCode>,
pub(crate) reply_to: Option<MessageId>,
}
impl<ID, T> From<(ID, T)> for Log
where
ID: Into<ProgramIdWrapper>,
T: AsRef<[u8]>,
{
fn from(other: (ID, T)) -> Self {
Self::builder().dest(other.0).payload_bytes(other.1)
}
}
impl<ID1, ID2, T> From<(ID1, ID2, T)> for Log
where
ID1: Into<ProgramIdWrapper>,
ID2: Into<ProgramIdWrapper>,
T: AsRef<[u8]>,
{
fn from(other: (ID1, ID2, T)) -> Self {
Self::builder()
.source(other.0)
.dest(other.1)
.payload_bytes(other.2)
}
}
impl Log {
pub fn builder() -> Self {
Default::default()
}
pub fn error_builder(error_reason: ErrorReplyReason) -> Self {
let mut log = Self::builder();
log.reply_code = Some(error_reason.into());
log.payload = Some(
error_reason
.to_string()
.into_bytes()
.try_into()
.expect("Infallible"),
);
log
}
pub fn auto_reply_builder() -> Self {
Self {
reply_code: Some(ReplyCode::Success(SuccessReplyReason::Auto)),
..Default::default()
}
}
pub fn payload(self, payload: impl Encode) -> Self {
self.payload_bytes(payload.encode())
}
pub fn payload_bytes(mut self, payload: impl AsRef<[u8]>) -> Self {
if self.payload.is_some() {
usage_panic!("Payload was already set for this log");
}
if let Some(ReplyCode::Success(SuccessReplyReason::Auto)) = self.reply_code {
usage_panic!("Cannot set payload for auto reply");
}
self.payload = Some(payload.as_ref().to_vec().try_into().unwrap());
self
}
pub fn source(mut self, source: impl Into<ProgramIdWrapper>) -> Self {
if self.source.is_some() {
usage_panic!("Source was already set for this log");
}
self.source = Some(source.into().0);
self
}
pub fn dest(mut self, dest: impl Into<ProgramIdWrapper>) -> Self {
if self.destination.is_some() {
usage_panic!("Destination was already set for this log");
}
self.destination = Some(dest.into().0);
self
}
pub fn reply_code(mut self, reply_code: ReplyCode) -> Self {
if self.reply_code.is_some() {
usage_panic!("Reply code was already set for this log");
}
if self.payload.is_some() && reply_code == ReplyCode::Success(SuccessReplyReason::Auto) {
usage_panic!("Cannot set auto reply for log with payload");
}
self.reply_code = Some(reply_code);
self
}
pub fn reply_to(mut self, reply_to: MessageId) -> Self {
if self.reply_to.is_some() {
usage_panic!("Reply destination was already set for this log");
}
self.reply_to = Some(reply_to);
self
}
}
impl PartialEq<UserStoredMessage> for Log {
fn eq(&self, other: &UserStoredMessage) -> bool {
let has_any = self.source.is_some()
|| self.destination.is_some()
|| self.payload.is_some()
|| self.reply_to.is_some();
if matches!(self.source, Some(source) if source != other.source()) {
return false;
}
if matches!(self.destination, Some(dest) if dest != other.destination()) {
return false;
}
if matches!(&self.payload, Some(payload) if payload.inner() != other.payload_bytes()) {
return false;
}
if matches!(self.reply_to, Some(reply_to) if reply_to != other.id()) {
return false;
}
has_any
}
}
impl<T: Codec + Debug> PartialEq<DecodedCoreLog<T>> for Log {
fn eq(&self, other: &DecodedCoreLog<T>) -> bool {
let core_log = CoreLog {
id: other.id,
source: other.source,
destination: other.destination,
payload: other.payload.encode().try_into().unwrap(),
reply_code: other.reply_code,
reply_to: other.reply_to,
};
core_log.eq(self)
}
}
impl<T: Codec + Debug> PartialEq<Log> for DecodedCoreLog<T> {
fn eq(&self, other: &Log) -> bool {
other.eq(self)
}
}
impl PartialEq<CoreLog> for Log {
fn eq(&self, other: &CoreLog) -> bool {
if self.reply_code.is_some() && self.reply_code != other.reply_code {
return false;
}
if self.reply_to.is_some() && self.reply_to != other.reply_to {
return false;
}
if let Some(source) = self.source {
if source != other.source {
return false;
}
}
if let Some(destination) = self.destination {
if destination != other.destination {
return false;
}
}
if let Some(payload) = &self.payload {
if payload.inner() != other.payload.inner() {
return false;
}
}
true
}
}
impl PartialEq<Log> for CoreLog {
fn eq(&self, other: &Log) -> bool {
other.eq(self)
}
}
#[derive(Debug, Default)]
pub struct BlockRunResult {
pub block_info: BlockInfo,
pub gas_allowance_spent: Gas,
pub succeed: BTreeSet<MessageId>,
pub failed: BTreeSet<MessageId>,
pub not_executed: BTreeSet<MessageId>,
pub total_processed: u32,
pub log: Vec<CoreLog>,
pub gas_burned: BTreeMap<MessageId, Gas>,
}
impl BlockRunResult {
pub fn contains<T: Into<Log> + Clone>(&self, log: &T) -> bool {
let log = log.clone().into();
self.log.iter().any(|e| e == &log)
}
pub fn log(&self) -> &[CoreLog] {
&self.log
}
pub fn decoded_log<T: Codec + Debug>(&self) -> Vec<DecodedCoreLog<T>> {
self.log
.clone()
.into_iter()
.flat_map(DecodedCoreLog::try_from_log)
.collect()
}
pub fn assert_panicked_with(&self, message_id: MessageId, msg: impl Into<String>) {
let panic_log = self.message_panic_log(message_id);
assert!(panic_log.is_some(), "Program did not panic");
let msg = msg.into();
let payload = String::from_utf8(
panic_log
.expect("Asserted using `Option::is_some()`")
.payload()
.into(),
)
.expect("Unable to decode panic message");
assert!(
payload.starts_with(&format!("Panic occurred: panicked with '{msg}'")),
"expected panic message that contains `{msg}`, but the actual panic message is `{payload}`"
);
}
pub fn spent_value(&self) -> Value {
let spent_gas = self
.gas_burned
.values()
.fold(Gas::zero(), |acc, &x| acc.saturating_add(x));
GAS_MULTIPLIER.gas_to_value(spent_gas.0)
}
fn message_panic_log(&self, message_id: MessageId) -> Option<&CoreLog> {
let msg_log = self
.log
.iter()
.find(|log| log.reply_to == Some(message_id))?;
let is_panic = matches!(
msg_log.reply_code(),
Some(ReplyCode::Error(ErrorReplyReason::Execution(
SimpleExecutionError::UserspacePanic
)))
);
is_panic.then_some(msg_log)
}
}
#[test]
fn soft_into() {
let log: Log = (1, "payload").into();
assert_eq!(Log::builder().dest(1).payload_bytes("payload"), log);
assert_eq!(
Log::builder().source(1).dest(2).payload_bytes("payload"),
Log::from((1, 2, "payload")),
);
let v = vec![1; 32];
assert_eq!(
Log::builder().source(1).dest(&v).payload_bytes("payload"),
Log::from((1, v, "payload"))
);
}