casper_execution_engine/runtime/
stack.rs

1//! Runtime stacks.
2use casper_types::{account::AccountHash, system::Caller};
3
4/// A runtime stack frame.
5///
6/// Currently it aliases to a [`Caller`].
7///
8/// NOTE: Once we need to add more data to a stack frame we should make this a newtype, rather than
9/// change [`Caller`].
10pub type RuntimeStackFrame = Caller;
11
12/// The runtime stack.
13#[derive(Clone, Debug)]
14pub struct RuntimeStack {
15    frames: Vec<RuntimeStackFrame>,
16    max_height: usize,
17}
18
19/// Error returned on an attempt to pop off an empty stack.
20#[cfg(test)]
21#[derive(Debug)]
22struct RuntimeStackUnderflow;
23
24/// Error returned on an attempt to push to a stack already at the maximum height.
25#[derive(Debug)]
26pub struct RuntimeStackOverflow;
27
28impl RuntimeStack {
29    /// Creates an empty stack.
30    pub fn new(max_height: usize) -> Self {
31        Self {
32            frames: Vec::with_capacity(max_height),
33            max_height,
34        }
35    }
36
37    /// Creates a stack with one entry.
38    pub fn new_with_frame(max_height: usize, frame: RuntimeStackFrame) -> Self {
39        let mut frames = Vec::with_capacity(max_height);
40        frames.push(frame);
41        Self { frames, max_height }
42    }
43
44    /// Is the stack empty?
45    pub fn is_empty(&self) -> bool {
46        self.frames.is_empty()
47    }
48
49    /// The height of the stack.
50    pub fn len(&self) -> usize {
51        self.frames.len()
52    }
53
54    /// The current stack frame.
55    pub fn current_frame(&self) -> Option<&RuntimeStackFrame> {
56        self.frames.last()
57    }
58
59    /// The previous stack frame.
60    pub fn previous_frame(&self) -> Option<&RuntimeStackFrame> {
61        self.frames.iter().nth_back(1)
62    }
63
64    /// The first stack frame.
65    pub fn first_frame(&self) -> Option<&RuntimeStackFrame> {
66        self.frames.first()
67    }
68
69    /// Pops the current frame from the stack.
70    #[cfg(test)]
71    fn pop(&mut self) -> Result<(), RuntimeStackUnderflow> {
72        self.frames.pop().ok_or(RuntimeStackUnderflow)?;
73        Ok(())
74    }
75
76    /// Pushes a frame onto the stack.
77    pub fn push(&mut self, frame: RuntimeStackFrame) -> Result<(), RuntimeStackOverflow> {
78        if self.len() < self.max_height {
79            self.frames.push(frame);
80            Ok(())
81        } else {
82            Err(RuntimeStackOverflow)
83        }
84    }
85
86    // It is here for backwards compatibility only.
87    /// A view of the stack in the previous stack format.
88    pub fn call_stack_elements(&self) -> &Vec<Caller> {
89        &self.frames
90    }
91
92    /// Returns a stack with exactly one session element with the associated account hash.
93    pub fn from_account_hash(account_hash: AccountHash, max_height: usize) -> Self {
94        RuntimeStack {
95            frames: vec![Caller::initiator(account_hash)],
96            max_height,
97        }
98    }
99}
100
101#[cfg(test)]
102mod test {
103    use core::convert::TryInto;
104
105    use casper_types::account::{AccountHash, ACCOUNT_HASH_LENGTH};
106
107    use super::*;
108
109    fn nth_frame(n: usize) -> Caller {
110        let mut bytes = [0_u8; ACCOUNT_HASH_LENGTH];
111        let n: u32 = n.try_into().unwrap();
112        bytes[0..4].copy_from_slice(&n.to_le_bytes());
113        Caller::initiator(AccountHash::new(bytes))
114    }
115
116    #[allow(clippy::redundant_clone)]
117    #[test]
118    fn stack_should_respect_max_height_after_clone() {
119        const MAX_HEIGHT: usize = 3;
120        let mut stack = RuntimeStack::new(MAX_HEIGHT);
121        stack.push(nth_frame(1)).unwrap();
122
123        let mut stack2 = stack.clone();
124        stack2.push(nth_frame(2)).unwrap();
125        stack2.push(nth_frame(3)).unwrap();
126        stack2.push(nth_frame(4)).unwrap_err();
127        assert_eq!(stack2.len(), MAX_HEIGHT);
128    }
129
130    #[test]
131    fn stack_should_work_as_expected() {
132        const MAX_HEIGHT: usize = 6;
133
134        let mut stack = RuntimeStack::new(MAX_HEIGHT);
135        assert!(stack.is_empty());
136        assert_eq!(stack.len(), 0);
137        assert_eq!(stack.current_frame(), None);
138        assert_eq!(stack.previous_frame(), None);
139        assert_eq!(stack.first_frame(), None);
140
141        stack.push(nth_frame(0)).unwrap();
142        assert!(!stack.is_empty());
143        assert_eq!(stack.len(), 1);
144        assert_eq!(stack.current_frame(), Some(&nth_frame(0)));
145        assert_eq!(stack.previous_frame(), None);
146        assert_eq!(stack.first_frame(), Some(&nth_frame(0)));
147
148        let mut n: usize = 1;
149        while stack.push(nth_frame(n)).is_ok() {
150            n += 1;
151            assert!(!stack.is_empty());
152            assert_eq!(stack.len(), n);
153            assert_eq!(stack.current_frame(), Some(&nth_frame(n - 1)));
154            assert_eq!(stack.previous_frame(), Some(&nth_frame(n - 2)));
155            assert_eq!(stack.first_frame(), Some(&nth_frame(0)));
156        }
157        assert!(!stack.is_empty());
158        assert_eq!(stack.len(), MAX_HEIGHT);
159        assert_eq!(stack.current_frame(), Some(&nth_frame(MAX_HEIGHT - 1)));
160        assert_eq!(stack.previous_frame(), Some(&nth_frame(MAX_HEIGHT - 2)));
161        assert_eq!(stack.first_frame(), Some(&nth_frame(0)));
162
163        while stack.len() >= 3 {
164            stack.pop().unwrap();
165            n = n.checked_sub(1).unwrap();
166            assert!(!stack.is_empty());
167            assert_eq!(stack.len(), n);
168            assert_eq!(stack.current_frame(), Some(&nth_frame(n - 1)));
169            assert_eq!(stack.previous_frame(), Some(&nth_frame(n - 2)));
170            assert_eq!(stack.first_frame(), Some(&nth_frame(0)));
171        }
172
173        stack.pop().unwrap();
174        assert!(!stack.is_empty());
175        assert_eq!(stack.len(), 1);
176        assert_eq!(stack.current_frame(), Some(&nth_frame(0)));
177        assert_eq!(stack.previous_frame(), None);
178        assert_eq!(stack.first_frame(), Some(&nth_frame(0)));
179
180        stack.pop().unwrap();
181        assert!(stack.is_empty());
182        assert_eq!(stack.len(), 0);
183        assert_eq!(stack.current_frame(), None);
184        assert_eq!(stack.previous_frame(), None);
185        assert_eq!(stack.first_frame(), None);
186
187        assert!(stack.pop().is_err());
188    }
189}