diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9b827ccd..807b5c29 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -40,3 +40,20 @@ jobs: - name: Startup Nitro testnode run: ${{ github.workspace }}/.github/workflows/testnode.bash --init-force ${{ (matrix.l3node == 'l3node' && '--l3node') || (matrix.l3node == 'l3node-token-6' && '--l3node --l3-fee-token --l3-token-bridge --l3-fee-token-decimals 6') || '' }} ${{ matrix.tokenbridge == 'tokenbridge' && '--tokenbridge' || '--no-tokenbridge' }} --detach ${{ matrix.pos == 'pos' && '--pos' || '' }} --simple ${{ (matrix.simple == 'simple' && '--simple') || (matrix.simple == 'no-simple' && '--no-simple') || '' }} + + bold_upgrade: + runs-on: ubuntu-8 + + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + with: + driver-opts: network=host + + - name: Startup Nitro testnode + run: ${{ github.workspace }}/.github/workflows/testnode.bash --init-force --bold-upgrade --simple --detach diff --git a/boldupgrader/Dockerfile b/boldupgrader/Dockerfile new file mode 100644 index 00000000..89f51e85 --- /dev/null +++ b/boldupgrader/Dockerfile @@ -0,0 +1,14 @@ +FROM node:18-bullseye-slim +RUN apt-get update && \ + apt-get install -y git docker.io python3 make gcc g++ curl jq +ARG BOLD_CONTRACTS_BRANCH=bold-merge-script +WORKDIR /workspace +RUN git clone --no-checkout https://github.com/OffchainLabs/nitro-contracts.git ./ +RUN git checkout ${BOLD_CONTRACTS_BRANCH} +RUN yarn install && yarn cache clean +RUN curl -L https://foundry.paradigm.xyz | bash +ENV PATH="${PATH}:/root/.foundry/bin" +RUN foundryup +RUN touch scripts/config.ts +RUN yarn build:all +ENTRYPOINT ["yarn"] \ No newline at end of file diff --git a/docker-compose-ci-cache.json b/docker-compose-ci-cache.json index 0fee603d..8a9dacb6 100644 --- a/docker-compose-ci-cache.json +++ b/docker-compose-ci-cache.json @@ -22,6 +22,17 @@ "type=docker" ] }, + "boldupgrader": { + "cache-from": [ + "type=local,src=/tmp/.buildx-cache" + ], + "cache-to": [ + "type=local,dest=/tmp/.buildx-cache,mode=max" + ], + "output": [ + "type=docker" + ] + }, "tokenbridge": { "cache-from": [ "type=local,src=/tmp/.buildx-cache" diff --git a/docker-compose.yaml b/docker-compose.yaml index c3cbf4cf..1a769ee4 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -354,6 +354,26 @@ services: - "tokenbridge-data:/workspace" - /var/run/docker.sock:/var/run/docker.sock + boldupgrader: + depends_on: + - geth + - sequencer + pid: host + build: + context: boldupgrader/ + args: + BOLD_CONTRACTS_BRANCH: ${BOLD_CONTRACTS_BRANCH:-} + environment: + - L1_RPC_URL=http://geth:8545 + - L1_PRIV_KEY=0xdc04c5399f82306ec4b4d654a342f40e2e0620fe39950d967e1e574b32d4dd36 + - CONFIG_NETWORK_NAME=local + - DEPLOYED_CONTRACTS_DIR=./scripts/files/ + - DISABLE_VERIFICATION=true + volumes: + - "config:/config" + - "boldupgrader-data:/workspace" + - /var/run/docker.sock:/var/run/docker.sock + rollupcreator: depends_on: - geth @@ -383,3 +403,4 @@ volumes: config: postgres-data: tokenbridge-data: + boldupgrader-data: diff --git a/scripts/ethcommands.ts b/scripts/ethcommands.ts index ac85ed69..aa5b333d 100644 --- a/scripts/ethcommands.ts +++ b/scripts/ethcommands.ts @@ -5,6 +5,7 @@ import { namedAccount, namedAddress } from "./accounts"; import * as L1GatewayRouter from "@arbitrum/token-bridge-contracts/build/contracts/contracts/tokenbridge/ethereum/gateway/L1GatewayRouter.sol/L1GatewayRouter.json"; import * as L1AtomicTokenBridgeCreator from "@arbitrum/token-bridge-contracts/build/contracts/contracts/tokenbridge/ethereum/L1AtomicTokenBridgeCreator.sol/L1AtomicTokenBridgeCreator.json"; import * as ERC20 from "@openzeppelin/contracts/build/contracts/ERC20.json"; +import * as TestWETH9 from "@arbitrum/token-bridge-contracts/build/contracts/contracts/tokenbridge/test/TestWETH9.sol/TestWETH9.json"; import * as fs from "fs"; import { ARB_OWNER } from "./consts"; const path = require("path"); @@ -139,6 +140,14 @@ async function deployERC20Contract(deployerWallet: Wallet, decimals: number): Pr return token.address; } +async function deployWETHContract(deployerWallet: Wallet): Promise { + const wethFactory = new ContractFactory(TestWETH9.abi, TestWETH9.bytecode, deployerWallet); + const weth = await wethFactory.deploy("Wrapped Ether", "WETH"); + await weth.deployTransaction.wait(); + + return weth.address; +} + export const bridgeFundsCommand = { command: "bridge-funds", describe: "sends funds from l1 to l2", @@ -299,6 +308,10 @@ export const createERC20Command = { boolean: true, describe: "if true, deploy on L1 and bridge to L2", }, + l1: { + boolean: true, + describe: "if true, deploy on L1 only", + }, decimals: { string: true, describe: "number of decimals for token", @@ -308,26 +321,26 @@ export const createERC20Command = { handler: async (argv: any) => { console.log("create-erc20"); - if (argv.bridgeable) { - // deploy token on l1 and bridge to l2 - const l1l2tokenbridge = JSON.parse( - fs - .readFileSync(path.join(consts.tokenbridgedatapath, "l1l2_network.json")) - .toString() - ); + if (argv.bridgeable || argv.l1) { + // deploy token on l1 const l1provider = new ethers.providers.WebSocketProvider(argv.l1url); - const l2provider = new ethers.providers.WebSocketProvider(argv.l2url); - - const deployerWallet = new Wallet( - ethers.utils.sha256(ethers.utils.toUtf8Bytes(argv.deployer)), - l1provider - ); + const deployerWallet = namedAccount(argv.deployer).connect(l1provider); const tokenAddress = await deployERC20Contract(deployerWallet, argv.decimals); const token = new ethers.Contract(tokenAddress, ERC20.abi, deployerWallet); console.log("Contract deployed at L1 address:", token.address); + if (!argv.bridgeable) return; + + // bridge to l2 + const l2provider = new ethers.providers.WebSocketProvider(argv.l2url); + const l1l2tokenbridge = JSON.parse( + fs + .readFileSync(path.join(consts.tokenbridgedatapath, "l1l2_network.json")) + .toString() + ); + const l1GatewayRouter = new ethers.Contract(l1l2tokenbridge.l2Network.tokenBridge.l1GatewayRouter, L1GatewayRouter.abi, deployerWallet); await (await token.functions.approve(l1l2tokenbridge.l2Network.tokenBridge.l1ERC20Gateway, ethers.constants.MaxUint256)).wait(); const supply = await token.totalSupply(); @@ -361,10 +374,7 @@ export const createERC20Command = { // no l1-l2 token bridge, deploy token on l2 directly argv.provider = new ethers.providers.WebSocketProvider(argv.l2url); - const deployerWallet = new Wallet( - ethers.utils.sha256(ethers.utils.toUtf8Bytes(argv.deployer)), - argv.provider - ); + const deployerWallet = namedAccount(argv.deployer).connect(argv.provider); const tokenAddress = await deployERC20Contract(deployerWallet, argv.decimals); console.log("Contract deployed at address:", tokenAddress); @@ -392,11 +402,19 @@ export const transferERC20Command = { string: true, describe: "address (see general help)", }, + l1: { + boolean: true, + describe: "if true, transfer on L1", + }, }, handler: async (argv: any) => { console.log("transfer-erc20"); - argv.provider = new ethers.providers.WebSocketProvider(argv.l2url); + if (argv.l1) { + argv.provider = new ethers.providers.WebSocketProvider(argv.l1url); + } else { + argv.provider = new ethers.providers.WebSocketProvider(argv.l2url); + } const account = namedAccount(argv.from).connect(argv.provider); const tokenContract = new ethers.Contract(argv.token, ERC20.abi, account); const tokenDecimals = await tokenContract.decimals(); @@ -406,6 +424,38 @@ export const transferERC20Command = { }, }; +export const createWETHCommand = { + command: "create-weth", + describe: "creates WETH on L1", + builder: { + deployer: { + string: true, + describe: "account (see general help)" + }, + deposit: { + number: true, + describe: "amount of weth to deposit", + default: 100, + } + }, + handler: async (argv: any) => { + console.log("create-weth"); + + const l1provider = new ethers.providers.WebSocketProvider(argv.l1url); + const deployerWallet = namedAccount(argv.deployer).connect(l1provider); + + const wethAddress = await deployWETHContract(deployerWallet); + const weth = new ethers.Contract(wethAddress, TestWETH9.abi, deployerWallet); + console.log("WETH deployed at L1 address:", weth.address); + + if (argv.deposit > 0) { + const amount = ethers.utils.parseEther(argv.deposit.toString()); + const depositTx = await deployerWallet.sendTransaction({ to: wethAddress, value: amount, data:"0xd0e30db0" }); // deposit() + await depositTx.wait(); + } + }, +}; + export const sendL1Command = { command: "send-l1", describe: "sends funds between l1 accounts", diff --git a/scripts/index.ts b/scripts/index.ts index 69cfe156..46066f07 100644 --- a/scripts/index.ts +++ b/scripts/index.ts @@ -14,6 +14,7 @@ import { bridgeNativeTokenToL3Command, bridgeToL3Command, createERC20Command, + createWETHCommand, transferERC20Command, sendL1Command, sendL2Command, @@ -38,6 +39,7 @@ async function main() { .command(bridgeToL3Command) .command(bridgeNativeTokenToL3Command) .command(createERC20Command) + .command(createWETHCommand) .command(transferERC20Command) .command(sendL1Command) .command(sendL2Command) diff --git a/test-node.bash b/test-node.bash index 91945e0e..ee988620 100755 --- a/test-node.bash +++ b/test-node.bash @@ -5,17 +5,22 @@ set -eu NITRO_NODE_VERSION=offchainlabs/nitro-node:v3.2.1-d81324d-dev BLOCKSCOUT_VERSION=offchainlabs/blockscout:v1.1.0-0e716c8 -# This commit matches v2.1.0 release of nitro-contracts, with additional support to set arb owner through upgrade executor -DEFAULT_NITRO_CONTRACTS_VERSION="99c07a7db2fcce75b751c5a2bd4936e898cda065" +DEFAULT_NITRO_CONTRACTS_VERSION="v2.1.1-beta.0" DEFAULT_TOKEN_BRIDGE_VERSION="v1.2.2" +# The is the latest bold-merge commit in nitro-contracts at the time +DEFAULT_BOLD_CONTRACTS_VERSION="42d80e40" + # Set default versions if not overriden by provided env vars : ${NITRO_CONTRACTS_BRANCH:=$DEFAULT_NITRO_CONTRACTS_VERSION} +: ${BOLD_CONTRACTS_BRANCH:=$DEFAULT_BOLD_CONTRACTS_VERSION} : ${TOKEN_BRIDGE_BRANCH:=$DEFAULT_TOKEN_BRIDGE_VERSION} export NITRO_CONTRACTS_BRANCH +export BOLD_CONTRACTS_BRANCH export TOKEN_BRIDGE_BRANCH echo "Using NITRO_CONTRACTS_BRANCH: $NITRO_CONTRACTS_BRANCH" +echo "Using BOLD_CONTRACTS_BRANCH: $BOLD_CONTRACTS_BRANCH" echo "Using TOKEN_BRIDGE_BRANCH: $TOKEN_BRIDGE_BRANCH" mydir=`dirname $0` @@ -44,6 +49,7 @@ blockscout=false tokenbridge=false l3node=false consensusclient=false +boldupgrade=false redundantsequencers=0 l3_custom_fee_token=false l3_token_bridge=false @@ -203,6 +209,10 @@ while [[ $# -gt 0 ]]; do l1chainid=1337 shift ;; + --bold-upgrade) + boldupgrade=true + shift + ;; --l3node) l3node=true shift @@ -282,8 +292,8 @@ while [[ $# -gt 0 ]]; do echo --no-build-dev-nitro don\'t rebuild dev nitro docker image echo --build-dev-blockscout rebuild dev blockscout docker image echo --no-build-dev-blockscout don\'t rebuild dev blockscout docker image - echo --build-utils rebuild scripts, rollupcreator, token bridge docker images - echo --no-build-utils don\'t rebuild scripts, rollupcreator, token bridge docker images + echo --build-utils rebuild scripts, rollupcreator, boldupgrader, token bridge docker images + echo --no-build-utils don\'t rebuild scripts, rollupcreator, boldupgrader, token bridge docker images echo --force-build-utils force rebuilding utils, useful if NITRO_CONTRACTS_ or TOKEN_BRIDGE_BRANCH changes echo echo script runs inside a separate docker. For SCRIPT-ARGS, run $0 script --help @@ -352,7 +362,7 @@ if $dev_blockscout && $build_dev_blockscout; then fi if $build_utils; then - LOCAL_BUILD_NODES="scripts rollupcreator" + LOCAL_BUILD_NODES="scripts rollupcreator boldupgrader" # always build tokenbridge in CI mode to avoid caching issues if $tokenbridge || $l3_token_bridge || $ci; then LOCAL_BUILD_NODES="$LOCAL_BUILD_NODES tokenbridge" @@ -473,11 +483,11 @@ if $force_init; then docker compose up --wait $INITIAL_SEQ_NODES docker compose run scripts bridge-funds --ethamount 100000 --wait docker compose run scripts send-l2 --ethamount 100 --to l2owner --wait + rollupAddress=`docker compose run --entrypoint sh poster -c "jq -r '.[0].rollup.rollup' /config/deployed_chain_info.json | tail -n 1 | tr -d '\r\n'"` if $tokenbridge; then echo == Deploying L1-L2 token bridge sleep 10 # no idea why this sleep is needed but without it the deploy fails randomly - rollupAddress=`docker compose run --entrypoint sh poster -c "jq -r '.[0].rollup.rollup' /config/deployed_chain_info.json | tail -n 1 | tr -d '\r\n'"` docker compose run -e ROLLUP_OWNER_KEY=$l2ownerKey -e ROLLUP_ADDRESS=$rollupAddress -e PARENT_KEY=$devprivkey -e PARENT_RPC=http://geth:8545 -e CHILD_KEY=$devprivkey -e CHILD_RPC=http://sequencer:8547 tokenbridge deploy:local:token-bridge docker compose run --entrypoint sh tokenbridge -c "cat network.json && cp network.json l1l2_network.json && cp network.json localNetwork.json" echo @@ -486,6 +496,21 @@ if $force_init; then echo == Deploy CacheManager on L2 docker compose run -e CHILD_CHAIN_RPC="http://sequencer:8547" -e CHAIN_OWNER_PRIVKEY=$l2ownerKey rollupcreator deploy-cachemanager-testnode + if $boldupgrade; then + echo == Deploying WETH as BOLD stake token + stakeTokenAddress=`docker compose run scripts create-weth --deployer l2owner --deposit 100 | tail -n 1 | awk '{ print $NF }'` + echo BOLD stake token address: $stakeTokenAddress + docker compose run scripts transfer-erc20 --token $stakeTokenAddress --l1 --amount 100 --from l2owner --to validator + echo == Preparing BOLD upgrade + docker compose run -e TESTNODE_MODE=true -e ROLLUP_ADDRESS=$rollupAddress -e STAKE_TOKEN=$stakeTokenAddress boldupgrader script:bold-prepare + # retry this 10 times because the staker might not have made a node yet + for i in {1..10}; do + docker compose run -e TESTNODE_MODE=true -e ROLLUP_ADDRESS=$rollupAddress -e STAKE_TOKEN=$stakeTokenAddress boldupgrader script:bold-populate-lookup && break || true + echo "Failed to populate lookup table, retrying..." + sleep 10 + done + docker compose run -e TESTNODE_MODE=true -e ROLLUP_ADDRESS=$rollupAddress -e STAKE_TOKEN=$stakeTokenAddress boldupgrader script:bold-local-execute + fi if $l3node; then echo == Funding l3 users