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

Update ERC-7417: Move to Review #658

Merged
merged 32 commits into from
Oct 29, 2024
Merged

Conversation

Dexaran
Copy link
Contributor

@Dexaran Dexaran commented Sep 27, 2024

When opening a pull request to submit a new EIP, please use the suggested template: https://github.com/ethereum/EIPs/blob/master/eip-template.md

We have a GitHub bot that automatically merges some PRs. It will merge yours immediately if certain criteria are met:

  • The PR edits only existing draft PRs.
  • The build passes.
  • Your GitHub username or email address is listed in the 'author' header of all affected PRs, inside .
  • If matching on email address, the email address is the one publicly listed on your GitHub profile.

@eip-review-bot
Copy link
Collaborator

eip-review-bot commented Sep 27, 2024

✅ All reviewers have approved.

@eip-review-bot eip-review-bot changed the title Move 7417 to Review Update ERC-7417: Move to Review Sep 27, 2024
Copy link
Collaborator

@SamWilsn SamWilsn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A lot of the content in your Rationale section is justifying the proposal as a whole. That kind of content belongs in the Motivation section. The Motivation should convince the reader that this proposal is necessary (and the best option among competing proposals), while the Rationale section should explain choices made while creating this proposal.

My favourite analogy is:

Motivation: We need to build a shed because...
Rationale: The shed needs to be red because...

@Dexaran
Copy link
Contributor Author

Dexaran commented Oct 11, 2024

@SamWilsn the rationale and motivation sections are updated. We are good to go.

Also I have a couple of questions:

  1. The code is currently under security audit. Once it's done - can we attach the audit report somehow? Or can we attach it as a comment in the code of the reference implementation?
  2. I'd like to deploy the code and write down the contract address to the proposal text. This needs to be done after the proposal will pass the review state (as it makes no sense to deploy something and then re-deploy if changes are requested). Can this final change be applied in the last call?

Copy link

The commit 62bdf2d (as a parent of 680c733) contains errors.
Please inspect the Run Summary for details.

@github-actions github-actions bot added the w-ci label Oct 23, 2024
@github-actions github-actions bot removed the w-ci label Oct 23, 2024
Copy link
Collaborator

@SamWilsn SamWilsn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some small grammar fixes. Feel free to fix them before moving to Last Call.


The implementation of this service is an alternative to convincing each token developer to choose an alternative standard at the moment of the token deployment or during the development stage of their project. With this service there will be no need to choose one standard and stick with it as every token can be available in both concurrently.

The implementation of this Token Converter service is supposed to be a contract deployed on Ethereum mainnet once and forever. It's address will be provided in the text of this proposal as to avoid any potential trust issues and assure the developers that the service they are interacting with is exactly the one which drives the conversion process of existing tokens.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's address will be provided in the text of this proposal [...]

Should be written in the present tense:

Suggested change
The implementation of this Token Converter service is supposed to be a contract deployed on Ethereum mainnet once and forever. It's address will be provided in the text of this proposal as to avoid any potential trust issues and assure the developers that the service they are interacting with is exactly the one which drives the conversion process of existing tokens.
The implementation of this Token Converter service is supposed to be a contract deployed on Ethereum mainnet once and forever. Its address is provided in the text of this proposal as to avoid any potential trust issues and assure the developers that the service they are interacting with is exactly the one which drives the conversion process of existing tokens.

2. [ERC-20](./eip-20.md) tokens that return `true` on success and `false` on an error without reverting the transaction.
3. [ERC-20](./eip-20.md) tokens that don't have return values and revert on an error.

Technically the third category of tokens is not compatible with [ERC-20](./eip-20.md) standard. However, USDT token deployed on Ethereum mainnet at `0xdac17f958d2ee523a2206206994597c13d831ec7` address does not implement return values and it is one of the most used tokens and it is not an option to deny supporting USDT due to it's improper implementation of the standard.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Technically the third category of tokens is not compatible with [ERC-20](./eip-20.md) standard. However, USDT token deployed on Ethereum mainnet at `0xdac17f958d2ee523a2206206994597c13d831ec7` address does not implement return values and it is one of the most used tokens and it is not an option to deny supporting USDT due to it's improper implementation of the standard.
Technically the third category of tokens is not compatible with [ERC-20](./eip-20.md) standard. However, USDT token deployed on Ethereum mainnet at `0xdac17f958d2ee523a2206206994597c13d831ec7` address does not implement return values and it is one of the most used tokens and it is not an option to deny supporting USDT due to its improper implementation of the standard.

@eip-review-bot eip-review-bot enabled auto-merge (squash) October 29, 2024 14:49
Copy link
Collaborator

