Skip to main content

move_neovm/
runtime.rs

1//! Move runtime support for NeoVM
2//!
3//! This module provides runtime helpers that emulate Move-specific
4//! semantics on NeoVM, particularly around resource types.
5
6use std::collections::HashMap;
7
8/// Resource ownership tracker
9///
10/// Move's linear type system requires that resources cannot be copied
11/// or implicitly dropped. This tracker enforces those semantics at runtime.
12#[derive(Debug, Default)]
13pub struct ResourceTracker {
14    /// Map from (address, type) -> exists
15    resources: HashMap<(Vec<u8>, String), bool>,
16}
17
18impl ResourceTracker {
19    pub fn new() -> Self {
20        Self::default()
21    }
22
23    /// Check if a resource exists at an address
24    pub fn exists(&self, address: &[u8], type_name: &str) -> bool {
25        self.resources
26            .get(&(address.to_vec(), type_name.to_string()))
27            .copied()
28            .unwrap_or(false)
29    }
30
31    /// Move a resource to an address (move_to)
32    ///
33    /// Fails if resource already exists at that address
34    pub fn move_to(&mut self, address: &[u8], type_name: &str) -> Result<(), ResourceError> {
35        let key = (address.to_vec(), type_name.to_string());
36        if self.resources.get(&key).copied().unwrap_or(false) {
37            return Err(ResourceError::AlreadyExists {
38                type_name: type_name.to_string(),
39            });
40        }
41        self.resources.insert(key, true);
42        Ok(())
43    }
44
45    /// Move a resource from an address (move_from)
46    ///
47    /// Fails if resource does not exist at that address
48    pub fn move_from(&mut self, address: &[u8], type_name: &str) -> Result<(), ResourceError> {
49        let key = (address.to_vec(), type_name.to_string());
50        if !self.resources.get(&key).copied().unwrap_or(false) {
51            return Err(ResourceError::NotFound {
52                type_name: type_name.to_string(),
53            });
54        }
55        self.resources.insert(key, false);
56        Ok(())
57    }
58
59    /// Borrow a resource (creates a reference)
60    pub fn borrow(&self, address: &[u8], type_name: &str) -> Result<(), ResourceError> {
61        if !self.exists(address, type_name) {
62            return Err(ResourceError::NotFound {
63                type_name: type_name.to_string(),
64            });
65        }
66        Ok(())
67    }
68}
69
70/// Resource operation errors
71#[derive(Debug, Clone, thiserror::Error)]
72pub enum ResourceError {
73    #[error("resource '{type_name}' already exists at address")]
74    AlreadyExists { type_name: String },
75
76    #[error("resource '{type_name}' not found at address")]
77    NotFound { type_name: String },
78
79    #[error("resource '{type_name}' cannot be copied")]
80    CannotCopy { type_name: String },
81
82    #[error("resource '{type_name}' cannot be dropped")]
83    CannotDrop { type_name: String },
84}
85
86/// Generate NeoVM helper code for resource tracking
87///
88/// This would be injected into the compiled contract to track resources.
89pub fn generate_resource_tracker_code() -> Vec<u8> {
90    // Placeholder - would generate NeoVM bytecode for:
91    // 1. Storage-based resource existence tracking
92    // 2. Runtime checks before move_to/move_from
93    // 3. Error handling for resource violations
94    Vec::new()
95}
96
97/// Map Move signer type to Neo CheckWitness
98///
99/// In Move, `signer` represents the transaction sender.
100/// In Neo, this maps to CheckWitness verification.
101pub fn signer_to_checkwitness() -> &'static str {
102    "System.Runtime.CheckWitness"
103}
104
105/// Prefix byte for resource storage keys
106const RESOURCE_PREFIX: u8 = b'R';
107
108/// Separator byte between address and type name in storage keys
109const RESOURCE_SEPARATOR: u8 = b':';
110
111/// Map Move global storage to Neo contract storage
112///
113/// Move: `borrow_global<T>`(address)
114/// Neo: Storage.Get(prefix + address + type_hash)
115pub fn global_storage_key(address: &[u8], type_name: &str) -> Vec<u8> {
116    let mut key = Vec::with_capacity(address.len() + type_name.len() + 2);
117    key.push(RESOURCE_PREFIX);
118    key.extend_from_slice(address);
119    key.push(RESOURCE_SEPARATOR);
120    key.extend_from_slice(type_name.as_bytes());
121    key
122}
123
124#[cfg(test)]
125mod tests {
126    use super::*;
127
128    #[test]
129    fn test_resource_lifecycle() {
130        let mut tracker = ResourceTracker::new();
131        let addr = b"test_address";
132        let type_name = "0x1::Coin::Coin";
133
134        // Initially doesn't exist
135        assert!(!tracker.exists(addr, type_name));
136
137        // Move to address
138        tracker.move_to(addr, type_name).unwrap();
139        assert!(tracker.exists(addr, type_name));
140
141        // Can't move to same address again
142        assert!(tracker.move_to(addr, type_name).is_err());
143
144        // Move from address
145        tracker.move_from(addr, type_name).unwrap();
146        assert!(!tracker.exists(addr, type_name));
147
148        // Can't move from non-existent
149        assert!(tracker.move_from(addr, type_name).is_err());
150    }
151}