Skip to content
This repository was archived by the owner on Jul 14, 2026. It is now read-only.

Commit 3ad21b0

Browse files
authored
rpc: add solend patch (gPA filter)
Adds a patch that allows more complex queries for gPA.
1 parent cc72620 commit 3ad21b0

3 files changed

Lines changed: 248 additions & 0 deletions

File tree

rpc-client-types/src/filter.rs

Lines changed: 245 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
use {
22
base64::{prelude::BASE64_STANDARD, Engine},
33
serde::{Deserialize, Serialize},
4+
solana_account::{AccountSharedData, ReadableAccount},
5+
spl_generic_token::{token::GenericTokenAccount, token_2022::Account},
46
std::borrow::Cow,
57
thiserror::Error,
68
};
@@ -15,6 +17,7 @@ pub enum RpcFilterType {
1517
DataSize(u64),
1618
Memcmp(Memcmp),
1719
TokenAccountState,
20+
ValueCmp(ValueCmp),
1821
}
1922

2023
impl RpcFilterType {
@@ -55,6 +58,22 @@ impl RpcFilterType {
5558
}
5659
}
5760
RpcFilterType::TokenAccountState => Ok(()),
61+
RpcFilterType::ValueCmp(_) => Ok(()),
62+
}
63+
}
64+
65+
#[deprecated(
66+
since = "2.0.0",
67+
note = "Use solana_rpc::filter::filter_allows instead"
68+
)]
69+
pub fn allows(&self, account: &AccountSharedData) -> bool {
70+
match self {
71+
RpcFilterType::DataSize(size) => account.data().len() as u64 == *size,
72+
RpcFilterType::Memcmp(compare) => compare.bytes_match(account.data()),
73+
RpcFilterType::TokenAccountState => Account::valid_account_data(account.data()),
74+
RpcFilterType::ValueCmp(compare) => {
75+
compare.values_match(account.data()).unwrap_or(false)
76+
}
5877
}
5978
}
6079
}
@@ -67,6 +86,8 @@ pub enum RpcFilterError {
6786
Base58DecodeError(#[from] bs58::decode::Error),
6887
#[error("base64 decode error")]
6988
Base64DecodeError(#[from] base64::DecodeError),
89+
#[error("invalid ValueCmp filter")]
90+
InvalidValueCmp,
7091
}
7192

7293
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize)]
@@ -208,6 +229,178 @@ impl Memcmp {
208229
}
209230
}
210231

