soroban_sdk/
address.rs

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