From 16789cc522df476e441a08d9ee64ca00a65d0de7 Mon Sep 17 00:00:00 2001 From: TucksonDev Date: Fri, 20 Dec 2024 13:34:51 +0000 Subject: [PATCH] feat:add verification of usdc contracts --- hardhat.config.ts | 16 +- package.json | 2 + .../deployUsdcBridge.ts | 32 ++- .../verifyParentUsdcBridge.ts | 213 +++++++++++++++ .../verifyUsdcBridge.ts | 244 ++++++++++++++++++ 5 files changed, 491 insertions(+), 16 deletions(-) create mode 100644 scripts/usdc-bridge-deployment/verifyParentUsdcBridge.ts create mode 100644 scripts/usdc-bridge-deployment/verifyUsdcBridge.ts diff --git a/hardhat.config.ts b/hardhat.config.ts index f2d5ca674..29938bce8 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -35,6 +35,15 @@ const config = { }, }, }, + { + version: '0.6.12', + settings: { + optimizer: { + enabled: true, + runs: 100, + }, + }, + }, { version: '0.8.16', settings: { @@ -191,7 +200,7 @@ const config = { arbitrumTestnet: process.env['ARBISCAN_API_KEY'], nova: process.env['NOVA_ARBISCAN_API_KEY'], arbGoerliRollup: process.env['ARBISCAN_API_KEY'], - arbSepoliaRollup: 'x', + arbSepoliaRollup: process.env['ARBISCAN_API_KEY'], orbit: 'x', }, customChains: [ @@ -215,9 +224,8 @@ const config = { network: 'arbSepoliaRollup', chainId: 421614, urls: { - apiURL: - 'https://sepolia-explorer.arbitrum.io/api?module=contract&action=verify', - browserURL: 'https://sepolia-explorer.arbitrum.io/', + apiURL: 'https://api-sepolia.arbiscan.io/api', + browserURL: 'https://sepolia.arbiscan.io/', }, }, { diff --git a/package.json b/package.json index e958d3dd0..8cc5f4f94 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,8 @@ "test:creation-code": "hardhat test test-e2e/creationCodeTest.ts", "blockscout:verify": "hardhat run ./scripts/orbitVerifyOnBlockscout.ts", "deploy:usdc-token-bridge": "hardhat run scripts/usdc-bridge-deployment/deployUsdcBridge.ts", + "verify:usdc-token-bridge": "hardhat run scripts/usdc-bridge-deployment/verifyUsdcBridge.ts", + "verify:l1-usdc-token-bridge": "hardhat run scripts/usdc-bridge-deployment/verifyParentUsdcBridge.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/usdc-bridge-deployment/deployUsdcBridge.ts b/scripts/usdc-bridge-deployment/deployUsdcBridge.ts index 68fb06e66..88aaeb030 100644 --- a/scripts/usdc-bridge-deployment/deployUsdcBridge.ts +++ b/scripts/usdc-bridge-deployment/deployUsdcBridge.ts @@ -384,22 +384,30 @@ async function _initializeGateways( ///// verify initialization if ( - (await l1UsdcGateway.router()) != l1Router || - (await l1UsdcGateway.inbox()) != inbox || - (await l1UsdcGateway.l1USDC()) != l1Usdc || - (await l1UsdcGateway.l2USDC()) != l2Usdc || - (await l1UsdcGateway.owner()) != _owner || - (await l1UsdcGateway.counterpartGateway()) != _l2CounterPart + (await l1UsdcGateway.counterpartGateway()).toLowerCase() != + _l2CounterPart.toLowerCase() + ) { + console.log('_l2CounterPart') + } + if ( + (await l1UsdcGateway.router()).toLowerCase() != l1Router.toLowerCase() || + (await l1UsdcGateway.inbox()).toLowerCase() != inbox.toLowerCase() || + (await l1UsdcGateway.l1USDC()).toLowerCase() != l1Usdc.toLowerCase() || + (await l1UsdcGateway.l2USDC()).toLowerCase() != l2Usdc.toLowerCase() || + (await l1UsdcGateway.owner()).toLowerCase() != _owner.toLowerCase() || + (await l1UsdcGateway.counterpartGateway()).toLowerCase() != + _l2CounterPart.toLowerCase() ) { throw new Error('L1 USDC gateway initialization failed') } if ( - (await l2UsdcGateway.counterpartGateway()) != _l1Counterpart || - (await l2UsdcGateway.router()) != l2Router || - (await l2UsdcGateway.l1USDC()) != l1Usdc || - (await l2UsdcGateway.l2USDC()) != l2Usdc || - (await l2UsdcGateway.owner()) != ownerL2 + (await l2UsdcGateway.counterpartGateway()).toLowerCase() != + _l1Counterpart.toLowerCase() || + (await l2UsdcGateway.router()).toLowerCase() != l2Router.toLowerCase() || + (await l2UsdcGateway.l1USDC()).toLowerCase() != l1Usdc.toLowerCase() || + (await l2UsdcGateway.l2USDC()).toLowerCase() != l2Usdc.toLowerCase() || + (await l2UsdcGateway.owner()).toLowerCase() != ownerL2.toLowerCase() ) { throw new Error('L2 USDC gateway initialization failed') } @@ -459,7 +467,7 @@ async function _registerGateway( const maxGas = retryableParams.gasLimit const gasPriceBid = retryableParams.maxFeePerGas.mul(3) - let maxSubmissionCost = retryableParams.maxSubmissionCost + const maxSubmissionCost = retryableParams.maxSubmissionCost let totalFee = maxGas.mul(gasPriceBid).add(maxSubmissionCost) if (isFeeToken) { totalFee = await _getPrescaledAmount( diff --git a/scripts/usdc-bridge-deployment/verifyParentUsdcBridge.ts b/scripts/usdc-bridge-deployment/verifyParentUsdcBridge.ts new file mode 100644 index 000000000..87804db97 --- /dev/null +++ b/scripts/usdc-bridge-deployment/verifyParentUsdcBridge.ts @@ -0,0 +1,213 @@ +import { ethers } from 'hardhat' +import { run } from 'hardhat' +import { + IERC20Bridge__factory, + IInboxBase__factory, + L1GatewayRouter__factory, + L1OrbitGatewayRouter__factory, +} from '../../build/types' +import { JsonRpcProvider, Provider } from '@ethersproject/providers' + +main().then(() => console.log('Done.')) + +/** + * USDC bridge verification script for the parent chain. + * Script will verify the following contracts: + * - L1 proxy admin + * - L1 USDC gateway (behind a TUP with L1ProxyAdmin as admin) + */ +async function main() { + console.log('Starting USDC bridge contract verification on the parent chain') + _checkEnvVars() + + // + // Loading chain information + // + const parentRpc = process.env['PARENT_RPC'] as string + const parentProvider = new JsonRpcProvider(parentRpc) + + const inboxAddress = process.env['INBOX'] as string + const parentGatewayRouterAddress = process.env['L1_ROUTER'] as string + const parentUsdcAddress = process.env['L1_USDC'] as string + + // + // Getting deployed contract addresses + // + const isFeeToken = + (await _getFeeToken(inboxAddress, parentProvider)) != + ethers.constants.AddressZero + + // Parent chain gateway router + const parentGatewayRouter = isFeeToken + ? L1OrbitGatewayRouter__factory.connect( + parentGatewayRouterAddress, + parentProvider + ) + : L1GatewayRouter__factory.connect( + parentGatewayRouterAddress, + parentProvider + ) + + // Parent chain USDC gateway + const parentUsdcGatewayAddress = await parentGatewayRouter.l1TokenToGateway( + parentUsdcAddress + ) + if (parentUsdcGatewayAddress === ethers.constants.AddressZero) { + throw new Error( + 'It looks like the new Usdc custom gateway is not registered in the GatewayRouter' + ) + } + const parentUsdcGatewayLogicAddress = await _getLogicAddress( + parentUsdcGatewayAddress, + parentProvider + ) + + // Proxy admin + const parentProxyAdminAddress = await _getAdminAddress( + parentUsdcGatewayAddress, + parentProvider + ) + + // Verify single TUP, others TUPs will be verified by bytecode match + await _verifyContract( + 'TransparentUpgradeableProxy', + parentUsdcGatewayAddress, + [parentUsdcGatewayLogicAddress, parentProxyAdminAddress, '0x'] + ) + + // Verify Parent chain USDC gateway + await _verifyContract( + isFeeToken ? 'L1OrbitUSDCGateway' : 'L1USDCGateway', + parentUsdcGatewayLogicAddress, + [] + ) + + // Verify Parent chain ProxyAdmin + await _verifyContract('ProxyAdmin', parentProxyAdminAddress, []) +} + +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 _getAdminAddress( + contractAddress: string, + provider: Provider +): Promise { + return ( + await _getAddressAtStorageSlot( + contractAddress, + provider, + '0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103' + ) + ).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) +} + +/** + * Fetch fee token if it exists or return zero address + */ +async function _getFeeToken( + inbox: string, + provider: Provider +): Promise { + const bridge = await IInboxBase__factory.connect(inbox, provider).bridge() + + let feeToken = ethers.constants.AddressZero + + try { + feeToken = await IERC20Bridge__factory.connect( + bridge, + provider + ).nativeToken() + } catch { + // ignore + } + + return feeToken +} + +/** + * Check if all required env vars are set + */ +function _checkEnvVars() { + const requiredEnvVars = [ + 'PARENT_RPC', + 'CHILD_RPC', + 'L1_ROUTER', + 'L2_ROUTER', + 'INBOX', + 'L1_USDC', + ] + + for (const envVar of requiredEnvVars) { + if (!process.env[envVar]) { + throw new Error(`Missing env var ${envVar}`) + } + } +} diff --git a/scripts/usdc-bridge-deployment/verifyUsdcBridge.ts b/scripts/usdc-bridge-deployment/verifyUsdcBridge.ts new file mode 100644 index 000000000..f52672642 --- /dev/null +++ b/scripts/usdc-bridge-deployment/verifyUsdcBridge.ts @@ -0,0 +1,244 @@ +import { ethers } from 'hardhat' +import { run } from 'hardhat' +import { + IERC20Bridge__factory, + IFiatTokenProxy__factory, + IInboxBase__factory, + L1GatewayRouter__factory, + L1OrbitGatewayRouter__factory, + L1OrbitUSDCGateway__factory, + L1USDCGateway__factory, +} from '../../build/types' +import { JsonRpcProvider, Provider } from '@ethersproject/providers' + +main().then(() => console.log('Done.')) + +/** + * USDC bridge verification script for the orbit chain. + * Script will verify the following contracts: + * - L2 proxy admin + * - L2 USDC (behind a IFiatTokenProxy with L2ProxyAdmin as admin) + * - L2 USDC gateway (behind a TUP with L2ProxyAdmin as admin) + */ +async function main() { + console.log('Starting USDC bridge contract verification on the orbit chain') + _checkEnvVars() + + // + // Loading chain information + // + const parentRpc = process.env['PARENT_RPC'] as string + const childRpc = process.env['CHILD_RPC'] as string + + const parentProvider = new JsonRpcProvider(parentRpc) + const childProvider = new JsonRpcProvider(childRpc) + + const inboxAddress = process.env['INBOX'] as string + const parentGatewayRouterAddress = process.env['L1_ROUTER'] as string + const parentUsdcAddress = process.env['L1_USDC'] as string + + // + // Getting deployed contract addresses + // + const isFeeToken = + (await _getFeeToken(inboxAddress, parentProvider)) != + ethers.constants.AddressZero + + // Parent chain gateway router + const parentGatewayRouter = isFeeToken + ? L1OrbitGatewayRouter__factory.connect( + parentGatewayRouterAddress, + parentProvider + ) + : L1GatewayRouter__factory.connect( + parentGatewayRouterAddress, + parentProvider + ) + + // Parent chain USDC gateway + const parentUsdcGatewayAddress = await parentGatewayRouter.l1TokenToGateway( + parentUsdcAddress + ) + if (parentUsdcGatewayAddress === ethers.constants.AddressZero) { + throw new Error( + 'It looks like the new Usdc custom gateway is not registered in the GatewayRouter' + ) + } + const parentUsdcGateway = isFeeToken + ? L1OrbitUSDCGateway__factory.connect( + parentUsdcGatewayAddress, + parentProvider + ) + : L1USDCGateway__factory.connect(parentUsdcGatewayAddress, parentProvider) + + // Child chain Usdc gateway + const childUsdcGatewayAddress = await parentUsdcGateway.counterpartGateway() + const childUsdcGatewayLogicAddress = await _getLogicAddress( + childUsdcGatewayAddress, + childProvider + ) + + // Child chain Usdc + const childUsdcAddress = await parentUsdcGateway.calculateL2TokenAddress( + parentUsdcAddress + ) + const childUsdc = IFiatTokenProxy__factory.connect( + childUsdcAddress, + childProvider + ) + const childUsdcLogicAddress = await childUsdc.implementation() + console.log(`Implementation: ${childUsdcLogicAddress}`) + + // Proxy admin + const childProxyAdminAddress = await _getAdminAddress( + childUsdcGatewayAddress, + childProvider + ) + + // Verify single TUP, others TUPs will be verified by bytecode match + await _verifyContract( + 'TransparentUpgradeableProxy', + childUsdcGatewayAddress, + [childUsdcGatewayLogicAddress, childProxyAdminAddress, '0x'] + ) + + // Verify Child chain USDC gateway + await _verifyContract('L2USDCGateway', childUsdcGatewayLogicAddress, []) + + // Verify Child chain USDC proxy + await _verifyContract('IFiatTokenProxy', childUsdcAddress, [ + childUsdcLogicAddress, + ]) + + // Verify Child chain USDC logic + await _verifyContract('IFiatToken', childUsdcLogicAddress, []) + + // Verify Child chain ProxyAdmin + await _verifyContract('ProxyAdmin', childProxyAdminAddress, []) +} + +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 _getAdminAddress( + contractAddress: string, + provider: Provider +): Promise { + return ( + await _getAddressAtStorageSlot( + contractAddress, + provider, + '0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103' + ) + ).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) +} + +/** + * Fetch fee token if it exists or return zero address + */ +async function _getFeeToken( + inbox: string, + provider: Provider +): Promise { + const bridge = await IInboxBase__factory.connect(inbox, provider).bridge() + + let feeToken = ethers.constants.AddressZero + + try { + feeToken = await IERC20Bridge__factory.connect( + bridge, + provider + ).nativeToken() + } catch { + // ignore + } + + return feeToken +} + +/** + * Check if all required env vars are set + */ +function _checkEnvVars() { + const requiredEnvVars = [ + 'PARENT_RPC', + 'CHILD_RPC', + 'L1_ROUTER', + 'L2_ROUTER', + 'INBOX', + 'L1_USDC', + ] + + for (const envVar of requiredEnvVars) { + if (!process.env[envVar]) { + throw new Error(`Missing env var ${envVar}`) + } + } +}