Skip to content

Spike: Token Muxed Extension #1443

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 8 commits into from
Closed
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 11 additions & 8 deletions soroban-sdk/src/bytes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1076,18 +1076,21 @@ impl<const N: usize> From<&BytesN<N>> for Bytes {
}

#[cfg(not(target_family = "wasm"))]
impl<const N: usize> TryFrom<&BytesN<N>> for ScVal {
type Error = ConversionError;
fn try_from(v: &BytesN<N>) -> Result<Self, ConversionError> {
Ok(ScVal::try_from_val(&v.0.env, &v.0.obj.to_val())?)
impl<const N: usize> From<&BytesN<N>> for ScVal {
fn from(v: &BytesN<N>) -> Self {
// This conversion occurs only in test utilities, and theoretically all
// values should convert to an ScVal because the Env won't let the host
// type to exist otherwise, unwrapping. Even if there are edge cases
// that don't, this is a trade off for a better test developer
// experience.
ScVal::try_from_val(&v.0.env, &v.0.obj.to_val()).unwrap()
}
}

#[cfg(not(target_family = "wasm"))]
impl<const N: usize> TryFrom<BytesN<N>> for ScVal {
type Error = ConversionError;
fn try_from(v: BytesN<N>) -> Result<Self, ConversionError> {
(&v).try_into()
impl<const N: usize> From<BytesN<N>> for ScVal {
fn from(v: BytesN<N>) -> Self {
(&v).into()
}
}

Expand Down
35 changes: 35 additions & 0 deletions soroban-sdk/src/testutils/arbitrary.rs
Original file line number Diff line number Diff line change
Expand Up @@ -682,6 +682,41 @@ mod objects {
}
}

/// Implementations for imaginary types, that don't really exist outside of the
/// Rust SDK and are merely mapped to and from other types.
mod imaginary {
use super::api::*;
use crate::token::muxed_ext::Mux;
use crate::ConversionError;
use crate::{BytesN, Env, String, TryFromVal};
use arbitrary::Arbitrary;
use std::string::String as RustString;

#[derive(Arbitrary, Debug, Clone, Eq, PartialEq, Ord, PartialOrd)]
pub enum ArbitraryMux {
None,
Id(u64),
Text(RustString),
Hash([u8; 32]),
}

impl SorobanArbitrary for Mux {
type Prototype = ArbitraryMux;
}

impl TryFromVal<Env, ArbitraryMux> for Mux {
type Error = ConversionError;
fn try_from_val(env: &Env, v: &ArbitraryMux) -> Result<Self, Self::Error> {
match v {
ArbitraryMux::None => Ok(Mux::None),
ArbitraryMux::Id(id) => Ok(Mux::Id(*id)),
ArbitraryMux::Text(text) => Ok(Mux::Text(String::from_str(env, &text))),
ArbitraryMux::Hash(hash) => Ok(Mux::Hash(BytesN::from_array(env, &hash))),
}
}
}
}

/// Implementations of `soroban_sdk::testutils::arbitrary::api` for tuples of Soroban types.
///
/// The implementation is similar to objects, but macroized.
Expand Down
2 changes: 2 additions & 0 deletions soroban-sdk/src/token.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
//! Use [`TokenClient`] for calling token contracts such as the Stellar Asset
//! Contract.

pub mod muxed_ext;

use crate::{contractclient, contractspecfn, Address, Env, String};

// The interface below was copied from
Expand Down
110 changes: 110 additions & 0 deletions soroban-sdk/src/token/muxed_ext.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
use crate::{
contractclient, contractspecfn, Address, BytesN, ConversionError, Env, String, TryFromVal,
TryIntoVal, Val,
};
use core::fmt::Debug;

/// Extension interface for Token contracts that implement the transfer_muxed
/// extension, such as the Stellar Asset Contract.
///
/// Defined by [SEP-??].
///
/// [SEP-??]: https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-00??.md
///
/// Tokens allow holders of the token to transfer tokens to other addresses, and
/// the transfer_muxed extension allows a token to be transferred with an
/// accompanying muxed ID for both the from and to addresses. The muxed IDs are
/// emitted in respective events.
///
/// Tokens implementing the extension expose a single function for doing so:
/// - [`transfer_muxed`][Self::transfer_muxed]
#[contractspecfn(name = "super::StellarAssetSpec", export = false)]
#[contractclient(crate_path = "crate", name = "TokenMuxedExtTransferClient")]
pub trait TokenMuxedExtInterface {
/// Transfer `amount` from `from` to `to`.
///
/// Passess through the `from_id` and `to_id` to the event.
///
/// # Arguments
///
/// * `from` - The address holding the balance of tokens which will be
/// withdrawn from.
/// * `from_mux` - The muxed ID of the sender to be emitted in the event.
/// * `to` - The address which will receive the transferred tokens.
/// * `to_mux` - The muxed ID of the receiver to be emitted in the event.
/// * `amount` - The amount of tokens to be transferred.
///
/// # Events
///
/// Emits an event with topics `["transfer_muxed", from: Address, to: Address],
/// data = {amount: i128, from_mux: Mux, to_mux: Mux}`
fn transfer_muxed(
env: Env,
from: Address,
from_mux: Mux,
to: Address,
to_mux: Mux,
amount: i128,
);
}

/// Mux is a value that off-chain identifies a sub-identifier of an Address
/// on-chain.
///
/// A mux is also commonly referred to as a memo.
///
/// A mux may be a void (none), an ID (64-bit number), a String, or a 32-byte
/// Hash.
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum Mux {
None,
Id(u64),
Text(String),
Hash(BytesN<32>),
}

impl TryFromVal<Env, Val> for Mux {
type Error = ConversionError;

fn try_from_val(env: &Env, v: &Val) -> Result<Self, Self::Error> {
if v.is_void() {
Ok(Self::None)
} else if let Ok(v) = v.try_into_val(env) {
Ok(Self::Id(v))
} else if let Ok(v) = v.try_into_val(env) {
Ok(Self::Text(v))
} else if let Ok(v) = v.try_into_val(env) {
Ok(Self::Hash(v))
} else {
Err(ConversionError)
}
}
}

impl TryFromVal<Env, Mux> for Val {
type Error = ConversionError;

fn try_from_val(env: &Env, v: &Mux) -> Result<Self, Self::Error> {
match v {
Mux::None => Ok(Val::VOID.to_val()),
Mux::Id(v) => v.try_into_val(env).map_err(|_| ConversionError),
Mux::Text(v) => v.try_into_val(env).map_err(|_| ConversionError),
Mux::Hash(v) => v.try_into_val(env).map_err(|_| ConversionError),
}
}
}

#[cfg(not(target_family = "wasm"))]
use crate::env::internal::xdr::ScVal;

#[cfg(not(target_family = "wasm"))]
impl From<&Mux> for ScVal {
fn from(v: &Mux) -> Self {
match v {
Mux::None => ScVal::Void,
Mux::Id(v) => ScVal::U64(*v),
Mux::Text(v) => v.into(),
Mux::Hash(v) => v.into(),
}
}
}
26 changes: 25 additions & 1 deletion soroban-token-sdk/src/event.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use soroban_sdk::{symbol_short, Address, Env, Symbol};
use soroban_sdk::{contracttype, symbol_short, token::muxed_ext::Mux, Address, Env, Symbol};

pub struct Events {
env: Env,
Expand All @@ -22,6 +22,23 @@ impl Events {
self.env.events().publish(topics, amount);
}

pub fn transfer_muxed(
&self,
from: Address,
from_mux: Mux,
to: Address,
to_mux: Mux,
amount: i128,
) {
let topics = (Symbol::new(&self.env, "transfer_muxed"), from, to);
let data = TransferMuxedData {
amount,
from_mux,
to_mux,
};
self.env.events().publish(topics, data);
}

pub fn mint(&self, admin: Address, to: Address, amount: i128) {
let topics = (symbol_short!("mint"), admin, to);
self.env.events().publish(topics, amount);
Expand All @@ -47,3 +64,10 @@ impl Events {
self.env.events().publish(topics, amount);
}
}

#[contracttype]
struct TransferMuxedData {
amount: i128,
from_mux: Mux,
to_mux: Mux,
}
Loading