#Token Interface Proposal

51 messages · Page 1 of 1 (latest)

inland finch
#

I'd like to propose that we split up the token interface into a set of small interfaces that tokens can choose to implement.

pub trait Token {
    fn name(env: Env) -> Bytes;
    fn symbol(env: Env) -> Bytes;
    fn decimals(env: Env) -> u32;
    fn balance(env: Env, id: Address) -> i128;
    fn spendable_balance(env: Env, id: Address) -> i128;
    fn transfer(env: Env, from: Address, to: Address, amount: i128);
}
pub trait Mint {
    fn mint(env: Env, to: Address, amount: i128);
}
pub trait Burn {
    fn burn(env: Env, from: Address, amount: i128);
}
pub trait Allowance {
    fn allowance(env: Env, from: Address, spender: Address) -> i128;
    fn increase_allowance(env: Env, from: Address, spender: Address, amount: i128);
    fn decrease_allowance(env: Env, from: Address, spender: Address, amount: i128);
}
pub trait AllowanceTransfer {
    fn transfer_from(env: Env, spender: Address, from: Address, to: Address, amount: i128);
}
pub trait AllowanceBurn {
    fn burn_from(env: Env, spender: Address, from: Address, amount: i128);
}
pub trait Authorized {
    fn authorized(env: Env, id: Address) -> bool;
    fn set_authorized(env: Env, id: Address, authorize: bool);
}
pub trait Clawback {
    fn clawback(env: Env, from: Address, amount: i128);
}
pub trait Admin {
    fn admin(env: Env) -> Option<Address>;
    fn set_admin(env: Env, new_admin: Address);
}
#

@frail ermine recently pointed out to me how the interface is pretty big, and there's a good chance folks won't want to implement it all. At some point we discussed the idea of contracts just panicking in functions they don't support, such as clawback, but that approach also is a poor way of communicating what functionality a token supports.

#

@white remnant reminded me recently about how in the Ethereum ecosystem some interfaces are piecemeal, and you can implement this EIP and that EIP. A similar approach will probably work pretty well for us. Contracts in their contractmeta! will be able to exactly specify which SEPs they implement, where each of the above interfaces would be a separate SEP.

dawn sentinel
#

this would be awesome, but how can one implement these in their contract?

inland finch
#

We will have a challenge implementing this in the SDK, because the SDK currently doesn't support very well a contract implementing multiple traits. We've really gotta deal with that, but if we do, the above would be pretty smooth.

#

The path I suggest to implementing the above would be:
• Break up the interface per above. (So we can start referring to these separately, and start SEPifying them.)
• Join them back together into a super interface temporarily (to retain existing behavior).
• Get out the 💪🏻 and fix the multi-trait support.
• Remove the super interface.

frail ermine
#

Yeah I think something like this would be great. It'll also keep custom token WASM sizes down.

white remnant
#

Small nit: unsure about trait naming conventions but it feels like they should be nouns or adjectives, no? so Mint->Minter/Mintable, Clawback->Clawbacker/Clawbackable etc

inland finch
#

In Rust traits are commonly named after their function.

#

For example, the clone function everyone implements is contained in the Clone trait. The eq function in the Eq trait. The try_from function in the TryFrom trait.

white remnant
#

Weird. But ok

#

Also, aren't we missing a dependency decleration here? Don't Mint/Burn/etc depend on the existence of an Admin?

inland finch
#

Not necessarily. Dependencies would only constrain the space of usefulness. A contract can still implement Mint, Burn, and Admin. And another contract can still say they are compatible with contracts that Mint, Burn, and Admin.

dawn sentinel
#

auth scheme for these can be defined based on internal storage, so it's possible to mint using different auth patterns

inland finch
#

Also, Admin only defines that a contract has a single Admin, not that there is administrative capabilities.

#

The bigger any interface, the less interoperable it is.

#

transfer_from should probably be in its own interface. A token may only want to allow burning from.

inland finch
#

cc @sullen lily I added ☝🏻 to the board

storm cipher
#

Will each trait be its own SEP?

white remnant
#

@storm cipher I think so

storm cipher
#

I'm on board with breaking up the token interface, but I think the suggested break up is too specific, and the majority do not need a SEP as represented. My concern is the confusion when trying to decide what your app should support for token functions, and what functions a token creator should support? With the super interface, its very clear what I need to implement to be fully interoperable with a SAC token.

I'd suggest combining the Allowance functions into one, Admin functions into one (admin, mint, burn), and the Stellar Asset ones into one (Authorized, Clawback). I'm not entirely convinced the Admin one really needs to be SEPified either, as that feels like more of an implementation detail instead of a standard.

brave pilot
#

I think allowance is key to a lot of central patterns that should be expected by every token and therefore be part of the main interface. Agree on the rest.

