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

L2 direct bridging #792

Merged
merged 26 commits into from
Mar 13, 2024
Merged

L2 direct bridging #792

merged 26 commits into from
Mar 13, 2024

Conversation

lukasz-zimnoch
Copy link
Member

@lukasz-zimnoch lukasz-zimnoch commented Feb 28, 2024

Refs: #750

Here we present the smart contracts necessary to enable the L2 direct bridging (a.k.a native bridging) feature. This mechanism allows getting canonical TBTC on the given supported L2 chain, without the need to touch the L1 Ethereum chain the tBTC protocol is deployed on.

Changes made as part of this pull request introduce a generic mechanism that can be deployed on all supported L2 EVM-based chains and deploy the mechanism on Ethereum Sepolia and Base Sepolia chains for testing purposes.

Motivation

Right now, a user of the supported L2 chain willing to obtain canonical L2 TBTC has to go the following path:

  1. Generate a Bitcoin deposit address (using tBTC dApp or SDK)
  2. Make a deposit transaction on Bitcoin
  3. Reveal the Bitcoin transaction to the tBTC Bridge to start the minting process (Ethereum transaction)
  4. Once TBTC is minted on Ethereum, go to the Wormhole Token Portal and bridge minted TBTC to the given L2 (another Ethereum transaction)
  5. Canonical L2 TBTC lands on the user account automatically

This flow is unwieldy and has major drawbacks:

  • It's complicated and requires multiple transactions
  • It requires L2 users to interact with the L1 Ethereum chain
  • It requires interacting with the Wormhole Token Portal

The idea behind direct bridging is simplifying the above flow to something like:

  1. Generate a Bitcoin deposit address (using dApp or SDK)
  2. Make a deposit transaction on Bitcoin
  3. Reveal the Bitcoin transaction to the tBTC Bridge using a single transaction on the L2 chain
  4. Canonical L2 TBTC lands on the user account automatically

Although this flow still relies on Wormhole underneath, the advantages are:

  • Simpler flow with fewer transactions
  • L2 users interact only with the given L2 chain
  • No need to touch the Wormhole Token Portal
  • As a next iteration, we can even get rid of the reveal deposit transaction on L2 and use Bitcoin deposit transactions as a trigger. This will improve UX even more. See the Next iteration: Gasless bridging section for details.

High-level architecture

The high-level architecture of the direct briding mechanism is presented on the chart below:

