From 4159c1b4eee81f9d0c9669412685dd0ef8a1bcd3 Mon Sep 17 00:00:00 2001 From: Goran Vladika Date: Mon, 15 Apr 2024 16:21:23 +0200 Subject: [PATCH 01/11] Add missing await --- scripts/atomicTokenBridgeDeployer.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/atomicTokenBridgeDeployer.ts b/scripts/atomicTokenBridgeDeployer.ts index c549c5280..8f0c13feb 100644 --- a/scripts/atomicTokenBridgeDeployer.ts +++ b/scripts/atomicTokenBridgeDeployer.ts @@ -362,6 +362,7 @@ export const deployL1TokenBridgeCreator = async ( l1Deployer ) const upgradeExecutor = await upgradeExecutorFactory.deploy() + await upgradeExecutor.deployed() const l1Templates = { routerTemplate: routerTemplate.address, From 84be29adc779e5a28eef0717bac78b387bedbcb9 Mon Sep 17 00:00:00 2001 From: Goran Vladika Date: Tue, 16 Apr 2024 16:16:55 +0200 Subject: [PATCH 02/11] Add orbit template to hardhat config --- hardhat.config.ts | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/hardhat.config.ts b/hardhat.config.ts index d2b790bc1..4f1d586db 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -143,6 +143,9 @@ const config = { }, timeout: 100000, }, + orbit: { + url: 'http://127.0.0.1:8547', + }, }, typechain: { outDir: 'build/types', @@ -179,6 +182,7 @@ const config = { nova: process.env['NOVA_ARBISCAN_API_KEY'], arbGoerliRollup: process.env['ARBISCAN_API_KEY'], arbSepoliaRollup: 'x', + orbit: 'x', }, customChains: [ { @@ -201,10 +205,19 @@ const config = { network: 'arbSepoliaRollup', chainId: 421614, urls: { - apiURL: 'https://sepolia-explorer.arbitrum.io/api?module=contract&action=verify', + apiURL: + 'https://sepolia-explorer.arbitrum.io/api?module=contract&action=verify', browserURL: 'https://sepolia-explorer.arbitrum.io/', }, - } + }, + { + network: 'orbit', + chainId: 412346, + urls: { + apiURL: 'http://127.0.0.1:4000/api?module=contract&action=verify', + browserURL: 'http://127.0.0.1:4000/', + }, + }, ], }, } From 2740f72078ecd187f236f95c817f3af07a556aaa Mon Sep 17 00:00:00 2001 From: Goran Vladika Date: Tue, 16 Apr 2024 16:17:33 +0200 Subject: [PATCH 03/11] Draft script --- package.json | 1 + scripts/orbitVerifyOnBlockscout.ts | 122 +++++++++++++++++++++++++++++ 2 files changed, 123 insertions(+) create mode 100644 scripts/orbitVerifyOnBlockscout.ts diff --git a/package.json b/package.json index e80ec2448..92b888487 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,7 @@ "create:token-bridge": "ts-node ./scripts/deployment/createTokenBridge.ts", "test:tokenbridge:deployment": "hardhat test test-e2e/tokenBridgeDeploymentTest.ts", "test:creation-code": "hardhat test test-e2e/creationCodeTest.ts", + "blockscout:verify": "hardhat run ./scripts/orbitVerifyOnBlockscout.ts", "typechain": "hardhat typechain", "deploy:tokenbridge": "hardhat run scripts/deploy_token_bridge_l1.ts --network mainnet", "gen:uml": "sol2uml ./contracts/tokenbridge/arbitrum,./contracts/tokenbridge/ethereum,./contracts/tokenbridge/libraries -o ./gatewayUML.svg", diff --git a/scripts/orbitVerifyOnBlockscout.ts b/scripts/orbitVerifyOnBlockscout.ts new file mode 100644 index 000000000..f320851b2 --- /dev/null +++ b/scripts/orbitVerifyOnBlockscout.ts @@ -0,0 +1,122 @@ +import { ethers } from 'hardhat' +import { run } from 'hardhat' +import { L1AtomicTokenBridgeCreator__factory } from '../build/types' +import { Provider } from '@ethersproject/providers' + +main().then(() => console.log('Done.')) + +async function main() { + const parentRpcUrl = process.env['PARENT_RPC'] as string + const tokenBridgeCreatorAddress = process.env[ + 'TOKEN_BRIDGE_CREATOR' + ] as string + const inboxAddress = process.env['INBOX_ADDRESS'] as string + if (!parentRpcUrl || !tokenBridgeCreatorAddress || !inboxAddress) { + throw new Error( + 'Missing required environment variables PARENT_RPC, L1_TOKEN_BRIDGE_CREATOR and INBOX_ADDRESS' + ) + } + + const parentProvider = new ethers.providers.JsonRpcProvider(parentRpcUrl) + const tokenBridgeCreator = L1AtomicTokenBridgeCreator__factory.connect( + tokenBridgeCreatorAddress, + parentProvider + ) + const l2Factory = await tokenBridgeCreator.canonicalL2FactoryAddress() + const l2Deployment = await tokenBridgeCreator.inboxToL2Deployment( + inboxAddress + ) + + console.log( + 'Start verification of token bridge contracts deployed to chain', + (await parentProvider.getNetwork()).chainId + ) + + await verifyContract('L2AtomicTokenBridgeFactory', l2Factory, []) + + await verifyContract( + 'L2ERC20Gateway', + await _getLogicAddress(l2Deployment.standardGateway, parentProvider), + [] + ) + await verifyContract( + 'L2CustomGateway', + await _getLogicAddress(l2Deployment.customGateway, parentProvider), + [] + ) + await verifyContract( + 'L2GatewayRouter', + await _getLogicAddress(l2Deployment.router, parentProvider), + [] + ) +} + +async function verifyContract( + contractName: string, + contractAddress: string, + constructorArguments: any[] = [], + contractPathAndName?: string // optional +): Promise { + try { + // Define the verification options with possible 'contract' property + const verificationOptions: { + contract?: string + address: string + constructorArguments: any[] + } = { + address: contractAddress, + constructorArguments: constructorArguments, + } + + // if contractPathAndName is provided, add it to the verification options + if (contractPathAndName) { + verificationOptions.contract = contractPathAndName + } + + await run('verify:verify', verificationOptions) + console.log(`Verified contract ${contractName} successfully.`) + } catch (error: any) { + if (error.message.includes('Already Verified')) { + console.log(`Contract ${contractName} is already verified.`) + } else { + console.error( + `Verification for ${contractName} failed with the following error: ${error.message}` + ) + } + } +} + +async function _getLogicAddress( + contractAddress: string, + provider: Provider +): Promise { + return ( + await _getAddressAtStorageSlot( + contractAddress, + provider, + '0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc' + ) + ).toLowerCase() +} + +async function _getAddressAtStorageSlot( + contractAddress: string, + provider: Provider, + storageSlotBytes: string +): Promise { + const storageValue = await provider.getStorageAt( + contractAddress, + storageSlotBytes + ) + + if (!storageValue) { + return '' + } + + // remove excess bytes + const formatAddress = + storageValue.substring(0, 2) + storageValue.substring(26) + + // return address as checksum address + return ethers.utils.getAddress(formatAddress) +} From f2a7cc9b2e22520e7616d507e3d77ca9da8e18d9 Mon Sep 17 00:00:00 2001 From: Goran Vladika Date: Tue, 16 Apr 2024 18:17:44 +0200 Subject: [PATCH 04/11] Verify TUP --- scripts/orbitVerifyOnBlockscout.ts | 44 ++++++++++++++++++++++-------- 1 file changed, 33 insertions(+), 11 deletions(-) diff --git a/scripts/orbitVerifyOnBlockscout.ts b/scripts/orbitVerifyOnBlockscout.ts index f320851b2..810408225 100644 --- a/scripts/orbitVerifyOnBlockscout.ts +++ b/scripts/orbitVerifyOnBlockscout.ts @@ -11,13 +11,16 @@ async function main() { 'TOKEN_BRIDGE_CREATOR' ] as string const inboxAddress = process.env['INBOX_ADDRESS'] as string + if (!parentRpcUrl || !tokenBridgeCreatorAddress || !inboxAddress) { throw new Error( - 'Missing required environment variables PARENT_RPC, L1_TOKEN_BRIDGE_CREATOR and INBOX_ADDRESS' + 'Missing required environment variables PARENT_RPC, TOKEN_BRIDGE_CREATOR and INBOX_ADDRESS' ) } const parentProvider = new ethers.providers.JsonRpcProvider(parentRpcUrl) + const orbitProvider = ethers.provider + const tokenBridgeCreator = L1AtomicTokenBridgeCreator__factory.connect( tokenBridgeCreatorAddress, parentProvider @@ -29,29 +32,48 @@ async function main() { console.log( 'Start verification of token bridge contracts deployed to chain', - (await parentProvider.getNetwork()).chainId + (await orbitProvider.getNetwork()).chainId ) - await verifyContract('L2AtomicTokenBridgeFactory', l2Factory, []) + await _verifyContract('L2AtomicTokenBridgeFactory', l2Factory, []) - await verifyContract( + // verify single TUP, others TUPs will be verified by bytecode match + await _verifyContract('TransparentUpgradeableProxy', l2Deployment.router, [ + l2Factory, + l2Deployment.proxyAdmin, + '0x', + ]) + await _verifyContract( + 'L2GatewayRouter', + await _getLogicAddress(l2Deployment.router, orbitProvider), + [] + ) + await _verifyContract( 'L2ERC20Gateway', - await _getLogicAddress(l2Deployment.standardGateway, parentProvider), + await _getLogicAddress(l2Deployment.standardGateway, orbitProvider), [] ) - await verifyContract( + await _verifyContract( 'L2CustomGateway', - await _getLogicAddress(l2Deployment.customGateway, parentProvider), + await _getLogicAddress(l2Deployment.customGateway, orbitProvider), [] ) - await verifyContract( - 'L2GatewayRouter', - await _getLogicAddress(l2Deployment.router, parentProvider), + await _verifyContract( + 'L2WethGateway', + await _getLogicAddress(l2Deployment.wethGateway, orbitProvider), [] ) + await _verifyContract('ArbMulticall2', l2Deployment.multicall, []) + await _verifyContract('ProxyAdmin', l2Deployment.proxyAdmin, []) + + // await _verifyContract( + // 'aeWETH', + // await _getLogicAddress(l2Deployment.weth, orbitProvider), + // [] + // ) } -async function verifyContract( +async function _verifyContract( contractName: string, contractAddress: string, constructorArguments: any[] = [], From bccfd0bf1ab9d296659524e708891a1365801ad4 Mon Sep 17 00:00:00 2001 From: Goran Vladika Date: Tue, 16 Apr 2024 18:37:10 +0200 Subject: [PATCH 05/11] Verify beacon and arb erc20 --- scripts/orbitVerifyOnBlockscout.ts | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/scripts/orbitVerifyOnBlockscout.ts b/scripts/orbitVerifyOnBlockscout.ts index 810408225..9588a7d50 100644 --- a/scripts/orbitVerifyOnBlockscout.ts +++ b/scripts/orbitVerifyOnBlockscout.ts @@ -1,6 +1,10 @@ import { ethers } from 'hardhat' import { run } from 'hardhat' -import { L1AtomicTokenBridgeCreator__factory } from '../build/types' +import { + BeaconProxyFactory__factory, + L1AtomicTokenBridgeCreator__factory, + UpgradeableBeacon__factory, +} from '../build/types' import { Provider } from '@ethersproject/providers' main().then(() => console.log('Done.')) @@ -21,6 +25,7 @@ async function main() { const parentProvider = new ethers.providers.JsonRpcProvider(parentRpcUrl) const orbitProvider = ethers.provider + /// collect addresses const tokenBridgeCreator = L1AtomicTokenBridgeCreator__factory.connect( tokenBridgeCreatorAddress, parentProvider @@ -29,12 +34,22 @@ async function main() { const l2Deployment = await tokenBridgeCreator.inboxToL2Deployment( inboxAddress ) + const beaconProxyFactory = BeaconProxyFactory__factory.connect( + l2Deployment.beaconProxyFactory, + orbitProvider + ) + const upgradeableBeacon = UpgradeableBeacon__factory.connect( + await beaconProxyFactory.beacon(), + orbitProvider + ) + const standardArbERC20 = await upgradeableBeacon.implementation() console.log( 'Start verification of token bridge contracts deployed to chain', (await orbitProvider.getNetwork()).chainId ) + // verify L2 factory await _verifyContract('L2AtomicTokenBridgeFactory', l2Factory, []) // verify single TUP, others TUPs will be verified by bytecode match @@ -43,6 +58,8 @@ async function main() { l2Deployment.proxyAdmin, '0x', ]) + + // verify orbit contracts await _verifyContract( 'L2GatewayRouter', await _getLogicAddress(l2Deployment.router, orbitProvider), @@ -63,6 +80,11 @@ async function main() { await _getLogicAddress(l2Deployment.wethGateway, orbitProvider), [] ) + await _verifyContract('BeaconProxyFactory', beaconProxyFactory.address, []) + await _verifyContract('UpgradeableBeacon', upgradeableBeacon.address, [ + standardArbERC20, + ]) + await _verifyContract('StandardArbERC20', standardArbERC20, []) await _verifyContract('ArbMulticall2', l2Deployment.multicall, []) await _verifyContract('ProxyAdmin', l2Deployment.proxyAdmin, []) From 884da42e5c422a6acc034f5597c1ddc994189221 Mon Sep 17 00:00:00 2001 From: Goran Vladika Date: Tue, 16 Apr 2024 18:49:11 +0200 Subject: [PATCH 06/11] Deploy and verify dummy aeWeth --- scripts/orbitVerifyOnBlockscout.ts | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/scripts/orbitVerifyOnBlockscout.ts b/scripts/orbitVerifyOnBlockscout.ts index 9588a7d50..726cd738b 100644 --- a/scripts/orbitVerifyOnBlockscout.ts +++ b/scripts/orbitVerifyOnBlockscout.ts @@ -1,6 +1,7 @@ import { ethers } from 'hardhat' import { run } from 'hardhat' import { + AeWETH__factory, BeaconProxyFactory__factory, L1AtomicTokenBridgeCreator__factory, UpgradeableBeacon__factory, @@ -15,15 +16,22 @@ async function main() { 'TOKEN_BRIDGE_CREATOR' ] as string const inboxAddress = process.env['INBOX_ADDRESS'] as string - - if (!parentRpcUrl || !tokenBridgeCreatorAddress || !inboxAddress) { + const deployerKey = process.env['DEPLOYER_KEY'] as string + + if ( + !parentRpcUrl || + !tokenBridgeCreatorAddress || + !inboxAddress || + !deployerKey + ) { throw new Error( - 'Missing required environment variables PARENT_RPC, TOKEN_BRIDGE_CREATOR and INBOX_ADDRESS' + 'Required env vars: PARENT_RPC, TOKEN_BRIDGE_CREATOR, INBOX_ADDRESS and DEPLOYER_KEY' ) } const parentProvider = new ethers.providers.JsonRpcProvider(parentRpcUrl) const orbitProvider = ethers.provider + const deployerOnOrbit = new ethers.Wallet(deployerKey, orbitProvider) /// collect addresses const tokenBridgeCreator = L1AtomicTokenBridgeCreator__factory.connect( @@ -88,11 +96,10 @@ async function main() { await _verifyContract('ArbMulticall2', l2Deployment.multicall, []) await _verifyContract('ProxyAdmin', l2Deployment.proxyAdmin, []) - // await _verifyContract( - // 'aeWETH', - // await _getLogicAddress(l2Deployment.weth, orbitProvider), - // [] - // ) + /// special cases - aeWETH and UpgradeExecutor + const dummyAeWethFac = await new AeWETH__factory(deployerOnOrbit).deploy() + const dummyAeWeth = await dummyAeWethFac.deployed() + await _verifyContract('aeWETH', dummyAeWeth.address, []) } async function _verifyContract( From c191f8762837f982b6ae040b458724671ef5d9fe Mon Sep 17 00:00:00 2001 From: Goran Vladika Date: Thu, 18 Apr 2024 14:59:33 +0200 Subject: [PATCH 07/11] Use workaround to deploy and verify UpgradeExecutor --- .../test/UpgradeExecutorForVerification.sol | 1223 +++++++++++++++++ hardhat.config.ts | 12 +- scripts/orbitVerifyOnBlockscout.ts | 39 +- 3 files changed, 1263 insertions(+), 11 deletions(-) create mode 100644 contracts/tokenbridge/test/UpgradeExecutorForVerification.sol diff --git a/contracts/tokenbridge/test/UpgradeExecutorForVerification.sol b/contracts/tokenbridge/test/UpgradeExecutorForVerification.sol new file mode 100644 index 000000000..15a313ba3 --- /dev/null +++ b/contracts/tokenbridge/test/UpgradeExecutorForVerification.sol @@ -0,0 +1,1223 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity <0.9.0 =0.8.16 >=0.6.2 ^0.8.0 ^0.8.1 ^0.8.2; + +// node_modules/@openzeppelin/contracts/security/ReentrancyGuard.sol + +// OpenZeppelin Contracts v4.4.1 (security/ReentrancyGuard.sol) + +/** + * @dev Contract module that helps prevent reentrant calls to a function. + * + * Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier + * available, which can be applied to functions to make sure there are no nested + * (reentrant) calls to them. + * + * Note that because there is a single `nonReentrant` guard, functions marked as + * `nonReentrant` may not call one another. This can be worked around by making + * those functions `private`, and then adding `external` `nonReentrant` entry + * points to them. + * + * TIP: If you would like to learn more about reentrancy and alternative ways + * to protect against it, check out our blog post + * https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul]. + */ +abstract contract ReentrancyGuard { + // Booleans are more expensive than uint256 or any type that takes up a full + // word because each write operation emits an extra SLOAD to first read the + // slot's contents, replace the bits taken up by the boolean, and then write + // back. This is the compiler's defense against contract upgrades and + // pointer aliasing, and it cannot be disabled. + + // The values being non-zero value makes deployment a bit more expensive, + // but in exchange the refund on every call to nonReentrant will be lower in + // amount. Since refunds are capped to a percentage of the total + // transaction's gas, it is best to keep them low in cases like this one, to + // increase the likelihood of the full refund coming into effect. + uint256 private constant _NOT_ENTERED = 1; + uint256 private constant _ENTERED = 2; + + uint256 private _status; + + constructor() { + _status = _NOT_ENTERED; + } + + /** + * @dev Prevents a contract from calling itself, directly or indirectly. + * Calling a `nonReentrant` function from another `nonReentrant` + * function is not supported. It is possible to prevent this from happening + * by making the `nonReentrant` function external, and making it call a + * `private` function that does the actual work. + */ + modifier nonReentrant() { + // On the first call to nonReentrant, _notEntered will be true + require(_status != _ENTERED, "ReentrancyGuard: reentrant call"); + + // Any calls to nonReentrant after this point will fail + _status = _ENTERED; + + _; + + // By storing the original value once again, a refund is triggered (see + // https://eips.ethereum.org/EIPS/eip-2200) + _status = _NOT_ENTERED; + } +} + +// node_modules/@openzeppelin/contracts/utils/Address.sol + +// OpenZeppelin Contracts (last updated v4.7.0) (utils/Address.sol) + +/** + * @dev Collection of functions related to the address type + */ +library Address { + /** + * @dev Returns true if `account` is a contract. + * + * [IMPORTANT] + * ==== + * It is unsafe to assume that an address for which this function returns + * false is an externally-owned account (EOA) and not a contract. + * + * Among others, `isContract` will return false for the following + * types of addresses: + * + * - an externally-owned account + * - a contract in construction + * - an address where a contract will be created + * - an address where a contract lived, but was destroyed + * ==== + * + * [IMPORTANT] + * ==== + * You shouldn't rely on `isContract` to protect against flash loan attacks! + * + * Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets + * like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract + * constructor. + * ==== + */ + function isContract(address account) internal view returns (bool) { + // This method relies on extcodesize/address.code.length, which returns 0 + // for contracts in construction, since the code is only stored at the end + // of the constructor execution. + + return account.code.length > 0; + } + + /** + * @dev Replacement for Solidity's `transfer`: sends `amount` wei to + * `recipient`, forwarding all available gas and reverting on errors. + * + * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost + * of certain opcodes, possibly making contracts go over the 2300 gas limit + * imposed by `transfer`, making them unable to receive funds via + * `transfer`. {sendValue} removes this limitation. + * + * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more]. + * + * IMPORTANT: because control is transferred to `recipient`, care must be + * taken to not create reentrancy vulnerabilities. Consider using + * {ReentrancyGuard} or the + * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern]. + */ + function sendValue(address payable recipient, uint256 amount) internal { + require(address(this).balance >= amount, "Address: insufficient balance"); + + (bool success, ) = recipient.call{value: amount}(""); + require(success, "Address: unable to send value, recipient may have reverted"); + } + + /** + * @dev Performs a Solidity function call using a low level `call`. A + * plain `call` is an unsafe replacement for a function call: use this + * function instead. + * + * If `target` reverts with a revert reason, it is bubbled up by this + * function (like regular Solidity function calls). + * + * Returns the raw returned data. To convert to the expected return value, + * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`]. + * + * Requirements: + * + * - `target` must be a contract. + * - calling `target` with `data` must not revert. + * + * _Available since v3.1._ + */ + function functionCall(address target, bytes memory data) internal returns (bytes memory) { + return functionCall(target, data, "Address: low-level call failed"); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with + * `errorMessage` as a fallback revert reason when `target` reverts. + * + * _Available since v3.1._ + */ + function functionCall( + address target, + bytes memory data, + string memory errorMessage + ) internal returns (bytes memory) { + return functionCallWithValue(target, data, 0, errorMessage); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], + * but also transferring `value` wei to `target`. + * + * Requirements: + * + * - the calling contract must have an ETH balance of at least `value`. + * - the called Solidity function must be `payable`. + * + * _Available since v3.1._ + */ + function functionCallWithValue( + address target, + bytes memory data, + uint256 value + ) internal returns (bytes memory) { + return functionCallWithValue(target, data, value, "Address: low-level call with value failed"); + } + + /** + * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but + * with `errorMessage` as a fallback revert reason when `target` reverts. + * + * _Available since v3.1._ + */ + function functionCallWithValue( + address target, + bytes memory data, + uint256 value, + string memory errorMessage + ) internal returns (bytes memory) { + require(address(this).balance >= value, "Address: insufficient balance for call"); + require(isContract(target), "Address: call to non-contract"); + + (bool success, bytes memory returndata) = target.call{value: value}(data); + return verifyCallResult(success, returndata, errorMessage); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], + * but performing a static call. + * + * _Available since v3.3._ + */ + function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) { + return functionStaticCall(target, data, "Address: low-level static call failed"); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`], + * but performing a static call. + * + * _Available since v3.3._ + */ + function functionStaticCall( + address target, + bytes memory data, + string memory errorMessage + ) internal view returns (bytes memory) { + require(isContract(target), "Address: static call to non-contract"); + + (bool success, bytes memory returndata) = target.staticcall(data); + return verifyCallResult(success, returndata, errorMessage); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], + * but performing a delegate call. + * + * _Available since v3.4._ + */ + function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) { + return functionDelegateCall(target, data, "Address: low-level delegate call failed"); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`], + * but performing a delegate call. + * + * _Available since v3.4._ + */ + function functionDelegateCall( + address target, + bytes memory data, + string memory errorMessage + ) internal returns (bytes memory) { + require(isContract(target), "Address: delegate call to non-contract"); + + (bool success, bytes memory returndata) = target.delegatecall(data); + return verifyCallResult(success, returndata, errorMessage); + } + + /** + * @dev Tool to verifies that a low level call was successful, and revert if it wasn't, either by bubbling the + * revert reason using the provided one. + * + * _Available since v4.3._ + */ + function verifyCallResult( + bool success, + bytes memory returndata, + string memory errorMessage + ) internal pure returns (bytes memory) { + if (success) { + return returndata; + } else { + // Look for revert reason and bubble it up if present + if (returndata.length > 0) { + // The easiest way to bubble the revert reason is using memory via assembly + /// @solidity memory-safe-assembly + assembly { + let returndata_size := mload(returndata) + revert(add(32, returndata), returndata_size) + } + } else { + revert(errorMessage); + } + } + } +} + +// node_modules/@openzeppelin/contracts-upgradeable/access/IAccessControlUpgradeable.sol + +// OpenZeppelin Contracts v4.4.1 (access/IAccessControl.sol) + +/** + * @dev External interface of AccessControl declared to support ERC165 detection. + */ +interface IAccessControlUpgradeable { + /** + * @dev Emitted when `newAdminRole` is set as ``role``'s admin role, replacing `previousAdminRole` + * + * `DEFAULT_ADMIN_ROLE` is the starting admin for all roles, despite + * {RoleAdminChanged} not being emitted signaling this. + * + * _Available since v3.1._ + */ + event RoleAdminChanged(bytes32 indexed role, bytes32 indexed previousAdminRole, bytes32 indexed newAdminRole); + + /** + * @dev Emitted when `account` is granted `role`. + * + * `sender` is the account that originated the contract call, an admin role + * bearer except when using {AccessControl-_setupRole}. + */ + event RoleGranted(bytes32 indexed role, address indexed account, address indexed sender); + + /** + * @dev Emitted when `account` is revoked `role`. + * + * `sender` is the account that originated the contract call: + * - if using `revokeRole`, it is the admin role bearer + * - if using `renounceRole`, it is the role bearer (i.e. `account`) + */ + event RoleRevoked(bytes32 indexed role, address indexed account, address indexed sender); + + /** + * @dev Returns `true` if `account` has been granted `role`. + */ + function hasRole(bytes32 role, address account) external view returns (bool); + + /** + * @dev Returns the admin role that controls `role`. See {grantRole} and + * {revokeRole}. + * + * To change a role's admin, use {AccessControl-_setRoleAdmin}. + */ + function getRoleAdmin(bytes32 role) external view returns (bytes32); + + /** + * @dev Grants `role` to `account`. + * + * If `account` had not been already granted `role`, emits a {RoleGranted} + * event. + * + * Requirements: + * + * - the caller must have ``role``'s admin role. + */ + function grantRole(bytes32 role, address account) external; + + /** + * @dev Revokes `role` from `account`. + * + * If `account` had been granted `role`, emits a {RoleRevoked} event. + * + * Requirements: + * + * - the caller must have ``role``'s admin role. + */ + function revokeRole(bytes32 role, address account) external; + + /** + * @dev Revokes `role` from the calling account. + * + * Roles are often managed via {grantRole} and {revokeRole}: this function's + * purpose is to provide a mechanism for accounts to lose their privileges + * if they are compromised (such as when a trusted device is misplaced). + * + * If the calling account had been granted `role`, emits a {RoleRevoked} + * event. + * + * Requirements: + * + * - the caller must be `account`. + */ + function renounceRole(bytes32 role, address account) external; +} + +// node_modules/@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol + +// OpenZeppelin Contracts (last updated v4.7.0) (utils/Address.sol) + +/** + * @dev Collection of functions related to the address type + */ +library AddressUpgradeable { + /** + * @dev Returns true if `account` is a contract. + * + * [IMPORTANT] + * ==== + * It is unsafe to assume that an address for which this function returns + * false is an externally-owned account (EOA) and not a contract. + * + * Among others, `isContract` will return false for the following + * types of addresses: + * + * - an externally-owned account + * - a contract in construction + * - an address where a contract will be created + * - an address where a contract lived, but was destroyed + * ==== + * + * [IMPORTANT] + * ==== + * You shouldn't rely on `isContract` to protect against flash loan attacks! + * + * Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets + * like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract + * constructor. + * ==== + */ + function isContract(address account) internal view returns (bool) { + // This method relies on extcodesize/address.code.length, which returns 0 + // for contracts in construction, since the code is only stored at the end + // of the constructor execution. + + return account.code.length > 0; + } + + /** + * @dev Replacement for Solidity's `transfer`: sends `amount` wei to + * `recipient`, forwarding all available gas and reverting on errors. + * + * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost + * of certain opcodes, possibly making contracts go over the 2300 gas limit + * imposed by `transfer`, making them unable to receive funds via + * `transfer`. {sendValue} removes this limitation. + * + * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more]. + * + * IMPORTANT: because control is transferred to `recipient`, care must be + * taken to not create reentrancy vulnerabilities. Consider using + * {ReentrancyGuard} or the + * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern]. + */ + function sendValue(address payable recipient, uint256 amount) internal { + require(address(this).balance >= amount, "Address: insufficient balance"); + + (bool success, ) = recipient.call{value: amount}(""); + require(success, "Address: unable to send value, recipient may have reverted"); + } + + /** + * @dev Performs a Solidity function call using a low level `call`. A + * plain `call` is an unsafe replacement for a function call: use this + * function instead. + * + * If `target` reverts with a revert reason, it is bubbled up by this + * function (like regular Solidity function calls). + * + * Returns the raw returned data. To convert to the expected return value, + * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`]. + * + * Requirements: + * + * - `target` must be a contract. + * - calling `target` with `data` must not revert. + * + * _Available since v3.1._ + */ + function functionCall(address target, bytes memory data) internal returns (bytes memory) { + return functionCall(target, data, "Address: low-level call failed"); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with + * `errorMessage` as a fallback revert reason when `target` reverts. + * + * _Available since v3.1._ + */ + function functionCall( + address target, + bytes memory data, + string memory errorMessage + ) internal returns (bytes memory) { + return functionCallWithValue(target, data, 0, errorMessage); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], + * but also transferring `value` wei to `target`. + * + * Requirements: + * + * - the calling contract must have an ETH balance of at least `value`. + * - the called Solidity function must be `payable`. + * + * _Available since v3.1._ + */ + function functionCallWithValue( + address target, + bytes memory data, + uint256 value + ) internal returns (bytes memory) { + return functionCallWithValue(target, data, value, "Address: low-level call with value failed"); + } + + /** + * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but + * with `errorMessage` as a fallback revert reason when `target` reverts. + * + * _Available since v3.1._ + */ + function functionCallWithValue( + address target, + bytes memory data, + uint256 value, + string memory errorMessage + ) internal returns (bytes memory) { + require(address(this).balance >= value, "Address: insufficient balance for call"); + require(isContract(target), "Address: call to non-contract"); + + (bool success, bytes memory returndata) = target.call{value: value}(data); + return verifyCallResult(success, returndata, errorMessage); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], + * but performing a static call. + * + * _Available since v3.3._ + */ + function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) { + return functionStaticCall(target, data, "Address: low-level static call failed"); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`], + * but performing a static call. + * + * _Available since v3.3._ + */ + function functionStaticCall( + address target, + bytes memory data, + string memory errorMessage + ) internal view returns (bytes memory) { + require(isContract(target), "Address: static call to non-contract"); + + (bool success, bytes memory returndata) = target.staticcall(data); + return verifyCallResult(success, returndata, errorMessage); + } + + /** + * @dev Tool to verifies that a low level call was successful, and revert if it wasn't, either by bubbling the + * revert reason using the provided one. + * + * _Available since v4.3._ + */ + function verifyCallResult( + bool success, + bytes memory returndata, + string memory errorMessage + ) internal pure returns (bytes memory) { + if (success) { + return returndata; + } else { + // Look for revert reason and bubble it up if present + if (returndata.length > 0) { + // The easiest way to bubble the revert reason is using memory via assembly + /// @solidity memory-safe-assembly + assembly { + let returndata_size := mload(returndata) + revert(add(32, returndata), returndata_size) + } + } else { + revert(errorMessage); + } + } + } +} + +// node_modules/@openzeppelin/contracts-upgradeable/utils/StringsUpgradeable.sol + +// OpenZeppelin Contracts (last updated v4.7.0) (utils/Strings.sol) + +/** + * @dev String operations. + */ +library StringsUpgradeable { + bytes16 private constant _HEX_SYMBOLS = "0123456789abcdef"; + uint8 private constant _ADDRESS_LENGTH = 20; + + /** + * @dev Converts a `uint256` to its ASCII `string` decimal representation. + */ + function toString(uint256 value) internal pure returns (string memory) { + // Inspired by OraclizeAPI's implementation - MIT licence + // https://github.com/oraclize/ethereum-api/blob/b42146b063c7d6ee1358846c198246239e9360e8/oraclizeAPI_0.4.25.sol + + if (value == 0) { + return "0"; + } + uint256 temp = value; + uint256 digits; + while (temp != 0) { + digits++; + temp /= 10; + } + bytes memory buffer = new bytes(digits); + while (value != 0) { + digits -= 1; + buffer[digits] = bytes1(uint8(48 + uint256(value % 10))); + value /= 10; + } + return string(buffer); + } + + /** + * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation. + */ + function toHexString(uint256 value) internal pure returns (string memory) { + if (value == 0) { + return "0x00"; + } + uint256 temp = value; + uint256 length = 0; + while (temp != 0) { + length++; + temp >>= 8; + } + return toHexString(value, length); + } + + /** + * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length. + */ + function toHexString(uint256 value, uint256 length) internal pure returns (string memory) { + bytes memory buffer = new bytes(2 * length + 2); + buffer[0] = "0"; + buffer[1] = "x"; + for (uint256 i = 2 * length + 1; i > 1; --i) { + buffer[i] = _HEX_SYMBOLS[value & 0xf]; + value >>= 4; + } + require(value == 0, "Strings: hex length insufficient"); + return string(buffer); + } + + /** + * @dev Converts an `address` with fixed length of 20 bytes to its not checksummed ASCII `string` hexadecimal representation. + */ + function toHexString(address addr) internal pure returns (string memory) { + return toHexString(uint256(uint160(addr)), _ADDRESS_LENGTH); + } +} + +// node_modules/@openzeppelin/contracts-upgradeable/utils/introspection/IERC165Upgradeable.sol + +// OpenZeppelin Contracts v4.4.1 (utils/introspection/IERC165.sol) + +/** + * @dev Interface of the ERC165 standard, as defined in the + * https://eips.ethereum.org/EIPS/eip-165[EIP]. + * + * Implementers can declare support of contract interfaces, which can then be + * queried by others ({ERC165Checker}). + * + * For an implementation, see {ERC165}. + */ +interface IERC165Upgradeable { + /** + * @dev Returns true if this contract implements the interface defined by + * `interfaceId`. See the corresponding + * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section] + * to learn more about how these ids are created. + * + * This function call must use less than 30 000 gas. + */ + function supportsInterface(bytes4 interfaceId) external view returns (bool); +} + +// src/IUpgradeExecutor.sol + +interface IUpgradeExecutor { + function initialize(address admin, address[] memory executors) external; + function execute(address upgrade, bytes memory upgradeCallData) external payable; + function executeCall(address target, bytes memory targetCallData) external payable; +} + +// node_modules/@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol + +// OpenZeppelin Contracts (last updated v4.7.0) (proxy/utils/Initializable.sol) + +/** + * @dev This is a base contract to aid in writing upgradeable contracts, or any kind of contract that will be deployed + * behind a proxy. Since proxied contracts do not make use of a constructor, it's common to move constructor logic to an + * external initializer function, usually called `initialize`. It then becomes necessary to protect this initializer + * function so it can only be called once. The {initializer} modifier provided by this contract will have this effect. + * + * The initialization functions use a version number. Once a version number is used, it is consumed and cannot be + * reused. This mechanism prevents re-execution of each "step" but allows the creation of new initialization steps in + * case an upgrade adds a module that needs to be initialized. + * + * For example: + * + * [.hljs-theme-light.nopadding] + * ``` + * contract MyToken is ERC20Upgradeable { + * function initialize() initializer public { + * __ERC20_init("MyToken", "MTK"); + * } + * } + * contract MyTokenV2 is MyToken, ERC20PermitUpgradeable { + * function initializeV2() reinitializer(2) public { + * __ERC20Permit_init("MyToken"); + * } + * } + * ``` + * + * TIP: To avoid leaving the proxy in an uninitialized state, the initializer function should be called as early as + * possible by providing the encoded function call as the `_data` argument to {ERC1967Proxy-constructor}. + * + * CAUTION: When used with inheritance, manual care must be taken to not invoke a parent initializer twice, or to ensure + * that all initializers are idempotent. This is not verified automatically as constructors are by Solidity. + * + * [CAUTION] + * ==== + * Avoid leaving a contract uninitialized. + * + * An uninitialized contract can be taken over by an attacker. This applies to both a proxy and its implementation + * contract, which may impact the proxy. To prevent the implementation contract from being used, you should invoke + * the {_disableInitializers} function in the constructor to automatically lock it when it is deployed: + * + * [.hljs-theme-light.nopadding] + * ``` + * /// @custom:oz-upgrades-unsafe-allow constructor + * constructor() { + * _disableInitializers(); + * } + * ``` + * ==== + */ +abstract contract Initializable { + /** + * @dev Indicates that the contract has been initialized. + * @custom:oz-retyped-from bool + */ + uint8 private _initialized; + + /** + * @dev Indicates that the contract is in the process of being initialized. + */ + bool private _initializing; + + /** + * @dev Triggered when the contract has been initialized or reinitialized. + */ + event Initialized(uint8 version); + + /** + * @dev A modifier that defines a protected initializer function that can be invoked at most once. In its scope, + * `onlyInitializing` functions can be used to initialize parent contracts. Equivalent to `reinitializer(1)`. + */ + modifier initializer() { + bool isTopLevelCall = !_initializing; + require( + (isTopLevelCall && _initialized < 1) || (!AddressUpgradeable.isContract(address(this)) && _initialized == 1), + "Initializable: contract is already initialized" + ); + _initialized = 1; + if (isTopLevelCall) { + _initializing = true; + } + _; + if (isTopLevelCall) { + _initializing = false; + emit Initialized(1); + } + } + + /** + * @dev A modifier that defines a protected reinitializer function that can be invoked at most once, and only if the + * contract hasn't been initialized to a greater version before. In its scope, `onlyInitializing` functions can be + * used to initialize parent contracts. + * + * `initializer` is equivalent to `reinitializer(1)`, so a reinitializer may be used after the original + * initialization step. This is essential to configure modules that are added through upgrades and that require + * initialization. + * + * Note that versions can jump in increments greater than 1; this implies that if multiple reinitializers coexist in + * a contract, executing them in the right order is up to the developer or operator. + */ + modifier reinitializer(uint8 version) { + require(!_initializing && _initialized < version, "Initializable: contract is already initialized"); + _initialized = version; + _initializing = true; + _; + _initializing = false; + emit Initialized(version); + } + + /** + * @dev Modifier to protect an initialization function so that it can only be invoked by functions with the + * {initializer} and {reinitializer} modifiers, directly or indirectly. + */ + modifier onlyInitializing() { + require(_initializing, "Initializable: contract is not initializing"); + _; + } + + /** + * @dev Locks the contract, preventing any future reinitialization. This cannot be part of an initializer call. + * Calling this in the constructor of a contract will prevent that contract from being initialized or reinitialized + * to any version. It is recommended to use this to lock implementation contracts that are designed to be called + * through proxies. + */ + function _disableInitializers() internal virtual { + require(!_initializing, "Initializable: contract is initializing"); + if (_initialized < type(uint8).max) { + _initialized = type(uint8).max; + emit Initialized(type(uint8).max); + } + } +} + +// node_modules/@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol + +// OpenZeppelin Contracts v4.4.1 (utils/Context.sol) + +/** + * @dev Provides information about the current execution context, including the + * sender of the transaction and its data. While these are generally available + * via msg.sender and msg.data, they should not be accessed in such a direct + * manner, since when dealing with meta-transactions the account sending and + * paying for execution may not be the actual sender (as far as an application + * is concerned). + * + * This contract is only required for intermediate, library-like contracts. + */ +abstract contract ContextUpgradeable is Initializable { + function __Context_init() internal onlyInitializing { + } + + function __Context_init_unchained() internal onlyInitializing { + } + function _msgSender() internal view virtual returns (address) { + return msg.sender; + } + + function _msgData() internal view virtual returns (bytes calldata) { + return msg.data; + } + + /** + * @dev This empty reserved space is put in place to allow future versions to add new + * variables without shifting down storage in the inheritance chain. + * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps + */ + uint256[50] private __gap; +} + +// node_modules/@openzeppelin/contracts-upgradeable/utils/introspection/ERC165Upgradeable.sol + +// OpenZeppelin Contracts v4.4.1 (utils/introspection/ERC165.sol) + +/** + * @dev Implementation of the {IERC165} interface. + * + * Contracts that want to implement ERC165 should inherit from this contract and override {supportsInterface} to check + * for the additional interface id that will be supported. For example: + * + * ```solidity + * function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) { + * return interfaceId == type(MyInterface).interfaceId || super.supportsInterface(interfaceId); + * } + * ``` + * + * Alternatively, {ERC165Storage} provides an easier to use but more expensive implementation. + */ +abstract contract ERC165Upgradeable is Initializable, IERC165Upgradeable { + function __ERC165_init() internal onlyInitializing { + } + + function __ERC165_init_unchained() internal onlyInitializing { + } + /** + * @dev See {IERC165-supportsInterface}. + */ + function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) { + return interfaceId == type(IERC165Upgradeable).interfaceId; + } + + /** + * @dev This empty reserved space is put in place to allow future versions to add new + * variables without shifting down storage in the inheritance chain. + * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps + */ + uint256[50] private __gap; +} + +// node_modules/@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol + +// OpenZeppelin Contracts (last updated v4.7.0) (access/AccessControl.sol) + +/** + * @dev Contract module that allows children to implement role-based access + * control mechanisms. This is a lightweight version that doesn't allow enumerating role + * members except through off-chain means by accessing the contract event logs. Some + * applications may benefit from on-chain enumerability, for those cases see + * {AccessControlEnumerable}. + * + * Roles are referred to by their `bytes32` identifier. These should be exposed + * in the external API and be unique. The best way to achieve this is by + * using `public constant` hash digests: + * + * ``` + * bytes32 public constant MY_ROLE = keccak256("MY_ROLE"); + * ``` + * + * Roles can be used to represent a set of permissions. To restrict access to a + * function call, use {hasRole}: + * + * ``` + * function foo() public { + * require(hasRole(MY_ROLE, msg.sender)); + * ... + * } + * ``` + * + * Roles can be granted and revoked dynamically via the {grantRole} and + * {revokeRole} functions. Each role has an associated admin role, and only + * accounts that have a role's admin role can call {grantRole} and {revokeRole}. + * + * By default, the admin role for all roles is `DEFAULT_ADMIN_ROLE`, which means + * that only accounts with this role will be able to grant or revoke other + * roles. More complex role relationships can be created by using + * {_setRoleAdmin}. + * + * WARNING: The `DEFAULT_ADMIN_ROLE` is also its own admin: it has permission to + * grant and revoke this role. Extra precautions should be taken to secure + * accounts that have been granted it. + */ +abstract contract AccessControlUpgradeable is Initializable, ContextUpgradeable, IAccessControlUpgradeable, ERC165Upgradeable { + function __AccessControl_init() internal onlyInitializing { + } + + function __AccessControl_init_unchained() internal onlyInitializing { + } + struct RoleData { + mapping(address => bool) members; + bytes32 adminRole; + } + + mapping(bytes32 => RoleData) private _roles; + + bytes32 public constant DEFAULT_ADMIN_ROLE = 0x00; + + /** + * @dev Modifier that checks that an account has a specific role. Reverts + * with a standardized message including the required role. + * + * The format of the revert reason is given by the following regular expression: + * + * /^AccessControl: account (0x[0-9a-f]{40}) is missing role (0x[0-9a-f]{64})$/ + * + * _Available since v4.1._ + */ + modifier onlyRole(bytes32 role) { + _checkRole(role); + _; + } + + /** + * @dev See {IERC165-supportsInterface}. + */ + function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) { + return interfaceId == type(IAccessControlUpgradeable).interfaceId || super.supportsInterface(interfaceId); + } + + /** + * @dev Returns `true` if `account` has been granted `role`. + */ + function hasRole(bytes32 role, address account) public view virtual override returns (bool) { + return _roles[role].members[account]; + } + + /** + * @dev Revert with a standard message if `_msgSender()` is missing `role`. + * Overriding this function changes the behavior of the {onlyRole} modifier. + * + * Format of the revert message is described in {_checkRole}. + * + * _Available since v4.6._ + */ + function _checkRole(bytes32 role) internal view virtual { + _checkRole(role, _msgSender()); + } + + /** + * @dev Revert with a standard message if `account` is missing `role`. + * + * The format of the revert reason is given by the following regular expression: + * + * /^AccessControl: account (0x[0-9a-f]{40}) is missing role (0x[0-9a-f]{64})$/ + */ + function _checkRole(bytes32 role, address account) internal view virtual { + if (!hasRole(role, account)) { + revert( + string( + abi.encodePacked( + "AccessControl: account ", + StringsUpgradeable.toHexString(uint160(account), 20), + " is missing role ", + StringsUpgradeable.toHexString(uint256(role), 32) + ) + ) + ); + } + } + + /** + * @dev Returns the admin role that controls `role`. See {grantRole} and + * {revokeRole}. + * + * To change a role's admin, use {_setRoleAdmin}. + */ + function getRoleAdmin(bytes32 role) public view virtual override returns (bytes32) { + return _roles[role].adminRole; + } + + /** + * @dev Grants `role` to `account`. + * + * If `account` had not been already granted `role`, emits a {RoleGranted} + * event. + * + * Requirements: + * + * - the caller must have ``role``'s admin role. + * + * May emit a {RoleGranted} event. + */ + function grantRole(bytes32 role, address account) public virtual override onlyRole(getRoleAdmin(role)) { + _grantRole(role, account); + } + + /** + * @dev Revokes `role` from `account`. + * + * If `account` had been granted `role`, emits a {RoleRevoked} event. + * + * Requirements: + * + * - the caller must have ``role``'s admin role. + * + * May emit a {RoleRevoked} event. + */ + function revokeRole(bytes32 role, address account) public virtual override onlyRole(getRoleAdmin(role)) { + _revokeRole(role, account); + } + + /** + * @dev Revokes `role` from the calling account. + * + * Roles are often managed via {grantRole} and {revokeRole}: this function's + * purpose is to provide a mechanism for accounts to lose their privileges + * if they are compromised (such as when a trusted device is misplaced). + * + * If the calling account had been revoked `role`, emits a {RoleRevoked} + * event. + * + * Requirements: + * + * - the caller must be `account`. + * + * May emit a {RoleRevoked} event. + */ + function renounceRole(bytes32 role, address account) public virtual override { + require(account == _msgSender(), "AccessControl: can only renounce roles for self"); + + _revokeRole(role, account); + } + + /** + * @dev Grants `role` to `account`. + * + * If `account` had not been already granted `role`, emits a {RoleGranted} + * event. Note that unlike {grantRole}, this function doesn't perform any + * checks on the calling account. + * + * May emit a {RoleGranted} event. + * + * [WARNING] + * ==== + * This function should only be called from the constructor when setting + * up the initial roles for the system. + * + * Using this function in any other way is effectively circumventing the admin + * system imposed by {AccessControl}. + * ==== + * + * NOTE: This function is deprecated in favor of {_grantRole}. + */ + function _setupRole(bytes32 role, address account) internal virtual { + _grantRole(role, account); + } + + /** + * @dev Sets `adminRole` as ``role``'s admin role. + * + * Emits a {RoleAdminChanged} event. + */ + function _setRoleAdmin(bytes32 role, bytes32 adminRole) internal virtual { + bytes32 previousAdminRole = getRoleAdmin(role); + _roles[role].adminRole = adminRole; + emit RoleAdminChanged(role, previousAdminRole, adminRole); + } + + /** + * @dev Grants `role` to `account`. + * + * Internal function without access restriction. + * + * May emit a {RoleGranted} event. + */ + function _grantRole(bytes32 role, address account) internal virtual { + if (!hasRole(role, account)) { + _roles[role].members[account] = true; + emit RoleGranted(role, account, _msgSender()); + } + } + + /** + * @dev Revokes `role` from `account`. + * + * Internal function without access restriction. + * + * May emit a {RoleRevoked} event. + */ + function _revokeRole(bytes32 role, address account) internal virtual { + if (hasRole(role, account)) { + _roles[role].members[account] = false; + emit RoleRevoked(role, account, _msgSender()); + } + } + + /** + * @dev This empty reserved space is put in place to allow future versions to add new + * variables without shifting down storage in the inheritance chain. + * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps + */ + uint256[49] private __gap; +} + +// src/UpgradeExecutor.sol + +/// @title A root contract from which it execute upgrades +/// @notice Does not contain upgrade logic itself, only the means to call upgrade contracts and execute them +/// @dev We use these upgrade contracts as they allow multiple actions to take place in an upgrade +/// and for these actions to interact. However because we are delegatecalling into these upgrade +/// contracts, it's important that these upgrade contract do not touch or modify contract state. +contract UpgradeExecutor is + Initializable, + AccessControlUpgradeable, + ReentrancyGuard, + IUpgradeExecutor +{ + using Address for address; + + bytes32 public constant ADMIN_ROLE = keccak256("ADMIN_ROLE"); + bytes32 public constant EXECUTOR_ROLE = keccak256("EXECUTOR_ROLE"); + + /// @notice Emitted when an upgrade execution occurs + event UpgradeExecuted(address indexed upgrade, uint256 value, bytes data); + + /// @notice Emitted when target call occurs + event TargetCallExecuted(address indexed target, uint256 value, bytes data); + + constructor() { + _disableInitializers(); + } + + /// @notice Initialise the upgrade executor + /// @param admin The admin who can update other roles, and itself - ADMIN_ROLE + /// @param executors Can call the execute function - EXECUTOR_ROLE + function initialize(address admin, address[] memory executors) public initializer { + require(admin != address(0), "UpgradeExecutor: zero admin"); + + __AccessControl_init(); + + _setRoleAdmin(ADMIN_ROLE, ADMIN_ROLE); + _setRoleAdmin(EXECUTOR_ROLE, ADMIN_ROLE); + + _setupRole(ADMIN_ROLE, admin); + for (uint256 i = 0; i < executors.length; ++i) { + _setupRole(EXECUTOR_ROLE, executors[i]); + } + } + + /// @notice Execute an upgrade by delegate calling an upgrade contract + /// @dev Only executor can call this. Since we're using a delegatecall here the Upgrade contract + /// will have access to the state of this contract - including the roles. Only upgrade contracts + /// that do not touch local state should be used. + function execute(address upgrade, bytes memory upgradeCallData) + public + payable + onlyRole(EXECUTOR_ROLE) + nonReentrant + { + // OZ Address library check if the address is a contract and bubble up inner revert reason + address(upgrade).functionDelegateCall( + upgradeCallData, "UpgradeExecutor: inner delegate call failed without reason" + ); + + emit UpgradeExecuted(upgrade, msg.value, upgradeCallData); + } + + /// @notice Execute an upgrade by directly calling target contract + /// @dev Only executor can call this. + function executeCall(address target, bytes memory targetCallData) + public + payable + onlyRole(EXECUTOR_ROLE) + nonReentrant + { + // OZ Address library check if the address is a contract and bubble up inner revert reason + address(target).functionCallWithValue( + targetCallData, msg.value, "UpgradeExecutor: inner call failed without reason" + ); + + emit TargetCallExecuted(target, msg.value, targetCallData); + } +} diff --git a/hardhat.config.ts b/hardhat.config.ts index 4f1d586db..f2d5ca674 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -45,7 +45,17 @@ const config = { }, }, ], - overrides: {}, + overrides: { + 'contracts/tokenbridge/test/UpgradeExecutorForVerification.sol': { + version: '0.8.16', + settings: { + optimizer: { + enabled: true, + runs: 20000, + }, + }, + }, + }, }, networks: { hardhat: { diff --git a/scripts/orbitVerifyOnBlockscout.ts b/scripts/orbitVerifyOnBlockscout.ts index 726cd738b..3038b39dc 100644 --- a/scripts/orbitVerifyOnBlockscout.ts +++ b/scripts/orbitVerifyOnBlockscout.ts @@ -7,6 +7,10 @@ import { UpgradeableBeacon__factory, } from '../build/types' import { Provider } from '@ethersproject/providers' +import { + abi as UpgradeExecutorABI, + bytecode as UpgradeExecutorBytecode, +} from '@offchainlabs/upgrade-executor/build/contracts/src/UpgradeExecutor.sol/UpgradeExecutor.json' main().then(() => console.log('Done.')) @@ -18,14 +22,15 @@ async function main() { const inboxAddress = process.env['INBOX_ADDRESS'] as string const deployerKey = process.env['DEPLOYER_KEY'] as string - if ( - !parentRpcUrl || - !tokenBridgeCreatorAddress || - !inboxAddress || - !deployerKey - ) { + if (!parentRpcUrl || !tokenBridgeCreatorAddress || !inboxAddress) { throw new Error( - 'Required env vars: PARENT_RPC, TOKEN_BRIDGE_CREATOR, INBOX_ADDRESS and DEPLOYER_KEY' + 'Required env vars: PARENT_RPC, TOKEN_BRIDGE_CREATOR, INBOX_ADDRESS' + ) + } + + if (!deployerKey) { + console.log( + 'DEPLOYER_KEY is missing. Deployer key is required if you want to have aeWETH and UpgradeExecutor verified.' ) } @@ -97,9 +102,23 @@ async function main() { await _verifyContract('ProxyAdmin', l2Deployment.proxyAdmin, []) /// special cases - aeWETH and UpgradeExecutor - const dummyAeWethFac = await new AeWETH__factory(deployerOnOrbit).deploy() - const dummyAeWeth = await dummyAeWethFac.deployed() - await _verifyContract('aeWETH', dummyAeWeth.address, []) + + if (deployerKey) { + // deploy dummy aeWETH and verify it. Its deployed bytecode will match the actual aeWETH bytecode + const dummyAeWethFac = await new AeWETH__factory(deployerOnOrbit).deploy() + const dummyAeWeth = await dummyAeWethFac.deployed() + await _verifyContract('aeWETH', dummyAeWeth.address, []) + + // deploy dummy UpgradeExecutor and verify it. Its deployed bytecode will match the actual UpgradeExecutor bytecode + const dummyUpgradeExecutorFac = new ethers.ContractFactory( + UpgradeExecutorABI, + UpgradeExecutorBytecode, + deployerOnOrbit + ) + const dummyUpgradeExecutor = await dummyUpgradeExecutorFac.deploy() + await dummyUpgradeExecutor.deployed() + await _verifyContract('UpgradeExecutor', dummyUpgradeExecutor.address, []) + } } async function _verifyContract( From cbe7b9bb541f812ba1fdd3c3890670edcbdd50ac Mon Sep 17 00:00:00 2001 From: Goran Vladika Date: Thu, 18 Apr 2024 15:14:55 +0200 Subject: [PATCH 08/11] Use fully qualified name --- test-e2e/creationCodeTest.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test-e2e/creationCodeTest.ts b/test-e2e/creationCodeTest.ts index 30b6048c7..7f7fde644 100644 --- a/test-e2e/creationCodeTest.ts +++ b/test-e2e/creationCodeTest.ts @@ -123,7 +123,7 @@ describe('creationCodeTest', () => { }) it('UpgradeExecutor constructor has expected size', async function () { - const constructorBytecode = await _getConstructorBytecode('UpgradeExecutor') + const constructorBytecode = await _getConstructorBytecode('@offchainlabs/upgrade-executor/src/UpgradeExecutor.sol:UpgradeExecutor') const constructorBytecodeLength = _lengthInBytes(constructorBytecode) expect(constructorBytecodeLength).to.be.eq( From 5d07efaaf76b7b2a9d1d7aac830748a566b62eab Mon Sep 17 00:00:00 2001 From: Goran Vladika Date: Thu, 18 Apr 2024 15:21:21 +0200 Subject: [PATCH 09/11] Update instructions --- docs/deployment.md | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/docs/deployment.md b/docs/deployment.md index b58bc42dd..e060c993b 100644 --- a/docs/deployment.md +++ b/docs/deployment.md @@ -74,7 +74,7 @@ These contracts will be owned by deployer: - ProxyAdmin of L1AtomicTokenBridgeCreator and L1TokenBridgeRetryableSender (owner can do upgrades) -## Verify token bridge deployment +## Test token bridge deployment There is a verification script which checks that token bridge contracts have been properly deployed and initialized. Here are steps for running it. Checkout target code, install dependencies and build @@ -100,3 +100,14 @@ Run the script ``` yarn run test:tokenbridge:deployment ``` + +## Verify Orbit contracts' source code on the Blockscout +Script `scripts/orbitVerifyOnBlockscout.ts` does the source code verification of all the contracts deployed by the L1AtomicTokenBridgeCreator to the specific Orbit chain. + +Script is applicable for the verifying source code on the Blockscout explorer. Steps are following: + +1. Update `hardhat.config.ts`. Find `orbit` field under `networks` and `customChains` and replace values with correct RPC and blockscout endpoints. +2. `yarn install && yarn build` +3. Set up `.env` - provide `PARENT_RPC`, `TOKEN_BRIDGE_CREATOR` (address of token bridge creator on parent chain) and `INBOX_ADDRESS`. +4. Optionally provide the `DEPLOYER_KEY`. That's the private key of any funded address on the Orbit chain. It is required if you want to get `UpgradeExecutor` and `aeWETH` verified. Due to specifics of cross-chain deployment used by token bridge creator, the only way to get `UpgradeExecutor` and `aeWETH` verified is to deploy dummy instances on the Orbit chain and verify them. That way the original instances will get automatically verified because of the deployed bytecode match. If `DEPLOYER_KEY` is not provided, this step will be skipped. +5. Run script as following: `yarn run blockscout:verify --network orbit` \ No newline at end of file From c3fe24aa46248c19394b2e04b2789277d162a7f1 Mon Sep 17 00:00:00 2001 From: Goran Vladika Date: Tue, 7 May 2024 18:13:39 +0200 Subject: [PATCH 10/11] Make env var names more consistent --- scripts/orbitVerifyOnBlockscout.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/orbitVerifyOnBlockscout.ts b/scripts/orbitVerifyOnBlockscout.ts index 3038b39dc..69c826f63 100644 --- a/scripts/orbitVerifyOnBlockscout.ts +++ b/scripts/orbitVerifyOnBlockscout.ts @@ -15,16 +15,16 @@ import { main().then(() => console.log('Done.')) async function main() { - const parentRpcUrl = process.env['PARENT_RPC'] as string + const parentRpcUrl = process.env['BASECHAIN_RPC'] as string const tokenBridgeCreatorAddress = process.env[ - 'TOKEN_BRIDGE_CREATOR' + 'L1_TOKEN_BRIDGE_CREATOR' ] as string const inboxAddress = process.env['INBOX_ADDRESS'] as string const deployerKey = process.env['DEPLOYER_KEY'] as string if (!parentRpcUrl || !tokenBridgeCreatorAddress || !inboxAddress) { throw new Error( - 'Required env vars: PARENT_RPC, TOKEN_BRIDGE_CREATOR, INBOX_ADDRESS' + 'Required env vars: BASECHAIN_RPC, L1_TOKEN_BRIDGE_CREATOR, INBOX_ADDRESS' ) } From 9bb2f19bd121361d69b5fd88741314d59dba5056 Mon Sep 17 00:00:00 2001 From: Goran Vladika Date: Tue, 7 May 2024 18:18:37 +0200 Subject: [PATCH 11/11] Update docs --- docs/deployment.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/deployment.md b/docs/deployment.md index e060c993b..1b9dbaff3 100644 --- a/docs/deployment.md +++ b/docs/deployment.md @@ -108,6 +108,6 @@ Script is applicable for the verifying source code on the Blockscout explorer. S 1. Update `hardhat.config.ts`. Find `orbit` field under `networks` and `customChains` and replace values with correct RPC and blockscout endpoints. 2. `yarn install && yarn build` -3. Set up `.env` - provide `PARENT_RPC`, `TOKEN_BRIDGE_CREATOR` (address of token bridge creator on parent chain) and `INBOX_ADDRESS`. +3. Set up `.env` - provide `BASECHAIN_RPC`, `L1_TOKEN_BRIDGE_CREATOR` (address of token bridge creator on parent chain) and `INBOX_ADDRESS`. 4. Optionally provide the `DEPLOYER_KEY`. That's the private key of any funded address on the Orbit chain. It is required if you want to get `UpgradeExecutor` and `aeWETH` verified. Due to specifics of cross-chain deployment used by token bridge creator, the only way to get `UpgradeExecutor` and `aeWETH` verified is to deploy dummy instances on the Orbit chain and verify them. That way the original instances will get automatically verified because of the deployed bytecode match. If `DEPLOYER_KEY` is not provided, this step will be skipped. 5. Run script as following: `yarn run blockscout:verify --network orbit` \ No newline at end of file