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