soroban_sdk/
token.rs

1//! Token contains types for calling and accessing token contracts, including
2//! the Stellar Asset Contract.
3//!
4//! See [`TokenInterface`] for the interface of token contracts such as the
5//! Stellar Asset Contract.
6//!
7//! Use [`TokenClient`] for calling token contracts such as the Stellar Asset
8//! Contract.
9
10use crate::{contractclient, contractspecfn, Address, Env, String};
11
12// The interface below was copied from
13// https://github.com/stellar/rs-soroban-env/blob/main/soroban-env-host/src/native_contract/token/contract.rs
14// at commit b3c188f48dec51a956c1380fb6fe92201a3f716b.
15//
16// Differences between this interface and the built-in contract
17// 1. The return values here don't return Results.
18// 2. The implementations have been replaced with a panic.
19// 3. &Host type usage are replaced with Env
20
21#[doc(hidden)]
22#[deprecated(note = "use TokenInterface")]
23pub use TokenInterface as Interface;
24
25#[doc(hidden)]
26#[deprecated(note = "use TokenClient")]
27pub use TokenClient as Client;
28
29/// Interface for Token contracts, such as the Stellar Asset Contract.
30///
31/// Defined by [SEP-41].
32///
33/// [SEP-41]: https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0041.md
34///
35/// The token interface provides the following functionality.
36///
37/// If a contract implementing the interface does not support some of the
38/// functionality, it should return an error.
39///
40/// The interface does not define any set of standard errors. Errors can be
41/// defined by the implementing contract.
42///
43/// ## Meta
44///
45/// Tokens implementing the interface expose meta functions about the token:
46/// - [`decimals`][Self::decimals]
47/// - [`name`][Self::name]
48/// - [`symbol`][Self::symbol]
49///
50/// ## Balances
51///
52/// Tokens track a balance for each address that holds the token. Tokens implementing the interface expose
53/// a single function for getting the balance that an address holds:
54/// - [`balance`][Self::balance]
55///
56/// ## Transfers
57///
58/// Tokens allow holders of the token to transfer tokens to other addresses.
59/// Tokens implementing the interface expose a single function for doing so:
60/// - [`transfer`][Self::transfer]
61///
62/// ## Burning
63///
64/// Tokens allow holders of the token to burn, i.e. dispose of, tokens without
65/// transferring them to another holder. Tokens implementing the interface
66/// expose a single function for doing so:
67/// - [`burn`][Self::burn]
68///
69/// ## Allowances
70///
71/// Tokens can allow holders to permit others to transfer amounts from their
72/// balance using the following functions.
73/// - [`allowance`][Self::allowance]
74/// - [`approve`][Self::approve]
75/// - [`transfer_from`][Self::transfer_from]
76/// - [`burn_from`][Self::burn_from]
77///
78/// ## Minting
79///
80/// There are no functions in the token interface for minting tokens. Minting is
81/// an administrative function that can differ significantly from one token to
82/// the next.
83#[contractspecfn(name = "StellarAssetSpec", export = false)]
84#[contractclient(crate_path = "crate", name = "TokenClient")]
85pub trait TokenInterface {
86    /// Returns the allowance for `spender` to transfer from `from`.
87    ///
88    /// The amount returned is the amount that spender is allowed to transfer
89    /// out of from's balance. When the spender transfers amounts, the allowance
90    /// will be reduced by the amount transferred.
91    ///
92    /// # Arguments
93    ///
94    /// * `from` - The address holding the balance of tokens to be drawn from.
95    /// * `spender` - The address spending the tokens held by `from`.
96    fn allowance(env: Env, from: Address, spender: Address) -> i128;
97
98    /// Set the allowance by `amount` for `spender` to transfer/burn from
99    /// `from`.
100    ///
101    /// The amount set is the amount that spender is approved to transfer out of
102    /// from's balance. The spender will be allowed to transfer amounts, and
103    /// when an amount is transferred the allowance will be reduced by the
104    /// amount transferred.
105    ///
106    /// # Arguments
107    ///
108    /// * `from` - The address holding the balance of tokens to be drawn from.
109    /// * `spender` - The address being authorized to spend the tokens held by
110    ///   `from`.
111    /// * `amount` - The tokens to be made available to `spender`.
112    /// * `expiration_ledger` - The ledger number where this allowance expires. Cannot
113    ///    be less than the current ledger number unless the amount is being set to 0.
114    ///    An expired entry (where expiration_ledger < the current ledger number)
115    ///    should be treated as a 0 amount allowance.
116    ///
117    /// # Events
118    ///
119    /// Emits an event with topics `["approve", from: Address,
120    /// spender: Address], data = [amount: i128, expiration_ledger: u32]`
121    fn approve(env: Env, from: Address, spender: Address, amount: i128, expiration_ledger: u32);
122
123    /// Returns the balance of `id`.
124    ///
125    /// # Arguments
126    ///
127    /// * `id` - The address for which a balance is being queried. If the
128    ///   address has no existing balance, returns 0.
129    fn balance(env: Env, id: Address) -> i128;
130
131    /// Transfer `amount` from `from` to `to`.
132    ///
133    /// # Arguments
134    ///
135    /// * `from` - The address holding the balance of tokens which will be
136    ///   withdrawn from.
137    /// * `to` - The address which will receive the transferred tokens.
138    /// * `amount` - The amount of tokens to be transferred.
139    ///
140    /// # Events
141    ///
142    /// Emits an event with topics `["transfer", from: Address, to: Address],
143    /// data = amount: i128`
144    fn transfer(env: Env, from: Address, to: Address, amount: i128);
145
146    /// Transfer `amount` from `from` to `to`, consuming the allowance that
147    /// `spender` has on `from`'s balance. Authorized by spender
148    /// (`spender.require_auth()`).
149    ///
150    /// The spender will be allowed to transfer the amount from from's balance
151    /// if the amount is less than or equal to the allowance that the spender
152    /// has on the from's balance. The spender's allowance on from's balance
153    /// will be reduced by the amount.
154    ///
155    /// # Arguments
156    ///
157    /// * `spender` - The address authorizing the transfer, and having its
158    ///   allowance consumed during the transfer.
159    /// * `from` - The address holding the balance of tokens which will be
160    ///   withdrawn from.
161    /// * `to` - The address which will receive the transferred tokens.
162    /// * `amount` - The amount of tokens to be transferred.
163    ///
164    /// # Events
165    ///
166    /// Emits an event with topics `["transfer", from: Address, to: Address],
167    /// data = amount: i128`
168    fn transfer_from(env: Env, spender: Address, from: Address, to: Address, amount: i128);
169
170    /// Burn `amount` from `from`.
171    ///
172    /// Reduces from's balance by the amount, without transferring the balance
173    /// to another holder's balance.
174    ///
175    /// # Arguments
176    ///
177    /// * `from` - The address holding the balance of tokens which will be
178    ///   burned from.
179    /// * `amount` - The amount of tokens to be burned.
180    ///
181    /// # Events
182    ///
183    /// Emits an event with topics `["burn", from: Address], data = amount:
184    /// i128`
185    fn burn(env: Env, from: Address, amount: i128);
186
187    /// Burn `amount` from `from`, consuming the allowance of `spender`.
188    ///
189    /// Reduces from's balance by the amount, without transferring the balance
190    /// to another holder's balance.
191    ///
192    /// The spender will be allowed to burn the amount from from's balance, if
193    /// the amount is less than or equal to the allowance that the spender has
194    /// on the from's balance. The spender's allowance on from's balance will be
195    /// reduced by the amount.
196    ///
197    /// # Arguments
198    ///
199    /// * `spender` - The address authorizing the burn, and having its allowance
200    ///   consumed during the burn.
201    /// * `from` - The address holding the balance of tokens which will be
202    ///   burned from.
203    /// * `amount` - The amount of tokens to be burned.
204    ///
205    /// # Events
206    ///
207    /// Emits an event with topics `["burn", from: Address], data = amount:
208    /// i128`
209    fn burn_from(env: Env, spender: Address, from: Address, amount: i128);
210
211    /// Returns the number of decimals used to represent amounts of this token.
212    ///
213    /// # Panics
214    ///
215    /// If the contract has not yet been initialized.
216    fn decimals(env: Env) -> u32;
217
218    /// Returns the name for this token.
219    ///
220    /// # Panics
221    ///
222    /// If the contract has not yet been initialized.
223    fn name(env: Env) -> String;
224
225    /// Returns the symbol for this token.
226    ///
227    /// # Panics
228    ///
229    /// If the contract has not yet been initialized.
230    fn symbol(env: Env) -> String;
231}
232
233/// Interface for admin capabilities for Token contracts, such as the Stellar
234/// Asset Contract.
235#[contractspecfn(name = "StellarAssetSpec", export = false)]
236#[contractclient(crate_path = "crate", name = "StellarAssetClient")]
237pub trait StellarAssetInterface {
238    /// Sets the administrator to the specified address `new_admin`.
239    ///
240    /// # Arguments
241    ///
242    /// * `new_admin` - The address which will henceforth be the administrator
243    ///   of this token contract.
244    ///
245    /// # Events
246    ///
247    /// Emits an event with topics `["set_admin", admin: Address], data =
248    /// [new_admin: Address]`
249    fn set_admin(env: Env, new_admin: Address);
250
251    /// Returns the admin of the contract.
252    ///
253    /// # Panics
254    ///
255    /// If the admin is not set.
256    fn admin(env: Env) -> Address;
257
258    /// Sets whether the account is authorized to use its balance. If
259    /// `authorized` is true, `id` should be able to use its balance.
260    ///
261    /// # Arguments
262    ///
263    /// * `id` - The address being (de-)authorized.
264    /// * `authorize` - Whether or not `id` can use its balance.
265    ///
266    /// # Events
267    ///
268    /// Emits an event with topics `["set_authorized", id: Address], data =
269    /// [authorize: bool]`
270    fn set_authorized(env: Env, id: Address, authorize: bool);
271
272    /// Returns true if `id` is authorized to use its balance.
273    ///
274    /// # Arguments
275    ///
276    /// * `id` - The address for which token authorization is being checked.
277    fn authorized(env: Env, id: Address) -> bool;
278
279    /// Mints `amount` to `to`.
280    ///
281    /// # Arguments
282    ///
283    /// * `to` - The address which will receive the minted tokens.
284    /// * `amount` - The amount of tokens to be minted.
285    ///
286    /// # Events
287    ///
288    /// Emits an event with topics `["mint", admin: Address, to: Address], data
289    /// = amount: i128`
290    fn mint(env: Env, to: Address, amount: i128);
291
292    /// Clawback `amount` from `from` account. `amount` is burned in the
293    /// clawback process.
294    ///
295    /// # Arguments
296    ///
297    /// * `from` - The address holding the balance from which the clawback will
298    ///   take tokens.
299    /// * `amount` - The amount of tokens to be clawed back.
300    ///
301    /// # Events
302    ///
303    /// Emits an event with topics `["clawback", admin: Address, to: Address],
304    /// data = amount: i128`
305    fn clawback(env: Env, from: Address, amount: i128);
306}
307
308/// Spec contains the contract spec of Token contracts, including the general
309/// interface, as well as the admin interface, such as the Stellar Asset
310/// Contract.
311#[doc(hidden)]
312pub struct StellarAssetSpec;
313
314pub(crate) const SPEC_XDR_INPUT: &[&[u8]] = &[
315    &StellarAssetSpec::spec_xdr_allowance(),
316    &StellarAssetSpec::spec_xdr_authorized(),
317    &StellarAssetSpec::spec_xdr_approve(),
318    &StellarAssetSpec::spec_xdr_balance(),
319    &StellarAssetSpec::spec_xdr_burn(),
320    &StellarAssetSpec::spec_xdr_burn_from(),
321    &StellarAssetSpec::spec_xdr_clawback(),
322    &StellarAssetSpec::spec_xdr_decimals(),
323    &StellarAssetSpec::spec_xdr_mint(),
324    &StellarAssetSpec::spec_xdr_name(),
325    &StellarAssetSpec::spec_xdr_set_admin(),
326    &StellarAssetSpec::spec_xdr_admin(),
327    &StellarAssetSpec::spec_xdr_set_authorized(),
328    &StellarAssetSpec::spec_xdr_symbol(),
329    &StellarAssetSpec::spec_xdr_transfer(),
330    &StellarAssetSpec::spec_xdr_transfer_from(),
331];
332
333pub(crate) const SPEC_XDR_LEN: usize = 6456;
334
335impl StellarAssetSpec {
336    /// Returns the XDR spec for the Token contract.
337    pub const fn spec_xdr() -> [u8; SPEC_XDR_LEN] {
338        let input = SPEC_XDR_INPUT;
339        // Concatenate all XDR for each item that makes up the token spec.
340        let mut output = [0u8; SPEC_XDR_LEN];
341        let mut input_i = 0;
342        let mut output_i = 0;
343        while input_i < input.len() {
344            let subinput = input[input_i];
345            let mut subinput_i = 0;
346            while subinput_i < subinput.len() {
347                output[output_i] = subinput[subinput_i];
348                output_i += 1;
349                subinput_i += 1;
350            }
351            input_i += 1;
352        }
353
354        // Check that the numbers of bytes written is equal to the number of bytes
355        // expected in the output.
356        if output_i != output.len() {
357            panic!("unexpected output length",);
358        }
359
360        output
361    }
362}