xrpl_escrow_stdlib/ledger_objects/
escrow_storage.rs1use crate::ctx::escrow_finish::EscrowFinishContext;
2use crate::ledger_objects::traits::CurrentEscrowFields;
3use xrpl_common_stdlib::host::Result;
4use xrpl_common_stdlib::types::contract_data::XRPL_CONTRACT_DATA_SIZE;
5
6pub trait EscrowStorage: Sized {
7 fn encode(&self, out: &mut [u8]) -> Result<usize>;
8 fn decode(bytes: &[u8]) -> Result<Self>;
9}
10
11pub fn load_data<T: EscrowStorage>(ctx: &EscrowFinishContext) -> Result<Option<T>> {
12 let bytes = match ctx.escrow().get_data() {
13 Result::Ok(contract_data) => contract_data,
14 Result::Err(e) => return Result::Err(e),
15 };
16 if bytes.len == 0 {
17 return Result::Ok(None);
18 }
19 match T::decode(&bytes.data[..bytes.len]) {
20 Result::Ok(data) => Result::Ok(Some(data)),
21 Result::Err(e) => Result::Err(e),
22 }
23}
24
25pub fn save_data<T: EscrowStorage>(ctx: &EscrowFinishContext, data: &T) -> Result<()> {
26 let mut bytes = [0u8; XRPL_CONTRACT_DATA_SIZE];
27 let n = match data.encode(&mut bytes) {
28 Result::Ok(n) => n,
29 Result::Err(e) => return Result::Err(e),
30 };
31 ctx.set_data(&bytes[..n])
32}
33
34#[cfg(test)]
35mod tests {
36 use super::*;
37 use mockall::predicate::{always, eq};
38 use xrpl_common_stdlib::host::Error;
39 use xrpl_common_stdlib::host::error_codes::SOME_ERROR;
40 use xrpl_common_stdlib::host::host_bindings_trait::MockHostBindings;
41 use xrpl_common_stdlib::host::setup_mock;
42 use xrpl_common_stdlib::sfield;
43 use xrpl_stdlib_test_utils::EscrowScenario;
44
45 #[derive(Debug, PartialEq)]
49 struct TestPayload(u32);
50
51 impl EscrowStorage for TestPayload {
52 fn encode(&self, out: &mut [u8]) -> Result<usize> {
53 out[..4].copy_from_slice(&self.0.to_be_bytes());
54 Result::Ok(4)
55 }
56
57 fn decode(bytes: &[u8]) -> Result<Self> {
58 if bytes.len() < 4 {
59 return Result::Err(Error::InvalidDecoding);
60 }
61 let mut header = [0u8; 4];
62 header.copy_from_slice(&bytes[..4]);
63 Result::Ok(TestPayload(u32::from_be_bytes(header)))
64 }
65 }
66
67 fn expect_get_data(mock: &mut MockHostBindings, returning: i32, payload: Option<Vec<u8>>) {
68 mock.expect_home_le_field()
69 .with(eq(sfield::Data), always(), eq(XRPL_CONTRACT_DATA_SIZE))
70 .times(1)
71 .returning(move |_, out_buff_ptr, _| {
72 if let Some(payload) = &payload {
73 unsafe {
74 std::ptr::copy_nonoverlapping(
75 payload.as_ptr(),
76 out_buff_ptr,
77 payload.len(),
78 );
79 }
80 }
81 returning
82 });
83 }
84
85 #[test]
86 fn load_data_returns_none_when_escrow_has_no_stored_data() {
87 let mut mock = MockHostBindings::new();
88 expect_get_data(&mut mock, 0, None);
89 let _guard = setup_mock(mock);
90
91 let ctx = EscrowFinishContext::default();
92 let result: Result<Option<TestPayload>> = load_data(&ctx);
93
94 assert!(matches!(result, Result::Ok(None)));
95 }
96
97 #[test]
98 fn load_data_decodes_stored_bytes() {
99 let payload = 42u32.to_be_bytes().to_vec();
100 let mut mock = MockHostBindings::new();
101 expect_get_data(&mut mock, payload.len() as i32, Some(payload));
102 let _guard = setup_mock(mock);
103
104 let ctx = EscrowFinishContext::default();
105 let result: Result<Option<TestPayload>> = load_data(&ctx);
106
107 assert!(matches!(result, Result::Ok(Some(TestPayload(42)))));
108 }
109
110 #[test]
111 fn load_data_propagates_host_error() {
112 let mut mock = MockHostBindings::new();
113 expect_get_data(&mut mock, SOME_ERROR, None);
114 let _guard = setup_mock(mock);
115
116 let ctx = EscrowFinishContext::default();
117 let result: Result<Option<TestPayload>> = load_data(&ctx);
118
119 assert_eq!(result.err().unwrap().code(), SOME_ERROR);
120 }
121
122 #[test]
123 fn load_data_propagates_decode_error() {
124 let mut mock = MockHostBindings::new();
125 expect_get_data(&mut mock, 2, Some(vec![0xAB, 0xCD]));
127 let _guard = setup_mock(mock);
128
129 let ctx = EscrowFinishContext::default();
130 let result: Result<Option<TestPayload>> = load_data(&ctx);
131
132 assert!(result.is_err());
133 }
134
135 #[test]
136 fn save_data_writes_encoded_bytes_via_set_data() {
137 let mut mock = MockHostBindings::new();
138 mock.expect_set_data()
139 .withf(|_data_ptr, data_len| *data_len == 4)
140 .times(1)
141 .returning(|data_ptr, data_len| {
142 let bytes = unsafe { std::slice::from_raw_parts(data_ptr, data_len) };
143 assert_eq!(bytes, &42u32.to_be_bytes());
144 data_len as i32
145 });
146 let _guard = setup_mock(mock);
147
148 let ctx = EscrowFinishContext::default();
149 let result = save_data(&ctx, &TestPayload(42));
150
151 assert!(result.is_ok());
152 }
153
154 #[test]
155 fn save_data_propagates_host_error_without_swallowing_it() {
156 let _guard = EscrowScenario::builder()
157 .with_set_data_returns(Err(Error::from_code(
158 xrpl_common_stdlib::host::error_codes::SOME_ERROR,
159 )))
160 .install();
161
162 let ctx = EscrowFinishContext::default();
163 let result = save_data(&ctx, &TestPayload(42));
164
165 assert_eq!(
166 result.err().unwrap().code(),
167 Error::from_code(xrpl_common_stdlib::host::error_codes::SOME_ERROR).code()
168 );
169 }
170}