odra_core/
callstack.rs

1//! A module containing callstack-related data structures.
2//!
3//! The callstack is used to keep track of the current call context. It is used to determine
4//! the current account and contract address, the attached value, and the current entry point.
5//!
6//! The module provides building blocks for a callstack, such as `CallstackElement` and `ContractCall`.
7use super::{casper_types::U512, CallDef};
8use crate::prelude::*;
9
10/// A struct representing a callstack element.
11#[derive(Clone, Debug, PartialEq, Eq)]
12pub enum CallstackElement {
13    /// An account address.
14    Account(Address),
15    /// A contract call.
16    ContractCall {
17        /// The name of the contract.
18        contract_name: String,
19        /// The address of the contract.
20        address: Address,
21        /// The contract call definition.
22        call_def: CallDef
23    }
24}
25
26impl CallstackElement {
27    /// Creates a new element representing an account address.
28    pub fn new_account(address: Address) -> Self {
29        Self::Account(address)
30    }
31
32    /// Creates a new element representing a contract call.
33    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    /// Returns the address of the callstack element.
44    pub fn address(&self) -> &Address {
45        match self {
46            CallstackElement::Account(address) => address,
47            CallstackElement::ContractCall { address, .. } => address
48        }
49    }
50}
51
52/// A struct representing a callstack.
53#[derive(Clone, Default)]
54pub struct Callstack {
55    elements: Vec<CallstackElement>,
56    stack_record: Vec<String>
57}
58
59impl Callstack {
60    /// Returns the first (bottom most) callstack element.
61    pub fn first(&self) -> CallstackElement {
62        self.elements
63            .first()
64            .expect("Not enough elements on callstack")
65            .clone()
66    }
67
68    /// Returns the current callstack element and removes it from the callstack.
69    pub fn pop(&mut self) -> Option<CallstackElement> {
70        self.elements.pop()
71    }
72
73    /// Pushes a new callstack element onto the callstack.
74    pub fn push(&mut self, element: CallstackElement) {
75        self.elements.push(element);
76    }
77
78    /// Returns the attached value.
79    ///
80    /// If the current element is a contract call, the attached value is the amount of tokens
81    /// attached to the contract call. If the current element is an account, the attached
82    /// value is zero.
83    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    /// Attaches the given amount of tokens to the current contract call.
95    ///
96    /// If the current element is not a contract call, this method does nothing.
97    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    /// Returns the current callstack element.
104    pub fn current(&self) -> &CallstackElement {
105        self.elements
106            .last()
107            .expect("Not enough elements on callstack")
108    }
109
110    /// Returns the previous (second) callstack element.
111    pub fn previous(&self) -> &CallstackElement {
112        self.elements
113            .get(self.elements.len() - 2)
114            .expect("Not enough elements on callstack")
115    }
116
117    /// Returns the size of the callstack.
118    pub fn size(&self) -> usize {
119        self.elements.len()
120    }
121
122    /// Returns `true` if the callstack is empty.
123    pub fn is_empty(&self) -> bool {
124        self.elements.is_empty()
125    }
126
127    /// Stringifies the callstack elements and stores them in the stack record.
128    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    /// Returns the stack record as a string.
165    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}