1use crate::fields::decoder::{FromLedger, decode_host_result};
9use crate::host::{Error, Result, le_field};
10use crate::sfield::SField;
11use crate::types::blob::Blob;
12use core::mem::MaybeUninit;
13
14#[inline]
22pub fn get_field<T: FromLedger, const CODE: i32>(slot: i32, _: SField<T, CODE>) -> Result<T> {
23 let mut buf = T::empty_buffer();
24 let n = {
25 let slice = buf.as_mut();
26 unsafe { le_field(slot, CODE, slice.as_mut_ptr(), slice.len()) }
27 };
28 decode_host_result::<T>(buf, n)
29}
30
31#[inline]
40pub fn get_field_optional<T: FromLedger, const CODE: i32>(
41 slot: i32,
42 field: SField<T, CODE>,
43) -> Result<Option<T>> {
44 match get_field(slot, field) {
45 Result::Ok(value) => Result::Ok(Some(value)),
46 Result::Err(Error::FieldNotFound) => Result::Ok(None),
47 Result::Err(e) => Result::Err(e),
48 }
49}
50
51#[inline]
82pub fn get_blob_field<const N: usize, const CODE: i32>(
83 slot: i32,
84 _: SField<Blob<N>, CODE>,
85) -> Result<Blob<N>> {
86 let mut blob = MaybeUninit::<Blob<N>>::uninit();
87 let data_ptr = unsafe { core::ptr::addr_of_mut!((*blob.as_mut_ptr()).data) } as *mut u8;
90 unsafe { core::ptr::write_bytes(data_ptr, 0u8, N) };
95 let n = unsafe { le_field(slot, CODE, data_ptr, N) };
96 if n < 0 {
97 return Result::Err(Error::from_code(n));
98 }
99 if n as usize > N {
100 return Result::Err(Error::PointerOutOfBounds);
102 }
103 unsafe {
106 core::ptr::addr_of_mut!((*blob.as_mut_ptr()).len).write(n as usize);
107 Result::Ok(blob.assume_init())
108 }
109}
110
111#[inline]
117pub fn get_blob_field_optional<const N: usize, const CODE: i32>(
118 slot: i32,
119 field: SField<Blob<N>, CODE>,
120) -> Result<Option<Blob<N>>> {
121 match get_blob_field(slot, field) {
122 Result::Ok(blob) => Result::Ok(Some(blob)),
123 Result::Err(Error::FieldNotFound) => Result::Ok(None),
124 Result::Err(e) => Result::Err(e),
125 }
126}
127
128#[cfg(test)]
129mod tests {
130 use super::{get_blob_field, get_blob_field_optional, get_field, get_field_optional};
131 use crate::host::error_codes::{FIELD_NOT_FOUND, SOME_ERROR};
132 use crate::host::host_bindings_trait::MockHostBindings;
133 use crate::host::setup_mock;
134 use crate::sfield;
135 use crate::types::account_id::{ACCOUNT_ID_SIZE, AccountID};
136 use crate::types::number::Number;
137 use mockall::predicate::{always, eq};
138
139 const SLOT: i32 = 3;
140
141 fn expect_ledger_obj_field(
142 mock: &mut MockHostBindings,
143 slot: i32,
144 field_code: i32,
145 size: usize,
146 times: usize,
147 ) {
148 mock.expect_le_field()
149 .with(eq(slot), eq(field_code), always(), eq(size))
150 .times(times)
151 .returning(move |_, _, _, _| size as i32);
152 }
153
154 #[test]
155 fn test_get_field_success() {
156 let mut mock = MockHostBindings::new();
157 expect_ledger_obj_field(&mut mock, SLOT, sfield::Sequence.into(), 4, 1);
158 expect_ledger_obj_field(&mut mock, SLOT, sfield::Account.into(), ACCOUNT_ID_SIZE, 1);
159 let _guard = setup_mock(mock);
160
161 assert!(get_field::<u32, _>(SLOT, sfield::Sequence).is_ok());
162 assert!(get_field::<AccountID, _>(SLOT, sfield::Account).is_ok());
163 }
164
165 #[test]
166 fn test_get_field_decodes_stnumber_field() {
167 const VALUE: [u8; 12] = [
170 0x00, 0x03, 0x8D, 0x7E, 0xA4, 0xC6, 0x80, 0x00, 0xFF, 0xFF, 0xFF, 0xF1,
171 ];
172 let mut mock = MockHostBindings::new();
173 mock.expect_le_field()
174 .with(
175 eq(SLOT),
176 eq::<i32>(sfield::AssetsTotal.into()),
177 always(),
178 eq(VALUE.len()),
179 )
180 .times(1)
181 .returning(|_, _, out, out_len| {
182 unsafe { out.copy_from_nonoverlapping(VALUE.as_ptr(), VALUE.len()) }
183 out_len as i32
184 });
185 let _guard = setup_mock(mock);
186
187 assert_eq!(
188 get_field(SLOT, sfield::AssetsTotal).unwrap(),
189 Number::from(VALUE)
190 );
191 }
192
193 #[test]
194 fn test_get_field_optional_returns_none_on_field_not_found() {
195 let mut mock = MockHostBindings::new();
196 mock.expect_le_field()
197 .with(
198 eq(SLOT),
199 eq::<i32>(sfield::SourceTag.into()),
200 always(),
201 eq(4),
202 )
203 .times(1)
204 .returning(|_, _, _, _| FIELD_NOT_FOUND);
205 let _guard = setup_mock(mock);
206
207 let result = get_field_optional::<u32, _>(SLOT, sfield::SourceTag);
208 assert!(result.is_ok());
209 assert!(result.unwrap().is_none());
210 }
211
212 #[test]
213 fn test_get_field_optional_returns_some_when_present() {
214 let mut mock = MockHostBindings::new();
215 expect_ledger_obj_field(&mut mock, SLOT, sfield::SourceTag.into(), 4, 1);
216 let _guard = setup_mock(mock);
217
218 let result = get_field_optional::<u32, _>(SLOT, sfield::SourceTag);
219 assert!(result.is_ok());
220 assert!(result.unwrap().is_some());
221 }
222
223 #[test]
224 fn test_get_field_returns_decode_error_on_byte_mismatch() {
225 let mut mock = MockHostBindings::new();
226 mock.expect_le_field()
227 .with(
228 eq(SLOT),
229 eq::<i32>(sfield::Sequence.into()),
230 always(),
231 eq(4),
232 )
233 .times(1)
234 .returning(|_, _, _, _| 3);
235 let _guard = setup_mock(mock);
236
237 let result = get_field::<u32, _>(SLOT, sfield::Sequence);
238 assert!(result.is_err());
239 assert_eq!(
240 result.err().unwrap().code(),
241 crate::host::Error::InvalidDecoding.code()
242 );
243 }
244
245 #[test]
246 fn test_get_field_returns_err_on_internal_error() {
247 let mut mock = MockHostBindings::new();
248 mock.expect_le_field()
249 .with(eq(SLOT), eq::<i32>(sfield::Flags.into()), always(), eq(4))
250 .times(1)
251 .returning(|_, _, _, _| SOME_ERROR);
252 let _guard = setup_mock(mock);
253
254 let result = get_field::<u32, _>(SLOT, sfield::Flags);
255 assert!(result.is_err());
256 assert_eq!(result.err().unwrap().code(), SOME_ERROR);
257 }
258
259 #[test]
260 fn test_get_field_returns_err_when_host_reports_oversized_write() {
261 let mut mock = MockHostBindings::new();
264 mock.expect_le_field()
265 .with(
266 eq(SLOT),
267 eq::<i32>(sfield::Sequence.into()),
268 always(),
269 eq(4),
270 )
271 .times(1)
272 .returning(|_, _, _, _| 8); let _guard = setup_mock(mock);
274
275 let result = get_field::<u32, _>(SLOT, sfield::Sequence);
276 assert!(result.is_err());
277 assert_eq!(
278 result.err().unwrap().code(),
279 crate::host::Error::PointerOutOfBounds.code()
280 );
281 }
282
283 #[test]
284 fn test_get_blob_field_writes_bytes_directly_into_blob_data() {
285 let mut mock = MockHostBindings::new();
286 mock.expect_le_field()
287 .with(
288 eq(SLOT),
289 eq::<i32>(sfield::Condition.into()),
290 always(),
291 eq(128),
292 )
293 .times(1)
294 .returning(|_, _, buf, size| {
295 let slice = unsafe { core::slice::from_raw_parts_mut(buf, size) };
297 slice.fill(0xAB);
298 size as i32
299 });
300 let _guard = setup_mock(mock);
301
302 let blob = get_blob_field(SLOT, sfield::Condition).unwrap();
303 assert_eq!(blob.len(), 128);
304 assert!(blob.as_slice().iter().all(|&b| b == 0xAB));
305 }
306
307 #[test]
308 fn test_get_blob_field_zeroes_tail_when_host_writes_fewer_bytes() {
309 let mut mock = MockHostBindings::new();
312 mock.expect_le_field()
313 .with(
314 eq(SLOT),
315 eq::<i32>(sfield::Condition.into()),
316 always(),
317 eq(128),
318 )
319 .times(1)
320 .returning(|_, _, buf, _size| {
321 let slice = unsafe { core::slice::from_raw_parts_mut(buf, 10) };
322 slice.fill(0xFF);
323 10
324 });
325 let _guard = setup_mock(mock);
326
327 let blob = get_blob_field(SLOT, sfield::Condition).unwrap();
328 assert_eq!(blob.len(), 10);
329 assert_eq!(blob.data[9], 0xFF);
330 assert_eq!(blob.data[10], 0);
331 assert_eq!(blob.data[127], 0);
332 }
333
334 #[test]
335 fn test_get_blob_field_returns_err_on_internal_error() {
336 let mut mock = MockHostBindings::new();
337 mock.expect_le_field()
338 .with(
339 eq(SLOT),
340 eq::<i32>(sfield::Condition.into()),
341 always(),
342 eq(128),
343 )
344 .times(1)
345 .returning(|_, _, _, _| SOME_ERROR);
346 let _guard = setup_mock(mock);
347
348 let result = get_blob_field(SLOT, sfield::Condition);
349 assert!(result.is_err());
350 assert_eq!(result.err().unwrap().code(), SOME_ERROR);
351 }
352
353 #[test]
354 fn test_get_blob_field_returns_err_when_host_reports_oversized_write() {
355 let mut mock = MockHostBindings::new();
356 mock.expect_le_field()
357 .with(
358 eq(SLOT),
359 eq::<i32>(sfield::Condition.into()),
360 always(),
361 eq(128),
362 )
363 .times(1)
364 .returning(|_, _, _, _| 129); let _guard = setup_mock(mock);
366
367 let result = get_blob_field(SLOT, sfield::Condition);
368 assert!(result.is_err());
369 assert_eq!(
370 result.err().unwrap().code(),
371 crate::host::Error::PointerOutOfBounds.code()
372 );
373 }
374
375 #[test]
376 fn test_get_blob_field_optional_returns_none_on_field_not_found() {
377 let mut mock = MockHostBindings::new();
378 mock.expect_le_field()
379 .with(
380 eq(SLOT),
381 eq::<i32>(sfield::Condition.into()),
382 always(),
383 eq(128),
384 )
385 .times(1)
386 .returning(|_, _, _, _| FIELD_NOT_FOUND);
387 let _guard = setup_mock(mock);
388
389 let result = get_blob_field_optional(SLOT, sfield::Condition);
390 assert!(result.is_ok());
391 assert!(result.unwrap().is_none());
392 }
393
394 #[test]
395 fn test_get_blob_field_optional_returns_some_when_present() {
396 let mut mock = MockHostBindings::new();
397 expect_ledger_obj_field(&mut mock, SLOT, sfield::Condition.into(), 128, 1);
398 let _guard = setup_mock(mock);
399
400 let result = get_blob_field_optional(SLOT, sfield::Condition);
401 assert!(result.is_ok());
402 assert!(result.unwrap().is_some());
403 }
404}