use crate::{
error::usage_panic,
manager::ExtManager,
state::{accounts::Accounts, actors::Actors},
Log, Value, GAS_ALLOWANCE,
};
use codec::Encode;
use gear_common::{
auxiliary::{mailbox::*, BlockNumber},
storage::Interval,
};
use gear_core::{
ids::{prelude::MessageIdExt as _, MessageId, ProgramId},
message::{ReplyMessage, ReplyPacket},
};
use std::cell::RefCell;
pub struct ActorMailbox<'a> {
manager: &'a RefCell<ExtManager>,
user_id: ProgramId,
}
impl<'a> ActorMailbox<'a> {
pub(crate) fn new(user_id: ProgramId, manager: &'a RefCell<ExtManager>) -> ActorMailbox<'a> {
ActorMailbox { user_id, manager }
}
pub fn contains<T: Into<Log> + Clone>(&self, log: &T) -> bool {
self.find_message_by_log(&log.clone().into()).is_some()
}
pub fn reply(
&self,
log: Log,
payload: impl Encode,
value: Value,
) -> Result<MessageId, MailboxErrorImpl> {
self.reply_bytes(log, payload.encode(), value)
}
pub fn reply_bytes(
&self,
log: Log,
raw_payload: impl AsRef<[u8]>,
value: Value,
) -> Result<MessageId, MailboxErrorImpl> {
let reply_to_id = self
.find_message_by_log(&log)
.ok_or(MailboxErrorImpl::ElementNotFound)?
.id();
let mailboxed = self
.manager
.borrow_mut()
.read_mailbox_message(self.user_id, reply_to_id)?;
let destination = mailboxed.source();
let reply_id = MessageId::generate_reply(mailboxed.id());
let gas_limit = if self
.manager
.borrow_mut()
.gas_tree
.exists_and_deposit(reply_id)
{
0
} else {
GAS_ALLOWANCE
};
let dispatch = {
let payload = raw_payload
.as_ref()
.to_vec()
.try_into()
.unwrap_or_else(|err| unreachable!("Can't send reply with such payload: {err:?}"));
let message = ReplyMessage::from_packet(
reply_id,
ReplyPacket::new_with_gas(payload, gas_limit, value),
);
message.into_dispatch(self.user_id, destination, mailboxed.id())
};
Ok(self
.manager
.borrow_mut()
.validate_and_route_dispatch(dispatch))
}
pub fn claim_value<T: Into<Log>>(&self, log: T) -> Result<(), MailboxErrorImpl> {
let message_id = self
.find_message_by_log(&log.into())
.ok_or(MailboxErrorImpl::ElementNotFound)?
.id();
if !Accounts::exists(self.user_id) {
usage_panic!(
"User's {} balance is zero; mint value to it first.",
self.user_id
);
}
let mailboxed = self
.manager
.borrow_mut()
.read_mailbox_message(self.user_id, message_id)?;
if Actors::is_active_program(mailboxed.source()) {
let message = ReplyMessage::auto(mailboxed.id());
self.manager
.borrow_mut()
.gas_tree
.create(self.user_id, message.id(), 0, true)
.unwrap_or_else(|e| unreachable!("GasTree corrupted! {:?}", e));
let dispatch =
message.into_stored_dispatch(self.user_id, mailboxed.source(), mailboxed.id());
self.manager.borrow_mut().dispatches.push_back(dispatch);
}
Ok(())
}
fn find_message_by_log(&self, log: &Log) -> Option<MailboxedMessage> {
self.get_user_mailbox()
.find_map(|(msg, _)| log.eq(&msg).then_some(msg))
}
fn get_user_mailbox(&self) -> impl Iterator<Item = (MailboxedMessage, Interval<BlockNumber>)> {
self.manager.borrow().mailbox.iter_key(self.user_id)
}
}
#[cfg(test)]
mod tests {
use crate::{Log, Program, System, DEFAULT_USER_ALICE, EXISTENTIAL_DEPOSIT, GAS_MULTIPLIER};
use codec::Encode;
use demo_constructor::{Call, Calls, Scheme, WASM_BINARY};
use gear_core::{gas_metering::RentWeights, ids::ProgramId};
fn prepare_program(system: &System) -> (Program<'_>, ([u8; 32], Vec<u8>, Log)) {
let program = Program::from_binary_with_id(system, 121, WASM_BINARY);
let sender = ProgramId::from(DEFAULT_USER_ALICE).into_bytes();
let payload = b"sup!".to_vec();
let log = Log::builder().dest(sender).payload_bytes(payload.clone());
let msg_id = program.send(sender, Scheme::empty());
let res = system.run_next_block();
assert!(res.succeed.contains(&msg_id));
(program, (sender, payload, log))
}
#[test]
fn claim_value_from_mailbox() {
let system = System::new();
let (program, (sender, payload, log)) = prepare_program(&system);
let original_balance = system.balance_of(sender);
let value_send = 2 * EXISTENTIAL_DEPOSIT;
let handle = Calls::builder().send_value(sender, payload, value_send);
let msg_id = program.send_bytes_with_value(sender, handle.encode(), value_send);
let res = system.run_next_block();
assert!(res.succeed.contains(&msg_id));
assert!(res.contains(&log));
assert_eq!(
system.balance_of(sender),
original_balance
- value_send
- res.spent_value()
- GAS_MULTIPLIER.gas_to_value(RentWeights::default().mailbox_threshold.ref_time)
);
let mailbox = system.get_mailbox(sender);
assert!(mailbox.contains(&log));
assert!(mailbox.claim_value(log).is_ok());
assert_eq!(
system.balance_of(sender),
original_balance - res.spent_value()
);
}
#[test]
fn reply_to_mailbox_message() {
let system = System::new();
let (program, (sender, payload, log)) = prepare_program(&system);
let handle = Calls::builder().send(sender, payload);
let msg_id = program.send(sender, handle);
let res = system.run_next_block();
assert!(res.succeed.contains(&msg_id));
assert!(res.contains(&log));
let mailbox = system.get_mailbox(sender);
assert!(mailbox.contains(&log));
let msg_id = mailbox
.reply(log, Calls::default(), 0)
.expect("sending reply failed: didn't find message in mailbox");
let res = system.run_next_block();
assert!(res.succeed.contains(&msg_id));
}
#[test]
fn delayed_mailbox_message() {
let system = System::new();
let (program, (sender, payload, log)) = prepare_program(&system);
let delay = 5;
let handle = Calls::builder().add_call(Call::Send(
sender.into(),
payload.into(),
None,
0.into(),
delay.into(),
));
let msg_id = program.send(sender, handle);
let res = system.run_next_block();
assert!(res.succeed.contains(&msg_id));
let results = system.run_scheduled_tasks(delay);
let delayed_dispatch_res = results.last().expect("internal error: no blocks spent");
assert!(delayed_dispatch_res.contains(&log));
let mailbox = system.get_mailbox(sender);
assert!(mailbox.contains(&log));
}
}