Skip to content

Add on-chain validation to ERC20 wallet_watchAsset#1745

Merged
bergeron merged 10 commits intomainfrom
brian/validate-erc20-watchasset
Dec 11, 2023
Merged

Add on-chain validation to ERC20 wallet_watchAsset#1745
bergeron merged 10 commits intomainfrom
brian/validate-erc20-watchasset

Conversation

@bergeron
Copy link
Copy Markdown
Contributor

@bergeron bergeron commented Sep 29, 2023

Explanation

Problem:

wallet_watchAsset accepts type, address, symbol, and decimals from dapps. But it does not verify the accuracy of this information against the chain:

  • You can pass type=ERC20, but provide an address that is an EOA, ERC721, or ERC1155 contract.
  • You can pass an ERC20 contract, but with the wrong symbol or decimals. Malicious dapps can watch scam tokens under legit symbols, as seen here:
Screen.Recording.2023-09-28.at.10.27.46.PM.mov

Solution:

Query the chain to get the contract type, symbol, and decimals. Validate the request against the on-chain data. If symbol and decimals are defined in the contract (they're optional in ERC20), then the caller may now omit them in the request to wallet_watchAsset . Otherwise they will be validated against the on-chain data.

References

Changelog

@metamask/assets-controllers

  • BREAKING: TokensController.watchAsset now performs on-chain validation of
    the asset's symbol and decimals, if they're defined in the contract. If the symbol and decimals are defined in the contract, they are no longer required to be passed to watchAsset

Checklist

  • I've updated the test suite for new or updated code as appropriate
  • I've updated documentation (JSDoc, Markdown, etc.) for new or updated code as appropriate
  • I've highlighted breaking changes using the "BREAKING" category above as appropriate

@bergeron bergeron requested a review from a team as a code owner September 29, 2023 15:21
@bergeron bergeron requested a review from adonesky1 September 29, 2023 15:24
@adonesky1
Copy link
Copy Markdown
Contributor

adonesky1 commented Oct 19, 2023

In general for this task I was thinking that we would initially do validation in the confirmation itself as we do with addEthereumChain here warning users when incorrect details are being suggested.

That being said, I totally agree with your thoughts here - which mirror how I implemented the NFT part of the API:

Because symbol and decimals should be sourced on-chain, I don't think dapps should even be providing them. I think we should start ignoring the symbol and decimals provided by dapps, and remove those arguments in documentation.
This is also my interpretation of what the final version of EIP-747 intends. Looking through its history and discussion, symbol and decimals were initially in the payload. Later edits specified that on-chain information should override them. And they were later removed entirely for this reason.

but we need to get the greenlight from @rekmarks and @Gudahtt to change the expected behavior of this API (ignoring/deprecating detail params).

@bergeron
Copy link
Copy Markdown
Contributor Author

bergeron commented Dec 5, 2023

Based on feedback from Dan, I've continued allowing symbol and decimals in the request. This supports contracts that don't implement them (they're optional in ERC20). If they are defined in your contract, you can now omit them in the request to wallet_watchAsset. And if you provide them, they'll be validated against what's on chain.

* @param address - ERC1155 asset contract address.
* @returns Promise resolving to whether the contract implements ERC1155 URI Metadata interface.
*/
contractSupportsURIMetadataInterface = async (
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Just curious was there any functional purpose to changing these from arrow functions?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

They were changed to make them mockable with jest. Don't expect any functional changes.

interactingAddress: interactingAddress || this.config.selectedAddress,
};

validateTokenToWatch(asset);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[nit] doesn't really matter but could have preserved this utility function and just passed in the provider do those operations.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yeah it could have. Just didn't feel like it gained much from being separate. Already tightly coupled to watchAsset, not more testable, and would need to go beyond validation by returning data to avoid duplicating logic.

Comment on lines +42 to +45
const stubCreateEthers = (ctrl: TokensController, res: () => boolean) => {
return sinon.stub(ctrl, '_createEthersContract').callsFake(() => {
return {
supportsInterface: sinon.stub().returns(res),
supportsInterface: sinon.stub().returns(res()),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

?

const stubCreateEthers = (ctrl: TokensController, res: boolean) => {
  return sinon.stub(ctrl, '_createEthersContract').callsFake(() => {
    return {
      supportsInterface: sinon.stub().resolves(res),

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Either returns or resolves seems to work. But I think res being a function is useful. The stub can be setup once in beforeEach with a default value, while allowing individual tests can overwrite the default. Otherwise every test would need to setup the stub.

createEthersStub = stubCreateEthers(tokensController, () => isERC721);

adonesky1
adonesky1 previously approved these changes Dec 8, 2023
Copy link
Copy Markdown
Contributor

@adonesky1 adonesky1 left a comment

Choose a reason for hiding this comment

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

A few nits but generally LGTM!

adonesky1
adonesky1 previously approved these changes Dec 11, 2023
Copy link
Copy Markdown
Contributor

@adonesky1 adonesky1 left a comment

Choose a reason for hiding this comment

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

LGTM!

@bergeron bergeron merged commit 4db07b6 into main Dec 11, 2023
@bergeron bergeron deleted the brian/validate-erc20-watchasset branch December 11, 2023 17:43
Gudahtt added a commit that referenced this pull request Dec 14, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants