#Token interface changes

139 messages · Page 1 of 1 (latest)

sturdy bone
#

I'm looking for some feedback/new ideas on the token interface improvements enabled by Auth Next. I've described my own ideas here: https://github.com/stellar/rs-soroban-env/issues/653
the key points:

  • xfer_max (name TBD) function that allows users to combine allowance and xfer into 1 call (so that the user signs the max amount of token a contract may spend on their behalf and the contract can use it for any transfer within the limits). useful for contracts with dynamic pricing, like DEXes/AMMs etc.
  • make allowances ephemeral, i.e. never persisted and only valid for the operation lifetime. this makes allowances much safer but doesn't allow decoupling allowance transaction from the actual execution (which hopefully shouldn't be needed with auth next)

I think the main question is whether we still need persisted allowances with these changes, but I'm also looking for any other feedback.

civic relic
#

doesn't allow decoupling allowance transaction from the actual execution (which hopefully shouldn't be needed with auth next)
this is an interesting point - I wonder if there's an intentional use case for increasing allowances but not executing transfers

minor ravine
#

Don't you need persisted allowances for use cases like subscriptions?

#

Or are you advocating against that model because of the security issues around large persisted allowances?

last moat
sturdy bone
#

the thing about subscriptions is that ideally the policy should be defined on the user side, as there are no safety requirements from the contract standpoint (if the transfer fails for whatever reason the subscription just won't continue). so what could probably be done is some sort of 'subscription account' contract, which is one of the cases for 'wrapper' accounts that don't implement authentication but only authorization policy (I need to tinker more with these). it could benefit from the persistent allowances as well though...

hidden oxide
#

Seems like xfer_max could be implemented using xfer, if the contract gives them back the "change". i.e.

  • contract calls xfer to withdraw the max from the user
  • contract does stuff (mints out tokens, does the swap, whatever)
  • contract xfers the remainder back to the user
    But streamlining that flow with xfer_max might be nice, especially for "router" type contracts, which are not directly dealing with deposits.
sturdy bone
#

sure, that's 2 contract calls, same as allowance + xfer_from

#

the point of xfer_max is to have only a single call

hidden oxide
#

How would it be 2 contract calls?

sturdy bone
#

xfer from user to contract, xfer from contract to user

#

or increase allowance, xfer_from

hidden oxide
#

doesn't auth-next as-is let us avoid xfer-from for exact-amount transfers tho?

sturdy bone
#

both are for non-exact amounts

hidden oxide
#

Yes.

#

so is mine

sturdy bone
#

so both are 2 token calls

#

vs one

hidden oxide
#

not afaict

sturdy bone
#
  • contract calls xfer to withdraw the max from the user - call 1
#
  • contract xfers the remainder back to the user - call 2
hidden oxide
#

Oh, sure. But only one txn

sturdy bone
#

right, so xfer_max allows to do just a single call

hidden oxide
#

Sure. Does that matter?

sturdy bone
#

the allowance + xfer_from option is similarly one txn

hidden oxide
#

I assume it's a cost saving, but like, how much?

sturdy bone
#

it's more of an idiomatic thing

#

also makes the intentions more clear (there is nothing in xfer that would make you think of refunds)

hidden oxide
#

I agree it could be cleaner. The tradeoff is more stuff for devs to learn and maybe more scope for bugs? not sure about that.

sturdy bone
#

it also should probably cover most of the cases

#

maybe the only version of xfer should be xfer_max

hidden oxide
#

Could help wallets show more useful output: You're about to transfer up to x tokens...

sturdy bone
#

that's a bit confusing for simple payments though

#

FWIW both 2-call options seem less obvious to me

#

at least from the experience of the auth next discussions

#

the question of 'how do I withdraw non-exact amount' aroused multiple times and this seems to be the most straightforward solution because it's codified in the token interface directly (as opposite to some implementation pattern)

hidden oxide
#

Yep, agree that's nice. Maybe there's a better name for xfer_max. 🤔 As that could be read as: xfer the max I am allowed to. but idk. Or maybe there is a way to make that the default behaviour of xfer, but not have it be confusing for payments... (just thinking out loud)

sturdy bone
#

that's why I wrote 'name TBD' 🙂

sturdy bone
#

I wish we had default values, so that xfer had a non-optional component with max amount and allowance owner and an optional component with the actual amount and the actual recipient. but that's only achievable with a struct now, which is not pretty for the token interface

hidden oxide
#

Wouldn't the optional arg be the isMax type thing, like?

pub fn xfer(max: i128, allowLesser: Option<true>)
sturdy bone
#

yeah, but you still need to explicitly pass None, don't you?

hidden oxide
#

Yeah you would.

#

I just mean the optional is whether it is the max-vs-exact

#

tbh maybe having the xfer and xfer_from always take a max (instead of an exact amount) is the right choice... Not sure.

#

But subscriptions are the complicated thing that needs more thought anyway. so.. good luck 😹

sturdy bone
#

FWIW the xfer_max (probably xfer_from_contract better specifies the intent) would have a signature like (from: Address, xfer_target: Address, max_amount: i128, recipient: Address, actual_amount: i128) where we call from.require_auth_for_args(xfer_target, max_amount) and xfer_target.require_auth_for_args(recipient, actual_amount) in case when xfer_target != recipient, which is a bit involved... we could drop the recipient argument, but that would make the function less flexible

#

subscriptions are different... I have an idea for a safe pattern for subscriptions (need to make sure it works as expected though), but it is pretty involved as well

quasi ferry
#

for subscriptions could we have another function xfer_max_time_bound (terrible name) where a contract can only spend a up to a certain amount within a certain time window? The aggregated amount is reset after the time window but authorization still holds.

sturdy bone
#

this seems overly specific to me

#

time-bound allowance would be a bit more natural, but it's still imperfect because it seems more natural that it means 'spend up to X until time T', not 'spend up to X within a time window, then reset'. implementing a sliding time window in a generic fashion is tricky as there might be different requirements

#

FWIW the safe mechanism I've been referring to is to use account abstraction. basically we would have a 'subscription account' contract that doesn't perform any authentication but only implements the customizable authorization policy. e.g. you can say 'allow contract X spend up to N units of token Y within time window T' and this can be arbitrarily customizable according to the requirements. for the subscription account itself there are two options: manual funding or unlimited allowance from the 'source' account and I think the second approach is more convenient to use, which is why the persisted allowances are justifiable (given that you should be the owner of the account contract, unlike in case of creating allowance to an unknown contract)

#

this keeps the token interface simple but allows arbitrarily complex and customizable rules for allowances

last moat
#

simple 😆

sturdy bone
#

well, token interface itself only needs to provide allowance support, which I consider simple

#

the subscription account might sound complex on paper, but it's probably not super complicated as well; I can try to implement one in soroban examples. the idea is that you'd just need to implement something like this once and provide the sufficient client harness

#

the cool thing is that it will be usable with any contract without doing anything special - just do xfer from the subscription account

last moat
#

it is simple honestly, but it takes time to conceptualize and learn for each person. Especially having worked with so many different token models, it feels familiar to me though

#

i think implementing it in one of the examples would be great.

#

i need to look at how freighter just announced soroban token support i wanna see what they're doin

#

i'm still struggling to understand how a soroban contract interacts with the trustline, i am assuming you can't create a trustline from a contract?

sturdy bone
#

no, you can't

last moat
#

i guess if the token is not exported it doesn't matter

sturdy bone
#

it's not impossible to do, but also not something critical

last moat
#

it is simple in theory but then just in my trying to better understand the host environment, and how it's basically a contract that is at the host level

#

makes sense.

sturdy bone
#

the tricky thing is that creating a trustline should require authorizaton from the account owner (as that's not free) and we don't quite support auth next with host fns

quasi ferry
#

Yes using account abstraction to decouple the logic from the token contract makes total sense. I am still not use to how powerful account abstraction is.

sturdy bone
#

yeah, it's powerful and I'm sure there are a lot of uses that we've yet to come up with... the downside is that some new tools need to be built so it's not as quick to set up compared to jamming a bunch of functions into token interface. but OTOH when this is built the things become both safe and simple for everyone

last moat
#

if i could build my own sdk from scratch then i'd understand everything on the level i want to 😆
Also I think you are taking the best approach possible with the token interface by finding the right balance of not having tons of functions that have to be implemented, but still enabling the widest possible set of use cases.

sturdy bone
#

so I've been thinking about this and having two kinds of allowances with different lifetimes is annoying. so here is a crazy idea: what if we only allow 'ephemeral' allowances as in the initial proposal, but also add a function like add_balance_owner that effectively gives an unlimited persistent allowance to the provided party. now, isn't that completely opposite to my sentiment on danger of unlimited allowances? well, it kind of is scary, BUT you'd only use it to 'merge' balances of multiple accounts and it has very distinct name/semantics from the regular allowance operations. this allows easily using multiple 'special-purpose' accounts for things like subscriptions without the need to fund them manually (so you basically can still have your classic stellar account as the main custodial account and kind of add 'plugins' to it). does that sound too crazy, any thoughts?

last moat
#

as opposed to an admin? sorry slightly confused what the add_owner is used for, maybe it'll click after i read your message a few more times lol

sturdy bone
#

add_balance_owner

#

or something like that

#

basically allow multiple accounts to effectively own the same balance

last moat
#

hmm that could be dangerous but it depends how it is implemented i guess

sturdy bone
#

account abstraction allows building cool things from the architectural standpoint, but if every account contract is custodial and maintains its own set of balances, managing multiple accounts becomes a problem (that's my hunch, maybe it doesn't?)

last moat
#

so one account managing the balance of other accounts, but we already have that...

#

i think the use case isn't fully connecting in my head yet sorry

sturdy bone
#

well, it's about as dangerous as adding a signer on your multisig account - very dangerous, but also pretty transparent

last moat
#

it would all depend who the owners are of course yes. adding a malicious signer to your multisig would suck; same scenario here, the underlying functionality could still prove useful

sturdy bone
#

yeah...

#

as for the use cases, let's come back to e.g. subscriptions case

#

let's say you have your classic stellar account with some balances and we implement subscriptions via a separate account contract. that comes with a lot of flexibility in terms of what are the possible subscription rules, but since this is a separate account, it needs to have a balance as well

#

so you either need to manually fund your subscription account

#

or like in this proposal you simply make it an owner of the relevant balances of your classic stellar account

last moat
#

ah that is more clear your analogy of adding a signer is apt also.
It sounds useful; but is there a reason that can't be accomplished by authorizing the second account with an allowance like we have now? I don't think i'd want to loose the ability to put a limit on the allowance

sturdy bone
#

the reason is that I don't want this to be generally useful outside of sharing the account balances and want to rely on temp allowances otherwise. this operation could have a limit as well, but that makes things weird semantically (the account itself can provide richer policies than a simple allowance). the key thing here is that you are the owner of the account contract, which is pretty different from giving allowance to an unknown contract

#

it's possible to implement everything via a custom account contract that implements a 'meta'-multisig where the account is owned by the several addresses... but the downside is that it has to be used as a primary account, while the shared ownership allows to use account contracts as 'plugins' (which is relevant given how many existing stellar accounts we already have)

last moat
#

yeah i think having a limit might prove useful on it, at least as an <option... i think this could be useful in some situuations that would otherwise require like a factory or deployer contract to give the user admin

#

i'll be working on a token interface implementation soon for a project i'm working on, and will be using allowance for sure so i'll suurely have more useful thoughts once i'm hands on

sturdy bone
#

just curious, how are you going to use the allowance?

last moat
#

well in one case to allow for a user to let a contract transfer a token like a custom account contract. I also want to see if i can use it to authorize minting of NFTs, but it might be easier to use a balance for that

sturdy bone
#

let me be more specific: do you need to persist the allowance?

last moat
#

i think so.

#

not forever,

#

but beyond one ledger

#

if the capability exists in my mind it would be easier, but since i'm still designing the system; that might change.

sturdy bone
#

but why exactly do you need to persist it?

last moat
#

the way i was thinking was to basically to make something like a claimable balance with a contract. i guess it's not entirely necessary and perhaps there is a better way to do it...

#

maybe using a balance can do the same thing

#

i too have been hard pressed to find a scenario where an allowance NEEDS to persist and it can't be achieved any other way

sturdy bone
#

yeah, you should be able to get away without it most of the time...

minor ravine
#

There's nothing stopping an app from using this against the way you want it to be used.

sturdy bone
#

as I've mentioned, names and use cases matter. this is conceptually closer to multisig accounts than classic allowances

minor ravine
#

But yeah I get your point

sturdy bone
#

sure... the same goes for adding signers to your account

#

basically if there is a special function, it's much easier to special-case it at the wallet level

#

that was just an idea; but I actually think that the main problem with it is that it has to be built into token and not the fact that this provides unlimited allowance

#

I'll think more about this over the weekend. in general, I'm thinking how can we make the classic accounts benefit from the account abstraction - currently I suspect there won't be much value in using custom accounts due to the fact that an existing user would need to migrate to it... so something like 'account extension' could be useful to have

minor ravine
#

Ah so you're trying to solve this subscription edge case generically using account abstraction? I thought you were just updating the token.

sturdy bone
#

well, it's all tied together

#

basically account abstraction may universally solve a lot of cases like subscriptions without the need for persistent allowances, but the prerequisite to it is to have an account funded, which might be problematic

#

in the end we can give up and leave persisted allowances as is, but we might be able to come up with something better...

#

(for the sake of non-token related cases as well)

quasi ferry
#

this is conceptually closer to multisig accounts than classic allowances
I agree with you it is very close to multisg. However adding a signer is an operation so in my mind it is easier to audit and surface it to the user. We have a very limited number of operations and it is easy to see what a tx by just putting it in the laboratory.
If I understand this correctly we are talking about a function that is called by a smart contract. It could be easy for an attacker to "hide" this call amongst lots of other calls. I am afraid it will be hard to guarantee that the information ("this contract will allow this account to be the owner of your account") will be presented to the user whatever the scenario is.

sturdy bone
#

It could be easy for an attacker to "hide" this call amongst lots of other calls.
this shouldn't be possible, as long as wallet can understand soroban, that is. the same applies to xfers/approves etc. but I'm thinking about generalizing this and we could even make this a top-level operation for clarity

heady vector
#

I only skimmed the above convo so apologies if I'm repeating something -

What is the use case for 'ephemeral' allowances when a fn like xfer_max exists? If I'm forced to sign an approval anyway, why not just sign the xfer_max fn?

It seems like the current allowances implementation is nice for any form of "i trust you enough not to sign everytime I want tokens transferred" form of use case (like subscriptions), it gives enough control to the account to revoke this privilege through the token when necessary.

sturdy bone
#

If I'm forced to sign an approval anyway, why not just sign the xfer_max fn?
xfer_max is less flexible and won't cover the cases where e.g. we need to dynamically distribute the token between multiple parties. completely removing the approval mechaism from the token seems a bit too limiting.

It seems like the current allowances implementation is nice for any form of "i trust you enough not to sign everytime I want tokens transferred"
the thing is that this shouldn't be needed most of the time and ephemeral allowance or xfer_max would be enough. for cases like subscriptions I'd much rather prefer the account-based implementation that enforces time and value bounds without making the token interface more complicated (see extensible accounts thread around here). unlimited persistent allowance just begs for trouble and limited persistent allowance is likely to be mis-used to do things that can be done more efficiently via xfer_max/ephemeral allowance (if the erc-20 patterns are just copied over).

#

I feel like we'll still end up preserving the persistent allowances anyway, but I'd at least want to rename them like increase_persistent_allowance or something (yay to 32-byte function name limit!)

heady vector
#

I don't think the approval mechanism should be removed, rather, that xfer_maxis a good enough substitute for most applications of the ephemeral implementation.

I haven't read the extensible accounts thread yet - I'll give that a look!

sturdy bone
#

well, I'd want to only leave the ephemeral approvals there, which is what this discussion mostly is about. I realize that this could be over-engineering, but OTOH I'm reasonably certain that this should be generally safer - the question is 'how much', I suppose

quasi ferry
#

Visa proposal for subscription use case

sturdy bone
#

so that's basically what I've been proposing, right?

#

even including the account delegation...

lone ermine
# sturdy bone the thing about subscriptions is that ideally the policy should be defined on th...

There was a "no link required" facebook vector attack on Metamask. A javascript library did a bunch of offchain signed approvals for NFT on OpenSea, then published them as a single set later on. There are a lot of problems with how that attack was able to succeed, but any mitigation to prevent a similar attack is important. The idea of a "pending authorization" is always problematic because we are creating a trust system, which is usually not understood by the user. My goal here would be to minimize the amount of potential confusion and to keep xfer_max separate from xfer, in every way.

sturdy bone
#

xfer_max never got in

#

and not sure about any possible attack vectors with it in particular; it just allowed spending less than what was signed

#

I agree that any pending authorizations are more risky in general. but I guess that's a tradeoff between ux/features and security

#

my point was that it's better to control the approvals/subscriptions and stuff like that on the user side, because as long as they trust their custom account/wallet implementation, they can get the same guarantees while using any contract (i.e. if I sign a $10/month subscription no matter where, the most I'm losing in the worst case is $10/month)

lone ermine
# sturdy bone FWIW the safe mechanism I've been referring to is to use account abstraction. ba...

I would prefer a visible budget, with visible spend. A sub account style system similar to how people are recommended to set up their banking. Why expose the life savings to make a netflix payment? Subscription models are a really good idea that haven't been implemented much in practice because of poor UI/UX for the most part. The ideal situation would be IoT + Telemetry + Infrastructure payments and billing + subscription. This should be a "secure and comfortable" way to pay your electricity bill in advance, maybe receiving a credit by charging and discharging batteries into the grid (California law includes this feature)

sturdy bone
#

Why expose the life savings to make a netflix payment
yeah, that's perfectly fair and I've actually proposed a couple ways of making this more accessible with something like account delegation

#

I guess the main issue here is that 'this is more work'

lone ermine
# sturdy bone I feel like we'll still end up preserving the persistent allowances anyway, but ...

A problem with ephemeral allowances is some sort of "off chain signature" that isn't published into the chain immediately. It isn't a common thing, but it is a thing. Attack vectors and batch processing are two reasons why time limits get complicated (one positive benefit, one negative). If there is ever a layer2 ecosystem, a time limit set is leveraged by the layer 2 and also abused by it. (The trust model on Optomism is scary.)

sturdy bone
#

FWIW the lifetime for ephemeral allowances is the transaction execution time, so there are no time bounds there. but they got ditched anyway

lone ermine
#

To be fair, extensible accounts could be a wallet feature. The main thing that I think needs on chain support is a few function calls between contracts as events and notifications.