diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8d52c6b4..d2c653be 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -32,7 +32,7 @@ jobs: with: version: nightly - name: Build - run: cargo build --all-targets + run: cargo build --release --all-targets lints: runs-on: kuberunner steps: @@ -49,7 +49,7 @@ jobs: with: version: nightly - name: Run clippy - run: cargo clippy --all-targets -- -D warnings $(cat .lints | cut -f1 -d"#" | tr '\n' ' ') + run: cargo clippy --release --all-targets -- -D warnings $(cat .lints | cut -f1 -d"#" | tr '\n' ' ') - name: Run rustfmt run: cargo fmt -- --check tests: diff --git a/Cargo.lock b/Cargo.lock index eb073798..477d3cd9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1794,8 +1794,8 @@ dependencies = [ "extended_vft_wasm", "gtest 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)", "parity-scale-codec", - "sails-idl-gen 0.4.0", - "sails-rs 0.4.0", + "sails-idl-gen 0.5.0", + "sails-rs 0.5.0", "tokio", "vft-client", "vft-gateway", @@ -1809,7 +1809,7 @@ dependencies = [ "gear-wasm-instrument 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)", "gstd 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)", "parity-scale-codec", - "sails-rs 0.4.0", + "sails-rs 0.5.0", "scale-info", "vft-gateway-client", ] @@ -1820,9 +1820,9 @@ version = "0.1.0" dependencies = [ "bridging-payment-app", "mockall 0.12.1", - "sails-client-gen 0.4.0", - "sails-idl-gen 0.4.0", - "sails-rs 0.4.0", + "sails-client-gen 0.5.0", + "sails-idl-gen 0.5.0", + "sails-rs 0.5.0", ] [[package]] @@ -1835,8 +1835,8 @@ dependencies = [ "extended_vft_wasm", "gtest 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)", "parity-scale-codec", - "sails-idl-gen 0.4.0", - "sails-rs 0.4.0", + "sails-idl-gen 0.5.0", + "sails-rs 0.5.0", "tokio", "vft-client", "vft-treasury", @@ -1849,7 +1849,7 @@ version = "0.1.0" dependencies = [ "gstd 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)", "parity-scale-codec", - "sails-rs 0.4.0", + "sails-rs 0.5.0", "scale-info", "vft-treasury-client", ] @@ -1860,9 +1860,9 @@ version = "0.1.0" dependencies = [ "bridging-payment-vara-supply-app", "mockall 0.12.1", - "sails-client-gen 0.4.0", - "sails-idl-gen 0.4.0", - "sails-rs 0.4.0", + "sails-client-gen 0.5.0", + "sails-idl-gen 0.5.0", + "sails-rs 0.5.0", ] [[package]] @@ -3600,6 +3600,49 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" +[[package]] +name = "erc20-relay" +version = "0.1.0" +dependencies = [ + "erc20-relay-app", + "sails-idl-gen 0.5.0", + "sails-rs 0.5.0", +] + +[[package]] +name = "erc20-relay-app" +version = "0.1.0" +dependencies = [ + "alloy-rlp", + "alloy-sol-types", + "checkpoint_light_client-io", + "erc20-relay", + "erc20-relay-client", + "ethereum-common", + "futures", + "gclient 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "gear-wasm-instrument 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "getrandom 0.2.15", + "gstd 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "hex", + "hex-literal", + "lazy_static", + "sails-client-gen 0.5.0", + "sails-rs 0.5.0", + "tokio", +] + +[[package]] +name = "erc20-relay-client" +version = "0.1.0" +dependencies = [ + "erc20-relay-app", + "mockall 0.12.1", + "sails-client-gen 0.5.0", + "sails-idl-gen 0.5.0", + "sails-rs 0.5.0", +] + [[package]] name = "errno" version = "0.3.9" @@ -9848,7 +9891,7 @@ dependencies = [ "prover", "rand 0.8.5", "reqwest 0.11.27", - "sails-rs 0.4.0", + "sails-rs 0.5.0", "serde", "serde_json", "thiserror", @@ -10337,15 +10380,15 @@ dependencies = [ [[package]] name = "sails-client-gen" -version = "0.4.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "298eecdb11ee41e68215a02c97541f2bcc7c24c310cec026483a9324254a283a" +checksum = "503481b7d240abc0bb7c187ed9f4ebd677f872eb19646cf1c8fa1a8f97fa55d1" dependencies = [ "anyhow", "convert_case 0.6.0", "genco", "parity-scale-codec", - "sails-idl-parser 0.4.0", + "sails-idl-parser 0.5.0", ] [[package]] @@ -10364,13 +10407,13 @@ dependencies = [ [[package]] name = "sails-idl-gen" -version = "0.4.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9007c11042e82c2a1edf720ff510ba0996af4ee549dce3faba455679533f06d9" +checksum = "8dc2e7d4e201aaf83210001163c487585093f6925dd4418d6b52f20cb840e246" dependencies = [ "convert_case 0.6.0", "handlebars", - "sails-rs 0.4.0", + "sails-rs 0.5.0", "scale-info", "serde", "serde_json", @@ -10390,9 +10433,9 @@ dependencies = [ [[package]] name = "sails-idl-parser" -version = "0.4.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "711bea52cfacceeca67312491b905bd2df6a1a4c0969be313381b30367b54092" +checksum = "ad0c48a07852f792a0e8b8955d2e3b1383a8eadb61ea5e6eed83e4f7a1035277" dependencies = [ "lalrpop", "lalrpop-util", @@ -10411,12 +10454,12 @@ dependencies = [ [[package]] name = "sails-macros" -version = "0.4.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4efcda7e656871d17e79259110a6b17d0d660e2ab3f949a91d55c165bcbd4d0d" +checksum = "99e50ef0bbb4ddfca2479f33aae0d33d5fecf86eeaa39a63e1a6b785605ffee9" dependencies = [ "proc-macro-error", - "sails-macros-core 0.4.0", + "sails-macros-core 0.5.0", ] [[package]] @@ -10434,9 +10477,9 @@ dependencies = [ [[package]] name = "sails-macros-core" -version = "0.4.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3694ccad10e20282caf8111c39382c3e1f00433c5f0c48d4592e1f201b82f598" +checksum = "2c866b06fc0a1bc08f8c0b549d7c13f56b270d753388777cdf4551c7ad3b7200" dependencies = [ "convert_case 0.6.0", "parity-scale-codec", @@ -10470,15 +10513,16 @@ dependencies = [ [[package]] name = "sails-rs" -version = "0.4.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67effccdaef6232976af5cabf54775bd57e362b09e201ceb5e732f8dd7194c9c" +checksum = "893c65b27bcaaaa5a0460a3f22126771f99f2ad171134e10a90589c563d52f48" dependencies = [ "futures", "gclient 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)", "gear-core 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)", "gear-core-errors 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)", "gear-wasm-builder 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "gear-wasm-instrument 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)", "gprimitives 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)", "gstd 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)", "gtest 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -10486,7 +10530,7 @@ dependencies = [ "hex", "mockall 0.12.1", "parity-scale-codec", - "sails-macros 0.4.0", + "sails-macros 0.5.0", "scale-info", "spin 0.9.8", "thiserror-no-std", @@ -14005,8 +14049,8 @@ version = "0.1.0" dependencies = [ "git-download", "mockall 0.12.1", - "sails-client-gen 0.4.0", - "sails-rs 0.4.0", + "sails-client-gen 0.5.0", + "sails-rs 0.5.0", ] [[package]] @@ -14018,8 +14062,8 @@ dependencies = [ "gear-core 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)", "gtest 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)", "parity-scale-codec", - "sails-idl-gen 0.4.0", - "sails-rs 0.4.0", + "sails-idl-gen 0.5.0", + "sails-rs 0.5.0", "scale-info", "tokio", "vft-client", @@ -14037,8 +14081,8 @@ dependencies = [ "git-download", "gstd 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)", "parity-scale-codec", - "sails-client-gen 0.4.0", - "sails-rs 0.4.0", + "sails-client-gen 0.5.0", + "sails-rs 0.5.0", "scale-info", "vft-client", ] @@ -14048,9 +14092,9 @@ name = "vft-gateway-client" version = "0.1.0" dependencies = [ "mockall 0.12.1", - "sails-client-gen 0.4.0", - "sails-idl-gen 0.4.0", - "sails-rs 0.4.0", + "sails-client-gen 0.5.0", + "sails-idl-gen 0.5.0", + "sails-rs 0.5.0", "vft-gateway-app", ] @@ -14075,8 +14119,8 @@ dependencies = [ "gear-core 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)", "gtest 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)", "parity-scale-codec", - "sails-idl-gen 0.4.0", - "sails-rs 0.4.0", + "sails-idl-gen 0.5.0", + "sails-rs 0.5.0", "scale-info", "tokio", "vft-client", @@ -14093,7 +14137,7 @@ dependencies = [ "gear-wasm-instrument 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)", "gstd 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)", "parity-scale-codec", - "sails-rs 0.4.0", + "sails-rs 0.5.0", "scale-info", "vft-client", ] @@ -14103,9 +14147,9 @@ name = "vft-treasury-client" version = "0.1.0" dependencies = [ "mockall 0.12.1", - "sails-client-gen 0.4.0", - "sails-idl-gen 0.4.0", - "sails-rs 0.4.0", + "sails-client-gen 0.5.0", + "sails-idl-gen 0.5.0", + "sails-rs 0.5.0", "vft-treasury-app", ] diff --git a/Cargo.toml b/Cargo.toml index 1e1e25bc..31efba13 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,6 +22,8 @@ members = [ "gear-programs/vft-client", "gear-programs/*", "gear-programs/checkpoint-light-client/io", + "gear-programs/erc20-relay/app", + "gear-programs/erc20-relay/client", "utils-prometheus", ] @@ -59,6 +61,9 @@ gear_proof_storage = { path = "gear-programs/proof-storage" } checkpoint_light_client-io = { path = "gear-programs/checkpoint-light-client/io", default-features = false } utils-prometheus = { path = "utils-prometheus" } checkpoint_light_client = { path = "gear-programs/checkpoint-light-client", default-features = false } +erc20-relay = { path = "gear-programs/erc20-relay" } +erc20-relay-app = { path = "gear-programs/erc20-relay/app" } +erc20-relay-client = { path = "gear-programs/erc20-relay/client" } # Contracts' deps extended_vft_wasm = { git = "https://github.com/gear-foundation/standards/", branch = "gstd-pinned-v1.5.0"} @@ -98,6 +103,7 @@ ethereum-types = { version = "0.14.1", default-features = false, features = [ ff = { version = "0.13.0", features = ["derive"] } futures = { version = "0.3.30", features = ["executor"] } futures-util = "0.3.28" +getrandom = { version = "0.2", default-features = false } git-download = "0.1" hash-db = { version = "0.15.2", default-features = false } hash256-std-hasher = { version = "0.15.2", default-features = false } @@ -110,7 +116,7 @@ lazy_static = "1.4.0" libc = "0.2.153" log = "0.4.14" memory-db = { version = "0.27.0", default-features = false } -mockall = { version = "0.12" } +mockall = "0.12" num = { version = "0.4", features = ["rand"] } paste = "1.0.14" pretty_env_logger = "0.5.0" @@ -156,9 +162,9 @@ gbuiltin-eth-bridge = { git = "https://github.com/gear-tech/gear.git", tag = "v1 pallet-gear-eth-bridge-rpc-runtime-api = { git = "https://github.com/gear-tech/gear.git", tag = "v1.5.0", default-features = false, features = [ "std", ] } -sails-idl-gen = "0.4.0" -sails-client-gen = "0.4.0" -sails-rs = "0.4.0" +sails-idl-gen = "0.5.0" +sails-client-gen = "0.5.0" +sails-rs = "0.5.0" subxt = "0.32.1" sc-consensus-grandpa = { version = "0.10.0-dev", git = "https://github.com/gear-tech/polkadot-sdk.git", branch = "gear-v1.4.0", default-features = false } @@ -178,6 +184,7 @@ alloy-consensus = { version = "0.2.1", default-features = false } alloy-eips = { version = "0.2.1", default-features = false } alloy-rlp = { version = "0.3.8", default-features = false } alloy-primitives = { version = "0.7.7", default-features = false } +alloy-sol-types = { version = "0.7.7", default-features = false } alloy = { version = "0.2.0", package = "alloy", features = [ "sol-types", "contract", diff --git a/ethereum-common/src/lib.rs b/ethereum-common/src/lib.rs index fe09204d..67394f15 100644 --- a/ethereum-common/src/lib.rs +++ b/ethereum-common/src/lib.rs @@ -23,7 +23,8 @@ use core::{ slice::{self, SliceIndex}, }; -pub use ethereum_types::{H256, U256}; +pub use ethereum_types::{H160, H256, U256}; +pub use hash_db; use parity_scale_codec::{Decode, Encode}; use scale_info::TypeInfo; use serde::{de, Deserialize}; diff --git a/ethereum-common/src/utils.rs b/ethereum-common/src/utils.rs index 85117da6..bb4ea24a 100644 --- a/ethereum-common/src/utils.rs +++ b/ethereum-common/src/utils.rs @@ -1,5 +1,4 @@ use super::*; -use alloy_consensus::ReceiptEnvelope; use alloy_eips::eip2718::Encodable2718; use alloy_primitives::Log; use alloy_rlp::Encodable; @@ -7,8 +6,9 @@ use core::str::FromStr; const CAPACITY_RLP_RECEIPT: usize = 10_000; +pub type ReceiptEnvelope = alloy_consensus::ReceiptEnvelope; /// Tuple with a transaction index and the related receipt. -pub type Receipt = (u64, ReceiptEnvelope); +pub type Receipt = (u64, ReceiptEnvelope); pub fn calculate_epoch(slot: u64) -> u64 { slot / SLOTS_PER_EPOCH @@ -120,10 +120,7 @@ pub fn rlp_encode_transaction_index(index: &u64) -> Vec { buf } -pub fn rlp_encode_index_and_receipt( - index: &u64, - receipt: &ReceiptEnvelope, -) -> (Vec, Vec) { +pub fn rlp_encode_index_and_receipt(index: &u64, receipt: &ReceiptEnvelope) -> (Vec, Vec) { let mut buf = Vec::with_capacity(CAPACITY_RLP_RECEIPT); receipt.encode_2718(&mut buf); diff --git a/gear-programs/checkpoint-light-client/Cargo.toml b/gear-programs/checkpoint-light-client/Cargo.toml index 59ef72ae..6ad1739a 100644 --- a/gear-programs/checkpoint-light-client/Cargo.toml +++ b/gear-programs/checkpoint-light-client/Cargo.toml @@ -21,8 +21,8 @@ gear-wasm-instrument.workspace = true checkpoint_light_client-io.workspace = true [target.'cfg(all(target_arch = "wasm32", target_os = "unknown"))'.dependencies] -getrandom = { version = "0.2", default-features = false, features = ["custom"] } -lazy_static = { version = "1.1", features = ["spin_no_std"] } +getrandom = { workspace = true, features = ["custom"] } +lazy_static = { workspace = true, features = ["spin_no_std"] } [features] std = [ diff --git a/gear-programs/erc20-relay/Cargo.toml b/gear-programs/erc20-relay/Cargo.toml new file mode 100644 index 00000000..d9df66a2 --- /dev/null +++ b/gear-programs/erc20-relay/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "erc20-relay" +version.workspace = true +edition.workspace = true + +[dependencies] +erc20-relay-app.workspace = true + +[build-dependencies] +erc20-relay-app.workspace = true +sails-rs = { workspace = true, features = ["wasm-builder"] } +sails-idl-gen.workspace = true + +[features] +wasm-binary = [] +gas_calculation = ["erc20-relay-app/gas_calculation"] diff --git a/gear-programs/erc20-relay/README.md b/gear-programs/erc20-relay/README.md new file mode 100644 index 00000000..0486ba5e --- /dev/null +++ b/gear-programs/erc20-relay/README.md @@ -0,0 +1,8 @@ +## The **erc20-relay** program + +The program workspace includes the following packages: +- `erc20-relay` is the package allowing to build WASM binary for the program and IDL file for it. +- `erc20-relay-app` is the package containing business logic for the program represented by the `Erc20RelayService` structure. +- `erc20-relay-client` is the package containing the client for the program allowing to interact with it from another program, tests, or + off-chain client. + diff --git a/gear-programs/erc20-relay/app/Cargo.toml b/gear-programs/erc20-relay/app/Cargo.toml new file mode 100644 index 00000000..65c4b2d2 --- /dev/null +++ b/gear-programs/erc20-relay/app/Cargo.toml @@ -0,0 +1,34 @@ +[package] +name = "erc20-relay-app" +version.workspace = true +edition.workspace = true + +[dependencies] +alloy-rlp.workspace = true +alloy-sol-types = { workspace = true, features = ["json"] } +checkpoint_light_client-io.workspace = true +ethereum-common.workspace = true +gear-wasm-instrument.workspace = true +sails-rs.workspace = true + +[target.'cfg(all(target_arch = "wasm32", target_os = "unknown"))'.dependencies] +getrandom = { workspace = true, features = ["custom"] } +lazy_static = { workspace = true, features = ["spin_no_std"] } + +[dev-dependencies] +erc20-relay-client.workspace = true +erc20-relay = { workspace = true, features = ["wasm-binary", "gas_calculation"] } +futures.workspace = true +gclient.workspace = true +gstd.workspace = true +sails-rs = { workspace = true, features = ["gclient"] } +tokio = { workspace = true, features = ["rt", "macros"] } +hex-literal.workspace = true +hex.workspace = true + +[build-dependencies] +sails-client-gen.workspace = true + +[features] +gas_calculation = [] +mocks = [] diff --git a/gear-programs/erc20-relay/app/build.rs b/gear-programs/erc20-relay/app/build.rs new file mode 100644 index 00000000..4538ec3a --- /dev/null +++ b/gear-programs/erc20-relay/app/build.rs @@ -0,0 +1,23 @@ +use sails_client_gen::ClientGenerator; +use std::{env, path::PathBuf}; + +fn main() { + let idl_file_path = { + let mut path = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap()); + path.pop(); + path.pop(); + + path.push("vft-gateway"); + path.push("src"); + path.push("wasm"); + path.push("vft-gateway.idl"); + + path + }; + + // Generate client code from IDL file + ClientGenerator::from_idl_path(&idl_file_path) + .with_mocks("mocks") + .generate_to(PathBuf::from(env::var("OUT_DIR").unwrap()).join("vft-gateway.rs")) + .unwrap(); +} diff --git a/gear-programs/erc20-relay/app/src/ERC20Treasury.json b/gear-programs/erc20-relay/app/src/ERC20Treasury.json new file mode 100644 index 00000000..fb82ff21 --- /dev/null +++ b/gear-programs/erc20-relay/app/src/ERC20Treasury.json @@ -0,0 +1 @@ +{"abi":[{"type":"constructor","inputs":[{"name":"message_queue","type":"address","internalType":"address"}],"stateMutability":"nonpayable"},{"type":"function","name":"deposit","inputs":[{"name":"token","type":"address","internalType":"address"},{"name":"amount","type":"uint256","internalType":"uint256"},{"name":"to","type":"bytes32","internalType":"bytes32"}],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"processVaraMessage","inputs":[{"name":"vara_msg","type":"tuple","internalType":"struct VaraMessage","components":[{"name":"nonce","type":"bytes32","internalType":"bytes32"},{"name":"sender","type":"bytes32","internalType":"bytes32"},{"name":"receiver","type":"address","internalType":"address"},{"name":"data","type":"bytes","internalType":"bytes"}]}],"outputs":[{"name":"","type":"bool","internalType":"bool"}],"stateMutability":"nonpayable"},{"type":"event","name":"Deposit","inputs":[{"name":"from","type":"address","indexed":true,"internalType":"address"},{"name":"to","type":"bytes32","indexed":true,"internalType":"bytes32"},{"name":"token","type":"address","indexed":true,"internalType":"address"},{"name":"amount","type":"uint256","indexed":false,"internalType":"uint256"}],"anonymous":false},{"type":"event","name":"Withdraw","inputs":[{"name":"to","type":"address","indexed":true,"internalType":"address"},{"name":"token","type":"address","indexed":true,"internalType":"address"},{"name":"amount","type":"uint256","indexed":false,"internalType":"uint256"}],"anonymous":false},{"type":"error","name":"AddressEmptyCode","inputs":[{"name":"target","type":"address","internalType":"address"}]},{"type":"error","name":"BadArguments","inputs":[]},{"type":"error","name":"BadEthAddress","inputs":[]},{"type":"error","name":"BadVaraAddress","inputs":[]},{"type":"error","name":"FailedCall","inputs":[]},{"type":"error","name":"InsufficientBalance","inputs":[{"name":"balance","type":"uint256","internalType":"uint256"},{"name":"needed","type":"uint256","internalType":"uint256"}]},{"type":"error","name":"NotAuthorized","inputs":[]},{"type":"error","name":"SafeERC20FailedOperation","inputs":[{"name":"token","type":"address","internalType":"address"}]}],"bytecode":{"object":"0x60a0604052348015600e575f80fd5b5060405161065d38038061065d833981016040819052602b91603b565b6001600160a01b03166080526066565b5f60208284031215604a575f80fd5b81516001600160a01b0381168114605f575f80fd5b9392505050565b6080516105e061007d5f395f608301526105e05ff3fe608060405234801561000f575f80fd5b5060043610610034575f3560e01c806313cfea221461003857806326b3293f1461005f575b5f80fd5b61004b610046366004610490565b610074565b604051901515815260200160405180910390f35b61007261006d3660046104e2565b6101e6565b005b5f808080336001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016146100c15760405163ea8e4eb560e01b815260040160405180910390fd5b6100ce6060860186610512565b90506038146100f057604051630b9cddcd60e11b815260040160405180910390fd5b30610101606087016040880161055c565b6001600160a01b031614610128576040516399f36bdf60e01b815260040160405180910390fd5b60208501357f9e662fa61013715253844d3ef0344685ad4cc2664853e782e94545de1eb2af3b1461016c5760405163670e8a3760e01b815260040160405180910390fd5b50505060c435606090811c9060d835901c60ec3560801c61018e82848361024e565b816001600160a01b0316836001600160a01b03167f9b1bfa7fa9ee420a16e124f794c35ac9f90472acc99140eb2f6447c714cad8eb836040516101d391815260200190565b60405180910390a3506001949350505050565b6101fb6001600160a01b0384163330856102b2565b6001600160a01b03831681336001600160a01b03167fbe120278019af83a2d6506be58cc7d863f4f76830d552cb338ea426d1e3e01c18560405161024191815260200190565b60405180910390a4505050565b6040516001600160a01b038381166024830152604482018390526102ad91859182169063a9059cbb906064015b604051602081830303815290604052915060e01b6020820180516001600160e01b0383818316178352505050506102f1565b505050565b6040516001600160a01b0384811660248301528381166044830152606482018390526102eb9186918216906323b872dd9060840161027b565b50505050565b5f6103056001600160a01b03841683610357565b905080515f141580156103295750808060200190518101906103279190610575565b155b156102ad57604051635274afe760e01b81526001600160a01b03841660048201526024015b60405180910390fd5b606061036483835f61036b565b9392505050565b6060814710156103975760405163cf47918160e01b81524760048201526024810183905260440161034e565b5f80856001600160a01b031684866040516103b29190610594565b5f6040518083038185875af1925050503d805f81146103ec576040519150601f19603f3d011682016040523d82523d5f602084013e6103f1565b606091505b509150915061040186838361040b565b9695505050505050565b6060826104205761041b82610467565b610364565b815115801561043757506001600160a01b0384163b155b1561046057604051639996b31560e01b81526001600160a01b038516600482015260240161034e565b5080610364565b8051156104775780518082602001fd5b60405163d6bda27560e01b815260040160405180910390fd5b5f602082840312156104a0575f80fd5b813567ffffffffffffffff8111156104b6575f80fd5b820160808185031215610364575f80fd5b80356001600160a01b03811681146104dd575f80fd5b919050565b5f805f606084860312156104f4575f80fd5b6104fd846104c7565b95602085013595506040909401359392505050565b5f808335601e19843603018112610527575f80fd5b83018035915067ffffffffffffffff821115610541575f80fd5b602001915036819003821315610555575f80fd5b9250929050565b5f6020828403121561056c575f80fd5b610364826104c7565b5f60208284031215610585575f80fd5b81518015158114610364575f80fd5b5f82518060208501845e5f92019182525091905056fea2646970667358221220824c04dbacc3efb722b07e2ecc0f3f22675dd8a1c284b82367323600e719e75164736f6c634300081a0033","sourceMap":"564:2095:47:-:0;;;722:90;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;-1:-1:-1;;;;;767:37:47;;;564:2095;;14:290:60;84:6;137:2;125:9;116:7;112:23;108:32;105:52;;;153:1;150;143:12;105:52;179:16;;-1:-1:-1;;;;;224:31:60;;214:42;;204:70;;270:1;267;260:12;204:70;293:5;14:290;-1:-1:-1;;;14:290:60:o;:::-;564:2095:47;;;;;;;;;;;;","linkReferences":{}},"deployedBytecode":{"object":"0x608060405234801561000f575f80fd5b5060043610610034575f3560e01c806313cfea221461003857806326b3293f1461005f575b5f80fd5b61004b610046366004610490565b610074565b604051901515815260200160405180910390f35b61007261006d3660046104e2565b6101e6565b005b5f808080336001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016146100c15760405163ea8e4eb560e01b815260040160405180910390fd5b6100ce6060860186610512565b90506038146100f057604051630b9cddcd60e11b815260040160405180910390fd5b30610101606087016040880161055c565b6001600160a01b031614610128576040516399f36bdf60e01b815260040160405180910390fd5b60208501357f9e662fa61013715253844d3ef0344685ad4cc2664853e782e94545de1eb2af3b1461016c5760405163670e8a3760e01b815260040160405180910390fd5b50505060c435606090811c9060d835901c60ec3560801c61018e82848361024e565b816001600160a01b0316836001600160a01b03167f9b1bfa7fa9ee420a16e124f794c35ac9f90472acc99140eb2f6447c714cad8eb836040516101d391815260200190565b60405180910390a3506001949350505050565b6101fb6001600160a01b0384163330856102b2565b6001600160a01b03831681336001600160a01b03167fbe120278019af83a2d6506be58cc7d863f4f76830d552cb338ea426d1e3e01c18560405161024191815260200190565b60405180910390a4505050565b6040516001600160a01b038381166024830152604482018390526102ad91859182169063a9059cbb906064015b604051602081830303815290604052915060e01b6020820180516001600160e01b0383818316178352505050506102f1565b505050565b6040516001600160a01b0384811660248301528381166044830152606482018390526102eb9186918216906323b872dd9060840161027b565b50505050565b5f6103056001600160a01b03841683610357565b905080515f141580156103295750808060200190518101906103279190610575565b155b156102ad57604051635274afe760e01b81526001600160a01b03841660048201526024015b60405180910390fd5b606061036483835f61036b565b9392505050565b6060814710156103975760405163cf47918160e01b81524760048201526024810183905260440161034e565b5f80856001600160a01b031684866040516103b29190610594565b5f6040518083038185875af1925050503d805f81146103ec576040519150601f19603f3d011682016040523d82523d5f602084013e6103f1565b606091505b509150915061040186838361040b565b9695505050505050565b6060826104205761041b82610467565b610364565b815115801561043757506001600160a01b0384163b155b1561046057604051639996b31560e01b81526001600160a01b038516600482015260240161034e565b5080610364565b8051156104775780518082602001fd5b60405163d6bda27560e01b815260040160405180910390fd5b5f602082840312156104a0575f80fd5b813567ffffffffffffffff8111156104b6575f80fd5b820160808185031215610364575f80fd5b80356001600160a01b03811681146104dd575f80fd5b919050565b5f805f606084860312156104f4575f80fd5b6104fd846104c7565b95602085013595506040909401359392505050565b5f808335601e19843603018112610527575f80fd5b83018035915067ffffffffffffffff821115610541575f80fd5b602001915036819003821315610555575f80fd5b9250929050565b5f6020828403121561056c575f80fd5b610364826104c7565b5f60208284031215610585575f80fd5b81518015158114610364575f80fd5b5f82518060208501845e5f92019182525091905056fea2646970667358221220824c04dbacc3efb722b07e2ecc0f3f22675dd8a1c284b82367323600e719e75164736f6c634300081a0033","sourceMap":"564:2095:47:-:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;1744:913;;;;;;:::i;:::-;;:::i;:::-;;;576:14:60;;569:22;551:41;;539:2;524:18;1744:913:47;;;;;;;1157:206;;;;;;:::i;:::-;;:::i;:::-;;1744:913;1835:4;;;;1928:10;-1:-1:-1;;;;;1942:21:47;1928:35;;1924:88;;1986:15;;-1:-1:-1;;;1986:15:47;;;;;;;;;;;1924:88;2026:13;;;;:8;:13;:::i;:::-;:20;;2050:12;2026:36;2022:88;;2085:14;;-1:-1:-1;;;2085:14:47;;;;;;;;;;;2022:88;2152:4;2123:17;;;;;;;;:::i;:::-;-1:-1:-1;;;;;2123:34:47;;2119:87;;2180:15;;-1:-1:-1;;;2180:15:47;;;;;;;;;;;2119:87;2219:15;;;;81:66:56;2219:41:47;2215:95;;2283:16;;-1:-1:-1;;;2283:16:47;;;;;;;;;;;2215:95;-1:-1:-1;;;2376:4:47;2363:18;2359:2;2355:27;;;;2425:4;2412:18;2404:27;;2476:4;2463:18;2458:3;2454:28;2501:62;2404:27;2355;2454:28;2501:35;:62::i;:::-;2614:5;-1:-1:-1;;;;;2578:51:47;2595:8;-1:-1:-1;;;;;2578:51:47;;2622:6;2578:51;;;;1971:25:60;;1959:2;1944:18;;1825:177;2578:51:47;;;;;;;;-1:-1:-1;2646:4:47;;1744:913;-1:-1:-1;;;;1744:913:47:o;1157:206::-;1234:67;-1:-1:-1;;;;;1234:30:47;;735:10:39;1287:4:47;1294:6;1234:30;:67::i;:::-;-1:-1:-1;;;;;1316:40:47;;1338:2;735:10:39;-1:-1:-1;;;;;1316:40:47;;1349:6;1316:40;;;;1971:25:60;;1959:2;1944:18;;1825:177;1316:40:47;;;;;;;;1157:206;;;:::o;1303:160:37:-;1412:43;;-1:-1:-1;;;;;2199:32:60;;;1412:43:37;;;2181:51:60;2248:18;;;2241:34;;;1385:71:37;;1405:5;;1427:14;;;;;2154:18:60;;1412:43:37;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;;;;1412:43:37;;;;;;;;;;;1385:19;:71::i;:::-;1303:160;;;:::o;1702:188::-;1829:53;;-1:-1:-1;;;;;2506:32:60;;;1829:53:37;;;2488:51:60;2575:32;;;2555:18;;;2548:60;2624:18;;;2617:34;;;1802:81:37;;1822:5;;1844:18;;;;;2461::60;;1829:53:37;2286:371:60;1802:81:37;1702:188;;;;:::o;6468:629::-;6887:23;6913:33;-1:-1:-1;;;;;6913:27:37;;6941:4;6913:27;:33::i;:::-;6887:59;;6960:10;:17;6981:1;6960:22;;:57;;;;;6998:10;6987:30;;;;;;;;;;;;:::i;:::-;6986:31;6960:57;6956:135;;;7040:40;;-1:-1:-1;;;7040:40:37;;-1:-1:-1;;;;;3108:32:60;;7040:40:37;;;3090:51:60;3063:18;;7040:40:37;;;;;;;;2484:151:38;2559:12;2590:38;2612:6;2620:4;2626:1;2590:21;:38::i;:::-;2583:45;2484:151;-1:-1:-1;;;2484:151:38:o;2959:407::-;3058:12;3110:5;3086:21;:29;3082:123;;;3138:56;;-1:-1:-1;;;3138:56:38;;3165:21;3138:56;;;3326:25:60;3367:18;;;3360:34;;;3299:18;;3138:56:38;3152:248:60;3082:123:38;3215:12;3229:23;3256:6;-1:-1:-1;;;;;3256:11:38;3275:5;3282:4;3256:31;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;3214:73;;;;3304:55;3331:6;3339:7;3348:10;3304:26;:55::i;:::-;3297:62;2959:407;-1:-1:-1;;;;;;2959:407:38:o;4421:582::-;4565:12;4594:7;4589:408;;4617:19;4625:10;4617:7;:19::i;:::-;4589:408;;;4841:17;;:22;:49;;;;-1:-1:-1;;;;;;4867:18:38;;;:23;4841:49;4837:119;;;4917:24;;-1:-1:-1;;;4917:24:38;;-1:-1:-1;;;;;3108:32:60;;4917:24:38;;;3090:51:60;3063:18;;4917:24:38;2944:203:60;4837:119:38;-1:-1:-1;4976:10:38;4969:17;;5543:518;5674:17;;:21;5670:385;;5902:10;5896:17;5958:15;5945:10;5941:2;5937:19;5930:44;5670:385;6025:19;;-1:-1:-1;;;6025:19:38;;;;;;;;;;;14:392:60;105:6;158:2;146:9;137:7;133:23;129:32;126:52;;;174:1;171;164:12;126:52;214:9;201:23;247:18;239:6;236:30;233:50;;;279:1;276;269:12;233:50;302:22;;358:3;340:16;;;336:26;333:46;;;375:1;372;365:12;603:173;671:20;;-1:-1:-1;;;;;720:31:60;;710:42;;700:70;;766:1;763;756:12;700:70;603:173;;;:::o;781:322::-;858:6;866;874;927:2;915:9;906:7;902:23;898:32;895:52;;;943:1;940;933:12;895:52;966:29;985:9;966:29;:::i;:::-;956:39;1042:2;1027:18;;1014:32;;-1:-1:-1;1093:2:60;1078:18;;;1065:32;;781:322;-1:-1:-1;;;781:322:60:o;1108:521::-;1185:4;1191:6;1251:11;1238:25;1345:2;1341:7;1330:8;1314:14;1310:29;1306:43;1286:18;1282:68;1272:96;;1364:1;1361;1354:12;1272:96;1391:33;;1443:20;;;-1:-1:-1;1486:18:60;1475:30;;1472:50;;;1518:1;1515;1508:12;1472:50;1551:4;1539:17;;-1:-1:-1;1582:14:60;1578:27;;;1568:38;;1565:58;;;1619:1;1616;1609:12;1565:58;1108:521;;;;;:::o;1634:186::-;1693:6;1746:2;1734:9;1725:7;1721:23;1717:32;1714:52;;;1762:1;1759;1752:12;1714:52;1785:29;1804:9;1785:29;:::i;2662:277::-;2729:6;2782:2;2770:9;2761:7;2757:23;2753:32;2750:52;;;2798:1;2795;2788:12;2750:52;2830:9;2824:16;2883:5;2876:13;2869:21;2862:5;2859:32;2849:60;;2905:1;2902;2895:12;3405:301;3534:3;3572:6;3566:13;3618:6;3611:4;3603:6;3599:17;3594:3;3588:37;3680:1;3644:16;;3669:13;;;-1:-1:-1;3644:16:60;3405:301;-1:-1:-1;3405:301:60:o","linkReferences":{},"immutableReferences":{"50104":[{"start":131,"length":32}]}},"methodIdentifiers":{"deposit(address,uint256,bytes32)":"26b3293f","processVaraMessage((bytes32,bytes32,address,bytes))":"13cfea22"},"rawMetadata":"{\"compiler\":{\"version\":\"0.8.26+commit.8a97fa7a\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"inputs\":[{\"internalType\":\"address\",\"name\":\"message_queue\",\"type\":\"address\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"target\",\"type\":\"address\"}],\"name\":\"AddressEmptyCode\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"BadArguments\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"BadEthAddress\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"BadVaraAddress\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"FailedCall\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"balance\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"needed\",\"type\":\"uint256\"}],\"name\":\"InsufficientBalance\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"NotAuthorized\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"token\",\"type\":\"address\"}],\"name\":\"SafeERC20FailedOperation\",\"type\":\"error\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"from\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"to\",\"type\":\"bytes32\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"token\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"Deposit\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"token\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"Withdraw\",\"type\":\"event\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"token\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"},{\"internalType\":\"bytes32\",\"name\":\"to\",\"type\":\"bytes32\"}],\"name\":\"deposit\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"nonce\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"sender\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"receiver\",\"type\":\"address\"},{\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"}],\"internalType\":\"struct VaraMessage\",\"name\":\"vara_msg\",\"type\":\"tuple\"}],\"name\":\"processVaraMessage\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}],\"devdoc\":{\"errors\":{\"AddressEmptyCode(address)\":[{\"details\":\"There's no code at `target` (it is not a contract).\"}],\"FailedCall()\":[{\"details\":\"A call to an address target failed. The target may have reverted.\"}],\"InsufficientBalance(uint256,uint256)\":[{\"details\":\"The ETH balance of the account is not enough to perform the operation.\"}],\"SafeERC20FailedOperation(address)\":[{\"details\":\"An operation with an ERC-20 token failed.\"}]},\"kind\":\"dev\",\"methods\":{\"deposit(address,uint256,bytes32)\":{\"details\":\"Deposit token to `Treasury` using `safeTransferFrom`. Allowance needs to allow treasury contract transferring `amount` of tokens. Emits `Deposit` event.\",\"params\":{\"amount\":\"quantity of deposited token\",\"to\":\"destination of transfer on VARA network\",\"token\":\"token address to deposit\"}},\"processVaraMessage((bytes32,bytes32,address,bytes))\":{\"details\":\"Request withdraw of tokens. This request must be sent by `MessageQueue` only. Expected `payload` in `VaraMessage` consisits of these: - `receiver` - account to withdraw tokens to - `token` - token to withdraw - `amount` - amount of tokens to withdraw\",\"params\":{\"vara_msg\":\"`VaraMessage` received from MessageQueue.\"}}},\"version\":1},\"userdoc\":{\"kind\":\"user\",\"methods\":{},\"version\":1}},\"settings\":{\"compilationTarget\":{\"src/ERC20Treasury.sol\":\"ERC20Treasury\"},\"evmVersion\":\"cancun\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"ipfs\"},\"optimizer\":{\"enabled\":true,\"runs\":200},\"remappings\":[\":@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/\",\":ds-test/=lib/openzeppelin-contracts/lib/forge-std/lib/ds-test/src/\",\":erc4626-tests/=lib/openzeppelin-contracts/lib/erc4626-tests/\",\":forge-std/=lib/forge-std/src/\",\":halmos-cheatcodes/=lib/openzeppelin-contracts/lib/halmos-cheatcodes/src/\",\":openzeppelin-contracts/=lib/openzeppelin-contracts/\"]},\"sources\":{\"lib/openzeppelin-contracts/contracts/interfaces/IERC1363.sol\":{\"keccak256\":\"0x9f21f1bcc51daf7fe3998608d7eeb96b16a9c3816898a0cf6a9407bd105c9253\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://71cd1acb2370851314e9e2fc84123228e468037435eea0ed1c459346a214ce73\",\"dweb:/ipfs/QmXw5XVVnrjX3m224Zs9jdQVY3abwiCEVBjk9w24DXsFSi\"]},\"lib/openzeppelin-contracts/contracts/interfaces/IERC165.sol\":{\"keccak256\":\"0xde7e9fd9aee8d4f40772f96bb3b58836cbc6dfc0227014a061947f8821ea9724\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://11fea9f8bc98949ac6709f0c1699db7430d2948137aa94d5a9e95a91f61a710a\",\"dweb:/ipfs/QmQdfRXxQjwP6yn3DVo1GHPpriKNcFghSPi94Z1oKEFUNS\"]},\"lib/openzeppelin-contracts/contracts/interfaces/IERC20.sol\":{\"keccak256\":\"0xce41876e78d1badc0512229b4d14e4daf83bc1003d7f83978d18e0e56f965b9c\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://a2608291cb038b388d80b79a06b6118a42f7894ff67b7da10ec0dbbf5b2973ba\",\"dweb:/ipfs/QmWohqcBLbcxmA4eGPhZDXe5RYMMEEpFq22nfkaUMvTfw1\"]},\"lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol\":{\"keccak256\":\"0xee2337af2dc162a973b4be6d3f7c16f06298259e0af48c5470d2839bfa8a22f4\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://30c476b4b2f405c1bb3f0bae15b006d129c80f1bfd9d0f2038160a3bb9745009\",\"dweb:/ipfs/Qmb3VcuDufv6xbHeVgksC4tHpc5gKYVqBEwjEXW72XzSvN\"]},\"lib/openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol\":{\"keccak256\":\"0xde02e3a80c5c3b3a2187fbfbdfc7ed7c8c0d5b2e4a0ff5671611674b6c96bd91\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://44b4a6161c6b718c37229643c8e6881b82b14dbcf7ea1b0b081fbc7b810e3488\",\"dweb:/ipfs/QmUAxfrzeBusBHRkCfgzvD8axBKvdmtWz9rb52rYBH5K1w\"]},\"lib/openzeppelin-contracts/contracts/utils/Address.sol\":{\"keccak256\":\"0x80b4189de089dc632b752b365a16c5063b58cc24da0dd38b82f2c25f56d25c84\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://81e2717e78844156a86733f1cada84dba906ffe03e4957de12ca219c65e9191b\",\"dweb:/ipfs/QmW8vg3AafPJRo7EC75RQJTtjiaYmfPa4U4sqmEuBXXzaP\"]},\"lib/openzeppelin-contracts/contracts/utils/Context.sol\":{\"keccak256\":\"0x493033a8d1b176a037b2cc6a04dad01a5c157722049bbecf632ca876224dd4b2\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://6a708e8a5bdb1011c2c381c9a5cfd8a9a956d7d0a9dc1bd8bcdaf52f76ef2f12\",\"dweb:/ipfs/Qmax9WHBnVsZP46ZxEMNRQpLQnrdE4dK8LehML1Py8FowF\"]},\"lib/openzeppelin-contracts/contracts/utils/Errors.sol\":{\"keccak256\":\"0x1b0625096e82d06abdcf1844172ef78ef54a5e878761f4d905fda07eaf098424\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://5cd99f1a4836c07461cb3ea023ae2f6d1d01e80694b764a87623aa7252754756\",\"dweb:/ipfs/QmNPNDuiNU6TJatZcdBcrwixBoo5MSXNDq4kaXhpJLWGpB\"]},\"lib/openzeppelin-contracts/contracts/utils/introspection/IERC165.sol\":{\"keccak256\":\"0xc859863e3bda7ec3cddf6dafe2ffe91bcbe648d1395b856b839c32ee9617c44c\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://a9d5417888b873cf2225ed5d50b2a67be97c1504134a2a580512168d587ad82e\",\"dweb:/ipfs/QmNr5fTb2heFW658NZn7dDnofZgFvQTnNxKRJ3wdnR1skX\"]},\"src/ERC20Treasury.sol\":{\"keccak256\":\"0xfa4a5e76407fa2f4d9921785266662de2ec236646f2b533ddee58952b6ba687c\",\"urls\":[\"bzz-raw://c587c88d11b40107eb839f0c6a58e6bb4d3eba1b322f74ba6fdf338c478e8a5f\",\"dweb:/ipfs/QmbkDx7LsDMoUkoGvXf9LwXFjrEC1QmrFNwdpWQJiNRULa\"]},\"src/interfaces/IERC20Treasury.sol\":{\"keccak256\":\"0x7b5b14f4beba9333003f19664606640ee4c1608fd451da3d2890456768328aff\",\"urls\":[\"bzz-raw://d7d5270761601fbd8255de0f343adb4beb790f2e41321855a881066650b7a934\",\"dweb:/ipfs/QmNhx5kEocYXKBcX4CvFt4RDKmThYr4TmHxdEAseEputaQ\"]},\"src/interfaces/IMessageQueue.sol\":{\"keccak256\":\"0x25d2dd1c7616e63052f4bc5742d7c1d416f21a4dd73ce7f809b92a8e3fe34a92\",\"urls\":[\"bzz-raw://e5b2a1095a57498017d8ab6c016c1eb4b8757d06d1718e179a9fa23069f47c3b\",\"dweb:/ipfs/QmezCNrpEyz6R9UUH4gxHH3CUe8Y33HFBTuhyUEGZ6MsK4\"]},\"src/libraries/Environment.sol\":{\"keccak256\":\"0xe1f5143e4182aab3d28da7162ca987d8fd3f8bc3bf3ca8314aff4d562207e455\",\"urls\":[\"bzz-raw://ecb44da89f92e895ac277810b58aa8e8ee91140c53e297c78862e2aa3e405c64\",\"dweb:/ipfs/QmaHz4Ejyap7zuWZ5HVxv22Q4FAY6fjoM3PQWhK8S5jm6y\"]}},\"version\":1}","metadata":{"compiler":{"version":"0.8.26+commit.8a97fa7a"},"language":"Solidity","output":{"abi":[{"inputs":[{"internalType":"address","name":"message_queue","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[{"internalType":"address","name":"target","type":"address"}],"type":"error","name":"AddressEmptyCode"},{"inputs":[],"type":"error","name":"BadArguments"},{"inputs":[],"type":"error","name":"BadEthAddress"},{"inputs":[],"type":"error","name":"BadVaraAddress"},{"inputs":[],"type":"error","name":"FailedCall"},{"inputs":[{"internalType":"uint256","name":"balance","type":"uint256"},{"internalType":"uint256","name":"needed","type":"uint256"}],"type":"error","name":"InsufficientBalance"},{"inputs":[],"type":"error","name":"NotAuthorized"},{"inputs":[{"internalType":"address","name":"token","type":"address"}],"type":"error","name":"SafeERC20FailedOperation"},{"inputs":[{"internalType":"address","name":"from","type":"address","indexed":true},{"internalType":"bytes32","name":"to","type":"bytes32","indexed":true},{"internalType":"address","name":"token","type":"address","indexed":true},{"internalType":"uint256","name":"amount","type":"uint256","indexed":false}],"type":"event","name":"Deposit","anonymous":false},{"inputs":[{"internalType":"address","name":"to","type":"address","indexed":true},{"internalType":"address","name":"token","type":"address","indexed":true},{"internalType":"uint256","name":"amount","type":"uint256","indexed":false}],"type":"event","name":"Withdraw","anonymous":false},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"bytes32","name":"to","type":"bytes32"}],"stateMutability":"nonpayable","type":"function","name":"deposit"},{"inputs":[{"internalType":"struct VaraMessage","name":"vara_msg","type":"tuple","components":[{"internalType":"bytes32","name":"nonce","type":"bytes32"},{"internalType":"bytes32","name":"sender","type":"bytes32"},{"internalType":"address","name":"receiver","type":"address"},{"internalType":"bytes","name":"data","type":"bytes"}]}],"stateMutability":"nonpayable","type":"function","name":"processVaraMessage","outputs":[{"internalType":"bool","name":"","type":"bool"}]}],"devdoc":{"kind":"dev","methods":{"deposit(address,uint256,bytes32)":{"details":"Deposit token to `Treasury` using `safeTransferFrom`. Allowance needs to allow treasury contract transferring `amount` of tokens. Emits `Deposit` event.","params":{"amount":"quantity of deposited token","to":"destination of transfer on VARA network","token":"token address to deposit"}},"processVaraMessage((bytes32,bytes32,address,bytes))":{"details":"Request withdraw of tokens. This request must be sent by `MessageQueue` only. Expected `payload` in `VaraMessage` consisits of these: - `receiver` - account to withdraw tokens to - `token` - token to withdraw - `amount` - amount of tokens to withdraw","params":{"vara_msg":"`VaraMessage` received from MessageQueue."}}},"version":1},"userdoc":{"kind":"user","methods":{},"version":1}},"settings":{"remappings":["@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/","ds-test/=lib/openzeppelin-contracts/lib/forge-std/lib/ds-test/src/","erc4626-tests/=lib/openzeppelin-contracts/lib/erc4626-tests/","forge-std/=lib/forge-std/src/","halmos-cheatcodes/=lib/openzeppelin-contracts/lib/halmos-cheatcodes/src/","openzeppelin-contracts/=lib/openzeppelin-contracts/"],"optimizer":{"enabled":true,"runs":200},"metadata":{"bytecodeHash":"ipfs"},"compilationTarget":{"src/ERC20Treasury.sol":"ERC20Treasury"},"evmVersion":"cancun","libraries":{}},"sources":{"lib/openzeppelin-contracts/contracts/interfaces/IERC1363.sol":{"keccak256":"0x9f21f1bcc51daf7fe3998608d7eeb96b16a9c3816898a0cf6a9407bd105c9253","urls":["bzz-raw://71cd1acb2370851314e9e2fc84123228e468037435eea0ed1c459346a214ce73","dweb:/ipfs/QmXw5XVVnrjX3m224Zs9jdQVY3abwiCEVBjk9w24DXsFSi"],"license":"MIT"},"lib/openzeppelin-contracts/contracts/interfaces/IERC165.sol":{"keccak256":"0xde7e9fd9aee8d4f40772f96bb3b58836cbc6dfc0227014a061947f8821ea9724","urls":["bzz-raw://11fea9f8bc98949ac6709f0c1699db7430d2948137aa94d5a9e95a91f61a710a","dweb:/ipfs/QmQdfRXxQjwP6yn3DVo1GHPpriKNcFghSPi94Z1oKEFUNS"],"license":"MIT"},"lib/openzeppelin-contracts/contracts/interfaces/IERC20.sol":{"keccak256":"0xce41876e78d1badc0512229b4d14e4daf83bc1003d7f83978d18e0e56f965b9c","urls":["bzz-raw://a2608291cb038b388d80b79a06b6118a42f7894ff67b7da10ec0dbbf5b2973ba","dweb:/ipfs/QmWohqcBLbcxmA4eGPhZDXe5RYMMEEpFq22nfkaUMvTfw1"],"license":"MIT"},"lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol":{"keccak256":"0xee2337af2dc162a973b4be6d3f7c16f06298259e0af48c5470d2839bfa8a22f4","urls":["bzz-raw://30c476b4b2f405c1bb3f0bae15b006d129c80f1bfd9d0f2038160a3bb9745009","dweb:/ipfs/Qmb3VcuDufv6xbHeVgksC4tHpc5gKYVqBEwjEXW72XzSvN"],"license":"MIT"},"lib/openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol":{"keccak256":"0xde02e3a80c5c3b3a2187fbfbdfc7ed7c8c0d5b2e4a0ff5671611674b6c96bd91","urls":["bzz-raw://44b4a6161c6b718c37229643c8e6881b82b14dbcf7ea1b0b081fbc7b810e3488","dweb:/ipfs/QmUAxfrzeBusBHRkCfgzvD8axBKvdmtWz9rb52rYBH5K1w"],"license":"MIT"},"lib/openzeppelin-contracts/contracts/utils/Address.sol":{"keccak256":"0x80b4189de089dc632b752b365a16c5063b58cc24da0dd38b82f2c25f56d25c84","urls":["bzz-raw://81e2717e78844156a86733f1cada84dba906ffe03e4957de12ca219c65e9191b","dweb:/ipfs/QmW8vg3AafPJRo7EC75RQJTtjiaYmfPa4U4sqmEuBXXzaP"],"license":"MIT"},"lib/openzeppelin-contracts/contracts/utils/Context.sol":{"keccak256":"0x493033a8d1b176a037b2cc6a04dad01a5c157722049bbecf632ca876224dd4b2","urls":["bzz-raw://6a708e8a5bdb1011c2c381c9a5cfd8a9a956d7d0a9dc1bd8bcdaf52f76ef2f12","dweb:/ipfs/Qmax9WHBnVsZP46ZxEMNRQpLQnrdE4dK8LehML1Py8FowF"],"license":"MIT"},"lib/openzeppelin-contracts/contracts/utils/Errors.sol":{"keccak256":"0x1b0625096e82d06abdcf1844172ef78ef54a5e878761f4d905fda07eaf098424","urls":["bzz-raw://5cd99f1a4836c07461cb3ea023ae2f6d1d01e80694b764a87623aa7252754756","dweb:/ipfs/QmNPNDuiNU6TJatZcdBcrwixBoo5MSXNDq4kaXhpJLWGpB"],"license":"MIT"},"lib/openzeppelin-contracts/contracts/utils/introspection/IERC165.sol":{"keccak256":"0xc859863e3bda7ec3cddf6dafe2ffe91bcbe648d1395b856b839c32ee9617c44c","urls":["bzz-raw://a9d5417888b873cf2225ed5d50b2a67be97c1504134a2a580512168d587ad82e","dweb:/ipfs/QmNr5fTb2heFW658NZn7dDnofZgFvQTnNxKRJ3wdnR1skX"],"license":"MIT"},"src/ERC20Treasury.sol":{"keccak256":"0xfa4a5e76407fa2f4d9921785266662de2ec236646f2b533ddee58952b6ba687c","urls":["bzz-raw://c587c88d11b40107eb839f0c6a58e6bb4d3eba1b322f74ba6fdf338c478e8a5f","dweb:/ipfs/QmbkDx7LsDMoUkoGvXf9LwXFjrEC1QmrFNwdpWQJiNRULa"],"license":null},"src/interfaces/IERC20Treasury.sol":{"keccak256":"0x7b5b14f4beba9333003f19664606640ee4c1608fd451da3d2890456768328aff","urls":["bzz-raw://d7d5270761601fbd8255de0f343adb4beb790f2e41321855a881066650b7a934","dweb:/ipfs/QmNhx5kEocYXKBcX4CvFt4RDKmThYr4TmHxdEAseEputaQ"],"license":null},"src/interfaces/IMessageQueue.sol":{"keccak256":"0x25d2dd1c7616e63052f4bc5742d7c1d416f21a4dd73ce7f809b92a8e3fe34a92","urls":["bzz-raw://e5b2a1095a57498017d8ab6c016c1eb4b8757d06d1718e179a9fa23069f47c3b","dweb:/ipfs/QmezCNrpEyz6R9UUH4gxHH3CUe8Y33HFBTuhyUEGZ6MsK4"],"license":null},"src/libraries/Environment.sol":{"keccak256":"0xe1f5143e4182aab3d28da7162ca987d8fd3f8bc3bf3ca8314aff4d562207e455","urls":["bzz-raw://ecb44da89f92e895ac277810b58aa8e8ee91140c53e297c78862e2aa3e405c64","dweb:/ipfs/QmaHz4Ejyap7zuWZ5HVxv22Q4FAY6fjoM3PQWhK8S5jm6y"],"license":null}},"version":1},"id":47} \ No newline at end of file diff --git a/gear-programs/erc20-relay/app/src/abi.rs b/gear-programs/erc20-relay/app/src/abi.rs new file mode 100644 index 00000000..2620aaa5 --- /dev/null +++ b/gear-programs/erc20-relay/app/src/abi.rs @@ -0,0 +1,5 @@ +alloy_sol_types::sol!( + #[allow(missing_docs)] + ERC20_TREASURY, + "./src/ERC20Treasury.json" +); diff --git a/gear-programs/erc20-relay/app/src/error.rs b/gear-programs/erc20-relay/app/src/error.rs new file mode 100644 index 00000000..3645783a --- /dev/null +++ b/gear-programs/erc20-relay/app/src/error.rs @@ -0,0 +1,21 @@ +use super::{Decode, Encode, TypeInfo}; + +#[derive(Clone, Debug, Encode, Decode, TypeInfo)] +#[codec(crate = sails_rs::scale_codec)] +#[scale_info(crate = sails_rs::scale_info)] +pub enum Error { + DecodeReceiptEnvelopeFailure, + FailedEthTransaction, + NotSupportedEvent, + AlreadyProcessed, + TooOldTransaction, + SendFailure, + ReplyFailure, + HandleResultDecodeFailure, + MissingCheckpoint, + InvalidBlockProof, + TrieDbFailure, + InvalidReceiptProof, + ReplyTimeout, + ReplyHook, +} diff --git a/gear-programs/erc20-relay/app/src/lib.rs b/gear-programs/erc20-relay/app/src/lib.rs new file mode 100644 index 00000000..3834e042 --- /dev/null +++ b/gear-programs/erc20-relay/app/src/lib.rs @@ -0,0 +1,78 @@ +#![no_std] + +pub mod abi; +pub mod error; +pub mod service; + +use cell::RefCell; +use collections::BTreeSet; +use sails_rs::{ + gstd::{ExecContext, GStdExecContext}, + prelude::*, +}; +use service::Erc20Relay as Erc20RelayService; + +pub struct State { + admin: ActorId, + checkpoints: ActorId, + address: H160, + token: H160, + vft_gateway: ActorId, + reply_timeout: u32, + reply_deposit: u64, +} + +pub struct Erc20RelayProgram(RefCell); + +#[sails_rs::program] +impl Erc20RelayProgram { + pub fn new( + checkpoints: ActorId, + address: H160, + token: H160, + reply_timeout: u32, + reply_deposit: u64, + ) -> Self { + unsafe { + service::TRANSACTIONS = Some(BTreeSet::new()); + } + + let exec_context = GStdExecContext::new(); + Self(RefCell::new(State { + admin: exec_context.actor_id(), + checkpoints, + address, + token, + vft_gateway: Default::default(), + reply_timeout, + reply_deposit, + })) + } + + pub fn gas_calculation(_reply_timeout: u32, _reply_deposit: u64) -> Self { + #[cfg(feature = "gas_calculation")] + { + let self_ = Self::new( + Default::default(), + Default::default(), + Default::default(), + _reply_timeout, + _reply_deposit, + ); + + let transactions = service::transactions_mut(); + for i in 0..service::CAPACITY_STEP_SIZE { + transactions.insert((0, i as u64)); + } + + self_ + } + + #[cfg(not(feature = "gas_calculation"))] + panic!("Please rebuild with enabled `gas_calculation` feature") + } + + pub fn erc20_relay(&self) -> Erc20RelayService { + Erc20RelayService::new(&self.0, GStdExecContext::new()) + } +} diff --git a/gear-programs/erc20-relay/app/src/service.rs b/gear-programs/erc20-relay/app/src/service.rs new file mode 100644 index 00000000..d369fe4e --- /dev/null +++ b/gear-programs/erc20-relay/app/src/service.rs @@ -0,0 +1,326 @@ +// Incorporate code generated based on the IDL file +#[allow(dead_code)] +mod vft { + include!(concat!(env!("OUT_DIR"), "/vft-gateway.rs")); +} + +use super::{abi::ERC20_TREASURY, error::Error, BTreeSet, ExecContext, RefCell, State}; +use alloy_sol_types::SolEvent; +use checkpoint_light_client_io::{Handle, HandleResult}; +use ethereum_common::{ + beacon::{light::Block as LightBeaconBlock, BlockHeader as BeaconBlockHeader}, + hash_db, memory_db, + patricia_trie::TrieDB, + tree_hash::TreeHash, + trie_db::{HashDB, Trie}, + utils as eth_utils, + utils::ReceiptEnvelope, + H160, H256, U256, +}; +use ops::ControlFlow::*; +use sails_rs::{ + calls::ActionIo, + gstd::{self, msg}, + prelude::*, +}; +use vft::vft_gateway; + +pub(crate) const CAPACITY: usize = 500_000; + +#[cfg(feature = "gas_calculation")] +pub(crate) const CAPACITY_STEP_SIZE: usize = 50_000; + +pub(crate) static mut TRANSACTIONS: Option> = None; + +pub(crate) fn transactions_mut() -> &'static mut BTreeSet<(u64, u64)> { + unsafe { + TRANSACTIONS + .as_mut() + .expect("Program should be constructed") + } +} + +#[derive(Clone, Debug, Decode, TypeInfo)] +#[codec(crate = sails_rs::scale_codec)] +#[scale_info(crate = sails_rs::scale_info)] +pub struct BlockInclusionProof { + pub block: LightBeaconBlock, + pub headers: Vec, +} + +#[derive(Clone, Debug, Decode, TypeInfo)] +#[codec(crate = sails_rs::scale_codec)] +#[scale_info(crate = sails_rs::scale_info)] +pub struct EthToVaraEvent { + pub proof_block: BlockInclusionProof, + pub proof: Vec>, + pub transaction_index: u64, + pub receipt_rlp: Vec, +} + +#[derive(Encode, TypeInfo)] +#[codec(crate = sails_rs::scale_codec)] +#[scale_info(crate = sails_rs::scale_info)] +enum Event { + Relayed { + fungible_token: H160, + to: ActorId, + amount: U256, + }, +} + +struct CheckedReceipt { + envelope: ReceiptEnvelope, + event: ERC20_TREASURY::Deposit, +} + +pub struct Erc20Relay<'a, ExecContext> { + state: &'a RefCell, + exec_context: ExecContext, +} + +#[sails_rs::service(events = Event)] +impl<'a, T> Erc20Relay<'a, T> +where + T: ExecContext, +{ + pub fn new(state: &'a RefCell, exec_context: T) -> Self { + Self { + state, + exec_context, + } + } + + pub fn vft_gateway(&self) -> ActorId { + self.state.borrow().vft_gateway + } + + pub fn set_vft_gateway(&mut self, vft_gateway: ActorId) { + let source = self.exec_context.actor_id(); + let mut state = self.state.borrow_mut(); + if source != state.admin { + panic!("Not admin"); + } + + state.vft_gateway = vft_gateway; + } + + pub async fn relay(&mut self, message: EthToVaraEvent) -> Result<(), Error> { + let CheckedReceipt { + envelope: receipt, + event, + } = self.decode_and_check_receipt(&message)?; + + let EthToVaraEvent { + proof_block: BlockInclusionProof { block, mut headers }, + proof, + transaction_index, + .. + } = message; + + // verify the proof of block inclusion + let checkpoints = self.state.borrow().checkpoints; + let slot = block.slot; + let checkpoint = Self::request_checkpoint(checkpoints, slot).await?; + + headers.sort_unstable_by(|a, b| a.slot.cmp(&b.slot)); + let Continue(block_root_parent) = + headers + .iter() + .rev() + .try_fold(checkpoint, |block_root_parent, header| { + let block_root = header.tree_hash_root(); + match block_root == block_root_parent { + true => Continue(header.parent_root), + false => Break(()), + } + }) + else { + return Err(Error::InvalidBlockProof); + }; + + let block_root = block.tree_hash_root(); + if block_root != block_root_parent { + return Err(Error::InvalidBlockProof); + } + + // verify Merkle-PATRICIA proof + let receipts_root = H256::from(block.body.execution_payload.receipts_root.0 .0); + let mut memory_db = memory_db::new(); + for proof_node in &proof { + memory_db.insert(hash_db::EMPTY_PREFIX, proof_node); + } + + let trie = TrieDB::new(&memory_db, &receipts_root).map_err(|_| Error::TrieDbFailure)?; + + let (key_db, value_db) = + eth_utils::rlp_encode_index_and_receipt(&transaction_index, &receipt); + match trie.get(&key_db) { + Ok(Some(found_value)) if found_value == value_db => (), + _ => return Err(Error::InvalidReceiptProof), + } + + let amount = U256::from_little_endian(event.amount.as_le_slice()); + let receiver = ActorId::from(event.to.0); + let fungible_token = H160::from(event.token.0 .0); + let call_payload = + vft_gateway::io::MintTokens::encode_call(fungible_token, receiver, amount); + let (vft_gateway_id, reply_timeout, reply_deposit) = { + let state = self.state.borrow(); + + (state.vft_gateway, state.reply_timeout, state.reply_deposit) + }; + gstd::msg::send_bytes_for_reply(vft_gateway_id, call_payload, 0, reply_deposit) + .map_err(|_| Error::SendFailure)? + .up_to(Some(reply_timeout)) + .map_err(|_| Error::ReplyTimeout)? + .handle_reply(move || handle_reply(slot, transaction_index)) + .map_err(|_| Error::ReplyHook)? + .await + .map_err(|_| Error::ReplyFailure)?; + + let _ = self.notify_on(Event::Relayed { + fungible_token, + to: ActorId::from(event.to.0), + amount, + }); + + Ok(()) + } + + fn decode_and_check_receipt(&self, message: &EthToVaraEvent) -> Result { + use alloy_rlp::Decodable; + + let receipt = ReceiptEnvelope::decode(&mut &message.receipt_rlp[..]) + .map_err(|_| Error::DecodeReceiptEnvelopeFailure)?; + + if !receipt.is_success() { + return Err(Error::FailedEthTransaction); + } + + let slot = message.proof_block.block.slot; + let state = self.state.borrow_mut(); + // decode log and check that it is from an allowed address + let event = receipt + .logs() + .iter() + .find_map(|log| { + let eth_address = H160::from(log.address.0 .0); + let Ok(event) = ERC20_TREASURY::Deposit::decode_log_data(log, true) else { + return None; + }; + + (eth_address == state.address && H160::from(event.token.0 .0) == state.token) + .then_some(event) + }) + .ok_or(Error::NotSupportedEvent)?; + + // check for double spending + let transactions = transactions_mut(); + let key = (slot, message.transaction_index); + if transactions.contains(&key) { + return Err(Error::AlreadyProcessed); + } + + if CAPACITY <= transactions.len() + && transactions + .first() + .map(|first| &key < first) + .unwrap_or(false) + { + return Err(Error::TooOldTransaction); + } + + Ok(CheckedReceipt { + envelope: receipt, + event, + }) + } + + async fn request_checkpoint(checkpoints: ActorId, slot: u64) -> Result { + let request = Handle::GetCheckpointFor { slot }.encode(); + let reply = msg::send_bytes_for_reply(checkpoints, &request, 0, 0) + .map_err(|_| Error::SendFailure)? + .await + .map_err(|_| Error::ReplyFailure)?; + + match HandleResult::decode(&mut reply.as_slice()) + .map_err(|_| Error::HandleResultDecodeFailure)? + { + HandleResult::Checkpoint(Ok((_slot, hash))) => Ok(hash), + HandleResult::Checkpoint(Err(_)) => Err(Error::MissingCheckpoint), + _ => panic!("Unexpected result to `GetCheckpointFor` request"), + } + } + + pub fn fill_transactions(&mut self) -> bool { + #[cfg(feature = "gas_calculation")] + { + let transactions = transactions_mut(); + if CAPACITY == transactions.len() { + return false; + } + + let count = cmp::min(CAPACITY - transactions.len(), CAPACITY_STEP_SIZE); + let (last, _) = transactions.last().copied().unwrap(); + for i in 0..count { + transactions.insert((last + 1, i as u64)); + } + + true + } + + #[cfg(not(feature = "gas_calculation"))] + panic!("Please rebuild with enabled `gas_calculation` feature") + } + + pub async fn calculate_gas_for_reply( + &mut self, + _slot: u64, + _transaction_index: u64, + ) -> Result<(), Error> { + #[cfg(feature = "gas_calculation")] + { + let call_payload = vft_gateway::io::MintTokens::encode_call( + Default::default(), + Default::default(), + Default::default(), + ); + let (reply_timeout, reply_deposit) = { + let state = self.state.borrow(); + + (state.reply_timeout, state.reply_deposit) + }; + let source = self.exec_context.actor_id(); + gstd::msg::send_bytes_for_reply(source, call_payload, 0, reply_deposit) + .map_err(|_| Error::SendFailure)? + .up_to(Some(reply_timeout)) + .map_err(|_| Error::ReplyTimeout)? + .handle_reply(move || handle_reply(_slot, _transaction_index)) + .map_err(|_| Error::ReplyHook)? + .await + .map_err(|_| Error::ReplyFailure)?; + + Ok(()) + } + + #[cfg(not(feature = "gas_calculation"))] + panic!("Please rebuild with enabled `gas_calculation` feature") + } +} + +fn handle_reply(slot: u64, transaction_index: u64) { + let reply_bytes = msg::load_bytes().expect("Unable to load bytes"); + let reply = vft_gateway::io::MintTokens::decode_reply(&reply_bytes) + .expect("Unable to decode MintTokens reply"); + if let Err(e) = reply { + panic!("Request to mint tokens failed: {e:?}"); + } + + let transactions = transactions_mut(); + if CAPACITY <= transactions.len() { + transactions.pop_first(); + } + + transactions.insert((slot, transaction_index)); +} diff --git a/gear-programs/erc20-relay/app/tests/gclient.rs b/gear-programs/erc20-relay/app/tests/gclient.rs new file mode 100644 index 00000000..d698443e --- /dev/null +++ b/gear-programs/erc20-relay/app/tests/gclient.rs @@ -0,0 +1,164 @@ +// Incorporate code generated based on the IDL file +#[allow(dead_code)] +mod vft { + include!(concat!(env!("OUT_DIR"), "/vft-gateway.rs")); +} + +use erc20_relay_client::traits::{Erc20Relay, Erc20RelayFactory}; +use gclient::{Event, EventProcessor, GearApi, GearEvent}; +use sails_rs::{calls::*, gclient::calls::*, prelude::*}; +use vft::vft_gateway; + +async fn spin_up_node() -> (GClientRemoting, CodeId, GasUnit) { + let api = GearApi::dev().await.unwrap(); + let gas_limit = api.block_gas_limit().unwrap(); + let remoting = GClientRemoting::new(api); + let (code_id, _) = remoting + .api() + .upload_code(erc20_relay::WASM_BINARY) + .await + .unwrap(); + + (remoting, code_id, gas_limit) +} + +#[tokio::test] +#[ignore = "Requires running node"] +async fn gas_for_reply() { + use erc20_relay_client::{traits::Erc20Relay as _, Erc20Relay, Erc20RelayFactory}; + + let route = ::ROUTE; + + let (remoting, code_id, gas_limit) = spin_up_node().await; + let account_id: ActorId = <[u8; 32]>::from(remoting.api().account_id().clone()).into(); + + let factory = Erc20RelayFactory::new(remoting.clone()); + + let program_id = factory + .gas_calculation(1_000, 5_500_000_000) + .with_gas_limit(gas_limit) + .send_recv(code_id, []) + .await + .unwrap(); + + let mut client = Erc20Relay::new(remoting.clone()); + while client + .fill_transactions() + .send_recv(program_id) + .await + .unwrap() + {} + + println!("prepared"); + + for i in 5..10 { + let mut listener = remoting.api().subscribe().await.unwrap(); + + client + .calculate_gas_for_reply(i, i) + .with_gas_limit(10_000_000_000) + .send(program_id) + .await + .unwrap(); + + let message_id = listener + .proc(|e| match e { + Event::Gear(GearEvent::UserMessageSent { message, .. }) + if message.destination == account_id.into() && message.details.is_none() => + { + message.payload.0.starts_with(route).then_some(message.id) + } + _ => None, + }) + .await + .unwrap(); + + println!("message_id = {}", hex::encode(message_id.0.as_ref())); + + let reply: ::Reply = Ok(()); + let payload = { + let mut result = Vec::with_capacity(route.len() + reply.encoded_size()); + result.extend_from_slice(route); + reply.encode_to(&mut result); + + result + }; + let gas_info = remoting + .api() + .calculate_reply_gas(None, message_id.into(), payload, 0, true) + .await + .unwrap(); + + println!("gas_info = {gas_info:?}"); + } +} + +#[tokio::test] +#[ignore = "Requires running node"] +async fn set_vft_gateway() { + let (remoting, code_id, gas_limit) = spin_up_node().await; + + let factory = erc20_relay_client::Erc20RelayFactory::new(remoting.clone()); + + let program_id = factory + .new( + Default::default(), + Default::default(), + Default::default(), + 10_000, + 1_000_000_000, + ) + .with_gas_limit(gas_limit) + .send_recv(code_id, []) + .await + .unwrap(); + + let mut client = erc20_relay_client::Erc20Relay::new(remoting.clone()); + + // by default address of the VFT gateway is not set + let vft_gateway = client.vft_gateway().recv(program_id).await.unwrap(); + assert_eq!(vft_gateway, Default::default()); + + let vft_gateway_new = ActorId::from([1u8; 32]); + + // admin should be able to set the VFT gateway address + client + .set_vft_gateway(vft_gateway_new) + .send_recv(program_id) + .await + .unwrap(); + + let vft_gateway = client.vft_gateway().recv(program_id).await.unwrap(); + assert_eq!(vft_gateway, vft_gateway_new); + + // and reset it + client + .set_vft_gateway(Default::default()) + .send_recv(program_id) + .await + .unwrap(); + + let vft_gateway = client.vft_gateway().recv(program_id).await.unwrap(); + assert_eq!(vft_gateway, Default::default()); + + // another account isn't permitted to change the VFT gateway address + let api = GearApi::dev().await.unwrap().with("//Bob").unwrap(); + let remoting = GClientRemoting::new(api); + + let mut client = erc20_relay_client::Erc20Relay::new(remoting.clone()); + let result = client + .set_vft_gateway(Default::default()) + .send_recv(program_id) + .await; + assert!(result.is_err()); + + let result = client + .set_vft_gateway(vft_gateway_new) + .send_recv(program_id) + .await; + assert!(result.is_err()); + + // anyone should be able to read the address + let vft_gateway = client.vft_gateway().recv(program_id).await.unwrap(); + assert_eq!(vft_gateway, Default::default()); +} diff --git a/gear-programs/erc20-relay/build.rs b/gear-programs/erc20-relay/build.rs new file mode 100644 index 00000000..79bbe9bf --- /dev/null +++ b/gear-programs/erc20-relay/build.rs @@ -0,0 +1,23 @@ +use std::{ + env, + fs::File, + io::{BufRead, BufReader}, + path::PathBuf, +}; + +fn main() { + sails_rs::build_wasm(); + + if env::var("__GEAR_WASM_BUILDER_NO_BUILD").is_ok() { + return; + } + + let bin_path_file = File::open(".binpath").unwrap(); + let mut bin_path_reader = BufReader::new(bin_path_file); + let mut bin_path = String::new(); + bin_path_reader.read_line(&mut bin_path).unwrap(); + + let mut idl_path = PathBuf::from(bin_path); + idl_path.set_extension("idl"); + sails_idl_gen::generate_idl_to_file::(idl_path).unwrap(); +} diff --git a/gear-programs/erc20-relay/client/Cargo.toml b/gear-programs/erc20-relay/client/Cargo.toml new file mode 100644 index 00000000..f3e4a679 --- /dev/null +++ b/gear-programs/erc20-relay/client/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "erc20-relay-client" +version.workspace = true +edition.workspace = true + +[dependencies] +mockall = { workspace = true, optional = true } +sails-rs.workspace = true + +[build-dependencies] +erc20-relay-app.workspace = true +sails-client-gen.workspace = true +sails-idl-gen.workspace = true + +[features] +mocks = ["sails-rs/mockall", "dep:mockall"] diff --git a/gear-programs/erc20-relay/client/build.rs b/gear-programs/erc20-relay/client/build.rs new file mode 100644 index 00000000..bff1eaef --- /dev/null +++ b/gear-programs/erc20-relay/client/build.rs @@ -0,0 +1,17 @@ +use sails_client_gen::ClientGenerator; +use std::{env, path::PathBuf}; + +fn main() { + let out_dir_path = PathBuf::from(env::var("OUT_DIR").unwrap()); + let idl_file_path = out_dir_path.join("erc20_relay.idl"); + + // Generate IDL file for the program + sails_idl_gen::generate_idl_to_file::(&idl_file_path) + .unwrap(); + + // Generate client code from IDL file + ClientGenerator::from_idl_path(&idl_file_path) + .with_mocks("mocks") + .generate_to(PathBuf::from(env::var("OUT_DIR").unwrap()).join("erc20_relay_client.rs")) + .unwrap(); +} diff --git a/gear-programs/erc20-relay/client/src/lib.rs b/gear-programs/erc20-relay/client/src/lib.rs new file mode 100644 index 00000000..18eb001a --- /dev/null +++ b/gear-programs/erc20-relay/client/src/lib.rs @@ -0,0 +1,4 @@ +#![no_std] + +// Incorporate code generated based on the IDL file +include!(concat!(env!("OUT_DIR"), "/erc20_relay_client.rs")); diff --git a/gear-programs/erc20-relay/src/lib.rs b/gear-programs/erc20-relay/src/lib.rs new file mode 100644 index 00000000..f0ca302b --- /dev/null +++ b/gear-programs/erc20-relay/src/lib.rs @@ -0,0 +1,15 @@ +#![no_std] +#![allow(unused_imports)] + +#[cfg(target_arch = "wasm32")] +pub use erc20_relay_app::wasm::*; + +#[cfg(feature = "wasm-binary")] +#[cfg(not(target_arch = "wasm32"))] +pub use code::WASM_BINARY_OPT as WASM_BINARY; + +#[cfg(feature = "wasm-binary")] +#[cfg(not(target_arch = "wasm32"))] +mod code { + include!(concat!(env!("OUT_DIR"), "/wasm_binary.rs")); +}