alloy_provider/provider/ccip_read/
bounds.rs1use super::{CcipReadConfig, CcipReadError};
8use alloy_primitives::U256;
9use alloy_sol_types::Error;
10
11pub(super) fn offchain_lookup(data: &[u8], config: &CcipReadConfig) -> Result<(), CcipReadError> {
14 offchain_lookup_fields(data, &mut Limits::new(config)).map_err(|error| match error {
15 CheckError::Abi(error) => CcipReadError::InvalidOffchainLookup(error),
16 CheckError::Limit(message) => CcipReadError::ResourceLimit(message.into()),
17 })
18}
19
20pub(super) fn batch(data: &[u8], config: &CcipReadConfig) -> Result<(), CcipReadError> {
23 batch_requests(data, &mut Limits::new(config)).map_err(|error| match error {
24 CheckError::Abi(error) => CcipReadError::InvalidBatch(error.to_string()),
25 CheckError::Limit(message) => CcipReadError::ResourceLimit(message.into()),
26 })
27}
28
29fn offchain_lookup_fields(data: &[u8], limits: &mut Limits<'_>) -> Result<(), CheckError> {
30 let data = data.get(4..).ok_or(Error::Overrun)?;
31 let mut head = data;
32 word(&mut head)?; limits.urls(indirect(&mut head, data)?)?;
34 limits.bytes(indirect(&mut head, data)?, false)?; word(&mut head)?; limits.bytes(indirect(&mut head, data)?, false) }
38
39fn batch_requests(data: &[u8], limits: &mut Limits<'_>) -> Result<(), CheckError> {
40 let config = limits.config;
41 if data.len() > config.max_revert_data_size {
42 return Err(CheckError::Limit("batch data exceeds revert data size limit"));
43 }
44 let data = data.get(4..).ok_or(Error::Overrun)?;
45 let mut head = data;
46 let (count, array) = limits.array(
47 indirect(&mut head, data)?,
48 config.max_batch_size,
49 "batch request count exceeds configured limit",
50 )?;
51 let mut head = array;
52 for _ in 0..count {
53 let request = indirect(&mut head, array)?;
54 let mut fields = request;
55 word(&mut fields)?; limits.urls(indirect(&mut fields, request)?)?;
57 limits.bytes(indirect(&mut fields, request)?, false)?; }
59 Ok(())
60}
61
62enum CheckError {
63 Abi(Error),
64 Limit(&'static str),
65}
66
67impl From<Error> for CheckError {
68 fn from(error: Error) -> Self {
69 Self::Abi(error)
70 }
71}
72
73struct Limits<'a> {
74 config: &'a CcipReadConfig,
75 remaining: usize,
76}
77
78impl<'a> Limits<'a> {
79 const fn new(config: &'a CcipReadConfig) -> Self {
80 Self { config, remaining: config.max_revert_data_size }
81 }
82
83 fn charge(&mut self, size: usize) -> Result<(), CheckError> {
84 self.remaining = self
85 .remaining
86 .checked_sub(size)
87 .ok_or(CheckError::Limit("decoded ABI data exceeds revert data size limit"))?;
88 Ok(())
89 }
90
91 fn array<'b>(
92 &mut self,
93 mut data: &'b [u8],
94 max: usize,
95 message: &'static str,
96 ) -> Result<(usize, &'b [u8]), CheckError> {
97 let count = offset(&mut data)?;
98 if count > max {
99 return Err(CheckError::Limit(message));
100 }
101 let size = count.checked_mul(32).ok_or(Error::Overrun)?;
102 data.get(..size).ok_or(Error::Overrun)?;
103 self.charge(size)?;
104 Ok((count, data))
106 }
107
108 fn urls(&mut self, data: &[u8]) -> Result<(), CheckError> {
109 let (count, array) = self.array(
110 data,
111 self.config.max_gateway_urls,
112 "gateway URL count exceeds configured limit",
113 )?;
114 let mut head = array;
115 for _ in 0..count {
116 self.bytes(indirect(&mut head, array)?, true)?;
117 }
118 Ok(())
119 }
120
121 fn bytes(&mut self, mut data: &[u8], string: bool) -> Result<(), CheckError> {
122 let len = offset(&mut data)?;
123 let bytes = data.get(..len).ok_or(Error::Overrun)?;
124 self.charge(len)?;
125 if string {
126 for chunk in bytes.utf8_chunks() {
129 if !chunk.invalid().is_empty() {
130 self.charge(3 - chunk.invalid().len())?;
131 }
132 }
133 }
134 Ok(())
135 }
136}
137
138fn word<'a>(head: &mut &'a [u8]) -> Result<&'a [u8; 32], Error> {
139 let (word, rest) = head.split_first_chunk::<32>().ok_or(Error::Overrun)?;
140 *head = rest;
141 Ok(word)
142}
143
144fn offset(head: &mut &[u8]) -> Result<usize, Error> {
145 U256::from_be_bytes(*word(head)?).try_into().map_err(|_| Error::Overrun)
146}
147
148fn indirect<'a>(head: &mut &[u8], base: &'a [u8]) -> Result<&'a [u8], Error> {
149 base.get(offset(head)?..).ok_or(Error::Overrun)
150}