diff --git a/README.md b/README.md index 35f8bf6b..0c8f7ba8 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/cmd/ulxly/BridgeAssetUsage.md b/cmd/ulxly/BridgeAssetUsage.md new file mode 100644 index 00000000..f0fd14bc --- /dev/null +++ b/cmd/ulxly/BridgeAssetUsage.md @@ -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. \ No newline at end of file diff --git a/cmd/ulxly/BridgeMessageUsage.md b/cmd/ulxly/BridgeMessageUsage.md new file mode 100644 index 00000000..0650833a --- /dev/null +++ b/cmd/ulxly/BridgeMessageUsage.md @@ -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. + diff --git a/cmd/ulxly/BridgeWETHMessageUsage.md b/cmd/ulxly/BridgeWETHMessageUsage.md new file mode 100644 index 00000000..652ae026 --- /dev/null +++ b/cmd/ulxly/BridgeWETHMessageUsage.md @@ -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 +``` + diff --git a/cmd/ulxly/ClaimAssetUsage.md b/cmd/ulxly/ClaimAssetUsage.md new file mode 100644 index 00000000..4af32127 --- /dev/null +++ b/cmd/ulxly/ClaimAssetUsage.md @@ -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. \ No newline at end of file diff --git a/cmd/ulxly/ClaimMessageUsage.md b/cmd/ulxly/ClaimMessageUsage.md new file mode 100644 index 00000000..5059b8f1 --- /dev/null +++ b/cmd/ulxly/ClaimMessageUsage.md @@ -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. diff --git a/cmd/ulxly/depositClaimUsage.md b/cmd/ulxly/depositClaimUsage.md deleted file mode 100644 index bfe1706c..00000000 --- a/cmd/ulxly/depositClaimUsage.md +++ /dev/null @@ -1,76 +0,0 @@ -This command will attempt to send a claim transaction to 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 ether - * @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 - ) - -``` - -Each transaction will require manual input of parameters. Example usage: - -```bash -polycli ulxly deposit-claim \ - --bridge-address 0xD71f8F956AD979Cc2988381B8A743a2fE280537D \ - --private-key 12d7de8621a77640c9241b2595ba78ce443d05e94090365ab3bb5e19df82c625 \ - --claim-index 0 \ - --claim-address 0xE34aaF64b29273B7D567FCFc40544c014EEe9970 \ - --claim-network 0 \ - --rpc-url http://127.0.0.1:32790 \ - --bridge-service-url http://127.0.0.1:32804 -``` - -This command would use the supplied private key and attempt to send a claim transaction to the bridge contract address with the input flags. -Successful deposit transaction will output logs like below: - -```bash -Claim Transaction Successful: 0x7180201b19e1aa596503d8541137d6f341e682835bf7a54aab6422c89158866b -``` - -Upon successful claim, the transferred funds can be queried in the destination network using tools like `cast balance --rpc-url ` - - -Failed deposit transactions will output logs like below: - -```bash -Claim Transaction Failed: 0x32ac34797159c79e57ae801c350bccfe5f8105d4dd3b717e31d811397e98036a -``` - -The reason for failing may be very difficult to debug. I have personally spun up a bridge-ui and compared the byte data of a successful transaction to the byte data of a failing claim transaction queried using: - -```! -curl http://127.0.0.1:32790 \ --X POST \ --H "Content-Type: application/json" \ ---data '{"method":"debug_traceTransaction","params":["0x32ac34797159c79e57ae801c350bccfe5f8105d4dd3b717e31d811397e98036a", {"tracer": "callTracer"}], "id":1,"jsonrpc":"2.0"}' | jq '.' -``` diff --git a/cmd/ulxly/depositGetUsage.md b/cmd/ulxly/depositGetUsage.md index 409467fb..b1067f83 100644 --- a/cmd/ulxly/depositGetUsage.md +++ b/cmd/ulxly/depositGetUsage.md @@ -1,5 +1,5 @@ This command will attempt to scan a range of blocks and look for uLxLy -Bridge Events. This is is the specific signature that we're interested +Bridge Events. This is the specific signature that we're interested in: ```solidity @@ -27,7 +27,7 @@ Each event that we counter will be parsed and written as JSON to stdout. Example usage: ```bash -polycli ulxly deposit-get \ +polycli ulxly get-deposits \ --bridge-address 0x528e26b25a34a4A5d0dbDa1d57D318153d2ED582 \ --rpc-url https://eth-sepolia.g.alchemy.com/v2/demo \ --from-block 4880876 \ @@ -35,12 +35,12 @@ polycli ulxly deposit-get \ --filter-size 9999 > cardona-4880876-to-6028159.ndjson ``` -This command would look for bridge events from block `4880876` to +This command will look for bridge events from block `4880876` to block `6028159` in increments of `9999` blocks at a time for the contract address `0x528e26b25a34a4A5d0dbDa1d57D318153d2ED582`. The output will be written as newline delimited JSON. -This command is very specific for the ulxly bridge and it's meant to +This command is very specific for the ulxly bridge, and it's meant to serve as the input to the proof command. diff --git a/cmd/ulxly/depositNewUsage.md b/cmd/ulxly/depositNewUsage.md deleted file mode 100644 index de9d842a..00000000 --- a/cmd/ulxly/depositNewUsage.md +++ /dev/null @@ -1,61 +0,0 @@ -This command will attempt to send a deposit transaction to the bridge contract. - -```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 - ); - -``` - -Each transaction will require manual input of parameters. Example usage: - -```bash -polycli ulxly deposit-new \ - --private-key 12d7de8621a77640c9241b2595ba78ce443d05e94090365ab3bb5e19df82c625 \ - --gas-limit 300000 \ - --amount 1000000000000000000 \ - --rpc-url http://127.0.0.1:8545 \ - --bridge-address 0xD71f8F956AD979Cc2988381B8A743a2fE280537D \ - --destination-network 1 \ - --destination-address 0xE34aaF64b29273B7D567FCFc40544c014EEe9970 -``` - -This command would use the supplied private key and attempt to send a deposit transaction to the bridge contract address with the input flags. -Successful deposit transaction will output logs like below: - -```bash -Deposit Transaction Successful: 0x8c9b82e8abdfb4aad5fccd91879397acfa73e4261282c8dc634734d05ad889d3 -``` - -Upon successful deposit, the transaction can be queried using `polycli ulxly deposit-get` command - - -Failed deposit transactions will output logs like below: - -```bash -Deposit Transaction Failed: 0x60385209b0e9db359c24c88c2fb8a5c9e4628fffe8d5fb2b5e64dfac3a2b7639 -Try increasing the gas limit: -Current gas limit: 100000 -Cumulative gas used for transaction: 98641 -``` - -The reason for failing may likely be due to the `out of gas` error. Increasing the `--gas-limit` flag value will likely resolve this. diff --git a/cmd/ulxly/proofUsage.md b/cmd/ulxly/proofUsage.md index 3b493530..b832a2dd 100644 --- a/cmd/ulxly/proofUsage.md +++ b/cmd/ulxly/proofUsage.md @@ -6,12 +6,12 @@ Example usage: ```bash polycli ulxly proof \ --file-name cardona-4880876-to-6028159.ndjson \ - --deposit-number 24386 | jq '.' + --deposit-count 24386 | jq '.' ``` In this case we are assuming we have a file `cardona-4880876-to-6028159.ndjson` that would have been generated -with a call to `polycli ulxly deposits`. The output will be the +with a call to `polycli ulxly get-deposits`. The output will be the sibling hashes necessary to prove inclusion of deposit `24386`. This is a real verifiable deposit if you'd like to sanity check: @@ -66,7 +66,10 @@ This is the proof response from polycli: ![Sample Tree](./tree-diagram.png) -When we're creating the proof here, we're essentially storing all of -the paths to the various leafs. When we want to generate a proof, we -essentially find the appropriate sibling node in the tree to prove -that the leaf is part of the given merkle root. +When we're creating the proof here, we're essentially storing the paths to the +various leafs. When we want to generate a proof, we find the appropriate sibling +node in the tree to prove that the leaf is part of the given merkle root. + +## Full example + +TODO \ No newline at end of file diff --git a/cmd/ulxly/ulxly.go b/cmd/ulxly/ulxly.go index 4a008d4d..5d6f8281 100644 --- a/cmd/ulxly/ulxly.go +++ b/cmd/ulxly/ulxly.go @@ -10,6 +10,7 @@ import ( "encoding/json" "errors" "fmt" + "github.com/ethereum/go-ethereum" "io" "math/big" "net/http" @@ -39,45 +40,6 @@ const ( TreeDepth = 32 ) -type uLxLyArgs struct { - FromBlock *uint64 - ToBlock *uint64 - RPCURL *string - BridgeAddress *string - FilterSize *uint64 - - ClaimIndex *string - ClaimAddress *string - ClaimOriginNetwork *string - ClaimDestinationNetwork *string - BridgeServiceRPCURL *string - ClaimRPCURL *string - ClaimPrivateKey *string - ClaimBridgeAddress *string - ClaimGasLimit *uint64 - ClaimChainID *string - ClaimTimeoutTxnReceipt *uint32 - ClaimMessage *bool - ClaimWETH *bool - - InputFileName *string - DepositNum *uint32 - DepositPrivateKey *string - DepositGasLimit *uint64 - Amount *int64 // HACK: This should be big.Int but depositNewCmd.PersistentFlags() doesn't support that type. - DestinationNetwork *uint32 - DestinationAddress *string - DepositRPCURL *string - DepositBridgeAddress *string - DepositChainID *string - TokenAddress *string - IsForced *bool - CallData *string - DepositTimeoutTxnReceipt *uint32 - DepositMessage *bool - DepositWETH *bool -} - type IMT struct { Branches map[uint32][]common.Hash Leaves map[uint32]common.Hash @@ -101,362 +63,657 @@ type BridgeProof struct { RollupExitRoot string `json:"rollup_exit_root"` } `json:"proof"` } +type BridgeDeposit struct { + LeafType uint8 `json:"leaf_type"` + OrigNet uint32 `json:"orig_net"` + OrigAddr string `json:"orig_addr"` + Amount string `json:"amount"` + DestNet uint32 `json:"dest_net"` + DestAddr string `json:"dest_addr"` + BlockNum string `json:"block_num"` + DepositCnt uint32 `json:"deposit_cnt"` + NetworkID uint32 `json:"network_id"` + TxHash string `json:"tx_hash"` + ClaimTxHash string `json:"claim_tx_hash"` + Metadata string `json:"metadata"` + ReadyForClaim bool `json:"ready_for_claim"` + GlobalIndex string `json:"global_index"` +} -type BridgeDeposits struct { - Deposit []struct { - LeafType int `json:"leaf_type"` - OrigNet int `json:"orig_net"` - OrigAddr string `json:"orig_addr"` - Amount string `json:"amount"` - DestNet int `json:"dest_net"` - DestAddr string `json:"dest_addr"` - BlockNum string `json:"block_num"` - DepositCnt string `json:"deposit_cnt"` - NetworkID int `json:"network_id"` - TxHash string `json:"tx_hash"` - ClaimTxHash string `json:"claim_tx_hash"` - Metadata string `json:"metadata"` - ReadyForClaim bool `json:"ready_for_claim"` - GlobalIndex string `json:"global_index"` - } `json:"deposits"` - TotalCnt string `json:"total_cnt"` -} - -var ulxlyInputArgs uLxLyArgs +type DepositID struct { + DepositCnt uint32 `json:"deposit_cnt"` + NetworkID uint32 `json:"network_id"` +} -var ULxLyCmd = &cobra.Command{ - Use: "ulxly", - Short: "Utilities for interacting with the lxly bridge", - Long: "These are low level tools for directly scanning bridge events and constructing proofs.", - Args: cobra.NoArgs, +type BridgeDepositResponse struct { + Deposit BridgeDeposit `json:"deposit"` + Code *int `json:"code"` + Message *string `json:"message"` } -//go:embed depositGetUsage.md -var depositGetUsage string -var depositGetCmd = &cobra.Command{ - Use: "deposit-get", - Short: "Get a range of deposits", - Long: depositGetUsage, - Args: cobra.NoArgs, - PreRunE: checkGetDepositArgs, - RunE: func(cmd *cobra.Command, args []string) error { - ctx := cmd.Context() - // Dial the Ethereum RPC server. - rpc, err := ethrpc.DialContext(ctx, *ulxlyInputArgs.RPCURL) - if err != nil { - log.Error().Err(err).Msg("Unable to Dial RPC") - return err +func readDeposit(cmd *cobra.Command) error { + rpcUrl, err := cmd.Flags().GetString(ArgRPCURL) + if err != nil { + return err + } + bridgeAddress, err := cmd.Flags().GetString(ArgBridgeAddress) + if err != nil { + return err + } + + toBlock := *inputUlxlyArgs.toBlock + fromBlock := *inputUlxlyArgs.fromBlock + filter := *inputUlxlyArgs.filterSize + + // Dial the Ethereum RPC server. + rpc, err := ethrpc.DialContext(cmd.Context(), rpcUrl) + if err != nil { + log.Error().Err(err).Msg("Unable to Dial RPC") + return err + } + defer rpc.Close() + ec := ethclient.NewClient(rpc) + + bridgeV2, err := ulxly.NewUlxly(common.HexToAddress(bridgeAddress), ec) + if err != nil { + return err + } + currentBlock := fromBlock + for currentBlock < toBlock { + endBlock := currentBlock + filter + if endBlock > toBlock { + endBlock = toBlock } - defer rpc.Close() - ec := ethclient.NewClient(rpc) - bridgeV2, err := ulxly.NewUlxly(common.HexToAddress(*ulxlyInputArgs.BridgeAddress), ec) + opts := bind.FilterOpts{ + Start: currentBlock, + End: &endBlock, + Context: cmd.Context(), + } + evtV2Iterator, err := bridgeV2.FilterBridgeEvent(&opts) if err != nil { return err } - fromBlock := *ulxlyInputArgs.FromBlock - toBlock := *ulxlyInputArgs.ToBlock - currentBlock := fromBlock - for currentBlock < toBlock { - endBlock := currentBlock + *ulxlyInputArgs.FilterSize - if endBlock > toBlock { - endBlock = toBlock - } - opts := bind.FilterOpts{ - Start: currentBlock, - End: &endBlock, - Context: ctx, - } - evtV2Iterator, err := bridgeV2.FilterBridgeEvent(&opts) + for evtV2Iterator.Next() { + evt := evtV2Iterator.Event + log.Info().Uint32("deposit", evt.DepositCount).Uint64("block-number", evt.Raw.BlockNumber).Msg("Found ulxly Deposit") + var jBytes []byte + jBytes, err = json.Marshal(evt) if err != nil { return err } + fmt.Println(string(jBytes)) + } + err = evtV2Iterator.Close() + if err != nil { + log.Error().Err(err).Msg("error closing event iterator") + } + currentBlock = endBlock + } - for evtV2Iterator.Next() { - evt := evtV2Iterator.Event - log.Info().Uint32("deposit", evt.DepositCount).Uint64("block-number", evt.Raw.BlockNumber).Msg("Found ulxly Deposit") - var jBytes []byte - jBytes, err = json.Marshal(evt) - if err != nil { - return err - } - fmt.Println(string(jBytes)) - } - err = evtV2Iterator.Close() - if err != nil { - log.Error().Err(err).Msg("error closing event iterator") + return nil +} + +func proof(args []string) error { + depositNumber := *inputUlxlyArgs.depositNumber + rawDepositData, err := getInputData(args) + if err != nil { + return err + } + return readDeposits(rawDepositData, uint32(depositNumber)) +} + +func emptyProof() error { + p := new(Proof) + + e := generateEmptyHashes(TreeDepth) + copy(p.Siblings[:], e) + fmt.Println(p.String()) + return nil +} + +func zeroProof() error { + p := new(Proof) + + e := generateZeroHashes(TreeDepth) + copy(p.Siblings[:], e) + fmt.Println(p.String()) + return nil +} + +type JsonError struct { + Code int `json:"code"` + Message string `json:"message"` + Data interface{} `json:"data"` +} + +func logAndReturnJsonError(cmd *cobra.Command, client *ethclient.Client, tx *types.Transaction, opts *bind.TransactOpts, err error) error { + + var callErr error + if tx != nil { + // in case the error came down to gas estimation, we can sometimes get more information by doing a call + _, callErr = client.CallContract(cmd.Context(), ethereum.CallMsg{ + From: opts.From, + To: tx.To(), + Gas: tx.Gas(), + GasPrice: tx.GasPrice(), + GasFeeCap: tx.GasFeeCap(), + GasTipCap: tx.GasTipCap(), + Value: tx.Value(), + Data: tx.Data(), + AccessList: tx.AccessList(), + BlobGasFeeCap: tx.BlobGasFeeCap(), + BlobHashes: tx.BlobHashes(), + }, nil) + + if *inputUlxlyArgs.dryRun { + castCmd := "cast call" + castCmd += fmt.Sprintf(" --rpc-url %s", *inputUlxlyArgs.rpcURL) + castCmd += fmt.Sprintf(" --from %s", opts.From.String()) + castCmd += fmt.Sprintf(" --gas-limit %d", tx.Gas()) + if tx.Type() == types.LegacyTxType { + castCmd += fmt.Sprintf(" --gas-price %s", tx.GasPrice().String()) + } else { + castCmd += fmt.Sprintf(" --gas-price %s", tx.GasFeeCap().String()) + castCmd += fmt.Sprintf(" --priority-gas-price %s", tx.GasTipCap().String()) } - currentBlock = endBlock + castCmd += fmt.Sprintf(" --value %s", tx.Value().String()) + castCmd += fmt.Sprintf(" %s", tx.To().String()) + castCmd += fmt.Sprintf(" %s", common.Bytes2Hex(tx.Data())) + log.Info().Str("cmd", castCmd).Msg("use this command to replicate the call") } + } + if err == nil { return nil - }, -} - -//go:embed depositNewUsage.md -var depositNewUsage string -var depositNewCmd = &cobra.Command{ - Use: "deposit-new", - Short: "Make a uLxLy deposit transaction", - Long: depositNewUsage, - Args: cobra.NoArgs, - PreRunE: checkDepositArgs, - RunE: func(cmd *cobra.Command, args []string) error { - ctx := cmd.Context() - // Dial the Ethereum RPC server. - client, err := ethclient.DialContext(ctx, *ulxlyInputArgs.DepositRPCURL) - if err != nil { - log.Error().Err(err).Msg("Unable to Dial RPC") - return err - } - defer client.Close() - // Initialize and assign variables required to send transaction payload - bridgeV2, privateKey, fromAddress, gasLimit, gasPrice, toAddress, signer := generateTransactionPayload(ctx, client, *ulxlyInputArgs.DepositBridgeAddress, *ulxlyInputArgs.DepositPrivateKey, *ulxlyInputArgs.DepositGasLimit, *ulxlyInputArgs.DestinationAddress, *ulxlyInputArgs.DepositChainID) - - value := big.NewInt(*ulxlyInputArgs.Amount) - tokenAddress := common.HexToAddress(*ulxlyInputArgs.TokenAddress) - callData := common.Hex2Bytes(*ulxlyInputArgs.CallData) - - tops := &bind.TransactOpts{ - Signer: func(address common.Address, transaction *types.Transaction) (*types.Transaction, error) { - return types.SignTx(transaction, signer, privateKey) - }, - From: fromAddress, - Context: ctx, - GasLimit: gasLimit, - GasPrice: gasPrice, - GasFeeCap: nil, - GasTipCap: nil, + } + + var jsonError JsonError + jsonErrorBytes, jsErr := json.Marshal(err) + if jsErr != nil { + log.Error().Err(err).Msg("Unable to interact with the bridge contract") + return err + } + + jsErr = json.Unmarshal(jsonErrorBytes, &jsonError) + if jsErr != nil { + log.Error().Err(err).Msg("Unable to interact with the bridge contract") + return err + } + + errLog := log.Error(). + Err(err). + Str("message", jsonError.Message). + Int("code", jsonError.Code). + Interface("data", jsonError.Data) + + if callErr != nil { + errLog = errLog.Err(callErr) + } + + if errCode, isValid := jsonError.Data.(string); isValid && errCode == "0x646cf558" { + // I don't want to bother with the additional error logging for previously claimed deposits + return err + } + + errLog.Msg("Unable to interact with bridge contract") + + return err +} + +func bridgeAsset(cmd *cobra.Command) error { + bridgeAddress := *inputUlxlyArgs.bridgeAddress + privateKey := *inputUlxlyArgs.privateKey + gasLimit := *inputUlxlyArgs.gasLimit + destinationAddress := *inputUlxlyArgs.destAddress + chainID := *inputUlxlyArgs.chainID + amount := *inputUlxlyArgs.value + tokenAddr := *inputUlxlyArgs.tokenAddress + callDataString := *inputUlxlyArgs.callData + destinationNetwork := *inputUlxlyArgs.destNetwork + isForced := *inputUlxlyArgs.forceUpdate + timeoutTxnReceipt := *inputUlxlyArgs.timeout + RPCURL := *inputUlxlyArgs.rpcURL + + // Dial the Ethereum RPC server. + client, err := ethclient.DialContext(cmd.Context(), RPCURL) + if err != nil { + log.Error().Err(err).Msg("Unable to Dial RPC") + return err + } + defer client.Close() + // Initialize and assign variables required to send transaction payload + bridgeV2, toAddress, auth, err := generateTransactionPayload(cmd.Context(), client, bridgeAddress, privateKey, gasLimit, destinationAddress, chainID) + if err != nil { + log.Error().Err(err).Msg("error generating transaction payload") + return err + } + + value, _ := big.NewInt(0).SetString(amount, 0) + tokenAddress := common.HexToAddress(tokenAddr) + callData := common.Hex2Bytes(strings.TrimPrefix(callDataString, "0x")) + + if tokenAddress == common.HexToAddress("0x0000000000000000000000000000000000000000") { + auth.Value = value + } + + bridgeTxn, err := bridgeV2.BridgeAsset(auth, destinationNetwork, toAddress, value, tokenAddress, isForced, callData) + if err = logAndReturnJsonError(cmd, client, bridgeTxn, auth, err); err != nil { + return err + } + log.Info().Msg("bridgeTxn: " + bridgeTxn.Hash().String()) + return WaitMineTransaction(cmd.Context(), client, bridgeTxn, timeoutTxnReceipt) +} + +func bridgeMessage(cmd *cobra.Command) error { + bridgeAddress := *inputUlxlyArgs.bridgeAddress + privateKey := *inputUlxlyArgs.privateKey + gasLimit := *inputUlxlyArgs.gasLimit + destinationAddress := *inputUlxlyArgs.destAddress + chainID := *inputUlxlyArgs.chainID + amount := *inputUlxlyArgs.value + tokenAddr := *inputUlxlyArgs.tokenAddress + callDataString := *inputUlxlyArgs.callData + destinationNetwork := *inputUlxlyArgs.destNetwork + isForced := *inputUlxlyArgs.forceUpdate + timeoutTxnReceipt := *inputUlxlyArgs.timeout + RPCURL := *inputUlxlyArgs.rpcURL + + // Dial the Ethereum RPC server. + client, err := ethclient.DialContext(cmd.Context(), RPCURL) + if err != nil { + log.Error().Err(err).Msg("Unable to Dial RPC") + return err + } + defer client.Close() + // Initialize and assign variables required to send transaction payload + bridgeV2, toAddress, auth, err := generateTransactionPayload(cmd.Context(), client, bridgeAddress, privateKey, gasLimit, destinationAddress, chainID) + if err != nil { + log.Error().Err(err).Msg("error generating transaction payload") + return err + } + + value, _ := big.NewInt(0).SetString(amount, 0) + tokenAddress := common.HexToAddress(tokenAddr) + callData := common.Hex2Bytes(strings.TrimPrefix(callDataString, "0x")) + + if tokenAddress == common.HexToAddress("0x0000000000000000000000000000000000000000") { + auth.Value = value + } + + bridgeTxn, err := bridgeV2.BridgeMessage(auth, destinationNetwork, toAddress, isForced, callData) + if err = logAndReturnJsonError(cmd, client, bridgeTxn, auth, err); err != nil { + return err + } + log.Info().Msg("bridgeTxn: " + bridgeTxn.Hash().String()) + return WaitMineTransaction(cmd.Context(), client, bridgeTxn, timeoutTxnReceipt) +} + +func bridgeWETHMessage(cmd *cobra.Command) error { + bridgeAddress := *inputUlxlyArgs.bridgeAddress + privateKey := *inputUlxlyArgs.privateKey + gasLimit := *inputUlxlyArgs.gasLimit + destinationAddress := *inputUlxlyArgs.destAddress + chainID := *inputUlxlyArgs.chainID + amount := *inputUlxlyArgs.value + callDataString := *inputUlxlyArgs.callData + destinationNetwork := *inputUlxlyArgs.destNetwork + isForced := *inputUlxlyArgs.forceUpdate + timeoutTxnReceipt := *inputUlxlyArgs.timeout + RPCURL := *inputUlxlyArgs.rpcURL + + // Dial the Ethereum RPC server. + client, err := ethclient.DialContext(cmd.Context(), RPCURL) + if err != nil { + log.Error().Err(err).Msg("Unable to Dial RPC") + return err + } + defer client.Close() + // Initialize and assign variables required to send transaction payload + bridgeV2, toAddress, auth, err := generateTransactionPayload(cmd.Context(), client, bridgeAddress, privateKey, gasLimit, destinationAddress, chainID) + if err != nil { + log.Error().Err(err).Msg("error generating transaction payload") + return err + } + // Check if WETH is allowed + wethAddress, err := bridgeV2.WETHToken(&bind.CallOpts{Pending: false}) + if err != nil { + log.Error().Err(err).Msg("error getting WETH address from the bridge smc") + return err + } + if wethAddress == (common.Address{}) { + return fmt.Errorf("bridge WETH not allowed. Native ETH token configured in this network. This tx will fail") + } + + value, _ := big.NewInt(0).SetString(amount, 0) + callData := common.Hex2Bytes(strings.TrimPrefix(callDataString, "0x")) + + bridgeTxn, err := bridgeV2.BridgeMessageWETH(auth, destinationNetwork, toAddress, value, isForced, callData) + if err = logAndReturnJsonError(cmd, client, bridgeTxn, auth, err); err != nil { + return err + } + log.Info().Msg("bridgeTxn: " + bridgeTxn.Hash().String()) + return WaitMineTransaction(cmd.Context(), client, bridgeTxn, timeoutTxnReceipt) +} + +func claimAsset(cmd *cobra.Command) error { + bridgeAddress := *inputUlxlyArgs.bridgeAddress + privateKey := *inputUlxlyArgs.privateKey + gasLimit := *inputUlxlyArgs.gasLimit + destinationAddress := *inputUlxlyArgs.destAddress + chainID := *inputUlxlyArgs.chainID + timeoutTxnReceipt := *inputUlxlyArgs.timeout + RPCURL := *inputUlxlyArgs.rpcURL + depositCount := *inputUlxlyArgs.depositCount + depositNetwork := *inputUlxlyArgs.depositNetwork + bridgeServiceUrl := *inputUlxlyArgs.bridgeServiceURL + globalIndexOverride := *inputUlxlyArgs.globalIndex + + // Dial Ethereum client + client, err := ethclient.DialContext(cmd.Context(), RPCURL) + if err != nil { + log.Error().Err(err).Msg("Unable to Dial RPC") + return err + } + defer client.Close() + // Initialize and assign variables required to send transaction payload + bridgeV2, toAddress, auth, err := generateTransactionPayload(cmd.Context(), client, bridgeAddress, privateKey, gasLimit, destinationAddress, chainID) + if err != nil { + log.Error().Err(err).Msg("error generating transaction payload") + return err + } + + ///////////////////////////////////////// TODO BORRAR + // gasPrice, err := client.SuggestGasPrice(ctx) // This call is done automatically if it is not set + // if err != nil { + // log.Error().Err(err).Msg("Cannot get suggested gas price") + // } + // auth.GasPrice = big.NewInt(0).Mul(gasPrice,big.NewInt(10)) + ///////////////////////////////////////////////////////// + + // Call the bridge service RPC URL to get the merkle proofs and exit roots and parses them to the correct formats. + bridgeServiceProofEndpoint := fmt.Sprintf("%s/merkle-proof?deposit_cnt=%d&net_id=%d", bridgeServiceUrl, depositCount, depositNetwork) + merkleProofArray, rollupMerkleProofArray, mainExitRoot, rollupExitRoot := getMerkleProofsExitRoots(bridgeServiceProofEndpoint) + + // Call the bridge service RPC URL to get the deposits data and parses them to the correct formats. + bridgeServiceDepositsEndpoint := fmt.Sprintf("%s/bridge?net_id=%d&deposit_cnt=%d", bridgeServiceUrl, depositNetwork, depositCount) + globalIndex, originAddress, amount, metadata, leafType, claimDestNetwork, claimOriginalNetwork, err := getDeposit(bridgeServiceDepositsEndpoint) + if err != nil { + log.Error().Err(err) + return err + } + if leafType != 0 { + log.Warn().Msg("Deposit leafType is not asset") + } + if globalIndexOverride != "" { + globalIndex.SetString(globalIndexOverride, 10) + } + claimTxn, err := bridgeV2.ClaimAsset(auth, merkleProofArray, rollupMerkleProofArray, globalIndex, [32]byte(mainExitRoot), [32]byte(rollupExitRoot), claimOriginalNetwork, originAddress, claimDestNetwork, toAddress, amount, metadata) + if err = logAndReturnJsonError(cmd, client, claimTxn, auth, err); err != nil { + return err + } + log.Info().Msg("claimTxn: " + claimTxn.Hash().String()) + return WaitMineTransaction(cmd.Context(), client, claimTxn, timeoutTxnReceipt) +} + +func claimMessage(cmd *cobra.Command) error { + bridgeAddress := *inputUlxlyArgs.bridgeAddress + privateKey := *inputUlxlyArgs.privateKey + gasLimit := *inputUlxlyArgs.gasLimit + destinationAddress := *inputUlxlyArgs.destAddress + chainID := *inputUlxlyArgs.chainID + timeoutTxnReceipt := *inputUlxlyArgs.timeout + RPCURL := *inputUlxlyArgs.rpcURL + depositCount := *inputUlxlyArgs.depositCount + depositNetwork := *inputUlxlyArgs.depositNetwork + bridgeServiceUrl := *inputUlxlyArgs.bridgeServiceURL + globalIndexOverride := *inputUlxlyArgs.globalIndex + + // Dial Ethereum client + client, err := ethclient.DialContext(cmd.Context(), RPCURL) + if err != nil { + log.Error().Err(err).Msg("Unable to Dial RPC") + return err + } + defer client.Close() + // Initialize and assign variables required to send transaction payload + bridgeV2, toAddress, auth, err := generateTransactionPayload(cmd.Context(), client, bridgeAddress, privateKey, gasLimit, destinationAddress, chainID) + if err != nil { + log.Error().Err(err).Msg("error generating transaction payload") + return err + } + + // Call the bridge service RPC URL to get the merkle proofs and exit roots and parses them to the correct formats. + bridgeServiceProofEndpoint := fmt.Sprintf("%s/merkle-proof?deposit_cnt=%d&net_id=%d", bridgeServiceUrl, depositCount, depositNetwork) + merkleProofArray, rollupMerkleProofArray, mainExitRoot, rollupExitRoot := getMerkleProofsExitRoots(bridgeServiceProofEndpoint) + + // Call the bridge service RPC URL to get the deposits data and parses them to the correct formats. + bridgeServiceDepositsEndpoint := fmt.Sprintf("%s/bridge?net_id=%d&deposit_cnt=%d", bridgeServiceUrl, depositNetwork, depositCount) + globalIndex, originAddress, amount, metadata, leafType, claimDestNetwork, claimOriginalNetwork, err := getDeposit(bridgeServiceDepositsEndpoint) + if err != nil { + log.Error().Err(err) + return err + } + if leafType != 1 { + log.Warn().Msg("Deposit leafType is not message") + } + if globalIndexOverride != "" { + globalIndex.SetString(globalIndexOverride, 10) + } + //ClaimMessage(opts *bind.TransactOpts, smtProofLocalExitRoot [32][32]byte, smtProofRollupExitRoot [32][32]byte, globalIndex *big.Int, mainnetExitRoot [32]byte, rollupExitRoot [32]byte, originNetwork uint32, originAddress common.Address, destinationNetwork uint32, destinationAddress common.Address, amount *big.Int, metadata []byte) (*types.Transaction, error) { + claimTxn, err := bridgeV2.ClaimMessage(auth, merkleProofArray, rollupMerkleProofArray, globalIndex, [32]byte(mainExitRoot), [32]byte(rollupExitRoot), claimOriginalNetwork, originAddress, claimDestNetwork, toAddress, amount, metadata) + if err = logAndReturnJsonError(cmd, client, claimTxn, auth, err); err != nil { + return err + } + log.Info().Msg("claimTxn: " + claimTxn.Hash().String()) + return WaitMineTransaction(cmd.Context(), client, claimTxn, timeoutTxnReceipt) +} + +func getBridgeServiceURLs() (map[uint32]string, error) { + bridgeServiceUrls := *inputUlxlyArgs.bridgeServiceURLs + urlMap := make(map[uint32]string) + for _, mapping := range bridgeServiceUrls { + pieces := strings.Split(mapping, "=") + if len(pieces) != 2 { + return nil, fmt.Errorf("bridge service url mapping should contain a networkid and url separated by an equal sign. Got: %s", mapping) } - if tokenAddress == common.HexToAddress("0x0000000000000000000000000000000000000000") { - tops = &bind.TransactOpts{ - Signer: func(address common.Address, transaction *types.Transaction) (*types.Transaction, error) { - return types.SignTx(transaction, signer, privateKey) - }, - Value: value, - From: fromAddress, - Context: ctx, - GasLimit: gasLimit, - GasPrice: gasPrice, - GasFeeCap: nil, - GasTipCap: nil, - } + networkId, err := strconv.ParseInt(pieces[0], 10, 32) + if err != nil { + return nil, err } + urlMap[uint32(networkId)] = pieces[1] + } + return urlMap, nil +} - var bridgeTxn *types.Transaction - switch { - case *ulxlyInputArgs.DepositMessage: - bridgeTxn, err = bridgeV2.BridgeMessage(tops, *ulxlyInputArgs.DestinationNetwork, toAddress, *ulxlyInputArgs.IsForced, callData) - if err != nil { - log.Error().Err(err).Msg("Unable to interact with bridge contract") - return err +func claimEverything(cmd *cobra.Command) error { + privateKey := *inputUlxlyArgs.privateKey + gasLimit := *inputUlxlyArgs.gasLimit + chainID := *inputUlxlyArgs.chainID + timeoutTxnReceipt := *inputUlxlyArgs.timeout + bridgeAddress := *inputUlxlyArgs.bridgeAddress + destinationAddress := *inputUlxlyArgs.destAddress + RPCURL := *inputUlxlyArgs.rpcURL + limit := *inputUlxlyArgs.bridgeLimit + offset := *inputUlxlyArgs.bridgeOffset + urls, err := getBridgeServiceURLs() + if err != nil { + return err + } + + depositMap := make(map[DepositID]*BridgeDeposit) + + for _, bridgeServiceUrl := range urls { + deposits, bErr := getDepositsForAddress(fmt.Sprintf("%s/bridges/%s?offset=%d&limit=%d", bridgeServiceUrl, destinationAddress, offset, limit)) + if bErr != nil { + return bErr + } + for idx, deposit := range deposits { + depId := DepositID{ + DepositCnt: deposit.DepositCnt, + NetworkID: deposit.NetworkID, } - case *ulxlyInputArgs.DepositWETH: - bridgeTxn, err = bridgeV2.BridgeMessageWETH(tops, *ulxlyInputArgs.DestinationNetwork, toAddress, value, *ulxlyInputArgs.IsForced, callData) - if err != nil { - log.Error().Err(err).Msg("Unable to interact with bridge contract") - return err + _, hasKey := depositMap[depId] + // if we haven't seen this deposit at all, we'll store it + if !hasKey { + depositMap[depId] = &deposits[idx] + continue } - default: - bridgeTxn, err = bridgeV2.BridgeAsset(tops, *ulxlyInputArgs.DestinationNetwork, toAddress, value, tokenAddress, *ulxlyInputArgs.IsForced, callData) - if err != nil { - log.Error().Err(err).Msg("Unable to interact with bridge contract") - return err + + // if this new deposit is ready for claim OR it has already been claimed we should over ride the exisitng value + if deposit.ReadyForClaim || deposit.ClaimTxHash != "" { + depositMap[depId] = &deposits[idx] } } + } - // Wait for the transaction to be mined - // TODO: Consider creating a function for this section - txnMinedTimer := time.NewTimer(time.Duration(*ulxlyInputArgs.DepositTimeoutTxnReceipt) * time.Second) - defer txnMinedTimer.Stop() - for { - select { - case <-txnMinedTimer.C: - log.Info().Msg("Wait timer for transaction receipt exceeded!") - return nil - default: - r, err := client.TransactionReceipt(ctx, bridgeTxn.Hash()) - if err != nil { - if err.Error() != "not found" { - log.Error().Err(err) - return err - } - time.Sleep(1 * time.Second) - continue - } - if r.Status != 0 { - log.Info().Interface("txHash", r.TxHash).Msg("Deposit transaction successful") - return nil - } else if r.Status == 0 { - log.Error().Interface("txHash", r.TxHash).Msg("Deposit transaction failed") - log.Info().Uint64("currentGasLimit", gasLimit).Uint64("cumulativeGasUsedForTx", r.CumulativeGasUsed).Msg("Perhaps try increasing the gas limit") - return nil - } - time.Sleep(1 * time.Second) - } + client, err := ethclient.DialContext(cmd.Context(), RPCURL) + if err != nil { + log.Error().Err(err).Msg("Unable to Dial RPC") + return err + } + defer client.Close() + + bridgeContract, _, opts, err := generateTransactionPayload(cmd.Context(), client, bridgeAddress, privateKey, gasLimit, destinationAddress, chainID) + if err != nil { + return err + } + currentNetworkID, err := bridgeContract.NetworkID(nil) + if err != nil { + return err + } + log.Info().Uint32("networkID", currentNetworkID).Msg("detected current networkid") + for _, deposit := range depositMap { + if deposit.DestNet != currentNetworkID { + log.Debug().Uint32("destination_network", deposit.DestNet).Msg("discarding deposit for different network") + continue } - }, -} - -//go:embed depositClaimUsage.md -var depositClaimUsage string -var depositClaimCmd = &cobra.Command{ - Use: "deposit-claim", - Short: "Make a uLxLy claim transaction", - Long: depositClaimUsage, - Args: cobra.NoArgs, - PreRunE: checkClaimArgs, - RunE: func(cmd *cobra.Command, args []string) error { - ctx := cmd.Context() - // Dial Ethereum client - client, err := ethclient.DialContext(ctx, *ulxlyInputArgs.ClaimRPCURL) - if err != nil { - log.Error().Err(err).Msg("Unable to Dial RPC") - return err + if deposit.ClaimTxHash != "" { + log.Info().Str("txhash", deposit.ClaimTxHash).Msg("It looks like this tx was already claimed") + continue } - defer client.Close() - // Initialize and assign variables required to send transaction payload - bridgeV2, privateKey, fromAddress, gasLimit, gasPrice, toAddress, signer := generateTransactionPayload(ctx, client, *ulxlyInputArgs.ClaimBridgeAddress, *ulxlyInputArgs.ClaimPrivateKey, *ulxlyInputArgs.ClaimGasLimit, *ulxlyInputArgs.ClaimAddress, *ulxlyInputArgs.ClaimChainID) - - // Call the bridge service RPC URL to get the merkle proofs and exit roots and parses them to the correct formats. - bridgeServiceProofEndpoint := fmt.Sprintf("%s/merkle-proof?deposit_cnt=%s&net_id=%s", *ulxlyInputArgs.BridgeServiceRPCURL, *ulxlyInputArgs.ClaimIndex, *ulxlyInputArgs.ClaimOriginNetwork) - merkleProofArray, rollupMerkleProofArray, mainExitRoot, rollupExitRoot := getMerkleProofsExitRoots(bridgeServiceProofEndpoint) - - tops := &bind.TransactOpts{ - Signer: func(address common.Address, transaction *types.Transaction) (*types.Transaction, error) { - return types.SignTx(transaction, signer, privateKey) - }, - // Value: value, - From: fromAddress, - Context: ctx, - GasLimit: gasLimit, - GasPrice: gasPrice, - GasFeeCap: nil, - GasTipCap: nil, + claimTx, dErr := claimSingleDeposit(cmd, client, bridgeContract, opts, *deposit, urls, currentNetworkID) + if dErr != nil { + log.Warn().Err(dErr).Uint32("DepositCnt", deposit.DepositCnt). + Uint32("OrigNet", deposit.OrigNet). + Uint32("DestNet", deposit.DestNet). + Uint32("NetworkID", deposit.NetworkID). + Str("OrigAddr", deposit.OrigAddr). + Str("DestAddr", deposit.DestAddr). + Msg("There was an error claiming") + continue } - - // Call the bridge service RPC URL to get the deposits data and parses them to the correct formats. - bridgeServiceDepositsEndpoint := fmt.Sprintf("%s/bridges/%s", *ulxlyInputArgs.BridgeServiceRPCURL, *ulxlyInputArgs.ClaimAddress) - globalIndex, originAddress, amount, metadata, err := getDeposits(bridgeServiceDepositsEndpoint) - if err != nil { - log.Error().Err(err) - return err + dErr = WaitMineTransaction(cmd.Context(), client, claimTx, timeoutTxnReceipt) + if dErr != nil { + log.Error().Err(dErr).Msg("error while waiting for tx to main") } + } - claimOriginNetwork, _ := strconv.Atoi(*ulxlyInputArgs.ClaimOriginNetwork) // Convert ClaimOriginNetwork to int - claimDestinationNetwork, _ := strconv.Atoi(*ulxlyInputArgs.ClaimDestinationNetwork) // Convert ClaimDestinationNetwork to int - var claimTxn *types.Transaction - switch { - case *ulxlyInputArgs.ClaimMessage: - claimTxn, err = bridgeV2.ClaimMessage(tops, merkleProofArray, rollupMerkleProofArray, globalIndex, [32]byte(mainExitRoot), [32]byte(rollupExitRoot), uint32(claimOriginNetwork), originAddress, uint32(claimDestinationNetwork), toAddress, amount, metadata) - if err != nil { - log.Error().Err(err).Msg("Unable to interact with bridge contract") - return err - } + return nil +} + +func claimSingleDeposit(cmd *cobra.Command, client *ethclient.Client, bridgeContract *ulxly.Ulxly, opts *bind.TransactOpts, deposit BridgeDeposit, bridgeURLs map[uint32]string, currentNetworkID uint32) (*types.Transaction, error) { + networkIDForBridgeService := deposit.NetworkID + if deposit.NetworkID == 0 { + networkIDForBridgeService = currentNetworkID + } + bridgeUrl, hasKey := bridgeURLs[networkIDForBridgeService] + if !hasKey { + return nil, fmt.Errorf("we don't have a bridge service url for network: %d", deposit.DestNet) + } + bridgeServiceProofEndpoint := fmt.Sprintf("%s/merkle-proof?deposit_cnt=%d&net_id=%d", bridgeUrl, deposit.DepositCnt, deposit.NetworkID) + merkleProofArray, rollupMerkleProofArray, mainExitRoot, rollupExitRoot := getMerkleProofsExitRoots(bridgeServiceProofEndpoint) + if len(mainExitRoot) != 32 || len(rollupExitRoot) != 32 { + log.Warn(). + Uint32("DepositCnt", deposit.DepositCnt). + Uint32("OrigNet", deposit.OrigNet). + Uint32("DestNet", deposit.DestNet). + Uint32("NetworkID", deposit.NetworkID). + Str("OrigAddr", deposit.OrigAddr). + Str("DestAddr", deposit.DestAddr). + Msg("deposit can't be claimed!") + return nil, fmt.Errorf("the exit roots from the bridge service were empty: %s", bridgeServiceProofEndpoint) + } + + globalIndex, isValid := new(big.Int).SetString(deposit.GlobalIndex, 10) + if !isValid { + return nil, fmt.Errorf("global index %s is not a valid integer", deposit.GlobalIndex) + } + amount, isValid := new(big.Int).SetString(deposit.Amount, 10) + if !isValid { + return nil, fmt.Errorf("amount %s is not a valid integer", deposit.Amount) + } + + originAddress := common.HexToAddress(deposit.OrigAddr) + toAddress := common.HexToAddress(deposit.DestAddr) + metadata := common.Hex2Bytes(strings.TrimPrefix(deposit.Metadata, "0x")) + + var claimTx *types.Transaction + var err error + if deposit.LeafType == 0 { + claimTx, err = bridgeContract.ClaimAsset(opts, merkleProofArray, rollupMerkleProofArray, globalIndex, [32]byte(mainExitRoot), [32]byte(rollupExitRoot), deposit.OrigNet, originAddress, deposit.DestNet, toAddress, amount, metadata) + } else { + claimTx, err = bridgeContract.ClaimMessage(opts, merkleProofArray, rollupMerkleProofArray, globalIndex, [32]byte(mainExitRoot), [32]byte(rollupExitRoot), deposit.OrigNet, originAddress, deposit.DestNet, toAddress, amount, metadata) + } + + if err = logAndReturnJsonError(cmd, client, claimTx, opts, err); err != nil { + log.Warn(). + Uint32("DepositCnt", deposit.DepositCnt). + Uint32("OrigNet", deposit.OrigNet). + Uint32("DestNet", deposit.DestNet). + Uint32("NetworkID", deposit.NetworkID). + Str("OrigAddr", deposit.OrigAddr). + Str("DestAddr", deposit.DestAddr). + Msg("attempt to claim deposit failed") + return nil, err + } + log.Info().Stringer("txhash", claimTx.Hash()).Msg("sent claim") + + return claimTx, nil +} + +// Wait for the transaction to be mined +func WaitMineTransaction(ctx context.Context, client *ethclient.Client, tx *types.Transaction, txTimeout uint64) error { + if inputUlxlyArgs.dryRun != nil && *inputUlxlyArgs.dryRun { + txJson, _ := tx.MarshalJSON() + log.Info().RawJSON("tx", txJson).Msg("Skipping receipt check. Dry run is enabled") + return nil + } + txnMinedTimer := time.NewTimer(time.Duration(txTimeout) * time.Second) + defer txnMinedTimer.Stop() + for { + select { + case <-txnMinedTimer.C: + log.Info().Msg("Wait timer for transaction receipt exceeded!") + return nil default: - claimTxn, err = bridgeV2.ClaimAsset(tops, merkleProofArray, rollupMerkleProofArray, globalIndex, [32]byte(mainExitRoot), [32]byte(rollupExitRoot), uint32(claimOriginNetwork), originAddress, uint32(claimDestinationNetwork), toAddress, amount, metadata) + r, err := client.TransactionReceipt(ctx, tx.Hash()) if err != nil { - log.Error().Err(err).Msg("Unable to interact with bridge contract") - return err - } - } - - // Wait for the transaction to be mined - // TODO: Consider creating a function for this section - txnMinedTimer := time.NewTimer(time.Duration(*ulxlyInputArgs.ClaimTimeoutTxnReceipt) * time.Second) - defer txnMinedTimer.Stop() - for { - select { - case <-txnMinedTimer.C: - log.Info().Msg("Wait timer for transaction receipt exceeded!") - return nil - default: - r, err := client.TransactionReceipt(ctx, claimTxn.Hash()) - if err != nil { - if err.Error() != "not found" { - log.Error().Err(err) - return err - } - time.Sleep(1 * time.Second) - continue - } - if r.Status != 0 { - log.Info().Interface("txHash", r.TxHash).Msg("Claim transaction successful") - return nil - } else if r.Status == 0 { - log.Info().Interface("txHash", r.TxHash).Msg("Claim transaction failed") - return nil + if err.Error() != "not found" { + log.Error().Err(err) + return err } time.Sleep(1 * time.Second) + continue } + if r.Status != 0 { + log.Info().Interface("txHash", r.TxHash).Msg("Deposit transaction successful") + return nil + } else if r.Status == 0 { + log.Error().Interface("txHash", r.TxHash).Msg("Deposit transaction failed") + log.Info().Uint64("GasUsed", tx.Gas()).Uint64("cumulativeGasUsedForTx", r.CumulativeGasUsed).Msg("Perhaps try increasing the gas limit") + return nil + } + time.Sleep(1 * time.Second) } - }, -} - -//go:embed proofUsage.md -var proofUsage string -var ProofCmd = &cobra.Command{ - Use: "proof", - Short: "generate a merkle proof", - Long: proofUsage, - Args: cobra.NoArgs, - PreRunE: checkProofArgs, - RunE: func(cmd *cobra.Command, args []string) error { - rawDepositData, err := getInputData(cmd, args) - if err != nil { - return err - } - return readDeposits(rawDepositData) - }, -} - -var EmptyProofCmd = &cobra.Command{ - Use: "empty-proof", - Short: "print an empty proof structure", - Long: `Use this command to print an empty proof response that's filled with -zero-valued siblings like -0x0000000000000000000000000000000000000000000000000000000000000000. This -can be useful when you need to submit a dummy proof.`, - Args: cobra.NoArgs, - PreRunE: checkProofArgs, - RunE: func(cmd *cobra.Command, args []string) error { - p := new(Proof) - - e := generateEmptyHashes(TreeDepth) - copy(p.Siblings[:], e) - fmt.Println(p.String()) - return nil - }, -} - -var ZeroProofCmd = &cobra.Command{ - Use: "zero-proof", - Short: "print a proof structure with the zero hashes", - Long: `Use this command to print a proof response that's filled with the zero -hashes. This values are very helpful for debugging because it would -tell you how populated the tree is and roughly which leaves and -siblings are empty. It's also helpful for sanity checking a proof -response to understand if the hashed value is part of the zero hashes -or if it's actually an intermediate hash.`, - Args: cobra.NoArgs, - PreRunE: checkProofArgs, - RunE: func(cmd *cobra.Command, args []string) error { - p := new(Proof) - - e := generateZeroHashes(TreeDepth) - copy(p.Siblings[:], e) - fmt.Println(p.String()) - return nil - }, + } } -func checkProofArgs(cmd *cobra.Command, args []string) error { - return nil -} -func getInputData(_ *cobra.Command, args []string) ([]byte, error) { - if ulxlyInputArgs.InputFileName != nil && *ulxlyInputArgs.InputFileName != "" { - return os.ReadFile(*ulxlyInputArgs.InputFileName) +func getInputData(args []string) ([]byte, error) { + fileName := *inputUlxlyArgs.inputFileName + if fileName != "" { + return os.ReadFile(fileName) } if len(args) > 1 { @@ -466,9 +723,11 @@ func getInputData(_ *cobra.Command, args []string) ([]byte, error) { return io.ReadAll(os.Stdin) } -func readDeposits(rawDeposits []byte) error { +func readDeposits(rawDeposits []byte, depositNumber uint32) error { buf := bytes.NewBuffer(rawDeposits) scanner := bufio.NewScanner(buf) + scannerBuf := make([]byte, 0) + scanner.Buffer(scannerBuf, 1024*1024) imt := new(IMT) imt.Init() seenDeposit := make(map[uint32]common.Hash, 0) @@ -497,12 +756,17 @@ func readDeposits(rawDeposits []byte) error { Str("root", common.Hash(imt.Roots[len(imt.Roots)-1]).String()). Msg("adding event to tree") // There's no point adding more leaves if we can prove the deposit already? - if evt.DepositCount >= *ulxlyInputArgs.DepositNum { + if evt.DepositCount >= depositNumber { break } } + if err := scanner.Err(); err != nil { + log.Error().Err(err).Msg("there was an error reading the deposit file") + return err + } - p := imt.GetProof(*ulxlyInputArgs.DepositNum) + log.Info().Msg("finished") + p := imt.GetProof(depositNumber) fmt.Println(p.String()) return nil } @@ -703,54 +967,54 @@ func generateEmptyHashes(height uint8) []common.Hash { return zeroHashes } -func generateTransactionPayload(ctx context.Context, client *ethclient.Client, ulxlyInputArgBridge string, ulxlyInputArgPvtKey string, ulxlyInputArgGasLimit uint64, ulxlyInputArgDestAddr string, ulxlyInputArgChainID string) (bridgeV2 *ulxly.Ulxly, privateKey *ecdsa.PrivateKey, fromAddress common.Address, gasLimit uint64, gasPrice *big.Int, toAddress common.Address, signer types.Signer) { - var err error +func generateTransactionPayload(ctx context.Context, client *ethclient.Client, ulxlyInputArgBridge string, ulxlyInputArgPvtKey string, ulxlyInputArgGasLimit uint64, ulxlyInputArgDestAddr string, ulxlyInputArgChainID string) (bridgeV2 *ulxly.Ulxly, toAddress common.Address, opts *bind.TransactOpts, err error) { + ulxlyInputArgPvtKey = strings.TrimPrefix(ulxlyInputArgPvtKey, "0x") bridgeV2, err = ulxly.NewUlxly(common.HexToAddress(ulxlyInputArgBridge), client) if err != nil { return } - privateKey, err = crypto.HexToECDSA(ulxlyInputArgPvtKey) + privateKey, err := crypto.HexToECDSA(ulxlyInputArgPvtKey) if err != nil { log.Error().Err(err).Msg("Unable to retrieve private key") + return } - publicKey := privateKey.Public() - publicKeyECDSA, ok := publicKey.(*ecdsa.PublicKey) - if !ok { - log.Error().Msg("Error casting public key to ECDSA") - } - - fromAddress = crypto.PubkeyToAddress(*publicKeyECDSA) - // value := big.NewInt(*ulxlyInputArgs.Amount) - gasLimit = ulxlyInputArgGasLimit - gasPrice, err = client.SuggestGasPrice(ctx) - if err != nil { - log.Error().Err(err).Msg("Cannot get suggested gas price") - } - // gasTipCap, err := client.SuggestGasTipCap(ctx) - // if err != nil { - // log.Error().Err(err).Msg("Cannot get suggested gas tip cap") - // } - - toAddress = common.HexToAddress(ulxlyInputArgDestAddr) + gasLimit := ulxlyInputArgGasLimit chainID := new(big.Int) // For manual input of chainID, use the user's input if ulxlyInputArgChainID != "" { chainID.SetString(ulxlyInputArgChainID, 10) } else { // If there is no user input for chainID, infer it from context - chainID, err = client.NetworkID(ctx) + chainID, err = client.ChainID(ctx) if err != nil { log.Error().Err(err).Msg("Cannot get chain ID") return } } - signer = types.LatestSignerForChainID(chainID) - - return bridgeV2, privateKey, fromAddress, gasLimit, gasPrice, toAddress, signer + opts, err = bind.NewKeyedTransactorWithChainID(privateKey, chainID) + if err != nil { + log.Error().Err(err).Msg("Cannot generate transactionOpts") + return + } + if inputUlxlyArgs.gasPrice != nil && *inputUlxlyArgs.gasPrice != "" { + gasPrice := new(big.Int) + gasPrice.SetString(*inputUlxlyArgs.gasPrice, 10) + opts.GasPrice = gasPrice + } + if inputUlxlyArgs.dryRun != nil && *inputUlxlyArgs.dryRun { + opts.NoSend = true + } + opts.Context = ctx + opts.GasLimit = gasLimit + toAddress = common.HexToAddress(ulxlyInputArgDestAddr) + if toAddress == (common.Address{}) { + toAddress = opts.From + } + return bridgeV2, toAddress, opts, err } func getMerkleProofsExitRoots(bridgeServiceProofEndpoint string) (merkleProofArray [32][32]byte, rollupMerkleProofArray [32][32]byte, mainExitRoot []byte, rollupExitRoot []byte) { @@ -779,7 +1043,7 @@ func getMerkleProofsExitRoots(bridgeServiceProofEndpoint string) (merkleProofArr merkleProof = append(merkleProof, [32]byte(byteMP)) } if len(merkleProof) == 0 { - log.Error().Msg("The Merkle Proofs cannot be retrieved, double check the input arguments and try again.") + log.Error().Str("url", bridgeServiceProofEndpoint).Msg("The Merkle Proofs cannot be retrieved, double check the input arguments and try again.") return } merkleProofArray = [32][32]byte(merkleProof) @@ -801,18 +1065,18 @@ func getMerkleProofsExitRoots(bridgeServiceProofEndpoint string) (merkleProofArr return merkleProofArray, rollupMerkleProofArray, mainExitRoot, rollupExitRoot } -func getDeposits(bridgeServiceDepositsEndpoint string) (globalIndex *big.Int, originAddress common.Address, amount *big.Int, metadata []byte, err error) { - reqBridgeDeposits, err := http.Get(bridgeServiceDepositsEndpoint) +func getDeposit(bridgeServiceDepositsEndpoint string) (globalIndex *big.Int, originAddress common.Address, amount *big.Int, metadata []byte, leafType uint8, claimDestNetwork, claimOriginalNetwork uint32, err error) { + reqBridgeDeposit, err := http.Get(bridgeServiceDepositsEndpoint) if err != nil { log.Error().Err(err) return } - bodyBridgeDeposit, err := io.ReadAll(reqBridgeDeposits.Body) // Response body is []byte + bodyBridgeDeposit, err := io.ReadAll(reqBridgeDeposit.Body) // Response body is []byte if err != nil { log.Error().Err(err) return } - var bridgeDeposit BridgeDeposits + var bridgeDeposit BridgeDepositResponse err = json.Unmarshal(bodyBridgeDeposit, &bridgeDeposit) // Parse []byte to go struct pointer, and shadow err variable if err != nil { log.Error().Err(err).Msg("Can not unmarshal JSON") @@ -822,106 +1086,378 @@ func getDeposits(bridgeServiceDepositsEndpoint string) (globalIndex *big.Int, or globalIndex = new(big.Int) amount = new(big.Int) - intClaimIndex, _ := strconv.Atoi(*ulxlyInputArgs.ClaimIndex) // Convert deposit_cnt to int - destinationNetwork, _ := strconv.Atoi(*ulxlyInputArgs.ClaimDestinationNetwork) - for index, deposit := range bridgeDeposit.Deposit { - intDepositCnt, _ := strconv.Atoi(deposit.DepositCnt) // Convert deposit_cnt to int - if intDepositCnt == intClaimIndex && destinationNetwork == deposit.DestNet { // deposit_cnt must match the user's input value - if !bridgeDeposit.Deposit[index].ReadyForClaim { - log.Error().Msg("The claim transaction is not yet ready to be claimed. Try again in a few blocks.") - return nil, common.HexToAddress("0x0"), nil, nil, errors.New("the claim transaction is not yet ready to be claimed, try again in a few blocks") - } else if bridgeDeposit.Deposit[index].ClaimTxHash != "" { - log.Info().Str("claimTxHash", bridgeDeposit.Deposit[index].ClaimTxHash).Msg("The claim transaction has already been claimed") - return nil, common.HexToAddress("0x0"), nil, nil, errors.New("the claim transaction has already been claimed") - } - originAddress = common.HexToAddress(bridgeDeposit.Deposit[index].OrigAddr) - globalIndex.SetString(bridgeDeposit.Deposit[index].GlobalIndex, 10) - amount.SetString(bridgeDeposit.Deposit[index].Amount, 10) - metadata = common.Hex2Bytes(bridgeDeposit.Deposit[index].Metadata) - return globalIndex, originAddress, amount, metadata, nil - } + defer reqBridgeDeposit.Body.Close() + if bridgeDeposit.Code != nil { + return globalIndex, originAddress, amount, metadata, leafType, claimDestNetwork, claimOriginalNetwork, fmt.Errorf("error code received getting the deposit. Code: %d, Message: %s", *bridgeDeposit.Code, *bridgeDeposit.Message) } - defer reqBridgeDeposits.Body.Close() - return nil, common.HexToAddress("0x0"), nil, nil, errors.New("failed to correctly get deposits") + if !bridgeDeposit.Deposit.ReadyForClaim { + log.Error().Msg("The claim transaction is not yet ready to be claimed. Try again in a few blocks.") + return nil, common.HexToAddress("0x0"), nil, nil, 0, 0, 0, errors.New("the claim transaction is not yet ready to be claimed, try again in a few blocks") + } else if bridgeDeposit.Deposit.ClaimTxHash != "" { + log.Info().Str("claimTxHash", bridgeDeposit.Deposit.ClaimTxHash).Msg("The claim transaction has already been claimed") + return nil, common.HexToAddress("0x0"), nil, nil, 0, 0, 0, errors.New("the claim transaction has already been claimed") + } + originAddress = common.HexToAddress(bridgeDeposit.Deposit.OrigAddr) + globalIndex.SetString(bridgeDeposit.Deposit.GlobalIndex, 10) + amount.SetString(bridgeDeposit.Deposit.Amount, 10) + + metadata = common.Hex2Bytes(strings.TrimPrefix(bridgeDeposit.Deposit.Metadata, "0x")) + leafType = bridgeDeposit.Deposit.LeafType + claimDestNetwork = bridgeDeposit.Deposit.DestNet + claimOriginalNetwork = bridgeDeposit.Deposit.OrigNet + log.Info(). + Stringer("globalIndex", globalIndex). + Stringer("originAddress", originAddress). + Stringer("amount", amount). + Str("metadata", bridgeDeposit.Deposit.Metadata). + Uint8("leafType", leafType). + Uint32("claimDestNetwork", claimDestNetwork). + Uint32("claimOriginalNetwork", claimOriginalNetwork). + Msg("Got Deposit") + return globalIndex, originAddress, amount, metadata, leafType, claimDestNetwork, claimOriginalNetwork, nil } -func checkGetDepositArgs(cmd *cobra.Command, args []string) error { - if *ulxlyInputArgs.BridgeAddress == "" { - return fmt.Errorf("please provide the bridge address") +func getDepositsForAddress(bridgeRequestUrl string) ([]BridgeDeposit, error) { + var resp struct { + Deposits []BridgeDeposit `json:"deposits"` + Total int `json:"total_cnt,string"` } - if *ulxlyInputArgs.FromBlock > *ulxlyInputArgs.ToBlock { - return fmt.Errorf("the from block should be less than the to block") + httpResp, err := http.Get(bridgeRequestUrl) + if err != nil { + return nil, err } - return nil + defer httpResp.Body.Close() + respBytes, err := io.ReadAll(httpResp.Body) + if err != nil { + return nil, err + } + err = json.Unmarshal(respBytes, &resp) + if err != nil { + return nil, err + } + if len(resp.Deposits) != resp.Total { + log.Warn().Int("total_deposits", resp.Total).Int("retrieved_deposits", len(resp.Deposits)).Msg("not all deposits were retrieved") + } + + return resp.Deposits, nil +} + +//go:embed BridgeAssetUsage.md +var bridgeAssetUsage string + +//go:embed BridgeMessageUsage.md +var bridgeMessageUsage string + +//go:embed BridgeWETHMessageUsage.md +var bridgeWETHMessageUsage string + +//go:embed ClaimAssetUsage.md +var claimAssetUsage string + +//go:embed ClaimMessageUsage.md +var claimMessageUsage string + +//go:embed proofUsage.md +var proofUsage string + +//go:embed depositGetUsage.md +var depositGetUsage string + +var ULxLyCmd = &cobra.Command{ + Use: "ulxly", + Short: "Utilities for interacting with the uLxLy bridge", + Long: "Basic utility commands for interacting with the bridge contracts, bridge services, and generating proofs", + Args: cobra.NoArgs, +} +var ulxlyBridgeAndClaimCmd = &cobra.Command{ + Args: cobra.NoArgs, + Hidden: true, } -func checkDepositArgs(cmd *cobra.Command, args []string) error { - if *ulxlyInputArgs.DepositBridgeAddress == "" { - return fmt.Errorf("please provide the bridge address") +var ulxlxBridgeCmd = &cobra.Command{ + Use: "bridge", + Short: "Commands for moving funds and sending messages from one chain to another", + Args: cobra.NoArgs, +} + +var ulxlyClaimCmd = &cobra.Command{ + Use: "claim", + Short: "Commands for claiming deposits on a particular chain", + Args: cobra.NoArgs, +} + +type ulxlyArgs struct { + gasLimit *uint64 + chainID *string + privateKey *string + addressOfPrivateKey string + value *string + rpcURL *string + bridgeAddress *string + destNetwork *uint32 + destAddress *string + tokenAddress *string + forceUpdate *bool + callData *string + timeout *uint64 + depositCount *uint64 + depositNetwork *uint64 + bridgeServiceURL *string + inputFileName *string + fromBlock *uint64 + toBlock *uint64 + filterSize *uint64 + depositNumber *uint64 + globalIndex *string + gasPrice *string + dryRun *bool + bridgeServiceURLs *[]string + bridgeLimit *int + bridgeOffset *int +} + +var inputUlxlyArgs = ulxlyArgs{} + +var ( + bridgeAssetCommand *cobra.Command + bridgeMessageCommand *cobra.Command + bridgeMessageWETHCommand *cobra.Command + claimAssetCommand *cobra.Command + claimMessageCommand *cobra.Command + claimEverythingCommand *cobra.Command + emptyProofCommand *cobra.Command + zeroProofCommand *cobra.Command + proofCommand *cobra.Command + getDepositCommand *cobra.Command +) + +const ( + ArgGasLimit = "gas-limit" + ArgChainID = "chain-id" + ArgPrivateKey = "private-key" + ArgValue = "value" + ArgRPCURL = "rpc-url" + ArgBridgeAddress = "bridge-address" + ArgDestNetwork = "destination-network" + ArgDestAddress = "destination-address" + ArgForceUpdate = "force-update-root" + ArgCallData = "call-data" + ArgTimeout = "transaction-receipt-timeout" + ArgDepositCount = "deposit-count" + ArgDepositNetwork = "deposit-network" + ArgBridgeServiceURL = "bridge-service-url" + ArgFileName = "file-name" + ArgFromBlock = "from-block" + ArgToBlock = "to-block" + ArgFilterSize = "filter-size" + ArgTokenAddress = "token-address" + ArgGlobalIndex = "global-index" + ArgDryRun = "dry-run" + ArgGasPrice = "gas-price" + ArgBridgeMappings = "bridge-service-map" + ArgBridgeLimit = "bridge-limit" + ArgBridgeOffset = "bridge-offset" +) + +func prepInputs(cmd *cobra.Command, args []string) error { + if *inputUlxlyArgs.dryRun && *inputUlxlyArgs.gasLimit == 0 { + dryRunGasLimit := uint64(10_000_000) + inputUlxlyArgs.gasLimit = &dryRunGasLimit + } + pvtKey := strings.TrimPrefix(*inputUlxlyArgs.privateKey, "0x") + + privateKey, err := crypto.HexToECDSA(pvtKey) + if err != nil { + return err } - if *ulxlyInputArgs.DepositGasLimit < 130000 && *ulxlyInputArgs.DepositGasLimit != 0 { - return fmt.Errorf("the gas limit may be too low for the transaction to pass") + publicKey := privateKey.Public() + + publicKeyECDSA, ok := publicKey.(*ecdsa.PublicKey) + if !ok { + return fmt.Errorf("cannot assert type: publicKey is not of type *ecdsa.PublicKey") } - if *ulxlyInputArgs.DepositMessage && *ulxlyInputArgs.DepositWETH { - return fmt.Errorf("choose a single deposit mode (asset, message, or WETH)") + fromAddress := crypto.PubkeyToAddress(*publicKeyECDSA) + inputUlxlyArgs.addressOfPrivateKey = fromAddress.String() + if *inputUlxlyArgs.destAddress == "" { + *inputUlxlyArgs.destAddress = fromAddress.String() + log.Info().Stringer("destAddress", fromAddress).Msg("No destination address specified. Using private key's address") } return nil } -func checkClaimArgs(cmd *cobra.Command, args []string) error { - if *ulxlyInputArgs.ClaimGasLimit < 150000 && *ulxlyInputArgs.ClaimGasLimit != 0 { - return fmt.Errorf("the gas limit may be too low for the transaction to pass") - } - if *ulxlyInputArgs.ClaimMessage && *ulxlyInputArgs.ClaimWETH { - return fmt.Errorf("choose a single claim mode (asset, message, or WETH)") +func fatalIfError(err error) { + if err == nil { + return } - return nil + log.Fatal().Err(err).Msg("Unexpected error occurred") } func init() { - ULxLyCmd.AddCommand(depositClaimCmd) - ULxLyCmd.AddCommand(depositNewCmd) - ULxLyCmd.AddCommand(depositGetCmd) - ULxLyCmd.AddCommand(ProofCmd) - ULxLyCmd.AddCommand(EmptyProofCmd) - ULxLyCmd.AddCommand(ZeroProofCmd) - - ulxlyInputArgs.ClaimIndex = depositClaimCmd.PersistentFlags().String("claim-index", "0", "The deposit count, or index to initiate a claim transaction for.") - ulxlyInputArgs.ClaimAddress = depositClaimCmd.PersistentFlags().String("claim-address", "", "The address that is receiving the bridged asset.") - ulxlyInputArgs.ClaimOriginNetwork = depositClaimCmd.PersistentFlags().String("origin-network", "0", "The network ID of the origin network.") - ulxlyInputArgs.ClaimDestinationNetwork = depositClaimCmd.PersistentFlags().String("destination-network", "1", "The network ID of the destination network.") - ulxlyInputArgs.ClaimRPCURL = depositClaimCmd.PersistentFlags().String("rpc-url", "http://127.0.0.1:8545", "The RPC endpoint of the destination network") - ulxlyInputArgs.BridgeServiceRPCURL = depositClaimCmd.PersistentFlags().String("bridge-service-url", "", "The RPC endpoint of the bridge service component.") - ulxlyInputArgs.ClaimPrivateKey = depositClaimCmd.PersistentFlags().String("private-key", "", "The private key of the sender account.") - ulxlyInputArgs.ClaimBridgeAddress = depositClaimCmd.PersistentFlags().String("bridge-address", "", "The address of the bridge contract.") - ulxlyInputArgs.ClaimGasLimit = depositClaimCmd.PersistentFlags().Uint64("gas-limit", 0, "The gas limit for the transaction. Setting this value to 0 will estimate the gas limit.") - ulxlyInputArgs.ClaimChainID = depositClaimCmd.PersistentFlags().String("chain-id", "", "The chainID.") - ulxlyInputArgs.ClaimTimeoutTxnReceipt = depositClaimCmd.PersistentFlags().Uint32("transaction-receipt-timeout", 60, "The timeout limit to check for the transaction receipt of the claim.") - ulxlyInputArgs.ClaimMessage = depositClaimCmd.PersistentFlags().Bool("claim-message", false, "Claim a message instead of an asset.") - ulxlyInputArgs.ClaimWETH = depositClaimCmd.PersistentFlags().Bool("claim-weth", false, "Claim a weth instead of an asset.") - - ulxlyInputArgs.DepositGasLimit = depositNewCmd.PersistentFlags().Uint64("gas-limit", 0, "The gas limit for the transaction. Setting this value to 0 will estimate the gas limit.") - ulxlyInputArgs.DepositChainID = depositNewCmd.PersistentFlags().String("chain-id", "", "The chainID.") - ulxlyInputArgs.DepositPrivateKey = depositNewCmd.PersistentFlags().String("private-key", "", "The private key of the sender account.") - ulxlyInputArgs.Amount = depositNewCmd.PersistentFlags().Int64("amount", 0, "The amount to send.") - ulxlyInputArgs.DepositRPCURL = depositNewCmd.PersistentFlags().String("rpc-url", "http://127.0.0.1:8545", "The RPC endpoint of the network") - ulxlyInputArgs.DepositBridgeAddress = depositNewCmd.PersistentFlags().String("bridge-address", "", "The address of the bridge contract.") - ulxlyInputArgs.DestinationNetwork = depositNewCmd.PersistentFlags().Uint32("destination-network", 1, "The destination network number.") - ulxlyInputArgs.DestinationAddress = depositNewCmd.PersistentFlags().String("destination-address", "", "The address of receiver in destination network.") - ulxlyInputArgs.TokenAddress = depositNewCmd.PersistentFlags().String("token-address", "0x0000000000000000000000000000000000000000", "The address of the token to send.") - ulxlyInputArgs.IsForced = depositNewCmd.PersistentFlags().Bool("force-update-root", true, "Force the update of the Global Exit Root.") - ulxlyInputArgs.CallData = depositNewCmd.PersistentFlags().String("call-data", "0x", "For bridging assets - raw data of the call `permit` of the token. For bridging messages - the metadata.") - ulxlyInputArgs.DepositTimeoutTxnReceipt = depositNewCmd.PersistentFlags().Uint32("transaction-receipt-timeout", 60, "The timeout limit to check for the transaction receipt of the deposit.") - ulxlyInputArgs.DepositMessage = depositNewCmd.PersistentFlags().Bool("bridge-message", false, "Bridge a message instead of an asset.") - ulxlyInputArgs.DepositWETH = depositNewCmd.PersistentFlags().Bool("bridge-weth", false, "Bridge a weth instead of an asset.") - - ulxlyInputArgs.FromBlock = depositGetCmd.PersistentFlags().Uint64("from-block", 0, "The block height to start query at.") - ulxlyInputArgs.ToBlock = depositGetCmd.PersistentFlags().Uint64("to-block", 0, "The block height to start query at.") - ulxlyInputArgs.RPCURL = depositGetCmd.PersistentFlags().String("rpc-url", "http://127.0.0.1:8545", "The RPC to query for events") - ulxlyInputArgs.FilterSize = depositGetCmd.PersistentFlags().Uint64("filter-size", 1000, "The batch size for individual filter queries") - - ulxlyInputArgs.BridgeAddress = depositGetCmd.Flags().String("bridge-address", "", "The address of the lxly bridge") - ulxlyInputArgs.InputFileName = ProofCmd.PersistentFlags().String("file-name", "", "The filename with ndjson data of deposits") - ulxlyInputArgs.DepositNum = ProofCmd.PersistentFlags().Uint32("deposit-number", 0, "The deposit that we would like to prove") + bridgeAssetCommand = &cobra.Command{ + Use: "asset", + Short: "Move ETH or an ERC20 between to chains", + Long: bridgeAssetUsage, + PreRunE: prepInputs, + RunE: func(cmd *cobra.Command, args []string) error { + return bridgeAsset(cmd) + }, + } + bridgeMessageCommand = &cobra.Command{ + Use: "message", + Short: "Send some ETH along with data from one chain to another chain", + Long: bridgeMessageUsage, + PreRunE: prepInputs, + RunE: func(cmd *cobra.Command, args []string) error { + return bridgeMessage(cmd) + }, + } + bridgeMessageWETHCommand = &cobra.Command{ + Use: "weth", + Short: "For L2's that use a gas token, use this to transfer WETH to another chain", + Long: bridgeWETHMessageUsage, + PreRunE: prepInputs, + RunE: func(cmd *cobra.Command, args []string) error { + return bridgeWETHMessage(cmd) + }, + } + claimAssetCommand = &cobra.Command{ + Use: "asset", + Short: "Claim a deposit", + Long: claimAssetUsage, + PreRunE: prepInputs, + RunE: func(cmd *cobra.Command, args []string) error { + return claimAsset(cmd) + }, + } + claimMessageCommand = &cobra.Command{ + Use: "message", + Short: "Claim a message", + Long: claimMessageUsage, + PreRunE: prepInputs, + RunE: func(cmd *cobra.Command, args []string) error { + return claimMessage(cmd) + }, + } + claimEverythingCommand = &cobra.Command{ + Use: "claim-everything", + Short: "Attempt to claim as many deposits and messages as possible", + PreRunE: prepInputs, + RunE: func(cmd *cobra.Command, args []string) error { + return claimEverything(cmd) + }, + } + emptyProofCommand = &cobra.Command{ + Use: "empty-proof", + Short: "create an empty proof", + Long: "Use this command to print an empty proof response that's filled with zero-valued siblings like 0x0000000000000000000000000000000000000000000000000000000000000000. This can be useful when you need to submit a dummy proof.", + RunE: func(cmd *cobra.Command, args []string) error { + return emptyProof() + }, + } + zeroProofCommand = &cobra.Command{ + Use: "zero-proof", + Short: "create a proof that's filled with zeros", + Long: `Use this command to print a proof response that's filled with the zero +hashes. This values are very helpful for debugging because it would +tell you how populated the tree is and roughly which leaves and +siblings are empty. It's also helpful for sanity checking a proof +response to understand if the hashed value is part of the zero hashes +or if it's actually an intermediate hash.`, + RunE: func(cmd *cobra.Command, args []string) error { + return zeroProof() + }, + } + proofCommand = &cobra.Command{ + Use: "proof", + Short: "Generate a proof for a given range of deposits", + Long: proofUsage, + RunE: func(cmd *cobra.Command, args []string) error { + return proof(args) + }, + } + getDepositCommand = &cobra.Command{ + Use: "get-deposits", + Short: "Generate ndjson for each bridge deposit over a particular range of blocks", + Long: depositGetUsage, + RunE: func(cmd *cobra.Command, args []string) error { + return readDeposit(cmd) + }, + } + + // Arguments for both bridge and claim + inputUlxlyArgs.rpcURL = ulxlyBridgeAndClaimCmd.PersistentFlags().String(ArgRPCURL, "", "the URL of the RPC to send the transaction") + inputUlxlyArgs.bridgeAddress = ulxlyBridgeAndClaimCmd.PersistentFlags().String(ArgBridgeAddress, "", "the address of the lxly bridge") + inputUlxlyArgs.gasLimit = ulxlyBridgeAndClaimCmd.PersistentFlags().Uint64(ArgGasLimit, 0, "force a gas limit when sending a transaction") + inputUlxlyArgs.chainID = ulxlyBridgeAndClaimCmd.PersistentFlags().String(ArgChainID, "", "set the chain id to be used in the transaction") + inputUlxlyArgs.privateKey = ulxlyBridgeAndClaimCmd.PersistentFlags().String(ArgPrivateKey, "", "the hex encoded private key to be used when sending the tx") + inputUlxlyArgs.destAddress = ulxlyBridgeAndClaimCmd.PersistentFlags().String(ArgDestAddress, "", "the address where the bridge will be sent to") + inputUlxlyArgs.timeout = ulxlyBridgeAndClaimCmd.PersistentFlags().Uint64(ArgTimeout, 60, "the amount of time to wait while trying to confirm a transaction receipt") + inputUlxlyArgs.gasPrice = ulxlyBridgeAndClaimCmd.PersistentFlags().String(ArgGasPrice, "", "the gas price to be used") + inputUlxlyArgs.dryRun = ulxlyBridgeAndClaimCmd.PersistentFlags().Bool(ArgDryRun, false, "do all of the transaction steps but do not send the transaction") + fatalIfError(ulxlyBridgeAndClaimCmd.MarkPersistentFlagRequired(ArgPrivateKey)) + fatalIfError(ulxlyBridgeAndClaimCmd.MarkPersistentFlagRequired(ArgRPCURL)) + fatalIfError(ulxlyBridgeAndClaimCmd.MarkPersistentFlagRequired(ArgBridgeAddress)) + + // bridge specific args + inputUlxlyArgs.forceUpdate = ulxlxBridgeCmd.PersistentFlags().Bool(ArgForceUpdate, true, "indicates if the new global exit root is updated or not") + inputUlxlyArgs.value = ulxlxBridgeCmd.PersistentFlags().String(ArgValue, "", "the amount in wei to be sent along with the transaction") + inputUlxlyArgs.destNetwork = ulxlxBridgeCmd.PersistentFlags().Uint32(ArgDestNetwork, 0, "the rollup id of the destination network") + inputUlxlyArgs.tokenAddress = ulxlxBridgeCmd.PersistentFlags().String(ArgTokenAddress, "0x0000000000000000000000000000000000000000", "the address of an ERC20 token to be used") + inputUlxlyArgs.callData = ulxlxBridgeCmd.PersistentFlags().String(ArgCallData, "0x", "call data to be passed directly with bridge-message or as an ERC20 Permit") + fatalIfError(ulxlxBridgeCmd.MarkPersistentFlagRequired(ArgDestNetwork)) + + // Claim specific args + inputUlxlyArgs.depositCount = ulxlyClaimCmd.PersistentFlags().Uint64(ArgDepositCount, 0, "the deposit count of the bridge transaction") + inputUlxlyArgs.depositNetwork = ulxlyClaimCmd.PersistentFlags().Uint64(ArgDepositNetwork, 0, "the rollup id of the network where the deposit was initially made") + inputUlxlyArgs.bridgeServiceURL = ulxlyClaimCmd.PersistentFlags().String(ArgBridgeServiceURL, "", "the URL of the bridge service") + inputUlxlyArgs.globalIndex = ulxlyClaimCmd.PersistentFlags().String(ArgGlobalIndex, "", "an override of the global index value") + fatalIfError(ulxlyClaimCmd.MarkPersistentFlagRequired(ArgDepositCount)) + fatalIfError(ulxlyClaimCmd.MarkPersistentFlagRequired(ArgDepositNetwork)) + fatalIfError(ulxlyClaimCmd.MarkPersistentFlagRequired(ArgBridgeServiceURL)) + + // Claim Everything Helper Command + inputUlxlyArgs.bridgeServiceURLs = claimEverythingCommand.Flags().StringSlice(ArgBridgeMappings, nil, "Mappings between network ids and bridge service urls. E.g. '1=http://network-1-bridgeurl,7=http://network-2-bridgeurl'") + inputUlxlyArgs.bridgeLimit = claimEverythingCommand.Flags().Int(ArgBridgeLimit, 25, "Limit the number or responses returned by the bridge service when claiming") + inputUlxlyArgs.bridgeOffset = claimEverythingCommand.Flags().Int(ArgBridgeOffset, 0, "The offset to specify for pagination of the underlying bridge service deposits") + fatalIfError(claimEverythingCommand.MarkFlagRequired(ArgBridgeMappings)) + + // Args that are just for the get deposit command + inputUlxlyArgs.fromBlock = getDepositCommand.Flags().Uint64(ArgFromBlock, 0, "The start of the range of blocks to retrieve") + inputUlxlyArgs.toBlock = getDepositCommand.Flags().Uint64(ArgToBlock, 0, "The end of the range of blocks to retrieve") + inputUlxlyArgs.filterSize = getDepositCommand.Flags().Uint64(ArgFilterSize, 1000, "The batch size for individual filter queries") + getDepositCommand.Flags().String(ArgRPCURL, "", "The RPC URL to read deposit data") + getDepositCommand.Flags().String(ArgBridgeAddress, "", "The address of the ulxly bridge") + fatalIfError(getDepositCommand.MarkFlagRequired(ArgFromBlock)) + fatalIfError(getDepositCommand.MarkFlagRequired(ArgToBlock)) + fatalIfError(getDepositCommand.MarkFlagRequired(ArgRPCURL)) + + // Args for the proof command + inputUlxlyArgs.inputFileName = proofCommand.Flags().String(ArgFileName, "", "An ndjson file with deposit data") + inputUlxlyArgs.depositNumber = proofCommand.Flags().Uint64(ArgDepositCount, 0, "The deposit number to generate a proof for") + + // Top Level + ULxLyCmd.AddCommand(ulxlyBridgeAndClaimCmd) + ULxLyCmd.AddCommand(emptyProofCommand) + ULxLyCmd.AddCommand(zeroProofCommand) + ULxLyCmd.AddCommand(proofCommand) + ULxLyCmd.AddCommand(getDepositCommand) + + ULxLyCmd.AddCommand(ulxlxBridgeCmd) + ULxLyCmd.AddCommand(ulxlyClaimCmd) + ULxLyCmd.AddCommand(claimEverythingCommand) + + // Bridge and Claim + ulxlyBridgeAndClaimCmd.AddCommand(ulxlxBridgeCmd) + ulxlyBridgeAndClaimCmd.AddCommand(ulxlyClaimCmd) + ulxlyBridgeAndClaimCmd.AddCommand(claimEverythingCommand) + + // Bridge + ulxlxBridgeCmd.AddCommand(bridgeAssetCommand) + ulxlxBridgeCmd.AddCommand(bridgeMessageCommand) + ulxlxBridgeCmd.AddCommand(bridgeMessageWETHCommand) + + // Claim + ulxlyClaimCmd.AddCommand(claimAssetCommand) + ulxlyClaimCmd.AddCommand(claimMessageCommand) } diff --git a/doc/polycli.md b/doc/polycli.md index b0b91213..00657b07 100644 --- a/doc/polycli.md +++ b/doc/polycli.md @@ -71,7 +71,7 @@ Polycli is a collection of tools that are meant to be useful while building, tes - [polycli signer](polycli_signer.md) - Utilities for security signing transactions -- [polycli ulxly](polycli_ulxly.md) - Utilities for interacting with the lxly bridge +- [polycli ulxly](polycli_ulxly.md) - Utilities for interacting with the uLxLy bridge - [polycli version](polycli_version.md) - Get the current version of this application diff --git a/doc/polycli_ulxly.md b/doc/polycli_ulxly.md index f7f37912..4bd59e72 100644 --- a/doc/polycli_ulxly.md +++ b/doc/polycli_ulxly.md @@ -11,11 +11,11 @@ ## Description -Utilities for interacting with the lxly bridge +Utilities for interacting with the uLxLy bridge ## Usage -These are low level tools for directly scanning bridge events and constructing proofs. +Basic utility commands for interacting with the bridge contracts, bridge services, and generating proofs ## Flags ```bash @@ -40,15 +40,17 @@ The command also inherits flags from parent commands. ## See also - [polycli](polycli.md) - A Swiss Army knife of blockchain tools. -- [polycli ulxly deposit-claim](polycli_ulxly_deposit-claim.md) - Make a uLxLy claim transaction +- [polycli ulxly bridge](polycli_ulxly_bridge.md) - Commands for moving funds and sending messages from one chain to another -- [polycli ulxly deposit-get](polycli_ulxly_deposit-get.md) - Get a range of deposits +- [polycli ulxly claim](polycli_ulxly_claim.md) - Commands for claiming deposits on a particular chain -- [polycli ulxly deposit-new](polycli_ulxly_deposit-new.md) - Make a uLxLy deposit transaction +- [polycli ulxly claim-everything](polycli_ulxly_claim-everything.md) - Attempt to claim as many deposits and messages as possible -- [polycli ulxly empty-proof](polycli_ulxly_empty-proof.md) - print an empty proof structure +- [polycli ulxly empty-proof](polycli_ulxly_empty-proof.md) - create an empty proof -- [polycli ulxly proof](polycli_ulxly_proof.md) - generate a merkle proof +- [polycli ulxly get-deposits](polycli_ulxly_get-deposits.md) - Generate ndjson for each bridge deposit over a particular range of blocks -- [polycli ulxly zero-proof](polycli_ulxly_zero-proof.md) - print a proof structure with the zero hashes +- [polycli ulxly proof](polycli_ulxly_proof.md) - Generate a proof for a given range of deposits + +- [polycli ulxly zero-proof](polycli_ulxly_zero-proof.md) - create a proof that's filled with zeros diff --git a/doc/polycli_ulxly__bridge.md b/doc/polycli_ulxly__bridge.md new file mode 100644 index 00000000..c643200e --- /dev/null +++ b/doc/polycli_ulxly__bridge.md @@ -0,0 +1,59 @@ +# `polycli ulxly bridge` + +> Auto-generated documentation. + +## Table of Contents + +- [Description](#description) +- [Usage](#usage) +- [Flags](#flags) +- [See Also](#see-also) + +## Description + +Commands for moving funds and sending messages from one chain to another + +## Flags + +```bash + --call-data string call data to be passed directly with bridge-message or as an ERC20 Permit (default "0x") + --destination-network uint32 the rollup id of the destination network + --force-update-root indicates if the new global exit root is updated or not (default true) + -h, --help help for bridge + --token-address string the address of an ERC20 token to be used (default "0x0000000000000000000000000000000000000000") + --value string the amount in wei to be sent along with the transaction +``` + +The command also inherits flags from parent commands. + +```bash + --bridge-address string the address of the lxly bridge + --chain-id string set the chain id to be used in the transaction + --config string config file (default is $HOME/.polygon-cli.yaml) + --destination-address string the address where the bridge will be sent to + --dry-run do all of the transaction steps but do not send the transaction + --gas-limit uint force a gas limit when sending a transaction + --gas-price string the gas price to be used + --pretty-logs Should logs be in pretty format or JSON (default true) + --private-key string the hex encoded private key to be used when sending the tx + --rpc-url string the URL of the RPC to send the transaction + --transaction-receipt-timeout uint the amount of time to wait while trying to confirm a transaction receipt (default 60) + -v, --verbosity int 0 - Silent + 100 Panic + 200 Fatal + 300 Error + 400 Warning + 500 Info + 600 Debug + 700 Trace (default 500) +``` + +## See also + +- [polycli ulxly ](polycli_ulxly_.md) - +- [polycli ulxly bridge asset](polycli_ulxly__bridge_asset.md) - Move ETH or an ERC20 between to chains + +- [polycli ulxly bridge message](polycli_ulxly__bridge_message.md) - Send some ETH along with data from one chain to another chain + +- [polycli ulxly bridge weth](polycli_ulxly__bridge_weth.md) - For L2's that use a gas token, use this to transfer WETH to another chain + diff --git a/doc/polycli_ulxly__bridge_asset.md b/doc/polycli_ulxly__bridge_asset.md new file mode 100644 index 00000000..c8670fd8 --- /dev/null +++ b/doc/polycli_ulxly__bridge_asset.md @@ -0,0 +1,129 @@ +# `polycli ulxly bridge asset` + +> Auto-generated documentation. + +## Table of Contents + +- [Description](#description) +- [Usage](#usage) +- [Flags](#flags) +- [See Also](#see-also) + +## Description + +Move ETH or an ERC20 between to chains + +```bash +polycli ulxly bridge asset [flags] +``` + +## Usage + +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. +## Flags + +```bash + -h, --help help for asset +``` + +The command also inherits flags from parent commands. + +```bash + --bridge-address string the address of the lxly bridge + --call-data string call data to be passed directly with bridge-message or as an ERC20 Permit (default "0x") + --chain-id string set the chain id to be used in the transaction + --config string config file (default is $HOME/.polygon-cli.yaml) + --destination-address string the address where the bridge will be sent to + --destination-network uint32 the rollup id of the destination network + --dry-run do all of the transaction steps but do not send the transaction + --force-update-root indicates if the new global exit root is updated or not (default true) + --gas-limit uint force a gas limit when sending a transaction + --gas-price string the gas price to be used + --pretty-logs Should logs be in pretty format or JSON (default true) + --private-key string the hex encoded private key to be used when sending the tx + --rpc-url string the URL of the RPC to send the transaction + --token-address string the address of an ERC20 token to be used (default "0x0000000000000000000000000000000000000000") + --transaction-receipt-timeout uint the amount of time to wait while trying to confirm a transaction receipt (default 60) + --value string the amount in wei to be sent along with the transaction + -v, --verbosity int 0 - Silent + 100 Panic + 200 Fatal + 300 Error + 400 Warning + 500 Info + 600 Debug + 700 Trace (default 500) +``` + +## See also + +- [polycli ulxly bridge](polycli_ulxly__bridge.md) - Commands for moving funds and sending messages from one chain to another diff --git a/doc/polycli_ulxly__bridge_message.md b/doc/polycli_ulxly__bridge_message.md new file mode 100644 index 00000000..16a8d63c --- /dev/null +++ b/doc/polycli_ulxly__bridge_message.md @@ -0,0 +1,126 @@ +# `polycli ulxly bridge message` + +> Auto-generated documentation. + +## Table of Contents + +- [Description](#description) +- [Usage](#usage) +- [Flags](#flags) +- [See Also](#see-also) + +## Description + +Send some ETH along with data from one chain to another chain + +```bash +polycli ulxly bridge message [flags] +``` + +## Usage + +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. + + +## Flags + +```bash + -h, --help help for message +``` + +The command also inherits flags from parent commands. + +```bash + --bridge-address string the address of the lxly bridge + --call-data string call data to be passed directly with bridge-message or as an ERC20 Permit (default "0x") + --chain-id string set the chain id to be used in the transaction + --config string config file (default is $HOME/.polygon-cli.yaml) + --destination-address string the address where the bridge will be sent to + --destination-network uint32 the rollup id of the destination network + --dry-run do all of the transaction steps but do not send the transaction + --force-update-root indicates if the new global exit root is updated or not (default true) + --gas-limit uint force a gas limit when sending a transaction + --gas-price string the gas price to be used + --pretty-logs Should logs be in pretty format or JSON (default true) + --private-key string the hex encoded private key to be used when sending the tx + --rpc-url string the URL of the RPC to send the transaction + --token-address string the address of an ERC20 token to be used (default "0x0000000000000000000000000000000000000000") + --transaction-receipt-timeout uint the amount of time to wait while trying to confirm a transaction receipt (default 60) + --value string the amount in wei to be sent along with the transaction + -v, --verbosity int 0 - Silent + 100 Panic + 200 Fatal + 300 Error + 400 Warning + 500 Info + 600 Debug + 700 Trace (default 500) +``` + +## See also + +- [polycli ulxly bridge](polycli_ulxly__bridge.md) - Commands for moving funds and sending messages from one chain to another diff --git a/doc/polycli_ulxly__bridge_weth.md b/doc/polycli_ulxly__bridge_weth.md new file mode 100644 index 00000000..d0011bb5 --- /dev/null +++ b/doc/polycli_ulxly__bridge_weth.md @@ -0,0 +1,95 @@ +# `polycli ulxly bridge weth` + +> Auto-generated documentation. + +## Table of Contents + +- [Description](#description) +- [Usage](#usage) +- [Flags](#flags) +- [See Also](#see-also) + +## Description + +For L2's that use a gas token, use this to transfer WETH to another chain + +```bash +polycli ulxly bridge weth [flags] +``` + +## Usage + +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 +``` + + +## Flags + +```bash + -h, --help help for weth +``` + +The command also inherits flags from parent commands. + +```bash + --bridge-address string the address of the lxly bridge + --call-data string call data to be passed directly with bridge-message or as an ERC20 Permit (default "0x") + --chain-id string set the chain id to be used in the transaction + --config string config file (default is $HOME/.polygon-cli.yaml) + --destination-address string the address where the bridge will be sent to + --destination-network uint32 the rollup id of the destination network + --dry-run do all of the transaction steps but do not send the transaction + --force-update-root indicates if the new global exit root is updated or not (default true) + --gas-limit uint force a gas limit when sending a transaction + --gas-price string the gas price to be used + --pretty-logs Should logs be in pretty format or JSON (default true) + --private-key string the hex encoded private key to be used when sending the tx + --rpc-url string the URL of the RPC to send the transaction + --token-address string the address of an ERC20 token to be used (default "0x0000000000000000000000000000000000000000") + --transaction-receipt-timeout uint the amount of time to wait while trying to confirm a transaction receipt (default 60) + --value string the amount in wei to be sent along with the transaction + -v, --verbosity int 0 - Silent + 100 Panic + 200 Fatal + 300 Error + 400 Warning + 500 Info + 600 Debug + 700 Trace (default 500) +``` + +## See also + +- [polycli ulxly bridge](polycli_ulxly__bridge.md) - Commands for moving funds and sending messages from one chain to another diff --git a/doc/polycli_ulxly__claim-everything.md b/doc/polycli_ulxly__claim-everything.md new file mode 100644 index 00000000..2659f520 --- /dev/null +++ b/doc/polycli_ulxly__claim-everything.md @@ -0,0 +1,55 @@ +# `polycli ulxly claim-everything` + +> Auto-generated documentation. + +## Table of Contents + +- [Description](#description) +- [Usage](#usage) +- [Flags](#flags) +- [See Also](#see-also) + +## Description + +Attempt to claim as many deposits and messages as possible + +```bash +polycli ulxly claim-everything [flags] +``` + +## Flags + +```bash + --bridge-limit int Limit the number or responses returned by the bridge service when claiming (default 25) + --bridge-offset int The offset to specify for pagination of the underlying bridge service deposits + --bridge-service-map strings Mappings between network ids and bridge service urls. E.g. '1=http://network-1-bridgeurl,7=http://network-2-bridgeurl' + -h, --help help for claim-everything +``` + +The command also inherits flags from parent commands. + +```bash + --bridge-address string the address of the lxly bridge + --chain-id string set the chain id to be used in the transaction + --config string config file (default is $HOME/.polygon-cli.yaml) + --destination-address string the address where the bridge will be sent to + --dry-run do all of the transaction steps but do not send the transaction + --gas-limit uint force a gas limit when sending a transaction + --gas-price string the gas price to be used + --pretty-logs Should logs be in pretty format or JSON (default true) + --private-key string the hex encoded private key to be used when sending the tx + --rpc-url string the URL of the RPC to send the transaction + --transaction-receipt-timeout uint the amount of time to wait while trying to confirm a transaction receipt (default 60) + -v, --verbosity int 0 - Silent + 100 Panic + 200 Fatal + 300 Error + 400 Warning + 500 Info + 600 Debug + 700 Trace (default 500) +``` + +## See also + +- [polycli ulxly ](polycli_ulxly_.md) - diff --git a/doc/polycli_ulxly__claim.md b/doc/polycli_ulxly__claim.md new file mode 100644 index 00000000..50f45e52 --- /dev/null +++ b/doc/polycli_ulxly__claim.md @@ -0,0 +1,56 @@ +# `polycli ulxly claim` + +> Auto-generated documentation. + +## Table of Contents + +- [Description](#description) +- [Usage](#usage) +- [Flags](#flags) +- [See Also](#see-also) + +## Description + +Commands for claiming deposits on a particular chain + +## Flags + +```bash + --bridge-service-url string the URL of the bridge service + --deposit-count uint the deposit count of the bridge transaction + --deposit-network uint the rollup id of the network where the deposit was initially made + --global-index string an override of the global index value + -h, --help help for claim +``` + +The command also inherits flags from parent commands. + +```bash + --bridge-address string the address of the lxly bridge + --chain-id string set the chain id to be used in the transaction + --config string config file (default is $HOME/.polygon-cli.yaml) + --destination-address string the address where the bridge will be sent to + --dry-run do all of the transaction steps but do not send the transaction + --gas-limit uint force a gas limit when sending a transaction + --gas-price string the gas price to be used + --pretty-logs Should logs be in pretty format or JSON (default true) + --private-key string the hex encoded private key to be used when sending the tx + --rpc-url string the URL of the RPC to send the transaction + --transaction-receipt-timeout uint the amount of time to wait while trying to confirm a transaction receipt (default 60) + -v, --verbosity int 0 - Silent + 100 Panic + 200 Fatal + 300 Error + 400 Warning + 500 Info + 600 Debug + 700 Trace (default 500) +``` + +## See also + +- [polycli ulxly ](polycli_ulxly_.md) - +- [polycli ulxly claim asset](polycli_ulxly__claim_asset.md) - Claim a deposit + +- [polycli ulxly claim message](polycli_ulxly__claim_message.md) - Claim a message + diff --git a/doc/polycli_ulxly__claim_asset.md b/doc/polycli_ulxly__claim_asset.md new file mode 100644 index 00000000..ae7f2534 --- /dev/null +++ b/doc/polycli_ulxly__claim_asset.md @@ -0,0 +1,138 @@ +# `polycli ulxly claim asset` + +> Auto-generated documentation. + +## Table of Contents + +- [Description](#description) +- [Usage](#usage) +- [Flags](#flags) +- [See Also](#see-also) + +## Description + +Claim a deposit + +```bash +polycli ulxly claim asset [flags] +``` + +## Usage + +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. +## Flags + +```bash + -h, --help help for asset +``` + +The command also inherits flags from parent commands. + +```bash + --bridge-address string the address of the lxly bridge + --bridge-service-url string the URL of the bridge service + --chain-id string set the chain id to be used in the transaction + --config string config file (default is $HOME/.polygon-cli.yaml) + --deposit-count uint the deposit count of the bridge transaction + --deposit-network uint the rollup id of the network where the deposit was initially made + --destination-address string the address where the bridge will be sent to + --dry-run do all of the transaction steps but do not send the transaction + --gas-limit uint force a gas limit when sending a transaction + --gas-price string the gas price to be used + --global-index string an override of the global index value + --pretty-logs Should logs be in pretty format or JSON (default true) + --private-key string the hex encoded private key to be used when sending the tx + --rpc-url string the URL of the RPC to send the transaction + --transaction-receipt-timeout uint the amount of time to wait while trying to confirm a transaction receipt (default 60) + -v, --verbosity int 0 - Silent + 100 Panic + 200 Fatal + 300 Error + 400 Warning + 500 Info + 600 Debug + 700 Trace (default 500) +``` + +## See also + +- [polycli ulxly claim](polycli_ulxly__claim.md) - Commands for claiming deposits on a particular chain diff --git a/doc/polycli_ulxly__claim_message.md b/doc/polycli_ulxly__claim_message.md new file mode 100644 index 00000000..c41ccb21 --- /dev/null +++ b/doc/polycli_ulxly__claim_message.md @@ -0,0 +1,143 @@ +# `polycli ulxly claim message` + +> Auto-generated documentation. + +## Table of Contents + +- [Description](#description) +- [Usage](#usage) +- [Flags](#flags) +- [See Also](#see-also) + +## Description + +Claim a message + +```bash +polycli ulxly claim message [flags] +``` + +## Usage + +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. + +## Flags + +```bash + -h, --help help for message +``` + +The command also inherits flags from parent commands. + +```bash + --bridge-address string the address of the lxly bridge + --bridge-service-url string the URL of the bridge service + --chain-id string set the chain id to be used in the transaction + --config string config file (default is $HOME/.polygon-cli.yaml) + --deposit-count uint the deposit count of the bridge transaction + --deposit-network uint the rollup id of the network where the deposit was initially made + --destination-address string the address where the bridge will be sent to + --dry-run do all of the transaction steps but do not send the transaction + --gas-limit uint force a gas limit when sending a transaction + --gas-price string the gas price to be used + --global-index string an override of the global index value + --pretty-logs Should logs be in pretty format or JSON (default true) + --private-key string the hex encoded private key to be used when sending the tx + --rpc-url string the URL of the RPC to send the transaction + --transaction-receipt-timeout uint the amount of time to wait while trying to confirm a transaction receipt (default 60) + -v, --verbosity int 0 - Silent + 100 Panic + 200 Fatal + 300 Error + 400 Warning + 500 Info + 600 Debug + 700 Trace (default 500) +``` + +## See also + +- [polycli ulxly claim](polycli_ulxly__claim.md) - Commands for claiming deposits on a particular chain diff --git a/doc/polycli_ulxly_empty-proof.md b/doc/polycli_ulxly_empty-proof.md index 0d138202..076c79e7 100644 --- a/doc/polycli_ulxly_empty-proof.md +++ b/doc/polycli_ulxly_empty-proof.md @@ -11,7 +11,7 @@ ## Description -print an empty proof structure +create an empty proof ```bash polycli ulxly empty-proof [flags] @@ -19,10 +19,7 @@ polycli ulxly empty-proof [flags] ## Usage -Use this command to print an empty proof response that's filled with -zero-valued siblings like -0x0000000000000000000000000000000000000000000000000000000000000000. This -can be useful when you need to submit a dummy proof. +Use this command to print an empty proof response that's filled with zero-valued siblings like 0x0000000000000000000000000000000000000000000000000000000000000000. This can be useful when you need to submit a dummy proof. ## Flags ```bash @@ -46,4 +43,4 @@ The command also inherits flags from parent commands. ## See also -- [polycli ulxly](polycli_ulxly.md) - Utilities for interacting with the lxly bridge +- [polycli ulxly](polycli_ulxly.md) - Utilities for interacting with the uLxLy bridge diff --git a/doc/polycli_ulxly_get-deposits.md b/doc/polycli_ulxly_get-deposits.md new file mode 100644 index 00000000..75e445b7 --- /dev/null +++ b/doc/polycli_ulxly_get-deposits.md @@ -0,0 +1,97 @@ +# `polycli ulxly get-deposits` + +> Auto-generated documentation. + +## Table of Contents + +- [Description](#description) +- [Usage](#usage) +- [Flags](#flags) +- [See Also](#see-also) + +## Description + +Generate ndjson for each bridge deposit over a particular range of blocks + +```bash +polycli ulxly get-deposits [flags] +``` + +## Usage + +This command will attempt to scan a range of blocks and look for uLxLy +Bridge Events. This is the specific signature that we're interested +in: + +```solidity + /** + * @dev Emitted when bridge assets or messages to another network + */ + event BridgeEvent( + uint8 leafType, + uint32 originNetwork, + address originAddress, + uint32 destinationNetwork, + address destinationAddress, + uint256 amount, + bytes metadata, + uint32 depositCount + ); + +``` + +If you're looking at the raw topics from on chain or in an explorer, this is the associated value: + +`0x501781209a1f8899323b96b4ef08b168df93e0a90c673d1e4cce39366cb62f9b` + +Each event that we counter will be parsed and written as JSON to +stdout. Example usage: + +```bash +polycli ulxly get-deposits \ + --bridge-address 0x528e26b25a34a4A5d0dbDa1d57D318153d2ED582 \ + --rpc-url https://eth-sepolia.g.alchemy.com/v2/demo \ + --from-block 4880876 \ + --to-block 6028159 \ + --filter-size 9999 > cardona-4880876-to-6028159.ndjson +``` + +This command will look for bridge events from block `4880876` to +block `6028159` in increments of `9999` blocks at a time for the +contract address `0x528e26b25a34a4A5d0dbDa1d57D318153d2ED582`. The +output will be written as newline delimited JSON. + +This command is very specific for the ulxly bridge, and it's meant to +serve as the input to the proof command. + + + +## Flags + +```bash + --bridge-address string The address of the ulxly bridge + --filter-size uint The batch size for individual filter queries (default 1000) + --from-block uint The start of the range of blocks to retrieve + -h, --help help for get-deposits + --rpc-url string The RPC URL to read deposit data + --to-block uint The end of the range of blocks to retrieve +``` + +The command also inherits flags from parent commands. + +```bash + --config string config file (default is $HOME/.polygon-cli.yaml) + --pretty-logs Should logs be in pretty format or JSON (default true) + -v, --verbosity int 0 - Silent + 100 Panic + 200 Fatal + 300 Error + 400 Warning + 500 Info + 600 Debug + 700 Trace (default 500) +``` + +## See also + +- [polycli ulxly](polycli_ulxly.md) - Utilities for interacting with the uLxLy bridge diff --git a/doc/polycli_ulxly_proof.md b/doc/polycli_ulxly_proof.md index 5e7e0072..6fca50e8 100644 --- a/doc/polycli_ulxly_proof.md +++ b/doc/polycli_ulxly_proof.md @@ -11,7 +11,7 @@ ## Description -generate a merkle proof +Generate a proof for a given range of deposits ```bash polycli ulxly proof [flags] @@ -27,12 +27,12 @@ Example usage: ```bash polycli ulxly proof \ --file-name cardona-4880876-to-6028159.ndjson \ - --deposit-number 24386 | jq '.' + --deposit-count 24386 | jq '.' ``` In this case we are assuming we have a file `cardona-4880876-to-6028159.ndjson` that would have been generated -with a call to `polycli ulxly deposits`. The output will be the +with a call to `polycli ulxly get-deposits`. The output will be the sibling hashes necessary to prove inclusion of deposit `24386`. This is a real verifiable deposit if you'd like to sanity check: @@ -87,17 +87,19 @@ This is the proof response from polycli: ![Sample Tree](./tree-diagram.png) -When we're creating the proof here, we're essentially storing all of -the paths to the various leafs. When we want to generate a proof, we -essentially find the appropriate sibling node in the tree to prove -that the leaf is part of the given merkle root. +When we're creating the proof here, we're essentially storing the paths to the +various leafs. When we want to generate a proof, we find the appropriate sibling +node in the tree to prove that the leaf is part of the given merkle root. +## Full example + +TODO ## Flags ```bash - --deposit-number uint32 The deposit that we would like to prove - --file-name string The filename with ndjson data of deposits - -h, --help help for proof + --deposit-count uint The deposit number to generate a proof for + --file-name string An ndjson file with deposit data + -h, --help help for proof ``` The command also inherits flags from parent commands. @@ -117,4 +119,4 @@ The command also inherits flags from parent commands. ## See also -- [polycli ulxly](polycli_ulxly.md) - Utilities for interacting with the lxly bridge +- [polycli ulxly](polycli_ulxly.md) - Utilities for interacting with the uLxLy bridge diff --git a/doc/polycli_ulxly_zero-proof.md b/doc/polycli_ulxly_zero-proof.md index c9bd4296..c2f63ed3 100644 --- a/doc/polycli_ulxly_zero-proof.md +++ b/doc/polycli_ulxly_zero-proof.md @@ -11,7 +11,7 @@ ## Description -print a proof structure with the zero hashes +create a proof that's filled with zeros ```bash polycli ulxly zero-proof [flags] @@ -48,4 +48,4 @@ The command also inherits flags from parent commands. ## See also -- [polycli ulxly](polycli_ulxly.md) - Utilities for interacting with the lxly bridge +- [polycli ulxly](polycli_ulxly.md) - Utilities for interacting with the uLxLy bridge diff --git a/go.mod b/go.mod index 768e8f56..3a90de50 100644 --- a/go.mod +++ b/go.mod @@ -159,12 +159,16 @@ require ( github.com/Microsoft/go-winio v0.6.2 // indirect github.com/cockroachdb/fifo v0.0.0-20240816210425-c5d0cb0b6fc0 // indirect github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 // indirect + github.com/cpuguy83/go-md2man/v2 v2.0.5 // indirect github.com/crate-crypto/go-ipa v0.0.0-20240724233137-53bbb0ceb27a // indirect github.com/ethereum/go-verkle v0.1.1-0.20240829091221-dffa7562dbe9 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/russross/blackfriday/v2 v2.1.0 // indirect + github.com/urfave/cli/v2 v2.27.5 // indirect + github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect github.com/yusufpapurcu/wmi v1.2.4 // indirect go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.55.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.55.0 // indirect diff --git a/go.sum b/go.sum index 09313523..99208a67 100644 --- a/go.sum +++ b/go.sum @@ -95,6 +95,8 @@ github.com/consensys/gnark-crypto v0.14.0/go.mod h1:CU4UijNPsHawiVGNxe9co07FkzCe github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4= github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/cpuguy83/go-md2man/v2 v2.0.5 h1:ZtcqGrnekaHpVLArFSe4HK5DoKx1T0rq2DwVB0alcyc= +github.com/cpuguy83/go-md2man/v2 v2.0.5/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/crate-crypto/go-ipa v0.0.0-20240724233137-53bbb0ceb27a h1:W8mUrRp6NOVl3J+MYp5kPMoUZPp7aOYHtaua31lwRHg= github.com/crate-crypto/go-ipa v0.0.0-20240724233137-53bbb0ceb27a/go.mod h1:sTwzHBvIzm2RfVCGNEBZgRyjwK40bVoun3ZnGOCafNM= github.com/crate-crypto/go-kzg-4844 v1.1.0 h1:EN/u9k2TF6OWSHrCCDBBU6GLNMq88OspHHlMnHfoyU4= @@ -402,6 +404,8 @@ github.com/tyler-smith/go-bip39 v1.1.0 h1:5eUemwrMargf3BSLRRCalXT93Ns6pQJIjYQN2n github.com/tyler-smith/go-bip39 v1.1.0/go.mod h1:gUYDtqQw1JS3ZJ8UWVcGTGqqr6YIN3CWg+kkNaLt55U= github.com/urfave/cli/v2 v2.25.7 h1:VAzn5oq403l5pHjc4OhD54+XGO9cdKVL/7lDjF+iKUs= github.com/urfave/cli/v2 v2.25.7/go.mod h1:8qnjx1vcq5s2/wpsqoZFndg2CE5tNFyrTvS6SinrnYQ= +github.com/urfave/cli/v2 v2.27.5 h1:WoHEJLdsXr6dDWoJgMq/CboDmyY/8HMMH1fTECbih+w= +github.com/urfave/cli/v2 v2.27.5/go.mod h1:3Sevf16NykTbInEnD0yKkjDAeZDS0A6bzhBH5hrMvTQ= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo= github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= @@ -411,6 +415,8 @@ github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17 github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU= github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8= +github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4= +github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=