Skip to content
This repository has been archived by the owner on Oct 3, 2024. It is now read-only.

feat: update bridging docs #1090

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
26 changes: 26 additions & 0 deletions docs/build/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -372,6 +372,32 @@ curl -X POST -H "Content-Type: application/json" \
}
```

### `zks_getBridgehubContract`

Returns L1 address of the Bridgehub contract.

#### Inputs

None.

#### curl example

```curl
curl -X POST -H "Content-Type: application/json" \
--data '{"jsonrpc": "2.0", "id": 1, "method": "zks_getBridgehubContract", "params": [ ]}' \
"https://mainnet.era.zksync.io"
```

#### Output

```json
{
"jsonrpc": "2.0",
"result": "0x35a54c8c757806eb6820629bc82d90e056394c92",
"id": 1
}
```

### `zks_getBytecodeByHash`

Returns bytecode of a transaction given by its hash.
Expand Down
26 changes: 14 additions & 12 deletions docs/build/developer-reference/bridging-asset.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,30 +13,34 @@ Under the hood, bridging is implemented by having two contracts
(one deployed to L1, and the second deployed to L2)
communicating with each other using [L1 <-> L2 interoperability](./l1-l2-interop.md).

Developers are free to build their own [custom bridge for any token](#custom-bridges-on-l1-and-l2) however, we provide default bridges (one for ETH and one for ERC20 tokens), which can be used for basic bridging.
Developers will soon be free to build their own custom bridges, however, we provide the default [Shared Bridge](https://github.com/matter-labs/era-contracts/blob/360a9183c2435bb8846f7047edcdb8a75b7a887c/l1-contracts/contracts/bridge/interfaces/IL1SharedBridge.sol), which can be used for basic bridging of both ETH and ERC20 tokens.

::: warning

Addresses of tokens on L2 will always differ from the same token L1 address. Also note, that tokens bridged via the default bridge only support standard ERC20 functionality, i.e. rebase tokens and other custom behavior are not supported.

:::

::: warning
At this moment, WETH bridging is not supported.
:::

## Default bridges

You can get the default bridge addresses using the [`zks_getBridgeContracts`](../api.md#zks-getbridgecontracts) endpoint or [`getDefaultBridgeAddresses`](../sdks/js/providers.md#getdefaultbridgeaddresses) method of `Provider`. Similar methods are available in the other SDKs.

You can get the address of the Bridgehub contract using the [`zks_getBridgehubContract`](../api.md#zks-getbridgehubcontract) endpoint or [`getBridgehubContractAddress`](../sdks/js/providers.md#getbridgehubcontractaddress) method of `Provider`.

### Deposits (to L2)

Users must call the `deposit` method on the L1 bridge contract, which triggers the following actions:
Users must call either `requestL2TransactionDirect` or `requestL2TransactionTwoBridges` methods on L1 Bridgehub contract, depending on the base token of the chain and the token being deposited. This triggers the following actions:

- The user's L1 tokens will be sent to the L1 bridge and become locked there.
- The L1 bridge initiates a transaction to the L2 bridge using L1 -> L2 communication.
- Within the L2 transaction, tokens will be minted and sent to the specified address on L2.
- If the token does not exist on zkSync yet, a new contract is deployed for it. Given the L2 token address is deterministic (based on the original L1 address, name and symbol), it doesn't matter who is the first person bridging it, the new L2 address will be the same.
- For every executed L1 -> L2 transaction, there will be an L2 -> L1 log message confirming its execution.
- Lastly, the `finalizeDeposit`method is called and it finalizes the deposit and mints funds on L2.

You can find example scripts to deposit ETH and ERC20 tokens using the default bridges in the how-to section of the docs.
- Lastly, the `finalizeDeposit` method is called on L2 and it finalizes the deposit and mints funds on L2.

### Withdrawals (to L1)

Expand All @@ -57,17 +61,15 @@ Users must call the `withdraw` method on the L2 bridge contract, which will trig
On the testnet environment, we automatically finalize all withdrawals, i.e., for every withdrawal, we will take care of it by making an L1 transaction that proves the inclusion for each message.
:::

## Custom bridges on L1 and L2

To build a custom bridge, create a regular Solidity contract which extends one of the interfaces mentioned below for the layer. The interfaces provide access to the zkSync Era SDK deposit and withdraw implementations.
## Era's legacy depositing interface

- L1: [IL1Bridge.sol](https://github.com/matter-labs/era-contracts/blob/main/l1-contracts/contracts/bridge/interfaces/IL1Bridge.sol)
Era still supports the legacy bridging interface, which is based on the [`L1ERC20Bridge`](https://github.com/matter-labs/era-contracts/blob/360a9183c2435bb8846f7047edcdb8a75b7a887c/l1-contracts/contracts/bridge/interfaces/IL1ERC20Bridge.sol) contract. The legacy interface will not be available for other ZK chains, but is still available on Era.

For more information, check out our example [L1 custom bridge implementation](https://github.com/matter-labs/era-contracts/blob/main/l1-contracts/contracts/bridge/L1ERC20Bridge.sol).
To use the legacy interface, users must call the `deposit` method on the L1 bridge contract. Both interfaces work identically.

- L2: [IL2Bridge.sol](https://github.com/matter-labs/era-contracts/blob/main/l1-contracts/contracts/bridge/interfaces/IL2Bridge.sol)
## Custom bridges on L1 and L2

For more information, check out our example [L2 custom bridge implementation](https://github.com/matter-labs/era-contracts/blob/main/l2-contracts/contracts/bridge/L2ERC20Bridge.sol).
Coming soon.

## Adding Tokens to the Bridge UI

Expand Down
35 changes: 19 additions & 16 deletions docs/build/developer-reference/l1-l2-interop.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ Many use cases require multi-layer interoperability, such as:

## L1 to L2 communication

L1 to L2 communication is governed by the [`IZkSync.sol`](https://github.com/matter-labs/era-contracts/blob/main/l1-contracts/contracts/zksync/interfaces/IZkSync.sol) inherited interfaces.
L1 to L2 communication is governed by the [`IBridgehub.sol`](https://github.com/matter-labs/era-contracts/blob/360a9183c2435bb8846f7047edcdb8a75b7a887c/l1-contracts/contracts/bridgehub/IBridgehub.sol) interface.

:::tip

Expand Down Expand Up @@ -69,7 +69,7 @@ Learn [how to send a message from L2 to L1 using zksync-ethers](../tutorials/how

Once a message is sent, a proof can be retrieved using the [`zks_getL2ToL1LogProof` JSON RPC API method](../api.md#zks-getl2tol1logproof).

This proof can be verified on L1 using the [`proveL2MessageInclusion`](https://github.com/matter-labs/era-contracts/blob/6250292a98179cd442516f130540d6f862c06a16/l1-contracts/contracts/zksync/facets/Mailbox.sol#L35) function, which returns a boolean value indicating whether the message was successfully sent to L1.
This proof can be verified on L1 using the [`proveL2MessageInclusion`](https://github.com/matter-labs/era-contracts/blob/360a9183c2435bb8846f7047edcdb8a75b7a887c/l1-contracts/contracts/bridgehub/IBridgehub.sol#L71) function, which returns a boolean value indicating whether the message was successfully sent to L1.

The example contract below receives the proof and the information related to the transaction sent to the L2 messenger contract. It then verifies that the message was included in an L2 block.

Expand All @@ -78,7 +78,7 @@ The example contract below receives the proof and the information related to the
pragma solidity ^0.8.0;

// Importing zkSync contract interface
import "@matterlabs/zksync-contracts/l1/contracts/zksync/interfaces/IZkSync.sol";
import "@matterlabs/zksync-contracts/l1/contracts/bridgehub/IBridgehub.sol";

contract Example {
// NOTE: The zkSync contract implements only the functionality for proving that a message belongs to a block
Expand All @@ -89,37 +89,40 @@ contract Example {
mapping(uint256 => mapping(uint256 => bool)) isL2ToL1MessageProcessed;

function consumeMessageFromL2(
// The address of the zkSync smart contract.
// The chain ID of the target ZK chain from which the message was sent, e.g. 324 for Era mainnet.
uint256 _chainId,
// The address of the Bridgehub smart contract.
// It is not recommended to hardcode it during the alpha testnet as regenesis may happen.
address _zkSyncAddress,
// zkSync block number in which the message was sent
uint256 _l2BlockNumber,
address _bridgehubAddress,
// zkSync batch number in which the message was sent
uint256 _l2BatchNumber,
// Message index, that can be received via API
uint256 _index,
// The tx number in block
uint16 _l2TxNumberInBlock,
// The tx number in batch
uint16 _l2TxNumberInBatch,
// The message that was sent from l2
bytes calldata _message,
// Merkle proof for the message
bytes32[] calldata _proof
) external {
// check that the message has not been processed yet
require(!isL2ToL1MessageProcessed[_l2BlockNumber][_index]);
require(!isL2ToL1MessageProcessed[_l2BatchNumber][_index]);

IZkSync zksync = IZkSync(_zkSyncAddress);
IBridgehub bridgehub = IBridgehub(_bridgehubAddres);
address someSender = 0x19A5bFCBE15f98Aa073B9F81b58466521479DF8D;
L2Message memory message = L2Message({sender: someSender, data: _message, txNumberInBlock:_l2TxNumberInBlock});
L2Message memory message = L2Message({sender: someSender, data: _message, txNumberInBatch: _l2TxNumberInBatch});

bool success = zksync.proveL2MessageInclusion(
_l2BlockNumber,
bool success = bridgehub.proveL2MessageInclusion(
_chainId,
_l2BatchNumber,
_index,
message,
_proof
);
require(success, "Failed to prove message inclusion");

// Mark message as processed
isL2ToL1MessageProcessed[_l2BlockNumber][_index] = true;
isL2ToL1MessageProcessed[_l2BatchNumber][_index] = true;
}
}

Expand All @@ -129,4 +132,4 @@ contract Example {

1. All transaction types are supported by the priority queue.

2. The priority queue must be fully permissionless to prevent malicious activity. For example, malicious users might send multiple transactions which push up the block gas limit to unworkable levels. To mitigate against this, submitting transactions to the priority queue is no longer free and users must pay a fee to the operator. To obtain the cost for sending an L2 to L1 message, please refer to [step 5 of how to send an L1 to L2 transaction](../../build/tutorials/how-to/send-transaction-l1-l2.md#step-by-step).
2. The priority queue must be fully permissionless to prevent malicious activity. For example, malicious users might send multiple transactions which push up the block gas limit to unworkable levels. To mitigate against this, submitting transactions to the priority queue is no longer free and users must pay a fee to the operator. To obtain the cost for sending an L1 to L2 transaction, please refer to [step 5 of how to send an L1 to L2 transaction](../../build/tutorials/how-to/send-transaction-l1-l2.md#step-by-step).
17 changes: 17 additions & 0 deletions docs/build/sdks/js/providers.md
Original file line number Diff line number Diff line change
Expand Up @@ -482,6 +482,23 @@ const provider = Provider.getDefaultProvider(types.Network.Sepolia);
console.log(`Default bridges: ${toJSON(await provider.getDefaultBridgeAddresses())}`);
```

