From 3f7681c21b8e458cb661252cb4e0e21f7779a4a5 Mon Sep 17 00:00:00 2001 From: Igor Matsak Date: Thu, 15 Feb 2024 23:21:09 +0300 Subject: [PATCH] EVM/Kernel: FA-bridge (part 1) --- etherlink/kernel_evm/Cargo.lock | 4 + etherlink/kernel_evm/Cargo.toml | 6 +- etherlink/kernel_evm/fa_bridge/Cargo.toml | 12 ++ etherlink/kernel_evm/fa_bridge/README.md | 12 ++ etherlink/kernel_evm/fa_bridge/src/lib.rs | 32 +++++ .../fa_bridge/tests/contracts/Makefile | 12 ++ .../contracts/artifacts/MockPrecompile.abi | 127 ++++++++++++++++++ .../tests/contracts/artifacts/MockWrapper.abi | 112 +++++++++++++++ .../contracts/artifacts/MockWrapper.bytecode | Bin 0 -> 1322 bytes .../fa_bridge/tests/contracts/foundry.toml | 7 + .../tests/contracts/src/MockPrecompile.sol | 40 ++++++ .../tests/contracts/src/MockWrapper.sol | 100 ++++++++++++++ 12 files changed, 462 insertions(+), 2 deletions(-) create mode 100644 etherlink/kernel_evm/fa_bridge/Cargo.toml create mode 100644 etherlink/kernel_evm/fa_bridge/README.md create mode 100644 etherlink/kernel_evm/fa_bridge/src/lib.rs create mode 100644 etherlink/kernel_evm/fa_bridge/tests/contracts/Makefile create mode 100644 etherlink/kernel_evm/fa_bridge/tests/contracts/artifacts/MockPrecompile.abi create mode 100644 etherlink/kernel_evm/fa_bridge/tests/contracts/artifacts/MockWrapper.abi create mode 100644 etherlink/kernel_evm/fa_bridge/tests/contracts/artifacts/MockWrapper.bytecode create mode 100644 etherlink/kernel_evm/fa_bridge/tests/contracts/foundry.toml create mode 100644 etherlink/kernel_evm/fa_bridge/tests/contracts/src/MockPrecompile.sol create mode 100644 etherlink/kernel_evm/fa_bridge/tests/contracts/src/MockWrapper.sol diff --git a/etherlink/kernel_evm/Cargo.lock b/etherlink/kernel_evm/Cargo.lock index 13a49902c58d..0646a1e3c3d9 100644 --- a/etherlink/kernel_evm/Cargo.lock +++ b/etherlink/kernel_evm/Cargo.lock @@ -581,6 +581,10 @@ dependencies = [ "thiserror", ] +[[package]] +name = "fa-bridge" +version = "0.1.0" + [[package]] name = "fastrand" version = "1.9.0" diff --git a/etherlink/kernel_evm/Cargo.toml b/etherlink/kernel_evm/Cargo.toml index f8a983175167..da81bd2a39e1 100644 --- a/etherlink/kernel_evm/Cargo.toml +++ b/etherlink/kernel_evm/Cargo.toml @@ -1,12 +1,13 @@ # SPDX-FileCopyrightText: 2023 Nomadic Labs # SPDX-FileCopyrightText: 2023 Marigold # SPDX-FileCopyrightText: 2023 Functori +# SPDX-FileCopyrightText: 2023 PK Lab # # SPDX-License-Identifier: MIT [workspace] -members = ["ethereum", "kernel", "evm_execution", "evm_evaluation", "logging"] +members = ["ethereum", "fa_bridge", "kernel", "evm_execution", "evm_evaluation", "logging"] [workspace.dependencies] @@ -44,6 +45,7 @@ libsecp256k1 = { version = "0.7", default-features = false, features = [ ] } # kernel crates +fa-bridge = { path = "./fa_bridge" } tezos_ethereum = { path = "./ethereum" } evm-execution = { path = "./evm_execution" } tezos-evm-logging = { path = "./logging" } @@ -71,4 +73,4 @@ tezos-smart-rollup-storage = { path = "../../src/kernel_sdk/storage" } # property based testing rand = { version = "0.8" } -proptest = { version = "1.0" } +proptest = { version = "1.0" } \ No newline at end of file diff --git a/etherlink/kernel_evm/fa_bridge/Cargo.toml b/etherlink/kernel_evm/fa_bridge/Cargo.toml new file mode 100644 index 000000000000..e36e8662d014 --- /dev/null +++ b/etherlink/kernel_evm/fa_bridge/Cargo.toml @@ -0,0 +1,12 @@ +# SPDX-FileCopyrightText: 2023 PK Lab +# +# SPDX-License-Identifier: MIT + +[package] +name = "fa-bridge" +version = "0.1.0" +edition = "2021" +license = "MIT" + +[lib] +crate-type = ["cdylib", "rlib"] diff --git a/etherlink/kernel_evm/fa_bridge/README.md b/etherlink/kernel_evm/fa_bridge/README.md new file mode 100644 index 000000000000..7c32b18ece90 --- /dev/null +++ b/etherlink/kernel_evm/fa_bridge/README.md @@ -0,0 +1,12 @@ +# FA token bridge + +This crate contains primitives that are necessary to implement a permissionless ticket based asset bridge, as described in the [specification](https://hackmd.io/I_5FJBwoQcqOa2bTtRYH-w); + +## How to build test contracts + +[Foundry](https://github.com/foundry-rs/foundry) is required to compile test smart contracts. Please use actual [installation guide](https://book.getfoundry.sh/getting-started/installation). + +After installing Foundry to build artifacts do: +``` +make artifacts +``` \ No newline at end of file diff --git a/etherlink/kernel_evm/fa_bridge/src/lib.rs b/etherlink/kernel_evm/fa_bridge/src/lib.rs new file mode 100644 index 000000000000..d4fc52fa58f5 --- /dev/null +++ b/etherlink/kernel_evm/fa_bridge/src/lib.rs @@ -0,0 +1,32 @@ +// SPDX-FileCopyrightText: 2023 PK Lab +// +// SPDX-License-Identifier: MIT + +//! FA token bridge. +//! +//! A permissionless transport protocol, that enables ticket transfers +//! from L1 to L2 and back, supporting two destination types: +//! 1. Simple address, which can be both externally owner account, +//! or a smart contract wallet (that supports tickets) +//! 2. Proxy contract, exposing standard methods for deposits (on L2) +//! and withdrawals (on L1); must handle both ticket and +//! routing info that carries the final receiver address. +//! +//! FA bridge maintains the global ticket table, which is a ledger +//! tracking internal ticket ownerships on Etherlink side. +//! +//! FA bridge consists of two main parts: +//! * The one responsible for deposit handling: integrates with the +//! inbox handling flow, results in a pseudo transaction from +//! Zero account. +//! * The one responsible for withdrawal handling: implemented as +//! as precompiled contract, which can be invoked both by EOA +//! or another smart contract. +//! +//! It should be noted that FA withdrawal precompile DOES NOT post any +//! messages to the outbox since it cannot know if the outer transaction +//! fails or succeeds. +//! +//! All the state updates (ticket table, outbox message counter) are done +//! using the transactional Eth account storage, so that they are discarded +//! in case of a revert/failure. diff --git a/etherlink/kernel_evm/fa_bridge/tests/contracts/Makefile b/etherlink/kernel_evm/fa_bridge/tests/contracts/Makefile new file mode 100644 index 000000000000..022a426fd4ff --- /dev/null +++ b/etherlink/kernel_evm/fa_bridge/tests/contracts/Makefile @@ -0,0 +1,12 @@ +# SPDX-FileCopyrightText: 2023 PK Lab +# +# SPDX-License-Identifier: MIT + +.PHONY: artifacts + +artifacts: + mkdir artifacts || true + forge build + jq ".abi" build/MockWrapper.sol/MockWrapper.json > artifacts/MockWrapper.abi + jq -r ".bytecode.object" build/MockWrapper.sol/MockWrapper.json | xxd -r -p > artifacts/MockWrapper.bytecode + jq ".abi" build/MockPrecompile.sol/MockPrecompile.json > artifacts/MockPrecompile.abi diff --git a/etherlink/kernel_evm/fa_bridge/tests/contracts/artifacts/MockPrecompile.abi b/etherlink/kernel_evm/fa_bridge/tests/contracts/artifacts/MockPrecompile.abi new file mode 100644 index 000000000000..f80b97c7af32 --- /dev/null +++ b/etherlink/kernel_evm/fa_bridge/tests/contracts/artifacts/MockPrecompile.abi @@ -0,0 +1,127 @@ +[ + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "ticketHash", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "address", + "name": "ticketOwner", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "receiver", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "inboxLevel", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "inboxMsgId", + "type": "uint256" + } + ], + "name": "Deposit", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "ticketHash", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "ticketOwner", + "type": "address" + }, + { + "indexed": false, + "internalType": "bytes22", + "name": "receiver", + "type": "bytes22" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "outboxLevel", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "outboxMsgId", + "type": "uint256" + } + ], + "name": "Withdrawal", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "ticketOwner", + "type": "address" + }, + { + "internalType": "bytes", + "name": "receiver", + "type": "bytes" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "bytes22", + "name": "ticketer", + "type": "bytes22" + }, + { + "internalType": "bytes", + "name": "content", + "type": "bytes" + } + ], + "name": "withdraw", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } +] diff --git a/etherlink/kernel_evm/fa_bridge/tests/contracts/artifacts/MockWrapper.abi b/etherlink/kernel_evm/fa_bridge/tests/contracts/artifacts/MockWrapper.abi new file mode 100644 index 000000000000..9005a8bbe2ba --- /dev/null +++ b/etherlink/kernel_evm/fa_bridge/tests/contracts/artifacts/MockWrapper.abi @@ -0,0 +1,112 @@ +[ + { + "inputs": [ + { + "internalType": "bytes22", + "name": "ticketer_", + "type": "bytes22" + }, + { + "internalType": "bytes", + "name": "content_", + "type": "bytes" + }, + { + "internalType": "address", + "name": "kernel_", + "type": "address" + }, + { + "internalType": "uint256", + "name": "flag_", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "Burn", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "receiver", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "Mint", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "receiver", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "ticketHash", + "type": "uint256" + } + ], + "name": "deposit", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "ticketHash", + "type": "uint256" + } + ], + "name": "withdraw", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } +] diff --git a/etherlink/kernel_evm/fa_bridge/tests/contracts/artifacts/MockWrapper.bytecode b/etherlink/kernel_evm/fa_bridge/tests/contracts/artifacts/MockWrapper.bytecode new file mode 100644 index 0000000000000000000000000000000000000000..fa1c1ed28b37fc4947839bcfff727881cee09453 GIT binary patch literal 1322 zcmYdjNN@-;X%J0h5C~6TX!si)kl+xQ$f{+*+>pqs)oj_wnBdSjAt;eSe_|q|Vpwz{ zgJnxgBE$5s=mdt)1jdGt1jYo$gay*fl43AciA(4qAEE;5Bg1jU|>%<8`4S@*?6DBe$Or8`F5D*rf?B`KknD9V4fgvct zF@Ys0K_!9VZ*&4fbaSJCXd=U(@Ww{Qz~&~#Ah4-VK(-|?Ok@lSi*5*nIlfV>Q6!O3 z1`-w%Cj@|8(vZN=kkHWD&MYLF$Y>6cYJunokY<*IXz_=LCn&Tr22Pj~05;D-nz^A# zP&AP-2O`Q*a%WGDLj#JQX<1?kS_xhNn{8Jj|L~XL480Q)nPR~~*f5cK!r$nI1cnfi zM26?6Disn~K&e13L8XZ?sNOd}IXk>4v7jKe$V#ESC_gVV%wt4Atw`wbK jp}+sn{hoh#Lho8Z&f62F&bzQUr8qw)*_nYul))GP0Aam^ literal 0 HcmV?d00001 diff --git a/etherlink/kernel_evm/fa_bridge/tests/contracts/foundry.toml b/etherlink/kernel_evm/fa_bridge/tests/contracts/foundry.toml new file mode 100644 index 000000000000..a8e346a27e34 --- /dev/null +++ b/etherlink/kernel_evm/fa_bridge/tests/contracts/foundry.toml @@ -0,0 +1,7 @@ +# SPDX-FileCopyrightText: 2023 PK Lab +# +# SPDX-License-Identifier: MIT + +[profile.default] +src = "src" +out = "build" \ No newline at end of file diff --git a/etherlink/kernel_evm/fa_bridge/tests/contracts/src/MockPrecompile.sol b/etherlink/kernel_evm/fa_bridge/tests/contracts/src/MockPrecompile.sol new file mode 100644 index 000000000000..a6d752b20da8 --- /dev/null +++ b/etherlink/kernel_evm/fa_bridge/tests/contracts/src/MockPrecompile.sol @@ -0,0 +1,40 @@ +// SPDX-FileCopyrightText: 2023 PK Lab +// +// SPDX-License-Identifier: MIT + +pragma solidity >=0.8.19; + +/** + * MockWrapper is a helper contract to generate ABIs + * for the FA brige precompile. + */ +contract MockPrecompile { + + event Deposit( + uint256 indexed ticketHash, + address ticketOwner, + address receiver, + uint256 amount, + uint256 inboxLevel, + uint256 inboxMsgId + ); + + event Withdrawal( + uint256 indexed ticketHash, + address sender, + address ticketOwner, + bytes22 receiver, + uint256 amount, + uint256 outboxLevel, + uint256 outboxMsgId + + ); + + function withdraw( + address ticketOwner, + bytes memory receiver, + uint256 amount, + bytes22 ticketer, + bytes memory content + ) public {} +} \ No newline at end of file diff --git a/etherlink/kernel_evm/fa_bridge/tests/contracts/src/MockWrapper.sol b/etherlink/kernel_evm/fa_bridge/tests/contracts/src/MockWrapper.sol new file mode 100644 index 000000000000..ec6e28e44e91 --- /dev/null +++ b/etherlink/kernel_evm/fa_bridge/tests/contracts/src/MockWrapper.sol @@ -0,0 +1,100 @@ +// SPDX-FileCopyrightText: 2023 PK Lab +// +// SPDX-License-Identifier: MIT + +pragma solidity >=0.8.19; + +function calcTicketHash(bytes22 ticketer, bytes memory content) + pure + returns (uint256) +{ + return uint256(keccak256(abi.encodePacked(ticketer, content))); +} + +/** + * MockWrapper is a mock token contract which represents a L1 token on L2. + */ +contract MockWrapper { + uint256 private _ticketHash; + address private _kernel; + + /** + * @param ticketer_ whitelisted Tezos L1 address of the ticketer contract + * @param content_ identifier of the L1 token (ticket id) + * @param kernel_ address of the rollup kernel which is responsible for + * minting tokens + * @param flag_ debug flag for tracking storage changes + */ + constructor( + bytes22 ticketer_, + bytes memory content_, + address kernel_, + uint256 flag_ + ) { + _ticketHash = calcTicketHash(ticketer_, content_); + _kernel = kernel_; + setFlag(flag_); + this; + } + + function setFlag(uint256 value) internal { + bytes32 slot = keccak256(abi.encodePacked("FLAG_TAG")); + assembly { + sstore(slot, value) + } + } + + event Mint ( + address indexed receiver, + uint256 amount + ); + + event Burn ( + address indexed sender, + uint256 amount + ); + + /** + * Mints `amount` tokens for `to` address if provided `ticket`hash` + * is correct. + * + * Requirements: + * + * - only kernel address allowed to mint tokens. + * - `ticketHash` must be equal to the token hash of the ticketer + * and identifier used during deployment. + */ + function deposit(address receiver, uint256 amount, uint256 ticketHash) + public + { + require( + _kernel == msg.sender, + "MockWrapper: only kernel allowed to mint tokens" + ); + require(_ticketHash == ticketHash, "MockWrapper: wrong ticket hash"); + emit Mint(receiver, amount); + setFlag(amount); + } + + /** + * Burns `amount` tokens for `to` address if provided `ticket hash` + * is correct. + * + * Requirements: + * + * - only kernel address allowed to burn tokens. + * - `ticketHash` must be equal to the token hash of the ticketer + * and identifier used during deployment. + */ + function withdraw(address sender, uint256 amount, uint256 ticketHash) + public + { + require( + _kernel == msg.sender, + "MockWrapper: only kernel allowed to mint tokens" + ); + require(_ticketHash == ticketHash, "MockWrapper: wrong ticket hash"); + emit Burn(sender, amount); + setFlag(amount); + } +} \ No newline at end of file -- GitLab