diff --git a/packages/accounts-controller/package.json b/packages/accounts-controller/package.json index 5cffe2bddf9..78f04a59e04 100644 --- a/packages/accounts-controller/package.json +++ b/packages/accounts-controller/package.json @@ -44,7 +44,7 @@ "@ethereumjs/util": "^8.1.0", "@metamask/base-controller": "^5.0.1", "@metamask/eth-snap-keyring": "^2.1.1", - "@metamask/keyring-api": "^3.0.0", + "@metamask/keyring-api": "^4.0.0", "@metamask/snaps-sdk": "^1.3.2", "@metamask/snaps-utils": "^5.1.2", "@metamask/utils": "^8.3.0", diff --git a/packages/assets-controllers/package.json b/packages/assets-controllers/package.json index a7030155b27..6d01745cc87 100644 --- a/packages/assets-controllers/package.json +++ b/packages/assets-controllers/package.json @@ -73,7 +73,7 @@ "devDependencies": { "@metamask/auto-changelog": "^3.4.4", "@metamask/ethjs-provider-http": "^0.3.0", - "@metamask/keyring-api": "^3.0.0", + "@metamask/keyring-api": "^4.0.0", "@types/jest": "^27.4.1", "@types/lodash": "^4.14.191", "@types/node": "^16.18.54", diff --git a/packages/keyring-controller/package.json b/packages/keyring-controller/package.json index 4a91f7d0a0a..ba9d35f9e8f 100644 --- a/packages/keyring-controller/package.json +++ b/packages/keyring-controller/package.json @@ -48,7 +48,7 @@ "@metamask/eth-hd-keyring": "^7.0.1", "@metamask/eth-sig-util": "^7.0.1", "@metamask/eth-simple-keyring": "^6.0.1", - "@metamask/keyring-api": "^3.0.0", + "@metamask/keyring-api": "^4.0.0", "@metamask/message-manager": "^8.0.1", "@metamask/utils": "^8.3.0", "async-mutex": "^0.2.6", diff --git a/packages/user-operation-controller/src/UserOperationController.test.ts b/packages/user-operation-controller/src/UserOperationController.test.ts index f20651f53d9..43e7439707b 100644 --- a/packages/user-operation-controller/src/UserOperationController.test.ts +++ b/packages/user-operation-controller/src/UserOperationController.test.ts @@ -826,6 +826,42 @@ describe('UserOperationController', () => { expect(prepareMock).toHaveBeenCalledTimes(1); }); + it('uses gas limits suggested by smart contract account during #addPaymasterData', async () => { + const controller = new UserOperationController(optionsMock); + const UPDATE_USER_OPERATION_WITH_GAS_LIMITS_RESPONSE_MOCK: UpdateUserOperationResponse = + { + paymasterAndData: '0xA', + callGasLimit: '0x123', + preVerificationGas: '0x456', + verificationGasLimit: '0x789', + }; + smartContractAccount.updateUserOperation.mockResolvedValue( + UPDATE_USER_OPERATION_WITH_GAS_LIMITS_RESPONSE_MOCK, + ); + const { id, hash } = await addUserOperation( + controller, + ADD_USER_OPERATION_REQUEST_MOCK, + { ...ADD_USER_OPERATION_OPTIONS_MOCK, smartContractAccount }, + ); + + await hash(); + + expect(Object.keys(controller.state.userOperations)).toHaveLength(1); + expect( + controller.state.userOperations[id].userOperation.callGasLimit, + ).toBe(UPDATE_USER_OPERATION_WITH_GAS_LIMITS_RESPONSE_MOCK.callGasLimit); + expect( + controller.state.userOperations[id].userOperation.verificationGasLimit, + ).toBe( + UPDATE_USER_OPERATION_WITH_GAS_LIMITS_RESPONSE_MOCK.verificationGasLimit, + ); + expect( + controller.state.userOperations[id].userOperation.preVerificationGas, + ).toBe( + UPDATE_USER_OPERATION_WITH_GAS_LIMITS_RESPONSE_MOCK.preVerificationGas, + ); + }); + describe('if approval request resolved with updated transaction', () => { it('updates gas fees without regeneration if paymaster data not set', async () => { const controller = new UserOperationController(optionsMock); diff --git a/packages/user-operation-controller/src/UserOperationController.ts b/packages/user-operation-controller/src/UserOperationController.ts index af36c5393c2..66d5962434d 100644 --- a/packages/user-operation-controller/src/UserOperationController.ts +++ b/packages/user-operation-controller/src/UserOperationController.ts @@ -537,6 +537,15 @@ export class UserOperationController extends BaseController< validateUpdateUserOperationResponse(response); userOperation.paymasterAndData = response.paymasterAndData ?? EMPTY_BYTES; + if (response.callGasLimit) { + userOperation.callGasLimit = response.callGasLimit; + } + if (response.preVerificationGas) { + userOperation.preVerificationGas = response.preVerificationGas; + } + if (response.verificationGasLimit) { + userOperation.verificationGasLimit = response.verificationGasLimit; + } this.#updateMetadata(metadata); } diff --git a/packages/user-operation-controller/src/helpers/SnapSmartContractAccount.test.ts b/packages/user-operation-controller/src/helpers/SnapSmartContractAccount.test.ts index 83f77157544..f3223232390 100644 --- a/packages/user-operation-controller/src/helpers/SnapSmartContractAccount.test.ts +++ b/packages/user-operation-controller/src/helpers/SnapSmartContractAccount.test.ts @@ -61,6 +61,9 @@ const PATCH_USER_OPERATION_RESPONSE_MOCK: Awaited< ReturnType > = { paymasterAndData: '0x123', + callGasLimit: '0x444', + verificationGasLimit: '0x555', + preVerificationGas: '0x667', }; const SIGN_USER_OPERATION_RESPONSE_MOCK: Awaited< @@ -222,6 +225,11 @@ describe('SnapSmartContractAccount', () => { expect(response).toStrictEqual({ paymasterAndData: PATCH_USER_OPERATION_RESPONSE_MOCK.paymasterAndData, + callGasLimit: PATCH_USER_OPERATION_RESPONSE_MOCK.callGasLimit, + preVerificationGas: + PATCH_USER_OPERATION_RESPONSE_MOCK.preVerificationGas, + verificationGasLimit: + PATCH_USER_OPERATION_RESPONSE_MOCK.verificationGasLimit, }); expect(patchMock).toHaveBeenCalledTimes(1); @@ -244,6 +252,9 @@ describe('SnapSmartContractAccount', () => { expect(response).toStrictEqual({ paymasterAndData: undefined, + callGasLimit: undefined, + preVerificationGas: undefined, + verificationGasLimit: undefined, }); }); }); diff --git a/packages/user-operation-controller/src/helpers/SnapSmartContractAccount.ts b/packages/user-operation-controller/src/helpers/SnapSmartContractAccount.ts index 7db5d57cb23..c8e77462c4f 100644 --- a/packages/user-operation-controller/src/helpers/SnapSmartContractAccount.ts +++ b/packages/user-operation-controller/src/helpers/SnapSmartContractAccount.ts @@ -65,12 +65,16 @@ export class SnapSmartContractAccount implements SmartContractAccount { const { userOperation } = request; const { sender } = userOperation; - const { paymasterAndData: responsePaymasterAndData } = - await this.#messenger.call( - 'KeyringController:patchUserOperation', - sender, - userOperation, - ); + const { + paymasterAndData: responsePaymasterAndData, + verificationGasLimit, + preVerificationGas, + callGasLimit, + } = await this.#messenger.call( + 'KeyringController:patchUserOperation', + sender, + userOperation, + ); const paymasterAndData = responsePaymasterAndData === EMPTY_BYTES @@ -79,6 +83,9 @@ export class SnapSmartContractAccount implements SmartContractAccount { return { paymasterAndData, + verificationGasLimit, + preVerificationGas, + callGasLimit, }; } diff --git a/packages/user-operation-controller/src/types.ts b/packages/user-operation-controller/src/types.ts index 9162189cade..70c165f12dd 100644 --- a/packages/user-operation-controller/src/types.ts +++ b/packages/user-operation-controller/src/types.ts @@ -253,6 +253,15 @@ export type UpdateUserOperationResponse = { * Not required if a paymaster is not sponsoring the transaction. */ paymasterAndData?: string; + + /** + * The final gas limits for the user operation suggested by the smart contract account. + * The simulated gas limits may be different after the bundler estimates gas with the use + * of the paymaster. + */ + callGasLimit?: string; + preVerificationGas?: string; + verificationGasLimit?: string; }; /** diff --git a/packages/user-operation-controller/src/utils/validation.test.ts b/packages/user-operation-controller/src/utils/validation.test.ts index 4f6345a542d..5098185e730 100644 --- a/packages/user-operation-controller/src/utils/validation.test.ts +++ b/packages/user-operation-controller/src/utils/validation.test.ts @@ -432,7 +432,25 @@ describe('validation', () => { 'paymasterAndData', 'wrong type', 123, - 'Expected a value of type `Hexadecimal String`, but received: `123`', + 'Expected a value of type `Hexadecimal String or 0x`, but received: `123`', + ], + [ + 'callGasLimit', + 'wrong type', + 123, + 'Expected a value of type `Hexadecimal String or 0x`, but received: `123`', + ], + [ + 'preVerificationGas', + 'wrong type', + 123, + 'Expected a value of type `Hexadecimal String or 0x`, but received: `123`', + ], + [ + 'verificationGasLimit', + 'wrong type', + 123, + 'Expected a value of type `Hexadecimal String or 0x`, but received: `123`', ], ])( 'throws if %s is %s', diff --git a/packages/user-operation-controller/src/utils/validation.ts b/packages/user-operation-controller/src/utils/validation.ts index 362178a6a5a..10cffda2878 100644 --- a/packages/user-operation-controller/src/utils/validation.ts +++ b/packages/user-operation-controller/src/utils/validation.ts @@ -135,11 +135,12 @@ export function validatePrepareUserOperationResponse( export function validateUpdateUserOperationResponse( response: UpdateUserOperationResponse, ) { - const HexOrEmptyBytes = defineHex(); - const ValidResponse = optional( object({ - paymasterAndData: optional(HexOrEmptyBytes), + paymasterAndData: optional(defineHexOrEmptyBytes()), + callGasLimit: optional(defineHexOrEmptyBytes()), + preVerificationGas: optional(defineHexOrEmptyBytes()), + verificationGasLimit: optional(defineHexOrEmptyBytes()), }), ); diff --git a/yarn.lock b/yarn.lock index e65c0743e9e..e1058205db8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1646,7 +1646,7 @@ __metadata: "@metamask/auto-changelog": ^3.4.4 "@metamask/base-controller": ^5.0.1 "@metamask/eth-snap-keyring": ^2.1.1 - "@metamask/keyring-api": ^3.0.0 + "@metamask/keyring-api": ^4.0.0 "@metamask/keyring-controller": ^14.0.1 "@metamask/snaps-controllers": ^4.0.0 "@metamask/snaps-sdk": ^1.3.2 @@ -1764,7 +1764,7 @@ __metadata: "@metamask/controller-utils": ^9.0.2 "@metamask/eth-query": ^4.0.0 "@metamask/ethjs-provider-http": ^0.3.0 - "@metamask/keyring-api": ^3.0.0 + "@metamask/keyring-api": ^4.0.0 "@metamask/keyring-controller": ^14.0.1 "@metamask/metamask-eth-abis": ^3.1.1 "@metamask/network-controller": ^18.1.0 @@ -2436,9 +2436,9 @@ __metadata: languageName: node linkType: hard -"@metamask/keyring-api@npm:^3.0.0": - version: 3.0.0 - resolution: "@metamask/keyring-api@npm:3.0.0" +"@metamask/keyring-api@npm:^4.0.0": + version: 4.0.0 + resolution: "@metamask/keyring-api@npm:4.0.0" dependencies: "@metamask/providers": ^14.0.1 "@metamask/snaps-sdk": ^1.3.2 @@ -2446,7 +2446,7 @@ __metadata: "@types/uuid": ^9.0.1 superstruct: ^1.0.3 uuid: ^9.0.0 - checksum: 5e3fdc122789d605681070aa6ed6c656d5c9bb1f037fd4bf1ed2ec5fa453a0fc8b9663ddfd2106c122889682e2ae1c8ddd16913798f24821b22899f743ce1a31 + checksum: 137521a967651fcc3435eb31d51c060a74199df28910c2c27a2472469db842f0fbe267d029138eb08854b8bee909886b6df038eb38dd08afbbca5e586f266d30 languageName: node linkType: hard @@ -2466,7 +2466,7 @@ __metadata: "@metamask/eth-hd-keyring": ^7.0.1 "@metamask/eth-sig-util": ^7.0.1 "@metamask/eth-simple-keyring": ^6.0.1 - "@metamask/keyring-api": ^3.0.0 + "@metamask/keyring-api": ^4.0.0 "@metamask/message-manager": ^8.0.1 "@metamask/scure-bip39": ^2.1.1 "@metamask/utils": ^8.3.0