From e59ce54ffb5476c0a7763a27c0240b220c5a2dcc Mon Sep 17 00:00:00 2001 From: Supanat Potiwarakorn Date: Mon, 5 Aug 2024 16:30:28 +0700 Subject: [PATCH 01/14] poc --- .gitignore | 3 +++ Cargo.lock | 7 +++++++ Cargo.toml | 5 +++++ Makefile | 3 +++ rustlib/add.go | 11 +++++++++++ rustlib/link.go | 6 ++++++ rustlib/rustsrc/Cargo.toml | 10 ++++++++++ rustlib/rustsrc/src/lib.rs | 9 +++++++++ rustlib/sub.go | 11 +++++++++++ 9 files changed, 65 insertions(+) create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 rustlib/add.go create mode 100644 rustlib/link.go create mode 100644 rustlib/rustsrc/Cargo.toml create mode 100644 rustlib/rustsrc/src/lib.rs create mode 100644 rustlib/sub.go diff --git a/.gitignore b/.gitignore index caa271342..9e45553be 100644 --- a/.gitignore +++ b/.gitignore @@ -26,3 +26,6 @@ sqs.log # Vim swap files .*.swp .*.swo + +# rust +target/ \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 000000000..1af506839 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "rustlib" +version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 000000000..9de021380 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,5 @@ +[workspace] +members = [ + "rustlib/rustsrc", +] +resolver = "2" diff --git a/Makefile b/Makefile index 1c5f28064..4c4237a0d 100644 --- a/Makefile +++ b/Makefile @@ -67,6 +67,9 @@ build: -ldflags "-w -s -linkmode=external -extldflags '-Wl,-z,muldefs -static'" \ -v -o /osmosis/build/sqsd app/*.go +build-rust-lib: + cargo build --release -p rustlib + ############################################################################### ### Docker ### ############################################################################### diff --git a/rustlib/add.go b/rustlib/add.go new file mode 100644 index 000000000..5936ebe90 --- /dev/null +++ b/rustlib/add.go @@ -0,0 +1,11 @@ +package rustexports + +/* +#include "../target/release/librustlib.h" +*/ +import "C" + +func Add(a, b float64) float64 { + result := C.add(C.double(a), C.double(b)) + return float64(result) +} diff --git a/rustlib/link.go b/rustlib/link.go new file mode 100644 index 000000000..31d7e926e --- /dev/null +++ b/rustlib/link.go @@ -0,0 +1,6 @@ +package rustexports + +/* +#cgo LDFLAGS: target/release/librustlib.a -ldl +*/ +import "C" diff --git a/rustlib/rustsrc/Cargo.toml b/rustlib/rustsrc/Cargo.toml new file mode 100644 index 000000000..04fbb9bed --- /dev/null +++ b/rustlib/rustsrc/Cargo.toml @@ -0,0 +1,10 @@ +[package] +edition = "2021" +name = "rustlib" +version = "0.1.0" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[lib] +crate-type = ["staticlib"] + +[dependencies] diff --git a/rustlib/rustsrc/src/lib.rs b/rustlib/rustsrc/src/lib.rs new file mode 100644 index 000000000..857e6e3ab --- /dev/null +++ b/rustlib/rustsrc/src/lib.rs @@ -0,0 +1,9 @@ +#[no_mangle] +pub extern "C" fn add(left: f64, right: f64) -> f64 { + left + right +} + +#[no_mangle] +pub extern "C" fn sub(left: f64, right: f64) -> f64 { + left - right +} diff --git a/rustlib/sub.go b/rustlib/sub.go new file mode 100644 index 000000000..a3b289ee4 --- /dev/null +++ b/rustlib/sub.go @@ -0,0 +1,11 @@ +package rustexports + +/* +#include "../target/release/librustlib.h" +*/ +import "C" + +func Sub(a, b float64) float64 { + result := C.sub(C.double(a), C.double(b)) + return float64(result) +} From b08e5885bbf624d03a6f0c65897c1fb081c43334 Mon Sep 17 00:00:00 2001 From: Supanat Potiwarakorn Date: Mon, 5 Aug 2024 16:48:11 +0700 Subject: [PATCH 02/14] automate binding gen --- Cargo.lock | 423 +++++++++++++++++++++++++++++++++++++ rustlib/rustsrc/Cargo.toml | 3 + rustlib/rustsrc/build.rs | 23 ++ 3 files changed, 449 insertions(+) create mode 100644 rustlib/rustsrc/build.rs diff --git a/Cargo.lock b/Cargo.lock index 1af506839..159ec1a24 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,429 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi", +] + +[[package]] +name = "autocfg" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" + +[[package]] +name = "cbindgen" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da6bc11b07529f16944307272d5bd9b22530bc7d05751717c9d416586cedab49" +dependencies = [ + "clap", + "heck", + "indexmap", + "log", + "proc-macro2", + "quote", + "serde", + "serde_json", + "syn 1.0.109", + "tempfile", + "toml", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "clap" +version = "3.2.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ea181bf566f71cb9a5d17a59e1871af638180a18fb0035c92ae62b705207123" +dependencies = [ + "atty", + "bitflags 1.3.2", + "clap_lex", + "indexmap", + "strsim", + "termcolor", + "textwrap", +] + +[[package]] +name = "clap_lex" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5" +dependencies = [ + "os_str_bytes", +] + +[[package]] +name = "errno" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "fastrand" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown", +] + +[[package]] +name = "itoa" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" + +[[package]] +name = "libc" +version = "0.2.155" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" + +[[package]] +name = "linux-raw-sys" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" + +[[package]] +name = "log" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + +[[package]] +name = "os_str_bytes" +version = "6.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2355d85b9a3786f481747ced0e0ff2ba35213a1f9bd406ed906554d7af805a1" + +[[package]] +name = "proc-macro2" +version = "1.0.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rustix" +version = "0.38.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" +dependencies = [ + "bitflags 2.6.0", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.52.0", +] + [[package]] name = "rustlib" version = "0.1.0" +dependencies = [ + "cbindgen", +] + +[[package]] +name = "ryu" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" + +[[package]] +name = "serde" +version = "1.0.204" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc76f558e0cbb2a839d37354c575f1dc3fdc6546b5be373ba43d95f231bf7c12" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.204" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0cd7e117be63d3c3678776753929474f3b04a43a080c744d6b0ae2a8c28e222" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.72", +] + +[[package]] +name = "serde_json" +version = "1.0.122" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "784b6203951c57ff748476b126ccb5e8e2959a5c19e5c617ab1956be3dbc68da" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.72" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc4b9b9bf2add8093d3f2c0204471e951b2285580335de42f9d2534f3ae7a8af" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tempfile" +version = "3.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8fcd239983515c23a32fb82099f97d0b11b8c72f654ed659363a95c3dad7a53" +dependencies = [ + "cfg-if", + "fastrand", + "once_cell", + "rustix", + "windows-sys 0.52.0", +] + +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "textwrap" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23d434d3f8967a09480fb04132ebe0a3e088c173e6d0ee7897abbdf4eab0f8b9" + +[[package]] +name = "toml" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" +dependencies = [ + "serde", +] + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" diff --git a/rustlib/rustsrc/Cargo.toml b/rustlib/rustsrc/Cargo.toml index 04fbb9bed..38089601d 100644 --- a/rustlib/rustsrc/Cargo.toml +++ b/rustlib/rustsrc/Cargo.toml @@ -8,3 +8,6 @@ version = "0.1.0" crate-type = ["staticlib"] [dependencies] + +[build-dependencies] +cbindgen = "0.26.0" diff --git a/rustlib/rustsrc/build.rs b/rustlib/rustsrc/build.rs new file mode 100644 index 000000000..2b276e8ed --- /dev/null +++ b/rustlib/rustsrc/build.rs @@ -0,0 +1,23 @@ +use std::{env, path::PathBuf, str::FromStr}; + +fn main() { + let crate_dir = env::var("CARGO_MANIFEST_DIR").unwrap(); + let create_name = env::var("CARGO_PKG_NAME").unwrap(); + let write_dest = PathBuf::from_str(crate_dir.as_str()) + .unwrap() + .join("..") + .join("..") + .join("target") + .join("release") + .join(format!("lib{}.h", create_name)); + + let mut conf = cbindgen::Config::default(); + conf.no_includes = true; + + cbindgen::Builder::new() + .with_crate(crate_dir) + .with_config(conf) + .generate() + .expect("Unable to generate bindings") + .write_to_file(write_dest); +} From e5810a92c26e42ede74a532769e9cdc41ce6c27d Mon Sep 17 00:00:00 2001 From: Supanat Potiwarakorn Date: Wed, 7 Aug 2024 15:45:20 +0700 Subject: [PATCH 03/14] wrap and test ffi logic --- Cargo.lock | 583 +++++++++++++++++++++++- Cargo.toml | 2 +- Makefile | 2 +- rustffi/link.go | 6 + rustffi/numbers.go | 55 +++ rustffi/numbers_test.go | 131 ++++++ {rustlib => rustffi}/rustsrc/Cargo.toml | 4 +- {rustlib => rustffi}/rustsrc/build.rs | 2 +- rustffi/rustsrc/src/lib.rs | 12 + rustffi/rustsrc/src/numbers.rs | 26 ++ rustffi/rustsrc/src/result.rs | 26 ++ rustffi/rustsrc/src/transmuter.rs | 29 ++ rustffi/transmuter.go | 25 + rustlib/add.go | 11 - rustlib/link.go | 6 - rustlib/rustsrc/src/lib.rs | 9 - rustlib/sub.go | 11 - 17 files changed, 893 insertions(+), 47 deletions(-) create mode 100644 rustffi/link.go create mode 100644 rustffi/numbers.go create mode 100644 rustffi/numbers_test.go rename {rustlib => rustffi}/rustsrc/Cargo.toml (67%) rename {rustlib => rustffi}/rustsrc/build.rs (93%) create mode 100644 rustffi/rustsrc/src/lib.rs create mode 100644 rustffi/rustsrc/src/numbers.rs create mode 100644 rustffi/rustsrc/src/result.rs create mode 100644 rustffi/rustsrc/src/transmuter.rs create mode 100644 rustffi/transmuter.go delete mode 100644 rustlib/add.go delete mode 100644 rustlib/link.go delete mode 100644 rustlib/rustsrc/src/lib.rs delete mode 100644 rustlib/sub.go diff --git a/Cargo.lock b/Cargo.lock index 159ec1a24..4734ddef9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,17 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "ahash" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" +dependencies = [ + "getrandom", + "once_cell", + "version_check", +] + [[package]] name = "atty" version = "0.2.14" @@ -19,6 +30,30 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + +[[package]] +name = "base64ct" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" + +[[package]] +name = "bech32" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d86b93f97252c47b41663388e6d155714a9d0c398b99f1005cbc5f978b29f445" + [[package]] name = "bitflags" version = "1.3.2" @@ -31,6 +66,36 @@ version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" +[[package]] +name = "block-buffer" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" +dependencies = [ + "generic-array", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bnum" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56953345e39537a3e18bdaeba4cb0c58a78c1f61f361dc0fa7c5c7340ae87c5f" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + [[package]] name = "cbindgen" version = "0.26.0" @@ -80,6 +145,221 @@ dependencies = [ "os_str_bytes", ] +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + +[[package]] +name = "cosmwasm-crypto" +version = "1.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd50718a2b6830ce9eb5d465de5a018a12e71729d66b70807ce97e6dd14f931d" +dependencies = [ + "digest 0.10.7", + "ecdsa", + "ed25519-zebra", + "k256", + "rand_core 0.6.4", + "thiserror", +] + +[[package]] +name = "cosmwasm-derive" +version = "1.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "242e98e7a231c122e08f300d9db3262d1007b51758a8732cd6210b3e9faa4f3a" +dependencies = [ + "syn 1.0.109", +] + +[[package]] +name = "cosmwasm-schema" +version = "1.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7879036156092ad1c22fe0d7316efc5a5eceec2bc3906462a2560215f2a2f929" +dependencies = [ + "cosmwasm-schema-derive", + "schemars", + "serde", + "serde_json", + "thiserror", +] + +[[package]] +name = "cosmwasm-schema-derive" +version = "1.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bb57855fbfc83327f8445ae0d413b1a05ac0d68c396ab4d122b2abd7bb82cb6" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "cosmwasm-std" +version = "1.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c1556156fdf892a55cced6115968b961eaaadd6f724a2c2cb7d1e168e32dd3" +dependencies = [ + "base64", + "bech32", + "bnum", + "cosmwasm-crypto", + "cosmwasm-derive", + "derivative", + "forward_ref", + "hex", + "schemars", + "serde", + "serde-json-wasm", + "sha2 0.10.8", + "static_assertions", + "thiserror", +] + +[[package]] +name = "cpufeatures" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" +dependencies = [ + "libc", +] + +[[package]] +name = "crypto-bigint" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" +dependencies = [ + "generic-array", + "rand_core 0.6.4", + "subtle", + "zeroize", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "curve25519-dalek" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b9fdf9972b2bd6af2d913799d9ebc165ea4d2e65878e329d9c6b372c4491b61" +dependencies = [ + "byteorder", + "digest 0.9.0", + "rand_core 0.5.1", + "subtle", + "zeroize", +] + +[[package]] +name = "der" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0" +dependencies = [ + "const-oid", + "zeroize", +] + +[[package]] +name = "derivative" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "digest" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +dependencies = [ + "generic-array", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer 0.10.4", + "const-oid", + "crypto-common", + "subtle", +] + +[[package]] +name = "dyn-clone" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125" + +[[package]] +name = "ecdsa" +version = "0.16.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" +dependencies = [ + "der", + "digest 0.10.7", + "elliptic-curve", + "rfc6979", + "signature", + "spki", +] + +[[package]] +name = "ed25519-zebra" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c24f403d068ad0b359e577a77f92392118be3f3c927538f2bb544a5ecd828c6" +dependencies = [ + "curve25519-dalek", + "hashbrown", + "hex", + "rand_core 0.6.4", + "serde", + "sha2 0.9.9", + "zeroize", +] + +[[package]] +name = "elliptic-curve" +version = "0.13.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" +dependencies = [ + "base16ct", + "crypto-bigint", + "digest 0.10.7", + "ff", + "generic-array", + "group", + "pkcs8", + "rand_core 0.6.4", + "sec1", + "subtle", + "zeroize", +] + [[package]] name = "errno" version = "0.3.9" @@ -96,11 +376,63 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" +[[package]] +name = "ff" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" +dependencies = [ + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "forward_ref" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8cbd1169bd7b4a0a20d92b9af7a7e0422888bd38a6f5ec29c1fd8c1558a272e" + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", + "zeroize", +] + +[[package]] +name = "getrandom" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff", + "rand_core 0.6.4", + "subtle", +] + [[package]] name = "hashbrown" version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +dependencies = [ + "ahash", +] [[package]] name = "heck" @@ -117,6 +449,21 @@ dependencies = [ "libc", ] +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest 0.10.7", +] + [[package]] name = "indexmap" version = "1.9.3" @@ -133,6 +480,20 @@ version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" +[[package]] +name = "k256" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cadb76004ed8e97623117f3df85b17aaa6626ab0b0831e6573f104df16cd1bcc" +dependencies = [ + "cfg-if", + "ecdsa", + "elliptic-curve", + "once_cell", + "sha2 0.10.8", + "signature", +] + [[package]] name = "libc" version = "0.2.155" @@ -163,12 +524,28 @@ version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +[[package]] +name = "opaque-debug" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" + [[package]] name = "os_str_bytes" version = "6.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2355d85b9a3786f481747ced0e0ff2ba35213a1f9bd406ed906554d7af805a1" +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + [[package]] name = "proc-macro2" version = "1.0.86" @@ -187,6 +564,31 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rand_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rfc6979" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +dependencies = [ + "hmac", + "subtle", +] + [[package]] name = "rustix" version = "0.38.34" @@ -201,17 +603,48 @@ dependencies = [ ] [[package]] -name = "rustlib" -version = "0.1.0" +name = "ryu" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" + +[[package]] +name = "schemars" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09c024468a378b7e36765cd36702b7a90cc3cba11654f6685c8f233408e89e92" dependencies = [ - "cbindgen", + "dyn-clone", + "schemars_derive", + "serde", + "serde_json", ] [[package]] -name = "ryu" -version = "1.0.18" +name = "schemars_derive" +version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" +checksum = "b1eee588578aff73f856ab961cd2f79e36bc45d7ded33a7562adba4667aecc0e" +dependencies = [ + "proc-macro2", + "quote", + "serde_derive_internals", + "syn 2.0.72", +] + +[[package]] +name = "sec1" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" +dependencies = [ + "base16ct", + "der", + "generic-array", + "pkcs8", + "subtle", + "zeroize", +] [[package]] name = "serde" @@ -222,6 +655,15 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "serde-json-wasm" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e9213a07d53faa0b8dd81e767a54a8188a242fdb9be99ab75ec576a774bfdd7" +dependencies = [ + "serde", +] + [[package]] name = "serde_derive" version = "1.0.204" @@ -233,6 +675,17 @@ dependencies = [ "syn 2.0.72", ] +[[package]] +name = "serde_derive_internals" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.72", +] + [[package]] name = "serde_json" version = "1.0.122" @@ -245,12 +698,77 @@ dependencies = [ "serde", ] +[[package]] +name = "sha2" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" +dependencies = [ + "block-buffer 0.9.0", + "cfg-if", + "cpufeatures", + "digest 0.9.0", + "opaque-debug", +] + +[[package]] +name = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest 0.10.7", +] + +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "digest 0.10.7", + "rand_core 0.6.4", +] + +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "sqs_ffi" +version = "0.1.0" +dependencies = [ + "cbindgen", + "thiserror", + "transmuter_math", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + [[package]] name = "strsim" version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + [[package]] name = "syn" version = "1.0.109" @@ -301,6 +819,26 @@ version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23d434d3f8967a09480fb04132ebe0a3e088c173e6d0ee7897abbdf4eab0f8b9" +[[package]] +name = "thiserror" +version = "1.0.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.72", +] + [[package]] name = "toml" version = "0.5.11" @@ -310,12 +848,39 @@ dependencies = [ "serde", ] +[[package]] +name = "transmuter_math" +version = "0.1.0" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "thiserror", +] + +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + [[package]] name = "unicode-ident" version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + [[package]] name = "winapi" version = "0.3.9" @@ -428,3 +993,9 @@ name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "zeroize" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" diff --git a/Cargo.toml b/Cargo.toml index 9de021380..6ff366ef2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [workspace] members = [ - "rustlib/rustsrc", + "rustffi/rustsrc", ] resolver = "2" diff --git a/Makefile b/Makefile index 4c4237a0d..edf3bbd03 100644 --- a/Makefile +++ b/Makefile @@ -68,7 +68,7 @@ build: -v -o /osmosis/build/sqsd app/*.go build-rust-lib: - cargo build --release -p rustlib + cargo build --release -p sqs_ffi ############################################################################### ### Docker ### diff --git a/rustffi/link.go b/rustffi/link.go new file mode 100644 index 000000000..a4c5b90fb --- /dev/null +++ b/rustffi/link.go @@ -0,0 +1,6 @@ +package rustffi + +/* +#cgo LDFLAGS: target/release/libsqs_ffi.a -ldl +*/ +import "C" diff --git a/rustffi/numbers.go b/rustffi/numbers.go new file mode 100644 index 000000000..9b2c4a7c7 --- /dev/null +++ b/rustffi/numbers.go @@ -0,0 +1,55 @@ +package rustffi + +/* +#include "../target/release/libsqs_ffi.h" +*/ +import "C" +import ( + "errors" + "fmt" + "math/big" + + "github.com/osmosis-labs/osmosis/osmomath" +) + +func NewFFIDecimal(d *osmomath.Dec) (C.struct_FFIDecimal, error) { + u128, err := NewFFIU128(d.BigInt()) + if err != nil { + return C.struct_FFIDecimal{}, err + } + return C.struct_FFIDecimal{_0: u128}, nil +} + +func FFIDecimalToDec(d C.struct_FFIDecimal) osmomath.Dec { + return osmomath.NewDecFromBigIntWithPrec(FFIU128ToBigInt(d._0), osmomath.DecPrecision) +} + +func NewFFIU128(i *big.Int) (C.struct_FFIU128, error) { + if i.Sign() < 0 { + return C.struct_FFIU128{}, errors.New("negative number is not supported") + } + + bits := i.Bits() + u128Bits := [2]C.ulonglong{} + + if len(bits) == 0 { + u128Bits[0] = C.ulonglong(0) + u128Bits[1] = C.ulonglong(0) + } else if len(bits) == 1 { + u128Bits[0] = C.ulonglong(bits[0]) + u128Bits[1] = C.ulonglong(0) + } else if len(bits) == 2 { + u128Bits[0] = C.ulonglong(bits[0]) + u128Bits[1] = C.ulonglong(bits[1]) + } else { + return C.struct_FFIU128{}, fmt.Errorf("%d is too large to fit in U128", i) + } + return C.struct_FFIU128{_0: u128Bits}, nil +} + +func FFIU128ToBigInt(u128 C.struct_FFIU128) *big.Int { + bits := [2]big.Word{} + bits[0] = big.Word(u128._0[0]) + bits[1] = big.Word(u128._0[1]) + return big.NewInt(0).SetBits(bits[:]) +} diff --git a/rustffi/numbers_test.go b/rustffi/numbers_test.go new file mode 100644 index 000000000..f764da799 --- /dev/null +++ b/rustffi/numbers_test.go @@ -0,0 +1,131 @@ +package rustffi_test + +import ( + "errors" + "math/big" + "testing" + + "github.com/osmosis-labs/osmosis/osmomath" + "github.com/osmosis-labs/sqs/rustffi" + "gotest.tools/assert" +) + +func TestFFINewU128(t *testing.T) { + testCases := []struct { + name string + input *big.Int + expectedError error + }{ + { + name: "Zero", + input: big.NewInt(0), + }, + { + name: "Small positive number", + input: big.NewInt(42), + }, + { + name: "Large number within uint64", + input: new(big.Int).SetUint64(18446744073709551615), // 2^64 - 1 + }, + { + name: "Number larger than uint64", + input: func() *big.Int { + v, _ := new(big.Int).SetString("18446744073709551616", 10) // 2^64 + return v + }(), + }, + { + name: "Max u128", + input: func() *big.Int { + v, _ := new(big.Int).SetString("340282366920938463463374607431768211455", 10) // 2^128 - 1 + return v + }(), + }, + { + name: "Max u128 + 1", + input: func() *big.Int { + v, _ := new(big.Int).SetString("340282366920938463463374607431768211456", 10) // 2^128 + return v + }(), + expectedError: errors.New("340282366920938463463374607431768211456 is too large to fit in U128"), + }, + { + name: "Negative number", + input: big.NewInt(-1), + expectedError: errors.New("negative number is not supported"), + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + result, err := rustffi.NewFFIU128(tc.input) + + if tc.expectedError == nil { + assert.NilError(t, err) + } else { + assert.ErrorContains(t, err, tc.expectedError.Error()) + return + } + + // Test conversion back to big.Int + backToBigInt := rustffi.FFIU128ToBigInt(result) + if backToBigInt.Cmp(tc.input) != 0 { + t.Errorf("FFIU128ToBigInt(NewFFIU128(%v)) = %v, want %v", tc.input, backToBigInt, tc.input) + } + }) + } +} + +func TestNewDecimal(t *testing.T) { + testCases := []struct { + name string + input osmomath.Dec + expectedError error + }{ + { + name: "Zero", + input: osmomath.NewDec(0), + }, + { + name: "One", + input: osmomath.NewDec(1), + }, + { + name: "Large positive number", + input: osmomath.NewDec(1000000000000000000), + }, + { + name: "Fractional number", + input: osmomath.NewDecWithPrec(123456789, 9), // 0.123456789 + }, + { + name: "Negative one", + input: osmomath.NewDec(-1), + expectedError: errors.New("negative number is not supported"), + }, + { + name: "Large negative number", + input: osmomath.NewDec(-1000000000000000000), + expectedError: errors.New("negative number is not supported"), + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + result, err := rustffi.NewFFIDecimal(&tc.input) + + if tc.expectedError == nil { + assert.NilError(t, err) + + // Test conversion back to osmomath.Dec + backToDec := rustffi.FFIDecimalToDec(result) + if !backToDec.Equal(tc.input) { + t.Errorf("FFIDecimalToDec(NewFFIDecimal(%v)) = %v, want %v", tc.input, backToDec, tc.input) + } + } else { + assert.ErrorContains(t, err, tc.expectedError.Error()) + } + }) + } +} diff --git a/rustlib/rustsrc/Cargo.toml b/rustffi/rustsrc/Cargo.toml similarity index 67% rename from rustlib/rustsrc/Cargo.toml rename to rustffi/rustsrc/Cargo.toml index 38089601d..b9eda851c 100644 --- a/rustlib/rustsrc/Cargo.toml +++ b/rustffi/rustsrc/Cargo.toml @@ -1,6 +1,6 @@ [package] edition = "2021" -name = "rustlib" +name = "sqs_ffi" version = "0.1.0" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -8,6 +8,8 @@ version = "0.1.0" crate-type = ["staticlib"] [dependencies] +thiserror = "1.0.63" +transmuter_math = {path = "../../../transmuter/packages/transmuter_math"} [build-dependencies] cbindgen = "0.26.0" diff --git a/rustlib/rustsrc/build.rs b/rustffi/rustsrc/build.rs similarity index 93% rename from rustlib/rustsrc/build.rs rename to rustffi/rustsrc/build.rs index 2b276e8ed..993e49a8e 100644 --- a/rustlib/rustsrc/build.rs +++ b/rustffi/rustsrc/build.rs @@ -12,7 +12,7 @@ fn main() { .join(format!("lib{}.h", create_name)); let mut conf = cbindgen::Config::default(); - conf.no_includes = true; + conf.language = cbindgen::Language::C; cbindgen::Builder::new() .with_crate(crate_dir) diff --git a/rustffi/rustsrc/src/lib.rs b/rustffi/rustsrc/src/lib.rs new file mode 100644 index 000000000..9b99ff2f0 --- /dev/null +++ b/rustffi/rustsrc/src/lib.rs @@ -0,0 +1,12 @@ +mod numbers; +mod result; +mod transmuter; + +use crate::result::FFIResult; +use transmuter::FFIDivision; + +#[no_mangle] +pub extern "C" fn print_division(division: FFIDivision) -> FFIResult { + println!("{:?}", division.into_division()); + FFIResult::ok(0) +} diff --git a/rustffi/rustsrc/src/numbers.rs b/rustffi/rustsrc/src/numbers.rs new file mode 100644 index 000000000..aed4bd945 --- /dev/null +++ b/rustffi/rustsrc/src/numbers.rs @@ -0,0 +1,26 @@ +/// FFI-safe little endian u128 construction +#[repr(C)] +#[derive(Debug)] +pub struct FFIU128([u64; 2]); + +impl From for u128 { + fn from(value: FFIU128) -> Self { + value.0[0] as u128 | (value.0[1] as u128) << 64 + } +} + +impl From for FFIU128 { + fn from(value: u128) -> Self { + FFIU128([value as u64, (value >> 64) as u64]) + } +} + +#[repr(C)] +#[derive(Debug)] +pub struct FFIDecimal(FFIU128); + +impl From for transmuter_math::Decimal { + fn from(value: FFIDecimal) -> Self { + transmuter_math::Decimal::raw(value.0.into()) + } +} diff --git a/rustffi/rustsrc/src/result.rs b/rustffi/rustsrc/src/result.rs new file mode 100644 index 000000000..4693bd60c --- /dev/null +++ b/rustffi/rustsrc/src/result.rs @@ -0,0 +1,26 @@ +use std::{ffi::CString, fmt::Display}; + +#[repr(C)] +pub struct FFIResult { + ok: *const T, + err: *const std::ffi::c_char, +} + +impl FFIResult { + pub fn ok(value: T) -> Self { + Self { + ok: Box::into_raw(Box::new(value)), + err: std::ptr::null(), + } + } + + pub fn err(value: E) -> Self { + let err = + CString::new(value.to_string()).expect("string must not contain zero internal byte"); + + Self { + ok: std::ptr::null(), + err: err.into_raw(), + } + } +} diff --git a/rustffi/rustsrc/src/transmuter.rs b/rustffi/rustsrc/src/transmuter.rs new file mode 100644 index 000000000..21f138594 --- /dev/null +++ b/rustffi/rustsrc/src/transmuter.rs @@ -0,0 +1,29 @@ +use transmuter_math::{Division, Timestamp}; + +use crate::numbers::FFIDecimal; + +#[repr(C)] +pub struct FFIDivision { + /// Time where the division is mark as started + pub started_at: u64, + + /// Time where it is last updated + pub updated_at: u64, + + /// The latest value that gets updated + pub latest_value: FFIDecimal, + + /// sum of each updated value * elasped time since last update + pub integral: FFIDecimal, +} + +impl FFIDivision { + pub fn into_division(self) -> Division { + Division::unchecked_new( + Timestamp::from_nanos(self.started_at), + Timestamp::from_nanos(self.updated_at), + self.latest_value.into(), + self.integral.into(), + ) + } +} diff --git a/rustffi/transmuter.go b/rustffi/transmuter.go new file mode 100644 index 000000000..24a2ea7fa --- /dev/null +++ b/rustffi/transmuter.go @@ -0,0 +1,25 @@ +package rustffi + +/* +#include "../target/release/libsqs_ffi.h" +*/ +import "C" +import "github.com/osmosis-labs/osmosis/osmomath" + +func NewFFIDivision(startedAt, updatedAt int64, lastestValue, integral osmomath.Dec) (C.struct_FFIDivision, error) { + latestValueFFIDec, err := NewFFIDecimal(&lastestValue) + if err != nil { + return C.struct_FFIDivision{}, err + } + integralFFIDec, err := NewFFIDecimal(&integral) + if err != nil { + return C.struct_FFIDivision{}, err + } + + return C.struct_FFIDivision{ + started_at: C.uint64_t(startedAt), + updated_at: C.uint64_t(updatedAt), + latest_value: latestValueFFIDec, + integral: integralFFIDec, + }, nil +} diff --git a/rustlib/add.go b/rustlib/add.go deleted file mode 100644 index 5936ebe90..000000000 --- a/rustlib/add.go +++ /dev/null @@ -1,11 +0,0 @@ -package rustexports - -/* -#include "../target/release/librustlib.h" -*/ -import "C" - -func Add(a, b float64) float64 { - result := C.add(C.double(a), C.double(b)) - return float64(result) -} diff --git a/rustlib/link.go b/rustlib/link.go deleted file mode 100644 index 31d7e926e..000000000 --- a/rustlib/link.go +++ /dev/null @@ -1,6 +0,0 @@ -package rustexports - -/* -#cgo LDFLAGS: target/release/librustlib.a -ldl -*/ -import "C" diff --git a/rustlib/rustsrc/src/lib.rs b/rustlib/rustsrc/src/lib.rs deleted file mode 100644 index 857e6e3ab..000000000 --- a/rustlib/rustsrc/src/lib.rs +++ /dev/null @@ -1,9 +0,0 @@ -#[no_mangle] -pub extern "C" fn add(left: f64, right: f64) -> f64 { - left + right -} - -#[no_mangle] -pub extern "C" fn sub(left: f64, right: f64) -> f64 { - left - right -} diff --git a/rustlib/sub.go b/rustlib/sub.go deleted file mode 100644 index a3b289ee4..000000000 --- a/rustlib/sub.go +++ /dev/null @@ -1,11 +0,0 @@ -package rustexports - -/* -#include "../target/release/librustlib.h" -*/ -import "C" - -func Sub(a, b float64) float64 { - result := C.sub(C.double(a), C.double(b)) - return float64(result) -} From 5c8eae457ec90eeb6823a88b08d65629dbc8b94b Mon Sep 17 00:00:00 2001 From: Supanat Potiwarakorn Date: Wed, 7 Aug 2024 15:46:44 +0700 Subject: [PATCH 04/14] add number conversion test from rust side --- rustffi/rustsrc/src/numbers.rs | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/rustffi/rustsrc/src/numbers.rs b/rustffi/rustsrc/src/numbers.rs index aed4bd945..674540dca 100644 --- a/rustffi/rustsrc/src/numbers.rs +++ b/rustffi/rustsrc/src/numbers.rs @@ -24,3 +24,36 @@ impl From for transmuter_math::Decimal { transmuter_math::Decimal::raw(value.0.into()) } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_ffiu128_conversion() { + // Test small number + let small: u128 = 42; + let ffi_small = FFIU128::from(small); + assert_eq!(u128::from(ffi_small), small); + + // Test large number + let large: u128 = 0xFFFFFFFFFFFFFFFF_FFFFFFFFFFFFFFFF; + let ffi_large = FFIU128::from(large); + assert_eq!(u128::from(ffi_large), large); + + // Test middle range number + let middle: u128 = 0x0123456789ABCDEF_0123456789ABCDEF; + let ffi_middle = FFIU128::from(middle); + assert_eq!(u128::from(ffi_middle), middle); + + // Test zero + let zero: u128 = 0; + let ffi_zero = FFIU128::from(zero); + assert_eq!(u128::from(ffi_zero), zero); + + // Test max u64 + 1 + let over_u64: u128 = 0x0000000000000001_0000000000000000; + let ffi_over_u64 = FFIU128::from(over_u64); + assert_eq!(u128::from(ffi_over_u64), over_u64); + } +} From a0e5935f706e6315b5559786e2833174c4107d0b Mon Sep 17 00:00:00 2001 From: Supanat Potiwarakorn Date: Wed, 7 Aug 2024 17:36:02 +0700 Subject: [PATCH 05/14] test compressed_moving_average --- rustffi/numbers_test.go | 10 +- rustffi/rustsrc/src/lib.rs | 9 -- rustffi/rustsrc/src/numbers.rs | 40 +++++++- rustffi/rustsrc/src/transmuter.rs | 45 ++++++++- rustffi/transmuter.go | 60 ++++++++++- rustffi/transmuter_test.go | 163 ++++++++++++++++++++++++++++++ 6 files changed, 306 insertions(+), 21 deletions(-) create mode 100644 rustffi/transmuter_test.go diff --git a/rustffi/numbers_test.go b/rustffi/numbers_test.go index f764da799..b387a9304 100644 --- a/rustffi/numbers_test.go +++ b/rustffi/numbers_test.go @@ -7,7 +7,7 @@ import ( "github.com/osmosis-labs/osmosis/osmomath" "github.com/osmosis-labs/sqs/rustffi" - "gotest.tools/assert" + "github.com/stretchr/testify/require" ) func TestFFINewU128(t *testing.T) { @@ -62,9 +62,9 @@ func TestFFINewU128(t *testing.T) { result, err := rustffi.NewFFIU128(tc.input) if tc.expectedError == nil { - assert.NilError(t, err) + require.NoError(t, err) } else { - assert.ErrorContains(t, err, tc.expectedError.Error()) + require.ErrorContains(t, err, tc.expectedError.Error()) return } @@ -116,7 +116,7 @@ func TestNewDecimal(t *testing.T) { result, err := rustffi.NewFFIDecimal(&tc.input) if tc.expectedError == nil { - assert.NilError(t, err) + require.NoError(t, err) // Test conversion back to osmomath.Dec backToDec := rustffi.FFIDecimalToDec(result) @@ -124,7 +124,7 @@ func TestNewDecimal(t *testing.T) { t.Errorf("FFIDecimalToDec(NewFFIDecimal(%v)) = %v, want %v", tc.input, backToDec, tc.input) } } else { - assert.ErrorContains(t, err, tc.expectedError.Error()) + require.ErrorContains(t, err, tc.expectedError.Error()) } }) } diff --git a/rustffi/rustsrc/src/lib.rs b/rustffi/rustsrc/src/lib.rs index 9b99ff2f0..833b457c2 100644 --- a/rustffi/rustsrc/src/lib.rs +++ b/rustffi/rustsrc/src/lib.rs @@ -1,12 +1,3 @@ mod numbers; mod result; mod transmuter; - -use crate::result::FFIResult; -use transmuter::FFIDivision; - -#[no_mangle] -pub extern "C" fn print_division(division: FFIDivision) -> FFIResult { - println!("{:?}", division.into_division()); - FFIResult::ok(0) -} diff --git a/rustffi/rustsrc/src/numbers.rs b/rustffi/rustsrc/src/numbers.rs index 674540dca..d03c3583a 100644 --- a/rustffi/rustsrc/src/numbers.rs +++ b/rustffi/rustsrc/src/numbers.rs @@ -1,6 +1,6 @@ /// FFI-safe little endian u128 construction #[repr(C)] -#[derive(Debug)] +#[derive(Clone)] pub struct FFIU128([u64; 2]); impl From for u128 { @@ -16,7 +16,7 @@ impl From for FFIU128 { } #[repr(C)] -#[derive(Debug)] +#[derive(Clone)] pub struct FFIDecimal(FFIU128); impl From for transmuter_math::Decimal { @@ -25,6 +25,12 @@ impl From for transmuter_math::Decimal { } } +impl From for FFIDecimal { + fn from(value: transmuter_math::Decimal) -> Self { + FFIDecimal(value.atomics().u128().into()) + } +} + #[cfg(test)] mod tests { use super::*; @@ -56,4 +62,34 @@ mod tests { let ffi_over_u64 = FFIU128::from(over_u64); assert_eq!(u128::from(ffi_over_u64), over_u64); } + + #[test] + fn test_ffidecimal_conversion() { + use transmuter_math::Decimal; + + // Test small decimal + let small = Decimal::raw(42); + let ffi_small = FFIDecimal::from(small); + assert_eq!(Decimal::from(ffi_small), small); + + // Test large decimal + let large = Decimal::raw(340282366920938463463374607431768211455u128); // Max value for Decimal + let ffi_large = FFIDecimal::from(large); + assert_eq!(Decimal::from(ffi_large), large); + + // Test middle range decimal + let middle = Decimal::raw(1000000000000000000u128); // 1.0 in Decimal + let ffi_middle = FFIDecimal::from(middle); + assert_eq!(Decimal::from(ffi_middle), middle); + + // Test zero + let zero = Decimal::zero(); + let ffi_zero = FFIDecimal::from(zero); + assert_eq!(Decimal::from(ffi_zero), zero); + + // Test fractional decimal + let fractional = Decimal::raw(1234567890123456789u128); // ~1.234567890123456789 + let ffi_fractional = FFIDecimal::from(fractional); + assert_eq!(Decimal::from(ffi_fractional), fractional); + } } diff --git a/rustffi/rustsrc/src/transmuter.rs b/rustffi/rustsrc/src/transmuter.rs index 21f138594..22ec25c11 100644 --- a/rustffi/rustsrc/src/transmuter.rs +++ b/rustffi/rustsrc/src/transmuter.rs @@ -1,8 +1,8 @@ -use transmuter_math::{Division, Timestamp}; - -use crate::numbers::FFIDecimal; +use crate::{numbers::FFIDecimal, result::FFIResult}; +use transmuter_math::{Division, Timestamp, Uint64}; #[repr(C)] +#[derive(Clone)] pub struct FFIDivision { /// Time where the division is mark as started pub started_at: u64, @@ -27,3 +27,42 @@ impl FFIDivision { ) } } + +fn ptr_to_option(ptr: *const T) -> Option { + if ptr.is_null() { + None + } else { + Some(unsafe { &*ptr }.clone()) + } +} + +#[no_mangle] +pub extern "C" fn compressed_moving_average( + latest_removed_division: *const FFIDivision, + divisions_ptr: *const FFIDivision, + divisions_len: usize, + division_size: u64, + window_size: u64, + block_time: u64, // timestamp nanos +) -> FFIResult { + let latest_removed_division = ptr_to_option(latest_removed_division); + let divisions = unsafe { std::slice::from_raw_parts(divisions_ptr, divisions_len) }.to_vec(); + + let res = transmuter_math::compressed_moving_average( + latest_removed_division.map(|d| d.into_division()), + divisions + .into_iter() + .map(|d| d.into_division()) + .collect::>() + .as_slice(), + Uint64::from(division_size), + Uint64::from(window_size), + Timestamp::from_nanos(block_time), + ); + match res { + Ok(decimal) => FFIResult::ok(decimal.into()), + Err(e) => FFIResult::err(e), + } +} + +// TODO: expose `clean_up_outdated_divisions` diff --git a/rustffi/transmuter.go b/rustffi/transmuter.go index 24a2ea7fa..328da0410 100644 --- a/rustffi/transmuter.go +++ b/rustffi/transmuter.go @@ -4,9 +4,20 @@ package rustffi #include "../target/release/libsqs_ffi.h" */ import "C" -import "github.com/osmosis-labs/osmosis/osmomath" +import ( + "errors" + "unsafe" -func NewFFIDivision(startedAt, updatedAt int64, lastestValue, integral osmomath.Dec) (C.struct_FFIDivision, error) { + "github.com/osmosis-labs/osmosis/osmomath" +) + +func NewFFIDivision(startedAt, updatedAt uint64, lastestValue, prevValue osmomath.Dec) (C.struct_FFIDivision, error) { + elapsedTime := updatedAt - startedAt + integral := prevValue.MulInt(osmomath.NewIntFromUint64(elapsedTime)) + return NewFFIDivisionRaw(startedAt, updatedAt, lastestValue, integral) +} + +func NewFFIDivisionRaw(startedAt, updatedAt uint64, lastestValue, integral osmomath.Dec) (C.struct_FFIDivision, error) { latestValueFFIDec, err := NewFFIDecimal(&lastestValue) if err != nil { return C.struct_FFIDivision{}, err @@ -23,3 +34,48 @@ func NewFFIDivision(startedAt, updatedAt int64, lastestValue, integral osmomath. integral: integralFFIDec, }, nil } + +func NewFFIDivisions(divisions []struct { + StartedAt uint64 + UpdatedAt uint64 + LatestValue osmomath.Dec + PrevValue osmomath.Dec +}) ([]C.struct_FFIDivision, error) { + ffidivisions := make([]C.struct_FFIDivision, len(divisions)) + for i, division := range divisions { + div, err := NewFFIDivision(division.StartedAt, division.UpdatedAt, division.LatestValue, division.PrevValue) + if err != nil { + return nil, err + } + ffidivisions[i] = div + } + return ffidivisions, nil +} + +func CompressedMovingAverage(latestRemovedDivision *C.struct_FFIDivision, divisions []C.struct_FFIDivision, divisionSize, windowSize, blockTime uint64) (osmomath.Dec, error) { + var divisionsPtr *C.struct_FFIDivision + if len(divisions) > 0 { + divisionsPtr = &divisions[0] + } + + result := C.compressed_moving_average( + latestRemovedDivision, + divisionsPtr, + C.uintptr_t(len(divisions)), + C.uint64_t(divisionSize), + C.uint64_t(windowSize), + C.uint64_t(blockTime), + ) + + errPtr := unsafe.Pointer(result.err) + okPtr := unsafe.Pointer(result.ok) + defer C.free(errPtr) + defer C.free(okPtr) + + if result.err != nil { + return osmomath.Dec{}, errors.New(C.GoString(result.err)) + } + + // CONTRACT: result.ok must not be nil if result.err is nil + return FFIDecimalToDec(*result.ok), nil +} diff --git a/rustffi/transmuter_test.go b/rustffi/transmuter_test.go new file mode 100644 index 000000000..fc7dd6c21 --- /dev/null +++ b/rustffi/transmuter_test.go @@ -0,0 +1,163 @@ +package rustffi + +import ( + "testing" + + "github.com/osmosis-labs/osmosis/osmomath" + "github.com/stretchr/testify/require" +) + +func TestCompressedMovingAverage(t *testing.T) { + t.Run("no divisions", func(t *testing.T) { + divisions, err := NewFFIDivisions([]struct { + StartedAt uint64 + UpdatedAt uint64 + LatestValue osmomath.Dec + PrevValue osmomath.Dec + }{}) + require.NoError(t, err) + + average, err := CompressedMovingAverage(nil, divisions, 100, 1000, 1270) + require.ErrorContains(t, err, "Missing data points to calculate moving average") + require.Equal(t, osmomath.Dec{}, average) + }) + + t.Run("2 divisions", func(t *testing.T) { + // Create a slice of FFIDivisions + divisions, err := NewFFIDivisions([]struct { + StartedAt uint64 + UpdatedAt uint64 + LatestValue osmomath.Dec + PrevValue osmomath.Dec + }{ + {1100, 1110, osmomath.NewDecWithPrec(20, 2), osmomath.NewDecWithPrec(10, 2)}, + {1200, 1260, osmomath.NewDecWithPrec(30, 2), osmomath.NewDecWithPrec(20, 2)}, + }) + require.NoError(t, err) + + // Call CompressedMovingAverage + result, err := CompressedMovingAverage(nil, divisions, 100, 1000, 1270) + + // Check for errors + require.NoError(t, err) + + // Calculate expected result + expected := osmomath.NewDecWithPrec(10, 2).MulInt64(10). + Add(osmomath.NewDecWithPrec(20, 2).MulInt64(90)). + Add(osmomath.NewDecWithPrec(20, 2).MulInt64(60)). + Add(osmomath.NewDecWithPrec(30, 2).MulInt64(10)). + Quo(osmomath.NewDec(170)) + + // Verify the result matches the expected value + require.Equal(t, expected, result) + }) + + t.Run("test average when div is skipping", func(t *testing.T) { + // skipping 1 division + divisionSize := uint64(200) + windowSize := uint64(600) + + divisions, err := NewFFIDivisions([]struct { + StartedAt uint64 + UpdatedAt uint64 + LatestValue osmomath.Dec + PrevValue osmomath.Dec + }{ + {1100, 1110, osmomath.NewDecWithPrec(20, 2), osmomath.NewDecWithPrec(10, 2)}, + // -- skip 1300 -> 1500 -- + // 20% * 200 - 1 div size + {1500, 1540, osmomath.NewDecWithPrec(30, 2), osmomath.NewDecWithPrec(20, 2)}, + }) + require.NoError(t, err) + + blockTime := uint64(1600) + + average, err := CompressedMovingAverage(nil, divisions, divisionSize, windowSize, blockTime) + require.NoError(t, err) + + expected := osmomath.NewDecWithPrec(10, 2).MulInt64(10). + Add(osmomath.NewDecWithPrec(20, 2).MulInt64(190)). + Add(osmomath.NewDecWithPrec(20, 2).MulInt64(200)). // skipped div + Add(osmomath.NewDecWithPrec(20, 2).MulInt64(40)). + Add(osmomath.NewDecWithPrec(30, 2).MulInt64(60)). + Quo(osmomath.NewDec(500)) + + require.Equal(t, expected, average) + + latestRemovedDivision, err := NewFFIDivision(700, 750, osmomath.NewDecWithPrec(10, 2), osmomath.NewDecWithPrec(15, 2)) + require.NoError(t, err) + + average, err = CompressedMovingAverage(&latestRemovedDivision, divisions, divisionSize, windowSize, blockTime) + require.NoError(t, err) + + expected = osmomath.NewDecWithPrec(10, 2).MulInt64(100). // before first div + Add(osmomath.NewDecWithPrec(10, 2).MulInt64(10)). + Add(osmomath.NewDecWithPrec(20, 2).MulInt64(190)). + Add(osmomath.NewDecWithPrec(20, 2).MulInt64(200)). // skipped div + Add(osmomath.NewDecWithPrec(20, 2).MulInt64(40)). + Add(osmomath.NewDecWithPrec(30, 2).MulInt64(60)). + Quo(osmomath.NewDec(600)). + Sub(osmomath.NewDecWithPrec(1, osmomath.DecPrecision)) // remove round up + + require.Equal(t, expected, average) + + blockTime = uint64(1700) + average, err = CompressedMovingAverage(&latestRemovedDivision, divisions, divisionSize, windowSize, blockTime) + require.NoError(t, err) + + expected = osmomath.NewDecWithPrec(10, 2).MulInt64(10). + Add(osmomath.NewDecWithPrec(20, 2).MulInt64(190)). + Add(osmomath.NewDecWithPrec(20, 2).MulInt64(200)). // skipped div + Add(osmomath.NewDecWithPrec(20, 2).MulInt64(40)). + Add(osmomath.NewDecWithPrec(30, 2).MulInt64(160)). + Quo(osmomath.NewDec(600)) + + require.Equal(t, expected, average) + + // skipping 2 divisions + divisionSize = uint64(100) + windowSize = uint64(600) + + divisions, err = NewFFIDivisions([]struct { + StartedAt uint64 + UpdatedAt uint64 + LatestValue osmomath.Dec + PrevValue osmomath.Dec + }{ + {1100, 1110, osmomath.NewDecWithPrec(20, 2), osmomath.NewDecWithPrec(10, 2)}, + // -- skip 1300 -> 1500 -- + // 20% * 200 - 2 div size + {1500, 1540, osmomath.NewDecWithPrec(30, 2), osmomath.NewDecWithPrec(20, 2)}, + }) + require.NoError(t, err) + + blockTime = uint64(1600) + + average, err = CompressedMovingAverage(nil, divisions, divisionSize, windowSize, blockTime) + require.NoError(t, err) + + expected = osmomath.NewDecWithPrec(10, 2).MulInt64(10). + Add(osmomath.NewDecWithPrec(20, 2).MulInt64(190)). + Add(osmomath.NewDecWithPrec(20, 2).MulInt64(100)). // skipped div + Add(osmomath.NewDecWithPrec(20, 2).MulInt64(100)). // skipped div + Add(osmomath.NewDecWithPrec(20, 2).MulInt64(40)). + Add(osmomath.NewDecWithPrec(30, 2).MulInt64(60)). + Quo(osmomath.NewDec(500)) + + require.Equal(t, expected, average) + + blockTime = uint64(1710) + + average, err = CompressedMovingAverage(nil, divisions, divisionSize, windowSize, blockTime) + require.NoError(t, err) + + expected = osmomath.NewDecWithPrec(20, 2).MulInt64(190). + Add(osmomath.NewDecWithPrec(20, 2).MulInt64(100)). // skipped div + Add(osmomath.NewDecWithPrec(20, 2).MulInt64(100)). // skipped div + Add(osmomath.NewDecWithPrec(20, 2).MulInt64(40)). + Add(osmomath.NewDecWithPrec(30, 2).MulInt64(170)). + Quo(osmomath.NewDec(600)) + + require.Equal(t, expected, average) + }) +} From 31116a7c814b28c47b714d7e0a1882dd7138d86f Mon Sep 17 00:00:00 2001 From: Supanat Potiwarakorn Date: Wed, 7 Aug 2024 17:54:37 +0700 Subject: [PATCH 06/14] create a FFISlice struct --- rustffi/rustsrc/src/lib.rs | 2 ++ rustffi/rustsrc/src/option.rs | 7 +++++++ rustffi/rustsrc/src/slice.rs | 12 ++++++++++++ rustffi/rustsrc/src/transmuter.rs | 19 ++++++------------- rustffi/transmuter.go | 19 ++++++++++++------- 5 files changed, 39 insertions(+), 20 deletions(-) create mode 100644 rustffi/rustsrc/src/option.rs create mode 100644 rustffi/rustsrc/src/slice.rs diff --git a/rustffi/rustsrc/src/lib.rs b/rustffi/rustsrc/src/lib.rs index 833b457c2..2f3152c0a 100644 --- a/rustffi/rustsrc/src/lib.rs +++ b/rustffi/rustsrc/src/lib.rs @@ -1,3 +1,5 @@ mod numbers; +mod option; mod result; +mod slice; mod transmuter; diff --git a/rustffi/rustsrc/src/option.rs b/rustffi/rustsrc/src/option.rs new file mode 100644 index 000000000..94e859d43 --- /dev/null +++ b/rustffi/rustsrc/src/option.rs @@ -0,0 +1,7 @@ +pub fn nullable_ptr_to_option(ptr: *const T) -> Option { + if ptr.is_null() { + None + } else { + Some(unsafe { &*ptr }.clone()) + } +} diff --git a/rustffi/rustsrc/src/slice.rs b/rustffi/rustsrc/src/slice.rs new file mode 100644 index 000000000..b20bf2fd9 --- /dev/null +++ b/rustffi/rustsrc/src/slice.rs @@ -0,0 +1,12 @@ +#[repr(C)] + +pub struct FFISlice { + ptr: *const T, + len: usize, +} + +impl FFISlice { + pub fn as_slice(&self) -> &[T] { + unsafe { std::slice::from_raw_parts(self.ptr, self.len) } + } +} diff --git a/rustffi/rustsrc/src/transmuter.rs b/rustffi/rustsrc/src/transmuter.rs index 22ec25c11..7192c3eb8 100644 --- a/rustffi/rustsrc/src/transmuter.rs +++ b/rustffi/rustsrc/src/transmuter.rs @@ -1,4 +1,6 @@ -use crate::{numbers::FFIDecimal, result::FFIResult}; +use crate::{ + numbers::FFIDecimal, option::nullable_ptr_to_option, result::FFIResult, slice::FFISlice, +}; use transmuter_math::{Division, Timestamp, Uint64}; #[repr(C)] @@ -28,25 +30,16 @@ impl FFIDivision { } } -fn ptr_to_option(ptr: *const T) -> Option { - if ptr.is_null() { - None - } else { - Some(unsafe { &*ptr }.clone()) - } -} - #[no_mangle] pub extern "C" fn compressed_moving_average( latest_removed_division: *const FFIDivision, - divisions_ptr: *const FFIDivision, - divisions_len: usize, + divisions: FFISlice, division_size: u64, window_size: u64, block_time: u64, // timestamp nanos ) -> FFIResult { - let latest_removed_division = ptr_to_option(latest_removed_division); - let divisions = unsafe { std::slice::from_raw_parts(divisions_ptr, divisions_len) }.to_vec(); + let latest_removed_division = nullable_ptr_to_option(latest_removed_division); + let divisions = divisions.as_slice().to_vec(); let res = transmuter_math::compressed_moving_average( latest_removed_division.map(|d| d.into_division()), diff --git a/rustffi/transmuter.go b/rustffi/transmuter.go index 328da0410..f693aa564 100644 --- a/rustffi/transmuter.go +++ b/rustffi/transmuter.go @@ -53,15 +53,9 @@ func NewFFIDivisions(divisions []struct { } func CompressedMovingAverage(latestRemovedDivision *C.struct_FFIDivision, divisions []C.struct_FFIDivision, divisionSize, windowSize, blockTime uint64) (osmomath.Dec, error) { - var divisionsPtr *C.struct_FFIDivision - if len(divisions) > 0 { - divisionsPtr = &divisions[0] - } - result := C.compressed_moving_average( latestRemovedDivision, - divisionsPtr, - C.uintptr_t(len(divisions)), + newFFIDivisionSlice(divisions), C.uint64_t(divisionSize), C.uint64_t(windowSize), C.uint64_t(blockTime), @@ -79,3 +73,14 @@ func CompressedMovingAverage(latestRemovedDivision *C.struct_FFIDivision, divisi // CONTRACT: result.ok must not be nil if result.err is nil return FFIDecimalToDec(*result.ok), nil } + +func newFFIDivisionSlice(divisions []C.struct_FFIDivision) C.struct_FFISlice_FFIDivision { + var divisionsPtr *C.struct_FFIDivision + if len(divisions) > 0 { + divisionsPtr = &divisions[0] + } + return C.struct_FFISlice_FFIDivision{ + ptr: divisionsPtr, + len: C.uintptr_t(len(divisions)), + } +} From c9be7819bdf377658371ace5203b88c0ebb8a324 Mon Sep 17 00:00:00 2001 From: Supanat Potiwarakorn Date: Fri, 9 Aug 2024 13:05:15 +0700 Subject: [PATCH 07/14] expose is division outdated --- rustffi/rustsrc/src/result.rs | 9 +++++++ rustffi/rustsrc/src/transmuter.rs | 17 ++++++++++++- rustffi/transmuter.go | 20 +++++++++++++++ rustffi/transmuter_test.go | 42 +++++++++++++++++++++++++++++++ 4 files changed, 87 insertions(+), 1 deletion(-) diff --git a/rustffi/rustsrc/src/result.rs b/rustffi/rustsrc/src/result.rs index 4693bd60c..93b6f0de9 100644 --- a/rustffi/rustsrc/src/result.rs +++ b/rustffi/rustsrc/src/result.rs @@ -24,3 +24,12 @@ impl FFIResult { } } } + +impl From> for FFIResult { + fn from(value: Result) -> Self { + match value { + Ok(value) => Self::ok(value), + Err(value) => Self::err(value), + } + } +} diff --git a/rustffi/rustsrc/src/transmuter.rs b/rustffi/rustsrc/src/transmuter.rs index 7192c3eb8..894858660 100644 --- a/rustffi/rustsrc/src/transmuter.rs +++ b/rustffi/rustsrc/src/transmuter.rs @@ -58,4 +58,19 @@ pub extern "C" fn compressed_moving_average( } } -// TODO: expose `clean_up_outdated_divisions` +#[no_mangle] +pub extern "C" fn is_division_outdated( + division: FFIDivision, + block_time: u64, + window_size: u64, + division_size: u64, +) -> FFIResult { + division + .into_division() + .is_outdated( + Timestamp::from_nanos(block_time), + Uint64::from(window_size), + Uint64::from(division_size), + ) + .into() +} diff --git a/rustffi/transmuter.go b/rustffi/transmuter.go index f693aa564..411fd7d94 100644 --- a/rustffi/transmuter.go +++ b/rustffi/transmuter.go @@ -74,6 +74,26 @@ func CompressedMovingAverage(latestRemovedDivision *C.struct_FFIDivision, divisi return FFIDecimalToDec(*result.ok), nil } +func IsDivisionOutdated(division C.struct_FFIDivision, blockTime, windowSize, divisionSize uint64) (bool, error) { + result := C.is_division_outdated( + division, + C.uint64_t(blockTime), + C.uint64_t(windowSize), + C.uint64_t(divisionSize), + ) + + errPtr := unsafe.Pointer(result.err) + okPtr := unsafe.Pointer(result.ok) + defer C.free(errPtr) + defer C.free(okPtr) + + if result.err != nil { + return false, errors.New(C.GoString(result.err)) + } + + return bool(*result.ok), nil +} + func newFFIDivisionSlice(divisions []C.struct_FFIDivision) C.struct_FFISlice_FFIDivision { var divisionsPtr *C.struct_FFIDivision if len(divisions) > 0 { diff --git a/rustffi/transmuter_test.go b/rustffi/transmuter_test.go index fc7dd6c21..959e52e54 100644 --- a/rustffi/transmuter_test.go +++ b/rustffi/transmuter_test.go @@ -1,6 +1,7 @@ package rustffi import ( + "errors" "testing" "github.com/osmosis-labs/osmosis/osmomath" @@ -161,3 +162,44 @@ func TestCompressedMovingAverage(t *testing.T) { require.Equal(t, expected, average) }) } + +func TestIsDivisionOutdated(t *testing.T) { + division, err := NewFFIDivisionRaw( + 1000000000, + 1000000022, + osmomath.NewDecWithPrec(10, 2), + osmomath.NewDecWithPrec(22, 2), + ) + require.NoError(t, err) + + windowSize := uint64(1000) + divisionSize := uint64(100) + + testCases := []struct { + name string + blockTime uint64 + expected bool + expectedError error + }{ + {name: "within window - start", blockTime: 1000000000, expected: false}, + {name: "within window - near end", blockTime: 1000000999, expected: false}, + {name: "within window - at end", blockTime: 1000001000, expected: false}, + {name: "within window - last valid", blockTime: 1000001099, expected: false}, + {name: "out of window - first invalid", blockTime: 1000001100, expected: true}, + {name: "out of window - just after", blockTime: 1000001101, expected: true}, + {name: "out of window - far after", blockTime: 1000001200, expected: true}, + {name: "blocktime too old", blockTime: 1, expectedError: errors.New("Cannot Sub with 1 and 1000")}, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + result, err := IsDivisionOutdated(division, tc.blockTime, windowSize, divisionSize) + if tc.expectedError != nil { + require.EqualError(t, err, tc.expectedError.Error()) + return + } + require.NoError(t, err) + require.Equal(t, tc.expected, result) + }) + } +} From 6bbaa3418f96f1f6f26ffc4a82bca3c5fb174c40 Mon Sep 17 00:00:00 2001 From: Supanat Potiwarakorn Date: Fri, 9 Aug 2024 15:31:37 +0700 Subject: [PATCH 08/14] check change rate limiter --- domain/errors.go | 10 + router/usecase/pools/export_test.go | 7 + .../routable_cw_alloy_transmuter_pool.go | 189 ++++++++++++++++-- rustffi/transmuter.go | 27 +++ sqsdomain/cosmwasmpool/alloy_transmuter.go | 4 +- 5 files changed, 216 insertions(+), 21 deletions(-) diff --git a/domain/errors.go b/domain/errors.go index 90a637c59..5ceb34003 100644 --- a/domain/errors.go +++ b/domain/errors.go @@ -324,3 +324,13 @@ type StaticRateLimiterInvalidUpperLimitError struct { func (e StaticRateLimiterInvalidUpperLimitError) Error() string { return fmt.Sprintf("invalid upper limit (%s) for weight (%s) and denom (%s)", e.UpperLimit, e.Weight, e.Denom) } + +type ChangeRateLimiterInvalidUpperLimitError struct { + UpperLimit string + Weight string + Denom string +} + +func (e ChangeRateLimiterInvalidUpperLimitError) Error() string { + return fmt.Sprintf("invalid upper limit (%s) for weight (%s) and denom (%s)", e.UpperLimit, e.Weight, e.Denom) +} diff --git a/router/usecase/pools/export_test.go b/router/usecase/pools/export_test.go index 6ce5c4040..accf1c6e1 100644 --- a/router/usecase/pools/export_test.go +++ b/router/usecase/pools/export_test.go @@ -1,11 +1,14 @@ package pools import ( + "time" + sdk "github.com/cosmos/cosmos-sdk/types" "github.com/osmosis-labs/osmosis/osmomath" cwpoolmodel "github.com/osmosis-labs/osmosis/v25/x/cosmwasmpool/model" "github.com/osmosis-labs/sqs/domain" "github.com/osmosis-labs/sqs/sqsdomain" + "github.com/osmosis-labs/sqs/sqsdomain/cosmwasmpool" ) type ( @@ -30,3 +33,7 @@ func NewRoutableCosmWasmPoolWithCustomModel( func (r *routableAlloyTransmuterPoolImpl) CheckStaticRateLimiter(tokenInCoin sdk.Coin) error { return r.checkStaticRateLimiter(tokenInCoin) } + +func CleanUpOutdatedDivision(changeLimier cosmwasmpool.ChangeLimiter, time time.Time) (*cosmwasmpool.Division, []cosmwasmpool.Division, error) { + return cleanUpOutdatedDivision(changeLimier, time) +} diff --git a/router/usecase/pools/routable_cw_alloy_transmuter_pool.go b/router/usecase/pools/routable_cw_alloy_transmuter_pool.go index a0021337a..96ab7ab06 100644 --- a/router/usecase/pools/routable_cw_alloy_transmuter_pool.go +++ b/router/usecase/pools/routable_cw_alloy_transmuter_pool.go @@ -4,11 +4,13 @@ import ( "context" "fmt" "strings" + "time" "cosmossdk.io/math" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/osmosis-labs/sqs/domain" + "github.com/osmosis-labs/sqs/rustffi" "github.com/osmosis-labs/sqs/sqsdomain/cosmwasmpool" "github.com/osmosis-labs/osmosis/osmomath" @@ -186,11 +188,14 @@ func (r *routableAlloyTransmuterPoolImpl) CalcTokenOutAmt(tokenIn sdk.Coin, toke return osmomath.BigDec{}, domain.ZeroNormalizationFactorError{Denom: tokenOutDenom, PoolId: r.GetId()} } - // Check static upper rate limiter + // Check static & change upper rate limiter // We only need to check it for the token in coin since that is the only one that is increased by the current quote. if err := r.checkStaticRateLimiter(tokenIn); err != nil { return osmomath.BigDec{}, err } + if err := r.checkChangeRateLimiter(tokenIn, time.Now()); err != nil { + return osmomath.BigDec{}, err + } tokenInAmount := osmomath.BigDecFromSDKInt(tokenIn.Amount) @@ -215,11 +220,172 @@ func (r *routableAlloyTransmuterPoolImpl) checkStaticRateLimiter(tokenInCoin sdk } // Check if the static rate limiter exists for the token in denom updated balance. - tokeInStaticLimiter, ok := r.AlloyTransmuterData.RateLimiterConfig.GetStaticLimiter(tokenInCoin.Denom) + tokenInStaticLimiter, ok := r.AlloyTransmuterData.RateLimiterConfig.GetStaticLimiter(tokenInCoin.Denom) if !ok { return nil } + weights, err := r.computeResultedWeights(tokenInCoin) + if err != nil { + return err + } + + // Validate upper limit + upperLimitInt := osmomath.MustNewDecFromStr(tokenInStaticLimiter.UpperLimit) + + // Token in weight + tokenInWeight := weights[tokenInCoin.Denom] + + // Check the upper limit + if tokenInWeight.GT(upperLimitInt) { + return domain.StaticRateLimiterInvalidUpperLimitError{ + UpperLimit: tokenInStaticLimiter.UpperLimit, + Weight: tokenInWeight.String(), + Denom: tokenInCoin.Denom, + } + } + + return nil +} + +func (r *routableAlloyTransmuterPoolImpl) checkChangeRateLimiter(tokenInCoin sdk.Coin, time time.Time) error { + tokenInChangeLimiter, ok := r.AlloyTransmuterData.RateLimiterConfig.GetChangeLimiter(tokenInCoin.Denom) + + // no error if rate limiter not found + if !ok { + return nil + } + + latestRemovedDivision, updatedDivisions, err := cleanUpOutdatedDivision(tokenInChangeLimiter, time) + if err != nil { + return err + } + + // Check for upper limit if there is any existing division or there is any removed divisions + hasAnyPrevDataPoints := latestRemovedDivision != nil || len(updatedDivisions) != 0 + + if hasAnyPrevDataPoints { + latestValue, err := osmomath.NewDecFromStr(latestRemovedDivision.LatestValue) + if err != nil { + return err + } + integral, err := osmomath.NewDecFromStr(latestRemovedDivision.Integral) + if err != nil { + return err + } + ffiLatestRemovedDivision, err := rustffi.NewFFIDivisionRaw( + latestRemovedDivision.StartedAt, latestRemovedDivision.UpdatedAt, latestValue, integral, + ) + if err != nil { + return err + } + + // TODO: Find a way to remove this interim type + type _division = struct { + StartedAt uint64 + UpdatedAt uint64 + LatestValue string + Integral string + } + + _updatedDivisions := make([]_division, len(updatedDivisions)) + for _, division := range updatedDivisions { + _updatedDivisions = append(_updatedDivisions, _division(division)) + } + + ffiUpdatedDivisions, err := rustffi.NewFFIDivisionsRaw(_updatedDivisions) + if err != nil { + return err + } + + divisionSize := tokenInChangeLimiter.WindowConfig.WindowSize / tokenInChangeLimiter.WindowConfig.DivisionCount + + avg, err := rustffi.CompressedMovingAverage(&ffiLatestRemovedDivision, ffiUpdatedDivisions, divisionSize, tokenInChangeLimiter.WindowConfig.WindowSize, uint64(time.UnixNano())) + if err != nil { + return err + } + + // Calculate upper limit using saturating addition + boundaryOffset, err := osmomath.NewDecFromStr(tokenInChangeLimiter.BoundaryOffset) + if err != nil { + return err + } + + upperLimit := avg.Add(boundaryOffset) + weights, err := r.computeResultedWeights(tokenInCoin) + if err != nil { + return err + } + + // Check if the value exceeds the upper limit + tokenInWeight := weights[tokenInCoin.Denom] + if tokenInWeight.GT(upperLimit) { + return domain.StaticRateLimiterInvalidUpperLimitError{ + UpperLimit: upperLimit.String(), + Weight: tokenInWeight.String(), + Denom: tokenInCoin.Denom, + } + } + + } + + return nil +} + +// cleanUpOutdatedDivision checks if any divisions in the change limiter is out of the interested window given a specified time +// returns (latestRemovedDivision, updatedDivisions, error) +// +// CONTRACT: Divisions must be ordered by `StartedAt` +func cleanUpOutdatedDivision(changeLimier cosmwasmpool.ChangeLimiter, time time.Time) (*cosmwasmpool.Division, []cosmwasmpool.Division, error) { + divisions := changeLimier.Divisions + windowSize := changeLimier.WindowConfig.WindowSize + divisionSize := changeLimier.WindowConfig.WindowSize / changeLimier.WindowConfig.DivisionCount + + var latestRemovedDivision *cosmwasmpool.Division + latestRemovedIndex := -1 + + for i, division := range divisions { + latestValue, err := osmomath.NewDecFromStr(division.LatestValue) + if err != nil { + return nil, []cosmwasmpool.Division{}, err + } + integral, err := osmomath.NewDecFromStr(division.Integral) + if err != nil { + return nil, []cosmwasmpool.Division{}, err + } + + ffiDivision, err := rustffi.NewFFIDivisionRaw(division.StartedAt, division.UpdatedAt, latestValue, integral) + if err != nil { + return nil, []cosmwasmpool.Division{}, err + } + isDivisionOutdated, err := rustffi.IsDivisionOutdated(ffiDivision, uint64(time.UnixNano()), windowSize, divisionSize) + if err != nil { + return nil, []cosmwasmpool.Division{}, err + } + + if isDivisionOutdated { + latestRemovedDivision = &division + latestRemovedIndex = i + } else { + break + } + } + + // no division is outdated or no division at all + if latestRemovedDivision == nil || len(divisions) == 0 { + return nil, divisions, nil + } + + // every division is outdated + if latestRemovedIndex == len(divisions)-1 { + return latestRemovedDivision, []cosmwasmpool.Division{}, nil + } + + // some division before last division is outdated + return latestRemovedDivision, divisions[latestRemovedIndex+1:], nil +} + +func (r *routableAlloyTransmuterPoolImpl) computeResultedWeights(tokenInCoin sdk.Coin) (map[string]osmomath.Dec, error) { preComputedData := r.AlloyTransmuterData.PreComputedData normalizationFactors := preComputedData.NormalizationScalingFactors @@ -246,7 +412,7 @@ func (r *routableAlloyTransmuterPoolImpl) checkStaticRateLimiter(tokenInCoin sdk normalizationScalingFactor, ok := normalizationFactors[assetDenom] if !ok { - return fmt.Errorf("normalization scaling factor not found for asset %s, pool id %d", assetDenom, r.GetId()) + return nil, fmt.Errorf("normalization scaling factor not found for asset %s, pool id %d", assetDenom, r.GetId()) } // Normalize balance @@ -275,20 +441,5 @@ func (r *routableAlloyTransmuterPoolImpl) checkStaticRateLimiter(tokenInCoin sdk weights[assetDenom] = normalizedBalances[assetDenom].ToLegacyDec().Quo(normalizeTotal.ToLegacyDec()) } - // Validate upper limit - upperLimitInt := osmomath.MustNewDecFromStr(tokeInStaticLimiter.UpperLimit) - - // Token in weight - tokenInWeight := weights[tokenInCoin.Denom] - - // Check the upper limit - if tokenInWeight.GT(upperLimitInt) { - return domain.StaticRateLimiterInvalidUpperLimitError{ - UpperLimit: tokeInStaticLimiter.UpperLimit, - Weight: tokenInWeight.String(), - Denom: tokenInCoin.Denom, - } - } - - return nil + return weights, nil } diff --git a/rustffi/transmuter.go b/rustffi/transmuter.go index 411fd7d94..aa3658d87 100644 --- a/rustffi/transmuter.go +++ b/rustffi/transmuter.go @@ -52,6 +52,33 @@ func NewFFIDivisions(divisions []struct { return ffidivisions, nil } +func NewFFIDivisionsRaw(divisions []struct { + StartedAt uint64 + UpdatedAt uint64 + LatestValue string + Integral string +}) ([]C.struct_FFIDivision, error) { + ffidivisions := make([]C.struct_FFIDivision, len(divisions)) + for i, division := range divisions { + latestValue, err := osmomath.NewDecFromStr(division.LatestValue) + if err != nil { + return nil, err + } + + integral, err := osmomath.NewDecFromStr(division.Integral) + if err != nil { + return nil, err + } + + div, err := NewFFIDivisionRaw(division.StartedAt, division.UpdatedAt, latestValue, integral) + if err != nil { + return nil, err + } + ffidivisions[i] = div + } + return ffidivisions, nil +} + func CompressedMovingAverage(latestRemovedDivision *C.struct_FFIDivision, divisions []C.struct_FFIDivision, divisionSize, windowSize, blockTime uint64) (osmomath.Dec, error) { result := C.compressed_moving_average( latestRemovedDivision, diff --git a/sqsdomain/cosmwasmpool/alloy_transmuter.go b/sqsdomain/cosmwasmpool/alloy_transmuter.go index ff4588bee..b853c1109 100644 --- a/sqsdomain/cosmwasmpool/alloy_transmuter.go +++ b/sqsdomain/cosmwasmpool/alloy_transmuter.go @@ -82,10 +82,10 @@ type WindowConfig struct { // Division represents a time division with its associated values. type Division struct { // StartedAt is the time when the division is marked as started (Unix timestamp). - StartedAt int64 `json:"started_at"` + StartedAt uint64 `json:"started_at"` // UpdatedAt is the time when the division was last updated (Unix timestamp). - UpdatedAt int64 `json:"updated_at"` + UpdatedAt uint64 `json:"updated_at"` // LatestValue is the latest value that gets updated (represented as a decimal string). LatestValue string `json:"latest_value"` From 0c8d1bda34eec1a8d84a1e154b1cd324bd8e7bbd Mon Sep 17 00:00:00 2001 From: Supanat Potiwarakorn Date: Mon, 12 Aug 2024 12:08:41 +0700 Subject: [PATCH 09/14] make sure vscode test runnable with cgo --- Makefile | 2 +- go.mod | 2 +- go.sum | 8 ++--- .../routable_cw_alloy_transmuter_pool.go | 34 +++++++++++-------- rustffi/link.go | 2 +- rustffi/transmuter.go | 28 ++++++--------- 6 files changed, 35 insertions(+), 41 deletions(-) diff --git a/Makefile b/Makefile index edf3bbd03..3c059b3ca 100644 --- a/Makefile +++ b/Makefile @@ -62,7 +62,7 @@ test-unit: @VERSION=$(VERSION) go test -mod=readonly $(PACKAGES_UNIT) build: - BUILD_TAGS=muslc LINK_STATICALLY=true GOWORK=off go build -mod=readonly \ + CGO_ENABLED=1 BUILD_TAGS=muslc LINK_STATICALLY=true GOWORK=off go build -mod=readonly \ -tags "netgo,ledger,muslc" \ -ldflags "-w -s -linkmode=external -extldflags '-Wl,-z,muldefs -static'" \ -v -o /osmosis/build/sqsd app/*.go diff --git a/go.mod b/go.mod index 33b00eedc..3288091e9 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( github.com/osmosis-labs/osmosis/osmomath v0.0.13 github.com/osmosis-labs/osmosis/osmoutils v0.0.13 github.com/osmosis-labs/osmosis/v25 v25.0.2-0.20240524131320-44f70454a543 - github.com/osmosis-labs/sqs/sqsdomain v0.18.4-0.20240805054530-4cc24d511795 + github.com/osmosis-labs/sqs/sqsdomain v0.18.4-0.20240809083137-6bbaa3418f96 github.com/prometheus/client_golang v1.19.1 github.com/sirupsen/logrus v1.9.3 github.com/spf13/viper v1.18.2 diff --git a/go.sum b/go.sum index c56863736..836ccdfeb 100644 --- a/go.sum +++ b/go.sum @@ -897,12 +897,8 @@ github.com/osmosis-labs/osmosis/x/epochs v0.0.9 h1:KKNMuoGlGv3yxmh+hF5yIqjYbxjXW github.com/osmosis-labs/osmosis/x/epochs v0.0.9/go.mod h1:jROhCibKGjWW1IyPaCFUIEJ9P25S0VawgIpWRxcqYqQ= github.com/osmosis-labs/osmosis/x/ibc-hooks v0.0.15 h1:bUBZwiMibgQWQQSqyMPqj0p54hpsDwbkCpNROWdWYJk= github.com/osmosis-labs/osmosis/x/ibc-hooks v0.0.15/go.mod h1:c72yyA6FvQNgOm/NxQuDXQfRpYy2JCJpf1o+G4kFuyM= -github.com/osmosis-labs/sqs/sqsdomain v0.18.4-0.20240728064930-64323e0ec2b6 h1:WgiKKSnQv1e5SSgz0O0wbKjtcSH4ZdYRt/Jl/ORCuU0= -github.com/osmosis-labs/sqs/sqsdomain v0.18.4-0.20240728064930-64323e0ec2b6/go.mod h1:PInk1hZ2DQ0Kd9tHSafPgXdavdyXQyNysVF4FNzo1eU= -github.com/osmosis-labs/sqs/sqsdomain v0.18.4-0.20240804234936-efe4db2eb803 h1:bSJyloY2tkj7Ikn7IOu2yW+7MbJkifKlREurnRdB1xs= -github.com/osmosis-labs/sqs/sqsdomain v0.18.4-0.20240804234936-efe4db2eb803/go.mod h1:PInk1hZ2DQ0Kd9tHSafPgXdavdyXQyNysVF4FNzo1eU= -github.com/osmosis-labs/sqs/sqsdomain v0.18.4-0.20240805054530-4cc24d511795 h1:pZvk4Q7xfk+WQ8I12OMELa/ioGrAaFyaU4xSq2Zb88A= -github.com/osmosis-labs/sqs/sqsdomain v0.18.4-0.20240805054530-4cc24d511795/go.mod h1:PInk1hZ2DQ0Kd9tHSafPgXdavdyXQyNysVF4FNzo1eU= +github.com/osmosis-labs/sqs/sqsdomain v0.18.4-0.20240809083137-6bbaa3418f96 h1:JN9nB9dC5b8YLoUVm2dkJsNR4j/cSWJD4elUy9BjImA= +github.com/osmosis-labs/sqs/sqsdomain v0.18.4-0.20240809083137-6bbaa3418f96/go.mod h1:PInk1hZ2DQ0Kd9tHSafPgXdavdyXQyNysVF4FNzo1eU= github.com/osmosis-labs/wasmd v0.45.0-osmo h1:NIp7pvJV5HuBN1HwPgEmXKQM2TjVIVdJErIHnB9IMO8= github.com/osmosis-labs/wasmd v0.45.0-osmo/go.mod h1:J6eRvwii5T1WxhetZkBg1kOJS3GTn1Bw2OLyZBb8EVU= github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM= diff --git a/router/usecase/pools/routable_cw_alloy_transmuter_pool.go b/router/usecase/pools/routable_cw_alloy_transmuter_pool.go index 96ab7ab06..1c4925340 100644 --- a/router/usecase/pools/routable_cw_alloy_transmuter_pool.go +++ b/router/usecase/pools/routable_cw_alloy_transmuter_pool.go @@ -280,22 +280,26 @@ func (r *routableAlloyTransmuterPoolImpl) checkChangeRateLimiter(tokenInCoin sdk return err } - // TODO: Find a way to remove this interim type - type _division = struct { - StartedAt uint64 - UpdatedAt uint64 - LatestValue string - Integral string - } - - _updatedDivisions := make([]_division, len(updatedDivisions)) + ffiUpdatedDivisions := make([]rustffi.FFIDivision, len(updatedDivisions)) for _, division := range updatedDivisions { - _updatedDivisions = append(_updatedDivisions, _division(division)) - } - - ffiUpdatedDivisions, err := rustffi.NewFFIDivisionsRaw(_updatedDivisions) - if err != nil { - return err + latestValue, err := osmomath.NewDecFromStr(division.LatestValue) + if err != nil { + return err + } + integral, err := osmomath.NewDecFromStr(division.Integral) + if err != nil { + return err + } + d, err := rustffi.NewFFIDivisionRaw( + division.StartedAt, + division.UpdatedAt, + latestValue, + integral, + ) + if err != nil { + return err + } + ffiUpdatedDivisions = append(ffiUpdatedDivisions, d) } divisionSize := tokenInChangeLimiter.WindowConfig.WindowSize / tokenInChangeLimiter.WindowConfig.DivisionCount diff --git a/rustffi/link.go b/rustffi/link.go index a4c5b90fb..649f35535 100644 --- a/rustffi/link.go +++ b/rustffi/link.go @@ -1,6 +1,6 @@ package rustffi /* -#cgo LDFLAGS: target/release/libsqs_ffi.a -ldl +#cgo LDFLAGS: ${SRCDIR}/../target/release/libsqs_ffi.a -ldl */ import "C" diff --git a/rustffi/transmuter.go b/rustffi/transmuter.go index aa3658d87..38e93554f 100644 --- a/rustffi/transmuter.go +++ b/rustffi/transmuter.go @@ -11,6 +11,15 @@ import ( "github.com/osmosis-labs/osmosis/osmomath" ) +type Division struct { + StartedAt uint64 + UpdatedAt uint64 + LatestValue osmomath.Dec + Integral osmomath.Dec +} + +type FFIDivision = C.struct_FFIDivision + func NewFFIDivision(startedAt, updatedAt uint64, lastestValue, prevValue osmomath.Dec) (C.struct_FFIDivision, error) { elapsedTime := updatedAt - startedAt integral := prevValue.MulInt(osmomath.NewIntFromUint64(elapsedTime)) @@ -52,25 +61,10 @@ func NewFFIDivisions(divisions []struct { return ffidivisions, nil } -func NewFFIDivisionsRaw(divisions []struct { - StartedAt uint64 - UpdatedAt uint64 - LatestValue string - Integral string -}) ([]C.struct_FFIDivision, error) { +func NewFFIDivisionsRaw(divisions []Division) ([]C.struct_FFIDivision, error) { ffidivisions := make([]C.struct_FFIDivision, len(divisions)) for i, division := range divisions { - latestValue, err := osmomath.NewDecFromStr(division.LatestValue) - if err != nil { - return nil, err - } - - integral, err := osmomath.NewDecFromStr(division.Integral) - if err != nil { - return nil, err - } - - div, err := NewFFIDivisionRaw(division.StartedAt, division.UpdatedAt, latestValue, integral) + div, err := NewFFIDivisionRaw(division.StartedAt, division.UpdatedAt, division.LatestValue, division.Integral) if err != nil { return nil, err } From 5d2a59912a4abd9fa4da5b668af0db7b9a1c3644 Mon Sep 17 00:00:00 2001 From: Supanat Potiwarakorn Date: Mon, 12 Aug 2024 12:30:11 +0700 Subject: [PATCH 10/14] extract weight compute --- Makefile | 2 +- router/usecase/pools/export_test.go | 8 ++- .../routable_cw_alloy_transmuter_pool.go | 61 +++++++++---------- .../routable_cw_alloy_transmuter_pool_test.go | 6 +- 4 files changed, 42 insertions(+), 35 deletions(-) diff --git a/Makefile b/Makefile index 3c059b3ca..edf3bbd03 100644 --- a/Makefile +++ b/Makefile @@ -62,7 +62,7 @@ test-unit: @VERSION=$(VERSION) go test -mod=readonly $(PACKAGES_UNIT) build: - CGO_ENABLED=1 BUILD_TAGS=muslc LINK_STATICALLY=true GOWORK=off go build -mod=readonly \ + BUILD_TAGS=muslc LINK_STATICALLY=true GOWORK=off go build -mod=readonly \ -tags "netgo,ledger,muslc" \ -ldflags "-w -s -linkmode=external -extldflags '-Wl,-z,muldefs -static'" \ -v -o /osmosis/build/sqsd app/*.go diff --git a/router/usecase/pools/export_test.go b/router/usecase/pools/export_test.go index accf1c6e1..7ed70ce1a 100644 --- a/router/usecase/pools/export_test.go +++ b/router/usecase/pools/export_test.go @@ -30,8 +30,12 @@ func NewRoutableCosmWasmPoolWithCustomModel( return newRoutableCosmWasmPoolWithCustomModel(pool, cosmwasmPool, cosmWasmPoolsParams, tokenOutDenom, takerFee) } -func (r *routableAlloyTransmuterPoolImpl) CheckStaticRateLimiter(tokenInCoin sdk.Coin) error { - return r.checkStaticRateLimiter(tokenInCoin) +func (r *routableAlloyTransmuterPoolImpl) CheckStaticRateLimiter(tokenInDenom string, tokenInWeight osmomath.Dec) error { + return r.checkStaticRateLimiter(tokenInDenom, tokenInWeight) +} + +func (r *routableAlloyTransmuterPoolImpl) ComputeResultedWeights(tokenInCoin sdk.Coin) (map[string]osmomath.Dec, error) { + return r.computeResultedWeights(tokenInCoin) } func CleanUpOutdatedDivision(changeLimier cosmwasmpool.ChangeLimiter, time time.Time) (*cosmwasmpool.Division, []cosmwasmpool.Division, error) { diff --git a/router/usecase/pools/routable_cw_alloy_transmuter_pool.go b/router/usecase/pools/routable_cw_alloy_transmuter_pool.go index 1c4925340..d76acb25c 100644 --- a/router/usecase/pools/routable_cw_alloy_transmuter_pool.go +++ b/router/usecase/pools/routable_cw_alloy_transmuter_pool.go @@ -188,13 +188,30 @@ func (r *routableAlloyTransmuterPoolImpl) CalcTokenOutAmt(tokenIn sdk.Coin, toke return osmomath.BigDec{}, domain.ZeroNormalizationFactorError{Denom: tokenOutDenom, PoolId: r.GetId()} } - // Check static & change upper rate limiter - // We only need to check it for the token in coin since that is the only one that is increased by the current quote. - if err := r.checkStaticRateLimiter(tokenIn); err != nil { - return osmomath.BigDec{}, err - } - if err := r.checkChangeRateLimiter(tokenIn, time.Now()); err != nil { - return osmomath.BigDec{}, err + staticLimiterExists := len(r.AlloyTransmuterData.RateLimiterConfig.StaticLimiterByDenomMap) != 0 + changeLimiterExists := len(r.AlloyTransmuterData.RateLimiterConfig.ChangeLimiterByDenomMap) != 0 + + if staticLimiterExists || changeLimiterExists { + weights, err := r.computeResultedWeights(tokenIn) + if err != nil { + return osmomath.BigDec{}, err + } + + tokenInWeight := weights[tokenIn.Denom] + + // Check static & change upper rate limiter + // We only need to check it for the token in coin since that is the only one that is increased by the current quote. + if staticLimiterExists { + if err := r.checkStaticRateLimiter(tokenIn.Denom, tokenInWeight); err != nil { + return osmomath.BigDec{}, err + } + } + + if changeLimiterExists { + if err := r.checkChangeRateLimiter(tokenIn.Denom, tokenInWeight, time.Now()); err != nil { + return osmomath.BigDec{}, err + } + } } tokenInAmount := osmomath.BigDecFromSDKInt(tokenIn.Amount) @@ -213,43 +230,30 @@ func (r *routableAlloyTransmuterPoolImpl) CalcTokenOutAmt(tokenIn sdk.Coin, toke // No-op if the static rate limiter is not set. // Returns error if the token in weight is greater than the upper limit. // Returns nil if the token in weight is less than or equal to the upper limit. -func (r *routableAlloyTransmuterPoolImpl) checkStaticRateLimiter(tokenInCoin sdk.Coin) error { - // If no static rate limiter is set, return - if len(r.AlloyTransmuterData.RateLimiterConfig.StaticLimiterByDenomMap) == 0 { - return nil - } - +func (r *routableAlloyTransmuterPoolImpl) checkStaticRateLimiter(tokenInDenom string, tokenInWeight osmomath.Dec) error { // Check if the static rate limiter exists for the token in denom updated balance. - tokenInStaticLimiter, ok := r.AlloyTransmuterData.RateLimiterConfig.GetStaticLimiter(tokenInCoin.Denom) + tokenInStaticLimiter, ok := r.AlloyTransmuterData.RateLimiterConfig.GetStaticLimiter(tokenInDenom) if !ok { return nil } - weights, err := r.computeResultedWeights(tokenInCoin) - if err != nil { - return err - } - // Validate upper limit upperLimitInt := osmomath.MustNewDecFromStr(tokenInStaticLimiter.UpperLimit) - // Token in weight - tokenInWeight := weights[tokenInCoin.Denom] - // Check the upper limit if tokenInWeight.GT(upperLimitInt) { return domain.StaticRateLimiterInvalidUpperLimitError{ UpperLimit: tokenInStaticLimiter.UpperLimit, Weight: tokenInWeight.String(), - Denom: tokenInCoin.Denom, + Denom: tokenInDenom, } } return nil } -func (r *routableAlloyTransmuterPoolImpl) checkChangeRateLimiter(tokenInCoin sdk.Coin, time time.Time) error { - tokenInChangeLimiter, ok := r.AlloyTransmuterData.RateLimiterConfig.GetChangeLimiter(tokenInCoin.Denom) +func (r *routableAlloyTransmuterPoolImpl) checkChangeRateLimiter(tokenInDenom string, tokenInWeight osmomath.Dec, time time.Time) error { + tokenInChangeLimiter, ok := r.AlloyTransmuterData.RateLimiterConfig.GetChangeLimiter(tokenInDenom) // no error if rate limiter not found if !ok { @@ -316,18 +320,13 @@ func (r *routableAlloyTransmuterPoolImpl) checkChangeRateLimiter(tokenInCoin sdk } upperLimit := avg.Add(boundaryOffset) - weights, err := r.computeResultedWeights(tokenInCoin) - if err != nil { - return err - } // Check if the value exceeds the upper limit - tokenInWeight := weights[tokenInCoin.Denom] if tokenInWeight.GT(upperLimit) { return domain.StaticRateLimiterInvalidUpperLimitError{ UpperLimit: upperLimit.String(), Weight: tokenInWeight.String(), - Denom: tokenInCoin.Denom, + Denom: tokenInDenom, } } diff --git a/router/usecase/pools/routable_cw_alloy_transmuter_pool_test.go b/router/usecase/pools/routable_cw_alloy_transmuter_pool_test.go index f321ffcc8..bae0682f1 100644 --- a/router/usecase/pools/routable_cw_alloy_transmuter_pool_test.go +++ b/router/usecase/pools/routable_cw_alloy_transmuter_pool_test.go @@ -410,8 +410,12 @@ func (s *RoutablePoolTestSuite) TestCheckStaticRateLimiter() { r := routablePool.(*pools.RoutableAlloyTransmuterPoolImpl) + weights, err := r.ComputeResultedWeights(tc.tokenInCoin) + s.Require().NoError(err) + tokenInWeight := weights[tc.tokenInCoin.Denom] + // System under test - err := r.CheckStaticRateLimiter(tc.tokenInCoin) + err = r.CheckStaticRateLimiter(tc.tokenInCoin.Denom, tokenInWeight) if tc.expectError != nil { s.Require().Error(err) From c6584f07c463c096b5153849a1b32698cff59276 Mon Sep 17 00:00:00 2001 From: Supanat Potiwarakorn Date: Mon, 12 Aug 2024 12:54:03 +0700 Subject: [PATCH 11/14] add test for cleanUpOutdatedDivision --- .../routable_cw_alloy_transmuter_pool.go | 3 +- .../routable_cw_alloy_transmuter_pool_test.go | 83 +++++++++++++++++++ 2 files changed, 85 insertions(+), 1 deletion(-) diff --git a/router/usecase/pools/routable_cw_alloy_transmuter_pool.go b/router/usecase/pools/routable_cw_alloy_transmuter_pool.go index d76acb25c..9a53c43c4 100644 --- a/router/usecase/pools/routable_cw_alloy_transmuter_pool.go +++ b/router/usecase/pools/routable_cw_alloy_transmuter_pool.go @@ -367,7 +367,8 @@ func cleanUpOutdatedDivision(changeLimier cosmwasmpool.ChangeLimiter, time time. } if isDivisionOutdated { - latestRemovedDivision = &division + divisionCpy := division // copy division to avoid pointer issue as division will be modified + latestRemovedDivision = &divisionCpy latestRemovedIndex = i } else { break diff --git a/router/usecase/pools/routable_cw_alloy_transmuter_pool_test.go b/router/usecase/pools/routable_cw_alloy_transmuter_pool_test.go index bae0682f1..4a8392aa3 100644 --- a/router/usecase/pools/routable_cw_alloy_transmuter_pool_test.go +++ b/router/usecase/pools/routable_cw_alloy_transmuter_pool_test.go @@ -2,6 +2,7 @@ package pools_test import ( "context" + "time" sdk "github.com/cosmos/cosmos-sdk/types" @@ -426,3 +427,85 @@ func (s *RoutablePoolTestSuite) TestCheckStaticRateLimiter() { }) } } + +func (s *RoutablePoolTestSuite) TestCleanUpOutdatedDivision() { + testCases := []struct { + name string + changeLimiter cosmwasmpool.ChangeLimiter + currentTime time.Time + expectedRemoved *cosmwasmpool.Division + expectedUpdated []cosmwasmpool.Division + expectError bool + }{ + { + name: "No outdated divisions", + changeLimiter: cosmwasmpool.ChangeLimiter{ + WindowConfig: cosmwasmpool.WindowConfig{ + WindowSize: 100, + DivisionCount: 2, + }, + Divisions: []cosmwasmpool.Division{ + {StartedAt: 50, UpdatedAt: 75, LatestValue: "1.0", Integral: "25.0"}, + {StartedAt: 75, UpdatedAt: 100, LatestValue: "2.0", Integral: "50.0"}, + }, + }, + currentTime: time.Unix(0, 110), + expectedRemoved: nil, + expectedUpdated: []cosmwasmpool.Division{ + {StartedAt: 50, UpdatedAt: 75, LatestValue: "1.0", Integral: "25.0"}, + {StartedAt: 75, UpdatedAt: 100, LatestValue: "2.0", Integral: "50.0"}, + }, + expectError: false, + }, + { + name: "One outdated division", + changeLimiter: cosmwasmpool.ChangeLimiter{ + WindowConfig: cosmwasmpool.WindowConfig{ + WindowSize: 100, + DivisionCount: 2, + }, + Divisions: []cosmwasmpool.Division{ + {StartedAt: 0, UpdatedAt: 50, LatestValue: "1.0", Integral: "50.0"}, + {StartedAt: 50, UpdatedAt: 51, LatestValue: "2.0", Integral: "100.0"}, + }, + }, + currentTime: time.Unix(0, 150), + expectedRemoved: &cosmwasmpool.Division{StartedAt: 0, UpdatedAt: 50, LatestValue: "1.0", Integral: "50.0"}, + expectedUpdated: []cosmwasmpool.Division{ + {StartedAt: 50, UpdatedAt: 51, LatestValue: "2.0", Integral: "100.0"}, + }, + expectError: false, + }, + { + name: "All divisions outdated", + changeLimiter: cosmwasmpool.ChangeLimiter{ + WindowConfig: cosmwasmpool.WindowConfig{ + WindowSize: 100, + DivisionCount: 2, + }, + Divisions: []cosmwasmpool.Division{ + {StartedAt: 0, UpdatedAt: 25, LatestValue: "1.0", Integral: "25.0"}, + {StartedAt: 50, UpdatedAt: 51, LatestValue: "2.0", Integral: "50.0"}, + }, + }, + currentTime: time.Unix(0, 200), + expectedRemoved: &cosmwasmpool.Division{StartedAt: 50, UpdatedAt: 51, LatestValue: "2.0", Integral: "50.0"}, + expectedUpdated: []cosmwasmpool.Division{}, + expectError: false, + }, + } + + for _, tc := range testCases { + s.Run(tc.name, func() { + removed, updated, err := pools.CleanUpOutdatedDivision(tc.changeLimiter, tc.currentTime) + + if tc.expectError { + s.Require().Error(err) + } else { + s.Require().NoError(err) + s.Require().Equal(tc.expectedRemoved, removed) + s.Require().Equal(tc.expectedUpdated, updated) + } + }) + } +} From be94bd1c549e06643fa95226a50b52f98e0f2b9e Mon Sep 17 00:00:00 2001 From: Supanat Potiwarakorn Date: Mon, 12 Aug 2024 13:26:40 +0700 Subject: [PATCH 12/14] add test for change limiter checks --- router/usecase/pools/export_test.go | 4 + .../routable_cw_alloy_transmuter_pool.go | 32 ++--- .../routable_cw_alloy_transmuter_pool_test.go | 111 ++++++++++++++++++ 3 files changed, 133 insertions(+), 14 deletions(-) diff --git a/router/usecase/pools/export_test.go b/router/usecase/pools/export_test.go index 7ed70ce1a..dfcea6619 100644 --- a/router/usecase/pools/export_test.go +++ b/router/usecase/pools/export_test.go @@ -34,6 +34,10 @@ func (r *routableAlloyTransmuterPoolImpl) CheckStaticRateLimiter(tokenInDenom st return r.checkStaticRateLimiter(tokenInDenom, tokenInWeight) } +func (r *routableAlloyTransmuterPoolImpl) CheckChangeRateLimiter(tokenInDenom string, tokenInWeight osmomath.Dec, currentTime time.Time) error { + return r.checkChangeRateLimiter(tokenInDenom, tokenInWeight, currentTime) +} + func (r *routableAlloyTransmuterPoolImpl) ComputeResultedWeights(tokenInCoin sdk.Coin) (map[string]osmomath.Dec, error) { return r.computeResultedWeights(tokenInCoin) } diff --git a/router/usecase/pools/routable_cw_alloy_transmuter_pool.go b/router/usecase/pools/routable_cw_alloy_transmuter_pool.go index 9a53c43c4..42bd2228f 100644 --- a/router/usecase/pools/routable_cw_alloy_transmuter_pool.go +++ b/router/usecase/pools/routable_cw_alloy_transmuter_pool.go @@ -269,19 +269,23 @@ func (r *routableAlloyTransmuterPoolImpl) checkChangeRateLimiter(tokenInDenom st hasAnyPrevDataPoints := latestRemovedDivision != nil || len(updatedDivisions) != 0 if hasAnyPrevDataPoints { - latestValue, err := osmomath.NewDecFromStr(latestRemovedDivision.LatestValue) - if err != nil { - return err - } - integral, err := osmomath.NewDecFromStr(latestRemovedDivision.Integral) - if err != nil { - return err - } - ffiLatestRemovedDivision, err := rustffi.NewFFIDivisionRaw( - latestRemovedDivision.StartedAt, latestRemovedDivision.UpdatedAt, latestValue, integral, - ) - if err != nil { - return err + var ffiLatestRemovedDivisionPtr *rustffi.FFIDivision + if latestRemovedDivision != nil { + latestValue, err := osmomath.NewDecFromStr(latestRemovedDivision.LatestValue) + if err != nil { + return err + } + integral, err := osmomath.NewDecFromStr(latestRemovedDivision.Integral) + if err != nil { + return err + } + ffiLatestRemovedDivision, err := rustffi.NewFFIDivisionRaw( + latestRemovedDivision.StartedAt, latestRemovedDivision.UpdatedAt, latestValue, integral, + ) + if err != nil { + return err + } + ffiLatestRemovedDivisionPtr = &ffiLatestRemovedDivision } ffiUpdatedDivisions := make([]rustffi.FFIDivision, len(updatedDivisions)) @@ -308,7 +312,7 @@ func (r *routableAlloyTransmuterPoolImpl) checkChangeRateLimiter(tokenInDenom st divisionSize := tokenInChangeLimiter.WindowConfig.WindowSize / tokenInChangeLimiter.WindowConfig.DivisionCount - avg, err := rustffi.CompressedMovingAverage(&ffiLatestRemovedDivision, ffiUpdatedDivisions, divisionSize, tokenInChangeLimiter.WindowConfig.WindowSize, uint64(time.UnixNano())) + avg, err := rustffi.CompressedMovingAverage(ffiLatestRemovedDivisionPtr, ffiUpdatedDivisions, divisionSize, tokenInChangeLimiter.WindowConfig.WindowSize, uint64(time.UnixNano())) if err != nil { return err } diff --git a/router/usecase/pools/routable_cw_alloy_transmuter_pool_test.go b/router/usecase/pools/routable_cw_alloy_transmuter_pool_test.go index 4a8392aa3..cc4462fc9 100644 --- a/router/usecase/pools/routable_cw_alloy_transmuter_pool_test.go +++ b/router/usecase/pools/routable_cw_alloy_transmuter_pool_test.go @@ -509,3 +509,114 @@ func (s *RoutablePoolTestSuite) TestCleanUpOutdatedDivision() { }) } } + +func (s *RoutablePoolTestSuite) TestCheckChangeRateLimiter() { + testCases := []struct { + name string + changeLimiter cosmwasmpool.ChangeLimiter + tokenInDenom string + tokenInWeight osmomath.Dec + currentTime time.Time + expectedError error + }{ + { + name: "No clean up outdated - within limit", + changeLimiter: cosmwasmpool.ChangeLimiter{ + WindowConfig: cosmwasmpool.WindowConfig{ + WindowSize: 100, + DivisionCount: 2, + }, + BoundaryOffset: "0.05", + Divisions: []cosmwasmpool.Division{ + {StartedAt: 100, UpdatedAt: 20, LatestValue: "0.5", Integral: "0"}, + }, + }, + tokenInDenom: "denoma", + tokenInWeight: osmomath.MustNewDecFromStr("0.55"), + currentTime: time.Unix(0, 121), + expectedError: nil, + }, + { + name: "No clean up outdated - exceeds limit", + changeLimiter: cosmwasmpool.ChangeLimiter{ + WindowConfig: cosmwasmpool.WindowConfig{ + WindowSize: 100, + DivisionCount: 2, + }, + BoundaryOffset: "0.05", + Divisions: []cosmwasmpool.Division{ + {StartedAt: 100, UpdatedAt: 20, LatestValue: "0.5", Integral: "0"}, + }, + }, + tokenInDenom: "denoma", + tokenInWeight: osmomath.MustNewDecFromStr("0.555000000000000001"), + currentTime: time.Unix(0, 121), + expectedError: domain.StaticRateLimiterInvalidUpperLimitError{ + UpperLimit: "0.555000000000000000", + Weight: "0.555000000000000001", + Denom: "denoma", + }, + }, + { + name: "With clean up outdated - within limit", + changeLimiter: cosmwasmpool.ChangeLimiter{ + WindowConfig: cosmwasmpool.WindowConfig{ + WindowSize: 100, + DivisionCount: 4, + }, + BoundaryOffset: "0.05", + Divisions: []cosmwasmpool.Division{ + {StartedAt: 0, UpdatedAt: 10, LatestValue: "0.4", Integral: "0"}, + }, + }, + tokenInDenom: "denomb", + tokenInWeight: osmomath.MustNewDecFromStr("0.45"), + currentTime: time.Unix(0, 125), + expectedError: nil, + }, + { + name: "With clean up outdated - exceeds limit", + changeLimiter: cosmwasmpool.ChangeLimiter{ + WindowConfig: cosmwasmpool.WindowConfig{ + WindowSize: 100, + DivisionCount: 4, + }, + BoundaryOffset: "0.05", + Divisions: []cosmwasmpool.Division{ + {StartedAt: 0, UpdatedAt: 10, LatestValue: "0.4", Integral: "0"}, + }, + }, + tokenInDenom: "denomb", + tokenInWeight: osmomath.MustNewDecFromStr("0.450000000000000001"), + currentTime: time.Unix(0, 125), + expectedError: domain.StaticRateLimiterInvalidUpperLimitError{ + UpperLimit: "0.450000000000000000", + Weight: "0.450000000000000001", + Denom: "denomb", + }, + }, + } + + for _, tc := range testCases { + s.Run(tc.name, func() { + pool := &pools.RoutableAlloyTransmuterPoolImpl{ + AlloyTransmuterData: &cosmwasmpool.AlloyTransmuterData{ + RateLimiterConfig: cosmwasmpool.AlloyedRateLimiter{ + ChangeLimiterByDenomMap: map[string]cosmwasmpool.ChangeLimiter{ + tc.tokenInDenom: tc.changeLimiter, + }, + }, + }, + } + + err := pool.CheckChangeRateLimiter(tc.tokenInDenom, tc.tokenInWeight, tc.currentTime) + + if tc.expectedError != nil { + s.Require().Error(err) + s.Require().Equal(tc.expectedError, err) + } else { + s.Require().NoError(err) + } + }) + } +} From 7669c7818b9d62c9b4951b39eb39cf7beabbe992 Mon Sep 17 00:00:00 2001 From: Supanat Potiwarakorn Date: Mon, 12 Aug 2024 13:37:13 +0700 Subject: [PATCH 13/14] use crate.io version of transmuter math --- Cargo.lock | 4 +++- rustffi/rustsrc/Cargo.toml | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4734ddef9..3108f1d2a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -850,7 +850,9 @@ dependencies = [ [[package]] name = "transmuter_math" -version = "0.1.0" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af2780103282f99c1750878bdac2c85e9cd070d2a6e0f38fdb0aff978437a985" dependencies = [ "cosmwasm-schema", "cosmwasm-std", diff --git a/rustffi/rustsrc/Cargo.toml b/rustffi/rustsrc/Cargo.toml index b9eda851c..a2bf2b1d3 100644 --- a/rustffi/rustsrc/Cargo.toml +++ b/rustffi/rustsrc/Cargo.toml @@ -9,7 +9,7 @@ crate-type = ["staticlib"] [dependencies] thiserror = "1.0.63" -transmuter_math = {path = "../../../transmuter/packages/transmuter_math"} +transmuter_math = "1.0.0" [build-dependencies] cbindgen = "0.26.0" From 64698b034d88a47f5a4feb3e801c565546245575 Mon Sep 17 00:00:00 2001 From: Supanat Potiwarakorn Date: Tue, 13 Aug 2024 09:41:18 +0700 Subject: [PATCH 14/14] fix typo --- router/usecase/pools/export_test.go | 4 ++-- router/usecase/pools/routable_cw_alloy_transmuter_pool.go | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/router/usecase/pools/export_test.go b/router/usecase/pools/export_test.go index dfcea6619..f935005fd 100644 --- a/router/usecase/pools/export_test.go +++ b/router/usecase/pools/export_test.go @@ -42,6 +42,6 @@ func (r *routableAlloyTransmuterPoolImpl) ComputeResultedWeights(tokenInCoin sdk return r.computeResultedWeights(tokenInCoin) } -func CleanUpOutdatedDivision(changeLimier cosmwasmpool.ChangeLimiter, time time.Time) (*cosmwasmpool.Division, []cosmwasmpool.Division, error) { - return cleanUpOutdatedDivision(changeLimier, time) +func CleanUpOutdatedDivision(changeLimiter cosmwasmpool.ChangeLimiter, time time.Time) (*cosmwasmpool.Division, []cosmwasmpool.Division, error) { + return cleanUpOutdatedDivision(changeLimiter, time) } diff --git a/router/usecase/pools/routable_cw_alloy_transmuter_pool.go b/router/usecase/pools/routable_cw_alloy_transmuter_pool.go index 42bd2228f..328122eb3 100644 --- a/router/usecase/pools/routable_cw_alloy_transmuter_pool.go +++ b/router/usecase/pools/routable_cw_alloy_transmuter_pool.go @@ -343,10 +343,10 @@ func (r *routableAlloyTransmuterPoolImpl) checkChangeRateLimiter(tokenInDenom st // returns (latestRemovedDivision, updatedDivisions, error) // // CONTRACT: Divisions must be ordered by `StartedAt` -func cleanUpOutdatedDivision(changeLimier cosmwasmpool.ChangeLimiter, time time.Time) (*cosmwasmpool.Division, []cosmwasmpool.Division, error) { - divisions := changeLimier.Divisions - windowSize := changeLimier.WindowConfig.WindowSize - divisionSize := changeLimier.WindowConfig.WindowSize / changeLimier.WindowConfig.DivisionCount +func cleanUpOutdatedDivision(changeLimiter cosmwasmpool.ChangeLimiter, time time.Time) (*cosmwasmpool.Division, []cosmwasmpool.Division, error) { + divisions := changeLimiter.Divisions + windowSize := changeLimiter.WindowConfig.WindowSize + divisionSize := changeLimiter.WindowConfig.WindowSize / changeLimiter.WindowConfig.DivisionCount var latestRemovedDivision *cosmwasmpool.Division latestRemovedIndex := -1