Skip to main content

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#[cfg_attr(
218    feature = "experimental_spec_shaking_v2",
219    contracttype(crate_path = "crate")
220)]
221#[cfg_attr(
222    not(feature = "experimental_spec_shaking_v2"),
223    contracttype(crate_path = "crate", export = false)
224)]
225#[derive(Clone, Debug, PartialEq, Eq)]
226pub enum Executable {
227    Wasm(BytesN<32>),
228    StellarAsset,
229    Account,
230}
231
232impl Address {
233    /// Ensures that this Address has authorized invocation of the current
234    /// contract with the provided arguments.
235    ///
236    /// During the on-chain execution the Soroban host will perform the needed
237    /// authentication (verify the signatures) and ensure the replay prevention.
238    /// The contracts don't need to perform this tasks.
239    ///
240    /// The arguments don't have to match the arguments of the contract
241    /// invocation. However, it's considered the best practice to have a
242    /// well-defined, deterministic and ledger-state-independent mapping between
243    /// the contract invocation arguments and `require_auth` arguments. This
244    /// will allow the contract callers to easily build the required signature
245    /// payloads and prevent potential authorization failures.
246    ///
247    /// ### Panics
248    ///
249    /// If the invocation is not authorized.
250    pub fn require_auth_for_args(&self, args: Vec<Val>) {
251        self.env.require_auth_for_args(self, args);
252    }
253
254    /// Ensures that this Address has authorized invocation of the current
255    /// contract with all the invocation arguments
256    ///
257    /// This works exactly in the same fashion as `require_auth_for_args`, but
258    /// arguments are automatically inferred from the current contract
259    /// invocation.
260    ///
261    /// This is useful when there is only a single Address that needs to
262    /// authorize the contract invocation and there are no dynamic arguments
263    /// that don't need authorization.
264    ///
265    /// ### Panics
266    ///
267    /// If the invocation is not authorized.
268    pub fn require_auth(&self) {
269        self.env.require_auth(self);
270    }
271
272    /// Creates an `Address` corresponding to the provided Stellar strkey.
273    ///
274    /// The only supported strkey types are account keys (`G...`) and contract keys (`C...`). Any
275    /// other valid or invalid strkey will cause this to panic.
276    ///
277    /// Prefer using the `Address` directly as input or output argument. Only
278    /// use this in special cases when addresses need to be shared between
279    /// different environments (e.g. different chains).
280    pub fn from_str(env: &Env, strkey: &str) -> Address {
281        Address::from_string(&String::from_str(env, strkey))
282    }
283
284    /// Creates an `Address` corresponding to the provided Stellar strkey.
285    ///
286    /// The only supported strkey types are account keys (`G...`) and contract keys (`C...`). Any
287    /// other valid or invalid strkey will cause this to panic.
288    ///
289    /// Prefer using the `Address` directly as input or output argument. Only
290    /// use this in special cases when addresses need to be shared between
291    /// different environments (e.g. different chains).
292    pub fn from_string(strkey: &String) -> Self {
293        let env = strkey.env();
294        unsafe {
295            Self::unchecked_new(
296                env.clone(),
297                env.strkey_to_address(strkey.to_object().to_val())
298                    .unwrap_infallible(),
299            )
300        }
301    }
302
303    /// Creates an `Address` corresponding to the provided Stellar strkey bytes.
304    ///
305    /// This behaves exactly in the same fashion as `from_strkey`, i.e. the bytes should contain
306    /// exactly the same contents as `String` would (i.e. base-32 ASCII string).
307    ///
308    /// The only supported strkey types are account keys (`G...`) and contract keys (`C...`). Any
309    /// other valid or invalid strkey will cause this to panic.
310    ///
311    /// Prefer using the `Address` directly as input or output argument. Only
312    /// use this in special cases when addresses need to be shared between
313    /// different environments (e.g. different chains).
314    pub fn from_string_bytes(strkey: &Bytes) -> Self {
315        let env = strkey.env();
316        unsafe {
317            Self::unchecked_new(
318                env.clone(),
319                env.strkey_to_address(strkey.to_object().to_val())
320                    .unwrap_infallible(),
321            )
322        }
323    }
324
325    /// Returns the executable type of this address, if any.
326    ///
327    /// Returns None when the contract or account does not exist.   
328    ///
329    /// For Wasm contracts, this also returns the hash of the contract code.
330    /// Otherwise, this just returns which kind of 'built-in' executable this is
331    /// (StellarAsset or Account).
332    pub fn executable(&self) -> Option<Executable> {
333        let executable_val: Val =
334            Env::get_address_executable(&self.env, self.obj).unwrap_infallible();
335        executable_val.into_val(&self.env)
336    }
337
338    /// Returns whether this address exists in the ledger.
339    ///
340    /// For the contract addresses, this means that there is a corresponding
341    /// contract instance deployed. For account addresses, this means that the
342    /// account entry exists in the ledger.
343    pub fn exists(&self) -> bool {
344        let executable_val: Val =
345            Env::get_address_executable(&self.env, self.obj).unwrap_infallible();
346        !executable_val.is_void()
347    }
348
349    /// Converts this `Address` into the corresponding Stellar strkey.
350    pub fn to_string(&self) -> String {
351        String::try_from_val(
352            &self.env,
353            &self.env.address_to_strkey(self.obj).unwrap_infallible(),
354        )
355        .unwrap_optimized()
356    }
357
358    #[inline(always)]
359    pub(crate) unsafe fn unchecked_new(env: Env, obj: AddressObject) -> Self {
360        Self { env, obj }
361    }
362
363    #[inline(always)]
364    pub fn env(&self) -> &Env {
365        &self.env
366    }
367
368    pub fn as_val(&self) -> &Val {
369        self.obj.as_val()
370    }
371
372    pub fn to_val(&self) -> Val {
373        self.obj.to_val()
374    }
375
376    pub fn as_object(&self) -> &AddressObject {
377        &self.obj
378    }
379
380    pub fn to_object(&self) -> AddressObject {
381        self.obj
382    }
383
384    /// Extracts the payload from the address.
385    ///
386    /// Returns:
387    /// - For contract addresses (C...), returns [`AddressPayload::ContractIdHash`]
388    ///   containing the 32-byte contract hash.
389    /// - For account addresses (G...), returns [`AddressPayload::AccountIdPublicKeyEd25519`]
390    ///   containing the 32-byte Ed25519 public key.
391    ///
392    /// Returns `None` if the address type is not recognized. This may occur if
393    /// a new address type has been introduced to the network that this version
394    /// of this library is not aware of.
395    ///
396    /// # Warning
397    ///
398    /// For account addresses, the returned Ed25519 public key corresponds to
399    /// the account's master key, which depending on the configuration of that
400    /// account may or may not be a signer of the account. Do not use this for
401    /// custom Ed25519 signature verification as a form of authentication
402    /// because the master key may not be configured the signer of the account.
403    #[cfg(any(test, feature = "hazmat-address"))]
404    #[cfg_attr(feature = "docs", doc(cfg(feature = "hazmat-address")))]
405    pub fn to_payload(&self) -> Option<AddressPayload> {
406        AddressPayload::from_address(self)
407    }
408
409    /// Constructs an [`Address`] from an [`AddressPayload`].
410    ///
411    /// This is the inverse of [`to_payload`][Address::to_payload].
412    ///
413    /// # Warning
414    ///
415    /// For account addresses, the returned Ed25519 public key corresponds to
416    /// the account's master key, which depending on the configuration of that
417    /// account may or may not be a signer of the account. Do not use this for
418    /// custom Ed25519 signature verification as a form of authentication
419    /// because the master key may not be configured the signer of the account.
420    #[cfg(any(test, feature = "hazmat-address"))]
421    #[cfg_attr(feature = "docs", doc(cfg(feature = "hazmat-address")))]
422    pub fn from_payload(env: &Env, payload: AddressPayload) -> Address {
423        payload.to_address(env)
424    }
425}
426
427#[cfg(any(not(target_family = "wasm"), test, feature = "testutils"))]
428use crate::env::xdr::{ContractId, Hash};
429use crate::unwrap::UnwrapOptimized;
430
431#[cfg(any(test, feature = "testutils"))]
432#[cfg_attr(feature = "docs", doc(cfg(feature = "testutils")))]
433impl crate::testutils::Address for Address {
434    fn generate(env: &Env) -> Self {
435        Self::try_from_val(
436            env,
437            &ScAddress::Contract(ContractId(Hash(env.with_generator(|mut g| g.address())))),
438        )
439        .unwrap()
440    }
441}
442
443#[cfg(not(target_family = "wasm"))]
444impl Address {
445    pub(crate) fn contract_id(&self) -> ContractId {
446        let sc_address: ScAddress = self.try_into().unwrap();
447        if let ScAddress::Contract(c) = sc_address {
448            c
449        } else {
450            panic!("address is not a contract {:?}", self);
451        }
452    }
453
454    pub(crate) fn from_contract_id(env: &Env, contract_id: [u8; 32]) -> Self {
455        Self::try_from_val(env, &ScAddress::Contract(ContractId(Hash(contract_id)))).unwrap()
456    }
457}