xrpl_stdlib_test_utils/
mock_escrow.rs1use crate::mock_common::{MockGuard, MockHostBindings, apply_default_expectations, setup_mock};
7use xrpl_wasm_stdlib::core::types::account_id::AccountID;
8use xrpl_wasm_stdlib::core::types::amount::Amount;
9use xrpl_wasm_stdlib::host::Error;
10use xrpl_wasm_stdlib::host::error_codes::BUFFER_TOO_SMALL;
11use xrpl_wasm_stdlib::sfield;
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 update_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_update_data_returns(mut self, result: Result<(), Error>) -> Self {
50 self.update_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_get_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, len) = amount.to_stamount_bytes();
98 return write_bytes(&bytes[..len], out_buff_ptr, out_buff_len);
99 }
100 out_buff_len as i32
101 });
102 }
103
104 if let Some(status) = self.update_data_status {
105 mock.expect_update_data().returning(
106 move |_data_ptr, data_len| {
107 if status == 0 { data_len as i32 } else { status }
108 },
109 );
110 }
111 }
112}
113
114fn write_bytes(bytes: &[u8], out_buff_ptr: *mut u8, out_buff_len: usize) -> i32 {
117 if out_buff_len < bytes.len() {
118 return BUFFER_TOO_SMALL;
119 }
120 unsafe {
121 std::ptr::copy_nonoverlapping(bytes.as_ptr(), out_buff_ptr, bytes.len());
122 }
123 bytes.len() as i32
124}
125
126#[cfg(test)]
127mod tests {
128 use super::*;
129 use xrpl_wasm_stdlib::core::current_tx::get_field;
130
131 fn test_account() -> AccountID {
132 AccountID::from([0xAB; 20])
133 }
134
135 #[test]
136 fn write_bytes_returns_buffer_too_small_when_the_caller_buffer_is_undersized() {
137 let mut undersized = [0u8; 4];
138 let result = write_bytes(&[1, 2, 3, 4, 5], undersized.as_mut_ptr(), undersized.len());
139 assert_eq!(result, BUFFER_TOO_SMALL);
140 }
141
142 #[test]
143 fn with_account_is_readable_back_through_the_real_getter() {
144 let _guard = EscrowScenario::builder()
145 .with_account(test_account())
146 .install();
147
148 let account: AccountID = get_field(sfield::Account).unwrap();
149 assert_eq!(account, test_account());
150 }
151
152 #[test]
153 fn with_amount_is_readable_back_through_the_real_getter() {
154 let configured = Amount::XRP { num_drops: 1_000 };
155 let _guard = EscrowScenario::builder()
156 .with_amount(configured.clone())
157 .install();
158
159 let amount: Amount = get_field(sfield::Amount).unwrap();
160 assert_eq!(amount, configured);
161 }
162
163 #[test]
164 fn unconfigured_fields_fall_back_to_defaults() {
165 let _guard = EscrowScenario::builder()
166 .with_account(test_account())
167 .install();
168
169 let result: xrpl_wasm_stdlib::host::Result<u32> = get_field(sfield::OfferSequence);
172 assert!(result.is_ok());
173 }
174
175 #[test]
176 fn with_update_data_returns_ok_reports_the_payload_length_through_the_real_host_call() {
177 let _guard = EscrowScenario::builder()
178 .with_update_data_returns(Ok(()))
179 .install();
180
181 let payload = b"payload";
182 let code = unsafe { xrpl_wasm_stdlib::host::update_data(payload.as_ptr(), payload.len()) };
183 assert_eq!(code, payload.len() as i32);
184 }
185
186 #[test]
187 fn with_update_data_returns_err_reports_the_error_code_through_the_real_host_call() {
188 let _guard = EscrowScenario::builder()
189 .with_update_data_returns(Err(Error::InternalError))
190 .install();
191
192 let payload = b"payload";
193 let code = unsafe { xrpl_wasm_stdlib::host::update_data(payload.as_ptr(), payload.len()) };
194 assert_eq!(code, Error::InternalError.code());
195 assert!(code < 0);
196 }
197
198 #[test]
199 fn build_onto_lets_the_caller_override_the_scenario() {
200 let overridden_account = AccountID::from([0u8; 20]);
201 let mut mock = MockHostBindings::new();
202 let expected_code: i32 = i32::from(sfield::Account);
203 mock.expect_get_tx_field()
204 .withf(move |field, _, _| *field == expected_code)
205 .returning(move |_, out_buff_ptr, out_buff_len| {
206 write_bytes(&overridden_account.0, out_buff_ptr, out_buff_len)
207 });
208
209 let mock = EscrowScenario::builder()
212 .with_account(test_account())
213 .build_onto(mock);
214
215 let _guard = setup_mock(mock);
216 let account: AccountID = get_field(sfield::Account).unwrap();
217 assert_eq!(account, overridden_account);
218 assert_ne!(account, test_account());
219 }
220}