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 address of the contract.
18        address: Address,
19        /// The contract call definition.
20        call_def: CallDef
21    }
22}
23
24impl CallstackElement {
25    /// Creates a new element representing an account address.
26    pub fn new_account(address: Address) -> Self {
27        Self::Account(address)
28    }
29
30    /// Creates a new element representing a contract call.
31    pub fn new_contract_call(address: Address, call_def: CallDef) -> Self {
32        Self::ContractCall { address, call_def }
33    }
34}
35
36impl CallstackElement {
37    /// Returns the address of the callstack element.
38    pub fn address(&self) -> &Address {
39        match self {
40            CallstackElement::Account(address) => address,
41            CallstackElement::ContractCall { address, .. } => address
42        }
43    }
44}
45
46/// A struct representing a callstack.
47#[derive(Clone, Default)]
48pub struct Callstack(Vec<CallstackElement>);
49
50impl Callstack {
51    /// Returns the first (bottom most) callstack element.
52    pub fn first(&self) -> CallstackElement {
53        self.0
54            .first()
55            .expect("Not enough elements on callstack")
56            .clone()
57    }
58
59    /// Returns the current callstack element and removes it from the callstack.
60    pub fn pop(&mut self) -> Option<CallstackElement> {
61        self.0.pop()
62    }
63
64    /// Pushes a new callstack element onto the callstack.
65    pub fn push(&mut self, element: CallstackElement) {
66        self.0.push(element);
67    }
68
69    /// Returns the attached value.
70    ///
71    /// If the current element is a contract call, the attached value is the amount of tokens
72    /// attached to the contract call. If the current element is an account, the attached
73    /// value is zero.
74    pub fn attached_value(&self) -> U512 {
75        let ce = self.0.last().expect("Not enough elements on callstack");
76        match ce {
77            CallstackElement::Account(_) => U512::zero(),
78            CallstackElement::ContractCall { call_def, .. } => call_def.amount()
79        }
80    }
81
82    /// Attaches the given amount of tokens to the current contract call.
83    ///
84    /// If the current element is not a contract call, this method does nothing.
85    pub fn attach_value(&mut self, amount: U512) {
86        if let Some(CallstackElement::ContractCall { call_def, .. }) = self.0.last_mut() {
87            *call_def = call_def.clone().with_amount(amount);
88        }
89    }
90
91    /// Returns the current callstack element.
92    pub fn current(&self) -> &CallstackElement {
93        self.0.last().expect("Not enough elements on callstack")
94    }
95
96    /// Returns the previous (second) callstack element.
97    pub fn previous(&self) -> &CallstackElement {
98        self.0
99            .get(self.0.len() - 2)
100            .expect("Not enough elements on callstack")
101    }
102
103    /// Returns the size of the callstack.
104    pub fn size(&self) -> usize {
105        self.0.len()
106    }
107
108    /// Returns `true` if the callstack is empty.
109    pub fn is_empty(&self) -> bool {
110        self.0.is_empty()
111    }
112}
113
114#[cfg(test)]
115mod tests {
116    use casper_types::{account::AccountHash, RuntimeArgs};
117
118    use super::*;
119
120    #[test]
121    fn test_first() {
122        let mut callstack = Callstack::default();
123        callstack.push(mock_account_element());
124        callstack.push(mock_contract_element());
125
126        assert_eq!(callstack.first(), mock_account_element());
127    }
128
129    #[test]
130    fn test_pop() {
131        let mut callstack = Callstack::default();
132        callstack.push(mock_account_element());
133        callstack.push(mock_contract_element());
134
135        assert_eq!(callstack.pop(), Some(mock_contract_element()));
136    }
137
138    #[test]
139    fn test_push() {
140        let mut callstack = Callstack::default();
141        callstack.push(mock_account_element());
142        callstack.push(mock_contract_element());
143
144        assert_eq!(callstack.size(), 2);
145    }
146
147    #[test]
148    fn test_attached_value() {
149        let mut callstack = Callstack::default();
150        callstack.push(mock_account_element());
151        assert_eq!(callstack.attached_value(), U512::zero());
152
153        callstack.push(mock_contract_element_with_value(U512::from(100)));
154        assert_eq!(callstack.attached_value(), U512::from(100));
155    }
156
157    #[test]
158    fn test_attach_value() {
159        let mut callstack = Callstack::default();
160        callstack.push(mock_account_element());
161        callstack.push(mock_contract_element());
162
163        callstack.attach_value(U512::from(200));
164
165        assert_eq!(
166            callstack.current(),
167            &mock_contract_element_with_value(U512::from(200))
168        );
169    }
170
171    #[test]
172    fn test_previous() {
173        let mut callstack = Callstack::default();
174        callstack.push(mock_account_element());
175        callstack.push(mock_contract_element());
176
177        assert_eq!(callstack.previous(), &mock_account_element());
178    }
179
180    #[test]
181    fn test_size() {
182        let mut callstack = Callstack::default();
183
184        callstack.push(mock_account_element());
185        callstack.push(mock_contract_element());
186
187        assert_eq!(callstack.size(), 2);
188    }
189
190    #[test]
191    fn test_is_empty() {
192        let mut callstack = Callstack::default();
193        assert!(callstack.is_empty());
194
195        callstack.push(mock_account_element());
196        assert!(!callstack.is_empty());
197    }
198
199    const PACKAGE_HASH: &str =
200        "package-7ba9daac84bebee8111c186588f21ebca35550b6cf1244e71768bd871938be6a";
201    const ACCOUNT_HASH: &str =
202        "account-hash-3b4ffcfb21411ced5fc1560c3f6ffed86f4885e5ea05cde49d90962a48a14d95";
203
204    fn mock_account_element() -> CallstackElement {
205        CallstackElement::Account(Address::Account(
206            AccountHash::from_formatted_str(ACCOUNT_HASH).unwrap()
207        ))
208    }
209
210    fn mock_contract_element() -> CallstackElement {
211        CallstackElement::new_contract_call(
212            Address::new(PACKAGE_HASH).unwrap(),
213            CallDef::new("a", false, RuntimeArgs::default())
214        )
215    }
216
217    fn mock_contract_element_with_value(amount: U512) -> CallstackElement {
218        CallstackElement::new_contract_call(
219            Address::new(PACKAGE_HASH).unwrap(),
220            CallDef::new("a", false, RuntimeArgs::default()).with_amount(amount)
221        )
222    }
223}