Skip to main content

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}