diff --git a/src/actions/buildAddExecutor.ts b/src/actions/buildAddExecutor.ts new file mode 100644 index 00000000..f77d60d7 --- /dev/null +++ b/src/actions/buildAddExecutor.ts @@ -0,0 +1,43 @@ +import { Address, Chain, PrepareTransactionRequestParameters, PublicClient, Transport } from 'viem'; +import { upgradeExecutorABI } from '../contracts/UpgradeExecutor'; +import { + ActionParameters, + PrepareTransactionRequestReturnTypeWithChainId, + WithAccount, +} from '../types/Actions'; +import { Prettify } from '../types/utils'; +import { UPGRADE_EXECUTOR_ROLE_EXECUTOR } from '../upgradeExecutorEncodeFunctionData'; +import { prepareUpgradeExecutorCallParameters } from '../prepareUpgradeExecutorCallParameters'; + +export type BuildAddExecutorParameters = Prettify< + WithAccount< + ActionParameters< + { + address: Address; + }, + 'upgradeExecutor', + Curried + > + > +>; + +export type BuildAddExecutorReturnType = PrepareTransactionRequestReturnTypeWithChainId; + +export async function buildAddExecutor( + client: PublicClient, + { account, upgradeExecutor: upgradeExecutorAddress, params }: BuildAddExecutorParameters, +): Promise { + const request = await client.prepareTransactionRequest({ + chain: client.chain as Chain | undefined, + account, + ...prepareUpgradeExecutorCallParameters({ + to: upgradeExecutorAddress, + upgradeExecutor: upgradeExecutorAddress, + args: [UPGRADE_EXECUTOR_ROLE_EXECUTOR, params.address], + abi: upgradeExecutorABI, + functionName: 'grantRole', + }), + } satisfies PrepareTransactionRequestParameters); + + return { ...request, chainId: client.chain.id }; +} diff --git a/src/actions/buildRemoveExecutor.ts b/src/actions/buildRemoveExecutor.ts new file mode 100644 index 00000000..25e84769 --- /dev/null +++ b/src/actions/buildRemoveExecutor.ts @@ -0,0 +1,43 @@ +import { Address, Chain, PrepareTransactionRequestParameters, PublicClient, Transport } from 'viem'; +import { upgradeExecutorABI } from '../contracts/UpgradeExecutor'; +import { + ActionParameters, + PrepareTransactionRequestReturnTypeWithChainId, + WithAccount, +} from '../types/Actions'; +import { Prettify } from '../types/utils'; +import { UPGRADE_EXECUTOR_ROLE_EXECUTOR } from '../upgradeExecutorEncodeFunctionData'; +import { prepareUpgradeExecutorCallParameters } from '../prepareUpgradeExecutorCallParameters'; + +export type BuildRemoveExecutorParameters = Prettify< + WithAccount< + ActionParameters< + { + address: Address; + }, + 'upgradeExecutor', + Curried + > + > +>; + +export type BuildRemoveExecutorReturnType = PrepareTransactionRequestReturnTypeWithChainId; + +export async function buildRemoveExecutor( + client: PublicClient, + { account, upgradeExecutor: upgradeExecutorAddress, params }: BuildRemoveExecutorParameters, +): Promise { + const request = await client.prepareTransactionRequest({ + chain: client.chain as Chain | undefined, + account, + ...prepareUpgradeExecutorCallParameters({ + to: upgradeExecutorAddress, + upgradeExecutor: upgradeExecutorAddress, + args: [UPGRADE_EXECUTOR_ROLE_EXECUTOR, params.address], + abi: upgradeExecutorABI, + functionName: 'revokeRole', + }), + } satisfies PrepareTransactionRequestParameters); + + return { ...request, chainId: client.chain.id }; +} diff --git a/src/actions/hasRole.ts b/src/actions/hasRole.ts new file mode 100644 index 00000000..ef0a0c1a --- /dev/null +++ b/src/actions/hasRole.ts @@ -0,0 +1,27 @@ +import { Address, Chain, PublicClient, ReadContractReturnType, Transport } from 'viem'; +import { upgradeExecutorABI } from '../contracts/UpgradeExecutor'; +import { UpgradeExecutorRole } from '../upgradeExecutorEncodeFunctionData'; +import { ActionParameters } from '../types/Actions'; + +export type HasRoleParameters = ActionParameters< + { + role: UpgradeExecutorRole; + address: Address; + }, + 'upgradeExecutor', + Curried +>; + +export type HasRoleReturnType = ReadContractReturnType; + +export async function hasRole( + client: PublicClient, + { upgradeExecutor, params }: HasRoleParameters, +): Promise { + return client.readContract({ + abi: upgradeExecutorABI, + functionName: 'hasRole', + address: upgradeExecutor, + args: [params.role, params.address], + }); +} diff --git a/src/actions/upgradeExecutor.integration.test.ts b/src/actions/upgradeExecutor.integration.test.ts new file mode 100644 index 00000000..0d8e8241 --- /dev/null +++ b/src/actions/upgradeExecutor.integration.test.ts @@ -0,0 +1,93 @@ +import { describe, it, expect } from 'vitest'; +import { getInformationFromTestnode, getNitroTestnodePrivateKeyAccounts } from '../testHelpers'; +import { Address, createPublicClient, http } from 'viem'; +import { nitroTestnodeL2 } from '../chains'; +import { generatePrivateKey, privateKeyToAccount } from 'viem/accounts'; +import { upgradeExecutorFetchPrivilegedAccounts } from '../upgradeExecutorFetchPrivilegedAccounts'; +import { buildAddExecutor } from './buildAddExecutor'; +import { UPGRADE_EXECUTOR_ROLE_EXECUTOR } from '../upgradeExecutorEncodeFunctionData'; +import { buildRemoveExecutor } from './buildRemoveExecutor'; +import { hasRole } from './hasRole'; + +const { l3UpgradeExecutor } = getInformationFromTestnode(); +const { l3RollupOwner } = getNitroTestnodePrivateKeyAccounts(); + +const client = createPublicClient({ + chain: nitroTestnodeL2, + transport: http(), +}); + +describe('Upgrade executor', () => { + it('buildAddExecutor and buildRemoveExecutor update upgradeExecutor', async () => { + async function changeUpgradeExecutor(address: Address, add: boolean) { + const fn = add ? buildAddExecutor : buildRemoveExecutor; + const transactionRequest = await fn(client, { + account: l3RollupOwner.address, + upgradeExecutor: l3UpgradeExecutor, + params: { + address, + }, + }); + const txHash = await client.sendRawTransaction({ + serializedTransaction: await l3RollupOwner.signTransaction(transactionRequest), + }); + await client.waitForTransactionReceipt({ hash: txHash }); + } + const addUpgradeExecutor = (address: Address) => changeUpgradeExecutor(address, true); + const removeUpgradeExecutor = (address: Address) => changeUpgradeExecutor(address, false); + + const randomAddress = privateKeyToAccount(generatePrivateKey()).address; + + const initialState = await upgradeExecutorFetchPrivilegedAccounts({ + upgradeExecutorAddress: l3UpgradeExecutor, + publicClient: client, + }); + + expect( + await hasRole(client, { + params: { + role: UPGRADE_EXECUTOR_ROLE_EXECUTOR, + address: randomAddress, + }, + upgradeExecutor: l3UpgradeExecutor, + }), + ).toBeFalsy(); + + await addUpgradeExecutor(randomAddress); + expect( + await upgradeExecutorFetchPrivilegedAccounts({ + upgradeExecutorAddress: l3UpgradeExecutor, + publicClient: client, + }), + ).toEqual({ + ...initialState, + [randomAddress]: [UPGRADE_EXECUTOR_ROLE_EXECUTOR], + }); + expect( + await hasRole(client, { + params: { + role: UPGRADE_EXECUTOR_ROLE_EXECUTOR, + address: randomAddress, + }, + upgradeExecutor: l3UpgradeExecutor, + }), + ).toBeTruthy(); + + await removeUpgradeExecutor(randomAddress); + expect( + await upgradeExecutorFetchPrivilegedAccounts({ + upgradeExecutorAddress: l3UpgradeExecutor, + publicClient: client, + }), + ).toEqual(initialState); + expect( + await hasRole(client, { + params: { + role: UPGRADE_EXECUTOR_ROLE_EXECUTOR, + address: randomAddress, + }, + upgradeExecutor: l3UpgradeExecutor, + }), + ).toBeFalsy(); + }); +}); diff --git a/src/prepareUpgradeExecutorCallParameters.ts b/src/prepareUpgradeExecutorCallParameters.ts index dcde21c0..617995d5 100644 --- a/src/prepareUpgradeExecutorCallParameters.ts +++ b/src/prepareUpgradeExecutorCallParameters.ts @@ -10,8 +10,9 @@ import { upgradeExecutorEncodeFunctionData, UpgradeExecutorFunctionName, } from './upgradeExecutorEncodeFunctionData'; +import { upgradeExecutorABI } from './contracts/UpgradeExecutor'; -type ABIs = typeof sequencerInboxABI | typeof arbOwnerABI; +type ABIs = typeof sequencerInboxABI | typeof arbOwnerABI | typeof upgradeExecutorABI; type FunctionName = GetFunctionName; type EncodeFunctionDataParameters<