@eip-review-bot eip-review-bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All Reviewers Have Approved; Performing Automatic Merge...

@eip-review-bot eip-review-bot merged commit fc8c30e into ethereum:master Oct 29, 2024
10 checks passed
@u59149403
Copy link

@Dexaran , some notes. My review applies to version published at https://eips.ethereum.org/EIPS/eip-7417 . Everywhere I say "what this means?" or ask any other question, this is not a question. This is statement "I was not able to understand immediately, and thus other people will probably have difficulties, too, so, please, rephrase". (Same applies to my bug #719 )


On ERC20Rescue.extractor in reference implementation: ha-ha. ERC with hard-coded address of particular person will never be accepted. Hard-coded address simply means that this contract is a little centralized, which is unacceptable for ERC. If you think this is needed, then simply deploy your converter as a public service, but don't submit it as ERC.

Also, I have an idea: instead of hard-coded address just let user submit cryptographically verified proof of log, which shows that the user mistakenly sent money.

As well as I understand contract cannot read logs. But contract can verify logs, see details here: https://blog.ethereum.org/2015/11/15/merkling-in-ethereum . If some user mistakenly sends money, then they can send cryptographically verified log entry of that transfer to your contract. And your contract will verify this transfer on-chain. And will send money back. I'm nearly sure this is totally possible. I'm very enthusiastic about this. If you think this is not possible, tell me your concerns, I will try to address them.

Obviously, this will depend on particular way Ethereum currently stores logs. As well as I understand they are currently stored as Merkle tree. If this ever changes, code will need to be rewritten.

Of course, such cryptographic verifying will possibly be gas-expensive. But if someone mistakenly sends a million USD, they will accept the cost.


On ERC223WrapperToken.transferFrom in reference implementation: there is inconsistency here: transferFrom logs one Transfer event with 3 values, but transfer(address _to, uint _value) logs 2 Transfer events (one of them with zero data). (Pause here. Day passed.) Okay, after 1 day after writing this paragraph I suddenly understood, what is going on. Both overloaded transfer functions attempt to call tokenReceived, but transferFrom do not. So, we emit 3-argument Transfer always when we transfer tokens, and we emit 4-argument Transfer when we attempt to call tokenReceived. Thus both transfer functions emit 2 events and transferFrom emit 1 event. So, okay, there seem to be no error here (right?!). But this was very hard to understand. There definitely need to exist comment in code explaining all these. And possibly even in main text.


On ERC223WrapperToken.TransferData: this event is never used. Did you run compiler or static analyzer on your code?


On ERC223WrapperToken.set: I suggest moving this code to constructor to make the code easier to follow


On ERC223WrapperToken.transfer: why 3-argument transfer is payable and 2-argument transfer is not? This is not explained anywhere. ERC-223 allows both versions to be payable. Moreover, in 3-argument transfer you first update token balances and then send ETH. This directly contradicts ERC-223, which states: "If ether was sent alongside tokens in this way then ether MUST be delivered first, then token balances must be updated". (Well, strictly speaking there is no contradiction, because this sentence from ERC-223 applies to 2-argument transfer. But I still think it is good idea to support this requirement in 3-argument transfer, too, assuming it is payable.)


On ERC223WrapperToken.standard: this function returns uint32, but function standard from https://eips.ethereum.org/EIPS/eip-223 reference implementation returns string. As well as I understand, EIP-223 is immutable now, but ERC-7417 is not, so I suggest replacing uint32 with string.


On ERC20WrapperToken.burn and all other burn and mint methods. There is a convention: emit Transfer to zero address when tokens are burnt and emit Transfer from zero when they are minted. For example: https://etherscan.io/tx/0x9a40a46b37740a7115f39e9bfdfbd044257143a57040df04c8674b5b0fae9cfe . As you can see on this Etherscan page in "ERC-20 Tokens Transferred" section, there was 5 ERC-20 token transfers in this transaction. One of them was burning of USDS and another was minting of DAI. So, this is already established convention, which is already integrated in Etherscan GUI (and possibly others). So, I suggest using this convention in all your ERC-20 and ERC-223 tokens. Moreover, in case of minting ERC-20 tokens, this is requirement by the standard. ERC-20 reads: "A token contract which creates new tokens SHOULD trigger a Transfer event with the _from address set to 0x0 when tokens are created"


