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