Skip to content

Commit 37fed98

Browse files
Lean into Transfer events with data as a map (#1534)
### What Remove the `token::publish_transfer_event` function. Rename Transfer to TransferLegacy. Rename TransferMuxed to Transfer. ### Why To encourage developers publishing the newer and easier to extend Transfer event. While it's true that the Stellar Asset Contract, for largely legacy reasons, continues to publish a `transfer` event with the amount as the data, there's no reason for the SDK to encourage other token implementations to do the same. Rather than giving developers a function to call to publish the transfer event across these two different data forms, we should just point them to publishing the newer form that is extendable. ### Merging This PR is intended to merge to `main` after #1533. It is marked as a draft PR until #1533 is merged.
1 parent 6f324d8 commit 37fed98

9 files changed

Lines changed: 195 additions & 68 deletions

File tree

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

soroban-sdk/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ soroban-env-host = { workspace = true, features = ["testutils"] }
4646
soroban-ledger-snapshot = { workspace = true, features = ["testutils"] }
4747
stellar-xdr = { workspace = true, features = ["curr", "std"] }
4848
soroban-spec = { workspace = true }
49+
soroban-token-sdk = { workspace = true }
4950
ed25519-dalek = "2.0.0"
5051
rand = "0.8.5"
5152
ctor = "0.2.9"

soroban-sdk/src/_migrating/v23_token_transfer.rs

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -19,20 +19,16 @@
1919
//! `transfer` implementation that still uses `Address` destination:
2020
//!
2121
//! ```
22-
//! use soroban_sdk::{Env, Address, token};
22+
//! use soroban_sdk::{Env, Address};
23+
//! use soroban_token_sdk::TokenUtils;
2324
//! // ... inside some token contract ...
2425
//! fn transfer(env: Env, from: Address, to: Address, amount: i128) {
2526
//! // Authorize the transfer source.
2627
//! from.require_auth();
2728
//! // Token-specific implementation of balance movement.
2829
//! token_impl::move_balance(&env, &from, &to, amount);
29-
//! // Publish an event (notice that this uses the new event format - see
30-
//! // the previous migration step).
31-
//! token::Transfer {
32-
//! from,
33-
//! to,
34-
//! amount,
35-
//! }.publish(&env);
30+
//! // Publish the event.
31+
//! TokenUtils::new(&env).events().transfer(from, to, amount);
3632
//! }
3733
//!
3834
//! mod token_impl {
@@ -55,9 +51,13 @@
5551
//! let to = muxed_to.address();
5652
//! // Token-specific implementation of balance movement (same as before).
5753
//! token_impl::move_balance(&env, &from, &to, amount);
58-
//! // Publish an appropriate transfer event that includes the muxed ID
59-
//! // when it's non-None.
60-
//! token::publish_transfer_event(&env, &from, &muxed_to, amount);
54+
//! // Publish the transfer event.
55+
//! token::Transfer {
56+
//! from,
57+
//! to,
58+
//! to_muxed_id: muxed_to.id(),
59+
//! amount,
60+
//! }.publish(&env);
6161
//! }
6262
//!
6363
//! mod token_impl {

soroban-sdk/src/tests/token_events.rs

Lines changed: 50 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use soroban_sdk::{
77
contract, symbol_short,
88
testutils::{Address as _, Events as _, MuxedAddress as _},
99
token::StellarAssetClient,
10-
token::{Approve, Burn, Clawback, Mint, Transfer, TransferMuxed},
10+
token::{Approve, Burn, Clawback, Mint, Transfer, TransferLegacy},
1111
vec, xdr, Address, Env, IntoVal, Map, MuxedAddress, Symbol, Val,
1212
};
1313

@@ -66,15 +66,15 @@ fn test_approve() {
6666
}
6767

6868
#[test]
69-
fn test_transfer() {
69+
fn test_transfer_legacy() {
7070
let env = Env::default();
7171
env.mock_all_auths();
7272

7373
let from = Address::generate(&env);
7474
let to = Address::generate(&env);
7575
let amount = 123;
7676

77-
let event = Transfer {
77+
let event = TransferLegacy {
7878
from: from.clone(),
7979
to: to.clone(),
8080
amount,
@@ -116,18 +116,62 @@ fn test_transfer() {
116116
}
117117

118118
#[test]
119-
fn test_transfer_muxed() {
119+
fn test_transfer_without_id() {
120+
let env = Env::default();
121+
env.mock_all_auths();
122+
123+
let from = Address::generate(&env);
124+
let to: MuxedAddress = MuxedAddress::generate(&env).address().into();
125+
let amount = 123;
126+
127+
let event = Transfer {
128+
from: from.clone(),
129+
to: to.address(),
130+
to_muxed_id: to.id(),
131+
amount,
132+
};
133+
134+
// Verify the event publishes the expected topics and data.
135+
let topics = (symbol_short!("transfer"), from.clone(), to.address());
136+
let data = Map::<Symbol, Val>::from_array(
137+
&env,
138+
[
139+
(Symbol::new(&env, "to_muxed_id"), Val::VOID.to_val()),
140+
(Symbol::new(&env, "amount"), amount.into_val(&env)),
141+
],
142+
);
143+
144+
let id = env.register(Contract, ());
145+
env.as_contract(&id, || event.publish(&env));
146+
let token_events = env.events().all();
147+
assert_eq!(
148+
token_events,
149+
vec![
150+
&env,
151+
(id.clone(), topics.into_val(&env), data.into_val(&env))
152+
]
153+
);
154+
155+
// No comparison is made with the Stellar Asset Contract for publishing Transfer with a
156+
// MuxedAddress that does not contain an ID, because the Stellar Asset Contract for legacy
157+
// reasons, to minimise the changes to its behavior over time, still publishes TransferLegacy
158+
// in this case. See [`test_transfer_with_id`] for a test that exercises Transfer in the case
159+
// that the Stellar Asset contract does publish that event.
160+
}
161+
162+
#[test]
163+
fn test_transfer_with_id() {
120164
let env = Env::default();
121165
env.mock_all_auths();
122166

123167
let from = Address::generate(&env);
124168
let to = MuxedAddress::generate(&env);
125169
let amount = 123;
126170

127-
let event = TransferMuxed {
171+
let event = Transfer {
128172
from: from.clone(),
129173
to: to.address(),
130-
to_muxed_id: to.id().unwrap(),
174+
to_muxed_id: to.id(),
131175
amount,
132176
};
133177

soroban-sdk/src/token.rs

Lines changed: 12 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -139,16 +139,9 @@ pub trait TokenInterface {
139139
///
140140
/// # Events
141141
///
142-
/// Emits an event with topics
143-
/// `["transfer", from: Address, to: Address]
144-
/// If `to` is not a muxed address (i.e. has no muxed_id set), then data
145-
/// will have the following format (the `Transfer` event):
146-
/// `data = amount: i128`
147-
///
148-
/// If `to` is a muxed address (i.e. has a muxed_id set), then data will
149-
/// have be emitted as map with Symbol keys in the following format (the
150-
/// `TransferMuxed` event):
151-
/// `data = { "amount": i128, "to_muxed_id": u64 }`
142+
/// Publishes the [`Transfer`] event.
143+
///
144+
/// Legacy implementations may publish [`TransferLegacy`].
152145
fn transfer(env: Env, from: Address, to: MuxedAddress, amount: i128);
153146

154147
/// Transfer `amount` from `from` to `to`, consuming the allowance that
@@ -249,7 +242,7 @@ pub struct Approve {
249242
}
250243

251244
#[contractevent(crate_path = "crate", topics = ["transfer"], data_format = "single-value", export = false)]
252-
pub struct Transfer {
245+
pub struct TransferLegacy {
253246
#[topic]
254247
pub from: Address,
255248
#[topic]
@@ -258,12 +251,12 @@ pub struct Transfer {
258251
}
259252

260253
#[contractevent(crate_path = "crate", topics = ["transfer"], data_format = "map", export = false)]
261-
pub struct TransferMuxed {
254+
pub struct Transfer {
262255
#[topic]
263256
pub from: Address,
264257
#[topic]
265258
pub to: Address,
266-
pub to_muxed_id: u64,
259+
pub to_muxed_id: Option<u64>,
267260
pub amount: i128,
268261
}
269262

@@ -288,30 +281,6 @@ pub struct Clawback {
288281
pub amount: i128,
289282
}
290283

291-
/// Publish a `transfer` event.
292-
///
293-
/// Publishes a [`Transfer`] event if the `to` has no muxed ID.
294-
///
295-
/// Publishes a [`TransferMuxed`] event if the `to` has a muxed ID.
296-
pub fn publish_transfer_event(env: &Env, from: &Address, to: &MuxedAddress, amount: i128) {
297-
if let Some(to_muxed_id) = to.id() {
298-
TransferMuxed {
299-
from: from.clone(),
300-
to: to.address(),
301-
to_muxed_id,
302-
amount,
303-
}
304-
.publish(env);
305-
} else {
306-
Transfer {
307-
from: from.clone(),
308-
to: to.address(),
309-
amount,
310-
}
311-
.publish(env);
312-
}
313-
}
314-
315284
/// Spec contains the contract spec of Token contracts.
316285
#[doc(hidden)]
317286
pub struct TokenSpec;
@@ -328,14 +297,14 @@ pub(crate) const TOKEN_SPEC_XDR_INPUT: &[&[u8]] = &[
328297
&TokenSpec::spec_xdr_transfer(),
329298
&TokenSpec::spec_xdr_transfer_from(),
330299
&Approve::spec_xdr(),
300+
&TransferLegacy::spec_xdr(),
331301
&Transfer::spec_xdr(),
332-
&TransferMuxed::spec_xdr(),
333302
&Burn::spec_xdr(),
334303
&Mint::spec_xdr(),
335304
&Clawback::spec_xdr(),
336305
];
337306

338-
pub(crate) const TOKEN_SPEC_XDR_LEN: usize = 5724;
307+
pub(crate) const TOKEN_SPEC_XDR_LEN: usize = 5392;
339308

340309
impl TokenSpec {
341310
/// Returns the XDR spec for the Token contract.
@@ -427,16 +396,9 @@ pub trait StellarAssetInterface {
427396
///
428397
/// # Events
429398
///
430-
/// Emits an event with topics
431-
/// `["transfer", from: Address, to: Address]
432-
/// If `to` is not a muxed address (i.e. has no muxed_id set), then data
433-
/// will have the following format (the `Transfer` event):
434-
/// `data = amount: i128`
399+
/// Publishes the [`Transfer`] event.
435400
///
436-
/// If `to` is a muxed address (i.e. has a muxed_id set), then data will
437-
/// have be emitted as map with Symbol keys in the following format (the
438-
/// `TransferMuxed` event):
439-
/// `data = { "amount": i128, "to_muxed_id": u64 }`
401+
/// Legacy implementations may publish [`TransferLegacy`].
440402
fn transfer(env: Env, from: Address, to: MuxedAddress, amount: i128);
441403

442404
/// Transfer `amount` from `from` to `to`, consuming the allowance that
@@ -633,16 +595,16 @@ pub(crate) const STELLAR_ASSET_SPEC_XDR_INPUT: &[&[u8]] = &[
633595
&StellarAssetSpec::spec_xdr_transfer(),
634596
&StellarAssetSpec::spec_xdr_transfer_from(),
635597
&Approve::spec_xdr(),
598+
&TransferLegacy::spec_xdr(),
636599
&Transfer::spec_xdr(),
637-
&TransferMuxed::spec_xdr(),
638600
&Burn::spec_xdr(),
639601
&Mint::spec_xdr(),
640602
&Clawback::spec_xdr(),
641603
&SetAdmin::spec_xdr(),
642604
&SetAuthorized::spec_xdr(),
643605
];
644606

645-
pub(crate) const STELLAR_ASSET_SPEC_XDR_LEN: usize = 7664;
607+
pub(crate) const STELLAR_ASSET_SPEC_XDR_LEN: usize = 7332;
646608

647609
impl StellarAssetSpec {
648610
/// Returns the XDR spec for the Token contract.

soroban-sdk/test_snapshots/tests/token_events/test_transfer.1.json renamed to soroban-sdk/test_snapshots/tests/token_events/test_transfer_legacy.1.json

File renamed without changes.

soroban-sdk/test_snapshots/tests/token_events/test_transfer_muxed.1.json renamed to soroban-sdk/test_snapshots/tests/token_events/test_transfer_with_id.1.json

File renamed without changes.

0 commit comments

Comments
 (0)