xrpl_macros/lib.rs
1//! Procedural-macro entry points for `xrpl-wasm-stdlib`.
2//!
3//! Each macro here is a thin shim that delegates to its module's `expand`.
4//! Logic, helpers, and unit tests live in the per-macro files.
5//!
6//! - **Typed-constant macros** (`r_address!`, `hash256!`, `pubkey!`,
7//! `currency!`, `blob!`): validate at compile time and emit a typed XRPL
8//! value. `hex_util` holds decode helpers shared across these macros.
9//! - **Entry-point macros** (`#[smart_escrow]`, `#[smart_contract]`): wrap
10//! user functions in the `extern "C"` symbols the XRPL host calls. All
11//! three stages — parse, validate, codegen — live in `entry_point/` and are
12//! shared between the two macros so adding a third follows the same pattern.
13
14use proc_macro::TokenStream;
15
16mod blob;
17mod currency;
18mod entry_point;
19mod hash256;
20mod hex_util;
21mod pubkey;
22mod r_address;
23
24/// Converts an XRPL classic address (r-address) to a 20-byte [`AccountID`] at compile time.
25///
26/// Accepts a Base58Check-encoded string starting with `'r'`. Full checksum
27/// verification happens at compile time — a bad address is a compile error, not a
28/// runtime panic. Only string literals are accepted; runtime `&str` values are not.
29///
30/// # Example
31///
32/// ```rust,ignore
33/// use xrpl_wasm_stdlib::r_address;
34/// use xrpl_wasm_stdlib::core::types::account_id::AccountID;
35///
36/// const ACCOUNT: AccountID = r_address!("rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh");
37/// ```
38#[proc_macro]
39pub fn r_address(input: TokenStream) -> TokenStream {
40 match r_address::expand(input.into()) {
41 Ok(tokens) => tokens.into(),
42 Err(err) => err.to_compile_error().into(),
43 }
44}
45
46/// Converts a 64-character hex string to a 32-byte [`Hash256`] (`UInt<32>`) at compile time.
47///
48/// Accepts uppercase, lowercase, and mixed-case hex. Wrong length or non-hex
49/// characters are compile errors.
50///
51/// # Example
52///
53/// ```rust,ignore
54/// use xrpl_wasm_stdlib::hash256;
55/// use xrpl_wasm_stdlib::core::types::uint::Hash256;
56///
57/// const H: Hash256 =
58/// hash256!("0000000000000000000000000000000000000000000000000000000000000001");
59/// ```
60#[proc_macro]
61pub fn hash256(input: TokenStream) -> TokenStream {
62 match hash256::expand(input.into()) {
63 Ok(tokens) => tokens.into(),
64 Err(err) => err.to_compile_error().into(),
65 }
66}
67
68/// Converts a 66-character hex string to a 33-byte [`PublicKey`] at compile time.
69///
70/// The first two hex characters must be a valid XRPL public key prefix:
71/// `02` or `03` (secp256k1) or `ED` (Ed25519). The prefix check is
72/// case-insensitive — `ed` is accepted and normalised. Any other prefix, wrong
73/// length, or non-hex characters are compile errors.
74///
75/// # Example
76///
77/// ```rust,ignore
78/// use xrpl_wasm_stdlib::pubkey;
79/// use xrpl_wasm_stdlib::core::types::public_key::PublicKey;
80///
81/// const KEY: PublicKey =
82/// pubkey!("02C7387FFC25C156CA7F8A6D760C8D01EF642CEE9CE4680C33FFB3FF39AFECFE70");
83/// ```
84#[proc_macro]
85pub fn pubkey(input: TokenStream) -> TokenStream {
86 match pubkey::expand(input.into()) {
87 Ok(tokens) => tokens.into(),
88 Err(err) => err.to_compile_error().into(),
89 }
90}
91
92/// Converts an XRPL currency code to a 20-byte [`Currency`] at compile time.
93///
94/// Two forms are accepted:
95///
96/// - **Standard (3 ASCII alphanumeric chars)** — stored verbatim in bytes 12–14;
97/// bytes 0–11 and 15–19 are zero. `"XRP"` is reserved and rejected. Standard
98/// codes are case-sensitive (`"USD"` and `"usd"` are distinct on-ledger
99/// identifiers); use uppercase by convention.
100/// - **Non-standard (40 hex chars)** — interpreted as a raw 20-byte value. Must
101/// not start with `00` (that would alias the standard-code format).
102///
103/// # Example
104///
105/// ```rust,ignore
106/// use xrpl_wasm_stdlib::currency;
107/// use xrpl_wasm_stdlib::core::types::currency::Currency;
108///
109/// const USD: Currency = currency!("USD");
110/// const CUSTOM: Currency = currency!("0158415500000000C1F76FF6ECB0BAC600000000");
111/// ```
112#[proc_macro]
113pub fn currency(input: TokenStream) -> TokenStream {
114 match currency::expand(input.into()) {
115 Ok(tokens) => tokens.into(),
116 Err(err) => err.to_compile_error().into(),
117 }
118}
119
120/// Converts a hex string to a compile-time [`Blob<N>`].
121///
122/// Two forms:
123///
124/// - `blob!("DEADBEEF")` — exact-fit `Blob<4>` where `N` equals the decoded byte count.
125/// - `blob!("DEADBEEF", 128)` — `Blob<128>` zero-padded to the given capacity;
126/// the decoded byte count must not exceed the capacity.
127///
128/// In both forms `len` is set to the decoded byte count. The hex string must
129/// have an even number of characters.
130///
131/// # Example
132///
133/// ```rust,ignore
134/// use xrpl_wasm_stdlib::blob;
135/// use xrpl_wasm_stdlib::core::types::blob::Blob;
136///
137/// const EXACT: Blob<4> = blob!("DEADBEEF");
138/// const PADDED: Blob<32> = blob!("DEADBEEF", 32);
139/// ```
140#[proc_macro]
141pub fn blob(input: TokenStream) -> TokenStream {
142 match blob::expand(input.into()) {
143 Ok(tokens) => tokens.into(),
144 Err(err) => err.to_compile_error().into(),
145 }
146}
147
148/// Wraps a Smart Escrow finish function in the `extern "C" fn finish()` entry point
149/// the XRPL host calls when an `EscrowFinish` transaction invokes the feature.
150///
151/// The annotated function must:
152/// - Take `EscrowFinishContext` as its first (and only) argument.
153/// - Return `FinishResult` or `i32`.
154/// - The attribute takes no arguments — `#[smart_escrow(anything)]` is a compile error.
155///
156/// Any other signature is a compile error pointing at the offending token.
157///
158/// # Usage
159///
160/// Import this attribute from `xrpl_escrow_stdlib`, which re-exports it alongside
161/// `EscrowFinishContext`. Importing directly from `xrpl_macros` is unsupported
162/// because the generated code references types in `xrpl_escrow_stdlib`.
163///
164/// `FinishResult` and `i32` convert into each other (`From<i32>` /
165/// `From<FinishResult>`), so a host error code can be propagated with
166/// `.into()` either way:
167///
168/// ```rust,ignore
169/// // Import from the feature crate, not from xrpl_macros directly.
170/// use xrpl_escrow_stdlib::{smart_escrow, EscrowFinishContext, FinishResult};
171/// use xrpl_escrow_stdlib::core::current_tx::traits::TransactionCommonFields;
172/// use xrpl_escrow_stdlib::core::types::amount::Amount;
173///
174/// #[smart_escrow]
175/// fn finish(ctx: EscrowFinishContext) -> FinishResult {
176/// let fee = match ctx.tx().get_fee() {
177/// Ok(f) => f,
178/// Err(e) => return e.code().into(), // i32 -> FinishResult
179/// };
180///
181/// match fee {
182/// Amount::XRP { num_drops } if num_drops > 1000 => FinishResult::succeed(),
183/// _ => FinishResult::reject(),
184/// }
185/// }
186/// ```
187///
188/// Returning `i32` directly skips the macro's `FinishResult` handling:
189///
190/// ```rust,ignore
191/// use xrpl_escrow_stdlib::{smart_escrow, EscrowFinishContext, FinishResult};
192/// use xrpl_escrow_stdlib::core::current_tx::traits::TransactionCommonFields;
193/// use xrpl_escrow_stdlib::core::types::amount::Amount;
194///
195/// #[smart_escrow]
196/// fn finish(ctx: EscrowFinishContext) -> i32 {
197/// let fee = match ctx.tx().get_fee() {
198/// Ok(f) => f,
199/// Err(e) => return e.code(),
200/// };
201///
202/// let result = match fee {
203/// Amount::XRP { num_drops } if num_drops > 1000 => FinishResult::succeed(),
204/// _ => FinishResult::reject(),
205/// };
206///
207/// result.into()
208/// }
209/// ```
210#[proc_macro_attribute]
211pub fn smart_escrow(attr: TokenStream, item: TokenStream) -> TokenStream {
212 entry_point::smart_escrow::expand(attr, item)
213}
214
215/// Wraps a Smart Contract entry function in the appropriate `extern "C"` export.
216#[proc_macro_attribute]
217pub fn smart_contract(attr: TokenStream, item: TokenStream) -> TokenStream {
218 entry_point::smart_contract::expand(attr, item)
219}