soroban_sdk/
address.rs

1use core::{cmp::Ordering, convert::Infallible, fmt::Debug};
2
3use super::{
4    contracttype, env::internal::AddressObject, env::internal::Env as _, unwrap::UnwrapInfallible,
5    Bytes, BytesN, ConversionError, Env, IntoVal, String, TryFromVal, TryIntoVal, Val, Vec,
6};
7
8#[cfg(any(test, feature = "hazmat-address"))]
9use crate::address_payload::AddressPayload;
10
11#[cfg(not(target_family = "wasm"))]
12use crate::env::internal::xdr::{AccountId, ScVal};
13#[cfg(any(test, feature = "testutils", not(target_family = "wasm")))]
14use crate::env::xdr::ScAddress;
15
16/// Address is a universal opaque identifier to use in contracts.
17///
18/// Address can be used as an input argument (for example, to identify the
19/// payment recipient), as a data key (for example, to store the balance), as
20/// the authentication & authorization source (for example, to authorize the
21/// token transfer) etc.
22///
23/// See `require_auth` documentation for more details on using Address for
24/// authorization.
25///
26/// Internally, Address may represent a Stellar account or a contract. Contract
27/// address may be used to identify the account contracts - special contracts
28/// that allow customizing authentication logic and adding custom authorization
29/// rules.
30///
31/// In tests Addresses should be generated via `Address::generate()`.
32#[derive(Clone)]
33pub struct Address {
34    env: Env,
35    obj: AddressObject,
36}
37
38impl Debug for Address {
39    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
40        #[cfg(target_family = "wasm")]
41        write!(f, "Address(..)")?;
42        #[cfg(not(target_family = "wasm"))]
43        {
44            use crate::env::internal::xdr;
45            use stellar_strkey::{ed25519, Contract, Strkey};
46            let sc_val = ScVal::try_from(self).map_err(|_| core::fmt::Error)?;
47            if let ScVal::Address(addr) = sc_val {
48                match addr {
49                    xdr::ScAddress::Account(account_id) => {
50                        let xdr::AccountId(xdr::PublicKey::PublicKeyTypeEd25519(xdr::Uint256(
51                            ed25519,
52                        ))) = account_id;
53                        let strkey = Strkey::PublicKeyEd25519(ed25519::PublicKey(ed25519));
54                        write!(f, "AccountId({})", strkey.to_string())?;
55                    }
56                    xdr::ScAddress::Contract(xdr::ContractId(contract_id)) => {
57                        let strkey = Strkey::Contract(Contract(contract_id.0));
58                        write!(f, "Contract({})", strkey.to_string())?;
59                    }
60                    ScAddress::MuxedAccount(_)
61                    | ScAddress::ClaimableBalance(_)
62                    | ScAddress::LiquidityPool(_) => {
63                        return Err(core::fmt::Error);
64                    }
65                }
66            } else {
67                return Err(core::fmt::Error);
68            }
69        }
70        Ok(())
71    }
72}
73
74impl Eq for Address {}
75
76impl PartialEq for Address {
77    fn eq(&self, other: &Self) -> bool {
78        self.partial_cmp(other) == Some(Ordering::Equal)
79    }
80}
81
82impl PartialOrd for Address {
83    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
84        Some(Ord::cmp(self, other))
85    }
86}
87
88impl Ord for Address {
89    fn cmp(&self, other: &Self) -> Ordering {
90        #[cfg(not(target_family = "wasm"))]
91        if !self.env.is_same_env(&other.env) {
92            return ScVal::from(self).cmp(&ScVal::from(other));
93        }
94        let v = self
95            .env
96            .obj_cmp(self.obj.to_val(), other.obj.to_val())
97            .unwrap_infallible();
98        v.cmp(&0)
99    }
100}
101
102impl TryFromVal<Env, AddressObject> for Address {
103    type Error = Infallible;
104
105    fn try_from_val(env: &Env, val: &AddressObject) -> Result<Self, Self::Error> {
106        Ok(unsafe { Address::unchecked_new(env.clone(), *val) })
107    }
108}
109
110impl TryFromVal<Env, Val> for Address {
111    type Error = ConversionError;
112
113    fn try_from_val(env: &Env, val: &Val) -> Result<Self, Self::Error> {
114        Ok(AddressObject::try_from_val(env, val)?
115            .try_into_val(env)
116            .unwrap_infallible())
117    }
118}
119
120impl TryFromVal<Env, Address> for Val {
121    type Error = ConversionError;
122
123    fn try_from_val(_env: &Env, v: &Address) -> Result<Self, Self::Error> {
124        Ok(v.to_val())
125    }
126}
127
128impl TryFromVal<Env, &Address> for Val {
129    type Error = ConversionError;
130
131    fn try_from_val(_env: &Env, v: &&Address) -> Result<Self, Self::Error> {
132        Ok(v.to_val())
133    }
134}
135
136#[cfg(not(target_family = "wasm"))]
137impl From<&Address> for ScVal {
138    fn from(v: &Address) -> Self {
139        // This conversion occurs only in test utilities, and theoretically all
140        // values should convert to an ScVal because the Env won't let the host
141        // type to exist otherwise, unwrapping. Even if there are edge cases
142        // that don't, this is a trade off for a better test developer
143        // experience.
144        ScVal::try_from_val(&v.env, &v.obj.to_val()).unwrap()
145    }
146}
147
148#[cfg(not(target_family = "wasm"))]
149impl From<Address> for ScVal {
150    fn from(v: Address) -> Self {
151        (&v).into()
152    }
153}
154
155#[cfg(not(target_family = "wasm"))]
156impl TryFromVal<Env, ScVal> for Address {
157    type Error = ConversionError;
158    fn try_from_val(env: &Env, val: &ScVal) -> Result<Self, Self::Error> {
159        Ok(
160            AddressObject::try_from_val(env, &Val::try_from_val(env, val)?)?
161                .try_into_val(env)
162                .unwrap_infallible(),
163        )
164    }
165}
166
167#[cfg(not(target_family = "wasm"))]
168impl From<&Address> for ScAddress {
169    fn from(v: &Address) -> Self {
170        match ScVal::try_from_val(&v.env, &v.obj.to_val()).unwrap() {
171            ScVal::Address(a) => a,
172            _ => panic!("expected ScVal::Address"),
173        }
174    }
175}
176
177#[cfg(not(target_family = "wasm"))]
178impl From<Address> for ScAddress {
179    fn from(v: Address) -> Self {
180        (&v).into()
181    }
182}
183
184#[cfg(not(target_family = "wasm"))]
185impl TryFromVal<Env, ScAddress> for Address {
186    type Error = ConversionError;
187    fn try_from_val(env: &Env, val: &ScAddress) -> Result<Self, Self::Error> {
188        Ok(AddressObject::try_from_val(
189            env,
190            &Val::try_from_val(env, &ScVal::Address(val.clone()))?,
191        )?
192        .try_into_val(env)
193        .unwrap_infallible())
194    }
195}
196
197#[cfg(not(target_family = "wasm"))]
198impl TryFrom<&Address> for AccountId {
199    type Error = ConversionError;
200    fn try_from(v: &Address) -> Result<Self, Self::Error> {
201        let sc: ScAddress = v.into();
202        match sc {
203            ScAddress::Account(aid) => Ok(aid),
204            _ => Err(ConversionError),
205        }
206    }
207}
208
209#[cfg(not(target_family = "wasm"))]
210impl TryFrom<Address> for AccountId {
211    type Error = ConversionError;
212    fn try_from(v: Address) -> Result<Self, Self::Error> {
213        (&v).try_into()
214    }
215}
216
217#[contracttype(crate_path = "crate", export = false)]
218#[derive(Clone, Debug, PartialEq, Eq)]
219pub enum Executable {
220    Wasm(BytesN<32>),
221    StellarAsset,
222    Account,
223}
224
225impl Address {
226    /// Ensures that this Address has authorized invocation of the current
227    /// contract with the provided arguments.
228    ///
229    /// During the on-chain execution the Soroban host will perform the needed
230    /// authentication (verify the signatures) and ensure the replay prevention.
231    /// The contracts don't need to perform this tasks.
232    ///
233    /// The arguments don't have to match the arguments of the contract
234    /// invocation. However, it's considered the best practice to have a
235    /// well-defined, deterministic and ledger-state-independent mapping between
236    /// the contract invocation arguments and `require_auth` arguments. This
237    /// will allow the contract callers to easily build the required signature
238    /// payloads and prevent potential authorization failures.
239    ///
240    /// ### Panics
241    ///
242    /// If the invocation is not authorized.
243    pub fn require_auth_for_args(&self, args: Vec<Val>) {
244        self.env.require_auth_for_args(self, args);
245    }
246
247    /// Ensures that this Address has authorized invocation of the current
248    /// contract with all the invocation arguments
249    ///
250    /// This works exactly in the same fashion as `require_auth_for_args`, but
251    /// arguments are automatically inferred from the current contract
252    /// invocation.
253    ///
254    /// This is useful when there is only a single Address that needs to
255    /// authorize the contract invocation and there are no dynamic arguments
256    /// that don't need authorization.
257    ///
258    /// ### Panics
259    ///
260    /// If the invocation is not authorized.
261    pub fn require_auth(&self) {
262        self.env.require_auth(self);
263    }
264
265    /// Creates an `Address` corresponding to the provided Stellar strkey.
266    ///
267    /// The only supported strkey types are account keys (`G...`) and contract keys (`C...`). Any
268    /// other valid or invalid strkey will cause this to panic.
269    ///
270    /// Prefer using the `Address` directly as input or output argument. Only
271    /// use this in special cases when addresses need to be shared between
272    /// different environments (e.g. different chains).
273    pub fn from_str(env: &Env, strkey: &str) -> Address {
274        Address::from_string(&String::from_str(env, strkey))
275    }
276
277    /// Creates an `Address` corresponding to the provided Stellar strkey.
278    ///
279    /// The only supported strkey types are account keys (`G...`) and contract keys (`C...`). Any
280    /// other valid or invalid strkey will cause this to panic.
281    ///
282    /// Prefer using the `Address` directly as input or output argument. Only
283    /// use this in special cases when addresses need to be shared between
284    /// different environments (e.g. different chains).
285    pub fn from_string(strkey: &String) -> Self {
286        let env = strkey.env();
287        unsafe {
288            Self::unchecked_new(
289                env.clone(),
290                env.strkey_to_address(strkey.to_object().to_val())
291                    .unwrap_infallible(),
292            )
293        }
294    }
295
296    /// Creates an `Address` corresponding to the provided Stellar strkey bytes.
297    ///
298    /// This behaves exactly in the same fashion as `from_strkey`, i.e. the bytes should contain
299    /// exactly the same contents as `String` would (i.e. base-32 ASCII string).
300    ///
301    /// The only supported strkey types are account keys (`G...`) and contract keys (`C...`). Any
302    /// other valid or invalid strkey will cause this to panic.
303    ///
304    /// Prefer using the `Address` directly as input or output argument. Only
305    /// use this in special cases when addresses need to be shared between
306    /// different environments (e.g. different chains).
307    pub fn from_string_bytes(strkey: &Bytes) -> Self {
308        let env = strkey.env();
309        unsafe {
310            Self::unchecked_new(
311                env.clone(),
312                env.strkey_to_address(strkey.to_object().to_val())
313                    .unwrap_infallible(),
314            )
315        }
316    }
317
318    /// Returns the executable type of this address, if any.
319    ///
320    /// Returns None when the contract or account does not exist.   
321    ///
322    /// For Wasm contracts, this also returns the hash of the contract code.
323    /// Otherwise, this just returns which kind of 'built-in' executable this is
324    /// (StellarAsset or Account).
325    pub fn executable(&self) -> Option<Executable> {
326        let executable_val: Val =
327            Env::get_address_executable(&self.env, self.obj).unwrap_infallible();
328        executable_val.into_val(&self.env)
329    }
330
331    /// Returns whether this address exists in the ledger.
332    ///
333    /// For the contract addresses, this means that there is a corresponding
334    /// contract instance deployed. For account addresses, this means that the
335    /// account entry exists in the ledger.
336    pub fn exists(&self) -> bool {
337        let executable_val: Val =
338            Env::get_address_executable(&self.env, self.obj).unwrap_infallible();
339        !executable_val.is_void()
340    }
341
342    /// Converts this `Address` into the corresponding Stellar strkey.
343    pub fn to_string(&self) -> String {
344        String::try_from_val(
345            &self.env,
346            &self.env.address_to_strkey(self.obj).unwrap_infallible(),
347        )
348        .unwrap_optimized()
349    }
350
351    #[inline(always)]
352    pub(crate) unsafe fn unchecked_new(env: Env, obj: AddressObject) -> Self {
353        Self { env, obj }
354    }
355
356    #[inline(always)]
357    pub fn env(&self) -> &Env {
358        &self.env
359    }
360
361    pub fn as_val(&self) -> &Val {
362        self.obj.as_val()
363    }
364
365    pub fn to_val(&self) -> Val {
366        self.obj.to_val()
367    }
368
369    pub fn as_object(&self) -> &AddressObject {
370        &self.obj
371    }
372
373    pub fn to_object(&self) -> AddressObject {
374        self.obj
375    }
376
377    /// Extracts the payload from the address.
378    ///
379    /// Returns:
380    /// - For contract addresses (C...), returns [`AddressPayload::ContractIdHash`]
381    ///   containing the 32-byte contract hash.
382    /// - For account addresses (G...), returns [`AddressPayload::AccountIdPublicKeyEd25519`]
383    ///   containing the 32-byte Ed25519 public key.
384    ///
385    /// Returns `None` if the address type is not recognized. This may occur if
386    /// a new address type has been introduced to the network that this version
387    /// of this library is not aware of.
388    ///
389    /// # Warning
390    ///
391    /// For account addresses, the returned Ed25519 public key corresponds to
392    /// the account's master key, which depending on the configuration of that
393    /// account may or may not be a signer of the account. Do not use this for
394    /// custom Ed25519 signature verification as a form of authentication
395    /// because the master key may not be configured the signer of the account.
396    #[cfg(any(test, feature = "hazmat-address"))]
397    #[cfg_attr(
398        feature = "docs",
399        doc(cfg(any(feature = "hazmat", feature = "hazmat-address")))
400    )]
401    pub fn to_payload(&self) -> Option<AddressPayload> {
402        AddressPayload::from_address(self)
403    }
404
405    /// Constructs an [`Address`] from an [`AddressPayload`].
406    ///
407    /// This is the inverse of [`to_payload`][Address::to_payload].
408    ///
409    /// # Warning
410    ///
411    /// For account addresses, the returned Ed25519 public key corresponds to
412    /// the account's master key, which depending on the configuration of that
413    /// account may or may not be a signer of the account. Do not use this for
414    /// custom Ed25519 signature verification as a form of authentication
415    /// because the master key may not be configured the signer of the account.
416    #[cfg(any(test, feature = "hazmat-address"))]
417    #[cfg_attr(
418        feature = "docs",
419        doc(cfg(any(feature = "hazmat", feature = "hazmat-address")))
420    )]
421    pub fn from_payload(env: &Env, payload: AddressPayload) -> Address {
422        payload.to_address(env)
423    }
424}
425
426#[cfg(any(not(target_family = "wasm"), test, feature = "testutils"))]
427use crate::env::xdr::{ContractId, Hash};
428use crate::unwrap::UnwrapOptimized;
429
430#[cfg(any(test, feature = "testutils"))]
431#[cfg_attr(feature = "docs", doc(cfg(feature = "testutils")))]
432impl crate::testutils::Address for Address {
433    fn generate(env: &Env) -> Self {
434        Self::try_from_val(
435            env,
436            &ScAddress::Contract(ContractId(Hash(env.with_generator(|mut g| g.address())))),
437        )
438        .unwrap()
439    }
440}
441
442#[cfg(not(target_family = "wasm"))]
443impl Address {
444    pub(crate) fn contract_id(&self) -> ContractId {
445        let sc_address: ScAddress = self.try_into().unwrap();
446        if let ScAddress::Contract(c) = sc_address {
447            c
448        } else {
449            panic!("address is not a contract {:?}", self);
450        }
451    }
452
453    pub(crate) fn from_contract_id(env: &Env, contract_id: [u8; 32]) -> Self {
454        Self::try_from_val(env, &ScAddress::Contract(ContractId(Hash(contract_id)))).unwrap()
455    }
456}