Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ERC1363 #3017

Closed
wants to merge 18 commits into from
Closed

ERC1363 #3017

wants to merge 18 commits into from

Conversation

Amxx
Copy link
Collaborator

@Amxx Amxx commented Dec 12, 2021

Fixes #3736

ERC1363 is marked final

I think we should implement it. It would be an important move toward adoption of these mechanisms that can both improve the UX (only one tx needed instead of 2) and save a lot of gas during contract interaction (using approval added 2 sstore and 2 events: one when approval is set and one when transferFrom is called.)

PR Checklist

  • Tests
  • Documentation
  • Changelog entry

@Amxx
Copy link
Collaborator Author

Amxx commented Dec 13, 2021

AFAIK, we cannot go further with coverage...

@frangio
Copy link
Contributor

frangio commented Dec 27, 2021

There are many EIPs for transferAndCall functionality. Why should we implement this one? I can see that the EIP is Final but it looks like there was zero discussion about it, or I can't find it. Has an implementation been audited and deployed to production?

@Amxx
Copy link
Collaborator Author

Amxx commented Dec 28, 2021

IMO, this "transfer and call" mechanism is really interesting, and some projects could really benefit from it. In particular, it can be used to avoid the approval + transferFrom workflow, which costs 2 sstores and 2 Approval events.

AFAIK, there are not that many other options.

  • ERC223 is just an open issue (no proper merged ERC document) and is not backward compatible with ERC20.
  • ERC667 is just an open issue (no proper merged ERC document).
  • ERC1363 is a properly formatted ERC, and is complete in terms of features (while remaining simple/clear).

An I missing other standardization efforts?

My understanding is that this feature can be used both

  • within a web3app, to have multiple contracts interact
  • across apps, to improve interoperability.

The second point would be great in the long run, but I'd say my main fear is that projects would need the first point and will either not use it, for lack of knowledge of this pattern (making their workflow overly expensive/complex) or use a non-standard version which will work for them in the short run, but make point 2 much harder to achieve.

IMO, we need to push toward more standardization, and one way to do that is by providing a safe & effective implementation.

Note: I've opened this discussion in sept 2020, and the discussion was not that rich :/

@Amxx Amxx added this to the 4.8 milestone Jun 13, 2022
@frangio
Copy link
Contributor

frangio commented Jun 16, 2022

@lukehutch Your comments sound like you're angry about things. I recommend reviewing your approach to the conversation. This is a place for constructive discussion.

@lukehutch
Copy link

@frangio edited, sorry.

@frangio
Copy link
Contributor

frangio commented Jun 16, 2022

I honestly don't see anything in the EIP that strongly indicates transferAndCall should revert when the destination is not a contract. @lukehutch You mentioned that the spec says transferring to EOAs is not allowed (#2620 (comment): "search for MUST implement") but I don't see it. It says "A contract that wants to accept token payments via transferAndCall or transferFromAndCall MUST implement the following interface" but this is only making a statement about contracts, it's not making a statement about EOAs.

What can be read as ambiguous is that the intention is to "execute code" on the receiver, and the wording that transferAndCall should transfer "and then call onTransferReceived on receiver". The ambiguity is that CALL at the EVM level succeeds for EOAs, but a "call" in Solidity fails if the target is not a contract.

