Skip to content

Commit

Permalink
uLxLy Bridge Commands (#448)
Browse files Browse the repository at this point in the history
* feat: copying for alonso

* refactor: switching to cobra.. needs testing

* fix: minor issues related to missing field and 0x prefix

* fix: deposit_cnt type, missing function call, call data in wrong level

* feat: adding ability to override global index while the bug is fixed

* fix: dropping string tag

* feat: adding dry-run and gas-price

* feat: better logging of revert data

* fix: issue with bridge address

* fix: token too long for large bridge messages

* fix: handling 0x prefix encoding issue

* feat: intial implementation for claim-everything

* feat: adding more logging

* feat: additional logging

* feat: surpressing error logs

* fix: null tx

* feat: better defaults for dry running

* fix: dropping flag that should not be needed

* fix: some lint issues

* fix: generating docs

* docs: updating some of the examples and docs

* docs: updating

* docs: removing old file

* docs: minor review changes
  • Loading branch information
praetoriansentry authored Jan 9, 2025
1 parent 0622262 commit e6526b6
Show file tree
Hide file tree
Showing 27 changed files with 2,296 additions and 650 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ Note: Do not modify this section! It is auto-generated by `cobra` using `make ge

- [polycli signer](doc/polycli_signer.md) - Utilities for security signing transactions

- [polycli ulxly](doc/polycli_ulxly.md) - Utilities for interacting with the lxly bridge
- [polycli ulxly](doc/polycli_ulxly.md) - Utilities for interacting with the uLxLy bridge

- [polycli version](doc/polycli_version.md) - Get the current version of this application

Expand Down
70 changes: 70 additions & 0 deletions cmd/ulxly/BridgeAssetUsage.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
This command will directly attempt to make a deposit on the uLxLy bridge. This call responds to the method defined below:

```solidity
/**
* @notice Deposit add a new leaf to the merkle tree
* note If this function is called with a reentrant token, it would be possible to `claimTokens` in the same call
* Reducing the supply of tokens on this contract, and actually locking tokens in the contract.
* Therefore we recommend to third parties bridges that if they do implement reentrant call of `beforeTransfer` of some reentrant tokens
* do not call any external address in that case
* note User/UI must be aware of the existing/available networks when choosing the destination network
* @param destinationNetwork Network destination
* @param destinationAddress Address destination
* @param amount Amount of tokens
* @param token Token address, 0 address is reserved for ether
* @param forceUpdateGlobalExitRoot Indicates if the new global exit root is updated or not
* @param permitData Raw data of the call `permit` of the token
*/
function bridgeAsset(
uint32 destinationNetwork,
address destinationAddress,
uint256 amount,
address token,
bool forceUpdateGlobalExitRoot,
bytes calldata permitData
) public payable virtual ifNotEmergencyState nonReentrant {
```

The source of this method is [here](https://github.com/0xPolygonHermez/zkevm-contracts/blob/c8659e6282340de7bdb8fdbf7924a9bd2996bc98/contracts/v2/PolygonZkEVMBridgeV2.sol#L198-L219).
Below is an example of how we would make simple bridge of native ETH from Sepolia (L1) into Cardona (L2).

```bash
polycli ulxly bridge asset \
--bridge-address 0x528e26b25a34a4A5d0dbDa1d57D318153d2ED582 \
--private-key 0x32430699cd4f46ab2422f1df4ad6546811be20c9725544e99253a887e971f92b \
--destination-network 1 \
--value 10000000000000000 \
--rpc-url https://sepolia.drpc.org
```

[This](https://sepolia.etherscan.io/tx/0xf57b8171b2f62dce3eedbe3e50d5ee8413d61438af64286b5017ed9d5d154816) is the transaction that was created and mined from running this command.

Here is another example that will bridge a [test ERC20 token](https://sepolia.etherscan.io/address/0xC92AeF5873d058a76685140F3328B0DED79733Af) from Sepolia (L1) into Cardona (L2). In order for this to work, the token would need to have an [approval](https://sepolia.etherscan.io/tx/0x028513b13a2a7899de4db56e60d1dad66c7b7e29f91c54f385fdfdfc8f14b8b4#eventlog) for the bridge to spend tokens for that particular user.

```bash
polycli ulxly bridge asset \
--bridge-address 0x528e26b25a34a4A5d0dbDa1d57D318153d2ED582 \
--private-key 0x32430699cd4f46ab2422f1df4ad6546811be20c9725544e99253a887e971f92b \
--destination-network 1 \
--value 10000000000000000 \
--token-address 0xC92AeF5873d058a76685140F3328B0DED79733Af \
--destination-address 0x3878Cff9d621064d393EEF92bF1e12A944c5ba84 \
--rpc-url https://sepolia.drpc.org
```

[This](https://sepolia.etherscan.io/tx/0x8ed1c2c0f2e994c86867f401c86fea3c709a28a18629d473cf683049f176fa93) is the transaction that was created and mined from running this command.

Assuming you have funds on L2, a bridge from L2 to L1 looks pretty much the same.
The command below will bridge `123456` of the native ETH on Cardona (L2) back to network 0 which corresponds to Sepolia (L1).

```bash
polycli ulxly bridge asset \
--bridge-address 0x528e26b25a34a4A5d0dbDa1d57D318153d2ED582 \
--private-key 0x32430699cd4f46ab2422f1df4ad6546811be20c9725544e99253a887e971f92b \
--destination-network 0 \
--value 123456 \
--destination-address 0x3878Cff9d621064d393EEF92bF1e12A944c5ba84 \
--rpc-url https://rpc.cardona.zkevm-rpc.com
```

[This](https://cardona-zkevm.polygonscan.com/tx/0x0294dae3cfb26881e5dde9f182531aa5be0818956d029d50e9872543f020df2e) is the transaction that was created and mined from running this command.
66 changes: 66 additions & 0 deletions cmd/ulxly/BridgeMessageUsage.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
This command is very similar to `polycli ulxly bridge asset`, but instead this is a more generic interface that can be used to transfer ETH and make a contract call. This is the underlying solidity interface that we're referencing.

```solidity
/**
* @notice Bridge message and send ETH value
* note User/UI must be aware of the existing/available networks when choosing the destination network
* @param destinationNetwork Network destination
* @param destinationAddress Address destination
* @param forceUpdateGlobalExitRoot Indicates if the new global exit root is updated or not
* @param metadata Message metadata
*/
function bridgeMessage(
uint32 destinationNetwork,
address destinationAddress,
bool forceUpdateGlobalExitRoot,
bytes calldata metadata
) external payable ifNotEmergencyState {
```

The source code for this particular method is [here](https://github.com/0xPolygonHermez/zkevm-contracts/blob/c8659e6282340de7bdb8fdbf7924a9bd2996bc98/contracts/v2/PolygonZkEVMBridgeV2.sol#L324-L337).

Below is a simple example of using this command to bridge a small amount of ETH from Sepolia (L1) to Cardona (L2). In this case, we're not including any call data, so it's essentially equivalent to a `bridge asset` call, but the deposit will not be automatically claimed on L2.

```bash
polycli ulxly bridge message \
--bridge-address 0x528e26b25a34a4A5d0dbDa1d57D318153d2ED582 \
--private-key 0x32430699cd4f46ab2422f1df4ad6546811be20c9725544e99253a887e971f92b \
--destination-network 1 \
--value 10000000000000000 \
--rpc-url https://sepolia.drpc.org
```

[This](https://sepolia.etherscan.io/tx/0x1a6e2be69fa65e866889d95403b2fe820f08b6a07b96c6afbde646b8092addb2) is the transaction that was generated and mined from this command.

In most cases, you'll want to specify some `call-data` and a `destination-address` in order for a contract to be called on the destination chain. For example:
```bash
polycli ulxly bridge message \
--bridge-address 0x528e26b25a34a4A5d0dbDa1d57D318153d2ED582 \
--private-key 0x32430699cd4f46ab2422f1df4ad6546811be20c9725544e99253a887e971f92b \
--destination-network 1 \
--destination-address 0xC92AeF5873d058a76685140F3328B0DED79733Af \
--call-data 0x40c10f190000000000000000000000003878cff9d621064d393eef92bf1e12a944c5ba84000000000000000000000000000000000000000000000000002386f26fc10000 \
--value 0 \
--rpc-url https://sepolia.drpc.org
```
[This](https://sepolia.etherscan.io/tx/0x517b9d827a3a81770d608a6b997e230d992e1e0cabc0fd2797285693b1cc6a9f) is the transaction that was created and mined from running the above command.

In this case, I've configured the destination address to be a test contract I've deployed on L2.
```soldity
// SPDX-License-Identifier: AGPL-3.0
pragma solidity 0.8.20;
contract MessageEmitter {
event MessageReceived (address originAddress, uint32 originNetwork, bytes data);
function onMessageReceived(address originAddress, uint32 originNetwork, bytes memory data) external payable {
emit MessageReceived(originAddress, originNetwork, data);
}
}
```

The idea is to have minimal contract that will meet the expected interface of the bridge contract: https://github.com/0xPolygonHermez/zkevm-contracts/blob/v9.0.0-rc.3-pp/contracts/interfaces/IBridgeMessageReceiver.sol

In this case, I didn't bother implementing the proxy to an ERC20 or extending some ERC20 contract. I'm just emitting an event to know that the transaction actually fired as expected.
The calldata comes from running this command `cast calldata 'mint(address account, uint256 amount)' 0x3878Cff9d621064d393EEF92bF1e12A944c5ba84 10000000000000000`. Again, in this case no ERC20 will be minted because I didn't set it up.

35 changes: 35 additions & 0 deletions cmd/ulxly/BridgeWETHMessageUsage.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
This command is not used very often but can be used on L2 networks that have a gas token.

```solidity
/**
* @notice Bridge message and send ETH value
* note User/UI must be aware of the existing/available networks when choosing the destination network
* @param destinationNetwork Network destination
* @param destinationAddress Address destination
* @param amountWETH Amount of WETH tokens
* @param forceUpdateGlobalExitRoot Indicates if the new global exit root is updated or not
* @param metadata Message metadata
*/
function bridgeMessageWETH(
uint32 destinationNetwork,
address destinationAddress,
uint256 amountWETH,
bool forceUpdateGlobalExitRoot,
bytes calldata metadata
) external ifNotEmergencyState {
```
[Here](https://github.com/0xPolygonHermez/zkevm-contracts/blob/c8659e6282340de7bdb8fdbf7924a9bd2996bc98/contracts/v2/PolygonZkEVMBridgeV2.sol#L352-L367) is the source code that corresponds to this interface.

Assuming the network is configured with a gas token, you could call this method like this:

```bash
polycli ulxly bridge weth \
--bridge-address 0x528e26b25a34a4A5d0dbDa1d57D318153d2ED582 \
--destination-address 0x3878Cff9d621064d393EEF92bF1e12A944c5ba84 \
--private-key 0x32430699cd4f46ab2422f1df4ad6546811be20c9725544e99253a887e971f92b \
--value 123456 \
--destination-network 1 \
--rpc-url http://l2-rpc-url.invalid \
--token-address $WETH_ADDRESS
```

80 changes: 80 additions & 0 deletions cmd/ulxly/ClaimAssetUsage.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
This command will connect to the bridge service, generate a proof, and then attempt to claim the deposit on which never network is referred to in the `--rpc-url` argument.
This is the corresponding interface in the bridge contract:

```solidity
/**
* @notice Verify merkle proof and withdraw tokens/ether
* @param smtProofLocalExitRoot Smt proof to proof the leaf against the network exit root
* @param smtProofRollupExitRoot Smt proof to proof the rollupLocalExitRoot against the rollups exit root
* @param globalIndex Global index is defined as:
* | 191 bits | 1 bit | 32 bits | 32 bits |
* | 0 | mainnetFlag | rollupIndex | localRootIndex |
* note that only the rollup index will be used only in case the mainnet flag is 0
* note that global index do not assert the unused bits to 0.
* This means that when synching the events, the globalIndex must be decoded the same way that in the Smart contract
* to avoid possible synch attacks
* @param mainnetExitRoot Mainnet exit root
* @param rollupExitRoot Rollup exit root
* @param originNetwork Origin network
* @param originTokenAddress Origin token address, 0 address is reserved for gas token address. If WETH address is zero, means this gas token is ether, else means is a custom erc20 gas token
* @param destinationNetwork Network destination
* @param destinationAddress Address destination
* @param amount Amount of tokens
* @param metadata Abi encoded metadata if any, empty otherwise
*/
function claimAsset(
bytes32[_DEPOSIT_CONTRACT_TREE_DEPTH] calldata smtProofLocalExitRoot,
bytes32[_DEPOSIT_CONTRACT_TREE_DEPTH] calldata smtProofRollupExitRoot,
uint256 globalIndex,
bytes32 mainnetExitRoot,
bytes32 rollupExitRoot,
uint32 originNetwork,
address originTokenAddress,
uint32 destinationNetwork,
address destinationAddress,
uint256 amount,
bytes calldata metadata
) external ifNotEmergencyState {
```

[Here](https://github.com/0xPolygonHermez/zkevm-contracts/blob/c8659e6282340de7bdb8fdbf7924a9bd2996bc98/contracts/v2/PolygonZkEVMBridgeV2.sol#L433-L465) is a direct link to the source code as well.

In order to claim an asset or a message, you need to know deposit count. Usually this is in the event data of the transaction. Alternatively, you can usually directly attempt to see the pending deposits by querying the bridge API directly. In the case of Cardona, the bridge service is running here: https://bridge-api.cardona.zkevm-rpc.com

```bash
curl -s https://bridge-api.cardona.zkevm-rpc.com/bridges/0x3878Cff9d621064d393EEF92bF1e12A944c5ba84 | jq '.'
```

In the output of the above command, I can see a deposit that looks like this:
```json
{
"leaf_type": 0,
"orig_net": 0,
"orig_addr": "0x0000000000000000000000000000000000000000",
"amount": "123456",
"dest_net": 0,
"dest_addr": "0x3878Cff9d621064d393EEF92bF1e12A944c5ba84",
"block_num": "9695587",
"deposit_cnt": 9075,
"network_id": 1,
"tx_hash": "0x0294dae3cfb26881e5dde9f182531aa5be0818956d029d50e9872543f020df2e",
"claim_tx_hash": "",
"metadata": "0x",
"ready_for_claim": true,
"global_index": "9075"
}
```

If we want to claim this deposit, we can use a command like this:

```bash
polycli ulxly claim asset \
--bridge-address 0x528e26b25a34a4A5d0dbDa1d57D318153d2ED582 \
--bridge-service-url https://bridge-api.cardona.zkevm-rpc.com \
--private-key 0x32430699cd4f46ab2422f1df4ad6546811be20c9725544e99253a887e971f92b \
--deposit-network 1 \
--deposit-count 9075 \
--rpc-url https://sepolia.drpc.org
```

[Here](https://sepolia.etherscan.io/tx/0x21fee6e47a3b6733034fb963b20fe7accb0fb168257450f8f0053d6af8e4bc76) is the transaction that was created and mined based on this command.
84 changes: 84 additions & 0 deletions cmd/ulxly/ClaimMessageUsage.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
This command is used to claim a message type deposit. Here is the interface of the method that's being used:

```solidity
/**
* @notice Verify merkle proof and execute message
* If the receiving address is an EOA, the call will result as a success
* Which means that the amount of ether will be transferred correctly, but the message
* will not trigger any execution
* @param smtProofLocalExitRoot Smt proof to proof the leaf against the exit root
* @param smtProofRollupExitRoot Smt proof to proof the rollupLocalExitRoot against the rollups exit root
* @param globalIndex Global index is defined as:
* | 191 bits | 1 bit | 32 bits | 32 bits |
* | 0 | mainnetFlag | rollupIndex | localRootIndex |
* note that only the rollup index will be used only in case the mainnet flag is 0
* note that global index do not assert the unused bits to 0.
* This means that when synching the events, the globalIndex must be decoded the same way that in the Smart contract
* to avoid possible synch attacks
* @param mainnetExitRoot Mainnet exit root
* @param rollupExitRoot Rollup exit root
* @param originNetwork Origin network
* @param originAddress Origin address
* @param destinationNetwork Network destination
* @param destinationAddress Address destination
* @param amount message value
* @param metadata Abi encoded metadata if any, empty otherwise
*/
function claimMessage(
bytes32[_DEPOSIT_CONTRACT_TREE_DEPTH] calldata smtProofLocalExitRoot,
bytes32[_DEPOSIT_CONTRACT_TREE_DEPTH] calldata smtProofRollupExitRoot,
uint256 globalIndex,
bytes32 mainnetExitRoot,
bytes32 rollupExitRoot,
uint32 originNetwork,
address originAddress,
uint32 destinationNetwork,
address destinationAddress,
uint256 amount,
bytes calldata metadata
) external ifNotEmergencyState {
```

[Here](https://github.com/0xPolygonHermez/zkevm-contracts/blob/c8659e6282340de7bdb8fdbf7924a9bd2996bc98/contracts/v2/PolygonZkEVMBridgeV2.sol#L588-L623) is a link to the source code.

This command is essentially identical to `claim asset`, but it's specific to deposits that are of the message leaf type rather than assets. In order to use this command, I'm going to try to claim one of the messages that I sent while testing `polycli ulxly bridge message`.

```bash
curl -s https://bridge-api.cardona.zkevm-rpc.com/bridges/0xC92AeF5873d058a76685140F3328B0DED79733Af | jq '.'
```

This will show me the deposits that are destined for the test contract that I deployed on L2. At the moment here is the deposit I'm interested in:

```json
{
"leaf_type": 1,
"orig_net": 0,
"orig_addr": "0x3878Cff9d621064d393EEF92bF1e12A944c5ba84",
"amount": "0",
"dest_net": 1,
"dest_addr": "0xC92AeF5873d058a76685140F3328B0DED79733Af",
"block_num": "7435415",
"deposit_cnt": 67305,
"network_id": 0,
"tx_hash": "0x517b9d827a3a81770d608a6b997e230d992e1e0cabc0fd2797285693b1cc6a9f",
"claim_tx_hash": "",
"metadata": "0x40c10f190000000000000000000000003878cff9d621064d393eef92bf1e12a944c5ba84000000000000000000000000000000000000000000000000002386f26fc10000",
"ready_for_claim": true,
"global_index": "18446744073709618921"
}
```

I'm going to use this command to try to claim this message on L2.

```bash
polycli ulxly claim message \
--bridge-address 0x528e26b25a34a4A5d0dbDa1d57D318153d2ED582 \
--bridge-service-url https://bridge-api.cardona.zkevm-rpc.com \
--private-key 0x32430699cd4f46ab2422f1df4ad6546811be20c9725544e99253a887e971f92b \
--destination-address 0xC92AeF5873d058a76685140F3328B0DED79733Af \
--deposit-network 0 \
--deposit-count 67305 \
--rpc-url https://rpc.cardona.zkevm-rpc.com
```

[Here](https://cardona-zkevm.polygonscan.com/tx/0x6df4c4e43776d703bf1996334a4e1975bb3c124192563c93e3d199d9240dd56f#eventlog) is the transaction that was generated by this command. Everything looks to have worked properly. The `MessageReceived(address,uint32,bytes)` event with signature `0xe97c9b3f13b44bc13bde4743ae654dff72f8dc2ff9ff6070efc5999f77a37716` showed up in the explorer so our contract fired properly when the claim was made.
Loading

0 comments on commit e6526b6

Please sign in to comment.