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