Skip to content

Commit fc50bf8

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

File tree

3 files changed

+234
-0
lines changed

3 files changed

+234
-0
lines changed

rpc-client-types/src/filter.rs

Lines changed: 231 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ pub enum RpcFilterType {
1717
DataSize(u64),
1818
Memcmp(Memcmp),
1919
TokenAccountState,
20+
ValueCmp(ValueCmp),
2021
}
2122

2223
impl RpcFilterType {
@@ -57,6 +58,7 @@ impl RpcFilterType {
5758
}
5859
}
5960
RpcFilterType::TokenAccountState => Ok(()),
61+
RpcFilterType::ValueCmp(_) => Ok(()),
6062
}
6163
}
6264

@@ -69,6 +71,9 @@ impl RpcFilterType {
6971
RpcFilterType::DataSize(size) => account.data().len() as u64 == *size,
7072
RpcFilterType::Memcmp(compare) => compare.bytes_match(account.data()),
7173
RpcFilterType::TokenAccountState => Account::valid_account_data(account.data()),
74+
RpcFilterType::ValueCmp(compare) => {
75+
compare.values_match(account.data()).unwrap_or(false)
76+
}
7277
}
7378
}
7479
}
@@ -81,6 +86,8 @@ pub enum RpcFilterError {
8186
Base58DecodeError(#[from] bs58::decode::Error),
8287
#[error("base64 decode error")]
8388
Base64DecodeError(#[from] base64::DecodeError),
89+
#[error("invalid ValueCmp filter")]
90+
InvalidValueCmp,
8491
}
8592

8693
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize)]
@@ -222,6 +229,178 @@ impl Memcmp {
222229
}
223230
}
224231

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+
225404
#[cfg(test)]
226405
mod tests {
227406
use {
@@ -455,4 +634,56 @@ mod tests {
455634
serde_json::from_str::<Value>(BYTES_FILTER_WITH_ENCODING).unwrap()
456635
);
457636
}
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+
}
458689
}

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
@@ -2611,6 +2611,7 @@ fn get_spl_token_owner_filter(program_id: &Pubkey, filters: &[RpcFilterType]) ->
26112611
}
26122612
}
26132613
RpcFilterType::TokenAccountState => token_account_state_filter = true,
2614+
RpcFilterType::ValueCmp(_) => {}
26142615
}
26152616
}
26162617
if data_size_filter == Some(account_packed_len as u64)
@@ -2662,6 +2663,7 @@ fn get_spl_token_mint_filter(program_id: &Pubkey, filters: &[RpcFilterType]) ->
26622663
}
26632664
}
26642665
RpcFilterType::TokenAccountState => token_account_state_filter = true,
2666+
RpcFilterType::ValueCmp(_) => {}
26652667
}
26662668
}
26672669
if data_size_filter == Some(account_packed_len as u64)

0 commit comments

Comments
 (0)