Skip to content

Commit

Permalink
fix(TXL-207): fixes gaps in transaction validation and async error lo…
Browse files Browse the repository at this point in the history
…gging (#4596)

## Explanation
This PR repairs some gaps in our validation of transaction parameters:
* validate that if a transaction envelope type is specified that it is a
valid type
* validate transaction parameters before right before adding transaction
to state
* catch and log async errors

Notes
* The steps to reproduce in
MetaMask/metamask-extension#23180 revealed an
async error that was being swallowed.

<!--
Thanks for your contribution! Take a moment to answer these questions so
that reviewers have the information they need to properly understand
your changes:

* What is the current state of things and why does it need to change?
* What is the solution your changes offer and how does it work?
* Are there any changes whose purpose might not obvious to those
unfamiliar with the domain?
* If your primary goal was to update one package but you found you had
to update another one along the way, why did you do so?
* If you had to upgrade a dependency, why did you do so?
-->

## References
This PR will fix these bugs once clients are updated:
* Related: MetaMask/MetaMask-planning#2341 
* Related: MetaMask/metamask-extension#23180

## Changelog

<!--
If you're making any consumer-facing changes, list those changes here as
if you were updating a changelog, using the template below as a guide.

(CATEGORY is one of BREAKING, ADDED, CHANGED, DEPRECATED, REMOVED, or
FIXED. For security-related issues, follow the Security Advisory
process.)

Please take care to name the exact pieces of the API you've added or
changed (e.g. types, interfaces, functions, or methods).

If there are any breaking changes, make sure to offer a solution for
consumers to follow once they upgrade to the changes.

Finally, if you're only making changes to development scripts or tests,
you may replace the template below with "None".
-->

### `@metamask/transaction-controller`

- **FIXED**: Fixes gaps in transaction parameter validation and async
error logging

## 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
  • Loading branch information
dbrans authored Aug 12, 2024
1 parent d8d1b22 commit 7796d35
Show file tree
Hide file tree
Showing 4 changed files with 53 additions and 13 deletions.
6 changes: 3 additions & 3 deletions packages/transaction-controller/jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@ module.exports = merge(baseConfig, {
coverageThreshold: {
global: {
branches: 93.44,
functions: 98.4,
lines: 98.72,
statements: 98.73,
functions: 97.39,
lines: 98.4,
statements: 98.42,
},
},

Expand Down
29 changes: 19 additions & 10 deletions packages/transaction-controller/src/TransactionController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1110,8 +1110,10 @@ export class TransactionController extends BaseController<
this.addMetadata(addedTransactionMeta);

if (requireApproval !== false) {
// eslint-disable-next-line @typescript-eslint/no-floating-promises
this.#updateSimulationData(addedTransactionMeta);
this.#updateSimulationData(addedTransactionMeta).catch((error) => {
log('Error while updating simulation data', error);
throw error;
});
} else {
log('Skipping simulation as approval not required');
}
Expand Down Expand Up @@ -1694,8 +1696,10 @@ export class TransactionController extends BaseController<
this.onTransactionStatusChange(updatedTransactionMeta);

// Intentional given potential duration of process.
// eslint-disable-next-line @typescript-eslint/no-floating-promises
this.updatePostBalance(updatedTransactionMeta);
this.updatePostBalance(updatedTransactionMeta).catch((error) => {
log('Error while updating post balance', error);
throw error;
});

this.messagingSystem.publish(
`${controllerName}:transactionConfirmed`,
Expand Down Expand Up @@ -2429,6 +2433,7 @@ export class TransactionController extends BaseController<
}

private addMetadata(transactionMeta: TransactionMeta) {
validateTxParams(transactionMeta.txParams);
this.update((state) => {
state.transactions = this.trimTransactionsForState([
...state.transactions,
Expand All @@ -2439,8 +2444,8 @@ export class TransactionController extends BaseController<

private async updateGasProperties(transactionMeta: TransactionMeta) {
const isEIP1559Compatible =
(await this.getEIP1559Compatibility(transactionMeta.networkClientId)) &&
transactionMeta.txParams.type !== TransactionEnvelopeType.legacy;
transactionMeta.txParams.type !== TransactionEnvelopeType.legacy &&
(await this.getEIP1559Compatibility(transactionMeta.networkClientId));

const { networkClientId, chainId } = transactionMeta;

Expand Down Expand Up @@ -3358,8 +3363,10 @@ export class TransactionController extends BaseController<
this.onTransactionStatusChange(transactionMeta);

// Intentional given potential duration of process.
// eslint-disable-next-line @typescript-eslint/no-floating-promises
this.updatePostBalance(transactionMeta);
this.updatePostBalance(transactionMeta).catch((error) => {
log('Error while updating post balance', error);
throw error;
});
}

private async updatePostBalance(transactionMeta: TransactionMeta) {
Expand Down Expand Up @@ -3710,8 +3717,10 @@ export class TransactionController extends BaseController<
)
) {
log('Updating simulation data due to transaction parameter update');
// eslint-disable-next-line @typescript-eslint/no-floating-promises
this.#updateSimulationData(transactionMeta);
this.#updateSimulationData(transactionMeta).catch((error) => {
log('Error updating simulation data', error);
throw error;
});
}
}

Expand Down
9 changes: 9 additions & 0 deletions packages/transaction-controller/src/utils/validation.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,15 @@ import { validateTxParams } from './validation';

describe('validation', () => {
describe('validateTxParams', () => {
it('should throw if unknown transaction envelope type is specified', () => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
expect(() => validateTxParams({ type: '0x3' } as any)).toThrow(
rpcErrors.invalidParams(
'Invalid transaction envelope type: "0x3". Must be one of: 0x0, 0x1, 0x2',
),
);
});

it('should throw if no from address', () => {
// TODO: Replace `any` with type
// eslint-disable-next-line @typescript-eslint/no-explicit-any
Expand Down
22 changes: 22 additions & 0 deletions packages/transaction-controller/src/utils/validation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ export function validateTxParams(
txParams: TransactionParams,
isEIP1559Compatible = true,
) {
validateEnvelopeType(txParams.type);
validateEIP1559Compatibility(txParams, isEIP1559Compatible);
validateParamFrom(txParams.from);
validateParamRecipient(txParams);
Expand All @@ -64,6 +65,27 @@ export function validateTxParams(
validateGasFeeParams(txParams);
}

/**
* Validates the `type` property, ensuring that if it is specified, it is a valid transaction envelope type.
*
* @param type - The transaction envelope type to validate.
* @throws Throws invalid params if the type is not a valid transaction envelope type.
*/
function validateEnvelopeType(type: string | undefined) {
if (
type &&
!Object.values(TransactionEnvelopeType).includes(
type as TransactionEnvelopeType,
)
) {
throw rpcErrors.invalidParams(
`Invalid transaction envelope type: "${type}". Must be one of: ${Object.values(
TransactionEnvelopeType,
).join(', ')}`,
);
}
}

/**
* Validates EIP-1559 compatibility for transaction creation.
*
Expand Down

0 comments on commit 7796d35

Please sign in to comment.