Skip to content

Commit

Permalink
Adding Base module for canonical TBTC (#673)
Browse files Browse the repository at this point in the history
Part of #542

This PR is a continuation of tBTC expansion to L2s - BASE chain. Code
was largely copied from the existing cross-chain/* modules with some
naming modifications, addresses and chain ids. It creates a new
cross-chain/base module to bring tBTC to BASE with the help of Wormhole.
Here's a diagram to illustrate the flow
```
+----------------------------+         +-------------------------------------------------------------------+
|          Ethereum          |         |                                Base                               |
|                            |         |                                                                   |
|  +----------------------+  |         |  +----------------------+  +---------------------+  +----------+  |
|  | Wormhole TokenBridge |--|---------|--| Wormhole TokenBridge |--| BaseWormholeGateway |--| BaseTBTC |  |
|  +----------------------+  |         |  +----------------------+  +---------------------+  +----------+  |
|                            |         |                                                                   |
+----------------------------+         +-------------------------------------------------------------------+
```

- `Wormhole TokenBridge` on Ethereum - holds all `TBTC` bridged to BASE.
This is an external contract created by the Wormhole team.
- `Wormhole TokenBridge` on BASE - mints Wormhole wrapped `TBTC` ie.
`wormholeTBTC`. This is an external contract created by the Wormhole
team.
- `BaseWormholeGateway` on BASE - acts as a vending machine that wraps
and unwraps `wormholeTBTC` to the canonical `tBTC` token on BASE. This
is an upgradable Gateway behind OZ transparent proxy. The Threshold team
creates it.
- `BaseTBTC` canonical `tBTC` token on BASE. It is upgradable behind OZ
transparent proxy. The Threshold team creates it.
All the ownership of Threshold-created contracts is transferred to the
Threshold governance including a proxy admin ownership.

A couple of things to pay closer attention at:
- addresses under the `external/` dir
- gateway address to a wormhole chain id: `30`
- gateway addresses to other wormhole chains id: `23 - Arbitrum`, `24 -
Optimism`, `5 - Polygon`
- Goerli BASE and Mainnet BASE chain ids: `84531` and `8453`

## Deploy on BASE testnet / mainnet

Create a `.envrc` file and include the following:

```
export L2_CHAIN_API_URL=<API_URL> // e.g. "https://goerli.base.org"
export L2_ACCOUNTS_PRIVATE_KEYS=<0xOWNER_PRIV_KEY>
export BASESCAN_API_KEY=<API_KEY> // needs to be created on [basescan](https://basescan.org/)
```

```sh
  yarn deploy --network <network>
```

where `network` can be:
- `baseGeorli` // testnet
- `base` // mainnet

## Useful links:

https://docs.base.org/network-information/
https://docs.wormhole.com/wormhole/supported-environments/evm#base

TODO:
- [ ] Register TBTC on BASE using Portal on
[Testnet](https://testnet.portal-bridge-ui.pages.dev/18c38dbb6a/#/register)
and [Mainnet](https://www.portalbridge.com/#/register). After that
replace the address in `BaseWormholeTBTC.json` under `external/base`
~and `external/goerliBase`~ with a newly registered token.
  • Loading branch information
lukasz-zimnoch authored Aug 1, 2023
2 parents 93b8744 + c9ba93e commit 990dc97
Show file tree
Hide file tree
Showing 47 changed files with 14,063 additions and 0 deletions.
8 changes: 8 additions & 0 deletions cross-chain/base/.eslintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
artifacts/
build/
cache/
deployments/
export/
hardhat-dependency-compiler/
typechain/
export.json
18 changes: 18 additions & 0 deletions cross-chain/base/.eslintrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"root": true,
"extends": ["@thesis-co"],
"parserOptions": {
"ecmaVersion": 2017,
"sourceType": "module"
},
"env": {
"es6": true,
"mocha": true
},
"rules": {
"new-cap": "off",
"import/no-extraneous-dependencies": "off",
"@typescript-eslint/no-use-before-define": "off",
"no-plusplus": ["error", { "allowForLoopAfterthoughts": true }]
}
}
15 changes: 15 additions & 0 deletions cross-chain/base/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Hardhat
/artifacts/
/build/
/cache/
/export/
/external/npm
/typechain/
/export.json
/deployments/*
!/deployments/mainnet/
!/deployments/base/
!/deployments/baseGoerli/

# OZ
/.openzeppelin/unknown-*.json
3 changes: 3 additions & 0 deletions cross-chain/base/.mocharc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"require": "ts-node/register/files"
}
9 changes: 9 additions & 0 deletions cross-chain/base/.prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
.openzeppelin/
artifacts/
build/
cache/
deployments/
export/
hardhat-dependency-compiler/
typechain/
export.json
11 changes: 11 additions & 0 deletions cross-chain/base/.prettierrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
module.exports = {
...require("@thesis-co/prettier-config"),
overrides: [
{
files: "*.sol",
options: {
tabWidth: 4,
},
},
],
}
7 changes: 7 additions & 0 deletions cross-chain/base/.solhint.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"extends": "keep",
"plugins": [],
"rules": {
"func-visibility": ["error", { "ignoreConstructors": true }]
}
}
2 changes: 2 additions & 0 deletions cross-chain/base/.solhintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
hardhat-dependency-compiler/
node_modules/
1 change: 1 addition & 0 deletions cross-chain/base/.tsconfig-eslint.json
59 changes: 59 additions & 0 deletions cross-chain/base/README.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
:toc: macro

= Threshold cross-chain - Base

This package brings Bitcoin to Ethereum L2 Base. For more details please
see link:https://github.com/keep-network/tbtc-v2/blob/main/docs/rfc/rfc-8.adoc[RFC 8: Cross-chain Tokenized Threshold BTC]

== How it works?

```
+----------------------------+ +-------------------------------------------------------------------+
| Ethereum | | Base |
| | | |
| +----------------------+ | | +----------------------+ +---------------------+ +----------+ |
| | Wormhole TokenBridge |--|---------|--| Wormhole TokenBridge |--| BaseWormholeGateway |--| BaseTBTC | |
| +----------------------+ | | +----------------------+ +---------------------+ +----------+ |
| | | |
+----------------------------+ +-------------------------------------------------------------------+
```

- `BaseTBTC` canonical tBTC token on Base with a minting authority
delegated to `BaseWormholeGateway`.
- `BaseWormholeGateway` is a smart contract wrapping and unwrapping
Wormhole-specific tBTC representation into the canonical `BaseTBTC` token.

=== Updating Wormhole Gateway mapping

The deployment scripts are responsible for managing updates of the tBTC gateway
addresses across various chains. These addresses are stored in the `external/`
directory for a specific network.
It is important to note that these addresses should remain constant for the
mainnet network. However, there may be instances where a new version of a
cross-chain module is deployed to the testing network, which would require a
manual update of the corresponding address.

=== Deploy contracts

To deploy all contracts on the given network, please run:
```
yarn deploy --network <network>
```

Supported networks:
- `hardhat` - for local development
- `baseGoerli` - L2 testing network
- `base` - L2 mainnet

Currently, this module does not deploy any contracts on L1. All the existing
Wormhole contract addresses that are used in this module are stored under
`external/<network>` dir.

If contracts haven't been built yet or changes occurred, `deploy` task will build
the contracts before running the deployment script. This command produces
an `export.json` file containing contract deployment info. Note that for the
chains other than `hardhat` the following environment variables are needed:

- `L2_CHAIN_API_URL` - URL to access blockchain services, e.g. `https://goerli.base.org`
- `L2_ACCOUNTS_PRIVATE_KEYS` - Private keys for the deployer and council `<0xOwnerPrivKey,0xCouncilPrivKey>`
- `BASESCAN_API_KEY` - Base Etherscan API key
18 changes: 18 additions & 0 deletions cross-chain/base/contracts/test/BaseTBTCUpgraded.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// SPDX-License-Identifier: GPL-3.0-only

pragma solidity ^0.8.17;

import "@keep-network/tbtc-v2/contracts/l2/L2TBTC.sol";

/// @notice Canonical tBTC Token on Base - upgraded version.
/// @dev This contract is intended solely for testing purposes. As it currently
/// stands in the implementation of L2TBTC.sol, there are no reserved
/// storage gap slots available, thereby limiting the upgradability to a
/// child contract only.
contract BaseTBTCUpgraded is L2TBTC {
string public newVar;

function initializeV2(string memory _newVar) public {
newVar = _newVar;
}
}
18 changes: 18 additions & 0 deletions cross-chain/base/contracts/test/BaseWormholeGatewayUpgraded.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// SPDX-License-Identifier: GPL-3.0-only

pragma solidity ^0.8.17;

import "@keep-network/tbtc-v2/contracts/l2/L2WormholeGateway.sol";

/// @notice Wormhole gateway for L2 Base - upgraded version.
/// @dev This contract is intended solely for testing purposes. As it currently
/// stands in the implementation of L2WormholeGateway.sol, there are no
/// reserved storage gap slots available, thereby limiting the upgradability
/// to a child contract only.
contract BaseWormholeGatewayUpgraded is L2WormholeGateway {
string public newVar;

function initializeV2(string memory _newVar) public {
newVar = _newVar;
}
}
28 changes: 28 additions & 0 deletions cross-chain/base/deploy_l2/00_resolve_arbitrum_wormhole_gateway.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import type { HardhatRuntimeEnvironment } from "hardhat/types"
import type { DeployFunction } from "hardhat-deploy/types"

const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) {
const { helpers, deployments } = hre
const { log } = deployments

const ArbitrumWormholeGateway = await deployments.getOrNull(
"ArbitrumWormholeGateway"
)

if (
ArbitrumWormholeGateway &&
helpers.address.isValid(ArbitrumWormholeGateway.address)
) {
log(
`using existing ArbitrumWormholeGateway at ${ArbitrumWormholeGateway.address}`
)
} else if (hre.network.name === "hardhat") {
log("using fake ArbitrumWormholeGateway for hardhat network")
} else {
throw new Error("deployed ArbitrumWormholeGateway contract not found")
}
}

export default func

func.tags = ["ArbitrumWormholeGateway"]
21 changes: 21 additions & 0 deletions cross-chain/base/deploy_l2/00_resolve_base_token_bridge.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import type { HardhatRuntimeEnvironment } from "hardhat/types"
import type { DeployFunction } from "hardhat-deploy/types"

const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) {
const { helpers, deployments } = hre
const { log } = deployments

const BaseTokenBridge = await deployments.getOrNull("BaseTokenBridge")

if (BaseTokenBridge && helpers.address.isValid(BaseTokenBridge.address)) {
log(`using existing Base TokenBridge at ${BaseTokenBridge.address}`)
} else if (hre.network.name === "hardhat") {
log("using fake Base TokenBridge for hardhat network")
} else {
throw new Error("deployed Base TokenBridge contract not found")
}
}

export default func

func.tags = ["BaseTokenBridge"]
21 changes: 21 additions & 0 deletions cross-chain/base/deploy_l2/00_resolve_base_wormhole_tbtc.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import type { HardhatRuntimeEnvironment } from "hardhat/types"
import type { DeployFunction } from "hardhat-deploy/types"

const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) {
const { helpers, deployments } = hre
const { log } = deployments

const BaseWormholeTBTC = await deployments.getOrNull("BaseWormholeTBTC")

if (BaseWormholeTBTC && helpers.address.isValid(BaseWormholeTBTC.address)) {
log(`using existing Base WormholeTBTC at ${BaseWormholeTBTC.address}`)
} else if (hre.network.name === "hardhat") {
log("using fake Base WormholeTBTC for hardhat network")
} else {
throw new Error("deployed Base WormholeTBTC contract not found")
}
}

export default func

func.tags = ["BaseWormholeTBTC"]
28 changes: 28 additions & 0 deletions cross-chain/base/deploy_l2/00_resolve_optimism_wormhole_gateway.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import type { HardhatRuntimeEnvironment } from "hardhat/types"
import type { DeployFunction } from "hardhat-deploy/types"

const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) {
const { helpers, deployments } = hre
const { log } = deployments

const OptimismWormholeGateway = await deployments.getOrNull(
"OptimismWormholeGateway"
)

if (
OptimismWormholeGateway &&
helpers.address.isValid(OptimismWormholeGateway.address)
) {
log(
`using existing OptimismWormholeGateway at ${OptimismWormholeGateway.address}`
)
} else if (hre.network.name === "hardhat") {
log("using fake OptimismWormholeGateway for hardhat network")
} else {
throw new Error("deployed OptimismWormholeGateway contract not found")
}
}

export default func

func.tags = ["OptimismWormholeGateway"]
28 changes: 28 additions & 0 deletions cross-chain/base/deploy_l2/00_resolve_polygon_wormhole_gateway.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import type { HardhatRuntimeEnvironment } from "hardhat/types"
import type { DeployFunction } from "hardhat-deploy/types"

const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) {
const { helpers, deployments } = hre
const { log } = deployments

const PolygonWormholeGateway = await deployments.getOrNull(
"PolygonWormholeGateway"
)

if (
PolygonWormholeGateway &&
helpers.address.isValid(PolygonWormholeGateway.address)
) {
log(
`using existing PolygonWormholeGateway at ${PolygonWormholeGateway.address}`
)
} else if (hre.network.name === "hardhat") {
log("using fake PolygonWormholeGateway for hardhat network")
} else {
throw new Error("deployed PolygonWormholeGateway contract not found")
}
}

export default func

func.tags = ["PolygonWormholeGateway"]
36 changes: 36 additions & 0 deletions cross-chain/base/deploy_l2/01_deploy_base_tbtc_token.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import type { HardhatRuntimeEnvironment } from "hardhat/types"
import type { DeployFunction } from "hardhat-deploy/types"

const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) {
const { ethers, getNamedAccounts, helpers } = hre
const { deployer } = await getNamedAccounts()

const [, proxyDeployment] = await helpers.upgrades.deployProxy("BaseTBTC", {
contractName: "@keep-network/tbtc-v2/contracts/l2/L2TBTC.sol:L2TBTC",
initializerArgs: ["Base tBTC v2", "tBTC"],
factoryOpts: { signer: await ethers.getSigner(deployer) },
proxyOpts: {
kind: "transparent",
},
})

// TODO: Investigate the possibility of adding Tenderly verification for
// L2 and upgradable proxy.

// Contracts can be verified on L2 Base Etherscan in a similar way as we
// do it on L1 Etherscan
if (hre.network.tags.basescan) {
// We use `verify` instead of `verify:verify` as the `verify` task is defined
// in "@openzeppelin/hardhat-upgrades" to verify the proxy’s implementation
// contract, the proxy itself and any proxy-related contracts, as well as
// link the proxy to the implementation contract’s ABI on (Ether)scan.
await hre.run("verify", {
address: proxyDeployment.address,
constructorArgsParams: proxyDeployment.args,
})
}
}

export default func

func.tags = ["BaseTBTC"]
Loading

0 comments on commit 990dc97

Please sign in to comment.