232+
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
233+
pub struct ValueCmp {
234+
pub left: Operand,
235+
comparator: Comparator,
236+
pub right: Operand,
237+
}
238+
239+
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
240+
pub enum Operand {
241+
Mem {
242+
offset: usize,
243+
value_type: ValueType,
244+
},
245+
Constant(String),
246+
}
247+
248+
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
249+
pub enum ValueType {
250+
U8,
251+
U16,
252+
U32,
253+
U64,
254+
U128,
255+
}
256+
257+
enum WrappedValueType {
258+
U8(u8),
259+
U16(u16),
260+
U32(u32),
261+
U64(u64),
262+
U128(u128),
263+
}
264+
265+
impl ValueCmp {
266+
fn parse_mem_into_value_type(
267+
o: &Operand,
268+
data: &[u8],
269+
) -> Result<WrappedValueType, RpcFilterError> {
270+
match o {
271+
Operand::Mem { offset, value_type } => match value_type {
272+
ValueType::U8 => {
273+
if *offset >= data.len() {
274+
return Err(RpcFilterError::InvalidValueCmp);
275+
}
276+
277+
Ok(WrappedValueType::U8(data[*offset]))
278+
}
279+
ValueType::U16 => {
280+
if *offset + 1 >= data.len() {
281+
return Err(RpcFilterError::InvalidValueCmp);
282+
}
283+
Ok(WrappedValueType::U16(u16::from_le_bytes(
284+
data[*offset..*offset + 2].try_into().unwrap(),
285+
)))
286+
}
287+
ValueType::U32 => {
288+
if *offset + 3 >= data.len() {
289+
return Err(RpcFilterError::InvalidValueCmp);
290+
}
291+
Ok(WrappedValueType::U32(u32::from_le_bytes(
292+
data[*offset..*offset + 4].try_into().unwrap(),
293+
)))
294+
}
295+
ValueType::U64 => {
296+
if *offset + 7 >= data.len() {
297+
return Err(RpcFilterError::InvalidValueCmp);
298+
}
299+
Ok(WrappedValueType::U64(u64::from_le_bytes(
300+
data[*offset..*offset + 8].try_into().unwrap(),
301+
)))
302+
}
303+
ValueType::U128 => {
304+
if *offset + 15 >= data.len() {
305+
return Err(RpcFilterError::InvalidValueCmp);
306+
}
307+
Ok(WrappedValueType::U128(u128::from_le_bytes(
308+
data[*offset..*offset + 16].try_into().unwrap(),
309+
)))
310+
}
311+
},
312+
_ => Err(RpcFilterError::InvalidValueCmp),
313+
}
314+
}
315+
316+
pub fn values_match(&self, data: &[u8]) -> Result<bool, RpcFilterError> {
317+
match (&self.left, &self.right) {
318+
(left @ Operand::Mem { .. }, right @ Operand::Mem { .. }) => {
319+
let left = Self::parse_mem_into_value_type(left, data)?;
320+
let right = Self::parse_mem_into_value_type(right, data)?;
321+
322+
match (left, right) {
323+
(WrappedValueType::U8(left), WrappedValueType::U8(right)) => {
324+
Ok(self.comparator.compare(left, right))
325+
}
326+
(WrappedValueType::U16(left), WrappedValueType::U16(right)) => {
327+
Ok(self.comparator.compare(left, right))
328+
}
329+
(WrappedValueType::U32(left), WrappedValueType::U32(right)) => {
330+
Ok(self.comparator.compare(left, right))
331+
}
332+
(WrappedValueType::U64(left), WrappedValueType::U64(right)) => {
333+
Ok(self.comparator.compare(left, right))
334+
}
335+
(WrappedValueType::U128(left), WrappedValueType::U128(right)) => {
336+
Ok(self.comparator.compare(left, right))
337+
}
338+
_ => Err(RpcFilterError::InvalidValueCmp),
339+
}
340+
}
341+
(left @ Operand::Mem { .. }, Operand::Constant(constant)) => {
342+
match Self::parse_mem_into_value_type(left, data)? {
343+
WrappedValueType::U8(left) => {
344+
let right = constant
345+
.parse::<u8>()
346+
.map_err(|_| RpcFilterError::InvalidValueCmp)?;
347+
Ok(self.comparator.compare(left, right))
348+
}
349+
WrappedValueType::U16(left) => {
350+
let right = constant
351+
.parse::<u16>()
352+
.map_err(|_| RpcFilterError::InvalidValueCmp)?;
353+
Ok(self.comparator.compare(left, right))
354+
}
355+
WrappedValueType::U32(left) => {
356+
let right = constant
357+
.parse::<u32>()
358+
.map_err(|_| RpcFilterError::InvalidValueCmp)?;
359+
Ok(self.comparator.compare(left, right))
360+
}
361+
WrappedValueType::U64(left) => {
362+
let right = constant
363+
.parse::<u64>()
364+
.map_err(|_| RpcFilterError::InvalidValueCmp)?;
365+
Ok(self.comparator.compare(left, right))
366+
}
367+
WrappedValueType::U128(left) => {
368+
let right = constant
369+
.parse::<u128>()
370+
.map_err(|_| RpcFilterError::InvalidValueCmp)?;
371+
Ok(self.comparator.compare(left, right))
372+
}
373+
}
374+
}
375+
_ => Err(RpcFilterError::InvalidValueCmp),
376+
}
377+
}
378+
}
379+
380+
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
381+
pub enum Comparator {
382+
Eq = 0,
383+
Ne,
384+
Gt,
385+
Ge,
386+
Lt,
387+
Le,
388+
}
389+
390+
impl Comparator {
391+
// write a generic function to compare two values
392+
pub fn compare<T: PartialOrd>(&self, left: T, right: T) -> bool {
393+
match self {
394+
Comparator::Eq => left == right,
395+
Comparator::Ne => left != right,
396+
Comparator::Gt => left > right,
397+
Comparator::Ge => left >= right,
398+
Comparator::Lt => left < right,
399+
Comparator::Le => left <= right,
400+
}
401+
}
402+
}
403+
211404
#[cfg(test)]
212405
mod tests {
213406
use {
@@ -441,4 +634,56 @@ mod tests {
441634
serde_json::from_str::<Value>(BYTES_FILTER_WITH_ENCODING).unwrap()
442635
);
443636
}
637+
638+
#[test]
639+
fn test_values_match() {
640+
// test all the ValueCmp cases
641+
let data = vec![1, 2, 3, 4, 5];
642+
643+
let filter = ValueCmp {
644+
left: Operand::Mem {
645+
offset: 1,
646+
value_type: ValueType::U8,
647+
},
648+
comparator: Comparator::Eq,
649+
right: Operand::Constant("2".to_string()),
650+
};
651+
652+
assert!(ValueCmp {
653+
left: Operand::Mem {
654+
offset: 1,
655+
value_type: ValueType::U8
656+
},
657+
comparator: Comparator::Eq,
658+
right: Operand::Constant("2".to_string())
659+
}
660+
.values_match(&data)
661+
.unwrap());
662+
663+
assert!(ValueCmp {
664+
left: Operand::Mem {
665+
offset: 1,
666+
value_type: ValueType::U8
667+
},
668+
comparator: Comparator::Lt,
669+
right: Operand::Constant("3".to_string())
670+
}
671+
.values_match(&data)
672+
.unwrap());
673+
674+
assert!(ValueCmp {
675+
left: Operand::Mem {
676+
offset: 0,
677+
value_type: ValueType::U32
678+
},
679+
comparator: Comparator::Eq,
680+
right: Operand::Constant("67305985".to_string())
681+
}
682+
.values_match(&data)
683+
.unwrap());
684+
685+
// serialize
686+
let s = serde_json::to_string(&filter).unwrap();
687+
println!("{}", s);
688+
}
444689
}