### `getBridgehubContractAddress`

Returns the address of the zkSync Era Bridgehub contract on L1.

```ts
async getBridgehubContractAddress(): Promise<Address>
```

#### Example

```ts
import { Provider, types } from "zksync-ethers";

const provider = Provider.getDefaultProvider(types.Network.Sepolia);
console.log(`Bridgehub contract: ${await provider.getBridgehubContractAddress()}`);
```

### `getDefaultProvider`

Static method which returns a Provider object from the RPC URL or localhost.
Expand Down
55 changes: 45 additions & 10 deletions docs/build/tutorials/how-to/send-transaction-l1-l2.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ Along with zkSync Era's built-in censorship resistance that requires multi-layer

1. Import the zkSync Era library or contract containing the required functionality.

The import gives access to the [`IZkSync.sol`](https://github.com/matter-labs/era-contracts/blob/main/l1-contracts/contracts/zksync/interfaces/IZkSync.sol) inherited interfaces that include the gas estimation functionality.
The import gives access to the [`IBridgehub.sol`](https://github.com/matter-labs/era-contracts/blob/360a9183c2435bb8846f7047edcdb8a75b7a887c/l1-contracts/contracts/bridgehub/IBridgehub.sol) interface that includes the gas estimation functionality.

You can do it using yarn (recommended), or [download the contracts](https://github.com/matter-labs/era-contracts) from the repo.

Expand Down Expand Up @@ -119,6 +119,7 @@ Along with zkSync Era's built-in censorship resistance that requires multi-layer

5. Get the base cost by calling the [`l2TransactionBaseCost`](https://github.com/matter-labs/era-contracts/blob/87cd8d7b0f8c02e9672c0603a821641a566b5dd8/l1-contracts/contracts/zksync/interfaces/IMailbox.sol#L131) function with:

- The chain ID of the target ZK chain as `_chainId`.
- The gas price returned at step 2 as `_gasPrice`.
- The gas value returned at step 3 as `_l2GasLimit`.
- A constant representing how much gas is required to publish a byte of data from L1 to L2 as `_l2GasPerPubdataByteLimit`. At the time of writing, the JavaScript API provides this constant as [`REQUIRED_L1_TO_L2_GAS_PER_PUBDATA_LIMIT`](../../sdks/js/utils.md#gas).
Expand All @@ -128,6 +129,7 @@ Along with zkSync Era's built-in censorship resistance that requires multi-layer

```solidity
function l2TransactionBaseCost(
uint256 _chainId,
uint256 _gasPrice,
uint256 _l2GasLimit,
uint256 _l2GasPerPubdataByteLimit
Expand All @@ -141,14 +143,17 @@ Along with zkSync Era's built-in censorship resistance that requires multi-layer
gasLimit: BigNumberish;
gasPerPubdataByte?: BigNumberish;
gasPrice?: BigNumberish;
chainId?: BigNumberish;
}): Promise<BigNumber> {
const zksyncContract = await this.getMainContract();
const parameters = { ...layer1TxDefaults(), ...params };
parameters.gasPrice ??= await this._providerL1().getGasPrice();
parameters.gasPerPubdataByte ??= REQUIRED_L1_TO_L2_GAS_PER_PUBDATA_LIMIT;
parameters.chainId ??= (await this.getNetwork()).chainId;

return BigNumber.from(
await zksyncContract.l2TransactionBaseCost(
parameters.chainId,
parameters.gasPrice,
parameters.gasLimit,
parameters.gasPerPubdataByte
Expand All @@ -169,7 +174,7 @@ Along with zkSync Era's built-in censorship resistance that requires multi-layer
}
```

7. Send the transaction, including the gas price and base cost in the value parameters, by calling the [`requestL2Transaction`](https://github.com/matter-labs/era-contracts/blob/87cd8d7b0f8c02e9672c0603a821641a566b5dd8/l1-contracts/contracts/zksync/interfaces/IMailbox.sol#L121) function.
7. Send the transaction, including the gas price and base cost in the value parameters, by calling the [`requestL2TransactionTwoBridges`](https://github.com/matter-labs/era-contracts/blob/360a9183c2435bb8846f7047edcdb8a75b7a887c/l1-contracts/contracts/bridgehub/Bridgehub.sol#L264) or [`requestL2TransactionDirect`](https://github.com/matter-labs/era-contracts/blob/360a9183c2435bb8846f7047edcdb8a75b7a887c/l1-contracts/contracts/bridgehub/Bridgehub.sol#L218).

Include the gas limit value from step 3 as `_l2GasLimit` and the `REQUIRED_L1_TO_L2_GAS_PER_PUBDATA_LIMIT` constant as `_l2GasPerPubdataByteLimit`.

Expand All @@ -179,15 +184,45 @@ Along with zkSync Era's built-in censorship resistance that requires multi-layer
@tab Solidity

```solidity
function requestL2Transaction(
address _contractL2,
uint256 _l2Value,
bytes calldata _calldata,
uint256 _l2GasLimit,
uint256 _l2GasPerPubdataByteLimit,
bytes[] calldata _factoryDeps,
address _refundRecipient
/// This method assumes that either ether is the base token or the msg.sender has approved mintValue allowance for the shared bridge.
/// This means this is not ideal for contract calls, as the contract would have to handle token allowance of the base token.
function requestL2TransactionDirect(
L2TransactionRequestDirect calldata _request
) external payable returns (bytes32 canonicalTxHash);

struct L2TransactionRequestDirect {
uint256 chainId;
uint256 mintValue;
address l2Contract;
uint256 l2Value;
bytes l2Calldata;
uint256 l2GasLimit;
uint256 l2GasPerPubdataByteLimit;
bytes[] factoryDeps;
address refundRecipient;
}

/// This method assumes that either ether is the base token or
/// the msg.sender has approved the shared bridge with the mintValue,
/// and also the necessary approvals are given for the second bridge.
/// Each contract that handles the users ERC20 tokens needs approvals from the user, this contract allows
/// the user to approve for each token only its respective bridge
/// This function is great for contract calls to L2, the secondBridge can be any contract.
function requestL2TransactionTwoBridges(
L2TransactionRequestTwoBridgesOuter calldata _request
) external payable returns (bytes32 canonicalTxHash);

struct L2TransactionRequestTwoBridgesOuter {
uint256 chainId;
uint256 mintValue;
uint256 l2Value;
uint256 l2GasLimit;
uint256 l2GasPerPubdataByteLimit;
address refundRecipient;
address secondBridgeAddress;
uint256 secondBridgeValue;
bytes secondBridgeCalldata;
}
```

@tab TypeScript
Expand Down
Loading