From 275c39519db3987af8f1a446b03b429723ca7f3f Mon Sep 17 00:00:00 2001 From: Michael Standen Date: Thu, 19 Dec 2024 14:32:59 +1300 Subject: [PATCH] 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