ERC-6909-multi-token-standard

,

One thing that I like about 1155 that isn’t here is the bytes calldata _data as additional arbitrary data in an unspecified format in the transfer functions. Personally, I would like to see them here as well, as they could be useful for extensions to the standard or for a specific token implementation.

I can’t talk for @jtriley-eth but AFAIK the data was used to call the receiver, this is no longer required and you could still make a function that accepts data

As @RenanSouza2 mentioned, arbitrary data is useful for callbacks, but the core ERC doesn’t include callbacks, so there wouldn’t be much use for it that I’m aware of.

Though a callback extension may be useful in the future.

1 Like

Just catching up with this ERC and I have a few questions.

  1. What’s the reason for the boolean return values?

  2. One minor issue with ERC-20 is that the Approval event is not sufficient to compute allowances because transferFrom decreases allowance without any identifying events (Transfer doesn’t log the spender). This ERC has the exact same behavior. OpenZeppelin Contracts used to emit Approval during transferFrom to mitigate this, but we are removing it as it’s non-standard and the additional event adds significant gas cost. It would be nice to think this through for this ERC and have some kind of answer. Some thoughts:

    • What is even the point of the Approval event if it can’t be used reliably? The only answer I can come up with is that it allows implementing things like https://revoke.cash, a very important application so it makes sense to keep some event for approve. That said, in practice you only use it to check that an approval happened at some point, and whether there is any allowance remaining needs to be recovered via the allowance getter. So perhaps the Approval event should not contain a parameter for amount, given that it’s not reliable?
    • An alternative is to include the spender/operator in the transfer events, which then allows to compute allowances based on the events. ERC-1155 TransferSingle and TransferBatch include an operator parameter. Adding a parameter it would have to be non-indexed, and it would only cost 250 additional gas.
  3. Why isn’t the spec stricter about not decrementing allowance when the spender is an operator?

  4. Why isn’t the spec stricter about emitting Transfer for mints and burns?

  5. The motivation for the ERC-1155 callbacks and the “safe” nomenclature come from the fact that they protect against lost tokens by checking that the receiver is able to handle them. Since this protection is removed, now wallets are responsible for preventing that kind of error. I think the ERC should document the risk and recommend possible mitigations (e.g., wallets should consider rejecting transfers where the receiver is set to the token itself).

Question regarding the standard, why require the transfer, transferFrom, approve and setOperator functions to have a constant return boolean of true? Explicitly returning a value even a constant costs gas.

The only reason I could potentially see is backwards compatibility, however the functions have different inputs and therefore selectors anyway so they wouldn’t really be backwards compatible anyway?

Looks really clean! Some thoughts:

  1. How about using mediaURI instead of image for the metadata? So it doesn’t suggest it is limited to images.
  2. While I agree the word “safe” is misleading, the callback is big deal for advanced implementations. Can you reconsider adding a way to notify the destination contract that a token is being transferred to it? Maybe something along “transferFromAndNotify”?
  3. A small size optimization on reference implementation: transfer can simply call transferFrom instead of repeating the logic. Moreover, do we even need an transfer method?

Why Boolean Return Values

When a contract is called, if no data is returned, Solidity and Vyper perform extcode checks to assert the called address is a contract account and not an externally owned account. Reading a boolean from returndata is cheaper than external code checks.

Approval Issue

The Approval event is still useful for a few reasons.

  1. It indicates an approval at some point in an account’s history, which is useful
  2. As a convention, events to log state mutations is good practice.
  3. The information can still be gathered in a few different ways.

Getting a spender+owner allowance using strictly events is still possible with one additional event filter (Transfer with matching spender and owner). But the more efficient way to aggregate allowances would be to call the ‘allowance’ getter once per account on the first approval event, negating the need to aggregate events, regardless of whether all delegated token movements log a new Allowance event.

Strictness on Operator Allowances

On operator allowances, the convention ‘should’ follow the specification’s recommendation, but there may be edge cases where an implementor may want more flexibility in allowance updates for operators.

Strictness on Mint and Burn Events

Since mint and burn functions are not included in the specification, it seems out of scope to definitively constrain the behavior of the functions. The ‘should’ notation indicates that this is convention and would be good practice, but overall the ERC aims to remove all constraints that aren’t strictly necessary.

Safe Transfer Notes

It seems redundant to document this behavior, as any transfer to an unintended address would almost always result in loss of funds, regardless of the kind of account receiving the token.

Thank you!

Media URI

This makes sense, would this cause more issues with integrating the metadata into client libraries? I would assume if all other token metadata standards contain the ‘image’ tag it would be more of a hurdle for integrations.

Callbacks

Callbacks in theory seem useful, however I’m not aware of ERC-1155 callback usage in practice. Protocols with callback systems seem to implement their own callback system beyond the standard anyway. I think it would be best to leave callback systems unconstrained and allow implementors to decide on their callback architecture.

Transfer vs TransferFrom

The reference implementation maximizes for readability over size or runtime gas optimization. On removing transfer altogether, I would personally prefer keeping both to remove unnecessary checks and branches on simple transfers, though I’m curious to hear other opinions on this one.

Should contracts that interact with ERC-6909 tokens validate that function calls return true or should the return value be ignored?

I agree with this, what I’m pointing out is that not all state mutations are logged in the current spec.

I don’t think this is true. The spender is not logged in the Transfer event. Would you be open to adding it?

