use super::{Memory, MemoryError, OwnedVec};
#[derive(Debug)]
pub struct OwnedString {
bytes: OwnedVec<u8>,
}
impl OwnedString {
pub(super) fn new(memory: &Memory) -> Self {
Self {
bytes: memory.vector(),
}
}
#[must_use]
pub fn memory(&self) -> &Memory {
self.bytes.memory()
}
#[must_use]
pub fn len(&self) -> usize {
self.bytes.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.bytes.is_empty()
}
#[must_use]
pub fn capacity(&self) -> usize {
self.bytes.capacity()
}
pub fn as_str(&self, execution: &Memory) -> Result<&str, MemoryError> {
execution.charge_work(self.bytes.len() as u64)?;
Ok(core::str::from_utf8(&self.bytes).expect("only valid UTF-8 is appended"))
}
#[must_use]
pub fn as_bytes(&self) -> &[u8] {
&self.bytes
}
pub fn try_push_str(&mut self, text: &str) -> Result<(), MemoryError> {
self.bytes.try_extend_from_slice(text.as_bytes())
}
pub fn try_push(&mut self, character: char) -> Result<(), MemoryError> {
self.memory().charge_work(1)?;
let mut encoded = [0; 4];
self.try_push_str(character.encode_utf8(&mut encoded))
}
pub fn try_copy(&self) -> Result<Self, MemoryError> {
Ok(Self {
bytes: self.bytes.try_copy()?,
})
}
}
#[cfg(test)]
mod tests {
use super::super::MemoryAccount;
use super::*;
use alloc::sync::Arc;
#[test]
fn repeated_scalar_append_has_amortized_growth() {
const LENGTH: usize = 32_768;
let memory = Memory::new(Arc::new(MemoryAccount::new(LENGTH * 2, LENGTH * 8)));
let mut text = memory.string();
for _ in 0..LENGTH {
text.try_push('a').unwrap();
}
assert_eq!(text.len(), LENGTH);
assert_eq!(memory.used(), text.capacity());
assert!(memory.work_used() <= LENGTH * 8);
drop(text);
assert_eq!(memory.used(), 0);
}
#[test]
fn text_is_valid_utf8_and_retains_its_capacity_after_move() {
let memory = Memory::new(Arc::new(MemoryAccount::new(64, 1024)));
let mut text = memory.string();
text.try_push_str("Дія").unwrap();
text.try_push('🦀').unwrap();
assert_eq!(text.as_str(&memory).unwrap(), "Дія🦀");
let capacity = text.capacity();
let observer = memory.clone();
drop(memory);
assert_eq!(observer.used(), capacity);
let copy = text.try_copy().unwrap();
assert_eq!(copy.as_str(&observer).unwrap(), "Дія🦀");
assert_eq!(observer.used(), capacity + copy.capacity());
drop(text);
assert_eq!(observer.used(), copy.capacity());
drop(copy);
assert_eq!(observer.used(), 0);
}
#[test]
fn refusal_preserves_text_and_does_not_hide_utf8_scan_work() {
let memory = Memory::new(Arc::new(MemoryAccount::new(4, 1024)));
let mut text = memory.string();
text.try_push('🦀').unwrap();
assert_eq!(text.try_push('!'), Err(MemoryError::LimitExceeded));
assert_eq!(text.as_str(&memory).unwrap(), "🦀");
memory
.charge_work((1024 - memory.work_used()) as u64)
.unwrap();
assert_eq!(text.as_str(&memory), Err(MemoryError::WorkExceeded));
assert_eq!(text.as_bytes(), "🦀".as_bytes());
}
#[test]
fn retained_text_cannot_pay_for_a_new_operations_validation() {
let storage = Memory::new(Arc::new(MemoryAccount::new(64, 1024)));
let execution = Memory::new(Arc::new(MemoryAccount::new(64, 0)));
let mut text = storage.string();
text.try_push_str("issuer").unwrap();
let used = storage.work_used();
assert_eq!(text.as_str(&execution), Err(MemoryError::WorkExceeded));
assert_eq!(storage.work_used(), used);
assert_eq!(storage.used(), text.capacity());
assert_eq!(execution.used(), 0);
}
}