Skip to main content

soroban_sdk/
muxed_address.rs

1use core::{cmp::Ordering, convert::Infallible, fmt::Debug};
2
3use super::{
4    env::internal::{AddressObject, Env as _, MuxedAddressObject, Tag},
5    ConversionError, Env, TryFromVal, TryIntoVal, Val,
6};
7use crate::{env::internal, unwrap::UnwrapInfallible, Address, Bytes, String};
8
9#[cfg(not(target_family = "wasm"))]
10use crate::env::internal::xdr::{ScAddress, ScVal};
11
12#[derive(Clone)]
13enum AddressObjectWrapper {
14    Address(AddressObject),
15    MuxedAddress(MuxedAddressObject),
16}
17
18/// MuxedAddress is a union type that represents either the regular `Address`,
19/// or a 'multiplexed' address that consists of a regular address and a u64 id
20/// and can be used for representing the 'virtual' accounts that allows for
21/// managing multiple balances off-chain with only a single on-chain balance
22/// entry. The address part can be used as a regular `Address`, and the id
23/// part should be used only in the events for the off-chain processing.
24///
25/// This type is only necessary in a few special cases, such as token transfers
26/// that support non-custodial accounts (e.g. for the exchange support). Prefer
27/// using the regular `Address` type unless multiplexing support is necessary.
28///
29/// This type is compatible with `Address` at the contract interface level, i.e.
30/// if a contract accepts `MuxedAddress` as an input, then its callers may still
31/// pass `Address` into the call successfully. This means that if a
32/// contract has upgraded its interface to switch from `Address` argument to
33/// `MuxedAddress` argument, it won't break any of its existing clients.
34///
35/// Currently only the regular Stellar accounts can be multiplexed, i.e.
36/// multiplexed contract addresses don't exist.
37///
38/// Note, that multiplexed addresses can not be used directly as a storage key.
39/// This is a precaution to prevent accidental unexpected fragmentation of
40/// the key space (like creating an arbitrary number of balances for the same
41/// actual `Address`).
42#[derive(Clone)]
43pub struct MuxedAddress {
44    env: Env,
45    obj: AddressObjectWrapper,
46}
47
48impl Debug for MuxedAddress {
49    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
50        #[cfg(target_family = "wasm")]
51        match &self.obj {
52            AddressObjectWrapper::Address(_) => write!(f, "Address(..)"),
53            AddressObjectWrapper::MuxedAddress(_) => write!(f, "MuxedAddress(..)"),
54        }
55        #[cfg(not(target_family = "wasm"))]
56        {
57            use crate::env::internal::xdr;
58            use stellar_strkey::Strkey;
59            match &self.obj {
60                AddressObjectWrapper::Address(address_object) => {
61                    Address::try_from_val(self.env(), address_object)
62                        .map_err(|_| core::fmt::Error)?
63                        .fmt(f)
64                }
65                AddressObjectWrapper::MuxedAddress(muxed_address_object) => {
66                    let sc_val = ScVal::try_from_val(self.env(), &muxed_address_object.to_val())
67                        .map_err(|_| core::fmt::Error)?;
68                    if let ScVal::Address(addr) = sc_val {
69                        match addr {
70                            xdr::ScAddress::MuxedAccount(muxed_account) => {
71                                let strkey = Strkey::MuxedAccountEd25519(
72                                    stellar_strkey::ed25519::MuxedAccount {
73                                        ed25519: muxed_account.ed25519.0,
74                                        id: muxed_account.id,
75                                    },
76                                );
77                                write!(f, "MuxedAccount({})", strkey.to_string())
78                            }
79                            _ => Err(core::fmt::Error),
80                        }
81                    } else {
82                        Err(core::fmt::Error)
83                    }
84                }
85            }
86        }
87    }
88}
89
90impl Eq for MuxedAddress {}
91
92impl PartialEq for MuxedAddress {
93    fn eq(&self, other: &Self) -> bool {
94        self.partial_cmp(other) == Some(Ordering::Equal)
95    }
96}
97
98impl PartialOrd for MuxedAddress {
99    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
100        Some(Ord::cmp(self, other))
101    }
102}
103
104impl Ord for MuxedAddress {
105    fn cmp(&self, other: &Self) -> Ordering {
106        let v = self
107            .env
108            .obj_cmp(self.to_val(), other.to_val())
109            .unwrap_infallible();
110        v.cmp(&0)
111    }
112}
113
114impl TryFromVal<Env, MuxedAddressObject> for MuxedAddress {
115    type Error = Infallible;
116
117    fn try_from_val(env: &Env, val: &MuxedAddressObject) -> Result<Self, Self::Error> {
118        Ok(unsafe { MuxedAddress::unchecked_new(env.clone(), *val) })
119    }
120}
121
122impl TryFromVal<Env, AddressObject> for MuxedAddress {
123    type Error = Infallible;
124
125    fn try_from_val(env: &Env, val: &AddressObject) -> Result<Self, Self::Error> {
126        Ok(unsafe { MuxedAddress::unchecked_new_from_address(env.clone(), *val) })
127    }
128}
129
130impl TryFromVal<Env, Val> for MuxedAddress {
131    type Error = ConversionError;
132
133    fn try_from_val(env: &Env, val: &Val) -> Result<Self, Self::Error> {
134        if val.get_tag() == Tag::AddressObject {
135            Ok(AddressObject::try_from_val(env, val)?
136                .try_into_val(env)
137                .unwrap_infallible())
138        } else {
139            Ok(MuxedAddressObject::try_from_val(env, val)?
140                .try_into_val(env)
141                .unwrap_infallible())
142        }
143    }
144}
145
146impl TryFromVal<Env, MuxedAddress> for Val {
147    type Error = ConversionError;
148
149    fn try_from_val(_env: &Env, v: &MuxedAddress) -> Result<Self, Self::Error> {
150        Ok(v.to_val())
151    }
152}
153
154impl TryFromVal<Env, &MuxedAddress> for Val {
155    type Error = ConversionError;
156
157    fn try_from_val(_env: &Env, v: &&MuxedAddress) -> Result<Self, Self::Error> {
158        Ok(v.to_val())
159    }
160}
161
162impl From<&MuxedAddress> for MuxedAddress {
163    fn from(address: &MuxedAddress) -> Self {
164        address.clone()
165    }
166}
167
168impl From<Address> for MuxedAddress {
169    fn from(address: Address) -> Self {
170        (&address).into()
171    }
172}
173
174impl From<&Address> for MuxedAddress {
175    fn from(address: &Address) -> Self {
176        address
177            .as_object()
178            .try_into_val(address.env())
179            .unwrap_infallible()
180    }
181}
182
183impl MuxedAddress {
184    /// Creates a `MuxedAddress` corresponding to the provided Stellar strkey.
185    ///
186    /// Supported strkey types:
187    /// - Account address (G...)
188    /// - Muxed account address (M...)
189    /// - Contract address (C...)
190    ///
191    /// Any other strkey type will cause this to panic.
192    ///
193    /// Prefer using the `MuxedAddress` directly as input or output argument. Only
194    /// use this in special cases when addresses need to be shared between
195    /// different environments (e.g. different chains).
196    pub fn from_str(env: &Env, strkey: &str) -> MuxedAddress {
197        match strkey.as_bytes().first() {
198            Some(b'M') => MuxedAddress::from_muxed_strkey_bytes(
199                env,
200                &Bytes::from_slice(env, strkey.as_bytes()),
201            ),
202            _ => Address::from_str(env, strkey).into(),
203        }
204    }
205
206    /// Creates a `MuxedAddress` corresponding to the provided Stellar strkey.
207    ///
208    /// Supported strkey types:
209    /// - Account address (G...)
210    /// - Muxed account address (M...)
211    /// - Contract address (C...)
212    ///
213    /// Any other strkey type will cause this to panic.
214    ///
215    /// Prefer using the `MuxedAddress` directly as input or output argument. Only
216    /// use this in special cases when addresses need to be shared between
217    /// different environments (e.g. different chains).
218    pub fn from_string(strkey: &String) -> Self {
219        let env = strkey.env();
220        let strkey_bytes = strkey.to_bytes();
221        match strkey_bytes.first() {
222            Some(b'M') => MuxedAddress::from_muxed_strkey_bytes(env, &strkey_bytes),
223            _ => Address::from_string(strkey).into(),
224        }
225    }
226
227    /// Creates a `MuxedAddress` corresponding to the provided Stellar strkey
228    /// in raw bytes form.
229    ///
230    /// Supported strkey types:
231    /// - Account address (G...)
232    /// - Muxed account address (M...)
233    /// - Contract address (C...)
234    ///
235    /// Any other strkey type will cause this to panic.
236    ///
237    /// Prefer using the `MuxedAddress` directly as input or output argument. Only
238    /// use this in special cases when addresses need to be shared between
239    /// different environments (e.g. different chains).
240    pub fn from_string_bytes(strkey: &Bytes) -> Self {
241        let env = strkey.env();
242        match strkey.first() {
243            Some(b'M') => MuxedAddress::from_muxed_strkey_bytes(env, strkey),
244            _ => Address::from_string_bytes(strkey).into(),
245        }
246    }
247
248    /// Internal: parses a muxed account strkey (M...) and builds MuxedAddress.
249    fn from_muxed_strkey_bytes(env: &Env, strkey: &Bytes) -> Self {
250        use crate::xdr::{FromXdr, ScAddressType, ScValType};
251        use stellar_strkey::ed25519::MuxedAccount;
252
253        // Copy strkey bytes into buffer for parsing.
254        const MAX_STRKEY_LEN: usize = 69;
255        let mut strkey_buf = [0u8; MAX_STRKEY_LEN];
256        let len = strkey.len() as usize;
257        if len > strkey_buf.len() {
258            sdk_panic!("unexpected strkey length");
259        }
260        strkey.copy_into_slice(&mut strkey_buf[..len]);
261
262        let muxed = MuxedAccount::from_slice(&strkey_buf[..len])
263            .unwrap_or_else(|_| sdk_panic!("muxed strkey invalid"));
264
265        // Build XDR bytes
266        // XDR layout for ScVal::Address(ScAddress::MuxedAccount(MuxedEd25519Account))
267        // MuxedEd25519Account: { id: uint64, ed25519: uint256 }
268        // Total: 48 bytes
269        const SCVAL_ADDRESS: i32 = ScValType::Address as i32;
270        const SCADDRESS_MUXED_ACCOUNT: i32 = ScAddressType::MuxedAccount as i32;
271        let mut buf = [0u8; 48];
272        buf[0..4].copy_from_slice(&SCVAL_ADDRESS.to_be_bytes());
273        buf[4..8].copy_from_slice(&SCADDRESS_MUXED_ACCOUNT.to_be_bytes());
274        buf[8..16].copy_from_slice(&muxed.id.to_be_bytes()); // 8-byte mux id (big-endian)
275        buf[16..48].copy_from_slice(&muxed.ed25519); // 32-byte ed25519 public key
276        let xdr_bytes = Bytes::from_slice(env, &buf);
277
278        MuxedAddress::from_xdr(env, &xdr_bytes).unwrap_or_else(|_| sdk_panic!("invalid xdr"))
279    }
280
281    /// Returns the `Address` part of this multiplexed address.
282    ///
283    /// The address part is necessary to perform most of the operations, such
284    /// as authorization or storage.
285    pub fn address(&self) -> Address {
286        match &self.obj {
287            AddressObjectWrapper::Address(address_object) => {
288                Address::try_from_val(&self.env, address_object).unwrap_infallible()
289            }
290            AddressObjectWrapper::MuxedAddress(muxed_address_object) => Address::try_from_val(
291                &self.env,
292                &internal::Env::get_address_from_muxed_address(&self.env, *muxed_address_object)
293                    .unwrap_infallible(),
294            )
295            .unwrap_infallible(),
296        }
297    }
298
299    /// Returns the multiplexing identifier part of this multiplexed address,
300    /// if any.
301    ///
302    /// Returns `None` for the regular (non-multiplexed) addresses.
303    ///
304    /// This identifier should normally be used in the events in order to allow
305    /// for tracking the virtual balances associated with this address off-chain.
306    pub fn id(&self) -> Option<u64> {
307        match &self.obj {
308            AddressObjectWrapper::Address(_) => None,
309            AddressObjectWrapper::MuxedAddress(muxed_address_object) => Some(
310                u64::try_from_val(
311                    &self.env,
312                    &internal::Env::get_id_from_muxed_address(&self.env, *muxed_address_object)
313                        .unwrap_infallible(),
314                )
315                .unwrap(),
316            ),
317        }
318    }
319
320    #[inline(always)]
321    pub(crate) unsafe fn unchecked_new_from_address(env: Env, obj: AddressObject) -> Self {
322        Self {
323            env,
324            obj: AddressObjectWrapper::Address(obj),
325        }
326    }
327
328    #[inline(always)]
329    pub(crate) unsafe fn unchecked_new(env: Env, obj: MuxedAddressObject) -> Self {
330        Self {
331            env,
332            obj: AddressObjectWrapper::MuxedAddress(obj),
333        }
334    }
335
336    #[inline(always)]
337    pub fn env(&self) -> &Env {
338        &self.env
339    }
340
341    pub fn as_val(&self) -> &Val {
342        match &self.obj {
343            AddressObjectWrapper::Address(o) => o.as_val(),
344            AddressObjectWrapper::MuxedAddress(o) => o.as_val(),
345        }
346    }
347
348    pub fn to_val(&self) -> Val {
349        match self.obj {
350            AddressObjectWrapper::Address(o) => o.to_val(),
351            AddressObjectWrapper::MuxedAddress(o) => o.to_val(),
352        }
353    }
354}
355
356#[cfg(not(target_family = "wasm"))]
357impl TryFromVal<Env, ScVal> for MuxedAddress {
358    type Error = ConversionError;
359    fn try_from_val(env: &Env, val: &ScVal) -> Result<Self, Self::Error> {
360        let v = Val::try_from_val(env, val)?;
361        match val {
362            ScVal::Address(sc_address) => match sc_address {
363                ScAddress::Account(_) | ScAddress::Contract(_) => {
364                    Ok(AddressObject::try_from_val(env, &v)?
365                        .try_into_val(env)
366                        .unwrap_infallible())
367                }
368                ScAddress::MuxedAccount(_) => Ok(MuxedAddressObject::try_from_val(env, &v)?
369                    .try_into_val(env)
370                    .unwrap_infallible()),
371                ScAddress::ClaimableBalance(_) | ScAddress::LiquidityPool(_) => {
372                    panic!("unsupported ScAddress type")
373                }
374            },
375            _ => panic!("incorrect scval type"),
376        }
377    }
378}
379
380#[cfg(not(target_family = "wasm"))]
381impl TryFromVal<Env, ScAddress> for MuxedAddress {
382    type Error = ConversionError;
383    fn try_from_val(env: &Env, val: &ScAddress) -> Result<Self, Self::Error> {
384        ScVal::Address(val.clone()).try_into_val(env)
385    }
386}
387
388#[cfg(any(test, feature = "testutils"))]
389#[cfg_attr(feature = "docs", doc(cfg(feature = "testutils")))]
390impl crate::testutils::MuxedAddress for MuxedAddress {
391    fn generate(env: &Env) -> crate::MuxedAddress {
392        let sc_val = ScVal::Address(crate::env::internal::xdr::ScAddress::MuxedAccount(
393            crate::env::internal::xdr::MuxedEd25519Account {
394                ed25519: crate::env::internal::xdr::Uint256(
395                    env.with_generator(|mut g| g.address()),
396                ),
397                id: env.with_generator(|mut g| g.mux_id()),
398            },
399        ));
400        sc_val.try_into_val(env).unwrap()
401    }
402
403    fn new<T: Into<MuxedAddress>>(address: T, id: u64) -> crate::MuxedAddress {
404        let address: MuxedAddress = address.into();
405        let sc_val = ScVal::try_from_val(&address.env, address.as_val()).unwrap();
406        let account_id = match sc_val {
407            ScVal::Address(address) => match address {
408                ScAddress::MuxedAccount(muxed_account) => muxed_account.ed25519,
409                ScAddress::Account(crate::env::internal::xdr::AccountId(
410                    crate::env::internal::xdr::PublicKey::PublicKeyTypeEd25519(account_id),
411                )) => account_id,
412                ScAddress::Contract(_) => panic!("contract addresses can not be multiplexed"),
413                ScAddress::ClaimableBalance(_) | ScAddress::LiquidityPool(_) => unreachable!(),
414            },
415            _ => unreachable!(),
416        };
417        let result_sc_val = ScVal::Address(ScAddress::MuxedAccount(
418            crate::env::internal::xdr::MuxedEd25519Account {
419                id,
420                ed25519: account_id,
421            },
422        ));
423        result_sc_val.try_into_val(&address.env).unwrap()
424    }
425}