use crate::{
ext::Engine,
types::{
AccountId,
Balance,
BlockNumber,
BlockTimestamp,
},
AccountError,
Error,
};
use std::collections::HashMap;
#[derive(Debug, Clone)]
pub struct EmittedEvent {
pub topics: Vec<Vec<u8>>,
pub data: Vec<u8>,
}
#[derive(Clone)]
pub struct RecordedDebugMessages {
debug_messages: Vec<String>,
}
impl RecordedDebugMessages {
pub fn new() -> Self {
Self {
debug_messages: Vec::new(),
}
}
pub fn record(&mut self, message: String) {
self.debug_messages.push(message);
}
pub fn clear(&mut self) {
self.debug_messages.clear();
}
}
impl Default for RecordedDebugMessages {
fn default() -> Self {
Self::new()
}
}
impl IntoIterator for RecordedDebugMessages {
type Item = String;
type IntoIter = std::vec::IntoIter<Self::Item>;
fn into_iter(self) -> Self::IntoIter {
self.debug_messages.into_iter()
}
}
pub struct DebugInfo {
emitted_events: Vec<EmittedEvent>,
emitted_debug_messages: RecordedDebugMessages,
count_reads: HashMap<AccountId, usize>,
count_writes: HashMap<AccountId, usize>,
cells_per_account: HashMap<AccountId, HashMap<Vec<u8>, bool>>,
}
impl Default for DebugInfo {
fn default() -> Self {
Self::new()
}
}
impl DebugInfo {
pub fn new() -> Self {
Self {
emitted_events: Vec::new(),
emitted_debug_messages: RecordedDebugMessages::new(),
count_reads: HashMap::new(),
count_writes: HashMap::new(),
cells_per_account: HashMap::new(),
}
}
pub fn reset(&mut self) {
self.count_reads.clear();
self.count_writes.clear();
self.emitted_events.clear();
self.emitted_debug_messages.clear();
self.cells_per_account.clear();
}
pub fn inc_writes(&mut self, account_id: AccountId) {
self.count_writes
.entry(account_id)
.and_modify(|v| *v += 1)
.or_insert(1);
}
pub fn inc_reads(&mut self, account_id: AccountId) {
self.count_reads
.entry(account_id)
.and_modify(|v| *v += 1)
.or_insert(1);
}
pub fn record_cell_for_account(&mut self, account_id: AccountId, key: Vec<u8>) {
self.cells_per_account
.entry(account_id)
.and_modify(|hm| {
let _ = hm.insert(key.clone(), true);
})
.or_insert({
let mut hm = HashMap::new();
hm.insert(key, true);
hm
});
}
pub fn remove_cell_for_account(
&mut self,
account_id: AccountId,
key: Vec<u8>,
) -> Option<bool> {
self.cells_per_account
.get_mut(&account_id)
.map(|hm| hm.remove(&key))
.unwrap_or(None)
}
pub fn record_debug_message(&mut self, message: String) {
self.emitted_debug_messages.record(message);
}
pub fn record_event(&mut self, event: EmittedEvent) {
self.emitted_events.push(event);
}
}
impl Engine {
pub fn initialize_or_reset(&mut self) {
self.exec_context.reset();
self.database.clear();
self.debug_info.reset();
}
pub fn get_contract_storage_rw(&self, account_id: Vec<u8>) -> (usize, usize) {
let account_id = AccountId::from(account_id);
let reads = self.debug_info.count_reads.get(&account_id).unwrap_or(&0);
let writes = self.debug_info.count_writes.get(&account_id).unwrap_or(&0);
(*reads, *writes)
}
pub fn count_reads(&self) -> usize {
self.debug_info.count_reads.values().sum()
}
pub fn count_writes(&self) -> usize {
self.debug_info.count_writes.values().sum()
}
pub fn set_caller(&mut self, caller: Vec<u8>) {
self.exec_context.caller = Some(caller.into());
}
pub fn set_contract(&mut self, caller: Vec<u8>) {
self.exec_context.contracts.push(caller);
}
pub fn set_callee(&mut self, callee: Vec<u8>) {
self.exec_context.callee = Some(callee.into());
}
pub fn count_used_storage_cells(&self, account_id: &[u8]) -> Result<usize, Error> {
let cells = self
.debug_info
.cells_per_account
.get(&account_id.to_owned().into())
.ok_or_else(|| {
Error::Account(AccountError::NoAccountForId(account_id.to_vec()))
})?;
Ok(cells.len())
}
pub fn advance_block(&mut self) {
self.exec_context.block_number += 1;
self.exec_context.block_timestamp += self.chain_spec.block_time;
}
pub fn get_callee(&self) -> Vec<u8> {
self.exec_context.callee()
}
pub fn is_contract(&self, account_id: Vec<u8>) -> bool {
self.exec_context.contracts.contains(&account_id)
}
pub fn get_emitted_debug_messages(&self) -> RecordedDebugMessages {
self.debug_info.emitted_debug_messages.clone()
}
pub fn get_emitted_events(&self) -> impl Iterator<Item = EmittedEvent> {
self.debug_info.emitted_events.clone().into_iter()
}
pub fn get_balance(&self, account_id: Vec<u8>) -> Result<Balance, Error> {
self.database
.get_balance(&account_id)
.ok_or(Error::Account(AccountError::NoAccountForId(account_id)))
}
pub fn set_balance(&mut self, account_id: Vec<u8>, new_balance: Balance) {
self.database.set_balance(&account_id, new_balance);
}
pub fn set_value_transferred(&mut self, value: Balance) {
self.exec_context.value_transferred = value;
}
pub fn set_block_timestamp(&mut self, new_block_timestamp: BlockTimestamp) {
self.exec_context.block_timestamp = new_block_timestamp;
}
pub fn set_block_number(&mut self, new_block_number: BlockNumber) {
self.exec_context.block_number = new_block_number;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn setting_getting_callee() {
let mut engine = Engine::new();
let account_id = vec![1; 32];
engine.set_callee(account_id.clone());
assert_eq!(engine.get_callee(), account_id);
}
#[test]
fn count_cells_per_account_must_stay_the_same() {
let mut engine = Engine::new();
let account_id = vec![1; 32];
engine.set_callee(account_id.clone());
let key: &[u8; 32] = &[0x42; 32];
engine.set_storage(key, &[0x05_u8; 5]);
assert_eq!(engine.count_used_storage_cells(&account_id), Ok(1));
engine.set_storage(key, &[0x05_u8; 6]);
assert_eq!(engine.count_used_storage_cells(&account_id), Ok(1));
}
#[test]
fn count_cells_per_account_must_be_reset() {
let mut engine = Engine::new();
let account_id = vec![1; 32];
engine.set_callee(account_id.clone());
let key: &[u8; 32] = &[0x42; 32];
engine.set_storage(key, &[0x05_u8; 5]);
assert_eq!(engine.count_used_storage_cells(&account_id), Ok(1));
engine.clear_storage(key);
assert_eq!(engine.count_used_storage_cells(&account_id), Ok(0));
}
#[test]
fn count_total_writes() {
let mut engine = Engine::new();
let key: &[u8; 32] = &[0x42; 32];
engine.set_callee(vec![1; 32]);
engine.set_storage(key, &[0x05_u8; 5]);
engine.set_storage(key, &[0x05_u8; 6]);
engine.get_storage(key).unwrap();
engine.set_callee(vec![2; 32]);
engine.set_storage(key, &[0x07_u8; 7]);
engine.get_storage(key).unwrap();
assert_eq!(engine.count_writes(), 3);
assert_eq!(engine.count_reads(), 2);
}
}