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..3108f1d2a --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,1003 @@ +# This file is automatically @generated by Cargo. +# 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" +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 = "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" +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 = "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" +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 = "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" +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 = "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" +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 = "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" +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 = "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" +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 = "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" +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 = "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" +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 = "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 = [ + "dyn-clone", + "schemars_derive", + "serde", + "serde_json", +] + +[[package]] +name = "schemars_derive" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +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" +version = "1.0.204" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc76f558e0cbb2a839d37354c575f1dc3fdc6546b5be373ba43d95f231bf7c12" +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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0cd7e117be63d3c3678776753929474f3b04a43a080c744d6b0ae2a8c28e222" +dependencies = [ + "proc-macro2", + "quote", + "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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "784b6203951c57ff748476b126ccb5e8e2959a5c19e5c617ab1956be3dbc68da" +dependencies = [ + "itoa", + "memchr", + "ryu", + "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" +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 = "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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" +dependencies = [ + "serde", +] + +[[package]] +name = "transmuter_math" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af2780103282f99c1750878bdac2c85e9cd070d2a6e0f38fdb0aff978437a985" +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" +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" + +[[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 new file mode 100644 index 000000000..6ff366ef2 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,5 @@ +[workspace] +members = [ + "rustffi/rustsrc", +] +resolver = "2" diff --git a/Makefile b/Makefile index 1c5f28064..edf3bbd03 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 sqs_ffi + ############################################################################### ### Docker ### ############################################################################### 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/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/export_test.go b/router/usecase/pools/export_test.go index 6ce5c4040..f935005fd 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 ( @@ -27,6 +30,18 @@ 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) 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) +} + +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 a0021337a..328122eb3 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,10 +188,30 @@ func (r *routableAlloyTransmuterPoolImpl) CalcTokenOutAmt(tokenIn sdk.Coin, toke return osmomath.BigDec{}, domain.ZeroNormalizationFactorError{Denom: tokenOutDenom, PoolId: r.GetId()} } - // Check static 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 + 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) @@ -208,18 +230,170 @@ 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 { +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(tokenInDenom) + if !ok { return nil } - // Check if the static rate limiter exists for the token in denom updated balance. - tokeInStaticLimiter, ok := r.AlloyTransmuterData.RateLimiterConfig.GetStaticLimiter(tokenInCoin.Denom) + // Validate upper limit + upperLimitInt := osmomath.MustNewDecFromStr(tokenInStaticLimiter.UpperLimit) + + // Check the upper limit + if tokenInWeight.GT(upperLimitInt) { + return domain.StaticRateLimiterInvalidUpperLimitError{ + UpperLimit: tokenInStaticLimiter.UpperLimit, + Weight: tokenInWeight.String(), + Denom: tokenInDenom, + } + } + + return nil +} + +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 { 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 { + 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)) + for _, division := range updatedDivisions { + 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 + + avg, err := rustffi.CompressedMovingAverage(ffiLatestRemovedDivisionPtr, 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) + + // Check if the value exceeds the upper limit + if tokenInWeight.GT(upperLimit) { + return domain.StaticRateLimiterInvalidUpperLimitError{ + UpperLimit: upperLimit.String(), + Weight: tokenInWeight.String(), + Denom: tokenInDenom, + } + } + + } + + 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(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 + + 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 { + divisionCpy := division // copy division to avoid pointer issue as division will be modified + latestRemovedDivision = &divisionCpy + 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 +420,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 +449,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/router/usecase/pools/routable_cw_alloy_transmuter_pool_test.go b/router/usecase/pools/routable_cw_alloy_transmuter_pool_test.go index f321ffcc8..cc4462fc9 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" @@ -410,8 +411,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) @@ -422,3 +427,196 @@ 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) + } + }) + } +} + +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) + } + }) + } +} diff --git a/rustffi/link.go b/rustffi/link.go new file mode 100644 index 000000000..649f35535 --- /dev/null +++ b/rustffi/link.go @@ -0,0 +1,6 @@ +package rustffi + +/* +#cgo LDFLAGS: ${SRCDIR}/../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..b387a9304 --- /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" + "github.com/stretchr/testify/require" +) + +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 { + require.NoError(t, err) + } else { + require.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 { + require.NoError(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 { + require.ErrorContains(t, err, tc.expectedError.Error()) + } + }) + } +} diff --git a/rustffi/rustsrc/Cargo.toml b/rustffi/rustsrc/Cargo.toml new file mode 100644 index 000000000..a2bf2b1d3 --- /dev/null +++ b/rustffi/rustsrc/Cargo.toml @@ -0,0 +1,15 @@ +[package] +edition = "2021" +name = "sqs_ffi" +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] +thiserror = "1.0.63" +transmuter_math = "1.0.0" + +[build-dependencies] +cbindgen = "0.26.0" diff --git a/rustffi/rustsrc/build.rs b/rustffi/rustsrc/build.rs new file mode 100644 index 000000000..993e49a8e --- /dev/null +++ b/rustffi/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.language = cbindgen::Language::C; + + cbindgen::Builder::new() + .with_crate(crate_dir) + .with_config(conf) + .generate() + .expect("Unable to generate bindings") + .write_to_file(write_dest); +} diff --git a/rustffi/rustsrc/src/lib.rs b/rustffi/rustsrc/src/lib.rs new file mode 100644 index 000000000..2f3152c0a --- /dev/null +++ b/rustffi/rustsrc/src/lib.rs @@ -0,0 +1,5 @@ +mod numbers; +mod option; +mod result; +mod slice; +mod transmuter; diff --git a/rustffi/rustsrc/src/numbers.rs b/rustffi/rustsrc/src/numbers.rs new file mode 100644 index 000000000..d03c3583a --- /dev/null +++ b/rustffi/rustsrc/src/numbers.rs @@ -0,0 +1,95 @@ +/// FFI-safe little endian u128 construction +#[repr(C)] +#[derive(Clone)] +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(Clone)] +pub struct FFIDecimal(FFIU128); + +impl From for transmuter_math::Decimal { + fn from(value: FFIDecimal) -> Self { + transmuter_math::Decimal::raw(value.0.into()) + } +} + +impl From for FFIDecimal { + fn from(value: transmuter_math::Decimal) -> Self { + FFIDecimal(value.atomics().u128().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); + } + + #[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/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/result.rs b/rustffi/rustsrc/src/result.rs new file mode 100644 index 000000000..93b6f0de9 --- /dev/null +++ b/rustffi/rustsrc/src/result.rs @@ -0,0 +1,35 @@ +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(), + } + } +} + +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/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 new file mode 100644 index 000000000..894858660 --- /dev/null +++ b/rustffi/rustsrc/src/transmuter.rs @@ -0,0 +1,76 @@ +use crate::{ + numbers::FFIDecimal, option::nullable_ptr_to_option, result::FFIResult, slice::FFISlice, +}; +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, + + /// 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(), + ) + } +} + +#[no_mangle] +pub extern "C" fn compressed_moving_average( + latest_removed_division: *const FFIDivision, + divisions: FFISlice, + division_size: u64, + window_size: u64, + block_time: u64, // timestamp nanos +) -> FFIResult { + 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()), + 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), + } +} + +#[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 new file mode 100644 index 000000000..38e93554f --- /dev/null +++ b/rustffi/transmuter.go @@ -0,0 +1,127 @@ +package rustffi + +/* +#include "../target/release/libsqs_ffi.h" +*/ +import "C" +import ( + "errors" + "unsafe" + + "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)) + 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 + } + 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 +} + +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 NewFFIDivisionsRaw(divisions []Division) ([]C.struct_FFIDivision, error) { + ffidivisions := make([]C.struct_FFIDivision, len(divisions)) + for i, division := range divisions { + div, err := NewFFIDivisionRaw(division.StartedAt, division.UpdatedAt, division.LatestValue, division.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, + newFFIDivisionSlice(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 +} + +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 { + divisionsPtr = &divisions[0] + } + return C.struct_FFISlice_FFIDivision{ + ptr: divisionsPtr, + len: C.uintptr_t(len(divisions)), + } +} diff --git a/rustffi/transmuter_test.go b/rustffi/transmuter_test.go new file mode 100644 index 000000000..959e52e54 --- /dev/null +++ b/rustffi/transmuter_test.go @@ -0,0 +1,205 @@ +package rustffi + +import ( + "errors" + "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) + }) +} + +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) + }) + } +} 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"`