xrpl_wasm_stdlib/core/ledger_objects/traits.rs
1use crate::core::ledger_objects::{current_ledger_object, ledger_object};
2use crate::core::types::account_id::AccountID;
3use crate::core::types::amount::Amount;
4use crate::core::types::blob::{Blob, CONDITION_BLOB_SIZE, ConditionBlob, UriBlob, WasmBlob};
5use crate::core::types::contract_data::{ContractData, XRPL_CONTRACT_DATA_SIZE};
6use crate::core::types::public_key::PUBLIC_KEY_BUFFER_SIZE;
7use crate::core::types::uint::{Hash128, Hash256};
8
9/// This module provides traits for interacting with XRP Ledger objects.
10///
11/// It defines common interfaces for accessing and manipulating different types of ledger objects,
12/// particularly focusing on Escrow objects. The traits provide methods to get and set various
13/// fields of ledger objects, with separate traits for current ledger objects and general ledger objects.
14use crate::host::error_codes::{match_result_code, match_result_code_optional};
15use crate::host::{Error, get_current_ledger_obj_field, get_ledger_obj_field, update_data};
16use crate::host::{Result, Result::Err, Result::Ok};
17use crate::sfield;
18
19/// Trait providing access to common fields present in all ledger objects.
20///
21/// This trait defines methods to access standard fields that are common across
22/// different types of ledger objects in the XRP Ledger.
23pub trait LedgerObjectCommonFields {
24 // NOTE: `get_ledger_index()` is not in this trait because `sfLedgerIndex` is not actually a field on a ledger
25 // object (it's a synthetic field that maps to the `index` field, which is the unique ID of an object in the
26 // ledger's state tree). See https://github.com/XRPLF/rippled/issues/3649 for more context.
27
28 /// Returns the slot number (register number) where the ledger object is stored.
29 ///
30 /// This number is used to identify and access the specific ledger object
31 /// when retrieving or modifying its fields.
32 ///
33 /// # Returns
34 ///
35 /// The slot number as an i32 value
36 fn get_slot_num(&self) -> i32;
37
38 /// Retrieves the flags field of the ledger object.
39 ///
40 /// # Arguments
41 ///
42 /// * `register_num` - The register number where the ledger object is stored
43 ///
44 /// # Returns
45 ///
46 /// The flags as a u32 value
47 fn get_flags(&self) -> Result<u32> {
48 ledger_object::get_field(self.get_slot_num(), sfield::Flags)
49 }
50
51 /// Retrieves the ledger entry type of the object.
52 ///
53 /// The value 0x0075, mapped to the string Escrow, indicates that this is an Escrow entry.
54 ///
55 /// # Returns
56 ///
57 /// The ledger entry type as a u16 value
58 fn get_ledger_entry_type(&self) -> Result<u16> {
59 current_ledger_object::get_field(sfield::LedgerEntryType)
60 }
61}
62
63/// Trait providing access to common fields in the current ledger object.
64///
65/// This trait defines methods to access standard fields that are common across
66/// different types of ledger objects, specifically for the current ledger object
67/// being processed.
68pub trait CurrentLedgerObjectCommonFields {
69 // NOTE: `get_ledger_index()` is not in this trait because `sfLedgerIndex` is not actually a field on a ledger
70 // object (it's a synthetic field that maps to the `index` field, which is the unique ID of an object in the
71 // ledger's state tree). See https://github.com/XRPLF/rippled/issues/3649 for more context.
72
73 /// Retrieves the flags field of the current ledger object.
74 ///
75 /// # Returns
76 ///
77 /// The flags as a u32 value
78 fn get_flags(&self) -> Result<u32> {
79 current_ledger_object::get_field(sfield::Flags)
80 }
81
82 /// Retrieves the ledger entry type of the current ledger object.
83 ///
84 /// The value 0x0075, mapped to the string Escrow, indicates that this is an Escrow entry.
85 ///
86 /// # Returns
87 ///
88 /// The ledger entry type as a u16 value
89 fn get_ledger_entry_type(&self) -> Result<u16> {
90 current_ledger_object::get_field(sfield::LedgerEntryType)
91 }
92}
93
94/// Trait providing access to fields specific to Escrow objects in the current ledger.
95///
96/// This trait extends `CurrentLedgerObjectCommonFields` and provides methods to access
97/// fields that are specific to Escrow objects in the current ledger being processed.
98pub trait CurrentEscrowFields: CurrentLedgerObjectCommonFields {
99 /// The address of the owner (sender) of this escrow. This is the account that provided the XRP
100 /// and gets it back if the escrow is canceled.
101 fn get_account(&self) -> Result<AccountID> {
102 current_ledger_object::get_field(sfield::Account)
103 }
104
105 /// The amount currently held in the escrow (could be XRP, IOU, or MPT).
106 fn get_amount(&self) -> Result<Amount> {
107 current_ledger_object::get_field(sfield::Amount)
108 }
109
110 /// The escrow can be canceled if and only if this field is present and the time it specifies
111 /// has passed. Specifically, this is specified as seconds since the Ripple Epoch and it
112 /// "has passed" if it's earlier than the close time of the previous validated ledger.
113 fn get_cancel_after(&self) -> Result<Option<u32>> {
114 current_ledger_object::get_field_optional(sfield::CancelAfter)
115 }
116
117 /// A PREIMAGE-SHA-256 crypto-condition in full crypto-condition format. If present, the EscrowFinish
118 /// transaction must contain a fulfillment that satisfies this condition.
119 fn get_condition(&self) -> Result<Option<ConditionBlob>> {
120 let mut buffer = [0u8; CONDITION_BLOB_SIZE];
121
122 let result_code = unsafe {
123 get_current_ledger_obj_field(sfield::Condition, buffer.as_mut_ptr(), buffer.len())
124 };
125
126 match_result_code_optional(result_code, || {
127 if result_code > 0 {
128 let blob = ConditionBlob {
129 data: buffer,
130 len: result_code as usize,
131 };
132 Some(blob)
133 } else {
134 None
135 }
136 })
137 }
138
139 /// The destination address where the XRP is paid if the escrow is successful.
140 fn get_destination(&self) -> Result<AccountID> {
141 current_ledger_object::get_field(sfield::Destination)
142 }
143
144 /// A hint indicating which page of the destination's owner directory links to this object, in
145 /// case the directory consists of multiple pages. Omitted on escrows created before enabling the fix1523 amendment.
146 fn get_destination_node(&self) -> Result<Option<u64>> {
147 current_ledger_object::get_field_optional(sfield::DestinationNode)
148 }
149
150 /// An arbitrary tag to further specify the destination for this escrow, such as a hosted
151 /// recipient at the destination address.
152 fn get_destination_tag(&self) -> Result<Option<u32>> {
153 current_ledger_object::get_field_optional(sfield::DestinationTag)
154 }
155
156 /// The time, in seconds since the Ripple Epoch, after which this escrow can be finished. Any
157 /// EscrowFinish transaction before this time fails. (Specifically, this is compared with the
158 /// close time of the previous validated ledger.)
159 fn get_finish_after(&self) -> Result<Option<u32>> {
160 current_ledger_object::get_field_optional(sfield::FinishAfter)
161 }
162
163 // TODO: Implement this function.
164 // /// The value 0x0075, mapped to the string Escrow, indicates that this is an Escrow entry.
165 // fn get_ledger_entry_type(&self) -> Result<LedgerEntryType> {
166 // return Ok(LedgerEntryType::Escrow);
167 // }
168
169 /// A hint indicating which page of the sender's owner directory links to this entry, in case
170 /// the directory consists of multiple pages.
171 fn get_owner_node(&self) -> Result<u64> {
172 current_ledger_object::get_field(sfield::OwnerNode)
173 }
174
175 /// The identifying hash of the transaction that most recently modified this entry.
176 fn get_previous_txn_id(&self) -> Result<Hash256> {
177 current_ledger_object::get_field(sfield::PreviousTxnID)
178 }
179
180 /// The index of the ledger that contains the transaction that most recently modified this
181 /// entry.
182 fn get_previous_txn_lgr_seq(&self) -> Result<u32> {
183 current_ledger_object::get_field(sfield::PreviousTxnLgrSeq)
184 }
185
186 /// An arbitrary tag to further specify the source for this escrow, such as a hosted recipient
187 /// at the owner's address.
188 fn get_source_tag(&self) -> Result<Option<u32>> {
189 current_ledger_object::get_field_optional(sfield::SourceTag)
190 }
191
192 /// The WASM code that is executing.
193 fn get_finish_function(&self) -> Result<Option<WasmBlob>> {
194 current_ledger_object::get_field_optional(sfield::FinishFunction)
195 }
196
197 /// Retrieves the contract `data` from the current escrow object.
198 ///
199 /// This function fetches the `data` field from the current ledger object and returns it as a
200 /// ContractData structure. The data is read into a fixed-size buffer of XRPL_CONTRACT_DATA_SIZE.
201 ///
202 /// # Returns
203 ///
204 /// Returns a `Result<ContractData>` where:
205 /// * `Ok(ContractData)` - Contains the retrieved data and its actual length
206 /// * `Err(Error)` - If the retrieval operation failed
207 fn get_data(&self) -> Result<ContractData> {
208 let mut data: [u8; XRPL_CONTRACT_DATA_SIZE] = [0; XRPL_CONTRACT_DATA_SIZE];
209
210 let result_code =
211 unsafe { get_current_ledger_obj_field(sfield::Data, data.as_mut_ptr(), data.len()) };
212
213 match result_code {
214 code if code >= 0 => Ok(ContractData {
215 data,
216 len: code as usize,
217 }),
218 code => Err(Error::from_code(code)),
219 }
220 }
221
222 /// Updates the contract data in the current escrow object.
223 ///
224 /// # Arguments
225 ///
226 /// * `data` - The contract data to update
227 ///
228 /// # Returns
229 ///
230 /// Returns a `Result<()>` where:
231 /// * `Ok(())` - The data was successfully updated
232 /// * `Err(Error)` - If the update operation failed
233 fn update_current_escrow_data(data: ContractData) -> Result<()> {
234 // TODO: Make sure rippled always deletes any existing data bytes in rippled, and sets the new
235 // length to be `data.len` (e.g., if the developer writes 2 bytes, then that's the new
236 // length and any old bytes are lost).
237 let result_code = unsafe { update_data(data.data.as_ptr(), data.len) };
238 match_result_code(result_code, || ())
239 }
240}
241
242/// Trait providing access to fields specific to Escrow objects in any ledger.
243///
244/// This trait extends `LedgerObjectCommonFields` and provides methods to access
245/// fields that are specific to Escrow objects in any ledger, not just the current one.
246/// Each method requires a register number to identify which ledger object to access.
247pub trait EscrowFields: LedgerObjectCommonFields {
248 /// The address of the owner (sender) of this escrow. This is the account that provided the XRP
249 /// and gets it back if the escrow is canceled.
250 fn get_account(&self) -> Result<AccountID> {
251 ledger_object::get_field(self.get_slot_num(), sfield::Account)
252 }
253
254 /// The amount of XRP, in drops, currently held in the escrow.
255 fn get_amount(&self) -> Result<Amount> {
256 // Create a buffer large enough for any Amount type
257 const BUFFER_SIZE: usize = 48usize;
258 let mut buffer = [0u8; BUFFER_SIZE];
259
260 let result_code = unsafe {
261 get_ledger_obj_field(
262 self.get_slot_num(),
263 sfield::Amount,
264 buffer.as_mut_ptr(),
265 buffer.len(),
266 )
267 };
268
269 match_result_code(result_code, || Amount::from(buffer))
270 }
271
272 /// The escrow can be canceled if and only if this field is present and the time it specifies
273 /// has passed. Specifically, this is specified as seconds since the Ripple Epoch and it
274 /// "has passed" if it's earlier than the close time of the previous validated ledger.
275 fn get_cancel_after(&self) -> Result<Option<u32>> {
276 ledger_object::get_field_optional(self.get_slot_num(), sfield::CancelAfter)
277 }
278
279 /// A PREIMAGE-SHA-256 crypto-condition in full crypto-condition format. If present, the EscrowFinish
280 /// transaction must contain a fulfillment that satisfies this condition.
281 fn get_condition(&self) -> Result<Option<ConditionBlob>> {
282 let mut buffer = [0u8; CONDITION_BLOB_SIZE];
283
284 let result_code = unsafe {
285 get_ledger_obj_field(
286 self.get_slot_num(),
287 sfield::Condition,
288 buffer.as_mut_ptr(),
289 buffer.len(),
290 )
291 };
292
293 match_result_code_optional(result_code, || {
294 if result_code > 0 {
295 let blob = ConditionBlob {
296 data: buffer,
297 len: result_code as usize,
298 };
299 Some(blob)
300 } else {
301 None
302 }
303 })
304 }
305
306 /// The destination address where the XRP is paid if the escrow is successful.
307 fn get_destination(&self) -> Result<AccountID> {
308 ledger_object::get_field(self.get_slot_num(), sfield::Destination)
309 }
310
311 /// A hint indicating which page of the destination's owner directory links to this object, in
312 /// case the directory consists of multiple pages. Omitted on escrows created before enabling the fix1523 amendment.
313 fn get_destination_node(&self) -> Result<Option<u64>> {
314 ledger_object::get_field_optional(self.get_slot_num(), sfield::DestinationNode)
315 }
316
317 /// An arbitrary tag to further specify the destination for this escrow, such as a hosted
318 /// recipient at the destination address.
319 fn get_destination_tag(&self) -> Result<Option<u32>> {
320 ledger_object::get_field_optional(self.get_slot_num(), sfield::DestinationTag)
321 }
322
323 /// The time, in seconds since the Ripple Epoch, after which this escrow can be finished. Any
324 /// EscrowFinish transaction before this time fails. (Specifically, this is compared with the
325 /// close time of the previous validated ledger.)
326 fn get_finish_after(&self) -> Result<Option<u32>> {
327 ledger_object::get_field_optional(self.get_slot_num(), sfield::FinishAfter)
328 }
329
330 // TODO: Implement this function.
331 // /// The value 0x0075, mapped to the string Escrow, indicates that this is an Escrow entry.
332 // fn get_ledger_entry_type(&self) -> Result<LedgerEntryType> {
333 // return Ok(LedgerEntryType::Escrow);
334 // }
335
336 /// A hint indicating which page of the sender's owner directory links to this entry, in case
337 /// the directory consists of multiple pages.
338 fn get_owner_node(&self) -> Result<u64> {
339 ledger_object::get_field(self.get_slot_num(), sfield::OwnerNode)
340 }
341
342 /// The identifying hash of the transaction that most recently modified this entry.
343 fn get_previous_txn_id(&self) -> Result<Hash256> {
344 ledger_object::get_field(self.get_slot_num(), sfield::PreviousTxnID)
345 }
346
347 /// The index of the ledger that contains the transaction that most recently modified this
348 /// entry.
349 fn get_previous_txn_lgr_seq(&self) -> Result<u32> {
350 ledger_object::get_field(self.get_slot_num(), sfield::PreviousTxnLgrSeq)
351 }
352
353 /// An arbitrary tag to further specify the source for this escrow, such as a hosted recipient
354 /// at the owner's address.
355 fn get_source_tag(&self) -> Result<Option<u32>> {
356 ledger_object::get_field_optional(self.get_slot_num(), sfield::SourceTag)
357 }
358
359 /// The WASM code that is executing.
360 fn get_finish_function(&self) -> Result<Option<WasmBlob>> {
361 ledger_object::get_field_optional(self.get_slot_num(), sfield::FinishFunction)
362 }
363
364 /// Retrieves the contract data from the specified ledger object.
365 ///
366 /// This function fetches the `data` field from the ledger object at the specified register
367 /// and returns it as a ContractData structure. The data is read into a fixed-size buffer
368 /// of XRPL_CONTRACT_DATA_SIZE.
369 ///
370 /// # Arguments
371 ///
372 /// * `register_num` - The register number where the ledger object is stored
373 ///
374 /// # Returns
375 ///
376 /// Returns a `Result<ContractData>` where:
377 /// * `Ok(ContractData)` - Contains the retrieved data and its actual length
378 /// * `Err(Error)` - If the retrieval operation failed
379 fn get_data(&self) -> Result<ContractData> {
380 let mut data: [u8; XRPL_CONTRACT_DATA_SIZE] = [0; XRPL_CONTRACT_DATA_SIZE];
381
382 let result_code = unsafe {
383 get_ledger_obj_field(
384 self.get_slot_num(),
385 sfield::Data,
386 data.as_mut_ptr(),
387 data.len(),
388 )
389 };
390
391 match result_code {
392 code if code >= 0 => Ok(ContractData {
393 data,
394 len: code as usize,
395 }),
396 code => Err(Error::from_code(code)),
397 }
398 }
399}
400
401/// Trait providing access to fields specific to AccountRoot objects in any ledger.
402///
403/// This trait extends `LedgerObjectCommonFields` and provides methods to access
404/// fields that are specific to Escrow objects in any ledger, not just the current one.
405/// Each method requires a register number to identify which ledger object to access.
406pub trait AccountFields: LedgerObjectCommonFields {
407 /// The identifying address of the account.
408 fn get_account(&self) -> Result<AccountID> {
409 ledger_object::get_field(self.get_slot_num(), sfield::Account)
410 }
411
412 /// AccountTxnID field for the account.
413 fn account_txn_id(&self) -> Result<Option<Hash256>> {
414 ledger_object::get_field_optional(self.get_slot_num(), sfield::AccountTxnID)
415 }
416
417 /// The ledger entry ID of the corresponding AMM ledger entry. Set during account creation; cannot be modified.
418 /// If present, indicates that this is a special AMM AccountRoot; always omitted on non-AMM accounts.
419 /// (Added by the AMM amendment)
420 fn amm_id(&self) -> Result<Option<Hash256>> {
421 ledger_object::get_field_optional(self.get_slot_num(), sfield::AMMID)
422 }
423
424 /// The account's current XRP balance in drops.
425 fn balance(&self) -> Result<Option<Amount>> {
426 ledger_object::get_field_optional(self.get_slot_num(), sfield::Balance)
427 }
428
429 /// How many total of this account's issued non-fungible tokens have been burned.
430 /// This number is always equal or less than MintedNFTokens.
431 fn burned_nf_tokens(&self) -> Result<Option<u32>> {
432 ledger_object::get_field_optional(self.get_slot_num(), sfield::BurnedNFTokens)
433 }
434
435 /// A domain associated with this account. In JSON, this is the hexadecimal for the ASCII representation of the
436 /// domain. Cannot be more than 256 bytes in length.
437 fn domain(&self) -> Result<Option<UriBlob>> {
438 ledger_object::get_field_optional(self.get_slot_num(), sfield::Domain)
439 }
440
441 /// The MD5 hash of an email address. Clients can use this to look up an avatar through services such as Gravatar.
442 fn email_hash(&self) -> Result<Option<Hash128>> {
443 ledger_object::get_field_optional(self.get_slot_num(), sfield::EmailHash)
444 }
445
446 /// The account's Sequence Number at the time it minted its first non-fungible-token.
447 /// (Added by the fixNFTokenRemint amendment)
448 fn first_nf_token_sequence(&self) -> Result<Option<u32>> {
449 ledger_object::get_field_optional(self.get_slot_num(), sfield::FirstNFTokenSequence)
450 }
451
452 /// The value 0x0061, mapped to the string AccountRoot, indicates that this is an AccountRoot object.
453 fn ledger_entry_type(&self) -> Result<u16> {
454 ledger_object::get_field(self.get_slot_num(), sfield::LedgerEntryType)
455 }
456
457 /// A public key that may be used to send encrypted messages to this account. In JSON, uses hexadecimal.
458 /// Must be exactly 33 bytes, with the first byte indicating the key type: 0x02 or 0x03 for secp256k1 keys,
459 /// 0xED for Ed25519 keys.
460 fn message_key(&self) -> Result<Option<Blob<{ PUBLIC_KEY_BUFFER_SIZE }>>> {
461 ledger_object::get_field_optional(self.get_slot_num(), sfield::MessageKey)
462 }
463
464 /// How many total non-fungible tokens have been minted by and on behalf of this account.
465 /// (Added by the NonFungibleTokensV1_1 amendment)
466 fn minted_nf_tokens(&self) -> Result<Option<u32>> {
467 ledger_object::get_field_optional(self.get_slot_num(), sfield::MintedNFTokens)
468 }
469
470 /// Another account that can mint non-fungible tokens on behalf of this account.
471 /// (Added by the NonFungibleTokensV1_1 amendment)
472 fn nf_token_minter(&self) -> Result<Option<AccountID>> {
473 ledger_object::get_field_optional(self.get_slot_num(), sfield::NFTokenMinter)
474 }
475
476 /// The number of objects this account owns in the ledger, which contributes to its owner reserve.
477 fn owner_count(&self) -> Result<u32> {
478 ledger_object::get_field(self.get_slot_num(), sfield::OwnerCount)
479 }
480
481 /// The identifying hash of the transaction that most recently modified this object.
482 fn previous_txn_id(&self) -> Result<Hash256> {
483 ledger_object::get_field(self.get_slot_num(), sfield::PreviousTxnID)
484 }
485
486 /// The index of the ledger that contains the transaction that most recently modified this object.
487 fn previous_txn_lgr_seq(&self) -> Result<u32> {
488 ledger_object::get_field(self.get_slot_num(), sfield::PreviousTxnLgrSeq)
489 }
490
491 /// The address of a key pair that can be used to sign transactions for this account instead of the master key.
492 /// Use a SetRegularKey transaction to change this value.
493 fn regular_key(&self) -> Result<Option<AccountID>> {
494 ledger_object::get_field_optional(self.get_slot_num(), sfield::RegularKey)
495 }
496
497 /// The sequence number of the next valid transaction for this account.
498 fn sequence(&self) -> Result<u32> {
499 ledger_object::get_field(self.get_slot_num(), sfield::Sequence)
500 }
501
502 /// How many Tickets this account owns in the ledger. This is updated automatically to ensure that
503 /// the account stays within the hard limit of 250 Tickets at a time. This field is omitted if the account has zero
504 /// Tickets. (Added by the TicketBatch amendment.)
505 fn ticket_count(&self) -> Result<Option<u32>> {
506 ledger_object::get_field_optional(self.get_slot_num(), sfield::TicketCount)
507 }
508
509 /// How many significant digits to use for exchange rates of Offers involving currencies issued by this address.
510 /// Valid values are 3 to 15, inclusive. (Added by the TickSize amendment.)
511 fn tick_size(&self) -> Result<Option<u8>> {
512 ledger_object::get_field_optional(self.get_slot_num(), sfield::TickSize)
513 }
514
515 /// A transfer fee to charge other users for sending currency issued by this account to each other.
516 fn transfer_rate(&self) -> Result<Option<u32>> {
517 ledger_object::get_field_optional(self.get_slot_num(), sfield::TransferRate)
518 }
519
520 /// An arbitrary 256-bit value that users can set.
521 fn wallet_locator(&self) -> Result<Option<Hash256>> {
522 ledger_object::get_field_optional(self.get_slot_num(), sfield::WalletLocator)
523 }
524}
525
526#[cfg(test)]
527mod tests {
528 // NOTE: Will be replaced fully by the next PR that adds new unit tests.
529}