diff --git a/README.md b/README.md index f244316179..cabc4a1656 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,9 @@ See security audit reports [here](./audits). This repository is offered under the Apache 2.0 license. See LICENSE for details. +## Deployment +Check [this doc](./docs/deployment.md) for instructions on deployment and verification of token bridge. + ## Contact Discord - [Arbitrum](https://discord.com/invite/5KE54JwyTs) diff --git a/contracts/tokenbridge/ethereum/L1AtomicTokenBridgeCreator.sol b/contracts/tokenbridge/ethereum/L1AtomicTokenBridgeCreator.sol index 9376f8ac29..bf70d9e55e 100644 --- a/contracts/tokenbridge/ethereum/L1AtomicTokenBridgeCreator.sol +++ b/contracts/tokenbridge/ethereum/L1AtomicTokenBridgeCreator.sol @@ -45,8 +45,8 @@ import {IAccessControlUpgradeable} from /** * @title Layer1 token bridge creator - * @notice This contract is used to deploy token bridge on custom L2 chains. - * @dev Throughout the contract terms L1 and L2 are used, but those can be considered as base (N) chain and child (N+1) chain + * @notice This contract is used to deploy token bridge on custom Orbit chains. + * @dev Throughout the contract terms L1 and L2 are used, but those can be considered as parent (N) chain and child (N+1) chain. */ contract L1AtomicTokenBridgeCreator is Initializable, OwnableUpgradeable { using SafeERC20 for IERC20; @@ -184,6 +184,9 @@ contract L1AtomicTokenBridgeCreator is Initializable, OwnableUpgradeable { * L2 UpgradeExecutor will set 2 accounts to have EXECUTOR role - rollupOwner and alias of L1UpgradeExecutor. * 'rollupOwner' can be either EOA or a contract. If it is a contract, address will be aliased before sending to L2 * in order to be usable. + * + * Warning: Due to asynchronous communication between parent and child chain, always check child chain contracts are + * fully deployed and initialized before sending tokens to the bridge. Otherwise tokens might be permanently lost. */ function createTokenBridge( address inbox, diff --git a/docs/deployment.md b/docs/deployment.md index 212eb9b3e0..b58bc42dd9 100644 --- a/docs/deployment.md +++ b/docs/deployment.md @@ -1,6 +1,6 @@ # How to deploy RollupCreator and TokenBridgeCreator? -## RollupCreator +## Deploy RollupCreator RollupCreator is in nitro-contracts repo ``` cd nitro-contracts @@ -38,7 +38,7 @@ yarn run deploy-factory --network arb1 Script output will contain all deployed addresses. -## TokenBridgeCreator +## Deploy TokenBridgeCreator Checkout target code, install dependencies and build ``` cd token-bridge-contracts @@ -71,4 +71,32 @@ Script outputs `L1TokenBridgeCreator` and `L1TokenBridgeRetryableSender` address These contracts will be owned by deployer: - RollupCreator (owner can set templates) - L1AtomicTokenBridgeCreator (owner can set templates) -- ProxyAdmin of L1AtomicTokenBridgeCreator and L1TokenBridgeRetryableSender (owner can do upgrades) \ No newline at end of file +- ProxyAdmin of L1AtomicTokenBridgeCreator and L1TokenBridgeRetryableSender (owner can do upgrades) + + +## Verify 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 +``` +cd token-bridge-contracts +yarn install +yarn build +``` + +Populate .env +``` +ROLLUP_ADDRESS +L1_TOKEN_BRIDGE_CREATOR +L1_RETRYABLE_SENDER +BASECHAIN_DEPLOYER_KEY +BASECHAIN_RPC +ORBIT_RPC +``` +(`L1_RETRYABLE_SENDER` address can be obtained by calling `retryableSender()` on the L1 token bridge creator) + + +Run the script +``` +yarn run test:tokenbridge:deployment +``` diff --git a/package.json b/package.json index 11487f5b9f..dc16eb10c5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@arbitrum/token-bridge-contracts", - "version": "1.1.0", + "version": "1.1.2", "license": "Apache-2.0", "scripts": { "prepublishOnly": "hardhat clean && hardhat compile", diff --git a/scripts/atomicTokenBridgeDeployer.ts b/scripts/atomicTokenBridgeDeployer.ts index 885dc9bf28..cadbee9492 100644 --- a/scripts/atomicTokenBridgeDeployer.ts +++ b/scripts/atomicTokenBridgeDeployer.ts @@ -455,7 +455,8 @@ export const deployL1TokenBridgeCreator = async ( ) await l1Verifier.verifyWithAddress( 'l1TokenBridgeCreatorLogic', - l1TokenBridgeCreatorLogic.address + l1TokenBridgeCreatorLogic.address, + abi.encode(['address'], [l2MulticallAddressOnL1.address]) ) await l1Verifier.verifyWithAddress( 'l1TokenBridgeCreatorProxy', diff --git a/tasks/compareBytecode.ts b/tasks/compareBytecode.ts new file mode 100644 index 0000000000..8590838c89 --- /dev/null +++ b/tasks/compareBytecode.ts @@ -0,0 +1,57 @@ +import { task } from "hardhat/config"; +import { ethers } from "ethers"; +import "@nomiclabs/hardhat-etherscan" +import { Bytecode } from "@nomiclabs/hardhat-etherscan/dist/src/solc/bytecode" +import { TASK_VERIFY_GET_CONTRACT_INFORMATION, TASK_VERIFY_GET_COMPILER_VERSIONS, TASK_VERIFY_GET_LIBRARIES } from "@nomiclabs/hardhat-etherscan/dist/src/constants" +import fs from "fs"; + +task("compareBytecode", "Compares deployed bytecode with local builds") + .addParam("contractAddrs", "A comma-separated list of deployed contract addresses") + .setAction(async ({ contractAddrs }, hre) => { + const addresses = contractAddrs.split(','); + + // Get all local contract artifact paths + const artifactPaths = await hre.artifacts.getArtifactPaths(); + + for (const contractAddr of addresses) { + + // Fetch deployed contract bytecode + const deployedBytecode = await hre.ethers.provider.getCode(contractAddr.trim()); + const deployedCodeHash = ethers.utils.keccak256(deployedBytecode); + let matchFound = false; + + for (const artifactPath of artifactPaths) { + const artifact = JSON.parse(fs.readFileSync(artifactPath, "utf8")); + if (artifact.deployedBytecode) { + const localCodeHash = ethers.utils.keccak256(artifact.deployedBytecode); + + // Compare codehashes + if (deployedCodeHash === localCodeHash) { + console.log(`Contract Address ${contractAddr.trim()} matches with ${artifact.contractName}`); + matchFound = true; + break; + } + } + } + + if (!matchFound) { + const deployedBytecodeHex = deployedBytecode.startsWith("0x") + ? deployedBytecode.slice(2) + : deployedBytecode; + try { + const info = await hre.run(TASK_VERIFY_GET_CONTRACT_INFORMATION, { + deployedBytecode: new Bytecode(deployedBytecodeHex), + matchingCompilerVersions: await hre.run( + TASK_VERIFY_GET_COMPILER_VERSIONS + ), + libraries: await hre.run(TASK_VERIFY_GET_LIBRARIES), + }) + console.log(`Contract Address ${contractAddr.trim()} matches with ${info.contractName} without checking constructor arguments`); + } catch (error) { + console.log(`No matching contract found for address ${contractAddr.trim()}`); + } + } + } + }); + +export default {};