xrpl_stdlib_test_utils/
mock_escrow.rs1use crate::mock_common::{MockGuard, MockHostBindings, apply_default_expectations, setup_mock};
7use xrpl_common_stdlib::host::Error;
8use xrpl_common_stdlib::host::error_codes::BUFFER_TOO_SMALL;
9use xrpl_common_stdlib::sfield;
10use xrpl_common_stdlib::types::account_id::AccountID;
11use xrpl_common_stdlib::types::amount::{AMOUNT_SIZE, Amount};
12
13pub struct EscrowScenario;
22
23impl EscrowScenario {
24 pub fn builder() -> EscrowScenarioBuilder {
25 EscrowScenarioBuilder::default()
26 }
27}
28
29#[derive(Default)]
30pub struct EscrowScenarioBuilder {
31 account: Option<AccountID>,
32 amount: Option<Amount>,
33 set_data_status: Option<i32>,
36}
37
38impl EscrowScenarioBuilder {
39 pub fn with_account(mut self, account: AccountID) -> Self {
40 self.account = Some(account);
41 self
42 }
43
44 pub fn with_amount(mut self, amount: Amount) -> Self {
45 self.amount = Some(amount);
46 self
47 }
48
49 pub fn with_set_data_returns(mut self, result: Result<(), Error>) -> Self {
50 self.set_data_status = Some(match result {
51 Ok(()) => 0,
52 Err(error) => error.code(),
53 });
54 self
55 }
56
57 pub fn build(self) -> MockHostBindings {
60 let mut mock = MockHostBindings::new();
61 self.apply(&mut mock);
62 apply_default_expectations(&mut mock);
63 mock
64 }
65
66 pub fn build_onto(self, mut mock: MockHostBindings) -> MockHostBindings {
70 self.apply(&mut mock);
71 mock
72 }
73
74 pub fn install(self) -> MockGuard {
77 setup_mock(self.build())
78 }
79
80 fn apply(&self, mock: &mut MockHostBindings) {
81 if self.account.is_some() || self.amount.is_some() {
82 let account = self.account;
83 let amount = self.amount.clone();
84 let account_code = i32::from(sfield::Account);
85 let amount_code = i32::from(sfield::Amount);
86
87 mock.expect_tx_field()
88 .returning(move |field, out_buff_ptr, out_buff_len| {
89 if field == account_code
90 && let Some(account) = account
91 {
92 return write_bytes(&account.0, out_buff_ptr, out_buff_len);
93 }
94 if field == amount_code
95 && let Some(amount) = &amount
96 {
97 let (bytes, _) = amount.to_stamount_bytes();
102 let len = match amount {
103 Amount::XRP { .. } => 8,
104 Amount::MPT { .. } => 33,
105 Amount::IOU { .. } => AMOUNT_SIZE,
106 };
107 return write_bytes(&bytes[..len], out_buff_ptr, out_buff_len);
108 }
109 out_buff_len as i32
110 });
111 }
112
113 if let Some(status) = self.set_data_status {
114 mock.expect_set_data().returning(
115 move |_data_ptr, data_len| {
116 if status == 0 { data_len as i32 } else { status }
117 },
118 );
119 }
120 }
121}
122
123fn write_bytes(bytes: &[u8], out_buff_ptr: *mut u8, out_buff_len: usize) -> i32 {
126 if out_buff_len < bytes.len() {
127 return BUFFER_TOO_SMALL;
128 }
129 unsafe {
130 std::ptr::copy_nonoverlapping(bytes.as_ptr(), out_buff_ptr, bytes.len());
131 }
132 bytes.len() as i32
133}
134
135#[cfg(test)]
136mod tests {
137 use super::*;
138 use xrpl_common_stdlib::current_tx::get_field;
139
140 fn test_account() -> AccountID {
141 AccountID::from([0xAB; 20])
142 }
143
144 #[test]
145 fn write_bytes_returns_buffer_too_small_when_the_caller_buffer_is_undersized() {
146 let mut undersized = [0u8; 4];
147 let result = write_bytes(&[1, 2, 3, 4, 5], undersized.as_mut_ptr(), undersized.len());
148 assert_eq!(result, BUFFER_TOO_SMALL);
149 }
150
151 #[test]
152 fn with_account_is_readable_back_through_the_real_getter() {
153 let _guard = EscrowScenario::builder()
154 .with_account(test_account())
155 .install();
156
157 let account: AccountID = get_field(sfield::Account).unwrap();
158 assert_eq!(account, test_account());
159 }
160
161 #[test]
162 fn with_amount_is_readable_back_through_the_real_getter() {
163 let configured = Amount::XRP { num_drops: 1_000 };
164 let _guard = EscrowScenario::builder()
165 .with_amount(configured.clone())
166 .install();
167
168 let amount: Amount = get_field(sfield::Amount).unwrap();
169 assert_eq!(amount, configured);
170 }
171
172 #[test]
173 fn unconfigured_fields_fall_back_to_defaults() {
174 let _guard = EscrowScenario::builder()
175 .with_account(test_account())
176 .install();
177
178 let result: xrpl_common_stdlib::host::Result<u32> = get_field(sfield::OfferSequence);
181 assert!(result.is_ok());
182 }
183
184 #[test]
185 fn with_set_data_returns_ok_reports_the_payload_length_through_the_real_host_call() {
186 let _guard = EscrowScenario::builder()
187 .with_set_data_returns(Ok(()))
188 .install();
189
190 let payload = b"payload";
191 let code = unsafe { xrpl_common_stdlib::host::set_data(payload.as_ptr(), payload.len()) };
192 assert_eq!(code, payload.len() as i32);
193 }
194
195 #[test]
196 fn with_set_data_returns_err_reports_the_error_code_through_the_real_host_call() {
197 let _guard = EscrowScenario::builder()
198 .with_set_data_returns(Err(Error::from_code(
199 xrpl_common_stdlib::host::error_codes::SOME_ERROR,
200 )))
201 .install();
202
203 let payload = b"payload";
204 let code = unsafe { xrpl_common_stdlib::host::set_data(payload.as_ptr(), payload.len()) };
205 assert_eq!(
206 code,
207 Error::from_code(xrpl_common_stdlib::host::error_codes::SOME_ERROR).code()
208 );
209 assert!(code < 0);
210 }
211
212 #[test]
213 fn build_onto_lets_the_caller_override_the_scenario() {
214 let overridden_account = AccountID::from([0u8; 20]);
215 let mut mock = MockHostBindings::new();
216 let expected_code: i32 = i32::from(sfield::Account);
217 mock.expect_tx_field()
218 .withf(move |field, _, _| *field == expected_code)
219 .returning(move |_, out_buff_ptr, out_buff_len| {
220 write_bytes(&overridden_account.0, out_buff_ptr, out_buff_len)
221 });
222
223 let mock = EscrowScenario::builder()
226 .with_account(test_account())
227 .build_onto(mock);
228
229 let _guard = setup_mock(mock);
230 let account: AccountID = get_field(sfield::Account).unwrap();
231 assert_eq!(account, overridden_account);
232 assert_ne!(account, test_account());
233 }
234}