This repository showcases smart contracts for an FA tokens bridge between Tezos and Etherlink, aligned with the TZIP-029 standard.
On the Tezos side, there are smart contracts written in CameLIGO, featuring:
- Ticketer, which enables wrapping of FA1.2 and FA2 tokens into tickets. These tickets can then be sent to the bridge using a permissionless ticket transport mechanism.
- TokenBridgeHelper, which is designed to allow users to transfer tickets even without the support for the ticket_transfer operation in the current Tezos infrastructure. The Token Bridge Helper implementation focused on FA1.2 and FA2 tokens only.
On the Etherlink side, the setup includes Solidity contracts, notably the ERC20Proxy: an ERC20 contract that implements the L2 proxy deposit interface and withdraw interface.
Additionally, the scripts directory contains Python scripts with CLI commands enabling interaction with the bridge. These commands include contract deployment on both sides of the bridge, as well as deposit and withdrawal helpers.
- To run scripts it is required for the Poetry to be installed in the system:
curl -sSL https://install.python-poetry.org | python3 -
- Install all Python dependencies with:
poetry install
-
Install Foundry by following the installation guide
-
Install Solidity dependencies with Forge (part of the Foundry toolchain). Installation should be executed from the
etherlink
directory:
(cd etherlink && forge test)
Forge uses git submodules to manage dependencies. It is possible to check versions of the Solidity libraries installed by running git submodule status
. Here are the versions used to compile contracts:
1d9650e951204a0ddce9ff89c32f1997984cef4d etherlink/lib/forge-std (v1.6.1)
fd81a96f01cc42ef1c9a5399364968d0e07e9e90 etherlink/lib/openzeppelin-contracts (v4.9.3)
- Set up environment variables by running
init_wallets
script. It will ask users to enter their Tezos and Etherlink private and public keys. It also offers options to configure parameters for L1 and L2 communication. Default values are available and test keys are included:
poetry run init_wallets
Users also may need some funds in their accounts to pay for the execution fees:
- To fund a Tezos account, the faucet can be used.
- One of the ways to fund an Etherlink account is to execute
fund_etherlink_account
script which will send 1 xtz to the account which is set in the environment variables:
poetry run fund_etherlink_account
To configure the bridge for a new token (i.e., to list a new token pair), users need to engage with the Ticket Transport Layer. This critical component of the bridge facilitates the transfer of tickets between Tezos and Etherlink.
The FA2 and FA1.2 standard Tezos tokens are not inherently ticket-native. Therefore, to bridge these tokens, users must initially convert them to tickets. To do this they first need to deploy a Ticketer contract on the Tezos side. This contract will be associated with the specific Tezos token and provide entrypoint to wrap them into tickets. This contract is unnecessary for the forthcoming tokens that are ticket-native, such as those following FA2.1 standard.
NOTE: Upgrades to the Ticketer contract are highly unwanted because this will lead to liquidity fragmentation. Ideally, it should be deployed once and remain unchanged indefinitely.
Once it's determined what Ticketer will represent the token, users can deploy an ERC20Proxy – an ERC20 token integrated with bridge deposit and withdrawal interfaces. The Ticketer address and ticket content are provided to the ERC20Proxy constructor during origination to bind the L2 token to the L1 ticket. It is also required to provide rollup kernel address to the ERC20 proxy which will be allowed to mint and burn tokens which is the 0x00
address.
Additionally, deploying a TokenBridgeHelper on the Tezos side is required, targeting the specific Token and Ticketer pair on the L1 side and ERC20Proxy address on the L2 side. This deployment is crucial since wallets currently do not support the transfer_ticket operation. The TokenBridgeHelper streamlines the process by wrapping tokens into tickets and enabling their transfer to the rollup in a single transaction. Note, however, that this type of contract may become obsolete in the future when (1) wallets begin supporting the transfer_ticket operation and (2) Tezos undergoes a protocol upgrade that permits implicit addresses to transfer tickets with arbitrary data.
For demonstration purposes, users can deploy a test token intended for bridging. The bridge has been tested with two types of tokens available in the repository:
- The FA1.2 standard Ctez token.
- The FA2 standard fxhash token.
To deploy a test version of a token and allocate the total supply to the token's originator, the deploy_token
command can be used. It is possible to configure --token-type
, --token-id
, and --total-supply
providing these params to the command. The following example demonstrates how to deploy an FA1.2 token type using default parameters:
poetry run deploy_token --token-type FA1.2 --total-supply 1000
Here is an example of the deployed token.
The deploy_ticketer
command can be used to deploy a ticketer configured for a specific token. It requires the --token-address
, --token-type
, and --token-id
parameters to be provided. Below is an example that demonstrates how to deploy a ticketer for the FA1.2 token previously deployed on Oxfordnet:
poetry run deploy_ticketer --token-address KT1QKYoSpV5BLKg8xoexG25yZwA1sjVrrymU --token-type FA1.2
Here is an example of the deployed ticketer.
After the Ticketer contract is deployed, users can obtain the parameters required for the ERC20Proxy origination, ticketer-address_bytes and content_bytes. To do this users can execute get_ticketer_params
command:
poetry run get_ticketer_params --ticketer KT1C2gmhvA1FrscWe8KbrHf8dWG9XJETR127
For example, the ticketer deployed in the previous step has the following parameters:
address_bytes: 0125cf30bfba37ed7907f524f7b4eaf304e03d097600
content_bytes: 0707000005090a0000005f05020000005907040100000010636f6e74726163745f616464726573730a0000001c050a0000001601aca11e3f7734be9b46df1642a7d5f7d66c7bf6e8000704010000000a746f6b656e5f747970650a0000000b0501000000054641312e32
Then, to deploy a token contract on the Etherlink side, the deploy_erc20
command can be used. This script requires the --ticketer-address-bytes
and --ticketer-content-bytes
, as well as --token-name
, --token-symbol
, and --decimals
to be provided for the L2 token contract configuration. Below is an example that originates ERC20 contract connected to the ticketer previously deployed:
poetry run deploy_erc20 --ticketer-address-bytes 0125cf30bfba37ed7907f524f7b4eaf304e03d097600 --ticket-content-bytes 0707000005090a0000005f05020000005907040100000010636f6e74726163745f616464726573730a0000001c050a0000001601aca11e3f7734be9b46df1642a7d5f7d66c7bf6e8000704010000000a746f6b656e5f747970650a0000000b0501000000054641312e32 --token-name "FA1.2 Test Token" --token-symbol "FA1.2" --decimals 0
Here is an example of the deployed token.
Finally, to allow the interaction of Tezos wallets with tickets, users need to deploy a Token Bridge Helper. During origination, Token Bridge Helper linked to the Token, Ticketer and ERC20Proxy. To originate Token Bridge Helper user should run the deploy_token_bridge_helper
command, which requires the --ticketer-address
and --proxy-address
(the address of the L2 token) parameters to be provided. The Ticketer's storage will be parsed to retrieve information about the token. Below is an example that illustrates deploying a token bridge helper for the previously deployed ticketer and proxy:
poetry run deploy_token_bridge_helper --ticketer-address KT1C2gmhvA1FrscWe8KbrHf8dWG9XJETR127 --proxy-address 0x03E39FF2b379FBcd9284Ab457113D82fF4daBBF4
Here is an example of the deployed helper.
To deposit a token, users need to wrap tokens to tickets and then transfer them to the smart rollup address, along with routing info provided in the specified format: a 40 bytes payload comprising | receiver | proxy |
. The Token Bridge Helper contract allows users to perform these operations in one call. To deposit a token users may utilize deposit
command and provide the --token-bridge-helper-address
and a bridged --amount
as parameters. Here is an example:
poetry run deposit --token-bridge-helper-address KT1RWw9NyPDZm9jeiEA1hXMd4PgGQVPHYrzj --amount 77
Here are examples of these operations on the Tezos and Etherlink sides.
The withdrawal process consists of two steps:
- Initiating the withdrawal on the Etherlink side, leading to the creation of an outbox message from Etherlink to Tezos.
- Finalizing the withdrawal on the Tezos side by executing the outbox message, after the settlement of the commitment.
To initiate a withdrawal on the Etherlink side, users must invoke the withdrawal precompile. An ERC20Proxy address along with routing info should be provided. The specified format of the routing info is a 44-byte concatenation of two forged contracts: | receiver | proxy |
.
To demonstrate this operation, the withdraw
script is provided, which requires the --proxy-address
(an ERC20 token address), the --router-address
(a ticketer address which will process the ticket from the rollup), and the bridged --amount
to withdraw from Etherlink. Furthermore, --ticketer-address-bytes
and --ticketer-content-bytes
are required to enable the ERC20Proxy to validate the token before burning it.
NOTE: The Ticketer implements a router withdrawal interface, and it will automatically unwrap tickets for a specified receiver
.
Below is an example demonstrating the execution of a script that initiates the withdrawal of 18 tokens previously deposited on Etherlink.
poetry run withdraw --proxy-address 0x03E39FF2b379FBcd9284Ab457113D82fF4daBBF4 --amount 18 --ticketer-address-bytes 0125cf30bfba37ed7907f524f7b4eaf304e03d097600 --ticket-content-bytes 0707000005090a0000005f05020000005907040100000010636f6e74726163745f616464726573730a0000001c050a0000001601aca11e3f7734be9b46df1642a7d5f7d66c7bf6e8000704010000000a746f6b656e5f747970650a0000000b0501000000054641312e32 --router-address KT1C2gmhvA1FrscWe8KbrHf8dWG9XJETR127
Here is an example of the withdraw operation.
To finalize the withdrawal process on the Tezos side, users must invoke the outbox message after it has been settled. To do this, the commitment hash and proof bytes need to be acquired, which in turn are obtainable from the rollup node by outboxLevel and outboxMsgId. These parameters are part of the Withdrawal event that is emitted during the withdrawal process on the Etherlink side.
Since both outboxLevel and outboxMsgId are of type uint256
, to extract outboxMsgId, users should take the last 32 bytes from the Withdrawal event logs. For outboxLevel, the preceding 32 bytes should be taken and both should be converted into integers. Here's how this process can be executed:
- Link to the block explorer with the withdrawal transaction: blockscout
- Withdrawal event data:
0x0000000000000000000000007e6f6ccfe485a087f0f819eabfdbfb1a49b9767700000000000000000000000003e39ff2b379fbcd9284ab457113d82ff4dabbf40000a79057282732a2736064001cf4b4c56b84ec31ee000000000000000000000000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000012071e0000000000000000000000000000000000000000000000000000000000000000
- outboxMsgId: The last 32 bytes are
0000000000000000000000000000000000000000000000000000000000000000
, which is integer0
. - outboxLevel: The 32 bytes preceding those are
000000000000000000000000000000000000000000000000000000000012071e
, which is integer1181470
.
The parse_withdrawal_event
script allows to parse withdrawal event by transaction hash:
poetry run parse_withdrawal_event --tx-hash 0x51b9f37dbd18ca24b6c92f8685713f5d9767fbd1bdb3b8b8a00b88197c42c73c
After the withdrawal transaction is settled, users can retrieve the commitment and proof, which are necessary for the execute_outbox_message
call. This involves making a call to the global/block/head/helpers/proofs/outbox/{outbox_level}/messages
endpoint of the Rollup RPC Node. The get_proof
script can be used to get this information:
poetry run get_proof --level 1181470 --index 0
Since the outbox message has settled on the L1 side, users can execute it by initiating the execute_outbox_message
operation. Bellow a script execute_outbox_message
used to finalize withdrawal for the test FA1.2 token where acquired --commitment
and --proof
are provided:
poetry run execute_outbox_message --commitment src13PirA1PzjLWRYJvNFJL5V6tfdbhzmAgML2UtQHuojjjFv6ARsf --proof 03000251dd09b9e7c635d0a23a9a02c6268edcf2a3e74a9980cd8ce568612f8dd6886951dd09b9e7c635d0a23a9a02c6268edcf2a3e74a9980cd8ce568612f8dd688690005820764757261626c65d09177f273e0b4b3646d59424754cbc1a5368b75a201a7fa133551e39afbc25b3903746167c00800000004536f6d650003c02411766c7a6adf7e0f2f3a1608fdde179275ff6881c5712387bd21d401d9365c820576616c7565810370766d8107627566666572738205696e707574820468656164c00100066c656e677468c00100066f75747075740004820132810a6c6173745f6c6576656cc0040012072b0133810f76616c69646974795f706572696f64c00400013b0082013181086f7574626f7865730200013b00c07dc3e23702625eb3ecd86216a989553e1c95c9611f209e54fcefbf598f96b849019e68014f18c0dda898697e4f913f5af0017d501c8dd825696ea12f41168fd98a1243d3d4b0ae0127a40113bf0109f2c0f8d9aa295d40a87361a32f534a539c3304d87286125baf97fad5692209f5e1a40104cec02e83ddeb15e1e93ccc0b9eeaa17453499b0acccefb56831e4b57f084467da05b01027101013800b1c00a9ab9a19d10248b3810f3be53af9fa0012280024e512fce2dc30653e85b94f20054002a0017c0e68c3b7ad7e6f130881c5da857a5992daf77fb911cf6a94fb669f9eeb6a11a23000ec0e48a085d60ed9f24c26a44d4d9825d545098165a8aec0a518059431f1a564ea30005c0ba795987ddd0ec6bbcaef394f12f322b7fe73ee5b5f53c0b95466fc9b9e165270004c0f37ede9ba69186bc32eb2d85541fe28de505097a3755fb0eed841ffe20ae0ace820731313230323030820468656164c00100066c656e677468c0010007313138313437300003810468656164c001008208636f6e74656e7473810130c0d3000000cf00000000ca07070a000000160000a79057282732a2736064001cf4b4c56b84ec31ee07070a000000160125cf30bfba37ed7907f524f7b4eaf304e03d09760007070707000005090a0000005f05020000005907040100000010636f6e74726163745f616464726573730a0000001c050a0000001601aca11e3f7734be9b46df1642a7d5f7d66c7bf6e8000704010000000a746f6b656e5f747970650a0000000b0501000000054641312e3200120125cf30bfba37ed7907f524f7b4eaf304e03d097600000000087769746864726177066c656e677468c00101c0ad7442a17d12b3355594da691f158d37a0d44a7eae4a901f02b78819014efa26c05bc495b3831636f3f356a681b5e81bc47c250151048452fd09ab24f5550d6334c00bece9f7a7ae300e23932bbc972eeebc35b64f82ddbf3ca35f3f911fe96189e0c0760bdf103b185a9ca7234e7a0b631c7e5a2c45d4a86aad483758c36db0017107c008796620943748d80efa68d459f16839f0b95891b566ec5ccbfa80e120e0f420c0ebe4e9a10f88ea5b510233f903f66ee80a8faa3c212a5bddf27421cc0deb7362c09cfb372f3f6c6f9659e15f9a8a9b3b3d2824a707c426d43eb8711e83bf2252990134810d6d6573736167655f6c696d6974c002a401047761736dd0d535afa926245d335484f18570628898b144af4686673c5dbd96fa2431025e4d0012071e0000000000ca07070a000000160000a79057282732a2736064001cf4b4c56b84ec31ee07070a000000160125cf30bfba37ed7907f524f7b4eaf304e03d09760007070707000005090a0000005f05020000005907040100000010636f6e74726163745f616464726573730a0000001c050a0000001601aca11e3f7734be9b46df1642a7d5f7d66c7bf6e8000704010000000a746f6b656e5f747970650a0000000b0501000000054641312e3200120125cf30bfba37ed7907f524f7b4eaf304e03d097600000000087769746864726177
Here is an example of the finished withdrawal.
To compile Tezos-side contracts, the LIGO compiler must be installed. The most convenient method is to use the Docker version of the LIGO compiler. Compilation of all contracts using the dockerized LIGO compiler can be initiated with the following command:
poetry run build_tezos_contracts
NOTE: This repository includes built Tezos side contracts which are located in the tezos/build directory.
To compile contracts on the Etherlink side, Foundry must be installed. To initiate the compilation, navigate to the etherlink directory and run forge build
, or execute the following script from the root directory:
poetry run build_etherlink_contracts
NOTE: This repository includes built Etherlink side contracts which are located in the etherlink/build directory.
The testing stack for Tezos contracts is based on Python and requires poetry, pytezos, and pytest to be installed.
To run tests for the Tezos contracts, execute:
poetry run pytest tezos/tests
The Etherlink contract tests use the Foundry stack and are implemented in Solidity. To run these tests, navigate to the etherlink directory and run forge test
, or execute the following script from the root directory:
poetry run etherlink_tests
To perform linting, execute the following commands:
poetry run mypy .
poetry run ruff .
poetry run black .