I think the EIP is the ultimate source of truth. @vittominacori's implementation isn't linked in the EIP so arguably it's not even a "reference" implementation, and it doesn't claim to be so in the repository (says it's "an implementation").

All that said, I'm slightly leaning towards requiring the receiver to be a contract, because I think the general expectation for a "call" is that it will actually execute code. The fact that CALL silently succeeds when the target is not a contract is generally seen as a bad quirk of the EVM.

@vittominacori
Copy link
Contributor

@frangio my own implementation is not linked because of the reviewer said to remove external link to repo and test cases (also if I've seen many EIP referencing their own implementation).

However maybe I missed explicit reference to EOA address but it was clear for me to refer only contracts by saying that "contracts MUST implement..." and that transferAndCall, transferFromAndCall and approveAndCall method is a call to a method and not the CALL.

Will try to update the EIP as they say "A Final EIP exists in a state of finality and should only be updated to correct errata and add non-normative clarifications." in order to clarify this missing declaration.

@lukehutch
Copy link

lukehutch commented Jun 16, 2022

@frangio Exactly, the EIP does not mention EOAs at all, or even hint at what should be done for EOA recipients or spenders. And the statement that you found is what I was referring to. It is simply ambiguous about the EOA case (by omission), as I pointed out several times in the issue about deprecating ERC777.

Because it is ambiguous, implementations should not all decide differently about what should be done in this case. I think @Amxx did the right thing by asking @vittominacori what his intention was in this case, and he made that intention very clear in the comment that I quoted (and the comment he posted seconds before I posted this one): he believes ERC20 APIs should be use to send to EOAs, since ERC1363 is a proper superset of ERC20 -- therefore, ERC1363 should revert if trying to send to EOAs. And I don't think the reference implementation is irrelevant, since it proves what @vittominacori's intent really was in the spec (even though the implementation is not linked from the spec -- it used to be, but had to be removed at the request of a reviewer).

Note that ERC1363's reference implementation refusing to send to EOAs is different from ERC777 and ERC4524, both of which allow sending to EOAs through their ERC20-superset APIs, by actual definition in the spec. I think there is room for a sender notification API that refuses to send to EOAs.

a "call" in Solidity fails if the target is not a contract

Correct if "call" is passed a non-empty ABI encoding of a function call. I don't think it matters that transferAndCall has a different connotation at the EVM level and the Solidity/Yul level, because almost nobody working in Solidity deals with EVM nuances. The Solidity "call" behavior should be the most relevant here.

@Amxx
Copy link
Collaborator Author

Amxx commented Jun 16, 2022

In solidity, doing

(bool success,) = target.call(anything)

Will be true if target is an EOA. Just like sending à transaction with any data field to an EOA will not revert.

@lukehutch
Copy link

Correct, to prevent sending to EOA you have to do

require(account.code.length > 0, "Can't send to EOA");

(Although that also prevents sending to contracts whose constructors have not finished executing)

@frangio
Copy link
Contributor

frangio commented Jun 16, 2022

I meant external function calls like target.foo() revert if the target is not a contract.

@lukehutch
Copy link

I meant external function calls like target.foo() revert if the target is not a contract.

Not true, if the target implements a fallback function. This is actually a huge potential security problem that affects hundreds of millions of dollars locked up in deployed contracts.

https://media.dedaub.com/phantom-functions-and-the-billion-dollar-no-op-c56f062ae49f

This can even affect ERC777, because there is no return value for the sender and recipient notification interface.

ERC1363 and ERC4524 solve this by requiring their notification hooks to return their own function selector.

@Amxx
Copy link
Collaborator Author

Amxx commented Jun 17, 2022

I meant external function calls like target.foo() revert if the target is not a contract.

Not true, if the target implements a fallback function. This is actually a huge potential security problem that affects hundreds of millions of dollars locked up in deployed contracts.

How can a "not a contract" have a fallback function?


When doing target.foo(), under the hood solidity does something like

require(target.isContract);
(bool success, bytes memory returndata) = target.call(abi.encodeCall(target.foo, ()));
require(success);
return abi.decode(returndata, <foo return type>);

@vittominacori vittominacori mentioned this pull request Jun 17, 2022
3 tasks
@Amxx
Copy link
Collaborator Author

Amxx commented Jul 15, 2022

#3525 (comment)

IMO all standard (ERC) interfaces should be in contract/interfaces.

In particular, this is the case of ERC3156 & ERC4626. "older" interfaces are still in their respective folders, and linked into contract/interfaces.

I believe that in 5.0 we should move all the standard interfaces that are not yet there (ERC20, ERC721, ...) into the interfaces folder

@vittominacori
Copy link
Contributor

Yes, so in order to be similar to any other OZ interface in #3525 I moved them into token folder and then linked in interfaces folder like the ERC20.
I see that here the interface is all written into the interfaces folder
https://github.com/OpenZeppelin/openzeppelin-contracts/blob/08dd234f57f0372260422ac85281a21d70adac50/contracts/interfaces/IERC1363.sol

Also I created different mock files instead of having all into a single file like here https://github.com/OpenZeppelin/openzeppelin-contracts/blob/08dd234f57f0372260422ac85281a21d70adac50/contracts/mocks/ERC1363Mock.sol

@Amxx
Copy link
Collaborator Author

Amxx commented Jul 16, 2022

Again, I think 1363 should be just like 3156 or 4626, with the ERC interface in contracts/interfaces. I don't see why 1363 would be handled differently than 4626.
The erc20/extension folder has enough files already, no need to make things more difficult

The mock fonder is also messy. IMO it's worth putting all the 1363 related mocks in a single file.

@frangio frangio mentioned this pull request Sep 28, 2022
@vittominacori
Copy link
Contributor

Hi guys, is there any work to be done to merge this PR?

@vittominacori vittominacori mentioned this pull request Jan 24, 2023
3 tasks
@radeksvarz
Copy link

Reading the whole discussion here - regarding the adoption, it is like chicken-egg problem. People will start using that once the 1363 is available in the OZ wizard.

I can see there are only foundry-tests blocking the merge now?

@Amxx
Copy link
Collaborator Author

Amxx commented Feb 23, 2023

I can see there are only foundry-tests blocking the merge now?

We only do foundry tests when we want fuzzing. IMO fuzzing is not needed here.

AFAIK, the blocker is a formal decision on what to do when targetting EOA. It seems that different people have different interpretations of the ERC document, and that the original idea of the ERC author might not match the ERC document. In that case I'd personnally follow the ERC document, but that is not resolved.

@radeksvarz
Copy link

Ah, in that case - since there is an ambiguity in understanding EOA detail in the ERC and as I understood OZ shall not be opinionated I propose there were both implementations. OZ can leave it up to the using developer to decide on a behavior towards EOAs.

@Amxx
Copy link
Collaborator Author

Amxx commented Feb 23, 2023

I propose there were both implementations

That is the best way to make sure people are confused, don't know which one to use (possibly don't understand the difference) ... In the end you'll just have no standard behavior, and no reliability on what to expect from a contract.

Either we come to an agreement on what to provide or we don't provide anything (so far its been the later)

@vittominacori
Copy link
Contributor

I really don't think the behavior of the function with EOA receivers is such an important issue.

The important bit in this EIP is the addition of a callback for contracts. I discussed with @Amxx earlier today and he's now come to this as well.

I personally kind of appreciate the simplicity of being sure that the callback always runs.

In summary: we will merge an implementation of EIP-1363 that reverts on transferAndCall to an EOA receiver.

I don't want to open the discussion again but above we agreed to revert to EOA receiver.
I just want to remember that the EIP says that

A contract that wants to accept token payments via transferAndCall or transferFromAndCall MUST implement the following interface...

Yes, it doesn't say nothing about EOA addresses but the original purpose was to give a way to use transfer with a receiver that can make a callback.

Why should someone want to call a method on a receiver and expecting it is done but it doesn't as it is an EOA?
To transfer to EOA you can use the standard ERC20 transfer as ERC1363 extends it.

The *AndCall methods expect to have the call executed. My vision as EIP author.

Here my implementation https://github.com/vittominacori/erc1363-payable-token
I've PR this over OZ but it was now obsolete. I could PR again using the latest OZ version.
Just check if it should go into ERC20/extensions or in other position.

@changeset-bot

This comment was marked as duplicate.

@vittominacori
Copy link
Contributor

Yes I'm ok that the EIP could be confusing but for me (as author) the following concept is clear.

If I call the *AndCall on an EOA it is impossible that the magic value is returned so I don't want that tokens go to the EOA without doing nothing else. Tokens can be lost and the desired action will never be executed.

If I use *AndCall (and we should force that behavior via throwing for EOA) the receiver MUST do the callback.
Otherwise we don't need about this and we can simply use transfer.

@changeset-bot

This comment was marked as duplicate.

@Amxx
Copy link
Collaborator Author

Amxx commented Feb 23, 2023

If I use *AndCall (and we should force that behavior via throwing for EOA) the receiver MUST do the callback.
Otherwise we don't need about this and we can simply use transfer.

I'm seeing "optimistic" use cases were someone wants to send and give a change for the receiver to react. That is eactly the safeTransfer usecase from ERC-721.

  • If its an EOA: we'll just let him handle the asset ... there is no way for it to react anyway.
  • If it is not an EOA: we'll expect him to confirm that the assets will be handled.

This usecase can be achieved using your approach by doing

if (target.code.length == 0) {
    token.transfer(target, amount);
} else {
    token.transferAndCall(target, amount);
}

The thing is that with your approach token.transferAndCall will also check that target is a contract, so we are inefficient.


On the other hand if we imagine that transferAndCall does not revert, and if you want to implement transferAndCall in a way that reverts, you can do

require(target.code.length > 0, "some error");
token.transferAndCall(target, amount);

with basically no overhead.


IMO not reverting on EOA is more efficient (because it remove check that people are free to do, or not do, before calling). It also matches ERC721.safeTransfer, which I'd argue is a very strong usecase for ERC1363transferAndCall (even if the naming is not the same). I know you disagree on that.

We "often" see devs looking for a ERC721.safeTransfer equivalent for ERC20. I'd like to point them at 1363 and not have them build a new standard. The thing is if 1363 doesn't meet this usecase, people will continue to propose new ERC, and we would be in a situation where tokens would ideally implement both 1363 & this new "safeTransfer" ERC ...

I believe a version of 1363 that doesn't revert on EOA (which IMO is what the document says, despite the author's intentions) is the best shot we have to addressing a long standing issue in the community ... when a version of 1363 that does revert on EOA would not be working for some (IMO important) usecases and would lead to this long standing issue not being fixed.

@changeset-bot

This comment was marked as duplicate.

@changeset-bot

This comment was marked as duplicate.

@changeset-bot

This comment was marked as duplicate.

@bcxbb
Copy link

bcxbb commented Jun 28, 2023

What is the status of this ERC? Anything I can do to contribute to finalizing it?

@vittominacori
Copy link
Contributor

Once v5 will be ready, I will be happy to close this PR and open a fresh new one with the new guidelines.

@OpenZeppelin OpenZeppelin deleted a comment from freefilm010 Jul 18, 2023
@vittominacori vittominacori mentioned this pull request Sep 27, 2023
3 tasks
@Amxx
Copy link
Collaborator Author

Amxx commented Jan 2, 2024

Replaced by #4631

@Amxx Amxx closed this Jan 2, 2024
@Amxx Amxx deleted the feature/ERC1363 branch April 3, 2024 23:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

EIP-1363 support
6 participants