bob-i1
  • The green contracts are existing tBTC contracts that form the current bridging flow based on the Wormhole Token Portal (see RFC 8). The TBTC Bridge component is the Bridge contract deployed on L1 Ethereum responsible for minting the L1 TBTC token. The L2WormholeGateway contract has the authority to mint canonical L2 TBTC on the given L2, based on received Wormhole L2 TBTC tokens. The L2TBTC component is the canonical L2 TBTC token contract.
  • The blue contracts are the new contracts that enable the new direct bridging flow. The AbstractTBTCDepositor contract (introduced by The AbstractTBTCDepositor contract #778) provides some useful tooling facilitating integration with the tBTC Bridge and its new deposit with extra data function (developed in Expose revealDepositWithExtraData in the Bridge contract #760) which is the foundation of the L2 direct bridging mechanism. The L1BitcoinDepositor and L2BitcoinDepositor components are smart contracts handling the actual direct bridging actions on the L1 and L2 chains respectively. Those two contracts are introduced by this pull request.
  • The red contracts belong to the Wormhole protocol that handles cross-chain operations. In the context of the direct bridging mechanism, that means the transfer of minted L1 TBTC to the L2 chain. The TokenBridge contract handles the bookkeeping part of the transfer. The Relayer contract handles the actual execution of it.
  • The yellow off-chain relayer bot is the planned component (implemented as an immediate next step) that will "turn the crank" of the direct bridging mechanism. It will initialize deposits on the L1 chain (based on L2 events in the first iteration) and finalize them once L1 TBTC is minted by the tBTC Bridge contract.

The above components interact with each other in the following way:

  1. The user makes the BTC deposit funding transaction and calls the L2BitcoinDepositor.initializeDeposit function to initiate the deposit process on L2 (the call is made through a dApp and tBTC SDK).
  2. The off-chain relayer bot observes the DepositInitialized event emitted by the L2BitcoinDepositor contract.
  3. After assessing the deposit validity, the off-chain relayer bot calls the L1BitcoinDepositor.initializeDeposit function on L1.
  4. The L1BitcoinDepositor contract calls the Bridge.revealDepositWithExtra function under the hood (through the AbstractTBTCDepositor abstract contract).
  5. After some time (several hours), the Bridge contract mints L1 TBTC to the L1BitcoinDepositor contract.
  6. The off-chain bot observes the mint event and calls L1BitcoinDepositor.finalizeDeposit to start the finalization process and move L1 TBTC to the L2 chain.
  7. The L1BitcoinDepositor calls Wormhole's TokenBridge.transferTokensWithPayload function to initiate the cross-chain transfer of L1 TBTC. This call pulls out the L1 TBTC from the L1BitcoinDepositor contract and locks it in the TokenBridge.
  8. The L1BitcoinDepositor calls Wormhole's Relay.sendVaasToEvm to send a cross-chain message to L2BitcoinDepositor and notify it about a pending cross-chain transfer.
  9. The Wormhole protocol calls the L2BitcoinDepositor.receiveWormholeMessages function to deliver the cross-chain message.
  10. The L2BitcoinDepositor contract calls the L2WormholeGateway.receiveTbtc function under the hood. It passes the VAA representing the cross-chain transfer as an argument of the call.
  11. The L2WormholeGateway uses the obtained VAA to finalize the cross-chain transfer by calling the Wormhole's TokenBridge.completeTransferWithPayload function. This call redeems Wormhole-wrapped L2 TBTC from the TokenBridge.
  12. The L2WormholeGateway uses obtained Wormhole-wrapped L2 TBTC to call L2TBTC.mint and mint canonical L2 TBTC.
  13. Minted canonical L2 TBTC is transferred to the L2 user.

Immediate next steps

Changes presented in this pull request introduce the on-chain components of the direct bridging mechanism. To make the mechanism complete, the following steps need to take place:

  • Expose the L2 direct bridging feature in the tBTC Typescript SDK. This feature will be incrementally exposed for specific L2 chains the mechanism will be deployed for. The first L2 chain will be Base.
  • Implement the off-chain relayer bot

Next iteration: Gasless bridging

The plans for the future include some improvements to the first iteration of the direct bridging mechanism described above. Namely, the next iteration will bring gasless direct bridging that will not require any L2 transaction to initiate the process and will rely on a single Bitcoin funding transaction issued by the L2 user. On the technical level, the first two steps of the flow will be replaced by a direct call to the off-chain relayer's REST endpoint:

bob-i2

@lukasz-zimnoch lukasz-zimnoch self-assigned this Feb 28, 2024
@lukasz-zimnoch lukasz-zimnoch added the ⛓️ solidity Solidity contracts label Feb 28, 2024
Copy link

Solidity API documentation preview available in the artifacts of the https://github.com/keep-network/tbtc-v2/actions/runs/8081515935 check.

Copy link

Solidity API documentation preview available in the artifacts of the https://github.com/keep-network/tbtc-v2/actions/runs/8081877407 check.

Copy link

Solidity API documentation preview available in the artifacts of the https://github.com/keep-network/tbtc-v2/actions/runs/8082593661 check.

Here we present a draft implementation of the `L2BitcoinDepositor` contract
that acts as an entrypoint of the tBTC direct bridging feature on the given
L2 chain. This contract exposes the `initializeDeposit` function that takes the
deposit data (funding tx, reveal info, original depositor) and relays it to the
`L1BitcoinDepositor` contract using the Wormhole Relayer infrastructure.
Copy link

Solidity API documentation preview available in the artifacts of the https://github.com/keep-network/tbtc-v2/actions/runs/8083002051 check.

Here we present a draft implementation of the `L1BitcoinDepositor` contract
that acts as the central point of the tBTC direct bridging feature on the
L1 Ethereum chain where TBTC minting occurs. This contract exposes the
`receiveWormholeMessages` function that receives a message from
`L2BitcoinDepositor` contract (using the Wormhole Relayer infrastructure),
extracts deposit data from it, and initiates the deposit on the tBTC `Bridge`
side. Moreover, the contract also exposes the `initializeDeposit` function
directly. The goal here is to satisfy future use cases that don't rely on
Wormhole Relayer for cross-chain messaging.
So far, the `_initializeDeposit` accepted `calldata` parameters. This
does not work for integrator contracts that obtain deposit parameters
as `memory` objects (the conversion `memory -> calldata` is not possible).

To overcome this problem, we are changing parameters of `_initializeDeposit`
to be `memory` as well. This is not breaking integrators that receive
deposit parameters as `calldata` because the `calldata -> memory` conversion
is fine.
Copy link

Solidity API documentation preview available in the artifacts of the https://github.com/keep-network/tbtc-v2/actions/runs/8083278660 check.

This will be useful to get TBTC token address directly from the vault
contract.
Copy link

Solidity API documentation preview available in the artifacts of the https://github.com/keep-network/tbtc-v2/actions/runs/8097115790 check.

Here we implement the finalization of the deposit flow. This process
is started on the `L1BitcoinDepositor` side once the tBTC `Bridge` completes
minting of the TBTC token. The process consists of multiple steps:

1. The `L1BitcoinDepositor` marks the given deposit as finalized and determines
   the amount of TBTC minted.
2. The `L1BitcoinDepositor` initiates a Wormhole token transfer. This transfer
   locks minted TBTC on L1 within the Wormhole Token Bridge and unlocks
   Wormhole-wrapped L2 TBTC for the `L2WormholeGateway` contract.
3. The `L2BitcoinDepositor` sends the transfer VAA to `L2BitcoinDepositor`.
4. The `L2BitcoinDepositor` finalizes the transfer by calling the
   `L2WormholeGateway` that redeems Wormhole-wrapped L2 TBTC from the
   Wormhole Token Bridge and uses it to mint canonical L2 TBTC for the
   L2 deposit owner.
Copy link

Solidity API documentation preview available in the artifacts of the https://github.com/keep-network/tbtc-v2/actions/runs/8097176915 check.

Here we adjust the new contracts for deployment:
- We are introducing the `attach*BitcoinDepositor` functions to solve
  the chicken & egg problem that occurs upon deployment
- We are adjusting gas limits for Wormhole to real-world values
- We are getting rid of cross-chain Wormhole refunds that don't work
  for small amounts
Here we add deployment scripts for both `L*BitcoinDepositor` contracts.
We use Base as the reference chain.
We are testing the implementation by deploying it on Base Sepolia chain.
Here are the relevant deployment artifacts.
Copy link

github-actions bot commented Mar 1, 2024

Solidity API documentation preview available in the artifacts of the https://github.com/keep-network/tbtc-v2/actions/runs/8110832044 check.

Such a deposit reveal was tested for Base -> Ethereum and turned out
to be super expensive. It takes ~0,045 ETH so ~150 USD to transfer deposit
data to Ethereum. This is an unacceptable cost. To overcome that problem,
we are abandoning the idea of using Wormhole Relay for that in favor
of our own relay bot implementation. This also makes sense as eventually,
we are aiming towards using gas-less reveals on Base.
Copy link

github-actions bot commented Mar 4, 2024

Solidity API documentation preview available in the artifacts of the https://github.com/keep-network/tbtc-v2/actions/runs/8139957444 check.

Copy link

github-actions bot commented Mar 4, 2024

Solidity API documentation preview available in the artifacts of the https://github.com/keep-network/tbtc-v2/actions/runs/8143372301 check.

Copy link

github-actions bot commented Mar 5, 2024

Solidity API documentation preview available in the artifacts of the https://github.com/keep-network/tbtc-v2/actions/runs/8153045964 check.

Copy link

github-actions bot commented Mar 5, 2024

Solidity API documentation preview available in the artifacts of the https://github.com/keep-network/tbtc-v2/actions/runs/8155881875 check.

Copy link

github-actions bot commented Mar 6, 2024

Solidity API documentation preview available in the artifacts of the https://github.com/keep-network/tbtc-v2/actions/runs/8173029249 check.

Copy link

github-actions bot commented Mar 7, 2024

Solidity API documentation preview available in the artifacts of the https://github.com/keep-network/tbtc-v2/actions/runs/8185130042 check.

The OZ upgrade plugin complains about unsafe upgrade if standard versions of
`IERC20` and `SafeERC20` are used.
We need to make sure only owner can call this function.
Copy link

github-actions bot commented Mar 7, 2024

Solidity API documentation preview available in the artifacts of the https://github.com/keep-network/tbtc-v2/actions/runs/8185638599 check.

Copy link

github-actions bot commented Mar 7, 2024

Solidity API documentation preview available in the artifacts of the https://github.com/keep-network/tbtc-v2/actions/runs/8185665844 check.

It's enough to store the `gasSpent` as `uint96` which allows us to save
one storage slot. Moreover, we are increasing the default value of
`initializeDepositGasOffset` to adhere to real-world gas consumption.
function initializeV2(string memory _newVar) public {
newVar = _newVar;
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Could you please drop an empty line at each of the files?

@tomaszslabon tomaszslabon merged commit 89616d5 into main Mar 13, 2024
36 checks passed
@tomaszslabon tomaszslabon deleted the l2-direct-bridging branch March 13, 2024 12:14
michalsmiarowski added a commit that referenced this pull request Mar 19, 2024
Closes: #750

This pull request enhances the tBTC Typescript SDK with support of the L2 direct
bridging mechanism introduced by
#792. Moreover, we are adding Base
as the first L2 chain with direct bridging enabled.

### Initialization of the cross-chain contracts

To enable the SDK to work with a supported L2 chain, we introduce a new
`initializeCrossChain` method that is exposed from the main `TBTC` component.
This function sets up the SDK to work with the given L2 chain and attaches the
provided signer to the tBTC cross-chain contracts deployed there. It's worth
noting that the signer can be read-only (e.g. Ethers provider) but in that case,
only read functions of the L2 contracts will be available.

After initialization, the cross-chain contracts for the given chain can be
directly accessed using the new `TBTC.crossChainContracts` method. This
low-level access method is especially useful for reading data, e.g. get
canonical L2 TBTC balance for the given address.

### Cross-chain deposits

To leverage the new L2 direct bridging mechanism, the SDK adds the concept of a
cross-chain deposit. A cross-chain deposit is a deposit that targets an L2 chain
other than the L1 chain the tBTC system is deployed on. Such a deposit is
currently initiated using a transaction on the L2 chain (plans for the future
include gas-less initiation). On the technical level, this is handled by the new
`initiateCrossChainDeposit` method exposed by the `DepositsService` component. 

### Usage

```typescript
import { Hex, TBTC } from "../src"
import { JsonRpcProvider, Provider } from "@ethersproject/providers"
import { Signer, Wallet } from "ethers"

async function main() {
  // Create a readonly Ethers provider for the Ethereum L1 chain.
  const ethereumProvider: Provider = new JsonRpcProvider("...")
  // Create an instance of the tBTC SDK. It is enough to pass a readonly
  // Ethers provider as parameter. In this example, the SDK does not issue
  // transactions on the Ethereum L1 chain.
  const sdk: TBTC = await TBTC.initializeMainnet(ethereumProvider)

  // Create a signer for Base. This signer will be used to issue transactions
  // on the Base chain and will be used as the owner of the deposit.
  const baseSigner: Signer = new Wallet("...", new JsonRpcProvider("..."))
  // Initialize cross-chain contracts for the Base chain.
  await sdk.initializeCrossChain("Base", baseSigner)

  // Get BTC recovery address for the deposit.
  const bitcoinRecoveryAddress: string = "..."

  // Initiate a cross-chain deposit to the Base chain.
  const deposit = await sdk.deposits.initiateCrossChainDeposit(
    bitcoinRecoveryAddress,
    "Base"
  )
  // Get BTC deposit address and send funds to it.
  const depositAddress: string = await deposit.getBitcoinAddress()
  // Initiate minting once BTC transaction is made. This will call
  // revealDepositOnBehalf function of the ExampleDepositor contract
  // under the hood.
  const baseTxHash: Hex = await deposit.initiateMinting()

  console.log(
    `Minting initiated. Base transaction hash: ${baseTxHash.toPrefixedString()}`
  )
}
```
@somaweb3
Copy link

Who will be running the off-chain relayer bot? Will it be a single centralized entity?

@JSanchezFDZ JSanchezFDZ mentioned this pull request Jul 29, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
⛓️ solidity Solidity contracts
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants