Skip to main content

odra_core/
contract_env.rs

1use crate::args::EntrypointArgument;
2use crate::call_def::CallDef;
3use crate::casper_types::bytesrepr::{deserialize_from_slice, Bytes, FromBytes, ToBytes};
4use crate::casper_types::crypto::PublicKey;
5use crate::casper_types::{CLTyped, CLValue, BLAKE2B_DIGEST_LENGTH, U512};
6use crate::module::Revertible;
7use crate::validator::ValidatorInfo;
8pub use crate::ContractContext;
9use crate::VmError::{Serialization, TypeMismatch};
10use crate::{consts, prelude::*, utils};
11use casper_event_standard::{EventInstance, Schema, Schemas, EVENTS_SCHEMA};
12use casper_types::CLValueError;
13use rand_chacha::rand_core::{RngCore, SeedableRng};
14use rand_chacha::ChaCha8Rng;
15
16const KEY_LEN: usize = 64;
17pub(crate) type StorageKey = [u8; KEY_LEN];
18
19/// Maximum nesting depth for module paths.
20pub(crate) const MAX_PATH_LEN: usize = 8;
21
22/// Trait that needs to be implemented by all contract refs.
23pub trait ContractRef {
24    /// Creates a new instance of the Contract Ref.
25    fn new(env: Rc<ContractEnv>, address: Address) -> Self;
26    /// Returns the address of the contract.
27    fn address(&self) -> &Address;
28    /// Creates a new contract reference with attached tokens, based on the current instance.
29    ///
30    /// If there are tokens attached to the current instance, the tokens will be attached
31    /// to the next contract call.
32    fn with_tokens(&self, tokens: U512) -> Self;
33}
34
35/// Represents the environment accessible in the contract context.
36///
37/// The `ContractEnv` struct provides methods for interacting with the contract environment,
38/// such as accessing storage, calling other contracts, and handling various contract-related operations.
39///
40/// The `ContractEnv` is available for the user to use in the module code.
41#[derive(Clone)]
42pub struct ContractEnv {
43    path: [u8; MAX_PATH_LEN],
44    path_len: u8,
45    mapping_data: Vec<u8>,
46    backend: Rc<RefCell<dyn ContractContext>>
47}
48
49impl Revertible for ContractEnv {
50    fn revert<E: Into<OdraError>>(&self, e: E) -> ! {
51        self.revert(e)
52    }
53}
54
55impl ContractEnv {
56    /// Creates a new ContractEnv instance.
57    pub const fn new(backend: Rc<RefCell<dyn ContractContext>>) -> Self {
58        Self {
59            path: [0u8; MAX_PATH_LEN],
60            path_len: 0,
61            mapping_data: Vec::new(),
62            backend
63        }
64    }
65
66    /// Returns the index bytes for the current path, using the appropriate encoding.
67    ///
68    /// Two encoding modes exist to support backward compatibility:
69    ///
70    /// **Legacy encoding** (default, when all path indices fit in 4 bits):
71    /// Packs indices into a `u32` using 4-bit left shifts, identical to the original
72    /// `(parent << 4) + child` formula. Produces 4 big-endian bytes. This ensures
73    /// deployed contracts with ≤15 fields per module get the same storage keys.
74    ///
75    /// **Path encoding** (indices > 15, or V2 mode):
76    /// Emits `[0xFF, path_len, path[0], ..., path[n]]`. The `0xFF` prefix cannot
77    /// collide with legacy keys (whose first byte never exceeds `0x0F`). The
78    /// `path_len` byte makes the boundary with appended `mapping_data` unambiguous,
79    /// preventing collisions between e.g. a `Var` at a deeper path and a `Mapping`
80    /// at a shallower path with matching key bytes.
81    ///
82    /// **Why `path_len` is necessary — collision example:**
83    ///
84    /// Consider two fields whose path bytes and mapping data concatenate identically:
85    /// - Field A: `Var` at path `[3, 5]` (depth 2), no mapping data.
86    /// - Field B: `Mapping` at path `[3]` (depth 1), mapping key serializes to `[5]`.
87    ///
88    /// The final hash input is `index_bytes ++ mapping_data`.
89    ///
90    /// Without `path_len` (hypothetical `[0xFF, path..., mapping_data...]`):
91    /// - A → `[0xFF, 3, 5]`, B → `[0xFF, 3] ++ [5]` = `[0xFF, 3, 5]` — **collision!**
92    ///
93    /// With `path_len` (actual `[0xFF, path_len, path..., mapping_data...]`):
94    /// - A → `[0xFF, 2, 3, 5]`, B → `[0xFF, 1, 3] ++ [5]` = `[0xFF, 1, 3, 5]` — **distinct.**
95    pub(crate) fn index_bytes(&self) -> Vec<u8> {
96        let path = &self.path[..self.path_len as usize];
97        // Legacy: pack indices into u32 via 4-bit shifts (e.g. path [3, 15] → 0x3F).
98        // Only used when all indices fit in a nibble, preserving old storage keys.
99        if path.iter().all(|&idx| idx <= 15) {
100            let index: u32 = path.iter().fold(0u32, |acc, &idx| (acc << 4) + idx as u32);
101            index.to_be_bytes().to_vec()
102        } else {
103            // Path encoding: [0xFF, len, idx_0, idx_1, ...]. Used for fields 16+.
104            let mut bytes = Vec::with_capacity(2 + path.len());
105            bytes.push(0xFF);
106            bytes.push(self.path_len);
107            bytes.extend_from_slice(path);
108            bytes
109        }
110    }
111
112    /// Returns the current storage key for the contract environment.
113    pub(crate) fn current_key(&self) -> StorageKey {
114        let mut result = [0u8; KEY_LEN];
115        let index_bytes = self.index_bytes();
116        let mut key = Vec::with_capacity(index_bytes.len() + self.mapping_data.len());
117        key.extend_from_slice(&index_bytes);
118        key.extend_from_slice(&self.mapping_data);
119        let hashed_key = self.backend.borrow().hash(key.as_slice());
120        utils::hex_to_slice(&hashed_key, &mut result);
121        result
122    }
123
124    /// Adds the given data to the mapping data of the contract environment.
125    pub(crate) fn add_to_mapping_data(&mut self, data: &[u8]) {
126        self.mapping_data.extend_from_slice(data);
127    }
128
129    /// Returns a child contract environment with the specified index.
130    pub(crate) fn child(&self, index: u8) -> Self {
131        let mut new_path = self.path;
132        let Some(slot) = new_path.get_mut(self.path_len as usize) else {
133            self.revert(ExecutionError::PathIndexOutOfBounds)
134        };
135        *slot = index;
136
137        Self {
138            path: new_path,
139            path_len: self.path_len + 1,
140            mapping_data: self.mapping_data.clone(),
141            backend: self.backend.clone()
142        }
143    }
144
145    /// Retrieves the value associated with the given key from the contract storage.
146    ///
147    /// # Returns
148    ///
149    /// The value associated with the key, if it exists.
150    pub fn get_value<T: FromBytes>(&self, key: &[u8]) -> Option<T> {
151        self.backend
152            .borrow()
153            .get_value(key)
154            .map(|bytes| deserialize_from_slice(bytes).unwrap_or_revert(self))
155    }
156
157    /// Sets the value associated with the given key in the contract storage.
158    pub fn set_value<T: ToBytes + CLTyped>(&self, key: &[u8], value: T) {
159        let result = value.to_bytes().map_err(ExecutionError::from);
160        let bytes = result.unwrap_or_revert(self);
161        self.backend.borrow().set_value(key, bytes.into());
162    }
163
164    /// Retrieves the value associated with the given named key from the contract storage.
165    pub fn get_named_value<T: FromBytes + CLTyped, U: AsRef<str>>(&self, name: U) -> Option<T> {
166        let key = name.as_ref();
167        let bytes = self.backend.borrow().get_named_value(key);
168        bytes.map(|b| deserialize_from_slice(b).unwrap_or_revert(self))
169    }
170
171    /// Sets the value associated with the given named key in the contract storage.
172    pub fn set_named_value<T: CLTyped + ToBytes, U: AsRef<str>>(&self, name: U, value: T) {
173        let key = name.as_ref();
174        let cl_value = CLValue::from_t(value)
175            .map_err(|e| match e {
176                CLValueError::Serialization(_) => OdraError::VmError(Serialization),
177                CLValueError::Type(e) => OdraError::VmError(TypeMismatch {
178                    found: e.found,
179                    expected: e.expected
180                })
181            })
182            .unwrap_or_revert(self);
183        self.backend.borrow().set_named_value(key, cl_value);
184    }
185
186    /// Retrieves the value associated with the given named key from the named dictionary in the contract storage.
187    pub fn get_dictionary_value<T: FromBytes + CLTyped, U: AsRef<str>>(
188        &self,
189        dictionary_name: U,
190        key: &[u8]
191    ) -> Option<T> {
192        let dictionary_name = dictionary_name.as_ref();
193        let bytes = self
194            .backend
195            .borrow()
196            .get_dictionary_value(dictionary_name, key);
197        bytes.map(|b| {
198            deserialize_from_slice(b)
199                .map_err(|_| ExecutionError::Formatting)
200                .unwrap_or_revert(self)
201        })
202    }
203
204    /// Sets the value associated with the given named key in the named dictionary in the contract storage.
205    pub fn set_dictionary_value<T: CLTyped + ToBytes, U: AsRef<str>>(
206        &self,
207        dictionary_name: U,
208        key: &[u8],
209        value: T
210    ) {
211        let dictionary_name = dictionary_name.as_ref();
212        let cl_value = CLValue::from_t(value)
213            .map_err(|_| ExecutionError::Formatting)
214            .unwrap_or_revert(self);
215        self.backend
216            .borrow()
217            .set_dictionary_value(dictionary_name, key, cl_value);
218    }
219
220    /// Removes the dictionary from the contract storage.
221    pub fn remove_dictionary<U: AsRef<str>>(&self, dictionary_name: U) {
222        let dictionary_name = dictionary_name.as_ref();
223        self.backend.borrow().remove_dictionary(dictionary_name);
224    }
225
226    /// Initializes the empty dictionary with the given name.
227    pub fn init_dictionary<U: AsRef<str>>(&self, dictionary_name: U) {
228        let dictionary_name = dictionary_name.as_ref();
229        self.backend.borrow().init_dictionary(dictionary_name);
230    }
231
232    /// Returns the address of the caller of the contract.
233    pub fn caller(&self) -> Address {
234        let backend = self.backend.borrow();
235        backend.caller()
236    }
237
238    /// Calls another contract with the specified address and call definition.
239    ///
240    /// # Returns
241    ///
242    /// The result of the contract call. If any error occurs during the call, the contract will revert.
243    pub fn call_contract<T: FromBytes>(&self, address: Address, call: CallDef) -> T {
244        let backend = self.backend.borrow();
245        let bytes = backend.call_contract(address, call);
246        deserialize_from_slice(bytes).unwrap_or_revert(self)
247    }
248
249    /// Returns the address of the current contract.
250    pub fn self_address(&self) -> Address {
251        let backend = self.backend.borrow();
252        backend.self_address()
253    }
254
255    /// Transfers tokens to the specified address.
256    pub fn transfer_tokens(&self, to: &Address, amount: &U512) {
257        let backend = self.backend.borrow();
258        backend.transfer_tokens(to, amount)
259    }
260
261    /// Returns the current block time in milliseconds.
262    pub fn get_block_time(&self) -> u64 {
263        let backend = self.backend.borrow();
264        backend.get_block_time()
265    }
266
267    /// Returns the current block time in milliseconds.
268    pub fn get_block_time_millis(&self) -> u64 {
269        let backend = self.backend.borrow();
270        backend.get_block_time()
271    }
272
273    /// Returns the current block time in seconds.
274    pub fn get_block_time_secs(&self) -> u64 {
275        let backend = self.backend.borrow();
276        backend.get_block_time().checked_div(1000).unwrap()
277    }
278
279    /// Returns the value attached to the contract call.
280    pub fn attached_value(&self) -> U512 {
281        let backend = self.backend.borrow();
282        backend.attached_value()
283    }
284
285    /// Returns the CSPR balance of the current contract.
286    pub fn self_balance(&self) -> U512 {
287        let backend = self.backend.borrow();
288        backend.self_balance()
289    }
290
291    /// Reverts the contract execution with the specified error.
292    pub fn revert<E: Into<OdraError>>(&self, error: E) -> ! {
293        let backend = self.backend.borrow();
294        backend.revert(error.into())
295    }
296
297    /// Emits an event with the specified data.
298    pub fn emit_event<T: ToBytes + EventInstance>(&self, event: T) {
299        let backend = self.backend.borrow();
300        let result = event.to_bytes().map_err(ExecutionError::from);
301        let bytes = result.unwrap_or_revert(self);
302        backend.emit_event(&bytes.into())
303    }
304
305    /// Emits an event with the specified data using the native mechanism.
306    pub fn emit_native_event<T: ToBytes + EventInstance>(&self, event: T) {
307        let backend = self.backend.borrow();
308        let result = event.to_bytes().map_err(ExecutionError::from);
309        let bytes = result.unwrap_or_revert(self);
310        backend.emit_native_event(&bytes.into())
311    }
312
313    /// Verifies the signature of a message using the specified signature, public key, and message.
314    ///
315    /// # Arguments
316    ///
317    /// * `message` - The message to verify.
318    /// * `signature` - The signature to verify.
319    /// * `public_key` - The public key to use for verification.
320    ///
321    /// # Returns
322    ///
323    /// `true` if the signature is valid, `false` otherwise.
324    pub fn verify_signature(
325        &self,
326        message: &Bytes,
327        signature: &Bytes,
328        public_key: &PublicKey
329    ) -> bool {
330        let (signature, _) = casper_types::crypto::Signature::from_bytes(signature.as_slice())
331            .unwrap_or_else(|_| self.revert(ExecutionError::CouldNotDeserializeSignature));
332        self.backend
333            .borrow()
334            .verify_signature(message, &signature, public_key)
335    }
336
337    /// Hashes the specified value.
338    ///
339    /// # Returns
340    ///
341    /// The hash value as a 32-byte array.
342    pub fn hash<T: AsRef<[u8]>>(&self, value: T) -> [u8; BLAKE2B_DIGEST_LENGTH] {
343        self.backend.borrow().hash(value.as_ref())
344    }
345
346    /// Delegate tokens to a validator
347    ///
348    /// # Arguments
349    ///
350    /// * `validator` - The validator to delegate to
351    /// * `amount` - The amount of tokens to delegate
352    pub fn delegate(&self, validator: PublicKey, amount: U512) {
353        self.backend.borrow().delegate(validator, amount)
354    }
355
356    /// Undelegate tokens from a validator
357    ///
358    /// # Arguments
359    ///
360    /// * `validator` - The validator to undelegate from
361    /// * `amount` - The amount of tokens to undelegate
362    pub fn undelegate(&self, validator: PublicKey, amount: U512) {
363        self.backend.borrow().undelegate(validator, amount)
364    }
365
366    /// Returns the amount of tokens delegated to a validator
367    ///
368    /// # Arguments
369    ///
370    /// * `validator` - The validator to get the delegated amount from
371    ///
372    /// # Returns
373    ///
374    /// The amount of tokens delegated to the validator
375    pub fn delegated_amount(&self, validator: PublicKey) -> U512 {
376        self.backend.borrow().delegated_amount(validator)
377    }
378
379    /// Returns information about the validator
380    ///
381    /// # Arguments
382    /// - validator - The validator to query
383    ///
384    /// # Returns
385    /// Option<ValidatorBid>
386    pub fn get_validator_info(&self, validator: PublicKey) -> Option<ValidatorInfo> {
387        self.backend.borrow().get_validator_info(validator)
388    }
389
390    /// Returns a vector of pseudorandom bytes of the specified size.
391    /// There is no guarantee that the returned bytes are in any way cryptographically secure.
392    pub fn pseudorandom_bytes(&self, size: usize) -> Vec<u8> {
393        let seed_bytes = self.backend.borrow().pseudorandom_bytes();
394
395        if size <= seed_bytes.len() {
396            return seed_bytes[..size].to_vec();
397        }
398
399        // Use initial random bytes as seed for ChaCha8
400        let mut result = seed_bytes.to_vec();
401        let mut rng = ChaCha8Rng::from_seed(seed_bytes);
402        let additional_bytes = size - result.len();
403        let mut extra = vec![0u8; additional_bytes];
404        rng.fill_bytes(&mut extra);
405        result.extend_from_slice(&extra);
406
407        result
408    }
409
410    /// Returns a pseudorandom integer.
411    pub fn pseudorandom_number(&self, high: U512) -> U512 {
412        let seed_bytes = self.backend.borrow().pseudorandom_bytes();
413        let mut rng = ChaCha8Rng::from_seed(seed_bytes);
414        let bits = high.bits();
415        let bytes_len = bits.div_ceil(8);
416        let max = U512::from(1u64) << bits; // 2^bits
417        let limit = max - (max % high);
418        loop {
419            let mut bytes = vec![0u8; bytes_len];
420            rng.fill_bytes(&mut bytes);
421            let candidate = U512::from_big_endian(&bytes);
422
423            if candidate < limit {
424                return candidate % high;
425            }
426            // else: reject and try again
427        }
428    }
429}
430
431/// Represents the environment accessible in the contract execution context.
432///
433/// `ExecutionEnv` provides pre- and post-execution methods for the contract, such as performing non-reentrant checks
434/// and handling the attached value.
435pub struct ExecutionEnv {
436    env: Rc<ContractEnv>
437}
438
439impl Revertible for ExecutionEnv {
440    fn revert<E: Into<OdraError>>(&self, e: E) -> ! {
441        self.env.revert(e)
442    }
443}
444
445impl ExecutionEnv {
446    /// Creates a new ExecutionEnv instance.
447    pub fn new(env: Rc<ContractEnv>) -> Self {
448        Self { env }
449    }
450
451    /// Performs non-reentrant checks before executing a function.
452    pub fn non_reentrant_before(&self) {
453        // Check if reentrancy guard is set to true
454        let status: bool = self
455            .env
456            .get_value(consts::REENTRANCY_GUARD.as_slice())
457            .unwrap_or_default();
458        if status {
459            // Revert execution with ReentrantCall error
460            self.env.revert(ExecutionError::ReentrantCall);
461        }
462        // Set reentrancy guard to true
463        self.env
464            .set_value(consts::REENTRANCY_GUARD.as_slice(), true);
465    }
466
467    /// Resets the reentrancy guard after executing a function.
468    pub fn non_reentrant_after(&self) {
469        // Set reentrancy guard to false
470        self.env
471            .set_value(consts::REENTRANCY_GUARD.as_slice(), false);
472    }
473
474    /// Handles the attached value in the execution environment.
475    pub fn handle_attached_value(&self) {
476        self.env.backend.borrow().handle_attached_value();
477    }
478
479    /// Clears the attached value in the execution environment.
480    pub fn clear_attached_value(&self) {
481        self.env.backend.borrow().clear_attached_value();
482    }
483
484    /// Retrieves the value of a named argument from the execution environment.
485    ///
486    /// # Returns
487    ///
488    /// The deserialized value of the named argument. If the argument does not exist or deserialization fails,
489    /// the contract will revert.
490    pub fn get_named_arg<T: FromBytes + EntrypointArgument>(&self, name: &str) -> T {
491        if T::is_required() {
492            let result = self.env.backend.borrow().get_named_arg_bytes(name);
493            match result {
494                Ok(bytes) => deserialize_from_slice(bytes).unwrap_or_revert(self),
495                Err(err) => self.env.revert(err)
496            }
497        } else {
498            let bytes = self.env.backend.borrow().get_opt_named_arg_bytes(name);
499            let result = bytes.map(|bytes| deserialize_from_slice(bytes).unwrap_or_revert(self));
500            T::unwrap(result, &self.env)
501        }
502    }
503
504    /// Migrates the schemas in the contract storage to the new schemas.
505    pub fn migrate_schemas(&self, new_schemas: BTreeMap<String, Schema>) {
506        let mut old_schemas: Schemas = self.env.get_named_value(EVENTS_SCHEMA).unwrap_or_default();
507
508        for (name, new_schema) in new_schemas.iter() {
509            match old_schemas.0.get(name) {
510                // If the schema is not present in the old schemas, we add it.
511                None => {
512                    old_schemas.0.insert(name.clone(), new_schema.clone());
513                }
514                // If an existing schema is different from the new one, we revert.
515                Some(old_schema) => {
516                    if old_schema != new_schema {
517                        self.env.revert(ExecutionError::SchemaMismatch);
518                    }
519                }
520            }
521        }
522
523        // Store the updated schemas back to the contract storage.
524        self.env.set_named_value(EVENTS_SCHEMA, old_schemas);
525    }
526
527    /// Emits an event with the specified data.
528    pub fn emit_event<T: ToBytes + EventInstance>(&self, event: T) {
529        self.env.emit_event(event);
530    }
531}
532
533#[cfg(test)]
534mod tests {
535    use super::*;
536    use crate::contract_context::MockContractContext;
537
538    fn make_env() -> ContractEnv {
539        let mut ctx = MockContractContext::new();
540        ctx.expect_hash().returning(|input| {
541            let mut result = [0u8; 32];
542            for (i, byte) in input.iter().enumerate() {
543                if i < 32 {
544                    result[i] = *byte;
545                }
546            }
547            result
548        });
549        ContractEnv::new(Rc::new(RefCell::new(ctx)))
550    }
551
552    fn legacy_u32_for_path(path: &[u8]) -> u32 {
553        path.iter().fold(0u32, |acc, &idx| (acc << 4) + idx as u32)
554    }
555
556    #[test]
557    fn encoding_matches_old_u32_formula() {
558        let env = make_env();
559        let child = env.child(3);
560        assert_eq!(
561            child.index_bytes(),
562            legacy_u32_for_path(&[3]).to_be_bytes().to_vec()
563        );
564
565        let grandchild = child.child(15);
566        assert_eq!(
567            grandchild.index_bytes(),
568            legacy_u32_for_path(&[3, 15]).to_be_bytes().to_vec()
569        );
570
571        let deep = env.child(1).child(2).child(3).child(4);
572        assert_eq!(
573            deep.index_bytes(),
574            legacy_u32_for_path(&[1, 2, 3, 4]).to_be_bytes().to_vec()
575        );
576    }
577
578    #[test]
579    fn path_encoding_used_for_indices_above_15() {
580        let env = make_env();
581        let child = env.child(3).child(16);
582        let bytes = child.index_bytes();
583        assert_eq!(bytes[0], 0xFF);
584        assert_eq!(bytes[1], 2);
585        assert_eq!(bytes[2], 3);
586        assert_eq!(bytes[3], 16);
587    }
588
589    #[test]
590    fn no_collision_between_var_and_mapping() {
591        let env = make_env();
592
593        let var_key = env.child(3).child(16).current_key();
594        let mut map_env = env.child(3);
595        map_env.add_to_mapping_data(&[16]);
596        let map_key = map_env.current_key();
597        assert_ne!(var_key, map_key);
598
599        let var_key2 = env.child(3).child(1).current_key();
600        let mut map_env2 = env.child(3);
601        map_env2.add_to_mapping_data(&[0xFF, 2, 3, 1]);
602        let map_key2 = map_env2.current_key();
603        assert_ne!(var_key2, map_key2);
604    }
605
606    #[test]
607    fn no_collision_between_small_and_path_encoding() {
608        let env = make_env();
609        let small_key = env.child(1).child(2).current_key();
610        let path_key = env.child(1).child(20).current_key();
611        assert_ne!(small_key, path_key);
612    }
613}