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};
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<Address> for MuxedAddress {
163    fn from(address: Address) -> Self {
164        (&address).into()
165    }
166}
167
168impl From<&Address> for MuxedAddress {
169    fn from(address: &Address) -> Self {
170        address
171            .as_object()
172            .try_into_val(address.env())
173            .unwrap_infallible()
174    }
175}
176
177impl MuxedAddress {
178    /// Returns the `Address` part of this multiplexed address.
179    ///
180    /// The address part is necessary to perform most of the operations, such
181    /// as authorization or storage.
182    pub fn address(&self) -> Address {
183        match &self.obj {
184            AddressObjectWrapper::Address(address_object) => {
185                Address::try_from_val(&self.env, address_object).unwrap_infallible()
186            }
187            AddressObjectWrapper::MuxedAddress(muxed_address_object) => Address::try_from_val(
188                &self.env,
189                &internal::Env::get_address_from_muxed_address(&self.env, *muxed_address_object)
190                    .unwrap_infallible(),
191            )
192            .unwrap_infallible(),
193        }
194    }
195
196    /// Returns the multiplexing identifier part of this multiplexed address,
197    /// if any.
198    ///
199    /// Returns `None` for the regular (non-multiplexed) addresses.
200    ///
201    /// This identifier should normally be used in the events in order to allow
202    /// for tracking the virtual balances associated with this address off-chain.
203    pub fn id(&self) -> Option<u64> {
204        match &self.obj {
205            AddressObjectWrapper::Address(_) => None,
206            AddressObjectWrapper::MuxedAddress(muxed_address_object) => Some(
207                u64::try_from_val(
208                    &self.env,
209                    &internal::Env::get_id_from_muxed_address(&self.env, *muxed_address_object)
210                        .unwrap_infallible(),
211                )
212                .unwrap(),
213            ),
214        }
215    }
216
217    #[inline(always)]
218    pub(crate) unsafe fn unchecked_new_from_address(env: Env, obj: AddressObject) -> Self {
219        Self {
220            env,
221            obj: AddressObjectWrapper::Address(obj),
222        }
223    }
224
225    #[inline(always)]
226    pub(crate) unsafe fn unchecked_new(env: Env, obj: MuxedAddressObject) -> Self {
227        Self {
228            env,
229            obj: AddressObjectWrapper::MuxedAddress(obj),
230        }
231    }
232
233    #[inline(always)]
234    pub fn env(&self) -> &Env {
235        &self.env
236    }
237
238    pub fn as_val(&self) -> &Val {
239        match &self.obj {
240            AddressObjectWrapper::Address(o) => o.as_val(),
241            AddressObjectWrapper::MuxedAddress(o) => o.as_val(),
242        }
243    }
244
245    pub fn to_val(&self) -> Val {
246        match self.obj {
247            AddressObjectWrapper::Address(o) => o.to_val(),
248            AddressObjectWrapper::MuxedAddress(o) => o.to_val(),
249        }
250    }
251}
252
253#[cfg(not(target_family = "wasm"))]
254impl TryFromVal<Env, ScVal> for MuxedAddress {
255    type Error = ConversionError;
256    fn try_from_val(env: &Env, val: &ScVal) -> Result<Self, Self::Error> {
257        let v = Val::try_from_val(env, val)?;
258        match val {
259            ScVal::Address(sc_address) => match sc_address {
260                ScAddress::Account(_) | ScAddress::Contract(_) => {
261                    Ok(AddressObject::try_from_val(env, &v)?
262                        .try_into_val(env)
263                        .unwrap_infallible())
264                }
265                ScAddress::MuxedAccount(_) => Ok(MuxedAddressObject::try_from_val(env, &v)?
266                    .try_into_val(env)
267                    .unwrap_infallible()),
268                ScAddress::ClaimableBalance(_) | ScAddress::LiquidityPool(_) => {
269                    panic!("unsupported ScAddress type")
270                }
271            },
272            _ => panic!("incorrect scval type"),
273        }
274    }
275}
276
277#[cfg(not(target_family = "wasm"))]
278impl TryFromVal<Env, ScAddress> for MuxedAddress {
279    type Error = ConversionError;
280    fn try_from_val(env: &Env, val: &ScAddress) -> Result<Self, Self::Error> {
281        ScVal::Address(val.clone()).try_into_val(env)
282    }
283}
284
285#[cfg(any(test, feature = "testutils"))]
286#[cfg_attr(feature = "docs", doc(cfg(feature = "testutils")))]
287impl crate::testutils::MuxedAddress for MuxedAddress {
288    fn generate(env: &Env) -> crate::MuxedAddress {
289        let sc_val = ScVal::Address(crate::env::internal::xdr::ScAddress::MuxedAccount(
290            crate::env::internal::xdr::MuxedEd25519Account {
291                ed25519: crate::env::internal::xdr::Uint256(
292                    env.with_generator(|mut g| g.address()),
293                ),
294                id: env.with_generator(|mut g| g.mux_id()),
295            },
296        ));
297        sc_val.try_into_val(env).unwrap()
298    }
299
300    fn new<T: Into<MuxedAddress>>(address: T, id: u64) -> crate::MuxedAddress {
301        let address: MuxedAddress = address.into();
302        let sc_val = ScVal::try_from_val(&address.env, address.as_val()).unwrap();
303        let account_id = match sc_val {
304            ScVal::Address(address) => match address {
305                ScAddress::MuxedAccount(muxed_account) => muxed_account.ed25519,
306                ScAddress::Account(crate::env::internal::xdr::AccountId(
307                    crate::env::internal::xdr::PublicKey::PublicKeyTypeEd25519(account_id),
308                )) => account_id,
309                ScAddress::Contract(_) => panic!("contract addresses can not be multiplexed"),
310                ScAddress::ClaimableBalance(_) | ScAddress::LiquidityPool(_) => unreachable!(),
311            },
312            _ => unreachable!(),
313        };
314        let result_sc_val = ScVal::Address(ScAddress::MuxedAccount(
315            crate::env::internal::xdr::MuxedEd25519Account {
316                id,
317                ed25519: account_id,
318            },
319        ));
320        result_sc_val.try_into_val(&address.env).unwrap()
321    }
322}