use crate::{manager::ExtManager, CoreLog, Log, RunResult};
use codec::Encode;
use core_processor::common::JournalHandler;
use gear_core::{
ids::{MessageId, ProgramId},
message::{Dispatch, DispatchKind, Message, ReplyDetails, StoredMessage},
};
use gear_core_errors::{ReplyCode, SuccessReplyReason};
use std::{cell::RefCell, convert::TryInto};
pub struct Mailbox<'a> {
manager: &'a RefCell<ExtManager>,
user_id: ProgramId,
}
impl<'a> Mailbox<'a> {
pub(crate) fn new(user_id: ProgramId, manager: &'a RefCell<ExtManager>) -> Mailbox<'a> {
Mailbox { user_id, manager }
}
pub fn contains<T: Into<Log> + Clone>(&self, log: &T) -> bool {
let log: Log = log.clone().into();
if let Some(mailbox) = self.manager.borrow().mailbox.get(&self.user_id) {
return mailbox.iter().any(|message| log.eq(message));
}
self.manager
.borrow_mut()
.mailbox
.insert(self.user_id, Vec::default());
false
}
pub fn take_message<T: Into<Log>>(&self, log: T) -> MessageReplier {
MessageReplier::new(self.remove_message(log), self.manager)
}
pub fn reply(&self, log: Log, payload: impl Encode, value: u128) -> RunResult {
self.reply_bytes(log, payload.encode(), value)
}
pub fn reply_bytes(&self, log: Log, raw_payload: impl AsRef<[u8]>, value: u128) -> RunResult {
self.take_message(log).reply_bytes(raw_payload, value)
}
pub fn claim_value<T: Into<Log>>(&self, log: T) {
let message = self.remove_message(log);
self.manager.borrow_mut().send_value(
message.source(),
Some(message.destination()),
message.value(),
);
}
#[track_caller]
fn remove_message<T: Into<Log>>(&self, log: T) -> StoredMessage {
let log = log.into();
let mut manager = self.manager.borrow_mut();
let messages = manager
.mailbox
.get_mut(&self.user_id)
.expect("Infallible. No mailbox associated with this user id");
let index = messages
.iter()
.position(|message| log.eq(message))
.expect("No message that satisfies log");
messages.remove(index)
}
}
pub struct MessageReplier<'a> {
log: CoreLog,
manager: &'a RefCell<ExtManager>,
}
impl<'a> MessageReplier<'a> {
pub(crate) fn new(
message: StoredMessage,
manager: &'a RefCell<ExtManager>,
) -> MessageReplier<'a> {
MessageReplier {
log: message.into(),
manager,
}
}
pub fn reply(&self, payload: impl Encode, value: u128) -> RunResult {
self.reply_bytes(payload.encode(), value)
}
pub fn reply_bytes(&self, raw_payload: impl AsRef<[u8]>, value: u128) -> RunResult {
let message = Message::new(
MessageId::from(self.manager.borrow_mut().fetch_inc_message_nonce()),
self.log.destination(),
self.log.source(),
raw_payload.as_ref().to_vec().try_into().unwrap(),
None,
value,
Some(
ReplyDetails::new(
self.log.id(),
ReplyCode::Success(SuccessReplyReason::Manual),
)
.into(),
),
);
self.manager
.borrow_mut()
.validate_and_run_dispatch(Dispatch::new(DispatchKind::Reply, message))
}
}
#[cfg(test)]
mod tests {
use std::convert::TryInto;
use crate::{program::ProgramIdWrapper, Log, Program, System};
use codec::Encode;
use gear_core::{
ids::MessageId,
message::{Dispatch, DispatchKind, Message, Payload},
};
#[test]
fn mailbox_walk_through_test() {
let system = System::new();
let message_id: MessageId = Default::default();
let source_user_id = ProgramIdWrapper::from(100).0;
let destination_user_id = ProgramIdWrapper::from(200).0;
let message_payload: Payload = vec![1, 2, 3].try_into().unwrap();
let encoded_message_payload: Payload = message_payload.encode().try_into().unwrap();
let reply_payload: Payload = vec![3, 2, 1].try_into().unwrap();
let encoded_reply_payload: Payload = reply_payload.encode().try_into().unwrap();
let log = Log::builder().payload(message_payload);
let message = Message::new(
message_id,
source_user_id,
destination_user_id,
encoded_message_payload.clone(),
Default::default(),
0,
None,
);
let message_result =
system.send_dispatch(Dispatch::new(DispatchKind::Handle, message.clone()));
let message_log = message_result
.log
.last()
.expect("No message log in run result");
let destination_user_mailbox = system.get_mailbox(destination_user_id);
let message_replier = destination_user_mailbox.take_message(log);
let reply_log = message_replier.reply(reply_payload, 0).log;
let last_reply_log = reply_log.last().expect("No message log in run result");
let second_message_result =
system.send_dispatch(Dispatch::new(DispatchKind::Handle, message));
let second_message_log = message_result
.log
.last()
.expect("No message log in run result");
assert!(!message_result.main_failed);
assert!(!message_result.others_failed);
assert!(!second_message_result.main_failed);
assert!(!second_message_result.others_failed);
assert_eq!(reply_log.len(), 1);
assert_eq!(last_reply_log.payload(), encoded_reply_payload.inner());
assert_eq!(message_log.payload(), encoded_message_payload.inner());
assert_eq!(
second_message_log.payload(),
encoded_message_payload.inner()
);
}
#[test]
fn mailbox_deletes_message_after_reply() {
let system = System::new();
let message_id: MessageId = Default::default();
let source_user_id = ProgramIdWrapper::from(100).0;
let destination_user_id = ProgramIdWrapper::from(200).0;
let message_payload: Payload = vec![1, 2, 3].try_into().unwrap();
let reply_payload: Payload = vec![3, 2, 1].try_into().unwrap();
let message_log = Log::builder().payload(message_payload.clone());
let message = Message::new(
message_id,
source_user_id,
destination_user_id,
message_payload.encode().try_into().unwrap(),
Default::default(),
0,
None,
);
system.send_dispatch(Dispatch::new(DispatchKind::Handle, message));
let mut destination_user_mailbox = system.get_mailbox(destination_user_id);
destination_user_mailbox.reply(message_log.clone(), reply_payload, 0);
destination_user_mailbox = system.get_mailbox(destination_user_id);
assert!(!destination_user_mailbox.contains(&message_log))
}
#[test]
fn mailbox_reply_bytes_test() {
let system = System::new();
let message_id: MessageId = Default::default();
let source_user_id = ProgramIdWrapper::from(100).0;
let destination_user_id = ProgramIdWrapper::from(200).0;
let message_payload: Payload = vec![1, 2, 3].try_into().unwrap();
let reply_payload_array: [u8; 3] = [3, 2, 1];
let reply_payload: Payload = reply_payload_array.to_vec().try_into().unwrap();
let log = Log::builder().payload(message_payload.clone());
let message = Message::new(
message_id,
source_user_id,
destination_user_id,
message_payload.encode().try_into().unwrap(),
Default::default(),
0,
None,
);
system.send_dispatch(Dispatch::new(DispatchKind::Handle, message));
let destination_user_mailbox = system.get_mailbox(destination_user_id);
let message_replier = destination_user_mailbox.take_message(log);
let result = message_replier.reply_bytes(reply_payload_array, 0);
let result_log = result.log;
let last_result_log = result_log.last().expect("No message log in run result");
assert_eq!(last_result_log.payload(), reply_payload.inner());
}
#[test]
fn mailbox_deletes_message_after_taking() {
let system = System::new();
let message_id: MessageId = Default::default();
let source_user_id = ProgramIdWrapper::from(100).0;
let destination_user_id = ProgramIdWrapper::from(200).0;
let message_payload: Payload = vec![1, 2, 3].try_into().unwrap();
let log = Log::builder().payload(message_payload.clone());
let message = Message::new(
message_id,
source_user_id,
destination_user_id,
message_payload.encode().try_into().unwrap(),
Default::default(),
0,
None,
);
system.send_dispatch(Dispatch::new(DispatchKind::Handle, message));
let destination_user_mailbox = system.get_mailbox(destination_user_id);
destination_user_mailbox.take_message(log.clone());
assert!(!destination_user_mailbox.contains(&log))
}
#[test]
#[should_panic(expected = "No message that satisfies log")]
fn take_unknown_log_message() {
let system = System::new();
let source_user_id = 100;
let destination_user_id = 200;
let log = Log::builder().source(source_user_id);
let mailbox = system.get_mailbox(destination_user_id);
mailbox.take_message(log);
}
#[test]
#[should_panic(expected = "Mailbox available only for users")]
fn take_programs_mailbox() {
let system = System::new();
let restricted_user_id = 42;
Program::from_binary_with_id(
&system,
restricted_user_id,
demo_futures_unordered::WASM_BINARY,
);
system.get_mailbox(restricted_user_id);
}
#[test]
fn claim_value_from_mailbox() {
let system = System::new();
let message_id: MessageId = Default::default();
let sender_id = 1;
let receiver_id = 42;
let payload = b"hello".to_vec();
let log = Log::builder()
.source(sender_id)
.dest(receiver_id)
.payload(payload.clone());
let message = Message::new(
message_id,
sender_id.into(),
receiver_id.into(),
payload.encode().try_into().unwrap(),
Default::default(),
2 * crate::EXISTENTIAL_DEPOSIT,
None,
);
system.mint_to(sender_id, 2 * crate::EXISTENTIAL_DEPOSIT);
system.send_dispatch(Dispatch::new(DispatchKind::Handle, message));
let receiver_mailbox = system.get_mailbox(receiver_id);
receiver_mailbox.claim_value(log);
assert_eq!(
system.balance_of(receiver_id),
2 * crate::EXISTENTIAL_DEPOSIT
);
}
#[test]
fn delayed_dispatches_works() {
let system = System::new();
let message_id: MessageId = Default::default();
let source_user_id = ProgramIdWrapper::from(100).0;
let destination_user_id = ProgramIdWrapper::from(200).0;
let message_payload: Payload = vec![1, 2, 3].try_into().unwrap();
let log = Log::builder().payload(message_payload.clone());
let message = Message::new(
message_id,
source_user_id,
destination_user_id,
message_payload.encode().try_into().unwrap(),
Default::default(),
0,
None,
);
let bn_before_schedule = 5;
let scheduled_delay = 10;
system.0.borrow_mut().send_delayed_dispatch(
Dispatch::new(DispatchKind::Handle, message),
scheduled_delay,
);
let mailbox = system.get_mailbox(destination_user_id);
assert!(!mailbox.contains(&log));
assert_eq!(system.spend_blocks(bn_before_schedule).len(), 0);
assert!(!mailbox.contains(&log));
assert_eq!(
system
.spend_blocks(scheduled_delay - bn_before_schedule)
.len(),
1
);
assert!(mailbox.contains(&log));
}
}