diff --git a/Cargo.lock b/Cargo.lock index 360b218759..f3414e52bf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -110,6 +110,42 @@ version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" +[[package]] +name = "alloy-primitives" +version = "0.8.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eacedba97e65cdc7ab592f2b22ef5d3ab8d60b2056bc3a6e6363577e8270ec6f" +dependencies = [ + "alloy-rlp", + "bytes", + "cfg-if", + "const-hex", + "derive_more 2.0.1", + "foldhash", + "indexmap 2.7.1", + "itoa", + "k256", + "keccak-asm", + "paste", + "proptest", + "rand", + "ruint", + "rustc-hash 2.1.0", + "serde", + "sha3", + "tiny-keccak", +] + +[[package]] +name = "alloy-rlp" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6c1d995bff8d011f7cd6c81820d51825e6e06d6db73914c1630ecf544d83d6" +dependencies = [ + "arrayvec", + "bytes", +] + [[package]] name = "android-tzdata" version = "0.1.1" @@ -211,8 +247,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fb00293ba84f51ce3bd026bd0de55899c4e68f0a39a5728cebae3a73ffdc0a4f" dependencies = [ "ark-ec", - "ark-ff", - "ark-std", + "ark-ff 0.4.2", + "ark-std 0.4.0", ] [[package]] @@ -224,7 +260,7 @@ dependencies = [ "ark-bls12-377", "ark-ec", "ark-models-ext", - "ark-std", + "ark-std 0.4.0", ] [[package]] @@ -234,9 +270,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c775f0d12169cba7aae4caeb547bb6a50781c7449a8aa53793827c9ec4abf488" dependencies = [ "ark-ec", - "ark-ff", - "ark-serialize", - "ark-std", + "ark-ff 0.4.2", + "ark-serialize 0.4.2", + "ark-std 0.4.0", ] [[package]] @@ -247,10 +283,10 @@ checksum = "b1dc4b3d08f19e8ec06e949712f95b8361e43f1391d94f65e4234df03480631c" dependencies = [ "ark-bls12-381", "ark-ec", - "ark-ff", + "ark-ff 0.4.2", "ark-models-ext", - "ark-serialize", - "ark-std", + "ark-serialize 0.4.2", + "ark-std 0.4.0", ] [[package]] @@ -261,8 +297,8 @@ checksum = "2e0605daf0cc5aa2034b78d008aaf159f56901d92a52ee4f6ecdfdac4f426700" dependencies = [ "ark-bls12-377", "ark-ec", - "ark-ff", - "ark-std", + "ark-ff 0.4.2", + "ark-std 0.4.0", ] [[package]] @@ -273,9 +309,9 @@ checksum = "ccee5fba47266f460067588ee1bf070a9c760bf2050c1c509982c5719aadb4f2" dependencies = [ "ark-bw6-761", "ark-ec", - "ark-ff", + "ark-ff 0.4.2", "ark-models-ext", - "ark-std", + "ark-std 0.4.0", ] [[package]] @@ -285,12 +321,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f3a13b34da09176a8baba701233fdffbaa7c1b1192ce031a3da4e55ce1f1a56" dependencies = [ "ark-ec", - "ark-ff", + "ark-ff 0.4.2", "ark-r1cs-std", "ark-relations", - "ark-serialize", + "ark-serialize 0.4.2", "ark-snark", - "ark-std", + "ark-std 0.4.0", "blake2 0.10.6", "derivative", "digest 0.10.7", @@ -304,10 +340,10 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "defd9a439d56ac24968cca0571f598a61bc8c55f71d50a89cda591cb750670ba" dependencies = [ - "ark-ff", + "ark-ff 0.4.2", "ark-poly", - "ark-serialize", - "ark-std", + "ark-serialize 0.4.2", + "ark-std 0.4.0", "derivative", "hashbrown 0.13.2", "itertools 0.10.5", @@ -324,8 +360,8 @@ checksum = "b10d901b9ac4b38f9c32beacedfadcdd64e46f8d7f8e88c1ae1060022cf6f6c6" dependencies = [ "ark-bls12-377", "ark-ec", - "ark-ff", - "ark-std", + "ark-ff 0.4.2", + "ark-std 0.4.0", ] [[package]] @@ -336,9 +372,9 @@ checksum = "524a4fb7540df2e1a8c2e67a83ba1d1e6c3947f4f9342cc2359fc2e789ad731d" dependencies = [ "ark-ec", "ark-ed-on-bls12-377", - "ark-ff", + "ark-ff 0.4.2", "ark-models-ext", - "ark-std", + "ark-std 0.4.0", ] [[package]] @@ -349,8 +385,8 @@ checksum = "f9cde0f2aa063a2a5c28d39b47761aa102bda7c13c84fc118a61b87c7b2f785c" dependencies = [ "ark-bls12-381", "ark-ec", - "ark-ff", - "ark-std", + "ark-ff 0.4.2", + "ark-std 0.4.0", ] [[package]] @@ -361,9 +397,27 @@ checksum = "d15185f1acb49a07ff8cbe5f11a1adc5a93b19e211e325d826ae98e98e124346" dependencies = [ "ark-ec", "ark-ed-on-bls12-381-bandersnatch", - "ark-ff", + "ark-ff 0.4.2", "ark-models-ext", - "ark-std", + "ark-std 0.4.0", +] + +[[package]] +name = "ark-ff" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b3235cc41ee7a12aaaf2c575a2ad7b46713a8a50bda2fc3b003a04845c05dd6" +dependencies = [ + "ark-ff-asm 0.3.0", + "ark-ff-macros 0.3.0", + "ark-serialize 0.3.0", + "ark-std 0.3.0", + "derivative", + "num-bigint", + "num-traits", + "paste", + "rustc_version 0.3.3", + "zeroize", ] [[package]] @@ -372,10 +426,10 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec847af850f44ad29048935519032c33da8aa03340876d351dfab5660d2966ba" dependencies = [ - "ark-ff-asm", - "ark-ff-macros", - "ark-serialize", - "ark-std", + "ark-ff-asm 0.4.2", + "ark-ff-macros 0.4.2", + "ark-serialize 0.4.2", + "ark-std 0.4.0", "derivative", "digest 0.10.7", "itertools 0.10.5", @@ -386,6 +440,16 @@ dependencies = [ "zeroize", ] +[[package]] +name = "ark-ff-asm" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db02d390bf6643fb404d3d22d31aee1c4bc4459600aef9113833d17e786c6e44" +dependencies = [ + "quote", + "syn 1.0.109", +] + [[package]] name = "ark-ff-asm" version = "0.4.2" @@ -396,6 +460,18 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "ark-ff-macros" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db2fd794a08ccb318058009eefdf15bcaaaaf6f8161eb3345f907222bac38b20" +dependencies = [ + "num-bigint", + "num-traits", + "quote", + "syn 1.0.109", +] + [[package]] name = "ark-ff-macros" version = "0.4.2" @@ -416,9 +492,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3e9eab5d4b5ff2f228b763d38442adc9b084b0a465409b059fac5c2308835ec2" dependencies = [ "ark-ec", - "ark-ff", - "ark-serialize", - "ark-std", + "ark-ff 0.4.2", + "ark-serialize 0.4.2", + "ark-std 0.4.0", "derivative", ] @@ -428,9 +504,9 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d320bfc44ee185d899ccbadfa8bc31aab923ce1558716e1997a1e74057fe86bf" dependencies = [ - "ark-ff", - "ark-serialize", - "ark-std", + "ark-ff 0.4.2", + "ark-serialize 0.4.2", + "ark-std 0.4.0", "derivative", "hashbrown 0.13.2", ] @@ -442,9 +518,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "de1d1472e5cb020cb3405ce2567c91c8d43f21b674aef37b0202f5c3304761db" dependencies = [ "ark-ec", - "ark-ff", + "ark-ff 0.4.2", "ark-relations", - "ark-std", + "ark-std 0.4.0", "derivative", "num-bigint", "num-integer", @@ -458,8 +534,8 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "00796b6efc05a3f48225e59cb6a2cda78881e7c390872d5786aaf112f31fb4f0" dependencies = [ - "ark-ff", - "ark-std", + "ark-ff 0.4.2", + "ark-std 0.4.0", "tracing", "tracing-subscriber 0.2.25", ] @@ -471,9 +547,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51bd73bb6ddb72630987d37fa963e99196896c0d0ea81b7c894567e74a2f83af" dependencies = [ "ark-ec", - "ark-ff", - "ark-serialize", - "ark-std", + "ark-ff 0.4.2", + "ark-serialize 0.4.2", + "ark-std 0.4.0", "parity-scale-codec", "scale-info", ] @@ -485,13 +561,23 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f69c00b3b529be29528a6f2fd5fa7b1790f8bed81b9cdca17e326538545a179" dependencies = [ "ark-ec", - "ark-ff", - "ark-serialize", - "ark-std", + "ark-ff 0.4.2", + "ark-serialize 0.4.2", + "ark-std 0.4.0", "parity-scale-codec", "scale-info", ] +[[package]] +name = "ark-serialize" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d6c2b318ee6e10f8c2853e73a83adc0ccb88995aa978d8a3408d492ab2ee671" +dependencies = [ + "ark-std 0.3.0", + "digest 0.9.0", +] + [[package]] name = "ark-serialize" version = "0.4.2" @@ -499,7 +585,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "adb7b85a02b83d2f22f89bd5cac66c9c89474240cb6207cb1efc16d098e822a5" dependencies = [ "ark-serialize-derive", - "ark-std", + "ark-std 0.4.0", "digest 0.10.7", "num-bigint", ] @@ -521,10 +607,20 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "84d3cc6833a335bb8a600241889ead68ee89a3cf8448081fb7694c0fe503da63" dependencies = [ - "ark-ff", + "ark-ff 0.4.2", "ark-relations", - "ark-serialize", - "ark-std", + "ark-serialize 0.4.2", + "ark-std 0.4.0", +] + +[[package]] +name = "ark-std" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1df2c09229cbc5a028b1d70e00fdb2acee28b1055dfb5ca73eea49c5a25c4e7c" +dependencies = [ + "num-traits", + "rand", ] [[package]] @@ -829,6 +925,21 @@ dependencies = [ "syn 2.0.96", ] +[[package]] +name = "bit-set" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" + [[package]] name = "bitcoin-internals" version = "0.2.0" @@ -1327,6 +1438,19 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "const-hex" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b0485bab839b018a8f1723fc5391819fea5f8f0f32288ef8a735fd096b6160c" +dependencies = [ + "cfg-if", + "cpufeatures", + "hex", + "proptest", + "serde", +] + [[package]] name = "const-oid" version = "0.9.6" @@ -1877,7 +2001,16 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a9b99b9cbbe49445b21764dc0625032a89b145a2642e67603e1c936f5458d05" dependencies = [ - "derive_more-impl", + "derive_more-impl 1.0.0", +] + +[[package]] +name = "derive_more" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "093242cf7570c207c83073cf82f79706fe7b8317e98620a47d5be7c3d8497678" +dependencies = [ + "derive_more-impl 2.0.1", ] [[package]] @@ -1891,6 +2024,18 @@ dependencies = [ "syn 2.0.96", ] +[[package]] +name = "derive_more-impl" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", + "unicode-xid", +] + [[package]] name = "difflib" version = "0.4.0" @@ -2397,6 +2542,28 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +[[package]] +name = "fastrlp" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "139834ddba373bbdd213dffe02c8d110508dcf1726c2be27e8d1f7d7e1856418" +dependencies = [ + "arrayvec", + "auto_impl", + "bytes", +] + +[[package]] +name = "fastrlp" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce8dba4714ef14b8274c371879b175aa55b16b30f269663f19d576f380018dc4" +dependencies = [ + "arrayvec", + "auto_impl", + "bytes", +] + [[package]] name = "fc-api" version = "1.0.0-dev" @@ -4394,6 +4561,16 @@ dependencies = [ "cpufeatures", ] +[[package]] +name = "keccak-asm" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "505d1856a39b200489082f90d897c3f07c455563880bc5952e38eabf731c83b6" +dependencies = [ + "digest 0.10.7", + "sha3-asm", +] + [[package]] name = "keystream" version = "1.0.0" @@ -5813,7 +5990,7 @@ dependencies = [ name = "node-subtensor-runtime" version = "4.0.0-dev" dependencies = [ - "ark-serialize", + "ark-serialize 0.4.2", "fp-account", "fp-evm", "fp-rpc", @@ -6316,10 +6493,10 @@ dependencies = [ "ark-bls12-381", "ark-crypto-primitives", "ark-ec", - "ark-ff", + "ark-ff 0.4.2", "ark-scale 0.0.11", - "ark-serialize", - "ark-std", + "ark-serialize 0.4.2", + "ark-std 0.4.0", "frame-benchmarking", "frame-support", "frame-system", @@ -6615,7 +6792,7 @@ version = "4.0.0-dev" dependencies = [ "approx", "ark-bls12-381", - "ark-serialize", + "ark-serialize 0.4.2", "frame-benchmarking", "frame-support", "frame-system", @@ -7489,6 +7666,26 @@ dependencies = [ "syn 2.0.96", ] +[[package]] +name = "proptest" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14cae93065090804185d3b75f0bf93b8eeda30c7a9b4a33d3bdb3988d6229e50" +dependencies = [ + "bit-set", + "bit-vec", + "bitflags 2.8.0", + "lazy_static", + "num-traits", + "rand", + "rand_chacha", + "rand_xorshift", + "regex-syntax 0.8.5", + "rusty-fork", + "tempfile", + "unarray", +] + [[package]] name = "prost" version = "0.11.9" @@ -7809,6 +8006,15 @@ dependencies = [ "rand_core", ] +[[package]] +name = "rand_xorshift" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d25bf25ec5ae4a3f1b92f929810509a2f53d7dca2f50b794ff57e3face536c8f" +dependencies = [ + "rand_core", +] + [[package]] name = "raw-cpuid" version = "11.3.0" @@ -8110,6 +8316,38 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "ruint" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "825df406ec217a8116bd7b06897c6cc8f65ffefc15d030ae2c9540acc9ed50b6" +dependencies = [ + "alloy-rlp", + "ark-ff 0.3.0", + "ark-ff 0.4.2", + "bytes", + "fastrlp 0.3.1", + "fastrlp 0.4.0", + "num-bigint", + "num-integer", + "num-traits", + "parity-scale-codec", + "primitive-types 0.12.2", + "proptest", + "rand", + "rlp", + "ruint-macro", + "serde", + "valuable", + "zeroize", +] + +[[package]] +name = "ruint-macro" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48fd7bd8a6377e15ad9d42a8ec25371b94ddc67abe7c8b9127bec79bebaaae18" + [[package]] name = "rustc-demangle" version = "0.1.24" @@ -8143,6 +8381,15 @@ dependencies = [ "semver 0.9.0", ] +[[package]] +name = "rustc_version" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0dfe2087c51c460008730de8b57e6a320782fbfb312e1f4d520e6c6fae155ee" +dependencies = [ + "semver 0.11.0", +] + [[package]] name = "rustc_version" version = "0.4.1" @@ -8248,6 +8495,18 @@ version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4" +[[package]] +name = "rusty-fork" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb3dcc6e454c328bb824492db107ab7c0ae8fcffe4ad210136ef014458c1bc4f" +dependencies = [ + "fnv", + "quick-error", + "tempfile", + "wait-timeout", +] + [[package]] name = "rw-stream-sink" version = "0.4.0" @@ -9580,7 +9839,7 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a3186ec9e65071a2095434b1f5bb24838d4e8e130f584c790f6033c79943537" dependencies = [ - "semver-parser", + "semver-parser 0.7.0", ] [[package]] @@ -9589,7 +9848,16 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" dependencies = [ - "semver-parser", + "semver-parser 0.7.0", +] + +[[package]] +name = "semver" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f301af10236f6df4160f7c3f04eec6dbc70ace82d23326abad5edee88801c6b6" +dependencies = [ + "semver-parser 0.10.3", ] [[package]] @@ -9607,6 +9875,15 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" +[[package]] +name = "semver-parser" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9900206b54a3527fdc7b8a938bffd94a568bac4f4aa8113b209df75a09c0dec2" +dependencies = [ + "pest", +] + [[package]] name = "send_wrapper" version = "0.6.0" @@ -9798,6 +10075,16 @@ dependencies = [ "keccak", ] +[[package]] +name = "sha3-asm" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c28efc5e327c837aa837c59eae585fc250715ef939ac32881bcc11677cd02d46" +dependencies = [ + "cc", + "cfg-if", +] + [[package]] name = "sharded-slab" version = "0.1.7" @@ -11268,6 +11555,7 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" name = "swap" version = "0.1.0" dependencies = [ + "alloy-primitives", "safe-math", "sp-arithmetic", "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-stable2409)", @@ -11540,10 +11828,10 @@ dependencies = [ "ark-bls12-377", "ark-bls12-381", "ark-ec", - "ark-ff", + "ark-ff 0.4.2", "ark-poly", - "ark-serialize", - "ark-std", + "ark-serialize 0.4.2", + "ark-std 0.4.0", "array-bytes", "chacha20poly1305", "generic-array 0.14.7", @@ -11990,6 +12278,12 @@ dependencies = [ "static_assertions", ] +[[package]] +name = "unarray" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" + [[package]] name = "unicode-bidi" version = "0.3.18" @@ -12141,8 +12435,8 @@ dependencies = [ "ark-bls12-377", "ark-bls12-381", "ark-ec", - "ark-ff", - "ark-serialize", + "ark-ff 0.4.2", + "ark-serialize 0.4.2", "ark-serialize-derive", "arrayref", "constcat", @@ -12156,6 +12450,15 @@ dependencies = [ "zeroize", ] +[[package]] +name = "wait-timeout" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ac3b126d3914f9849036f826e054cbabdc8519970b8998ddaf3b5bd3c65f11" +dependencies = [ + "libc", +] + [[package]] name = "walkdir" version = "2.5.0" diff --git a/Cargo.toml b/Cargo.toml index 1e5c417d8d..d3cedd96d0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -99,6 +99,7 @@ proc-macro2 = { version = "1", features = ["span-locations"] } thiserror = "1.0" walkdir = "2" approx = "0.5" +alloy-primitives = { version = "0.8.23", default-features = false } subtensor-macros = { path = "support/macros" } diff --git a/primitives/safe-math/src/lib.rs b/primitives/safe-math/src/lib.rs index 8069133f92..eda964e678 100644 --- a/primitives/safe-math/src/lib.rs +++ b/primitives/safe-math/src/lib.rs @@ -177,6 +177,27 @@ pub trait FixedExt: Fixed { ln_x.checked_div(ln_base) } + /// Returns the largest integer less than or equal to the fixed-point number. + fn checked_floor(&self) -> Option { + // Approach using the integer and fractional parts + if *self >= Self::from_num(0) { + // For non-negative numbers, simply return the integer part + return Some(Self::from_num(self.int())); + } + + // For negative numbers + let int_part = self.int(); + let frac_part = self.frac(); + + if frac_part == Self::from_num(0) { + // No fractional part, return as is + return Some(*self); + } + + // Has fractional part, we need to round down + int_part.checked_sub(Self::from_num(1)) + } + fn abs_diff(&self, b: Self) -> Self { if *self < b { b.saturating_sub(*self) @@ -321,4 +342,33 @@ mod tests { // Log with base 1 should return None assert!(x.checked_log(I64F64::from_num(1.0)).is_none()); } + + #[test] + fn test_checked_floor() { + // Test cases: (input, expected floor result) + let test_cases = [ + // Positive and negative integers (should remain unchanged) + (0.0, 0.0), + (1.0, 1.0), + (5.0, 5.0), + (-1.0, -1.0), + (-5.0, -5.0), + // Positive fractions (should truncate to integer part) + (0.5, 0.0), + (1.5, 1.0), + (3.75, 3.0), + (9.999, 9.0), + // Negative fractions (should round down to next integer) + (-0.1, -1.0), + (-1.5, -2.0), + (-3.75, -4.0), + (-9.999, -10.0), + ]; + + for &(input, expected) in &test_cases { + let x = I64F64::from_num(input); + let expected = I64F64::from_num(expected); + assert_eq!(x.checked_floor().unwrap(), expected,); + } + } } diff --git a/primitives/swap/Cargo.toml b/primitives/swap/Cargo.toml index c5d2dc0361..bd377cbaa3 100644 --- a/primitives/swap/Cargo.toml +++ b/primitives/swap/Cargo.toml @@ -4,10 +4,11 @@ version = "0.1.0" edition = { workspace = true } [dependencies] -substrate-fixed = { workspace = true } +alloy-primitives = { workspace = true } +safe-math = { workspace = true } sp-arithmetic = { workspace = true } sp-std = { workspace = true } -safe-math = { workspace = true } +substrate-fixed = { workspace = true } [lints] workspace = true @@ -15,8 +16,9 @@ workspace = true [features] default = ["std"] std = [ - "substrate-fixed/std", - "sp-std/std", + "alloy-primitives/std", "safe-math/std", "sp-arithmetic/std", + "sp-std/std", + "substrate-fixed/std", ] diff --git a/primitives/swap/src/lib.rs b/primitives/swap/src/lib.rs index 225e80350d..c6bb400f36 100644 --- a/primitives/swap/src/lib.rs +++ b/primitives/swap/src/lib.rs @@ -1,8 +1,14 @@ +use core::marker::PhantomData; + use safe_math::*; use substrate_fixed::types::U64F64; -/// The width of a single price tick. Expressed in rao units. -pub const TICK_SPACING: u64 = 10_000; +use self::tick_math::{ + TickMathError, get_sqrt_ratio_at_tick, get_tick_at_sqrt_ratio, u64f64_to_u256_q64_96, + u256_q64_96_to_u64f64, +}; + +mod tick_math; type SqrtPrice = U64F64; @@ -30,6 +36,7 @@ struct RemoveLiquidityResult { /// fees_tao - fees accrued by the position in quote currency (TAO) /// fees_alpha - fees accrued by the position in base currency (Alpha) /// +#[cfg_attr(test, derive(Debug, PartialEq))] struct Position { tick_low: u64, tick_high: u64, @@ -40,22 +47,13 @@ struct Position { impl Position { /// Converts tick index into SQRT of price - pub fn tick_index_to_sqrt_price(tick_idx: u64) -> SqrtPrice { - // python: (1 + self.tick_spacing) ** (i / 2) - let tick_spacing_tao = SqrtPrice::from_num(TICK_SPACING).saturating_div(SqrtPrice::from_num(1e9)) - + SqrtPrice::from_num(1.0); - - tick_spacing_tao - .checked_pow(tick_idx / 2) - .unwrap_or_default() + pub fn tick_index_to_sqrt_price(tick_idx: i32) -> Result { + get_sqrt_ratio_at_tick(tick_idx).and_then(u256_q64_96_to_u64f64) } /// Converts SQRT price to tick index - pub fn sqrt_price_to_tick_index(sqrt_price: SqrtPrice) -> u64 { - let tick_spacing_tao = SqrtPrice::from_num(TICK_SPACING).saturating_div(SqrtPrice::from_num(1e9)) - + SqrtPrice::from_num(1.0); - // python: math.floor(math.log(sqrt_p) / math.log(1 + self.tick_spacing)) * 2 - todo!() + pub fn sqrt_price_to_tick_index(sqrt_price: SqrtPrice) -> Result { + get_tick_at_sqrt_ratio(u64f64_to_u256_q64_96(sqrt_price)) } /// Converts position to token amounts @@ -63,29 +61,30 @@ impl Position { /// returns tuple of (TAO, Alpha) /// pub fn to_token_amounts(self, current_tick: u64) -> (u64, u64) { - let one = 1.into(); - - let sqrt_price_curr = Self::tick_index_to_sqrt_price(current_tick); - let sqrt_pa = Self::tick_index_to_sqrt_price(self.tick_low); - let sqrt_pb = Self::tick_index_to_sqrt_price(self.tick_high); - - if sqrt_price_curr < sqrt_pa { - ( - liquidity - .saturating_mul(one.safe_div(sqrt_pa).saturating_sub(one.safe_div(sqrt_pb))), - 0, - ) - } else if sqrt_price_curr > sqrt_pb { - (0, liquidity.saturating_mul(sqrt_pb.saturating_sub(sqrt_pa))) - } else { - ( - liquidity.saturating_mul( - one.save_div(sqrt_price_curr) - .saturating_sub(one.safe_div(sqrt_pb)), - ), - liquidity.saturating_mul(sqrt_price_curr.saturating_sub(sqrt_pa)), - ) - } + // let one = 1.into(); + + // let sqrt_price_curr = Self::tick_index_to_sqrt_price(current_tick); + // let sqrt_pa = Self::tick_index_to_sqrt_price(self.tick_low); + // let sqrt_pb = Self::tick_index_to_sqrt_price(self.tick_high); + + // if sqrt_price_curr < sqrt_pa { + // ( + // liquidity + // .saturating_mul(one.safe_div(sqrt_pa).saturating_sub(one.safe_div(sqrt_pb))), + // 0, + // ) + // } else if sqrt_price_curr > sqrt_pb { + // (0, liquidity.saturating_mul(sqrt_pb.saturating_sub(sqrt_pa))) + // } else { + // ( + // liquidity.saturating_mul( + // one.save_div(sqrt_price_curr) + // .saturating_sub(one.safe_div(sqrt_pb)), + // ), + // liquidity.saturating_mul(sqrt_price_curr.saturating_sub(sqrt_pa)), + // ) + // } + todo!() } } @@ -148,24 +147,25 @@ pub trait SwapDataOperations { pub struct Swap where AccountIdType: Eq, - Ops: SwapDataOperations, + Ops: SwapDataOperations, { state_ops: Ops, - phantom_key: marker::PhantomData, + phantom_key: PhantomData, } impl Swap where AccountIdType: Eq, - Ops: SwapDataOperations, + Ops: SwapDataOperations, { pub fn new(ops: Ops) -> Self { - if !ops.is_v3_initialized() { - // TODO: Initialize the v3 - // Set price, set initial (protocol owned) liquidity and positions, etc. - } + // if !ops.is_v3_initialized() { + // // TODO: Initialize the v3 + // // Set price, set initial (protocol owned) liquidity and positions, etc. + // } - Swap { state_ops: ops } + // Swap { state_ops: ops } + todo!() } /// Auxiliary method to calculate Alpha amount to match given TAO @@ -174,9 +174,8 @@ where /// Returns (Alpha, Liquidity) tuple /// pub fn get_tao_based_liquidity(&self, tao: u64) -> (u64, u64) { - let current_price = self.state_ops.get_alpha_sqrt_price(); - - // TODO + // let current_price = self.state_ops.get_alpha_sqrt_price(); + todo!() } /// Auxiliary method to calculate TAO amount to match given Alpha @@ -185,64 +184,64 @@ where /// Returns (TAO, Liquidity) tuple /// pub fn get_alpha_based_liquidity(&self, alpha: u64) -> (u64, u64) { - let current_price = self.state_ops.get_alpha_sqrt_price(); + // let current_price = self.state_ops.get_alpha_sqrt_price(); - // TODO + todo!() } /// Add liquidity at tick index. Creates new tick if it doesn't exist /// fn add_liquidity_at_index(tick_index: u64, liquidity: u64, upper: bool) { // Calculate net liquidity addition - let net_addition = if upper { - (liquidity as i128).neg() - } else { - liquidity as i128 - } - - // Find tick by index - let new_tick = if let Some(tick) = self.state_ops.get_tick_by_index(tick_index) { - tick.liquidity_net = tick.liquidity_net.saturating_add(net_addition); - tick.liquidity_gross = tick.liquidity_gross.saturating_add(liquidity); - } else { - // Create a new tick - Tick { - liquidity_net: net_addition, - liquidity_gross: liquidity, - fees_out_tao: 0_u64, - fees_out_alpha: 0_u64, - } - } - - // TODO: Review why Python code uses this code to find index for the new ticks: - // self.get_tick_index(user_position[0]) + 1 - self.state_ops.insert_tick_by_index(tick_index, new_tick); + // let net_addition = if upper { + // (liquidity as i128).neg() + // } else { + // liquidity as i128 + // } + + // // Find tick by index + // let new_tick = if let Some(tick) = self.state_ops.get_tick_by_index(tick_index) { + // tick.liquidity_net = tick.liquidity_net.saturating_add(net_addition); + // tick.liquidity_gross = tick.liquidity_gross.saturating_add(liquidity); + // } else { + // // Create a new tick + // Tick { + // liquidity_net: net_addition, + // liquidity_gross: liquidity, + // fees_out_tao: 0_u64, + // fees_out_alpha: 0_u64, + // } + // } + + // // TODO: Review why Python code uses this code to find index for the new ticks: + // // self.get_tick_index(user_position[0]) + 1 + // self.state_ops.insert_tick_by_index(tick_index, new_tick); } /// Remove liquidity at tick index. /// fn remove_liquidity_at_index(tick_index: u64, liquidity: u64, upper: bool) { - // Calculate net liquidity addition - let net_reduction = if upper { - (liquidity as i128).neg() - } else { - liquidity as i128 - } - - // Find tick by index - let new_tick = if let Some(tick) = self.state_ops.get_tick_by_index(tick_index) { - tick.liquidity_net = tick.liquidity_net.saturating_sub(net_reduction); - tick.liquidity_gross = tick.liquidity_gross.saturating_sub(liquidity); - } - - // If any liquidity is left at the tick, update it, otherwise remove - if tick.liquidity_gross == 0 { - self.state_ops.remove_tick(tick_index); - } else { - self.state_ops.insert_tick_by_index(tick_index, new_tick); - } + // // Calculate net liquidity addition + // let net_reduction = if upper { + // (liquidity as i128).neg() + // } else { + // liquidity as i128 + // } + + // // Find tick by index + // let new_tick = if let Some(tick) = self.state_ops.get_tick_by_index(tick_index) { + // tick.liquidity_net = tick.liquidity_net.saturating_sub(net_reduction); + // tick.liquidity_gross = tick.liquidity_gross.saturating_sub(liquidity); + // } + + // // If any liquidity is left at the tick, update it, otherwise remove + // if tick.liquidity_gross == 0 { + // self.state_ops.remove_tick(tick_index); + // } else { + // self.state_ops.insert_tick_by_index(tick_index, new_tick); + // } } - + /// Add liquidity /// /// The added liquidity amount can be calculated from TAO and Alpha @@ -256,114 +255,116 @@ where account_id: &AccountIdType, tick_low: u64, tick_high: u64, - liquidity: u64 + liquidity: u64, ) -> Result { - // Check if we can add a position - let position_count = self.state_ops.get_position_count(account_id); - let max_positions = get_max_positions(); - if position_count >= max_positions { - return Err(()); - } - - // Add liquidity at tick - self.add_liquidity_at_index(tick_low, liquidity, false); - self.add_liquidity_at_index(tick_high, liquidity, true); - - // Update current tick and liquidity - // TODO: Review why python uses this code to get the new tick index: - // k = self.get_tick_index(i) - let current_price = self.state_ops.get_alpha_sqrt_price(); - let current_tick_index = Position::sqrt_price_to_tick_index(current_price); - - // Update current tick liquidity - if (tick_low <= current_tick_index) && (current_tick_index <= tick_high) { - let new_current_liquidity = self.state_ops.get_current_liquidity() - .saturating_add(liquidity); - self.state_ops.set_current_liquidity(new_current_liquidity); - } - - // Update balances - let position = Position { - tick_low, - tick_high, - liquidity, - fees_tao: 0_u64, - fees_alpha: 0_u64, - } - let (tao, alpha) = position.to_token_amounts(current_tick_index); - self.state_ops.withdraw_balances(account_id, tao, alpha); - - // Update reserves - let new_tao_reserve = self.state_ops.get_tao_reserve().saturating_add(tao); - self.state_ops.set_tao_reserve(new_tao_reserve); - let new_alpha_reserve = self.get_alpha_reserve().saturating_add(alpha); - self.state_ops.set_alpha_reserve(new_alpha); - - // Update user positions - let position_id = position_count.saturating_add(1); - self.state_ops.set_position(account_id, position_id, position); - - Ok(liquidity) + // // Check if we can add a position + // let position_count = self.state_ops.get_position_count(account_id); + // let max_positions = get_max_positions(); + // if position_count >= max_positions { + // return Err(()); + // } + + // // Add liquidity at tick + // self.add_liquidity_at_index(tick_low, liquidity, false); + // self.add_liquidity_at_index(tick_high, liquidity, true); + + // // Update current tick and liquidity + // // TODO: Review why python uses this code to get the new tick index: + // // k = self.get_tick_index(i) + // let current_price = self.state_ops.get_alpha_sqrt_price(); + // let current_tick_index = Position::sqrt_price_to_tick_index(current_price); + + // // Update current tick liquidity + // if (tick_low <= current_tick_index) && (current_tick_index <= tick_high) { + // let new_current_liquidity = self.state_ops.get_current_liquidity() + // .saturating_add(liquidity); + // self.state_ops.set_current_liquidity(new_current_liquidity); + // } + + // // Update balances + // let position = Position { + // tick_low, + // tick_high, + // liquidity, + // fees_tao: 0_u64, + // fees_alpha: 0_u64, + // } + // let (tao, alpha) = position.to_token_amounts(current_tick_index); + // self.state_ops.withdraw_balances(account_id, tao, alpha); + + // // Update reserves + // let new_tao_reserve = self.state_ops.get_tao_reserve().saturating_add(tao); + // self.state_ops.set_tao_reserve(new_tao_reserve); + // let new_alpha_reserve = self.get_alpha_reserve().saturating_add(alpha); + // self.state_ops.set_alpha_reserve(new_alpha); + + // // Update user positions + // let position_id = position_count.saturating_add(1); + // self.state_ops.set_position(account_id, position_id, position); + + // Ok(liquidity) + todo!() } /// Remove liquidity and credit balances back to account_id - /// + /// /// Account ID and Position ID identify position in the storage map - /// + /// pub fn remove_liquidity( &self, account_id: &AccountIdType, position_id: u16, ) -> Result { // Check if position exists - if let Some(pos) = self.state_ops.get_position(account_id, position_id) { - // Get current price - let current_price = self.state_ops.get_alpha_sqrt_price(); - let current_tick_index = Position::sqrt_price_to_tick_index(current_price); - - // Collect fees and get tao and alpha amounts - let (fee_tao, fee_alpha) = self.collect_fees(pos); - let (tao, alpha) = pos.to_token_amounts(current_tick_index); - - // Update liquidity at position ticks - self.remove_liquidity_at_index(pos.tick_low, pos.liquidity, false); - self.remove_liquidity_at_index(pos.tick_high, pos.liquidity, true); - - // Update current tick liquidity - if (pos.tick_low <= current_tick_index) && (current_tick_index <= pos.tick_high) { - let new_current_liquidity = self.state_ops.get_current_liquidity() - .saturating_sub(liquidity); - self.state_ops.set_current_liquidity(new_current_liquidity); - } - - // Remove user position - self.state_ops.remove_position(account_id, position_id); - - // Update current price (why?) - // i = self.sqrt_price_to_tick(self.sqrt_price_curr) - // k = self.get_tick_index(i) - // self.i_curr = self.active_ticks[k] - todo!(); - - // Update reserves - let new_tao_reserve = self.state_ops.get_tao_reserve().saturating_sub(tao); - self.state_ops.set_tao_reserve(new_tao_reserve); - let new_alpha_reserve = self.get_alpha_reserve().saturating_sub(alpha); - self.state_ops.set_alpha_reserve(new_alpha); - - // Return Ok result - Ok(RemoveLiquidityResult{ - tao, - alpha, - fee_tao, - fee_alpha, - }) - } else { - // Position doesn't exist - Err(()) - } + // if let Some(pos) = self.state_ops.get_position(account_id, position_id) { + // // Get current price + // let current_price = self.state_ops.get_alpha_sqrt_price(); + // let current_tick_index = Position::sqrt_price_to_tick_index(current_price); + + // // Collect fees and get tao and alpha amounts + // let (fee_tao, fee_alpha) = self.collect_fees(pos); + // let (tao, alpha) = pos.to_token_amounts(current_tick_index); + + // // Update liquidity at position ticks + // self.remove_liquidity_at_index(pos.tick_low, pos.liquidity, false); + // self.remove_liquidity_at_index(pos.tick_high, pos.liquidity, true); + + // // Update current tick liquidity + // if (pos.tick_low <= current_tick_index) && (current_tick_index <= pos.tick_high) { + // let new_current_liquidity = self.state_ops.get_current_liquidity() + // .saturating_sub(liquidity); + // self.state_ops.set_current_liquidity(new_current_liquidity); + // } + + // // Remove user position + // self.state_ops.remove_position(account_id, position_id); + + // // Update current price (why?) + // // i = self.sqrt_price_to_tick(self.sqrt_price_curr) + // // k = self.get_tick_index(i) + // // self.i_curr = self.active_ticks[k] + // todo!(); + + // // Update reserves + // let new_tao_reserve = self.state_ops.get_tao_reserve().saturating_sub(tao); + // self.state_ops.set_tao_reserve(new_tao_reserve); + // let new_alpha_reserve = self.get_alpha_reserve().saturating_sub(alpha); + // self.state_ops.set_alpha_reserve(new_alpha); + + // // Return Ok result + // Ok(RemoveLiquidityResult{ + // tao, + // alpha, + // fee_tao, + // fee_alpha, + // }) + // } else { + // // Position doesn't exist + // Err(()) + // } + todo!() } - + /// Perform a swap /// /// Returns a tuple (amount, refund), where amount is the resulting paid out amount @@ -374,7 +375,7 @@ where amount: u64, sqrt_price_limit: SqrtPrice, ) -> (u64, u64) { - // TODO + todo!() } /// Updates position @@ -393,10 +394,66 @@ where // fee0 = liquidity * fee0 // fee1 = liquidity * fee1 - // return fee0, fee1 todo!() } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_tick_index_to_sqrt_price() { + let tick_spacing = SqrtPrice::from_num(1.0001); + + // At tick index 0, the sqrt price should be 1.0 + let sqrt_price = Position::tick_index_to_sqrt_price(0).unwrap(); + assert_eq!(sqrt_price, SqrtPrice::from_num(1.0)); + + let sqrt_price = Position::tick_index_to_sqrt_price(2).unwrap(); + assert_eq!(sqrt_price, tick_spacing); + + let sqrt_price = Position::tick_index_to_sqrt_price(4).unwrap(); + // Calculate the expected value: (1 + TICK_SPACING/1e9 + 1.0)^2 + let expected = tick_spacing * tick_spacing; + assert_eq!(sqrt_price, expected); + // Test with tick index 10 + let sqrt_price = Position::tick_index_to_sqrt_price(10).unwrap(); + // Calculate the expected value: (1 + TICK_SPACING/1e9 + 1.0)^5 + let expected_sqrt_price_10 = tick_spacing.checked_pow(5).unwrap(); + assert_eq!(sqrt_price, expected_sqrt_price_10); + } + + #[test] + fn test_sqrt_price_to_tick_index() { + let tick_spacing = SqrtPrice::from_num(1.0001); + let tick_index = Position::sqrt_price_to_tick_index(SqrtPrice::from_num(1.0)).unwrap(); + assert_eq!(tick_index, 0); + + // Test with sqrt price equal to tick_spacing_tao (should be tick index 2) + let tick_index = Position::sqrt_price_to_tick_index(tick_spacing).unwrap(); + assert_eq!(tick_index, 2); + + // Test with sqrt price equal to tick_spacing_tao^2 (should be tick index 4) + let sqrt_price = tick_spacing * tick_spacing; + let tick_index = Position::sqrt_price_to_tick_index(sqrt_price).unwrap(); + assert_eq!(tick_index, 4); + + // Test with sqrt price equal to tick_spacing_tao^5 (should be tick index 10) + let sqrt_price = tick_spacing.checked_pow(5).unwrap(); + let tick_index = Position::sqrt_price_to_tick_index(sqrt_price).unwrap(); + assert_eq!(tick_index, 10); + } + + #[test] + fn test_roundtrip_tick_index_sqrt_price() { + for tick_index in [0, 2, 4, 10, 100, 1000].iter() { + let sqrt_price = Position::tick_index_to_sqrt_price(*tick_index).unwrap(); + let round_trip_tick_index = Position::sqrt_price_to_tick_index(sqrt_price).unwrap(); + assert_eq!(round_trip_tick_index, *tick_index); + } + } } diff --git a/primitives/swap/src/tick_math.rs b/primitives/swap/src/tick_math.rs new file mode 100644 index 0000000000..685b98eb3a --- /dev/null +++ b/primitives/swap/src/tick_math.rs @@ -0,0 +1,523 @@ +//! This module is adopted from github.com/0xKitsune/uniswap-v3-math +use core::error::Error; +use core::fmt; +use core::ops::{BitOr, Neg, Shl, Shr}; + +use alloy_primitives::{I256, U256}; +use substrate_fixed::types::U64F64; + +const U256_1: U256 = U256::from_limbs([1, 0, 0, 0]); +const U256_2: U256 = U256::from_limbs([2, 0, 0, 0]); +const U256_3: U256 = U256::from_limbs([3, 0, 0, 0]); +const U256_4: U256 = U256::from_limbs([4, 0, 0, 0]); +const U256_5: U256 = U256::from_limbs([5, 0, 0, 0]); +const U256_6: U256 = U256::from_limbs([6, 0, 0, 0]); +const U256_7: U256 = U256::from_limbs([7, 0, 0, 0]); +const U256_8: U256 = U256::from_limbs([8, 0, 0, 0]); +const U256_15: U256 = U256::from_limbs([15, 0, 0, 0]); +const U256_16: U256 = U256::from_limbs([16, 0, 0, 0]); +const U256_32: U256 = U256::from_limbs([32, 0, 0, 0]); +const U256_64: U256 = U256::from_limbs([64, 0, 0, 0]); +const U256_127: U256 = U256::from_limbs([127, 0, 0, 0]); +const U256_128: U256 = U256::from_limbs([128, 0, 0, 0]); +const U256_255: U256 = U256::from_limbs([255, 0, 0, 0]); + +const U256_256: U256 = U256::from_limbs([256, 0, 0, 0]); +const U256_512: U256 = U256::from_limbs([512, 0, 0, 0]); +const U256_1024: U256 = U256::from_limbs([1024, 0, 0, 0]); +const U256_2048: U256 = U256::from_limbs([2048, 0, 0, 0]); +const U256_4096: U256 = U256::from_limbs([4096, 0, 0, 0]); +const U256_8192: U256 = U256::from_limbs([8192, 0, 0, 0]); +const U256_16384: U256 = U256::from_limbs([16384, 0, 0, 0]); +const U256_32768: U256 = U256::from_limbs([32768, 0, 0, 0]); +const U256_65536: U256 = U256::from_limbs([65536, 0, 0, 0]); +const U256_131072: U256 = U256::from_limbs([131072, 0, 0, 0]); +const U256_262144: U256 = U256::from_limbs([262144, 0, 0, 0]); +const U256_524288: U256 = U256::from_limbs([524288, 0, 0, 0]); + +const U256_MAX_TICK: U256 = U256::from_limbs([887272, 0, 0, 0]); + +const MIN_TICK: i32 = -887272; +const MAX_TICK: i32 = -MIN_TICK; + +const MIN_SQRT_RATIO: U256 = U256::from_limbs([4295128739, 0, 0, 0]); +const MAX_SQRT_RATIO: U256 = + U256::from_limbs([6743328256752651558, 17280870778742802505, 4294805859, 0]); + +const SQRT_10001: I256 = I256::from_raw(U256::from_limbs([11745905768312294533, 13863, 0, 0])); +const TICK_LOW: I256 = I256::from_raw(U256::from_limbs([ + 6552757943157144234, + 184476617836266586, + 0, + 0, +])); +const TICK_HIGH: I256 = I256::from_raw(U256::from_limbs([ + 4998474450511881007, + 15793544031827761793, + 0, + 0, +])); + +pub(crate) fn get_sqrt_ratio_at_tick(tick: i32) -> Result { + let abs_tick = if tick < 0 { + U256::from(tick.neg()) + } else { + U256::from(tick) + }; + + if abs_tick > U256_MAX_TICK { + return Err(TickMathError::TickTooHigh); + } + + let mut ratio = if abs_tick & (U256_1) != U256::ZERO { + U256::from_limbs([12262481743371124737, 18445821805675392311, 0, 0]) + } else { + U256::from_limbs([0, 0, 1, 0]) + }; + + if !(abs_tick & U256_2).is_zero() { + ratio = (ratio * U256::from_limbs([6459403834229662010, 18444899583751176498, 0, 0])) >> 128 + } + if !(abs_tick & U256_4).is_zero() { + ratio = + (ratio * U256::from_limbs([17226890335427755468, 18443055278223354162, 0, 0])) >> 128 + } + if !(abs_tick & U256_8).is_zero() { + ratio = (ratio * U256::from_limbs([2032852871939366096, 18439367220385604838, 0, 0])) >> 128 + } + if !(abs_tick & U256_16).is_zero() { + ratio = + (ratio * U256::from_limbs([14545316742740207172, 18431993317065449817, 0, 0])) >> 128 + } + if !(abs_tick & U256_32).is_zero() { + ratio = (ratio * U256::from_limbs([5129152022828963008, 18417254355718160513, 0, 0])) >> 128 + } + if !(abs_tick & U256_64).is_zero() { + ratio = (ratio * U256::from_limbs([4894419605888772193, 18387811781193591352, 0, 0])) >> 128 + } + if !(abs_tick & U256_128).is_zero() { + ratio = (ratio * U256::from_limbs([1280255884321894483, 18329067761203520168, 0, 0])) >> 128 + } + if !(abs_tick & U256_256).is_zero() { + ratio = + (ratio * U256::from_limbs([15924666964335305636, 18212142134806087854, 0, 0])) >> 128 + } + if !(abs_tick & U256_512).is_zero() { + ratio = (ratio * U256::from_limbs([8010504389359918676, 17980523815641551639, 0, 0])) >> 128 + } + if !(abs_tick & U256_1024).is_zero() { + ratio = + (ratio * U256::from_limbs([10668036004952895731, 17526086738831147013, 0, 0])) >> 128 + } + if !(abs_tick & U256_2048).is_zero() { + ratio = (ratio * U256::from_limbs([4878133418470705625, 16651378430235024244, 0, 0])) >> 128 + } + if !(abs_tick & U256_4096).is_zero() { + ratio = (ratio * U256::from_limbs([9537173718739605541, 15030750278693429944, 0, 0])) >> 128 + } + if !(abs_tick & U256_8192).is_zero() { + ratio = (ratio * U256::from_limbs([9972618978014552549, 12247334978882834399, 0, 0])) >> 128 + } + if !(abs_tick & U256_16384).is_zero() { + ratio = (ratio * U256::from_limbs([10428997489610666743, 8131365268884726200, 0, 0])) >> 128 + } + if !(abs_tick & U256_32768).is_zero() { + ratio = (ratio * U256::from_limbs([9305304367709015974, 3584323654723342297, 0, 0])) >> 128 + } + if !(abs_tick & U256_65536).is_zero() { + ratio = (ratio * U256::from_limbs([14301143598189091785, 696457651847595233, 0, 0])) >> 128 + } + if !(abs_tick & U256_131072).is_zero() { + ratio = (ratio * U256::from_limbs([7393154844743099908, 26294789957452057, 0, 0])) >> 128 + } + if !(abs_tick & U256_262144).is_zero() { + ratio = (ratio * U256::from_limbs([2209338891292245656, 37481735321082, 0, 0])) >> 128 + } + if !(abs_tick & U256_524288).is_zero() { + ratio = (ratio * U256::from_limbs([10518117631919034274, 76158723, 0, 0])) >> 128 + } + + if tick > 0 { + ratio = U256::MAX / ratio; + } + + Ok((ratio >> 32) + + if (ratio.wrapping_rem(U256_1 << 32)).is_zero() { + U256::ZERO + } else { + U256_1 + }) +} + +pub(crate) fn get_tick_at_sqrt_ratio(sqrt_price_x_96: U256) -> Result { + if !(sqrt_price_x_96 >= MIN_SQRT_RATIO && sqrt_price_x_96 < MAX_SQRT_RATIO) { + return Err(TickMathError::SqrtPriceOutOfBounds); + } + + let ratio: U256 = sqrt_price_x_96.shl(32); + let mut r = ratio; + let mut msb = U256::ZERO; + + let mut f = if r > U256::from_limbs([18446744073709551615, 18446744073709551615, 0, 0]) { + U256_1.shl(U256_7) + } else { + U256::ZERO + }; + msb = msb.bitor(f); + r = r.shr(f); + + f = if r > U256::from_limbs([18446744073709551615, 0, 0, 0]) { + U256_1.shl(U256_6) + } else { + U256::ZERO + }; + msb = msb.bitor(f); + r = r.shr(f); + + f = if r > U256::from_limbs([4294967295, 0, 0, 0]) { + U256_1.shl(U256_5) + } else { + U256::ZERO + }; + msb = msb.bitor(f); + r = r.shr(f); + + f = if r > U256::from_limbs([65535, 0, 0, 0]) { + U256_1.shl(U256_4) + } else { + U256::ZERO + }; + msb = msb.bitor(f); + r = r.shr(f); + + f = if r > U256_255 { + U256_1.shl(U256_3) + } else { + U256::ZERO + }; + msb = msb.bitor(f); + r = r.shr(f); + + f = if r > U256_15 { + U256_1.shl(U256_2) + } else { + U256::ZERO + }; + msb = msb.bitor(f); + r = r.shr(f); + + f = if r > U256_3 { + U256_1.shl(U256_1) + } else { + U256::ZERO + }; + msb = msb.bitor(f); + r = r.shr(f); + + f = if r > U256_1 { U256_1 } else { U256::ZERO }; + + msb = msb.bitor(f); + + r = if msb >= U256_128 { + ratio.shr(msb - U256_127) + } else { + ratio.shl(U256_127 - msb) + }; + + let mut log_2: I256 = (I256::from_raw(msb) - I256::from_limbs([128, 0, 0, 0])).shl(64); + + for i in (51..=63).rev() { + r = r.overflowing_mul(r).0.shr(U256_127); + let f: U256 = r.shr(128); + log_2 = log_2.bitor(I256::from_raw(f.shl(i))); + + r = r.shr(f); + } + + r = r.overflowing_mul(r).0.shr(U256_127); + let f: U256 = r.shr(128); + log_2 = log_2.bitor(I256::from_raw(f.shl(50))); + + let log_sqrt10001 = log_2.wrapping_mul(SQRT_10001); + + let tick_low = ((log_sqrt10001 - TICK_LOW) >> 128_u8).low_i32(); + + let tick_high = ((log_sqrt10001 + TICK_HIGH) >> 128_u8).low_i32(); + + let tick = if tick_low == tick_high { + tick_low + } else if get_sqrt_ratio_at_tick(tick_high)? <= sqrt_price_x_96 { + tick_high + } else { + tick_low + }; + + Ok(tick) +} + +/// Convert U256 to U64F64 +/// +/// # Arguments +/// * `value` - The U256 value in Q64.96 format +/// +/// # Returns +/// * `Result` - Converted value or error if too large +pub(crate) fn u256_to_u64f64( + value: U256, + source_fractional_bits: u32, +) -> Result { + if value > U256::from(u128::MAX) { + return Err(TickMathError::ConversionError); + } + + let mut value: u128 = value + .try_into() + .map_err(|_| TickMathError::ConversionError)?; + + // Adjust to 64 fractional bits (U64F64 format) + if source_fractional_bits < 64 { + // Shift left to add more fractional bits + value = value + .checked_shl(64 - source_fractional_bits) + .ok_or(TickMathError::Overflow)?; + } else if source_fractional_bits > 64 { + // Shift right to remove excess fractional bits + value = value >> (source_fractional_bits - 64); + } + + Ok(U64F64::from_bits(value)) +} + +/// Convert U64F64 to U256 +/// +/// # Arguments +/// * `value` - The U64F64 value to convert +/// * `target_fractional_bits` - Number of fractional bits in the target U256 format +/// +/// # Returns +/// * `U256` - Converted value +pub(crate) fn u64f64_to_u256(value: U64F64, target_fractional_bits: u32) -> U256 { + let mut bits = value.to_bits(); + + // Adjust to target fractional bits + if target_fractional_bits < 64 { + // Shift right to remove excess fractional bits + bits = bits >> (64 - target_fractional_bits); + } else if target_fractional_bits > 64 { + // Shift left to add more fractional bits + bits = bits << (target_fractional_bits - 64); + } + + // Create U256 + U256::from(bits) +} + +/// Convert U256 in Q64.96 format (Uniswap's sqrt price format) to U64F64 +pub(crate) fn u256_q64_96_to_u64f64(value: U256) -> Result { + u256_to_u64f64(value, 96) +} + +/// Convert U64F64 to U256 in Q64.96 format (Uniswap's sqrt price format) +pub(crate) fn u64f64_to_u256_q64_96(value: U64F64) -> U256 { + u64f64_to_u256(value, 96) +} + +#[derive(Debug)] +pub enum TickMathError { + TickTooHigh, + SqrtPriceOutOfBounds, + ConversionError, + Overflow, +} + +impl fmt::Display for TickMathError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::TickTooHigh => f.write_str("The given tick must be less than, or equal to, the maximum tick"), + Self::SqrtPriceOutOfBounds =>f.write_str("Second inequality must be < because the price can never reach the price at the max tick"), + Self::ConversionError => f.write_str("Error converting from one number type into another"), + Self::Overflow => f.write_str("Number overflow in arithmetic operation") + } + } +} + +impl Error for TickMathError {} + +#[cfg(test)] +mod test { + use super::*; + use std::{ops::Sub, str::FromStr}; + + #[test] + fn test_get_sqrt_ratio_at_tick_bounds() { + // the function should return an error if the tick is out of bounds + if let Err(err) = get_sqrt_ratio_at_tick(MIN_TICK - 1) { + assert!(matches!(err, TickMathError::TickTooHigh)); + } else { + panic!("get_qrt_ratio_at_tick did not respect lower tick bound") + } + if let Err(err) = get_sqrt_ratio_at_tick(MAX_TICK + 1) { + assert!(matches!(err, TickMathError::TickTooHigh)); + } else { + panic!("get_qrt_ratio_at_tick did not respect upper tick bound") + } + } + + #[test] + fn test_get_sqrt_ratio_at_tick_values() { + // test individual values for correct results + assert_eq!( + get_sqrt_ratio_at_tick(MIN_TICK).unwrap(), + U256::from(4295128739u64), + "sqrt ratio at min incorrect" + ); + assert_eq!( + get_sqrt_ratio_at_tick(MIN_TICK + 1).unwrap(), + U256::from(4295343490u64), + "sqrt ratio at min + 1 incorrect" + ); + assert_eq!( + get_sqrt_ratio_at_tick(MAX_TICK - 1).unwrap(), + U256::from_str("1461373636630004318706518188784493106690254656249").unwrap(), + "sqrt ratio at max - 1 incorrect" + ); + assert_eq!( + get_sqrt_ratio_at_tick(MAX_TICK).unwrap(), + U256::from_str("1461446703485210103287273052203988822378723970342").unwrap(), + "sqrt ratio at max incorrect" + ); + // checking hard coded values against solidity results + assert_eq!( + get_sqrt_ratio_at_tick(50).unwrap(), + U256::from(79426470787362580746886972461u128), + "sqrt ratio at 50 incorrect" + ); + assert_eq!( + get_sqrt_ratio_at_tick(100).unwrap(), + U256::from(79625275426524748796330556128u128), + "sqrt ratio at 100 incorrect" + ); + assert_eq!( + get_sqrt_ratio_at_tick(250).unwrap(), + U256::from(80224679980005306637834519095u128), + "sqrt ratio at 250 incorrect" + ); + assert_eq!( + get_sqrt_ratio_at_tick(500).unwrap(), + U256::from(81233731461783161732293370115u128), + "sqrt ratio at 500 incorrect" + ); + assert_eq!( + get_sqrt_ratio_at_tick(1000).unwrap(), + U256::from(83290069058676223003182343270u128), + "sqrt ratio at 1000 incorrect" + ); + assert_eq!( + get_sqrt_ratio_at_tick(2500).unwrap(), + U256::from(89776708723587163891445672585u128), + "sqrt ratio at 2500 incorrect" + ); + assert_eq!( + get_sqrt_ratio_at_tick(3000).unwrap(), + U256::from(92049301871182272007977902845u128), + "sqrt ratio at 3000 incorrect" + ); + assert_eq!( + get_sqrt_ratio_at_tick(4000).unwrap(), + U256::from(96768528593268422080558758223u128), + "sqrt ratio at 4000 incorrect" + ); + assert_eq!( + get_sqrt_ratio_at_tick(5000).unwrap(), + U256::from(101729702841318637793976746270u128), + "sqrt ratio at 5000 incorrect" + ); + assert_eq!( + get_sqrt_ratio_at_tick(50000).unwrap(), + U256::from(965075977353221155028623082916u128), + "sqrt ratio at 50000 incorrect" + ); + assert_eq!( + get_sqrt_ratio_at_tick(150000).unwrap(), + U256::from(143194173941309278083010301478497u128), + "sqrt ratio at 150000 incorrect" + ); + assert_eq!( + get_sqrt_ratio_at_tick(250000).unwrap(), + U256::from(21246587762933397357449903968194344u128), + "sqrt ratio at 250000 incorrect" + ); + assert_eq!( + get_sqrt_ratio_at_tick(500000).unwrap(), + U256::from_str("5697689776495288729098254600827762987878").unwrap(), + "sqrt ratio at 500000 incorrect" + ); + assert_eq!( + get_sqrt_ratio_at_tick(738203).unwrap(), + U256::from_str("847134979253254120489401328389043031315994541").unwrap(), + "sqrt ratio at 738203 incorrect" + ); + } + + #[test] + fn test_get_tick_at_sqrt_ratio() { + //throws for too low + let result = get_tick_at_sqrt_ratio(MIN_SQRT_RATIO.sub(U256_1)); + assert_eq!( + result.unwrap_err().to_string(), + "Second inequality must be < because the price can never reach the price at the max tick" + ); + + //throws for too high + let result = get_tick_at_sqrt_ratio(MAX_SQRT_RATIO); + assert_eq!( + result.unwrap_err().to_string(), + "Second inequality must be < because the price can never reach the price at the max tick" + ); + + //ratio of min tick + let result = get_tick_at_sqrt_ratio(MIN_SQRT_RATIO).unwrap(); + assert_eq!(result, MIN_TICK); + + //ratio of min tick + 1 + let result = get_tick_at_sqrt_ratio(U256::from_str("4295343490").unwrap()).unwrap(); + assert_eq!(result, MIN_TICK + 1); + } + + #[test] + fn test_roundtrip() { + for tick_index in [0, 2, 4, 10, 100, 1000].iter() { + let sqrt_price = get_sqrt_ratio_at_tick(*tick_index).unwrap(); + let round_trip_tick_index = get_tick_at_sqrt_ratio(sqrt_price).unwrap(); + assert_eq!(round_trip_tick_index, *tick_index); + } + } + + #[test] + fn test_u256_to_u64f64_q64_96() { + // Test tick 0 (sqrt price = 1.0 * 2^96) + let tick0_sqrt_price = U256::from(1u128 << 96); + let fixed_price = u256_q64_96_to_u64f64(tick0_sqrt_price).unwrap(); + + // Should be 1.0 in U64F64 + assert_eq!(fixed_price, U64F64::from_num(1.0)); + + // Round trip back to U256 Q64.96 + let back_to_u256 = u64f64_to_u256_q64_96(fixed_price); + assert_eq!(back_to_u256, tick0_sqrt_price); + } + + #[test] + fn test_u256_with_other_formats() { + // Test with a value that has 32 fractional bits + let value_32frac = U256::from(123456789u128 << 32); // 123456789.0 in Q96.32 + let fixed_value = u256_to_u64f64(value_32frac, 32).unwrap(); + + // Should be 123456789.0 in U64F64 + assert_eq!(fixed_value, U64F64::from_num(123456789.0)); + + // Round trip back to U256 with 32 fractional bits + let back_to_u256 = u64f64_to_u256(fixed_value, 32); + assert_eq!(back_to_u256, value_32frac); + } +}