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