From 3e3ff7a028ff32519ce081ab6a0cb6acb1f12293 Mon Sep 17 00:00:00 2001 From: Michael Standen Date: Thu, 19 Dec 2024 14:32:59 +1300 Subject: [PATCH 1/2] Add immutable wallet proxy hook --- packages/abi/src/wallet/index.ts | 6 +- packages/abi/src/wallet/moduleHooks.ts | 248 +++++++++++++++++++++ packages/abi/src/wallet/walletProxyHook.ts | 9 + packages/account/src/account.ts | 34 ++- 4 files changed, 295 insertions(+), 2 deletions(-) create mode 100644 packages/abi/src/wallet/moduleHooks.ts create mode 100644 packages/abi/src/wallet/walletProxyHook.ts diff --git a/packages/abi/src/wallet/index.ts b/packages/abi/src/wallet/index.ts index cb9bdf867..73a90929a 100644 --- a/packages/abi/src/wallet/index.ts +++ b/packages/abi/src/wallet/index.ts @@ -4,8 +4,10 @@ import * as erc6492 from './erc6492' import * as factory from './factory' import * as mainModule from './mainModule' import * as mainModuleUpgradable from './mainModuleUpgradable' +import * as moduleHooks from './moduleHooks' import * as sequenceUtils from './sequenceUtils' import * as requireFreshSigner from './libs/requireFreshSigners' +import * as walletProxyHook from './walletProxyHook' export const walletContracts = { erc6492, @@ -14,6 +16,8 @@ export const walletContracts = { factory, mainModule, mainModuleUpgradable, + moduleHooks, sequenceUtils, - requireFreshSigner + requireFreshSigner, + walletProxyHook } diff --git a/packages/abi/src/wallet/moduleHooks.ts b/packages/abi/src/wallet/moduleHooks.ts new file mode 100644 index 000000000..e35174b94 --- /dev/null +++ b/packages/abi/src/wallet/moduleHooks.ts @@ -0,0 +1,248 @@ +export const abi = [ + { + inputs: [ + { + internalType: 'bytes4', + name: '_signature', + type: 'bytes4' + } + ], + name: 'HookAlreadyExists', + type: 'error' + }, + { + inputs: [ + { + internalType: 'bytes4', + name: '_signature', + type: 'bytes4' + } + ], + name: 'HookDoesNotExist', + type: 'error' + }, + { + inputs: [ + { + internalType: 'address', + name: '_sender', + type: 'address' + }, + { + internalType: 'address', + name: '_self', + type: 'address' + } + ], + name: 'OnlySelfAuth', + type: 'error' + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: 'bytes4', + name: '_signature', + type: 'bytes4' + }, + { + indexed: false, + internalType: 'address', + name: '_implementation', + type: 'address' + } + ], + name: 'DefinedHook', + type: 'event' + }, + { + stateMutability: 'payable', + type: 'fallback' + }, + { + inputs: [ + { + internalType: 'bytes4', + name: '_signature', + type: 'bytes4' + }, + { + internalType: 'address', + name: '_implementation', + type: 'address' + } + ], + name: 'addHook', + outputs: [], + stateMutability: 'nonpayable', + type: 'function' + }, + { + inputs: [ + { + internalType: 'address', + name: '', + type: 'address' + }, + { + internalType: 'address', + name: '', + type: 'address' + }, + { + internalType: 'uint256[]', + name: '', + type: 'uint256[]' + }, + { + internalType: 'uint256[]', + name: '', + type: 'uint256[]' + }, + { + internalType: 'bytes', + name: '', + type: 'bytes' + } + ], + name: 'onERC1155BatchReceived', + outputs: [ + { + internalType: 'bytes4', + name: '', + type: 'bytes4' + } + ], + stateMutability: 'nonpayable', + type: 'function' + }, + { + inputs: [ + { + internalType: 'address', + name: '', + type: 'address' + }, + { + internalType: 'address', + name: '', + type: 'address' + }, + { + internalType: 'uint256', + name: '', + type: 'uint256' + }, + { + internalType: 'uint256', + name: '', + type: 'uint256' + }, + { + internalType: 'bytes', + name: '', + type: 'bytes' + } + ], + name: 'onERC1155Received', + outputs: [ + { + internalType: 'bytes4', + name: '', + type: 'bytes4' + } + ], + stateMutability: 'nonpayable', + type: 'function' + }, + { + inputs: [ + { + internalType: 'address', + name: '', + type: 'address' + }, + { + internalType: 'address', + name: '', + type: 'address' + }, + { + internalType: 'uint256', + name: '', + type: 'uint256' + }, + { + internalType: 'bytes', + name: '', + type: 'bytes' + } + ], + name: 'onERC721Received', + outputs: [ + { + internalType: 'bytes4', + name: '', + type: 'bytes4' + } + ], + stateMutability: 'nonpayable', + type: 'function' + }, + { + inputs: [ + { + internalType: 'bytes4', + name: '_signature', + type: 'bytes4' + } + ], + name: 'readHook', + outputs: [ + { + internalType: 'address', + name: '', + type: 'address' + } + ], + stateMutability: 'view', + type: 'function' + }, + { + inputs: [ + { + internalType: 'bytes4', + name: '_signature', + type: 'bytes4' + } + ], + name: 'removeHook', + outputs: [], + stateMutability: 'nonpayable', + type: 'function' + }, + { + inputs: [ + { + internalType: 'bytes4', + name: '_interfaceID', + type: 'bytes4' + } + ], + name: 'supportsInterface', + outputs: [ + { + internalType: 'bool', + name: '', + type: 'bool' + } + ], + stateMutability: 'pure', + type: 'function' + }, + { + stateMutability: 'payable', + type: 'receive' + } +] as const diff --git a/packages/abi/src/wallet/walletProxyHook.ts b/packages/abi/src/wallet/walletProxyHook.ts new file mode 100644 index 000000000..d977e1f0a --- /dev/null +++ b/packages/abi/src/wallet/walletProxyHook.ts @@ -0,0 +1,9 @@ +export const abi = [ + { + type: 'function', + name: 'PROXY_getImplementation', + inputs: [], + outputs: [{ name: '', type: 'address', internalType: 'address' }], + stateMutability: 'view' + } +] as const diff --git a/packages/account/src/account.ts b/packages/account/src/account.ts index 8ca3fca73..56e92d09f 100644 --- a/packages/account/src/account.ts +++ b/packages/account/src/account.ts @@ -402,13 +402,45 @@ export class Account { status: AccountStatus, chainId: ethers.BigNumberish ): Promise { + txs = Array.isArray(txs) ? txs : [txs] // if onchain wallet config is not up to date // then we should append an extra transaction that updates it // to the latest "lazy" state if (status.onChain.imageHash !== status.imageHash) { const wallet = this.walletForStatus(chainId, status) const updateConfig = await wallet.buildUpdateConfigurationTransaction(status.config) - return [Array.isArray(txs) ? txs : [txs], updateConfig.transactions].flat() + txs = [...txs, ...updateConfig.transactions] + } + + // On immutable chains, we add the WalletProxyHook + if (chainId === ChainId.IMMUTABLE_ZKEVM || chainId === ChainId.IMMUTABLE_ZKEVM_TESTNET) { + const provider = this.providerFor(chainId) + if (provider) { + const hook = new ethers.Contract(this.address, walletContracts.walletProxyHook.abi, provider) + let implementation + try { + implementation = await hook.PROXY_getImplementation() + } catch (e) { + // Handle below + console.log('Error getting implementation address', e) + } + if (!implementation || implementation === ethers.ZeroAddress) { + console.log('Adding wallet proxy hook') + const hooksInterface = new ethers.Interface(walletContracts.moduleHooks.abi) + const tx: commons.transaction.Transaction = { + to: this.address, + data: hooksInterface.encodeFunctionData(hooksInterface.getFunction('addHook')!, [ + '0x90611127', + '0x1f56dbAD5e8319F0DE9a323E24A31b5077dEB1a4' + ]), + gasLimit: 50000, // Expected ~28k gas. Buffer added + delegateCall: false, + revertOnError: false, + value: 0 + } + txs = [tx, ...txs] + } + } } return txs From e9c6931551b7257744640167d31dd67d8c733720 Mon Sep 17 00:00:00 2001 From: Michael Standen Date: Wed, 8 Jan 2025 06:40:46 +1300 Subject: [PATCH 2/2] Pass getImplementationHook in context --- packages/account/src/account.ts | 5 +++-- packages/core/src/commons/context.ts | 2 ++ packages/core/src/v2/index.ts | 3 ++- packages/tests/src/context/v2.ts | 3 ++- 4 files changed, 9 insertions(+), 4 deletions(-) diff --git a/packages/account/src/account.ts b/packages/account/src/account.ts index 56e92d09f..7a9e8da89 100644 --- a/packages/account/src/account.ts +++ b/packages/account/src/account.ts @@ -413,7 +413,8 @@ export class Account { } // On immutable chains, we add the WalletProxyHook - if (chainId === ChainId.IMMUTABLE_ZKEVM || chainId === ChainId.IMMUTABLE_ZKEVM_TESTNET) { + const { proxyImplementationHook } = this.contexts[status.config.version] + if (proxyImplementationHook && (chainId === ChainId.IMMUTABLE_ZKEVM || chainId === ChainId.IMMUTABLE_ZKEVM_TESTNET)) { const provider = this.providerFor(chainId) if (provider) { const hook = new ethers.Contract(this.address, walletContracts.walletProxyHook.abi, provider) @@ -431,7 +432,7 @@ export class Account { to: this.address, data: hooksInterface.encodeFunctionData(hooksInterface.getFunction('addHook')!, [ '0x90611127', - '0x1f56dbAD5e8319F0DE9a323E24A31b5077dEB1a4' + proxyImplementationHook, ]), gasLimit: 50000, // Expected ~28k gas. Buffer added delegateCall: false, diff --git a/packages/core/src/commons/context.ts b/packages/core/src/commons/context.ts index 7794efae9..6bad5cb1b 100644 --- a/packages/core/src/commons/context.ts +++ b/packages/core/src/commons/context.ts @@ -12,6 +12,8 @@ export type WalletContext = { guestModule: string walletCreationCode: string + + proxyImplementationHook?: string; } export function addressOf(context: WalletContext, imageHash: ethers.BytesLike) { diff --git a/packages/core/src/v2/index.ts b/packages/core/src/v2/index.ts index f921265a4..98b12e1f0 100644 --- a/packages/core/src/v2/index.ts +++ b/packages/core/src/v2/index.ts @@ -21,5 +21,6 @@ export const DeployedWalletContext: WalletContext = { guestModule: '0xfea230Ee243f88BC698dD8f1aE93F8301B6cdfaE', mainModule: '0xfBf8f1A5E00034762D928f46d438B947f5d4065d', mainModuleUpgradable: '0x4222dcA3974E39A8b41c411FeDDE9b09Ae14b911', - walletCreationCode: '0x603a600e3d39601a805130553df3363d3d373d3d3d363d30545af43d82803e903d91601857fd5bf3' + walletCreationCode: '0x603a600e3d39601a805130553df3363d3d373d3d3d363d30545af43d82803e903d91601857fd5bf3', + proxyImplementationHook: '0x1f56dbAD5e8319F0DE9a323E24A31b5077dEB1a4', } diff --git a/packages/tests/src/context/v2.ts b/packages/tests/src/context/v2.ts index d30c9e6de..2e06deb4f 100644 --- a/packages/tests/src/context/v2.ts +++ b/packages/tests/src/context/v2.ts @@ -43,6 +43,7 @@ export async function deployV2Context(signer: ethers.Signer): Promise