Skip to main content

xrpl_escrow_stdlib/ledger_objects/
escrow_storage.rs

1use 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    /// Length-prefixed test payload: 4-byte big-endian length header followed by the
46    /// payload bytes. Self-describing so `decode` works whether it's handed an exact-size
47    /// slice or a larger buffer padded with trailing zeros.
48    #[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        // Fewer bytes than TestPayload's 4-byte header, so decode fails.
126        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}