This doesn’t get you the current up to date allowance, only the initial one, am I understanding correctly?

  1. Callers to ERC6909 should validate return values in my opinion, but that’s beyond the scope of the specification.
  2. All state mutations are logged, as the allowance delta is implied by the transferFrom call.
  3. You’re right, it’s not logged in the Transfer event, my mistake, though I’m hesitant to break the Transfer event convention for this case. Most indexing software can account for getting the caller of the transferFrom method.
  4. No, what I mean is you can use Approval to see spender/owner pairs that have had a non-zero approval in the past, but an allowance call will return the up-to-date allowance for the spender and owner. This can also be batched efficiently in a few different ways.

So it would be a valid use of an ERC-6909 token to make a transfer and ignore the return value. I guess this makes sense given that the spec says implementations “MUST return true”, any other behavior is non-compliant so anything could happen. I think the most likely outcome is that users will not validate the return values.

As far as I know this requires traces, so it definitely places higher requirements on indexers. I’m only familiar with Dune and Allium, and the caller is not available in their logs tables.

This ERC is already deviating from existing Transfer events, so I don’t think there’s a strong convention that would be broken by adding the spender. Compared with ERC-20, it adds an id. Compared with ERC-721, it adds an amount. Compared with ERC-1155, it removes the operator/spender.

My vote would be for adding the operator in the event. If not, I would remove the amount from the Approval event, to make it explicit that it should not be relied upon, and that checking allowance is necessary.

2 Likes

You are creating a new standard which is not backwards compatible, why carry the limitations of previous ones?

I’m sure I’m not the only one who wishes ERC20 had callbacks, it would make several use cases much simpler. It is the same case for NFTs, having the option to notify a destination contract about an NFT being sent prevents you from having 2 do to calls, 1 to approve the destination contract to manage your NFT, and another for the destination contract to pull it.

After thinking on this and discussing this with a few early implementors, I think a ‘caller’ or ‘operator’ as the first indexed argument makes sense. The updated event will have the following schema.

- name: Transfer
  type: event

  inputs:
    - name: caller
      indexed: true
      type: address
    - name: sender
      indexed: true
      type: address
    - name: receiver
      indexed: true
      type: address
    - name: id
      indexed: false
      type: uint256
    - name: amount
      indexed: false
      type: uint256

In Solidity:

event Transfer(
    address indexed caller,
    address indexed sender,
    address indexed receiver,
    uint256 id,
    uint256 amount
);
4 Likes

That’s great!

One point I might disagree with is making the caller indexed and not the token id. In practice I’m not sure indexed arguments make a big difference, but if I had to choose an access pattern to prioritize it would be querying by token id. What do you think?

2 Likes

If Solmate is already shipping an implementation, this ERC should start moving towards finalization.

I like this ERC because it’s minimal, but I wish it also provided a good answer to the UX and security issues with approvals and operators. By UX I’m referring to having to send 2 transactions in order to use a token with an app, and by security I’m referring to the issues that arise from using infinite approvals or operators in order to mitigate this UX annoyance.

Lately I’ve come to think that temporaryApproveAndCall might be the best pattern to solve that issue: a function to set an allowance, make a call to a target contract with specified data (wrapped in a handler function like onTemporaryApprove), and reset the allowance afterwards. In a world with transient storage, this can also be very efficient. For ERC-6909 in particular I’m not sure how this pattern should be combined with multiple token ids, perhaps there would be a variant for temporary allowance of a single id, and a variant for a temporary operator for all ids.

Curious to hear thoughts about the issues and this proposed solution in particular.

My concern is that with ERC20 we’ve seen many many attempts at standardizing a solution to this problem and none has gotten wide adoption[^1] because they were separate standards and came too late. I would hate to see something similar happen around ERC-6909 when there is the opportunity to address it from the start. The subsequent popular token standards ERC721 and ERC1155 both had a solution in the form of “safeTransfer”, which I think shows that there is demand for a solution.

[^1]: Except perhaps permit, but this is not even a full solution to the UX problem because it still requires two wallet interactions (signature + transaction).

I agree, we should finalize sooner rather than later.


On transfer delegation, I’m not sure a minimalist multi-token standard is the right place for experimentation on this front. There is demand for alternate solutions, I do agree with this, but there doesn’t appear to be a clear winner in terms of how to implement such a scheme.

Operators cover the delegate-everything case, while approvals cover the granular conservative case; each without external interaction or complex operations.

Callback systems are great on the protocol-level where protocols are able to create their own interfaces and functionality more tailored to their use case. Signature permit systems are also useful and vary from protocol to protocol, but I think these are things that should be constrained by protocols, not the core standard.

If the only goal of this ERC is to be a minimalist multi-token standard, I agree.

I guess my point was that I’d like to see this ERC pursue the more ambitious goal to supercede all 3 currently popular token standards and fix their flaws. I think this ERC could be that but is missing a solution to UX and security.

I bring it up in this discussion, instead of leaving it as a protocol-level decision, to consider applications that have to work with arbitrary tokens. For example, I’d like an ERC-6909 exchange to be able to rely on the existence of this solution (be it temporaryApproveAndCall or something else). If it’s not standardized and not widely deployed (as it would be if it was part of the token ERC), then the applications won’t use it.

In any case I will experiment with some designs, and perhaps that can become a separate ERC. I do think it makes sense to decouple this solution from the core of the token, but it will definitely not be adopted as widely.

Looks great, looking forward to it being finalized

Why is amount indexed in the transfer event?

Very good. I hope many Marketplace support this ERC token