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