rpc/src/filter.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,6 @@ pub fn filter_allows(filter: &RpcFilterType, account: &AccountSharedData) -> boo
99
RpcFilterType::DataSize(size) => account.data().len() as u64 == *size,
1010
RpcFilterType::Memcmp(compare) => compare.bytes_match(account.data()),
1111
RpcFilterType::TokenAccountState => Account::valid_account_data(account.data()),
12+
RpcFilterType::ValueCmp(compare) => compare.values_match(account.data()).unwrap_or(false),
1213
}
1314
}

rpc/src/rpc.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2608,6 +2608,7 @@ fn get_spl_token_owner_filter(
26082608
}
26092609
}
26102610
RpcFilterType::TokenAccountState => token_account_state_filter = true,
2611+
RpcFilterType::ValueCmp(_) => {}
26112612
}
26122613
}
26132614
if data_size_filter == Some(account_packed_len as u64)
@@ -2659,6 +2660,7 @@ fn get_spl_token_mint_filter(
26592660
}
26602661
}
26612662
RpcFilterType::TokenAccountState => token_account_state_filter = true,
2663+
RpcFilterType::ValueCmp(_) => {}
26622664
}
26632665
}
26642666
if data_size_filter == Some(account_packed_len as u64)

0 commit comments

Comments
 (0)