Skip to main content

neo_types/
array.rs

1// Copyright (c) 2025-2026 R3E Network
2// Licensed under the MIT License
3
4use std::ops::Index;
5use std::vec::Vec;
6
7#[cfg(feature = "serde")]
8use serde::{Deserialize, Serialize};
9
10/// Neo N3 Array type
11#[derive(Debug, Clone, PartialEq, Eq)]
12#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
13#[cfg_attr(
14    feature = "serde",
15    serde(bound(serialize = "T: Serialize", deserialize = "T: Deserialize<'de>"))
16)]
17pub struct NeoArray<T> {
18    data: Vec<T>,
19}
20
21impl<T> NeoArray<T> {
22    pub fn new() -> Self {
23        Self { data: Vec::new() }
24    }
25
26    pub fn with_capacity(capacity: usize) -> Self {
27        Self {
28            data: Vec::with_capacity(capacity),
29        }
30    }
31
32    pub fn from_vec(data: Vec<T>) -> Self {
33        Self { data }
34    }
35
36    pub fn push(&mut self, item: T) {
37        self.data.push(item);
38    }
39
40    pub fn pop(&mut self) -> Option<T> {
41        self.data.pop()
42    }
43
44    pub fn len(&self) -> usize {
45        self.data.len()
46    }
47
48    pub fn is_empty(&self) -> bool {
49        self.data.is_empty()
50    }
51
52    pub fn get(&self, index: usize) -> Option<&T> {
53        self.data.get(index)
54    }
55
56    pub fn get_mut(&mut self, index: usize) -> Option<&mut T> {
57        self.data.get_mut(index)
58    }
59
60    pub fn iter(&self) -> impl Iterator<Item = &T> {
61        self.data.iter()
62    }
63}
64
65impl<T> Default for NeoArray<T> {
66    fn default() -> Self {
67        Self::new()
68    }
69}
70
71impl<T> From<Vec<T>> for NeoArray<T> {
72    fn from(data: Vec<T>) -> Self {
73        Self { data }
74    }
75}
76
77impl<T> FromIterator<T> for NeoArray<T> {
78    fn from_iter<I: IntoIterator<Item = T>>(iter: I) -> Self {
79        Self {
80            data: Vec::from_iter(iter),
81        }
82    }
83}
84
85impl<T> Extend<T> for NeoArray<T> {
86    fn extend<I: IntoIterator<Item = T>>(&mut self, iter: I) {
87        self.data.extend(iter);
88    }
89}
90
91impl<T> Index<usize> for NeoArray<T> {
92    type Output = T;
93    fn index(&self, index: usize) -> &T {
94        &self.data[index]
95    }
96}
97
98impl<T> IntoIterator for NeoArray<T> {
99    type Item = T;
100    type IntoIter = std::vec::IntoIter<T>;
101    fn into_iter(self) -> Self::IntoIter {
102        self.data.into_iter()
103    }
104}
105
106impl<'a, T> IntoIterator for &'a NeoArray<T> {
107    type Item = &'a T;
108    type IntoIter = std::slice::Iter<'a, T>;
109    fn into_iter(self) -> Self::IntoIter {
110        self.data.iter()
111    }
112}
113
114impl<T: PartialEq> PartialEq<Vec<T>> for NeoArray<T> {
115    fn eq(&self, other: &Vec<T>) -> bool {
116        self.data == *other
117    }
118}
119
120impl<T: PartialEq> PartialEq<NeoArray<T>> for Vec<T> {
121    fn eq(&self, other: &NeoArray<T>) -> bool {
122        *self == other.data
123    }
124}
125
126/// An error returned when a `NeoArray` would exceed `NeoArray::MAX_SIZE` items.
127#[derive(Debug, Clone, Copy, PartialEq, Eq)]
128pub struct ArrayFullError {
129    /// The current length of the array when the push was attempted.
130    pub current_len: usize,
131}
132
133impl core::fmt::Display for ArrayFullError {
134    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
135        write!(
136            f,
137            "NeoArray is full (current_len = {}; MAX_SIZE = {})",
138            self.current_len,
139            crate::MAX_STACK_SIZE
140        )
141    }
142}
143
144impl std::error::Error for ArrayFullError {}
145
146impl<T> NeoArray<T> {
147    /// Maximum number of items in a `NeoArray` on-chain. Mirrors the single
148    /// source of truth `MAX_STACK_SIZE` (the C# NeoVM `Limits.MaxStackSize`,
149    /// 1024); exceeding it at runtime raises `FAULT`. Spelled as an
150    /// associated const for parity with `NeoByteString::MAX_SIZE`.
151    pub const MAX_SIZE: usize = crate::MAX_STACK_SIZE;
152
153    /// Try to push an item, returning `Err(ArrayFullError)` if the array
154    /// is already at `MAX_SIZE` (1024) items. Use this instead of `push`
155    /// when the array size is data-dependent and you need to handle the
156    /// bound explicitly (e.g. paginated reads).
157    pub fn try_push(&mut self, item: T) -> Result<(), ArrayFullError> {
158        if self.data.len() >= Self::MAX_SIZE {
159            return Err(ArrayFullError {
160                current_len: self.data.len(),
161            });
162        }
163        self.data.push(item);
164        Ok(())
165    }
166
167    /// Returns the remaining capacity before the on-chain `MAX_SIZE`
168    /// limit is reached.
169    pub fn remaining_capacity(&self) -> usize {
170        Self::MAX_SIZE.saturating_sub(self.data.len())
171    }
172}
173
174#[cfg(test)]
175mod tests {
176    use super::*;
177
178    const MAX_SIZE: usize = NeoArray::<u32>::MAX_SIZE;
179
180    #[test]
181    fn max_size_tracks_single_source() {
182        assert_eq!(NeoArray::<u32>::MAX_SIZE, crate::MAX_STACK_SIZE);
183    }
184
185    #[test]
186    fn try_push_within_limit() {
187        let mut arr: NeoArray<u32> = NeoArray::new();
188        for i in 0..100 {
189            arr.try_push(i).expect("well under MAX_SIZE");
190        }
191        assert_eq!(arr.len(), 100);
192        assert_eq!(arr.remaining_capacity(), MAX_SIZE - 100);
193    }
194
195    #[test]
196    fn try_push_at_limit_returns_error() {
197        let mut arr: NeoArray<u32> = NeoArray::new();
198        for i in 0..MAX_SIZE {
199            arr.try_push(i as u32).expect("fits within MAX_SIZE");
200        }
201        assert_eq!(arr.len(), MAX_SIZE);
202        assert_eq!(arr.remaining_capacity(), 0);
203        // 1025th push must fail.
204        let err = arr.try_push(MAX_SIZE as u32).unwrap_err();
205        assert_eq!(err.current_len, MAX_SIZE);
206        assert_eq!(
207            err.to_string(),
208            "NeoArray is full (current_len = 1024; MAX_SIZE = 1024)"
209        );
210    }
211}