safecoin_client/
rpc_filter.rs

1#![allow(deprecated)]
2use {
3    crate::version_req::VersionReq,
4    solana_sdk::account::{AccountSharedData, ReadableAccount},
5    safe_token_2022::{generic_token_account::GenericTokenAccount, state::Account},
6    std::borrow::Cow,
7    thiserror::Error,
8};
9
10const MAX_DATA_SIZE: usize = 128;
11const MAX_DATA_BASE58_SIZE: usize = 175;
12const MAX_DATA_BASE64_SIZE: usize = 172;
13
14#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
15#[serde(rename_all = "camelCase")]
16pub enum RpcFilterType {
17    DataSize(u64),
18    Memcmp(Memcmp),
19    TokenAccountState,
20}
21
22impl RpcFilterType {
23    pub fn verify(&self) -> Result<(), RpcFilterError> {
24        match self {
25            RpcFilterType::DataSize(_) => Ok(()),
26            RpcFilterType::Memcmp(compare) => {
27                let encoding = compare.encoding.as_ref().unwrap_or(&MemcmpEncoding::Binary);
28                match encoding {
29                    MemcmpEncoding::Binary => {
30                        use MemcmpEncodedBytes::*;
31                        match &compare.bytes {
32                            // DEPRECATED
33                            Binary(bytes) => {
34                                if bytes.len() > MAX_DATA_BASE58_SIZE {
35                                    return Err(RpcFilterError::Base58DataTooLarge);
36                                }
37                                let bytes = bs58::decode(&bytes)
38                                    .into_vec()
39                                    .map_err(RpcFilterError::DecodeError)?;
40                                if bytes.len() > MAX_DATA_SIZE {
41                                    Err(RpcFilterError::Base58DataTooLarge)
42                                } else {
43                                    Ok(())
44                                }
45                            }
46                            Base58(bytes) => {
47                                if bytes.len() > MAX_DATA_BASE58_SIZE {
48                                    return Err(RpcFilterError::DataTooLarge);
49                                }
50                                let bytes = bs58::decode(&bytes).into_vec()?;
51                                if bytes.len() > MAX_DATA_SIZE {
52                                    Err(RpcFilterError::DataTooLarge)
53                                } else {
54                                    Ok(())
55                                }
56                            }
57                            Base64(bytes) => {
58                                if bytes.len() > MAX_DATA_BASE64_SIZE {
59                                    return Err(RpcFilterError::DataTooLarge);
60                                }
61                                let bytes = base64::decode(bytes)?;
62                                if bytes.len() > MAX_DATA_SIZE {
63                                    Err(RpcFilterError::DataTooLarge)
64                                } else {
65                                    Ok(())
66                                }
67                            }
68                            Bytes(bytes) => {
69                                if bytes.len() > MAX_DATA_SIZE {
70                                    return Err(RpcFilterError::DataTooLarge);
71                                }
72                                Ok(())
73                            }
74                        }
75                    }
76                }
77            }
78            RpcFilterType::TokenAccountState => Ok(()),
79        }
80    }
81
82    pub fn allows(&self, account: &AccountSharedData) -> bool {
83        match self {
84            RpcFilterType::DataSize(size) => account.data().len() as u64 == *size,
85            RpcFilterType::Memcmp(compare) => compare.bytes_match(account.data()),
86            RpcFilterType::TokenAccountState => Account::valid_account_data(account.data()),
87        }
88    }
89}
90
91#[derive(Error, PartialEq, Eq, Debug)]
92pub enum RpcFilterError {
93    #[error("encoded binary data should be less than 129 bytes")]
94    DataTooLarge,
95    #[deprecated(
96        since = "1.8.1",
97        note = "Error for MemcmpEncodedBytes::Binary which is deprecated"
98    )]
99    #[error("encoded binary (base 58) data should be less than 129 bytes")]
100    Base58DataTooLarge,
101    #[deprecated(
102        since = "1.8.1",
103        note = "Error for MemcmpEncodedBytes::Binary which is deprecated"
104    )]
105    #[error("bs58 decode error")]
106    DecodeError(bs58::decode::Error),
107    #[error("base58 decode error")]
108    Base58DecodeError(#[from] bs58::decode::Error),
109    #[error("base64 decode error")]
110    Base64DecodeError(#[from] base64::DecodeError),
111}
112
113#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
114#[serde(rename_all = "camelCase")]
115pub enum MemcmpEncoding {
116    Binary,
117}
118
119#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
120#[serde(rename_all = "camelCase", untagged)]
121pub enum MemcmpEncodedBytes {
122    #[deprecated(
123        since = "1.8.1",
124        note = "Please use MemcmpEncodedBytes::Base58 instead"
125    )]
126    Binary(String),
127    Base58(String),
128    Base64(String),
129    Bytes(Vec<u8>),
130}
131
132#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
133#[serde(into = "RpcMemcmp", from = "RpcMemcmp")]
134pub struct Memcmp {
135    /// Data offset to begin match
136    pub offset: usize,
137    /// Bytes, encoded with specified encoding, or default Binary
138    pub bytes: MemcmpEncodedBytes,
139    /// Optional encoding specification
140    #[deprecated(
141        since = "1.11.2",
142        note = "Field has no server-side effect. Specify encoding with `MemcmpEncodedBytes` variant instead."
143    )]
144    pub encoding: Option<MemcmpEncoding>,
145}
146
147impl Memcmp {
148    pub fn new_raw_bytes(offset: usize, bytes: Vec<u8>) -> Self {
149        Self {
150            offset,
151            bytes: MemcmpEncodedBytes::Bytes(bytes),
152            encoding: None,
153        }
154    }
155
156    pub fn new_base58_encoded(offset: usize, bytes: &[u8]) -> Self {
157        Self {
158            offset,
159            bytes: MemcmpEncodedBytes::Base58(bs58::encode(bytes).into_string()),
160            encoding: None,
161        }
162    }
163
164    pub fn bytes(&self) -> Option<Cow<Vec<u8>>> {
165        use MemcmpEncodedBytes::*;
166        match &self.bytes {
167            Binary(bytes) | Base58(bytes) => bs58::decode(bytes).into_vec().ok().map(Cow::Owned),
168            Base64(bytes) => base64::decode(bytes).ok().map(Cow::Owned),
169            Bytes(bytes) => Some(Cow::Borrowed(bytes)),
170        }
171    }
172
173    pub fn bytes_match(&self, data: &[u8]) -> bool {
174        match self.bytes() {
175            Some(bytes) => {
176                if self.offset > data.len() {
177                    return false;
178                }
179                if data[self.offset..].len() < bytes.len() {
180                    return false;
181                }
182                data[self.offset..self.offset + bytes.len()] == bytes[..]
183            }
184            None => false,
185        }
186    }
187}
188
189// Internal struct to hold Memcmp filter data as either encoded String or raw Bytes
190#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
191#[serde(untagged)]
192enum DataType {
193    Encoded(String),
194    Raw(Vec<u8>),
195}
196
197// Internal struct used to specify explicit Base58 and Base64 encoding
198#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
199#[serde(rename_all = "camelCase")]
200enum RpcMemcmpEncoding {
201    Base58,
202    Base64,
203    // This variant exists only to preserve backward compatibility with generic `Memcmp` serde
204    #[serde(other)]
205    Binary,
206}
207
208// Internal struct to enable Memcmp filters with explicit Base58 and Base64 encoding. The From
209// implementations emulate `#[serde(tag = "encoding", content = "bytes")]` for
210// `MemcmpEncodedBytes`. On the next major version, all these internal elements should be removed
211// and replaced with adjacent tagging of `MemcmpEncodedBytes`.
212#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
213struct RpcMemcmp {
214    offset: usize,
215    bytes: DataType,
216    encoding: Option<RpcMemcmpEncoding>,
217}
218
219impl From<Memcmp> for RpcMemcmp {
220    fn from(memcmp: Memcmp) -> RpcMemcmp {
221        let (bytes, encoding) = match memcmp.bytes {
222            MemcmpEncodedBytes::Binary(string) => {
223                (DataType::Encoded(string), Some(RpcMemcmpEncoding::Binary))
224            }
225            MemcmpEncodedBytes::Base58(string) => {
226                (DataType::Encoded(string), Some(RpcMemcmpEncoding::Base58))
227            }
228            MemcmpEncodedBytes::Base64(string) => {
229                (DataType::Encoded(string), Some(RpcMemcmpEncoding::Base64))
230            }
231            MemcmpEncodedBytes::Bytes(vector) => (DataType::Raw(vector), None),
232        };
233        RpcMemcmp {
234            offset: memcmp.offset,
235            bytes,
236            encoding,
237        }
238    }
239}
240
241impl From<RpcMemcmp> for Memcmp {
242    fn from(memcmp: RpcMemcmp) -> Memcmp {
243        let encoding = memcmp.encoding.unwrap_or(RpcMemcmpEncoding::Binary);
244        let bytes = match (encoding, memcmp.bytes) {
245            (RpcMemcmpEncoding::Binary, DataType::Encoded(string))
246            | (RpcMemcmpEncoding::Base58, DataType::Encoded(string)) => {
247                MemcmpEncodedBytes::Base58(string)
248            }
249            (RpcMemcmpEncoding::Binary, DataType::Raw(vector)) => MemcmpEncodedBytes::Bytes(vector),
250            (RpcMemcmpEncoding::Base64, DataType::Encoded(string)) => {
251                MemcmpEncodedBytes::Base64(string)
252            }
253            _ => unreachable!(),
254        };
255        Memcmp {
256            offset: memcmp.offset,
257            bytes,
258            encoding: None,
259        }
260    }
261}
262
263pub(crate) fn maybe_map_filters(
264    node_version: Option<semver::Version>,
265    filters: &mut [RpcFilterType],
266) -> Result<(), String> {
267    let version_reqs = VersionReq::from_strs(&["<1.11.2", "~1.13"])?;
268    let needs_mapping = node_version
269        .map(|version| version_reqs.matches_any(&version))
270        .unwrap_or(true);
271    if needs_mapping {
272        for filter in filters.iter_mut() {
273            if let RpcFilterType::Memcmp(memcmp) = filter {
274                match &memcmp.bytes {
275                    MemcmpEncodedBytes::Base58(string) => {
276                        memcmp.bytes = MemcmpEncodedBytes::Binary(string.clone());
277                    }
278                    MemcmpEncodedBytes::Base64(_) => {
279                        return Err("RPC node on old version does not support base64 \
280                            encoding for memcmp filters"
281                            .to_string());
282                    }
283                    _ => {}
284                }
285            }
286        }
287    }
288    Ok(())
289}
290
291#[cfg(test)]
292mod tests {
293    use super::*;
294
295    #[test]
296    fn test_worst_case_encoded_tx_goldens() {
297        let ff_data = vec![0xffu8; MAX_DATA_SIZE];
298        let data58 = bs58::encode(&ff_data).into_string();
299        assert_eq!(data58.len(), MAX_DATA_BASE58_SIZE);
300        let data64 = base64::encode(&ff_data);
301        assert_eq!(data64.len(), MAX_DATA_BASE64_SIZE);
302    }
303
304    #[test]
305    fn test_bytes_match() {
306        let data = vec![1, 2, 3, 4, 5];
307
308        // Exact match of data succeeds
309        assert!(Memcmp {
310            offset: 0,
311            bytes: MemcmpEncodedBytes::Base58(bs58::encode(vec![1, 2, 3, 4, 5]).into_string()),
312            encoding: None,
313        }
314        .bytes_match(&data));
315
316        // Partial match of data succeeds
317        assert!(Memcmp {
318            offset: 0,
319            bytes: MemcmpEncodedBytes::Base58(bs58::encode(vec![1, 2]).into_string()),
320            encoding: None,
321        }
322        .bytes_match(&data));
323
324        // Offset partial match of data succeeds
325        assert!(Memcmp {
326            offset: 2,
327            bytes: MemcmpEncodedBytes::Base58(bs58::encode(vec![3, 4]).into_string()),
328            encoding: None,
329        }
330        .bytes_match(&data));
331
332        // Incorrect partial match of data fails
333        assert!(!Memcmp {
334            offset: 0,
335            bytes: MemcmpEncodedBytes::Base58(bs58::encode(vec![2]).into_string()),
336            encoding: None,
337        }
338        .bytes_match(&data));
339
340        // Bytes overrun data fails
341        assert!(!Memcmp {
342            offset: 2,
343            bytes: MemcmpEncodedBytes::Base58(bs58::encode(vec![3, 4, 5, 6]).into_string()),
344            encoding: None,
345        }
346        .bytes_match(&data));
347
348        // Offset outside data fails
349        assert!(!Memcmp {
350            offset: 6,
351            bytes: MemcmpEncodedBytes::Base58(bs58::encode(vec![5]).into_string()),
352            encoding: None,
353        }
354        .bytes_match(&data));
355
356        // Invalid base-58 fails
357        assert!(!Memcmp {
358            offset: 0,
359            bytes: MemcmpEncodedBytes::Base58("III".to_string()),
360            encoding: None,
361        }
362        .bytes_match(&data));
363    }
364
365    #[test]
366    fn test_verify_memcmp() {
367        let base58_bytes = "\
368            1111111111111111111111111111111111111111111111111111111111111111\
369            1111111111111111111111111111111111111111111111111111111111111111";
370        assert_eq!(base58_bytes.len(), 128);
371        assert_eq!(
372            RpcFilterType::Memcmp(Memcmp {
373                offset: 0,
374                bytes: MemcmpEncodedBytes::Base58(base58_bytes.to_string()),
375                encoding: None,
376            })
377            .verify(),
378            Ok(())
379        );
380
381        let base58_bytes = "\
382            1111111111111111111111111111111111111111111111111111111111111111\
383            1111111111111111111111111111111111111111111111111111111111111111\
384            1";
385        assert_eq!(base58_bytes.len(), 129);
386        assert_eq!(
387            RpcFilterType::Memcmp(Memcmp {
388                offset: 0,
389                bytes: MemcmpEncodedBytes::Base58(base58_bytes.to_string()),
390                encoding: None,
391            })
392            .verify(),
393            Err(RpcFilterError::DataTooLarge)
394        );
395    }
396}