From cf7c8741ae0e44c4d19963f4b98f17ce152c39b6 Mon Sep 17 00:00:00 2001 From: Aristides Staffieri Date: Fri, 31 Jul 2026 10:22:02 -0600 Subject: [PATCH 1/2] SEP-41: Clarify allowance behavior SEP-41 specifies the allowance functions but not how allowances behave. The question that prompted this -- whether approving a spender a second time replaces the first allowance or adds to it -- is answerable today only from a security caution on developers.stellar.org, not from the SEP or from CAP-46-6. Per review feedback, describe the behavior briefly in the doc comments of the functions it applies to, in the register ERC-20 uses, rather than in a standalone section enumerating state changes: - allowance returns what spender is still allowed to withdraw, or 0 once the allowance has expired. - approve allows repeated withdrawals up to amount, and overwrites the current allowance rather than adding to it. Notes that an allowance is a spending limit rather than a reservation of balance, and that clients, not contracts, should zero and check an allowance before replacing it. - transfer_from and burn_from draw on the allowance and fail if it or the balance is short. Storage layout and auth requirements are deliberately left unspecified: allowances are not described as keyed per (from, spender) pair, values are not described as recorded entries, and no require_auth requirement is added. --- ecosystem/sep-0041.md | 35 +++++++++++++++++++++++++++-------- 1 file changed, 27 insertions(+), 8 deletions(-) diff --git a/ecosystem/sep-0041.md b/ecosystem/sep-0041.md index a7b89e6fb..906343601 100644 --- a/ecosystem/sep-0041.md +++ b/ecosystem/sep-0041.md @@ -6,7 +6,7 @@ Title: Soroban Token Interface Authors: Jonathan Jove <@jonjove>, Siddharth Suresh <@sisuresh>, Simon Chow <@chowbao>, Leigh McCulloch <@leighmcculloch> Status: Draft Created: 2023-09-22 -Updated: 2026-06-08 +Updated: 2026-07-31 Version 0.5.0 Discussion: https://discord.com/channels/897514728459468821/1159937045322547250, https://github.com/stellar/stellar-protocol/discussions/1584 ``` @@ -51,7 +51,8 @@ indistinguishable. ```rust pub trait TokenInterface { - /// Returns the allowance for `spender` to transfer from `from`. + /// Returns the amount which `spender` is still allowed to withdraw from + /// `from`, or 0 if the allowance has expired. /// /// # Arguments /// @@ -59,15 +60,28 @@ pub trait TokenInterface { /// - `spender` - The address spending the tokens held by `from`. fn allowance(env: Env, from: Address, spender: Address) -> i128; - /// Set the allowance by `amount` for `spender` to transfer/burn from - /// `from`. + /// Allows `spender` to withdraw from `from` multiple times, up to a total + /// of `amount`, until `live_until_ledger`. If this function is called again + /// it overwrites the current allowance with `amount` and + /// `live_until_ledger`. + /// + /// NOTE: An allowance is a spending limit, not a reservation. It does not + /// lock any of `from`'s balance and may exceed it. + /// + /// NOTE: Because calling this function again replaces the current allowance + /// rather than adding to it, `spender` may spend the current allowance + /// before the new allowance is set, and so spend more than intended. + /// Clients SHOULD set the allowance to 0 and confirm that none of the + /// current allowance was spent before setting a new amount. Contracts + /// SHOULD NOT require this. /// /// # Arguments /// /// - `from` - The address holding the balance of tokens to be drawn from. /// - `spender` - The address being authorized to spend the tokens held by /// `from`. - /// - `amount` - The tokens to be made available to `spender`. + /// - `amount` - The tokens to be made available to `spender`. Set to 0 to + /// revoke the allowance. /// - `live_until_ledger` - The ledger number where this allowance expires. /// Cannot be less than the current ledger number unless the amount is being /// set to 0. An expired entry (where live_until_ledger < the current @@ -106,8 +120,10 @@ pub trait TokenInterface { /// are separated in the event. fn transfer(env: Env, from: Address, to: MuxedAddress, amount: i128); - /// Transfer `amount` from `from` to `to`, consuming the allowance of - /// `spender`. Authorized by spender (`spender.require_auth()`). + /// Transfer `amount` from `from` to `to`, using `spender`'s allowance to + /// withdraw from `from`. Authorized by spender (`spender.require_auth()`). + /// Reduces the allowance by `amount` without changing when it expires. + /// Fails if the allowance or `from`'s balance is less than `amount`. /// /// # Arguments /// @@ -140,7 +156,10 @@ pub trait TokenInterface { /// - data - `amount: i128` or `{ amount: i128 }` fn burn(env: Env, from: Address, amount: i128); - /// Burn `amount` from `from`, consuming the allowance of `spender`. + /// Burn `amount` from `from`, using `spender`'s allowance to withdraw from + /// `from`. Reduces the allowance by `amount` without changing when it + /// expires. Fails if the allowance or `from`'s balance is less than + /// `amount`. /// /// # Arguments /// From 4ac0756fe6fb4f359d49bccbe6ef098c7a13d41a Mon Sep 17 00:00:00 2001 From: Aristides Staffieri Date: Mon, 3 Aug 2026 10:11:45 -0600 Subject: [PATCH 2/2] SEP-41: Clarify allowance behavior SEP-41 specifies the allowance functions but not how allowances behave. The question that prompted this -- whether approving a spender a second time replaces the first allowance or adds to it -- is answerable today only from a security caution on developers.stellar.org, not from the SEP or from CAP-46-6. Describe the behavior in the doc comments of the functions it applies to, in the register ERC-20 uses, rather than in a standalone section enumerating state changes. Each function keeps a brief summary line for rustdoc, with the added detail in a following paragraph: - allowance returns what spender is still allowed to transfer from from. - approve allows repeated transfers up to amount, and overwrites the current allowance rather than adding to it. Notes that an allowance is a spending limit rather than a reservation of balance, and that clients, not contracts, should zero and check an allowance before replacing it. - transfer_from and burn_from reduce the allowance without changing when it expires, and fail if the allowance or the balance is short. Storage layout and auth requirements are deliberately left unspecified: allowances are not described as keyed per (from, spender) pair, values are not described as recorded entries, and no require_auth requirement is added. --- ecosystem/sep-0041.md | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/ecosystem/sep-0041.md b/ecosystem/sep-0041.md index 906343601..508591278 100644 --- a/ecosystem/sep-0041.md +++ b/ecosystem/sep-0041.md @@ -6,8 +6,8 @@ Title: Soroban Token Interface Authors: Jonathan Jove <@jonjove>, Siddharth Suresh <@sisuresh>, Simon Chow <@chowbao>, Leigh McCulloch <@leighmcculloch> Status: Draft Created: 2023-09-22 -Updated: 2026-07-31 -Version 0.5.0 +Updated: 2026-08-03 +Version 0.5.1 Discussion: https://discord.com/channels/897514728459468821/1159937045322547250, https://github.com/stellar/stellar-protocol/discussions/1584 ``` @@ -51,8 +51,8 @@ indistinguishable. ```rust pub trait TokenInterface { - /// Returns the amount which `spender` is still allowed to withdraw from - /// `from`, or 0 if the allowance has expired. + /// Returns the amount which `spender` is still allowed to transfer from + /// `from`. /// /// # Arguments /// @@ -60,7 +60,9 @@ pub trait TokenInterface { /// - `spender` - The address spending the tokens held by `from`. fn allowance(env: Env, from: Address, spender: Address) -> i128; - /// Allows `spender` to withdraw from `from` multiple times, up to a total + /// Set the allowance of `spender` to `amount`. + /// + /// Allows `spender` to transfer from `from` multiple times, up to a total /// of `amount`, until `live_until_ledger`. If this function is called again /// it overwrites the current allowance with `amount` and /// `live_until_ledger`. @@ -120,8 +122,9 @@ pub trait TokenInterface { /// are separated in the event. fn transfer(env: Env, from: Address, to: MuxedAddress, amount: i128); - /// Transfer `amount` from `from` to `to`, using `spender`'s allowance to - /// withdraw from `from`. Authorized by spender (`spender.require_auth()`). + /// Transfer `amount` from `from` to `to`, consuming the allowance of + /// `spender`. Authorized by spender (`spender.require_auth()`). + /// /// Reduces the allowance by `amount` without changing when it expires. /// Fails if the allowance or `from`'s balance is less than `amount`. /// @@ -156,10 +159,10 @@ pub trait TokenInterface { /// - data - `amount: i128` or `{ amount: i128 }` fn burn(env: Env, from: Address, amount: i128); - /// Burn `amount` from `from`, using `spender`'s allowance to withdraw from - /// `from`. Reduces the allowance by `amount` without changing when it - /// expires. Fails if the allowance or `from`'s balance is less than - /// `amount`. + /// Burn `amount` from `from`, consuming the allowance of `spender`. + /// + /// Reduces the allowance by `amount` without changing when it expires. + /// Fails if the allowance or `from`'s balance is less than `amount`. /// /// # Arguments /// @@ -328,6 +331,10 @@ and a clawback action that emits a clawback event must reduce total supply. no separate burn or transfer event is emitted alongside the clawback event. - `v0.5.0` - Document the single-value/vec and map data formats for all events, generalizing the form already used by `transfer` and `mint`. +- `v0.5.1` - Clarify in the interface doc comments that `approve` overwrites + the current allowance rather than adding to it, that an allowance is a + spending limit and not a reservation of balance, and that `transfer_from` and + `burn_from` reduce the allowance and fail if it or the balance is short. ## Implementations