On TokenStandardConverter.createERC223Wrapper and TokenStandardConverter.createERC20Wrapper: you should try harder to detect errors. If token's standard exists and returns "223", then this is ERC-223 token and thus createERC223Wrapper should return error. Similarly for ERC-20. Also, if supportsInterface(type(IERC223).interfaceId returns true, then createERC223Wrapper should fail and similarly for ERC-20. (Here I assume that type(IERC223).interfaceId and type(IERC20).interfaceId are not equal. And of course, you should check that these 2 values are equal to popular ones.)

I just spent some more time thinking about this. Now I think that possibility of creating both ERC-223 wrapper and ERC-20 wrapper for the same contract is absolute disaster. It will lead to confusion. So you should try really hard to detect tokens. So I suggest doing so: attempt to do zero-value transfer of a token to a contract. If it succeeds and tokenReceived is called, then this is ERC-223. If it succeeds and tokenReceived is not called, then this is ERC-20. If it doesn't succeed, then we abort. So, now we are absolutely sure that we will never create 2 wrappers for the same contract


ERC223WrapperToken has standard, but ERC20WrapperToken has no. Why? Also, in IERC223WrapperToken standard() returns string, but in ERC223WrapperToken it returns number. So, implementation and interface doesn't match. Also, you don't inherit ERC223WrapperToken from IERC223WrapperToken. If you did, then, as well as I understand, compiler did catch that type incompatibility. Similarly, IERC20WrapperToken has standard and ERC20WrapperToken has no, and compiler doesn't catch this, because ERC20WrapperToken doesn't inherit from IERC20WrapperToken.


ERC223WrapperToken.transfer first updates balances and then calls tokenReceived. So, tokenReceived sees transfer. Thus tokenReceived (from logical point of view) happens after transfer. But emit Transfer happens after tokenReceived! Thus any logs created by tokenReceived will appear before Transfer! Including any ERC-20 or ERC-223 token transfers! Thus blockchain explorers will show transfers in wrong order. So, please, do emit Transfer immediately after updating balances


"If there is no ERC-223 wrapper for the _ERC20token then creates it by calling a createERC223Wrapper(_erc20toke) function" - _ERC20token should be.


ERC223WrapperToken.transferFrom and ERC20WrapperToken.transferFrom: you violate widely known practices. First, if msg.sender == _from, then allowances is not checked and not changed. Second, if allowances is maximal value, then it is not changed, i. e. maximal allowance is infinite allowance. Go read WETH9 ( https://etherscan.io/token/0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2#code ) and OpenZeppelin's ERC-20 ( https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/ERC20.sol ).


"When developing the Converter it was decided to prioritize compatibility with existing ecosystem" - this phrase implies that you don't emit ERC-223 4-argument Transfer events. But you do emit them in transfer functions in your ERC-223 wrapper!


"standard() function usage for the introspection" - this section is not convincing. We can distinguish ERC-20 and ERC-223 tokens using ERC-165. We just need to agree on concrete values for type(IERC20).interfaceId and type(IERC223).interfaceId. Yes, there are a lot of ERC-20 tokens, which do not support ERC-165 standard, but the same applies to standard()


ERC165: you invented _registerInterface logic in abstract contract ERC165, but you don't use it in inherited classes. Same for mapping _supportedInterfaces. So I suggest removing this logic


standardERC20 and IERC20: there is no any difference between these two classes. And I'm nearly sure that type(...).interfaceId for them is same (but I didn't checked). So, please, remove standardERC20


IERC223WrapperToken has comment "@ dev Interface of the ERC20 standard." 🤦 I highly suggest re-reading whole document.


ERC223WrapperToken.supportsInterface: please, don't return true on type(IERC20).interfaceId. ERC223WrapperToken doesn't support ERC-20 in full


Okay, now I found actual bug. Let's assume that 0x01000B5fE61411C466b70631d7fF070187179Bbf address owner (presumably this is you) will call TokenStandardConverter.extractStuckERC20 with original ERC-223 token (for which ERC-20 wrapper exists) as an argument. Then you will be able to withdraw all amount of this token.


Anybody can call ERC223WrapperToken.rescueERC20. So, it is possible that in some point you will lose control over your wallet, then someone will call rescueERC20, and then the money will be stuck in your wallet


If you liked this review, feel free to ping me on next versions of this proposal and on anything related to tokens

@gorbunovperm
Copy link

gorbunovperm commented Nov 18, 2024

@u59149403 Feel free to check out my fix for the vulnerability you discovered.

Okay, now I found actual bug. Let's assume that 0x01000B5fE61411C466b70631d7fF070187179Bbf address owner (presumably this is you) will call TokenStandardConverter.extractStuckERC20 with original ERC-223 token (for which ERC-20 wrapper exists) as an argument. Then you will be able to withdraw all amount of this token.

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.

5 participants