1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
//! External dependencies of the near-vm-logic.

use near_primitives::hash::CryptoHash;
use near_primitives::types::TrieNodesCount;
use near_primitives_core::types::{AccountId, Balance};
use near_vm_errors::VMLogicError;

use std::borrow::Cow;

/// Representation of the address slice of guest memory.
#[derive(Clone, Copy)]
pub struct MemSlice {
    /// Offset within guest memory at which the slice starts.
    pub ptr: u64,
    /// Length of the slice.
    pub len: u64,
}

impl MemSlice {
    #[inline]
    pub fn len<T: TryFrom<u64>>(&self) -> Result<T, ()> {
        T::try_from(self.len).map_err(|_| ())
    }

    #[inline]
    pub fn end<T: TryFrom<u64>>(&self) -> Result<T, ()> {
        T::try_from(self.ptr.checked_add(self.len).ok_or(())?).map_err(|_| ())
    }

    #[inline]
    pub fn range<T: TryFrom<u64>>(&self) -> Result<std::ops::Range<T>, ()> {
        let end = self.end()?;
        let start = T::try_from(self.ptr).map_err(|_| ())?;
        Ok(start..end)
    }
}

/// An abstraction over the memory of the smart contract.
pub trait MemoryLike {
    /// Returns success if the memory interval is completely inside smart
    /// contract’s memory.
    ///
    /// You often don’t need to use this method since other methods will perform
    /// the check, however it may be necessary to prevent potential denial of
    /// service attacks.  See [`Self::read_memory`] for description.
    fn fits_memory(&self, slice: MemSlice) -> Result<(), ()>;

    /// Returns view of the content of the given memory interval.
    ///
    /// Not all implementations support borrowing the memory directly.  In those
    /// cases, the data is copied into a vector.
    fn view_memory(&self, slice: MemSlice) -> Result<Cow<[u8]>, ()>;

    /// Reads the content of the given memory interval.
    ///
    /// Returns error if the memory interval isn’t completely inside the smart
    /// contract memory.
    ///
    /// # Potential denial of service
    ///
    /// Note that improper use of this function may lead to denial of service
    /// attacks.  For example, consider the following function:
    ///
    /// ```
    /// # use near_vm_logic::{MemoryLike, MemSlice};
    ///
    /// fn read_vec(mem: &dyn MemoryLike, slice: MemSlice) -> Result<Vec<u8>, ()> {
    ///     let mut vec = vec![0; slice.len()?];
    ///     mem.read_memory(slice.ptr, &mut vec[..])?;
    ///     Ok(vec)
    /// }
    /// ```
    ///
    /// If attacker controls length argument, it may cause attempt at allocation
    /// of arbitrarily-large buffer and crash the program.  In situations like
    /// this, it’s necessary to use [`Self::fits_memory`] method to verify that
    /// the length is valid.  For example:
    ///
    /// ```
    /// # use near_vm_logic::{MemoryLike, MemSlice};
    ///
    /// fn read_vec(mem: &dyn MemoryLike, slice: MemSlice) -> Result<Vec<u8>, ()> {
    ///     mem.fits_memory(slice)?;
    ///     let mut vec = vec![0; slice.len()?];
    ///     mem.read_memory(slice.ptr, &mut vec[..])?;
    ///     Ok(vec)
    /// }
    /// ```
    fn read_memory(&self, offset: u64, buffer: &mut [u8]) -> Result<(), ()>;

    /// Writes the buffer into the smart contract memory.
    ///
    /// Returns error if the memory interval isn’t completely inside the smart
    /// contract memory.
    fn write_memory(&mut self, offset: u64, buffer: &[u8]) -> Result<(), ()>;
}

/// This enum represents if a storage_get call will be performed through flat storage or trie
pub enum StorageGetMode {
    FlatStorage,
    Trie,
}

pub type Result<T, E = VMLogicError> = ::std::result::Result<T, E>;

/// Logical pointer to a value in storage.
/// Allows getting value length before getting the value itself. This is needed so that runtime
/// can charge gas before accessing a potentially large value.
pub trait ValuePtr {
    /// Returns the length of the value
    fn len(&self) -> u32;

    /// Dereferences the pointer.
    /// Takes a box because currently runtime code uses dynamic dispatch.
    /// # Errors
    /// StorageError if reading from storage fails
    fn deref(&self) -> Result<Vec<u8>>;
}

/// An external blockchain interface for the Runtime logic
pub trait External {
    /// Write `value` to the `key` of the storage trie associated with the current account.
    ///
    /// # Example
    ///
    /// ```
    /// # use near_vm_logic::mocks::mock_external::MockedExternal;
    /// # use near_vm_logic::External;
    ///
    /// # let mut external = MockedExternal::new();
    /// assert_eq!(external.storage_set(b"key42", b"value1337"), Ok(()));
    /// // Should return an old value if the key exists
    /// assert_eq!(external.storage_set(b"key42", b"new_value"), Ok(()));
    /// ```
    fn storage_set(&mut self, key: &[u8], value: &[u8]) -> Result<()>;

