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}