diff --git a/Cargo.lock b/Cargo.lock index 92ef55b986..a0a46b7a60 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -212,9 +212,9 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.7" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "038dfcf04a5feb68e9c60b21c9625a54c2c0616e79b72b0fd87075a056ae1d1b" +checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" [[package]] name = "anstyle-parse" @@ -1810,9 +1810,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.13" +version = "4.5.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fbb260a053428790f3de475e304ff84cdbc4face759ea7a3e64c1edd938a7fc" +checksum = "3e5a21b8495e732f1b3c364c9949b201ca7bae518c502c80256c96ad79eaf6ac" dependencies = [ "clap_builder", "clap_derive 4.5.13", @@ -1820,9 +1820,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.13" +version = "4.5.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64b17d7ea74e9f833c7dbf2cbe4fb12ff26783eda4782a8975b72f895c9b4d99" +checksum = "8cf2dd12af7a047ad9d6da2b6b249759a22a7abc0f474c1dae1777afa4b21a73" dependencies = [ "anstream", "anstyle", @@ -2241,7 +2241,7 @@ dependencies = [ "anes", "cast", "ciborium", - "clap 4.5.13", + "clap 4.5.17", "criterion-plot", "futures", "is-terminal", @@ -2432,7 +2432,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6cd12917efc3a8b069a4975ef3cb2f2d835d42d04b3814d90838488f9dd9bf69" dependencies = [ "anyhow", - "clap 4.5.13", + "clap 4.5.17", "console", "cucumber-codegen", "cucumber-expressions", @@ -2671,7 +2671,7 @@ name = "datatest-stable" version = "0.1.1" source = "git+https://github.com/rooch-network/diem-devtools?branch=feature/pub-test-opts#c65933ee5aa5721ca0e6602995f1464ce0a93a53" dependencies = [ - "clap 4.5.13", + "clap 4.5.17", "regex", "termcolor", "walkdir", @@ -4037,7 +4037,7 @@ version = "0.7.1" dependencies = [ "anyhow", "bcs", - "clap 4.5.13", + "clap 4.5.17", "framework-builder", "framework-types", "include_dir", @@ -4781,23 +4781,6 @@ dependencies = [ "tokio-rustls 0.24.1", ] -[[package]] -name = "hyper-rustls" -version = "0.26.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0bea761b46ae2b24eb4aef630d8d1c398157b6fc29e6350ecf090a0b70c952c" -dependencies = [ - "futures-util", - "http 1.1.0", - "hyper 1.3.1", - "hyper-util", - "rustls 0.22.4", - "rustls-pki-types", - "tokio", - "tokio-rustls 0.25.0", - "tower-service", -] - [[package]] name = "hyper-rustls" version = "0.27.2" @@ -4814,6 +4797,7 @@ dependencies = [ "tokio", "tokio-rustls 0.26.0", "tower-service", + "webpki-roots 0.26.1", ] [[package]] @@ -6185,7 +6169,7 @@ version = "0.1.0" source = "git+https://github.com/rooch-network/move?rev=c05b5f71a24beb498eb743d73be4f69d8d325e62#c05b5f71a24beb498eb743d73be4f69d8d325e62" dependencies = [ "anyhow", - "clap 4.5.13", + "clap 4.5.17", "crossterm 0.26.1", "move-binary-format", "move-bytecode-source-map", @@ -6203,7 +6187,7 @@ source = "git+https://github.com/rooch-network/move?rev=c05b5f71a24beb498eb743d7 dependencies = [ "anyhow", "bcs", - "clap 4.5.13", + "clap 4.5.17", "codespan-reporting", "colored", "difference", @@ -6264,7 +6248,7 @@ source = "git+https://github.com/rooch-network/move?rev=c05b5f71a24beb498eb743d7 dependencies = [ "anyhow", "bcs", - "clap 4.5.13", + "clap 4.5.17", "codespan-reporting", "difference", "hex", @@ -6315,7 +6299,7 @@ source = "git+https://github.com/rooch-network/move?rev=c05b5f71a24beb498eb743d7 dependencies = [ "anyhow", "bcs", - "clap 4.5.13", + "clap 4.5.17", "codespan", "colored", "move-binary-format", @@ -6334,7 +6318,7 @@ version = "0.1.0" source = "git+https://github.com/rooch-network/move?rev=c05b5f71a24beb498eb743d73be4f69d8d325e62#c05b5f71a24beb498eb743d73be4f69d8d325e62" dependencies = [ "anyhow", - "clap 4.5.13", + "clap 4.5.17", "colored", "move-binary-format", "move-bytecode-source-map", @@ -6386,7 +6370,7 @@ source = "git+https://github.com/rooch-network/move?rev=c05b5f71a24beb498eb743d7 dependencies = [ "anyhow", "bcs", - "clap 4.5.13", + "clap 4.5.17", "move-binary-format", "move-bytecode-source-map", "move-bytecode-verifier", @@ -6478,7 +6462,7 @@ source = "git+https://github.com/rooch-network/move?rev=c05b5f71a24beb498eb743d7 dependencies = [ "anyhow", "bcs", - "clap 4.5.13", + "clap 4.5.17", "colored", "dirs-next", "itertools 0.10.5", @@ -6515,7 +6499,7 @@ dependencies = [ "anyhow", "async-trait", "atty", - "clap 4.5.13", + "clap 4.5.17", "codespan", "codespan-reporting", "futures", @@ -6669,7 +6653,7 @@ version = "0.1.0" source = "git+https://github.com/rooch-network/move?rev=c05b5f71a24beb498eb743d73be4f69d8d325e62#c05b5f71a24beb498eb743d73be4f69d8d325e62" dependencies = [ "anyhow", - "clap 4.5.13", + "clap 4.5.17", "colored", "move-binary-format", "move-bytecode-source-map", @@ -6701,7 +6685,7 @@ source = "git+https://github.com/rooch-network/move?rev=c05b5f71a24beb498eb743d7 dependencies = [ "anyhow", "better_any", - "clap 4.5.13", + "clap 4.5.17", "codespan-reporting", "colored", "itertools 0.10.5", @@ -6774,7 +6758,7 @@ version = "0.7.1" dependencies = [ "anyhow", "backtrace", - "clap 4.5.13", + "clap 4.5.17", "codespan", "codespan-reporting", "itertools 0.13.0", @@ -6833,7 +6817,7 @@ dependencies = [ name = "moveos-config" version = "0.7.1" dependencies = [ - "clap 4.5.13", + "clap 4.5.17", "serde 1.0.210", "tempfile", ] @@ -7427,7 +7411,7 @@ dependencies = [ "percent-encoding", "quick-xml 0.31.0", "reqsign", - "reqwest 0.12.4", + "reqwest 0.12.7", "serde 1.0.210", "serde_json", "tokio", @@ -8530,6 +8514,54 @@ dependencies = [ "serde 1.0.210", ] +[[package]] +name = "quinn" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c7c5fdde3cdae7203427dc4f0a68fe0ed09833edc525a03456b153b79828684" +dependencies = [ + "bytes", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash 2.0.0", + "rustls 0.23.10", + "socket2", + "thiserror", + "tokio", + "tracing", +] + +[[package]] +name = "quinn-proto" +version = "0.11.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fadfaed2cd7f389d0161bb73eeb07b7b78f8691047a6f3e73caaeae55310a4a6" +dependencies = [ + "bytes", + "rand 0.8.5", + "ring 0.17.8", + "rustc-hash 2.0.0", + "rustls 0.23.10", + "slab", + "thiserror", + "tinyvec", + "tracing", +] + +[[package]] +name = "quinn-udp" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bffec3605b73c6f1754535084a85229fa8a30f86014e6c81aeec4abb68b0285" +dependencies = [ + "libc", + "once_cell", + "socket2", + "tracing", + "windows-sys 0.52.0", +] + [[package]] name = "quote" version = "0.6.13" @@ -8865,7 +8897,7 @@ dependencies = [ "log", "percent-encoding", "rand 0.8.5", - "reqwest 0.12.4", + "reqwest 0.12.7", "rsa 0.9.6", "serde 1.0.210", "serde_json", @@ -8905,7 +8937,7 @@ dependencies = [ "serde_json", "serde_urlencoded", "sync_wrapper 0.1.2", - "system-configuration", + "system-configuration 0.5.1", "tokio", "tokio-native-tls", "tokio-rustls 0.24.1", @@ -8917,14 +8949,14 @@ dependencies = [ "wasm-streams", "web-sys", "webpki-roots 0.25.4", - "winreg 0.50.0", + "winreg", ] [[package]] name = "reqwest" -version = "0.12.4" +version = "0.12.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "566cafdd92868e0939d3fb961bd0dc25fcfaaed179291093b3d43e6b3150ea10" +checksum = "f8f4955649ef5c38cc7f9e8aa41761d48fb9677197daea9984dc54f56aad5e63" dependencies = [ "base64 0.22.1", "bytes", @@ -8936,7 +8968,7 @@ dependencies = [ "http-body 1.0.0", "http-body-util", "hyper 1.3.1", - "hyper-rustls 0.26.0", + "hyper-rustls 0.27.2", "hyper-tls 0.6.0", "hyper-util", "ipnet", @@ -8947,17 +8979,18 @@ dependencies = [ "once_cell", "percent-encoding", "pin-project-lite", - "rustls 0.22.4", + "quinn", + "rustls 0.23.10", "rustls-pemfile 2.1.2", "rustls-pki-types", "serde 1.0.210", "serde_json", "serde_urlencoded", - "sync_wrapper 0.1.2", - "system-configuration", + "sync_wrapper 1.0.1", + "system-configuration 0.6.1", "tokio", "tokio-native-tls", - "tokio-rustls 0.25.0", + "tokio-rustls 0.26.0", "tokio-util", "tower-service", "url", @@ -8966,7 +8999,7 @@ dependencies = [ "wasm-streams", "web-sys", "webpki-roots 0.26.1", - "winreg 0.52.0", + "windows-registry", ] [[package]] @@ -9149,7 +9182,7 @@ dependencies = [ "bitcoin 0.32.2", "bitcoin-move", "chrono", - "clap 4.5.13", + "clap 4.5.17", "codespan-reporting", "csv", "datatest-stable 0.1.1", @@ -9238,7 +9271,7 @@ dependencies = [ "bitcoin 0.32.2", "bitcoincore-rpc", "bitcoincore-rpc-json", - "clap 4.5.13", + "clap 4.5.17", "criterion", "ethers", "hex", @@ -9296,7 +9329,7 @@ name = "rooch-config" version = "0.7.1" dependencies = [ "anyhow", - "clap 4.5.13", + "clap 4.5.17", "dirs", "dirs-next", "moveos-config", @@ -9387,7 +9420,7 @@ dependencies = [ "anyhow", "axum 0.7.5", "axum-server", - "clap 4.5.13", + "clap 4.5.17", "futures", "move-core-types", "moveos-types", @@ -9435,7 +9468,7 @@ dependencies = [ "bcs", "bitcoin 0.32.2", "bitcoin-move", - "clap 4.5.13", + "clap 4.5.17", "coerce", "datatest-stable 0.1.3", "ethers", @@ -9475,7 +9508,7 @@ dependencies = [ "anyhow", "bcs", "bitcoin-move", - "clap 4.5.13", + "clap 4.5.17", "framework-builder", "framework-release", "include_dir", @@ -9534,7 +9567,7 @@ version = "0.7.1" dependencies = [ "anyhow", "bcs", - "clap 4.5.13", + "clap 4.5.17", "codespan-reporting", "datatest-stable 0.1.3", "move-binary-format", @@ -9613,7 +9646,7 @@ name = "rooch-open-rpc" version = "0.7.1" dependencies = [ "anyhow", - "clap 4.5.13", + "clap 4.5.17", "fastcrypto 0.1.8 (git+https://github.com/MystenLabs/fastcrypto?rev=56f6223b84ada922b6cb2c672c69db2ea3dc6a13)", "rand 0.8.5", "schemars", @@ -9639,7 +9672,7 @@ dependencies = [ name = "rooch-open-rpc-spec" version = "0.7.1" dependencies = [ - "clap 4.5.13", + "clap 4.5.17", "pretty_assertions", "rooch-open-rpc-spec-builder", "rooch-rpc-api", @@ -9652,7 +9685,7 @@ name = "rooch-open-rpc-spec-builder" version = "0.7.1" dependencies = [ "anyhow", - "clap 4.5.13", + "clap 4.5.17", "rand 0.8.5", "rooch-open-rpc", "rooch-rpc-api", @@ -9660,6 +9693,28 @@ dependencies = [ "serde_json", ] +[[package]] +name = "rooch-oracle" +version = "0.1.0" +dependencies = [ + "anyhow", + "bcs", + "clap 4.5.17", + "futures-util", + "log", + "move-core-types", + "moveos-types", + "reqwest 0.12.7", + "rooch-rpc-api", + "rooch-rpc-client", + "rooch-types", + "serde_json", + "tokio", + "tokio-tungstenite 0.23.1", + "tracing-subscriber", + "tungstenite 0.24.0", +] + [[package]] name = "rooch-ord" version = "0.7.1" @@ -9677,7 +9732,7 @@ dependencies = [ "moveos-types", "ordinals", "prometheus", - "reqwest 0.12.4", + "reqwest 0.12.7", "rooch-types", "serde 1.0.210", "serde_json", @@ -9923,7 +9978,7 @@ dependencies = [ "bitcoincore-rpc", "bs58 0.5.1", "chacha20poly1305", - "clap 4.5.13", + "clap 4.5.17", "derive_more", "enum_dispatch", "ethers", @@ -11426,6 +11481,9 @@ name = "sync_wrapper" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394" +dependencies = [ + "futures-core", +] [[package]] name = "synthez" @@ -11468,7 +11526,18 @@ checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" dependencies = [ "bitflags 1.3.2", "core-foundation", - "system-configuration-sys", + "system-configuration-sys 0.5.0", +] + +[[package]] +name = "system-configuration" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" +dependencies = [ + "bitflags 2.5.0", + "core-foundation", + "system-configuration-sys 0.6.0", ] [[package]] @@ -11481,6 +11550,16 @@ dependencies = [ "libc", ] +[[package]] +name = "system-configuration-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "tap" version = "1.0.1" @@ -11643,7 +11722,7 @@ dependencies = [ "anyhow", "assert_cmd", "backtrace", - "clap 4.5.13", + "clap 4.5.17", "cucumber", "env_logger", "futures", @@ -11941,6 +12020,20 @@ dependencies = [ "webpki-roots 0.26.1", ] +[[package]] +name = "tokio-tungstenite" +version = "0.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6989540ced10490aaf14e6bad2e3d33728a2813310a0c71d1574304c49631cd" +dependencies = [ + "futures-util", + "log", + "native-tls", + "tokio", + "tokio-native-tls", + "tungstenite 0.23.0", +] + [[package]] name = "tokio-util" version = "0.7.12" @@ -12312,6 +12405,43 @@ dependencies = [ "utf-8", ] +[[package]] +name = "tungstenite" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e2e2ce1e47ed2994fd43b04c8f618008d4cabdd5ee34027cf14f9d918edd9c8" +dependencies = [ + "byteorder", + "bytes", + "data-encoding", + "http 1.1.0", + "httparse", + "log", + "native-tls", + "rand 0.8.5", + "sha1", + "thiserror", + "utf-8", +] + +[[package]] +name = "tungstenite" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18e5b8366ee7a95b16d32197d0b2604b43a0be89dc5fac9f8e96ccafbaedda8a" +dependencies = [ + "byteorder", + "bytes", + "data-encoding", + "http 1.1.0", + "httparse", + "log", + "rand 0.8.5", + "sha1", + "thiserror", + "utf-8", +] + [[package]] name = "typed-arena" version = "2.0.2" @@ -13152,6 +13282,36 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-registry" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e400001bb720a623c1c69032f8e3e4cf09984deec740f007dd2b03ec864804b0" +dependencies = [ + "windows-result", + "windows-strings", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-result" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-strings" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" +dependencies = [ + "windows-result", + "windows-targets 0.52.6", +] + [[package]] name = "windows-sys" version = "0.33.0" @@ -13371,16 +13531,6 @@ dependencies = [ "windows-sys 0.48.0", ] -[[package]] -name = "winreg" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a277a57398d4bfa075df44f501a17cfdf8542d224f0d36095a2adc7aee4ef0a5" -dependencies = [ - "cfg-if", - "windows-sys 0.48.0", -] - [[package]] name = "ws_stream_wasm" version = "0.7.4" diff --git a/Cargo.toml b/Cargo.toml index 42d62b49be..c63276eace 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,6 +36,7 @@ members = [ "crates/rooch-open-rpc-macros", "crates/rooch-open-rpc-spec", "crates/rooch-open-rpc-spec-builder", + "crates/rooch-oracle", "crates/rooch-pipeline-processor", "crates/rooch-proposer", "crates/rooch-relayer", @@ -62,7 +63,8 @@ default-members = [ "moveos/moveos", "frameworks/framework-release", "crates/rooch", - "crates/rooch-faucet" + "crates/rooch-faucet", + "crates/rooch-oracle" ] # All workspace members should inherit these keys @@ -169,6 +171,7 @@ ethers = { version = "2.0.7", features = ["legacy"] } eyre = "0.6.8" fastcrypto = { git = "https://github.com/MystenLabs/fastcrypto", rev = "56f6223b84ada922b6cb2c672c69db2ea3dc6a13" } futures = "0.3.28" +futures-util = "0.3.30" hex = "0.4.3" rustc-hex = "2.1" itertools = "0.13.0" @@ -215,10 +218,12 @@ tiny-keccak = { version = "2", features = ["keccak", "sha3"] } tiny-bip39 = "1.0.0" tokio = { version = "1.40.0", features = ["full"] } tokio-util = "0.7.12" +tokio-tungstenite = { version = "0.23.1", features = ["native-tls"] } tonic = { version = "0.8", features = ["gzip"] } tracing = "0.1.37" tracing-appender = "0.2.2" tracing-subscriber = { version = "0.3.15" } +tungstenite = "0.24.0" codespan-reporting = "0.11.1" codespan = "0.11.1" diff --git a/crates/rooch-oracle/Cargo.toml b/crates/rooch-oracle/Cargo.toml new file mode 100644 index 0000000000..72a19b0a49 --- /dev/null +++ b/crates/rooch-oracle/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "rooch-oracle" +version = "0.1.0" +edition = "2021" + +[dependencies] +tokio = { workspace = true } +tokio-tungstenite = { workspace = true } +tungstenite = { workspace = true } +serde_json = { workspace = true } +futures-util = { workspace = true } +tracing-subscriber = { workspace = true } +log = { workspace = true } +clap = { workspace = true } +reqwest = { workspace = true } +anyhow = { workspace = true } +bcs = { workspace = true } + +rooch-rpc-api = { workspace = true } +move-core-types = { workspace = true } +moveos-types = { workspace = true } +rooch-types = { workspace = true } +rooch-rpc-client = { workspace = true } diff --git a/crates/rooch-oracle/src/binance.rs b/crates/rooch-oracle/src/binance.rs new file mode 100644 index 0000000000..787f00696a --- /dev/null +++ b/crates/rooch-oracle/src/binance.rs @@ -0,0 +1,127 @@ +// Copyright (c) RoochNetwork +// SPDX-License-Identifier: Apache-2.0 + +use crate::data_process::{execute_transaction, parse_and_convert, subscribe_websocket, State}; +use clap::Parser; +use log::info; +use move_core_types::account_address::AccountAddress; +use move_core_types::identifier::Identifier; +use move_core_types::language_storage::ModuleId; +use moveos_types::move_types::FunctionId; +use moveos_types::transaction::MoveAction; +use rooch_rpc_client::wallet_context::WalletContext; +use serde_json::Value; +use std::path::PathBuf; +use std::sync::Arc; +use std::time::Duration; +use tokio::sync::{mpsc, RwLock}; +use tokio::time::Instant; + +#[derive(Parser, Debug, Clone)] +pub struct BinanceConfig { + #[arg( + long, + default_value = "wss://stream.binance.com:9443/ws/btcusdt@ticker" + )] + pub binance_url: String, + + #[arg( + long, + env = "ROOCH_BINANCE_WALLET_DIR", + default_value = "~/.rooch/rooch_config" + )] + pub binance_wallet_dir: Option, + + #[arg(long, env = "ROOCH_BINANCE_WALLET_PWD")] + pub binance_wallet_pwd: Option, + + #[arg(long, default_value = "10")] + pub binance_submit_interval: u64, + + #[arg(long, env = "ROOCH_BINANCE_ORACLE_ID")] + pub binance_oracle_id: String, + + #[arg(long, env = "ROOCH_BINANCE_ADMIN_ID")] + pub binance_admin_id: String, +} + +pub struct Binance { + pub wallet_state: Arc>, + binance_config: BinanceConfig, +} + +impl Binance { + pub async fn new(config: BinanceConfig) -> Self { + let wallet = WalletContext::new(config.binance_wallet_dir.clone()).unwrap(); + let wallet_pwd = config.binance_wallet_pwd.clone(); + Self { + wallet_state: Arc::new(RwLock::new(State { + wallet_pwd, + context: wallet, + })), + binance_config: config, + } + } + + pub async fn subscribe(&self, package_id: &str) { + let (tx, mut rx) = mpsc::channel(1); + let url = self.binance_config.binance_url.clone(); + let handle = tokio::spawn(async move { + subscribe_websocket(url, tx, None).await; + }); + let function_id = FunctionId::new( + ModuleId::new( + AccountAddress::from_hex_literal(package_id).unwrap(), + Identifier::new("trusted_oracle").unwrap(), + ), + Identifier::new("submit_data").unwrap(), + ); + let address_mapping = self + .wallet_state + .read() + .await + .context + .address_mapping + .clone(); + let oracle_obj = parse_and_convert( + format!("object_id:{}", self.binance_config.binance_oracle_id).as_str(), + &address_mapping, + ); + let ticker = parse_and_convert("string:BTCUSD", &address_mapping); + let identifier = parse_and_convert("string:Binance", &address_mapping); + let admin_obj = parse_and_convert( + format!("object_id:{}", self.binance_config.binance_admin_id).as_str(), + &address_mapping, + ); + let mut last_execution = Instant::now() - Duration::from_secs(10); // 初始化为10秒前 + while let Some(msg) = rx.recv().await { + let wallet_state = self.wallet_state.write().await; + + let msg_value = serde_json::from_str::(&msg).unwrap(); + if msg_value["c"].as_str().is_none() + || Instant::now().duration_since(last_execution) + < Duration::from_secs(self.binance_config.binance_submit_interval) + { + continue; + } + last_execution = Instant::now(); + let price = format!( + "u256:{}", + msg_value["c"].as_str().unwrap().parse::().unwrap() * 10f64.powi(8) + ); + let decimal = "8u8".to_string(); + let args = vec![ + oracle_obj.clone(), + ticker.clone(), + parse_and_convert(price.as_str(), &address_mapping), + parse_and_convert(decimal.as_str(), &address_mapping), + identifier.clone(), + admin_obj.clone(), + ]; + let move_action = MoveAction::new_function_call(function_id.clone(), vec![], args); + let _ = execute_transaction(move_action, wallet_state).await; + info!("Received Binance price: {}", msg_value["c"]); + } + handle.await.expect("The task failed"); + } +} diff --git a/crates/rooch-oracle/src/data_process.rs b/crates/rooch-oracle/src/data_process.rs new file mode 100644 index 0000000000..630ee4f115 --- /dev/null +++ b/crates/rooch-oracle/src/data_process.rs @@ -0,0 +1,129 @@ +// Copyright (c) RoochNetwork +// SPDX-License-Identifier: Apache-2.0 + +use anyhow::Result; +use futures_util::{SinkExt, StreamExt}; +use log::{error, info, warn}; +use move_core_types::account_address::AccountAddress; +use moveos_types::transaction::MoveAction; +use rooch_rpc_api::jsonrpc_types::KeptVMStatusView; +use rooch_rpc_client::wallet_context::WalletContext; +use rooch_types::address::RoochAddress; +use rooch_types::function_arg::FunctionArg; +use serde_json::Value; +use std::collections::BTreeMap; +use std::str::FromStr; +use std::time::Duration; +use tokio::sync::{mpsc, RwLockWriteGuard}; +use tokio_tungstenite::connect_async; +use tokio_tungstenite::tungstenite::Message; + +pub async fn subscribe_websocket( + url: String, + tx: mpsc::Sender, + subscribe_msg: Option, +) { + loop { + let (ws_stream, _) = match connect_async(&url).await { + Ok(stream) => stream, + Err(e) => { + warn!("Failed to connect: {} error:{}", url, e); + tokio::time::sleep(Duration::from_secs(5)).await; + continue; + } + }; + + info!("Connected to {}", url); + + let (mut write, mut read) = ws_stream.split(); + + if subscribe_msg.is_some() { + if let Err(e) = write + .send(Message::Text(subscribe_msg.clone().unwrap().to_string())) + .await + { + warn!("Failed to send message: {}", e); + continue; + } + } + + while let Some(message) = read.next().await { + match message { + Ok(msg) => { + if let Message::Text(text) = msg { + if let Err(e) = tx.send(text).await { + warn!("Failed to send message through channel: {}", e); + break; + } + } + } + Err(e) => { + warn!("Error: {}", e); + break; + } + } + } + + warn!("Connection lost or error occurred, restarting..."); + tokio::time::sleep(Duration::from_secs(5)).await; + } +} + +pub async fn subscribe_http(url: String, tx: mpsc::Sender, interval: u64) { + loop { + match reqwest::get(&url).await { + Ok(response) => { + if let Ok(value) = response.json::().await { + if let Err(e) = tx.send(value).await { + warn!("Failed to send message through channel: {}", e); + } + } + } + Err(e) => { + warn!("Failed to fetch price: {}", e); + } + }; + + tokio::time::sleep(Duration::from_secs(interval)).await; + } +} + +pub struct State { + pub(crate) wallet_pwd: Option, + pub context: WalletContext, +} + +#[allow(clippy::needless_lifetimes)] +pub async fn execute_transaction<'a>( + action: MoveAction, + state: RwLockWriteGuard<'a, State>, +) -> Result<()> { + let sender: RoochAddress = state.context.client_config.active_address.unwrap(); + let pwd = state.wallet_pwd.clone(); + let result = state + .context + .sign_and_execute(sender, action, pwd, None) + .await; + match result { + Ok(tx) => match tx.execution_info.status { + KeptVMStatusView::Executed => { + info!("Executed success tx_has: {}", tx.execution_info.tx_hash); + } + _ => { + error!("Transfer gases failed {:?}", tx.execution_info.status); + } + }, + Err(e) => { + error!("Transfer gases failed {}", e); + } + }; + Ok(()) +} + +pub fn parse_and_convert(arg: &str, address_mapping: &BTreeMap) -> Vec { + let mapping = |input: &str| -> Option { address_mapping.get(input).cloned() }; + FunctionArg::from_str(arg) + .unwrap() + .into_bytes(&mapping) + .unwrap() +} diff --git a/crates/rooch-oracle/src/lib.rs b/crates/rooch-oracle/src/lib.rs new file mode 100644 index 0000000000..7f0d5271fd --- /dev/null +++ b/crates/rooch-oracle/src/lib.rs @@ -0,0 +1,7 @@ +// Copyright (c) RoochNetwork +// SPDX-License-Identifier: Apache-2.0 + +pub mod binance; +pub mod data_process; +pub mod okx; +pub mod pyth; diff --git a/crates/rooch-oracle/src/main.rs b/crates/rooch-oracle/src/main.rs new file mode 100644 index 0000000000..4950949b71 --- /dev/null +++ b/crates/rooch-oracle/src/main.rs @@ -0,0 +1,62 @@ +// Copyright (c) RoochNetwork +// SPDX-License-Identifier: Apache-2.0 + +use clap::Parser; +use rooch_oracle::binance::{Binance, BinanceConfig}; +use rooch_oracle::okx::{Okx, OkxConfig}; +use rooch_oracle::pyth::{Pyth, PythConfig}; + +#[derive(Parser, Clone)] +#[clap( + name = "Rooch Oracle", + about = "Oracle backend for BTC tokens price on Rooch", + rename_all = "kebab-case" +)] +pub struct Config { + #[clap(flatten)] + pub okx_config: OkxConfig, + #[clap(flatten)] + pub binance_config: BinanceConfig, + #[clap(flatten)] + pub pyth_config: PythConfig, + #[clap(short, long, env = "ROOCH_ORACLE_PACKAGE")] + pub package_id: String, +} + +#[tokio::main] +async fn main() { + let _ = tracing_subscriber::fmt::try_init(); + + let config = Config::parse(); + let Config { + okx_config, + binance_config, + pyth_config, + package_id, + } = config; + let okx_handle = tokio::spawn({ + let package_id = package_id.clone(); + async move { + let okx = Okx::new(okx_config).await; + okx.subscribe(package_id.as_str()).await; + } + }); + let binance_handle = tokio::spawn({ + let package_id = package_id.clone(); + async move { + let binance = Binance::new(binance_config).await; + binance.subscribe(package_id.as_str()).await; + } + }); + let pyth_handle = tokio::spawn({ + let package_id = package_id.clone(); + async move { + let pyth = Pyth::new(pyth_config).await; + pyth.subscribe(package_id.as_str()).await; + } + }); + + okx_handle.await.expect("okx error"); + binance_handle.await.expect("binance error"); + pyth_handle.await.expect("binance error") +} diff --git a/crates/rooch-oracle/src/okx.rs b/crates/rooch-oracle/src/okx.rs new file mode 100644 index 0000000000..5d13628c40 --- /dev/null +++ b/crates/rooch-oracle/src/okx.rs @@ -0,0 +1,138 @@ +// Copyright (c) RoochNetwork +// SPDX-License-Identifier: Apache-2.0 + +use crate::data_process::{execute_transaction, parse_and_convert, subscribe_websocket, State}; +use clap::Parser; +use log::info; +use move_core_types::account_address::AccountAddress; +use move_core_types::identifier::Identifier; +use move_core_types::language_storage::ModuleId; +use moveos_types::move_types::FunctionId; +use moveos_types::transaction::MoveAction; +use rooch_rpc_client::wallet_context::WalletContext; +use serde_json::{json, Value}; +use std::path::PathBuf; +use std::sync::Arc; +use std::time::Duration; +use tokio::sync::{mpsc, RwLock}; +use tokio::time::Instant; + +#[derive(Parser, Debug, Clone)] +pub struct OkxConfig { + #[arg(long, default_value = "wss://ws.okx.com:8443/ws/v5/public")] + pub okx_url: String, + + #[arg( + long, + env = "ROOCH_OKX_WALLET_DIR", + default_value = "~/.rooch/rooch_config" + )] + pub okx_wallet_dir: Option, + + #[arg(long, env = "ROOCH_OKX_WALLET_PWD")] + pub okx_wallet_pwd: Option, + + #[arg(long, default_value = "10")] + pub okx_submit_interval: u64, + + #[arg(long, env = "ROOCH_OKX_ORACLE_ID")] + pub okx_oracle_id: String, + + #[arg(long, env = "ROOCH_OKX_ADMIN_ID")] + pub okx_admin_id: String, +} + +pub struct Okx { + pub wallet_state: Arc>, + okx_config: OkxConfig, +} + +impl Okx { + pub async fn new(config: OkxConfig) -> Self { + let wallet = WalletContext::new(config.okx_wallet_dir.clone()).unwrap(); + let wallet_pwd = config.okx_wallet_pwd.clone(); + Self { + wallet_state: Arc::new(RwLock::new(State { + wallet_pwd, + context: wallet, + })), + okx_config: config, + } + } + + pub async fn subscribe(&self, package_id: &str) { + let subscribe_msg = json!({ + "op": "subscribe", + "args": [{ + "channel": "tickers", + "instId": "BTC-USDT" + }] + }); + let (tx, mut rx) = mpsc::channel(1); + let url = self.okx_config.okx_url.clone(); + let handle = tokio::spawn(async move { + subscribe_websocket(url, tx, Some(subscribe_msg)).await; + }); + let function_id = FunctionId::new( + ModuleId::new( + AccountAddress::from_hex_literal(package_id).unwrap(), + Identifier::new("trusted_oracle").unwrap(), + ), + Identifier::new("submit_data").unwrap(), + ); + let address_mapping = self + .wallet_state + .read() + .await + .context + .address_mapping + .clone(); + let oracle_obj = parse_and_convert( + format!("object_id:{}", self.okx_config.okx_oracle_id).as_str(), + &address_mapping, + ); + let ticker = parse_and_convert("string:BTCUSD", &address_mapping); + let identifier = parse_and_convert("string:OKX", &address_mapping); + let admin_obj = parse_and_convert( + format!("object_id:{}", self.okx_config.okx_admin_id).as_str(), + &address_mapping, + ); + let mut last_execution = Instant::now() - Duration::from_secs(10); + while let Some(msg) = rx.recv().await { + let wallet_state = self.wallet_state.write().await; + let msg_value = serde_json::from_str::(&msg).unwrap(); + if msg_value["data"][0]["last"].as_str().is_none() + || Instant::now().duration_since(last_execution) + < Duration::from_secs(self.okx_config.okx_submit_interval) + { + continue; + } + last_execution = Instant::now(); + let price = format!( + "u256:{}", + msg_value["data"][0]["last"] + .as_str() + .unwrap() + .parse::() + .unwrap() + * 10f64.powi(8) + ); + let decimal = "8u8".to_string(); + let args = vec![ + oracle_obj.clone(), + ticker.clone(), + parse_and_convert(price.as_str(), &address_mapping), + parse_and_convert(decimal.as_str(), &address_mapping), + identifier.clone(), + admin_obj.clone(), + ]; + let move_action = MoveAction::new_function_call(function_id.clone(), vec![], args); + let _ = execute_transaction(move_action, wallet_state).await; + info!( + "Received Okex price: {:?}", + msg_value["data"][0]["last"].as_str().unwrap() + ); + } + handle.await.expect("The task failed"); + } +} diff --git a/crates/rooch-oracle/src/pyth.rs b/crates/rooch-oracle/src/pyth.rs new file mode 100644 index 0000000000..4e8e6bf932 --- /dev/null +++ b/crates/rooch-oracle/src/pyth.rs @@ -0,0 +1,114 @@ +// Copyright (c) RoochNetwork +// SPDX-License-Identifier: Apache-2.0 + +use crate::data_process::{execute_transaction, parse_and_convert, subscribe_http, State}; +use clap::Parser; +use log::info; +use move_core_types::account_address::AccountAddress; +use move_core_types::identifier::Identifier; +use move_core_types::language_storage::ModuleId; +use moveos_types::move_types::FunctionId; +use moveos_types::transaction::MoveAction; +use rooch_rpc_client::wallet_context::WalletContext; +use std::path::PathBuf; +use std::sync::Arc; +use tokio::sync::{mpsc, RwLock}; + +#[derive(Parser, Debug, Clone)] +pub struct PythConfig { + #[arg( + long, + default_value = "https://hermes.pyth.network/v2/updates/price/latest?ids%5B%5D=0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43&ids%5B%5D=0xc96458d393fe9deb7a7d63a0ac41e2898a67a7750dbd166673279e06c868df0a" + )] + pub pyth_url: String, + + #[arg( + long, + env = "ROOCH_PYTH_WALLET_DIR", + default_value = "~/.rooch/rooch_config" + )] + pub pyth_wallet_dir: Option, + + #[arg(long, env = "ROOCH_PYTH_WALLET_PWD")] + pub pyth_wallet_pwd: Option, + + #[arg(long, default_value = "10")] + pub pyth_submit_interval: u64, + + #[arg(long, env = "ROOCH_PYTH_ORACLE_ID")] + pub pyth_oracle_id: String, + + #[arg(long, env = "ROOCH_PYTH_ADMIN_ID")] + pub pyth_admin_id: String, +} + +pub struct Pyth { + pub wallet_state: Arc>, + pyth_config: PythConfig, +} + +impl Pyth { + pub async fn new(config: PythConfig) -> Self { + let wallet = WalletContext::new(config.pyth_wallet_dir.clone()).unwrap(); + let wallet_pwd = config.pyth_wallet_pwd.clone(); + Self { + wallet_state: Arc::new(RwLock::new(State { + wallet_pwd, + context: wallet, + })), + pyth_config: config, + } + } + pub async fn subscribe(&self, package_id: &str) { + let (tx, mut rx) = mpsc::channel(1); + let url = self.pyth_config.pyth_url.clone(); + let interval = self.pyth_config.pyth_submit_interval; + let handle = tokio::spawn(async move { + subscribe_http(url, tx, interval).await; + }); + let function_id = FunctionId::new( + ModuleId::new( + AccountAddress::from_hex_literal(package_id).unwrap(), + Identifier::new("trusted_oracle").unwrap(), + ), + Identifier::new("submit_data").unwrap(), + ); + let address_mapping = self + .wallet_state + .read() + .await + .context + .address_mapping + .clone(); + let oracle_obj = parse_and_convert( + format!("object_id:{}", self.pyth_config.pyth_oracle_id).as_str(), + &address_mapping, + ); + let ticker = parse_and_convert("string:BTCUSD", &address_mapping); + let identifier = parse_and_convert("string:PYTH", &address_mapping); + let admin_obj = parse_and_convert( + format!("object_id:{}", self.pyth_config.pyth_admin_id).as_str(), + &address_mapping, + ); + while let Some(msg) = rx.recv().await { + let wallet_state = self.wallet_state.write().await; + let price = format!( + "u256:{}", + msg["parsed"][0]["ema_price"]["price"].as_str().unwrap() + ); + let decimal = "8u8".to_string(); + let args = vec![ + oracle_obj.clone(), + ticker.clone(), + parse_and_convert(price.as_str(), &address_mapping), + parse_and_convert(decimal.as_str(), &address_mapping), + identifier.clone(), + admin_obj.clone(), + ]; + let move_action = MoveAction::new_function_call(function_id.clone(), vec![], args); + let _ = execute_transaction(move_action, wallet_state).await; + info!("Received Pyth price: {}", price); + } + handle.await.expect("The task failed"); + } +} diff --git a/infra/rooch-portal-v2/contract/gas_market/Move.toml b/infra/rooch-portal-v2/contract/gas_market/Move.toml new file mode 100644 index 0000000000..6ddd4e9759 --- /dev/null +++ b/infra/rooch-portal-v2/contract/gas_market/Move.toml @@ -0,0 +1,17 @@ +[package] +name = "gas_market" +version = "0.0.1" + +[dependencies] +MoveStdlib = { local = "../../../../frameworks/move-stdlib" } +MoveosStdlib = { local = "../../../../frameworks/moveos-stdlib" } +RoochFramework = { local = "../../../../frameworks/rooch-framework" } +BitcoinMove = { local = "../../../../frameworks/bitcoin-move" } + + +[addresses] +gas_market = "_" +std = "0x1" +moveos_std = "0x2" +rooch_framework = "0x3" +bitcoin_move = "0x4" diff --git a/infra/rooch-portal-v2/contract/gas_market/sources/gas_market.move b/infra/rooch-portal-v2/contract/gas_market/sources/gas_market.move new file mode 100644 index 0000000000..9b6acd3e2b --- /dev/null +++ b/infra/rooch-portal-v2/contract/gas_market/sources/gas_market.move @@ -0,0 +1,198 @@ +module gas_market::gas_market { + use std::option; + use std::string::utf8; + use moveos_std::decimal_value::value; + use gas_market::trusted_oracle::trusted_price; + use moveos_std::address::to_string; + use bitcoin_move::utxo::{ReceiveUTXOEvent, unpack_receive_utxo_event}; + use moveos_std::event_queue::{Subscriber, consume}; + use moveos_std::event_queue; + use moveos_std::table; + use moveos_std::signer::{module_signer, address_of}; + use moveos_std::account::{move_resource_to, borrow_mut_resource}; + use moveos_std::table::Table; + use rooch_framework::account_coin_store; + use moveos_std::tx_context::sender; + use moveos_std::object::{Object, to_shared, transfer}; + use rooch_framework::coin_store::CoinStore; + use moveos_std::object; + use rooch_framework::gas_coin::RGas; + use rooch_framework::coin_store; + #[test_only] + use moveos_std::decimal_value; + + struct AdminCap has store, key {} + + struct RGasMarket has key, store{ + rgas_store: Object>, + unit_price: u256, + receive_btc_address: address, + is_open: bool + } + + struct MarketInfo has key, store{ + total_deposit: u256, + total_withdraw: u256, + buyer: Table, + uncheck_info: Table + } + + // 0.01 U = (BTC decimal * BTC Price decimal / RGas decimal) * 0.01 + // (10**8 * 10**8 / 10**8) * 0.01 + const DEFAULT_UNIT_PRICE: u256 = 1000000; + const BTC_USD: vector = b"BTCUSD"; + + const ErrorMarketNotOpen: u64 = 0; + const ErrorReceiverAddress: u64 = 1; + const ErrorTokenPrice: u64 = 2; + const ErrorNoUncheckTxid: u64 = 3; + + + fun init() { + let rgas_market_obj = object::new_named_object(RGasMarket{ + rgas_store: coin_store::create_coin_store(), + unit_price: DEFAULT_UNIT_PRICE, + receive_btc_address: sender(), + is_open: true + }); + let admin_cap = object::new_named_object(AdminCap{}); + + move_resource_to(&module_signer(), MarketInfo{ + total_deposit: 0, + total_withdraw: 0, + buyer: table::new(), + uncheck_info: table::new() + }); + + to_shared(event_queue::subscribe(to_string(&sender()))); + transfer(admin_cap, sender()); + to_shared(rgas_market_obj) + } + + public entry fun add_rgas_coin( + account: &signer, + rgas_market_obj: &mut Object, + amount: u256 + ){ + let rgas_market = object::borrow_mut(rgas_market_obj); + assert!(rgas_market.is_open, ErrorMarketNotOpen); + let rgas_coin = account_coin_store::withdraw(account, amount); + coin_store::deposit(&mut rgas_market.rgas_store, rgas_coin); + let market_info_mut = borrow_mut_resource(address_of(&module_signer())); + market_info_mut.total_deposit = market_info_mut.total_deposit + amount + } + + public entry fun withdraw_rgas_coin( + _admin: &mut Object, + rgas_market_obj: &mut Object, + amount: u256 + ){ + let rgas_market = object::borrow_mut(rgas_market_obj); + assert!(rgas_market.is_open, ErrorMarketNotOpen); + + let rgas_coin = coin_store::withdraw(&mut rgas_market.rgas_store, amount); + account_coin_store::deposit(sender(), rgas_coin); + let market_info_mut = borrow_mut_resource(address_of(&module_signer())); + market_info_mut.total_withdraw = market_info_mut.total_withdraw + amount + } + + public entry fun consume_event( + rgas_market_obj: &mut Object, + subscriber_obj: &mut Object> + ){ + let rgas_market = object::borrow_mut(rgas_market_obj); + assert!(rgas_market.is_open, ErrorMarketNotOpen); + let consume_event = option::extract(&mut consume(subscriber_obj)); + let (txid, sender, receiver, value) = unpack_receive_utxo_event(consume_event); + assert!(receiver == rgas_market.receive_btc_address, ErrorReceiverAddress); + let withdraw_amount = btc_to_rgas(value); + if (option::is_some(&sender)){ + let sender_addr = option::extract(&mut sender); + let rgas_coin = coin_store::withdraw(&mut rgas_market.rgas_store, withdraw_amount); + account_coin_store::deposit(sender_addr, rgas_coin); + let market_info_mut = borrow_mut_resource(address_of(&module_signer())); + if (!table::contains(&market_info_mut.buyer, sender_addr)){ + table::add(&mut market_info_mut.buyer, sender_addr, withdraw_amount) + }else { + let total_amount = *table::borrow(&market_info_mut.buyer, sender_addr) + withdraw_amount; + table::upsert(&mut market_info_mut.buyer, sender_addr, total_amount); + } + }else { + let market_info_mut = borrow_mut_resource(address_of(&module_signer())); + if (!table::contains(&market_info_mut.uncheck_info, txid)){ + table::add(&mut market_info_mut.uncheck_info, txid, withdraw_amount) + }else { + let total_amount = *table::borrow(&market_info_mut.uncheck_info, txid) + withdraw_amount; + table::upsert(&mut market_info_mut.uncheck_info, txid, total_amount); + } + } + } + + public entry fun consume_uncheck( + _admin: &mut Object, + rgas_market_obj: &mut Object, + txid: address, + sender_addr: address, + amount: u256 + ){ + let rgas_market = object::borrow_mut(rgas_market_obj); + assert!(rgas_market.is_open, ErrorMarketNotOpen); + let rgas_coin = coin_store::withdraw(&mut rgas_market.rgas_store, amount); + account_coin_store::deposit(sender_addr, rgas_coin); + let market_info_mut = borrow_mut_resource(address_of(&module_signer())); + assert!(table::contains(&market_info_mut.uncheck_info, txid), ErrorNoUncheckTxid); + let remaining_amount = *table::borrow(&market_info_mut.uncheck_info, txid) - amount; + if (remaining_amount > 0) { + table::upsert(&mut market_info_mut.uncheck_info, txid, remaining_amount); + }else { + let _ = table::remove(&mut market_info_mut.uncheck_info, txid); + }; + + if (!table::contains(&market_info_mut.buyer, sender_addr)){ + table::add(&mut market_info_mut.buyer, sender_addr, amount) + }else { + let total_amount = *table::borrow(&market_info_mut.buyer, sender_addr) + amount; + table::upsert(&mut market_info_mut.buyer, sender_addr, total_amount); + } + } + + public entry fun remove_uncheck( + _admin: &mut Object, + txid: address, + ){ + let market_info_mut = borrow_mut_resource(address_of(&module_signer())); + table::remove(&mut market_info_mut.uncheck_info, txid); + } + + public fun btc_to_rgas(sats_amount: u64): u256 { + let price_info = trusted_price(utf8(BTC_USD)); + let btc_price = value(&price_info); + (sats_amount as u256) * btc_price / DEFAULT_UNIT_PRICE + } + + public fun rgas_to_btc(rgas_amount: u256): u256 { + let price_info = trusted_price(utf8(BTC_USD)); + let token_price = value(&price_info); + // TODO If the input quantity of rgas is too small, the return value is 0, and should return u64? + rgas_amount * DEFAULT_UNIT_PRICE / token_price + } + + #[test] + fun test_btc_to_rgas(){ + let price_info = decimal_value::new(5005206000000, 8); + let token_price = value(&price_info); + let a = 100000000 * token_price / DEFAULT_UNIT_PRICE; + assert!(a == 500520600000000, 1); + } + + #[test] + fun test_rgas_to_btc() { + let rgas_amount = 500520600000000; + let price_info = decimal_value::new(5005206000000, 8); + let token_price = value(&price_info); + let b = rgas_amount * DEFAULT_UNIT_PRICE / token_price; + assert!(b == 100000000, 2); + } + + +} diff --git a/infra/rooch-portal-v2/contract/gas_market/sources/trusted_oracle.move b/infra/rooch-portal-v2/contract/gas_market/sources/trusted_oracle.move new file mode 100644 index 0000000000..f2ae270dca --- /dev/null +++ b/infra/rooch-portal-v2/contract/gas_market/sources/trusted_oracle.move @@ -0,0 +1,118 @@ +module gas_market::trusted_oracle { + use std::signer::address_of; + use std::string::{utf8, String}; + use std::vector; + use moveos_std::event::emit; + use rooch_framework::oracle::{SimpleOracle, OracleAdminCap}; + use moveos_std::decimal_value::{DecimalValue, new}; + use moveos_std::tx_context::sender; + use moveos_std::signer::module_signer; + use moveos_std::account::{move_resource_to, borrow_resource}; + use moveos_std::object::{to_shared, ObjectID, transfer, Object}; + use moveos_std::object; + use rooch_framework::oracle; + use rooch_framework::oracle_meta; + #[test_only] + use moveos_std::timestamp; + #[test_only] + use moveos_std::decimal_value; + #[test_only] + use rooch_framework::genesis; + + + struct Oracle has key{ + ids: vector + } + + struct NewOracleEvent has copy, drop { + name: String, + oracle_id: ObjectID, + admin_id: ObjectID + } + + fun init() { + let (oracle1, admin_cap1)= oracle::create(utf8(b"pyth"), utf8(b"https://hermes.pyth.network"), utf8(b"Price Data From Pyth")); + let (oracle2, admin_cap2)= oracle::create(utf8(b"binance"), utf8(b"https://api.binance.com/api/v3/ticker/price"), utf8(b"Price Data From Binance")); + let (oracle3, admin_cap3)= oracle::create(utf8(b"okex"), utf8(b"https://www.okx.com/api/v5/market/tickers?instType=SPOT"), utf8(b"Price Data From Okex")); + let signer = module_signer(); + move_resource_to(&signer, Oracle{ + ids: vector[object::id(&oracle1), object::id(&oracle2), object::id(&oracle3)] + }); + emit(NewOracleEvent{ + name: utf8(b"pyth"), + oracle_id: object::id(&oracle1), + admin_id: object::id(&admin_cap1) + }); + emit(NewOracleEvent{ + name: utf8(b"binance"), + oracle_id: object::id(&oracle2), + admin_id: object::id(&admin_cap2) + }); + emit(NewOracleEvent{ + name: utf8(b"okex"), + oracle_id: object::id(&oracle3), + admin_id: object::id(&admin_cap3) + }); + + to_shared(oracle1); + to_shared(oracle2); + to_shared(oracle3); + + transfer(admin_cap1, sender()); + transfer(admin_cap2, sender()); + transfer(admin_cap3, sender()); + } + + public fun trusted_price(ticker: String): DecimalValue { + let meta_oracle = oracle_meta::new(2, 60000, ticker); + let signer = module_signer(); + let oracle = borrow_resource(address_of(&signer)); + let i = 0; + while (i < vector::length(&oracle.ids)){ + let oracle_id = vector::borrow(&oracle.ids, i); + oracle_meta::add_simple_oracle(&mut meta_oracle, object::borrow_object(*oracle_id)); + i = i + 1; + }; + + let trusted_data = oracle_meta::median(meta_oracle); + *oracle_meta::value(&trusted_data) + } + + public entry fun submit_data( + oracle_obj: &mut Object, + ticker: String, + value: u256, + decimal: u8, + identifier: String, + admin_obj: &mut Object, + ){ + let decimal_value = new(value, decimal); + oracle::submit_data(oracle_obj, ticker, decimal_value, identifier, admin_obj) + } + + #[test] + fun test_btc_price() { + genesis::init_for_test(); + let (oracle1, admin_cap1)= oracle::create(utf8(b"pyth"), utf8(b"https://hermes.pyth.network"), utf8(b"Price Data From Pyth")); + let (oracle2, admin_cap2)= oracle::create(utf8(b"binance"), utf8(b"https://api.binance.com/api/v3/ticker/price"), utf8(b"Price Data From Binance")); + let (oracle3, admin_cap3)= oracle::create(utf8(b"okex"), utf8(b"https://www.okx.com/api/v5/market/tickers?instType=SPOT"), utf8(b"Price Data From Okex")); + let signer = module_signer(); + move_resource_to(&signer, Oracle{ + ids: vector[object::id(&oracle1), object::id(&oracle2), object::id(&oracle3)] + }); + timestamp::fast_forward_milliseconds_for_test(100000000); + submit_data(&mut oracle1, utf8(b"BTCUSD"), 5805106000000, 8, utf8(b"1"), &mut admin_cap1); + submit_data(&mut oracle2, utf8(b"BTCUSD"), 5805206000000, 8, utf8(b"2"), &mut admin_cap2); + submit_data(&mut oracle3, utf8(b"BTCUSD"), 5805306000000, 8, utf8(b"3"), &mut admin_cap3); + to_shared(oracle1); + to_shared(oracle2); + to_shared(oracle3); + + transfer(admin_cap1, sender()); + transfer(admin_cap2, sender()); + transfer(admin_cap3, sender()); + let price = trusted_price(utf8(b"BTCUSD")); + assert!(decimal_value::value(&price) == 5805206000000, 1); + assert!(decimal_value::decimal(&price) == 8, 1); + } +}