    /// Read `key` from the storage trie associated with the current account.
    ///
    /// # Arguments
    ///
    /// * `key` - the key to read
    ///
    /// * `mode`- whether the lookup will be performed through flat storage or trie
    /// # Errors
    ///
    /// This function could return [`near_vm_errors::VMRunnerError::ExternalError`].
    ///
    /// # Example
    /// ```
    /// # use near_vm_logic::mocks::mock_external::MockedExternal;
    /// # use near_vm_logic::{External, StorageGetMode, ValuePtr};
    ///
    /// # let mut external = MockedExternal::new();
    /// external.storage_set(b"key42", b"value1337").unwrap();
    /// assert_eq!(external.storage_get(b"key42", StorageGetMode::Trie).unwrap().map(|ptr| ptr.deref().unwrap()), Some(b"value1337".to_vec()));
    /// // Returns Ok(None) if there is no value for a key
    /// assert_eq!(external.storage_get(b"no_key", StorageGetMode::Trie).unwrap().map(|ptr| ptr.deref().unwrap()), None);
    /// ```
    fn storage_get<'a>(
        &'a self,
        key: &[u8],
        mode: StorageGetMode,
    ) -> Result<Option<Box<dyn ValuePtr + 'a>>>;

    /// Removes the `key` from the storage trie associated with the current account.
    ///
    /// The operation will succeed even if the `key` does not exist.
    ///
    /// # Arguments
    ///
    /// * `key` - the key to remove
    ///
    /// # Example
    /// ```
    /// # use near_vm_logic::mocks::mock_external::MockedExternal;
    /// # use near_vm_logic::External;
    ///
    /// # let mut external = MockedExternal::new();
    /// external.storage_set(b"key42", b"value1337").unwrap();
    /// // Returns Ok if exists
    /// assert_eq!(external.storage_remove(b"key42"), Ok(()));
    /// // Returns Ok if there was no value
    /// assert_eq!(external.storage_remove(b"no_value_key"), Ok(()));
    /// ```
    fn storage_remove(&mut self, key: &[u8]) -> Result<()>;

    /// Note: The method is currently unused and untested.
    ///
    /// Removes all keys with a given `prefix` from the storage trie associated with current
    /// account.
    ///
    /// # Arguments
    ///
    /// * `prefix` - a prefix for all keys to remove
    ///
    /// # Errors
    ///
    /// This function could return [`near_vm_errors::VMError`].
    ///
    /// # Example
    /// ```
    /// # use near_vm_logic::mocks::mock_external::MockedExternal;
    /// # use near_vm_logic::External;
    ///
    /// # let mut external = MockedExternal::new();
    /// external.storage_set(b"key1", b"value1337").unwrap();
    /// external.storage_set(b"key2", b"value1337").unwrap();
    /// assert_eq!(external.storage_remove_subtree(b"key"), Ok(()));
    /// assert!(!external.storage_has_key(b"key1").unwrap());
    /// assert!(!external.storage_has_key(b"key2").unwrap());
    /// ```
    fn storage_remove_subtree(&mut self, prefix: &[u8]) -> Result<()>;

    /// Check whether the `key` is present in the storage trie associated with the current account.
    ///
    /// Returns `Ok(true)` if key is present, `Ok(false)` if the key is not present.
    ///
    /// # Arguments
    ///
    /// * `key` - a key to check
    ///
    /// # Errors
    ///
    /// This function could return [`near_vm_errors::VMError`].
    ///
    /// # Example
    /// ```
    /// # use near_vm_logic::mocks::mock_external::MockedExternal;
    /// # use near_vm_logic::External;
    ///
    /// # let mut external = MockedExternal::new();
    /// external.storage_set(b"key42", b"value1337").unwrap();
    /// // Returns value if exists
    /// assert_eq!(external.storage_has_key(b"key42"), Ok(true));
    /// // Returns None if there was no value
    /// assert_eq!(external.storage_has_key(b"no_value_key"), Ok(false));
    /// ```
    fn storage_has_key(&mut self, key: &[u8]) -> Result<bool>;

    fn generate_data_id(&mut self) -> CryptoHash;

    /// Returns amount of touched trie nodes by storage operations
    fn get_trie_nodes_count(&self) -> TrieNodesCount;

    /// Returns the validator stake for given account in the current epoch.
    /// If the account is not a validator, returns `None`.
    fn validator_stake(&self, account_id: &AccountId) -> Result<Option<Balance>>;

    /// Returns total stake of validators in the current epoch.
    fn validator_total_stake(&self) -> Result<Balance>;
}