1use super::{casper_types::U512, CallDef};
8use crate::prelude::*;
9
10#[derive(Clone, Debug, PartialEq, Eq)]
12pub enum CallstackElement {
13 Account(Address),
15 ContractCall {
17 contract_name: String,
19 address: Address,
21 call_def: CallDef
23 }
24}
25
26impl CallstackElement {
27 pub fn new_account(address: Address) -> Self {
29 Self::Account(address)
30 }
31
32 pub fn new_contract_call(contract_name: String, address: Address, call_def: CallDef) -> Self {
34 Self::ContractCall {
35 contract_name,
36 address,
37 call_def
38 }
39 }
40}
41
42impl CallstackElement {
43 pub fn address(&self) -> &Address {
45 match self {
46 CallstackElement::Account(address) => address,
47 CallstackElement::ContractCall { address, .. } => address
48 }
49 }
50}
51
52#[derive(Clone, Default)]
54pub struct Callstack {
55 elements: Vec<CallstackElement>,
56 stack_record: Vec<String>
57}
58
59impl Callstack {
60 pub fn first(&self) -> CallstackElement {
62 self.elements
63 .first()
64 .expect("Not enough elements on callstack")
65 .clone()
66 }
67
68 pub fn pop(&mut self) -> Option<CallstackElement> {
70 self.elements.pop()
71 }
72
73 pub fn push(&mut self, element: CallstackElement) {
75 self.elements.push(element);
76 }
77
78 pub fn attached_value(&self) -> U512 {
84 let ce = self
85 .elements
86 .last()
87 .expect("Not enough elements on callstack");
88 match ce {
89 CallstackElement::Account(_) => U512::zero(),
90 CallstackElement::ContractCall { call_def, .. } => call_def.amount()
91 }
92 }
93
94 pub fn attach_value(&mut self, amount: U512) {
98 if let Some(CallstackElement::ContractCall { call_def, .. }) = self.elements.last_mut() {
99 *call_def = call_def.clone().with_amount(amount);
100 }
101 }
102
103 pub fn current(&self) -> &CallstackElement {
105 self.elements
106 .last()
107 .expect("Not enough elements on callstack")
108 }
109
110 pub fn previous(&self) -> &CallstackElement {
112 self.elements
113 .get(self.elements.len() - 2)
114 .expect("Not enough elements on callstack")
115 }
116
117 pub fn size(&self) -> usize {
119 self.elements.len()
120 }
121
122 pub fn is_empty(&self) -> bool {
124 self.elements.is_empty()
125 }
126
127 pub fn record(&mut self) {
129 self.stack_record.clear();
130 for element in self.elements.iter().rev() {
131 match element {
132 CallstackElement::Account(address) => {
133 self.stack_record.push(format!(" ↳ caller: {:?}", address));
134 }
135 CallstackElement::ContractCall {
136 contract_name,
137 call_def,
138 ..
139 } => {
140 self.stack_record
141 .push(format!(" ↳ contract: {:?}", contract_name));
142 self.stack_record
143 .push(format!(" ↳ entry point: {}", call_def.entry_point()));
144 let args: Vec<String> = call_def
145 .args()
146 .named_args()
147 .map(|arg| {
148 let mut arg_json = serde_json::to_value(arg.cl_value())
149 .unwrap_or(serde_json::Value::Null);
150 arg_json.as_object_mut().unwrap().remove("bytes");
151 format!(
152 " ↳ arg: {:?} - {}",
153 arg.name(),
154 serde_json::to_string(&arg_json).unwrap_or_default()
155 )
156 })
157 .collect();
158 self.stack_record.extend(args);
159 }
160 }
161 }
162 }
163
164 pub fn record_to_string(&self) -> String {
166 self.stack_record.join("\n")
167 }
168}
169
170#[cfg(test)]
171mod tests {
172 use casper_types::{account::AccountHash, RuntimeArgs};
173
174 use super::*;
175
176 #[test]
177 fn test_first() {
178 let mut callstack = Callstack::default();
179 callstack.push(mock_account_element());
180 callstack.push(mock_contract_element());
181
182 assert_eq!(callstack.first(), mock_account_element());
183 }
184
185 #[test]
186 fn test_pop() {
187 let mut callstack = Callstack::default();
188 callstack.push(mock_account_element());
189 callstack.push(mock_contract_element());
190
191 assert_eq!(callstack.pop(), Some(mock_contract_element()));
192 }
193
194 #[test]
195 fn test_push() {
196 let mut callstack = Callstack::default();
197 callstack.push(mock_account_element());
198 callstack.push(mock_contract_element());
199
200 assert_eq!(callstack.size(), 2);
201 }
202
203 #[test]
204 fn test_attached_value() {
205 let mut callstack = Callstack::default();
206 callstack.push(mock_account_element());
207 assert_eq!(callstack.attached_value(), U512::zero());
208
209 callstack.push(mock_contract_element_with_value(U512::from(100)));
210 assert_eq!(callstack.attached_value(), U512::from(100));
211 }
212
213 #[test]
214 fn test_attach_value() {
215 let mut callstack = Callstack::default();
216 callstack.push(mock_account_element());
217 callstack.push(mock_contract_element());
218
219 callstack.attach_value(U512::from(200));
220
221 assert_eq!(
222 callstack.current(),
223 &mock_contract_element_with_value(U512::from(200))
224 );
225 }
226
227 #[test]
228 fn test_previous() {
229 let mut callstack = Callstack::default();
230 callstack.push(mock_account_element());
231 callstack.push(mock_contract_element());
232
233 assert_eq!(callstack.previous(), &mock_account_element());
234 }
235
236 #[test]
237 fn test_size() {
238 let mut callstack = Callstack::default();
239
240 callstack.push(mock_account_element());
241 callstack.push(mock_contract_element());
242
243 assert_eq!(callstack.size(), 2);
244 }
245
246 #[test]
247 fn test_is_empty() {
248 let mut callstack = Callstack::default();
249 assert!(callstack.is_empty());
250
251 callstack.push(mock_account_element());
252 assert!(!callstack.is_empty());
253 }
254
255 const PACKAGE_HASH: &str =
256 "package-7ba9daac84bebee8111c186588f21ebca35550b6cf1244e71768bd871938be6a";
257 const ACCOUNT_HASH: &str =
258 "account-hash-3b4ffcfb21411ced5fc1560c3f6ffed86f4885e5ea05cde49d90962a48a14d95";
259
260 fn mock_account_element() -> CallstackElement {
261 CallstackElement::Account(Address::Account(
262 AccountHash::from_formatted_str(ACCOUNT_HASH).unwrap()
263 ))
264 }
265
266 fn mock_contract_element() -> CallstackElement {
267 CallstackElement::new_contract_call(
268 "mock_contract".to_string(),
269 Address::new(PACKAGE_HASH).unwrap(),
270 CallDef::new("a", false, RuntimeArgs::default())
271 )
272 }
273
274 fn mock_contract_element_with_value(amount: U512) -> CallstackElement {
275 CallstackElement::new_contract_call(
276 "mock_contract".to_string(),
277 Address::new(PACKAGE_HASH).unwrap(),
278 CallDef::new("a", false, RuntimeArgs::default()).with_amount(amount)
279 )
280 }
281}