Skip to content

Commit 675ec71

Browse files
authored
Muxed address support for Soroban SDK. (#1454)
### What This introduces the `MuxedAddress` type that maps to either `AddressObject` or `MuxedAddressObject`. This also adds a new helper for building token events with muxed addresses as per CAP-67. ### Why Support for CAP-67. ### Known limitations N/A
1 parent 382d3e7 commit 675ec71

File tree

110 files changed

+626
-106
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

110 files changed

+626
-106
lines changed

soroban-sdk-macros/src/map_type.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ pub fn map_type(t: &Type, allow_hash: bool) -> Result<ScSpecTypeDef, Error> {
4242
"Error" => Ok(ScSpecTypeDef::Error),
4343
"Bytes" => Ok(ScSpecTypeDef::Bytes),
4444
"Address" => Ok(ScSpecTypeDef::Address),
45+
"MuxedAddress" => Ok(ScSpecTypeDef::MuxedAddress),
4546
"Timepoint" => Ok(ScSpecTypeDef::Timepoint),
4647
"Duration" => Ok(ScSpecTypeDef::Duration),
4748
// The BLS types defined below are represented in the contract's

soroban-sdk/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -736,6 +736,7 @@ pub mod unwrap;
736736
mod env;
737737

738738
mod address;
739+
mod muxed_address;
739740
mod symbol;
740741

741742
pub use env::{ConversionError, Env};
@@ -797,6 +798,7 @@ mod vec;
797798
pub use address::Address;
798799
pub use bytes::{Bytes, BytesN};
799800
pub use map::Map;
801+
pub use muxed_address::MuxedAddress;
800802
pub use symbol::Symbol;
801803
pub use vec::Vec;
802804
mod num;

soroban-sdk/src/muxed_address.rs

Lines changed: 316 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,316 @@
1+
use core::{cmp::Ordering, convert::Infallible, fmt::Debug};
2+
3+
use super::{
4+
env::internal::{AddressObject, Env as _, MuxedAddressObject, Tag},
5+
ConversionError, Env, TryFromVal, TryIntoVal, Val,
6+
};
7+
use crate::{env::internal, unwrap::UnwrapInfallible, Address};
8+
9+
#[cfg(not(target_family = "wasm"))]
10+
use crate::env::internal::xdr::{ScAddress, ScVal};
11+
12+
#[derive(Clone)]
13+
enum AddressObjectWrapper {
14+
Address(AddressObject),
15+
MuxedAddress(MuxedAddressObject),
16+
}
17+
18+
/// MuxedAddress is a union type that represents either the regular `Address`,
19+
/// or a 'multiplexed' address that consists of a regular address and a u64 id
20+
/// and can be used for representing the 'virtual' accounts that allows for
21+
/// managing multiple balances off-chain with only a single on-chain balance
22+
/// entry. The address part can be used as a regular `Address`, and the id
23+
/// part should be used only in the events for the off-chain processing.
24+
///
25+
/// This type is only necessary in a few special cases, such as token transfers
26+
/// that support non-custodial accounts (e.g. for the exchange support). Prefer
27+
/// using the regular `Address` type unless multiplexing support is necessary.
28+
///
29+
/// This type is compatible with `Address` at the contract interface level, i.e.
30+
/// if a contract accepts `MuxedAddress` as an input, then its callers may still
31+
/// pass `Address` into the call successfully. This means that if a
32+
/// contract has upgraded its interface to switch from `Address` argument to
33+
/// `MuxedAddress` argument, it won't break any of its existing clients.
34+
///
35+
/// Currently only the regular Stellar accounts can be multiplexed, i.e.
36+
/// multiplexed contract addresses don't exist.
37+
///
38+
/// Note, that multiplexed addresses can not be used directly as a storage key.
39+
/// This is a precaution to prevent accidental unexpected fragmentation of
40+
/// the key space (like creating an arbitrary number of balances for the same
41+
/// actual `Address`).
42+
#[derive(Clone)]
43+
pub struct MuxedAddress {
44+
env: Env,
45+
obj: AddressObjectWrapper,
46+
}
47+
48+
impl Debug for MuxedAddress {
49+
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
50+
#[cfg(target_family = "wasm")]
51+
match &self.obj {
52+
AddressObjectWrapper::Address(_) => write!(f, "Address(..)"),
53+
AddressObjectWrapper::MuxedAddress(_) => write!(f, "MuxedAddress(..)"),
54+
}
55+
#[cfg(not(target_family = "wasm"))]
56+
{
57+
use crate::env::internal::xdr;
58+
use stellar_strkey::Strkey;
59+
match &self.obj {
60+
AddressObjectWrapper::Address(address_object) => {
61+
Address::try_from_val(self.env(), address_object)
62+
.map_err(|_| core::fmt::Error)?
63+
.fmt(f)
64+
}
65+
AddressObjectWrapper::MuxedAddress(muxed_address_object) => {
66+
let sc_val = ScVal::try_from_val(self.env(), &muxed_address_object.to_val())
67+
.map_err(|_| core::fmt::Error)?;
68+
if let ScVal::Address(addr) = sc_val {
69+
match addr {
70+
xdr::ScAddress::MuxedAccount(muxed_account) => {
71+
let strkey = Strkey::MuxedAccountEd25519(
72+
stellar_strkey::ed25519::MuxedAccount {
73+
ed25519: muxed_account.ed25519.0,
74+
id: muxed_account.id,
75+
},
76+
);
77+
write!(f, "MuxedAccount({})", strkey.to_string())
78+
}
79+
_ => Err(core::fmt::Error),
80+
}
81+
} else {
82+
Err(core::fmt::Error)
83+
}
84+
}
85+
}
86+
}
87+
}
88+
}
89+
90+
impl Eq for MuxedAddress {}
91+
92+
impl PartialEq for MuxedAddress {
93+
fn eq(&self, other: &Self) -> bool {
94+
self.partial_cmp(other) == Some(Ordering::Equal)
95+
}
96+
}
97+
98+
impl PartialOrd for MuxedAddress {
99+
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
100+
Some(Ord::cmp(self, other))
101+
}
102+
}
103+
104+
impl Ord for MuxedAddress {
105+
fn cmp(&self, other: &Self) -> Ordering {
106+
let v = self
107+
.env
108+
.obj_cmp(self.to_val(), other.to_val())
109+
.unwrap_infallible();
110+
v.cmp(&0)
111+
}
112+
}
113+
114+
impl TryFromVal<Env, MuxedAddressObject> for MuxedAddress {
115+
type Error = Infallible;
116+
117+
fn try_from_val(env: &Env, val: &MuxedAddressObject) -> Result<Self, Self::Error> {
118+
Ok(unsafe { MuxedAddress::unchecked_new(env.clone(), *val) })
119+
}
120+
}
121+
122+
impl TryFromVal<Env, AddressObject> for MuxedAddress {
123+
type Error = Infallible;
124+
125+
fn try_from_val(env: &Env, val: &AddressObject) -> Result<Self, Self::Error> {
126+
Ok(unsafe { MuxedAddress::unchecked_new_from_address(env.clone(), *val) })
127+
}
128+
}
129+
130+
impl TryFromVal<Env, Val> for MuxedAddress {
131+
type Error = ConversionError;
132+
133+
fn try_from_val(env: &Env, val: &Val) -> Result<Self, Self::Error> {
134+
if val.get_tag() == Tag::AddressObject {
135+
Ok(AddressObject::try_from_val(env, val)?
136+
.try_into_val(env)
137+
.unwrap_infallible())
138+
} else {
139+
Ok(MuxedAddressObject::try_from_val(env, val)?
140+
.try_into_val(env)
141+
.unwrap_infallible())
142+
}
143+
}
144+
}
145+
146+
impl TryFromVal<Env, MuxedAddress> for Val {
147+
type Error = ConversionError;
148+
149+
fn try_from_val(_env: &Env, v: &MuxedAddress) -> Result<Self, Self::Error> {
150+
Ok(v.to_val())
151+
}
152+
}
153+
154+
impl TryFromVal<Env, &MuxedAddress> for Val {
155+
type Error = ConversionError;
156+
157+
fn try_from_val(_env: &Env, v: &&MuxedAddress) -> Result<Self, Self::Error> {
158+
Ok(v.to_val())
159+
}
160+
}
161+
162+
impl From<Address> for MuxedAddress {
163+
fn from(address: Address) -> Self {
164+
address
165+
.as_object()
166+
.try_into_val(address.env())
167+
.unwrap_infallible()
168+
}
169+
}
170+
171+
impl MuxedAddress {
172+
/// Returns the `Address` part of this multiplexed address.
173+
///
174+
/// The address part is necessary to perform most of the operations, such
175+
/// as authorization or storage.
176+
pub fn address(&self) -> Address {
177+
match &self.obj {
178+
AddressObjectWrapper::Address(address_object) => {
179+
Address::try_from_val(&self.env, address_object).unwrap_infallible()
180+
}
181+
AddressObjectWrapper::MuxedAddress(muxed_address_object) => Address::try_from_val(
182+
&self.env,
183+
&internal::Env::get_address_from_muxed_address(&self.env, *muxed_address_object)
184+
.unwrap_infallible(),
185+
)
186+
.unwrap_infallible(),
187+
}
188+
}
189+
190+
/// Returns the multiplexing identifier part of this multiplexed address,
191+
/// if any.
192+
///
193+
/// Returns `None` for the regular (non-multiplexed) addresses.
194+
///
195+
/// This identifier should normally be used in the events in order to allow
196+
/// for tracking the virtual balances associated with this address off-chain.
197+
pub fn id(&self) -> Option<u64> {
198+
match &self.obj {
199+
AddressObjectWrapper::Address(_) => None,
200+
AddressObjectWrapper::MuxedAddress(muxed_address_object) => Some(
201+
u64::try_from_val(
202+
&self.env,
203+
&internal::Env::get_id_from_muxed_address(&self.env, *muxed_address_object)
204+
.unwrap_infallible(),
205+
)
206+
.unwrap(),
207+
),
208+
}
209+
}
210+
211+
#[inline(always)]
212+
pub(crate) unsafe fn unchecked_new_from_address(env: Env, obj: AddressObject) -> Self {
213+
Self {
214+
env,
215+
obj: AddressObjectWrapper::Address(obj),
216+
}
217+
}
218+
219+
#[inline(always)]
220+
pub(crate) unsafe fn unchecked_new(env: Env, obj: MuxedAddressObject) -> Self {
221+
Self {
222+
env,
223+
obj: AddressObjectWrapper::MuxedAddress(obj),
224+
}
225+
}
226+
227+
#[inline(always)]
228+
pub fn env(&self) -> &Env {
229+
&self.env
230+
}
231+
232+
pub fn as_val(&self) -> &Val {
233+
match &self.obj {
234+
AddressObjectWrapper::Address(o) => o.as_val(),
235+
AddressObjectWrapper::MuxedAddress(o) => o.as_val(),
236+
}
237+
}
238+
239+
pub fn to_val(&self) -> Val {
240+
match self.obj {
241+
AddressObjectWrapper::Address(o) => o.to_val(),
242+
AddressObjectWrapper::MuxedAddress(o) => o.to_val(),
243+
}
244+
}
245+
}
246+
247+
#[cfg(not(target_family = "wasm"))]
248+
impl TryFromVal<Env, ScVal> for MuxedAddress {
249+
type Error = ConversionError;
250+
fn try_from_val(env: &Env, val: &ScVal) -> Result<Self, Self::Error> {
251+
let v = Val::try_from_val(env, val)?;
252+
match val {
253+
ScVal::Address(sc_address) => match sc_address {
254+
ScAddress::Account(_) | ScAddress::Contract(_) => {
255+
Ok(AddressObject::try_from_val(env, &v)?
256+
.try_into_val(env)
257+
.unwrap_infallible())
258+
}
259+
ScAddress::MuxedAccount(_) => Ok(MuxedAddressObject::try_from_val(env, &v)?
260+
.try_into_val(env)
261+
.unwrap_infallible()),
262+
ScAddress::ClaimableBalance(_) | ScAddress::LiquidityPool(_) => {
263+
panic!("unsupported ScAddress type")
264+
}
265+
},
266+
_ => panic!("incorrect scval type"),
267+
}
268+
}
269+
}
270+
271+
#[cfg(not(target_family = "wasm"))]
272+
impl TryFromVal<Env, ScAddress> for MuxedAddress {
273+
type Error = ConversionError;
274+
fn try_from_val(env: &Env, val: &ScAddress) -> Result<Self, Self::Error> {
275+
ScVal::Address(val.clone()).try_into_val(env)
276+
}
277+
}
278+
279+
#[cfg(any(test, feature = "testutils"))]
280+
#[cfg_attr(feature = "docs", doc(cfg(feature = "testutils")))]
281+
impl crate::testutils::MuxedAddress for MuxedAddress {
282+
fn generate(env: &Env) -> crate::MuxedAddress {
283+
let sc_val = ScVal::Address(crate::env::internal::xdr::ScAddress::MuxedAccount(
284+
crate::env::internal::xdr::MuxedEd25519Account {
285+
ed25519: crate::env::internal::xdr::Uint256(
286+
env.with_generator(|mut g| g.address()),
287+
),
288+
id: env.with_generator(|mut g| g.mux_id()),
289+
},
290+
));
291+
sc_val.try_into_val(env).unwrap()
292+
}
293+
294+
fn new<T: Into<MuxedAddress>>(address: T, id: u64) -> crate::MuxedAddress {
295+
let address: MuxedAddress = address.into();
296+
let sc_val = ScVal::try_from_val(&address.env, address.as_val()).unwrap();
297+
let account_id = match sc_val {
298+
ScVal::Address(address) => match address {
299+
ScAddress::MuxedAccount(muxed_account) => muxed_account.ed25519,
300+
ScAddress::Account(crate::env::internal::xdr::AccountId(
301+
crate::env::internal::xdr::PublicKey::PublicKeyTypeEd25519(account_id),
302+
)) => account_id,
303+
ScAddress::Contract(_) => panic!("contract addresses can not be multiplexed"),
304+
ScAddress::ClaimableBalance(_) | ScAddress::LiquidityPool(_) => unreachable!(),
305+
},
306+
_ => unreachable!(),
307+
};
308+
let result_sc_val = ScVal::Address(ScAddress::MuxedAccount(
309+
crate::env::internal::xdr::MuxedEd25519Account {
310+
id,
311+
ed25519: account_id,
312+
},
313+
));
314+
result_sc_val.try_into_val(&address.env).unwrap()
315+
}
316+
}

soroban-sdk/src/tests.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ mod crypto_secp256r1;
3434
mod crypto_sha256;
3535
mod env;
3636
mod max_ttl;
37+
mod muxed_address;
3738
mod prng;
3839
mod proptest_scval_cmp;
3940
mod proptest_val_cmp;

0 commit comments

Comments
 (0)