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

Tokens controller approve reject refactor full #1268

Merged
merged 18 commits into from
May 19, 2023

Conversation

bergarces
Copy link
Contributor

@bergarces bergarces commented Apr 26, 2023

Description

Stops triggering the acceptance or rejection of the approval from within TokensController. That'll be the responsibility of the caller.

This is part of a series of tickets that aim to refactor and simplify how we await for the confirmation promise.

Changes

  • BREAKING: Awaits approval request in TokensController instead of relaying on acceptWatchAsset and rejectWatchAsset, which are being removed.
  • BREAKING: Changes return value for watchAsset.

References

Checklist

  • I've updated the test suite for new or updated code as appropriate
  • I've updated documentation for new or updated code as appropriate (note: this will usually be JSDoc)
  • I've highlighted breaking changes using the "BREAKING" category above as appropriate

@bergarces bergarces force-pushed the tokens-controller-approve-reject-refactor-full branch from 8662958 to b9ba7a7 Compare April 26, 2023 12:06
@bergarces bergarces force-pushed the tokens-controller-approve-reject-refactor-full branch from f808086 to 5b6ba7a Compare April 26, 2023 14:31
@@ -667,144 +614,48 @@ export class TokensController extends BaseController<
asset: Token,
type: string,
interactingAddress?: string,
): Promise<AssetSuggestionResult> {
): Promise<void> {
Copy link
Contributor Author

@bergarces bergarces Apr 26, 2023

Choose a reason for hiding this comment

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

If this wasn't already a breaking change I'd have avoided doing this, but the changes are already breaking.

When this wasn't awaiting for the request to be approved or declined it was returning both the pending asset and the promise to await, but now that watchAsset handles the whole process there's no need to return the pending asset (which was not being used anyway) nor the address of the token (which was not being used either).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This makes more sense when looking at the before/after of how it's handled in the clients

Copy link
Member

Choose a reason for hiding this comment

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

For reference, the counter argument is that it could be good practice to provide useful return data in all our API methods for the sake of future-proofing. Though given the level of complexity we've reached, I'd agree to simplify where we can as we can add this back when needed.

Naturally this wouldn't be possible if the return value ever reached the dApp, but currently the only result from the middleware is a boolean.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The thing is that I'm not entirely sure what would be useful to return in this case. The suggestedAssetMeta or the Token from addToken.

If we do the second, and then we end up adding more types, it's going to turn into a bigger breaking change.

I think leaving it to return void is safest, as we probably won't need to update anything in every client if we eventually return something (even though it still counts as breaking change).

const { selectedAddress } = this.config;

const suggestedAssetMeta: SuggestedAssetMeta & {
interactingAddress: string;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

We don't need to specify that interactingAddress is not optional, because selectedAddress isn't.

@@ -120,7 +85,6 @@ export interface TokensState extends BaseState {
allTokens: { [key: string]: { [key: string]: Token[] } };
allIgnoredTokens: { [key: string]: { [key: string]: string[] } };
allDetectedTokens: { [key: string]: { [key: string]: Token[] } };
suggestedAssets: SuggestedAssetMeta[];
Copy link
Contributor Author

Choose a reason for hiding this comment

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

No longer need for status if we don't want to persist them after restarts.

Copy link
Member

Choose a reason for hiding this comment

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

As this was previously persisted, does this warrant a migration to delete this data on upgrade?
Not sure what the best practice is and if we prioritise fewer migrations over dormant data in the user state.

Copy link
Contributor

Choose a reason for hiding this comment

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

How was this decision come to? Not saying I disagree with it. I'm just curious what the thinking was here.

Copy link
Contributor Author

@bergarces bergarces May 16, 2023

Choose a reason for hiding this comment

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

The ability to persist pending wallet_watchAsset after a restart had no tests to cover that case and it was inadvertently broken in a previous PR.

When we found out about the regression, we reached out, asked if that feature was intentional, accidental or important, so that we could decide between fixing it first. The decision was to not restore that feature and have it behave the same way as other confirmations such as the ones that ask specific permissions (which are also not persisted).

cc @cryptotavares

Choose a reason for hiding this comment

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

@adonesky1 I have discussed this with @danjm and @bschorchit and we didn't thought of any relevant use case. As so we have decided to standardise the pending wallet_watchAsset confirmation behaviour (i.e no persisting confirmations after a restart... just like permissions for example. The only exception are transactions). But is there a use case that you can think for persisting pending approvals to add suggested assets?

Copy link
Contributor

Choose a reason for hiding this comment

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

I guess the one reason I can think of to keep suggestedAsset state (though we can probably accomplish this by looking at the approval controller state) is that to avoid duplicate suggestions. This is obviously not about persisting state across reloads. We do not currently prevent duplicate suggestions:
Screenshot 2023-05-16 at 1 09 17 PM

But I was thinking maybe we ought to?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It'd be easier to rate limit approvals from wallet_watchAsset type from ApprovalController.

@bergarces bergarces force-pushed the tokens-controller-approve-reject-refactor-full branch from 3b37ef1 to 38e2ebf Compare May 4, 2023 19:37
}
} catch (error) {
this.failSuggestedAsset(suggestedAssetMeta, error);
return Promise.reject(error);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This error return is maintained by:

  • Throwing with what is now the default clause if the type is not ERC20 (this should probably be changed to throw using ethErrors from eth-rpc-errors).
  • Letting validateTokenToWatch throw freely.

return reject(new Error(meta.error.message));
/* istanbul ignore next */
default:
return reject(new Error(`Unknown status: ${meta.status}`));
Copy link
Contributor Author

Choose a reason for hiding this comment

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

All the cases in this switch are maintained (except the return value when it resolves):

): Promise<void> {
if (type !== 'ERC20') {
throw new Error(`Asset of type ${type} not supported`);
}
Copy link
Contributor Author

@bergarces bergarces May 4, 2023

Choose a reason for hiding this comment

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

I have added this validation because I don't think there's any point on keeping the switch given that this is only for ERC20. By keeping the switch we would have to either:

  • Add a switch before validation and another one after awaiting for request approval (like it currently does in the acceptWatchAsset), which in turn generates a default case that is unreachable.

  • Another option would be to define a variable that holds a callback and is populated in the first switch, something like:

let addToken: () => void;
switch (type) {	
  case 'ERC20':	
    validateTokenToWatch(asset);	
    addToken = () => { this.addToken(/* stuff */) };
    break;	
  default:	
    throw new Error(`Asset of type ${type} not supported`);
}

That way, if there were multiple cases, we could avoid needing a second switch after waiting for approval. This would be my choice if there was more than one type or plans to add another type here, but that's not the case and doing it will be more difficult to read for no reason (NFTs are being handled elsewhere).

Suggestions welcome though.

Copy link
Member

Choose a reason for hiding this comment

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

This sounds good to me, an if is cleaner if we don't expect to ever have any more cases.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This file has undergone chunky refactors. I suggest to review it by looking both at the diff and the final product.

*
* @param suggestedAssetID - The ID of the suggestedAsset to accept.
*/
rejectWatchAsset(suggestedAssetID: string) {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Nothing needs to happen now when rejecting since there's no longer status for suggestedAssets, so this is dead code.

@bergarces bergarces marked this pull request as ready for review May 4, 2023 20:38
@bergarces bergarces requested a review from a team as a code owner May 4, 2023 20:38
@bergarces bergarces marked this pull request as draft May 5, 2023 08:33
Copy link
Contributor Author

Choose a reason for hiding this comment

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

The reason so many tests have been removed, is because some of them were testing the watchAsset part and others were testing approval/rejection.

That all happens within watchAsset now, so they can be simplified maintaining coverage.

@bergarces bergarces marked this pull request as ready for review May 11, 2023 16:10
.stub(tokensController, '_generateRandomId')
.callsFake(() => requestId);
type = 'ERC20';
it('stores token correctly under interacting address if user confirms', async function () {
Copy link
Member

Choose a reason for hiding this comment

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

I appreciate it's a negative test as it's verifying we don't have any error handling logic, but should we have a test to verify that the method throws if the promise from the messenger call rejects?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I can't see what value that'll add as part of a unit test. It's a promise that is being awaited and that is not wrapped in a try/catch, so yes, it will throw.

In an e2e test we can check that, when rejected, it indeed throws a userRejectedRequest RPC error.

Copy link
Member

@matthewwalsh0 matthewwalsh0 May 19, 2023

Choose a reason for hiding this comment

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

The value would be it would assert our intention that it's not catching the approval errors.

What you've said concerning the code is correct but the unit test would programmatically enforce that requirement going forward, and ensure it's not captured and handled in future without us being aware and explicitly changing our expectations.


this._requestApproval(suggestedAssetMeta);
await this._requestApproval(suggestedAssetMeta);
Copy link
Contributor

Choose a reason for hiding this comment

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

Should we wrap this in a try/catch and handle a failure state gracefully since the catch within the method is removed?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I don't think we should after these changes.

If, for example, the user rejects to add the token from the front end, we now rely on them sending the userRejectedRequest error, which we want to throw as it is.

@adonesky1
Copy link
Contributor

adonesky1 commented May 18, 2023

Just want to highlight this PR I've opened with NFT side changes that mirror this refactor: #1173

matthewwalsh0
matthewwalsh0 previously approved these changes May 19, 2023
@bergarces bergarces merged commit 09385b1 into main May 19, 2023
@bergarces bergarces deleted the tokens-controller-approve-reject-refactor-full branch May 19, 2023 12:14
@mcmire mcmire mentioned this pull request May 25, 2023
Gudahtt added a commit that referenced this pull request May 26, 2023
Gudahtt added a commit that referenced this pull request May 26, 2023
Gudahtt added a commit that referenced this pull request May 26, 2023
Gudahtt added a commit that referenced this pull request May 26, 2023
Gudahtt added a commit that referenced this pull request May 26, 2023
Gudahtt added a commit that referenced this pull request May 26, 2023
brad-decker pushed a commit to dragana8/core that referenced this pull request Jun 1, 2023
* refactor to wait for pending approval promise to resolve/reject
MajorLift pushed a commit that referenced this pull request Oct 11, 2023
* refactor to wait for pending approval promise to resolve/reject
MajorLift pushed a commit that referenced this pull request Oct 11, 2023
* refactor to wait for pending approval promise to resolve/reject
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.

5 participants