Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
128 changes: 16 additions & 112 deletions scripts/atomicTokenBridgeDeployer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,11 @@ import { ParentToChildMessageGasParams } from '@arbitrum/sdk/dist/lib/message/Pa
*/
const ADDRESS_DEAD = '0x000000000000000000000000000000000000dEaD'

/**
* ContractVerifier instance for L1 contracts
*/
let l1Verifier: ContractVerifier

/**
* @notice Deploys a contract using the provided factory class and signer.
* @dev Supports optional contract verification and deployment via CREATE2.
Expand Down Expand Up @@ -106,16 +111,14 @@ export async function deployContract(
} ${constructorArgs.join(' ')}`
)

/*
if (verify) {
await verifyContract(
l1Verifier.queueContractForVerification(
signer,
contractName,
contract.address,
constructorArgs
)
}
*/

return contract
}
Expand Down Expand Up @@ -328,6 +331,14 @@ export const deployL1TokenBridgeCreator = async (
verifyContracts = false,
useCreate2 = false
) => {
// Start the ContractVerifier instance if needed
if (verifyContracts) {
l1Verifier = new ContractVerifier(
(await l1Deployer.provider!.getNetwork()).chainId,
process.env.ARBISCAN_API_KEY!
)
}

/// deploy creator behind proxy
const l2MulticallAddressOnL1 = await deployContract(
ArbMulticall2__factory,
Expand Down Expand Up @@ -633,116 +644,9 @@ export const deployL1TokenBridgeCreator = async (
)
).wait()

///// verify contracts
// Trigger the verification of contracts
if (verifyContracts) {
console.log('\n\n Start contract verification \n\n')
const l1Verifier = new ContractVerifier(
(await l1Deployer.provider!.getNetwork()).chainId,
process.env.ARBISCAN_API_KEY!
)
const abi = ethers.utils.defaultAbiCoder

await l1Verifier.verifyWithAddress(
'l1TokenBridgeCreatorProxyAdmin',
l1TokenBridgeCreatorProxyAdmin.address
)
await l1Verifier.verifyWithAddress(
'l1TokenBridgeCreatorLogic',
l1TokenBridgeCreatorLogic.address,
abi.encode(['address'], [l2MulticallAddressOnL1.address])
)
await l1Verifier.verifyWithAddress(
'l1TokenBridgeCreatorProxy',
l1TokenBridgeCreatorProxy.address,
abi.encode(
['address', 'address', 'bytes'],
[
l1TokenBridgeCreatorLogic.address,
l1TokenBridgeCreatorProxyAdmin.address,
'0x',
]
)
)
await l1Verifier.verifyWithAddress(
'retryableSenderLogic',
retryableSenderLogic.address
)
await l1Verifier.verifyWithAddress(
'retryableSenderProxy',
retryableSenderProxy.address,
abi.encode(
['address', 'address', 'bytes'],
[
retryableSenderLogic.address,
l1TokenBridgeCreatorProxyAdmin.address,
'0x',
]
)
)
await l1Verifier.verifyWithAddress('routerTemplate', routerTemplate.address)
await l1Verifier.verifyWithAddress(
'standardGatewayTemplate',
standardGatewayTemplate.address
)
await l1Verifier.verifyWithAddress(
'customGatewayTemplate',
customGatewayTemplate.address
)
await l1Verifier.verifyWithAddress(
'wethGatewayTemplate',
wethGatewayTemplate.address
)
await l1Verifier.verifyWithAddress(
'feeTokenBasedRouterTemplate',
feeTokenBasedRouterTemplate.address
)
await l1Verifier.verifyWithAddress(
'feeTokenBasedStandardGatewayTemplate',
feeTokenBasedStandardGatewayTemplate.address
)
await l1Verifier.verifyWithAddress(
'feeTokenBasedCustomGatewayTemplate',
feeTokenBasedCustomGatewayTemplate.address
)
await l1Verifier.verifyWithAddress(
'upgradeExecutor',
upgradeExecutor.address,
'',
20000
)
await l1Verifier.verifyWithAddress(
'l2TokenBridgeFactoryOnL1',
l2TokenBridgeFactoryOnL1.address
)
await l1Verifier.verifyWithAddress(
'l2GatewayRouterOnL1',
l2GatewayRouterOnL1.address
)
await l1Verifier.verifyWithAddress(
'l2StandardGatewayAddressOnL1',
l2StandardGatewayAddressOnL1.address
)
await l1Verifier.verifyWithAddress(
'l2CustomGatewayAddressOnL1',
l2CustomGatewayAddressOnL1.address
)
await l1Verifier.verifyWithAddress(
'l2WethGatewayAddressOnL1',
l2WethGatewayAddressOnL1.address
)
await l1Verifier.verifyWithAddress(
'l2WethAddressOnL1',
l2WethAddressOnL1.address
)
await l1Verifier.verifyWithAddress(
'l2MulticallAddressOnL1',
l2MulticallAddressOnL1.address
)

await l1Verifier.verifyWithAddress('l1Multicall', l1Multicall.address)

await new Promise(resolve => setTimeout(resolve, 2000))
console.log('\n\n Contract verification done \n\n')
await l1Verifier.verifyAllQueuedContracts()
}

return { l1TokenBridgeCreator, retryableSender }
Expand Down
163 changes: 139 additions & 24 deletions scripts/contractVerifier.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,51 @@
import { exec } from 'child_process'
import { ethers } from 'ethers'
import { BigNumber, ethers, Signer } from 'ethers'

type VerificationRequest = {
signer: Signer
contractName: string
contractAddress: string
constructorArguments: any[]
}

// Helper function to encode constructor arguments based on their types
function abiEncodeConstructorArguments(args: any[]): string {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Crafted this function to avoid having to pass the types of arguments for verification.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

very nice

if (args.length === 0) return ''

// Infer types from the arguments
const types: string[] = args.map(arg => {
if (typeof arg === 'string' && arg.match(/^0x[a-fA-F0-9]{40}$/)) {
return 'address'
} else if (typeof arg === 'string' && arg.match(/^0x[a-fA-F0-9]*$/)) {
return 'bytes'
} else if (typeof arg === 'string') {
return 'string'
} else if (typeof arg === 'number' || BigNumber.isBigNumber(arg)) {
return 'uint256'
} else if (typeof arg === 'boolean') {
return 'bool'
} else if (Array.isArray(arg)) {
// For arrays, detect the inner type from first element
if (arg.length > 0) {
const innerType =
typeof arg[0] === 'string' && arg[0].match(/^0x[a-fA-F0-9]{40}$/)
? 'address'
: 'string'
return `${innerType}[]`
}
return 'string[]' // fallback
}
return 'bytes32' // fallback for unknown types
})

const abi = ethers.utils.defaultAbiCoder
return abi.encode(types, args)
}

export class ContractVerifier {
chainId: number
apiKey: string = ''
apiKey = ''
verificationQueue: VerificationRequest[] = []

readonly NUM_OF_OPTIMIZATIONS = 100
readonly COMPILER_VERSION = '0.8.16'
Expand All @@ -17,41 +59,40 @@ export class ContractVerifier {
'node_modules/@offchainlabs/upgrade-executor/src/UpgradeExecutor.sol:UpgradeExecutor'

readonly contractToSource = {
l1TokenBridgeCreatorProxyAdmin: this.PROXY_ADMIN,
l1TokenBridgeCreatorLogic:
ProxyAdmin: this.PROXY_ADMIN,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed the keys of this object to use directly the name of the contract instead of custom keys (since these are not used anymore in the calling function)

TransparentUpgradeableProxy: this.TUP,
UpgradeExecutor: this.EXECUTOR,
L1AtomicTokenBridgeCreator:
'contracts/tokenbridge/ethereum/L1AtomicTokenBridgeCreator.sol:L1AtomicTokenBridgeCreator',
l1TokenBridgeCreatorProxy: this.TUP,
retryableSenderLogic:
L1TokenBridgeRetryableSender:
'contracts/tokenbridge/ethereum/L1TokenBridgeRetryableSender.sol:L1TokenBridgeRetryableSender',
retryableSenderProxy: this.TUP,
routerTemplate:
L1GatewayRouter:
'contracts/tokenbridge/ethereum/gateway/L1GatewayRouter.sol:L1GatewayRouter',
standardGatewayTemplate:
L1ERC20Gateway:
'contracts/tokenbridge/ethereum/gateway/L1ERC20Gateway.sol:L1ERC20Gateway',
customGatewayTemplate:
L1CustomGateway:
'contracts/tokenbridge/ethereum/gateway/L1CustomGateway.sol:L1CustomGateway',
wethGatewayTemplate:
L1WethGateway:
'contracts/tokenbridge/ethereum/gateway/L1WethGateway.sol:L1WethGateway',
feeTokenBasedRouterTemplate:
L1OrbitGatewayRouter:
'contracts/tokenbridge/ethereum/gateway/L1OrbitGatewayRouter.sol:L1OrbitGatewayRouter',
feeTokenBasedStandardGatewayTemplate:
L1OrbitERC20Gateway:
'contracts/tokenbridge/ethereum/gateway/L1OrbitERC20Gateway.sol:L1OrbitERC20Gateway',
feeTokenBasedCustomGatewayTemplate:
L1OrbitCustomGateway:
'contracts/tokenbridge/ethereum/gateway/L1OrbitCustomGateway.sol:L1OrbitCustomGateway',
upgradeExecutor: this.EXECUTOR,
l2TokenBridgeFactoryOnL1:
L2AtomicTokenBridgeFactory:
'contracts/tokenbridge/arbitrum/L2AtomicTokenBridgeFactory.sol:L2AtomicTokenBridgeFactory',
l2GatewayRouterOnL1:
L2GatewayRouter:
'contracts/tokenbridge/arbitrum/gateway/L2GatewayRouter.sol:L2GatewayRouter',
l2StandardGatewayAddressOnL1:
L2ERC20Gateway:
'contracts/tokenbridge/arbitrum/gateway/L2ERC20Gateway.sol:L2ERC20Gateway',
l2CustomGatewayAddressOnL1:
L2CustomGateway:
'contracts/tokenbridge/arbitrum/gateway/L2CustomGateway.sol:L2CustomGateway',
l2WethGatewayAddressOnL1:
L2WethGateway:
'contracts/tokenbridge/arbitrum/gateway/L2WethGateway.sol:L2WethGateway',
l2WethAddressOnL1: 'contracts/tokenbridge/libraries/aeWETH.sol:aeWETH',
l2MulticallAddressOnL1: 'contracts/rpc-utils/MulticallV2.sol:ArbMulticall2',
l1Multicall: 'contracts/rpc-utils/MulticallV2.sol:Multicall2',
AeWETH: 'contracts/tokenbridge/libraries/aeWETH.sol:aeWETH',
ArbMulticall2: 'contracts/rpc-utils/MulticallV2.sol:ArbMulticall2',
Multicall2: 'contracts/rpc-utils/MulticallV2.sol:Multicall2',
}

constructor(chainId: number, apiKey: string) {
Expand All @@ -61,12 +102,86 @@ export class ContractVerifier {
}
}

queueContractForVerification(
signer: Signer,
contractName: string,
contractAddress: string,
constructorArguments: any[] = []
): void {
this.verificationQueue.push({
signer,
contractName,
contractAddress,
constructorArguments,
})
}

async verifyAllQueuedContracts(): Promise<void> {
if (this.verificationQueue.length === 0) {
return
}

if (!this.apiKey) {
console.warn(
'ARBISCAN_API_KEY is not set. Skipping contract verification.'
)
this.verificationQueue = [] // Clear queue
return
}

console.log()
console.log(`=== Verification of contracts ===`)

for (let i = 0; i < this.verificationQueue.length; i++) {
const request = this.verificationQueue[i]
await this.verifyContract(
request.signer,
request.contractName,
request.contractAddress,
request.constructorArguments
)

// Add a small delay between verifications to avoid rate limiting
if (i < this.verificationQueue.length - 1) {
await new Promise(resolve => setTimeout(resolve, 1000))
}
}

// Clear the queue after processing
this.verificationQueue = []

// Allow a few seconds for all pending verifications to complete
await new Promise(resolve => setTimeout(resolve, 3000))
}

async verifyContract(
signer: Signer,
contractName: string,
contractAddress: string,
constructorArguments: any[] = []
): Promise<void> {
const contractVerifier = new ContractVerifier(
(await signer.provider!.getNetwork()).chainId,
process.env.ARBISCAN_API_KEY!
)

// Encode constructor arguments if provided
const encodedConstructorArgs =
abiEncodeConstructorArguments(constructorArguments)

await contractVerifier.verifyWithAddress(
contractName,
contractAddress,
encodedConstructorArgs
)
}

async verifyWithAddress(
name: string,
contractAddress: string,
constructorArgs?: string,
_numOfOptimization?: number
) {
): Promise<void> {
// avoid rate limiting
await new Promise(resolve => setTimeout(resolve, 1000))

Expand Down
Loading