storm cipher
dawn sentinel
#

(should be done in preview 10)

inland finch
#

If a token is going to implement allowance, but not one aspect of allowance, it could just panic in that function.

inland finch
#

@storm cipher I think you're suggesting something like this?

pub trait Token {
    fn name(env: Env) -> Bytes;
    fn symbol(env: Env) -> Bytes;
    fn decimals(env: Env) -> u32;
    fn balance(env: Env, id: Address) -> i128;
    fn spendable_balance(env: Env, id: Address) -> i128;
    fn transfer(env: Env, from: Address, to: Address, amount: i128);
    fn authorized(env: Env, id: Address) -> bool;
}
pub trait Allowance {
    fn allowance(env: Env, from: Address, spender: Address) -> i128;
    fn increase_allowance(env: Env, from: Address, spender: Address, amount: i128);
    fn decrease_allowance(env: Env, from: Address, spender: Address, amount: i128);
    fn transfer_from(env: Env, spender: Address, from: Address, to: Address, amount: i128);
    fn burn_from(env: Env, spender: Address, from: Address, amount: i128);
}
pub trait Admin {
    fn admin(env: Env) -> Option<Address>;
    fn set_admin(env: Env, new_admin: Address);
    fn mint(env: Env, to: Address, amount: i128);
    fn burn(env: Env, from: Address, amount: i128);
    fn clawback(env: Env, from: Address, amount: i128);
    fn set_authorized(env: Env, id: Address, authorize: bool);
}
#

@frail ermine recently pointed out to me how the interface is pretty big, and there's a good chance folks won't want to implement it all.
@frail ermine I know you were looking for a way for folks to implement smaller interfaces. Does this ☝🏻 still address your concern? I suspect no.

storm cipher
frail ermine
#

Yeah this still works with what I was thinking. My main concern was that the Admin methods wouldn't be used by other contracts, so it'd be a waste to force every token contract to implement them.

inland finch
#

👏🏻 Okay great, thanks. 🎉

delicate blaze
#

Hello!
I understand that its simpler to have different traits.
But shouldn't at least the Token and Allowance traits be mandatory to comply with the token interface?

I mean. While developing a Dapp we must know if tokens will have allowance functions or not.
Otherwise someone might design a dapp that only works for a group of "allowance-implemented tokens"

inland finch
# brave pilot I think allowance is key to a lot of central patterns that should be expected b...

allowance is key to a lot of central patterns that should be expected by every token

shouldn't at least the Token and Allowance traits be mandatory

A couple compelling points above from @brave pilot and @delicate blaze.

Even if the goal is to get rid of allowances for majority of use cases, as long as they're present or needed in some cases, they are only useful if there's broad interopability by everyone implementing them.

dawn sentinel
#

well, allowance is only central because of lack of alternatives

inland finch
#

I changed the proposed interfaces to:

pub trait Interface {
    fn name(env: Env) -> Bytes;
    fn symbol(env: Env) -> Bytes;
    fn decimals(env: Env) -> u32;
    fn balance(env: Env, id: Address) -> i128;
    fn spendable_balance(env: Env, id: Address) -> i128;
    fn transfer(env: Env, from: Address, to: Address, amount: i128);
    fn transfer_from(env: Env, spender: Address, from: Address, to: Address, amount: i128);
    fn authorized(env: Env, id: Address) -> bool;
    fn allowance(env: Env, from: Address, spender: Address) -> i128;
    fn increase_allowance(env: Env, from: Address, spender: Address, amount: i128);
    fn decrease_allowance(env: Env, from: Address, spender: Address, amount: i128);
    fn burn(env: Env, from: Address, amount: i128);
    fn burn_from(env: Env, spender: Address, from: Address, amount: i128);
}
pub trait AdminInterface {
    fn admin(env: Env) -> Option<Address>;
    fn set_admin(env: Env, new_admin: Address);
    fn mint(env: Env, to: Address, amount: i128);
    fn clawback(env: Env, from: Address, amount: i128);
    fn set_authorized(env: Env, id: Address, authorize: bool);
}
dawn sentinel
#

also for e.g. internal tokens allowance seems redundant

#

hmm, this doesn't have approve change

inland finch
#

The approve change isn't in the SDK's Env yet.

dawn sentinel
#

that's fair, but it should be for p10, right?

inland finch
#

Yup, we can update the SDK after the SDK Env is updated.

#

The PR doesn't prevent us from splitting allowances out next though.

broken rock
#

I like this idea

#

Why are name and symbol bytes instead of strings?

#

i know we talked about that at some point but just wondering

inland finch
#

The SDK hasn't been updated yet to match the environment. The environment is strings.

#

🔜

brave pilot
#

not sure if burn should be in but looks good 🙂

inland veldt
#

Looks good, but why we don't have total_issuance method?