Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New Arbitrum Token Bridge #1

Open
wants to merge 82 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
82 commits
Select commit Hold shift + click to select a range
bd53ac0
Add inbound and outbound transfers
Apr 26, 2024
1a0b7cc
Use gov relay to configure L2 gateway
May 1, 2024
9d71fba
Add router tests
May 1, 2024
7a283e1
Add newlines
May 1, 2024
4019fc0
Refactor integration tests
May 2, 2024
2afa3ea
Replace file with registerToken and add unit tests
May 2, 2024
850d26b
Refactor initGateways
May 2, 2024
eeb86fb
Clean debug comments
May 8, 2024
8bc5bdf
Implements required interfaces
May 8, 2024
4f3fa0b
Allow bulk token registration
May 9, 2024
4f446c4
Update chainlog
May 9, 2024
a0de856
Add init script sanity checks
May 9, 2024
97d8498
Cleanup comment
May 9, 2024
37726ad
Add test for ERC165
May 13, 2024
de154b5
Add relyGateway xchain msg
May 14, 2024
f1e7623
Update licenses
May 15, 2024
0993595
Add sepolia deploy script
May 15, 2024
ccbff55
require l1Router and inbox at init
May 15, 2024
009c78c
Use Domain contract
May 16, 2024
a908102
Use single xchain msg at init
May 16, 2024
6ce3b50
Add init script for Sepolia/Tenderly
May 16, 2024
5c2f4f3
Add gas param margins
May 16, 2024
8bcb0e2
Add Deposit test script; update deployed l2Spell; add RetryableTicket…
May 17, 2024
2e7cf8a
Add L2 side of Withdraw deploy test
May 17, 2024
f2faf2e
Add relay comment
May 20, 2024
7b592a4
Remove whitespace
May 21, 2024
450765f
Remove l1/l2 directories
May 24, 2024
6aedabf
Fix revert reasons
May 24, 2024
1059fd3
Remove whitespace
May 24, 2024
90817a8
Fix license
May 24, 2024
4e2cb0b
Remove TODO comment
May 30, 2024
afa295d
Apply review suggestions
May 30, 2024
f2b227f
Remove unnecessary override's
Jun 3, 2024
9c2d517
Remove L2 gateway import from L2 spell
Jun 3, 2024
d2c8889
Remove addTokens from init scripts
Jun 3, 2024
6be2379
Add README
Jun 10, 2024
24ec24f
Fix README
Jun 13, 2024
a76f3c1
Test call to getOutboundCalldata in testOutboundTransfer
Jun 13, 2024
29937f5
Update foundry.toml
telome Jun 13, 2024
4576c0f
Use config tokens for deployment
Jun 14, 2024
7839454
Apply suggestions from code review
telome Jun 17, 2024
e85127e
Inline parseOutboundData
Jun 17, 2024
2362af7
Add more info to README
Jun 18, 2024
230d826
Fix Deploy.s
Jun 18, 2024
493a297
Remove unnecessary l1Token != address(0) check
Jun 18, 2024
8ac05e1
Add note to README
Jun 18, 2024
5e30861
Add missing natspec
Jun 18, 2024
0a89d52
Fix link in README
Jun 19, 2024
e464682
Remove leftover TODOs
Jun 20, 2024
896b663
Add missing newlines
Jun 20, 2024
82e450a
Fix mock intendation
Jun 20, 2024
1ad3d9c
More intendation fix
Jun 20, 2024
e4a1784
Fix more endlines
Jun 20, 2024
5a2f7e4
Update README.md
telome Jun 20, 2024
b225225
Update deploy/TokenGatewayInit.sol
telome Jun 20, 2024
127ddbd
Update script/Init.s.sol
telome Jun 20, 2024
709fd04
Reword comment
Jun 21, 2024
1ad5ad9
Use one config file per root domain
Jun 21, 2024
e3c42a4
Add note about supporting std erc20s
Jun 21, 2024
89d4301
Fix tests
Jun 21, 2024
740ef1c
Remove unnecessary config entries
Jun 21, 2024
56ea3c5
Update test/Integration.t.sol
telome Jun 21, 2024
2471056
Add l2Token ward check in tests
Jun 21, 2024
7fd3718
Add L2-side sanity checks (#2)
telome Jun 25, 2024
c22741f
Add recommendations from Cantina
Jun 28, 2024
098ebac
Minor formatting changes
Jun 28, 2024
6248966
Merge pull request #3 from makerdao/cantina-fixes
telome Jul 1, 2024
d0b447c
Add Cantina Report (#4)
oldchili Jul 3, 2024
844b320
Remove redundant var in init script
Jul 9, 2024
c3c60c2
Merge pull request #5 from makerdao/cs-fix
telome Jul 9, 2024
18f1d02
Add Chainsecurity audit report
oldchili Jul 11, 2024
c062e49
Merge pull request #6 from makerdao/add-cs-report
oldchili Jul 12, 2024
aac0feb
Update README so env vars are exported
Jul 16, 2024
643c62b
Use different L1 & L2 deployers (#7)
telome Jul 18, 2024
d2b2185
Use new dss-test functions (#8)
telome Aug 21, 2024
74d5b4c
Update CI
Aug 23, 2024
542f2cf
Implement fileable escrow (#9)
sunbreak1211 Sep 19, 2024
7bccdb3
Add maxWithdraws to L2TokenGateway (#10)
telome Oct 8, 2024
aedb60f
Upgradable L1TokenGateway and L2TokenGateway (#11)
telome Oct 8, 2024
90c5a20
Update CS audit report (#12)
telome Oct 14, 2024
c935a66
Add Cantina report (#14)
oldchili Nov 13, 2024
b3e6d4b
Add Certora specs (#13)
sunbreak1211 Nov 19, 2024
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
12 changes: 12 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
export FOUNDRY_SCRIPT_DEPS=deployed
export FOUNDRY_EXPORTS_OVERWRITE_LATEST=true
export L1="sepolia"
export L2="arbitrum_one_sepolia"
export MAINNET_RPC_URL=
export ARBITRUM_ONE_RPC_URL=
export SEPOLIA_RPC_URL=
export ARBITRUM_ONE_SEPOLIA_RPC_URL=
export L1_PRIVATE_KEY="0x$(cat /path/to/pkey1)"
export L2_PRIVATE_KEY="0x$(cat /path/to/pkey2)"
export ETHERSCAN_KEY=
export ARBISCAN_KEY=
45 changes: 45 additions & 0 deletions .github/workflows/certora.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
name: Certora

on: [push, pull_request]

jobs:
certora:
name: Certora
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
arbitrum-token-bridge:
- l1-token-gateway
- l2-token-gateway

steps:
- name: Checkout
uses: actions/checkout@v3
with:
submodules: recursive

- uses: actions/setup-java@v2
with:
distribution: 'zulu'
java-version: '11'
java-package: jre

- name: Set up Python 3.8
uses: actions/setup-python@v3
with:
python-version: 3.8

- name: Install solc-select
run: pip3 install solc-select

- name: Solc Select 0.8.21
run: solc-select install 0.8.21

- name: Install Certora
run: pip3 install certora-cli-beta

- name: Verify ${{ matrix.arbitrum-token-bridge }}
run: make certora-${{ matrix.arbitrum-token-bridge }} results=1
env:
CERTORAKEY: ${{ secrets.CERTORAKEY }}
5 changes: 4 additions & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: test

on: workflow_dispatch
on: [push, pull_request]

env:
FOUNDRY_PROFILE: ci
Expand Down Expand Up @@ -32,3 +32,6 @@ jobs:
run: |
forge test -vvv
id: test
env:
MAINNET_RPC_URL: ${{ secrets.MAINNET_RPC_URL }}
ARBITRUM_ONE_RPC_URL: ${{ secrets.ARBITRUM_ONE_RPC_URL }}
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,6 @@ docs/

# Dotenv file
.env

# Certora
.certora_internal
6 changes: 6 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
[submodule "lib/dss-test"]
path = lib/dss-test
url = https://github.com/makerdao/dss-test
[submodule "lib/openzeppelin-foundry-upgrades"]
path = lib/openzeppelin-foundry-upgrades
url = https://github.com/OpenZeppelin/openzeppelin-foundry-upgrades
[submodule "lib/openzeppelin-contracts-upgradeable"]
path = lib/openzeppelin-contracts-upgradeable
url = https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable
3 changes: 3 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
PATH := ~/.solc-select/artifacts/:~/.solc-select/artifacts/solc-0.8.21:$(PATH)
certora-l1-token-gateway :; PATH=${PATH} certoraRun certora/L1TokenGateway.conf$(if $(rule), --rule $(rule),)$(if $(results), --wait_for_results all,)
certora-l2-token-gateway :; PATH=${PATH} certoraRun certora/L2TokenGateway.conf$(if $(rule), --rule $(rule),)$(if $(results), --wait_for_results all,)
119 changes: 119 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
# MakerDAO Arbitrum Token Bridge

## Overview

The Arbitrum Token Bridge is a [custom Arbitrum bridge](https://docs.arbitrum.io/build-decentralized-apps/token-bridging/bridge-tokens-programmatically/how-to-bridge-tokens-custom-gateway) that allows users to deposit a supported token to Arbitrum and withdraw it back to Ethereum. It operates similarly to the previously deployed [Arbitrum Dai Bridge](https://github.com/makerdao/arbitrum-dai-bridge) and relies on the same security model but allows MakerDAO governance to update the set of tokens supported by the bridge.

## Contracts

- `L1TokenGateway.sol` - L1 side of the bridge. Transfers the deposited tokens into an escrow contract. Transfer them back to the user upon receiving a withdrawal message from the `L2TokenGateway`.
- `L2TokenGateway.sol` - L2 side of the bridge. Mints new L2 tokens after receiving a deposit message from `L1TokenGateway`. Burns L2 tokens when withdrawing them to L1.

The `L1TokenGateway` and `L2TokenGateway` contracts use the ERC-1822 UUPS pattern for upgradeability and the ERC-1967 proxy storage slots standard. It is important that the `TokenGatewayDeploy` library sequences be used for deploying.

### External dependencies

- The L2 implementations of the bridged tokens are not provided as part of this repository and are assumed to exist in external repositories. It is assumed that only simple, regular ERC20 tokens will be used with this bridge. In particular, the supported tokens are assumed to revert on failure (instead of returning false) and do not execute any hook on transfer.
- The [escrow contract](https://etherscan.io/address/0xA10c7CE4b876998858b1a9E12b10092229539400#code) holds the bridged tokens on L1. This is assumed to be the same escrow as the one used by the Arbitrum Dai Bridge.
- The [`L1GovernanceRelay`](https://etherscan.io/address/0x9ba25c289e351779E0D481Ba37489317c34A899d#code) & [`L2GovernanceRelay`](https://arbiscan.io/address/0x10E6593CDda8c58a1d0f14C5164B376352a55f2F#code) allow governance to exert admin control over the deployed L2 contracts. These contracts have been previously deployed to control the Arbitrum Dai Bridge.

## User flows

### L1 to L2 deposits

To deposit a given amount of a supported token into Arbitrum, Alice calls `outboundTransfer[CustomRefund]()` on the `L1TokenGateway`. This call locks Alice's tokens into an escrow contract and creates an [Arbitrum Retryable Ticket](https://docs.arbitrum.io/how-arbitrum-works/arbos/l1-l2-messaging#retryable-tickets) which instructs the Arbitrum sequencer to asynchroneously call `finalizeInboundTransfer()` on `L2TokenGateway`. That latter call mints an equivalent amount of L2 tokens for Alice.

Note that the `outboundTransfer[CustomRefund]` payable function requires a number of gas parameters to be provided, and must be called with some corresponding amount of ETH as `msg.value`. An example of how to calculate these parameters is provided in `script/Deposit.s.sol`.

### L2 to L1 withdrawals

To withdraw her tokens back to L1, Alice calls `outboundTransfer()` on the `L2TokenGateway`. This call burns Alice's tokens and performs a call to the [ArbSys](https://docs.arbitrum.io/how-arbitrum-works/arbos/l2-l1-messaging#client-flow) precompile contract, which enables anyone to call `finalizeInboundTransfer()` on `L1TokenGateway` after the ~7 days security period. That latter call releases an equivalent amount of L1 tokens from the escrow to Alice.

## Upgrades

### Upgrade the bridge implementation(s)

`L1TokenGateway` and/or `L2TokenGateway` implementations can be upgraded by calling the `upgradeToAndCall` function of their inherited `UUPSUpgradeable` parent. Special care must be taken to ensure any deposit or withdrawal that is in transit at the time of the upgrade will still be able to get confirmed on the destination side.

### Upgrade to a new bridge (and deprecate this bridge)

As an alternative upgrade mechanism, a new bridge can be deployed to be used with the escrow.

1. Deploy the new token bridge and connect it to the same escrow as the one used by this bridge. The old and new bridges can operate in parallel.
2. Optionally, deprecate the old bridge by closing it. This involves calling `close()` on both the `L1TokenGateway` and `L2TokenGateway` so that no new outbound message can be sent to the other side of the bridge. After all cross-chain messages are done processing (can take ~1 week), the bridge is effectively closed and governance can consider revoking the approval to transfer funds from the escrow on L1 and the token minting rights on L2.

### Upgrade a single token to a new bridge

To migrate a single token to a new bridge, follow the steps below:

1. Deploy the new token bridge and connect it to the same escrow as the one used by this bridge.
2. Unregister the token on `L1TokenGateway`, removing the ability to initiate new L1 to L2 transfers for that token.
3. Wait a few days to give a chance for any failed L1 to L2 transfer to be retried.
4. Execute an L2 spell to unregister the token on `L2TokenGateway`, removing the ability to initiate new L2 to L1 transfers for that token.
sunbreak1211 marked this conversation as resolved.
Show resolved Hide resolved

Note that step 3 is required because unregistering the token on `L2TokenGateway` not only removes the ability to initiate new L2 to L1 transfers but also causes the finalization of pending L1 to L2 transfers to revert. This is a point of difference with the implementation of the Arbitrum generic-custom gateway, where a missing L2 token triggers a withdrawal of the tokens back to L1 instead of a revert.

## Tests

### OZ upgradeability validations

The OZ validations can be run alongside the existing tests:
`VALIDATE=true forge test --ffi --build-info --extra-output storageLayout`

## Deployment

### Declare env variables

Add the required env variables listed in `.env.example` to your `.env` file, and run `source .env`.

Make sure to set the `L1` and `L2` env variables according to your desired deployment environment.

Mainnet deployment:

```
L1=mainnet
L2=arbitrum_one
```

Testnet deployment:

```
L1=sepolia
L2=arbitrum_one_sepolia
```

### Deploy the bridge

Deploy the L1 and L2 tokens (not included in this repo) that must be supported by the bridge then fill in the addresses of these tokens in `script/input/{chainId}/config.json` as two arrays of address strings under the `tokens` key for both the L1 and L2 domains. On testnet, if the `tokens` key is missing for a domain, mock tokens will automatically be deployed for that domain.

The following command deploys the L1 and L2 sides of the bridge:

```
forge script script/Deploy.s.sol:Deploy --slow --multi --broadcast --verify
```

### Initialize the bridge

On mainnet, the bridge should be initialized via the spell process. On testnet, the bridge initialization can be performed via the following command:

```
forge script script/Init.s.sol:Init --slow --multi --broadcast
```

### Test the deployment

Make sure the L1 deployer account holds at least 10^18 units of the first token listed under `"l1Tokens"` in `script/output/{chainId}/deployed-latest.json`. To perform a test deposit of that token, use the following command:

```
forge script script/Deposit.s.sol:Deposit --slow --multi --broadcast
```

To subsequently perform a test withdrawal, use the following command:

```
forge script script/Withdraw.s.sol:Withdraw --slow --multi --broadcast --skip-simulation
```

Note that the `--skip-simulation` flag is required due to usage of custom Arb OpCodes in ArbSys.

The message can be relayed manually to L1 using [this Arbitrum tool](https://retryable-dashboard.arbitrum.io/).
oldchili marked this conversation as resolved.
Show resolved Hide resolved
Binary file not shown.
Binary file not shown.
Binary file not shown.
37 changes: 37 additions & 0 deletions certora/L1TokenGateway.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
{
"files": [
"src/L1TokenGateway.sol",
"certora/harness/Auxiliar.sol",
"test/mocks/InboxMock.sol",
"test/mocks/InboxMock.sol:BridgeMock",
"test/mocks/InboxMock.sol:OutboxMock",
"test/mocks/GemMock.sol",
"certora/harness/ImplementationMock.sol"
],
"solc": "solc-0.8.21",
"solc_optimize_map": {
"L1TokenGateway": "200",
"Auxiliar": "0",
"InboxMock": "0",
"BridgeMock": "0",
"OutboxMock": "0",
"GemMock": "0",
"ImplementationMock": "0"
},
"link": [
"L1TokenGateway:inbox=InboxMock",
"InboxMock:bridge=BridgeMock",
"BridgeMock:activeOutbox=OutboxMock"
],
"verify": "L1TokenGateway:certora/L1TokenGateway.spec",
"rule_sanity": "basic",
"multi_assert_check": true,
"parametric_contracts": ["L1TokenGateway"],
"build_cache": true,
"optimistic_hashing": true,
"hashing_length_bound": "512",
"prover_args": [
"-enableStorageSplitting false"
],
"msg": "L1TokenGateway"
}
Loading
Loading